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, SnapVersion};
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, StateProviderFactory,
25 StateRangeProviderFactory,
26};
27use reth_tasks::Runtime;
28use secp256k1::SECP256K1;
29use std::{collections::HashSet, net::SocketAddr, sync::Arc};
30
31use crate::{
33 protocol::{IntoRlpxSubProtocol, RlpxSubProtocols},
34 transactions::TransactionPropagationMode,
35};
36pub use secp256k1::SecretKey;
37
38pub fn rng_secret_key() -> SecretKey {
40 SecretKey::new(&mut rand_08::thread_rng())
41}
42
43#[derive(Debug)]
45pub struct NetworkConfig<C, N: NetworkPrimitives = EthNetworkPrimitives> {
46 pub client: C,
51 pub secret_key: SecretKey,
53 pub boot_nodes: HashSet<TrustedPeer>,
55 pub dns_discovery_config: Option<DnsDiscoveryConfig>,
57 pub discovery_v4_addr: SocketAddr,
59 pub discovery_v4_config: Option<Discv4Config>,
61 pub discovery_v5_config: Option<reth_discv5::Config>,
63 pub listener_addr: SocketAddr,
65 pub peers_config: PeersConfig,
67 pub sessions_config: SessionsConfig,
69 pub chain_id: u64,
71 pub fork_filter: ForkFilter,
78 pub block_import: Box<dyn BlockImport<N::NewBlockPayload>>,
80 pub network_mode: NetworkMode,
82 pub executor: Runtime,
84 pub status: UnifiedStatus,
86 pub hello_message: HelloMessageWithProtocols,
88 pub extra_protocols: RlpxSubProtocols,
93 pub tx_gossip_disabled: bool,
95 pub transactions_manager_config: TransactionsManagerConfig,
97 pub nat: Option<NatResolver>,
99 pub handshake: Arc<dyn EthRlpxHandshake>,
104 pub eth_max_message_size: usize,
106 pub required_block_hashes: Vec<BlockNumHash>,
109}
110
111impl<N: NetworkPrimitives> NetworkConfig<(), N> {
114 pub fn builder(secret_key: SecretKey, executor: Runtime) -> NetworkConfigBuilder<N> {
116 NetworkConfigBuilder::new(secret_key, executor)
117 }
118
119 pub fn builder_with_rng_secret_key(executor: Runtime) -> NetworkConfigBuilder<N> {
121 NetworkConfigBuilder::with_rng_secret_key(executor)
122 }
123}
124
125impl<C, N: NetworkPrimitives> NetworkConfig<C, N> {
126 pub fn apply<F>(self, f: F) -> Self
128 where
129 F: FnOnce(Self) -> Self,
130 {
131 f(self)
132 }
133
134 pub fn set_discovery_v4(mut self, discovery_config: Discv4Config) -> Self {
136 self.discovery_v4_config = Some(discovery_config);
137 self
138 }
139
140 pub const fn set_listener_addr(mut self, listener_addr: SocketAddr) -> Self {
142 self.listener_addr = listener_addr;
143 self
144 }
145
146 pub const fn listener_addr(&self) -> &SocketAddr {
148 &self.listener_addr
149 }
150}
151
152impl<C, N> NetworkConfig<C, N>
153where
154 C: BlockNumReader + 'static,
155 N: NetworkPrimitives,
156{
157 pub async fn manager(self) -> Result<NetworkManager<N>, NetworkError> {
159 NetworkManager::new(self).await
160 }
161}
162
163impl<C, N> NetworkConfig<C, N>
164where
165 N: NetworkPrimitives,
166 C: BalProvider
167 + StateProviderFactory
168 + StateRangeProviderFactory
169 + BlockReader<Block = N::Block, Receipt = N::Receipt, Header = N::BlockHeader>
170 + Clone
171 + Unpin
172 + 'static,
173{
174 pub async fn start_network(self) -> Result<NetworkHandle<N>, NetworkError> {
176 let client = self.client.clone();
177 let (handle, network, _txpool, eth) = NetworkManager::builder::<C>(self)
178 .await?
179 .request_handler::<C>(client)
180 .split_with_handle();
181
182 tokio::task::spawn(network);
183 tokio::task::spawn(eth);
184 Ok(handle)
185 }
186}
187
188#[derive(Debug)]
190pub struct NetworkConfigBuilder<N: NetworkPrimitives = EthNetworkPrimitives> {
191 secret_key: SecretKey,
193 dns_discovery_config: Option<DnsDiscoveryConfig>,
195 discovery_v4_builder: Option<Discv4ConfigBuilder>,
197 discovery_v5_builder: Option<reth_discv5::ConfigBuilder>,
199 boot_nodes: HashSet<TrustedPeer>,
201 discovery_addr: Option<SocketAddr>,
203 listener_addr: Option<SocketAddr>,
205 peers_config: Option<PeersConfig>,
207 sessions_config: Option<SessionsConfig>,
209 network_mode: NetworkMode,
211 executor: Runtime,
213 hello_message: Option<HelloMessageWithProtocols>,
215 extra_protocols: RlpxSubProtocols,
218 head: Option<Head>,
220 tx_gossip_disabled: bool,
222 block_import: Option<Box<dyn BlockImport<N::NewBlockPayload>>>,
224 transactions_manager_config: TransactionsManagerConfig,
226 nat: Option<NatResolver>,
228 handshake: Arc<dyn EthRlpxHandshake>,
231 eth_max_message_size: usize,
233 required_block_hashes: Vec<BlockNumHash>,
235 network_id: Option<u64>,
237 snap_enabled: bool,
239}
240
241impl NetworkConfigBuilder<EthNetworkPrimitives> {
242 pub fn eth(secret_key: SecretKey, executor: Runtime) -> Self {
244 Self::new(secret_key, executor)
245 }
246}
247
248#[expect(missing_docs)]
251impl<N: NetworkPrimitives> NetworkConfigBuilder<N> {
252 pub fn with_rng_secret_key(executor: Runtime) -> Self {
254 Self::new(rng_secret_key(), executor)
255 }
256
257 pub fn new(secret_key: SecretKey, executor: Runtime) -> Self {
259 Self {
260 secret_key,
261 dns_discovery_config: Some(Default::default()),
262 discovery_v4_builder: Some(Default::default()),
263 discovery_v5_builder: None,
264 boot_nodes: Default::default(),
265 discovery_addr: None,
266 listener_addr: None,
267 peers_config: None,
268 sessions_config: None,
269 network_mode: Default::default(),
270 executor,
271 hello_message: None,
272 extra_protocols: Default::default(),
273 head: None,
274 tx_gossip_disabled: false,
275 block_import: None,
276 transactions_manager_config: Default::default(),
277 nat: None,
278 handshake: Arc::new(EthHandshake::default()),
279 eth_max_message_size: MAX_MESSAGE_SIZE,
280 required_block_hashes: Vec::new(),
281 network_id: None,
282 snap_enabled: false,
283 }
284 }
285
286 pub fn apply<F>(self, f: F) -> Self
288 where
289 F: FnOnce(Self) -> Self,
290 {
291 f(self)
292 }
293
294 pub fn get_peer_id(&self) -> PeerId {
296 pk2id(&self.secret_key.public_key(SECP256K1))
297 }
298
299 pub const fn secret_key(&self) -> &SecretKey {
301 &self.secret_key
302 }
303
304 pub const fn network_mode(mut self, network_mode: NetworkMode) -> Self {
306 self.network_mode = network_mode;
307 self
308 }
309
310 pub const fn with_pow(self) -> Self {
318 self.network_mode(NetworkMode::Work)
319 }
320
321 pub const fn set_head(mut self, head: Head) -> Self {
327 self.head = Some(head);
328 self
329 }
330
331 pub fn hello_message(mut self, hello_message: HelloMessageWithProtocols) -> Self {
342 self.hello_message = Some(hello_message);
343 self
344 }
345
346 pub fn peer_config(mut self, config: PeersConfig) -> Self {
348 self.peers_config = Some(config);
349 self
350 }
351
352 pub fn with_task_executor(mut self, executor: Runtime) -> Self {
354 self.executor = executor;
355 self
356 }
357
358 pub const fn sessions_config(mut self, config: SessionsConfig) -> Self {
360 self.sessions_config = Some(config);
361 self
362 }
363
364 pub const fn transactions_manager_config(mut self, config: TransactionsManagerConfig) -> Self {
366 self.transactions_manager_config = config;
367 self
368 }
369
370 pub const fn transaction_propagation_mode(mut self, mode: TransactionPropagationMode) -> Self {
372 self.transactions_manager_config.propagation_mode = mode;
373 self
374 }
375
376 pub const fn set_addrs(self, addr: SocketAddr) -> Self {
384 self.listener_addr(addr).discovery_addr(addr)
385 }
386
387 pub const fn listener_addr(mut self, listener_addr: SocketAddr) -> Self {
391 self.listener_addr = Some(listener_addr);
392 self
393 }
394
395 pub fn listener_port(mut self, port: u16) -> Self {
399 self.listener_addr.get_or_insert(DEFAULT_DISCOVERY_ADDRESS).set_port(port);
400 self
401 }
402
403 pub const fn discovery_addr(mut self, discovery_addr: SocketAddr) -> Self {
405 self.discovery_addr = Some(discovery_addr);
406 self
407 }
408
409 pub fn discovery_port(mut self, port: u16) -> Self {
413 self.discovery_addr.get_or_insert(DEFAULT_DISCOVERY_ADDRESS).set_port(port);
414 self
415 }
416
417 pub fn with_unused_ports(self) -> Self {
420 self.with_unused_discovery_port().with_unused_listener_port()
421 }
422
423 pub fn with_unused_discovery_port(self) -> Self {
426 self.discovery_port(0)
427 }
428
429 pub fn with_unused_listener_port(self) -> Self {
432 self.listener_port(0)
433 }
434
435 pub fn external_ip_resolver(mut self, resolver: NatResolver) -> Self {
442 self.discovery_v4_builder
443 .get_or_insert_with(Discv4Config::builder)
444 .external_ip_resolver(Some(resolver.clone()));
445 self.nat = Some(resolver);
446 self
447 }
448
449 pub fn discovery(mut self, builder: Discv4ConfigBuilder) -> Self {
451 self.discovery_v4_builder = Some(builder);
452 self
453 }
454
455 pub fn discovery_v5(mut self, builder: reth_discv5::ConfigBuilder) -> Self {
457 self.discovery_v5_builder = Some(builder);
458 self
459 }
460
461 pub fn dns_discovery(mut self, config: DnsDiscoveryConfig) -> Self {
463 self.dns_discovery_config = Some(config);
464 self
465 }
466
467 pub fn mainnet_boot_nodes(self) -> Self {
469 self.boot_nodes(mainnet_nodes())
470 }
471
472 pub fn sepolia_boot_nodes(self) -> Self {
474 self.boot_nodes(sepolia_nodes())
475 }
476
477 pub fn boot_nodes<T: Into<TrustedPeer>>(mut self, nodes: impl IntoIterator<Item = T>) -> Self {
479 self.boot_nodes = nodes.into_iter().map(Into::into).collect();
480 self
481 }
482
483 pub fn boot_nodes_iter(&self) -> impl Iterator<Item = &TrustedPeer> + '_ {
485 self.boot_nodes.iter()
486 }
487
488 pub fn disable_dns_discovery(mut self) -> Self {
490 self.dns_discovery_config = None;
491 self
492 }
493
494 pub fn disable_nat(mut self) -> Self {
496 self.nat = None;
497 self
498 }
499
500 pub fn disable_discovery(self) -> Self {
502 self.disable_discv4_discovery().disable_discv5_discovery().disable_dns_discovery()
503 }
504
505 pub fn disable_discovery_if(self, disable: bool) -> Self {
507 if disable {
508 self.disable_discovery()
509 } else {
510 self
511 }
512 }
513
514 pub fn disable_discv4_discovery(mut self) -> Self {
516 self.discovery_v4_builder = None;
517 self
518 }
519
520 pub fn disable_discv5_discovery(mut self) -> Self {
522 self.discovery_v5_builder = None;
523 self
524 }
525
526 pub fn disable_dns_discovery_if(self, disable: bool) -> Self {
528 if disable {
529 self.disable_dns_discovery()
530 } else {
531 self
532 }
533 }
534
535 pub fn disable_discv4_discovery_if(self, disable: bool) -> Self {
537 if disable {
538 self.disable_discv4_discovery()
539 } else {
540 self
541 }
542 }
543
544 pub fn disable_discv5_discovery_if(self, disable: bool) -> Self {
546 if disable {
547 self.disable_discv5_discovery()
548 } else {
549 self
550 }
551 }
552
553 pub fn add_rlpx_sub_protocol(mut self, protocol: impl IntoRlpxSubProtocol) -> Self {
557 self.extra_protocols.push(protocol);
558 self
559 }
560
561 pub const fn with_snap(mut self, snap_enabled: bool) -> Self {
565 self.snap_enabled = snap_enabled;
566 self
567 }
568
569 pub const fn disable_tx_gossip(mut self, disable_tx_gossip: bool) -> Self {
571 self.tx_gossip_disabled = disable_tx_gossip;
572 self
573 }
574
575 pub fn required_block_hashes(mut self, hashes: Vec<BlockNumHash>) -> Self {
577 self.required_block_hashes = hashes;
578 self
579 }
580
581 pub fn block_import(mut self, block_import: Box<dyn BlockImport<N::NewBlockPayload>>) -> Self {
583 self.block_import = Some(block_import);
584 self
585 }
586
587 pub fn build_with_noop_provider<ChainSpec>(
590 self,
591 chain_spec: Arc<ChainSpec>,
592 ) -> NetworkConfig<NoopProvider<ChainSpec>, N>
593 where
594 ChainSpec: EthChainSpec + Hardforks + 'static,
595 {
596 self.build(NoopProvider::eth(chain_spec))
597 }
598
599 pub fn add_nat(mut self, nat: Option<NatResolver>) -> Self {
601 self.nat = nat;
602 self
603 }
604
605 pub fn eth_rlpx_handshake(mut self, handshake: Arc<dyn EthRlpxHandshake>) -> Self {
607 self.handshake = handshake;
608 self
609 }
610
611 pub const fn eth_max_message_size(mut self, max_message_size: usize) -> Self {
616 self.eth_max_message_size = max_message_size;
617 self
618 }
619
620 pub const fn eth_max_message_size_opt(mut self, max_message_size: Option<usize>) -> Self {
622 if let Some(max_message_size) = max_message_size {
623 self.eth_max_message_size = max_message_size;
624 }
625 self
626 }
627
628 pub const fn network_id(mut self, network_id: Option<u64>) -> Self {
630 self.network_id = network_id;
631 self
632 }
633
634 pub fn build<C>(self, client: C) -> NetworkConfig<C, N>
641 where
642 C: ChainSpecProvider<ChainSpec: Hardforks>,
643 {
644 let peer_id = self.get_peer_id();
645 let chain_spec = client.chain_spec();
646 let Self {
647 secret_key,
648 mut dns_discovery_config,
649 mut discovery_v4_builder,
650 mut discovery_v5_builder,
651 boot_nodes,
652 discovery_addr,
653 listener_addr,
654 peers_config,
655 sessions_config,
656 network_mode,
657 executor,
658 hello_message,
659 extra_protocols,
660 head,
661 tx_gossip_disabled,
662 block_import,
663 transactions_manager_config,
664 nat,
665 handshake,
666 eth_max_message_size,
667 required_block_hashes,
668 network_id,
669 snap_enabled,
670 } = self;
671
672 let head = head.unwrap_or_else(|| Head {
673 hash: chain_spec.genesis_hash(),
674 number: 0,
675 timestamp: chain_spec.genesis().timestamp,
676 difficulty: chain_spec.genesis().difficulty,
677 total_difficulty: chain_spec.genesis().difficulty,
678 });
679
680 let listener_addr = listener_addr.unwrap_or(DEFAULT_DISCOVERY_ADDRESS);
681 let advertised_ip = nat.clone().and_then(|nat| nat.as_external_ip(listener_addr.port()));
685
686 let snap_versions = snap_enabled.then(|| vec![SnapVersion::V2 as u64]);
688 if let (Some(builder), Some(versions)) = (discovery_v4_builder.as_mut(), &snap_versions) {
689 builder.add_eip868_pair("snap", versions);
690 }
691
692 discovery_v5_builder = discovery_v5_builder.map(|mut builder| {
693 let fork_id = chain_spec.fork_id(&head);
694 if let Some(network_stack_id) = NetworkStackId::id(&chain_spec) {
695 builder = builder.fork(network_stack_id, fork_id)
696 } else {
697 builder = builder.fork_if_unset(NetworkStackId::ETH, fork_id)
700 }
701
702 if let Some(ip) = advertised_ip {
703 builder = builder.advertised_ip(ip);
704 }
705
706 if let Some(versions) = &snap_versions {
707 builder = builder.add_enr_kv_pair(b"snap", alloy_rlp::encode(versions).into());
708 }
709
710 builder
711 });
712
713 let mut hello_message =
714 hello_message.unwrap_or_else(|| HelloMessage::builder(peer_id).build());
715 hello_message.port = listener_addr.port();
716 hello_message = hello_message.with_snap(snap_enabled);
717
718 let mut status = UnifiedStatus::spec_builder(&chain_spec, &head);
720
721 if let Some(id) = network_id {
722 status.chain = id.into();
723 }
724
725 let fork_filter = chain_spec.fork_filter(head);
727
728 let chain_id = chain_spec.chain().id();
730
731 if let Some(dns_networks) =
733 dns_discovery_config.as_mut().and_then(|c| c.bootstrap_dns_networks.as_mut()) &&
734 dns_networks.is_empty() &&
735 let Some(link) = chain_spec.chain().public_dns_network_protocol()
736 {
737 dns_networks.insert(link.parse().expect("is valid DNS link entry"));
738 }
739
740 NetworkConfig {
741 client,
742 secret_key,
743 boot_nodes,
744 dns_discovery_config,
745 discovery_v4_config: discovery_v4_builder.map(|builder| builder.build()),
746 discovery_v5_config: discovery_v5_builder.map(|builder| builder.build()),
747 discovery_v4_addr: discovery_addr.unwrap_or(DEFAULT_DISCOVERY_ADDRESS),
748 listener_addr,
749 peers_config: peers_config.unwrap_or_default(),
750 sessions_config: sessions_config.unwrap_or_default(),
751 chain_id,
752 block_import: block_import.unwrap_or_else(|| Box::<ProofOfStakeBlockImport>::default()),
753 network_mode,
754 executor,
755 status,
756 hello_message,
757 extra_protocols,
758 fork_filter,
759 tx_gossip_disabled,
760 transactions_manager_config,
761 nat,
762 handshake,
763 eth_max_message_size,
764 required_block_hashes,
765 }
766 }
767}
768
769#[derive(Debug, Clone, Copy, Eq, PartialEq, Default)]
775#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
776pub enum NetworkMode {
777 Work,
779 #[default]
781 Stake,
782}
783
784impl NetworkMode {
787 pub const fn is_stake(&self) -> bool {
789 matches!(self, Self::Stake)
790 }
791}
792
793#[cfg(test)]
794mod tests {
795 use super::*;
796 use alloy_eips::eip2124::ForkHash;
797 use alloy_genesis::Genesis;
798 use alloy_primitives::U256;
799 use reth_chainspec::{
800 Chain, ChainSpecBuilder, EthereumHardfork, ForkCondition, ForkId, MAINNET,
801 };
802 use reth_discv5::build_local_enr;
803 use reth_dns_discovery::tree::LinkEntry;
804 use reth_storage_api::noop::NoopProvider;
805 use std::{net::Ipv4Addr, sync::Arc};
806
807 fn builder() -> NetworkConfigBuilder {
808 let secret_key = SecretKey::new(&mut rand_08::thread_rng());
809 NetworkConfigBuilder::new(secret_key, Runtime::test())
810 }
811
812 #[test]
813 fn test_snap_advertisement_default_off() {
814 let config = builder().build(NoopProvider::default());
816 assert!(config.hello_message.protocols.iter().all(|p| p.cap.name != "snap"));
817 }
818
819 #[test]
820 fn test_snap_advertisement_when_enabled() {
821 let config = builder().with_snap(true).build(NoopProvider::default());
822 let snap_caps =
823 config.hello_message.protocols.iter().filter(|p| p.cap.name == "snap").count();
824 assert_eq!(snap_caps, 1);
825 assert_eq!(
826 config
827 .hello_message
828 .protocols
829 .iter()
830 .find(|p| p.cap.name == "snap")
831 .unwrap()
832 .cap
833 .version,
834 2
835 );
836 assert!(config.hello_message.protocols.iter().any(|p| p.cap.name == "eth"));
838 }
839
840 #[test]
841 fn test_snap_enr_entry_lists_served_versions() {
842 let config = builder()
843 .with_snap(true)
844 .discovery_v5(reth_discv5::Config::builder((Ipv4Addr::LOCALHOST, 30303).into()))
845 .build(NoopProvider::default());
846
847 let discv4 = config.discovery_v4_config.expect("should build discv4 config");
848 assert_eq!(
849 discv4.additional_eip868_rlp_pairs.get(b"snap".as_slice()).map(|rlp| rlp.as_ref()),
850 Some([0xc1, 0x02].as_slice())
851 );
852
853 let (local_enr, _, _, _) = build_local_enr(
854 &config.secret_key,
855 &config.discovery_v5_config.expect("should build discv5 config"),
856 );
857 assert_eq!(
858 local_enr.get_decodable::<Vec<u64>>("snap").expect("should read 'snap'").unwrap(),
859 vec![2]
860 );
861 }
862
863 #[test]
864 fn test_snap_enr_entry_absent_by_default() {
865 let config = builder()
866 .discovery_v5(reth_discv5::Config::builder((Ipv4Addr::LOCALHOST, 30303).into()))
867 .build(NoopProvider::default());
868
869 let discv4 = config.discovery_v4_config.expect("should build discv4 config");
870 assert!(!discv4.additional_eip868_rlp_pairs.contains_key(b"snap".as_slice()));
871
872 let (local_enr, _, _, _) = build_local_enr(
873 &config.secret_key,
874 &config.discovery_v5_config.expect("should build discv5 config"),
875 );
876 assert!(local_enr.get_raw_rlp("snap").is_none());
877 }
878
879 #[test]
880 fn test_network_dns_defaults() {
881 let config = builder().build(NoopProvider::default());
882
883 let dns = config.dns_discovery_config.unwrap();
884 let bootstrap_nodes = dns.bootstrap_dns_networks.unwrap();
885 let mainnet_dns: LinkEntry =
886 Chain::mainnet().public_dns_network_protocol().unwrap().parse().unwrap();
887 assert!(bootstrap_nodes.contains(&mainnet_dns));
888 assert_eq!(bootstrap_nodes.len(), 1);
889 }
890
891 #[test]
892 fn test_network_fork_filter_default() {
893 let mut chain_spec = Arc::clone(&MAINNET);
894
895 Arc::make_mut(&mut chain_spec).hardforks = Default::default();
897
898 let genesis_fork_hash = ForkHash::from(chain_spec.genesis_hash());
900
901 let config = builder().build_with_noop_provider(chain_spec);
903
904 let status = config.status;
905 let fork_filter = config.fork_filter;
906
907 assert_eq!(status.forkid.next, 0);
909
910 assert_eq!(fork_filter.current().next, 0);
912
913 assert_eq!(status.forkid.hash, genesis_fork_hash);
915 assert_eq!(fork_filter.current().hash, genesis_fork_hash);
916 }
917
918 #[test]
919 fn test_discv5_fork_id_default() {
920 const GENESIS_TIME: u64 = 151_515;
921
922 let genesis = Genesis::default().with_timestamp(GENESIS_TIME);
923
924 let active_fork = (EthereumHardfork::Shanghai, ForkCondition::Timestamp(GENESIS_TIME));
925 let future_fork = (EthereumHardfork::Cancun, ForkCondition::Timestamp(GENESIS_TIME + 1));
926
927 let chain_spec = ChainSpecBuilder::default()
928 .chain(Chain::dev())
929 .genesis(genesis)
930 .with_fork(active_fork.0, active_fork.1)
931 .with_fork(future_fork.0, future_fork.1)
932 .build();
933
934 let genesis_fork_hash = ForkHash::from(chain_spec.genesis_hash());
936 let fork_id = ForkId { hash: genesis_fork_hash, next: GENESIS_TIME + 1 };
937 assert_eq!(
939 fork_id,
940 chain_spec.fork_id(&Head {
941 hash: chain_spec.genesis_hash(),
942 number: 0,
943 timestamp: GENESIS_TIME,
944 difficulty: U256::ZERO,
945 total_difficulty: U256::ZERO,
946 })
947 );
948 assert_ne!(fork_id, chain_spec.latest_fork_id());
949
950 let fork_key = b"odyssey";
952 let config = builder()
953 .discovery_v5(
954 reth_discv5::Config::builder((Ipv4Addr::LOCALHOST, 30303).into())
955 .fork(fork_key, fork_id),
956 )
957 .build_with_noop_provider(Arc::new(chain_spec));
958
959 let (local_enr, _, _, _) = build_local_enr(
960 &config.secret_key,
961 &config.discovery_v5_config.expect("should build config"),
962 );
963
964 let advertised_fork_id = *local_enr
967 .get_decodable::<Vec<ForkId>>(fork_key)
968 .expect("should read 'odyssey'")
969 .expect("should decode fork id list")
970 .first()
971 .expect("should be non-empty");
972
973 assert_eq!(advertised_fork_id, fork_id);
974 }
975
976 #[test]
977 fn test_discv5_fork_id_custom_chain_id_fallback() {
978 const GENESIS_TIME: u64 = 151_515;
979
980 let genesis = Genesis::default().with_timestamp(GENESIS_TIME);
981
982 let chain_spec = ChainSpecBuilder::default()
983 .chain(Chain::from_id(3151908))
984 .genesis(genesis)
985 .with_fork(EthereumHardfork::Shanghai, ForkCondition::Timestamp(GENESIS_TIME))
986 .build();
987
988 let fork_id = chain_spec.fork_id(&Head {
989 hash: chain_spec.genesis_hash(),
990 number: 0,
991 timestamp: GENESIS_TIME,
992 difficulty: U256::ZERO,
993 total_difficulty: U256::ZERO,
994 });
995
996 let config = builder()
997 .discovery_v5(reth_discv5::Config::builder((Ipv4Addr::LOCALHOST, 30303).into()))
998 .build_with_noop_provider(Arc::new(chain_spec));
999
1000 let (local_enr, _, _, _) = build_local_enr(
1001 &config.secret_key,
1002 &config.discovery_v5_config.expect("should build config"),
1003 );
1004
1005 let advertised_fork_id = *local_enr
1006 .get_decodable::<Vec<ForkId>>(NetworkStackId::ETH)
1007 .expect("should read 'eth'")
1008 .expect("should decode fork id list")
1009 .first()
1010 .expect("should be non-empty");
1011
1012 assert_eq!(advertised_fork_id, fork_id);
1013 }
1014}