1use crate::{
4 error::NetworkError,
5 import::{BlockImport, ProofOfStakeBlockImport},
6 transactions::TransactionsManagerConfig,
7 NetworkHandle, NetworkManager,
8};
9use reth_chainspec::{ChainSpecProvider, EthChainSpec, Hardforks};
10use reth_discv4::{Discv4Config, Discv4ConfigBuilder, NatResolver, DEFAULT_DISCOVERY_ADDRESS};
11use reth_discv5::NetworkStackId;
12use reth_dns_discovery::DnsDiscoveryConfig;
13use reth_eth_wire::{
14 handshake::{EthHandshake, EthRlpxHandshake},
15 EthNetworkPrimitives, HelloMessage, HelloMessageWithProtocols, NetworkPrimitives,
16 UnifiedStatus,
17};
18use reth_ethereum_forks::{ForkFilter, Head};
19use reth_network_peers::{mainnet_nodes, pk2id, sepolia_nodes, PeerId, TrustedPeer};
20use reth_network_types::{PeersConfig, SessionsConfig};
21use reth_storage_api::{noop::NoopProvider, BlockNumReader, BlockReader, HeaderProvider};
22use reth_tasks::{TaskSpawner, TokioTaskExecutor};
23use secp256k1::SECP256K1;
24use std::{collections::HashSet, net::SocketAddr, sync::Arc};
25
26use crate::{
28 protocol::{IntoRlpxSubProtocol, RlpxSubProtocols},
29 transactions::TransactionPropagationMode,
30};
31pub use secp256k1::SecretKey;
32
33pub fn rng_secret_key() -> SecretKey {
35 SecretKey::new(&mut rand_08::thread_rng())
36}
37
38#[derive(Debug)]
40pub struct NetworkConfig<C, N: NetworkPrimitives = EthNetworkPrimitives> {
41 pub client: C,
46 pub secret_key: SecretKey,
48 pub boot_nodes: HashSet<TrustedPeer>,
50 pub dns_discovery_config: Option<DnsDiscoveryConfig>,
52 pub discovery_v4_addr: SocketAddr,
54 pub discovery_v4_config: Option<Discv4Config>,
56 pub discovery_v5_config: Option<reth_discv5::Config>,
58 pub listener_addr: SocketAddr,
60 pub peers_config: PeersConfig,
62 pub sessions_config: SessionsConfig,
64 pub chain_id: u64,
66 pub fork_filter: ForkFilter,
73 pub block_import: Box<dyn BlockImport<N::NewBlockPayload>>,
75 pub network_mode: NetworkMode,
77 pub executor: Box<dyn TaskSpawner>,
79 pub status: UnifiedStatus,
81 pub hello_message: HelloMessageWithProtocols,
83 pub extra_protocols: RlpxSubProtocols,
85 pub tx_gossip_disabled: bool,
87 pub transactions_manager_config: TransactionsManagerConfig,
89 pub nat: Option<NatResolver>,
91 pub handshake: Arc<dyn EthRlpxHandshake>,
96}
97
98impl<N: NetworkPrimitives> NetworkConfig<(), N> {
101 pub fn builder(secret_key: SecretKey) -> NetworkConfigBuilder<N> {
103 NetworkConfigBuilder::new(secret_key)
104 }
105
106 pub fn builder_with_rng_secret_key() -> NetworkConfigBuilder<N> {
108 NetworkConfigBuilder::with_rng_secret_key()
109 }
110}
111
112impl<C, N: NetworkPrimitives> NetworkConfig<C, N> {
113 pub fn new(client: C, secret_key: SecretKey) -> Self
115 where
116 C: ChainSpecProvider<ChainSpec: Hardforks>,
117 {
118 NetworkConfig::builder(secret_key).build(client)
119 }
120
121 pub fn apply<F>(self, f: F) -> Self
123 where
124 F: FnOnce(Self) -> Self,
125 {
126 f(self)
127 }
128
129 pub fn set_discovery_v4(mut self, discovery_config: Discv4Config) -> Self {
131 self.discovery_v4_config = Some(discovery_config);
132 self
133 }
134
135 pub const fn set_listener_addr(mut self, listener_addr: SocketAddr) -> Self {
137 self.listener_addr = listener_addr;
138 self
139 }
140
141 pub const fn listener_addr(&self) -> &SocketAddr {
143 &self.listener_addr
144 }
145}
146
147impl<C, N> NetworkConfig<C, N>
148where
149 C: BlockNumReader + 'static,
150 N: NetworkPrimitives,
151{
152 pub async fn manager(self) -> Result<NetworkManager<N>, NetworkError> {
154 NetworkManager::new(self).await
155 }
156}
157
158impl<C, N> NetworkConfig<C, N>
159where
160 N: NetworkPrimitives,
161 C: BlockReader<Block = N::Block, Receipt = N::Receipt, Header = N::BlockHeader>
162 + HeaderProvider
163 + Clone
164 + Unpin
165 + 'static,
166{
167 pub async fn start_network(self) -> Result<NetworkHandle<N>, NetworkError> {
169 let client = self.client.clone();
170 let (handle, network, _txpool, eth) = NetworkManager::builder::<C>(self)
171 .await?
172 .request_handler::<C>(client)
173 .split_with_handle();
174
175 tokio::task::spawn(network);
176 tokio::task::spawn(eth);
177 Ok(handle)
178 }
179}
180
181#[derive(Debug)]
183pub struct NetworkConfigBuilder<N: NetworkPrimitives = EthNetworkPrimitives> {
184 secret_key: SecretKey,
186 dns_discovery_config: Option<DnsDiscoveryConfig>,
188 discovery_v4_builder: Option<Discv4ConfigBuilder>,
190 discovery_v5_builder: Option<reth_discv5::ConfigBuilder>,
192 boot_nodes: HashSet<TrustedPeer>,
194 discovery_addr: Option<SocketAddr>,
196 listener_addr: Option<SocketAddr>,
198 peers_config: Option<PeersConfig>,
200 sessions_config: Option<SessionsConfig>,
202 network_mode: NetworkMode,
204 executor: Option<Box<dyn TaskSpawner>>,
206 hello_message: Option<HelloMessageWithProtocols>,
208 extra_protocols: RlpxSubProtocols,
210 head: Option<Head>,
212 tx_gossip_disabled: bool,
214 block_import: Option<Box<dyn BlockImport<N::NewBlockPayload>>>,
216 transactions_manager_config: TransactionsManagerConfig,
218 nat: Option<NatResolver>,
220 handshake: Arc<dyn EthRlpxHandshake>,
223}
224
225impl NetworkConfigBuilder<EthNetworkPrimitives> {
226 pub fn eth(secret_key: SecretKey) -> Self {
228 Self::new(secret_key)
229 }
230}
231
232#[expect(missing_docs)]
235impl<N: NetworkPrimitives> NetworkConfigBuilder<N> {
236 pub fn with_rng_secret_key() -> Self {
238 Self::new(rng_secret_key())
239 }
240
241 pub fn new(secret_key: SecretKey) -> 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: None,
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 }
264 }
265
266 pub fn apply<F>(self, f: F) -> Self
268 where
269 F: FnOnce(Self) -> Self,
270 {
271 f(self)
272 }
273
274 pub fn get_peer_id(&self) -> PeerId {
276 pk2id(&self.secret_key.public_key(SECP256K1))
277 }
278
279 pub const fn secret_key(&self) -> &SecretKey {
281 &self.secret_key
282 }
283
284 pub const fn network_mode(mut self, network_mode: NetworkMode) -> Self {
286 self.network_mode = network_mode;
287 self
288 }
289
290 pub const fn with_pow(self) -> Self {
298 self.network_mode(NetworkMode::Work)
299 }
300
301 pub const fn set_head(mut self, head: Head) -> Self {
307 self.head = Some(head);
308 self
309 }
310
311 pub fn hello_message(mut self, hello_message: HelloMessageWithProtocols) -> Self {
322 self.hello_message = Some(hello_message);
323 self
324 }
325
326 pub fn peer_config(mut self, config: PeersConfig) -> Self {
328 self.peers_config = Some(config);
329 self
330 }
331
332 pub fn with_task_executor(mut self, executor: Box<dyn TaskSpawner>) -> Self {
336 self.executor = Some(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));
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 const 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 block_import(mut self, block_import: Box<dyn BlockImport<N::NewBlockPayload>>) -> Self {
549 self.block_import = Some(block_import);
550 self
551 }
552
553 pub fn build_with_noop_provider<ChainSpec>(
556 self,
557 chain_spec: Arc<ChainSpec>,
558 ) -> NetworkConfig<NoopProvider<ChainSpec>, N>
559 where
560 ChainSpec: EthChainSpec + Hardforks + 'static,
561 {
562 self.build(NoopProvider::eth(chain_spec))
563 }
564
565 pub const fn add_nat(mut self, nat: Option<NatResolver>) -> Self {
567 self.nat = nat;
568 self
569 }
570
571 pub fn eth_rlpx_handshake(mut self, handshake: Arc<dyn EthRlpxHandshake>) -> Self {
573 self.handshake = handshake;
574 self
575 }
576
577 pub fn build<C>(self, client: C) -> NetworkConfig<C, N>
584 where
585 C: ChainSpecProvider<ChainSpec: Hardforks>,
586 {
587 let peer_id = self.get_peer_id();
588 let chain_spec = client.chain_spec();
589 let Self {
590 secret_key,
591 mut dns_discovery_config,
592 discovery_v4_builder,
593 mut discovery_v5_builder,
594 boot_nodes,
595 discovery_addr,
596 listener_addr,
597 peers_config,
598 sessions_config,
599 network_mode,
600 executor,
601 hello_message,
602 extra_protocols,
603 head,
604 tx_gossip_disabled,
605 block_import,
606 transactions_manager_config,
607 nat,
608 handshake,
609 } = self;
610
611 let head = head.unwrap_or_else(|| Head {
612 hash: chain_spec.genesis_hash(),
613 number: 0,
614 timestamp: chain_spec.genesis().timestamp,
615 difficulty: chain_spec.genesis().difficulty,
616 total_difficulty: chain_spec.genesis().difficulty,
617 });
618
619 discovery_v5_builder = discovery_v5_builder.map(|mut builder| {
620 if let Some(network_stack_id) = NetworkStackId::id(&chain_spec) {
621 let fork_id = chain_spec.fork_id(&head);
622 builder = builder.fork(network_stack_id, fork_id)
623 }
624
625 builder
626 });
627
628 let listener_addr = listener_addr.unwrap_or(DEFAULT_DISCOVERY_ADDRESS);
629
630 let mut hello_message =
631 hello_message.unwrap_or_else(|| HelloMessage::builder(peer_id).build());
632 hello_message.port = listener_addr.port();
633
634 let status = UnifiedStatus::spec_builder(&chain_spec, &head);
636
637 let fork_filter = chain_spec.fork_filter(head);
639
640 let chain_id = chain_spec.chain().id();
642
643 if let Some(dns_networks) =
645 dns_discovery_config.as_mut().and_then(|c| c.bootstrap_dns_networks.as_mut())
646 {
647 if dns_networks.is_empty() {
648 if let Some(link) = chain_spec.chain().public_dns_network_protocol() {
649 dns_networks.insert(link.parse().expect("is valid DNS link entry"));
650 }
651 }
652 }
653
654 NetworkConfig {
655 client,
656 secret_key,
657 boot_nodes,
658 dns_discovery_config,
659 discovery_v4_config: discovery_v4_builder.map(|builder| builder.build()),
660 discovery_v5_config: discovery_v5_builder.map(|builder| builder.build()),
661 discovery_v4_addr: discovery_addr.unwrap_or(DEFAULT_DISCOVERY_ADDRESS),
662 listener_addr,
663 peers_config: peers_config.unwrap_or_default(),
664 sessions_config: sessions_config.unwrap_or_default(),
665 chain_id,
666 block_import: block_import.unwrap_or_else(|| Box::<ProofOfStakeBlockImport>::default()),
667 network_mode,
668 executor: executor.unwrap_or_else(|| Box::<TokioTaskExecutor>::default()),
669 status,
670 hello_message,
671 extra_protocols,
672 fork_filter,
673 tx_gossip_disabled,
674 transactions_manager_config,
675 nat,
676 handshake,
677 }
678 }
679}
680
681#[derive(Debug, Clone, Copy, Eq, PartialEq, Default)]
687#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
688pub enum NetworkMode {
689 Work,
691 #[default]
693 Stake,
694}
695
696impl NetworkMode {
699 pub const fn is_stake(&self) -> bool {
701 matches!(self, Self::Stake)
702 }
703}
704
705#[cfg(test)]
706mod tests {
707 use super::*;
708 use alloy_eips::eip2124::ForkHash;
709 use alloy_genesis::Genesis;
710 use alloy_primitives::U256;
711 use reth_chainspec::{
712 Chain, ChainSpecBuilder, EthereumHardfork, ForkCondition, ForkId, MAINNET,
713 };
714 use reth_discv5::build_local_enr;
715 use reth_dns_discovery::tree::LinkEntry;
716 use reth_storage_api::noop::NoopProvider;
717 use std::{net::Ipv4Addr, sync::Arc};
718
719 fn builder() -> NetworkConfigBuilder {
720 let secret_key = SecretKey::new(&mut rand_08::thread_rng());
721 NetworkConfigBuilder::new(secret_key)
722 }
723
724 #[test]
725 fn test_network_dns_defaults() {
726 let config = builder().build(NoopProvider::default());
727
728 let dns = config.dns_discovery_config.unwrap();
729 let bootstrap_nodes = dns.bootstrap_dns_networks.unwrap();
730 let mainnet_dns: LinkEntry =
731 Chain::mainnet().public_dns_network_protocol().unwrap().parse().unwrap();
732 assert!(bootstrap_nodes.contains(&mainnet_dns));
733 assert_eq!(bootstrap_nodes.len(), 1);
734 }
735
736 #[test]
737 fn test_network_fork_filter_default() {
738 let mut chain_spec = Arc::clone(&MAINNET);
739
740 Arc::make_mut(&mut chain_spec).hardforks = Default::default();
742
743 let genesis_fork_hash = ForkHash::from(chain_spec.genesis_hash());
745
746 let config = builder().build_with_noop_provider(chain_spec);
748
749 let status = config.status;
750 let fork_filter = config.fork_filter;
751
752 assert_eq!(status.forkid.next, 0);
754
755 assert_eq!(fork_filter.current().next, 0);
757
758 assert_eq!(status.forkid.hash, genesis_fork_hash);
760 assert_eq!(fork_filter.current().hash, genesis_fork_hash);
761 }
762
763 #[test]
764 fn test_discv5_fork_id_default() {
765 const GENESIS_TIME: u64 = 151_515;
766
767 let genesis = Genesis::default().with_timestamp(GENESIS_TIME);
768
769 let active_fork = (EthereumHardfork::Shanghai, ForkCondition::Timestamp(GENESIS_TIME));
770 let future_fork = (EthereumHardfork::Cancun, ForkCondition::Timestamp(GENESIS_TIME + 1));
771
772 let chain_spec = ChainSpecBuilder::default()
773 .chain(Chain::dev())
774 .genesis(genesis)
775 .with_fork(active_fork.0, active_fork.1)
776 .with_fork(future_fork.0, future_fork.1)
777 .build();
778
779 let genesis_fork_hash = ForkHash::from(chain_spec.genesis_hash());
781 let fork_id = ForkId { hash: genesis_fork_hash, next: GENESIS_TIME + 1 };
782 assert_eq!(
784 fork_id,
785 chain_spec.fork_id(&Head {
786 hash: chain_spec.genesis_hash(),
787 number: 0,
788 timestamp: GENESIS_TIME,
789 difficulty: U256::ZERO,
790 total_difficulty: U256::ZERO,
791 })
792 );
793 assert_ne!(fork_id, chain_spec.latest_fork_id());
794
795 let fork_key = b"odyssey";
797 let config = builder()
798 .discovery_v5(
799 reth_discv5::Config::builder((Ipv4Addr::LOCALHOST, 30303).into())
800 .fork(fork_key, fork_id),
801 )
802 .build_with_noop_provider(Arc::new(chain_spec));
803
804 let (local_enr, _, _, _) = build_local_enr(
805 &config.secret_key,
806 &config.discovery_v5_config.expect("should build config"),
807 );
808
809 let advertised_fork_id = *local_enr
812 .get_decodable::<Vec<ForkId>>(fork_key)
813 .expect("should read 'odyssey'")
814 .expect("should decode fork id list")
815 .first()
816 .expect("should be non-empty");
817
818 assert_eq!(advertised_fork_id, fork_id);
819 }
820}