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 #[arg(long = "snap.v2")]
247 pub snap_v2: bool,
248
249 #[expect(clippy::doc_markdown)]
250 #[arg(long, value_delimiter = ',')]
254 pub trusted_peers: Vec<TrustedPeer>,
255
256 #[arg(long)]
258 pub trusted_only: bool,
259
260 #[arg(long, value_delimiter = ',')]
264 pub bootnodes: Option<Vec<TrustedPeer>>,
265
266 #[arg(long, default_value_t = DefaultNetworkArgs::get_global().dns_retries)]
268 pub dns_retries: usize,
269
270 #[arg(long, value_name = "FILE", verbatim_doc_comment, conflicts_with = "no_persist_peers")]
273 pub peers_file: Option<PathBuf>,
274
275 #[arg(long, value_name = "IDENTITY", default_value = version_metadata().p2p_client_version.as_ref())]
277 pub identity: String,
278
279 #[arg(long, value_name = "PATH", conflicts_with = "p2p_secret_key_hex")]
284 pub p2p_secret_key: Option<PathBuf>,
285
286 #[arg(long, value_name = "HEX", conflicts_with = "p2p_secret_key")]
291 pub p2p_secret_key_hex: Option<B256>,
292
293 #[arg(long, verbatim_doc_comment)]
295 pub no_persist_peers: bool,
296
297 #[arg(long, default_value_t = DefaultNetworkArgs::get_global().nat.clone())]
299 pub nat: NatResolver,
300
301 #[arg(long = "addr", value_name = "ADDR", default_value_t = DefaultNetworkArgs::get_global().addr)]
303 pub addr: IpAddr,
304
305 #[arg(long = "port", value_name = "PORT", default_value_t = DefaultNetworkArgs::get_global().port)]
307 pub port: u16,
308
309 #[arg(long)]
311 pub max_outbound_peers: Option<usize>,
312
313 #[arg(long)]
315 pub max_inbound_peers: Option<usize>,
316
317 #[arg(
322 long,
323 value_name = "COUNT",
324 conflicts_with = "max_outbound_peers",
325 conflicts_with = "max_inbound_peers"
326 )]
327 pub max_peers: Option<usize>,
328
329 #[arg(long = "max-tx-reqs", value_name = "COUNT", default_value_t = DefaultNetworkArgs::get_global().max_concurrent_tx_requests, verbatim_doc_comment)]
331 pub max_concurrent_tx_requests: u32,
332
333 #[arg(long = "max-tx-reqs-peer", value_name = "COUNT", default_value_t = DefaultNetworkArgs::get_global().max_concurrent_tx_requests_per_peer, verbatim_doc_comment)]
335 pub max_concurrent_tx_requests_per_peer: u8,
336
337 #[arg(long = "max-seen-tx-history", value_name = "COUNT", default_value_t = DefaultNetworkArgs::get_global().max_seen_tx_history, verbatim_doc_comment)]
341 pub max_seen_tx_history: u32,
342
343 #[arg(long = "max-pending-imports", value_name = "COUNT", default_value_t = DefaultNetworkArgs::get_global().max_pending_pool_imports, verbatim_doc_comment)]
344 pub max_pending_pool_imports: usize,
346
347 #[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)]
351 pub soft_limit_byte_size_pooled_transactions_response: usize,
352
353 #[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)]
365 pub soft_limit_byte_size_pooled_transactions_response_on_pack_request: usize,
366
367 #[arg(long = "max-tx-pending-fetch", value_name = "COUNT", default_value_t = DefaultNetworkArgs::get_global().max_capacity_cache_txns_pending_fetch, verbatim_doc_comment)]
369 pub max_capacity_cache_txns_pending_fetch: u32,
370
371 #[arg(long = "tx-channel-memory-limit", value_name = "BYTES", default_value_t = DefaultNetworkArgs::get_global().tx_channel_memory_limit_bytes, verbatim_doc_comment)]
378 pub tx_channel_memory_limit_bytes: usize,
379
380 #[arg(long = "net-if.experimental", conflicts_with = "addr", value_name = "IF_NAME")]
386 pub net_if: Option<String>,
387
388 #[arg(long = "tx-propagation-policy", default_value_t = DefaultNetworkArgs::get_global().tx_propagation_policy)]
392 pub tx_propagation_policy: TransactionPropagationKind,
393
394 #[arg(long = "tx-ingress-policy", default_value_t = DefaultNetworkArgs::get_global().tx_ingress_policy)]
398 pub tx_ingress_policy: TransactionIngressPolicy,
399
400 #[arg(long = "disable-tx-gossip")]
405 pub disable_tx_gossip: bool,
406
407 #[arg(
412 long = "tx-propagation-mode",
413 default_value_t = DefaultNetworkArgs::get_global().propagation_mode,
414 help = "Transaction propagation mode (sqrt, all, max:<number>)"
415 )]
416 pub propagation_mode: TransactionPropagationMode,
417
418 #[arg(long = "required-block-hashes", value_delimiter = ',', value_parser = parse_block_num_hash)]
422 pub required_block_hashes: Vec<BlockNumHash>,
423
424 #[arg(long)]
426 pub network_id: Option<u64>,
427
428 #[arg(long = "eth-max-message-size", value_name = "BYTES")]
430 pub eth_max_message_size: Option<NonZeroUsize>,
431
432 #[arg(long, value_name = "NETRESTRICT")]
439 pub netrestrict: Option<String>,
440
441 #[arg(long, default_value_t = DefaultNetworkArgs::get_global().enforce_enr_fork_id)]
447 pub enforce_enr_fork_id: bool,
448}
449
450impl NetworkArgs {
451 pub fn resolved_addr(&self) -> IpAddr {
459 if let Some(ref if_name) = self.net_if {
460 let if_name = if if_name.is_empty() { DEFAULT_NET_IF_NAME } else { if_name };
461 return match reth_net_nat::net_if::resolve_net_if_ip(if_name) {
462 Ok(addr) => addr,
463 Err(err) => {
464 error!(target: "reth::cli",
465 if_name,
466 %err,
467 "Failed to read network interface IP"
468 );
469
470 DEFAULT_DISCOVERY_ADDR
471 }
472 };
473 }
474
475 self.addr
476 }
477
478 fn resolved_discovery_addr(&self, listener_addr: IpAddr) -> IpAddr {
486 if self.net_if.is_some() && self.discovery.addr == DEFAULT_DISCOVERY_ADDR {
487 return listener_addr;
488 }
489
490 self.discovery.addr
491 }
492
493 pub fn resolved_bootnodes(&self) -> Option<Vec<NodeRecord>> {
495 self.bootnodes.as_deref().map(|bootnodes| {
496 bootnodes.iter().filter_map(|node| node.resolve_blocking().ok()).collect()
497 })
498 }
499
500 pub fn resolved_max_inbound_peers(&self) -> Option<usize> {
502 if let Some(max_peers) = self.max_peers {
503 if max_peers == 0 {
504 Some(0)
505 } else {
506 let outbound = (max_peers / 3).max(1);
507 Some(max_peers.saturating_sub(outbound))
508 }
509 } else {
510 self.max_inbound_peers
511 }
512 }
513
514 pub fn resolved_max_outbound_peers(&self) -> Option<usize> {
516 if let Some(max_peers) = self.max_peers {
517 if max_peers == 0 {
518 Some(0)
519 } else {
520 Some((max_peers / 3).max(1))
521 }
522 } else {
523 self.max_outbound_peers
524 }
525 }
526
527 pub const fn transactions_manager_config(&self) -> TransactionsManagerConfig {
529 TransactionsManagerConfig {
530 transaction_fetcher_config: TransactionFetcherConfig::new(
531 self.max_concurrent_tx_requests,
532 self.max_concurrent_tx_requests_per_peer,
533 self.soft_limit_byte_size_pooled_transactions_response,
534 self.soft_limit_byte_size_pooled_transactions_response_on_pack_request,
535 self.max_capacity_cache_txns_pending_fetch,
536 ),
537 max_transactions_seen_by_peer_history: self.max_seen_tx_history,
538 max_pending_pool_imports: self.max_pending_pool_imports,
539 propagation_mode: self.propagation_mode,
540 ingress_policy: self.tx_ingress_policy,
541 tx_channel_memory_limit_bytes: self.tx_channel_memory_limit_bytes,
542 }
543 }
544
545 pub fn network_config<N: NetworkPrimitives>(
558 &self,
559 config: &Config,
560 chain_spec: impl EthChainSpec,
561 secret_key: SecretKey,
562 default_peers_file: PathBuf,
563 executor: Runtime,
564 ) -> NetworkConfigBuilder<N> {
565 let listener_addr = self.resolved_addr();
568 let discovery_addr = self.resolved_discovery_addr(listener_addr);
572 let chain_bootnodes = self
573 .resolved_bootnodes()
574 .or_else(|| {
575 (!config.bootnodes.is_empty()).then(|| {
576 config
577 .bootnodes
578 .iter()
579 .filter_map(|node| node.resolve_blocking().ok())
580 .collect()
581 })
582 })
583 .unwrap_or_else(|| chain_spec.bootnodes().unwrap_or_else(mainnet_nodes));
584 let peers_file = self.peers_file.clone().unwrap_or(default_peers_file);
585
586 let ip_filter = self.ip_filter().unwrap_or_default();
588 let peers_config = config
589 .peers_config_with_basic_nodes_from_file(
590 self.persistent_peers_file(peers_file).as_deref(),
591 )
592 .with_max_inbound_opt(self.resolved_max_inbound_peers())
593 .with_max_outbound_opt(self.resolved_max_outbound_peers())
594 .with_ip_filter(ip_filter)
595 .with_enforce_enr_fork_id(self.enforce_enr_fork_id);
596
597 NetworkConfigBuilder::<N>::new(secret_key, executor)
599 .with_snap(self.snap_v2)
600 .external_ip_resolver(self.nat.clone())
601 .sessions_config(
602 config.sessions.clone().with_upscaled_event_buffer(peers_config.max_peers()),
603 )
604 .peer_config(peers_config)
605 .boot_nodes(chain_bootnodes.clone())
606 .transactions_manager_config(self.transactions_manager_config())
607 .apply(|builder| {
609 let peer_id = builder.get_peer_id();
610 builder.hello_message(
611 HelloMessageWithProtocols::builder(peer_id)
612 .client_version(&self.identity)
613 .build(),
614 )
615 })
616 .apply(|builder| {
618 let rlpx_socket = (listener_addr, self.port).into();
619 self.discovery.apply_to_builder(builder, rlpx_socket, chain_bootnodes)
620 })
621 .listener_addr(SocketAddr::new(listener_addr, self.port))
622 .discovery_addr(SocketAddr::new(discovery_addr, self.discovery.port))
623 .disable_tx_gossip(self.disable_tx_gossip)
624 .required_block_hashes(self.required_block_hashes.clone())
625 .eth_max_message_size_opt(self.eth_max_message_size.map(NonZeroUsize::get))
626 .network_id(self.network_id)
627 }
628
629 pub fn persistent_peers_file(&self, peers_file: PathBuf) -> Option<PathBuf> {
631 self.no_persist_peers.not().then_some(peers_file)
632 }
633
634 pub const fn with_discovery(mut self, discovery: DiscoveryArgs) -> Self {
636 self.discovery = discovery;
637 self
638 }
639
640 pub const fn with_unused_p2p_port(mut self) -> Self {
643 self.port = 0;
644 self
645 }
646
647 pub const fn with_unused_ports(mut self) -> Self {
650 self = self.with_unused_p2p_port();
651 self.discovery = self.discovery.with_unused_discovery_port();
652 self
653 }
654
655 pub fn with_nat_resolver(mut self, nat: NatResolver) -> Self {
657 self.nat = nat;
658 self
659 }
660
661 pub fn adjust_instance_ports(&mut self, instance: Option<u16>) {
667 if let Some(instance) = instance {
668 debug_assert_ne!(instance, 0, "instance must be non-zero");
669 self.port += instance - 1;
670 self.discovery.adjust_instance_ports(instance);
671 }
672 }
673
674 pub async fn resolve_trusted_peers(&self) -> Result<Vec<NodeRecord>, std::io::Error> {
676 futures::future::try_join_all(
677 self.trusted_peers.iter().map(|peer| async move { peer.resolve().await }),
678 )
679 .await
680 }
681
682 pub fn secret_key(
688 &self,
689 default_secret_key_path: PathBuf,
690 ) -> Result<SecretKey, SecretKeyError> {
691 if let Some(b256) = &self.p2p_secret_key_hex {
692 SecretKey::from_slice(b256.as_slice()).map_err(SecretKeyError::SecretKeyDecodeError)
694 } else {
695 let secret_key_path = self.p2p_secret_key.clone().unwrap_or(default_secret_key_path);
697 get_secret_key(&secret_key_path)
698 }
699 }
700
701 pub fn ip_filter(&self) -> Result<IpFilter, ipnet::AddrParseError> {
705 if let Some(netrestrict) = &self.netrestrict {
706 IpFilter::from_cidr_string(netrestrict)
707 } else {
708 Ok(IpFilter::allow_all())
709 }
710 }
711}
712
713impl Default for NetworkArgs {
714 fn default() -> Self {
715 let DefaultNetworkArgs {
716 dns_retries,
717 nat,
718 addr,
719 port,
720 max_concurrent_tx_requests,
721 max_concurrent_tx_requests_per_peer,
722 max_seen_tx_history,
723 max_pending_pool_imports,
724 soft_limit_byte_size_pooled_transactions_response,
725 soft_limit_byte_size_pooled_transactions_response_on_pack_request,
726 max_capacity_cache_txns_pending_fetch,
727 tx_channel_memory_limit_bytes,
728 tx_propagation_policy,
729 tx_ingress_policy,
730 propagation_mode,
731 enforce_enr_fork_id,
732 } = DefaultNetworkArgs::get_global().clone();
733 Self {
734 discovery: DiscoveryArgs::default(),
735 snap_v2: false,
736 trusted_peers: vec![],
737 trusted_only: false,
738 bootnodes: None,
739 dns_retries,
740 peers_file: None,
741 identity: version_metadata().p2p_client_version.to_string(),
742 p2p_secret_key: None,
743 p2p_secret_key_hex: None,
744 no_persist_peers: false,
745 nat,
746 addr,
747 port,
748 max_outbound_peers: None,
749 max_inbound_peers: None,
750 max_peers: None,
751 max_concurrent_tx_requests,
752 max_concurrent_tx_requests_per_peer,
753 soft_limit_byte_size_pooled_transactions_response,
754 soft_limit_byte_size_pooled_transactions_response_on_pack_request,
755 max_pending_pool_imports,
756 max_seen_tx_history,
757 max_capacity_cache_txns_pending_fetch,
758 tx_channel_memory_limit_bytes,
759 net_if: None,
760 tx_propagation_policy,
761 tx_ingress_policy,
762 disable_tx_gossip: false,
763 propagation_mode,
764 required_block_hashes: vec![],
765 network_id: None,
766 eth_max_message_size: None,
767 netrestrict: None,
768 enforce_enr_fork_id,
769 }
770 }
771}
772
773static DISCOVERY_DEFAULTS: OnceLock<DefaultDiscoveryArgs> = OnceLock::new();
775
776#[derive(Debug, Clone, Copy)]
778pub struct DefaultDiscoveryArgs {
779 pub disable_discovery: bool,
781 pub disable_dns_discovery: bool,
783 pub disable_discv4_discovery: bool,
785 pub disable_discv5_discovery: bool,
787 pub disable_nat: bool,
789 pub addr: IpAddr,
791 pub port: u16,
793 pub discv5_addr: Option<Ipv4Addr>,
795 pub discv5_addr_ipv6: Option<Ipv6Addr>,
797 pub discv5_port: Option<u16>,
799 pub discv5_port_ipv6: Option<u16>,
801 pub discv5_lookup_interval: u64,
803 pub discv5_bootstrap_lookup_interval: u64,
805 pub discv5_bootstrap_lookup_countdown: u64,
807}
808
809impl DefaultDiscoveryArgs {
810 pub fn try_init(self) -> Result<(), Self> {
812 DISCOVERY_DEFAULTS.set(self)
813 }
814
815 pub fn get_global() -> &'static Self {
817 DISCOVERY_DEFAULTS.get_or_init(Self::default)
818 }
819
820 pub const fn with_disable_discovery(mut self, disable: bool) -> Self {
822 self.disable_discovery = disable;
823 self
824 }
825
826 pub const fn with_disable_dns_discovery(mut self, disable: bool) -> Self {
828 self.disable_dns_discovery = disable;
829 self
830 }
831
832 pub const fn with_disable_discv4_discovery(mut self, disable: bool) -> Self {
834 self.disable_discv4_discovery = disable;
835 self
836 }
837
838 pub const fn with_disable_discv5_discovery(mut self, disable: bool) -> Self {
840 self.disable_discv5_discovery = disable;
841 self
842 }
843
844 pub const fn with_disable_nat(mut self, disable: bool) -> Self {
846 self.disable_nat = disable;
847 self
848 }
849
850 pub const fn with_addr(mut self, addr: IpAddr) -> Self {
852 self.addr = addr;
853 self
854 }
855
856 pub const fn with_port(mut self, port: u16) -> Self {
858 self.port = port;
859 self
860 }
861
862 pub fn with_discv5_addr(mut self, addr: impl Into<Option<Ipv4Addr>>) -> Self {
864 self.discv5_addr = addr.into();
865 self
866 }
867
868 pub fn with_discv5_addr_ipv6(mut self, addr: impl Into<Option<Ipv6Addr>>) -> Self {
870 self.discv5_addr_ipv6 = addr.into();
871 self
872 }
873
874 pub fn with_discv5_port(mut self, port: impl Into<Option<u16>>) -> Self {
876 self.discv5_port = port.into();
877 self
878 }
879
880 pub fn with_discv5_port_ipv6(mut self, port: impl Into<Option<u16>>) -> Self {
882 self.discv5_port_ipv6 = port.into();
883 self
884 }
885
886 pub const fn with_discv5_lookup_interval(mut self, interval: u64) -> Self {
888 self.discv5_lookup_interval = interval;
889 self
890 }
891
892 pub const fn with_discv5_bootstrap_lookup_interval(mut self, interval: u64) -> Self {
894 self.discv5_bootstrap_lookup_interval = interval;
895 self
896 }
897
898 pub const fn with_discv5_bootstrap_lookup_countdown(mut self, countdown: u64) -> Self {
900 self.discv5_bootstrap_lookup_countdown = countdown;
901 self
902 }
903}
904
905impl Default for DefaultDiscoveryArgs {
906 fn default() -> Self {
907 Self {
908 disable_discovery: false,
909 disable_dns_discovery: false,
910 disable_discv4_discovery: false,
911 disable_discv5_discovery: false,
912 disable_nat: false,
913 addr: DEFAULT_DISCOVERY_ADDR,
914 port: DEFAULT_DISCOVERY_PORT,
915 discv5_addr: None,
916 discv5_addr_ipv6: None,
917 discv5_port: Some(DEFAULT_DISCOVERY_V5_PORT),
918 discv5_port_ipv6: Some(DEFAULT_DISCOVERY_V5_PORT),
919 discv5_lookup_interval: DEFAULT_SECONDS_LOOKUP_INTERVAL,
920 discv5_bootstrap_lookup_interval: DEFAULT_SECONDS_BOOTSTRAP_LOOKUP_INTERVAL,
921 discv5_bootstrap_lookup_countdown: DEFAULT_COUNT_BOOTSTRAP_LOOKUPS,
922 }
923 }
924}
925
926#[derive(Debug, Clone, Args, PartialEq, Eq)]
928pub struct DiscoveryArgs {
929 #[arg(short, long, default_value_if("dev", "true", "true"), default_value_t = DefaultDiscoveryArgs::get_global().disable_discovery)]
931 pub disable_discovery: bool,
932
933 #[arg(long, conflicts_with = "disable_discovery", default_value_t = DefaultDiscoveryArgs::get_global().disable_dns_discovery)]
935 pub disable_dns_discovery: bool,
936
937 #[arg(long, conflicts_with = "disable_discovery", default_value_t = DefaultDiscoveryArgs::get_global().disable_discv4_discovery)]
939 pub disable_discv4_discovery: bool,
940
941 #[arg(long, conflicts_with = "disable_discovery", hide = true)]
946 pub enable_discv5_discovery: bool,
947
948 #[arg(long, conflicts_with = "disable_discovery", default_value_t = DefaultDiscoveryArgs::get_global().disable_discv5_discovery)]
950 pub disable_discv5_discovery: bool,
951
952 #[arg(long, conflicts_with = "disable_discovery", default_value_t = DefaultDiscoveryArgs::get_global().disable_nat)]
954 pub disable_nat: bool,
955
956 #[arg(id = "discovery.addr", long = "discovery.addr", value_name = "DISCOVERY_ADDR", default_value_t = DefaultDiscoveryArgs::get_global().addr)]
961 pub addr: IpAddr,
962
963 #[arg(id = "discovery.port", long = "discovery.port", value_name = "DISCOVERY_PORT", default_value_t = DefaultDiscoveryArgs::get_global().port)]
965 pub port: u16,
966
967 #[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()))))]
970 pub discv5_addr: Option<Ipv4Addr>,
971
972 #[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()))))]
975 pub discv5_addr_ipv6: Option<Ipv6Addr>,
976
977 #[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()))))]
980 pub discv5_port: Option<u16>,
981
982 #[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()))))]
987 pub discv5_port_ipv6: Option<u16>,
988
989 #[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)]
992 pub discv5_lookup_interval: u64,
993
994 #[arg(id = "discovery.v5.bootstrap.lookup-interval", long = "discovery.v5.bootstrap.lookup-interval", value_name = "DISCOVERY_V5_BOOTSTRAP_LOOKUP_INTERVAL",
997 default_value_t = DefaultDiscoveryArgs::get_global().discv5_bootstrap_lookup_interval)]
998 pub discv5_bootstrap_lookup_interval: u64,
999
1000 #[arg(id = "discovery.v5.bootstrap.lookup-countdown", long = "discovery.v5.bootstrap.lookup-countdown", value_name = "DISCOVERY_V5_BOOTSTRAP_LOOKUP_COUNTDOWN",
1002 default_value_t = DefaultDiscoveryArgs::get_global().discv5_bootstrap_lookup_countdown)]
1003 pub discv5_bootstrap_lookup_countdown: u64,
1004}
1005
1006impl DiscoveryArgs {
1007 pub fn apply_to_builder<N>(
1009 &self,
1010 mut network_config_builder: NetworkConfigBuilder<N>,
1011 rlpx_tcp_socket: SocketAddr,
1012 boot_nodes: impl IntoIterator<Item = NodeRecord>,
1013 ) -> NetworkConfigBuilder<N>
1014 where
1015 N: NetworkPrimitives,
1016 {
1017 if self.disable_discovery || self.disable_dns_discovery {
1018 network_config_builder = network_config_builder.disable_dns_discovery();
1019 }
1020
1021 if self.disable_discovery || self.disable_discv4_discovery {
1022 network_config_builder = network_config_builder.disable_discv4_discovery();
1023 }
1024
1025 if self.disable_nat {
1026 network_config_builder = network_config_builder.disable_nat();
1028 }
1029
1030 if self.should_enable_discv5() {
1031 network_config_builder = network_config_builder
1032 .discovery_v5(self.discovery_v5_builder(rlpx_tcp_socket, boot_nodes));
1033 }
1034
1035 network_config_builder
1036 }
1037
1038 pub fn discovery_v5_builder(
1040 &self,
1041 rlpx_tcp_socket: SocketAddr,
1042 boot_nodes: impl IntoIterator<Item = NodeRecord>,
1043 ) -> reth_discv5::ConfigBuilder {
1044 let Self {
1045 discv5_addr,
1046 discv5_addr_ipv6,
1047 discv5_port,
1048 discv5_port_ipv6,
1049 discv5_lookup_interval,
1050 discv5_bootstrap_lookup_interval,
1051 discv5_bootstrap_lookup_countdown,
1052 port,
1053 ..
1054 } = self;
1055
1056 let has_discv5_addr_args = discv5_addr.is_some() || discv5_addr_ipv6.is_some();
1057
1058 let discv5_addr_ipv4 = discv5_addr.or(match rlpx_tcp_socket {
1060 SocketAddr::V4(addr) => Some(*addr.ip()),
1061 SocketAddr::V6(_) => None,
1062 });
1063 let discv5_addr_ipv6 = discv5_addr_ipv6.or(match rlpx_tcp_socket {
1064 SocketAddr::V4(_) => None,
1065 SocketAddr::V6(addr) => Some(*addr.ip()),
1066 });
1067
1068 let mut discv5_config_builder =
1069 reth_discv5::discv5::ConfigBuilder::new(ListenConfig::from_two_sockets(
1070 discv5_addr_ipv4.map(|addr| SocketAddrV4::new(addr, discv5_port.unwrap_or(*port))),
1071 discv5_addr_ipv6
1072 .map(|addr| SocketAddrV6::new(addr, discv5_port_ipv6.unwrap_or(*port), 0, 0)),
1073 ));
1074
1075 if has_discv5_addr_args || self.disable_nat {
1076 discv5_config_builder.disable_enr_update();
1078 }
1079 reth_discv5::Config::builder(rlpx_tcp_socket)
1080 .discv5_config(discv5_config_builder.build())
1081 .add_unsigned_boot_nodes(boot_nodes)
1082 .lookup_interval(*discv5_lookup_interval)
1083 .bootstrap_lookup_interval(*discv5_bootstrap_lookup_interval)
1084 .bootstrap_lookup_countdown(*discv5_bootstrap_lookup_countdown)
1085 }
1086
1087 const fn should_enable_discv5(&self) -> bool {
1091 !(self.disable_discovery || self.disable_discv5_discovery)
1092 }
1093
1094 pub const fn with_unused_discovery_port(mut self) -> Self {
1097 self.port = 0;
1098 self.discv5_port = Some(0);
1099 self.discv5_port_ipv6 = Some(0);
1100 self
1101 }
1102
1103 pub fn with_discv5_port(mut self, port: impl Into<Option<u16>>) -> Self {
1105 self.discv5_port = port.into();
1106 self
1107 }
1108
1109 pub fn adjust_instance_ports(&mut self, instance: u16) {
1115 debug_assert_ne!(instance, 0, "instance must be non-zero");
1116 self.port += instance - 1;
1117 self.discv5_port = self.discv5_port.map(|port| port + instance - 1);
1118 self.discv5_port_ipv6 = self.discv5_port_ipv6.map(|port| port + instance - 1);
1119 }
1120}
1121
1122impl Default for DiscoveryArgs {
1123 fn default() -> Self {
1124 let DefaultDiscoveryArgs {
1125 disable_discovery,
1126 disable_dns_discovery,
1127 disable_discv4_discovery,
1128 disable_discv5_discovery,
1129 disable_nat,
1130 addr,
1131 port,
1132 discv5_addr,
1133 discv5_addr_ipv6,
1134 discv5_port,
1135 discv5_port_ipv6,
1136 discv5_lookup_interval,
1137 discv5_bootstrap_lookup_interval,
1138 discv5_bootstrap_lookup_countdown,
1139 } = *DefaultDiscoveryArgs::get_global();
1140 Self {
1141 disable_discovery,
1142 disable_dns_discovery,
1143 disable_discv4_discovery,
1144 enable_discv5_discovery: false,
1145 disable_discv5_discovery,
1146 disable_nat,
1147 addr,
1148 port,
1149 discv5_addr,
1150 discv5_addr_ipv6,
1151 discv5_port,
1152 discv5_port_ipv6,
1153 discv5_lookup_interval,
1154 discv5_bootstrap_lookup_interval,
1155 discv5_bootstrap_lookup_countdown,
1156 }
1157 }
1158}
1159
1160fn parse_block_num_hash(s: &str) -> Result<BlockNumHash, String> {
1162 if let Some((num_str, hash_str)) = s.split_once('=') {
1163 let number = num_str.parse().map_err(|_| format!("Invalid block number: {}", num_str))?;
1164 let hash = B256::from_str(hash_str).map_err(|_| format!("Invalid hash: {}", hash_str))?;
1165 Ok(BlockNumHash::new(number, hash))
1166 } else {
1167 let hash = B256::from_str(s).map_err(|_| format!("Invalid hash: {}", s))?;
1169 Ok(BlockNumHash::new(0, hash))
1170 }
1171}
1172
1173#[cfg(test)]
1174mod tests {
1175 use super::*;
1176 use clap::Parser;
1177 use reth_chainspec::MAINNET;
1178 use reth_config::Config;
1179 use reth_network::EthNetworkPrimitives;
1180 use reth_network_peers::NodeRecord;
1181 use secp256k1::SecretKey;
1182 use std::{
1183 fs,
1184 time::{SystemTime, UNIX_EPOCH},
1185 };
1186
1187 #[derive(Parser)]
1189 struct CommandParser<T: Args> {
1190 #[command(flatten)]
1191 args: T,
1192 }
1193
1194 #[test]
1195 fn parse_nat_args() {
1196 let args = CommandParser::<NetworkArgs>::parse_from(["reth", "--nat", "none"]).args;
1197 assert_eq!(args.nat, NatResolver::None);
1198
1199 let args =
1200 CommandParser::<NetworkArgs>::parse_from(["reth", "--nat", "extip:0.0.0.0"]).args;
1201 assert_eq!(args.nat, NatResolver::ExternalIp("0.0.0.0".parse().unwrap()));
1202 }
1203
1204 #[test]
1205 fn parse_peer_args() {
1206 let args =
1207 CommandParser::<NetworkArgs>::parse_from(["reth", "--max-outbound-peers", "50"]).args;
1208 assert_eq!(args.max_outbound_peers, Some(50));
1209 assert_eq!(args.max_inbound_peers, None);
1210
1211 let args = CommandParser::<NetworkArgs>::parse_from([
1212 "reth",
1213 "--max-outbound-peers",
1214 "75",
1215 "--max-inbound-peers",
1216 "15",
1217 ])
1218 .args;
1219 assert_eq!(args.max_outbound_peers, Some(75));
1220 assert_eq!(args.max_inbound_peers, Some(15));
1221 }
1222
1223 #[test]
1224 fn parse_trusted_peer_args() {
1225 let args =
1226 CommandParser::<NetworkArgs>::parse_from([
1227 "reth",
1228 "--trusted-peers",
1229 "enode://d860a01f9722d78051619d1e2351aba3f43f943f6f00718d1b9baa4101932a1f5011f16bb2b1bb35db20d6fe28fa0bf09636d26a87d31de9ec6203eeedb1f666@18.138.108.67:30303,enode://22a8232c3abc76a16ae9d6c3b164f98775fe226f0917b0ca871128a74a8e9630b458460865bab457221f1d448dd9791d24c4e5d88786180ac185df813a68d4de@3.209.45.79:30303"
1230 ])
1231 .args;
1232
1233 assert_eq!(
1234 args.trusted_peers,
1235 vec![
1236 "enode://d860a01f9722d78051619d1e2351aba3f43f943f6f00718d1b9baa4101932a1f5011f16bb2b1bb35db20d6fe28fa0bf09636d26a87d31de9ec6203eeedb1f666@18.138.108.67:30303".parse().unwrap(),
1237 "enode://22a8232c3abc76a16ae9d6c3b164f98775fe226f0917b0ca871128a74a8e9630b458460865bab457221f1d448dd9791d24c4e5d88786180ac185df813a68d4de@3.209.45.79:30303".parse().unwrap()
1238 ]
1239 );
1240 }
1241
1242 #[test]
1243 fn parse_retry_strategy_args() {
1244 let tests = vec![0, 10];
1245
1246 for retries in tests {
1247 let retries_str = retries.to_string();
1248 let args = CommandParser::<NetworkArgs>::parse_from([
1249 "reth",
1250 "--dns-retries",
1251 retries_str.as_str(),
1252 ])
1253 .args;
1254
1255 assert_eq!(args.dns_retries, retries);
1256 }
1257 }
1258
1259 #[test]
1260 fn transactions_manager_config_uses_max_pending_imports() {
1261 let args = NetworkArgs { max_pending_pool_imports: 50_000, ..Default::default() };
1262
1263 let config = args.transactions_manager_config();
1264
1265 assert_eq!(config.max_pending_pool_imports, 50_000);
1266 }
1267
1268 #[test]
1269 fn parse_disable_tx_gossip_args() {
1270 let args = CommandParser::<NetworkArgs>::parse_from(["reth", "--disable-tx-gossip"]).args;
1271 assert!(args.disable_tx_gossip);
1272 }
1273
1274 #[test]
1275 fn parse_max_peers_flag() {
1276 let args = CommandParser::<NetworkArgs>::parse_from(["reth", "--max-peers", "90"]).args;
1277
1278 assert_eq!(args.max_peers, Some(90));
1279 assert_eq!(args.max_outbound_peers, None);
1280 assert_eq!(args.max_inbound_peers, None);
1281 assert_eq!(args.resolved_max_outbound_peers(), Some(30));
1282 assert_eq!(args.resolved_max_inbound_peers(), Some(60));
1283 }
1284
1285 #[test]
1286 fn max_peers_conflicts_with_outbound() {
1287 let result = CommandParser::<NetworkArgs>::try_parse_from([
1288 "reth",
1289 "--max-peers",
1290 "90",
1291 "--max-outbound-peers",
1292 "50",
1293 ]);
1294 assert!(
1295 result.is_err(),
1296 "Should fail when both --max-peers and --max-outbound-peers are used"
1297 );
1298 }
1299
1300 #[test]
1301 fn max_peers_conflicts_with_inbound() {
1302 let result = CommandParser::<NetworkArgs>::try_parse_from([
1303 "reth",
1304 "--max-peers",
1305 "90",
1306 "--max-inbound-peers",
1307 "30",
1308 ]);
1309 assert!(
1310 result.is_err(),
1311 "Should fail when both --max-peers and --max-inbound-peers are used"
1312 );
1313 }
1314
1315 #[test]
1316 fn max_peers_split_calculation() {
1317 let args = CommandParser::<NetworkArgs>::parse_from(["reth", "--max-peers", "90"]).args;
1318
1319 assert_eq!(args.max_peers, Some(90));
1320 assert_eq!(args.resolved_max_outbound_peers(), Some(30));
1321 assert_eq!(args.resolved_max_inbound_peers(), Some(60));
1322 }
1323
1324 #[test]
1325 fn max_peers_small_values() {
1326 let args1 = CommandParser::<NetworkArgs>::parse_from(["reth", "--max-peers", "1"]).args;
1327 assert_eq!(args1.resolved_max_outbound_peers(), Some(1));
1328 assert_eq!(args1.resolved_max_inbound_peers(), Some(0));
1329
1330 let args2 = CommandParser::<NetworkArgs>::parse_from(["reth", "--max-peers", "2"]).args;
1331 assert_eq!(args2.resolved_max_outbound_peers(), Some(1));
1332 assert_eq!(args2.resolved_max_inbound_peers(), Some(1));
1333
1334 let args3 = CommandParser::<NetworkArgs>::parse_from(["reth", "--max-peers", "3"]).args;
1335 assert_eq!(args3.resolved_max_outbound_peers(), Some(1));
1336 assert_eq!(args3.resolved_max_inbound_peers(), Some(2));
1337 }
1338
1339 #[test]
1340 fn resolved_peers_without_max_peers() {
1341 let args = CommandParser::<NetworkArgs>::parse_from([
1342 "reth",
1343 "--max-outbound-peers",
1344 "75",
1345 "--max-inbound-peers",
1346 "15",
1347 ])
1348 .args;
1349
1350 assert_eq!(args.max_peers, None);
1351 assert_eq!(args.resolved_max_outbound_peers(), Some(75));
1352 assert_eq!(args.resolved_max_inbound_peers(), Some(15));
1353 }
1354
1355 #[test]
1356 fn resolved_peers_with_defaults() {
1357 let args = CommandParser::<NetworkArgs>::parse_from(["reth"]).args;
1358
1359 assert_eq!(args.max_peers, None);
1360 assert_eq!(args.resolved_max_outbound_peers(), None);
1361 assert_eq!(args.resolved_max_inbound_peers(), None);
1362 }
1363
1364 #[test]
1365 fn network_args_default_sanity_test() {
1366 let default_args = NetworkArgs::default();
1367 let args = CommandParser::<NetworkArgs>::parse_from(["reth"]).args;
1368
1369 assert_eq!(args, default_args);
1370 }
1371
1372 #[test]
1373 fn net_if_uses_resolved_addr_for_default_discovery_addr() {
1374 let args =
1375 CommandParser::<NetworkArgs>::parse_from(["reth", "--net-if.experimental", "en0"]).args;
1376 let listener_addr = IpAddr::V4(Ipv4Addr::new(192, 0, 2, 1));
1377
1378 assert_eq!(args.resolved_discovery_addr(listener_addr), listener_addr);
1379 }
1380
1381 #[test]
1382 fn net_if_preserves_custom_discovery_addr() {
1383 let custom_discovery_addr = IpAddr::V4(Ipv4Addr::new(192, 0, 2, 2));
1384 let args = CommandParser::<NetworkArgs>::parse_from([
1385 "reth",
1386 "--net-if.experimental",
1387 "en0",
1388 "--discovery.addr",
1389 "192.0.2.2",
1390 ])
1391 .args;
1392 let listener_addr = IpAddr::V4(Ipv4Addr::new(192, 0, 2, 1));
1393
1394 assert_eq!(args.resolved_discovery_addr(listener_addr), custom_discovery_addr);
1395 }
1396
1397 #[test]
1398 fn default_discovery_addr_is_preserved_without_net_if() {
1399 let args = CommandParser::<NetworkArgs>::parse_from(["reth"]).args;
1400 let listener_addr = IpAddr::V4(Ipv4Addr::new(192, 0, 2, 1));
1401
1402 assert_eq!(args.resolved_discovery_addr(listener_addr), DEFAULT_DISCOVERY_ADDR);
1403 }
1404
1405 #[test]
1406 fn parse_eth_max_message_size() {
1407 let args = CommandParser::<NetworkArgs>::parse_from([
1408 "reth",
1409 "--eth-max-message-size",
1410 "15728640",
1411 ])
1412 .args;
1413
1414 assert_eq!(args.eth_max_message_size, Some(NonZeroUsize::new(15 * 1024 * 1024).unwrap()));
1415 }
1416
1417 #[test]
1418 fn parse_eth_max_message_size_zero_rejected() {
1419 let result =
1420 CommandParser::<NetworkArgs>::try_parse_from(["reth", "--eth-max-message-size", "0"]);
1421 assert!(result.is_err());
1422 }
1423
1424 #[test]
1425 fn parse_eth_max_message_size_above_rlpx_cap() {
1426 let result = CommandParser::<NetworkArgs>::try_parse_from([
1427 "reth",
1428 "--eth-max-message-size",
1429 "16777216",
1430 ]);
1431 assert!(result.is_ok());
1432 let args = result.unwrap().args;
1433 assert_eq!(args.eth_max_message_size, Some(NonZeroUsize::new(16 * 1024 * 1024).unwrap()));
1434 }
1435
1436 #[test]
1437 fn parse_required_block_hashes() {
1438 let args = CommandParser::<NetworkArgs>::parse_from([
1439 "reth",
1440 "--required-block-hashes",
1441 "0x1111111111111111111111111111111111111111111111111111111111111111,23115201=0x2222222222222222222222222222222222222222222222222222222222222222",
1442 ])
1443 .args;
1444
1445 assert_eq!(args.required_block_hashes.len(), 2);
1446 assert_eq!(args.required_block_hashes[0].number, 0);
1448 assert_eq!(
1449 args.required_block_hashes[0].hash.to_string(),
1450 "0x1111111111111111111111111111111111111111111111111111111111111111"
1451 );
1452 assert_eq!(args.required_block_hashes[1].number, 23115201);
1454 assert_eq!(
1455 args.required_block_hashes[1].hash.to_string(),
1456 "0x2222222222222222222222222222222222222222222222222222222222222222"
1457 );
1458 }
1459
1460 #[test]
1461 fn parse_empty_required_block_hashes() {
1462 let args = CommandParser::<NetworkArgs>::parse_from(["reth"]).args;
1463 assert!(args.required_block_hashes.is_empty());
1464 }
1465
1466 #[test]
1467 fn test_parse_block_num_hash() {
1468 let result = parse_block_num_hash(
1470 "0x1111111111111111111111111111111111111111111111111111111111111111",
1471 );
1472 assert!(result.is_ok());
1473 assert_eq!(result.unwrap().number, 0);
1474
1475 let result = parse_block_num_hash(
1477 "23115201=0x2222222222222222222222222222222222222222222222222222222222222222",
1478 );
1479 assert!(result.is_ok());
1480 assert_eq!(result.unwrap().number, 23115201);
1481
1482 assert!(parse_block_num_hash("invalid").is_err());
1484 assert!(parse_block_num_hash(
1485 "abc=0x1111111111111111111111111111111111111111111111111111111111111111"
1486 )
1487 .is_err());
1488 }
1489
1490 #[test]
1491 fn parse_p2p_secret_key_hex() {
1492 let hex = "4c0883a69102937d6231471b5dbb6204fe512961708279f8c5c58b3b9c4e8b8f";
1493 let args =
1494 CommandParser::<NetworkArgs>::parse_from(["reth", "--p2p-secret-key-hex", hex]).args;
1495
1496 let expected: B256 = hex.parse().unwrap();
1497 assert_eq!(args.p2p_secret_key_hex, Some(expected));
1498 assert_eq!(args.p2p_secret_key, None);
1499 }
1500
1501 #[test]
1502 fn parse_p2p_secret_key_hex_with_0x_prefix() {
1503 let hex = "0x4c0883a69102937d6231471b5dbb6204fe512961708279f8c5c58b3b9c4e8b8f";
1504 let args =
1505 CommandParser::<NetworkArgs>::parse_from(["reth", "--p2p-secret-key-hex", hex]).args;
1506
1507 let expected: B256 = hex.parse().unwrap();
1508 assert_eq!(args.p2p_secret_key_hex, Some(expected));
1509 assert_eq!(args.p2p_secret_key, None);
1510 }
1511
1512 #[test]
1513 fn test_p2p_secret_key_and_hex_are_mutually_exclusive() {
1514 let result = CommandParser::<NetworkArgs>::try_parse_from([
1515 "reth",
1516 "--p2p-secret-key",
1517 "/path/to/key",
1518 "--p2p-secret-key-hex",
1519 "4c0883a69102937d6231471b5dbb6204fe512961708279f8c5c58b3b9c4e8b8f",
1520 ]);
1521
1522 assert!(result.is_err());
1523 }
1524
1525 #[test]
1526 fn test_secret_key_method_with_hex() {
1527 let hex = "4c0883a69102937d6231471b5dbb6204fe512961708279f8c5c58b3b9c4e8b8f";
1528 let args =
1529 CommandParser::<NetworkArgs>::parse_from(["reth", "--p2p-secret-key-hex", hex]).args;
1530
1531 let temp_dir = std::env::temp_dir();
1532 let default_path = temp_dir.join("default_key");
1533 let secret_key = args.secret_key(default_path).unwrap();
1534
1535 assert_eq!(alloy_primitives::hex::encode(secret_key.secret_bytes()), hex);
1537 }
1538
1539 #[test]
1540 fn parse_netrestrict_single_network() {
1541 let args =
1542 CommandParser::<NetworkArgs>::parse_from(["reth", "--netrestrict", "192.168.0.0/16"])
1543 .args;
1544
1545 assert_eq!(args.netrestrict, Some("192.168.0.0/16".to_string()));
1546
1547 let ip_filter = args.ip_filter().unwrap();
1548 assert!(ip_filter.has_restrictions());
1549 assert!(ip_filter.is_allowed(&"192.168.1.1".parse().unwrap()));
1550 assert!(!ip_filter.is_allowed(&"10.0.0.1".parse().unwrap()));
1551 }
1552
1553 #[test]
1554 fn parse_netrestrict_multiple_networks() {
1555 let args = CommandParser::<NetworkArgs>::parse_from([
1556 "reth",
1557 "--netrestrict",
1558 "192.168.0.0/16,10.0.0.0/8",
1559 ])
1560 .args;
1561
1562 assert_eq!(args.netrestrict, Some("192.168.0.0/16,10.0.0.0/8".to_string()));
1563
1564 let ip_filter = args.ip_filter().unwrap();
1565 assert!(ip_filter.has_restrictions());
1566 assert!(ip_filter.is_allowed(&"192.168.1.1".parse().unwrap()));
1567 assert!(ip_filter.is_allowed(&"10.5.10.20".parse().unwrap()));
1568 assert!(!ip_filter.is_allowed(&"172.16.0.1".parse().unwrap()));
1569 }
1570
1571 #[test]
1572 fn parse_netrestrict_ipv6() {
1573 let args =
1574 CommandParser::<NetworkArgs>::parse_from(["reth", "--netrestrict", "2001:db8::/32"])
1575 .args;
1576
1577 let ip_filter = args.ip_filter().unwrap();
1578 assert!(ip_filter.has_restrictions());
1579 assert!(ip_filter.is_allowed(&"2001:db8::1".parse().unwrap()));
1580 assert!(!ip_filter.is_allowed(&"2001:db9::1".parse().unwrap()));
1581 }
1582
1583 #[test]
1584 fn netrestrict_not_set() {
1585 let args = CommandParser::<NetworkArgs>::parse_from(["reth"]).args;
1586 assert_eq!(args.netrestrict, None);
1587
1588 let ip_filter = args.ip_filter().unwrap();
1589 assert!(!ip_filter.has_restrictions());
1590 assert!(ip_filter.is_allowed(&"192.168.1.1".parse().unwrap()));
1591 assert!(ip_filter.is_allowed(&"10.0.0.1".parse().unwrap()));
1592 }
1593
1594 #[test]
1595 fn netrestrict_invalid_cidr() {
1596 let args =
1597 CommandParser::<NetworkArgs>::parse_from(["reth", "--netrestrict", "invalid-cidr"])
1598 .args;
1599
1600 assert!(args.ip_filter().is_err());
1601 }
1602
1603 #[test]
1604 fn snap_v2_flag_controls_advertisement() {
1605 for (cli, enabled) in [(vec!["reth"], false), (vec!["reth", "--snap.v2"], true)] {
1606 let args = CommandParser::<NetworkArgs>::parse_from(cli).args;
1607 assert_eq!(args.snap_v2, enabled);
1608 let config = args
1609 .network_config::<EthNetworkPrimitives>(
1610 &Config::default(),
1611 MAINNET.clone(),
1612 SecretKey::from_byte_array(&[1u8; 32]).unwrap(),
1613 PathBuf::new(),
1614 Runtime::test(),
1615 )
1616 .build_with_noop_provider(MAINNET.clone());
1617 let versions = config
1618 .hello_message
1619 .protocols
1620 .iter()
1621 .filter(|protocol| protocol.cap.name == "snap")
1622 .map(|protocol| protocol.cap.version)
1623 .collect::<Vec<_>>();
1624 assert_eq!(versions, if enabled { vec![2] } else { vec![] });
1625 }
1626 }
1627
1628 #[test]
1629 fn network_config_preserves_basic_nodes_from_peers_file() {
1630 let enode = "enode://6f8a80d14311c39f35f516fa664deaaaa13e85b2f7493f37f6144d86991ec012937307647bd3b9a82abe2974e1407241d54947bbb39763a4cac9f77166ad92a0@10.3.58.6:30303?discport=30301";
1631 let unique = SystemTime::now().duration_since(UNIX_EPOCH).unwrap().as_nanos();
1632
1633 let peers_file = std::env::temp_dir().join(format!("reth_peers_test_{}.json", unique));
1634 fs::write(&peers_file, format!("[\"{}\"]", enode)).expect("write peers file");
1635
1636 let args = NetworkArgs {
1638 peers_file: Some(peers_file.clone()),
1639 no_persist_peers: false,
1640 ..Default::default()
1641 };
1642
1643 let secret_key = SecretKey::from_byte_array(&[1u8; 32]).unwrap();
1645 let builder = args.network_config::<EthNetworkPrimitives>(
1646 &Config::default(),
1647 MAINNET.clone(),
1648 secret_key,
1649 peers_file.clone(),
1650 Runtime::test(),
1651 );
1652
1653 let net_cfg = builder.build_with_noop_provider(MAINNET.clone());
1654
1655 let node: NodeRecord = enode.parse().unwrap();
1657 assert!(net_cfg.peers_config.persisted_peers.iter().any(|p| p.record == node));
1658
1659 let _ = fs::remove_file(&peers_file);
1661 }
1662
1663 #[test]
1664 fn network_config_prefers_cli_bootnodes_over_config_file() {
1665 let enr = "enr:-IS4QHCYrYZbAKWCBRlAy5zzaDZXJBGkcnh4MHcBFZntXNFrdvJjX04jRzjzCBOonrkTfj499SZuOh8R33Ls8RRcy5wBgmlkgnY0gmlwhH8AAAGJc2VjcDI1NmsxoQPKY0yuDUmstAHYpMa2_oxVtw0RW_QAdpzBQA8yWM0xOIN1ZHCCdl8";
1666 let enode = "enode://6f8a80d14311c39f35f516fa664deaaaa13e85b2f7493f37f6144d86991ec012937307647bd3b9a82abe2974e1407241d54947bbb39763a4cac9f77166ad92a0@10.3.58.6:30303";
1667 let config = Config { bootnodes: vec![enr.parse().unwrap()], ..Default::default() };
1668 let secret_key = SecretKey::from_byte_array(&[1u8; 32]).unwrap();
1669
1670 let boot_nodes = |args: &NetworkArgs| {
1671 args.network_config::<EthNetworkPrimitives>(
1672 &config,
1673 MAINNET.clone(),
1674 secret_key,
1675 PathBuf::from("peers.json"),
1676 Runtime::test(),
1677 )
1678 .boot_nodes_iter()
1679 .cloned()
1680 .collect::<Vec<_>>()
1681 };
1682
1683 let args = NetworkArgs { no_persist_peers: true, ..Default::default() };
1684 assert_eq!(boot_nodes(&args), vec![enr.parse::<TrustedPeer>().unwrap()]);
1685
1686 let args = NetworkArgs { bootnodes: Some(vec![enode.parse().unwrap()]), ..args };
1687 assert_eq!(boot_nodes(&args), vec![enode.parse::<TrustedPeer>().unwrap()]);
1688 }
1689}