reth_node_core/args/
network.rs

1//! clap [Args](clap::Args) for network related arguments.
2
3use std::{
4    net::{IpAddr, Ipv4Addr, Ipv6Addr, SocketAddr, SocketAddrV4, SocketAddrV6},
5    ops::Not,
6    path::PathBuf,
7};
8
9use clap::Args;
10use reth_chainspec::EthChainSpec;
11use reth_config::Config;
12use reth_discv4::{NodeRecord, DEFAULT_DISCOVERY_ADDR, DEFAULT_DISCOVERY_PORT};
13use reth_discv5::{
14    discv5::ListenConfig, DEFAULT_COUNT_BOOTSTRAP_LOOKUPS, DEFAULT_DISCOVERY_V5_PORT,
15    DEFAULT_SECONDS_BOOTSTRAP_LOOKUP_INTERVAL, DEFAULT_SECONDS_LOOKUP_INTERVAL,
16};
17use reth_net_nat::{NatResolver, DEFAULT_NET_IF_NAME};
18use reth_network::{
19    transactions::{
20        constants::{
21            tx_fetcher::{
22                DEFAULT_MAX_CAPACITY_CACHE_PENDING_FETCH, DEFAULT_MAX_COUNT_CONCURRENT_REQUESTS,
23                DEFAULT_MAX_COUNT_CONCURRENT_REQUESTS_PER_PEER,
24            },
25            tx_manager::{
26                DEFAULT_MAX_COUNT_PENDING_POOL_IMPORTS, DEFAULT_MAX_COUNT_TRANSACTIONS_SEEN_BY_PEER,
27            },
28        },
29        TransactionFetcherConfig, TransactionsManagerConfig,
30        DEFAULT_SOFT_LIMIT_BYTE_SIZE_POOLED_TRANSACTIONS_RESP_ON_PACK_GET_POOLED_TRANSACTIONS_REQ,
31        SOFT_LIMIT_BYTE_SIZE_POOLED_TRANSACTIONS_RESPONSE,
32    },
33    HelloMessageWithProtocols, NetworkConfigBuilder, SessionsConfig,
34};
35use reth_network_peers::{mainnet_nodes, TrustedPeer};
36use secp256k1::SecretKey;
37use tracing::error;
38
39use crate::version::P2P_CLIENT_VERSION;
40
41/// Parameters for configuring the network more granularity via CLI
42#[derive(Debug, Clone, Args, PartialEq, Eq)]
43#[command(next_help_heading = "Networking")]
44pub struct NetworkArgs {
45    /// Arguments to setup discovery service.
46    #[command(flatten)]
47    pub discovery: DiscoveryArgs,
48
49    #[allow(clippy::doc_markdown)]
50    /// Comma separated enode URLs of trusted peers for P2P connections.
51    ///
52    /// --trusted-peers enode://abcd@192.168.0.1:30303
53    #[arg(long, value_delimiter = ',')]
54    pub trusted_peers: Vec<TrustedPeer>,
55
56    /// Connect to or accept from trusted peers only
57    #[arg(long)]
58    pub trusted_only: bool,
59
60    /// Comma separated enode URLs for P2P discovery bootstrap.
61    ///
62    /// Will fall back to a network-specific default if not specified.
63    #[arg(long, value_delimiter = ',')]
64    pub bootnodes: Option<Vec<TrustedPeer>>,
65
66    /// Amount of DNS resolution requests retries to perform when peering.
67    #[arg(long, default_value_t = 0)]
68    pub dns_retries: usize,
69
70    /// The path to the known peers file. Connected peers are dumped to this file on nodes
71    /// shutdown, and read on startup. Cannot be used with `--no-persist-peers`.
72    #[arg(long, value_name = "FILE", verbatim_doc_comment, conflicts_with = "no_persist_peers")]
73    pub peers_file: Option<PathBuf>,
74
75    /// Custom node identity
76    #[arg(long, value_name = "IDENTITY", default_value = P2P_CLIENT_VERSION)]
77    pub identity: String,
78
79    /// Secret key to use for this node.
80    ///
81    /// This will also deterministically set the peer ID. If not specified, it will be set in the
82    /// data dir for the chain being used.
83    #[arg(long, value_name = "PATH")]
84    pub p2p_secret_key: Option<PathBuf>,
85
86    /// Do not persist peers.
87    #[arg(long, verbatim_doc_comment)]
88    pub no_persist_peers: bool,
89
90    /// NAT resolution method (any|none|upnp|publicip|extip:\<IP\>)
91    #[arg(long, default_value = "any")]
92    pub nat: NatResolver,
93
94    /// Network listening address
95    #[arg(long = "addr", value_name = "ADDR", default_value_t = DEFAULT_DISCOVERY_ADDR)]
96    pub addr: IpAddr,
97
98    /// Network listening port
99    #[arg(long = "port", value_name = "PORT", default_value_t = DEFAULT_DISCOVERY_PORT)]
100    pub port: u16,
101
102    /// Maximum number of outbound requests. default: 100
103    #[arg(long)]
104    pub max_outbound_peers: Option<usize>,
105
106    /// Maximum number of inbound requests. default: 30
107    #[arg(long)]
108    pub max_inbound_peers: Option<usize>,
109
110    /// Max concurrent `GetPooledTransactions` requests.
111    #[arg(long = "max-tx-reqs", value_name = "COUNT", default_value_t = DEFAULT_MAX_COUNT_CONCURRENT_REQUESTS, verbatim_doc_comment)]
112    pub max_concurrent_tx_requests: u32,
113
114    /// Max concurrent `GetPooledTransactions` requests per peer.
115    #[arg(long = "max-tx-reqs-peer", value_name = "COUNT", default_value_t = DEFAULT_MAX_COUNT_CONCURRENT_REQUESTS_PER_PEER, verbatim_doc_comment)]
116    pub max_concurrent_tx_requests_per_peer: u8,
117
118    /// Max number of seen transactions to remember per peer.
119    ///
120    /// Default is 320 transaction hashes.
121    #[arg(long = "max-seen-tx-history", value_name = "COUNT", default_value_t = DEFAULT_MAX_COUNT_TRANSACTIONS_SEEN_BY_PEER, verbatim_doc_comment)]
122    pub max_seen_tx_history: u32,
123
124    #[arg(long = "max-pending-imports", value_name = "COUNT", default_value_t = DEFAULT_MAX_COUNT_PENDING_POOL_IMPORTS, verbatim_doc_comment)]
125    /// Max number of transactions to import concurrently.
126    pub max_pending_pool_imports: usize,
127
128    /// Experimental, for usage in research. Sets the max accumulated byte size of transactions
129    /// to pack in one response.
130    /// Spec'd at 2MiB.
131    #[arg(long = "pooled-tx-response-soft-limit", value_name = "BYTES", default_value_t = SOFT_LIMIT_BYTE_SIZE_POOLED_TRANSACTIONS_RESPONSE, verbatim_doc_comment)]
132    pub soft_limit_byte_size_pooled_transactions_response: usize,
133
134    /// Experimental, for usage in research. Sets the max accumulated byte size of transactions to
135    /// request in one request.
136    ///
137    /// Since `RLPx` protocol version 68, the byte size of a transaction is shared as metadata in a
138    /// transaction announcement (see `RLPx` specs). This allows a node to request a specific size
139    /// response.
140    ///
141    /// By default, nodes request only 128 KiB worth of transactions, but should a peer request
142    /// more, up to 2 MiB, a node will answer with more than 128 KiB.
143    ///
144    /// Default is 128 KiB.
145    #[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)]
146    pub soft_limit_byte_size_pooled_transactions_response_on_pack_request: usize,
147
148    /// Max capacity of cache of hashes for transactions pending fetch.
149    #[arg(long = "max-tx-pending-fetch", value_name = "COUNT", default_value_t = DEFAULT_MAX_CAPACITY_CACHE_PENDING_FETCH, verbatim_doc_comment)]
150    pub max_capacity_cache_txns_pending_fetch: u32,
151
152    /// Name of network interface used to communicate with peers.
153    ///
154    /// If flag is set, but no value is passed, the default interface for docker `eth0` is tried.
155    #[arg(long = "net-if.experimental", conflicts_with = "addr", value_name = "IF_NAME")]
156    pub net_if: Option<String>,
157}
158
159impl NetworkArgs {
160    /// Returns the resolved IP address.
161    pub fn resolved_addr(&self) -> IpAddr {
162        if let Some(ref if_name) = self.net_if {
163            let if_name = if if_name.is_empty() { DEFAULT_NET_IF_NAME } else { if_name };
164            return match reth_net_nat::net_if::resolve_net_if_ip(if_name) {
165                Ok(addr) => addr,
166                Err(err) => {
167                    error!(target: "reth::cli",
168                        if_name,
169                        %err,
170                        "Failed to read network interface IP"
171                    );
172
173                    DEFAULT_DISCOVERY_ADDR
174                }
175            };
176        }
177
178        self.addr
179    }
180
181    /// Returns the resolved bootnodes if any are provided.
182    pub fn resolved_bootnodes(&self) -> Option<Vec<NodeRecord>> {
183        self.bootnodes.clone().map(|bootnodes| {
184            bootnodes.into_iter().filter_map(|node| node.resolve_blocking().ok()).collect()
185        })
186    }
187
188    /// Build a [`NetworkConfigBuilder`] from a [`Config`] and a [`EthChainSpec`], in addition to
189    /// the values in this option struct.
190    ///
191    /// The `default_peers_file` will be used as the default location to store the persistent peers
192    /// file if `no_persist_peers` is false, and there is no provided `peers_file`.
193    ///
194    /// Configured Bootnodes are prioritized, if unset, the chain spec bootnodes are used
195    /// Priority order for bootnodes configuration:
196    /// 1. --bootnodes flag
197    /// 2. Network preset flags (e.g. --holesky)
198    /// 3. default to mainnet nodes
199    pub fn network_config(
200        &self,
201        config: &Config,
202        chain_spec: impl EthChainSpec,
203        secret_key: SecretKey,
204        default_peers_file: PathBuf,
205    ) -> NetworkConfigBuilder {
206        let addr = self.resolved_addr();
207        let chain_bootnodes = self
208            .resolved_bootnodes()
209            .unwrap_or_else(|| chain_spec.bootnodes().unwrap_or_else(mainnet_nodes));
210        let peers_file = self.peers_file.clone().unwrap_or(default_peers_file);
211
212        // Configure peer connections
213        let peers_config = config
214            .peers
215            .clone()
216            .with_max_inbound_opt(self.max_inbound_peers)
217            .with_max_outbound_opt(self.max_outbound_peers);
218
219        // Configure transactions manager
220        let transactions_manager_config = TransactionsManagerConfig {
221            transaction_fetcher_config: TransactionFetcherConfig::new(
222                self.max_concurrent_tx_requests,
223                self.max_concurrent_tx_requests_per_peer,
224                self.soft_limit_byte_size_pooled_transactions_response,
225                self.soft_limit_byte_size_pooled_transactions_response_on_pack_request,
226                self.max_capacity_cache_txns_pending_fetch,
227            ),
228            max_transactions_seen_by_peer_history: self.max_seen_tx_history,
229            propagation_mode: Default::default(),
230        };
231
232        // Configure basic network stack
233        NetworkConfigBuilder::new(secret_key)
234            .peer_config(config.peers_config_with_basic_nodes_from_file(
235                self.persistent_peers_file(peers_file).as_deref(),
236            ))
237            .external_ip_resolver(self.nat)
238            .sessions_config(
239                SessionsConfig::default().with_upscaled_event_buffer(peers_config.max_peers()),
240            )
241            .peer_config(peers_config)
242            .boot_nodes(chain_bootnodes.clone())
243            .transactions_manager_config(transactions_manager_config)
244            // Configure node identity
245            .apply(|builder| {
246                let peer_id = builder.get_peer_id();
247                builder.hello_message(
248                    HelloMessageWithProtocols::builder(peer_id)
249                        .client_version(&self.identity)
250                        .build(),
251                )
252            })
253            // apply discovery settings
254            .apply(|builder| {
255                let rlpx_socket = (addr, self.port).into();
256                self.discovery.apply_to_builder(builder, rlpx_socket, chain_bootnodes)
257            })
258            .listener_addr(SocketAddr::new(
259                addr, // set discovery port based on instance number
260                self.port,
261            ))
262            .discovery_addr(SocketAddr::new(
263                self.discovery.addr,
264                // set discovery port based on instance number
265                self.discovery.port,
266            ))
267    }
268
269    /// If `no_persist_peers` is false then this returns the path to the persistent peers file path.
270    pub fn persistent_peers_file(&self, peers_file: PathBuf) -> Option<PathBuf> {
271        self.no_persist_peers.not().then_some(peers_file)
272    }
273
274    /// Sets the p2p port to zero, to allow the OS to assign a random unused port when
275    /// the network components bind to a socket.
276    pub const fn with_unused_p2p_port(mut self) -> Self {
277        self.port = 0;
278        self
279    }
280
281    /// Sets the p2p and discovery ports to zero, allowing the OD to assign a random unused port
282    /// when network components bind to sockets.
283    pub const fn with_unused_ports(mut self) -> Self {
284        self = self.with_unused_p2p_port();
285        self.discovery = self.discovery.with_unused_discovery_port();
286        self
287    }
288
289    /// Change networking port numbers based on the instance number.
290    /// Ports are updated to `previous_value + instance - 1`
291    ///
292    /// # Panics
293    /// Warning: if `instance` is zero in debug mode, this will panic.
294    pub fn adjust_instance_ports(&mut self, instance: u16) {
295        debug_assert_ne!(instance, 0, "instance must be non-zero");
296        self.port += instance - 1;
297        self.discovery.adjust_instance_ports(instance);
298    }
299
300    /// Resolve all trusted peers at once
301    pub async fn resolve_trusted_peers(&self) -> Result<Vec<NodeRecord>, std::io::Error> {
302        futures::future::try_join_all(
303            self.trusted_peers.iter().map(|peer| async move { peer.resolve().await }),
304        )
305        .await
306    }
307}
308
309impl Default for NetworkArgs {
310    fn default() -> Self {
311        Self {
312            discovery: DiscoveryArgs::default(),
313            trusted_peers: vec![],
314            trusted_only: false,
315            bootnodes: None,
316            dns_retries: 0,
317            peers_file: None,
318            identity: P2P_CLIENT_VERSION.to_string(),
319            p2p_secret_key: None,
320            no_persist_peers: false,
321            nat: NatResolver::Any,
322            addr: DEFAULT_DISCOVERY_ADDR,
323            port: DEFAULT_DISCOVERY_PORT,
324            max_outbound_peers: None,
325            max_inbound_peers: None,
326            max_concurrent_tx_requests: DEFAULT_MAX_COUNT_CONCURRENT_REQUESTS,
327            max_concurrent_tx_requests_per_peer: DEFAULT_MAX_COUNT_CONCURRENT_REQUESTS_PER_PEER,
328            soft_limit_byte_size_pooled_transactions_response:
329                SOFT_LIMIT_BYTE_SIZE_POOLED_TRANSACTIONS_RESPONSE,
330            soft_limit_byte_size_pooled_transactions_response_on_pack_request: DEFAULT_SOFT_LIMIT_BYTE_SIZE_POOLED_TRANSACTIONS_RESP_ON_PACK_GET_POOLED_TRANSACTIONS_REQ,
331            max_pending_pool_imports: DEFAULT_MAX_COUNT_PENDING_POOL_IMPORTS,
332            max_seen_tx_history: DEFAULT_MAX_COUNT_TRANSACTIONS_SEEN_BY_PEER,
333            max_capacity_cache_txns_pending_fetch: DEFAULT_MAX_CAPACITY_CACHE_PENDING_FETCH,
334            net_if: None,
335        }
336    }
337}
338
339/// Arguments to setup discovery
340#[derive(Debug, Clone, Args, PartialEq, Eq)]
341pub struct DiscoveryArgs {
342    /// Disable the discovery service.
343    #[arg(short, long, default_value_if("dev", "true", "true"))]
344    pub disable_discovery: bool,
345
346    /// Disable the DNS discovery.
347    #[arg(long, conflicts_with = "disable_discovery")]
348    pub disable_dns_discovery: bool,
349
350    /// Disable Discv4 discovery.
351    #[arg(long, conflicts_with = "disable_discovery")]
352    pub disable_discv4_discovery: bool,
353
354    /// Enable Discv5 discovery.
355    #[arg(long, conflicts_with = "disable_discovery")]
356    pub enable_discv5_discovery: bool,
357
358    /// Disable Nat discovery.
359    #[arg(long, conflicts_with = "disable_discovery")]
360    pub disable_nat: bool,
361
362    /// The UDP address to use for devp2p peer discovery version 4.
363    #[arg(id = "discovery.addr", long = "discovery.addr", value_name = "DISCOVERY_ADDR", default_value_t = DEFAULT_DISCOVERY_ADDR)]
364    pub addr: IpAddr,
365
366    /// The UDP port to use for devp2p peer discovery version 4.
367    #[arg(id = "discovery.port", long = "discovery.port", value_name = "DISCOVERY_PORT", default_value_t = DEFAULT_DISCOVERY_PORT)]
368    pub port: u16,
369
370    /// The UDP IPv4 address to use for devp2p peer discovery version 5. Overwritten by `RLPx`
371    /// address, if it's also IPv4.
372    #[arg(id = "discovery.v5.addr", long = "discovery.v5.addr", value_name = "DISCOVERY_V5_ADDR", default_value = None)]
373    pub discv5_addr: Option<Ipv4Addr>,
374
375    /// The UDP IPv6 address to use for devp2p peer discovery version 5. Overwritten by `RLPx`
376    /// address, if it's also IPv6.
377    #[arg(id = "discovery.v5.addr.ipv6", long = "discovery.v5.addr.ipv6", value_name = "DISCOVERY_V5_ADDR_IPV6", default_value = None)]
378    pub discv5_addr_ipv6: Option<Ipv6Addr>,
379
380    /// The UDP IPv4 port to use for devp2p peer discovery version 5. Not used unless `--addr` is
381    /// IPv4, or `--discovery.v5.addr` is set.
382    #[arg(id = "discovery.v5.port", long = "discovery.v5.port", value_name = "DISCOVERY_V5_PORT",
383    default_value_t = DEFAULT_DISCOVERY_V5_PORT)]
384    pub discv5_port: u16,
385
386    /// The UDP IPv6 port to use for devp2p peer discovery version 5. Not used unless `--addr` is
387    /// IPv6, or `--discovery.addr.ipv6` is set.
388    #[arg(id = "discovery.v5.port.ipv6", long = "discovery.v5.port.ipv6", value_name = "DISCOVERY_V5_PORT_IPV6",
389    default_value = None, default_value_t = DEFAULT_DISCOVERY_V5_PORT)]
390    pub discv5_port_ipv6: u16,
391
392    /// The interval in seconds at which to carry out periodic lookup queries, for the whole
393    /// run of the program.
394    #[arg(id = "discovery.v5.lookup-interval", long = "discovery.v5.lookup-interval", value_name = "DISCOVERY_V5_LOOKUP_INTERVAL", default_value_t = DEFAULT_SECONDS_LOOKUP_INTERVAL)]
395    pub discv5_lookup_interval: u64,
396
397    /// The interval in seconds at which to carry out boost lookup queries, for a fixed number of
398    /// times, at bootstrap.
399    #[arg(id = "discovery.v5.bootstrap.lookup-interval", long = "discovery.v5.bootstrap.lookup-interval", value_name = "DISCOVERY_V5_BOOTSTRAP_LOOKUP_INTERVAL",
400        default_value_t = DEFAULT_SECONDS_BOOTSTRAP_LOOKUP_INTERVAL)]
401    pub discv5_bootstrap_lookup_interval: u64,
402
403    /// The number of times to carry out boost lookup queries at bootstrap.
404    #[arg(id = "discovery.v5.bootstrap.lookup-countdown", long = "discovery.v5.bootstrap.lookup-countdown", value_name = "DISCOVERY_V5_BOOTSTRAP_LOOKUP_COUNTDOWN",
405        default_value_t = DEFAULT_COUNT_BOOTSTRAP_LOOKUPS)]
406    pub discv5_bootstrap_lookup_countdown: u64,
407}
408
409impl DiscoveryArgs {
410    /// Apply the discovery settings to the given [`NetworkConfigBuilder`]
411    pub fn apply_to_builder(
412        &self,
413        mut network_config_builder: NetworkConfigBuilder,
414        rlpx_tcp_socket: SocketAddr,
415        boot_nodes: impl IntoIterator<Item = NodeRecord>,
416    ) -> NetworkConfigBuilder {
417        if self.disable_discovery || self.disable_dns_discovery {
418            network_config_builder = network_config_builder.disable_dns_discovery();
419        }
420
421        if self.disable_discovery || self.disable_discv4_discovery {
422            network_config_builder = network_config_builder.disable_discv4_discovery();
423        }
424
425        if self.disable_discovery || self.disable_nat {
426            network_config_builder = network_config_builder.disable_nat();
427        }
428
429        if !self.disable_discovery && self.enable_discv5_discovery {
430            network_config_builder = network_config_builder
431                .discovery_v5(self.discovery_v5_builder(rlpx_tcp_socket, boot_nodes));
432        }
433
434        network_config_builder
435    }
436
437    /// Creates a [`reth_discv5::ConfigBuilder`] filling it with the values from this struct.
438    pub fn discovery_v5_builder(
439        &self,
440        rlpx_tcp_socket: SocketAddr,
441        boot_nodes: impl IntoIterator<Item = NodeRecord>,
442    ) -> reth_discv5::ConfigBuilder {
443        let Self {
444            discv5_addr,
445            discv5_addr_ipv6,
446            discv5_port,
447            discv5_port_ipv6,
448            discv5_lookup_interval,
449            discv5_bootstrap_lookup_interval,
450            discv5_bootstrap_lookup_countdown,
451            ..
452        } = self;
453
454        // Use rlpx address if none given
455        let discv5_addr_ipv4 = discv5_addr.or(match rlpx_tcp_socket {
456            SocketAddr::V4(addr) => Some(*addr.ip()),
457            SocketAddr::V6(_) => None,
458        });
459        let discv5_addr_ipv6 = discv5_addr_ipv6.or(match rlpx_tcp_socket {
460            SocketAddr::V4(_) => None,
461            SocketAddr::V6(addr) => Some(*addr.ip()),
462        });
463
464        reth_discv5::Config::builder(rlpx_tcp_socket)
465            .discv5_config(
466                reth_discv5::discv5::ConfigBuilder::new(ListenConfig::from_two_sockets(
467                    discv5_addr_ipv4.map(|addr| SocketAddrV4::new(addr, *discv5_port)),
468                    discv5_addr_ipv6.map(|addr| SocketAddrV6::new(addr, *discv5_port_ipv6, 0, 0)),
469                ))
470                .build(),
471            )
472            .add_unsigned_boot_nodes(boot_nodes)
473            .lookup_interval(*discv5_lookup_interval)
474            .bootstrap_lookup_interval(*discv5_bootstrap_lookup_interval)
475            .bootstrap_lookup_countdown(*discv5_bootstrap_lookup_countdown)
476    }
477
478    /// Set the discovery port to zero, to allow the OS to assign a random unused port when
479    /// discovery binds to the socket.
480    pub const fn with_unused_discovery_port(mut self) -> Self {
481        self.port = 0;
482        self
483    }
484
485    /// Change networking port numbers based on the instance number.
486    /// Ports are updated to `previous_value + instance - 1`
487    ///
488    /// # Panics
489    /// Warning: if `instance` is zero in debug mode, this will panic.
490    pub fn adjust_instance_ports(&mut self, instance: u16) {
491        debug_assert_ne!(instance, 0, "instance must be non-zero");
492        self.port += instance - 1;
493        self.discv5_port += instance - 1;
494        self.discv5_port_ipv6 += instance - 1;
495    }
496}
497
498impl Default for DiscoveryArgs {
499    fn default() -> Self {
500        Self {
501            disable_discovery: false,
502            disable_dns_discovery: false,
503            disable_discv4_discovery: false,
504            enable_discv5_discovery: false,
505            disable_nat: false,
506            addr: DEFAULT_DISCOVERY_ADDR,
507            port: DEFAULT_DISCOVERY_PORT,
508            discv5_addr: None,
509            discv5_addr_ipv6: None,
510            discv5_port: DEFAULT_DISCOVERY_V5_PORT,
511            discv5_port_ipv6: DEFAULT_DISCOVERY_V5_PORT,
512            discv5_lookup_interval: DEFAULT_SECONDS_LOOKUP_INTERVAL,
513            discv5_bootstrap_lookup_interval: DEFAULT_SECONDS_BOOTSTRAP_LOOKUP_INTERVAL,
514            discv5_bootstrap_lookup_countdown: DEFAULT_COUNT_BOOTSTRAP_LOOKUPS,
515        }
516    }
517}
518
519#[cfg(test)]
520mod tests {
521    use super::*;
522    use clap::Parser;
523    /// A helper type to parse Args more easily
524    #[derive(Parser)]
525    struct CommandParser<T: Args> {
526        #[command(flatten)]
527        args: T,
528    }
529
530    #[test]
531    fn parse_nat_args() {
532        let args = CommandParser::<NetworkArgs>::parse_from(["reth", "--nat", "none"]).args;
533        assert_eq!(args.nat, NatResolver::None);
534
535        let args =
536            CommandParser::<NetworkArgs>::parse_from(["reth", "--nat", "extip:0.0.0.0"]).args;
537        assert_eq!(args.nat, NatResolver::ExternalIp("0.0.0.0".parse().unwrap()));
538    }
539
540    #[test]
541    fn parse_peer_args() {
542        let args =
543            CommandParser::<NetworkArgs>::parse_from(["reth", "--max-outbound-peers", "50"]).args;
544        assert_eq!(args.max_outbound_peers, Some(50));
545        assert_eq!(args.max_inbound_peers, None);
546
547        let args = CommandParser::<NetworkArgs>::parse_from([
548            "reth",
549            "--max-outbound-peers",
550            "75",
551            "--max-inbound-peers",
552            "15",
553        ])
554        .args;
555        assert_eq!(args.max_outbound_peers, Some(75));
556        assert_eq!(args.max_inbound_peers, Some(15));
557    }
558
559    #[test]
560    fn parse_trusted_peer_args() {
561        let args =
562            CommandParser::<NetworkArgs>::parse_from([
563            "reth",
564            "--trusted-peers",
565            "enode://d860a01f9722d78051619d1e2351aba3f43f943f6f00718d1b9baa4101932a1f5011f16bb2b1bb35db20d6fe28fa0bf09636d26a87d31de9ec6203eeedb1f666@18.138.108.67:30303,enode://22a8232c3abc76a16ae9d6c3b164f98775fe226f0917b0ca871128a74a8e9630b458460865bab457221f1d448dd9791d24c4e5d88786180ac185df813a68d4de@3.209.45.79:30303"
566        ])
567        .args;
568
569        assert_eq!(
570            args.trusted_peers,
571            vec![
572            "enode://d860a01f9722d78051619d1e2351aba3f43f943f6f00718d1b9baa4101932a1f5011f16bb2b1bb35db20d6fe28fa0bf09636d26a87d31de9ec6203eeedb1f666@18.138.108.67:30303".parse().unwrap(),
573            "enode://22a8232c3abc76a16ae9d6c3b164f98775fe226f0917b0ca871128a74a8e9630b458460865bab457221f1d448dd9791d24c4e5d88786180ac185df813a68d4de@3.209.45.79:30303".parse().unwrap()
574            ]
575        );
576    }
577
578    #[test]
579    fn parse_retry_strategy_args() {
580        let tests = vec![0, 10];
581
582        for retries in tests {
583            let args = CommandParser::<NetworkArgs>::parse_from([
584                "reth",
585                "--dns-retries",
586                retries.to_string().as_str(),
587            ])
588            .args;
589
590            assert_eq!(args.dns_retries, retries);
591        }
592    }
593
594    #[cfg(not(feature = "optimism"))]
595    #[test]
596    fn network_args_default_sanity_test() {
597        let default_args = NetworkArgs::default();
598        let args = CommandParser::<NetworkArgs>::parse_from(["reth"]).args;
599
600        assert_eq!(args, default_args);
601    }
602}