Skip to main content

reth_node_core/args/
network.rs

1//! clap [Args](clap::Args) for network related arguments.
2
3use alloy_eips::BlockNumHash;
4use alloy_primitives::B256;
5use std::{
6    net::{IpAddr, Ipv4Addr, Ipv6Addr, SocketAddr, SocketAddrV4, SocketAddrV6},
7    ops::Not,
8    path::PathBuf,
9};
10
11use crate::version::version_metadata;
12use clap::Args;
13use reth_chainspec::EthChainSpec;
14use reth_cli_util::{get_secret_key, load_secret_key::SecretKeyError};
15use reth_config::Config;
16use reth_discv4::{NodeRecord, DEFAULT_DISCOVERY_ADDR, DEFAULT_DISCOVERY_PORT};
17use reth_discv5::{
18    discv5::ListenConfig, DEFAULT_COUNT_BOOTSTRAP_LOOKUPS, DEFAULT_DISCOVERY_V5_PORT,
19    DEFAULT_SECONDS_BOOTSTRAP_LOOKUP_INTERVAL, DEFAULT_SECONDS_LOOKUP_INTERVAL,
20};
21use reth_net_banlist::IpFilter;
22use reth_net_nat::{NatResolver, DEFAULT_NET_IF_NAME};
23use reth_network::{
24    transactions::{
25        config::{TransactionIngressPolicy, TransactionPropagationKind},
26        constants::{
27            tx_fetcher::{
28                DEFAULT_MAX_CAPACITY_CACHE_PENDING_FETCH, DEFAULT_MAX_COUNT_CONCURRENT_REQUESTS,
29                DEFAULT_MAX_COUNT_CONCURRENT_REQUESTS_PER_PEER,
30            },
31            tx_manager::{
32                DEFAULT_MAX_COUNT_PENDING_POOL_IMPORTS, DEFAULT_MAX_COUNT_TRANSACTIONS_SEEN_BY_PEER,
33            },
34        },
35        TransactionFetcherConfig, TransactionPropagationMode, TransactionsManagerConfig,
36        DEFAULT_SOFT_LIMIT_BYTE_SIZE_POOLED_TRANSACTIONS_RESP_ON_PACK_GET_POOLED_TRANSACTIONS_REQ,
37        SOFT_LIMIT_BYTE_SIZE_POOLED_TRANSACTIONS_RESPONSE,
38    },
39    HelloMessageWithProtocols, NetworkConfigBuilder, NetworkPrimitives,
40};
41use reth_network_peers::{mainnet_nodes, TrustedPeer};
42use reth_tasks::Runtime;
43use secp256k1::SecretKey;
44use std::str::FromStr;
45use tracing::error;
46
47/// Parameters for configuring the network more granularity via CLI
48#[derive(Debug, Clone, Args, PartialEq, Eq)]
49#[command(next_help_heading = "Networking")]
50pub struct NetworkArgs {
51    /// Arguments to setup discovery service.
52    #[command(flatten)]
53    pub discovery: DiscoveryArgs,
54
55    #[expect(clippy::doc_markdown)]
56    /// Comma separated enode URLs of trusted peers for P2P connections.
57    ///
58    /// --trusted-peers enode://abcd@192.168.0.1:30303
59    #[arg(long, value_delimiter = ',')]
60    pub trusted_peers: Vec<TrustedPeer>,
61
62    /// Connect to or accept from trusted peers only
63    #[arg(long)]
64    pub trusted_only: bool,
65
66    /// Comma separated enode URLs for P2P discovery bootstrap.
67    ///
68    /// Will fall back to a network-specific default if not specified.
69    #[arg(long, value_delimiter = ',')]
70    pub bootnodes: Option<Vec<TrustedPeer>>,
71
72    /// Amount of DNS resolution requests retries to perform when peering.
73    #[arg(long, default_value_t = 0)]
74    pub dns_retries: usize,
75
76    /// The path to the known peers file. Connected peers are dumped to this file on nodes
77    /// shutdown, and read on startup. Cannot be used with `--no-persist-peers`.
78    #[arg(long, value_name = "FILE", verbatim_doc_comment, conflicts_with = "no_persist_peers")]
79    pub peers_file: Option<PathBuf>,
80
81    /// Custom node identity
82    #[arg(long, value_name = "IDENTITY", default_value = version_metadata().p2p_client_version.as_ref())]
83    pub identity: String,
84
85    /// Secret key to use for this node.
86    ///
87    /// This will also deterministically set the peer ID. If not specified, it will be set in the
88    /// data dir for the chain being used.
89    #[arg(long, value_name = "PATH", conflicts_with = "p2p_secret_key_hex")]
90    pub p2p_secret_key: Option<PathBuf>,
91
92    /// Hex encoded secret key to use for this node.
93    ///
94    /// This will also deterministically set the peer ID. Cannot be used together with
95    /// `--p2p-secret-key`.
96    #[arg(long, value_name = "HEX", conflicts_with = "p2p_secret_key")]
97    pub p2p_secret_key_hex: Option<B256>,
98
99    /// Do not persist peers.
100    #[arg(long, verbatim_doc_comment)]
101    pub no_persist_peers: bool,
102
103    /// NAT resolution method (any|none|upnp|publicip|extip:\<IP\>)
104    #[arg(long, default_value = "any")]
105    pub nat: NatResolver,
106
107    /// Network listening address
108    #[arg(long = "addr", value_name = "ADDR", default_value_t = DEFAULT_DISCOVERY_ADDR)]
109    pub addr: IpAddr,
110
111    /// Network listening port
112    #[arg(long = "port", value_name = "PORT", default_value_t = DEFAULT_DISCOVERY_PORT)]
113    pub port: u16,
114
115    /// Maximum number of outbound peers. default: 100
116    #[arg(long)]
117    pub max_outbound_peers: Option<usize>,
118
119    /// Maximum number of inbound peers. default: 30
120    #[arg(long)]
121    pub max_inbound_peers: Option<usize>,
122
123    /// Maximum number of total peers (inbound + outbound).
124    ///
125    /// Splits peers using approximately 2:1 inbound:outbound ratio. Cannot be used together with
126    /// `--max-outbound-peers` or `--max-inbound-peers`.
127    #[arg(
128        long,
129        value_name = "COUNT",
130        conflicts_with = "max_outbound_peers",
131        conflicts_with = "max_inbound_peers"
132    )]
133    pub max_peers: Option<usize>,
134
135    /// Max concurrent `GetPooledTransactions` requests.
136    #[arg(long = "max-tx-reqs", value_name = "COUNT", default_value_t = DEFAULT_MAX_COUNT_CONCURRENT_REQUESTS, verbatim_doc_comment)]
137    pub max_concurrent_tx_requests: u32,
138
139    /// Max concurrent `GetPooledTransactions` requests per peer.
140    #[arg(long = "max-tx-reqs-peer", value_name = "COUNT", default_value_t = DEFAULT_MAX_COUNT_CONCURRENT_REQUESTS_PER_PEER, verbatim_doc_comment)]
141    pub max_concurrent_tx_requests_per_peer: u8,
142
143    /// Max number of seen transactions to remember per peer.
144    ///
145    /// Default is 320 transaction hashes.
146    #[arg(long = "max-seen-tx-history", value_name = "COUNT", default_value_t = DEFAULT_MAX_COUNT_TRANSACTIONS_SEEN_BY_PEER, verbatim_doc_comment)]
147    pub max_seen_tx_history: u32,
148
149    #[arg(long = "max-pending-imports", value_name = "COUNT", default_value_t = DEFAULT_MAX_COUNT_PENDING_POOL_IMPORTS, verbatim_doc_comment)]
150    /// Max number of transactions to import concurrently.
151    pub max_pending_pool_imports: usize,
152
153    /// Experimental, for usage in research. Sets the max accumulated byte size of transactions
154    /// to pack in one response.
155    /// Spec'd at 2MiB.
156    #[arg(long = "pooled-tx-response-soft-limit", value_name = "BYTES", default_value_t = SOFT_LIMIT_BYTE_SIZE_POOLED_TRANSACTIONS_RESPONSE, verbatim_doc_comment)]
157    pub soft_limit_byte_size_pooled_transactions_response: usize,
158
159    /// Experimental, for usage in research. Sets the max accumulated byte size of transactions to
160    /// request in one request.
161    ///
162    /// Since `RLPx` protocol version 68, the byte size of a transaction is shared as metadata in a
163    /// transaction announcement (see `RLPx` specs). This allows a node to request a specific size
164    /// response.
165    ///
166    /// By default, nodes request only 128 KiB worth of transactions, but should a peer request
167    /// more, up to 2 MiB, a node will answer with more than 128 KiB.
168    ///
169    /// Default is 128 KiB.
170    #[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)]
171    pub soft_limit_byte_size_pooled_transactions_response_on_pack_request: usize,
172
173    /// Max capacity of cache of hashes for transactions pending fetch.
174    #[arg(long = "max-tx-pending-fetch", value_name = "COUNT", default_value_t = DEFAULT_MAX_CAPACITY_CACHE_PENDING_FETCH, verbatim_doc_comment)]
175    pub max_capacity_cache_txns_pending_fetch: u32,
176
177    /// Name of network interface used to communicate with peers.
178    ///
179    /// If flag is set, but no value is passed, the default interface for docker `eth0` is tried.
180    #[arg(long = "net-if.experimental", conflicts_with = "addr", value_name = "IF_NAME")]
181    pub net_if: Option<String>,
182
183    /// Transaction Propagation Policy
184    ///
185    /// The policy determines which peers transactions are gossiped to.
186    #[arg(long = "tx-propagation-policy", default_value_t = TransactionPropagationKind::All)]
187    pub tx_propagation_policy: TransactionPropagationKind,
188
189    /// Transaction ingress policy
190    ///
191    /// Determines which peers' transactions are accepted over P2P.
192    #[arg(long = "tx-ingress-policy", default_value_t = TransactionIngressPolicy::All)]
193    pub tx_ingress_policy: TransactionIngressPolicy,
194
195    /// Disable transaction pool gossip
196    ///
197    /// Disables gossiping of transactions in the mempool to peers. This can be omitted for
198    /// personal nodes, though providers should always opt to enable this flag.
199    #[arg(long = "disable-tx-gossip")]
200    pub disable_tx_gossip: bool,
201
202    /// Sets the transaction propagation mode by determining how new pending transactions are
203    /// propagated to other peers in full.
204    ///
205    /// Examples: sqrt, all, max:10
206    #[arg(
207        long = "tx-propagation-mode",
208        default_value = "sqrt",
209        help = "Transaction propagation mode (sqrt, all, max:<number>)"
210    )]
211    pub propagation_mode: TransactionPropagationMode,
212
213    /// Comma separated list of required block hashes or block number=hash pairs.
214    /// Peers that don't have these blocks will be filtered out.
215    /// Format: hash or `block_number=hash` (e.g., 23115201=0x1234...)
216    #[arg(long = "required-block-hashes", value_delimiter = ',', value_parser = parse_block_num_hash)]
217    pub required_block_hashes: Vec<BlockNumHash>,
218
219    /// Optional network ID to override the chain specification's network ID for P2P connections
220    #[arg(long)]
221    pub network_id: Option<u64>,
222
223    /// Restrict network communication to the given IP networks (CIDR masks).
224    ///
225    /// Comma separated list of CIDR network specifications.
226    /// Only peers with IP addresses within these ranges will be allowed to connect.
227    ///
228    /// Example: --netrestrict "192.168.0.0/16,10.0.0.0/8"
229    #[arg(long, value_name = "NETRESTRICT")]
230    pub netrestrict: Option<String>,
231
232    /// Enforce EIP-868 ENR fork ID validation for discovered peers.
233    ///
234    /// When enabled, peers discovered without a confirmed fork ID are not added to the peer set
235    /// until their fork ID is verified via EIP-868 ENR request. This filters out peers from other
236    /// networks that pollute the discovery table.
237    #[arg(long)]
238    pub enforce_enr_fork_id: bool,
239}
240
241impl NetworkArgs {
242    /// Returns the resolved IP address.
243    pub fn resolved_addr(&self) -> IpAddr {
244        if let Some(ref if_name) = self.net_if {
245            let if_name = if if_name.is_empty() { DEFAULT_NET_IF_NAME } else { if_name };
246            return match reth_net_nat::net_if::resolve_net_if_ip(if_name) {
247                Ok(addr) => addr,
248                Err(err) => {
249                    error!(target: "reth::cli",
250                        if_name,
251                        %err,
252                        "Failed to read network interface IP"
253                    );
254
255                    DEFAULT_DISCOVERY_ADDR
256                }
257            };
258        }
259
260        self.addr
261    }
262
263    /// Returns the resolved bootnodes if any are provided.
264    pub fn resolved_bootnodes(&self) -> Option<Vec<NodeRecord>> {
265        self.bootnodes.clone().map(|bootnodes| {
266            bootnodes.into_iter().filter_map(|node| node.resolve_blocking().ok()).collect()
267        })
268    }
269
270    /// Returns the max inbound peers (2:1 ratio).
271    pub fn resolved_max_inbound_peers(&self) -> Option<usize> {
272        if let Some(max_peers) = self.max_peers {
273            if max_peers == 0 {
274                Some(0)
275            } else {
276                let outbound = (max_peers / 3).max(1);
277                Some(max_peers.saturating_sub(outbound))
278            }
279        } else {
280            self.max_inbound_peers
281        }
282    }
283
284    /// Returns the max outbound peers (1:2 ratio).
285    pub fn resolved_max_outbound_peers(&self) -> Option<usize> {
286        if let Some(max_peers) = self.max_peers {
287            if max_peers == 0 {
288                Some(0)
289            } else {
290                Some((max_peers / 3).max(1))
291            }
292        } else {
293            self.max_outbound_peers
294        }
295    }
296
297    /// Configures and returns a `TransactionsManagerConfig` based on the current settings.
298    pub const fn transactions_manager_config(&self) -> TransactionsManagerConfig {
299        TransactionsManagerConfig {
300            transaction_fetcher_config: TransactionFetcherConfig::new(
301                self.max_concurrent_tx_requests,
302                self.max_concurrent_tx_requests_per_peer,
303                self.soft_limit_byte_size_pooled_transactions_response,
304                self.soft_limit_byte_size_pooled_transactions_response_on_pack_request,
305                self.max_capacity_cache_txns_pending_fetch,
306            ),
307            max_transactions_seen_by_peer_history: self.max_seen_tx_history,
308            propagation_mode: self.propagation_mode,
309            ingress_policy: self.tx_ingress_policy,
310        }
311    }
312
313    /// Build a [`NetworkConfigBuilder`] from a [`Config`] and a [`EthChainSpec`], in addition to
314    /// the values in this option struct.
315    ///
316    /// The `default_peers_file` will be used as the default location to store the persistent peers
317    /// file if `no_persist_peers` is false, and there is no provided `peers_file`.
318    ///
319    /// Configured Bootnodes are prioritized, if unset, the chain spec bootnodes are used
320    /// Priority order for bootnodes configuration:
321    /// 1. --bootnodes flag
322    /// 2. Network preset flags (e.g. --holesky)
323    /// 3. default to mainnet nodes
324    pub fn network_config<N: NetworkPrimitives>(
325        &self,
326        config: &Config,
327        chain_spec: impl EthChainSpec,
328        secret_key: SecretKey,
329        default_peers_file: PathBuf,
330        executor: Runtime,
331    ) -> NetworkConfigBuilder<N> {
332        let addr = self.resolved_addr();
333        let chain_bootnodes = self
334            .resolved_bootnodes()
335            .unwrap_or_else(|| chain_spec.bootnodes().unwrap_or_else(mainnet_nodes));
336        let peers_file = self.peers_file.clone().unwrap_or(default_peers_file);
337
338        // Configure peer connections
339        let ip_filter = self.ip_filter().unwrap_or_default();
340        let peers_config = config
341            .peers_config_with_basic_nodes_from_file(
342                self.persistent_peers_file(peers_file).as_deref(),
343            )
344            .with_max_inbound_opt(self.resolved_max_inbound_peers())
345            .with_max_outbound_opt(self.resolved_max_outbound_peers())
346            .with_ip_filter(ip_filter)
347            .with_enforce_enr_fork_id(self.enforce_enr_fork_id);
348
349        // Configure basic network stack
350        NetworkConfigBuilder::<N>::new(secret_key, executor)
351            .external_ip_resolver(self.nat.clone())
352            .sessions_config(
353                config.sessions.clone().with_upscaled_event_buffer(peers_config.max_peers()),
354            )
355            .peer_config(peers_config)
356            .boot_nodes(chain_bootnodes.clone())
357            .transactions_manager_config(self.transactions_manager_config())
358            // Configure node identity
359            .apply(|builder| {
360                let peer_id = builder.get_peer_id();
361                builder.hello_message(
362                    HelloMessageWithProtocols::builder(peer_id)
363                        .client_version(&self.identity)
364                        .build(),
365                )
366            })
367            // apply discovery settings
368            .apply(|builder| {
369                let rlpx_socket = (addr, self.port).into();
370                self.discovery.apply_to_builder(builder, rlpx_socket, chain_bootnodes)
371            })
372            .listener_addr(SocketAddr::new(
373                addr, // set discovery port based on instance number
374                self.port,
375            ))
376            .discovery_addr(SocketAddr::new(
377                self.discovery.addr,
378                // set discovery port based on instance number
379                self.discovery.port,
380            ))
381            .disable_tx_gossip(self.disable_tx_gossip)
382            .required_block_hashes(self.required_block_hashes.clone())
383            .network_id(self.network_id)
384    }
385
386    /// If `no_persist_peers` is false then this returns the path to the persistent peers file path.
387    pub fn persistent_peers_file(&self, peers_file: PathBuf) -> Option<PathBuf> {
388        self.no_persist_peers.not().then_some(peers_file)
389    }
390
391    /// Configures the [`DiscoveryArgs`].
392    pub const fn with_discovery(mut self, discovery: DiscoveryArgs) -> Self {
393        self.discovery = discovery;
394        self
395    }
396
397    /// Sets the p2p port to zero, to allow the OS to assign a random unused port when
398    /// the network components bind to a socket.
399    pub const fn with_unused_p2p_port(mut self) -> Self {
400        self.port = 0;
401        self
402    }
403
404    /// Sets the p2p and discovery ports to zero, allowing the OD to assign a random unused port
405    /// when network components bind to sockets.
406    pub const fn with_unused_ports(mut self) -> Self {
407        self = self.with_unused_p2p_port();
408        self.discovery = self.discovery.with_unused_discovery_port();
409        self
410    }
411
412    /// Configures the [`NatResolver`]
413    pub fn with_nat_resolver(mut self, nat: NatResolver) -> Self {
414        self.nat = nat;
415        self
416    }
417
418    /// Change networking port numbers based on the instance number, if provided.
419    /// Ports are updated to `previous_value + instance - 1`
420    ///
421    /// # Panics
422    /// Warning: if `instance` is zero in debug mode, this will panic.
423    pub fn adjust_instance_ports(&mut self, instance: Option<u16>) {
424        if let Some(instance) = instance {
425            debug_assert_ne!(instance, 0, "instance must be non-zero");
426            self.port += instance - 1;
427            self.discovery.adjust_instance_ports(instance);
428        }
429    }
430
431    /// Resolve all trusted peers at once
432    pub async fn resolve_trusted_peers(&self) -> Result<Vec<NodeRecord>, std::io::Error> {
433        futures::future::try_join_all(
434            self.trusted_peers.iter().map(|peer| async move { peer.resolve().await }),
435        )
436        .await
437    }
438
439    /// Load the p2p secret key from the provided options.
440    ///
441    /// If `p2p_secret_key_hex` is provided, it will be used directly.
442    /// If `p2p_secret_key` is provided, it will be loaded from the file.
443    /// If neither is provided, the `default_secret_key_path` will be used.
444    pub fn secret_key(
445        &self,
446        default_secret_key_path: PathBuf,
447    ) -> Result<SecretKey, SecretKeyError> {
448        if let Some(b256) = &self.p2p_secret_key_hex {
449            // Use the B256 value directly (already validated as 32 bytes)
450            SecretKey::from_slice(b256.as_slice()).map_err(SecretKeyError::SecretKeyDecodeError)
451        } else {
452            // Load from file (either provided path or default)
453            let secret_key_path = self.p2p_secret_key.clone().unwrap_or(default_secret_key_path);
454            get_secret_key(&secret_key_path)
455        }
456    }
457
458    /// Creates an IP filter from the netrestrict argument.
459    ///
460    /// Returns an error if the CIDR format is invalid.
461    pub fn ip_filter(&self) -> Result<IpFilter, ipnet::AddrParseError> {
462        if let Some(netrestrict) = &self.netrestrict {
463            IpFilter::from_cidr_string(netrestrict)
464        } else {
465            Ok(IpFilter::allow_all())
466        }
467    }
468}
469
470impl Default for NetworkArgs {
471    fn default() -> Self {
472        Self {
473            discovery: DiscoveryArgs::default(),
474            trusted_peers: vec![],
475            trusted_only: false,
476            bootnodes: None,
477            dns_retries: 0,
478            peers_file: None,
479            identity: version_metadata().p2p_client_version.to_string(),
480            p2p_secret_key: None,
481            p2p_secret_key_hex: None,
482            no_persist_peers: false,
483            nat: NatResolver::Any,
484            addr: DEFAULT_DISCOVERY_ADDR,
485            port: DEFAULT_DISCOVERY_PORT,
486            max_outbound_peers: None,
487            max_inbound_peers: None,
488            max_peers: None,
489            max_concurrent_tx_requests: DEFAULT_MAX_COUNT_CONCURRENT_REQUESTS,
490            max_concurrent_tx_requests_per_peer: DEFAULT_MAX_COUNT_CONCURRENT_REQUESTS_PER_PEER,
491            soft_limit_byte_size_pooled_transactions_response:
492                SOFT_LIMIT_BYTE_SIZE_POOLED_TRANSACTIONS_RESPONSE,
493            soft_limit_byte_size_pooled_transactions_response_on_pack_request: DEFAULT_SOFT_LIMIT_BYTE_SIZE_POOLED_TRANSACTIONS_RESP_ON_PACK_GET_POOLED_TRANSACTIONS_REQ,
494            max_pending_pool_imports: DEFAULT_MAX_COUNT_PENDING_POOL_IMPORTS,
495            max_seen_tx_history: DEFAULT_MAX_COUNT_TRANSACTIONS_SEEN_BY_PEER,
496            max_capacity_cache_txns_pending_fetch: DEFAULT_MAX_CAPACITY_CACHE_PENDING_FETCH,
497            net_if: None,
498            tx_propagation_policy: TransactionPropagationKind::default(),
499            tx_ingress_policy: TransactionIngressPolicy::default(),
500            disable_tx_gossip: false,
501            propagation_mode: TransactionPropagationMode::Sqrt,
502            required_block_hashes: vec![],
503            network_id: None,
504            netrestrict: None,
505            enforce_enr_fork_id: false,
506        }
507    }
508}
509
510/// Arguments to setup discovery
511#[derive(Debug, Clone, Args, PartialEq, Eq)]
512pub struct DiscoveryArgs {
513    /// Disable the discovery service.
514    #[arg(short, long, default_value_if("dev", "true", "true"))]
515    pub disable_discovery: bool,
516
517    /// Disable the DNS discovery.
518    #[arg(long, conflicts_with = "disable_discovery")]
519    pub disable_dns_discovery: bool,
520
521    /// Disable Discv4 discovery.
522    #[arg(long, conflicts_with = "disable_discovery")]
523    pub disable_discv4_discovery: bool,
524
525    /// Enable Discv5 discovery.
526    #[arg(long, conflicts_with = "disable_discovery")]
527    pub enable_discv5_discovery: bool,
528
529    /// Disable Nat discovery.
530    #[arg(long, conflicts_with = "disable_discovery")]
531    pub disable_nat: bool,
532
533    /// The UDP address to use for devp2p peer discovery version 4.
534    #[arg(id = "discovery.addr", long = "discovery.addr", value_name = "DISCOVERY_ADDR", default_value_t = DEFAULT_DISCOVERY_ADDR)]
535    pub addr: IpAddr,
536
537    /// The UDP port to use for devp2p peer discovery version 4.
538    #[arg(id = "discovery.port", long = "discovery.port", value_name = "DISCOVERY_PORT", default_value_t = DEFAULT_DISCOVERY_PORT)]
539    pub port: u16,
540
541    /// The UDP IPv4 address to use for devp2p peer discovery version 5. Overwritten by `RLPx`
542    /// address, if it's also IPv4.
543    #[arg(id = "discovery.v5.addr", long = "discovery.v5.addr", value_name = "DISCOVERY_V5_ADDR", default_value = None)]
544    pub discv5_addr: Option<Ipv4Addr>,
545
546    /// The UDP IPv6 address to use for devp2p peer discovery version 5. Overwritten by `RLPx`
547    /// address, if it's also IPv6.
548    #[arg(id = "discovery.v5.addr.ipv6", long = "discovery.v5.addr.ipv6", value_name = "DISCOVERY_V5_ADDR_IPV6", default_value = None)]
549    pub discv5_addr_ipv6: Option<Ipv6Addr>,
550
551    /// The UDP IPv4 port to use for devp2p peer discovery version 5. Not used unless `--addr` is
552    /// IPv4, or `--discovery.v5.addr` is set.
553    #[arg(id = "discovery.v5.port", long = "discovery.v5.port", value_name = "DISCOVERY_V5_PORT",
554    default_value_t = DEFAULT_DISCOVERY_V5_PORT)]
555    pub discv5_port: u16,
556
557    /// The UDP IPv6 port to use for devp2p peer discovery version 5. Not used unless `--addr` is
558    /// IPv6, or `--discovery.addr.ipv6` is set.
559    #[arg(id = "discovery.v5.port.ipv6", long = "discovery.v5.port.ipv6", value_name = "DISCOVERY_V5_PORT_IPV6",
560    default_value_t = DEFAULT_DISCOVERY_V5_PORT)]
561    pub discv5_port_ipv6: u16,
562
563    /// The interval in seconds at which to carry out periodic lookup queries, for the whole
564    /// run of the program.
565    #[arg(id = "discovery.v5.lookup-interval", long = "discovery.v5.lookup-interval", value_name = "DISCOVERY_V5_LOOKUP_INTERVAL", default_value_t = DEFAULT_SECONDS_LOOKUP_INTERVAL)]
566    pub discv5_lookup_interval: u64,
567
568    /// The interval in seconds at which to carry out boost lookup queries, for a fixed number of
569    /// times, at bootstrap.
570    #[arg(id = "discovery.v5.bootstrap.lookup-interval", long = "discovery.v5.bootstrap.lookup-interval", value_name = "DISCOVERY_V5_BOOTSTRAP_LOOKUP_INTERVAL",
571        default_value_t = DEFAULT_SECONDS_BOOTSTRAP_LOOKUP_INTERVAL)]
572    pub discv5_bootstrap_lookup_interval: u64,
573
574    /// The number of times to carry out boost lookup queries at bootstrap.
575    #[arg(id = "discovery.v5.bootstrap.lookup-countdown", long = "discovery.v5.bootstrap.lookup-countdown", value_name = "DISCOVERY_V5_BOOTSTRAP_LOOKUP_COUNTDOWN",
576        default_value_t = DEFAULT_COUNT_BOOTSTRAP_LOOKUPS)]
577    pub discv5_bootstrap_lookup_countdown: u64,
578}
579
580impl DiscoveryArgs {
581    /// Apply the discovery settings to the given [`NetworkConfigBuilder`]
582    pub fn apply_to_builder<N>(
583        &self,
584        mut network_config_builder: NetworkConfigBuilder<N>,
585        rlpx_tcp_socket: SocketAddr,
586        boot_nodes: impl IntoIterator<Item = NodeRecord>,
587    ) -> NetworkConfigBuilder<N>
588    where
589        N: NetworkPrimitives,
590    {
591        if self.disable_discovery || self.disable_dns_discovery {
592            network_config_builder = network_config_builder.disable_dns_discovery();
593        }
594
595        if self.disable_discovery || self.disable_discv4_discovery {
596            network_config_builder = network_config_builder.disable_discv4_discovery();
597        }
598
599        if self.disable_nat {
600            // 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>
601            network_config_builder = network_config_builder.disable_nat();
602        }
603
604        if self.should_enable_discv5() {
605            network_config_builder = network_config_builder
606                .discovery_v5(self.discovery_v5_builder(rlpx_tcp_socket, boot_nodes));
607        }
608
609        network_config_builder
610    }
611
612    /// Creates a [`reth_discv5::ConfigBuilder`] filling it with the values from this struct.
613    pub fn discovery_v5_builder(
614        &self,
615        rlpx_tcp_socket: SocketAddr,
616        boot_nodes: impl IntoIterator<Item = NodeRecord>,
617    ) -> reth_discv5::ConfigBuilder {
618        let Self {
619            discv5_addr,
620            discv5_addr_ipv6,
621            discv5_port,
622            discv5_port_ipv6,
623            discv5_lookup_interval,
624            discv5_bootstrap_lookup_interval,
625            discv5_bootstrap_lookup_countdown,
626            ..
627        } = self;
628
629        // Use rlpx address if none given
630        let discv5_addr_ipv4 = discv5_addr.or(match rlpx_tcp_socket {
631            SocketAddr::V4(addr) => Some(*addr.ip()),
632            SocketAddr::V6(_) => None,
633        });
634        let discv5_addr_ipv6 = discv5_addr_ipv6.or(match rlpx_tcp_socket {
635            SocketAddr::V4(_) => None,
636            SocketAddr::V6(addr) => Some(*addr.ip()),
637        });
638
639        reth_discv5::Config::builder(rlpx_tcp_socket)
640            .discv5_config(
641                reth_discv5::discv5::ConfigBuilder::new(ListenConfig::from_two_sockets(
642                    discv5_addr_ipv4.map(|addr| SocketAddrV4::new(addr, *discv5_port)),
643                    discv5_addr_ipv6.map(|addr| SocketAddrV6::new(addr, *discv5_port_ipv6, 0, 0)),
644                ))
645                .build(),
646            )
647            .add_unsigned_boot_nodes(boot_nodes)
648            .lookup_interval(*discv5_lookup_interval)
649            .bootstrap_lookup_interval(*discv5_bootstrap_lookup_interval)
650            .bootstrap_lookup_countdown(*discv5_bootstrap_lookup_countdown)
651    }
652
653    /// Returns true if discv5 discovery should be configured
654    const fn should_enable_discv5(&self) -> bool {
655        if self.disable_discovery {
656            return false;
657        }
658
659        self.enable_discv5_discovery ||
660            self.discv5_addr.is_some() ||
661            self.discv5_addr_ipv6.is_some()
662    }
663
664    /// Set the discovery port to zero, to allow the OS to assign a random unused port when
665    /// discovery binds to the socket.
666    pub const fn with_unused_discovery_port(mut self) -> Self {
667        self.port = 0;
668        self
669    }
670
671    /// Set the discovery V5 port
672    pub const fn with_discv5_port(mut self, port: u16) -> Self {
673        self.discv5_port = port;
674        self
675    }
676
677    /// Change networking port numbers based on the instance number.
678    /// Ports are updated to `previous_value + instance - 1`
679    ///
680    /// # Panics
681    /// Warning: if `instance` is zero in debug mode, this will panic.
682    pub fn adjust_instance_ports(&mut self, instance: u16) {
683        debug_assert_ne!(instance, 0, "instance must be non-zero");
684        self.port += instance - 1;
685        self.discv5_port += instance - 1;
686        self.discv5_port_ipv6 += instance - 1;
687    }
688}
689
690impl Default for DiscoveryArgs {
691    fn default() -> Self {
692        Self {
693            disable_discovery: false,
694            disable_dns_discovery: false,
695            disable_discv4_discovery: false,
696            enable_discv5_discovery: false,
697            disable_nat: false,
698            addr: DEFAULT_DISCOVERY_ADDR,
699            port: DEFAULT_DISCOVERY_PORT,
700            discv5_addr: None,
701            discv5_addr_ipv6: None,
702            discv5_port: DEFAULT_DISCOVERY_V5_PORT,
703            discv5_port_ipv6: DEFAULT_DISCOVERY_V5_PORT,
704            discv5_lookup_interval: DEFAULT_SECONDS_LOOKUP_INTERVAL,
705            discv5_bootstrap_lookup_interval: DEFAULT_SECONDS_BOOTSTRAP_LOOKUP_INTERVAL,
706            discv5_bootstrap_lookup_countdown: DEFAULT_COUNT_BOOTSTRAP_LOOKUPS,
707        }
708    }
709}
710
711/// Parse a block number=hash pair or just a hash into `BlockNumHash`
712fn parse_block_num_hash(s: &str) -> Result<BlockNumHash, String> {
713    if let Some((num_str, hash_str)) = s.split_once('=') {
714        let number = num_str.parse().map_err(|_| format!("Invalid block number: {}", num_str))?;
715        let hash = B256::from_str(hash_str).map_err(|_| format!("Invalid hash: {}", hash_str))?;
716        Ok(BlockNumHash::new(number, hash))
717    } else {
718        // For backward compatibility, treat as hash-only with number 0
719        let hash = B256::from_str(s).map_err(|_| format!("Invalid hash: {}", s))?;
720        Ok(BlockNumHash::new(0, hash))
721    }
722}
723
724#[cfg(test)]
725mod tests {
726    use super::*;
727    use clap::Parser;
728    use reth_chainspec::MAINNET;
729    use reth_config::Config;
730    use reth_network_peers::NodeRecord;
731    use secp256k1::SecretKey;
732    use std::{
733        fs,
734        time::{SystemTime, UNIX_EPOCH},
735    };
736
737    /// A helper type to parse Args more easily
738    #[derive(Parser)]
739    struct CommandParser<T: Args> {
740        #[command(flatten)]
741        args: T,
742    }
743
744    #[test]
745    fn parse_nat_args() {
746        let args = CommandParser::<NetworkArgs>::parse_from(["reth", "--nat", "none"]).args;
747        assert_eq!(args.nat, NatResolver::None);
748
749        let args =
750            CommandParser::<NetworkArgs>::parse_from(["reth", "--nat", "extip:0.0.0.0"]).args;
751        assert_eq!(args.nat, NatResolver::ExternalIp("0.0.0.0".parse().unwrap()));
752    }
753
754    #[test]
755    fn parse_peer_args() {
756        let args =
757            CommandParser::<NetworkArgs>::parse_from(["reth", "--max-outbound-peers", "50"]).args;
758        assert_eq!(args.max_outbound_peers, Some(50));
759        assert_eq!(args.max_inbound_peers, None);
760
761        let args = CommandParser::<NetworkArgs>::parse_from([
762            "reth",
763            "--max-outbound-peers",
764            "75",
765            "--max-inbound-peers",
766            "15",
767        ])
768        .args;
769        assert_eq!(args.max_outbound_peers, Some(75));
770        assert_eq!(args.max_inbound_peers, Some(15));
771    }
772
773    #[test]
774    fn parse_trusted_peer_args() {
775        let args =
776            CommandParser::<NetworkArgs>::parse_from([
777            "reth",
778            "--trusted-peers",
779            "enode://d860a01f9722d78051619d1e2351aba3f43f943f6f00718d1b9baa4101932a1f5011f16bb2b1bb35db20d6fe28fa0bf09636d26a87d31de9ec6203eeedb1f666@18.138.108.67:30303,enode://22a8232c3abc76a16ae9d6c3b164f98775fe226f0917b0ca871128a74a8e9630b458460865bab457221f1d448dd9791d24c4e5d88786180ac185df813a68d4de@3.209.45.79:30303"
780        ])
781        .args;
782
783        assert_eq!(
784            args.trusted_peers,
785            vec![
786            "enode://d860a01f9722d78051619d1e2351aba3f43f943f6f00718d1b9baa4101932a1f5011f16bb2b1bb35db20d6fe28fa0bf09636d26a87d31de9ec6203eeedb1f666@18.138.108.67:30303".parse().unwrap(),
787            "enode://22a8232c3abc76a16ae9d6c3b164f98775fe226f0917b0ca871128a74a8e9630b458460865bab457221f1d448dd9791d24c4e5d88786180ac185df813a68d4de@3.209.45.79:30303".parse().unwrap()
788            ]
789        );
790    }
791
792    #[test]
793    fn parse_retry_strategy_args() {
794        let tests = vec![0, 10];
795
796        for retries in tests {
797            let retries_str = retries.to_string();
798            let args = CommandParser::<NetworkArgs>::parse_from([
799                "reth",
800                "--dns-retries",
801                retries_str.as_str(),
802            ])
803            .args;
804
805            assert_eq!(args.dns_retries, retries);
806        }
807    }
808
809    #[test]
810    fn parse_disable_tx_gossip_args() {
811        let args = CommandParser::<NetworkArgs>::parse_from(["reth", "--disable-tx-gossip"]).args;
812        assert!(args.disable_tx_gossip);
813    }
814
815    #[test]
816    fn parse_max_peers_flag() {
817        let args = CommandParser::<NetworkArgs>::parse_from(["reth", "--max-peers", "90"]).args;
818
819        assert_eq!(args.max_peers, Some(90));
820        assert_eq!(args.max_outbound_peers, None);
821        assert_eq!(args.max_inbound_peers, None);
822        assert_eq!(args.resolved_max_outbound_peers(), Some(30));
823        assert_eq!(args.resolved_max_inbound_peers(), Some(60));
824    }
825
826    #[test]
827    fn max_peers_conflicts_with_outbound() {
828        let result = CommandParser::<NetworkArgs>::try_parse_from([
829            "reth",
830            "--max-peers",
831            "90",
832            "--max-outbound-peers",
833            "50",
834        ]);
835        assert!(
836            result.is_err(),
837            "Should fail when both --max-peers and --max-outbound-peers are used"
838        );
839    }
840
841    #[test]
842    fn max_peers_conflicts_with_inbound() {
843        let result = CommandParser::<NetworkArgs>::try_parse_from([
844            "reth",
845            "--max-peers",
846            "90",
847            "--max-inbound-peers",
848            "30",
849        ]);
850        assert!(
851            result.is_err(),
852            "Should fail when both --max-peers and --max-inbound-peers are used"
853        );
854    }
855
856    #[test]
857    fn max_peers_split_calculation() {
858        let args = CommandParser::<NetworkArgs>::parse_from(["reth", "--max-peers", "90"]).args;
859
860        assert_eq!(args.max_peers, Some(90));
861        assert_eq!(args.resolved_max_outbound_peers(), Some(30));
862        assert_eq!(args.resolved_max_inbound_peers(), Some(60));
863    }
864
865    #[test]
866    fn max_peers_small_values() {
867        let args1 = CommandParser::<NetworkArgs>::parse_from(["reth", "--max-peers", "1"]).args;
868        assert_eq!(args1.resolved_max_outbound_peers(), Some(1));
869        assert_eq!(args1.resolved_max_inbound_peers(), Some(0));
870
871        let args2 = CommandParser::<NetworkArgs>::parse_from(["reth", "--max-peers", "2"]).args;
872        assert_eq!(args2.resolved_max_outbound_peers(), Some(1));
873        assert_eq!(args2.resolved_max_inbound_peers(), Some(1));
874
875        let args3 = CommandParser::<NetworkArgs>::parse_from(["reth", "--max-peers", "3"]).args;
876        assert_eq!(args3.resolved_max_outbound_peers(), Some(1));
877        assert_eq!(args3.resolved_max_inbound_peers(), Some(2));
878    }
879
880    #[test]
881    fn resolved_peers_without_max_peers() {
882        let args = CommandParser::<NetworkArgs>::parse_from([
883            "reth",
884            "--max-outbound-peers",
885            "75",
886            "--max-inbound-peers",
887            "15",
888        ])
889        .args;
890
891        assert_eq!(args.max_peers, None);
892        assert_eq!(args.resolved_max_outbound_peers(), Some(75));
893        assert_eq!(args.resolved_max_inbound_peers(), Some(15));
894    }
895
896    #[test]
897    fn resolved_peers_with_defaults() {
898        let args = CommandParser::<NetworkArgs>::parse_from(["reth"]).args;
899
900        assert_eq!(args.max_peers, None);
901        assert_eq!(args.resolved_max_outbound_peers(), None);
902        assert_eq!(args.resolved_max_inbound_peers(), None);
903    }
904
905    #[test]
906    fn network_args_default_sanity_test() {
907        let default_args = NetworkArgs::default();
908        let args = CommandParser::<NetworkArgs>::parse_from(["reth"]).args;
909
910        assert_eq!(args, default_args);
911    }
912
913    #[test]
914    fn parse_required_block_hashes() {
915        let args = CommandParser::<NetworkArgs>::parse_from([
916            "reth",
917            "--required-block-hashes",
918            "0x1111111111111111111111111111111111111111111111111111111111111111,23115201=0x2222222222222222222222222222222222222222222222222222222222222222",
919        ])
920        .args;
921
922        assert_eq!(args.required_block_hashes.len(), 2);
923        // First hash without block number (should default to 0)
924        assert_eq!(args.required_block_hashes[0].number, 0);
925        assert_eq!(
926            args.required_block_hashes[0].hash.to_string(),
927            "0x1111111111111111111111111111111111111111111111111111111111111111"
928        );
929        // Second with block number=hash format
930        assert_eq!(args.required_block_hashes[1].number, 23115201);
931        assert_eq!(
932            args.required_block_hashes[1].hash.to_string(),
933            "0x2222222222222222222222222222222222222222222222222222222222222222"
934        );
935    }
936
937    #[test]
938    fn parse_empty_required_block_hashes() {
939        let args = CommandParser::<NetworkArgs>::parse_from(["reth"]).args;
940        assert!(args.required_block_hashes.is_empty());
941    }
942
943    #[test]
944    fn test_parse_block_num_hash() {
945        // Test hash only format
946        let result = parse_block_num_hash(
947            "0x1111111111111111111111111111111111111111111111111111111111111111",
948        );
949        assert!(result.is_ok());
950        assert_eq!(result.unwrap().number, 0);
951
952        // Test block_number=hash format
953        let result = parse_block_num_hash(
954            "23115201=0x2222222222222222222222222222222222222222222222222222222222222222",
955        );
956        assert!(result.is_ok());
957        assert_eq!(result.unwrap().number, 23115201);
958
959        // Test invalid formats
960        assert!(parse_block_num_hash("invalid").is_err());
961        assert!(parse_block_num_hash(
962            "abc=0x1111111111111111111111111111111111111111111111111111111111111111"
963        )
964        .is_err());
965    }
966
967    #[test]
968    fn parse_p2p_secret_key_hex() {
969        let hex = "4c0883a69102937d6231471b5dbb6204fe512961708279f8c5c58b3b9c4e8b8f";
970        let args =
971            CommandParser::<NetworkArgs>::parse_from(["reth", "--p2p-secret-key-hex", hex]).args;
972
973        let expected: B256 = hex.parse().unwrap();
974        assert_eq!(args.p2p_secret_key_hex, Some(expected));
975        assert_eq!(args.p2p_secret_key, None);
976    }
977
978    #[test]
979    fn parse_p2p_secret_key_hex_with_0x_prefix() {
980        let hex = "0x4c0883a69102937d6231471b5dbb6204fe512961708279f8c5c58b3b9c4e8b8f";
981        let args =
982            CommandParser::<NetworkArgs>::parse_from(["reth", "--p2p-secret-key-hex", hex]).args;
983
984        let expected: B256 = hex.parse().unwrap();
985        assert_eq!(args.p2p_secret_key_hex, Some(expected));
986        assert_eq!(args.p2p_secret_key, None);
987    }
988
989    #[test]
990    fn test_p2p_secret_key_and_hex_are_mutually_exclusive() {
991        let result = CommandParser::<NetworkArgs>::try_parse_from([
992            "reth",
993            "--p2p-secret-key",
994            "/path/to/key",
995            "--p2p-secret-key-hex",
996            "4c0883a69102937d6231471b5dbb6204fe512961708279f8c5c58b3b9c4e8b8f",
997        ]);
998
999        assert!(result.is_err());
1000    }
1001
1002    #[test]
1003    fn test_secret_key_method_with_hex() {
1004        let hex = "4c0883a69102937d6231471b5dbb6204fe512961708279f8c5c58b3b9c4e8b8f";
1005        let args =
1006            CommandParser::<NetworkArgs>::parse_from(["reth", "--p2p-secret-key-hex", hex]).args;
1007
1008        let temp_dir = std::env::temp_dir();
1009        let default_path = temp_dir.join("default_key");
1010        let secret_key = args.secret_key(default_path).unwrap();
1011
1012        // Verify the secret key matches the hex input
1013        assert_eq!(alloy_primitives::hex::encode(secret_key.secret_bytes()), hex);
1014    }
1015
1016    #[test]
1017    fn parse_netrestrict_single_network() {
1018        let args =
1019            CommandParser::<NetworkArgs>::parse_from(["reth", "--netrestrict", "192.168.0.0/16"])
1020                .args;
1021
1022        assert_eq!(args.netrestrict, Some("192.168.0.0/16".to_string()));
1023
1024        let ip_filter = args.ip_filter().unwrap();
1025        assert!(ip_filter.has_restrictions());
1026        assert!(ip_filter.is_allowed(&"192.168.1.1".parse().unwrap()));
1027        assert!(!ip_filter.is_allowed(&"10.0.0.1".parse().unwrap()));
1028    }
1029
1030    #[test]
1031    fn parse_netrestrict_multiple_networks() {
1032        let args = CommandParser::<NetworkArgs>::parse_from([
1033            "reth",
1034            "--netrestrict",
1035            "192.168.0.0/16,10.0.0.0/8",
1036        ])
1037        .args;
1038
1039        assert_eq!(args.netrestrict, Some("192.168.0.0/16,10.0.0.0/8".to_string()));
1040
1041        let ip_filter = args.ip_filter().unwrap();
1042        assert!(ip_filter.has_restrictions());
1043        assert!(ip_filter.is_allowed(&"192.168.1.1".parse().unwrap()));
1044        assert!(ip_filter.is_allowed(&"10.5.10.20".parse().unwrap()));
1045        assert!(!ip_filter.is_allowed(&"172.16.0.1".parse().unwrap()));
1046    }
1047
1048    #[test]
1049    fn parse_netrestrict_ipv6() {
1050        let args =
1051            CommandParser::<NetworkArgs>::parse_from(["reth", "--netrestrict", "2001:db8::/32"])
1052                .args;
1053
1054        let ip_filter = args.ip_filter().unwrap();
1055        assert!(ip_filter.has_restrictions());
1056        assert!(ip_filter.is_allowed(&"2001:db8::1".parse().unwrap()));
1057        assert!(!ip_filter.is_allowed(&"2001:db9::1".parse().unwrap()));
1058    }
1059
1060    #[test]
1061    fn netrestrict_not_set() {
1062        let args = CommandParser::<NetworkArgs>::parse_from(["reth"]).args;
1063        assert_eq!(args.netrestrict, None);
1064
1065        let ip_filter = args.ip_filter().unwrap();
1066        assert!(!ip_filter.has_restrictions());
1067        assert!(ip_filter.is_allowed(&"192.168.1.1".parse().unwrap()));
1068        assert!(ip_filter.is_allowed(&"10.0.0.1".parse().unwrap()));
1069    }
1070
1071    #[test]
1072    fn netrestrict_invalid_cidr() {
1073        let args =
1074            CommandParser::<NetworkArgs>::parse_from(["reth", "--netrestrict", "invalid-cidr"])
1075                .args;
1076
1077        assert!(args.ip_filter().is_err());
1078    }
1079
1080    #[test]
1081    fn network_config_preserves_basic_nodes_from_peers_file() {
1082        let enode = "enode://6f8a80d14311c39f35f516fa664deaaaa13e85b2f7493f37f6144d86991ec012937307647bd3b9a82abe2974e1407241d54947bbb39763a4cac9f77166ad92a0@10.3.58.6:30303?discport=30301";
1083        let unique = SystemTime::now().duration_since(UNIX_EPOCH).unwrap().as_nanos();
1084
1085        let peers_file = std::env::temp_dir().join(format!("reth_peers_test_{}.json", unique));
1086        fs::write(&peers_file, format!("[\"{}\"]", enode)).expect("write peers file");
1087
1088        // Build NetworkArgs with peers_file set and no_persist_peers=false
1089        let args = NetworkArgs {
1090            peers_file: Some(peers_file.clone()),
1091            no_persist_peers: false,
1092            ..Default::default()
1093        };
1094
1095        // Build the network config using a deterministic secret key
1096        let secret_key = SecretKey::from_byte_array(&[1u8; 32]).unwrap();
1097        let builder = args.network_config::<reth_network::EthNetworkPrimitives>(
1098            &Config::default(),
1099            MAINNET.clone(),
1100            secret_key,
1101            peers_file.clone(),
1102            Runtime::test(),
1103        );
1104
1105        let net_cfg = builder.build_with_noop_provider(MAINNET.clone());
1106
1107        // Assert persisted_peers contains our node (legacy format is auto-converted)
1108        let node: NodeRecord = enode.parse().unwrap();
1109        assert!(net_cfg.peers_config.persisted_peers.iter().any(|p| p.record == node));
1110
1111        // Cleanup
1112        let _ = fs::remove_file(&peers_file);
1113    }
1114}