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