diff --git a/src/cutecoin/core/account.py b/src/cutecoin/core/account.py index c83ae933303a9150ade0864933835eab2bedfaea..0effe4565f2a3d0127460ca3f87643189f08d494 100644 --- a/src/cutecoin/core/account.py +++ b/src/cutecoin/core/account.py @@ -324,42 +324,10 @@ class Account(QObject): :param cutecoin.core.community.Community community: The community target of the certification :param str pubkey: The certified identity pubkey """ - blockid = "" - selfcert = None - - def build_certification_data(reply): - if reply.error() == QNetworkReply.NoError: - strdata = bytes(reply.readAll()).decode('utf-8') - json_data = json.loads(strdata) - nonlocal blockid - blockid = community.blockid(json_data) - future_certdata.set_result(True) - - def build_certification_selfcert(reply): - if reply.error() == QNetworkReply.NoError: - strdata = bytes(reply.readAll()).decode('utf-8') - json_data = json.loads(strdata) - nonlocal selfcert - selfcert = identity.selfcert(community, json_data) - future_selfcert.set_result(True) - logging.debug("Certdata") - future_certdata = asyncio.Future() - reply = community.bma_access.request(qtbma.blockchain.Current) - reply.finished.connect(lambda: build_certification_data(reply)) - yield from future_certdata - - logging.debug("Identity") + blockid = yield from community.blockid() identity = yield from self._identities_registry.future_lookup(pubkey, community) - logging.debug("YEILDDDDDDDDDdD") - - logging.debug("Selfcert") - future_selfcert = asyncio.Future() - reply = community.bma_access.request( qtbma.wot.Lookup, req_args={'search': pubkey}) - reply.finished.connect(lambda: build_certification_selfcert(reply)) - yield from future_selfcert - - logging.debug("End") + selfcert = yield from identity.selfcert(community) certification = Certification(PROTOCOL_VERSION, community.currency, self.pubkey, pubkey, blockid['number'], blockid['hash'], None) @@ -485,40 +453,12 @@ class Account(QObject): :param community: The community target of the membership document :param str mstype: The type of membership demand. "IN" to join, "OUT" to leave ''' - blockid = "" - selfcert = None logging.debug("Send membership") - def build_membership_data(reply): - if reply.error() == QNetworkReply.NoError: - strdata = bytes(reply.readAll()).decode('utf-8') - json_data = json.loads(strdata) - nonlocal blockid - blockid = community.blockid(json_data) - future_msdata.set_result(True) - else: - raise ConnectionError(self.tr("Failed to get data build membership document")) - - def build_selfcert(reply): - if reply.error() == QNetworkReply.NoError: - strdata = bytes(reply.readAll()).decode('utf-8') - json_data = json.loads(strdata) - nonlocal selfcert - selfcert = self.identity(community).selfcert(community, json_data) - future_selfcert.set_result(True) - else: - raise ConnectionError(self.tr("Failed to get data build membership document")) - - future_msdata = asyncio.Future() - reply = community.bma_access.request(qtbma.blockchain.Current) - reply.finished.connect(lambda: build_membership_data(reply)) - logging.debug("msdata") - yield from future_msdata - future_selfcert = asyncio.Future() - reply = community.bma_access.request( qtbma.wot.Lookup, req_args={'search': self.pubkey}) - reply.finished.connect(lambda: build_selfcert(reply)) - logging.debug("selfcert") - yield from future_selfcert + blockid = yield from community.blockid() + self_identity = yield from self._identities_registry.future_lookup(self.pubkey, community) + selfcert = yield from self_identity.selfcert(community) + membership = Membership(PROTOCOL_VERSION, community.currency, selfcert.pubkey, blockid['number'], blockid['hash'], mstype, selfcert.uid, diff --git a/src/cutecoin/core/community.py b/src/cutecoin/core/community.py index 3bcb11dc0dde06b20e95f0191ab568438e79ef05..5a0eb89d7ffaac755ddd8e48c4e986ee77d5bcab 100644 --- a/src/cutecoin/core/community.py +++ b/src/cutecoin/core/community.py @@ -8,6 +8,7 @@ import logging import hashlib import re import time +import asyncio from PyQt5.QtCore import QObject, pyqtSignal from requests.exceptions import RequestException @@ -247,12 +248,14 @@ class Community(QObject): req_args={'number': number}) return data - def blockid(self, block): + @asyncio.coroutine + def blockid(self): ''' Get the block id. :return: The current block ID as [NUMBER-HASH] format. ''' + block = yield from self.bma_access.future_request(qtbma.blockchain.Current) signed_raw = "{0}{1}\n".format(block['raw'], block['signature']) block_hash = hashlib.sha1(signed_raw.encode("ascii")).hexdigest().upper() block_number = block['number'] diff --git a/src/cutecoin/core/net/api/bma/access.py b/src/cutecoin/core/net/api/bma/access.py index 8d6ce80d1927217911118303d130489e63a3ae90..6294ea4157d645c9b1cd15f56dc0972f62aeb9fc 100644 --- a/src/cutecoin/core/net/api/bma/access.py +++ b/src/cutecoin/core/net/api/bma/access.py @@ -4,6 +4,7 @@ from . import blockchain, network, node, tx, wot, ConnectionHandler from .....tools.exceptions import NoPeerAvailable import logging import json +import asyncio import random class BmaAccess(QObject): @@ -66,17 +67,22 @@ class BmaAccess(QObject): 'value': data[d]}) return entries - def get(self, caller, request, req_args={}, get_args={}, tries=0): - """ - Get Json data from the specified URL - :rtype : dict - """ - cache_key = (str(request), + @staticmethod + def _gen_cache_key(request, req_args, get_args): + return (str(request), str(tuple(frozenset(sorted(req_args.keys())))), str(tuple(frozenset(sorted(req_args.values())))), str(tuple(frozenset(sorted(get_args.keys())))), str(tuple(frozenset(sorted(get_args.values()))))) + def _get_from_cache(self, request, req_args, get_args): + """ + Get data from the cache + :param request: The requested data + :param cache_key: The key + :return: + """ + cache_key = BmaAccess._gen_cache_key(request, req_args, get_args) if cache_key in self._data.keys(): need_reload = False if 'metadata' in self._data[cache_key]: @@ -89,6 +95,50 @@ class BmaAccess(QObject): else: need_reload = True ret_data = request.null_value + return need_reload, ret_data + + def _update_cache(self, request, req_args, get_args, data): + """ + Update data in cache and returns True if cached data changed + :param class request: A bma request class calling for data + :param dict req_args: Arguments to pass to the request constructor + :param dict get_args: Arguments to pass to the request __get__ method + :param dict data: Json data to save in cache + :return: True if data changed + :rtype: bool + """ + cache_key = BmaAccess._gen_cache_key(request, req_args, get_args) + if cache_key not in self._data: + self._data[cache_key] = {} + + if 'metadata' not in self._data[cache_key]: + self._data[cache_key]['metadata'] = {} + + if 'value' not in self._data[cache_key]: + self._data[cache_key]['value'] = {} + self._data[cache_key]['metadata']['block'] = self._network.latest_block + + if self._data[cache_key]['value'] != data: + self._data[cache_key]['value'] = data + return True + return False + + def get(self, caller, request, req_args={}, get_args={}, tries=0): + """ + Get Json data from the specified URL and emit "inner_data_changed" + on the caller if the data changed. + + :param PyQt5.QtCore.QObject caller: The objet calling + :param class request: A bma request class calling for data + :param dict req_args: Arguments to pass to the request constructor + :param dict get_args: Arguments to pass to the request __get__ method + :return: The cached data + :rtype: dict + """ + data = self._get_from_cache(request, req_args, get_args) + need_reload = data[0] + ret_data = data[1] + cache_key = BmaAccess._gen_cache_key(request, req_args, get_args) if need_reload: #Move to network nstead of community @@ -99,16 +149,53 @@ class BmaAccess(QObject): self._pending_requests[cache_key].append(caller) logging.debug("Callers".format(self._pending_requests[cache_key])) else: - reply = self.request(request, req_args, get_args) + reply = self.simple_request(request, req_args, get_args) logging.debug("New pending request {0}, caller {1}".format(cache_key, caller)) self._pending_requests[cache_key] = [caller] reply.finished.connect(lambda: self.handle_reply(request, req_args, get_args, tries)) return ret_data - def request(self, request, req_args={}, get_args={}): + def future_request(self, request, req_args={}, get_args={}): ''' - Start a request to the network. + Start a request to the network and returns a future. + + :param class request: A bma request class calling for data + :param dict req_args: Arguments to pass to the request constructor + :param dict get_args: Arguments to pass to the request __get__ method + :return: The future data + :rtype: dict + ''' + def handle_future_reply(reply): + if reply.error() == QNetworkReply.NoError: + strdata = bytes(reply.readAll()).decode('utf-8') + json_data = json.loads(strdata) + self._update_cache(request, req_args, get_args, json_data) + future_data.set_result(json_data) + + future_data = asyncio.Future() + data = self._get_from_cache(request, req_args, get_args) + need_reload = data[0] + + if need_reload: + nodes = self._network.synced_nodes + if len(nodes) > 0: + node = random.choice(nodes) + server = node.endpoint.conn_handler().server + port = node.endpoint.conn_handler().port + conn_handler = ConnectionHandler(self._network.network_manager, server, port) + req = request(conn_handler, **req_args) + reply = req.get(**get_args) + reply.finished.connect(lambda: handle_future_reply(reply)) + else: + raise NoPeerAvailable(self.currency, len(nodes)) + else: + future_data.set_result(data[1]) + return future_data + + def simple_request(self, request, req_args={}, get_args={}): + ''' + Start a request to the network but don't cache its result. :param class request: A bma request class calling for data :param dict req_args: Arguments to pass to the request constructor @@ -131,31 +218,12 @@ class BmaAccess(QObject): def handle_reply(self, request, req_args, get_args, tries): reply = self.sender() logging.debug("Handling QtNetworkReply for {0}".format(str(request))) - cache_key = (str(request), - str(tuple(frozenset(sorted(req_args.keys())))), - str(tuple(frozenset(sorted(req_args.values())))), - str(tuple(frozenset(sorted(get_args.keys())))), - str(tuple(frozenset(sorted(get_args.values()))))) + cache_key = BmaAccess._gen_cache_key(request, req_args, get_args) + if reply.error() == QNetworkReply.NoError: strdata = bytes(reply.readAll()).decode('utf-8') json_data = json.loads(strdata) - #logging.debug("Data in reply : {0}".format(strdata)) - - if cache_key not in self._data: - self._data[cache_key] = {} - - if 'metadata' not in self._data[cache_key]: - self._data[cache_key]['metadata'] = {} - - if 'value' not in self._data[cache_key]: - self._data[cache_key]['value'] = {} - self._data[cache_key]['metadata']['block'] = self._network.latest_block - - change = False - if self._data[cache_key]['value'] != json_data: - change = True - if change: - self._data[cache_key]['value'] = json_data + if self._update_cache(request, req_args, get_args): logging.debug(self._pending_requests.keys()) for caller in self._pending_requests[cache_key]: logging.debug("Emit change for {0} : {1} ".format(caller, request)) diff --git a/src/cutecoin/core/registry/identities.py b/src/cutecoin/core/registry/identities.py index 9c618c1603c1ef183caf4184fda536e23aee47a3..753ca38476c76cbe54921bbbfe0a674a74a7700f 100644 --- a/src/cutecoin/core/registry/identities.py +++ b/src/cutecoin/core/registry/identities.py @@ -62,7 +62,7 @@ class IdentitiesRegistry: else: identity = Identity.empty(pubkey) self._instances[pubkey] = identity - reply = community.bma_access.request(qtbma.wot.Lookup, req_args={'search': pubkey}) + reply = community.bma_access.simple_request(qtbma.wot.Lookup, req_args={'search': pubkey}) reply.finished.connect(lambda: self.handle_lookup(reply, identity)) return identity @@ -94,7 +94,7 @@ class IdentitiesRegistry: else: identity = Identity.empty(pubkey) self._instances[pubkey] = identity - reply = community.bma_access.request(qtbma.wot.Lookup, req_args={'search': pubkey}) + reply = community.bma_access.simple_request(qtbma.wot.Lookup, req_args={'search': pubkey}) reply.finished.connect(lambda: handle_reply(reply)) logging.debug("Return") yield from future_identity diff --git a/src/cutecoin/core/registry/identity.py b/src/cutecoin/core/registry/identity.py index 32d94f928d82459005d240ef3fbfd7925a3e9421..ad26e014a25719491ab1a6a08f9f40bd289cf5f9 100644 --- a/src/cutecoin/core/registry/identity.py +++ b/src/cutecoin/core/registry/identity.py @@ -6,6 +6,7 @@ Created on 11 févr. 2014 import logging import time +import asyncio from ucoinpy.documents.certification import SelfCertification from cutecoin.tools.exceptions import Error, NoPeerAvailable,\ @@ -60,7 +61,8 @@ class Identity(QObject): return cls(uid, pubkey, status) - def selfcert(self, community, lookup_data): + @asyncio.coroutine + def selfcert(self, community): """ Get the identity self certification. This request is not cached in the person object. @@ -70,7 +72,7 @@ class Identity(QObject): :rtype: ucoinpy.documents.certification.SelfCertification """ timestamp = 0 - + lookup_data = yield from community.bma_access.future_request(qtbma.wot.Lookup, req_args={'search': self.pubkey}) for result in lookup_data['results']: if result["pubkey"] == self.pubkey: uids = result['uids'] @@ -86,6 +88,7 @@ class Identity(QObject): timestamp, uid, signature) + return None def get_join_date(self, community): """ diff --git a/src/cutecoin/core/wallet.py b/src/cutecoin/core/wallet.py index 8053cf60c2ed50d9de91447ca3a4341e85d3b7c7..6d757faa9ec93766a355d724276884a467159805 100644 --- a/src/cutecoin/core/wallet.py +++ b/src/cutecoin/core/wallet.py @@ -138,7 +138,7 @@ class Cache(): # Lets look if transactions took too long to be validated awaiting = [t for t in self._transfers if t.state == Transfer.AWAITING] - tx_history = community.bma_access.request(qtbma.tx.history.Blocks, + tx_history = community.bma_access.simple_request(qtbma.tx.history.Blocks, req_args={'pubkey': self.wallet.pubkey, 'from_':self.latest_block, 'to_': self.current_block}) @@ -352,7 +352,7 @@ class Wallet(QObject): ''' time = community.get_block().mediantime block_number = community.current_blockid()['number'] - block = community.request(bma.blockchain.Block, + block = community.simple_request(bma.blockchain.Block, req_args={'number': block_number}) txid = len(block['transactions']) key = None diff --git a/src/cutecoin/gui/wot_tab.py b/src/cutecoin/gui/wot_tab.py index 4d1854ebe4b2c2b1ad7944e757025686673e5340..679c12fffa11531d214bbd6e4b6df2aa5aac4865 100644 --- a/src/cutecoin/gui/wot_tab.py +++ b/src/cutecoin/gui/wot_tab.py @@ -135,7 +135,7 @@ class WotTabWidget(QWidget, Ui_WotTabWidget): if len(text) < 2: return False try: - response = self.community.request(bma.wot.Lookup, {'search': text}) + response = self.community.simple_request(bma.wot.Lookup, {'search': text}) except Exception as e: logging.debug('bma.wot.Lookup request error : ' + str(e)) return False