use core::convert::Infallible;
use database_interface::{
Database, DatabaseCommit, DatabaseRef, EmptyDB, BENCH_CALLER, BENCH_CALLER_BALANCE,
BENCH_TARGET, BENCH_TARGET_BALANCE,
};
use primitives::{
hash_map::Entry, Address, AddressMap, B256Map, HashMap, Log, StorageKey, StorageKeyMap,
StorageValue, U256Map, B256, KECCAK_EMPTY, U256,
};
use state::{Account, AccountInfo, Bytecode};
use std::vec::Vec;
pub type InMemoryDB = CacheDB<EmptyDB>;
#[derive(Debug, Clone)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct Cache {
pub accounts: AddressMap<DbAccount>,
pub contracts: B256Map<Bytecode>,
pub logs: Vec<Log>,
pub block_hashes: U256Map<B256>,
}
impl Default for Cache {
fn default() -> Self {
let mut contracts = HashMap::default();
contracts.insert(KECCAK_EMPTY, Bytecode::default());
contracts.insert(B256::ZERO, Bytecode::default());
Cache {
accounts: HashMap::default(),
contracts,
logs: Vec::default(),
block_hashes: HashMap::default(),
}
}
}
#[derive(Debug, Clone)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct CacheDB<ExtDB> {
pub cache: Cache,
pub db: ExtDB,
}
impl<ExtDB: Default> Default for CacheDB<ExtDB> {
fn default() -> Self {
Self::new(ExtDB::default())
}
}
impl<ExtDb> CacheDB<CacheDB<ExtDb>> {
pub fn flatten(self) -> CacheDB<ExtDb> {
let CacheDB {
cache:
Cache {
accounts,
contracts,
logs,
block_hashes,
},
db: mut inner,
..
} = self;
inner.cache.accounts.extend(accounts);
inner.cache.contracts.extend(contracts);
inner.cache.logs.extend(logs);
inner.cache.block_hashes.extend(block_hashes);
inner
}
pub fn discard_outer(self) -> CacheDB<ExtDb> {
self.db
}
}
impl<ExtDB> CacheDB<ExtDB> {
pub fn new(db: ExtDB) -> Self {
Self {
cache: Cache::default(),
db,
}
}
pub fn insert_contract(&mut self, account: &mut AccountInfo) {
if let Some(code) = &account.code {
if !code.is_empty() {
if account.code_hash == KECCAK_EMPTY {
account.code_hash = code.hash_slow();
}
self.cache
.contracts
.entry(account.code_hash)
.or_insert_with(|| code.clone());
}
}
if account.code_hash.is_zero() {
account.code_hash = KECCAK_EMPTY;
}
}
pub fn insert_account_info(&mut self, address: Address, mut info: AccountInfo) {
self.insert_contract(&mut info);
let account_entry = self.cache.accounts.entry(address).or_default();
account_entry.update_info(info);
if account_entry.account_state == AccountState::NotExisting {
account_entry.update_account_state(AccountState::None);
}
}
pub fn nest(self) -> CacheDB<Self> {
CacheDB::new(self)
}
}
impl<ExtDB: DatabaseRef> CacheDB<ExtDB> {
pub fn load_account(&mut self, address: Address) -> Result<&mut DbAccount, ExtDB::Error> {
let db = &self.db;
match self.cache.accounts.entry(address) {
Entry::Occupied(entry) => Ok(entry.into_mut()),
Entry::Vacant(entry) => Ok(entry.insert(
db.basic_ref(address)?
.map(|info| DbAccount {
info,
..Default::default()
})
.unwrap_or_else(DbAccount::new_not_existing),
)),
}
}
pub fn insert_account_storage(
&mut self,
address: Address,
slot: StorageKey,
value: StorageValue,
) -> Result<(), ExtDB::Error> {
let account = self.load_account(address)?;
account.storage.insert(slot, value);
Ok(())
}
pub fn replace_account_storage(
&mut self,
address: Address,
storage: StorageKeyMap<StorageValue>,
) -> Result<(), ExtDB::Error> {
let account = self.load_account(address)?;
account.account_state = AccountState::StorageCleared;
account.storage = storage.into_iter().collect();
Ok(())
}
#[cfg(feature = "std")]
pub fn pretty_print(&self) -> String {
let mut output = String::new();
output.push_str("CacheDB:\n");
output.push_str(&format!(
" accounts: {} total\n",
self.cache.accounts.len()
));
let mut accounts: Vec<_> = self.cache.accounts.iter().collect();
accounts.sort_by_key(|(addr, _)| *addr);
for (address, db_account) in accounts {
output.push_str(&format!(" [{address}]:\n"));
output.push_str(&format!(" state: {:?}\n", db_account.account_state));
if let Some(info) = db_account.info() {
output.push_str(&format!(" balance: {}\n", info.balance));
output.push_str(&format!(" nonce: {}\n", info.nonce));
output.push_str(&format!(" code_hash: {}\n", info.code_hash));
if let Some(code) = info.code {
if !code.is_empty() {
output.push_str(&format!(" code: {} bytes\n", code.len()));
}
}
} else {
output.push_str(" account: None (not existing)\n");
}
if !db_account.storage.is_empty() {
output.push_str(&format!(
" storage: {} slots\n",
db_account.storage.len()
));
let mut storage: Vec<_> = db_account.storage.iter().collect();
storage.sort_by_key(|(k, _)| *k);
for (key, value) in storage {
output.push_str(&format!(" [{key:#x}]: {value:#x}\n"));
}
}
}
if !self.cache.contracts.is_empty() {
output.push_str(&format!(
" contracts: {} total\n",
self.cache.contracts.len()
));
let mut contracts: Vec<_> = self.cache.contracts.iter().collect();
contracts.sort_by_key(|(h, _)| *h);
for (hash, bytecode) in contracts {
output.push_str(&format!(" [{hash}]: {} bytes\n", bytecode.len()));
}
}
if !self.cache.logs.is_empty() {
output.push_str(&format!(" logs: {} total\n", self.cache.logs.len()));
for (i, log) in self.cache.logs.iter().enumerate() {
output.push_str(&format!(
" [{i}]: address: {:?}, topics: {}\n",
log.address,
log.data.topics().len()
));
}
}
if !self.cache.block_hashes.is_empty() {
output.push_str(&format!(
" block_hashes: {} total\n",
self.cache.block_hashes.len()
));
let mut block_hashes: Vec<_> = self.cache.block_hashes.iter().collect();
block_hashes.sort_by_key(|(num, _)| *num);
for (num, hash) in block_hashes {
output.push_str(&format!(" [{num}]: {hash}\n"));
}
}
output.push_str("}\n");
output
}
}
impl<ExtDB> DatabaseCommit for CacheDB<ExtDB> {
fn commit(&mut self, changes: AddressMap<Account>) {
for (address, mut account) in changes {
if !account.is_touched() {
continue;
}
if account.is_selfdestructed() {
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();
continue;
}
let is_newly_created = account.is_created();
self.insert_contract(&mut account.info);
let db_account = self.cache.accounts.entry(address).or_default();
db_account.info = account.info;
db_account.account_state = if is_newly_created {
db_account.storage.clear();
AccountState::StorageCleared
} else if db_account.account_state.is_storage_cleared() {
AccountState::StorageCleared
} else {
AccountState::Touched
};
db_account.storage.extend(
account
.storage
.into_iter()
.map(|(key, value)| (key, value.present_value())),
);
}
}
}
impl<ExtDB: DatabaseRef> Database for CacheDB<ExtDB> {
type Error = ExtDB::Error;
fn basic(&mut self, address: Address) -> Result<Option<AccountInfo>, Self::Error> {
Ok(self.load_account(address)?.info())
}
fn code_by_hash(&mut self, code_hash: B256) -> Result<Bytecode, Self::Error> {
match self.cache.contracts.entry(code_hash) {
Entry::Occupied(entry) => Ok(entry.get().clone()),
Entry::Vacant(entry) => {
Ok(entry.insert(self.db.code_by_hash_ref(code_hash)?).clone())
}
}
}
fn storage(
&mut self,
address: Address,
index: StorageKey,
) -> Result<StorageValue, Self::Error> {
match self.cache.accounts.entry(address) {
Entry::Occupied(mut acc_entry) => {
let acc_entry = acc_entry.get_mut();
match acc_entry.storage.entry(index) {
Entry::Occupied(entry) => Ok(*entry.get()),
Entry::Vacant(entry) => {
if matches!(
acc_entry.account_state,
AccountState::StorageCleared | AccountState::NotExisting
) {
Ok(StorageValue::ZERO)
} else {
let slot = self.db.storage_ref(address, index)?;
entry.insert(slot);
Ok(slot)
}
}
}
}
Entry::Vacant(acc_entry) => {
let info = self.db.basic_ref(address)?;
let (account, value) = if info.is_some() {
let value = self.db.storage_ref(address, index)?;
let mut account: DbAccount = info.into();
account.storage.insert(index, value);
(account, value)
} else {
(info.into(), StorageValue::ZERO)
};
acc_entry.insert(account);
Ok(value)
}
}
}
fn block_hash(&mut self, number: u64) -> Result<B256, Self::Error> {
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)?;
entry.insert(hash);
Ok(hash)
}
}
}
}
impl<ExtDB: DatabaseRef> DatabaseRef for CacheDB<ExtDB> {
type Error = ExtDB::Error;
fn basic_ref(&self, address: Address) -> Result<Option<AccountInfo>, Self::Error> {
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.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: StorageKey,
) -> Result<StorageValue, Self::Error> {
match self.cache.accounts.get(&address) {
Some(acc_entry) => match acc_entry.storage.get(&index) {
Some(entry) => Ok(*entry),
None => {
if matches!(
acc_entry.account_state,
AccountState::StorageCleared | AccountState::NotExisting
) {
Ok(StorageValue::ZERO)
} else {
self.db.storage_ref(address, index)
}
}
},
None => self.db.storage_ref(address, index),
}
}
fn block_hash_ref(&self, number: u64) -> Result<B256, Self::Error> {
match self.cache.block_hashes.get(&U256::from(number)) {
Some(entry) => Ok(*entry),
None => self.db.block_hash_ref(number),
}
}
}
#[derive(Debug, Clone, Default)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct DbAccount {
pub info: AccountInfo,
pub account_state: AccountState,
pub storage: StorageKeyMap<StorageValue>,
}
impl DbAccount {
pub fn new_not_existing() -> Self {
Self {
account_state: AccountState::NotExisting,
..Default::default()
}
}
pub fn info(&self) -> Option<AccountInfo> {
if matches!(self.account_state, AccountState::NotExisting) {
None
} else {
Some(self.info.clone())
}
}
#[inline(always)]
pub fn update_info(&mut self, info: AccountInfo) {
self.info = info;
}
#[inline(always)]
pub fn update_account_state(&mut self, account_state: AccountState) {
self.account_state = account_state;
}
}
impl From<Option<AccountInfo>> for DbAccount {
fn from(from: Option<AccountInfo>) -> Self {
from.map(Self::from).unwrap_or_else(Self::new_not_existing)
}
}
impl From<AccountInfo> for DbAccount {
fn from(info: AccountInfo) -> Self {
Self {
info,
account_state: AccountState::None,
..Default::default()
}
}
}
#[derive(Debug, Clone, Default, PartialEq, Eq, Hash)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub enum AccountState {
NotExisting,
Touched,
StorageCleared,
#[default]
None,
}
impl AccountState {
pub fn is_storage_cleared(&self) -> bool {
matches!(self, AccountState::StorageCleared)
}
}
#[derive(Debug, Default, Clone)]
pub struct BenchmarkDB(pub Bytecode, B256);
impl BenchmarkDB {
pub fn new_bytecode(bytecode: Bytecode) -> Self {
let hash = bytecode.hash_slow();
Self(bytecode, hash)
}
}
impl Database for BenchmarkDB {
type Error = Infallible;
fn basic(&mut self, address: Address) -> Result<Option<AccountInfo>, Self::Error> {
if address == BENCH_TARGET {
return Ok(Some(AccountInfo {
nonce: 1,
balance: BENCH_TARGET_BALANCE,
code: Some(self.0.clone()),
code_hash: self.1,
..Default::default()
}));
}
if address == BENCH_CALLER {
return Ok(Some(AccountInfo {
nonce: 0,
balance: BENCH_CALLER_BALANCE,
code: None,
code_hash: KECCAK_EMPTY,
..Default::default()
}));
}
Ok(None)
}
fn code_by_hash(&mut self, code_hash: B256) -> Result<Bytecode, Self::Error> {
if code_hash == self.1 {
Ok(self.0.clone())
} else {
Ok(Bytecode::default())
}
}
fn storage(
&mut self,
_address: Address,
_index: StorageKey,
) -> Result<StorageValue, Self::Error> {
Ok(StorageValue::default())
}
fn block_hash(&mut self, _number: u64) -> Result<B256, Self::Error> {
Ok(B256::default())
}
}
#[cfg(test)]
mod tests {
use super::{CacheDB, EmptyDB};
use database_interface::Database;
use primitives::{Address, HashMap, StorageKey, StorageValue};
use state::AccountInfo;
#[test]
fn test_insert_account_storage() {
let account = Address::with_last_byte(42);
let nonce = 42;
let mut init_state = CacheDB::new(EmptyDB::default());
init_state.insert_account_info(
account,
AccountInfo {
nonce,
..Default::default()
},
);
let (key, value) = (StorageKey::from(123), StorageValue::from(456));
let mut new_state = CacheDB::new(init_state);
new_state
.insert_account_storage(account, key, value)
.unwrap();
assert_eq!(new_state.basic(account).unwrap().unwrap().nonce, nonce);
assert_eq!(new_state.storage(account, key), Ok(value));
}
#[test]
fn test_replace_account_storage() {
let account = Address::with_last_byte(42);
let nonce = 42;
let mut init_state = CacheDB::new(EmptyDB::default());
init_state.insert_account_info(
account,
AccountInfo {
nonce,
..Default::default()
},
);
let (key0, value0) = (StorageKey::from(123), StorageValue::from(456));
let (key1, value1) = (StorageKey::from(789), StorageValue::from(999));
init_state
.insert_account_storage(account, key0, value0)
.unwrap();
let mut new_state = CacheDB::new(init_state);
new_state
.replace_account_storage(account, HashMap::from_iter([(key1, value1)]))
.unwrap();
assert_eq!(new_state.basic(account).unwrap().unwrap().nonce, nonce);
assert_eq!(new_state.storage(account, key0), Ok(StorageValue::ZERO));
assert_eq!(new_state.storage(account, key1), Ok(value1));
}
#[cfg(feature = "std")]
#[test]
fn test_pretty_print_cachedb() {
use primitives::{Bytes, Log, LogData, B256, U256};
let account = Address::with_last_byte(55);
let mut cachedb = CacheDB::new(EmptyDB::default());
cachedb.insert_account_info(
account,
AccountInfo {
nonce: 7,
..Default::default()
},
);
let key = StorageKey::from(1);
let value = StorageValue::from(2);
cachedb.insert_account_storage(account, key, value).unwrap();
let log = Log {
address: account,
data: LogData::new(Vec::new(), Bytes::from(vec![0x01u8]))
.expect("LogData should have <=4 topics"),
};
cachedb.cache.logs.push(log);
cachedb
.cache
.block_hashes
.insert(U256::from(123u64), B256::from([1u8; 32]));
let s = cachedb.pretty_print();
assert!(s.contains("CacheDB:"));
assert!(s.contains("accounts: 1 total"));
assert!(s.contains("storage: 1 slots"));
assert!(s.contains("logs: 1 total"));
assert!(s.contains("block_hashes: 1 total"));
}
#[cfg(feature = "serde")]
#[test]
fn test_serialize_deserialize_cachedb() {
let account = Address::with_last_byte(69);
let nonce = 420;
let mut init_state = CacheDB::new(EmptyDB::default());
init_state.insert_account_info(
account,
AccountInfo {
nonce,
..Default::default()
},
);
let serialized = serde_json::to_string(&init_state).unwrap();
let deserialized: CacheDB<EmptyDB> = serde_json::from_str(&serialized).unwrap();
assert!(deserialized.cache.accounts.contains_key(&account));
assert_eq!(
deserialized
.cache
.accounts
.get(&account)
.unwrap()
.info
.nonce,
nonce
);
}
}