Skip to main content

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