1use std::{
4 net::{IpAddr, Ipv4Addr, Ipv6Addr, SocketAddr, SocketAddrV4, SocketAddrV6},
5 ops::Not,
6 path::PathBuf,
7};
8
9use clap::Args;
10use reth_chainspec::EthChainSpec;
11use reth_config::Config;
12use reth_discv4::{NodeRecord, DEFAULT_DISCOVERY_ADDR, DEFAULT_DISCOVERY_PORT};
13use reth_discv5::{
14 discv5::ListenConfig, DEFAULT_COUNT_BOOTSTRAP_LOOKUPS, DEFAULT_DISCOVERY_V5_PORT,
15 DEFAULT_SECONDS_BOOTSTRAP_LOOKUP_INTERVAL, DEFAULT_SECONDS_LOOKUP_INTERVAL,
16};
17use reth_net_nat::{NatResolver, DEFAULT_NET_IF_NAME};
18use reth_network::{
19 transactions::{
20 constants::{
21 tx_fetcher::{
22 DEFAULT_MAX_CAPACITY_CACHE_PENDING_FETCH, DEFAULT_MAX_COUNT_CONCURRENT_REQUESTS,
23 DEFAULT_MAX_COUNT_CONCURRENT_REQUESTS_PER_PEER,
24 },
25 tx_manager::{
26 DEFAULT_MAX_COUNT_PENDING_POOL_IMPORTS, DEFAULT_MAX_COUNT_TRANSACTIONS_SEEN_BY_PEER,
27 },
28 },
29 TransactionFetcherConfig, TransactionsManagerConfig,
30 DEFAULT_SOFT_LIMIT_BYTE_SIZE_POOLED_TRANSACTIONS_RESP_ON_PACK_GET_POOLED_TRANSACTIONS_REQ,
31 SOFT_LIMIT_BYTE_SIZE_POOLED_TRANSACTIONS_RESPONSE,
32 },
33 HelloMessageWithProtocols, NetworkConfigBuilder, SessionsConfig,
34};
35use reth_network_peers::{mainnet_nodes, TrustedPeer};
36use secp256k1::SecretKey;
37use tracing::error;
38
39use crate::version::P2P_CLIENT_VERSION;
40
41#[derive(Debug, Clone, Args, PartialEq, Eq)]
43#[command(next_help_heading = "Networking")]
44pub struct NetworkArgs {
45 #[command(flatten)]
47 pub discovery: DiscoveryArgs,
48
49 #[allow(clippy::doc_markdown)]
50 #[arg(long, value_delimiter = ',')]
54 pub trusted_peers: Vec<TrustedPeer>,
55
56 #[arg(long)]
58 pub trusted_only: bool,
59
60 #[arg(long, value_delimiter = ',')]
64 pub bootnodes: Option<Vec<TrustedPeer>>,
65
66 #[arg(long, default_value_t = 0)]
68 pub dns_retries: usize,
69
70 #[arg(long, value_name = "FILE", verbatim_doc_comment, conflicts_with = "no_persist_peers")]
73 pub peers_file: Option<PathBuf>,
74
75 #[arg(long, value_name = "IDENTITY", default_value = P2P_CLIENT_VERSION)]
77 pub identity: String,
78
79 #[arg(long, value_name = "PATH")]
84 pub p2p_secret_key: Option<PathBuf>,
85
86 #[arg(long, verbatim_doc_comment)]
88 pub no_persist_peers: bool,
89
90 #[arg(long, default_value = "any")]
92 pub nat: NatResolver,
93
94 #[arg(long = "addr", value_name = "ADDR", default_value_t = DEFAULT_DISCOVERY_ADDR)]
96 pub addr: IpAddr,
97
98 #[arg(long = "port", value_name = "PORT", default_value_t = DEFAULT_DISCOVERY_PORT)]
100 pub port: u16,
101
102 #[arg(long)]
104 pub max_outbound_peers: Option<usize>,
105
106 #[arg(long)]
108 pub max_inbound_peers: Option<usize>,
109
110 #[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 #[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 #[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 pub max_pending_pool_imports: usize,
127
128 #[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 #[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 #[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 #[arg(long = "net-if.experimental", conflicts_with = "addr", value_name = "IF_NAME")]
156 pub net_if: Option<String>,
157}
158
159impl NetworkArgs {
160 pub fn resolved_addr(&self) -> IpAddr {
162 if let Some(ref if_name) = self.net_if {
163 let if_name = if if_name.is_empty() { DEFAULT_NET_IF_NAME } else { if_name };
164 return match reth_net_nat::net_if::resolve_net_if_ip(if_name) {
165 Ok(addr) => addr,
166 Err(err) => {
167 error!(target: "reth::cli",
168 if_name,
169 %err,
170 "Failed to read network interface IP"
171 );
172
173 DEFAULT_DISCOVERY_ADDR
174 }
175 };
176 }
177
178 self.addr
179 }
180
181 pub fn resolved_bootnodes(&self) -> Option<Vec<NodeRecord>> {
183 self.bootnodes.clone().map(|bootnodes| {
184 bootnodes.into_iter().filter_map(|node| node.resolve_blocking().ok()).collect()
185 })
186 }
187
188 pub fn network_config(
200 &self,
201 config: &Config,
202 chain_spec: impl EthChainSpec,
203 secret_key: SecretKey,
204 default_peers_file: PathBuf,
205 ) -> NetworkConfigBuilder {
206 let addr = self.resolved_addr();
207 let chain_bootnodes = self
208 .resolved_bootnodes()
209 .unwrap_or_else(|| chain_spec.bootnodes().unwrap_or_else(mainnet_nodes));
210 let peers_file = self.peers_file.clone().unwrap_or(default_peers_file);
211
212 let peers_config = config
214 .peers
215 .clone()
216 .with_max_inbound_opt(self.max_inbound_peers)
217 .with_max_outbound_opt(self.max_outbound_peers);
218
219 let transactions_manager_config = TransactionsManagerConfig {
221 transaction_fetcher_config: TransactionFetcherConfig::new(
222 self.max_concurrent_tx_requests,
223 self.max_concurrent_tx_requests_per_peer,
224 self.soft_limit_byte_size_pooled_transactions_response,
225 self.soft_limit_byte_size_pooled_transactions_response_on_pack_request,
226 self.max_capacity_cache_txns_pending_fetch,
227 ),
228 max_transactions_seen_by_peer_history: self.max_seen_tx_history,
229 propagation_mode: Default::default(),
230 };
231
232 NetworkConfigBuilder::new(secret_key)
234 .peer_config(config.peers_config_with_basic_nodes_from_file(
235 self.persistent_peers_file(peers_file).as_deref(),
236 ))
237 .external_ip_resolver(self.nat)
238 .sessions_config(
239 SessionsConfig::default().with_upscaled_event_buffer(peers_config.max_peers()),
240 )
241 .peer_config(peers_config)
242 .boot_nodes(chain_bootnodes.clone())
243 .transactions_manager_config(transactions_manager_config)
244 .apply(|builder| {
246 let peer_id = builder.get_peer_id();
247 builder.hello_message(
248 HelloMessageWithProtocols::builder(peer_id)
249 .client_version(&self.identity)
250 .build(),
251 )
252 })
253 .apply(|builder| {
255 let rlpx_socket = (addr, self.port).into();
256 self.discovery.apply_to_builder(builder, rlpx_socket, chain_bootnodes)
257 })
258 .listener_addr(SocketAddr::new(
259 addr, self.port,
261 ))
262 .discovery_addr(SocketAddr::new(
263 self.discovery.addr,
264 self.discovery.port,
266 ))
267 }
268
269 pub fn persistent_peers_file(&self, peers_file: PathBuf) -> Option<PathBuf> {
271 self.no_persist_peers.not().then_some(peers_file)
272 }
273
274 pub const fn with_unused_p2p_port(mut self) -> Self {
277 self.port = 0;
278 self
279 }
280
281 pub const fn with_unused_ports(mut self) -> Self {
284 self = self.with_unused_p2p_port();
285 self.discovery = self.discovery.with_unused_discovery_port();
286 self
287 }
288
289 pub fn adjust_instance_ports(&mut self, instance: u16) {
295 debug_assert_ne!(instance, 0, "instance must be non-zero");
296 self.port += instance - 1;
297 self.discovery.adjust_instance_ports(instance);
298 }
299
300 pub async fn resolve_trusted_peers(&self) -> Result<Vec<NodeRecord>, std::io::Error> {
302 futures::future::try_join_all(
303 self.trusted_peers.iter().map(|peer| async move { peer.resolve().await }),
304 )
305 .await
306 }
307}
308
309impl Default for NetworkArgs {
310 fn default() -> Self {
311 Self {
312 discovery: DiscoveryArgs::default(),
313 trusted_peers: vec![],
314 trusted_only: false,
315 bootnodes: None,
316 dns_retries: 0,
317 peers_file: None,
318 identity: P2P_CLIENT_VERSION.to_string(),
319 p2p_secret_key: None,
320 no_persist_peers: false,
321 nat: NatResolver::Any,
322 addr: DEFAULT_DISCOVERY_ADDR,
323 port: DEFAULT_DISCOVERY_PORT,
324 max_outbound_peers: None,
325 max_inbound_peers: None,
326 max_concurrent_tx_requests: DEFAULT_MAX_COUNT_CONCURRENT_REQUESTS,
327 max_concurrent_tx_requests_per_peer: DEFAULT_MAX_COUNT_CONCURRENT_REQUESTS_PER_PEER,
328 soft_limit_byte_size_pooled_transactions_response:
329 SOFT_LIMIT_BYTE_SIZE_POOLED_TRANSACTIONS_RESPONSE,
330 soft_limit_byte_size_pooled_transactions_response_on_pack_request: DEFAULT_SOFT_LIMIT_BYTE_SIZE_POOLED_TRANSACTIONS_RESP_ON_PACK_GET_POOLED_TRANSACTIONS_REQ,
331 max_pending_pool_imports: DEFAULT_MAX_COUNT_PENDING_POOL_IMPORTS,
332 max_seen_tx_history: DEFAULT_MAX_COUNT_TRANSACTIONS_SEEN_BY_PEER,
333 max_capacity_cache_txns_pending_fetch: DEFAULT_MAX_CAPACITY_CACHE_PENDING_FETCH,
334 net_if: None,
335 }
336 }
337}
338
339#[derive(Debug, Clone, Args, PartialEq, Eq)]
341pub struct DiscoveryArgs {
342 #[arg(short, long, default_value_if("dev", "true", "true"))]
344 pub disable_discovery: bool,
345
346 #[arg(long, conflicts_with = "disable_discovery")]
348 pub disable_dns_discovery: bool,
349
350 #[arg(long, conflicts_with = "disable_discovery")]
352 pub disable_discv4_discovery: bool,
353
354 #[arg(long, conflicts_with = "disable_discovery")]
356 pub enable_discv5_discovery: bool,
357
358 #[arg(long, conflicts_with = "disable_discovery")]
360 pub disable_nat: bool,
361
362 #[arg(id = "discovery.addr", long = "discovery.addr", value_name = "DISCOVERY_ADDR", default_value_t = DEFAULT_DISCOVERY_ADDR)]
364 pub addr: IpAddr,
365
366 #[arg(id = "discovery.port", long = "discovery.port", value_name = "DISCOVERY_PORT", default_value_t = DEFAULT_DISCOVERY_PORT)]
368 pub port: u16,
369
370 #[arg(id = "discovery.v5.addr", long = "discovery.v5.addr", value_name = "DISCOVERY_V5_ADDR", default_value = None)]
373 pub discv5_addr: Option<Ipv4Addr>,
374
375 #[arg(id = "discovery.v5.addr.ipv6", long = "discovery.v5.addr.ipv6", value_name = "DISCOVERY_V5_ADDR_IPV6", default_value = None)]
378 pub discv5_addr_ipv6: Option<Ipv6Addr>,
379
380 #[arg(id = "discovery.v5.port", long = "discovery.v5.port", value_name = "DISCOVERY_V5_PORT",
383 default_value_t = DEFAULT_DISCOVERY_V5_PORT)]
384 pub discv5_port: u16,
385
386 #[arg(id = "discovery.v5.port.ipv6", long = "discovery.v5.port.ipv6", value_name = "DISCOVERY_V5_PORT_IPV6",
389 default_value = None, default_value_t = DEFAULT_DISCOVERY_V5_PORT)]
390 pub discv5_port_ipv6: u16,
391
392 #[arg(id = "discovery.v5.lookup-interval", long = "discovery.v5.lookup-interval", value_name = "DISCOVERY_V5_LOOKUP_INTERVAL", default_value_t = DEFAULT_SECONDS_LOOKUP_INTERVAL)]
395 pub discv5_lookup_interval: u64,
396
397 #[arg(id = "discovery.v5.bootstrap.lookup-interval", long = "discovery.v5.bootstrap.lookup-interval", value_name = "DISCOVERY_V5_BOOTSTRAP_LOOKUP_INTERVAL",
400 default_value_t = DEFAULT_SECONDS_BOOTSTRAP_LOOKUP_INTERVAL)]
401 pub discv5_bootstrap_lookup_interval: u64,
402
403 #[arg(id = "discovery.v5.bootstrap.lookup-countdown", long = "discovery.v5.bootstrap.lookup-countdown", value_name = "DISCOVERY_V5_BOOTSTRAP_LOOKUP_COUNTDOWN",
405 default_value_t = DEFAULT_COUNT_BOOTSTRAP_LOOKUPS)]
406 pub discv5_bootstrap_lookup_countdown: u64,
407}
408
409impl DiscoveryArgs {
410 pub fn apply_to_builder(
412 &self,
413 mut network_config_builder: NetworkConfigBuilder,
414 rlpx_tcp_socket: SocketAddr,
415 boot_nodes: impl IntoIterator<Item = NodeRecord>,
416 ) -> NetworkConfigBuilder {
417 if self.disable_discovery || self.disable_dns_discovery {
418 network_config_builder = network_config_builder.disable_dns_discovery();
419 }
420
421 if self.disable_discovery || self.disable_discv4_discovery {
422 network_config_builder = network_config_builder.disable_discv4_discovery();
423 }
424
425 if self.disable_discovery || self.disable_nat {
426 network_config_builder = network_config_builder.disable_nat();
427 }
428
429 if !self.disable_discovery && self.enable_discv5_discovery {
430 network_config_builder = network_config_builder
431 .discovery_v5(self.discovery_v5_builder(rlpx_tcp_socket, boot_nodes));
432 }
433
434 network_config_builder
435 }
436
437 pub fn discovery_v5_builder(
439 &self,
440 rlpx_tcp_socket: SocketAddr,
441 boot_nodes: impl IntoIterator<Item = NodeRecord>,
442 ) -> reth_discv5::ConfigBuilder {
443 let Self {
444 discv5_addr,
445 discv5_addr_ipv6,
446 discv5_port,
447 discv5_port_ipv6,
448 discv5_lookup_interval,
449 discv5_bootstrap_lookup_interval,
450 discv5_bootstrap_lookup_countdown,
451 ..
452 } = self;
453
454 let discv5_addr_ipv4 = discv5_addr.or(match rlpx_tcp_socket {
456 SocketAddr::V4(addr) => Some(*addr.ip()),
457 SocketAddr::V6(_) => None,
458 });
459 let discv5_addr_ipv6 = discv5_addr_ipv6.or(match rlpx_tcp_socket {
460 SocketAddr::V4(_) => None,
461 SocketAddr::V6(addr) => Some(*addr.ip()),
462 });
463
464 reth_discv5::Config::builder(rlpx_tcp_socket)
465 .discv5_config(
466 reth_discv5::discv5::ConfigBuilder::new(ListenConfig::from_two_sockets(
467 discv5_addr_ipv4.map(|addr| SocketAddrV4::new(addr, *discv5_port)),
468 discv5_addr_ipv6.map(|addr| SocketAddrV6::new(addr, *discv5_port_ipv6, 0, 0)),
469 ))
470 .build(),
471 )
472 .add_unsigned_boot_nodes(boot_nodes)
473 .lookup_interval(*discv5_lookup_interval)
474 .bootstrap_lookup_interval(*discv5_bootstrap_lookup_interval)
475 .bootstrap_lookup_countdown(*discv5_bootstrap_lookup_countdown)
476 }
477
478 pub const fn with_unused_discovery_port(mut self) -> Self {
481 self.port = 0;
482 self
483 }
484
485 pub fn adjust_instance_ports(&mut self, instance: u16) {
491 debug_assert_ne!(instance, 0, "instance must be non-zero");
492 self.port += instance - 1;
493 self.discv5_port += instance - 1;
494 self.discv5_port_ipv6 += instance - 1;
495 }
496}
497
498impl Default for DiscoveryArgs {
499 fn default() -> Self {
500 Self {
501 disable_discovery: false,
502 disable_dns_discovery: false,
503 disable_discv4_discovery: false,
504 enable_discv5_discovery: false,
505 disable_nat: false,
506 addr: DEFAULT_DISCOVERY_ADDR,
507 port: DEFAULT_DISCOVERY_PORT,
508 discv5_addr: None,
509 discv5_addr_ipv6: None,
510 discv5_port: DEFAULT_DISCOVERY_V5_PORT,
511 discv5_port_ipv6: DEFAULT_DISCOVERY_V5_PORT,
512 discv5_lookup_interval: DEFAULT_SECONDS_LOOKUP_INTERVAL,
513 discv5_bootstrap_lookup_interval: DEFAULT_SECONDS_BOOTSTRAP_LOOKUP_INTERVAL,
514 discv5_bootstrap_lookup_countdown: DEFAULT_COUNT_BOOTSTRAP_LOOKUPS,
515 }
516 }
517}
518
519#[cfg(test)]
520mod tests {
521 use super::*;
522 use clap::Parser;
523 #[derive(Parser)]
525 struct CommandParser<T: Args> {
526 #[command(flatten)]
527 args: T,
528 }
529
530 #[test]
531 fn parse_nat_args() {
532 let args = CommandParser::<NetworkArgs>::parse_from(["reth", "--nat", "none"]).args;
533 assert_eq!(args.nat, NatResolver::None);
534
535 let args =
536 CommandParser::<NetworkArgs>::parse_from(["reth", "--nat", "extip:0.0.0.0"]).args;
537 assert_eq!(args.nat, NatResolver::ExternalIp("0.0.0.0".parse().unwrap()));
538 }
539
540 #[test]
541 fn parse_peer_args() {
542 let args =
543 CommandParser::<NetworkArgs>::parse_from(["reth", "--max-outbound-peers", "50"]).args;
544 assert_eq!(args.max_outbound_peers, Some(50));
545 assert_eq!(args.max_inbound_peers, None);
546
547 let args = CommandParser::<NetworkArgs>::parse_from([
548 "reth",
549 "--max-outbound-peers",
550 "75",
551 "--max-inbound-peers",
552 "15",
553 ])
554 .args;
555 assert_eq!(args.max_outbound_peers, Some(75));
556 assert_eq!(args.max_inbound_peers, Some(15));
557 }
558
559 #[test]
560 fn parse_trusted_peer_args() {
561 let args =
562 CommandParser::<NetworkArgs>::parse_from([
563 "reth",
564 "--trusted-peers",
565 "enode://d860a01f9722d78051619d1e2351aba3f43f943f6f00718d1b9baa4101932a1f5011f16bb2b1bb35db20d6fe28fa0bf09636d26a87d31de9ec6203eeedb1f666@18.138.108.67:30303,enode://22a8232c3abc76a16ae9d6c3b164f98775fe226f0917b0ca871128a74a8e9630b458460865bab457221f1d448dd9791d24c4e5d88786180ac185df813a68d4de@3.209.45.79:30303"
566 ])
567 .args;
568
569 assert_eq!(
570 args.trusted_peers,
571 vec![
572 "enode://d860a01f9722d78051619d1e2351aba3f43f943f6f00718d1b9baa4101932a1f5011f16bb2b1bb35db20d6fe28fa0bf09636d26a87d31de9ec6203eeedb1f666@18.138.108.67:30303".parse().unwrap(),
573 "enode://22a8232c3abc76a16ae9d6c3b164f98775fe226f0917b0ca871128a74a8e9630b458460865bab457221f1d448dd9791d24c4e5d88786180ac185df813a68d4de@3.209.45.79:30303".parse().unwrap()
574 ]
575 );
576 }
577
578 #[test]
579 fn parse_retry_strategy_args() {
580 let tests = vec![0, 10];
581
582 for retries in tests {
583 let args = CommandParser::<NetworkArgs>::parse_from([
584 "reth",
585 "--dns-retries",
586 retries.to_string().as_str(),
587 ])
588 .args;
589
590 assert_eq!(args.dns_retries, retries);
591 }
592 }
593
594 #[cfg(not(feature = "optimism"))]
595 #[test]
596 fn network_args_default_sanity_test() {
597 let default_args = NetworkArgs::default();
598 let args = CommandParser::<NetworkArgs>::parse_from(["reth"]).args;
599
600 assert_eq!(args, default_args);
601 }
602}