diff --git a/src/sakia/app.py b/src/sakia/app.py index a88c25a09e8fc8f1758aa5b3e5d4aaada1bd96cd..6435732d0915c184c83860d11cd0165d258a9b1f 100644 --- a/src/sakia/app.py +++ b/src/sakia/app.py @@ -61,7 +61,7 @@ class Application(QObject): sources_services = attr.ib(default=attr.Factory(dict)) transactions_services = attr.ib(default=attr.Factory(dict)) documents_service = attr.ib(default=None) - current_ref = attr.ib(default=Relative) + current_ref = attr.ib(default=Quantitative) _logger = attr.ib(default=attr.Factory(lambda:logging.getLogger('sakia'))) available_version = attr.ib(init=False) _translator = attr.ib(init=False) diff --git a/src/sakia/data/processors/blockchain.py b/src/sakia/data/processors/blockchain.py index 7acecae9de75ca682661f62c0ef9ff7a6095ca45..7d3aaa54914a7b3ec4d3bff0c16de63bdc92d3c4 100644 --- a/src/sakia/data/processors/blockchain.py +++ b/src/sakia/data/processors/blockchain.py @@ -231,58 +231,58 @@ class BlockchainProcessor: except errors.DuniterError as e: raise - log_stream("Requesting current block") + log_stream("Requesting current block") + try: + current_block = await self._bma_connector.get(currency, bma.blockchain.current, verify=False) + signed_raw = "{0}{1}\n".format(current_block['raw'], current_block['signature']) + block = Block.from_signed_raw(signed_raw) + blockchain.current_buid = block.blockUID + blockchain.median_time = block.mediantime + blockchain.current_members_count = block.members_count + except errors.DuniterError as e: + if e.ucode != errors.NO_CURRENT_BLOCK: + raise + + log_stream("Requesting blocks with dividend") + with_ud = await self._bma_connector.get(currency, bma.blockchain.ud, verify=False) + blocks_with_ud = with_ud['result']['blocks'] + + if len(blocks_with_ud) > 0: + log_stream("Requesting last block with dividend") try: - current_block = await self._bma_connector.get(currency, bma.blockchain.current, verify=False) - signed_raw = "{0}{1}\n".format(current_block['raw'], current_block['signature']) - block = Block.from_signed_raw(signed_raw) - blockchain.current_buid = block.blockUID - blockchain.median_time = block.mediantime - blockchain.current_members_count = block.members_count + index = max(len(blocks_with_ud) - 1, 0) + block_number = blocks_with_ud[index] + block_with_ud = await self._bma_connector.get(currency, bma.blockchain.block, + req_args={'number': block_number}, verify=False) + if block_with_ud: + blockchain.last_members_count = block_with_ud['membersCount'] + blockchain.last_ud = block_with_ud['dividend'] + blockchain.last_ud_base = block_with_ud['unitbase'] + blockchain.last_ud_time = block_with_ud['medianTime'] + blockchain.current_mass = block_with_ud['monetaryMass'] except errors.DuniterError as e: if e.ucode != errors.NO_CURRENT_BLOCK: raise - log_stream("Requesting blocks with dividend") - with_ud = await self._bma_connector.get(currency, bma.blockchain.ud, verify=False) - blocks_with_ud = with_ud['result']['blocks'] - - if len(blocks_with_ud) > 0: - log_stream("Requesting last block with dividend") - try: - index = max(len(blocks_with_ud) - 1, 0) - block_number = blocks_with_ud[index] - block_with_ud = await self._bma_connector.get(currency, bma.blockchain.block, - req_args={'number': block_number}, verify=False) - if block_with_ud: - blockchain.last_members_count = block_with_ud['membersCount'] - blockchain.last_ud = block_with_ud['dividend'] - blockchain.last_ud_base = block_with_ud['unitbase'] - blockchain.last_ud_time = block_with_ud['medianTime'] - blockchain.current_mass = block_with_ud['monetaryMass'] - except errors.DuniterError as e: - if e.ucode != errors.NO_CURRENT_BLOCK: - raise - - log_stream("Requesting previous block with dividend") - try: - index = max(len(blocks_with_ud) - 2, 0) - block_number = blocks_with_ud[index] - block_with_ud = await self._bma_connector.get(currency, bma.blockchain.block, - req_args={'number': block_number}, verify=False) - blockchain.previous_mass = block_with_ud['monetaryMass'] - blockchain.previous_members_count = block_with_ud['membersCount'] - blockchain.previous_ud = block_with_ud['dividend'] - blockchain.previous_ud_base = block_with_ud['unitbase'] - blockchain.previous_ud_time = block_with_ud['medianTime'] - except errors.DuniterError as e: - if e.ucode != errors.NO_CURRENT_BLOCK: - raise - + log_stream("Requesting previous block with dividend") try: - self._repo.insert(blockchain) - except sqlite3.IntegrityError: - pass + index = max(len(blocks_with_ud) - 2, 0) + block_number = blocks_with_ud[index] + block_with_ud = await self._bma_connector.get(currency, bma.blockchain.block, + req_args={'number': block_number}, verify=False) + blockchain.previous_mass = block_with_ud['monetaryMass'] + blockchain.previous_members_count = block_with_ud['membersCount'] + blockchain.previous_ud = block_with_ud['dividend'] + blockchain.previous_ud_base = block_with_ud['unitbase'] + blockchain.previous_ud_time = block_with_ud['medianTime'] + except errors.DuniterError as e: + if e.ucode != errors.NO_CURRENT_BLOCK: + raise + + try: + self._repo.insert(blockchain) + except sqlite3.IntegrityError: + self._repo.update(blockchain) def handle_new_blocks(self, currency, blocks): """ diff --git a/src/sakia/data/processors/certifications.py b/src/sakia/data/processors/certifications.py index e9fc98773973e6d41115e6bfe884fc8764ff95ca..833acf27e32a2b7772864715e86a56ced3771079 100644 --- a/src/sakia/data/processors/certifications.py +++ b/src/sakia/data/processors/certifications.py @@ -59,17 +59,26 @@ class CertificationsProcessor: return parameters.sig_period - (blockchain_time - certified.timestamp) return 0 - def create_certification(self, currency, cert, blockstamp): + def create_or_update_certification(self, currency, cert, timestamp, blockstamp): """ Creates a certification and insert it in the db :param duniterpy.documents.Certification cert: + :param int timestamp: the timestamp of the transaction :param duniterpy.documents.BlockUID blockstamp: :return: the instanciated certification :rtype: sakia.data.entities.Certification """ - cert = Certification(currency, cert.pubkey_from, cert.pubkey_to, cert.timestamp.number, - 0, cert.signatures[0], blockstamp) - self._certifications_repo.insert(cert) + cert = Certification(currency=currency, + certifier=cert.pubkey_from, + certified=cert.pubkey_to, + block=cert.timestamp.number, + timestamp=timestamp, + signature=cert.signatures[0], + written_on=blockstamp.number) + try: + self._certifications_repo.insert(cert) + except sqlite3.IntegrityError: + self._certifications_repo.update(cert) return cert def insert_or_update_certification(self, cert): diff --git a/src/sakia/data/processors/connections.py b/src/sakia/data/processors/connections.py index bfb4752b55c1ce8b6e797fdeaa2069e269cf8684..4ea72fd17df11e5784894da02220cde4a40f9aff 100644 --- a/src/sakia/data/processors/connections.py +++ b/src/sakia/data/processors/connections.py @@ -35,8 +35,11 @@ class ConnectionsProcessor: def connections(self): return self._connections_repo.get_all() - def connections_with_uids(self): - return [r for r in self._connections_repo.get_all() if r.uid] + def connections_with_uids(self, currency=""): + if currency: + return [r for r in self._connections_repo.get_all(currency=currency) if r.uid] + else: + return [r for r in self._connections_repo.get_all() if r.uid] def connections_to(self, currency): return self._connections_repo.get_all(currency=currency) diff --git a/src/sakia/gui/navigation/txhistory/table_model.py b/src/sakia/gui/navigation/txhistory/table_model.py index 40d588d8f3fe9ae5f3f811bf3b1602634bcfe1b6..6c68f8c6a131c3b4ef88751535ba5c6701bb20fa 100644 --- a/src/sakia/gui/navigation/txhistory/table_model.py +++ b/src/sakia/gui/navigation/txhistory/table_model.py @@ -102,7 +102,7 @@ class TxFilterProxyModel(QSortFilterProxyModel): ) if source_index.column() == model.columns_types.index('amount'): amount = self.app.current_ref.instance(source_data, model.connection.currency, - self.app, block_data).diff_localized(False, True) + self.app, block_data).diff_localized(False, False) return amount if role == Qt.FontRole: diff --git a/src/sakia/money/quantitative.py b/src/sakia/money/quantitative.py index 1f4c06dafa456d362dff65af2883c19a8e3e8bd5..eb2e122d36fc9bcc0d567e3f5cbaf1d92966186a 100644 --- a/src/sakia/money/quantitative.py +++ b/src/sakia/money/quantitative.py @@ -67,7 +67,7 @@ class Quantitative(BaseReferential): unicodes[str(n)] = ord('\u2070') + n if base > 1: - return "x10" + "".join([chr(unicodes[e]) for e in str(base)]) + return ".10" + "".join([chr(unicodes[e]) for e in str(base)]) else: return "" @@ -92,12 +92,8 @@ class Quantitative(BaseReferential): def localized(self, units=False, show_base=False): value = self.value() dividend, base = self._blockchain_processor.last_ud(self.currency) - if show_base: - localized_value = Quantitative.to_si(value, base) - prefix = Quantitative.base_str(base) - else: - localized_value = QLocale().toString(float(value), 'f', 2) - prefix = "" + localized_value = Quantitative.to_si(value, base) + prefix = Quantitative.base_str(base) if units or show_base: return QCoreApplication.translate("Quantitative", @@ -111,12 +107,8 @@ class Quantitative(BaseReferential): def diff_localized(self, units=False, show_base=False): value = self.differential() dividend, base = self._blockchain_processor.last_ud(self.currency) - if show_base: - localized_value = Quantitative.to_si(value, base) - prefix = Quantitative.base_str(base) - else: - localized_value = QLocale().toString(float(value), 'f', 2) - prefix = "" + localized_value = Quantitative.to_si(value, base) + prefix = Quantitative.base_str(base) if units or show_base: return QCoreApplication.translate("Quantitative", diff --git a/src/sakia/services/blockchain.py b/src/sakia/services/blockchain.py index 62a3e33f2d14f322a4ab80fb0255a6ebfe040c1d..561b80a0ad90c182960ec1d4f447b9b09d181cec 100644 --- a/src/sakia/services/blockchain.py +++ b/src/sakia/services/blockchain.py @@ -33,6 +33,9 @@ class BlockchainService(QObject): self._sources_service = sources_service self._logger = logging.getLogger('sakia') + def handle_new_blocks(self, blocks): + self._blockchain_processor.handle_new_blocks(self.currency, blocks) + async def handle_blockchain_progress(self, network_blockstamp): """ Handle a new current block uid @@ -48,7 +51,7 @@ class BlockchainService(QObject): if len(blocks) > 0: identities = await self._identities_service.handle_new_blocks(blocks) changed_tx, new_tx, new_dividends = await self._transactions_service.handle_new_blocks(blocks) - self._blockchain_processor.handle_new_blocks(self.currency, blocks) + self.handle_new_blocks(blocks) self.app.db.commit() for tx in changed_tx: self.app.transaction_state_changed.emit(tx) diff --git a/src/sakia/services/documents.py b/src/sakia/services/documents.py index d676ca3617fb8934dd7155a272515108506bf198..2fc43b34c9f41d7d594aa36235ade75b02b9adf9 100644 --- a/src/sakia/services/documents.py +++ b/src/sakia/services/documents.py @@ -4,15 +4,15 @@ import logging from duniterpy.key import SigningKey from duniterpy.documents import Certification, Membership, Revocation, InputSource, \ - OutputSource, SIGParameter, Unlock, block_uid + OutputSource, SIGParameter, Unlock, block_uid, BlockUID from duniterpy.documents import Identity as IdentityDoc from duniterpy.documents import Transaction as TransactionDoc from duniterpy.documents.transaction import reduce_base from duniterpy.grammars import output -from duniterpy.api import bma, errors +from duniterpy.api import bma from sakia.data.entities import Identity, Transaction from sakia.data.processors import BlockchainProcessor, IdentitiesProcessor, NodesProcessor, \ - TransactionsProcessor, SourcesProcessor + TransactionsProcessor, SourcesProcessor, CertificationsProcessor from sakia.data.connectors import BmaConnector, parse_bma_responses from sakia.errors import NotEnoughChangeError @@ -32,6 +32,7 @@ class DocumentsService: _bma_connector = attr.ib() _blockchain_processor = attr.ib() _identities_processor = attr.ib() + _certifications_processor = attr.ib() _transactions_processor = attr.ib() _sources_processor = attr.ib() _logger = attr.ib(default=attr.Factory(lambda: logging.getLogger('sakia'))) @@ -45,6 +46,7 @@ class DocumentsService: return cls(BmaConnector(NodesProcessor(app.db.nodes_repo), app.parameters), BlockchainProcessor.instanciate(app), IdentitiesProcessor.instanciate(app), + CertificationsProcessor.instanciate(app), TransactionsProcessor.instanciate(app), SourcesProcessor.instanciate(app)) @@ -157,9 +159,12 @@ class DocumentsService: certification.sign(identity.document(), [key]) signed_cert = certification.signed_raw(identity.document()) self._logger.debug("Certification : {0}".format(signed_cert)) - + timestamp = self._blockchain_processor.time(connection.currency) responses = await self._bma_connector.broadcast(connection.currency, bma.wot.certify, req_args={'cert': signed_cert}) result = await parse_bma_responses(responses) + if result[0]: + self._certifications_processor.create_or_update_certification(connection.currency, certification, + timestamp, BlockUID.empty()) return result diff --git a/src/sakia/services/identities.py b/src/sakia/services/identities.py index ef1958fc0c4e39b8bc2157f34f6f7c1ba6fe4d84..fbbdc211170d980766652509ee14192097d7d9b0 100644 --- a/src/sakia/services/identities.py +++ b/src/sakia/services/identities.py @@ -21,6 +21,7 @@ class IdentitiesService(QObject): :param sakia.data.processors.IdentitiesProcessor identities_processor: the identities processor for given currency :param sakia.data.processors.CertificationsProcessor certs_processor: the certifications processor for given currency :param sakia.data.processors.BlockchainProcessor blockchain_processor: the blockchain processor for given currency + :param sakia.data.processors.ConnectionsProcessor connections_processor: the connections processor :param sakia.data.connectors.BmaConnector bma_connector: The connector to BMA API """ super().__init__() @@ -66,7 +67,7 @@ class IdentitiesService(QObject): return 0 def _get_connections_identities(self): - connections = self._connections_processor.connections_to(self.currency) + connections = self._connections_processor.connections_with_uids(self.currency) identities = [] for c in connections: identities.append(self._identities_processor.get_identity(self.currency, c.pubkey)) @@ -216,7 +217,7 @@ class IdentitiesService(QObject): return need_refresh - def _parse_certifications(self, block): + async def _parse_certifications(self, block): """ Parse certified pubkeys found in a block and refresh local data This method only creates certifications if one of both identities is @@ -232,7 +233,8 @@ class IdentitiesService(QObject): # if we have are a target or a source of the certification for identity in connections_identities: if cert.pubkey_from == identity.pubkey or cert.pubkey_to in identity.pubkey: - self._certs_processor.create_certification(self.currency, cert, block.blockUID) + timestamp = await self._blockchain_processor.timestamp(self.currency, cert.timestamp) + self._certs_processor.create_or_update_certification(self.currency, cert, timestamp, block.blockUID) need_refresh.append(identity) return need_refresh @@ -261,7 +263,7 @@ class IdentitiesService(QObject): self._logger.debug(str(e)) return identity - def parse_block(self, block): + async def parse_block(self, block): """ Parse a block to refresh local data :param block: @@ -270,7 +272,7 @@ class IdentitiesService(QObject): self._parse_revocations(block) need_refresh = [] need_refresh += self._parse_memberships(block) - need_refresh += self._parse_certifications(block) + need_refresh += await self._parse_certifications(block) return set(need_refresh) async def handle_new_blocks(self, blocks): @@ -280,7 +282,7 @@ class IdentitiesService(QObject): """ need_refresh = [] for block in blocks: - need_refresh += self.parse_block(block) + need_refresh += await self.parse_block(block) refresh_futures = [] # for every identity for which we need a refresh, we gather # requirements requests diff --git a/tests/technical/test_blockchain_service.py b/tests/technical/test_blockchain_service.py new file mode 100644 index 0000000000000000000000000000000000000000..2fa73fdd787bc221274ece8a9524d6f2287e6ae4 --- /dev/null +++ b/tests/technical/test_blockchain_service.py @@ -0,0 +1,19 @@ +from sakia.data.entities import Identity +from duniterpy.documents.certification import Certification +import pytest + + +@pytest.mark.asyncio +async def test_new_block_with_ud(application_with_one_connection, fake_server): + previous_ud = application_with_one_connection.blockchain_services[fake_server.forge.currency].previous_ud() + fake_server.forge.forge_block() + fake_server.forge.generate_dividend() + fake_server.forge.forge_block() + fake_server.forge.generate_dividend() + fake_server.forge.forge_block() + new_blocks = fake_server.forge.blocks[-3:] + application_with_one_connection.blockchain_services[fake_server.forge.currency].handle_new_blocks( + new_blocks) + previous_ud_after_parse = application_with_one_connection.blockchain_services[fake_server.forge.currency].previous_ud() + assert previous_ud_after_parse > previous_ud + await fake_server.close() \ No newline at end of file diff --git a/tests/technical/test_identities_service.py b/tests/technical/test_identities_service.py index d2e2bb9cf26ae6a4fa92d9828edf8a379cb02209..ea63c1ac3006e543c33f0a1bc6d6b0eee06e4eb7 100644 --- a/tests/technical/test_identities_service.py +++ b/tests/technical/test_identities_service.py @@ -1,6 +1,34 @@ +from sakia.data.entities import Identity +from duniterpy.documents.certification import Certification import pytest @pytest.mark.asyncio -async def test_new_block_with_unknown_identities(application_with_one_connection, fake_server, bob, alice): - pass \ No newline at end of file +async def test_new_block_with_certs(application_with_one_connection, fake_server, bob, alice): + certs_before_send = application_with_one_connection.identities_services[fake_server.forge.currency].certifications_sent( + bob.key.pubkey) + alice_user_identity = fake_server.forge.user_identities[bob.key.pubkey] + alice_identity = Identity(currency=fake_server.forge.currency, + pubkey=alice.key.pubkey, + uid=alice.uid, + blockstamp=alice_user_identity.blockstamp, + signature=alice_user_identity.signature) + bob_connection = application_with_one_connection.db.connections_repo.get_one(pubkey=bob.key.pubkey) + await application_with_one_connection.documents_service.certify(bob_connection, + bob.password, alice_identity) + certs_after_send = application_with_one_connection.identities_services[fake_server.forge.currency].certifications_sent( + bob.key.pubkey) + assert len(certs_after_send) == len(certs_before_send) + 1 + assert certs_after_send[0].written_on == 0 + assert isinstance(fake_server.forge.pool[0], Certification) + fake_server.forge.forge_block() + fake_server.forge.forge_block() + fake_server.forge.forge_block() + new_blocks = fake_server.forge.blocks[-3:] + await application_with_one_connection.identities_services[fake_server.forge.currency].handle_new_blocks( + new_blocks) + certs_after_parse = application_with_one_connection.identities_services[fake_server.forge.currency].certifications_sent( + bob.key.pubkey) + assert len(certs_after_parse) == len(certs_after_send) + assert certs_after_parse[0].written_on == fake_server.forge.blocks[-3].number + await fake_server.close() \ No newline at end of file diff --git a/tests/unit/money/test_quantitative.py b/tests/unit/money/test_quantitative.py index 8ae1f3e482068eb61b93dc6e595a019f799526f4..950dbe0ad87651b3f7f86de292074b7ab30ec1ac 100644 --- a/tests/unit/money/test_quantitative.py +++ b/tests/unit/money/test_quantitative.py @@ -36,7 +36,7 @@ def test_localized_with_si(application_with_one_connection, bob): blockchain.last_ud_base = 3 application_with_one_connection.db.blockchains_repo.update(blockchain) value = referential.localized(units=True, show_base=True) - assert value == "1,010.10 x10³ TC" + assert value == "1,010.10 .10³ TC" def test_localized_no_units_no_si(application_with_one_connection, bob): @@ -53,7 +53,7 @@ def test_localized_no_units_with_si(application_with_one_connection, bob): blockchain.last_ud_base = 3 application_with_one_connection.db.blockchains_repo.update(blockchain) value = referential.localized(units=False, show_base=True) - assert value == "1,010.10 x10³" + assert value == "1,010.10 .10³" def test_diff_localized_no_si(application_with_one_connection, bob): @@ -70,7 +70,7 @@ def test_diff_localized_with_si(application_with_one_connection, bob): application_with_one_connection.db.blockchains_repo.update(blockchain) value = referential.diff_localized(units=True, show_base=True) - assert value == "1,010.10 x10³ TC" + assert value == "1,010.10 .10³ TC" def test_diff_localized_no_units_no_si(application_with_one_connection, bob): @@ -87,4 +87,14 @@ def test_diff_localized_no_units_with_si(application_with_one_connection, bob): blockchain.last_ud_base = 6 application_with_one_connection.db.blockchains_repo.update(blockchain) value = referential.diff_localized(units=False, show_base=True) - assert value == "101.00 x10⁶" + assert value == "101.00 .10⁶" + + +def test_diff_localized_no_units_no_base(application_with_one_connection, bob): + application_with_one_connection.parameters.digits_after_comma = 6 + referential = Quantitative(10100000000, bob.currency, application_with_one_connection, None) + blockchain = application_with_one_connection.db.blockchains_repo.get_one(currency=bob.currency) + blockchain.last_ud_base = 6 + application_with_one_connection.db.blockchains_repo.update(blockchain) + value = referential.diff_localized(units=False, show_base=False) + assert value == "101.00"