reth_node_core/args/
network.rs

1//! clap [Args](clap::Args) for network related arguments.
2
3use 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/// Parameters for configuring the network more granularity via CLI
43#[derive(Debug, Clone, Args, PartialEq, Eq)]
44#[command(next_help_heading = "Networking")]
45pub struct NetworkArgs {
46    /// Arguments to setup discovery service.
47    #[command(flatten)]
48    pub discovery: DiscoveryArgs,
49
50    #[expect(clippy::doc_markdown)]
51    /// Comma separated enode URLs of trusted peers for P2P connections.
52    ///
53    /// --trusted-peers enode://abcd@192.168.0.1:30303
54    #[arg(long, value_delimiter = ',')]
55    pub trusted_peers: Vec<TrustedPeer>,
56
57    /// Connect to or accept from trusted peers only
58    #[arg(long)]
59    pub trusted_only: bool,
60
61    /// Comma separated enode URLs for P2P discovery bootstrap.
62    ///
63    /// Will fall back to a network-specific default if not specified.
64    #[arg(long, value_delimiter = ',')]
65    pub bootnodes: Option<Vec<TrustedPeer>>,
66
67    /// Amount of DNS resolution requests retries to perform when peering.
68    #[arg(long, default_value_t = 0)]
69    pub dns_retries: usize,
70
71    /// The path to the known peers file. Connected peers are dumped to this file on nodes
72    /// shutdown, and read on startup. Cannot be used with `--no-persist-peers`.
73    #[arg(long, value_name = "FILE", verbatim_doc_comment, conflicts_with = "no_persist_peers")]
74    pub peers_file: Option<PathBuf>,
75
76    /// Custom node identity
77    #[arg(long, value_name = "IDENTITY", default_value = version_metadata().p2p_client_version.as_ref())]
78    pub identity: String,
79
80    /// Secret key to use for this node.
81    ///
82    /// This will also deterministically set the peer ID. If not specified, it will be set in the
83    /// data dir for the chain being used.
84    #[arg(long, value_name = "PATH")]
85    pub p2p_secret_key: Option<PathBuf>,
86
87    /// Do not persist peers.
88    #[arg(long, verbatim_doc_comment)]
89    pub no_persist_peers: bool,
90
91    /// NAT resolution method (any|none|upnp|publicip|extip:\<IP\>)
92    #[arg(long, default_value = "any")]
93    pub nat: NatResolver,
94
95    /// Network listening address
96    #[arg(long = "addr", value_name = "ADDR", default_value_t = DEFAULT_DISCOVERY_ADDR)]
97    pub addr: IpAddr,
98
99    /// Network listening port
100    #[arg(long = "port", value_name = "PORT", default_value_t = DEFAULT_DISCOVERY_PORT)]
101    pub port: u16,
102
103    /// Maximum number of outbound requests. default: 100
104    #[arg(long)]
105    pub max_outbound_peers: Option<usize>,
106
107    /// Maximum number of inbound requests. default: 30
108    #[arg(long)]
109    pub max_inbound_peers: Option<usize>,
110
111    /// Max concurrent `GetPooledTransactions` requests.
112    #[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    /// Max concurrent `GetPooledTransactions` requests per peer.
116    #[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    /// Max number of seen transactions to remember per peer.
120    ///
121    /// Default is 320 transaction hashes.
122    #[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    /// Max number of transactions to import concurrently.
127    pub max_pending_pool_imports: usize,
128
129    /// Experimental, for usage in research. Sets the max accumulated byte size of transactions
130    /// to pack in one response.
131    /// Spec'd at 2MiB.
132    #[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    /// Experimental, for usage in research. Sets the max accumulated byte size of transactions to
136    /// request in one request.
137    ///
138    /// Since `RLPx` protocol version 68, the byte size of a transaction is shared as metadata in a
139    /// transaction announcement (see `RLPx` specs). This allows a node to request a specific size
140    /// response.
141    ///
142    /// By default, nodes request only 128 KiB worth of transactions, but should a peer request
143    /// more, up to 2 MiB, a node will answer with more than 128 KiB.
144    ///
145    /// Default is 128 KiB.
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)]
147    pub soft_limit_byte_size_pooled_transactions_response_on_pack_request: usize,
148
149    /// Max capacity of cache of hashes for transactions pending fetch.
150    #[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    /// Name of network interface used to communicate with peers.
154    ///
155    /// If flag is set, but no value is passed, the default interface for docker `eth0` is tried.
156    #[arg(long = "net-if.experimental", conflicts_with = "addr", value_name = "IF_NAME")]
157    pub net_if: Option<String>,
158
159    /// Transaction Propagation Policy
160    ///
161    /// The policy determines which peers transactions are gossiped to.
162    #[arg(long = "tx-propagation-policy", default_value_t = TransactionPropagationKind::All)]
163    pub tx_propagation_policy: TransactionPropagationKind,
164
165    /// Disable transaction pool gossip
166    ///
167    /// Disables gossiping of transactions in the mempool to peers. This can be omitted for
168    /// personal nodes, though providers should always opt to enable this flag.
169    #[arg(long = "disable-tx-gossip")]
170    pub disable_tx_gossip: bool,
171
172    /// Sets the transaction propagation mode by determining how new pending transactions are
173    /// propagated to other peers in full.
174    ///
175    /// Examples: sqrt, all, max:10
176    #[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    /// Comma separated list of required block hashes.
184    /// Peers that don't have these blocks will be filtered out.
185    #[arg(long = "required-block-hashes", value_delimiter = ',')]
186    pub required_block_hashes: Vec<B256>,
187
188    /// Optional network ID to override the chain specification's network ID for P2P connections
189    #[arg(long)]
190    pub network_id: Option<u64>,
191}
192
193impl NetworkArgs {
194    /// Returns the resolved IP address.
195    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    /// Returns the resolved bootnodes if any are provided.
216    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    /// Configures and returns a `TransactionsManagerConfig` based on the current settings.
222    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    /// Build a [`NetworkConfigBuilder`] from a [`Config`] and a [`EthChainSpec`], in addition to
237    /// the values in this option struct.
238    ///
239    /// The `default_peers_file` will be used as the default location to store the persistent peers
240    /// file if `no_persist_peers` is false, and there is no provided `peers_file`.
241    ///
242    /// Configured Bootnodes are prioritized, if unset, the chain spec bootnodes are used
243    /// Priority order for bootnodes configuration:
244    /// 1. --bootnodes flag
245    /// 2. Network preset flags (e.g. --holesky)
246    /// 3. default to mainnet nodes
247    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        // Configure peer connections
261        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        // Configure basic network stack
268        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            // Configure node identity
280            .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 discovery settings
289            .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, // set discovery port based on instance number
295                self.port,
296            ))
297            .discovery_addr(SocketAddr::new(
298                self.discovery.addr,
299                // set discovery port based on instance number
300                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    /// If `no_persist_peers` is false then this returns the path to the persistent peers file path.
308    pub fn persistent_peers_file(&self, peers_file: PathBuf) -> Option<PathBuf> {
309        self.no_persist_peers.not().then_some(peers_file)
310    }
311
312    /// Sets the p2p port to zero, to allow the OS to assign a random unused port when
313    /// the network components bind to a socket.
314    pub const fn with_unused_p2p_port(mut self) -> Self {
315        self.port = 0;
316        self
317    }
318
319    /// Sets the p2p and discovery ports to zero, allowing the OD to assign a random unused port
320    /// when network components bind to sockets.
321    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    /// Change networking port numbers based on the instance number, if provided.
328    /// Ports are updated to `previous_value + instance - 1`
329    ///
330    /// # Panics
331    /// Warning: if `instance` is zero in debug mode, this will panic.
332    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    /// Resolve all trusted peers at once
341    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/// Arguments to setup discovery
385#[derive(Debug, Clone, Args, PartialEq, Eq)]
386pub struct DiscoveryArgs {
387    /// Disable the discovery service.
388    #[arg(short, long, default_value_if("dev", "true", "true"))]
389    pub disable_discovery: bool,
390
391    /// Disable the DNS discovery.
392    #[arg(long, conflicts_with = "disable_discovery")]
393    pub disable_dns_discovery: bool,
394
395    /// Disable Discv4 discovery.
396    #[arg(long, conflicts_with = "disable_discovery")]
397    pub disable_discv4_discovery: bool,
398
399    /// Enable Discv5 discovery.
400    #[arg(long, conflicts_with = "disable_discovery")]
401    pub enable_discv5_discovery: bool,
402
403    /// Disable Nat discovery.
404    #[arg(long, conflicts_with = "disable_discovery")]
405    pub disable_nat: bool,
406
407    /// The UDP address to use for devp2p peer discovery version 4.
408    #[arg(id = "discovery.addr", long = "discovery.addr", value_name = "DISCOVERY_ADDR", default_value_t = DEFAULT_DISCOVERY_ADDR)]
409    pub addr: IpAddr,
410
411    /// The UDP port to use for devp2p peer discovery version 4.
412    #[arg(id = "discovery.port", long = "discovery.port", value_name = "DISCOVERY_PORT", default_value_t = DEFAULT_DISCOVERY_PORT)]
413    pub port: u16,
414
415    /// The UDP IPv4 address to use for devp2p peer discovery version 5. Overwritten by `RLPx`
416    /// address, if it's also IPv4.
417    #[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    /// The UDP IPv6 address to use for devp2p peer discovery version 5. Overwritten by `RLPx`
421    /// address, if it's also IPv6.
422    #[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    /// The UDP IPv4 port to use for devp2p peer discovery version 5. Not used unless `--addr` is
426    /// IPv4, or `--discovery.v5.addr` is set.
427    #[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    /// The UDP IPv6 port to use for devp2p peer discovery version 5. Not used unless `--addr` is
432    /// IPv6, or `--discovery.addr.ipv6` is set.
433    #[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    /// The interval in seconds at which to carry out periodic lookup queries, for the whole
438    /// run of the program.
439    #[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    /// The interval in seconds at which to carry out boost lookup queries, for a fixed number of
443    /// times, at bootstrap.
444    #[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    /// The number of times to carry out boost lookup queries at bootstrap.
449    #[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    /// Apply the discovery settings to the given [`NetworkConfigBuilder`]
456    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            // 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>
475            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    /// Creates a [`reth_discv5::ConfigBuilder`] filling it with the values from this struct.
487    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        // Use rlpx address if none given
504        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    /// Returns true if discv5 discovery should be configured
528    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    /// Set the discovery port to zero, to allow the OS to assign a random unused port when
539    /// discovery binds to the socket.
540    pub const fn with_unused_discovery_port(mut self) -> Self {
541        self.port = 0;
542        self
543    }
544
545    /// Change networking port numbers based on the instance number.
546    /// Ports are updated to `previous_value + instance - 1`
547    ///
548    /// # Panics
549    /// Warning: if `instance` is zero in debug mode, this will panic.
550    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    /// A helper type to parse Args more easily
584    #[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}