diff --git a/src/sakia/data/connectors/bma.py b/src/sakia/data/connectors/bma.py index 9ec0dcda42d37aa96c0405b5f5390b77bace40a4..ed48ec3aa4ca586b3b8aaf7208b397fcb6581c88 100644 --- a/src/sakia/data/connectors/bma.py +++ b/src/sakia/data/connectors/bma.py @@ -9,7 +9,6 @@ import jsonschema from pkg_resources import parse_version import attr from sakia.errors import NoPeerAvailable -from sakia.data.processors import NodesProcessor @attr.s() @@ -17,7 +16,7 @@ class BmaConnector: """ This class is used to access BMA API. """ - _nodes_processor = attr.ib(validator=attr.validators.instance_of(NodesProcessor)) + _nodes_processor = attr.ib() _logger = attr.ib(default=attr.Factory(lambda: logging.getLogger('sakia'))) def filter_endpoints(self, request, nodes): diff --git a/src/sakia/data/entities/source.py b/src/sakia/data/entities/source.py index 3927531a7e941b29dbff39ed6f4cbcaff67e002f..ee73f344ef80b9f5b43270631736c03150ed556f 100644 --- a/src/sakia/data/entities/source.py +++ b/src/sakia/data/entities/source.py @@ -4,10 +4,10 @@ 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) + identifier = attr.ib(convert=str) + noffset = attr.ib(convert=int) 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/__init__.py b/src/sakia/data/processors/__init__.py index 3e7c81c2bb4ed75419af7f79d35f030d0750e7e9..3e7dd0f503d1fb8a1548024851145b388d6d98bf 100644 --- a/src/sakia/data/processors/__init__.py +++ b/src/sakia/data/processors/__init__.py @@ -3,3 +3,4 @@ from .identities import IdentitiesProcessor from .certifications import CertificationsProcessor from .blockchain import BlockchainProcessor from .connections import ConnectionsProcessor +from .sources import SourcesProcessor diff --git a/src/sakia/data/processors/blockchain.py b/src/sakia/data/processors/blockchain.py index 5fd2b40bcef8d45e740829ae78a5ddeb2a9c65ae..fb67afc682ccb2a21ff86933f48ebd1a9ecf6a7d 100644 --- a/src/sakia/data/processors/blockchain.py +++ b/src/sakia/data/processors/blockchain.py @@ -163,7 +163,7 @@ class BlockchainProcessor: async def initialize_blockchain(self, currency, log_stream): """ - Start blockchain service if it does not exists + Initialize blockchain for a given currency if no source exists locally """ blockchain = self._repo.get_one(currency=currency) if not blockchain: diff --git a/src/sakia/data/processors/sources.py b/src/sakia/data/processors/sources.py new file mode 100644 index 0000000000000000000000000000000000000000..0268e54d52ad84c5adbdc505cdedfd3340d5d5d8 --- /dev/null +++ b/src/sakia/data/processors/sources.py @@ -0,0 +1,47 @@ +import attr +import re +from ..entities import Source +from .nodes import NodesProcessor +from ..connectors import BmaConnector +from duniterpy.api import bma, errors +from duniterpy.documents import Block, BMAEndpoint +import asyncio + + +@attr.s +class SourcesProcessor: + _repo = attr.ib() # :type sakia.data.repositories.SourcesRepo + _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.sources_repo, + BmaConnector(NodesProcessor(app.db.nodes_repo))) + + async def initialize_sources(self, currency, pubkey, log_stream): + """ + Initialize sources for a given pubkey if no source exists locally + """ + one_source = self._repo.get_one(currency=currency) + if not one_source: + log_stream("Requesting sources") + try: + sources_data = await self._bma_connector.get(currency, bma.tx.Sources, req_args={'pubkey': pubkey}) + + log_stream("Found {0} sources".format(len(sources_data['sources']))) + for i, s in enumerate(sources_data['sources']): + source = Source(currency=currency, pubkey=pubkey, + identifier=s['identifier'], + type=s['type'], + noffset=s['noffset'], + amount=s['amount'], + base=s['base']) + self._repo.insert(source) + await asyncio.sleep(0) + log_stream("{0}/{1} sources".format(i, len(sources_data['sources']))) + except errors.DuniterError as e: + raise diff --git a/src/sakia/data/repositories/blockchains.py b/src/sakia/data/repositories/blockchains.py index e9c56195c0ff56addfedafbfb7f54c637841d3df..0d8b35ee779c17d637dce247730a11f585788022 100644 --- a/src/sakia/data/repositories/blockchains.py +++ b/src/sakia/data/repositories/blockchains.py @@ -34,13 +34,17 @@ class BlockchainsRepo: where_fields = attr.astuple(blockchain, filter=attr.filters.include(*BlockchainsRepo._primary_keys)) self._conn.execute("""UPDATE blockchains SET current_buid=?, - nb_members=?, + members_count=?, current_mass=?, median_time=?, last_ud=?, last_ud_base=?, last_ud_time=?, - previous_mass=? + previous_mass=?, + previous_members_count=?, + previous_ud=?, + previous_ud_base=?, + previous_ud_time=? WHERE currency=?""", updated_fields + where_fields) diff --git a/src/sakia/data/repositories/meta.sql b/src/sakia/data/repositories/meta.sql index 89a6f211a1132c8760aadd747b6de6b6275b8cd1..1406814e0038868b1542f8fbf1a7150c99355453 100644 --- a/src/sakia/data/repositories/meta.sql +++ b/src/sakia/data/repositories/meta.sql @@ -18,31 +18,35 @@ CREATE TABLE IF NOT EXISTS identities( -- BLOCKCHAIN TABLE CREATE TABLE IF NOT EXISTS blockchains ( - c FLOAT(1, 6), - dt INT, - ud0 INT, - sig_period INT, - sig_stock INT, - sig_window INT, - sig_validity INT, - sig_qty INT, - xpercent FLOAT(1, 6), - ms_validity INT, - step_max INT, - median_time_blocks INT, - avg_gen_time INT, - dt_diff_eval INT, - blocks_rot INT, - percent_rot FLOAT(1, 6), - current_buid INT, - nb_members INT, - current_mass INT, - median_time INT, - last_ud INT, - last_ud_base INT, - last_ud_time INT, - previous_mass INT, - currency VARCHAR(30), + c FLOAT(1, 6), + dt INT, + ud0 INT, + sig_period INT, + sig_stock INT, + sig_window INT, + sig_validity INT, + sig_qty INT, + xpercent FLOAT(1, 6), + ms_validity INT, + step_max INT, + median_time_blocks INT, + avg_gen_time INT, + dt_diff_eval INT, + blocks_rot INT, + percent_rot FLOAT(1, 6), + current_buid INT, + members_count INT, + current_mass INT, + median_time INT, + last_ud INT, + last_ud_base INT, + last_ud_time INT, + previous_mass INT, + previous_members_count INT, + previous_ud INT, + previous_ud_base INT, + previous_ud_time INT, + currency VARCHAR(30), PRIMARY KEY (currency) ); @@ -106,14 +110,14 @@ CREATE TABLE IF NOT EXISTS connections( -- Cnnections TABLE CREATE TABLE IF NOT EXISTS sources( - identifier VARCHAR(255), currency VARCHAR(30), pubkey VARCHAR(50), + identifier VARCHAR(255), + noffset INT, type VARCHAR(8), - offset INT, amount INT, base INT, - PRIMARY KEY (identifier) + PRIMARY KEY (currency, pubkey, identifier, noffset) ); diff --git a/src/sakia/services/blockchain.py b/src/sakia/services/blockchain.py index 3dffb766d32bc17bc49174daa6c4e73f67456068..f48d3df14932da90b9119d23d74a2ed8b4057551 100644 --- a/src/sakia/services/blockchain.py +++ b/src/sakia/services/blockchain.py @@ -1,6 +1,6 @@ from PyQt5.QtCore import QObject from duniterpy.api import bma -import asyncio +import math import logging @@ -34,3 +34,38 @@ class BlockchainService(QObject): blocks = await self._blockchain_processor.blocks(with_identities + with_money) self._identities_service.handle_new_blocks(blocks) + def parameters(self): + return self._blockchain_processor.parameters(self.currency) + + def members_count(self): + return self._blockchain_processor.members_count(self.currency) + + def monetary_mass(self): + return self._blockchain_processor.monetary_mass(self.currency) + + def last_ud(self): + return self._blockchain_processor.last_ud(self.currency) + + def last_ud_time(self): + return self._blockchain_processor.last_ud_time(self.currency) + + def previous_members_count(self): + return self._blockchain_processor.previous_members_count(self.currency) + + def previous_monetary_mass(self): + return self._blockchain_processor.previous_monetary_mass(self.currency) + + def previous_ud_time(self): + return self._blockchain_processor.previous_ud_time(self.currency) + + def previous_ud(self): + return self._blockchain_processor.previous_ud(self.currency) + + def computed_dividend(self): + """ + Computes next dividend value + :rtype: int + """ + parameters = self.parameters() + next_ud = parameters.c * self.monetary_mass() / self.nb_members() + return math.ceil(next_ud) diff --git a/src/sakia/tests/unit/data/test_blockchains_repo.py b/src/sakia/tests/unit/data/test_blockchains_repo.py index edeb258d4b9259aa5bedbaa7f25ab2995b90bf5d..51dbd074f50c7f86aed5b0c5a16644fdae6f3878 100644 --- a/src/sakia/tests/unit/data/test_blockchains_repo.py +++ b/src/sakia/tests/unit/data/test_blockchains_repo.py @@ -10,8 +10,7 @@ from sakia.data.repositories import BlockchainsRepo, SakiaDatabase class TestBlockchainsRepo(unittest.TestCase): def setUp(self): sqlite3.register_adapter(BlockUID, str) - self.meta_repo = SakiaDatabase(sqlite3.connect(":memory:", detect_types=sqlite3.PARSE_DECLTYPES), - None, None, None, None, None, None) + self.meta_repo = SakiaDatabase(sqlite3.connect(":memory:", detect_types=sqlite3.PARSE_DECLTYPES)) self.meta_repo.prepare() self.meta_repo.upgrade_database() @@ -21,7 +20,7 @@ class TestBlockchainsRepo(unittest.TestCase): def test_add_get_drop_blockchain(self): blockchains_repo = BlockchainsRepo(self.meta_repo.conn) blockchains_repo.insert(Blockchain( - BlockchainParameters( + parameters=BlockchainParameters( 0.1, 86400, 100000, @@ -38,15 +37,19 @@ class TestBlockchainsRepo(unittest.TestCase): 25, 10, 0.66), - "20-7518C700E78B56CC21FB1DDC6CBAB24E0FACC9A798F5ED8736EA007F38617D67", - 10, - 1000000, - 86400, - 100000, - 0, - 86400, - 999999, - "testcurrency" + current_buid="20-7518C700E78B56CC21FB1DDC6CBAB24E0FACC9A798F5ED8736EA007F38617D67", + members_count = 10, + current_mass = 1000000, + median_time = 86400, + last_ud = 100000, + last_ud_base = 0, + last_ud_time = 86400, + previous_mass = 999999, + previous_members_count = 10, + previous_ud = 6543, + previous_ud_base = 0, + previous_ud_time = 86400, + currency = "testcurrency" )) blockchain = blockchains_repo.get_one(currency="testcurrency") self.assertEqual(blockchain.parameters, BlockchainParameters( @@ -70,7 +73,7 @@ class TestBlockchainsRepo(unittest.TestCase): self.assertEqual(blockchain.current_buid, BlockUID(20, "7518C700E78B56CC21FB1DDC6CBAB24E0FACC9A798F5ED8736EA007F38617D67") ) - self.assertEqual(blockchain.nb_members, 10) + self.assertEqual(blockchain.members_count, 10) blockchains_repo.drop(blockchain) blockchain = blockchains_repo.get_one(currency="testcurrency") @@ -79,7 +82,7 @@ class TestBlockchainsRepo(unittest.TestCase): def test_add_get_multiple_blockchain(self): blockchains_repo = BlockchainsRepo(self.meta_repo.conn) blockchains_repo.insert(Blockchain( - BlockchainParameters( + parameters=BlockchainParameters( 0.1, 86400, 100000, @@ -96,15 +99,20 @@ class TestBlockchainsRepo(unittest.TestCase): 25, 10, 0.66), - "20-7518C700E78B56CC21FB1DDC6CBAB24E0FACC9A798F5ED8736EA007F38617D67", - 10, - 1000000, - 86400, - 100000, - 0, - 86400, - 999999, - "testcurrency" + + current_buid="20-7518C700E78B56CC21FB1DDC6CBAB24E0FACC9A798F5ED8736EA007F38617D67", + members_count = 10, + current_mass = 1000000, + median_time = 86400, + last_ud = 100000, + last_ud_base = 0, + last_ud_time = 86400, + previous_mass = 999999, + previous_members_count = 10, + previous_ud = 6543, + previous_ud_base = 0, + previous_ud_time = 86400, + currency = "testcurrency" )) blockchains_repo.insert(Blockchain( BlockchainParameters( @@ -124,26 +132,30 @@ class TestBlockchainsRepo(unittest.TestCase): 25, 10, 0.66), - "20-7518C700E78B56CC21FB1DDC6CBAB24E0FACC9A798F5ED8736EA007F38617D67", - 20, - 1000000, - 86400, - 100000, - 0, - 86400, - 999999, - "testcurrency2" + current_buid="20-7518C700E78B56CC21FB1DDC6CBAB24E0FACC9A798F5ED8736EA007F38617D67", + members_count = 20, + current_mass = 1000000, + median_time = 86400, + last_ud = 100000, + last_ud_base = 0, + last_ud_time = 86400, + previous_mass = 999999, + previous_members_count = 10, + previous_ud = 6543, + previous_ud_base = 0, + previous_ud_time = 86400, + currency = "testcurrency2" )) blockchains = blockchains_repo.get_all() # result sorted by currency name by default self.assertEquals(86400, blockchains[0].parameters.dt) self.assertEquals("testcurrency", blockchains[0].currency) - self.assertEquals(10, blockchains[0].nb_members) + self.assertEquals(10, blockchains[0].members_count) self.assertEquals(86400*365, blockchains[1].parameters.dt) self.assertEquals("testcurrency2", blockchains[1].currency) - self.assertEquals(20, blockchains[1].nb_members) + self.assertEquals(20, blockchains[1].members_count) def test_add_update_blockchain(self): blockchains_repo = BlockchainsRepo(self.meta_repo.conn) @@ -165,18 +177,22 @@ class TestBlockchainsRepo(unittest.TestCase): 25, 10, 0.66), - "20-7518C700E78B56CC21FB1DDC6CBAB24E0FACC9A798F5ED8736EA007F38617D67", - 10, - 1000000, - 86400, - 100000, - 0, - 86400, - 999999, - "testcurrency" + current_buid="20-7518C700E78B56CC21FB1DDC6CBAB24E0FACC9A798F5ED8736EA007F38617D67", + members_count = 10, + current_mass = 1000000, + median_time = 86400, + last_ud = 100000, + last_ud_base = 0, + last_ud_time = 86400, + previous_mass = 999999, + previous_members_count = 10, + previous_ud = 6543, + previous_ud_base = 0, + previous_ud_time = 86400, + currency = "testcurrency" ) blockchains_repo.insert(blockchain) - blockchain.nb_members = 30 + blockchain.members_count = 30 blockchains_repo.update(blockchain) blockchain2 = blockchains_repo.get_one(currency="testcurrency") - self.assertEquals(30, blockchain2.nb_members) + self.assertEquals(30, blockchain2.members_count) diff --git a/src/sakia/tests/unit/data/test_sources_repo.py b/src/sakia/tests/unit/data/test_sources_repo.py index 671c15e0971d7f694076abee2746d90e5c79eb90..27cbdbaad399691c97db132d6a50c222672eac85 100644 --- a/src/sakia/tests/unit/data/test_sources_repo.py +++ b/src/sakia/tests/unit/data/test_sources_repo.py @@ -16,11 +16,11 @@ class TestSourcesRepo(unittest.TestCase): def test_add_get_drop_source(self): sources_repo = SourcesRepo(self.meta_repo.conn) - sources_repo.insert(Source("0835CEE9B4766B3866DD942971B3EE2CF953599EB9D35BFD5F1345879498B843", - "testcurrency", + sources_repo.insert(Source("testcurrency", "FADxcH5LmXGmGFgdixSes6nWnC4Vb4pRUBYT81zQRhjn", - "T", + "0835CEE9B4766B3866DD942971B3EE2CF953599EB9D35BFD5F1345879498B843", 3, + "T", 1565, 1)) source = sources_repo.get_one(identifier="0835CEE9B4766B3866DD942971B3EE2CF953599EB9D35BFD5F1345879498B843") @@ -29,7 +29,7 @@ class TestSourcesRepo(unittest.TestCase): self.assertEqual(source.type, "T") self.assertEqual(source.amount, 1565) self.assertEqual(source.base, 1) - self.assertEqual(source.offset, 3) + self.assertEqual(source.noffset, 3) sources_repo.drop(source) source = sources_repo.get_one(identifier="0835CEE9B4766B3866DD942971B3EE2CF953599EB9D35BFD5F1345879498B843") @@ -37,18 +37,18 @@ class TestSourcesRepo(unittest.TestCase): def test_add_get_multiple_source(self): sources_repo = SourcesRepo(self.meta_repo.conn) - sources_repo.insert(Source("0835CEE9B4766B3866DD942971B3EE2CF953599EB9D35BFD5F1345879498B843", - "testcurrency", + sources_repo.insert(Source("testcurrency", "FADxcH5LmXGmGFgdixSes6nWnC4Vb4pRUBYT81zQRhjn", - "T", + "0835CEE9B4766B3866DD942971B3EE2CF953599EB9D35BFD5F1345879498B843", 3, + "T", 1565, 1)) - sources_repo.insert(Source("2pyPsXM8UCB88jP2NRM4rUHxb63qm89JMEWbpoRrhyDK", - "testcurrency", + sources_repo.insert(Source("testcurrency", "FADxcH5LmXGmGFgdixSes6nWnC4Vb4pRUBYT81zQRhjn", - "D", + "2pyPsXM8UCB88jP2NRM4rUHxb63qm89JMEWbpoRrhyDK", 22635, + "D", 726946, 1)) sources = sources_repo.get_all(currency="testcurrency", pubkey="FADxcH5LmXGmGFgdixSes6nWnC4Vb4pRUBYT81zQRhjn")