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_BYTE_SIZE_PENDING_POOL_IMPORTS, 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 max_pending_pool_imports_bytes: DEFAULT_MAX_BYTE_SIZE_PENDING_POOL_IMPORTS,
540 propagation_mode: self.propagation_mode,
541 ingress_policy: self.tx_ingress_policy,
542 tx_channel_memory_limit_bytes: self.tx_channel_memory_limit_bytes,
543 }
544 }
545
546 pub fn network_config<N: NetworkPrimitives>(
559 &self,
560 config: &Config,
561 chain_spec: impl EthChainSpec,
562 secret_key: SecretKey,
563 default_peers_file: PathBuf,
564 executor: Runtime,
565 ) -> NetworkConfigBuilder<N> {
566 let listener_addr = self.resolved_addr();
569 let discovery_addr = self.resolved_discovery_addr(listener_addr);
573 let chain_bootnodes = self
574 .resolved_bootnodes()
575 .or_else(|| {
576 (!config.bootnodes.is_empty()).then(|| {
577 config
578 .bootnodes
579 .iter()
580 .filter_map(|node| node.resolve_blocking().ok())
581 .collect()
582 })
583 })
584 .unwrap_or_else(|| chain_spec.bootnodes().unwrap_or_else(mainnet_nodes));
585 let peers_file = self.peers_file.clone().unwrap_or(default_peers_file);
586
587 let ip_filter = self.ip_filter().unwrap_or_default();
589 let peers_config = config
590 .peers_config_with_basic_nodes_from_file(
591 self.persistent_peers_file(peers_file).as_deref(),
592 )
593 .with_max_inbound_opt(self.resolved_max_inbound_peers())
594 .with_max_outbound_opt(self.resolved_max_outbound_peers())
595 .with_ip_filter(ip_filter)
596 .with_enforce_enr_fork_id(self.enforce_enr_fork_id || config.peers.enforce_enr_fork_id);
599
600 NetworkConfigBuilder::<N>::new(secret_key, executor)
602 .with_snap(self.snap_v2)
603 .external_ip_resolver(self.nat.clone())
604 .sessions_config(
605 config.sessions.clone().with_upscaled_event_buffer(peers_config.max_peers()),
606 )
607 .peer_config(peers_config)
608 .boot_nodes(chain_bootnodes.clone())
609 .transactions_manager_config(self.transactions_manager_config())
610 .apply(|builder| {
612 let peer_id = builder.get_peer_id();
613 builder.hello_message(
614 HelloMessageWithProtocols::builder(peer_id)
615 .client_version(&self.identity)
616 .build(),
617 )
618 })
619 .apply(|builder| {
621 let rlpx_socket = (listener_addr, self.port).into();
622 self.discovery.apply_to_builder(builder, rlpx_socket, chain_bootnodes)
623 })
624 .listener_addr(SocketAddr::new(listener_addr, self.port))
625 .discovery_addr(SocketAddr::new(discovery_addr, self.discovery.port))
626 .disable_tx_gossip(self.disable_tx_gossip)
627 .required_block_hashes(self.required_block_hashes.clone())
628 .eth_max_message_size_opt(self.eth_max_message_size.map(NonZeroUsize::get))
629 .network_id(self.network_id)
630 }
631
632 pub fn persistent_peers_file(&self, peers_file: PathBuf) -> Option<PathBuf> {
634 self.no_persist_peers.not().then_some(peers_file)
635 }
636
637 pub const fn with_discovery(mut self, discovery: DiscoveryArgs) -> Self {
639 self.discovery = discovery;
640 self
641 }
642
643 pub const fn with_unused_p2p_port(mut self) -> Self {
646 self.port = 0;
647 self
648 }
649
650 pub const fn with_unused_ports(mut self) -> Self {
653 self = self.with_unused_p2p_port();
654 self.discovery = self.discovery.with_unused_discovery_port();
655 self
656 }
657
658 pub fn with_nat_resolver(mut self, nat: NatResolver) -> Self {
660 self.nat = nat;
661 self
662 }
663
664 pub fn adjust_instance_ports(&mut self, instance: Option<u16>) {
670 if let Some(instance) = instance {
671 debug_assert_ne!(instance, 0, "instance must be non-zero");
672 self.port += instance - 1;
673 self.discovery.adjust_instance_ports(instance);
674 }
675 }
676
677 pub async fn resolve_trusted_peers(&self) -> Result<Vec<NodeRecord>, std::io::Error> {
679 futures::future::try_join_all(
680 self.trusted_peers.iter().map(|peer| async move { peer.resolve().await }),
681 )
682 .await
683 }
684
685 pub fn secret_key(
691 &self,
692 default_secret_key_path: PathBuf,
693 ) -> Result<SecretKey, SecretKeyError> {
694 if let Some(b256) = &self.p2p_secret_key_hex {
695 SecretKey::from_secret_bytes(b256.0).map_err(SecretKeyError::SecretKeyDecodeError)
697 } else {
698 let secret_key_path = self.p2p_secret_key.clone().unwrap_or(default_secret_key_path);
700 get_secret_key(&secret_key_path)
701 }
702 }
703
704 pub fn ip_filter(&self) -> Result<IpFilter, ipnet::AddrParseError> {
708 if let Some(netrestrict) = &self.netrestrict {
709 IpFilter::from_cidr_string(netrestrict)
710 } else {
711 Ok(IpFilter::allow_all())
712 }
713 }
714}
715
716impl Default for NetworkArgs {
717 fn default() -> Self {
718 let DefaultNetworkArgs {
719 dns_retries,
720 nat,
721 addr,
722 port,
723 max_concurrent_tx_requests,
724 max_concurrent_tx_requests_per_peer,
725 max_seen_tx_history,
726 max_pending_pool_imports,
727 soft_limit_byte_size_pooled_transactions_response,
728 soft_limit_byte_size_pooled_transactions_response_on_pack_request,
729 max_capacity_cache_txns_pending_fetch,
730 tx_channel_memory_limit_bytes,
731 tx_propagation_policy,
732 tx_ingress_policy,
733 propagation_mode,
734 enforce_enr_fork_id,
735 } = DefaultNetworkArgs::get_global().clone();
736 Self {
737 discovery: DiscoveryArgs::default(),
738 snap_v2: false,
739 trusted_peers: vec![],
740 trusted_only: false,
741 bootnodes: None,
742 dns_retries,
743 peers_file: None,
744 identity: version_metadata().p2p_client_version.to_string(),
745 p2p_secret_key: None,
746 p2p_secret_key_hex: None,
747 no_persist_peers: false,
748 nat,
749 addr,
750 port,
751 max_outbound_peers: None,
752 max_inbound_peers: None,
753 max_peers: None,
754 max_concurrent_tx_requests,
755 max_concurrent_tx_requests_per_peer,
756 soft_limit_byte_size_pooled_transactions_response,
757 soft_limit_byte_size_pooled_transactions_response_on_pack_request,
758 max_pending_pool_imports,
759 max_seen_tx_history,
760 max_capacity_cache_txns_pending_fetch,
761 tx_channel_memory_limit_bytes,
762 net_if: None,
763 tx_propagation_policy,
764 tx_ingress_policy,
765 disable_tx_gossip: false,
766 propagation_mode,
767 required_block_hashes: vec![],
768 network_id: None,
769 eth_max_message_size: None,
770 netrestrict: None,
771 enforce_enr_fork_id,
772 }
773 }
774}
775
776static DISCOVERY_DEFAULTS: OnceLock<DefaultDiscoveryArgs> = OnceLock::new();
778
779#[derive(Debug, Clone, Copy)]
781pub struct DefaultDiscoveryArgs {
782 pub disable_discovery: bool,
784 pub disable_dns_discovery: bool,
786 pub disable_discv4_discovery: bool,
788 pub disable_discv5_discovery: bool,
790 pub disable_nat: bool,
792 pub addr: IpAddr,
794 pub port: u16,
796 pub discv5_addr: Option<Ipv4Addr>,
798 pub discv5_addr_ipv6: Option<Ipv6Addr>,
800 pub discv5_port: Option<u16>,
802 pub discv5_port_ipv6: Option<u16>,
804 pub discv5_lookup_interval: u64,
806 pub discv5_bootstrap_lookup_interval: u64,
808 pub discv5_bootstrap_lookup_countdown: u64,
810}
811
812impl DefaultDiscoveryArgs {
813 pub fn try_init(self) -> Result<(), Self> {
815 DISCOVERY_DEFAULTS.set(self)
816 }
817
818 pub fn get_global() -> &'static Self {
820 DISCOVERY_DEFAULTS.get_or_init(Self::default)
821 }
822
823 pub const fn with_disable_discovery(mut self, disable: bool) -> Self {
825 self.disable_discovery = disable;
826 self
827 }
828
829 pub const fn with_disable_dns_discovery(mut self, disable: bool) -> Self {
831 self.disable_dns_discovery = disable;
832 self
833 }
834
835 pub const fn with_disable_discv4_discovery(mut self, disable: bool) -> Self {
837 self.disable_discv4_discovery = disable;
838 self
839 }
840
841 pub const fn with_disable_discv5_discovery(mut self, disable: bool) -> Self {
843 self.disable_discv5_discovery = disable;
844 self
845 }
846
847 pub const fn with_disable_nat(mut self, disable: bool) -> Self {
849 self.disable_nat = disable;
850 self
851 }
852
853 pub const fn with_addr(mut self, addr: IpAddr) -> Self {
855 self.addr = addr;
856 self
857 }
858
859 pub const fn with_port(mut self, port: u16) -> Self {
861 self.port = port;
862 self
863 }
864
865 pub fn with_discv5_addr(mut self, addr: impl Into<Option<Ipv4Addr>>) -> Self {
867 self.discv5_addr = addr.into();
868 self
869 }
870
871 pub fn with_discv5_addr_ipv6(mut self, addr: impl Into<Option<Ipv6Addr>>) -> Self {
873 self.discv5_addr_ipv6 = addr.into();
874 self
875 }
876
877 pub fn with_discv5_port(mut self, port: impl Into<Option<u16>>) -> Self {
879 self.discv5_port = port.into();
880 self
881 }
882
883 pub fn with_discv5_port_ipv6(mut self, port: impl Into<Option<u16>>) -> Self {
885 self.discv5_port_ipv6 = port.into();
886 self
887 }
888
889 pub const fn with_discv5_lookup_interval(mut self, interval: u64) -> Self {
891 self.discv5_lookup_interval = interval;
892 self
893 }
894
895 pub const fn with_discv5_bootstrap_lookup_interval(mut self, interval: u64) -> Self {
897 self.discv5_bootstrap_lookup_interval = interval;
898 self
899 }
900
901 pub const fn with_discv5_bootstrap_lookup_countdown(mut self, countdown: u64) -> Self {
903 self.discv5_bootstrap_lookup_countdown = countdown;
904 self
905 }
906}
907
908impl Default for DefaultDiscoveryArgs {
909 fn default() -> Self {
910 Self {
911 disable_discovery: false,
912 disable_dns_discovery: false,
913 disable_discv4_discovery: false,
914 disable_discv5_discovery: false,
915 disable_nat: false,
916 addr: DEFAULT_DISCOVERY_ADDR,
917 port: DEFAULT_DISCOVERY_PORT,
918 discv5_addr: None,
919 discv5_addr_ipv6: None,
920 discv5_port: Some(DEFAULT_DISCOVERY_V5_PORT),
921 discv5_port_ipv6: Some(DEFAULT_DISCOVERY_V5_PORT),
922 discv5_lookup_interval: DEFAULT_SECONDS_LOOKUP_INTERVAL,
923 discv5_bootstrap_lookup_interval: DEFAULT_SECONDS_BOOTSTRAP_LOOKUP_INTERVAL,
924 discv5_bootstrap_lookup_countdown: DEFAULT_COUNT_BOOTSTRAP_LOOKUPS,
925 }
926 }
927}
928
929#[derive(Debug, Clone, Args, PartialEq, Eq)]
931pub struct DiscoveryArgs {
932 #[arg(short, long, default_value_if("dev", "true", "true"), default_value_t = DefaultDiscoveryArgs::get_global().disable_discovery)]
934 pub disable_discovery: bool,
935
936 #[arg(long, conflicts_with = "disable_discovery", default_value_t = DefaultDiscoveryArgs::get_global().disable_dns_discovery)]
938 pub disable_dns_discovery: bool,
939
940 #[arg(long, conflicts_with = "disable_discovery", default_value_t = DefaultDiscoveryArgs::get_global().disable_discv4_discovery)]
942 pub disable_discv4_discovery: bool,
943
944 #[arg(long, conflicts_with = "disable_discovery", hide = true)]
949 pub enable_discv5_discovery: bool,
950
951 #[arg(long, conflicts_with = "disable_discovery", default_value_t = DefaultDiscoveryArgs::get_global().disable_discv5_discovery)]
953 pub disable_discv5_discovery: bool,
954
955 #[arg(long, conflicts_with = "disable_discovery", default_value_t = DefaultDiscoveryArgs::get_global().disable_nat)]
957 pub disable_nat: bool,
958
959 #[arg(id = "discovery.addr", long = "discovery.addr", value_name = "DISCOVERY_ADDR", default_value_t = DefaultDiscoveryArgs::get_global().addr)]
964 pub addr: IpAddr,
965
966 #[arg(id = "discovery.port", long = "discovery.port", value_name = "DISCOVERY_PORT", default_value_t = DefaultDiscoveryArgs::get_global().port)]
968 pub port: u16,
969
970 #[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()))))]
973 pub discv5_addr: Option<Ipv4Addr>,
974
975 #[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()))))]
978 pub discv5_addr_ipv6: Option<Ipv6Addr>,
979
980 #[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()))))]
983 pub discv5_port: Option<u16>,
984
985 #[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()))))]
990 pub discv5_port_ipv6: Option<u16>,
991
992 #[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)]
995 pub discv5_lookup_interval: u64,
996
997 #[arg(id = "discovery.v5.bootstrap.lookup-interval", long = "discovery.v5.bootstrap.lookup-interval", value_name = "DISCOVERY_V5_BOOTSTRAP_LOOKUP_INTERVAL",
1000 default_value_t = DefaultDiscoveryArgs::get_global().discv5_bootstrap_lookup_interval)]
1001 pub discv5_bootstrap_lookup_interval: u64,
1002
1003 #[arg(id = "discovery.v5.bootstrap.lookup-countdown", long = "discovery.v5.bootstrap.lookup-countdown", value_name = "DISCOVERY_V5_BOOTSTRAP_LOOKUP_COUNTDOWN",
1005 default_value_t = DefaultDiscoveryArgs::get_global().discv5_bootstrap_lookup_countdown)]
1006 pub discv5_bootstrap_lookup_countdown: u64,
1007}
1008
1009impl DiscoveryArgs {
1010 pub fn apply_to_builder<N>(
1012 &self,
1013 mut network_config_builder: NetworkConfigBuilder<N>,
1014 rlpx_tcp_socket: SocketAddr,
1015 boot_nodes: impl IntoIterator<Item = NodeRecord>,
1016 ) -> NetworkConfigBuilder<N>
1017 where
1018 N: NetworkPrimitives,
1019 {
1020 if self.disable_discovery || self.disable_dns_discovery {
1021 network_config_builder = network_config_builder.disable_dns_discovery();
1022 }
1023
1024 if self.disable_discovery || self.disable_discv4_discovery {
1025 network_config_builder = network_config_builder.disable_discv4_discovery();
1026 }
1027
1028 if self.disable_nat {
1029 network_config_builder = network_config_builder.disable_nat();
1031 }
1032
1033 if self.should_enable_discv5() {
1034 network_config_builder = network_config_builder
1035 .discovery_v5(self.discovery_v5_builder(rlpx_tcp_socket, boot_nodes));
1036 }
1037
1038 network_config_builder
1039 }
1040
1041 pub fn discovery_v5_builder(
1043 &self,
1044 rlpx_tcp_socket: SocketAddr,
1045 boot_nodes: impl IntoIterator<Item = NodeRecord>,
1046 ) -> reth_discv5::ConfigBuilder {
1047 let Self {
1048 discv5_addr,
1049 discv5_addr_ipv6,
1050 discv5_port,
1051 discv5_port_ipv6,
1052 discv5_lookup_interval,
1053 discv5_bootstrap_lookup_interval,
1054 discv5_bootstrap_lookup_countdown,
1055 port,
1056 ..
1057 } = self;
1058
1059 let has_discv5_addr_args = discv5_addr.is_some() || discv5_addr_ipv6.is_some();
1060
1061 let discv5_addr_ipv4 = discv5_addr.or(match rlpx_tcp_socket {
1063 SocketAddr::V4(addr) => Some(*addr.ip()),
1064 SocketAddr::V6(_) => None,
1065 });
1066 let discv5_addr_ipv6 = discv5_addr_ipv6.or(match rlpx_tcp_socket {
1067 SocketAddr::V4(_) => None,
1068 SocketAddr::V6(addr) => Some(*addr.ip()),
1069 });
1070
1071 let mut discv5_config_builder =
1072 reth_discv5::discv5::ConfigBuilder::new(ListenConfig::from_two_sockets(
1073 discv5_addr_ipv4.map(|addr| SocketAddrV4::new(addr, discv5_port.unwrap_or(*port))),
1074 discv5_addr_ipv6
1075 .map(|addr| SocketAddrV6::new(addr, discv5_port_ipv6.unwrap_or(*port), 0, 0)),
1076 ));
1077
1078 if has_discv5_addr_args || self.disable_nat {
1079 discv5_config_builder.disable_enr_update();
1081 }
1082 reth_discv5::Config::builder(rlpx_tcp_socket)
1083 .discv5_config(discv5_config_builder.build())
1084 .add_unsigned_boot_nodes(boot_nodes)
1085 .lookup_interval(*discv5_lookup_interval)
1086 .bootstrap_lookup_interval(*discv5_bootstrap_lookup_interval)
1087 .bootstrap_lookup_countdown(*discv5_bootstrap_lookup_countdown)
1088 }
1089
1090 const fn should_enable_discv5(&self) -> bool {
1094 !(self.disable_discovery || self.disable_discv5_discovery)
1095 }
1096
1097 pub const fn with_unused_discovery_port(mut self) -> Self {
1100 self.port = 0;
1101 self.discv5_port = Some(0);
1102 self.discv5_port_ipv6 = Some(0);
1103 self
1104 }
1105
1106 pub fn with_discv5_port(mut self, port: impl Into<Option<u16>>) -> Self {
1108 self.discv5_port = port.into();
1109 self
1110 }
1111
1112 pub fn adjust_instance_ports(&mut self, instance: u16) {
1118 debug_assert_ne!(instance, 0, "instance must be non-zero");
1119 self.port += instance - 1;
1120 self.discv5_port = self.discv5_port.map(|port| port + instance - 1);
1121 self.discv5_port_ipv6 = self.discv5_port_ipv6.map(|port| port + instance - 1);
1122 }
1123}
1124
1125impl Default for DiscoveryArgs {
1126 fn default() -> Self {
1127 let DefaultDiscoveryArgs {
1128 disable_discovery,
1129 disable_dns_discovery,
1130 disable_discv4_discovery,
1131 disable_discv5_discovery,
1132 disable_nat,
1133 addr,
1134 port,
1135 discv5_addr,
1136 discv5_addr_ipv6,
1137 discv5_port,
1138 discv5_port_ipv6,
1139 discv5_lookup_interval,
1140 discv5_bootstrap_lookup_interval,
1141 discv5_bootstrap_lookup_countdown,
1142 } = *DefaultDiscoveryArgs::get_global();
1143 Self {
1144 disable_discovery,
1145 disable_dns_discovery,
1146 disable_discv4_discovery,
1147 enable_discv5_discovery: false,
1148 disable_discv5_discovery,
1149 disable_nat,
1150 addr,
1151 port,
1152 discv5_addr,
1153 discv5_addr_ipv6,
1154 discv5_port,
1155 discv5_port_ipv6,
1156 discv5_lookup_interval,
1157 discv5_bootstrap_lookup_interval,
1158 discv5_bootstrap_lookup_countdown,
1159 }
1160 }
1161}
1162
1163fn parse_block_num_hash(s: &str) -> Result<BlockNumHash, String> {
1165 if let Some((num_str, hash_str)) = s.split_once('=') {
1166 let number = num_str.parse().map_err(|_| format!("Invalid block number: {}", num_str))?;
1167 let hash = B256::from_str(hash_str).map_err(|_| format!("Invalid hash: {}", hash_str))?;
1168 Ok(BlockNumHash::new(number, hash))
1169 } else {
1170 let hash = B256::from_str(s).map_err(|_| format!("Invalid hash: {}", s))?;
1172 Ok(BlockNumHash::new(0, hash))
1173 }
1174}
1175
1176#[cfg(test)]
1177mod tests {
1178 use super::*;
1179 use clap::Parser;
1180 use reth_chainspec::MAINNET;
1181 use reth_config::Config;
1182 use reth_network::EthNetworkPrimitives;
1183 use reth_network_peers::NodeRecord;
1184 use secp256k1::SecretKey;
1185 use std::{
1186 fs,
1187 time::{SystemTime, UNIX_EPOCH},
1188 };
1189
1190 #[derive(Parser)]
1192 struct CommandParser<T: Args> {
1193 #[command(flatten)]
1194 args: T,
1195 }
1196
1197 #[test]
1198 fn parse_nat_args() {
1199 let args = CommandParser::<NetworkArgs>::parse_from(["reth", "--nat", "none"]).args;
1200 assert_eq!(args.nat, NatResolver::None);
1201
1202 let args =
1203 CommandParser::<NetworkArgs>::parse_from(["reth", "--nat", "extip:0.0.0.0"]).args;
1204 assert_eq!(args.nat, NatResolver::ExternalIp("0.0.0.0".parse().unwrap()));
1205 }
1206
1207 #[test]
1208 fn parse_peer_args() {
1209 let args =
1210 CommandParser::<NetworkArgs>::parse_from(["reth", "--max-outbound-peers", "50"]).args;
1211 assert_eq!(args.max_outbound_peers, Some(50));
1212 assert_eq!(args.max_inbound_peers, None);
1213
1214 let args = CommandParser::<NetworkArgs>::parse_from([
1215 "reth",
1216 "--max-outbound-peers",
1217 "75",
1218 "--max-inbound-peers",
1219 "15",
1220 ])
1221 .args;
1222 assert_eq!(args.max_outbound_peers, Some(75));
1223 assert_eq!(args.max_inbound_peers, Some(15));
1224 }
1225
1226 #[test]
1227 fn parse_trusted_peer_args() {
1228 let args =
1229 CommandParser::<NetworkArgs>::parse_from([
1230 "reth",
1231 "--trusted-peers",
1232 "enode://d860a01f9722d78051619d1e2351aba3f43f943f6f00718d1b9baa4101932a1f5011f16bb2b1bb35db20d6fe28fa0bf09636d26a87d31de9ec6203eeedb1f666@18.138.108.67:30303,enode://22a8232c3abc76a16ae9d6c3b164f98775fe226f0917b0ca871128a74a8e9630b458460865bab457221f1d448dd9791d24c4e5d88786180ac185df813a68d4de@3.209.45.79:30303"
1233 ])
1234 .args;
1235
1236 assert_eq!(
1237 args.trusted_peers,
1238 vec![
1239 "enode://d860a01f9722d78051619d1e2351aba3f43f943f6f00718d1b9baa4101932a1f5011f16bb2b1bb35db20d6fe28fa0bf09636d26a87d31de9ec6203eeedb1f666@18.138.108.67:30303".parse().unwrap(),
1240 "enode://22a8232c3abc76a16ae9d6c3b164f98775fe226f0917b0ca871128a74a8e9630b458460865bab457221f1d448dd9791d24c4e5d88786180ac185df813a68d4de@3.209.45.79:30303".parse().unwrap()
1241 ]
1242 );
1243 }
1244
1245 #[test]
1246 fn parse_retry_strategy_args() {
1247 let tests = vec![0, 10];
1248
1249 for retries in tests {
1250 let retries_str = retries.to_string();
1251 let args = CommandParser::<NetworkArgs>::parse_from([
1252 "reth",
1253 "--dns-retries",
1254 retries_str.as_str(),
1255 ])
1256 .args;
1257
1258 assert_eq!(args.dns_retries, retries);
1259 }
1260 }
1261
1262 #[test]
1263 fn transactions_manager_config_uses_max_pending_imports() {
1264 let args = NetworkArgs { max_pending_pool_imports: 50_000, ..Default::default() };
1265
1266 let config = args.transactions_manager_config();
1267
1268 assert_eq!(config.max_pending_pool_imports, 50_000);
1269 }
1270
1271 #[test]
1272 fn parse_disable_tx_gossip_args() {
1273 let args = CommandParser::<NetworkArgs>::parse_from(["reth", "--disable-tx-gossip"]).args;
1274 assert!(args.disable_tx_gossip);
1275 }
1276
1277 #[test]
1278 fn parse_max_peers_flag() {
1279 let args = CommandParser::<NetworkArgs>::parse_from(["reth", "--max-peers", "90"]).args;
1280
1281 assert_eq!(args.max_peers, Some(90));
1282 assert_eq!(args.max_outbound_peers, None);
1283 assert_eq!(args.max_inbound_peers, None);
1284 assert_eq!(args.resolved_max_outbound_peers(), Some(30));
1285 assert_eq!(args.resolved_max_inbound_peers(), Some(60));
1286 }
1287
1288 #[test]
1289 fn max_peers_conflicts_with_outbound() {
1290 let result = CommandParser::<NetworkArgs>::try_parse_from([
1291 "reth",
1292 "--max-peers",
1293 "90",
1294 "--max-outbound-peers",
1295 "50",
1296 ]);
1297 assert!(
1298 result.is_err(),
1299 "Should fail when both --max-peers and --max-outbound-peers are used"
1300 );
1301 }
1302
1303 #[test]
1304 fn max_peers_conflicts_with_inbound() {
1305 let result = CommandParser::<NetworkArgs>::try_parse_from([
1306 "reth",
1307 "--max-peers",
1308 "90",
1309 "--max-inbound-peers",
1310 "30",
1311 ]);
1312 assert!(
1313 result.is_err(),
1314 "Should fail when both --max-peers and --max-inbound-peers are used"
1315 );
1316 }
1317
1318 #[test]
1319 fn max_peers_split_calculation() {
1320 let args = CommandParser::<NetworkArgs>::parse_from(["reth", "--max-peers", "90"]).args;
1321
1322 assert_eq!(args.max_peers, Some(90));
1323 assert_eq!(args.resolved_max_outbound_peers(), Some(30));
1324 assert_eq!(args.resolved_max_inbound_peers(), Some(60));
1325 }
1326
1327 #[test]
1328 fn max_peers_small_values() {
1329 let args1 = CommandParser::<NetworkArgs>::parse_from(["reth", "--max-peers", "1"]).args;
1330 assert_eq!(args1.resolved_max_outbound_peers(), Some(1));
1331 assert_eq!(args1.resolved_max_inbound_peers(), Some(0));
1332
1333 let args2 = CommandParser::<NetworkArgs>::parse_from(["reth", "--max-peers", "2"]).args;
1334 assert_eq!(args2.resolved_max_outbound_peers(), Some(1));
1335 assert_eq!(args2.resolved_max_inbound_peers(), Some(1));
1336
1337 let args3 = CommandParser::<NetworkArgs>::parse_from(["reth", "--max-peers", "3"]).args;
1338 assert_eq!(args3.resolved_max_outbound_peers(), Some(1));
1339 assert_eq!(args3.resolved_max_inbound_peers(), Some(2));
1340 }
1341
1342 #[test]
1343 fn resolved_peers_without_max_peers() {
1344 let args = CommandParser::<NetworkArgs>::parse_from([
1345 "reth",
1346 "--max-outbound-peers",
1347 "75",
1348 "--max-inbound-peers",
1349 "15",
1350 ])
1351 .args;
1352
1353 assert_eq!(args.max_peers, None);
1354 assert_eq!(args.resolved_max_outbound_peers(), Some(75));
1355 assert_eq!(args.resolved_max_inbound_peers(), Some(15));
1356 }
1357
1358 #[test]
1359 fn resolved_peers_with_defaults() {
1360 let args = CommandParser::<NetworkArgs>::parse_from(["reth"]).args;
1361
1362 assert_eq!(args.max_peers, None);
1363 assert_eq!(args.resolved_max_outbound_peers(), None);
1364 assert_eq!(args.resolved_max_inbound_peers(), None);
1365 }
1366
1367 #[test]
1368 fn network_args_default_sanity_test() {
1369 let default_args = NetworkArgs::default();
1370 let args = CommandParser::<NetworkArgs>::parse_from(["reth"]).args;
1371
1372 assert_eq!(args, default_args);
1373 }
1374
1375 #[test]
1376 fn net_if_uses_resolved_addr_for_default_discovery_addr() {
1377 let args =
1378 CommandParser::<NetworkArgs>::parse_from(["reth", "--net-if.experimental", "en0"]).args;
1379 let listener_addr = IpAddr::V4(Ipv4Addr::new(192, 0, 2, 1));
1380
1381 assert_eq!(args.resolved_discovery_addr(listener_addr), listener_addr);
1382 }
1383
1384 #[test]
1385 fn net_if_preserves_custom_discovery_addr() {
1386 let custom_discovery_addr = IpAddr::V4(Ipv4Addr::new(192, 0, 2, 2));
1387 let args = CommandParser::<NetworkArgs>::parse_from([
1388 "reth",
1389 "--net-if.experimental",
1390 "en0",
1391 "--discovery.addr",
1392 "192.0.2.2",
1393 ])
1394 .args;
1395 let listener_addr = IpAddr::V4(Ipv4Addr::new(192, 0, 2, 1));
1396
1397 assert_eq!(args.resolved_discovery_addr(listener_addr), custom_discovery_addr);
1398 }
1399
1400 #[test]
1401 fn default_discovery_addr_is_preserved_without_net_if() {
1402 let args = CommandParser::<NetworkArgs>::parse_from(["reth"]).args;
1403 let listener_addr = IpAddr::V4(Ipv4Addr::new(192, 0, 2, 1));
1404
1405 assert_eq!(args.resolved_discovery_addr(listener_addr), DEFAULT_DISCOVERY_ADDR);
1406 }
1407
1408 #[test]
1409 fn parse_eth_max_message_size() {
1410 let args = CommandParser::<NetworkArgs>::parse_from([
1411 "reth",
1412 "--eth-max-message-size",
1413 "15728640",
1414 ])
1415 .args;
1416
1417 assert_eq!(args.eth_max_message_size, Some(NonZeroUsize::new(15 * 1024 * 1024).unwrap()));
1418 }
1419
1420 #[test]
1421 fn parse_eth_max_message_size_zero_rejected() {
1422 let result =
1423 CommandParser::<NetworkArgs>::try_parse_from(["reth", "--eth-max-message-size", "0"]);
1424 assert!(result.is_err());
1425 }
1426
1427 #[test]
1428 fn parse_eth_max_message_size_above_rlpx_cap() {
1429 let result = CommandParser::<NetworkArgs>::try_parse_from([
1430 "reth",
1431 "--eth-max-message-size",
1432 "16777216",
1433 ]);
1434 assert!(result.is_ok());
1435 let args = result.unwrap().args;
1436 assert_eq!(args.eth_max_message_size, Some(NonZeroUsize::new(16 * 1024 * 1024).unwrap()));
1437 }
1438
1439 #[test]
1440 fn parse_required_block_hashes() {
1441 let args = CommandParser::<NetworkArgs>::parse_from([
1442 "reth",
1443 "--required-block-hashes",
1444 "0x1111111111111111111111111111111111111111111111111111111111111111,23115201=0x2222222222222222222222222222222222222222222222222222222222222222",
1445 ])
1446 .args;
1447
1448 assert_eq!(args.required_block_hashes.len(), 2);
1449 assert_eq!(args.required_block_hashes[0].number, 0);
1451 assert_eq!(
1452 args.required_block_hashes[0].hash.to_string(),
1453 "0x1111111111111111111111111111111111111111111111111111111111111111"
1454 );
1455 assert_eq!(args.required_block_hashes[1].number, 23115201);
1457 assert_eq!(
1458 args.required_block_hashes[1].hash.to_string(),
1459 "0x2222222222222222222222222222222222222222222222222222222222222222"
1460 );
1461 }
1462
1463 #[test]
1464 fn parse_empty_required_block_hashes() {
1465 let args = CommandParser::<NetworkArgs>::parse_from(["reth"]).args;
1466 assert!(args.required_block_hashes.is_empty());
1467 }
1468
1469 #[test]
1470 fn test_parse_block_num_hash() {
1471 let result = parse_block_num_hash(
1473 "0x1111111111111111111111111111111111111111111111111111111111111111",
1474 );
1475 assert!(result.is_ok());
1476 assert_eq!(result.unwrap().number, 0);
1477
1478 let result = parse_block_num_hash(
1480 "23115201=0x2222222222222222222222222222222222222222222222222222222222222222",
1481 );
1482 assert!(result.is_ok());
1483 assert_eq!(result.unwrap().number, 23115201);
1484
1485 assert!(parse_block_num_hash("invalid").is_err());
1487 assert!(parse_block_num_hash(
1488 "abc=0x1111111111111111111111111111111111111111111111111111111111111111"
1489 )
1490 .is_err());
1491 }
1492
1493 #[test]
1494 fn parse_p2p_secret_key_hex() {
1495 let hex = "4c0883a69102937d6231471b5dbb6204fe512961708279f8c5c58b3b9c4e8b8f";
1496 let args =
1497 CommandParser::<NetworkArgs>::parse_from(["reth", "--p2p-secret-key-hex", hex]).args;
1498
1499 let expected: B256 = hex.parse().unwrap();
1500 assert_eq!(args.p2p_secret_key_hex, Some(expected));
1501 assert_eq!(args.p2p_secret_key, None);
1502 }
1503
1504 #[test]
1505 fn parse_p2p_secret_key_hex_with_0x_prefix() {
1506 let hex = "0x4c0883a69102937d6231471b5dbb6204fe512961708279f8c5c58b3b9c4e8b8f";
1507 let args =
1508 CommandParser::<NetworkArgs>::parse_from(["reth", "--p2p-secret-key-hex", hex]).args;
1509
1510 let expected: B256 = hex.parse().unwrap();
1511 assert_eq!(args.p2p_secret_key_hex, Some(expected));
1512 assert_eq!(args.p2p_secret_key, None);
1513 }
1514
1515 #[test]
1516 fn test_p2p_secret_key_and_hex_are_mutually_exclusive() {
1517 let result = CommandParser::<NetworkArgs>::try_parse_from([
1518 "reth",
1519 "--p2p-secret-key",
1520 "/path/to/key",
1521 "--p2p-secret-key-hex",
1522 "4c0883a69102937d6231471b5dbb6204fe512961708279f8c5c58b3b9c4e8b8f",
1523 ]);
1524
1525 assert!(result.is_err());
1526 }
1527
1528 #[test]
1529 fn test_secret_key_method_with_hex() {
1530 let hex = "4c0883a69102937d6231471b5dbb6204fe512961708279f8c5c58b3b9c4e8b8f";
1531 let args =
1532 CommandParser::<NetworkArgs>::parse_from(["reth", "--p2p-secret-key-hex", hex]).args;
1533
1534 let temp_dir = std::env::temp_dir();
1535 let default_path = temp_dir.join("default_key");
1536 let secret_key = args.secret_key(default_path).unwrap();
1537
1538 assert_eq!(alloy_primitives::hex::encode(secret_key.to_secret_bytes()), hex);
1540 }
1541
1542 #[test]
1543 fn parse_netrestrict_single_network() {
1544 let args =
1545 CommandParser::<NetworkArgs>::parse_from(["reth", "--netrestrict", "192.168.0.0/16"])
1546 .args;
1547
1548 assert_eq!(args.netrestrict, Some("192.168.0.0/16".to_string()));
1549
1550 let ip_filter = args.ip_filter().unwrap();
1551 assert!(ip_filter.has_restrictions());
1552 assert!(ip_filter.is_allowed(&"192.168.1.1".parse().unwrap()));
1553 assert!(!ip_filter.is_allowed(&"10.0.0.1".parse().unwrap()));
1554 }
1555
1556 #[test]
1557 fn parse_netrestrict_multiple_networks() {
1558 let args = CommandParser::<NetworkArgs>::parse_from([
1559 "reth",
1560 "--netrestrict",
1561 "192.168.0.0/16,10.0.0.0/8",
1562 ])
1563 .args;
1564
1565 assert_eq!(args.netrestrict, Some("192.168.0.0/16,10.0.0.0/8".to_string()));
1566
1567 let ip_filter = args.ip_filter().unwrap();
1568 assert!(ip_filter.has_restrictions());
1569 assert!(ip_filter.is_allowed(&"192.168.1.1".parse().unwrap()));
1570 assert!(ip_filter.is_allowed(&"10.5.10.20".parse().unwrap()));
1571 assert!(!ip_filter.is_allowed(&"172.16.0.1".parse().unwrap()));
1572 }
1573
1574 #[test]
1575 fn parse_netrestrict_ipv6() {
1576 let args =
1577 CommandParser::<NetworkArgs>::parse_from(["reth", "--netrestrict", "2001:db8::/32"])
1578 .args;
1579
1580 let ip_filter = args.ip_filter().unwrap();
1581 assert!(ip_filter.has_restrictions());
1582 assert!(ip_filter.is_allowed(&"2001:db8::1".parse().unwrap()));
1583 assert!(!ip_filter.is_allowed(&"2001:db9::1".parse().unwrap()));
1584 }
1585
1586 #[test]
1587 fn netrestrict_not_set() {
1588 let args = CommandParser::<NetworkArgs>::parse_from(["reth"]).args;
1589 assert_eq!(args.netrestrict, None);
1590
1591 let ip_filter = args.ip_filter().unwrap();
1592 assert!(!ip_filter.has_restrictions());
1593 assert!(ip_filter.is_allowed(&"192.168.1.1".parse().unwrap()));
1594 assert!(ip_filter.is_allowed(&"10.0.0.1".parse().unwrap()));
1595 }
1596
1597 #[test]
1598 fn netrestrict_invalid_cidr() {
1599 let args =
1600 CommandParser::<NetworkArgs>::parse_from(["reth", "--netrestrict", "invalid-cidr"])
1601 .args;
1602
1603 assert!(args.ip_filter().is_err());
1604 }
1605
1606 #[test]
1607 fn snap_v2_flag_controls_advertisement() {
1608 for (cli, enabled) in [(vec!["reth"], false), (vec!["reth", "--snap.v2"], true)] {
1609 let args = CommandParser::<NetworkArgs>::parse_from(cli).args;
1610 assert_eq!(args.snap_v2, enabled);
1611 let config = args
1612 .network_config::<EthNetworkPrimitives>(
1613 &Config::default(),
1614 MAINNET.clone(),
1615 SecretKey::from_secret_bytes([1u8; 32]).unwrap(),
1616 PathBuf::new(),
1617 Runtime::test(),
1618 )
1619 .build_with_noop_provider(MAINNET.clone());
1620 let versions = config
1621 .hello_message
1622 .protocols
1623 .iter()
1624 .filter(|protocol| protocol.cap.name == "snap")
1625 .map(|protocol| protocol.cap.version)
1626 .collect::<Vec<_>>();
1627 assert_eq!(versions, if enabled { vec![2] } else { vec![] });
1628 }
1629 }
1630
1631 #[test]
1632 fn network_config_preserves_basic_nodes_from_peers_file() {
1633 let enode = "enode://6f8a80d14311c39f35f516fa664deaaaa13e85b2f7493f37f6144d86991ec012937307647bd3b9a82abe2974e1407241d54947bbb39763a4cac9f77166ad92a0@10.3.58.6:30303?discport=30301";
1634 let unique = SystemTime::now().duration_since(UNIX_EPOCH).unwrap().as_nanos();
1635
1636 let peers_file = std::env::temp_dir().join(format!("reth_peers_test_{}.json", unique));
1637 fs::write(&peers_file, format!("[\"{}\"]", enode)).expect("write peers file");
1638
1639 let args = NetworkArgs {
1641 peers_file: Some(peers_file.clone()),
1642 no_persist_peers: false,
1643 ..Default::default()
1644 };
1645
1646 let secret_key = SecretKey::from_secret_bytes([1u8; 32]).unwrap();
1648 let builder = args.network_config::<EthNetworkPrimitives>(
1649 &Config::default(),
1650 MAINNET.clone(),
1651 secret_key,
1652 peers_file.clone(),
1653 Runtime::test(),
1654 );
1655
1656 let net_cfg = builder.build_with_noop_provider(MAINNET.clone());
1657
1658 let node: NodeRecord = enode.parse().unwrap();
1660 assert!(net_cfg.peers_config.persisted_peers.iter().any(|p| p.record == node));
1661
1662 let _ = fs::remove_file(&peers_file);
1664 }
1665
1666 #[test]
1667 fn network_config_prefers_cli_bootnodes_over_config_file() {
1668 let enr = "enr:-IS4QHCYrYZbAKWCBRlAy5zzaDZXJBGkcnh4MHcBFZntXNFrdvJjX04jRzjzCBOonrkTfj499SZuOh8R33Ls8RRcy5wBgmlkgnY0gmlwhH8AAAGJc2VjcDI1NmsxoQPKY0yuDUmstAHYpMa2_oxVtw0RW_QAdpzBQA8yWM0xOIN1ZHCCdl8";
1669 let enode = "enode://6f8a80d14311c39f35f516fa664deaaaa13e85b2f7493f37f6144d86991ec012937307647bd3b9a82abe2974e1407241d54947bbb39763a4cac9f77166ad92a0@10.3.58.6:30303";
1670 let config = Config { bootnodes: vec![enr.parse().unwrap()], ..Default::default() };
1671 let secret_key = SecretKey::from_secret_bytes([1u8; 32]).unwrap();
1672
1673 let boot_nodes = |args: &NetworkArgs| {
1674 args.network_config::<EthNetworkPrimitives>(
1675 &config,
1676 MAINNET.clone(),
1677 secret_key,
1678 PathBuf::from("peers.json"),
1679 Runtime::test(),
1680 )
1681 .boot_nodes_iter()
1682 .cloned()
1683 .collect::<Vec<_>>()
1684 };
1685
1686 let args = NetworkArgs { no_persist_peers: true, ..Default::default() };
1687 assert_eq!(boot_nodes(&args), vec![enr.parse::<TrustedPeer>().unwrap()]);
1688
1689 let args = NetworkArgs { bootnodes: Some(vec![enode.parse().unwrap()]), ..args };
1690 assert_eq!(boot_nodes(&args), vec![enode.parse::<TrustedPeer>().unwrap()]);
1691 }
1692
1693 #[test]
1694 fn network_config_enforce_enr_fork_id_from_config_or_cli() {
1695 let secret_key = SecretKey::from_secret_bytes([1u8; 32]).unwrap();
1696
1697 let enforced = |args: &NetworkArgs, config: &Config| {
1698 args.network_config::<EthNetworkPrimitives>(
1699 config,
1700 MAINNET.clone(),
1701 secret_key,
1702 PathBuf::from("peers.json"),
1703 Runtime::test(),
1704 )
1705 .build_with_noop_provider(MAINNET.clone())
1706 .peers_config
1707 .enforce_enr_fork_id
1708 };
1709
1710 let args = NetworkArgs { no_persist_peers: true, ..Default::default() };
1711 let mut config = Config::default();
1712 assert!(!enforced(&args, &config));
1713
1714 config.peers.enforce_enr_fork_id = true;
1715 assert!(enforced(&args, &config));
1716
1717 config.peers.enforce_enr_fork_id = false;
1718 let args = NetworkArgs { enforce_enr_fork_id: true, ..args };
1719 assert!(enforced(&args, &config));
1720 }
1721}