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