diff --git a/src/sakia/data/entities/__init__.py b/src/sakia/data/entities/__init__.py index 34c5913afe57c061dd7f5fd5718f5c859db718c8..2957409ca2da180854ab11d9ae71af0358fc5752 100644 --- a/src/sakia/data/entities/__init__.py +++ b/src/sakia/data/entities/__init__.py @@ -6,3 +6,4 @@ from .node import Node from .connection import Connection from .user_parameters import UserParameters from .app_data import AppData +from .source import Source diff --git a/src/sakia/data/entities/blockchain.py b/src/sakia/data/entities/blockchain.py index 7e5b3ef9f627a7035ee53543da2a4743475c5c53..d38821de50db6478b2eb1b1e08c8fa7e303ed68d 100644 --- a/src/sakia/data/entities/blockchain.py +++ b/src/sakia/data/entities/blockchain.py @@ -45,7 +45,7 @@ class Blockchain: # block number and hash current_buid = attr.ib(convert=block_uid, default=BlockUID.empty()) # Number of members - nb_members = attr.ib(convert=int, default=0, cmp=False, hash=False) + members_count = attr.ib(convert=int, default=0, cmp=False, hash=False) # Current monetary mass in units current_mass = attr.ib(convert=int, default=0, cmp=False, hash=False) # Median time in seconds @@ -58,5 +58,13 @@ class Blockchain: last_ud_time = attr.ib(convert=int, default=0, cmp=False, hash=False) # Previous monetary mass in units previous_mass = attr.ib(convert=int, default=0, cmp=False, hash=False) + # Previous members count + previous_members_count = attr.ib(convert=int, default=0, cmp=False, hash=False) + # Previous UD amount in units (multiply by 10^base) + previous_ud = attr.ib(convert=int, default=0, cmp=False, hash=False) + # Previous UD base + previous_ud_base = attr.ib(convert=int, default=0, cmp=False, hash=False) + # Previous UD base + previous_ud_time = attr.ib(convert=int, default=0, cmp=False, hash=False) # Currency name currency = attr.ib(convert=str, default="", cmp=False, hash=False) diff --git a/src/sakia/data/entities/source.py b/src/sakia/data/entities/source.py new file mode 100644 index 0000000000000000000000000000000000000000..3927531a7e941b29dbff39ed6f4cbcaff67e002f --- /dev/null +++ b/src/sakia/data/entities/source.py @@ -0,0 +1,13 @@ +import attr +from duniterpy.documents import block_uid + + +@attr.s() +class Source: + identifier = attr.ib(convert=str) + currency = attr.ib(convert=str) + pubkey = attr.ib(convert=str) + type = attr.ib(convert=str, validator=lambda i, a, s: s == 'T' or s == 'D') + offset = attr.ib(convert=int, cmp=False, hash=False) + amount = attr.ib(convert=int, cmp=False, hash=False) + base = attr.ib(convert=int, cmp=False, hash=False) diff --git a/src/sakia/data/processors/blockchain.py b/src/sakia/data/processors/blockchain.py index 253667d219d23989ac3d0fdf075dafffdc19eb93..5fd2b40bcef8d45e740829ae78a5ddeb2a9c65ae 100644 --- a/src/sakia/data/processors/blockchain.py +++ b/src/sakia/data/processors/blockchain.py @@ -1,6 +1,8 @@ import attr import re from ..entities import Blockchain +from .nodes import NodesProcessor +from ..connectors import BmaConnector from duniterpy.api import bma, errors from duniterpy.documents import Block, BMAEndpoint import asyncio @@ -11,6 +13,15 @@ class BlockchainProcessor: _repo = attr.ib() # :type sakia.data.repositories.CertificationsRepo _bma_connector = attr.ib() # :type sakia.data.connectors.bma.BmaConnector + @classmethod + def instanciate(cls, app): + """ + Instanciate a blockchain processor + :param sakia.app.Application app: the app + """ + return cls(app.db.blockchains_repo, + BmaConnector(NodesProcessor(app.db.nodes_repo))) + def current_buid(self, currency): """ Get the local current blockuid @@ -39,12 +50,12 @@ class BlockchainProcessor: """ return self._repo.get_one({'currency': currency}).monetary_mass - def nb_members(self, currency): + def members_count(self, currency): """ Get the number of members in the blockchain :rtype: int """ - return self._repo.get_one({'currency': currency}).nb_members + return self._repo.get_one({'currency': currency}).members_count def last_ud(self, currency): """ @@ -54,6 +65,44 @@ class BlockchainProcessor: blockchain = self._repo.get_one({'currency': currency}) return blockchain.last_ud, blockchain.last_ud_base + def last_ud_time(self, currency): + """ + Get the last ud time + :rtype: int + """ + blockchain = self._repo.get_one({'currency': currency}) + return blockchain.last_ud_time + + def previous_monetary_mass(self, currency): + """ + Get the local current monetary mass + :rtype: int + """ + return self._repo.get_one({'currency': currency}).previous_mass + + def previous_members_count(self, currency): + """ + Get the local current monetary mass + :rtype: int + """ + return self._repo.get_one({'currency': currency}).previous_members_count + + def previous_ud(self, currency): + """ + Get the previous ud value and base + :rtype: int, int + """ + blockchain = self._repo.get_one({'currency': currency}) + return blockchain.previous_ud, blockchain.previous_ud_base + + def previous_ud_time(self, currency): + """ + Get the previous ud time + :rtype: int + """ + blockchain = self._repo.get_one({'currency': currency}) + return blockchain.previous_ud_time + async def new_blocks_with_identities(self, currency): """ Get blocks more recent than local blockuid @@ -66,13 +115,13 @@ class BlockchainProcessor: bma.blockchain.Actives, bma.blockchain.Excluded, bma.blockchain.Newcomers): - future_requests.append(self._bma_connector.get(req)) + future_requests.append(self._bma_connector.get(currency, req)) results = await asyncio.gather(future_requests) for res in results: with_identities += res["result"]["blocks"] - local_current_buid = self.current_buid() + local_current_buid = self.current_buid(currency) return sorted([b for b in with_identities if b > local_current_buid.number]) async def new_blocks_with_money(self, currency): @@ -83,13 +132,13 @@ class BlockchainProcessor: with_money = [] future_requests = [] for req in (bma.blockchain.UD, bma.blockchain.TX): - future_requests.append(self._bma_connector.get(req)) + future_requests.append(self._bma_connector.get(currency, req)) results = await asyncio.gather(future_requests) for res in results: with_money += res["result"]["blocks"] - local_current_buid = self.current_buid() + local_current_buid = self.current_buid(currency) return sorted([b for b in with_money if b > local_current_buid.number]) async def blocks(self, numbers, currency): @@ -103,7 +152,7 @@ class BlockchainProcessor: to_block = max(numbers) count = to_block - from_block - blocks_data = await self._bma_connector.get(bma.blockchain.Blocks, req_args={'count': count, + blocks_data = await self._bma_connector.get(currency, bma.blockchain.Blocks, req_args={'count': count, 'from_': from_block}) blocks = [] for data in blocks_data: diff --git a/src/sakia/data/repositories/__init__.py b/src/sakia/data/repositories/__init__.py index 19a9a16683bb98b100fea58c428dc827acac72e1..ac09c47b2a82b53263a9132e61a8b7641699ac97 100644 --- a/src/sakia/data/repositories/__init__.py +++ b/src/sakia/data/repositories/__init__.py @@ -5,3 +5,4 @@ from .certifications import CertificationsRepo from .transactions import TransactionsRepo from .nodes import NodesRepo from .connections import ConnectionsRepo +from .sources import SourcesRepo diff --git a/src/sakia/data/repositories/meta.py b/src/sakia/data/repositories/meta.py index 4a6361b303629132b58c605cfd8f502d497e1113..826f2959d26e07cc36dc478a4c201f62e041c9c3 100644 --- a/src/sakia/data/repositories/meta.py +++ b/src/sakia/data/repositories/meta.py @@ -9,6 +9,7 @@ from .blockchains import BlockchainsRepo from .certifications import CertificationsRepo from .transactions import TransactionsRepo from .nodes import NodesRepo +from .sources import SourcesRepo @attr.s(frozen=True) @@ -16,12 +17,13 @@ class SakiaDatabase: """The repository for Identities entities. """ conn = attr.ib() # :type sqlite3.Connection - connections_repo = attr.ib() - identities_repo = attr.ib() - blockchains_repo = attr.ib() - certifications_repo = attr.ib() - transactions_repo = attr.ib() - nodes_repo = attr.ib() + connections_repo = attr.ib(default=None) + identities_repo = attr.ib(default=None) + blockchains_repo = attr.ib(default=None) + certifications_repo = attr.ib(default=None) + transactions_repo = attr.ib(default=None) + nodes_repo = attr.ib(default=None) + sources_repo = attr.ib(default=None) _logger = attr.ib(default=attr.Factory(lambda: logging.getLogger('sakia'))) db_file = 'sakia.db' @@ -34,7 +36,7 @@ class SakiaDatabase: detect_types=sqlite3.PARSE_DECLTYPES) meta = SakiaDatabase(con, ConnectionsRepo(con), IdentitiesRepo(con), BlockchainsRepo(con), CertificationsRepo(con), TransactionsRepo(con), - NodesRepo(con)) + NodesRepo(con), SourcesRepo(con)) meta.prepare() meta.upgrade_database() return meta diff --git a/src/sakia/data/repositories/meta.sql b/src/sakia/data/repositories/meta.sql index 5cdbe8e94a2afcba979b48bc55337c3e25240ccb..89a6f211a1132c8760aadd747b6de6b6275b8cd1 100644 --- a/src/sakia/data/repositories/meta.sql +++ b/src/sakia/data/repositories/meta.sql @@ -104,4 +104,17 @@ CREATE TABLE IF NOT EXISTS connections( PRIMARY KEY (currency, pubkey) ); +-- Cnnections TABLE +CREATE TABLE IF NOT EXISTS sources( + identifier VARCHAR(255), + currency VARCHAR(30), + pubkey VARCHAR(50), + type VARCHAR(8), + offset INT, + amount INT, + base INT, + PRIMARY KEY (identifier) + ); + + diff --git a/src/sakia/data/repositories/sources.py b/src/sakia/data/repositories/sources.py new file mode 100644 index 0000000000000000000000000000000000000000..715e1f2b5b2035c9ed1fa08540c8f765ddb486eb --- /dev/null +++ b/src/sakia/data/repositories/sources.py @@ -0,0 +1,74 @@ +import attr + +from ..entities import Source + + +@attr.s(frozen=True) +class SourcesRepo: + """The repository for Communities entities. + """ + _conn = attr.ib() # :type sqlite3.Connection + _primary_keys = (Source.identifier,) + + def insert(self, source): + """ + Commit a source to the database + :param sakia.data.entities.Source source: the source to commit + """ + with self._conn: + source_tuple = attr.astuple(source) + values = ",".join(['?'] * len(source_tuple)) + self._conn.execute("INSERT INTO sources VALUES ({0})".format(values), source_tuple) + + def get_one(self, **search): + """ + Get an existing source in the database + :param dict search: the criterions of the lookup + :rtype: sakia.data.entities.Source + """ + with self._conn: + filters = [] + values = [] + for k, v in search.items(): + filters.append("{k}=?".format(k=k)) + values.append(v) + + request = "SELECT * FROM sources WHERE {filters}".format(filters=" AND ".join(filters)) + + c = self._conn.execute(request, tuple(values)) + data = c.fetchone() + if data: + return Source(*data) + + def get_all(self, **search): + """ + Get all existing source in the database corresponding to the search + :param dict search: the criterions of the lookup + :rtype: sakia.data.entities.Source + """ + with self._conn: + filters = [] + values = [] + for k, v in search.items(): + value = v + filters.append("{key} = ?".format(key=k)) + values.append(value) + + request = "SELECT * FROM sources WHERE {filters}".format(filters=" AND ".join(filters)) + + c = self._conn.execute(request, tuple(values)) + datas = c.fetchall() + if datas: + return [Source(*data) for data in datas] + return [] + + def drop(self, source): + """ + Drop an existing source from the database + :param sakia.data.entities.Source source: the source to update + """ + with self._conn: + where_fields = attr.astuple(source, filter=attr.filters.include(*SourcesRepo._primary_keys)) + self._conn.execute("""DELETE FROM sources + WHERE + identifier=?""", where_fields) diff --git a/src/sakia/tests/unit/data/test_sources_repo.py b/src/sakia/tests/unit/data/test_sources_repo.py new file mode 100644 index 0000000000000000000000000000000000000000..671c15e0971d7f694076abee2746d90e5c79eb90 --- /dev/null +++ b/src/sakia/tests/unit/data/test_sources_repo.py @@ -0,0 +1,62 @@ +from sakia.data.repositories import SourcesRepo, SakiaDatabase +from sakia.data.entities import Source +from duniterpy.documents import BlockUID +import unittest +import sqlite3 + + +class TestSourcesRepo(unittest.TestCase): + def setUp(self): + self.meta_repo = SakiaDatabase(sqlite3.connect(":memory:", detect_types=sqlite3.PARSE_DECLTYPES)) + self.meta_repo.prepare() + self.meta_repo.upgrade_database() + + def tearDown(self): + pass + + def test_add_get_drop_source(self): + sources_repo = SourcesRepo(self.meta_repo.conn) + sources_repo.insert(Source("0835CEE9B4766B3866DD942971B3EE2CF953599EB9D35BFD5F1345879498B843", + "testcurrency", + "FADxcH5LmXGmGFgdixSes6nWnC4Vb4pRUBYT81zQRhjn", + "T", + 3, + 1565, + 1)) + source = sources_repo.get_one(identifier="0835CEE9B4766B3866DD942971B3EE2CF953599EB9D35BFD5F1345879498B843") + self.assertEqual(source.currency, "testcurrency") + self.assertEqual(source.pubkey, "FADxcH5LmXGmGFgdixSes6nWnC4Vb4pRUBYT81zQRhjn") + self.assertEqual(source.type, "T") + self.assertEqual(source.amount, 1565) + self.assertEqual(source.base, 1) + self.assertEqual(source.offset, 3) + + sources_repo.drop(source) + source = sources_repo.get_one(identifier="0835CEE9B4766B3866DD942971B3EE2CF953599EB9D35BFD5F1345879498B843") + self.assertIsNone(source) + + def test_add_get_multiple_source(self): + sources_repo = SourcesRepo(self.meta_repo.conn) + sources_repo.insert(Source("0835CEE9B4766B3866DD942971B3EE2CF953599EB9D35BFD5F1345879498B843", + "testcurrency", + "FADxcH5LmXGmGFgdixSes6nWnC4Vb4pRUBYT81zQRhjn", + "T", + 3, + 1565, + 1)) + sources_repo.insert(Source("2pyPsXM8UCB88jP2NRM4rUHxb63qm89JMEWbpoRrhyDK", + "testcurrency", + "FADxcH5LmXGmGFgdixSes6nWnC4Vb4pRUBYT81zQRhjn", + "D", + 22635, + 726946, + 1)) + sources = sources_repo.get_all(currency="testcurrency", pubkey="FADxcH5LmXGmGFgdixSes6nWnC4Vb4pRUBYT81zQRhjn") + self.assertIn("testcurrency", [s.currency for s in sources]) + self.assertIn("FADxcH5LmXGmGFgdixSes6nWnC4Vb4pRUBYT81zQRhjn", [s.pubkey for s in sources]) + self.assertIn("2pyPsXM8UCB88jP2NRM4rUHxb63qm89JMEWbpoRrhyDK", [s.identifier for s in sources]) + self.assertIn("T", [s.type for s in sources]) + self.assertIn("D", [s.type for s in sources]) + self.assertIn(726946, [s.amount for s in sources]) + self.assertIn(1565, [s.amount for s in sources]) + self.assertIn("0835CEE9B4766B3866DD942971B3EE2CF953599EB9D35BFD5F1345879498B843", [s.identifier for s in sources])