From 0e52bb4359420f7b65cd25c367c004bdaadd69c7 Mon Sep 17 00:00:00 2001 From: JereSalo Date: Thu, 9 Oct 2025 16:01:49 -0300 Subject: [PATCH 1/7] initial account status implementation --- crates/vm/levm/src/db/gen_db.rs | 29 ++++++++++---------- crates/vm/levm/src/execution_handlers.rs | 7 +++++ crates/vm/levm/src/hooks/default_hook.rs | 4 +-- crates/vm/levm/src/opcode_handlers/system.rs | 8 ++++++ 4 files changed, 31 insertions(+), 17 deletions(-) diff --git a/crates/vm/levm/src/db/gen_db.rs b/crates/vm/levm/src/db/gen_db.rs index 3afc467189..a44dcc891e 100644 --- a/crates/vm/levm/src/db/gen_db.rs +++ b/crates/vm/levm/src/db/gen_db.rs @@ -1,5 +1,4 @@ use std::collections::BTreeMap; -use std::collections::HashSet; use std::sync::Arc; use bytes::Bytes; @@ -10,6 +9,7 @@ use ethrex_common::types::Account; use ethrex_common::utils::keccak; use super::Database; +use crate::account::AccountStatus; use crate::account::LevmAccount; use crate::call_frame::CallFrameBackup; use crate::errors::InternalError; @@ -29,10 +29,6 @@ pub struct GeneralizedDatabase { pub initial_accounts_state: CacheDB, pub codes: BTreeMap, pub tx_backup: Option, - /// For keeping track of all destroyed accounts during block execution. - /// Used in get_state_transitions for edge case in which account is destroyed and re-created afterwards - /// In that scenario we want to remove the previous storage of the account but we still want the account to exist. - pub destroyed_accounts: HashSet
, } impl GeneralizedDatabase { @@ -42,7 +38,6 @@ impl GeneralizedDatabase { current_accounts_state: CacheDB::new(), initial_accounts_state: CacheDB::new(), tx_backup: None, - destroyed_accounts: HashSet::new(), codes: BTreeMap::new(), } } @@ -65,7 +60,6 @@ impl GeneralizedDatabase { current_accounts_state: levm_accounts.clone(), initial_accounts_state: levm_accounts, tx_backup: None, - destroyed_accounts: HashSet::new(), codes, } } @@ -121,11 +115,6 @@ impl GeneralizedDatabase { address: Address, key: H256, ) -> Result { - // If the account was destroyed then we cannot rely on the DB to obtain its previous value - // This is critical when executing blocks in batches, as an account may be destroyed and created within the same batch - if self.destroyed_accounts.contains(&address) { - return Ok(Default::default()); - } let value = self.store.get_storage_value(address, key)?; // Account must already be in initial_accounts_state match self.initial_accounts_state.get_mut(&address) { @@ -168,10 +157,16 @@ impl GeneralizedDatabase { "Failed to get account {address} from immutable cache", ))))?; - // Edge case: Account was destroyed and created again afterwards with CREATE2. - if self.destroyed_accounts.contains(address) && !new_state_account.is_empty() { + // Edge case: + // 1. Account was destroyed and created again afterwards (usually with CREATE2). + // 2. Account was destroyed but then was sent some balance, so it's not going to be removed completely from the trie. + // This is a way of removing storage of an account. + if (new_state_account.status == AccountStatus::DestroyedCreated + || new_state_account.status == AccountStatus::Destroyed) + && !new_state_account.is_empty() + { // Push to account updates the removal of the account and then push the new state of the account. - // This is for clearing the account's storage when it was selfdestructed in the first place. + // This is for clearing the account's storage when it was destroyed but conserve de info. account_updates.push(AccountUpdate::removed(*address)); let new_account_update = AccountUpdate { address: *address, @@ -404,6 +399,10 @@ impl<'a> VM<'a> { if let Some(value) = account.storage.get(&key) { return Ok(*value); } + // If the account was destroyed and then created then we cannot rely on the DB to obtain storage values + if account.status == AccountStatus::DestroyedCreated { + return Ok(U256::zero()); + } } else { // When requesting storage of an account we should've previously requested and cached the account return Err(InternalError::AccountNotFound); diff --git a/crates/vm/levm/src/execution_handlers.rs b/crates/vm/levm/src/execution_handlers.rs index 86864d3679..763ed23a00 100644 --- a/crates/vm/levm/src/execution_handlers.rs +++ b/crates/vm/levm/src/execution_handlers.rs @@ -1,4 +1,5 @@ use crate::{ + account::AccountStatus, constants::*, errors::{ContextResult, ExceptionalHalt, InternalError, TxResult, VMError}, gas_cost::CODE_DEPOSIT_COST, @@ -118,6 +119,12 @@ impl<'a> VM<'a> { })); } + if new_account.status == AccountStatus::Destroyed { + new_account.status = AccountStatus::DestroyedCreated + } else { + new_account.status = AccountStatus::Created + } + self.increase_account_balance(new_contract_address, self.current_call_frame.msg_value)?; self.increment_account_nonce(new_contract_address)?; diff --git a/crates/vm/levm/src/hooks/default_hook.rs b/crates/vm/levm/src/hooks/default_hook.rs index 122fd4dbcc..2839c8eeb8 100644 --- a/crates/vm/levm/src/hooks/default_hook.rs +++ b/crates/vm/levm/src/hooks/default_hook.rs @@ -1,5 +1,5 @@ use crate::{ - account::LevmAccount, + account::{AccountStatus, LevmAccount}, constants::*, errors::{ContextResult, InternalError, TxValidationError, VMError}, gas_cost::{self, STANDARD_TOKEN_COST, TOTAL_COST_FLOOR_PER_TOKEN}, @@ -234,7 +234,7 @@ pub fn delete_self_destruct_accounts(vm: &mut VM<'_>) -> Result<(), VMError> { .backup_account_info(*address, account_to_remove)?; *account_to_remove = LevmAccount::default(); - vm.db.destroyed_accounts.insert(*address); + account_to_remove.status = AccountStatus::Destroyed; } Ok(()) diff --git a/crates/vm/levm/src/opcode_handlers/system.rs b/crates/vm/levm/src/opcode_handlers/system.rs index 67a338a967..999e87eb45 100644 --- a/crates/vm/levm/src/opcode_handlers/system.rs +++ b/crates/vm/levm/src/opcode_handlers/system.rs @@ -1,4 +1,5 @@ use crate::{ + account::AccountStatus, call_frame::CallFrame, constants::{FAIL, INIT_CODE_MAX_SIZE, SUCCESS}, errors::{ContextResult, ExceptionalHalt, InternalError, OpcodeResult, TxResult, VMError}, @@ -700,6 +701,13 @@ impl<'a> VM<'a> { self.increment_account_nonce(new_address)?; // 0 -> 1 self.transfer(deployer, new_address, value)?; + let new_account = self.get_account_mut(new_address)?; + if new_account.status == AccountStatus::Destroyed { + new_account.status = AccountStatus::DestroyedCreated + } else { + new_account.status = AccountStatus::Created + } + self.substate.push_backup(); self.substate.add_created_account(new_address); // Mostly for SELFDESTRUCT during initcode. From 7b3f9c6debe653c91a47799944a43a7ca9936c8a Mon Sep 17 00:00:00 2001 From: JereSalo Date: Thu, 9 Oct 2025 16:29:20 -0300 Subject: [PATCH 2/7] make state transitions easier --- crates/vm/levm/src/account.rs | 12 ++++++++++++ crates/vm/levm/src/execution_handlers.rs | 8 +------- crates/vm/levm/src/hooks/default_hook.rs | 4 ++-- crates/vm/levm/src/opcode_handlers/system.rs | 9 +-------- 4 files changed, 16 insertions(+), 17 deletions(-) diff --git a/crates/vm/levm/src/account.rs b/crates/vm/levm/src/account.rs index 919a94f871..e15a41b66c 100644 --- a/crates/vm/levm/src/account.rs +++ b/crates/vm/levm/src/account.rs @@ -53,6 +53,18 @@ impl From for LevmAccount { } impl LevmAccount { + pub fn created(&mut self) { + if self.status == AccountStatus::Destroyed { + self.status = AccountStatus::DestroyedCreated + } else { + self.status = AccountStatus::Created + } + } + + pub fn destroyed(&mut self) { + self.status = AccountStatus::Destroyed; + } + pub fn has_nonce(&self) -> bool { self.info.nonce != 0 } diff --git a/crates/vm/levm/src/execution_handlers.rs b/crates/vm/levm/src/execution_handlers.rs index 763ed23a00..90ab305613 100644 --- a/crates/vm/levm/src/execution_handlers.rs +++ b/crates/vm/levm/src/execution_handlers.rs @@ -1,5 +1,4 @@ use crate::{ - account::AccountStatus, constants::*, errors::{ContextResult, ExceptionalHalt, InternalError, TxResult, VMError}, gas_cost::CODE_DEPOSIT_COST, @@ -119,12 +118,7 @@ impl<'a> VM<'a> { })); } - if new_account.status == AccountStatus::Destroyed { - new_account.status = AccountStatus::DestroyedCreated - } else { - new_account.status = AccountStatus::Created - } - + new_account.created(); self.increase_account_balance(new_contract_address, self.current_call_frame.msg_value)?; self.increment_account_nonce(new_contract_address)?; diff --git a/crates/vm/levm/src/hooks/default_hook.rs b/crates/vm/levm/src/hooks/default_hook.rs index 2839c8eeb8..ee3b3124fa 100644 --- a/crates/vm/levm/src/hooks/default_hook.rs +++ b/crates/vm/levm/src/hooks/default_hook.rs @@ -1,5 +1,5 @@ use crate::{ - account::{AccountStatus, LevmAccount}, + account::LevmAccount, constants::*, errors::{ContextResult, InternalError, TxValidationError, VMError}, gas_cost::{self, STANDARD_TOKEN_COST, TOTAL_COST_FLOOR_PER_TOKEN}, @@ -234,7 +234,7 @@ pub fn delete_self_destruct_accounts(vm: &mut VM<'_>) -> Result<(), VMError> { .backup_account_info(*address, account_to_remove)?; *account_to_remove = LevmAccount::default(); - account_to_remove.status = AccountStatus::Destroyed; + account_to_remove.destroyed(); } Ok(()) diff --git a/crates/vm/levm/src/opcode_handlers/system.rs b/crates/vm/levm/src/opcode_handlers/system.rs index 999e87eb45..aa4d300243 100644 --- a/crates/vm/levm/src/opcode_handlers/system.rs +++ b/crates/vm/levm/src/opcode_handlers/system.rs @@ -1,5 +1,4 @@ use crate::{ - account::AccountStatus, call_frame::CallFrame, constants::{FAIL, INIT_CODE_MAX_SIZE, SUCCESS}, errors::{ContextResult, ExceptionalHalt, InternalError, OpcodeResult, TxResult, VMError}, @@ -700,13 +699,7 @@ impl<'a> VM<'a> { // Changes that revert in case the Create fails. self.increment_account_nonce(new_address)?; // 0 -> 1 self.transfer(deployer, new_address, value)?; - - let new_account = self.get_account_mut(new_address)?; - if new_account.status == AccountStatus::Destroyed { - new_account.status = AccountStatus::DestroyedCreated - } else { - new_account.status = AccountStatus::Created - } + self.get_account_mut(new_address)?.created(); self.substate.push_backup(); self.substate.add_created_account(new_address); // Mostly for SELFDESTRUCT during initcode. From fb835f3510e8f0117f8830bdf6cf025d024f9378 Mon Sep 17 00:00:00 2001 From: JereSalo Date: Thu, 9 Oct 2025 17:14:15 -0300 Subject: [PATCH 3/7] add modified state --- crates/vm/levm/src/account.rs | 13 ++++++++----- crates/vm/levm/src/db/gen_db.rs | 8 +++++++- 2 files changed, 15 insertions(+), 6 deletions(-) diff --git a/crates/vm/levm/src/account.rs b/crates/vm/levm/src/account.rs index e15a41b66c..12861bbaf6 100644 --- a/crates/vm/levm/src/account.rs +++ b/crates/vm/levm/src/account.rs @@ -65,6 +65,12 @@ impl LevmAccount { self.status = AccountStatus::Destroyed; } + pub fn mutated(&mut self) { + if self.status == AccountStatus::Unmodified { + self.status = AccountStatus::Modified; + } + } + pub fn has_nonce(&self) -> bool { self.info.nonce != 0 } @@ -81,11 +87,6 @@ impl LevmAccount { self.info.is_empty() } - /// Updates the account status. - pub fn update_status(&mut self, status: AccountStatus) { - self.status = status; - } - /// Checks if the account is unmodified. pub fn is_unmodified(&self) -> bool { matches!(self.status, AccountStatus::Unmodified) @@ -95,7 +96,9 @@ impl LevmAccount { #[derive(Clone, Default, Debug, PartialEq, Eq, Serialize, Deserialize)] pub enum AccountStatus { #[default] + /// Account was only read and not mutated at all. Unmodified, + /// Account accessed mutably, doesn't necessarily mean that its state has changed though but it could Modified, /// Contract executed a SELFDESTRUCT Destroyed, diff --git a/crates/vm/levm/src/db/gen_db.rs b/crates/vm/levm/src/db/gen_db.rs index a44dcc891e..b8be0ab77b 100644 --- a/crates/vm/levm/src/db/gen_db.rs +++ b/crates/vm/levm/src/db/gen_db.rs @@ -87,7 +87,9 @@ impl GeneralizedDatabase { /// Gets mutable reference of an account /// Warning: Use directly only if outside of the EVM, otherwise use `vm.get_account_mut` because it contemplates call frame backups. pub fn get_account_mut(&mut self, address: Address) -> Result<&mut LevmAccount, InternalError> { - self.load_account(address) + let acc = self.load_account(address)?; + acc.mutated(); + Ok(acc) } /// Gets code immutably given the code hash. @@ -149,6 +151,10 @@ impl GeneralizedDatabase { pub fn get_state_transitions(&mut self) -> Result, VMError> { let mut account_updates: Vec = vec![]; for (address, new_state_account) in self.current_accounts_state.iter() { + if new_state_account.is_unmodified() { + // Skip processing account that we know wasn't mutably accessed during execution + continue; + } // In case the account is not in immutable_cache (rare) we search for it in the actual database. let initial_state_account = self.initial_accounts_state From 8beff5223192277780682185b3016f82a698e1ce Mon Sep 17 00:00:00 2001 From: JereSalo Date: Thu, 9 Oct 2025 17:45:00 -0300 Subject: [PATCH 4/7] improve AccountStatus --- crates/vm/levm/src/account.rs | 19 ++++++------------- crates/vm/levm/src/db/gen_db.rs | 6 +++--- crates/vm/levm/src/execution_handlers.rs | 1 - crates/vm/levm/src/opcode_handlers/system.rs | 1 - 4 files changed, 9 insertions(+), 18 deletions(-) diff --git a/crates/vm/levm/src/account.rs b/crates/vm/levm/src/account.rs index 12861bbaf6..9e45241aca 100644 --- a/crates/vm/levm/src/account.rs +++ b/crates/vm/levm/src/account.rs @@ -53,22 +53,17 @@ impl From for LevmAccount { } impl LevmAccount { - pub fn created(&mut self) { - if self.status == AccountStatus::Destroyed { - self.status = AccountStatus::DestroyedCreated - } else { - self.status = AccountStatus::Created - } - } - pub fn destroyed(&mut self) { self.status = AccountStatus::Destroyed; } - pub fn mutated(&mut self) { + pub fn modified(&mut self) { if self.status == AccountStatus::Unmodified { self.status = AccountStatus::Modified; } + if self.status == AccountStatus::Destroyed { + self.status = AccountStatus::DestroyedModified; + } } pub fn has_nonce(&self) -> bool { @@ -102,9 +97,7 @@ pub enum AccountStatus { Modified, /// Contract executed a SELFDESTRUCT Destroyed, - /// Contract created via external transaction or CREATE/CREATE2 - Created, - /// Contract has been destroyed and then re-created, usually with CREATE2 + /// Contract has been destroyed and then modified /// This is a particular state because we'll still have in the Database the storage (trie) values but they are actually invalid. - DestroyedCreated, + DestroyedModified, } diff --git a/crates/vm/levm/src/db/gen_db.rs b/crates/vm/levm/src/db/gen_db.rs index b8be0ab77b..e61f21091f 100644 --- a/crates/vm/levm/src/db/gen_db.rs +++ b/crates/vm/levm/src/db/gen_db.rs @@ -88,7 +88,7 @@ impl GeneralizedDatabase { /// Warning: Use directly only if outside of the EVM, otherwise use `vm.get_account_mut` because it contemplates call frame backups. pub fn get_account_mut(&mut self, address: Address) -> Result<&mut LevmAccount, InternalError> { let acc = self.load_account(address)?; - acc.mutated(); + acc.modified(); Ok(acc) } @@ -167,7 +167,7 @@ impl GeneralizedDatabase { // 1. Account was destroyed and created again afterwards (usually with CREATE2). // 2. Account was destroyed but then was sent some balance, so it's not going to be removed completely from the trie. // This is a way of removing storage of an account. - if (new_state_account.status == AccountStatus::DestroyedCreated + if (new_state_account.status == AccountStatus::DestroyedModified || new_state_account.status == AccountStatus::Destroyed) && !new_state_account.is_empty() { @@ -406,7 +406,7 @@ impl<'a> VM<'a> { return Ok(*value); } // If the account was destroyed and then created then we cannot rely on the DB to obtain storage values - if account.status == AccountStatus::DestroyedCreated { + if account.status == AccountStatus::DestroyedModified { return Ok(U256::zero()); } } else { diff --git a/crates/vm/levm/src/execution_handlers.rs b/crates/vm/levm/src/execution_handlers.rs index 90ab305613..86864d3679 100644 --- a/crates/vm/levm/src/execution_handlers.rs +++ b/crates/vm/levm/src/execution_handlers.rs @@ -118,7 +118,6 @@ impl<'a> VM<'a> { })); } - new_account.created(); self.increase_account_balance(new_contract_address, self.current_call_frame.msg_value)?; self.increment_account_nonce(new_contract_address)?; diff --git a/crates/vm/levm/src/opcode_handlers/system.rs b/crates/vm/levm/src/opcode_handlers/system.rs index aa4d300243..67a338a967 100644 --- a/crates/vm/levm/src/opcode_handlers/system.rs +++ b/crates/vm/levm/src/opcode_handlers/system.rs @@ -699,7 +699,6 @@ impl<'a> VM<'a> { // Changes that revert in case the Create fails. self.increment_account_nonce(new_address)?; // 0 -> 1 self.transfer(deployer, new_address, value)?; - self.get_account_mut(new_address)?.created(); self.substate.push_backup(); self.substate.add_created_account(new_address); // Mostly for SELFDESTRUCT during initcode. From 1be67f9d47ba486589e464adc2d34f3e20bac1a9 Mon Sep 17 00:00:00 2001 From: JereSalo Date: Thu, 9 Oct 2025 17:46:10 -0300 Subject: [PATCH 5/7] improve naming --- crates/vm/levm/src/account.rs | 4 ++-- crates/vm/levm/src/db/gen_db.rs | 2 +- crates/vm/levm/src/hooks/default_hook.rs | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/crates/vm/levm/src/account.rs b/crates/vm/levm/src/account.rs index 9e45241aca..65d1988ec7 100644 --- a/crates/vm/levm/src/account.rs +++ b/crates/vm/levm/src/account.rs @@ -53,11 +53,11 @@ impl From for LevmAccount { } impl LevmAccount { - pub fn destroyed(&mut self) { + pub fn mark_destroyed(&mut self) { self.status = AccountStatus::Destroyed; } - pub fn modified(&mut self) { + pub fn mark_modified(&mut self) { if self.status == AccountStatus::Unmodified { self.status = AccountStatus::Modified; } diff --git a/crates/vm/levm/src/db/gen_db.rs b/crates/vm/levm/src/db/gen_db.rs index e61f21091f..0bb0b8cd6c 100644 --- a/crates/vm/levm/src/db/gen_db.rs +++ b/crates/vm/levm/src/db/gen_db.rs @@ -88,7 +88,7 @@ impl GeneralizedDatabase { /// Warning: Use directly only if outside of the EVM, otherwise use `vm.get_account_mut` because it contemplates call frame backups. pub fn get_account_mut(&mut self, address: Address) -> Result<&mut LevmAccount, InternalError> { let acc = self.load_account(address)?; - acc.modified(); + acc.mark_modified(); Ok(acc) } diff --git a/crates/vm/levm/src/hooks/default_hook.rs b/crates/vm/levm/src/hooks/default_hook.rs index ee3b3124fa..71f5631a26 100644 --- a/crates/vm/levm/src/hooks/default_hook.rs +++ b/crates/vm/levm/src/hooks/default_hook.rs @@ -234,7 +234,7 @@ pub fn delete_self_destruct_accounts(vm: &mut VM<'_>) -> Result<(), VMError> { .backup_account_info(*address, account_to_remove)?; *account_to_remove = LevmAccount::default(); - account_to_remove.destroyed(); + account_to_remove.mark_destroyed(); } Ok(()) From 9ebd58f2694d7117206aeae65cbd03e10792f818 Mon Sep 17 00:00:00 2001 From: JereSalo Date: Thu, 9 Oct 2025 17:50:01 -0300 Subject: [PATCH 6/7] Improve get state transititons --- crates/vm/levm/src/db/gen_db.rs | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/crates/vm/levm/src/db/gen_db.rs b/crates/vm/levm/src/db/gen_db.rs index 0bb0b8cd6c..9e10c5efab 100644 --- a/crates/vm/levm/src/db/gen_db.rs +++ b/crates/vm/levm/src/db/gen_db.rs @@ -163,16 +163,15 @@ impl GeneralizedDatabase { "Failed to get account {address} from immutable cache", ))))?; - // Edge case: - // 1. Account was destroyed and created again afterwards (usually with CREATE2). - // 2. Account was destroyed but then was sent some balance, so it's not going to be removed completely from the trie. - // This is a way of removing storage of an account. + // Edge cases: + // 1. Account was destroyed and created again afterwards. + // 2. Account was destroyed but then was sent ETH, so it's not going to be removed completely from the trie. + // This is a way of removing the storage of an account but keeping the info. if (new_state_account.status == AccountStatus::DestroyedModified || new_state_account.status == AccountStatus::Destroyed) && !new_state_account.is_empty() { // Push to account updates the removal of the account and then push the new state of the account. - // This is for clearing the account's storage when it was destroyed but conserve de info. account_updates.push(AccountUpdate::removed(*address)); let new_account_update = AccountUpdate { address: *address, From eda7ebfda5dc82a2ca0e9f10da053fe9b03d992e Mon Sep 17 00:00:00 2001 From: JereSalo Date: Thu, 9 Oct 2025 18:06:10 -0300 Subject: [PATCH 7/7] remove unnecessary check --- crates/vm/levm/src/db/gen_db.rs | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/crates/vm/levm/src/db/gen_db.rs b/crates/vm/levm/src/db/gen_db.rs index 9e10c5efab..6116e1b8a7 100644 --- a/crates/vm/levm/src/db/gen_db.rs +++ b/crates/vm/levm/src/db/gen_db.rs @@ -167,10 +167,7 @@ impl GeneralizedDatabase { // 1. Account was destroyed and created again afterwards. // 2. Account was destroyed but then was sent ETH, so it's not going to be removed completely from the trie. // This is a way of removing the storage of an account but keeping the info. - if (new_state_account.status == AccountStatus::DestroyedModified - || new_state_account.status == AccountStatus::Destroyed) - && !new_state_account.is_empty() - { + if new_state_account.status == AccountStatus::DestroyedModified { // Push to account updates the removal of the account and then push the new state of the account. account_updates.push(AccountUpdate::removed(*address)); let new_account_update = AccountUpdate {