class ConnectionMonitor(gevent.Greenlet): "monitors the connection by sending pings and checking pongs" ping_interval = 15. response_delay_threshold = 120. # FIXME, apply msg takes too long max_samples = 1000 log = slogging.get_logger('p2p.ctxmonitor') def __init__(self, proto): self.log.debug('init') assert isinstance(proto, P2PProtocol) self.proto = proto self.samples = collections.deque(maxlen=self.max_samples) self.last_response = self.last_request = time.time() super(ConnectionMonitor, self).__init__() # track responses self.proto.receive_pong_callbacks.append(self.track_response) self.proto.receive_hello_callbacks.append( lambda p, **kargs: self.start()) def track_response(self, proto): self.last_response = time.time() self.samples.appendleft(self.last_response - self.last_request) def latency(self, num_samples=max_samples): num_samples = min(num_samples, len(self.samples)) return sum( self.samples[i] for i in range(num_samples)) / num_samples if num_samples else 1 def _run(self): self.log.debug('started', monitor=self) while True: self.log.debug('pinging', monitor=self) self.proto.send_ping() now = self.last_request = time.time() gevent.sleep(self.ping_interval) self.log.debug('latency', peer=self.proto, latency='%.3f' % self.latency()) if now - self.last_response > self.response_delay_threshold: self.log.debug('unresponsive_peer', monitor=self) self.proto.peer.report_error('not responding to ping') self.proto.stop() self.proto.peer.stop() self.kill() def stop(self): self.log.debug('stopped', monitor=self) self.kill()
the idle bucket-refresh interval is 3600 seconds. Aside from the previously described exclusions, node discovery closely follows system and protocol described by Maymounkov and Mazieres. """ import operator import random import time from functools import total_ordering from devp2p import slogging from .crypto import sha3 from .utils import big_endian_to_int from rlp.utils import encode_hex, is_integer, str_to_bytes log = slogging.get_logger('p2p.discovery.kademlia') k_b = 8 # 8 bits per hop k_bucket_size = 16 k_request_timeout = 3 * 300 / 1000. # timeout of message round trips k_idle_bucket_refresh_interval = 3600 # ping all nodes in bucket if bucket was idle k_find_concurrency = 3 # parallel find node lookups k_pubkey_size = 512 k_id_size = 256 k_max_node_id = 2**k_id_size - 1 def random_nodeid(): return random.randint(0, k_max_node_id)
import time import re from gevent.server import StreamServer from gevent.socket import create_connection, timeout from .service import WiredService from .protocol import BaseProtocol from .p2p_protocol import P2PProtocol from .upnp import add_portmap, remove_portmap from devp2p import kademlia from .peer import Peer from devp2p import crypto from devp2p import utils from rlp.utils import decode_hex from devp2p import slogging log = slogging.get_logger('p2p.peermgr') def on_peer_exit(peer): peer.stop() class PeerManager(WiredService): """ todo: connects new peers if there are too few selects peers based on a DHT keeps track of peer reputation saves/loads peers (rather discovery buckets) to disc
import gevent import rlp from rlp import sedes from .multiplexer import Packet from .service import WiredService from devp2p import slogging log = slogging.get_logger('protocol') class ProtocolError(Exception): pass class SubProtocolError(ProtocolError): pass class BaseProtocol(gevent.Greenlet): """ A protocol mediates between the network and the service. It implements a collection of commands. For each command X the following methods are created at initialization: - packet = protocol.create_X(*args, **kargs) - protocol.send_X(*args, **kargs) is a shortcut for: protocol.send_packet(protocol.create_X(*args, **kargs)) - protocol._receive_X(data) on protocol.receive_packet, the packet is deserialized according to the command.structure and the command.receive method called with a dict containing the received data
def get_logger(self, name): return slogging.get_logger(name)
""" Test scenario: - Run app with min_peers > max_peers to force lots of peer.stop() (too many peers) - After X seconds of unsuccessful (by definition) discovery check that len(peers) <= min_peers """ class TestDriver(object): DISCOVERY_LOOP_SEC = 10 MIN_PEERS = 2 ExampleServiceAppDisconnect.testdriver = TestDriver() # To be able to run app with min_peers > max_peers one has to bypass asserts. app_helper.assert_config = lambda a, b, c, d: True app_helper.run(ExampleApp, ExampleServiceAppDisconnect, num_nodes=3, min_peers=2, max_peers=1, random_port=True) if __name__ == "__main__": import devp2p.slogging as slogging slogging.configure(config_string=':debug,p2p:info') log = slogging.get_logger('app') TestFullApp().test_inc_counter_app(3) TestFullApp().test_inc_counter_app(6) test_app_restart() test_disconnect()
import gevent import gevent.socket import ipaddress import rlp from rlp.utils import decode_hex, is_integer, str_to_bytes, bytes_to_str, safe_ord from gevent.server import DatagramServer from devp2p import slogging from devp2p import crypto from devp2p import kademlia from devp2p import utils from .service import BaseService from .upnp import add_portmap, remove_portmap log = slogging.get_logger('p2p.discovery') class DefectiveMessage(Exception): pass class InvalidSignature(DefectiveMessage): pass class WrongMAC(DefectiveMessage): pass class PacketExpired(DefectiveMessage):
import miniupnpc from devp2p import slogging log = slogging.get_logger('p2p.upnp') _upnp = None def _init_upnp(): global _upnp if _upnp: return _upnp u = miniupnpc.UPnP() u.discoverdelay = 200 try: log.debug('Discovering... delay=%ums' % u.discoverdelay) ndevices = u.discover() log.debug('%u device(s) detected', ndevices) _upnp = u except Exception as e: log.debug('Exception :%s', e) finally: return _upnp def add_portmap(port, proto, label=''): u = _init_upnp() try: # select an igd u.selectigd()
def main(): # config import yaml import io import sys import signal import gevent from .peermanager import PeerManager from .discovery import NodeDiscovery from devp2p import slogging log = slogging.get_logger('app') # read config sample_config = b""" discovery: bootstrap_nodes: # local bootstrap - enode://6ed2fecb28ff17dec8647f08aa4368b57790000e0e9b33a7b91f32c41b6ca9ba21600e9a8c44248ce63a71544388c6745fa291f88f8b81e109ba3da11f7b41b9@127.0.0.1:30303 # go_bootstrap #- enode://6cdd090303f394a1cac34ecc9f7cda18127eafa2a3a06de39f6d920b0e583e062a7362097c7c65ee490a758b442acd5c80c6fce4b148c6a391e946b45131365b@54.169.166.226:30303 # cpp_bootstrap #- enode://4a44599974518ea5b0f14c31c4463692ac0329cb84851f3435e6d1b18ee4eae4aa495f846a0fa1219bd58035671881d44423876e57db2abd57254d0197da0ebe@5.1.83.226:30303 p2p: num_peers: 10 listen_host: 0.0.0.0 listen_port: 30303 node: privkey_hex: 65462b0520ef7d3df61b9992ed3bea0c56ead753be7c8b3614e0ce01e4cac41b """ if len(sys.argv) == 1: config = yaml.load(io.BytesIO(sample_config)) pubkey = crypto.privtopub(decode_hex(config['node']['privkey_hex'])) config['node']['id'] = crypto.sha3(pubkey) else: fn = sys.argv[1] log.info('loading config from', fn=fn) config = yaml.load(open(fn)) # stop on every unhandled exception! gevent.get_hub( ).SYSTEM_ERROR = BaseException # (KeyboardInterrupt, SystemExit, SystemError) print(config) # create app app = BaseApp(config) # register services NodeDiscovery.register_with_app(app) PeerManager.register_with_app(app) # start app app.start() # wait for interupt evt = gevent.event.Event() # gevent.signal(signal.SIGQUIT, gevent.kill) ## killall pattern gevent.signal(signal.SIGQUIT, evt.set) gevent.signal(signal.SIGTERM, evt.set) gevent.signal(signal.SIGINT, evt.set) evt.wait() # finally stop app.stop()
from devp2p.protocol import BaseProtocol from devp2p.p2p_protocol import P2PProtocol from devp2p import kademlia from devp2p.peer import Peer from devp2p import crypto from devp2p import utils from devp2p import discovery from devp2p import slogging import sys #log = slogging.get_logger('p2p.peermgr') slogging.configure(':DEBUG') root_logger = slogging.getLogger() if root_logger.handlers == []: root_logger.addHandler(slogging.logging.StreamHandler(sys.stderr)) log = slogging.get_logger('p2p') from collections import defaultdict class ETHProtocol(BaseProtocol): """ DEV Ethereum Wire Protocol https://github.com/ethereum/wiki/wiki/Ethereum-Wire-Protocol https://github.com/ethereum/go-ethereum/blob/develop/eth/protocol.go#L15 """ protocol_id = 1 network_id = 0 max_cmd_id = 15 # FIXME name = 'eth' version = 63