1use alloy_primitives::B256;
4use std::{
5 net::{IpAddr, Ipv4Addr, Ipv6Addr, SocketAddr, SocketAddrV4, SocketAddrV6},
6 ops::Not,
7 path::PathBuf,
8};
9
10use crate::version::version_metadata;
11use clap::Args;
12use reth_chainspec::EthChainSpec;
13use reth_config::Config;
14use reth_discv4::{NodeRecord, DEFAULT_DISCOVERY_ADDR, DEFAULT_DISCOVERY_PORT};
15use reth_discv5::{
16 discv5::ListenConfig, DEFAULT_COUNT_BOOTSTRAP_LOOKUPS, DEFAULT_DISCOVERY_V5_PORT,
17 DEFAULT_SECONDS_BOOTSTRAP_LOOKUP_INTERVAL, DEFAULT_SECONDS_LOOKUP_INTERVAL,
18};
19use reth_net_nat::{NatResolver, DEFAULT_NET_IF_NAME};
20use reth_network::{
21 transactions::{
22 config::TransactionPropagationKind,
23 constants::{
24 tx_fetcher::{
25 DEFAULT_MAX_CAPACITY_CACHE_PENDING_FETCH, DEFAULT_MAX_COUNT_CONCURRENT_REQUESTS,
26 DEFAULT_MAX_COUNT_CONCURRENT_REQUESTS_PER_PEER,
27 },
28 tx_manager::{
29 DEFAULT_MAX_COUNT_PENDING_POOL_IMPORTS, DEFAULT_MAX_COUNT_TRANSACTIONS_SEEN_BY_PEER,
30 },
31 },
32 TransactionFetcherConfig, TransactionPropagationMode, TransactionsManagerConfig,
33 DEFAULT_SOFT_LIMIT_BYTE_SIZE_POOLED_TRANSACTIONS_RESP_ON_PACK_GET_POOLED_TRANSACTIONS_REQ,
34 SOFT_LIMIT_BYTE_SIZE_POOLED_TRANSACTIONS_RESPONSE,
35 },
36 HelloMessageWithProtocols, NetworkConfigBuilder, NetworkPrimitives, SessionsConfig,
37};
38use reth_network_peers::{mainnet_nodes, TrustedPeer};
39use secp256k1::SecretKey;
40use tracing::error;
41
42#[derive(Debug, Clone, Args, PartialEq, Eq)]
44#[command(next_help_heading = "Networking")]
45pub struct NetworkArgs {
46 #[command(flatten)]
48 pub discovery: DiscoveryArgs,
49
50 #[expect(clippy::doc_markdown)]
51 #[arg(long, value_delimiter = ',')]
55 pub trusted_peers: Vec<TrustedPeer>,
56
57 #[arg(long)]
59 pub trusted_only: bool,
60
61 #[arg(long, value_delimiter = ',')]
65 pub bootnodes: Option<Vec<TrustedPeer>>,
66
67 #[arg(long, default_value_t = 0)]
69 pub dns_retries: usize,
70
71 #[arg(long, value_name = "FILE", verbatim_doc_comment, conflicts_with = "no_persist_peers")]
74 pub peers_file: Option<PathBuf>,
75
76 #[arg(long, value_name = "IDENTITY", default_value = version_metadata().p2p_client_version.as_ref())]
78 pub identity: String,
79
80 #[arg(long, value_name = "PATH")]
85 pub p2p_secret_key: Option<PathBuf>,
86
87 #[arg(long, verbatim_doc_comment)]
89 pub no_persist_peers: bool,
90
91 #[arg(long, default_value = "any")]
93 pub nat: NatResolver,
94
95 #[arg(long = "addr", value_name = "ADDR", default_value_t = DEFAULT_DISCOVERY_ADDR)]
97 pub addr: IpAddr,
98
99 #[arg(long = "port", value_name = "PORT", default_value_t = DEFAULT_DISCOVERY_PORT)]
101 pub port: u16,
102
103 #[arg(long)]
105 pub max_outbound_peers: Option<usize>,
106
107 #[arg(long)]
109 pub max_inbound_peers: Option<usize>,
110
111 #[arg(long = "max-tx-reqs", value_name = "COUNT", default_value_t = DEFAULT_MAX_COUNT_CONCURRENT_REQUESTS, verbatim_doc_comment)]
113 pub max_concurrent_tx_requests: u32,
114
115 #[arg(long = "max-tx-reqs-peer", value_name = "COUNT", default_value_t = DEFAULT_MAX_COUNT_CONCURRENT_REQUESTS_PER_PEER, verbatim_doc_comment)]
117 pub max_concurrent_tx_requests_per_peer: u8,
118
119 #[arg(long = "max-seen-tx-history", value_name = "COUNT", default_value_t = DEFAULT_MAX_COUNT_TRANSACTIONS_SEEN_BY_PEER, verbatim_doc_comment)]
123 pub max_seen_tx_history: u32,
124
125 #[arg(long = "max-pending-imports", value_name = "COUNT", default_value_t = DEFAULT_MAX_COUNT_PENDING_POOL_IMPORTS, verbatim_doc_comment)]
126 pub max_pending_pool_imports: usize,
128
129 #[arg(long = "pooled-tx-response-soft-limit", value_name = "BYTES", default_value_t = SOFT_LIMIT_BYTE_SIZE_POOLED_TRANSACTIONS_RESPONSE, verbatim_doc_comment)]
133 pub soft_limit_byte_size_pooled_transactions_response: usize,
134
135 #[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)]
147 pub soft_limit_byte_size_pooled_transactions_response_on_pack_request: usize,
148
149 #[arg(long = "max-tx-pending-fetch", value_name = "COUNT", default_value_t = DEFAULT_MAX_CAPACITY_CACHE_PENDING_FETCH, verbatim_doc_comment)]
151 pub max_capacity_cache_txns_pending_fetch: u32,
152
153 #[arg(long = "net-if.experimental", conflicts_with = "addr", value_name = "IF_NAME")]
157 pub net_if: Option<String>,
158
159 #[arg(long = "tx-propagation-policy", default_value_t = TransactionPropagationKind::All)]
163 pub tx_propagation_policy: TransactionPropagationKind,
164
165 #[arg(long = "disable-tx-gossip")]
170 pub disable_tx_gossip: bool,
171
172 #[arg(
177 long = "tx-propagation-mode",
178 default_value = "sqrt",
179 help = "Transaction propagation mode (sqrt, all, max:<number>)"
180 )]
181 pub propagation_mode: TransactionPropagationMode,
182
183 #[arg(long = "required-block-hashes", value_delimiter = ',')]
186 pub required_block_hashes: Vec<B256>,
187
188 #[arg(long)]
190 pub network_id: Option<u64>,
191}
192
193impl NetworkArgs {
194 pub fn resolved_addr(&self) -> IpAddr {
196 if let Some(ref if_name) = self.net_if {
197 let if_name = if if_name.is_empty() { DEFAULT_NET_IF_NAME } else { if_name };
198 return match reth_net_nat::net_if::resolve_net_if_ip(if_name) {
199 Ok(addr) => addr,
200 Err(err) => {
201 error!(target: "reth::cli",
202 if_name,
203 %err,
204 "Failed to read network interface IP"
205 );
206
207 DEFAULT_DISCOVERY_ADDR
208 }
209 };
210 }
211
212 self.addr
213 }
214
215 pub fn resolved_bootnodes(&self) -> Option<Vec<NodeRecord>> {
217 self.bootnodes.clone().map(|bootnodes| {
218 bootnodes.into_iter().filter_map(|node| node.resolve_blocking().ok()).collect()
219 })
220 }
221 pub const fn transactions_manager_config(&self) -> TransactionsManagerConfig {
223 TransactionsManagerConfig {
224 transaction_fetcher_config: TransactionFetcherConfig::new(
225 self.max_concurrent_tx_requests,
226 self.max_concurrent_tx_requests_per_peer,
227 self.soft_limit_byte_size_pooled_transactions_response,
228 self.soft_limit_byte_size_pooled_transactions_response_on_pack_request,
229 self.max_capacity_cache_txns_pending_fetch,
230 ),
231 max_transactions_seen_by_peer_history: self.max_seen_tx_history,
232 propagation_mode: self.propagation_mode,
233 }
234 }
235
236 pub fn network_config<N: NetworkPrimitives>(
248 &self,
249 config: &Config,
250 chain_spec: impl EthChainSpec,
251 secret_key: SecretKey,
252 default_peers_file: PathBuf,
253 ) -> NetworkConfigBuilder<N> {
254 let addr = self.resolved_addr();
255 let chain_bootnodes = self
256 .resolved_bootnodes()
257 .unwrap_or_else(|| chain_spec.bootnodes().unwrap_or_else(mainnet_nodes));
258 let peers_file = self.peers_file.clone().unwrap_or(default_peers_file);
259
260 let peers_config = config
262 .peers
263 .clone()
264 .with_max_inbound_opt(self.max_inbound_peers)
265 .with_max_outbound_opt(self.max_outbound_peers);
266
267 NetworkConfigBuilder::<N>::new(secret_key)
269 .peer_config(config.peers_config_with_basic_nodes_from_file(
270 self.persistent_peers_file(peers_file).as_deref(),
271 ))
272 .external_ip_resolver(self.nat)
273 .sessions_config(
274 SessionsConfig::default().with_upscaled_event_buffer(peers_config.max_peers()),
275 )
276 .peer_config(peers_config)
277 .boot_nodes(chain_bootnodes.clone())
278 .transactions_manager_config(self.transactions_manager_config())
279 .apply(|builder| {
281 let peer_id = builder.get_peer_id();
282 builder.hello_message(
283 HelloMessageWithProtocols::builder(peer_id)
284 .client_version(&self.identity)
285 .build(),
286 )
287 })
288 .apply(|builder| {
290 let rlpx_socket = (addr, self.port).into();
291 self.discovery.apply_to_builder(builder, rlpx_socket, chain_bootnodes)
292 })
293 .listener_addr(SocketAddr::new(
294 addr, self.port,
296 ))
297 .discovery_addr(SocketAddr::new(
298 self.discovery.addr,
299 self.discovery.port,
301 ))
302 .disable_tx_gossip(self.disable_tx_gossip)
303 .required_block_hashes(self.required_block_hashes.clone())
304 .network_id(self.network_id)
305 }
306
307 pub fn persistent_peers_file(&self, peers_file: PathBuf) -> Option<PathBuf> {
309 self.no_persist_peers.not().then_some(peers_file)
310 }
311
312 pub const fn with_unused_p2p_port(mut self) -> Self {
315 self.port = 0;
316 self
317 }
318
319 pub const fn with_unused_ports(mut self) -> Self {
322 self = self.with_unused_p2p_port();
323 self.discovery = self.discovery.with_unused_discovery_port();
324 self
325 }
326
327 pub fn adjust_instance_ports(&mut self, instance: Option<u16>) {
333 if let Some(instance) = instance {
334 debug_assert_ne!(instance, 0, "instance must be non-zero");
335 self.port += instance - 1;
336 self.discovery.adjust_instance_ports(instance);
337 }
338 }
339
340 pub async fn resolve_trusted_peers(&self) -> Result<Vec<NodeRecord>, std::io::Error> {
342 futures::future::try_join_all(
343 self.trusted_peers.iter().map(|peer| async move { peer.resolve().await }),
344 )
345 .await
346 }
347}
348
349impl Default for NetworkArgs {
350 fn default() -> Self {
351 Self {
352 discovery: DiscoveryArgs::default(),
353 trusted_peers: vec![],
354 trusted_only: false,
355 bootnodes: None,
356 dns_retries: 0,
357 peers_file: None,
358 identity: version_metadata().p2p_client_version.to_string(),
359 p2p_secret_key: None,
360 no_persist_peers: false,
361 nat: NatResolver::Any,
362 addr: DEFAULT_DISCOVERY_ADDR,
363 port: DEFAULT_DISCOVERY_PORT,
364 max_outbound_peers: None,
365 max_inbound_peers: None,
366 max_concurrent_tx_requests: DEFAULT_MAX_COUNT_CONCURRENT_REQUESTS,
367 max_concurrent_tx_requests_per_peer: DEFAULT_MAX_COUNT_CONCURRENT_REQUESTS_PER_PEER,
368 soft_limit_byte_size_pooled_transactions_response:
369 SOFT_LIMIT_BYTE_SIZE_POOLED_TRANSACTIONS_RESPONSE,
370 soft_limit_byte_size_pooled_transactions_response_on_pack_request: DEFAULT_SOFT_LIMIT_BYTE_SIZE_POOLED_TRANSACTIONS_RESP_ON_PACK_GET_POOLED_TRANSACTIONS_REQ,
371 max_pending_pool_imports: DEFAULT_MAX_COUNT_PENDING_POOL_IMPORTS,
372 max_seen_tx_history: DEFAULT_MAX_COUNT_TRANSACTIONS_SEEN_BY_PEER,
373 max_capacity_cache_txns_pending_fetch: DEFAULT_MAX_CAPACITY_CACHE_PENDING_FETCH,
374 net_if: None,
375 tx_propagation_policy: TransactionPropagationKind::default(),
376 disable_tx_gossip: false,
377 propagation_mode: TransactionPropagationMode::Sqrt,
378 required_block_hashes: vec![],
379 network_id: None,
380 }
381 }
382}
383
384#[derive(Debug, Clone, Args, PartialEq, Eq)]
386pub struct DiscoveryArgs {
387 #[arg(short, long, default_value_if("dev", "true", "true"))]
389 pub disable_discovery: bool,
390
391 #[arg(long, conflicts_with = "disable_discovery")]
393 pub disable_dns_discovery: bool,
394
395 #[arg(long, conflicts_with = "disable_discovery")]
397 pub disable_discv4_discovery: bool,
398
399 #[arg(long, conflicts_with = "disable_discovery")]
401 pub enable_discv5_discovery: bool,
402
403 #[arg(long, conflicts_with = "disable_discovery")]
405 pub disable_nat: bool,
406
407 #[arg(id = "discovery.addr", long = "discovery.addr", value_name = "DISCOVERY_ADDR", default_value_t = DEFAULT_DISCOVERY_ADDR)]
409 pub addr: IpAddr,
410
411 #[arg(id = "discovery.port", long = "discovery.port", value_name = "DISCOVERY_PORT", default_value_t = DEFAULT_DISCOVERY_PORT)]
413 pub port: u16,
414
415 #[arg(id = "discovery.v5.addr", long = "discovery.v5.addr", value_name = "DISCOVERY_V5_ADDR", default_value = None)]
418 pub discv5_addr: Option<Ipv4Addr>,
419
420 #[arg(id = "discovery.v5.addr.ipv6", long = "discovery.v5.addr.ipv6", value_name = "DISCOVERY_V5_ADDR_IPV6", default_value = None)]
423 pub discv5_addr_ipv6: Option<Ipv6Addr>,
424
425 #[arg(id = "discovery.v5.port", long = "discovery.v5.port", value_name = "DISCOVERY_V5_PORT",
428 default_value_t = DEFAULT_DISCOVERY_V5_PORT)]
429 pub discv5_port: u16,
430
431 #[arg(id = "discovery.v5.port.ipv6", long = "discovery.v5.port.ipv6", value_name = "DISCOVERY_V5_PORT_IPV6",
434 default_value = None, default_value_t = DEFAULT_DISCOVERY_V5_PORT)]
435 pub discv5_port_ipv6: u16,
436
437 #[arg(id = "discovery.v5.lookup-interval", long = "discovery.v5.lookup-interval", value_name = "DISCOVERY_V5_LOOKUP_INTERVAL", default_value_t = DEFAULT_SECONDS_LOOKUP_INTERVAL)]
440 pub discv5_lookup_interval: u64,
441
442 #[arg(id = "discovery.v5.bootstrap.lookup-interval", long = "discovery.v5.bootstrap.lookup-interval", value_name = "DISCOVERY_V5_BOOTSTRAP_LOOKUP_INTERVAL",
445 default_value_t = DEFAULT_SECONDS_BOOTSTRAP_LOOKUP_INTERVAL)]
446 pub discv5_bootstrap_lookup_interval: u64,
447
448 #[arg(id = "discovery.v5.bootstrap.lookup-countdown", long = "discovery.v5.bootstrap.lookup-countdown", value_name = "DISCOVERY_V5_BOOTSTRAP_LOOKUP_COUNTDOWN",
450 default_value_t = DEFAULT_COUNT_BOOTSTRAP_LOOKUPS)]
451 pub discv5_bootstrap_lookup_countdown: u64,
452}
453
454impl DiscoveryArgs {
455 pub fn apply_to_builder<N>(
457 &self,
458 mut network_config_builder: NetworkConfigBuilder<N>,
459 rlpx_tcp_socket: SocketAddr,
460 boot_nodes: impl IntoIterator<Item = NodeRecord>,
461 ) -> NetworkConfigBuilder<N>
462 where
463 N: NetworkPrimitives,
464 {
465 if self.disable_discovery || self.disable_dns_discovery {
466 network_config_builder = network_config_builder.disable_dns_discovery();
467 }
468
469 if self.disable_discovery || self.disable_discv4_discovery {
470 network_config_builder = network_config_builder.disable_discv4_discovery();
471 }
472
473 if self.disable_nat {
474 network_config_builder = network_config_builder.disable_nat();
476 }
477
478 if self.should_enable_discv5() {
479 network_config_builder = network_config_builder
480 .discovery_v5(self.discovery_v5_builder(rlpx_tcp_socket, boot_nodes));
481 }
482
483 network_config_builder
484 }
485
486 pub fn discovery_v5_builder(
488 &self,
489 rlpx_tcp_socket: SocketAddr,
490 boot_nodes: impl IntoIterator<Item = NodeRecord>,
491 ) -> reth_discv5::ConfigBuilder {
492 let Self {
493 discv5_addr,
494 discv5_addr_ipv6,
495 discv5_port,
496 discv5_port_ipv6,
497 discv5_lookup_interval,
498 discv5_bootstrap_lookup_interval,
499 discv5_bootstrap_lookup_countdown,
500 ..
501 } = self;
502
503 let discv5_addr_ipv4 = discv5_addr.or(match rlpx_tcp_socket {
505 SocketAddr::V4(addr) => Some(*addr.ip()),
506 SocketAddr::V6(_) => None,
507 });
508 let discv5_addr_ipv6 = discv5_addr_ipv6.or(match rlpx_tcp_socket {
509 SocketAddr::V4(_) => None,
510 SocketAddr::V6(addr) => Some(*addr.ip()),
511 });
512
513 reth_discv5::Config::builder(rlpx_tcp_socket)
514 .discv5_config(
515 reth_discv5::discv5::ConfigBuilder::new(ListenConfig::from_two_sockets(
516 discv5_addr_ipv4.map(|addr| SocketAddrV4::new(addr, *discv5_port)),
517 discv5_addr_ipv6.map(|addr| SocketAddrV6::new(addr, *discv5_port_ipv6, 0, 0)),
518 ))
519 .build(),
520 )
521 .add_unsigned_boot_nodes(boot_nodes)
522 .lookup_interval(*discv5_lookup_interval)
523 .bootstrap_lookup_interval(*discv5_bootstrap_lookup_interval)
524 .bootstrap_lookup_countdown(*discv5_bootstrap_lookup_countdown)
525 }
526
527 const fn should_enable_discv5(&self) -> bool {
529 if self.disable_discovery {
530 return false;
531 }
532
533 self.enable_discv5_discovery ||
534 self.discv5_addr.is_some() ||
535 self.discv5_addr_ipv6.is_some()
536 }
537
538 pub const fn with_unused_discovery_port(mut self) -> Self {
541 self.port = 0;
542 self
543 }
544
545 pub fn adjust_instance_ports(&mut self, instance: u16) {
551 debug_assert_ne!(instance, 0, "instance must be non-zero");
552 self.port += instance - 1;
553 self.discv5_port += instance - 1;
554 self.discv5_port_ipv6 += instance - 1;
555 }
556}
557
558impl Default for DiscoveryArgs {
559 fn default() -> Self {
560 Self {
561 disable_discovery: false,
562 disable_dns_discovery: false,
563 disable_discv4_discovery: false,
564 enable_discv5_discovery: false,
565 disable_nat: false,
566 addr: DEFAULT_DISCOVERY_ADDR,
567 port: DEFAULT_DISCOVERY_PORT,
568 discv5_addr: None,
569 discv5_addr_ipv6: None,
570 discv5_port: DEFAULT_DISCOVERY_V5_PORT,
571 discv5_port_ipv6: DEFAULT_DISCOVERY_V5_PORT,
572 discv5_lookup_interval: DEFAULT_SECONDS_LOOKUP_INTERVAL,
573 discv5_bootstrap_lookup_interval: DEFAULT_SECONDS_BOOTSTRAP_LOOKUP_INTERVAL,
574 discv5_bootstrap_lookup_countdown: DEFAULT_COUNT_BOOTSTRAP_LOOKUPS,
575 }
576 }
577}
578
579#[cfg(test)]
580mod tests {
581 use super::*;
582 use clap::Parser;
583 #[derive(Parser)]
585 struct CommandParser<T: Args> {
586 #[command(flatten)]
587 args: T,
588 }
589
590 #[test]
591 fn parse_nat_args() {
592 let args = CommandParser::<NetworkArgs>::parse_from(["reth", "--nat", "none"]).args;
593 assert_eq!(args.nat, NatResolver::None);
594
595 let args =
596 CommandParser::<NetworkArgs>::parse_from(["reth", "--nat", "extip:0.0.0.0"]).args;
597 assert_eq!(args.nat, NatResolver::ExternalIp("0.0.0.0".parse().unwrap()));
598 }
599
600 #[test]
601 fn parse_peer_args() {
602 let args =
603 CommandParser::<NetworkArgs>::parse_from(["reth", "--max-outbound-peers", "50"]).args;
604 assert_eq!(args.max_outbound_peers, Some(50));
605 assert_eq!(args.max_inbound_peers, None);
606
607 let args = CommandParser::<NetworkArgs>::parse_from([
608 "reth",
609 "--max-outbound-peers",
610 "75",
611 "--max-inbound-peers",
612 "15",
613 ])
614 .args;
615 assert_eq!(args.max_outbound_peers, Some(75));
616 assert_eq!(args.max_inbound_peers, Some(15));
617 }
618
619 #[test]
620 fn parse_trusted_peer_args() {
621 let args =
622 CommandParser::<NetworkArgs>::parse_from([
623 "reth",
624 "--trusted-peers",
625 "enode://d860a01f9722d78051619d1e2351aba3f43f943f6f00718d1b9baa4101932a1f5011f16bb2b1bb35db20d6fe28fa0bf09636d26a87d31de9ec6203eeedb1f666@18.138.108.67:30303,enode://22a8232c3abc76a16ae9d6c3b164f98775fe226f0917b0ca871128a74a8e9630b458460865bab457221f1d448dd9791d24c4e5d88786180ac185df813a68d4de@3.209.45.79:30303"
626 ])
627 .args;
628
629 assert_eq!(
630 args.trusted_peers,
631 vec![
632 "enode://d860a01f9722d78051619d1e2351aba3f43f943f6f00718d1b9baa4101932a1f5011f16bb2b1bb35db20d6fe28fa0bf09636d26a87d31de9ec6203eeedb1f666@18.138.108.67:30303".parse().unwrap(),
633 "enode://22a8232c3abc76a16ae9d6c3b164f98775fe226f0917b0ca871128a74a8e9630b458460865bab457221f1d448dd9791d24c4e5d88786180ac185df813a68d4de@3.209.45.79:30303".parse().unwrap()
634 ]
635 );
636 }
637
638 #[test]
639 fn parse_retry_strategy_args() {
640 let tests = vec![0, 10];
641
642 for retries in tests {
643 let args = CommandParser::<NetworkArgs>::parse_from([
644 "reth",
645 "--dns-retries",
646 retries.to_string().as_str(),
647 ])
648 .args;
649
650 assert_eq!(args.dns_retries, retries);
651 }
652 }
653
654 #[test]
655 fn parse_disable_tx_gossip_args() {
656 let args = CommandParser::<NetworkArgs>::parse_from(["reth", "--disable-tx-gossip"]).args;
657 assert!(args.disable_tx_gossip);
658 }
659
660 #[test]
661 fn network_args_default_sanity_test() {
662 let default_args = NetworkArgs::default();
663 let args = CommandParser::<NetworkArgs>::parse_from(["reth"]).args;
664
665 assert_eq!(args, default_args);
666 }
667
668 #[test]
669 fn parse_required_block_hashes() {
670 let args = CommandParser::<NetworkArgs>::parse_from([
671 "reth",
672 "--required-block-hashes",
673 "0x1111111111111111111111111111111111111111111111111111111111111111,0x2222222222222222222222222222222222222222222222222222222222222222",
674 ])
675 .args;
676
677 assert_eq!(args.required_block_hashes.len(), 2);
678 assert_eq!(
679 args.required_block_hashes[0].to_string(),
680 "0x1111111111111111111111111111111111111111111111111111111111111111"
681 );
682 assert_eq!(
683 args.required_block_hashes[1].to_string(),
684 "0x2222222222222222222222222222222222222222222222222222222222222222"
685 );
686 }
687
688 #[test]
689 fn parse_empty_required_block_hashes() {
690 let args = CommandParser::<NetworkArgs>::parse_from(["reth"]).args;
691 assert!(args.required_block_hashes.is_empty());
692 }
693}