feat: resolve domains in enode strings (#8188)

Co-authored-by: Serge Radinovich <47865535+sergerad@users.noreply.github.com>
This commit is contained in:
Dan Cline
2024-06-05 19:43:25 -04:00
committed by GitHub
parent c5e38073b5
commit ef3f67743d
22 changed files with 470 additions and 64 deletions

72
Cargo.lock generated
View File

@ -140,7 +140,7 @@ dependencies = [
[[package]] [[package]]
name = "alloy-consensus" name = "alloy-consensus"
version = "0.1.0" version = "0.1.0"
source = "git+https://github.com/alloy-rs/alloy#4ecb7d86882ece8a9a7a5a892b71a3c198030731" source = "git+https://github.com/alloy-rs/alloy#eaf53556d1ee4ac0e611d6c06e3732a24f1d11ab"
dependencies = [ dependencies = [
"alloy-eips 0.1.0 (git+https://github.com/alloy-rs/alloy)", "alloy-eips 0.1.0 (git+https://github.com/alloy-rs/alloy)",
"alloy-primitives", "alloy-primitives",
@ -165,7 +165,7 @@ dependencies = [
"itoa", "itoa",
"serde", "serde",
"serde_json", "serde_json",
"winnow 0.6.9", "winnow 0.6.10",
] ]
[[package]] [[package]]
@ -189,7 +189,7 @@ dependencies = [
[[package]] [[package]]
name = "alloy-eips" name = "alloy-eips"
version = "0.1.0" version = "0.1.0"
source = "git+https://github.com/alloy-rs/alloy#4ecb7d86882ece8a9a7a5a892b71a3c198030731" source = "git+https://github.com/alloy-rs/alloy#eaf53556d1ee4ac0e611d6c06e3732a24f1d11ab"
dependencies = [ dependencies = [
"alloy-primitives", "alloy-primitives",
"alloy-rlp", "alloy-rlp",
@ -214,7 +214,7 @@ dependencies = [
[[package]] [[package]]
name = "alloy-genesis" name = "alloy-genesis"
version = "0.1.0" version = "0.1.0"
source = "git+https://github.com/alloy-rs/alloy#4ecb7d86882ece8a9a7a5a892b71a3c198030731" source = "git+https://github.com/alloy-rs/alloy#eaf53556d1ee4ac0e611d6c06e3732a24f1d11ab"
dependencies = [ dependencies = [
"alloy-primitives", "alloy-primitives",
"alloy-serde 0.1.0 (git+https://github.com/alloy-rs/alloy)", "alloy-serde 0.1.0 (git+https://github.com/alloy-rs/alloy)",
@ -405,7 +405,7 @@ dependencies = [
[[package]] [[package]]
name = "alloy-rpc-types" name = "alloy-rpc-types"
version = "0.1.0" version = "0.1.0"
source = "git+https://github.com/alloy-rs/alloy#4ecb7d86882ece8a9a7a5a892b71a3c198030731" source = "git+https://github.com/alloy-rs/alloy#eaf53556d1ee4ac0e611d6c06e3732a24f1d11ab"
dependencies = [ dependencies = [
"alloy-consensus 0.1.0 (git+https://github.com/alloy-rs/alloy)", "alloy-consensus 0.1.0 (git+https://github.com/alloy-rs/alloy)",
"alloy-eips 0.1.0 (git+https://github.com/alloy-rs/alloy)", "alloy-eips 0.1.0 (git+https://github.com/alloy-rs/alloy)",
@ -486,7 +486,7 @@ dependencies = [
[[package]] [[package]]
name = "alloy-serde" name = "alloy-serde"
version = "0.1.0" version = "0.1.0"
source = "git+https://github.com/alloy-rs/alloy#4ecb7d86882ece8a9a7a5a892b71a3c198030731" source = "git+https://github.com/alloy-rs/alloy#eaf53556d1ee4ac0e611d6c06e3732a24f1d11ab"
dependencies = [ dependencies = [
"alloy-primitives", "alloy-primitives",
"serde", "serde",
@ -579,7 +579,7 @@ version = "0.7.4"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "368cae4dc052cad1d8f72eb2ae0c38027116933eeb49213c200a9e9875f208d7" checksum = "368cae4dc052cad1d8f72eb2ae0c38027116933eeb49213c200a9e9875f208d7"
dependencies = [ dependencies = [
"winnow 0.6.9", "winnow 0.6.10",
] ]
[[package]] [[package]]
@ -2989,7 +2989,7 @@ dependencies = [
[[package]] [[package]]
name = "foundry-blob-explorers" name = "foundry-blob-explorers"
version = "0.1.0" version = "0.1.0"
source = "git+https://github.com/foundry-rs/block-explorers#1b024125d8327595f67f18a60ac29c49056c3a6d" source = "git+https://github.com/foundry-rs/block-explorers#1674a68b073a3637c16f2d3f9700cf6332ffe4a6"
dependencies = [ dependencies = [
"alloy-chains", "alloy-chains",
"alloy-eips 0.1.0 (git+https://github.com/alloy-rs/alloy)", "alloy-eips 0.1.0 (git+https://github.com/alloy-rs/alloy)",
@ -3554,9 +3554,9 @@ dependencies = [
[[package]] [[package]]
name = "hyper" name = "hyper"
version = "0.14.28" version = "0.14.29"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bf96e135eb83a2a8ddf766e426a841d8ddd7449d5f00d34ea02b41d2f19eef80" checksum = "f361cde2f109281a220d4307746cdfd5ee3f410da58a70377762396775634b33"
dependencies = [ dependencies = [
"bytes", "bytes",
"futures-channel", "futures-channel",
@ -3603,7 +3603,7 @@ checksum = "ec3efd23720e2049821a693cbc7e65ea87c72f1c58ff2f9522ff332b1491e590"
dependencies = [ dependencies = [
"futures-util", "futures-util",
"http 0.2.12", "http 0.2.12",
"hyper 0.14.28", "hyper 0.14.29",
"log", "log",
"rustls 0.21.12", "rustls 0.21.12",
"rustls-native-certs 0.6.3", "rustls-native-certs 0.6.3",
@ -4154,7 +4154,7 @@ dependencies = [
"beef", "beef",
"futures-timer", "futures-timer",
"futures-util", "futures-util",
"hyper 0.14.28", "hyper 0.14.29",
"jsonrpsee-types", "jsonrpsee-types",
"parking_lot 0.12.3", "parking_lot 0.12.3",
"pin-project", "pin-project",
@ -4176,7 +4176,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1ccf93fc4a0bfe05d851d37d7c32b7f370fe94336b52a2f0efc5f1981895c2e5" checksum = "1ccf93fc4a0bfe05d851d37d7c32b7f370fe94336b52a2f0efc5f1981895c2e5"
dependencies = [ dependencies = [
"async-trait", "async-trait",
"hyper 0.14.28", "hyper 0.14.29",
"hyper-rustls 0.24.2", "hyper-rustls 0.24.2",
"jsonrpsee-core", "jsonrpsee-core",
"jsonrpsee-types", "jsonrpsee-types",
@ -4210,7 +4210,7 @@ checksum = "12d8b6a9674422a8572e0b0abb12feeb3f2aeda86528c80d0350c2bd0923ab41"
dependencies = [ dependencies = [
"futures-util", "futures-util",
"http 0.2.12", "http 0.2.12",
"hyper 0.14.28", "hyper 0.14.29",
"jsonrpsee-core", "jsonrpsee-core",
"jsonrpsee-types", "jsonrpsee-types",
"pin-project", "pin-project",
@ -5716,9 +5716,9 @@ dependencies = [
[[package]] [[package]]
name = "proc-macro2" name = "proc-macro2"
version = "1.0.84" version = "1.0.85"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ec96c6a92621310b51366f1e28d05ef11489516e93be030060e5fc12024a49d6" checksum = "22244ce15aa966053a896d1accb3a6e68469b97c7f33f284b99f0d576879fc23"
dependencies = [ dependencies = [
"unicode-ident", "unicode-ident",
] ]
@ -6070,7 +6070,7 @@ dependencies = [
"h2", "h2",
"http 0.2.12", "http 0.2.12",
"http-body 0.4.6", "http-body 0.4.6",
"hyper 0.14.28", "hyper 0.14.29",
"ipnet", "ipnet",
"js-sys", "js-sys",
"log", "log",
@ -6191,6 +6191,7 @@ dependencies = [
"reth-evm", "reth-evm",
"reth-exex", "reth-exex",
"reth-fs-util", "reth-fs-util",
"reth-net-common",
"reth-network", "reth-network",
"reth-network-api", "reth-network-api",
"reth-network-p2p", "reth-network-p2p",
@ -7117,6 +7118,7 @@ dependencies = [
"serde_json", "serde_json",
"serde_with", "serde_with",
"thiserror", "thiserror",
"tokio",
"url", "url",
] ]
@ -7160,6 +7162,7 @@ name = "reth-node-builder"
version = "0.2.0-beta.8" version = "0.2.0-beta.8"
dependencies = [ dependencies = [
"aquamarine", "aquamarine",
"backon",
"confy", "confy",
"eyre", "eyre",
"fdlimit", "fdlimit",
@ -7212,7 +7215,7 @@ dependencies = [
"eyre", "eyre",
"futures", "futures",
"humantime", "humantime",
"hyper 0.14.28", "hyper 0.14.29",
"metrics", "metrics",
"metrics-exporter-prometheus", "metrics-exporter-prometheus",
"metrics-process", "metrics-process",
@ -7235,6 +7238,7 @@ dependencies = [
"reth-net-nat", "reth-net-nat",
"reth-network", "reth-network",
"reth-network-p2p", "reth-network-p2p",
"reth-network-types",
"reth-primitives", "reth-primitives",
"reth-provider", "reth-provider",
"reth-rpc", "reth-rpc",
@ -7316,7 +7320,7 @@ dependencies = [
"async-trait", "async-trait",
"clap", "clap",
"eyre", "eyre",
"hyper 0.14.28", "hyper 0.14.29",
"jsonrpsee", "jsonrpsee",
"parking_lot 0.12.3", "parking_lot 0.12.3",
"reqwest 0.12.4", "reqwest 0.12.4",
@ -7557,7 +7561,7 @@ dependencies = [
"futures", "futures",
"http 0.2.12", "http 0.2.12",
"http-body 0.4.6", "http-body 0.4.6",
"hyper 0.14.28", "hyper 0.14.29",
"jsonrpsee", "jsonrpsee",
"jsonwebtoken 8.3.0", "jsonwebtoken 8.3.0",
"metrics", "metrics",
@ -7630,7 +7634,7 @@ dependencies = [
name = "reth-rpc-builder" name = "reth-rpc-builder"
version = "0.2.0-beta.8" version = "0.2.0-beta.8"
dependencies = [ dependencies = [
"hyper 0.14.28", "hyper 0.14.29",
"jsonrpsee", "jsonrpsee",
"metrics", "metrics",
"pin-project", "pin-project",
@ -7705,7 +7709,7 @@ dependencies = [
"assert_matches", "assert_matches",
"http 0.2.12", "http 0.2.12",
"http-body 0.4.6", "http-body 0.4.6",
"hyper 0.14.28", "hyper 0.14.29",
"jsonrpsee", "jsonrpsee",
"pin-project", "pin-project",
"tempfile", "tempfile",
@ -8210,9 +8214,9 @@ dependencies = [
[[package]] [[package]]
name = "ruint" name = "ruint"
version = "1.12.1" version = "1.12.3"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8f308135fef9fc398342da5472ce7c484529df23743fb7c734e0f3d472971e62" checksum = "2c3cc4c2511671f327125da14133d0c5c5d137f006a1017a16f557bc85b16286"
dependencies = [ dependencies = [
"alloy-rlp", "alloy-rlp",
"arbitrary", "arbitrary",
@ -8235,9 +8239,9 @@ dependencies = [
[[package]] [[package]]
name = "ruint-macro" name = "ruint-macro"
version = "1.2.0" version = "1.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f86854cf50259291520509879a5c294c3c9a4c334e9ff65071c51e42ef1e2343" checksum = "48fd7bd8a6377e15ad9d42a8ec25371b94ddc67abe7c8b9127bec79bebaaae18"
[[package]] [[package]]
name = "rusqlite" name = "rusqlite"
@ -9530,14 +9534,14 @@ dependencies = [
[[package]] [[package]]
name = "toml" name = "toml"
version = "0.8.13" version = "0.8.14"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a4e43f8cc456c9704c851ae29c67e17ef65d2c30017c17a9765b89c382dc8bba" checksum = "6f49eb2ab21d2f26bd6db7bf383edc527a7ebaee412d17af4d40fdccd442f335"
dependencies = [ dependencies = [
"serde", "serde",
"serde_spanned", "serde_spanned",
"toml_datetime", "toml_datetime",
"toml_edit 0.22.13", "toml_edit 0.22.14",
] ]
[[package]] [[package]]
@ -9562,15 +9566,15 @@ dependencies = [
[[package]] [[package]]
name = "toml_edit" name = "toml_edit"
version = "0.22.13" version = "0.22.14"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c127785850e8c20836d49732ae6abfa47616e60bf9d9f57c43c250361a9db96c" checksum = "f21c7aaf97f1bd9ca9d4f9e73b0a6c74bd5afef56f2bc931943a6e1c37e04e38"
dependencies = [ dependencies = [
"indexmap 2.2.6", "indexmap 2.2.6",
"serde", "serde",
"serde_spanned", "serde_spanned",
"toml_datetime", "toml_datetime",
"winnow 0.6.9", "winnow 0.6.10",
] ]
[[package]] [[package]]
@ -10451,9 +10455,9 @@ dependencies = [
[[package]] [[package]]
name = "winnow" name = "winnow"
version = "0.6.9" version = "0.6.10"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "86c949fede1d13936a99f14fafd3e76fd642b556dd2ce96287fbe2e0151bfac6" checksum = "f217b6745021054125ef5741032a021a9c65f82bee2a8017cca928f1e3179991"
dependencies = [ dependencies = [
"memchr", "memchr",
] ]

View File

@ -378,6 +378,7 @@ dyn-clone = "1.0.17"
sha2 = { version = "0.10", default-features = false } sha2 = { version = "0.10", default-features = false }
paste = "1.0" paste = "1.0"
url = "2.3" url = "2.3"
backon = "0.4"
# metrics # metrics
metrics = "0.22.0" metrics = "0.22.0"

View File

@ -37,6 +37,7 @@ reth-rpc-types-compat.workspace = true
reth-rpc-api = { workspace = true, features = ["client"] } reth-rpc-api = { workspace = true, features = ["client"] }
reth-network = { workspace = true, features = ["serde"] } reth-network = { workspace = true, features = ["serde"] }
reth-network-p2p.workspace = true reth-network-p2p.workspace = true
reth-net-common.workspace = true
reth-network-api.workspace = true reth-network-api.workspace = true
reth-downloaders.workspace = true reth-downloaders.workspace = true
reth-tracing.workspace = true reth-tracing.workspace = true
@ -104,7 +105,7 @@ aquamarine.workspace = true
eyre.workspace = true eyre.workspace = true
clap = { workspace = true, features = ["derive", "env"] } clap = { workspace = true, features = ["derive", "env"] }
tempfile.workspace = true tempfile.workspace = true
backon = "0.4" backon.workspace = true
similar-asserts.workspace = true similar-asserts.workspace = true
itertools.workspace = true itertools.workspace = true
rayon.workspace = true rayon.workspace = true

View File

@ -88,8 +88,8 @@ impl Command {
let mut config: Config = confy::load_path(&config_path).unwrap_or_default(); let mut config: Config = confy::load_path(&config_path).unwrap_or_default();
for &peer in &self.network.trusted_peers { for peer in &self.network.trusted_peers {
config.peers.trusted_nodes.insert(peer); config.peers.trusted_nodes.insert(peer.resolve().await?);
} }
if config.peers.trusted_nodes.is_empty() && self.network.trusted_only { if config.peers.trusted_nodes.is_empty() && self.network.trusted_only {

View File

@ -118,9 +118,10 @@ impl Command {
let mut config = config; let mut config = config;
config.peers.trusted_nodes_only = self.network.trusted_only; config.peers.trusted_nodes_only = self.network.trusted_only;
if !self.network.trusted_peers.is_empty() { if !self.network.trusted_peers.is_empty() {
self.network.trusted_peers.iter().for_each(|peer| { for peer in &self.network.trusted_peers {
config.peers.trusted_nodes.insert(*peer); let peer = peer.resolve().await?;
}); config.peers.trusted_nodes.insert(peer);
}
} }
let network_secret_path = self let network_secret_path = self

View File

@ -126,6 +126,11 @@ Networking:
Will fall back to a network-specific default if not specified. Will fall back to a network-specific default if not specified.
--dns-retries <DNS_RETRIES>
Amount of DNS resolution requests retries to perform when peering
[default: 0]
--peers-file <FILE> --peers-file <FILE>
The path to the known peers file. Connected peers are dumped to this file on nodes The path to the known peers file. Connected peers are dumped to this file on nodes
shutdown, and read on startup. Cannot be used with `--no-persist-peers`. shutdown, and read on startup. Cannot be used with `--no-persist-peers`.

View File

@ -110,6 +110,11 @@ Networking:
Will fall back to a network-specific default if not specified. Will fall back to a network-specific default if not specified.
--dns-retries <DNS_RETRIES>
Amount of DNS resolution requests retries to perform when peering
[default: 0]
--peers-file <FILE> --peers-file <FILE>
The path to the known peers file. Connected peers are dumped to this file on nodes The path to the known peers file. Connected peers are dumped to this file on nodes
shutdown, and read on startup. Cannot be used with `--no-persist-peers`. shutdown, and read on startup. Cannot be used with `--no-persist-peers`.

View File

@ -177,6 +177,11 @@ Networking:
Will fall back to a network-specific default if not specified. Will fall back to a network-specific default if not specified.
--dns-retries <DNS_RETRIES>
Amount of DNS resolution requests retries to perform when peering
[default: 0]
--peers-file <FILE> --peers-file <FILE>
The path to the known peers file. Connected peers are dumped to this file on nodes The path to the known peers file. Connected peers are dumped to this file on nodes
shutdown, and read on startup. Cannot be used with `--no-persist-peers`. shutdown, and read on startup. Cannot be used with `--no-persist-peers`.

View File

@ -139,6 +139,11 @@ Networking:
Will fall back to a network-specific default if not specified. Will fall back to a network-specific default if not specified.
--dns-retries <DNS_RETRIES>
Amount of DNS resolution requests retries to perform when peering
[default: 0]
--peers-file <FILE> --peers-file <FILE>
The path to the known peers file. Connected peers are dumped to this file on nodes The path to the known peers file. Connected peers are dumped to this file on nodes
shutdown, and read on startup. Cannot be used with `--no-persist-peers`. shutdown, and read on startup. Cannot be used with `--no-persist-peers`.

View File

@ -10,6 +10,7 @@
pub mod ban_list; pub mod ban_list;
pub mod bandwidth_meter; pub mod bandwidth_meter;
/// Traits related to tokio streams /// Traits related to tokio streams
pub mod stream; pub mod stream;

View File

@ -14,7 +14,7 @@ use reth_dns_discovery::DnsDiscoveryConfig;
use reth_eth_wire::{HelloMessage, HelloMessageWithProtocols, Status}; use reth_eth_wire::{HelloMessage, HelloMessageWithProtocols, Status};
use reth_network_types::{pk2id, PeerId}; use reth_network_types::{pk2id, PeerId};
use reth_primitives::{ use reth_primitives::{
mainnet_nodes, sepolia_nodes, ChainSpec, ForkFilter, Head, NodeRecord, MAINNET, mainnet_nodes, sepolia_nodes, ChainSpec, ForkFilter, Head, TrustedPeer, MAINNET,
}; };
use reth_provider::{BlockReader, HeaderProvider}; use reth_provider::{BlockReader, HeaderProvider};
use reth_tasks::{TaskSpawner, TokioTaskExecutor}; use reth_tasks::{TaskSpawner, TokioTaskExecutor};
@ -41,7 +41,7 @@ pub struct NetworkConfig<C> {
/// The node's secret key, from which the node's identity is derived. /// The node's secret key, from which the node's identity is derived.
pub secret_key: SecretKey, pub secret_key: SecretKey,
/// All boot nodes to start network discovery with. /// All boot nodes to start network discovery with.
pub boot_nodes: HashSet<NodeRecord>, pub boot_nodes: HashSet<TrustedPeer>,
/// How to set up discovery over DNS. /// How to set up discovery over DNS.
pub dns_discovery_config: Option<DnsDiscoveryConfig>, pub dns_discovery_config: Option<DnsDiscoveryConfig>,
/// Address to use for discovery v4. /// Address to use for discovery v4.
@ -147,7 +147,7 @@ pub struct NetworkConfigBuilder {
#[serde(skip)] #[serde(skip)]
discovery_v5_builder: Option<reth_discv5::ConfigBuilder>, discovery_v5_builder: Option<reth_discv5::ConfigBuilder>,
/// All boot nodes to start network discovery with. /// All boot nodes to start network discovery with.
boot_nodes: HashSet<NodeRecord>, boot_nodes: HashSet<TrustedPeer>,
/// Address to use for discovery /// Address to use for discovery
discovery_addr: Option<SocketAddr>, discovery_addr: Option<SocketAddr>,
/// Listener for incoming connections /// Listener for incoming connections
@ -365,8 +365,8 @@ impl NetworkConfigBuilder {
} }
/// Sets the boot nodes. /// Sets the boot nodes.
pub fn boot_nodes(mut self, nodes: impl IntoIterator<Item = NodeRecord>) -> Self { pub fn boot_nodes<T: Into<TrustedPeer>>(mut self, nodes: impl IntoIterator<Item = T>) -> Self {
self.boot_nodes = nodes.into_iter().collect(); self.boot_nodes = nodes.into_iter().map(Into::into).collect();
self self
} }

View File

@ -206,9 +206,16 @@ where
})?; })?;
let listener_address = Arc::new(Mutex::new(incoming.local_address())); let listener_address = Arc::new(Mutex::new(incoming.local_address()));
// resolve boot nodes
let mut resolved_boot_nodes = vec![];
for record in &boot_nodes {
let resolved = record.resolve().await?;
resolved_boot_nodes.push(resolved);
}
discovery_v4_config = discovery_v4_config.map(|mut disc_config| { discovery_v4_config = discovery_v4_config.map(|mut disc_config| {
// merge configured boot nodes // merge configured boot nodes
disc_config.bootstrap_nodes.extend(boot_nodes.clone()); disc_config.bootstrap_nodes.extend(resolved_boot_nodes.clone());
disc_config.add_eip868_pair("eth", status.forkid); disc_config.add_eip868_pair("eth", status.forkid);
disc_config disc_config
}); });

View File

@ -25,6 +25,7 @@ secp256k1.workspace = true
serde_with.workspace = true serde_with.workspace = true
thiserror.workspace = true thiserror.workspace = true
url.workspace = true url.workspace = true
tokio = { workspace = true, features = ["full"] }
[dev-dependencies] [dev-dependencies]
alloy-primitives = { workspace = true, features = ["rand"] } alloy-primitives = { workspace = true, features = ["rand"] }

View File

@ -2,6 +2,43 @@
//! //!
//! This crate manages and converts Ethereum network entities such as node records, peer IDs, and //! This crate manages and converts Ethereum network entities such as node records, peer IDs, and
//! Ethereum Node Records (ENRs) //! Ethereum Node Records (ENRs)
//!
//! ## An overview of Node Record types
//!
//! Ethereum uses different types of "node records" to represent peers on the network.
//!
//! The simplest way to identify a peer is by public key. This is the [`PeerId`] type, which usually
//! represents a peer's secp256k1 public key.
//!
//! A more complete representation of a peer is the [`NodeRecord`] type, which includes the peer's
//! IP address, the ports where it is reachable (TCP and UDP), and the peer's public key. This is
//! what is returned from discovery v4 queries.
//!
//! The most comprehensive node record type is the Ethereum Node Record ([`Enr`]), which is a
//! signed, versioned record that includes the information from a [`NodeRecord`] along with
//! additional metadata. This is the data structure returned from discovery v5 queries.
//!
//! When we need to deserialize an identifier that could be any of these three types ([`PeerId`],
//! [`NodeRecord`], and [`Enr`]), we use the [`AnyNode`] type, which is an enum over the three
//! types. [`AnyNode`] is used in reth's `admin_addTrustedPeer` RPC method.
//!
//! The __final__ type is the [`TrustedPeer`] type, which is similar to a [`NodeRecord`] but may
//! include a domain name instead of a direct IP address. It includes a `resolve` method, which can
//! be used to resolve the domain name, producing a [`NodeRecord`]. This is useful for adding
//! trusted peers at startup, whose IP address may not be static each time the node starts. This is
//! common in orchestrated environments like Kubernetes, where there is reliable service discovery,
//! but services do not necessarily have static IPs.
//!
//! In short, the types are as follows:
//! - [`PeerId`]: A simple public key identifier.
//! - [`NodeRecord`]: A more complete representation of a peer, including IP address and ports.
//! - [`Enr`]: An Ethereum Node Record, which is a signed, versioned record that includes additional
//! metadata. Useful when interacting with discovery v5, or when custom metadata is required.
//! - [`AnyNode`]: An enum over [`PeerId`], [`NodeRecord`], and [`Enr`], useful in deserialization
//! when the type of the node record is not known.
//! - [`TrustedPeer`]: A [`NodeRecord`] with an optional domain name, which can be resolved to a
//! [`NodeRecord`]. Useful for adding trusted peers at startup, whose IP address may not be
//! static.
#![doc( #![doc(
html_logo_url = "https://raw.githubusercontent.com/paradigmxyz/reth/main/assets/reth-docs.png", html_logo_url = "https://raw.githubusercontent.com/paradigmxyz/reth/main/assets/reth-docs.png",
@ -24,6 +61,9 @@ pub type PeerId = B512;
pub mod node_record; pub mod node_record;
pub use node_record::{NodeRecord, NodeRecordParseError}; pub use node_record::{NodeRecord, NodeRecordParseError};
pub mod trusted_peer;
pub use trusted_peer::TrustedPeer;
/// This tag should be set to indicate to libsecp256k1 that the following bytes denote an /// This tag should be set to indicate to libsecp256k1 that the following bytes denote an
/// uncompressed pubkey. /// uncompressed pubkey.
/// ///

View File

@ -0,0 +1,300 @@
//! `NodeRecord` type that uses a domain instead of an IP.
use std::{
fmt::{self, Write},
io::Error,
net::IpAddr,
num::ParseIntError,
str::FromStr,
};
use crate::{NodeRecord, PeerId};
use secp256k1::{SecretKey, SECP256K1};
use serde_with::{DeserializeFromStr, SerializeDisplay};
use url::Host;
/// Represents the node record of a trusted peer. The only difference between this and a
/// [`NodeRecord`] is that this does not contain the IP address of the peer, but rather a domain
/// __or__ IP address.
///
/// This is useful when specifying nodes which are in internal infrastructure and may only be
/// discoverable reliably using DNS.
///
/// This should NOT be used for any use case other than in trusted peer lists.
#[derive(Clone, Debug, Eq, PartialEq, Hash, SerializeDisplay, DeserializeFromStr)]
pub struct TrustedPeer {
/// The host of a node.
pub host: Host,
/// TCP port of the port that accepts connections.
pub tcp_port: u16,
/// UDP discovery port.
pub udp_port: u16,
/// Public key of the discovery service
pub id: PeerId,
}
impl TrustedPeer {
/// Derive the [`NodeRecord`] from the secret key and addr
pub fn from_secret_key(host: Host, port: u16, sk: &SecretKey) -> Self {
let pk = secp256k1::PublicKey::from_secret_key(SECP256K1, sk);
let id = PeerId::from_slice(&pk.serialize_uncompressed()[1..]);
Self::new(host, port, id)
}
/// Creates a new record from a socket addr and peer id.
pub const fn new(host: Host, port: u16, id: PeerId) -> Self {
Self { host, tcp_port: port, udp_port: port, id }
}
/// Resolves the host in a [`TrustedPeer`] to an IP address, returning a [`NodeRecord`].
pub async fn resolve(&self) -> Result<NodeRecord, Error> {
let domain = match self.host.to_owned() {
Host::Ipv4(ip) => {
let id = self.id;
let tcp_port = self.tcp_port;
let udp_port = self.udp_port;
return Ok(NodeRecord { address: ip.into(), id, tcp_port, udp_port })
}
Host::Ipv6(ip) => {
let id = self.id;
let tcp_port = self.tcp_port;
let udp_port = self.udp_port;
return Ok(NodeRecord { address: ip.into(), id, tcp_port, udp_port })
}
Host::Domain(domain) => domain,
};
// Resolve the domain to an IP address
let mut ips = tokio::net::lookup_host(format!("{domain}:0")).await?;
let ip = ips
.next()
.ok_or_else(|| Error::new(std::io::ErrorKind::AddrNotAvailable, "No IP found"))?;
Ok(NodeRecord {
address: ip.ip(),
id: self.id,
tcp_port: self.tcp_port,
udp_port: self.udp_port,
})
}
}
impl fmt::Display for TrustedPeer {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str("enode://")?;
alloy_primitives::hex::encode(self.id.as_slice()).fmt(f)?;
f.write_char('@')?;
self.host.fmt(f)?;
f.write_char(':')?;
self.tcp_port.fmt(f)?;
if self.tcp_port != self.udp_port {
f.write_str("?discport=")?;
self.udp_port.fmt(f)?;
}
Ok(())
}
}
/// Possible error types when parsing a [`NodeRecord`]
#[derive(Debug, thiserror::Error)]
pub enum NodeRecordParseError {
/// Invalid url
#[error("Failed to parse url: {0}")]
InvalidUrl(String),
/// Invalid id
#[error("Failed to parse id")]
InvalidId(String),
/// Invalid discport
#[error("Failed to discport query: {0}")]
Discport(ParseIntError),
}
impl FromStr for TrustedPeer {
type Err = NodeRecordParseError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
use url::Url;
// Parse the URL with enode prefix replaced with http.
// The enode prefix causes the parser to use parse_opaque() on
// the host str which only handles domains and ipv6, not ipv4.
let url = Url::parse(s.replace("enode://", "http://").as_str())
.map_err(|e| NodeRecordParseError::InvalidUrl(e.to_string()))?;
let host = url
.host()
.ok_or_else(|| NodeRecordParseError::InvalidUrl("no host specified".to_string()))?
.to_owned();
let port = url
.port()
.ok_or_else(|| NodeRecordParseError::InvalidUrl("no port specified".to_string()))?;
let udp_port = if let Some(discovery_port) = url
.query_pairs()
.find_map(|(maybe_disc, port)| (maybe_disc.as_ref() == "discport").then_some(port))
{
discovery_port.parse::<u16>().map_err(NodeRecordParseError::Discport)?
} else {
port
};
let id = url
.username()
.parse::<PeerId>()
.map_err(|e| NodeRecordParseError::InvalidId(e.to_string()))?;
Ok(Self { host, id, tcp_port: port, udp_port })
}
}
impl From<NodeRecord> for TrustedPeer {
fn from(record: NodeRecord) -> Self {
let host = match record.address {
IpAddr::V4(ip) => Host::Ipv4(ip),
IpAddr::V6(ip) => Host::Ipv6(ip),
};
Self { host, tcp_port: record.tcp_port, udp_port: record.udp_port, id: record.id }
}
}
#[cfg(test)]
mod tests {
use super::*;
use std::net::Ipv6Addr;
#[test]
fn test_url_parse() {
let url = "enode://6f8a80d14311c39f35f516fa664deaaaa13e85b2f7493f37f6144d86991ec012937307647bd3b9a82abe2974e1407241d54947bbb39763a4cac9f77166ad92a0@10.3.58.6:30303?discport=30301";
let node: TrustedPeer = url.parse().unwrap();
assert_eq!(node, TrustedPeer {
host: Host::Ipv4([10,3,58,6].into()),
tcp_port: 30303,
udp_port: 30301,
id: "6f8a80d14311c39f35f516fa664deaaaa13e85b2f7493f37f6144d86991ec012937307647bd3b9a82abe2974e1407241d54947bbb39763a4cac9f77166ad92a0".parse().unwrap(),
})
}
#[test]
fn test_node_display() {
let url = "enode://6f8a80d14311c39f35f516fa664deaaaa13e85b2f7493f37f6144d86991ec012937307647bd3b9a82abe2974e1407241d54947bbb39763a4cac9f77166ad92a0@10.3.58.6:30303";
let node: TrustedPeer = url.parse().unwrap();
assert_eq!(url, &format!("{node}"));
}
#[test]
fn test_node_display_discport() {
let url = "enode://6f8a80d14311c39f35f516fa664deaaaa13e85b2f7493f37f6144d86991ec012937307647bd3b9a82abe2974e1407241d54947bbb39763a4cac9f77166ad92a0@10.3.58.6:30303?discport=30301";
let node: TrustedPeer = url.parse().unwrap();
assert_eq!(url, &format!("{node}"));
}
#[test]
fn test_node_serialize() {
let cases = vec![
// IPv4
(
TrustedPeer {
host: Host::Ipv4([10, 3, 58, 6].into()),
tcp_port: 30303u16,
udp_port: 30301u16,
id: PeerId::from_str("6f8a80d14311c39f35f516fa664deaaaa13e85b2f7493f37f6144d86991ec012937307647bd3b9a82abe2974e1407241d54947bbb39763a4cac9f77166ad92a0").unwrap(),
},
"\"enode://6f8a80d14311c39f35f516fa664deaaaa13e85b2f7493f37f6144d86991ec012937307647bd3b9a82abe2974e1407241d54947bbb39763a4cac9f77166ad92a0@10.3.58.6:30303?discport=30301\""
),
// IPv6
(
TrustedPeer {
host: Host::Ipv6(Ipv6Addr::new(0x2001, 0xdb8, 0x3c4d, 0x15, 0x0, 0x0, 0xabcd, 0xef12)),
tcp_port: 52150u16,
udp_port: 52151u16,
id: PeerId::from_str("1dd9d65c4552b5eb43d5ad55a2ee3f56c6cbc1c64a5c8d659f51fcd51bace24351232b8d7821617d2b29b54b81cdefb9b3e9c37d7fd5f63270bcc9e1a6f6a439").unwrap(),
},
"\"enode://1dd9d65c4552b5eb43d5ad55a2ee3f56c6cbc1c64a5c8d659f51fcd51bace24351232b8d7821617d2b29b54b81cdefb9b3e9c37d7fd5f63270bcc9e1a6f6a439@[2001:db8:3c4d:15::abcd:ef12]:52150?discport=52151\""
),
// URL
(
TrustedPeer {
host: Host::Domain("my-domain".to_string()),
tcp_port: 52150u16,
udp_port: 52151u16,
id: PeerId::from_str("1dd9d65c4552b5eb43d5ad55a2ee3f56c6cbc1c64a5c8d659f51fcd51bace24351232b8d7821617d2b29b54b81cdefb9b3e9c37d7fd5f63270bcc9e1a6f6a439").unwrap(),
},
"\"enode://1dd9d65c4552b5eb43d5ad55a2ee3f56c6cbc1c64a5c8d659f51fcd51bace24351232b8d7821617d2b29b54b81cdefb9b3e9c37d7fd5f63270bcc9e1a6f6a439@my-domain:52150?discport=52151\""
),
];
for (node, expected) in cases {
let ser = serde_json::to_string::<TrustedPeer>(&node).expect("couldn't serialize");
assert_eq!(ser, expected);
}
}
#[test]
fn test_node_deserialize() {
let cases = vec![
// IPv4
(
"\"enode://6f8a80d14311c39f35f516fa664deaaaa13e85b2f7493f37f6144d86991ec012937307647bd3b9a82abe2974e1407241d54947bbb39763a4cac9f77166ad92a0@10.3.58.6:30303?discport=30301\"",
TrustedPeer {
host: Host::Ipv4([10, 3, 58, 6].into()),
tcp_port: 30303u16,
udp_port: 30301u16,
id: PeerId::from_str("6f8a80d14311c39f35f516fa664deaaaa13e85b2f7493f37f6144d86991ec012937307647bd3b9a82abe2974e1407241d54947bbb39763a4cac9f77166ad92a0").unwrap(),
}
),
// IPv6
(
"\"enode://1dd9d65c4552b5eb43d5ad55a2ee3f56c6cbc1c64a5c8d659f51fcd51bace24351232b8d7821617d2b29b54b81cdefb9b3e9c37d7fd5f63270bcc9e1a6f6a439@[2001:db8:3c4d:15::abcd:ef12]:52150?discport=52151\"",
TrustedPeer {
host: Host::Ipv6(Ipv6Addr::new(0x2001, 0xdb8, 0x3c4d, 0x15, 0x0, 0x0, 0xabcd, 0xef12)),
tcp_port: 52150u16,
udp_port: 52151u16,
id: PeerId::from_str("1dd9d65c4552b5eb43d5ad55a2ee3f56c6cbc1c64a5c8d659f51fcd51bace24351232b8d7821617d2b29b54b81cdefb9b3e9c37d7fd5f63270bcc9e1a6f6a439").unwrap(),
}
),
// URL
(
"\"enode://1dd9d65c4552b5eb43d5ad55a2ee3f56c6cbc1c64a5c8d659f51fcd51bace24351232b8d7821617d2b29b54b81cdefb9b3e9c37d7fd5f63270bcc9e1a6f6a439@my-domain:52150?discport=52151\"",
TrustedPeer {
host: Host::Domain("my-domain".to_string()),
tcp_port: 52150u16,
udp_port: 52151u16,
id: PeerId::from_str("1dd9d65c4552b5eb43d5ad55a2ee3f56c6cbc1c64a5c8d659f51fcd51bace24351232b8d7821617d2b29b54b81cdefb9b3e9c37d7fd5f63270bcc9e1a6f6a439").unwrap(),
}
),
];
for (url, expected) in cases {
let node: TrustedPeer = serde_json::from_str(url).expect("couldn't deserialize");
assert_eq!(node, expected);
}
}
#[tokio::test]
async fn test_resolve_dns_node_record() {
// Set up tests
let tests = vec![("localhost")];
// Run tests
for domain in tests {
// Construct record
let rec =
TrustedPeer::new(url::Host::Domain(domain.to_owned()), 30300, PeerId::random());
// Resolve domain and validate
let rec = rec.resolve().await.unwrap();
match rec.address {
std::net::IpAddr::V4(addr) => {
assert_eq!(addr, std::net::Ipv4Addr::new(127, 0, 0, 1))
}
std::net::IpAddr::V6(addr) => {
assert_eq!(addr, std::net::Ipv6Addr::new(0, 0, 0, 0, 0, 0, 0, 1))
}
}
}
}
}

View File

@ -96,6 +96,7 @@ procfs = "0.16.0"
[dev-dependencies] [dev-dependencies]
# test vectors generation # test vectors generation
proptest.workspace = true proptest.workspace = true
reth-network-types.workspace = true
[features] [features]
optimism = [ optimism = [

View File

@ -17,7 +17,7 @@ use reth_network::{
}, },
HelloMessageWithProtocols, NetworkConfigBuilder, SessionsConfig, HelloMessageWithProtocols, NetworkConfigBuilder, SessionsConfig,
}; };
use reth_primitives::{mainnet_nodes, ChainSpec, NodeRecord}; use reth_primitives::{mainnet_nodes, ChainSpec, TrustedPeer};
use secp256k1::SecretKey; use secp256k1::SecretKey;
use std::{ use std::{
net::{IpAddr, Ipv4Addr, Ipv6Addr, SocketAddr}, net::{IpAddr, Ipv4Addr, Ipv6Addr, SocketAddr},
@ -39,7 +39,7 @@ pub struct NetworkArgs {
/// ///
/// --trusted-peers enode://abcd@192.168.0.1:30303 /// --trusted-peers enode://abcd@192.168.0.1:30303
#[arg(long, value_delimiter = ',')] #[arg(long, value_delimiter = ',')]
pub trusted_peers: Vec<NodeRecord>, pub trusted_peers: Vec<TrustedPeer>,
/// Connect to or accept from trusted peers only /// Connect to or accept from trusted peers only
#[arg(long)] #[arg(long)]
@ -49,7 +49,11 @@ pub struct NetworkArgs {
/// ///
/// Will fall back to a network-specific default if not specified. /// Will fall back to a network-specific default if not specified.
#[arg(long, value_delimiter = ',')] #[arg(long, value_delimiter = ',')]
pub bootnodes: Option<Vec<NodeRecord>>, pub bootnodes: Option<Vec<TrustedPeer>>,
/// Amount of DNS resolution requests retries to perform when peering.
#[arg(long, default_value_t = 0)]
pub dns_retries: usize,
/// The path to the known peers file. Connected peers are dumped to this file on nodes /// The path to the known peers file. Connected peers are dumped to this file on nodes
/// shutdown, and read on startup. Cannot be used with `--no-persist-peers`. /// shutdown, and read on startup. Cannot be used with `--no-persist-peers`.
@ -125,10 +129,7 @@ impl NetworkArgs {
secret_key: SecretKey, secret_key: SecretKey,
default_peers_file: PathBuf, default_peers_file: PathBuf,
) -> NetworkConfigBuilder { ) -> NetworkConfigBuilder {
let boot_nodes = self let chain_bootnodes = chain_spec.bootnodes().unwrap_or_else(mainnet_nodes);
.bootnodes
.clone()
.unwrap_or_else(|| chain_spec.bootnodes().unwrap_or_else(mainnet_nodes));
let peers_file = self.peers_file.clone().unwrap_or(default_peers_file); let peers_file = self.peers_file.clone().unwrap_or(default_peers_file);
// Configure peer connections // Configure peer connections
@ -156,7 +157,7 @@ impl NetworkArgs {
SessionsConfig::default().with_upscaled_event_buffer(peers_config.max_peers()), SessionsConfig::default().with_upscaled_event_buffer(peers_config.max_peers()),
) )
.peer_config(peers_config) .peer_config(peers_config)
.boot_nodes(boot_nodes.clone()) .boot_nodes(chain_bootnodes.clone())
.chain_spec(chain_spec.clone()) .chain_spec(chain_spec.clone())
.transactions_manager_config(transactions_manager_config) .transactions_manager_config(transactions_manager_config)
// Configure node identity // Configure node identity
@ -189,7 +190,7 @@ impl NetworkArgs {
} = self.discovery; } = self.discovery;
builder builder
.add_unsigned_boot_nodes(boot_nodes.into_iter()) .add_unsigned_boot_nodes(chain_bootnodes.into_iter())
.lookup_interval(discv5_lookup_interval) .lookup_interval(discv5_lookup_interval)
.bootstrap_lookup_interval(discv5_bootstrap_lookup_interval) .bootstrap_lookup_interval(discv5_bootstrap_lookup_interval)
.bootstrap_lookup_countdown(discv5_bootstrap_lookup_countdown) .bootstrap_lookup_countdown(discv5_bootstrap_lookup_countdown)
@ -224,6 +225,7 @@ impl Default for NetworkArgs {
trusted_peers: vec![], trusted_peers: vec![],
trusted_only: false, trusted_only: false,
bootnodes: None, bootnodes: None,
dns_retries: 0,
peers_file: None, peers_file: None,
identity: P2P_CLIENT_VERSION.to_string(), identity: P2P_CLIENT_VERSION.to_string(),
p2p_secret_key: None, p2p_secret_key: None,
@ -416,6 +418,22 @@ mod tests {
); );
} }
#[test]
fn parse_retry_strategy_args() {
let tests = vec![0, 10];
for retries in tests {
let args = CommandParser::<NetworkArgs>::parse_from([
"reth",
"--dns-retries",
retries.to_string().as_str(),
])
.args;
assert_eq!(args.dns_retries, retries);
}
}
#[cfg(not(feature = "optimism"))] #[cfg(not(feature = "optimism"))]
#[test] #[test]
fn network_args_default_sanity_test() { fn network_args_default_sanity_test() {

View File

@ -58,6 +58,7 @@ eyre.workspace = true
fdlimit.workspace = true fdlimit.workspace = true
confy.workspace = true confy.workspace = true
rayon.workspace = true rayon.workspace = true
backon.workspace = true
[dev-dependencies] [dev-dependencies]
tempfile.workspace = true tempfile.workspace = true

View File

@ -1,5 +1,6 @@
//! Helper types that can be used by launchers. //! Helper types that can be used by launchers.
use backon::{ConstantBuilder, Retryable};
use eyre::Context; use eyre::Context;
use rayon::ThreadPoolBuilder; use rayon::ThreadPoolBuilder;
use reth_auto_seal_consensus::MiningMode; use reth_auto_seal_consensus::MiningMode;
@ -56,17 +57,19 @@ impl LaunchContext {
/// `config`. /// `config`.
/// ///
/// Attaches both the `NodeConfig` and the loaded `reth.toml` config to the launch context. /// Attaches both the `NodeConfig` and the loaded `reth.toml` config to the launch context.
pub fn with_loaded_toml_config( pub async fn with_loaded_toml_config(
self, self,
config: NodeConfig, config: NodeConfig,
) -> eyre::Result<LaunchContextWith<WithConfigs>> { ) -> eyre::Result<LaunchContextWith<WithConfigs>> {
let toml_config = self.load_toml_config(&config)?; let toml_config = self.load_toml_config(&config).await?;
Ok(self.with(WithConfigs { config, toml_config })) Ok(self.with(WithConfigs { config, toml_config }))
} }
/// Loads the reth config with the configured `data_dir` and overrides settings according to the /// Loads the reth config with the configured `data_dir` and overrides settings according to the
/// `config`. /// `config`.
pub fn load_toml_config(&self, config: &NodeConfig) -> eyre::Result<reth_config::Config> { ///
/// This is async because the trusted peers may have to be resolved.
pub async fn load_toml_config(&self, config: &NodeConfig) -> eyre::Result<reth_config::Config> {
let config_path = config.config.clone().unwrap_or_else(|| self.data_dir.config()); let config_path = config.config.clone().unwrap_or_else(|| self.data_dir.config());
let mut toml_config = confy::load_path::<reth_config::Config>(&config_path) let mut toml_config = confy::load_path::<reth_config::Config>(&config_path)
@ -81,9 +84,16 @@ impl LaunchContext {
if !config.network.trusted_peers.is_empty() { if !config.network.trusted_peers.is_empty() {
info!(target: "reth::cli", "Adding trusted nodes"); info!(target: "reth::cli", "Adding trusted nodes");
config.network.trusted_peers.iter().for_each(|peer| {
toml_config.peers.trusted_nodes.insert(*peer); // resolve trusted peers if they use a domain instead of dns
}); for peer in &config.network.trusted_peers {
let backoff = ConstantBuilder::default().with_max_times(config.network.dns_retries);
let resolved = (move || { peer.resolve() })
.retry(&backoff)
.notify(|err, _| warn!(target: "reth::cli", "Error resolving peer domain: {err}. Retrying..."))
.await?;
toml_config.peers.trusted_nodes.insert(resolved);
}
} }
Ok(toml_config) Ok(toml_config)

View File

@ -94,7 +94,7 @@ where
let ctx = ctx let ctx = ctx
.with_configured_globals() .with_configured_globals()
// load the toml config // load the toml config
.with_loaded_toml_config(config)? .with_loaded_toml_config(config).await?
// attach the database // attach the database
.attach(database.clone()) .attach(database.clone())
// ensure certain settings take effect // ensure certain settings take effect

View File

@ -73,7 +73,7 @@ pub use integer_list::IntegerList;
pub use log::{logs_bloom, Log}; pub use log::{logs_bloom, Log};
pub use net::{ pub use net::{
goerli_nodes, holesky_nodes, mainnet_nodes, parse_nodes, sepolia_nodes, NodeRecord, goerli_nodes, holesky_nodes, mainnet_nodes, parse_nodes, sepolia_nodes, NodeRecord,
NodeRecordParseError, GOERLI_BOOTNODES, HOLESKY_BOOTNODES, MAINNET_BOOTNODES, NodeRecordParseError, TrustedPeer, GOERLI_BOOTNODES, HOLESKY_BOOTNODES, MAINNET_BOOTNODES,
SEPOLIA_BOOTNODES, SEPOLIA_BOOTNODES,
}; };
pub use prune::{ pub use prune::{

View File

@ -1,4 +1,4 @@
pub use reth_network_types::{NodeRecord, NodeRecordParseError}; pub use reth_network_types::{NodeRecord, NodeRecordParseError, TrustedPeer};
// Ethereum bootnodes come from <https://github.com/ledgerwatch/erigon/blob/devel/params/bootnodes.go> // Ethereum bootnodes come from <https://github.com/ledgerwatch/erigon/blob/devel/params/bootnodes.go>
// OP bootnodes come from <https://github.com/ethereum-optimism/op-geth/blob/optimism/params/bootnodes.go> // OP bootnodes come from <https://github.com/ethereum-optimism/op-geth/blob/optimism/params/bootnodes.go>