From 8a3baa91633249b5623e068e7b7ca108b1cf467e Mon Sep 17 00:00:00 2001 From: Inso <insomniak.fr@gmail.com> Date: Thu, 13 Mar 2014 23:42:45 +0100 Subject: [PATCH] Huge nodes refactoring to consider trusts and hosters for THT --- doc/uml/models.pu | 2 + src/cutecoin/gui/configureAccountDialog.py | 22 +++++--- src/cutecoin/gui/configureCommunityDialog.py | 38 +++++++------- src/cutecoin/models/account/__init__.py | 35 +++++++++++++ src/cutecoin/models/community/__init__.py | 53 ++++++++++++++++---- src/cutecoin/models/community/treeModel.py | 46 +++++++++++++---- src/cutecoin/models/node/__init__.py | 28 ++++++++--- src/cutecoin/models/node/itemModel.py | 9 +++- 8 files changed, 178 insertions(+), 55 deletions(-) diff --git a/doc/uml/models.pu b/doc/uml/models.pu index 672dc897..82a546ca 100644 --- a/doc/uml/models.pu +++ b/doc/uml/models.pu @@ -1,5 +1,7 @@ @startuml +#TODO: Rework UML with all informations gathered lately + class Account { pgpkey } diff --git a/src/cutecoin/gui/configureAccountDialog.py b/src/cutecoin/gui/configureAccountDialog.py index 99a6ef87..93261706 100644 --- a/src/cutecoin/gui/configureAccountDialog.py +++ b/src/cutecoin/gui/configureAccountDialog.py @@ -7,10 +7,14 @@ from cutecoin.gen_resources.accountConfigurationDialog_uic import Ui_AccountConf from cutecoin.gui.configureCommunityDialog import ConfigureCommunityDialog from cutecoin.models.account.communities.listModel import CommunitiesListModel from cutecoin.core.exceptions import KeyAlreadyUsed -from PyQt5.QtWidgets import QDialog, QErrorMessage from cutecoin.models.account import Account from cutecoin.models.account import Communities +from cutecoin.models.node import Node + +from PyQt5.QtWidgets import QDialog, QErrorMessage, QInputDialog + import gnupg +import re class ConfigureAccountDialog(QDialog, Ui_AccountConfigurationDialog): @@ -55,10 +59,16 @@ class ConfigureAccountDialog(QDialog, Ui_AccountConfigurationDialog): self.edit_accountName.setText(self.account.name) def openAddCommunityDialog(self): - dialog = ConfigureCommunityDialog(None) - dialog.buttonBox.accepted.connect(self.actionAddCommunity) - dialog.setAccount(self.account) - dialog.exec_() + + text, ok = QInputDialog.getText(self, 'Add a community', + 'Enter a main node address you trust :') + + if ok: + server, port = text.split(':')[0], int(text.split(':')[1]) + + dialog = ConfigureCommunityDialog(self.account, None, Node(server, port)) + dialog.buttonBox.accepted.connect(self.actionAddCommunity) + dialog.exec_() def actionAddCommunity(self): self.combo_keysList.setEnabled(False) @@ -78,7 +88,7 @@ class ConfigureAccountDialog(QDialog, Ui_AccountConfigurationDialog): def openEditCommunityDialog(self, index): community = self.account.communities.communitiesList[index.row()] - dialog = ConfigureCommunityDialog(community) + dialog = ConfigureCommunityDialog(self.account, community) dialog.buttonBox.accepted.connect(self.actionEditCommunity) dialog.setAccount(self.account) dialog.exec_() diff --git a/src/cutecoin/gui/configureCommunityDialog.py b/src/cutecoin/gui/configureCommunityDialog.py index b3c91865..14f5a38e 100644 --- a/src/cutecoin/gui/configureCommunityDialog.py +++ b/src/cutecoin/gui/configureCommunityDialog.py @@ -6,7 +6,7 @@ Created on 8 mars 2014 from cutecoin.gen_resources.communityConfigurationDialog_uic import Ui_CommunityConfigurationDialog from PyQt5.QtWidgets import QDialog, QErrorMessage, QMenu from cutecoin.models.community.treeModel import CommunityTreeModel -from cutecoin.models.node import TrustedNode +from cutecoin.models.node import Node from cutecoin.core.exceptions import NotMemberOfCommunityError class ConfigureCommunityDialog(QDialog, Ui_CommunityConfigurationDialog): @@ -15,58 +15,56 @@ class ConfigureCommunityDialog(QDialog, Ui_CommunityConfigurationDialog): ''' - def __init__(self, community): + def __init__(self, account, community, defaultNode=None): ''' Constructor ''' super(ConfigureCommunityDialog, self).__init__() self.setupUi(self) self.community = community + self.account = account if self.community is None: self.setWindowTitle("Add a community") + try: + defaultNode.trust = True + defaultNode.hoster = True + self.community = self.account.communities.addCommunity(defaultNode, self.account.keyFingerprint()) + self.account.wallets.addWallet(self.community) + self.communityView.setModel( CommunityTreeModel(self.community) ) + #TODO: Ask for THT pull + except NotMemberOfCommunityError as e: + QErrorMessage(self).showMessage(e.message) else: self.setWindowTitle("Configure community " + self.community.currency) - self.setData() - - def setData(self): - if self.community is not None: + #TODO: Ask for THT push self.communityView.setModel( CommunityTreeModel(self.community)) - def setAccount(self, account): - self.account = account - def addNode(self): ''' Add node slot ''' server = self.serverEdit.text() port = self.portBox.value() - if self.community == None: - try: - self.community = self.account.communities.addCommunity(TrustedNode(server, port), self.account.keyFingerprint()) - self.account.wallets.addWallet(self.community) - self.communityView.setModel( CommunityTreeModel(self.community) ) - except NotMemberOfCommunityError as e: - QErrorMessage(self).showMessage(e.message) - else: - self.community.addTrustedNode( TrustedNode(server, port )) + if self.community is not None: + self.community.nodes.append(Node(server, port, trust=True)) self.communityView.setModel( CommunityTreeModel(self.community )) def showContextMenu(self, point): menu = QMenu() action = menu.addAction("Delete", self.removeNode) if self.community is not None: - if len(self.community.trustedNodes) == 1: + if len(self.community.nodes) == 1: action.setEnabled(False) menu.exec_(self.communityView.mapToGlobal(point)) def removeNode(self): for index in self.communityView.selectedIndexes(): - self.community.trustedNodes.pop(index.row()) + self.community.nodes.pop(index.row()) self.communityView.setModel( CommunityTreeModel(self.community )) def accept(self): + #TODO: Ask for THT push self.close() diff --git a/src/cutecoin/models/account/__init__.py b/src/cutecoin/models/account/__init__.py index ea975aa9..fbd287a5 100644 --- a/src/cutecoin/models/account/__init__.py +++ b/src/cutecoin/models/account/__init__.py @@ -125,6 +125,41 @@ class Account(object): issuance = ucoin.wrappers.transactions.Issue(self.keyFingerprint(), community.amendmentNumber(), coins, keyId=self.pgpKeyId) return issuance() + def tht(self, community): + if community in self.communities.communitiesList: + tht = community.ucoinRequest(ucoin.ucg.tht(self.keyFingerprint())) + return tht['entries'] + return None + + def updateTrustsAndHosters(self, community): + if community in self.communities.communitiesList: + hostersFg = community.hosters() + trustsFg = community.trusts() + for trust in community.trusts(): + peering = trust.peering() + trustsFg.append(peering['Fingerprint']) + for hoster in community.hosters(): + peering = hoster.peering() + hostersFg.append(peering['Fingerprint']) + entry = { + 'version' : '1', + 'currency' : community.currency, + 'fingerprint' : self.fingerprint(), + 'hosters' : self.hostersFg, + 'trusts' : self.trustsFg + } + jsonEntry = json.dump(entry, indent = 2) + gpg = gnupg.GPG() + signature = gpg.sign(jsonEntry, keyId=self.keyid, detach=True) + + dataPost = { + 'entry' : entry, + 'signature' : signature + } + + community.ucoinPost(ucoin.ucg.tht(self.keyFingerprint()), json.dump(dataPost, indent=2)) + + def transferCoins(self, node, recipient, coins, message): transfer = ucoin.wrappers.transactions.RawTransfer(self.keyFingerprint(), recipient.fingerprint, coins, message, keyid=self.pgpKeyId, server=node.server, port=node.port) return transfer() diff --git a/src/cutecoin/models/community/__init__.py b/src/cutecoin/models/community/__init__.py index ebc0570c..4d625543 100644 --- a/src/cutecoin/models/community/__init__.py +++ b/src/cutecoin/models/community/__init__.py @@ -9,35 +9,35 @@ import hashlib import json import logging -from cutecoin.models.node import TrustedNode +from cutecoin.models.node import Node from cutecoin.models.wallet import Wallet class Community(object): ''' classdocs ''' - def __init__(self, trustedNodes): + def __init__(self, nodes): ''' A community is a group of nodes using the same currency. They are all using the same amendment and are syncing their datas. An account is a member of a community if he is a member of the current amendment. ''' - self.trustedNodes = trustedNodes + self.nodes = nodes currentAmendment = self.ucoinRequest(ucoin.hdc.amendments.Current()) self.currency = currentAmendment['currency'] @classmethod def create(cls, mainNode): - knownNodes = [] - knownNodes.append(mainNode) - return cls(knownNodes) + nodes = [] + nodes.append(mainNode) + return cls(nodes) @classmethod def load(cls, jsonData, account): knownNodes = [] for nodeData in jsonData['nodes']: - knownNodes.append(TrustedNode(nodeData['server'], nodeData['port'])) + knownNodes.append(Node(nodeData['server'], nodeData['port'], nodeData['trust'], nodeData['hoster'])) community = cls(knownNodes) @@ -48,6 +48,33 @@ class Community(object): return community + #TODO: Check if its working + def _searchTrustAddresses(self, trustFg, nextNode, traversedNodes): + nextFg = nextNode.peering()['fingerprint'] + if nextFg not in traversedNodes: + traversedNodes.append(nextFg) + if trustFg == nextFg: + return trustFg + else: + for peer in nextNode.peers(): + # Look for next node informations + found = self._searchTrustAddresses(trustFg, Node(peer['ipv4'], int(peer['port'])), traversedNodes) + if found is not None: + return found + return None + + def synchronizeTrusts(self, trustsFingerprints): + trusts = [] + for trustFg in trustsFingerprints: + trusts.append(self._searchTrustAddresses(trustFg, self.trusts()[0])) + return trusts + + def trusts(self): + return [node for node in self.nodes if node.trust] + + def hosters(self): + return [node for node in self.nodes if node.trust] + def membersFingerprints(self): ''' Listing members of a community @@ -59,14 +86,20 @@ class Community(object): return members def ucoinRequest(self, request, get_args={}): - for node in self.trustedNodes: + for node in self.trusts(): logging.debug("Trying to connect to : " + node.getText()) request = node.use(request) return request.get(**get_args) - raise RuntimeError("Cannot connect to any node") + def ucoinPost(self, request, get_args={}): + for node in self.hosters(): + logging.debug("Trying to connect to : " + node.getText()) + request = node.use(request) + return request.post(**get_args) + raise RuntimeError("Cannot connect to any node") + def amendmentId(self): currentAmendment = self.ucoinRequest(ucoin.hdc.amendments.Current()) currentAmendmentHash = hashlib.sha1(currentAmendment['raw'].encode('utf-8')).hexdigest().upper() @@ -103,7 +136,7 @@ class Community(object): def jsonifyNodesList(self): data = [] - for node in self.trustedNodes: + for node in self.nodes: data.append(node.jsonify()) return data diff --git a/src/cutecoin/models/community/treeModel.py b/src/cutecoin/models/community/treeModel.py index c916dadd..0ec7bb22 100644 --- a/src/cutecoin/models/community/treeModel.py +++ b/src/cutecoin/models/community/treeModel.py @@ -23,29 +23,41 @@ class CommunityTreeModel(QAbstractItemModel): self.refreshTreeNodes() def columnCount(self, parent): - return 1 + return 3 def data(self, index, role): if not index.isValid(): return None - if role != Qt.DisplayRole: - return None - item = index.internalPointer() - return item.data(0) + + if role == Qt.DisplayRole and index.column() == 0: + return item.data(0) + elif role == Qt.CheckStateRole and index.column() == 1: + return Qt.Checked if item.trust else Qt.Unchecked + elif role == Qt.CheckStateRole and index.column() == 2: + return Qt.Checked if item.hoster else Qt.Unchecked + + + return None def flags(self, index): if not index.isValid(): return Qt.NoItemFlags - return Qt.ItemIsEnabled | Qt.ItemIsSelectable + if index.column() == 0: + return Qt.ItemIsEnabled | Qt.ItemIsSelectable + else: + return Qt.ItemIsEnabled | Qt.ItemIsSelectable | Qt.ItemIsUserCheckable def headerData(self, section, orientation, role): - if orientation == Qt.Horizontal and role == Qt.DisplayRole: - return self.rootItem.data(0) - + if orientation == Qt.Horizontal and role == Qt.DisplayRole and section == 0: + return self.rootItem.data(0) + " nodes" + elif orientation == Qt.Horizontal and role == Qt.DisplayRole and section == 1: + return "Trust" + elif orientation == Qt.Horizontal and role == Qt.DisplayRole and section == 2: + return "Hoster" return None def index(self, row, column, parent): @@ -86,10 +98,24 @@ class CommunityTreeModel(QAbstractItemModel): return parentItem.childCount() + def setData(self, index, value, role=Qt.EditRole): + if index.column() == 0: + return False + + if role == Qt.EditRole: + return False + if role == Qt.CheckStateRole: + item = index.internalPointer() + if index.column() == 1: + item.trust = value + elif index.column() == 2: + item.host = value + self.dataChanged.emit(index, index) + return True def refreshTreeNodes(self): logging.debug("root : " + self.rootItem.data(0)) - for mainNode in self.community.trustedNodes: + for mainNode in self.community.nodes: mainNodeItem = MainNodeItem(mainNode, self.rootItem) logging.debug("mainNode : " + mainNode.getText() + " / " + mainNodeItem.data(0)) self.rootItem.appendChild(mainNodeItem) diff --git a/src/cutecoin/models/node/__init__.py b/src/cutecoin/models/node/__init__.py index ccf7e8e1..3f126737 100644 --- a/src/cutecoin/models/node/__init__.py +++ b/src/cutecoin/models/node/__init__.py @@ -8,14 +8,16 @@ import ucoinpy as ucoin class Node(object): ''' - A ucoin node + A ucoin node using BMA protocol ''' - def __init__(self, server, port): + def __init__(self, server, port, trust=False, hoster=False): ''' Constructor ''' self.server = server self.port = port + self.trust = trust + self.hoster = hoster def __eq__(self, other): return ( self.server == other.server and self.port == other.port ) @@ -23,11 +25,9 @@ class Node(object): def getText(self): return self.server + ":" + str(self.port) - -class TrustedNode(Node): ''' - TrustedNode is a node the community is reading to get informations. - The account sends data one of the community main nodes. + TrustedNode is a node the community is reading to get informations. + The account sends data one of the community main nodes. ''' def downstreamPeers(self): ucoin.settings['server'] = self.server @@ -37,8 +37,20 @@ class TrustedNode(Node): for peer in ucoin.ucg.peering.peers.DownStream().get()['peers']: node = Node(peer['ipv4'], peer['port']) peers.append(node) + return peers + #TODO: Peering is not json format. Parse it differently + def peering(self): + request = ucoin.ucg.peering.Peer() + self.use(request) + return request.get() + + def peers(self): + request = ucoin.ucg.peering.Peers() + self.use(request) + return request.get() + def use(self, request): request.server = self.server request.port = self.port @@ -46,5 +58,7 @@ class TrustedNode(Node): def jsonify(self): return {'server' : self.server, - 'port' : self.port} + 'port' : self.port, + 'trust':self.trust, + 'hoster':self.hoster} diff --git a/src/cutecoin/models/node/itemModel.py b/src/cutecoin/models/node/itemModel.py index c5f3afcb..fb1ad706 100644 --- a/src/cutecoin/models/node/itemModel.py +++ b/src/cutecoin/models/node/itemModel.py @@ -9,6 +9,8 @@ class NodeItem(object): def __init__(self, node, mainNodeItem=None): self.mainNodeItem = mainNodeItem self.nodeText = node.getText() + self.trust = node.trust + self.hoster = node.hoster def appendChild(self, item): pass @@ -34,13 +36,14 @@ class NodeItem(object): def row(self): if self.mainNodeItem: return self.mainNodeItem.nodeItems.index(self) - return 0 class MainNodeItem(object): def __init__(self, mainNode, communityItem=None): self.communityItem = communityItem self.mainNodeText = mainNode.getText() + self.trust = mainNode.trust + self.hoster = mainNode.hoster self.nodeItems = [] def appendChild(self, nodeItem): @@ -68,4 +71,6 @@ class MainNodeItem(object): if self.communityItem: return self.communityItem.mainNodeItems.index(self) - return 0 \ No newline at end of file + return 0 + + -- GitLab