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::{noop::NoopProvider, BlockNumReader, BlockReader, HeaderProvider};
24use reth_tasks::Runtime;
25use secp256k1::SECP256K1;
26use std::{collections::HashSet, net::SocketAddr, sync::Arc};
27
28use crate::{
30 protocol::{IntoRlpxSubProtocol, RlpxSubProtocols},
31 transactions::TransactionPropagationMode,
32};
33pub use secp256k1::SecretKey;
34
35pub fn rng_secret_key() -> SecretKey {
37 SecretKey::new(&mut rand_08::thread_rng())
38}
39
40#[derive(Debug)]
42pub struct NetworkConfig<C, N: NetworkPrimitives = EthNetworkPrimitives> {
43 pub client: C,
48 pub secret_key: SecretKey,
50 pub boot_nodes: HashSet<TrustedPeer>,
52 pub dns_discovery_config: Option<DnsDiscoveryConfig>,
54 pub discovery_v4_addr: SocketAddr,
56 pub discovery_v4_config: Option<Discv4Config>,
58 pub discovery_v5_config: Option<reth_discv5::Config>,
60 pub listener_addr: SocketAddr,
62 pub peers_config: PeersConfig,
64 pub sessions_config: SessionsConfig,
66 pub chain_id: u64,
68 pub fork_filter: ForkFilter,
75 pub block_import: Box<dyn BlockImport<N::NewBlockPayload>>,
77 pub network_mode: NetworkMode,
79 pub executor: Runtime,
81 pub status: UnifiedStatus,
83 pub hello_message: HelloMessageWithProtocols,
85 pub extra_protocols: RlpxSubProtocols,
87 pub tx_gossip_disabled: bool,
89 pub transactions_manager_config: TransactionsManagerConfig,
91 pub nat: Option<NatResolver>,
93 pub handshake: Arc<dyn EthRlpxHandshake>,
98 pub eth_max_message_size: usize,
100 pub required_block_hashes: Vec<BlockNumHash>,
103}
104
105impl<N: NetworkPrimitives> NetworkConfig<(), N> {
108 pub fn builder(secret_key: SecretKey, executor: Runtime) -> NetworkConfigBuilder<N> {
110 NetworkConfigBuilder::new(secret_key, executor)
111 }
112
113 pub fn builder_with_rng_secret_key(executor: Runtime) -> NetworkConfigBuilder<N> {
115 NetworkConfigBuilder::with_rng_secret_key(executor)
116 }
117}
118
119impl<C, N: NetworkPrimitives> NetworkConfig<C, N> {
120 pub fn apply<F>(self, f: F) -> Self
122 where
123 F: FnOnce(Self) -> Self,
124 {
125 f(self)
126 }
127
128 pub fn set_discovery_v4(mut self, discovery_config: Discv4Config) -> Self {
130 self.discovery_v4_config = Some(discovery_config);
131 self
132 }
133
134 pub const fn set_listener_addr(mut self, listener_addr: SocketAddr) -> Self {
136 self.listener_addr = listener_addr;
137 self
138 }
139
140 pub const fn listener_addr(&self) -> &SocketAddr {
142 &self.listener_addr
143 }
144}
145
146impl<C, N> NetworkConfig<C, N>
147where
148 C: BlockNumReader + 'static,
149 N: NetworkPrimitives,
150{
151 pub async fn manager(self) -> Result<NetworkManager<N>, NetworkError> {
153 NetworkManager::new(self).await
154 }
155}
156
157impl<C, N> NetworkConfig<C, N>
158where
159 N: NetworkPrimitives,
160 C: BlockReader<Block = N::Block, Receipt = N::Receipt, Header = N::BlockHeader>
161 + HeaderProvider
162 + Clone
163 + Unpin
164 + 'static,
165{
166 pub async fn start_network(self) -> Result<NetworkHandle<N>, NetworkError> {
168 let client = self.client.clone();
169 let (handle, network, _txpool, eth) = NetworkManager::builder::<C>(self)
170 .await?
171 .request_handler::<C>(client)
172 .split_with_handle();
173
174 tokio::task::spawn(network);
175 tokio::task::spawn(eth);
176 Ok(handle)
177 }
178}
179
180#[derive(Debug)]
182pub struct NetworkConfigBuilder<N: NetworkPrimitives = EthNetworkPrimitives> {
183 secret_key: SecretKey,
185 dns_discovery_config: Option<DnsDiscoveryConfig>,
187 discovery_v4_builder: Option<Discv4ConfigBuilder>,
189 discovery_v5_builder: Option<reth_discv5::ConfigBuilder>,
191 boot_nodes: HashSet<TrustedPeer>,
193 discovery_addr: Option<SocketAddr>,
195 listener_addr: Option<SocketAddr>,
197 peers_config: Option<PeersConfig>,
199 sessions_config: Option<SessionsConfig>,
201 network_mode: NetworkMode,
203 executor: Runtime,
205 hello_message: Option<HelloMessageWithProtocols>,
207 extra_protocols: RlpxSubProtocols,
209 head: Option<Head>,
211 tx_gossip_disabled: bool,
213 block_import: Option<Box<dyn BlockImport<N::NewBlockPayload>>>,
215 transactions_manager_config: TransactionsManagerConfig,
217 nat: Option<NatResolver>,
219 handshake: Arc<dyn EthRlpxHandshake>,
222 eth_max_message_size: usize,
224 required_block_hashes: Vec<BlockNumHash>,
226 network_id: Option<u64>,
228}
229
230impl NetworkConfigBuilder<EthNetworkPrimitives> {
231 pub fn eth(secret_key: SecretKey, executor: Runtime) -> Self {
233 Self::new(secret_key, executor)
234 }
235}
236
237#[expect(missing_docs)]
240impl<N: NetworkPrimitives> NetworkConfigBuilder<N> {
241 pub fn with_rng_secret_key(executor: Runtime) -> Self {
243 Self::new(rng_secret_key(), executor)
244 }
245
246 pub fn new(secret_key: SecretKey, executor: Runtime) -> Self {
248 Self {
249 secret_key,
250 dns_discovery_config: Some(Default::default()),
251 discovery_v4_builder: Some(Default::default()),
252 discovery_v5_builder: None,
253 boot_nodes: Default::default(),
254 discovery_addr: None,
255 listener_addr: None,
256 peers_config: None,
257 sessions_config: None,
258 network_mode: Default::default(),
259 executor,
260 hello_message: None,
261 extra_protocols: Default::default(),
262 head: None,
263 tx_gossip_disabled: false,
264 block_import: None,
265 transactions_manager_config: Default::default(),
266 nat: None,
267 handshake: Arc::new(EthHandshake::default()),
268 eth_max_message_size: MAX_MESSAGE_SIZE,
269 required_block_hashes: Vec::new(),
270 network_id: None,
271 }
272 }
273
274 pub fn apply<F>(self, f: F) -> Self
276 where
277 F: FnOnce(Self) -> Self,
278 {
279 f(self)
280 }
281
282 pub fn get_peer_id(&self) -> PeerId {
284 pk2id(&self.secret_key.public_key(SECP256K1))
285 }
286
287 pub const fn secret_key(&self) -> &SecretKey {
289 &self.secret_key
290 }
291
292 pub const fn network_mode(mut self, network_mode: NetworkMode) -> Self {
294 self.network_mode = network_mode;
295 self
296 }
297
298 pub const fn with_pow(self) -> Self {
306 self.network_mode(NetworkMode::Work)
307 }
308
309 pub const fn set_head(mut self, head: Head) -> Self {
315 self.head = Some(head);
316 self
317 }
318
319 pub fn hello_message(mut self, hello_message: HelloMessageWithProtocols) -> Self {
330 self.hello_message = Some(hello_message);
331 self
332 }
333
334 pub fn peer_config(mut self, config: PeersConfig) -> Self {
336 self.peers_config = Some(config);
337 self
338 }
339
340 pub fn with_task_executor(mut self, executor: Runtime) -> Self {
342 self.executor = executor;
343 self
344 }
345
346 pub const fn sessions_config(mut self, config: SessionsConfig) -> Self {
348 self.sessions_config = Some(config);
349 self
350 }
351
352 pub const fn transactions_manager_config(mut self, config: TransactionsManagerConfig) -> Self {
354 self.transactions_manager_config = config;
355 self
356 }
357
358 pub const fn transaction_propagation_mode(mut self, mode: TransactionPropagationMode) -> Self {
360 self.transactions_manager_config.propagation_mode = mode;
361 self
362 }
363
364 pub const fn set_addrs(self, addr: SocketAddr) -> Self {
372 self.listener_addr(addr).discovery_addr(addr)
373 }
374
375 pub const fn listener_addr(mut self, listener_addr: SocketAddr) -> Self {
379 self.listener_addr = Some(listener_addr);
380 self
381 }
382
383 pub fn listener_port(mut self, port: u16) -> Self {
387 self.listener_addr.get_or_insert(DEFAULT_DISCOVERY_ADDRESS).set_port(port);
388 self
389 }
390
391 pub const fn discovery_addr(mut self, discovery_addr: SocketAddr) -> Self {
393 self.discovery_addr = Some(discovery_addr);
394 self
395 }
396
397 pub fn discovery_port(mut self, port: u16) -> Self {
401 self.discovery_addr.get_or_insert(DEFAULT_DISCOVERY_ADDRESS).set_port(port);
402 self
403 }
404
405 pub fn with_unused_ports(self) -> Self {
408 self.with_unused_discovery_port().with_unused_listener_port()
409 }
410
411 pub fn with_unused_discovery_port(self) -> Self {
414 self.discovery_port(0)
415 }
416
417 pub fn with_unused_listener_port(self) -> Self {
420 self.listener_port(0)
421 }
422
423 pub fn external_ip_resolver(mut self, resolver: NatResolver) -> Self {
430 self.discovery_v4_builder
431 .get_or_insert_with(Discv4Config::builder)
432 .external_ip_resolver(Some(resolver.clone()));
433 self.nat = Some(resolver);
434 self
435 }
436
437 pub fn discovery(mut self, builder: Discv4ConfigBuilder) -> Self {
439 self.discovery_v4_builder = Some(builder);
440 self
441 }
442
443 pub fn discovery_v5(mut self, builder: reth_discv5::ConfigBuilder) -> Self {
445 self.discovery_v5_builder = Some(builder);
446 self
447 }
448
449 pub fn dns_discovery(mut self, config: DnsDiscoveryConfig) -> Self {
451 self.dns_discovery_config = Some(config);
452 self
453 }
454
455 pub fn mainnet_boot_nodes(self) -> Self {
457 self.boot_nodes(mainnet_nodes())
458 }
459
460 pub fn sepolia_boot_nodes(self) -> Self {
462 self.boot_nodes(sepolia_nodes())
463 }
464
465 pub fn boot_nodes<T: Into<TrustedPeer>>(mut self, nodes: impl IntoIterator<Item = T>) -> Self {
467 self.boot_nodes = nodes.into_iter().map(Into::into).collect();
468 self
469 }
470
471 pub fn boot_nodes_iter(&self) -> impl Iterator<Item = &TrustedPeer> + '_ {
473 self.boot_nodes.iter()
474 }
475
476 pub fn disable_dns_discovery(mut self) -> Self {
478 self.dns_discovery_config = None;
479 self
480 }
481
482 pub fn disable_nat(mut self) -> Self {
484 self.nat = None;
485 self
486 }
487
488 pub fn disable_discovery(self) -> Self {
490 self.disable_discv4_discovery().disable_discv5_discovery().disable_dns_discovery()
491 }
492
493 pub fn disable_discovery_if(self, disable: bool) -> Self {
495 if disable {
496 self.disable_discovery()
497 } else {
498 self
499 }
500 }
501
502 pub fn disable_discv4_discovery(mut self) -> Self {
504 self.discovery_v4_builder = None;
505 self
506 }
507
508 pub fn disable_discv5_discovery(mut self) -> Self {
510 self.discovery_v5_builder = None;
511 self
512 }
513
514 pub fn disable_dns_discovery_if(self, disable: bool) -> Self {
516 if disable {
517 self.disable_dns_discovery()
518 } else {
519 self
520 }
521 }
522
523 pub fn disable_discv4_discovery_if(self, disable: bool) -> Self {
525 if disable {
526 self.disable_discv4_discovery()
527 } else {
528 self
529 }
530 }
531
532 pub fn disable_discv5_discovery_if(self, disable: bool) -> Self {
534 if disable {
535 self.disable_discv5_discovery()
536 } else {
537 self
538 }
539 }
540
541 pub fn add_rlpx_sub_protocol(mut self, protocol: impl IntoRlpxSubProtocol) -> Self {
543 self.extra_protocols.push(protocol);
544 self
545 }
546
547 pub const fn disable_tx_gossip(mut self, disable_tx_gossip: bool) -> Self {
549 self.tx_gossip_disabled = disable_tx_gossip;
550 self
551 }
552
553 pub fn required_block_hashes(mut self, hashes: Vec<BlockNumHash>) -> Self {
555 self.required_block_hashes = hashes;
556 self
557 }
558
559 pub fn block_import(mut self, block_import: Box<dyn BlockImport<N::NewBlockPayload>>) -> Self {
561 self.block_import = Some(block_import);
562 self
563 }
564
565 pub fn build_with_noop_provider<ChainSpec>(
568 self,
569 chain_spec: Arc<ChainSpec>,
570 ) -> NetworkConfig<NoopProvider<ChainSpec>, N>
571 where
572 ChainSpec: EthChainSpec + Hardforks + 'static,
573 {
574 self.build(NoopProvider::eth(chain_spec))
575 }
576
577 pub fn add_nat(mut self, nat: Option<NatResolver>) -> Self {
579 self.nat = nat;
580 self
581 }
582
583 pub fn eth_rlpx_handshake(mut self, handshake: Arc<dyn EthRlpxHandshake>) -> Self {
585 self.handshake = handshake;
586 self
587 }
588
589 pub const fn eth_max_message_size(mut self, max_message_size: usize) -> Self {
594 self.eth_max_message_size = max_message_size;
595 self
596 }
597
598 pub const fn eth_max_message_size_opt(mut self, max_message_size: Option<usize>) -> Self {
600 if let Some(max_message_size) = max_message_size {
601 self.eth_max_message_size = max_message_size;
602 }
603 self
604 }
605
606 pub const fn network_id(mut self, network_id: Option<u64>) -> Self {
608 self.network_id = network_id;
609 self
610 }
611
612 pub fn build<C>(self, client: C) -> NetworkConfig<C, N>
619 where
620 C: ChainSpecProvider<ChainSpec: Hardforks>,
621 {
622 let peer_id = self.get_peer_id();
623 let chain_spec = client.chain_spec();
624 let Self {
625 secret_key,
626 mut dns_discovery_config,
627 discovery_v4_builder,
628 mut discovery_v5_builder,
629 boot_nodes,
630 discovery_addr,
631 listener_addr,
632 peers_config,
633 sessions_config,
634 network_mode,
635 executor,
636 hello_message,
637 extra_protocols,
638 head,
639 tx_gossip_disabled,
640 block_import,
641 transactions_manager_config,
642 nat,
643 handshake,
644 eth_max_message_size,
645 required_block_hashes,
646 network_id,
647 } = self;
648
649 let head = head.unwrap_or_else(|| Head {
650 hash: chain_spec.genesis_hash(),
651 number: 0,
652 timestamp: chain_spec.genesis().timestamp,
653 difficulty: chain_spec.genesis().difficulty,
654 total_difficulty: chain_spec.genesis().difficulty,
655 });
656
657 discovery_v5_builder = discovery_v5_builder.map(|mut builder| {
658 if let Some(network_stack_id) = NetworkStackId::id(&chain_spec) {
659 let fork_id = chain_spec.fork_id(&head);
660 builder = builder.fork(network_stack_id, fork_id)
661 }
662
663 builder
664 });
665
666 let listener_addr = listener_addr.unwrap_or(DEFAULT_DISCOVERY_ADDRESS);
667
668 let mut hello_message =
669 hello_message.unwrap_or_else(|| HelloMessage::builder(peer_id).build());
670 hello_message.port = listener_addr.port();
671
672 let mut status = UnifiedStatus::spec_builder(&chain_spec, &head);
674
675 if let Some(id) = network_id {
676 status.chain = id.into();
677 }
678
679 let fork_filter = chain_spec.fork_filter(head);
681
682 let chain_id = chain_spec.chain().id();
684
685 if let Some(dns_networks) =
687 dns_discovery_config.as_mut().and_then(|c| c.bootstrap_dns_networks.as_mut()) &&
688 dns_networks.is_empty() &&
689 let Some(link) = chain_spec.chain().public_dns_network_protocol()
690 {
691 dns_networks.insert(link.parse().expect("is valid DNS link entry"));
692 }
693
694 NetworkConfig {
695 client,
696 secret_key,
697 boot_nodes,
698 dns_discovery_config,
699 discovery_v4_config: discovery_v4_builder.map(|builder| builder.build()),
700 discovery_v5_config: discovery_v5_builder.map(|builder| builder.build()),
701 discovery_v4_addr: discovery_addr.unwrap_or(DEFAULT_DISCOVERY_ADDRESS),
702 listener_addr,
703 peers_config: peers_config.unwrap_or_default(),
704 sessions_config: sessions_config.unwrap_or_default(),
705 chain_id,
706 block_import: block_import.unwrap_or_else(|| Box::<ProofOfStakeBlockImport>::default()),
707 network_mode,
708 executor,
709 status,
710 hello_message,
711 extra_protocols,
712 fork_filter,
713 tx_gossip_disabled,
714 transactions_manager_config,
715 nat,
716 handshake,
717 eth_max_message_size,
718 required_block_hashes,
719 }
720 }
721}
722
723#[derive(Debug, Clone, Copy, Eq, PartialEq, Default)]
729#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
730pub enum NetworkMode {
731 Work,
733 #[default]
735 Stake,
736}
737
738impl NetworkMode {
741 pub const fn is_stake(&self) -> bool {
743 matches!(self, Self::Stake)
744 }
745}
746
747#[cfg(test)]
748mod tests {
749 use super::*;
750 use alloy_eips::eip2124::ForkHash;
751 use alloy_genesis::Genesis;
752 use alloy_primitives::U256;
753 use reth_chainspec::{
754 Chain, ChainSpecBuilder, EthereumHardfork, ForkCondition, ForkId, MAINNET,
755 };
756 use reth_discv5::build_local_enr;
757 use reth_dns_discovery::tree::LinkEntry;
758 use reth_storage_api::noop::NoopProvider;
759 use std::{net::Ipv4Addr, sync::Arc};
760
761 fn builder() -> NetworkConfigBuilder {
762 let secret_key = SecretKey::new(&mut rand_08::thread_rng());
763 NetworkConfigBuilder::new(secret_key, Runtime::test())
764 }
765
766 #[test]
767 fn test_network_dns_defaults() {
768 let config = builder().build(NoopProvider::default());
769
770 let dns = config.dns_discovery_config.unwrap();
771 let bootstrap_nodes = dns.bootstrap_dns_networks.unwrap();
772 let mainnet_dns: LinkEntry =
773 Chain::mainnet().public_dns_network_protocol().unwrap().parse().unwrap();
774 assert!(bootstrap_nodes.contains(&mainnet_dns));
775 assert_eq!(bootstrap_nodes.len(), 1);
776 }
777
778 #[test]
779 fn test_network_fork_filter_default() {
780 let mut chain_spec = Arc::clone(&MAINNET);
781
782 Arc::make_mut(&mut chain_spec).hardforks = Default::default();
784
785 let genesis_fork_hash = ForkHash::from(chain_spec.genesis_hash());
787
788 let config = builder().build_with_noop_provider(chain_spec);
790
791 let status = config.status;
792 let fork_filter = config.fork_filter;
793
794 assert_eq!(status.forkid.next, 0);
796
797 assert_eq!(fork_filter.current().next, 0);
799
800 assert_eq!(status.forkid.hash, genesis_fork_hash);
802 assert_eq!(fork_filter.current().hash, genesis_fork_hash);
803 }
804
805 #[test]
806 fn test_discv5_fork_id_default() {
807 const GENESIS_TIME: u64 = 151_515;
808
809 let genesis = Genesis::default().with_timestamp(GENESIS_TIME);
810
811 let active_fork = (EthereumHardfork::Shanghai, ForkCondition::Timestamp(GENESIS_TIME));
812 let future_fork = (EthereumHardfork::Cancun, ForkCondition::Timestamp(GENESIS_TIME + 1));
813
814 let chain_spec = ChainSpecBuilder::default()
815 .chain(Chain::dev())
816 .genesis(genesis)
817 .with_fork(active_fork.0, active_fork.1)
818 .with_fork(future_fork.0, future_fork.1)
819 .build();
820
821 let genesis_fork_hash = ForkHash::from(chain_spec.genesis_hash());
823 let fork_id = ForkId { hash: genesis_fork_hash, next: GENESIS_TIME + 1 };
824 assert_eq!(
826 fork_id,
827 chain_spec.fork_id(&Head {
828 hash: chain_spec.genesis_hash(),
829 number: 0,
830 timestamp: GENESIS_TIME,
831 difficulty: U256::ZERO,
832 total_difficulty: U256::ZERO,
833 })
834 );
835 assert_ne!(fork_id, chain_spec.latest_fork_id());
836
837 let fork_key = b"odyssey";
839 let config = builder()
840 .discovery_v5(
841 reth_discv5::Config::builder((Ipv4Addr::LOCALHOST, 30303).into())
842 .fork(fork_key, fork_id),
843 )
844 .build_with_noop_provider(Arc::new(chain_spec));
845
846 let (local_enr, _, _, _) = build_local_enr(
847 &config.secret_key,
848 &config.discovery_v5_config.expect("should build config"),
849 );
850
851 let advertised_fork_id = *local_enr
854 .get_decodable::<Vec<ForkId>>(fork_key)
855 .expect("should read 'odyssey'")
856 .expect("should decode fork id list")
857 .first()
858 .expect("should be non-empty");
859
860 assert_eq!(advertised_fork_id, fork_id);
861 }
862}