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_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::Runtime;
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: Runtime,
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<BlockNumHash>,
100}
101
102impl<N: NetworkPrimitives> NetworkConfig<(), N> {
105 pub fn builder(secret_key: SecretKey, executor: Runtime) -> NetworkConfigBuilder<N> {
107 NetworkConfigBuilder::new(secret_key, executor)
108 }
109
110 pub fn builder_with_rng_secret_key(executor: Runtime) -> NetworkConfigBuilder<N> {
112 NetworkConfigBuilder::with_rng_secret_key(executor)
113 }
114}
115
116impl<C, N: NetworkPrimitives> NetworkConfig<C, N> {
117 pub fn apply<F>(self, f: F) -> Self
119 where
120 F: FnOnce(Self) -> Self,
121 {
122 f(self)
123 }
124
125 pub fn set_discovery_v4(mut self, discovery_config: Discv4Config) -> Self {
127 self.discovery_v4_config = Some(discovery_config);
128 self
129 }
130
131 pub const fn set_listener_addr(mut self, listener_addr: SocketAddr) -> Self {
133 self.listener_addr = listener_addr;
134 self
135 }
136
137 pub const fn listener_addr(&self) -> &SocketAddr {
139 &self.listener_addr
140 }
141}
142
143impl<C, N> NetworkConfig<C, N>
144where
145 C: BlockNumReader + 'static,
146 N: NetworkPrimitives,
147{
148 pub async fn manager(self) -> Result<NetworkManager<N>, NetworkError> {
150 NetworkManager::new(self).await
151 }
152}
153
154impl<C, N> NetworkConfig<C, N>
155where
156 N: NetworkPrimitives,
157 C: BlockReader<Block = N::Block, Receipt = N::Receipt, Header = N::BlockHeader>
158 + HeaderProvider
159 + Clone
160 + Unpin
161 + 'static,
162{
163 pub async fn start_network(self) -> Result<NetworkHandle<N>, NetworkError> {
165 let client = self.client.clone();
166 let (handle, network, _txpool, eth) = NetworkManager::builder::<C>(self)
167 .await?
168 .request_handler::<C>(client)
169 .split_with_handle();
170
171 tokio::task::spawn(network);
172 tokio::task::spawn(eth);
173 Ok(handle)
174 }
175}
176
177#[derive(Debug)]
179pub struct NetworkConfigBuilder<N: NetworkPrimitives = EthNetworkPrimitives> {
180 secret_key: SecretKey,
182 dns_discovery_config: Option<DnsDiscoveryConfig>,
184 discovery_v4_builder: Option<Discv4ConfigBuilder>,
186 discovery_v5_builder: Option<reth_discv5::ConfigBuilder>,
188 boot_nodes: HashSet<TrustedPeer>,
190 discovery_addr: Option<SocketAddr>,
192 listener_addr: Option<SocketAddr>,
194 peers_config: Option<PeersConfig>,
196 sessions_config: Option<SessionsConfig>,
198 network_mode: NetworkMode,
200 executor: Runtime,
202 hello_message: Option<HelloMessageWithProtocols>,
204 extra_protocols: RlpxSubProtocols,
206 head: Option<Head>,
208 tx_gossip_disabled: bool,
210 block_import: Option<Box<dyn BlockImport<N::NewBlockPayload>>>,
212 transactions_manager_config: TransactionsManagerConfig,
214 nat: Option<NatResolver>,
216 handshake: Arc<dyn EthRlpxHandshake>,
219 required_block_hashes: Vec<BlockNumHash>,
221 network_id: Option<u64>,
223}
224
225impl NetworkConfigBuilder<EthNetworkPrimitives> {
226 pub fn eth(secret_key: SecretKey, executor: Runtime) -> Self {
228 Self::new(secret_key, executor)
229 }
230}
231
232#[expect(missing_docs)]
235impl<N: NetworkPrimitives> NetworkConfigBuilder<N> {
236 pub fn with_rng_secret_key(executor: Runtime) -> Self {
238 Self::new(rng_secret_key(), executor)
239 }
240
241 pub fn new(secret_key: SecretKey, executor: Runtime) -> Self {
243 Self {
244 secret_key,
245 dns_discovery_config: Some(Default::default()),
246 discovery_v4_builder: Some(Default::default()),
247 discovery_v5_builder: None,
248 boot_nodes: Default::default(),
249 discovery_addr: None,
250 listener_addr: None,
251 peers_config: None,
252 sessions_config: None,
253 network_mode: Default::default(),
254 executor,
255 hello_message: None,
256 extra_protocols: Default::default(),
257 head: None,
258 tx_gossip_disabled: false,
259 block_import: None,
260 transactions_manager_config: Default::default(),
261 nat: None,
262 handshake: Arc::new(EthHandshake::default()),
263 required_block_hashes: Vec::new(),
264 network_id: None,
265 }
266 }
267
268 pub fn apply<F>(self, f: F) -> Self
270 where
271 F: FnOnce(Self) -> Self,
272 {
273 f(self)
274 }
275
276 pub fn get_peer_id(&self) -> PeerId {
278 pk2id(&self.secret_key.public_key(SECP256K1))
279 }
280
281 pub const fn secret_key(&self) -> &SecretKey {
283 &self.secret_key
284 }
285
286 pub const fn network_mode(mut self, network_mode: NetworkMode) -> Self {
288 self.network_mode = network_mode;
289 self
290 }
291
292 pub const fn with_pow(self) -> Self {
300 self.network_mode(NetworkMode::Work)
301 }
302
303 pub const fn set_head(mut self, head: Head) -> Self {
309 self.head = Some(head);
310 self
311 }
312
313 pub fn hello_message(mut self, hello_message: HelloMessageWithProtocols) -> Self {
324 self.hello_message = Some(hello_message);
325 self
326 }
327
328 pub fn peer_config(mut self, config: PeersConfig) -> Self {
330 self.peers_config = Some(config);
331 self
332 }
333
334 pub fn with_task_executor(mut self, executor: Runtime) -> Self {
336 self.executor = executor;
337 self
338 }
339
340 pub const fn sessions_config(mut self, config: SessionsConfig) -> Self {
342 self.sessions_config = Some(config);
343 self
344 }
345
346 pub const fn transactions_manager_config(mut self, config: TransactionsManagerConfig) -> Self {
348 self.transactions_manager_config = config;
349 self
350 }
351
352 pub const fn transaction_propagation_mode(mut self, mode: TransactionPropagationMode) -> Self {
354 self.transactions_manager_config.propagation_mode = mode;
355 self
356 }
357
358 pub const fn set_addrs(self, addr: SocketAddr) -> Self {
366 self.listener_addr(addr).discovery_addr(addr)
367 }
368
369 pub const fn listener_addr(mut self, listener_addr: SocketAddr) -> Self {
373 self.listener_addr = Some(listener_addr);
374 self
375 }
376
377 pub fn listener_port(mut self, port: u16) -> Self {
381 self.listener_addr.get_or_insert(DEFAULT_DISCOVERY_ADDRESS).set_port(port);
382 self
383 }
384
385 pub const fn discovery_addr(mut self, discovery_addr: SocketAddr) -> Self {
387 self.discovery_addr = Some(discovery_addr);
388 self
389 }
390
391 pub fn discovery_port(mut self, port: u16) -> Self {
395 self.discovery_addr.get_or_insert(DEFAULT_DISCOVERY_ADDRESS).set_port(port);
396 self
397 }
398
399 pub fn with_unused_ports(self) -> Self {
402 self.with_unused_discovery_port().with_unused_listener_port()
403 }
404
405 pub fn with_unused_discovery_port(self) -> Self {
408 self.discovery_port(0)
409 }
410
411 pub fn with_unused_listener_port(self) -> Self {
414 self.listener_port(0)
415 }
416
417 pub fn external_ip_resolver(mut self, resolver: NatResolver) -> Self {
424 self.discovery_v4_builder
425 .get_or_insert_with(Discv4Config::builder)
426 .external_ip_resolver(Some(resolver.clone()));
427 self.nat = Some(resolver);
428 self
429 }
430
431 pub fn discovery(mut self, builder: Discv4ConfigBuilder) -> Self {
433 self.discovery_v4_builder = Some(builder);
434 self
435 }
436
437 pub fn discovery_v5(mut self, builder: reth_discv5::ConfigBuilder) -> Self {
439 self.discovery_v5_builder = Some(builder);
440 self
441 }
442
443 pub fn dns_discovery(mut self, config: DnsDiscoveryConfig) -> Self {
445 self.dns_discovery_config = Some(config);
446 self
447 }
448
449 pub fn mainnet_boot_nodes(self) -> Self {
451 self.boot_nodes(mainnet_nodes())
452 }
453
454 pub fn sepolia_boot_nodes(self) -> Self {
456 self.boot_nodes(sepolia_nodes())
457 }
458
459 pub fn boot_nodes<T: Into<TrustedPeer>>(mut self, nodes: impl IntoIterator<Item = T>) -> Self {
461 self.boot_nodes = nodes.into_iter().map(Into::into).collect();
462 self
463 }
464
465 pub fn boot_nodes_iter(&self) -> impl Iterator<Item = &TrustedPeer> + '_ {
467 self.boot_nodes.iter()
468 }
469
470 pub fn disable_dns_discovery(mut self) -> Self {
472 self.dns_discovery_config = None;
473 self
474 }
475
476 pub fn disable_nat(mut self) -> Self {
478 self.nat = None;
479 self
480 }
481
482 pub fn disable_discovery(self) -> Self {
484 self.disable_discv4_discovery().disable_discv5_discovery().disable_dns_discovery()
485 }
486
487 pub fn disable_discovery_if(self, disable: bool) -> Self {
489 if disable {
490 self.disable_discovery()
491 } else {
492 self
493 }
494 }
495
496 pub fn disable_discv4_discovery(mut self) -> Self {
498 self.discovery_v4_builder = None;
499 self
500 }
501
502 pub fn disable_discv5_discovery(mut self) -> Self {
504 self.discovery_v5_builder = None;
505 self
506 }
507
508 pub fn disable_dns_discovery_if(self, disable: bool) -> Self {
510 if disable {
511 self.disable_dns_discovery()
512 } else {
513 self
514 }
515 }
516
517 pub fn disable_discv4_discovery_if(self, disable: bool) -> Self {
519 if disable {
520 self.disable_discv4_discovery()
521 } else {
522 self
523 }
524 }
525
526 pub fn disable_discv5_discovery_if(self, disable: bool) -> Self {
528 if disable {
529 self.disable_discv5_discovery()
530 } else {
531 self
532 }
533 }
534
535 pub fn add_rlpx_sub_protocol(mut self, protocol: impl IntoRlpxSubProtocol) -> Self {
537 self.extra_protocols.push(protocol);
538 self
539 }
540
541 pub const fn disable_tx_gossip(mut self, disable_tx_gossip: bool) -> Self {
543 self.tx_gossip_disabled = disable_tx_gossip;
544 self
545 }
546
547 pub fn required_block_hashes(mut self, hashes: Vec<BlockNumHash>) -> Self {
549 self.required_block_hashes = hashes;
550 self
551 }
552
553 pub fn block_import(mut self, block_import: Box<dyn BlockImport<N::NewBlockPayload>>) -> Self {
555 self.block_import = Some(block_import);
556 self
557 }
558
559 pub fn build_with_noop_provider<ChainSpec>(
562 self,
563 chain_spec: Arc<ChainSpec>,
564 ) -> NetworkConfig<NoopProvider<ChainSpec>, N>
565 where
566 ChainSpec: EthChainSpec + Hardforks + 'static,
567 {
568 self.build(NoopProvider::eth(chain_spec))
569 }
570
571 pub fn add_nat(mut self, nat: Option<NatResolver>) -> Self {
573 self.nat = nat;
574 self
575 }
576
577 pub fn eth_rlpx_handshake(mut self, handshake: Arc<dyn EthRlpxHandshake>) -> Self {
579 self.handshake = handshake;
580 self
581 }
582
583 pub const fn network_id(mut self, network_id: Option<u64>) -> Self {
585 self.network_id = network_id;
586 self
587 }
588
589 pub fn build<C>(self, client: C) -> NetworkConfig<C, N>
596 where
597 C: ChainSpecProvider<ChainSpec: Hardforks>,
598 {
599 let peer_id = self.get_peer_id();
600 let chain_spec = client.chain_spec();
601 let Self {
602 secret_key,
603 mut dns_discovery_config,
604 discovery_v4_builder,
605 mut discovery_v5_builder,
606 boot_nodes,
607 discovery_addr,
608 listener_addr,
609 peers_config,
610 sessions_config,
611 network_mode,
612 executor,
613 hello_message,
614 extra_protocols,
615 head,
616 tx_gossip_disabled,
617 block_import,
618 transactions_manager_config,
619 nat,
620 handshake,
621 required_block_hashes,
622 network_id,
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 mut status = UnifiedStatus::spec_builder(&chain_spec, &head);
650
651 if let Some(id) = network_id {
652 status.chain = id.into();
653 }
654
655 let fork_filter = chain_spec.fork_filter(head);
657
658 let chain_id = chain_spec.chain().id();
660
661 if let Some(dns_networks) =
663 dns_discovery_config.as_mut().and_then(|c| c.bootstrap_dns_networks.as_mut()) &&
664 dns_networks.is_empty() &&
665 let Some(link) = chain_spec.chain().public_dns_network_protocol()
666 {
667 dns_networks.insert(link.parse().expect("is valid DNS link entry"));
668 }
669
670 NetworkConfig {
671 client,
672 secret_key,
673 boot_nodes,
674 dns_discovery_config,
675 discovery_v4_config: discovery_v4_builder.map(|builder| builder.build()),
676 discovery_v5_config: discovery_v5_builder.map(|builder| builder.build()),
677 discovery_v4_addr: discovery_addr.unwrap_or(DEFAULT_DISCOVERY_ADDRESS),
678 listener_addr,
679 peers_config: peers_config.unwrap_or_default(),
680 sessions_config: sessions_config.unwrap_or_default(),
681 chain_id,
682 block_import: block_import.unwrap_or_else(|| Box::<ProofOfStakeBlockImport>::default()),
683 network_mode,
684 executor,
685 status,
686 hello_message,
687 extra_protocols,
688 fork_filter,
689 tx_gossip_disabled,
690 transactions_manager_config,
691 nat,
692 handshake,
693 required_block_hashes,
694 }
695 }
696}
697
698#[derive(Debug, Clone, Copy, Eq, PartialEq, Default)]
704#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
705pub enum NetworkMode {
706 Work,
708 #[default]
710 Stake,
711}
712
713impl NetworkMode {
716 pub const fn is_stake(&self) -> bool {
718 matches!(self, Self::Stake)
719 }
720}
721
722#[cfg(test)]
723mod tests {
724 use super::*;
725 use alloy_eips::eip2124::ForkHash;
726 use alloy_genesis::Genesis;
727 use alloy_primitives::U256;
728 use reth_chainspec::{
729 Chain, ChainSpecBuilder, EthereumHardfork, ForkCondition, ForkId, MAINNET,
730 };
731 use reth_discv5::build_local_enr;
732 use reth_dns_discovery::tree::LinkEntry;
733 use reth_storage_api::noop::NoopProvider;
734 use std::{net::Ipv4Addr, sync::Arc};
735
736 fn builder() -> NetworkConfigBuilder {
737 let secret_key = SecretKey::new(&mut rand_08::thread_rng());
738 NetworkConfigBuilder::new(secret_key, Runtime::test())
739 }
740
741 #[test]
742 fn test_network_dns_defaults() {
743 let config = builder().build(NoopProvider::default());
744
745 let dns = config.dns_discovery_config.unwrap();
746 let bootstrap_nodes = dns.bootstrap_dns_networks.unwrap();
747 let mainnet_dns: LinkEntry =
748 Chain::mainnet().public_dns_network_protocol().unwrap().parse().unwrap();
749 assert!(bootstrap_nodes.contains(&mainnet_dns));
750 assert_eq!(bootstrap_nodes.len(), 1);
751 }
752
753 #[test]
754 fn test_network_fork_filter_default() {
755 let mut chain_spec = Arc::clone(&MAINNET);
756
757 Arc::make_mut(&mut chain_spec).hardforks = Default::default();
759
760 let genesis_fork_hash = ForkHash::from(chain_spec.genesis_hash());
762
763 let config = builder().build_with_noop_provider(chain_spec);
765
766 let status = config.status;
767 let fork_filter = config.fork_filter;
768
769 assert_eq!(status.forkid.next, 0);
771
772 assert_eq!(fork_filter.current().next, 0);
774
775 assert_eq!(status.forkid.hash, genesis_fork_hash);
777 assert_eq!(fork_filter.current().hash, genesis_fork_hash);
778 }
779
780 #[test]
781 fn test_discv5_fork_id_default() {
782 const GENESIS_TIME: u64 = 151_515;
783
784 let genesis = Genesis::default().with_timestamp(GENESIS_TIME);
785
786 let active_fork = (EthereumHardfork::Shanghai, ForkCondition::Timestamp(GENESIS_TIME));
787 let future_fork = (EthereumHardfork::Cancun, ForkCondition::Timestamp(GENESIS_TIME + 1));
788
789 let chain_spec = ChainSpecBuilder::default()
790 .chain(Chain::dev())
791 .genesis(genesis)
792 .with_fork(active_fork.0, active_fork.1)
793 .with_fork(future_fork.0, future_fork.1)
794 .build();
795
796 let genesis_fork_hash = ForkHash::from(chain_spec.genesis_hash());
798 let fork_id = ForkId { hash: genesis_fork_hash, next: GENESIS_TIME + 1 };
799 assert_eq!(
801 fork_id,
802 chain_spec.fork_id(&Head {
803 hash: chain_spec.genesis_hash(),
804 number: 0,
805 timestamp: GENESIS_TIME,
806 difficulty: U256::ZERO,
807 total_difficulty: U256::ZERO,
808 })
809 );
810 assert_ne!(fork_id, chain_spec.latest_fork_id());
811
812 let fork_key = b"odyssey";
814 let config = builder()
815 .discovery_v5(
816 reth_discv5::Config::builder((Ipv4Addr::LOCALHOST, 30303).into())
817 .fork(fork_key, fork_id),
818 )
819 .build_with_noop_provider(Arc::new(chain_spec));
820
821 let (local_enr, _, _, _) = build_local_enr(
822 &config.secret_key,
823 &config.discovery_v5_config.expect("should build config"),
824 );
825
826 let advertised_fork_id = *local_enr
829 .get_decodable::<Vec<ForkId>>(fork_key)
830 .expect("should read 'odyssey'")
831 .expect("should decode fork id list")
832 .first()
833 .expect("should be non-empty");
834
835 assert_eq!(advertised_fork_id, fork_id);
836 }
837}