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")]
379 pub net_if: Option<String>,
380
381 #[arg(long = "tx-propagation-policy", default_value_t = DefaultNetworkArgs::get_global().tx_propagation_policy)]
385 pub tx_propagation_policy: TransactionPropagationKind,
386
387 #[arg(long = "tx-ingress-policy", default_value_t = DefaultNetworkArgs::get_global().tx_ingress_policy)]
391 pub tx_ingress_policy: TransactionIngressPolicy,
392
393 #[arg(long = "disable-tx-gossip")]
398 pub disable_tx_gossip: bool,
399
400 #[arg(
405 long = "tx-propagation-mode",
406 default_value_t = DefaultNetworkArgs::get_global().propagation_mode,
407 help = "Transaction propagation mode (sqrt, all, max:<number>)"
408 )]
409 pub propagation_mode: TransactionPropagationMode,
410
411 #[arg(long = "required-block-hashes", value_delimiter = ',', value_parser = parse_block_num_hash)]
415 pub required_block_hashes: Vec<BlockNumHash>,
416
417 #[arg(long)]
419 pub network_id: Option<u64>,
420
421 #[arg(long = "eth-max-message-size", value_name = "BYTES")]
423 pub eth_max_message_size: Option<NonZeroUsize>,
424
425 #[arg(long, value_name = "NETRESTRICT")]
432 pub netrestrict: Option<String>,
433
434 #[arg(long, default_value_t = DefaultNetworkArgs::get_global().enforce_enr_fork_id)]
440 pub enforce_enr_fork_id: bool,
441}
442
443impl NetworkArgs {
444 pub fn resolved_addr(&self) -> IpAddr {
446 if let Some(ref if_name) = self.net_if {
447 let if_name = if if_name.is_empty() { DEFAULT_NET_IF_NAME } else { if_name };
448 return match reth_net_nat::net_if::resolve_net_if_ip(if_name) {
449 Ok(addr) => addr,
450 Err(err) => {
451 error!(target: "reth::cli",
452 if_name,
453 %err,
454 "Failed to read network interface IP"
455 );
456
457 DEFAULT_DISCOVERY_ADDR
458 }
459 };
460 }
461
462 self.addr
463 }
464
465 pub fn resolved_bootnodes(&self) -> Option<Vec<NodeRecord>> {
467 self.bootnodes.clone().map(|bootnodes| {
468 bootnodes.into_iter().filter_map(|node| node.resolve_blocking().ok()).collect()
469 })
470 }
471
472 pub fn resolved_max_inbound_peers(&self) -> Option<usize> {
474 if let Some(max_peers) = self.max_peers {
475 if max_peers == 0 {
476 Some(0)
477 } else {
478 let outbound = (max_peers / 3).max(1);
479 Some(max_peers.saturating_sub(outbound))
480 }
481 } else {
482 self.max_inbound_peers
483 }
484 }
485
486 pub fn resolved_max_outbound_peers(&self) -> Option<usize> {
488 if let Some(max_peers) = self.max_peers {
489 if max_peers == 0 {
490 Some(0)
491 } else {
492 Some((max_peers / 3).max(1))
493 }
494 } else {
495 self.max_outbound_peers
496 }
497 }
498
499 pub const fn transactions_manager_config(&self) -> TransactionsManagerConfig {
501 TransactionsManagerConfig {
502 transaction_fetcher_config: TransactionFetcherConfig::new(
503 self.max_concurrent_tx_requests,
504 self.max_concurrent_tx_requests_per_peer,
505 self.soft_limit_byte_size_pooled_transactions_response,
506 self.soft_limit_byte_size_pooled_transactions_response_on_pack_request,
507 self.max_capacity_cache_txns_pending_fetch,
508 ),
509 max_transactions_seen_by_peer_history: self.max_seen_tx_history,
510 propagation_mode: self.propagation_mode,
511 ingress_policy: self.tx_ingress_policy,
512 tx_channel_memory_limit_bytes: self.tx_channel_memory_limit_bytes,
513 }
514 }
515
516 pub fn network_config<N: NetworkPrimitives>(
528 &self,
529 config: &Config,
530 chain_spec: impl EthChainSpec,
531 secret_key: SecretKey,
532 default_peers_file: PathBuf,
533 executor: Runtime,
534 ) -> NetworkConfigBuilder<N> {
535 let addr = self.resolved_addr();
536 let chain_bootnodes = self
537 .resolved_bootnodes()
538 .unwrap_or_else(|| chain_spec.bootnodes().unwrap_or_else(mainnet_nodes));
539 let peers_file = self.peers_file.clone().unwrap_or(default_peers_file);
540
541 let ip_filter = self.ip_filter().unwrap_or_default();
543 let peers_config = config
544 .peers_config_with_basic_nodes_from_file(
545 self.persistent_peers_file(peers_file).as_deref(),
546 )
547 .with_max_inbound_opt(self.resolved_max_inbound_peers())
548 .with_max_outbound_opt(self.resolved_max_outbound_peers())
549 .with_ip_filter(ip_filter)
550 .with_enforce_enr_fork_id(self.enforce_enr_fork_id);
551
552 NetworkConfigBuilder::<N>::new(secret_key, executor)
554 .external_ip_resolver(self.nat.clone())
555 .sessions_config(
556 config.sessions.clone().with_upscaled_event_buffer(peers_config.max_peers()),
557 )
558 .peer_config(peers_config)
559 .boot_nodes(chain_bootnodes.clone())
560 .transactions_manager_config(self.transactions_manager_config())
561 .apply(|builder| {
563 let peer_id = builder.get_peer_id();
564 builder.hello_message(
565 HelloMessageWithProtocols::builder(peer_id)
566 .client_version(&self.identity)
567 .build(),
568 )
569 })
570 .apply(|builder| {
572 let rlpx_socket = (addr, self.port).into();
573 self.discovery.apply_to_builder(builder, rlpx_socket, chain_bootnodes)
574 })
575 .listener_addr(SocketAddr::new(addr, self.port))
576 .discovery_addr(SocketAddr::new(self.discovery.addr, self.discovery.port))
577 .disable_tx_gossip(self.disable_tx_gossip)
578 .required_block_hashes(self.required_block_hashes.clone())
579 .eth_max_message_size_opt(self.eth_max_message_size.map(NonZeroUsize::get))
580 .network_id(self.network_id)
581 }
582
583 pub fn persistent_peers_file(&self, peers_file: PathBuf) -> Option<PathBuf> {
585 self.no_persist_peers.not().then_some(peers_file)
586 }
587
588 pub const fn with_discovery(mut self, discovery: DiscoveryArgs) -> Self {
590 self.discovery = discovery;
591 self
592 }
593
594 pub const fn with_unused_p2p_port(mut self) -> Self {
597 self.port = 0;
598 self
599 }
600
601 pub const fn with_unused_ports(mut self) -> Self {
604 self = self.with_unused_p2p_port();
605 self.discovery = self.discovery.with_unused_discovery_port();
606 self
607 }
608
609 pub fn with_nat_resolver(mut self, nat: NatResolver) -> Self {
611 self.nat = nat;
612 self
613 }
614
615 pub fn adjust_instance_ports(&mut self, instance: Option<u16>) {
621 if let Some(instance) = instance {
622 debug_assert_ne!(instance, 0, "instance must be non-zero");
623 self.port += instance - 1;
624 self.discovery.adjust_instance_ports(instance);
625 }
626 }
627
628 pub async fn resolve_trusted_peers(&self) -> Result<Vec<NodeRecord>, std::io::Error> {
630 futures::future::try_join_all(
631 self.trusted_peers.iter().map(|peer| async move { peer.resolve().await }),
632 )
633 .await
634 }
635
636 pub fn secret_key(
642 &self,
643 default_secret_key_path: PathBuf,
644 ) -> Result<SecretKey, SecretKeyError> {
645 if let Some(b256) = &self.p2p_secret_key_hex {
646 SecretKey::from_slice(b256.as_slice()).map_err(SecretKeyError::SecretKeyDecodeError)
648 } else {
649 let secret_key_path = self.p2p_secret_key.clone().unwrap_or(default_secret_key_path);
651 get_secret_key(&secret_key_path)
652 }
653 }
654
655 pub fn ip_filter(&self) -> Result<IpFilter, ipnet::AddrParseError> {
659 if let Some(netrestrict) = &self.netrestrict {
660 IpFilter::from_cidr_string(netrestrict)
661 } else {
662 Ok(IpFilter::allow_all())
663 }
664 }
665}
666
667impl Default for NetworkArgs {
668 fn default() -> Self {
669 let DefaultNetworkArgs {
670 dns_retries,
671 nat,
672 addr,
673 port,
674 max_concurrent_tx_requests,
675 max_concurrent_tx_requests_per_peer,
676 max_seen_tx_history,
677 max_pending_pool_imports,
678 soft_limit_byte_size_pooled_transactions_response,
679 soft_limit_byte_size_pooled_transactions_response_on_pack_request,
680 max_capacity_cache_txns_pending_fetch,
681 tx_channel_memory_limit_bytes,
682 tx_propagation_policy,
683 tx_ingress_policy,
684 propagation_mode,
685 enforce_enr_fork_id,
686 } = DefaultNetworkArgs::get_global().clone();
687 Self {
688 discovery: DiscoveryArgs::default(),
689 trusted_peers: vec![],
690 trusted_only: false,
691 bootnodes: None,
692 dns_retries,
693 peers_file: None,
694 identity: version_metadata().p2p_client_version.to_string(),
695 p2p_secret_key: None,
696 p2p_secret_key_hex: None,
697 no_persist_peers: false,
698 nat,
699 addr,
700 port,
701 max_outbound_peers: None,
702 max_inbound_peers: None,
703 max_peers: None,
704 max_concurrent_tx_requests,
705 max_concurrent_tx_requests_per_peer,
706 soft_limit_byte_size_pooled_transactions_response,
707 soft_limit_byte_size_pooled_transactions_response_on_pack_request,
708 max_pending_pool_imports,
709 max_seen_tx_history,
710 max_capacity_cache_txns_pending_fetch,
711 tx_channel_memory_limit_bytes,
712 net_if: None,
713 tx_propagation_policy,
714 tx_ingress_policy,
715 disable_tx_gossip: false,
716 propagation_mode,
717 required_block_hashes: vec![],
718 network_id: None,
719 eth_max_message_size: None,
720 netrestrict: None,
721 enforce_enr_fork_id,
722 }
723 }
724}
725
726static DISCOVERY_DEFAULTS: OnceLock<DefaultDiscoveryArgs> = OnceLock::new();
728
729#[derive(Debug, Clone, Copy)]
731pub struct DefaultDiscoveryArgs {
732 pub disable_discovery: bool,
734 pub disable_dns_discovery: bool,
736 pub disable_discv4_discovery: bool,
738 pub disable_discv5_discovery: bool,
740 pub disable_nat: bool,
742 pub addr: IpAddr,
744 pub port: u16,
746 pub discv5_addr: Option<Ipv4Addr>,
748 pub discv5_addr_ipv6: Option<Ipv6Addr>,
750 pub discv5_port: Option<u16>,
752 pub discv5_port_ipv6: Option<u16>,
754 pub discv5_lookup_interval: u64,
756 pub discv5_bootstrap_lookup_interval: u64,
758 pub discv5_bootstrap_lookup_countdown: u64,
760}
761
762impl DefaultDiscoveryArgs {
763 pub fn try_init(self) -> Result<(), Self> {
765 DISCOVERY_DEFAULTS.set(self)
766 }
767
768 pub fn get_global() -> &'static Self {
770 DISCOVERY_DEFAULTS.get_or_init(Self::default)
771 }
772
773 pub const fn with_disable_discovery(mut self, disable: bool) -> Self {
775 self.disable_discovery = disable;
776 self
777 }
778
779 pub const fn with_disable_dns_discovery(mut self, disable: bool) -> Self {
781 self.disable_dns_discovery = disable;
782 self
783 }
784
785 pub const fn with_disable_discv4_discovery(mut self, disable: bool) -> Self {
787 self.disable_discv4_discovery = disable;
788 self
789 }
790
791 pub const fn with_disable_discv5_discovery(mut self, disable: bool) -> Self {
793 self.disable_discv5_discovery = disable;
794 self
795 }
796
797 pub const fn with_disable_nat(mut self, disable: bool) -> Self {
799 self.disable_nat = disable;
800 self
801 }
802
803 pub const fn with_addr(mut self, addr: IpAddr) -> Self {
805 self.addr = addr;
806 self
807 }
808
809 pub const fn with_port(mut self, port: u16) -> Self {
811 self.port = port;
812 self
813 }
814
815 pub fn with_discv5_addr(mut self, addr: impl Into<Option<Ipv4Addr>>) -> Self {
817 self.discv5_addr = addr.into();
818 self
819 }
820
821 pub fn with_discv5_addr_ipv6(mut self, addr: impl Into<Option<Ipv6Addr>>) -> Self {
823 self.discv5_addr_ipv6 = addr.into();
824 self
825 }
826
827 pub fn with_discv5_port(mut self, port: impl Into<Option<u16>>) -> Self {
829 self.discv5_port = port.into();
830 self
831 }
832
833 pub fn with_discv5_port_ipv6(mut self, port: impl Into<Option<u16>>) -> Self {
835 self.discv5_port_ipv6 = port.into();
836 self
837 }
838
839 pub const fn with_discv5_lookup_interval(mut self, interval: u64) -> Self {
841 self.discv5_lookup_interval = interval;
842 self
843 }
844
845 pub const fn with_discv5_bootstrap_lookup_interval(mut self, interval: u64) -> Self {
847 self.discv5_bootstrap_lookup_interval = interval;
848 self
849 }
850
851 pub const fn with_discv5_bootstrap_lookup_countdown(mut self, countdown: u64) -> Self {
853 self.discv5_bootstrap_lookup_countdown = countdown;
854 self
855 }
856}
857
858impl Default for DefaultDiscoveryArgs {
859 fn default() -> Self {
860 Self {
861 disable_discovery: false,
862 disable_dns_discovery: false,
863 disable_discv4_discovery: false,
864 disable_discv5_discovery: false,
865 disable_nat: false,
866 addr: DEFAULT_DISCOVERY_ADDR,
867 port: DEFAULT_DISCOVERY_PORT,
868 discv5_addr: None,
869 discv5_addr_ipv6: None,
870 discv5_port: Some(DEFAULT_DISCOVERY_V5_PORT),
871 discv5_port_ipv6: Some(DEFAULT_DISCOVERY_V5_PORT),
872 discv5_lookup_interval: DEFAULT_SECONDS_LOOKUP_INTERVAL,
873 discv5_bootstrap_lookup_interval: DEFAULT_SECONDS_BOOTSTRAP_LOOKUP_INTERVAL,
874 discv5_bootstrap_lookup_countdown: DEFAULT_COUNT_BOOTSTRAP_LOOKUPS,
875 }
876 }
877}
878
879#[derive(Debug, Clone, Args, PartialEq, Eq)]
881pub struct DiscoveryArgs {
882 #[arg(short, long, default_value_if("dev", "true", "true"), default_value_t = DefaultDiscoveryArgs::get_global().disable_discovery)]
884 pub disable_discovery: bool,
885
886 #[arg(long, conflicts_with = "disable_discovery", default_value_t = DefaultDiscoveryArgs::get_global().disable_dns_discovery)]
888 pub disable_dns_discovery: bool,
889
890 #[arg(long, conflicts_with = "disable_discovery", default_value_t = DefaultDiscoveryArgs::get_global().disable_discv4_discovery)]
892 pub disable_discv4_discovery: bool,
893
894 #[arg(long, conflicts_with = "disable_discovery", hide = true)]
899 pub enable_discv5_discovery: bool,
900
901 #[arg(long, conflicts_with = "disable_discovery", default_value_t = DefaultDiscoveryArgs::get_global().disable_discv5_discovery)]
903 pub disable_discv5_discovery: bool,
904
905 #[arg(long, conflicts_with = "disable_discovery", default_value_t = DefaultDiscoveryArgs::get_global().disable_nat)]
907 pub disable_nat: bool,
908
909 #[arg(id = "discovery.addr", long = "discovery.addr", value_name = "DISCOVERY_ADDR", default_value_t = DefaultDiscoveryArgs::get_global().addr)]
911 pub addr: IpAddr,
912
913 #[arg(id = "discovery.port", long = "discovery.port", value_name = "DISCOVERY_PORT", default_value_t = DefaultDiscoveryArgs::get_global().port)]
915 pub port: u16,
916
917 #[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()))))]
920 pub discv5_addr: Option<Ipv4Addr>,
921
922 #[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()))))]
925 pub discv5_addr_ipv6: Option<Ipv6Addr>,
926
927 #[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()))))]
930 pub discv5_port: Option<u16>,
931
932 #[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()))))]
937 pub discv5_port_ipv6: Option<u16>,
938
939 #[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)]
942 pub discv5_lookup_interval: u64,
943
944 #[arg(id = "discovery.v5.bootstrap.lookup-interval", long = "discovery.v5.bootstrap.lookup-interval", value_name = "DISCOVERY_V5_BOOTSTRAP_LOOKUP_INTERVAL",
947 default_value_t = DefaultDiscoveryArgs::get_global().discv5_bootstrap_lookup_interval)]
948 pub discv5_bootstrap_lookup_interval: u64,
949
950 #[arg(id = "discovery.v5.bootstrap.lookup-countdown", long = "discovery.v5.bootstrap.lookup-countdown", value_name = "DISCOVERY_V5_BOOTSTRAP_LOOKUP_COUNTDOWN",
952 default_value_t = DefaultDiscoveryArgs::get_global().discv5_bootstrap_lookup_countdown)]
953 pub discv5_bootstrap_lookup_countdown: u64,
954}
955
956impl DiscoveryArgs {
957 pub fn apply_to_builder<N>(
959 &self,
960 mut network_config_builder: NetworkConfigBuilder<N>,
961 rlpx_tcp_socket: SocketAddr,
962 boot_nodes: impl IntoIterator<Item = NodeRecord>,
963 ) -> NetworkConfigBuilder<N>
964 where
965 N: NetworkPrimitives,
966 {
967 if self.disable_discovery || self.disable_dns_discovery {
968 network_config_builder = network_config_builder.disable_dns_discovery();
969 }
970
971 if self.disable_discovery || self.disable_discv4_discovery {
972 network_config_builder = network_config_builder.disable_discv4_discovery();
973 }
974
975 if self.disable_nat {
976 network_config_builder = network_config_builder.disable_nat();
978 }
979
980 if self.should_enable_discv5() {
981 network_config_builder = network_config_builder
982 .discovery_v5(self.discovery_v5_builder(rlpx_tcp_socket, boot_nodes));
983 }
984
985 network_config_builder
986 }
987
988 pub fn discovery_v5_builder(
990 &self,
991 rlpx_tcp_socket: SocketAddr,
992 boot_nodes: impl IntoIterator<Item = NodeRecord>,
993 ) -> reth_discv5::ConfigBuilder {
994 let Self {
995 discv5_addr,
996 discv5_addr_ipv6,
997 discv5_port,
998 discv5_port_ipv6,
999 discv5_lookup_interval,
1000 discv5_bootstrap_lookup_interval,
1001 discv5_bootstrap_lookup_countdown,
1002 port,
1003 ..
1004 } = self;
1005
1006 let has_discv5_addr_args = discv5_addr.is_some() || discv5_addr_ipv6.is_some();
1007
1008 let discv5_addr_ipv4 = discv5_addr.or(match rlpx_tcp_socket {
1010 SocketAddr::V4(addr) => Some(*addr.ip()),
1011 SocketAddr::V6(_) => None,
1012 });
1013 let discv5_addr_ipv6 = discv5_addr_ipv6.or(match rlpx_tcp_socket {
1014 SocketAddr::V4(_) => None,
1015 SocketAddr::V6(addr) => Some(*addr.ip()),
1016 });
1017
1018 let mut discv5_config_builder =
1019 reth_discv5::discv5::ConfigBuilder::new(ListenConfig::from_two_sockets(
1020 discv5_addr_ipv4.map(|addr| SocketAddrV4::new(addr, discv5_port.unwrap_or(*port))),
1021 discv5_addr_ipv6
1022 .map(|addr| SocketAddrV6::new(addr, discv5_port_ipv6.unwrap_or(*port), 0, 0)),
1023 ));
1024
1025 if has_discv5_addr_args || self.disable_nat {
1026 discv5_config_builder.disable_enr_update();
1028 }
1029 reth_discv5::Config::builder(rlpx_tcp_socket)
1030 .discv5_config(discv5_config_builder.build())
1031 .add_unsigned_boot_nodes(boot_nodes)
1032 .lookup_interval(*discv5_lookup_interval)
1033 .bootstrap_lookup_interval(*discv5_bootstrap_lookup_interval)
1034 .bootstrap_lookup_countdown(*discv5_bootstrap_lookup_countdown)
1035 }
1036
1037 const fn should_enable_discv5(&self) -> bool {
1041 if self.disable_discovery || self.disable_discv5_discovery {
1042 return false;
1043 }
1044
1045 true
1046 }
1047
1048 pub const fn with_unused_discovery_port(mut self) -> Self {
1051 self.port = 0;
1052 self.discv5_port = Some(0);
1053 self.discv5_port_ipv6 = Some(0);
1054 self
1055 }
1056
1057 pub fn with_discv5_port(mut self, port: impl Into<Option<u16>>) -> Self {
1059 self.discv5_port = port.into();
1060 self
1061 }
1062
1063 pub fn adjust_instance_ports(&mut self, instance: u16) {
1069 debug_assert_ne!(instance, 0, "instance must be non-zero");
1070 self.port += instance - 1;
1071 self.discv5_port = self.discv5_port.map(|port| port + instance - 1);
1072 self.discv5_port_ipv6 = self.discv5_port_ipv6.map(|port| port + instance - 1);
1073 }
1074}
1075
1076impl Default for DiscoveryArgs {
1077 fn default() -> Self {
1078 let DefaultDiscoveryArgs {
1079 disable_discovery,
1080 disable_dns_discovery,
1081 disable_discv4_discovery,
1082 disable_discv5_discovery,
1083 disable_nat,
1084 addr,
1085 port,
1086 discv5_addr,
1087 discv5_addr_ipv6,
1088 discv5_port,
1089 discv5_port_ipv6,
1090 discv5_lookup_interval,
1091 discv5_bootstrap_lookup_interval,
1092 discv5_bootstrap_lookup_countdown,
1093 } = *DefaultDiscoveryArgs::get_global();
1094 Self {
1095 disable_discovery,
1096 disable_dns_discovery,
1097 disable_discv4_discovery,
1098 enable_discv5_discovery: false,
1099 disable_discv5_discovery,
1100 disable_nat,
1101 addr,
1102 port,
1103 discv5_addr,
1104 discv5_addr_ipv6,
1105 discv5_port,
1106 discv5_port_ipv6,
1107 discv5_lookup_interval,
1108 discv5_bootstrap_lookup_interval,
1109 discv5_bootstrap_lookup_countdown,
1110 }
1111 }
1112}
1113
1114fn parse_block_num_hash(s: &str) -> Result<BlockNumHash, String> {
1116 if let Some((num_str, hash_str)) = s.split_once('=') {
1117 let number = num_str.parse().map_err(|_| format!("Invalid block number: {}", num_str))?;
1118 let hash = B256::from_str(hash_str).map_err(|_| format!("Invalid hash: {}", hash_str))?;
1119 Ok(BlockNumHash::new(number, hash))
1120 } else {
1121 let hash = B256::from_str(s).map_err(|_| format!("Invalid hash: {}", s))?;
1123 Ok(BlockNumHash::new(0, hash))
1124 }
1125}
1126
1127#[cfg(test)]
1128mod tests {
1129 use super::*;
1130 use clap::Parser;
1131 use reth_chainspec::MAINNET;
1132 use reth_config::Config;
1133 use reth_network_peers::NodeRecord;
1134 use secp256k1::SecretKey;
1135 use std::{
1136 fs,
1137 time::{SystemTime, UNIX_EPOCH},
1138 };
1139
1140 #[derive(Parser)]
1142 struct CommandParser<T: Args> {
1143 #[command(flatten)]
1144 args: T,
1145 }
1146
1147 #[test]
1148 fn parse_nat_args() {
1149 let args = CommandParser::<NetworkArgs>::parse_from(["reth", "--nat", "none"]).args;
1150 assert_eq!(args.nat, NatResolver::None);
1151
1152 let args =
1153 CommandParser::<NetworkArgs>::parse_from(["reth", "--nat", "extip:0.0.0.0"]).args;
1154 assert_eq!(args.nat, NatResolver::ExternalIp("0.0.0.0".parse().unwrap()));
1155 }
1156
1157 #[test]
1158 fn parse_peer_args() {
1159 let args =
1160 CommandParser::<NetworkArgs>::parse_from(["reth", "--max-outbound-peers", "50"]).args;
1161 assert_eq!(args.max_outbound_peers, Some(50));
1162 assert_eq!(args.max_inbound_peers, None);
1163
1164 let args = CommandParser::<NetworkArgs>::parse_from([
1165 "reth",
1166 "--max-outbound-peers",
1167 "75",
1168 "--max-inbound-peers",
1169 "15",
1170 ])
1171 .args;
1172 assert_eq!(args.max_outbound_peers, Some(75));
1173 assert_eq!(args.max_inbound_peers, Some(15));
1174 }
1175
1176 #[test]
1177 fn parse_trusted_peer_args() {
1178 let args =
1179 CommandParser::<NetworkArgs>::parse_from([
1180 "reth",
1181 "--trusted-peers",
1182 "enode://d860a01f9722d78051619d1e2351aba3f43f943f6f00718d1b9baa4101932a1f5011f16bb2b1bb35db20d6fe28fa0bf09636d26a87d31de9ec6203eeedb1f666@18.138.108.67:30303,enode://22a8232c3abc76a16ae9d6c3b164f98775fe226f0917b0ca871128a74a8e9630b458460865bab457221f1d448dd9791d24c4e5d88786180ac185df813a68d4de@3.209.45.79:30303"
1183 ])
1184 .args;
1185
1186 assert_eq!(
1187 args.trusted_peers,
1188 vec![
1189 "enode://d860a01f9722d78051619d1e2351aba3f43f943f6f00718d1b9baa4101932a1f5011f16bb2b1bb35db20d6fe28fa0bf09636d26a87d31de9ec6203eeedb1f666@18.138.108.67:30303".parse().unwrap(),
1190 "enode://22a8232c3abc76a16ae9d6c3b164f98775fe226f0917b0ca871128a74a8e9630b458460865bab457221f1d448dd9791d24c4e5d88786180ac185df813a68d4de@3.209.45.79:30303".parse().unwrap()
1191 ]
1192 );
1193 }
1194
1195 #[test]
1196 fn parse_retry_strategy_args() {
1197 let tests = vec![0, 10];
1198
1199 for retries in tests {
1200 let retries_str = retries.to_string();
1201 let args = CommandParser::<NetworkArgs>::parse_from([
1202 "reth",
1203 "--dns-retries",
1204 retries_str.as_str(),
1205 ])
1206 .args;
1207
1208 assert_eq!(args.dns_retries, retries);
1209 }
1210 }
1211
1212 #[test]
1213 fn parse_disable_tx_gossip_args() {
1214 let args = CommandParser::<NetworkArgs>::parse_from(["reth", "--disable-tx-gossip"]).args;
1215 assert!(args.disable_tx_gossip);
1216 }
1217
1218 #[test]
1219 fn parse_max_peers_flag() {
1220 let args = CommandParser::<NetworkArgs>::parse_from(["reth", "--max-peers", "90"]).args;
1221
1222 assert_eq!(args.max_peers, Some(90));
1223 assert_eq!(args.max_outbound_peers, None);
1224 assert_eq!(args.max_inbound_peers, None);
1225 assert_eq!(args.resolved_max_outbound_peers(), Some(30));
1226 assert_eq!(args.resolved_max_inbound_peers(), Some(60));
1227 }
1228
1229 #[test]
1230 fn max_peers_conflicts_with_outbound() {
1231 let result = CommandParser::<NetworkArgs>::try_parse_from([
1232 "reth",
1233 "--max-peers",
1234 "90",
1235 "--max-outbound-peers",
1236 "50",
1237 ]);
1238 assert!(
1239 result.is_err(),
1240 "Should fail when both --max-peers and --max-outbound-peers are used"
1241 );
1242 }
1243
1244 #[test]
1245 fn max_peers_conflicts_with_inbound() {
1246 let result = CommandParser::<NetworkArgs>::try_parse_from([
1247 "reth",
1248 "--max-peers",
1249 "90",
1250 "--max-inbound-peers",
1251 "30",
1252 ]);
1253 assert!(
1254 result.is_err(),
1255 "Should fail when both --max-peers and --max-inbound-peers are used"
1256 );
1257 }
1258
1259 #[test]
1260 fn max_peers_split_calculation() {
1261 let args = CommandParser::<NetworkArgs>::parse_from(["reth", "--max-peers", "90"]).args;
1262
1263 assert_eq!(args.max_peers, Some(90));
1264 assert_eq!(args.resolved_max_outbound_peers(), Some(30));
1265 assert_eq!(args.resolved_max_inbound_peers(), Some(60));
1266 }
1267
1268 #[test]
1269 fn max_peers_small_values() {
1270 let args1 = CommandParser::<NetworkArgs>::parse_from(["reth", "--max-peers", "1"]).args;
1271 assert_eq!(args1.resolved_max_outbound_peers(), Some(1));
1272 assert_eq!(args1.resolved_max_inbound_peers(), Some(0));
1273
1274 let args2 = CommandParser::<NetworkArgs>::parse_from(["reth", "--max-peers", "2"]).args;
1275 assert_eq!(args2.resolved_max_outbound_peers(), Some(1));
1276 assert_eq!(args2.resolved_max_inbound_peers(), Some(1));
1277
1278 let args3 = CommandParser::<NetworkArgs>::parse_from(["reth", "--max-peers", "3"]).args;
1279 assert_eq!(args3.resolved_max_outbound_peers(), Some(1));
1280 assert_eq!(args3.resolved_max_inbound_peers(), Some(2));
1281 }
1282
1283 #[test]
1284 fn resolved_peers_without_max_peers() {
1285 let args = CommandParser::<NetworkArgs>::parse_from([
1286 "reth",
1287 "--max-outbound-peers",
1288 "75",
1289 "--max-inbound-peers",
1290 "15",
1291 ])
1292 .args;
1293
1294 assert_eq!(args.max_peers, None);
1295 assert_eq!(args.resolved_max_outbound_peers(), Some(75));
1296 assert_eq!(args.resolved_max_inbound_peers(), Some(15));
1297 }
1298
1299 #[test]
1300 fn resolved_peers_with_defaults() {
1301 let args = CommandParser::<NetworkArgs>::parse_from(["reth"]).args;
1302
1303 assert_eq!(args.max_peers, None);
1304 assert_eq!(args.resolved_max_outbound_peers(), None);
1305 assert_eq!(args.resolved_max_inbound_peers(), None);
1306 }
1307
1308 #[test]
1309 fn network_args_default_sanity_test() {
1310 let default_args = NetworkArgs::default();
1311 let args = CommandParser::<NetworkArgs>::parse_from(["reth"]).args;
1312
1313 assert_eq!(args, default_args);
1314 }
1315
1316 #[test]
1317 fn parse_eth_max_message_size() {
1318 let args = CommandParser::<NetworkArgs>::parse_from([
1319 "reth",
1320 "--eth-max-message-size",
1321 "15728640",
1322 ])
1323 .args;
1324
1325 assert_eq!(args.eth_max_message_size, Some(NonZeroUsize::new(15 * 1024 * 1024).unwrap()));
1326 }
1327
1328 #[test]
1329 fn parse_eth_max_message_size_zero_rejected() {
1330 let result =
1331 CommandParser::<NetworkArgs>::try_parse_from(["reth", "--eth-max-message-size", "0"]);
1332 assert!(result.is_err());
1333 }
1334
1335 #[test]
1336 fn parse_eth_max_message_size_above_rlpx_cap() {
1337 let result = CommandParser::<NetworkArgs>::try_parse_from([
1338 "reth",
1339 "--eth-max-message-size",
1340 "16777216",
1341 ]);
1342 assert!(result.is_ok());
1343 let args = result.unwrap().args;
1344 assert_eq!(args.eth_max_message_size, Some(NonZeroUsize::new(16 * 1024 * 1024).unwrap()));
1345 }
1346
1347 #[test]
1348 fn parse_required_block_hashes() {
1349 let args = CommandParser::<NetworkArgs>::parse_from([
1350 "reth",
1351 "--required-block-hashes",
1352 "0x1111111111111111111111111111111111111111111111111111111111111111,23115201=0x2222222222222222222222222222222222222222222222222222222222222222",
1353 ])
1354 .args;
1355
1356 assert_eq!(args.required_block_hashes.len(), 2);
1357 assert_eq!(args.required_block_hashes[0].number, 0);
1359 assert_eq!(
1360 args.required_block_hashes[0].hash.to_string(),
1361 "0x1111111111111111111111111111111111111111111111111111111111111111"
1362 );
1363 assert_eq!(args.required_block_hashes[1].number, 23115201);
1365 assert_eq!(
1366 args.required_block_hashes[1].hash.to_string(),
1367 "0x2222222222222222222222222222222222222222222222222222222222222222"
1368 );
1369 }
1370
1371 #[test]
1372 fn parse_empty_required_block_hashes() {
1373 let args = CommandParser::<NetworkArgs>::parse_from(["reth"]).args;
1374 assert!(args.required_block_hashes.is_empty());
1375 }
1376
1377 #[test]
1378 fn test_parse_block_num_hash() {
1379 let result = parse_block_num_hash(
1381 "0x1111111111111111111111111111111111111111111111111111111111111111",
1382 );
1383 assert!(result.is_ok());
1384 assert_eq!(result.unwrap().number, 0);
1385
1386 let result = parse_block_num_hash(
1388 "23115201=0x2222222222222222222222222222222222222222222222222222222222222222",
1389 );
1390 assert!(result.is_ok());
1391 assert_eq!(result.unwrap().number, 23115201);
1392
1393 assert!(parse_block_num_hash("invalid").is_err());
1395 assert!(parse_block_num_hash(
1396 "abc=0x1111111111111111111111111111111111111111111111111111111111111111"
1397 )
1398 .is_err());
1399 }
1400
1401 #[test]
1402 fn parse_p2p_secret_key_hex() {
1403 let hex = "4c0883a69102937d6231471b5dbb6204fe512961708279f8c5c58b3b9c4e8b8f";
1404 let args =
1405 CommandParser::<NetworkArgs>::parse_from(["reth", "--p2p-secret-key-hex", hex]).args;
1406
1407 let expected: B256 = hex.parse().unwrap();
1408 assert_eq!(args.p2p_secret_key_hex, Some(expected));
1409 assert_eq!(args.p2p_secret_key, None);
1410 }
1411
1412 #[test]
1413 fn parse_p2p_secret_key_hex_with_0x_prefix() {
1414 let hex = "0x4c0883a69102937d6231471b5dbb6204fe512961708279f8c5c58b3b9c4e8b8f";
1415 let args =
1416 CommandParser::<NetworkArgs>::parse_from(["reth", "--p2p-secret-key-hex", hex]).args;
1417
1418 let expected: B256 = hex.parse().unwrap();
1419 assert_eq!(args.p2p_secret_key_hex, Some(expected));
1420 assert_eq!(args.p2p_secret_key, None);
1421 }
1422
1423 #[test]
1424 fn test_p2p_secret_key_and_hex_are_mutually_exclusive() {
1425 let result = CommandParser::<NetworkArgs>::try_parse_from([
1426 "reth",
1427 "--p2p-secret-key",
1428 "/path/to/key",
1429 "--p2p-secret-key-hex",
1430 "4c0883a69102937d6231471b5dbb6204fe512961708279f8c5c58b3b9c4e8b8f",
1431 ]);
1432
1433 assert!(result.is_err());
1434 }
1435
1436 #[test]
1437 fn test_secret_key_method_with_hex() {
1438 let hex = "4c0883a69102937d6231471b5dbb6204fe512961708279f8c5c58b3b9c4e8b8f";
1439 let args =
1440 CommandParser::<NetworkArgs>::parse_from(["reth", "--p2p-secret-key-hex", hex]).args;
1441
1442 let temp_dir = std::env::temp_dir();
1443 let default_path = temp_dir.join("default_key");
1444 let secret_key = args.secret_key(default_path).unwrap();
1445
1446 assert_eq!(alloy_primitives::hex::encode(secret_key.secret_bytes()), hex);
1448 }
1449
1450 #[test]
1451 fn parse_netrestrict_single_network() {
1452 let args =
1453 CommandParser::<NetworkArgs>::parse_from(["reth", "--netrestrict", "192.168.0.0/16"])
1454 .args;
1455
1456 assert_eq!(args.netrestrict, Some("192.168.0.0/16".to_string()));
1457
1458 let ip_filter = args.ip_filter().unwrap();
1459 assert!(ip_filter.has_restrictions());
1460 assert!(ip_filter.is_allowed(&"192.168.1.1".parse().unwrap()));
1461 assert!(!ip_filter.is_allowed(&"10.0.0.1".parse().unwrap()));
1462 }
1463
1464 #[test]
1465 fn parse_netrestrict_multiple_networks() {
1466 let args = CommandParser::<NetworkArgs>::parse_from([
1467 "reth",
1468 "--netrestrict",
1469 "192.168.0.0/16,10.0.0.0/8",
1470 ])
1471 .args;
1472
1473 assert_eq!(args.netrestrict, Some("192.168.0.0/16,10.0.0.0/8".to_string()));
1474
1475 let ip_filter = args.ip_filter().unwrap();
1476 assert!(ip_filter.has_restrictions());
1477 assert!(ip_filter.is_allowed(&"192.168.1.1".parse().unwrap()));
1478 assert!(ip_filter.is_allowed(&"10.5.10.20".parse().unwrap()));
1479 assert!(!ip_filter.is_allowed(&"172.16.0.1".parse().unwrap()));
1480 }
1481
1482 #[test]
1483 fn parse_netrestrict_ipv6() {
1484 let args =
1485 CommandParser::<NetworkArgs>::parse_from(["reth", "--netrestrict", "2001:db8::/32"])
1486 .args;
1487
1488 let ip_filter = args.ip_filter().unwrap();
1489 assert!(ip_filter.has_restrictions());
1490 assert!(ip_filter.is_allowed(&"2001:db8::1".parse().unwrap()));
1491 assert!(!ip_filter.is_allowed(&"2001:db9::1".parse().unwrap()));
1492 }
1493
1494 #[test]
1495 fn netrestrict_not_set() {
1496 let args = CommandParser::<NetworkArgs>::parse_from(["reth"]).args;
1497 assert_eq!(args.netrestrict, None);
1498
1499 let ip_filter = args.ip_filter().unwrap();
1500 assert!(!ip_filter.has_restrictions());
1501 assert!(ip_filter.is_allowed(&"192.168.1.1".parse().unwrap()));
1502 assert!(ip_filter.is_allowed(&"10.0.0.1".parse().unwrap()));
1503 }
1504
1505 #[test]
1506 fn netrestrict_invalid_cidr() {
1507 let args =
1508 CommandParser::<NetworkArgs>::parse_from(["reth", "--netrestrict", "invalid-cidr"])
1509 .args;
1510
1511 assert!(args.ip_filter().is_err());
1512 }
1513
1514 #[test]
1515 fn network_config_preserves_basic_nodes_from_peers_file() {
1516 let enode = "enode://6f8a80d14311c39f35f516fa664deaaaa13e85b2f7493f37f6144d86991ec012937307647bd3b9a82abe2974e1407241d54947bbb39763a4cac9f77166ad92a0@10.3.58.6:30303?discport=30301";
1517 let unique = SystemTime::now().duration_since(UNIX_EPOCH).unwrap().as_nanos();
1518
1519 let peers_file = std::env::temp_dir().join(format!("reth_peers_test_{}.json", unique));
1520 fs::write(&peers_file, format!("[\"{}\"]", enode)).expect("write peers file");
1521
1522 let args = NetworkArgs {
1524 peers_file: Some(peers_file.clone()),
1525 no_persist_peers: false,
1526 ..Default::default()
1527 };
1528
1529 let secret_key = SecretKey::from_byte_array(&[1u8; 32]).unwrap();
1531 let builder = args.network_config::<reth_network::EthNetworkPrimitives>(
1532 &Config::default(),
1533 MAINNET.clone(),
1534 secret_key,
1535 peers_file.clone(),
1536 Runtime::test(),
1537 );
1538
1539 let net_cfg = builder.build_with_noop_provider(MAINNET.clone());
1540
1541 let node: NodeRecord = enode.parse().unwrap();
1543 assert!(net_cfg.peers_config.persisted_peers.iter().any(|p| p.record == node));
1544
1545 let _ = fs::remove_file(&peers_file);
1547 }
1548}