diff --git a/crates/evm/src/call.rs b/crates/evm/src/call.rs index afce08785..676d4502f 100644 --- a/crates/evm/src/call.rs +++ b/crates/evm/src/call.rs @@ -64,7 +64,7 @@ impl Evm { .map(|info| info.nonce) .unwrap_or(0); - let db: EvmDb<'_, C> = self.get_db(working_set); + let db: EvmDb<'_, C> = self.get_db(working_set, cfg_env.handler_cfg.spec_id); let system_txs = create_system_transactions(system_events, system_nonce, cfg_env.chain_id); let mut citrea_handler_ext = CitreaExternal::new(l1_fee_rate); @@ -154,7 +154,7 @@ impl Evm { log_index_start = tx.receipt.log_index_start + tx.receipt.receipt.logs.len() as u64; } - let evm_db: EvmDb<'_, C> = self.get_db(working_set); + let evm_db: EvmDb<'_, C> = self.get_db(working_set, cfg_env.handler_cfg.spec_id); let results = executor::execute_multiple_tx( evm_db, diff --git a/crates/evm/src/evm/db.rs b/crates/evm/src/evm/db.rs index d72fab82d..94b8b5ed6 100644 --- a/crates/evm/src/evm/db.rs +++ b/crates/evm/src/evm/db.rs @@ -2,7 +2,8 @@ use std::collections::HashMap; use reth_primitives::{keccak256, Address, B256}; -use revm::primitives::{AccountInfo as ReVmAccountInfo, Bytecode, U256}; +use revm::primitives::SpecId::CANCUN; +use revm::primitives::{AccountInfo as ReVmAccountInfo, Bytecode, SpecId, U256}; use revm::Database; use sov_modules_api::{StateMapAccessor, WorkingSet}; use sov_state::codec::BcsCodec; @@ -34,23 +35,29 @@ impl std::error::Error for DBError { pub(crate) struct EvmDb<'a, C: sov_modules_api::Context> { pub(crate) accounts: sov_modules_api::StateMap, - pub(crate) code: sov_modules_api::OffchainStateMap, + pub(crate) code: sov_modules_api::StateMap, + pub(crate) offchain_code: sov_modules_api::OffchainStateMap, pub(crate) last_block_hashes: sov_modules_api::StateMap, pub(crate) working_set: &'a mut WorkingSet, + pub(crate) current_spec: SpecId, } impl<'a, C: sov_modules_api::Context> EvmDb<'a, C> { pub(crate) fn new( accounts: sov_modules_api::StateMap, - code: sov_modules_api::OffchainStateMap, + code: sov_modules_api::StateMap, + offchain_code: sov_modules_api::OffchainStateMap, last_block_hashes: sov_modules_api::StateMap, working_set: &'a mut WorkingSet, + current_spec: SpecId, ) -> Self { Self { accounts, code, + offchain_code, last_block_hashes, working_set, + current_spec, } } @@ -80,6 +87,17 @@ impl<'a, C: sov_modules_api::Context> EvmDb<'a, C> { ); } } + + pub(crate) fn check_against_code_hash( + &self, + code: &Bytecode, + code_hash: &B256, + ) -> Result<(), DBError> { + if *code_hash != keccak256(code.original_bytes()) { + return Err(DBError::CodeHashMismatch); + } + Ok(()) + } } impl<'a, C: sov_modules_api::Context> Database for EvmDb<'a, C> { @@ -92,12 +110,24 @@ impl<'a, C: sov_modules_api::Context> Database for EvmDb<'a, C> { fn code_by_hash(&mut self, code_hash: B256) -> Result { // TODO move to new_raw_with_hash for better performance - let code = self - .code - .get(&code_hash, &mut self.working_set.offchain_state()); + + // If CANCUN or later forks are activated, try to fetch code from offchain storage + // first. This is to prevent slower lookups in `code`. + if self.current_spec.is_enabled_in(CANCUN) { + if let Some(code) = self + .offchain_code + .get(&code_hash, &mut self.working_set.offchain_state()) + { + self.check_against_code_hash(&code, &code_hash)?; + return Ok(code); + } + } + let code = self.code.get(&code_hash, self.working_set); if let Some(code) = code { - if code_hash != keccak256(code.original_bytes()) { - return Err(DBError::CodeHashMismatch); + // Gradually migrate contract codes into the offchain code state map. + if self.current_spec.is_enabled_in(CANCUN) { + self.offchain_code + .set(&code_hash, &code, &mut self.working_set.offchain_state()); } Ok(code) } else { diff --git a/crates/evm/src/evm/db_commit.rs b/crates/evm/src/evm/db_commit.rs index 4e417f2b1..0d6129476 100644 --- a/crates/evm/src/evm/db_commit.rs +++ b/crates/evm/src/evm/db_commit.rs @@ -1,6 +1,7 @@ use std::collections::BTreeMap; use alloy_primitives::{Address, U256}; +use revm::primitives::SpecId::CANCUN; use revm::primitives::{Account, AccountInfo, HashMap}; use revm::DatabaseCommit; use sov_modules_api::{StateMapAccessor, StateVecAccessor}; @@ -51,13 +52,22 @@ impl<'a, C: sov_modules_api::Context> DatabaseCommit for EvmDb<'a, C> { if let Some(ref code) = account_info.code { if !code.is_empty() { - let offchain_state = &mut self.working_set.offchain_state(); let exists_in_db = self .code - .get(&account_info.code_hash, offchain_state) + .get(&account_info.code_hash, self.working_set) .is_some(); + if !exists_in_db { - self.code.set(&account_info.code_hash, code, offchain_state); + if self.current_spec.is_enabled_in(CANCUN) { + self.offchain_code.set( + &account_info.code_hash, + code, + &mut self.working_set.offchain_state(), + ); + } else { + self.code + .set(&account_info.code_hash, code, self.working_set); + } } } } diff --git a/crates/evm/src/evm/db_init.rs b/crates/evm/src/evm/db_init.rs index 39d2ad20c..d39e04173 100644 --- a/crates/evm/src/evm/db_init.rs +++ b/crates/evm/src/evm/db_init.rs @@ -1,6 +1,7 @@ use reth_primitives::U256; #[cfg(test)] use revm::db::{CacheDB, EmptyDB}; +use revm::primitives::SpecId::CANCUN; use revm::primitives::{Address, Bytecode, B256}; use sov_modules_api::StateMapAccessor; @@ -20,8 +21,12 @@ impl<'a, C: sov_modules_api::Context> InitEvmDb for EvmDb<'a, C> { } fn insert_code(&mut self, code_hash: B256, code: Bytecode) { - self.code - .set(&code_hash, &code, &mut self.working_set.offchain_state()) + if self.current_spec.is_enabled_in(CANCUN) { + self.offchain_code + .set(&code_hash, &code, &mut self.working_set.offchain_state()) + } else { + self.code.set(&code_hash, &code, self.working_set) + } } fn insert_storage(&mut self, address: Address, index: U256, value: U256) { diff --git a/crates/evm/src/evm/tests.rs b/crates/evm/src/evm/tests.rs index 157722fb1..ca03b81d9 100644 --- a/crates/evm/src/evm/tests.rs +++ b/crates/evm/src/evm/tests.rs @@ -1,6 +1,7 @@ use std::str::FromStr; use reth_primitives::{Address, TxKind}; +use revm::primitives::SpecId::SHANGHAI; use revm::primitives::{CfgEnvWithHandlerCfg, ExecutionResult, Output, SpecId, U256}; use revm::{Database, DatabaseCommit}; use sov_modules_api::WorkingSet; @@ -27,7 +28,7 @@ fn simple_contract_execution_sov_state() { WorkingSet::new(new_orphan_storage(tmpdir.path()).unwrap()); let evm = Evm::::default(); - let evm_db: EvmDb<'_, C> = evm.get_db(&mut working_set); + let evm_db: EvmDb<'_, C> = evm.get_db(&mut working_set, SHANGHAI); simple_contract_execution(evm_db); } diff --git a/crates/evm/src/genesis.rs b/crates/evm/src/genesis.rs index 925038cc6..3e8cfc567 100644 --- a/crates/evm/src/genesis.rs +++ b/crates/evm/src/genesis.rs @@ -162,7 +162,29 @@ impl Evm { config: &::Config, working_set: &mut WorkingSet, ) -> Result<()> { - let mut evm_db = self.get_db(working_set); + let mut spec = config + .spec + .iter() + .map(|(k, v)| { + // https://github.com/Sovereign-Labs/sovereign-sdk/issues/912 + if *v == SpecId::CANCUN { + panic!("Cancun is not supported"); + } + + (*k, *v) + }) + .collect::>(); + + spec.sort_by(|a, b| a.0.cmp(&b.0)); + + if spec.is_empty() { + spec.push((0, SpecId::SHANGHAI)); + } else if spec[0].0 != 0u64 { + panic!("EVM spec must start from block 0"); + } + + let (_, current_spec) = spec.last().expect("Spec should be set"); + let mut evm_db = self.get_db(working_set, *current_spec); for acc in &config.data { let code = Bytecode::new_raw(acc.code.clone()); @@ -190,27 +212,6 @@ impl Evm { } } - let mut spec = config - .spec - .iter() - .map(|(k, v)| { - // https://github.com/Sovereign-Labs/sovereign-sdk/issues/912 - if *v == SpecId::CANCUN { - panic!("Cancun is not supported"); - } - - (*k, *v) - }) - .collect::>(); - - spec.sort_by(|a, b| a.0.cmp(&b.0)); - - if spec.is_empty() { - spec.push((0, SpecId::SHANGHAI)); - } else if spec[0].0 != 0u64 { - panic!("EVM spec must start from block 0"); - } - let chain_cfg = EvmChainConfig { chain_id: config.chain_id, limit_contract_code_size: config.limit_contract_code_size, diff --git a/crates/evm/src/lib.rs b/crates/evm/src/lib.rs index e9a8343db..13a591f4c 100644 --- a/crates/evm/src/lib.rs +++ b/crates/evm/src/lib.rs @@ -80,7 +80,14 @@ pub struct Evm { /// Mapping from code hash to code. Used for lazy-loading code into a contract account. #[state(rename = "c")] - pub(crate) code: sov_modules_api::OffchainStateMap< + pub(crate) code: + sov_modules_api::StateMap, + + /// Mapping from code hash to code. Used for lazy-loading code into a contract account. + /// This is the new offchain version which is not counted in the state diff. + /// Activated after FORK1 + #[state(rename = "occ")] + pub(crate) offchain_code: sov_modules_api::OffchainStateMap< reth_primitives::B256, revm::primitives::Bytecode, BcsCodec, @@ -189,12 +196,18 @@ impl sov_modules_api::Module for Evm { } impl Evm { - pub(crate) fn get_db<'a>(&self, working_set: &'a mut WorkingSet) -> EvmDb<'a, C> { + pub(crate) fn get_db<'a>( + &self, + working_set: &'a mut WorkingSet, + current_spec: SpecId, + ) -> EvmDb<'a, C> { EvmDb::new( self.accounts.clone(), self.code.clone(), + self.offchain_code.clone(), self.latest_block_hashes.clone(), working_set, + current_spec, ) } diff --git a/crates/evm/src/query.rs b/crates/evm/src/query.rs index 70b58213b..c8c0dbd1b 100644 --- a/crates/evm/src/query.rs +++ b/crates/evm/src/query.rs @@ -20,6 +20,7 @@ use reth_rpc_types::{ TransactionReceipt, }; use reth_rpc_types_compat::block::from_primitive_with_hash; +use revm::primitives::SpecId::CANCUN; use revm::primitives::{ CfgEnvWithHandlerCfg, EVMError, ExecutionResult, HaltReason, InvalidTransaction, TransactTo, }; @@ -348,13 +349,24 @@ impl Evm { block_id: Option, working_set: &mut WorkingSet, ) -> RpcResult { + let cfg = self + .cfg + .get(working_set) + .expect("EVM chain config should be set"); + // TODO: Fix this in #1436 + let (_, current_spec) = cfg.spec.last().expect("Spec should be set"); + self.set_state_to_end_of_evm_block_by_block_id(block_id, working_set)?; let account = self.accounts.get(&address, working_set).unwrap_or_default(); let code = if let Some(code_hash) = account.code_hash { - self.code - .get(&code_hash, &mut working_set.offchain_state()) - .unwrap_or_default() + if current_spec.is_enabled_in(CANCUN) { + self.offchain_code + .get(&code_hash, &mut working_set.offchain_state()) + .unwrap_or_else(|| self.code.get(&code_hash, working_set).unwrap_or_default()) + } else { + self.code.get(&code_hash, working_set).unwrap_or_default() + } } else { Default::default() }; @@ -538,7 +550,8 @@ impl Evm { (block_env, cfg_env) }; - let mut evm_db = self.get_db(working_set); + let current_spec = cfg_env.handler_cfg.spec_id; + let mut evm_db = self.get_db(working_set, current_spec); if let Some(mut block_overrides) = block_overrides { apply_block_overrides(&mut block_env, &mut block_overrides, &mut evm_db); @@ -635,7 +648,8 @@ impl Evm { // cfg_env.disable_base_fee = true; - let mut evm_db = self.get_db(working_set); + let current_spec = cfg_env.handler_cfg.spec_id; + let mut evm_db = self.get_db(working_set, current_spec); let from = request.from.unwrap_or_default(); let account = evm_db @@ -815,6 +829,8 @@ impl Evm { // cfg_env.disable_base_fee = true; + let current_spec = cfg_env.handler_cfg.spec_id; + // set nonce to None so that the correct nonce is chosen by the EVM request.nonce = None; @@ -847,7 +863,7 @@ impl Evm { tx_env.gas_limit = MIN_TRANSACTION_GAS; let res = inspect_no_tracing( - self.get_db(working_set), + self.get_db(working_set, current_spec), cfg_env.clone(), block_env, tx_env.clone(), @@ -892,7 +908,7 @@ impl Evm { // if the provided gas limit is less than computed cap, use that tx_env.gas_limit = std::cmp::min(tx_env.gas_limit, highest_gas_limit as u64); // highest_gas_limit is capped to u64::MAX - let evm_db = self.get_db(working_set); + let evm_db = self.get_db(working_set, current_spec); // execute the call without writing to db let result = inspect_no_tracing( @@ -910,7 +926,7 @@ impl Evm { // if price or limit was included in the request then we can execute the request // again with the block's gas limit to check if revert is gas related or not if request_gas_limit.is_some() || request_gas_price.is_some() { - let evm_db = self.get_db(working_set); + let evm_db = self.get_db(working_set, current_spec); return Err(map_out_of_gas_err( block_env, tx_env.clone(), @@ -934,7 +950,7 @@ impl Evm { // if price or limit was included in the request then we can execute the request // again with the block's gas limit to check if revert is gas related or not return if request_gas_limit.is_some() || request_gas_price.is_some() { - let evm_db = self.get_db(working_set); + let evm_db = self.get_db(working_set, current_spec); Err(map_out_of_gas_err( block_env, tx_env.clone(), @@ -975,7 +991,7 @@ impl Evm { tx_env.gas_limit = optimistic_gas_limit; // (result, env) = executor::transact(&mut db, env)?; let curr_result = inspect_no_tracing( - self.get_db(working_set), + self.get_db(working_set, current_spec), cfg_env.clone(), block_env, tx_env.clone(), @@ -1015,7 +1031,7 @@ impl Evm { let mut tx_env = tx_env.clone(); tx_env.gas_limit = mid_gas_limit; - let evm_db = self.get_db(working_set); + let evm_db = self.get_db(working_set, current_spec); let result = inspect_no_tracing( evm_db, cfg_env.clone(), @@ -1148,10 +1164,11 @@ impl Evm { .expect("EVM chain config should be set"); let cfg_env = get_cfg_env(&block_env, cfg); let l1_fee_rate = sealed_block.l1_fee_rate; + let current_spec = cfg_env.handler_cfg.spec_id; // EvmDB is the replacement of revm::CacheDB because cachedb requires immutable state // TODO: Move to CacheDB once immutable state is implemented - let mut evm_db = self.get_db(working_set); + let mut evm_db = self.get_db(working_set, current_spec); // TODO: Convert below steps to blocking task like in reth after implementing the semaphores let mut traces = Vec::new(); diff --git a/crates/evm/src/tests/hooks_tests.rs b/crates/evm/src/tests/hooks_tests.rs index dad506f79..bfbf68622 100644 --- a/crates/evm/src/tests/hooks_tests.rs +++ b/crates/evm/src/tests/hooks_tests.rs @@ -100,7 +100,7 @@ fn end_soft_confirmation_hook_sets_head() { Block { header: Header { parent_hash: B256::from(hex!( - "6384b7c0ffffa957bf8767b7b0dee2ed5e54fb85c8ce70a41ca8b16bd5300cd1" + "42b2df14615729c49a449b8f42c1a9eb4b9b62fb6a70464eabfa362cd1d20f75" )), ommers_hash: EMPTY_OMMER_ROOT_HASH, diff --git a/crates/evm/src/tests/utils.rs b/crates/evm/src/tests/utils.rs index 5d37d36a7..96439d871 100644 --- a/crates/evm/src/tests/utils.rs +++ b/crates/evm/src/tests/utils.rs @@ -23,10 +23,10 @@ type C = DefaultContext; lazy_static! { pub(crate) static ref GENESIS_HASH: B256 = B256::from(hex!( - "95c19b7b244abc3d6baa9a56f4b159b80477e9a882b87e7beafe79b332b0e4c4" + "600287474db03ec020caf020ad58fe9c7918bd9b078ddfdba31642daae8bdffe" )); pub(crate) static ref GENESIS_STATE_ROOT: B256 = B256::from(hex!( - "a92caa3cc93a5294538d1fa497b849a284163a4c431e3d00f5a8f6b485c811f1" + "6945eecef532f6de29cc417e3b9c2a948ab9b0af8c167392c3955a090f1cbb16" )); }