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 dns_networks.is_empty() &&
661 let Some(link) = chain_spec.chain().public_dns_network_protocol()
662 {
663 dns_networks.insert(link.parse().expect("is valid DNS link entry"));
664 }
665
666 NetworkConfig {
667 client,
668 secret_key,
669 boot_nodes,
670 dns_discovery_config,
671 discovery_v4_config: discovery_v4_builder.map(|builder| builder.build()),
672 discovery_v5_config: discovery_v5_builder.map(|builder| builder.build()),
673 discovery_v4_addr: discovery_addr.unwrap_or(DEFAULT_DISCOVERY_ADDRESS),
674 listener_addr,
675 peers_config: peers_config.unwrap_or_default(),
676 sessions_config: sessions_config.unwrap_or_default(),
677 chain_id,
678 block_import: block_import.unwrap_or_else(|| Box::<ProofOfStakeBlockImport>::default()),
679 network_mode,
680 executor: executor.unwrap_or_else(|| Box::<TokioTaskExecutor>::default()),
681 status,
682 hello_message,
683 extra_protocols,
684 fork_filter,
685 tx_gossip_disabled,
686 transactions_manager_config,
687 nat,
688 handshake,
689 required_block_hashes,
690 }
691 }
692}
693
694#[derive(Debug, Clone, Copy, Eq, PartialEq, Default)]
700#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
701pub enum NetworkMode {
702 Work,
704 #[default]
706 Stake,
707}
708
709impl NetworkMode {
712 pub const fn is_stake(&self) -> bool {
714 matches!(self, Self::Stake)
715 }
716}
717
718#[cfg(test)]
719mod tests {
720 use super::*;
721 use alloy_eips::eip2124::ForkHash;
722 use alloy_genesis::Genesis;
723 use alloy_primitives::U256;
724 use reth_chainspec::{
725 Chain, ChainSpecBuilder, EthereumHardfork, ForkCondition, ForkId, MAINNET,
726 };
727 use reth_discv5::build_local_enr;
728 use reth_dns_discovery::tree::LinkEntry;
729 use reth_storage_api::noop::NoopProvider;
730 use std::{net::Ipv4Addr, sync::Arc};
731
732 fn builder() -> NetworkConfigBuilder {
733 let secret_key = SecretKey::new(&mut rand_08::thread_rng());
734 NetworkConfigBuilder::new(secret_key)
735 }
736
737 #[test]
738 fn test_network_dns_defaults() {
739 let config = builder().build(NoopProvider::default());
740
741 let dns = config.dns_discovery_config.unwrap();
742 let bootstrap_nodes = dns.bootstrap_dns_networks.unwrap();
743 let mainnet_dns: LinkEntry =
744 Chain::mainnet().public_dns_network_protocol().unwrap().parse().unwrap();
745 assert!(bootstrap_nodes.contains(&mainnet_dns));
746 assert_eq!(bootstrap_nodes.len(), 1);
747 }
748
749 #[test]
750 fn test_network_fork_filter_default() {
751 let mut chain_spec = Arc::clone(&MAINNET);
752
753 Arc::make_mut(&mut chain_spec).hardforks = Default::default();
755
756 let genesis_fork_hash = ForkHash::from(chain_spec.genesis_hash());
758
759 let config = builder().build_with_noop_provider(chain_spec);
761
762 let status = config.status;
763 let fork_filter = config.fork_filter;
764
765 assert_eq!(status.forkid.next, 0);
767
768 assert_eq!(fork_filter.current().next, 0);
770
771 assert_eq!(status.forkid.hash, genesis_fork_hash);
773 assert_eq!(fork_filter.current().hash, genesis_fork_hash);
774 }
775
776 #[test]
777 fn test_discv5_fork_id_default() {
778 const GENESIS_TIME: u64 = 151_515;
779
780 let genesis = Genesis::default().with_timestamp(GENESIS_TIME);
781
782 let active_fork = (EthereumHardfork::Shanghai, ForkCondition::Timestamp(GENESIS_TIME));
783 let future_fork = (EthereumHardfork::Cancun, ForkCondition::Timestamp(GENESIS_TIME + 1));
784
785 let chain_spec = ChainSpecBuilder::default()
786 .chain(Chain::dev())
787 .genesis(genesis)
788 .with_fork(active_fork.0, active_fork.1)
789 .with_fork(future_fork.0, future_fork.1)
790 .build();
791
792 let genesis_fork_hash = ForkHash::from(chain_spec.genesis_hash());
794 let fork_id = ForkId { hash: genesis_fork_hash, next: GENESIS_TIME + 1 };
795 assert_eq!(
797 fork_id,
798 chain_spec.fork_id(&Head {
799 hash: chain_spec.genesis_hash(),
800 number: 0,
801 timestamp: GENESIS_TIME,
802 difficulty: U256::ZERO,
803 total_difficulty: U256::ZERO,
804 })
805 );
806 assert_ne!(fork_id, chain_spec.latest_fork_id());
807
808 let fork_key = b"odyssey";
810 let config = builder()
811 .discovery_v5(
812 reth_discv5::Config::builder((Ipv4Addr::LOCALHOST, 30303).into())
813 .fork(fork_key, fork_id),
814 )
815 .build_with_noop_provider(Arc::new(chain_spec));
816
817 let (local_enr, _, _, _) = build_local_enr(
818 &config.secret_key,
819 &config.discovery_v5_config.expect("should build config"),
820 );
821
822 let advertised_fork_id = *local_enr
825 .get_decodable::<Vec<ForkId>>(fork_key)
826 .expect("should read 'odyssey'")
827 .expect("should decode fork id list")
828 .first()
829 .expect("should be non-empty");
830
831 assert_eq!(advertised_fork_id, fork_id);
832 }
833}