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