diff --git a/src/sakia/app.py b/src/sakia/app.py index aa1f9358c675beeadab3f2945c826adfdc8ab947..ce184ec48ed5fdad5c7c9ee62242e2095f9354a0 100644 --- a/src/sakia/app.py +++ b/src/sakia/app.py @@ -85,7 +85,7 @@ class Application(QObject): nodes_processor = NodesProcessor(self.db.nodes_repo) bma_connector = BmaConnector(nodes_processor) identities_processor = IdentitiesProcessor(self.db.identities_repo, self.db.blockchains_repo, bma_connector) - certs_processor = CertificationsProcessor(self.db.certifications_repo, bma_connector) + certs_processor = CertificationsProcessor(self.db.certifications_repo, self.db.identities_repo, bma_connector) blockchain_processor = BlockchainProcessor.instanciate(self) sources_processor = SourcesProcessor.instanciate(self) diff --git a/src/sakia/data/processors/certifications.py b/src/sakia/data/processors/certifications.py index 3d6ac6a30cfec44f5bbd58c0a03eccdd27f9965a..23cb69d00867b1d4434a5a6b7bd35a00ab058a97 100644 --- a/src/sakia/data/processors/certifications.py +++ b/src/sakia/data/processors/certifications.py @@ -1,12 +1,29 @@ import attr -from ..entities import Certification +from duniterpy.api import bma, errors +from duniterpy.documents import BlockUID +from ..connectors import BmaConnector +from ..processors import NodesProcessor +from ..entities import Certification, Identity import sqlite3 +import logging +from sakia.errors import NoPeerAvailable @attr.s class CertificationsProcessor: - _repo = attr.ib() # :type sakia.data.repositories.CertificationsRepo + _certifications_repo = attr.ib() # :type sakia.data.repositories.CertificationsRepo + _identities_repo = attr.ib() # :type sakia.data.repositories.IdentitiesRepo _bma_connector = attr.ib() # :type sakia.data.connectors.bma.BmaConnector + _logger = attr.ib(default=attr.Factory(lambda: logging.getLogger('sakia'))) + + @classmethod + def instanciate(cls, app): + """ + Instanciate a blockchain processor + :param sakia.app.Application app: the app + """ + return cls(app.db.certifications_repo, app.db.identities_repo, + BmaConnector(NodesProcessor(app.db.nodes_repo))) def create_certification(self, currency, cert, blockstamp): """ @@ -28,6 +45,86 @@ class CertificationsProcessor: :return: """ try: - self._repo.insert(cert) + self._certifications_repo.insert(cert) except sqlite3.IntegrityError: - self._repo.update(cert) + self._certifications_repo.update(cert) + + async def initialize_certifications(self, identity, log_stream): + """ + Initialize certifications to and from a given identity + :param sakia.data.entities.Identity identity: + :param function log_stream: + """ + log_stream("Requesting certifiers of data") + identities = list() + certifiers = list() + try: + data = await self._bma_connector.get(identity.currency, bma.wot.CertifiersOf, + {'search': identity.pubkey}) + + for certifier_data in data['certifications']: + certification = Certification(currency=identity.currency, + certified=identity.pubkey, + certifier=certifier_data['pubkey'], + block=certifier_data['cert_time']['block'], + timestamp=certifier_data['cert_time']['medianTime'], + signature=certifier_data['signature']) + other_identity = Identity(currency=identity.currency, + pubkey=certifier_data['pubkey'], + uid=certifier_data['uid'], + blockstamp=certifier_data['sigDate'], + member=certifier_data['isMember']) + if certifier_data['written']: + certification.written_on = BlockUID(certifier_data['written']['number'], + certifier_data['written']['hash']) + + certifiers.append(certification) + identities.append(other_identity) + except errors.DuniterError as e: + if e.ucode in (errors.NO_MATCHING_IDENTITY, errors.NO_MEMBER_MATCHING_PUB_OR_UID): + logging.debug("Certifiers of error : {0}".format(str(e))) + else: + logging.debug(str(e)) + except NoPeerAvailable as e: + logging.debug(str(e)) + + log_stream("Requesting certified by data") + certified = list() + try: + data = await self._bma_connector.get(identity.currency, bma.wot.CertifiedBy, {'search': identity.pubkey}) + for certified_data in data['certifications']: + certification = Certification(currency=identity.currency, + certifier=identity.pubkey, + certified=certifier_data['pubkey'], + block=certifier_data['cert_time']['block'], + timestamp=certifier_data['cert_time']['medianTime'], + signature=certifier_data['signature']) + other_identity = Identity(currency=identity.currency, + pubkey=certified_data['pubkey'], + uid=certified_data['uid'], + blockstamp=certified_data['sigDate'], + member=certified_data['isMember']) + if certified_data['written']: + certification.written_on = BlockUID(certified_data['written']['number'], + certified_data['written']['hash']) + + certified.append(certification) + identities.append(other_identity) + except errors.DuniterError as e: + if e.ucode in (errors.NO_MATCHING_IDENTITY, errors.NO_MEMBER_MATCHING_PUB_OR_UID): + logging.debug("Certified by error : {0}".format(str(e))) + except NoPeerAvailable as e: + logging.debug(str(e)) + + log_stream('Commiting certifications...') + for i, cert in enumerate(certifiers + certified): + log_stream('Certification {0}/{1}'.format(i, len(certifiers + certified))) + self.commit_certification(cert) + + log_stream('Commiting identities...') + for i, idty in enumerate(identities): + log_stream('Identity {0}/{1}'.format(i, len(identities))) + try: + self._identities_repo.insert(idty) + except sqlite3.IntegrityError: + self._identities_repo.update(idty) diff --git a/src/sakia/data/processors/identities.py b/src/sakia/data/processors/identities.py index 13a343ba78eb09ef328dd70624b3d7a063172430..f85ed66f477ccbeb09ebdcc3252e3dd47cc7098a 100644 --- a/src/sakia/data/processors/identities.py +++ b/src/sakia/data/processors/identities.py @@ -3,10 +3,11 @@ import sqlite3 import logging import asyncio from ..entities import Identity +from ..connectors import BmaConnector +from ..processors import NodesProcessor from duniterpy.api import bma, errors -from duniterpy import PROTOCOL_VERSION from duniterpy.key import SigningKey -from duniterpy.documents import SelfCertification, BlockUID +from duniterpy.documents import SelfCertification, BlockUID, block_uid from aiohttp.errors import ClientError from sakia.errors import NoPeerAvailable @@ -18,6 +19,15 @@ class IdentitiesProcessor: _bma_connector = attr.ib() # :type sakia.data.connectors.bma.BmaConnector _logger = attr.ib(default=attr.Factory(lambda: logging.getLogger('sakia'))) + @classmethod + def instanciate(cls, app): + """ + Instanciate a blockchain processor + :param sakia.app.Application app: the app + """ + return cls(app.db.identities_repo, app.db.blockchains_repo, + BmaConnector(NodesProcessor(app.db.nodes_repo))) + async def find_from_pubkey(self, currency, pubkey): """ Get the list of identities corresponding to a pubkey @@ -38,11 +48,10 @@ class IdentitiesProcessor: for uid_data in uids: identity = Identity(currency, pubkey) identity.uid = uid_data['uid'] - identity.blockstamp = data['sigDate'] + identity.blockstamp = data['meta']['timestamp'] identity.signature = data['self'] if identity not in identities: identities.append(identity) - self._identities_repo.insert(identity) except (errors.DuniterError, asyncio.TimeoutError, ClientError) as e: tries += 1 self._logger.debug(str(e)) @@ -67,11 +76,12 @@ class IdentitiesProcessor: for result in data['results']: pubkey = result['pubkey'] for uid_data in result['uids']: - identity = Identity(currency, pubkey, uid_data['uid'], uid_data['sigDate']) - identity.signature = data['self'] - if identity not in identities: - identities.append(identity) - self._identities_repo.insert(identity) + if not uid_data['revoked']: + identity = Identity(currency, pubkey, uid_data['uid'], uid_data['sigDate']) + identity.signature = data['self'] + identity.blockstamp = data['meta']['timestamp'] + if identity not in identities: + identities.append(identity) except (errors.DuniterError, asyncio.TimeoutError, ClientError) as e: tries += 1 except NoPeerAvailable: @@ -116,6 +126,36 @@ class IdentitiesProcessor: except sqlite3.IntegrityError: self._identities_repo.update(identity) + async def initialize_identity(self, identity, log_stream): + """ + Initialize memberships and other data for given identity + :param sakia.data.entities.Identity identity: + :param function log_stream: + """ + log_stream("Requesting membership data") + memberships_data = await self._bma_connector.get(identity.currency, bma.blockchain.Membership, + req_args={'search': identity.pubkey}) + if block_uid(memberships_data['sigDate']) == identity.blockstamp \ + and memberships_data['uid'] == identity.uid: + for ms in memberships_data['memberships']: + if block_uid(ms['written']) > identity.membership_written_on: + identity.membership_buid = BlockUID(ms['blockNumber'], ms['blockHash']) + identity.membership_type = ms['membership'] + + if identity.membership_buid: + log_stream("Requesting membership timestamp") + ms_block_data = await self._bma_connector.get(identity.currency, bma.blockchain.Block, + req_args={'number': identity.membership_buid.number}) + if ms_block_data: + identity.membership_timestamp = ms_block_data['medianTime'] + + if memberships_data['memberships']: + log_stream("Requesting identity requirements status") + + requirements_data = await self._bma_connector.get(identity.currency, bma.wot.Requirements, + get_args={'search': identity.pubkey}) + identity.member = requirements_data['membershipExpiresIn'] > 0 and not requirements_data['outdistanced'] + async def publish_selfcert(self, currency, identity, salt, password): """ Send our self certification to a target community @@ -152,6 +192,4 @@ class IdentitiesProcessor: identity.signature = selfcert.signatures[0] identity.timestamp = timestamp - self.commit_identity(identity) - - return result \ No newline at end of file + return result, identity diff --git a/src/sakia/data/repositories/certifications.py b/src/sakia/data/repositories/certifications.py index d9413c4c8f3b1faa221305a6c9c149816b99712a..d4a5975b2ec2efcc925318f54e89c51117db3b02 100644 --- a/src/sakia/data/repositories/certifications.py +++ b/src/sakia/data/repositories/certifications.py @@ -37,7 +37,7 @@ class CertificationsRepo: currency=? AND certifier=? AND certified=? AND - blockstamp=?""", + block=?""", updated_fields + where_fields) def get_one(self, **search): @@ -94,4 +94,4 @@ class CertificationsRepo: currency=? AND certifier=? AND certified=? AND - blockstamp=?""", where_fields) + block=?""", where_fields) diff --git a/src/sakia/data/repositories/meta.sql b/src/sakia/data/repositories/meta.sql index 0a99b1a43bdfef1b9c5b73236e1093285d560e7c..b77c0d470bb5a5605a731a17d4b12d9b5164d552 100644 --- a/src/sakia/data/repositories/meta.sql +++ b/src/sakia/data/repositories/meta.sql @@ -59,11 +59,11 @@ CREATE TABLE IF NOT EXISTS certifications( currency VARCHAR(30), certifier VARCHAR(50), certified VARCHAR(50), - blockstamp VARCHAR(100), + block INT, ts INT, signature VARCHAR(100), written_on VARCHAR(100), - PRIMARY KEY (currency, certifier, certified, blockstamp) + PRIMARY KEY (currency, certifier, certified, block) ); -- TRANSACTIONS TABLE diff --git a/src/sakia/gui/dialogs/connection_cfg/controller.py b/src/sakia/gui/dialogs/connection_cfg/controller.py index 1400ce737a1949913f356c69ba44f43e9776afcd..fb81e05a37f915620a921356103824db7034eb50 100644 --- a/src/sakia/gui/dialogs/connection_cfg/controller.py +++ b/src/sakia/gui/dialogs/connection_cfg/controller.py @@ -140,7 +140,7 @@ class ConnectionConfigController(ComponentController): self._logger.debug("Connect mode") self.view.button_next.clicked.connect(self.check_connect) self.view.stacked_pages.setCurrentWidget(self.view.page_connection) - await self.step_key + connection_identity = await self.step_key self.model.commit_connection() self.view.stacked_pages.setCurrentWidget(self.view.page_services) @@ -148,17 +148,29 @@ class ConnectionConfigController(ComponentController): self.view.progress_bar.setMaximum(3) await self.model.initialize_blockchain(self.view.stream_log) self.view.progress_bar.setValue(1) - await self.model.initialize_sources(self.view.stream_log) - self.view.progress_bar.setValue(3) + + if mode in (ConnectionConfigController.REGISTER, ConnectionConfigController.CONNECT): + self.view.stream_log("Saving identity...") + self.model.commit_identity(connection_identity) + self.view.stream_log("Initializing identity informations...") + await self.model.initialize_identity(connection_identity, log_stream=self.view.stream_log) + self.view.stream_log("Initializing certifications informations...") + await self.model.initialize_certifications(connection_identity, log_stream=self.view.stream_log) + + self.view.progress_bar.setValue(2) if mode == ConnectionConfigController.REGISTER: self.view.display_info(self.tr("Broadcasting identity...")) self.view.stream_log("Broadcasting identity...") password = await self.password_asker.async_exec() - result = await self.model.publish_selfcert(self.model.connection.salt, password) + result, connection_identity = await self.model.publish_selfcert(self.model.connection.salt, password) if result[0]: self.view.show_success(self.model.notification()) else: self.view.show_error(self.model.notification(), result[1]) + + self.view.progress_bar.setValue(3) + await self.model.initialize_sources(self.view.stream_log) + self._logger.debug("Validate changes") self.accept() @@ -200,7 +212,7 @@ class ConnectionConfigController(ComponentController): password = self.view.edit_password.text() self.model.set_scrypt_infos(salt, password) self.model.set_uid(self.view.edit_account_name.text()) - registered = await self.model.check_registered() + registered, found_identity = await self.model.check_registered() self.view.button_connect.setEnabled(True) self.view.button_register.setEnabled(True) if registered[0] is False and registered[2] is None: @@ -209,7 +221,7 @@ class ConnectionConfigController(ComponentController): self.view.display_info(self.tr("""Your pubkey or UID is different on the network. Yours : {0}, the network : {1}""".format(registered[1], registered[2]))) else: - self.step_key.set_result(True) + self.step_key.set_result(found_identity) except NoPeerAvailable: self.config_dialog.label_error.setText(self.tr("Could not connect. Check node peering entry")) @@ -222,9 +234,9 @@ Yours : {0}, the network : {1}""".format(registered[1], registered[2]))) password = self.view.edit_password.text() self.model.set_scrypt_infos(salt, password) self.model.set_uid(self.view.edit_account_name.text()) - registered = await self.model.check_registered() + registered, found_identity = await self.model.check_registered() if registered[0] is False and registered[2] is None: - self.step_key.set_result(True) + self.step_key.set_result(None) elif registered[0] is False and registered[2]: self.view.display_info(self.tr("""Your pubkey or UID was already found on the network. Yours : {0}, the network : {1}""".format(registered[1], registered[2]))) diff --git a/src/sakia/gui/dialogs/connection_cfg/model.py b/src/sakia/gui/dialogs/connection_cfg/model.py index bb5e4dd39f83e6d19f9e12849976a709505eba17..e3937962dba0673f55618006ae7532de28ecd1b5 100644 --- a/src/sakia/gui/dialogs/connection_cfg/model.py +++ b/src/sakia/gui/dialogs/connection_cfg/model.py @@ -1,11 +1,12 @@ import aiohttp -from duniterpy.documents import BlockUID, BMAEndpoint, Block +from duniterpy.documents import BlockUID, BMAEndpoint from duniterpy.api import bma, errors from duniterpy.key import SigningKey -from sakia.data.entities import Connection, Identity, Blockchain, Node -from sakia.data.connectors import NodeConnector, BmaConnector -from sakia.data.processors import ConnectionsProcessor, NodesProcessor, BlockchainProcessor, SourcesProcessor +from sakia.data.entities import Connection, Identity, Node +from sakia.data.connectors import NodeConnector +from sakia.data.processors import ConnectionsProcessor, NodesProcessor, BlockchainProcessor, \ + SourcesProcessor, CertificationsProcessor from sakia.gui.component.model import ComponentModel @@ -53,6 +54,9 @@ class ConnectionConfigModel(ComponentModel): ConnectionsProcessor(self.app.db.connections_repo).commit_connection(self.connection) NodesProcessor(self.app.db.nodes_repo).commit_node(self.node_connector.node) + def commit_identity(self, identity): + self.identities_processor.commit_identity(identity) + async def initialize_blockchain(self, log_stream): """ Download blockchain information locally @@ -71,15 +75,34 @@ class ConnectionConfigModel(ComponentModel): sources_processor = SourcesProcessor.instanciate(self.app) await sources_processor.initialize_sources(self.node_connector.node.currency, self.connection.pubkey, log_stream) + async def initialize_identity(self, identity, log_stream): + """ + Download identity information locally + :param sakia.data.entities.Identity identity: the identity to initialize + :param function log_stream: a method to log data in the screen + :return: + """ + await self.identities_processor.initialize_identity(identity, log_stream) + + async def initialize_certifications(self, identity, log_stream): + """ + Download certifications information locally + :param sakia.data.entities.Identity identity: the identity to initialize + :param function log_stream: a method to log data in the screen + :return: + """ + certifications_processor = CertificationsProcessor.instanciate(self.app) + await certifications_processor.initialize_certifications(identity, log_stream) + async def publish_selfcert(self, salt, password): """" Publish the self certification of the connection identity """ return await self.identities_processor.publish_selfcert(self.node_connector.node.currency, - Identity(self.connection.currency, - self.connection.pubkey, - self.connection.uid), - salt, password) + Identity(self.connection.currency, + self.connection.pubkey, + self.connection.uid), + salt, password) async def check_registered(self): """ @@ -87,6 +110,7 @@ class ConnectionConfigModel(ComponentModel): :return: (True if found, local value, network value) """ identity = Identity(self.connection.currency, self.connection.pubkey, self.connection.uid) + found_identity = Identity(self.connection.currency, self.connection.pubkey, self.connection.uid) def _parse_uid_certifiers(data): return identity.uid == data['uid'], identity.uid, data['uid'] @@ -101,6 +125,7 @@ class ConnectionConfigModel(ComponentModel): if BlockUID.from_str(uid_data["meta"]["timestamp"]) >= timestamp: timestamp = uid_data["meta"]["timestamp"] found_uid = uid_data["uid"] + found_identity.timestamp = timestamp # We save the timestamp in the found identity return identity.uid == found_uid, identity.uid, found_uid def _parse_pubkey_certifiers(data): @@ -116,6 +141,7 @@ class ConnectionConfigModel(ComponentModel): if BlockUID.from_str(uid_data["meta"]["timestamp"]) >= timestamp: timestamp = BlockUID.from_str(uid_data["meta"]["timestamp"]) found_uid = uid_data["uid"] + found_identity.timestamp = timestamp # We save the timestamp in the found identity if found_uid == identity.uid: found_result = result['pubkey'], found_uid if found_result[1] == identity.uid: @@ -168,4 +194,5 @@ class ConnectionConfigModel(ComponentModel): } await execute_requests(pubkey_parsers, identity.uid) - return registered + return registered, found_identity + diff --git a/src/sakia/gui/navigation/model.py b/src/sakia/gui/navigation/model.py index 065118c1aeaca3a32ac5d3bfe56f30e43497766a..39ce6ccfc277bf676c484e12054c3e2aadad5e19 100644 --- a/src/sakia/gui/navigation/model.py +++ b/src/sakia/gui/navigation/model.py @@ -50,13 +50,13 @@ class NavigationModel(ComponentModel): # 'component': "TxHistory", # } # }, - # { - # 'node': { - # 'title': self.tr('Network'), - # 'icon': ':/icons/network_icon', - # 'component': "Network", - # } - # }, + { + 'node': { + 'title': self.tr('Network'), + 'icon': ':/icons/network_icon', + 'component': "Network", + } + }, # { # 'node': { # 'title': self.tr('Identities'), diff --git a/src/sakia/tests/unit/data/test_certifications_repo.py b/src/sakia/tests/unit/data/test_certifications_repo.py index fdd79d9ec4d5d9501f5b7d7955e009a4a96b0f87..082d043ec6541c4ae26c48afb49ffb59ef6775d2 100644 --- a/src/sakia/tests/unit/data/test_certifications_repo.py +++ b/src/sakia/tests/unit/data/test_certifications_repo.py @@ -7,6 +7,7 @@ import sqlite3 class TestCertificationsRepo(unittest.TestCase): def setUp(self): + sqlite3.register_adapter(BlockUID, str) self.meta_repo = SakiaDatabase(sqlite3.connect(":memory:", detect_types=sqlite3.PARSE_DECLTYPES)) self.meta_repo.prepare() self.meta_repo.upgrade_database() @@ -67,7 +68,7 @@ class TestCertificationsRepo(unittest.TestCase): certification = Certification("testcurrency", "7Aqw6Efa9EzE7gtsc8SveLLrM7gm6NEGoywSv4FJx6pZ", "FADxcH5LmXGmGFgdixSes6nWnC4Vb4pRUBYT81zQRhjn", - "20-7518C700E78B56CC21FB1DDC6CBAB24E0FACC9A798F5ED8736EA007F38617D67", + 20, 1473108382, "H41/8OGV2W4CLKbE35kk5t1HJQsb3jEM0/QGLUf80CwJvGZf3HvVCcNtHPUFoUBKEDQO9mPK3KJkqOoxHpqHCw==", None) diff --git a/src/sakia/tests/unit/data/test_identies_repo.py b/src/sakia/tests/unit/data/test_identies_repo.py index 1bcc4e2e89b77f702015da354fb90327e78ffdce..c0616f13a1a4064e2d4bcdfd5c7524f85d2d9638 100644 --- a/src/sakia/tests/unit/data/test_identies_repo.py +++ b/src/sakia/tests/unit/data/test_identies_repo.py @@ -7,6 +7,9 @@ import sqlite3 class TestIdentitiesRepo(unittest.TestCase): def setUp(self): + sqlite3.register_adapter(BlockUID, str) + sqlite3.register_adapter(bool, int) + sqlite3.register_converter("BOOLEAN", lambda v: bool(int(v))) self.meta_repo = SakiaDatabase(sqlite3.connect(":memory:", detect_types=sqlite3.PARSE_DECLTYPES)) self.meta_repo.prepare() self.meta_repo.upgrade_database()