ethrex-vm 30.0.0

EVM execution backend and database abstraction for the ethrex Ethereum execution client
Documentation
use ethrex_common::U256 as CoreU256;
use ethrex_common::constants::EMPTY_KECCAK_HASH;
use ethrex_common::types::{AccountState, Code, CodeMetadata};
use ethrex_common::{Address as CoreAddress, H256 as CoreH256};
use ethrex_levm::db::Database as LevmDatabase;

use crate::VmDatabase;
use crate::db::DynVmDatabase;
use ethrex_levm::errors::DatabaseError;
use std::collections::HashMap;
use std::result::Result;
use std::sync::{Arc, Mutex};

#[derive(Clone)]
pub struct DatabaseLogger {
    pub block_hashes_accessed: Arc<Mutex<HashMap<u64, CoreH256>>>,
    pub state_accessed: Arc<Mutex<HashMap<CoreAddress, Vec<CoreH256>>>>,
    pub code_accessed: Arc<Mutex<Vec<CoreH256>>>,
    pub store: Arc<dyn LevmDatabase>,
}

impl DatabaseLogger {
    pub fn new(store: Arc<dyn LevmDatabase>) -> Self {
        Self {
            block_hashes_accessed: Arc::new(Mutex::new(HashMap::new())),
            state_accessed: Arc::new(Mutex::new(HashMap::new())),
            code_accessed: Arc::new(Mutex::new(vec![])),
            store,
        }
    }
}

impl LevmDatabase for DatabaseLogger {
    fn get_account_state(&self, address: CoreAddress) -> Result<AccountState, DatabaseError> {
        self.state_accessed
            .lock()
            .map_err(|_| DatabaseError::Custom("Could not lock mutex".to_string()))?
            .entry(address)
            .or_default();
        let state = self.store.as_ref().get_account_state(address)?;
        Ok(state)
    }

    fn get_storage_value(
        &self,
        address: CoreAddress,
        key: CoreH256,
    ) -> Result<CoreU256, DatabaseError> {
        self.state_accessed
            .lock()
            .map_err(|_| DatabaseError::Custom("Could not lock mutex".to_string()))?
            .entry(address)
            .and_modify(|keys| keys.push(key))
            .or_insert(vec![key]);
        self.store.as_ref().get_storage_value(address, key)
    }

    fn get_block_hash(&self, block_number: u64) -> Result<CoreH256, DatabaseError> {
        let block_hash = self.store.as_ref().get_block_hash(block_number)?;
        self.block_hashes_accessed
            .lock()
            .map_err(|_| DatabaseError::Custom("Could not lock mutex".to_string()))?
            .insert(block_number, block_hash);
        Ok(block_hash)
    }

    fn get_chain_config(&self) -> Result<ethrex_common::types::ChainConfig, DatabaseError> {
        self.store.as_ref().get_chain_config()
    }

    fn get_account_code(&self, code_hash: CoreH256) -> Result<Code, DatabaseError> {
        if code_hash != *EMPTY_KECCAK_HASH {
            let mut code_accessed = self
                .code_accessed
                .lock()
                .map_err(|_| DatabaseError::Custom("Could not lock mutex".to_string()))?;
            code_accessed.push(code_hash);
        }
        self.store.as_ref().get_account_code(code_hash)
    }

    fn code_cache_budget_bytes(&self) -> u64 {
        self.store.as_ref().code_cache_budget_bytes()
    }

    fn prefetch_codes(&self, code_hashes: &[CoreH256]) -> Result<u64, DatabaseError> {
        // Record before delegating, exactly as the per-hash path does, so a batched
        // read cannot leave the witness short of a bytecode it observed.
        {
            let mut code_accessed = self
                .code_accessed
                .lock()
                .map_err(|_| DatabaseError::Custom("Could not lock mutex".to_string()))?;
            code_accessed.extend(
                code_hashes
                    .iter()
                    .filter(|h| **h != *EMPTY_KECCAK_HASH)
                    .copied(),
            );
        }
        self.store.as_ref().prefetch_codes(code_hashes)
    }

    fn get_code_metadata(&self, code_hash: CoreH256) -> Result<CodeMetadata, DatabaseError> {
        // A size-only read still observes the bytecode, so the witness must carry it:
        // EIP-8025 stateless validation recomputes the length from the code itself, and
        // that is also how `ExecutionWitness::get_code_metadata` answers.
        if code_hash != *EMPTY_KECCAK_HASH {
            self.code_accessed
                .lock()
                .map_err(|_| DatabaseError::Custom("Could not lock mutex".to_string()))?
                .push(code_hash);
        }
        self.store.get_code_metadata(code_hash)
    }
}

impl LevmDatabase for DynVmDatabase {
    fn get_account_state(&self, address: CoreAddress) -> Result<AccountState, DatabaseError> {
        let acc_state = <dyn VmDatabase>::get_account_state(self.as_ref(), address)
            .map_err(|e| DatabaseError::Custom(e.to_string()))?
            .unwrap_or_default();

        Ok(acc_state)
    }

    fn get_account_states_batch(
        &self,
        addresses: &[CoreAddress],
    ) -> Result<Vec<AccountState>, DatabaseError> {
        let states = <dyn VmDatabase>::get_account_states_batch(self.as_ref(), addresses)
            .map_err(|e| DatabaseError::Custom(e.to_string()))?;
        Ok(states
            .into_iter()
            .map(|opt| opt.unwrap_or_default())
            .collect())
    }

    fn code_cache_budget_bytes(&self) -> u64 {
        <dyn VmDatabase>::code_cache_budget_bytes(self.as_ref())
    }

    fn get_storage_value(
        &self,
        address: CoreAddress,
        key: CoreH256,
    ) -> Result<ethrex_common::U256, DatabaseError> {
        Ok(
            <dyn VmDatabase>::get_storage_slot(self.as_ref(), address, key)
                .map_err(|e| DatabaseError::Custom(e.to_string()))?
                .unwrap_or_default(),
        )
    }

    fn get_storage_values_batch(
        &self,
        keys: &[(CoreAddress, CoreH256)],
    ) -> Result<Vec<CoreU256>, DatabaseError> {
        let values = <dyn VmDatabase>::get_storage_slots_batch(self.as_ref(), keys)
            .map_err(|e| DatabaseError::Custom(e.to_string()))?;
        Ok(values
            .into_iter()
            .map(|opt| opt.unwrap_or_default())
            .collect())
    }

    fn get_block_hash(&self, block_number: u64) -> Result<CoreH256, DatabaseError> {
        <dyn VmDatabase>::get_block_hash(self.as_ref(), block_number)
            .map_err(|e| DatabaseError::Custom(e.to_string()))
    }

    fn get_chain_config(&self) -> Result<ethrex_common::types::ChainConfig, DatabaseError> {
        <dyn VmDatabase>::get_chain_config(self.as_ref())
            .map_err(|e| DatabaseError::Custom(e.to_string()))
    }

    fn get_account_code(&self, code_hash: CoreH256) -> Result<Code, DatabaseError> {
        <dyn VmDatabase>::get_account_code(self.as_ref(), code_hash)
            .map_err(|e| DatabaseError::Custom(e.to_string()))
    }

    fn get_account_codes_batch(
        &self,
        code_hashes: &[CoreH256],
    ) -> Result<Vec<Option<Code>>, DatabaseError> {
        <dyn VmDatabase>::get_account_codes_batch(self.as_ref(), code_hashes)
            .map_err(|e| DatabaseError::Custom(e.to_string()))
    }

    fn get_code_metadata(&self, code_hash: CoreH256) -> Result<CodeMetadata, DatabaseError> {
        <dyn VmDatabase>::get_code_metadata(self.as_ref(), code_hash)
            .map_err(|e| DatabaseError::Custom(e.to_string()))
    }
}