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}
230
231impl NetworkConfigBuilder<EthNetworkPrimitives> {
232 pub fn eth(secret_key: SecretKey) -> Self {
234 Self::new(secret_key)
235 }
236}
237
238#[expect(missing_docs)]
241impl<N: NetworkPrimitives> NetworkConfigBuilder<N> {
242 pub fn with_rng_secret_key() -> Self {
244 Self::new(rng_secret_key())
245 }
246
247 pub fn new(secret_key: SecretKey) -> Self {
249 Self {
250 secret_key,
251 dns_discovery_config: Some(Default::default()),
252 discovery_v4_builder: Some(Default::default()),
253 discovery_v5_builder: None,
254 boot_nodes: Default::default(),
255 discovery_addr: None,
256 listener_addr: None,
257 peers_config: None,
258 sessions_config: None,
259 network_mode: Default::default(),
260 executor: None,
261 hello_message: None,
262 extra_protocols: Default::default(),
263 head: None,
264 tx_gossip_disabled: false,
265 block_import: None,
266 transactions_manager_config: Default::default(),
267 nat: None,
268 handshake: Arc::new(EthHandshake::default()),
269 required_block_hashes: Vec::new(),
270 }
271 }
272
273 pub fn apply<F>(self, f: F) -> Self
275 where
276 F: FnOnce(Self) -> Self,
277 {
278 f(self)
279 }
280
281 pub fn get_peer_id(&self) -> PeerId {
283 pk2id(&self.secret_key.public_key(SECP256K1))
284 }
285
286 pub const fn secret_key(&self) -> &SecretKey {
288 &self.secret_key
289 }
290
291 pub const fn network_mode(mut self, network_mode: NetworkMode) -> Self {
293 self.network_mode = network_mode;
294 self
295 }
296
297 pub const fn with_pow(self) -> Self {
305 self.network_mode(NetworkMode::Work)
306 }
307
308 pub const fn set_head(mut self, head: Head) -> Self {
314 self.head = Some(head);
315 self
316 }
317
318 pub fn hello_message(mut self, hello_message: HelloMessageWithProtocols) -> Self {
329 self.hello_message = Some(hello_message);
330 self
331 }
332
333 pub fn peer_config(mut self, config: PeersConfig) -> Self {
335 self.peers_config = Some(config);
336 self
337 }
338
339 pub fn with_task_executor(mut self, executor: Box<dyn TaskSpawner>) -> Self {
343 self.executor = Some(executor);
344 self
345 }
346
347 pub const fn sessions_config(mut self, config: SessionsConfig) -> Self {
349 self.sessions_config = Some(config);
350 self
351 }
352
353 pub const fn transactions_manager_config(mut self, config: TransactionsManagerConfig) -> Self {
355 self.transactions_manager_config = config;
356 self
357 }
358
359 pub const fn transaction_propagation_mode(mut self, mode: TransactionPropagationMode) -> Self {
361 self.transactions_manager_config.propagation_mode = mode;
362 self
363 }
364
365 pub const fn set_addrs(self, addr: SocketAddr) -> Self {
373 self.listener_addr(addr).discovery_addr(addr)
374 }
375
376 pub const fn listener_addr(mut self, listener_addr: SocketAddr) -> Self {
380 self.listener_addr = Some(listener_addr);
381 self
382 }
383
384 pub fn listener_port(mut self, port: u16) -> Self {
388 self.listener_addr.get_or_insert(DEFAULT_DISCOVERY_ADDRESS).set_port(port);
389 self
390 }
391
392 pub const fn discovery_addr(mut self, discovery_addr: SocketAddr) -> Self {
394 self.discovery_addr = Some(discovery_addr);
395 self
396 }
397
398 pub fn discovery_port(mut self, port: u16) -> Self {
402 self.discovery_addr.get_or_insert(DEFAULT_DISCOVERY_ADDRESS).set_port(port);
403 self
404 }
405
406 pub fn with_unused_ports(self) -> Self {
409 self.with_unused_discovery_port().with_unused_listener_port()
410 }
411
412 pub fn with_unused_discovery_port(self) -> Self {
415 self.discovery_port(0)
416 }
417
418 pub fn with_unused_listener_port(self) -> Self {
421 self.listener_port(0)
422 }
423
424 pub fn external_ip_resolver(mut self, resolver: NatResolver) -> Self {
431 self.discovery_v4_builder
432 .get_or_insert_with(Discv4Config::builder)
433 .external_ip_resolver(Some(resolver));
434 self.nat = Some(resolver);
435 self
436 }
437
438 pub fn discovery(mut self, builder: Discv4ConfigBuilder) -> Self {
440 self.discovery_v4_builder = Some(builder);
441 self
442 }
443
444 pub fn discovery_v5(mut self, builder: reth_discv5::ConfigBuilder) -> Self {
446 self.discovery_v5_builder = Some(builder);
447 self
448 }
449
450 pub fn dns_discovery(mut self, config: DnsDiscoveryConfig) -> Self {
452 self.dns_discovery_config = Some(config);
453 self
454 }
455
456 pub fn mainnet_boot_nodes(self) -> Self {
458 self.boot_nodes(mainnet_nodes())
459 }
460
461 pub fn sepolia_boot_nodes(self) -> Self {
463 self.boot_nodes(sepolia_nodes())
464 }
465
466 pub fn boot_nodes<T: Into<TrustedPeer>>(mut self, nodes: impl IntoIterator<Item = T>) -> Self {
468 self.boot_nodes = nodes.into_iter().map(Into::into).collect();
469 self
470 }
471
472 pub fn boot_nodes_iter(&self) -> impl Iterator<Item = &TrustedPeer> + '_ {
474 self.boot_nodes.iter()
475 }
476
477 pub fn disable_dns_discovery(mut self) -> Self {
479 self.dns_discovery_config = None;
480 self
481 }
482
483 pub const fn disable_nat(mut self) -> Self {
485 self.nat = None;
486 self
487 }
488
489 pub fn disable_discovery(self) -> Self {
491 self.disable_discv4_discovery().disable_discv5_discovery().disable_dns_discovery()
492 }
493
494 pub fn disable_discovery_if(self, disable: bool) -> Self {
496 if disable {
497 self.disable_discovery()
498 } else {
499 self
500 }
501 }
502
503 pub fn disable_discv4_discovery(mut self) -> Self {
505 self.discovery_v4_builder = None;
506 self
507 }
508
509 pub fn disable_discv5_discovery(mut self) -> Self {
511 self.discovery_v5_builder = None;
512 self
513 }
514
515 pub fn disable_dns_discovery_if(self, disable: bool) -> Self {
517 if disable {
518 self.disable_dns_discovery()
519 } else {
520 self
521 }
522 }
523
524 pub fn disable_discv4_discovery_if(self, disable: bool) -> Self {
526 if disable {
527 self.disable_discv4_discovery()
528 } else {
529 self
530 }
531 }
532
533 pub fn disable_discv5_discovery_if(self, disable: bool) -> Self {
535 if disable {
536 self.disable_discv5_discovery()
537 } else {
538 self
539 }
540 }
541
542 pub fn add_rlpx_sub_protocol(mut self, protocol: impl IntoRlpxSubProtocol) -> Self {
544 self.extra_protocols.push(protocol);
545 self
546 }
547
548 pub const fn disable_tx_gossip(mut self, disable_tx_gossip: bool) -> Self {
550 self.tx_gossip_disabled = disable_tx_gossip;
551 self
552 }
553
554 pub fn required_block_hashes(mut self, hashes: Vec<B256>) -> Self {
556 self.required_block_hashes = hashes;
557 self
558 }
559
560 pub fn block_import(mut self, block_import: Box<dyn BlockImport<N::NewBlockPayload>>) -> Self {
562 self.block_import = Some(block_import);
563 self
564 }
565
566 pub fn build_with_noop_provider<ChainSpec>(
569 self,
570 chain_spec: Arc<ChainSpec>,
571 ) -> NetworkConfig<NoopProvider<ChainSpec>, N>
572 where
573 ChainSpec: EthChainSpec + Hardforks + 'static,
574 {
575 self.build(NoopProvider::eth(chain_spec))
576 }
577
578 pub const fn add_nat(mut self, nat: Option<NatResolver>) -> Self {
580 self.nat = nat;
581 self
582 }
583
584 pub fn eth_rlpx_handshake(mut self, handshake: Arc<dyn EthRlpxHandshake>) -> Self {
586 self.handshake = handshake;
587 self
588 }
589
590 pub fn build<C>(self, client: C) -> NetworkConfig<C, N>
597 where
598 C: ChainSpecProvider<ChainSpec: Hardforks>,
599 {
600 let peer_id = self.get_peer_id();
601 let chain_spec = client.chain_spec();
602 let Self {
603 secret_key,
604 mut dns_discovery_config,
605 discovery_v4_builder,
606 mut discovery_v5_builder,
607 boot_nodes,
608 discovery_addr,
609 listener_addr,
610 peers_config,
611 sessions_config,
612 network_mode,
613 executor,
614 hello_message,
615 extra_protocols,
616 head,
617 tx_gossip_disabled,
618 block_import,
619 transactions_manager_config,
620 nat,
621 handshake,
622 required_block_hashes,
623 } = self;
624
625 let head = head.unwrap_or_else(|| Head {
626 hash: chain_spec.genesis_hash(),
627 number: 0,
628 timestamp: chain_spec.genesis().timestamp,
629 difficulty: chain_spec.genesis().difficulty,
630 total_difficulty: chain_spec.genesis().difficulty,
631 });
632
633 discovery_v5_builder = discovery_v5_builder.map(|mut builder| {
634 if let Some(network_stack_id) = NetworkStackId::id(&chain_spec) {
635 let fork_id = chain_spec.fork_id(&head);
636 builder = builder.fork(network_stack_id, fork_id)
637 }
638
639 builder
640 });
641
642 let listener_addr = listener_addr.unwrap_or(DEFAULT_DISCOVERY_ADDRESS);
643
644 let mut hello_message =
645 hello_message.unwrap_or_else(|| HelloMessage::builder(peer_id).build());
646 hello_message.port = listener_addr.port();
647
648 let status = UnifiedStatus::spec_builder(&chain_spec, &head);
650
651 let fork_filter = chain_spec.fork_filter(head);
653
654 let chain_id = chain_spec.chain().id();
656
657 if let Some(dns_networks) =
659 dns_discovery_config.as_mut().and_then(|c| c.bootstrap_dns_networks.as_mut())
660 {
661 if dns_networks.is_empty() {
662 if let Some(link) = chain_spec.chain().public_dns_network_protocol() {
663 dns_networks.insert(link.parse().expect("is valid DNS link entry"));
664 }
665 }
666 }
667
668 NetworkConfig {
669 client,
670 secret_key,
671 boot_nodes,
672 dns_discovery_config,
673 discovery_v4_config: discovery_v4_builder.map(|builder| builder.build()),
674 discovery_v5_config: discovery_v5_builder.map(|builder| builder.build()),
675 discovery_v4_addr: discovery_addr.unwrap_or(DEFAULT_DISCOVERY_ADDRESS),
676 listener_addr,
677 peers_config: peers_config.unwrap_or_default(),
678 sessions_config: sessions_config.unwrap_or_default(),
679 chain_id,
680 block_import: block_import.unwrap_or_else(|| Box::<ProofOfStakeBlockImport>::default()),
681 network_mode,
682 executor: executor.unwrap_or_else(|| Box::<TokioTaskExecutor>::default()),
683 status,
684 hello_message,
685 extra_protocols,
686 fork_filter,
687 tx_gossip_disabled,
688 transactions_manager_config,
689 nat,
690 handshake,
691 required_block_hashes,
692 }
693 }
694}
695
696#[derive(Debug, Clone, Copy, Eq, PartialEq, Default)]
702#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
703pub enum NetworkMode {
704 Work,
706 #[default]
708 Stake,
709}
710
711impl NetworkMode {
714 pub const fn is_stake(&self) -> bool {
716 matches!(self, Self::Stake)
717 }
718}
719
720#[cfg(test)]
721mod tests {
722 use super::*;
723 use alloy_eips::eip2124::ForkHash;
724 use alloy_genesis::Genesis;
725 use alloy_primitives::U256;
726 use reth_chainspec::{
727 Chain, ChainSpecBuilder, EthereumHardfork, ForkCondition, ForkId, MAINNET,
728 };
729 use reth_discv5::build_local_enr;
730 use reth_dns_discovery::tree::LinkEntry;
731 use reth_storage_api::noop::NoopProvider;
732 use std::{net::Ipv4Addr, sync::Arc};
733
734 fn builder() -> NetworkConfigBuilder {
735 let secret_key = SecretKey::new(&mut rand_08::thread_rng());
736 NetworkConfigBuilder::new(secret_key)
737 }
738
739 #[test]
740 fn test_network_dns_defaults() {
741 let config = builder().build(NoopProvider::default());
742
743 let dns = config.dns_discovery_config.unwrap();
744 let bootstrap_nodes = dns.bootstrap_dns_networks.unwrap();
745 let mainnet_dns: LinkEntry =
746 Chain::mainnet().public_dns_network_protocol().unwrap().parse().unwrap();
747 assert!(bootstrap_nodes.contains(&mainnet_dns));
748 assert_eq!(bootstrap_nodes.len(), 1);
749 }
750
751 #[test]
752 fn test_network_fork_filter_default() {
753 let mut chain_spec = Arc::clone(&MAINNET);
754
755 Arc::make_mut(&mut chain_spec).hardforks = Default::default();
757
758 let genesis_fork_hash = ForkHash::from(chain_spec.genesis_hash());
760
761 let config = builder().build_with_noop_provider(chain_spec);
763
764 let status = config.status;
765 let fork_filter = config.fork_filter;
766
767 assert_eq!(status.forkid.next, 0);
769
770 assert_eq!(fork_filter.current().next, 0);
772
773 assert_eq!(status.forkid.hash, genesis_fork_hash);
775 assert_eq!(fork_filter.current().hash, genesis_fork_hash);
776 }
777
778 #[test]
779 fn test_discv5_fork_id_default() {
780 const GENESIS_TIME: u64 = 151_515;
781
782 let genesis = Genesis::default().with_timestamp(GENESIS_TIME);
783
784 let active_fork = (EthereumHardfork::Shanghai, ForkCondition::Timestamp(GENESIS_TIME));
785 let future_fork = (EthereumHardfork::Cancun, ForkCondition::Timestamp(GENESIS_TIME + 1));
786
787 let chain_spec = ChainSpecBuilder::default()
788 .chain(Chain::dev())
789 .genesis(genesis)
790 .with_fork(active_fork.0, active_fork.1)
791 .with_fork(future_fork.0, future_fork.1)
792 .build();
793
794 let genesis_fork_hash = ForkHash::from(chain_spec.genesis_hash());
796 let fork_id = ForkId { hash: genesis_fork_hash, next: GENESIS_TIME + 1 };
797 assert_eq!(
799 fork_id,
800 chain_spec.fork_id(&Head {
801 hash: chain_spec.genesis_hash(),
802 number: 0,
803 timestamp: GENESIS_TIME,
804 difficulty: U256::ZERO,
805 total_difficulty: U256::ZERO,
806 })
807 );
808 assert_ne!(fork_id, chain_spec.latest_fork_id());
809
810 let fork_key = b"odyssey";
812 let config = builder()
813 .discovery_v5(
814 reth_discv5::Config::builder((Ipv4Addr::LOCALHOST, 30303).into())
815 .fork(fork_key, fork_id),
816 )
817 .build_with_noop_provider(Arc::new(chain_spec));
818
819 let (local_enr, _, _, _) = build_local_enr(
820 &config.secret_key,
821 &config.discovery_v5_config.expect("should build config"),
822 );
823
824 let advertised_fork_id = *local_enr
827 .get_decodable::<Vec<ForkId>>(fork_key)
828 .expect("should read 'odyssey'")
829 .expect("should decode fork id list")
830 .first()
831 .expect("should be non-empty");
832
833 assert_eq!(advertised_fork_id, fork_id);
834 }
835}