Skip to content
Snippets Groups Projects
Commit 94a3975d authored by inso's avatar inso
Browse files

Better network synchronization checking ( #199 )

parent c790aa9c
Branches
Tags
No related merge requests found
......@@ -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={}):
"""
......
......@@ -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]
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():
......
......@@ -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)])
......@@ -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)
......@@ -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
......
......@@ -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):
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please to comment