diff --git a/Cargo.lock b/Cargo.lock index 1f8160c450f5..d0332a27e5dc 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -6399,6 +6399,7 @@ dependencies = [ "reth-exex", "reth-fs-util", "reth-interfaces", + "reth-net-common", "reth-network", "reth-network-api", "reth-nippy-jar", @@ -6702,6 +6703,7 @@ dependencies = [ "thiserror", "tokio", "tracing", + "url", ] [[package]] @@ -7131,6 +7133,7 @@ dependencies = [ "alloy-primitives", "pin-project", "tokio", + "url", ] [[package]] @@ -7289,6 +7292,7 @@ dependencies = [ "reth-evm", "reth-exex", "reth-interfaces", + "reth-net-common", "reth-network", "reth-node-api", "reth-node-core", diff --git a/bin/reth/Cargo.toml b/bin/reth/Cargo.toml index b95140aadfa4..0fc9c50c3e62 100644 --- a/bin/reth/Cargo.toml +++ b/bin/reth/Cargo.toml @@ -35,6 +35,7 @@ reth-rpc-types.workspace = true reth-rpc-types-compat.workspace = true reth-rpc-api = { workspace = true, features = ["client"] } reth-network = { workspace = true, features = ["serde"] } +reth-net-common.workspace = true reth-network-api.workspace = true reth-downloaders.workspace = true reth-tracing.workspace = true diff --git a/bin/reth/src/commands/stage/run.rs b/bin/reth/src/commands/stage/run.rs index d34b67db42a1..4da61c1dc651 100644 --- a/bin/reth/src/commands/stage/run.rs +++ b/bin/reth/src/commands/stage/run.rs @@ -23,6 +23,7 @@ use reth_config::{ use reth_db::init_db; use reth_downloaders::bodies::bodies::BodiesDownloaderBuilder; use reth_exex::ExExManagerHandle; +use reth_net_common::dns_node_record_resolve::resolve_dns_node_record; use reth_primitives::ChainSpec; use reth_provider::{ ProviderFactory, StageCheckpointReader, StageCheckpointWriter, StaticFileProviderFactory, @@ -178,9 +179,10 @@ impl Command { let mut config = config; config.peers.trusted_nodes_only = self.network.trusted_only; if !self.network.trusted_peers.is_empty() { - self.network.trusted_peers.iter().for_each(|peer| { - config.peers.trusted_nodes.insert(*peer); - }); + for peer in &self.network.trusted_peers { + let peer = resolve_dns_node_record(peer.clone()).await?; + config.peers.trusted_nodes.insert(peer); + } } let network_secret_path = self diff --git a/crates/net/common/Cargo.toml b/crates/net/common/Cargo.toml index 3d73f480f570..d95cc6952239 100644 --- a/crates/net/common/Cargo.toml +++ b/crates/net/common/Cargo.toml @@ -17,4 +17,5 @@ alloy-primitives.workspace = true # async pin-project.workspace = true +url.workspace = true tokio = { workspace = true, features = ["full"] } diff --git a/crates/net/common/src/dns_node_record_resolve.rs b/crates/net/common/src/dns_node_record_resolve.rs new file mode 100644 index 000000000000..0d87397c3067 --- /dev/null +++ b/crates/net/common/src/dns_node_record_resolve.rs @@ -0,0 +1,41 @@ +//! Resolves a [DNSNodeRecord] to a [NodeRecord] by looking up the record in the DNS system. + +use reth_network_types::{DNSNodeRecord, NodeRecord}; +use std::io::Error; +use url::Host; + +/// Resolves the host in a [DNSNodeRecord] to an IP address, returning a [NodeRecord]. If the domain +/// cannot be resolved, an error is returned. +/// +/// If the host is already an IP address, the [NodeRecord] is returned immediately. +pub async fn resolve_dns_node_record(node_record: DNSNodeRecord) -> Result { + let domain = match node_record.host { + Host::Ipv4(ip) => { + let id = node_record.id; + let tcp_port = node_record.tcp_port; + let udp_port = node_record.udp_port; + + return Ok(NodeRecord { address: ip.into(), id, tcp_port, udp_port }) + } + Host::Ipv6(ip) => { + let id = node_record.id; + let tcp_port = node_record.tcp_port; + let udp_port = node_record.udp_port; + + return Ok(NodeRecord { address: ip.into(), id, tcp_port, udp_port }) + } + Host::Domain(domain) => domain, + }; + + let mut ips = tokio::net::lookup_host(domain).await?; + let ip = ips + .next() + .ok_or_else(|| Error::new(std::io::ErrorKind::AddrNotAvailable, "No IP found"))?; + + Ok(NodeRecord { + address: ip.ip(), + id: node_record.id, + tcp_port: node_record.tcp_port, + udp_port: node_record.udp_port, + }) +} diff --git a/crates/net/common/src/lib.rs b/crates/net/common/src/lib.rs index f9eed8344519..c7a44e58a9b3 100644 --- a/crates/net/common/src/lib.rs +++ b/crates/net/common/src/lib.rs @@ -10,7 +10,10 @@ pub mod ban_list; pub mod bandwidth_meter; + /// Traits related to tokio streams pub mod stream; +pub mod dns_node_record_resolve; + pub mod ratelimit; diff --git a/crates/net/discv5/Cargo.toml b/crates/net/discv5/Cargo.toml index a73888ae0ea2..968bdcb025a6 100644 --- a/crates/net/discv5/Cargo.toml +++ b/crates/net/discv5/Cargo.toml @@ -31,6 +31,7 @@ futures.workspace = true # io rand.workspace = true +url.workspace = true # misc derive_more.workspace = true diff --git a/crates/net/discv5/src/config.rs b/crates/net/discv5/src/config.rs index 5d4d2dfaded3..68f56b8a61b6 100644 --- a/crates/net/discv5/src/config.rs +++ b/crates/net/discv5/src/config.rs @@ -9,8 +9,9 @@ use std::{ use derive_more::Display; use discv5::ListenConfig; use multiaddr::{Multiaddr, Protocol}; -use reth_primitives::{Bytes, EnrForkIdEntry, ForkId, NodeRecord}; +use reth_primitives::{Bytes, DNSNodeRecord, EnrForkIdEntry, ForkId}; use tracing::warn; +use url::Host; use crate::{enr::discv4_id_to_multiaddr_id, filter::MustNotIncludeKeys, NetworkStackId}; @@ -129,9 +130,12 @@ impl ConfigBuilder { } /// Adds boot nodes in the form a list of [`NodeRecord`]s, parsed enodes. - pub fn add_unsigned_boot_nodes(mut self, enodes: impl Iterator) -> Self { + pub fn add_unsigned_boot_nodes>( + mut self, + enodes: impl Iterator, + ) -> Self { for node in enodes { - if let Ok(node) = BootNode::from_unsigned(node) { + if let Ok(node) = BootNode::from_unsigned(node.into()) { self.bootstrap_nodes.insert(node); } } @@ -425,14 +429,17 @@ pub enum BootNode { } impl BootNode { - /// Parses a [`NodeRecord`] and serializes according to CL format. Note: [`discv5`] is + /// Parses a [`DNSNodeRecord`] and serializes according to CL format. Note: [`discv5`] is /// originally a CL library hence needs this format to add the node. - pub fn from_unsigned(node_record: NodeRecord) -> Result { - let NodeRecord { address, udp_port, id, .. } = node_record; + pub fn from_unsigned(node_record: DNSNodeRecord) -> Result { + let DNSNodeRecord { host, udp_port, id, .. } = node_record; let mut multi_address = Multiaddr::empty(); - match address { - IpAddr::V4(ip) => multi_address.push(Protocol::Ip4(ip)), - IpAddr::V6(ip) => multi_address.push(Protocol::Ip6(ip)), + match host { + Host::Ipv4(ip) => multi_address.push(Protocol::Ip4(ip)), + Host::Ipv6(ip) => multi_address.push(Protocol::Ip6(ip)), + Host::Domain(domain) => { + multi_address.push(Protocol::Dns(domain.into())); + } } multi_address.push(Protocol::Udp(udp_port)); diff --git a/crates/net/network/src/config.rs b/crates/net/network/src/config.rs index 368f958b2a30..5a0839182f58 100644 --- a/crates/net/network/src/config.rs +++ b/crates/net/network/src/config.rs @@ -14,7 +14,7 @@ use reth_dns_discovery::DnsDiscoveryConfig; use reth_eth_wire::{HelloMessage, HelloMessageWithProtocols, Status}; use reth_network_types::{pk2id, PeerId}; use reth_primitives::{ - mainnet_nodes, sepolia_nodes, ChainSpec, ForkFilter, Head, NodeRecord, MAINNET, + mainnet_nodes, sepolia_nodes, ChainSpec, DNSNodeRecord, ForkFilter, Head, MAINNET, }; use reth_provider::{BlockReader, HeaderProvider}; use reth_tasks::{TaskSpawner, TokioTaskExecutor}; @@ -41,7 +41,7 @@ pub struct NetworkConfig { /// The node's secret key, from which the node's identity is derived. pub secret_key: SecretKey, /// All boot nodes to start network discovery with. - pub boot_nodes: HashSet, + pub boot_nodes: HashSet, /// How to set up discovery over DNS. pub dns_discovery_config: Option, /// Address to use for discovery v4. @@ -147,7 +147,7 @@ pub struct NetworkConfigBuilder { #[serde(skip)] discovery_v5_builder: Option, /// All boot nodes to start network discovery with. - boot_nodes: HashSet, + boot_nodes: HashSet, /// Address to use for discovery discovery_addr: Option, /// Listener for incoming connections @@ -356,8 +356,11 @@ impl NetworkConfigBuilder { } /// Sets the boot nodes. - pub fn boot_nodes(mut self, nodes: impl IntoIterator) -> Self { - self.boot_nodes = nodes.into_iter().collect(); + pub fn boot_nodes>( + mut self, + nodes: impl IntoIterator, + ) -> Self { + self.boot_nodes = nodes.into_iter().map(Into::into).collect(); self } diff --git a/crates/net/network/src/manager.rs b/crates/net/network/src/manager.rs index b6b1d4d1ecbc..907a6050e449 100644 --- a/crates/net/network/src/manager.rs +++ b/crates/net/network/src/manager.rs @@ -42,7 +42,9 @@ use reth_eth_wire::{ DisconnectReason, EthVersion, Status, }; use reth_metrics::common::mpsc::UnboundedMeteredSender; -use reth_net_common::bandwidth_meter::BandwidthMeter; +use reth_net_common::{ + bandwidth_meter::BandwidthMeter, dns_node_record_resolve::resolve_dns_node_record, +}; use reth_network_api::ReputationChangeKind; use reth_network_types::PeerId; use reth_primitives::{ForkId, NodeRecord}; @@ -206,9 +208,16 @@ where })?; let listener_address = Arc::new(Mutex::new(incoming.local_address())); + // resolve boot nodes + let mut resolved_boot_nodes = vec![]; + for record in boot_nodes.iter() { + let resolved = resolve_dns_node_record(record.clone()).await?; + resolved_boot_nodes.push(resolved); + } + discovery_v4_config = discovery_v4_config.map(|mut disc_config| { // 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 }); diff --git a/crates/net/types/src/dns_node_record.rs b/crates/net/types/src/dns_node_record.rs new file mode 100644 index 000000000000..b97cfa44fd2b --- /dev/null +++ b/crates/net/types/src/dns_node_record.rs @@ -0,0 +1,233 @@ +//! NodeRecord type that uses a domain instead of an IP. + +use std::{ + fmt::{self, Write}, + net::IpAddr, + num::ParseIntError, + str::FromStr, +}; + +use crate::{NodeRecord, PeerId}; +use secp256k1::{SecretKey, SECP256K1}; +use serde_with::{DeserializeFromStr, SerializeDisplay}; +use url::Host; + +/// Represents a ENR in discovery. +/// +/// Note: this is only an excerpt of the [`NodeRecord`] data structure. +#[derive(Clone, Debug, Eq, PartialEq, Hash, SerializeDisplay, DeserializeFromStr)] +pub struct DNSNodeRecord { + /// 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 DNSNodeRecord { + /// 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 fn new(host: Host, port: u16, id: PeerId) -> Self { + Self { host, tcp_port: port, udp_port: port, id } + } +} + +impl fmt::Display for DNSNodeRecord { + 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 DNSNodeRecord { + type Err = NodeRecordParseError; + + fn from_str(s: &str) -> Result { + use url::Url; + + let url = Url::parse(s).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::().map_err(NodeRecordParseError::Discport)? + } else { + port + }; + + let id = url + .username() + .parse::() + .map_err(|e| NodeRecordParseError::InvalidId(e.to_string()))?; + + Ok(Self { host, id, tcp_port: port, udp_port }) + } +} + +impl From for DNSNodeRecord { + 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: DNSNodeRecord = url.parse().unwrap(); + assert_eq!(node, DNSNodeRecord { + 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: DNSNodeRecord = 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: DNSNodeRecord = url.parse().unwrap(); + assert_eq!(url, &format!("{node}")); + } + + #[test] + fn test_node_serialize() { + let cases = vec![ + // IPv4 + ( + DNSNodeRecord { + 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 + ( + DNSNodeRecord { + 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 + ( + DNSNodeRecord { + 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::(&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\"", + DNSNodeRecord { + 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\"", + DNSNodeRecord { + 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\"", + DNSNodeRecord { + 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: DNSNodeRecord = serde_json::from_str(url).expect("couldn't deserialize"); + assert_eq!(node, expected); + } + } +} diff --git a/crates/net/types/src/lib.rs b/crates/net/types/src/lib.rs index e4b9f28a4fdc..d324530cac14 100644 --- a/crates/net/types/src/lib.rs +++ b/crates/net/types/src/lib.rs @@ -24,6 +24,9 @@ pub type PeerId = B512; pub mod node_record; pub use node_record::{NodeRecord, NodeRecordParseError}; +pub mod dns_node_record; +pub use dns_node_record::DNSNodeRecord; + /// This tag should be set to indicate to libsecp256k1 that the following bytes denote an /// uncompressed pubkey. /// diff --git a/crates/node-core/src/args/network.rs b/crates/node-core/src/args/network.rs index 115ec8517ab4..ad579da7bf2f 100644 --- a/crates/node-core/src/args/network.rs +++ b/crates/node-core/src/args/network.rs @@ -17,7 +17,7 @@ use reth_network::{ }, HelloMessageWithProtocols, NetworkConfigBuilder, SessionsConfig, }; -use reth_primitives::{mainnet_nodes, ChainSpec, NodeRecord}; +use reth_primitives::{mainnet_nodes, ChainSpec, DNSNodeRecord}; use secp256k1::SecretKey; use std::{ net::{IpAddr, Ipv4Addr, Ipv6Addr, SocketAddr}, @@ -38,7 +38,7 @@ pub struct NetworkArgs { /// /// --trusted-peers enode://abcd@192.168.0.1:30303 #[arg(long, value_delimiter = ',')] - pub trusted_peers: Vec, + pub trusted_peers: Vec, /// Connect to or accept from trusted peers only #[arg(long)] @@ -48,7 +48,7 @@ pub struct NetworkArgs { /// /// Will fall back to a network-specific default if not specified. #[arg(long, value_delimiter = ',')] - pub bootnodes: Option>, + pub bootnodes: Option>, /// 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`. @@ -124,10 +124,12 @@ impl NetworkArgs { secret_key: SecretKey, default_peers_file: PathBuf, ) -> NetworkConfigBuilder { - let boot_nodes = self - .bootnodes - .clone() - .unwrap_or_else(|| chain_spec.bootnodes().unwrap_or_else(mainnet_nodes)); + let chain_bootnodes = chain_spec + .bootnodes() + .unwrap_or_else(mainnet_nodes) + .into_iter() + .map(DNSNodeRecord::from) + .collect(); let peers_file = self.peers_file.clone().unwrap_or(default_peers_file); // Configure peer connections diff --git a/crates/node/builder/Cargo.toml b/crates/node/builder/Cargo.toml index e36ac2e2c396..3fe6b85840b9 100644 --- a/crates/node/builder/Cargo.toml +++ b/crates/node/builder/Cargo.toml @@ -26,6 +26,7 @@ reth-rpc-layer.workspace = true reth-node-api.workspace = true reth-node-core.workspace = true reth-network.workspace = true +reth-net-common.workspace = true reth-primitives.workspace = true reth-payload-builder.workspace = true reth-transaction-pool.workspace = true diff --git a/crates/node/builder/src/launch/common.rs b/crates/node/builder/src/launch/common.rs index 8a5d8e519005..c58b47637e03 100644 --- a/crates/node/builder/src/launch/common.rs +++ b/crates/node/builder/src/launch/common.rs @@ -10,6 +10,7 @@ use reth_auto_seal_consensus::MiningMode; use reth_config::{config::EtlConfig, PruneConfig}; use reth_db::{database::Database, database_metrics::DatabaseMetrics}; use reth_interfaces::p2p::headers::client::HeadersClient; +use reth_net_common::dns_node_record_resolve::resolve_dns_node_record; use reth_node_core::{ cli::config::RethRpcConfig, dirs::{ChainPath, DataDirPath}, @@ -50,17 +51,19 @@ impl LaunchContext { /// `config`. /// /// 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, config: NodeConfig, ) -> eyre::Result> { - let toml_config = self.load_toml_config(&config)?; + let toml_config = self.load_toml_config(&config).await?; Ok(self.with(WithConfigs { config, toml_config })) } /// Loads the reth config with the configured `data_dir` and overrides settings according to the /// `config`. - pub fn load_toml_config(&self, config: &NodeConfig) -> eyre::Result { + /// + /// This is async because the trusted peers may have to be resolved. + pub async fn load_toml_config(&self, config: &NodeConfig) -> eyre::Result { let config_path = config.config.clone().unwrap_or_else(|| self.data_dir.config()); let mut toml_config = confy::load_path::(&config_path) @@ -75,9 +78,14 @@ impl LaunchContext { if !config.network.trusted_peers.is_empty() { 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 resolved = resolve_dns_node_record(peer.clone()) + .await + .wrap_err_with(|| format!("Could not resolve trusted peer {peer}"))?; + toml_config.peers.trusted_nodes.insert(resolved); + } } Ok(toml_config) diff --git a/crates/node/builder/src/launch/mod.rs b/crates/node/builder/src/launch/mod.rs index 4987586bc9f3..320ac7624982 100644 --- a/crates/node/builder/src/launch/mod.rs +++ b/crates/node/builder/src/launch/mod.rs @@ -92,7 +92,7 @@ where let ctx = ctx .with_configured_globals() // load the toml config - .with_loaded_toml_config(config)? + .with_loaded_toml_config(config).await? // attach the database .attach(database.clone()) // ensure certain settings take effect diff --git a/crates/primitives/src/lib.rs b/crates/primitives/src/lib.rs index b10582cf9e52..cf9f815c0664 100644 --- a/crates/primitives/src/lib.rs +++ b/crates/primitives/src/lib.rs @@ -73,8 +73,8 @@ pub use header::{Header, HeaderValidationError, HeadersDirection, SealedHeader}; pub use integer_list::IntegerList; pub use log::{logs_bloom, Log}; pub use net::{ - goerli_nodes, holesky_nodes, mainnet_nodes, parse_nodes, sepolia_nodes, NodeRecord, - NodeRecordParseError, GOERLI_BOOTNODES, HOLESKY_BOOTNODES, MAINNET_BOOTNODES, + goerli_nodes, holesky_nodes, mainnet_nodes, parse_nodes, sepolia_nodes, DNSNodeRecord, + NodeRecord, NodeRecordParseError, GOERLI_BOOTNODES, HOLESKY_BOOTNODES, MAINNET_BOOTNODES, SEPOLIA_BOOTNODES, }; pub use prune::{ diff --git a/crates/primitives/src/net.rs b/crates/primitives/src/net.rs index dcb10545f7f2..f96c2862239f 100644 --- a/crates/primitives/src/net.rs +++ b/crates/primitives/src/net.rs @@ -1,4 +1,4 @@ -pub use reth_network_types::{NodeRecord, NodeRecordParseError}; +pub use reth_network_types::{DNSNodeRecord, NodeRecord, NodeRecordParseError}; // Ethereum bootnodes come from // OP bootnodes come from