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 crate::version::version_metadata;
10use clap::Args;
11use reth_chainspec::EthChainSpec;
12use reth_config::Config;
13use reth_discv4::{NodeRecord, DEFAULT_DISCOVERY_ADDR, DEFAULT_DISCOVERY_PORT};
14use reth_discv5::{
15    discv5::ListenConfig, DEFAULT_COUNT_BOOTSTRAP_LOOKUPS, DEFAULT_DISCOVERY_V5_PORT,
16    DEFAULT_SECONDS_BOOTSTRAP_LOOKUP_INTERVAL, DEFAULT_SECONDS_LOOKUP_INTERVAL,
17};
18use reth_net_nat::{NatResolver, DEFAULT_NET_IF_NAME};
19use reth_network::{
20    transactions::{
21        config::TransactionPropagationKind,
22        constants::{
23            tx_fetcher::{
24                DEFAULT_MAX_CAPACITY_CACHE_PENDING_FETCH, DEFAULT_MAX_COUNT_CONCURRENT_REQUESTS,
25                DEFAULT_MAX_COUNT_CONCURRENT_REQUESTS_PER_PEER,
26            },
27            tx_manager::{
28                DEFAULT_MAX_COUNT_PENDING_POOL_IMPORTS, DEFAULT_MAX_COUNT_TRANSACTIONS_SEEN_BY_PEER,
29            },
30        },
31        TransactionFetcherConfig, TransactionPropagationMode, TransactionsManagerConfig,
32        DEFAULT_SOFT_LIMIT_BYTE_SIZE_POOLED_TRANSACTIONS_RESP_ON_PACK_GET_POOLED_TRANSACTIONS_REQ,
33        SOFT_LIMIT_BYTE_SIZE_POOLED_TRANSACTIONS_RESPONSE,
34    },
35    HelloMessageWithProtocols, NetworkConfigBuilder, NetworkPrimitives, SessionsConfig,
36};
37use reth_network_peers::{mainnet_nodes, TrustedPeer};
38use secp256k1::SecretKey;
39use tracing::error;
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    #[expect(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 = version_metadata().p2p_client_version.as_ref())]
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    /// Transaction Propagation Policy
159    ///
160    /// The policy determines which peers transactions are gossiped to.
161    #[arg(long = "tx-propagation-policy", default_value_t = TransactionPropagationKind::All)]
162    pub tx_propagation_policy: TransactionPropagationKind,
163
164    /// Disable transaction pool gossip
165    ///
166    /// Disables gossiping of transactions in the mempool to peers. This can be omitted for
167    /// personal nodes, though providers should always opt to enable this flag.
168    #[arg(long = "disable-tx-gossip")]
169    pub disable_tx_gossip: bool,
170
171    /// Sets the transaction propagation mode by determining how new pending transactions are
172    /// propagated to other peers in full.
173    ///
174    /// Examples: sqrt, all, max:10
175    #[arg(
176        long = "tx-propagation-mode",
177        default_value = "sqrt",
178        help = "Transaction propagation mode (sqrt, all, max:<number>)"
179    )]
180    pub propagation_mode: TransactionPropagationMode,
181}
182
183impl NetworkArgs {
184    /// Returns the resolved IP address.
185    pub fn resolved_addr(&self) -> IpAddr {
186        if let Some(ref if_name) = self.net_if {
187            let if_name = if if_name.is_empty() { DEFAULT_NET_IF_NAME } else { if_name };
188            return match reth_net_nat::net_if::resolve_net_if_ip(if_name) {
189                Ok(addr) => addr,
190                Err(err) => {
191                    error!(target: "reth::cli",
192                        if_name,
193                        %err,
194                        "Failed to read network interface IP"
195                    );
196
197                    DEFAULT_DISCOVERY_ADDR
198                }
199            };
200        }
201
202        self.addr
203    }
204
205    /// Returns the resolved bootnodes if any are provided.
206    pub fn resolved_bootnodes(&self) -> Option<Vec<NodeRecord>> {
207        self.bootnodes.clone().map(|bootnodes| {
208            bootnodes.into_iter().filter_map(|node| node.resolve_blocking().ok()).collect()
209        })
210    }
211    /// Configures and returns a `TransactionsManagerConfig` based on the current settings.
212    pub const fn transactions_manager_config(&self) -> TransactionsManagerConfig {
213        TransactionsManagerConfig {
214            transaction_fetcher_config: TransactionFetcherConfig::new(
215                self.max_concurrent_tx_requests,
216                self.max_concurrent_tx_requests_per_peer,
217                self.soft_limit_byte_size_pooled_transactions_response,
218                self.soft_limit_byte_size_pooled_transactions_response_on_pack_request,
219                self.max_capacity_cache_txns_pending_fetch,
220            ),
221            max_transactions_seen_by_peer_history: self.max_seen_tx_history,
222            propagation_mode: self.propagation_mode,
223        }
224    }
225
226    /// Build a [`NetworkConfigBuilder`] from a [`Config`] and a [`EthChainSpec`], in addition to
227    /// the values in this option struct.
228    ///
229    /// The `default_peers_file` will be used as the default location to store the persistent peers
230    /// file if `no_persist_peers` is false, and there is no provided `peers_file`.
231    ///
232    /// Configured Bootnodes are prioritized, if unset, the chain spec bootnodes are used
233    /// Priority order for bootnodes configuration:
234    /// 1. --bootnodes flag
235    /// 2. Network preset flags (e.g. --holesky)
236    /// 3. default to mainnet nodes
237    pub fn network_config<N: NetworkPrimitives>(
238        &self,
239        config: &Config,
240        chain_spec: impl EthChainSpec,
241        secret_key: SecretKey,
242        default_peers_file: PathBuf,
243    ) -> NetworkConfigBuilder<N> {
244        let addr = self.resolved_addr();
245        let chain_bootnodes = self
246            .resolved_bootnodes()
247            .unwrap_or_else(|| chain_spec.bootnodes().unwrap_or_else(mainnet_nodes));
248        let peers_file = self.peers_file.clone().unwrap_or(default_peers_file);
249
250        // Configure peer connections
251        let peers_config = config
252            .peers
253            .clone()
254            .with_max_inbound_opt(self.max_inbound_peers)
255            .with_max_outbound_opt(self.max_outbound_peers);
256
257        // Configure basic network stack
258        NetworkConfigBuilder::<N>::new(secret_key)
259            .peer_config(config.peers_config_with_basic_nodes_from_file(
260                self.persistent_peers_file(peers_file).as_deref(),
261            ))
262            .external_ip_resolver(self.nat)
263            .sessions_config(
264                SessionsConfig::default().with_upscaled_event_buffer(peers_config.max_peers()),
265            )
266            .peer_config(peers_config)
267            .boot_nodes(chain_bootnodes.clone())
268            .transactions_manager_config(self.transactions_manager_config())
269            // Configure node identity
270            .apply(|builder| {
271                let peer_id = builder.get_peer_id();
272                builder.hello_message(
273                    HelloMessageWithProtocols::builder(peer_id)
274                        .client_version(&self.identity)
275                        .build(),
276                )
277            })
278            // apply discovery settings
279            .apply(|builder| {
280                let rlpx_socket = (addr, self.port).into();
281                self.discovery.apply_to_builder(builder, rlpx_socket, chain_bootnodes)
282            })
283            .listener_addr(SocketAddr::new(
284                addr, // set discovery port based on instance number
285                self.port,
286            ))
287            .discovery_addr(SocketAddr::new(
288                self.discovery.addr,
289                // set discovery port based on instance number
290                self.discovery.port,
291            ))
292            .disable_tx_gossip(self.disable_tx_gossip)
293    }
294
295    /// If `no_persist_peers` is false then this returns the path to the persistent peers file path.
296    pub fn persistent_peers_file(&self, peers_file: PathBuf) -> Option<PathBuf> {
297        self.no_persist_peers.not().then_some(peers_file)
298    }
299
300    /// Sets the p2p port to zero, to allow the OS to assign a random unused port when
301    /// the network components bind to a socket.
302    pub const fn with_unused_p2p_port(mut self) -> Self {
303        self.port = 0;
304        self
305    }
306
307    /// Sets the p2p and discovery ports to zero, allowing the OD to assign a random unused port
308    /// when network components bind to sockets.
309    pub const fn with_unused_ports(mut self) -> Self {
310        self = self.with_unused_p2p_port();
311        self.discovery = self.discovery.with_unused_discovery_port();
312        self
313    }
314
315    /// Change networking port numbers based on the instance number, if provided.
316    /// Ports are updated to `previous_value + instance - 1`
317    ///
318    /// # Panics
319    /// Warning: if `instance` is zero in debug mode, this will panic.
320    pub fn adjust_instance_ports(&mut self, instance: Option<u16>) {
321        if let Some(instance) = instance {
322            debug_assert_ne!(instance, 0, "instance must be non-zero");
323            self.port += instance - 1;
324            self.discovery.adjust_instance_ports(instance);
325        }
326    }
327
328    /// Resolve all trusted peers at once
329    pub async fn resolve_trusted_peers(&self) -> Result<Vec<NodeRecord>, std::io::Error> {
330        futures::future::try_join_all(
331            self.trusted_peers.iter().map(|peer| async move { peer.resolve().await }),
332        )
333        .await
334    }
335}
336
337impl Default for NetworkArgs {
338    fn default() -> Self {
339        Self {
340            discovery: DiscoveryArgs::default(),
341            trusted_peers: vec![],
342            trusted_only: false,
343            bootnodes: None,
344            dns_retries: 0,
345            peers_file: None,
346            identity: version_metadata().p2p_client_version.to_string(),
347            p2p_secret_key: None,
348            no_persist_peers: false,
349            nat: NatResolver::Any,
350            addr: DEFAULT_DISCOVERY_ADDR,
351            port: DEFAULT_DISCOVERY_PORT,
352            max_outbound_peers: None,
353            max_inbound_peers: None,
354            max_concurrent_tx_requests: DEFAULT_MAX_COUNT_CONCURRENT_REQUESTS,
355            max_concurrent_tx_requests_per_peer: DEFAULT_MAX_COUNT_CONCURRENT_REQUESTS_PER_PEER,
356            soft_limit_byte_size_pooled_transactions_response:
357                SOFT_LIMIT_BYTE_SIZE_POOLED_TRANSACTIONS_RESPONSE,
358            soft_limit_byte_size_pooled_transactions_response_on_pack_request: DEFAULT_SOFT_LIMIT_BYTE_SIZE_POOLED_TRANSACTIONS_RESP_ON_PACK_GET_POOLED_TRANSACTIONS_REQ,
359            max_pending_pool_imports: DEFAULT_MAX_COUNT_PENDING_POOL_IMPORTS,
360            max_seen_tx_history: DEFAULT_MAX_COUNT_TRANSACTIONS_SEEN_BY_PEER,
361            max_capacity_cache_txns_pending_fetch: DEFAULT_MAX_CAPACITY_CACHE_PENDING_FETCH,
362            net_if: None,
363            tx_propagation_policy: TransactionPropagationKind::default(),
364            disable_tx_gossip: false,
365            propagation_mode: TransactionPropagationMode::Sqrt,
366        }
367    }
368}
369
370/// Arguments to setup discovery
371#[derive(Debug, Clone, Args, PartialEq, Eq)]
372pub struct DiscoveryArgs {
373    /// Disable the discovery service.
374    #[arg(short, long, default_value_if("dev", "true", "true"))]
375    pub disable_discovery: bool,
376
377    /// Disable the DNS discovery.
378    #[arg(long, conflicts_with = "disable_discovery")]
379    pub disable_dns_discovery: bool,
380
381    /// Disable Discv4 discovery.
382    #[arg(long, conflicts_with = "disable_discovery")]
383    pub disable_discv4_discovery: bool,
384
385    /// Enable Discv5 discovery.
386    #[arg(long, conflicts_with = "disable_discovery")]
387    pub enable_discv5_discovery: bool,
388
389    /// Disable Nat discovery.
390    #[arg(long, conflicts_with = "disable_discovery")]
391    pub disable_nat: bool,
392
393    /// The UDP address to use for devp2p peer discovery version 4.
394    #[arg(id = "discovery.addr", long = "discovery.addr", value_name = "DISCOVERY_ADDR", default_value_t = DEFAULT_DISCOVERY_ADDR)]
395    pub addr: IpAddr,
396
397    /// The UDP port to use for devp2p peer discovery version 4.
398    #[arg(id = "discovery.port", long = "discovery.port", value_name = "DISCOVERY_PORT", default_value_t = DEFAULT_DISCOVERY_PORT)]
399    pub port: u16,
400
401    /// The UDP IPv4 address to use for devp2p peer discovery version 5. Overwritten by `RLPx`
402    /// address, if it's also IPv4.
403    #[arg(id = "discovery.v5.addr", long = "discovery.v5.addr", value_name = "DISCOVERY_V5_ADDR", default_value = None)]
404    pub discv5_addr: Option<Ipv4Addr>,
405
406    /// The UDP IPv6 address to use for devp2p peer discovery version 5. Overwritten by `RLPx`
407    /// address, if it's also IPv6.
408    #[arg(id = "discovery.v5.addr.ipv6", long = "discovery.v5.addr.ipv6", value_name = "DISCOVERY_V5_ADDR_IPV6", default_value = None)]
409    pub discv5_addr_ipv6: Option<Ipv6Addr>,
410
411    /// The UDP IPv4 port to use for devp2p peer discovery version 5. Not used unless `--addr` is
412    /// IPv4, or `--discovery.v5.addr` is set.
413    #[arg(id = "discovery.v5.port", long = "discovery.v5.port", value_name = "DISCOVERY_V5_PORT",
414    default_value_t = DEFAULT_DISCOVERY_V5_PORT)]
415    pub discv5_port: u16,
416
417    /// The UDP IPv6 port to use for devp2p peer discovery version 5. Not used unless `--addr` is
418    /// IPv6, or `--discovery.addr.ipv6` is set.
419    #[arg(id = "discovery.v5.port.ipv6", long = "discovery.v5.port.ipv6", value_name = "DISCOVERY_V5_PORT_IPV6",
420    default_value = None, default_value_t = DEFAULT_DISCOVERY_V5_PORT)]
421    pub discv5_port_ipv6: u16,
422
423    /// The interval in seconds at which to carry out periodic lookup queries, for the whole
424    /// run of the program.
425    #[arg(id = "discovery.v5.lookup-interval", long = "discovery.v5.lookup-interval", value_name = "DISCOVERY_V5_LOOKUP_INTERVAL", default_value_t = DEFAULT_SECONDS_LOOKUP_INTERVAL)]
426    pub discv5_lookup_interval: u64,
427
428    /// The interval in seconds at which to carry out boost lookup queries, for a fixed number of
429    /// times, at bootstrap.
430    #[arg(id = "discovery.v5.bootstrap.lookup-interval", long = "discovery.v5.bootstrap.lookup-interval", value_name = "DISCOVERY_V5_BOOTSTRAP_LOOKUP_INTERVAL",
431        default_value_t = DEFAULT_SECONDS_BOOTSTRAP_LOOKUP_INTERVAL)]
432    pub discv5_bootstrap_lookup_interval: u64,
433
434    /// The number of times to carry out boost lookup queries at bootstrap.
435    #[arg(id = "discovery.v5.bootstrap.lookup-countdown", long = "discovery.v5.bootstrap.lookup-countdown", value_name = "DISCOVERY_V5_BOOTSTRAP_LOOKUP_COUNTDOWN",
436        default_value_t = DEFAULT_COUNT_BOOTSTRAP_LOOKUPS)]
437    pub discv5_bootstrap_lookup_countdown: u64,
438}
439
440impl DiscoveryArgs {
441    /// Apply the discovery settings to the given [`NetworkConfigBuilder`]
442    pub fn apply_to_builder<N>(
443        &self,
444        mut network_config_builder: NetworkConfigBuilder<N>,
445        rlpx_tcp_socket: SocketAddr,
446        boot_nodes: impl IntoIterator<Item = NodeRecord>,
447    ) -> NetworkConfigBuilder<N>
448    where
449        N: NetworkPrimitives,
450    {
451        if self.disable_discovery || self.disable_dns_discovery {
452            network_config_builder = network_config_builder.disable_dns_discovery();
453        }
454
455        if self.disable_discovery || self.disable_discv4_discovery {
456            network_config_builder = network_config_builder.disable_discv4_discovery();
457        }
458
459        if self.disable_nat {
460            // we only check for `disable-nat` here and not for disable discovery because nat:extip can be used without discovery: <https://github.com/paradigmxyz/reth/issues/14878>
461            network_config_builder = network_config_builder.disable_nat();
462        }
463
464        if self.should_enable_discv5() {
465            network_config_builder = network_config_builder
466                .discovery_v5(self.discovery_v5_builder(rlpx_tcp_socket, boot_nodes));
467        }
468
469        network_config_builder
470    }
471
472    /// Creates a [`reth_discv5::ConfigBuilder`] filling it with the values from this struct.
473    pub fn discovery_v5_builder(
474        &self,
475        rlpx_tcp_socket: SocketAddr,
476        boot_nodes: impl IntoIterator<Item = NodeRecord>,
477    ) -> reth_discv5::ConfigBuilder {
478        let Self {
479            discv5_addr,
480            discv5_addr_ipv6,
481            discv5_port,
482            discv5_port_ipv6,
483            discv5_lookup_interval,
484            discv5_bootstrap_lookup_interval,
485            discv5_bootstrap_lookup_countdown,
486            ..
487        } = self;
488
489        // Use rlpx address if none given
490        let discv5_addr_ipv4 = discv5_addr.or(match rlpx_tcp_socket {
491            SocketAddr::V4(addr) => Some(*addr.ip()),
492            SocketAddr::V6(_) => None,
493        });
494        let discv5_addr_ipv6 = discv5_addr_ipv6.or(match rlpx_tcp_socket {
495            SocketAddr::V4(_) => None,
496            SocketAddr::V6(addr) => Some(*addr.ip()),
497        });
498
499        reth_discv5::Config::builder(rlpx_tcp_socket)
500            .discv5_config(
501                reth_discv5::discv5::ConfigBuilder::new(ListenConfig::from_two_sockets(
502                    discv5_addr_ipv4.map(|addr| SocketAddrV4::new(addr, *discv5_port)),
503                    discv5_addr_ipv6.map(|addr| SocketAddrV6::new(addr, *discv5_port_ipv6, 0, 0)),
504                ))
505                .build(),
506            )
507            .add_unsigned_boot_nodes(boot_nodes)
508            .lookup_interval(*discv5_lookup_interval)
509            .bootstrap_lookup_interval(*discv5_bootstrap_lookup_interval)
510            .bootstrap_lookup_countdown(*discv5_bootstrap_lookup_countdown)
511    }
512
513    /// Returns true if discv5 discovery should be configured
514    const fn should_enable_discv5(&self) -> bool {
515        if self.disable_discovery {
516            return false;
517        }
518
519        self.enable_discv5_discovery ||
520            self.discv5_addr.is_some() ||
521            self.discv5_addr_ipv6.is_some()
522    }
523
524    /// Set the discovery port to zero, to allow the OS to assign a random unused port when
525    /// discovery binds to the socket.
526    pub const fn with_unused_discovery_port(mut self) -> Self {
527        self.port = 0;
528        self
529    }
530
531    /// Change networking port numbers based on the instance number.
532    /// Ports are updated to `previous_value + instance - 1`
533    ///
534    /// # Panics
535    /// Warning: if `instance` is zero in debug mode, this will panic.
536    pub fn adjust_instance_ports(&mut self, instance: u16) {
537        debug_assert_ne!(instance, 0, "instance must be non-zero");
538        self.port += instance - 1;
539        self.discv5_port += instance - 1;
540        self.discv5_port_ipv6 += instance - 1;
541    }
542}
543
544impl Default for DiscoveryArgs {
545    fn default() -> Self {
546        Self {
547            disable_discovery: false,
548            disable_dns_discovery: false,
549            disable_discv4_discovery: false,
550            enable_discv5_discovery: false,
551            disable_nat: false,
552            addr: DEFAULT_DISCOVERY_ADDR,
553            port: DEFAULT_DISCOVERY_PORT,
554            discv5_addr: None,
555            discv5_addr_ipv6: None,
556            discv5_port: DEFAULT_DISCOVERY_V5_PORT,
557            discv5_port_ipv6: DEFAULT_DISCOVERY_V5_PORT,
558            discv5_lookup_interval: DEFAULT_SECONDS_LOOKUP_INTERVAL,
559            discv5_bootstrap_lookup_interval: DEFAULT_SECONDS_BOOTSTRAP_LOOKUP_INTERVAL,
560            discv5_bootstrap_lookup_countdown: DEFAULT_COUNT_BOOTSTRAP_LOOKUPS,
561        }
562    }
563}
564
565#[cfg(test)]
566mod tests {
567    use super::*;
568    use clap::Parser;
569    /// A helper type to parse Args more easily
570    #[derive(Parser)]
571    struct CommandParser<T: Args> {
572        #[command(flatten)]
573        args: T,
574    }
575
576    #[test]
577    fn parse_nat_args() {
578        let args = CommandParser::<NetworkArgs>::parse_from(["reth", "--nat", "none"]).args;
579        assert_eq!(args.nat, NatResolver::None);
580
581        let args =
582            CommandParser::<NetworkArgs>::parse_from(["reth", "--nat", "extip:0.0.0.0"]).args;
583        assert_eq!(args.nat, NatResolver::ExternalIp("0.0.0.0".parse().unwrap()));
584    }
585
586    #[test]
587    fn parse_peer_args() {
588        let args =
589            CommandParser::<NetworkArgs>::parse_from(["reth", "--max-outbound-peers", "50"]).args;
590        assert_eq!(args.max_outbound_peers, Some(50));
591        assert_eq!(args.max_inbound_peers, None);
592
593        let args = CommandParser::<NetworkArgs>::parse_from([
594            "reth",
595            "--max-outbound-peers",
596            "75",
597            "--max-inbound-peers",
598            "15",
599        ])
600        .args;
601        assert_eq!(args.max_outbound_peers, Some(75));
602        assert_eq!(args.max_inbound_peers, Some(15));
603    }
604
605    #[test]
606    fn parse_trusted_peer_args() {
607        let args =
608            CommandParser::<NetworkArgs>::parse_from([
609            "reth",
610            "--trusted-peers",
611            "enode://d860a01f9722d78051619d1e2351aba3f43f943f6f00718d1b9baa4101932a1f5011f16bb2b1bb35db20d6fe28fa0bf09636d26a87d31de9ec6203eeedb1f666@18.138.108.67:30303,enode://22a8232c3abc76a16ae9d6c3b164f98775fe226f0917b0ca871128a74a8e9630b458460865bab457221f1d448dd9791d24c4e5d88786180ac185df813a68d4de@3.209.45.79:30303"
612        ])
613        .args;
614
615        assert_eq!(
616            args.trusted_peers,
617            vec![
618            "enode://d860a01f9722d78051619d1e2351aba3f43f943f6f00718d1b9baa4101932a1f5011f16bb2b1bb35db20d6fe28fa0bf09636d26a87d31de9ec6203eeedb1f666@18.138.108.67:30303".parse().unwrap(),
619            "enode://22a8232c3abc76a16ae9d6c3b164f98775fe226f0917b0ca871128a74a8e9630b458460865bab457221f1d448dd9791d24c4e5d88786180ac185df813a68d4de@3.209.45.79:30303".parse().unwrap()
620            ]
621        );
622    }
623
624    #[test]
625    fn parse_retry_strategy_args() {
626        let tests = vec![0, 10];
627
628        for retries in tests {
629            let args = CommandParser::<NetworkArgs>::parse_from([
630                "reth",
631                "--dns-retries",
632                retries.to_string().as_str(),
633            ])
634            .args;
635
636            assert_eq!(args.dns_retries, retries);
637        }
638    }
639
640    #[test]
641    fn parse_disable_tx_gossip_args() {
642        let args = CommandParser::<NetworkArgs>::parse_from(["reth", "--disable-tx-gossip"]).args;
643        assert!(args.disable_tx_gossip);
644    }
645
646    #[test]
647    fn network_args_default_sanity_test() {
648        let default_args = NetworkArgs::default();
649        let args = CommandParser::<NetworkArgs>::parse_from(["reth"]).args;
650
651        assert_eq!(args, default_args);
652    }
653}