reth_chainspec/
spec.rs

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