diff --git a/src/cutecoin/core/registry/identities.py b/src/cutecoin/core/registry/identities.py index 014cf22ce0203809250f8b44a7edcfb11f647f3c..74f3d7ca7ade3e32c81c3a39783dab8604b12246 100644 --- a/src/cutecoin/core/registry/identities.py +++ b/src/cutecoin/core/registry/identities.py @@ -74,16 +74,15 @@ class IdentitiesRegistry: for result in data['results']: if result["pubkey"] == identity.pubkey: uids = result['uids'] - identity_uid = "" for uid_data in uids: if uid_data["meta"]["timestamp"] > timestamp: - timestamp = uid_data["meta"]["timestamp"] - identity_uid = uid_data["uid"] - identity.uid = identity_uid + identity.sigdate = uid_data["meta"]["timestamp"] + identity.uid = uid_data["uid"] identity.blockchain_state = BlockchainState.BUFFERED identity.local_state = LocalState.PARTIAL + timestamp = identity.sigdate return identity - except ValueError as e: + except ValueError: lookup_tries += 1 except asyncio.TimeoutError: lookup_tries += 1 @@ -95,6 +94,13 @@ class IdentitiesRegistry: @asyncio.coroutine def future_find(self, pubkey, community): + """ + + :param pubkey: The pubkey we look for + :param community: The community where we look for the identity + :return: The identity found + :rtype: cutecoin.core.registry.Identity + """ if pubkey in self._identities(community): identity = self._identities(community)[pubkey] else: @@ -103,9 +109,10 @@ class IdentitiesRegistry: tries = 0 while tries < 3 and identity.local_state == LocalState.NOT_FOUND: try: - data = yield from community.bma_access.simple_request(bma.wot.CertifiersOf, + data = yield from community.bma_access.simple_request(bma.blockchain.Membership, req_args={'search': pubkey}) identity.uid = data['uid'] + identity.sigdate = data['sigDate'] identity.local_state = LocalState.PARTIAL identity.blockchain_state = BlockchainState.VALIDATED except ValueError as e: @@ -122,34 +129,38 @@ class IdentitiesRegistry: return identity return identity - def from_handled_data(self, uid, pubkey, blockchain_state, community): + def from_handled_data(self, uid, pubkey, sigdate, blockchain_state, community): """ Get a person from a metadata dict. A metadata dict has a 'text' key corresponding to the person uid, and a 'id' key corresponding to the person pubkey. - :param dict metadata: The person metadata - :return: A new person if pubkey wasn't knwon, else the existing instance. + :param str uid: The person uid, also known as its uid on the network + :param str pubkey: The person pubkey + :param int sig_date: The date of signature of the self certification + :param LocalState local_state: The local status of the identity + :param cutecoin.core.Community community: The community from which we found data + :rtype: cutecoin.core.registry.Identity """ identities = self._identities(community) if pubkey in identities: - if self._identities(community)[pubkey].blockchain_state == BlockchainState.NOT_FOUND: - self._identities(community)[pubkey].blockchain_state = blockchain_state - elif self._identities(community)[pubkey].blockchain_state != BlockchainState.VALIDATED \ + if identities[pubkey].blockchain_state == BlockchainState.NOT_FOUND: + identities[pubkey].blockchain_state = blockchain_state + elif identities[pubkey].blockchain_state != BlockchainState.VALIDATED \ and blockchain_state == BlockchainState.VALIDATED: - self._identities(community)[pubkey].blockchain_state = blockchain_state + identities[pubkey].blockchain_state = blockchain_state + + if identities[pubkey].uid != uid: + identities[pubkey].uid = uid - # TODO: Random bug in ucoin makes the uid change without reason in requests answers - # https://github.com/ucoin-io/ucoin/issues/149 - #if self._instances[pubkey].uid != uid: - # self._instances[pubkey].uid = uid - # self._instances[pubkey].inner_data_changed.emit("BlockchainState") + if sigdate and identities[pubkey].sigdate != sigdate: + identities[pubkey].sigdate = sigdate - if self._identities(community)[pubkey].local_state == LocalState.NOT_FOUND: - self._identities(community)[pubkey].local_state = LocalState.COMPLETED + if identities[pubkey].local_state == LocalState.NOT_FOUND: + identities[pubkey].local_state = LocalState.COMPLETED - return self._identities(community)[pubkey] + return identities[pubkey] else: - identity = Identity.from_handled_data(uid, pubkey, blockchain_state) + identity = Identity.from_handled_data(uid, pubkey, sigdate, blockchain_state) self._identities(community)[pubkey] = identity return identity diff --git a/src/cutecoin/core/registry/identity.py b/src/cutecoin/core/registry/identity.py index c944dddde0548b1508a21d36d0ca4d6b0fa0c41e..784e5916a77cda864ba19a12fd61beb5cc93c743 100644 --- a/src/cutecoin/core/registry/identity.py +++ b/src/cutecoin/core/registry/identity.py @@ -14,7 +14,7 @@ from ucoinpy.api import bma as bma from ucoinpy.api.bma import PROTOCOL_VERSION from ...tools.exceptions import Error, NoPeerAvailable,\ - MembershipNotFoundError + MembershipNotFoundError, LookupFailureError from PyQt5.QtCore import QObject, pyqtSignal @@ -49,28 +49,30 @@ class Identity(QObject): """ A person with a uid and a pubkey """ - def __init__(self, uid, pubkey, local_state, blockchain_state): + def __init__(self, uid, pubkey, sigdate, local_state, blockchain_state): """ Initializing a person object. - :param str uid: The person uid, also known as its uid on the network - :param str pubkey: The person pubkey + :param str uid: The identity uid, also known as its uid on the network + :param str pubkey: The identity pubkey + :parma int sig_date: The date of signature of the self certification :param LocalState local_state: The local status of the identity :param BlockchainState blockchain_state: The blockchain status of the identity """ super().__init__() self.uid = uid self.pubkey = pubkey + self.sigdate = sigdate self.local_state = local_state self.blockchain_state = blockchain_state @classmethod def empty(cls, pubkey): - return cls("", pubkey, LocalState.NOT_FOUND, BlockchainState.NOT_FOUND) + return cls("", pubkey, None, LocalState.NOT_FOUND, BlockchainState.NOT_FOUND) @classmethod - def from_handled_data(cls, uid, pubkey, blockchain_state): - return cls(uid, pubkey, LocalState.COMPLETED, blockchain_state) + def from_handled_data(cls, uid, pubkey, sigdate, blockchain_state): + return cls(uid, pubkey, sigdate, LocalState.COMPLETED, blockchain_state) @classmethod def from_json(cls, json_data): @@ -82,10 +84,11 @@ class Identity(QObject): """ pubkey = json_data['pubkey'] uid = json_data['uid'] + sigdate = json_data['sigdate'] local_state = LocalState[json_data['local_state']] blockchain_state = BlockchainState[json_data['blockchain_state']] - return cls(uid, pubkey, local_state, blockchain_state) + return cls(uid, pubkey, sigdate, local_state, blockchain_state) @asyncio.coroutine def selfcert(self, community): @@ -97,24 +100,40 @@ class Identity(QObject): :return: A SelfCertification ucoinpy object :rtype: ucoinpy.documents.certification.SelfCertification """ - timestamp = 0 - lookup_data = yield from community.bma_access.future_request(bma.wot.Lookup, req_args={'search': self.pubkey}) - for result in lookup_data['results']: - if result["pubkey"] == self.pubkey: - uids = result['uids'] - for uid_data in uids: - if uid_data["meta"]["timestamp"] > timestamp: - timestamp = uid_data["meta"]["timestamp"] - uid = uid_data["uid"] - signature = uid_data["self"] - - return SelfCertification(PROTOCOL_VERSION, - community.currency, - self.pubkey, - timestamp, - uid, - signature) - return None + try: + timestamp = 0 + lookup_data = yield from community.bma_access.future_request(bma.wot.Lookup, + req_args={'search': self.pubkey}) + + for result in lookup_data['results']: + if result["pubkey"] == self.pubkey: + uids = result['uids'] + for uid_data in uids: + # If we sigDate was written in the blockchain + if self.sigdate and uid_data["meta"]["timestamp"] == self.sigdate: + timestamp = uid_data["meta"]["timestamp"] + uid = uid_data["uid"] + signature = uid_data["self"] + # Else we choose the latest one found + elif uid_data["meta"]["timestamp"] > timestamp: + timestamp = uid_data["meta"]["timestamp"] + uid = uid_data["uid"] + signature = uid_data["self"] + + if not self.sigdate: + self.sigdate = timestamp + + return SelfCertification(PROTOCOL_VERSION, + community.currency, + self.pubkey, + timestamp, + uid, + signature) + except ValueError as e: + if '404' in str(e): + raise LookupFailureError(self.pubkey, community) + except NoPeerAvailable: + logging.debug("No peer available") @asyncio.coroutine def get_join_date(self, community): @@ -177,6 +196,9 @@ class Identity(QObject): {'search': self.pubkey}) block_number = -1 membership_data = None + #TODO: Should not be here, should be set when we look for the identity + #We do it because we do not have this info in certifiers-of yet... + self.sigdate = search['sigDate'] for ms in search['memberships']: if ms['blockNumber'] > block_number: block_number = ms['blockNumber'] @@ -273,6 +295,7 @@ class Identity(QObject): certifier = {} certifier['identity'] = identities_registry.from_handled_data(certifier_data['uid'], certifier_data['pubkey'], + None, BlockchainState.VALIDATED, community) certifier['cert_time'] = certifier_data['cert_time']['medianTime'] @@ -303,6 +326,7 @@ class Identity(QObject): certifier['identity'] = identities_registry.\ from_handled_data(uid, certifier_data['pubkey'], + None, BlockchainState.BUFFERED, community) block = yield from community.bma_access.future_request(bma.blockchain.Block, @@ -367,6 +391,7 @@ class Identity(QObject): certified = {} certified['identity'] = identities_registry.from_handled_data(certified_data['uid'], certified_data['pubkey'], + None, BlockchainState.VALIDATED, community) certified['cert_time'] = certified_data['cert_time']['medianTime'] @@ -390,6 +415,7 @@ class Identity(QObject): certified = {} certified['identity'] = identities_registry.from_handled_data(certified_data['uid'], certified_data['pubkey'], + None, BlockchainState.BUFFERED, community) certified['cert_time'] = certified_data['meta']['timestamp'] @@ -459,12 +485,14 @@ class Identity(QObject): """ data = {'uid': self.uid, 'pubkey': self.pubkey, + 'sigdate': self.sigdate, 'local_state': self.local_state.name, 'blockchain_state': self.blockchain_state.name} return data def __str__(self): - return "{0} - {1} - {2} - {3}".format(self.uid, - self.pubkey, - self.local_state, - self.blockchain_state) + return "{uid} - {pubkey} - {sigdate} - {local} - {blockchain}".format(uid=self.uid, + pubkey=self.pubkey, + sigdate=self.sigdate, + local=self.local_state, + blockchain=self.blockchain_state) diff --git a/src/cutecoin/gui/identities_tab.py b/src/cutecoin/gui/identities_tab.py index ed4515044923af0dd9a90b2f0a3df6502676dbb3..bf03857d2cc48854a4fc5353819e1853edf6a2cc 100644 --- a/src/cutecoin/gui/identities_tab.py +++ b/src/cutecoin/gui/identities_tab.py @@ -20,7 +20,7 @@ from .member import MemberDialog from .transfer import TransferMoneyDialog from cutecoin.gui.widgets.busy import Busy from .certification import CertificationDialog -from ..core.registry import Identity +from ..core.registry import Identity, BlockchainState from ..tools.exceptions import NoPeerAvailable from ..tools.decorators import asyncify, once_at_a_time, cancel_once_task @@ -210,8 +210,12 @@ class IdentitiesTabWidget(QWidget, Ui_IdentitiesTab): response = yield from self.community.bma_access.future_request(bma.wot.Lookup, {'search': text}) identities = [] for identity_data in response['results']: - identity = yield from self.app.identities_registry.future_find(identity_data['pubkey'], self.community) - identities.append(identity) + for uid_data in identity_data['uids']: + identity = Identity.from_handled_data(uid_data['uid'], + identity_data['pubkey'], + uid_data['meta']['timestamp'], + BlockchainState.BUFFERED) + identities.append(identity) self.edit_textsearch.clear() yield from self.refresh_identities(identities) diff --git a/src/cutecoin/models/identities.py b/src/cutecoin/models/identities.py index 8e238d87ccbd0bf45db6923e49c03af18080d15a..bc994d85dc4765317c45a20e7744bc26231e6324 100644 --- a/src/cutecoin/models/identities.py +++ b/src/cutecoin/models/identities.py @@ -52,8 +52,9 @@ class IdentitiesFilterProxyModel(QSortFilterProxyModel): if expiration_data is not None: will_expire_soon = (current_time > expiration_data*1000 - warning_expiration_time*1000) if role == Qt.DisplayRole: - if source_index.column() == self.sourceModel().columns_ids.index('renewed') \ - or source_index.column() == self.sourceModel().columns_ids.index('expiration'): + if source_index.column() in (self.sourceModel().columns_ids.index('renewed'), + self.sourceModel().columns_ids.index('expiration'), + self.sourceModel().columns_ids.index('publication')): if source_data is not None: return QLocale.toString( QLocale(), @@ -90,8 +91,9 @@ class IdentitiesTableModel(QAbstractTableModel): 'pubkey': self.tr('Pubkey'), 'renewed': self.tr('Renewed'), 'expiration': self.tr('Expiration'), - 'validation': self.tr('Validation')} - self.columns_ids = ('uid', 'pubkey', 'renewed', 'expiration') + 'publication': self.tr('Publication'), + 'validation': self.tr('Validation'),} + self.columns_ids = ('uid', 'pubkey', 'renewed', 'expiration', 'publication') self.identities_data = [] self._sig_validity = 0 @@ -126,7 +128,7 @@ class IdentitiesTableModel(QAbstractTableModel): join_date = None expiration_date = None - return identity.uid, identity.pubkey, join_date, expiration_date + return identity.uid, identity.pubkey, join_date, expiration_date, identity.sigdate @asyncio.coroutine def refresh_identities(self, identities): diff --git a/src/cutecoin/tests/core/test_identities.py b/src/cutecoin/tests/core/test_identities.py index 330803092329888d8dc40ef407dc11b12c4e568d..5b862cdb7d733c8953f11d35888eb2275d89b5f9 100644 --- a/src/cutecoin/tests/core/test_identities.py +++ b/src/cutecoin/tests/core/test_identities.py @@ -26,7 +26,7 @@ class TestIdentity(unittest.TestCase): community = mock.MagicMock() type(community).currency = mock.PropertyMock(return_value="test_currency") - identity = Identity("john", "7Aqw6Efa9EzE7gtsc8SveLLrM7gm6NEGoywSv4FJx6pZ", + identity = Identity("john", "7Aqw6Efa9EzE7gtsc8SveLLrM7gm6NEGoywSv4FJx6pZ", None, LocalState.COMPLETED, BlockchainState.VALIDATED) test_instances = { "test_currency": {"7Aqw6Efa9EzE7gtsc8SveLLrM7gm6NEGoywSv4FJx6pZ": identity} @@ -34,7 +34,9 @@ class TestIdentity(unittest.TestCase): identities_registry = IdentitiesRegistry(test_instances) identity_from_data = identities_registry.from_handled_data("john", - "7Aqw6Efa9EzE7gtsc8SveLLrM7gm6NEGoywSv4FJx6pZ", BlockchainState.VALIDATED, + "7Aqw6Efa9EzE7gtsc8SveLLrM7gm6NEGoywSv4FJx6pZ", + None, + BlockchainState.VALIDATED, community) self.assertEqual(identity, identity_from_data)