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,
16 UnifiedStatus,
17};
18use reth_ethereum_forks::{ForkFilter, Head};
19use reth_network_peers::{mainnet_nodes, pk2id, sepolia_nodes, PeerId, TrustedPeer};
20use reth_network_types::{PeersConfig, SessionsConfig};
21use reth_storage_api::{noop::NoopProvider, BlockNumReader, BlockReader, HeaderProvider};
22use reth_tasks::{TaskSpawner, TokioTaskExecutor};
23use secp256k1::SECP256K1;
24use std::{collections::HashSet, net::SocketAddr, sync::Arc};
25
26use crate::protocol::{IntoRlpxSubProtocol, RlpxSubProtocols};
28pub use secp256k1::SecretKey;
29
30pub fn rng_secret_key() -> SecretKey {
32 SecretKey::new(&mut rand_08::thread_rng())
33}
34
35#[derive(Debug)]
37pub struct NetworkConfig<C, N: NetworkPrimitives = EthNetworkPrimitives> {
38 pub client: C,
43 pub secret_key: SecretKey,
45 pub boot_nodes: HashSet<TrustedPeer>,
47 pub dns_discovery_config: Option<DnsDiscoveryConfig>,
49 pub discovery_v4_addr: SocketAddr,
51 pub discovery_v4_config: Option<Discv4Config>,
53 pub discovery_v5_config: Option<reth_discv5::Config>,
55 pub listener_addr: SocketAddr,
57 pub peers_config: PeersConfig,
59 pub sessions_config: SessionsConfig,
61 pub chain_id: u64,
63 pub fork_filter: ForkFilter,
70 pub block_import: Box<dyn BlockImport<N::Block>>,
72 pub network_mode: NetworkMode,
74 pub executor: Box<dyn TaskSpawner>,
76 pub status: UnifiedStatus,
78 pub hello_message: HelloMessageWithProtocols,
80 pub extra_protocols: RlpxSubProtocols,
82 pub tx_gossip_disabled: bool,
84 pub transactions_manager_config: TransactionsManagerConfig,
86 pub nat: Option<NatResolver>,
88 pub handshake: Arc<dyn EthRlpxHandshake>,
93}
94
95impl<N: NetworkPrimitives> NetworkConfig<(), N> {
98 pub fn builder(secret_key: SecretKey) -> NetworkConfigBuilder<N> {
100 NetworkConfigBuilder::new(secret_key)
101 }
102
103 pub fn builder_with_rng_secret_key() -> NetworkConfigBuilder<N> {
105 NetworkConfigBuilder::with_rng_secret_key()
106 }
107}
108
109impl<C, N: NetworkPrimitives> NetworkConfig<C, N> {
110 pub fn new(client: C, secret_key: SecretKey) -> Self
112 where
113 C: ChainSpecProvider<ChainSpec: Hardforks>,
114 {
115 NetworkConfig::builder(secret_key).build(client)
116 }
117
118 pub fn apply<F>(self, f: F) -> Self
120 where
121 F: FnOnce(Self) -> Self,
122 {
123 f(self)
124 }
125
126 pub fn set_discovery_v4(mut self, discovery_config: Discv4Config) -> Self {
128 self.discovery_v4_config = Some(discovery_config);
129 self
130 }
131
132 pub const fn set_listener_addr(mut self, listener_addr: SocketAddr) -> Self {
134 self.listener_addr = listener_addr;
135 self
136 }
137
138 pub const fn listener_addr(&self) -> &SocketAddr {
140 &self.listener_addr
141 }
142}
143
144impl<C, N> NetworkConfig<C, N>
145where
146 C: BlockNumReader + 'static,
147 N: NetworkPrimitives,
148{
149 pub async fn manager(self) -> Result<NetworkManager<N>, NetworkError> {
151 NetworkManager::new(self).await
152 }
153}
154
155impl<C, N> NetworkConfig<C, N>
156where
157 N: NetworkPrimitives,
158 C: BlockReader<Block = N::Block, Receipt = N::Receipt, Header = N::BlockHeader>
159 + HeaderProvider
160 + Clone
161 + Unpin
162 + 'static,
163{
164 pub async fn start_network(self) -> Result<NetworkHandle<N>, NetworkError> {
166 let client = self.client.clone();
167 let (handle, network, _txpool, eth) = NetworkManager::builder::<C>(self)
168 .await?
169 .request_handler::<C>(client)
170 .split_with_handle();
171
172 tokio::task::spawn(network);
173 tokio::task::spawn(eth);
174 Ok(handle)
175 }
176}
177
178#[derive(Debug)]
180pub struct NetworkConfigBuilder<N: NetworkPrimitives = EthNetworkPrimitives> {
181 secret_key: SecretKey,
183 dns_discovery_config: Option<DnsDiscoveryConfig>,
185 discovery_v4_builder: Option<Discv4ConfigBuilder>,
187 discovery_v5_builder: Option<reth_discv5::ConfigBuilder>,
189 boot_nodes: HashSet<TrustedPeer>,
191 discovery_addr: Option<SocketAddr>,
193 listener_addr: Option<SocketAddr>,
195 peers_config: Option<PeersConfig>,
197 sessions_config: Option<SessionsConfig>,
199 network_mode: NetworkMode,
201 executor: Option<Box<dyn TaskSpawner>>,
203 hello_message: Option<HelloMessageWithProtocols>,
205 extra_protocols: RlpxSubProtocols,
207 head: Option<Head>,
209 tx_gossip_disabled: bool,
211 block_import: Option<Box<dyn BlockImport<N::Block>>>,
213 transactions_manager_config: TransactionsManagerConfig,
215 nat: Option<NatResolver>,
217 handshake: Arc<dyn EthRlpxHandshake>,
220}
221
222impl NetworkConfigBuilder<EthNetworkPrimitives> {
223 pub fn eth(secret_key: SecretKey) -> Self {
225 Self::new(secret_key)
226 }
227}
228
229#[expect(missing_docs)]
232impl<N: NetworkPrimitives> NetworkConfigBuilder<N> {
233 pub fn with_rng_secret_key() -> Self {
235 Self::new(rng_secret_key())
236 }
237
238 pub fn new(secret_key: SecretKey) -> Self {
240 Self {
241 secret_key,
242 dns_discovery_config: Some(Default::default()),
243 discovery_v4_builder: Some(Default::default()),
244 discovery_v5_builder: None,
245 boot_nodes: Default::default(),
246 discovery_addr: None,
247 listener_addr: None,
248 peers_config: None,
249 sessions_config: None,
250 network_mode: Default::default(),
251 executor: None,
252 hello_message: None,
253 extra_protocols: Default::default(),
254 head: None,
255 tx_gossip_disabled: false,
256 block_import: None,
257 transactions_manager_config: Default::default(),
258 nat: None,
259 handshake: Arc::new(EthHandshake::default()),
260 }
261 }
262
263 pub fn apply<F>(self, f: F) -> Self
265 where
266 F: FnOnce(Self) -> Self,
267 {
268 f(self)
269 }
270
271 pub fn get_peer_id(&self) -> PeerId {
273 pk2id(&self.secret_key.public_key(SECP256K1))
274 }
275
276 pub const fn secret_key(&self) -> &SecretKey {
278 &self.secret_key
279 }
280
281 pub const fn network_mode(mut self, network_mode: NetworkMode) -> Self {
283 self.network_mode = network_mode;
284 self
285 }
286
287 pub const fn with_pow(self) -> Self {
295 self.network_mode(NetworkMode::Work)
296 }
297
298 pub const fn set_head(mut self, head: Head) -> Self {
304 self.head = Some(head);
305 self
306 }
307
308 pub fn hello_message(mut self, hello_message: HelloMessageWithProtocols) -> Self {
319 self.hello_message = Some(hello_message);
320 self
321 }
322
323 pub fn peer_config(mut self, config: PeersConfig) -> Self {
325 self.peers_config = Some(config);
326 self
327 }
328
329 pub fn with_task_executor(mut self, executor: Box<dyn TaskSpawner>) -> Self {
333 self.executor = Some(executor);
334 self
335 }
336
337 pub const fn sessions_config(mut self, config: SessionsConfig) -> Self {
339 self.sessions_config = Some(config);
340 self
341 }
342
343 pub const fn transactions_manager_config(mut self, config: TransactionsManagerConfig) -> Self {
345 self.transactions_manager_config = config;
346 self
347 }
348
349 pub const fn set_addrs(self, addr: SocketAddr) -> Self {
357 self.listener_addr(addr).discovery_addr(addr)
358 }
359
360 pub const fn listener_addr(mut self, listener_addr: SocketAddr) -> Self {
364 self.listener_addr = Some(listener_addr);
365 self
366 }
367
368 pub fn listener_port(mut self, port: u16) -> Self {
372 self.listener_addr.get_or_insert(DEFAULT_DISCOVERY_ADDRESS).set_port(port);
373 self
374 }
375
376 pub const fn discovery_addr(mut self, discovery_addr: SocketAddr) -> Self {
378 self.discovery_addr = Some(discovery_addr);
379 self
380 }
381
382 pub fn discovery_port(mut self, port: u16) -> Self {
386 self.discovery_addr.get_or_insert(DEFAULT_DISCOVERY_ADDRESS).set_port(port);
387 self
388 }
389
390 pub fn with_unused_ports(self) -> Self {
393 self.with_unused_discovery_port().with_unused_listener_port()
394 }
395
396 pub fn with_unused_discovery_port(self) -> Self {
399 self.discovery_port(0)
400 }
401
402 pub fn with_unused_listener_port(self) -> Self {
405 self.listener_port(0)
406 }
407
408 pub fn external_ip_resolver(mut self, resolver: NatResolver) -> Self {
415 self.discovery_v4_builder
416 .get_or_insert_with(Discv4Config::builder)
417 .external_ip_resolver(Some(resolver));
418 self.nat = Some(resolver);
419 self
420 }
421
422 pub fn discovery(mut self, builder: Discv4ConfigBuilder) -> Self {
424 self.discovery_v4_builder = Some(builder);
425 self
426 }
427
428 pub fn discovery_v5(mut self, builder: reth_discv5::ConfigBuilder) -> Self {
430 self.discovery_v5_builder = Some(builder);
431 self
432 }
433
434 pub fn dns_discovery(mut self, config: DnsDiscoveryConfig) -> Self {
436 self.dns_discovery_config = Some(config);
437 self
438 }
439
440 pub fn mainnet_boot_nodes(self) -> Self {
442 self.boot_nodes(mainnet_nodes())
443 }
444
445 pub fn sepolia_boot_nodes(self) -> Self {
447 self.boot_nodes(sepolia_nodes())
448 }
449
450 pub fn boot_nodes<T: Into<TrustedPeer>>(mut self, nodes: impl IntoIterator<Item = T>) -> Self {
452 self.boot_nodes = nodes.into_iter().map(Into::into).collect();
453 self
454 }
455
456 pub fn boot_nodes_iter(&self) -> impl Iterator<Item = &TrustedPeer> + '_ {
458 self.boot_nodes.iter()
459 }
460
461 pub fn disable_dns_discovery(mut self) -> Self {
463 self.dns_discovery_config = None;
464 self
465 }
466
467 pub const fn disable_nat(mut self) -> Self {
469 self.nat = None;
470 self
471 }
472
473 pub fn disable_discovery(self) -> Self {
475 self.disable_discv4_discovery().disable_discv5_discovery().disable_dns_discovery()
476 }
477
478 pub fn disable_discovery_if(self, disable: bool) -> Self {
480 if disable {
481 self.disable_discovery()
482 } else {
483 self
484 }
485 }
486
487 pub fn disable_discv4_discovery(mut self) -> Self {
489 self.discovery_v4_builder = None;
490 self
491 }
492
493 pub fn disable_discv5_discovery(mut self) -> Self {
495 self.discovery_v5_builder = None;
496 self
497 }
498
499 pub fn disable_dns_discovery_if(self, disable: bool) -> Self {
501 if disable {
502 self.disable_dns_discovery()
503 } else {
504 self
505 }
506 }
507
508 pub fn disable_discv4_discovery_if(self, disable: bool) -> Self {
510 if disable {
511 self.disable_discv4_discovery()
512 } else {
513 self
514 }
515 }
516
517 pub fn disable_discv5_discovery_if(self, disable: bool) -> Self {
519 if disable {
520 self.disable_discv5_discovery()
521 } else {
522 self
523 }
524 }
525
526 pub fn add_rlpx_sub_protocol(mut self, protocol: impl IntoRlpxSubProtocol) -> Self {
528 self.extra_protocols.push(protocol);
529 self
530 }
531
532 pub const fn disable_tx_gossip(mut self, disable_tx_gossip: bool) -> Self {
534 self.tx_gossip_disabled = disable_tx_gossip;
535 self
536 }
537
538 pub fn block_import(mut self, block_import: Box<dyn BlockImport<N::Block>>) -> Self {
540 self.block_import = Some(block_import);
541 self
542 }
543
544 pub fn build_with_noop_provider<ChainSpec>(
547 self,
548 chain_spec: Arc<ChainSpec>,
549 ) -> NetworkConfig<NoopProvider<ChainSpec>, N>
550 where
551 ChainSpec: EthChainSpec + Hardforks + 'static,
552 {
553 self.build(NoopProvider::eth(chain_spec))
554 }
555
556 pub const fn add_nat(mut self, nat: Option<NatResolver>) -> Self {
558 self.nat = nat;
559 self
560 }
561
562 pub fn eth_rlpx_handshake(mut self, handshake: Arc<dyn EthRlpxHandshake>) -> Self {
564 self.handshake = handshake;
565 self
566 }
567
568 pub fn build<C>(self, client: C) -> NetworkConfig<C, N>
575 where
576 C: ChainSpecProvider<ChainSpec: Hardforks>,
577 {
578 let peer_id = self.get_peer_id();
579 let chain_spec = client.chain_spec();
580 let Self {
581 secret_key,
582 mut dns_discovery_config,
583 discovery_v4_builder,
584 mut discovery_v5_builder,
585 boot_nodes,
586 discovery_addr,
587 listener_addr,
588 peers_config,
589 sessions_config,
590 network_mode,
591 executor,
592 hello_message,
593 extra_protocols,
594 head,
595 tx_gossip_disabled,
596 block_import,
597 transactions_manager_config,
598 nat,
599 handshake,
600 } = self;
601
602 let head = head.unwrap_or_else(|| Head {
603 hash: chain_spec.genesis_hash(),
604 number: 0,
605 timestamp: chain_spec.genesis().timestamp,
606 difficulty: chain_spec.genesis().difficulty,
607 total_difficulty: chain_spec.genesis().difficulty,
608 });
609
610 discovery_v5_builder = discovery_v5_builder.map(|mut builder| {
611 if let Some(network_stack_id) = NetworkStackId::id(&chain_spec) {
612 let fork_id = chain_spec.fork_id(&head);
613 builder = builder.fork(network_stack_id, fork_id)
614 }
615
616 builder
617 });
618
619 let listener_addr = listener_addr.unwrap_or(DEFAULT_DISCOVERY_ADDRESS);
620
621 let mut hello_message =
622 hello_message.unwrap_or_else(|| HelloMessage::builder(peer_id).build());
623 hello_message.port = listener_addr.port();
624
625 let status = UnifiedStatus::spec_builder(&chain_spec, &head);
627
628 let fork_filter = chain_spec.fork_filter(head);
630
631 let chain_id = chain_spec.chain().id();
633
634 if let Some(dns_networks) =
636 dns_discovery_config.as_mut().and_then(|c| c.bootstrap_dns_networks.as_mut())
637 {
638 if dns_networks.is_empty() {
639 if let Some(link) = chain_spec.chain().public_dns_network_protocol() {
640 dns_networks.insert(link.parse().expect("is valid DNS link entry"));
641 }
642 }
643 }
644
645 NetworkConfig {
646 client,
647 secret_key,
648 boot_nodes,
649 dns_discovery_config,
650 discovery_v4_config: discovery_v4_builder.map(|builder| builder.build()),
651 discovery_v5_config: discovery_v5_builder.map(|builder| builder.build()),
652 discovery_v4_addr: discovery_addr.unwrap_or(DEFAULT_DISCOVERY_ADDRESS),
653 listener_addr,
654 peers_config: peers_config.unwrap_or_default(),
655 sessions_config: sessions_config.unwrap_or_default(),
656 chain_id,
657 block_import: block_import.unwrap_or_else(|| Box::<ProofOfStakeBlockImport>::default()),
658 network_mode,
659 executor: executor.unwrap_or_else(|| Box::<TokioTaskExecutor>::default()),
660 status,
661 hello_message,
662 extra_protocols,
663 fork_filter,
664 tx_gossip_disabled,
665 transactions_manager_config,
666 nat,
667 handshake,
668 }
669 }
670}
671
672#[derive(Debug, Clone, Copy, Eq, PartialEq, Default)]
678#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
679pub enum NetworkMode {
680 Work,
682 #[default]
684 Stake,
685}
686
687impl NetworkMode {
690 pub const fn is_stake(&self) -> bool {
692 matches!(self, Self::Stake)
693 }
694}
695
696#[cfg(test)]
697mod tests {
698 use super::*;
699 use alloy_eips::eip2124::ForkHash;
700 use alloy_genesis::Genesis;
701 use alloy_primitives::U256;
702 use reth_chainspec::{
703 Chain, ChainSpecBuilder, EthereumHardfork, ForkCondition, ForkId, MAINNET,
704 };
705 use reth_discv5::build_local_enr;
706 use reth_dns_discovery::tree::LinkEntry;
707 use reth_storage_api::noop::NoopProvider;
708 use std::{net::Ipv4Addr, sync::Arc};
709
710 fn builder() -> NetworkConfigBuilder {
711 let secret_key = SecretKey::new(&mut rand_08::thread_rng());
712 NetworkConfigBuilder::new(secret_key)
713 }
714
715 #[test]
716 fn test_network_dns_defaults() {
717 let config = builder().build(NoopProvider::default());
718
719 let dns = config.dns_discovery_config.unwrap();
720 let bootstrap_nodes = dns.bootstrap_dns_networks.unwrap();
721 let mainnet_dns: LinkEntry =
722 Chain::mainnet().public_dns_network_protocol().unwrap().parse().unwrap();
723 assert!(bootstrap_nodes.contains(&mainnet_dns));
724 assert_eq!(bootstrap_nodes.len(), 1);
725 }
726
727 #[test]
728 fn test_network_fork_filter_default() {
729 let mut chain_spec = Arc::clone(&MAINNET);
730
731 Arc::make_mut(&mut chain_spec).hardforks = Default::default();
733
734 let genesis_fork_hash = ForkHash::from(chain_spec.genesis_hash());
736
737 let config = builder().build_with_noop_provider(chain_spec);
739
740 let status = config.status;
741 let fork_filter = config.fork_filter;
742
743 assert_eq!(status.forkid.next, 0);
745
746 assert_eq!(fork_filter.current().next, 0);
748
749 assert_eq!(status.forkid.hash, genesis_fork_hash);
751 assert_eq!(fork_filter.current().hash, genesis_fork_hash);
752 }
753
754 #[test]
755 fn test_discv5_fork_id_default() {
756 const GENESIS_TIME: u64 = 151_515;
757
758 let genesis = Genesis::default().with_timestamp(GENESIS_TIME);
759
760 let active_fork = (EthereumHardfork::Shanghai, ForkCondition::Timestamp(GENESIS_TIME));
761 let future_fork = (EthereumHardfork::Cancun, ForkCondition::Timestamp(GENESIS_TIME + 1));
762
763 let chain_spec = ChainSpecBuilder::default()
764 .chain(Chain::dev())
765 .genesis(genesis)
766 .with_fork(active_fork.0, active_fork.1)
767 .with_fork(future_fork.0, future_fork.1)
768 .build();
769
770 let genesis_fork_hash = ForkHash::from(chain_spec.genesis_hash());
772 let fork_id = ForkId { hash: genesis_fork_hash, next: GENESIS_TIME + 1 };
773 assert_eq!(
775 fork_id,
776 chain_spec.fork_id(&Head {
777 hash: chain_spec.genesis_hash(),
778 number: 0,
779 timestamp: GENESIS_TIME,
780 difficulty: U256::ZERO,
781 total_difficulty: U256::ZERO,
782 })
783 );
784 assert_ne!(fork_id, chain_spec.latest_fork_id());
785
786 let fork_key = b"odyssey";
788 let config = builder()
789 .discovery_v5(
790 reth_discv5::Config::builder((Ipv4Addr::LOCALHOST, 30303).into())
791 .fork(fork_key, fork_id),
792 )
793 .build_with_noop_provider(Arc::new(chain_spec));
794
795 let (local_enr, _, _, _) = build_local_enr(
796 &config.secret_key,
797 &config.discovery_v5_config.expect("should build config"),
798 );
799
800 let advertised_fork_id = *local_enr
803 .get_decodable::<Vec<ForkId>>(fork_key)
804 .expect("should read 'odyssey'")
805 .expect("should decode fork id list")
806 .first()
807 .expect("should be non-empty");
808
809 assert_eq!(advertised_fork_id, fork_id);
810 }
811}