diff --git a/.github/workflows/ci_l2_prover.yaml b/.github/workflows/ci_l2_prover.yaml index 2837750ac..1e423332f 100644 --- a/.github/workflows/ci_l2_prover.yaml +++ b/.github/workflows/ci_l2_prover.yaml @@ -61,7 +61,7 @@ jobs: - name: RISC-V SP1 toolchain install run: | curl -L https://sp1.succinct.xyz | bash - ~/.sp1/bin/sp1up --version 4.0.0 + ~/.sp1/bin/sp1up --version 4.1.0 - name: Build prover and SP1's zkVM run: | cd crates/l2/prover diff --git a/Makefile b/Makefile index 32c301b26..b7d7f0489 100644 --- a/Makefile +++ b/Makefile @@ -126,6 +126,9 @@ run-hive-all: build-image setup-hive ## ๐Ÿงช Run all Hive testing suites run-hive-debug: build-image setup-hive ## ๐Ÿž Run Hive testing suite in debug mode cd hive && ./hive --sim $(SIMULATION) --client ethrex --sim.loglevel $(SIM_LOG_LEVEL) --sim.limit "$(TEST_PATTERN)" --docker.output +run-hive-debug-levm: build-image setup-hive ## ๐Ÿž Run Hive testing suite with LEVM in debug mode + cd hive && ./hive --client ethrex --ethrex.flags "--evm levm" --sim $(SIMULATION) --sim.limit "$(TEST_PATTERN)" --docker.output + clean-hive-logs: ## ๐Ÿงน Clean Hive logs rm -rf ./hive/workspace/logs diff --git a/cmd/ef_tests/state/runner/levm_runner.rs b/cmd/ef_tests/state/runner/levm_runner.rs index ec81b82b2..c2a23faa2 100644 --- a/cmd/ef_tests/state/runner/levm_runner.rs +++ b/cmd/ef_tests/state/runner/levm_runner.rs @@ -15,10 +15,7 @@ use ethrex_levm::{ Environment, }; use ethrex_storage::AccountUpdate; -use ethrex_vm::{ - backends::{self}, - db::StoreWrapper, -}; +use ethrex_vm::backends::{self}; use keccak_hash::keccak; use std::{collections::HashMap, sync::Arc}; @@ -94,11 +91,7 @@ pub fn prepare_vm_for_tx( test: &EFTest, fork: &Fork, ) -> Result { - let (initial_state, block_hash) = utils::load_initial_state(test); - let db = Arc::new(StoreWrapper { - store: initial_state.database().unwrap().clone(), - block_hash, - }); + let db = Arc::new(utils::load_initial_state_levm(test)); let tx = test .transactions @@ -315,11 +308,11 @@ pub fn ensure_post_state( } // Execution result was successful and no exception was expected. None => { - let (initial_state, block_hash) = utils::load_initial_state(test); + let store_wrapper = utils::load_initial_state_levm(test); let levm_account_updates = backends::levm::LEVM::get_state_transitions( Some(*fork), - &initial_state, - block_hash, + &store_wrapper.store, + store_wrapper.block_hash, &execution_report.new_state, ) .map_err(|_| { diff --git a/cmd/ef_tests/state/runner/revm_runner.rs b/cmd/ef_tests/state/runner/revm_runner.rs index 79906c4d7..a779d58c4 100644 --- a/cmd/ef_tests/state/runner/revm_runner.rs +++ b/cmd/ef_tests/state/runner/revm_runner.rs @@ -2,7 +2,7 @@ use crate::{ report::{ComparisonReport, EFTestReport, EFTestReportForkResult, TestReRunReport, TestVector}, runner::{levm_runner::post_state_root, EFTestRunnerError, InternalError}, types::EFTest, - utils::{effective_gas_price, load_initial_state}, + utils::{effective_gas_price, load_initial_state, load_initial_state_levm}, }; use bytes::Bytes; use ethrex_common::{ @@ -327,20 +327,17 @@ pub fn ensure_post_state( Some(_expected_exception) => {} // We only want to compare account updates when no exception is expected. None => { - let (initial_state, block_hash) = load_initial_state(test); + let store_wrapper = load_initial_state_levm(test); let levm_account_updates = backends::levm::LEVM::get_state_transitions( Some(*fork), - &initial_state, - block_hash, + &store_wrapper.store, + store_wrapper.block_hash, &levm_execution_report.new_state, ) .map_err(|_| { InternalError::Custom("Error at LEVM::get_state_transitions()".to_owned()) })?; - let revm_account_updates = backends::revm_b::REVM::get_state_transitions(revm_state) - .map_err(|_| { - InternalError::Custom("Error at REVM::get_state_transitions()".to_owned()) - })?; + let revm_account_updates = backends::revm_b::REVM::get_state_transitions(revm_state); let account_updates_report = compare_levm_revm_account_updates( vector, test, @@ -522,7 +519,7 @@ pub fn _ensure_post_state_revm( // Execution result was successful and no exception was expected. None => { let revm_account_updates = - backends::revm_b::REVM::get_state_transitions(revm_state).unwrap(); + backends::revm_b::REVM::get_state_transitions(revm_state); let pos_state_root = post_state_root(&revm_account_updates, test); let expected_post_state_root_hash = test.post.vector_post_value(vector, *fork).hash; diff --git a/cmd/ef_tests/state/utils.rs b/cmd/ef_tests/state/utils.rs index 2a1b57ce0..35f3c6806 100644 --- a/cmd/ef_tests/state/utils.rs +++ b/cmd/ef_tests/state/utils.rs @@ -4,9 +4,10 @@ use crate::{ }; use ethrex_common::{types::Genesis, H256, U256}; use ethrex_storage::{EngineType, Store}; -use ethrex_vm::db::{evm_state, EvmState}; +use ethrex_vm::db::{evm_state, EvmState, StoreWrapper}; use spinoff::Spinner; +/// Loads initial state, used for REVM as it contains EvmState. pub fn load_initial_state(test: &EFTest) -> (EvmState, H256) { let genesis = Genesis::from(test); @@ -22,6 +23,21 @@ pub fn load_initial_state(test: &EFTest) -> (EvmState, H256) { ) } +/// Loads initial state, function for LEVM as it does not require EvmState +pub fn load_initial_state_levm(test: &EFTest) -> StoreWrapper { + let genesis = Genesis::from(test); + + let storage = Store::new("./temp", EngineType::InMemory).expect("Failed to create Store"); + storage.add_initial_state(genesis.clone()).unwrap(); + + let block_hash = genesis.get_block().header.compute_block_hash(); + + StoreWrapper { + store: storage, + block_hash, + } +} + pub fn spinner_update_text_or_print(spinner: &mut Spinner, text: String, spinner_enabled: bool) { if !spinner_enabled { println!("{}", text); diff --git a/cmd/ethrex/cli.rs b/cmd/ethrex/cli.rs index 6e76f9910..220d18d1e 100644 --- a/cmd/ethrex/cli.rs +++ b/cmd/ethrex/cli.rs @@ -1,6 +1,6 @@ use clap::{Arg, ArgAction, Command}; use ethrex_p2p::types::Node; -use ethrex_vm::backends::EVM; +use ethrex_vm::backends::EvmImplementation; use tracing::Level; pub fn cli() -> Command { @@ -138,7 +138,7 @@ pub fn cli() -> Command { .required(false) .default_value("revm") .value_name("EVM_BACKEND") - .value_parser(clap::value_parser!(EVM)) + .value_parser(clap::value_parser!(EvmImplementation)) .help("Has to be `levm` or `revm`"), ) .subcommand( diff --git a/cmd/ethrex/ethrex.rs b/cmd/ethrex/ethrex.rs index 8a5966839..11170b0f2 100644 --- a/cmd/ethrex/ethrex.rs +++ b/cmd/ethrex/ethrex.rs @@ -10,7 +10,7 @@ use ethrex_p2p::{ }; use ethrex_rlp::decode::RLPDecode; use ethrex_storage::{EngineType, Store}; -use ethrex_vm::backends::EVM; +use ethrex_vm::backends::{EvmImplementation, EVM}; use k256::ecdsa::SigningKey; use local_ip_address::local_ip; use rand::rngs::OsRng; @@ -158,8 +158,6 @@ async fn main() { let sync_mode = sync_mode(&matches); - let evm = matches.get_one::("evm").unwrap_or(&EVM::REVM); - let path = path::PathBuf::from(data_dir.clone()); let store: Store = if path.ends_with("memory") { Store::new(&data_dir, EngineType::InMemory).expect("Failed to create Store") @@ -177,7 +175,12 @@ async fn main() { } Store::new(&data_dir, engine_type).expect("Failed to create Store") }; - let blockchain = Blockchain::new(evm.clone(), store.clone()); + let evm_impl = matches + .get_one::("evm") + .unwrap_or(&EvmImplementation::REVM); + let evm = EVM::new(evm_impl.clone(), store.clone()); + + let blockchain = Blockchain::new(evm, store.clone()); let genesis = read_genesis_file(&network); store diff --git a/crates/blockchain/blockchain.rs b/crates/blockchain/blockchain.rs index d697424b0..10a9fb2db 100644 --- a/crates/blockchain/blockchain.rs +++ b/crates/blockchain/blockchain.rs @@ -18,9 +18,8 @@ use std::{ops::Div, time::Instant}; use ethrex_storage::error::StoreError; use ethrex_storage::Store; -use ethrex_vm::backends::BlockExecutionResult; -use ethrex_vm::backends::EVM; -use ethrex_vm::db::evm_state; +use ethrex_vm::backends::EvmImplementation; +use ethrex_vm::backends::{BlockExecutionResult, EVM}; use fork_choice::apply_fork_choice; use tracing::{error, info, warn}; @@ -43,12 +42,17 @@ impl Blockchain { pub fn default_with_store(store: Store) -> Self { Self { - vm: Default::default(), + vm: EVM::new(EvmImplementation::REVM, store.clone()), storage: store, } } pub fn add_block(&self, block: &Block) -> Result<(), ChainError> { + info!( + "Add block: Adding block {} with hash {:#x}.", + block.header.number, + block.hash() + ); let since = Instant::now(); let block_hash = block.header.compute_block_hash(); @@ -59,8 +63,8 @@ impl Blockchain { self.storage.add_pending_block(block.clone())?; return Err(ChainError::ParentNotFound); }; - let mut state = evm_state(self.storage.clone(), block.header.parent_hash); - let chain_config = state.chain_config().map_err(ChainError::from)?; + + let chain_config = self.storage.get_chain_config()?; // Validate the block pre-execution validate_block(block, &parent_header, &chain_config)?; @@ -68,14 +72,13 @@ impl Blockchain { receipts, requests, account_updates, - } = self.vm.execute_block(block, &mut state)?; + } = self.vm.execute_block(block)?; validate_gas_used(&receipts, &block.header)?; // Apply the account updates over the last block's state and compute the new state root - let new_state_root = state - .database() - .ok_or(ChainError::StoreError(StoreError::MissingStore))? + let new_state_root = self + .storage .apply_account_updates(block.header.parent_hash, &account_updates)? .ok_or(ChainError::ParentStateNotFound)?; @@ -138,12 +141,12 @@ impl Blockchain { } if let Some(last_block) = blocks.last() { let hash = last_block.hash(); - match self.vm { - EVM::LEVM => { + match self.vm.evm_impl { + EvmImplementation::LEVM => { // We are allowing this not to unwrap so that tests can run even if block execution results in the wrong root hash with LEVM. let _ = apply_fork_choice(&self.storage, hash, hash, hash); } - EVM::REVM => { + EvmImplementation::REVM => { apply_fork_choice(&self.storage, hash, hash, hash).unwrap(); } } diff --git a/crates/blockchain/payload.rs b/crates/blockchain/payload.rs index 6eb3e9df7..3e4ba3578 100644 --- a/crates/blockchain/payload.rs +++ b/crates/blockchain/payload.rs @@ -177,7 +177,7 @@ pub fn calc_excess_blob_gas( pub struct PayloadBuildContext<'a> { pub payload: &'a mut Block, - pub evm_state: &'a mut EvmState, + pub evm_state: EvmState, pub block_cache: CacheDB, pub remaining_gas: u64, pub receipts: Vec, @@ -189,8 +189,9 @@ pub struct PayloadBuildContext<'a> { } impl<'a> PayloadBuildContext<'a> { - fn new(payload: &'a mut Block, evm_state: &'a mut EvmState) -> Result { - let config = evm_state.chain_config()?; + fn new(payload: &'a mut Block, storage: &Store) -> Result { + let config = storage.get_chain_config()?; + let evm_state = evm_state(storage.clone(), payload.header.parent_hash); let base_fee_per_blob_gas = calculate_base_fee_per_blob_gas( payload.header.excess_blob_gas.unwrap_or_default(), config @@ -243,8 +244,7 @@ impl Blockchain { let gas_limit = payload.header.gas_limit; debug!("Building payload"); - let mut evm_state = evm_state(self.storage.clone(), payload.header.parent_hash); - let mut context = PayloadBuildContext::new(payload, &mut evm_state)?; + let mut context = PayloadBuildContext::new(payload, &self.storage)?; self.apply_system_operations(&mut context)?; self.apply_withdrawals(&mut context)?; self.fill_transactions(&mut context)?; @@ -278,7 +278,7 @@ impl Blockchain { self.vm .process_withdrawals( withdrawals, - context.evm_state, + &mut context.evm_state, &context.payload.header, &mut context.block_cache, ) @@ -292,12 +292,10 @@ impl Blockchain { &self, context: &mut PayloadBuildContext, ) -> Result<(), EvmError> { - let chain_config = context.chain_config()?; self.vm.apply_system_calls( - context.evm_state, + &mut context.evm_state, &context.payload.header, &mut context.block_cache, - &chain_config, ) } @@ -506,13 +504,11 @@ impl Blockchain { head: &HeadTransaction, context: &mut PayloadBuildContext, ) -> Result { - let chain_config = context.chain_config()?; let (report, gas_used) = self.vm.execute_tx( - context.evm_state, + &mut context.evm_state, &head.tx, &context.payload.header, &mut context.block_cache, - &chain_config, &mut context.remaining_gas, )?; context.block_value += U256::from(gas_used) * head.tip; @@ -529,7 +525,7 @@ impl Blockchain { let requests = self.vm.extract_requests( &context.receipts, - context.evm_state, + &mut context.evm_state, &context.payload.header, &mut context.block_cache, ); @@ -542,9 +538,10 @@ impl Blockchain { } fn finalize_payload(&self, context: &mut PayloadBuildContext) -> Result<(), ChainError> { + let parent_hash = context.payload.header.parent_hash; let account_updates = self.vm.get_state_transitions( - context.evm_state, - context.parent_hash(), + &mut context.evm_state, + parent_hash, &context.block_cache, )?; diff --git a/crates/l2/proposer/l1_committer.rs b/crates/l2/proposer/l1_committer.rs index ba515a394..ddf3d7167 100644 --- a/crates/l2/proposer/l1_committer.rs +++ b/crates/l2/proposer/l1_committer.rs @@ -20,7 +20,7 @@ use ethrex_rpc::clients::eth::{ eth_sender::Overrides, BlockByNumber, EthClient, WrappedTransaction, }; use ethrex_storage::{error::StoreError, Store}; -use ethrex_vm::{backends::EVM, db::evm_state}; +use ethrex_vm::backends::EVM; use keccak_hash::keccak; use secp256k1::SecretKey; use std::{collections::HashMap, str::FromStr, time::Duration}; @@ -254,18 +254,14 @@ impl Committer { ) -> Result { info!("Preparing state diff for block {}", block.header.number); - let mut state = evm_state(store.clone(), block.header.parent_hash); - - let result = EVM::default() - .execute_block(block, &mut state) + let result = EVM::default_with_storage(store.clone()) + .execute_block(block) .map_err(CommitterError::from)?; let account_updates = result.account_updates; let mut modified_accounts = HashMap::new(); for account_update in &account_updates { - let prev_nonce = match state - .database() - .ok_or(CommitterError::FailedToRetrieveDataFromStorage)? + let prev_nonce = match store // If we want the state_diff of a batch, we will have to change the -1 with the `batch_size` // and we may have to keep track of the latestCommittedBlock (last block of the batch), // the batch_size and the latestCommittedBatch in the contract. diff --git a/crates/vm/backends/levm.rs b/crates/vm/backends/levm.rs index 1124a38e9..a3d0decd4 100644 --- a/crates/vm/backends/levm.rs +++ b/crates/vm/backends/levm.rs @@ -3,11 +3,8 @@ use super::constants::{ SYSTEM_ADDRESS, WITHDRAWAL_REQUEST_PREDEPLOY_ADDRESS, }; use super::BlockExecutionResult; -use crate::backends::get_state_transitions; - use crate::db::StoreWrapper; use crate::EvmError; -use crate::EvmState; use ethrex_common::types::requests::Requests; use ethrex_common::types::Fork; use ethrex_common::{ @@ -38,35 +35,33 @@ pub use ethrex_levm::db::CacheDB; pub struct LEVM; impl LEVM { - pub fn execute_block( - block: &Block, - state: &mut EvmState, - ) -> Result { + pub fn execute_block(block: &Block, store: Store) -> Result { let store_wrapper = Arc::new(StoreWrapper { - store: state.database().unwrap().clone(), + store: store.clone(), block_hash: block.header.parent_hash, }); let mut block_cache: CacheDB = HashMap::new(); let block_header = &block.header; - let config = state.chain_config()?; + let config = store.get_chain_config()?; cfg_if::cfg_if! { if #[cfg(not(feature = "l2"))] { let fork = config.fork(block_header.timestamp); if block_header.parent_beacon_block_root.is_some() && fork >= Fork::Cancun { - Self::beacon_root_contract_call(block_header, state, &mut block_cache)?; + Self::beacon_root_contract_call(block_header, &store, &mut block_cache)?; } if fork >= Fork::Prague { //eip 2935: stores parent block hash in system contract - Self::process_block_hash_history(block_header, state, &mut block_cache)?; + Self::process_block_hash_history(block_header, &store, &mut block_cache)?; } } } // Account updates are initialized like this because of the beacon_root_contract_call, it is going to be empty if it wasn't called. // Here we get the state_transitions from the db and then we get the state_transitions from the cache_db. - let mut account_updates = get_state_transitions(state); + let mut account_updates = + Self::get_state_transitions(None, &store, block.header.parent_hash, &block_cache)?; let mut receipts = Vec::new(); let mut cumulative_gas_used = 0; @@ -125,11 +120,11 @@ impl LEVM { } let requests = - extract_all_requests_levm(&receipts, state, &block.header, &mut block_cache)?; + extract_all_requests_levm(&receipts, &store, &block.header, &mut block_cache)?; account_updates.extend(Self::get_state_transitions( None, - state, + &store, block.header.parent_hash, &block_cache, )?); @@ -201,19 +196,13 @@ impl LEVM { // Warning only pass the fork if running the ef-tests. // ISSUE #2021: https://github.com/lambdaclass/ethrex/issues/2021 ef_tests: Option, - initial_state: &EvmState, + store: &Store, block_hash: H256, new_state: &CacheDB, ) -> Result, EvmError> { - let current_db = match initial_state { - EvmState::Store(state) => state.database.store.clone(), - EvmState::Execution(_cache_db) => { - unreachable!("Execution state should not be passed here") - } - }; let mut account_updates: Vec = vec![]; for (new_state_account_address, new_state_account) in new_state { - let initial_account_state = current_db + let initial_account_state = store .get_account_info_by_hash(block_hash, *new_state_account_address) .expect("Error getting account info by address") .unwrap_or_default(); @@ -231,7 +220,7 @@ impl LEVM { // Get the code hash of the new state account bytecode let potential_new_bytecode_hash = code_hash(&new_state_account.info.bytecode); // Look into the current database to see if the bytecode hash is already present - let current_bytecode = current_db + let current_bytecode = store .get_account_code(potential_new_bytecode_hash) .expect("Error getting account code by hash"); let code = new_state_account.info.bytecode.clone(); @@ -275,14 +264,14 @@ impl LEVM { added_storage, }; - let block_header = current_db + let block_header = store .get_block_header_by_hash(block_hash)? .ok_or(StoreError::MissingStore)?; - let fork_from_config = initial_state.chain_config()?.fork(block_header.timestamp); + let fork_from_config = store.get_chain_config()?.fork(block_header.timestamp); // Here we take the passed fork through the ef_tests variable, or we set it to the fork based on the timestamp. let fork = ef_tests.unwrap_or(fork_from_config); if let Some(old_info) = - current_db.get_account_info_by_hash(block_hash, account_update.address)? + store.get_account_info_by_hash(block_hash, account_update.address)? { // https://eips.ethereum.org/EIPS/eip-161 // if an account was empty and is now empty, after spurious dragon, it should be removed @@ -304,7 +293,7 @@ impl LEVM { pub fn process_withdrawals( block_cache: &mut CacheDB, withdrawals: &[Withdrawal], - store: Option<&Store>, + store: &Store, parent_hash: H256, ) -> Result<(), ethrex_storage::error::StoreError> { // For every withdrawal we increment the target account's balance @@ -316,11 +305,9 @@ impl LEVM { // We check if it was in block_cache, if not, we get it from DB. let mut account = block_cache.get(&address).cloned().unwrap_or({ let acc_info = store - .ok_or(StoreError::MissingStore)? .get_account_info_by_hash(parent_hash, address)? .unwrap_or_default(); let acc_code = store - .ok_or(StoreError::MissingStore)? .get_account_code(acc_info.code_hash)? .unwrap_or_default(); @@ -346,7 +333,7 @@ impl LEVM { /// `new_state` is being modified inside [generic_system_contract_levm]. pub fn beacon_root_contract_call( block_header: &BlockHeader, - state: &mut EvmState, + store: &Store, new_state: &mut CacheDB, ) -> Result<(), EvmError> { let beacon_root = match block_header.parent_beacon_block_root { @@ -361,7 +348,7 @@ impl LEVM { generic_system_contract_levm( block_header, Bytes::copy_from_slice(beacon_root.as_bytes()), - state, + store, new_state, *BEACON_ROOTS_ADDRESS, *SYSTEM_ADDRESS, @@ -371,13 +358,13 @@ impl LEVM { /// `new_state` is being modified inside [generic_system_contract_levm]. pub fn process_block_hash_history( block_header: &BlockHeader, - state: &mut EvmState, + store: &Store, new_state: &mut CacheDB, ) -> Result<(), EvmError> { generic_system_contract_levm( block_header, Bytes::copy_from_slice(block_header.parent_hash.as_bytes()), - state, + store, new_state, *HISTORY_STORAGE_ADDRESS, *SYSTEM_ADDRESS, @@ -386,13 +373,13 @@ impl LEVM { } pub(crate) fn read_withdrawal_requests( block_header: &BlockHeader, - state: &mut EvmState, + store: &Store, new_state: &mut CacheDB, ) -> Option { let report = generic_system_contract_levm( block_header, Bytes::new(), - state, + store, new_state, *WITHDRAWAL_REQUEST_PREDEPLOY_ADDRESS, *SYSTEM_ADDRESS, @@ -406,13 +393,13 @@ impl LEVM { } pub(crate) fn dequeue_consolidation_requests( block_header: &BlockHeader, - state: &mut EvmState, + store: &Store, new_state: &mut CacheDB, ) -> Option { let report = generic_system_contract_levm( block_header, Bytes::new(), - state, + store, new_state, *CONSOLIDATION_REQUEST_PREDEPLOY_ADDRESS, *SYSTEM_ADDRESS, @@ -430,17 +417,17 @@ impl LEVM { pub fn generic_system_contract_levm( block_header: &BlockHeader, calldata: Bytes, - state: &mut EvmState, + store: &Store, new_state: &mut CacheDB, contract_address: Address, system_address: Address, ) -> Result { let store_wrapper = Arc::new(StoreWrapper { - store: state.database().unwrap().clone(), + store: store.clone(), block_hash: block_header.parent_hash, }); - let chain_config = state.chain_config()?; + let chain_config = store.get_chain_config()?; let config = EVMConfig::new_from_chain_config(&chain_config, block_header); let env = Environment { origin: system_address, @@ -496,11 +483,11 @@ pub fn generic_system_contract_levm( #[allow(unused_variables)] pub fn extract_all_requests_levm( receipts: &[Receipt], - state: &mut EvmState, + store: &Store, header: &BlockHeader, cache: &mut CacheDB, ) -> Result, EvmError> { - let config = state.chain_config()?; + let config = store.get_chain_config()?; let fork = config.fork(header.timestamp); if fork < Fork::Prague { @@ -517,7 +504,7 @@ pub fn extract_all_requests_levm( "deposit_contract_address config is missing".to_string(), ))?; - let withdrawals_data: Vec = match LEVM::read_withdrawal_requests(header, state, cache) { + let withdrawals_data: Vec = match LEVM::read_withdrawal_requests(header, store, cache) { Some(report) => { // the cache is updated inside the generic_system_call report.output.into() @@ -526,7 +513,7 @@ pub fn extract_all_requests_levm( }; let consolidation_data: Vec = - match LEVM::dequeue_consolidation_requests(header, state, cache) { + match LEVM::dequeue_consolidation_requests(header, store, cache) { Some(report) => { // the cache is updated inside the generic_system_call report.output.into() diff --git a/crates/vm/backends/mod.rs b/crates/vm/backends/mod.rs index dff1598a4..61383418b 100644 --- a/crates/vm/backends/mod.rs +++ b/crates/vm/backends/mod.rs @@ -2,58 +2,54 @@ mod constants; pub mod levm; pub mod revm_b; +use crate::db::evm_state; use crate::{db::StoreWrapper, errors::EvmError, spec_id, EvmState, SpecId}; use ethrex_common::types::requests::Requests; -use ethrex_common::types::{ - Block, BlockHeader, ChainConfig, Fork, Receipt, Transaction, Withdrawal, -}; -use ethrex_common::{types::AccountInfo, Address, BigEndianHash, H256, U256}; +use ethrex_common::types::{Block, BlockHeader, Fork, Receipt, Transaction, Withdrawal}; +use ethrex_common::H256; use ethrex_levm::db::CacheDB; +use ethrex_storage::Store; use ethrex_storage::{error::StoreError, AccountUpdate}; use levm::LEVM; use revm_b::REVM; use std::str::FromStr; use std::sync::Arc; -use revm::db::states::bundle_state::BundleRetention; -use revm::db::{AccountState, AccountStatus}; -use revm::primitives::B256; - #[derive(Debug, Default, Clone)] -pub enum EVM { +pub enum EvmImplementation { #[default] REVM, LEVM, } -impl FromStr for EVM { - type Err = EvmError; - fn from_str(s: &str) -> Result { - match s { - "levm" => Ok(EVM::LEVM), - "revm" => Ok(EVM::REVM), - _ => Err(EvmError::InvalidEVM(s.to_string())), +#[derive(Debug, Clone)] +pub struct EVM { + pub evm_impl: EvmImplementation, + pub storage: Store, +} + +impl EVM { + pub fn new(evm: EvmImplementation, storage: Store) -> Self { + Self { + evm_impl: evm, + storage, } } -} -pub struct BlockExecutionResult { - pub receipts: Vec, - pub requests: Vec, - pub account_updates: Vec, -} + pub fn default_with_storage(storage: Store) -> Self { + Self { + evm_impl: EvmImplementation::REVM, + storage, + } + } -impl EVM { - /// Wraps [REVM::execute_block] and [LEVM::execute_block]. - /// The output is [BlockExecutionResult]. - pub fn execute_block( - &self, - block: &Block, - state: &mut EvmState, - ) -> Result { - match self { - EVM::REVM => REVM::execute_block(block, state), - EVM::LEVM => LEVM::execute_block(block, state), + pub fn execute_block(&self, block: &Block) -> Result { + match self.evm_impl { + EvmImplementation::REVM => { + let mut state = evm_state(self.storage.clone(), block.header.parent_hash); + REVM::execute_block(block, &mut state) + } + EvmImplementation::LEVM => LEVM::execute_block(block, self.storage.clone()), } } @@ -65,16 +61,17 @@ impl EVM { tx: &Transaction, block_header: &BlockHeader, block_cache: &mut CacheDB, - chain_config: &ChainConfig, remaining_gas: &mut u64, ) -> Result<(Receipt, u64), EvmError> { - match self { - EVM::REVM => { + let chain_config = self.storage.get_chain_config()?; + + match self.evm_impl { + EvmImplementation::REVM => { let execution_result = REVM::execute_tx( tx, block_header, state, - spec_id(chain_config, block_header.timestamp), + spec_id(&chain_config, block_header.timestamp), )?; *remaining_gas = remaining_gas.saturating_sub(execution_result.gas_used()); @@ -88,9 +85,9 @@ impl EVM { Ok((receipt, execution_result.gas_used())) } - EVM::LEVM => { + EvmImplementation::LEVM => { let store_wrapper = Arc::new(StoreWrapper { - store: state.database().unwrap().clone(), + store: self.storage.clone(), block_hash: block_header.parent_hash, }); @@ -99,7 +96,7 @@ impl EVM { block_header, store_wrapper.clone(), block_cache.clone(), - chain_config, + &chain_config, )?; *remaining_gas = remaining_gas.saturating_sub(execution_report.gas_used); @@ -134,11 +131,11 @@ impl EVM { state: &mut EvmState, block_header: &BlockHeader, block_cache: &mut CacheDB, - chain_config: &ChainConfig, ) -> Result<(), EvmError> { - match self { - EVM::REVM => { - let spec_id = spec_id(chain_config, block_header.timestamp); + let chain_config = self.storage.get_chain_config()?; + match self.evm_impl { + EvmImplementation::REVM => { + let spec_id = spec_id(&chain_config, block_header.timestamp); if block_header.parent_beacon_block_root.is_some() && spec_id >= SpecId::CANCUN { REVM::beacon_root_contract_call(block_header, state)?; } @@ -148,16 +145,17 @@ impl EVM { } Ok(()) } - EVM::LEVM => { + EvmImplementation::LEVM => { let fork = chain_config.fork(block_header.timestamp); let mut new_state = CacheDB::new(); + let store = self.storage.clone(); if block_header.parent_beacon_block_root.is_some() && fork >= Fork::Cancun { - LEVM::beacon_root_contract_call(block_header, state, &mut new_state)?; + LEVM::beacon_root_contract_call(block_header, &store, &mut new_state)?; } if fork >= Fork::Prague { - LEVM::process_block_hash_history(block_header, state, &mut new_state)?; + LEVM::process_block_hash_history(block_header, &store, &mut new_state)?; } // Now original_value is going to be the same as the current_value, for the next transaction. @@ -188,9 +186,11 @@ impl EVM { parent_hash: H256, block_cache: &CacheDB, ) -> Result, EvmError> { - match self { - EVM::REVM => REVM::get_state_transitions(state), - EVM::LEVM => LEVM::get_state_transitions(None, state, parent_hash, block_cache), + match self.evm_impl { + EvmImplementation::REVM => Ok(REVM::get_state_transitions(state)), + EvmImplementation::LEVM => { + LEVM::get_state_transitions(None, &self.storage, parent_hash, block_cache) + } } } @@ -203,17 +203,12 @@ impl EVM { block_header: &BlockHeader, block_cache: &mut CacheDB, ) -> Result<(), StoreError> { - match self { - EVM::REVM => REVM::process_withdrawals(state, withdrawals), - EVM::LEVM => { + match self.evm_impl { + EvmImplementation::REVM => REVM::process_withdrawals(state, withdrawals), + EvmImplementation::LEVM => { let parent_hash = block_header.parent_hash; let mut new_state = CacheDB::new(); - LEVM::process_withdrawals( - &mut new_state, - withdrawals, - state.database(), - parent_hash, - )?; + LEVM::process_withdrawals(&mut new_state, withdrawals, &self.storage, parent_hash)?; block_cache.extend(new_state); Ok(()) } @@ -227,137 +222,28 @@ impl EVM { header: &BlockHeader, cache: &mut CacheDB, ) -> Result, EvmError> { - match self { - EVM::LEVM => levm::extract_all_requests_levm(receipts, state, header, cache), - EVM::REVM => revm_b::extract_all_requests(receipts, state, header), + match self.evm_impl { + EvmImplementation::LEVM => { + levm::extract_all_requests_levm(receipts, &self.storage, header, cache) + } + EvmImplementation::REVM => revm_b::extract_all_requests(receipts, state, header), } } } -/// Gets the state_transitions == [AccountUpdate] from the [EvmState]. -/// This function is primarily used in [LEVM::execute_block] and [REVM::execute_block]. -pub fn get_state_transitions(initial_state: &mut EvmState) -> Vec { - match initial_state { - EvmState::Store(db) => { - db.merge_transitions(BundleRetention::PlainState); - let bundle = db.take_bundle(); - - // Update accounts - let mut account_updates = Vec::new(); - for (address, account) in bundle.state() { - if account.status.is_not_modified() { - continue; - } - let address = Address::from_slice(address.0.as_slice()); - // Remove account from DB if destroyed (Process DestroyedChanged as changed account) - if matches!( - account.status, - AccountStatus::Destroyed | AccountStatus::DestroyedAgain - ) { - account_updates.push(AccountUpdate::removed(address)); - continue; - } - - // If account is empty, do not add to the database - if account - .account_info() - .is_some_and(|acc_info| acc_info.is_empty()) - { - continue; - } - - // Apply account changes to DB - let mut account_update = AccountUpdate::new(address); - // If the account was changed then both original and current info will be present in the bundle account - if account.is_info_changed() { - // Update account info in DB - if let Some(new_acc_info) = account.account_info() { - let code_hash = H256::from_slice(new_acc_info.code_hash.as_slice()); - let account_info = AccountInfo { - code_hash, - balance: U256::from_little_endian(new_acc_info.balance.as_le_slice()), - nonce: new_acc_info.nonce, - }; - account_update.info = Some(account_info); - if account.is_contract_changed() { - // Update code in db - if let Some(code) = new_acc_info.code { - account_update.code = Some(code.original_bytes().clone().0); - } - } - } - } - // Update account storage in DB - for (key, slot) in account.storage.iter() { - if slot.is_changed() { - // TODO check if we need to remove the value from our db when value is zero - // if slot.present_value().is_zero() { - // account_update.removed_keys.push(H256::from_uint(&U256::from_little_endian(key.as_le_slice()))) - // } - account_update.added_storage.insert( - H256::from_uint(&U256::from_little_endian(key.as_le_slice())), - U256::from_little_endian(slot.present_value().as_le_slice()), - ); - } - } - account_updates.push(account_update) - } - account_updates - } - EvmState::Execution(db) => { - // Update accounts - let mut account_updates = Vec::new(); - for (revm_address, account) in &db.accounts { - if account.account_state == AccountState::None { - // EVM didn't interact with this account - continue; - } - - let address = Address::from_slice(revm_address.0.as_slice()); - // Remove account from DB if destroyed - if account.account_state == AccountState::NotExisting { - account_updates.push(AccountUpdate::removed(address)); - continue; - } - - // If account is empty, do not add to the database - if account.info().is_some_and(|acc_info| acc_info.is_empty()) { - continue; - } - - // Apply account changes to DB - let mut account_update = AccountUpdate::new(address); - // Update account info in DB - if let Some(new_acc_info) = account.info() { - // If code changed, update - if matches!(db.db.accounts.get(&address), Some(account) if B256::from(account.code_hash.0) != new_acc_info.code_hash) - { - account_update.code = new_acc_info - .code - .map(|code| bytes::Bytes::copy_from_slice(code.bytes_slice())); - } - - let account_info = AccountInfo { - code_hash: H256::from_slice(new_acc_info.code_hash.as_slice()), - balance: U256::from_little_endian(new_acc_info.balance.as_le_slice()), - nonce: new_acc_info.nonce, - }; - account_update.info = Some(account_info); - } - // Update account storage in DB - for (key, slot) in account.storage.iter() { - // TODO check if we need to remove the value from our db when value is zero - // if slot.present_value().is_zero() { - // account_update.removed_keys.push(H256::from_uint(&U256::from_little_endian(key.as_le_slice()))) - // } - account_update.added_storage.insert( - H256::from_uint(&U256::from_little_endian(key.as_le_slice())), - U256::from_little_endian(slot.as_le_slice()), - ); - } - account_updates.push(account_update) - } - account_updates +impl FromStr for EvmImplementation { + type Err = EvmError; + fn from_str(s: &str) -> Result { + match s { + "levm" => Ok(EvmImplementation::LEVM), + "revm" => Ok(EvmImplementation::REVM), + _ => Err(EvmError::InvalidEVM(s.to_string())), } } } + +pub struct BlockExecutionResult { + pub receipts: Vec, + pub requests: Vec, + pub account_updates: Vec, +} diff --git a/crates/vm/backends/revm_b.rs b/crates/vm/backends/revm_b.rs index 5f05d9e43..f60354d13 100644 --- a/crates/vm/backends/revm_b.rs +++ b/crates/vm/backends/revm_b.rs @@ -3,14 +3,16 @@ use super::constants::{ SYSTEM_ADDRESS, WITHDRAWAL_REQUEST_PREDEPLOY_ADDRESS, }; use super::BlockExecutionResult; -use crate::backends::get_state_transitions; - use crate::spec_id; use crate::EvmError; use crate::EvmState; use crate::ExecutionResult; +use ethrex_common::types::AccountInfo; +use ethrex_common::{BigEndianHash, H256, U256}; use ethrex_storage::{error::StoreError, AccountUpdate}; +use revm::db::states::bundle_state::BundleRetention; +use revm::db::AccountStatus; use revm::{ db::AccountState as RevmAccountState, inspectors::TracerEip3155, @@ -91,7 +93,7 @@ impl REVM { } } - let account_updates = get_state_transitions(state); + let account_updates = Self::get_state_transitions(state); Ok(BlockExecutionResult { receipts, @@ -111,12 +113,6 @@ impl REVM { run_evm(tx_env, block_env, state, spec_id) } - pub fn get_state_transitions( - initial_state: &mut EvmState, - ) -> Result, EvmError> { - Ok(get_state_transitions(initial_state)) - } - pub fn process_withdrawals( initial_state: &mut EvmState, withdrawals: &[Withdrawal], @@ -230,6 +226,137 @@ impl REVM { None } } + + /// Gets the state_transitions == [AccountUpdate] from the [EvmState]. + pub fn get_state_transitions( + initial_state: &mut EvmState, + ) -> Vec { + match initial_state { + EvmState::Store(db) => { + db.merge_transitions(BundleRetention::PlainState); + let bundle = db.take_bundle(); + + // Update accounts + let mut account_updates = Vec::new(); + for (address, account) in bundle.state() { + if account.status.is_not_modified() { + continue; + } + let address = Address::from_slice(address.0.as_slice()); + // Remove account from DB if destroyed (Process DestroyedChanged as changed account) + if matches!( + account.status, + AccountStatus::Destroyed | AccountStatus::DestroyedAgain + ) { + account_updates.push(AccountUpdate::removed(address)); + continue; + } + + // If account is empty, do not add to the database + if account + .account_info() + .is_some_and(|acc_info| acc_info.is_empty()) + { + continue; + } + + // Apply account changes to DB + let mut account_update = AccountUpdate::new(address); + // If the account was changed then both original and current info will be present in the bundle account + if account.is_info_changed() { + // Update account info in DB + if let Some(new_acc_info) = account.account_info() { + let code_hash = H256::from_slice(new_acc_info.code_hash.as_slice()); + let account_info = AccountInfo { + code_hash, + balance: U256::from_little_endian( + new_acc_info.balance.as_le_slice(), + ), + nonce: new_acc_info.nonce, + }; + account_update.info = Some(account_info); + if account.is_contract_changed() { + // Update code in db + if let Some(code) = new_acc_info.code { + account_update.code = Some(code.original_bytes().clone().0); + } + } + } + } + // Update account storage in DB + for (key, slot) in account.storage.iter() { + if slot.is_changed() { + // TODO check if we need to remove the value from our db when value is zero + // if slot.present_value().is_zero() { + // account_update.removed_keys.push(H256::from_uint(&U256::from_little_endian(key.as_le_slice()))) + // } + account_update.added_storage.insert( + H256::from_uint(&U256::from_little_endian(key.as_le_slice())), + U256::from_little_endian(slot.present_value().as_le_slice()), + ); + } + } + account_updates.push(account_update) + } + account_updates + } + EvmState::Execution(db) => { + // Update accounts + let mut account_updates = Vec::new(); + for (revm_address, account) in &db.accounts { + if account.account_state == RevmAccountState::None { + // EVM didn't interact with this account + continue; + } + + let address = Address::from_slice(revm_address.0.as_slice()); + // Remove account from DB if destroyed + if account.account_state == RevmAccountState::NotExisting { + account_updates.push(AccountUpdate::removed(address)); + continue; + } + + // If account is empty, do not add to the database + if account.info().is_some_and(|acc_info| acc_info.is_empty()) { + continue; + } + + // Apply account changes to DB + let mut account_update = AccountUpdate::new(address); + // Update account info in DB + if let Some(new_acc_info) = account.info() { + // If code changed, update + if matches!(db.db.accounts.get(&address), Some(account) if B256::from(account.code_hash.0) != new_acc_info.code_hash) + { + account_update.code = new_acc_info + .code + .map(|code| bytes::Bytes::copy_from_slice(code.bytes_slice())); + } + + let account_info = AccountInfo { + code_hash: H256::from_slice(new_acc_info.code_hash.as_slice()), + balance: U256::from_little_endian(new_acc_info.balance.as_le_slice()), + nonce: new_acc_info.nonce, + }; + account_update.info = Some(account_info); + } + // Update account storage in DB + for (key, slot) in account.storage.iter() { + // TODO check if we need to remove the value from our db when value is zero + // if slot.present_value().is_zero() { + // account_update.removed_keys.push(H256::from_uint(&U256::from_little_endian(key.as_le_slice()))) + // } + account_update.added_storage.insert( + H256::from_uint(&U256::from_little_endian(key.as_le_slice())), + U256::from_little_endian(slot.as_le_slice()), + ); + } + account_updates.push(account_update) + } + account_updates + } + } + } } /// Runs the transaction and returns the result, but does not commit it.