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