Skip to content
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions crates/database/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

## [Unreleased]

### Changed

- Fields related to state changes are moved out from `CacheDB` into `Cache` (#2131)

## [1.0.0-alpha.1](https://github.com/bluealloy/revm/releases/tag/revm-database-v1.0.0-alpha.1) - 2025-02-16

### Added
Expand Down
101 changes: 66 additions & 35 deletions crates/database/src/in_memory_db.rs
Original file line number Diff line number Diff line change
@@ -1,22 +1,24 @@
use core::convert::Infallible;
use database_interface::{Database, DatabaseCommit, DatabaseRef, EmptyDB};
use primitives::{address, hash_map::Entry, Address, HashMap, Log, B256, KECCAK_EMPTY, U256};
use primitives::{
address,
hash_map::{Entry, HashMap},
Address, Log, B256, KECCAK_EMPTY, U256,
};
use state::{Account, AccountInfo, Bytecode};
use std::vec::Vec;

/// A [Database] implementation that stores all state changes in memory.
pub type InMemoryDB = CacheDB<EmptyDB>;

/// A [Database] implementation that stores all state changes in memory.
///
/// This implementation wraps a [DatabaseRef] that is used to load data ([AccountInfo]).
/// A cache used in [CacheDB]. Its kept separate so it can be used independently.
///
/// Accounts and code are stored in two separate maps, the `accounts` map maps addresses to [DbAccount],
/// whereas contracts are identified by their code hash, and are stored in the `contracts` map.
/// The [DbAccount] holds the code hash of the contract, which is used to look up the contract in the `contracts` map.
#[derive(Debug, Clone)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct CacheDB<ExtDB> {
pub struct Cache {
/// Account info where None means it is not existing. Not existing state is needed for Pre TANGERINE forks.
/// `code` is always `None`, and bytecode can be found in `contracts`.
pub accounts: HashMap<Address, DbAccount>,
Expand All @@ -26,6 +28,31 @@ pub struct CacheDB<ExtDB> {
pub logs: Vec<Log>,
/// All cached block hashes from the [DatabaseRef].
pub block_hashes: HashMap<U256, B256>,
}

impl Default for Cache {
fn default() -> Self {
let mut contracts = HashMap::new();
contracts.insert(KECCAK_EMPTY, Bytecode::default());
contracts.insert(B256::ZERO, Bytecode::default());

Cache {
accounts: HashMap::default(),
contracts,
logs: Vec::default(),
block_hashes: HashMap::default(),
}
}
}

/// A [Database] implementation that stores all state changes in memory.
///
/// This implementation wraps a [DatabaseRef] that is used to load data ([AccountInfo]).
#[derive(Debug, Clone)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct CacheDB<ExtDB> {
/// The cache that stores all state changes.
pub cache: Cache,
/// The underlying database ([DatabaseRef]) that is used to load data.
///
/// Note: This is read-only, data is never written to this database.
Expand All @@ -48,17 +75,20 @@ impl<ExtDb> CacheDB<CacheDB<ExtDb>> {
/// - Block hashes are overridden with outer block hashes
pub fn flatten(self) -> CacheDB<ExtDb> {
let CacheDB {
accounts,
contracts,
logs,
block_hashes,
cache:
Cache {
accounts,
contracts,
logs,
block_hashes,
},
db: mut inner,
} = self;

inner.accounts.extend(accounts);
inner.contracts.extend(contracts);
inner.logs.extend(logs);
inner.block_hashes.extend(block_hashes);
inner.cache.accounts.extend(accounts);
inner.cache.contracts.extend(contracts);
inner.cache.logs.extend(logs);
inner.cache.block_hashes.extend(block_hashes);
inner
}

Expand All @@ -71,14 +101,8 @@ impl<ExtDb> CacheDB<CacheDB<ExtDb>> {
impl<ExtDB> CacheDB<ExtDB> {
/// Creates a new cache with the given external database.
pub fn new(db: ExtDB) -> Self {
let mut contracts = HashMap::default();
contracts.insert(KECCAK_EMPTY, Bytecode::default());
contracts.insert(B256::ZERO, Bytecode::default());
Self {
accounts: HashMap::default(),
contracts,
logs: Vec::default(),
block_hashes: HashMap::default(),
cache: Cache::default(),
db,
}
}
Expand All @@ -94,7 +118,8 @@ impl<ExtDB> CacheDB<ExtDB> {
if account.code_hash == KECCAK_EMPTY {
account.code_hash = code.hash_slow();
}
self.contracts
self.cache
.contracts
.entry(account.code_hash)
.or_insert_with(|| code.clone());
}
Expand All @@ -107,7 +132,7 @@ impl<ExtDB> CacheDB<ExtDB> {
/// Inserts account info but not override storage
pub fn insert_account_info(&mut self, address: Address, mut info: AccountInfo) {
self.insert_contract(&mut info);
self.accounts.entry(address).or_default().info = info;
self.cache.accounts.entry(address).or_default().info = info;
}

/// Wraps the cache in a [CacheDB], creating a nested cache.
Expand All @@ -122,7 +147,7 @@ impl<ExtDB: DatabaseRef> CacheDB<ExtDB> {
/// If the account was not found in the cache, it will be loaded from the underlying database.
pub fn load_account(&mut self, address: Address) -> Result<&mut DbAccount, ExtDB::Error> {
let db = &self.db;
match self.accounts.entry(address) {
match self.cache.accounts.entry(address) {
Entry::Occupied(entry) => Ok(entry.into_mut()),
Entry::Vacant(entry) => Ok(entry.insert(
db.basic_ref(address)?
Expand Down Expand Up @@ -167,7 +192,7 @@ impl<ExtDB> DatabaseCommit for CacheDB<ExtDB> {
continue;
}
if account.is_selfdestructed() {
let db_account = self.accounts.entry(address).or_default();
let db_account = self.cache.accounts.entry(address).or_default();
db_account.storage.clear();
db_account.account_state = AccountState::NotExisting;
db_account.info = AccountInfo::default();
Expand All @@ -176,7 +201,7 @@ impl<ExtDB> DatabaseCommit for CacheDB<ExtDB> {
let is_newly_created = account.is_created();
self.insert_contract(&mut account.info);

let db_account = self.accounts.entry(address).or_default();
let db_account = self.cache.accounts.entry(address).or_default();
db_account.info = account.info;

db_account.account_state = if is_newly_created {
Expand All @@ -202,7 +227,7 @@ impl<ExtDB: DatabaseRef> Database for CacheDB<ExtDB> {
type Error = ExtDB::Error;

fn basic(&mut self, address: Address) -> Result<Option<AccountInfo>, Self::Error> {
let basic = match self.accounts.entry(address) {
let basic = match self.cache.accounts.entry(address) {
Entry::Occupied(entry) => entry.into_mut(),
Entry::Vacant(entry) => entry.insert(
self.db
Expand All @@ -218,7 +243,7 @@ impl<ExtDB: DatabaseRef> Database for CacheDB<ExtDB> {
}

fn code_by_hash(&mut self, code_hash: B256) -> Result<Bytecode, Self::Error> {
match self.contracts.entry(code_hash) {
match self.cache.contracts.entry(code_hash) {
Entry::Occupied(entry) => Ok(entry.get().clone()),
Entry::Vacant(entry) => {
// If you return code bytes when basic fn is called this function is not needed.
Expand All @@ -231,7 +256,7 @@ impl<ExtDB: DatabaseRef> Database for CacheDB<ExtDB> {
///
/// It is assumed that account is already loaded.
fn storage(&mut self, address: Address, index: U256) -> Result<U256, Self::Error> {
match self.accounts.entry(address) {
match self.cache.accounts.entry(address) {
Entry::Occupied(mut acc_entry) => {
let acc_entry = acc_entry.get_mut();
match acc_entry.storage.entry(index) {
Expand Down Expand Up @@ -268,7 +293,7 @@ impl<ExtDB: DatabaseRef> Database for CacheDB<ExtDB> {
}

fn block_hash(&mut self, number: u64) -> Result<B256, Self::Error> {
match self.block_hashes.entry(U256::from(number)) {
match self.cache.block_hashes.entry(U256::from(number)) {
Entry::Occupied(entry) => Ok(*entry.get()),
Entry::Vacant(entry) => {
let hash = self.db.block_hash_ref(number)?;
Expand All @@ -283,21 +308,21 @@ impl<ExtDB: DatabaseRef> DatabaseRef for CacheDB<ExtDB> {
type Error = ExtDB::Error;

fn basic_ref(&self, address: Address) -> Result<Option<AccountInfo>, Self::Error> {
match self.accounts.get(&address) {
match self.cache.accounts.get(&address) {
Some(acc) => Ok(acc.info()),
None => self.db.basic_ref(address),
}
}

fn code_by_hash_ref(&self, code_hash: B256) -> Result<Bytecode, Self::Error> {
match self.contracts.get(&code_hash) {
match self.cache.contracts.get(&code_hash) {
Some(entry) => Ok(entry.clone()),
None => self.db.code_by_hash_ref(code_hash),
}
}

fn storage_ref(&self, address: Address, index: U256) -> Result<U256, Self::Error> {
match self.accounts.get(&address) {
match self.cache.accounts.get(&address) {
Some(acc_entry) => match acc_entry.storage.get(&index) {
Some(entry) => Ok(*entry),
None => {
Expand All @@ -316,7 +341,7 @@ impl<ExtDB: DatabaseRef> DatabaseRef for CacheDB<ExtDB> {
}

fn block_hash_ref(&self, number: u64) -> Result<B256, Self::Error> {
match self.block_hashes.get(&U256::from(number)) {
match self.cache.block_hashes.get(&U256::from(number)) {
Some(entry) => Ok(*entry),
None => self.db.block_hash_ref(number),
}
Expand Down Expand Up @@ -524,9 +549,15 @@ mod tests {
let serialized = serde_json::to_string(&init_state).unwrap();
let deserialized: CacheDB<EmptyDB> = serde_json::from_str(&serialized).unwrap();

assert!(deserialized.accounts.contains_key(&account));
assert!(deserialized.cache.accounts.contains_key(&account));
assert_eq!(
deserialized.accounts.get(&account).unwrap().info.nonce,
deserialized
.cache
.accounts
.get(&account)
.unwrap()
.info
.nonce,
nonce
);
}
Expand Down
Loading