1use crate::{
4 error::NetworkError,
5 import::{BlockImport, ProofOfStakeBlockImport},
6 transactions::TransactionsManagerConfig,
7 NetworkHandle, NetworkManager,
8};
9use alloy_eips::BlockNumHash;
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_eth_wire_types::message::MAX_MESSAGE_SIZE;
20use reth_ethereum_forks::{ForkFilter, Head};
21use reth_network_peers::{mainnet_nodes, pk2id, sepolia_nodes, PeerId, TrustedPeer};
22use reth_network_types::{PeersConfig, SessionsConfig};
23use reth_storage_api::{
24 noop::NoopProvider, BalProvider, BlockNumReader, BlockReader, HeaderProvider,
25};
26use reth_tasks::Runtime;
27use secp256k1::SECP256K1;
28use std::{collections::HashSet, net::SocketAddr, sync::Arc};
29
30use crate::{
32 protocol::{IntoRlpxSubProtocol, RlpxSubProtocols},
33 transactions::TransactionPropagationMode,
34};
35pub use secp256k1::SecretKey;
36
37pub fn rng_secret_key() -> SecretKey {
39 SecretKey::new(&mut rand_08::thread_rng())
40}
41
42#[derive(Debug)]
44pub struct NetworkConfig<C, N: NetworkPrimitives = EthNetworkPrimitives> {
45 pub client: C,
50 pub secret_key: SecretKey,
52 pub boot_nodes: HashSet<TrustedPeer>,
54 pub dns_discovery_config: Option<DnsDiscoveryConfig>,
56 pub discovery_v4_addr: SocketAddr,
58 pub discovery_v4_config: Option<Discv4Config>,
60 pub discovery_v5_config: Option<reth_discv5::Config>,
62 pub listener_addr: SocketAddr,
64 pub peers_config: PeersConfig,
66 pub sessions_config: SessionsConfig,
68 pub chain_id: u64,
70 pub fork_filter: ForkFilter,
77 pub block_import: Box<dyn BlockImport<N::NewBlockPayload>>,
79 pub network_mode: NetworkMode,
81 pub executor: Runtime,
83 pub status: UnifiedStatus,
85 pub hello_message: HelloMessageWithProtocols,
87 pub extra_protocols: RlpxSubProtocols,
89 pub tx_gossip_disabled: bool,
91 pub transactions_manager_config: TransactionsManagerConfig,
93 pub nat: Option<NatResolver>,
95 pub handshake: Arc<dyn EthRlpxHandshake>,
100 pub eth_max_message_size: usize,
102 pub required_block_hashes: Vec<BlockNumHash>,
105}
106
107impl<N: NetworkPrimitives> NetworkConfig<(), N> {
110 pub fn builder(secret_key: SecretKey, executor: Runtime) -> NetworkConfigBuilder<N> {
112 NetworkConfigBuilder::new(secret_key, executor)
113 }
114
115 pub fn builder_with_rng_secret_key(executor: Runtime) -> NetworkConfigBuilder<N> {
117 NetworkConfigBuilder::with_rng_secret_key(executor)
118 }
119}
120
121impl<C, N: NetworkPrimitives> NetworkConfig<C, N> {
122 pub fn apply<F>(self, f: F) -> Self
124 where
125 F: FnOnce(Self) -> Self,
126 {
127 f(self)
128 }
129
130 pub fn set_discovery_v4(mut self, discovery_config: Discv4Config) -> Self {
132 self.discovery_v4_config = Some(discovery_config);
133 self
134 }
135
136 pub const fn set_listener_addr(mut self, listener_addr: SocketAddr) -> Self {
138 self.listener_addr = listener_addr;
139 self
140 }
141
142 pub const fn listener_addr(&self) -> &SocketAddr {
144 &self.listener_addr
145 }
146}
147
148impl<C, N> NetworkConfig<C, N>
149where
150 C: BlockNumReader + 'static,
151 N: NetworkPrimitives,
152{
153 pub async fn manager(self) -> Result<NetworkManager<N>, NetworkError> {
155 NetworkManager::new(self).await
156 }
157}
158
159impl<C, N> NetworkConfig<C, N>
160where
161 N: NetworkPrimitives,
162 C: BalProvider
163 + BlockReader<Block = N::Block, Receipt = N::Receipt, Header = N::BlockHeader>
164 + HeaderProvider
165 + Clone
166 + Unpin
167 + 'static,
168{
169 pub async fn start_network(self) -> Result<NetworkHandle<N>, NetworkError> {
171 let client = self.client.clone();
172 let (handle, network, _txpool, eth) = NetworkManager::builder::<C>(self)
173 .await?
174 .request_handler::<C>(client)
175 .split_with_handle();
176
177 tokio::task::spawn(network);
178 tokio::task::spawn(eth);
179 Ok(handle)
180 }
181}
182
183#[derive(Debug)]
185pub struct NetworkConfigBuilder<N: NetworkPrimitives = EthNetworkPrimitives> {
186 secret_key: SecretKey,
188 dns_discovery_config: Option<DnsDiscoveryConfig>,
190 discovery_v4_builder: Option<Discv4ConfigBuilder>,
192 discovery_v5_builder: Option<reth_discv5::ConfigBuilder>,
194 boot_nodes: HashSet<TrustedPeer>,
196 discovery_addr: Option<SocketAddr>,
198 listener_addr: Option<SocketAddr>,
200 peers_config: Option<PeersConfig>,
202 sessions_config: Option<SessionsConfig>,
204 network_mode: NetworkMode,
206 executor: Runtime,
208 hello_message: Option<HelloMessageWithProtocols>,
210 extra_protocols: RlpxSubProtocols,
212 head: Option<Head>,
214 tx_gossip_disabled: bool,
216 block_import: Option<Box<dyn BlockImport<N::NewBlockPayload>>>,
218 transactions_manager_config: TransactionsManagerConfig,
220 nat: Option<NatResolver>,
222 handshake: Arc<dyn EthRlpxHandshake>,
225 eth_max_message_size: usize,
227 required_block_hashes: Vec<BlockNumHash>,
229 network_id: Option<u64>,
231}
232
233impl NetworkConfigBuilder<EthNetworkPrimitives> {
234 pub fn eth(secret_key: SecretKey, executor: Runtime) -> Self {
236 Self::new(secret_key, executor)
237 }
238}
239
240#[expect(missing_docs)]
243impl<N: NetworkPrimitives> NetworkConfigBuilder<N> {
244 pub fn with_rng_secret_key(executor: Runtime) -> Self {
246 Self::new(rng_secret_key(), executor)
247 }
248
249 pub fn new(secret_key: SecretKey, executor: Runtime) -> 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,
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 eth_max_message_size: MAX_MESSAGE_SIZE,
272 required_block_hashes: Vec::new(),
273 network_id: None,
274 }
275 }
276
277 pub fn apply<F>(self, f: F) -> Self
279 where
280 F: FnOnce(Self) -> Self,
281 {
282 f(self)
283 }
284
285 pub fn get_peer_id(&self) -> PeerId {
287 pk2id(&self.secret_key.public_key(SECP256K1))
288 }
289
290 pub const fn secret_key(&self) -> &SecretKey {
292 &self.secret_key
293 }
294
295 pub const fn network_mode(mut self, network_mode: NetworkMode) -> Self {
297 self.network_mode = network_mode;
298 self
299 }
300
301 pub const fn with_pow(self) -> Self {
309 self.network_mode(NetworkMode::Work)
310 }
311
312 pub const fn set_head(mut self, head: Head) -> Self {
318 self.head = Some(head);
319 self
320 }
321
322 pub fn hello_message(mut self, hello_message: HelloMessageWithProtocols) -> Self {
333 self.hello_message = Some(hello_message);
334 self
335 }
336
337 pub fn peer_config(mut self, config: PeersConfig) -> Self {
339 self.peers_config = Some(config);
340 self
341 }
342
343 pub fn with_task_executor(mut self, executor: Runtime) -> Self {
345 self.executor = executor;
346 self
347 }
348
349 pub const fn sessions_config(mut self, config: SessionsConfig) -> Self {
351 self.sessions_config = Some(config);
352 self
353 }
354
355 pub const fn transactions_manager_config(mut self, config: TransactionsManagerConfig) -> Self {
357 self.transactions_manager_config = config;
358 self
359 }
360
361 pub const fn transaction_propagation_mode(mut self, mode: TransactionPropagationMode) -> Self {
363 self.transactions_manager_config.propagation_mode = mode;
364 self
365 }
366
367 pub const fn set_addrs(self, addr: SocketAddr) -> Self {
375 self.listener_addr(addr).discovery_addr(addr)
376 }
377
378 pub const fn listener_addr(mut self, listener_addr: SocketAddr) -> Self {
382 self.listener_addr = Some(listener_addr);
383 self
384 }
385
386 pub fn listener_port(mut self, port: u16) -> Self {
390 self.listener_addr.get_or_insert(DEFAULT_DISCOVERY_ADDRESS).set_port(port);
391 self
392 }
393
394 pub const fn discovery_addr(mut self, discovery_addr: SocketAddr) -> Self {
396 self.discovery_addr = Some(discovery_addr);
397 self
398 }
399
400 pub fn discovery_port(mut self, port: u16) -> Self {
404 self.discovery_addr.get_or_insert(DEFAULT_DISCOVERY_ADDRESS).set_port(port);
405 self
406 }
407
408 pub fn with_unused_ports(self) -> Self {
411 self.with_unused_discovery_port().with_unused_listener_port()
412 }
413
414 pub fn with_unused_discovery_port(self) -> Self {
417 self.discovery_port(0)
418 }
419
420 pub fn with_unused_listener_port(self) -> Self {
423 self.listener_port(0)
424 }
425
426 pub fn external_ip_resolver(mut self, resolver: NatResolver) -> Self {
433 self.discovery_v4_builder
434 .get_or_insert_with(Discv4Config::builder)
435 .external_ip_resolver(Some(resolver.clone()));
436 self.nat = Some(resolver);
437 self
438 }
439
440 pub fn discovery(mut self, builder: Discv4ConfigBuilder) -> Self {
442 self.discovery_v4_builder = Some(builder);
443 self
444 }
445
446 pub fn discovery_v5(mut self, builder: reth_discv5::ConfigBuilder) -> Self {
448 self.discovery_v5_builder = Some(builder);
449 self
450 }
451
452 pub fn dns_discovery(mut self, config: DnsDiscoveryConfig) -> Self {
454 self.dns_discovery_config = Some(config);
455 self
456 }
457
458 pub fn mainnet_boot_nodes(self) -> Self {
460 self.boot_nodes(mainnet_nodes())
461 }
462
463 pub fn sepolia_boot_nodes(self) -> Self {
465 self.boot_nodes(sepolia_nodes())
466 }
467
468 pub fn boot_nodes<T: Into<TrustedPeer>>(mut self, nodes: impl IntoIterator<Item = T>) -> Self {
470 self.boot_nodes = nodes.into_iter().map(Into::into).collect();
471 self
472 }
473
474 pub fn boot_nodes_iter(&self) -> impl Iterator<Item = &TrustedPeer> + '_ {
476 self.boot_nodes.iter()
477 }
478
479 pub fn disable_dns_discovery(mut self) -> Self {
481 self.dns_discovery_config = None;
482 self
483 }
484
485 pub fn disable_nat(mut self) -> Self {
487 self.nat = None;
488 self
489 }
490
491 pub fn disable_discovery(self) -> Self {
493 self.disable_discv4_discovery().disable_discv5_discovery().disable_dns_discovery()
494 }
495
496 pub fn disable_discovery_if(self, disable: bool) -> Self {
498 if disable {
499 self.disable_discovery()
500 } else {
501 self
502 }
503 }
504
505 pub fn disable_discv4_discovery(mut self) -> Self {
507 self.discovery_v4_builder = None;
508 self
509 }
510
511 pub fn disable_discv5_discovery(mut self) -> Self {
513 self.discovery_v5_builder = None;
514 self
515 }
516
517 pub fn disable_dns_discovery_if(self, disable: bool) -> Self {
519 if disable {
520 self.disable_dns_discovery()
521 } else {
522 self
523 }
524 }
525
526 pub fn disable_discv4_discovery_if(self, disable: bool) -> Self {
528 if disable {
529 self.disable_discv4_discovery()
530 } else {
531 self
532 }
533 }
534
535 pub fn disable_discv5_discovery_if(self, disable: bool) -> Self {
537 if disable {
538 self.disable_discv5_discovery()
539 } else {
540 self
541 }
542 }
543
544 pub fn add_rlpx_sub_protocol(mut self, protocol: impl IntoRlpxSubProtocol) -> Self {
546 self.extra_protocols.push(protocol);
547 self
548 }
549
550 pub const fn disable_tx_gossip(mut self, disable_tx_gossip: bool) -> Self {
552 self.tx_gossip_disabled = disable_tx_gossip;
553 self
554 }
555
556 pub fn required_block_hashes(mut self, hashes: Vec<BlockNumHash>) -> Self {
558 self.required_block_hashes = hashes;
559 self
560 }
561
562 pub fn block_import(mut self, block_import: Box<dyn BlockImport<N::NewBlockPayload>>) -> Self {
564 self.block_import = Some(block_import);
565 self
566 }
567
568 pub fn build_with_noop_provider<ChainSpec>(
571 self,
572 chain_spec: Arc<ChainSpec>,
573 ) -> NetworkConfig<NoopProvider<ChainSpec>, N>
574 where
575 ChainSpec: EthChainSpec + Hardforks + 'static,
576 {
577 self.build(NoopProvider::eth(chain_spec))
578 }
579
580 pub fn add_nat(mut self, nat: Option<NatResolver>) -> Self {
582 self.nat = nat;
583 self
584 }
585
586 pub fn eth_rlpx_handshake(mut self, handshake: Arc<dyn EthRlpxHandshake>) -> Self {
588 self.handshake = handshake;
589 self
590 }
591
592 pub const fn eth_max_message_size(mut self, max_message_size: usize) -> Self {
597 self.eth_max_message_size = max_message_size;
598 self
599 }
600
601 pub const fn eth_max_message_size_opt(mut self, max_message_size: Option<usize>) -> Self {
603 if let Some(max_message_size) = max_message_size {
604 self.eth_max_message_size = max_message_size;
605 }
606 self
607 }
608
609 pub const fn network_id(mut self, network_id: Option<u64>) -> Self {
611 self.network_id = network_id;
612 self
613 }
614
615 pub fn build<C>(self, client: C) -> NetworkConfig<C, N>
622 where
623 C: ChainSpecProvider<ChainSpec: Hardforks>,
624 {
625 let peer_id = self.get_peer_id();
626 let chain_spec = client.chain_spec();
627 let Self {
628 secret_key,
629 mut dns_discovery_config,
630 discovery_v4_builder,
631 mut discovery_v5_builder,
632 boot_nodes,
633 discovery_addr,
634 listener_addr,
635 peers_config,
636 sessions_config,
637 network_mode,
638 executor,
639 hello_message,
640 extra_protocols,
641 head,
642 tx_gossip_disabled,
643 block_import,
644 transactions_manager_config,
645 nat,
646 handshake,
647 eth_max_message_size,
648 required_block_hashes,
649 network_id,
650 } = self;
651
652 let head = head.unwrap_or_else(|| Head {
653 hash: chain_spec.genesis_hash(),
654 number: 0,
655 timestamp: chain_spec.genesis().timestamp,
656 difficulty: chain_spec.genesis().difficulty,
657 total_difficulty: chain_spec.genesis().difficulty,
658 });
659
660 let listener_addr = listener_addr.unwrap_or(DEFAULT_DISCOVERY_ADDRESS);
661 let advertised_ip = nat.clone().and_then(|nat| nat.as_external_ip(listener_addr.port()));
665
666 discovery_v5_builder = discovery_v5_builder.map(|mut builder| {
667 if let Some(network_stack_id) = NetworkStackId::id(&chain_spec) {
668 let fork_id = chain_spec.fork_id(&head);
669 builder = builder.fork(network_stack_id, fork_id)
670 }
671
672 if let Some(ip) = advertised_ip {
673 builder = builder.advertised_ip(ip);
674 }
675
676 builder
677 });
678
679 let mut hello_message =
680 hello_message.unwrap_or_else(|| HelloMessage::builder(peer_id).build());
681 hello_message.port = listener_addr.port();
682
683 let mut status = UnifiedStatus::spec_builder(&chain_spec, &head);
685
686 if let Some(id) = network_id {
687 status.chain = id.into();
688 }
689
690 let fork_filter = chain_spec.fork_filter(head);
692
693 let chain_id = chain_spec.chain().id();
695
696 if let Some(dns_networks) =
698 dns_discovery_config.as_mut().and_then(|c| c.bootstrap_dns_networks.as_mut()) &&
699 dns_networks.is_empty() &&
700 let Some(link) = chain_spec.chain().public_dns_network_protocol()
701 {
702 dns_networks.insert(link.parse().expect("is valid DNS link entry"));
703 }
704
705 NetworkConfig {
706 client,
707 secret_key,
708 boot_nodes,
709 dns_discovery_config,
710 discovery_v4_config: discovery_v4_builder.map(|builder| builder.build()),
711 discovery_v5_config: discovery_v5_builder.map(|builder| builder.build()),
712 discovery_v4_addr: discovery_addr.unwrap_or(DEFAULT_DISCOVERY_ADDRESS),
713 listener_addr,
714 peers_config: peers_config.unwrap_or_default(),
715 sessions_config: sessions_config.unwrap_or_default(),
716 chain_id,
717 block_import: block_import.unwrap_or_else(|| Box::<ProofOfStakeBlockImport>::default()),
718 network_mode,
719 executor,
720 status,
721 hello_message,
722 extra_protocols,
723 fork_filter,
724 tx_gossip_disabled,
725 transactions_manager_config,
726 nat,
727 handshake,
728 eth_max_message_size,
729 required_block_hashes,
730 }
731 }
732}
733
734#[derive(Debug, Clone, Copy, Eq, PartialEq, Default)]
740#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
741pub enum NetworkMode {
742 Work,
744 #[default]
746 Stake,
747}
748
749impl NetworkMode {
752 pub const fn is_stake(&self) -> bool {
754 matches!(self, Self::Stake)
755 }
756}
757
758#[cfg(test)]
759mod tests {
760 use super::*;
761 use alloy_eips::eip2124::ForkHash;
762 use alloy_genesis::Genesis;
763 use alloy_primitives::U256;
764 use reth_chainspec::{
765 Chain, ChainSpecBuilder, EthereumHardfork, ForkCondition, ForkId, MAINNET,
766 };
767 use reth_discv5::build_local_enr;
768 use reth_dns_discovery::tree::LinkEntry;
769 use reth_storage_api::noop::NoopProvider;
770 use std::{net::Ipv4Addr, sync::Arc};
771
772 fn builder() -> NetworkConfigBuilder {
773 let secret_key = SecretKey::new(&mut rand_08::thread_rng());
774 NetworkConfigBuilder::new(secret_key, Runtime::test())
775 }
776
777 #[test]
778 fn test_network_dns_defaults() {
779 let config = builder().build(NoopProvider::default());
780
781 let dns = config.dns_discovery_config.unwrap();
782 let bootstrap_nodes = dns.bootstrap_dns_networks.unwrap();
783 let mainnet_dns: LinkEntry =
784 Chain::mainnet().public_dns_network_protocol().unwrap().parse().unwrap();
785 assert!(bootstrap_nodes.contains(&mainnet_dns));
786 assert_eq!(bootstrap_nodes.len(), 1);
787 }
788
789 #[test]
790 fn test_network_fork_filter_default() {
791 let mut chain_spec = Arc::clone(&MAINNET);
792
793 Arc::make_mut(&mut chain_spec).hardforks = Default::default();
795
796 let genesis_fork_hash = ForkHash::from(chain_spec.genesis_hash());
798
799 let config = builder().build_with_noop_provider(chain_spec);
801
802 let status = config.status;
803 let fork_filter = config.fork_filter;
804
805 assert_eq!(status.forkid.next, 0);
807
808 assert_eq!(fork_filter.current().next, 0);
810
811 assert_eq!(status.forkid.hash, genesis_fork_hash);
813 assert_eq!(fork_filter.current().hash, genesis_fork_hash);
814 }
815
816 #[test]
817 fn test_discv5_fork_id_default() {
818 const GENESIS_TIME: u64 = 151_515;
819
820 let genesis = Genesis::default().with_timestamp(GENESIS_TIME);
821
822 let active_fork = (EthereumHardfork::Shanghai, ForkCondition::Timestamp(GENESIS_TIME));
823 let future_fork = (EthereumHardfork::Cancun, ForkCondition::Timestamp(GENESIS_TIME + 1));
824
825 let chain_spec = ChainSpecBuilder::default()
826 .chain(Chain::dev())
827 .genesis(genesis)
828 .with_fork(active_fork.0, active_fork.1)
829 .with_fork(future_fork.0, future_fork.1)
830 .build();
831
832 let genesis_fork_hash = ForkHash::from(chain_spec.genesis_hash());
834 let fork_id = ForkId { hash: genesis_fork_hash, next: GENESIS_TIME + 1 };
835 assert_eq!(
837 fork_id,
838 chain_spec.fork_id(&Head {
839 hash: chain_spec.genesis_hash(),
840 number: 0,
841 timestamp: GENESIS_TIME,
842 difficulty: U256::ZERO,
843 total_difficulty: U256::ZERO,
844 })
845 );
846 assert_ne!(fork_id, chain_spec.latest_fork_id());
847
848 let fork_key = b"odyssey";
850 let config = builder()
851 .discovery_v5(
852 reth_discv5::Config::builder((Ipv4Addr::LOCALHOST, 30303).into())
853 .fork(fork_key, fork_id),
854 )
855 .build_with_noop_provider(Arc::new(chain_spec));
856
857 let (local_enr, _, _, _) = build_local_enr(
858 &config.secret_key,
859 &config.discovery_v5_config.expect("should build config"),
860 );
861
862 let advertised_fork_id = *local_enr
865 .get_decodable::<Vec<ForkId>>(fork_key)
866 .expect("should read 'odyssey'")
867 .expect("should decode fork id list")
868 .first()
869 .expect("should be non-empty");
870
871 assert_eq!(advertised_fork_id, fork_id);
872 }
873}