Skip to content

Commit

Permalink
feat(net): add discv4 crate (#113)
Browse files Browse the repository at this point in the history
* port kad

* feat: port kad bucket

* feat: add discv4

* chore: rustfmt

* cargo update

* just reuse discv5 table

* test: add rlp tests

* message encoding

* feat: impl codec roundtrip testing

* more work in message handling

* implement ping

* feat: impl commands

* cleanup

* more cleanup

* trim config

* more docs

* feat: implement recursive lookup

* docs

* cleanup config

* feat: implement update stream

* chore: config cleanup

* docs: add crate docs

* feat: more testing

* fix deny

* clarify ring

* docs: more docs

* use discv5 master

* docs: address review and add comments

* update readme

* rustmft

* chore(clippy): make clippy happy
  • Loading branch information
mattsse committed Oct 25, 2022
1 parent 2b67e75 commit ce64fef
Show file tree
Hide file tree
Showing 11 changed files with 3,231 additions and 62 deletions.
939 changes: 878 additions & 61 deletions Cargo.lock

Large diffs are not rendered by default.

1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ members = [
"crates/net/p2p",
"crates/net/ecies",
"crates/net/eth-wire",
"crates/net/discv4",
"crates/net/rpc",
"crates/net/rpc-api",
"crates/net/rpc-types",
Expand Down
42 changes: 42 additions & 0 deletions crates/net/discv4/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
[package]
name = "reth-discv4"
version = "0.1.0"
edition = "2021"
license = "Apache-2.0"
repository = "https://github.com/foundry-rs/reth"
readme = "README.md"
description = """
Ethereum network support
"""

[dependencies]
# reth
reth-primitives = { path = "../../primitives" }
reth-rlp = { path = "../../common/rlp" }
reth-rlp-derive = { path = "../../common/rlp-derive" }

# ethereum
discv5 = { git = "https://github.com/sigp/discv5" }
secp256k1 = { version = "0.24", features = [
"global-context",
"rand-std",
"recovery",
] }

# async/futures
tokio = { version = "1", features = ["io-util", "net", "time"] }
tokio-stream = "0.1"

# misc
generic-array = "0.14"
tracing = "0.1"
bytes = "1.2"
thiserror = "1.0"
url = "2.3"
hex = "0.4"
public-ip = "0.2"

[dev-dependencies]
rand = "0.8"
tokio = { version = "1", features = ["full"] }
tracing-test = "0.2"
22 changes: 22 additions & 0 deletions crates/net/discv4/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
# <h1 align="center"> discv4 </h1>

This is a rust implementation of
the [Discovery v4](https://github.com/ethereum/devp2p/blob/40ab248bf7e017e83cc9812a4e048446709623e8/discv4.md)
peer discovery protocol.

For comparison to Discovery v5,
see [discv5#comparison-with-node-discovery-v4](https://github.com/ethereum/devp2p/blob/40ab248bf7e017e83cc9812a4e048446709623e8/discv5/discv5.md#comparison-with-node-discovery-v4)

This is inspired by the [discv5](https://github.com/sigp/discv5) crate and reuses its kademlia implementation.

## Finding peers

The discovery service continuously attempts to connect to other nodes on the network until it has found enough peers.
If UPnP (Universal Plug and Play) is supported by the router the service is running on, it will also accept connections
from external nodes. In the discovery protocol, nodes exchange information about where the node can be reached to
eventually establish RLPx sessions.

## Trouble Shooting

The discv4 protocol depends on the local system clock. If the clock is not accurate it can cause connectivity issues
because the expiration timestamps might be wrong.
61 changes: 61 additions & 0 deletions crates/net/discv4/src/bootnodes.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
//! Various known bootstrap nodes for networks

// <https://github.com/ledgerwatch/erigon/blob/610e648dc43ec8cd6563313e28f06f534a9091b3/params/bootnodes.go>

use crate::node::NodeRecord;

/// Ethereum Foundation Go Bootnodes
pub static MAINNET_BOOTNODES : [&str; 8] = [
"enode://d860a01f9722d78051619d1e2351aba3f43f943f6f00718d1b9baa4101932a1f5011f16bb2b1bb35db20d6fe28fa0bf09636d26a87d31de9ec6203eeedb1f666@18.138.108.67:30303", // bootnode-aws-ap-southeast-1-001
"enode://22a8232c3abc76a16ae9d6c3b164f98775fe226f0917b0ca871128a74a8e9630b458460865bab457221f1d448dd9791d24c4e5d88786180ac185df813a68d4de@3.209.45.79:30303", // bootnode-aws-us-east-1-001
"enode://8499da03c47d637b20eee24eec3c356c9a2e6148d6fe25ca195c7949ab8ec2c03e3556126b0d7ed644675e78c4318b08691b7b57de10e5f0d40d05b09238fa0a@52.187.207.27:30303", // bootnode-azure-australiaeast-001
"enode://103858bdb88756c71f15e9b5e09b56dc1be52f0a5021d46301dbbfb7e130029cc9d0d6f73f693bc29b665770fff7da4d34f3c6379fe12721b5d7a0bcb5ca1fc1@191.234.162.198:30303", // bootnode-azure-brazilsouth-001
"enode://715171f50508aba88aecd1250af392a45a330af91d7b90701c436b618c86aaa1589c9184561907bebbb56439b8f8787bc01f49a7c77276c58c1b09822d75e8e8@52.231.165.108:30303", // bootnode-azure-koreasouth-001
"enode://5d6d7cd20d6da4bb83a1d28cadb5d409b64edf314c0335df658c1a54e32c7c4a7ab7823d57c39b6a757556e68ff1df17c748b698544a55cb488b52479a92b60f@104.42.217.25:30303", // bootnode-azure-westus-001
"enode://2b252ab6a1d0f971d9722cb839a42cb81db019ba44c08754628ab4a823487071b5695317c8ccd085219c3a03af063495b2f1da8d18218da2d6a82981b45e6ffc@65.108.70.101:30303", // bootnode-hetzner-hel
"enode://4aeb4ab6c14b23e2c4cfdce879c04b0748a20d8e9b59e25ded2a08143e265c6c25936e74cbc8e641e3312ca288673d91f2f93f8e277de3cfa444ecdaaf982052@157.90.35.166:30303", // bootnode-hetzner-fsn
];

/// SEPOLIA BOOTNODES
pub static SEPOLIA_BOOTNODES : [&str; 2] = [
// geth
"enode://9246d00bc8fd1742e5ad2428b80fc4dc45d786283e05ef6edbd9002cbc335d40998444732fbe921cb88e1d2c73d1b1de53bae6a2237996e9bfe14f871baf7066@18.168.182.86:30303",
// besu
"enode://ec66ddcf1a974950bd4c782789a7e04f8aa7110a72569b6e65fcd51e937e74eed303b1ea734e4d19cfaec9fbff9b6ee65bf31dcb50ba79acce9dd63a6aca61c7@52.14.151.177:30303",
];

/// GOERLI bootnodes
pub static GOERLI_BOOTNODES : [&str; 7] = [
// Upstream bootnodes
"enode://011f758e6552d105183b1761c5e2dea0111bc20fd5f6422bc7f91e0fabbec9a6595caf6239b37feb773dddd3f87240d99d859431891e4a642cf2a0a9e6cbb98a@51.141.78.53:30303",
"enode://176b9417f511d05b6b2cf3e34b756cf0a7096b3094572a8f6ef4cdcb9d1f9d00683bf0f83347eebdf3b81c3521c2332086d9592802230bf528eaf606a1d9677b@13.93.54.137:30303",
"enode://46add44b9f13965f7b9875ac6b85f016f341012d84f975377573800a863526f4da19ae2c620ec73d11591fa9510e992ecc03ad0751f53cc02f7c7ed6d55c7291@94.237.54.114:30313",
"enode://b5948a2d3e9d486c4d75bf32713221c2bd6cf86463302339299bd227dc2e276cd5a1c7ca4f43a0e9122fe9af884efed563bd2a1fd28661f3b5f5ad7bf1de5949@18.218.250.66:30303",

// Ethereum Foundation bootnode
"enode://a61215641fb8714a373c80edbfa0ea8878243193f57c96eeb44d0bc019ef295abd4e044fd619bfc4c59731a73fb79afe84e9ab6da0c743ceb479cbb6d263fa91@3.11.147.67:30303",

// Goerli Initiative bootnodes
"enode://d4f764a48ec2a8ecf883735776fdefe0a3949eb0ca476bd7bc8d0954a9defe8fea15ae5da7d40b5d2d59ce9524a99daedadf6da6283fca492cc80b53689fb3b3@46.4.99.122:32109",
"enode://d2b720352e8216c9efc470091aa91ddafc53e222b32780f505c817ceef69e01d5b0b0797b69db254c586f493872352f5a022b4d8479a00fc92ec55f9ad46a27e@88.99.70.182:30303",
];

/// Returns parsed mainnet nodes
pub fn mainnet_nodes() -> Vec<NodeRecord> {
parse_nodes(&MAINNET_BOOTNODES[..])
}

/// Returns parsed goerli nodes
pub fn goerli_nodes() -> Vec<NodeRecord> {
parse_nodes(&GOERLI_BOOTNODES[..])
}

/// Returns parsed sepolia nodes
pub fn sepolia_nodes() -> Vec<NodeRecord> {
parse_nodes(&SEPOLIA_BOOTNODES[..])
}

/// Parses all the nodes
pub fn parse_nodes(nodes: impl IntoIterator<Item = impl AsRef<str>>) -> Vec<NodeRecord> {
nodes.into_iter().map(|s| s.as_ref().parse().unwrap()).collect()
}
128 changes: 128 additions & 0 deletions crates/net/discv4/src/config.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,128 @@
use crate::node::NodeRecord;
use discv5::PermitBanList;
///! A set of configuration parameters to tune the discovery protocol.
// This basis of this file has been taken from the discv5 codebase:
// https://github.com/sigp/discv5
use std::collections::HashSet;
use std::time::Duration;

/// Configuration parameters that define the performance of the discovery network.
#[derive(Clone, Debug)]
pub struct Discv4Config {
/// Whether to enable the incoming packet filter. Default: false.
pub enable_packet_filter: bool,
/// The number of retries for each UDP request. Default: 1.
pub request_retries: u8,
/// The time between pings to ensure connectivity amongst connected nodes. Default: 300
/// seconds.
pub ping_interval: Duration,
/// The duration of we consider a ping timed out.
pub ping_timeout: Duration,
/// The rate at which lookups should be triggered.
pub lookup_interval: Duration,
/// The duration of we consider a FindNode request timed out.
pub find_node_timeout: Duration,
/// The duration we set for neighbours responses
pub neighbours_timeout: Duration,
/// A set of lists that permit or ban IP's or NodeIds from the server. See
/// `crate::PermitBanList`.
pub permit_ban_list: PermitBanList,
/// Set the default duration for which nodes are banned for. This timeouts are checked every 5
/// minutes, so the precision will be to the nearest 5 minutes. If set to `None`, bans from
/// the filter will last indefinitely. Default is 1 hour.
pub ban_duration: Option<Duration>,
/// Nodes to boot from.
pub bootstrap_nodes: HashSet<NodeRecord>,
}

impl Discv4Config {
/// Returns a new default builder instance
pub fn builder() -> Discv4ConfigBuilder {
Default::default()
}
}

impl Default for Discv4Config {
fn default() -> Self {
Self {
enable_packet_filter: false,
request_retries: 1,
ping_interval: Duration::from_secs(300),
ping_timeout: Duration::from_secs(5),
find_node_timeout: Duration::from_secs(2),
neighbours_timeout: Duration::from_secs(30),
lookup_interval: Duration::from_secs(20),
permit_ban_list: PermitBanList::default(),
ban_duration: Some(Duration::from_secs(3600)), // 1 hour
bootstrap_nodes: Default::default(),
}
}
}

#[derive(Debug, Default)]
pub struct Discv4ConfigBuilder {
config: Discv4Config,
}

impl Discv4ConfigBuilder {
/// Whether to enable the incoming packet filter.
pub fn enable_packet_filter(&mut self) -> &mut Self {
self.config.enable_packet_filter = true;
self
}

/// The number of retries for each UDP request.
pub fn request_retries(&mut self, retries: u8) -> &mut Self {
self.config.request_retries = retries;
self
}

/// The time between pings to ensure connectivity amongst connected nodes.
pub fn ping_interval(&mut self, interval: Duration) -> &mut Self {
self.config.ping_interval = interval;
self
}

/// Sets the timeout for pings
pub fn ping_timeout(&mut self, duration: Duration) -> &mut Self {
self.config.ping_timeout = duration;
self
}

/// A set of lists that permit or ban IP's or NodeIds from the server. See
/// `crate::PermitBanList`.
pub fn permit_ban_list(&mut self, list: PermitBanList) -> &mut Self {
self.config.permit_ban_list = list;
self
}

/// Sets the lookup interval duration.
pub fn lookup_interval(&mut self, lookup_interval: Duration) -> &mut Self {
self.config.lookup_interval = lookup_interval;
self
}

/// Set the default duration for which nodes are banned for. This timeouts are checked every 5
/// minutes, so the precision will be to the nearest 5 minutes. If set to `None`, bans from
/// the filter will last indefinitely. Default is 1 hour.
pub fn ban_duration(&mut self, ban_duration: Option<Duration>) -> &mut Self {
self.config.ban_duration = ban_duration;
self
}

/// Adds a boot node
pub fn add_boot_node(&mut self, node: NodeRecord) -> &mut Self {
self.config.bootstrap_nodes.insert(node);
self
}

/// Adds multiple boot nodes
pub fn add_boot_nodes(&mut self, nodes: impl IntoIterator<Item = NodeRecord>) -> &mut Self {
self.config.bootstrap_nodes.extend(nodes);
self
}

pub fn build(&mut self) -> Discv4Config {
self.config.clone()
}
}
36 changes: 36 additions & 0 deletions crates/net/discv4/src/error.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
//! Error types that can occur in this crate.

use tokio::sync::{mpsc::error::SendError, oneshot::error::RecvError};

/// Error thrown when decoding a UDP packet.
#[derive(Debug, thiserror::Error)]
#[allow(missing_docs)]
pub enum DecodePacketError {
#[error("Failed to rlp decode: {0:?}")]
Rlp(#[from] reth_rlp::DecodeError),
#[error("Received packet len too short.")]
PacketTooShort,
#[error("Hash of the header not equals to the hash of the data.")]
HashMismatch,
#[error("Message id {0} is not supported.")]
UnknownMessage(u8),
#[error("Failed to recover public key: {0:?}")]
Secp256k1(#[from] secp256k1::Error),
}

/// High level errors that can occur when interacting with the discovery service
#[derive(Debug, thiserror::Error)]
pub enum Discv4Error {
/// Failed to send a command over the channel
#[error("Failed to send on a closed channel")]
Send,
/// Failed to receive a command response
#[error(transparent)]
Receive(#[from] RecvError),
}

impl<T> From<SendError<T>> for Discv4Error {
fn from(_: SendError<T>) -> Self {
Discv4Error::Send
}
}

0 comments on commit ce64fef

Please sign in to comment.