![Build Status] ![Doc Status] ![Crates Status]
This is a rust implementation of the Discovery v5 peer discovery protocol.
Discovery v5 is a protocol designed for encrypted peer discovery and topic advertisement. Each peer/node
on the network is identified via it's ENR
(Ethereum Node
Record), which is essentially a signed key-value store
containing the node's public key and optionally IP address and port.
Discv5 employs a kademlia-like routing table to store and manage discovered peers and topics. The protocol allows for external IP discovery in NAT environments through regular PING/PONG's with discovered nodes. Nodes return the external IP address that they have received and a simple majority is chosen as our external IP address. If an external IP address is updated, this is produced as an event to notify the swarm (if one is used for this behaviour).
For a simple CLI discovery service see discv5-cli
A simple example of creating this service is as follows:
```rust use discv5::{enr, enr::{CombinedKey, NodeId}, TokioExecutor, Discv5, Discv5ConfigBuilder}; use std::net::SocketAddr;
// listening address and port
let listen_addr = "0.0.0.0:9000".parse::
// construct a local ENR let enrkey = CombinedKey::generatesecp256k1(); let enr = enr::EnrBuilder::new("v4").build(&enr_key).unwrap();
// build the tokio executor let mut runtime = tokio::runtime::Builder::newmultithread() .threadname("Discv5-example") .enableall() .build() .unwrap();
// default configuration let config = Discv5ConfigBuilder::new().build();
// construct the discv5 server let mut discv5 = Discv5::new(enr, enr_key, config).unwrap();
// In order to bootstrap the routing table an external ENR should be added
// This can be done via addenr. I.e.:
// discv5.addenr(
// start the discv5 server runtime.blockon(discv5.start(listenaddr));
// run a findnode query runtime.blockon(async { let foundnodes = discv5.findnode(NodeId::random()).await.unwrap(); println!("Found nodes: {:?}", found_nodes); }); ```
This protocol will drop messages (i.e not respond to requests) from peers that
advertise non-contactable address in their ENR (e.g 127.0.0.1
when connecting
to non-local nodes). This section
explains the rationale behind this design decision.
An ENR is a signed record which is primarily used in this protocol for
identifying and connecting to peers. ENRs have OPTIONAL ip
and port
fields.
If a node does not know its contactable address (i.e if it is behind a NAT), it should leave these fields
empty. This is done for the following reasons:
1. When we receive an ENR we must decide whether to add it to our local routing
table and advertise it to other peers. If a node has put some
non-contactable address in the ENR (e.g 127.0.0.1
when connecting to
non-local nodes) we cannot use this ENR
to contact the node and we therefore do not wish to advertise it to other
nodes. Putting a non-contactable address is therefore functionally
equivalent to leaving the fields empty.
2. For every new inbound connection, we do not wish to check that the address
given to us in an ENR
is contactable. We do not want the scenario, where
any peer can give us any address and force us to attempt a connection to
arbitrary addresses (to check their validity) as it consumes unnecessary
bandwidth and we want to avoid DOS attacks where malicious users spam many
nodes attempting them all to send messages to a victim IP.
To handle the above two cases this protocol filters out and only advertises contactable ENRs. It doesn't make sense for a discovery protocol to advertise non-contactable peers.
This is done in the following way:
127.0.0.1
, or
potentially some internal subnet IP that is unreachable from our current
network) we consider this peer malicious/faulty
and drop all packets. This way we can efficiently drop peers that may try to
get us to send messages to arbitrary remote IPs, and we can be sure that all
ENRs in our routing table are contactable (at least by our local node at
some point in time).