Skip to main content

reth_chainspec/
spec.rs

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