1use crate::{
4 error::NetworkError,
5 import::{BlockImport, ProofOfStakeBlockImport},
6 transactions::TransactionsManagerConfig,
7 NetworkHandle, NetworkManager,
8};
9use alloy_primitives::B256;
10use reth_chainspec::{ChainSpecProvider, EthChainSpec, Hardforks};
11use reth_discv4::{Discv4Config, Discv4ConfigBuilder, NatResolver, DEFAULT_DISCOVERY_ADDRESS};
12use reth_discv5::NetworkStackId;
13use reth_dns_discovery::DnsDiscoveryConfig;
14use reth_eth_wire::{
15 handshake::{EthHandshake, EthRlpxHandshake},
16 EthNetworkPrimitives, HelloMessage, HelloMessageWithProtocols, NetworkPrimitives,
17 UnifiedStatus,
18};
19use reth_ethereum_forks::{ForkFilter, Head};
20use reth_network_peers::{mainnet_nodes, pk2id, sepolia_nodes, PeerId, TrustedPeer};
21use reth_network_types::{PeersConfig, SessionsConfig};
22use reth_storage_api::{noop::NoopProvider, BlockNumReader, BlockReader, HeaderProvider};
23use reth_tasks::{TaskSpawner, TokioTaskExecutor};
24use secp256k1::SECP256K1;
25use std::{collections::HashSet, net::SocketAddr, sync::Arc};
26
27use crate::{
29 protocol::{IntoRlpxSubProtocol, RlpxSubProtocols},
30 transactions::TransactionPropagationMode,
31};
32pub use secp256k1::SecretKey;
33
34pub fn rng_secret_key() -> SecretKey {
36 SecretKey::new(&mut rand_08::thread_rng())
37}
38
39#[derive(Debug)]
41pub struct NetworkConfig<C, N: NetworkPrimitives = EthNetworkPrimitives> {
42 pub client: C,
47 pub secret_key: SecretKey,
49 pub boot_nodes: HashSet<TrustedPeer>,
51 pub dns_discovery_config: Option<DnsDiscoveryConfig>,
53 pub discovery_v4_addr: SocketAddr,
55 pub discovery_v4_config: Option<Discv4Config>,
57 pub discovery_v5_config: Option<reth_discv5::Config>,
59 pub listener_addr: SocketAddr,
61 pub peers_config: PeersConfig,
63 pub sessions_config: SessionsConfig,
65 pub chain_id: u64,
67 pub fork_filter: ForkFilter,
74 pub block_import: Box<dyn BlockImport<N::NewBlockPayload>>,
76 pub network_mode: NetworkMode,
78 pub executor: Box<dyn TaskSpawner>,
80 pub status: UnifiedStatus,
82 pub hello_message: HelloMessageWithProtocols,
84 pub extra_protocols: RlpxSubProtocols,
86 pub tx_gossip_disabled: bool,
88 pub transactions_manager_config: TransactionsManagerConfig,
90 pub nat: Option<NatResolver>,
92 pub handshake: Arc<dyn EthRlpxHandshake>,
97 pub required_block_hashes: Vec<B256>,
100}
101
102impl<N: NetworkPrimitives> NetworkConfig<(), N> {
105 pub fn builder(secret_key: SecretKey) -> NetworkConfigBuilder<N> {
107 NetworkConfigBuilder::new(secret_key)
108 }
109
110 pub fn builder_with_rng_secret_key() -> NetworkConfigBuilder<N> {
112 NetworkConfigBuilder::with_rng_secret_key()
113 }
114}
115
116impl<C, N: NetworkPrimitives> NetworkConfig<C, N> {
117 pub fn new(client: C, secret_key: SecretKey) -> Self
119 where
120 C: ChainSpecProvider<ChainSpec: Hardforks>,
121 {
122 NetworkConfig::builder(secret_key).build(client)
123 }
124
125 pub fn apply<F>(self, f: F) -> Self
127 where
128 F: FnOnce(Self) -> Self,
129 {
130 f(self)
131 }
132
133 pub fn set_discovery_v4(mut self, discovery_config: Discv4Config) -> Self {
135 self.discovery_v4_config = Some(discovery_config);
136 self
137 }
138
139 pub const fn set_listener_addr(mut self, listener_addr: SocketAddr) -> Self {
141 self.listener_addr = listener_addr;
142 self
143 }
144
145 pub const fn listener_addr(&self) -> &SocketAddr {
147 &self.listener_addr
148 }
149}
150
151impl<C, N> NetworkConfig<C, N>
152where
153 C: BlockNumReader + 'static,
154 N: NetworkPrimitives,
155{
156 pub async fn manager(self) -> Result<NetworkManager<N>, NetworkError> {
158 NetworkManager::new(self).await
159 }
160}
161
162impl<C, N> NetworkConfig<C, N>
163where
164 N: NetworkPrimitives,
165 C: BlockReader<Block = N::Block, Receipt = N::Receipt, Header = N::BlockHeader>
166 + HeaderProvider
167 + Clone
168 + Unpin
169 + 'static,
170{
171 pub async fn start_network(self) -> Result<NetworkHandle<N>, NetworkError> {
173 let client = self.client.clone();
174 let (handle, network, _txpool, eth) = NetworkManager::builder::<C>(self)
175 .await?
176 .request_handler::<C>(client)
177 .split_with_handle();
178
179 tokio::task::spawn(network);
180 tokio::task::spawn(eth);
181 Ok(handle)
182 }
183}
184
185#[derive(Debug)]
187pub struct NetworkConfigBuilder<N: NetworkPrimitives = EthNetworkPrimitives> {
188 secret_key: SecretKey,
190 dns_discovery_config: Option<DnsDiscoveryConfig>,
192 discovery_v4_builder: Option<Discv4ConfigBuilder>,
194 discovery_v5_builder: Option<reth_discv5::ConfigBuilder>,
196 boot_nodes: HashSet<TrustedPeer>,
198 discovery_addr: Option<SocketAddr>,
200 listener_addr: Option<SocketAddr>,
202 peers_config: Option<PeersConfig>,
204 sessions_config: Option<SessionsConfig>,
206 network_mode: NetworkMode,
208 executor: Option<Box<dyn TaskSpawner>>,
210 hello_message: Option<HelloMessageWithProtocols>,
212 extra_protocols: RlpxSubProtocols,
214 head: Option<Head>,
216 tx_gossip_disabled: bool,
218 block_import: Option<Box<dyn BlockImport<N::NewBlockPayload>>>,
220 transactions_manager_config: TransactionsManagerConfig,
222 nat: Option<NatResolver>,
224 handshake: Arc<dyn EthRlpxHandshake>,
227 required_block_hashes: Vec<B256>,
229 network_id: Option<u64>,
231}
232
233impl NetworkConfigBuilder<EthNetworkPrimitives> {
234 pub fn eth(secret_key: SecretKey) -> Self {
236 Self::new(secret_key)
237 }
238}
239
240#[expect(missing_docs)]
243impl<N: NetworkPrimitives> NetworkConfigBuilder<N> {
244 pub fn with_rng_secret_key() -> Self {
246 Self::new(rng_secret_key())
247 }
248
249 pub fn new(secret_key: SecretKey) -> Self {
251 Self {
252 secret_key,
253 dns_discovery_config: Some(Default::default()),
254 discovery_v4_builder: Some(Default::default()),
255 discovery_v5_builder: None,
256 boot_nodes: Default::default(),
257 discovery_addr: None,
258 listener_addr: None,
259 peers_config: None,
260 sessions_config: None,
261 network_mode: Default::default(),
262 executor: None,
263 hello_message: None,
264 extra_protocols: Default::default(),
265 head: None,
266 tx_gossip_disabled: false,
267 block_import: None,
268 transactions_manager_config: Default::default(),
269 nat: None,
270 handshake: Arc::new(EthHandshake::default()),
271 required_block_hashes: Vec::new(),
272 network_id: None,
273 }
274 }
275
276 pub fn apply<F>(self, f: F) -> Self
278 where
279 F: FnOnce(Self) -> Self,
280 {
281 f(self)
282 }
283
284 pub fn get_peer_id(&self) -> PeerId {
286 pk2id(&self.secret_key.public_key(SECP256K1))
287 }
288
289 pub const fn secret_key(&self) -> &SecretKey {
291 &self.secret_key
292 }
293
294 pub const fn network_mode(mut self, network_mode: NetworkMode) -> Self {
296 self.network_mode = network_mode;
297 self
298 }
299
300 pub const fn with_pow(self) -> Self {
308 self.network_mode(NetworkMode::Work)
309 }
310
311 pub const fn set_head(mut self, head: Head) -> Self {
317 self.head = Some(head);
318 self
319 }
320
321 pub fn hello_message(mut self, hello_message: HelloMessageWithProtocols) -> Self {
332 self.hello_message = Some(hello_message);
333 self
334 }
335
336 pub fn peer_config(mut self, config: PeersConfig) -> Self {
338 self.peers_config = Some(config);
339 self
340 }
341
342 pub fn with_task_executor(mut self, executor: Box<dyn TaskSpawner>) -> Self {
346 self.executor = Some(executor);
347 self
348 }
349
350 pub const fn sessions_config(mut self, config: SessionsConfig) -> Self {
352 self.sessions_config = Some(config);
353 self
354 }
355
356 pub const fn transactions_manager_config(mut self, config: TransactionsManagerConfig) -> Self {
358 self.transactions_manager_config = config;
359 self
360 }
361
362 pub const fn transaction_propagation_mode(mut self, mode: TransactionPropagationMode) -> Self {
364 self.transactions_manager_config.propagation_mode = mode;
365 self
366 }
367
368 pub const fn set_addrs(self, addr: SocketAddr) -> Self {
376 self.listener_addr(addr).discovery_addr(addr)
377 }
378
379 pub const fn listener_addr(mut self, listener_addr: SocketAddr) -> Self {
383 self.listener_addr = Some(listener_addr);
384 self
385 }
386
387 pub fn listener_port(mut self, port: u16) -> Self {
391 self.listener_addr.get_or_insert(DEFAULT_DISCOVERY_ADDRESS).set_port(port);
392 self
393 }
394
395 pub const fn discovery_addr(mut self, discovery_addr: SocketAddr) -> Self {
397 self.discovery_addr = Some(discovery_addr);
398 self
399 }
400
401 pub fn discovery_port(mut self, port: u16) -> Self {
405 self.discovery_addr.get_or_insert(DEFAULT_DISCOVERY_ADDRESS).set_port(port);
406 self
407 }
408
409 pub fn with_unused_ports(self) -> Self {
412 self.with_unused_discovery_port().with_unused_listener_port()
413 }
414
415 pub fn with_unused_discovery_port(self) -> Self {
418 self.discovery_port(0)
419 }
420
421 pub fn with_unused_listener_port(self) -> Self {
424 self.listener_port(0)
425 }
426
427 pub fn external_ip_resolver(mut self, resolver: NatResolver) -> Self {
434 self.discovery_v4_builder
435 .get_or_insert_with(Discv4Config::builder)
436 .external_ip_resolver(Some(resolver));
437 self.nat = Some(resolver);
438 self
439 }
440
441 pub fn discovery(mut self, builder: Discv4ConfigBuilder) -> Self {
443 self.discovery_v4_builder = Some(builder);
444 self
445 }
446
447 pub fn discovery_v5(mut self, builder: reth_discv5::ConfigBuilder) -> Self {
449 self.discovery_v5_builder = Some(builder);
450 self
451 }
452
453 pub fn dns_discovery(mut self, config: DnsDiscoveryConfig) -> Self {
455 self.dns_discovery_config = Some(config);
456 self
457 }
458
459 pub fn mainnet_boot_nodes(self) -> Self {
461 self.boot_nodes(mainnet_nodes())
462 }
463
464 pub fn sepolia_boot_nodes(self) -> Self {
466 self.boot_nodes(sepolia_nodes())
467 }
468
469 pub fn boot_nodes<T: Into<TrustedPeer>>(mut self, nodes: impl IntoIterator<Item = T>) -> Self {
471 self.boot_nodes = nodes.into_iter().map(Into::into).collect();
472 self
473 }
474
475 pub fn boot_nodes_iter(&self) -> impl Iterator<Item = &TrustedPeer> + '_ {
477 self.boot_nodes.iter()
478 }
479
480 pub fn disable_dns_discovery(mut self) -> Self {
482 self.dns_discovery_config = None;
483 self
484 }
485
486 pub const fn disable_nat(mut self) -> Self {
488 self.nat = None;
489 self
490 }
491
492 pub fn disable_discovery(self) -> Self {
494 self.disable_discv4_discovery().disable_discv5_discovery().disable_dns_discovery()
495 }
496
497 pub fn disable_discovery_if(self, disable: bool) -> Self {
499 if disable {
500 self.disable_discovery()
501 } else {
502 self
503 }
504 }
505
506 pub fn disable_discv4_discovery(mut self) -> Self {
508 self.discovery_v4_builder = None;
509 self
510 }
511
512 pub fn disable_discv5_discovery(mut self) -> Self {
514 self.discovery_v5_builder = None;
515 self
516 }
517
518 pub fn disable_dns_discovery_if(self, disable: bool) -> Self {
520 if disable {
521 self.disable_dns_discovery()
522 } else {
523 self
524 }
525 }
526
527 pub fn disable_discv4_discovery_if(self, disable: bool) -> Self {
529 if disable {
530 self.disable_discv4_discovery()
531 } else {
532 self
533 }
534 }
535
536 pub fn disable_discv5_discovery_if(self, disable: bool) -> Self {
538 if disable {
539 self.disable_discv5_discovery()
540 } else {
541 self
542 }
543 }
544
545 pub fn add_rlpx_sub_protocol(mut self, protocol: impl IntoRlpxSubProtocol) -> Self {
547 self.extra_protocols.push(protocol);
548 self
549 }
550
551 pub const fn disable_tx_gossip(mut self, disable_tx_gossip: bool) -> Self {
553 self.tx_gossip_disabled = disable_tx_gossip;
554 self
555 }
556
557 pub fn required_block_hashes(mut self, hashes: Vec<B256>) -> Self {
559 self.required_block_hashes = hashes;
560 self
561 }
562
563 pub fn block_import(mut self, block_import: Box<dyn BlockImport<N::NewBlockPayload>>) -> Self {
565 self.block_import = Some(block_import);
566 self
567 }
568
569 pub fn build_with_noop_provider<ChainSpec>(
572 self,
573 chain_spec: Arc<ChainSpec>,
574 ) -> NetworkConfig<NoopProvider<ChainSpec>, N>
575 where
576 ChainSpec: EthChainSpec + Hardforks + 'static,
577 {
578 self.build(NoopProvider::eth(chain_spec))
579 }
580
581 pub const fn add_nat(mut self, nat: Option<NatResolver>) -> Self {
583 self.nat = nat;
584 self
585 }
586
587 pub fn eth_rlpx_handshake(mut self, handshake: Arc<dyn EthRlpxHandshake>) -> Self {
589 self.handshake = handshake;
590 self
591 }
592
593 pub const fn network_id(mut self, network_id: Option<u64>) -> Self {
595 self.network_id = network_id;
596 self
597 }
598
599 pub fn build<C>(self, client: C) -> NetworkConfig<C, N>
606 where
607 C: ChainSpecProvider<ChainSpec: Hardforks>,
608 {
609 let peer_id = self.get_peer_id();
610 let chain_spec = client.chain_spec();
611 let Self {
612 secret_key,
613 mut dns_discovery_config,
614 discovery_v4_builder,
615 mut discovery_v5_builder,
616 boot_nodes,
617 discovery_addr,
618 listener_addr,
619 peers_config,
620 sessions_config,
621 network_mode,
622 executor,
623 hello_message,
624 extra_protocols,
625 head,
626 tx_gossip_disabled,
627 block_import,
628 transactions_manager_config,
629 nat,
630 handshake,
631 required_block_hashes,
632 network_id,
633 } = self;
634
635 let head = head.unwrap_or_else(|| Head {
636 hash: chain_spec.genesis_hash(),
637 number: 0,
638 timestamp: chain_spec.genesis().timestamp,
639 difficulty: chain_spec.genesis().difficulty,
640 total_difficulty: chain_spec.genesis().difficulty,
641 });
642
643 discovery_v5_builder = discovery_v5_builder.map(|mut builder| {
644 if let Some(network_stack_id) = NetworkStackId::id(&chain_spec) {
645 let fork_id = chain_spec.fork_id(&head);
646 builder = builder.fork(network_stack_id, fork_id)
647 }
648
649 builder
650 });
651
652 let listener_addr = listener_addr.unwrap_or(DEFAULT_DISCOVERY_ADDRESS);
653
654 let mut hello_message =
655 hello_message.unwrap_or_else(|| HelloMessage::builder(peer_id).build());
656 hello_message.port = listener_addr.port();
657
658 let mut status = UnifiedStatus::spec_builder(&chain_spec, &head);
660
661 if let Some(id) = network_id {
662 status.chain = id.into();
663 }
664
665 let fork_filter = chain_spec.fork_filter(head);
667
668 let chain_id = chain_spec.chain().id();
670
671 if let Some(dns_networks) =
673 dns_discovery_config.as_mut().and_then(|c| c.bootstrap_dns_networks.as_mut()) &&
674 dns_networks.is_empty() &&
675 let Some(link) = chain_spec.chain().public_dns_network_protocol()
676 {
677 dns_networks.insert(link.parse().expect("is valid DNS link entry"));
678 }
679
680 NetworkConfig {
681 client,
682 secret_key,
683 boot_nodes,
684 dns_discovery_config,
685 discovery_v4_config: discovery_v4_builder.map(|builder| builder.build()),
686 discovery_v5_config: discovery_v5_builder.map(|builder| builder.build()),
687 discovery_v4_addr: discovery_addr.unwrap_or(DEFAULT_DISCOVERY_ADDRESS),
688 listener_addr,
689 peers_config: peers_config.unwrap_or_default(),
690 sessions_config: sessions_config.unwrap_or_default(),
691 chain_id,
692 block_import: block_import.unwrap_or_else(|| Box::<ProofOfStakeBlockImport>::default()),
693 network_mode,
694 executor: executor.unwrap_or_else(|| Box::<TokioTaskExecutor>::default()),
695 status,
696 hello_message,
697 extra_protocols,
698 fork_filter,
699 tx_gossip_disabled,
700 transactions_manager_config,
701 nat,
702 handshake,
703 required_block_hashes,
704 }
705 }
706}
707
708#[derive(Debug, Clone, Copy, Eq, PartialEq, Default)]
714#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
715pub enum NetworkMode {
716 Work,
718 #[default]
720 Stake,
721}
722
723impl NetworkMode {
726 pub const fn is_stake(&self) -> bool {
728 matches!(self, Self::Stake)
729 }
730}
731
732#[cfg(test)]
733mod tests {
734 use super::*;
735 use alloy_eips::eip2124::ForkHash;
736 use alloy_genesis::Genesis;
737 use alloy_primitives::U256;
738 use reth_chainspec::{
739 Chain, ChainSpecBuilder, EthereumHardfork, ForkCondition, ForkId, MAINNET,
740 };
741 use reth_discv5::build_local_enr;
742 use reth_dns_discovery::tree::LinkEntry;
743 use reth_storage_api::noop::NoopProvider;
744 use std::{net::Ipv4Addr, sync::Arc};
745
746 fn builder() -> NetworkConfigBuilder {
747 let secret_key = SecretKey::new(&mut rand_08::thread_rng());
748 NetworkConfigBuilder::new(secret_key)
749 }
750
751 #[test]
752 fn test_network_dns_defaults() {
753 let config = builder().build(NoopProvider::default());
754
755 let dns = config.dns_discovery_config.unwrap();
756 let bootstrap_nodes = dns.bootstrap_dns_networks.unwrap();
757 let mainnet_dns: LinkEntry =
758 Chain::mainnet().public_dns_network_protocol().unwrap().parse().unwrap();
759 assert!(bootstrap_nodes.contains(&mainnet_dns));
760 assert_eq!(bootstrap_nodes.len(), 1);
761 }
762
763 #[test]
764 fn test_network_fork_filter_default() {
765 let mut chain_spec = Arc::clone(&MAINNET);
766
767 Arc::make_mut(&mut chain_spec).hardforks = Default::default();
769
770 let genesis_fork_hash = ForkHash::from(chain_spec.genesis_hash());
772
773 let config = builder().build_with_noop_provider(chain_spec);
775
776 let status = config.status;
777 let fork_filter = config.fork_filter;
778
779 assert_eq!(status.forkid.next, 0);
781
782 assert_eq!(fork_filter.current().next, 0);
784
785 assert_eq!(status.forkid.hash, genesis_fork_hash);
787 assert_eq!(fork_filter.current().hash, genesis_fork_hash);
788 }
789
790 #[test]
791 fn test_discv5_fork_id_default() {
792 const GENESIS_TIME: u64 = 151_515;
793
794 let genesis = Genesis::default().with_timestamp(GENESIS_TIME);
795
796 let active_fork = (EthereumHardfork::Shanghai, ForkCondition::Timestamp(GENESIS_TIME));
797 let future_fork = (EthereumHardfork::Cancun, ForkCondition::Timestamp(GENESIS_TIME + 1));
798
799 let chain_spec = ChainSpecBuilder::default()
800 .chain(Chain::dev())
801 .genesis(genesis)
802 .with_fork(active_fork.0, active_fork.1)
803 .with_fork(future_fork.0, future_fork.1)
804 .build();
805
806 let genesis_fork_hash = ForkHash::from(chain_spec.genesis_hash());
808 let fork_id = ForkId { hash: genesis_fork_hash, next: GENESIS_TIME + 1 };
809 assert_eq!(
811 fork_id,
812 chain_spec.fork_id(&Head {
813 hash: chain_spec.genesis_hash(),
814 number: 0,
815 timestamp: GENESIS_TIME,
816 difficulty: U256::ZERO,
817 total_difficulty: U256::ZERO,
818 })
819 );
820 assert_ne!(fork_id, chain_spec.latest_fork_id());
821
822 let fork_key = b"odyssey";
824 let config = builder()
825 .discovery_v5(
826 reth_discv5::Config::builder((Ipv4Addr::LOCALHOST, 30303).into())
827 .fork(fork_key, fork_id),
828 )
829 .build_with_noop_provider(Arc::new(chain_spec));
830
831 let (local_enr, _, _, _) = build_local_enr(
832 &config.secret_key,
833 &config.discovery_v5_config.expect("should build config"),
834 );
835
836 let advertised_fork_id = *local_enr
839 .get_decodable::<Vec<ForkId>>(fork_key)
840 .expect("should read 'odyssey'")
841 .expect("should decode fork id list")
842 .first()
843 .expect("should be non-empty");
844
845 assert_eq!(advertised_fork_id, fork_id);
846 }
847}