1pub use alloy_eips::eip1559::BaseFeeParams;
2use alloy_evm::eth::spec::EthExecutorSpec;
3
4use crate::{
5 constants::{MAINNET_DEPOSIT_CONTRACT, MAINNET_PRUNE_DELETE_LIMIT},
6 ethereum::SEPOLIA_PARIS_TTD,
7 holesky, hoodi, mainnet,
8 mainnet::{MAINNET_PARIS_BLOCK, MAINNET_PARIS_TTD},
9 sepolia,
10 sepolia::SEPOLIA_PARIS_BLOCK,
11 EthChainSpec,
12};
13use alloc::{
14 boxed::Box,
15 collections::BTreeMap,
16 format,
17 string::{String, ToString},
18 sync::Arc,
19 vec::Vec,
20};
21use alloy_chains::{Chain, NamedChain};
22use alloy_consensus::{
23 constants::{
24 EMPTY_WITHDRAWALS, HOLESKY_GENESIS_HASH, HOODI_GENESIS_HASH, MAINNET_GENESIS_HASH,
25 SEPOLIA_GENESIS_HASH,
26 },
27 Header,
28};
29use alloy_eips::{
30 eip1559::INITIAL_BASE_FEE, eip7685::EMPTY_REQUESTS_HASH, eip7840::BlobParams,
31 eip7892::BlobScheduleBlobParams, eip7928::EMPTY_BLOCK_ACCESS_LIST_HASH,
32};
33use alloy_genesis::{ChainConfig, Genesis};
34use alloy_primitives::{address, b256, Address, BlockNumber, B256, U256};
35use alloy_trie::root::state_root_ref_unhashed;
36use core::fmt::Debug;
37use derive_more::From;
38use reth_ethereum_forks::{
39 ChainHardforks, DisplayHardforks, EthereumHardfork, EthereumHardforks, ForkCondition,
40 ForkFilter, ForkFilterKey, ForkHash, ForkId, Hardfork, Hardforks, Head, DEV_HARDFORKS,
41};
42use reth_network_peers::{holesky_nodes, hoodi_nodes, mainnet_nodes, sepolia_nodes, NodeRecord};
43use reth_primitives_traits::{sync::LazyLock, BlockHeader, SealedHeader};
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(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::from(1))),
193 hardforks,
194 deposit_contract: Some(DepositContract::new(
195 address!("0x4242424242424242424242424242424242424242"),
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::from(0))),
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::from(0))),
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| -> Option<u64> {
276 match hardforks.fork(fork) {
277 ForkCondition::Timestamp(t) => Some(t),
278 _ => None,
279 }
280 };
281
282 let (terminal_total_difficulty, terminal_total_difficulty_passed) =
284 match hardforks.fork(EthereumHardfork::Paris) {
285 ForkCondition::TTD { total_difficulty, .. } => (Some(total_difficulty), true),
286 _ => (None, false),
287 };
288
289 let dao_fork_support = hardforks.fork(EthereumHardfork::Dao) != ForkCondition::Never;
291
292 ChainConfig {
293 chain_id: chain.map(|c| c.id()).unwrap_or(0),
294 homestead_block: block_num(EthereumHardfork::Homestead),
295 dao_fork_block: block_num(EthereumHardfork::Dao),
296 dao_fork_support,
297 eip150_block: block_num(EthereumHardfork::Tangerine),
298 eip155_block: block_num(EthereumHardfork::SpuriousDragon),
299 eip158_block: block_num(EthereumHardfork::SpuriousDragon),
300 byzantium_block: block_num(EthereumHardfork::Byzantium),
301 constantinople_block: block_num(EthereumHardfork::Constantinople),
302 petersburg_block: block_num(EthereumHardfork::Petersburg),
303 istanbul_block: block_num(EthereumHardfork::Istanbul),
304 muir_glacier_block: block_num(EthereumHardfork::MuirGlacier),
305 berlin_block: block_num(EthereumHardfork::Berlin),
306 london_block: block_num(EthereumHardfork::London),
307 arrow_glacier_block: block_num(EthereumHardfork::ArrowGlacier),
308 gray_glacier_block: block_num(EthereumHardfork::GrayGlacier),
309 merge_netsplit_block: None,
310 shanghai_time: timestamp(EthereumHardfork::Shanghai),
311 cancun_time: timestamp(EthereumHardfork::Cancun),
312 prague_time: timestamp(EthereumHardfork::Prague),
313 osaka_time: timestamp(EthereumHardfork::Osaka),
314 amsterdam_time: timestamp(EthereumHardfork::Amsterdam),
315 bpo1_time: timestamp(EthereumHardfork::Bpo1),
316 bpo2_time: timestamp(EthereumHardfork::Bpo2),
317 bpo3_time: timestamp(EthereumHardfork::Bpo3),
318 bpo4_time: timestamp(EthereumHardfork::Bpo4),
319 bpo5_time: timestamp(EthereumHardfork::Bpo5),
320 terminal_total_difficulty,
321 terminal_total_difficulty_passed,
322 deposit_contract_address,
323 blob_schedule,
324 ..Default::default()
325 }
326}
327
328pub fn mainnet_chain_config() -> ChainConfig {
330 let hardforks: ChainHardforks = EthereumHardfork::mainnet().into();
331 let blob_schedule = blob_params_to_schedule(&MAINNET.blob_params, &hardforks);
332 create_chain_config(
333 Some(Chain::mainnet()),
334 &hardforks,
335 Some(MAINNET_DEPOSIT_CONTRACT.address),
336 blob_schedule,
337 )
338}
339
340pub fn blob_params_to_schedule(
342 params: &BlobScheduleBlobParams,
343 hardforks: &ChainHardforks,
344) -> BTreeMap<String, BlobParams> {
345 let mut schedule = BTreeMap::new();
346 schedule.insert("cancun".to_string(), params.cancun);
347 schedule.insert("prague".to_string(), params.prague);
348 schedule.insert("osaka".to_string(), params.osaka);
349
350 let bpo_forks = EthereumHardfork::bpo_variants();
352 for (timestamp, blob_params) in ¶ms.scheduled {
353 for bpo_fork in bpo_forks {
354 if let ForkCondition::Timestamp(fork_ts) = hardforks.fork(bpo_fork) &&
355 fork_ts == *timestamp
356 {
357 schedule.insert(bpo_fork.name().to_lowercase(), *blob_params);
358 break;
359 }
360 }
361 }
362
363 schedule
364}
365
366#[derive(Clone, Debug, PartialEq, Eq)]
369pub enum BaseFeeParamsKind {
370 Constant(BaseFeeParams),
372 Variable(ForkBaseFeeParams),
375}
376
377impl Default for BaseFeeParamsKind {
378 fn default() -> Self {
379 BaseFeeParams::ethereum().into()
380 }
381}
382
383impl From<BaseFeeParams> for BaseFeeParamsKind {
384 fn from(params: BaseFeeParams) -> Self {
385 Self::Constant(params)
386 }
387}
388
389impl From<ForkBaseFeeParams> for BaseFeeParamsKind {
390 fn from(params: ForkBaseFeeParams) -> Self {
391 Self::Variable(params)
392 }
393}
394
395#[derive(Clone, Debug, PartialEq, Eq, From)]
398pub struct ForkBaseFeeParams(Vec<(Box<dyn Hardfork>, BaseFeeParams)>);
399
400impl<H: BlockHeader> core::ops::Deref for ChainSpec<H> {
401 type Target = ChainHardforks;
402
403 fn deref(&self) -> &Self::Target {
404 &self.hardforks
405 }
406}
407
408#[derive(Debug, Clone, PartialEq, Eq)]
416pub struct ChainSpec<H: BlockHeader = Header> {
417 pub chain: Chain,
419
420 pub genesis: Genesis,
422
423 pub genesis_header: SealedHeader<H>,
425
426 pub paris_block_and_final_difficulty: Option<(u64, U256)>,
429
430 pub hardforks: ChainHardforks,
432
433 pub deposit_contract: Option<DepositContract>,
435
436 pub base_fee_params: BaseFeeParamsKind,
438
439 pub prune_delete_limit: usize,
441
442 pub blob_params: BlobScheduleBlobParams,
444}
445
446impl<H: BlockHeader> Default for ChainSpec<H> {
447 fn default() -> Self {
448 Self {
449 chain: Default::default(),
450 genesis: Default::default(),
451 genesis_header: Default::default(),
452 paris_block_and_final_difficulty: Default::default(),
453 hardforks: Default::default(),
454 deposit_contract: Default::default(),
455 base_fee_params: BaseFeeParamsKind::Constant(BaseFeeParams::ethereum()),
456 prune_delete_limit: MAINNET_PRUNE_DELETE_LIMIT,
457 blob_params: Default::default(),
458 }
459 }
460}
461
462impl ChainSpec {
463 pub fn from_genesis(genesis: Genesis) -> Self {
465 genesis.into()
466 }
467
468 pub fn builder() -> ChainSpecBuilder {
470 ChainSpecBuilder::default()
471 }
472
473 pub fn from_chain_id(chain_id: u64) -> Option<Arc<Self>> {
475 match NamedChain::try_from(chain_id).ok()? {
476 NamedChain::Mainnet => Some(MAINNET.clone()),
477 NamedChain::Sepolia => Some(SEPOLIA.clone()),
478 NamedChain::Holesky => Some(HOLESKY.clone()),
479 NamedChain::Hoodi => Some(HOODI.clone()),
480 NamedChain::Dev => Some(DEV.clone()),
481 _ => None,
482 }
483 }
484}
485
486impl<H: BlockHeader> ChainSpec<H> {
487 pub const fn chain(&self) -> Chain {
489 self.chain
490 }
491
492 #[inline]
494 pub const fn is_ethereum(&self) -> bool {
495 self.chain.is_ethereum()
496 }
497
498 #[inline]
500 pub fn is_optimism_mainnet(&self) -> bool {
501 self.chain == Chain::optimism_mainnet()
502 }
503
504 #[inline]
506 pub fn paris_block(&self) -> Option<u64> {
507 self.paris_block_and_final_difficulty.map(|(block, _)| block)
508 }
509
510 pub const fn genesis(&self) -> &Genesis {
514 &self.genesis
515 }
516
517 pub fn genesis_header(&self) -> &H {
519 &self.genesis_header
520 }
521
522 pub fn sealed_genesis_header(&self) -> SealedHeader<H> {
524 SealedHeader::new(self.genesis_header().clone(), self.genesis_hash())
525 }
526
527 pub fn initial_base_fee(&self) -> Option<u64> {
529 let genesis_base_fee =
531 self.genesis.base_fee_per_gas.map(|fee| fee as u64).unwrap_or(INITIAL_BASE_FEE);
532
533 self.hardforks.fork(EthereumHardfork::London).active_at_block(0).then_some(genesis_base_fee)
535 }
536
537 pub fn base_fee_params_at_timestamp(&self, timestamp: u64) -> BaseFeeParams {
539 match self.base_fee_params {
540 BaseFeeParamsKind::Constant(bf_params) => bf_params,
541 BaseFeeParamsKind::Variable(ForkBaseFeeParams(ref bf_params)) => {
542 for (fork, params) in bf_params.iter().rev() {
546 if self.hardforks.is_fork_active_at_timestamp(fork.clone(), timestamp) {
547 return *params
548 }
549 }
550
551 bf_params.first().map(|(_, params)| *params).unwrap_or_else(BaseFeeParams::ethereum)
552 }
553 }
554 }
555
556 pub fn genesis_hash(&self) -> B256 {
558 self.genesis_header.hash()
559 }
560
561 pub const fn genesis_timestamp(&self) -> u64 {
563 self.genesis.timestamp
564 }
565
566 pub fn get_final_paris_total_difficulty(&self) -> Option<U256> {
568 self.paris_block_and_final_difficulty.map(|(_, final_difficulty)| final_difficulty)
569 }
570
571 pub fn hardfork_fork_filter<HF: Hardfork + Clone>(&self, fork: HF) -> Option<ForkFilter> {
573 match self.hardforks.fork(fork.clone()) {
574 ForkCondition::Never => None,
575 _ => Some(self.fork_filter(self.satisfy(self.hardforks.fork(fork)))),
576 }
577 }
578
579 pub fn display_hardforks(&self) -> DisplayHardforks {
581 let hardforks_with_meta = self.hardforks.forks_iter().map(|(fork, condition)| {
583 let metadata = match condition {
585 ForkCondition::Timestamp(timestamp) => {
586 EthChainSpec::blob_params_at_timestamp(self, timestamp).map(|params| {
589 format!(
590 "blob: (target: {}, max: {}, fraction: {})",
591 params.target_blob_count, params.max_blob_count, params.update_fraction
592 )
593 })
594 }
595 _ => None,
596 };
597 (fork, condition, metadata)
598 });
599
600 DisplayHardforks::with_meta(hardforks_with_meta)
601 }
602
603 #[inline]
605 pub fn hardfork_fork_id<HF: Hardfork + Clone>(&self, fork: HF) -> Option<ForkId> {
606 let condition = self.hardforks.fork(fork);
607 match condition {
608 ForkCondition::Never => None,
609 _ => Some(self.fork_id(&self.satisfy(condition))),
610 }
611 }
612
613 #[inline]
616 pub fn shanghai_fork_id(&self) -> Option<ForkId> {
617 self.hardfork_fork_id(EthereumHardfork::Shanghai)
618 }
619
620 #[inline]
623 pub fn cancun_fork_id(&self) -> Option<ForkId> {
624 self.hardfork_fork_id(EthereumHardfork::Cancun)
625 }
626
627 #[inline]
630 pub fn latest_fork_id(&self) -> ForkId {
631 self.hardfork_fork_id(self.hardforks.last().unwrap().0).unwrap()
632 }
633
634 pub fn fork_filter(&self, head: Head) -> ForkFilter {
636 let forks = self.hardforks.forks_iter().filter_map(|(_, condition)| {
637 Some(match condition {
640 ForkCondition::Block(block) |
641 ForkCondition::TTD { fork_block: Some(block), .. } => ForkFilterKey::Block(block),
642 ForkCondition::Timestamp(time) => ForkFilterKey::Time(time),
643 _ => return None,
644 })
645 });
646
647 ForkFilter::new(head, self.genesis_hash(), self.genesis_timestamp(), forks)
648 }
649
650 pub fn fork_id(&self, head: &Head) -> ForkId {
662 let mut forkhash = ForkHash::from(self.genesis_hash());
663
664 let mut current_applied = 0;
670
671 for (_, cond) in self.hardforks.forks_iter() {
673 if let ForkCondition::Block(block) |
676 ForkCondition::TTD { fork_block: Some(block), .. } = cond
677 {
678 if head.number >= block {
679 if block != current_applied {
681 forkhash += block;
682 current_applied = block;
683 }
684 } else {
685 return ForkId { hash: forkhash, next: block }
688 }
689 }
690 }
691
692 for timestamp in self.hardforks.forks_iter().filter_map(|(_, cond)| {
696 cond.as_timestamp().filter(|time| time > &self.genesis.timestamp)
698 }) {
699 if head.timestamp >= timestamp {
700 if timestamp != current_applied {
702 forkhash += timestamp;
703 current_applied = timestamp;
704 }
705 } else {
706 return ForkId { hash: forkhash, next: timestamp }
710 }
711 }
712
713 ForkId { hash: forkhash, next: 0 }
714 }
715
716 pub(crate) fn satisfy(&self, cond: ForkCondition) -> Head {
722 match cond {
723 ForkCondition::Block(number) => Head { number, ..Default::default() },
724 ForkCondition::Timestamp(timestamp) => {
725 Head {
728 timestamp,
729 number: self.last_block_fork_before_merge_or_timestamp().unwrap_or_default(),
730 ..Default::default()
731 }
732 }
733 ForkCondition::TTD { total_difficulty, fork_block, .. } => Head {
734 total_difficulty,
735 number: fork_block.unwrap_or_default(),
736 ..Default::default()
737 },
738 ForkCondition::Never => unreachable!(),
739 }
740 }
741
742 pub(crate) fn last_block_fork_before_merge_or_timestamp(&self) -> Option<u64> {
754 let mut hardforks_iter = self.hardforks.forks_iter().peekable();
755 while let Some((_, curr_cond)) = hardforks_iter.next() {
756 if let Some((_, next_cond)) = hardforks_iter.peek() {
757 match next_cond {
761 ForkCondition::TTD { fork_block: Some(block), .. } => return Some(*block),
764
765 ForkCondition::TTD { .. } | ForkCondition::Timestamp(_) => {
768 if let ForkCondition::Block(block_num) = curr_cond {
771 return Some(block_num);
772 }
773 }
774 ForkCondition::Block(_) | ForkCondition::Never => {}
775 }
776 }
777 }
778 None
779 }
780
781 pub fn bootnodes(&self) -> Option<Vec<NodeRecord>> {
783 use NamedChain as C;
784
785 match self.chain.try_into().ok()? {
786 C::Mainnet => Some(mainnet_nodes()),
787 C::Sepolia => Some(sepolia_nodes()),
788 C::Holesky => Some(holesky_nodes()),
789 C::Hoodi => Some(hoodi_nodes()),
790 _ => None,
791 }
792 }
793
794 pub fn map_header<NewH: BlockHeader>(self, f: impl FnOnce(H) -> NewH) -> ChainSpec<NewH> {
796 let Self {
797 chain,
798 genesis,
799 genesis_header,
800 paris_block_and_final_difficulty,
801 hardforks,
802 deposit_contract,
803 base_fee_params,
804 prune_delete_limit,
805 blob_params,
806 } = self;
807 ChainSpec {
808 chain,
809 genesis,
810 genesis_header: SealedHeader::new_unhashed(f(genesis_header.into_header())),
811 paris_block_and_final_difficulty,
812 hardforks,
813 deposit_contract,
814 base_fee_params,
815 prune_delete_limit,
816 blob_params,
817 }
818 }
819}
820
821impl From<Genesis> for ChainSpec {
822 fn from(genesis: Genesis) -> Self {
823 let hardfork_opts = [
825 (EthereumHardfork::Frontier.boxed(), Some(0)),
826 (EthereumHardfork::Homestead.boxed(), genesis.config.homestead_block),
827 (EthereumHardfork::Dao.boxed(), genesis.config.dao_fork_block),
828 (EthereumHardfork::Tangerine.boxed(), genesis.config.eip150_block),
829 (EthereumHardfork::SpuriousDragon.boxed(), genesis.config.eip155_block),
830 (EthereumHardfork::Byzantium.boxed(), genesis.config.byzantium_block),
831 (EthereumHardfork::Constantinople.boxed(), genesis.config.constantinople_block),
832 (EthereumHardfork::Petersburg.boxed(), genesis.config.petersburg_block),
833 (EthereumHardfork::Istanbul.boxed(), genesis.config.istanbul_block),
834 (EthereumHardfork::MuirGlacier.boxed(), genesis.config.muir_glacier_block),
835 (EthereumHardfork::Berlin.boxed(), genesis.config.berlin_block),
836 (EthereumHardfork::London.boxed(), genesis.config.london_block),
837 (EthereumHardfork::ArrowGlacier.boxed(), genesis.config.arrow_glacier_block),
838 (EthereumHardfork::GrayGlacier.boxed(), genesis.config.gray_glacier_block),
839 ];
840 let mut hardforks = hardfork_opts
841 .into_iter()
842 .filter_map(|(hardfork, opt)| opt.map(|block| (hardfork, ForkCondition::Block(block))))
843 .collect::<Vec<_>>();
844
845 let paris_block_and_final_difficulty = if let Some(ttd) =
849 genesis.config.terminal_total_difficulty
850 {
851 hardforks.push((
852 EthereumHardfork::Paris.boxed(),
853 ForkCondition::TTD {
854 activation_block_number: genesis
857 .config
858 .merge_netsplit_block
859 .or_else(|| {
860 match genesis.config.chain_id {
868 1 if ttd == MAINNET_PARIS_TTD => return Some(MAINNET_PARIS_BLOCK),
869 11155111 if ttd == SEPOLIA_PARIS_TTD => {
870 return Some(SEPOLIA_PARIS_BLOCK)
871 }
872 _ => {}
873 };
874 None
875 })
876 .unwrap_or_default(),
877 total_difficulty: ttd,
878 fork_block: genesis.config.merge_netsplit_block,
879 },
880 ));
881
882 genesis.config.merge_netsplit_block.map(|block| (block, ttd))
883 } else {
884 None
885 };
886
887 let time_hardfork_opts = [
889 (EthereumHardfork::Shanghai.boxed(), genesis.config.shanghai_time),
890 (EthereumHardfork::Cancun.boxed(), genesis.config.cancun_time),
891 (EthereumHardfork::Prague.boxed(), genesis.config.prague_time),
892 (EthereumHardfork::Osaka.boxed(), genesis.config.osaka_time),
893 (EthereumHardfork::Bpo1.boxed(), genesis.config.bpo1_time),
894 (EthereumHardfork::Bpo2.boxed(), genesis.config.bpo2_time),
895 (EthereumHardfork::Bpo3.boxed(), genesis.config.bpo3_time),
896 (EthereumHardfork::Bpo4.boxed(), genesis.config.bpo4_time),
897 (EthereumHardfork::Bpo5.boxed(), genesis.config.bpo5_time),
898 (EthereumHardfork::Amsterdam.boxed(), genesis.config.amsterdam_time),
899 ];
900
901 let mut time_hardforks = time_hardfork_opts
902 .into_iter()
903 .filter_map(|(hardfork, opt)| {
904 opt.map(|time| (hardfork, ForkCondition::Timestamp(time)))
905 })
906 .collect::<Vec<_>>();
907
908 hardforks.append(&mut time_hardforks);
909
910 let mainnet_hardforks: ChainHardforks = EthereumHardfork::mainnet().into();
912 let mainnet_order = mainnet_hardforks.forks_iter();
913
914 let mut ordered_hardforks = Vec::with_capacity(hardforks.len());
915 for (hardfork, _) in mainnet_order {
916 if let Some(pos) = hardforks.iter().position(|(e, _)| **e == *hardfork) {
917 ordered_hardforks.push(hardforks.remove(pos));
918 }
919 }
920
921 ordered_hardforks.append(&mut hardforks);
923
924 let blob_params = genesis.config.blob_schedule_blob_params();
926
927 let deposit_contract = genesis.config.deposit_contract_address.map(|address| {
932 DepositContract { address, block: 0, topic: MAINNET_DEPOSIT_CONTRACT.topic }
933 });
934
935 let hardforks = ChainHardforks::new(ordered_hardforks);
936
937 Self {
938 chain: genesis.config.chain_id.into(),
939 genesis_header: SealedHeader::new_unhashed(make_genesis_header(&genesis, &hardforks)),
940 genesis,
941 hardforks,
942 paris_block_and_final_difficulty,
943 deposit_contract,
944 blob_params,
945 ..Default::default()
946 }
947 }
948}
949
950impl<H: BlockHeader> Hardforks for ChainSpec<H> {
951 fn fork<HF: Hardfork>(&self, fork: HF) -> ForkCondition {
952 self.hardforks.fork(fork)
953 }
954
955 fn forks_iter(&self) -> impl Iterator<Item = (&dyn Hardfork, ForkCondition)> {
956 self.hardforks.forks_iter()
957 }
958
959 fn fork_id(&self, head: &Head) -> ForkId {
960 self.fork_id(head)
961 }
962
963 fn latest_fork_id(&self) -> ForkId {
964 self.latest_fork_id()
965 }
966
967 fn fork_filter(&self, head: Head) -> ForkFilter {
968 self.fork_filter(head)
969 }
970}
971
972impl<H: BlockHeader> EthereumHardforks for ChainSpec<H> {
973 fn ethereum_fork_activation(&self, fork: EthereumHardfork) -> ForkCondition {
974 self.fork(fork)
975 }
976}
977
978#[auto_impl::auto_impl(&, Arc)]
980pub trait ChainSpecProvider: Debug + Send {
981 type ChainSpec: EthChainSpec + 'static;
983
984 fn chain_spec(&self) -> Arc<Self::ChainSpec>;
986}
987
988#[derive(Debug, Default, Clone)]
990pub struct ChainSpecBuilder {
991 chain: Option<Chain>,
992 genesis: Option<Genesis>,
993 hardforks: ChainHardforks,
994}
995
996impl ChainSpecBuilder {
997 pub fn mainnet() -> Self {
999 Self {
1000 chain: Some(MAINNET.chain),
1001 genesis: Some(MAINNET.genesis.clone()),
1002 hardforks: MAINNET.hardforks.clone(),
1003 }
1004 }
1005}
1006
1007impl ChainSpecBuilder {
1008 pub const fn chain(mut self, chain: Chain) -> Self {
1010 self.chain = Some(chain);
1011 self
1012 }
1013
1014 pub fn reset(mut self) -> Self {
1016 self.hardforks = ChainHardforks::default();
1017 self
1018 }
1019
1020 pub fn genesis(mut self, genesis: Genesis) -> Self {
1022 self.genesis = Some(genesis);
1023 self
1024 }
1025
1026 pub fn with_fork<H: Hardfork>(mut self, fork: H, condition: ForkCondition) -> Self {
1028 self.hardforks.insert(fork, condition);
1029 self
1030 }
1031
1032 pub fn with_forks(mut self, forks: ChainHardforks) -> Self {
1034 self.hardforks = forks;
1035 self
1036 }
1037
1038 pub fn without_fork<H: Hardfork>(mut self, fork: H) -> Self {
1040 self.hardforks.remove(&fork);
1041 self
1042 }
1043
1044 pub fn paris_at_ttd(self, ttd: U256, activation_block_number: BlockNumber) -> Self {
1048 self.with_fork(
1049 EthereumHardfork::Paris,
1050 ForkCondition::TTD { activation_block_number, total_difficulty: ttd, fork_block: None },
1051 )
1052 }
1053
1054 pub fn frontier_activated(mut self) -> Self {
1056 self.hardforks.insert(EthereumHardfork::Frontier, ForkCondition::Block(0));
1057 self
1058 }
1059
1060 pub fn dao_activated(mut self) -> Self {
1062 self = self.frontier_activated();
1063 self.hardforks.insert(EthereumHardfork::Dao, ForkCondition::Block(0));
1064 self
1065 }
1066
1067 pub fn homestead_activated(mut self) -> Self {
1069 self = self.dao_activated();
1070 self.hardforks.insert(EthereumHardfork::Homestead, ForkCondition::Block(0));
1071 self
1072 }
1073
1074 pub fn tangerine_whistle_activated(mut self) -> Self {
1076 self = self.homestead_activated();
1077 self.hardforks.insert(EthereumHardfork::Tangerine, ForkCondition::Block(0));
1078 self
1079 }
1080
1081 pub fn spurious_dragon_activated(mut self) -> Self {
1083 self = self.tangerine_whistle_activated();
1084 self.hardforks.insert(EthereumHardfork::SpuriousDragon, ForkCondition::Block(0));
1085 self
1086 }
1087
1088 pub fn byzantium_activated(mut self) -> Self {
1090 self = self.spurious_dragon_activated();
1091 self.hardforks.insert(EthereumHardfork::Byzantium, ForkCondition::Block(0));
1092 self
1093 }
1094
1095 pub fn constantinople_activated(mut self) -> Self {
1097 self = self.byzantium_activated();
1098 self.hardforks.insert(EthereumHardfork::Constantinople, ForkCondition::Block(0));
1099 self
1100 }
1101
1102 pub fn petersburg_activated(mut self) -> Self {
1104 self = self.constantinople_activated();
1105 self.hardforks.insert(EthereumHardfork::Petersburg, ForkCondition::Block(0));
1106 self
1107 }
1108
1109 pub fn istanbul_activated(mut self) -> Self {
1111 self = self.petersburg_activated();
1112 self.hardforks.insert(EthereumHardfork::Istanbul, ForkCondition::Block(0));
1113 self
1114 }
1115
1116 pub fn muirglacier_activated(mut self) -> Self {
1118 self = self.istanbul_activated();
1119 self.hardforks.insert(EthereumHardfork::MuirGlacier, ForkCondition::Block(0));
1120 self
1121 }
1122
1123 pub fn berlin_activated(mut self) -> Self {
1125 self = self.muirglacier_activated();
1126 self.hardforks.insert(EthereumHardfork::Berlin, ForkCondition::Block(0));
1127 self
1128 }
1129
1130 pub fn london_activated(mut self) -> Self {
1132 self = self.berlin_activated();
1133 self.hardforks.insert(EthereumHardfork::London, ForkCondition::Block(0));
1134 self
1135 }
1136
1137 pub fn arrowglacier_activated(mut self) -> Self {
1139 self = self.london_activated();
1140 self.hardforks.insert(EthereumHardfork::ArrowGlacier, ForkCondition::Block(0));
1141 self
1142 }
1143
1144 pub fn grayglacier_activated(mut self) -> Self {
1146 self = self.arrowglacier_activated();
1147 self.hardforks.insert(EthereumHardfork::GrayGlacier, ForkCondition::Block(0));
1148 self
1149 }
1150
1151 pub fn paris_activated(mut self) -> Self {
1153 self = self.grayglacier_activated();
1154 self.hardforks.insert(
1155 EthereumHardfork::Paris,
1156 ForkCondition::TTD {
1157 activation_block_number: 0,
1158 total_difficulty: U256::ZERO,
1159 fork_block: None,
1160 },
1161 );
1162 self
1163 }
1164
1165 pub fn shanghai_activated(mut self) -> Self {
1167 self = self.paris_activated();
1168 self.hardforks.insert(EthereumHardfork::Shanghai, ForkCondition::Timestamp(0));
1169 self
1170 }
1171
1172 pub fn cancun_activated(mut self) -> Self {
1174 self = self.shanghai_activated();
1175 self.hardforks.insert(EthereumHardfork::Cancun, ForkCondition::Timestamp(0));
1176 self
1177 }
1178
1179 pub fn prague_activated(mut self) -> Self {
1181 self = self.cancun_activated();
1182 self.hardforks.insert(EthereumHardfork::Prague, ForkCondition::Timestamp(0));
1183 self
1184 }
1185
1186 pub fn with_prague_at(mut self, timestamp: u64) -> Self {
1188 self.hardforks.insert(EthereumHardfork::Prague, ForkCondition::Timestamp(timestamp));
1189 self
1190 }
1191
1192 pub fn osaka_activated(mut self) -> Self {
1194 self = self.prague_activated();
1195 self.hardforks.insert(EthereumHardfork::Osaka, ForkCondition::Timestamp(0));
1196 self
1197 }
1198
1199 pub fn with_osaka_at(mut self, timestamp: u64) -> Self {
1201 self.hardforks.insert(EthereumHardfork::Osaka, ForkCondition::Timestamp(timestamp));
1202 self
1203 }
1204
1205 pub fn amsterdam_activated(mut self) -> Self {
1207 self = self.osaka_activated();
1208 self.hardforks.insert(EthereumHardfork::Amsterdam, ForkCondition::Timestamp(0));
1209 self
1210 }
1211
1212 pub fn with_amsterdam_at(mut self, timestamp: u64) -> Self {
1214 self.hardforks.insert(EthereumHardfork::Amsterdam, ForkCondition::Timestamp(timestamp));
1215 self
1216 }
1217
1218 pub fn build(self) -> ChainSpec {
1225 let paris_block_and_final_difficulty = {
1226 self.hardforks.get(EthereumHardfork::Paris).and_then(|cond| {
1227 if let ForkCondition::TTD { total_difficulty, activation_block_number, .. } = cond {
1228 Some((activation_block_number, total_difficulty))
1229 } else {
1230 None
1231 }
1232 })
1233 };
1234 let genesis = self.genesis.expect("The genesis is required");
1235 ChainSpec {
1236 chain: self.chain.expect("The chain is required"),
1237 genesis_header: SealedHeader::new_unhashed(make_genesis_header(
1238 &genesis,
1239 &self.hardforks,
1240 )),
1241 genesis,
1242 hardforks: self.hardforks,
1243 paris_block_and_final_difficulty,
1244 deposit_contract: None,
1245 ..Default::default()
1246 }
1247 }
1248}
1249
1250impl From<&Arc<ChainSpec>> for ChainSpecBuilder {
1251 fn from(value: &Arc<ChainSpec>) -> Self {
1252 Self {
1253 chain: Some(value.chain),
1254 genesis: Some(value.genesis.clone()),
1255 hardforks: value.hardforks.clone(),
1256 }
1257 }
1258}
1259
1260impl<H: BlockHeader> EthExecutorSpec for ChainSpec<H> {
1261 fn deposit_contract_address(&self) -> Option<Address> {
1262 self.deposit_contract.map(|deposit_contract| deposit_contract.address)
1263 }
1264}
1265
1266#[derive(Debug, Clone, Copy, PartialEq, Eq)]
1268pub struct DepositContract {
1269 pub address: Address,
1271 pub block: BlockNumber,
1273 pub topic: B256,
1275}
1276
1277impl DepositContract {
1278 pub const fn new(address: Address, block: BlockNumber, topic: B256) -> Self {
1280 Self { address, block, topic }
1281 }
1282}
1283
1284#[cfg(any(test, feature = "test-utils"))]
1286pub fn test_fork_ids(spec: &ChainSpec, cases: &[(Head, ForkId)]) {
1287 for (block, expected_id) in cases {
1288 let computed_id = spec.fork_id(block);
1289 assert_eq!(
1290 expected_id, &computed_id,
1291 "Expected fork ID {:?}, computed fork ID {:?} at block {}",
1292 expected_id, computed_id, block.number
1293 );
1294 }
1295}
1296
1297#[cfg(test)]
1298mod tests {
1299 use super::*;
1300 use alloy_chains::Chain;
1301 use alloy_consensus::constants::ETH_TO_WEI;
1302 use alloy_eips::{eip4844::BLOB_TX_MIN_BLOB_GASPRICE, eip7840::BlobParams};
1303 use alloy_evm::block::calc::{base_block_reward, block_reward};
1304 use alloy_genesis::{ChainConfig, GenesisAccount};
1305 use alloy_primitives::{b256, hex};
1306 use alloy_trie::{TrieAccount, EMPTY_ROOT_HASH};
1307 use core::ops::Deref;
1308 use reth_ethereum_forks::{ForkCondition, ForkHash, ForkId, Head};
1309 use std::{collections::HashMap, str::FromStr};
1310
1311 fn test_hardfork_fork_ids(spec: &ChainSpec, cases: &[(EthereumHardfork, ForkId)]) {
1312 for (hardfork, expected_id) in cases {
1313 if let Some(computed_id) = spec.hardfork_fork_id(*hardfork) {
1314 assert_eq!(
1315 expected_id, &computed_id,
1316 "Expected fork ID {expected_id:?}, computed fork ID {computed_id:?} for hardfork {hardfork}"
1317 );
1318 if matches!(hardfork, EthereumHardfork::Shanghai) {
1319 if let Some(shanghai_id) = spec.shanghai_fork_id() {
1320 assert_eq!(
1321 expected_id, &shanghai_id,
1322 "Expected fork ID {expected_id:?}, computed fork ID {computed_id:?} for Shanghai hardfork"
1323 );
1324 } else {
1325 panic!("Expected ForkCondition to return Some for Hardfork::Shanghai");
1326 }
1327 }
1328 }
1329 }
1330 }
1331
1332 #[test]
1333 fn test_hardfork_list_display_mainnet() {
1334 assert_eq!(
1335 MAINNET.display_hardforks().to_string(),
1336 "Pre-merge hard forks (block based):
1337- Frontier @0
1338- Homestead @1150000
1339- Dao @1920000
1340- Tangerine @2463000
1341- SpuriousDragon @2675000
1342- Byzantium @4370000
1343- Constantinople @7280000
1344- Petersburg @7280000
1345- Istanbul @9069000
1346- MuirGlacier @9200000
1347- Berlin @12244000
1348- London @12965000
1349- ArrowGlacier @13773000
1350- GrayGlacier @15050000
1351Merge hard forks:
1352- Paris @58750000000000000000000 (network is known to be merged)
1353Post-merge hard forks (timestamp based):
1354- Shanghai @1681338455
1355- Cancun @1710338135 blob: (target: 3, max: 6, fraction: 3338477)
1356- Prague @1746612311 blob: (target: 6, max: 9, fraction: 5007716)
1357- Osaka @1764798551 blob: (target: 6, max: 9, fraction: 5007716)
1358- Bpo1 @1765290071 blob: (target: 10, max: 15, fraction: 8346193)
1359- Bpo2 @1767747671 blob: (target: 14, max: 21, fraction: 11684671)"
1360 );
1361 }
1362
1363 #[test]
1364 fn test_hardfork_list_ignores_disabled_forks() {
1365 let spec = ChainSpec::builder()
1366 .chain(Chain::mainnet())
1367 .genesis(Genesis::default())
1368 .with_fork(EthereumHardfork::Frontier, ForkCondition::Block(0))
1369 .with_fork(EthereumHardfork::Shanghai, ForkCondition::Never)
1370 .build();
1371 assert_eq!(
1372 spec.display_hardforks().to_string(),
1373 "Pre-merge hard forks (block based):
1374- Frontier @0"
1375 );
1376 }
1377
1378 #[test]
1380 fn ignores_genesis_fork_blocks() {
1381 let spec = ChainSpec::builder()
1382 .chain(Chain::mainnet())
1383 .genesis(Genesis::default())
1384 .with_fork(EthereumHardfork::Frontier, ForkCondition::Block(0))
1385 .with_fork(EthereumHardfork::Homestead, ForkCondition::Block(0))
1386 .with_fork(EthereumHardfork::Tangerine, ForkCondition::Block(0))
1387 .with_fork(EthereumHardfork::SpuriousDragon, ForkCondition::Block(0))
1388 .with_fork(EthereumHardfork::Byzantium, ForkCondition::Block(0))
1389 .with_fork(EthereumHardfork::Constantinople, ForkCondition::Block(0))
1390 .with_fork(EthereumHardfork::Istanbul, ForkCondition::Block(0))
1391 .with_fork(EthereumHardfork::MuirGlacier, ForkCondition::Block(0))
1392 .with_fork(EthereumHardfork::Berlin, ForkCondition::Block(0))
1393 .with_fork(EthereumHardfork::London, ForkCondition::Block(0))
1394 .with_fork(EthereumHardfork::ArrowGlacier, ForkCondition::Block(0))
1395 .with_fork(EthereumHardfork::GrayGlacier, ForkCondition::Block(0))
1396 .build();
1397
1398 assert_eq!(spec.deref().len(), 12, "12 forks should be active.");
1399 assert_eq!(
1400 spec.fork_id(&Head { number: 1, ..Default::default() }),
1401 ForkId { hash: ForkHash::from(spec.genesis_hash()), next: 0 },
1402 "the fork ID should be the genesis hash; forks at genesis are ignored for fork filters"
1403 );
1404 }
1405
1406 #[test]
1407 fn ignores_duplicate_fork_blocks() {
1408 let empty_genesis = Genesis::default();
1409 let unique_spec = ChainSpec::builder()
1410 .chain(Chain::mainnet())
1411 .genesis(empty_genesis.clone())
1412 .with_fork(EthereumHardfork::Frontier, ForkCondition::Block(0))
1413 .with_fork(EthereumHardfork::Homestead, ForkCondition::Block(1))
1414 .build();
1415
1416 let duplicate_spec = ChainSpec::builder()
1417 .chain(Chain::mainnet())
1418 .genesis(empty_genesis)
1419 .with_fork(EthereumHardfork::Frontier, ForkCondition::Block(0))
1420 .with_fork(EthereumHardfork::Homestead, ForkCondition::Block(1))
1421 .with_fork(EthereumHardfork::Tangerine, ForkCondition::Block(1))
1422 .build();
1423
1424 assert_eq!(
1425 unique_spec.fork_id(&Head { number: 2, ..Default::default() }),
1426 duplicate_spec.fork_id(&Head { number: 2, ..Default::default() }),
1427 "duplicate fork blocks should be deduplicated for fork filters"
1428 );
1429 }
1430
1431 #[test]
1432 fn test_chainspec_satisfy() {
1433 let empty_genesis = Genesis::default();
1434 let happy_path_case = ChainSpec::builder()
1436 .chain(Chain::mainnet())
1437 .genesis(empty_genesis.clone())
1438 .with_fork(EthereumHardfork::Frontier, ForkCondition::Block(0))
1439 .with_fork(EthereumHardfork::Homestead, ForkCondition::Block(73))
1440 .with_fork(EthereumHardfork::Shanghai, ForkCondition::Timestamp(11313123))
1441 .build();
1442 let happy_path_head = happy_path_case.satisfy(ForkCondition::Timestamp(11313123));
1443 let happy_path_expected = Head { number: 73, timestamp: 11313123, ..Default::default() };
1444 assert_eq!(
1445 happy_path_head, happy_path_expected,
1446 "expected satisfy() to return {happy_path_expected:#?}, but got {happy_path_head:#?} "
1447 );
1448 let multiple_timestamp_fork_case = ChainSpec::builder()
1450 .chain(Chain::mainnet())
1451 .genesis(empty_genesis.clone())
1452 .with_fork(EthereumHardfork::Frontier, ForkCondition::Block(0))
1453 .with_fork(EthereumHardfork::Homestead, ForkCondition::Block(73))
1454 .with_fork(EthereumHardfork::Shanghai, ForkCondition::Timestamp(11313123))
1455 .with_fork(EthereumHardfork::Cancun, ForkCondition::Timestamp(11313398))
1456 .build();
1457 let multi_timestamp_head =
1458 multiple_timestamp_fork_case.satisfy(ForkCondition::Timestamp(11313398));
1459 let mult_timestamp_expected =
1460 Head { number: 73, timestamp: 11313398, ..Default::default() };
1461 assert_eq!(
1462 multi_timestamp_head, mult_timestamp_expected,
1463 "expected satisfy() to return {mult_timestamp_expected:#?}, but got {multi_timestamp_head:#?} "
1464 );
1465 let no_block_fork_case = ChainSpec::builder()
1467 .chain(Chain::mainnet())
1468 .genesis(empty_genesis.clone())
1469 .with_fork(EthereumHardfork::Shanghai, ForkCondition::Timestamp(11313123))
1470 .build();
1471 let no_block_fork_head = no_block_fork_case.satisfy(ForkCondition::Timestamp(11313123));
1472 let no_block_fork_expected = Head { number: 0, timestamp: 11313123, ..Default::default() };
1473 assert_eq!(
1474 no_block_fork_head, no_block_fork_expected,
1475 "expected satisfy() to return {no_block_fork_expected:#?}, but got {no_block_fork_head:#?} ",
1476 );
1477 let fork_cond_ttd_blocknum_case = ChainSpec::builder()
1479 .chain(Chain::mainnet())
1480 .genesis(empty_genesis.clone())
1481 .with_fork(EthereumHardfork::Frontier, ForkCondition::Block(0))
1482 .with_fork(EthereumHardfork::Homestead, ForkCondition::Block(73))
1483 .with_fork(
1484 EthereumHardfork::Paris,
1485 ForkCondition::TTD {
1486 activation_block_number: 101,
1487 fork_block: Some(101),
1488 total_difficulty: U256::from(10_790_000),
1489 },
1490 )
1491 .with_fork(EthereumHardfork::Shanghai, ForkCondition::Timestamp(11313123))
1492 .build();
1493 let fork_cond_ttd_blocknum_head =
1494 fork_cond_ttd_blocknum_case.satisfy(ForkCondition::Timestamp(11313123));
1495 let fork_cond_ttd_blocknum_expected =
1496 Head { number: 101, timestamp: 11313123, ..Default::default() };
1497 assert_eq!(
1498 fork_cond_ttd_blocknum_head, fork_cond_ttd_blocknum_expected,
1499 "expected satisfy() to return {fork_cond_ttd_blocknum_expected:#?}, but got {fork_cond_ttd_blocknum_head:#?} ",
1500 );
1501
1502 let fork_cond_block_only_case = ChainSpec::builder()
1506 .chain(Chain::mainnet())
1507 .genesis(empty_genesis)
1508 .with_fork(EthereumHardfork::Frontier, ForkCondition::Block(0))
1509 .with_fork(EthereumHardfork::Homestead, ForkCondition::Block(73))
1510 .build();
1511 let fork_cond_block_only_head = fork_cond_block_only_case.satisfy(ForkCondition::Block(73));
1512 let fork_cond_block_only_expected = Head { number: 73, ..Default::default() };
1513 assert_eq!(
1514 fork_cond_block_only_head, fork_cond_block_only_expected,
1515 "expected satisfy() to return {fork_cond_block_only_expected:#?}, but got {fork_cond_block_only_head:#?} ",
1516 );
1517 let fork_cond_ttd_no_new_spec = fork_cond_block_only_case.satisfy(ForkCondition::TTD {
1520 activation_block_number: 101,
1521 fork_block: None,
1522 total_difficulty: U256::from(10_790_000),
1523 });
1524 let fork_cond_ttd_no_new_spec_expected =
1525 Head { total_difficulty: U256::from(10_790_000), ..Default::default() };
1526 assert_eq!(
1527 fork_cond_ttd_no_new_spec, fork_cond_ttd_no_new_spec_expected,
1528 "expected satisfy() to return {fork_cond_ttd_no_new_spec_expected:#?}, but got {fork_cond_ttd_no_new_spec:#?} ",
1529 );
1530 }
1531
1532 #[test]
1533 fn mainnet_hardfork_fork_ids() {
1534 test_hardfork_fork_ids(
1535 &MAINNET,
1536 &[
1537 (
1538 EthereumHardfork::Frontier,
1539 ForkId { hash: ForkHash(hex!("0xfc64ec04")), next: 1150000 },
1540 ),
1541 (
1542 EthereumHardfork::Homestead,
1543 ForkId { hash: ForkHash(hex!("0x97c2c34c")), next: 1920000 },
1544 ),
1545 (
1546 EthereumHardfork::Dao,
1547 ForkId { hash: ForkHash(hex!("0x91d1f948")), next: 2463000 },
1548 ),
1549 (
1550 EthereumHardfork::Tangerine,
1551 ForkId { hash: ForkHash(hex!("0x7a64da13")), next: 2675000 },
1552 ),
1553 (
1554 EthereumHardfork::SpuriousDragon,
1555 ForkId { hash: ForkHash(hex!("0x3edd5b10")), next: 4370000 },
1556 ),
1557 (
1558 EthereumHardfork::Byzantium,
1559 ForkId { hash: ForkHash(hex!("0xa00bc324")), next: 7280000 },
1560 ),
1561 (
1562 EthereumHardfork::Constantinople,
1563 ForkId { hash: ForkHash(hex!("0x668db0af")), next: 9069000 },
1564 ),
1565 (
1566 EthereumHardfork::Petersburg,
1567 ForkId { hash: ForkHash(hex!("0x668db0af")), next: 9069000 },
1568 ),
1569 (
1570 EthereumHardfork::Istanbul,
1571 ForkId { hash: ForkHash(hex!("0x879d6e30")), next: 9200000 },
1572 ),
1573 (
1574 EthereumHardfork::MuirGlacier,
1575 ForkId { hash: ForkHash(hex!("0xe029e991")), next: 12244000 },
1576 ),
1577 (
1578 EthereumHardfork::Berlin,
1579 ForkId { hash: ForkHash(hex!("0x0eb440f6")), next: 12965000 },
1580 ),
1581 (
1582 EthereumHardfork::London,
1583 ForkId { hash: ForkHash(hex!("0xb715077d")), next: 13773000 },
1584 ),
1585 (
1586 EthereumHardfork::ArrowGlacier,
1587 ForkId { hash: ForkHash(hex!("0x20c327fc")), next: 15050000 },
1588 ),
1589 (
1590 EthereumHardfork::GrayGlacier,
1591 ForkId { hash: ForkHash(hex!("0xf0afd0e3")), next: 1681338455 },
1592 ),
1593 (
1594 EthereumHardfork::Shanghai,
1595 ForkId { hash: ForkHash(hex!("0xdce96c2d")), next: 1710338135 },
1596 ),
1597 (
1598 EthereumHardfork::Cancun,
1599 ForkId { hash: ForkHash(hex!("0x9f3d2254")), next: 1746612311 },
1600 ),
1601 (
1602 EthereumHardfork::Prague,
1603 ForkId {
1604 hash: ForkHash(hex!("0xc376cf8b")),
1605 next: mainnet::MAINNET_OSAKA_TIMESTAMP,
1606 },
1607 ),
1608 ],
1609 );
1610 }
1611
1612 #[test]
1613 fn sepolia_hardfork_fork_ids() {
1614 test_hardfork_fork_ids(
1615 &SEPOLIA,
1616 &[
1617 (
1618 EthereumHardfork::Frontier,
1619 ForkId { hash: ForkHash(hex!("0xfe3366e7")), next: 1735371 },
1620 ),
1621 (
1622 EthereumHardfork::Homestead,
1623 ForkId { hash: ForkHash(hex!("0xfe3366e7")), next: 1735371 },
1624 ),
1625 (
1626 EthereumHardfork::Tangerine,
1627 ForkId { hash: ForkHash(hex!("0xfe3366e7")), next: 1735371 },
1628 ),
1629 (
1630 EthereumHardfork::SpuriousDragon,
1631 ForkId { hash: ForkHash(hex!("0xfe3366e7")), next: 1735371 },
1632 ),
1633 (
1634 EthereumHardfork::Byzantium,
1635 ForkId { hash: ForkHash(hex!("0xfe3366e7")), next: 1735371 },
1636 ),
1637 (
1638 EthereumHardfork::Constantinople,
1639 ForkId { hash: ForkHash(hex!("0xfe3366e7")), next: 1735371 },
1640 ),
1641 (
1642 EthereumHardfork::Petersburg,
1643 ForkId { hash: ForkHash(hex!("0xfe3366e7")), next: 1735371 },
1644 ),
1645 (
1646 EthereumHardfork::Istanbul,
1647 ForkId { hash: ForkHash(hex!("0xfe3366e7")), next: 1735371 },
1648 ),
1649 (
1650 EthereumHardfork::Berlin,
1651 ForkId { hash: ForkHash(hex!("0xfe3366e7")), next: 1735371 },
1652 ),
1653 (
1654 EthereumHardfork::London,
1655 ForkId { hash: ForkHash(hex!("0xfe3366e7")), next: 1735371 },
1656 ),
1657 (
1658 EthereumHardfork::Paris,
1659 ForkId { hash: ForkHash(hex!("0xb96cbd13")), next: 1677557088 },
1660 ),
1661 (
1662 EthereumHardfork::Shanghai,
1663 ForkId { hash: ForkHash(hex!("0xf7f9bc08")), next: 1706655072 },
1664 ),
1665 (
1666 EthereumHardfork::Cancun,
1667 ForkId { hash: ForkHash(hex!("0x88cf81d9")), next: 1741159776 },
1668 ),
1669 (
1670 EthereumHardfork::Prague,
1671 ForkId {
1672 hash: ForkHash(hex!("0xed88b5fd")),
1673 next: sepolia::SEPOLIA_OSAKA_TIMESTAMP,
1674 },
1675 ),
1676 ],
1677 );
1678 }
1679
1680 #[test]
1681 fn mainnet_fork_ids() {
1682 test_fork_ids(
1683 &MAINNET,
1684 &[
1685 (
1686 Head { number: 0, ..Default::default() },
1687 ForkId { hash: ForkHash(hex!("0xfc64ec04")), next: 1150000 },
1688 ),
1689 (
1690 Head { number: 1150000, ..Default::default() },
1691 ForkId { hash: ForkHash(hex!("0x97c2c34c")), next: 1920000 },
1692 ),
1693 (
1694 Head { number: 1920000, ..Default::default() },
1695 ForkId { hash: ForkHash(hex!("0x91d1f948")), next: 2463000 },
1696 ),
1697 (
1698 Head { number: 2463000, ..Default::default() },
1699 ForkId { hash: ForkHash(hex!("0x7a64da13")), next: 2675000 },
1700 ),
1701 (
1702 Head { number: 2675000, ..Default::default() },
1703 ForkId { hash: ForkHash(hex!("0x3edd5b10")), next: 4370000 },
1704 ),
1705 (
1706 Head { number: 4370000, ..Default::default() },
1707 ForkId { hash: ForkHash(hex!("0xa00bc324")), next: 7280000 },
1708 ),
1709 (
1710 Head { number: 7280000, ..Default::default() },
1711 ForkId { hash: ForkHash(hex!("0x668db0af")), next: 9069000 },
1712 ),
1713 (
1714 Head { number: 9069000, ..Default::default() },
1715 ForkId { hash: ForkHash(hex!("0x879d6e30")), next: 9200000 },
1716 ),
1717 (
1718 Head { number: 9200000, ..Default::default() },
1719 ForkId { hash: ForkHash(hex!("0xe029e991")), next: 12244000 },
1720 ),
1721 (
1722 Head { number: 12244000, ..Default::default() },
1723 ForkId { hash: ForkHash(hex!("0x0eb440f6")), next: 12965000 },
1724 ),
1725 (
1726 Head { number: 12965000, ..Default::default() },
1727 ForkId { hash: ForkHash(hex!("0xb715077d")), next: 13773000 },
1728 ),
1729 (
1730 Head { number: 13773000, ..Default::default() },
1731 ForkId { hash: ForkHash(hex!("0x20c327fc")), next: 15050000 },
1732 ),
1733 (
1734 Head { number: 15050000, ..Default::default() },
1735 ForkId { hash: ForkHash(hex!("0xf0afd0e3")), next: 1681338455 },
1736 ),
1737 (
1739 Head { number: 20000000, timestamp: 1681338455, ..Default::default() },
1740 ForkId { hash: ForkHash(hex!("0xdce96c2d")), next: 1710338135 },
1741 ),
1742 (
1744 Head { number: 20000001, timestamp: 1710338135, ..Default::default() },
1745 ForkId { hash: ForkHash(hex!("0x9f3d2254")), next: 1746612311 },
1746 ),
1747 (
1749 Head { number: 20000004, timestamp: 1746612311, ..Default::default() },
1750 ForkId {
1751 hash: ForkHash(hex!("0xc376cf8b")),
1752 next: mainnet::MAINNET_OSAKA_TIMESTAMP,
1753 },
1754 ),
1755 (
1757 Head {
1758 number: 20000004,
1759 timestamp: mainnet::MAINNET_OSAKA_TIMESTAMP,
1760 ..Default::default()
1761 },
1762 ForkId {
1763 hash: ForkHash(hex!("0x5167e2a6")),
1764 next: mainnet::MAINNET_BPO1_TIMESTAMP,
1765 },
1766 ),
1767 ],
1768 );
1769 }
1770
1771 #[test]
1772 fn hoodi_fork_ids() {
1773 test_fork_ids(
1774 &HOODI,
1775 &[
1776 (
1777 Head { number: 0, ..Default::default() },
1778 ForkId { hash: ForkHash(hex!("0xbef71d30")), next: 1742999832 },
1779 ),
1780 (
1782 Head { number: 0, timestamp: 1742999833, ..Default::default() },
1783 ForkId {
1784 hash: ForkHash(hex!("0x0929e24e")),
1785 next: hoodi::HOODI_OSAKA_TIMESTAMP,
1786 },
1787 ),
1788 (
1790 Head {
1791 number: 0,
1792 timestamp: hoodi::HOODI_OSAKA_TIMESTAMP,
1793 ..Default::default()
1794 },
1795 ForkId {
1796 hash: ForkHash(hex!("0xe7e0e7ff")),
1797 next: hoodi::HOODI_BPO1_TIMESTAMP,
1798 },
1799 ),
1800 ],
1801 )
1802 }
1803
1804 #[test]
1805 fn holesky_fork_ids() {
1806 test_fork_ids(
1807 &HOLESKY,
1808 &[
1809 (
1810 Head { number: 0, ..Default::default() },
1811 ForkId { hash: ForkHash(hex!("0xc61a6098")), next: 1696000704 },
1812 ),
1813 (
1815 Head { number: 123, ..Default::default() },
1816 ForkId { hash: ForkHash(hex!("0xc61a6098")), next: 1696000704 },
1817 ),
1818 (
1820 Head { number: 123, timestamp: 1696000703, ..Default::default() },
1821 ForkId { hash: ForkHash(hex!("0xc61a6098")), next: 1696000704 },
1822 ),
1823 (
1825 Head { number: 123, timestamp: 1696000704, ..Default::default() },
1826 ForkId { hash: ForkHash(hex!("0xfd4f016b")), next: 1707305664 },
1827 ),
1828 (
1830 Head { number: 123, timestamp: 1707305663, ..Default::default() },
1831 ForkId { hash: ForkHash(hex!("0xfd4f016b")), next: 1707305664 },
1832 ),
1833 (
1835 Head { number: 123, timestamp: 1707305664, ..Default::default() },
1836 ForkId { hash: ForkHash(hex!("0x9b192ad0")), next: 1740434112 },
1837 ),
1838 (
1840 Head { number: 123, timestamp: 1740434111, ..Default::default() },
1841 ForkId { hash: ForkHash(hex!("0x9b192ad0")), next: 1740434112 },
1842 ),
1843 (
1845 Head { number: 123, timestamp: 1740434112, ..Default::default() },
1846 ForkId {
1847 hash: ForkHash(hex!("0xdfbd9bed")),
1848 next: holesky::HOLESKY_OSAKA_TIMESTAMP,
1849 },
1850 ),
1851 (
1853 Head {
1854 number: 123,
1855 timestamp: holesky::HOLESKY_OSAKA_TIMESTAMP,
1856 ..Default::default()
1857 },
1858 ForkId {
1859 hash: ForkHash(hex!("0x783def52")),
1860 next: holesky::HOLESKY_BPO1_TIMESTAMP,
1861 },
1862 ),
1863 ],
1864 )
1865 }
1866
1867 #[test]
1868 fn sepolia_fork_ids() {
1869 test_fork_ids(
1870 &SEPOLIA,
1871 &[
1872 (
1873 Head { number: 0, ..Default::default() },
1874 ForkId { hash: ForkHash(hex!("0xfe3366e7")), next: 1735371 },
1875 ),
1876 (
1877 Head { number: 1735370, ..Default::default() },
1878 ForkId { hash: ForkHash(hex!("0xfe3366e7")), next: 1735371 },
1879 ),
1880 (
1881 Head { number: 1735371, ..Default::default() },
1882 ForkId { hash: ForkHash(hex!("0xb96cbd13")), next: 1677557088 },
1883 ),
1884 (
1885 Head { number: 1735372, timestamp: 1677557087, ..Default::default() },
1886 ForkId { hash: ForkHash(hex!("0xb96cbd13")), next: 1677557088 },
1887 ),
1888 (
1890 Head { number: 1735373, timestamp: 1677557088, ..Default::default() },
1891 ForkId { hash: ForkHash(hex!("0xf7f9bc08")), next: 1706655072 },
1892 ),
1893 (
1895 Head { number: 1735374, timestamp: 1706655071, ..Default::default() },
1896 ForkId { hash: ForkHash(hex!("0xf7f9bc08")), next: 1706655072 },
1897 ),
1898 (
1900 Head { number: 1735375, timestamp: 1706655072, ..Default::default() },
1901 ForkId { hash: ForkHash(hex!("0x88cf81d9")), next: 1741159776 },
1902 ),
1903 (
1905 Head { number: 1735376, timestamp: 1741159775, ..Default::default() },
1906 ForkId { hash: ForkHash(hex!("0x88cf81d9")), next: 1741159776 },
1907 ),
1908 (
1910 Head { number: 1735377, timestamp: 1741159776, ..Default::default() },
1911 ForkId {
1912 hash: ForkHash(hex!("0xed88b5fd")),
1913 next: sepolia::SEPOLIA_OSAKA_TIMESTAMP,
1914 },
1915 ),
1916 (
1918 Head {
1919 number: 1735377,
1920 timestamp: sepolia::SEPOLIA_OSAKA_TIMESTAMP,
1921 ..Default::default()
1922 },
1923 ForkId {
1924 hash: ForkHash(hex!("0xe2ae4999")),
1925 next: sepolia::SEPOLIA_BPO1_TIMESTAMP,
1926 },
1927 ),
1928 ],
1929 );
1930 }
1931
1932 #[test]
1933 fn dev_fork_ids() {
1934 test_fork_ids(
1935 &DEV,
1936 &[(
1937 Head { number: 0, ..Default::default() },
1938 ForkId { hash: ForkHash(hex!("0x0b1a4ef7")), next: 0 },
1939 )],
1940 )
1941 }
1942
1943 #[test]
1947 fn timestamped_forks() {
1948 let mainnet_with_timestamps = ChainSpecBuilder::mainnet().build();
1949 test_fork_ids(
1950 &mainnet_with_timestamps,
1951 &[
1952 (
1953 Head { number: 0, timestamp: 0, ..Default::default() },
1954 ForkId { hash: ForkHash(hex!("0xfc64ec04")), next: 1150000 },
1955 ), (
1957 Head { number: 1149999, timestamp: 0, ..Default::default() },
1958 ForkId { hash: ForkHash(hex!("0xfc64ec04")), next: 1150000 },
1959 ), (
1961 Head { number: 1150000, timestamp: 0, ..Default::default() },
1962 ForkId { hash: ForkHash(hex!("0x97c2c34c")), next: 1920000 },
1963 ), (
1965 Head { number: 1919999, timestamp: 0, ..Default::default() },
1966 ForkId { hash: ForkHash(hex!("0x97c2c34c")), next: 1920000 },
1967 ), (
1969 Head { number: 1920000, timestamp: 0, ..Default::default() },
1970 ForkId { hash: ForkHash(hex!("0x91d1f948")), next: 2463000 },
1971 ), (
1973 Head { number: 2462999, timestamp: 0, ..Default::default() },
1974 ForkId { hash: ForkHash(hex!("0x91d1f948")), next: 2463000 },
1975 ), (
1977 Head { number: 2463000, timestamp: 0, ..Default::default() },
1978 ForkId { hash: ForkHash(hex!("0x7a64da13")), next: 2675000 },
1979 ), (
1981 Head { number: 2674999, timestamp: 0, ..Default::default() },
1982 ForkId { hash: ForkHash(hex!("0x7a64da13")), next: 2675000 },
1983 ), (
1985 Head { number: 2675000, timestamp: 0, ..Default::default() },
1986 ForkId { hash: ForkHash(hex!("0x3edd5b10")), next: 4370000 },
1987 ), (
1989 Head { number: 4369999, timestamp: 0, ..Default::default() },
1990 ForkId { hash: ForkHash(hex!("0x3edd5b10")), next: 4370000 },
1991 ), (
1993 Head { number: 4370000, timestamp: 0, ..Default::default() },
1994 ForkId { hash: ForkHash(hex!("0xa00bc324")), next: 7280000 },
1995 ), (
1997 Head { number: 7279999, timestamp: 0, ..Default::default() },
1998 ForkId { hash: ForkHash(hex!("0xa00bc324")), next: 7280000 },
1999 ), (
2001 Head { number: 7280000, timestamp: 0, ..Default::default() },
2002 ForkId { hash: ForkHash(hex!("0x668db0af")), next: 9069000 },
2003 ), (
2005 Head { number: 9068999, timestamp: 0, ..Default::default() },
2006 ForkId { hash: ForkHash(hex!("0x668db0af")), next: 9069000 },
2007 ), (
2009 Head { number: 9069000, timestamp: 0, ..Default::default() },
2010 ForkId { hash: ForkHash(hex!("0x879d6e30")), next: 9200000 },
2011 ), (
2013 Head { number: 9199999, timestamp: 0, ..Default::default() },
2014 ForkId { hash: ForkHash(hex!("0x879d6e30")), next: 9200000 },
2015 ), (
2017 Head { number: 9200000, timestamp: 0, ..Default::default() },
2018 ForkId { hash: ForkHash(hex!("0xe029e991")), next: 12244000 },
2019 ), (
2021 Head { number: 12243999, timestamp: 0, ..Default::default() },
2022 ForkId { hash: ForkHash(hex!("0xe029e991")), next: 12244000 },
2023 ), (
2025 Head { number: 12244000, timestamp: 0, ..Default::default() },
2026 ForkId { hash: ForkHash(hex!("0x0eb440f6")), next: 12965000 },
2027 ), (
2029 Head { number: 12964999, timestamp: 0, ..Default::default() },
2030 ForkId { hash: ForkHash(hex!("0x0eb440f6")), next: 12965000 },
2031 ), (
2033 Head { number: 12965000, timestamp: 0, ..Default::default() },
2034 ForkId { hash: ForkHash(hex!("0xb715077d")), next: 13773000 },
2035 ), (
2037 Head { number: 13772999, timestamp: 0, ..Default::default() },
2038 ForkId { hash: ForkHash(hex!("0xb715077d")), next: 13773000 },
2039 ), (
2041 Head { number: 13773000, timestamp: 0, ..Default::default() },
2042 ForkId { hash: ForkHash(hex!("0x20c327fc")), next: 15050000 },
2043 ), (
2045 Head { number: 15049999, timestamp: 0, ..Default::default() },
2046 ForkId { hash: ForkHash(hex!("0x20c327fc")), next: 15050000 },
2047 ), (
2049 Head { number: 15050000, timestamp: 0, ..Default::default() },
2050 ForkId { hash: ForkHash(hex!("0xf0afd0e3")), next: 1681338455 },
2051 ), (
2053 Head { number: 19999999, timestamp: 1667999999, ..Default::default() },
2054 ForkId { hash: ForkHash(hex!("0xf0afd0e3")), next: 1681338455 },
2055 ), (
2057 Head { number: 20000000, timestamp: 1681338455, ..Default::default() },
2058 ForkId { hash: ForkHash(hex!("0xdce96c2d")), next: 1710338135 },
2059 ), (
2061 Head { number: 20000001, timestamp: 1710338134, ..Default::default() },
2062 ForkId { hash: ForkHash(hex!("0xdce96c2d")), next: 1710338135 },
2063 ), (
2065 Head { number: 20000002, timestamp: 1710338135, ..Default::default() },
2066 ForkId { hash: ForkHash(hex!("0x9f3d2254")), next: 1746612311 },
2067 ), (
2069 Head { number: 20000003, timestamp: 1746612310, ..Default::default() },
2070 ForkId { hash: ForkHash(hex!("0x9f3d2254")), next: 1746612311 },
2071 ), (
2073 Head { number: 20000004, timestamp: 1746612311, ..Default::default() },
2074 ForkId {
2075 hash: ForkHash(hex!("0xc376cf8b")),
2076 next: mainnet::MAINNET_OSAKA_TIMESTAMP,
2077 },
2078 ),
2079 (
2081 Head {
2082 number: 20000004,
2083 timestamp: mainnet::MAINNET_OSAKA_TIMESTAMP,
2084 ..Default::default()
2085 },
2086 ForkId {
2087 hash: ForkHash(hex!("0x5167e2a6")),
2088 next: mainnet::MAINNET_BPO1_TIMESTAMP,
2089 },
2090 ),
2091 ],
2092 );
2093 }
2094
2095 fn construct_chainspec(
2098 builder: ChainSpecBuilder,
2099 shanghai_time: u64,
2100 cancun_time: u64,
2101 ) -> ChainSpec {
2102 builder
2103 .with_fork(EthereumHardfork::Shanghai, ForkCondition::Timestamp(shanghai_time))
2104 .with_fork(EthereumHardfork::Cancun, ForkCondition::Timestamp(cancun_time))
2105 .build()
2106 }
2107
2108 #[test]
2113 fn test_timestamp_fork_in_genesis() {
2114 let timestamp = 1690475657u64;
2115 let default_spec_builder = ChainSpecBuilder::default()
2116 .chain(Chain::from_id(1337))
2117 .genesis(Genesis::default().with_timestamp(timestamp))
2118 .paris_activated();
2119
2120 let tests = [
2123 (
2124 construct_chainspec(default_spec_builder.clone(), timestamp - 1, timestamp + 1),
2125 timestamp + 1,
2126 ),
2127 (
2128 construct_chainspec(default_spec_builder.clone(), timestamp, timestamp + 1),
2129 timestamp + 1,
2130 ),
2131 (
2132 construct_chainspec(default_spec_builder, timestamp + 1, timestamp + 2),
2133 timestamp + 1,
2134 ),
2135 ];
2136
2137 for (spec, expected_timestamp) in tests {
2138 let got_forkid = spec.fork_id(&Head { number: 0, timestamp: 0, ..Default::default() });
2139 let genesis_hash = spec.genesis_hash();
2144 let expected_forkid =
2145 ForkId { hash: ForkHash::from(genesis_hash), next: expected_timestamp };
2146 assert_eq!(got_forkid, expected_forkid);
2147 }
2148 }
2149
2150 #[test]
2152 fn check_terminal_ttd() {
2153 let chainspec = ChainSpecBuilder::mainnet().build();
2154
2155 let terminal_block_ttd = U256::from(58750003716598352816469_u128);
2157 let terminal_block_difficulty = U256::from(11055787484078698_u128);
2158 assert!(!chainspec
2159 .fork(EthereumHardfork::Paris)
2160 .active_at_ttd(terminal_block_ttd, terminal_block_difficulty));
2161
2162 let first_pos_block_ttd = U256::from(58750003716598352816469_u128);
2164 let first_pos_difficulty = U256::ZERO;
2165 assert!(chainspec
2166 .fork(EthereumHardfork::Paris)
2167 .active_at_ttd(first_pos_block_ttd, first_pos_difficulty));
2168 }
2169
2170 #[test]
2171 fn geth_genesis_with_shanghai() {
2172 let geth_genesis = r#"
2173 {
2174 "config": {
2175 "chainId": 1337,
2176 "homesteadBlock": 0,
2177 "eip150Block": 0,
2178 "eip150Hash": "0x0000000000000000000000000000000000000000000000000000000000000000",
2179 "eip155Block": 0,
2180 "eip158Block": 0,
2181 "byzantiumBlock": 0,
2182 "constantinopleBlock": 0,
2183 "petersburgBlock": 0,
2184 "istanbulBlock": 0,
2185 "muirGlacierBlock": 0,
2186 "berlinBlock": 0,
2187 "londonBlock": 0,
2188 "arrowGlacierBlock": 0,
2189 "grayGlacierBlock": 0,
2190 "shanghaiTime": 0,
2191 "cancunTime": 1,
2192 "terminalTotalDifficulty": 0,
2193 "terminalTotalDifficultyPassed": true,
2194 "ethash": {}
2195 },
2196 "nonce": "0x0",
2197 "timestamp": "0x0",
2198 "extraData": "0x",
2199 "gasLimit": "0x4c4b40",
2200 "difficulty": "0x1",
2201 "mixHash": "0x0000000000000000000000000000000000000000000000000000000000000000",
2202 "coinbase": "0x0000000000000000000000000000000000000000",
2203 "alloc": {
2204 "658bdf435d810c91414ec09147daa6db62406379": {
2205 "balance": "0x487a9a304539440000"
2206 },
2207 "aa00000000000000000000000000000000000000": {
2208 "code": "0x6042",
2209 "storage": {
2210 "0x0000000000000000000000000000000000000000000000000000000000000000": "0x0000000000000000000000000000000000000000000000000000000000000000",
2211 "0x0100000000000000000000000000000000000000000000000000000000000000": "0x0100000000000000000000000000000000000000000000000000000000000000",
2212 "0x0200000000000000000000000000000000000000000000000000000000000000": "0x0200000000000000000000000000000000000000000000000000000000000000",
2213 "0x0300000000000000000000000000000000000000000000000000000000000000": "0x0000000000000000000000000000000000000000000000000000000000000303"
2214 },
2215 "balance": "0x1",
2216 "nonce": "0x1"
2217 },
2218 "bb00000000000000000000000000000000000000": {
2219 "code": "0x600154600354",
2220 "storage": {
2221 "0x0000000000000000000000000000000000000000000000000000000000000000": "0x0000000000000000000000000000000000000000000000000000000000000000",
2222 "0x0100000000000000000000000000000000000000000000000000000000000000": "0x0100000000000000000000000000000000000000000000000000000000000000",
2223 "0x0200000000000000000000000000000000000000000000000000000000000000": "0x0200000000000000000000000000000000000000000000000000000000000000",
2224 "0x0300000000000000000000000000000000000000000000000000000000000000": "0x0000000000000000000000000000000000000000000000000000000000000303"
2225 },
2226 "balance": "0x2",
2227 "nonce": "0x1"
2228 }
2229 },
2230 "number": "0x0",
2231 "gasUsed": "0x0",
2232 "parentHash": "0x0000000000000000000000000000000000000000000000000000000000000000",
2233 "baseFeePerGas": "0x3b9aca00"
2234 }
2235 "#;
2236
2237 let genesis: Genesis = serde_json::from_str(geth_genesis).unwrap();
2238 let chainspec = ChainSpec::from(genesis);
2239
2240 assert_eq!(
2242 chainspec.hardforks.get(EthereumHardfork::Homestead).unwrap(),
2243 ForkCondition::Block(0)
2244 );
2245 assert_eq!(
2246 chainspec.hardforks.get(EthereumHardfork::Tangerine).unwrap(),
2247 ForkCondition::Block(0)
2248 );
2249 assert_eq!(
2250 chainspec.hardforks.get(EthereumHardfork::SpuriousDragon).unwrap(),
2251 ForkCondition::Block(0)
2252 );
2253 assert_eq!(
2254 chainspec.hardforks.get(EthereumHardfork::Byzantium).unwrap(),
2255 ForkCondition::Block(0)
2256 );
2257 assert_eq!(
2258 chainspec.hardforks.get(EthereumHardfork::Constantinople).unwrap(),
2259 ForkCondition::Block(0)
2260 );
2261 assert_eq!(
2262 chainspec.hardforks.get(EthereumHardfork::Petersburg).unwrap(),
2263 ForkCondition::Block(0)
2264 );
2265 assert_eq!(
2266 chainspec.hardforks.get(EthereumHardfork::Istanbul).unwrap(),
2267 ForkCondition::Block(0)
2268 );
2269 assert_eq!(
2270 chainspec.hardforks.get(EthereumHardfork::MuirGlacier).unwrap(),
2271 ForkCondition::Block(0)
2272 );
2273 assert_eq!(
2274 chainspec.hardforks.get(EthereumHardfork::Berlin).unwrap(),
2275 ForkCondition::Block(0)
2276 );
2277 assert_eq!(
2278 chainspec.hardforks.get(EthereumHardfork::London).unwrap(),
2279 ForkCondition::Block(0)
2280 );
2281 assert_eq!(
2282 chainspec.hardforks.get(EthereumHardfork::ArrowGlacier).unwrap(),
2283 ForkCondition::Block(0)
2284 );
2285 assert_eq!(
2286 chainspec.hardforks.get(EthereumHardfork::GrayGlacier).unwrap(),
2287 ForkCondition::Block(0)
2288 );
2289
2290 assert_eq!(
2292 chainspec.hardforks.get(EthereumHardfork::Shanghai).unwrap(),
2293 ForkCondition::Timestamp(0)
2294 );
2295
2296 assert_eq!(
2298 chainspec.hardforks.get(EthereumHardfork::Cancun).unwrap(),
2299 ForkCondition::Timestamp(1)
2300 );
2301
2302 let key_rlp = vec![
2304 (
2305 hex!("0x658bdf435d810c91414ec09147daa6db62406379"),
2306 &hex!(
2307 "0xf84d8089487a9a304539440000a056e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421a0c5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470"
2308 )[..],
2309 ),
2310 (
2311 hex!("0xaa00000000000000000000000000000000000000"),
2312 &hex!(
2313 "0xf8440101a08afc95b7d18a226944b9c2070b6bda1c3a36afcc3730429d47579c94b9fe5850a0ce92c756baff35fa740c3557c1a971fd24d2d35b7c8e067880d50cd86bb0bc99"
2314 )[..],
2315 ),
2316 (
2317 hex!("0xbb00000000000000000000000000000000000000"),
2318 &hex!(
2319 "0xf8440102a08afc95b7d18a226944b9c2070b6bda1c3a36afcc3730429d47579c94b9fe5850a0e25a53cbb501cec2976b393719c63d832423dd70a458731a0b64e4847bbca7d2"
2320 )[..],
2321 ),
2322 ];
2323
2324 for (key, expected_rlp) in key_rlp {
2325 let account = chainspec.genesis.alloc.get(&key).expect("account should exist");
2326 assert_eq!(&alloy_rlp::encode(TrieAccount::from(account.clone())), expected_rlp);
2327 }
2328
2329 let expected_state_root: B256 =
2330 hex!("0x078dc6061b1d8eaa8493384b59c9c65ceb917201221d08b80c4de6770b6ec7e7").into();
2331 assert_eq!(chainspec.genesis_header().state_root, expected_state_root);
2332
2333 assert_eq!(chainspec.genesis_header().withdrawals_root, Some(EMPTY_ROOT_HASH));
2334
2335 let expected_hash: B256 =
2336 hex!("0x1fc027d65f820d3eef441ebeec139ebe09e471cf98516dce7b5643ccb27f418c").into();
2337 let hash = chainspec.genesis_hash();
2338 assert_eq!(hash, expected_hash);
2339 }
2340
2341 #[test]
2342 fn hive_geth_json() {
2343 let hive_json = r#"
2344 {
2345 "nonce": "0x0000000000000042",
2346 "difficulty": "0x2123456",
2347 "mixHash": "0x123456789abcdef123456789abcdef123456789abcdef123456789abcdef1234",
2348 "coinbase": "0xaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
2349 "timestamp": "0x123456",
2350 "parentHash": "0x0000000000000000000000000000000000000000000000000000000000000000",
2351 "extraData": "0xfafbfcfd",
2352 "gasLimit": "0x2fefd8",
2353 "alloc": {
2354 "dbdbdb2cbd23b783741e8d7fcf51e459b497e4a6": {
2355 "balance": "0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"
2356 },
2357 "e6716f9544a56c530d868e4bfbacb172315bdead": {
2358 "balance": "0x11",
2359 "code": "0x12"
2360 },
2361 "b9c015918bdaba24b4ff057a92a3873d6eb201be": {
2362 "balance": "0x21",
2363 "storage": {
2364 "0x0000000000000000000000000000000000000000000000000000000000000001": "0x22"
2365 }
2366 },
2367 "1a26338f0d905e295fccb71fa9ea849ffa12aaf4": {
2368 "balance": "0x31",
2369 "nonce": "0x32"
2370 },
2371 "0000000000000000000000000000000000000001": {
2372 "balance": "0x41"
2373 },
2374 "0000000000000000000000000000000000000002": {
2375 "balance": "0x51"
2376 },
2377 "0000000000000000000000000000000000000003": {
2378 "balance": "0x61"
2379 },
2380 "0000000000000000000000000000000000000004": {
2381 "balance": "0x71"
2382 }
2383 },
2384 "config": {
2385 "ethash": {},
2386 "chainId": 10,
2387 "homesteadBlock": 0,
2388 "eip150Block": 0,
2389 "eip155Block": 0,
2390 "eip158Block": 0,
2391 "byzantiumBlock": 0,
2392 "constantinopleBlock": 0,
2393 "petersburgBlock": 0,
2394 "istanbulBlock": 0
2395 }
2396 }
2397 "#;
2398
2399 let genesis = serde_json::from_str::<Genesis>(hive_json).unwrap();
2400 let chainspec: ChainSpec = genesis.into();
2401 assert_eq!(chainspec.chain, Chain::from_named(NamedChain::Optimism));
2402 let expected_state_root: B256 =
2403 hex!("0x9a6049ac535e3dc7436c189eaa81c73f35abd7f282ab67c32944ff0301d63360").into();
2404 assert_eq!(chainspec.genesis_header().state_root, expected_state_root);
2405 let hard_forks = vec![
2406 EthereumHardfork::Byzantium,
2407 EthereumHardfork::Homestead,
2408 EthereumHardfork::Istanbul,
2409 EthereumHardfork::Petersburg,
2410 EthereumHardfork::Constantinople,
2411 ];
2412 for fork in hard_forks {
2413 assert_eq!(chainspec.hardforks.get(fork).unwrap(), ForkCondition::Block(0));
2414 }
2415
2416 let expected_hash: B256 =
2417 hex!("0x5ae31c6522bd5856129f66be3d582b842e4e9faaa87f21cce547128339a9db3c").into();
2418 let hash = chainspec.genesis_header().hash_slow();
2419 assert_eq!(hash, expected_hash);
2420 }
2421
2422 #[test]
2423 fn test_hive_paris_block_genesis_json() {
2424 let hive_paris = r#"
2427 {
2428 "config": {
2429 "ethash": {},
2430 "chainId": 3503995874084926,
2431 "homesteadBlock": 0,
2432 "eip150Block": 6,
2433 "eip155Block": 12,
2434 "eip158Block": 12,
2435 "byzantiumBlock": 18,
2436 "constantinopleBlock": 24,
2437 "petersburgBlock": 30,
2438 "istanbulBlock": 36,
2439 "muirGlacierBlock": 42,
2440 "berlinBlock": 48,
2441 "londonBlock": 54,
2442 "arrowGlacierBlock": 60,
2443 "grayGlacierBlock": 66,
2444 "mergeNetsplitBlock": 72,
2445 "terminalTotalDifficulty": 9454784,
2446 "shanghaiTime": 780,
2447 "cancunTime": 840
2448 },
2449 "nonce": "0x0",
2450 "timestamp": "0x0",
2451 "extraData": "0x68697665636861696e",
2452 "gasLimit": "0x23f3e20",
2453 "difficulty": "0x20000",
2454 "mixHash": "0x0000000000000000000000000000000000000000000000000000000000000000",
2455 "coinbase": "0x0000000000000000000000000000000000000000",
2456 "alloc": {
2457 "000f3df6d732807ef1319fb7b8bb8522d0beac02": {
2458 "code": "0x3373fffffffffffffffffffffffffffffffffffffffe14604d57602036146024575f5ffd5b5f35801560495762001fff810690815414603c575f5ffd5b62001fff01545f5260205ff35b5f5ffd5b62001fff42064281555f359062001fff015500",
2459 "balance": "0x2a"
2460 },
2461 "0c2c51a0990aee1d73c1228de158688341557508": {
2462 "balance": "0xc097ce7bc90715b34b9f1000000000"
2463 },
2464 "14e46043e63d0e3cdcf2530519f4cfaf35058cb2": {
2465 "balance": "0xc097ce7bc90715b34b9f1000000000"
2466 },
2467 "16c57edf7fa9d9525378b0b81bf8a3ced0620c1c": {
2468 "balance": "0xc097ce7bc90715b34b9f1000000000"
2469 },
2470 "1f4924b14f34e24159387c0a4cdbaa32f3ddb0cf": {
2471 "balance": "0xc097ce7bc90715b34b9f1000000000"
2472 },
2473 "1f5bde34b4afc686f136c7a3cb6ec376f7357759": {
2474 "balance": "0xc097ce7bc90715b34b9f1000000000"
2475 },
2476 "2d389075be5be9f2246ad654ce152cf05990b209": {
2477 "balance": "0xc097ce7bc90715b34b9f1000000000"
2478 },
2479 "3ae75c08b4c907eb63a8960c45b86e1e9ab6123c": {
2480 "balance": "0xc097ce7bc90715b34b9f1000000000"
2481 },
2482 "4340ee1b812acb40a1eb561c019c327b243b92df": {
2483 "balance": "0xc097ce7bc90715b34b9f1000000000"
2484 },
2485 "4a0f1452281bcec5bd90c3dce6162a5995bfe9df": {
2486 "balance": "0xc097ce7bc90715b34b9f1000000000"
2487 },
2488 "4dde844b71bcdf95512fb4dc94e84fb67b512ed8": {
2489 "balance": "0xc097ce7bc90715b34b9f1000000000"
2490 },
2491 "5f552da00dfb4d3749d9e62dcee3c918855a86a0": {
2492 "balance": "0xc097ce7bc90715b34b9f1000000000"
2493 },
2494 "654aa64f5fbefb84c270ec74211b81ca8c44a72e": {
2495 "balance": "0xc097ce7bc90715b34b9f1000000000"
2496 },
2497 "717f8aa2b982bee0e29f573d31df288663e1ce16": {
2498 "balance": "0xc097ce7bc90715b34b9f1000000000"
2499 },
2500 "7435ed30a8b4aeb0877cef0c6e8cffe834eb865f": {
2501 "balance": "0xc097ce7bc90715b34b9f1000000000"
2502 },
2503 "83c7e323d189f18725ac510004fdc2941f8c4a78": {
2504 "balance": "0xc097ce7bc90715b34b9f1000000000"
2505 },
2506 "84e75c28348fb86acea1a93a39426d7d60f4cc46": {
2507 "balance": "0xc097ce7bc90715b34b9f1000000000"
2508 },
2509 "8bebc8ba651aee624937e7d897853ac30c95a067": {
2510 "storage": {
2511 "0x0000000000000000000000000000000000000000000000000000000000000001": "0x0000000000000000000000000000000000000000000000000000000000000001",
2512 "0x0000000000000000000000000000000000000000000000000000000000000002": "0x0000000000000000000000000000000000000000000000000000000000000002",
2513 "0x0000000000000000000000000000000000000000000000000000000000000003": "0x0000000000000000000000000000000000000000000000000000000000000003"
2514 },
2515 "balance": "0x1",
2516 "nonce": "0x1"
2517 },
2518 "c7b99a164efd027a93f147376cc7da7c67c6bbe0": {
2519 "balance": "0xc097ce7bc90715b34b9f1000000000"
2520 },
2521 "d803681e487e6ac18053afc5a6cd813c86ec3e4d": {
2522 "balance": "0xc097ce7bc90715b34b9f1000000000"
2523 },
2524 "e7d13f7aa2a838d24c59b40186a0aca1e21cffcc": {
2525 "balance": "0xc097ce7bc90715b34b9f1000000000"
2526 },
2527 "eda8645ba6948855e3b3cd596bbb07596d59c603": {
2528 "balance": "0xc097ce7bc90715b34b9f1000000000"
2529 }
2530 },
2531 "number": "0x0",
2532 "gasUsed": "0x0",
2533 "parentHash": "0x0000000000000000000000000000000000000000000000000000000000000000",
2534 "baseFeePerGas": null,
2535 "excessBlobGas": null,
2536 "blobGasUsed": null
2537 }
2538 "#;
2539
2540 let genesis: Genesis = serde_json::from_str(hive_paris).unwrap();
2542 let chainspec = ChainSpec::from(genesis);
2543
2544 let expected_forkid = ForkId { hash: ForkHash(hex!("0xbc0c2605")), next: 0 };
2546 let got_forkid =
2547 chainspec.fork_id(&Head { number: 73, timestamp: 840, ..Default::default() });
2548
2549 assert_eq!(got_forkid, expected_forkid);
2551 assert_eq!(chainspec.paris_block_and_final_difficulty, Some((72, U256::from(9454784))));
2553 }
2554
2555 #[test]
2556 fn test_parse_genesis_json() {
2557 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"}"#;
2558 let genesis: Genesis = serde_json::from_str(s).unwrap();
2559 let acc = genesis
2560 .alloc
2561 .get(&"0xaa00000000000000000000000000000000000000".parse::<Address>().unwrap())
2562 .unwrap();
2563 assert_eq!(acc.balance, U256::from(1));
2564 assert_eq!(genesis.base_fee_per_gas, Some(0x1337));
2565 }
2566
2567 #[test]
2568 fn test_parse_cancun_genesis_json() {
2569 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"}"#;
2570 let genesis: Genesis = serde_json::from_str(s).unwrap();
2571 let acc = genesis
2572 .alloc
2573 .get(&"0xaa00000000000000000000000000000000000000".parse::<Address>().unwrap())
2574 .unwrap();
2575 assert_eq!(acc.balance, U256::from(1));
2576 assert_eq!(genesis.config.cancun_time, Some(4661));
2578 }
2579
2580 #[test]
2581 fn test_parse_prague_genesis_all_formats() {
2582 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"}"#;
2583 let genesis: Genesis = serde_json::from_str(s).unwrap();
2584
2585 let acc = genesis
2587 .alloc
2588 .get(&"0xaa00000000000000000000000000000000000000".parse::<Address>().unwrap())
2589 .unwrap();
2590 assert_eq!(acc.balance, U256::from(1));
2591 assert_eq!(genesis.config.cancun_time, Some(4661));
2593 assert_eq!(genesis.config.prague_time, Some(4662));
2595 }
2596
2597 #[test]
2598 fn test_parse_cancun_genesis_all_formats() {
2599 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"}"#;
2600 let genesis: Genesis = serde_json::from_str(s).unwrap();
2601
2602 let acc = genesis
2604 .alloc
2605 .get(&"0xaa00000000000000000000000000000000000000".parse::<Address>().unwrap())
2606 .unwrap();
2607 assert_eq!(acc.balance, U256::from(1));
2608 assert_eq!(genesis.config.cancun_time, Some(4661));
2610 }
2611
2612 #[test]
2613 fn test_paris_block_and_total_difficulty() {
2614 let genesis = Genesis { gas_limit: 0x2fefd8u64, ..Default::default() };
2615 let paris_chainspec = ChainSpecBuilder::default()
2616 .chain(Chain::from_id(1337))
2617 .genesis(genesis)
2618 .paris_activated()
2619 .build();
2620 assert_eq!(paris_chainspec.paris_block_and_final_difficulty, Some((0, U256::ZERO)));
2621 }
2622
2623 #[test]
2624 fn test_default_cancun_header_forkhash() {
2625 let genesis = Genesis { gas_limit: 0x2fefd8u64, ..Default::default() };
2627 let default_chainspec = ChainSpecBuilder::default()
2628 .chain(Chain::from_id(1337))
2629 .genesis(genesis)
2630 .cancun_activated()
2631 .build();
2632 let mut header = default_chainspec.genesis_header().clone();
2633
2634 header.state_root =
2636 B256::from_str("0x62e2595e017f0ca23e08d17221010721a71c3ae932f4ea3cb12117786bb392d4")
2637 .unwrap();
2638
2639 assert_eq!(header.withdrawals_root, Some(EMPTY_WITHDRAWALS));
2641
2642 assert_eq!(header.parent_beacon_block_root, Some(B256::ZERO));
2645 assert_eq!(header.blob_gas_used, Some(0));
2646 assert_eq!(header.excess_blob_gas, Some(0));
2647
2648 let genesis_hash = header.hash_slow();
2650 let expected_hash =
2651 b256!("0x16bb7c59613a5bad3f7c04a852fd056545ade2483968d9a25a1abb05af0c4d37");
2652 assert_eq!(genesis_hash, expected_hash);
2653
2654 let expected_forkhash = ForkHash(hex!("0x8062457a"));
2656 assert_eq!(ForkHash::from(genesis_hash), expected_forkhash);
2657 }
2658
2659 #[test]
2660 fn holesky_paris_activated_at_genesis() {
2661 assert!(HOLESKY
2662 .fork(EthereumHardfork::Paris)
2663 .active_at_ttd(HOLESKY.genesis.difficulty, HOLESKY.genesis.difficulty));
2664 }
2665
2666 #[test]
2667 fn test_genesis_format_deserialization() {
2668 let config = ChainConfig {
2670 chain_id: 2600,
2671 homestead_block: Some(0),
2672 eip150_block: Some(0),
2673 eip155_block: Some(0),
2674 eip158_block: Some(0),
2675 byzantium_block: Some(0),
2676 constantinople_block: Some(0),
2677 petersburg_block: Some(0),
2678 istanbul_block: Some(0),
2679 berlin_block: Some(0),
2680 london_block: Some(0),
2681 shanghai_time: Some(0),
2682 terminal_total_difficulty: Some(U256::ZERO),
2683 terminal_total_difficulty_passed: true,
2684 ..Default::default()
2685 };
2686 let genesis = Genesis {
2688 config,
2689 nonce: 0,
2690 timestamp: 1698688670,
2691 gas_limit: 5000,
2692 difficulty: U256::ZERO,
2693 mix_hash: B256::ZERO,
2694 coinbase: Address::ZERO,
2695 ..Default::default()
2696 };
2697
2698 let address = hex!("0x6Be02d1d3665660d22FF9624b7BE0551ee1Ac91b").into();
2700 let account = GenesisAccount::default().with_balance(U256::from(33));
2701 let genesis = genesis.extend_accounts(HashMap::from([(address, account)]));
2702
2703 let serialized_genesis = serde_json::to_string(&genesis).unwrap();
2705 let deserialized_genesis: Genesis = serde_json::from_str(&serialized_genesis).unwrap();
2706
2707 assert_eq!(genesis, deserialized_genesis);
2708 }
2709
2710 #[test]
2711 fn check_fork_id_chainspec_with_fork_condition_never() {
2712 let spec: ChainSpec = ChainSpec {
2713 chain: Chain::mainnet(),
2714 genesis: Genesis::default(),
2715 hardforks: ChainHardforks::new(vec![(
2716 EthereumHardfork::Frontier.boxed(),
2717 ForkCondition::Never,
2718 )]),
2719 paris_block_and_final_difficulty: None,
2720 deposit_contract: None,
2721 ..Default::default()
2722 };
2723
2724 assert_eq!(spec.hardfork_fork_id(EthereumHardfork::Frontier), None);
2725 }
2726
2727 #[test]
2728 fn check_fork_filter_chainspec_with_fork_condition_never() {
2729 let spec: ChainSpec = ChainSpec {
2730 chain: Chain::mainnet(),
2731 genesis: Genesis::default(),
2732 hardforks: ChainHardforks::new(vec![(
2733 EthereumHardfork::Shanghai.boxed(),
2734 ForkCondition::Never,
2735 )]),
2736 paris_block_and_final_difficulty: None,
2737 deposit_contract: None,
2738 ..Default::default()
2739 };
2740
2741 assert_eq!(spec.hardfork_fork_filter(EthereumHardfork::Shanghai), None);
2742 }
2743
2744 #[test]
2745 fn latest_eth_mainnet_fork_id() {
2746 assert_eq!(ForkId { hash: ForkHash(hex!("0x07c9462e")), next: 0 }, MAINNET.latest_fork_id())
2748 }
2749
2750 #[test]
2751 fn latest_hoodi_mainnet_fork_id() {
2752 assert_eq!(ForkId { hash: ForkHash(hex!("0x23aa1351")), next: 0 }, HOODI.latest_fork_id())
2754 }
2755
2756 #[test]
2757 fn latest_holesky_mainnet_fork_id() {
2758 assert_eq!(ForkId { hash: ForkHash(hex!("0x9bc6cb31")), next: 0 }, HOLESKY.latest_fork_id())
2760 }
2761
2762 #[test]
2763 fn latest_sepolia_mainnet_fork_id() {
2764 assert_eq!(ForkId { hash: ForkHash(hex!("0x268956b6")), next: 0 }, SEPOLIA.latest_fork_id())
2766 }
2767
2768 #[test]
2769 fn test_fork_order_ethereum_mainnet() {
2770 let genesis = Genesis {
2771 config: ChainConfig {
2772 chain_id: 0,
2773 homestead_block: Some(0),
2774 dao_fork_block: Some(0),
2775 dao_fork_support: false,
2776 eip150_block: Some(0),
2777 eip155_block: Some(0),
2778 eip158_block: Some(0),
2779 byzantium_block: Some(0),
2780 constantinople_block: Some(0),
2781 petersburg_block: Some(0),
2782 istanbul_block: Some(0),
2783 muir_glacier_block: Some(0),
2784 berlin_block: Some(0),
2785 london_block: Some(0),
2786 arrow_glacier_block: Some(0),
2787 gray_glacier_block: Some(0),
2788 merge_netsplit_block: Some(0),
2789 shanghai_time: Some(0),
2790 cancun_time: Some(0),
2791 terminal_total_difficulty: Some(U256::ZERO),
2792 ..Default::default()
2793 },
2794 ..Default::default()
2795 };
2796
2797 let chain_spec: ChainSpec = genesis.into();
2798
2799 let hardforks: Vec<_> = chain_spec.hardforks.forks_iter().map(|(h, _)| h).collect();
2800 let expected_hardforks = vec![
2801 EthereumHardfork::Frontier.boxed(),
2802 EthereumHardfork::Homestead.boxed(),
2803 EthereumHardfork::Dao.boxed(),
2804 EthereumHardfork::Tangerine.boxed(),
2805 EthereumHardfork::SpuriousDragon.boxed(),
2806 EthereumHardfork::Byzantium.boxed(),
2807 EthereumHardfork::Constantinople.boxed(),
2808 EthereumHardfork::Petersburg.boxed(),
2809 EthereumHardfork::Istanbul.boxed(),
2810 EthereumHardfork::MuirGlacier.boxed(),
2811 EthereumHardfork::Berlin.boxed(),
2812 EthereumHardfork::London.boxed(),
2813 EthereumHardfork::ArrowGlacier.boxed(),
2814 EthereumHardfork::GrayGlacier.boxed(),
2815 EthereumHardfork::Paris.boxed(),
2816 EthereumHardfork::Shanghai.boxed(),
2817 EthereumHardfork::Cancun.boxed(),
2818 ];
2819
2820 assert!(expected_hardforks
2821 .iter()
2822 .zip(hardforks.iter())
2823 .all(|(expected, actual)| &**expected == *actual));
2824 assert_eq!(expected_hardforks.len(), hardforks.len());
2825 }
2826
2827 #[test]
2828 fn test_calc_base_block_reward() {
2829 let cases = [
2831 ((0, U256::ZERO), Some(ETH_TO_WEI * 5)),
2833 ((4370000, U256::ZERO), Some(ETH_TO_WEI * 3)),
2835 ((7280000, U256::ZERO), Some(ETH_TO_WEI * 2)),
2837 ((15537394, U256::from(58_750_000_000_000_000_000_000_u128)), None),
2839 ];
2840
2841 for ((block_number, _td), expected_reward) in cases {
2842 assert_eq!(base_block_reward(&*MAINNET, block_number), expected_reward);
2843 }
2844 }
2845
2846 #[test]
2847 fn test_calc_full_block_reward() {
2848 let base_reward = ETH_TO_WEI;
2849 let one_thirty_twoth_reward = base_reward >> 5;
2850
2851 let cases = [
2853 (0, base_reward),
2854 (1, base_reward + one_thirty_twoth_reward),
2855 (2, base_reward + one_thirty_twoth_reward * 2),
2856 ];
2857
2858 for (num_ommers, expected_reward) in cases {
2859 assert_eq!(block_reward(base_reward, num_ommers), expected_reward);
2860 }
2861 }
2862
2863 #[test]
2864 fn blob_params_from_genesis() {
2865 let s = r#"{
2866 "blobSchedule": {
2867 "cancun":{
2868 "baseFeeUpdateFraction":3338477,
2869 "max":6,
2870 "target":3
2871 },
2872 "prague":{
2873 "baseFeeUpdateFraction":3338477,
2874 "max":6,
2875 "target":3
2876 }
2877 }
2878 }"#;
2879 let config: ChainConfig = serde_json::from_str(s).unwrap();
2880 let hardfork_params = config.blob_schedule_blob_params();
2881 let expected = BlobScheduleBlobParams {
2882 cancun: BlobParams {
2883 target_blob_count: 3,
2884 max_blob_count: 6,
2885 update_fraction: 3338477,
2886 min_blob_fee: BLOB_TX_MIN_BLOB_GASPRICE,
2887 max_blobs_per_tx: 6,
2888 blob_base_cost: 0,
2889 },
2890 prague: BlobParams {
2891 target_blob_count: 3,
2892 max_blob_count: 6,
2893 update_fraction: 3338477,
2894 min_blob_fee: BLOB_TX_MIN_BLOB_GASPRICE,
2895 max_blobs_per_tx: 6,
2896 blob_base_cost: 0,
2897 },
2898 ..Default::default()
2899 };
2900 assert_eq!(hardfork_params, expected);
2901 }
2902
2903 #[test]
2904 fn parse_perf_net_genesis() {
2905 let s = r#"{
2906 "config": {
2907 "chainId": 1,
2908 "homesteadBlock": 1150000,
2909 "daoForkBlock": 1920000,
2910 "daoForkSupport": true,
2911 "eip150Block": 2463000,
2912 "eip150Hash": "0x2086799aeebeae135c246c65021c82b4e15a2c451340993aacfd2751886514f0",
2913 "eip155Block": 2675000,
2914 "eip158Block": 2675000,
2915 "byzantiumBlock": 4370000,
2916 "constantinopleBlock": 7280000,
2917 "petersburgBlock": 7280000,
2918 "istanbulBlock": 9069000,
2919 "muirGlacierBlock": 9200000,
2920 "berlinBlock": 12244000,
2921 "londonBlock": 12965000,
2922 "arrowGlacierBlock": 13773000,
2923 "grayGlacierBlock": 15050000,
2924 "terminalTotalDifficulty": 58750000000000000000000,
2925 "terminalTotalDifficultyPassed": true,
2926 "shanghaiTime": 1681338455,
2927 "cancunTime": 1710338135,
2928 "pragueTime": 1746612311,
2929 "ethash": {},
2930 "depositContractAddress": "0x00000000219ab540356cBB839Cbe05303d7705Fa",
2931 "blobSchedule": {
2932 "cancun": {
2933 "target": 3,
2934 "max": 6,
2935 "baseFeeUpdateFraction": 3338477
2936 },
2937 "prague": {
2938 "target": 6,
2939 "max": 9,
2940 "baseFeeUpdateFraction": 5007716
2941 }
2942 }
2943 },
2944 "nonce": "0x42",
2945 "timestamp": "0x0",
2946 "extraData": "0x11bbe8db4e347b4e8c937c1c8370e4b5ed33adb3db69cbdb7a38e1e50b1b82fa",
2947 "gasLimit": "0x1388",
2948 "difficulty": "0x400000000",
2949 "mixHash": "0x0000000000000000000000000000000000000000000000000000000000000000",
2950 "coinbase": "0x0000000000000000000000000000000000000000",
2951 "number": "0x0",
2952 "gasUsed": "0x0",
2953 "parentHash": "0x0000000000000000000000000000000000000000000000000000000000000000",
2954 "baseFeePerGas": null
2955}"#;
2956
2957 let genesis = serde_json::from_str::<Genesis>(s).unwrap();
2958 let chainspec = ChainSpec::from_genesis(genesis);
2959 let activation = chainspec.hardforks.fork(EthereumHardfork::Paris);
2960 assert_eq!(
2961 activation,
2962 ForkCondition::TTD {
2963 activation_block_number: MAINNET_PARIS_BLOCK,
2964 total_difficulty: MAINNET_PARIS_TTD,
2965 fork_block: None,
2966 }
2967 )
2968 }
2969}