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