Skip to content
Snippets Groups Projects
Commit 702e05f5 authored by inso's avatar inso
Browse files

Refactoring netcode so that background crawling can be implemented

parent 75c50934
No related branches found
No related tags found
No related merge requests found
...@@ -9,7 +9,8 @@ from ucoinpy import PROTOCOL_VERSION ...@@ -9,7 +9,8 @@ from ucoinpy import PROTOCOL_VERSION
from ucoinpy.documents.peer import Peer, Endpoint, BMAEndpoint from ucoinpy.documents.peer import Peer, Endpoint, BMAEndpoint
from ucoinpy.documents.block import Block from ucoinpy.documents.block import Block
from ..tools.exceptions import NoPeerAvailable from ..tools.exceptions import NoPeerAvailable
from .network.node import Node from .net.node import Node
from .net.network import Network
import logging import logging
import inspect import inspect
import hashlib import hashlib
...@@ -75,13 +76,12 @@ class Community(object): ...@@ -75,13 +76,12 @@ class Community(object):
classdocs classdocs
''' '''
def __init__(self, currency, peers): def __init__(self, currency, network):
''' '''
A community is a group of nodes using the same currency. A community is a group of nodes using the same currency.
''' '''
self.currency = currency self.currency = currency
self.peers = [p for p in peers if p.currency == currency] self._network = network
self._nodes = [Node.from_peer(p) for p in peers]
self._cache = Cache(self) self._cache = Cache(self)
self._cache.refresh() self._cache.refresh()
...@@ -97,47 +97,11 @@ class Community(object): ...@@ -97,47 +97,11 @@ class Community(object):
@classmethod @classmethod
def load(cls, json_data): def load(cls, json_data):
peers = []
currency = json_data['currency'] currency = json_data['currency']
for data in json_data['peers']: network = Network.from_json(currency, json_data['peers'])
for e in data['endpoints']:
if Endpoint.from_inline(e) is not None:
endpoint = Endpoint.from_inline(e)
try:
peering = bma.network.Peering(endpoint.conn_handler())
peer_data = peering.get()
peer = Peer.from_signed_raw("{0}{1}\n".format(peer_data['raw'],
peer_data['signature']))
peers.append(peer)
break
except:
pass
community = cls(currency, peers)
logging.debug("Creating community")
community.peers = community.peering()
logging.debug("{0} peers found".format(len(community.peers)))
logging.debug([peer.pubkey for peer in community.peers])
return community
@classmethod
def without_network(cls, json_data):
peers = []
currency = json_data['currency'] community = cls(currency, network)
for data in json_data['peers']:
endpoints = []
for e in data['endpoints']:
endpoints.append(Endpoint.from_inline(e))
peer = Peer(PROTOCOL_VERSION, currency, data['pubkey'],
"0-DA39A3EE5E6B4B0D3255BFEF95601890AFD80709",
endpoints, None)
peers.append(peer)
community = cls(currency, peers)
return community return community
def load_cache(self, json_data): def load_cache(self, json_data):
...@@ -206,48 +170,9 @@ class Community(object): ...@@ -206,48 +170,9 @@ class Community(object):
if '404' in e: if '404' in e:
return 0 return 0
def _peering_traversal(self, peer, found_peers, traversed_pubkeys):
logging.debug("Read {0} peering".format(peer.pubkey))
traversed_pubkeys.append(peer.pubkey)
if peer.currency == self.currency and \
peer.pubkey not in [p.pubkey for p in found_peers]:
found_peers.append(peer)
try:
e = next(e for e in peer.endpoints if type(e) is BMAEndpoint)
next_peers = bma.network.peering.Peers(e.conn_handler()).get()
for p in next_peers:
next_peer = Peer.from_signed_raw("{0}{1}\n".format(p['value']['raw'],
p['value']['signature']))
logging.debug(traversed_pubkeys)
logging.debug("Traversing : next to read : {0} : {1}".format(next_peer.pubkey,
(next_peer.pubkey not in traversed_pubkeys)))
if next_peer.pubkey not in traversed_pubkeys:
self._peering_traversal(next_peer, found_peers, traversed_pubkeys)
except Timeout:
pass
except ConnectionError:
pass
except ValueError:
pass
except RequestException as e:
pass
def peering(self):
peers = []
traversed_pubkeys = []
for p in self.peers:
logging.debug(traversed_pubkeys)
logging.debug("Peering : next to read : {0} : {1}".format(p.pubkey,
(p.pubkey not in traversed_pubkeys)))
if p.pubkey not in traversed_pubkeys:
self._peering_traversal(p, peers, traversed_pubkeys)
logging.debug("Peers found : {0}".format(peers))
return peers
@property @property
def nodes(self): def nodes(self):
return self._nodes return self._network.all_nodes
def get_block(self, number=None): def get_block(self, number=None):
if number is None: if number is None:
...@@ -287,10 +212,10 @@ class Community(object): ...@@ -287,10 +212,10 @@ class Community(object):
if cached: if cached:
return self._cache.request(request, req_args, get_args) return self._cache.request(request, req_args, get_args)
else: else:
for peer in self.peers.copy(): nodes = self._network.online_nodes
e = next(e for e in peer.endpoints if type(e) is BMAEndpoint) for node in nodes:
try: try:
req = request(e.conn_handler(), **req_args) req = request(node.endpoint.conn_handler(), **req_args)
data = req.get(**get_args) data = req.get(**get_args)
if inspect.isgenerator(data): if inspect.isgenerator(data):
...@@ -306,40 +231,33 @@ class Community(object): ...@@ -306,40 +231,33 @@ class Community(object):
else: else:
raise raise
except RequestException: except RequestException:
# Move the timeout peer to the end
self.peers.remove(peer)
self.peers.append(peer)
continue continue
raise NoPeerAvailable(self.currency, len(self.peers)) raise NoPeerAvailable(self.currency, len(nodes))
def post(self, request, req_args={}, post_args={}): def post(self, request, req_args={}, post_args={}):
for peer in self.peers: nodes = self._network.online_nodes
e = next(e for e in peer.endpoints if type(e) is BMAEndpoint) for node in nodes:
logging.debug("Trying to connect to : " + peer.pubkey) req = request(node.endpoint.conn_handler(), **req_args)
req = request(e.conn_handler(), **req_args) logging.debug("Trying to connect to : " + node.pubkey)
req = request(node.endpoint.conn_handler(), **req_args)
try: try:
req.post(**post_args) req.post(**post_args)
return return
except ValueError as e: except ValueError as e:
raise raise
except RequestException: except RequestException:
# Move the timeout peer to the end
self.peers.remove(peer)
self.peers.append(peer)
continue continue
except: raise NoPeerAvailable(self.currency, len(nodes))
raise
raise NoPeerAvailable(self.currency, len(self.peers))
def broadcast(self, request, req_args={}, post_args={}): def broadcast(self, request, req_args={}, post_args={}):
tries = 0 tries = 0
ok = False ok = False
value_error = None value_error = None
for peer in self.peers: nodes = self._network.online_nodes
e = next(e for e in peer.endpoints if type(e) is BMAEndpoint) for node in nodes:
logging.debug("Trying to connect to : " + peer.pubkey) logging.debug("Trying to connect to : " + node.pubkey)
req = request(e.conn_handler(), **req_args) req = request(node.endpoint.conn_handler(), **req_args)
try: try:
req.post(**post_args) req.post(**post_args)
ok = True ok = True
...@@ -348,32 +266,17 @@ class Community(object): ...@@ -348,32 +266,17 @@ class Community(object):
continue continue
except RequestException: except RequestException:
tries = tries + 1 tries = tries + 1
# Move the timeout peer to the end
self.peers.remove(peer)
self.peers.append(peer)
continue continue
except:
raise
if not ok: if not ok:
raise value_error raise value_error
if tries == len(self.peers): if tries == len(self.peers):
raise NoPeerAvailable(self.currency, len(self.peers)) raise NoPeerAvailable(self.currency, len(nodes))
def jsonify_peers_list(self):
data = []
for peer in self.peers:
endpoints_data = []
for e in peer.endpoints:
endpoints_data.append(e.inline())
data.append({'endpoints': endpoints_data,
'pubkey': peer.pubkey})
return data
def jsonify(self): def jsonify(self):
data = {'currency': self.currency, data = {'currency': self.currency,
'peers': self.jsonify_peers_list()} 'peers': self.network.jsonify()}
return data return data
def get_parameters(self): def get_parameters(self):
......
'''
Created on 24 févr. 2015
@author: inso
'''
from ucoinpy.documents.peer import Peer, BMAEndpoint
from ucoinpy.api import bma
from .node import Node
import logging
import time
from PyQt5.QtCore import QObject, pyqtSignal
class Network(QObject):
'''
classdocs
'''
nodes_changed = pyqtSignal()
def __init__(self, currency, nodes):
'''
Constructor
'''
self.currency = currency
self._nodes = nodes
#TODO: Crawl nodes at startup
@classmethod
def from_json(cls, currency, json_data):
nodes = []
for data in json_data:
node = Node.from_json(currency, data)
nodes.append(node)
block_max = max([n.block for n in nodes])
for node in nodes:
node.check_sync(currency, block_max)
return cls(currency, nodes)
def jsonify(self):
data = []
for node in self.nodes:
data.append(node.jsonify())
return data
@property
def online_nodes(self):
return [n for n in self._nodes if n.state == Node.ONLINE]
@property
def all_nodes(self):
return self._nodes.copy()
def perpetual_crawling(self):
while self.must_crawl:
self.crawling(interval=10)
def crawling(self, interval=0):
nodes = []
traversed_pubkeys = []
for n in self._nodes:
logging.debug(traversed_pubkeys)
logging.debug("Peering : next to read : {0} : {1}".format(n.pubkey,
(n.pubkey not in traversed_pubkeys)))
if n.pubkey not in traversed_pubkeys:
n.peering_traversal(self.currency, nodes,
traversed_pubkeys, interval)
time.sleep(interval)
block_max = max([n.block for n in nodes])
for node in [n for n in nodes if n.state == Node.ONLINE]:
node.check_sync(block_max)
#TODO: Offline nodes for too long have to be removed
#TODO: Corrupted nodes should maybe be removed faster ?
logging.debug("Nodes found : {0}".format(nodes))
return nodes
'''
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
from ...core.person import Person
from ...tools.exceptions import PersonNotFoundError
import logging
import time
class Node(object):
'''
classdocs
'''
ONLINE = 1
OFFLINE = 2
DESYNCED = 3
CORRUPTED = 4
def __init__(self, endpoints, pubkey, block, state):
'''
Constructor
'''
self._endpoints = endpoints
self._pubkey = pubkey
self._block = block
self._state = state
@classmethod
def from_peer(cls, currency, peer):
node = cls(peer.endpoints, "", 0, Node.ONLINE)
node.refresh_state(currency)
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(endpoints, "", 0, Node.ONLINE)
node.refresh_state(currency)
return node
@property
def pubkey(self):
return self._pubkey
@property
def endpoint(self):
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
def check_sync(self, currency, block):
if self._block < block:
self._state = Node.DESYNCED
else:
self._state = Node.ONLINE
def refresh_state(self, currency):
try:
informations = bma.network.Peering(self.endpoint.conn_handler()).get()
block = bma.blockchain.Current(self.endpoint.conn_handler()).get()
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 node_currency != currency:
self.state = Node.CORRUPTED
self._block = block_number
self._pubkey = node_pubkey
def peering_traversal(self, currency, found_nodes, traversed_pubkeys, interval):
logging.debug("Read {0} peering".format(self.pubkey))
traversed_pubkeys.append(self.pubkey)
self.refresh_state(currency)
if self.pubkey not in [n.pubkey for n in found_nodes]:
found_nodes.append(self)
try:
next_peers = bma.network.peering.Peers(self.endpoint.conn_handler()).get()
for p in next_peers:
next_peer = Peer.from_signed_raw("{0}{1}\n".format(p['value']['raw'],
p['value']['signature']))
logging.debug(traversed_pubkeys)
logging.debug("Traversing : next to read : {0} : {1}".format(next_peer.pubkey,
(next_peer.pubkey not in traversed_pubkeys)))
next_node = Node.from_peer(next_peer)
if next_node.pubkey not in traversed_pubkeys:
next_node.peering_traversal(currency, found_nodes, traversed_pubkeys)
time.sleep(interval)
except RequestException as e:
self._state = Node.OFFLINE
'''
Created on 21 févr. 2015
@author: inso
'''
from ucoinpy.documents.peer import Peer, BMAEndpoint
from ucoinpy.api import bma
from ...core.person import Person
from ...tools.exceptions import PersonNotFoundError
class Node(object):
'''
classdocs
'''
def __init__(self, endpoint, pubkey, block):
'''
Constructor
'''
self._endpoint = endpoint
self._pubkey = pubkey
self._block = block
@classmethod
def from_peer(cls, peer):
e = next((e for e in peer.endpoints if type(e) is BMAEndpoint))
informations = bma.network.Peering(e.conn_handler()).get()
try:
block = bma.blockchain.Current(e.conn_handler()).get()
block_number = block["number"]
except ValueError as e:
if '404' in e:
block_number = 0
node_pubkey = informations["pubkey"]
return cls(e, node_pubkey, block_number)
@property
def pubkey(self):
return self._pubkey
@property
def endpoint(self):
return self._endpoint
@property
def block(self):
return self._block
...@@ -117,6 +117,7 @@ class NetworkTableModel(QAbstractTableModel): ...@@ -117,6 +117,7 @@ class NetworkTableModel(QAbstractTableModel):
node = self.nodes[row] node = self.nodes[row]
if role == Qt.DisplayRole: if role == Qt.DisplayRole:
return self.data_node(node)[col] return self.data_node(node)[col]
#TODO: Display colors depending on node state
def flags(self, index): def flags(self, index):
return Qt.ItemIsSelectable | Qt.ItemIsEnabled return Qt.ItemIsSelectable | Qt.ItemIsEnabled
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment