use alloy_primitives::{map::B256HashMap, Address, StorageKey, StorageValue, B256};
use metrics::Gauge;
use moka::sync::CacheBuilder;
use reth_errors::ProviderResult;
use reth_metrics::Metrics;
use reth_primitives::{Account, Bytecode};
use reth_provider::{
AccountReader, BlockHashReader, HashedPostStateProvider, StateProofProvider, StateProvider,
StateRootProvider, StorageRootProvider,
};
use reth_trie::{
updates::TrieUpdates, AccountProof, HashedPostState, HashedStorage, MultiProof,
MultiProofTargets, StorageMultiProof, StorageProof, TrieInput,
};
use revm_primitives::map::DefaultHashBuilder;
type Cache<K, V> = moka::sync::Cache<K, V, alloy_primitives::map::DefaultHashBuilder>;
pub(crate) struct CachedStateProvider<S> {
state_provider: S,
caches: ProviderCaches,
metrics: CachedStateMetrics,
}
impl<S> CachedStateProvider<S>
where
S: StateProvider,
{
pub(crate) const fn new_with_caches(
state_provider: S,
caches: ProviderCaches,
metrics: CachedStateMetrics,
) -> Self {
Self { state_provider, caches, metrics }
}
}
#[derive(Metrics, Clone)]
#[metrics(scope = "sync.caching")]
pub(crate) struct CachedStateMetrics {
code_cache_hits: Gauge,
code_cache_misses: Gauge,
storage_cache_hits: Gauge,
storage_cache_misses: Gauge,
account_cache_hits: Gauge,
account_cache_misses: Gauge,
}
impl CachedStateMetrics {
pub(crate) fn reset(&self) {
self.code_cache_hits.set(0);
self.code_cache_misses.set(0);
self.storage_cache_hits.set(0);
self.storage_cache_misses.set(0);
self.account_cache_hits.set(0);
self.account_cache_misses.set(0);
}
pub(crate) fn zeroed() -> Self {
let zeroed = Self::default();
zeroed.reset();
zeroed
}
}
impl<S: AccountReader> AccountReader for CachedStateProvider<S> {
fn basic_account(&self, address: &Address) -> ProviderResult<Option<Account>> {
if let Some(res) = self.caches.account_cache.get(address) {
self.metrics.account_cache_hits.increment(1);
return Ok(res)
}
self.metrics.account_cache_misses.increment(1);
let res = self.state_provider.basic_account(address)?;
self.caches.account_cache.insert(*address, res);
Ok(res)
}
}
impl<S: StateProvider> StateProvider for CachedStateProvider<S> {
fn storage(
&self,
account: Address,
storage_key: StorageKey,
) -> ProviderResult<Option<StorageValue>> {
if let Some(res) = self.caches.storage_cache.get(&(account, storage_key)) {
self.metrics.storage_cache_hits.increment(1);
return Ok(res)
}
self.metrics.storage_cache_misses.increment(1);
let final_res = self.state_provider.storage(account, storage_key)?;
self.caches.storage_cache.insert((account, storage_key), final_res);
Ok(final_res)
}
fn bytecode_by_hash(&self, code_hash: &B256) -> ProviderResult<Option<Bytecode>> {
if let Some(res) = self.caches.code_cache.get(code_hash) {
self.metrics.code_cache_hits.increment(1);
return Ok(res)
}
self.metrics.code_cache_misses.increment(1);
let final_res = self.state_provider.bytecode_by_hash(code_hash)?;
self.caches.code_cache.insert(*code_hash, final_res.clone());
Ok(final_res)
}
}
impl<S: StateRootProvider> StateRootProvider for CachedStateProvider<S> {
fn state_root(&self, hashed_state: HashedPostState) -> ProviderResult<B256> {
self.state_provider.state_root(hashed_state)
}
fn state_root_from_nodes(&self, input: TrieInput) -> ProviderResult<B256> {
self.state_provider.state_root_from_nodes(input)
}
fn state_root_from_nodes_with_updates(
&self,
input: TrieInput,
) -> ProviderResult<(B256, TrieUpdates)> {
self.state_provider.state_root_from_nodes_with_updates(input)
}
fn state_root_with_updates(
&self,
hashed_state: HashedPostState,
) -> ProviderResult<(B256, TrieUpdates)> {
self.state_provider.state_root_with_updates(hashed_state)
}
}
impl<S: StateProofProvider> StateProofProvider for CachedStateProvider<S> {
fn proof(
&self,
input: TrieInput,
address: Address,
slots: &[B256],
) -> ProviderResult<AccountProof> {
self.state_provider.proof(input, address, slots)
}
fn multiproof(
&self,
input: TrieInput,
targets: MultiProofTargets,
) -> ProviderResult<MultiProof> {
self.state_provider.multiproof(input, targets)
}
fn witness(
&self,
input: TrieInput,
target: HashedPostState,
) -> ProviderResult<B256HashMap<alloy_primitives::Bytes>> {
self.state_provider.witness(input, target)
}
}
impl<S: StorageRootProvider> StorageRootProvider for CachedStateProvider<S> {
fn storage_root(
&self,
address: Address,
hashed_storage: HashedStorage,
) -> ProviderResult<B256> {
self.state_provider.storage_root(address, hashed_storage)
}
fn storage_proof(
&self,
address: Address,
slot: B256,
hashed_storage: HashedStorage,
) -> ProviderResult<StorageProof> {
self.state_provider.storage_proof(address, slot, hashed_storage)
}
fn storage_multiproof(
&self,
address: Address,
slots: &[B256],
hashed_storage: HashedStorage,
) -> ProviderResult<StorageMultiProof> {
self.state_provider.storage_multiproof(address, slots, hashed_storage)
}
}
impl<S: BlockHashReader> BlockHashReader for CachedStateProvider<S> {
fn block_hash(&self, number: alloy_primitives::BlockNumber) -> ProviderResult<Option<B256>> {
self.state_provider.block_hash(number)
}
fn canonical_hashes_range(
&self,
start: alloy_primitives::BlockNumber,
end: alloy_primitives::BlockNumber,
) -> ProviderResult<Vec<B256>> {
self.state_provider.canonical_hashes_range(start, end)
}
}
impl<S: HashedPostStateProvider> HashedPostStateProvider for CachedStateProvider<S> {
fn hashed_post_state(&self, bundle_state: &reth_revm::db::BundleState) -> HashedPostState {
self.state_provider.hashed_post_state(bundle_state)
}
}
#[derive(Debug, Clone)]
pub(crate) struct ProviderCaches {
code_cache: Cache<B256, Option<Bytecode>>,
storage_cache: Cache<(Address, StorageKey), Option<StorageValue>>,
account_cache: Cache<Address, Option<Account>>,
}
#[derive(Debug)]
pub(crate) struct ProviderCacheBuilder {
code_cache_size: u64,
storage_cache_size: u64,
account_cache_size: u64,
}
impl ProviderCacheBuilder {
pub(crate) fn build_caches(self) -> ProviderCaches {
ProviderCaches {
code_cache: CacheBuilder::new(self.code_cache_size)
.build_with_hasher(DefaultHashBuilder::default()),
storage_cache: CacheBuilder::new(self.storage_cache_size)
.build_with_hasher(DefaultHashBuilder::default()),
account_cache: CacheBuilder::new(self.account_cache_size)
.build_with_hasher(DefaultHashBuilder::default()),
}
}
}
impl Default for ProviderCacheBuilder {
fn default() -> Self {
Self { code_cache_size: 1000000, storage_cache_size: 1000000, account_cache_size: 1000000 }
}
}