From ef57a46824d52d2e334e5bd4126b7b17a9cc5c8a Mon Sep 17 00:00:00 2001 From: rakita Date: Wed, 9 Aug 2023 00:45:50 +0200 Subject: [PATCH] feat: State with account status (#499) * wip separate initial checks * tests passing, consolidate some checks * test * feat: add transfer test * temp * Update crates/interpreter/src/gas/calc.rs * / Initial gas that is deducted from the transaction gas limit. * fmt * account states * temp * Global account BlockState. wip * few comments * wip * Tests working * wip changes * work on update and revert * AccountState as a simple enum flag * Add storage for EmptyEip161 account * split state to component files * continue spliting, wip State * feat: Add initcode limit to be double of bytecode limit (#491) * bigger cleanup, structure it * wip reverts and the flow * increments balance and some wip on reverts * feat: Run CI on release branches (#506) * feat: Add initcode limit to be double of bytecode limit (#503) * chore: Move Precompiles to EVMData so Inspector can access it (#504) * fix previous commit (#507) * wip cache account, changeset and revert * plain state wip, reverts compiles now * working changes * working and, added timestamps and println * Add reverts * clippy happy, some cleanup, work on reverts * wip on spliting and creating BundleState * needed things for integration * Few utilities, emptydb with typed error * wip * fix some things * cleanup * refactoring * clippy and some cleanup * add state clear flag * bugs fixes * add state builder * some cleanup and being more strict * cleanup, and simplification * check nonce for account * make storage was_destroyed * dont store storage when not needed * dont ask db for destroyed accounts * debug logs * test account * more debug * save previous state on double touch * check if pre eip161 touched account is already empty * Changr address * diferent debug account * set wipe flag for destroyed * remove some test clones * nit * clippy * make state behind std * lto * cleanup * set bigger stack size in tests * fmt --- Cargo.lock | 12 + bins/revme/src/statetest/merkle_trie.rs | 11 +- bins/revme/src/statetest/models/mod.rs | 4 +- bins/revme/src/statetest/runner.rs | 95 ++-- crates/interpreter/src/instructions/host.rs | 4 +- .../interpreter/src/interpreter/analysis.rs | 11 +- crates/interpreter/src/interpreter/memory.rs | 4 +- crates/primitives/Cargo.toml | 3 +- crates/primitives/src/bytecode.rs | 54 ++- crates/primitives/src/constants.rs | 5 + crates/primitives/src/db.rs | 30 +- crates/primitives/src/env.rs | 2 +- crates/primitives/src/lib.rs | 1 + crates/primitives/src/state.rs | 28 +- crates/revm/Cargo.toml | 12 +- crates/revm/src/db.rs | 13 + crates/revm/src/db/emptydb.rs | 80 ++++ crates/revm/src/db/ethersdb.rs | 11 +- crates/revm/src/db/in_memory_db.rs | 41 +- crates/revm/src/db/states.rs | 26 ++ crates/revm/src/db/states/account_status.rs | 106 +++++ crates/revm/src/db/states/bundle_account.rs | 350 +++++++++++++++ crates/revm/src/db/states/bundle_state.rs | 382 ++++++++++++++++ crates/revm/src/db/states/cache.rs | 149 +++++++ crates/revm/src/db/states/cache_account.rs | 413 ++++++++++++++++++ crates/revm/src/db/states/changes.rs | 32 ++ crates/revm/src/db/states/plain_account.rs | 44 ++ crates/revm/src/db/states/reverts.rs | 126 ++++++ crates/revm/src/db/states/state.rs | 226 ++++++++++ crates/revm/src/db/states/state_builder.rs | 136 ++++++ .../revm/src/db/states/transition_account.rs | 103 +++++ crates/revm/src/db/states/transition_state.rs | 44 ++ crates/revm/src/evm_impl.rs | 1 - crates/revm/src/journaled_state.rs | 39 +- crates/revm/src/lib.rs | 5 +- 35 files changed, 2455 insertions(+), 148 deletions(-) create mode 100644 crates/revm/src/db/emptydb.rs create mode 100644 crates/revm/src/db/states.rs create mode 100644 crates/revm/src/db/states/account_status.rs create mode 100644 crates/revm/src/db/states/bundle_account.rs create mode 100644 crates/revm/src/db/states/bundle_state.rs create mode 100644 crates/revm/src/db/states/cache.rs create mode 100644 crates/revm/src/db/states/cache_account.rs create mode 100644 crates/revm/src/db/states/changes.rs create mode 100644 crates/revm/src/db/states/plain_account.rs create mode 100644 crates/revm/src/db/states/reverts.rs create mode 100644 crates/revm/src/db/states/state.rs create mode 100644 crates/revm/src/db/states/state_builder.rs create mode 100644 crates/revm/src/db/states/transition_account.rs create mode 100644 crates/revm/src/db/states/transition_state.rs diff --git a/Cargo.lock b/Cargo.lock index 59a68e2e03..43987dfe9d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2207,6 +2207,8 @@ dependencies = [ "futures", "hex", "hex-literal", + "once_cell", + "rayon", "revm-interpreter", "revm-precompile", "serde", @@ -2266,6 +2268,7 @@ dependencies = [ "ruint", "serde", "sha3", + "to-binary", ] [[package]] @@ -2914,6 +2917,15 @@ version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" +[[package]] +name = "to-binary" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4424552bc848fd1afbcd81f0e8a54b7401b90fd81bb418655ad6dc6d0823bbe3" +dependencies = [ + "hex", +] + [[package]] name = "tokio" version = "1.29.1" diff --git a/bins/revme/src/statetest/merkle_trie.rs b/bins/revme/src/statetest/merkle_trie.rs index f1bed5750d..b37ec4a2ee 100644 --- a/bins/revme/src/statetest/merkle_trie.rs +++ b/bins/revme/src/statetest/merkle_trie.rs @@ -3,7 +3,7 @@ use hash_db::Hasher; use plain_hasher::PlainHasher; use primitive_types::{H160, H256}; use revm::{ - db::DbAccount, + db::PlainAccount, primitives::{keccak256, Log, B160, B256, U256}, }; use rlp::RlpStream; @@ -30,10 +30,13 @@ pub fn log_rlp_hash(logs: Vec) -> B256 { keccak256(&out) } -pub fn state_merkle_trie_root(accounts: impl Iterator) -> B256 { +pub fn state_merkle_trie_root<'a>( + accounts: impl IntoIterator, +) -> B256 { let vec = accounts + .into_iter() .map(|(address, info)| { - let acc_root = trie_account_rlp(&info); + let acc_root = trie_account_rlp(info); (H160::from(address.0), acc_root) }) .collect(); @@ -42,7 +45,7 @@ pub fn state_merkle_trie_root(accounts: impl Iterator) } /// Returns the RLP for this account. -pub fn trie_account_rlp(acc: &DbAccount) -> Bytes { +pub fn trie_account_rlp(acc: &PlainAccount) -> Bytes { let mut stream = RlpStream::new_list(4); stream.append(&acc.info.nonce); stream.append(&acc.info.balance); diff --git a/bins/revme/src/statetest/models/mod.rs b/bins/revme/src/statetest/models/mod.rs index 140f065c11..9b94cef315 100644 --- a/bins/revme/src/statetest/models/mod.rs +++ b/bins/revme/src/statetest/models/mod.rs @@ -1,6 +1,6 @@ use bytes::Bytes; -use revm::primitives::{B160, B256, U256}; -use std::collections::{BTreeMap, HashMap}; +use revm::primitives::{HashMap, B160, B256, U256}; +use std::collections::BTreeMap; mod deserializer; mod spec; diff --git a/bins/revme/src/statetest/runner.rs b/bins/revme/src/statetest/runner.rs index c7506f5fc6..bedbe96d63 100644 --- a/bins/revme/src/statetest/runner.rs +++ b/bins/revme/src/statetest/runner.rs @@ -1,30 +1,26 @@ use std::io::stdout; use std::{ - collections::HashMap, ffi::OsStr, path::{Path, PathBuf}, sync::{atomic::AtomicBool, Arc, Mutex}, time::{Duration, Instant}, }; -use indicatif::ProgressBar; - -use revm::inspectors::TracerEip3155; -use revm::{ - db::AccountState, - interpreter::CreateScheme, - primitives::{Bytecode, Env, ExecutionResult, SpecId, TransactTo, B160, B256, U256}, -}; -use std::sync::atomic::Ordering; -use walkdir::{DirEntry, WalkDir}; - use super::{ merkle_trie::{log_rlp_hash, state_merkle_trie_root}, models::{SpecName, TestSuit}, }; use hex_literal::hex; +use indicatif::ProgressBar; +use revm::inspectors::TracerEip3155; use revm::primitives::keccak256; +use revm::{ + interpreter::CreateScheme, + primitives::{Bytecode, Env, ExecutionResult, HashMap, SpecId, TransactTo, B160, B256, U256}, +}; +use std::sync::atomic::Ordering; use thiserror::Error; +use walkdir::{DirEntry, WalkDir}; #[derive(Debug, Error)] pub enum TestError { @@ -62,10 +58,19 @@ pub fn execute_test_suit( if path.file_name() == Some(OsStr::new("ValueOverflow.json")) { return Ok(()); } + + // precompiles having storage is not possible + if path.file_name() == Some(OsStr::new("RevertPrecompiledTouch_storage.json")) + || path.file_name() == Some(OsStr::new("RevertPrecompiledTouch.json")) + { + return Ok(()); + } + // txbyte is of type 02 and we dont parse tx bytes for this test to fail. if path.file_name() == Some(OsStr::new("typeTwoBerlin.json")) { return Ok(()); } + // Test checks if nonce overflows. We are handling this correctly but we are not parsing exception in testsuite // There are more nonce overflow tests that are in internal call/create, and those tests are passing and are enabled. if path.file_name() == Some(OsStr::new("CreateTransactionHighNonce.json")) { @@ -78,7 +83,10 @@ pub fn execute_test_suit( } // Test check if gas price overflows, we handle this correctly but does not match tests specific exception. - if path.file_name() == Some(OsStr::new("HighGasPrice.json")) { + if path.file_name() == Some(OsStr::new("HighGasPrice.json")) + || path.file_name() == Some(OsStr::new("CREATE_HighNonce.json")) + || path.file_name() == Some(OsStr::new("CREATE_HighNonceMinus1.json")) + { return Ok(()); } @@ -153,19 +161,15 @@ pub fn execute_test_suit( for (name, unit) in suit.0.into_iter() { // Create database and insert cache - let mut database = revm::InMemoryDB::default(); - for (address, info) in unit.pre.iter() { + let mut cache_state = revm::CacheState::new(false); + for (address, info) in unit.pre.into_iter() { let acc_info = revm::primitives::AccountInfo { balance: info.balance, code_hash: keccak256(&info.code), // try with dummy hash. code: Some(Bytecode::new_raw(info.code.clone())), nonce: info.nonce, }; - database.insert_account_info(*address, acc_info); - // insert storage: - for (&slot, &value) in info.storage.iter() { - let _ = database.insert_account_storage(*address, slot, value); - } + cache_state.insert_account_with_storage(address, acc_info, info.storage.clone()); } let mut env = Env::default(); // cfg env. SpecId is set down the road @@ -247,9 +251,16 @@ pub fn execute_test_suit( }; env.tx.transact_to = to; - let mut database_cloned = database.clone(); + let mut cache = cache_state.clone(); + cache.set_state_clear_flag(SpecId::enabled( + env.cfg.spec_id, + revm::primitives::SpecId::SPURIOUS_DRAGON, + )); + let mut state = revm::db::StateBuilder::default() + .with_cached_prestate(cache) + .build(); let mut evm = revm::new(); - evm.database(&mut database_cloned); + evm.database(&mut state); evm.env = env.clone(); // do the deed @@ -264,22 +275,8 @@ pub fn execute_test_suit( *elapsed.lock().unwrap() += timer; - let is_legacy = !SpecId::enabled( - evm.env.cfg.spec_id, - revm::primitives::SpecId::SPURIOUS_DRAGON, - ); let db = evm.db().unwrap(); - let state_root = state_merkle_trie_root( - db.accounts - .iter() - .filter(|(_address, acc)| { - (is_legacy && !matches!(acc.account_state, AccountState::NotExisting)) - || (!is_legacy - && (!(acc.info.is_empty()) - || matches!(acc.account_state, AccountState::None))) - }) - .map(|(k, v)| (*k, v.clone())), - ); + let state_root = state_merkle_trie_root(db.cache.trie_account()); let logs = match &exec_result { Ok(ExecutionResult::Success { logs, .. }) => logs.clone(), _ => Vec::new(), @@ -290,8 +287,16 @@ pub fn execute_test_suit( "Roots did not match:\nState root: wanted {:?}, got {state_root:?}\nLogs root: wanted {:?}, got {logs_root:?}", test.hash, test.logs ); - let mut database_cloned = database.clone(); - evm.database(&mut database_cloned); + + let mut cache = cache_state.clone(); + cache.set_state_clear_flag(SpecId::enabled( + env.cfg.spec_id, + revm::primitives::SpecId::SPURIOUS_DRAGON, + )); + let mut state = revm::db::StateBuilder::default() + .with_cached_prestate(cache) + .build(); + evm.database(&mut state); let _ = evm.inspect_commit(TracerEip3155::new(Box::new(stdout()), false, false)); let db = evm.db().unwrap(); @@ -319,8 +324,12 @@ pub fn execute_test_suit( println!("Output: {out:?} {path:?} UNIT_TEST:{name}\n"); } } - println!("\nApplied state:\n{db:#?}\n"); + println!(" TEST NAME: {:?}", name); + println!("\nApplied state:\n{:#?}\n", db.cache); println!("\nState root: {state_root:?}\n"); + println!("env.tx: {:?}\n", env.tx); + println!("env.block: {:?}\n", env.block); + println!("env.cfg: {:?}\n", env.cfg); return Err(TestError::RootMismatch { spec_id: env.cfg.spec_id, id, @@ -358,9 +367,9 @@ pub fn run( let mut thread = std::thread::Builder::new(); // Allow bigger stack in debug mode to prevent stack overflow errors - if cfg!(debug_assertions) { - thread = thread.stack_size(4 * 1024 * 1024); - } + //if cfg!(debug_assertions) { + thread = thread.stack_size(4 * 1024 * 1024); + //} joins.push( thread diff --git a/crates/interpreter/src/instructions/host.rs b/crates/interpreter/src/instructions/host.rs index dcf4fd6d78..57e34394b5 100644 --- a/crates/interpreter/src/instructions/host.rs +++ b/crates/interpreter/src/instructions/host.rs @@ -255,8 +255,8 @@ pub fn selfdestruct(interpreter: &mut Interpreter, host: &mut dyn Ho pub fn prepare_create_inputs( interpreter: &mut Interpreter, - create_inputs: &mut Option>, host: &mut dyn Host, + create_inputs: &mut Option>, ) { check_staticcall!(interpreter); if IS_CREATE2 { @@ -328,7 +328,7 @@ pub fn create( host: &mut dyn Host, ) { let mut create_input: Option> = None; - prepare_create_inputs::(interpreter, &mut create_input, host); + prepare_create_inputs::(interpreter, host, &mut create_input); let Some(mut create_input) = create_input else { return; diff --git a/crates/interpreter/src/interpreter/analysis.rs b/crates/interpreter/src/interpreter/analysis.rs index ccfab4253b..077edbd701 100644 --- a/crates/interpreter/src/interpreter/analysis.rs +++ b/crates/interpreter/src/interpreter/analysis.rs @@ -1,5 +1,5 @@ use crate::opcode; -use crate::primitives::{Bytecode, BytecodeState, Bytes, B256}; +use crate::primitives::{Bytecode, BytecodeState, Bytes}; use alloc::sync::Arc; // use bitvec::order::Lsb0; // use bitvec::prelude::bitvec; @@ -15,7 +15,6 @@ use revm_primitives::{ /// /// If the bytecode is already analyzed, it is returned as-is. pub fn to_analysed(bytecode: Bytecode) -> Bytecode { - let hash = bytecode.hash; let (bytecode, len) = match bytecode.state { BytecodeState::Raw => { let len = bytecode.bytecode.len(); @@ -29,7 +28,6 @@ pub fn to_analysed(bytecode: Bytecode) -> Bytecode { Bytecode { bytecode, - hash, state: BytecodeState::Analysed { len, jump_map }, } } @@ -67,7 +65,6 @@ fn analyze(code: &[u8]) -> JumpMap { pub struct BytecodeLocked { bytecode: Bytes, len: usize, - hash: B256, jump_map: JumpMap, } @@ -87,7 +84,6 @@ impl TryFrom for BytecodeLocked { Ok(BytecodeLocked { bytecode: bytecode.bytecode, len, - hash: bytecode.hash, jump_map, }) } else { @@ -104,10 +100,6 @@ impl BytecodeLocked { self.len } - pub fn hash(&self) -> B256 { - self.hash - } - pub fn is_empty(&self) -> bool { self.len == 0 } @@ -115,7 +107,6 @@ impl BytecodeLocked { pub fn unlock(self) -> Bytecode { Bytecode { bytecode: self.bytecode, - hash: self.hash, state: BytecodeState::Analysed { len: self.len, jump_map: self.jump_map, diff --git a/crates/interpreter/src/interpreter/memory.rs b/crates/interpreter/src/interpreter/memory.rs index 4fa4daa70d..d821ab679e 100644 --- a/crates/interpreter/src/interpreter/memory.rs +++ b/crates/interpreter/src/interpreter/memory.rs @@ -14,7 +14,9 @@ pub struct Memory { impl Default for Memory { fn default() -> Self { - Memory::new() + Self { + data: Vec::with_capacity(4 * 1024), // took it from evmone + } } } diff --git a/crates/primitives/Cargo.toml b/crates/primitives/Cargo.toml index f584ccc81d..0c3341f6e1 100644 --- a/crates/primitives/Cargo.toml +++ b/crates/primitives/Cargo.toml @@ -12,7 +12,6 @@ readme = "../../README.md" [dependencies] bytes = { version = "1.4", default-features = false } hashbrown = { version = "0.14" } -hex = { version = "0.4", default-features = false } primitive-types = { version = "0.12", default-features = false } rlp = { version = "0.5", default-features = false } # used for create2 address calculation ruint = { version = "1.8.0", features = ["primitive-types", "rlp"] } @@ -28,6 +27,8 @@ fixed-hash = { version = "0.8", default-features = false, features = [ #utility hex-literal = "0.4" +hex = { version = "0.4", default-features = false } +to-binary = "0.4" derive_more = "0.99" enumn = "0.1" diff --git a/crates/primitives/src/bytecode.rs b/crates/primitives/src/bytecode.rs index 1cd8aa8f9b..72bd1f590e 100644 --- a/crates/primitives/src/bytecode.rs +++ b/crates/primitives/src/bytecode.rs @@ -3,12 +3,22 @@ use alloc::{sync::Arc, vec, vec::Vec}; use bitvec::prelude::{bitvec, Lsb0}; use bitvec::vec::BitVec; use bytes::Bytes; +use core::fmt::Debug; +use to_binary::BinaryString; /// A map of valid `jump` destinations. -#[derive(Clone, Debug, Eq, PartialEq, Default)] +#[derive(Clone, Eq, PartialEq, Default)] #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] pub struct JumpMap(pub Arc>); +impl Debug for JumpMap { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + f.debug_struct("JumpMap") + .field("map", &BinaryString::from(self.0.as_raw_slice())) + .finish() + } +} + impl JumpMap { /// Get the raw bytes of the jump map pub fn as_slice(&self) -> &[u8] { @@ -34,15 +44,23 @@ pub enum BytecodeState { Analysed { len: usize, jump_map: JumpMap }, } -#[derive(Clone, Debug, Eq, PartialEq)] +#[derive(Clone, Eq, PartialEq)] #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] pub struct Bytecode { #[cfg_attr(feature = "serde", serde(with = "crate::utilities::serde_hex_bytes"))] pub bytecode: Bytes, - pub hash: B256, pub state: BytecodeState, } +impl Debug for Bytecode { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + f.debug_struct("Bytecode") + .field("bytecode", &hex::encode(&self.bytecode[..])) + .field("state", &self.state) + .finish() + } +} + impl Default for Bytecode { fn default() -> Self { Bytecode::new() @@ -54,7 +72,6 @@ impl Bytecode { pub fn new() -> Self { Bytecode { bytecode: vec![0].into(), - hash: KECCAK_EMPTY, state: BytecodeState::Analysed { len: 0, jump_map: JumpMap(Arc::new(bitvec![u8, Lsb0; 0])), @@ -62,15 +79,18 @@ impl Bytecode { } } - pub fn new_raw(bytecode: Bytes) -> Self { - let hash = if bytecode.is_empty() { + /// Calculate hash of the bytecode. + pub fn hash_slow(&self) -> B256 { + if self.is_empty() { KECCAK_EMPTY } else { - keccak256(&bytecode) - }; + keccak256(&self.original_bytes()) + } + } + + pub fn new_raw(bytecode: Bytes) -> Self { Self { bytecode, - hash, state: BytecodeState::Raw, } } @@ -79,10 +99,9 @@ impl Bytecode { /// /// # Safety /// Hash need to be appropriate keccak256 over bytecode. - pub unsafe fn new_raw_with_hash(bytecode: Bytes, hash: B256) -> Self { + pub unsafe fn new_raw_with_hash(bytecode: Bytes) -> Self { Self { bytecode, - hash, state: BytecodeState::Raw, } } @@ -92,15 +111,9 @@ impl Bytecode { /// # Safety /// Bytecode need to end with STOP (0x00) opcode as checked bytecode assumes /// that it is safe to iterate over bytecode without checking lengths - pub unsafe fn new_checked(bytecode: Bytes, len: usize, hash: Option) -> Self { - let hash = match hash { - None if len == 0 => KECCAK_EMPTY, - None => keccak256(&bytecode), - Some(hash) => hash, - }; + pub unsafe fn new_checked(bytecode: Bytes, len: usize) -> Self { Self { bytecode, - hash, state: BytecodeState::Checked { len }, } } @@ -118,10 +131,6 @@ impl Bytecode { } } - pub fn hash(&self) -> B256 { - self.hash - } - pub fn state(&self) -> &BytecodeState { &self.state } @@ -150,7 +159,6 @@ impl Bytecode { bytecode.resize(len + 33, 0); Self { bytecode: bytecode.into(), - hash: self.hash, state: BytecodeState::Checked { len }, } } diff --git a/crates/primitives/src/constants.rs b/crates/primitives/src/constants.rs index 606b77c078..a715086c83 100644 --- a/crates/primitives/src/constants.rs +++ b/crates/primitives/src/constants.rs @@ -1,3 +1,5 @@ +use crate::B160; + /// Interpreter stack limit pub const STACK_LIMIT: u64 = 1024; /// EVM call stack limit @@ -11,3 +13,6 @@ pub const MAX_CODE_SIZE: usize = 0x6000; /// /// Limit of maximum initcode size is 2 * MAX_CODE_SIZE pub const MAX_INITCODE_SIZE: usize = 2 * MAX_CODE_SIZE; + +/// Precompile 3 is special in few places +pub const PRECOMPILE3: B160 = B160([0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 3]); diff --git a/crates/primitives/src/db.rs b/crates/primitives/src/db.rs index e65b82d6ef..5790fdbe4e 100644 --- a/crates/primitives/src/db.rs +++ b/crates/primitives/src/db.rs @@ -25,7 +25,13 @@ pub trait Database { fn block_hash(&mut self, number: U256) -> Result; } -#[auto_impl(& mut, Box)] +impl From for WrapDatabaseRef { + fn from(f: F) -> Self { + WrapDatabaseRef(f) + } +} + +#[auto_impl(&mut, Box)] pub trait DatabaseCommit { fn commit(&mut self, changes: Map); } @@ -46,6 +52,28 @@ pub trait DatabaseRef { fn block_hash(&self, number: U256) -> Result; } +pub struct WrapDatabaseRef(pub T); + +impl Database for WrapDatabaseRef { + type Error = T::Error; + + fn basic(&mut self, address: B160) -> Result, Self::Error> { + self.0.basic(address) + } + + fn code_by_hash(&mut self, code_hash: B256) -> Result { + self.0.code_by_hash(code_hash) + } + + fn storage(&mut self, address: B160, index: U256) -> Result { + self.0.storage(address, index) + } + + fn block_hash(&mut self, number: U256) -> Result { + self.0.block_hash(number) + } +} + pub struct RefDBWrapper<'a, Error> { pub db: &'a dyn DatabaseRef, } diff --git a/crates/primitives/src/env.rs b/crates/primitives/src/env.rs index 7f91a7d067..1e2306dcc9 100644 --- a/crates/primitives/src/env.rs +++ b/crates/primitives/src/env.rs @@ -308,7 +308,7 @@ impl Env { } } - // Check if the transaction's chain id is correct + // Check if access list is empty for transactions before BERLIN if !SPEC::enabled(SpecId::BERLIN) && !self.tx.access_list.is_empty() { return Err(InvalidTransaction::AccessListNotSupported); } diff --git a/crates/primitives/src/lib.rs b/crates/primitives/src/lib.rs index 1502cf80e5..9abae157b4 100644 --- a/crates/primitives/src/lib.rs +++ b/crates/primitives/src/lib.rs @@ -20,6 +20,7 @@ pub use bytes; pub use bytes::Bytes; pub use hex; pub use hex_literal; +pub use to_binary; /// Address type is last 20 bytes of hash of ethereum account pub type Address = B160; /// Hash, in Ethereum usually keccak256. diff --git a/crates/primitives/src/state.rs b/crates/primitives/src/state.rs index de04f02853..3b6e1e91a5 100644 --- a/crates/primitives/src/state.rs +++ b/crates/primitives/src/state.rs @@ -83,6 +83,11 @@ impl Account { self.status |= AccountStatus::Created; } + /// Unmark created flag. + pub fn unmark_created(&mut self) { + self.status -= AccountStatus::Created; + } + /// Is account loaded as not existing from database /// This is needed for pre spurious dragon hardforks where /// existing and empty were two separate states. @@ -91,7 +96,7 @@ impl Account { } /// Is account newly created in this transaction. - pub fn is_newly_created(&self) -> bool { + pub fn is_created(&self) -> bool { self.status.contains(AccountStatus::Created) } @@ -136,6 +141,13 @@ impl StorageSlot { } } + pub fn new_changed(original_value: U256, present_value: U256) -> Self { + Self { + original_value, + present_value, + } + } + /// Returns true if the present value differs from the original value pub fn is_changed(&self) -> bool { self.original_value != self.present_value @@ -185,8 +197,7 @@ impl PartialEq for AccountInfo { } impl AccountInfo { - pub fn new(balance: U256, nonce: u64, code: Bytecode) -> Self { - let code_hash = code.hash(); + pub fn new(balance: U256, nonce: u64, code_hash: B256, code: Bytecode) -> Self { Self { balance, nonce, @@ -204,6 +215,17 @@ impl AccountInfo { !self.is_empty() } + /// Return bytecode hash associated with this account. + /// If account does not have code, it return's `KECCAK_EMPTY` hash. + pub fn code_hash(&self) -> B256 { + self.code_hash + } + + /// Take bytecode from account. Code will be set to None. + pub fn take_bytecode(&mut self) -> Option { + self.code.take() + } + pub fn from_balance(balance: U256) -> Self { AccountInfo { balance, diff --git a/crates/revm/Cargo.toml b/crates/revm/Cargo.toml index 2fd0fcb312..65541061ba 100644 --- a/crates/revm/Cargo.toml +++ b/crates/revm/Cargo.toml @@ -13,18 +13,26 @@ readme = "../../README.md" revm-precompile = { path = "../precompile", version = "2.0.2", default-features = false } revm-interpreter = { path = "../interpreter", version = "1.1.2", default-features = false } +#misc auto_impl = { version = "1.1", default-features = false } +once_cell = { version = "1.17", default-features = false } # Optional serde = { version = "1.0", features = ["derive", "rc"], optional = true } serde_json = { version = "1.0", features = ["preserve_order"], optional = true } # ethersdb -tokio = { version = "1.28", features = ["rt-multi-thread", "macros"], optional = true } +tokio = { version = "1.28", features = [ + "rt-multi-thread", + "macros", +], optional = true } ethers-providers = { version = "2.0", optional = true } ethers-core = { version = "2.0", optional = true } futures = { version = "0.3.27", optional = true } +# parallel execution inside state. +rayon = { version = "1.7", optional = true } + [dev-dependencies] hex-literal = "0.4" ethers-contract = { version = "2.0.3", default-features = false } @@ -51,7 +59,7 @@ optional_block_gas_limit = ["revm-interpreter/optional_block_gas_limit"] optional_eip3607 = ["revm-interpreter/optional_eip3607"] optional_gas_refund = ["revm-interpreter/optional_gas_refund"] optional_no_base_fee = ["revm-interpreter/optional_no_base_fee"] -std = ["revm-interpreter/std"] +std = ["revm-interpreter/std", "once_cell/std", "rayon"] ethersdb = ["std", "tokio", "futures", "ethers-providers", "ethers-core"] serde = ["dep:serde", "dep:serde_json", "revm-interpreter/serde"] arbitrary = ["revm-interpreter/arbitrary"] diff --git a/crates/revm/src/db.rs b/crates/revm/src/db.rs index d3ea17c6d1..076a6eef5f 100644 --- a/crates/revm/src/db.rs +++ b/crates/revm/src/db.rs @@ -1,3 +1,4 @@ +pub mod emptydb; pub mod in_memory_db; #[cfg(feature = "ethersdb")] @@ -5,6 +6,16 @@ pub mod ethersdb; #[cfg(feature = "ethersdb")] pub use ethersdb::EthersDB; +#[cfg(feature = "std")] +pub mod states; + +#[cfg(feature = "std")] +pub use states::{ + AccountRevert, AccountStatus, BundleAccount, BundleState, CacheState, PlainAccount, + RevertToSlot, State, StateBuilder, StorageWithOriginalValues, TransitionAccount, + TransitionState, +}; + #[cfg(all(not(feature = "ethersdb"), feature = "web3db"))] compile_error!( "`web3db` feature is deprecated, drop-in replacement can be found with feature `ethersdb`" @@ -12,3 +23,5 @@ compile_error!( pub use crate::primitives::db::*; pub use in_memory_db::*; + +pub use emptydb::{EmptyDB, EmptyDBTyped}; diff --git a/crates/revm/src/db/emptydb.rs b/crates/revm/src/db/emptydb.rs new file mode 100644 index 0000000000..65bc67cab3 --- /dev/null +++ b/crates/revm/src/db/emptydb.rs @@ -0,0 +1,80 @@ +use core::{convert::Infallible, marker::PhantomData}; +use revm_interpreter::primitives::{ + db::{Database, DatabaseRef}, + keccak256, AccountInfo, Bytecode, B160, B256, U256, +}; + +pub type EmptyDB = EmptyDBTyped; + +impl Default for EmptyDB { + fn default() -> Self { + Self { + keccak_block_hash: false, + _phantom: PhantomData, + } + } +} + +/// An empty database that always returns default values when queried. +#[derive(Debug, Clone)] +pub struct EmptyDBTyped { + pub keccak_block_hash: bool, + pub _phantom: PhantomData, +} + +impl EmptyDBTyped { + pub fn new() -> Self { + Self { + keccak_block_hash: false, + _phantom: PhantomData, + } + } + + pub fn new_keccak_block_hash() -> Self { + Self { + keccak_block_hash: true, + _phantom: PhantomData, + } + } +} + +impl Database for EmptyDBTyped { + type Error = T; + + fn basic(&mut self, address: B160) -> Result, Self::Error> { + ::basic(self, address) + } + + fn code_by_hash(&mut self, code_hash: B256) -> Result { + ::code_by_hash(self, code_hash) + } + + fn storage(&mut self, address: B160, index: U256) -> Result { + ::storage(self, address, index) + } + + fn block_hash(&mut self, number: U256) -> Result { + ::block_hash(self, number) + } +} + +impl DatabaseRef for EmptyDBTyped { + type Error = T; + /// Get basic account information. + fn basic(&self, _address: B160) -> Result, Self::Error> { + Ok(None) + } + /// Get account code by its hash + fn code_by_hash(&self, _code_hash: B256) -> Result { + Ok(Bytecode::new()) + } + /// Get storage value of address at index. + fn storage(&self, _address: B160, _index: U256) -> Result { + Ok(U256::default()) + } + + // History related + fn block_hash(&self, number: U256) -> Result { + Ok(keccak256(&number.to_be_bytes::<{ U256::BYTES }>())) + } +} diff --git a/crates/revm/src/db/ethersdb.rs b/crates/revm/src/db/ethersdb.rs index 85e5d69287..2de1307df7 100644 --- a/crates/revm/src/db/ethersdb.rs +++ b/crates/revm/src/db/ethersdb.rs @@ -69,6 +69,11 @@ where }; let (nonce, balance, code) = self.block_on(f); // panic on not getting data? + let bytecode = Bytecode::new_raw( + code.unwrap_or_else(|e| panic!("ethers get code error: {e:?}")) + .0, + ); + let code_hash = bytecode.hash_slow(); Ok(Some(AccountInfo::new( U256::from_limbs( balance @@ -78,10 +83,8 @@ where nonce .unwrap_or_else(|e| panic!("ethers get nonce error: {e:?}")) .as_u64(), - Bytecode::new_raw( - code.unwrap_or_else(|e| panic!("ethers get code error: {e:?}")) - .0, - ), + code_hash, + bytecode, ))) } diff --git a/crates/revm/src/db/in_memory_db.rs b/crates/revm/src/db/in_memory_db.rs index 1199b5b000..bf13978278 100644 --- a/crates/revm/src/db/in_memory_db.rs +++ b/crates/revm/src/db/in_memory_db.rs @@ -1,7 +1,6 @@ -use super::{DatabaseCommit, DatabaseRef}; +use super::{DatabaseCommit, DatabaseRef, EmptyDB}; use crate::primitives::{ - hash_map::Entry, keccak256, Account, AccountInfo, Bytecode, HashMap, Log, B160, B256, - KECCAK_EMPTY, U256, + hash_map::Entry, Account, AccountInfo, Bytecode, HashMap, Log, B160, B256, KECCAK_EMPTY, U256, }; use crate::Database; use alloc::vec::Vec; @@ -11,7 +10,10 @@ pub type InMemoryDB = CacheDB; impl Default for InMemoryDB { fn default() -> Self { - CacheDB::new(EmptyDB {}) + CacheDB::new(EmptyDB { + keccak_block_hash: true, + _phantom: core::marker::PhantomData, + }) } } @@ -61,7 +63,7 @@ impl CacheDB { pub fn insert_contract(&mut self, account: &mut AccountInfo) { if let Some(code) = &account.code { if !code.is_empty() { - account.code_hash = code.hash(); + account.code_hash = code.hash_slow(); self.contracts .entry(account.code_hash) .or_insert_with(|| code.clone()); @@ -134,7 +136,7 @@ impl DatabaseCommit for CacheDB { db_account.info = AccountInfo::default(); continue; } - let is_newly_created = account.is_newly_created(); + let is_newly_created = account.is_created(); self.insert_contract(&mut account.info); let db_account = self.accounts.entry(address).or_default(); @@ -355,31 +357,6 @@ impl AccountState { } } -/// An empty database that always returns default values when queried. -#[derive(Debug, Default, Clone)] -pub struct EmptyDB(); - -impl DatabaseRef for EmptyDB { - type Error = Infallible; - /// Get basic account information. - fn basic(&self, _address: B160) -> Result, Self::Error> { - Ok(None) - } - /// Get account code by its hash - fn code_by_hash(&self, _code_hash: B256) -> Result { - Ok(Bytecode::new()) - } - /// Get storage value of address at index. - fn storage(&self, _address: B160, _index: U256) -> Result { - Ok(U256::default()) - } - - // History related - fn block_hash(&self, number: U256) -> Result { - Ok(keccak256(&number.to_be_bytes::<{ U256::BYTES }>())) - } -} - /// Custom benchmarking DB that only has account info for the zero address. /// /// Any other address will return an empty account. @@ -388,7 +365,7 @@ pub struct BenchmarkDB(pub Bytecode, B256); impl BenchmarkDB { pub fn new_bytecode(bytecode: Bytecode) -> Self { - let hash = bytecode.hash(); + let hash = bytecode.hash_slow(); Self(bytecode, hash) } } diff --git a/crates/revm/src/db/states.rs b/crates/revm/src/db/states.rs new file mode 100644 index 0000000000..20f4d565f7 --- /dev/null +++ b/crates/revm/src/db/states.rs @@ -0,0 +1,26 @@ +pub mod account_status; +pub mod bundle_account; +pub mod bundle_state; +pub mod cache; +pub mod cache_account; +pub mod changes; +pub mod plain_account; +pub mod reverts; +pub mod state; +pub mod state_builder; +pub mod transition_account; +pub mod transition_state; + +/// Account status for Block and Bundle states. +pub use account_status::AccountStatus; +pub use bundle_account::BundleAccount; +pub use bundle_state::BundleState; +pub use cache::CacheState; +pub use cache_account::CacheAccount; +pub use changes::{StateChangeset, StateReverts}; +pub use plain_account::{PlainAccount, StorageWithOriginalValues}; +pub use reverts::{AccountRevert, RevertToSlot}; +pub use state::State; +pub use state_builder::StateBuilder; +pub use transition_account::TransitionAccount; +pub use transition_state::TransitionState; diff --git a/crates/revm/src/db/states/account_status.rs b/crates/revm/src/db/states/account_status.rs new file mode 100644 index 0000000000..c8b73f6040 --- /dev/null +++ b/crates/revm/src/db/states/account_status.rs @@ -0,0 +1,106 @@ +/// After account get loaded from database it can be in a lot of different states +/// while we execute multiple transaction and even blocks over account that is in memory. +/// This structure models all possible states that account can be in. +#[derive(Clone, Copy, Default, Debug, Eq, PartialEq)] +pub enum AccountStatus { + #[default] + LoadedNotExisting, + Loaded, + LoadedEmptyEIP161, + InMemoryChange, + Changed, + Destroyed, + DestroyedChanged, + DestroyedAgain, +} + +impl AccountStatus { + /// Account is not midified and just loaded from database. + pub fn not_modified(&self) -> bool { + matches!( + self, + AccountStatus::LoadedNotExisting + | AccountStatus::Loaded + | AccountStatus::LoadedEmptyEIP161 + ) + } + + /// Account was destroyed by calling SELFDESTRUCT. + /// This means that full account and storage are inside memory. + pub fn was_destroyed(&self) -> bool { + matches!( + self, + AccountStatus::Destroyed + | AccountStatus::DestroyedChanged + | AccountStatus::DestroyedAgain + ) + } + + /// This means storage is known, it can be newly created or storage got destroyed. + pub fn storage_known(&self) -> bool { + matches!( + self, + AccountStatus::LoadedNotExisting + | AccountStatus::InMemoryChange + | AccountStatus::Destroyed + | AccountStatus::DestroyedChanged + | AccountStatus::DestroyedAgain + ) + } + + /// Account is modified but not destroyed. + /// This means that some of storage values can be found in both + /// memory and database. + pub fn modified_but_not_destroyed(&self) -> bool { + matches!(self, AccountStatus::Changed | AccountStatus::InMemoryChange) + } +} + +#[cfg(test)] +mod test { + + use super::*; + + #[test] + fn test_account_status() { + // account not modified + assert!(AccountStatus::Loaded.not_modified()); + assert!(AccountStatus::LoadedEmptyEIP161.not_modified()); + assert!(AccountStatus::LoadedNotExisting.not_modified()); + assert!(!AccountStatus::Changed.not_modified()); + assert!(!AccountStatus::InMemoryChange.not_modified()); + assert!(!AccountStatus::Destroyed.not_modified()); + assert!(!AccountStatus::DestroyedChanged.not_modified()); + assert!(!AccountStatus::DestroyedAgain.not_modified()); + + // we know full storage + assert!(!AccountStatus::LoadedEmptyEIP161.storage_known()); + assert!(AccountStatus::LoadedNotExisting.storage_known()); + assert!(AccountStatus::InMemoryChange.storage_known()); + assert!(AccountStatus::Destroyed.storage_known()); + assert!(AccountStatus::DestroyedChanged.storage_known()); + assert!(AccountStatus::DestroyedAgain.storage_known()); + assert!(!AccountStatus::Loaded.storage_known()); + assert!(!AccountStatus::Changed.storage_known()); + + // account was destroyed + assert!(!AccountStatus::LoadedEmptyEIP161.was_destroyed()); + assert!(!AccountStatus::LoadedNotExisting.was_destroyed()); + assert!(!AccountStatus::InMemoryChange.was_destroyed()); + assert!(AccountStatus::Destroyed.was_destroyed()); + assert!(AccountStatus::DestroyedChanged.was_destroyed()); + assert!(AccountStatus::DestroyedAgain.was_destroyed()); + assert!(!AccountStatus::Loaded.was_destroyed()); + assert!(!AccountStatus::Changed.was_destroyed()); + + // account modified but not destroyed + assert!(AccountStatus::Changed.modified_but_not_destroyed()); + assert!(AccountStatus::InMemoryChange.modified_but_not_destroyed()); + assert!(!AccountStatus::Loaded.modified_but_not_destroyed()); + assert!(!AccountStatus::LoadedEmptyEIP161.modified_but_not_destroyed()); + assert!(!AccountStatus::LoadedNotExisting.modified_but_not_destroyed()); + assert!(!AccountStatus::Destroyed.modified_but_not_destroyed()); + assert!(!AccountStatus::DestroyedChanged.modified_but_not_destroyed()); + assert!(!AccountStatus::DestroyedAgain.modified_but_not_destroyed()); + } +} diff --git a/crates/revm/src/db/states/bundle_account.rs b/crates/revm/src/db/states/bundle_account.rs new file mode 100644 index 0000000000..9592445b6a --- /dev/null +++ b/crates/revm/src/db/states/bundle_account.rs @@ -0,0 +1,350 @@ +use super::{ + reverts::AccountInfoRevert, AccountRevert, AccountStatus, RevertToSlot, + StorageWithOriginalValues, TransitionAccount, +}; +use revm_interpreter::primitives::{AccountInfo, StorageSlot, U256}; +use revm_precompile::HashMap; + +/// Account information focused on creating of database changesets +/// and Reverts. +/// +/// Status is needed as to know from what state we are applying the TransitionAccount. +/// +/// Original account info is needed to know if there was a change. +/// Same thing for storage with original value. +/// +/// On selfdestruct storage original value is ignored. +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct BundleAccount { + pub info: Option, + pub original_info: Option, + /// Contain both original and present state. + /// When extracting changeset we compare if original value is different from present value. + /// If it is different we add it to changeset. + /// + /// If Account was destroyed we ignore original value and comprate present state with U256::ZERO. + pub storage: StorageWithOriginalValues, + /// Account status. + pub status: AccountStatus, +} + +impl BundleAccount { + /// Create new BundleAccount. + pub fn new( + original_info: Option, + present_info: Option, + storage: StorageWithOriginalValues, + status: AccountStatus, + ) -> Self { + Self { + info: present_info, + original_info, + storage, + status, + } + } + + /// Return storage slot if it exist. + /// + /// In case we know that account is destroyed return `Some(U256::ZERO)` + pub fn storage_slot(&self, slot: U256) -> Option { + let slot = self.storage.get(&slot).map(|s| s.present_value); + if slot.is_some() { + slot + } else if self.status.storage_known() { + Some(U256::ZERO) + } else { + None + } + } + + /// Fetch account info if it exist. + pub fn account_info(&self) -> Option { + self.info.clone() + } + + /// Was this account destroyed. + pub fn was_destroyed(&self) -> bool { + self.status.was_destroyed() + } + + /// Return true of account info was changed. + pub fn is_info_changed(&self) -> bool { + self.info != self.original_info + } + + /// Return true if contract was changed + pub fn is_contract_changed(&self) -> bool { + self.info.as_ref().map(|a| a.code_hash) != self.original_info.as_ref().map(|a| a.code_hash) + } + + /// Revert account to previous state and return true if account can be removed. + pub fn revert(&mut self, revert: AccountRevert) -> bool { + self.status = revert.previous_status; + + match revert.account { + AccountInfoRevert::DoNothing => (), + AccountInfoRevert::DeleteIt => { + self.info = None; + self.storage = HashMap::new(); + return true; + } + AccountInfoRevert::RevertTo(info) => self.info = Some(info), + }; + // revert stoarge + for (key, slot) in revert.storage { + match slot { + RevertToSlot::Some(value) => { + // Dont overwrite original values if present + // if storage is not present set original values as currect value. + self.storage + .entry(key) + .or_insert(StorageSlot::new_changed(value, U256::ZERO)) + .present_value = value; + } + RevertToSlot::Destroyed => { + // if it was destroyed this means that storage was created and we need to remove it. + self.storage.remove(&key); + } + } + } + false + } + + /// Extend account with another account. + /// + /// It is similar with the update but it is done with another BundleAccount. + /// + /// Original values of acccount and storage stay the same. + pub(crate) fn extend(&mut self, other: Self) { + self.status = other.status; + self.info = other.info; + // extend storage + for (key, storage_slot) in other.storage { + // update present value or insert storage slot. + self.storage + .entry(key) + .or_insert(storage_slot) + .present_value = storage_slot.present_value; + } + } + + /// Update to new state and generate AccountRevert that if applied to new state will + /// revert it to previous state. If no revert is present, update is noop. + pub fn update_and_create_revert( + &mut self, + transition: TransitionAccount, + ) -> Option { + let updated_info = transition.info; + let updated_storage = transition.storage; + let updated_status = transition.status; + // storage was destroyed so we should clear current storage. + if transition.storage_was_destroyed { + self.storage.clear(); + } + + // the helper that extends this storage but preserves original value. + let extend_storage = + |this_storage: &mut StorageWithOriginalValues, + storage_update: StorageWithOriginalValues| { + for (key, value) in storage_update { + // TODO small optimization but if present and original values are same we can + // remove this entry from this storage. + this_storage.entry(key).or_insert(value).present_value = value.present_value; + } + }; + + let previous_storage_from_update = + |updated_storage: &StorageWithOriginalValues| -> HashMap { + updated_storage + .iter() + .filter(|s| s.1.original_value != s.1.present_value) + .map(|(key, value)| (*key, RevertToSlot::Some(value.original_value))) + .collect() + }; + + // Needed for some reverts. + let info_revert = if self.info != updated_info { + AccountInfoRevert::RevertTo(self.info.clone().unwrap_or_default()) + } else { + AccountInfoRevert::DoNothing + }; + + match updated_status { + AccountStatus::Changed => { + let previous_storage = previous_storage_from_update(&updated_storage); + match self.status { + AccountStatus::Changed | AccountStatus::Loaded => { + // extend the storage. original values is not used inside bundle. + extend_storage(&mut self.storage, updated_storage); + } + AccountStatus::LoadedEmptyEIP161 => { + // Do nothing. + // Only change that can happen from LoadedEmpty to Changed + // is if balance is send to account. So we are only checking account change here. + } + _ => unreachable!("Invalid state transfer to Changed from {self:?}"), + }; + let previous_status = self.status; + self.status = AccountStatus::Changed; + self.info = updated_info; + Some(AccountRevert { + account: info_revert, + storage: previous_storage, + previous_status, + wipe_storage: false, + }) + } + AccountStatus::InMemoryChange => { + let previous_storage = previous_storage_from_update(&updated_storage); + let in_memory_info_revert = match self.status { + AccountStatus::Loaded | AccountStatus::InMemoryChange => { + // from loaded (Or LoadedEmpty) to InMemoryChange can happen if there is balance change + // or new created account but Loaded didn't have contract. + extend_storage(&mut self.storage, updated_storage); + info_revert + } + AccountStatus::LoadedEmptyEIP161 => { + self.storage = updated_storage; + info_revert + } + AccountStatus::LoadedNotExisting => { + self.storage = updated_storage; + AccountInfoRevert::DeleteIt + } + _ => unreachable!("Invalid change to InMemoryChange from {self:?}"), + }; + let previous_status = self.status; + self.status = AccountStatus::InMemoryChange; + self.info = updated_info; + Some(AccountRevert { + account: in_memory_info_revert, + storage: previous_storage, + previous_status, + wipe_storage: false, + }) + } + AccountStatus::Loaded + | AccountStatus::LoadedNotExisting + | AccountStatus::LoadedEmptyEIP161 => { + // No changeset, maybe just update data + // Do nothing for now. + None + } + AccountStatus::Destroyed => { + let this_info = self.info.take().unwrap_or_default(); + let this_storage = self.storage.drain().collect(); + let ret = match self.status { + AccountStatus::InMemoryChange | AccountStatus::Changed | AccountStatus::Loaded | AccountStatus::LoadedEmptyEIP161 => { + AccountRevert::new_selfdestructed(self.status, this_info, this_storage) + } + AccountStatus::LoadedNotExisting => { + // Do nothing as we have LoadedNotExisting -> Destroyed (It is noop) + return None; + } + _ => unreachable!("Invalid transition to Destroyed account from: {self:?} to {updated_info:?} {updated_status:?}"), + }; + self.status = AccountStatus::Destroyed; + // set present to destroyed. + Some(ret) + } + AccountStatus::DestroyedChanged => { + // Previous block created account or changed. + // (It was destroyed on previous block or one before). + + // check common pre destroy paths. + if let Some(revert_state) = + AccountRevert::new_selfdestructed_from_bundle(self, &updated_storage) + { + // set to destroyed and revert state. + self.status = AccountStatus::DestroyedChanged; + self.info = updated_info; + self.storage = updated_storage; + + return Some(revert_state); + } + + let ret = match self.status { + AccountStatus::Destroyed | AccountStatus::LoadedNotExisting => { + // from destroyed state new account is made + Some(AccountRevert { + account: AccountInfoRevert::DeleteIt, + storage: previous_storage_from_update(&updated_storage), + previous_status: self.status, + wipe_storage: false, + }) + } + AccountStatus::DestroyedChanged => { + // Stays same as DestroyedNewChanged + Some(AccountRevert { + // empty account + account: info_revert, + storage: previous_storage_from_update(&updated_storage), + previous_status: AccountStatus::DestroyedChanged, + wipe_storage: false, + }) + } + AccountStatus::DestroyedAgain => Some(AccountRevert::new_selfdestructed_again( + // destroyed again will set empty account. + AccountStatus::DestroyedAgain, + AccountInfo::default(), + HashMap::default(), + updated_storage.clone(), + )), + _ => unreachable!("Invalid state transfer to DestroyedNew from {self:?}"), + }; + self.status = AccountStatus::DestroyedChanged; + self.info = updated_info; + // extends current storage. + extend_storage(&mut self.storage, updated_storage); + + ret + } + AccountStatus::DestroyedAgain => { + // Previous block created account + // (It was destroyed on previous block or one before). + + // check common pre destroy paths. + let ret = if let Some(revert_state) = + AccountRevert::new_selfdestructed_from_bundle(self, &HashMap::default()) + { + Some(revert_state) + } else { + match self.status { + AccountStatus::Destroyed + | AccountStatus::DestroyedAgain + | AccountStatus::LoadedNotExisting => { + // From destroyed to destroyed again. is noop + // + // DestroyedAgain to DestroyedAgain is noop + // + // From LoadedNotExisting to DestroyedAgain + // is noop as account is destroyed again + None + } + AccountStatus::DestroyedChanged => { + // From destroyed new to destroyed again. + let ret = AccountRevert { + // empty account + account: AccountInfoRevert::RevertTo( + self.info.clone().unwrap_or_default(), + ), + // TODO(rakita) is this invalid? + storage: previous_storage_from_update(&updated_storage), + previous_status: AccountStatus::DestroyedChanged, + wipe_storage: false, + }; + Some(ret) + } + _ => unreachable!("Invalid state to DestroyedAgain from {self:?}"), + } + }; + // set to destroyed and revert state. + self.status = AccountStatus::DestroyedAgain; + self.info = None; + self.storage.clear(); + ret + } + } + } +} diff --git a/crates/revm/src/db/states/bundle_state.rs b/crates/revm/src/db/states/bundle_state.rs new file mode 100644 index 0000000000..8309bddde2 --- /dev/null +++ b/crates/revm/src/db/states/bundle_state.rs @@ -0,0 +1,382 @@ +use super::{ + changes::StateChangeset, reverts::AccountInfoRevert, AccountRevert, AccountStatus, + BundleAccount, RevertToSlot, StateReverts, TransitionState, +}; +use rayon::slice::ParallelSliceMut; +use revm_interpreter::primitives::{ + hash_map::{self, Entry}, + AccountInfo, Bytecode, HashMap, StorageSlot, B160, B256, U256, +}; + +/// Bundle state contain only values that got changed +/// +/// For every account it contains both original and present state. +/// This is needed to decide if there were any changes to the account. +/// +/// Reverts and created when TransitionState is applied to BundleState. +/// And can be used to revert BundleState to the state before transition. +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct BundleState { + /// Account state. + pub state: HashMap, + /// All created contracts in this block. + pub contracts: HashMap, + /// Changes to revert. + /// + /// Note: Inside vector is *not* sorted by address. + /// But it is unique by address. + pub reverts: Vec>, +} + +impl Default for BundleState { + fn default() -> Self { + Self { + state: HashMap::new(), + reverts: Vec::new(), + contracts: HashMap::new(), + } + } +} + +impl BundleState { + /// Return reference to the state. + pub fn state(&self) -> &HashMap { + &self.state + } + + /// Is bundle state empty. + pub fn is_empty(&self) -> bool { + self.len() == 0 + } + + /// Return number of changed accounts. + pub fn len(&self) -> usize { + self.state.len() + } + + /// Create it with new and old values of both Storage and AccountInfo. + pub fn new( + state: impl IntoIterator< + Item = ( + B160, + Option, + Option, + HashMap, + ), + >, + reverts: impl IntoIterator< + Item = impl IntoIterator< + Item = ( + B160, + Option>, + impl IntoIterator, + ), + >, + >, + contracts: impl IntoIterator, + ) -> Self { + // Create state from iterator. + let state = state + .into_iter() + .map(|(address, original, present, storage)| { + ( + address, + BundleAccount::new( + original, + present, + storage + .into_iter() + .map(|(k, (o_val, p_val))| (k, StorageSlot::new_changed(o_val, p_val))) + .collect(), + AccountStatus::Changed, + ), + ) + }) + .collect(); + + // Create reverts from iterator. + let reverts = reverts + .into_iter() + .map(|block_reverts| { + block_reverts + .into_iter() + .map(|(address, account, storage)| { + let account = if let Some(account) = account { + if let Some(account) = account { + AccountInfoRevert::RevertTo(account) + } else { + AccountInfoRevert::DeleteIt + } + } else { + AccountInfoRevert::DoNothing + }; + ( + address, + AccountRevert { + account, + storage: storage + .into_iter() + .map(|(k, v)| (k, RevertToSlot::Some(v))) + .collect(), + previous_status: AccountStatus::Changed, + wipe_storage: false, + }, + ) + }) + .collect::>() + }) + .collect::>(); + + Self { + state, + contracts: contracts.into_iter().collect(), + reverts, + } + } + + /// Get account from state + pub fn account(&self, addres: &B160) -> Option<&BundleAccount> { + self.state.get(addres) + } + + /// Get bytecode from state + pub fn bytecode(&self, hash: &B256) -> Option { + self.contracts.get(hash).cloned() + } + + /// Consume `TransitionState` by applying the changes and creating the reverts + pub fn apply_block_substate_and_create_reverts(&mut self, mut transitions: TransitionState) { + let mut reverts = Vec::new(); + for (address, transition) in transitions.take().transitions.into_iter() { + // add new contract if it was created/changed. + if let Some((hash, new_bytecode)) = transition.has_new_contract() { + self.contracts.insert(hash, new_bytecode.clone()); + } + // update state and create revert. + let revert = match self.state.entry(address) { + hash_map::Entry::Occupied(mut entry) => { + // update and create revert if it is present + entry.get_mut().update_and_create_revert(transition) + } + hash_map::Entry::Vacant(entry) => { + // make revert from transition account + let present_bundle = transition.present_bundle_account(); + let revert = transition.create_revert(); + if revert.is_some() { + entry.insert(present_bundle); + } + revert + } + }; + + // append revert if present. + if let Some(revert) = revert { + reverts.push((address, revert)); + } + } + self.reverts.push(reverts); + } + + /// Nuke the bundle state and return sorted plain state. + /// + /// `omit_changed_check` does not check If account is same as + /// original state, this assumption can't be made in cases when + /// we split the bundle state and commit part of it. + pub fn take_sorted_plain_change_inner(&mut self, omit_changed_check: bool) -> StateChangeset { + let mut accounts = Vec::new(); + let mut storage = Vec::new(); + + for (address, account) in self.state.drain() { + // append account info if it is changed. + let was_destroyed = account.was_destroyed(); + if omit_changed_check || account.is_info_changed() { + let mut info = account.info; + if let Some(info) = info.as_mut() { + info.code = None + } + accounts.push((address, info)); + } + + // append storage changes + + // NOTE: Assumption is that revert is going to remova whole plain storage from + // database so we can check if plain state was wiped or not. + let mut account_storage_changed = Vec::with_capacity(account.storage.len()); + if was_destroyed { + // If storage was destroyed that means that storage was wipped. + // In that case we need to check if present storage value is different then ZERO. + for (key, slot) in account.storage { + if omit_changed_check || slot.present_value != U256::ZERO { + account_storage_changed.push((key, slot.present_value)); + } + } + } else { + // if account is not destroyed check if original values was changed. + // so we can update it. + for (key, slot) in account.storage { + if omit_changed_check || slot.is_changed() { + account_storage_changed.push((key, slot.present_value)); + } + } + } + + account_storage_changed.sort_by(|a, b| a.0.cmp(&b.0)); + // append storage changes to account. + storage.push(( + address, + (account.status.was_destroyed(), account_storage_changed), + )); + } + + accounts.par_sort_unstable_by(|a, b| a.0.cmp(&b.0)); + storage.par_sort_unstable_by(|a, b| a.0.cmp(&b.0)); + + let mut contracts = self.contracts.drain().collect::>(); + contracts.par_sort_unstable_by(|a, b| a.0.cmp(&b.0)); + + StateChangeset { + accounts, + storage, + contracts, + } + } + + /// Return and clear all reverts from BundleState, sort them before returning. + pub fn take_reverts(&mut self) -> StateReverts { + let mut state_reverts = StateReverts::default(); + for reverts in self.reverts.drain(..) { + let mut accounts = Vec::new(); + let mut storage = Vec::new(); + for (address, revert_account) in reverts.into_iter() { + match revert_account.account { + AccountInfoRevert::RevertTo(acc) => accounts.push((address, Some(acc))), + AccountInfoRevert::DeleteIt => accounts.push((address, None)), + AccountInfoRevert::DoNothing => (), + } + if revert_account.wipe_storage || !revert_account.storage.is_empty() { + let mut account_storage = Vec::new(); + for (key, revert_slot) in revert_account.storage { + account_storage.push((key, revert_slot.to_previous_value())); + } + account_storage.par_sort_unstable_by(|a, b| a.0.cmp(&b.0)); + storage.push((address, revert_account.wipe_storage, account_storage)); + } + } + accounts.par_sort_unstable_by(|a, b| a.0.cmp(&b.0)); + state_reverts.accounts.push(accounts); + state_reverts.storage.push(storage); + } + + state_reverts + } + + /// Extend the state with state that is build on top of it. + pub fn extend(&mut self, other: Self) { + // Extend the state. + for (address, other) in other.state { + match self.state.entry(address) { + hash_map::Entry::Occupied(mut entry) => { + entry.get_mut().extend(other); + } + hash_map::Entry::Vacant(entry) => { + // just insert if empty + entry.insert(other); + } + } + } + // Contract can be just extended, when counter is introduced we will take into account that. + self.contracts.extend(other.contracts); + // Reverts can be just extended + self.reverts.extend(other.reverts); + } + + /// This will returnd detached lower part of reverts + /// + /// Note that plain state will stay the same and returned BundleState + /// will contain only reverts and will be considered broken. + /// + /// If given number is greater then number of reverts then None is returned. + /// Same if given transition number is zero. + pub fn detach_lower_part_reverts(&mut self, num_of_detachments: usize) -> Option { + if num_of_detachments == 0 { + return None; + } + if num_of_detachments > self.reverts.len() { + return None; + } + // split is done as [0, num) and [num, len]. + let (detach, this) = self.reverts.split_at(num_of_detachments); + + let detached_reverts = detach.to_vec(); + self.reverts = this.to_vec(); + Some(Self { + reverts: detached_reverts, + ..Default::default() + }) + } + + /// Reverse the state changes by N transitions back + pub fn revert(&mut self, mut transition: usize) { + if transition == 0 { + return; + } + + // revert the state. + while let Some(reverts) = self.reverts.pop() { + for (address, revert_account) in reverts.into_iter() { + if let Entry::Occupied(mut entry) = self.state.entry(address) { + if entry.get_mut().revert(revert_account) { + entry.remove(); + } + } else { + unreachable!("Account {address:?} {revert_account:?} for revert should exist"); + } + } + transition -= 1; + if transition == 0 { + // break the loop. + break; + } + } + } +} + +#[cfg(test)] +mod tests { + use revm_interpreter::primitives::KECCAK_EMPTY; + + use crate::{db::StorageWithOriginalValues, TransitionAccount}; + + use super::*; + + #[test] + fn transition_all_states() { + // dummy data + let address = B160([0x01; 20]); + let acc1 = AccountInfo { + balance: U256::from(10), + nonce: 1, + code_hash: KECCAK_EMPTY, + code: None, + }; + + let mut bundle_state = BundleState::default(); + + // have transition from loaded to all other states + + let transition = TransitionAccount { + info: Some(acc1), + status: AccountStatus::InMemoryChange, + previous_info: None, + previous_status: AccountStatus::LoadedNotExisting, + storage: StorageWithOriginalValues::default(), + storage_was_destroyed: false, + }; + + // apply first transition + bundle_state.apply_block_substate_and_create_reverts(TransitionState::with_capacity( + address, + transition.clone(), + )); + } +} diff --git a/crates/revm/src/db/states/cache.rs b/crates/revm/src/db/states/cache.rs new file mode 100644 index 0000000000..4d0a215cbe --- /dev/null +++ b/crates/revm/src/db/states/cache.rs @@ -0,0 +1,149 @@ +use super::{ + plain_account::PlainStorage, transition_account::TransitionAccount, CacheAccount, PlainAccount, +}; +use revm_interpreter::primitives::{AccountInfo, Bytecode, HashMap, State as EVMState, B160, B256}; +/// Cache state contains both modified and original values. +/// +/// Cache state is main state that revm uses to access state. +/// It loads all accounts from database and applies revm output to it. +/// +/// It generates transitions that is used to build BundleState. +#[derive(Debug, Clone)] +pub struct CacheState { + /// Block state account with account state + pub accounts: HashMap, + /// created contracts + /// TODO add bytecode counter for number of bytecodes added/removed. + pub contracts: HashMap, + /// Has EIP-161 state clear enabled (Spurious Dragon hardfork). + pub has_state_clear: bool, +} + +impl Default for CacheState { + fn default() -> Self { + Self::new(true) + } +} + +impl CacheState { + /// New default state. + pub fn new(has_state_clear: bool) -> Self { + Self { + accounts: HashMap::default(), + contracts: HashMap::default(), + has_state_clear, + } + } + + /// Set state clear flag. EIP-161. + pub fn set_state_clear_flag(&mut self, has_state_clear: bool) { + self.has_state_clear = has_state_clear; + } + + /// Helper function that returns all accounts. + /// Used inside tests to generate merkle tree. + pub fn trie_account(&self) -> impl IntoIterator { + self.accounts.iter().filter_map(|(address, account)| { + account + .account + .as_ref() + .map(|plain_acc| (*address, plain_acc)) + }) + } + + /// Insert not existing account. + pub fn insert_not_existing(&mut self, address: B160) { + self.accounts + .insert(address, CacheAccount::new_loaded_not_existing()); + } + + /// Insert Loaded (Or LoadedEmptyEip161 if account is empty) account. + pub fn insert_account(&mut self, address: B160, info: AccountInfo) { + let account = if !info.is_empty() { + CacheAccount::new_loaded(info, HashMap::default()) + } else { + CacheAccount::new_loaded_empty_eip161(HashMap::default()) + }; + self.accounts.insert(address, account); + } + + /// Similar to `insert_account` but with storage. + pub fn insert_account_with_storage( + &mut self, + address: B160, + info: AccountInfo, + storage: PlainStorage, + ) { + let account = if !info.is_empty() { + CacheAccount::new_loaded(info, storage) + } else { + CacheAccount::new_loaded_empty_eip161(storage) + }; + self.accounts.insert(address, account); + } + + /// Apply output of revm execution and create TransactionAccount + /// that is used to build BundleState. + pub fn apply_evm_state(&mut self, evm_state: EVMState) -> Vec<(B160, TransitionAccount)> { + let mut transitions = Vec::with_capacity(evm_state.len()); + for (address, account) in evm_state { + if !account.is_touched() { + // not touched account are never changed. + continue; + } + let this_account = self + .accounts + .get_mut(&address) + .expect("All accounts should be present inside cache"); + + if account.is_selfdestructed() { + // If it is marked as selfdestructed inside revm + // we need to changed state to destroyed. + if let Some(transition) = this_account.selfdestruct() { + transitions.push((address, transition)); + } + continue; + } + if account.is_created() { + // Note: it can happen that created contract get selfdestructed in same block + // that is why is_created is checked after selfdestructed + // + // Note: Create2 opcode (Petersburg) was after state clear EIP (Spurious Dragon) + // + // Note: It is possibility to create KECCAK_EMPTY contract with some storage + // by just setting storage inside CRATE contstructor. Overlap of those contracts + // is not possible because CREATE2 is introduced later. + + transitions.push(( + address, + this_account.newly_created(account.info, account.storage), + )); + } else { + // Account is touched, but not selfdestructed or newly created. + // Account can be touched and not changed. + + // And when empty account is touched it needs to be removed from database. + // EIP-161 state clear + if account.is_empty() { + if self.has_state_clear { + // touch empty account. + if let Some(transition) = this_account.touch_empty_eip161() { + transitions.push((address, transition)); + } + } else { + // if account is empty and state clear is not enabled we should save + // empty account. + if let Some(transition) = + this_account.touch_create_pre_eip161(account.storage) + { + transitions.push((address, transition)); + } + } + } else { + transitions.push((address, this_account.change(account.info, account.storage))); + } + }; + } + transitions + } +} diff --git a/crates/revm/src/db/states/cache_account.rs b/crates/revm/src/db/states/cache_account.rs new file mode 100644 index 0000000000..35d74a6bb5 --- /dev/null +++ b/crates/revm/src/db/states/cache_account.rs @@ -0,0 +1,413 @@ +use super::{ + plain_account::PlainStorage, AccountStatus, PlainAccount, StorageWithOriginalValues, + TransitionAccount, +}; +use revm_interpreter::primitives::{AccountInfo, KECCAK_EMPTY, U256}; +use revm_precompile::HashMap; + +/// Cache account is to store account from database be able +/// to be updated from output of revm and while doing that +/// create TransitionAccount needed for BundleState. +#[derive(Clone, Debug)] +pub struct CacheAccount { + pub account: Option, + pub status: AccountStatus, +} + +impl CacheAccount { + /// Create new account that is loaded from database. + pub fn new_loaded(info: AccountInfo, storage: PlainStorage) -> Self { + Self { + account: Some(PlainAccount { info, storage }), + status: AccountStatus::Loaded, + } + } + + /// Create new account that is loaded empty from database. + pub fn new_loaded_empty_eip161(storage: PlainStorage) -> Self { + Self { + account: Some(PlainAccount::new_empty_with_storage(storage)), + status: AccountStatus::LoadedEmptyEIP161, + } + } + + /// Loaded not existing account. + pub fn new_loaded_not_existing() -> Self { + Self { + account: None, + status: AccountStatus::LoadedNotExisting, + } + } + + /// Create new account that is newly created + pub fn new_newly_created(info: AccountInfo, storage: PlainStorage) -> Self { + Self { + account: Some(PlainAccount { info, storage }), + status: AccountStatus::InMemoryChange, + } + } + + /// Create account that is destroyed. + pub fn new_destroyed() -> Self { + Self { + account: None, + status: AccountStatus::Destroyed, + } + } + + /// Create changed account + pub fn new_changed(info: AccountInfo, storage: PlainStorage) -> Self { + Self { + account: Some(PlainAccount { info, storage }), + status: AccountStatus::Changed, + } + } + + /// Return true if account is some + pub fn is_some(&self) -> bool { + matches!( + self.status, + AccountStatus::Changed + | AccountStatus::InMemoryChange + | AccountStatus::DestroyedChanged + | AccountStatus::Loaded + | AccountStatus::LoadedEmptyEIP161 + ) + } + + /// Return storage slot if it exist. + pub fn storage_slot(&self, slot: U256) -> Option { + self.account + .as_ref() + .and_then(|a| a.storage.get(&slot).cloned()) + } + + /// Fetch account info if it exist. + pub fn account_info(&self) -> Option { + self.account.as_ref().map(|a| a.info.clone()) + } + + /// Desolve account into components. + pub fn into_components(self) -> (Option<(AccountInfo, PlainStorage)>, AccountStatus) { + (self.account.map(|a| a.into_components()), self.status) + } + + /// Account got touched and before EIP161 state clear this account is considered created. + pub fn touch_create_pre_eip161( + &mut self, + storage: StorageWithOriginalValues, + ) -> Option { + let previous_status = self.status; + + self.status = match self.status { + AccountStatus::DestroyedChanged => { + if self + .account + .as_ref() + .map(|a| a.info.is_empty()) + .unwrap_or_default() + { + return None; + } + AccountStatus::DestroyedChanged + } + AccountStatus::Destroyed | AccountStatus::DestroyedAgain => { + AccountStatus::DestroyedChanged + } + AccountStatus::LoadedEmptyEIP161 => { + return None; + } + AccountStatus::InMemoryChange | AccountStatus::LoadedNotExisting => { + AccountStatus::InMemoryChange + } + AccountStatus::Loaded | AccountStatus::Changed => { + unreachable!("Wrong state transition, touch crate is not possible from {self:?}") + } + }; + let plain_storage = storage.iter().map(|(k, v)| (*k, v.present_value)).collect(); + let previous_info = self.account.take().map(|a| a.info); + + self.account = Some(PlainAccount::new_empty_with_storage(plain_storage)); + + Some(TransitionAccount { + info: Some(AccountInfo::default()), + status: self.status, + previous_info, + previous_status, + storage, + storage_was_destroyed: false, + }) + } + + /// Touche empty account, related to EIP-161 state clear. + /// + /// This account returns Transition that is used to create the BundleState. + pub fn touch_empty_eip161(&mut self) -> Option { + let previous_status = self.status; + + // Set account to None. + let previous_info = self.account.take().map(|acc| acc.info); + + // Set account state to Destroyed as we need to clear the storage if it exist. + self.status = match self.status { + AccountStatus::InMemoryChange + | AccountStatus::Destroyed + | AccountStatus::LoadedEmptyEIP161 => { + // account can be created empty them touched. + AccountStatus::Destroyed + } + AccountStatus::LoadedNotExisting => { + // account can be touched but not existing. + // This is a noop. + AccountStatus::LoadedNotExisting + } + AccountStatus::DestroyedAgain | AccountStatus::DestroyedChanged => { + // do nothing + AccountStatus::DestroyedAgain + } + _ => { + // do nothing + unreachable!("Wrong state transition, touch empty is not possible from {self:?}"); + } + }; + if matches!( + previous_status, + AccountStatus::LoadedNotExisting + | AccountStatus::Destroyed + | AccountStatus::DestroyedAgain + ) { + None + } else { + Some(TransitionAccount { + info: None, + status: self.status, + previous_info, + previous_status, + storage: HashMap::default(), + storage_was_destroyed: true, + }) + } + } + + /// Consume self and make account as destroyed. + /// + /// Set account as None and set status to Destroyer or DestroyedAgain. + pub fn selfdestruct(&mut self) -> Option { + // account should be None after selfdestruct so we can take it. + let previous_info = self.account.take().map(|a| a.info); + let previous_status = self.status; + + self.status = match self.status { + AccountStatus::DestroyedChanged + | AccountStatus::DestroyedAgain + | AccountStatus::Destroyed => { + // mark as destroyed again, this can happen if account is created and + // then selfdestructed in same block. + // Note: there is no big difference between Destroyed and DestroyedAgain + // in this case, but was added for clarity. + AccountStatus::DestroyedAgain + } + + _ => AccountStatus::Destroyed, + }; + + if previous_status == AccountStatus::LoadedNotExisting { + // transitions for account loaded as not existing. + self.status = AccountStatus::LoadedNotExisting; + None + } else { + Some(TransitionAccount { + info: None, + status: self.status, + previous_info, + previous_status, + storage: HashMap::new(), + storage_was_destroyed: true, + }) + } + } + + /// Newly created account. + pub fn newly_created( + &mut self, + new_info: AccountInfo, + new_storage: StorageWithOriginalValues, + ) -> TransitionAccount { + let previous_status = self.status; + let previous_info = self.account.take().map(|a| a.info); + + let new_bundle_storage = new_storage + .iter() + .map(|(k, s)| (*k, s.present_value)) + .collect(); + + self.status = match self.status { + // if account was destroyed previously just copy new info to it. + AccountStatus::DestroyedAgain + | AccountStatus::Destroyed + | AccountStatus::DestroyedChanged => AccountStatus::DestroyedChanged, + // if account is loaded from db. + AccountStatus::LoadedNotExisting + // Loaded empty eip161 to creates is not possible as CREATE2 was added after EIP-161 + | AccountStatus::LoadedEmptyEIP161 + | AccountStatus::Loaded + | AccountStatus::Changed + | AccountStatus::InMemoryChange => { + // if account is loaded and not empty this means that account has some balance + // this does not mean that accoun't can be created. + // We are assuming that EVM did necessary checks before allowing account to be created. + AccountStatus::InMemoryChange + } + }; + let transition_account = TransitionAccount { + info: Some(new_info.clone()), + status: self.status, + previous_status, + previous_info, + storage: new_storage, + storage_was_destroyed: false, + }; + self.account = Some(PlainAccount { + info: new_info, + storage: new_bundle_storage, + }); + transition_account + } + + /// Increment balance by `balance` amount. Assume that balance will not + /// overflow or be zero. + /// + /// Note: to skip some edgecases we assume that additional balance is never zero. + /// And as increment is always related to block fee/reward and withdrawals this is correct. + pub fn increment_balance(&mut self, balance: u128) -> TransitionAccount { + self.account_info_change(|info| { + info.balance += U256::from(balance); + }) + .1 + } + + fn account_info_change T>( + &mut self, + change: F, + ) -> (T, TransitionAccount) { + let previous_status = self.status; + let previous_info = self.account_info(); + let mut account = self.account.take().unwrap_or_default(); + let output = change(&mut account.info); + self.account = Some(account); + + self.status = match self.status { + AccountStatus::Loaded => { + // Account that have nonce zero and empty code hash is considered to be fully in memory. + if previous_info.as_ref().map(|a| (a.code_hash, a.nonce)) == Some((KECCAK_EMPTY, 0)) + { + AccountStatus::InMemoryChange + } else { + AccountStatus::Changed + } + } + AccountStatus::LoadedNotExisting + | AccountStatus::LoadedEmptyEIP161 + | AccountStatus::InMemoryChange => AccountStatus::InMemoryChange, + AccountStatus::Changed => AccountStatus::Changed, + AccountStatus::Destroyed + | AccountStatus::DestroyedAgain + | AccountStatus::DestroyedChanged => AccountStatus::DestroyedChanged, + }; + + ( + output, + TransitionAccount { + info: self.account_info(), + status: self.status, + previous_info, + previous_status, + storage: HashMap::new(), + storage_was_destroyed: false, + }, + ) + } + + /// Drain balance from account and return transition and drained amount + /// + /// Used for DAO hardfork transition. + pub fn drain_balance(&mut self) -> (u128, TransitionAccount) { + self.account_info_change(|info| { + let output = info.balance; + info.balance = U256::ZERO; + output.try_into().unwrap() + }) + } + + pub fn change( + &mut self, + new: AccountInfo, + storage: StorageWithOriginalValues, + ) -> TransitionAccount { + let previous_status = self.status; + let previous_info = self.account.as_ref().map(|a| a.info.clone()); + let mut this_storage = self + .account + .take() + .map(|acc| acc.storage) + .unwrap_or_default(); + + this_storage.extend(storage.iter().map(|(k, s)| (*k, s.present_value))); + let changed_account = PlainAccount { + info: new, + storage: this_storage, + }; + + self.status = match self.status { + AccountStatus::Loaded => { + if previous_info.as_ref().map(|a| (a.code_hash, a.nonce)) == Some((KECCAK_EMPTY, 0)) + { + // account is fully in memory + AccountStatus::InMemoryChange + } else { + // can be contract and some of storage slots can be present inside db. + AccountStatus::Changed + } + } + AccountStatus::Changed => { + // Update to new changed state. + AccountStatus::Changed + } + AccountStatus::InMemoryChange => { + // promote to NewChanged. + // Check if account is empty is done outside of this fn. + AccountStatus::InMemoryChange + } + AccountStatus::DestroyedChanged => { + // have same state + AccountStatus::DestroyedChanged + } + AccountStatus::LoadedEmptyEIP161 => { + // Change on empty account, should transfer storage if there is any. + // There is posibility that there are storage inside db. + // That storage is used in merkle tree calculation before state clear EIP. + AccountStatus::InMemoryChange + } + AccountStatus::LoadedNotExisting => { + // if it is loaded not existing and then changed + // This means this is balance transfer that created the account. + AccountStatus::InMemoryChange + } + AccountStatus::Destroyed | AccountStatus::DestroyedAgain => { + // If account is destroyed and then changed this means this is + // balance tranfer. + AccountStatus::DestroyedChanged + } + }; + self.account = Some(changed_account); + + TransitionAccount { + info: self.account.as_ref().map(|a| a.info.clone()), + status: self.status, + previous_info, + previous_status, + storage, + storage_was_destroyed: false, + } + } +} diff --git a/crates/revm/src/db/states/changes.rs b/crates/revm/src/db/states/changes.rs new file mode 100644 index 0000000000..cae855d793 --- /dev/null +++ b/crates/revm/src/db/states/changes.rs @@ -0,0 +1,32 @@ +use revm_interpreter::primitives::{AccountInfo, Bytecode, B160, B256, U256}; + +/// Sorted accounts/storages/contracts for inclusion into database. +/// Structure is made so it is easier to apply dirrectly to database +/// that mostly have saparate tables to store account/storage/contract data. +#[derive(Clone, Debug, Default)] +pub struct StateChangeset { + /// Vector of account presorted by address, with removed contracts bytecode + pub accounts: Vec<(B160, Option)>, + /// Vector of storage presorted by address + /// First bool is indicatior if storage needs to be dropped. + pub storage: StorageChangeset, + /// Vector of contracts presorted by bytecode hash + pub contracts: Vec<(B256, Bytecode)>, +} + +/// Storage changeset +pub type StorageChangeset = Vec<(B160, (bool, Vec<(U256, U256)>))>; + +#[derive(Clone, Debug, Default)] +pub struct StateReverts { + /// Vector of account presorted by anddress, with removed cotracts bytecode + /// + /// Note: AccountInfo None means that account needs to be removed. + pub accounts: Vec)>>, + /// Vector of storage presorted by address + /// U256::ZERO means that storage needs to be removed. + pub storage: StorageRevert, +} + +/// Storage reverts +pub type StorageRevert = Vec)>>; diff --git a/crates/revm/src/db/states/plain_account.rs b/crates/revm/src/db/states/plain_account.rs new file mode 100644 index 0000000000..9d5c891f17 --- /dev/null +++ b/crates/revm/src/db/states/plain_account.rs @@ -0,0 +1,44 @@ +use revm_interpreter::primitives::{AccountInfo, HashMap, StorageSlot, U256}; + +/// TODO rename this to BundleAccount. As for the block level we have original state. +#[derive(Clone, Debug, Default)] +pub struct PlainAccount { + pub info: AccountInfo, + pub storage: PlainStorage, +} + +impl PlainAccount { + pub fn new_empty_with_storage(storage: PlainStorage) -> Self { + Self { + info: AccountInfo::default(), + storage, + } + } + + pub fn into_components(self) -> (AccountInfo, PlainStorage) { + (self.info, self.storage) + } +} + +/// TODO Rename this to become StorageWithOriginalValues or something like that. +/// This is used inside EVM and for block state. It is needed for block state to +/// be able to create changeset agains bundle state. +/// +/// This storage represent values that are before block changed. +/// +/// Note: Storage that we get EVM contains original values before t +pub type StorageWithOriginalValues = HashMap; + +/// Simple plain storage that does not have previous value. +/// This is used for loading from database, cache and for bundle state. +/// +pub type PlainStorage = HashMap; + +impl From for PlainAccount { + fn from(info: AccountInfo) -> Self { + Self { + info, + storage: HashMap::new(), + } + } +} diff --git a/crates/revm/src/db/states/reverts.rs b/crates/revm/src/db/states/reverts.rs new file mode 100644 index 0000000000..19d4685417 --- /dev/null +++ b/crates/revm/src/db/states/reverts.rs @@ -0,0 +1,126 @@ +use revm_interpreter::primitives::{AccountInfo, HashMap, U256}; + +use super::{AccountStatus, BundleAccount, StorageWithOriginalValues}; + +/// Assumption is that Revert can return full state from any future state to any past state. +/// +/// It is created when new account state is applied to old account state. +/// And it is used to revert new account state to the old account state. +/// +/// AccountRevert is structured in this way as we need to save it inside database. +/// And we need to be able to read it from database. +#[derive(Clone, Default, Debug, PartialEq, Eq)] +pub struct AccountRevert { + pub account: AccountInfoRevert, + pub storage: HashMap, + pub previous_status: AccountStatus, + pub wipe_storage: bool, +} + +impl AccountRevert { + /// Very similar to new_selfdestructed but it will add additional zeros (RevertToSlot::Destroyed) + /// for the storage that are set if account is again created. + pub fn new_selfdestructed_again( + status: AccountStatus, + account: AccountInfo, + mut previous_storage: StorageWithOriginalValues, + updated_storage: StorageWithOriginalValues, + ) -> Self { + // Take present storage values as the storages that we are going to revert to. + // As those values got destroyed. + let mut previous_storage: HashMap = previous_storage + .drain() + .map(|(key, value)| (key, RevertToSlot::Some(value.present_value))) + .collect(); + for (key, _) in updated_storage { + previous_storage + .entry(key) + .or_insert(RevertToSlot::Destroyed); + } + AccountRevert { + account: AccountInfoRevert::RevertTo(account), + storage: previous_storage, + previous_status: status, + wipe_storage: false, + } + } + + /// Create revert for states that were before selfdestruct. + pub fn new_selfdestructed_from_bundle( + bundle_account: &mut BundleAccount, + updated_storage: &StorageWithOriginalValues, + ) -> Option { + match bundle_account.status { + AccountStatus::InMemoryChange + | AccountStatus::Changed + | AccountStatus::LoadedEmptyEIP161 + | AccountStatus::Loaded => { + let mut ret = AccountRevert::new_selfdestructed_again( + bundle_account.status, + bundle_account.info.clone().unwrap_or_default(), + bundle_account.storage.drain().collect(), + updated_storage.clone(), + ); + ret.wipe_storage = true; + Some(ret) + } + _ => None, + } + } + + /// Create new selfdestruct revert. + pub fn new_selfdestructed( + status: AccountStatus, + account: AccountInfo, + mut storage: StorageWithOriginalValues, + ) -> Self { + // Zero all present storage values and save present values to AccountRevert. + let previous_storage = storage + .iter_mut() + .map(|(key, value)| { + // take previous value and set ZERO as storage got destroyed. + (*key, RevertToSlot::Some(value.present_value)) + }) + .collect(); + + Self { + account: AccountInfoRevert::RevertTo(account), + storage: previous_storage, + previous_status: status, + wipe_storage: true, + } + } +} + +#[derive(Clone, Default, Debug, PartialEq, Eq)] +pub enum AccountInfoRevert { + #[default] + /// Nothing changed + DoNothing, + /// Account was created and on revert we need to remove it with all storage. + DeleteIt, + /// Account was changed and on revert we need to put old state. + RevertTo(AccountInfo), +} + +/// So storage can have multiple types: +/// * Zero, on revert remove plain state. +/// * Value, on revert set this value +/// * Destroyed, should be removed on revert but on Revert set it as zero. +/// +/// Note: It is completely different state if Storage is Zero or Some or if Storage was +/// Destroyed. Because if it is destroyed, previous values can be found in database or it can be zero. +#[derive(Clone, Debug, PartialEq, Eq)] +pub enum RevertToSlot { + Some(U256), + Destroyed, +} + +impl RevertToSlot { + pub fn to_previous_value(self) -> U256 { + match self { + RevertToSlot::Some(value) => value, + RevertToSlot::Destroyed => U256::ZERO, + } + } +} diff --git a/crates/revm/src/db/states/state.rs b/crates/revm/src/db/states/state.rs new file mode 100644 index 0000000000..e6066e1630 --- /dev/null +++ b/crates/revm/src/db/states/state.rs @@ -0,0 +1,226 @@ +use super::{ + cache::CacheState, plain_account::PlainStorage, BundleState, CacheAccount, TransitionState, +}; +use crate::TransitionAccount; +use revm_interpreter::primitives::{ + db::{Database, DatabaseCommit}, + hash_map, Account, AccountInfo, Bytecode, HashMap, B160, B256, U256, +}; + +/// State of blockchain. +/// +/// State clear flag is set inside CacheState and by default it is enabled. +/// If you want to disable it use `set_state_clear_flag` function. +pub struct State<'a, DBError> { + /// Cached state contains both changed from evm executiong and cached/loaded account/storages + /// from database. This allows us to have only one layer of cache where we can fetch data. + /// Additionaly we can introuduce some preloading of data from database. + pub cache: CacheState, + /// Optional database that we use to fetch data from. If database is not present, we will + /// return not existing account and storage. + /// + /// Note: It is marked as Send so database can be shared between threads. + pub database: Box + Send + 'a>, + /// Block state, it aggregates transactions transitions into one state. + /// + /// Build reverts and state that gets applied to the state. + pub transition_state: Option, + /// After block is finishes we merge those changes inside bundle. + /// Bundle is used to update database and create changesets. + /// + /// Bundle state can be present if we want to use preloaded bundle. + pub bundle_state: Option, + /// Addition layer that is going to be used to fetched values before fetching values + /// from database. + /// + /// Bundle is the main output of the state execution and this allows setting previous bundle + /// and using its values for execution. + pub use_preloaded_bundle: bool, + // if enabled USE Background thread for transitions and bundle + //pub use_background_thread: bool, +} + +impl<'a, DBError> State<'a, DBError> { + /// Iterate over received balances and increment all account balances. + /// If account is not found inside cache state it will be loaded from database. + /// + /// Update will create transitions for all accounts that are updated. + pub fn increment_balances( + &mut self, + balances: impl IntoIterator, + ) -> Result<(), DBError> { + // make transition and update cache state + let mut transitions = Vec::new(); + for (address, balance) in balances { + let original_account = self.load_cache_account(address)?; + transitions.push((address, original_account.increment_balance(balance))) + } + // append transition + if let Some(s) = self.transition_state.as_mut() { + s.add_transitions(transitions) + } + Ok(()) + } + + /// Drain balances from given account and return those values. + /// + /// It is used for DAO hardfork state change to move values from given accounts. + pub fn drain_balances( + &mut self, + addresses: impl IntoIterator, + ) -> Result, DBError> { + // make transition and update cache state + let mut transitions = Vec::new(); + let mut balances = Vec::new(); + for address in addresses { + let original_account = self.load_cache_account(address)?; + let (balance, transition) = original_account.drain_balance(); + balances.push(balance); + transitions.push((address, transition)) + } + // append transition + if let Some(s) = self.transition_state.as_mut() { + s.add_transitions(transitions) + } + Ok(balances) + } + + /// State clear EIP-161 is enabled in Spurious Dragon hardfork. + pub fn set_state_clear_flag(&mut self, has_state_clear: bool) { + self.cache.set_state_clear_flag(has_state_clear); + } + + pub fn insert_not_existing(&mut self, address: B160) { + self.cache.insert_not_existing(address) + } + + pub fn insert_account(&mut self, address: B160, info: AccountInfo) { + self.cache.insert_account(address, info) + } + + pub fn insert_account_with_storage( + &mut self, + address: B160, + info: AccountInfo, + storage: PlainStorage, + ) { + self.cache + .insert_account_with_storage(address, info, storage) + } + + /// Apply evm transitions to transition state. + fn apply_transition(&mut self, transitions: Vec<(B160, TransitionAccount)>) { + // add transition to transition state. + if let Some(s) = self.transition_state.as_mut() { + s.add_transitions(transitions) + } + } + + /// Take all transitions and merge them inside bundle state. + /// This action will create final post state and all reverts so that + /// we at any time revert state of bundle to the state before transition + /// is applied. + pub fn merge_transitions(&mut self) { + if let Some(transition_state) = self.transition_state.as_mut() { + let transition_state = transition_state.take(); + + if self.bundle_state.is_none() { + self.bundle_state = Some(BundleState::default()); + } + + self.bundle_state + .as_mut() + .unwrap() + .apply_block_substate_and_create_reverts(transition_state); + } + } + + pub fn load_cache_account(&mut self, address: B160) -> Result<&mut CacheAccount, DBError> { + match self.cache.accounts.entry(address) { + hash_map::Entry::Vacant(entry) => { + let info = self.database.basic(address)?; + let bundle_account = match info { + None => CacheAccount::new_loaded_not_existing(), + Some(acc) if acc.is_empty() => { + CacheAccount::new_loaded_empty_eip161(HashMap::new()) + } + Some(acc) => CacheAccount::new_loaded(acc, HashMap::new()), + }; + Ok(entry.insert(bundle_account)) + } + hash_map::Entry::Occupied(entry) => Ok(entry.into_mut()), + } + } + + /// Takes changeset and reverts from state and replaces it with empty one. + /// This will trop pending Transition and any transitions would be lost. + /// + /// TODO make cache aware of transitions dropping by having global transition counter. + pub fn take_bundle(&mut self) -> BundleState { + std::mem::take(self.bundle_state.as_mut().unwrap()) + } +} + +impl<'a, DBError> Database for State<'a, DBError> { + type Error = DBError; + + fn basic(&mut self, address: B160) -> Result, Self::Error> { + self.load_cache_account(address).map(|a| a.account_info()) + } + + fn code_by_hash( + &mut self, + code_hash: revm_interpreter::primitives::B256, + ) -> Result { + let res = match self.cache.contracts.entry(code_hash) { + hash_map::Entry::Occupied(entry) => Ok(entry.get().clone()), + hash_map::Entry::Vacant(entry) => { + let code = self.database.code_by_hash(code_hash)?; + entry.insert(code.clone()); + Ok(code) + } + }; + res + } + + fn storage(&mut self, address: B160, index: U256) -> Result { + // Account is guaranteed to be loaded. + if let Some(account) = self.cache.accounts.get_mut(&address) { + // account will always be some, but if it is not, U256::ZERO will be returned. + let is_storage_known = account.status.storage_known(); + Ok(account + .account + .as_mut() + .map(|account| match account.storage.entry(index) { + hash_map::Entry::Occupied(entry) => Ok(*entry.get()), + hash_map::Entry::Vacant(entry) => { + // if account was destroyed or account is newely build + // we return zero and dont ask detabase. + let value = if is_storage_known { + U256::ZERO + } else { + self.database.storage(address, index)? + }; + entry.insert(value); + Ok(value) + } + }) + .transpose()? + .unwrap_or_default()) + } else { + unreachable!("For accessing any storage account is guaranteed to be loaded beforehand") + } + } + + fn block_hash(&mut self, number: U256) -> Result { + // TODO maybe cache it. + self.database.block_hash(number) + } +} + +impl<'a, DBError> DatabaseCommit for State<'a, DBError> { + fn commit(&mut self, evm_state: HashMap) { + let transitions = self.cache.apply_evm_state(evm_state); + self.apply_transition(transitions); + } +} diff --git a/crates/revm/src/db/states/state_builder.rs b/crates/revm/src/db/states/state_builder.rs new file mode 100644 index 0000000000..f562bea1b0 --- /dev/null +++ b/crates/revm/src/db/states/state_builder.rs @@ -0,0 +1,136 @@ +use super::{cache::CacheState, BundleState, State, TransitionState}; +use crate::db::EmptyDB; +use core::convert::Infallible; +use revm_interpreter::primitives::db::Database; + +/// Allows building of State and initializing it with different options. +pub struct StateBuilder<'a, DBError> { + pub with_state_clear: bool, + /// Optional database that we use to fetch data from. If database is not present, we will + /// return not existing account and storage. + /// + /// Note: It is marked as Send so database can be shared between threads. + pub database: Box + Send + 'a>, + /// if there is prestate that we want to use. + /// This would mean that we have additional state layer between evm and disk/database. + pub with_bundle_prestate: Option, + /// This will initialize cache to this state. + pub with_cache_prestate: Option, + /// Do we want to create reverts and update bundle state. + /// Default is true. + pub without_bundle_update: bool, + /// Do we want to merge transitions in background. + /// This will allows evm to continue executing. + /// Default is false. + pub with_background_transition_merge: bool, +} + +impl Default for StateBuilder<'_, Infallible> { + fn default() -> Self { + Self { + with_state_clear: true, + database: Box::::default(), + with_cache_prestate: None, + with_bundle_prestate: None, + without_bundle_update: false, + with_background_transition_merge: false, + } + } +} + +impl<'a, DBError> StateBuilder<'a, DBError> { + /// Create default instance of builder. + pub fn new() -> StateBuilder<'a, Infallible> { + StateBuilder::<'a, Infallible>::default() + } + + pub fn with_database( + self, + database: Box + Send + 'a>, + ) -> StateBuilder<'a, NewDBError> { + // cast to the different database, + // Note that we return different type depending of the database NewDBError. + StateBuilder { + with_state_clear: self.with_state_clear, + database, + with_cache_prestate: self.with_cache_prestate, + with_bundle_prestate: self.with_bundle_prestate, + without_bundle_update: self.without_bundle_update, + with_background_transition_merge: self.with_background_transition_merge, + } + } + + /// By default state clear flag is enabled but for initial sync on mainnet + /// we want to disable it so proper consensus changes are in place. + pub fn without_state_clear(self) -> Self { + Self { + with_state_clear: false, + ..self + } + } + + /// Allows setting prestate that is going to be used for execution. + /// This bundle state will act as additional layer of cache. + /// and State after not finding data inside StateCache will try to find it inside BundleState. + /// + /// On update Bundle state will be changed and updated. + pub fn with_bundle_prestate(self, bundle: BundleState) -> Self { + Self { + with_bundle_prestate: Some(bundle), + ..self + } + } + + /// Dont make transitions and dont update bundle state. + /// + /// This is good option if we dont care about creating reverts + /// or getting output of changed states. + pub fn without_bundle_update(self) -> Self { + Self { + without_bundle_update: true, + ..self + } + } + + /// It will use different cache for the state. If set, it will ignore bundle prestate. + /// and will ignore `without_state_clear` flag as cache contains its own state_clear flag. + /// + /// This is useful for testing. + pub fn with_cached_prestate(self, cache: CacheState) -> Self { + Self { + with_cache_prestate: Some(cache), + ..self + } + } + + /// Starts the thread that will take transitions and do merge to the bundle state + /// in the background. + pub fn with_background_transition_merge(self) -> Self { + Self { + with_background_transition_merge: true, + ..self + } + } + + pub fn build(mut self) -> State<'a, DBError> { + let use_preloaded_bundle = if self.with_cache_prestate.is_some() { + self.with_bundle_prestate = None; + false + } else { + self.with_bundle_prestate.is_some() + }; + State { + cache: self + .with_cache_prestate + .unwrap_or(CacheState::new(self.with_state_clear)), + database: self.database, + transition_state: if self.without_bundle_update { + None + } else { + Some(TransitionState::default()) + }, + bundle_state: self.with_bundle_prestate, + use_preloaded_bundle, + } + } +} diff --git a/crates/revm/src/db/states/transition_account.rs b/crates/revm/src/db/states/transition_account.rs new file mode 100644 index 0000000000..9904afc69b --- /dev/null +++ b/crates/revm/src/db/states/transition_account.rs @@ -0,0 +1,103 @@ +use super::{AccountRevert, BundleAccount, StorageWithOriginalValues}; +use crate::db::AccountStatus; +use revm_interpreter::primitives::{AccountInfo, Bytecode, B256}; + +/// Account Created when EVM state is merged to cache state. +/// And it is send to Block state. +/// +/// It is used when block state gets merged to bundle state to +/// create needed Reverts. +#[derive(Clone, Debug, Default)] +pub struct TransitionAccount { + pub info: Option, + pub status: AccountStatus, + /// Previous account info is needed for account that got initialy loaded. + /// Initialy loaded account are not present inside bundle and are needed + /// to generate Reverts. + pub previous_info: Option, + /// Mostly needed when previous status Loaded/LoadedEmpty. + pub previous_status: AccountStatus, + /// Storage contains both old and new account + pub storage: StorageWithOriginalValues, + /// If there is transition that clears the storage we shold mark it here and + /// delete all storages in BundleState. This flag is needed if we have transition + /// between Destroyed states from DestroyedChanged-> DestroyedAgain-> DestroyedChanged + /// in the end transition that we would have would be `DestroyedChanged->DestroyedChanged` + /// and with only that info we coudn't decide what to do. + pub storage_was_destroyed: bool, +} + +impl TransitionAccount { + /// Create new LoadedEmpty account. + pub fn new_empty_eip161(storage: StorageWithOriginalValues) -> Self { + Self { + info: Some(AccountInfo::default()), + status: AccountStatus::InMemoryChange, + previous_info: None, + previous_status: AccountStatus::LoadedNotExisting, + storage, + storage_was_destroyed: false, + } + } + + /// Return new contract bytecode if it is changed or newly created. + pub fn has_new_contract(&self) -> Option<(B256, &Bytecode)> { + let present_new_codehash = self.info.as_ref().map(|info| &info.code_hash); + let previous_codehash = self.previous_info.as_ref().map(|info| &info.code_hash); + if present_new_codehash != previous_codehash { + return self + .info + .as_ref() + .and_then(|info| info.code.as_ref().map(|c| (info.code_hash, c))); + } + None + } + + /// Update new values of transition. Dont override old values + /// both account info and old storages need to be left intact. + pub fn update(&mut self, other: Self) { + self.info = other.info.clone(); + self.status = other.status; + + // if transition is from some to destroyed drop the storage. + // This need to be done here as it is one increment of the state. + if matches!( + other.status, + AccountStatus::Destroyed | AccountStatus::DestroyedAgain + ) { + self.storage = other.storage; + self.storage_was_destroyed = true; + } else { + // update changed values to this transition. + for (key, slot) in other.storage.into_iter() { + self.storage.entry(key).or_insert(slot).present_value = slot.present_value; + } + } + } + + /// Consume Self and create account revert from it. + pub fn create_revert(self) -> Option { + let mut previous_account = self.original_bundle_account(); + previous_account.update_and_create_revert(self) + } + + /// Present bundle account + pub fn present_bundle_account(&self) -> BundleAccount { + BundleAccount { + info: self.info.clone(), + original_info: self.previous_info.clone(), + storage: self.storage.clone(), + status: self.status, + } + } + + /// Original bundle account + fn original_bundle_account(&self) -> BundleAccount { + BundleAccount { + info: self.previous_info.clone(), + original_info: self.previous_info.clone(), + storage: StorageWithOriginalValues::new(), + status: self.previous_status, + } + } +} diff --git a/crates/revm/src/db/states/transition_state.rs b/crates/revm/src/db/states/transition_state.rs new file mode 100644 index 0000000000..b96fff49c7 --- /dev/null +++ b/crates/revm/src/db/states/transition_state.rs @@ -0,0 +1,44 @@ +use super::TransitionAccount; +use revm_interpreter::primitives::{hash_map::Entry, HashMap, B160}; + +#[derive(Clone, Debug)] +pub struct TransitionState { + /// Block state account with account state + pub transitions: HashMap, +} + +impl Default for TransitionState { + fn default() -> Self { + // be default make state clear EIP enabled + TransitionState { + transitions: HashMap::new(), + } + } +} + +impl TransitionState { + /// Create new transition state with one transition. + pub fn with_capacity(address: B160, transition: TransitionAccount) -> Self { + let mut transitions = HashMap::new(); + transitions.insert(address, transition); + TransitionState { transitions } + } + /// Return transition id and all account transitions. Leave empty transition map. + pub fn take(&mut self) -> TransitionState { + core::mem::take(self) + } + + pub fn add_transitions(&mut self, transitions: Vec<(B160, TransitionAccount)>) { + for (address, account) in transitions { + match self.transitions.entry(address) { + Entry::Occupied(entry) => { + let entry = entry.into_mut(); + entry.update(account); + } + Entry::Vacant(entry) => { + entry.insert(account); + } + } + } + } +} diff --git a/crates/revm/src/evm_impl.rs b/crates/revm/src/evm_impl.rs index 4adfa157ac..97c8e71a7d 100644 --- a/crates/revm/src/evm_impl.rs +++ b/crates/revm/src/evm_impl.rs @@ -757,7 +757,6 @@ impl<'a, GSPEC: Spec, DB: Database + 'a, const INSPECT: bool> Host .load_code(address, db) .map_err(|e| *error = Some(e)) .ok()?; - if acc.is_empty() { return Some((B256::zero(), is_cold)); } diff --git a/crates/revm/src/journaled_state.rs b/crates/revm/src/journaled_state.rs index 55d0a957c8..362a373022 100644 --- a/crates/revm/src/journaled_state.rs +++ b/crates/revm/src/journaled_state.rs @@ -5,8 +5,8 @@ use crate::primitives::{ }; use alloc::{vec, vec::Vec}; use core::mem::{self}; -use revm_interpreter::primitives::Spec; use revm_interpreter::primitives::SpecId::SPURIOUS_DRAGON; +use revm_interpreter::primitives::{Spec, PRECOMPILE3}; #[derive(Debug, Clone, Eq, PartialEq)] #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] @@ -61,6 +61,10 @@ pub enum JournalEntry { NonceChange { address: B160, //geth has nonce value, }, + /// Create account: + /// Actions: Mark account as created + /// Revert: Unmart account as created and reset nonce to zero. + AccountCreated { address: B160 }, /// It is used to track both storage change and hot load of storage slot. For hot load in regard /// to EIP-2929 AccessList had_value will be None /// Action: Storage change or hot load @@ -81,7 +85,7 @@ pub enum JournalEntry { /// Code changed /// Action: Account code changed /// Revert: Revert to previous bytecode. - CodeChange { address: B160, had_code: Bytecode }, + CodeChange { address: B160 }, } /// SubRoutine checkpoint that will help us to go back from this @@ -168,12 +172,9 @@ impl JournaledState { self.journal .last_mut() .unwrap() - .push(JournalEntry::CodeChange { - address, - had_code: code.clone(), - }); + .push(JournalEntry::CodeChange { address }); - account.info.code_hash = code.hash(); + account.info.code_hash = code.hash_slow(); account.info.code = Some(code); } @@ -271,6 +272,9 @@ impl JournaledState { // set account status to created. account.mark_created(); + + // this entry will revert set nonce. + last_journal.push(JournalEntry::AccountCreated { address }); account.info.code = None; // Set all storages to default value. They need to be present to act as accessed slots in access list. @@ -295,8 +299,8 @@ impl JournaledState { // EIP-161: State trie clearing (invariant-preserving alternative) if SPEC::enabled(SPURIOUS_DRAGON) { + // nonce is going to be reset to zero in AccountCreated journal entry. account.info.nonce = 1; - last_journal.push(JournalEntry::NonceChange { address }); } // Sub balance from caller @@ -343,14 +347,9 @@ impl JournaledState { journal_entries: Vec, is_spurious_dragon_enabled: bool, ) { - const PRECOMPILE3: B160 = - B160([0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 3]); for entry in journal_entries.into_iter().rev() { match entry { JournalEntry::AccountLoaded { address } => { - if is_spurious_dragon_enabled && address == PRECOMPILE3 { - continue; - } state.remove(&address); } JournalEntry::AccountTouched { address } => { @@ -393,6 +392,11 @@ impl JournaledState { JournalEntry::NonceChange { address } => { state.get_mut(&address).unwrap().info.nonce -= 1; } + JournalEntry::AccountCreated { address } => { + let account = &mut state.get_mut(&address).unwrap(); + account.unmark_created(); + account.info.nonce = 0; + } JournalEntry::StorageChange { address, key, @@ -419,10 +423,10 @@ impl JournaledState { transient_storage.insert(tkey, had_value); } } - JournalEntry::CodeChange { address, had_code } => { + JournalEntry::CodeChange { address } => { let acc = state.get_mut(&address).unwrap(); - acc.info.code_hash = had_code.hash(); - acc.info.code = Some(had_code); + acc.info.code_hash = KECCAK_EMPTY; + acc.info.code = None; } } } @@ -626,7 +630,8 @@ impl JournaledState { db: &mut DB, ) -> Result<(U256, bool), DB::Error> { let account = self.state.get_mut(&address).unwrap(); // asume acc is hot - let is_newly_created = account.is_newly_created(); + // only if account is created in this tx we can assume that storage is empty. + let is_newly_created = account.is_created(); let load = match account.storage.entry(key) { Entry::Occupied(occ) => (occ.get().present_value, false), Entry::Vacant(vac) => { diff --git a/crates/revm/src/lib.rs b/crates/revm/src/lib.rs index 39f030d57a..2517666237 100644 --- a/crates/revm/src/lib.rs +++ b/crates/revm/src/lib.rs @@ -12,7 +12,10 @@ compile_error!("`with-serde` feature has been renamed to `serde`."); pub(crate) const USE_GAS: bool = !cfg!(feature = "no_gas_measuring"); pub type DummyStateDB = InMemoryDB; -pub use db::{Database, DatabaseCommit, InMemoryDB}; +#[cfg(feature = "std")] +pub use db::{CacheState, StateBuilder, TransitionAccount, TransitionState}; + +pub use db::{Database, DatabaseCommit, InMemoryDB, State}; pub use evm::{evm_inner, new, EVM}; pub use evm_impl::EVMData; pub use journaled_state::{JournalEntry, JournaledState};