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, SessionsConfig,
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(long = "max-tx-reqs", value_name = "COUNT", default_value_t = DEFAULT_MAX_COUNT_CONCURRENT_REQUESTS, verbatim_doc_comment)]
124 pub max_concurrent_tx_requests: u32,
125
126 #[arg(long = "max-tx-reqs-peer", value_name = "COUNT", default_value_t = DEFAULT_MAX_COUNT_CONCURRENT_REQUESTS_PER_PEER, verbatim_doc_comment)]
128 pub max_concurrent_tx_requests_per_peer: u8,
129
130 #[arg(long = "max-seen-tx-history", value_name = "COUNT", default_value_t = DEFAULT_MAX_COUNT_TRANSACTIONS_SEEN_BY_PEER, verbatim_doc_comment)]
134 pub max_seen_tx_history: u32,
135
136 #[arg(long = "max-pending-imports", value_name = "COUNT", default_value_t = DEFAULT_MAX_COUNT_PENDING_POOL_IMPORTS, verbatim_doc_comment)]
137 pub max_pending_pool_imports: usize,
139
140 #[arg(long = "pooled-tx-response-soft-limit", value_name = "BYTES", default_value_t = SOFT_LIMIT_BYTE_SIZE_POOLED_TRANSACTIONS_RESPONSE, verbatim_doc_comment)]
144 pub soft_limit_byte_size_pooled_transactions_response: usize,
145
146 #[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)]
158 pub soft_limit_byte_size_pooled_transactions_response_on_pack_request: usize,
159
160 #[arg(long = "max-tx-pending-fetch", value_name = "COUNT", default_value_t = DEFAULT_MAX_CAPACITY_CACHE_PENDING_FETCH, verbatim_doc_comment)]
162 pub max_capacity_cache_txns_pending_fetch: u32,
163
164 #[arg(long = "net-if.experimental", conflicts_with = "addr", value_name = "IF_NAME")]
168 pub net_if: Option<String>,
169
170 #[arg(long = "tx-propagation-policy", default_value_t = TransactionPropagationKind::All)]
174 pub tx_propagation_policy: TransactionPropagationKind,
175
176 #[arg(long = "tx-ingress-policy", default_value_t = TransactionIngressPolicy::All)]
180 pub tx_ingress_policy: TransactionIngressPolicy,
181
182 #[arg(long = "disable-tx-gossip")]
187 pub disable_tx_gossip: bool,
188
189 #[arg(
194 long = "tx-propagation-mode",
195 default_value = "sqrt",
196 help = "Transaction propagation mode (sqrt, all, max:<number>)"
197 )]
198 pub propagation_mode: TransactionPropagationMode,
199
200 #[arg(long = "required-block-hashes", value_delimiter = ',', value_parser = parse_block_num_hash)]
204 pub required_block_hashes: Vec<BlockNumHash>,
205
206 #[arg(long)]
208 pub network_id: Option<u64>,
209
210 #[arg(long, value_name = "NETRESTRICT")]
217 pub netrestrict: Option<String>,
218}
219
220impl NetworkArgs {
221 pub fn resolved_addr(&self) -> IpAddr {
223 if let Some(ref if_name) = self.net_if {
224 let if_name = if if_name.is_empty() { DEFAULT_NET_IF_NAME } else { if_name };
225 return match reth_net_nat::net_if::resolve_net_if_ip(if_name) {
226 Ok(addr) => addr,
227 Err(err) => {
228 error!(target: "reth::cli",
229 if_name,
230 %err,
231 "Failed to read network interface IP"
232 );
233
234 DEFAULT_DISCOVERY_ADDR
235 }
236 };
237 }
238
239 self.addr
240 }
241
242 pub fn resolved_bootnodes(&self) -> Option<Vec<NodeRecord>> {
244 self.bootnodes.clone().map(|bootnodes| {
245 bootnodes.into_iter().filter_map(|node| node.resolve_blocking().ok()).collect()
246 })
247 }
248 pub const fn transactions_manager_config(&self) -> TransactionsManagerConfig {
250 TransactionsManagerConfig {
251 transaction_fetcher_config: TransactionFetcherConfig::new(
252 self.max_concurrent_tx_requests,
253 self.max_concurrent_tx_requests_per_peer,
254 self.soft_limit_byte_size_pooled_transactions_response,
255 self.soft_limit_byte_size_pooled_transactions_response_on_pack_request,
256 self.max_capacity_cache_txns_pending_fetch,
257 ),
258 max_transactions_seen_by_peer_history: self.max_seen_tx_history,
259 propagation_mode: self.propagation_mode,
260 ingress_policy: self.tx_ingress_policy,
261 }
262 }
263
264 pub fn network_config<N: NetworkPrimitives>(
276 &self,
277 config: &Config,
278 chain_spec: impl EthChainSpec,
279 secret_key: SecretKey,
280 default_peers_file: PathBuf,
281 ) -> NetworkConfigBuilder<N> {
282 let addr = self.resolved_addr();
283 let chain_bootnodes = self
284 .resolved_bootnodes()
285 .unwrap_or_else(|| chain_spec.bootnodes().unwrap_or_else(mainnet_nodes));
286 let peers_file = self.peers_file.clone().unwrap_or(default_peers_file);
287
288 let ip_filter = self.ip_filter().unwrap_or_default();
290 let peers_config = config
291 .peers
292 .clone()
293 .with_max_inbound_opt(self.max_inbound_peers)
294 .with_max_outbound_opt(self.max_outbound_peers)
295 .with_ip_filter(ip_filter);
296
297 NetworkConfigBuilder::<N>::new(secret_key)
299 .peer_config(config.peers_config_with_basic_nodes_from_file(
300 self.persistent_peers_file(peers_file).as_deref(),
301 ))
302 .external_ip_resolver(self.nat)
303 .sessions_config(
304 SessionsConfig::default().with_upscaled_event_buffer(peers_config.max_peers()),
305 )
306 .peer_config(peers_config)
307 .boot_nodes(chain_bootnodes.clone())
308 .transactions_manager_config(self.transactions_manager_config())
309 .apply(|builder| {
311 let peer_id = builder.get_peer_id();
312 builder.hello_message(
313 HelloMessageWithProtocols::builder(peer_id)
314 .client_version(&self.identity)
315 .build(),
316 )
317 })
318 .apply(|builder| {
320 let rlpx_socket = (addr, self.port).into();
321 self.discovery.apply_to_builder(builder, rlpx_socket, chain_bootnodes)
322 })
323 .listener_addr(SocketAddr::new(
324 addr, self.port,
326 ))
327 .discovery_addr(SocketAddr::new(
328 self.discovery.addr,
329 self.discovery.port,
331 ))
332 .disable_tx_gossip(self.disable_tx_gossip)
333 .required_block_hashes(self.required_block_hashes.clone())
334 .network_id(self.network_id)
335 }
336
337 pub fn persistent_peers_file(&self, peers_file: PathBuf) -> Option<PathBuf> {
339 self.no_persist_peers.not().then_some(peers_file)
340 }
341
342 pub const fn with_discovery(mut self, discovery: DiscoveryArgs) -> Self {
344 self.discovery = discovery;
345 self
346 }
347
348 pub const fn with_unused_p2p_port(mut self) -> Self {
351 self.port = 0;
352 self
353 }
354
355 pub const fn with_unused_ports(mut self) -> Self {
358 self = self.with_unused_p2p_port();
359 self.discovery = self.discovery.with_unused_discovery_port();
360 self
361 }
362
363 pub const fn with_nat_resolver(mut self, nat: NatResolver) -> Self {
365 self.nat = nat;
366 self
367 }
368
369 pub fn adjust_instance_ports(&mut self, instance: Option<u16>) {
375 if let Some(instance) = instance {
376 debug_assert_ne!(instance, 0, "instance must be non-zero");
377 self.port += instance - 1;
378 self.discovery.adjust_instance_ports(instance);
379 }
380 }
381
382 pub async fn resolve_trusted_peers(&self) -> Result<Vec<NodeRecord>, std::io::Error> {
384 futures::future::try_join_all(
385 self.trusted_peers.iter().map(|peer| async move { peer.resolve().await }),
386 )
387 .await
388 }
389
390 pub fn secret_key(
396 &self,
397 default_secret_key_path: PathBuf,
398 ) -> Result<SecretKey, SecretKeyError> {
399 if let Some(b256) = &self.p2p_secret_key_hex {
400 SecretKey::from_slice(b256.as_slice()).map_err(SecretKeyError::SecretKeyDecodeError)
402 } else {
403 let secret_key_path = self.p2p_secret_key.clone().unwrap_or(default_secret_key_path);
405 get_secret_key(&secret_key_path)
406 }
407 }
408
409 pub fn ip_filter(&self) -> Result<IpFilter, ipnet::AddrParseError> {
413 if let Some(netrestrict) = &self.netrestrict {
414 IpFilter::from_cidr_string(netrestrict)
415 } else {
416 Ok(IpFilter::allow_all())
417 }
418 }
419}
420
421impl Default for NetworkArgs {
422 fn default() -> Self {
423 Self {
424 discovery: DiscoveryArgs::default(),
425 trusted_peers: vec![],
426 trusted_only: false,
427 bootnodes: None,
428 dns_retries: 0,
429 peers_file: None,
430 identity: version_metadata().p2p_client_version.to_string(),
431 p2p_secret_key: None,
432 p2p_secret_key_hex: None,
433 no_persist_peers: false,
434 nat: NatResolver::Any,
435 addr: DEFAULT_DISCOVERY_ADDR,
436 port: DEFAULT_DISCOVERY_PORT,
437 max_outbound_peers: None,
438 max_inbound_peers: None,
439 max_concurrent_tx_requests: DEFAULT_MAX_COUNT_CONCURRENT_REQUESTS,
440 max_concurrent_tx_requests_per_peer: DEFAULT_MAX_COUNT_CONCURRENT_REQUESTS_PER_PEER,
441 soft_limit_byte_size_pooled_transactions_response:
442 SOFT_LIMIT_BYTE_SIZE_POOLED_TRANSACTIONS_RESPONSE,
443 soft_limit_byte_size_pooled_transactions_response_on_pack_request: DEFAULT_SOFT_LIMIT_BYTE_SIZE_POOLED_TRANSACTIONS_RESP_ON_PACK_GET_POOLED_TRANSACTIONS_REQ,
444 max_pending_pool_imports: DEFAULT_MAX_COUNT_PENDING_POOL_IMPORTS,
445 max_seen_tx_history: DEFAULT_MAX_COUNT_TRANSACTIONS_SEEN_BY_PEER,
446 max_capacity_cache_txns_pending_fetch: DEFAULT_MAX_CAPACITY_CACHE_PENDING_FETCH,
447 net_if: None,
448 tx_propagation_policy: TransactionPropagationKind::default(),
449 tx_ingress_policy: TransactionIngressPolicy::default(),
450 disable_tx_gossip: false,
451 propagation_mode: TransactionPropagationMode::Sqrt,
452 required_block_hashes: vec![],
453 network_id: None,
454 netrestrict: None,
455 }
456 }
457}
458
459#[derive(Debug, Clone, Args, PartialEq, Eq)]
461pub struct DiscoveryArgs {
462 #[arg(short, long, default_value_if("dev", "true", "true"))]
464 pub disable_discovery: bool,
465
466 #[arg(long, conflicts_with = "disable_discovery")]
468 pub disable_dns_discovery: bool,
469
470 #[arg(long, conflicts_with = "disable_discovery")]
472 pub disable_discv4_discovery: bool,
473
474 #[arg(long, conflicts_with = "disable_discovery")]
476 pub enable_discv5_discovery: bool,
477
478 #[arg(long, conflicts_with = "disable_discovery")]
480 pub disable_nat: bool,
481
482 #[arg(id = "discovery.addr", long = "discovery.addr", value_name = "DISCOVERY_ADDR", default_value_t = DEFAULT_DISCOVERY_ADDR)]
484 pub addr: IpAddr,
485
486 #[arg(id = "discovery.port", long = "discovery.port", value_name = "DISCOVERY_PORT", default_value_t = DEFAULT_DISCOVERY_PORT)]
488 pub port: u16,
489
490 #[arg(id = "discovery.v5.addr", long = "discovery.v5.addr", value_name = "DISCOVERY_V5_ADDR", default_value = None)]
493 pub discv5_addr: Option<Ipv4Addr>,
494
495 #[arg(id = "discovery.v5.addr.ipv6", long = "discovery.v5.addr.ipv6", value_name = "DISCOVERY_V5_ADDR_IPV6", default_value = None)]
498 pub discv5_addr_ipv6: Option<Ipv6Addr>,
499
500 #[arg(id = "discovery.v5.port", long = "discovery.v5.port", value_name = "DISCOVERY_V5_PORT",
503 default_value_t = DEFAULT_DISCOVERY_V5_PORT)]
504 pub discv5_port: u16,
505
506 #[arg(id = "discovery.v5.port.ipv6", long = "discovery.v5.port.ipv6", value_name = "DISCOVERY_V5_PORT_IPV6",
509 default_value = None, default_value_t = DEFAULT_DISCOVERY_V5_PORT)]
510 pub discv5_port_ipv6: u16,
511
512 #[arg(id = "discovery.v5.lookup-interval", long = "discovery.v5.lookup-interval", value_name = "DISCOVERY_V5_LOOKUP_INTERVAL", default_value_t = DEFAULT_SECONDS_LOOKUP_INTERVAL)]
515 pub discv5_lookup_interval: u64,
516
517 #[arg(id = "discovery.v5.bootstrap.lookup-interval", long = "discovery.v5.bootstrap.lookup-interval", value_name = "DISCOVERY_V5_BOOTSTRAP_LOOKUP_INTERVAL",
520 default_value_t = DEFAULT_SECONDS_BOOTSTRAP_LOOKUP_INTERVAL)]
521 pub discv5_bootstrap_lookup_interval: u64,
522
523 #[arg(id = "discovery.v5.bootstrap.lookup-countdown", long = "discovery.v5.bootstrap.lookup-countdown", value_name = "DISCOVERY_V5_BOOTSTRAP_LOOKUP_COUNTDOWN",
525 default_value_t = DEFAULT_COUNT_BOOTSTRAP_LOOKUPS)]
526 pub discv5_bootstrap_lookup_countdown: u64,
527}
528
529impl DiscoveryArgs {
530 pub fn apply_to_builder<N>(
532 &self,
533 mut network_config_builder: NetworkConfigBuilder<N>,
534 rlpx_tcp_socket: SocketAddr,
535 boot_nodes: impl IntoIterator<Item = NodeRecord>,
536 ) -> NetworkConfigBuilder<N>
537 where
538 N: NetworkPrimitives,
539 {
540 if self.disable_discovery || self.disable_dns_discovery {
541 network_config_builder = network_config_builder.disable_dns_discovery();
542 }
543
544 if self.disable_discovery || self.disable_discv4_discovery {
545 network_config_builder = network_config_builder.disable_discv4_discovery();
546 }
547
548 if self.disable_nat {
549 network_config_builder = network_config_builder.disable_nat();
551 }
552
553 if self.should_enable_discv5() {
554 network_config_builder = network_config_builder
555 .discovery_v5(self.discovery_v5_builder(rlpx_tcp_socket, boot_nodes));
556 }
557
558 network_config_builder
559 }
560
561 pub fn discovery_v5_builder(
563 &self,
564 rlpx_tcp_socket: SocketAddr,
565 boot_nodes: impl IntoIterator<Item = NodeRecord>,
566 ) -> reth_discv5::ConfigBuilder {
567 let Self {
568 discv5_addr,
569 discv5_addr_ipv6,
570 discv5_port,
571 discv5_port_ipv6,
572 discv5_lookup_interval,
573 discv5_bootstrap_lookup_interval,
574 discv5_bootstrap_lookup_countdown,
575 ..
576 } = self;
577
578 let discv5_addr_ipv4 = discv5_addr.or(match rlpx_tcp_socket {
580 SocketAddr::V4(addr) => Some(*addr.ip()),
581 SocketAddr::V6(_) => None,
582 });
583 let discv5_addr_ipv6 = discv5_addr_ipv6.or(match rlpx_tcp_socket {
584 SocketAddr::V4(_) => None,
585 SocketAddr::V6(addr) => Some(*addr.ip()),
586 });
587
588 reth_discv5::Config::builder(rlpx_tcp_socket)
589 .discv5_config(
590 reth_discv5::discv5::ConfigBuilder::new(ListenConfig::from_two_sockets(
591 discv5_addr_ipv4.map(|addr| SocketAddrV4::new(addr, *discv5_port)),
592 discv5_addr_ipv6.map(|addr| SocketAddrV6::new(addr, *discv5_port_ipv6, 0, 0)),
593 ))
594 .build(),
595 )
596 .add_unsigned_boot_nodes(boot_nodes)
597 .lookup_interval(*discv5_lookup_interval)
598 .bootstrap_lookup_interval(*discv5_bootstrap_lookup_interval)
599 .bootstrap_lookup_countdown(*discv5_bootstrap_lookup_countdown)
600 }
601
602 const fn should_enable_discv5(&self) -> bool {
604 if self.disable_discovery {
605 return false;
606 }
607
608 self.enable_discv5_discovery ||
609 self.discv5_addr.is_some() ||
610 self.discv5_addr_ipv6.is_some()
611 }
612
613 pub const fn with_unused_discovery_port(mut self) -> Self {
616 self.port = 0;
617 self
618 }
619
620 pub const fn with_discv5_port(mut self, port: u16) -> Self {
622 self.discv5_port = port;
623 self
624 }
625
626 pub fn adjust_instance_ports(&mut self, instance: u16) {
632 debug_assert_ne!(instance, 0, "instance must be non-zero");
633 self.port += instance - 1;
634 self.discv5_port += instance - 1;
635 self.discv5_port_ipv6 += instance - 1;
636 }
637}
638
639impl Default for DiscoveryArgs {
640 fn default() -> Self {
641 Self {
642 disable_discovery: false,
643 disable_dns_discovery: false,
644 disable_discv4_discovery: false,
645 enable_discv5_discovery: false,
646 disable_nat: false,
647 addr: DEFAULT_DISCOVERY_ADDR,
648 port: DEFAULT_DISCOVERY_PORT,
649 discv5_addr: None,
650 discv5_addr_ipv6: None,
651 discv5_port: DEFAULT_DISCOVERY_V5_PORT,
652 discv5_port_ipv6: DEFAULT_DISCOVERY_V5_PORT,
653 discv5_lookup_interval: DEFAULT_SECONDS_LOOKUP_INTERVAL,
654 discv5_bootstrap_lookup_interval: DEFAULT_SECONDS_BOOTSTRAP_LOOKUP_INTERVAL,
655 discv5_bootstrap_lookup_countdown: DEFAULT_COUNT_BOOTSTRAP_LOOKUPS,
656 }
657 }
658}
659
660fn parse_block_num_hash(s: &str) -> Result<BlockNumHash, String> {
662 if let Some((num_str, hash_str)) = s.split_once('=') {
663 let number = num_str.parse().map_err(|_| format!("Invalid block number: {}", num_str))?;
664 let hash = B256::from_str(hash_str).map_err(|_| format!("Invalid hash: {}", hash_str))?;
665 Ok(BlockNumHash::new(number, hash))
666 } else {
667 let hash = B256::from_str(s).map_err(|_| format!("Invalid hash: {}", s))?;
669 Ok(BlockNumHash::new(0, hash))
670 }
671}
672
673#[cfg(test)]
674mod tests {
675 use super::*;
676 use clap::Parser;
677 #[derive(Parser)]
679 struct CommandParser<T: Args> {
680 #[command(flatten)]
681 args: T,
682 }
683
684 #[test]
685 fn parse_nat_args() {
686 let args = CommandParser::<NetworkArgs>::parse_from(["reth", "--nat", "none"]).args;
687 assert_eq!(args.nat, NatResolver::None);
688
689 let args =
690 CommandParser::<NetworkArgs>::parse_from(["reth", "--nat", "extip:0.0.0.0"]).args;
691 assert_eq!(args.nat, NatResolver::ExternalIp("0.0.0.0".parse().unwrap()));
692 }
693
694 #[test]
695 fn parse_peer_args() {
696 let args =
697 CommandParser::<NetworkArgs>::parse_from(["reth", "--max-outbound-peers", "50"]).args;
698 assert_eq!(args.max_outbound_peers, Some(50));
699 assert_eq!(args.max_inbound_peers, None);
700
701 let args = CommandParser::<NetworkArgs>::parse_from([
702 "reth",
703 "--max-outbound-peers",
704 "75",
705 "--max-inbound-peers",
706 "15",
707 ])
708 .args;
709 assert_eq!(args.max_outbound_peers, Some(75));
710 assert_eq!(args.max_inbound_peers, Some(15));
711 }
712
713 #[test]
714 fn parse_trusted_peer_args() {
715 let args =
716 CommandParser::<NetworkArgs>::parse_from([
717 "reth",
718 "--trusted-peers",
719 "enode://d860a01f9722d78051619d1e2351aba3f43f943f6f00718d1b9baa4101932a1f5011f16bb2b1bb35db20d6fe28fa0bf09636d26a87d31de9ec6203eeedb1f666@18.138.108.67:30303,enode://22a8232c3abc76a16ae9d6c3b164f98775fe226f0917b0ca871128a74a8e9630b458460865bab457221f1d448dd9791d24c4e5d88786180ac185df813a68d4de@3.209.45.79:30303"
720 ])
721 .args;
722
723 assert_eq!(
724 args.trusted_peers,
725 vec![
726 "enode://d860a01f9722d78051619d1e2351aba3f43f943f6f00718d1b9baa4101932a1f5011f16bb2b1bb35db20d6fe28fa0bf09636d26a87d31de9ec6203eeedb1f666@18.138.108.67:30303".parse().unwrap(),
727 "enode://22a8232c3abc76a16ae9d6c3b164f98775fe226f0917b0ca871128a74a8e9630b458460865bab457221f1d448dd9791d24c4e5d88786180ac185df813a68d4de@3.209.45.79:30303".parse().unwrap()
728 ]
729 );
730 }
731
732 #[test]
733 fn parse_retry_strategy_args() {
734 let tests = vec![0, 10];
735
736 for retries in tests {
737 let args = CommandParser::<NetworkArgs>::parse_from([
738 "reth",
739 "--dns-retries",
740 retries.to_string().as_str(),
741 ])
742 .args;
743
744 assert_eq!(args.dns_retries, retries);
745 }
746 }
747
748 #[test]
749 fn parse_disable_tx_gossip_args() {
750 let args = CommandParser::<NetworkArgs>::parse_from(["reth", "--disable-tx-gossip"]).args;
751 assert!(args.disable_tx_gossip);
752 }
753
754 #[test]
755 fn network_args_default_sanity_test() {
756 let default_args = NetworkArgs::default();
757 let args = CommandParser::<NetworkArgs>::parse_from(["reth"]).args;
758
759 assert_eq!(args, default_args);
760 }
761
762 #[test]
763 fn parse_required_block_hashes() {
764 let args = CommandParser::<NetworkArgs>::parse_from([
765 "reth",
766 "--required-block-hashes",
767 "0x1111111111111111111111111111111111111111111111111111111111111111,23115201=0x2222222222222222222222222222222222222222222222222222222222222222",
768 ])
769 .args;
770
771 assert_eq!(args.required_block_hashes.len(), 2);
772 assert_eq!(args.required_block_hashes[0].number, 0);
774 assert_eq!(
775 args.required_block_hashes[0].hash.to_string(),
776 "0x1111111111111111111111111111111111111111111111111111111111111111"
777 );
778 assert_eq!(args.required_block_hashes[1].number, 23115201);
780 assert_eq!(
781 args.required_block_hashes[1].hash.to_string(),
782 "0x2222222222222222222222222222222222222222222222222222222222222222"
783 );
784 }
785
786 #[test]
787 fn parse_empty_required_block_hashes() {
788 let args = CommandParser::<NetworkArgs>::parse_from(["reth"]).args;
789 assert!(args.required_block_hashes.is_empty());
790 }
791
792 #[test]
793 fn test_parse_block_num_hash() {
794 let result = parse_block_num_hash(
796 "0x1111111111111111111111111111111111111111111111111111111111111111",
797 );
798 assert!(result.is_ok());
799 assert_eq!(result.unwrap().number, 0);
800
801 let result = parse_block_num_hash(
803 "23115201=0x2222222222222222222222222222222222222222222222222222222222222222",
804 );
805 assert!(result.is_ok());
806 assert_eq!(result.unwrap().number, 23115201);
807
808 assert!(parse_block_num_hash("invalid").is_err());
810 assert!(parse_block_num_hash(
811 "abc=0x1111111111111111111111111111111111111111111111111111111111111111"
812 )
813 .is_err());
814 }
815
816 #[test]
817 fn parse_p2p_secret_key_hex() {
818 let hex = "4c0883a69102937d6231471b5dbb6204fe512961708279f8c5c58b3b9c4e8b8f";
819 let args =
820 CommandParser::<NetworkArgs>::parse_from(["reth", "--p2p-secret-key-hex", hex]).args;
821
822 let expected: B256 = hex.parse().unwrap();
823 assert_eq!(args.p2p_secret_key_hex, Some(expected));
824 assert_eq!(args.p2p_secret_key, None);
825 }
826
827 #[test]
828 fn parse_p2p_secret_key_hex_with_0x_prefix() {
829 let hex = "0x4c0883a69102937d6231471b5dbb6204fe512961708279f8c5c58b3b9c4e8b8f";
830 let args =
831 CommandParser::<NetworkArgs>::parse_from(["reth", "--p2p-secret-key-hex", hex]).args;
832
833 let expected: B256 = hex.parse().unwrap();
834 assert_eq!(args.p2p_secret_key_hex, Some(expected));
835 assert_eq!(args.p2p_secret_key, None);
836 }
837
838 #[test]
839 fn test_p2p_secret_key_and_hex_are_mutually_exclusive() {
840 let result = CommandParser::<NetworkArgs>::try_parse_from([
841 "reth",
842 "--p2p-secret-key",
843 "/path/to/key",
844 "--p2p-secret-key-hex",
845 "4c0883a69102937d6231471b5dbb6204fe512961708279f8c5c58b3b9c4e8b8f",
846 ]);
847
848 assert!(result.is_err());
849 }
850
851 #[test]
852 fn test_secret_key_method_with_hex() {
853 let hex = "4c0883a69102937d6231471b5dbb6204fe512961708279f8c5c58b3b9c4e8b8f";
854 let args =
855 CommandParser::<NetworkArgs>::parse_from(["reth", "--p2p-secret-key-hex", hex]).args;
856
857 let temp_dir = std::env::temp_dir();
858 let default_path = temp_dir.join("default_key");
859 let secret_key = args.secret_key(default_path).unwrap();
860
861 assert_eq!(alloy_primitives::hex::encode(secret_key.secret_bytes()), hex);
863 }
864
865 #[test]
866 fn parse_netrestrict_single_network() {
867 let args =
868 CommandParser::<NetworkArgs>::parse_from(["reth", "--netrestrict", "192.168.0.0/16"])
869 .args;
870
871 assert_eq!(args.netrestrict, Some("192.168.0.0/16".to_string()));
872
873 let ip_filter = args.ip_filter().unwrap();
874 assert!(ip_filter.has_restrictions());
875 assert!(ip_filter.is_allowed(&"192.168.1.1".parse().unwrap()));
876 assert!(!ip_filter.is_allowed(&"10.0.0.1".parse().unwrap()));
877 }
878
879 #[test]
880 fn parse_netrestrict_multiple_networks() {
881 let args = CommandParser::<NetworkArgs>::parse_from([
882 "reth",
883 "--netrestrict",
884 "192.168.0.0/16,10.0.0.0/8",
885 ])
886 .args;
887
888 assert_eq!(args.netrestrict, Some("192.168.0.0/16,10.0.0.0/8".to_string()));
889
890 let ip_filter = args.ip_filter().unwrap();
891 assert!(ip_filter.has_restrictions());
892 assert!(ip_filter.is_allowed(&"192.168.1.1".parse().unwrap()));
893 assert!(ip_filter.is_allowed(&"10.5.10.20".parse().unwrap()));
894 assert!(!ip_filter.is_allowed(&"172.16.0.1".parse().unwrap()));
895 }
896
897 #[test]
898 fn parse_netrestrict_ipv6() {
899 let args =
900 CommandParser::<NetworkArgs>::parse_from(["reth", "--netrestrict", "2001:db8::/32"])
901 .args;
902
903 let ip_filter = args.ip_filter().unwrap();
904 assert!(ip_filter.has_restrictions());
905 assert!(ip_filter.is_allowed(&"2001:db8::1".parse().unwrap()));
906 assert!(!ip_filter.is_allowed(&"2001:db9::1".parse().unwrap()));
907 }
908
909 #[test]
910 fn netrestrict_not_set() {
911 let args = CommandParser::<NetworkArgs>::parse_from(["reth"]).args;
912 assert_eq!(args.netrestrict, None);
913
914 let ip_filter = args.ip_filter().unwrap();
915 assert!(!ip_filter.has_restrictions());
916 assert!(ip_filter.is_allowed(&"192.168.1.1".parse().unwrap()));
917 assert!(ip_filter.is_allowed(&"10.0.0.1".parse().unwrap()));
918 }
919
920 #[test]
921 fn netrestrict_invalid_cidr() {
922 let args =
923 CommandParser::<NetworkArgs>::parse_from(["reth", "--netrestrict", "invalid-cidr"])
924 .args;
925
926 assert!(args.ip_filter().is_err());
927 }
928}