reth_chainspec/
spec.rs

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