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