Skip to main content

reth_chainspec/
spec.rs

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