From ae40c8ce3b4d00b390893df8d6948771aa387580 Mon Sep 17 00:00:00 2001 From: fborello-lambda Date: Fri, 31 Jan 2025 10:59:52 -0300 Subject: [PATCH 01/26] feat: add evm cli arg --- cmd/ethrex/cli.rs | 10 ++++++++++ cmd/ethrex/ethrex.rs | 3 +++ crates/vm/errors.rs | 6 ++++-- crates/vm/vm.rs | 20 +++++++++++++++++++- 4 files changed, 36 insertions(+), 3 deletions(-) diff --git a/cmd/ethrex/cli.rs b/cmd/ethrex/cli.rs index c71be7395f..6effce9fe1 100644 --- a/cmd/ethrex/cli.rs +++ b/cmd/ethrex/cli.rs @@ -1,5 +1,6 @@ use clap::{Arg, ArgAction, Command}; use ethrex_net::bootnode::BootNode; +use ethrex_vm::EVM; use tracing::Level; pub fn cli() -> Command { @@ -122,6 +123,15 @@ pub fn cli() -> Command { .required(false) .value_name("PROMETHEUS_METRICS_PORT"), ) + .arg( + Arg::new("evm") + .long("evm") + .required(false) + .default_value("revm") + .value_name("EVM_BACKEND") + .value_parser(clap::value_parser!(EVM)) + .help("Has to be `levm` or `revm`"), + ) .subcommand( Command::new("removedb").about("Remove the database").arg( Arg::new("datadir") diff --git a/cmd/ethrex/ethrex.rs b/cmd/ethrex/ethrex.rs index e2482ca3f3..ee9b7ce53d 100644 --- a/cmd/ethrex/ethrex.rs +++ b/cmd/ethrex/ethrex.rs @@ -10,6 +10,7 @@ use ethrex_net::{ }; use ethrex_rlp::decode::RLPDecode; use ethrex_storage::{EngineType, Store}; +use ethrex_vm::EVM; use k256::ecdsa::SigningKey; use local_ip_address::local_ip; use rand::rngs::OsRng; @@ -134,6 +135,8 @@ async fn main() { let sync_mode = sync_mode(&matches); + let evm = matches.get_one::("evm").unwrap_or(&EVM::REVM); + cfg_if::cfg_if! { if #[cfg(feature = "redb")] { let store = Store::new(&data_dir, EngineType::RedB).expect("Failed to create Store"); diff --git a/crates/vm/errors.rs b/crates/vm/errors.rs index a80ae2a787..907193df0a 100644 --- a/crates/vm/errors.rs +++ b/crates/vm/errors.rs @@ -18,9 +18,11 @@ pub enum EvmError { #[error("Execution DB error: {0}")] ExecutionDB(#[from] ExecutionDBError), #[error("{0}")] - Custom(String), - #[error("{0}")] Precompile(String), + #[error("Invalid EVM or EVM not supported: {0}")] + InvalidEVM(String), + #[error("{0}")] + Custom(String), } #[derive(Debug, Error)] diff --git a/crates/vm/vm.rs b/crates/vm/vm.rs index a5490da971..acf5ece007 100644 --- a/crates/vm/vm.rs +++ b/crates/vm/vm.rs @@ -7,7 +7,7 @@ mod mods; use db::StoreWrapper; use execution_db::ExecutionDB; -use std::cmp::min; +use std::{cmp::min, str::FromStr}; use ethrex_core::{ types::{ @@ -37,6 +37,24 @@ pub use errors::EvmError; pub use execution_result::*; pub use revm::primitives::{Address as RevmAddress, SpecId, U256 as RevmU256}; +#[derive(Debug, Clone)] +pub enum EVM { + LEVM, + REVM, +} + +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())), + } + } +} + +// TODO use the type inside ethrex_core type AccessList = Vec<(Address, Vec)>; pub const WITHDRAWAL_MAGIC_DATA: &[u8] = b"burn"; From c236a95a991bffef74f07dbdb3dc8db6159f1561 Mon Sep 17 00:00:00 2001 From: fborello-lambda Date: Fri, 31 Jan 2025 17:42:26 -0300 Subject: [PATCH 02/26] refactor: separate vm.rs into 2 different modules levm and revm modules --- cmd/ethrex/cli.rs | 2 +- cmd/ethrex/ethrex.rs | 4 +- crates/blockchain/blockchain.rs | 11 +- crates/blockchain/payload.rs | 11 +- crates/networking/rpc/eth/transaction.rs | 2 +- crates/vm/Cargo.toml | 4 +- crates/vm/db.rs | 142 ++-- crates/vm/errors.rs | 22 +- crates/vm/evm_backends/levm.rs | 285 +++++++++ crates/vm/evm_backends/mod.rs | 22 + crates/vm/evm_backends/revm.rs | 457 +++++++++++++ crates/vm/execution_db.rs | 6 +- crates/vm/vm.rs | 782 +---------------------- 13 files changed, 906 insertions(+), 844 deletions(-) create mode 100644 crates/vm/evm_backends/levm.rs create mode 100644 crates/vm/evm_backends/mod.rs create mode 100644 crates/vm/evm_backends/revm.rs diff --git a/cmd/ethrex/cli.rs b/cmd/ethrex/cli.rs index 6effce9fe1..bd9e35bd3b 100644 --- a/cmd/ethrex/cli.rs +++ b/cmd/ethrex/cli.rs @@ -1,6 +1,6 @@ use clap::{Arg, ArgAction, Command}; use ethrex_net::bootnode::BootNode; -use ethrex_vm::EVM; +use ethrex_vm::evm_backends::EVM; use tracing::Level; pub fn cli() -> Command { diff --git a/cmd/ethrex/ethrex.rs b/cmd/ethrex/ethrex.rs index ee9b7ce53d..1d5e21501b 100644 --- a/cmd/ethrex/ethrex.rs +++ b/cmd/ethrex/ethrex.rs @@ -10,7 +10,7 @@ use ethrex_net::{ }; use ethrex_rlp::decode::RLPDecode; use ethrex_storage::{EngineType, Store}; -use ethrex_vm::EVM; +use ethrex_vm::evm_backends::EVM; use k256::ecdsa::SigningKey; use local_ip_address::local_ip; use rand::rngs::OsRng; @@ -135,7 +135,7 @@ async fn main() { let sync_mode = sync_mode(&matches); - let evm = matches.get_one::("evm").unwrap_or(&EVM::REVM); + let _evm = matches.get_one::("evm").unwrap_or(&EVM::REVM); cfg_if::cfg_if! { if #[cfg(feature = "redb")] { diff --git a/crates/blockchain/blockchain.rs b/crates/blockchain/blockchain.rs index 509f6dd76b..f3e6daead8 100644 --- a/crates/blockchain/blockchain.rs +++ b/crates/blockchain/blockchain.rs @@ -16,7 +16,12 @@ use ethrex_core::H256; use ethrex_storage::error::StoreError; use ethrex_storage::{AccountUpdate, Store}; -use ethrex_vm::{evm_state, execute_block, EvmState}; +#[cfg(feature = "levm")] +use ethrex_vm::evm_backends::levm; +use ethrex_vm::{ + db::{evm_state, EvmState}, + evm_backends::revm, +}; //TODO: Implement a struct Chain or BlockChain to encapsulate //functionality and canonical chain state and config @@ -43,11 +48,11 @@ pub fn add_block(block: &Block, storage: &Store) -> Result<(), ChainError> { // TODO: Consider refactoring both implementations so that they have the same signature #[cfg(feature = "levm")] { - execute_block(block, &mut state)? + levm::execute_block(block, &mut state)? } #[cfg(not(feature = "levm"))] { - let receipts = execute_block(block, &mut state)?; + let receipts = revm::execute_block(block, &mut state)?; let account_updates = ethrex_vm::get_state_transitions(&mut state); (receipts, account_updates) } diff --git a/crates/blockchain/payload.rs b/crates/blockchain/payload.rs index 41cde782b7..34df0ac793 100644 --- a/crates/blockchain/payload.rs +++ b/crates/blockchain/payload.rs @@ -16,8 +16,9 @@ use ethrex_core::{ use ethrex_rlp::encode::RLPEncode; use ethrex_storage::{error::StoreError, Store}; use ethrex_vm::{ - beacon_root_contract_call, evm_state, execute_tx, get_state_transitions, process_withdrawals, - spec_id, EvmError, EvmState, SpecId, + db::{evm_state, EvmState}, + evm_backends::revm, + get_state_transitions, spec_id, EvmError, SpecId, }; use sha3::{Digest, Keccak256}; @@ -238,10 +239,10 @@ pub fn apply_withdrawals(context: &mut PayloadBuildContext) -> Result<(), EvmErr // Apply withdrawals & call beacon root contract, and obtain the new state root let spec_id = spec_id(&context.chain_config()?, context.payload.header.timestamp); if context.payload.header.parent_beacon_block_root.is_some() && spec_id == SpecId::CANCUN { - beacon_root_contract_call(context.evm_state, &context.payload.header, spec_id)?; + revm::beacon_root_contract_call(context.evm_state, &context.payload.header, spec_id)?; } let withdrawals = context.payload.body.withdrawals.clone().unwrap_or_default(); - process_withdrawals(context.evm_state, &withdrawals)?; + revm::process_withdrawals(context.evm_state, &withdrawals)?; Ok(()) } @@ -445,7 +446,7 @@ fn apply_plain_transaction( head: &HeadTransaction, context: &mut PayloadBuildContext, ) -> Result { - let result = execute_tx( + let result = revm::execute_tx( &head.tx, &context.payload.header, context.evm_state, diff --git a/crates/networking/rpc/eth/transaction.rs b/crates/networking/rpc/eth/transaction.rs index 92593b6cba..e098bce6ff 100644 --- a/crates/networking/rpc/eth/transaction.rs +++ b/crates/networking/rpc/eth/transaction.rs @@ -16,7 +16,7 @@ use ethrex_blockchain::mempool; use ethrex_rlp::encode::RLPEncode; use ethrex_storage::Store; -use ethrex_vm::{evm_state, ExecutionResult, SpecId}; +use ethrex_vm::{db::evm_state, ExecutionResult, SpecId}; use serde::Serialize; use serde_json::Value; diff --git a/crates/vm/Cargo.toml b/crates/vm/Cargo.toml index b183689baf..6cb6792001 100644 --- a/crates/vm/Cargo.toml +++ b/crates/vm/Cargo.toml @@ -6,7 +6,7 @@ edition = "2021" [dependencies] ethrex-core = { path = "../common", default-features = false } ethrex-storage = { path = "../storage/store", default-features = false } -ethrex-levm = { path = "./levm", optional = true } +ethrex-levm = { path = "./levm" } ethrex-trie = { path = "../storage/trie", default-features = false } ethrex-rlp = { path = "../common/rlp", default-features = false } revm = { version = "14.0.3", features = [ @@ -39,7 +39,7 @@ path = "./vm.rs" [features] default = ["c-kzg", "blst"] -levm = ["default", "ethrex-levm"] +levm = ["default"] l2 = [] c-kzg = ["revm/c-kzg"] blst = ["revm/blst"] diff --git a/crates/vm/db.rs b/crates/vm/db.rs index 5528ee6442..0110f9bf37 100644 --- a/crates/vm/db.rs +++ b/crates/vm/db.rs @@ -1,4 +1,8 @@ -use ethrex_core::{types::BlockHash, Address as CoreAddress, H256 as CoreH256}; +use crate::{execution_db::ExecutionDB, EvmError}; +use ethrex_core::{ + types::{BlockHash, ChainConfig}, + Address as CoreAddress, H256 as CoreH256, +}; use ethrex_storage::{error::StoreError, Store}; use revm::primitives::{ AccountInfo as RevmAccountInfo, Address as RevmAddress, Bytecode as RevmBytecode, @@ -10,57 +14,99 @@ pub struct StoreWrapper { pub block_hash: BlockHash, } -cfg_if::cfg_if! { - if #[cfg(feature = "levm")] { - use ethrex_core::{U256 as CoreU256}; - use ethrex_levm::db::Database as LevmDatabase; - - impl LevmDatabase for StoreWrapper { - fn get_account_info(&self, address: CoreAddress) -> ethrex_levm::account::AccountInfo { - let acc_info = self - .store - .get_account_info_by_hash(self.block_hash, address) - .unwrap() - .unwrap_or_default(); - - let acc_code = self - .store - .get_account_code(acc_info.code_hash) - .unwrap() - .unwrap_or_default(); - - ethrex_levm::account::AccountInfo { - balance: acc_info.balance, - nonce: acc_info.nonce, - bytecode: acc_code, - } - } - - fn account_exists(&self, address: CoreAddress) -> bool { - let acc_info = self - .store - .get_account_info_by_hash(self.block_hash, address) - .unwrap(); - - acc_info.is_some() - } - - fn get_storage_slot(&self, address: CoreAddress, key: CoreH256) -> CoreU256 { - self.store - .get_storage_at_hash(self.block_hash, address, key) - .unwrap() - .unwrap_or_default() - } - - fn get_block_hash(&self, block_number: u64) -> Option { - let a = self.store.get_block_header(block_number).unwrap(); - - a.map(|a| CoreH256::from(a.compute_block_hash().0)) - } +/// State used when running the EVM. The state can be represented with a [StoreWrapper] database, or +/// with a [ExecutionDB] in case we only want to store the necessary data for some particular +/// execution, for example when proving in L2 mode. +/// +/// Encapsulates state behaviour to be agnostic to the evm implementation for crate users. +pub enum EvmState { + Store(revm::db::State), + Execution(Box>), +} + +impl EvmState { + /// Get a reference to inner `Store` database + pub fn database(&self) -> Option<&Store> { + if let EvmState::Store(db) = self { + Some(&db.database.store) + } else { + None + } + } + + /// Gets the stored chain config + pub fn chain_config(&self) -> Result { + match self { + EvmState::Store(db) => db.database.store.get_chain_config().map_err(EvmError::from), + EvmState::Execution(db) => Ok(db.db.get_chain_config()), } } } +/// Builds EvmState from a Store +pub fn evm_state(store: Store, block_hash: BlockHash) -> EvmState { + EvmState::Store( + revm::db::State::builder() + .with_database(StoreWrapper { store, block_hash }) + .with_bundle_update() + .without_state_clear() + .build(), + ) +} + +impl From for EvmState { + fn from(value: ExecutionDB) -> Self { + EvmState::Execution(Box::new(revm::db::CacheDB::new(value))) + } +} + +use ethrex_core::U256 as CoreU256; +use ethrex_levm::db::Database as LevmDatabase; + +impl LevmDatabase for StoreWrapper { + fn get_account_info(&self, address: CoreAddress) -> ethrex_levm::account::AccountInfo { + let acc_info = self + .store + .get_account_info_by_hash(self.block_hash, address) + .unwrap() + .unwrap_or_default(); + + let acc_code = self + .store + .get_account_code(acc_info.code_hash) + .unwrap() + .unwrap_or_default(); + + ethrex_levm::account::AccountInfo { + balance: acc_info.balance, + nonce: acc_info.nonce, + bytecode: acc_code, + } + } + + fn account_exists(&self, address: CoreAddress) -> bool { + let acc_info = self + .store + .get_account_info_by_hash(self.block_hash, address) + .unwrap(); + + acc_info.is_some() + } + + fn get_storage_slot(&self, address: CoreAddress, key: CoreH256) -> CoreU256 { + self.store + .get_storage_at_hash(self.block_hash, address, key) + .unwrap() + .unwrap_or_default() + } + + fn get_block_hash(&self, block_number: u64) -> Option { + let a = self.store.get_block_header(block_number).unwrap(); + + a.map(|a| CoreH256::from(a.compute_block_hash().0)) + } +} + impl revm::Database for StoreWrapper { type Error = StoreError; diff --git a/crates/vm/errors.rs b/crates/vm/errors.rs index 907193df0a..b440045fa8 100644 --- a/crates/vm/errors.rs +++ b/crates/vm/errors.rs @@ -1,5 +1,6 @@ use ethereum_types::{H160, H256}; use ethrex_core::{types::BlockHash, Address}; +use ethrex_levm::errors::VMError; use ethrex_storage::error::StoreError; use ethrex_trie::TrieError; use revm::primitives::{ @@ -111,19 +112,14 @@ impl From> for EvmError { } } -cfg_if::cfg_if! { - if #[cfg(feature = "levm")] { - use ethrex_levm::errors::VMError; - impl From for EvmError { - fn from(value: VMError) -> Self { - if value.is_internal() { - // We don't categorize our internal errors yet, so we label them as "Custom" - EvmError::Custom(value.to_string()) - } else { - // If an error is not internal it means it is a transaction validation error. - EvmError::Transaction(value.to_string()) - } - } +impl From for EvmError { + fn from(value: VMError) -> Self { + if value.is_internal() { + // We don't categorize our internal errors yet, so we label them as "Custom" + EvmError::Custom(value.to_string()) + } else { + // If an error is not internal it means it is a transaction validation error. + EvmError::Transaction(value.to_string()) } } } diff --git a/crates/vm/evm_backends/levm.rs b/crates/vm/evm_backends/levm.rs new file mode 100644 index 0000000000..c6d21494da --- /dev/null +++ b/crates/vm/evm_backends/levm.rs @@ -0,0 +1,285 @@ +use crate::db::StoreWrapper; +use crate::EvmError; +use crate::EvmState; +use ethrex_core::{ + types::{ + code_hash, AccountInfo, Block, BlockHeader, Fork, Receipt, Transaction, TxKind, + GWEI_TO_WEI, + }, + Address, H256, U256, +}; +use ethrex_levm::{ + db::{CacheDB, Database as LevmDatabase}, + errors::{TransactionReport, TxResult, VMError}, + vm::VM, + Account, Environment, +}; +use ethrex_storage::AccountUpdate; +use lazy_static::lazy_static; +use revm_primitives::Bytes; +use std::{collections::HashMap, sync::Arc}; + +/// Executes all transactions in a block and returns their receipts. +pub fn execute_block( + block: &Block, + state: &mut EvmState, +) -> Result<(Vec, Vec), EvmError> { + let store_wrapper = Arc::new(StoreWrapper { + store: state.database().unwrap().clone(), + block_hash: block.header.parent_hash, + }); + let mut block_cache: CacheDB = HashMap::new(); + let block_header = &block.header; + let fork = state.chain_config()?.fork(block_header.timestamp); + //eip 4788: execute beacon_root_contract_call before block transactions + cfg_if::cfg_if! { + if #[cfg(not(feature = "l2"))] { + if block_header.parent_beacon_block_root.is_some() && fork == Fork::Cancun { + let report = beacon_root_contract_call_levm(store_wrapper.clone(), block_header, fork)?; + block_cache.extend(report.new_state); + } + } + } + + // Account updates are initialized like this because of the beacon_root_contract_call, it is going to be empty if it wasn't called. + let mut account_updates = + get_state_transitions_levm(state, block.header.parent_hash, &block_cache); + + let mut receipts = Vec::new(); + let mut cumulative_gas_used = 0; + + for tx in block.body.transactions.iter() { + let report = execute_tx_levm( + tx, + block_header, + store_wrapper.clone(), + block_cache.clone(), + fork, + ) + .map_err(EvmError::from)?; + + let mut new_state = report.new_state.clone(); + + // Now original_value is going to be the same as the current_value, for the next transaction. + // It should have only one value but it is convenient to keep on using our CacheDB structure + for account in new_state.values_mut() { + for storage_slot in account.storage.values_mut() { + storage_slot.original_value = storage_slot.current_value; + } + } + + block_cache.extend(new_state); + + // Currently, in LEVM, we don't substract refunded gas to used gas, but that can change in the future. + let gas_used = report.gas_used - report.gas_refunded; + cumulative_gas_used += gas_used; + let receipt = Receipt::new( + tx.tx_type(), + matches!(report.result.clone(), TxResult::Success), + cumulative_gas_used, + report.logs.clone(), + ); + + receipts.push(receipt); + } + + // Here we update block_cache with balance increments caused by withdrawals. + if let Some(withdrawals) = &block.body.withdrawals { + // For every withdrawal we increment the target account's balance + for (address, increment) in withdrawals + .iter() + .filter(|withdrawal| withdrawal.amount > 0) + .map(|w| (w.address, u128::from(w.amount) * u128::from(GWEI_TO_WEI))) + { + // 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_wrapper.get_account_info(address); + Account::from(acc_info) + }); + + account.info.balance += increment.into(); + + block_cache.insert(address, account); + } + } + + account_updates.extend(get_state_transitions_levm( + state, + block.header.parent_hash, + &block_cache, + )); + + Ok((receipts, account_updates)) +} + +fn execute_tx_levm( + tx: &Transaction, + block_header: &BlockHeader, + db: Arc, + block_cache: CacheDB, + fork: Fork, +) -> Result { + let gas_price: U256 = tx + .effective_gas_price(block_header.base_fee_per_gas) + .ok_or(VMError::InvalidTransaction)? + .into(); + + let env = Environment { + origin: tx.sender(), + refunded_gas: 0, + gas_limit: tx.gas_limit(), + fork, + block_number: block_header.number.into(), + coinbase: block_header.coinbase, + timestamp: block_header.timestamp.into(), + prev_randao: Some(block_header.prev_randao), + chain_id: tx.chain_id().unwrap_or_default().into(), + base_fee_per_gas: block_header.base_fee_per_gas.unwrap_or_default().into(), + gas_price, + block_excess_blob_gas: block_header.excess_blob_gas.map(U256::from), + block_blob_gas_used: block_header.blob_gas_used.map(U256::from), + tx_blob_hashes: tx.blob_versioned_hashes(), + tx_max_priority_fee_per_gas: tx.max_priority_fee().map(U256::from), + tx_max_fee_per_gas: tx.max_fee_per_gas().map(U256::from), + tx_max_fee_per_blob_gas: tx.max_fee_per_blob_gas().map(U256::from), + block_gas_limit: block_header.gas_limit, + transient_storage: HashMap::new(), + }; + + let mut vm = VM::new( + tx.to(), + env, + tx.value(), + tx.data().clone(), + db, + block_cache, + tx.access_list(), + // TODO: Here we should pass the tx.authorization_list + // We have to implement the EIP7702 tx in ethrex_core + None, + )?; + + vm.transact() +} + +fn get_state_transitions_levm( + initial_state: &EvmState, + block_hash: H256, + new_state: &CacheDB, +) -> Vec { + 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 { + // This stores things that have changed in the account. + let mut account_update = AccountUpdate::new(*new_state_account_address); + + // Account state before block execution. + let initial_account_state = current_db + .get_account_info_by_hash(block_hash, *new_state_account_address) + .expect("Error getting account info by address") + .unwrap_or_default(); + // Account state after block execution. + let new_state_acc_info = AccountInfo { + code_hash: code_hash(&new_state_account.info.bytecode), + balance: new_state_account.info.balance, + nonce: new_state_account.info.nonce, + }; + + // Compare Account Info + if initial_account_state != new_state_acc_info { + account_update.info = Some(new_state_acc_info.clone()); + } + + // If code hash is different it means the code is different too. + if initial_account_state.code_hash != new_state_acc_info.code_hash { + account_update.code = Some(new_state_account.info.bytecode.clone()); + } + + let mut updated_storage = HashMap::new(); + for (key, storage_slot) in &new_state_account.storage { + // original_value in storage_slot is not the original_value on the DB, be careful. + let original_value = current_db + .get_storage_at_hash(block_hash, *new_state_account_address, *key) + .unwrap() + .unwrap_or_default(); // Option inside result, I guess I have to assume it is zero. + + if original_value != storage_slot.current_value { + updated_storage.insert(*key, storage_slot.current_value); + } + } + account_update.added_storage = updated_storage; + + account_update.removed = new_state_account.is_empty(); + + if account_update != AccountUpdate::new(*new_state_account_address) { + account_updates.push(account_update); + } + } + account_updates +} + +/// Calls the eip4788 beacon block root system call contract +/// More info on https://eips.ethereum.org/EIPS/eip-4788 +fn beacon_root_contract_call_levm( + store_wrapper: Arc, + block_header: &BlockHeader, + fork: Fork, +) -> Result { + lazy_static! { + static ref SYSTEM_ADDRESS: Address = + Address::from_slice(&hex::decode("fffffffffffffffffffffffffffffffffffffffe").unwrap()); + static ref CONTRACT_ADDRESS: Address = + Address::from_slice(&hex::decode("000F3df6D732807Ef1319fB7B8bB8522d0Beac02").unwrap(),); + }; + // This is OK + let beacon_root = match block_header.parent_beacon_block_root { + None => { + return Err(EvmError::Header( + "parent_beacon_block_root field is missing".to_string(), + )) + } + Some(beacon_root) => beacon_root, + }; + + let env = Environment { + origin: *SYSTEM_ADDRESS, + gas_limit: 30_000_000, + block_number: block_header.number.into(), + coinbase: block_header.coinbase, + timestamp: block_header.timestamp.into(), + prev_randao: Some(block_header.prev_randao), + base_fee_per_gas: U256::zero(), + gas_price: U256::zero(), + block_excess_blob_gas: block_header.excess_blob_gas.map(U256::from), + block_blob_gas_used: block_header.blob_gas_used.map(U256::from), + block_gas_limit: 30_000_000, + transient_storage: HashMap::new(), + fork, + ..Default::default() + }; + + let calldata = Bytes::copy_from_slice(beacon_root.as_bytes()).into(); + + // Here execute with LEVM but just return transaction report. And I will handle it in the calling place. + + let mut vm = VM::new( + TxKind::Call(*CONTRACT_ADDRESS), + env, + U256::zero(), + calldata, + store_wrapper, + CacheDB::new(), + vec![], + None, + ) + .map_err(EvmError::from)?; + + let mut report = vm.transact().map_err(EvmError::from)?; + + report.new_state.remove(&*SYSTEM_ADDRESS); + + Ok(report) +} diff --git a/crates/vm/evm_backends/mod.rs b/crates/vm/evm_backends/mod.rs new file mode 100644 index 0000000000..8d8b00d6dd --- /dev/null +++ b/crates/vm/evm_backends/mod.rs @@ -0,0 +1,22 @@ +pub mod levm; +pub mod revm; + +use crate::errors::EvmError; +use std::str::FromStr; + +#[derive(Debug, Clone)] +pub enum EVM { + LEVM, + REVM, +} + +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())), + } + } +} diff --git a/crates/vm/evm_backends/revm.rs b/crates/vm/evm_backends/revm.rs new file mode 100644 index 0000000000..0a1a730780 --- /dev/null +++ b/crates/vm/evm_backends/revm.rs @@ -0,0 +1,457 @@ +use crate::spec_id; +use crate::EvmError; +use crate::EvmState; +use crate::ExecutionResult; +use ethrex_storage::error::StoreError; +use lazy_static::lazy_static; +use revm::{ + inspectors::TracerEip3155, + precompile::{PrecompileSpecId, Precompiles}, + primitives::{BlobExcessGasAndPrice, BlockEnv, TxEnv, B256}, + Database, DatabaseCommit, Evm, +}; +use revm_inspectors::access_list::AccessListInspector; +// Rename imported types for clarity +use ethrex_core::{ + types::{ + Block, BlockHeader, GenericTransaction, PrivilegedTxType, Receipt, Transaction, TxKind, + Withdrawal, GWEI_TO_WEI, INITIAL_BASE_FEE, + }, + Address, +}; +use revm_primitives::{ + ruint::Uint, AccessList as RevmAccessList, AccessListItem, Address as RevmAddress, Bytes, + FixedBytes, SpecId, TxKind as RevmTxKind, U256 as RevmU256, +}; +use std::cmp::min; + +#[cfg(feature = "l2")] +mod mods; + +/// Executes all transactions in a block and returns their receipts. +pub fn execute_block(block: &Block, state: &mut EvmState) -> Result, EvmError> { + let block_header = &block.header; + let spec_id = spec_id(&state.chain_config()?, block_header.timestamp); + //eip 4788: execute beacon_root_contract_call before block transactions + cfg_if::cfg_if! { + if #[cfg(not(feature = "l2"))] { + //eip 4788: execute beacon_root_contract_call before block transactions + if block_header.parent_beacon_block_root.is_some() && spec_id == SpecId::CANCUN { + beacon_root_contract_call(state, block_header, spec_id)?; + } + } + } + let mut receipts = Vec::new(); + let mut cumulative_gas_used = 0; + + for transaction in block.body.transactions.iter() { + let result = execute_tx(transaction, block_header, state, spec_id)?; + cumulative_gas_used += result.gas_used(); + let receipt = Receipt::new( + transaction.tx_type(), + result.is_success(), + cumulative_gas_used, + result.logs(), + ); + receipts.push(receipt); + } + + if let Some(withdrawals) = &block.body.withdrawals { + process_withdrawals(state, withdrawals)?; + } + + Ok(receipts) +} + +// Executes a single tx, doesn't perform state transitions +pub fn execute_tx( + tx: &Transaction, + header: &BlockHeader, + state: &mut EvmState, + spec_id: SpecId, +) -> Result { + let block_env = block_env(header); + let tx_env = tx_env(tx); + run_evm(tx_env, block_env, state, spec_id) +} + +/// Runs the transaction and returns the result, but does not commit it. +pub fn run_without_commit( + tx_env: TxEnv, + mut block_env: BlockEnv, + state: &mut EvmState, + spec_id: SpecId, +) -> Result { + adjust_disabled_base_fee( + &mut block_env, + tx_env.gas_price, + tx_env.max_fee_per_blob_gas, + ); + let chain_config = state.chain_config()?; + #[allow(unused_mut)] + let mut evm_builder = Evm::builder() + .with_block_env(block_env) + .with_tx_env(tx_env) + .with_spec_id(spec_id) + .modify_cfg_env(|env| { + env.disable_base_fee = true; + env.disable_block_gas_limit = true; + env.chain_id = chain_config.chain_id; + }); + let tx_result = match state { + EvmState::Store(db) => { + let mut evm = evm_builder.with_db(db).build(); + evm.transact().map_err(EvmError::from)? + } + EvmState::Execution(db) => { + let mut evm = evm_builder.with_db(db).build(); + evm.transact().map_err(EvmError::from)? + } + }; + Ok(tx_result.result.into()) +} + +/// Runs EVM, doesn't perform state transitions, but stores them +fn run_evm( + tx_env: TxEnv, + block_env: BlockEnv, + state: &mut EvmState, + spec_id: SpecId, +) -> Result { + let tx_result = { + let chain_spec = state.chain_config()?; + #[allow(unused_mut)] + let mut evm_builder = Evm::builder() + .with_block_env(block_env) + .with_tx_env(tx_env) + .modify_cfg_env(|cfg| cfg.chain_id = chain_spec.chain_id) + .with_spec_id(spec_id) + .with_external_context( + TracerEip3155::new(Box::new(std::io::stderr())).without_summary(), + ); + cfg_if::cfg_if! { + if #[cfg(feature = "l2")] { + use revm::{Handler, primitives::{CancunSpec, HandlerCfg}}; + use std::sync::Arc; + + evm_builder = evm_builder.with_handler({ + let mut evm_handler = Handler::new(HandlerCfg::new(SpecId::LATEST)); + evm_handler.pre_execution.deduct_caller = Arc::new(mods::deduct_caller::); + evm_handler.validation.tx_against_state = Arc::new(mods::validate_tx_against_state::); + evm_handler.execution.last_frame_return = Arc::new(mods::last_frame_return::); + // TODO: Override `end` function. We should deposit even if we revert. + // evm_handler.pre_execution.end + evm_handler + }); + } + } + + match state { + EvmState::Store(db) => { + let mut evm = evm_builder.with_db(db).build(); + evm.transact_commit().map_err(EvmError::from)? + } + EvmState::Execution(db) => { + let mut evm = evm_builder.with_db(db).build(); + evm.transact_commit().map_err(EvmError::from)? + } + } + }; + Ok(tx_result.into()) +} + +/// Processes a block's withdrawals, updating the account balances in the state +pub fn process_withdrawals( + state: &mut EvmState, + withdrawals: &[Withdrawal], +) -> Result<(), StoreError> { + match state { + EvmState::Store(db) => { + //balance_increments is a vector of tuples (Address, increment as u128) + let balance_increments = withdrawals + .iter() + .filter(|withdrawal| withdrawal.amount > 0) + .map(|withdrawal| { + ( + RevmAddress::from_slice(withdrawal.address.as_bytes()), + (withdrawal.amount as u128 * GWEI_TO_WEI as u128), + ) + }) + .collect::>(); + + db.increment_balances(balance_increments)?; + } + EvmState::Execution(_) => { + // TODO: We should check withdrawals are valid + // (by checking that accounts exist if this is the only error) but there's no state to + // change. + } + } + Ok(()) +} + +pub fn block_env(header: &BlockHeader) -> BlockEnv { + BlockEnv { + number: RevmU256::from(header.number), + coinbase: RevmAddress(header.coinbase.0.into()), + timestamp: RevmU256::from(header.timestamp), + gas_limit: RevmU256::from(header.gas_limit), + basefee: RevmU256::from(header.base_fee_per_gas.unwrap_or(INITIAL_BASE_FEE)), + difficulty: RevmU256::from_limbs(header.difficulty.0), + prevrandao: Some(header.prev_randao.as_fixed_bytes().into()), + blob_excess_gas_and_price: Some(BlobExcessGasAndPrice::new( + header.excess_blob_gas.unwrap_or_default(), + )), + } +} + +// Used for the L2 +pub const WITHDRAWAL_MAGIC_DATA: &[u8] = b"burn"; +pub const DEPOSIT_MAGIC_DATA: &[u8] = b"mint"; +pub fn tx_env(tx: &Transaction) -> TxEnv { + let max_fee_per_blob_gas = tx + .max_fee_per_blob_gas() + .map(|x| RevmU256::from_be_bytes(x.to_big_endian())); + TxEnv { + caller: match tx { + Transaction::PrivilegedL2Transaction(tx) if tx.tx_type == PrivilegedTxType::Deposit => { + RevmAddress::ZERO + } + _ => RevmAddress(tx.sender().0.into()), + }, + gas_limit: tx.gas_limit(), + gas_price: RevmU256::from(tx.gas_price()), + transact_to: match tx { + Transaction::PrivilegedL2Transaction(tx) + if tx.tx_type == PrivilegedTxType::Withdrawal => + { + RevmTxKind::Call(RevmAddress::ZERO) + } + _ => match tx.to() { + TxKind::Call(address) => RevmTxKind::Call(address.0.into()), + TxKind::Create => RevmTxKind::Create, + }, + }, + value: RevmU256::from_limbs(tx.value().0), + data: match tx { + Transaction::PrivilegedL2Transaction(tx) => match tx.tx_type { + PrivilegedTxType::Deposit => DEPOSIT_MAGIC_DATA.into(), + PrivilegedTxType::Withdrawal => { + let to = match tx.to { + TxKind::Call(to) => to, + _ => Address::zero(), + }; + [Bytes::from(WITHDRAWAL_MAGIC_DATA), Bytes::from(to.0)] + .concat() + .into() + } + }, + _ => tx.data().clone().into(), + }, + nonce: Some(tx.nonce()), + chain_id: tx.chain_id(), + access_list: tx + .access_list() + .into_iter() + .map(|(addr, list)| { + let (address, storage_keys) = ( + RevmAddress(addr.0.into()), + list.into_iter() + .map(|a| FixedBytes::from_slice(a.as_bytes())) + .collect(), + ); + AccessListItem { + address, + storage_keys, + } + }) + .collect(), + gas_priority_fee: tx.max_priority_fee().map(RevmU256::from), + blob_hashes: tx + .blob_versioned_hashes() + .into_iter() + .map(|hash| B256::from(hash.0)) + .collect(), + max_fee_per_blob_gas, + // TODO revise + // https://eips.ethereum.org/EIPS/eip-7702 + authorization_list: None, + } +} + +// Used to estimate gas and create access lists +pub(crate) fn tx_env_from_generic(tx: &GenericTransaction, basefee: u64) -> TxEnv { + let gas_price = calculate_gas_price(tx, basefee); + TxEnv { + caller: RevmAddress(tx.from.0.into()), + gas_limit: tx.gas.unwrap_or(u64::MAX), // Ensure tx doesn't fail due to gas limit + gas_price, + transact_to: match tx.to { + TxKind::Call(address) => RevmTxKind::Call(address.0.into()), + TxKind::Create => RevmTxKind::Create, + }, + value: RevmU256::from_limbs(tx.value.0), + data: tx.input.clone().into(), + nonce: tx.nonce, + chain_id: tx.chain_id, + access_list: tx + .access_list + .iter() + .map(|list| { + let (address, storage_keys) = ( + RevmAddress::from_slice(list.address.as_bytes()), + list.storage_keys + .iter() + .map(|a| FixedBytes::from_slice(a.as_bytes())) + .collect(), + ); + AccessListItem { + address, + storage_keys, + } + }) + .collect(), + gas_priority_fee: tx.max_priority_fee_per_gas.map(RevmU256::from), + blob_hashes: tx + .blob_versioned_hashes + .iter() + .map(|hash| B256::from(hash.0)) + .collect(), + max_fee_per_blob_gas: tx.max_fee_per_blob_gas.map(|x| RevmU256::from_limbs(x.0)), + // TODO revise + // https://eips.ethereum.org/EIPS/eip-7702 + authorization_list: None, + } +} + +// Creates an AccessListInspector that will collect the accesses used by the evm execution +pub(crate) fn access_list_inspector( + tx_env: &TxEnv, + state: &mut EvmState, + spec_id: SpecId, +) -> Result { + // Access list provided by the transaction + let current_access_list = RevmAccessList(tx_env.access_list.clone()); + // Addresses accessed when using precompiles + let precompile_addresses = Precompiles::new(PrecompileSpecId::from_spec_id(spec_id)) + .addresses() + .cloned(); + // Address that is either called or created by the transaction + let to = match tx_env.transact_to { + RevmTxKind::Call(address) => address, + RevmTxKind::Create => { + let nonce = match state { + EvmState::Store(db) => db.basic(tx_env.caller)?, + EvmState::Execution(db) => db.basic(tx_env.caller)?, + } + .map(|info| info.nonce) + .unwrap_or_default(); + tx_env.caller.create(nonce) + } + }; + Ok(AccessListInspector::new( + current_access_list, + tx_env.caller, + to, + precompile_addresses, + )) +} + +/// Calculating gas_price according to EIP-1559 rules +/// See https://github.com/ethereum/go-ethereum/blob/7ee9a6e89f59cee21b5852f5f6ffa2bcfc05a25f/internal/ethapi/transaction_args.go#L430 +fn calculate_gas_price(tx: &GenericTransaction, basefee: u64) -> Uint<256, 4> { + if tx.gas_price != 0 { + // Legacy gas field was specified, use it + RevmU256::from(tx.gas_price) + } else { + // Backfill the legacy gas price for EVM execution, (zero if max_fee_per_gas is zero) + RevmU256::from(min( + tx.max_priority_fee_per_gas.unwrap_or(0) + basefee, + tx.max_fee_per_gas.unwrap_or(0), + )) + } +} + +/// When basefee tracking is disabled (ie. env.disable_base_fee = true; env.disable_block_gas_limit = true;) +/// and no gas prices were specified, lower the basefee to 0 to avoid breaking EVM invariants (basefee < feecap) +/// See https://github.com/ethereum/go-ethereum/blob/00294e9d28151122e955c7db4344f06724295ec5/core/vm/evm.go#L137 +fn adjust_disabled_base_fee( + block_env: &mut BlockEnv, + tx_gas_price: Uint<256, 4>, + tx_blob_gas_price: Option>, +) { + if tx_gas_price == RevmU256::from(0) { + block_env.basefee = RevmU256::from(0); + } + if tx_blob_gas_price.is_some_and(|v| v == RevmU256::from(0)) { + block_env.blob_excess_gas_and_price = None; + } +} + +/// Calls the eip4788 beacon block root system call contract +/// As of the Cancun hard-fork, parent_beacon_block_root needs to be present in the block header. +pub fn beacon_root_contract_call( + state: &mut EvmState, + header: &BlockHeader, + spec_id: SpecId, +) -> Result { + lazy_static! { + static ref SYSTEM_ADDRESS: RevmAddress = RevmAddress::from_slice( + &hex::decode("fffffffffffffffffffffffffffffffffffffffe").unwrap() + ); + static ref CONTRACT_ADDRESS: RevmAddress = RevmAddress::from_slice( + &hex::decode("000F3df6D732807Ef1319fB7B8bB8522d0Beac02").unwrap(), + ); + }; + let beacon_root = match header.parent_beacon_block_root { + None => { + return Err(EvmError::Header( + "parent_beacon_block_root field is missing".to_string(), + )) + } + Some(beacon_root) => beacon_root, + }; + + let tx_env = TxEnv { + caller: *SYSTEM_ADDRESS, + transact_to: RevmTxKind::Call(*CONTRACT_ADDRESS), + gas_limit: 30_000_000, + data: revm::primitives::Bytes::copy_from_slice(beacon_root.as_bytes()), + ..Default::default() + }; + let mut block_env = block_env(header); + block_env.basefee = RevmU256::ZERO; + block_env.gas_limit = RevmU256::from(30_000_000); + + match state { + EvmState::Store(db) => { + let mut evm = Evm::builder() + .with_db(db) + .with_block_env(block_env) + .with_tx_env(tx_env) + .with_spec_id(spec_id) + .build(); + + let transaction_result = evm.transact()?; + let mut result_state = transaction_result.state; + result_state.remove(&*SYSTEM_ADDRESS); + result_state.remove(&evm.block().coinbase); + + evm.context.evm.db.commit(result_state); + + Ok(transaction_result.result.into()) + } + EvmState::Execution(db) => { + let mut evm = Evm::builder() + .with_db(db) + .with_block_env(block_env) + .with_tx_env(tx_env) + .with_spec_id(spec_id) + .build(); + + // Not necessary to commit to DB + let transaction_result = evm.transact()?; + Ok(transaction_result.result.into()) + } + } +} diff --git a/crates/vm/execution_db.rs b/crates/vm/execution_db.rs index 2bf2b8cafc..ead5f9a78f 100644 --- a/crates/vm/execution_db.rs +++ b/crates/vm/execution_db.rs @@ -21,8 +21,10 @@ use revm_primitives::SpecId; use serde::{Deserialize, Serialize}; use crate::{ - block_env, db::StoreWrapper, errors::ExecutionDBError, evm_state, execute_block, - get_state_transitions, spec_id, tx_env, EvmError, + block_env, + db::{evm_state, StoreWrapper}, + errors::ExecutionDBError, + execute_block, get_state_transitions, spec_id, tx_env, EvmError, }; /// In-memory EVM database for single execution data. diff --git a/crates/vm/vm.rs b/crates/vm/vm.rs index acf5ece007..c41a189d0f 100644 --- a/crates/vm/vm.rs +++ b/crates/vm/vm.rs @@ -1,406 +1,46 @@ pub mod db; pub mod errors; +pub mod evm_backends; pub mod execution_db; mod execution_result; -#[cfg(feature = "l2")] -mod mods; -use db::StoreWrapper; -use execution_db::ExecutionDB; -use std::{cmp::min, str::FromStr}; +use db::EvmState; +use crate::evm_backends::revm::*; use ethrex_core::{ - types::{ - AccountInfo, Block, BlockHash, BlockHeader, ChainConfig, Fork, GenericTransaction, - PrivilegedTxType, Receipt, Transaction, TxKind, Withdrawal, GWEI_TO_WEI, INITIAL_BASE_FEE, - }, + types::{AccountInfo, BlockHeader, ChainConfig, Fork, GenericTransaction, INITIAL_BASE_FEE}, Address, BigEndianHash, H256, U256, }; -use ethrex_storage::{error::StoreError, AccountUpdate, Store}; -use lazy_static::lazy_static; +use ethrex_storage::AccountUpdate; use revm::{ db::{states::bundle_state::BundleRetention, AccountState, AccountStatus}, inspector_handle_register, - inspectors::TracerEip3155, - precompile::{PrecompileSpecId, Precompiles}, - primitives::{BlobExcessGasAndPrice, BlockEnv, TxEnv, B256}, - Database, DatabaseCommit, Evm, + primitives::{BlockEnv, TxEnv, B256}, + Evm, }; -use revm_inspectors::access_list::AccessListInspector; // Rename imported types for clarity -use revm_primitives::{ - ruint::Uint, AccessList as RevmAccessList, AccessListItem, Bytes, FixedBytes, - TxKind as RevmTxKind, -}; +use revm_primitives::AccessList as RevmAccessList; // Export needed types pub use errors::EvmError; pub use execution_result::*; pub use revm::primitives::{Address as RevmAddress, SpecId, U256 as RevmU256}; -#[derive(Debug, Clone)] -pub enum EVM { - LEVM, - REVM, -} - -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())), - } - } -} - // TODO use the type inside ethrex_core type AccessList = Vec<(Address, Vec)>; -pub const WITHDRAWAL_MAGIC_DATA: &[u8] = b"burn"; -pub const DEPOSIT_MAGIC_DATA: &[u8] = b"mint"; - -/// State used when running the EVM. The state can be represented with a [StoreWrapper] database, or -/// with a [ExecutionDB] in case we only want to store the necessary data for some particular -/// execution, for example when proving in L2 mode. -/// -/// Encapsulates state behaviour to be agnostic to the evm implementation for crate users. -pub enum EvmState { - Store(revm::db::State), - Execution(Box>), -} - -impl EvmState { - /// Get a reference to inner `Store` database - pub fn database(&self) -> Option<&Store> { - if let EvmState::Store(db) = self { - Some(&db.database.store) - } else { - None - } - } - - /// Gets the stored chain config - pub fn chain_config(&self) -> Result { - match self { - EvmState::Store(db) => db.database.store.get_chain_config().map_err(EvmError::from), - EvmState::Execution(db) => Ok(db.db.get_chain_config()), - } - } -} - -impl From for EvmState { - fn from(value: ExecutionDB) -> Self { - EvmState::Execution(Box::new(revm::db::CacheDB::new(value))) - } -} +// ================== Execute Block functions ====================== cfg_if::cfg_if! { if #[cfg(feature = "levm")] { - use ethrex_levm::{ - db::{CacheDB, Database as LevmDatabase}, - errors::{TransactionReport, TxResult, VMError}, - vm::VM, - Environment, - Account - }; - use std::{collections::HashMap, sync::Arc}; - use ethrex_core::types::code_hash; - - /// Calls the eip4788 beacon block root system call contract - /// More info on https://eips.ethereum.org/EIPS/eip-4788 - pub fn beacon_root_contract_call_levm( - store_wrapper: Arc, - block_header: &BlockHeader, - fork: Fork, - ) -> Result { - lazy_static! { - static ref SYSTEM_ADDRESS: Address = - Address::from_slice(&hex::decode("fffffffffffffffffffffffffffffffffffffffe").unwrap()); - static ref CONTRACT_ADDRESS: Address = - Address::from_slice(&hex::decode("000F3df6D732807Ef1319fB7B8bB8522d0Beac02").unwrap(),); - }; - // This is OK - let beacon_root = match block_header.parent_beacon_block_root { - None => { - return Err(EvmError::Header( - "parent_beacon_block_root field is missing".to_string(), - )) - } - Some(beacon_root) => beacon_root, - }; - - let env = Environment { - origin: *SYSTEM_ADDRESS, - gas_limit: 30_000_000, - block_number: block_header.number.into(), - coinbase: block_header.coinbase, - timestamp: block_header.timestamp.into(), - prev_randao: Some(block_header.prev_randao), - base_fee_per_gas: U256::zero(), - gas_price: U256::zero(), - block_excess_blob_gas: block_header.excess_blob_gas.map(U256::from), - block_blob_gas_used: block_header.blob_gas_used.map(U256::from), - block_gas_limit: 30_000_000, - transient_storage: HashMap::new(), - fork, - ..Default::default() - }; - - let calldata = Bytes::copy_from_slice(beacon_root.as_bytes()).into(); - - // Here execute with LEVM but just return transaction report. And I will handle it in the calling place. - - let mut vm = VM::new( - TxKind::Call(*CONTRACT_ADDRESS), - env, - U256::zero(), - calldata, - store_wrapper, - CacheDB::new(), - vec![], - None - ) - .map_err(EvmError::from)?; - - let mut report = vm.transact().map_err(EvmError::from)?; - - report.new_state.remove(&*SYSTEM_ADDRESS); - - Ok(report) - - } - - pub fn get_state_transitions_levm( - initial_state: &EvmState, - block_hash: H256, - new_state: &CacheDB, - ) -> Vec { - 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 { - // This stores things that have changed in the account. - let mut account_update = AccountUpdate::new(*new_state_account_address); - - // Account state before block execution. - let initial_account_state = current_db - .get_account_info_by_hash(block_hash, *new_state_account_address) - .expect("Error getting account info by address") - .unwrap_or_default(); - // Account state after block execution. - let new_state_acc_info = AccountInfo { - code_hash: code_hash(&new_state_account.info.bytecode), - balance: new_state_account.info.balance, - nonce: new_state_account.info.nonce, - }; - - // Compare Account Info - if initial_account_state != new_state_acc_info { - account_update.info = Some(new_state_acc_info.clone()); - } - - // If code hash is different it means the code is different too. - if initial_account_state.code_hash != new_state_acc_info.code_hash { - account_update.code = Some(new_state_account.info.bytecode.clone()); - } - - let mut updated_storage = HashMap::new(); - for (key, storage_slot) in &new_state_account.storage { - // original_value in storage_slot is not the original_value on the DB, be careful. - let original_value = current_db.get_storage_at_hash(block_hash, *new_state_account_address, *key).unwrap().unwrap_or_default(); // Option inside result, I guess I have to assume it is zero. - - if original_value != storage_slot.current_value { - updated_storage.insert(*key, storage_slot.current_value); - } - } - account_update.added_storage = updated_storage; - - account_update.removed = new_state_account.is_empty(); - - if account_update != AccountUpdate::new(*new_state_account_address) { - account_updates.push(account_update); - } - } - account_updates - } - - /// Executes all transactions in a block and returns their receipts. - pub fn execute_block( - block: &Block, - state: &mut EvmState, - ) -> Result<(Vec, Vec), EvmError> { - let store_wrapper = Arc::new(StoreWrapper { - store: state.database().unwrap().clone(), - block_hash: block.header.parent_hash, - }); - let mut block_cache: CacheDB = HashMap::new(); - let block_header = &block.header; - let fork = state.chain_config()?.fork(block_header.timestamp); - //eip 4788: execute beacon_root_contract_call before block transactions - cfg_if::cfg_if! { - if #[cfg(not(feature = "l2"))] { - if block_header.parent_beacon_block_root.is_some() && fork == Fork::Cancun { - let report = beacon_root_contract_call_levm(store_wrapper.clone(), block_header, fork)?; - block_cache.extend(report.new_state); - } - } - } - - - // Account updates are initialized like this because of the beacon_root_contract_call, it is going to be empty if it wasn't called. - let mut account_updates = get_state_transitions(state); - - let mut receipts = Vec::new(); - let mut cumulative_gas_used = 0; - - for tx in block.body.transactions.iter() { - let report = execute_tx_levm(tx, block_header, store_wrapper.clone(), block_cache.clone(), fork).map_err(EvmError::from)?; - - let mut new_state = report.new_state.clone(); - - // Now original_value is going to be the same as the current_value, for the next transaction. - // It should have only one value but it is convenient to keep on using our CacheDB structure - for account in new_state.values_mut() { - for storage_slot in account.storage.values_mut() { - storage_slot.original_value = storage_slot.current_value; - } - } - - block_cache.extend(new_state); - - // Currently, in LEVM, we don't substract refunded gas to used gas, but that can change in the future. - let gas_used = report.gas_used - report.gas_refunded; - cumulative_gas_used += gas_used; - let receipt = Receipt::new( - tx.tx_type(), - matches!(report.result.clone(), TxResult::Success), - cumulative_gas_used, - report.logs.clone(), - ); - - receipts.push(receipt); - } - - // Here we update block_cache with balance increments caused by withdrawals. - if let Some(withdrawals) = &block.body.withdrawals { - // For every withdrawal we increment the target account's balance - for (address, increment) in withdrawals.iter().filter(|withdrawal| withdrawal.amount > 0).map(|w| (w.address, u128::from(w.amount) * u128::from(GWEI_TO_WEI))) { - // 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_wrapper.get_account_info(address); - Account::from(acc_info) - }); - - account.info.balance += increment.into(); - - block_cache.insert(address, account); - } - } - - account_updates.extend(get_state_transitions_levm(state, block.header.parent_hash, &block_cache)); - - Ok((receipts, account_updates)) - } - - pub fn execute_tx_levm( - tx: &Transaction, - block_header: &BlockHeader, - db: Arc, - block_cache: CacheDB, - fork: Fork - ) -> Result { - let gas_price : U256 = tx.effective_gas_price(block_header.base_fee_per_gas).ok_or(VMError::InvalidTransaction)?.into(); - - let env = Environment { - origin: tx.sender(), - refunded_gas: 0, - gas_limit: tx.gas_limit(), - fork, - block_number: block_header.number.into(), - coinbase: block_header.coinbase, - timestamp: block_header.timestamp.into(), - prev_randao: Some(block_header.prev_randao), - chain_id: tx.chain_id().unwrap_or_default().into(), - base_fee_per_gas: block_header.base_fee_per_gas.unwrap_or_default().into(), - gas_price, - block_excess_blob_gas: block_header.excess_blob_gas.map(U256::from), - block_blob_gas_used: block_header.blob_gas_used.map(U256::from), - tx_blob_hashes: tx.blob_versioned_hashes(), - tx_max_priority_fee_per_gas: tx.max_priority_fee().map(U256::from), - tx_max_fee_per_gas: tx.max_fee_per_gas().map(U256::from), - tx_max_fee_per_blob_gas: tx.max_fee_per_blob_gas().map(U256::from), - block_gas_limit: block_header.gas_limit, - transient_storage: HashMap::new(), - }; - - let mut vm = VM::new( - tx.to(), - env, - tx.value(), - tx.data().clone(), - db, - block_cache, - tx.access_list(), - // TODO: Here we should pass the tx.authorization_list - // We have to implement the EIP7702 tx in ethrex_core - None - )?; - - vm.transact() - } + use evm_backends::levm::execute_block; } else if #[cfg(not(feature = "levm"))] { - /// Executes all transactions in a block and returns their receipts. - pub fn execute_block(block: &Block, state: &mut EvmState) -> Result, EvmError> { - let block_header = &block.header; - let spec_id = spec_id(&state.chain_config()?, block_header.timestamp); - //eip 4788: execute beacon_root_contract_call before block transactions - cfg_if::cfg_if! { - if #[cfg(not(feature = "l2"))] { - //eip 4788: execute beacon_root_contract_call before block transactions - if block_header.parent_beacon_block_root.is_some() && spec_id == SpecId::CANCUN { - beacon_root_contract_call(state, block_header, spec_id)?; - } - } - } - let mut receipts = Vec::new(); - let mut cumulative_gas_used = 0; - - for transaction in block.body.transactions.iter() { - let result = execute_tx(transaction, block_header, state, spec_id)?; - cumulative_gas_used += result.gas_used(); - let receipt = Receipt::new( - transaction.tx_type(), - result.is_success(), - cumulative_gas_used, - result.logs(), - ); - receipts.push(receipt); - } - - if let Some(withdrawals) = &block.body.withdrawals { - process_withdrawals(state, withdrawals)?; - } - - Ok(receipts) - } + use evm_backends::revm::execute_block; } } -// Executes a single tx, doesn't perform state transitions -pub fn execute_tx( - tx: &Transaction, - header: &BlockHeader, - state: &mut EvmState, - spec_id: SpecId, -) -> Result { - let block_env = block_env(header); - let tx_env = tx_env(tx); - run_evm(tx_env, block_env, state, spec_id) -} +// ================== Commonly used functions ====================== +// TODO: IMPLEMENT FOR LEVM // Executes a single GenericTransaction, doesn't commit the result or perform state transitions pub fn simulate_tx_from_generic( tx: &GenericTransaction, @@ -413,71 +53,7 @@ pub fn simulate_tx_from_generic( run_without_commit(tx_env, block_env, state, spec_id) } -/// When basefee tracking is disabled (ie. env.disable_base_fee = true; env.disable_block_gas_limit = true;) -/// and no gas prices were specified, lower the basefee to 0 to avoid breaking EVM invariants (basefee < feecap) -/// See https://github.com/ethereum/go-ethereum/blob/00294e9d28151122e955c7db4344f06724295ec5/core/vm/evm.go#L137 -fn adjust_disabled_base_fee( - block_env: &mut BlockEnv, - tx_gas_price: Uint<256, 4>, - tx_blob_gas_price: Option>, -) { - if tx_gas_price == RevmU256::from(0) { - block_env.basefee = RevmU256::from(0); - } - if tx_blob_gas_price.is_some_and(|v| v == RevmU256::from(0)) { - block_env.blob_excess_gas_and_price = None; - } -} - -/// Runs EVM, doesn't perform state transitions, but stores them -fn run_evm( - tx_env: TxEnv, - block_env: BlockEnv, - state: &mut EvmState, - spec_id: SpecId, -) -> Result { - let tx_result = { - let chain_spec = state.chain_config()?; - #[allow(unused_mut)] - let mut evm_builder = Evm::builder() - .with_block_env(block_env) - .with_tx_env(tx_env) - .modify_cfg_env(|cfg| cfg.chain_id = chain_spec.chain_id) - .with_spec_id(spec_id) - .with_external_context( - TracerEip3155::new(Box::new(std::io::stderr())).without_summary(), - ); - cfg_if::cfg_if! { - if #[cfg(feature = "l2")] { - use revm::{Handler, primitives::{CancunSpec, HandlerCfg}}; - use std::sync::Arc; - - evm_builder = evm_builder.with_handler({ - let mut evm_handler = Handler::new(HandlerCfg::new(SpecId::LATEST)); - evm_handler.pre_execution.deduct_caller = Arc::new(mods::deduct_caller::); - evm_handler.validation.tx_against_state = Arc::new(mods::validate_tx_against_state::); - evm_handler.execution.last_frame_return = Arc::new(mods::last_frame_return::); - // TODO: Override `end` function. We should deposit even if we revert. - // evm_handler.pre_execution.end - evm_handler - }); - } - } - - match state { - EvmState::Store(db) => { - let mut evm = evm_builder.with_db(db).build(); - evm.transact_commit().map_err(EvmError::from)? - } - EvmState::Execution(db) => { - let mut evm = evm_builder.with_db(db).build(); - evm.transact_commit().map_err(EvmError::from)? - } - } - }; - Ok(tx_result.into()) -} - +// TODO: IMPLEMENT FOR LEVM /// Runs the transaction and returns the access list and estimated gas use (when running the tx with said access list) pub fn create_access_list( tx: &GenericTransaction, @@ -515,6 +91,7 @@ pub fn create_access_list( Ok((execution_result, access_list)) } +// TODO: IMPLEMENT FOR LEVM /// Runs the transaction and returns the access list for it fn create_access_list_inner( tx_env: TxEnv, @@ -557,42 +134,6 @@ fn create_access_list_inner( Ok((tx_result.result.into(), access_list)) } -/// Runs the transaction and returns the result, but does not commit it. -fn run_without_commit( - tx_env: TxEnv, - mut block_env: BlockEnv, - state: &mut EvmState, - spec_id: SpecId, -) -> Result { - adjust_disabled_base_fee( - &mut block_env, - tx_env.gas_price, - tx_env.max_fee_per_blob_gas, - ); - let chain_config = state.chain_config()?; - #[allow(unused_mut)] - let mut evm_builder = Evm::builder() - .with_block_env(block_env) - .with_tx_env(tx_env) - .with_spec_id(spec_id) - .modify_cfg_env(|env| { - env.disable_base_fee = true; - env.disable_block_gas_limit = true; - env.chain_id = chain_config.chain_id; - }); - let tx_result = match state { - EvmState::Store(db) => { - let mut evm = evm_builder.with_db(db).build(); - evm.transact().map_err(EvmError::from)? - } - EvmState::Execution(db) => { - let mut evm = evm_builder.with_db(db).build(); - evm.transact().map_err(EvmError::from)? - } - }; - Ok(tx_result.result.into()) -} - /// Merges transitions stored when executing transactions and returns the resulting account updates /// Doesn't update the DB pub fn get_state_transitions(state: &mut EvmState) -> Vec { @@ -721,279 +262,6 @@ pub fn get_state_transitions(state: &mut EvmState) -> Vec { } } -/// Processes a block's withdrawals, updating the account balances in the state -pub fn process_withdrawals( - state: &mut EvmState, - withdrawals: &[Withdrawal], -) -> Result<(), StoreError> { - match state { - EvmState::Store(db) => { - //balance_increments is a vector of tuples (Address, increment as u128) - let balance_increments = withdrawals - .iter() - .filter(|withdrawal| withdrawal.amount > 0) - .map(|withdrawal| { - ( - RevmAddress::from_slice(withdrawal.address.as_bytes()), - (withdrawal.amount as u128 * GWEI_TO_WEI as u128), - ) - }) - .collect::>(); - - db.increment_balances(balance_increments)?; - } - EvmState::Execution(_) => { - // TODO: We should check withdrawals are valid - // (by checking that accounts exist if this is the only error) but there's no state to - // change. - } - } - Ok(()) -} - -/// Builds EvmState from a Store -pub fn evm_state(store: Store, block_hash: BlockHash) -> EvmState { - EvmState::Store( - revm::db::State::builder() - .with_database(StoreWrapper { store, block_hash }) - .with_bundle_update() - .without_state_clear() - .build(), - ) -} - -/// Calls the eip4788 beacon block root system call contract -/// As of the Cancun hard-fork, parent_beacon_block_root needs to be present in the block header. -pub fn beacon_root_contract_call( - state: &mut EvmState, - header: &BlockHeader, - spec_id: SpecId, -) -> Result { - lazy_static! { - static ref SYSTEM_ADDRESS: RevmAddress = RevmAddress::from_slice( - &hex::decode("fffffffffffffffffffffffffffffffffffffffe").unwrap() - ); - static ref CONTRACT_ADDRESS: RevmAddress = RevmAddress::from_slice( - &hex::decode("000F3df6D732807Ef1319fB7B8bB8522d0Beac02").unwrap(), - ); - }; - let beacon_root = match header.parent_beacon_block_root { - None => { - return Err(EvmError::Header( - "parent_beacon_block_root field is missing".to_string(), - )) - } - Some(beacon_root) => beacon_root, - }; - - let tx_env = TxEnv { - caller: *SYSTEM_ADDRESS, - transact_to: RevmTxKind::Call(*CONTRACT_ADDRESS), - gas_limit: 30_000_000, - data: revm::primitives::Bytes::copy_from_slice(beacon_root.as_bytes()), - ..Default::default() - }; - let mut block_env = block_env(header); - block_env.basefee = RevmU256::ZERO; - block_env.gas_limit = RevmU256::from(30_000_000); - - match state { - EvmState::Store(db) => { - let mut evm = Evm::builder() - .with_db(db) - .with_block_env(block_env) - .with_tx_env(tx_env) - .with_spec_id(spec_id) - .build(); - - let transaction_result = evm.transact()?; - let mut result_state = transaction_result.state; - result_state.remove(&*SYSTEM_ADDRESS); - result_state.remove(&evm.block().coinbase); - - evm.context.evm.db.commit(result_state); - - Ok(transaction_result.result.into()) - } - EvmState::Execution(db) => { - let mut evm = Evm::builder() - .with_db(db) - .with_block_env(block_env) - .with_tx_env(tx_env) - .with_spec_id(spec_id) - .build(); - - // Not necessary to commit to DB - let transaction_result = evm.transact()?; - Ok(transaction_result.result.into()) - } - } -} - -pub fn block_env(header: &BlockHeader) -> BlockEnv { - BlockEnv { - number: RevmU256::from(header.number), - coinbase: RevmAddress(header.coinbase.0.into()), - timestamp: RevmU256::from(header.timestamp), - gas_limit: RevmU256::from(header.gas_limit), - basefee: RevmU256::from(header.base_fee_per_gas.unwrap_or(INITIAL_BASE_FEE)), - difficulty: RevmU256::from_limbs(header.difficulty.0), - prevrandao: Some(header.prev_randao.as_fixed_bytes().into()), - blob_excess_gas_and_price: Some(BlobExcessGasAndPrice::new( - header.excess_blob_gas.unwrap_or_default(), - )), - } -} - -pub fn tx_env(tx: &Transaction) -> TxEnv { - let max_fee_per_blob_gas = tx - .max_fee_per_blob_gas() - .map(|x| RevmU256::from_be_bytes(x.to_big_endian())); - TxEnv { - caller: match tx { - Transaction::PrivilegedL2Transaction(tx) if tx.tx_type == PrivilegedTxType::Deposit => { - RevmAddress::ZERO - } - _ => RevmAddress(tx.sender().0.into()), - }, - gas_limit: tx.gas_limit(), - gas_price: RevmU256::from(tx.gas_price()), - transact_to: match tx { - Transaction::PrivilegedL2Transaction(tx) - if tx.tx_type == PrivilegedTxType::Withdrawal => - { - RevmTxKind::Call(RevmAddress::ZERO) - } - _ => match tx.to() { - TxKind::Call(address) => RevmTxKind::Call(address.0.into()), - TxKind::Create => RevmTxKind::Create, - }, - }, - value: RevmU256::from_limbs(tx.value().0), - data: match tx { - Transaction::PrivilegedL2Transaction(tx) => match tx.tx_type { - PrivilegedTxType::Deposit => DEPOSIT_MAGIC_DATA.into(), - PrivilegedTxType::Withdrawal => { - let to = match tx.to { - TxKind::Call(to) => to, - _ => Address::zero(), - }; - [Bytes::from(WITHDRAWAL_MAGIC_DATA), Bytes::from(to.0)] - .concat() - .into() - } - }, - _ => tx.data().clone().into(), - }, - nonce: Some(tx.nonce()), - chain_id: tx.chain_id(), - access_list: tx - .access_list() - .into_iter() - .map(|(addr, list)| { - let (address, storage_keys) = ( - RevmAddress(addr.0.into()), - list.into_iter() - .map(|a| FixedBytes::from_slice(a.as_bytes())) - .collect(), - ); - AccessListItem { - address, - storage_keys, - } - }) - .collect(), - gas_priority_fee: tx.max_priority_fee().map(RevmU256::from), - blob_hashes: tx - .blob_versioned_hashes() - .into_iter() - .map(|hash| B256::from(hash.0)) - .collect(), - max_fee_per_blob_gas, - // TODO revise - // https://eips.ethereum.org/EIPS/eip-7702 - authorization_list: None, - } -} - -// Used to estimate gas and create access lists -fn tx_env_from_generic(tx: &GenericTransaction, basefee: u64) -> TxEnv { - let gas_price = calculate_gas_price(tx, basefee); - TxEnv { - caller: RevmAddress(tx.from.0.into()), - gas_limit: tx.gas.unwrap_or(u64::MAX), // Ensure tx doesn't fail due to gas limit - gas_price, - transact_to: match tx.to { - TxKind::Call(address) => RevmTxKind::Call(address.0.into()), - TxKind::Create => RevmTxKind::Create, - }, - value: RevmU256::from_limbs(tx.value.0), - data: tx.input.clone().into(), - nonce: tx.nonce, - chain_id: tx.chain_id, - access_list: tx - .access_list - .iter() - .map(|list| { - let (address, storage_keys) = ( - RevmAddress::from_slice(list.address.as_bytes()), - list.storage_keys - .iter() - .map(|a| FixedBytes::from_slice(a.as_bytes())) - .collect(), - ); - AccessListItem { - address, - storage_keys, - } - }) - .collect(), - gas_priority_fee: tx.max_priority_fee_per_gas.map(RevmU256::from), - blob_hashes: tx - .blob_versioned_hashes - .iter() - .map(|hash| B256::from(hash.0)) - .collect(), - max_fee_per_blob_gas: tx.max_fee_per_blob_gas.map(|x| RevmU256::from_limbs(x.0)), - // TODO revise - // https://eips.ethereum.org/EIPS/eip-7702 - authorization_list: None, - } -} - -// Creates an AccessListInspector that will collect the accesses used by the evm execution -fn access_list_inspector( - tx_env: &TxEnv, - state: &mut EvmState, - spec_id: SpecId, -) -> Result { - // Access list provided by the transaction - let current_access_list = RevmAccessList(tx_env.access_list.clone()); - // Addresses accessed when using precompiles - let precompile_addresses = Precompiles::new(PrecompileSpecId::from_spec_id(spec_id)) - .addresses() - .cloned(); - // Address that is either called or created by the transaction - let to = match tx_env.transact_to { - RevmTxKind::Call(address) => address, - RevmTxKind::Create => { - let nonce = match state { - EvmState::Store(db) => db.basic(tx_env.caller)?, - EvmState::Execution(db) => db.basic(tx_env.caller)?, - } - .map(|info| info.nonce) - .unwrap_or_default(); - tx_env.caller.create(nonce) - } - }; - Ok(AccessListInspector::new( - current_access_list, - tx_env.caller, - to, - precompile_addresses, - )) -} - /// Returns the spec id according to the block timestamp and the stored chain config /// WARNING: Assumes at least Merge fork is active pub fn spec_id(chain_config: &ChainConfig, block_timestamp: u64) -> SpecId { @@ -1024,23 +292,3 @@ pub fn fork_to_spec_id(fork: Fork) -> SpecId { Fork::PragueEof => SpecId::PRAGUE_EOF, } } - -/// Calculating gas_price according to EIP-1559 rules -/// See https://github.com/ethereum/go-ethereum/blob/7ee9a6e89f59cee21b5852f5f6ffa2bcfc05a25f/internal/ethapi/transaction_args.go#L430 -fn calculate_gas_price(tx: &GenericTransaction, basefee: u64) -> Uint<256, 4> { - if tx.gas_price != 0 { - // Legacy gas field was specified, use it - RevmU256::from(tx.gas_price) - } else { - // Backfill the legacy gas price for EVM execution, (zero if max_fee_per_gas is zero) - RevmU256::from(min( - tx.max_priority_fee_per_gas.unwrap_or(0) + basefee, - tx.max_fee_per_gas.unwrap_or(0), - )) - } -} - -// USED for revm ^19.0.0 -//pub fn is_prague(spec_id: SpecId) -> bool { -// spec_id >= SpecId::PRAGUE -//} From d9f34d5012ca2764ff5c6cca4bde33c380f0f3ad Mon Sep 17 00:00:00 2001 From: fborello-lambda Date: Mon, 3 Feb 2025 12:18:46 -0300 Subject: [PATCH 03/26] fix: imports --- cmd/ef_tests/levm/runner/levm_runner.rs | 2 +- cmd/ef_tests/levm/runner/revm_runner.rs | 5 ++++- cmd/ef_tests/levm/utils.rs | 2 +- crates/blockchain/blockchain.rs | 7 ++----- crates/blockchain/payload.rs | 5 +---- crates/l2/proposer/l1_committer.rs | 2 +- crates/vm/evm_backends/levm.rs | 9 ++++----- crates/vm/evm_backends/revm.rs | 3 +-- crates/vm/vm.rs | 2 ++ 9 files changed, 17 insertions(+), 20 deletions(-) diff --git a/cmd/ef_tests/levm/runner/levm_runner.rs b/cmd/ef_tests/levm/runner/levm_runner.rs index 9bd1468929..3f5ff3896f 100644 --- a/cmd/ef_tests/levm/runner/levm_runner.rs +++ b/cmd/ef_tests/levm/runner/levm_runner.rs @@ -15,7 +15,7 @@ use ethrex_levm::{ Environment, }; use ethrex_storage::AccountUpdate; -use ethrex_vm::{db::StoreWrapper, EvmState}; +use ethrex_vm::db::{EvmState, StoreWrapper}; use keccak_hash::keccak; use std::{collections::HashMap, sync::Arc}; diff --git a/cmd/ef_tests/levm/runner/revm_runner.rs b/cmd/ef_tests/levm/runner/revm_runner.rs index 3ad077b846..cf9eda0463 100644 --- a/cmd/ef_tests/levm/runner/revm_runner.rs +++ b/cmd/ef_tests/levm/runner/revm_runner.rs @@ -14,7 +14,10 @@ use ethrex_levm::{ Account, StorageSlot, }; use ethrex_storage::{error::StoreError, AccountUpdate}; -use ethrex_vm::{db::StoreWrapper, fork_to_spec_id, EvmState, RevmAddress, RevmU256}; +use ethrex_vm::{ + db::{EvmState, StoreWrapper}, + fork_to_spec_id, RevmAddress, RevmU256, +}; use revm::{ db::State, inspectors::TracerEip3155 as RevmTracerEip3155, diff --git a/cmd/ef_tests/levm/utils.rs b/cmd/ef_tests/levm/utils.rs index 7eaf2eb2de..d0a6906b5d 100644 --- a/cmd/ef_tests/levm/utils.rs +++ b/cmd/ef_tests/levm/utils.rs @@ -4,7 +4,7 @@ use crate::{ }; use ethrex_core::{types::Genesis, H256, U256}; use ethrex_storage::{EngineType, Store}; -use ethrex_vm::{evm_state, EvmState}; +use ethrex_vm::db::{evm_state, EvmState}; use spinoff::Spinner; pub fn load_initial_state(test: &EFTest) -> (EvmState, H256) { diff --git a/crates/blockchain/blockchain.rs b/crates/blockchain/blockchain.rs index f3e6daead8..a971ba6d3c 100644 --- a/crates/blockchain/blockchain.rs +++ b/crates/blockchain/blockchain.rs @@ -16,12 +16,9 @@ use ethrex_core::H256; use ethrex_storage::error::StoreError; use ethrex_storage::{AccountUpdate, Store}; +use ethrex_vm::db::{evm_state, EvmState}; #[cfg(feature = "levm")] use ethrex_vm::evm_backends::levm; -use ethrex_vm::{ - db::{evm_state, EvmState}, - evm_backends::revm, -}; //TODO: Implement a struct Chain or BlockChain to encapsulate //functionality and canonical chain state and config @@ -52,7 +49,7 @@ pub fn add_block(block: &Block, storage: &Store) -> Result<(), ChainError> { } #[cfg(not(feature = "levm"))] { - let receipts = revm::execute_block(block, &mut state)?; + let receipts = evm_backends::revm::execute_block(block, &mut state)?; let account_updates = ethrex_vm::get_state_transitions(&mut state); (receipts, account_updates) } diff --git a/crates/blockchain/payload.rs b/crates/blockchain/payload.rs index 36dd46a967..9cda086c03 100644 --- a/crates/blockchain/payload.rs +++ b/crates/blockchain/payload.rs @@ -23,10 +23,7 @@ use ethrex_vm::{evm_backends::revm::*, get_state_transitions, spec_id, SpecId}; use { ethrex_core::types::{Fork, GWEI_TO_WEI}, ethrex_levm::{db::CacheDB, Account, AccountInfo}, - ethrex_vm::{ - beacon_root_contract_call_levm, db::StoreWrapper, execute_tx_levm, - get_state_transitions_levm, - }, + ethrex_vm::{db::StoreWrapper, evm_backends::levm::*}, std::sync::Arc, }; diff --git a/crates/l2/proposer/l1_committer.rs b/crates/l2/proposer/l1_committer.rs index 72267350c8..717870c39e 100644 --- a/crates/l2/proposer/l1_committer.rs +++ b/crates/l2/proposer/l1_committer.rs @@ -20,7 +20,7 @@ use ethrex_l2_sdk::{ eth_client::{eth_sender::Overrides, BlockByNumber, EthClient, WrappedTransaction}, }; use ethrex_storage::{error::StoreError, Store}; -use ethrex_vm::{evm_state, execute_block, get_state_transitions}; +use ethrex_vm::{db::evm_state, evm_backends::revm::execute_block, get_state_transitions}; use keccak_hash::keccak; use secp256k1::SecretKey; use std::{collections::HashMap, time::Duration}; diff --git a/crates/vm/evm_backends/levm.rs b/crates/vm/evm_backends/levm.rs index c6d21494da..02ce8825f3 100644 --- a/crates/vm/evm_backends/levm.rs +++ b/crates/vm/evm_backends/levm.rs @@ -3,8 +3,7 @@ use crate::EvmError; use crate::EvmState; use ethrex_core::{ types::{ - code_hash, AccountInfo, Block, BlockHeader, Fork, Receipt, Transaction, TxKind, - GWEI_TO_WEI, + code_hash, AccountInfo, Block, BlockHeader, Fork, Receipt, Transaction, TxKind, GWEI_TO_WEI, }, Address, H256, U256, }; @@ -112,7 +111,7 @@ pub fn execute_block( Ok((receipts, account_updates)) } -fn execute_tx_levm( +pub fn execute_tx_levm( tx: &Transaction, block_header: &BlockHeader, db: Arc, @@ -162,7 +161,7 @@ fn execute_tx_levm( vm.transact() } -fn get_state_transitions_levm( +pub fn get_state_transitions_levm( initial_state: &EvmState, block_hash: H256, new_state: &CacheDB, @@ -223,7 +222,7 @@ fn get_state_transitions_levm( /// Calls the eip4788 beacon block root system call contract /// More info on https://eips.ethereum.org/EIPS/eip-4788 -fn beacon_root_contract_call_levm( +pub fn beacon_root_contract_call_levm( store_wrapper: Arc, block_header: &BlockHeader, fork: Fork, diff --git a/crates/vm/evm_backends/revm.rs b/crates/vm/evm_backends/revm.rs index 0a1a730780..b88a0b6456 100644 --- a/crates/vm/evm_backends/revm.rs +++ b/crates/vm/evm_backends/revm.rs @@ -25,8 +25,7 @@ use revm_primitives::{ }; use std::cmp::min; -#[cfg(feature = "l2")] -mod mods; +use crate::mods; /// Executes all transactions in a block and returns their receipts. pub fn execute_block(block: &Block, state: &mut EvmState) -> Result, EvmError> { diff --git a/crates/vm/vm.rs b/crates/vm/vm.rs index c41a189d0f..007acb15e5 100644 --- a/crates/vm/vm.rs +++ b/crates/vm/vm.rs @@ -3,6 +3,8 @@ pub mod errors; pub mod evm_backends; pub mod execution_db; mod execution_result; +#[cfg(feature = "l2")] +pub mod mods; use db::EvmState; From 422beb160d530d27d50e2f3beefd47d0fdef146d Mon Sep 17 00:00:00 2001 From: fborello-lambda Date: Mon, 3 Feb 2025 12:40:11 -0300 Subject: [PATCH 04/26] fix: gated import --- crates/blockchain/blockchain.rs | 3 ++- crates/vm/vm.rs | 3 +-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/crates/blockchain/blockchain.rs b/crates/blockchain/blockchain.rs index a971ba6d3c..11ff05bb8c 100644 --- a/crates/blockchain/blockchain.rs +++ b/crates/blockchain/blockchain.rs @@ -17,6 +17,7 @@ use ethrex_core::H256; use ethrex_storage::error::StoreError; use ethrex_storage::{AccountUpdate, Store}; use ethrex_vm::db::{evm_state, EvmState}; + #[cfg(feature = "levm")] use ethrex_vm::evm_backends::levm; @@ -49,7 +50,7 @@ pub fn add_block(block: &Block, storage: &Store) -> Result<(), ChainError> { } #[cfg(not(feature = "levm"))] { - let receipts = evm_backends::revm::execute_block(block, &mut state)?; + let receipts = ethrex_vm::evm_backends::revm::execute_block(block, &mut state)?; let account_updates = ethrex_vm::get_state_transitions(&mut state); (receipts, account_updates) } diff --git a/crates/vm/vm.rs b/crates/vm/vm.rs index 007acb15e5..3079d64070 100644 --- a/crates/vm/vm.rs +++ b/crates/vm/vm.rs @@ -3,8 +3,7 @@ pub mod errors; pub mod evm_backends; pub mod execution_db; mod execution_result; -#[cfg(feature = "l2")] -pub mod mods; +mod mods; use db::EvmState; From 636349286b9d927c48ce40dd973c555a517497b9 Mon Sep 17 00:00:00 2001 From: fborello-lambda Date: Mon, 3 Feb 2025 15:32:36 -0300 Subject: [PATCH 05/26] fix --- crates/common/types/block.rs | 1 + crates/networking/rpc/eth/mod.rs | 5 ++--- crates/networking/rpc/types/block.rs | 6 ++++-- crates/vm/evm_backends/levm.rs | 10 +++++----- crates/vm/evm_backends/revm.rs | 1 + crates/vm/vm.rs | 1 + 6 files changed, 14 insertions(+), 10 deletions(-) diff --git a/crates/common/types/block.rs b/crates/common/types/block.rs index 0edcfd2a57..56e732a61e 100644 --- a/crates/common/types/block.rs +++ b/crates/common/types/block.rs @@ -29,6 +29,7 @@ use once_cell::sync::OnceCell; lazy_static! { pub static ref DEFAULT_OMMERS_HASH: H256 = H256::from_slice(&hex::decode("1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347").unwrap()); // = Keccak256(RLP([])) as of EIP-3675 + pub static ref DEFAULT_REQUESTS_HASH: H256 = H256::from_slice(&hex::decode("e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855").unwrap()); // = Sha256([])) as of EIP-7685 } #[derive(PartialEq, Eq, Debug, Clone, Deserialize, Serialize, Default)] pub struct Block { diff --git a/crates/networking/rpc/eth/mod.rs b/crates/networking/rpc/eth/mod.rs index 8d981e0070..f1389eb1c0 100644 --- a/crates/networking/rpc/eth/mod.rs +++ b/crates/networking/rpc/eth/mod.rs @@ -15,8 +15,7 @@ pub mod test_utils { use bytes::Bytes; use ethrex_core::{ types::{ - Block, BlockBody, BlockHeader, EIP1559Transaction, Genesis, LegacyTransaction, - Transaction, TxKind, EMPTY_KECCACK_HASH, + Block, BlockBody, BlockHeader, EIP1559Transaction, Genesis, LegacyTransaction, Transaction, TxKind, DEFAULT_REQUESTS_HASH, EMPTY_KECCACK_HASH }, Address, Bloom, H256, U256, }; @@ -69,7 +68,7 @@ pub mod test_utils { blob_gas_used: Some(0x00), excess_blob_gas: Some(0x00), parent_beacon_block_root: Some(H256::zero()), - requests_hash: Some(*EMPTY_KECCACK_HASH), + requests_hash: Some(*DEFAULT_REQUESTS_HASH), } } diff --git a/crates/networking/rpc/types/block.rs b/crates/networking/rpc/types/block.rs index 41de1e1016..629c70dc19 100644 --- a/crates/networking/rpc/types/block.rs +++ b/crates/networking/rpc/types/block.rs @@ -102,7 +102,9 @@ mod test { use bytes::Bytes; use ethrex_core::{ - types::{EIP1559Transaction, Transaction, TxKind, EMPTY_KECCACK_HASH}, + types::{ + EIP1559Transaction, Transaction, TxKind, DEFAULT_REQUESTS_HASH, EMPTY_KECCACK_HASH, + }, Address, Bloom, H256, U256, }; use std::str::FromStr; @@ -152,7 +154,7 @@ mod test { blob_gas_used: Some(0x00), excess_blob_gas: Some(0x00), parent_beacon_block_root: Some(H256::zero()), - requests_hash: Some(*EMPTY_KECCACK_HASH), + requests_hash: Some(*DEFAULT_REQUESTS_HASH), }; let tx = EIP1559Transaction { diff --git a/crates/vm/evm_backends/levm.rs b/crates/vm/evm_backends/levm.rs index 02ce8825f3..03f75709d2 100644 --- a/crates/vm/evm_backends/levm.rs +++ b/crates/vm/evm_backends/levm.rs @@ -9,7 +9,7 @@ use ethrex_core::{ }; use ethrex_levm::{ db::{CacheDB, Database as LevmDatabase}, - errors::{TransactionReport, TxResult, VMError}, + errors::{ExecutionReport, TxResult, VMError}, vm::VM, Account, Environment, }; @@ -117,7 +117,7 @@ pub fn execute_tx_levm( db: Arc, block_cache: CacheDB, fork: Fork, -) -> Result { +) -> Result { let gas_price: U256 = tx .effective_gas_price(block_header.base_fee_per_gas) .ok_or(VMError::InvalidTransaction)? @@ -158,7 +158,7 @@ pub fn execute_tx_levm( None, )?; - vm.transact() + vm.execute() } pub fn get_state_transitions_levm( @@ -226,7 +226,7 @@ pub fn beacon_root_contract_call_levm( store_wrapper: Arc, block_header: &BlockHeader, fork: Fork, -) -> Result { +) -> Result { lazy_static! { static ref SYSTEM_ADDRESS: Address = Address::from_slice(&hex::decode("fffffffffffffffffffffffffffffffffffffffe").unwrap()); @@ -276,7 +276,7 @@ pub fn beacon_root_contract_call_levm( ) .map_err(EvmError::from)?; - let mut report = vm.transact().map_err(EvmError::from)?; + let mut report = vm.execute().map_err(EvmError::from)?; report.new_state.remove(&*SYSTEM_ADDRESS); diff --git a/crates/vm/evm_backends/revm.rs b/crates/vm/evm_backends/revm.rs index b88a0b6456..6bb9999dc5 100644 --- a/crates/vm/evm_backends/revm.rs +++ b/crates/vm/evm_backends/revm.rs @@ -25,6 +25,7 @@ use revm_primitives::{ }; use std::cmp::min; +#[cfg(feature = "levm")] use crate::mods; /// Executes all transactions in a block and returns their receipts. diff --git a/crates/vm/vm.rs b/crates/vm/vm.rs index 3079d64070..d137f52659 100644 --- a/crates/vm/vm.rs +++ b/crates/vm/vm.rs @@ -3,6 +3,7 @@ pub mod errors; pub mod evm_backends; pub mod execution_db; mod execution_result; +#[cfg(feature = "levm")] mod mods; use db::EvmState; From 142fb064a7aadc9e6e369672eab57bb9a5faf364 Mon Sep 17 00:00:00 2001 From: fborello-lambda Date: Mon, 3 Feb 2025 16:14:53 -0300 Subject: [PATCH 06/26] fix: >= --- crates/vm/evm_backends/revm.rs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/crates/vm/evm_backends/revm.rs b/crates/vm/evm_backends/revm.rs index e363752f54..48fd67f6c1 100644 --- a/crates/vm/evm_backends/revm.rs +++ b/crates/vm/evm_backends/revm.rs @@ -36,7 +36,7 @@ pub fn execute_block(block: &Block, state: &mut EvmState) -> Result cfg_if::cfg_if! { if #[cfg(not(feature = "l2"))] { //eip 4788: execute beacon_root_contract_call before block transactions - if block_header.parent_beacon_block_root.is_some() && spec_id == SpecId::CANCUN { + if block_header.parent_beacon_block_root.is_some() && spec_id >= SpecId::CANCUN { beacon_root_contract_call(state, block_header, spec_id)?; } } @@ -62,7 +62,6 @@ pub fn execute_block(block: &Block, state: &mut EvmState) -> Result Ok(receipts) } - // Executes a single tx, doesn't perform state transitions pub fn execute_tx( tx: &Transaction, From 53957485279621fd9b407ed40c30dfce778d1c60 Mon Sep 17 00:00:00 2001 From: fborello-lambda Date: Mon, 3 Feb 2025 16:25:59 -0300 Subject: [PATCH 07/26] fix: lint --- crates/common/types/genesis.rs | 4 ++-- crates/networking/rpc/eth/mod.rs | 3 ++- crates/networking/rpc/types/block.rs | 6 ++---- crates/vm/evm_backends/levm.rs | 5 ++++- 4 files changed, 10 insertions(+), 8 deletions(-) diff --git a/crates/common/types/genesis.rs b/crates/common/types/genesis.rs index f665d79e3e..e26931e7e8 100644 --- a/crates/common/types/genesis.rs +++ b/crates/common/types/genesis.rs @@ -8,7 +8,7 @@ use std::collections::HashMap; use super::{ compute_receipts_root, compute_transactions_root, compute_withdrawals_root, AccountState, - Block, BlockBody, BlockHeader, BlockNumber, DEFAULT_OMMERS_HASH, EMPTY_KECCACK_HASH, + Block, BlockBody, BlockHeader, BlockNumber, DEFAULT_OMMERS_HASH, DEFAULT_REQUESTS_HASH, INITIAL_BASE_FEE, }; @@ -334,7 +334,7 @@ impl Genesis { requests_hash: self .config .is_prague_activated(self.timestamp) - .then_some(*EMPTY_KECCACK_HASH), + .then_some(*DEFAULT_REQUESTS_HASH), } } diff --git a/crates/networking/rpc/eth/mod.rs b/crates/networking/rpc/eth/mod.rs index f1389eb1c0..5fc298a793 100644 --- a/crates/networking/rpc/eth/mod.rs +++ b/crates/networking/rpc/eth/mod.rs @@ -15,7 +15,8 @@ pub mod test_utils { use bytes::Bytes; use ethrex_core::{ types::{ - Block, BlockBody, BlockHeader, EIP1559Transaction, Genesis, LegacyTransaction, Transaction, TxKind, DEFAULT_REQUESTS_HASH, EMPTY_KECCACK_HASH + Block, BlockBody, BlockHeader, EIP1559Transaction, Genesis, LegacyTransaction, + Transaction, TxKind, DEFAULT_REQUESTS_HASH, }, Address, Bloom, H256, U256, }; diff --git a/crates/networking/rpc/types/block.rs b/crates/networking/rpc/types/block.rs index 629c70dc19..41de1e1016 100644 --- a/crates/networking/rpc/types/block.rs +++ b/crates/networking/rpc/types/block.rs @@ -102,9 +102,7 @@ mod test { use bytes::Bytes; use ethrex_core::{ - types::{ - EIP1559Transaction, Transaction, TxKind, DEFAULT_REQUESTS_HASH, EMPTY_KECCACK_HASH, - }, + types::{EIP1559Transaction, Transaction, TxKind, EMPTY_KECCACK_HASH}, Address, Bloom, H256, U256, }; use std::str::FromStr; @@ -154,7 +152,7 @@ mod test { blob_gas_used: Some(0x00), excess_blob_gas: Some(0x00), parent_beacon_block_root: Some(H256::zero()), - requests_hash: Some(*DEFAULT_REQUESTS_HASH), + requests_hash: Some(*EMPTY_KECCACK_HASH), }; let tx = EIP1559Transaction { diff --git a/crates/vm/evm_backends/levm.rs b/crates/vm/evm_backends/levm.rs index c38a69b21e..c47ce6d90f 100644 --- a/crates/vm/evm_backends/levm.rs +++ b/crates/vm/evm_backends/levm.rs @@ -1,12 +1,15 @@ use crate::db::StoreWrapper; use crate::EvmError; use crate::EvmState; +#[cfg(not(feature = "l2"))] +use ethrex_core::types::Fork; use ethrex_core::{ types::{ - code_hash, AccountInfo, Block, BlockHeader, Fork, Receipt, Transaction, TxKind, GWEI_TO_WEI, + code_hash, AccountInfo, Block, BlockHeader, Receipt, Transaction, TxKind, GWEI_TO_WEI, }, Address, H256, U256, }; + use ethrex_levm::{ db::{CacheDB, Database as LevmDatabase}, errors::{ExecutionReport, TxResult, VMError}, From 5b2f70ad1fbf478ddae2c79b52badea90ecdd453 Mon Sep 17 00:00:00 2001 From: fborello-lambda Date: Tue, 4 Feb 2025 10:30:41 -0300 Subject: [PATCH 08/26] feat: EVM_BACKEND OnceCell --- cmd/ethrex/ethrex.rs | 14 +++++++++++--- crates/blockchain/blockchain.rs | 25 +++++++++++++------------ crates/vm/evm_backends/mod.rs | 6 ++++++ crates/vm/vm.rs | 19 +++++++++---------- 4 files changed, 39 insertions(+), 25 deletions(-) diff --git a/cmd/ethrex/ethrex.rs b/cmd/ethrex/ethrex.rs index b35be425b6..f26190ca65 100644 --- a/cmd/ethrex/ethrex.rs +++ b/cmd/ethrex/ethrex.rs @@ -10,7 +10,7 @@ use ethrex_net::{ }; use ethrex_rlp::decode::RLPDecode; use ethrex_storage::{EngineType, Store}; -use ethrex_vm::evm_backends::EVM; +use ethrex_vm::{evm_backends::EVM, EVM_BACKEND}; use k256::ecdsa::SigningKey; use local_ip_address::local_ip; use rand::rngs::OsRng; @@ -151,7 +151,8 @@ async fn main() { let sync_mode = sync_mode(&matches); - let _evm = matches.get_one::("evm").unwrap_or(&EVM::REVM); + let evm = matches.get_one::("evm").unwrap_or(&EVM::REVM); + EVM_BACKEND.get_or_init(|| evm.clone()); cfg_if::cfg_if! { if #[cfg(feature = "redb")] { @@ -287,7 +288,14 @@ async fn main() { }; let max_tries = 3; let url = format!("http://{authrpc_socket_addr}"); - let block_producer_engine = ethrex_dev::block_producer::start_block_producer(url, authrpc_jwtsecret.into(), head_block_hash, max_tries, 1000, ethrex_core::Address::default()); + let block_producer_engine = ethrex_dev::block_producer::start_block_producer( + url, + authrpc_jwtsecret.into(), + head_block_hash, + max_tries, + 1000, + ethrex_core::Address::default(), + ); tracker.spawn(block_producer_engine); } else { ethrex_net::start_network( diff --git a/crates/blockchain/blockchain.rs b/crates/blockchain/blockchain.rs index 11ff05bb8c..a1f1e5f75f 100644 --- a/crates/blockchain/blockchain.rs +++ b/crates/blockchain/blockchain.rs @@ -18,8 +18,8 @@ use ethrex_storage::error::StoreError; use ethrex_storage::{AccountUpdate, Store}; use ethrex_vm::db::{evm_state, EvmState}; -#[cfg(feature = "levm")] -use ethrex_vm::evm_backends::levm; +use ethrex_vm::EVM_BACKEND; +use ethrex_vm::{evm_backends, evm_backends::EVM}; //TODO: Implement a struct Chain or BlockChain to encapsulate //functionality and canonical chain state and config @@ -43,16 +43,17 @@ pub fn add_block(block: &Block, storage: &Store) -> Result<(), ChainError> { // Validate the block pre-execution validate_block(block, &parent_header, &state)?; let (receipts, account_updates): (Vec, Vec) = { - // TODO: Consider refactoring both implementations so that they have the same signature - #[cfg(feature = "levm")] - { - levm::execute_block(block, &mut state)? - } - #[cfg(not(feature = "levm"))] - { - let receipts = ethrex_vm::evm_backends::revm::execute_block(block, &mut state)?; - let account_updates = ethrex_vm::get_state_transitions(&mut state); - (receipts, account_updates) + match EVM_BACKEND.get() { + Some(EVM::LEVM) => evm_backends::levm::execute_block(block, &mut state)?, + Some(EVM::REVM) => { + let receipts = evm_backends::revm::execute_block(block, &mut state)?; + let account_updates = ethrex_vm::get_state_transitions(&mut state); + (receipts, account_updates) + } + None => { + tracing::error!("Fatal Error, EVM_BACKEND uninitialized."); + unreachable!(); + } } }; diff --git a/crates/vm/evm_backends/mod.rs b/crates/vm/evm_backends/mod.rs index 8d8b00d6dd..e30841580b 100644 --- a/crates/vm/evm_backends/mod.rs +++ b/crates/vm/evm_backends/mod.rs @@ -10,6 +10,12 @@ pub enum EVM { REVM, } +impl Default for EVM { + fn default() -> Self { + EVM::REVM + } +} + impl FromStr for EVM { type Err = EvmError; fn from_str(s: &str) -> Result { diff --git a/crates/vm/vm.rs b/crates/vm/vm.rs index 78bfcc159b..7bc4fcf58f 100644 --- a/crates/vm/vm.rs +++ b/crates/vm/vm.rs @@ -7,6 +7,7 @@ mod execution_result; mod mods; use db::EvmState; +use evm_backends::EVM; use crate::evm_backends::revm::*; use ethrex_core::{ @@ -27,18 +28,16 @@ pub use errors::EvmError; pub use execution_result::*; pub use revm::primitives::{Address as RevmAddress, SpecId, U256 as RevmU256}; -// TODO use the type inside ethrex_core -type AccessList = Vec<(Address, Vec)>; +use std::sync::OnceLock; -// ================== Execute Block functions ====================== +// This global variable can be initialized by the ethrex cli. +// EVM_BACKEND.get_or_init(|| evm); +// Then, we can retrieve the evm with: +// EVM_BACKEND.get(); -> returns Option +pub static EVM_BACKEND: OnceLock = OnceLock::new(); -cfg_if::cfg_if! { - if #[cfg(feature = "levm")] { - use evm_backends::levm::execute_block; - } else if #[cfg(not(feature = "levm"))] { - use evm_backends::revm::execute_block; - } -} +// TODO use the type inside ethrex_core +type AccessList = Vec<(Address, Vec)>; // ================== Commonly used functions ====================== From 924f18030cd23a0ea06abb3fb485efb30e87344f Mon Sep 17 00:00:00 2001 From: fborello-lambda Date: Tue, 4 Feb 2025 12:57:36 -0300 Subject: [PATCH 09/26] rm levm feature --- cmd/ef_tests/levm/Cargo.toml | 4 +- cmd/ethrex/Cargo.toml | 1 - cmd/ethrex/ethrex.rs | 10 +- crates/blockchain/Cargo.toml | 3 +- crates/blockchain/payload.rs | 383 ++++++++++++++++++----------------- crates/vm/Cargo.toml | 1 - 6 files changed, 208 insertions(+), 194 deletions(-) diff --git a/cmd/ef_tests/levm/Cargo.toml b/cmd/ef_tests/levm/Cargo.toml index d3f548aa88..d141657fee 100644 --- a/cmd/ef_tests/levm/Cargo.toml +++ b/cmd/ef_tests/levm/Cargo.toml @@ -4,11 +4,11 @@ version.workspace = true edition.workspace = true [dependencies] -ethrex-blockchain = { workspace = true, features = ["levm"] } +ethrex-blockchain = { workspace = true } ethrex-core.workspace = true ethrex-storage.workspace = true ethrex-rlp.workspace = true -ethrex-vm = { workspace = true, features = ["levm"] } +ethrex-vm = { workspace = true } ethrex-levm = { path = "../../../crates/vm/levm" } serde.workspace = true serde_json.workspace = true diff --git a/cmd/ethrex/Cargo.toml b/cmd/ethrex/Cargo.toml index 05f707e54a..83d2b69325 100644 --- a/cmd/ethrex/Cargo.toml +++ b/cmd/ethrex/Cargo.toml @@ -48,4 +48,3 @@ metrics = ["ethrex-blockchain/metrics", "ethrex-l2/metrics"] libmdbx = ["dep:libmdbx", "ethrex-storage/libmdbx"] redb = ["dep:redb", "ethrex-storage/redb"] l2 = ["dep:ethrex-l2", "ethrex-vm/l2"] -levm = ["default", "ethrex-vm/levm", "ethrex-blockchain/levm"] diff --git a/cmd/ethrex/ethrex.rs b/cmd/ethrex/ethrex.rs index f26190ca65..cf463905e8 100644 --- a/cmd/ethrex/ethrex.rs +++ b/cmd/ethrex/ethrex.rs @@ -430,14 +430,18 @@ fn import_blocks(store: &Store, blocks: &Vec) { } if let Some(last_block) = blocks.last() { let hash = last_block.hash(); - cfg_if::cfg_if! { - if #[cfg(feature = "levm")] { + match EVM_BACKEND.get() { + Some(EVM::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(store, hash, hash, hash); } - else { + Some(EVM::REVM) => { apply_fork_choice(store, hash, hash, hash).unwrap(); } + None => { + tracing::error!("Fatal Error, EVM_BACKEND uninitialized."); + unreachable!(); + } } } info!("Added {} blocks to blockchain", size); diff --git a/crates/blockchain/Cargo.toml b/crates/blockchain/Cargo.toml index da6c6d7f5c..e9f0e0b726 100644 --- a/crates/blockchain/Cargo.toml +++ b/crates/blockchain/Cargo.toml @@ -6,7 +6,7 @@ edition = "2021" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] -ethrex-levm = { path = "../vm/levm", optional = true } +ethrex-levm = { path = "../vm/levm" } thiserror.workspace = true sha3.workspace = true tracing.workspace = true @@ -31,7 +31,6 @@ path = "./blockchain.rs" [features] default = ["c-kzg"] -levm = ["default", "ethrex-vm/levm", "ethrex-levm"] libmdbx = ["ethrex-core/libmdbx", "ethrex-storage/default", "ethrex-vm/libmdbx"] c-kzg = ["ethrex-core/c-kzg"] metrics = ["ethrex-metrics/transactions"] diff --git a/crates/blockchain/payload.rs b/crates/blockchain/payload.rs index 21141eb162..cb860e2c37 100644 --- a/crates/blockchain/payload.rs +++ b/crates/blockchain/payload.rs @@ -14,18 +14,15 @@ use ethrex_core::{ Address, Bloom, Bytes, H256, U256, }; -use ethrex_vm::{db::evm_state, db::EvmState, EvmError}; - -#[cfg(not(feature = "levm"))] -use ethrex_vm::{evm_backends::revm::*, get_state_transitions, spec_id, SpecId}; - -#[cfg(feature = "levm")] -use { - ethrex_core::types::{Fork, GWEI_TO_WEI}, - ethrex_levm::{db::CacheDB, vm::EVMConfig, Account, AccountInfo}, - ethrex_vm::{db::StoreWrapper, evm_backends::levm::*}, - std::sync::Arc, +use ethrex_core::types::{Fork, GWEI_TO_WEI}; +use ethrex_levm::{db::CacheDB, vm::EVMConfig, Account, AccountInfo}; +use ethrex_vm::{ + db::{evm_state, EvmState, StoreWrapper}, + evm_backends, + evm_backends::EVM, + get_state_transitions, spec_id, EvmError, SpecId, EVM_BACKEND, }; +use std::sync::Arc; use ethrex_rlp::encode::RLPEncode; use ethrex_storage::{error::StoreError, Store}; @@ -181,7 +178,6 @@ fn calc_excess_blob_gas( pub struct PayloadBuildContext<'a> { pub payload: &'a mut Block, pub evm_state: &'a mut EvmState, - #[cfg(feature = "levm")] pub block_cache: CacheDB, pub remaining_gas: u64, pub receipts: Vec, @@ -209,7 +205,6 @@ impl<'a> PayloadBuildContext<'a> { payload, evm_state, blobs_bundle: BlobsBundle::default(), - #[cfg(feature = "levm")] block_cache: CacheDB::new(), }) } @@ -252,53 +247,63 @@ pub fn build_payload( } pub fn apply_withdrawals(context: &mut PayloadBuildContext) -> Result<(), EvmError> { - #[cfg(feature = "levm")] - { - // Apply withdrawals & call beacon root contract, and obtain the new state root - let fork = context - .chain_config()? - .fork(context.payload.header.timestamp); - let blob_schedule = context - .chain_config()? - .get_fork_blob_schedule(context.payload.header.timestamp) - .unwrap_or(EVMConfig::canonical_values(fork)); - let config = EVMConfig::new(fork, blob_schedule); - - if context.payload.header.parent_beacon_block_root.is_some() && fork >= Fork::Cancun { - let store_wrapper = Arc::new(StoreWrapper { - store: context.evm_state.database().unwrap().clone(), - block_hash: context.payload.header.parent_hash, - }); - let report = beacon_root_contract_call_levm( - store_wrapper.clone(), - &context.payload.header, - config, - )?; + match EVM_BACKEND.get() { + Some(EVM::LEVM) => { + // Apply withdrawals & call beacon root contract, and obtain the new state root + let fork = context + .chain_config()? + .fork(context.payload.header.timestamp); + let blob_schedule = context + .chain_config()? + .get_fork_blob_schedule(context.payload.header.timestamp) + .unwrap_or(EVMConfig::canonical_values(fork)); + let config = EVMConfig::new(fork, blob_schedule); + + if context.payload.header.parent_beacon_block_root.is_some() && fork >= Fork::Cancun { + let store_wrapper = Arc::new(StoreWrapper { + store: context.evm_state.database().unwrap().clone(), + block_hash: context.payload.header.parent_hash, + }); + let report = evm_backends::levm::beacon_root_contract_call_levm( + store_wrapper.clone(), + &context.payload.header, + config, + )?; - let mut new_state = report.new_state.clone(); + let mut new_state = report.new_state.clone(); - // Now original_value is going to be the same as the current_value, for the next transaction. - // It should have only one value but it is convenient to keep on using our CacheDB structure - for account in new_state.values_mut() { - for storage_slot in account.storage.values_mut() { - storage_slot.original_value = storage_slot.current_value; + // Now original_value is going to be the same as the current_value, for the next transaction. + // It should have only one value but it is convenient to keep on using our CacheDB structure + for account in new_state.values_mut() { + for storage_slot in account.storage.values_mut() { + storage_slot.original_value = storage_slot.current_value; + } } - } - context.block_cache.extend(new_state); + context.block_cache.extend(new_state); + } + Ok(()) } - Ok(()) - } - #[cfg(not(feature = "levm"))] - { - // Apply withdrawals & call beacon root contract, and obtain the new state root - let spec_id = spec_id(&context.chain_config()?, context.payload.header.timestamp); - if context.payload.header.parent_beacon_block_root.is_some() && spec_id >= SpecId::CANCUN { - beacon_root_contract_call(context.evm_state, &context.payload.header, spec_id)?; + Some(EVM::REVM) => { + // Apply withdrawals & call beacon root contract, and obtain the new state root + let spec_id = spec_id(&context.chain_config()?, context.payload.header.timestamp); + if context.payload.header.parent_beacon_block_root.is_some() + && spec_id >= SpecId::CANCUN + { + evm_backends::revm::beacon_root_contract_call( + context.evm_state, + &context.payload.header, + spec_id, + )?; + } + let withdrawals = context.payload.body.withdrawals.clone().unwrap_or_default(); + evm_backends::revm::process_withdrawals(context.evm_state, &withdrawals)?; + Ok(()) + } + None => { + tracing::error!("Fatal Error, EVM_BACKEND uninitialized."); + unreachable!(); } - let withdrawals = context.payload.body.withdrawals.clone().unwrap_or_default(); - process_withdrawals(context.evm_state, &withdrawals)?; - Ok(()) } } @@ -502,148 +507,156 @@ fn apply_plain_transaction( head: &HeadTransaction, context: &mut PayloadBuildContext, ) -> Result { - #[cfg(feature = "levm")] - { - let store_wrapper = Arc::new(StoreWrapper { - store: context.evm_state.database().unwrap().clone(), - block_hash: context.payload.header.parent_hash, - }); + match EVM_BACKEND.get() { + Some(EVM::LEVM) => { + let store_wrapper = Arc::new(StoreWrapper { + store: context.evm_state.database().unwrap().clone(), + block_hash: context.payload.header.parent_hash, + }); - let fork = context - .chain_config()? - .fork(context.payload.header.timestamp); - let blob_schedule = context - .chain_config()? - .get_fork_blob_schedule(context.payload.header.timestamp) - .unwrap_or(EVMConfig::canonical_values(fork)); - let config = EVMConfig::new(fork, blob_schedule); - - let report = execute_tx_levm( - &head.tx, - &context.payload.header, - store_wrapper.clone(), - context.block_cache.clone(), - config, - ) - .map_err(|e| EvmError::Transaction(format!("Invalid Transaction: {e:?}")))?; - context.remaining_gas = context.remaining_gas.saturating_sub(report.gas_used); - context.block_value += U256::from(report.gas_used) * head.tip; - - let mut new_state = report.new_state.clone(); - - // Now original_value is going to be the same as the current_value, for the next transaction. - // It should have only one value but it is convenient to keep on using our CacheDB structure - for account in new_state.values_mut() { - for storage_slot in account.storage.values_mut() { - storage_slot.original_value = storage_slot.current_value; - } - } + let fork = context + .chain_config()? + .fork(context.payload.header.timestamp); + let blob_schedule = context + .chain_config()? + .get_fork_blob_schedule(context.payload.header.timestamp) + .unwrap_or(EVMConfig::canonical_values(fork)); + let config = EVMConfig::new(fork, blob_schedule); + + let report = evm_backends::levm::execute_tx_levm( + &head.tx, + &context.payload.header, + store_wrapper.clone(), + context.block_cache.clone(), + config, + ) + .map_err(|e| EvmError::Transaction(format!("Invalid Transaction: {e:?}")))?; + context.remaining_gas = context.remaining_gas.saturating_sub(report.gas_used); + context.block_value += U256::from(report.gas_used) * head.tip; - context.block_cache.extend(new_state); + let mut new_state = report.new_state.clone(); - let receipt = Receipt::new( - head.tx.tx_type(), - report.is_success(), - context.payload.header.gas_limit - context.remaining_gas, - report.logs.clone(), - ); - Ok(receipt) - } + // Now original_value is going to be the same as the current_value, for the next transaction. + // It should have only one value but it is convenient to keep on using our CacheDB structure + for account in new_state.values_mut() { + for storage_slot in account.storage.values_mut() { + storage_slot.original_value = storage_slot.current_value; + } + } - // REVM Implementation - #[cfg(not(feature = "levm"))] - { - let report = execute_tx( - &head.tx, - &context.payload.header, - context.evm_state, - spec_id( - &context.chain_config().map_err(ChainError::from)?, - context.payload.header.timestamp, - ), - )?; - context.remaining_gas = context.remaining_gas.saturating_sub(report.gas_used()); - context.block_value += U256::from(report.gas_used()) * head.tip; - let receipt = Receipt::new( - head.tx.tx_type(), - report.is_success(), - context.payload.header.gas_limit - context.remaining_gas, - report.logs(), - ); - Ok(receipt) + context.block_cache.extend(new_state); + + let receipt = Receipt::new( + head.tx.tx_type(), + report.is_success(), + context.payload.header.gas_limit - context.remaining_gas, + report.logs.clone(), + ); + Ok(receipt) + } + Some(EVM::REVM) => { + let report = evm_backends::revm::execute_tx( + &head.tx, + &context.payload.header, + context.evm_state, + spec_id( + &context.chain_config().map_err(ChainError::from)?, + context.payload.header.timestamp, + ), + )?; + context.remaining_gas = context.remaining_gas.saturating_sub(report.gas_used()); + context.block_value += U256::from(report.gas_used()) * head.tip; + let receipt = Receipt::new( + head.tx.tx_type(), + report.is_success(), + context.payload.header.gas_limit - context.remaining_gas, + report.logs(), + ); + Ok(receipt) + } + None => { + tracing::error!("Fatal Error, EVM_BACKEND uninitialized."); + unreachable!(); + } } } fn finalize_payload(context: &mut PayloadBuildContext) -> Result<(), StoreError> { - #[cfg(feature = "levm")] - { - if let Some(withdrawals) = &context.payload.body.withdrawals { - // For every withdrawal we increment the target account's balance - for (address, increment) in withdrawals - .iter() - .filter(|withdrawal| withdrawal.amount > 0) - .map(|w| (w.address, u128::from(w.amount) * u128::from(GWEI_TO_WEI))) - { - // We check if it was in block_cache, if not, we get it from DB. - let mut account = context.block_cache.get(&address).cloned().unwrap_or({ - let acc_info = context - .store() - .unwrap() - .get_account_info_by_hash(context.parent_hash(), address)? - .unwrap_or_default(); - let acc_code = context - .store() - .unwrap() - .get_account_code(acc_info.code_hash)? - .unwrap_or_default(); - - Account { - info: AccountInfo { - balance: acc_info.balance, - bytecode: acc_code, - nonce: acc_info.nonce, - }, - // This is the added_storage for the withdrawal. - // If not involved in the TX, there won't be any updates in the storage - storage: HashMap::new(), - } - }); - - account.info.balance += increment.into(); - context.block_cache.insert(address, account); + match EVM_BACKEND.get() { + Some(EVM::LEVM) => { + if let Some(withdrawals) = &context.payload.body.withdrawals { + // For every withdrawal we increment the target account's balance + for (address, increment) in withdrawals + .iter() + .filter(|withdrawal| withdrawal.amount > 0) + .map(|w| (w.address, u128::from(w.amount) * u128::from(GWEI_TO_WEI))) + { + // We check if it was in block_cache, if not, we get it from DB. + let mut account = context.block_cache.get(&address).cloned().unwrap_or({ + let acc_info = context + .store() + .unwrap() + .get_account_info_by_hash(context.parent_hash(), address)? + .unwrap_or_default(); + let acc_code = context + .store() + .unwrap() + .get_account_code(acc_info.code_hash)? + .unwrap_or_default(); + + Account { + info: AccountInfo { + balance: acc_info.balance, + bytecode: acc_code, + nonce: acc_info.nonce, + }, + // This is the added_storage for the withdrawal. + // If not involved in the TX, there won't be any updates in the storage + storage: HashMap::new(), + } + }); + + account.info.balance += increment.into(); + context.block_cache.insert(address, account); + } } - } - let account_updates = get_state_transitions_levm( - context.evm_state, - context.parent_hash(), - &context.block_cache.clone(), - ); + let account_updates = evm_backends::levm::get_state_transitions_levm( + context.evm_state, + context.parent_hash(), + &context.block_cache.clone(), + ); - context.payload.header.state_root = context - .store() - .ok_or(StoreError::MissingStore)? - .apply_account_updates(context.parent_hash(), &account_updates)? - .unwrap_or_default(); - context.payload.header.transactions_root = - compute_transactions_root(&context.payload.body.transactions); - context.payload.header.receipts_root = compute_receipts_root(&context.receipts); - context.payload.header.gas_used = context.payload.header.gas_limit - context.remaining_gas; - Ok(()) - } - #[cfg(not(feature = "levm"))] - { - let account_updates = get_state_transitions(context.evm_state); - context.payload.header.state_root = context - .store() - .ok_or(StoreError::MissingStore)? - .apply_account_updates(context.parent_hash(), &account_updates)? - .unwrap_or_default(); - context.payload.header.transactions_root = - compute_transactions_root(&context.payload.body.transactions); - context.payload.header.receipts_root = compute_receipts_root(&context.receipts); - context.payload.header.gas_used = context.payload.header.gas_limit - context.remaining_gas; - Ok(()) + context.payload.header.state_root = context + .store() + .ok_or(StoreError::MissingStore)? + .apply_account_updates(context.parent_hash(), &account_updates)? + .unwrap_or_default(); + context.payload.header.transactions_root = + compute_transactions_root(&context.payload.body.transactions); + context.payload.header.receipts_root = compute_receipts_root(&context.receipts); + context.payload.header.gas_used = + context.payload.header.gas_limit - context.remaining_gas; + Ok(()) + } + Some(EVM::REVM) => { + let account_updates = get_state_transitions(context.evm_state); + context.payload.header.state_root = context + .store() + .ok_or(StoreError::MissingStore)? + .apply_account_updates(context.parent_hash(), &account_updates)? + .unwrap_or_default(); + context.payload.header.transactions_root = + compute_transactions_root(&context.payload.body.transactions); + context.payload.header.receipts_root = compute_receipts_root(&context.receipts); + context.payload.header.gas_used = + context.payload.header.gas_limit - context.remaining_gas; + Ok(()) + } + None => { + tracing::error!("Fatal Error, EVM_BACKEND uninitialized."); + unreachable!(); + } } } diff --git a/crates/vm/Cargo.toml b/crates/vm/Cargo.toml index 6cb6792001..b21840bce6 100644 --- a/crates/vm/Cargo.toml +++ b/crates/vm/Cargo.toml @@ -39,7 +39,6 @@ path = "./vm.rs" [features] default = ["c-kzg", "blst"] -levm = ["default"] l2 = [] c-kzg = ["revm/c-kzg"] blst = ["revm/blst"] From a120d11021dded92646721d7cc2f2f38a13ca354 Mon Sep 17 00:00:00 2001 From: fborello-lambda Date: Wed, 5 Feb 2025 12:46:59 -0300 Subject: [PATCH 10/26] feat: integrate root's Makefile --- Makefile | 29 ++++++++++++++--------------- crates/l2/Makefile | 3 ++- 2 files changed, 16 insertions(+), 16 deletions(-) diff --git a/Makefile b/Makefile index 5e49ca531d..14a673c24d 100644 --- a/Makefile +++ b/Makefile @@ -83,19 +83,21 @@ stop-localnet-silent: @kurtosis enclave stop $(ENCLAVE) >/dev/null 2>&1 || true @kurtosis enclave rm $(ENCLAVE) --force >/dev/null 2>&1 || true -HIVE_REVISION := e95ca293cc3fb95a6d964938cd24958e8afa55fa +HIVE_REVISION := b0b0f98bd24676239722e3aa7885e29ef856d804 # Shallow clones can't specify a single revision, but at least we avoid working # the whole history by making it shallow since a given date (one day before our # target revision). HIVE_SHALLOW_SINCE := 2024-09-02 QUIET ?= false +# TODO change it back when we merge the PR. +# git clone --quiet --single-branch --branch master --shallow-since=$(HIVE_SHALLOW_SINCE) https://github.com/lambdaclass/hive ; \ +# git clone --single-branch --branch master --shallow-since=$(HIVE_SHALLOW_SINCE) https://github.com/lambdaclass/hive ; \ + hive: if [ "$(QUIET)" = "true" ]; then \ - git clone --quiet --single-branch --branch master --shallow-since=$(HIVE_SHALLOW_SINCE) https://github.com/lambdaclass/hive && \ - cd hive && git checkout --quiet --detach $(HIVE_REVISION) && go build .; \ + git clone --quiet https://github.com/lambdaclass/hive; \ else \ - git clone --single-branch --branch master --shallow-since=$(HIVE_SHALLOW_SINCE) https://github.com/lambdaclass/hive && \ - cd hive && git checkout --detach $(HIVE_REVISION) && go build .; \ + git clone https://github.com/lambdaclass/hive; \ fi setup-hive: hive ## ๐Ÿ Set up Hive testing framework @@ -169,17 +171,18 @@ install-cli: ## ๐Ÿ› ๏ธ Installs the ethrex-l2 cli start-node-with-flamegraph: rm-test-db ## ๐Ÿš€๐Ÿ”ฅ Starts an ethrex client used for testing @if [ -z "$$L" ]; then \ - LEVM=""; \ + LEVM="revm"; \ echo "Running the test-node without the LEVM feature"; \ echo "If you want to use levm, run the target with an L at the end: make L=1"; \ else \ - LEVM=",levm"; \ + LEVM="levm"; \ echo "Running the test-node with the LEVM feature"; \ fi; \ - sudo CARGO_PROFILE_RELEASE_DEBUG=true cargo flamegraph \ + sudo -E CARGO_PROFILE_RELEASE_DEBUG=true cargo flamegraph \ --bin ethrex \ - --features "dev$$LEVM" \ + --features "dev" \ -- \ + --evm $$LEVM \ --network test_data/genesis-l2.json \ --http.port 1729 \ --datadir test_ethrex @@ -193,14 +196,10 @@ load-node: install-cli ## ๐Ÿšง Runs a load-test. Run make start-node-with-flameg CONTRACT_INTERACTION="-c"; \ echo "Running the load-test with contract interaction"; \ fi; \ - ethrex_l2 test load --path test_data/private_keys.txt -i 100 -v --value 1 $$CONTRACT_INTERACTION + ethrex_l2 test load --path test_data/private_keys.txt -i 1000 -v --value 100000 $$CONTRACT_INTERACTION rm-test-db: ## ๐Ÿ›‘ Removes the DB used by the ethrex client used for testing sudo cargo run --release --bin ethrex -- removedb --datadir test_ethrex -flamegraph: - sudo -E CARGO_PROFILE_RELEASE_DEBUG=true cargo flamegraph --bin ethrex --features dev -- --network test_data/genesis-l2.json --http.port 1729 >/dev/null & +flamegraph: ## ๐Ÿšง Runs a load-test. Run make start-node-with-flamegraph and in a new terminal make flamegraph bash scripts/flamegraph.sh - -test-load: - ethrex_l2 test load --path ./test_data/private_keys.txt -i 1000 -v --value 10000000 --to 0xFCbaC0713ACf16708aB6BC977227041FA1BC618D diff --git a/crates/l2/Makefile b/crates/l2/Makefile index dd3f5a723f..dea20f49bf 100644 --- a/crates/l2/Makefile +++ b/crates/l2/Makefile @@ -73,11 +73,12 @@ init-l1-levm: ## ๐Ÿš€ Initializes an L1 Lambda ethrex Client with LEVM cargo run --release \ --manifest-path ../../Cargo.toml \ --bin ethrex \ - --features dev,ethrex-blockchain/levm,ethrex-vm/levm -- \ + --features "dev" -- \ --network ${L1_GENESIS_FILE_PATH} \ --http.port ${L1_PORT} \ --http.addr 0.0.0.0 \ --authrpc.port ${L1_AUTH_PORT} \ + --evm levm \ --datadir ${ethrex_L1_DEV_LIBMDBX} down-local-l1: ## ๐Ÿ›‘ Shuts down the L1 Lambda ethrex Client From 783a948041b657eb24562209422854e99a9a15a2 Mon Sep 17 00:00:00 2001 From: fborello-lambda Date: Wed, 5 Feb 2025 13:14:49 -0300 Subject: [PATCH 11/26] feat: integrate CI --- .github/workflows/ci_l1.yaml | 1 + .github/workflows/common_hive_reports.yaml | 7 +------ 2 files changed, 2 insertions(+), 6 deletions(-) diff --git a/.github/workflows/ci_l1.yaml b/.github/workflows/ci_l1.yaml index bd0617a6b4..ac2c1c961e 100644 --- a/.github/workflows/ci_l1.yaml +++ b/.github/workflows/ci_l1.yaml @@ -201,6 +201,7 @@ jobs: run: | docker load --input /tmp/ethrex_image.tar + # By default ethrex uses revm as evm backend. - name: Run Hive Simulation run: chmod +x hive && ./hive --client ethrex --sim ${{ matrix.simulation }} --sim.limit "${{ matrix.test_pattern }}" --sim.parallelism 4 diff --git a/.github/workflows/common_hive_reports.yaml b/.github/workflows/common_hive_reports.yaml index fc789402cc..4a7a70f5ab 100644 --- a/.github/workflows/common_hive_reports.yaml +++ b/.github/workflows/common_hive_reports.yaml @@ -39,7 +39,6 @@ jobs: steps: - name: Pull image - if: ${{ inputs.evm == 'revm' }} run: | docker pull ghcr.io/lambdaclass/ethrex:latest docker tag ghcr.io/lambdaclass/ethrex:latest ethrex:latest @@ -47,10 +46,6 @@ jobs: - name: Checkout sources uses: actions/checkout@v4 - - name: Build Image with LEVM - if: ${{ inputs.evm == 'levm' }} - run: cd crates/vm/levm && make build-image-levm - - name: Setup Go uses: actions/setup-go@v5 @@ -58,7 +53,7 @@ jobs: run: make setup-hive - name: Run Hive Simulation - run: cd hive && ./hive --client ethrex --sim ${{ matrix.test.simulation }} --sim.parallelism 16 + run: cd hive && ./hive --client ethrex --sim ${{ matrix.test.simulation }} --ethrex.flags "--evm ${{ intputs.evm }}" --sim.parallelism 16 continue-on-error: true - name: Upload results From 5bee77141f87b1801a2bba6e3b3d494ebe7bd912 Mon Sep 17 00:00:00 2001 From: fborello-lambda Date: Wed, 5 Feb 2025 13:16:53 -0300 Subject: [PATCH 12/26] feat: info msg --- cmd/ethrex/ethrex.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/cmd/ethrex/ethrex.rs b/cmd/ethrex/ethrex.rs index cf463905e8..909646382d 100644 --- a/cmd/ethrex/ethrex.rs +++ b/cmd/ethrex/ethrex.rs @@ -152,7 +152,8 @@ async fn main() { let sync_mode = sync_mode(&matches); let evm = matches.get_one::("evm").unwrap_or(&EVM::REVM); - EVM_BACKEND.get_or_init(|| evm.clone()); + let evm = EVM_BACKEND.get_or_init(|| evm.clone()); + info!("EVM_BACKEND set to: {:?}", evm); cfg_if::cfg_if! { if #[cfg(feature = "redb")] { From 80e35e9ecab906de58aa658ccab283ff728b1517 Mon Sep 17 00:00:00 2001 From: fborello-lambda Date: Wed, 5 Feb 2025 15:03:34 -0300 Subject: [PATCH 13/26] more integrations --- .github/workflows/flamegraph_reporter.yaml | 1 + Makefile | 3 +++ crates/vm/levm/Makefile | 14 +++++++------- 3 files changed, 11 insertions(+), 7 deletions(-) diff --git a/.github/workflows/flamegraph_reporter.yaml b/.github/workflows/flamegraph_reporter.yaml index ea4e894d4f..b2dad338e2 100644 --- a/.github/workflows/flamegraph_reporter.yaml +++ b/.github/workflows/flamegraph_reporter.yaml @@ -252,6 +252,7 @@ jobs: ethrex_l2 config create default --default ethrex_l2 config set default + # By default ethrex uses revm as evm backend. - id: generate-flamegraph-ethrex name: Generate Flamegraph data for Ethrex shell: bash diff --git a/Makefile b/Makefile index 14a673c24d..76b40f7570 100644 --- a/Makefile +++ b/Makefile @@ -125,6 +125,9 @@ SIM_LOG_LEVEL ?= 4 run-hive: build-image setup-hive ## ๐Ÿงช Run Hive testing suite cd hive && ./hive --client ethrex --sim $(SIMULATION) --sim.limit "$(TEST_PATTERN)" +run-hive-levm: build-image setup-hive ## ๐Ÿงช Run Hive testing suite with LEVM + cd hive && ./hive --client ethrex --ethrex.flags "--evm levm" --sim $(SIMULATION) --sim.limit "$(TEST_PATTERN)" + run-hive-all: build-image setup-hive ## ๐Ÿงช Run all Hive testing suites cd hive && ./hive --client ethrex --sim ".*" --sim.parallelism 4 diff --git a/crates/vm/levm/Makefile b/crates/vm/levm/Makefile index ae3729b9ce..c45630f6a8 100644 --- a/crates/vm/levm/Makefile +++ b/crates/vm/levm/Makefile @@ -136,16 +136,16 @@ build-revm-comparison: FLAGS := "--features ethrex-blockchain/levm,ethrex-vm/levm,ethrex/levm" -build-image-levm: ## ๐Ÿณ Build the Docker image with LEVM features +build-image-levm: ## ๐Ÿณ Build the Docker image cd ../../../ && \ - docker build -t ethrex --build-arg BUILD_FLAGS=$(FLAGS) . + docker build -t ethrex . -run-hive-debug-levm: build-image-levm ## ๐Ÿ Run Hive with LEVM in debug mode +run-hive-levm: build-image-levm ## ๐Ÿ Run Hive with LEVM and Build report $(MAKE) -C ../../../ setup-hive - cd ../../../hive && ./hive --sim ethereum/rpc-compat --client ethrex --sim.limit "$(TEST_PATTERN)" --docker.output || exit 0 - cd ../../../hive && ./hive --sim devp2p --client ethrex --sim.limit "$(TEST_PATTERN)" --docker.output || exit 0 - cd ../../../hive && ./hive --sim ethereum/engine --client ethrex --sim.limit "$(TEST_PATTERN)" --docker.output || exit 0 - cd ../../../hive && ./hive --sim ethereum/sync --client ethrex --sim.limit "$(TEST_PATTERN)" --docker.output || exit 0 + cd ../../../hive && ./hive --ethrex.flags "--evm levm" --sim ethereum/rpc-compat --client ethrex --sim.limit "$(TEST_PATTERN)" || exit 0 + cd ../../../hive && ./hive --ethrex.flags "--evm levm" --sim devp2p --client ethrex --sim.limit "$(TEST_PATTERN)" || exit 0 + cd ../../../hive && ./hive --ethrex.flags "--evm levm" --sim ethereum/engine --client ethrex --sim.limit "$(TEST_PATTERN)" || exit 0 + cd ../../../hive && ./hive --ethrex.flags "--evm levm" --sim ethereum/sync --client ethrex --sim.limit "$(TEST_PATTERN)" || exit 0 cargo run --release -p hive_report SUBDIRS := $(shell find $(VECTORS_DIR)/GeneralStateTests -maxdepth 1 -type d ! -path "$(VECTORS_DIR)/GeneralStateTests" -exec basename {} \;) From 4a5d742f395a557989fcd7f0973f640235c1cfaa Mon Sep 17 00:00:00 2001 From: fborello-lambda Date: Wed, 5 Feb 2025 15:22:25 -0300 Subject: [PATCH 14/26] fix: linter and use REVM for tests --- crates/blockchain/blockchain.rs | 7 ++----- crates/blockchain/payload.rs | 21 ++++++--------------- crates/vm/evm_backends/mod.rs | 11 +++-------- 3 files changed, 11 insertions(+), 28 deletions(-) diff --git a/crates/blockchain/blockchain.rs b/crates/blockchain/blockchain.rs index a1f1e5f75f..49c636554f 100644 --- a/crates/blockchain/blockchain.rs +++ b/crates/blockchain/blockchain.rs @@ -45,15 +45,12 @@ pub fn add_block(block: &Block, storage: &Store) -> Result<(), ChainError> { let (receipts, account_updates): (Vec, Vec) = { match EVM_BACKEND.get() { Some(EVM::LEVM) => evm_backends::levm::execute_block(block, &mut state)?, - Some(EVM::REVM) => { + // This means we are using REVM as default for tests + Some(EVM::REVM) | None => { let receipts = evm_backends::revm::execute_block(block, &mut state)?; let account_updates = ethrex_vm::get_state_transitions(&mut state); (receipts, account_updates) } - None => { - tracing::error!("Fatal Error, EVM_BACKEND uninitialized."); - unreachable!(); - } } }; diff --git a/crates/blockchain/payload.rs b/crates/blockchain/payload.rs index cb860e2c37..eeba61f77e 100644 --- a/crates/blockchain/payload.rs +++ b/crates/blockchain/payload.rs @@ -284,7 +284,8 @@ pub fn apply_withdrawals(context: &mut PayloadBuildContext) -> Result<(), EvmErr } Ok(()) } - Some(EVM::REVM) => { + // This means we are using REVM as default for tests + Some(EVM::REVM) | None => { // Apply withdrawals & call beacon root contract, and obtain the new state root let spec_id = spec_id(&context.chain_config()?, context.payload.header.timestamp); if context.payload.header.parent_beacon_block_root.is_some() @@ -300,10 +301,6 @@ pub fn apply_withdrawals(context: &mut PayloadBuildContext) -> Result<(), EvmErr evm_backends::revm::process_withdrawals(context.evm_state, &withdrawals)?; Ok(()) } - None => { - tracing::error!("Fatal Error, EVM_BACKEND uninitialized."); - unreachable!(); - } } } @@ -554,7 +551,8 @@ fn apply_plain_transaction( ); Ok(receipt) } - Some(EVM::REVM) => { + // This means we are using REVM as default for tests + Some(EVM::REVM) | None => { let report = evm_backends::revm::execute_tx( &head.tx, &context.payload.header, @@ -574,10 +572,6 @@ fn apply_plain_transaction( ); Ok(receipt) } - None => { - tracing::error!("Fatal Error, EVM_BACKEND uninitialized."); - unreachable!(); - } } } @@ -639,7 +633,8 @@ fn finalize_payload(context: &mut PayloadBuildContext) -> Result<(), StoreError> context.payload.header.gas_limit - context.remaining_gas; Ok(()) } - Some(EVM::REVM) => { + // This means we are using REVM as default for tests + Some(EVM::REVM) | None => { let account_updates = get_state_transitions(context.evm_state); context.payload.header.state_root = context .store() @@ -653,10 +648,6 @@ fn finalize_payload(context: &mut PayloadBuildContext) -> Result<(), StoreError> context.payload.header.gas_limit - context.remaining_gas; Ok(()) } - None => { - tracing::error!("Fatal Error, EVM_BACKEND uninitialized."); - unreachable!(); - } } } diff --git a/crates/vm/evm_backends/mod.rs b/crates/vm/evm_backends/mod.rs index e30841580b..e89fe5e38e 100644 --- a/crates/vm/evm_backends/mod.rs +++ b/crates/vm/evm_backends/mod.rs @@ -4,16 +4,11 @@ pub mod revm; use crate::errors::EvmError; use std::str::FromStr; -#[derive(Debug, Clone)] +#[derive(Debug, Clone, Default)] pub enum EVM { - LEVM, + #[default] REVM, -} - -impl Default for EVM { - fn default() -> Self { - EVM::REVM - } + LEVM, } impl FromStr for EVM { From 0d88e94cd9a18212b7f6a74616bfa77856e96d9c Mon Sep 17 00:00:00 2001 From: fborello-lambda Date: Wed, 5 Feb 2025 16:22:39 -0300 Subject: [PATCH 15/26] fix: typo --- .github/workflows/common_hive_reports.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/common_hive_reports.yaml b/.github/workflows/common_hive_reports.yaml index 4a7a70f5ab..1efe7a6009 100644 --- a/.github/workflows/common_hive_reports.yaml +++ b/.github/workflows/common_hive_reports.yaml @@ -53,7 +53,7 @@ jobs: run: make setup-hive - name: Run Hive Simulation - run: cd hive && ./hive --client ethrex --sim ${{ matrix.test.simulation }} --ethrex.flags "--evm ${{ intputs.evm }}" --sim.parallelism 16 + run: cd hive && ./hive --client ethrex --sim ${{ matrix.test.simulation }} --ethrex.flags "--evm ${{ inputs.evm }}" --sim.parallelism 16 continue-on-error: true - name: Upload results From e43f8b9d68ea3254ca0ab0c920374cf0a624b2f4 Mon Sep 17 00:00:00 2001 From: fborello-lambda Date: Thu, 6 Feb 2025 12:15:12 -0300 Subject: [PATCH 16/26] pr_comments: rename evm_backends -> backends --- cmd/ethrex/cli.rs | 2 +- cmd/ethrex/ethrex.rs | 2 +- crates/blockchain/blockchain.rs | 6 +++--- crates/blockchain/payload.rs | 16 ++++++++-------- crates/l2/proposer/l1_committer.rs | 2 +- crates/vm/{evm_backends => backends}/levm.rs | 0 crates/vm/{evm_backends => backends}/mod.rs | 0 crates/vm/{evm_backends => backends}/revm.rs | 0 crates/vm/vm.rs | 6 +++--- 9 files changed, 17 insertions(+), 17 deletions(-) rename crates/vm/{evm_backends => backends}/levm.rs (100%) rename crates/vm/{evm_backends => backends}/mod.rs (100%) rename crates/vm/{evm_backends => backends}/revm.rs (100%) diff --git a/cmd/ethrex/cli.rs b/cmd/ethrex/cli.rs index 0e5438053c..ebdc9f5b50 100644 --- a/cmd/ethrex/cli.rs +++ b/cmd/ethrex/cli.rs @@ -1,6 +1,6 @@ use clap::{Arg, ArgAction, Command}; use ethrex_net::types::Node; -use ethrex_vm::evm_backends::EVM; +use ethrex_vm::backends::EVM; use tracing::Level; pub fn cli() -> Command { diff --git a/cmd/ethrex/ethrex.rs b/cmd/ethrex/ethrex.rs index 909646382d..52a2bac488 100644 --- a/cmd/ethrex/ethrex.rs +++ b/cmd/ethrex/ethrex.rs @@ -10,7 +10,7 @@ use ethrex_net::{ }; use ethrex_rlp::decode::RLPDecode; use ethrex_storage::{EngineType, Store}; -use ethrex_vm::{evm_backends::EVM, EVM_BACKEND}; +use ethrex_vm::{backends::EVM, EVM_BACKEND}; use k256::ecdsa::SigningKey; use local_ip_address::local_ip; use rand::rngs::OsRng; diff --git a/crates/blockchain/blockchain.rs b/crates/blockchain/blockchain.rs index 49c636554f..3c29446537 100644 --- a/crates/blockchain/blockchain.rs +++ b/crates/blockchain/blockchain.rs @@ -19,7 +19,7 @@ use ethrex_storage::{AccountUpdate, Store}; use ethrex_vm::db::{evm_state, EvmState}; use ethrex_vm::EVM_BACKEND; -use ethrex_vm::{evm_backends, evm_backends::EVM}; +use ethrex_vm::{backends, backends::EVM}; //TODO: Implement a struct Chain or BlockChain to encapsulate //functionality and canonical chain state and config @@ -44,10 +44,10 @@ pub fn add_block(block: &Block, storage: &Store) -> Result<(), ChainError> { validate_block(block, &parent_header, &state)?; let (receipts, account_updates): (Vec, Vec) = { match EVM_BACKEND.get() { - Some(EVM::LEVM) => evm_backends::levm::execute_block(block, &mut state)?, + Some(EVM::LEVM) => backends::levm::execute_block(block, &mut state)?, // This means we are using REVM as default for tests Some(EVM::REVM) | None => { - let receipts = evm_backends::revm::execute_block(block, &mut state)?; + let receipts = backends::revm::execute_block(block, &mut state)?; let account_updates = ethrex_vm::get_state_transitions(&mut state); (receipts, account_updates) } diff --git a/crates/blockchain/payload.rs b/crates/blockchain/payload.rs index 73ba500efb..d0a4cc15e0 100644 --- a/crates/blockchain/payload.rs +++ b/crates/blockchain/payload.rs @@ -18,8 +18,8 @@ use ethrex_core::types::{Fork, GWEI_TO_WEI}; use ethrex_levm::{db::CacheDB, vm::EVMConfig, Account, AccountInfo}; use ethrex_vm::{ db::{evm_state, EvmState, StoreWrapper}, - evm_backends, - evm_backends::EVM, + backends, + backends::EVM, get_state_transitions, spec_id, EvmError, SpecId, EVM_BACKEND, }; use std::sync::Arc; @@ -263,7 +263,7 @@ pub fn apply_withdrawals(context: &mut PayloadBuildContext) -> Result<(), EvmErr store: context.evm_state.database().unwrap().clone(), block_hash: context.payload.header.parent_hash, }); - let report = evm_backends::levm::beacon_root_contract_call_levm( + let report = backends::levm::beacon_root_contract_call_levm( store_wrapper.clone(), &context.payload.header, config, @@ -290,14 +290,14 @@ pub fn apply_withdrawals(context: &mut PayloadBuildContext) -> Result<(), EvmErr if context.payload.header.parent_beacon_block_root.is_some() && spec_id >= SpecId::CANCUN { - evm_backends::revm::beacon_root_contract_call( + backends::revm::beacon_root_contract_call( context.evm_state, &context.payload.header, spec_id, )?; } let withdrawals = context.payload.body.withdrawals.clone().unwrap_or_default(); - evm_backends::revm::process_withdrawals(context.evm_state, &withdrawals)?; + backends::revm::process_withdrawals(context.evm_state, &withdrawals)?; Ok(()) } } @@ -519,7 +519,7 @@ fn apply_plain_transaction( .unwrap_or(EVMConfig::canonical_values(fork)); let config = EVMConfig::new(fork, blob_schedule); - let report = evm_backends::levm::execute_tx_levm( + let report = backends::levm::execute_tx_levm( &head.tx, &context.payload.header, store_wrapper.clone(), @@ -552,7 +552,7 @@ fn apply_plain_transaction( } // This means we are using REVM as default for tests Some(EVM::REVM) | None => { - let report = evm_backends::revm::execute_tx( + let report = backends::revm::execute_tx( &head.tx, &context.payload.header, context.evm_state, @@ -614,7 +614,7 @@ fn finalize_payload(context: &mut PayloadBuildContext) -> Result<(), StoreError> } } - let account_updates = evm_backends::levm::get_state_transitions_levm( + let account_updates = backends::levm::get_state_transitions_levm( context.evm_state, context.parent_hash(), &context.block_cache.clone(), diff --git a/crates/l2/proposer/l1_committer.rs b/crates/l2/proposer/l1_committer.rs index 717870c39e..bd30994731 100644 --- a/crates/l2/proposer/l1_committer.rs +++ b/crates/l2/proposer/l1_committer.rs @@ -20,7 +20,7 @@ use ethrex_l2_sdk::{ eth_client::{eth_sender::Overrides, BlockByNumber, EthClient, WrappedTransaction}, }; use ethrex_storage::{error::StoreError, Store}; -use ethrex_vm::{db::evm_state, evm_backends::revm::execute_block, get_state_transitions}; +use ethrex_vm::{db::evm_state, backends::revm::execute_block, get_state_transitions}; use keccak_hash::keccak; use secp256k1::SecretKey; use std::{collections::HashMap, time::Duration}; diff --git a/crates/vm/evm_backends/levm.rs b/crates/vm/backends/levm.rs similarity index 100% rename from crates/vm/evm_backends/levm.rs rename to crates/vm/backends/levm.rs diff --git a/crates/vm/evm_backends/mod.rs b/crates/vm/backends/mod.rs similarity index 100% rename from crates/vm/evm_backends/mod.rs rename to crates/vm/backends/mod.rs diff --git a/crates/vm/evm_backends/revm.rs b/crates/vm/backends/revm.rs similarity index 100% rename from crates/vm/evm_backends/revm.rs rename to crates/vm/backends/revm.rs diff --git a/crates/vm/vm.rs b/crates/vm/vm.rs index e996de0be6..483bf61052 100644 --- a/crates/vm/vm.rs +++ b/crates/vm/vm.rs @@ -1,15 +1,15 @@ pub mod db; pub mod errors; -pub mod evm_backends; +pub mod backends; pub mod execution_db; mod execution_result; #[cfg(feature = "l2")] mod mods; use db::EvmState; -use evm_backends::EVM; +use backends::EVM; -use crate::evm_backends::revm::*; +use crate::backends::revm::*; use ethrex_core::{ types::{ tx_fields::AccessList, AccountInfo, BlockHeader, ChainConfig, Fork, GenericTransaction, From 0fa44129020bb89d70f66436f186feb124af0ed1 Mon Sep 17 00:00:00 2001 From: fborello-lambda Date: Thu, 6 Feb 2025 13:16:21 -0300 Subject: [PATCH 17/26] chore: fmt --- crates/blockchain/payload.rs | 2 +- crates/l2/proposer/l1_committer.rs | 2 +- crates/vm/vm.rs | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/crates/blockchain/payload.rs b/crates/blockchain/payload.rs index d0a4cc15e0..a91e78ad5f 100644 --- a/crates/blockchain/payload.rs +++ b/crates/blockchain/payload.rs @@ -17,9 +17,9 @@ use ethrex_core::{ use ethrex_core::types::{Fork, GWEI_TO_WEI}; use ethrex_levm::{db::CacheDB, vm::EVMConfig, Account, AccountInfo}; use ethrex_vm::{ - db::{evm_state, EvmState, StoreWrapper}, backends, backends::EVM, + db::{evm_state, EvmState, StoreWrapper}, get_state_transitions, spec_id, EvmError, SpecId, EVM_BACKEND, }; use std::sync::Arc; diff --git a/crates/l2/proposer/l1_committer.rs b/crates/l2/proposer/l1_committer.rs index bd30994731..ae39e0a16e 100644 --- a/crates/l2/proposer/l1_committer.rs +++ b/crates/l2/proposer/l1_committer.rs @@ -20,7 +20,7 @@ use ethrex_l2_sdk::{ eth_client::{eth_sender::Overrides, BlockByNumber, EthClient, WrappedTransaction}, }; use ethrex_storage::{error::StoreError, Store}; -use ethrex_vm::{db::evm_state, backends::revm::execute_block, get_state_transitions}; +use ethrex_vm::{backends::revm::execute_block, db::evm_state, get_state_transitions}; use keccak_hash::keccak; use secp256k1::SecretKey; use std::{collections::HashMap, time::Duration}; diff --git a/crates/vm/vm.rs b/crates/vm/vm.rs index 483bf61052..dafc616a08 100644 --- a/crates/vm/vm.rs +++ b/crates/vm/vm.rs @@ -1,13 +1,13 @@ +pub mod backends; pub mod db; pub mod errors; -pub mod backends; pub mod execution_db; mod execution_result; #[cfg(feature = "l2")] mod mods; -use db::EvmState; use backends::EVM; +use db::EvmState; use crate::backends::revm::*; use ethrex_core::{ From b8037382c524d3a8b6b5ab1b5d71a683ee9ccc38 Mon Sep 17 00:00:00 2001 From: fborello-lambda Date: Thu, 6 Feb 2025 13:36:08 -0300 Subject: [PATCH 18/26] momentary fix --- .github/workflows/common_hive_reports.yaml | 4 +++- Makefile | 7 ++----- 2 files changed, 5 insertions(+), 6 deletions(-) diff --git a/.github/workflows/common_hive_reports.yaml b/.github/workflows/common_hive_reports.yaml index e446d636f0..e2614f280d 100644 --- a/.github/workflows/common_hive_reports.yaml +++ b/.github/workflows/common_hive_reports.yaml @@ -58,8 +58,10 @@ jobs: - name: Setup Hive run: make setup-hive + # TODO: We have to change the command after merging it: + # cd hive && ./hive --client ethrex --sim ${{ matrix.test.simulation }} --ethrex.flags "--evm ${{ inputs.evm }}" --sim.parallelism 16 - name: Run Hive Simulation - run: cd hive && ./hive --client ethrex --sim ${{ matrix.test.simulation }} --ethrex.flags "--evm ${{ inputs.evm }}" --sim.parallelism 16 + run: cd hive && ./hive --client ethrex --sim ${{ matrix.test.simulation }} --sim.parallelism 16 continue-on-error: true - name: Upload results diff --git a/Makefile b/Makefile index f9034f7308..0263232ef8 100644 --- a/Makefile +++ b/Makefile @@ -99,15 +99,12 @@ HIVE_REVISION := b0b0f98bd24676239722e3aa7885e29ef856d804 # target revision). HIVE_SHALLOW_SINCE := 2024-09-02 QUIET ?= false -# TODO change it back when we merge the PR. -# git clone --quiet --single-branch --branch master --shallow-since=$(HIVE_SHALLOW_SINCE) https://github.com/lambdaclass/hive ; \ -# git clone --single-branch --branch master --shallow-since=$(HIVE_SHALLOW_SINCE) https://github.com/lambdaclass/hive ; \ hive: if [ "$(QUIET)" = "true" ]; then \ - git clone --quiet https://github.com/lambdaclass/hive; \ + git clone --quiet --single-branch --branch master --shallow-since=$(HIVE_SHALLOW_SINCE) https://github.com/lambdaclass/hive ; \ else \ - git clone https://github.com/lambdaclass/hive; \ + git clone --single-branch --branch master --shallow-since=$(HIVE_SHALLOW_SINCE) https://github.com/lambdaclass/hive ; \ fi setup-hive: hive ## ๐Ÿ Set up Hive testing framework From b25a6e263dad92cb651cf73c8a9cc137e683d6f0 Mon Sep 17 00:00:00 2001 From: fborello-lambda Date: Thu, 6 Feb 2025 15:15:52 -0300 Subject: [PATCH 19/26] test ci --- .github/workflows/ci_levm.yaml | 12 ++++++++++-- crates/vm/levm/Makefile | 17 +++++++++++------ 2 files changed, 21 insertions(+), 8 deletions(-) diff --git a/.github/workflows/ci_levm.yaml b/.github/workflows/ci_levm.yaml index 493237d2b0..34a9bb0b7a 100644 --- a/.github/workflows/ci_levm.yaml +++ b/.github/workflows/ci_levm.yaml @@ -87,7 +87,10 @@ jobs: name: results_levm_trigger.md - name: Rename result (1) - run: cp results.md results_levm.md + run: | + cp results.md results_levm.md + echo "RESULTS:" + cat results_levm.md - name: Download results (main) uses: actions/download-artifact@v4 @@ -97,12 +100,17 @@ jobs: continue-on-error: true - name: Rename result (2) - run: cp results.md results_levm_main.md + run: | + cp results.md results_levm_main.md + echo "RESULTS:" + cat results_levm_main.md - name: Create diff message run: | bash .github/scripts/hive_levm_revm_diff.sh results_levm_main.md results_levm.md >> diff.md cat diff.md >> $GITHUB_STEP_SUMMARY + echo "SUMMARY:" + cat diff.md - name: Check Regression run: | diff --git a/crates/vm/levm/Makefile b/crates/vm/levm/Makefile index c45630f6a8..c6e77fa730 100644 --- a/crates/vm/levm/Makefile +++ b/crates/vm/levm/Makefile @@ -136,16 +136,21 @@ build-revm-comparison: FLAGS := "--features ethrex-blockchain/levm,ethrex-vm/levm,ethrex/levm" -build-image-levm: ## ๐Ÿณ Build the Docker image - cd ../../../ && \ +STAMP_FILE := ../../../.docker_build_stamp +$(STAMP_FILE): + cd ../../../ + $(shell find crates cmd -type f -name '*.rs') Cargo.toml Dockerfile docker build -t ethrex . + touch $(STAMP_FILE) + +build-image-levm: $(STAMP_FILE) ## ๐Ÿณ Build the Docker image run-hive-levm: build-image-levm ## ๐Ÿ Run Hive with LEVM and Build report $(MAKE) -C ../../../ setup-hive - cd ../../../hive && ./hive --ethrex.flags "--evm levm" --sim ethereum/rpc-compat --client ethrex --sim.limit "$(TEST_PATTERN)" || exit 0 - cd ../../../hive && ./hive --ethrex.flags "--evm levm" --sim devp2p --client ethrex --sim.limit "$(TEST_PATTERN)" || exit 0 - cd ../../../hive && ./hive --ethrex.flags "--evm levm" --sim ethereum/engine --client ethrex --sim.limit "$(TEST_PATTERN)" || exit 0 - cd ../../../hive && ./hive --ethrex.flags "--evm levm" --sim ethereum/sync --client ethrex --sim.limit "$(TEST_PATTERN)" || exit 0 + cd ../../../hive && ./hive --ethrex.flags "--evm levm" --sim ethereum/rpc-compat --client ethrex --sim.limit "$(TEST_PATTERN)" --sim.parallelism 16 || exit 0 + cd ../../../hive && ./hive --ethrex.flags "--evm levm" --sim devp2p --client ethrex --sim.limit "$(TEST_PATTERN)" --sim.parallelism 16 || exit 0 + cd ../../../hive && ./hive --ethrex.flags "--evm levm" --sim ethereum/engine --client ethrex --sim.limit "$(TEST_PATTERN)" --sim.parallelism 16 || exit 0 + cd ../../../hive && ./hive --ethrex.flags "--evm levm" --sim ethereum/sync --client ethrex --sim.limit "$(TEST_PATTERN)" --sim.parallelism 16 || exit 0 cargo run --release -p hive_report SUBDIRS := $(shell find $(VECTORS_DIR)/GeneralStateTests -maxdepth 1 -type d ! -path "$(VECTORS_DIR)/GeneralStateTests" -exec basename {} \;) From 2f757dae5ba51beed7a859b03242eaee5264dc8b Mon Sep 17 00:00:00 2001 From: fborello-lambda Date: Thu, 6 Feb 2025 16:36:09 -0300 Subject: [PATCH 20/26] chore(hive): fix Makefile and CI --- .github/workflows/common_hive_reports.yaml | 4 +--- crates/vm/levm/Makefile | 14 +++++++------- 2 files changed, 8 insertions(+), 10 deletions(-) diff --git a/.github/workflows/common_hive_reports.yaml b/.github/workflows/common_hive_reports.yaml index e2614f280d..e446d636f0 100644 --- a/.github/workflows/common_hive_reports.yaml +++ b/.github/workflows/common_hive_reports.yaml @@ -58,10 +58,8 @@ jobs: - name: Setup Hive run: make setup-hive - # TODO: We have to change the command after merging it: - # cd hive && ./hive --client ethrex --sim ${{ matrix.test.simulation }} --ethrex.flags "--evm ${{ inputs.evm }}" --sim.parallelism 16 - name: Run Hive Simulation - run: cd hive && ./hive --client ethrex --sim ${{ matrix.test.simulation }} --sim.parallelism 16 + run: cd hive && ./hive --client ethrex --sim ${{ matrix.test.simulation }} --ethrex.flags "--evm ${{ inputs.evm }}" --sim.parallelism 16 continue-on-error: true - name: Upload results diff --git a/crates/vm/levm/Makefile b/crates/vm/levm/Makefile index c6e77fa730..14085341ed 100644 --- a/crates/vm/levm/Makefile +++ b/crates/vm/levm/Makefile @@ -134,8 +134,6 @@ build-revm-comparison: ###### Build Client with LEVM ###### -FLAGS := "--features ethrex-blockchain/levm,ethrex-vm/levm,ethrex/levm" - STAMP_FILE := ../../../.docker_build_stamp $(STAMP_FILE): cd ../../../ @@ -145,13 +143,15 @@ $(STAMP_FILE): build-image-levm: $(STAMP_FILE) ## ๐Ÿณ Build the Docker image +SIM_PARALLELISM := 48 run-hive-levm: build-image-levm ## ๐Ÿ Run Hive with LEVM and Build report $(MAKE) -C ../../../ setup-hive - cd ../../../hive && ./hive --ethrex.flags "--evm levm" --sim ethereum/rpc-compat --client ethrex --sim.limit "$(TEST_PATTERN)" --sim.parallelism 16 || exit 0 - cd ../../../hive && ./hive --ethrex.flags "--evm levm" --sim devp2p --client ethrex --sim.limit "$(TEST_PATTERN)" --sim.parallelism 16 || exit 0 - cd ../../../hive && ./hive --ethrex.flags "--evm levm" --sim ethereum/engine --client ethrex --sim.limit "$(TEST_PATTERN)" --sim.parallelism 16 || exit 0 - cd ../../../hive && ./hive --ethrex.flags "--evm levm" --sim ethereum/sync --client ethrex --sim.limit "$(TEST_PATTERN)" --sim.parallelism 16 || exit 0 - cargo run --release -p hive_report + $(MAKE) -C ../../../ clean-hive-logs + cd ../../../hive && ./hive --ethrex.flags "--evm levm" --sim ethereum/rpc-compat --client ethrex --sim.limit "$(TEST_PATTERN)" --sim.parallelism $(SIM_PARALLELISM) || exit 0 + cd ../../../hive && ./hive --ethrex.flags "--evm levm" --sim devp2p --client ethrex --sim.limit "$(TEST_PATTERN)" --sim.parallelism $(SIM_PARALLELISM) || exit 0 + cd ../../../hive && ./hive --ethrex.flags "--evm levm" --sim ethereum/engine --client ethrex --sim.limit "$(TEST_PATTERN)" --sim.parallelism $(SIM_PARALLELISM) || exit 0 + cd ../../../hive && ./hive --ethrex.flags "--evm levm" --sim ethereum/sync --client ethrex --sim.limit "$(TEST_PATTERN)" --sim.parallelism $(SIM_PARALLELISM) || exit 0 + cd ../../../ && cargo run --release -p hive_report SUBDIRS := $(shell find $(VECTORS_DIR)/GeneralStateTests -maxdepth 1 -type d ! -path "$(VECTORS_DIR)/GeneralStateTests" -exec basename {} \;) From 00d9344747afb61a1b9a0a67bafea8a42783ff49 Mon Sep 17 00:00:00 2001 From: fborello-lambda Date: Thu, 6 Feb 2025 16:51:28 -0300 Subject: [PATCH 21/26] pr_comments: consistent behavior --- cmd/ethrex/ethrex.rs | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/cmd/ethrex/ethrex.rs b/cmd/ethrex/ethrex.rs index 52a2bac488..532e0c6335 100644 --- a/cmd/ethrex/ethrex.rs +++ b/cmd/ethrex/ethrex.rs @@ -436,13 +436,10 @@ fn import_blocks(store: &Store, blocks: &Vec) { // 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(store, hash, hash, hash); } - Some(EVM::REVM) => { + // This means we are using REVM as default + Some(EVM::REVM) | None => { apply_fork_choice(store, hash, hash, hash).unwrap(); } - None => { - tracing::error!("Fatal Error, EVM_BACKEND uninitialized."); - unreachable!(); - } } } info!("Added {} blocks to blockchain", size); From e21ddabad9d9c18faea6550435312be59762ea4e Mon Sep 17 00:00:00 2001 From: fborello-lambda Date: Thu, 6 Feb 2025 17:34:26 -0300 Subject: [PATCH 22/26] chore: improve makefile --- Makefile | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/Makefile b/Makefile index 9b43553496..2939f8c7e6 100644 --- a/Makefile +++ b/Makefile @@ -66,7 +66,7 @@ stop-localnet-silent: @kurtosis enclave stop $(ENCLAVE) >/dev/null 2>&1 || true @kurtosis enclave rm $(ENCLAVE) --force >/dev/null 2>&1 || true -HIVE_REVISION := b0b0f98bd24676239722e3aa7885e29ef856d804 +HIVE_REVISION := feb4333db7fe9f6dc161326ebb11957d4306d2f9 # Shallow clones can't specify a single revision, but at least we avoid working # the whole history by making it shallow since a given date (one day before our # target revision). @@ -76,8 +76,10 @@ QUIET ?= false hive: if [ "$(QUIET)" = "true" ]; then \ git clone --quiet --single-branch --branch master --shallow-since=$(HIVE_SHALLOW_SINCE) https://github.com/lambdaclass/hive ; \ + cd hive && git checkout --quiet --detach $(HIVE_REVISION) && go build .; \ else \ git clone --single-branch --branch master --shallow-since=$(HIVE_SHALLOW_SINCE) https://github.com/lambdaclass/hive ; \ + cd hive && git checkout --detach $(HIVE_REVISION) && go build .; \ fi setup-hive: hive ## ๐Ÿ Set up Hive testing framework @@ -117,6 +119,16 @@ run-hive-debug: build-image setup-hive ## ๐Ÿž Run Hive testing suite in debug m clean-hive-logs: ## ๐Ÿงน Clean Hive logs rm -rf ./hive/workspace/logs +SIM_PARALLELISM := 48 +EVM_BACKEND := revm +# `make run-hive-report SIM_PARALLELISM=24 EVM_BACKEND="levm"` +run-hive-report: build-image setup-hive clean-hive-logs ## ๐Ÿ Run Hive and Build report + cd hive && ./hive --ethrex.flags "--evm $(EVM_BACKEND)" --sim ethereum/rpc-compat --client ethrex --sim.limit "$(TEST_PATTERN)" --sim.parallelism $(SIM_PARALLELISM) || exit 0 + cd hive && ./hive --ethrex.flags "--evm $(EVM_BACKEND)" --sim devp2p --client ethrex --sim.limit "$(TEST_PATTERN)" --sim.parallelism $(SIM_PARALLELISM) || exit 0 + cd hive && ./hive --ethrex.flags "--evm $(EVM_BACKEND)" --sim ethereum/engine --client ethrex --sim.limit "$(TEST_PATTERN)" --sim.parallelism $(SIM_PARALLELISM) || exit 0 + cd hive && ./hive --ethrex.flags "--evm $(EVM_BACKEND)" --sim ethereum/sync --client ethrex --sim.limit "$(TEST_PATTERN)" --sim.parallelism $(SIM_PARALLELISM) || exit 0 + cargo run --release -p hive_report + loc: cargo run -p loc From d813b15085ca56b0216c6f1d8301fed374cd1c12 Mon Sep 17 00:00:00 2001 From: Federico Borello <156438142+fborello-lambda@users.noreply.github.com> Date: Fri, 7 Feb 2025 16:00:10 -0300 Subject: [PATCH 23/26] Update crates/vm/backends/levm.rs Co-authored-by: Tomas Fabrizio Orsi --- crates/vm/backends/levm.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/vm/backends/levm.rs b/crates/vm/backends/levm.rs index 593fd0442a..4d4a41445a 100644 --- a/crates/vm/backends/levm.rs +++ b/crates/vm/backends/levm.rs @@ -253,7 +253,7 @@ pub fn get_state_transitions_levm( /// Calls the eip4788 beacon block root system call contract /// More info on https://eips.ethereum.org/EIPS/eip-4788 pub fn beacon_root_contract_call_levm( - store_wrapper: Arc, + db: Arc, block_header: &BlockHeader, config: EVMConfig, ) -> Result { From bfa2414ba8afed06ab14b75b76cade3087d302f3 Mon Sep 17 00:00:00 2001 From: fborello-lambda Date: Fri, 7 Feb 2025 16:10:13 -0300 Subject: [PATCH 24/26] chore: update readme --- Makefile | 4 ++-- README.md | 1 + 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/Makefile b/Makefile index 2939f8c7e6..329e598097 100644 --- a/Makefile +++ b/Makefile @@ -75,10 +75,10 @@ QUIET ?= false hive: if [ "$(QUIET)" = "true" ]; then \ - git clone --quiet --single-branch --branch master --shallow-since=$(HIVE_SHALLOW_SINCE) https://github.com/lambdaclass/hive ; \ + git clone --quiet --single-branch --branch master --shallow-since=$(HIVE_SHALLOW_SINCE) https://github.com/lambdaclass/hive && \ cd hive && git checkout --quiet --detach $(HIVE_REVISION) && go build .; \ else \ - git clone --single-branch --branch master --shallow-since=$(HIVE_SHALLOW_SINCE) https://github.com/lambdaclass/hive ; \ + git clone --single-branch --branch master --shallow-since=$(HIVE_SHALLOW_SINCE) https://github.com/lambdaclass/hive && \ cd hive && git checkout --detach $(HIVE_REVISION) && go build .; \ fi diff --git a/README.md b/README.md index 6b74c8bc50..a2aadc9b89 100644 --- a/README.md +++ b/README.md @@ -282,6 +282,7 @@ ethrex supports the following command line arguments: - `--bootnodes `: Comma separated enode URLs for P2P discovery bootstrap. - `--log.level `: The verbosity level used for logs. Default value: info. possible values: info, debug, trace, warn, error - `--syncmode `: The way in which the node will sync its state. Can be either "full" or "snap" with "snap" as default value. +- `--evm `: Has to be `levm` or `revm`. Default value: `revm`. # ethrex L2 From c0efe4eadce99a6df2d93c6b4721dbbacfd10936 Mon Sep 17 00:00:00 2001 From: fborello-lambda Date: Fri, 7 Feb 2025 16:37:33 -0300 Subject: [PATCH 25/26] chore: fix linter --- crates/vm/backends/levm.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/vm/backends/levm.rs b/crates/vm/backends/levm.rs index 4d4a41445a..1510cfe651 100644 --- a/crates/vm/backends/levm.rs +++ b/crates/vm/backends/levm.rs @@ -299,7 +299,7 @@ pub fn beacon_root_contract_call_levm( env, U256::zero(), calldata, - store_wrapper, + db, CacheDB::new(), vec![], None, From 84d95faed649066c070299dc1fbeac52dfa74af4 Mon Sep 17 00:00:00 2001 From: fborello-lambda Date: Fri, 7 Feb 2025 17:10:00 -0300 Subject: [PATCH 26/26] chore: fix tests --- crates/vm/backends/levm.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/crates/vm/backends/levm.rs b/crates/vm/backends/levm.rs index 1510cfe651..593fd0442a 100644 --- a/crates/vm/backends/levm.rs +++ b/crates/vm/backends/levm.rs @@ -253,7 +253,7 @@ pub fn get_state_transitions_levm( /// Calls the eip4788 beacon block root system call contract /// More info on https://eips.ethereum.org/EIPS/eip-4788 pub fn beacon_root_contract_call_levm( - db: Arc, + store_wrapper: Arc, block_header: &BlockHeader, config: EVMConfig, ) -> Result { @@ -299,7 +299,7 @@ pub fn beacon_root_contract_call_levm( env, U256::zero(), calldata, - db, + store_wrapper, CacheDB::new(), vec![], None,