1pub use alloy_eips::eip1559::BaseFeeParams;
2
3use crate::{
4 constants::{MAINNET_DEPOSIT_CONTRACT, MAINNET_PRUNE_DELETE_LIMIT},
5 ethereum::SEPOLIA_PARIS_TTD,
6 holesky, hoodi, mainnet,
7 mainnet::{MAINNET_PARIS_BLOCK, MAINNET_PARIS_TTD},
8 sepolia,
9 sepolia::SEPOLIA_PARIS_BLOCK,
10 EthChainSpec,
11};
12use alloc::{
13 boxed::Box,
14 collections::BTreeMap,
15 format,
16 string::{String, ToString},
17 sync::Arc,
18 vec::Vec,
19};
20use alloy_chains::{Chain, NamedChain};
21use alloy_consensus::{
22 constants::{
23 EMPTY_WITHDRAWALS, HOLESKY_GENESIS_HASH, HOODI_GENESIS_HASH, MAINNET_GENESIS_HASH,
24 SEPOLIA_GENESIS_HASH,
25 },
26 Header,
27};
28use alloy_eips::{
29 eip1559::INITIAL_BASE_FEE, eip7685::EMPTY_REQUESTS_HASH, eip7840::BlobParams,
30 eip7892::BlobScheduleBlobParams, eip7928::EMPTY_BLOCK_ACCESS_LIST_HASH,
31};
32use alloy_genesis::{ChainConfig, Genesis};
33use alloy_primitives::{address, b256, Address, BlockNumber, B256, U256};
34use alloy_trie::root::state_root_ref_unhashed;
35use core::fmt::Debug;
36use derive_more::From;
37use reth_ethereum_forks::{
38 ChainHardforks, DisplayHardforks, EthereumHardfork, EthereumHardforks, ForkCondition,
39 ForkFilter, ForkFilterKey, ForkHash, ForkId, Hardfork, Hardforks, Head, DEV_HARDFORKS,
40};
41use reth_network_peers::{holesky_nodes, hoodi_nodes, mainnet_nodes, sepolia_nodes, NodeRecord};
42use reth_primitives_traits::{sync::LazyLock, BlockHeader, SealedHeader};
43use NamedChain as C;
44
45pub fn make_genesis_header(genesis: &Genesis, hardforks: &ChainHardforks) -> Header {
47 let base_fee_per_gas = hardforks
49 .fork(EthereumHardfork::London)
50 .active_at_block(0)
51 .then(|| genesis.base_fee_per_gas.map(|fee| fee as u64).unwrap_or(INITIAL_BASE_FEE));
52
53 let withdrawals_root = hardforks
56 .fork(EthereumHardfork::Shanghai)
57 .active_at_timestamp(genesis.timestamp)
58 .then_some(EMPTY_WITHDRAWALS);
59
60 let (parent_beacon_block_root, blob_gas_used, excess_blob_gas) =
65 if hardforks.fork(EthereumHardfork::Cancun).active_at_timestamp(genesis.timestamp) {
66 let blob_gas_used = genesis.blob_gas_used.unwrap_or(0);
67 let excess_blob_gas = genesis.excess_blob_gas.unwrap_or(0);
68 (Some(B256::ZERO), Some(blob_gas_used), Some(excess_blob_gas))
69 } else {
70 (None, None, None)
71 };
72
73 let requests_hash = hardforks
75 .fork(EthereumHardfork::Prague)
76 .active_at_timestamp(genesis.timestamp)
77 .then_some(EMPTY_REQUESTS_HASH);
78
79 let block_access_list_hash = hardforks
81 .fork(EthereumHardfork::Amsterdam)
82 .active_at_timestamp(genesis.timestamp)
83 .then_some(EMPTY_BLOCK_ACCESS_LIST_HASH);
84
85 let slot_number = hardforks
87 .fork(EthereumHardfork::Amsterdam)
88 .active_at_timestamp(genesis.timestamp)
89 .then_some(genesis.slot_number.unwrap_or(0));
90
91 Header {
92 number: genesis.number.unwrap_or_default(),
93 parent_hash: genesis.parent_hash.unwrap_or_default(),
94 gas_limit: genesis.gas_limit,
95 difficulty: genesis.difficulty,
96 nonce: genesis.nonce.into(),
97 extra_data: genesis.extra_data.clone(),
98 state_root: state_root_ref_unhashed(&genesis.alloc),
99 timestamp: genesis.timestamp,
100 mix_hash: genesis.mix_hash,
101 beneficiary: genesis.coinbase,
102 base_fee_per_gas,
103 withdrawals_root,
104 parent_beacon_block_root,
105 blob_gas_used,
106 excess_blob_gas,
107 requests_hash,
108 block_access_list_hash,
109 slot_number,
110 ..Default::default()
111 }
112}
113
114pub static MAINNET: LazyLock<Arc<ChainSpec>> = LazyLock::new(|| {
116 let genesis = serde_json::from_str(include_str!("../res/genesis/mainnet.json"))
117 .expect("Can't deserialize Mainnet genesis json");
118 let hardforks = EthereumHardfork::mainnet().into();
119 let mut spec = ChainSpec {
120 chain: Chain::mainnet(),
121 genesis_header: SealedHeader::new(
122 make_genesis_header(&genesis, &hardforks),
123 MAINNET_GENESIS_HASH,
124 ),
125 genesis,
126 paris_block_and_final_difficulty: Some((
128 MAINNET_PARIS_BLOCK,
129 U256::from(58_750_003_716_598_352_816_469u128),
130 )),
131 hardforks,
132 deposit_contract: Some(MAINNET_DEPOSIT_CONTRACT),
134 base_fee_params: BaseFeeParamsKind::Constant(BaseFeeParams::ethereum()),
135 prune_delete_limit: MAINNET_PRUNE_DELETE_LIMIT,
136 blob_params: BlobScheduleBlobParams::default().with_scheduled([
137 (mainnet::MAINNET_BPO1_TIMESTAMP, BlobParams::bpo1()),
138 (mainnet::MAINNET_BPO2_TIMESTAMP, BlobParams::bpo2()),
139 ]),
140 };
141 spec.genesis.config.dao_fork_support = true;
142 spec.into()
143});
144
145pub static SEPOLIA: LazyLock<Arc<ChainSpec>> = LazyLock::new(|| {
147 let genesis = serde_json::from_str(include_str!("../res/genesis/sepolia.json"))
148 .expect("Can't deserialize Sepolia genesis json");
149 let hardforks = EthereumHardfork::sepolia().into();
150 let mut spec = ChainSpec {
151 chain: Chain::sepolia(),
152 genesis_header: SealedHeader::new(
153 make_genesis_header(&genesis, &hardforks),
154 SEPOLIA_GENESIS_HASH,
155 ),
156 genesis,
157 paris_block_and_final_difficulty: Some((
159 SEPOLIA_PARIS_BLOCK,
160 U256::from(17_000_018_015_853_232u128),
161 )),
162 hardforks,
163 deposit_contract: Some(DepositContract::new(
165 address!("0x7f02c3e3c98b133055b8b348b2ac625669ed295d"),
166 1273020,
167 b256!("0x649bbc62d0e31342afea4e5cd82d4049e7e1ee912fc0889aa790803be39038c5"),
168 )),
169 base_fee_params: BaseFeeParamsKind::Constant(BaseFeeParams::ethereum()),
170 prune_delete_limit: 10000,
171 blob_params: BlobScheduleBlobParams::default().with_scheduled([
172 (sepolia::SEPOLIA_BPO1_TIMESTAMP, BlobParams::bpo1()),
173 (sepolia::SEPOLIA_BPO2_TIMESTAMP, BlobParams::bpo2()),
174 ]),
175 };
176 spec.genesis.config.dao_fork_support = true;
177 spec.into()
178});
179
180pub static HOLESKY: LazyLock<Arc<ChainSpec>> = LazyLock::new(|| {
182 let genesis = serde_json::from_str(include_str!("../res/genesis/holesky.json"))
183 .expect("Can't deserialize Holesky genesis json");
184 let hardforks = EthereumHardfork::holesky().into();
185 let mut spec = ChainSpec {
186 chain: Chain::holesky(),
187 genesis_header: SealedHeader::new(
188 make_genesis_header(&genesis, &hardforks),
189 HOLESKY_GENESIS_HASH,
190 ),
191 genesis,
192 paris_block_and_final_difficulty: Some((0, U256::ONE)),
193 hardforks,
194 deposit_contract: Some(DepositContract::new(
195 Address::repeat_byte(0x42),
196 0,
197 b256!("0x649bbc62d0e31342afea4e5cd82d4049e7e1ee912fc0889aa790803be39038c5"),
198 )),
199 base_fee_params: BaseFeeParamsKind::Constant(BaseFeeParams::ethereum()),
200 prune_delete_limit: 10000,
201 blob_params: BlobScheduleBlobParams::default().with_scheduled([
202 (holesky::HOLESKY_BPO1_TIMESTAMP, BlobParams::bpo1()),
203 (holesky::HOLESKY_BPO2_TIMESTAMP, BlobParams::bpo2()),
204 ]),
205 };
206 spec.genesis.config.dao_fork_support = true;
207 spec.into()
208});
209
210pub static HOODI: LazyLock<Arc<ChainSpec>> = LazyLock::new(|| {
214 let genesis = serde_json::from_str(include_str!("../res/genesis/hoodi.json"))
215 .expect("Can't deserialize Hoodi genesis json");
216 let hardforks = EthereumHardfork::hoodi().into();
217 let mut spec = ChainSpec {
218 chain: Chain::hoodi(),
219 genesis_header: SealedHeader::new(
220 make_genesis_header(&genesis, &hardforks),
221 HOODI_GENESIS_HASH,
222 ),
223 genesis,
224 paris_block_and_final_difficulty: Some((0, U256::ZERO)),
225 hardforks,
226 deposit_contract: Some(DepositContract::new(
227 address!("0x00000000219ab540356cBB839Cbe05303d7705Fa"),
228 0,
229 b256!("0x649bbc62d0e31342afea4e5cd82d4049e7e1ee912fc0889aa790803be39038c5"),
230 )),
231 base_fee_params: BaseFeeParamsKind::Constant(BaseFeeParams::ethereum()),
232 prune_delete_limit: 10000,
233 blob_params: BlobScheduleBlobParams::default().with_scheduled([
234 (hoodi::HOODI_BPO1_TIMESTAMP, BlobParams::bpo1()),
235 (hoodi::HOODI_BPO2_TIMESTAMP, BlobParams::bpo2()),
236 ]),
237 };
238 spec.genesis.config.dao_fork_support = true;
239 spec.into()
240});
241
242pub static DEV: LazyLock<Arc<ChainSpec>> = LazyLock::new(|| {
247 let genesis = serde_json::from_str(include_str!("../res/genesis/dev.json"))
248 .expect("Can't deserialize Dev testnet genesis json");
249 let hardforks = DEV_HARDFORKS.clone();
250 ChainSpec {
251 chain: Chain::dev(),
252 genesis_header: SealedHeader::seal_slow(make_genesis_header(&genesis, &hardforks)),
253 genesis,
254 paris_block_and_final_difficulty: Some((0, U256::ZERO)),
255 hardforks,
256 base_fee_params: BaseFeeParamsKind::Constant(BaseFeeParams::ethereum()),
257 deposit_contract: None, ..Default::default()
259 }
260 .into()
261});
262
263pub fn create_chain_config(
266 chain: Option<Chain>,
267 hardforks: &ChainHardforks,
268 deposit_contract_address: Option<Address>,
269 blob_schedule: BTreeMap<String, BlobParams>,
270) -> ChainConfig {
271 let block_num = |fork: EthereumHardfork| hardforks.fork(fork).block_number();
273
274 let timestamp = |fork: EthereumHardfork| hardforks.fork(fork).as_timestamp();
276
277 let (terminal_total_difficulty, terminal_total_difficulty_passed) =
279 match hardforks.fork(EthereumHardfork::Paris) {
280 ForkCondition::TTD { total_difficulty, .. } => (Some(total_difficulty), true),
281 _ => (None, false),
282 };
283
284 let dao_fork_support = hardforks.fork(EthereumHardfork::Dao) != ForkCondition::Never;
286
287 ChainConfig {
288 chain_id: chain.map(|c| c.id()).unwrap_or(0),
289 homestead_block: block_num(EthereumHardfork::Homestead),
290 dao_fork_block: block_num(EthereumHardfork::Dao),
291 dao_fork_support,
292 eip150_block: block_num(EthereumHardfork::Tangerine),
293 eip155_block: block_num(EthereumHardfork::SpuriousDragon),
294 eip158_block: block_num(EthereumHardfork::SpuriousDragon),
295 byzantium_block: block_num(EthereumHardfork::Byzantium),
296 constantinople_block: block_num(EthereumHardfork::Constantinople),
297 petersburg_block: block_num(EthereumHardfork::Petersburg),
298 istanbul_block: block_num(EthereumHardfork::Istanbul),
299 muir_glacier_block: block_num(EthereumHardfork::MuirGlacier),
300 berlin_block: block_num(EthereumHardfork::Berlin),
301 london_block: block_num(EthereumHardfork::London),
302 arrow_glacier_block: block_num(EthereumHardfork::ArrowGlacier),
303 gray_glacier_block: block_num(EthereumHardfork::GrayGlacier),
304 merge_netsplit_block: None,
305 shanghai_time: timestamp(EthereumHardfork::Shanghai),
306 cancun_time: timestamp(EthereumHardfork::Cancun),
307 prague_time: timestamp(EthereumHardfork::Prague),
308 osaka_time: timestamp(EthereumHardfork::Osaka),
309 amsterdam_time: timestamp(EthereumHardfork::Amsterdam),
310 bogota_time: timestamp(EthereumHardfork::Bogota),
311 bpo1_time: timestamp(EthereumHardfork::Bpo1),
312 bpo2_time: timestamp(EthereumHardfork::Bpo2),
313 bpo3_time: timestamp(EthereumHardfork::Bpo3),
314 bpo4_time: timestamp(EthereumHardfork::Bpo4),
315 bpo5_time: timestamp(EthereumHardfork::Bpo5),
316 terminal_total_difficulty,
317 terminal_total_difficulty_passed,
318 deposit_contract_address,
319 blob_schedule,
320 ..Default::default()
321 }
322}
323
324pub fn mainnet_chain_config() -> ChainConfig {
326 let hardforks: ChainHardforks = EthereumHardfork::mainnet().into();
327 let blob_schedule = blob_params_to_schedule(&MAINNET.blob_params, &hardforks);
328 create_chain_config(
329 Some(Chain::mainnet()),
330 &hardforks,
331 Some(MAINNET_DEPOSIT_CONTRACT.address),
332 blob_schedule,
333 )
334}
335
336pub fn blob_params_to_schedule(
338 params: &BlobScheduleBlobParams,
339 hardforks: &ChainHardforks,
340) -> BTreeMap<String, BlobParams> {
341 let mut schedule = BTreeMap::new();
342 schedule.insert("cancun".to_string(), params.cancun);
343 schedule.insert("prague".to_string(), params.prague);
344 schedule.insert("osaka".to_string(), params.osaka);
345
346 let bpo_forks = EthereumHardfork::bpo_variants();
348 for (timestamp, blob_params) in ¶ms.scheduled {
349 for bpo_fork in bpo_forks {
350 if hardforks.fork(bpo_fork).as_timestamp() == Some(*timestamp) {
351 schedule.insert(bpo_fork.name().to_lowercase(), *blob_params);
352 break;
353 }
354 }
355 }
356
357 schedule
358}
359
360#[derive(Clone, Debug, PartialEq, Eq)]
363pub enum BaseFeeParamsKind {
364 Constant(BaseFeeParams),
366 Variable(ForkBaseFeeParams),
369}
370
371impl Default for BaseFeeParamsKind {
372 fn default() -> Self {
373 BaseFeeParams::ethereum().into()
374 }
375}
376
377impl From<BaseFeeParams> for BaseFeeParamsKind {
378 fn from(params: BaseFeeParams) -> Self {
379 Self::Constant(params)
380 }
381}
382
383impl From<ForkBaseFeeParams> for BaseFeeParamsKind {
384 fn from(params: ForkBaseFeeParams) -> Self {
385 Self::Variable(params)
386 }
387}
388
389#[derive(Clone, Debug, PartialEq, Eq, From)]
392pub struct ForkBaseFeeParams(Vec<(Box<dyn Hardfork>, BaseFeeParams)>);
393
394impl<H: BlockHeader> core::ops::Deref for ChainSpec<H> {
395 type Target = ChainHardforks;
396
397 fn deref(&self) -> &Self::Target {
398 &self.hardforks
399 }
400}
401
402#[derive(Debug, Clone, PartialEq, Eq)]
410pub struct ChainSpec<H: BlockHeader = Header> {
411 pub chain: Chain,
413
414 pub genesis: Genesis,
416
417 pub genesis_header: SealedHeader<H>,
419
420 pub paris_block_and_final_difficulty: Option<(u64, U256)>,
423
424 pub hardforks: ChainHardforks,
426
427 pub deposit_contract: Option<DepositContract>,
429
430 pub base_fee_params: BaseFeeParamsKind,
432
433 pub prune_delete_limit: usize,
435
436 pub blob_params: BlobScheduleBlobParams,
438}
439
440impl<H: BlockHeader> Default for ChainSpec<H> {
441 fn default() -> Self {
442 Self {
443 chain: Default::default(),
444 genesis: Default::default(),
445 genesis_header: Default::default(),
446 paris_block_and_final_difficulty: Default::default(),
447 hardforks: Default::default(),
448 deposit_contract: Default::default(),
449 base_fee_params: BaseFeeParamsKind::Constant(BaseFeeParams::ethereum()),
450 prune_delete_limit: MAINNET_PRUNE_DELETE_LIMIT,
451 blob_params: Default::default(),
452 }
453 }
454}
455
456impl ChainSpec {
457 pub fn from_genesis(genesis: Genesis) -> Self {
459 genesis.into()
460 }
461
462 pub fn builder() -> ChainSpecBuilder {
464 ChainSpecBuilder::default()
465 }
466
467 pub fn from_chain_id(chain_id: u64) -> Option<Arc<Self>> {
469 match NamedChain::try_from(chain_id).ok()? {
470 NamedChain::Mainnet => Some(MAINNET.clone()),
471 NamedChain::Sepolia => Some(SEPOLIA.clone()),
472 NamedChain::Holesky => Some(HOLESKY.clone()),
473 NamedChain::Hoodi => Some(HOODI.clone()),
474 NamedChain::Dev => Some(DEV.clone()),
475 _ => None,
476 }
477 }
478}
479
480impl<H: BlockHeader> ChainSpec<H> {
481 pub const fn chain(&self) -> Chain {
483 self.chain
484 }
485
486 #[inline]
488 pub const fn is_ethereum(&self) -> bool {
489 self.chain.is_ethereum()
490 }
491
492 #[inline]
494 pub fn is_optimism_mainnet(&self) -> bool {
495 self.chain == Chain::optimism_mainnet()
496 }
497
498 #[inline]
500 pub fn paris_block(&self) -> Option<u64> {
501 self.paris_block_and_final_difficulty.map(|(block, _)| block)
502 }
503
504 pub const fn genesis(&self) -> &Genesis {
508 &self.genesis
509 }
510
511 pub fn genesis_header(&self) -> &H {
513 &self.genesis_header
514 }
515
516 pub fn sealed_genesis_header(&self) -> SealedHeader<H> {
518 SealedHeader::new(self.genesis_header().clone(), self.genesis_hash())
519 }
520
521 pub fn initial_base_fee(&self) -> Option<u64> {
523 let genesis_base_fee =
525 self.genesis.base_fee_per_gas.map(|fee| fee as u64).unwrap_or(INITIAL_BASE_FEE);
526
527 self.is_london_active_at_block(0).then_some(genesis_base_fee)
529 }
530
531 pub fn base_fee_params_at_timestamp(&self, timestamp: u64) -> BaseFeeParams {
533 match self.base_fee_params {
534 BaseFeeParamsKind::Constant(bf_params) => bf_params,
535 BaseFeeParamsKind::Variable(ForkBaseFeeParams(ref bf_params)) => {
536 for (fork, params) in bf_params.iter().rev() {
540 if self.hardforks.is_fork_active_at_timestamp(fork.clone(), timestamp) {
541 return *params
542 }
543 }
544
545 bf_params.first().map(|(_, params)| *params).unwrap_or_else(BaseFeeParams::ethereum)
546 }
547 }
548 }
549
550 pub fn genesis_hash(&self) -> B256 {
552 self.genesis_header.hash()
553 }
554
555 pub const fn genesis_timestamp(&self) -> u64 {
557 self.genesis.timestamp
558 }
559
560 pub fn get_final_paris_total_difficulty(&self) -> Option<U256> {
562 self.paris_block_and_final_difficulty.map(|(_, final_difficulty)| final_difficulty)
563 }
564
565 pub fn hardfork_fork_filter<HF: Hardfork + Clone>(&self, fork: HF) -> Option<ForkFilter> {
567 match self.hardforks.fork(fork.clone()) {
568 ForkCondition::Never => None,
569 _ => Some(self.fork_filter(self.satisfy(self.hardforks.fork(fork)))),
570 }
571 }
572
573 pub fn display_hardforks(&self) -> DisplayHardforks {
575 let hardforks_with_meta = self.hardforks.forks_iter().map(|(fork, condition)| {
577 let metadata = condition.as_timestamp().and_then(|timestamp| {
579 EthChainSpec::blob_params_at_timestamp(self, timestamp).map(|params| {
582 format!(
583 "blob: (target: {}, max: {}, fraction: {})",
584 params.target_blob_count, params.max_blob_count, params.update_fraction
585 )
586 })
587 });
588 (fork, condition, metadata)
589 });
590
591 DisplayHardforks::with_meta(hardforks_with_meta)
592 }
593
594 #[inline]
596 pub fn hardfork_fork_id<HF: Hardfork + Clone>(&self, fork: HF) -> Option<ForkId> {
597 let condition = self.hardforks.fork(fork);
598 match condition {
599 ForkCondition::Never => None,
600 _ => Some(self.fork_id(&self.satisfy(condition))),
601 }
602 }
603
604 #[inline]
607 pub fn shanghai_fork_id(&self) -> Option<ForkId> {
608 self.hardfork_fork_id(EthereumHardfork::Shanghai)
609 }
610
611 #[inline]
614 pub fn cancun_fork_id(&self) -> Option<ForkId> {
615 self.hardfork_fork_id(EthereumHardfork::Cancun)
616 }
617
618 #[inline]
621 pub fn latest_fork_id(&self) -> ForkId {
622 self.hardfork_fork_id(self.hardforks.last().unwrap().0).unwrap()
623 }
624
625 pub fn fork_filter(&self, head: Head) -> ForkFilter {
627 let forks = self.hardforks.forks_iter().filter_map(|(_, condition)| {
628 Some(match condition {
631 ForkCondition::Block(block) |
632 ForkCondition::TTD { fork_block: Some(block), .. } => ForkFilterKey::Block(block),
633 ForkCondition::Timestamp(time) => ForkFilterKey::Time(time),
634 _ => return None,
635 })
636 });
637
638 ForkFilter::new(head, self.genesis_hash(), self.genesis_timestamp(), forks)
639 }
640
641 pub fn fork_id(&self, head: &Head) -> ForkId {
653 let mut forkhash = ForkHash::from(self.genesis_hash());
654
655 let mut current_applied = 0;
661
662 for (_, cond) in self.hardforks.forks_iter() {
664 if let ForkCondition::Block(block) |
667 ForkCondition::TTD { fork_block: Some(block), .. } = cond
668 {
669 if head.number >= block {
670 if block != current_applied {
672 forkhash += block;
673 current_applied = block;
674 }
675 } else {
676 return ForkId { hash: forkhash, next: block }
679 }
680 }
681 }
682
683 for timestamp in self.hardforks.forks_iter().filter_map(|(_, cond)| {
687 cond.as_timestamp().filter(|time| time > &self.genesis.timestamp)
689 }) {
690 if head.timestamp >= timestamp {
691 if timestamp != current_applied {
693 forkhash += timestamp;
694 current_applied = timestamp;
695 }
696 } else {
697 return ForkId { hash: forkhash, next: timestamp }
701 }
702 }
703
704 ForkId { hash: forkhash, next: 0 }
705 }
706
707 pub(crate) fn satisfy(&self, cond: ForkCondition) -> Head {
713 match cond {
714 ForkCondition::Block(number) => Head { number, ..Default::default() },
715 ForkCondition::Timestamp(timestamp) => {
716 Head {
719 timestamp,
720 number: self.last_block_fork_before_merge_or_timestamp().unwrap_or_default(),
721 ..Default::default()
722 }
723 }
724 ForkCondition::TTD { total_difficulty, fork_block, .. } => Head {
725 total_difficulty,
726 number: fork_block.unwrap_or_default(),
727 ..Default::default()
728 },
729 ForkCondition::Never => unreachable!(),
730 }
731 }
732
733 pub(crate) fn last_block_fork_before_merge_or_timestamp(&self) -> Option<u64> {
745 let mut hardforks_iter = self.hardforks.forks_iter().peekable();
746 while let Some((_, curr_cond)) = hardforks_iter.next() {
747 if let Some((_, next_cond)) = hardforks_iter.peek() {
748 match next_cond {
752 ForkCondition::TTD { fork_block: Some(block), .. } => return Some(*block),
755
756 ForkCondition::TTD { .. } | ForkCondition::Timestamp(_) => {
759 if let ForkCondition::Block(block_num) = curr_cond {
762 return Some(block_num);
763 }
764 }
765 ForkCondition::Block(_) | ForkCondition::Never => {}
766 }
767 }
768 }
769 None
770 }
771
772 pub fn bootnodes(&self) -> Option<Vec<NodeRecord>> {
774 match self.chain.try_into().ok()? {
775 C::Mainnet => Some(mainnet_nodes()),
776 C::Sepolia => Some(sepolia_nodes()),
777 C::Holesky => Some(holesky_nodes()),
778 C::Hoodi => Some(hoodi_nodes()),
779 _ => None,
780 }
781 }
782
783 pub fn map_header<NewH: BlockHeader>(self, f: impl FnOnce(H) -> NewH) -> ChainSpec<NewH> {
785 let Self {
786 chain,
787 genesis,
788 genesis_header,
789 paris_block_and_final_difficulty,
790 hardforks,
791 deposit_contract,
792 base_fee_params,
793 prune_delete_limit,
794 blob_params,
795 } = self;
796 ChainSpec {
797 chain,
798 genesis,
799 genesis_header: SealedHeader::new_unhashed(f(genesis_header.into_header())),
800 paris_block_and_final_difficulty,
801 hardforks,
802 deposit_contract,
803 base_fee_params,
804 prune_delete_limit,
805 blob_params,
806 }
807 }
808}
809
810impl From<Genesis> for ChainSpec {
811 fn from(genesis: Genesis) -> Self {
812 let hardfork_opts = [
814 (EthereumHardfork::Frontier.boxed(), Some(0)),
815 (EthereumHardfork::Homestead.boxed(), genesis.config.homestead_block),
816 (EthereumHardfork::Dao.boxed(), genesis.config.dao_fork_block),
817 (EthereumHardfork::Tangerine.boxed(), genesis.config.eip150_block),
818 (EthereumHardfork::SpuriousDragon.boxed(), genesis.config.eip155_block),
819 (EthereumHardfork::Byzantium.boxed(), genesis.config.byzantium_block),
820 (EthereumHardfork::Constantinople.boxed(), genesis.config.constantinople_block),
821 (EthereumHardfork::Petersburg.boxed(), genesis.config.petersburg_block),
822 (EthereumHardfork::Istanbul.boxed(), genesis.config.istanbul_block),
823 (EthereumHardfork::MuirGlacier.boxed(), genesis.config.muir_glacier_block),
824 (EthereumHardfork::Berlin.boxed(), genesis.config.berlin_block),
825 (EthereumHardfork::London.boxed(), genesis.config.london_block),
826 (EthereumHardfork::ArrowGlacier.boxed(), genesis.config.arrow_glacier_block),
827 (EthereumHardfork::GrayGlacier.boxed(), genesis.config.gray_glacier_block),
828 ];
829 let mut hardforks = hardfork_opts
830 .into_iter()
831 .filter_map(|(hardfork, opt)| opt.map(|block| (hardfork, ForkCondition::Block(block))))
832 .collect::<Vec<_>>();
833
834 let paris_block_and_final_difficulty = if let Some(ttd) =
838 genesis.config.terminal_total_difficulty
839 {
840 hardforks.push((
841 EthereumHardfork::Paris.boxed(),
842 ForkCondition::TTD {
843 activation_block_number: genesis
846 .config
847 .merge_netsplit_block
848 .or_else(|| {
849 match genesis.config.chain_id {
857 1 if ttd == MAINNET_PARIS_TTD => return Some(MAINNET_PARIS_BLOCK),
858 11155111 if ttd == SEPOLIA_PARIS_TTD => {
859 return Some(SEPOLIA_PARIS_BLOCK)
860 }
861 _ => {}
862 };
863 None
864 })
865 .unwrap_or_default(),
866 total_difficulty: ttd,
867 fork_block: genesis.config.merge_netsplit_block,
868 },
869 ));
870
871 genesis.config.merge_netsplit_block.map(|block| (block, ttd))
872 } else {
873 None
874 };
875
876 let time_hardfork_opts = [
878 (EthereumHardfork::Shanghai.boxed(), genesis.config.shanghai_time),
879 (EthereumHardfork::Cancun.boxed(), genesis.config.cancun_time),
880 (EthereumHardfork::Prague.boxed(), genesis.config.prague_time),
881 (EthereumHardfork::Osaka.boxed(), genesis.config.osaka_time),
882 (EthereumHardfork::Bpo1.boxed(), genesis.config.bpo1_time),
883 (EthereumHardfork::Bpo2.boxed(), genesis.config.bpo2_time),
884 (EthereumHardfork::Bpo3.boxed(), genesis.config.bpo3_time),
885 (EthereumHardfork::Bpo4.boxed(), genesis.config.bpo4_time),
886 (EthereumHardfork::Bpo5.boxed(), genesis.config.bpo5_time),
887 (EthereumHardfork::Amsterdam.boxed(), genesis.config.amsterdam_time),
888 (EthereumHardfork::Bogota.boxed(), genesis.config.bogota_time),
889 ];
890
891 let mut time_hardforks = time_hardfork_opts
892 .into_iter()
893 .filter_map(|(hardfork, opt)| {
894 opt.map(|time| (hardfork, ForkCondition::Timestamp(time)))
895 })
896 .collect::<Vec<_>>();
897
898 hardforks.append(&mut time_hardforks);
899
900 let mainnet_hardforks: ChainHardforks = EthereumHardfork::mainnet().into();
902 let mainnet_order = mainnet_hardforks.forks_iter();
903
904 let mut ordered_hardforks = Vec::with_capacity(hardforks.len());
905 for (hardfork, _) in mainnet_order {
906 if let Some(pos) = hardforks.iter().position(|(e, _)| **e == *hardfork) {
907 ordered_hardforks.push(hardforks.remove(pos));
908 }
909 }
910
911 ordered_hardforks.append(&mut hardforks);
913
914 let blob_params = genesis.config.blob_schedule_blob_params();
916
917 let deposit_contract = genesis.config.deposit_contract_address.map(|address| {
922 DepositContract { address, block: 0, topic: MAINNET_DEPOSIT_CONTRACT.topic }
923 });
924
925 let hardforks = ChainHardforks::new(ordered_hardforks);
926
927 Self {
928 chain: genesis.config.chain_id.into(),
929 genesis_header: SealedHeader::new_unhashed(make_genesis_header(&genesis, &hardforks)),
930 genesis,
931 hardforks,
932 paris_block_and_final_difficulty,
933 deposit_contract,
934 blob_params,
935 ..Default::default()
936 }
937 }
938}
939
940impl<H: BlockHeader> Hardforks for ChainSpec<H> {
941 fn fork<HF: Hardfork>(&self, fork: HF) -> ForkCondition {
942 self.hardforks.fork(fork)
943 }
944
945 fn forks_iter(&self) -> impl Iterator<Item = (&dyn Hardfork, ForkCondition)> {
946 self.hardforks.forks_iter()
947 }
948
949 fn fork_id(&self, head: &Head) -> ForkId {
950 self.fork_id(head)
951 }
952
953 fn latest_fork_id(&self) -> ForkId {
954 self.latest_fork_id()
955 }
956
957 fn fork_filter(&self, head: Head) -> ForkFilter {
958 self.fork_filter(head)
959 }
960}
961
962impl<H: BlockHeader> EthereumHardforks for ChainSpec<H> {
963 fn ethereum_fork_activation(&self, fork: EthereumHardfork) -> ForkCondition {
964 self.fork(fork)
965 }
966}
967
968#[auto_impl::auto_impl(&, Arc)]
970pub trait ChainSpecProvider: Debug + Send {
971 type ChainSpec: EthChainSpec + 'static;
973
974 fn chain_spec(&self) -> Arc<Self::ChainSpec>;
976}
977
978#[derive(Debug, Default, Clone)]
980pub struct ChainSpecBuilder {
981 chain: Option<Chain>,
982 genesis: Option<Genesis>,
983 hardforks: ChainHardforks,
984}
985
986impl ChainSpecBuilder {
987 pub fn mainnet() -> Self {
989 Self {
990 chain: Some(MAINNET.chain),
991 genesis: Some(MAINNET.genesis.clone()),
992 hardforks: MAINNET.hardforks.clone(),
993 }
994 }
995}
996
997impl ChainSpecBuilder {
998 pub const fn chain(mut self, chain: Chain) -> Self {
1000 self.chain = Some(chain);
1001 self
1002 }
1003
1004 pub fn reset(mut self) -> Self {
1006 self.hardforks = ChainHardforks::default();
1007 self
1008 }
1009
1010 pub fn genesis(mut self, genesis: Genesis) -> Self {
1012 self.genesis = Some(genesis);
1013 self
1014 }
1015
1016 pub fn with_fork<H: Hardfork>(mut self, fork: H, condition: ForkCondition) -> Self {
1018 self.hardforks.insert(fork, condition);
1019 self
1020 }
1021
1022 pub fn with_forks(mut self, forks: ChainHardforks) -> Self {
1024 self.hardforks = forks;
1025 self
1026 }
1027
1028 pub fn without_fork<H: Hardfork>(mut self, fork: H) -> Self {
1030 self.hardforks.remove(&fork);
1031 self
1032 }
1033
1034 pub fn paris_at_ttd(self, ttd: U256, activation_block_number: BlockNumber) -> Self {
1038 self.with_fork(
1039 EthereumHardfork::Paris,
1040 ForkCondition::TTD { activation_block_number, total_difficulty: ttd, fork_block: None },
1041 )
1042 }
1043
1044 pub fn frontier_activated(mut self) -> Self {
1046 self.hardforks.insert(EthereumHardfork::Frontier, ForkCondition::Block(0));
1047 self
1048 }
1049
1050 pub fn dao_activated(mut self) -> Self {
1052 self = self.frontier_activated();
1053 self.hardforks.insert(EthereumHardfork::Dao, ForkCondition::Block(0));
1054 self
1055 }
1056
1057 pub fn homestead_activated(mut self) -> Self {
1059 self = self.dao_activated();
1060 self.hardforks.insert(EthereumHardfork::Homestead, ForkCondition::Block(0));
1061 self
1062 }
1063
1064 pub fn tangerine_whistle_activated(mut self) -> Self {
1066 self = self.homestead_activated();
1067 self.hardforks.insert(EthereumHardfork::Tangerine, ForkCondition::Block(0));
1068 self
1069 }
1070
1071 pub fn spurious_dragon_activated(mut self) -> Self {
1073 self = self.tangerine_whistle_activated();
1074 self.hardforks.insert(EthereumHardfork::SpuriousDragon, ForkCondition::Block(0));
1075 self
1076 }
1077
1078 pub fn byzantium_activated(mut self) -> Self {
1080 self = self.spurious_dragon_activated();
1081 self.hardforks.insert(EthereumHardfork::Byzantium, ForkCondition::Block(0));
1082 self
1083 }
1084
1085 pub fn constantinople_activated(mut self) -> Self {
1087 self = self.byzantium_activated();
1088 self.hardforks.insert(EthereumHardfork::Constantinople, ForkCondition::Block(0));
1089 self
1090 }
1091
1092 pub fn petersburg_activated(mut self) -> Self {
1094 self = self.constantinople_activated();
1095 self.hardforks.insert(EthereumHardfork::Petersburg, ForkCondition::Block(0));
1096 self
1097 }
1098
1099 pub fn istanbul_activated(mut self) -> Self {
1101 self = self.petersburg_activated();
1102 self.hardforks.insert(EthereumHardfork::Istanbul, ForkCondition::Block(0));
1103 self
1104 }
1105
1106 pub fn muirglacier_activated(mut self) -> Self {
1108 self = self.istanbul_activated();
1109 self.hardforks.insert(EthereumHardfork::MuirGlacier, ForkCondition::Block(0));
1110 self
1111 }
1112
1113 pub fn berlin_activated(mut self) -> Self {
1115 self = self.muirglacier_activated();
1116 self.hardforks.insert(EthereumHardfork::Berlin, ForkCondition::Block(0));
1117 self
1118 }
1119
1120 pub fn london_activated(mut self) -> Self {
1122 self = self.berlin_activated();
1123 self.hardforks.insert(EthereumHardfork::London, ForkCondition::Block(0));
1124 self
1125 }
1126
1127 pub fn arrowglacier_activated(mut self) -> Self {
1129 self = self.london_activated();
1130 self.hardforks.insert(EthereumHardfork::ArrowGlacier, ForkCondition::Block(0));
1131 self
1132 }
1133
1134 pub fn grayglacier_activated(mut self) -> Self {
1136 self = self.arrowglacier_activated();
1137 self.hardforks.insert(EthereumHardfork::GrayGlacier, ForkCondition::Block(0));
1138 self
1139 }
1140
1141 pub fn paris_activated(mut self) -> Self {
1143 self = self.grayglacier_activated();
1144 self.hardforks.insert(
1145 EthereumHardfork::Paris,
1146 ForkCondition::TTD {
1147 activation_block_number: 0,
1148 total_difficulty: U256::ZERO,
1149 fork_block: None,
1150 },
1151 );
1152 self
1153 }
1154
1155 pub fn shanghai_activated(mut self) -> Self {
1157 self = self.paris_activated();
1158 self.hardforks.insert(EthereumHardfork::Shanghai, ForkCondition::Timestamp(0));
1159 self
1160 }
1161
1162 pub fn cancun_activated(mut self) -> Self {
1164 self = self.shanghai_activated();
1165 self.hardforks.insert(EthereumHardfork::Cancun, ForkCondition::Timestamp(0));
1166 self
1167 }
1168
1169 pub fn prague_activated(mut self) -> Self {
1171 self = self.cancun_activated();
1172 self.hardforks.insert(EthereumHardfork::Prague, ForkCondition::Timestamp(0));
1173 self
1174 }
1175
1176 pub fn with_prague_at(mut self, timestamp: u64) -> Self {
1178 self.hardforks.insert(EthereumHardfork::Prague, ForkCondition::Timestamp(timestamp));
1179 self
1180 }
1181
1182 pub fn osaka_activated(mut self) -> Self {
1184 self = self.prague_activated();
1185 self.hardforks.insert(EthereumHardfork::Osaka, ForkCondition::Timestamp(0));
1186 self
1187 }
1188
1189 pub fn with_osaka_at(mut self, timestamp: u64) -> Self {
1191 self.hardforks.insert(EthereumHardfork::Osaka, ForkCondition::Timestamp(timestamp));
1192 self
1193 }
1194
1195 pub fn amsterdam_activated(mut self) -> Self {
1197 self = self.osaka_activated();
1198 self.hardforks.insert(EthereumHardfork::Amsterdam, ForkCondition::Timestamp(0));
1199 self
1200 }
1201
1202 pub fn with_amsterdam_at(mut self, timestamp: u64) -> Self {
1204 self.hardforks.insert(EthereumHardfork::Amsterdam, ForkCondition::Timestamp(timestamp));
1205 self
1206 }
1207
1208 pub fn bogota_activated(mut self) -> Self {
1210 self = self.amsterdam_activated();
1211 self.hardforks.insert(EthereumHardfork::Bogota, ForkCondition::Timestamp(0));
1212 self
1213 }
1214
1215 pub fn with_bogota_at(mut self, timestamp: u64) -> Self {
1217 self.hardforks.insert(EthereumHardfork::Bogota, ForkCondition::Timestamp(timestamp));
1218 self
1219 }
1220
1221 pub fn build(self) -> ChainSpec {
1228 let paris_block_and_final_difficulty = {
1229 self.hardforks.get(EthereumHardfork::Paris).and_then(|cond| {
1230 if let ForkCondition::TTD { total_difficulty, activation_block_number, .. } = cond {
1231 Some((activation_block_number, total_difficulty))
1232 } else {
1233 None
1234 }
1235 })
1236 };
1237 let genesis = self.genesis.expect("The genesis is required");
1238 ChainSpec {
1239 chain: self.chain.expect("The chain is required"),
1240 genesis_header: SealedHeader::new_unhashed(make_genesis_header(
1241 &genesis,
1242 &self.hardforks,
1243 )),
1244 genesis,
1245 hardforks: self.hardforks,
1246 paris_block_and_final_difficulty,
1247 deposit_contract: None,
1248 ..Default::default()
1249 }
1250 }
1251}
1252
1253impl From<&Arc<ChainSpec>> for ChainSpecBuilder {
1254 fn from(value: &Arc<ChainSpec>) -> Self {
1255 Self {
1256 chain: Some(value.chain),
1257 genesis: Some(value.genesis.clone()),
1258 hardforks: value.hardforks.clone(),
1259 }
1260 }
1261}
1262
1263#[derive(Debug, Clone, Copy, PartialEq, Eq)]
1265pub struct DepositContract {
1266 pub address: Address,
1268 pub block: BlockNumber,
1270 pub topic: B256,
1272}
1273
1274impl DepositContract {
1275 pub const fn new(address: Address, block: BlockNumber, topic: B256) -> Self {
1277 Self { address, block, topic }
1278 }
1279}
1280
1281#[cfg(any(test, feature = "test-utils"))]
1283pub fn test_fork_ids(spec: &ChainSpec, cases: &[(Head, ForkId)]) {
1284 for (block, expected_id) in cases {
1285 let computed_id = spec.fork_id(block);
1286 assert_eq!(
1287 expected_id, &computed_id,
1288 "Expected fork ID {:?}, computed fork ID {:?} at block {}",
1289 expected_id, computed_id, block.number
1290 );
1291 }
1292}
1293
1294#[cfg(test)]
1295mod tests {
1296
1297 use super::*;
1298 use alloy_chains::Chain;
1299 use alloy_eips::{eip4844::BLOB_TX_MIN_BLOB_GASPRICE, eip7840::BlobParams};
1300 use alloy_genesis::{ChainConfig, GenesisAccount};
1301 use alloy_primitives::{b256, hex};
1302 use alloy_trie::{TrieAccount, EMPTY_ROOT_HASH};
1303 use core::ops::Deref;
1304 use reth_ethereum_forks::{ForkCondition, ForkHash, ForkId, Head};
1305 use std::collections::HashMap;
1306
1307 fn test_hardfork_fork_ids(spec: &ChainSpec, cases: &[(EthereumHardfork, ForkId)]) {
1308 for (hardfork, expected_id) in cases {
1309 if let Some(computed_id) = spec.hardfork_fork_id(*hardfork) {
1310 assert_eq!(
1311 expected_id, &computed_id,
1312 "Expected fork ID {expected_id:?}, computed fork ID {computed_id:?} for hardfork {hardfork}"
1313 );
1314 if matches!(hardfork, EthereumHardfork::Shanghai) {
1315 if let Some(shanghai_id) = spec.shanghai_fork_id() {
1316 assert_eq!(
1317 expected_id, &shanghai_id,
1318 "Expected fork ID {expected_id:?}, computed fork ID {computed_id:?} for Shanghai hardfork"
1319 );
1320 } else {
1321 panic!("Expected ForkCondition to return Some for Hardfork::Shanghai");
1322 }
1323 }
1324 }
1325 }
1326 }
1327
1328 #[test]
1329 fn test_hardfork_list_display_mainnet() {
1330 assert_eq!(
1331 MAINNET.display_hardforks().to_string(),
1332 "Pre-merge hard forks (block based):
1333- Frontier @0
1334- Homestead @1150000
1335- Dao @1920000
1336- Tangerine @2463000
1337- SpuriousDragon @2675000
1338- Byzantium @4370000
1339- Constantinople @7280000
1340- Petersburg @7280000
1341- Istanbul @9069000
1342- MuirGlacier @9200000
1343- Berlin @12244000
1344- London @12965000
1345- ArrowGlacier @13773000
1346- GrayGlacier @15050000
1347Merge hard forks:
1348- Paris @58750000000000000000000 (network is known to be merged)
1349Post-merge hard forks (timestamp based):
1350- Shanghai @1681338455
1351- Cancun @1710338135 blob: (target: 3, max: 6, fraction: 3338477)
1352- Prague @1746612311 blob: (target: 6, max: 9, fraction: 5007716)
1353- Osaka @1764798551 blob: (target: 6, max: 9, fraction: 5007716)
1354- Bpo1 @1765290071 blob: (target: 10, max: 15, fraction: 8346193)
1355- Bpo2 @1767747671 blob: (target: 14, max: 21, fraction: 11684671)"
1356 );
1357 }
1358
1359 #[test]
1360 fn test_hardfork_list_ignores_disabled_forks() {
1361 let spec = ChainSpec::builder()
1362 .chain(Chain::mainnet())
1363 .genesis(Genesis::default())
1364 .with_fork(EthereumHardfork::Frontier, ForkCondition::Block(0))
1365 .with_fork(EthereumHardfork::Shanghai, ForkCondition::Never)
1366 .build();
1367 assert_eq!(
1368 spec.display_hardforks().to_string(),
1369 "Pre-merge hard forks (block based):
1370- Frontier @0"
1371 );
1372 }
1373
1374 #[test]
1376 fn ignores_genesis_fork_blocks() {
1377 let spec = ChainSpec::builder()
1378 .chain(Chain::mainnet())
1379 .genesis(Genesis::default())
1380 .with_fork(EthereumHardfork::Frontier, ForkCondition::Block(0))
1381 .with_fork(EthereumHardfork::Homestead, ForkCondition::Block(0))
1382 .with_fork(EthereumHardfork::Tangerine, ForkCondition::Block(0))
1383 .with_fork(EthereumHardfork::SpuriousDragon, ForkCondition::Block(0))
1384 .with_fork(EthereumHardfork::Byzantium, ForkCondition::Block(0))
1385 .with_fork(EthereumHardfork::Constantinople, ForkCondition::Block(0))
1386 .with_fork(EthereumHardfork::Istanbul, ForkCondition::Block(0))
1387 .with_fork(EthereumHardfork::MuirGlacier, ForkCondition::Block(0))
1388 .with_fork(EthereumHardfork::Berlin, ForkCondition::Block(0))
1389 .with_fork(EthereumHardfork::London, ForkCondition::Block(0))
1390 .with_fork(EthereumHardfork::ArrowGlacier, ForkCondition::Block(0))
1391 .with_fork(EthereumHardfork::GrayGlacier, ForkCondition::Block(0))
1392 .build();
1393
1394 assert_eq!(spec.deref().len(), 12, "12 forks should be active.");
1395 assert_eq!(
1396 spec.fork_id(&Head { number: 1, ..Default::default() }),
1397 ForkId { hash: ForkHash::from(spec.genesis_hash()), next: 0 },
1398 "the fork ID should be the genesis hash; forks at genesis are ignored for fork filters"
1399 );
1400 }
1401
1402 #[test]
1403 fn ignores_duplicate_fork_blocks() {
1404 let empty_genesis = Genesis::default();
1405 let unique_spec = ChainSpec::builder()
1406 .chain(Chain::mainnet())
1407 .genesis(empty_genesis.clone())
1408 .with_fork(EthereumHardfork::Frontier, ForkCondition::Block(0))
1409 .with_fork(EthereumHardfork::Homestead, ForkCondition::Block(1))
1410 .build();
1411
1412 let duplicate_spec = ChainSpec::builder()
1413 .chain(Chain::mainnet())
1414 .genesis(empty_genesis)
1415 .with_fork(EthereumHardfork::Frontier, ForkCondition::Block(0))
1416 .with_fork(EthereumHardfork::Homestead, ForkCondition::Block(1))
1417 .with_fork(EthereumHardfork::Tangerine, ForkCondition::Block(1))
1418 .build();
1419
1420 assert_eq!(
1421 unique_spec.fork_id(&Head { number: 2, ..Default::default() }),
1422 duplicate_spec.fork_id(&Head { number: 2, ..Default::default() }),
1423 "duplicate fork blocks should be deduplicated for fork filters"
1424 );
1425 }
1426
1427 #[test]
1428 fn test_chainspec_satisfy() {
1429 let empty_genesis = Genesis::default();
1430 let happy_path_case = ChainSpec::builder()
1432 .chain(Chain::mainnet())
1433 .genesis(empty_genesis.clone())
1434 .with_fork(EthereumHardfork::Frontier, ForkCondition::Block(0))
1435 .with_fork(EthereumHardfork::Homestead, ForkCondition::Block(73))
1436 .with_fork(EthereumHardfork::Shanghai, ForkCondition::Timestamp(11313123))
1437 .build();
1438 let happy_path_head = happy_path_case.satisfy(ForkCondition::Timestamp(11313123));
1439 let happy_path_expected = Head { number: 73, timestamp: 11313123, ..Default::default() };
1440 assert_eq!(
1441 happy_path_head, happy_path_expected,
1442 "expected satisfy() to return {happy_path_expected:#?}, but got {happy_path_head:#?} "
1443 );
1444 let multiple_timestamp_fork_case = ChainSpec::builder()
1446 .chain(Chain::mainnet())
1447 .genesis(empty_genesis.clone())
1448 .with_fork(EthereumHardfork::Frontier, ForkCondition::Block(0))
1449 .with_fork(EthereumHardfork::Homestead, ForkCondition::Block(73))
1450 .with_fork(EthereumHardfork::Shanghai, ForkCondition::Timestamp(11313123))
1451 .with_fork(EthereumHardfork::Cancun, ForkCondition::Timestamp(11313398))
1452 .build();
1453 let multi_timestamp_head =
1454 multiple_timestamp_fork_case.satisfy(ForkCondition::Timestamp(11313398));
1455 let mult_timestamp_expected =
1456 Head { number: 73, timestamp: 11313398, ..Default::default() };
1457 assert_eq!(
1458 multi_timestamp_head, mult_timestamp_expected,
1459 "expected satisfy() to return {mult_timestamp_expected:#?}, but got {multi_timestamp_head:#?} "
1460 );
1461 let no_block_fork_case = ChainSpec::builder()
1463 .chain(Chain::mainnet())
1464 .genesis(empty_genesis.clone())
1465 .with_fork(EthereumHardfork::Shanghai, ForkCondition::Timestamp(11313123))
1466 .build();
1467 let no_block_fork_head = no_block_fork_case.satisfy(ForkCondition::Timestamp(11313123));
1468 let no_block_fork_expected = Head { number: 0, timestamp: 11313123, ..Default::default() };
1469 assert_eq!(
1470 no_block_fork_head, no_block_fork_expected,
1471 "expected satisfy() to return {no_block_fork_expected:#?}, but got {no_block_fork_head:#?} ",
1472 );
1473 let fork_cond_ttd_blocknum_case = ChainSpec::builder()
1475 .chain(Chain::mainnet())
1476 .genesis(empty_genesis.clone())
1477 .with_fork(EthereumHardfork::Frontier, ForkCondition::Block(0))
1478 .with_fork(EthereumHardfork::Homestead, ForkCondition::Block(73))
1479 .with_fork(
1480 EthereumHardfork::Paris,
1481 ForkCondition::TTD {
1482 activation_block_number: 101,
1483 fork_block: Some(101),
1484 total_difficulty: U256::from(10_790_000),
1485 },
1486 )
1487 .with_fork(EthereumHardfork::Shanghai, ForkCondition::Timestamp(11313123))
1488 .build();
1489 let fork_cond_ttd_blocknum_head =
1490 fork_cond_ttd_blocknum_case.satisfy(ForkCondition::Timestamp(11313123));
1491 let fork_cond_ttd_blocknum_expected =
1492 Head { number: 101, timestamp: 11313123, ..Default::default() };
1493 assert_eq!(
1494 fork_cond_ttd_blocknum_head, fork_cond_ttd_blocknum_expected,
1495 "expected satisfy() to return {fork_cond_ttd_blocknum_expected:#?}, but got {fork_cond_ttd_blocknum_head:#?} ",
1496 );
1497
1498 let fork_cond_block_only_case = ChainSpec::builder()
1502 .chain(Chain::mainnet())
1503 .genesis(empty_genesis)
1504 .with_fork(EthereumHardfork::Frontier, ForkCondition::Block(0))
1505 .with_fork(EthereumHardfork::Homestead, ForkCondition::Block(73))
1506 .build();
1507 let fork_cond_block_only_head = fork_cond_block_only_case.satisfy(ForkCondition::Block(73));
1508 let fork_cond_block_only_expected = Head { number: 73, ..Default::default() };
1509 assert_eq!(
1510 fork_cond_block_only_head, fork_cond_block_only_expected,
1511 "expected satisfy() to return {fork_cond_block_only_expected:#?}, but got {fork_cond_block_only_head:#?} ",
1512 );
1513 let fork_cond_ttd_no_new_spec = fork_cond_block_only_case.satisfy(ForkCondition::TTD {
1516 activation_block_number: 101,
1517 fork_block: None,
1518 total_difficulty: U256::from(10_790_000),
1519 });
1520 let fork_cond_ttd_no_new_spec_expected =
1521 Head { total_difficulty: U256::from(10_790_000), ..Default::default() };
1522 assert_eq!(
1523 fork_cond_ttd_no_new_spec, fork_cond_ttd_no_new_spec_expected,
1524 "expected satisfy() to return {fork_cond_ttd_no_new_spec_expected:#?}, but got {fork_cond_ttd_no_new_spec:#?} ",
1525 );
1526 }
1527
1528 #[test]
1529 fn mainnet_hardfork_fork_ids() {
1530 test_hardfork_fork_ids(
1531 &MAINNET,
1532 &[
1533 (
1534 EthereumHardfork::Frontier,
1535 ForkId { hash: ForkHash(hex!("0xfc64ec04")), next: 1150000 },
1536 ),
1537 (
1538 EthereumHardfork::Homestead,
1539 ForkId { hash: ForkHash(hex!("0x97c2c34c")), next: 1920000 },
1540 ),
1541 (
1542 EthereumHardfork::Dao,
1543 ForkId { hash: ForkHash(hex!("0x91d1f948")), next: 2463000 },
1544 ),
1545 (
1546 EthereumHardfork::Tangerine,
1547 ForkId { hash: ForkHash(hex!("0x7a64da13")), next: 2675000 },
1548 ),
1549 (
1550 EthereumHardfork::SpuriousDragon,
1551 ForkId { hash: ForkHash(hex!("0x3edd5b10")), next: 4370000 },
1552 ),
1553 (
1554 EthereumHardfork::Byzantium,
1555 ForkId { hash: ForkHash(hex!("0xa00bc324")), next: 7280000 },
1556 ),
1557 (
1558 EthereumHardfork::Constantinople,
1559 ForkId { hash: ForkHash(hex!("0x668db0af")), next: 9069000 },
1560 ),
1561 (
1562 EthereumHardfork::Petersburg,
1563 ForkId { hash: ForkHash(hex!("0x668db0af")), next: 9069000 },
1564 ),
1565 (
1566 EthereumHardfork::Istanbul,
1567 ForkId { hash: ForkHash(hex!("0x879d6e30")), next: 9200000 },
1568 ),
1569 (
1570 EthereumHardfork::MuirGlacier,
1571 ForkId { hash: ForkHash(hex!("0xe029e991")), next: 12244000 },
1572 ),
1573 (
1574 EthereumHardfork::Berlin,
1575 ForkId { hash: ForkHash(hex!("0x0eb440f6")), next: 12965000 },
1576 ),
1577 (
1578 EthereumHardfork::London,
1579 ForkId { hash: ForkHash(hex!("0xb715077d")), next: 13773000 },
1580 ),
1581 (
1582 EthereumHardfork::ArrowGlacier,
1583 ForkId { hash: ForkHash(hex!("0x20c327fc")), next: 15050000 },
1584 ),
1585 (
1586 EthereumHardfork::GrayGlacier,
1587 ForkId { hash: ForkHash(hex!("0xf0afd0e3")), next: 1681338455 },
1588 ),
1589 (
1590 EthereumHardfork::Shanghai,
1591 ForkId { hash: ForkHash(hex!("0xdce96c2d")), next: 1710338135 },
1592 ),
1593 (
1594 EthereumHardfork::Cancun,
1595 ForkId { hash: ForkHash(hex!("0x9f3d2254")), next: 1746612311 },
1596 ),
1597 (
1598 EthereumHardfork::Prague,
1599 ForkId {
1600 hash: ForkHash(hex!("0xc376cf8b")),
1601 next: mainnet::MAINNET_OSAKA_TIMESTAMP,
1602 },
1603 ),
1604 ],
1605 );
1606 }
1607
1608 #[test]
1609 fn sepolia_hardfork_fork_ids() {
1610 test_hardfork_fork_ids(
1611 &SEPOLIA,
1612 &[
1613 (
1614 EthereumHardfork::Frontier,
1615 ForkId { hash: ForkHash(hex!("0xfe3366e7")), next: 1735371 },
1616 ),
1617 (
1618 EthereumHardfork::Homestead,
1619 ForkId { hash: ForkHash(hex!("0xfe3366e7")), next: 1735371 },
1620 ),
1621 (
1622 EthereumHardfork::Tangerine,
1623 ForkId { hash: ForkHash(hex!("0xfe3366e7")), next: 1735371 },
1624 ),
1625 (
1626 EthereumHardfork::SpuriousDragon,
1627 ForkId { hash: ForkHash(hex!("0xfe3366e7")), next: 1735371 },
1628 ),
1629 (
1630 EthereumHardfork::Byzantium,
1631 ForkId { hash: ForkHash(hex!("0xfe3366e7")), next: 1735371 },
1632 ),
1633 (
1634 EthereumHardfork::Constantinople,
1635 ForkId { hash: ForkHash(hex!("0xfe3366e7")), next: 1735371 },
1636 ),
1637 (
1638 EthereumHardfork::Petersburg,
1639 ForkId { hash: ForkHash(hex!("0xfe3366e7")), next: 1735371 },
1640 ),
1641 (
1642 EthereumHardfork::Istanbul,
1643 ForkId { hash: ForkHash(hex!("0xfe3366e7")), next: 1735371 },
1644 ),
1645 (
1646 EthereumHardfork::Berlin,
1647 ForkId { hash: ForkHash(hex!("0xfe3366e7")), next: 1735371 },
1648 ),
1649 (
1650 EthereumHardfork::London,
1651 ForkId { hash: ForkHash(hex!("0xfe3366e7")), next: 1735371 },
1652 ),
1653 (
1654 EthereumHardfork::Paris,
1655 ForkId { hash: ForkHash(hex!("0xb96cbd13")), next: 1677557088 },
1656 ),
1657 (
1658 EthereumHardfork::Shanghai,
1659 ForkId { hash: ForkHash(hex!("0xf7f9bc08")), next: 1706655072 },
1660 ),
1661 (
1662 EthereumHardfork::Cancun,
1663 ForkId { hash: ForkHash(hex!("0x88cf81d9")), next: 1741159776 },
1664 ),
1665 (
1666 EthereumHardfork::Prague,
1667 ForkId {
1668 hash: ForkHash(hex!("0xed88b5fd")),
1669 next: sepolia::SEPOLIA_OSAKA_TIMESTAMP,
1670 },
1671 ),
1672 (
1673 EthereumHardfork::Osaka,
1674 ForkId {
1675 hash: ForkHash(hex!("0xe2ae4999")),
1676 next: sepolia::SEPOLIA_BPO1_TIMESTAMP,
1677 },
1678 ),
1679 (
1680 EthereumHardfork::Bpo1,
1681 ForkId {
1682 hash: ForkHash(hex!("0x56078a1e")),
1683 next: sepolia::SEPOLIA_BPO2_TIMESTAMP,
1684 },
1685 ),
1686 (
1687 EthereumHardfork::Bpo2,
1688 ForkId {
1689 hash: ForkHash(hex!("0x268956b6")),
1690 next: sepolia::SEPOLIA_AMSTERDAM_TIMESTAMP,
1691 },
1692 ),
1693 (
1694 EthereumHardfork::Amsterdam,
1695 ForkId { hash: ForkHash(hex!("0x6c1d9423")), next: 0 },
1696 ),
1697 ],
1698 );
1699 }
1700
1701 #[test]
1702 fn mainnet_fork_ids() {
1703 test_fork_ids(
1704 &MAINNET,
1705 &[
1706 (
1707 Head { number: 0, ..Default::default() },
1708 ForkId { hash: ForkHash(hex!("0xfc64ec04")), next: 1150000 },
1709 ),
1710 (
1711 Head { number: 1150000, ..Default::default() },
1712 ForkId { hash: ForkHash(hex!("0x97c2c34c")), next: 1920000 },
1713 ),
1714 (
1715 Head { number: 1920000, ..Default::default() },
1716 ForkId { hash: ForkHash(hex!("0x91d1f948")), next: 2463000 },
1717 ),
1718 (
1719 Head { number: 2463000, ..Default::default() },
1720 ForkId { hash: ForkHash(hex!("0x7a64da13")), next: 2675000 },
1721 ),
1722 (
1723 Head { number: 2675000, ..Default::default() },
1724 ForkId { hash: ForkHash(hex!("0x3edd5b10")), next: 4370000 },
1725 ),
1726 (
1727 Head { number: 4370000, ..Default::default() },
1728 ForkId { hash: ForkHash(hex!("0xa00bc324")), next: 7280000 },
1729 ),
1730 (
1731 Head { number: 7280000, ..Default::default() },
1732 ForkId { hash: ForkHash(hex!("0x668db0af")), next: 9069000 },
1733 ),
1734 (
1735 Head { number: 9069000, ..Default::default() },
1736 ForkId { hash: ForkHash(hex!("0x879d6e30")), next: 9200000 },
1737 ),
1738 (
1739 Head { number: 9200000, ..Default::default() },
1740 ForkId { hash: ForkHash(hex!("0xe029e991")), next: 12244000 },
1741 ),
1742 (
1743 Head { number: 12244000, ..Default::default() },
1744 ForkId { hash: ForkHash(hex!("0x0eb440f6")), next: 12965000 },
1745 ),
1746 (
1747 Head { number: 12965000, ..Default::default() },
1748 ForkId { hash: ForkHash(hex!("0xb715077d")), next: 13773000 },
1749 ),
1750 (
1751 Head { number: 13773000, ..Default::default() },
1752 ForkId { hash: ForkHash(hex!("0x20c327fc")), next: 15050000 },
1753 ),
1754 (
1755 Head { number: 15050000, ..Default::default() },
1756 ForkId { hash: ForkHash(hex!("0xf0afd0e3")), next: 1681338455 },
1757 ),
1758 (
1760 Head { number: 20000000, timestamp: 1681338455, ..Default::default() },
1761 ForkId { hash: ForkHash(hex!("0xdce96c2d")), next: 1710338135 },
1762 ),
1763 (
1765 Head { number: 20000001, timestamp: 1710338135, ..Default::default() },
1766 ForkId { hash: ForkHash(hex!("0x9f3d2254")), next: 1746612311 },
1767 ),
1768 (
1770 Head { number: 20000004, timestamp: 1746612311, ..Default::default() },
1771 ForkId {
1772 hash: ForkHash(hex!("0xc376cf8b")),
1773 next: mainnet::MAINNET_OSAKA_TIMESTAMP,
1774 },
1775 ),
1776 (
1778 Head {
1779 number: 20000004,
1780 timestamp: mainnet::MAINNET_OSAKA_TIMESTAMP,
1781 ..Default::default()
1782 },
1783 ForkId {
1784 hash: ForkHash(hex!("0x5167e2a6")),
1785 next: mainnet::MAINNET_BPO1_TIMESTAMP,
1786 },
1787 ),
1788 ],
1789 );
1790 }
1791
1792 #[test]
1793 fn hoodi_fork_ids() {
1794 test_fork_ids(
1795 &HOODI,
1796 &[
1797 (
1798 Head { number: 0, ..Default::default() },
1799 ForkId { hash: ForkHash(hex!("0xbef71d30")), next: 1742999832 },
1800 ),
1801 (
1803 Head { number: 0, timestamp: 1742999833, ..Default::default() },
1804 ForkId {
1805 hash: ForkHash(hex!("0x0929e24e")),
1806 next: hoodi::HOODI_OSAKA_TIMESTAMP,
1807 },
1808 ),
1809 (
1811 Head {
1812 number: 0,
1813 timestamp: hoodi::HOODI_OSAKA_TIMESTAMP,
1814 ..Default::default()
1815 },
1816 ForkId {
1817 hash: ForkHash(hex!("0xe7e0e7ff")),
1818 next: hoodi::HOODI_BPO1_TIMESTAMP,
1819 },
1820 ),
1821 (
1823 Head {
1824 number: 0,
1825 timestamp: hoodi::HOODI_BPO1_TIMESTAMP,
1826 ..Default::default()
1827 },
1828 ForkId {
1829 hash: ForkHash(hex!("0x3893353e")),
1830 next: hoodi::HOODI_BPO2_TIMESTAMP,
1831 },
1832 ),
1833 (
1835 Head {
1836 number: 0,
1837 timestamp: hoodi::HOODI_BPO2_TIMESTAMP,
1838 ..Default::default()
1839 },
1840 ForkId { hash: ForkHash(hex!("0x23aa1351")), next: 0 },
1841 ),
1842 ],
1843 )
1844 }
1845
1846 #[test]
1847 fn holesky_fork_ids() {
1848 test_fork_ids(
1849 &HOLESKY,
1850 &[
1851 (
1852 Head { number: 0, ..Default::default() },
1853 ForkId { hash: ForkHash(hex!("0xc61a6098")), next: 1696000704 },
1854 ),
1855 (
1857 Head { number: 123, ..Default::default() },
1858 ForkId { hash: ForkHash(hex!("0xc61a6098")), next: 1696000704 },
1859 ),
1860 (
1862 Head { number: 123, timestamp: 1696000703, ..Default::default() },
1863 ForkId { hash: ForkHash(hex!("0xc61a6098")), next: 1696000704 },
1864 ),
1865 (
1867 Head { number: 123, timestamp: 1696000704, ..Default::default() },
1868 ForkId { hash: ForkHash(hex!("0xfd4f016b")), next: 1707305664 },
1869 ),
1870 (
1872 Head { number: 123, timestamp: 1707305663, ..Default::default() },
1873 ForkId { hash: ForkHash(hex!("0xfd4f016b")), next: 1707305664 },
1874 ),
1875 (
1877 Head { number: 123, timestamp: 1707305664, ..Default::default() },
1878 ForkId { hash: ForkHash(hex!("0x9b192ad0")), next: 1740434112 },
1879 ),
1880 (
1882 Head { number: 123, timestamp: 1740434111, ..Default::default() },
1883 ForkId { hash: ForkHash(hex!("0x9b192ad0")), next: 1740434112 },
1884 ),
1885 (
1887 Head { number: 123, timestamp: 1740434112, ..Default::default() },
1888 ForkId {
1889 hash: ForkHash(hex!("0xdfbd9bed")),
1890 next: holesky::HOLESKY_OSAKA_TIMESTAMP,
1891 },
1892 ),
1893 (
1895 Head {
1896 number: 123,
1897 timestamp: holesky::HOLESKY_OSAKA_TIMESTAMP,
1898 ..Default::default()
1899 },
1900 ForkId {
1901 hash: ForkHash(hex!("0x783def52")),
1902 next: holesky::HOLESKY_BPO1_TIMESTAMP,
1903 },
1904 ),
1905 ],
1906 )
1907 }
1908
1909 #[test]
1910 fn sepolia_fork_ids() {
1911 test_fork_ids(
1912 &SEPOLIA,
1913 &[
1914 (
1915 Head { number: 0, ..Default::default() },
1916 ForkId { hash: ForkHash(hex!("0xfe3366e7")), next: 1735371 },
1917 ),
1918 (
1919 Head { number: 1735370, ..Default::default() },
1920 ForkId { hash: ForkHash(hex!("0xfe3366e7")), next: 1735371 },
1921 ),
1922 (
1923 Head { number: 1735371, ..Default::default() },
1924 ForkId { hash: ForkHash(hex!("0xb96cbd13")), next: 1677557088 },
1925 ),
1926 (
1927 Head { number: 1735372, timestamp: 1677557087, ..Default::default() },
1928 ForkId { hash: ForkHash(hex!("0xb96cbd13")), next: 1677557088 },
1929 ),
1930 (
1932 Head { number: 1735373, timestamp: 1677557088, ..Default::default() },
1933 ForkId { hash: ForkHash(hex!("0xf7f9bc08")), next: 1706655072 },
1934 ),
1935 (
1937 Head { number: 1735374, timestamp: 1706655071, ..Default::default() },
1938 ForkId { hash: ForkHash(hex!("0xf7f9bc08")), next: 1706655072 },
1939 ),
1940 (
1942 Head { number: 1735375, timestamp: 1706655072, ..Default::default() },
1943 ForkId { hash: ForkHash(hex!("0x88cf81d9")), next: 1741159776 },
1944 ),
1945 (
1947 Head { number: 1735376, timestamp: 1741159775, ..Default::default() },
1948 ForkId { hash: ForkHash(hex!("0x88cf81d9")), next: 1741159776 },
1949 ),
1950 (
1952 Head { number: 1735377, timestamp: 1741159776, ..Default::default() },
1953 ForkId {
1954 hash: ForkHash(hex!("0xed88b5fd")),
1955 next: sepolia::SEPOLIA_OSAKA_TIMESTAMP,
1956 },
1957 ),
1958 (
1960 Head {
1961 number: 1735377,
1962 timestamp: sepolia::SEPOLIA_OSAKA_TIMESTAMP,
1963 ..Default::default()
1964 },
1965 ForkId {
1966 hash: ForkHash(hex!("0xe2ae4999")),
1967 next: sepolia::SEPOLIA_BPO1_TIMESTAMP,
1968 },
1969 ),
1970 (
1972 Head {
1973 number: 1735377,
1974 timestamp: sepolia::SEPOLIA_BPO1_TIMESTAMP,
1975 ..Default::default()
1976 },
1977 ForkId {
1978 hash: ForkHash(hex!("0x56078a1e")),
1979 next: sepolia::SEPOLIA_BPO2_TIMESTAMP,
1980 },
1981 ),
1982 (
1984 Head {
1985 number: 1735377,
1986 timestamp: sepolia::SEPOLIA_BPO2_TIMESTAMP,
1987 ..Default::default()
1988 },
1989 ForkId {
1990 hash: ForkHash(hex!("0x268956b6")),
1991 next: sepolia::SEPOLIA_AMSTERDAM_TIMESTAMP,
1992 },
1993 ),
1994 (
1996 Head {
1997 number: 1735377,
1998 timestamp: sepolia::SEPOLIA_AMSTERDAM_TIMESTAMP - 1,
1999 ..Default::default()
2000 },
2001 ForkId {
2002 hash: ForkHash(hex!("0x268956b6")),
2003 next: sepolia::SEPOLIA_AMSTERDAM_TIMESTAMP,
2004 },
2005 ),
2006 (
2008 Head {
2009 number: 1735377,
2010 timestamp: sepolia::SEPOLIA_AMSTERDAM_TIMESTAMP,
2011 ..Default::default()
2012 },
2013 ForkId { hash: ForkHash(hex!("0x6c1d9423")), next: 0 },
2014 ),
2015 (
2017 Head {
2018 number: 1735377,
2019 timestamp: sepolia::SEPOLIA_AMSTERDAM_TIMESTAMP + 1,
2020 ..Default::default()
2021 },
2022 ForkId { hash: ForkHash(hex!("0x6c1d9423")), next: 0 },
2023 ),
2024 ],
2025 );
2026 }
2027
2028 #[test]
2029 fn dev_fork_ids() {
2030 test_fork_ids(
2031 &DEV,
2032 &[(
2033 Head { number: 0, ..Default::default() },
2034 ForkId { hash: ForkHash(hex!("0x62d16ad0")), next: 0 },
2035 )],
2036 )
2037 }
2038
2039 #[test]
2043 fn timestamped_forks() {
2044 let mainnet_with_timestamps = ChainSpecBuilder::mainnet().build();
2045 test_fork_ids(
2046 &mainnet_with_timestamps,
2047 &[
2048 (
2049 Head { number: 0, timestamp: 0, ..Default::default() },
2050 ForkId { hash: ForkHash(hex!("0xfc64ec04")), next: 1150000 },
2051 ), (
2053 Head { number: 1149999, timestamp: 0, ..Default::default() },
2054 ForkId { hash: ForkHash(hex!("0xfc64ec04")), next: 1150000 },
2055 ), (
2057 Head { number: 1150000, timestamp: 0, ..Default::default() },
2058 ForkId { hash: ForkHash(hex!("0x97c2c34c")), next: 1920000 },
2059 ), (
2061 Head { number: 1919999, timestamp: 0, ..Default::default() },
2062 ForkId { hash: ForkHash(hex!("0x97c2c34c")), next: 1920000 },
2063 ), (
2065 Head { number: 1920000, timestamp: 0, ..Default::default() },
2066 ForkId { hash: ForkHash(hex!("0x91d1f948")), next: 2463000 },
2067 ), (
2069 Head { number: 2462999, timestamp: 0, ..Default::default() },
2070 ForkId { hash: ForkHash(hex!("0x91d1f948")), next: 2463000 },
2071 ), (
2073 Head { number: 2463000, timestamp: 0, ..Default::default() },
2074 ForkId { hash: ForkHash(hex!("0x7a64da13")), next: 2675000 },
2075 ), (
2077 Head { number: 2674999, timestamp: 0, ..Default::default() },
2078 ForkId { hash: ForkHash(hex!("0x7a64da13")), next: 2675000 },
2079 ), (
2081 Head { number: 2675000, timestamp: 0, ..Default::default() },
2082 ForkId { hash: ForkHash(hex!("0x3edd5b10")), next: 4370000 },
2083 ), (
2085 Head { number: 4369999, timestamp: 0, ..Default::default() },
2086 ForkId { hash: ForkHash(hex!("0x3edd5b10")), next: 4370000 },
2087 ), (
2089 Head { number: 4370000, timestamp: 0, ..Default::default() },
2090 ForkId { hash: ForkHash(hex!("0xa00bc324")), next: 7280000 },
2091 ), (
2093 Head { number: 7279999, timestamp: 0, ..Default::default() },
2094 ForkId { hash: ForkHash(hex!("0xa00bc324")), next: 7280000 },
2095 ), (
2097 Head { number: 7280000, timestamp: 0, ..Default::default() },
2098 ForkId { hash: ForkHash(hex!("0x668db0af")), next: 9069000 },
2099 ), (
2101 Head { number: 9068999, timestamp: 0, ..Default::default() },
2102 ForkId { hash: ForkHash(hex!("0x668db0af")), next: 9069000 },
2103 ), (
2105 Head { number: 9069000, timestamp: 0, ..Default::default() },
2106 ForkId { hash: ForkHash(hex!("0x879d6e30")), next: 9200000 },
2107 ), (
2109 Head { number: 9199999, timestamp: 0, ..Default::default() },
2110 ForkId { hash: ForkHash(hex!("0x879d6e30")), next: 9200000 },
2111 ), (
2113 Head { number: 9200000, timestamp: 0, ..Default::default() },
2114 ForkId { hash: ForkHash(hex!("0xe029e991")), next: 12244000 },
2115 ), (
2117 Head { number: 12243999, timestamp: 0, ..Default::default() },
2118 ForkId { hash: ForkHash(hex!("0xe029e991")), next: 12244000 },
2119 ), (
2121 Head { number: 12244000, timestamp: 0, ..Default::default() },
2122 ForkId { hash: ForkHash(hex!("0x0eb440f6")), next: 12965000 },
2123 ), (
2125 Head { number: 12964999, timestamp: 0, ..Default::default() },
2126 ForkId { hash: ForkHash(hex!("0x0eb440f6")), next: 12965000 },
2127 ), (
2129 Head { number: 12965000, timestamp: 0, ..Default::default() },
2130 ForkId { hash: ForkHash(hex!("0xb715077d")), next: 13773000 },
2131 ), (
2133 Head { number: 13772999, timestamp: 0, ..Default::default() },
2134 ForkId { hash: ForkHash(hex!("0xb715077d")), next: 13773000 },
2135 ), (
2137 Head { number: 13773000, timestamp: 0, ..Default::default() },
2138 ForkId { hash: ForkHash(hex!("0x20c327fc")), next: 15050000 },
2139 ), (
2141 Head { number: 15049999, timestamp: 0, ..Default::default() },
2142 ForkId { hash: ForkHash(hex!("0x20c327fc")), next: 15050000 },
2143 ), (
2145 Head { number: 15050000, timestamp: 0, ..Default::default() },
2146 ForkId { hash: ForkHash(hex!("0xf0afd0e3")), next: 1681338455 },
2147 ), (
2149 Head { number: 19999999, timestamp: 1667999999, ..Default::default() },
2150 ForkId { hash: ForkHash(hex!("0xf0afd0e3")), next: 1681338455 },
2151 ), (
2153 Head { number: 20000000, timestamp: 1681338455, ..Default::default() },
2154 ForkId { hash: ForkHash(hex!("0xdce96c2d")), next: 1710338135 },
2155 ), (
2157 Head { number: 20000001, timestamp: 1710338134, ..Default::default() },
2158 ForkId { hash: ForkHash(hex!("0xdce96c2d")), next: 1710338135 },
2159 ), (
2161 Head { number: 20000002, timestamp: 1710338135, ..Default::default() },
2162 ForkId { hash: ForkHash(hex!("0x9f3d2254")), next: 1746612311 },
2163 ), (
2165 Head { number: 20000003, timestamp: 1746612310, ..Default::default() },
2166 ForkId { hash: ForkHash(hex!("0x9f3d2254")), next: 1746612311 },
2167 ), (
2169 Head { number: 20000004, timestamp: 1746612311, ..Default::default() },
2170 ForkId {
2171 hash: ForkHash(hex!("0xc376cf8b")),
2172 next: mainnet::MAINNET_OSAKA_TIMESTAMP,
2173 },
2174 ),
2175 (
2177 Head {
2178 number: 20000004,
2179 timestamp: mainnet::MAINNET_OSAKA_TIMESTAMP,
2180 ..Default::default()
2181 },
2182 ForkId {
2183 hash: ForkHash(hex!("0x5167e2a6")),
2184 next: mainnet::MAINNET_BPO1_TIMESTAMP,
2185 },
2186 ),
2187 ],
2188 );
2189 }
2190
2191 fn construct_chainspec(
2194 builder: ChainSpecBuilder,
2195 shanghai_time: u64,
2196 cancun_time: u64,
2197 ) -> ChainSpec {
2198 builder
2199 .with_fork(EthereumHardfork::Shanghai, ForkCondition::Timestamp(shanghai_time))
2200 .with_fork(EthereumHardfork::Cancun, ForkCondition::Timestamp(cancun_time))
2201 .build()
2202 }
2203
2204 #[test]
2209 fn test_timestamp_fork_in_genesis() {
2210 let timestamp = 1690475657u64;
2211 let default_spec_builder = ChainSpecBuilder::default()
2212 .chain(Chain::from_id(1337))
2213 .genesis(Genesis::default().with_timestamp(timestamp))
2214 .paris_activated();
2215
2216 let tests = [
2219 (
2220 construct_chainspec(default_spec_builder.clone(), timestamp - 1, timestamp + 1),
2221 timestamp + 1,
2222 ),
2223 (
2224 construct_chainspec(default_spec_builder.clone(), timestamp, timestamp + 1),
2225 timestamp + 1,
2226 ),
2227 (
2228 construct_chainspec(default_spec_builder, timestamp + 1, timestamp + 2),
2229 timestamp + 1,
2230 ),
2231 ];
2232
2233 for (spec, expected_timestamp) in tests {
2234 let got_forkid = spec.fork_id(&Head { number: 0, timestamp: 0, ..Default::default() });
2235 let genesis_hash = spec.genesis_hash();
2240 let expected_forkid =
2241 ForkId { hash: ForkHash::from(genesis_hash), next: expected_timestamp };
2242 assert_eq!(got_forkid, expected_forkid);
2243 }
2244 }
2245
2246 #[test]
2248 fn check_terminal_ttd() {
2249 let chainspec = ChainSpecBuilder::mainnet().build();
2250
2251 let terminal_block_ttd = U256::from(58750003716598352816469_u128);
2253 let terminal_block_difficulty = U256::from(11055787484078698_u128);
2254 assert!(!chainspec
2255 .fork(EthereumHardfork::Paris)
2256 .active_at_ttd(terminal_block_ttd, terminal_block_difficulty));
2257
2258 let first_pos_block_ttd = U256::from(58750003716598352816469_u128);
2260 let first_pos_difficulty = U256::ZERO;
2261 assert!(chainspec
2262 .fork(EthereumHardfork::Paris)
2263 .active_at_ttd(first_pos_block_ttd, first_pos_difficulty));
2264 }
2265
2266 #[test]
2267 fn geth_genesis_with_shanghai() {
2268 let geth_genesis = r#"
2269 {
2270 "config": {
2271 "chainId": 1337,
2272 "homesteadBlock": 0,
2273 "eip150Block": 0,
2274 "eip150Hash": "0x0000000000000000000000000000000000000000000000000000000000000000",
2275 "eip155Block": 0,
2276 "eip158Block": 0,
2277 "byzantiumBlock": 0,
2278 "constantinopleBlock": 0,
2279 "petersburgBlock": 0,
2280 "istanbulBlock": 0,
2281 "muirGlacierBlock": 0,
2282 "berlinBlock": 0,
2283 "londonBlock": 0,
2284 "arrowGlacierBlock": 0,
2285 "grayGlacierBlock": 0,
2286 "shanghaiTime": 0,
2287 "cancunTime": 1,
2288 "terminalTotalDifficulty": 0,
2289 "terminalTotalDifficultyPassed": true,
2290 "ethash": {}
2291 },
2292 "nonce": "0x0",
2293 "timestamp": "0x0",
2294 "extraData": "0x",
2295 "gasLimit": "0x4c4b40",
2296 "difficulty": "0x1",
2297 "mixHash": "0x0000000000000000000000000000000000000000000000000000000000000000",
2298 "coinbase": "0x0000000000000000000000000000000000000000",
2299 "alloc": {
2300 "658bdf435d810c91414ec09147daa6db62406379": {
2301 "balance": "0x487a9a304539440000"
2302 },
2303 "aa00000000000000000000000000000000000000": {
2304 "code": "0x6042",
2305 "storage": {
2306 "0x0000000000000000000000000000000000000000000000000000000000000000": "0x0000000000000000000000000000000000000000000000000000000000000000",
2307 "0x0100000000000000000000000000000000000000000000000000000000000000": "0x0100000000000000000000000000000000000000000000000000000000000000",
2308 "0x0200000000000000000000000000000000000000000000000000000000000000": "0x0200000000000000000000000000000000000000000000000000000000000000",
2309 "0x0300000000000000000000000000000000000000000000000000000000000000": "0x0000000000000000000000000000000000000000000000000000000000000303"
2310 },
2311 "balance": "0x1",
2312 "nonce": "0x1"
2313 },
2314 "bb00000000000000000000000000000000000000": {
2315 "code": "0x600154600354",
2316 "storage": {
2317 "0x0000000000000000000000000000000000000000000000000000000000000000": "0x0000000000000000000000000000000000000000000000000000000000000000",
2318 "0x0100000000000000000000000000000000000000000000000000000000000000": "0x0100000000000000000000000000000000000000000000000000000000000000",
2319 "0x0200000000000000000000000000000000000000000000000000000000000000": "0x0200000000000000000000000000000000000000000000000000000000000000",
2320 "0x0300000000000000000000000000000000000000000000000000000000000000": "0x0000000000000000000000000000000000000000000000000000000000000303"
2321 },
2322 "balance": "0x2",
2323 "nonce": "0x1"
2324 }
2325 },
2326 "number": "0x0",
2327 "gasUsed": "0x0",
2328 "parentHash": "0x0000000000000000000000000000000000000000000000000000000000000000",
2329 "baseFeePerGas": "0x3b9aca00"
2330 }
2331 "#;
2332
2333 let genesis: Genesis = serde_json::from_str(geth_genesis).unwrap();
2334 let chainspec = ChainSpec::from(genesis);
2335
2336 assert_eq!(
2338 chainspec.hardforks.get(EthereumHardfork::Homestead).unwrap(),
2339 ForkCondition::Block(0)
2340 );
2341 assert_eq!(
2342 chainspec.hardforks.get(EthereumHardfork::Tangerine).unwrap(),
2343 ForkCondition::Block(0)
2344 );
2345 assert_eq!(
2346 chainspec.hardforks.get(EthereumHardfork::SpuriousDragon).unwrap(),
2347 ForkCondition::Block(0)
2348 );
2349 assert_eq!(
2350 chainspec.hardforks.get(EthereumHardfork::Byzantium).unwrap(),
2351 ForkCondition::Block(0)
2352 );
2353 assert_eq!(
2354 chainspec.hardforks.get(EthereumHardfork::Constantinople).unwrap(),
2355 ForkCondition::Block(0)
2356 );
2357 assert_eq!(
2358 chainspec.hardforks.get(EthereumHardfork::Petersburg).unwrap(),
2359 ForkCondition::Block(0)
2360 );
2361 assert_eq!(
2362 chainspec.hardforks.get(EthereumHardfork::Istanbul).unwrap(),
2363 ForkCondition::Block(0)
2364 );
2365 assert_eq!(
2366 chainspec.hardforks.get(EthereumHardfork::MuirGlacier).unwrap(),
2367 ForkCondition::Block(0)
2368 );
2369 assert_eq!(
2370 chainspec.hardforks.get(EthereumHardfork::Berlin).unwrap(),
2371 ForkCondition::Block(0)
2372 );
2373 assert_eq!(
2374 chainspec.hardforks.get(EthereumHardfork::London).unwrap(),
2375 ForkCondition::Block(0)
2376 );
2377 assert_eq!(
2378 chainspec.hardforks.get(EthereumHardfork::ArrowGlacier).unwrap(),
2379 ForkCondition::Block(0)
2380 );
2381 assert_eq!(
2382 chainspec.hardforks.get(EthereumHardfork::GrayGlacier).unwrap(),
2383 ForkCondition::Block(0)
2384 );
2385
2386 assert_eq!(
2388 chainspec.hardforks.get(EthereumHardfork::Shanghai).unwrap(),
2389 ForkCondition::Timestamp(0)
2390 );
2391
2392 assert_eq!(
2394 chainspec.hardforks.get(EthereumHardfork::Cancun).unwrap(),
2395 ForkCondition::Timestamp(1)
2396 );
2397
2398 let key_rlp = vec![
2400 (
2401 hex!("0x658bdf435d810c91414ec09147daa6db62406379"),
2402 &hex!(
2403 "0xf84d8089487a9a304539440000a056e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421a0c5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470"
2404 )[..],
2405 ),
2406 (
2407 hex!("0xaa00000000000000000000000000000000000000"),
2408 &hex!(
2409 "0xf8440101a08afc95b7d18a226944b9c2070b6bda1c3a36afcc3730429d47579c94b9fe5850a0ce92c756baff35fa740c3557c1a971fd24d2d35b7c8e067880d50cd86bb0bc99"
2410 )[..],
2411 ),
2412 (
2413 hex!("0xbb00000000000000000000000000000000000000"),
2414 &hex!(
2415 "0xf8440102a08afc95b7d18a226944b9c2070b6bda1c3a36afcc3730429d47579c94b9fe5850a0e25a53cbb501cec2976b393719c63d832423dd70a458731a0b64e4847bbca7d2"
2416 )[..],
2417 ),
2418 ];
2419
2420 for (key, expected_rlp) in key_rlp {
2421 let account = chainspec.genesis.alloc.get(&key).expect("account should exist");
2422 assert_eq!(&alloy_rlp::encode(TrieAccount::from(account.clone())), expected_rlp);
2423 }
2424
2425 let expected_state_root: B256 =
2426 b256!("0x078dc6061b1d8eaa8493384b59c9c65ceb917201221d08b80c4de6770b6ec7e7");
2427 assert_eq!(chainspec.genesis_header().state_root, expected_state_root);
2428
2429 assert_eq!(chainspec.genesis_header().withdrawals_root, Some(EMPTY_ROOT_HASH));
2430
2431 let expected_hash: B256 =
2432 b256!("0x1fc027d65f820d3eef441ebeec139ebe09e471cf98516dce7b5643ccb27f418c");
2433 let hash = chainspec.genesis_hash();
2434 assert_eq!(hash, expected_hash);
2435 }
2436
2437 #[test]
2438 fn hive_geth_json() {
2439 let hive_json = r#"
2440 {
2441 "nonce": "0x0000000000000042",
2442 "difficulty": "0x2123456",
2443 "mixHash": "0x123456789abcdef123456789abcdef123456789abcdef123456789abcdef1234",
2444 "coinbase": "0xaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
2445 "timestamp": "0x123456",
2446 "parentHash": "0x0000000000000000000000000000000000000000000000000000000000000000",
2447 "extraData": "0xfafbfcfd",
2448 "gasLimit": "0x2fefd8",
2449 "alloc": {
2450 "dbdbdb2cbd23b783741e8d7fcf51e459b497e4a6": {
2451 "balance": "0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"
2452 },
2453 "e6716f9544a56c530d868e4bfbacb172315bdead": {
2454 "balance": "0x11",
2455 "code": "0x12"
2456 },
2457 "b9c015918bdaba24b4ff057a92a3873d6eb201be": {
2458 "balance": "0x21",
2459 "storage": {
2460 "0x0000000000000000000000000000000000000000000000000000000000000001": "0x22"
2461 }
2462 },
2463 "1a26338f0d905e295fccb71fa9ea849ffa12aaf4": {
2464 "balance": "0x31",
2465 "nonce": "0x32"
2466 },
2467 "0000000000000000000000000000000000000001": {
2468 "balance": "0x41"
2469 },
2470 "0000000000000000000000000000000000000002": {
2471 "balance": "0x51"
2472 },
2473 "0000000000000000000000000000000000000003": {
2474 "balance": "0x61"
2475 },
2476 "0000000000000000000000000000000000000004": {
2477 "balance": "0x71"
2478 }
2479 },
2480 "config": {
2481 "ethash": {},
2482 "chainId": 10,
2483 "homesteadBlock": 0,
2484 "eip150Block": 0,
2485 "eip155Block": 0,
2486 "eip158Block": 0,
2487 "byzantiumBlock": 0,
2488 "constantinopleBlock": 0,
2489 "petersburgBlock": 0,
2490 "istanbulBlock": 0
2491 }
2492 }
2493 "#;
2494
2495 let genesis = serde_json::from_str::<Genesis>(hive_json).unwrap();
2496 let chainspec: ChainSpec = genesis.into();
2497 assert_eq!(chainspec.chain, Chain::optimism_mainnet());
2498 let expected_state_root: B256 =
2499 b256!("0x9a6049ac535e3dc7436c189eaa81c73f35abd7f282ab67c32944ff0301d63360");
2500 assert_eq!(chainspec.genesis_header().state_root, expected_state_root);
2501 let hard_forks = vec![
2502 EthereumHardfork::Byzantium,
2503 EthereumHardfork::Homestead,
2504 EthereumHardfork::Istanbul,
2505 EthereumHardfork::Petersburg,
2506 EthereumHardfork::Constantinople,
2507 ];
2508 for fork in hard_forks {
2509 assert_eq!(chainspec.hardforks.get(fork).unwrap(), ForkCondition::Block(0));
2510 }
2511
2512 let expected_hash: B256 =
2513 b256!("0x5ae31c6522bd5856129f66be3d582b842e4e9faaa87f21cce547128339a9db3c");
2514 let hash = chainspec.genesis_header().hash_slow();
2515 assert_eq!(hash, expected_hash);
2516 }
2517
2518 #[test]
2519 fn test_hive_paris_block_genesis_json() {
2520 let hive_paris = r#"
2523 {
2524 "config": {
2525 "ethash": {},
2526 "chainId": 3503995874084926,
2527 "homesteadBlock": 0,
2528 "eip150Block": 6,
2529 "eip155Block": 12,
2530 "eip158Block": 12,
2531 "byzantiumBlock": 18,
2532 "constantinopleBlock": 24,
2533 "petersburgBlock": 30,
2534 "istanbulBlock": 36,
2535 "muirGlacierBlock": 42,
2536 "berlinBlock": 48,
2537 "londonBlock": 54,
2538 "arrowGlacierBlock": 60,
2539 "grayGlacierBlock": 66,
2540 "mergeNetsplitBlock": 72,
2541 "terminalTotalDifficulty": 9454784,
2542 "shanghaiTime": 780,
2543 "cancunTime": 840
2544 },
2545 "nonce": "0x0",
2546 "timestamp": "0x0",
2547 "extraData": "0x68697665636861696e",
2548 "gasLimit": "0x23f3e20",
2549 "difficulty": "0x20000",
2550 "mixHash": "0x0000000000000000000000000000000000000000000000000000000000000000",
2551 "coinbase": "0x0000000000000000000000000000000000000000",
2552 "alloc": {
2553 "000f3df6d732807ef1319fb7b8bb8522d0beac02": {
2554 "code": "0x3373fffffffffffffffffffffffffffffffffffffffe14604d57602036146024575f5ffd5b5f35801560495762001fff810690815414603c575f5ffd5b62001fff01545f5260205ff35b5f5ffd5b62001fff42064281555f359062001fff015500",
2555 "balance": "0x2a"
2556 },
2557 "0c2c51a0990aee1d73c1228de158688341557508": {
2558 "balance": "0xc097ce7bc90715b34b9f1000000000"
2559 },
2560 "14e46043e63d0e3cdcf2530519f4cfaf35058cb2": {
2561 "balance": "0xc097ce7bc90715b34b9f1000000000"
2562 },
2563 "16c57edf7fa9d9525378b0b81bf8a3ced0620c1c": {
2564 "balance": "0xc097ce7bc90715b34b9f1000000000"
2565 },
2566 "1f4924b14f34e24159387c0a4cdbaa32f3ddb0cf": {
2567 "balance": "0xc097ce7bc90715b34b9f1000000000"
2568 },
2569 "1f5bde34b4afc686f136c7a3cb6ec376f7357759": {
2570 "balance": "0xc097ce7bc90715b34b9f1000000000"
2571 },
2572 "2d389075be5be9f2246ad654ce152cf05990b209": {
2573 "balance": "0xc097ce7bc90715b34b9f1000000000"
2574 },
2575 "3ae75c08b4c907eb63a8960c45b86e1e9ab6123c": {
2576 "balance": "0xc097ce7bc90715b34b9f1000000000"
2577 },
2578 "4340ee1b812acb40a1eb561c019c327b243b92df": {
2579 "balance": "0xc097ce7bc90715b34b9f1000000000"
2580 },
2581 "4a0f1452281bcec5bd90c3dce6162a5995bfe9df": {
2582 "balance": "0xc097ce7bc90715b34b9f1000000000"
2583 },
2584 "4dde844b71bcdf95512fb4dc94e84fb67b512ed8": {
2585 "balance": "0xc097ce7bc90715b34b9f1000000000"
2586 },
2587 "5f552da00dfb4d3749d9e62dcee3c918855a86a0": {
2588 "balance": "0xc097ce7bc90715b34b9f1000000000"
2589 },
2590 "654aa64f5fbefb84c270ec74211b81ca8c44a72e": {
2591 "balance": "0xc097ce7bc90715b34b9f1000000000"
2592 },
2593 "717f8aa2b982bee0e29f573d31df288663e1ce16": {
2594 "balance": "0xc097ce7bc90715b34b9f1000000000"
2595 },
2596 "7435ed30a8b4aeb0877cef0c6e8cffe834eb865f": {
2597 "balance": "0xc097ce7bc90715b34b9f1000000000"
2598 },
2599 "83c7e323d189f18725ac510004fdc2941f8c4a78": {
2600 "balance": "0xc097ce7bc90715b34b9f1000000000"
2601 },
2602 "84e75c28348fb86acea1a93a39426d7d60f4cc46": {
2603 "balance": "0xc097ce7bc90715b34b9f1000000000"
2604 },
2605 "8bebc8ba651aee624937e7d897853ac30c95a067": {
2606 "storage": {
2607 "0x0000000000000000000000000000000000000000000000000000000000000001": "0x0000000000000000000000000000000000000000000000000000000000000001",
2608 "0x0000000000000000000000000000000000000000000000000000000000000002": "0x0000000000000000000000000000000000000000000000000000000000000002",
2609 "0x0000000000000000000000000000000000000000000000000000000000000003": "0x0000000000000000000000000000000000000000000000000000000000000003"
2610 },
2611 "balance": "0x1",
2612 "nonce": "0x1"
2613 },
2614 "c7b99a164efd027a93f147376cc7da7c67c6bbe0": {
2615 "balance": "0xc097ce7bc90715b34b9f1000000000"
2616 },
2617 "d803681e487e6ac18053afc5a6cd813c86ec3e4d": {
2618 "balance": "0xc097ce7bc90715b34b9f1000000000"
2619 },
2620 "e7d13f7aa2a838d24c59b40186a0aca1e21cffcc": {
2621 "balance": "0xc097ce7bc90715b34b9f1000000000"
2622 },
2623 "eda8645ba6948855e3b3cd596bbb07596d59c603": {
2624 "balance": "0xc097ce7bc90715b34b9f1000000000"
2625 }
2626 },
2627 "number": "0x0",
2628 "gasUsed": "0x0",
2629 "parentHash": "0x0000000000000000000000000000000000000000000000000000000000000000",
2630 "baseFeePerGas": null,
2631 "excessBlobGas": null,
2632 "blobGasUsed": null
2633 }
2634 "#;
2635
2636 let genesis: Genesis = serde_json::from_str(hive_paris).unwrap();
2638 let chainspec = ChainSpec::from(genesis);
2639
2640 let expected_forkid = ForkId { hash: ForkHash(hex!("0xbc0c2605")), next: 0 };
2642 let got_forkid =
2643 chainspec.fork_id(&Head { number: 73, timestamp: 840, ..Default::default() });
2644
2645 assert_eq!(got_forkid, expected_forkid);
2647 assert_eq!(chainspec.paris_block_and_final_difficulty, Some((72, U256::from(9454784))));
2649 }
2650
2651 #[test]
2652 fn test_parse_genesis_json() {
2653 let s = r#"{"config":{"ethash":{},"chainId":1337,"homesteadBlock":0,"eip150Block":0,"eip155Block":0,"eip158Block":0,"byzantiumBlock":0,"constantinopleBlock":0,"petersburgBlock":0,"istanbulBlock":0,"berlinBlock":0,"londonBlock":0,"terminalTotalDifficulty":0,"terminalTotalDifficultyPassed":true,"shanghaiTime":0},"nonce":"0x0","timestamp":"0x0","extraData":"0x","gasLimit":"0x4c4b40","difficulty":"0x1","mixHash":"0x0000000000000000000000000000000000000000000000000000000000000000","coinbase":"0x0000000000000000000000000000000000000000","alloc":{"658bdf435d810c91414ec09147daa6db62406379":{"balance":"0x487a9a304539440000"},"aa00000000000000000000000000000000000000":{"code":"0x6042","storage":{"0x0000000000000000000000000000000000000000000000000000000000000000":"0x0000000000000000000000000000000000000000000000000000000000000000","0x0100000000000000000000000000000000000000000000000000000000000000":"0x0100000000000000000000000000000000000000000000000000000000000000","0x0200000000000000000000000000000000000000000000000000000000000000":"0x0200000000000000000000000000000000000000000000000000000000000000","0x0300000000000000000000000000000000000000000000000000000000000000":"0x0000000000000000000000000000000000000000000000000000000000000303"},"balance":"0x1","nonce":"0x1"},"bb00000000000000000000000000000000000000":{"code":"0x600154600354","storage":{"0x0000000000000000000000000000000000000000000000000000000000000000":"0x0000000000000000000000000000000000000000000000000000000000000000","0x0100000000000000000000000000000000000000000000000000000000000000":"0x0100000000000000000000000000000000000000000000000000000000000000","0x0200000000000000000000000000000000000000000000000000000000000000":"0x0200000000000000000000000000000000000000000000000000000000000000","0x0300000000000000000000000000000000000000000000000000000000000000":"0x0000000000000000000000000000000000000000000000000000000000000303"},"balance":"0x2","nonce":"0x1"}},"number":"0x0","gasUsed":"0x0","parentHash":"0x0000000000000000000000000000000000000000000000000000000000000000","baseFeePerGas":"0x1337"}"#;
2654 let genesis: Genesis = serde_json::from_str(s).unwrap();
2655 let acc =
2656 genesis.alloc.get(&address!("0xaa00000000000000000000000000000000000000")).unwrap();
2657 assert_eq!(acc.balance, U256::ONE);
2658 assert_eq!(genesis.base_fee_per_gas, Some(0x1337));
2659 }
2660
2661 #[test]
2662 fn test_parse_cancun_genesis_json() {
2663 let s = r#"{"config":{"ethash":{},"chainId":1337,"homesteadBlock":0,"eip150Block":0,"eip155Block":0,"eip158Block":0,"byzantiumBlock":0,"constantinopleBlock":0,"petersburgBlock":0,"istanbulBlock":0,"berlinBlock":0,"londonBlock":0,"terminalTotalDifficulty":0,"terminalTotalDifficultyPassed":true,"shanghaiTime":0,"cancunTime":4661},"nonce":"0x0","timestamp":"0x0","extraData":"0x","gasLimit":"0x4c4b40","difficulty":"0x1","mixHash":"0x0000000000000000000000000000000000000000000000000000000000000000","coinbase":"0x0000000000000000000000000000000000000000","alloc":{"658bdf435d810c91414ec09147daa6db62406379":{"balance":"0x487a9a304539440000"},"aa00000000000000000000000000000000000000":{"code":"0x6042","storage":{"0x0000000000000000000000000000000000000000000000000000000000000000":"0x0000000000000000000000000000000000000000000000000000000000000000","0x0100000000000000000000000000000000000000000000000000000000000000":"0x0100000000000000000000000000000000000000000000000000000000000000","0x0200000000000000000000000000000000000000000000000000000000000000":"0x0200000000000000000000000000000000000000000000000000000000000000","0x0300000000000000000000000000000000000000000000000000000000000000":"0x0000000000000000000000000000000000000000000000000000000000000303"},"balance":"0x1","nonce":"0x1"},"bb00000000000000000000000000000000000000":{"code":"0x600154600354","storage":{"0x0000000000000000000000000000000000000000000000000000000000000000":"0x0000000000000000000000000000000000000000000000000000000000000000","0x0100000000000000000000000000000000000000000000000000000000000000":"0x0100000000000000000000000000000000000000000000000000000000000000","0x0200000000000000000000000000000000000000000000000000000000000000":"0x0200000000000000000000000000000000000000000000000000000000000000","0x0300000000000000000000000000000000000000000000000000000000000000":"0x0000000000000000000000000000000000000000000000000000000000000303"},"balance":"0x2","nonce":"0x1"}},"number":"0x0","gasUsed":"0x0","parentHash":"0x0000000000000000000000000000000000000000000000000000000000000000","baseFeePerGas":"0x3b9aca00"}"#;
2664 let genesis: Genesis = serde_json::from_str(s).unwrap();
2665 let acc =
2666 genesis.alloc.get(&address!("0xaa00000000000000000000000000000000000000")).unwrap();
2667 assert_eq!(acc.balance, U256::ONE);
2668 assert_eq!(genesis.config.cancun_time, Some(4661));
2670 }
2671
2672 #[test]
2673 fn test_parse_prague_genesis_all_formats() {
2674 let s = r#"{"config":{"ethash":{},"chainId":1337,"homesteadBlock":0,"eip150Block":0,"eip155Block":0,"eip158Block":0,"byzantiumBlock":0,"constantinopleBlock":0,"petersburgBlock":0,"istanbulBlock":0,"berlinBlock":0,"londonBlock":0,"terminalTotalDifficulty":0,"terminalTotalDifficultyPassed":true,"shanghaiTime":0,"cancunTime":4661, "pragueTime": 4662},"nonce":"0x0","timestamp":"0x0","extraData":"0x","gasLimit":"0x4c4b40","difficulty":"0x1","mixHash":"0x0000000000000000000000000000000000000000000000000000000000000000","coinbase":"0x0000000000000000000000000000000000000000","alloc":{"658bdf435d810c91414ec09147daa6db62406379":{"balance":"0x487a9a304539440000"},"aa00000000000000000000000000000000000000":{"code":"0x6042","storage":{"0x0000000000000000000000000000000000000000000000000000000000000000":"0x0000000000000000000000000000000000000000000000000000000000000000","0x0100000000000000000000000000000000000000000000000000000000000000":"0x0100000000000000000000000000000000000000000000000000000000000000","0x0200000000000000000000000000000000000000000000000000000000000000":"0x0200000000000000000000000000000000000000000000000000000000000000","0x0300000000000000000000000000000000000000000000000000000000000000":"0x0000000000000000000000000000000000000000000000000000000000000303"},"balance":"0x1","nonce":"0x1"},"bb00000000000000000000000000000000000000":{"code":"0x600154600354","storage":{"0x0000000000000000000000000000000000000000000000000000000000000000":"0x0000000000000000000000000000000000000000000000000000000000000000","0x0100000000000000000000000000000000000000000000000000000000000000":"0x0100000000000000000000000000000000000000000000000000000000000000","0x0200000000000000000000000000000000000000000000000000000000000000":"0x0200000000000000000000000000000000000000000000000000000000000000","0x0300000000000000000000000000000000000000000000000000000000000000":"0x0000000000000000000000000000000000000000000000000000000000000303"},"balance":"0x2","nonce":"0x1"}},"number":"0x0","gasUsed":"0x0","parentHash":"0x0000000000000000000000000000000000000000000000000000000000000000","baseFeePerGas":"0x3b9aca00"}"#;
2675 let genesis: Genesis = serde_json::from_str(s).unwrap();
2676
2677 let acc =
2679 genesis.alloc.get(&address!("0xaa00000000000000000000000000000000000000")).unwrap();
2680 assert_eq!(acc.balance, U256::ONE);
2681 assert_eq!(genesis.config.cancun_time, Some(4661));
2683 assert_eq!(genesis.config.prague_time, Some(4662));
2685 }
2686
2687 #[test]
2688 fn test_parse_cancun_genesis_all_formats() {
2689 let s = r#"{"config":{"ethash":{},"chainId":1337,"homesteadBlock":0,"eip150Block":0,"eip155Block":0,"eip158Block":0,"byzantiumBlock":0,"constantinopleBlock":0,"petersburgBlock":0,"istanbulBlock":0,"berlinBlock":0,"londonBlock":0,"terminalTotalDifficulty":0,"terminalTotalDifficultyPassed":true,"shanghaiTime":0,"cancunTime":4661},"nonce":"0x0","timestamp":"0x0","extraData":"0x","gasLimit":"0x4c4b40","difficulty":"0x1","mixHash":"0x0000000000000000000000000000000000000000000000000000000000000000","coinbase":"0x0000000000000000000000000000000000000000","alloc":{"658bdf435d810c91414ec09147daa6db62406379":{"balance":"0x487a9a304539440000"},"aa00000000000000000000000000000000000000":{"code":"0x6042","storage":{"0x0000000000000000000000000000000000000000000000000000000000000000":"0x0000000000000000000000000000000000000000000000000000000000000000","0x0100000000000000000000000000000000000000000000000000000000000000":"0x0100000000000000000000000000000000000000000000000000000000000000","0x0200000000000000000000000000000000000000000000000000000000000000":"0x0200000000000000000000000000000000000000000000000000000000000000","0x0300000000000000000000000000000000000000000000000000000000000000":"0x0000000000000000000000000000000000000000000000000000000000000303"},"balance":"0x1","nonce":"0x1"},"bb00000000000000000000000000000000000000":{"code":"0x600154600354","storage":{"0x0000000000000000000000000000000000000000000000000000000000000000":"0x0000000000000000000000000000000000000000000000000000000000000000","0x0100000000000000000000000000000000000000000000000000000000000000":"0x0100000000000000000000000000000000000000000000000000000000000000","0x0200000000000000000000000000000000000000000000000000000000000000":"0x0200000000000000000000000000000000000000000000000000000000000000","0x0300000000000000000000000000000000000000000000000000000000000000":"0x0000000000000000000000000000000000000000000000000000000000000303"},"balance":"0x2","nonce":"0x1"}},"number":"0x0","gasUsed":"0x0","parentHash":"0x0000000000000000000000000000000000000000000000000000000000000000","baseFeePerGas":"0x3b9aca00"}"#;
2690 let genesis: Genesis = serde_json::from_str(s).unwrap();
2691
2692 let acc =
2694 genesis.alloc.get(&address!("0xaa00000000000000000000000000000000000000")).unwrap();
2695 assert_eq!(acc.balance, U256::ONE);
2696 assert_eq!(genesis.config.cancun_time, Some(4661));
2698 }
2699
2700 #[test]
2701 fn test_paris_block_and_total_difficulty() {
2702 let genesis = Genesis { gas_limit: 0x2fefd8u64, ..Default::default() };
2703 let paris_chainspec = ChainSpecBuilder::default()
2704 .chain(Chain::from_id(1337))
2705 .genesis(genesis)
2706 .paris_activated()
2707 .build();
2708 assert_eq!(paris_chainspec.paris_block_and_final_difficulty, Some((0, U256::ZERO)));
2709 }
2710
2711 #[test]
2712 fn test_default_cancun_header_forkhash() {
2713 let genesis = Genesis { gas_limit: 0x2fefd8u64, ..Default::default() };
2715 let default_chainspec = ChainSpecBuilder::default()
2716 .chain(Chain::from_id(1337))
2717 .genesis(genesis)
2718 .cancun_activated()
2719 .build();
2720 let mut header = default_chainspec.genesis_header().clone();
2721
2722 header.state_root =
2724 b256!("0x62e2595e017f0ca23e08d17221010721a71c3ae932f4ea3cb12117786bb392d4");
2725
2726 assert_eq!(header.withdrawals_root, Some(EMPTY_WITHDRAWALS));
2728
2729 assert_eq!(header.parent_beacon_block_root, Some(B256::ZERO));
2732 assert_eq!(header.blob_gas_used, Some(0));
2733 assert_eq!(header.excess_blob_gas, Some(0));
2734
2735 let genesis_hash = header.hash_slow();
2737 let expected_hash =
2738 b256!("0x16bb7c59613a5bad3f7c04a852fd056545ade2483968d9a25a1abb05af0c4d37");
2739 assert_eq!(genesis_hash, expected_hash);
2740
2741 let expected_forkhash = ForkHash(hex!("0x8062457a"));
2743 assert_eq!(ForkHash::from(genesis_hash), expected_forkhash);
2744 }
2745
2746 #[test]
2747 fn test_amsterdam_genesis_slot_number() {
2748 let genesis =
2750 Genesis { gas_limit: 0x2fefd8u64, ..Default::default() }.with_slot_number(Some(999));
2751 let chainspec = ChainSpecBuilder::default()
2752 .chain(Chain::from_id(1337))
2753 .genesis(genesis)
2754 .amsterdam_activated()
2755 .build();
2756 assert_eq!(chainspec.genesis_header().slot_number, Some(999));
2757
2758 let genesis = Genesis { gas_limit: 0x2fefd8u64, ..Default::default() };
2760 let chainspec = ChainSpecBuilder::default()
2761 .chain(Chain::from_id(1337))
2762 .genesis(genesis)
2763 .amsterdam_activated()
2764 .build();
2765 assert_eq!(chainspec.genesis_header().slot_number, Some(0));
2766 }
2767
2768 #[test]
2769 fn holesky_paris_activated_at_genesis() {
2770 assert!(HOLESKY
2771 .fork(EthereumHardfork::Paris)
2772 .active_at_ttd(HOLESKY.genesis.difficulty, HOLESKY.genesis.difficulty));
2773 }
2774
2775 #[test]
2776 fn test_genesis_format_deserialization() {
2777 let config = ChainConfig {
2779 chain_id: 2600,
2780 homestead_block: Some(0),
2781 eip150_block: Some(0),
2782 eip155_block: Some(0),
2783 eip158_block: Some(0),
2784 byzantium_block: Some(0),
2785 constantinople_block: Some(0),
2786 petersburg_block: Some(0),
2787 istanbul_block: Some(0),
2788 berlin_block: Some(0),
2789 london_block: Some(0),
2790 shanghai_time: Some(0),
2791 terminal_total_difficulty: Some(U256::ZERO),
2792 terminal_total_difficulty_passed: true,
2793 ..Default::default()
2794 };
2795 let genesis = Genesis {
2797 config,
2798 nonce: 0,
2799 timestamp: 1698688670,
2800 gas_limit: 5000,
2801 difficulty: U256::ZERO,
2802 mix_hash: B256::ZERO,
2803 coinbase: Address::ZERO,
2804 ..Default::default()
2805 };
2806
2807 let address = address!("0x6Be02d1d3665660d22FF9624b7BE0551ee1Ac91b");
2809 let account = GenesisAccount::default().with_balance(U256::from(33));
2810 let genesis = genesis.extend_accounts(HashMap::from([(address, account)]));
2811
2812 let serialized_genesis = serde_json::to_string(&genesis).unwrap();
2814 let deserialized_genesis: Genesis = serde_json::from_str(&serialized_genesis).unwrap();
2815
2816 assert_eq!(genesis, deserialized_genesis);
2817 }
2818
2819 #[test]
2820 fn check_fork_id_chainspec_with_fork_condition_never() {
2821 let spec: ChainSpec = ChainSpec {
2822 chain: Chain::mainnet(),
2823 genesis: Genesis::default(),
2824 hardforks: ChainHardforks::new(vec![(
2825 EthereumHardfork::Frontier.boxed(),
2826 ForkCondition::Never,
2827 )]),
2828 paris_block_and_final_difficulty: None,
2829 deposit_contract: None,
2830 ..Default::default()
2831 };
2832
2833 assert_eq!(spec.hardfork_fork_id(EthereumHardfork::Frontier), None);
2834 }
2835
2836 #[test]
2837 fn check_fork_filter_chainspec_with_fork_condition_never() {
2838 let spec: ChainSpec = ChainSpec {
2839 chain: Chain::mainnet(),
2840 genesis: Genesis::default(),
2841 hardforks: ChainHardforks::new(vec![(
2842 EthereumHardfork::Shanghai.boxed(),
2843 ForkCondition::Never,
2844 )]),
2845 paris_block_and_final_difficulty: None,
2846 deposit_contract: None,
2847 ..Default::default()
2848 };
2849
2850 assert_eq!(spec.hardfork_fork_filter(EthereumHardfork::Shanghai), None);
2851 }
2852
2853 #[test]
2854 fn latest_eth_mainnet_fork_id() {
2855 assert_eq!(ForkId { hash: ForkHash(hex!("0x07c9462e")), next: 0 }, MAINNET.latest_fork_id())
2857 }
2858
2859 #[test]
2860 fn latest_hoodi_mainnet_fork_id() {
2861 assert_eq!(ForkId { hash: ForkHash(hex!("0x23aa1351")), next: 0 }, HOODI.latest_fork_id())
2863 }
2864
2865 #[test]
2866 fn latest_holesky_mainnet_fork_id() {
2867 assert_eq!(ForkId { hash: ForkHash(hex!("0x9bc6cb31")), next: 0 }, HOLESKY.latest_fork_id())
2869 }
2870
2871 #[test]
2872 fn latest_sepolia_mainnet_fork_id() {
2873 assert_eq!(ForkId { hash: ForkHash(hex!("0x6c1d9423")), next: 0 }, SEPOLIA.latest_fork_id())
2875 }
2876
2877 #[test]
2878 fn test_fork_order_ethereum_mainnet() {
2879 let genesis = Genesis {
2880 config: ChainConfig {
2881 chain_id: 0,
2882 homestead_block: Some(0),
2883 dao_fork_block: Some(0),
2884 dao_fork_support: false,
2885 eip150_block: Some(0),
2886 eip155_block: Some(0),
2887 eip158_block: Some(0),
2888 byzantium_block: Some(0),
2889 constantinople_block: Some(0),
2890 petersburg_block: Some(0),
2891 istanbul_block: Some(0),
2892 muir_glacier_block: Some(0),
2893 berlin_block: Some(0),
2894 london_block: Some(0),
2895 arrow_glacier_block: Some(0),
2896 gray_glacier_block: Some(0),
2897 merge_netsplit_block: Some(0),
2898 shanghai_time: Some(0),
2899 cancun_time: Some(0),
2900 terminal_total_difficulty: Some(U256::ZERO),
2901 ..Default::default()
2902 },
2903 ..Default::default()
2904 };
2905
2906 let chain_spec: ChainSpec = genesis.into();
2907
2908 let hardforks: Vec<_> = chain_spec.hardforks.forks_iter().map(|(h, _)| h).collect();
2909 let expected_hardforks = vec![
2910 EthereumHardfork::Frontier.boxed(),
2911 EthereumHardfork::Homestead.boxed(),
2912 EthereumHardfork::Dao.boxed(),
2913 EthereumHardfork::Tangerine.boxed(),
2914 EthereumHardfork::SpuriousDragon.boxed(),
2915 EthereumHardfork::Byzantium.boxed(),
2916 EthereumHardfork::Constantinople.boxed(),
2917 EthereumHardfork::Petersburg.boxed(),
2918 EthereumHardfork::Istanbul.boxed(),
2919 EthereumHardfork::MuirGlacier.boxed(),
2920 EthereumHardfork::Berlin.boxed(),
2921 EthereumHardfork::London.boxed(),
2922 EthereumHardfork::ArrowGlacier.boxed(),
2923 EthereumHardfork::GrayGlacier.boxed(),
2924 EthereumHardfork::Paris.boxed(),
2925 EthereumHardfork::Shanghai.boxed(),
2926 EthereumHardfork::Cancun.boxed(),
2927 ];
2928
2929 assert!(expected_hardforks
2930 .iter()
2931 .zip(hardforks.iter())
2932 .all(|(expected, actual)| &**expected == *actual));
2933 assert_eq!(expected_hardforks.len(), hardforks.len());
2934 }
2935
2936 #[test]
2937 fn blob_params_from_genesis() {
2938 let s = r#"{
2939 "blobSchedule": {
2940 "cancun":{
2941 "baseFeeUpdateFraction":3338477,
2942 "max":6,
2943 "target":3
2944 },
2945 "prague":{
2946 "baseFeeUpdateFraction":3338477,
2947 "max":6,
2948 "target":3
2949 }
2950 }
2951 }"#;
2952 let config: ChainConfig = serde_json::from_str(s).unwrap();
2953 let hardfork_params = config.blob_schedule_blob_params();
2954 let expected = BlobScheduleBlobParams {
2955 cancun: BlobParams {
2956 target_blob_count: 3,
2957 max_blob_count: 6,
2958 update_fraction: 3338477,
2959 min_blob_fee: BLOB_TX_MIN_BLOB_GASPRICE,
2960 max_blobs_per_tx: 6,
2961 blob_base_cost: 0,
2962 },
2963 prague: BlobParams {
2964 target_blob_count: 3,
2965 max_blob_count: 6,
2966 update_fraction: 3338477,
2967 min_blob_fee: BLOB_TX_MIN_BLOB_GASPRICE,
2968 max_blobs_per_tx: 6,
2969 blob_base_cost: 0,
2970 },
2971 ..Default::default()
2972 };
2973 assert_eq!(hardfork_params, expected);
2974 }
2975
2976 #[test]
2977 fn parse_perf_net_genesis() {
2978 let s = r#"{
2979 "config": {
2980 "chainId": 1,
2981 "homesteadBlock": 1150000,
2982 "daoForkBlock": 1920000,
2983 "daoForkSupport": true,
2984 "eip150Block": 2463000,
2985 "eip150Hash": "0x2086799aeebeae135c246c65021c82b4e15a2c451340993aacfd2751886514f0",
2986 "eip155Block": 2675000,
2987 "eip158Block": 2675000,
2988 "byzantiumBlock": 4370000,
2989 "constantinopleBlock": 7280000,
2990 "petersburgBlock": 7280000,
2991 "istanbulBlock": 9069000,
2992 "muirGlacierBlock": 9200000,
2993 "berlinBlock": 12244000,
2994 "londonBlock": 12965000,
2995 "arrowGlacierBlock": 13773000,
2996 "grayGlacierBlock": 15050000,
2997 "terminalTotalDifficulty": 58750000000000000000000,
2998 "terminalTotalDifficultyPassed": true,
2999 "shanghaiTime": 1681338455,
3000 "cancunTime": 1710338135,
3001 "pragueTime": 1746612311,
3002 "ethash": {},
3003 "depositContractAddress": "0x00000000219ab540356cBB839Cbe05303d7705Fa",
3004 "blobSchedule": {
3005 "cancun": {
3006 "target": 3,
3007 "max": 6,
3008 "baseFeeUpdateFraction": 3338477
3009 },
3010 "prague": {
3011 "target": 6,
3012 "max": 9,
3013 "baseFeeUpdateFraction": 5007716
3014 }
3015 }
3016 },
3017 "nonce": "0x42",
3018 "timestamp": "0x0",
3019 "extraData": "0x11bbe8db4e347b4e8c937c1c8370e4b5ed33adb3db69cbdb7a38e1e50b1b82fa",
3020 "gasLimit": "0x1388",
3021 "difficulty": "0x400000000",
3022 "mixHash": "0x0000000000000000000000000000000000000000000000000000000000000000",
3023 "coinbase": "0x0000000000000000000000000000000000000000",
3024 "number": "0x0",
3025 "gasUsed": "0x0",
3026 "parentHash": "0x0000000000000000000000000000000000000000000000000000000000000000",
3027 "baseFeePerGas": null
3028}"#;
3029
3030 let genesis = serde_json::from_str::<Genesis>(s).unwrap();
3031 let chainspec = ChainSpec::from_genesis(genesis);
3032 let activation = chainspec.hardforks.fork(EthereumHardfork::Paris);
3033 assert_eq!(
3034 activation,
3035 ForkCondition::TTD {
3036 activation_block_number: MAINNET_PARIS_BLOCK,
3037 total_difficulty: MAINNET_PARIS_TTD,
3038 fork_block: None,
3039 }
3040 )
3041 }
3042}