diff --git a/src/cutecoin/core/net/api/bma/access.py b/src/cutecoin/core/net/api/bma/access.py index b77dd961edbedee64a2c42cc555f8a2caf71290f..a813a2d597f2afb5115c25c40004bd3743569372 100644 --- a/src/cutecoin/core/net/api/bma/access.py +++ b/src/cutecoin/core/net/api/bma/access.py @@ -230,7 +230,7 @@ class BmaAccess(QObject): reply = req.get(**get_args) reply.finished.connect(lambda: handle_future_reply(reply)) else: - raise NoPeerAvailable(self.currency, len(nodes)) + raise NoPeerAvailable("", len(nodes)) else: future_data.set_result(data[1]) return future_data @@ -251,7 +251,7 @@ class BmaAccess(QObject): reply = req.get(**get_args) return reply else: - raise NoPeerAvailable(self.currency, len(nodes)) + raise NoPeerAvailable("", len(nodes)) def broadcast(self, request, req_args={}, post_args={}): """ diff --git a/src/cutecoin/core/net/network.py b/src/cutecoin/core/net/network.py index 9369afe5420f426af0058bb07184f867ca40031d..ae81700fe2d5e0d4ed8d9a710df1e78a47877831 100644 --- a/src/cutecoin/core/net/network.py +++ b/src/cutecoin/core/net/network.py @@ -12,7 +12,9 @@ import asyncio from ucoinpy.documents.peer import Peer from ucoinpy.documents.block import Block +from .api import bma as qtbma from PyQt5.QtCore import pyqtSignal, pyqtSlot, QObject, QTimer +from collections import Counter class Network(QObject): @@ -71,8 +73,8 @@ class Network(QObject): other_node._uid = node.uid other_node._version = node.version other_node._software = node.software - if other_node.block_hash != node.block_hash: - other_node.set_block(node.block_number, node.block_hash) + if other_node.block['hash'] != node.block['hash']: + other_node.set_block(node.block) other_node.last_change = node.last_change other_node.state = node.state @@ -163,8 +165,11 @@ class Network(QObject): Get the latest block considered valid It is the most frequent last block of every known nodes """ - blocks = [n.block_number for n in self.nodes] - return max(set(blocks), key=blocks.count) + blocks = [n.block['number'] for n in self.synced_nodes] + if len(blocks) > 0: + return max(set(blocks), key=blocks.count) + else: + return 0 @property def latest_block_hash(self): @@ -172,18 +177,78 @@ class Network(QObject): Get the latest block considered valid It is the most frequent last block of every known nodes """ - blocks = [n.block_hash for n in self.nodes if n.block_hash != Block.Empty_Hash] + blocks = [n.block['hash'] for n in self.synced_nodes if n.block != qtbma.blockchain.Block.null_value] if len(blocks) > 0: return max(set(blocks), key=blocks.count) else: return Block.Empty_Hash + def check_nodes_sync(self): + """ + Check nodes sync with the following rules : + 1 : The block of the majority + 2 : The more last different issuers + 3 : The more difficulty + 4 : The biggest number or timestamp + """ + # rule number 1 : block of the majority + blocks = [n.block['hash'] for n in self.nodes if n.block != qtbma.blockchain.Block.null_value] + blocks_occurences = Counter(blocks) + blocks_by_occurences = {} + for key, value in blocks_occurences.items(): + the_block = [n.block for n in self.nodes if n.block['hash'] == key][0] + if value not in blocks_by_occurences: + blocks_by_occurences[value] = [the_block] + else: + blocks_by_occurences[value].append(the_block) + + if len(blocks_by_occurences) == 0: + for n in [n for n in self._nodes if n.state in (Node.ONLINE, Node.DESYNCED)]: + n.state = Node.ONLINE + return + + most_present = max(blocks_by_occurences.keys()) + + if len(blocks_by_occurences[most_present]) > 1: + # rule number 2 : more last different issuers + # not possible atm + blocks_by_issuers = blocks_by_occurences.copy() + most_issuers = max(blocks_by_issuers.keys()) + if len(blocks_by_issuers[most_issuers]) > 1: + # rule number 3 : biggest PowMin + blocks_by_powmin = {} + for block in blocks_by_issuers[most_issuers]: + if block['powMin'] in blocks_by_powmin: + blocks_by_powmin[block['powMin']].append(block) + else: + blocks_by_powmin[block['powMin']] = [block] + bigger_powmin = max(blocks_by_powmin.keys()) + if len(blocks_by_powmin[bigger_powmin]) > 1: + # rule number 3 : latest timestamp + blocks_by_ts = {} + for block in blocks_by_powmin[bigger_powmin]: + blocks_by_ts[block['time']] = block + latest_ts = max(blocks_by_ts.keys()) + synced_block_hash = blocks_by_ts[latest_ts]['hash'] + else: + synced_block_hash = blocks_by_powmin[bigger_powmin][0]['hash'] + else: + synced_block_hash = blocks_by_issuers[most_issuers][0]['hash'] + else: + synced_block_hash = blocks_by_occurences[most_present][0]['hash'] + + for n in [n for n in self._nodes if n.state in (Node.ONLINE, Node.DESYNCED)]: + if n.block['hash'] == synced_block_hash: + n.state = Node.ONLINE + else: + n.state = Node.DESYNCED + def fork_window(self, members_pubkeys): """ Get the medium of the fork window of the nodes members of a community :return: the medium fork window of knew network """ - fork_windows = [n.fork_window for n in self.nodes if n.software != "" + fork_windows = [n.fork_window for n in self.online_nodes if n.software != "" and n.pubkey in members_pubkeys] if len(fork_windows) > 0: return statistics.median(fork_windows) @@ -192,7 +257,7 @@ class Network(QObject): def add_node(self, node): """ - Add a node to the network. + Add a nod to the network. """ self._nodes.append(node) node.changed.connect(self.handle_change) @@ -256,8 +321,7 @@ class Network(QObject): def handle_change(self): node = self.sender() if node.state in (Node.ONLINE, Node.DESYNCED): - for nd in [n for n in self._nodes if n.state in (Node.ONLINE, Node.DESYNCED)]: - nd.check_sync(self.latest_block_hash) + self.check_nodes_sync() self.nodes_changed.emit() else: if node.last_change + 3600 < time.time(): diff --git a/src/cutecoin/core/net/node.py b/src/cutecoin/core/net/node.py index bf7fe9a6c2e552d5c6fb3f3a3a66eef721a1ee03..ed141ac451690770248a3adf9a0f44e65097bd17 100644 --- a/src/cutecoin/core/net/node.py +++ b/src/cutecoin/core/net/node.py @@ -38,7 +38,7 @@ class Node(QObject): changed = pyqtSignal() neighbour_found = pyqtSignal(Peer, str) - def __init__(self, network_manager, currency, endpoints, uid, pubkey, block_number, block_hash, + def __init__(self, network_manager, currency, endpoints, uid, pubkey, block, state, last_change, last_merkle, software, version, fork_window): """ Constructor @@ -48,8 +48,7 @@ class Node(QObject): self._endpoints = endpoints self._uid = uid self._pubkey = pubkey - self._block_number = block_number - self._block_hash = block_hash + self._block = block self._state = state self._neighbours = [] self._currency = currency @@ -117,7 +116,7 @@ class Node(QObject): node = cls(network_manager, peer.currency, [Endpoint.from_inline(e.inline()) for e in peer.endpoints], - "", pubkey, 0, Block.Empty_Hash, + "", pubkey, qtbma.blockchain.Block.null_value, Node.ONLINE, time.time(), {'root': "", 'leaves': []}, "", "", 0) @@ -132,8 +131,7 @@ class Node(QObject): software = "" version = "" fork_window = 0 - block_number = 0 - block_hash = Block.Empty_Hash + block = qtbma.blockchain.Block.null_value last_change = time.time() state = Node.ONLINE logging.debug(data) @@ -152,11 +150,8 @@ class Node(QObject): if 'last_change' in data: last_change = data['last_change'] - if 'block_number' in data: - block_number = data['block_number'] - - if 'block_hash' in data: - block_hash = data['block_hash'] + if 'block' in data: + block = data['block'] if 'state' in data: state = data['state'] @@ -171,7 +166,7 @@ class Node(QObject): fork_window = data['fork_window'] node = cls(network_manager, currency, endpoints, - uid, pubkey, block_number, block_hash, + uid, pubkey, block, state, last_change, {'root': "", 'leaves': []}, software, version, fork_window) @@ -196,8 +191,7 @@ class Node(QObject): 'currency': self._currency, 'state': self._state, 'last_change': self._last_change, - 'block_number': self.block_number, - 'block_hash': self.block_hash, + 'block': self.block, 'software': self._software, 'version': self._version, 'fork_window': self._fork_window @@ -217,16 +211,11 @@ class Node(QObject): return next((e for e in self._endpoints if type(e) is BMAEndpoint)) @property - def block_number(self): - return self._block_number - - @property - def block_hash(self): - return self._block_hash + def block(self): + return self._block - def set_block(self, block_number, block_hash): - self._block_number = block_number - self._block_hash = block_hash + def set_block(self, block): + self._block = block @property def state(self): @@ -292,13 +281,6 @@ class Node(QObject): self._fork_window = new_fork_window self.changed.emit() - def check_sync(self, block_hash): - logging.debug("Check sync") - if self.block_hash != block_hash: - self.state = Node.DESYNCED - else: - self.state = Node.ONLINE - def check_noerror(self, error_code, status_code): if error_code == QNetworkReply.NoError: if status_code in (200, 404): @@ -337,15 +319,14 @@ class Node(QObject): if status_code == 200: strdata = bytes(reply.readAll()).decode('utf-8') block_data = json.loads(strdata) - block_number = block_data['number'] block_hash = block_data['hash'] elif status_code == 404: - self.set_block(0, Block.Empty_Hash) + self.set_block(qtbma.blockchain.Block.null_value) - if block_hash != self.block_hash: - self.set_block(block_number, block_hash) - logging.debug("Changed block {0} -> {1}".format(self.block_number, - block_number)) + if block_hash != self.block['hash']: + self.set_block(block_data) + logging.debug("Changed block {0} -> {1}".format(self.block['number'], + block_data['number'])) self.changed.emit() else: @@ -488,5 +469,5 @@ class Node(QObject): self.changed.emit() def __str__(self): - return ','.join([str(self.pubkey), str(self.endpoint.server), str(self.endpoint.port), str(self.block_number), + return ','.join([str(self.pubkey), str(self.endpoint.server), str(self.endpoint.port), str(self.block['number']), str(self.currency), str(self.state), str(self.neighbours)]) diff --git a/src/cutecoin/gui/currency_tab.py b/src/cutecoin/gui/currency_tab.py index c3f40928de991c98466e53d314cc830b11967631..7394d39b7cb35cfc30bdf28bb27aef33978a4003 100644 --- a/src/cutecoin/gui/currency_tab.py +++ b/src/cutecoin/gui/currency_tab.py @@ -19,7 +19,7 @@ from .network_tab import NetworkTabWidget from .informations_tab import InformationsTabWidget from . import toast import asyncio -from ..tools.exceptions import MembershipNotFoundError +from ..tools.exceptions import MembershipNotFoundError, NoPeerAvailable from ..core.registry import IdentitiesRegistry @@ -213,4 +213,3 @@ class CurrencyTabWidget(QWidget, Ui_CurrencyTabWidget): self.retranslateUi(self) self.refresh_status() return super(CurrencyTabWidget, self).changeEvent(event) - diff --git a/src/cutecoin/main.py b/src/cutecoin/main.py index 1d4a211e351da6b53d6d84a779e1c478c5072fbf..fec1fbbbdd19ad8d3973a8ff365df2effba3845e 100755 --- a/src/cutecoin/main.py +++ b/src/cutecoin/main.py @@ -8,6 +8,8 @@ import sys import asyncio import logging import os +# To force cx_freeze import +import PyQt5.QtSvg from quamash import QEventLoop from PyQt5.QtWidgets import QApplication diff --git a/src/cutecoin/models/network.py b/src/cutecoin/models/network.py index 899752684bd10b345958709650c9503b96dccd8b..ed957576a890dc5ffdb9ca099334028a099ee774 100644 --- a/src/cutecoin/models/network.py +++ b/src/cutecoin/models/network.py @@ -162,7 +162,7 @@ class NetworkTableModel(QAbstractTableModel): is_root = self.community.network.is_root_node(node) - return (address, port, node.block_number, node.block_hash, node.uid, + return (address, port, node.block['number'], node.block['hash'], node.uid, is_member, node.pubkey, node.software, node.version, is_root) def data(self, index, role):