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::Args;
15use reth_chainspec::EthChainSpec;
16use reth_cli_util::{get_secret_key, load_secret_key::SecretKeyError};
17use reth_config::Config;
18use reth_discv4::{NodeRecord, DEFAULT_DISCOVERY_ADDR, DEFAULT_DISCOVERY_PORT};
19use reth_discv5::{
20 discv5::ListenConfig, DEFAULT_COUNT_BOOTSTRAP_LOOKUPS, DEFAULT_DISCOVERY_V5_PORT,
21 DEFAULT_SECONDS_BOOTSTRAP_LOOKUP_INTERVAL, DEFAULT_SECONDS_LOOKUP_INTERVAL,
22};
23use reth_net_banlist::IpFilter;
24use reth_net_nat::{NatResolver, DEFAULT_NET_IF_NAME};
25use reth_network::{
26 transactions::{
27 config::{TransactionIngressPolicy, TransactionPropagationKind},
28 constants::{
29 tx_fetcher::{
30 DEFAULT_MAX_CAPACITY_CACHE_PENDING_FETCH, DEFAULT_MAX_COUNT_CONCURRENT_REQUESTS,
31 DEFAULT_MAX_COUNT_CONCURRENT_REQUESTS_PER_PEER,
32 },
33 tx_manager::{
34 DEFAULT_MAX_COUNT_PENDING_POOL_IMPORTS, DEFAULT_MAX_COUNT_TRANSACTIONS_SEEN_BY_PEER,
35 },
36 },
37 TransactionFetcherConfig, TransactionPropagationMode, TransactionsManagerConfig,
38 DEFAULT_SOFT_LIMIT_BYTE_SIZE_POOLED_TRANSACTIONS_RESP_ON_PACK_GET_POOLED_TRANSACTIONS_REQ,
39 SOFT_LIMIT_BYTE_SIZE_POOLED_TRANSACTIONS_RESPONSE,
40 },
41 HelloMessageWithProtocols, NetworkConfigBuilder, NetworkPrimitives,
42};
43use reth_network_peers::{mainnet_nodes, TrustedPeer};
44use reth_tasks::Runtime;
45use secp256k1::SecretKey;
46use std::str::FromStr;
47use tracing::error;
48
49static NETWORK_DEFAULTS: OnceLock<DefaultNetworkArgs> = OnceLock::new();
51
52#[derive(Debug, Clone)]
56pub struct DefaultNetworkArgs {
57 pub dns_retries: usize,
59 pub nat: NatResolver,
61 pub addr: IpAddr,
63 pub port: u16,
65 pub max_concurrent_tx_requests: u32,
67 pub max_concurrent_tx_requests_per_peer: u8,
69 pub max_seen_tx_history: u32,
71 pub max_pending_pool_imports: usize,
73 pub soft_limit_byte_size_pooled_transactions_response: usize,
75 pub soft_limit_byte_size_pooled_transactions_response_on_pack_request: usize,
77 pub max_capacity_cache_txns_pending_fetch: u32,
79 pub tx_propagation_policy: TransactionPropagationKind,
81 pub tx_ingress_policy: TransactionIngressPolicy,
83 pub propagation_mode: TransactionPropagationMode,
85 pub enforce_enr_fork_id: bool,
87}
88
89impl DefaultNetworkArgs {
90 pub fn try_init(self) -> Result<(), Self> {
92 NETWORK_DEFAULTS.set(self)
93 }
94
95 pub fn get_global() -> &'static Self {
97 NETWORK_DEFAULTS.get_or_init(Self::default)
98 }
99
100 pub const fn with_dns_retries(mut self, v: usize) -> Self {
102 self.dns_retries = v;
103 self
104 }
105
106 pub fn with_nat(mut self, v: NatResolver) -> Self {
108 self.nat = v;
109 self
110 }
111
112 pub const fn with_addr(mut self, v: IpAddr) -> Self {
114 self.addr = v;
115 self
116 }
117
118 pub const fn with_port(mut self, v: u16) -> Self {
120 self.port = v;
121 self
122 }
123
124 pub const fn with_max_concurrent_tx_requests(mut self, v: u32) -> Self {
126 self.max_concurrent_tx_requests = v;
127 self
128 }
129
130 pub const fn with_max_concurrent_tx_requests_per_peer(mut self, v: u8) -> Self {
132 self.max_concurrent_tx_requests_per_peer = v;
133 self
134 }
135
136 pub const fn with_max_seen_tx_history(mut self, v: u32) -> Self {
138 self.max_seen_tx_history = v;
139 self
140 }
141
142 pub const fn with_max_pending_pool_imports(mut self, v: usize) -> Self {
144 self.max_pending_pool_imports = v;
145 self
146 }
147
148 pub const fn with_soft_limit_byte_size_pooled_transactions_response(
150 mut self,
151 v: usize,
152 ) -> Self {
153 self.soft_limit_byte_size_pooled_transactions_response = v;
154 self
155 }
156
157 pub const fn with_soft_limit_byte_size_pooled_transactions_response_on_pack_request(
159 mut self,
160 v: usize,
161 ) -> Self {
162 self.soft_limit_byte_size_pooled_transactions_response_on_pack_request = v;
163 self
164 }
165
166 pub const fn with_max_capacity_cache_txns_pending_fetch(mut self, v: u32) -> Self {
168 self.max_capacity_cache_txns_pending_fetch = v;
169 self
170 }
171
172 pub const fn with_tx_propagation_policy(mut self, v: TransactionPropagationKind) -> Self {
174 self.tx_propagation_policy = v;
175 self
176 }
177
178 pub const fn with_tx_ingress_policy(mut self, v: TransactionIngressPolicy) -> Self {
180 self.tx_ingress_policy = v;
181 self
182 }
183
184 pub const fn with_propagation_mode(mut self, v: TransactionPropagationMode) -> Self {
186 self.propagation_mode = v;
187 self
188 }
189
190 pub const fn with_enforce_enr_fork_id(mut self, v: bool) -> Self {
192 self.enforce_enr_fork_id = v;
193 self
194 }
195}
196
197impl Default for DefaultNetworkArgs {
198 fn default() -> Self {
199 Self {
200 dns_retries: 0,
201 nat: NatResolver::Any,
202 addr: DEFAULT_DISCOVERY_ADDR,
203 port: DEFAULT_DISCOVERY_PORT,
204 max_concurrent_tx_requests: DEFAULT_MAX_COUNT_CONCURRENT_REQUESTS,
205 max_concurrent_tx_requests_per_peer: DEFAULT_MAX_COUNT_CONCURRENT_REQUESTS_PER_PEER,
206 max_seen_tx_history: DEFAULT_MAX_COUNT_TRANSACTIONS_SEEN_BY_PEER,
207 max_pending_pool_imports: DEFAULT_MAX_COUNT_PENDING_POOL_IMPORTS,
208 soft_limit_byte_size_pooled_transactions_response:
209 SOFT_LIMIT_BYTE_SIZE_POOLED_TRANSACTIONS_RESPONSE,
210 soft_limit_byte_size_pooled_transactions_response_on_pack_request:
211 DEFAULT_SOFT_LIMIT_BYTE_SIZE_POOLED_TRANSACTIONS_RESP_ON_PACK_GET_POOLED_TRANSACTIONS_REQ,
212 max_capacity_cache_txns_pending_fetch: DEFAULT_MAX_CAPACITY_CACHE_PENDING_FETCH,
213 tx_propagation_policy: TransactionPropagationKind::default(),
214 tx_ingress_policy: TransactionIngressPolicy::default(),
215 propagation_mode: TransactionPropagationMode::Sqrt,
216 enforce_enr_fork_id: false,
217 }
218 }
219}
220
221#[derive(Debug, Clone, Args, PartialEq, Eq)]
223#[command(next_help_heading = "Networking")]
224pub struct NetworkArgs {
225 #[command(flatten)]
227 pub discovery: DiscoveryArgs,
228
229 #[expect(clippy::doc_markdown)]
230 #[arg(long, value_delimiter = ',')]
234 pub trusted_peers: Vec<TrustedPeer>,
235
236 #[arg(long)]
238 pub trusted_only: bool,
239
240 #[arg(long, value_delimiter = ',')]
244 pub bootnodes: Option<Vec<TrustedPeer>>,
245
246 #[arg(long, default_value_t = DefaultNetworkArgs::get_global().dns_retries)]
248 pub dns_retries: usize,
249
250 #[arg(long, value_name = "FILE", verbatim_doc_comment, conflicts_with = "no_persist_peers")]
253 pub peers_file: Option<PathBuf>,
254
255 #[arg(long, value_name = "IDENTITY", default_value = version_metadata().p2p_client_version.as_ref())]
257 pub identity: String,
258
259 #[arg(long, value_name = "PATH", conflicts_with = "p2p_secret_key_hex")]
264 pub p2p_secret_key: Option<PathBuf>,
265
266 #[arg(long, value_name = "HEX", conflicts_with = "p2p_secret_key")]
271 pub p2p_secret_key_hex: Option<B256>,
272
273 #[arg(long, verbatim_doc_comment)]
275 pub no_persist_peers: bool,
276
277 #[arg(long, default_value_t = DefaultNetworkArgs::get_global().nat.clone())]
279 pub nat: NatResolver,
280
281 #[arg(long = "addr", value_name = "ADDR", default_value_t = DefaultNetworkArgs::get_global().addr)]
283 pub addr: IpAddr,
284
285 #[arg(long = "port", value_name = "PORT", default_value_t = DefaultNetworkArgs::get_global().port)]
287 pub port: u16,
288
289 #[arg(long)]
291 pub max_outbound_peers: Option<usize>,
292
293 #[arg(long)]
295 pub max_inbound_peers: Option<usize>,
296
297 #[arg(
302 long,
303 value_name = "COUNT",
304 conflicts_with = "max_outbound_peers",
305 conflicts_with = "max_inbound_peers"
306 )]
307 pub max_peers: Option<usize>,
308
309 #[arg(long = "max-tx-reqs", value_name = "COUNT", default_value_t = DefaultNetworkArgs::get_global().max_concurrent_tx_requests, verbatim_doc_comment)]
311 pub max_concurrent_tx_requests: u32,
312
313 #[arg(long = "max-tx-reqs-peer", value_name = "COUNT", default_value_t = DefaultNetworkArgs::get_global().max_concurrent_tx_requests_per_peer, verbatim_doc_comment)]
315 pub max_concurrent_tx_requests_per_peer: u8,
316
317 #[arg(long = "max-seen-tx-history", value_name = "COUNT", default_value_t = DefaultNetworkArgs::get_global().max_seen_tx_history, verbatim_doc_comment)]
321 pub max_seen_tx_history: u32,
322
323 #[arg(long = "max-pending-imports", value_name = "COUNT", default_value_t = DefaultNetworkArgs::get_global().max_pending_pool_imports, verbatim_doc_comment)]
324 pub max_pending_pool_imports: usize,
326
327 #[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)]
331 pub soft_limit_byte_size_pooled_transactions_response: usize,
332
333 #[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)]
345 pub soft_limit_byte_size_pooled_transactions_response_on_pack_request: usize,
346
347 #[arg(long = "max-tx-pending-fetch", value_name = "COUNT", default_value_t = DefaultNetworkArgs::get_global().max_capacity_cache_txns_pending_fetch, verbatim_doc_comment)]
349 pub max_capacity_cache_txns_pending_fetch: u32,
350
351 #[arg(long = "net-if.experimental", conflicts_with = "addr", value_name = "IF_NAME")]
355 pub net_if: Option<String>,
356
357 #[arg(long = "tx-propagation-policy", default_value_t = DefaultNetworkArgs::get_global().tx_propagation_policy)]
361 pub tx_propagation_policy: TransactionPropagationKind,
362
363 #[arg(long = "tx-ingress-policy", default_value_t = DefaultNetworkArgs::get_global().tx_ingress_policy)]
367 pub tx_ingress_policy: TransactionIngressPolicy,
368
369 #[arg(long = "disable-tx-gossip")]
374 pub disable_tx_gossip: bool,
375
376 #[arg(
381 long = "tx-propagation-mode",
382 default_value_t = DefaultNetworkArgs::get_global().propagation_mode,
383 help = "Transaction propagation mode (sqrt, all, max:<number>)"
384 )]
385 pub propagation_mode: TransactionPropagationMode,
386
387 #[arg(long = "required-block-hashes", value_delimiter = ',', value_parser = parse_block_num_hash)]
391 pub required_block_hashes: Vec<BlockNumHash>,
392
393 #[arg(long)]
395 pub network_id: Option<u64>,
396
397 #[arg(long = "eth-max-message-size", value_name = "BYTES")]
399 pub eth_max_message_size: Option<NonZeroUsize>,
400
401 #[arg(long, value_name = "NETRESTRICT")]
408 pub netrestrict: Option<String>,
409
410 #[arg(long, default_value_t = DefaultNetworkArgs::get_global().enforce_enr_fork_id)]
416 pub enforce_enr_fork_id: bool,
417}
418
419impl NetworkArgs {
420 pub fn resolved_addr(&self) -> IpAddr {
422 if let Some(ref if_name) = self.net_if {
423 let if_name = if if_name.is_empty() { DEFAULT_NET_IF_NAME } else { if_name };
424 return match reth_net_nat::net_if::resolve_net_if_ip(if_name) {
425 Ok(addr) => addr,
426 Err(err) => {
427 error!(target: "reth::cli",
428 if_name,
429 %err,
430 "Failed to read network interface IP"
431 );
432
433 DEFAULT_DISCOVERY_ADDR
434 }
435 };
436 }
437
438 self.addr
439 }
440
441 pub fn resolved_bootnodes(&self) -> Option<Vec<NodeRecord>> {
443 self.bootnodes.clone().map(|bootnodes| {
444 bootnodes.into_iter().filter_map(|node| node.resolve_blocking().ok()).collect()
445 })
446 }
447
448 pub fn resolved_max_inbound_peers(&self) -> Option<usize> {
450 if let Some(max_peers) = self.max_peers {
451 if max_peers == 0 {
452 Some(0)
453 } else {
454 let outbound = (max_peers / 3).max(1);
455 Some(max_peers.saturating_sub(outbound))
456 }
457 } else {
458 self.max_inbound_peers
459 }
460 }
461
462 pub fn resolved_max_outbound_peers(&self) -> Option<usize> {
464 if let Some(max_peers) = self.max_peers {
465 if max_peers == 0 {
466 Some(0)
467 } else {
468 Some((max_peers / 3).max(1))
469 }
470 } else {
471 self.max_outbound_peers
472 }
473 }
474
475 pub const fn transactions_manager_config(&self) -> TransactionsManagerConfig {
477 TransactionsManagerConfig {
478 transaction_fetcher_config: TransactionFetcherConfig::new(
479 self.max_concurrent_tx_requests,
480 self.max_concurrent_tx_requests_per_peer,
481 self.soft_limit_byte_size_pooled_transactions_response,
482 self.soft_limit_byte_size_pooled_transactions_response_on_pack_request,
483 self.max_capacity_cache_txns_pending_fetch,
484 ),
485 max_transactions_seen_by_peer_history: self.max_seen_tx_history,
486 propagation_mode: self.propagation_mode,
487 ingress_policy: self.tx_ingress_policy,
488 }
489 }
490
491 pub fn network_config<N: NetworkPrimitives>(
503 &self,
504 config: &Config,
505 chain_spec: impl EthChainSpec,
506 secret_key: SecretKey,
507 default_peers_file: PathBuf,
508 executor: Runtime,
509 ) -> NetworkConfigBuilder<N> {
510 let addr = self.resolved_addr();
511 let chain_bootnodes = self
512 .resolved_bootnodes()
513 .unwrap_or_else(|| chain_spec.bootnodes().unwrap_or_else(mainnet_nodes));
514 let peers_file = self.peers_file.clone().unwrap_or(default_peers_file);
515
516 let ip_filter = self.ip_filter().unwrap_or_default();
518 let peers_config = config
519 .peers_config_with_basic_nodes_from_file(
520 self.persistent_peers_file(peers_file).as_deref(),
521 )
522 .with_max_inbound_opt(self.resolved_max_inbound_peers())
523 .with_max_outbound_opt(self.resolved_max_outbound_peers())
524 .with_ip_filter(ip_filter)
525 .with_enforce_enr_fork_id(self.enforce_enr_fork_id);
526
527 NetworkConfigBuilder::<N>::new(secret_key, executor)
529 .external_ip_resolver(self.nat.clone())
530 .sessions_config(
531 config.sessions.clone().with_upscaled_event_buffer(peers_config.max_peers()),
532 )
533 .peer_config(peers_config)
534 .boot_nodes(chain_bootnodes.clone())
535 .transactions_manager_config(self.transactions_manager_config())
536 .apply(|builder| {
538 let peer_id = builder.get_peer_id();
539 builder.hello_message(
540 HelloMessageWithProtocols::builder(peer_id)
541 .client_version(&self.identity)
542 .build(),
543 )
544 })
545 .apply(|builder| {
547 let rlpx_socket = (addr, self.port).into();
548 self.discovery.apply_to_builder(builder, rlpx_socket, chain_bootnodes)
549 })
550 .listener_addr(SocketAddr::new(
551 addr, self.port,
553 ))
554 .discovery_addr(SocketAddr::new(
555 self.discovery.addr,
556 self.discovery.port,
558 ))
559 .disable_tx_gossip(self.disable_tx_gossip)
560 .required_block_hashes(self.required_block_hashes.clone())
561 .eth_max_message_size_opt(self.eth_max_message_size.map(NonZeroUsize::get))
562 .network_id(self.network_id)
563 }
564
565 pub fn persistent_peers_file(&self, peers_file: PathBuf) -> Option<PathBuf> {
567 self.no_persist_peers.not().then_some(peers_file)
568 }
569
570 pub const fn with_discovery(mut self, discovery: DiscoveryArgs) -> Self {
572 self.discovery = discovery;
573 self
574 }
575
576 pub const fn with_unused_p2p_port(mut self) -> Self {
579 self.port = 0;
580 self
581 }
582
583 pub const fn with_unused_ports(mut self) -> Self {
586 self = self.with_unused_p2p_port();
587 self.discovery = self.discovery.with_unused_discovery_port();
588 self
589 }
590
591 pub fn with_nat_resolver(mut self, nat: NatResolver) -> Self {
593 self.nat = nat;
594 self
595 }
596
597 pub fn adjust_instance_ports(&mut self, instance: Option<u16>) {
603 if let Some(instance) = instance {
604 debug_assert_ne!(instance, 0, "instance must be non-zero");
605 self.port += instance - 1;
606 self.discovery.adjust_instance_ports(instance);
607 }
608 }
609
610 pub async fn resolve_trusted_peers(&self) -> Result<Vec<NodeRecord>, std::io::Error> {
612 futures::future::try_join_all(
613 self.trusted_peers.iter().map(|peer| async move { peer.resolve().await }),
614 )
615 .await
616 }
617
618 pub fn secret_key(
624 &self,
625 default_secret_key_path: PathBuf,
626 ) -> Result<SecretKey, SecretKeyError> {
627 if let Some(b256) = &self.p2p_secret_key_hex {
628 SecretKey::from_slice(b256.as_slice()).map_err(SecretKeyError::SecretKeyDecodeError)
630 } else {
631 let secret_key_path = self.p2p_secret_key.clone().unwrap_or(default_secret_key_path);
633 get_secret_key(&secret_key_path)
634 }
635 }
636
637 pub fn ip_filter(&self) -> Result<IpFilter, ipnet::AddrParseError> {
641 if let Some(netrestrict) = &self.netrestrict {
642 IpFilter::from_cidr_string(netrestrict)
643 } else {
644 Ok(IpFilter::allow_all())
645 }
646 }
647}
648
649impl Default for NetworkArgs {
650 fn default() -> Self {
651 let DefaultNetworkArgs {
652 dns_retries,
653 nat,
654 addr,
655 port,
656 max_concurrent_tx_requests,
657 max_concurrent_tx_requests_per_peer,
658 max_seen_tx_history,
659 max_pending_pool_imports,
660 soft_limit_byte_size_pooled_transactions_response,
661 soft_limit_byte_size_pooled_transactions_response_on_pack_request,
662 max_capacity_cache_txns_pending_fetch,
663 tx_propagation_policy,
664 tx_ingress_policy,
665 propagation_mode,
666 enforce_enr_fork_id,
667 } = DefaultNetworkArgs::get_global().clone();
668 Self {
669 discovery: DiscoveryArgs::default(),
670 trusted_peers: vec![],
671 trusted_only: false,
672 bootnodes: None,
673 dns_retries,
674 peers_file: None,
675 identity: version_metadata().p2p_client_version.to_string(),
676 p2p_secret_key: None,
677 p2p_secret_key_hex: None,
678 no_persist_peers: false,
679 nat,
680 addr,
681 port,
682 max_outbound_peers: None,
683 max_inbound_peers: None,
684 max_peers: None,
685 max_concurrent_tx_requests,
686 max_concurrent_tx_requests_per_peer,
687 soft_limit_byte_size_pooled_transactions_response,
688 soft_limit_byte_size_pooled_transactions_response_on_pack_request,
689 max_pending_pool_imports,
690 max_seen_tx_history,
691 max_capacity_cache_txns_pending_fetch,
692 net_if: None,
693 tx_propagation_policy,
694 tx_ingress_policy,
695 disable_tx_gossip: false,
696 propagation_mode,
697 required_block_hashes: vec![],
698 network_id: None,
699 eth_max_message_size: None,
700 netrestrict: None,
701 enforce_enr_fork_id,
702 }
703 }
704}
705
706#[derive(Debug, Clone, Args, PartialEq, Eq)]
708pub struct DiscoveryArgs {
709 #[arg(short, long, default_value_if("dev", "true", "true"))]
711 pub disable_discovery: bool,
712
713 #[arg(long, conflicts_with = "disable_discovery")]
715 pub disable_dns_discovery: bool,
716
717 #[arg(long, conflicts_with = "disable_discovery")]
719 pub disable_discv4_discovery: bool,
720
721 #[arg(long, conflicts_with = "disable_discovery")]
723 pub enable_discv5_discovery: bool,
724
725 #[arg(long, conflicts_with = "disable_discovery")]
727 pub disable_nat: bool,
728
729 #[arg(id = "discovery.addr", long = "discovery.addr", value_name = "DISCOVERY_ADDR", default_value_t = DEFAULT_DISCOVERY_ADDR)]
731 pub addr: IpAddr,
732
733 #[arg(id = "discovery.port", long = "discovery.port", value_name = "DISCOVERY_PORT", default_value_t = DEFAULT_DISCOVERY_PORT)]
735 pub port: u16,
736
737 #[arg(id = "discovery.v5.addr", long = "discovery.v5.addr", value_name = "DISCOVERY_V5_ADDR", default_value = None)]
740 pub discv5_addr: Option<Ipv4Addr>,
741
742 #[arg(id = "discovery.v5.addr.ipv6", long = "discovery.v5.addr.ipv6", value_name = "DISCOVERY_V5_ADDR_IPV6", default_value = None)]
745 pub discv5_addr_ipv6: Option<Ipv6Addr>,
746
747 #[arg(id = "discovery.v5.port", long = "discovery.v5.port", value_name = "DISCOVERY_V5_PORT",
750 default_value_t = DEFAULT_DISCOVERY_V5_PORT)]
751 pub discv5_port: u16,
752
753 #[arg(id = "discovery.v5.port.ipv6", long = "discovery.v5.port.ipv6", value_name = "DISCOVERY_V5_PORT_IPV6",
756 default_value_t = DEFAULT_DISCOVERY_V5_PORT)]
757 pub discv5_port_ipv6: u16,
758
759 #[arg(id = "discovery.v5.lookup-interval", long = "discovery.v5.lookup-interval", value_name = "DISCOVERY_V5_LOOKUP_INTERVAL", default_value_t = DEFAULT_SECONDS_LOOKUP_INTERVAL)]
762 pub discv5_lookup_interval: u64,
763
764 #[arg(id = "discovery.v5.bootstrap.lookup-interval", long = "discovery.v5.bootstrap.lookup-interval", value_name = "DISCOVERY_V5_BOOTSTRAP_LOOKUP_INTERVAL",
767 default_value_t = DEFAULT_SECONDS_BOOTSTRAP_LOOKUP_INTERVAL)]
768 pub discv5_bootstrap_lookup_interval: u64,
769
770 #[arg(id = "discovery.v5.bootstrap.lookup-countdown", long = "discovery.v5.bootstrap.lookup-countdown", value_name = "DISCOVERY_V5_BOOTSTRAP_LOOKUP_COUNTDOWN",
772 default_value_t = DEFAULT_COUNT_BOOTSTRAP_LOOKUPS)]
773 pub discv5_bootstrap_lookup_countdown: u64,
774}
775
776impl DiscoveryArgs {
777 pub fn apply_to_builder<N>(
779 &self,
780 mut network_config_builder: NetworkConfigBuilder<N>,
781 rlpx_tcp_socket: SocketAddr,
782 boot_nodes: impl IntoIterator<Item = NodeRecord>,
783 ) -> NetworkConfigBuilder<N>
784 where
785 N: NetworkPrimitives,
786 {
787 if self.disable_discovery || self.disable_dns_discovery {
788 network_config_builder = network_config_builder.disable_dns_discovery();
789 }
790
791 if self.disable_discovery || self.disable_discv4_discovery {
792 network_config_builder = network_config_builder.disable_discv4_discovery();
793 }
794
795 if self.disable_nat {
796 network_config_builder = network_config_builder.disable_nat();
798 }
799
800 if self.should_enable_discv5() {
801 network_config_builder = network_config_builder
802 .discovery_v5(self.discovery_v5_builder(rlpx_tcp_socket, boot_nodes));
803 }
804
805 network_config_builder
806 }
807
808 pub fn discovery_v5_builder(
810 &self,
811 rlpx_tcp_socket: SocketAddr,
812 boot_nodes: impl IntoIterator<Item = NodeRecord>,
813 ) -> reth_discv5::ConfigBuilder {
814 let Self {
815 discv5_addr,
816 discv5_addr_ipv6,
817 discv5_port,
818 discv5_port_ipv6,
819 discv5_lookup_interval,
820 discv5_bootstrap_lookup_interval,
821 discv5_bootstrap_lookup_countdown,
822 ..
823 } = self;
824
825 let has_discv5_addr_args = discv5_addr.is_some() || discv5_addr_ipv6.is_some();
826
827 let discv5_addr_ipv4 = discv5_addr.or(match rlpx_tcp_socket {
829 SocketAddr::V4(addr) => Some(*addr.ip()),
830 SocketAddr::V6(_) => None,
831 });
832 let discv5_addr_ipv6 = discv5_addr_ipv6.or(match rlpx_tcp_socket {
833 SocketAddr::V4(_) => None,
834 SocketAddr::V6(addr) => Some(*addr.ip()),
835 });
836
837 let mut discv5_config_builder =
838 reth_discv5::discv5::ConfigBuilder::new(ListenConfig::from_two_sockets(
839 discv5_addr_ipv4.map(|addr| SocketAddrV4::new(addr, *discv5_port)),
840 discv5_addr_ipv6.map(|addr| SocketAddrV6::new(addr, *discv5_port_ipv6, 0, 0)),
841 ));
842
843 if has_discv5_addr_args || self.disable_nat {
844 discv5_config_builder.disable_enr_update();
846 }
847 reth_discv5::Config::builder(rlpx_tcp_socket)
848 .discv5_config(discv5_config_builder.build())
849 .add_unsigned_boot_nodes(boot_nodes)
850 .lookup_interval(*discv5_lookup_interval)
851 .bootstrap_lookup_interval(*discv5_bootstrap_lookup_interval)
852 .bootstrap_lookup_countdown(*discv5_bootstrap_lookup_countdown)
853 }
854
855 const fn should_enable_discv5(&self) -> bool {
857 if self.disable_discovery {
858 return false;
859 }
860
861 self.enable_discv5_discovery ||
862 self.discv5_addr.is_some() ||
863 self.discv5_addr_ipv6.is_some()
864 }
865
866 pub const fn with_unused_discovery_port(mut self) -> Self {
869 self.port = 0;
870 self
871 }
872
873 pub const fn with_discv5_port(mut self, port: u16) -> Self {
875 self.discv5_port = port;
876 self
877 }
878
879 pub fn adjust_instance_ports(&mut self, instance: u16) {
885 debug_assert_ne!(instance, 0, "instance must be non-zero");
886 self.port += instance - 1;
887 self.discv5_port += instance - 1;
888 self.discv5_port_ipv6 += instance - 1;
889 }
890}
891
892impl Default for DiscoveryArgs {
893 fn default() -> Self {
894 Self {
895 disable_discovery: false,
896 disable_dns_discovery: false,
897 disable_discv4_discovery: false,
898 enable_discv5_discovery: false,
899 disable_nat: false,
900 addr: DEFAULT_DISCOVERY_ADDR,
901 port: DEFAULT_DISCOVERY_PORT,
902 discv5_addr: None,
903 discv5_addr_ipv6: None,
904 discv5_port: DEFAULT_DISCOVERY_V5_PORT,
905 discv5_port_ipv6: DEFAULT_DISCOVERY_V5_PORT,
906 discv5_lookup_interval: DEFAULT_SECONDS_LOOKUP_INTERVAL,
907 discv5_bootstrap_lookup_interval: DEFAULT_SECONDS_BOOTSTRAP_LOOKUP_INTERVAL,
908 discv5_bootstrap_lookup_countdown: DEFAULT_COUNT_BOOTSTRAP_LOOKUPS,
909 }
910 }
911}
912
913fn parse_block_num_hash(s: &str) -> Result<BlockNumHash, String> {
915 if let Some((num_str, hash_str)) = s.split_once('=') {
916 let number = num_str.parse().map_err(|_| format!("Invalid block number: {}", num_str))?;
917 let hash = B256::from_str(hash_str).map_err(|_| format!("Invalid hash: {}", hash_str))?;
918 Ok(BlockNumHash::new(number, hash))
919 } else {
920 let hash = B256::from_str(s).map_err(|_| format!("Invalid hash: {}", s))?;
922 Ok(BlockNumHash::new(0, hash))
923 }
924}
925
926#[cfg(test)]
927mod tests {
928 use super::*;
929 use clap::Parser;
930 use reth_chainspec::MAINNET;
931 use reth_config::Config;
932 use reth_network_peers::NodeRecord;
933 use secp256k1::SecretKey;
934 use std::{
935 fs,
936 time::{SystemTime, UNIX_EPOCH},
937 };
938
939 #[derive(Parser)]
941 struct CommandParser<T: Args> {
942 #[command(flatten)]
943 args: T,
944 }
945
946 #[test]
947 fn parse_nat_args() {
948 let args = CommandParser::<NetworkArgs>::parse_from(["reth", "--nat", "none"]).args;
949 assert_eq!(args.nat, NatResolver::None);
950
951 let args =
952 CommandParser::<NetworkArgs>::parse_from(["reth", "--nat", "extip:0.0.0.0"]).args;
953 assert_eq!(args.nat, NatResolver::ExternalIp("0.0.0.0".parse().unwrap()));
954 }
955
956 #[test]
957 fn parse_peer_args() {
958 let args =
959 CommandParser::<NetworkArgs>::parse_from(["reth", "--max-outbound-peers", "50"]).args;
960 assert_eq!(args.max_outbound_peers, Some(50));
961 assert_eq!(args.max_inbound_peers, None);
962
963 let args = CommandParser::<NetworkArgs>::parse_from([
964 "reth",
965 "--max-outbound-peers",
966 "75",
967 "--max-inbound-peers",
968 "15",
969 ])
970 .args;
971 assert_eq!(args.max_outbound_peers, Some(75));
972 assert_eq!(args.max_inbound_peers, Some(15));
973 }
974
975 #[test]
976 fn parse_trusted_peer_args() {
977 let args =
978 CommandParser::<NetworkArgs>::parse_from([
979 "reth",
980 "--trusted-peers",
981 "enode://d860a01f9722d78051619d1e2351aba3f43f943f6f00718d1b9baa4101932a1f5011f16bb2b1bb35db20d6fe28fa0bf09636d26a87d31de9ec6203eeedb1f666@18.138.108.67:30303,enode://22a8232c3abc76a16ae9d6c3b164f98775fe226f0917b0ca871128a74a8e9630b458460865bab457221f1d448dd9791d24c4e5d88786180ac185df813a68d4de@3.209.45.79:30303"
982 ])
983 .args;
984
985 assert_eq!(
986 args.trusted_peers,
987 vec![
988 "enode://d860a01f9722d78051619d1e2351aba3f43f943f6f00718d1b9baa4101932a1f5011f16bb2b1bb35db20d6fe28fa0bf09636d26a87d31de9ec6203eeedb1f666@18.138.108.67:30303".parse().unwrap(),
989 "enode://22a8232c3abc76a16ae9d6c3b164f98775fe226f0917b0ca871128a74a8e9630b458460865bab457221f1d448dd9791d24c4e5d88786180ac185df813a68d4de@3.209.45.79:30303".parse().unwrap()
990 ]
991 );
992 }
993
994 #[test]
995 fn parse_retry_strategy_args() {
996 let tests = vec![0, 10];
997
998 for retries in tests {
999 let retries_str = retries.to_string();
1000 let args = CommandParser::<NetworkArgs>::parse_from([
1001 "reth",
1002 "--dns-retries",
1003 retries_str.as_str(),
1004 ])
1005 .args;
1006
1007 assert_eq!(args.dns_retries, retries);
1008 }
1009 }
1010
1011 #[test]
1012 fn parse_disable_tx_gossip_args() {
1013 let args = CommandParser::<NetworkArgs>::parse_from(["reth", "--disable-tx-gossip"]).args;
1014 assert!(args.disable_tx_gossip);
1015 }
1016
1017 #[test]
1018 fn parse_max_peers_flag() {
1019 let args = CommandParser::<NetworkArgs>::parse_from(["reth", "--max-peers", "90"]).args;
1020
1021 assert_eq!(args.max_peers, Some(90));
1022 assert_eq!(args.max_outbound_peers, None);
1023 assert_eq!(args.max_inbound_peers, None);
1024 assert_eq!(args.resolved_max_outbound_peers(), Some(30));
1025 assert_eq!(args.resolved_max_inbound_peers(), Some(60));
1026 }
1027
1028 #[test]
1029 fn max_peers_conflicts_with_outbound() {
1030 let result = CommandParser::<NetworkArgs>::try_parse_from([
1031 "reth",
1032 "--max-peers",
1033 "90",
1034 "--max-outbound-peers",
1035 "50",
1036 ]);
1037 assert!(
1038 result.is_err(),
1039 "Should fail when both --max-peers and --max-outbound-peers are used"
1040 );
1041 }
1042
1043 #[test]
1044 fn max_peers_conflicts_with_inbound() {
1045 let result = CommandParser::<NetworkArgs>::try_parse_from([
1046 "reth",
1047 "--max-peers",
1048 "90",
1049 "--max-inbound-peers",
1050 "30",
1051 ]);
1052 assert!(
1053 result.is_err(),
1054 "Should fail when both --max-peers and --max-inbound-peers are used"
1055 );
1056 }
1057
1058 #[test]
1059 fn max_peers_split_calculation() {
1060 let args = CommandParser::<NetworkArgs>::parse_from(["reth", "--max-peers", "90"]).args;
1061
1062 assert_eq!(args.max_peers, Some(90));
1063 assert_eq!(args.resolved_max_outbound_peers(), Some(30));
1064 assert_eq!(args.resolved_max_inbound_peers(), Some(60));
1065 }
1066
1067 #[test]
1068 fn max_peers_small_values() {
1069 let args1 = CommandParser::<NetworkArgs>::parse_from(["reth", "--max-peers", "1"]).args;
1070 assert_eq!(args1.resolved_max_outbound_peers(), Some(1));
1071 assert_eq!(args1.resolved_max_inbound_peers(), Some(0));
1072
1073 let args2 = CommandParser::<NetworkArgs>::parse_from(["reth", "--max-peers", "2"]).args;
1074 assert_eq!(args2.resolved_max_outbound_peers(), Some(1));
1075 assert_eq!(args2.resolved_max_inbound_peers(), Some(1));
1076
1077 let args3 = CommandParser::<NetworkArgs>::parse_from(["reth", "--max-peers", "3"]).args;
1078 assert_eq!(args3.resolved_max_outbound_peers(), Some(1));
1079 assert_eq!(args3.resolved_max_inbound_peers(), Some(2));
1080 }
1081
1082 #[test]
1083 fn resolved_peers_without_max_peers() {
1084 let args = CommandParser::<NetworkArgs>::parse_from([
1085 "reth",
1086 "--max-outbound-peers",
1087 "75",
1088 "--max-inbound-peers",
1089 "15",
1090 ])
1091 .args;
1092
1093 assert_eq!(args.max_peers, None);
1094 assert_eq!(args.resolved_max_outbound_peers(), Some(75));
1095 assert_eq!(args.resolved_max_inbound_peers(), Some(15));
1096 }
1097
1098 #[test]
1099 fn resolved_peers_with_defaults() {
1100 let args = CommandParser::<NetworkArgs>::parse_from(["reth"]).args;
1101
1102 assert_eq!(args.max_peers, None);
1103 assert_eq!(args.resolved_max_outbound_peers(), None);
1104 assert_eq!(args.resolved_max_inbound_peers(), None);
1105 }
1106
1107 #[test]
1108 fn network_args_default_sanity_test() {
1109 let default_args = NetworkArgs::default();
1110 let args = CommandParser::<NetworkArgs>::parse_from(["reth"]).args;
1111
1112 assert_eq!(args, default_args);
1113 }
1114
1115 #[test]
1116 fn parse_eth_max_message_size() {
1117 let args = CommandParser::<NetworkArgs>::parse_from([
1118 "reth",
1119 "--eth-max-message-size",
1120 "15728640",
1121 ])
1122 .args;
1123
1124 assert_eq!(args.eth_max_message_size, Some(NonZeroUsize::new(15 * 1024 * 1024).unwrap()));
1125 }
1126
1127 #[test]
1128 fn parse_eth_max_message_size_zero_rejected() {
1129 let result =
1130 CommandParser::<NetworkArgs>::try_parse_from(["reth", "--eth-max-message-size", "0"]);
1131 assert!(result.is_err());
1132 }
1133
1134 #[test]
1135 fn parse_eth_max_message_size_above_rlpx_cap() {
1136 let result = CommandParser::<NetworkArgs>::try_parse_from([
1137 "reth",
1138 "--eth-max-message-size",
1139 "16777216",
1140 ]);
1141 assert!(result.is_ok());
1142 let args = result.unwrap().args;
1143 assert_eq!(args.eth_max_message_size, Some(NonZeroUsize::new(16 * 1024 * 1024).unwrap()));
1144 }
1145
1146 #[test]
1147 fn parse_required_block_hashes() {
1148 let args = CommandParser::<NetworkArgs>::parse_from([
1149 "reth",
1150 "--required-block-hashes",
1151 "0x1111111111111111111111111111111111111111111111111111111111111111,23115201=0x2222222222222222222222222222222222222222222222222222222222222222",
1152 ])
1153 .args;
1154
1155 assert_eq!(args.required_block_hashes.len(), 2);
1156 assert_eq!(args.required_block_hashes[0].number, 0);
1158 assert_eq!(
1159 args.required_block_hashes[0].hash.to_string(),
1160 "0x1111111111111111111111111111111111111111111111111111111111111111"
1161 );
1162 assert_eq!(args.required_block_hashes[1].number, 23115201);
1164 assert_eq!(
1165 args.required_block_hashes[1].hash.to_string(),
1166 "0x2222222222222222222222222222222222222222222222222222222222222222"
1167 );
1168 }
1169
1170 #[test]
1171 fn parse_empty_required_block_hashes() {
1172 let args = CommandParser::<NetworkArgs>::parse_from(["reth"]).args;
1173 assert!(args.required_block_hashes.is_empty());
1174 }
1175
1176 #[test]
1177 fn test_parse_block_num_hash() {
1178 let result = parse_block_num_hash(
1180 "0x1111111111111111111111111111111111111111111111111111111111111111",
1181 );
1182 assert!(result.is_ok());
1183 assert_eq!(result.unwrap().number, 0);
1184
1185 let result = parse_block_num_hash(
1187 "23115201=0x2222222222222222222222222222222222222222222222222222222222222222",
1188 );
1189 assert!(result.is_ok());
1190 assert_eq!(result.unwrap().number, 23115201);
1191
1192 assert!(parse_block_num_hash("invalid").is_err());
1194 assert!(parse_block_num_hash(
1195 "abc=0x1111111111111111111111111111111111111111111111111111111111111111"
1196 )
1197 .is_err());
1198 }
1199
1200 #[test]
1201 fn parse_p2p_secret_key_hex() {
1202 let hex = "4c0883a69102937d6231471b5dbb6204fe512961708279f8c5c58b3b9c4e8b8f";
1203 let args =
1204 CommandParser::<NetworkArgs>::parse_from(["reth", "--p2p-secret-key-hex", hex]).args;
1205
1206 let expected: B256 = hex.parse().unwrap();
1207 assert_eq!(args.p2p_secret_key_hex, Some(expected));
1208 assert_eq!(args.p2p_secret_key, None);
1209 }
1210
1211 #[test]
1212 fn parse_p2p_secret_key_hex_with_0x_prefix() {
1213 let hex = "0x4c0883a69102937d6231471b5dbb6204fe512961708279f8c5c58b3b9c4e8b8f";
1214 let args =
1215 CommandParser::<NetworkArgs>::parse_from(["reth", "--p2p-secret-key-hex", hex]).args;
1216
1217 let expected: B256 = hex.parse().unwrap();
1218 assert_eq!(args.p2p_secret_key_hex, Some(expected));
1219 assert_eq!(args.p2p_secret_key, None);
1220 }
1221
1222 #[test]
1223 fn test_p2p_secret_key_and_hex_are_mutually_exclusive() {
1224 let result = CommandParser::<NetworkArgs>::try_parse_from([
1225 "reth",
1226 "--p2p-secret-key",
1227 "/path/to/key",
1228 "--p2p-secret-key-hex",
1229 "4c0883a69102937d6231471b5dbb6204fe512961708279f8c5c58b3b9c4e8b8f",
1230 ]);
1231
1232 assert!(result.is_err());
1233 }
1234
1235 #[test]
1236 fn test_secret_key_method_with_hex() {
1237 let hex = "4c0883a69102937d6231471b5dbb6204fe512961708279f8c5c58b3b9c4e8b8f";
1238 let args =
1239 CommandParser::<NetworkArgs>::parse_from(["reth", "--p2p-secret-key-hex", hex]).args;
1240
1241 let temp_dir = std::env::temp_dir();
1242 let default_path = temp_dir.join("default_key");
1243 let secret_key = args.secret_key(default_path).unwrap();
1244
1245 assert_eq!(alloy_primitives::hex::encode(secret_key.secret_bytes()), hex);
1247 }
1248
1249 #[test]
1250 fn parse_netrestrict_single_network() {
1251 let args =
1252 CommandParser::<NetworkArgs>::parse_from(["reth", "--netrestrict", "192.168.0.0/16"])
1253 .args;
1254
1255 assert_eq!(args.netrestrict, Some("192.168.0.0/16".to_string()));
1256
1257 let ip_filter = args.ip_filter().unwrap();
1258 assert!(ip_filter.has_restrictions());
1259 assert!(ip_filter.is_allowed(&"192.168.1.1".parse().unwrap()));
1260 assert!(!ip_filter.is_allowed(&"10.0.0.1".parse().unwrap()));
1261 }
1262
1263 #[test]
1264 fn parse_netrestrict_multiple_networks() {
1265 let args = CommandParser::<NetworkArgs>::parse_from([
1266 "reth",
1267 "--netrestrict",
1268 "192.168.0.0/16,10.0.0.0/8",
1269 ])
1270 .args;
1271
1272 assert_eq!(args.netrestrict, Some("192.168.0.0/16,10.0.0.0/8".to_string()));
1273
1274 let ip_filter = args.ip_filter().unwrap();
1275 assert!(ip_filter.has_restrictions());
1276 assert!(ip_filter.is_allowed(&"192.168.1.1".parse().unwrap()));
1277 assert!(ip_filter.is_allowed(&"10.5.10.20".parse().unwrap()));
1278 assert!(!ip_filter.is_allowed(&"172.16.0.1".parse().unwrap()));
1279 }
1280
1281 #[test]
1282 fn parse_netrestrict_ipv6() {
1283 let args =
1284 CommandParser::<NetworkArgs>::parse_from(["reth", "--netrestrict", "2001:db8::/32"])
1285 .args;
1286
1287 let ip_filter = args.ip_filter().unwrap();
1288 assert!(ip_filter.has_restrictions());
1289 assert!(ip_filter.is_allowed(&"2001:db8::1".parse().unwrap()));
1290 assert!(!ip_filter.is_allowed(&"2001:db9::1".parse().unwrap()));
1291 }
1292
1293 #[test]
1294 fn netrestrict_not_set() {
1295 let args = CommandParser::<NetworkArgs>::parse_from(["reth"]).args;
1296 assert_eq!(args.netrestrict, None);
1297
1298 let ip_filter = args.ip_filter().unwrap();
1299 assert!(!ip_filter.has_restrictions());
1300 assert!(ip_filter.is_allowed(&"192.168.1.1".parse().unwrap()));
1301 assert!(ip_filter.is_allowed(&"10.0.0.1".parse().unwrap()));
1302 }
1303
1304 #[test]
1305 fn netrestrict_invalid_cidr() {
1306 let args =
1307 CommandParser::<NetworkArgs>::parse_from(["reth", "--netrestrict", "invalid-cidr"])
1308 .args;
1309
1310 assert!(args.ip_filter().is_err());
1311 }
1312
1313 #[test]
1314 fn network_config_preserves_basic_nodes_from_peers_file() {
1315 let enode = "enode://6f8a80d14311c39f35f516fa664deaaaa13e85b2f7493f37f6144d86991ec012937307647bd3b9a82abe2974e1407241d54947bbb39763a4cac9f77166ad92a0@10.3.58.6:30303?discport=30301";
1316 let unique = SystemTime::now().duration_since(UNIX_EPOCH).unwrap().as_nanos();
1317
1318 let peers_file = std::env::temp_dir().join(format!("reth_peers_test_{}.json", unique));
1319 fs::write(&peers_file, format!("[\"{}\"]", enode)).expect("write peers file");
1320
1321 let args = NetworkArgs {
1323 peers_file: Some(peers_file.clone()),
1324 no_persist_peers: false,
1325 ..Default::default()
1326 };
1327
1328 let secret_key = SecretKey::from_byte_array(&[1u8; 32]).unwrap();
1330 let builder = args.network_config::<reth_network::EthNetworkPrimitives>(
1331 &Config::default(),
1332 MAINNET.clone(),
1333 secret_key,
1334 peers_file.clone(),
1335 Runtime::test(),
1336 );
1337
1338 let net_cfg = builder.build_with_noop_provider(MAINNET.clone());
1339
1340 let node: NodeRecord = enode.parse().unwrap();
1342 assert!(net_cfg.peers_config.persisted_peers.iter().any(|p| p.record == node));
1343
1344 let _ = fs::remove_file(&peers_file);
1346 }
1347}