diff --git a/crates/chainspec/src/spec.rs b/crates/chainspec/src/spec.rs index d6ca92aa2..f80a20924 100644 --- a/crates/chainspec/src/spec.rs +++ b/crates/chainspec/src/spec.rs @@ -755,13 +755,19 @@ impl ChainSpecBuilder { } /// Add the given fork with the given activation condition to the spec. - pub fn with_fork(mut self, fork: EthereumHardfork, condition: ForkCondition) -> Self { + pub fn with_fork(mut self, fork: H, condition: ForkCondition) -> Self { self.hardforks.insert(fork, condition); self } + /// Add the given chain hardforks to the spec. + pub fn with_forks(mut self, forks: ChainHardforks) -> Self { + self.hardforks = forks; + self + } + /// Remove the given fork from the spec. - pub fn without_fork(mut self, fork: EthereumHardfork) -> Self { + pub fn without_fork(mut self, fork: H) -> Self { self.hardforks.remove(fork); self } @@ -876,63 +882,6 @@ impl ChainSpecBuilder { self } - /// Enable Bedrock at genesis - #[cfg(feature = "optimism")] - pub fn bedrock_activated(mut self) -> Self { - self = self.paris_activated(); - self.hardforks - .insert(reth_optimism_forks::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(reth_optimism_forks::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(reth_optimism_forks::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(reth_optimism_forks::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(reth_optimism_forks::OptimismHardfork::Fjord, ForkCondition::Timestamp(0)); - self - } - - /// Enable Granite at genesis - #[cfg(feature = "optimism")] - pub fn granite_activated(mut self) -> Self { - self = self.fjord_activated(); - self.hardforks - .insert(reth_optimism_forks::OptimismHardfork::Granite, ForkCondition::Timestamp(0)); - self - } - /// Build the resulting [`ChainSpec`]. /// /// # Panics diff --git a/crates/optimism/chainspec/src/lib.rs b/crates/optimism/chainspec/src/lib.rs index 9b2d1c8c1..5ebc18f67 100644 --- a/crates/optimism/chainspec/src/lib.rs +++ b/crates/optimism/chainspec/src/lib.rs @@ -16,25 +16,154 @@ mod dev; mod op; mod op_sepolia; -use std::fmt::Display; - +use alloy_chains::Chain; use alloy_genesis::Genesis; use alloy_primitives::{Parity, Signature, B256, U256}; pub use base::BASE_MAINNET; pub use base_sepolia::BASE_SEPOLIA; +use derive_more::{Constructor, Deref, From, Into}; pub use dev::OP_DEV; +use once_cell::sync::OnceCell; pub use op::OP_MAINNET; pub use op_sepolia::OP_SEPOLIA; - -use derive_more::{Constructor, Deref, Into}; -use once_cell::sync::OnceCell; use reth_chainspec::{ - BaseFeeParams, BaseFeeParamsKind, ChainSpec, DepositContract, EthChainSpec, EthereumHardforks, - ForkFilter, ForkId, Hardforks, Head, + BaseFeeParams, BaseFeeParamsKind, ChainSpec, ChainSpecBuilder, DepositContract, EthChainSpec, + EthereumHardforks, ForkFilter, ForkId, Hardforks, Head, }; -use reth_ethereum_forks::{ChainHardforks, EthereumHardfork, ForkCondition}; +use reth_ethereum_forks::{ChainHardforks, EthereumHardfork, ForkCondition, Hardfork}; use reth_network_peers::NodeRecord; use reth_primitives_traits::Header; +use std::fmt::Display; + +/// Chain spec builder for a OP stack chain. +#[derive(Debug, Default, From)] +pub struct OpChainSpecBuilder { + /// [`ChainSpecBuilder`] + inner: ChainSpecBuilder, +} + +impl OpChainSpecBuilder { + /// Construct a new builder from the base mainnet chain spec. + pub fn base_mainnet() -> Self { + let mut inner = ChainSpecBuilder::default() + .chain(BASE_MAINNET.chain) + .genesis(BASE_MAINNET.genesis.clone()); + let forks = BASE_MAINNET.hardforks.clone(); + inner = inner.with_forks(forks); + + Self { inner } + } + + /// Construct a new builder from the optimism mainnet chain spec. + pub fn optimism_mainnet() -> Self { + let mut inner = + ChainSpecBuilder::default().chain(OP_MAINNET.chain).genesis(OP_MAINNET.genesis.clone()); + let forks = OP_MAINNET.hardforks.clone(); + inner = inner.with_forks(forks); + + Self { inner } + } +} + +impl OpChainSpecBuilder { + /// Set the chain ID + pub fn chain(mut self, chain: Chain) -> Self { + self.inner = self.inner.chain(chain); + self + } + + /// Set the genesis block. + pub fn genesis(mut self, genesis: Genesis) -> Self { + self.inner = self.inner.genesis(genesis); + self + } + + /// Add the given fork with the given activation condition to the spec. + pub fn with_fork(mut self, fork: H, condition: ForkCondition) -> Self { + self.inner = self.inner.with_fork(fork, condition); + self + } + + /// Add the given forks with the given activation condition to the spec. + pub fn with_forks(mut self, forks: ChainHardforks) -> Self { + self.inner = self.inner.with_forks(forks); + self + } + + /// Remove the given fork from the spec. + pub fn without_fork(mut self, fork: reth_optimism_forks::OptimismHardfork) -> Self { + self.inner = self.inner.without_fork(fork); + self + } + + /// Enable Bedrock at genesis + pub fn bedrock_activated(mut self) -> Self { + self.inner = self.inner.paris_activated(); + self.inner = self + .inner + .with_fork(reth_optimism_forks::OptimismHardfork::Bedrock, ForkCondition::Block(0)); + self + } + + /// Enable Regolith at genesis + pub fn regolith_activated(mut self) -> Self { + self = self.bedrock_activated(); + self.inner = self.inner.with_fork( + reth_optimism_forks::OptimismHardfork::Regolith, + ForkCondition::Timestamp(0), + ); + self + } + + /// Enable Canyon at genesis + pub fn canyon_activated(mut self) -> Self { + self = self.regolith_activated(); + // Canyon also activates changes from L1's Shanghai hardfork + self.inner = self.inner.with_fork(EthereumHardfork::Shanghai, ForkCondition::Timestamp(0)); + self.inner = self + .inner + .with_fork(reth_optimism_forks::OptimismHardfork::Canyon, ForkCondition::Timestamp(0)); + self + } + + /// Enable Ecotone at genesis + pub fn ecotone_activated(mut self) -> Self { + self = self.canyon_activated(); + self.inner = self.inner.with_fork(EthereumHardfork::Cancun, ForkCondition::Timestamp(0)); + self.inner = self + .inner + .with_fork(reth_optimism_forks::OptimismHardfork::Ecotone, ForkCondition::Timestamp(0)); + self + } + + /// Enable Fjord at genesis + pub fn fjord_activated(mut self) -> Self { + self = self.ecotone_activated(); + self.inner = self + .inner + .with_fork(reth_optimism_forks::OptimismHardfork::Fjord, ForkCondition::Timestamp(0)); + self + } + + /// Enable Granite at genesis + pub fn granite_activated(mut self) -> Self { + self = self.fjord_activated(); + self.inner = self + .inner + .with_fork(reth_optimism_forks::OptimismHardfork::Granite, ForkCondition::Timestamp(0)); + self + } + + /// Build the resulting [`OpChainSpec`]. + /// + /// # Panics + /// + /// This function panics if the chain ID and genesis is not set ([`Self::chain`] and + /// [`Self::genesis`]) + pub fn build(self) -> OpChainSpec { + OpChainSpec { inner: self.inner.build() } + } +} /// OP stack chain spec type. #[derive(Debug, Clone, Deref, Into, Constructor, PartialEq, Eq)] @@ -286,6 +415,8 @@ mod tests { #[test] fn base_mainnet_forkids() { + let base_mainnet = OpChainSpecBuilder::base_mainnet().build(); + let _ = base_mainnet.genesis_hash.set(BASE_MAINNET.genesis_hash.get().copied().unwrap()); test_fork_ids( &BASE_MAINNET, &[ @@ -372,8 +503,12 @@ mod tests { #[test] fn op_mainnet_forkids() { + let op_mainnet = OpChainSpecBuilder::optimism_mainnet().build(); + // for OP mainnet we have to do this because the genesis header can't be properly computed + // from the genesis.json file + let _ = op_mainnet.genesis_hash.set(OP_MAINNET.genesis_hash()); test_fork_ids( - &OP_MAINNET, + &op_mainnet, &[ ( Head { number: 0, ..Default::default() }, @@ -483,9 +618,19 @@ mod tests { ) } + #[test] + fn latest_base_mainnet_fork_id_with_builder() { + let base_mainnet = OpChainSpecBuilder::base_mainnet().build(); + assert_eq!( + ForkId { hash: ForkHash([0xbc, 0x38, 0xf9, 0xca]), next: 0 }, + base_mainnet.latest_fork_id() + ) + } + #[test] fn is_bedrock_active() { - assert!(!OP_MAINNET.is_bedrock_active_at_block(1)) + let op_mainnet = OpChainSpecBuilder::optimism_mainnet().build(); + assert!(!op_mainnet.is_bedrock_active_at_block(1)) } #[test] diff --git a/crates/optimism/evm/src/config.rs b/crates/optimism/evm/src/config.rs index 95d2c9a66..668fcba4d 100644 --- a/crates/optimism/evm/src/config.rs +++ b/crates/optimism/evm/src/config.rs @@ -1,5 +1,5 @@ -use reth_chainspec::ChainSpec; use reth_ethereum_forks::{EthereumHardfork, Head}; +use reth_optimism_chainspec::OpChainSpec; use reth_optimism_forks::OptimismHardfork; /// Returns the revm [`SpecId`](revm_primitives::SpecId) at the given timestamp. @@ -9,7 +9,7 @@ use reth_optimism_forks::OptimismHardfork; /// This is only intended to be used after the Bedrock, when hardforks are activated by /// timestamp. pub fn revm_spec_by_timestamp_after_bedrock( - chain_spec: &ChainSpec, + chain_spec: &OpChainSpec, timestamp: u64, ) -> revm_primitives::SpecId { if chain_spec.fork(OptimismHardfork::Granite).active_at_timestamp(timestamp) { @@ -28,7 +28,7 @@ pub fn revm_spec_by_timestamp_after_bedrock( } /// Map the latest active hardfork at the given block to a revm [`SpecId`](revm_primitives::SpecId). -pub fn revm_spec(chain_spec: &ChainSpec, block: &Head) -> revm_primitives::SpecId { +pub fn revm_spec(chain_spec: &OpChainSpec, block: &Head) -> revm_primitives::SpecId { if chain_spec.fork(OptimismHardfork::Granite).active_at_head(block) { revm_primitives::GRANITE } else if chain_spec.fork(OptimismHardfork::Fjord).active_at_head(block) { @@ -79,12 +79,13 @@ pub fn revm_spec(chain_spec: &ChainSpec, block: &Head) -> revm_primitives::SpecI mod tests { use super::*; use reth_chainspec::ChainSpecBuilder; + use reth_optimism_chainspec::{OpChainSpec, OpChainSpecBuilder}; #[test] fn test_revm_spec_by_timestamp_after_merge() { #[inline(always)] - fn op_cs(f: impl FnOnce(ChainSpecBuilder) -> ChainSpecBuilder) -> ChainSpec { - let cs = ChainSpecBuilder::mainnet().chain(reth_chainspec::Chain::from_id(10)); + fn op_cs(f: impl FnOnce(OpChainSpecBuilder) -> OpChainSpecBuilder) -> OpChainSpec { + let cs = ChainSpecBuilder::mainnet().chain(reth_chainspec::Chain::from_id(10)).into(); f(cs).build() } assert_eq!( @@ -116,8 +117,8 @@ mod tests { #[test] fn test_to_revm_spec() { #[inline(always)] - fn op_cs(f: impl FnOnce(ChainSpecBuilder) -> ChainSpecBuilder) -> ChainSpec { - let cs = ChainSpecBuilder::mainnet().chain(reth_chainspec::Chain::from_id(10)); + fn op_cs(f: impl FnOnce(OpChainSpecBuilder) -> OpChainSpecBuilder) -> OpChainSpec { + let cs = ChainSpecBuilder::mainnet().chain(reth_chainspec::Chain::from_id(10)).into(); f(cs).build() } assert_eq!( diff --git a/crates/optimism/evm/src/execute.rs b/crates/optimism/evm/src/execute.rs index 2491e99b5..f4abb8c88 100644 --- a/crates/optimism/evm/src/execute.rs +++ b/crates/optimism/evm/src/execute.rs @@ -513,8 +513,8 @@ mod tests { use crate::OpChainSpec; use alloy_consensus::TxEip1559; use alloy_primitives::{b256, Address, StorageKey, StorageValue}; - use reth_chainspec::{ChainSpecBuilder, MIN_TRANSACTION_GAS}; - use reth_optimism_chainspec::{optimism_deposit_tx_signature, BASE_MAINNET}; + use reth_chainspec::MIN_TRANSACTION_GAS; + use reth_optimism_chainspec::{optimism_deposit_tx_signature, OpChainSpecBuilder}; use reth_primitives::{Account, Block, BlockBody, Signature, Transaction, TransactionSigned}; use reth_revm::{ database::StateProviderDatabase, test_utils::StateProviderTest, L1_BLOCK_CONTRACT, @@ -548,8 +548,7 @@ mod tests { db } - fn executor_provider(chain_spec: Arc) -> OpExecutorProvider { - let chain_spec = Arc::new(OpChainSpec::new(Arc::unwrap_or_clone(chain_spec))); + fn executor_provider(chain_spec: Arc) -> OpExecutorProvider { OpExecutorProvider { evm_config: OptimismEvmConfig::new(chain_spec.clone()), chain_spec } } @@ -572,11 +571,7 @@ mod tests { let account = Account { balance: U256::MAX, ..Account::default() }; db.insert_account(addr, account, None, HashMap::default()); - let chain_spec = Arc::new( - ChainSpecBuilder::from(&Arc::new(BASE_MAINNET.inner.clone())) - .regolith_activated() - .build(), - ); + let chain_spec = Arc::new(OpChainSpecBuilder::base_mainnet().regolith_activated().build()); let tx = TransactionSigned::from_transaction_and_signature( Transaction::Eip1559(TxEip1559 { @@ -656,11 +651,7 @@ mod tests { db.insert_account(addr, account, None, HashMap::default()); - let chain_spec = Arc::new( - ChainSpecBuilder::from(&Arc::new(BASE_MAINNET.inner.clone())) - .canyon_activated() - .build(), - ); + let chain_spec = Arc::new(OpChainSpecBuilder::base_mainnet().canyon_activated().build()); let tx = TransactionSigned::from_transaction_and_signature( Transaction::Eip1559(TxEip1559 { diff --git a/crates/optimism/node/tests/e2e/utils.rs b/crates/optimism/node/tests/e2e/utils.rs index 1e9ffa652..863bf254e 100644 --- a/crates/optimism/node/tests/e2e/utils.rs +++ b/crates/optimism/node/tests/e2e/utils.rs @@ -1,15 +1,13 @@ -use std::sync::Arc; - use alloy_genesis::Genesis; use alloy_primitives::{Address, B256}; use reth::{rpc::types::engine::PayloadAttributes, tasks::TaskManager}; -use reth_chainspec::ChainSpecBuilder; use reth_e2e_test_utils::{transaction::TransactionTestContext, wallet::Wallet, NodeHelperType}; -use reth_optimism_chainspec::{OpChainSpec, BASE_MAINNET}; +use reth_optimism_chainspec::OpChainSpecBuilder; use reth_optimism_node::{ node::OptimismAddOns, OptimismBuiltPayload, OptimismNode, OptimismPayloadBuilderAttributes, }; use reth_payload_builder::EthPayloadBuilderAttributes; +use std::sync::Arc; use tokio::sync::Mutex; /// Optimism Node Helper type @@ -19,13 +17,7 @@ pub(crate) async fn setup(num_nodes: usize) -> eyre::Result<(Vec, TaskMa let genesis: Genesis = serde_json::from_str(include_str!("../assets/genesis.json")).unwrap(); reth_e2e_test_utils::setup( num_nodes, - Arc::new(OpChainSpec::new( - ChainSpecBuilder::default() - .chain(BASE_MAINNET.chain) - .genesis(genesis) - .ecotone_activated() - .build(), - )), + Arc::new(OpChainSpecBuilder::base_mainnet().genesis(genesis).ecotone_activated().build()), false, ) .await