1use crate::{
4 error::NetworkError,
5 import::{BlockImport, ProofOfStakeBlockImport},
6 transactions::TransactionsManagerConfig,
7 NetworkHandle, NetworkManager,
8};
9use reth_chainspec::{ChainSpecProvider, EthChainSpec, Hardforks};
10use reth_discv4::{Discv4Config, Discv4ConfigBuilder, NatResolver, DEFAULT_DISCOVERY_ADDRESS};
11use reth_discv5::NetworkStackId;
12use reth_dns_discovery::DnsDiscoveryConfig;
13use reth_eth_wire::{
14 handshake::{EthHandshake, EthRlpxHandshake},
15 EthNetworkPrimitives, HelloMessage, HelloMessageWithProtocols, NetworkPrimitives, Status,
16};
17use reth_ethereum_forks::{ForkFilter, Head};
18use reth_network_peers::{mainnet_nodes, pk2id, sepolia_nodes, PeerId, TrustedPeer};
19use reth_network_types::{PeersConfig, SessionsConfig};
20use reth_storage_api::{noop::NoopProvider, BlockNumReader, BlockReader, HeaderProvider};
21use reth_tasks::{TaskSpawner, TokioTaskExecutor};
22use secp256k1::SECP256K1;
23use std::{collections::HashSet, net::SocketAddr, sync::Arc};
24
25use crate::protocol::{IntoRlpxSubProtocol, RlpxSubProtocols};
27pub use secp256k1::SecretKey;
28
29pub fn rng_secret_key() -> SecretKey {
31 SecretKey::new(&mut rand_08::thread_rng())
32}
33
34#[derive(Debug)]
36pub struct NetworkConfig<C, N: NetworkPrimitives = EthNetworkPrimitives> {
37 pub client: C,
42 pub secret_key: SecretKey,
44 pub boot_nodes: HashSet<TrustedPeer>,
46 pub dns_discovery_config: Option<DnsDiscoveryConfig>,
48 pub discovery_v4_addr: SocketAddr,
50 pub discovery_v4_config: Option<Discv4Config>,
52 pub discovery_v5_config: Option<reth_discv5::Config>,
54 pub listener_addr: SocketAddr,
56 pub peers_config: PeersConfig,
58 pub sessions_config: SessionsConfig,
60 pub chain_id: u64,
62 pub fork_filter: ForkFilter,
69 pub block_import: Box<dyn BlockImport<N::Block>>,
71 pub network_mode: NetworkMode,
73 pub executor: Box<dyn TaskSpawner>,
75 pub status: Status,
77 pub hello_message: HelloMessageWithProtocols,
79 pub extra_protocols: RlpxSubProtocols,
81 pub tx_gossip_disabled: bool,
83 pub transactions_manager_config: TransactionsManagerConfig,
85 pub nat: Option<NatResolver>,
87 pub handshake: Arc<dyn EthRlpxHandshake>,
92}
93
94impl<N: NetworkPrimitives> NetworkConfig<(), N> {
97 pub fn builder(secret_key: SecretKey) -> NetworkConfigBuilder<N> {
99 NetworkConfigBuilder::new(secret_key)
100 }
101
102 pub fn builder_with_rng_secret_key() -> NetworkConfigBuilder<N> {
104 NetworkConfigBuilder::with_rng_secret_key()
105 }
106}
107
108impl<C, N: NetworkPrimitives> NetworkConfig<C, N> {
109 pub fn new(client: C, secret_key: SecretKey) -> Self
111 where
112 C: ChainSpecProvider<ChainSpec: Hardforks>,
113 {
114 NetworkConfig::builder(secret_key).build(client)
115 }
116
117 pub fn apply<F>(self, f: F) -> Self
119 where
120 F: FnOnce(Self) -> Self,
121 {
122 f(self)
123 }
124
125 pub fn set_discovery_v4(mut self, discovery_config: Discv4Config) -> Self {
127 self.discovery_v4_config = Some(discovery_config);
128 self
129 }
130
131 pub const fn set_listener_addr(mut self, listener_addr: SocketAddr) -> Self {
133 self.listener_addr = listener_addr;
134 self
135 }
136
137 pub const fn listener_addr(&self) -> &SocketAddr {
139 &self.listener_addr
140 }
141}
142
143impl<C, N> NetworkConfig<C, N>
144where
145 C: BlockNumReader + 'static,
146 N: NetworkPrimitives,
147{
148 pub async fn manager(self) -> Result<NetworkManager<N>, NetworkError> {
150 NetworkManager::new(self).await
151 }
152}
153
154impl<C, N> NetworkConfig<C, N>
155where
156 N: NetworkPrimitives,
157 C: BlockReader<Block = N::Block, Receipt = N::Receipt, Header = N::BlockHeader>
158 + HeaderProvider
159 + Clone
160 + Unpin
161 + 'static,
162{
163 pub async fn start_network(self) -> Result<NetworkHandle<N>, NetworkError> {
165 let client = self.client.clone();
166 let (handle, network, _txpool, eth) = NetworkManager::builder::<C>(self)
167 .await?
168 .request_handler::<C>(client)
169 .split_with_handle();
170
171 tokio::task::spawn(network);
172 tokio::task::spawn(eth);
173 Ok(handle)
174 }
175}
176
177#[derive(Debug)]
179pub struct NetworkConfigBuilder<N: NetworkPrimitives = EthNetworkPrimitives> {
180 secret_key: SecretKey,
182 dns_discovery_config: Option<DnsDiscoveryConfig>,
184 discovery_v4_builder: Option<Discv4ConfigBuilder>,
186 discovery_v5_builder: Option<reth_discv5::ConfigBuilder>,
188 boot_nodes: HashSet<TrustedPeer>,
190 discovery_addr: Option<SocketAddr>,
192 listener_addr: Option<SocketAddr>,
194 peers_config: Option<PeersConfig>,
196 sessions_config: Option<SessionsConfig>,
198 network_mode: NetworkMode,
200 executor: Option<Box<dyn TaskSpawner>>,
202 hello_message: Option<HelloMessageWithProtocols>,
204 extra_protocols: RlpxSubProtocols,
206 head: Option<Head>,
208 tx_gossip_disabled: bool,
210 block_import: Option<Box<dyn BlockImport<N::Block>>>,
212 transactions_manager_config: TransactionsManagerConfig,
214 nat: Option<NatResolver>,
216 handshake: Arc<dyn EthRlpxHandshake>,
219}
220
221impl NetworkConfigBuilder<EthNetworkPrimitives> {
222 pub fn eth(secret_key: SecretKey) -> Self {
224 Self::new(secret_key)
225 }
226}
227
228#[expect(missing_docs)]
231impl<N: NetworkPrimitives> NetworkConfigBuilder<N> {
232 pub fn with_rng_secret_key() -> Self {
234 Self::new(rng_secret_key())
235 }
236
237 pub fn new(secret_key: SecretKey) -> Self {
239 Self {
240 secret_key,
241 dns_discovery_config: Some(Default::default()),
242 discovery_v4_builder: Some(Default::default()),
243 discovery_v5_builder: None,
244 boot_nodes: Default::default(),
245 discovery_addr: None,
246 listener_addr: None,
247 peers_config: None,
248 sessions_config: None,
249 network_mode: Default::default(),
250 executor: None,
251 hello_message: None,
252 extra_protocols: Default::default(),
253 head: None,
254 tx_gossip_disabled: false,
255 block_import: None,
256 transactions_manager_config: Default::default(),
257 nat: None,
258 handshake: Arc::new(EthHandshake::default()),
259 }
260 }
261
262 pub fn apply<F>(self, f: F) -> Self
264 where
265 F: FnOnce(Self) -> Self,
266 {
267 f(self)
268 }
269
270 pub fn get_peer_id(&self) -> PeerId {
272 pk2id(&self.secret_key.public_key(SECP256K1))
273 }
274
275 pub const fn secret_key(&self) -> &SecretKey {
277 &self.secret_key
278 }
279
280 pub const fn network_mode(mut self, network_mode: NetworkMode) -> Self {
282 self.network_mode = network_mode;
283 self
284 }
285
286 pub const fn with_pow(self) -> Self {
294 self.network_mode(NetworkMode::Work)
295 }
296
297 pub const fn set_head(mut self, head: Head) -> Self {
303 self.head = Some(head);
304 self
305 }
306
307 pub fn hello_message(mut self, hello_message: HelloMessageWithProtocols) -> Self {
318 self.hello_message = Some(hello_message);
319 self
320 }
321
322 pub fn peer_config(mut self, config: PeersConfig) -> Self {
324 self.peers_config = Some(config);
325 self
326 }
327
328 pub fn with_task_executor(mut self, executor: Box<dyn TaskSpawner>) -> Self {
332 self.executor = Some(executor);
333 self
334 }
335
336 pub const fn sessions_config(mut self, config: SessionsConfig) -> Self {
338 self.sessions_config = Some(config);
339 self
340 }
341
342 pub const fn transactions_manager_config(mut self, config: TransactionsManagerConfig) -> Self {
344 self.transactions_manager_config = config;
345 self
346 }
347
348 pub const fn set_addrs(self, addr: SocketAddr) -> Self {
356 self.listener_addr(addr).discovery_addr(addr)
357 }
358
359 pub const fn listener_addr(mut self, listener_addr: SocketAddr) -> Self {
363 self.listener_addr = Some(listener_addr);
364 self
365 }
366
367 pub fn listener_port(mut self, port: u16) -> Self {
371 self.listener_addr.get_or_insert(DEFAULT_DISCOVERY_ADDRESS).set_port(port);
372 self
373 }
374
375 pub const fn discovery_addr(mut self, discovery_addr: SocketAddr) -> Self {
377 self.discovery_addr = Some(discovery_addr);
378 self
379 }
380
381 pub fn discovery_port(mut self, port: u16) -> Self {
385 self.discovery_addr.get_or_insert(DEFAULT_DISCOVERY_ADDRESS).set_port(port);
386 self
387 }
388
389 pub fn with_unused_ports(self) -> Self {
392 self.with_unused_discovery_port().with_unused_listener_port()
393 }
394
395 pub fn with_unused_discovery_port(self) -> Self {
398 self.discovery_port(0)
399 }
400
401 pub fn with_unused_listener_port(self) -> Self {
404 self.listener_port(0)
405 }
406
407 pub fn external_ip_resolver(mut self, resolver: NatResolver) -> Self {
414 self.discovery_v4_builder
415 .get_or_insert_with(Discv4Config::builder)
416 .external_ip_resolver(Some(resolver));
417 self.nat = Some(resolver);
418 self
419 }
420
421 pub fn discovery(mut self, builder: Discv4ConfigBuilder) -> Self {
423 self.discovery_v4_builder = Some(builder);
424 self
425 }
426
427 pub fn discovery_v5(mut self, builder: reth_discv5::ConfigBuilder) -> Self {
429 self.discovery_v5_builder = Some(builder);
430 self
431 }
432
433 pub fn dns_discovery(mut self, config: DnsDiscoveryConfig) -> Self {
435 self.dns_discovery_config = Some(config);
436 self
437 }
438
439 pub fn mainnet_boot_nodes(self) -> Self {
441 self.boot_nodes(mainnet_nodes())
442 }
443
444 pub fn sepolia_boot_nodes(self) -> Self {
446 self.boot_nodes(sepolia_nodes())
447 }
448
449 pub fn boot_nodes<T: Into<TrustedPeer>>(mut self, nodes: impl IntoIterator<Item = T>) -> Self {
451 self.boot_nodes = nodes.into_iter().map(Into::into).collect();
452 self
453 }
454
455 pub fn boot_nodes_iter(&self) -> impl Iterator<Item = &TrustedPeer> + '_ {
457 self.boot_nodes.iter()
458 }
459
460 pub fn disable_dns_discovery(mut self) -> Self {
462 self.dns_discovery_config = None;
463 self
464 }
465
466 pub const fn disable_nat(mut self) -> Self {
468 self.nat = None;
469 self
470 }
471
472 pub fn disable_discovery(self) -> Self {
474 self.disable_discv4_discovery().disable_discv5_discovery().disable_dns_discovery()
475 }
476
477 pub fn disable_discovery_if(self, disable: bool) -> Self {
479 if disable {
480 self.disable_discovery()
481 } else {
482 self
483 }
484 }
485
486 pub fn disable_discv4_discovery(mut self) -> Self {
488 self.discovery_v4_builder = None;
489 self
490 }
491
492 pub fn disable_discv5_discovery(mut self) -> Self {
494 self.discovery_v5_builder = None;
495 self
496 }
497
498 pub fn disable_dns_discovery_if(self, disable: bool) -> Self {
500 if disable {
501 self.disable_dns_discovery()
502 } else {
503 self
504 }
505 }
506
507 pub fn disable_discv4_discovery_if(self, disable: bool) -> Self {
509 if disable {
510 self.disable_discv4_discovery()
511 } else {
512 self
513 }
514 }
515
516 pub fn disable_discv5_discovery_if(self, disable: bool) -> Self {
518 if disable {
519 self.disable_discv5_discovery()
520 } else {
521 self
522 }
523 }
524
525 pub fn add_rlpx_sub_protocol(mut self, protocol: impl IntoRlpxSubProtocol) -> Self {
527 self.extra_protocols.push(protocol);
528 self
529 }
530
531 pub const fn disable_tx_gossip(mut self, disable_tx_gossip: bool) -> Self {
533 self.tx_gossip_disabled = disable_tx_gossip;
534 self
535 }
536
537 pub fn block_import(mut self, block_import: Box<dyn BlockImport<N::Block>>) -> Self {
539 self.block_import = Some(block_import);
540 self
541 }
542
543 pub fn build_with_noop_provider<ChainSpec>(
546 self,
547 chain_spec: Arc<ChainSpec>,
548 ) -> NetworkConfig<NoopProvider<ChainSpec>, N>
549 where
550 ChainSpec: EthChainSpec + Hardforks + 'static,
551 {
552 self.build(NoopProvider::eth(chain_spec))
553 }
554
555 pub const fn add_nat(mut self, nat: Option<NatResolver>) -> Self {
557 self.nat = nat;
558 self
559 }
560
561 pub fn eth_rlpx_handshake(mut self, handshake: Arc<dyn EthRlpxHandshake>) -> Self {
563 self.handshake = handshake;
564 self
565 }
566
567 pub fn build<C>(self, client: C) -> NetworkConfig<C, N>
574 where
575 C: ChainSpecProvider<ChainSpec: Hardforks>,
576 {
577 let peer_id = self.get_peer_id();
578 let chain_spec = client.chain_spec();
579 let Self {
580 secret_key,
581 mut dns_discovery_config,
582 discovery_v4_builder,
583 mut discovery_v5_builder,
584 boot_nodes,
585 discovery_addr,
586 listener_addr,
587 peers_config,
588 sessions_config,
589 network_mode,
590 executor,
591 hello_message,
592 extra_protocols,
593 head,
594 tx_gossip_disabled,
595 block_import,
596 transactions_manager_config,
597 nat,
598 handshake,
599 } = self;
600
601 let head = head.unwrap_or_else(|| Head {
602 hash: chain_spec.genesis_hash(),
603 number: 0,
604 timestamp: chain_spec.genesis().timestamp,
605 difficulty: chain_spec.genesis().difficulty,
606 total_difficulty: chain_spec.genesis().difficulty,
607 });
608
609 discovery_v5_builder = discovery_v5_builder.map(|mut builder| {
610 if let Some(network_stack_id) = NetworkStackId::id(&chain_spec) {
611 let fork_id = chain_spec.fork_id(&head);
612 builder = builder.fork(network_stack_id, fork_id)
613 }
614
615 builder
616 });
617
618 let listener_addr = listener_addr.unwrap_or(DEFAULT_DISCOVERY_ADDRESS);
619
620 let mut hello_message =
621 hello_message.unwrap_or_else(|| HelloMessage::builder(peer_id).build());
622 hello_message.port = listener_addr.port();
623
624 let status = Status::spec_builder(&chain_spec, &head).build();
626
627 let fork_filter = chain_spec.fork_filter(head);
629
630 let chain_id = chain_spec.chain().id();
632
633 if let Some(dns_networks) =
635 dns_discovery_config.as_mut().and_then(|c| c.bootstrap_dns_networks.as_mut())
636 {
637 if dns_networks.is_empty() {
638 if let Some(link) = chain_spec.chain().public_dns_network_protocol() {
639 dns_networks.insert(link.parse().expect("is valid DNS link entry"));
640 }
641 }
642 }
643
644 NetworkConfig {
645 client,
646 secret_key,
647 boot_nodes,
648 dns_discovery_config,
649 discovery_v4_config: discovery_v4_builder.map(|builder| builder.build()),
650 discovery_v5_config: discovery_v5_builder.map(|builder| builder.build()),
651 discovery_v4_addr: discovery_addr.unwrap_or(DEFAULT_DISCOVERY_ADDRESS),
652 listener_addr,
653 peers_config: peers_config.unwrap_or_default(),
654 sessions_config: sessions_config.unwrap_or_default(),
655 chain_id,
656 block_import: block_import.unwrap_or_else(|| Box::<ProofOfStakeBlockImport>::default()),
657 network_mode,
658 executor: executor.unwrap_or_else(|| Box::<TokioTaskExecutor>::default()),
659 status,
660 hello_message,
661 extra_protocols,
662 fork_filter,
663 tx_gossip_disabled,
664 transactions_manager_config,
665 nat,
666 handshake,
667 }
668 }
669}
670
671#[derive(Debug, Clone, Copy, Eq, PartialEq, Default)]
677#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
678pub enum NetworkMode {
679 Work,
681 #[default]
683 Stake,
684}
685
686impl NetworkMode {
689 pub const fn is_stake(&self) -> bool {
691 matches!(self, Self::Stake)
692 }
693}
694
695#[cfg(test)]
696mod tests {
697 use super::*;
698 use alloy_eips::eip2124::ForkHash;
699 use alloy_genesis::Genesis;
700 use alloy_primitives::U256;
701 use reth_chainspec::{
702 Chain, ChainSpecBuilder, EthereumHardfork, ForkCondition, ForkId, MAINNET,
703 };
704 use reth_discv5::build_local_enr;
705 use reth_dns_discovery::tree::LinkEntry;
706 use reth_storage_api::noop::NoopProvider;
707 use std::{net::Ipv4Addr, sync::Arc};
708
709 fn builder() -> NetworkConfigBuilder {
710 let secret_key = SecretKey::new(&mut rand_08::thread_rng());
711 NetworkConfigBuilder::new(secret_key)
712 }
713
714 #[test]
715 fn test_network_dns_defaults() {
716 let config = builder().build(NoopProvider::default());
717
718 let dns = config.dns_discovery_config.unwrap();
719 let bootstrap_nodes = dns.bootstrap_dns_networks.unwrap();
720 let mainnet_dns: LinkEntry =
721 Chain::mainnet().public_dns_network_protocol().unwrap().parse().unwrap();
722 assert!(bootstrap_nodes.contains(&mainnet_dns));
723 assert_eq!(bootstrap_nodes.len(), 1);
724 }
725
726 #[test]
727 fn test_network_fork_filter_default() {
728 let mut chain_spec = Arc::clone(&MAINNET);
729
730 Arc::make_mut(&mut chain_spec).hardforks = Default::default();
732
733 let genesis_fork_hash = ForkHash::from(chain_spec.genesis_hash());
735
736 let config = builder().build_with_noop_provider(chain_spec);
738
739 let status = config.status;
740 let fork_filter = config.fork_filter;
741
742 assert_eq!(status.forkid.next, 0);
744
745 assert_eq!(fork_filter.current().next, 0);
747
748 assert_eq!(status.forkid.hash, genesis_fork_hash);
750 assert_eq!(fork_filter.current().hash, genesis_fork_hash);
751 }
752
753 #[test]
754 fn test_discv5_fork_id_default() {
755 const GENESIS_TIME: u64 = 151_515;
756
757 let genesis = Genesis::default().with_timestamp(GENESIS_TIME);
758
759 let active_fork = (EthereumHardfork::Shanghai, ForkCondition::Timestamp(GENESIS_TIME));
760 let future_fork = (EthereumHardfork::Cancun, ForkCondition::Timestamp(GENESIS_TIME + 1));
761
762 let chain_spec = ChainSpecBuilder::default()
763 .chain(Chain::dev())
764 .genesis(genesis)
765 .with_fork(active_fork.0, active_fork.1)
766 .with_fork(future_fork.0, future_fork.1)
767 .build();
768
769 let genesis_fork_hash = ForkHash::from(chain_spec.genesis_hash());
771 let fork_id = ForkId { hash: genesis_fork_hash, next: GENESIS_TIME + 1 };
772 assert_eq!(
774 fork_id,
775 chain_spec.fork_id(&Head {
776 hash: chain_spec.genesis_hash(),
777 number: 0,
778 timestamp: GENESIS_TIME,
779 difficulty: U256::ZERO,
780 total_difficulty: U256::ZERO,
781 })
782 );
783 assert_ne!(fork_id, chain_spec.latest_fork_id());
784
785 let fork_key = b"odyssey";
787 let config = builder()
788 .discovery_v5(
789 reth_discv5::Config::builder((Ipv4Addr::LOCALHOST, 30303).into())
790 .fork(fork_key, fork_id),
791 )
792 .build_with_noop_provider(Arc::new(chain_spec));
793
794 let (local_enr, _, _, _) = build_local_enr(
795 &config.secret_key,
796 &config.discovery_v5_config.expect("should build config"),
797 );
798
799 let advertised_fork_id = *local_enr
802 .get_decodable::<Vec<ForkId>>(fork_key)
803 .expect("should read 'odyssey'")
804 .expect("should decode fork id list")
805 .first()
806 .expect("should be non-empty");
807
808 assert_eq!(advertised_fork_id, fork_id);
809 }
810}