diff --git a/Cargo.lock b/Cargo.lock index c31c36592e..18eba9dba7 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -7127,6 +7127,7 @@ dependencies = [ "reth-db-api", "reth-db-common", "reth-discv4", + "reth-discv5", "reth-downloaders", "reth-ecies", "reth-eth-wire", @@ -7135,6 +7136,7 @@ dependencies = [ "reth-evm", "reth-exex", "reth-fs-util", + "reth-net-nat", "reth-network", "reth-network-p2p", "reth-network-peers", @@ -7158,6 +7160,7 @@ dependencies = [ "serde", "serde_json", "tokio", + "tokio-stream", "toml", "tracing", ] diff --git a/crates/cli/commands/Cargo.toml b/crates/cli/commands/Cargo.toml index 67a28c741a..e1cb010d43 100644 --- a/crates/cli/commands/Cargo.toml +++ b/crates/cli/commands/Cargo.toml @@ -27,6 +27,7 @@ reth-eth-wire.workspace = true reth-evm.workspace = true reth-exex.workspace = true reth-fs-util.workspace = true +reth-net-nat.workspace = true reth-network = { workspace = true, features = ["serde"] } reth-network-p2p.workspace = true reth-network-peers = { workspace = true, features = ["secp256k1"] } @@ -47,6 +48,8 @@ reth-trie = { workspace = true, features = ["metrics"] } reth-trie-db = { workspace = true, features = ["metrics"] } reth-trie-common = { workspace = true, optional = true } reth-primitives-traits.workspace = true +reth-discv4.workspace = true +reth-discv5.workspace = true # ethereum alloy-eips.workspace = true @@ -68,6 +71,7 @@ serde_json.workspace = true tracing.workspace = true backon.workspace = true secp256k1 = { workspace = true, features = ["global-context", "std", "recovery"] } +tokio-stream.workspace = true # io fdlimit.workspace = true @@ -84,7 +88,6 @@ arbitrary = { workspace = true, optional = true } proptest-arbitrary-interop = { workspace = true, optional = true } [dev-dependencies] -reth-discv4.workspace = true reth-ethereum-cli.workspace = true [features] diff --git a/crates/cli/commands/src/p2p/bootnode.rs b/crates/cli/commands/src/p2p/bootnode.rs new file mode 100644 index 0000000000..9be60aca65 --- /dev/null +++ b/crates/cli/commands/src/p2p/bootnode.rs @@ -0,0 +1,107 @@ +//! Standalone bootnode command + +use clap::Parser; +use reth_discv4::{DiscoveryUpdate, Discv4, Discv4Config}; +use reth_discv5::{discv5::Event, Config, Discv5}; +use reth_net_nat::NatResolver; +use reth_network_peers::NodeRecord; +use std::{net::SocketAddr, str::FromStr}; +use tokio::select; +use tokio_stream::StreamExt; +use tracing::info; + +/// Satrt a discovery only bootnode. +#[derive(Parser, Debug)] +pub struct Command { + /// Listen address for the bootnode (default: ":30301"). + #[arg(long, default_value = ":30301")] + pub addr: String, + + /// Generate a new node key and save it to the specified file. + #[arg(long, default_value = "")] + pub gen_key: String, + + /// Private key filename for the node. + #[arg(long, default_value = "")] + pub node_key: String, + + /// NAT resolution method (any|none|upnp|publicip|extip:\) + #[arg(long, default_value = "any")] + pub nat: NatResolver, + + /// Run a v5 topic discovery bootnode. + #[arg(long)] + pub v5: bool, +} + +impl Command { + /// Execute the bootnode command. + pub async fn execute(self) -> eyre::Result<()> { + info!("Bootnode started with config: {:?}", self); + let sk = reth_network::config::rng_secret_key(); + let socket_addr = SocketAddr::from_str(&self.addr)?; + let local_enr = NodeRecord::from_secret_key(socket_addr, &sk); + + let config = Discv4Config::builder().external_ip_resolver(Some(self.nat)).build(); + + let (_discv4, mut discv4_service) = + Discv4::bind(socket_addr, local_enr, sk, config).await?; + + info!("Started discv4 at address:{:?}", socket_addr); + + let mut discv4_updates = discv4_service.update_stream(); + discv4_service.spawn(); + + // Optional discv5 update event listener if v5 is enabled + let mut discv5_updates = None; + + if self.v5 { + info!("Starting discv5"); + let config = Config::builder(socket_addr).build(); + let (_discv5, updates, _local_enr_discv5) = Discv5::start(&sk, config).await?; + discv5_updates = Some(updates); + }; + + // event info loop for logging + loop { + select! { + //discv4 updates + update = discv4_updates.next() => { + if let Some(update) = update { + match update { + DiscoveryUpdate::Added(record) => { + info!("(Discv4) new peer added, peer_id={:?}", record.id); + } + DiscoveryUpdate::Removed(peer_id) => { + info!("(Discv4) peer with peer-id={:?} removed", peer_id); + } + _ => {} + } + } else { + info!("(Discv4) update stream ended."); + break; + } + } + //if discv5, discv5 update stream, else do nothing + update = async { + if let Some(updates) = &mut discv5_updates { + updates.recv().await + } else { + futures::future::pending().await + } + } => { + if let Some(update) = update { + if let Event::SessionEstablished(enr, _) = update { + info!("(Discv5) new peer added, peer_id={:?}", enr.id()); + } + } else { + info!("(Discv5) update stream ended."); + break; + } + } + } + } + + Ok(()) + } +} diff --git a/crates/cli/commands/src/p2p/mod.rs b/crates/cli/commands/src/p2p/mod.rs index 41a9b7b65a..bb4c708d92 100644 --- a/crates/cli/commands/src/p2p/mod.rs +++ b/crates/cli/commands/src/p2p/mod.rs @@ -16,6 +16,7 @@ use reth_node_core::{ utils::get_single_header, }; +pub mod bootnode; mod rlpx; /// `reth p2p` command