Newer
Older
'''
Created on 21 févr. 2015
@author: inso
'''
from ucoinpy.documents.peer import Peer, BMAEndpoint, Endpoint
from ucoinpy.api import bma
from requests.exceptions import RequestException
import logging
import time
from PyQt5.QtCore import QObject, pyqtSignal
class Node(QObject):
A node is a peer seend from the client point of view.
This node can have multiple states :
- ONLINE : The node is available for requests
- OFFLINE: The node is disconnected
- DESYNCED : The node is online but is desynced from the network
- CORRUPTED : The node is corrupted, some weird behaviour is going on
'''
ONLINE = 1
OFFLINE = 2
DESYNCED = 3
CORRUPTED = 4
changed = pyqtSignal()
def __init__(self, currency, endpoints, pubkey, block, state):
'''
Constructor
'''
self._endpoints = endpoints
self._pubkey = pubkey
self._block = block
self._state = state
self._currency = currency
@classmethod
def from_address(cls, currency, address, port):
'''
Factory method to get a node from a given address
:param str currency: The node currency. None if we don't know\
the currency it should have, for example if its the first one we add
:param str address: The node address
:param int port: The node port
'''
peer_data = bma.network.Peering(ConnectionHandler(address, port)).get()
peer = Peer.from_signed_raw("{0}{1}\n".format(peer_data['raw'],
peer_data['signature']))
if currency is not None:
if peer.currency != currency:
raise InvalidNodeCurrency(peer.currency, currency)
node = cls(peer.currency, peer.endpoints, peer.pubkey, 0, Node.ONLINE)
@classmethod
def from_peer(cls, currency, peer):
'''
Factory method to get a node from a peer document.
:param str currency: The node currency. None if we don't know\
the currency it should have, for example if its the first one we add
:param peer: The peer document
'''
if currency is not None:
if peer.currency != currency:
raise InvalidNodeCurrency(peer.currency, currency)
node = cls(peer.currency, peer.endpoints, "", 0, Node.ONLINE)
node.refresh_state()
return node
@classmethod
def from_json(cls, currency, data):
endpoints = []
for endpoint_data in data['endpoints']:
endpoints.append(Endpoint.from_inline(endpoint_data))
node = cls(currency, endpoints, "", 0, Node.ONLINE)
node.refresh_state()
data = {'pubkey': self._pubkey,
'currency': self._currency}
endpoints = []
for e in self._endpoints:
endpoints.append(e.inline())
data['endpoints'] = endpoints
return data
@property
def pubkey(self):
return self._pubkey
@property
def endpoint(self) -> BMAEndpoint:
return next((e for e in self._endpoints if type(e) is BMAEndpoint))
@property
def block(self):
return self._block
@property
def state(self):
return self._state
@property
def currency(self):
return self._currency
@property
def neighbours(self):
return self._neighbours
if self._block < block:
self._state = Node.DESYNCED
else:
self._state = Node.ONLINE
try:
informations = bma.network.Peering(self.endpoint.conn_handler()).get()
block = bma.blockchain.Current(self.endpoint.conn_handler()).get()
peers_data = bma.network.peering.Peers(self.endpoint.conn_handler()).get()
neighbours = []
for p in peers_data:
peer = Peer.from_signed_raw("{0}{1}\n".format(p['value']['raw'],
p['value']['signature']))
neighbours.append(peer.endpoints)
block_number = block["number"]
node_pubkey = informations["pubkey"]
node_currency = informations["currency"]
except ValueError as e:
if '404' in e:
block_number = 0
except RequestException:
self._state = Node.OFFLINE
# If not is offline, do not refresh last data
if self._state != Node.OFFLINE:
# If not changed its currency, consider it corrupted
if node_currency != self._currency:
self.state = Node.CORRUPTED
emit_change = True
else:
if block_number != self._block:
self._block = block_number
emit_change = True
if node_pubkey != self._pubkey:
self._pubkey = node_pubkey
emit_change = True
logging.debug(neighbours)
new_inlines = [e.inline() for n in neighbours for e in n]
last_inlines = [e.inline() for n in self._neighbours for e in n]
hash_new_neighbours = hash(tuple(frozenset(sorted(new_inlines))))
hash_last_neighbours = hash(tuple(frozenset(sorted(last_inlines))))
if hash_new_neighbours != hash_last_neighbours:
self._neighbours = neighbours
emit_change = True
if emit_change:
self.changed.emit()
logging.debug("Read {0} peering".format(self.pubkey))
traversed_pubkeys.append(self.pubkey)
if self.pubkey not in [n.pubkey for n in found_nodes]:
# if node is corrupted remove it
if self._state != Node.CORRUPTED:
found_nodes.append(self)
e = next(e for e in n if type(e) is BMAEndpoint)
peering = bma.network.Peering(e.conn_handler()).get()
peer = Peer.from_signed_raw("{0}{1}\n".format(peering['raw'],
peering['signature']))
logging.debug(traversed_pubkeys)
logging.debug("Traversing : next to read : {0} : {1}".format(node.pubkey,
(node.pubkey not in traversed_pubkeys)))
if node.pubkey not in traversed_pubkeys:
time.sleep(interval)
except RequestException as e:
self._state = Node.OFFLINE
def __str__(self):
return ','.join([str(self.pubkey), str(self.endpoint.server), str(self.endpoint.port), str(self.block),
str(self.currency), str(self.state), str(self.neighbours)])