/*
This file is part of cpp - ethereum .
cpp - ethereum is free software : you can redistribute it and / or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation , either version 3 of the License , or
( at your option ) any later version .
cpp - ethereum is distributed in the hope that it will be useful ,
but WITHOUT ANY WARRANTY ; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE . See the
GNU General Public License for more details .
You should have received a copy of the GNU General Public License
along with cpp - ethereum . If not , see < http : //www.gnu.org/licenses/>.
*/
/** @file NodeTable.h
* @ author Alex Leverington < nessence @ gmail . com >
* @ date 2014
*/
# pragma once
# include <algorithm>
# include <deque>
# include <boost/integer/static_log2.hpp>
# include <libdevcrypto/Common.h>
# include <libp2p/UDP.h>
namespace dev
{
namespace p2p
{
/**
* @ brief IPv4 , UDP / TCP endpoints .
*/
struct NodeIPEndpoint
{
NodeIPEndpoint ( ) : udp ( bi : : udp : : endpoint ( ) ) , tcp ( bi : : tcp : : endpoint ( ) ) { }
NodeIPEndpoint ( bi : : udp : : endpoint _udp ) : udp ( _udp ) { }
NodeIPEndpoint ( bi : : tcp : : endpoint _tcp ) : tcp ( _tcp ) { }
NodeIPEndpoint ( bi : : udp : : endpoint _udp , bi : : tcp : : endpoint _tcp ) : udp ( _udp ) , tcp ( _tcp ) { }
bi : : udp : : endpoint udp ;
bi : : tcp : : endpoint tcp ;
operator bool ( ) const { return udp . address ( ) . is_unspecified ( ) & & tcp . address ( ) . is_unspecified ( ) ; }
} ;
struct Node
{
Node ( ) : endpoint ( NodeIPEndpoint ( ) ) { } ;
Node ( Public _pubk , NodeIPEndpoint _ip , bool _required = false ) : id ( _pubk ) , endpoint ( _ip ) , required ( _required ) { }
Node ( Public _pubk , bi : : udp : : endpoint _udp , bool _required = false ) : Node ( _pubk , NodeIPEndpoint ( _udp ) , _required ) { }
virtual NodeId const & address ( ) const { return id ; }
virtual Public const & publicKey ( ) const { return id ; }
NodeId id ;
/// Endpoints by which we expect to reach node.
NodeIPEndpoint endpoint ;
/// If true, node will not be removed from Node list.
bool required = false ;
operator bool ( ) const { return ( bool ) id ; }
} ;
/**
* NodeEntry
* @ brief Entry in Node Table
*/
struct NodeEntry : public Node
{
NodeEntry ( Node _src , Public _pubk , NodeIPEndpoint _gw ) ;
NodeEntry ( Node _src , Public _pubk , bi : : udp : : endpoint _udp ) ;
const unsigned distance ; ///< Node's distance (xor of _src as integer).
} ;
enum NodeTableEventType {
NodeEntryAdded ,
NodeEntryRemoved
} ;
class NodeTable ;
class NodeTableEventHandler
{
friend class NodeTable ;
public :
virtual void processEvent ( NodeId _n , NodeTableEventType _e ) = 0 ;
protected :
/// Called by NodeTable on behalf of an implementation (Host) to process new events without blocking nodetable.
void processEvents ( ) { std : : list < std : : pair < NodeId , NodeTableEventType > > events ; { Guard l ( x_events ) ; if ( ! m_nodeEvents . size ( ) ) return ; m_nodeEvents . unique ( ) ; for ( auto const & n : m_nodeEvents ) events . push_back ( std : : make_pair ( n , m_events [ n ] ) ) ; m_nodeEvents . empty ( ) ; m_events . empty ( ) ; } for ( auto const & e : events ) processEvent ( e . first , e . second ) ; }
/// Called by NodeTable to append event.
virtual void appendEvent ( NodeId _n , NodeTableEventType _e ) { Guard l ( x_events ) ; m_nodeEvents . push_back ( _n ) ; m_events [ _n ] = _e ; }
Mutex x_events ;
std : : list < NodeId > m_nodeEvents ;
std : : map < NodeId , NodeTableEventType > m_events ;
} ;
/**
* NodeTable using modified kademlia for node discovery and preference .
* untouched buckets are refreshed if they have not been touched within an hour
*
* Thread - safety is ensured by modifying NodeEntry details via
* shared_ptr replacement instead of mutating values .
*
* [ Integration ]
* @ todo deadline - timer which maintains tcp / peer connections
* @ todo restore nodes : affects refreshbuckets
* @ todo TCP endpoints
* @ todo makeRequired : don ' t try to evict node if node isRequired .
* @ todo makeRequired : exclude bucket from refresh if we have node as peer .
*
* [ Optimization ]
* @ todo encapsulate doFindNode into NetworkAlgorithm ( task )
* @ todo Pong to include ip : port where ping was received
* @ todo expiration and sha3 ( id ) ' to ' for messages which are replies ( prevents replay )
* @ todo std : : shared_ptr < PingNode > m_cachedPingPacket ;
* @ todo std : : shared_ptr < FindNeighbours > m_cachedFindSelfPacket ;
* @ todo store root node in table ?
*
* [ Networking ]
* @ todo TCP endpoints
* @ todo eth / upnp / natpmp / stun / ice / etc for public - discovery
* @ todo firewall
*
* [ Protocol ]
* @ todo post - eviction pong
* @ todo optimize knowledge at opposite edges ; eg , s_bitsPerStep lookups . ( Can be done via pointers to NodeBucket )
* @ todo ^ s_bitsPerStep = 5 ; // Denoted by b in [Kademlia]. Bits by which address space is divided.
* @ todo optimize ( use tree for state and / or custom compare for cache )
* @ todo reputation ( aka universal siblings lists )
*/
class NodeTable : UDPSocketEvents , public std : : enable_shared_from_this < NodeTable >
{
friend struct Neighbours ;
using NodeSocket = UDPSocket < NodeTable , 1280 > ;
using TimePoint = std : : chrono : : steady_clock : : time_point ;
using EvictionTimeout = std : : pair < std : : pair < NodeId , TimePoint > , NodeId > ; ///< First NodeId may be evicted and replaced with second NodeId.
public :
NodeTable ( ba : : io_service & _io , KeyPair _alias , uint16_t _udpPort = 30303 ) ;
~ NodeTable ( ) ;
/// Constants for Kademlia, mostly derived from address space.
static unsigned const s_addressByteSize = sizeof ( NodeId ) ; ///< Size of address type in bytes.
static unsigned const s_bits = 8 * s_addressByteSize ; ///< Denoted by n in [Kademlia].
static unsigned const s_bins = s_bits - 1 ; ///< Size of m_state (excludes root, which is us).
static unsigned const s_maxSteps = boost : : static_log2 < s_bits > : : value ; ///< Max iterations of discovery. (doFindNode)
/// Chosen constants
static unsigned const s_bucketSize = 16 ; ///< Denoted by k in [Kademlia]. Number of nodes stored in each bucket.
static unsigned const s_alpha = 3 ; ///< Denoted by \alpha in [Kademlia]. Number of concurrent FindNode requests.
/// Intervals
boost : : posix_time : : milliseconds const c_evictionCheckInterval = boost : : posix_time : : milliseconds ( 75 ) ; ///< Interval at which eviction timeouts are checked.
std : : chrono : : milliseconds const c_reqTimeout = std : : chrono : : milliseconds ( 300 ) ; ///< How long to wait for requests (evict, find iterations).
std : : chrono : : seconds const c_bucketRefresh = std : : chrono : : seconds ( 3600 ) ; ///< Refresh interval prevents bucket from becoming stale. [Kademlia]
static unsigned dist ( NodeId const & _a , NodeId const & _b ) { u512 d = _a ^ _b ; unsigned ret ; for ( ret = 0 ; d > > = 1 ; + + ret ) { } ; return ret ; }
/// Set event handler for NodeEntryAdded and NodeEntryRemoved events.
void setEventHandler ( NodeTableEventHandler * _handler ) { m_nodeEvents . reset ( _handler ) ; }
/// Called by implementation which provided handler to process NodeEntryAdded/NodeEntryRemoved events. Events are coalesced by type whereby old events are ignored.
void processEvents ( ) { if ( m_nodeEvents ) m_nodeEvents - > processEvents ( ) ; }
/// Add node. Node will be pinged if it's not already known.
std : : shared_ptr < NodeEntry > addNode ( Public const & _pubk , bi : : udp : : endpoint const & _udp , bi : : tcp : : endpoint const & _tcp = bi : : tcp : : endpoint ( ) ) ;
/// Add node. Node will be pinged if it's not already known.
std : : shared_ptr < NodeEntry > addNode ( Node const & _node ) ;
void join ( ) ;
NodeEntry root ( ) const { return NodeEntry ( m_node , m_node . publicKey ( ) , m_node . endpoint . udp ) ; }
std : : list < NodeId > nodes ( ) const ;
std : : list < NodeEntry > state ( ) const ;
bool haveNode ( NodeId _id ) { Guard l ( x_nodes ) ; return ! ! m_nodes [ _id ] ; }
Node operator [ ] ( NodeId _id ) ;
std : : shared_ptr < NodeEntry > getNodeEntry ( NodeId _id ) ;
protected :
struct NodeBucket
{
unsigned distance ;
TimePoint modified ;
std : : list < std : : weak_ptr < NodeEntry > > nodes ;
} ;
/// Repeatedly sends s_alpha concurrent requests to nodes nearest to target, for nodes nearest to target, up to s_maxSteps rounds.
void doFindNode ( NodeId _node , unsigned _round = 0 , std : : shared_ptr < std : : set < std : : shared_ptr < NodeEntry > > > _tried = std : : shared_ptr < std : : set < std : : shared_ptr < NodeEntry > > > ( ) ) ;
/// Returns nodes nearest to target.
std : : vector < std : : shared_ptr < NodeEntry > > findNearest ( NodeId _target ) ;
void ping ( bi : : udp : : endpoint _to ) const ;
void ping ( NodeEntry * _n ) const ;
void evict ( std : : shared_ptr < NodeEntry > _leastSeen , std : : shared_ptr < NodeEntry > _new ) ;
void noteNode ( Public const & _pubk , bi : : udp : : endpoint const & _endpoint ) ;
void noteNode ( std : : shared_ptr < NodeEntry > _n ) ;
void dropNode ( std : : shared_ptr < NodeEntry > _n ) ;
NodeBucket & bucket ( NodeEntry const * _n ) ;
/// General Network Events
void onReceived ( UDPSocketFace * , bi : : udp : : endpoint const & _from , bytesConstRef _packet ) ;
void onDisconnected ( UDPSocketFace * ) { } ;
/// Tasks
void doCheckEvictions ( boost : : system : : error_code const & _ec ) ;
void doRefreshBuckets ( boost : : system : : error_code const & _ec ) ;
# ifndef BOOST_AUTO_TEST_SUITE
private :
# else
protected :
# endif
/// Sends FindNeighbor packet. See doFindNode.
void requestNeighbours ( NodeEntry const & _node , NodeId _target ) const ;
std : : unique_ptr < NodeTableEventHandler > m_nodeEvents ; ///< Event handler for node events.
Node m_node ; ///< This node.
Secret m_secret ; ///< This nodes secret key.
mutable Mutex x_nodes ; ///< Mutable for thread-safe copy in nodes() const.
std : : map < NodeId , std : : shared_ptr < NodeEntry > > m_nodes ; ///< Nodes
mutable Mutex x_state ;
std : : array < NodeBucket , s_bins > m_state ; ///< State of p2p node network.
Mutex x_evictions ;
std : : deque < EvictionTimeout > m_evictions ; ///< Eviction timeouts.
ba : : io_service & m_io ; ///< Used by bucket refresh timer.
std : : shared_ptr < NodeSocket > m_socket ; ///< Shared pointer for our UDPSocket; ASIO requires shared_ptr.
NodeSocket * m_socketPtr ; ///< Set to m_socket.get().
boost : : asio : : deadline_timer m_bucketRefreshTimer ; ///< Timer which schedules and enacts bucket refresh.
boost : : asio : : deadline_timer m_evictionCheckTimer ; ///< Timer for handling node evictions.
} ;
inline std : : ostream & operator < < ( std : : ostream & _out , NodeTable const & _nodeTable )
{
_out < < _nodeTable . root ( ) . address ( ) < < " \t " < < " 0 \t " < < _nodeTable . root ( ) . endpoint . udp . address ( ) < < " : " < < _nodeTable . root ( ) . endpoint . udp . port ( ) < < std : : endl ;
auto s = _nodeTable . state ( ) ;
for ( auto n : s )
_out < < n . address ( ) < < " \t " < < n . distance < < " \t " < < n . endpoint . udp . address ( ) < < " : " < < n . endpoint . udp . port ( ) < < std : : endl ;
return _out ;
}
/**
* Ping packet : Check if node is alive .
* PingNode is cached and regenerated after expiration - t , where t is timeout .
*
* RLP Encoded Items : 3
* Minimum Encoded Size : 18 bytes
* Maximum Encoded Size : bytes // todo after u128 addresses
*
* signature : Signature of message .
* ipAddress : Our IP address .
* port : Our port .
* expiration : Triggers regeneration of packet . May also provide control over synchronization .
*
* Ping is used to implement evict . When a new node is seen for
* a given bucket which is full , the least - responsive node is pinged .
* If the pinged node doesn ' t respond then it is removed and the new
* node is inserted .
*
* @ todo uint128_t for ip address ( < - > integer ipv4 / 6 , asio - address , asio - endpoint )
*
*/
struct PingNode : RLPXDatagram < PingNode >
{
PingNode ( bi : : udp : : endpoint _ep ) : RLPXDatagram < PingNode > ( _ep ) { }
PingNode ( bi : : udp : : endpoint _ep , std : : string _src , uint16_t _srcPort , std : : chrono : : seconds _expiration = std : : chrono : : seconds ( 60 ) ) : RLPXDatagram < PingNode > ( _ep ) , ipAddress ( _src ) , port ( _srcPort ) , expiration ( futureFromEpoch ( _expiration ) ) { }
std : : string ipAddress ;
unsigned port ;
unsigned expiration ;
void streamRLP ( RLPStream & _s ) const { _s . appendList ( 3 ) ; _s < < ipAddress < < port < < expiration ; }
void interpretRLP ( bytesConstRef _bytes ) { RLP r ( _bytes ) ; ipAddress = r [ 0 ] . toString ( ) ; port = r [ 1 ] . toInt < unsigned > ( ) ; expiration = r [ 2 ] . toInt < unsigned > ( ) ; }
} ;
/**
* Pong packet : response to ping
*
* RLP Encoded Items : 1
* Minimum Encoded Size : 33 bytes
* Maximum Encoded Size : 33 bytes
*
* @ todo expiration
* @ todo value of replyTo
* @ todo create from PingNode ( reqs RLPXDatagram verify flag )
*/
struct Pong : RLPXDatagram < Pong >
{
Pong ( bi : : udp : : endpoint _ep ) : RLPXDatagram < Pong > ( _ep ) { }
h256 replyTo ; // hash of rlp of PingNode
unsigned expiration ;
void streamRLP ( RLPStream & _s ) const { _s . appendList ( 1 ) ; _s < < replyTo ; }
void interpretRLP ( bytesConstRef _bytes ) { RLP r ( _bytes ) ; replyTo = ( h256 ) r [ 0 ] ; }
} ;
/**
* FindNode Packet : Request k - nodes , closest to the target .
* FindNode is cached and regenerated after expiration - t , where t is timeout .
* FindNode implicitly results in finding neighbours of a given node .
*
* RLP Encoded Items : 2
* Minimum Encoded Size : 21 bytes
* Maximum Encoded Size : 30 bytes
*
* target : NodeId of node . The responding node will send back nodes closest to the target .
* expiration : Triggers regeneration of packet . May also provide control over synchronization .
*
*/
struct FindNode : RLPXDatagram < FindNode >
{
FindNode ( bi : : udp : : endpoint _ep ) : RLPXDatagram < FindNode > ( _ep ) { }
FindNode ( bi : : udp : : endpoint _ep , NodeId _target , std : : chrono : : seconds _expiration = std : : chrono : : seconds ( 30 ) ) : RLPXDatagram < FindNode > ( _ep ) , target ( _target ) , expiration ( futureFromEpoch ( _expiration ) ) { }
h512 target ;
unsigned expiration ;
void streamRLP ( RLPStream & _s ) const { _s . appendList ( 2 ) ; _s < < target < < expiration ; }
void interpretRLP ( bytesConstRef _bytes ) { RLP r ( _bytes ) ; target = r [ 0 ] . toHash < h512 > ( ) ; expiration = r [ 1 ] . toInt < unsigned > ( ) ; }
} ;
/**
* Node Packet : Multiple node packets are sent in response to FindNode .
*
* RLP Encoded Items : 2 ( first item is list )
* Minimum Encoded Size : 10 bytes
*
* @ todo nonce : Should be replaced with expiration .
*/
struct Neighbours : RLPXDatagram < Neighbours >
{
struct Node
{
Node ( ) = default ;
Node ( RLP const & _r ) { interpretRLP ( _r ) ; }
std : : string ipAddress ;
unsigned port ;
NodeId node ;
void streamRLP ( RLPStream & _s ) const { _s . appendList ( 3 ) ; _s < < ipAddress < < port < < node ; }
void interpretRLP ( RLP const & _r ) { ipAddress = _r [ 0 ] . toString ( ) ; port = _r [ 1 ] . toInt < unsigned > ( ) ; node = h512 ( _r [ 2 ] . toBytes ( ) ) ; }
} ;
Neighbours ( bi : : udp : : endpoint _ep ) : RLPXDatagram < Neighbours > ( _ep ) { }
Neighbours ( bi : : udp : : endpoint _to , std : : vector < std : : shared_ptr < NodeEntry > > const & _nearest , unsigned _offset = 0 , unsigned _limit = 0 ) : RLPXDatagram < Neighbours > ( _to )
{
auto limit = _limit ? std : : min ( _nearest . size ( ) , ( size_t ) ( _offset + _limit ) ) : _nearest . size ( ) ;
for ( auto i = _offset ; i < limit ; i + + )
{
Node node ;
node . ipAddress = _nearest [ i ] - > endpoint . udp . address ( ) . to_string ( ) ;
node . port = _nearest [ i ] - > endpoint . udp . port ( ) ;
node . node = _nearest [ i ] - > publicKey ( ) ;
nodes . push_back ( node ) ;
}
}
std : : list < Node > nodes ;
unsigned expiration = 1 ;
void streamRLP ( RLPStream & _s ) const { _s . appendList ( 2 ) ; _s . appendList ( nodes . size ( ) ) ; for ( auto & n : nodes ) n . streamRLP ( _s ) ; _s < < expiration ; }
void interpretRLP ( bytesConstRef _bytes ) { RLP r ( _bytes ) ; for ( auto n : r [ 0 ] ) nodes . push_back ( Node ( n ) ) ; expiration = r [ 1 ] . toInt < unsigned > ( ) ; }
} ;
struct NodeTableWarn : public LogChannel { static const char * name ( ) { return " !P! " ; } static const int verbosity = 0 ; } ;
struct NodeTableNote : public LogChannel { static const char * name ( ) { return " *P* " ; } static const int verbosity = 1 ; } ;
struct NodeTableMessageSummary : public LogChannel { static const char * name ( ) { return " -P- " ; } static const int verbosity = 2 ; } ;
struct NodeTableConnect : public LogChannel { static const char * name ( ) { return " +P+ " ; } static const int verbosity = 10 ; } ;
struct NodeTableMessageDetail : public LogChannel { static const char * name ( ) { return " =P= " ; } static const int verbosity = 5 ; } ;
struct NodeTableTriviaSummary : public LogChannel { static const char * name ( ) { return " -P- " ; } static const int verbosity = 10 ; } ;
struct NodeTableTriviaDetail : public LogChannel { static const char * name ( ) { return " =P= " ; } static const int verbosity = 11 ; } ;
struct NodeTableAllDetail : public LogChannel { static const char * name ( ) { return " =P= " ; } static const int verbosity = 13 ; } ;
struct NodeTableEgress : public LogChannel { static const char * name ( ) { return " >>P " ; } static const int verbosity = 14 ; } ;
struct NodeTableIngress : public LogChannel { static const char * name ( ) { return " <<P " ; } static const int verbosity = 15 ; } ;
}
}