diff --git a/Cargo.lock b/Cargo.lock index af49c63e2..cacf5aeec 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3688,6 +3688,15 @@ version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6fe2267d4ed49bc07b63801559be28c718ea06c4738b7a03c94df7386d2cde46" +[[package]] +name = "hive_comparison" +version = "0.1.0" +dependencies = [ + "hive_report", + "serde", + "serde_json", +] + [[package]] name = "hive_report" version = "0.1.0" diff --git a/Cargo.toml b/Cargo.toml index 68c83cbfa..9ea0cf324 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -14,6 +14,7 @@ members = [ "cmd/ef_tests/levm", "cmd/ethrex_l2", "cmd/hive_report", + "cmd/hive_comparison", "crates/vm/levm", "crates/vm/levm/bench/revm_comparison", "crates/l2/", diff --git a/cmd/hive_comparison/Cargo.toml b/cmd/hive_comparison/Cargo.toml new file mode 100644 index 000000000..5e3c8cabd --- /dev/null +++ b/cmd/hive_comparison/Cargo.toml @@ -0,0 +1,9 @@ +[package] +name = "hive_comparison" +version.workspace = true +edition.workspace = true + +[dependencies] +serde_json.workspace = true +serde.workspace = true +hive_report = { path = "../hive_report" } diff --git a/cmd/hive_comparison/src/main.rs b/cmd/hive_comparison/src/main.rs new file mode 100644 index 000000000..b23438b08 --- /dev/null +++ b/cmd/hive_comparison/src/main.rs @@ -0,0 +1,86 @@ +use std::fs::{self, File}; +use std::io::BufReader; + +use hive_report::{HiveResult, JsonFile}; + +fn main() -> Result<(), Box> { + // 1. Clear logs, build image with LEVM and run + // 2. Store results_by_category in results_levm variable + // 1. Clear logs, build image with REVM and run + // 4. Store results_by_category in results_revm variable + // 5. Compare results_levm with results_revm. (They should have the same tests ran) + // For now we can just compare the amount of test passed on each category and see if it is the same. + + // Warning: The code down below is copy-pasted just for testing purposes, progress has not been made yet :) + + let mut results = Vec::new(); + + for entry in fs::read_dir("hive/workspace/logs")? { + let entry = entry?; + let path = entry.path(); + + if path.is_file() + && path.extension().and_then(|s| s.to_str()) == Some("json") + && path.file_name().and_then(|s| s.to_str()) != Some("hive.json") + { + let file_name = path + .file_name() + .and_then(|s| s.to_str()) + .expect("Path should be a valid string"); + let file = File::open(&path)?; + let reader = BufReader::new(file); + + let json_data: JsonFile = match serde_json::from_reader(reader) { + Ok(data) => data, + Err(_) => { + eprintln!("Error processing file: {}", file_name); + continue; + } + }; + + let total_tests = json_data.test_cases.len(); + let passed_tests = json_data + .test_cases + .values() + .filter(|test_case| test_case.summary_result.pass) + .count(); + + let result = HiveResult::new(json_data.name, passed_tests, total_tests); + if !result.should_skip() { + results.push(result); + } + } + } + + // First by category ascending, then by passed tests descending, then by success percentage descending. + results.sort_by(|a, b| { + a.category + .cmp(&b.category) + .then_with(|| b.passed_tests.cmp(&a.passed_tests)) + .then_with(|| { + b.success_percentage + .partial_cmp(&a.success_percentage) + .unwrap() + }) + }); + + dbg!(&results); + let results_by_category = results.chunk_by(|a, b| a.category == b.category); + + dbg!(&results_by_category); + + // for results in results_by_category { + // // print category + // println!("*{}*", results[0].category); + // for result in results { + // println!("\t{}", result); + // } + // println!(); + // } + + Ok(()) +} + +// fn generate_results_by_category() { + +// } diff --git a/cmd/hive_report/src/lib.rs b/cmd/hive_report/src/lib.rs new file mode 100644 index 000000000..b3fe5ae68 --- /dev/null +++ b/cmd/hive_report/src/lib.rs @@ -0,0 +1,74 @@ +use serde::Deserialize; + +#[derive(Debug, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct TestCase { + pub summary_result: SummaryResult, +} + +#[derive(Debug, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct SummaryResult { + pub pass: bool, +} + +#[derive(Debug, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct JsonFile { + pub name: String, + pub test_cases: std::collections::HashMap, +} + +#[derive(Debug)] +pub struct HiveResult { + pub category: String, + pub display_name: String, + pub passed_tests: usize, + pub total_tests: usize, + pub success_percentage: f64, +} + +impl HiveResult { + pub fn new(suite: String, passed_tests: usize, total_tests: usize) -> Self { + let success_percentage = (passed_tests as f64 / total_tests as f64) * 100.0; + + let (category, display_name) = match suite.as_str() { + "engine-api" => ("Engine", "Paris"), + "engine-auth" => ("Engine", "Auth"), + "engine-cancun" => ("Engine", "Cancun"), + "engine-exchange-capabilities" => ("Engine", "Exchange Capabilities"), + "engine-withdrawals" => ("Engine", "Shanghai"), + "discv4" => ("P2P", "Discovery V4"), + "eth" => ("P2P", "Eth capability"), + "snap" => ("P2P", "Snap capability"), + "rpc-compat" => ("RPC", "RPC API Compatibility"), + "sync" => ("Sync", "Node Syncing"), + other => { + eprintln!("Warn: Unknown suite: {}. Skipping", other); + ("", "") + } + }; + + HiveResult { + category: category.to_string(), + display_name: display_name.to_string(), + passed_tests, + total_tests, + success_percentage, + } + } + + pub fn should_skip(&self) -> bool { + self.category.is_empty() + } +} + +impl std::fmt::Display for HiveResult { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!( + f, + "{}: {}/{} ({:.02}%)", + self.display_name, self.passed_tests, self.total_tests, self.success_percentage + ) + } +} diff --git a/cmd/hive_report/src/main.rs b/cmd/hive_report/src/main.rs index d45747cd8..53f642e66 100644 --- a/cmd/hive_report/src/main.rs +++ b/cmd/hive_report/src/main.rs @@ -1,78 +1,7 @@ -use serde::Deserialize; use std::fs::{self, File}; use std::io::BufReader; -#[derive(Debug, Deserialize)] -#[serde(rename_all = "camelCase")] -struct TestCase { - summary_result: SummaryResult, -} - -#[derive(Debug, Deserialize)] -#[serde(rename_all = "camelCase")] -struct SummaryResult { - pass: bool, -} - -#[derive(Debug, Deserialize)] -#[serde(rename_all = "camelCase")] -struct JsonFile { - name: String, - test_cases: std::collections::HashMap, -} - -struct HiveResult { - category: String, - display_name: String, - passed_tests: usize, - total_tests: usize, - success_percentage: f64, -} - -impl HiveResult { - fn new(suite: String, passed_tests: usize, total_tests: usize) -> Self { - let success_percentage = (passed_tests as f64 / total_tests as f64) * 100.0; - - let (category, display_name) = match suite.as_str() { - "engine-api" => ("Engine", "Paris"), - "engine-auth" => ("Engine", "Auth"), - "engine-cancun" => ("Engine", "Cancun"), - "engine-exchange-capabilities" => ("Engine", "Exchange Capabilities"), - "engine-withdrawals" => ("Engine", "Shanghai"), - "discv4" => ("P2P", "Discovery V4"), - "eth" => ("P2P", "Eth capability"), - "snap" => ("P2P", "Snap capability"), - "rpc-compat" => ("RPC", "RPC API Compatibility"), - "sync" => ("Sync", "Node Syncing"), - other => { - eprintln!("Warn: Unknown suite: {}. Skipping", other); - ("", "") - } - }; - - HiveResult { - category: category.to_string(), - display_name: display_name.to_string(), - passed_tests, - total_tests, - success_percentage, - } - } - - fn should_skip(&self) -> bool { - self.category.is_empty() - } -} - -impl std::fmt::Display for HiveResult { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - write!( - f, - "{}: {}/{} ({:.02}%)", - self.display_name, self.passed_tests, self.total_tests, self.success_percentage - ) - } -} +use hive_report::{HiveResult, JsonFile}; fn main() -> Result<(), Box> { let mut results = Vec::new(); diff --git a/crates/blockchain/blockchain.rs b/crates/blockchain/blockchain.rs index 7b162d3b5..30d2ee02b 100644 --- a/crates/blockchain/blockchain.rs +++ b/crates/blockchain/blockchain.rs @@ -15,8 +15,8 @@ use ethrex_core::types::{ use ethrex_core::H256; use ethrex_storage::error::StoreError; -use ethrex_storage::{AccountUpdate, Store}; -use ethrex_vm::{evm_state, execute_block, spec_id, EvmState, SpecId}; +use ethrex_storage::Store; +use ethrex_vm::{execute_block, spec_id, SpecId}; //TODO: Implement a struct Chain or BlockChain to encapsulate //functionality and canonical chain state and config @@ -35,30 +35,15 @@ pub fn add_block(block: &Block, storage: &Store) -> Result<(), ChainError> { storage.add_pending_block(block.clone())?; return Err(ChainError::ParentNotFound); }; - let mut state = evm_state(storage.clone(), block.header.parent_hash); - // 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")] - { - execute_block(block, &mut state)? - } - #[cfg(not(feature = "levm"))] - { - let receipts = execute_block(block, &mut state)?; - let account_updates = ethrex_vm::get_state_transitions(&mut state); - (receipts, account_updates) - } - }; + validate_block(block, &parent_header, storage)?; + + let (receipts, account_updates) = execute_block(block, storage)?; validate_gas_used(&receipts, &block.header)?; // Apply the account updates over the last block's state and compute the new state root - let new_state_root = state - .database() - .ok_or(ChainError::StoreError(StoreError::MissingStore))? + let new_state_root = storage .apply_account_updates(block.header.parent_hash, &account_updates)? .ok_or(ChainError::ParentStateNotFound)?; @@ -149,10 +134,10 @@ pub fn find_parent_header( pub fn validate_block( block: &Block, parent_header: &BlockHeader, - state: &EvmState, + store: &Store, ) -> Result<(), ChainError> { let spec = spec_id( - &state.chain_config().map_err(ChainError::from)?, + &store.get_chain_config().map_err(ChainError::from)?, block.header.timestamp, ); diff --git a/crates/l2/proposer/l1_committer.rs b/crates/l2/proposer/l1_committer.rs index 0c1536d09..05218977f 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::execute_block; use keccak_hash::keccak; use secp256k1::SecretKey; use std::{collections::HashMap, time::Duration}; @@ -242,15 +242,13 @@ impl Committer { ) -> Result { info!("Preparing state diff for block {}", block.header.number); - let mut state = evm_state(store.clone(), block.header.parent_hash); - execute_block(block, &mut state).map_err(CommitterError::from)?; - let account_updates = get_state_transitions(&mut state); + let account_updates = execute_block(block, &store) + .map_err(CommitterError::from)? + .1; let mut modified_accounts = HashMap::new(); for account_update in &account_updates { - let prev_nonce = match state - .database() - .ok_or(CommitterError::FailedToRetrieveDataFromStorage)? + let prev_nonce = match store // If we want the state_diff of a batch, we will have to change the -1 with the `batch_size` // and we may have to keep track of the latestCommittedBlock (last block of the batch), // the batch_size and the latestCommittedBatch in the contract. diff --git a/crates/vm/execution_db.rs b/crates/vm/execution_db.rs index 6eba56378..3440d3fcb 100644 --- a/crates/vm/execution_db.rs +++ b/crates/vm/execution_db.rs @@ -17,7 +17,7 @@ use revm::{ }; use serde::{Deserialize, Serialize}; -use crate::{errors::ExecutionDBError, evm_state, execute_block, get_state_transitions}; +use crate::{errors::ExecutionDBError, execute_block}; /// In-memory EVM database for caching execution data. /// @@ -49,10 +49,8 @@ impl ExecutionDB { // TODO: perform validation to exit early // Execute and obtain account updates - let mut state = evm_state(store.clone(), block.header.parent_hash); let chain_config = store.get_chain_config()?; - execute_block(block, &mut state).map_err(Box::new)?; - let account_updates = get_state_transitions(&mut state); + let account_updates = execute_block(block, store).map_err(Box::new)?.1; // Store data touched by updates and get all touched storage keys for each account let mut accounts = HashMap::new(); diff --git a/crates/vm/vm.rs b/crates/vm/vm.rs index b9ad2f08e..e6d8c55b1 100644 --- a/crates/vm/vm.rs +++ b/crates/vm/vm.rs @@ -89,22 +89,79 @@ cfg_if::cfg_if! { use std::{collections::HashMap, sync::Arc}; use ethrex_core::types::code_hash; + pub fn beacon_root_contract_call_levm( + store_wrapper: Arc, + block_header: &BlockHeader, + spec_id: SpecId, + ) -> 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(), + spec_id, + ..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![], + ) + .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, + initial_state: &Store, 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 + let initial_account_state = initial_state .get_account_info_by_hash(block_hash, *new_state_account_address) .expect("Error getting account info by address") .unwrap_or_default(); @@ -128,7 +185,7 @@ cfg_if::cfg_if! { 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. + let original_value = initial_state.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); @@ -145,33 +202,31 @@ cfg_if::cfg_if! { account_updates } - /// Executes all transactions in a block and returns their receipts. + /// Executes all transactions in a block and returns their receipts and account updates. pub fn execute_block( block: &Block, - state: &mut EvmState, + store: &Store, ) -> Result<(Vec, Vec), EvmError> { + let store_wrapper = Arc::new(StoreWrapper { + store: store.clone(), + block_hash: block.header.parent_hash, + }); + let mut block_cache: CacheDB = HashMap::new(); + let block_header = &block.header; - let spec_id = spec_id(&state.chain_config()?, block_header.timestamp); + let spec_id = spec_id(&store.get_chain_config()?, 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() && spec_id == SpecId::CANCUN { - beacon_root_contract_call(state, block_header, spec_id)?; + let report = beacon_root_contract_call_levm(store_wrapper.clone(), block_header, spec_id)?; + block_cache.extend(report.new_state); } } } - let store_wrapper = Arc::new(StoreWrapper { - store: state.database().unwrap().clone(), - block_hash: block.header.parent_hash, - }); - - // 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; - let mut block_cache: CacheDB = HashMap::new(); for tx in block.body.transactions.iter() { let report = execute_tx_levm(tx, block_header, store_wrapper.clone(), block_cache.clone(), spec_id).map_err(EvmError::from)?; @@ -217,7 +272,7 @@ cfg_if::cfg_if! { } } - account_updates.extend(get_state_transitions_levm(state, block.header.parent_hash, &block_cache)); + let account_updates = get_state_transitions_levm(store, block.header.parent_hash, &block_cache); Ok((receipts, account_updates)) } @@ -266,8 +321,10 @@ cfg_if::cfg_if! { vm.transact() } } 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> { + /// Executes all transactions in a block and returns their receipts and account updates. + pub fn execute_block(block: &Block, store: &Store) -> Result<(Vec, Vec), EvmError> { + let mut state = evm_state(store.clone(), block.header.parent_hash); + 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 @@ -275,7 +332,7 @@ 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)?; + beacon_root_contract_call(&mut state, block_header, spec_id)?; } } } @@ -283,7 +340,7 @@ cfg_if::cfg_if! { let mut cumulative_gas_used = 0; for transaction in block.body.transactions.iter() { - let result = execute_tx(transaction, block_header, state, spec_id)?; + let result = execute_tx(transaction, block_header, &mut state, spec_id)?; cumulative_gas_used += result.gas_used(); let receipt = Receipt::new( transaction.tx_type(), @@ -295,10 +352,12 @@ cfg_if::cfg_if! { } if let Some(withdrawals) = &block.body.withdrawals { - process_withdrawals(state, withdrawals)?; + process_withdrawals(&mut state, withdrawals)?; } - Ok(receipts) + let account_updates = self::get_state_transitions(&mut state); + + Ok((receipts, account_updates)) } } }