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