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