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