1use alloy_eips::BlockNumHash;
4use alloy_primitives::B256;
5use std::{
6 net::{IpAddr, Ipv4Addr, Ipv6Addr, SocketAddr, SocketAddrV4, SocketAddrV6},
7 ops::Not,
8 path::PathBuf,
9};
10
11use crate::version::version_metadata;
12use clap::Args;
13use reth_chainspec::EthChainSpec;
14use reth_cli_util::{get_secret_key, load_secret_key::SecretKeyError};
15use reth_config::Config;
16use reth_discv4::{NodeRecord, DEFAULT_DISCOVERY_ADDR, DEFAULT_DISCOVERY_PORT};
17use reth_discv5::{
18 discv5::ListenConfig, DEFAULT_COUNT_BOOTSTRAP_LOOKUPS, DEFAULT_DISCOVERY_V5_PORT,
19 DEFAULT_SECONDS_BOOTSTRAP_LOOKUP_INTERVAL, DEFAULT_SECONDS_LOOKUP_INTERVAL,
20};
21use reth_net_banlist::IpFilter;
22use reth_net_nat::{NatResolver, DEFAULT_NET_IF_NAME};
23use reth_network::{
24 transactions::{
25 config::{TransactionIngressPolicy, TransactionPropagationKind},
26 constants::{
27 tx_fetcher::{
28 DEFAULT_MAX_CAPACITY_CACHE_PENDING_FETCH, DEFAULT_MAX_COUNT_CONCURRENT_REQUESTS,
29 DEFAULT_MAX_COUNT_CONCURRENT_REQUESTS_PER_PEER,
30 },
31 tx_manager::{
32 DEFAULT_MAX_COUNT_PENDING_POOL_IMPORTS, DEFAULT_MAX_COUNT_TRANSACTIONS_SEEN_BY_PEER,
33 },
34 },
35 TransactionFetcherConfig, TransactionPropagationMode, TransactionsManagerConfig,
36 DEFAULT_SOFT_LIMIT_BYTE_SIZE_POOLED_TRANSACTIONS_RESP_ON_PACK_GET_POOLED_TRANSACTIONS_REQ,
37 SOFT_LIMIT_BYTE_SIZE_POOLED_TRANSACTIONS_RESPONSE,
38 },
39 HelloMessageWithProtocols, NetworkConfigBuilder, NetworkPrimitives,
40};
41use reth_network_peers::{mainnet_nodes, TrustedPeer};
42use secp256k1::SecretKey;
43use std::str::FromStr;
44use tracing::error;
45
46#[derive(Debug, Clone, Args, PartialEq, Eq)]
48#[command(next_help_heading = "Networking")]
49pub struct NetworkArgs {
50 #[command(flatten)]
52 pub discovery: DiscoveryArgs,
53
54 #[expect(clippy::doc_markdown)]
55 #[arg(long, value_delimiter = ',')]
59 pub trusted_peers: Vec<TrustedPeer>,
60
61 #[arg(long)]
63 pub trusted_only: bool,
64
65 #[arg(long, value_delimiter = ',')]
69 pub bootnodes: Option<Vec<TrustedPeer>>,
70
71 #[arg(long, default_value_t = 0)]
73 pub dns_retries: usize,
74
75 #[arg(long, value_name = "FILE", verbatim_doc_comment, conflicts_with = "no_persist_peers")]
78 pub peers_file: Option<PathBuf>,
79
80 #[arg(long, value_name = "IDENTITY", default_value = version_metadata().p2p_client_version.as_ref())]
82 pub identity: String,
83
84 #[arg(long, value_name = "PATH", conflicts_with = "p2p_secret_key_hex")]
89 pub p2p_secret_key: Option<PathBuf>,
90
91 #[arg(long, value_name = "HEX", conflicts_with = "p2p_secret_key")]
96 pub p2p_secret_key_hex: Option<B256>,
97
98 #[arg(long, verbatim_doc_comment)]
100 pub no_persist_peers: bool,
101
102 #[arg(long, default_value = "any")]
104 pub nat: NatResolver,
105
106 #[arg(long = "addr", value_name = "ADDR", default_value_t = DEFAULT_DISCOVERY_ADDR)]
108 pub addr: IpAddr,
109
110 #[arg(long = "port", value_name = "PORT", default_value_t = DEFAULT_DISCOVERY_PORT)]
112 pub port: u16,
113
114 #[arg(long)]
116 pub max_outbound_peers: Option<usize>,
117
118 #[arg(long)]
120 pub max_inbound_peers: Option<usize>,
121
122 #[arg(
127 long,
128 value_name = "COUNT",
129 conflicts_with = "max_outbound_peers",
130 conflicts_with = "max_inbound_peers"
131 )]
132 pub max_peers: Option<usize>,
133
134 #[arg(long = "max-tx-reqs", value_name = "COUNT", default_value_t = DEFAULT_MAX_COUNT_CONCURRENT_REQUESTS, verbatim_doc_comment)]
136 pub max_concurrent_tx_requests: u32,
137
138 #[arg(long = "max-tx-reqs-peer", value_name = "COUNT", default_value_t = DEFAULT_MAX_COUNT_CONCURRENT_REQUESTS_PER_PEER, verbatim_doc_comment)]
140 pub max_concurrent_tx_requests_per_peer: u8,
141
142 #[arg(long = "max-seen-tx-history", value_name = "COUNT", default_value_t = DEFAULT_MAX_COUNT_TRANSACTIONS_SEEN_BY_PEER, verbatim_doc_comment)]
146 pub max_seen_tx_history: u32,
147
148 #[arg(long = "max-pending-imports", value_name = "COUNT", default_value_t = DEFAULT_MAX_COUNT_PENDING_POOL_IMPORTS, verbatim_doc_comment)]
149 pub max_pending_pool_imports: usize,
151
152 #[arg(long = "pooled-tx-response-soft-limit", value_name = "BYTES", default_value_t = SOFT_LIMIT_BYTE_SIZE_POOLED_TRANSACTIONS_RESPONSE, verbatim_doc_comment)]
156 pub soft_limit_byte_size_pooled_transactions_response: usize,
157
158 #[arg(long = "pooled-tx-pack-soft-limit", value_name = "BYTES", default_value_t = DEFAULT_SOFT_LIMIT_BYTE_SIZE_POOLED_TRANSACTIONS_RESP_ON_PACK_GET_POOLED_TRANSACTIONS_REQ, verbatim_doc_comment)]
170 pub soft_limit_byte_size_pooled_transactions_response_on_pack_request: usize,
171
172 #[arg(long = "max-tx-pending-fetch", value_name = "COUNT", default_value_t = DEFAULT_MAX_CAPACITY_CACHE_PENDING_FETCH, verbatim_doc_comment)]
174 pub max_capacity_cache_txns_pending_fetch: u32,
175
176 #[arg(long = "net-if.experimental", conflicts_with = "addr", value_name = "IF_NAME")]
180 pub net_if: Option<String>,
181
182 #[arg(long = "tx-propagation-policy", default_value_t = TransactionPropagationKind::All)]
186 pub tx_propagation_policy: TransactionPropagationKind,
187
188 #[arg(long = "tx-ingress-policy", default_value_t = TransactionIngressPolicy::All)]
192 pub tx_ingress_policy: TransactionIngressPolicy,
193
194 #[arg(long = "disable-tx-gossip")]
199 pub disable_tx_gossip: bool,
200
201 #[arg(
206 long = "tx-propagation-mode",
207 default_value = "sqrt",
208 help = "Transaction propagation mode (sqrt, all, max:<number>)"
209 )]
210 pub propagation_mode: TransactionPropagationMode,
211
212 #[arg(long = "required-block-hashes", value_delimiter = ',', value_parser = parse_block_num_hash)]
216 pub required_block_hashes: Vec<BlockNumHash>,
217
218 #[arg(long)]
220 pub network_id: Option<u64>,
221
222 #[arg(long, value_name = "NETRESTRICT")]
229 pub netrestrict: Option<String>,
230
231 #[arg(long)]
237 pub enforce_enr_fork_id: bool,
238}
239
240impl NetworkArgs {
241 pub fn resolved_addr(&self) -> IpAddr {
243 if let Some(ref if_name) = self.net_if {
244 let if_name = if if_name.is_empty() { DEFAULT_NET_IF_NAME } else { if_name };
245 return match reth_net_nat::net_if::resolve_net_if_ip(if_name) {
246 Ok(addr) => addr,
247 Err(err) => {
248 error!(target: "reth::cli",
249 if_name,
250 %err,
251 "Failed to read network interface IP"
252 );
253
254 DEFAULT_DISCOVERY_ADDR
255 }
256 };
257 }
258
259 self.addr
260 }
261
262 pub fn resolved_bootnodes(&self) -> Option<Vec<NodeRecord>> {
264 self.bootnodes.clone().map(|bootnodes| {
265 bootnodes.into_iter().filter_map(|node| node.resolve_blocking().ok()).collect()
266 })
267 }
268
269 pub fn resolved_max_inbound_peers(&self) -> Option<usize> {
271 if let Some(max_peers) = self.max_peers {
272 if max_peers == 0 {
273 Some(0)
274 } else {
275 let outbound = (max_peers / 3).max(1);
276 Some(max_peers.saturating_sub(outbound))
277 }
278 } else {
279 self.max_inbound_peers
280 }
281 }
282
283 pub fn resolved_max_outbound_peers(&self) -> Option<usize> {
285 if let Some(max_peers) = self.max_peers {
286 if max_peers == 0 {
287 Some(0)
288 } else {
289 Some((max_peers / 3).max(1))
290 }
291 } else {
292 self.max_outbound_peers
293 }
294 }
295
296 pub const fn transactions_manager_config(&self) -> TransactionsManagerConfig {
298 TransactionsManagerConfig {
299 transaction_fetcher_config: TransactionFetcherConfig::new(
300 self.max_concurrent_tx_requests,
301 self.max_concurrent_tx_requests_per_peer,
302 self.soft_limit_byte_size_pooled_transactions_response,
303 self.soft_limit_byte_size_pooled_transactions_response_on_pack_request,
304 self.max_capacity_cache_txns_pending_fetch,
305 ),
306 max_transactions_seen_by_peer_history: self.max_seen_tx_history,
307 propagation_mode: self.propagation_mode,
308 ingress_policy: self.tx_ingress_policy,
309 }
310 }
311
312 pub fn network_config<N: NetworkPrimitives>(
324 &self,
325 config: &Config,
326 chain_spec: impl EthChainSpec,
327 secret_key: SecretKey,
328 default_peers_file: PathBuf,
329 ) -> NetworkConfigBuilder<N> {
330 let addr = self.resolved_addr();
331 let chain_bootnodes = self
332 .resolved_bootnodes()
333 .unwrap_or_else(|| chain_spec.bootnodes().unwrap_or_else(mainnet_nodes));
334 let peers_file = self.peers_file.clone().unwrap_or(default_peers_file);
335
336 let ip_filter = self.ip_filter().unwrap_or_default();
338 let peers_config = config
339 .peers_config_with_basic_nodes_from_file(
340 self.persistent_peers_file(peers_file).as_deref(),
341 )
342 .with_max_inbound_opt(self.resolved_max_inbound_peers())
343 .with_max_outbound_opt(self.resolved_max_outbound_peers())
344 .with_ip_filter(ip_filter)
345 .with_enforce_enr_fork_id(self.enforce_enr_fork_id);
346
347 NetworkConfigBuilder::<N>::new(secret_key)
349 .external_ip_resolver(self.nat.clone())
350 .sessions_config(
351 config.sessions.clone().with_upscaled_event_buffer(peers_config.max_peers()),
352 )
353 .peer_config(peers_config)
354 .boot_nodes(chain_bootnodes.clone())
355 .transactions_manager_config(self.transactions_manager_config())
356 .apply(|builder| {
358 let peer_id = builder.get_peer_id();
359 builder.hello_message(
360 HelloMessageWithProtocols::builder(peer_id)
361 .client_version(&self.identity)
362 .build(),
363 )
364 })
365 .apply(|builder| {
367 let rlpx_socket = (addr, self.port).into();
368 self.discovery.apply_to_builder(builder, rlpx_socket, chain_bootnodes)
369 })
370 .listener_addr(SocketAddr::new(
371 addr, self.port,
373 ))
374 .discovery_addr(SocketAddr::new(
375 self.discovery.addr,
376 self.discovery.port,
378 ))
379 .disable_tx_gossip(self.disable_tx_gossip)
380 .required_block_hashes(self.required_block_hashes.clone())
381 .network_id(self.network_id)
382 }
383
384 pub fn persistent_peers_file(&self, peers_file: PathBuf) -> Option<PathBuf> {
386 self.no_persist_peers.not().then_some(peers_file)
387 }
388
389 pub const fn with_discovery(mut self, discovery: DiscoveryArgs) -> Self {
391 self.discovery = discovery;
392 self
393 }
394
395 pub const fn with_unused_p2p_port(mut self) -> Self {
398 self.port = 0;
399 self
400 }
401
402 pub const fn with_unused_ports(mut self) -> Self {
405 self = self.with_unused_p2p_port();
406 self.discovery = self.discovery.with_unused_discovery_port();
407 self
408 }
409
410 pub fn with_nat_resolver(mut self, nat: NatResolver) -> Self {
412 self.nat = nat;
413 self
414 }
415
416 pub fn adjust_instance_ports(&mut self, instance: Option<u16>) {
422 if let Some(instance) = instance {
423 debug_assert_ne!(instance, 0, "instance must be non-zero");
424 self.port += instance - 1;
425 self.discovery.adjust_instance_ports(instance);
426 }
427 }
428
429 pub async fn resolve_trusted_peers(&self) -> Result<Vec<NodeRecord>, std::io::Error> {
431 futures::future::try_join_all(
432 self.trusted_peers.iter().map(|peer| async move { peer.resolve().await }),
433 )
434 .await
435 }
436
437 pub fn secret_key(
443 &self,
444 default_secret_key_path: PathBuf,
445 ) -> Result<SecretKey, SecretKeyError> {
446 if let Some(b256) = &self.p2p_secret_key_hex {
447 SecretKey::from_slice(b256.as_slice()).map_err(SecretKeyError::SecretKeyDecodeError)
449 } else {
450 let secret_key_path = self.p2p_secret_key.clone().unwrap_or(default_secret_key_path);
452 get_secret_key(&secret_key_path)
453 }
454 }
455
456 pub fn ip_filter(&self) -> Result<IpFilter, ipnet::AddrParseError> {
460 if let Some(netrestrict) = &self.netrestrict {
461 IpFilter::from_cidr_string(netrestrict)
462 } else {
463 Ok(IpFilter::allow_all())
464 }
465 }
466}
467
468impl Default for NetworkArgs {
469 fn default() -> Self {
470 Self {
471 discovery: DiscoveryArgs::default(),
472 trusted_peers: vec![],
473 trusted_only: false,
474 bootnodes: None,
475 dns_retries: 0,
476 peers_file: None,
477 identity: version_metadata().p2p_client_version.to_string(),
478 p2p_secret_key: None,
479 p2p_secret_key_hex: None,
480 no_persist_peers: false,
481 nat: NatResolver::Any,
482 addr: DEFAULT_DISCOVERY_ADDR,
483 port: DEFAULT_DISCOVERY_PORT,
484 max_outbound_peers: None,
485 max_inbound_peers: None,
486 max_peers: None,
487 max_concurrent_tx_requests: DEFAULT_MAX_COUNT_CONCURRENT_REQUESTS,
488 max_concurrent_tx_requests_per_peer: DEFAULT_MAX_COUNT_CONCURRENT_REQUESTS_PER_PEER,
489 soft_limit_byte_size_pooled_transactions_response:
490 SOFT_LIMIT_BYTE_SIZE_POOLED_TRANSACTIONS_RESPONSE,
491 soft_limit_byte_size_pooled_transactions_response_on_pack_request: DEFAULT_SOFT_LIMIT_BYTE_SIZE_POOLED_TRANSACTIONS_RESP_ON_PACK_GET_POOLED_TRANSACTIONS_REQ,
492 max_pending_pool_imports: DEFAULT_MAX_COUNT_PENDING_POOL_IMPORTS,
493 max_seen_tx_history: DEFAULT_MAX_COUNT_TRANSACTIONS_SEEN_BY_PEER,
494 max_capacity_cache_txns_pending_fetch: DEFAULT_MAX_CAPACITY_CACHE_PENDING_FETCH,
495 net_if: None,
496 tx_propagation_policy: TransactionPropagationKind::default(),
497 tx_ingress_policy: TransactionIngressPolicy::default(),
498 disable_tx_gossip: false,
499 propagation_mode: TransactionPropagationMode::Sqrt,
500 required_block_hashes: vec![],
501 network_id: None,
502 netrestrict: None,
503 enforce_enr_fork_id: false,
504 }
505 }
506}
507
508#[derive(Debug, Clone, Args, PartialEq, Eq)]
510pub struct DiscoveryArgs {
511 #[arg(short, long, default_value_if("dev", "true", "true"))]
513 pub disable_discovery: bool,
514
515 #[arg(long, conflicts_with = "disable_discovery")]
517 pub disable_dns_discovery: bool,
518
519 #[arg(long, conflicts_with = "disable_discovery")]
521 pub disable_discv4_discovery: bool,
522
523 #[arg(long, conflicts_with = "disable_discovery")]
525 pub enable_discv5_discovery: bool,
526
527 #[arg(long, conflicts_with = "disable_discovery")]
529 pub disable_nat: bool,
530
531 #[arg(id = "discovery.addr", long = "discovery.addr", value_name = "DISCOVERY_ADDR", default_value_t = DEFAULT_DISCOVERY_ADDR)]
533 pub addr: IpAddr,
534
535 #[arg(id = "discovery.port", long = "discovery.port", value_name = "DISCOVERY_PORT", default_value_t = DEFAULT_DISCOVERY_PORT)]
537 pub port: u16,
538
539 #[arg(id = "discovery.v5.addr", long = "discovery.v5.addr", value_name = "DISCOVERY_V5_ADDR", default_value = None)]
542 pub discv5_addr: Option<Ipv4Addr>,
543
544 #[arg(id = "discovery.v5.addr.ipv6", long = "discovery.v5.addr.ipv6", value_name = "DISCOVERY_V5_ADDR_IPV6", default_value = None)]
547 pub discv5_addr_ipv6: Option<Ipv6Addr>,
548
549 #[arg(id = "discovery.v5.port", long = "discovery.v5.port", value_name = "DISCOVERY_V5_PORT",
552 default_value_t = DEFAULT_DISCOVERY_V5_PORT)]
553 pub discv5_port: u16,
554
555 #[arg(id = "discovery.v5.port.ipv6", long = "discovery.v5.port.ipv6", value_name = "DISCOVERY_V5_PORT_IPV6",
558 default_value = None, default_value_t = DEFAULT_DISCOVERY_V5_PORT)]
559 pub discv5_port_ipv6: u16,
560
561 #[arg(id = "discovery.v5.lookup-interval", long = "discovery.v5.lookup-interval", value_name = "DISCOVERY_V5_LOOKUP_INTERVAL", default_value_t = DEFAULT_SECONDS_LOOKUP_INTERVAL)]
564 pub discv5_lookup_interval: u64,
565
566 #[arg(id = "discovery.v5.bootstrap.lookup-interval", long = "discovery.v5.bootstrap.lookup-interval", value_name = "DISCOVERY_V5_BOOTSTRAP_LOOKUP_INTERVAL",
569 default_value_t = DEFAULT_SECONDS_BOOTSTRAP_LOOKUP_INTERVAL)]
570 pub discv5_bootstrap_lookup_interval: u64,
571
572 #[arg(id = "discovery.v5.bootstrap.lookup-countdown", long = "discovery.v5.bootstrap.lookup-countdown", value_name = "DISCOVERY_V5_BOOTSTRAP_LOOKUP_COUNTDOWN",
574 default_value_t = DEFAULT_COUNT_BOOTSTRAP_LOOKUPS)]
575 pub discv5_bootstrap_lookup_countdown: u64,
576}
577
578impl DiscoveryArgs {
579 pub fn apply_to_builder<N>(
581 &self,
582 mut network_config_builder: NetworkConfigBuilder<N>,
583 rlpx_tcp_socket: SocketAddr,
584 boot_nodes: impl IntoIterator<Item = NodeRecord>,
585 ) -> NetworkConfigBuilder<N>
586 where
587 N: NetworkPrimitives,
588 {
589 if self.disable_discovery || self.disable_dns_discovery {
590 network_config_builder = network_config_builder.disable_dns_discovery();
591 }
592
593 if self.disable_discovery || self.disable_discv4_discovery {
594 network_config_builder = network_config_builder.disable_discv4_discovery();
595 }
596
597 if self.disable_nat {
598 network_config_builder = network_config_builder.disable_nat();
600 }
601
602 if self.should_enable_discv5() {
603 network_config_builder = network_config_builder
604 .discovery_v5(self.discovery_v5_builder(rlpx_tcp_socket, boot_nodes));
605 }
606
607 network_config_builder
608 }
609
610 pub fn discovery_v5_builder(
612 &self,
613 rlpx_tcp_socket: SocketAddr,
614 boot_nodes: impl IntoIterator<Item = NodeRecord>,
615 ) -> reth_discv5::ConfigBuilder {
616 let Self {
617 discv5_addr,
618 discv5_addr_ipv6,
619 discv5_port,
620 discv5_port_ipv6,
621 discv5_lookup_interval,
622 discv5_bootstrap_lookup_interval,
623 discv5_bootstrap_lookup_countdown,
624 ..
625 } = self;
626
627 let discv5_addr_ipv4 = discv5_addr.or(match rlpx_tcp_socket {
629 SocketAddr::V4(addr) => Some(*addr.ip()),
630 SocketAddr::V6(_) => None,
631 });
632 let discv5_addr_ipv6 = discv5_addr_ipv6.or(match rlpx_tcp_socket {
633 SocketAddr::V4(_) => None,
634 SocketAddr::V6(addr) => Some(*addr.ip()),
635 });
636
637 reth_discv5::Config::builder(rlpx_tcp_socket)
638 .discv5_config(
639 reth_discv5::discv5::ConfigBuilder::new(ListenConfig::from_two_sockets(
640 discv5_addr_ipv4.map(|addr| SocketAddrV4::new(addr, *discv5_port)),
641 discv5_addr_ipv6.map(|addr| SocketAddrV6::new(addr, *discv5_port_ipv6, 0, 0)),
642 ))
643 .build(),
644 )
645 .add_unsigned_boot_nodes(boot_nodes)
646 .lookup_interval(*discv5_lookup_interval)
647 .bootstrap_lookup_interval(*discv5_bootstrap_lookup_interval)
648 .bootstrap_lookup_countdown(*discv5_bootstrap_lookup_countdown)
649 }
650
651 const fn should_enable_discv5(&self) -> bool {
653 if self.disable_discovery {
654 return false;
655 }
656
657 self.enable_discv5_discovery ||
658 self.discv5_addr.is_some() ||
659 self.discv5_addr_ipv6.is_some()
660 }
661
662 pub const fn with_unused_discovery_port(mut self) -> Self {
665 self.port = 0;
666 self
667 }
668
669 pub const fn with_discv5_port(mut self, port: u16) -> Self {
671 self.discv5_port = port;
672 self
673 }
674
675 pub fn adjust_instance_ports(&mut self, instance: u16) {
681 debug_assert_ne!(instance, 0, "instance must be non-zero");
682 self.port += instance - 1;
683 self.discv5_port += instance - 1;
684 self.discv5_port_ipv6 += instance - 1;
685 }
686}
687
688impl Default for DiscoveryArgs {
689 fn default() -> Self {
690 Self {
691 disable_discovery: false,
692 disable_dns_discovery: false,
693 disable_discv4_discovery: false,
694 enable_discv5_discovery: false,
695 disable_nat: false,
696 addr: DEFAULT_DISCOVERY_ADDR,
697 port: DEFAULT_DISCOVERY_PORT,
698 discv5_addr: None,
699 discv5_addr_ipv6: None,
700 discv5_port: DEFAULT_DISCOVERY_V5_PORT,
701 discv5_port_ipv6: DEFAULT_DISCOVERY_V5_PORT,
702 discv5_lookup_interval: DEFAULT_SECONDS_LOOKUP_INTERVAL,
703 discv5_bootstrap_lookup_interval: DEFAULT_SECONDS_BOOTSTRAP_LOOKUP_INTERVAL,
704 discv5_bootstrap_lookup_countdown: DEFAULT_COUNT_BOOTSTRAP_LOOKUPS,
705 }
706 }
707}
708
709fn parse_block_num_hash(s: &str) -> Result<BlockNumHash, String> {
711 if let Some((num_str, hash_str)) = s.split_once('=') {
712 let number = num_str.parse().map_err(|_| format!("Invalid block number: {}", num_str))?;
713 let hash = B256::from_str(hash_str).map_err(|_| format!("Invalid hash: {}", hash_str))?;
714 Ok(BlockNumHash::new(number, hash))
715 } else {
716 let hash = B256::from_str(s).map_err(|_| format!("Invalid hash: {}", s))?;
718 Ok(BlockNumHash::new(0, hash))
719 }
720}
721
722#[cfg(test)]
723mod tests {
724 use super::*;
725 use clap::Parser;
726 use reth_chainspec::MAINNET;
727 use reth_config::Config;
728 use reth_network_peers::NodeRecord;
729 use secp256k1::SecretKey;
730 use std::{
731 fs,
732 time::{SystemTime, UNIX_EPOCH},
733 };
734
735 #[derive(Parser)]
737 struct CommandParser<T: Args> {
738 #[command(flatten)]
739 args: T,
740 }
741
742 #[test]
743 fn parse_nat_args() {
744 let args = CommandParser::<NetworkArgs>::parse_from(["reth", "--nat", "none"]).args;
745 assert_eq!(args.nat, NatResolver::None);
746
747 let args =
748 CommandParser::<NetworkArgs>::parse_from(["reth", "--nat", "extip:0.0.0.0"]).args;
749 assert_eq!(args.nat, NatResolver::ExternalIp("0.0.0.0".parse().unwrap()));
750 }
751
752 #[test]
753 fn parse_peer_args() {
754 let args =
755 CommandParser::<NetworkArgs>::parse_from(["reth", "--max-outbound-peers", "50"]).args;
756 assert_eq!(args.max_outbound_peers, Some(50));
757 assert_eq!(args.max_inbound_peers, None);
758
759 let args = CommandParser::<NetworkArgs>::parse_from([
760 "reth",
761 "--max-outbound-peers",
762 "75",
763 "--max-inbound-peers",
764 "15",
765 ])
766 .args;
767 assert_eq!(args.max_outbound_peers, Some(75));
768 assert_eq!(args.max_inbound_peers, Some(15));
769 }
770
771 #[test]
772 fn parse_trusted_peer_args() {
773 let args =
774 CommandParser::<NetworkArgs>::parse_from([
775 "reth",
776 "--trusted-peers",
777 "enode://d860a01f9722d78051619d1e2351aba3f43f943f6f00718d1b9baa4101932a1f5011f16bb2b1bb35db20d6fe28fa0bf09636d26a87d31de9ec6203eeedb1f666@18.138.108.67:30303,enode://22a8232c3abc76a16ae9d6c3b164f98775fe226f0917b0ca871128a74a8e9630b458460865bab457221f1d448dd9791d24c4e5d88786180ac185df813a68d4de@3.209.45.79:30303"
778 ])
779 .args;
780
781 assert_eq!(
782 args.trusted_peers,
783 vec![
784 "enode://d860a01f9722d78051619d1e2351aba3f43f943f6f00718d1b9baa4101932a1f5011f16bb2b1bb35db20d6fe28fa0bf09636d26a87d31de9ec6203eeedb1f666@18.138.108.67:30303".parse().unwrap(),
785 "enode://22a8232c3abc76a16ae9d6c3b164f98775fe226f0917b0ca871128a74a8e9630b458460865bab457221f1d448dd9791d24c4e5d88786180ac185df813a68d4de@3.209.45.79:30303".parse().unwrap()
786 ]
787 );
788 }
789
790 #[test]
791 fn parse_retry_strategy_args() {
792 let tests = vec![0, 10];
793
794 for retries in tests {
795 let retries_str = retries.to_string();
796 let args = CommandParser::<NetworkArgs>::parse_from([
797 "reth",
798 "--dns-retries",
799 retries_str.as_str(),
800 ])
801 .args;
802
803 assert_eq!(args.dns_retries, retries);
804 }
805 }
806
807 #[test]
808 fn parse_disable_tx_gossip_args() {
809 let args = CommandParser::<NetworkArgs>::parse_from(["reth", "--disable-tx-gossip"]).args;
810 assert!(args.disable_tx_gossip);
811 }
812
813 #[test]
814 fn parse_max_peers_flag() {
815 let args = CommandParser::<NetworkArgs>::parse_from(["reth", "--max-peers", "90"]).args;
816
817 assert_eq!(args.max_peers, Some(90));
818 assert_eq!(args.max_outbound_peers, None);
819 assert_eq!(args.max_inbound_peers, None);
820 assert_eq!(args.resolved_max_outbound_peers(), Some(30));
821 assert_eq!(args.resolved_max_inbound_peers(), Some(60));
822 }
823
824 #[test]
825 fn max_peers_conflicts_with_outbound() {
826 let result = CommandParser::<NetworkArgs>::try_parse_from([
827 "reth",
828 "--max-peers",
829 "90",
830 "--max-outbound-peers",
831 "50",
832 ]);
833 assert!(
834 result.is_err(),
835 "Should fail when both --max-peers and --max-outbound-peers are used"
836 );
837 }
838
839 #[test]
840 fn max_peers_conflicts_with_inbound() {
841 let result = CommandParser::<NetworkArgs>::try_parse_from([
842 "reth",
843 "--max-peers",
844 "90",
845 "--max-inbound-peers",
846 "30",
847 ]);
848 assert!(
849 result.is_err(),
850 "Should fail when both --max-peers and --max-inbound-peers are used"
851 );
852 }
853
854 #[test]
855 fn max_peers_split_calculation() {
856 let args = CommandParser::<NetworkArgs>::parse_from(["reth", "--max-peers", "90"]).args;
857
858 assert_eq!(args.max_peers, Some(90));
859 assert_eq!(args.resolved_max_outbound_peers(), Some(30));
860 assert_eq!(args.resolved_max_inbound_peers(), Some(60));
861 }
862
863 #[test]
864 fn max_peers_small_values() {
865 let args1 = CommandParser::<NetworkArgs>::parse_from(["reth", "--max-peers", "1"]).args;
866 assert_eq!(args1.resolved_max_outbound_peers(), Some(1));
867 assert_eq!(args1.resolved_max_inbound_peers(), Some(0));
868
869 let args2 = CommandParser::<NetworkArgs>::parse_from(["reth", "--max-peers", "2"]).args;
870 assert_eq!(args2.resolved_max_outbound_peers(), Some(1));
871 assert_eq!(args2.resolved_max_inbound_peers(), Some(1));
872
873 let args3 = CommandParser::<NetworkArgs>::parse_from(["reth", "--max-peers", "3"]).args;
874 assert_eq!(args3.resolved_max_outbound_peers(), Some(1));
875 assert_eq!(args3.resolved_max_inbound_peers(), Some(2));
876 }
877
878 #[test]
879 fn resolved_peers_without_max_peers() {
880 let args = CommandParser::<NetworkArgs>::parse_from([
881 "reth",
882 "--max-outbound-peers",
883 "75",
884 "--max-inbound-peers",
885 "15",
886 ])
887 .args;
888
889 assert_eq!(args.max_peers, None);
890 assert_eq!(args.resolved_max_outbound_peers(), Some(75));
891 assert_eq!(args.resolved_max_inbound_peers(), Some(15));
892 }
893
894 #[test]
895 fn resolved_peers_with_defaults() {
896 let args = CommandParser::<NetworkArgs>::parse_from(["reth"]).args;
897
898 assert_eq!(args.max_peers, None);
899 assert_eq!(args.resolved_max_outbound_peers(), None);
900 assert_eq!(args.resolved_max_inbound_peers(), None);
901 }
902
903 #[test]
904 fn network_args_default_sanity_test() {
905 let default_args = NetworkArgs::default();
906 let args = CommandParser::<NetworkArgs>::parse_from(["reth"]).args;
907
908 assert_eq!(args, default_args);
909 }
910
911 #[test]
912 fn parse_required_block_hashes() {
913 let args = CommandParser::<NetworkArgs>::parse_from([
914 "reth",
915 "--required-block-hashes",
916 "0x1111111111111111111111111111111111111111111111111111111111111111,23115201=0x2222222222222222222222222222222222222222222222222222222222222222",
917 ])
918 .args;
919
920 assert_eq!(args.required_block_hashes.len(), 2);
921 assert_eq!(args.required_block_hashes[0].number, 0);
923 assert_eq!(
924 args.required_block_hashes[0].hash.to_string(),
925 "0x1111111111111111111111111111111111111111111111111111111111111111"
926 );
927 assert_eq!(args.required_block_hashes[1].number, 23115201);
929 assert_eq!(
930 args.required_block_hashes[1].hash.to_string(),
931 "0x2222222222222222222222222222222222222222222222222222222222222222"
932 );
933 }
934
935 #[test]
936 fn parse_empty_required_block_hashes() {
937 let args = CommandParser::<NetworkArgs>::parse_from(["reth"]).args;
938 assert!(args.required_block_hashes.is_empty());
939 }
940
941 #[test]
942 fn test_parse_block_num_hash() {
943 let result = parse_block_num_hash(
945 "0x1111111111111111111111111111111111111111111111111111111111111111",
946 );
947 assert!(result.is_ok());
948 assert_eq!(result.unwrap().number, 0);
949
950 let result = parse_block_num_hash(
952 "23115201=0x2222222222222222222222222222222222222222222222222222222222222222",
953 );
954 assert!(result.is_ok());
955 assert_eq!(result.unwrap().number, 23115201);
956
957 assert!(parse_block_num_hash("invalid").is_err());
959 assert!(parse_block_num_hash(
960 "abc=0x1111111111111111111111111111111111111111111111111111111111111111"
961 )
962 .is_err());
963 }
964
965 #[test]
966 fn parse_p2p_secret_key_hex() {
967 let hex = "4c0883a69102937d6231471b5dbb6204fe512961708279f8c5c58b3b9c4e8b8f";
968 let args =
969 CommandParser::<NetworkArgs>::parse_from(["reth", "--p2p-secret-key-hex", hex]).args;
970
971 let expected: B256 = hex.parse().unwrap();
972 assert_eq!(args.p2p_secret_key_hex, Some(expected));
973 assert_eq!(args.p2p_secret_key, None);
974 }
975
976 #[test]
977 fn parse_p2p_secret_key_hex_with_0x_prefix() {
978 let hex = "0x4c0883a69102937d6231471b5dbb6204fe512961708279f8c5c58b3b9c4e8b8f";
979 let args =
980 CommandParser::<NetworkArgs>::parse_from(["reth", "--p2p-secret-key-hex", hex]).args;
981
982 let expected: B256 = hex.parse().unwrap();
983 assert_eq!(args.p2p_secret_key_hex, Some(expected));
984 assert_eq!(args.p2p_secret_key, None);
985 }
986
987 #[test]
988 fn test_p2p_secret_key_and_hex_are_mutually_exclusive() {
989 let result = CommandParser::<NetworkArgs>::try_parse_from([
990 "reth",
991 "--p2p-secret-key",
992 "/path/to/key",
993 "--p2p-secret-key-hex",
994 "4c0883a69102937d6231471b5dbb6204fe512961708279f8c5c58b3b9c4e8b8f",
995 ]);
996
997 assert!(result.is_err());
998 }
999
1000 #[test]
1001 fn test_secret_key_method_with_hex() {
1002 let hex = "4c0883a69102937d6231471b5dbb6204fe512961708279f8c5c58b3b9c4e8b8f";
1003 let args =
1004 CommandParser::<NetworkArgs>::parse_from(["reth", "--p2p-secret-key-hex", hex]).args;
1005
1006 let temp_dir = std::env::temp_dir();
1007 let default_path = temp_dir.join("default_key");
1008 let secret_key = args.secret_key(default_path).unwrap();
1009
1010 assert_eq!(alloy_primitives::hex::encode(secret_key.secret_bytes()), hex);
1012 }
1013
1014 #[test]
1015 fn parse_netrestrict_single_network() {
1016 let args =
1017 CommandParser::<NetworkArgs>::parse_from(["reth", "--netrestrict", "192.168.0.0/16"])
1018 .args;
1019
1020 assert_eq!(args.netrestrict, Some("192.168.0.0/16".to_string()));
1021
1022 let ip_filter = args.ip_filter().unwrap();
1023 assert!(ip_filter.has_restrictions());
1024 assert!(ip_filter.is_allowed(&"192.168.1.1".parse().unwrap()));
1025 assert!(!ip_filter.is_allowed(&"10.0.0.1".parse().unwrap()));
1026 }
1027
1028 #[test]
1029 fn parse_netrestrict_multiple_networks() {
1030 let args = CommandParser::<NetworkArgs>::parse_from([
1031 "reth",
1032 "--netrestrict",
1033 "192.168.0.0/16,10.0.0.0/8",
1034 ])
1035 .args;
1036
1037 assert_eq!(args.netrestrict, Some("192.168.0.0/16,10.0.0.0/8".to_string()));
1038
1039 let ip_filter = args.ip_filter().unwrap();
1040 assert!(ip_filter.has_restrictions());
1041 assert!(ip_filter.is_allowed(&"192.168.1.1".parse().unwrap()));
1042 assert!(ip_filter.is_allowed(&"10.5.10.20".parse().unwrap()));
1043 assert!(!ip_filter.is_allowed(&"172.16.0.1".parse().unwrap()));
1044 }
1045
1046 #[test]
1047 fn parse_netrestrict_ipv6() {
1048 let args =
1049 CommandParser::<NetworkArgs>::parse_from(["reth", "--netrestrict", "2001:db8::/32"])
1050 .args;
1051
1052 let ip_filter = args.ip_filter().unwrap();
1053 assert!(ip_filter.has_restrictions());
1054 assert!(ip_filter.is_allowed(&"2001:db8::1".parse().unwrap()));
1055 assert!(!ip_filter.is_allowed(&"2001:db9::1".parse().unwrap()));
1056 }
1057
1058 #[test]
1059 fn netrestrict_not_set() {
1060 let args = CommandParser::<NetworkArgs>::parse_from(["reth"]).args;
1061 assert_eq!(args.netrestrict, None);
1062
1063 let ip_filter = args.ip_filter().unwrap();
1064 assert!(!ip_filter.has_restrictions());
1065 assert!(ip_filter.is_allowed(&"192.168.1.1".parse().unwrap()));
1066 assert!(ip_filter.is_allowed(&"10.0.0.1".parse().unwrap()));
1067 }
1068
1069 #[test]
1070 fn netrestrict_invalid_cidr() {
1071 let args =
1072 CommandParser::<NetworkArgs>::parse_from(["reth", "--netrestrict", "invalid-cidr"])
1073 .args;
1074
1075 assert!(args.ip_filter().is_err());
1076 }
1077
1078 #[test]
1079 fn network_config_preserves_basic_nodes_from_peers_file() {
1080 let enode = "enode://6f8a80d14311c39f35f516fa664deaaaa13e85b2f7493f37f6144d86991ec012937307647bd3b9a82abe2974e1407241d54947bbb39763a4cac9f77166ad92a0@10.3.58.6:30303?discport=30301";
1081 let unique = SystemTime::now().duration_since(UNIX_EPOCH).unwrap().as_nanos();
1082
1083 let peers_file = std::env::temp_dir().join(format!("reth_peers_test_{}.json", unique));
1084 fs::write(&peers_file, format!("[\"{}\"]", enode)).expect("write peers file");
1085
1086 let args = NetworkArgs {
1088 peers_file: Some(peers_file.clone()),
1089 no_persist_peers: false,
1090 ..Default::default()
1091 };
1092
1093 let secret_key = SecretKey::from_byte_array(&[1u8; 32]).unwrap();
1095 let builder = args.network_config::<reth_network::EthNetworkPrimitives>(
1096 &Config::default(),
1097 MAINNET.clone(),
1098 secret_key,
1099 peers_file.clone(),
1100 );
1101
1102 let net_cfg = builder.build_with_noop_provider(MAINNET.clone());
1103
1104 let node: NodeRecord = enode.parse().unwrap();
1106 assert!(net_cfg.peers_config.basic_nodes.contains(&node));
1107
1108 let _ = fs::remove_file(&peers_file);
1110 }
1111}