use ethrex_common::{
Address, H256, U256,
constants::EMPTY_KECCAK_HASH,
types::{AccountState, BlockHash, BlockHeader, BlockNumber, ChainConfig, Code, CodeMetadata},
};
use ethrex_crypto::keccak::keccak_hash;
use ethrex_storage::Store;
use ethrex_vm::{EvmError, VmDatabase};
use rustc_hash::FxHashMap;
use std::{
cmp::Ordering,
collections::BTreeMap,
sync::{Arc, Mutex, RwLock},
};
use tracing::instrument;
#[derive(Clone, Copy)]
struct AccountStateCacheEntry {
state: AccountState,
hashed_address: H256,
}
type AccountStateCache = FxHashMap<Address, Option<AccountStateCacheEntry>>;
#[derive(Clone)]
pub struct StoreVmDatabase {
pub store: Store,
pub block_hash: BlockHash,
pub block_hash_cache: Arc<Mutex<BTreeMap<BlockNumber, BlockHash>>>,
account_state_cache: Arc<RwLock<AccountStateCache>>,
pub state_root: H256,
}
impl StoreVmDatabase {
pub fn new(store: Store, block_header: BlockHeader) -> Result<Self, EvmError> {
if !store
.has_state_root(block_header.state_root)
.map_err(|e| EvmError::DB(e.to_string()))?
{
return Err(EvmError::DB(format!(
"state root missing for block {} (state_root {:#x})",
block_header.number, block_header.state_root
)));
}
Ok(StoreVmDatabase {
store,
block_hash: block_header.hash(),
block_hash_cache: Arc::new(Mutex::new(BTreeMap::new())),
account_state_cache: Arc::new(RwLock::new(FxHashMap::default())),
state_root: block_header.state_root,
})
}
pub fn new_with_block_hash_cache(
store: Store,
block_header: BlockHeader,
block_hash_cache: BTreeMap<BlockNumber, BlockHash>,
) -> Result<Self, EvmError> {
if !store
.has_state_root(block_header.state_root)
.map_err(|e| EvmError::DB(e.to_string()))?
{
return Err(EvmError::DB(format!(
"state root missing for block {} (state_root {:#x})",
block_header.number, block_header.state_root
)));
}
Ok(StoreVmDatabase {
store,
block_hash: block_header.hash(),
block_hash_cache: Arc::new(Mutex::new(block_hash_cache)),
account_state_cache: Arc::new(RwLock::new(FxHashMap::default())),
state_root: block_header.state_root,
})
}
fn get_cached_account_state_entry(
&self,
address: Address,
) -> Result<Option<AccountStateCacheEntry>, EvmError> {
if let Some(entry) = self
.account_state_cache
.read()
.map_err(|_| EvmError::Custom("LockError".to_string()))?
.get(&address)
.copied()
{
return Ok(entry);
}
let loaded = self
.store
.get_account_state_by_root(self.state_root, address)
.map_err(|e| EvmError::DB(e.to_string()))?;
let cached = loaded.map(|state| AccountStateCacheEntry {
state,
hashed_address: H256::from(keccak_hash(address.to_fixed_bytes())),
});
self.account_state_cache
.write()
.map_err(|_| EvmError::Custom("LockError".to_string()))?
.insert(address, cached);
Ok(cached)
}
}
impl VmDatabase for StoreVmDatabase {
#[instrument(
level = "trace",
name = "Account read",
skip_all,
fields(namespace = "block_execution")
)]
fn get_account_state(&self, address: Address) -> Result<Option<AccountState>, EvmError> {
Ok(self
.get_cached_account_state_entry(address)?
.map(|entry| entry.state))
}
#[instrument(
level = "trace",
name = "Storage read",
skip_all,
fields(namespace = "block_execution")
)]
fn get_storage_slot(&self, address: Address, key: H256) -> Result<Option<U256>, EvmError> {
let Some(entry) = self.get_cached_account_state_entry(address)? else {
return Ok(None);
};
self.store
.get_storage_at_root_with_known_storage_root(
self.state_root,
entry.hashed_address,
entry.state.storage_root,
key,
)
.map_err(|e| EvmError::DB(e.to_string()))
}
#[instrument(
level = "trace",
name = "Block hash read",
skip_all,
fields(namespace = "block_execution")
)]
fn get_block_hash(&self, block_number: u64) -> Result<H256, EvmError> {
let mut block_hash_cache = self
.block_hash_cache
.lock()
.map_err(|_| EvmError::Custom("LockError".to_string()))?;
if let Some(block_hash) = block_hash_cache.get(&block_number) {
return Ok(*block_hash);
}
if self
.store
.is_canonical_sync(self.block_hash)
.map_err(|err| EvmError::DB(err.to_string()))?
{
if let Some(hash) = self
.store
.get_canonical_block_hash_sync(block_number)
.map_err(|err| EvmError::DB(err.to_string()))?
{
block_hash_cache.insert(block_number, hash);
return Ok(hash);
}
} else {
let oldest_succesor = block_hash_cache
.iter()
.find_map(|(key, hash)| (*key > block_number).then_some(*hash))
.unwrap_or(self.block_hash);
for ancestor_res in self.store.ancestors(oldest_succesor) {
let (hash, ancestor) = ancestor_res.map_err(|e| EvmError::DB(e.to_string()))?;
block_hash_cache.insert(ancestor.number, hash);
match ancestor.number.cmp(&block_number) {
Ordering::Greater => continue,
Ordering::Equal => return Ok(hash),
Ordering::Less => {
return Err(EvmError::DB(format!(
"Block number requested {block_number} is higher than the current block number {}",
ancestor.number
)));
}
}
}
}
Err(EvmError::DB(format!(
"Block hash not found for block number {block_number}"
)))
}
fn get_chain_config(&self) -> Result<ChainConfig, EvmError> {
Ok(self.store.get_chain_config())
}
#[instrument(
level = "trace",
name = "Account code read",
skip_all,
fields(namespace = "block_execution")
)]
fn get_account_code(&self, code_hash: H256) -> Result<Code, EvmError> {
if code_hash == *EMPTY_KECCAK_HASH {
return Ok(Code::default());
}
match self.store.get_account_code(code_hash) {
Ok(Some(code)) => Ok(code),
Ok(None) => Err(EvmError::DB(format!(
"Code not found for hash: {code_hash:?}",
))),
Err(e) => Err(EvmError::DB(e.to_string())),
}
}
#[instrument(
level = "trace",
name = "Code metadata read",
skip_all,
fields(namespace = "block_execution")
)]
fn get_code_metadata(&self, code_hash: H256) -> Result<CodeMetadata, EvmError> {
use ethrex_common::constants::EMPTY_KECCAK_HASH;
if code_hash == *EMPTY_KECCAK_HASH {
return Ok(CodeMetadata { length: 0 });
}
match self.store.get_code_metadata(code_hash) {
Ok(Some(metadata)) => Ok(metadata),
Ok(None) => Err(EvmError::DB(format!(
"Code metadata not found for hash: {code_hash:?}",
))),
Err(e) => Err(EvmError::DB(e.to_string())),
}
}
}