1use alloy_eips::BlockNumHash;
4use alloy_primitives::B256;
5use std::{
6 net::{IpAddr, Ipv4Addr, Ipv6Addr, SocketAddr, SocketAddrV4, SocketAddrV6},
7 ops::Not,
8 path::PathBuf,
9 sync::OnceLock,
10};
11
12use crate::version::version_metadata;
13use clap::Args;
14use reth_chainspec::EthChainSpec;
15use reth_cli_util::{get_secret_key, load_secret_key::SecretKeyError};
16use reth_config::Config;
17use reth_discv4::{NodeRecord, DEFAULT_DISCOVERY_ADDR, DEFAULT_DISCOVERY_PORT};
18use reth_discv5::{
19 discv5::ListenConfig, DEFAULT_COUNT_BOOTSTRAP_LOOKUPS, DEFAULT_DISCOVERY_V5_PORT,
20 DEFAULT_SECONDS_BOOTSTRAP_LOOKUP_INTERVAL, DEFAULT_SECONDS_LOOKUP_INTERVAL,
21};
22use reth_net_banlist::IpFilter;
23use reth_net_nat::{NatResolver, DEFAULT_NET_IF_NAME};
24use reth_network::{
25 transactions::{
26 config::{TransactionIngressPolicy, TransactionPropagationKind},
27 constants::{
28 tx_fetcher::{
29 DEFAULT_MAX_CAPACITY_CACHE_PENDING_FETCH, DEFAULT_MAX_COUNT_CONCURRENT_REQUESTS,
30 DEFAULT_MAX_COUNT_CONCURRENT_REQUESTS_PER_PEER,
31 },
32 tx_manager::{
33 DEFAULT_MAX_COUNT_PENDING_POOL_IMPORTS, DEFAULT_MAX_COUNT_TRANSACTIONS_SEEN_BY_PEER,
34 },
35 },
36 TransactionFetcherConfig, TransactionPropagationMode, TransactionsManagerConfig,
37 DEFAULT_SOFT_LIMIT_BYTE_SIZE_POOLED_TRANSACTIONS_RESP_ON_PACK_GET_POOLED_TRANSACTIONS_REQ,
38 SOFT_LIMIT_BYTE_SIZE_POOLED_TRANSACTIONS_RESPONSE,
39 },
40 HelloMessageWithProtocols, NetworkConfigBuilder, NetworkPrimitives,
41};
42use reth_network_peers::{mainnet_nodes, TrustedPeer};
43use reth_tasks::Runtime;
44use secp256k1::SecretKey;
45use std::str::FromStr;
46use tracing::error;
47
48static NETWORK_DEFAULTS: OnceLock<DefaultNetworkArgs> = OnceLock::new();
50
51#[derive(Debug, Clone)]
55pub struct DefaultNetworkArgs {
56 pub dns_retries: usize,
58 pub nat: NatResolver,
60 pub addr: IpAddr,
62 pub port: u16,
64 pub max_concurrent_tx_requests: u32,
66 pub max_concurrent_tx_requests_per_peer: u8,
68 pub max_seen_tx_history: u32,
70 pub max_pending_pool_imports: usize,
72 pub soft_limit_byte_size_pooled_transactions_response: usize,
74 pub soft_limit_byte_size_pooled_transactions_response_on_pack_request: usize,
76 pub max_capacity_cache_txns_pending_fetch: u32,
78 pub tx_propagation_policy: TransactionPropagationKind,
80 pub tx_ingress_policy: TransactionIngressPolicy,
82 pub propagation_mode: TransactionPropagationMode,
84}
85
86impl DefaultNetworkArgs {
87 pub fn try_init(self) -> Result<(), Self> {
89 NETWORK_DEFAULTS.set(self)
90 }
91
92 pub fn get_global() -> &'static Self {
94 NETWORK_DEFAULTS.get_or_init(Self::default)
95 }
96
97 pub const fn with_dns_retries(mut self, v: usize) -> Self {
99 self.dns_retries = v;
100 self
101 }
102
103 pub fn with_nat(mut self, v: NatResolver) -> Self {
105 self.nat = v;
106 self
107 }
108
109 pub const fn with_addr(mut self, v: IpAddr) -> Self {
111 self.addr = v;
112 self
113 }
114
115 pub const fn with_port(mut self, v: u16) -> Self {
117 self.port = v;
118 self
119 }
120
121 pub const fn with_max_concurrent_tx_requests(mut self, v: u32) -> Self {
123 self.max_concurrent_tx_requests = v;
124 self
125 }
126
127 pub const fn with_max_concurrent_tx_requests_per_peer(mut self, v: u8) -> Self {
129 self.max_concurrent_tx_requests_per_peer = v;
130 self
131 }
132
133 pub const fn with_max_seen_tx_history(mut self, v: u32) -> Self {
135 self.max_seen_tx_history = v;
136 self
137 }
138
139 pub const fn with_max_pending_pool_imports(mut self, v: usize) -> Self {
141 self.max_pending_pool_imports = v;
142 self
143 }
144
145 pub const fn with_soft_limit_byte_size_pooled_transactions_response(
147 mut self,
148 v: usize,
149 ) -> Self {
150 self.soft_limit_byte_size_pooled_transactions_response = v;
151 self
152 }
153
154 pub const fn with_soft_limit_byte_size_pooled_transactions_response_on_pack_request(
156 mut self,
157 v: usize,
158 ) -> Self {
159 self.soft_limit_byte_size_pooled_transactions_response_on_pack_request = v;
160 self
161 }
162
163 pub const fn with_max_capacity_cache_txns_pending_fetch(mut self, v: u32) -> Self {
165 self.max_capacity_cache_txns_pending_fetch = v;
166 self
167 }
168
169 pub const fn with_tx_propagation_policy(mut self, v: TransactionPropagationKind) -> Self {
171 self.tx_propagation_policy = v;
172 self
173 }
174
175 pub const fn with_tx_ingress_policy(mut self, v: TransactionIngressPolicy) -> Self {
177 self.tx_ingress_policy = v;
178 self
179 }
180
181 pub const fn with_propagation_mode(mut self, v: TransactionPropagationMode) -> Self {
183 self.propagation_mode = v;
184 self
185 }
186}
187
188impl Default for DefaultNetworkArgs {
189 fn default() -> Self {
190 Self {
191 dns_retries: 0,
192 nat: NatResolver::Any,
193 addr: DEFAULT_DISCOVERY_ADDR,
194 port: DEFAULT_DISCOVERY_PORT,
195 max_concurrent_tx_requests: DEFAULT_MAX_COUNT_CONCURRENT_REQUESTS,
196 max_concurrent_tx_requests_per_peer: DEFAULT_MAX_COUNT_CONCURRENT_REQUESTS_PER_PEER,
197 max_seen_tx_history: DEFAULT_MAX_COUNT_TRANSACTIONS_SEEN_BY_PEER,
198 max_pending_pool_imports: DEFAULT_MAX_COUNT_PENDING_POOL_IMPORTS,
199 soft_limit_byte_size_pooled_transactions_response:
200 SOFT_LIMIT_BYTE_SIZE_POOLED_TRANSACTIONS_RESPONSE,
201 soft_limit_byte_size_pooled_transactions_response_on_pack_request:
202 DEFAULT_SOFT_LIMIT_BYTE_SIZE_POOLED_TRANSACTIONS_RESP_ON_PACK_GET_POOLED_TRANSACTIONS_REQ,
203 max_capacity_cache_txns_pending_fetch: DEFAULT_MAX_CAPACITY_CACHE_PENDING_FETCH,
204 tx_propagation_policy: TransactionPropagationKind::default(),
205 tx_ingress_policy: TransactionIngressPolicy::default(),
206 propagation_mode: TransactionPropagationMode::Sqrt,
207 }
208 }
209}
210
211#[derive(Debug, Clone, Args, PartialEq, Eq)]
213#[command(next_help_heading = "Networking")]
214pub struct NetworkArgs {
215 #[command(flatten)]
217 pub discovery: DiscoveryArgs,
218
219 #[expect(clippy::doc_markdown)]
220 #[arg(long, value_delimiter = ',')]
224 pub trusted_peers: Vec<TrustedPeer>,
225
226 #[arg(long)]
228 pub trusted_only: bool,
229
230 #[arg(long, value_delimiter = ',')]
234 pub bootnodes: Option<Vec<TrustedPeer>>,
235
236 #[arg(long, default_value_t = DefaultNetworkArgs::get_global().dns_retries)]
238 pub dns_retries: usize,
239
240 #[arg(long, value_name = "FILE", verbatim_doc_comment, conflicts_with = "no_persist_peers")]
243 pub peers_file: Option<PathBuf>,
244
245 #[arg(long, value_name = "IDENTITY", default_value = version_metadata().p2p_client_version.as_ref())]
247 pub identity: String,
248
249 #[arg(long, value_name = "PATH", conflicts_with = "p2p_secret_key_hex")]
254 pub p2p_secret_key: Option<PathBuf>,
255
256 #[arg(long, value_name = "HEX", conflicts_with = "p2p_secret_key")]
261 pub p2p_secret_key_hex: Option<B256>,
262
263 #[arg(long, verbatim_doc_comment)]
265 pub no_persist_peers: bool,
266
267 #[arg(long, default_value_t = DefaultNetworkArgs::get_global().nat.clone())]
269 pub nat: NatResolver,
270
271 #[arg(long = "addr", value_name = "ADDR", default_value_t = DefaultNetworkArgs::get_global().addr)]
273 pub addr: IpAddr,
274
275 #[arg(long = "port", value_name = "PORT", default_value_t = DefaultNetworkArgs::get_global().port)]
277 pub port: u16,
278
279 #[arg(long)]
281 pub max_outbound_peers: Option<usize>,
282
283 #[arg(long)]
285 pub max_inbound_peers: Option<usize>,
286
287 #[arg(
292 long,
293 value_name = "COUNT",
294 conflicts_with = "max_outbound_peers",
295 conflicts_with = "max_inbound_peers"
296 )]
297 pub max_peers: Option<usize>,
298
299 #[arg(long = "max-tx-reqs", value_name = "COUNT", default_value_t = DefaultNetworkArgs::get_global().max_concurrent_tx_requests, verbatim_doc_comment)]
301 pub max_concurrent_tx_requests: u32,
302
303 #[arg(long = "max-tx-reqs-peer", value_name = "COUNT", default_value_t = DefaultNetworkArgs::get_global().max_concurrent_tx_requests_per_peer, verbatim_doc_comment)]
305 pub max_concurrent_tx_requests_per_peer: u8,
306
307 #[arg(long = "max-seen-tx-history", value_name = "COUNT", default_value_t = DefaultNetworkArgs::get_global().max_seen_tx_history, verbatim_doc_comment)]
311 pub max_seen_tx_history: u32,
312
313 #[arg(long = "max-pending-imports", value_name = "COUNT", default_value_t = DefaultNetworkArgs::get_global().max_pending_pool_imports, verbatim_doc_comment)]
314 pub max_pending_pool_imports: usize,
316
317 #[arg(long = "pooled-tx-response-soft-limit", value_name = "BYTES", default_value_t = DefaultNetworkArgs::get_global().soft_limit_byte_size_pooled_transactions_response, verbatim_doc_comment)]
321 pub soft_limit_byte_size_pooled_transactions_response: usize,
322
323 #[arg(long = "pooled-tx-pack-soft-limit", value_name = "BYTES", default_value_t = DefaultNetworkArgs::get_global().soft_limit_byte_size_pooled_transactions_response_on_pack_request, verbatim_doc_comment)]
335 pub soft_limit_byte_size_pooled_transactions_response_on_pack_request: usize,
336
337 #[arg(long = "max-tx-pending-fetch", value_name = "COUNT", default_value_t = DefaultNetworkArgs::get_global().max_capacity_cache_txns_pending_fetch, verbatim_doc_comment)]
339 pub max_capacity_cache_txns_pending_fetch: u32,
340
341 #[arg(long = "net-if.experimental", conflicts_with = "addr", value_name = "IF_NAME")]
345 pub net_if: Option<String>,
346
347 #[arg(long = "tx-propagation-policy", default_value_t = DefaultNetworkArgs::get_global().tx_propagation_policy)]
351 pub tx_propagation_policy: TransactionPropagationKind,
352
353 #[arg(long = "tx-ingress-policy", default_value_t = DefaultNetworkArgs::get_global().tx_ingress_policy)]
357 pub tx_ingress_policy: TransactionIngressPolicy,
358
359 #[arg(long = "disable-tx-gossip")]
364 pub disable_tx_gossip: bool,
365
366 #[arg(
371 long = "tx-propagation-mode",
372 default_value_t = DefaultNetworkArgs::get_global().propagation_mode,
373 help = "Transaction propagation mode (sqrt, all, max:<number>)"
374 )]
375 pub propagation_mode: TransactionPropagationMode,
376
377 #[arg(long = "required-block-hashes", value_delimiter = ',', value_parser = parse_block_num_hash)]
381 pub required_block_hashes: Vec<BlockNumHash>,
382
383 #[arg(long)]
385 pub network_id: Option<u64>,
386
387 #[arg(long, value_name = "NETRESTRICT")]
394 pub netrestrict: Option<String>,
395
396 #[arg(long)]
402 pub enforce_enr_fork_id: bool,
403}
404
405impl NetworkArgs {
406 pub fn resolved_addr(&self) -> IpAddr {
408 if let Some(ref if_name) = self.net_if {
409 let if_name = if if_name.is_empty() { DEFAULT_NET_IF_NAME } else { if_name };
410 return match reth_net_nat::net_if::resolve_net_if_ip(if_name) {
411 Ok(addr) => addr,
412 Err(err) => {
413 error!(target: "reth::cli",
414 if_name,
415 %err,
416 "Failed to read network interface IP"
417 );
418
419 DEFAULT_DISCOVERY_ADDR
420 }
421 };
422 }
423
424 self.addr
425 }
426
427 pub fn resolved_bootnodes(&self) -> Option<Vec<NodeRecord>> {
429 self.bootnodes.clone().map(|bootnodes| {
430 bootnodes.into_iter().filter_map(|node| node.resolve_blocking().ok()).collect()
431 })
432 }
433
434 pub fn resolved_max_inbound_peers(&self) -> Option<usize> {
436 if let Some(max_peers) = self.max_peers {
437 if max_peers == 0 {
438 Some(0)
439 } else {
440 let outbound = (max_peers / 3).max(1);
441 Some(max_peers.saturating_sub(outbound))
442 }
443 } else {
444 self.max_inbound_peers
445 }
446 }
447
448 pub fn resolved_max_outbound_peers(&self) -> Option<usize> {
450 if let Some(max_peers) = self.max_peers {
451 if max_peers == 0 {
452 Some(0)
453 } else {
454 Some((max_peers / 3).max(1))
455 }
456 } else {
457 self.max_outbound_peers
458 }
459 }
460
461 pub const fn transactions_manager_config(&self) -> TransactionsManagerConfig {
463 TransactionsManagerConfig {
464 transaction_fetcher_config: TransactionFetcherConfig::new(
465 self.max_concurrent_tx_requests,
466 self.max_concurrent_tx_requests_per_peer,
467 self.soft_limit_byte_size_pooled_transactions_response,
468 self.soft_limit_byte_size_pooled_transactions_response_on_pack_request,
469 self.max_capacity_cache_txns_pending_fetch,
470 ),
471 max_transactions_seen_by_peer_history: self.max_seen_tx_history,
472 propagation_mode: self.propagation_mode,
473 ingress_policy: self.tx_ingress_policy,
474 }
475 }
476
477 pub fn network_config<N: NetworkPrimitives>(
489 &self,
490 config: &Config,
491 chain_spec: impl EthChainSpec,
492 secret_key: SecretKey,
493 default_peers_file: PathBuf,
494 executor: Runtime,
495 ) -> NetworkConfigBuilder<N> {
496 let addr = self.resolved_addr();
497 let chain_bootnodes = self
498 .resolved_bootnodes()
499 .unwrap_or_else(|| chain_spec.bootnodes().unwrap_or_else(mainnet_nodes));
500 let peers_file = self.peers_file.clone().unwrap_or(default_peers_file);
501
502 let ip_filter = self.ip_filter().unwrap_or_default();
504 let peers_config = config
505 .peers_config_with_basic_nodes_from_file(
506 self.persistent_peers_file(peers_file).as_deref(),
507 )
508 .with_max_inbound_opt(self.resolved_max_inbound_peers())
509 .with_max_outbound_opt(self.resolved_max_outbound_peers())
510 .with_ip_filter(ip_filter)
511 .with_enforce_enr_fork_id(self.enforce_enr_fork_id);
512
513 NetworkConfigBuilder::<N>::new(secret_key, executor)
515 .external_ip_resolver(self.nat.clone())
516 .sessions_config(
517 config.sessions.clone().with_upscaled_event_buffer(peers_config.max_peers()),
518 )
519 .peer_config(peers_config)
520 .boot_nodes(chain_bootnodes.clone())
521 .transactions_manager_config(self.transactions_manager_config())
522 .apply(|builder| {
524 let peer_id = builder.get_peer_id();
525 builder.hello_message(
526 HelloMessageWithProtocols::builder(peer_id)
527 .client_version(&self.identity)
528 .build(),
529 )
530 })
531 .apply(|builder| {
533 let rlpx_socket = (addr, self.port).into();
534 self.discovery.apply_to_builder(builder, rlpx_socket, chain_bootnodes)
535 })
536 .listener_addr(SocketAddr::new(
537 addr, self.port,
539 ))
540 .discovery_addr(SocketAddr::new(
541 self.discovery.addr,
542 self.discovery.port,
544 ))
545 .disable_tx_gossip(self.disable_tx_gossip)
546 .required_block_hashes(self.required_block_hashes.clone())
547 .network_id(self.network_id)
548 }
549
550 pub fn persistent_peers_file(&self, peers_file: PathBuf) -> Option<PathBuf> {
552 self.no_persist_peers.not().then_some(peers_file)
553 }
554
555 pub const fn with_discovery(mut self, discovery: DiscoveryArgs) -> Self {
557 self.discovery = discovery;
558 self
559 }
560
561 pub const fn with_unused_p2p_port(mut self) -> Self {
564 self.port = 0;
565 self
566 }
567
568 pub const fn with_unused_ports(mut self) -> Self {
571 self = self.with_unused_p2p_port();
572 self.discovery = self.discovery.with_unused_discovery_port();
573 self
574 }
575
576 pub fn with_nat_resolver(mut self, nat: NatResolver) -> Self {
578 self.nat = nat;
579 self
580 }
581
582 pub fn adjust_instance_ports(&mut self, instance: Option<u16>) {
588 if let Some(instance) = instance {
589 debug_assert_ne!(instance, 0, "instance must be non-zero");
590 self.port += instance - 1;
591 self.discovery.adjust_instance_ports(instance);
592 }
593 }
594
595 pub async fn resolve_trusted_peers(&self) -> Result<Vec<NodeRecord>, std::io::Error> {
597 futures::future::try_join_all(
598 self.trusted_peers.iter().map(|peer| async move { peer.resolve().await }),
599 )
600 .await
601 }
602
603 pub fn secret_key(
609 &self,
610 default_secret_key_path: PathBuf,
611 ) -> Result<SecretKey, SecretKeyError> {
612 if let Some(b256) = &self.p2p_secret_key_hex {
613 SecretKey::from_slice(b256.as_slice()).map_err(SecretKeyError::SecretKeyDecodeError)
615 } else {
616 let secret_key_path = self.p2p_secret_key.clone().unwrap_or(default_secret_key_path);
618 get_secret_key(&secret_key_path)
619 }
620 }
621
622 pub fn ip_filter(&self) -> Result<IpFilter, ipnet::AddrParseError> {
626 if let Some(netrestrict) = &self.netrestrict {
627 IpFilter::from_cidr_string(netrestrict)
628 } else {
629 Ok(IpFilter::allow_all())
630 }
631 }
632}
633
634impl Default for NetworkArgs {
635 fn default() -> Self {
636 let DefaultNetworkArgs {
637 dns_retries,
638 nat,
639 addr,
640 port,
641 max_concurrent_tx_requests,
642 max_concurrent_tx_requests_per_peer,
643 max_seen_tx_history,
644 max_pending_pool_imports,
645 soft_limit_byte_size_pooled_transactions_response,
646 soft_limit_byte_size_pooled_transactions_response_on_pack_request,
647 max_capacity_cache_txns_pending_fetch,
648 tx_propagation_policy,
649 tx_ingress_policy,
650 propagation_mode,
651 } = DefaultNetworkArgs::get_global().clone();
652 Self {
653 discovery: DiscoveryArgs::default(),
654 trusted_peers: vec![],
655 trusted_only: false,
656 bootnodes: None,
657 dns_retries,
658 peers_file: None,
659 identity: version_metadata().p2p_client_version.to_string(),
660 p2p_secret_key: None,
661 p2p_secret_key_hex: None,
662 no_persist_peers: false,
663 nat,
664 addr,
665 port,
666 max_outbound_peers: None,
667 max_inbound_peers: None,
668 max_peers: None,
669 max_concurrent_tx_requests,
670 max_concurrent_tx_requests_per_peer,
671 soft_limit_byte_size_pooled_transactions_response,
672 soft_limit_byte_size_pooled_transactions_response_on_pack_request,
673 max_pending_pool_imports,
674 max_seen_tx_history,
675 max_capacity_cache_txns_pending_fetch,
676 net_if: None,
677 tx_propagation_policy,
678 tx_ingress_policy,
679 disable_tx_gossip: false,
680 propagation_mode,
681 required_block_hashes: vec![],
682 network_id: None,
683 netrestrict: None,
684 enforce_enr_fork_id: false,
685 }
686 }
687}
688
689#[derive(Debug, Clone, Args, PartialEq, Eq)]
691pub struct DiscoveryArgs {
692 #[arg(short, long, default_value_if("dev", "true", "true"))]
694 pub disable_discovery: bool,
695
696 #[arg(long, conflicts_with = "disable_discovery")]
698 pub disable_dns_discovery: bool,
699
700 #[arg(long, conflicts_with = "disable_discovery")]
702 pub disable_discv4_discovery: bool,
703
704 #[arg(long, conflicts_with = "disable_discovery")]
706 pub enable_discv5_discovery: bool,
707
708 #[arg(long, conflicts_with = "disable_discovery")]
710 pub disable_nat: bool,
711
712 #[arg(id = "discovery.addr", long = "discovery.addr", value_name = "DISCOVERY_ADDR", default_value_t = DEFAULT_DISCOVERY_ADDR)]
714 pub addr: IpAddr,
715
716 #[arg(id = "discovery.port", long = "discovery.port", value_name = "DISCOVERY_PORT", default_value_t = DEFAULT_DISCOVERY_PORT)]
718 pub port: u16,
719
720 #[arg(id = "discovery.v5.addr", long = "discovery.v5.addr", value_name = "DISCOVERY_V5_ADDR", default_value = None)]
723 pub discv5_addr: Option<Ipv4Addr>,
724
725 #[arg(id = "discovery.v5.addr.ipv6", long = "discovery.v5.addr.ipv6", value_name = "DISCOVERY_V5_ADDR_IPV6", default_value = None)]
728 pub discv5_addr_ipv6: Option<Ipv6Addr>,
729
730 #[arg(id = "discovery.v5.port", long = "discovery.v5.port", value_name = "DISCOVERY_V5_PORT",
733 default_value_t = DEFAULT_DISCOVERY_V5_PORT)]
734 pub discv5_port: u16,
735
736 #[arg(id = "discovery.v5.port.ipv6", long = "discovery.v5.port.ipv6", value_name = "DISCOVERY_V5_PORT_IPV6",
739 default_value_t = DEFAULT_DISCOVERY_V5_PORT)]
740 pub discv5_port_ipv6: u16,
741
742 #[arg(id = "discovery.v5.lookup-interval", long = "discovery.v5.lookup-interval", value_name = "DISCOVERY_V5_LOOKUP_INTERVAL", default_value_t = DEFAULT_SECONDS_LOOKUP_INTERVAL)]
745 pub discv5_lookup_interval: u64,
746
747 #[arg(id = "discovery.v5.bootstrap.lookup-interval", long = "discovery.v5.bootstrap.lookup-interval", value_name = "DISCOVERY_V5_BOOTSTRAP_LOOKUP_INTERVAL",
750 default_value_t = DEFAULT_SECONDS_BOOTSTRAP_LOOKUP_INTERVAL)]
751 pub discv5_bootstrap_lookup_interval: u64,
752
753 #[arg(id = "discovery.v5.bootstrap.lookup-countdown", long = "discovery.v5.bootstrap.lookup-countdown", value_name = "DISCOVERY_V5_BOOTSTRAP_LOOKUP_COUNTDOWN",
755 default_value_t = DEFAULT_COUNT_BOOTSTRAP_LOOKUPS)]
756 pub discv5_bootstrap_lookup_countdown: u64,
757}
758
759impl DiscoveryArgs {
760 pub fn apply_to_builder<N>(
762 &self,
763 mut network_config_builder: NetworkConfigBuilder<N>,
764 rlpx_tcp_socket: SocketAddr,
765 boot_nodes: impl IntoIterator<Item = NodeRecord>,
766 ) -> NetworkConfigBuilder<N>
767 where
768 N: NetworkPrimitives,
769 {
770 if self.disable_discovery || self.disable_dns_discovery {
771 network_config_builder = network_config_builder.disable_dns_discovery();
772 }
773
774 if self.disable_discovery || self.disable_discv4_discovery {
775 network_config_builder = network_config_builder.disable_discv4_discovery();
776 }
777
778 if self.disable_nat {
779 network_config_builder = network_config_builder.disable_nat();
781 }
782
783 if self.should_enable_discv5() {
784 network_config_builder = network_config_builder
785 .discovery_v5(self.discovery_v5_builder(rlpx_tcp_socket, boot_nodes));
786 }
787
788 network_config_builder
789 }
790
791 pub fn discovery_v5_builder(
793 &self,
794 rlpx_tcp_socket: SocketAddr,
795 boot_nodes: impl IntoIterator<Item = NodeRecord>,
796 ) -> reth_discv5::ConfigBuilder {
797 let Self {
798 discv5_addr,
799 discv5_addr_ipv6,
800 discv5_port,
801 discv5_port_ipv6,
802 discv5_lookup_interval,
803 discv5_bootstrap_lookup_interval,
804 discv5_bootstrap_lookup_countdown,
805 ..
806 } = self;
807
808 let has_discv5_addr_args = discv5_addr.is_some() || discv5_addr_ipv6.is_some();
809
810 let discv5_addr_ipv4 = discv5_addr.or(match rlpx_tcp_socket {
812 SocketAddr::V4(addr) => Some(*addr.ip()),
813 SocketAddr::V6(_) => None,
814 });
815 let discv5_addr_ipv6 = discv5_addr_ipv6.or(match rlpx_tcp_socket {
816 SocketAddr::V4(_) => None,
817 SocketAddr::V6(addr) => Some(*addr.ip()),
818 });
819
820 let mut discv5_config_builder =
821 reth_discv5::discv5::ConfigBuilder::new(ListenConfig::from_two_sockets(
822 discv5_addr_ipv4.map(|addr| SocketAddrV4::new(addr, *discv5_port)),
823 discv5_addr_ipv6.map(|addr| SocketAddrV6::new(addr, *discv5_port_ipv6, 0, 0)),
824 ));
825
826 if has_discv5_addr_args || self.disable_nat {
827 discv5_config_builder.disable_enr_update();
829 }
830 reth_discv5::Config::builder(rlpx_tcp_socket)
831 .discv5_config(discv5_config_builder.build())
832 .add_unsigned_boot_nodes(boot_nodes)
833 .lookup_interval(*discv5_lookup_interval)
834 .bootstrap_lookup_interval(*discv5_bootstrap_lookup_interval)
835 .bootstrap_lookup_countdown(*discv5_bootstrap_lookup_countdown)
836 }
837
838 const fn should_enable_discv5(&self) -> bool {
840 if self.disable_discovery {
841 return false;
842 }
843
844 self.enable_discv5_discovery ||
845 self.discv5_addr.is_some() ||
846 self.discv5_addr_ipv6.is_some()
847 }
848
849 pub const fn with_unused_discovery_port(mut self) -> Self {
852 self.port = 0;
853 self
854 }
855
856 pub const fn with_discv5_port(mut self, port: u16) -> Self {
858 self.discv5_port = port;
859 self
860 }
861
862 pub fn adjust_instance_ports(&mut self, instance: u16) {
868 debug_assert_ne!(instance, 0, "instance must be non-zero");
869 self.port += instance - 1;
870 self.discv5_port += instance - 1;
871 self.discv5_port_ipv6 += instance - 1;
872 }
873}
874
875impl Default for DiscoveryArgs {
876 fn default() -> Self {
877 Self {
878 disable_discovery: false,
879 disable_dns_discovery: false,
880 disable_discv4_discovery: false,
881 enable_discv5_discovery: false,
882 disable_nat: false,
883 addr: DEFAULT_DISCOVERY_ADDR,
884 port: DEFAULT_DISCOVERY_PORT,
885 discv5_addr: None,
886 discv5_addr_ipv6: None,
887 discv5_port: DEFAULT_DISCOVERY_V5_PORT,
888 discv5_port_ipv6: DEFAULT_DISCOVERY_V5_PORT,
889 discv5_lookup_interval: DEFAULT_SECONDS_LOOKUP_INTERVAL,
890 discv5_bootstrap_lookup_interval: DEFAULT_SECONDS_BOOTSTRAP_LOOKUP_INTERVAL,
891 discv5_bootstrap_lookup_countdown: DEFAULT_COUNT_BOOTSTRAP_LOOKUPS,
892 }
893 }
894}
895
896fn parse_block_num_hash(s: &str) -> Result<BlockNumHash, String> {
898 if let Some((num_str, hash_str)) = s.split_once('=') {
899 let number = num_str.parse().map_err(|_| format!("Invalid block number: {}", num_str))?;
900 let hash = B256::from_str(hash_str).map_err(|_| format!("Invalid hash: {}", hash_str))?;
901 Ok(BlockNumHash::new(number, hash))
902 } else {
903 let hash = B256::from_str(s).map_err(|_| format!("Invalid hash: {}", s))?;
905 Ok(BlockNumHash::new(0, hash))
906 }
907}
908
909#[cfg(test)]
910mod tests {
911 use super::*;
912 use clap::Parser;
913 use reth_chainspec::MAINNET;
914 use reth_config::Config;
915 use reth_network_peers::NodeRecord;
916 use secp256k1::SecretKey;
917 use std::{
918 fs,
919 time::{SystemTime, UNIX_EPOCH},
920 };
921
922 #[derive(Parser)]
924 struct CommandParser<T: Args> {
925 #[command(flatten)]
926 args: T,
927 }
928
929 #[test]
930 fn parse_nat_args() {
931 let args = CommandParser::<NetworkArgs>::parse_from(["reth", "--nat", "none"]).args;
932 assert_eq!(args.nat, NatResolver::None);
933
934 let args =
935 CommandParser::<NetworkArgs>::parse_from(["reth", "--nat", "extip:0.0.0.0"]).args;
936 assert_eq!(args.nat, NatResolver::ExternalIp("0.0.0.0".parse().unwrap()));
937 }
938
939 #[test]
940 fn parse_peer_args() {
941 let args =
942 CommandParser::<NetworkArgs>::parse_from(["reth", "--max-outbound-peers", "50"]).args;
943 assert_eq!(args.max_outbound_peers, Some(50));
944 assert_eq!(args.max_inbound_peers, None);
945
946 let args = CommandParser::<NetworkArgs>::parse_from([
947 "reth",
948 "--max-outbound-peers",
949 "75",
950 "--max-inbound-peers",
951 "15",
952 ])
953 .args;
954 assert_eq!(args.max_outbound_peers, Some(75));
955 assert_eq!(args.max_inbound_peers, Some(15));
956 }
957
958 #[test]
959 fn parse_trusted_peer_args() {
960 let args =
961 CommandParser::<NetworkArgs>::parse_from([
962 "reth",
963 "--trusted-peers",
964 "enode://d860a01f9722d78051619d1e2351aba3f43f943f6f00718d1b9baa4101932a1f5011f16bb2b1bb35db20d6fe28fa0bf09636d26a87d31de9ec6203eeedb1f666@18.138.108.67:30303,enode://22a8232c3abc76a16ae9d6c3b164f98775fe226f0917b0ca871128a74a8e9630b458460865bab457221f1d448dd9791d24c4e5d88786180ac185df813a68d4de@3.209.45.79:30303"
965 ])
966 .args;
967
968 assert_eq!(
969 args.trusted_peers,
970 vec![
971 "enode://d860a01f9722d78051619d1e2351aba3f43f943f6f00718d1b9baa4101932a1f5011f16bb2b1bb35db20d6fe28fa0bf09636d26a87d31de9ec6203eeedb1f666@18.138.108.67:30303".parse().unwrap(),
972 "enode://22a8232c3abc76a16ae9d6c3b164f98775fe226f0917b0ca871128a74a8e9630b458460865bab457221f1d448dd9791d24c4e5d88786180ac185df813a68d4de@3.209.45.79:30303".parse().unwrap()
973 ]
974 );
975 }
976
977 #[test]
978 fn parse_retry_strategy_args() {
979 let tests = vec![0, 10];
980
981 for retries in tests {
982 let retries_str = retries.to_string();
983 let args = CommandParser::<NetworkArgs>::parse_from([
984 "reth",
985 "--dns-retries",
986 retries_str.as_str(),
987 ])
988 .args;
989
990 assert_eq!(args.dns_retries, retries);
991 }
992 }
993
994 #[test]
995 fn parse_disable_tx_gossip_args() {
996 let args = CommandParser::<NetworkArgs>::parse_from(["reth", "--disable-tx-gossip"]).args;
997 assert!(args.disable_tx_gossip);
998 }
999
1000 #[test]
1001 fn parse_max_peers_flag() {
1002 let args = CommandParser::<NetworkArgs>::parse_from(["reth", "--max-peers", "90"]).args;
1003
1004 assert_eq!(args.max_peers, Some(90));
1005 assert_eq!(args.max_outbound_peers, None);
1006 assert_eq!(args.max_inbound_peers, None);
1007 assert_eq!(args.resolved_max_outbound_peers(), Some(30));
1008 assert_eq!(args.resolved_max_inbound_peers(), Some(60));
1009 }
1010
1011 #[test]
1012 fn max_peers_conflicts_with_outbound() {
1013 let result = CommandParser::<NetworkArgs>::try_parse_from([
1014 "reth",
1015 "--max-peers",
1016 "90",
1017 "--max-outbound-peers",
1018 "50",
1019 ]);
1020 assert!(
1021 result.is_err(),
1022 "Should fail when both --max-peers and --max-outbound-peers are used"
1023 );
1024 }
1025
1026 #[test]
1027 fn max_peers_conflicts_with_inbound() {
1028 let result = CommandParser::<NetworkArgs>::try_parse_from([
1029 "reth",
1030 "--max-peers",
1031 "90",
1032 "--max-inbound-peers",
1033 "30",
1034 ]);
1035 assert!(
1036 result.is_err(),
1037 "Should fail when both --max-peers and --max-inbound-peers are used"
1038 );
1039 }
1040
1041 #[test]
1042 fn max_peers_split_calculation() {
1043 let args = CommandParser::<NetworkArgs>::parse_from(["reth", "--max-peers", "90"]).args;
1044
1045 assert_eq!(args.max_peers, Some(90));
1046 assert_eq!(args.resolved_max_outbound_peers(), Some(30));
1047 assert_eq!(args.resolved_max_inbound_peers(), Some(60));
1048 }
1049
1050 #[test]
1051 fn max_peers_small_values() {
1052 let args1 = CommandParser::<NetworkArgs>::parse_from(["reth", "--max-peers", "1"]).args;
1053 assert_eq!(args1.resolved_max_outbound_peers(), Some(1));
1054 assert_eq!(args1.resolved_max_inbound_peers(), Some(0));
1055
1056 let args2 = CommandParser::<NetworkArgs>::parse_from(["reth", "--max-peers", "2"]).args;
1057 assert_eq!(args2.resolved_max_outbound_peers(), Some(1));
1058 assert_eq!(args2.resolved_max_inbound_peers(), Some(1));
1059
1060 let args3 = CommandParser::<NetworkArgs>::parse_from(["reth", "--max-peers", "3"]).args;
1061 assert_eq!(args3.resolved_max_outbound_peers(), Some(1));
1062 assert_eq!(args3.resolved_max_inbound_peers(), Some(2));
1063 }
1064
1065 #[test]
1066 fn resolved_peers_without_max_peers() {
1067 let args = CommandParser::<NetworkArgs>::parse_from([
1068 "reth",
1069 "--max-outbound-peers",
1070 "75",
1071 "--max-inbound-peers",
1072 "15",
1073 ])
1074 .args;
1075
1076 assert_eq!(args.max_peers, None);
1077 assert_eq!(args.resolved_max_outbound_peers(), Some(75));
1078 assert_eq!(args.resolved_max_inbound_peers(), Some(15));
1079 }
1080
1081 #[test]
1082 fn resolved_peers_with_defaults() {
1083 let args = CommandParser::<NetworkArgs>::parse_from(["reth"]).args;
1084
1085 assert_eq!(args.max_peers, None);
1086 assert_eq!(args.resolved_max_outbound_peers(), None);
1087 assert_eq!(args.resolved_max_inbound_peers(), None);
1088 }
1089
1090 #[test]
1091 fn network_args_default_sanity_test() {
1092 let default_args = NetworkArgs::default();
1093 let args = CommandParser::<NetworkArgs>::parse_from(["reth"]).args;
1094
1095 assert_eq!(args, default_args);
1096 }
1097
1098 #[test]
1099 fn parse_required_block_hashes() {
1100 let args = CommandParser::<NetworkArgs>::parse_from([
1101 "reth",
1102 "--required-block-hashes",
1103 "0x1111111111111111111111111111111111111111111111111111111111111111,23115201=0x2222222222222222222222222222222222222222222222222222222222222222",
1104 ])
1105 .args;
1106
1107 assert_eq!(args.required_block_hashes.len(), 2);
1108 assert_eq!(args.required_block_hashes[0].number, 0);
1110 assert_eq!(
1111 args.required_block_hashes[0].hash.to_string(),
1112 "0x1111111111111111111111111111111111111111111111111111111111111111"
1113 );
1114 assert_eq!(args.required_block_hashes[1].number, 23115201);
1116 assert_eq!(
1117 args.required_block_hashes[1].hash.to_string(),
1118 "0x2222222222222222222222222222222222222222222222222222222222222222"
1119 );
1120 }
1121
1122 #[test]
1123 fn parse_empty_required_block_hashes() {
1124 let args = CommandParser::<NetworkArgs>::parse_from(["reth"]).args;
1125 assert!(args.required_block_hashes.is_empty());
1126 }
1127
1128 #[test]
1129 fn test_parse_block_num_hash() {
1130 let result = parse_block_num_hash(
1132 "0x1111111111111111111111111111111111111111111111111111111111111111",
1133 );
1134 assert!(result.is_ok());
1135 assert_eq!(result.unwrap().number, 0);
1136
1137 let result = parse_block_num_hash(
1139 "23115201=0x2222222222222222222222222222222222222222222222222222222222222222",
1140 );
1141 assert!(result.is_ok());
1142 assert_eq!(result.unwrap().number, 23115201);
1143
1144 assert!(parse_block_num_hash("invalid").is_err());
1146 assert!(parse_block_num_hash(
1147 "abc=0x1111111111111111111111111111111111111111111111111111111111111111"
1148 )
1149 .is_err());
1150 }
1151
1152 #[test]
1153 fn parse_p2p_secret_key_hex() {
1154 let hex = "4c0883a69102937d6231471b5dbb6204fe512961708279f8c5c58b3b9c4e8b8f";
1155 let args =
1156 CommandParser::<NetworkArgs>::parse_from(["reth", "--p2p-secret-key-hex", hex]).args;
1157
1158 let expected: B256 = hex.parse().unwrap();
1159 assert_eq!(args.p2p_secret_key_hex, Some(expected));
1160 assert_eq!(args.p2p_secret_key, None);
1161 }
1162
1163 #[test]
1164 fn parse_p2p_secret_key_hex_with_0x_prefix() {
1165 let hex = "0x4c0883a69102937d6231471b5dbb6204fe512961708279f8c5c58b3b9c4e8b8f";
1166 let args =
1167 CommandParser::<NetworkArgs>::parse_from(["reth", "--p2p-secret-key-hex", hex]).args;
1168
1169 let expected: B256 = hex.parse().unwrap();
1170 assert_eq!(args.p2p_secret_key_hex, Some(expected));
1171 assert_eq!(args.p2p_secret_key, None);
1172 }
1173
1174 #[test]
1175 fn test_p2p_secret_key_and_hex_are_mutually_exclusive() {
1176 let result = CommandParser::<NetworkArgs>::try_parse_from([
1177 "reth",
1178 "--p2p-secret-key",
1179 "/path/to/key",
1180 "--p2p-secret-key-hex",
1181 "4c0883a69102937d6231471b5dbb6204fe512961708279f8c5c58b3b9c4e8b8f",
1182 ]);
1183
1184 assert!(result.is_err());
1185 }
1186
1187 #[test]
1188 fn test_secret_key_method_with_hex() {
1189 let hex = "4c0883a69102937d6231471b5dbb6204fe512961708279f8c5c58b3b9c4e8b8f";
1190 let args =
1191 CommandParser::<NetworkArgs>::parse_from(["reth", "--p2p-secret-key-hex", hex]).args;
1192
1193 let temp_dir = std::env::temp_dir();
1194 let default_path = temp_dir.join("default_key");
1195 let secret_key = args.secret_key(default_path).unwrap();
1196
1197 assert_eq!(alloy_primitives::hex::encode(secret_key.secret_bytes()), hex);
1199 }
1200
1201 #[test]
1202 fn parse_netrestrict_single_network() {
1203 let args =
1204 CommandParser::<NetworkArgs>::parse_from(["reth", "--netrestrict", "192.168.0.0/16"])
1205 .args;
1206
1207 assert_eq!(args.netrestrict, Some("192.168.0.0/16".to_string()));
1208
1209 let ip_filter = args.ip_filter().unwrap();
1210 assert!(ip_filter.has_restrictions());
1211 assert!(ip_filter.is_allowed(&"192.168.1.1".parse().unwrap()));
1212 assert!(!ip_filter.is_allowed(&"10.0.0.1".parse().unwrap()));
1213 }
1214
1215 #[test]
1216 fn parse_netrestrict_multiple_networks() {
1217 let args = CommandParser::<NetworkArgs>::parse_from([
1218 "reth",
1219 "--netrestrict",
1220 "192.168.0.0/16,10.0.0.0/8",
1221 ])
1222 .args;
1223
1224 assert_eq!(args.netrestrict, Some("192.168.0.0/16,10.0.0.0/8".to_string()));
1225
1226 let ip_filter = args.ip_filter().unwrap();
1227 assert!(ip_filter.has_restrictions());
1228 assert!(ip_filter.is_allowed(&"192.168.1.1".parse().unwrap()));
1229 assert!(ip_filter.is_allowed(&"10.5.10.20".parse().unwrap()));
1230 assert!(!ip_filter.is_allowed(&"172.16.0.1".parse().unwrap()));
1231 }
1232
1233 #[test]
1234 fn parse_netrestrict_ipv6() {
1235 let args =
1236 CommandParser::<NetworkArgs>::parse_from(["reth", "--netrestrict", "2001:db8::/32"])
1237 .args;
1238
1239 let ip_filter = args.ip_filter().unwrap();
1240 assert!(ip_filter.has_restrictions());
1241 assert!(ip_filter.is_allowed(&"2001:db8::1".parse().unwrap()));
1242 assert!(!ip_filter.is_allowed(&"2001:db9::1".parse().unwrap()));
1243 }
1244
1245 #[test]
1246 fn netrestrict_not_set() {
1247 let args = CommandParser::<NetworkArgs>::parse_from(["reth"]).args;
1248 assert_eq!(args.netrestrict, None);
1249
1250 let ip_filter = args.ip_filter().unwrap();
1251 assert!(!ip_filter.has_restrictions());
1252 assert!(ip_filter.is_allowed(&"192.168.1.1".parse().unwrap()));
1253 assert!(ip_filter.is_allowed(&"10.0.0.1".parse().unwrap()));
1254 }
1255
1256 #[test]
1257 fn netrestrict_invalid_cidr() {
1258 let args =
1259 CommandParser::<NetworkArgs>::parse_from(["reth", "--netrestrict", "invalid-cidr"])
1260 .args;
1261
1262 assert!(args.ip_filter().is_err());
1263 }
1264
1265 #[test]
1266 fn network_config_preserves_basic_nodes_from_peers_file() {
1267 let enode = "enode://6f8a80d14311c39f35f516fa664deaaaa13e85b2f7493f37f6144d86991ec012937307647bd3b9a82abe2974e1407241d54947bbb39763a4cac9f77166ad92a0@10.3.58.6:30303?discport=30301";
1268 let unique = SystemTime::now().duration_since(UNIX_EPOCH).unwrap().as_nanos();
1269
1270 let peers_file = std::env::temp_dir().join(format!("reth_peers_test_{}.json", unique));
1271 fs::write(&peers_file, format!("[\"{}\"]", enode)).expect("write peers file");
1272
1273 let args = NetworkArgs {
1275 peers_file: Some(peers_file.clone()),
1276 no_persist_peers: false,
1277 ..Default::default()
1278 };
1279
1280 let secret_key = SecretKey::from_byte_array(&[1u8; 32]).unwrap();
1282 let builder = args.network_config::<reth_network::EthNetworkPrimitives>(
1283 &Config::default(),
1284 MAINNET.clone(),
1285 secret_key,
1286 peers_file.clone(),
1287 Runtime::test(),
1288 );
1289
1290 let net_cfg = builder.build_with_noop_provider(MAINNET.clone());
1291
1292 let node: NodeRecord = enode.parse().unwrap();
1294 assert!(net_cfg.peers_config.persisted_peers.iter().any(|p| p.record == node));
1295
1296 let _ = fs::remove_file(&peers_file);
1298 }
1299}