1use crate::{
4 error::NetworkError,
5 import::{BlockImport, ProofOfStakeBlockImport},
6 transactions::TransactionsManagerConfig,
7 NetworkHandle, NetworkManager,
8};
9use alloy_eips::BlockNumHash;
10use futures::FutureExt;
11use reth_chainspec::{ChainSpecProvider, EthChainSpec, Hardforks};
12use reth_discv4::{Discv4Config, Discv4ConfigBuilder, NatResolver, DEFAULT_DISCOVERY_ADDRESS};
13use reth_discv5::NetworkStackId;
14use reth_dns_discovery::DnsDiscoveryConfig;
15use reth_eth_wire::{
16 handshake::{EthHandshake, EthRlpxHandshake},
17 EthNetworkPrimitives, HelloMessage, HelloMessageWithProtocols, NetworkPrimitives,
18 UnifiedStatus, MAX_PAYLOAD_SIZE,
19};
20use reth_eth_wire_types::{message::MAX_MESSAGE_SIZE, SnapVersion};
21use reth_ethereum_forks::{ForkFilter, Head};
22use reth_network_peers::{mainnet_nodes, pk2id, sepolia_nodes, PeerId, TrustedPeer};
23use reth_network_types::{PeersConfig, SessionsConfig};
24use reth_storage_api::{
25 noop::NoopProvider, BalProvider, BlockNumReader, BlockReader, StateProviderFactory,
26 StateRangeProviderFactory,
27};
28use reth_tasks::Runtime;
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::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 p2p_max_payload_size: usize,
109 pub required_block_hashes: Vec<BlockNumHash>,
112}
113
114impl<N: NetworkPrimitives> NetworkConfig<(), N> {
117 pub fn builder(secret_key: SecretKey, executor: Runtime) -> NetworkConfigBuilder<N> {
119 NetworkConfigBuilder::new(secret_key, executor)
120 }
121
122 pub fn builder_with_rng_secret_key(executor: Runtime) -> NetworkConfigBuilder<N> {
124 NetworkConfigBuilder::with_rng_secret_key(executor)
125 }
126}
127
128impl<C, N: NetworkPrimitives> NetworkConfig<C, N> {
129 pub fn apply<F>(self, f: F) -> Self
131 where
132 F: FnOnce(Self) -> Self,
133 {
134 f(self)
135 }
136
137 pub fn set_discovery_v4(mut self, discovery_config: Discv4Config) -> Self {
139 self.discovery_v4_config = Some(discovery_config);
140 self
141 }
142
143 pub const fn set_listener_addr(mut self, listener_addr: SocketAddr) -> Self {
145 self.listener_addr = listener_addr;
146 self
147 }
148
149 pub const fn listener_addr(&self) -> &SocketAddr {
151 &self.listener_addr
152 }
153}
154
155impl<C, N> NetworkConfig<C, N>
156where
157 C: BlockNumReader + 'static,
158 N: NetworkPrimitives,
159{
160 pub async fn manager(self) -> Result<NetworkManager<N>, NetworkError> {
162 NetworkManager::new(self).await
163 }
164}
165
166impl<C, N> NetworkConfig<C, N>
167where
168 N: NetworkPrimitives,
169 C: BalProvider
170 + StateProviderFactory
171 + StateRangeProviderFactory
172 + BlockReader<Block = N::Block, Receipt = N::Receipt, Header = N::BlockHeader>
173 + Clone
174 + Unpin
175 + 'static,
176{
177 pub async fn start_network(self) -> Result<NetworkHandle<N>, NetworkError> {
179 let client = self.client.clone();
180 let (handle, network, _txpool, eth) = NetworkManager::builder::<C>(self)
181 .await?
182 .request_handler::<C>(client)
183 .split_with_handle();
184
185 tokio::task::spawn(network.boxed());
186 tokio::task::spawn(eth.boxed());
187 Ok(handle)
188 }
189}
190
191#[derive(Debug)]
193pub struct NetworkConfigBuilder<N: NetworkPrimitives = EthNetworkPrimitives> {
194 secret_key: SecretKey,
196 dns_discovery_config: Option<DnsDiscoveryConfig>,
198 discovery_v4_builder: Option<Discv4ConfigBuilder>,
200 discovery_v5_builder: Option<reth_discv5::ConfigBuilder>,
202 boot_nodes: HashSet<TrustedPeer>,
204 discovery_addr: Option<SocketAddr>,
206 listener_addr: Option<SocketAddr>,
208 peers_config: Option<PeersConfig>,
210 sessions_config: Option<SessionsConfig>,
212 network_mode: NetworkMode,
214 executor: Runtime,
216 hello_message: Option<HelloMessageWithProtocols>,
218 extra_protocols: RlpxSubProtocols,
221 head: Option<Head>,
223 tx_gossip_disabled: bool,
225 block_import: Option<Box<dyn BlockImport<N::NewBlockPayload>>>,
227 transactions_manager_config: TransactionsManagerConfig,
229 nat: Option<NatResolver>,
231 handshake: Arc<dyn EthRlpxHandshake>,
234 eth_max_message_size: usize,
236 p2p_max_payload_size: usize,
238 required_block_hashes: Vec<BlockNumHash>,
240 network_id: Option<u64>,
242 snap_enabled: bool,
244}
245
246impl NetworkConfigBuilder<EthNetworkPrimitives> {
247 pub fn eth(secret_key: SecretKey, executor: Runtime) -> Self {
249 Self::new(secret_key, executor)
250 }
251}
252
253#[expect(missing_docs)]
256impl<N: NetworkPrimitives> NetworkConfigBuilder<N> {
257 pub fn with_rng_secret_key(executor: Runtime) -> Self {
259 Self::new(rng_secret_key(), executor)
260 }
261
262 pub fn new(secret_key: SecretKey, executor: Runtime) -> Self {
264 Self {
265 secret_key,
266 dns_discovery_config: Some(Default::default()),
267 discovery_v4_builder: Some(Default::default()),
268 discovery_v5_builder: None,
269 boot_nodes: Default::default(),
270 discovery_addr: None,
271 listener_addr: None,
272 peers_config: None,
273 sessions_config: None,
274 network_mode: Default::default(),
275 executor,
276 hello_message: None,
277 extra_protocols: Default::default(),
278 head: None,
279 tx_gossip_disabled: false,
280 block_import: None,
281 transactions_manager_config: Default::default(),
282 nat: None,
283 handshake: Arc::new(EthHandshake::default()),
284 eth_max_message_size: MAX_MESSAGE_SIZE,
285 p2p_max_payload_size: MAX_PAYLOAD_SIZE,
286 required_block_hashes: Vec::new(),
287 network_id: None,
288 snap_enabled: false,
289 }
290 }
291
292 pub fn apply<F>(self, f: F) -> Self
294 where
295 F: FnOnce(Self) -> Self,
296 {
297 f(self)
298 }
299
300 pub fn get_peer_id(&self) -> PeerId {
302 pk2id(&self.secret_key.public_key())
303 }
304
305 pub const fn secret_key(&self) -> &SecretKey {
307 &self.secret_key
308 }
309
310 pub const fn network_mode(mut self, network_mode: NetworkMode) -> Self {
312 self.network_mode = network_mode;
313 self
314 }
315
316 pub const fn with_pow(self) -> Self {
324 self.network_mode(NetworkMode::Work)
325 }
326
327 pub const fn set_head(mut self, head: Head) -> Self {
333 self.head = Some(head);
334 self
335 }
336
337 pub fn hello_message(mut self, hello_message: HelloMessageWithProtocols) -> Self {
348 self.hello_message = Some(hello_message);
349 self
350 }
351
352 pub fn peer_config(mut self, config: PeersConfig) -> Self {
354 self.peers_config = Some(config);
355 self
356 }
357
358 pub fn with_task_executor(mut self, executor: Runtime) -> Self {
360 self.executor = executor;
361 self
362 }
363
364 pub const fn sessions_config(mut self, config: SessionsConfig) -> Self {
366 self.sessions_config = Some(config);
367 self
368 }
369
370 pub const fn transactions_manager_config(mut self, config: TransactionsManagerConfig) -> Self {
372 self.transactions_manager_config = config;
373 self
374 }
375
376 pub const fn transaction_propagation_mode(mut self, mode: TransactionPropagationMode) -> Self {
378 self.transactions_manager_config.propagation_mode = mode;
379 self
380 }
381
382 pub const fn set_addrs(self, addr: SocketAddr) -> Self {
390 self.listener_addr(addr).discovery_addr(addr)
391 }
392
393 pub const fn listener_addr(mut self, listener_addr: SocketAddr) -> Self {
397 self.listener_addr = Some(listener_addr);
398 self
399 }
400
401 pub fn listener_port(mut self, port: u16) -> Self {
405 self.listener_addr.get_or_insert(DEFAULT_DISCOVERY_ADDRESS).set_port(port);
406 self
407 }
408
409 pub const fn discovery_addr(mut self, discovery_addr: SocketAddr) -> Self {
411 self.discovery_addr = Some(discovery_addr);
412 self
413 }
414
415 pub fn discovery_port(mut self, port: u16) -> Self {
419 self.discovery_addr.get_or_insert(DEFAULT_DISCOVERY_ADDRESS).set_port(port);
420 self
421 }
422
423 pub fn with_unused_ports(self) -> Self {
426 self.with_unused_discovery_port().with_unused_listener_port()
427 }
428
429 pub fn with_unused_discovery_port(self) -> Self {
432 self.discovery_port(0)
433 }
434
435 pub fn with_unused_listener_port(self) -> Self {
438 self.listener_port(0)
439 }
440
441 pub fn external_ip_resolver(mut self, resolver: NatResolver) -> Self {
448 self.discovery_v4_builder
449 .get_or_insert_with(Discv4Config::builder)
450 .external_ip_resolver(Some(resolver.clone()));
451 self.nat = Some(resolver);
452 self
453 }
454
455 pub fn discovery(mut self, builder: Discv4ConfigBuilder) -> Self {
457 self.discovery_v4_builder = Some(builder);
458 self
459 }
460
461 pub fn discovery_v5(mut self, builder: reth_discv5::ConfigBuilder) -> Self {
463 self.discovery_v5_builder = Some(builder);
464 self
465 }
466
467 pub fn dns_discovery(mut self, config: DnsDiscoveryConfig) -> Self {
469 self.dns_discovery_config = Some(config);
470 self
471 }
472
473 pub fn mainnet_boot_nodes(self) -> Self {
475 self.boot_nodes(mainnet_nodes())
476 }
477
478 pub fn sepolia_boot_nodes(self) -> Self {
480 self.boot_nodes(sepolia_nodes())
481 }
482
483 pub fn boot_nodes<T: Into<TrustedPeer>>(mut self, nodes: impl IntoIterator<Item = T>) -> Self {
485 self.boot_nodes = nodes.into_iter().map(Into::into).collect();
486 self
487 }
488
489 pub fn boot_nodes_iter(&self) -> impl Iterator<Item = &TrustedPeer> + '_ {
491 self.boot_nodes.iter()
492 }
493
494 pub fn disable_dns_discovery(mut self) -> Self {
496 self.dns_discovery_config = None;
497 self
498 }
499
500 pub fn disable_nat(mut self) -> Self {
502 self.nat = None;
503 self
504 }
505
506 pub fn disable_discovery(self) -> Self {
508 self.disable_discv4_discovery().disable_discv5_discovery().disable_dns_discovery()
509 }
510
511 pub fn disable_discovery_if(self, disable: bool) -> Self {
513 if disable {
514 self.disable_discovery()
515 } else {
516 self
517 }
518 }
519
520 pub fn disable_discv4_discovery(mut self) -> Self {
522 self.discovery_v4_builder = None;
523 self
524 }
525
526 pub fn disable_discv5_discovery(mut self) -> Self {
528 self.discovery_v5_builder = None;
529 self
530 }
531
532 pub fn disable_dns_discovery_if(self, disable: bool) -> Self {
534 if disable {
535 self.disable_dns_discovery()
536 } else {
537 self
538 }
539 }
540
541 pub fn disable_discv4_discovery_if(self, disable: bool) -> Self {
543 if disable {
544 self.disable_discv4_discovery()
545 } else {
546 self
547 }
548 }
549
550 pub fn disable_discv5_discovery_if(self, disable: bool) -> Self {
552 if disable {
553 self.disable_discv5_discovery()
554 } else {
555 self
556 }
557 }
558
559 pub fn add_rlpx_sub_protocol(mut self, protocol: impl IntoRlpxSubProtocol) -> Self {
563 self.extra_protocols.push(protocol);
564 self
565 }
566
567 pub const fn with_snap(mut self, snap_enabled: bool) -> Self {
571 self.snap_enabled = snap_enabled;
572 self
573 }
574
575 pub const fn disable_tx_gossip(mut self, disable_tx_gossip: bool) -> Self {
577 self.tx_gossip_disabled = disable_tx_gossip;
578 self
579 }
580
581 pub fn required_block_hashes(mut self, hashes: Vec<BlockNumHash>) -> Self {
583 self.required_block_hashes = hashes;
584 self
585 }
586
587 pub fn block_import(mut self, block_import: Box<dyn BlockImport<N::NewBlockPayload>>) -> Self {
589 self.block_import = Some(block_import);
590 self
591 }
592
593 pub fn build_with_noop_provider<ChainSpec>(
596 self,
597 chain_spec: Arc<ChainSpec>,
598 ) -> NetworkConfig<NoopProvider<ChainSpec>, N>
599 where
600 ChainSpec: EthChainSpec + Hardforks + 'static,
601 {
602 self.build(NoopProvider::eth(chain_spec))
603 }
604
605 pub fn add_nat(mut self, nat: Option<NatResolver>) -> Self {
607 self.nat = nat;
608 self
609 }
610
611 pub fn eth_rlpx_handshake(mut self, handshake: Arc<dyn EthRlpxHandshake>) -> Self {
613 self.handshake = handshake;
614 self
615 }
616
617 pub const fn eth_max_message_size(mut self, max_message_size: usize) -> Self {
626 self.eth_max_message_size = max_message_size;
627 self
628 }
629
630 pub const fn eth_max_message_size_opt(mut self, max_message_size: Option<usize>) -> Self {
632 if let Some(max_message_size) = max_message_size {
633 self.eth_max_message_size = max_message_size;
634 }
635 self
636 }
637
638 pub const fn p2p_max_payload_size(mut self, max_payload_size: usize) -> Self {
646 self.p2p_max_payload_size = max_payload_size;
647 self
648 }
649
650 pub const fn network_id(mut self, network_id: Option<u64>) -> Self {
652 self.network_id = network_id;
653 self
654 }
655
656 pub fn build<C>(self, client: C) -> NetworkConfig<C, N>
663 where
664 C: ChainSpecProvider<ChainSpec: Hardforks>,
665 {
666 let peer_id = self.get_peer_id();
667 let chain_spec = client.chain_spec();
668 let Self {
669 secret_key,
670 mut dns_discovery_config,
671 mut discovery_v4_builder,
672 mut discovery_v5_builder,
673 boot_nodes,
674 discovery_addr,
675 listener_addr,
676 peers_config,
677 sessions_config,
678 network_mode,
679 executor,
680 hello_message,
681 extra_protocols,
682 head,
683 tx_gossip_disabled,
684 block_import,
685 transactions_manager_config,
686 nat,
687 handshake,
688 eth_max_message_size,
689 p2p_max_payload_size,
690 required_block_hashes,
691 network_id,
692 snap_enabled,
693 } = self;
694
695 let head = head.unwrap_or_else(|| Head {
696 hash: chain_spec.genesis_hash(),
697 number: 0,
698 timestamp: chain_spec.genesis().timestamp,
699 difficulty: chain_spec.genesis().difficulty,
700 total_difficulty: chain_spec.genesis().difficulty,
701 });
702
703 let listener_addr = listener_addr.unwrap_or(DEFAULT_DISCOVERY_ADDRESS);
704 let advertised_ip = nat.clone().and_then(|nat| nat.as_external_ip(listener_addr.port()));
708
709 let snap_versions = snap_enabled.then(|| vec![SnapVersion::V2 as u64]);
711 if let (Some(builder), Some(versions)) = (discovery_v4_builder.as_mut(), &snap_versions) {
712 builder.add_eip868_pair("snap", versions);
713 }
714
715 discovery_v5_builder = discovery_v5_builder.map(|mut builder| {
716 let fork_id = chain_spec.fork_id(&head);
717 if let Some(network_stack_id) = NetworkStackId::id(&chain_spec) {
718 builder = builder.fork(network_stack_id, fork_id)
719 } else {
720 builder = builder.fork_if_unset(NetworkStackId::ETH, fork_id)
723 }
724
725 if let Some(ip) = advertised_ip {
726 builder = builder.advertised_ip(ip);
727 }
728
729 if let Some(versions) = &snap_versions {
730 builder = builder.add_enr_kv_pair(b"snap", alloy_rlp::encode(versions).into());
731 }
732
733 builder
734 });
735
736 let mut hello_message =
737 hello_message.unwrap_or_else(|| HelloMessage::builder(peer_id).build());
738 hello_message.port = listener_addr.port();
739 hello_message = hello_message.with_snap(snap_enabled);
740
741 let mut status = UnifiedStatus::spec_builder(&chain_spec, &head);
743
744 if let Some(id) = network_id {
745 status.chain = id.into();
746 }
747
748 let fork_filter = chain_spec.fork_filter(head);
750
751 let chain_id = chain_spec.chain().id();
753
754 if let Some(dns_networks) =
756 dns_discovery_config.as_mut().and_then(|c| c.bootstrap_dns_networks.as_mut()) &&
757 dns_networks.is_empty() &&
758 let Some(link) = chain_spec.chain().public_dns_network_protocol()
759 {
760 dns_networks.insert(link.parse().expect("is valid DNS link entry"));
761 }
762
763 NetworkConfig {
764 client,
765 secret_key,
766 boot_nodes,
767 dns_discovery_config,
768 discovery_v4_config: discovery_v4_builder.map(|builder| builder.build()),
769 discovery_v5_config: discovery_v5_builder.map(|builder| builder.build()),
770 discovery_v4_addr: discovery_addr.unwrap_or(DEFAULT_DISCOVERY_ADDRESS),
771 listener_addr,
772 peers_config: peers_config.unwrap_or_default(),
773 sessions_config: sessions_config.unwrap_or_default(),
774 chain_id,
775 block_import: block_import.unwrap_or_else(|| Box::<ProofOfStakeBlockImport>::default()),
776 network_mode,
777 executor,
778 status,
779 hello_message,
780 extra_protocols,
781 fork_filter,
782 tx_gossip_disabled,
783 transactions_manager_config,
784 nat,
785 handshake,
786 eth_max_message_size,
787 p2p_max_payload_size,
788 required_block_hashes,
789 }
790 }
791}
792
793#[derive(Debug, Clone, Copy, Eq, PartialEq, Default)]
799#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
800pub enum NetworkMode {
801 Work,
803 #[default]
805 Stake,
806}
807
808impl NetworkMode {
811 pub const fn is_stake(&self) -> bool {
813 matches!(self, Self::Stake)
814 }
815}
816
817#[cfg(test)]
818mod tests {
819 use super::*;
820 use alloy_eips::eip2124::ForkHash;
821 use alloy_genesis::Genesis;
822 use alloy_primitives::U256;
823 use reth_chainspec::{
824 Chain, ChainSpecBuilder, EthereumHardfork, ForkCondition, ForkId, MAINNET,
825 };
826 use reth_discv5::build_local_enr;
827 use reth_dns_discovery::tree::LinkEntry;
828 use reth_storage_api::noop::NoopProvider;
829 use std::{net::Ipv4Addr, sync::Arc};
830
831 fn builder() -> NetworkConfigBuilder {
832 let secret_key = SecretKey::new(&mut rand::rng());
833 NetworkConfigBuilder::new(secret_key, Runtime::test())
834 }
835
836 #[test]
837 fn test_snap_advertisement_default_off() {
838 let config = builder().build(NoopProvider::default());
840 assert!(config.hello_message.protocols.iter().all(|p| p.cap.name != "snap"));
841 }
842
843 #[test]
844 fn test_snap_advertisement_when_enabled() {
845 let config = builder().with_snap(true).build(NoopProvider::default());
846 let snap_caps =
847 config.hello_message.protocols.iter().filter(|p| p.cap.name == "snap").count();
848 assert_eq!(snap_caps, 1);
849 assert_eq!(
850 config
851 .hello_message
852 .protocols
853 .iter()
854 .find(|p| p.cap.name == "snap")
855 .unwrap()
856 .cap
857 .version,
858 2
859 );
860 assert!(config.hello_message.protocols.iter().any(|p| p.cap.name == "eth"));
862 }
863
864 #[test]
865 fn test_snap_enr_entry_lists_served_versions() {
866 let config = builder()
867 .with_snap(true)
868 .discovery_v5(reth_discv5::Config::builder((Ipv4Addr::LOCALHOST, 30303).into()))
869 .build(NoopProvider::default());
870
871 let discv4 = config.discovery_v4_config.expect("should build discv4 config");
872 assert_eq!(
873 discv4.additional_eip868_rlp_pairs.get(b"snap".as_slice()).map(|rlp| rlp.as_ref()),
874 Some([0xc1, 0x02].as_slice())
875 );
876
877 let (local_enr, _, _, _) = build_local_enr(
878 &config.secret_key,
879 &config.discovery_v5_config.expect("should build discv5 config"),
880 );
881 assert_eq!(
882 local_enr.get_decodable::<Vec<u64>>("snap").expect("should read 'snap'").unwrap(),
883 vec![2]
884 );
885 }
886
887 #[test]
888 fn test_snap_enr_entry_absent_by_default() {
889 let config = builder()
890 .discovery_v5(reth_discv5::Config::builder((Ipv4Addr::LOCALHOST, 30303).into()))
891 .build(NoopProvider::default());
892
893 let discv4 = config.discovery_v4_config.expect("should build discv4 config");
894 assert!(!discv4.additional_eip868_rlp_pairs.contains_key(b"snap".as_slice()));
895
896 let (local_enr, _, _, _) = build_local_enr(
897 &config.secret_key,
898 &config.discovery_v5_config.expect("should build discv5 config"),
899 );
900 assert!(local_enr.get_raw_rlp("snap").is_none());
901 }
902
903 #[test]
904 fn test_network_dns_defaults() {
905 let config = builder().build(NoopProvider::default());
906
907 let dns = config.dns_discovery_config.unwrap();
908 let bootstrap_nodes = dns.bootstrap_dns_networks.unwrap();
909 let mainnet_dns: LinkEntry =
910 Chain::mainnet().public_dns_network_protocol().unwrap().parse().unwrap();
911 assert!(bootstrap_nodes.contains(&mainnet_dns));
912 assert_eq!(bootstrap_nodes.len(), 1);
913 }
914
915 #[test]
916 fn test_network_fork_filter_default() {
917 let mut chain_spec = Arc::clone(&MAINNET);
918
919 Arc::make_mut(&mut chain_spec).hardforks = Default::default();
921
922 let genesis_fork_hash = ForkHash::from(chain_spec.genesis_hash());
924
925 let config = builder().build_with_noop_provider(chain_spec);
927
928 let status = config.status;
929 let fork_filter = config.fork_filter;
930
931 assert_eq!(status.forkid.next, 0);
933
934 assert_eq!(fork_filter.current().next, 0);
936
937 assert_eq!(status.forkid.hash, genesis_fork_hash);
939 assert_eq!(fork_filter.current().hash, genesis_fork_hash);
940 }
941
942 #[test]
943 fn test_discv5_fork_id_default() {
944 const GENESIS_TIME: u64 = 151_515;
945
946 let genesis = Genesis::default().with_timestamp(GENESIS_TIME);
947
948 let active_fork = (EthereumHardfork::Shanghai, ForkCondition::Timestamp(GENESIS_TIME));
949 let future_fork = (EthereumHardfork::Cancun, ForkCondition::Timestamp(GENESIS_TIME + 1));
950
951 let chain_spec = ChainSpecBuilder::default()
952 .chain(Chain::dev())
953 .genesis(genesis)
954 .with_fork(active_fork.0, active_fork.1)
955 .with_fork(future_fork.0, future_fork.1)
956 .build();
957
958 let genesis_fork_hash = ForkHash::from(chain_spec.genesis_hash());
960 let fork_id = ForkId { hash: genesis_fork_hash, next: GENESIS_TIME + 1 };
961 assert_eq!(
963 fork_id,
964 chain_spec.fork_id(&Head {
965 hash: chain_spec.genesis_hash(),
966 number: 0,
967 timestamp: GENESIS_TIME,
968 difficulty: U256::ZERO,
969 total_difficulty: U256::ZERO,
970 })
971 );
972 assert_ne!(fork_id, chain_spec.latest_fork_id());
973
974 let fork_key = b"odyssey";
976 let config = builder()
977 .discovery_v5(
978 reth_discv5::Config::builder((Ipv4Addr::LOCALHOST, 30303).into())
979 .fork(fork_key, fork_id),
980 )
981 .build_with_noop_provider(Arc::new(chain_spec));
982
983 let (local_enr, _, _, _) = build_local_enr(
984 &config.secret_key,
985 &config.discovery_v5_config.expect("should build config"),
986 );
987
988 let advertised_fork_id = *local_enr
991 .get_decodable::<Vec<ForkId>>(fork_key)
992 .expect("should read 'odyssey'")
993 .expect("should decode fork id list")
994 .first()
995 .expect("should be non-empty");
996
997 assert_eq!(advertised_fork_id, fork_id);
998 }
999
1000 #[test]
1001 fn test_discv5_fork_id_custom_chain_id_fallback() {
1002 const GENESIS_TIME: u64 = 151_515;
1003
1004 let genesis = Genesis::default().with_timestamp(GENESIS_TIME);
1005
1006 let chain_spec = ChainSpecBuilder::default()
1007 .chain(Chain::from_id(3151908))
1008 .genesis(genesis)
1009 .with_fork(EthereumHardfork::Shanghai, ForkCondition::Timestamp(GENESIS_TIME))
1010 .build();
1011
1012 let fork_id = chain_spec.fork_id(&Head {
1013 hash: chain_spec.genesis_hash(),
1014 number: 0,
1015 timestamp: GENESIS_TIME,
1016 difficulty: U256::ZERO,
1017 total_difficulty: U256::ZERO,
1018 });
1019
1020 let config = builder()
1021 .discovery_v5(reth_discv5::Config::builder((Ipv4Addr::LOCALHOST, 30303).into()))
1022 .build_with_noop_provider(Arc::new(chain_spec));
1023
1024 let (local_enr, _, _, _) = build_local_enr(
1025 &config.secret_key,
1026 &config.discovery_v5_config.expect("should build config"),
1027 );
1028
1029 let advertised_fork_id = *local_enr
1030 .get_decodable::<Vec<ForkId>>(NetworkStackId::ETH)
1031 .expect("should read 'eth'")
1032 .expect("should decode fork id list")
1033 .first()
1034 .expect("should be non-empty");
1035
1036 assert_eq!(advertised_fork_id, fork_id);
1037 }
1038}