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