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,
92 pub tx_gossip_disabled: bool,
94 pub transactions_manager_config: TransactionsManagerConfig,
96 pub nat: Option<NatResolver>,
98 pub handshake: Arc<dyn EthRlpxHandshake>,
103 pub eth_max_message_size: usize,
105 pub required_block_hashes: Vec<BlockNumHash>,
108}
109
110impl<N: NetworkPrimitives> NetworkConfig<(), N> {
113 pub fn builder(secret_key: SecretKey, executor: Runtime) -> NetworkConfigBuilder<N> {
115 NetworkConfigBuilder::new(secret_key, executor)
116 }
117
118 pub fn builder_with_rng_secret_key(executor: Runtime) -> NetworkConfigBuilder<N> {
120 NetworkConfigBuilder::with_rng_secret_key(executor)
121 }
122}
123
124impl<C, N: NetworkPrimitives> NetworkConfig<C, N> {
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: BalProvider
166 + BlockReader<Block = N::Block, Receipt = N::Receipt, Header = N::BlockHeader>
167 + HeaderProvider
168 + Clone
169 + Unpin
170 + 'static,
171{
172 pub async fn start_network(self) -> Result<NetworkHandle<N>, NetworkError> {
174 let client = self.client.clone();
175 let (handle, network, _txpool, eth) = NetworkManager::builder::<C>(self)
176 .await?
177 .request_handler::<C>(client)
178 .split_with_handle();
179
180 tokio::task::spawn(network);
181 tokio::task::spawn(eth);
182 Ok(handle)
183 }
184}
185
186#[derive(Debug)]
188pub struct NetworkConfigBuilder<N: NetworkPrimitives = EthNetworkPrimitives> {
189 secret_key: SecretKey,
191 dns_discovery_config: Option<DnsDiscoveryConfig>,
193 discovery_v4_builder: Option<Discv4ConfigBuilder>,
195 discovery_v5_builder: Option<reth_discv5::ConfigBuilder>,
197 boot_nodes: HashSet<TrustedPeer>,
199 discovery_addr: Option<SocketAddr>,
201 listener_addr: Option<SocketAddr>,
203 peers_config: Option<PeersConfig>,
205 sessions_config: Option<SessionsConfig>,
207 network_mode: NetworkMode,
209 executor: Runtime,
211 hello_message: Option<HelloMessageWithProtocols>,
213 extra_protocols: RlpxSubProtocols,
216 head: Option<Head>,
218 tx_gossip_disabled: bool,
220 block_import: Option<Box<dyn BlockImport<N::NewBlockPayload>>>,
222 transactions_manager_config: TransactionsManagerConfig,
224 nat: Option<NatResolver>,
226 handshake: Arc<dyn EthRlpxHandshake>,
229 eth_max_message_size: usize,
231 required_block_hashes: Vec<BlockNumHash>,
233 network_id: Option<u64>,
235 snap_enabled: bool,
237}
238
239impl NetworkConfigBuilder<EthNetworkPrimitives> {
240 pub fn eth(secret_key: SecretKey, executor: Runtime) -> Self {
242 Self::new(secret_key, executor)
243 }
244}
245
246#[expect(missing_docs)]
249impl<N: NetworkPrimitives> NetworkConfigBuilder<N> {
250 pub fn with_rng_secret_key(executor: Runtime) -> Self {
252 Self::new(rng_secret_key(), executor)
253 }
254
255 pub fn new(secret_key: SecretKey, executor: Runtime) -> Self {
257 Self {
258 secret_key,
259 dns_discovery_config: Some(Default::default()),
260 discovery_v4_builder: Some(Default::default()),
261 discovery_v5_builder: None,
262 boot_nodes: Default::default(),
263 discovery_addr: None,
264 listener_addr: None,
265 peers_config: None,
266 sessions_config: None,
267 network_mode: Default::default(),
268 executor,
269 hello_message: None,
270 extra_protocols: Default::default(),
271 head: None,
272 tx_gossip_disabled: false,
273 block_import: None,
274 transactions_manager_config: Default::default(),
275 nat: None,
276 handshake: Arc::new(EthHandshake::default()),
277 eth_max_message_size: MAX_MESSAGE_SIZE,
278 required_block_hashes: Vec::new(),
279 network_id: None,
280 snap_enabled: false,
281 }
282 }
283
284 pub fn apply<F>(self, f: F) -> Self
286 where
287 F: FnOnce(Self) -> Self,
288 {
289 f(self)
290 }
291
292 pub fn get_peer_id(&self) -> PeerId {
294 pk2id(&self.secret_key.public_key(SECP256K1))
295 }
296
297 pub const fn secret_key(&self) -> &SecretKey {
299 &self.secret_key
300 }
301
302 pub const fn network_mode(mut self, network_mode: NetworkMode) -> Self {
304 self.network_mode = network_mode;
305 self
306 }
307
308 pub const fn with_pow(self) -> Self {
316 self.network_mode(NetworkMode::Work)
317 }
318
319 pub const fn set_head(mut self, head: Head) -> Self {
325 self.head = Some(head);
326 self
327 }
328
329 pub fn hello_message(mut self, hello_message: HelloMessageWithProtocols) -> Self {
340 self.hello_message = Some(hello_message);
341 self
342 }
343
344 pub fn peer_config(mut self, config: PeersConfig) -> Self {
346 self.peers_config = Some(config);
347 self
348 }
349
350 pub fn with_task_executor(mut self, executor: Runtime) -> Self {
352 self.executor = executor;
353 self
354 }
355
356 pub const fn sessions_config(mut self, config: SessionsConfig) -> Self {
358 self.sessions_config = Some(config);
359 self
360 }
361
362 pub const fn transactions_manager_config(mut self, config: TransactionsManagerConfig) -> Self {
364 self.transactions_manager_config = config;
365 self
366 }
367
368 pub const fn transaction_propagation_mode(mut self, mode: TransactionPropagationMode) -> Self {
370 self.transactions_manager_config.propagation_mode = mode;
371 self
372 }
373
374 pub const fn set_addrs(self, addr: SocketAddr) -> Self {
382 self.listener_addr(addr).discovery_addr(addr)
383 }
384
385 pub const fn listener_addr(mut self, listener_addr: SocketAddr) -> Self {
389 self.listener_addr = Some(listener_addr);
390 self
391 }
392
393 pub fn listener_port(mut self, port: u16) -> Self {
397 self.listener_addr.get_or_insert(DEFAULT_DISCOVERY_ADDRESS).set_port(port);
398 self
399 }
400
401 pub const fn discovery_addr(mut self, discovery_addr: SocketAddr) -> Self {
403 self.discovery_addr = Some(discovery_addr);
404 self
405 }
406
407 pub fn discovery_port(mut self, port: u16) -> Self {
411 self.discovery_addr.get_or_insert(DEFAULT_DISCOVERY_ADDRESS).set_port(port);
412 self
413 }
414
415 pub fn with_unused_ports(self) -> Self {
418 self.with_unused_discovery_port().with_unused_listener_port()
419 }
420
421 pub fn with_unused_discovery_port(self) -> Self {
424 self.discovery_port(0)
425 }
426
427 pub fn with_unused_listener_port(self) -> Self {
430 self.listener_port(0)
431 }
432
433 pub fn external_ip_resolver(mut self, resolver: NatResolver) -> Self {
440 self.discovery_v4_builder
441 .get_or_insert_with(Discv4Config::builder)
442 .external_ip_resolver(Some(resolver.clone()));
443 self.nat = Some(resolver);
444 self
445 }
446
447 pub fn discovery(mut self, builder: Discv4ConfigBuilder) -> Self {
449 self.discovery_v4_builder = Some(builder);
450 self
451 }
452
453 pub fn discovery_v5(mut self, builder: reth_discv5::ConfigBuilder) -> Self {
455 self.discovery_v5_builder = Some(builder);
456 self
457 }
458
459 pub fn dns_discovery(mut self, config: DnsDiscoveryConfig) -> Self {
461 self.dns_discovery_config = Some(config);
462 self
463 }
464
465 pub fn mainnet_boot_nodes(self) -> Self {
467 self.boot_nodes(mainnet_nodes())
468 }
469
470 pub fn sepolia_boot_nodes(self) -> Self {
472 self.boot_nodes(sepolia_nodes())
473 }
474
475 pub fn boot_nodes<T: Into<TrustedPeer>>(mut self, nodes: impl IntoIterator<Item = T>) -> Self {
477 self.boot_nodes = nodes.into_iter().map(Into::into).collect();
478 self
479 }
480
481 pub fn boot_nodes_iter(&self) -> impl Iterator<Item = &TrustedPeer> + '_ {
483 self.boot_nodes.iter()
484 }
485
486 pub fn disable_dns_discovery(mut self) -> Self {
488 self.dns_discovery_config = None;
489 self
490 }
491
492 pub fn disable_nat(mut self) -> Self {
494 self.nat = None;
495 self
496 }
497
498 pub fn disable_discovery(self) -> Self {
500 self.disable_discv4_discovery().disable_discv5_discovery().disable_dns_discovery()
501 }
502
503 pub fn disable_discovery_if(self, disable: bool) -> Self {
505 if disable {
506 self.disable_discovery()
507 } else {
508 self
509 }
510 }
511
512 pub fn disable_discv4_discovery(mut self) -> Self {
514 self.discovery_v4_builder = None;
515 self
516 }
517
518 pub fn disable_discv5_discovery(mut self) -> Self {
520 self.discovery_v5_builder = None;
521 self
522 }
523
524 pub fn disable_dns_discovery_if(self, disable: bool) -> Self {
526 if disable {
527 self.disable_dns_discovery()
528 } else {
529 self
530 }
531 }
532
533 pub fn disable_discv4_discovery_if(self, disable: bool) -> Self {
535 if disable {
536 self.disable_discv4_discovery()
537 } else {
538 self
539 }
540 }
541
542 pub fn disable_discv5_discovery_if(self, disable: bool) -> Self {
544 if disable {
545 self.disable_discv5_discovery()
546 } else {
547 self
548 }
549 }
550
551 pub fn add_rlpx_sub_protocol(mut self, protocol: impl IntoRlpxSubProtocol) -> Self {
555 self.extra_protocols.push(protocol);
556 self
557 }
558
559 pub const fn with_snap(mut self, snap_enabled: bool) -> Self {
563 self.snap_enabled = snap_enabled;
564 self
565 }
566
567 pub const fn disable_tx_gossip(mut self, disable_tx_gossip: bool) -> Self {
569 self.tx_gossip_disabled = disable_tx_gossip;
570 self
571 }
572
573 pub fn required_block_hashes(mut self, hashes: Vec<BlockNumHash>) -> Self {
575 self.required_block_hashes = hashes;
576 self
577 }
578
579 pub fn block_import(mut self, block_import: Box<dyn BlockImport<N::NewBlockPayload>>) -> Self {
581 self.block_import = Some(block_import);
582 self
583 }
584
585 pub fn build_with_noop_provider<ChainSpec>(
588 self,
589 chain_spec: Arc<ChainSpec>,
590 ) -> NetworkConfig<NoopProvider<ChainSpec>, N>
591 where
592 ChainSpec: EthChainSpec + Hardforks + 'static,
593 {
594 self.build(NoopProvider::eth(chain_spec))
595 }
596
597 pub fn add_nat(mut self, nat: Option<NatResolver>) -> Self {
599 self.nat = nat;
600 self
601 }
602
603 pub fn eth_rlpx_handshake(mut self, handshake: Arc<dyn EthRlpxHandshake>) -> Self {
605 self.handshake = handshake;
606 self
607 }
608
609 pub const fn eth_max_message_size(mut self, max_message_size: usize) -> Self {
614 self.eth_max_message_size = max_message_size;
615 self
616 }
617
618 pub const fn eth_max_message_size_opt(mut self, max_message_size: Option<usize>) -> Self {
620 if let Some(max_message_size) = max_message_size {
621 self.eth_max_message_size = max_message_size;
622 }
623 self
624 }
625
626 pub const fn network_id(mut self, network_id: Option<u64>) -> Self {
628 self.network_id = network_id;
629 self
630 }
631
632 pub fn build<C>(self, client: C) -> NetworkConfig<C, N>
639 where
640 C: ChainSpecProvider<ChainSpec: Hardforks>,
641 {
642 let peer_id = self.get_peer_id();
643 let chain_spec = client.chain_spec();
644 let Self {
645 secret_key,
646 mut dns_discovery_config,
647 discovery_v4_builder,
648 mut discovery_v5_builder,
649 boot_nodes,
650 discovery_addr,
651 listener_addr,
652 peers_config,
653 sessions_config,
654 network_mode,
655 executor,
656 hello_message,
657 extra_protocols,
658 head,
659 tx_gossip_disabled,
660 block_import,
661 transactions_manager_config,
662 nat,
663 handshake,
664 eth_max_message_size,
665 required_block_hashes,
666 network_id,
667 snap_enabled,
668 } = self;
669
670 let head = head.unwrap_or_else(|| Head {
671 hash: chain_spec.genesis_hash(),
672 number: 0,
673 timestamp: chain_spec.genesis().timestamp,
674 difficulty: chain_spec.genesis().difficulty,
675 total_difficulty: chain_spec.genesis().difficulty,
676 });
677
678 let listener_addr = listener_addr.unwrap_or(DEFAULT_DISCOVERY_ADDRESS);
679 let advertised_ip = nat.clone().and_then(|nat| nat.as_external_ip(listener_addr.port()));
683
684 discovery_v5_builder = discovery_v5_builder.map(|mut builder| {
685 if let Some(network_stack_id) = NetworkStackId::id(&chain_spec) {
686 let fork_id = chain_spec.fork_id(&head);
687 builder = builder.fork(network_stack_id, fork_id)
688 }
689
690 if let Some(ip) = advertised_ip {
691 builder = builder.advertised_ip(ip);
692 }
693
694 builder
695 });
696
697 let mut hello_message =
698 hello_message.unwrap_or_else(|| HelloMessage::builder(peer_id).build());
699 hello_message.port = listener_addr.port();
700 hello_message = hello_message.with_snap(snap_enabled);
701
702 let mut status = UnifiedStatus::spec_builder(&chain_spec, &head);
704
705 if let Some(id) = network_id {
706 status.chain = id.into();
707 }
708
709 let fork_filter = chain_spec.fork_filter(head);
711
712 let chain_id = chain_spec.chain().id();
714
715 if let Some(dns_networks) =
717 dns_discovery_config.as_mut().and_then(|c| c.bootstrap_dns_networks.as_mut()) &&
718 dns_networks.is_empty() &&
719 let Some(link) = chain_spec.chain().public_dns_network_protocol()
720 {
721 dns_networks.insert(link.parse().expect("is valid DNS link entry"));
722 }
723
724 NetworkConfig {
725 client,
726 secret_key,
727 boot_nodes,
728 dns_discovery_config,
729 discovery_v4_config: discovery_v4_builder.map(|builder| builder.build()),
730 discovery_v5_config: discovery_v5_builder.map(|builder| builder.build()),
731 discovery_v4_addr: discovery_addr.unwrap_or(DEFAULT_DISCOVERY_ADDRESS),
732 listener_addr,
733 peers_config: peers_config.unwrap_or_default(),
734 sessions_config: sessions_config.unwrap_or_default(),
735 chain_id,
736 block_import: block_import.unwrap_or_else(|| Box::<ProofOfStakeBlockImport>::default()),
737 network_mode,
738 executor,
739 status,
740 hello_message,
741 extra_protocols,
742 fork_filter,
743 tx_gossip_disabled,
744 transactions_manager_config,
745 nat,
746 handshake,
747 eth_max_message_size,
748 required_block_hashes,
749 }
750 }
751}
752
753#[derive(Debug, Clone, Copy, Eq, PartialEq, Default)]
759#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
760pub enum NetworkMode {
761 Work,
763 #[default]
765 Stake,
766}
767
768impl NetworkMode {
771 pub const fn is_stake(&self) -> bool {
773 matches!(self, Self::Stake)
774 }
775}
776
777#[cfg(test)]
778mod tests {
779 use super::*;
780 use alloy_eips::eip2124::ForkHash;
781 use alloy_genesis::Genesis;
782 use alloy_primitives::U256;
783 use reth_chainspec::{
784 Chain, ChainSpecBuilder, EthereumHardfork, ForkCondition, ForkId, MAINNET,
785 };
786 use reth_discv5::build_local_enr;
787 use reth_dns_discovery::tree::LinkEntry;
788 use reth_storage_api::noop::NoopProvider;
789 use std::{net::Ipv4Addr, sync::Arc};
790
791 fn builder() -> NetworkConfigBuilder {
792 let secret_key = SecretKey::new(&mut rand_08::thread_rng());
793 NetworkConfigBuilder::new(secret_key, Runtime::test())
794 }
795
796 #[test]
797 fn test_snap_advertisement_default_off() {
798 let config = builder().build(NoopProvider::default());
800 assert!(config.hello_message.protocols.iter().all(|p| p.cap.name != "snap"));
801 }
802
803 #[test]
804 fn test_snap_advertisement_when_enabled() {
805 let config = builder().with_snap(true).build(NoopProvider::default());
806 let snap_caps =
807 config.hello_message.protocols.iter().filter(|p| p.cap.name == "snap").count();
808 assert_eq!(snap_caps, 1);
809 assert_eq!(
810 config
811 .hello_message
812 .protocols
813 .iter()
814 .find(|p| p.cap.name == "snap")
815 .unwrap()
816 .cap
817 .version,
818 2
819 );
820 assert!(config.hello_message.protocols.iter().any(|p| p.cap.name == "eth"));
822 }
823
824 #[test]
825 fn test_network_dns_defaults() {
826 let config = builder().build(NoopProvider::default());
827
828 let dns = config.dns_discovery_config.unwrap();
829 let bootstrap_nodes = dns.bootstrap_dns_networks.unwrap();
830 let mainnet_dns: LinkEntry =
831 Chain::mainnet().public_dns_network_protocol().unwrap().parse().unwrap();
832 assert!(bootstrap_nodes.contains(&mainnet_dns));
833 assert_eq!(bootstrap_nodes.len(), 1);
834 }
835
836 #[test]
837 fn test_network_fork_filter_default() {
838 let mut chain_spec = Arc::clone(&MAINNET);
839
840 Arc::make_mut(&mut chain_spec).hardforks = Default::default();
842
843 let genesis_fork_hash = ForkHash::from(chain_spec.genesis_hash());
845
846 let config = builder().build_with_noop_provider(chain_spec);
848
849 let status = config.status;
850 let fork_filter = config.fork_filter;
851
852 assert_eq!(status.forkid.next, 0);
854
855 assert_eq!(fork_filter.current().next, 0);
857
858 assert_eq!(status.forkid.hash, genesis_fork_hash);
860 assert_eq!(fork_filter.current().hash, genesis_fork_hash);
861 }
862
863 #[test]
864 fn test_discv5_fork_id_default() {
865 const GENESIS_TIME: u64 = 151_515;
866
867 let genesis = Genesis::default().with_timestamp(GENESIS_TIME);
868
869 let active_fork = (EthereumHardfork::Shanghai, ForkCondition::Timestamp(GENESIS_TIME));
870 let future_fork = (EthereumHardfork::Cancun, ForkCondition::Timestamp(GENESIS_TIME + 1));
871
872 let chain_spec = ChainSpecBuilder::default()
873 .chain(Chain::dev())
874 .genesis(genesis)
875 .with_fork(active_fork.0, active_fork.1)
876 .with_fork(future_fork.0, future_fork.1)
877 .build();
878
879 let genesis_fork_hash = ForkHash::from(chain_spec.genesis_hash());
881 let fork_id = ForkId { hash: genesis_fork_hash, next: GENESIS_TIME + 1 };
882 assert_eq!(
884 fork_id,
885 chain_spec.fork_id(&Head {
886 hash: chain_spec.genesis_hash(),
887 number: 0,
888 timestamp: GENESIS_TIME,
889 difficulty: U256::ZERO,
890 total_difficulty: U256::ZERO,
891 })
892 );
893 assert_ne!(fork_id, chain_spec.latest_fork_id());
894
895 let fork_key = b"odyssey";
897 let config = builder()
898 .discovery_v5(
899 reth_discv5::Config::builder((Ipv4Addr::LOCALHOST, 30303).into())
900 .fork(fork_key, fork_id),
901 )
902 .build_with_noop_provider(Arc::new(chain_spec));
903
904 let (local_enr, _, _, _) = build_local_enr(
905 &config.secret_key,
906 &config.discovery_v5_config.expect("should build config"),
907 );
908
909 let advertised_fork_id = *local_enr
912 .get_decodable::<Vec<ForkId>>(fork_key)
913 .expect("should read 'odyssey'")
914 .expect("should decode fork id list")
915 .first()
916 .expect("should be non-empty");
917
918 assert_eq!(advertised_fork_id, fork_id);
919 }
920}