reth_chainspec/
spec.rs

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