diff --git a/crates/context/interface/src/journaled_state.rs b/crates/context/interface/src/journaled_state.rs index 0883be6b4b..7edc224ae8 100644 --- a/crates/context/interface/src/journaled_state.rs +++ b/crates/context/interface/src/journaled_state.rs @@ -7,6 +7,7 @@ use crate::{ context::{SStoreResult, SelfDestructResult}, host::LoadError, journaled_state::account::JournaledAccountTr, + ErasedError, }; use core::ops::{Deref, DerefMut}; use database_interface::Database; @@ -15,7 +16,6 @@ use primitives::{ }; use state::{Account, AccountInfo, Bytecode}; use std::{borrow::Cow, vec::Vec}; - /// Trait that contains database and journal of all changes that were made to the state. pub trait JournalTr { /// Database type that is used in the journal. @@ -304,6 +304,9 @@ pub enum JournalLoadError { ColdLoadSkipped, } +/// Journal error on loading of storage or account with Boxed Database error. +pub type JournalLoadErasedError = JournalLoadError; + impl JournalLoadError { /// Returns true if the error is a database error. #[inline] @@ -462,6 +465,7 @@ pub struct AccountInfoLoad<'a> { impl<'a> AccountInfoLoad<'a> { /// Creates new [`AccountInfoLoad`] with the given account info, cold load status and empty status. + #[inline] pub fn new(account: &'a AccountInfo, is_cold: bool, is_empty: bool) -> Self { Self { account: Cow::Borrowed(account), @@ -473,6 +477,7 @@ impl<'a> AccountInfoLoad<'a> { /// Maps the account info of the [`AccountInfoLoad`] to a new [`StateLoad`]. /// /// Useful for transforming the account info of the [`AccountInfoLoad`] and preserving the cold load status. + #[inline] pub fn into_state_load(self, f: F) -> StateLoad where F: FnOnce(Cow<'a, AccountInfo>) -> O, diff --git a/crates/context/interface/src/journaled_state/account.rs b/crates/context/interface/src/journaled_state/account.rs index 363b1feb31..ae5691f7a0 100644 --- a/crates/context/interface/src/journaled_state/account.rs +++ b/crates/context/interface/src/journaled_state/account.rs @@ -3,12 +3,19 @@ //! //! Useful to encapsulate account and journal entries together. So when account gets changed, we can add a journal entry for it. -use crate::journaled_state::entry::JournalEntry; +use crate::{ + context::{SStoreResult, StateLoad}, + journaled_state::{entry::JournalEntry, JournalLoadErasedError, JournalLoadError}, + ErasedError, +}; use super::entry::JournalEntryTr; use auto_impl::auto_impl; -use primitives::{Address, B256, KECCAK_EMPTY, U256}; -use state::{Account, Bytecode}; +use database_interface::Database; +use primitives::{ + hash_map::Entry, Address, HashMap, HashSet, StorageKey, StorageValue, B256, KECCAK_EMPTY, U256, +}; +use state::{Account, Bytecode, EvmStorageSlot}; use std::vec::Vec; /// Trait that contains database and journal of all changes that were made to the account. @@ -17,6 +24,24 @@ pub trait JournaledAccountTr { /// Returns the account. fn account(&self) -> &Account; + /// Sloads the storage slot and returns its mutable reference + fn sload( + &mut self, + key: StorageKey, + skip_cold_load: bool, + ) -> Result, JournalLoadErasedError>; + + /// Loads the storage slot and stores the new value + fn sstore( + &mut self, + key: StorageKey, + new: StorageValue, + skip_cold_load: bool, + ) -> Result, JournalLoadErasedError>; + + /// Loads the code of the account. and returns it as reference. + fn load_code(&mut self) -> Result<&Bytecode, JournalLoadErasedError>; + /// Returns the balance of the account. fn balance(&self) -> &U256; @@ -96,38 +121,166 @@ pub trait JournaledAccountTr { /// /// Useful to encapsulate account and journal entries together. So when account gets changed, we can add a journal entry for it. #[derive(Debug, PartialEq, Eq)] -pub struct JournaledAccount<'a, ENTRY: JournalEntryTr = JournalEntry> { +pub struct JournaledAccount<'a, DB, ENTRY: JournalEntryTr = JournalEntry> { /// Address of the account. address: Address, /// Mutable account. account: &'a mut Account, /// Journal entries. journal_entries: &'a mut Vec, + /// Access list. + access_list: &'a HashMap>, + /// Transaction ID. + transaction_id: usize, + /// Database used to load storage. + db: &'a mut DB, } -impl<'a, ENTRY: JournalEntryTr> JournaledAccount<'a, ENTRY> { - /// Consumes the journaled account and returns the mutable account. - #[inline] - pub fn into_account_ref(self) -> &'a Account { - self.account - } - - /// Creates a new journaled account. +impl<'a, DB: Database, ENTRY: JournalEntryTr> JournaledAccount<'a, DB, ENTRY> { + /// Creates new JournaledAccount #[inline] pub fn new( address: Address, account: &'a mut Account, journal_entries: &'a mut Vec, + db: &'a mut DB, + access_list: &'a HashMap>, + transaction_id: usize, ) -> Self { Self { address, account, journal_entries, + access_list, + transaction_id, + db, } } + + /// Loads the storage slot. + /// + /// If storage is cold and skip_cold_load is true, it will return [`JournalLoadError::ColdLoadSkipped`] error. + /// + /// Does not erase the db error. + #[inline(never)] + pub fn sload_concrete_error( + &mut self, + key: StorageKey, + skip_cold_load: bool, + ) -> Result, JournalLoadError> { + let is_newly_created = self.account.is_created(); + let (slot, is_cold) = match self.account.storage.entry(key) { + Entry::Occupied(occ) => { + let slot = occ.into_mut(); + // skip load if account is cold. + let is_cold = slot.is_cold_transaction_id(self.transaction_id); + if is_cold && skip_cold_load { + return Err(JournalLoadError::ColdLoadSkipped); + } + slot.mark_warm_with_transaction_id(self.transaction_id); + (slot, is_cold) + } + Entry::Vacant(vac) => { + // is storage cold + let is_cold = self + .access_list + .get(&self.address) + .and_then(|v| v.get(&key)) + .is_none(); + + if is_cold && skip_cold_load { + return Err(JournalLoadError::ColdLoadSkipped); + } + // if storage was cleared, we don't need to ping db. + let value = if is_newly_created { + StorageValue::ZERO + } else { + self.db.storage(self.address, key)? + }; + + let slot = vac.insert(EvmStorageSlot::new(value, self.transaction_id)); + (slot, is_cold) + } + }; + + if is_cold { + // add it to journal as cold loaded. + self.journal_entries + .push(ENTRY::storage_warmed(self.address, key)); + } + + Ok(StateLoad::new(slot, is_cold)) + } + + /// Stores the storage slot. + /// + /// If storage is cold and skip_cold_load is true, it will return [`JournalLoadError::ColdLoadSkipped`] error. + /// + /// Does not erase the db error. + #[inline] + pub fn sstore_concrete_error( + &mut self, + key: StorageKey, + new: StorageValue, + skip_cold_load: bool, + ) -> Result, JournalLoadError> { + // touch the account so changes are tracked. + self.touch(); + + // assume that acc exists and load the slot. + let slot = self.sload_concrete_error(key, skip_cold_load)?; + + let ret = Ok(StateLoad::new( + SStoreResult { + original_value: slot.original_value(), + present_value: slot.present_value(), + new_value: new, + }, + slot.is_cold, + )); + + // when new value is different from present, we need to add a journal entry and make a change. + if slot.present_value != new { + let previous_value = slot.present_value; + // insert value into present state. + slot.data.present_value = new; + + // add journal entry. + self.journal_entries + .push(ENTRY::storage_changed(self.address, key, previous_value)); + } + + ret + } + + /// Loads the code of the account. and returns it as reference. + /// + /// Does not erase the db error. + #[inline] + pub fn load_code_preserve_error(&mut self) -> Result<&Bytecode, JournalLoadError> { + if self.account.info.code.is_none() { + let hash = *self.code_hash(); + let code = if hash == KECCAK_EMPTY { + Bytecode::default() + } else { + self.db.code_by_hash(hash)? + }; + self.account.info.code = Some(code); + } + + Ok(self.account.info.code.as_ref().unwrap()) + } + + /// Consumes the journaled account and returns the account. + #[inline] + pub fn into_account(self) -> &'a Account { + self.account + } } -impl<'a, ENTRY: JournalEntryTr> JournaledAccountTr for JournaledAccount<'a, ENTRY> { +impl<'a, DB: Database, ENTRY: JournalEntryTr> JournaledAccountTr + for JournaledAccount<'a, DB, ENTRY> +{ /// Returns the account. fn account(&self) -> &Account { self.account @@ -295,4 +448,34 @@ impl<'a, ENTRY: JournalEntryTr> JournaledAccountTr for JournaledAccount<'a, ENTR self.set_code(hash, bytecode); self.bump_nonce(); } + + /// Loads the storage slot. + #[inline] + fn sload( + &mut self, + key: StorageKey, + skip_cold_load: bool, + ) -> Result, JournalLoadErasedError> { + self.sload_concrete_error(key, skip_cold_load) + .map_err(|i| i.map(ErasedError::new)) + } + + /// Stores the storage slot. + #[inline] + fn sstore( + &mut self, + key: StorageKey, + new: StorageValue, + skip_cold_load: bool, + ) -> Result, JournalLoadErasedError> { + self.sstore_concrete_error(key, new, skip_cold_load) + .map_err(|i| i.map(ErasedError::new)) + } + + /// Loads the code of the account. and returns it as reference. + #[inline] + fn load_code(&mut self) -> Result<&Bytecode, JournalLoadErasedError> { + self.load_code_preserve_error() + .map_err(|i| i.map(ErasedError::new)) + } } diff --git a/crates/context/interface/src/lib.rs b/crates/context/interface/src/lib.rs index dcb65e731d..cdf8a79c38 100644 --- a/crates/context/interface/src/lib.rs +++ b/crates/context/interface/src/lib.rs @@ -17,8 +17,7 @@ pub mod transaction; pub use block::Block; pub use cfg::{Cfg, CreateScheme, TransactTo}; pub use context::{ContextError, ContextSetters, ContextTr}; -pub use database_interface::erased_error::ErasedError; -pub use database_interface::{DBErrorMarker, Database}; +pub use database_interface::{erased_error::ErasedError, DBErrorMarker, Database}; pub use either; pub use host::{DummyHost, Host}; pub use journaled_state::JournalTr; diff --git a/crates/context/src/context.rs b/crates/context/src/context.rs index f03ac49d19..5bf1832fa3 100644 --- a/crates/context/src/context.rs +++ b/crates/context/src/context.rs @@ -8,7 +8,9 @@ use context_interface::{ }; use database_interface::{Database, DatabaseRef, EmptyDB, WrapDatabaseRef}; use derive_where::derive_where; -use primitives::{hardfork::SpecId, Address, Log, StorageKey, StorageValue, B256, U256}; +use primitives::{ + hardfork::SpecId, hints_util::cold_path, Address, Log, StorageKey, StorageValue, B256, U256, +}; /// EVM context contains data that EVM needs for execution. #[derive_where(Clone, Debug; BLOCK, CFG, CHAIN, TX, DB, JOURNAL, ::Error, LOCAL)] @@ -499,6 +501,7 @@ impl< self.db_mut() .block_hash(requested_number) .map_err(|e| { + cold_path(); *self.error() = Err(e.into()); }) .ok() @@ -532,6 +535,7 @@ impl< self.journal_mut() .selfdestruct(address, target, skip_cold_load) .map_err(|e| { + cold_path(); let (ret, err) = e.into_parts(); if let Some(err) = err { *self.error() = Err(err.into()); @@ -551,6 +555,7 @@ impl< self.journal_mut() .sstore_skip_cold_load(address, key, value, skip_cold_load) .map_err(|e| { + cold_path(); let (ret, err) = e.into_parts(); if let Some(err) = err { *self.error() = Err(err.into()); @@ -569,6 +574,7 @@ impl< self.journal_mut() .sload_skip_cold_load(address, key, skip_cold_load) .map_err(|e| { + cold_path(); let (ret, err) = e.into_parts(); if let Some(err) = err { *self.error() = Err(err.into()); @@ -584,14 +590,17 @@ impl< load_code: bool, skip_cold_load: bool, ) -> Result, LoadError> { - let error = &mut self.error; - let journal = &mut self.journaled_state; - match journal.load_account_info_skip_cold_load(address, load_code, skip_cold_load) { + match self.journaled_state.load_account_info_skip_cold_load( + address, + load_code, + skip_cold_load, + ) { Ok(a) => Ok(a), Err(e) => { + cold_path(); let (ret, err) = e.into_parts(); if let Some(err) = err { - *error = Err(err.into()); + self.error = Err(err.into()); } Err(ret) } diff --git a/crates/context/src/journal.rs b/crates/context/src/journal.rs index f9bde2ca74..8360261e70 100644 --- a/crates/context/src/journal.rs +++ b/crates/context/src/journal.rs @@ -93,9 +93,10 @@ impl JournalTr for Journal { type Database = DB; type State = EvmState; type JournaledAccount<'a> - = JournaledAccount<'a, ENTRY> + = JournaledAccount<'a, DB, ENTRY> where - Self: 'a; + ENTRY: 'a, + DB: 'a; fn new(database: DB) -> Journal { Self { @@ -118,7 +119,7 @@ impl JournalTr for Journal { key: StorageKey, ) -> Result, ::Error> { self.inner - .sload(&mut self.database, address, key, false) + .sload_assume_account_present(&mut self.database, address, key, false) .map_err(JournalLoadError::unwrap_db_error) } @@ -129,7 +130,7 @@ impl JournalTr for Journal { value: StorageValue, ) -> Result, ::Error> { self.inner - .sstore(&mut self.database, address, key, value, false) + .sstore_assume_account_present(&mut self.database, address, key, value, false) .map_err(JournalLoadError::unwrap_db_error) } @@ -341,7 +342,7 @@ impl JournalTr for Journal { ) -> Result, JournalLoadError<::Error>> { self.inner - .sload(&mut self.database, address, key, skip_cold_load) + .sload_assume_account_present(&mut self.database, address, key, skip_cold_load) } #[inline] @@ -353,8 +354,13 @@ impl JournalTr for Journal { skip_cold_load: bool, ) -> Result, JournalLoadError<::Error>> { - self.inner - .sstore(&mut self.database, address, key, value, skip_cold_load) + self.inner.sstore_assume_account_present( + &mut self.database, + address, + key, + value, + skip_cold_load, + ) } #[inline] diff --git a/crates/context/src/journal/inner.rs b/crates/context/src/journal/inner.rs index 36d1beb8db..6328b4262f 100644 --- a/crates/context/src/journal/inner.rs +++ b/crates/context/src/journal/inner.rs @@ -14,9 +14,10 @@ use database_interface::Database; use primitives::{ hardfork::SpecId::{self, *}, hash_map::Entry, + hints_util::unlikely, Address, HashMap, Log, StorageKey, StorageValue, B256, KECCAK_EMPTY, U256, }; -use state::{Account, EvmState, EvmStorageSlot, TransientStorage}; +use state::{Account, EvmState, TransientStorage}; use std::vec::Vec; /// Inner journal state that contains journal and state changes. /// @@ -565,11 +566,14 @@ impl JournalInner { /// Loads account into memory. return if it is cold or warm accessed #[inline] - pub fn load_account( - &mut self, - db: &mut DB, + pub fn load_account<'a, 'db, DB: Database>( + &'a mut self, + db: &'db mut DB, address: Address, - ) -> Result, DB::Error> { + ) -> Result, DB::Error> + where + 'db: 'a, + { self.load_account_optional(db, address, false, false) .map_err(JournalLoadError::unwrap_db_error) } @@ -621,63 +625,121 @@ impl JournalInner { /// In case of EIP-7702 delegated account will not be loaded, /// [`Self::load_account_delegated`] should be used instead. #[inline] - pub fn load_code( - &mut self, - db: &mut DB, + pub fn load_code<'a, 'db, DB: Database>( + &'a mut self, + db: &'db mut DB, address: Address, - ) -> Result, DB::Error> { + ) -> Result, DB::Error> + where + 'db: 'a, + { self.load_account_optional(db, address, true, false) .map_err(JournalLoadError::unwrap_db_error) } /// Loads account into memory. If account is already loaded it will be marked as warm. #[inline] - pub fn load_account_optional( - &mut self, - db: &mut DB, + pub fn load_account_optional<'a, 'db, DB: Database>( + &'a mut self, + db: &'db mut DB, address: Address, load_code: bool, skip_cold_load: bool, - ) -> Result, JournalLoadError> { - let load = self.load_account_mut_optional_code(db, address, load_code, skip_cold_load)?; - Ok(load.map(|i| i.into_account_ref())) + ) -> Result, JournalLoadError> + where + 'db: 'a, + { + let mut load = self.load_account_mut_optional(db, address, skip_cold_load)?; + if load_code { + load.data.load_code_preserve_error()?; + } + Ok(load.map(|i| i.into_account())) } /// Loads account into memory. If account is already loaded it will be marked as warm. #[inline] - pub fn load_account_mut( - &mut self, - db: &mut DB, + pub fn load_account_mut<'a, 'db, DB: Database>( + &'a mut self, + db: &'db mut DB, address: Address, - ) -> Result>, DB::Error> { - self.load_account_mut_optional_code(db, address, false, false) + ) -> Result>, DB::Error> + where + 'db: 'a, + { + self.load_account_mut_optional(db, address, false) .map_err(JournalLoadError::unwrap_db_error) } /// Loads account. If account is already loaded it will be marked as warm. - #[inline(never)] - pub fn load_account_mut_optional_code( - &mut self, - db: &mut DB, + #[inline] + pub fn load_account_mut_optional_code<'a, 'db, DB: Database>( + &'a mut self, + db: &'db mut DB, address: Address, load_code: bool, skip_cold_load: bool, - ) -> Result>, JournalLoadError> { - let load = match self.state.entry(address) { + ) -> Result>, JournalLoadError> + where + 'db: 'a, + { + let mut load = self.load_account_mut_optional(db, address, skip_cold_load)?; + if load_code { + load.data.load_code_preserve_error()?; + } + Ok(load) + } + + /// Gets the account mut reference. + /// + /// # Load Unsafe + /// + /// Use this function only if you know what you are doing. It will not mark the account as warm or cold. + /// It will not bump transition_id or return if it is cold or warm loaded. This function is useful + /// when we know account is warm, touched and already loaded. + /// + /// It is useful when we want to access storage from account that is currently being executed. + #[inline] + pub fn get_account_mut<'a, 'db, DB: Database>( + &'a mut self, + db: &'db mut DB, + address: Address, + ) -> Option> + where + 'db: 'a, + { + let account = self.state.get_mut(&address)?; + Some(JournaledAccount::new( + address, + account, + &mut self.journal, + db, + self.warm_addresses.access_list(), + self.transaction_id, + )) + } + + /// Loads account. If account is already loaded it will be marked as warm. + #[inline(never)] + pub fn load_account_mut_optional<'a, 'db, DB: Database>( + &'a mut self, + db: &'db mut DB, + address: Address, + skip_cold_load: bool, + ) -> Result>, JournalLoadError> + where + 'db: 'a, + { + let (account, is_cold) = match self.state.entry(address) { Entry::Occupied(entry) => { let account = entry.into_mut(); // skip load if account is cold. let mut is_cold = account.is_cold_transaction_id(self.transaction_id); - if is_cold { - // account can be loaded by we still need to check warm_addresses to see if it is cold. - let should_be_cold = self.warm_addresses.is_cold(&address); - // dont load it cold if skipping cold load is true. - if should_be_cold && skip_cold_load { - return Err(JournalLoadError::ColdLoadSkipped); - } - is_cold = should_be_cold; + if unlikely(is_cold) { + is_cold = self + .warm_addresses + .check_is_cold(&address, skip_cold_load)?; // mark it warm. account.mark_warm_with_transaction_id(self.transaction_id); @@ -690,20 +752,18 @@ impl JournalInner { } // unmark locally created account.unmark_created_locally(); + + // journal loading of cold account. + self.journal.push(ENTRY::account_warmed(address)); } - StateLoad { - data: account, - is_cold, - } + (account, is_cold) } Entry::Vacant(vac) => { - // Precompiles among some other account(coinbase included) are warm loaded so we need to take that into account - let is_cold = self.warm_addresses.is_cold(&address); - - // dont load cold account if skip_cold_load is true - if is_cold && skip_cold_load { - return Err(JournalLoadError::ColdLoadSkipped); - } + // Precompiles, among some other account(access list and coinbase included) + // are warm loaded so we need to take that into account + let is_cold = self + .warm_addresses + .check_is_cold(&address, skip_cold_load)?; let account = if let Some(account) = db.basic(address)? { let mut account: Account = account.into(); @@ -713,36 +773,29 @@ impl JournalInner { Account::new_not_existing(self.transaction_id) }; - StateLoad { - data: vac.insert(account), - is_cold, + // journal loading of cold account. + if is_cold { + self.journal.push(ENTRY::account_warmed(address)); } + + (vac.insert(account), is_cold) } }; - // journal loading of cold account. - if load.is_cold { - self.journal.push(ENTRY::account_warmed(address)); - } - - if load_code && load.data.info.code.is_none() { - let info = &mut load.data.info; - let code = if info.code_hash == KECCAK_EMPTY { - Bytecode::default() - } else { - db.code_by_hash(info.code_hash)? - }; - info.code = Some(code); - } - - Ok(load.map(|i| JournaledAccount::new(address, i, &mut self.journal))) + Ok(StateLoad::new( + JournaledAccount::new( + address, + account, + &mut self.journal, + db, + self.warm_addresses.access_list(), + self.transaction_id, + ), + is_cold, + )) } /// Loads storage slot. - /// - /// # Panics - /// - /// Panics if the account is not present in the state. #[inline] pub fn sload( &mut self, @@ -751,46 +804,45 @@ impl JournalInner { key: StorageKey, skip_cold_load: bool, ) -> Result, JournalLoadError> { - // assume acc is warm - let account = self.state.get_mut(&address).unwrap(); - - let is_newly_created = account.is_created(); - let (value, is_cold) = match account.storage.entry(key) { - Entry::Occupied(occ) => { - let slot = occ.into_mut(); - // skip load if account is cold. - let is_cold = slot.is_cold_transaction_id(self.transaction_id); - if skip_cold_load && is_cold { - return Err(JournalLoadError::ColdLoadSkipped); - } - slot.mark_warm_with_transaction_id(self.transaction_id); - (slot.present_value, is_cold) - } - Entry::Vacant(vac) => { - // is storage cold - let is_cold = !self.warm_addresses.is_storage_warm(&address, &key); - - if is_cold && skip_cold_load { - return Err(JournalLoadError::ColdLoadSkipped); - } - // if storage was cleared, we don't need to ping db. - let value = if is_newly_created { - StorageValue::ZERO - } else { - db.storage(address, key)? - }; - vac.insert(EvmStorageSlot::new(value, self.transaction_id)); + self.load_account_mut(db, address)? + .sload_concrete_error(key, skip_cold_load) + .map(|s| s.map(|s| s.present_value)) + } - (value, is_cold) - } + /// Loads storage slot. + /// + /// If account is not present it will return [`JournalLoadError::ColdLoadSkipped`] error. + #[inline] + pub fn sload_assume_account_present( + &mut self, + db: &mut DB, + address: Address, + key: StorageKey, + skip_cold_load: bool, + ) -> Result, JournalLoadError> { + let Some(mut account) = self.get_account_mut(db, address) else { + return Err(JournalLoadError::ColdLoadSkipped); }; - if is_cold { - // add it to journal as cold loaded. - self.journal.push(ENTRY::storage_warmed(address, key)); - } + account + .sload_concrete_error(key, skip_cold_load) + .map(|s| s.map(|s| s.present_value)) + } - Ok(StateLoad::new(value, is_cold)) + /// Stores storage slot. + /// + /// If account is not present it will load from database + #[inline] + pub fn sstore( + &mut self, + db: &mut DB, + address: Address, + key: StorageKey, + new: StorageValue, + skip_cold_load: bool, + ) -> Result, JournalLoadError> { + self.load_account_mut(db, address)? + .sstore_concrete_error(key, new, skip_cold_load) } /// Stores storage slot. @@ -799,7 +851,7 @@ impl JournalInner { /// /// **Note**: Account should already be present in our state. #[inline] - pub fn sstore( + pub fn sstore_assume_account_present( &mut self, db: &mut DB, address: Address, @@ -807,37 +859,11 @@ impl JournalInner { new: StorageValue, skip_cold_load: bool, ) -> Result, JournalLoadError> { - // assume that acc exists and load the slot. - let present = self.sload(db, address, key, skip_cold_load)?; - let acc = self.state.get_mut(&address).unwrap(); - - // if there is no original value in dirty return present value, that is our original. - let slot = acc.storage.get_mut(&key).unwrap(); - - // new value is same as present, we don't need to do anything - if present.data == new { - return Ok(StateLoad::new( - SStoreResult { - original_value: slot.original_value(), - present_value: present.data, - new_value: new, - }, - present.is_cold, - )); - } + let Some(mut account) = self.get_account_mut(db, address) else { + return Err(JournalLoadError::ColdLoadSkipped); + }; - self.journal - .push(ENTRY::storage_changed(address, key, present.data)); - // insert value into present state. - slot.present_value = new; - Ok(StateLoad::new( - SStoreResult { - original_value: slot.original_value(), - present_value: present.data, - new_value: new, - }, - present.is_cold, - )) + account.sstore_concrete_error(key, new, skip_cold_load) } /// Read transient storage tied to the account. @@ -928,7 +954,7 @@ mod tests { // Try to sload with skip_cold_load=true - should succeed because slot is in access list let mut db = EmptyDB::new(); - let result = journal.sload(&mut db, test_address, test_key, true); + let result = journal.sload_assume_account_present(&mut db, test_address, test_key, true); // Should succeed and return as warm assert!(result.is_ok()); diff --git a/crates/context/src/journal/warm_addresses.rs b/crates/context/src/journal/warm_addresses.rs index ac5f1a6a40..27e5bce936 100644 --- a/crates/context/src/journal/warm_addresses.rs +++ b/crates/context/src/journal/warm_addresses.rs @@ -3,6 +3,7 @@ //! It is used to optimize access to precompile addresses. use bitvec::{bitvec, vec::BitVec}; +use context_interface::journaled_state::JournalLoadError; use primitives::{short_address, Address, HashMap, HashSet, StorageKey, SHORT_ADDRESS_CAP}; /// Stores addresses that are warm loaded. Contains precompiles and coinbase address. @@ -91,6 +92,12 @@ impl WarmAddresses { self.access_list = access_list; } + /// Returns the access list. + #[inline] + pub fn access_list(&self) -> &HashMap> { + &self.access_list + } + /// Clear the coinbase address. #[inline] pub fn clear_coinbase(&mut self) { @@ -150,6 +157,22 @@ impl WarmAddresses { pub fn is_cold(&self, address: &Address) -> bool { !self.is_warm(address) } + + /// Checks if the address is cold loaded and returns an error if it is and skip_cold_load is true. + #[inline(never)] + pub fn check_is_cold( + &self, + address: &Address, + skip_cold_load: bool, + ) -> Result> { + let is_cold = self.is_cold(address); + + if is_cold && skip_cold_load { + return Err(JournalLoadError::ColdLoadSkipped); + } + + Ok(is_cold) + } } #[cfg(test)] diff --git a/crates/handler/src/frame.rs b/crates/handler/src/frame.rs index 50c2c59533..a40b1f4535 100644 --- a/crates/handler/src/frame.rs +++ b/crates/handler/src/frame.rs @@ -2,10 +2,10 @@ use crate::{ evm::FrameTr, item_or_result::FrameInitOrResult, precompile_provider::PrecompileProvider, CallFrame, CreateFrame, FrameData, FrameResult, ItemOrResult, }; -use context::{journaled_state::account::JournaledAccountTr, result::FromStringError}; +use context::result::FromStringError; use context_interface::{ context::ContextError, - journaled_state::{JournalCheckpoint, JournalTr}, + journaled_state::{account::JournaledAccountTr, JournalCheckpoint, JournalTr}, local::{FrameToken, OutFrame}, Cfg, ContextTr, Database, }; @@ -276,7 +276,8 @@ impl EthFrame { } // Fetch balance of caller. - let mut caller_info = context.journal_mut().load_account_mut(inputs.caller)?; + let journal = context.journal_mut(); + let mut caller_info = journal.load_account_mut(inputs.caller)?; // Check if caller has enough balance to send to the created contract. // decrement of balance is done in the create_account_checkpoint. @@ -304,7 +305,7 @@ impl EthFrame { drop(caller_info); // Drop caller info to avoid borrow checker issues. // warm load account. - context.journal_mut().load_account(created_address)?; + journal.load_account(created_address)?; // Create account, transfer funds and make the journal checkpoint. let checkpoint = match context.journal_mut().create_account_checkpoint( diff --git a/crates/handler/src/pre_execution.rs b/crates/handler/src/pre_execution.rs index 1112f81eb4..88dc789b83 100644 --- a/crates/handler/src/pre_execution.rs +++ b/crates/handler/src/pre_execution.rs @@ -4,9 +4,8 @@ use crate::{EvmTr, PrecompileProvider}; use bytecode::Bytecode; -use context::journaled_state::account::JournaledAccountTr; use context_interface::{ - journaled_state::JournalTr, + journaled_state::{account::JournaledAccountTr, JournalTr}, result::InvalidTransaction, transaction::{AccessListItemTr, AuthorizationTr, Transaction, TransactionType}, Block, Cfg, ContextTr, Database, diff --git a/crates/primitives/src/hints_util.rs b/crates/primitives/src/hints_util.rs new file mode 100644 index 0000000000..dc858e9d5f --- /dev/null +++ b/crates/primitives/src/hints_util.rs @@ -0,0 +1,32 @@ +//! Utility functions for hints. +//! Used from Hashbrown . + +// FIXME: Replace with `core::hint::{likely, unlikely}` once they are stable. +// pub use core::intrinsics::{likely, unlikely}; + +/// Cold path function. +#[inline(always)] +#[cold] +pub fn cold_path() {} + +/// Returns `b` but mark `false` path as cold +#[inline(always)] +pub fn likely(b: bool) -> bool { + if b { + true + } else { + cold_path(); + false + } +} + +/// Returns `b` but mark `true` path as cold +#[inline(always)] +pub fn unlikely(b: bool) -> bool { + if b { + cold_path(); + true + } else { + false + } +} diff --git a/crates/primitives/src/lib.rs b/crates/primitives/src/lib.rs index 5f1b2e112b..9193733bb2 100644 --- a/crates/primitives/src/lib.rs +++ b/crates/primitives/src/lib.rs @@ -24,6 +24,7 @@ pub mod eip7823; pub mod eip7825; pub mod eip7907; pub mod hardfork; +pub mod hints_util; mod once_lock; pub use constants::*; diff --git a/examples/cheatcode_inspector/src/main.rs b/examples/cheatcode_inspector/src/main.rs index 7142ea03f4..cbe0007aaa 100644 --- a/examples/cheatcode_inspector/src/main.rs +++ b/examples/cheatcode_inspector/src/main.rs @@ -60,10 +60,7 @@ impl Backend { impl JournalTr for Backend { type Database = InMemoryDB; type State = EvmState; - type JournaledAccount<'a> - = JournaledAccount<'a> - where - Self: 'a; + type JournaledAccount<'a> = JournaledAccount<'a, InMemoryDB, JournalEntry>; fn new(database: InMemoryDB) -> Self { Self::new(SpecId::default(), database) @@ -300,7 +297,7 @@ impl JournalTr for Backend { address: Address, load_code: bool, skip_cold_load: bool, - ) -> Result, JournalLoadError<::Error>> { + ) -> Result, JournalLoadError> { self.journaled_state .load_account_info_skip_cold_load(address, load_code, skip_cold_load) } @@ -309,7 +306,7 @@ impl JournalTr for Backend { &mut self, address: Address, load_code: bool, - ) -> Result>, ::Error> { + ) -> Result>, Infallible> { self.journaled_state .load_account_mut_optional_code(address, load_code) }