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