mirror of
https://github.com/hl-archive-node/nanoreth.git
synced 2025-12-06 10:59:55 +00:00
2826 lines
123 KiB
Rust
2826 lines
123 KiB
Rust
use crate::constants::MAINNET_DEPOSIT_CONTRACT;
|
|
#[cfg(not(feature = "std"))]
|
|
use alloc::{boxed::Box, sync::Arc, vec::Vec};
|
|
use alloy_chains::{Chain, ChainKind, NamedChain};
|
|
use alloy_genesis::Genesis;
|
|
use alloy_primitives::{address, b256, Address, BlockNumber, B256, U256};
|
|
use alloy_trie::EMPTY_ROOT_HASH;
|
|
use derive_more::From;
|
|
use once_cell::sync::Lazy;
|
|
use reth_ethereum_forks::{
|
|
ChainHardforks, DisplayHardforks, EthereumHardfork, EthereumHardforks, ForkCondition,
|
|
ForkFilter, ForkFilterKey, ForkHash, ForkId, Hardfork, Head, DEV_HARDFORKS,
|
|
};
|
|
use reth_network_peers::NodeRecord;
|
|
use reth_primitives_traits::{
|
|
constants::{EIP1559_INITIAL_BASE_FEE, EMPTY_WITHDRAWALS},
|
|
Header, SealedHeader,
|
|
};
|
|
use reth_trie_common::root::state_root_ref_unhashed;
|
|
#[cfg(feature = "std")]
|
|
use std::sync::Arc;
|
|
|
|
#[cfg(feature = "optimism")]
|
|
use crate::constants::optimism::{
|
|
BASE_SEPOLIA_BASE_FEE_PARAMS, BASE_SEPOLIA_CANYON_BASE_FEE_PARAMS, OP_BASE_FEE_PARAMS,
|
|
OP_CANYON_BASE_FEE_PARAMS, OP_SEPOLIA_BASE_FEE_PARAMS, OP_SEPOLIA_CANYON_BASE_FEE_PARAMS,
|
|
};
|
|
pub use alloy_eips::eip1559::BaseFeeParams;
|
|
#[cfg(feature = "optimism")]
|
|
use reth_ethereum_forks::OptimismHardfork;
|
|
use reth_network_peers::{
|
|
base_nodes, base_testnet_nodes, holesky_nodes, mainnet_nodes, op_nodes, op_testnet_nodes,
|
|
sepolia_nodes,
|
|
};
|
|
|
|
/// The Ethereum mainnet spec
|
|
pub static MAINNET: Lazy<Arc<ChainSpec>> = Lazy::new(|| {
|
|
ChainSpec {
|
|
chain: Chain::mainnet(),
|
|
genesis: serde_json::from_str(include_str!("../res/genesis/mainnet.json"))
|
|
.expect("Can't deserialize Mainnet genesis json"),
|
|
genesis_hash: Some(b256!(
|
|
"d4e56740f876aef8c010b86a40d5f56745a118d0906a34e69aec8c0db1cb8fa3"
|
|
)),
|
|
// <https://etherscan.io/block/15537394>
|
|
paris_block_and_final_difficulty: Some((
|
|
15537394,
|
|
U256::from(58_750_003_716_598_352_816_469u128),
|
|
)),
|
|
hardforks: EthereumHardfork::mainnet().into(),
|
|
// https://etherscan.io/tx/0xe75fb554e433e03763a1560646ee22dcb74e5274b34c5ad644e7c0f619a7e1d0
|
|
deposit_contract: Some(DepositContract::new(
|
|
address!("00000000219ab540356cbb839cbe05303d7705fa"),
|
|
11052984,
|
|
b256!("649bbc62d0e31342afea4e5cd82d4049e7e1ee912fc0889aa790803be39038c5"),
|
|
)),
|
|
base_fee_params: BaseFeeParamsKind::Constant(BaseFeeParams::ethereum()),
|
|
prune_delete_limit: 3500,
|
|
}
|
|
.into()
|
|
});
|
|
|
|
/// The Sepolia spec
|
|
pub static SEPOLIA: Lazy<Arc<ChainSpec>> = Lazy::new(|| {
|
|
ChainSpec {
|
|
chain: Chain::sepolia(),
|
|
genesis: serde_json::from_str(include_str!("../res/genesis/sepolia.json"))
|
|
.expect("Can't deserialize Sepolia genesis json"),
|
|
genesis_hash: Some(b256!(
|
|
"25a5cc106eea7138acab33231d7160d69cb777ee0c2c553fcddf5138993e6dd9"
|
|
)),
|
|
// <https://sepolia.etherscan.io/block/1450409>
|
|
paris_block_and_final_difficulty: Some((1450409, U256::from(17_000_018_015_853_232u128))),
|
|
hardforks: EthereumHardfork::sepolia().into(),
|
|
// https://sepolia.etherscan.io/tx/0x025ecbf81a2f1220da6285d1701dc89fb5a956b62562ee922e1a9efd73eb4b14
|
|
deposit_contract: Some(DepositContract::new(
|
|
address!("7f02c3e3c98b133055b8b348b2ac625669ed295d"),
|
|
1273020,
|
|
b256!("649bbc62d0e31342afea4e5cd82d4049e7e1ee912fc0889aa790803be39038c5"),
|
|
)),
|
|
base_fee_params: BaseFeeParamsKind::Constant(BaseFeeParams::ethereum()),
|
|
prune_delete_limit: 1700,
|
|
}
|
|
.into()
|
|
});
|
|
|
|
/// The Holesky spec
|
|
pub static HOLESKY: Lazy<Arc<ChainSpec>> = Lazy::new(|| {
|
|
ChainSpec {
|
|
chain: Chain::holesky(),
|
|
genesis: serde_json::from_str(include_str!("../res/genesis/holesky.json"))
|
|
.expect("Can't deserialize Holesky genesis json"),
|
|
genesis_hash: Some(b256!(
|
|
"b5f7f912443c940f21fd611f12828d75b534364ed9e95ca4e307729a4661bde4"
|
|
)),
|
|
paris_block_and_final_difficulty: Some((0, U256::from(1))),
|
|
hardforks: EthereumHardfork::holesky().into(),
|
|
deposit_contract: Some(DepositContract::new(
|
|
address!("4242424242424242424242424242424242424242"),
|
|
0,
|
|
b256!("649bbc62d0e31342afea4e5cd82d4049e7e1ee912fc0889aa790803be39038c5"),
|
|
)),
|
|
base_fee_params: BaseFeeParamsKind::Constant(BaseFeeParams::ethereum()),
|
|
prune_delete_limit: 1700,
|
|
}
|
|
.into()
|
|
});
|
|
|
|
/// Dev testnet specification
|
|
///
|
|
/// Includes 20 prefunded accounts with `10_000` ETH each derived from mnemonic "test test test test
|
|
/// test test test test test test test junk".
|
|
pub static DEV: Lazy<Arc<ChainSpec>> = Lazy::new(|| {
|
|
ChainSpec {
|
|
chain: Chain::dev(),
|
|
genesis: serde_json::from_str(include_str!("../res/genesis/dev.json"))
|
|
.expect("Can't deserialize Dev testnet genesis json"),
|
|
genesis_hash: Some(b256!(
|
|
"2f980576711e3617a5e4d83dd539548ec0f7792007d505a3d2e9674833af2d7c"
|
|
)),
|
|
paris_block_and_final_difficulty: Some((0, U256::from(0))),
|
|
hardforks: DEV_HARDFORKS.clone(),
|
|
base_fee_params: BaseFeeParamsKind::Constant(BaseFeeParams::ethereum()),
|
|
deposit_contract: None, // TODO: do we even have?
|
|
..Default::default()
|
|
}
|
|
.into()
|
|
});
|
|
|
|
/// The Optimism Mainnet spec
|
|
#[cfg(feature = "optimism")]
|
|
pub static OP_MAINNET: Lazy<Arc<ChainSpec>> = Lazy::new(|| {
|
|
ChainSpec {
|
|
chain: Chain::optimism_mainnet(),
|
|
// genesis contains empty alloc field because state at first bedrock block is imported
|
|
// manually from trusted source
|
|
genesis: serde_json::from_str(include_str!("../res/genesis/optimism.json"))
|
|
.expect("Can't deserialize Optimism Mainnet genesis json"),
|
|
genesis_hash: Some(b256!(
|
|
"7ca38a1916c42007829c55e69d3e9a73265554b586a499015373241b8a3fa48b"
|
|
)),
|
|
paris_block_and_final_difficulty: Some((0, U256::from(0))),
|
|
hardforks: OptimismHardfork::op_mainnet(),
|
|
base_fee_params: BaseFeeParamsKind::Variable(
|
|
vec![
|
|
(EthereumHardfork::London.boxed(), OP_BASE_FEE_PARAMS),
|
|
(OptimismHardfork::Canyon.boxed(), OP_CANYON_BASE_FEE_PARAMS),
|
|
]
|
|
.into(),
|
|
),
|
|
prune_delete_limit: 1700,
|
|
..Default::default()
|
|
}
|
|
.into()
|
|
});
|
|
|
|
/// The OP Sepolia spec
|
|
#[cfg(feature = "optimism")]
|
|
pub static OP_SEPOLIA: Lazy<Arc<ChainSpec>> = Lazy::new(|| {
|
|
ChainSpec {
|
|
chain: Chain::from_named(NamedChain::OptimismSepolia),
|
|
genesis: serde_json::from_str(include_str!("../res/genesis/sepolia_op.json"))
|
|
.expect("Can't deserialize OP Sepolia genesis json"),
|
|
genesis_hash: Some(b256!(
|
|
"102de6ffb001480cc9b8b548fd05c34cd4f46ae4aa91759393db90ea0409887d"
|
|
)),
|
|
paris_block_and_final_difficulty: Some((0, U256::from(0))),
|
|
hardforks: OptimismHardfork::op_sepolia(),
|
|
base_fee_params: BaseFeeParamsKind::Variable(
|
|
vec![
|
|
(EthereumHardfork::London.boxed(), OP_SEPOLIA_BASE_FEE_PARAMS),
|
|
(OptimismHardfork::Canyon.boxed(), OP_SEPOLIA_CANYON_BASE_FEE_PARAMS),
|
|
]
|
|
.into(),
|
|
),
|
|
prune_delete_limit: 1700,
|
|
..Default::default()
|
|
}
|
|
.into()
|
|
});
|
|
|
|
/// The Base Sepolia spec
|
|
#[cfg(feature = "optimism")]
|
|
pub static BASE_SEPOLIA: Lazy<Arc<ChainSpec>> = Lazy::new(|| {
|
|
ChainSpec {
|
|
chain: Chain::base_sepolia(),
|
|
genesis: serde_json::from_str(include_str!("../res/genesis/sepolia_base.json"))
|
|
.expect("Can't deserialize Base Sepolia genesis json"),
|
|
genesis_hash: Some(b256!(
|
|
"0dcc9e089e30b90ddfc55be9a37dd15bc551aeee999d2e2b51414c54eaf934e4"
|
|
)),
|
|
paris_block_and_final_difficulty: Some((0, U256::from(0))),
|
|
hardforks: OptimismHardfork::base_sepolia(),
|
|
base_fee_params: BaseFeeParamsKind::Variable(
|
|
vec![
|
|
(EthereumHardfork::London.boxed(), BASE_SEPOLIA_BASE_FEE_PARAMS),
|
|
(OptimismHardfork::Canyon.boxed(), BASE_SEPOLIA_CANYON_BASE_FEE_PARAMS),
|
|
]
|
|
.into(),
|
|
),
|
|
prune_delete_limit: 1700,
|
|
..Default::default()
|
|
}
|
|
.into()
|
|
});
|
|
|
|
/// The Base mainnet spec
|
|
#[cfg(feature = "optimism")]
|
|
pub static BASE_MAINNET: Lazy<Arc<ChainSpec>> = Lazy::new(|| {
|
|
ChainSpec {
|
|
chain: Chain::base_mainnet(),
|
|
genesis: serde_json::from_str(include_str!("../res/genesis/base.json"))
|
|
.expect("Can't deserialize Base genesis json"),
|
|
genesis_hash: Some(b256!(
|
|
"f712aa9241cc24369b143cf6dce85f0902a9731e70d66818a3a5845b296c73dd"
|
|
)),
|
|
paris_block_and_final_difficulty: Some((0, U256::from(0))),
|
|
hardforks: OptimismHardfork::base_mainnet(),
|
|
base_fee_params: BaseFeeParamsKind::Variable(
|
|
vec![
|
|
(EthereumHardfork::London.boxed(), OP_BASE_FEE_PARAMS),
|
|
(OptimismHardfork::Canyon.boxed(), OP_CANYON_BASE_FEE_PARAMS),
|
|
]
|
|
.into(),
|
|
),
|
|
prune_delete_limit: 1700,
|
|
..Default::default()
|
|
}
|
|
.into()
|
|
});
|
|
|
|
/// A wrapper around [`BaseFeeParams`] that allows for specifying constant or dynamic EIP-1559
|
|
/// parameters based on the active [Hardfork].
|
|
#[derive(Clone, Debug, PartialEq, Eq)]
|
|
pub enum BaseFeeParamsKind {
|
|
/// Constant [`BaseFeeParams`]; used for chains that don't have dynamic EIP-1559 parameters
|
|
Constant(BaseFeeParams),
|
|
/// Variable [`BaseFeeParams`]; used for chains that have dynamic EIP-1559 parameters like
|
|
/// Optimism
|
|
Variable(ForkBaseFeeParams),
|
|
}
|
|
|
|
impl Default for BaseFeeParamsKind {
|
|
fn default() -> Self {
|
|
BaseFeeParams::ethereum().into()
|
|
}
|
|
}
|
|
|
|
impl From<BaseFeeParams> for BaseFeeParamsKind {
|
|
fn from(params: BaseFeeParams) -> Self {
|
|
Self::Constant(params)
|
|
}
|
|
}
|
|
|
|
impl From<ForkBaseFeeParams> for BaseFeeParamsKind {
|
|
fn from(params: ForkBaseFeeParams) -> Self {
|
|
Self::Variable(params)
|
|
}
|
|
}
|
|
|
|
/// A type alias to a vector of tuples of [Hardfork] and [`BaseFeeParams`], sorted by [Hardfork]
|
|
/// activation order. This is used to specify dynamic EIP-1559 parameters for chains like Optimism.
|
|
#[derive(Clone, Debug, PartialEq, Eq, From)]
|
|
pub struct ForkBaseFeeParams(Vec<(Box<dyn Hardfork>, BaseFeeParams)>);
|
|
|
|
impl core::ops::Deref for ChainSpec {
|
|
type Target = ChainHardforks;
|
|
|
|
fn deref(&self) -> &Self::Target {
|
|
&self.hardforks
|
|
}
|
|
}
|
|
|
|
/// An Ethereum chain specification.
|
|
///
|
|
/// A chain specification describes:
|
|
///
|
|
/// - Meta-information about the chain (the chain ID)
|
|
/// - The genesis block of the chain ([`Genesis`])
|
|
/// - What hardforks are activated, and under which conditions
|
|
#[derive(Debug, Clone, PartialEq, Eq)]
|
|
pub struct ChainSpec {
|
|
/// The chain ID
|
|
pub chain: Chain,
|
|
|
|
/// The hash of the genesis block.
|
|
///
|
|
/// This acts as a small cache for known chains. If the chain is known, then the genesis hash
|
|
/// is also known ahead of time, and this will be `Some`.
|
|
pub genesis_hash: Option<B256>,
|
|
|
|
/// The genesis block
|
|
pub genesis: Genesis,
|
|
|
|
/// The block at which [`EthereumHardfork::Paris`] was activated and the final difficulty at
|
|
/// this block.
|
|
pub paris_block_and_final_difficulty: Option<(u64, U256)>,
|
|
|
|
/// The active hard forks and their activation conditions
|
|
pub hardforks: ChainHardforks,
|
|
|
|
/// The deposit contract deployed for `PoS`
|
|
pub deposit_contract: Option<DepositContract>,
|
|
|
|
/// The parameters that configure how a block's base fee is computed
|
|
pub base_fee_params: BaseFeeParamsKind,
|
|
|
|
/// The delete limit for pruner, per block. In the actual pruner run it will be multiplied by
|
|
/// the amount of blocks between pruner runs to account for the difference in amount of new
|
|
/// data coming in.
|
|
pub prune_delete_limit: usize,
|
|
}
|
|
|
|
impl Default for ChainSpec {
|
|
fn default() -> Self {
|
|
Self {
|
|
chain: Default::default(),
|
|
genesis_hash: Default::default(),
|
|
genesis: Default::default(),
|
|
paris_block_and_final_difficulty: Default::default(),
|
|
hardforks: Default::default(),
|
|
deposit_contract: Default::default(),
|
|
base_fee_params: BaseFeeParamsKind::Constant(BaseFeeParams::ethereum()),
|
|
prune_delete_limit: MAINNET.prune_delete_limit,
|
|
}
|
|
}
|
|
}
|
|
|
|
impl ChainSpec {
|
|
/// Get information about the chain itself
|
|
pub const fn chain(&self) -> Chain {
|
|
self.chain
|
|
}
|
|
|
|
/// Returns `true` if this chain contains Ethereum configuration.
|
|
#[inline]
|
|
pub const fn is_eth(&self) -> bool {
|
|
matches!(
|
|
self.chain.kind(),
|
|
ChainKind::Named(
|
|
NamedChain::Mainnet |
|
|
NamedChain::Morden |
|
|
NamedChain::Ropsten |
|
|
NamedChain::Rinkeby |
|
|
NamedChain::Goerli |
|
|
NamedChain::Kovan |
|
|
NamedChain::Holesky |
|
|
NamedChain::Sepolia
|
|
)
|
|
)
|
|
}
|
|
|
|
/// Returns `true` if this chain contains Optimism configuration.
|
|
#[inline]
|
|
#[cfg(feature = "optimism")]
|
|
pub fn is_optimism(&self) -> bool {
|
|
self.chain.is_optimism() || self.hardforks.get(OptimismHardfork::Bedrock).is_some()
|
|
}
|
|
|
|
/// Returns `true` if this chain contains Optimism configuration.
|
|
#[inline]
|
|
#[cfg(not(feature = "optimism"))]
|
|
pub const fn is_optimism(&self) -> bool {
|
|
self.chain.is_optimism()
|
|
}
|
|
|
|
/// Returns `true` if this chain is Optimism mainnet.
|
|
#[inline]
|
|
pub fn is_optimism_mainnet(&self) -> bool {
|
|
self.chain == Chain::optimism_mainnet()
|
|
}
|
|
|
|
/// Get the genesis block specification.
|
|
///
|
|
/// To get the header for the genesis block, use [`Self::genesis_header`] instead.
|
|
pub const fn genesis(&self) -> &Genesis {
|
|
&self.genesis
|
|
}
|
|
|
|
/// Get the header for the genesis block.
|
|
pub fn genesis_header(&self) -> Header {
|
|
// If London is activated at genesis, we set the initial base fee as per EIP-1559.
|
|
let base_fee_per_gas = self.initial_base_fee();
|
|
|
|
// If shanghai is activated, initialize the header with an empty withdrawals hash, and
|
|
// empty withdrawals list.
|
|
let withdrawals_root = self
|
|
.fork(EthereumHardfork::Shanghai)
|
|
.active_at_timestamp(self.genesis.timestamp)
|
|
.then_some(EMPTY_WITHDRAWALS);
|
|
|
|
// If Cancun is activated at genesis, we set:
|
|
// * parent beacon block root to 0x0
|
|
// * blob gas used to provided genesis or 0x0
|
|
// * excess blob gas to provided genesis or 0x0
|
|
let (parent_beacon_block_root, blob_gas_used, excess_blob_gas) =
|
|
if self.is_cancun_active_at_timestamp(self.genesis.timestamp) {
|
|
let blob_gas_used = self.genesis.blob_gas_used.unwrap_or(0);
|
|
let excess_blob_gas = self.genesis.excess_blob_gas.unwrap_or(0);
|
|
(Some(B256::ZERO), Some(blob_gas_used as u64), Some(excess_blob_gas as u64))
|
|
} else {
|
|
(None, None, None)
|
|
};
|
|
|
|
// If Prague is activated at genesis we set requests root to an empty trie root.
|
|
let requests_root = if self.is_prague_active_at_timestamp(self.genesis.timestamp) {
|
|
Some(EMPTY_ROOT_HASH)
|
|
} else {
|
|
None
|
|
};
|
|
|
|
Header {
|
|
gas_limit: self.genesis.gas_limit as u64,
|
|
difficulty: self.genesis.difficulty,
|
|
nonce: self.genesis.nonce,
|
|
extra_data: self.genesis.extra_data.clone(),
|
|
state_root: state_root_ref_unhashed(&self.genesis.alloc),
|
|
timestamp: self.genesis.timestamp,
|
|
mix_hash: self.genesis.mix_hash,
|
|
beneficiary: self.genesis.coinbase,
|
|
base_fee_per_gas,
|
|
withdrawals_root,
|
|
parent_beacon_block_root,
|
|
blob_gas_used,
|
|
excess_blob_gas,
|
|
requests_root,
|
|
..Default::default()
|
|
}
|
|
}
|
|
|
|
/// Get the sealed header for the genesis block.
|
|
pub fn sealed_genesis_header(&self) -> SealedHeader {
|
|
SealedHeader::new(self.genesis_header(), self.genesis_hash())
|
|
}
|
|
|
|
/// Get the initial base fee of the genesis block.
|
|
pub fn initial_base_fee(&self) -> Option<u64> {
|
|
// If the base fee is set in the genesis block, we use that instead of the default.
|
|
let genesis_base_fee =
|
|
self.genesis.base_fee_per_gas.map(|fee| fee as u64).unwrap_or(EIP1559_INITIAL_BASE_FEE);
|
|
|
|
// If London is activated at genesis, we set the initial base fee as per EIP-1559.
|
|
self.hardforks.fork(EthereumHardfork::London).active_at_block(0).then_some(genesis_base_fee)
|
|
}
|
|
|
|
/// Get the [`BaseFeeParams`] for the chain at the given timestamp.
|
|
pub fn base_fee_params_at_timestamp(&self, timestamp: u64) -> BaseFeeParams {
|
|
match self.base_fee_params {
|
|
BaseFeeParamsKind::Constant(bf_params) => bf_params,
|
|
BaseFeeParamsKind::Variable(ForkBaseFeeParams(ref bf_params)) => {
|
|
// Walk through the base fee params configuration in reverse order, and return the
|
|
// first one that corresponds to a hardfork that is active at the
|
|
// given timestamp.
|
|
for (fork, params) in bf_params.iter().rev() {
|
|
if self.hardforks.is_fork_active_at_timestamp(fork.clone(), timestamp) {
|
|
return *params
|
|
}
|
|
}
|
|
|
|
bf_params.first().map(|(_, params)| *params).unwrap_or(BaseFeeParams::ethereum())
|
|
}
|
|
}
|
|
}
|
|
|
|
/// Get the [`BaseFeeParams`] for the chain at the given block number
|
|
pub fn base_fee_params_at_block(&self, block_number: u64) -> BaseFeeParams {
|
|
match self.base_fee_params {
|
|
BaseFeeParamsKind::Constant(bf_params) => bf_params,
|
|
BaseFeeParamsKind::Variable(ForkBaseFeeParams(ref bf_params)) => {
|
|
// Walk through the base fee params configuration in reverse order, and return the
|
|
// first one that corresponds to a hardfork that is active at the
|
|
// given timestamp.
|
|
for (fork, params) in bf_params.iter().rev() {
|
|
if self.hardforks.is_fork_active_at_block(fork.clone(), block_number) {
|
|
return *params
|
|
}
|
|
}
|
|
|
|
bf_params.first().map(|(_, params)| *params).unwrap_or(BaseFeeParams::ethereum())
|
|
}
|
|
}
|
|
}
|
|
|
|
/// Get the hash of the genesis block.
|
|
pub fn genesis_hash(&self) -> B256 {
|
|
self.genesis_hash.unwrap_or_else(|| self.genesis_header().hash_slow())
|
|
}
|
|
|
|
/// Get the timestamp of the genesis block.
|
|
pub const fn genesis_timestamp(&self) -> u64 {
|
|
self.genesis.timestamp
|
|
}
|
|
|
|
/// Returns the final total difficulty if the Paris hardfork is known.
|
|
pub fn get_final_paris_total_difficulty(&self) -> Option<U256> {
|
|
self.paris_block_and_final_difficulty.map(|(_, final_difficulty)| final_difficulty)
|
|
}
|
|
|
|
/// Returns the final total difficulty if the given block number is after the Paris hardfork.
|
|
///
|
|
/// Note: technically this would also be valid for the block before the paris upgrade, but this
|
|
/// edge case is omitted here.
|
|
#[inline]
|
|
pub fn final_paris_total_difficulty(&self, block_number: u64) -> Option<U256> {
|
|
self.paris_block_and_final_difficulty.and_then(|(activated_at, final_difficulty)| {
|
|
(block_number >= activated_at).then_some(final_difficulty)
|
|
})
|
|
}
|
|
|
|
/// Get the fork filter for the given hardfork
|
|
pub fn hardfork_fork_filter<H: Hardfork + Clone>(&self, fork: H) -> Option<ForkFilter> {
|
|
match self.hardforks.fork(fork.clone()) {
|
|
ForkCondition::Never => None,
|
|
_ => Some(self.fork_filter(self.satisfy(self.hardforks.fork(fork)))),
|
|
}
|
|
}
|
|
|
|
/// Returns the hardfork display helper.
|
|
pub fn display_hardforks(&self) -> DisplayHardforks {
|
|
DisplayHardforks::new(
|
|
&self.hardforks,
|
|
self.paris_block_and_final_difficulty.map(|(block, _)| block),
|
|
)
|
|
}
|
|
|
|
/// Get the fork id for the given hardfork.
|
|
#[inline]
|
|
pub fn hardfork_fork_id<H: Hardfork + Clone>(&self, fork: H) -> Option<ForkId> {
|
|
let condition = self.hardforks.fork(fork);
|
|
match condition {
|
|
ForkCondition::Never => None,
|
|
_ => Some(self.fork_id(&self.satisfy(condition))),
|
|
}
|
|
}
|
|
|
|
/// Convenience method to get the fork id for [`EthereumHardfork::Shanghai`] from a given
|
|
/// chainspec.
|
|
#[inline]
|
|
pub fn shanghai_fork_id(&self) -> Option<ForkId> {
|
|
self.hardfork_fork_id(EthereumHardfork::Shanghai)
|
|
}
|
|
|
|
/// Convenience method to get the fork id for [`EthereumHardfork::Cancun`] from a given
|
|
/// chainspec.
|
|
#[inline]
|
|
pub fn cancun_fork_id(&self) -> Option<ForkId> {
|
|
self.hardfork_fork_id(EthereumHardfork::Cancun)
|
|
}
|
|
|
|
/// Convenience method to get the latest fork id from the chainspec. Panics if chainspec has no
|
|
/// hardforks.
|
|
#[inline]
|
|
pub fn latest_fork_id(&self) -> ForkId {
|
|
self.hardfork_fork_id(self.hardforks.last().unwrap().0).unwrap()
|
|
}
|
|
|
|
/// Creates a [`ForkFilter`] for the block described by [Head].
|
|
pub fn fork_filter(&self, head: Head) -> ForkFilter {
|
|
let forks = self.hardforks.forks_iter().filter_map(|(_, condition)| {
|
|
// We filter out TTD-based forks w/o a pre-known block since those do not show up in the
|
|
// fork filter.
|
|
Some(match condition {
|
|
ForkCondition::Block(block) |
|
|
ForkCondition::TTD { fork_block: Some(block), .. } => ForkFilterKey::Block(block),
|
|
ForkCondition::Timestamp(time) => ForkFilterKey::Time(time),
|
|
_ => return None,
|
|
})
|
|
});
|
|
|
|
ForkFilter::new(head, self.genesis_hash(), self.genesis_timestamp(), forks)
|
|
}
|
|
|
|
/// Compute the [`ForkId`] for the given [`Head`] following eip-6122 spec
|
|
pub fn fork_id(&self, head: &Head) -> ForkId {
|
|
let mut forkhash = ForkHash::from(self.genesis_hash());
|
|
let mut current_applied = 0;
|
|
|
|
// handle all block forks before handling timestamp based forks. see: https://eips.ethereum.org/EIPS/eip-6122
|
|
for (_, cond) in self.hardforks.forks_iter() {
|
|
// handle block based forks and the sepolia merge netsplit block edge case (TTD
|
|
// ForkCondition with Some(block))
|
|
if let ForkCondition::Block(block) |
|
|
ForkCondition::TTD { fork_block: Some(block), .. } = cond
|
|
{
|
|
if cond.active_at_head(head) {
|
|
if block != current_applied {
|
|
forkhash += block;
|
|
current_applied = block;
|
|
}
|
|
} else {
|
|
// we can return here because this block fork is not active, so we set the
|
|
// `next` value
|
|
return ForkId { hash: forkhash, next: block }
|
|
}
|
|
}
|
|
}
|
|
|
|
// timestamp are ALWAYS applied after the merge.
|
|
//
|
|
// this filter ensures that no block-based forks are returned
|
|
for timestamp in self.hardforks.forks_iter().filter_map(|(_, cond)| {
|
|
cond.as_timestamp().filter(|time| time > &self.genesis.timestamp)
|
|
}) {
|
|
let cond = ForkCondition::Timestamp(timestamp);
|
|
if cond.active_at_head(head) {
|
|
if timestamp != current_applied {
|
|
forkhash += timestamp;
|
|
current_applied = timestamp;
|
|
}
|
|
} else {
|
|
// can safely return here because we have already handled all block forks and
|
|
// have handled all active timestamp forks, and set the next value to the
|
|
// timestamp that is known but not active yet
|
|
return ForkId { hash: forkhash, next: timestamp }
|
|
}
|
|
}
|
|
|
|
ForkId { hash: forkhash, next: 0 }
|
|
}
|
|
|
|
/// An internal helper function that returns a head block that satisfies a given Fork condition.
|
|
pub(crate) fn satisfy(&self, cond: ForkCondition) -> Head {
|
|
match cond {
|
|
ForkCondition::Block(number) => Head { number, ..Default::default() },
|
|
ForkCondition::Timestamp(timestamp) => {
|
|
// to satisfy every timestamp ForkCondition, we find the last ForkCondition::Block
|
|
// if one exists, and include its block_num in the returned Head
|
|
if let Some(last_block_num) = self.last_block_fork_before_merge_or_timestamp() {
|
|
return Head { timestamp, number: last_block_num, ..Default::default() }
|
|
}
|
|
Head { timestamp, ..Default::default() }
|
|
}
|
|
ForkCondition::TTD { total_difficulty, .. } => {
|
|
Head { total_difficulty, ..Default::default() }
|
|
}
|
|
ForkCondition::Never => unreachable!(),
|
|
}
|
|
}
|
|
|
|
/// An internal helper function that returns the block number of the last block-based
|
|
/// fork that occurs before any existing TTD (merge)/timestamp based forks.
|
|
///
|
|
/// Note: this returns None if the `ChainSpec` is not configured with a TTD/Timestamp fork.
|
|
pub(crate) fn last_block_fork_before_merge_or_timestamp(&self) -> Option<u64> {
|
|
let mut hardforks_iter = self.hardforks.forks_iter().peekable();
|
|
while let Some((_, curr_cond)) = hardforks_iter.next() {
|
|
if let Some((_, next_cond)) = hardforks_iter.peek() {
|
|
// peek and find the first occurrence of ForkCondition::TTD (merge) , or in
|
|
// custom ChainSpecs, the first occurrence of
|
|
// ForkCondition::Timestamp. If curr_cond is ForkCondition::Block at
|
|
// this point, which it should be in most "normal" ChainSpecs,
|
|
// return its block_num
|
|
match next_cond {
|
|
ForkCondition::TTD { fork_block, .. } => {
|
|
// handle Sepolia merge netsplit case
|
|
if fork_block.is_some() {
|
|
return *fork_block
|
|
}
|
|
// ensure curr_cond is indeed ForkCondition::Block and return block_num
|
|
if let ForkCondition::Block(block_num) = curr_cond {
|
|
return Some(block_num)
|
|
}
|
|
}
|
|
ForkCondition::Timestamp(_) => {
|
|
// ensure curr_cond is indeed ForkCondition::Block and return block_num
|
|
if let ForkCondition::Block(block_num) = curr_cond {
|
|
return Some(block_num)
|
|
}
|
|
}
|
|
ForkCondition::Block(_) | ForkCondition::Never => continue,
|
|
}
|
|
}
|
|
}
|
|
None
|
|
}
|
|
|
|
/// Build a chainspec using [`ChainSpecBuilder`]
|
|
pub fn builder() -> ChainSpecBuilder {
|
|
ChainSpecBuilder::default()
|
|
}
|
|
|
|
/// Returns the known bootnode records for the given chain.
|
|
pub fn bootnodes(&self) -> Option<Vec<NodeRecord>> {
|
|
use NamedChain as C;
|
|
let chain = self.chain;
|
|
match chain.try_into().ok()? {
|
|
C::Mainnet => Some(mainnet_nodes()),
|
|
C::Sepolia => Some(sepolia_nodes()),
|
|
C::Holesky => Some(holesky_nodes()),
|
|
C::Base => Some(base_nodes()),
|
|
C::Optimism => Some(op_nodes()),
|
|
C::BaseGoerli | C::BaseSepolia => Some(base_testnet_nodes()),
|
|
C::OptimismSepolia | C::OptimismGoerli | C::OptimismKovan => Some(op_testnet_nodes()),
|
|
_ => None,
|
|
}
|
|
}
|
|
}
|
|
|
|
impl From<Genesis> for ChainSpec {
|
|
fn from(genesis: Genesis) -> Self {
|
|
#[cfg(feature = "optimism")]
|
|
let optimism_genesis_info = OptimismGenesisInfo::extract_from(&genesis);
|
|
#[cfg(feature = "optimism")]
|
|
let genesis_info =
|
|
optimism_genesis_info.optimism_chain_info.genesis_info.unwrap_or_default();
|
|
|
|
// Block-based hardforks
|
|
let hardfork_opts = [
|
|
(EthereumHardfork::Homestead.boxed(), genesis.config.homestead_block),
|
|
(EthereumHardfork::Dao.boxed(), genesis.config.dao_fork_block),
|
|
(EthereumHardfork::Tangerine.boxed(), genesis.config.eip150_block),
|
|
(EthereumHardfork::SpuriousDragon.boxed(), genesis.config.eip155_block),
|
|
(EthereumHardfork::Byzantium.boxed(), genesis.config.byzantium_block),
|
|
(EthereumHardfork::Constantinople.boxed(), genesis.config.constantinople_block),
|
|
(EthereumHardfork::Petersburg.boxed(), genesis.config.petersburg_block),
|
|
(EthereumHardfork::Istanbul.boxed(), genesis.config.istanbul_block),
|
|
(EthereumHardfork::MuirGlacier.boxed(), genesis.config.muir_glacier_block),
|
|
(EthereumHardfork::Berlin.boxed(), genesis.config.berlin_block),
|
|
(EthereumHardfork::London.boxed(), genesis.config.london_block),
|
|
(EthereumHardfork::ArrowGlacier.boxed(), genesis.config.arrow_glacier_block),
|
|
(EthereumHardfork::GrayGlacier.boxed(), genesis.config.gray_glacier_block),
|
|
#[cfg(feature = "optimism")]
|
|
(OptimismHardfork::Bedrock.boxed(), genesis_info.bedrock_block),
|
|
];
|
|
let mut hardforks = hardfork_opts
|
|
.into_iter()
|
|
.filter_map(|(hardfork, opt)| opt.map(|block| (hardfork, ForkCondition::Block(block))))
|
|
.collect::<Vec<_>>();
|
|
|
|
// Paris
|
|
let paris_block_and_final_difficulty =
|
|
if let Some(ttd) = genesis.config.terminal_total_difficulty {
|
|
hardforks.push((
|
|
EthereumHardfork::Paris.boxed(),
|
|
ForkCondition::TTD {
|
|
total_difficulty: ttd,
|
|
fork_block: genesis.config.merge_netsplit_block,
|
|
},
|
|
));
|
|
|
|
genesis.config.merge_netsplit_block.map(|block| (block, ttd))
|
|
} else {
|
|
None
|
|
};
|
|
|
|
// Time-based hardforks
|
|
let time_hardfork_opts = [
|
|
(EthereumHardfork::Shanghai.boxed(), genesis.config.shanghai_time),
|
|
(EthereumHardfork::Cancun.boxed(), genesis.config.cancun_time),
|
|
(EthereumHardfork::Prague.boxed(), genesis.config.prague_time),
|
|
#[cfg(feature = "optimism")]
|
|
(OptimismHardfork::Regolith.boxed(), genesis_info.regolith_time),
|
|
#[cfg(feature = "optimism")]
|
|
(OptimismHardfork::Canyon.boxed(), genesis_info.canyon_time),
|
|
#[cfg(feature = "optimism")]
|
|
(OptimismHardfork::Ecotone.boxed(), genesis_info.ecotone_time),
|
|
#[cfg(feature = "optimism")]
|
|
(OptimismHardfork::Fjord.boxed(), genesis_info.fjord_time),
|
|
];
|
|
|
|
let time_hardforks = time_hardfork_opts
|
|
.into_iter()
|
|
.filter_map(|(hardfork, opt)| {
|
|
opt.map(|time| (hardfork, ForkCondition::Timestamp(time)))
|
|
})
|
|
.collect::<Vec<_>>();
|
|
|
|
hardforks.extend(time_hardforks);
|
|
|
|
// Uses ethereum or optimism main chains to find proper order
|
|
#[cfg(not(feature = "optimism"))]
|
|
let mainnet_hardforks: ChainHardforks = EthereumHardfork::mainnet().into();
|
|
#[cfg(not(feature = "optimism"))]
|
|
let mainnet_order = mainnet_hardforks.forks_iter();
|
|
#[cfg(feature = "optimism")]
|
|
let mainnet_hardforks = OptimismHardfork::op_mainnet();
|
|
#[cfg(feature = "optimism")]
|
|
let mainnet_order = mainnet_hardforks.forks_iter();
|
|
|
|
let mut ordered_hardforks = Vec::with_capacity(hardforks.len());
|
|
for (hardfork, _) in mainnet_order {
|
|
if let Some(pos) = hardforks.iter().position(|(e, _)| **e == *hardfork) {
|
|
ordered_hardforks.push(hardforks[pos].clone());
|
|
}
|
|
}
|
|
|
|
// NOTE: in full node, we prune all receipts except the deposit contract's. We do not
|
|
// have the deployment block in the genesis file, so we use block zero. We use the same
|
|
// deposit topic as the mainnet contract if we have the deposit contract address in the
|
|
// genesis json.
|
|
let deposit_contract = genesis.config.deposit_contract_address.map(|address| {
|
|
DepositContract { address, block: 0, topic: MAINNET_DEPOSIT_CONTRACT.topic }
|
|
});
|
|
|
|
Self {
|
|
chain: genesis.config.chain_id.into(),
|
|
genesis,
|
|
genesis_hash: None,
|
|
hardforks: ChainHardforks::new(hardforks),
|
|
paris_block_and_final_difficulty,
|
|
deposit_contract,
|
|
#[cfg(feature = "optimism")]
|
|
base_fee_params: optimism_genesis_info.base_fee_params,
|
|
..Default::default()
|
|
}
|
|
}
|
|
}
|
|
|
|
/// A helper to build custom chain specs
|
|
#[derive(Debug, Default, Clone)]
|
|
pub struct ChainSpecBuilder {
|
|
chain: Option<Chain>,
|
|
genesis: Option<Genesis>,
|
|
hardforks: ChainHardforks,
|
|
}
|
|
|
|
impl ChainSpecBuilder {
|
|
/// Construct a new builder from the mainnet chain spec.
|
|
pub fn mainnet() -> Self {
|
|
Self {
|
|
chain: Some(MAINNET.chain),
|
|
genesis: Some(MAINNET.genesis.clone()),
|
|
hardforks: MAINNET.hardforks.clone(),
|
|
}
|
|
}
|
|
}
|
|
|
|
impl ChainSpecBuilder {
|
|
/// Set the chain ID
|
|
pub const fn chain(mut self, chain: Chain) -> Self {
|
|
self.chain = Some(chain);
|
|
self
|
|
}
|
|
|
|
/// Set the genesis block.
|
|
pub fn genesis(mut self, genesis: Genesis) -> Self {
|
|
self.genesis = Some(genesis);
|
|
self
|
|
}
|
|
|
|
/// Add the given fork with the given activation condition to the spec.
|
|
pub fn with_fork(mut self, fork: EthereumHardfork, condition: ForkCondition) -> Self {
|
|
self.hardforks.insert(fork, condition);
|
|
self
|
|
}
|
|
|
|
/// Remove the given fork from the spec.
|
|
pub fn without_fork(mut self, fork: EthereumHardfork) -> Self {
|
|
self.hardforks.remove(fork);
|
|
self
|
|
}
|
|
|
|
/// Enable the Paris hardfork at the given TTD.
|
|
///
|
|
/// Does not set the merge netsplit block.
|
|
pub fn paris_at_ttd(self, ttd: U256) -> Self {
|
|
self.with_fork(
|
|
EthereumHardfork::Paris,
|
|
ForkCondition::TTD { total_difficulty: ttd, fork_block: None },
|
|
)
|
|
}
|
|
|
|
/// Enable Frontier at genesis.
|
|
pub fn frontier_activated(mut self) -> Self {
|
|
self.hardforks.insert(EthereumHardfork::Frontier, ForkCondition::Block(0));
|
|
self
|
|
}
|
|
|
|
/// Enable Homestead at genesis.
|
|
pub fn homestead_activated(mut self) -> Self {
|
|
self = self.frontier_activated();
|
|
self.hardforks.insert(EthereumHardfork::Homestead, ForkCondition::Block(0));
|
|
self
|
|
}
|
|
|
|
/// Enable Tangerine at genesis.
|
|
pub fn tangerine_whistle_activated(mut self) -> Self {
|
|
self = self.homestead_activated();
|
|
self.hardforks.insert(EthereumHardfork::Tangerine, ForkCondition::Block(0));
|
|
self
|
|
}
|
|
|
|
/// Enable Spurious Dragon at genesis.
|
|
pub fn spurious_dragon_activated(mut self) -> Self {
|
|
self = self.tangerine_whistle_activated();
|
|
self.hardforks.insert(EthereumHardfork::SpuriousDragon, ForkCondition::Block(0));
|
|
self
|
|
}
|
|
|
|
/// Enable Byzantium at genesis.
|
|
pub fn byzantium_activated(mut self) -> Self {
|
|
self = self.spurious_dragon_activated();
|
|
self.hardforks.insert(EthereumHardfork::Byzantium, ForkCondition::Block(0));
|
|
self
|
|
}
|
|
|
|
/// Enable Constantinople at genesis.
|
|
pub fn constantinople_activated(mut self) -> Self {
|
|
self = self.byzantium_activated();
|
|
self.hardforks.insert(EthereumHardfork::Constantinople, ForkCondition::Block(0));
|
|
self
|
|
}
|
|
|
|
/// Enable Petersburg at genesis.
|
|
pub fn petersburg_activated(mut self) -> Self {
|
|
self = self.constantinople_activated();
|
|
self.hardforks.insert(EthereumHardfork::Petersburg, ForkCondition::Block(0));
|
|
self
|
|
}
|
|
|
|
/// Enable Istanbul at genesis.
|
|
pub fn istanbul_activated(mut self) -> Self {
|
|
self = self.petersburg_activated();
|
|
self.hardforks.insert(EthereumHardfork::Istanbul, ForkCondition::Block(0));
|
|
self
|
|
}
|
|
|
|
/// Enable Berlin at genesis.
|
|
pub fn berlin_activated(mut self) -> Self {
|
|
self = self.istanbul_activated();
|
|
self.hardforks.insert(EthereumHardfork::Berlin, ForkCondition::Block(0));
|
|
self
|
|
}
|
|
|
|
/// Enable London at genesis.
|
|
pub fn london_activated(mut self) -> Self {
|
|
self = self.berlin_activated();
|
|
self.hardforks.insert(EthereumHardfork::London, ForkCondition::Block(0));
|
|
self
|
|
}
|
|
|
|
/// Enable Paris at genesis.
|
|
pub fn paris_activated(mut self) -> Self {
|
|
self = self.london_activated();
|
|
self.hardforks.insert(
|
|
EthereumHardfork::Paris,
|
|
ForkCondition::TTD { fork_block: Some(0), total_difficulty: U256::ZERO },
|
|
);
|
|
self
|
|
}
|
|
|
|
/// Enable Shanghai at genesis.
|
|
pub fn shanghai_activated(mut self) -> Self {
|
|
self = self.paris_activated();
|
|
self.hardforks.insert(EthereumHardfork::Shanghai, ForkCondition::Timestamp(0));
|
|
self
|
|
}
|
|
|
|
/// Enable Cancun at genesis.
|
|
pub fn cancun_activated(mut self) -> Self {
|
|
self = self.shanghai_activated();
|
|
self.hardforks.insert(EthereumHardfork::Cancun, ForkCondition::Timestamp(0));
|
|
self
|
|
}
|
|
|
|
/// Enable Bedrock at genesis
|
|
#[cfg(feature = "optimism")]
|
|
pub fn bedrock_activated(mut self) -> Self {
|
|
self = self.paris_activated();
|
|
self.hardforks.insert(OptimismHardfork::Bedrock, ForkCondition::Block(0));
|
|
self
|
|
}
|
|
|
|
/// Enable Regolith at genesis
|
|
#[cfg(feature = "optimism")]
|
|
pub fn regolith_activated(mut self) -> Self {
|
|
self = self.bedrock_activated();
|
|
self.hardforks.insert(OptimismHardfork::Regolith, ForkCondition::Timestamp(0));
|
|
self
|
|
}
|
|
|
|
/// Enable Canyon at genesis
|
|
#[cfg(feature = "optimism")]
|
|
pub fn canyon_activated(mut self) -> Self {
|
|
self = self.regolith_activated();
|
|
// Canyon also activates changes from L1's Shanghai hardfork
|
|
self.hardforks.insert(EthereumHardfork::Shanghai, ForkCondition::Timestamp(0));
|
|
self.hardforks.insert(OptimismHardfork::Canyon, ForkCondition::Timestamp(0));
|
|
self
|
|
}
|
|
|
|
/// Enable Ecotone at genesis
|
|
#[cfg(feature = "optimism")]
|
|
pub fn ecotone_activated(mut self) -> Self {
|
|
self = self.canyon_activated();
|
|
self.hardforks.insert(EthereumHardfork::Cancun, ForkCondition::Timestamp(0));
|
|
self.hardforks.insert(OptimismHardfork::Ecotone, ForkCondition::Timestamp(0));
|
|
self
|
|
}
|
|
|
|
/// Enable Fjord at genesis
|
|
#[cfg(feature = "optimism")]
|
|
pub fn fjord_activated(mut self) -> Self {
|
|
self = self.ecotone_activated();
|
|
self.hardforks.insert(OptimismHardfork::Fjord, ForkCondition::Timestamp(0));
|
|
self
|
|
}
|
|
|
|
/// Build the resulting [`ChainSpec`].
|
|
///
|
|
/// # Panics
|
|
///
|
|
/// This function panics if the chain ID and genesis is not set ([`Self::chain`] and
|
|
/// [`Self::genesis`])
|
|
pub fn build(self) -> ChainSpec {
|
|
let paris_block_and_final_difficulty = {
|
|
self.hardforks.get(EthereumHardfork::Paris).and_then(|cond| {
|
|
if let ForkCondition::TTD { fork_block, total_difficulty } = cond {
|
|
fork_block.map(|fork_block| (fork_block, total_difficulty))
|
|
} else {
|
|
None
|
|
}
|
|
})
|
|
};
|
|
ChainSpec {
|
|
chain: self.chain.expect("The chain is required"),
|
|
genesis: self.genesis.expect("The genesis is required"),
|
|
genesis_hash: None,
|
|
hardforks: self.hardforks,
|
|
paris_block_and_final_difficulty,
|
|
deposit_contract: None,
|
|
..Default::default()
|
|
}
|
|
}
|
|
}
|
|
|
|
impl From<&Arc<ChainSpec>> for ChainSpecBuilder {
|
|
fn from(value: &Arc<ChainSpec>) -> Self {
|
|
Self {
|
|
chain: Some(value.chain),
|
|
genesis: Some(value.genesis.clone()),
|
|
hardforks: value.hardforks.clone(),
|
|
}
|
|
}
|
|
}
|
|
|
|
/// `PoS` deposit contract details.
|
|
#[derive(Debug, Clone, PartialEq, Eq)]
|
|
pub struct DepositContract {
|
|
/// Deposit Contract Address
|
|
pub address: Address,
|
|
/// Deployment Block
|
|
pub block: BlockNumber,
|
|
/// `DepositEvent` event signature
|
|
pub topic: B256,
|
|
}
|
|
|
|
impl DepositContract {
|
|
/// Creates a new [`DepositContract`].
|
|
pub const fn new(address: Address, block: BlockNumber, topic: B256) -> Self {
|
|
Self { address, block, topic }
|
|
}
|
|
}
|
|
|
|
/// Genesis info for Optimism.
|
|
#[cfg(feature = "optimism")]
|
|
#[derive(Default, Debug, serde::Deserialize)]
|
|
#[serde(rename_all = "camelCase")]
|
|
struct OptimismGenesisInfo {
|
|
optimism_chain_info: op_alloy_rpc_types::genesis::OptimismChainInfo,
|
|
#[serde(skip)]
|
|
base_fee_params: BaseFeeParamsKind,
|
|
}
|
|
|
|
#[cfg(feature = "optimism")]
|
|
impl OptimismGenesisInfo {
|
|
fn extract_from(genesis: &Genesis) -> Self {
|
|
let mut info = Self {
|
|
optimism_chain_info: op_alloy_rpc_types::genesis::OptimismChainInfo::extract_from(
|
|
&genesis.config.extra_fields,
|
|
)
|
|
.unwrap_or_default(),
|
|
..Default::default()
|
|
};
|
|
if let Some(optimism_base_fee_info) = &info.optimism_chain_info.base_fee_info {
|
|
if let (Some(elasticity), Some(denominator)) = (
|
|
optimism_base_fee_info.eip1559_elasticity,
|
|
optimism_base_fee_info.eip1559_denominator,
|
|
) {
|
|
let base_fee_params = if let Some(canyon_denominator) =
|
|
optimism_base_fee_info.eip1559_denominator_canyon
|
|
{
|
|
BaseFeeParamsKind::Variable(
|
|
vec![
|
|
(
|
|
EthereumHardfork::London.boxed(),
|
|
BaseFeeParams::new(denominator as u128, elasticity as u128),
|
|
),
|
|
(
|
|
OptimismHardfork::Canyon.boxed(),
|
|
BaseFeeParams::new(canyon_denominator as u128, elasticity as u128),
|
|
),
|
|
]
|
|
.into(),
|
|
)
|
|
} else {
|
|
BaseFeeParams::new(denominator as u128, elasticity as u128).into()
|
|
};
|
|
|
|
info.base_fee_params = base_fee_params;
|
|
}
|
|
}
|
|
|
|
info
|
|
}
|
|
}
|
|
|
|
#[cfg(test)]
|
|
mod tests {
|
|
use super::*;
|
|
use alloy_chains::Chain;
|
|
use alloy_genesis::{ChainConfig, GenesisAccount};
|
|
use alloy_primitives::{b256, hex};
|
|
use core::ops::Deref;
|
|
use reth_ethereum_forks::{ForkCondition, ForkHash, ForkId, Head};
|
|
use reth_trie_common::TrieAccount;
|
|
use std::{collections::HashMap, str::FromStr};
|
|
|
|
#[cfg(feature = "optimism")]
|
|
use reth_ethereum_forks::OptimismHardforks;
|
|
|
|
fn test_fork_ids(spec: &ChainSpec, cases: &[(Head, ForkId)]) {
|
|
for (block, expected_id) in cases {
|
|
let computed_id = spec.fork_id(block);
|
|
assert_eq!(
|
|
expected_id, &computed_id,
|
|
"Expected fork ID {:?}, computed fork ID {:?} at block {}",
|
|
expected_id, computed_id, block.number
|
|
);
|
|
}
|
|
}
|
|
|
|
fn test_hardfork_fork_ids(spec: &ChainSpec, cases: &[(EthereumHardfork, ForkId)]) {
|
|
for (hardfork, expected_id) in cases {
|
|
if let Some(computed_id) = spec.hardfork_fork_id(*hardfork) {
|
|
assert_eq!(
|
|
expected_id, &computed_id,
|
|
"Expected fork ID {expected_id:?}, computed fork ID {computed_id:?} for hardfork {hardfork}"
|
|
);
|
|
if matches!(hardfork, EthereumHardfork::Shanghai) {
|
|
if let Some(shangai_id) = spec.shanghai_fork_id() {
|
|
assert_eq!(
|
|
expected_id, &shangai_id,
|
|
"Expected fork ID {expected_id:?}, computed fork ID {computed_id:?} for Shanghai hardfork"
|
|
);
|
|
} else {
|
|
panic!("Expected ForkCondition to return Some for Hardfork::Shanghai");
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn test_hardfork_list_display_mainnet() {
|
|
assert_eq!(
|
|
MAINNET.display_hardforks().to_string(),
|
|
"Pre-merge hard forks (block based):
|
|
- Frontier @0
|
|
- Homestead @1150000
|
|
- Dao @1920000
|
|
- Tangerine @2463000
|
|
- SpuriousDragon @2675000
|
|
- Byzantium @4370000
|
|
- Constantinople @7280000
|
|
- Petersburg @7280000
|
|
- Istanbul @9069000
|
|
- MuirGlacier @9200000
|
|
- Berlin @12244000
|
|
- London @12965000
|
|
- ArrowGlacier @13773000
|
|
- GrayGlacier @15050000
|
|
Merge hard forks:
|
|
- Paris @58750000000000000000000 (network is known to be merged)
|
|
Post-merge hard forks (timestamp based):
|
|
- Shanghai @1681338455
|
|
- Cancun @1710338135"
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn test_hardfork_list_ignores_disabled_forks() {
|
|
let spec = ChainSpec::builder()
|
|
.chain(Chain::mainnet())
|
|
.genesis(Genesis::default())
|
|
.with_fork(EthereumHardfork::Frontier, ForkCondition::Block(0))
|
|
.with_fork(EthereumHardfork::Shanghai, ForkCondition::Never)
|
|
.build();
|
|
assert_eq!(
|
|
spec.display_hardforks().to_string(),
|
|
"Pre-merge hard forks (block based):
|
|
- Frontier @0"
|
|
);
|
|
}
|
|
|
|
// Tests that we skip any fork blocks in block #0 (the genesis ruleset)
|
|
#[test]
|
|
fn ignores_genesis_fork_blocks() {
|
|
let spec = ChainSpec::builder()
|
|
.chain(Chain::mainnet())
|
|
.genesis(Genesis::default())
|
|
.with_fork(EthereumHardfork::Frontier, ForkCondition::Block(0))
|
|
.with_fork(EthereumHardfork::Homestead, ForkCondition::Block(0))
|
|
.with_fork(EthereumHardfork::Tangerine, ForkCondition::Block(0))
|
|
.with_fork(EthereumHardfork::SpuriousDragon, ForkCondition::Block(0))
|
|
.with_fork(EthereumHardfork::Byzantium, ForkCondition::Block(0))
|
|
.with_fork(EthereumHardfork::Constantinople, ForkCondition::Block(0))
|
|
.with_fork(EthereumHardfork::Istanbul, ForkCondition::Block(0))
|
|
.with_fork(EthereumHardfork::MuirGlacier, ForkCondition::Block(0))
|
|
.with_fork(EthereumHardfork::Berlin, ForkCondition::Block(0))
|
|
.with_fork(EthereumHardfork::London, ForkCondition::Block(0))
|
|
.with_fork(EthereumHardfork::ArrowGlacier, ForkCondition::Block(0))
|
|
.with_fork(EthereumHardfork::GrayGlacier, ForkCondition::Block(0))
|
|
.build();
|
|
|
|
assert_eq!(spec.deref().len(), 12, "12 forks should be active.");
|
|
assert_eq!(
|
|
spec.fork_id(&Head { number: 1, ..Default::default() }),
|
|
ForkId { hash: ForkHash::from(spec.genesis_hash()), next: 0 },
|
|
"the fork ID should be the genesis hash; forks at genesis are ignored for fork filters"
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn ignores_duplicate_fork_blocks() {
|
|
let empty_genesis = Genesis::default();
|
|
let unique_spec = ChainSpec::builder()
|
|
.chain(Chain::mainnet())
|
|
.genesis(empty_genesis.clone())
|
|
.with_fork(EthereumHardfork::Frontier, ForkCondition::Block(0))
|
|
.with_fork(EthereumHardfork::Homestead, ForkCondition::Block(1))
|
|
.build();
|
|
|
|
let duplicate_spec = ChainSpec::builder()
|
|
.chain(Chain::mainnet())
|
|
.genesis(empty_genesis)
|
|
.with_fork(EthereumHardfork::Frontier, ForkCondition::Block(0))
|
|
.with_fork(EthereumHardfork::Homestead, ForkCondition::Block(1))
|
|
.with_fork(EthereumHardfork::Tangerine, ForkCondition::Block(1))
|
|
.build();
|
|
|
|
assert_eq!(
|
|
unique_spec.fork_id(&Head { number: 2, ..Default::default() }),
|
|
duplicate_spec.fork_id(&Head { number: 2, ..Default::default() }),
|
|
"duplicate fork blocks should be deduplicated for fork filters"
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn test_chainspec_satisfy() {
|
|
let empty_genesis = Genesis::default();
|
|
// happy path test case
|
|
let happy_path_case = ChainSpec::builder()
|
|
.chain(Chain::mainnet())
|
|
.genesis(empty_genesis.clone())
|
|
.with_fork(EthereumHardfork::Frontier, ForkCondition::Block(0))
|
|
.with_fork(EthereumHardfork::Homestead, ForkCondition::Block(73))
|
|
.with_fork(EthereumHardfork::Shanghai, ForkCondition::Timestamp(11313123))
|
|
.build();
|
|
let happy_path_head = happy_path_case.satisfy(ForkCondition::Timestamp(11313123));
|
|
let happy_path_expected = Head { number: 73, timestamp: 11313123, ..Default::default() };
|
|
assert_eq!(
|
|
happy_path_head, happy_path_expected,
|
|
"expected satisfy() to return {happy_path_expected:#?}, but got {happy_path_head:#?} "
|
|
);
|
|
// multiple timestamp test case (i.e Shanghai -> Cancun)
|
|
let multiple_timestamp_fork_case = ChainSpec::builder()
|
|
.chain(Chain::mainnet())
|
|
.genesis(empty_genesis.clone())
|
|
.with_fork(EthereumHardfork::Frontier, ForkCondition::Block(0))
|
|
.with_fork(EthereumHardfork::Homestead, ForkCondition::Block(73))
|
|
.with_fork(EthereumHardfork::Shanghai, ForkCondition::Timestamp(11313123))
|
|
.with_fork(EthereumHardfork::Cancun, ForkCondition::Timestamp(11313398))
|
|
.build();
|
|
let multi_timestamp_head =
|
|
multiple_timestamp_fork_case.satisfy(ForkCondition::Timestamp(11313398));
|
|
let mult_timestamp_expected =
|
|
Head { number: 73, timestamp: 11313398, ..Default::default() };
|
|
assert_eq!(
|
|
multi_timestamp_head, mult_timestamp_expected,
|
|
"expected satisfy() to return {mult_timestamp_expected:#?}, but got {multi_timestamp_head:#?} "
|
|
);
|
|
// no ForkCondition::Block test case
|
|
let no_block_fork_case = ChainSpec::builder()
|
|
.chain(Chain::mainnet())
|
|
.genesis(empty_genesis.clone())
|
|
.with_fork(EthereumHardfork::Shanghai, ForkCondition::Timestamp(11313123))
|
|
.build();
|
|
let no_block_fork_head = no_block_fork_case.satisfy(ForkCondition::Timestamp(11313123));
|
|
let no_block_fork_expected = Head { number: 0, timestamp: 11313123, ..Default::default() };
|
|
assert_eq!(
|
|
no_block_fork_head, no_block_fork_expected,
|
|
"expected satisfy() to return {no_block_fork_expected:#?}, but got {no_block_fork_head:#?} ",
|
|
);
|
|
// spec w/ ForkCondition::TTD with block_num test case (Sepolia merge netsplit edge case)
|
|
let fork_cond_ttd_blocknum_case = ChainSpec::builder()
|
|
.chain(Chain::mainnet())
|
|
.genesis(empty_genesis.clone())
|
|
.with_fork(EthereumHardfork::Frontier, ForkCondition::Block(0))
|
|
.with_fork(EthereumHardfork::Homestead, ForkCondition::Block(73))
|
|
.with_fork(
|
|
EthereumHardfork::Paris,
|
|
ForkCondition::TTD {
|
|
fork_block: Some(101),
|
|
total_difficulty: U256::from(10_790_000),
|
|
},
|
|
)
|
|
.with_fork(EthereumHardfork::Shanghai, ForkCondition::Timestamp(11313123))
|
|
.build();
|
|
let fork_cond_ttd_blocknum_head =
|
|
fork_cond_ttd_blocknum_case.satisfy(ForkCondition::Timestamp(11313123));
|
|
let fork_cond_ttd_blocknum_expected =
|
|
Head { number: 101, timestamp: 11313123, ..Default::default() };
|
|
assert_eq!(
|
|
fork_cond_ttd_blocknum_head, fork_cond_ttd_blocknum_expected,
|
|
"expected satisfy() to return {fork_cond_ttd_blocknum_expected:#?}, but got {fork_cond_ttd_blocknum_expected:#?} ",
|
|
);
|
|
|
|
// spec w/ only ForkCondition::Block - test the match arm for ForkCondition::Block to ensure
|
|
// no regressions, for these ForkConditions(Block/TTD) - a separate chain spec definition is
|
|
// technically unnecessary - but we include it here for thoroughness
|
|
let fork_cond_block_only_case = ChainSpec::builder()
|
|
.chain(Chain::mainnet())
|
|
.genesis(empty_genesis)
|
|
.with_fork(EthereumHardfork::Frontier, ForkCondition::Block(0))
|
|
.with_fork(EthereumHardfork::Homestead, ForkCondition::Block(73))
|
|
.build();
|
|
let fork_cond_block_only_head = fork_cond_block_only_case.satisfy(ForkCondition::Block(73));
|
|
let fork_cond_block_only_expected = Head { number: 73, ..Default::default() };
|
|
assert_eq!(
|
|
fork_cond_block_only_head, fork_cond_block_only_expected,
|
|
"expected satisfy() to return {fork_cond_block_only_expected:#?}, but got {fork_cond_block_only_head:#?} ",
|
|
);
|
|
// Fork::ConditionTTD test case without a new chain spec to demonstrate ChainSpec::satisfy
|
|
// is independent of ChainSpec for this(these - including ForkCondition::Block) match arm(s)
|
|
let fork_cond_ttd_no_new_spec = fork_cond_block_only_case.satisfy(ForkCondition::TTD {
|
|
fork_block: None,
|
|
total_difficulty: U256::from(10_790_000),
|
|
});
|
|
let fork_cond_ttd_no_new_spec_expected =
|
|
Head { total_difficulty: U256::from(10_790_000), ..Default::default() };
|
|
assert_eq!(
|
|
fork_cond_ttd_no_new_spec, fork_cond_ttd_no_new_spec_expected,
|
|
"expected satisfy() to return {fork_cond_ttd_blocknum_expected:#?}, but got {fork_cond_ttd_blocknum_expected:#?} ",
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn mainnet_hardfork_fork_ids() {
|
|
test_hardfork_fork_ids(
|
|
&MAINNET,
|
|
&[
|
|
(
|
|
EthereumHardfork::Frontier,
|
|
ForkId { hash: ForkHash([0xfc, 0x64, 0xec, 0x04]), next: 1150000 },
|
|
),
|
|
(
|
|
EthereumHardfork::Homestead,
|
|
ForkId { hash: ForkHash([0x97, 0xc2, 0xc3, 0x4c]), next: 1920000 },
|
|
),
|
|
(
|
|
EthereumHardfork::Dao,
|
|
ForkId { hash: ForkHash([0x91, 0xd1, 0xf9, 0x48]), next: 2463000 },
|
|
),
|
|
(
|
|
EthereumHardfork::Tangerine,
|
|
ForkId { hash: ForkHash([0x7a, 0x64, 0xda, 0x13]), next: 2675000 },
|
|
),
|
|
(
|
|
EthereumHardfork::SpuriousDragon,
|
|
ForkId { hash: ForkHash([0x3e, 0xdd, 0x5b, 0x10]), next: 4370000 },
|
|
),
|
|
(
|
|
EthereumHardfork::Byzantium,
|
|
ForkId { hash: ForkHash([0xa0, 0x0b, 0xc3, 0x24]), next: 7280000 },
|
|
),
|
|
(
|
|
EthereumHardfork::Constantinople,
|
|
ForkId { hash: ForkHash([0x66, 0x8d, 0xb0, 0xaf]), next: 9069000 },
|
|
),
|
|
(
|
|
EthereumHardfork::Petersburg,
|
|
ForkId { hash: ForkHash([0x66, 0x8d, 0xb0, 0xaf]), next: 9069000 },
|
|
),
|
|
(
|
|
EthereumHardfork::Istanbul,
|
|
ForkId { hash: ForkHash([0x87, 0x9d, 0x6e, 0x30]), next: 9200000 },
|
|
),
|
|
(
|
|
EthereumHardfork::MuirGlacier,
|
|
ForkId { hash: ForkHash([0xe0, 0x29, 0xe9, 0x91]), next: 12244000 },
|
|
),
|
|
(
|
|
EthereumHardfork::Berlin,
|
|
ForkId { hash: ForkHash([0x0e, 0xb4, 0x40, 0xf6]), next: 12965000 },
|
|
),
|
|
(
|
|
EthereumHardfork::London,
|
|
ForkId { hash: ForkHash([0xb7, 0x15, 0x07, 0x7d]), next: 13773000 },
|
|
),
|
|
(
|
|
EthereumHardfork::ArrowGlacier,
|
|
ForkId { hash: ForkHash([0x20, 0xc3, 0x27, 0xfc]), next: 15050000 },
|
|
),
|
|
(
|
|
EthereumHardfork::GrayGlacier,
|
|
ForkId { hash: ForkHash([0xf0, 0xaf, 0xd0, 0xe3]), next: 1681338455 },
|
|
),
|
|
(
|
|
EthereumHardfork::Shanghai,
|
|
ForkId { hash: ForkHash([0xdc, 0xe9, 0x6c, 0x2d]), next: 1710338135 },
|
|
),
|
|
(
|
|
EthereumHardfork::Cancun,
|
|
ForkId { hash: ForkHash([0x9f, 0x3d, 0x22, 0x54]), next: 0 },
|
|
),
|
|
],
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn sepolia_hardfork_fork_ids() {
|
|
test_hardfork_fork_ids(
|
|
&SEPOLIA,
|
|
&[
|
|
(
|
|
EthereumHardfork::Frontier,
|
|
ForkId { hash: ForkHash([0xfe, 0x33, 0x66, 0xe7]), next: 1735371 },
|
|
),
|
|
(
|
|
EthereumHardfork::Homestead,
|
|
ForkId { hash: ForkHash([0xfe, 0x33, 0x66, 0xe7]), next: 1735371 },
|
|
),
|
|
(
|
|
EthereumHardfork::Tangerine,
|
|
ForkId { hash: ForkHash([0xfe, 0x33, 0x66, 0xe7]), next: 1735371 },
|
|
),
|
|
(
|
|
EthereumHardfork::SpuriousDragon,
|
|
ForkId { hash: ForkHash([0xfe, 0x33, 0x66, 0xe7]), next: 1735371 },
|
|
),
|
|
(
|
|
EthereumHardfork::Byzantium,
|
|
ForkId { hash: ForkHash([0xfe, 0x33, 0x66, 0xe7]), next: 1735371 },
|
|
),
|
|
(
|
|
EthereumHardfork::Constantinople,
|
|
ForkId { hash: ForkHash([0xfe, 0x33, 0x66, 0xe7]), next: 1735371 },
|
|
),
|
|
(
|
|
EthereumHardfork::Petersburg,
|
|
ForkId { hash: ForkHash([0xfe, 0x33, 0x66, 0xe7]), next: 1735371 },
|
|
),
|
|
(
|
|
EthereumHardfork::Istanbul,
|
|
ForkId { hash: ForkHash([0xfe, 0x33, 0x66, 0xe7]), next: 1735371 },
|
|
),
|
|
(
|
|
EthereumHardfork::Berlin,
|
|
ForkId { hash: ForkHash([0xfe, 0x33, 0x66, 0xe7]), next: 1735371 },
|
|
),
|
|
(
|
|
EthereumHardfork::London,
|
|
ForkId { hash: ForkHash([0xfe, 0x33, 0x66, 0xe7]), next: 1735371 },
|
|
),
|
|
(
|
|
EthereumHardfork::Paris,
|
|
ForkId { hash: ForkHash([0xb9, 0x6c, 0xbd, 0x13]), next: 1677557088 },
|
|
),
|
|
(
|
|
EthereumHardfork::Shanghai,
|
|
ForkId { hash: ForkHash([0xf7, 0xf9, 0xbc, 0x08]), next: 1706655072 },
|
|
),
|
|
(
|
|
EthereumHardfork::Cancun,
|
|
ForkId { hash: ForkHash([0x88, 0xcf, 0x81, 0xd9]), next: 0 },
|
|
),
|
|
],
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn mainnet_forkids() {
|
|
test_fork_ids(
|
|
&MAINNET,
|
|
&[
|
|
(
|
|
Head { number: 0, ..Default::default() },
|
|
ForkId { hash: ForkHash([0xfc, 0x64, 0xec, 0x04]), next: 1150000 },
|
|
),
|
|
(
|
|
Head { number: 1150000, ..Default::default() },
|
|
ForkId { hash: ForkHash([0x97, 0xc2, 0xc3, 0x4c]), next: 1920000 },
|
|
),
|
|
(
|
|
Head { number: 1920000, ..Default::default() },
|
|
ForkId { hash: ForkHash([0x91, 0xd1, 0xf9, 0x48]), next: 2463000 },
|
|
),
|
|
(
|
|
Head { number: 2463000, ..Default::default() },
|
|
ForkId { hash: ForkHash([0x7a, 0x64, 0xda, 0x13]), next: 2675000 },
|
|
),
|
|
(
|
|
Head { number: 2675000, ..Default::default() },
|
|
ForkId { hash: ForkHash([0x3e, 0xdd, 0x5b, 0x10]), next: 4370000 },
|
|
),
|
|
(
|
|
Head { number: 4370000, ..Default::default() },
|
|
ForkId { hash: ForkHash([0xa0, 0x0b, 0xc3, 0x24]), next: 7280000 },
|
|
),
|
|
(
|
|
Head { number: 7280000, ..Default::default() },
|
|
ForkId { hash: ForkHash([0x66, 0x8d, 0xb0, 0xaf]), next: 9069000 },
|
|
),
|
|
(
|
|
Head { number: 9069000, ..Default::default() },
|
|
ForkId { hash: ForkHash([0x87, 0x9d, 0x6e, 0x30]), next: 9200000 },
|
|
),
|
|
(
|
|
Head { number: 9200000, ..Default::default() },
|
|
ForkId { hash: ForkHash([0xe0, 0x29, 0xe9, 0x91]), next: 12244000 },
|
|
),
|
|
(
|
|
Head { number: 12244000, ..Default::default() },
|
|
ForkId { hash: ForkHash([0x0e, 0xb4, 0x40, 0xf6]), next: 12965000 },
|
|
),
|
|
(
|
|
Head { number: 12965000, ..Default::default() },
|
|
ForkId { hash: ForkHash([0xb7, 0x15, 0x07, 0x7d]), next: 13773000 },
|
|
),
|
|
(
|
|
Head { number: 13773000, ..Default::default() },
|
|
ForkId { hash: ForkHash([0x20, 0xc3, 0x27, 0xfc]), next: 15050000 },
|
|
),
|
|
(
|
|
Head { number: 15050000, ..Default::default() },
|
|
ForkId { hash: ForkHash([0xf0, 0xaf, 0xd0, 0xe3]), next: 1681338455 },
|
|
),
|
|
// First Shanghai block
|
|
(
|
|
Head { number: 20000000, timestamp: 1681338455, ..Default::default() },
|
|
ForkId { hash: ForkHash([0xdc, 0xe9, 0x6c, 0x2d]), next: 1710338135 },
|
|
),
|
|
// First Cancun block
|
|
(
|
|
Head { number: 20000001, timestamp: 1710338135, ..Default::default() },
|
|
ForkId { hash: ForkHash([0x9f, 0x3d, 0x22, 0x54]), next: 0 },
|
|
),
|
|
// Future Cancun block
|
|
(
|
|
Head { number: 20000002, timestamp: 2000000000, ..Default::default() },
|
|
ForkId { hash: ForkHash([0x9f, 0x3d, 0x22, 0x54]), next: 0 },
|
|
),
|
|
],
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn holesky_forkids() {
|
|
test_fork_ids(
|
|
&HOLESKY,
|
|
&[
|
|
(
|
|
Head { number: 0, ..Default::default() },
|
|
ForkId { hash: ForkHash([0xc6, 0x1a, 0x60, 0x98]), next: 1696000704 },
|
|
),
|
|
// First MergeNetsplit block
|
|
(
|
|
Head { number: 123, ..Default::default() },
|
|
ForkId { hash: ForkHash([0xc6, 0x1a, 0x60, 0x98]), next: 1696000704 },
|
|
),
|
|
// Last MergeNetsplit block
|
|
(
|
|
Head { number: 123, timestamp: 1696000703, ..Default::default() },
|
|
ForkId { hash: ForkHash([0xc6, 0x1a, 0x60, 0x98]), next: 1696000704 },
|
|
),
|
|
// First Shanghai block
|
|
(
|
|
Head { number: 123, timestamp: 1696000704, ..Default::default() },
|
|
ForkId { hash: ForkHash([0xfd, 0x4f, 0x01, 0x6b]), next: 1707305664 },
|
|
),
|
|
// Last Shanghai block
|
|
(
|
|
Head { number: 123, timestamp: 1707305663, ..Default::default() },
|
|
ForkId { hash: ForkHash([0xfd, 0x4f, 0x01, 0x6b]), next: 1707305664 },
|
|
),
|
|
// First Cancun block
|
|
(
|
|
Head { number: 123, timestamp: 1707305664, ..Default::default() },
|
|
ForkId { hash: ForkHash([0x9b, 0x19, 0x2a, 0xd0]), next: 0 },
|
|
),
|
|
],
|
|
)
|
|
}
|
|
|
|
#[test]
|
|
fn sepolia_forkids() {
|
|
test_fork_ids(
|
|
&SEPOLIA,
|
|
&[
|
|
(
|
|
Head { number: 0, ..Default::default() },
|
|
ForkId { hash: ForkHash([0xfe, 0x33, 0x66, 0xe7]), next: 1735371 },
|
|
),
|
|
(
|
|
Head { number: 1735370, ..Default::default() },
|
|
ForkId { hash: ForkHash([0xfe, 0x33, 0x66, 0xe7]), next: 1735371 },
|
|
),
|
|
(
|
|
Head { number: 1735371, ..Default::default() },
|
|
ForkId { hash: ForkHash([0xb9, 0x6c, 0xbd, 0x13]), next: 1677557088 },
|
|
),
|
|
(
|
|
Head { number: 1735372, timestamp: 1677557087, ..Default::default() },
|
|
ForkId { hash: ForkHash([0xb9, 0x6c, 0xbd, 0x13]), next: 1677557088 },
|
|
),
|
|
// First Shanghai block
|
|
(
|
|
Head { number: 1735372, timestamp: 1677557088, ..Default::default() },
|
|
ForkId { hash: ForkHash([0xf7, 0xf9, 0xbc, 0x08]), next: 1706655072 },
|
|
),
|
|
// Last Shanghai block
|
|
(
|
|
Head { number: 1735372, timestamp: 1706655071, ..Default::default() },
|
|
ForkId { hash: ForkHash([0xf7, 0xf9, 0xbc, 0x08]), next: 1706655072 },
|
|
),
|
|
// First Cancun block
|
|
(
|
|
Head { number: 1735372, timestamp: 1706655072, ..Default::default() },
|
|
ForkId { hash: ForkHash([0x88, 0xcf, 0x81, 0xd9]), next: 0 },
|
|
),
|
|
],
|
|
);
|
|
}
|
|
|
|
#[cfg(feature = "optimism")]
|
|
#[test]
|
|
fn base_mainnet_forkids() {
|
|
test_fork_ids(
|
|
&BASE_MAINNET,
|
|
&[
|
|
(
|
|
Head { number: 0, ..Default::default() },
|
|
ForkId { hash: ForkHash([0x67, 0xda, 0x02, 0x60]), next: 1704992401 },
|
|
),
|
|
(
|
|
Head { number: 0, timestamp: 1704992400, ..Default::default() },
|
|
ForkId { hash: ForkHash([0x67, 0xda, 0x02, 0x60]), next: 1704992401 },
|
|
),
|
|
(
|
|
Head { number: 0, timestamp: 1704992401, ..Default::default() },
|
|
ForkId { hash: ForkHash([0x3c, 0x28, 0x3c, 0xb3]), next: 1710374401 },
|
|
),
|
|
(
|
|
Head { number: 0, timestamp: 1710374400, ..Default::default() },
|
|
ForkId { hash: ForkHash([0x3c, 0x28, 0x3c, 0xb3]), next: 1710374401 },
|
|
),
|
|
(
|
|
Head { number: 0, timestamp: 1710374401, ..Default::default() },
|
|
ForkId { hash: ForkHash([0x51, 0xcc, 0x98, 0xb3]), next: 1720627201 },
|
|
),
|
|
(
|
|
Head { number: 0, timestamp: 1720627200, ..Default::default() },
|
|
ForkId { hash: ForkHash([0x51, 0xcc, 0x98, 0xb3]), next: 1720627201 },
|
|
),
|
|
(
|
|
Head { number: 0, timestamp: 1720627201, ..Default::default() },
|
|
ForkId { hash: ForkHash([0xe4, 0x01, 0x0e, 0xb9]), next: 0 },
|
|
),
|
|
],
|
|
);
|
|
}
|
|
|
|
#[cfg(feature = "optimism")]
|
|
#[test]
|
|
fn op_sepolia_forkids() {
|
|
test_fork_ids(
|
|
&OP_SEPOLIA,
|
|
&[
|
|
(
|
|
Head { number: 0, ..Default::default() },
|
|
ForkId { hash: ForkHash([0x67, 0xa4, 0x03, 0x28]), next: 1699981200 },
|
|
),
|
|
(
|
|
Head { number: 0, timestamp: 1699981199, ..Default::default() },
|
|
ForkId { hash: ForkHash([0x67, 0xa4, 0x03, 0x28]), next: 1699981200 },
|
|
),
|
|
(
|
|
Head { number: 0, timestamp: 1699981200, ..Default::default() },
|
|
ForkId { hash: ForkHash([0xa4, 0x8d, 0x6a, 0x00]), next: 1708534800 },
|
|
),
|
|
(
|
|
Head { number: 0, timestamp: 1708534799, ..Default::default() },
|
|
ForkId { hash: ForkHash([0xa4, 0x8d, 0x6a, 0x00]), next: 1708534800 },
|
|
),
|
|
(
|
|
Head { number: 0, timestamp: 1708534800, ..Default::default() },
|
|
ForkId { hash: ForkHash([0xcc, 0x17, 0xc7, 0xeb]), next: 1716998400 },
|
|
),
|
|
(
|
|
Head { number: 0, timestamp: 1716998399, ..Default::default() },
|
|
ForkId { hash: ForkHash([0xcc, 0x17, 0xc7, 0xeb]), next: 1716998400 },
|
|
),
|
|
(
|
|
Head { number: 0, timestamp: 1716998400, ..Default::default() },
|
|
ForkId { hash: ForkHash([0x54, 0x0a, 0x8c, 0x5d]), next: 0 },
|
|
),
|
|
],
|
|
);
|
|
}
|
|
|
|
#[cfg(feature = "optimism")]
|
|
#[test]
|
|
fn op_mainnet_forkids() {
|
|
test_fork_ids(
|
|
&OP_MAINNET,
|
|
&[
|
|
(
|
|
Head { number: 0, ..Default::default() },
|
|
ForkId { hash: ForkHash([0xca, 0xf5, 0x17, 0xed]), next: 3950000 },
|
|
),
|
|
// TODO: complete these, see https://github.com/paradigmxyz/reth/issues/8012
|
|
(
|
|
Head { number: 105235063, timestamp: 1710374401, ..Default::default() },
|
|
ForkId { hash: ForkHash([0x19, 0xda, 0x4c, 0x52]), next: 1720627201 },
|
|
),
|
|
],
|
|
);
|
|
}
|
|
|
|
#[cfg(feature = "optimism")]
|
|
#[test]
|
|
fn base_sepolia_forkids() {
|
|
test_fork_ids(
|
|
&BASE_SEPOLIA,
|
|
&[
|
|
(
|
|
Head { number: 0, ..Default::default() },
|
|
ForkId { hash: ForkHash([0xb9, 0x59, 0xb9, 0xf7]), next: 1699981200 },
|
|
),
|
|
(
|
|
Head { number: 0, timestamp: 1699981199, ..Default::default() },
|
|
ForkId { hash: ForkHash([0xb9, 0x59, 0xb9, 0xf7]), next: 1699981200 },
|
|
),
|
|
(
|
|
Head { number: 0, timestamp: 1699981200, ..Default::default() },
|
|
ForkId { hash: ForkHash([0x60, 0x7c, 0xd5, 0xa1]), next: 1708534800 },
|
|
),
|
|
(
|
|
Head { number: 0, timestamp: 1708534799, ..Default::default() },
|
|
ForkId { hash: ForkHash([0x60, 0x7c, 0xd5, 0xa1]), next: 1708534800 },
|
|
),
|
|
(
|
|
Head { number: 0, timestamp: 1708534800, ..Default::default() },
|
|
ForkId { hash: ForkHash([0xbe, 0x96, 0x9b, 0x17]), next: 1716998400 },
|
|
),
|
|
(
|
|
Head { number: 0, timestamp: 1716998399, ..Default::default() },
|
|
ForkId { hash: ForkHash([0xbe, 0x96, 0x9b, 0x17]), next: 1716998400 },
|
|
),
|
|
(
|
|
Head { number: 0, timestamp: 1716998400, ..Default::default() },
|
|
ForkId { hash: ForkHash([0x4e, 0x45, 0x7a, 0x49]), next: 0 },
|
|
),
|
|
],
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn dev_forkids() {
|
|
test_fork_ids(
|
|
&DEV,
|
|
&[(
|
|
Head { number: 0, ..Default::default() },
|
|
ForkId { hash: ForkHash([0x45, 0xb8, 0x36, 0x12]), next: 0 },
|
|
)],
|
|
)
|
|
}
|
|
|
|
/// Checks that time-based forks work
|
|
///
|
|
/// This is based off of the test vectors here: <https://github.com/ethereum/go-ethereum/blob/5c8cc10d1e05c23ff1108022f4150749e73c0ca1/core/forkid/forkid_test.go#L155-L188>
|
|
#[test]
|
|
fn timestamped_forks() {
|
|
let mainnet_with_timestamps = ChainSpecBuilder::mainnet().build();
|
|
test_fork_ids(
|
|
&mainnet_with_timestamps,
|
|
&[
|
|
(
|
|
Head { number: 0, timestamp: 0, ..Default::default() },
|
|
ForkId { hash: ForkHash([0xfc, 0x64, 0xec, 0x04]), next: 1150000 },
|
|
), // Unsynced
|
|
(
|
|
Head { number: 1149999, timestamp: 0, ..Default::default() },
|
|
ForkId { hash: ForkHash([0xfc, 0x64, 0xec, 0x04]), next: 1150000 },
|
|
), // Last Frontier block
|
|
(
|
|
Head { number: 1150000, timestamp: 0, ..Default::default() },
|
|
ForkId { hash: ForkHash([0x97, 0xc2, 0xc3, 0x4c]), next: 1920000 },
|
|
), // First Homestead block
|
|
(
|
|
Head { number: 1919999, timestamp: 0, ..Default::default() },
|
|
ForkId { hash: ForkHash([0x97, 0xc2, 0xc3, 0x4c]), next: 1920000 },
|
|
), // Last Homestead block
|
|
(
|
|
Head { number: 1920000, timestamp: 0, ..Default::default() },
|
|
ForkId { hash: ForkHash([0x91, 0xd1, 0xf9, 0x48]), next: 2463000 },
|
|
), // First DAO block
|
|
(
|
|
Head { number: 2462999, timestamp: 0, ..Default::default() },
|
|
ForkId { hash: ForkHash([0x91, 0xd1, 0xf9, 0x48]), next: 2463000 },
|
|
), // Last DAO block
|
|
(
|
|
Head { number: 2463000, timestamp: 0, ..Default::default() },
|
|
ForkId { hash: ForkHash([0x7a, 0x64, 0xda, 0x13]), next: 2675000 },
|
|
), // First Tangerine block
|
|
(
|
|
Head { number: 2674999, timestamp: 0, ..Default::default() },
|
|
ForkId { hash: ForkHash([0x7a, 0x64, 0xda, 0x13]), next: 2675000 },
|
|
), // Last Tangerine block
|
|
(
|
|
Head { number: 2675000, timestamp: 0, ..Default::default() },
|
|
ForkId { hash: ForkHash([0x3e, 0xdd, 0x5b, 0x10]), next: 4370000 },
|
|
), // First Spurious block
|
|
(
|
|
Head { number: 4369999, timestamp: 0, ..Default::default() },
|
|
ForkId { hash: ForkHash([0x3e, 0xdd, 0x5b, 0x10]), next: 4370000 },
|
|
), // Last Spurious block
|
|
(
|
|
Head { number: 4370000, timestamp: 0, ..Default::default() },
|
|
ForkId { hash: ForkHash([0xa0, 0x0b, 0xc3, 0x24]), next: 7280000 },
|
|
), // First Byzantium block
|
|
(
|
|
Head { number: 7279999, timestamp: 0, ..Default::default() },
|
|
ForkId { hash: ForkHash([0xa0, 0x0b, 0xc3, 0x24]), next: 7280000 },
|
|
), // Last Byzantium block
|
|
(
|
|
Head { number: 7280000, timestamp: 0, ..Default::default() },
|
|
ForkId { hash: ForkHash([0x66, 0x8d, 0xb0, 0xaf]), next: 9069000 },
|
|
), // First and last Constantinople, first Petersburg block
|
|
(
|
|
Head { number: 9068999, timestamp: 0, ..Default::default() },
|
|
ForkId { hash: ForkHash([0x66, 0x8d, 0xb0, 0xaf]), next: 9069000 },
|
|
), // Last Petersburg block
|
|
(
|
|
Head { number: 9069000, timestamp: 0, ..Default::default() },
|
|
ForkId { hash: ForkHash([0x87, 0x9d, 0x6e, 0x30]), next: 9200000 },
|
|
), // First Istanbul and first Muir Glacier block
|
|
(
|
|
Head { number: 9199999, timestamp: 0, ..Default::default() },
|
|
ForkId { hash: ForkHash([0x87, 0x9d, 0x6e, 0x30]), next: 9200000 },
|
|
), // Last Istanbul and first Muir Glacier block
|
|
(
|
|
Head { number: 9200000, timestamp: 0, ..Default::default() },
|
|
ForkId { hash: ForkHash([0xe0, 0x29, 0xe9, 0x91]), next: 12244000 },
|
|
), // First Muir Glacier block
|
|
(
|
|
Head { number: 12243999, timestamp: 0, ..Default::default() },
|
|
ForkId { hash: ForkHash([0xe0, 0x29, 0xe9, 0x91]), next: 12244000 },
|
|
), // Last Muir Glacier block
|
|
(
|
|
Head { number: 12244000, timestamp: 0, ..Default::default() },
|
|
ForkId { hash: ForkHash([0x0e, 0xb4, 0x40, 0xf6]), next: 12965000 },
|
|
), // First Berlin block
|
|
(
|
|
Head { number: 12964999, timestamp: 0, ..Default::default() },
|
|
ForkId { hash: ForkHash([0x0e, 0xb4, 0x40, 0xf6]), next: 12965000 },
|
|
), // Last Berlin block
|
|
(
|
|
Head { number: 12965000, timestamp: 0, ..Default::default() },
|
|
ForkId { hash: ForkHash([0xb7, 0x15, 0x07, 0x7d]), next: 13773000 },
|
|
), // First London block
|
|
(
|
|
Head { number: 13772999, timestamp: 0, ..Default::default() },
|
|
ForkId { hash: ForkHash([0xb7, 0x15, 0x07, 0x7d]), next: 13773000 },
|
|
), // Last London block
|
|
(
|
|
Head { number: 13773000, timestamp: 0, ..Default::default() },
|
|
ForkId { hash: ForkHash([0x20, 0xc3, 0x27, 0xfc]), next: 15050000 },
|
|
), // First Arrow Glacier block
|
|
(
|
|
Head { number: 15049999, timestamp: 0, ..Default::default() },
|
|
ForkId { hash: ForkHash([0x20, 0xc3, 0x27, 0xfc]), next: 15050000 },
|
|
), // Last Arrow Glacier block
|
|
(
|
|
Head { number: 15050000, timestamp: 0, ..Default::default() },
|
|
ForkId { hash: ForkHash([0xf0, 0xaf, 0xd0, 0xe3]), next: 1681338455 },
|
|
), // First Gray Glacier block
|
|
(
|
|
Head { number: 19999999, timestamp: 1667999999, ..Default::default() },
|
|
ForkId { hash: ForkHash([0xf0, 0xaf, 0xd0, 0xe3]), next: 1681338455 },
|
|
), // Last Gray Glacier block
|
|
(
|
|
Head { number: 20000000, timestamp: 1681338455, ..Default::default() },
|
|
ForkId { hash: ForkHash([0xdc, 0xe9, 0x6c, 0x2d]), next: 1710338135 },
|
|
), // Last Shanghai block
|
|
(
|
|
Head { number: 20000001, timestamp: 1710338134, ..Default::default() },
|
|
ForkId { hash: ForkHash([0xdc, 0xe9, 0x6c, 0x2d]), next: 1710338135 },
|
|
), // First Cancun block
|
|
(
|
|
Head { number: 20000002, timestamp: 1710338135, ..Default::default() },
|
|
ForkId { hash: ForkHash([0x9f, 0x3d, 0x22, 0x54]), next: 0 },
|
|
), // Future Cancun block
|
|
(
|
|
Head { number: 20000003, timestamp: 2000000000, ..Default::default() },
|
|
ForkId { hash: ForkHash([0x9f, 0x3d, 0x22, 0x54]), next: 0 },
|
|
),
|
|
],
|
|
);
|
|
}
|
|
|
|
/// Constructs a [`ChainSpec`] with the given [`ChainSpecBuilder`], shanghai, and cancun fork
|
|
/// timestamps.
|
|
fn construct_chainspec(
|
|
builder: ChainSpecBuilder,
|
|
shanghai_time: u64,
|
|
cancun_time: u64,
|
|
) -> ChainSpec {
|
|
builder
|
|
.with_fork(EthereumHardfork::Shanghai, ForkCondition::Timestamp(shanghai_time))
|
|
.with_fork(EthereumHardfork::Cancun, ForkCondition::Timestamp(cancun_time))
|
|
.build()
|
|
}
|
|
|
|
/// Tests that time-based forks which are active at genesis are not included in forkid hash.
|
|
///
|
|
/// This is based off of the test vectors here:
|
|
/// <https://github.com/ethereum/go-ethereum/blob/2e02c1ffd9dffd1ec9e43c6b66f6c9bd1e556a0b/core/forkid/forkid_test.go#L390-L440>
|
|
#[test]
|
|
fn test_timestamp_fork_in_genesis() {
|
|
let timestamp = 1690475657u64;
|
|
let default_spec_builder = ChainSpecBuilder::default()
|
|
.chain(Chain::from_id(1337))
|
|
.genesis(Genesis::default().with_timestamp(timestamp))
|
|
.paris_activated();
|
|
|
|
// test format: (chain spec, expected next value) - the forkhash will be determined by the
|
|
// genesis hash of the constructed chainspec
|
|
let tests = [
|
|
(
|
|
construct_chainspec(default_spec_builder.clone(), timestamp - 1, timestamp + 1),
|
|
timestamp + 1,
|
|
),
|
|
(
|
|
construct_chainspec(default_spec_builder.clone(), timestamp, timestamp + 1),
|
|
timestamp + 1,
|
|
),
|
|
(
|
|
construct_chainspec(default_spec_builder, timestamp + 1, timestamp + 2),
|
|
timestamp + 1,
|
|
),
|
|
];
|
|
|
|
for (spec, expected_timestamp) in tests {
|
|
let got_forkid = spec.fork_id(&Head { number: 0, timestamp: 0, ..Default::default() });
|
|
// This is slightly different from the geth test because we use the shanghai timestamp
|
|
// to determine whether or not to include a withdrawals root in the genesis header.
|
|
// This makes the genesis hash different, and as a result makes the ChainSpec fork hash
|
|
// different.
|
|
let genesis_hash = spec.genesis_hash();
|
|
let expected_forkid =
|
|
ForkId { hash: ForkHash::from(genesis_hash), next: expected_timestamp };
|
|
assert_eq!(got_forkid, expected_forkid);
|
|
}
|
|
}
|
|
|
|
/// Checks that the fork is not active at a terminal ttd block.
|
|
#[test]
|
|
fn check_terminal_ttd() {
|
|
let chainspec = ChainSpecBuilder::mainnet().build();
|
|
|
|
// Check that Paris is not active on terminal PoW block #15537393.
|
|
let terminal_block_ttd = U256::from(58750003716598352816469_u128);
|
|
let terminal_block_difficulty = U256::from(11055787484078698_u128);
|
|
assert!(!chainspec
|
|
.fork(EthereumHardfork::Paris)
|
|
.active_at_ttd(terminal_block_ttd, terminal_block_difficulty));
|
|
|
|
// Check that Paris is active on first PoS block #15537394.
|
|
let first_pos_block_ttd = U256::from(58750003716598352816469_u128);
|
|
let first_pos_difficulty = U256::ZERO;
|
|
assert!(chainspec
|
|
.fork(EthereumHardfork::Paris)
|
|
.active_at_ttd(first_pos_block_ttd, first_pos_difficulty));
|
|
}
|
|
|
|
#[test]
|
|
fn geth_genesis_with_shanghai() {
|
|
let geth_genesis = r#"
|
|
{
|
|
"config": {
|
|
"chainId": 1337,
|
|
"homesteadBlock": 0,
|
|
"eip150Block": 0,
|
|
"eip150Hash": "0x0000000000000000000000000000000000000000000000000000000000000000",
|
|
"eip155Block": 0,
|
|
"eip158Block": 0,
|
|
"byzantiumBlock": 0,
|
|
"constantinopleBlock": 0,
|
|
"petersburgBlock": 0,
|
|
"istanbulBlock": 0,
|
|
"muirGlacierBlock": 0,
|
|
"berlinBlock": 0,
|
|
"londonBlock": 0,
|
|
"arrowGlacierBlock": 0,
|
|
"grayGlacierBlock": 0,
|
|
"shanghaiTime": 0,
|
|
"cancunTime": 1,
|
|
"terminalTotalDifficulty": 0,
|
|
"terminalTotalDifficultyPassed": true,
|
|
"ethash": {}
|
|
},
|
|
"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"
|
|
}
|
|
"#;
|
|
|
|
let genesis: Genesis = serde_json::from_str(geth_genesis).unwrap();
|
|
let chainspec = ChainSpec::from(genesis);
|
|
|
|
// assert a bunch of hardforks that should be set
|
|
assert_eq!(
|
|
chainspec.hardforks.get(EthereumHardfork::Homestead).unwrap(),
|
|
ForkCondition::Block(0)
|
|
);
|
|
assert_eq!(
|
|
chainspec.hardforks.get(EthereumHardfork::Tangerine).unwrap(),
|
|
ForkCondition::Block(0)
|
|
);
|
|
assert_eq!(
|
|
chainspec.hardforks.get(EthereumHardfork::SpuriousDragon).unwrap(),
|
|
ForkCondition::Block(0)
|
|
);
|
|
assert_eq!(
|
|
chainspec.hardforks.get(EthereumHardfork::Byzantium).unwrap(),
|
|
ForkCondition::Block(0)
|
|
);
|
|
assert_eq!(
|
|
chainspec.hardforks.get(EthereumHardfork::Constantinople).unwrap(),
|
|
ForkCondition::Block(0)
|
|
);
|
|
assert_eq!(
|
|
chainspec.hardforks.get(EthereumHardfork::Petersburg).unwrap(),
|
|
ForkCondition::Block(0)
|
|
);
|
|
assert_eq!(
|
|
chainspec.hardforks.get(EthereumHardfork::Istanbul).unwrap(),
|
|
ForkCondition::Block(0)
|
|
);
|
|
assert_eq!(
|
|
chainspec.hardforks.get(EthereumHardfork::MuirGlacier).unwrap(),
|
|
ForkCondition::Block(0)
|
|
);
|
|
assert_eq!(
|
|
chainspec.hardforks.get(EthereumHardfork::Berlin).unwrap(),
|
|
ForkCondition::Block(0)
|
|
);
|
|
assert_eq!(
|
|
chainspec.hardforks.get(EthereumHardfork::London).unwrap(),
|
|
ForkCondition::Block(0)
|
|
);
|
|
assert_eq!(
|
|
chainspec.hardforks.get(EthereumHardfork::ArrowGlacier).unwrap(),
|
|
ForkCondition::Block(0)
|
|
);
|
|
assert_eq!(
|
|
chainspec.hardforks.get(EthereumHardfork::GrayGlacier).unwrap(),
|
|
ForkCondition::Block(0)
|
|
);
|
|
|
|
// including time based hardforks
|
|
assert_eq!(
|
|
chainspec.hardforks.get(EthereumHardfork::Shanghai).unwrap(),
|
|
ForkCondition::Timestamp(0)
|
|
);
|
|
|
|
// including time based hardforks
|
|
assert_eq!(
|
|
chainspec.hardforks.get(EthereumHardfork::Cancun).unwrap(),
|
|
ForkCondition::Timestamp(1)
|
|
);
|
|
|
|
// alloc key -> expected rlp mapping
|
|
let key_rlp = vec![
|
|
(hex!("658bdf435d810c91414ec09147daa6db62406379"), &hex!("f84d8089487a9a304539440000a056e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421a0c5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470")[..]),
|
|
(hex!("aa00000000000000000000000000000000000000"), &hex!("f8440101a08afc95b7d18a226944b9c2070b6bda1c3a36afcc3730429d47579c94b9fe5850a0ce92c756baff35fa740c3557c1a971fd24d2d35b7c8e067880d50cd86bb0bc99")[..]),
|
|
(hex!("bb00000000000000000000000000000000000000"), &hex!("f8440102a08afc95b7d18a226944b9c2070b6bda1c3a36afcc3730429d47579c94b9fe5850a0e25a53cbb501cec2976b393719c63d832423dd70a458731a0b64e4847bbca7d2")[..]),
|
|
];
|
|
|
|
for (key, expected_rlp) in key_rlp {
|
|
let account = chainspec.genesis.alloc.get(&key).expect("account should exist");
|
|
assert_eq!(&alloy_rlp::encode(TrieAccount::from(account.clone())), expected_rlp);
|
|
}
|
|
|
|
assert_eq!(chainspec.genesis_hash, None);
|
|
let expected_state_root: B256 =
|
|
hex!("078dc6061b1d8eaa8493384b59c9c65ceb917201221d08b80c4de6770b6ec7e7").into();
|
|
assert_eq!(chainspec.genesis_header().state_root, expected_state_root);
|
|
|
|
let expected_withdrawals_hash: B256 =
|
|
hex!("56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421").into();
|
|
assert_eq!(chainspec.genesis_header().withdrawals_root, Some(expected_withdrawals_hash));
|
|
|
|
let expected_hash: B256 =
|
|
hex!("1fc027d65f820d3eef441ebeec139ebe09e471cf98516dce7b5643ccb27f418c").into();
|
|
let hash = chainspec.genesis_hash();
|
|
assert_eq!(hash, expected_hash);
|
|
}
|
|
|
|
#[test]
|
|
fn hive_geth_json() {
|
|
let hive_json = r#"
|
|
{
|
|
"nonce": "0x0000000000000042",
|
|
"difficulty": "0x2123456",
|
|
"mixHash": "0x123456789abcdef123456789abcdef123456789abcdef123456789abcdef1234",
|
|
"coinbase": "0xaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
|
|
"timestamp": "0x123456",
|
|
"parentHash": "0x0000000000000000000000000000000000000000000000000000000000000000",
|
|
"extraData": "0xfafbfcfd",
|
|
"gasLimit": "0x2fefd8",
|
|
"alloc": {
|
|
"dbdbdb2cbd23b783741e8d7fcf51e459b497e4a6": {
|
|
"balance": "0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"
|
|
},
|
|
"e6716f9544a56c530d868e4bfbacb172315bdead": {
|
|
"balance": "0x11",
|
|
"code": "0x12"
|
|
},
|
|
"b9c015918bdaba24b4ff057a92a3873d6eb201be": {
|
|
"balance": "0x21",
|
|
"storage": {
|
|
"0x0000000000000000000000000000000000000000000000000000000000000001": "0x22"
|
|
}
|
|
},
|
|
"1a26338f0d905e295fccb71fa9ea849ffa12aaf4": {
|
|
"balance": "0x31",
|
|
"nonce": "0x32"
|
|
},
|
|
"0000000000000000000000000000000000000001": {
|
|
"balance": "0x41"
|
|
},
|
|
"0000000000000000000000000000000000000002": {
|
|
"balance": "0x51"
|
|
},
|
|
"0000000000000000000000000000000000000003": {
|
|
"balance": "0x61"
|
|
},
|
|
"0000000000000000000000000000000000000004": {
|
|
"balance": "0x71"
|
|
}
|
|
},
|
|
"config": {
|
|
"ethash": {},
|
|
"chainId": 10,
|
|
"homesteadBlock": 0,
|
|
"eip150Block": 0,
|
|
"eip155Block": 0,
|
|
"eip158Block": 0,
|
|
"byzantiumBlock": 0,
|
|
"constantinopleBlock": 0,
|
|
"petersburgBlock": 0,
|
|
"istanbulBlock": 0
|
|
}
|
|
}
|
|
"#;
|
|
|
|
let genesis = serde_json::from_str::<Genesis>(hive_json).unwrap();
|
|
let chainspec: ChainSpec = genesis.into();
|
|
assert_eq!(chainspec.genesis_hash, None);
|
|
assert_eq!(chainspec.chain, Chain::from_named(NamedChain::Optimism));
|
|
let expected_state_root: B256 =
|
|
hex!("9a6049ac535e3dc7436c189eaa81c73f35abd7f282ab67c32944ff0301d63360").into();
|
|
assert_eq!(chainspec.genesis_header().state_root, expected_state_root);
|
|
let hard_forks = vec![
|
|
EthereumHardfork::Byzantium,
|
|
EthereumHardfork::Homestead,
|
|
EthereumHardfork::Istanbul,
|
|
EthereumHardfork::Petersburg,
|
|
EthereumHardfork::Constantinople,
|
|
];
|
|
for fork in hard_forks {
|
|
assert_eq!(chainspec.hardforks.get(fork).unwrap(), ForkCondition::Block(0));
|
|
}
|
|
|
|
let expected_hash: B256 =
|
|
hex!("5ae31c6522bd5856129f66be3d582b842e4e9faaa87f21cce547128339a9db3c").into();
|
|
let hash = chainspec.genesis_header().hash_slow();
|
|
assert_eq!(hash, expected_hash);
|
|
}
|
|
|
|
#[test]
|
|
fn test_hive_paris_block_genesis_json() {
|
|
// this tests that we can handle `parisBlock` in the genesis json and can use it to output
|
|
// a correct forkid
|
|
let hive_paris = r#"
|
|
{
|
|
"config": {
|
|
"ethash": {},
|
|
"chainId": 3503995874084926,
|
|
"homesteadBlock": 0,
|
|
"eip150Block": 6,
|
|
"eip155Block": 12,
|
|
"eip158Block": 12,
|
|
"byzantiumBlock": 18,
|
|
"constantinopleBlock": 24,
|
|
"petersburgBlock": 30,
|
|
"istanbulBlock": 36,
|
|
"muirGlacierBlock": 42,
|
|
"berlinBlock": 48,
|
|
"londonBlock": 54,
|
|
"arrowGlacierBlock": 60,
|
|
"grayGlacierBlock": 66,
|
|
"mergeNetsplitBlock": 72,
|
|
"terminalTotalDifficulty": 9454784,
|
|
"shanghaiTime": 780,
|
|
"cancunTime": 840
|
|
},
|
|
"nonce": "0x0",
|
|
"timestamp": "0x0",
|
|
"extraData": "0x68697665636861696e",
|
|
"gasLimit": "0x23f3e20",
|
|
"difficulty": "0x20000",
|
|
"mixHash": "0x0000000000000000000000000000000000000000000000000000000000000000",
|
|
"coinbase": "0x0000000000000000000000000000000000000000",
|
|
"alloc": {
|
|
"000f3df6d732807ef1319fb7b8bb8522d0beac02": {
|
|
"code": "0x3373fffffffffffffffffffffffffffffffffffffffe14604d57602036146024575f5ffd5b5f35801560495762001fff810690815414603c575f5ffd5b62001fff01545f5260205ff35b5f5ffd5b62001fff42064281555f359062001fff015500",
|
|
"balance": "0x2a"
|
|
},
|
|
"0c2c51a0990aee1d73c1228de158688341557508": {
|
|
"balance": "0xc097ce7bc90715b34b9f1000000000"
|
|
},
|
|
"14e46043e63d0e3cdcf2530519f4cfaf35058cb2": {
|
|
"balance": "0xc097ce7bc90715b34b9f1000000000"
|
|
},
|
|
"16c57edf7fa9d9525378b0b81bf8a3ced0620c1c": {
|
|
"balance": "0xc097ce7bc90715b34b9f1000000000"
|
|
},
|
|
"1f4924b14f34e24159387c0a4cdbaa32f3ddb0cf": {
|
|
"balance": "0xc097ce7bc90715b34b9f1000000000"
|
|
},
|
|
"1f5bde34b4afc686f136c7a3cb6ec376f7357759": {
|
|
"balance": "0xc097ce7bc90715b34b9f1000000000"
|
|
},
|
|
"2d389075be5be9f2246ad654ce152cf05990b209": {
|
|
"balance": "0xc097ce7bc90715b34b9f1000000000"
|
|
},
|
|
"3ae75c08b4c907eb63a8960c45b86e1e9ab6123c": {
|
|
"balance": "0xc097ce7bc90715b34b9f1000000000"
|
|
},
|
|
"4340ee1b812acb40a1eb561c019c327b243b92df": {
|
|
"balance": "0xc097ce7bc90715b34b9f1000000000"
|
|
},
|
|
"4a0f1452281bcec5bd90c3dce6162a5995bfe9df": {
|
|
"balance": "0xc097ce7bc90715b34b9f1000000000"
|
|
},
|
|
"4dde844b71bcdf95512fb4dc94e84fb67b512ed8": {
|
|
"balance": "0xc097ce7bc90715b34b9f1000000000"
|
|
},
|
|
"5f552da00dfb4d3749d9e62dcee3c918855a86a0": {
|
|
"balance": "0xc097ce7bc90715b34b9f1000000000"
|
|
},
|
|
"654aa64f5fbefb84c270ec74211b81ca8c44a72e": {
|
|
"balance": "0xc097ce7bc90715b34b9f1000000000"
|
|
},
|
|
"717f8aa2b982bee0e29f573d31df288663e1ce16": {
|
|
"balance": "0xc097ce7bc90715b34b9f1000000000"
|
|
},
|
|
"7435ed30a8b4aeb0877cef0c6e8cffe834eb865f": {
|
|
"balance": "0xc097ce7bc90715b34b9f1000000000"
|
|
},
|
|
"83c7e323d189f18725ac510004fdc2941f8c4a78": {
|
|
"balance": "0xc097ce7bc90715b34b9f1000000000"
|
|
},
|
|
"84e75c28348fb86acea1a93a39426d7d60f4cc46": {
|
|
"balance": "0xc097ce7bc90715b34b9f1000000000"
|
|
},
|
|
"8bebc8ba651aee624937e7d897853ac30c95a067": {
|
|
"storage": {
|
|
"0x0000000000000000000000000000000000000000000000000000000000000001": "0x0000000000000000000000000000000000000000000000000000000000000001",
|
|
"0x0000000000000000000000000000000000000000000000000000000000000002": "0x0000000000000000000000000000000000000000000000000000000000000002",
|
|
"0x0000000000000000000000000000000000000000000000000000000000000003": "0x0000000000000000000000000000000000000000000000000000000000000003"
|
|
},
|
|
"balance": "0x1",
|
|
"nonce": "0x1"
|
|
},
|
|
"c7b99a164efd027a93f147376cc7da7c67c6bbe0": {
|
|
"balance": "0xc097ce7bc90715b34b9f1000000000"
|
|
},
|
|
"d803681e487e6ac18053afc5a6cd813c86ec3e4d": {
|
|
"balance": "0xc097ce7bc90715b34b9f1000000000"
|
|
},
|
|
"e7d13f7aa2a838d24c59b40186a0aca1e21cffcc": {
|
|
"balance": "0xc097ce7bc90715b34b9f1000000000"
|
|
},
|
|
"eda8645ba6948855e3b3cd596bbb07596d59c603": {
|
|
"balance": "0xc097ce7bc90715b34b9f1000000000"
|
|
}
|
|
},
|
|
"number": "0x0",
|
|
"gasUsed": "0x0",
|
|
"parentHash": "0x0000000000000000000000000000000000000000000000000000000000000000",
|
|
"baseFeePerGas": null,
|
|
"excessBlobGas": null,
|
|
"blobGasUsed": null
|
|
}
|
|
"#;
|
|
|
|
// check that it deserializes properly
|
|
let genesis: Genesis = serde_json::from_str(hive_paris).unwrap();
|
|
let chainspec = ChainSpec::from(genesis);
|
|
|
|
// make sure we are at ForkHash("bc0c2605") with Head post-cancun
|
|
let expected_forkid = ForkId { hash: ForkHash([0xbc, 0x0c, 0x26, 0x05]), next: 0 };
|
|
let got_forkid =
|
|
chainspec.fork_id(&Head { number: 73, timestamp: 840, ..Default::default() });
|
|
|
|
// check that they're the same
|
|
assert_eq!(got_forkid, expected_forkid);
|
|
// Check that paris block and final difficulty are set correctly
|
|
assert_eq!(chainspec.paris_block_and_final_difficulty, Some((72, U256::from(9454784))));
|
|
}
|
|
|
|
#[test]
|
|
fn test_parse_genesis_json() {
|
|
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"}"#;
|
|
let genesis: Genesis = serde_json::from_str(s).unwrap();
|
|
let acc = genesis
|
|
.alloc
|
|
.get(&"0xaa00000000000000000000000000000000000000".parse::<Address>().unwrap())
|
|
.unwrap();
|
|
assert_eq!(acc.balance, U256::from(1));
|
|
assert_eq!(genesis.base_fee_per_gas, Some(0x1337));
|
|
}
|
|
|
|
#[test]
|
|
fn test_parse_cancun_genesis_json() {
|
|
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"}"#;
|
|
let genesis: Genesis = serde_json::from_str(s).unwrap();
|
|
let acc = genesis
|
|
.alloc
|
|
.get(&"0xaa00000000000000000000000000000000000000".parse::<Address>().unwrap())
|
|
.unwrap();
|
|
assert_eq!(acc.balance, U256::from(1));
|
|
// assert that the cancun time was picked up
|
|
assert_eq!(genesis.config.cancun_time, Some(4661));
|
|
}
|
|
|
|
#[test]
|
|
fn test_parse_prague_genesis_all_formats() {
|
|
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"}"#;
|
|
let genesis: Genesis = serde_json::from_str(s).unwrap();
|
|
|
|
// assert that the alloc was picked up
|
|
let acc = genesis
|
|
.alloc
|
|
.get(&"0xaa00000000000000000000000000000000000000".parse::<Address>().unwrap())
|
|
.unwrap();
|
|
assert_eq!(acc.balance, U256::from(1));
|
|
// assert that the cancun time was picked up
|
|
assert_eq!(genesis.config.cancun_time, Some(4661));
|
|
// assert that the prague time was picked up
|
|
assert_eq!(genesis.config.prague_time, Some(4662));
|
|
}
|
|
|
|
#[test]
|
|
fn test_parse_cancun_genesis_all_formats() {
|
|
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"}"#;
|
|
let genesis: Genesis = serde_json::from_str(s).unwrap();
|
|
|
|
// assert that the alloc was picked up
|
|
let acc = genesis
|
|
.alloc
|
|
.get(&"0xaa00000000000000000000000000000000000000".parse::<Address>().unwrap())
|
|
.unwrap();
|
|
assert_eq!(acc.balance, U256::from(1));
|
|
// assert that the cancun time was picked up
|
|
assert_eq!(genesis.config.cancun_time, Some(4661));
|
|
}
|
|
|
|
#[test]
|
|
fn test_paris_block_and_total_difficulty() {
|
|
let genesis = Genesis { gas_limit: 0x2fefd8u128, ..Default::default() };
|
|
let paris_chainspec = ChainSpecBuilder::default()
|
|
.chain(Chain::from_id(1337))
|
|
.genesis(genesis)
|
|
.paris_activated()
|
|
.build();
|
|
assert_eq!(paris_chainspec.paris_block_and_final_difficulty, Some((0, U256::ZERO)));
|
|
}
|
|
|
|
#[test]
|
|
fn test_default_cancun_header_forkhash() {
|
|
// set the gas limit from the hive test genesis according to the hash
|
|
let genesis = Genesis { gas_limit: 0x2fefd8u128, ..Default::default() };
|
|
let default_chainspec = ChainSpecBuilder::default()
|
|
.chain(Chain::from_id(1337))
|
|
.genesis(genesis)
|
|
.cancun_activated()
|
|
.build();
|
|
let mut header = default_chainspec.genesis_header();
|
|
|
|
// set the state root to the same as in the hive test the hash was pulled from
|
|
header.state_root =
|
|
B256::from_str("0x62e2595e017f0ca23e08d17221010721a71c3ae932f4ea3cb12117786bb392d4")
|
|
.unwrap();
|
|
|
|
// shanghai is activated so we should have a withdrawals root
|
|
assert_eq!(header.withdrawals_root, Some(EMPTY_WITHDRAWALS));
|
|
|
|
// cancun is activated so we should have a zero parent beacon block root, zero blob gas
|
|
// used, and zero excess blob gas
|
|
assert_eq!(header.parent_beacon_block_root, Some(B256::ZERO));
|
|
assert_eq!(header.blob_gas_used, Some(0));
|
|
assert_eq!(header.excess_blob_gas, Some(0));
|
|
|
|
// check the genesis hash
|
|
let genesis_hash = header.hash_slow();
|
|
let expected_hash =
|
|
b256!("16bb7c59613a5bad3f7c04a852fd056545ade2483968d9a25a1abb05af0c4d37");
|
|
assert_eq!(genesis_hash, expected_hash);
|
|
|
|
// check that the forkhash is correct
|
|
let expected_forkhash = ForkHash(hex!("8062457a"));
|
|
assert_eq!(ForkHash::from(genesis_hash), expected_forkhash);
|
|
}
|
|
|
|
#[test]
|
|
fn holesky_paris_activated_at_genesis() {
|
|
assert!(HOLESKY
|
|
.fork(EthereumHardfork::Paris)
|
|
.active_at_ttd(HOLESKY.genesis.difficulty, HOLESKY.genesis.difficulty));
|
|
}
|
|
|
|
#[test]
|
|
fn test_genesis_format_deserialization() {
|
|
// custom genesis with chain config
|
|
let config = ChainConfig {
|
|
chain_id: 2600,
|
|
homestead_block: Some(0),
|
|
eip150_block: Some(0),
|
|
eip155_block: Some(0),
|
|
eip158_block: Some(0),
|
|
byzantium_block: Some(0),
|
|
constantinople_block: Some(0),
|
|
petersburg_block: Some(0),
|
|
istanbul_block: Some(0),
|
|
berlin_block: Some(0),
|
|
london_block: Some(0),
|
|
shanghai_time: Some(0),
|
|
terminal_total_difficulty: Some(U256::ZERO),
|
|
terminal_total_difficulty_passed: true,
|
|
..Default::default()
|
|
};
|
|
// genesis
|
|
let genesis = Genesis {
|
|
config,
|
|
nonce: 0,
|
|
timestamp: 1698688670,
|
|
gas_limit: 5000,
|
|
difficulty: U256::ZERO,
|
|
mix_hash: B256::ZERO,
|
|
coinbase: Address::ZERO,
|
|
..Default::default()
|
|
};
|
|
|
|
// seed accounts after genesis struct created
|
|
let address = hex!("6Be02d1d3665660d22FF9624b7BE0551ee1Ac91b").into();
|
|
let account = GenesisAccount::default().with_balance(U256::from(33));
|
|
let genesis = genesis.extend_accounts(HashMap::from([(address, account)]));
|
|
|
|
// ensure genesis is deserialized correctly
|
|
let serialized_genesis = serde_json::to_string(&genesis).unwrap();
|
|
let deserialized_genesis: Genesis = serde_json::from_str(&serialized_genesis).unwrap();
|
|
|
|
assert_eq!(genesis, deserialized_genesis);
|
|
}
|
|
|
|
#[test]
|
|
fn check_fork_id_chainspec_with_fork_condition_never() {
|
|
let spec = ChainSpec {
|
|
chain: Chain::mainnet(),
|
|
genesis: Genesis::default(),
|
|
genesis_hash: None,
|
|
hardforks: ChainHardforks::new(vec![(
|
|
EthereumHardfork::Frontier.boxed(),
|
|
ForkCondition::Never,
|
|
)]),
|
|
paris_block_and_final_difficulty: None,
|
|
deposit_contract: None,
|
|
..Default::default()
|
|
};
|
|
|
|
assert_eq!(spec.hardfork_fork_id(EthereumHardfork::Frontier), None);
|
|
}
|
|
|
|
#[test]
|
|
fn check_fork_filter_chainspec_with_fork_condition_never() {
|
|
let spec = ChainSpec {
|
|
chain: Chain::mainnet(),
|
|
genesis: Genesis::default(),
|
|
genesis_hash: None,
|
|
hardforks: ChainHardforks::new(vec![(
|
|
EthereumHardfork::Shanghai.boxed(),
|
|
ForkCondition::Never,
|
|
)]),
|
|
paris_block_and_final_difficulty: None,
|
|
deposit_contract: None,
|
|
..Default::default()
|
|
};
|
|
|
|
assert_eq!(spec.hardfork_fork_filter(EthereumHardfork::Shanghai), None);
|
|
}
|
|
|
|
#[test]
|
|
#[cfg(feature = "optimism")]
|
|
fn base_mainnet_genesis() {
|
|
let genesis = BASE_MAINNET.genesis_header();
|
|
assert_eq!(
|
|
genesis.hash_slow(),
|
|
b256!("f712aa9241cc24369b143cf6dce85f0902a9731e70d66818a3a5845b296c73dd")
|
|
);
|
|
let base_fee = genesis
|
|
.next_block_base_fee(BASE_MAINNET.base_fee_params_at_timestamp(genesis.timestamp))
|
|
.unwrap();
|
|
// <https://base.blockscout.com/block/1>
|
|
assert_eq!(base_fee, 980000000);
|
|
}
|
|
|
|
#[test]
|
|
#[cfg(feature = "optimism")]
|
|
fn base_sepolia_genesis() {
|
|
let genesis = BASE_SEPOLIA.genesis_header();
|
|
assert_eq!(
|
|
genesis.hash_slow(),
|
|
b256!("0dcc9e089e30b90ddfc55be9a37dd15bc551aeee999d2e2b51414c54eaf934e4")
|
|
);
|
|
let base_fee = genesis
|
|
.next_block_base_fee(BASE_SEPOLIA.base_fee_params_at_timestamp(genesis.timestamp))
|
|
.unwrap();
|
|
// <https://base-sepolia.blockscout.com/block/1>
|
|
assert_eq!(base_fee, 980000000);
|
|
}
|
|
|
|
#[test]
|
|
#[cfg(feature = "optimism")]
|
|
fn op_sepolia_genesis() {
|
|
let genesis = OP_SEPOLIA.genesis_header();
|
|
assert_eq!(
|
|
genesis.hash_slow(),
|
|
b256!("102de6ffb001480cc9b8b548fd05c34cd4f46ae4aa91759393db90ea0409887d")
|
|
);
|
|
let base_fee = genesis
|
|
.next_block_base_fee(OP_SEPOLIA.base_fee_params_at_timestamp(genesis.timestamp))
|
|
.unwrap();
|
|
// <https://optimism-sepolia.blockscout.com/block/1>
|
|
assert_eq!(base_fee, 980000000);
|
|
}
|
|
|
|
#[test]
|
|
fn latest_eth_mainnet_fork_id() {
|
|
assert_eq!(
|
|
ForkId { hash: ForkHash([0x9f, 0x3d, 0x22, 0x54]), next: 0 },
|
|
MAINNET.latest_fork_id()
|
|
)
|
|
}
|
|
|
|
#[cfg(feature = "optimism")]
|
|
#[test]
|
|
fn latest_base_mainnet_fork_id() {
|
|
assert_eq!(
|
|
ForkId { hash: ForkHash([0xe4, 0x01, 0x0e, 0xb9]), next: 0 },
|
|
BASE_MAINNET.latest_fork_id()
|
|
)
|
|
}
|
|
|
|
#[cfg(feature = "optimism")]
|
|
#[test]
|
|
fn is_bedrock_active() {
|
|
assert!(!OP_MAINNET.is_bedrock_active_at_block(1))
|
|
}
|
|
|
|
#[cfg(feature = "optimism")]
|
|
#[test]
|
|
fn parse_optimism_hardforks() {
|
|
let geth_genesis = r#"
|
|
{
|
|
"config": {
|
|
"bedrockBlock": 10,
|
|
"regolithTime": 20,
|
|
"canyonTime": 30,
|
|
"ecotoneTime": 40,
|
|
"fjordTime": 50,
|
|
"optimism": {
|
|
"eip1559Elasticity": 60,
|
|
"eip1559Denominator": 70
|
|
}
|
|
}
|
|
}
|
|
"#;
|
|
let genesis: Genesis = serde_json::from_str(geth_genesis).unwrap();
|
|
|
|
let actual_bedrock_block = genesis.config.extra_fields.get("bedrockBlock");
|
|
assert_eq!(actual_bedrock_block, Some(serde_json::Value::from(10)).as_ref());
|
|
let actual_regolith_timestamp = genesis.config.extra_fields.get("regolithTime");
|
|
assert_eq!(actual_regolith_timestamp, Some(serde_json::Value::from(20)).as_ref());
|
|
let actual_canyon_timestamp = genesis.config.extra_fields.get("canyonTime");
|
|
assert_eq!(actual_canyon_timestamp, Some(serde_json::Value::from(30)).as_ref());
|
|
let actual_ecotone_timestamp = genesis.config.extra_fields.get("ecotoneTime");
|
|
assert_eq!(actual_ecotone_timestamp, Some(serde_json::Value::from(40)).as_ref());
|
|
let actual_fjord_timestamp = genesis.config.extra_fields.get("fjordTime");
|
|
assert_eq!(actual_fjord_timestamp, Some(serde_json::Value::from(50)).as_ref());
|
|
|
|
let optimism_object = genesis.config.extra_fields.get("optimism").unwrap();
|
|
assert_eq!(
|
|
optimism_object,
|
|
&serde_json::json!({
|
|
"eip1559Elasticity": 60,
|
|
"eip1559Denominator": 70,
|
|
})
|
|
);
|
|
|
|
let chain_spec: ChainSpec = genesis.into();
|
|
|
|
assert_eq!(
|
|
chain_spec.base_fee_params,
|
|
BaseFeeParamsKind::Constant(BaseFeeParams::new(70, 60))
|
|
);
|
|
|
|
assert!(!chain_spec.is_fork_active_at_block(OptimismHardfork::Bedrock, 0));
|
|
assert!(!chain_spec.is_fork_active_at_timestamp(OptimismHardfork::Regolith, 0));
|
|
assert!(!chain_spec.is_fork_active_at_timestamp(OptimismHardfork::Canyon, 0));
|
|
assert!(!chain_spec.is_fork_active_at_timestamp(OptimismHardfork::Ecotone, 0));
|
|
assert!(!chain_spec.is_fork_active_at_timestamp(OptimismHardfork::Fjord, 0));
|
|
|
|
assert!(chain_spec.is_fork_active_at_block(OptimismHardfork::Bedrock, 10));
|
|
assert!(chain_spec.is_fork_active_at_timestamp(OptimismHardfork::Regolith, 20));
|
|
assert!(chain_spec.is_fork_active_at_timestamp(OptimismHardfork::Canyon, 30));
|
|
assert!(chain_spec.is_fork_active_at_timestamp(OptimismHardfork::Ecotone, 40));
|
|
assert!(chain_spec.is_fork_active_at_timestamp(OptimismHardfork::Fjord, 50));
|
|
}
|
|
|
|
#[cfg(feature = "optimism")]
|
|
#[test]
|
|
fn parse_optimism_hardforks_variable_base_fee_params() {
|
|
let geth_genesis = r#"
|
|
{
|
|
"config": {
|
|
"bedrockBlock": 10,
|
|
"regolithTime": 20,
|
|
"canyonTime": 30,
|
|
"ecotoneTime": 40,
|
|
"fjordTime": 50,
|
|
"optimism": {
|
|
"eip1559Elasticity": 60,
|
|
"eip1559Denominator": 70,
|
|
"eip1559DenominatorCanyon": 80
|
|
}
|
|
}
|
|
}
|
|
"#;
|
|
let genesis: Genesis = serde_json::from_str(geth_genesis).unwrap();
|
|
|
|
let actual_bedrock_block = genesis.config.extra_fields.get("bedrockBlock");
|
|
assert_eq!(actual_bedrock_block, Some(serde_json::Value::from(10)).as_ref());
|
|
let actual_regolith_timestamp = genesis.config.extra_fields.get("regolithTime");
|
|
assert_eq!(actual_regolith_timestamp, Some(serde_json::Value::from(20)).as_ref());
|
|
let actual_canyon_timestamp = genesis.config.extra_fields.get("canyonTime");
|
|
assert_eq!(actual_canyon_timestamp, Some(serde_json::Value::from(30)).as_ref());
|
|
let actual_ecotone_timestamp = genesis.config.extra_fields.get("ecotoneTime");
|
|
assert_eq!(actual_ecotone_timestamp, Some(serde_json::Value::from(40)).as_ref());
|
|
let actual_fjord_timestamp = genesis.config.extra_fields.get("fjordTime");
|
|
assert_eq!(actual_fjord_timestamp, Some(serde_json::Value::from(50)).as_ref());
|
|
|
|
let optimism_object = genesis.config.extra_fields.get("optimism").unwrap();
|
|
assert_eq!(
|
|
optimism_object,
|
|
&serde_json::json!({
|
|
"eip1559Elasticity": 60,
|
|
"eip1559Denominator": 70,
|
|
"eip1559DenominatorCanyon": 80
|
|
})
|
|
);
|
|
|
|
let chain_spec: ChainSpec = genesis.into();
|
|
|
|
assert_eq!(
|
|
chain_spec.base_fee_params,
|
|
BaseFeeParamsKind::Variable(
|
|
vec![
|
|
(EthereumHardfork::London.boxed(), BaseFeeParams::new(70, 60)),
|
|
(OptimismHardfork::Canyon.boxed(), BaseFeeParams::new(80, 60)),
|
|
]
|
|
.into()
|
|
)
|
|
);
|
|
|
|
assert!(!chain_spec.is_fork_active_at_block(OptimismHardfork::Bedrock, 0));
|
|
assert!(!chain_spec.is_fork_active_at_timestamp(OptimismHardfork::Regolith, 0));
|
|
assert!(!chain_spec.is_fork_active_at_timestamp(OptimismHardfork::Canyon, 0));
|
|
assert!(!chain_spec.is_fork_active_at_timestamp(OptimismHardfork::Ecotone, 0));
|
|
assert!(!chain_spec.is_fork_active_at_timestamp(OptimismHardfork::Fjord, 0));
|
|
|
|
assert!(chain_spec.is_fork_active_at_block(OptimismHardfork::Bedrock, 10));
|
|
assert!(chain_spec.is_fork_active_at_timestamp(OptimismHardfork::Regolith, 20));
|
|
assert!(chain_spec.is_fork_active_at_timestamp(OptimismHardfork::Canyon, 30));
|
|
assert!(chain_spec.is_fork_active_at_timestamp(OptimismHardfork::Ecotone, 40));
|
|
assert!(chain_spec.is_fork_active_at_timestamp(OptimismHardfork::Fjord, 50));
|
|
}
|
|
|
|
#[cfg(feature = "optimism")]
|
|
#[test]
|
|
fn parse_genesis_optimism_with_variable_base_fee_params() {
|
|
use op_alloy_rpc_types::genesis::OptimismBaseFeeInfo;
|
|
|
|
let geth_genesis = r#"
|
|
{
|
|
"config": {
|
|
"chainId": 8453,
|
|
"homesteadBlock": 0,
|
|
"eip150Block": 0,
|
|
"eip155Block": 0,
|
|
"eip158Block": 0,
|
|
"byzantiumBlock": 0,
|
|
"constantinopleBlock": 0,
|
|
"petersburgBlock": 0,
|
|
"istanbulBlock": 0,
|
|
"muirGlacierBlock": 0,
|
|
"berlinBlock": 0,
|
|
"londonBlock": 0,
|
|
"arrowGlacierBlock": 0,
|
|
"grayGlacierBlock": 0,
|
|
"mergeNetsplitBlock": 0,
|
|
"bedrockBlock": 0,
|
|
"regolithTime": 15,
|
|
"terminalTotalDifficulty": 0,
|
|
"terminalTotalDifficultyPassed": true,
|
|
"optimism": {
|
|
"eip1559Elasticity": 6,
|
|
"eip1559Denominator": 50
|
|
}
|
|
}
|
|
}
|
|
"#;
|
|
let genesis: Genesis = serde_json::from_str(geth_genesis).unwrap();
|
|
let chainspec = ChainSpec::from(genesis.clone());
|
|
|
|
let actual_chain_id = genesis.config.chain_id;
|
|
assert_eq!(actual_chain_id, 8453);
|
|
|
|
assert_eq!(
|
|
chainspec.hardforks.get(EthereumHardfork::Istanbul),
|
|
Some(ForkCondition::Block(0))
|
|
);
|
|
|
|
let actual_bedrock_block = genesis.config.extra_fields.get("bedrockBlock");
|
|
assert_eq!(actual_bedrock_block, Some(serde_json::Value::from(0)).as_ref());
|
|
let actual_canyon_timestamp = genesis.config.extra_fields.get("canyonTime");
|
|
assert_eq!(actual_canyon_timestamp, None);
|
|
|
|
assert!(genesis.config.terminal_total_difficulty_passed);
|
|
|
|
let optimism_object = genesis.config.extra_fields.get("optimism").unwrap();
|
|
let optimism_base_fee_info =
|
|
serde_json::from_value::<OptimismBaseFeeInfo>(optimism_object.clone()).unwrap();
|
|
|
|
assert_eq!(
|
|
optimism_base_fee_info,
|
|
OptimismBaseFeeInfo {
|
|
eip1559_elasticity: Some(6),
|
|
eip1559_denominator: Some(50),
|
|
eip1559_denominator_canyon: None,
|
|
}
|
|
);
|
|
assert_eq!(
|
|
chainspec.base_fee_params,
|
|
BaseFeeParamsKind::Constant(BaseFeeParams {
|
|
max_change_denominator: 50,
|
|
elasticity_multiplier: 6,
|
|
})
|
|
);
|
|
|
|
assert!(chainspec.is_fork_active_at_block(OptimismHardfork::Bedrock, 0));
|
|
|
|
assert!(chainspec.is_fork_active_at_timestamp(OptimismHardfork::Regolith, 20));
|
|
}
|
|
}
|