1use alloy_eips::BlockNumHash;
4use alloy_primitives::B256;
5use std::{
6 net::{IpAddr, Ipv4Addr, Ipv6Addr, SocketAddr, SocketAddrV4, SocketAddrV6},
7 num::NonZeroUsize,
8 ops::Not,
9 path::PathBuf,
10 sync::OnceLock,
11};
12
13use crate::version::version_metadata;
14use clap::{
15 builder::{OsStr, Resettable},
16 Args,
17};
18use reth_chainspec::EthChainSpec;
19use reth_cli_util::{get_secret_key, load_secret_key::SecretKeyError};
20use reth_config::Config;
21use reth_discv4::{NodeRecord, DEFAULT_DISCOVERY_ADDR, DEFAULT_DISCOVERY_PORT};
22use reth_discv5::{
23 discv5::ListenConfig, DEFAULT_COUNT_BOOTSTRAP_LOOKUPS, DEFAULT_DISCOVERY_V5_PORT,
24 DEFAULT_SECONDS_BOOTSTRAP_LOOKUP_INTERVAL, DEFAULT_SECONDS_LOOKUP_INTERVAL,
25};
26use reth_net_banlist::IpFilter;
27use reth_net_nat::{NatResolver, DEFAULT_NET_IF_NAME};
28use reth_network::{
29 transactions::{
30 config::{TransactionIngressPolicy, TransactionPropagationKind},
31 constants::{
32 tx_fetcher::{
33 DEFAULT_MAX_CAPACITY_CACHE_PENDING_FETCH, DEFAULT_MAX_COUNT_CONCURRENT_REQUESTS,
34 DEFAULT_MAX_COUNT_CONCURRENT_REQUESTS_PER_PEER,
35 },
36 tx_manager::{
37 DEFAULT_MAX_COUNT_PENDING_POOL_IMPORTS,
38 DEFAULT_MAX_COUNT_TRANSACTIONS_SEEN_BY_PEER,
39 DEFAULT_TX_MANAGER_CHANNEL_MEMORY_LIMIT_BYTES,
40 },
41 },
42 TransactionFetcherConfig, TransactionPropagationMode, TransactionsManagerConfig,
43 DEFAULT_SOFT_LIMIT_BYTE_SIZE_POOLED_TRANSACTIONS_RESP_ON_PACK_GET_POOLED_TRANSACTIONS_REQ,
44 SOFT_LIMIT_BYTE_SIZE_POOLED_TRANSACTIONS_RESPONSE,
45 },
46 HelloMessageWithProtocols, NetworkConfigBuilder, NetworkPrimitives,
47};
48use reth_network_peers::{mainnet_nodes, TrustedPeer};
49use reth_tasks::Runtime;
50use secp256k1::SecretKey;
51use std::str::FromStr;
52use tracing::error;
53
54static NETWORK_DEFAULTS: OnceLock<DefaultNetworkArgs> = OnceLock::new();
56
57#[derive(Debug, Clone)]
61pub struct DefaultNetworkArgs {
62 pub dns_retries: usize,
64 pub nat: NatResolver,
66 pub addr: IpAddr,
68 pub port: u16,
70 pub max_concurrent_tx_requests: u32,
72 pub max_concurrent_tx_requests_per_peer: u8,
74 pub max_seen_tx_history: u32,
76 pub max_pending_pool_imports: usize,
78 pub soft_limit_byte_size_pooled_transactions_response: usize,
80 pub soft_limit_byte_size_pooled_transactions_response_on_pack_request: usize,
82 pub max_capacity_cache_txns_pending_fetch: u32,
84 pub tx_channel_memory_limit_bytes: usize,
86 pub tx_propagation_policy: TransactionPropagationKind,
88 pub tx_ingress_policy: TransactionIngressPolicy,
90 pub propagation_mode: TransactionPropagationMode,
92 pub enforce_enr_fork_id: bool,
94}
95
96impl DefaultNetworkArgs {
97 pub fn try_init(self) -> Result<(), Self> {
99 NETWORK_DEFAULTS.set(self)
100 }
101
102 pub fn get_global() -> &'static Self {
104 NETWORK_DEFAULTS.get_or_init(Self::default)
105 }
106
107 pub const fn with_dns_retries(mut self, v: usize) -> Self {
109 self.dns_retries = v;
110 self
111 }
112
113 pub fn with_nat(mut self, v: NatResolver) -> Self {
115 self.nat = v;
116 self
117 }
118
119 pub const fn with_addr(mut self, v: IpAddr) -> Self {
121 self.addr = v;
122 self
123 }
124
125 pub const fn with_port(mut self, v: u16) -> Self {
127 self.port = v;
128 self
129 }
130
131 pub const fn with_max_concurrent_tx_requests(mut self, v: u32) -> Self {
133 self.max_concurrent_tx_requests = v;
134 self
135 }
136
137 pub const fn with_max_concurrent_tx_requests_per_peer(mut self, v: u8) -> Self {
139 self.max_concurrent_tx_requests_per_peer = v;
140 self
141 }
142
143 pub const fn with_max_seen_tx_history(mut self, v: u32) -> Self {
145 self.max_seen_tx_history = v;
146 self
147 }
148
149 pub const fn with_max_pending_pool_imports(mut self, v: usize) -> Self {
151 self.max_pending_pool_imports = v;
152 self
153 }
154
155 pub const fn with_soft_limit_byte_size_pooled_transactions_response(
157 mut self,
158 v: usize,
159 ) -> Self {
160 self.soft_limit_byte_size_pooled_transactions_response = v;
161 self
162 }
163
164 pub const fn with_soft_limit_byte_size_pooled_transactions_response_on_pack_request(
166 mut self,
167 v: usize,
168 ) -> Self {
169 self.soft_limit_byte_size_pooled_transactions_response_on_pack_request = v;
170 self
171 }
172
173 pub const fn with_max_capacity_cache_txns_pending_fetch(mut self, v: u32) -> Self {
175 self.max_capacity_cache_txns_pending_fetch = v;
176 self
177 }
178
179 pub const fn with_tx_channel_memory_limit_bytes(mut self, v: usize) -> Self {
182 self.tx_channel_memory_limit_bytes = v;
183 self
184 }
185
186 pub const fn with_tx_propagation_policy(mut self, v: TransactionPropagationKind) -> Self {
188 self.tx_propagation_policy = v;
189 self
190 }
191
192 pub const fn with_tx_ingress_policy(mut self, v: TransactionIngressPolicy) -> Self {
194 self.tx_ingress_policy = v;
195 self
196 }
197
198 pub const fn with_propagation_mode(mut self, v: TransactionPropagationMode) -> Self {
200 self.propagation_mode = v;
201 self
202 }
203
204 pub const fn with_enforce_enr_fork_id(mut self, v: bool) -> Self {
206 self.enforce_enr_fork_id = v;
207 self
208 }
209}
210
211impl Default for DefaultNetworkArgs {
212 fn default() -> Self {
213 Self {
214 dns_retries: 0,
215 nat: NatResolver::Any,
216 addr: DEFAULT_DISCOVERY_ADDR,
217 port: DEFAULT_DISCOVERY_PORT,
218 max_concurrent_tx_requests: DEFAULT_MAX_COUNT_CONCURRENT_REQUESTS,
219 max_concurrent_tx_requests_per_peer: DEFAULT_MAX_COUNT_CONCURRENT_REQUESTS_PER_PEER,
220 max_seen_tx_history: DEFAULT_MAX_COUNT_TRANSACTIONS_SEEN_BY_PEER,
221 max_pending_pool_imports: DEFAULT_MAX_COUNT_PENDING_POOL_IMPORTS,
222 soft_limit_byte_size_pooled_transactions_response:
223 SOFT_LIMIT_BYTE_SIZE_POOLED_TRANSACTIONS_RESPONSE,
224 soft_limit_byte_size_pooled_transactions_response_on_pack_request:
225 DEFAULT_SOFT_LIMIT_BYTE_SIZE_POOLED_TRANSACTIONS_RESP_ON_PACK_GET_POOLED_TRANSACTIONS_REQ,
226 max_capacity_cache_txns_pending_fetch: DEFAULT_MAX_CAPACITY_CACHE_PENDING_FETCH,
227 tx_channel_memory_limit_bytes: DEFAULT_TX_MANAGER_CHANNEL_MEMORY_LIMIT_BYTES,
228 tx_propagation_policy: TransactionPropagationKind::default(),
229 tx_ingress_policy: TransactionIngressPolicy::default(),
230 propagation_mode: TransactionPropagationMode::Sqrt,
231 enforce_enr_fork_id: false,
232 }
233 }
234}
235
236#[derive(Debug, Clone, Args, PartialEq, Eq)]
238#[command(next_help_heading = "Networking")]
239pub struct NetworkArgs {
240 #[command(flatten)]
242 pub discovery: DiscoveryArgs,
243
244 #[expect(clippy::doc_markdown)]
245 #[arg(long, value_delimiter = ',')]
249 pub trusted_peers: Vec<TrustedPeer>,
250
251 #[arg(long)]
253 pub trusted_only: bool,
254
255 #[arg(long, value_delimiter = ',')]
259 pub bootnodes: Option<Vec<TrustedPeer>>,
260
261 #[arg(long, default_value_t = DefaultNetworkArgs::get_global().dns_retries)]
263 pub dns_retries: usize,
264
265 #[arg(long, value_name = "FILE", verbatim_doc_comment, conflicts_with = "no_persist_peers")]
268 pub peers_file: Option<PathBuf>,
269
270 #[arg(long, value_name = "IDENTITY", default_value = version_metadata().p2p_client_version.as_ref())]
272 pub identity: String,
273
274 #[arg(long, value_name = "PATH", conflicts_with = "p2p_secret_key_hex")]
279 pub p2p_secret_key: Option<PathBuf>,
280
281 #[arg(long, value_name = "HEX", conflicts_with = "p2p_secret_key")]
286 pub p2p_secret_key_hex: Option<B256>,
287
288 #[arg(long, verbatim_doc_comment)]
290 pub no_persist_peers: bool,
291
292 #[arg(long, default_value_t = DefaultNetworkArgs::get_global().nat.clone())]
294 pub nat: NatResolver,
295
296 #[arg(long = "addr", value_name = "ADDR", default_value_t = DefaultNetworkArgs::get_global().addr)]
298 pub addr: IpAddr,
299
300 #[arg(long = "port", value_name = "PORT", default_value_t = DefaultNetworkArgs::get_global().port)]
302 pub port: u16,
303
304 #[arg(long)]
306 pub max_outbound_peers: Option<usize>,
307
308 #[arg(long)]
310 pub max_inbound_peers: Option<usize>,
311
312 #[arg(
317 long,
318 value_name = "COUNT",
319 conflicts_with = "max_outbound_peers",
320 conflicts_with = "max_inbound_peers"
321 )]
322 pub max_peers: Option<usize>,
323
324 #[arg(long = "max-tx-reqs", value_name = "COUNT", default_value_t = DefaultNetworkArgs::get_global().max_concurrent_tx_requests, verbatim_doc_comment)]
326 pub max_concurrent_tx_requests: u32,
327
328 #[arg(long = "max-tx-reqs-peer", value_name = "COUNT", default_value_t = DefaultNetworkArgs::get_global().max_concurrent_tx_requests_per_peer, verbatim_doc_comment)]
330 pub max_concurrent_tx_requests_per_peer: u8,
331
332 #[arg(long = "max-seen-tx-history", value_name = "COUNT", default_value_t = DefaultNetworkArgs::get_global().max_seen_tx_history, verbatim_doc_comment)]
336 pub max_seen_tx_history: u32,
337
338 #[arg(long = "max-pending-imports", value_name = "COUNT", default_value_t = DefaultNetworkArgs::get_global().max_pending_pool_imports, verbatim_doc_comment)]
339 pub max_pending_pool_imports: usize,
341
342 #[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)]
346 pub soft_limit_byte_size_pooled_transactions_response: usize,
347
348 #[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)]
360 pub soft_limit_byte_size_pooled_transactions_response_on_pack_request: usize,
361
362 #[arg(long = "max-tx-pending-fetch", value_name = "COUNT", default_value_t = DefaultNetworkArgs::get_global().max_capacity_cache_txns_pending_fetch, verbatim_doc_comment)]
364 pub max_capacity_cache_txns_pending_fetch: u32,
365
366 #[arg(long = "tx-channel-memory-limit", value_name = "BYTES", default_value_t = DefaultNetworkArgs::get_global().tx_channel_memory_limit_bytes, verbatim_doc_comment)]
373 pub tx_channel_memory_limit_bytes: usize,
374
375 #[arg(long = "net-if.experimental", conflicts_with = "addr", value_name = "IF_NAME")]
381 pub net_if: Option<String>,
382
383 #[arg(long = "tx-propagation-policy", default_value_t = DefaultNetworkArgs::get_global().tx_propagation_policy)]
387 pub tx_propagation_policy: TransactionPropagationKind,
388
389 #[arg(long = "tx-ingress-policy", default_value_t = DefaultNetworkArgs::get_global().tx_ingress_policy)]
393 pub tx_ingress_policy: TransactionIngressPolicy,
394
395 #[arg(long = "disable-tx-gossip")]
400 pub disable_tx_gossip: bool,
401
402 #[arg(
407 long = "tx-propagation-mode",
408 default_value_t = DefaultNetworkArgs::get_global().propagation_mode,
409 help = "Transaction propagation mode (sqrt, all, max:<number>)"
410 )]
411 pub propagation_mode: TransactionPropagationMode,
412
413 #[arg(long = "required-block-hashes", value_delimiter = ',', value_parser = parse_block_num_hash)]
417 pub required_block_hashes: Vec<BlockNumHash>,
418
419 #[arg(long)]
421 pub network_id: Option<u64>,
422
423 #[arg(long = "eth-max-message-size", value_name = "BYTES")]
425 pub eth_max_message_size: Option<NonZeroUsize>,
426
427 #[arg(long, value_name = "NETRESTRICT")]
434 pub netrestrict: Option<String>,
435
436 #[arg(long, default_value_t = DefaultNetworkArgs::get_global().enforce_enr_fork_id)]
442 pub enforce_enr_fork_id: bool,
443}
444
445impl NetworkArgs {
446 pub fn resolved_addr(&self) -> IpAddr {
454 if let Some(ref if_name) = self.net_if {
455 let if_name = if if_name.is_empty() { DEFAULT_NET_IF_NAME } else { if_name };
456 return match reth_net_nat::net_if::resolve_net_if_ip(if_name) {
457 Ok(addr) => addr,
458 Err(err) => {
459 error!(target: "reth::cli",
460 if_name,
461 %err,
462 "Failed to read network interface IP"
463 );
464
465 DEFAULT_DISCOVERY_ADDR
466 }
467 };
468 }
469
470 self.addr
471 }
472
473 fn resolved_discovery_addr(&self, listener_addr: IpAddr) -> IpAddr {
481 if self.net_if.is_some() && self.discovery.addr == DEFAULT_DISCOVERY_ADDR {
482 return listener_addr;
483 }
484
485 self.discovery.addr
486 }
487
488 pub fn resolved_bootnodes(&self) -> Option<Vec<NodeRecord>> {
490 self.bootnodes.as_deref().map(|bootnodes| {
491 bootnodes.iter().filter_map(|node| node.resolve_blocking().ok()).collect()
492 })
493 }
494
495 pub fn resolved_max_inbound_peers(&self) -> Option<usize> {
497 if let Some(max_peers) = self.max_peers {
498 if max_peers == 0 {
499 Some(0)
500 } else {
501 let outbound = (max_peers / 3).max(1);
502 Some(max_peers.saturating_sub(outbound))
503 }
504 } else {
505 self.max_inbound_peers
506 }
507 }
508
509 pub fn resolved_max_outbound_peers(&self) -> Option<usize> {
511 if let Some(max_peers) = self.max_peers {
512 if max_peers == 0 {
513 Some(0)
514 } else {
515 Some((max_peers / 3).max(1))
516 }
517 } else {
518 self.max_outbound_peers
519 }
520 }
521
522 pub const fn transactions_manager_config(&self) -> TransactionsManagerConfig {
524 TransactionsManagerConfig {
525 transaction_fetcher_config: TransactionFetcherConfig::new(
526 self.max_concurrent_tx_requests,
527 self.max_concurrent_tx_requests_per_peer,
528 self.soft_limit_byte_size_pooled_transactions_response,
529 self.soft_limit_byte_size_pooled_transactions_response_on_pack_request,
530 self.max_capacity_cache_txns_pending_fetch,
531 ),
532 max_transactions_seen_by_peer_history: self.max_seen_tx_history,
533 max_pending_pool_imports: self.max_pending_pool_imports,
534 propagation_mode: self.propagation_mode,
535 ingress_policy: self.tx_ingress_policy,
536 tx_channel_memory_limit_bytes: self.tx_channel_memory_limit_bytes,
537 }
538 }
539
540 pub fn network_config<N: NetworkPrimitives>(
553 &self,
554 config: &Config,
555 chain_spec: impl EthChainSpec,
556 secret_key: SecretKey,
557 default_peers_file: PathBuf,
558 executor: Runtime,
559 ) -> NetworkConfigBuilder<N> {
560 let listener_addr = self.resolved_addr();
563 let discovery_addr = self.resolved_discovery_addr(listener_addr);
567 let chain_bootnodes = self
568 .resolved_bootnodes()
569 .or_else(|| {
570 (!config.bootnodes.is_empty()).then(|| {
571 config
572 .bootnodes
573 .iter()
574 .filter_map(|node| node.resolve_blocking().ok())
575 .collect()
576 })
577 })
578 .unwrap_or_else(|| chain_spec.bootnodes().unwrap_or_else(mainnet_nodes));
579 let peers_file = self.peers_file.clone().unwrap_or(default_peers_file);
580
581 let ip_filter = self.ip_filter().unwrap_or_default();
583 let peers_config = config
584 .peers_config_with_basic_nodes_from_file(
585 self.persistent_peers_file(peers_file).as_deref(),
586 )
587 .with_max_inbound_opt(self.resolved_max_inbound_peers())
588 .with_max_outbound_opt(self.resolved_max_outbound_peers())
589 .with_ip_filter(ip_filter)
590 .with_enforce_enr_fork_id(self.enforce_enr_fork_id);
591
592 NetworkConfigBuilder::<N>::new(secret_key, executor)
594 .external_ip_resolver(self.nat.clone())
595 .sessions_config(
596 config.sessions.clone().with_upscaled_event_buffer(peers_config.max_peers()),
597 )
598 .peer_config(peers_config)
599 .boot_nodes(chain_bootnodes.clone())
600 .transactions_manager_config(self.transactions_manager_config())
601 .apply(|builder| {
603 let peer_id = builder.get_peer_id();
604 builder.hello_message(
605 HelloMessageWithProtocols::builder(peer_id)
606 .client_version(&self.identity)
607 .build(),
608 )
609 })
610 .apply(|builder| {
612 let rlpx_socket = (listener_addr, self.port).into();
613 self.discovery.apply_to_builder(builder, rlpx_socket, chain_bootnodes)
614 })
615 .listener_addr(SocketAddr::new(listener_addr, self.port))
616 .discovery_addr(SocketAddr::new(discovery_addr, self.discovery.port))
617 .disable_tx_gossip(self.disable_tx_gossip)
618 .required_block_hashes(self.required_block_hashes.clone())
619 .eth_max_message_size_opt(self.eth_max_message_size.map(NonZeroUsize::get))
620 .network_id(self.network_id)
621 }
622
623 pub fn persistent_peers_file(&self, peers_file: PathBuf) -> Option<PathBuf> {
625 self.no_persist_peers.not().then_some(peers_file)
626 }
627
628 pub const fn with_discovery(mut self, discovery: DiscoveryArgs) -> Self {
630 self.discovery = discovery;
631 self
632 }
633
634 pub const fn with_unused_p2p_port(mut self) -> Self {
637 self.port = 0;
638 self
639 }
640
641 pub const fn with_unused_ports(mut self) -> Self {
644 self = self.with_unused_p2p_port();
645 self.discovery = self.discovery.with_unused_discovery_port();
646 self
647 }
648
649 pub fn with_nat_resolver(mut self, nat: NatResolver) -> Self {
651 self.nat = nat;
652 self
653 }
654
655 pub fn adjust_instance_ports(&mut self, instance: Option<u16>) {
661 if let Some(instance) = instance {
662 debug_assert_ne!(instance, 0, "instance must be non-zero");
663 self.port += instance - 1;
664 self.discovery.adjust_instance_ports(instance);
665 }
666 }
667
668 pub async fn resolve_trusted_peers(&self) -> Result<Vec<NodeRecord>, std::io::Error> {
670 futures::future::try_join_all(
671 self.trusted_peers.iter().map(|peer| async move { peer.resolve().await }),
672 )
673 .await
674 }
675
676 pub fn secret_key(
682 &self,
683 default_secret_key_path: PathBuf,
684 ) -> Result<SecretKey, SecretKeyError> {
685 if let Some(b256) = &self.p2p_secret_key_hex {
686 SecretKey::from_slice(b256.as_slice()).map_err(SecretKeyError::SecretKeyDecodeError)
688 } else {
689 let secret_key_path = self.p2p_secret_key.clone().unwrap_or(default_secret_key_path);
691 get_secret_key(&secret_key_path)
692 }
693 }
694
695 pub fn ip_filter(&self) -> Result<IpFilter, ipnet::AddrParseError> {
699 if let Some(netrestrict) = &self.netrestrict {
700 IpFilter::from_cidr_string(netrestrict)
701 } else {
702 Ok(IpFilter::allow_all())
703 }
704 }
705}
706
707impl Default for NetworkArgs {
708 fn default() -> Self {
709 let DefaultNetworkArgs {
710 dns_retries,
711 nat,
712 addr,
713 port,
714 max_concurrent_tx_requests,
715 max_concurrent_tx_requests_per_peer,
716 max_seen_tx_history,
717 max_pending_pool_imports,
718 soft_limit_byte_size_pooled_transactions_response,
719 soft_limit_byte_size_pooled_transactions_response_on_pack_request,
720 max_capacity_cache_txns_pending_fetch,
721 tx_channel_memory_limit_bytes,
722 tx_propagation_policy,
723 tx_ingress_policy,
724 propagation_mode,
725 enforce_enr_fork_id,
726 } = DefaultNetworkArgs::get_global().clone();
727 Self {
728 discovery: DiscoveryArgs::default(),
729 trusted_peers: vec![],
730 trusted_only: false,
731 bootnodes: None,
732 dns_retries,
733 peers_file: None,
734 identity: version_metadata().p2p_client_version.to_string(),
735 p2p_secret_key: None,
736 p2p_secret_key_hex: None,
737 no_persist_peers: false,
738 nat,
739 addr,
740 port,
741 max_outbound_peers: None,
742 max_inbound_peers: None,
743 max_peers: None,
744 max_concurrent_tx_requests,
745 max_concurrent_tx_requests_per_peer,
746 soft_limit_byte_size_pooled_transactions_response,
747 soft_limit_byte_size_pooled_transactions_response_on_pack_request,
748 max_pending_pool_imports,
749 max_seen_tx_history,
750 max_capacity_cache_txns_pending_fetch,
751 tx_channel_memory_limit_bytes,
752 net_if: None,
753 tx_propagation_policy,
754 tx_ingress_policy,
755 disable_tx_gossip: false,
756 propagation_mode,
757 required_block_hashes: vec![],
758 network_id: None,
759 eth_max_message_size: None,
760 netrestrict: None,
761 enforce_enr_fork_id,
762 }
763 }
764}
765
766static DISCOVERY_DEFAULTS: OnceLock<DefaultDiscoveryArgs> = OnceLock::new();
768
769#[derive(Debug, Clone, Copy)]
771pub struct DefaultDiscoveryArgs {
772 pub disable_discovery: bool,
774 pub disable_dns_discovery: bool,
776 pub disable_discv4_discovery: bool,
778 pub disable_discv5_discovery: bool,
780 pub disable_nat: bool,
782 pub addr: IpAddr,
784 pub port: u16,
786 pub discv5_addr: Option<Ipv4Addr>,
788 pub discv5_addr_ipv6: Option<Ipv6Addr>,
790 pub discv5_port: Option<u16>,
792 pub discv5_port_ipv6: Option<u16>,
794 pub discv5_lookup_interval: u64,
796 pub discv5_bootstrap_lookup_interval: u64,
798 pub discv5_bootstrap_lookup_countdown: u64,
800}
801
802impl DefaultDiscoveryArgs {
803 pub fn try_init(self) -> Result<(), Self> {
805 DISCOVERY_DEFAULTS.set(self)
806 }
807
808 pub fn get_global() -> &'static Self {
810 DISCOVERY_DEFAULTS.get_or_init(Self::default)
811 }
812
813 pub const fn with_disable_discovery(mut self, disable: bool) -> Self {
815 self.disable_discovery = disable;
816 self
817 }
818
819 pub const fn with_disable_dns_discovery(mut self, disable: bool) -> Self {
821 self.disable_dns_discovery = disable;
822 self
823 }
824
825 pub const fn with_disable_discv4_discovery(mut self, disable: bool) -> Self {
827 self.disable_discv4_discovery = disable;
828 self
829 }
830
831 pub const fn with_disable_discv5_discovery(mut self, disable: bool) -> Self {
833 self.disable_discv5_discovery = disable;
834 self
835 }
836
837 pub const fn with_disable_nat(mut self, disable: bool) -> Self {
839 self.disable_nat = disable;
840 self
841 }
842
843 pub const fn with_addr(mut self, addr: IpAddr) -> Self {
845 self.addr = addr;
846 self
847 }
848
849 pub const fn with_port(mut self, port: u16) -> Self {
851 self.port = port;
852 self
853 }
854
855 pub fn with_discv5_addr(mut self, addr: impl Into<Option<Ipv4Addr>>) -> Self {
857 self.discv5_addr = addr.into();
858 self
859 }
860
861 pub fn with_discv5_addr_ipv6(mut self, addr: impl Into<Option<Ipv6Addr>>) -> Self {
863 self.discv5_addr_ipv6 = addr.into();
864 self
865 }
866
867 pub fn with_discv5_port(mut self, port: impl Into<Option<u16>>) -> Self {
869 self.discv5_port = port.into();
870 self
871 }
872
873 pub fn with_discv5_port_ipv6(mut self, port: impl Into<Option<u16>>) -> Self {
875 self.discv5_port_ipv6 = port.into();
876 self
877 }
878
879 pub const fn with_discv5_lookup_interval(mut self, interval: u64) -> Self {
881 self.discv5_lookup_interval = interval;
882 self
883 }
884
885 pub const fn with_discv5_bootstrap_lookup_interval(mut self, interval: u64) -> Self {
887 self.discv5_bootstrap_lookup_interval = interval;
888 self
889 }
890
891 pub const fn with_discv5_bootstrap_lookup_countdown(mut self, countdown: u64) -> Self {
893 self.discv5_bootstrap_lookup_countdown = countdown;
894 self
895 }
896}
897
898impl Default for DefaultDiscoveryArgs {
899 fn default() -> Self {
900 Self {
901 disable_discovery: false,
902 disable_dns_discovery: false,
903 disable_discv4_discovery: false,
904 disable_discv5_discovery: false,
905 disable_nat: false,
906 addr: DEFAULT_DISCOVERY_ADDR,
907 port: DEFAULT_DISCOVERY_PORT,
908 discv5_addr: None,
909 discv5_addr_ipv6: None,
910 discv5_port: Some(DEFAULT_DISCOVERY_V5_PORT),
911 discv5_port_ipv6: Some(DEFAULT_DISCOVERY_V5_PORT),
912 discv5_lookup_interval: DEFAULT_SECONDS_LOOKUP_INTERVAL,
913 discv5_bootstrap_lookup_interval: DEFAULT_SECONDS_BOOTSTRAP_LOOKUP_INTERVAL,
914 discv5_bootstrap_lookup_countdown: DEFAULT_COUNT_BOOTSTRAP_LOOKUPS,
915 }
916 }
917}
918
919#[derive(Debug, Clone, Args, PartialEq, Eq)]
921pub struct DiscoveryArgs {
922 #[arg(short, long, default_value_if("dev", "true", "true"), default_value_t = DefaultDiscoveryArgs::get_global().disable_discovery)]
924 pub disable_discovery: bool,
925
926 #[arg(long, conflicts_with = "disable_discovery", default_value_t = DefaultDiscoveryArgs::get_global().disable_dns_discovery)]
928 pub disable_dns_discovery: bool,
929
930 #[arg(long, conflicts_with = "disable_discovery", default_value_t = DefaultDiscoveryArgs::get_global().disable_discv4_discovery)]
932 pub disable_discv4_discovery: bool,
933
934 #[arg(long, conflicts_with = "disable_discovery", hide = true)]
939 pub enable_discv5_discovery: bool,
940
941 #[arg(long, conflicts_with = "disable_discovery", default_value_t = DefaultDiscoveryArgs::get_global().disable_discv5_discovery)]
943 pub disable_discv5_discovery: bool,
944
945 #[arg(long, conflicts_with = "disable_discovery", default_value_t = DefaultDiscoveryArgs::get_global().disable_nat)]
947 pub disable_nat: bool,
948
949 #[arg(id = "discovery.addr", long = "discovery.addr", value_name = "DISCOVERY_ADDR", default_value_t = DefaultDiscoveryArgs::get_global().addr)]
954 pub addr: IpAddr,
955
956 #[arg(id = "discovery.port", long = "discovery.port", value_name = "DISCOVERY_PORT", default_value_t = DefaultDiscoveryArgs::get_global().port)]
958 pub port: u16,
959
960 #[arg(id = "discovery.v5.addr", long = "discovery.v5.addr", value_name = "DISCOVERY_V5_ADDR", default_value = Resettable::from(DefaultDiscoveryArgs::get_global().discv5_addr.map(|a| OsStr::from(a.to_string()))))]
963 pub discv5_addr: Option<Ipv4Addr>,
964
965 #[arg(id = "discovery.v5.addr.ipv6", long = "discovery.v5.addr.ipv6", value_name = "DISCOVERY_V5_ADDR_IPV6", default_value = Resettable::from(DefaultDiscoveryArgs::get_global().discv5_addr_ipv6.map(|a| OsStr::from(a.to_string()))))]
968 pub discv5_addr_ipv6: Option<Ipv6Addr>,
969
970 #[arg(id = "discovery.v5.port", long = "discovery.v5.port", value_name = "DISCOVERY_V5_PORT", default_value = Resettable::from(DefaultDiscoveryArgs::get_global().discv5_port.map(|p| OsStr::from(p.to_string()))))]
973 pub discv5_port: Option<u16>,
974
975 #[arg(id = "discovery.v5.port.ipv6", long = "discovery.v5.port.ipv6", value_name = "DISCOVERY_V5_PORT_IPV6", default_value = Resettable::from(DefaultDiscoveryArgs::get_global().discv5_port_ipv6.map(|p| OsStr::from(p.to_string()))))]
980 pub discv5_port_ipv6: Option<u16>,
981
982 #[arg(id = "discovery.v5.lookup-interval", long = "discovery.v5.lookup-interval", value_name = "DISCOVERY_V5_LOOKUP_INTERVAL", default_value_t = DefaultDiscoveryArgs::get_global().discv5_lookup_interval)]
985 pub discv5_lookup_interval: u64,
986
987 #[arg(id = "discovery.v5.bootstrap.lookup-interval", long = "discovery.v5.bootstrap.lookup-interval", value_name = "DISCOVERY_V5_BOOTSTRAP_LOOKUP_INTERVAL",
990 default_value_t = DefaultDiscoveryArgs::get_global().discv5_bootstrap_lookup_interval)]
991 pub discv5_bootstrap_lookup_interval: u64,
992
993 #[arg(id = "discovery.v5.bootstrap.lookup-countdown", long = "discovery.v5.bootstrap.lookup-countdown", value_name = "DISCOVERY_V5_BOOTSTRAP_LOOKUP_COUNTDOWN",
995 default_value_t = DefaultDiscoveryArgs::get_global().discv5_bootstrap_lookup_countdown)]
996 pub discv5_bootstrap_lookup_countdown: u64,
997}
998
999impl DiscoveryArgs {
1000 pub fn apply_to_builder<N>(
1002 &self,
1003 mut network_config_builder: NetworkConfigBuilder<N>,
1004 rlpx_tcp_socket: SocketAddr,
1005 boot_nodes: impl IntoIterator<Item = NodeRecord>,
1006 ) -> NetworkConfigBuilder<N>
1007 where
1008 N: NetworkPrimitives,
1009 {
1010 if self.disable_discovery || self.disable_dns_discovery {
1011 network_config_builder = network_config_builder.disable_dns_discovery();
1012 }
1013
1014 if self.disable_discovery || self.disable_discv4_discovery {
1015 network_config_builder = network_config_builder.disable_discv4_discovery();
1016 }
1017
1018 if self.disable_nat {
1019 network_config_builder = network_config_builder.disable_nat();
1021 }
1022
1023 if self.should_enable_discv5() {
1024 network_config_builder = network_config_builder
1025 .discovery_v5(self.discovery_v5_builder(rlpx_tcp_socket, boot_nodes));
1026 }
1027
1028 network_config_builder
1029 }
1030
1031 pub fn discovery_v5_builder(
1033 &self,
1034 rlpx_tcp_socket: SocketAddr,
1035 boot_nodes: impl IntoIterator<Item = NodeRecord>,
1036 ) -> reth_discv5::ConfigBuilder {
1037 let Self {
1038 discv5_addr,
1039 discv5_addr_ipv6,
1040 discv5_port,
1041 discv5_port_ipv6,
1042 discv5_lookup_interval,
1043 discv5_bootstrap_lookup_interval,
1044 discv5_bootstrap_lookup_countdown,
1045 port,
1046 ..
1047 } = self;
1048
1049 let has_discv5_addr_args = discv5_addr.is_some() || discv5_addr_ipv6.is_some();
1050
1051 let discv5_addr_ipv4 = discv5_addr.or(match rlpx_tcp_socket {
1053 SocketAddr::V4(addr) => Some(*addr.ip()),
1054 SocketAddr::V6(_) => None,
1055 });
1056 let discv5_addr_ipv6 = discv5_addr_ipv6.or(match rlpx_tcp_socket {
1057 SocketAddr::V4(_) => None,
1058 SocketAddr::V6(addr) => Some(*addr.ip()),
1059 });
1060
1061 let mut discv5_config_builder =
1062 reth_discv5::discv5::ConfigBuilder::new(ListenConfig::from_two_sockets(
1063 discv5_addr_ipv4.map(|addr| SocketAddrV4::new(addr, discv5_port.unwrap_or(*port))),
1064 discv5_addr_ipv6
1065 .map(|addr| SocketAddrV6::new(addr, discv5_port_ipv6.unwrap_or(*port), 0, 0)),
1066 ));
1067
1068 if has_discv5_addr_args || self.disable_nat {
1069 discv5_config_builder.disable_enr_update();
1071 }
1072 reth_discv5::Config::builder(rlpx_tcp_socket)
1073 .discv5_config(discv5_config_builder.build())
1074 .add_unsigned_boot_nodes(boot_nodes)
1075 .lookup_interval(*discv5_lookup_interval)
1076 .bootstrap_lookup_interval(*discv5_bootstrap_lookup_interval)
1077 .bootstrap_lookup_countdown(*discv5_bootstrap_lookup_countdown)
1078 }
1079
1080 const fn should_enable_discv5(&self) -> bool {
1084 if self.disable_discovery || self.disable_discv5_discovery {
1085 return false;
1086 }
1087
1088 true
1089 }
1090
1091 pub const fn with_unused_discovery_port(mut self) -> Self {
1094 self.port = 0;
1095 self.discv5_port = Some(0);
1096 self.discv5_port_ipv6 = Some(0);
1097 self
1098 }
1099
1100 pub fn with_discv5_port(mut self, port: impl Into<Option<u16>>) -> Self {
1102 self.discv5_port = port.into();
1103 self
1104 }
1105
1106 pub fn adjust_instance_ports(&mut self, instance: u16) {
1112 debug_assert_ne!(instance, 0, "instance must be non-zero");
1113 self.port += instance - 1;
1114 self.discv5_port = self.discv5_port.map(|port| port + instance - 1);
1115 self.discv5_port_ipv6 = self.discv5_port_ipv6.map(|port| port + instance - 1);
1116 }
1117}
1118
1119impl Default for DiscoveryArgs {
1120 fn default() -> Self {
1121 let DefaultDiscoveryArgs {
1122 disable_discovery,
1123 disable_dns_discovery,
1124 disable_discv4_discovery,
1125 disable_discv5_discovery,
1126 disable_nat,
1127 addr,
1128 port,
1129 discv5_addr,
1130 discv5_addr_ipv6,
1131 discv5_port,
1132 discv5_port_ipv6,
1133 discv5_lookup_interval,
1134 discv5_bootstrap_lookup_interval,
1135 discv5_bootstrap_lookup_countdown,
1136 } = *DefaultDiscoveryArgs::get_global();
1137 Self {
1138 disable_discovery,
1139 disable_dns_discovery,
1140 disable_discv4_discovery,
1141 enable_discv5_discovery: false,
1142 disable_discv5_discovery,
1143 disable_nat,
1144 addr,
1145 port,
1146 discv5_addr,
1147 discv5_addr_ipv6,
1148 discv5_port,
1149 discv5_port_ipv6,
1150 discv5_lookup_interval,
1151 discv5_bootstrap_lookup_interval,
1152 discv5_bootstrap_lookup_countdown,
1153 }
1154 }
1155}
1156
1157fn parse_block_num_hash(s: &str) -> Result<BlockNumHash, String> {
1159 if let Some((num_str, hash_str)) = s.split_once('=') {
1160 let number = num_str.parse().map_err(|_| format!("Invalid block number: {}", num_str))?;
1161 let hash = B256::from_str(hash_str).map_err(|_| format!("Invalid hash: {}", hash_str))?;
1162 Ok(BlockNumHash::new(number, hash))
1163 } else {
1164 let hash = B256::from_str(s).map_err(|_| format!("Invalid hash: {}", s))?;
1166 Ok(BlockNumHash::new(0, hash))
1167 }
1168}
1169
1170#[cfg(test)]
1171mod tests {
1172 use super::*;
1173 use clap::Parser;
1174 use reth_chainspec::MAINNET;
1175 use reth_config::Config;
1176 use reth_network_peers::NodeRecord;
1177 use secp256k1::SecretKey;
1178 use std::{
1179 fs,
1180 time::{SystemTime, UNIX_EPOCH},
1181 };
1182
1183 #[derive(Parser)]
1185 struct CommandParser<T: Args> {
1186 #[command(flatten)]
1187 args: T,
1188 }
1189
1190 #[test]
1191 fn parse_nat_args() {
1192 let args = CommandParser::<NetworkArgs>::parse_from(["reth", "--nat", "none"]).args;
1193 assert_eq!(args.nat, NatResolver::None);
1194
1195 let args =
1196 CommandParser::<NetworkArgs>::parse_from(["reth", "--nat", "extip:0.0.0.0"]).args;
1197 assert_eq!(args.nat, NatResolver::ExternalIp("0.0.0.0".parse().unwrap()));
1198 }
1199
1200 #[test]
1201 fn parse_peer_args() {
1202 let args =
1203 CommandParser::<NetworkArgs>::parse_from(["reth", "--max-outbound-peers", "50"]).args;
1204 assert_eq!(args.max_outbound_peers, Some(50));
1205 assert_eq!(args.max_inbound_peers, None);
1206
1207 let args = CommandParser::<NetworkArgs>::parse_from([
1208 "reth",
1209 "--max-outbound-peers",
1210 "75",
1211 "--max-inbound-peers",
1212 "15",
1213 ])
1214 .args;
1215 assert_eq!(args.max_outbound_peers, Some(75));
1216 assert_eq!(args.max_inbound_peers, Some(15));
1217 }
1218
1219 #[test]
1220 fn parse_trusted_peer_args() {
1221 let args =
1222 CommandParser::<NetworkArgs>::parse_from([
1223 "reth",
1224 "--trusted-peers",
1225 "enode://d860a01f9722d78051619d1e2351aba3f43f943f6f00718d1b9baa4101932a1f5011f16bb2b1bb35db20d6fe28fa0bf09636d26a87d31de9ec6203eeedb1f666@18.138.108.67:30303,enode://22a8232c3abc76a16ae9d6c3b164f98775fe226f0917b0ca871128a74a8e9630b458460865bab457221f1d448dd9791d24c4e5d88786180ac185df813a68d4de@3.209.45.79:30303"
1226 ])
1227 .args;
1228
1229 assert_eq!(
1230 args.trusted_peers,
1231 vec![
1232 "enode://d860a01f9722d78051619d1e2351aba3f43f943f6f00718d1b9baa4101932a1f5011f16bb2b1bb35db20d6fe28fa0bf09636d26a87d31de9ec6203eeedb1f666@18.138.108.67:30303".parse().unwrap(),
1233 "enode://22a8232c3abc76a16ae9d6c3b164f98775fe226f0917b0ca871128a74a8e9630b458460865bab457221f1d448dd9791d24c4e5d88786180ac185df813a68d4de@3.209.45.79:30303".parse().unwrap()
1234 ]
1235 );
1236 }
1237
1238 #[test]
1239 fn parse_enr_bootnode_args() {
1240 let enr = "enr:-IS4QHCYrYZbAKWCBRlAy5zzaDZXJBGkcnh4MHcBFZntXNFrdvJjX04jRzjzCBOonrkTfj499SZuOh8R33Ls8RRcy5wBgmlkgnY0gmlwhH8AAAGJc2VjcDI1NmsxoQPKY0yuDUmstAHYpMa2_oxVtw0RW_QAdpzBQA8yWM0xOIN1ZHCCdl8";
1241 let args = CommandParser::<NetworkArgs>::parse_from(["reth", "--bootnodes", enr]).args;
1242 let trusted =
1243 CommandParser::<NetworkArgs>::parse_from(["reth", "--trusted-peers", enr]).args;
1244 assert_eq!(args.bootnodes, Some(trusted.trusted_peers));
1245 }
1246
1247 #[test]
1248 fn parse_retry_strategy_args() {
1249 let tests = vec![0, 10];
1250
1251 for retries in tests {
1252 let retries_str = retries.to_string();
1253 let args = CommandParser::<NetworkArgs>::parse_from([
1254 "reth",
1255 "--dns-retries",
1256 retries_str.as_str(),
1257 ])
1258 .args;
1259
1260 assert_eq!(args.dns_retries, retries);
1261 }
1262 }
1263
1264 #[test]
1265 fn transactions_manager_config_uses_max_pending_imports() {
1266 let args = NetworkArgs { max_pending_pool_imports: 50_000, ..Default::default() };
1267
1268 let config = args.transactions_manager_config();
1269
1270 assert_eq!(config.max_pending_pool_imports, 50_000);
1271 }
1272
1273 #[test]
1274 fn parse_disable_tx_gossip_args() {
1275 let args = CommandParser::<NetworkArgs>::parse_from(["reth", "--disable-tx-gossip"]).args;
1276 assert!(args.disable_tx_gossip);
1277 }
1278
1279 #[test]
1280 fn parse_max_peers_flag() {
1281 let args = CommandParser::<NetworkArgs>::parse_from(["reth", "--max-peers", "90"]).args;
1282
1283 assert_eq!(args.max_peers, Some(90));
1284 assert_eq!(args.max_outbound_peers, None);
1285 assert_eq!(args.max_inbound_peers, None);
1286 assert_eq!(args.resolved_max_outbound_peers(), Some(30));
1287 assert_eq!(args.resolved_max_inbound_peers(), Some(60));
1288 }
1289
1290 #[test]
1291 fn max_peers_conflicts_with_outbound() {
1292 let result = CommandParser::<NetworkArgs>::try_parse_from([
1293 "reth",
1294 "--max-peers",
1295 "90",
1296 "--max-outbound-peers",
1297 "50",
1298 ]);
1299 assert!(
1300 result.is_err(),
1301 "Should fail when both --max-peers and --max-outbound-peers are used"
1302 );
1303 }
1304
1305 #[test]
1306 fn max_peers_conflicts_with_inbound() {
1307 let result = CommandParser::<NetworkArgs>::try_parse_from([
1308 "reth",
1309 "--max-peers",
1310 "90",
1311 "--max-inbound-peers",
1312 "30",
1313 ]);
1314 assert!(
1315 result.is_err(),
1316 "Should fail when both --max-peers and --max-inbound-peers are used"
1317 );
1318 }
1319
1320 #[test]
1321 fn max_peers_split_calculation() {
1322 let args = CommandParser::<NetworkArgs>::parse_from(["reth", "--max-peers", "90"]).args;
1323
1324 assert_eq!(args.max_peers, Some(90));
1325 assert_eq!(args.resolved_max_outbound_peers(), Some(30));
1326 assert_eq!(args.resolved_max_inbound_peers(), Some(60));
1327 }
1328
1329 #[test]
1330 fn max_peers_small_values() {
1331 let args1 = CommandParser::<NetworkArgs>::parse_from(["reth", "--max-peers", "1"]).args;
1332 assert_eq!(args1.resolved_max_outbound_peers(), Some(1));
1333 assert_eq!(args1.resolved_max_inbound_peers(), Some(0));
1334
1335 let args2 = CommandParser::<NetworkArgs>::parse_from(["reth", "--max-peers", "2"]).args;
1336 assert_eq!(args2.resolved_max_outbound_peers(), Some(1));
1337 assert_eq!(args2.resolved_max_inbound_peers(), Some(1));
1338
1339 let args3 = CommandParser::<NetworkArgs>::parse_from(["reth", "--max-peers", "3"]).args;
1340 assert_eq!(args3.resolved_max_outbound_peers(), Some(1));
1341 assert_eq!(args3.resolved_max_inbound_peers(), Some(2));
1342 }
1343
1344 #[test]
1345 fn resolved_peers_without_max_peers() {
1346 let args = CommandParser::<NetworkArgs>::parse_from([
1347 "reth",
1348 "--max-outbound-peers",
1349 "75",
1350 "--max-inbound-peers",
1351 "15",
1352 ])
1353 .args;
1354
1355 assert_eq!(args.max_peers, None);
1356 assert_eq!(args.resolved_max_outbound_peers(), Some(75));
1357 assert_eq!(args.resolved_max_inbound_peers(), Some(15));
1358 }
1359
1360 #[test]
1361 fn resolved_peers_with_defaults() {
1362 let args = CommandParser::<NetworkArgs>::parse_from(["reth"]).args;
1363
1364 assert_eq!(args.max_peers, None);
1365 assert_eq!(args.resolved_max_outbound_peers(), None);
1366 assert_eq!(args.resolved_max_inbound_peers(), None);
1367 }
1368
1369 #[test]
1370 fn network_args_default_sanity_test() {
1371 let default_args = NetworkArgs::default();
1372 let args = CommandParser::<NetworkArgs>::parse_from(["reth"]).args;
1373
1374 assert_eq!(args, default_args);
1375 }
1376
1377 #[test]
1378 fn net_if_uses_resolved_addr_for_default_discovery_addr() {
1379 let args =
1380 CommandParser::<NetworkArgs>::parse_from(["reth", "--net-if.experimental", "en0"]).args;
1381 let listener_addr = IpAddr::V4(Ipv4Addr::new(192, 0, 2, 1));
1382
1383 assert_eq!(args.resolved_discovery_addr(listener_addr), listener_addr);
1384 }
1385
1386 #[test]
1387 fn net_if_preserves_custom_discovery_addr() {
1388 let custom_discovery_addr = IpAddr::V4(Ipv4Addr::new(192, 0, 2, 2));
1389 let args = CommandParser::<NetworkArgs>::parse_from([
1390 "reth",
1391 "--net-if.experimental",
1392 "en0",
1393 "--discovery.addr",
1394 "192.0.2.2",
1395 ])
1396 .args;
1397 let listener_addr = IpAddr::V4(Ipv4Addr::new(192, 0, 2, 1));
1398
1399 assert_eq!(args.resolved_discovery_addr(listener_addr), custom_discovery_addr);
1400 }
1401
1402 #[test]
1403 fn default_discovery_addr_is_preserved_without_net_if() {
1404 let args = CommandParser::<NetworkArgs>::parse_from(["reth"]).args;
1405 let listener_addr = IpAddr::V4(Ipv4Addr::new(192, 0, 2, 1));
1406
1407 assert_eq!(args.resolved_discovery_addr(listener_addr), DEFAULT_DISCOVERY_ADDR);
1408 }
1409
1410 #[test]
1411 fn parse_eth_max_message_size() {
1412 let args = CommandParser::<NetworkArgs>::parse_from([
1413 "reth",
1414 "--eth-max-message-size",
1415 "15728640",
1416 ])
1417 .args;
1418
1419 assert_eq!(args.eth_max_message_size, Some(NonZeroUsize::new(15 * 1024 * 1024).unwrap()));
1420 }
1421
1422 #[test]
1423 fn parse_eth_max_message_size_zero_rejected() {
1424 let result =
1425 CommandParser::<NetworkArgs>::try_parse_from(["reth", "--eth-max-message-size", "0"]);
1426 assert!(result.is_err());
1427 }
1428
1429 #[test]
1430 fn parse_eth_max_message_size_above_rlpx_cap() {
1431 let result = CommandParser::<NetworkArgs>::try_parse_from([
1432 "reth",
1433 "--eth-max-message-size",
1434 "16777216",
1435 ]);
1436 assert!(result.is_ok());
1437 let args = result.unwrap().args;
1438 assert_eq!(args.eth_max_message_size, Some(NonZeroUsize::new(16 * 1024 * 1024).unwrap()));
1439 }
1440
1441 #[test]
1442 fn parse_required_block_hashes() {
1443 let args = CommandParser::<NetworkArgs>::parse_from([
1444 "reth",
1445 "--required-block-hashes",
1446 "0x1111111111111111111111111111111111111111111111111111111111111111,23115201=0x2222222222222222222222222222222222222222222222222222222222222222",
1447 ])
1448 .args;
1449
1450 assert_eq!(args.required_block_hashes.len(), 2);
1451 assert_eq!(args.required_block_hashes[0].number, 0);
1453 assert_eq!(
1454 args.required_block_hashes[0].hash.to_string(),
1455 "0x1111111111111111111111111111111111111111111111111111111111111111"
1456 );
1457 assert_eq!(args.required_block_hashes[1].number, 23115201);
1459 assert_eq!(
1460 args.required_block_hashes[1].hash.to_string(),
1461 "0x2222222222222222222222222222222222222222222222222222222222222222"
1462 );
1463 }
1464
1465 #[test]
1466 fn parse_empty_required_block_hashes() {
1467 let args = CommandParser::<NetworkArgs>::parse_from(["reth"]).args;
1468 assert!(args.required_block_hashes.is_empty());
1469 }
1470
1471 #[test]
1472 fn test_parse_block_num_hash() {
1473 let result = parse_block_num_hash(
1475 "0x1111111111111111111111111111111111111111111111111111111111111111",
1476 );
1477 assert!(result.is_ok());
1478 assert_eq!(result.unwrap().number, 0);
1479
1480 let result = parse_block_num_hash(
1482 "23115201=0x2222222222222222222222222222222222222222222222222222222222222222",
1483 );
1484 assert!(result.is_ok());
1485 assert_eq!(result.unwrap().number, 23115201);
1486
1487 assert!(parse_block_num_hash("invalid").is_err());
1489 assert!(parse_block_num_hash(
1490 "abc=0x1111111111111111111111111111111111111111111111111111111111111111"
1491 )
1492 .is_err());
1493 }
1494
1495 #[test]
1496 fn parse_p2p_secret_key_hex() {
1497 let hex = "4c0883a69102937d6231471b5dbb6204fe512961708279f8c5c58b3b9c4e8b8f";
1498 let args =
1499 CommandParser::<NetworkArgs>::parse_from(["reth", "--p2p-secret-key-hex", hex]).args;
1500
1501 let expected: B256 = hex.parse().unwrap();
1502 assert_eq!(args.p2p_secret_key_hex, Some(expected));
1503 assert_eq!(args.p2p_secret_key, None);
1504 }
1505
1506 #[test]
1507 fn parse_p2p_secret_key_hex_with_0x_prefix() {
1508 let hex = "0x4c0883a69102937d6231471b5dbb6204fe512961708279f8c5c58b3b9c4e8b8f";
1509 let args =
1510 CommandParser::<NetworkArgs>::parse_from(["reth", "--p2p-secret-key-hex", hex]).args;
1511
1512 let expected: B256 = hex.parse().unwrap();
1513 assert_eq!(args.p2p_secret_key_hex, Some(expected));
1514 assert_eq!(args.p2p_secret_key, None);
1515 }
1516
1517 #[test]
1518 fn test_p2p_secret_key_and_hex_are_mutually_exclusive() {
1519 let result = CommandParser::<NetworkArgs>::try_parse_from([
1520 "reth",
1521 "--p2p-secret-key",
1522 "/path/to/key",
1523 "--p2p-secret-key-hex",
1524 "4c0883a69102937d6231471b5dbb6204fe512961708279f8c5c58b3b9c4e8b8f",
1525 ]);
1526
1527 assert!(result.is_err());
1528 }
1529
1530 #[test]
1531 fn test_secret_key_method_with_hex() {
1532 let hex = "4c0883a69102937d6231471b5dbb6204fe512961708279f8c5c58b3b9c4e8b8f";
1533 let args =
1534 CommandParser::<NetworkArgs>::parse_from(["reth", "--p2p-secret-key-hex", hex]).args;
1535
1536 let temp_dir = std::env::temp_dir();
1537 let default_path = temp_dir.join("default_key");
1538 let secret_key = args.secret_key(default_path).unwrap();
1539
1540 assert_eq!(alloy_primitives::hex::encode(secret_key.secret_bytes()), hex);
1542 }
1543
1544 #[test]
1545 fn parse_netrestrict_single_network() {
1546 let args =
1547 CommandParser::<NetworkArgs>::parse_from(["reth", "--netrestrict", "192.168.0.0/16"])
1548 .args;
1549
1550 assert_eq!(args.netrestrict, Some("192.168.0.0/16".to_string()));
1551
1552 let ip_filter = args.ip_filter().unwrap();
1553 assert!(ip_filter.has_restrictions());
1554 assert!(ip_filter.is_allowed(&"192.168.1.1".parse().unwrap()));
1555 assert!(!ip_filter.is_allowed(&"10.0.0.1".parse().unwrap()));
1556 }
1557
1558 #[test]
1559 fn parse_netrestrict_multiple_networks() {
1560 let args = CommandParser::<NetworkArgs>::parse_from([
1561 "reth",
1562 "--netrestrict",
1563 "192.168.0.0/16,10.0.0.0/8",
1564 ])
1565 .args;
1566
1567 assert_eq!(args.netrestrict, Some("192.168.0.0/16,10.0.0.0/8".to_string()));
1568
1569 let ip_filter = args.ip_filter().unwrap();
1570 assert!(ip_filter.has_restrictions());
1571 assert!(ip_filter.is_allowed(&"192.168.1.1".parse().unwrap()));
1572 assert!(ip_filter.is_allowed(&"10.5.10.20".parse().unwrap()));
1573 assert!(!ip_filter.is_allowed(&"172.16.0.1".parse().unwrap()));
1574 }
1575
1576 #[test]
1577 fn parse_netrestrict_ipv6() {
1578 let args =
1579 CommandParser::<NetworkArgs>::parse_from(["reth", "--netrestrict", "2001:db8::/32"])
1580 .args;
1581
1582 let ip_filter = args.ip_filter().unwrap();
1583 assert!(ip_filter.has_restrictions());
1584 assert!(ip_filter.is_allowed(&"2001:db8::1".parse().unwrap()));
1585 assert!(!ip_filter.is_allowed(&"2001:db9::1".parse().unwrap()));
1586 }
1587
1588 #[test]
1589 fn netrestrict_not_set() {
1590 let args = CommandParser::<NetworkArgs>::parse_from(["reth"]).args;
1591 assert_eq!(args.netrestrict, None);
1592
1593 let ip_filter = args.ip_filter().unwrap();
1594 assert!(!ip_filter.has_restrictions());
1595 assert!(ip_filter.is_allowed(&"192.168.1.1".parse().unwrap()));
1596 assert!(ip_filter.is_allowed(&"10.0.0.1".parse().unwrap()));
1597 }
1598
1599 #[test]
1600 fn netrestrict_invalid_cidr() {
1601 let args =
1602 CommandParser::<NetworkArgs>::parse_from(["reth", "--netrestrict", "invalid-cidr"])
1603 .args;
1604
1605 assert!(args.ip_filter().is_err());
1606 }
1607
1608 #[test]
1609 fn network_config_preserves_basic_nodes_from_peers_file() {
1610 let enode = "enode://6f8a80d14311c39f35f516fa664deaaaa13e85b2f7493f37f6144d86991ec012937307647bd3b9a82abe2974e1407241d54947bbb39763a4cac9f77166ad92a0@10.3.58.6:30303?discport=30301";
1611 let unique = SystemTime::now().duration_since(UNIX_EPOCH).unwrap().as_nanos();
1612
1613 let peers_file = std::env::temp_dir().join(format!("reth_peers_test_{}.json", unique));
1614 fs::write(&peers_file, format!("[\"{}\"]", enode)).expect("write peers file");
1615
1616 let args = NetworkArgs {
1618 peers_file: Some(peers_file.clone()),
1619 no_persist_peers: false,
1620 ..Default::default()
1621 };
1622
1623 let secret_key = SecretKey::from_byte_array(&[1u8; 32]).unwrap();
1625 let builder = args.network_config::<reth_network::EthNetworkPrimitives>(
1626 &Config::default(),
1627 MAINNET.clone(),
1628 secret_key,
1629 peers_file.clone(),
1630 Runtime::test(),
1631 );
1632
1633 let net_cfg = builder.build_with_noop_provider(MAINNET.clone());
1634
1635 let node: NodeRecord = enode.parse().unwrap();
1637 assert!(net_cfg.peers_config.persisted_peers.iter().any(|p| p.record == node));
1638
1639 let _ = fs::remove_file(&peers_file);
1641 }
1642
1643 #[test]
1644 fn network_config_prefers_cli_bootnodes_over_config_file() {
1645 let enr = "enr:-IS4QHCYrYZbAKWCBRlAy5zzaDZXJBGkcnh4MHcBFZntXNFrdvJjX04jRzjzCBOonrkTfj499SZuOh8R33Ls8RRcy5wBgmlkgnY0gmlwhH8AAAGJc2VjcDI1NmsxoQPKY0yuDUmstAHYpMa2_oxVtw0RW_QAdpzBQA8yWM0xOIN1ZHCCdl8";
1646 let enode = "enode://6f8a80d14311c39f35f516fa664deaaaa13e85b2f7493f37f6144d86991ec012937307647bd3b9a82abe2974e1407241d54947bbb39763a4cac9f77166ad92a0@10.3.58.6:30303";
1647 let config = Config { bootnodes: vec![enr.parse().unwrap()], ..Default::default() };
1648 let secret_key = SecretKey::from_byte_array(&[1u8; 32]).unwrap();
1649
1650 let boot_nodes = |args: &NetworkArgs| {
1651 args.network_config::<reth_network::EthNetworkPrimitives>(
1652 &config,
1653 MAINNET.clone(),
1654 secret_key,
1655 PathBuf::from("peers.json"),
1656 Runtime::test(),
1657 )
1658 .boot_nodes_iter()
1659 .cloned()
1660 .collect::<Vec<_>>()
1661 };
1662
1663 let args = NetworkArgs { no_persist_peers: true, ..Default::default() };
1664 assert_eq!(boot_nodes(&args), vec![enr.parse::<TrustedPeer>().unwrap()]);
1665
1666 let args = NetworkArgs { bootnodes: Some(vec![enode.parse().unwrap()]), ..args };
1667 assert_eq!(boot_nodes(&args), vec![enode.parse::<TrustedPeer>().unwrap()]);
1668 }
1669}