From eba7d956b6580f847eb4baaf3e833ccccfd9c4e9 Mon Sep 17 00:00:00 2001 From: Inso <insomniak.fr@gmail.com> Date: Sat, 20 Jun 2015 11:53:49 +0200 Subject: [PATCH] Handle Membership document broadcast --- src/cutecoin/core/account.py | 94 +++++++++++++---------- src/cutecoin/core/community.py | 54 ++----------- src/cutecoin/core/net/api/bma/__init__.py | 2 +- src/cutecoin/core/net/api/bma/access.py | 26 +++++++ src/cutecoin/core/registry/identity.py | 40 +++++----- src/cutecoin/gui/community_tab.py | 46 +++++------ 6 files changed, 130 insertions(+), 132 deletions(-) diff --git a/src/cutecoin/core/account.py b/src/cutecoin/core/account.py index 256eb7fc..524c4de4 100644 --- a/src/cutecoin/core/account.py +++ b/src/cutecoin/core/account.py @@ -4,8 +4,6 @@ Created on 1 févr. 2014 @author: inso """ -from ucoinpy import PROTOCOL_VERSION -from ucoinpy.api import bma from ucoinpy.documents.certification import SelfCertification, Certification, Revocation from ucoinpy.documents.membership import Membership from ucoinpy.key import SigningKey @@ -13,14 +11,17 @@ from ucoinpy.key import SigningKey import logging import time import math +import json from PyQt5.QtCore import QObject, pyqtSignal, QCoreApplication, QT_TRANSLATE_NOOP +from PyQt5.QtNetwork import QNetworkReply from .wallet import Wallet from .community import Community from .registry import Identity, IdentitiesRegistry from ..tools.exceptions import ContactAlreadyExists - +from ..core.net.api import bma as qtbma +from ..core.net.api.bma import PROTOCOL_VERSION def quantitative(units, community): """ @@ -120,6 +121,7 @@ class Account(QObject): loading_progressed = pyqtSignal(int, int) inner_data_changed = pyqtSignal(str) wallets_changed = pyqtSignal() + document_broadcasted = pyqtSignal(str) def __init__(self, salt, pubkey, name, communities, wallets, contacts, identities_registry): ''' @@ -337,7 +339,7 @@ class Account(QObject): 'self_': selfcert.signed_raw(), 'other': "{0}\n".format(certification.inline())} logging.debug("Posted data : {0}".format(data)) - community.broadcast(bma.wot.Add, {}, data) + community.broadcast(qtbma.wot.Add, {}, data) def revoke(self, password, community): """ @@ -364,7 +366,7 @@ class Account(QObject): 'sig': revocation.signatures[0] } logging.debug("Posted data : {0}".format(data)) - community.broadcast(bma.wot.Revoke, {}, data) + community.broadcast(qtbma.wot.Revoke, {}, data) def transfers(self, community): ''' @@ -392,27 +394,6 @@ class Account(QObject): value += w.value(community) return value - def published_uid(self, community): - ''' - Check if this account identity is a member of a community - - :param community: The target community of this request - :return: True if the account is a member of the target community - ''' - self_person = self._identities_registry.lookup(self.pubkey, community) - return self_person.published_uid(community) - - def member_of(self, community): - ''' - Check if this account identity is a member of a community - - :param community: The target community of this request - :return: True if the account is a member of the target community - ''' - self_person = self._identities_registry.lookup(self.pubkey, community) - logging.debug("Self person : {0}".format(self_person.uid)) - return self_person.is_member(community) - def send_selfcert(self, password, community): ''' Send our self certification to a target community @@ -429,7 +410,7 @@ class Account(QObject): key = SigningKey(self.salt, password) selfcert.sign([key]) logging.debug("Key publish : {0}".format(selfcert.signed_raw())) - community.broadcast(bma.wot.Add, {}, {'pubkey': self.pubkey, + community.broadcast(qtbma.wot.Add, {}, {'pubkey': self.pubkey, 'self_': selfcert.signed_raw(), 'other': []}) @@ -441,20 +422,55 @@ 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 ''' - self_ = self._identities_registry.lookup(self.pubkey, community) - selfcert = self_.selfcert(community) + reply = community.bma_access.request(qtbma.blockchain.Current) + reply.finished.connect(lambda: self.__build_membership_data(password, community, mstype, reply)) + + def __build_membership_data(self, password, community, mstype, reply): + if reply.error() == QNetworkReply.NoError: + strdata = bytes(reply.readAll()).decode('utf-8') + json_data = json.loads(strdata) + blockid = community.blockid(json_data) + reply = community.bma_access.request( qtbma.wot.Lookup, req_args={'search': self.pubkey}) + reply.finished.connect(lambda: self.__broadcast_membership(community, blockid, mstype, password, reply)) + else: + raise ConnectionError(self.tr("Failed to get data build membership document")) + + def __broadcast_membership(self, community, blockid, mstype, password, reply): + if reply.error() == QNetworkReply.NoError: + strdata = bytes(reply.readAll()).decode('utf-8') + json_data = json.loads(strdata) + selfcert = self.identity(community).selfcert(community, json_data) + membership = Membership(PROTOCOL_VERSION, community.currency, + selfcert.pubkey, blockid['number'], + blockid['hash'], mstype, selfcert.uid, + selfcert.timestamp, None) + key = SigningKey(self.salt, password) + membership.sign([key]) + logging.debug("Membership : {0}".format(membership.signed_raw())) + replies = community.bma_access.broadcast(qtbma.blockchain.Membership, {}, + {'membership': membership.signed_raw()}) + for r in replies: + r.finished.connect(lambda reply=r: self.__handle_broadcast_replies(replies, reply)) + else: + raise ConnectionError(self.tr("Failed to get data build membership document")) - blockid = community.current_blockid() + def __handle_broadcast_replies(self, replies, reply): + """ + Handle the reply, if the request was accepted, disconnect + all other replies - membership = Membership(PROTOCOL_VERSION, community.currency, - selfcert.pubkey, blockid['number'], - blockid['hash'], mstype, selfcert.uid, - selfcert.timestamp, None) - key = SigningKey(self.salt, password) - membership.sign([key]) - logging.debug("Membership : {0}".format(membership.signed_raw())) - community.broadcast(bma.blockchain.Membership, {}, - {'membership': membership.signed_raw()}) + :param QNetworkReply reply: The reply of this handler + :param list of QNetworkReply replies: All request replies + :return: + """ + if reply.error() == QNetworkReply.NoError: + self.document_broadcasted.emit("Membership") + for r in replies: + try: + r.disconnect() + except TypeError as e: + if "disconnect()" in str(e): + logging.debug("Could not disconnect a reply") def jsonify(self): ''' diff --git a/src/cutecoin/core/community.py b/src/cutecoin/core/community.py index 84e6cd63..3bcb11dc 100644 --- a/src/cutecoin/core/community.py +++ b/src/cutecoin/core/community.py @@ -247,23 +247,15 @@ class Community(QObject): req_args={'number': number}) return data - def current_blockid(self): + def blockid(self, block): ''' - Get the current block id. + Get the block id. :return: The current block ID as [NUMBER-HASH] format. ''' - try: - block = self.bma_access.get(self, qtbma.blockchain.Current, cached=False) - signed_raw = "{0}{1}\n".format(block['raw'], block['signature']) - block_hash = hashlib.sha1(signed_raw.encode("ascii")).hexdigest().upper() - block_number = block['number'] - except ValueError as e: - if '404' in str(e): - block_number = 0 - block_hash = "DA39A3EE5E6B4B0D3255BFEF95601890AFD80709" - else: - raise + signed_raw = "{0}{1}\n".format(block['raw'], block['signature']) + block_hash = hashlib.sha1(signed_raw.encode("ascii")).hexdigest().upper() + block_number = block['number'] return {'number': block_number, 'hash': block_hash} def members_pubkeys(self): @@ -305,42 +297,6 @@ class Community(QObject): continue raise NoPeerAvailable(self.currency, len(nodes)) - def broadcast(self, request, req_args={}, post_args={}): - ''' - Broadcast data to a community. - Sends the data to all knew nodes. - - :param request: A ucoinpy bma request class - :param req_args: Arguments to pass to the request constructor - :param post_args: Arguments to pass to the request __post__ method - :return: The returned data - - .. note:: If one node accept the requests (returns 200), - the broadcast is considered accepted by the network. - ''' - tries = 0 - ok = False - value_error = None - nodes = self._network.online_nodes - for node in nodes: - logging.debug("Trying to connect to : " + node.pubkey) - req = request(node.endpoint.conn_handler(), **req_args) - try: - req.post(**post_args) - ok = True - except ValueError as e: - value_error = e - continue - except RequestException: - tries = tries + 1 - continue - - if not ok: - raise value_error - - if tries == len(nodes): - raise NoPeerAvailable(self.currency, len(nodes)) - def jsonify(self): ''' Jsonify the community datas. diff --git a/src/cutecoin/core/net/api/bma/__init__.py b/src/cutecoin/core/net/api/bma/__init__.py index e00fc569..0fe95df4 100644 --- a/src/cutecoin/core/net/api/bma/__init__.py +++ b/src/cutecoin/core/net/api/bma/__init__.py @@ -122,7 +122,7 @@ class API(object): request.setHeader(QNetworkRequest.ContentTypeHeader, "application/x-www-form-urlencoded") reply = self.conn_handler.network_manager.post(request, - post_data.toString(QUrl.FullyEncoded).toUtf8()) + post_data.toString(QUrl.FullyEncoded)) return reply diff --git a/src/cutecoin/core/net/api/bma/access.py b/src/cutecoin/core/net/api/bma/access.py index aefdb481..8d6ce80d 100644 --- a/src/cutecoin/core/net/api/bma/access.py +++ b/src/cutecoin/core/net/api/bma/access.py @@ -167,3 +167,29 @@ class BmaAccess(QObject): self._pending_requests.pop(cache_key) for caller in self._pending_requests[cache_key]: self.get(caller, request, req_args, get_args) + + def broadcast(self, request, req_args={}, post_args={}): + ''' + Broadcast data to a network. + Sends the data to all knew nodes. + + :param request: A ucoinpy bma request class + :param req_args: Arguments to pass to the request constructor + :param post_args: Arguments to pass to the request __post__ method + :return: All nodes replies + :rtype: tuple of QNetworkReply + + .. note:: If one node accept the requests (returns 200), + the broadcast should be considered accepted by the network. + ''' + nodes = self._network.online_nodes + replies = [] + for node in nodes: + logging.debug("Trying to connect to : " + node.pubkey) + 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.post(**post_args) + replies.append(reply) + return tuple(replies) diff --git a/src/cutecoin/core/registry/identity.py b/src/cutecoin/core/registry/identity.py index eb9cb649..32d94f92 100644 --- a/src/cutecoin/core/registry/identity.py +++ b/src/cutecoin/core/registry/identity.py @@ -60,34 +60,32 @@ class Identity(QObject): return cls(uid, pubkey, status) - def selfcert(self, community): + def selfcert(self, community, lookup_data): """ - Get the person self certification. + Get the identity self certification. This request is not cached in the person object. :param cutecoin.core.community.Community community: The community target to request the self certification :return: A SelfCertification ucoinpy object :rtype: ucoinpy.documents.certification.SelfCertification """ - data = community.bma_access.get(self, qtbma.wot.Lookup, req_args={'search': self.pubkey}) - if data != qtbma.wot.Lookup.null_value: - timestamp = 0 - - for result in 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) + timestamp = 0 + + 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) def get_join_date(self, community): """ diff --git a/src/cutecoin/gui/community_tab.py b/src/cutecoin/gui/community_tab.py index 8ead8516..fe717efe 100644 --- a/src/cutecoin/gui/community_tab.py +++ b/src/cutecoin/gui/community_tab.py @@ -68,6 +68,7 @@ class CommunityTabWidget(QWidget, Ui_CommunityTabWidget): self.account.identity(self.community).inner_data_changed.connect(self.handle_account_identity_change) self.search_direct_connections() + self.account.document_broadcasted.connect(self.display_broadcast_toast) self.refresh_quality_buttons() @@ -80,13 +81,12 @@ class CommunityTabWidget(QWidget, Ui_CommunityTabWidget): pubkey_index = model.sourceModel().index(source_index.row(), pubkey_col) pubkey = model.sourceModel().data(pubkey_index, Qt.DisplayRole) - identity = self.app.identities_registry(pubkey, self.community) + identity = self.app.identities_registry.lookup(pubkey, self.community) menu = QMenu(self) informations = QAction(self.tr("Informations"), self) informations.triggered.connect(self.menu_informations) informations.setData(identity) - add_contact = QAction(self.tr("Add as contact"), self) add_contact.triggered.connect(self.menu_add_as_contact) add_contact.setData(identity) @@ -169,6 +169,10 @@ class CommunityTabWidget(QWidget, Ui_CommunityTabWidget): index_wot_tab = self.tabs_information.indexOf(self.wot_tab) self.tabs_information.setCurrentIndex(index_wot_tab) + @pyqtSlot(str) + def display_broadcast_toast(self, document): + toast.display(document, self.tr("Success sending {0} demand".format(document))) + def send_membership_demand(self): password = self.password_asker.exec_() if self.password_asker.result() == QDialog.Rejected: @@ -176,7 +180,6 @@ class CommunityTabWidget(QWidget, Ui_CommunityTabWidget): try: self.account.send_membership(password, self.community, 'IN') - toast.display(self.tr("Membership"), self.tr("Success sending membership demand")) except ValueError as e: QMessageBox.critical(self, self.tr("Join demand error"), str(e)) @@ -188,10 +191,10 @@ You can't request a membership.""")) QMessageBox.critical(self, self.tr("Network error"), self.tr("Couldn't connect to network : {0}").format(e), QMessageBox.Ok) - except Exception as e: - QMessageBox.critical(self, "Error", - "{0}".format(e), - QMessageBox.Ok) + # except Exception as e: + # QMessageBox.critical(self, "Error", + # "{0}".format(e), + # QMessageBox.Ok) def send_membership_leaving(self): reply = QMessageBox.warning(self, self.tr("Warning"), @@ -206,7 +209,6 @@ The process to join back the community later will have to be done again.""") try: self.account.send_membership(password, self.community, 'OUT') - toast.display(self.tr("Membership"), self.tr("Success sending leaving demand")) except ValueError as e: QMessageBox.critical(self, self.tr("Leaving demand error"), str(e)) @@ -214,10 +216,10 @@ The process to join back the community later will have to be done again.""") QMessageBox.critical(self, self.tr("Network error"), self.tr("Couldn't connect to network : {0}").format(e), QMessageBox.Ok) - except Exception as e: - QMessageBox.critical(self, self.tr("Error"), - "{0}".format(e), - QMessageBox.Ok) + # except Exception as e: + # QMessageBox.critical(self, self.tr("Error"), + # "{0}".format(e), + # QMessageBox.Ok) def publish_uid(self): reply = QMessageBox.warning(self, self.tr("Warning"), @@ -240,10 +242,10 @@ Publishing your UID can be canceled by Revoke UID.""") QMessageBox.critical(self, self.tr("Network error"), self.tr("Couldn't connect to network : {0}").format(e), QMessageBox.Ok) - except Exception as e: - QMessageBox.critical(self, self.tr("Error"), - "{0}".format(e), - QMessageBox.Ok) + # except Exception as e: + # QMessageBox.critical(self, self.tr("Error"), + # "{0}".format(e), + # QMessageBox.Ok) def revoke_uid(self): reply = QMessageBox.warning(self, self.tr("Warning"), @@ -266,10 +268,10 @@ Revoking your UID can only success if it is not already validated by the network QMessageBox.critical(self, self.tr("Network error"), self.tr("Couldn't connect to network : {0}").format(e), QMessageBox.Ok) - except Exception as e: - QMessageBox.critical(self, self.tr("Error"), - "{0}".format(e), - QMessageBox.Ok) + # except Exception as e: + # QMessageBox.critical(self, self.tr("Error"), + # "{0}".format(e), + # QMessageBox.Ok) def search_text(self): """ @@ -362,9 +364,9 @@ Revoking your UID can only success if it is not already validated by the network def refresh_quality_buttons(self): try: - if self.account.published_uid(self.community): + if self.account.identity(self.community).published_uid(self.community): logging.debug("UID Published") - if self.account.member_of(self.community): + if self.account.identity(self.community).is_member(self.community): self.button_membership.setText(self.tr("Renew membership")) self.button_membership.show() self.button_publish_uid.hide() -- GitLab