reth_engine_tree/tree/
cached_state.rs

1//! Implements a state provider that has a shared cache in front of it.
2use alloy_primitives::{Address, StorageKey, StorageValue, B256};
3use metrics::Gauge;
4use mini_moka::sync::CacheBuilder;
5use reth_errors::ProviderResult;
6use reth_metrics::Metrics;
7use reth_primitives_traits::{Account, Bytecode};
8use reth_provider::{
9    AccountReader, BlockHashReader, HashedPostStateProvider, StateProofProvider, StateProvider,
10    StateRootProvider, StorageRootProvider,
11};
12use reth_revm::db::BundleState;
13use reth_trie::{
14    updates::TrieUpdates, AccountProof, HashedPostState, HashedStorage, MultiProof,
15    MultiProofTargets, StorageMultiProof, StorageProof, TrieInput,
16};
17use revm_primitives::map::DefaultHashBuilder;
18use std::time::Duration;
19use tracing::trace;
20
21pub(crate) type Cache<K, V> =
22    mini_moka::sync::Cache<K, V, alloy_primitives::map::DefaultHashBuilder>;
23
24/// A wrapper of a state provider and a shared cache.
25pub(crate) struct CachedStateProvider<S> {
26    /// The state provider
27    state_provider: S,
28
29    /// The caches used for the provider
30    caches: ProviderCaches,
31
32    /// Metrics for the cached state provider
33    metrics: CachedStateMetrics,
34}
35
36impl<S> CachedStateProvider<S>
37where
38    S: StateProvider,
39{
40    /// Creates a new [`CachedStateProvider`] from a [`ProviderCaches`], state provider, and
41    /// [`CachedStateMetrics`].
42    pub(crate) const fn new_with_caches(
43        state_provider: S,
44        caches: ProviderCaches,
45        metrics: CachedStateMetrics,
46    ) -> Self {
47        Self { state_provider, caches, metrics }
48    }
49}
50
51/// Metrics for the cached state provider, showing hits / misses for each cache
52#[derive(Metrics, Clone)]
53#[metrics(scope = "sync.caching")]
54pub(crate) struct CachedStateMetrics {
55    /// Code cache hits
56    code_cache_hits: Gauge,
57
58    /// Code cache misses
59    code_cache_misses: Gauge,
60
61    /// Code cache size
62    ///
63    /// NOTE: this uses the moka caches' `entry_count`, NOT the `weighted_size` method to calculate
64    /// size.
65    code_cache_size: Gauge,
66
67    /// Storage cache hits
68    storage_cache_hits: Gauge,
69
70    /// Storage cache misses
71    storage_cache_misses: Gauge,
72
73    /// Storage cache size
74    ///
75    /// NOTE: this uses the moka caches' `entry_count`, NOT the `weighted_size` method to calculate
76    /// size.
77    storage_cache_size: Gauge,
78
79    /// Account cache hits
80    account_cache_hits: Gauge,
81
82    /// Account cache misses
83    account_cache_misses: Gauge,
84
85    /// Account cache size
86    ///
87    /// NOTE: this uses the moka caches' `entry_count`, NOT the `weighted_size` method to calculate
88    /// size.
89    account_cache_size: Gauge,
90}
91
92impl CachedStateMetrics {
93    /// Sets all values to zero, indicating that a new block is being executed.
94    pub(crate) fn reset(&self) {
95        // code cache
96        self.code_cache_hits.set(0);
97        self.code_cache_misses.set(0);
98
99        // storage cache
100        self.storage_cache_hits.set(0);
101        self.storage_cache_misses.set(0);
102
103        // account cache
104        self.account_cache_hits.set(0);
105        self.account_cache_misses.set(0);
106    }
107
108    /// Returns a new zeroed-out instance of [`CachedStateMetrics`].
109    pub(crate) fn zeroed() -> Self {
110        let zeroed = Self::default();
111        zeroed.reset();
112        zeroed
113    }
114}
115
116impl<S: AccountReader> AccountReader for CachedStateProvider<S> {
117    fn basic_account(&self, address: &Address) -> ProviderResult<Option<Account>> {
118        if let Some(res) = self.caches.account_cache.get(address) {
119            self.metrics.account_cache_hits.increment(1);
120            return Ok(res)
121        }
122
123        self.metrics.account_cache_misses.increment(1);
124
125        let res = self.state_provider.basic_account(address)?;
126        self.caches.account_cache.insert(*address, res);
127        Ok(res)
128    }
129}
130
131/// Represents the status of a storage slot in the cache
132#[derive(Debug, Clone, PartialEq, Eq)]
133pub(crate) enum SlotStatus {
134    /// The account's storage cache doesn't exist
135    NotCached,
136    /// The storage slot is empty (either not in cache or explicitly None)
137    Empty,
138    /// The storage slot has a value
139    Value(StorageValue),
140}
141
142impl<S: StateProvider> StateProvider for CachedStateProvider<S> {
143    fn storage(
144        &self,
145        account: Address,
146        storage_key: StorageKey,
147    ) -> ProviderResult<Option<StorageValue>> {
148        match self.caches.get_storage(&account, &storage_key) {
149            SlotStatus::NotCached => {
150                self.metrics.storage_cache_misses.increment(1);
151                let final_res = self.state_provider.storage(account, storage_key)?;
152                self.caches.insert_storage(account, storage_key, final_res);
153                Ok(final_res)
154            }
155            SlotStatus::Empty => {
156                self.metrics.storage_cache_hits.increment(1);
157                Ok(None)
158            }
159            SlotStatus::Value(value) => {
160                self.metrics.storage_cache_hits.increment(1);
161                Ok(Some(value))
162            }
163        }
164    }
165
166    fn bytecode_by_hash(&self, code_hash: &B256) -> ProviderResult<Option<Bytecode>> {
167        if let Some(res) = self.caches.code_cache.get(code_hash) {
168            self.metrics.code_cache_hits.increment(1);
169            return Ok(res)
170        }
171
172        self.metrics.code_cache_misses.increment(1);
173
174        let final_res = self.state_provider.bytecode_by_hash(code_hash)?;
175        self.caches.code_cache.insert(*code_hash, final_res.clone());
176        Ok(final_res)
177    }
178}
179
180impl<S: StateRootProvider> StateRootProvider for CachedStateProvider<S> {
181    fn state_root(&self, hashed_state: HashedPostState) -> ProviderResult<B256> {
182        self.state_provider.state_root(hashed_state)
183    }
184
185    fn state_root_from_nodes(&self, input: TrieInput) -> ProviderResult<B256> {
186        self.state_provider.state_root_from_nodes(input)
187    }
188
189    fn state_root_with_updates(
190        &self,
191        hashed_state: HashedPostState,
192    ) -> ProviderResult<(B256, TrieUpdates)> {
193        self.state_provider.state_root_with_updates(hashed_state)
194    }
195
196    fn state_root_from_nodes_with_updates(
197        &self,
198        input: TrieInput,
199    ) -> ProviderResult<(B256, TrieUpdates)> {
200        self.state_provider.state_root_from_nodes_with_updates(input)
201    }
202}
203
204impl<S: StateProofProvider> StateProofProvider for CachedStateProvider<S> {
205    fn proof(
206        &self,
207        input: TrieInput,
208        address: Address,
209        slots: &[B256],
210    ) -> ProviderResult<AccountProof> {
211        self.state_provider.proof(input, address, slots)
212    }
213
214    fn multiproof(
215        &self,
216        input: TrieInput,
217        targets: MultiProofTargets,
218    ) -> ProviderResult<MultiProof> {
219        self.state_provider.multiproof(input, targets)
220    }
221
222    fn witness(
223        &self,
224        input: TrieInput,
225        target: HashedPostState,
226    ) -> ProviderResult<Vec<alloy_primitives::Bytes>> {
227        self.state_provider.witness(input, target)
228    }
229}
230
231impl<S: StorageRootProvider> StorageRootProvider for CachedStateProvider<S> {
232    fn storage_root(
233        &self,
234        address: Address,
235        hashed_storage: HashedStorage,
236    ) -> ProviderResult<B256> {
237        self.state_provider.storage_root(address, hashed_storage)
238    }
239
240    fn storage_proof(
241        &self,
242        address: Address,
243        slot: B256,
244        hashed_storage: HashedStorage,
245    ) -> ProviderResult<StorageProof> {
246        self.state_provider.storage_proof(address, slot, hashed_storage)
247    }
248
249    fn storage_multiproof(
250        &self,
251        address: Address,
252        slots: &[B256],
253        hashed_storage: HashedStorage,
254    ) -> ProviderResult<StorageMultiProof> {
255        self.state_provider.storage_multiproof(address, slots, hashed_storage)
256    }
257}
258
259impl<S: BlockHashReader> BlockHashReader for CachedStateProvider<S> {
260    fn block_hash(&self, number: alloy_primitives::BlockNumber) -> ProviderResult<Option<B256>> {
261        self.state_provider.block_hash(number)
262    }
263
264    fn canonical_hashes_range(
265        &self,
266        start: alloy_primitives::BlockNumber,
267        end: alloy_primitives::BlockNumber,
268    ) -> ProviderResult<Vec<B256>> {
269        self.state_provider.canonical_hashes_range(start, end)
270    }
271}
272
273impl<S: HashedPostStateProvider> HashedPostStateProvider for CachedStateProvider<S> {
274    fn hashed_post_state(&self, bundle_state: &reth_revm::db::BundleState) -> HashedPostState {
275        self.state_provider.hashed_post_state(bundle_state)
276    }
277}
278
279/// The set of caches that are used in the [`CachedStateProvider`].
280#[derive(Debug, Clone)]
281pub(crate) struct ProviderCaches {
282    /// The cache for bytecode
283    code_cache: Cache<B256, Option<Bytecode>>,
284
285    /// The cache for storage, organized hierarchically by account
286    storage_cache: Cache<Address, AccountStorageCache>,
287
288    /// The cache for basic accounts
289    account_cache: Cache<Address, Option<Account>>,
290}
291
292impl ProviderCaches {
293    /// Get storage value from hierarchical cache.
294    ///
295    /// Returns a `SlotStatus` indicating whether:
296    /// - `NotCached`: The account's storage cache doesn't exist
297    /// - `Empty`: The slot exists in the account's cache but is empty
298    /// - `Value`: The slot exists and has a specific value
299    pub(crate) fn get_storage(&self, address: &Address, key: &StorageKey) -> SlotStatus {
300        match self.storage_cache.get(address) {
301            None => SlotStatus::NotCached,
302            Some(account_cache) => account_cache.get_storage(key),
303        }
304    }
305
306    /// Insert storage value into hierarchical cache
307    pub(crate) fn insert_storage(
308        &self,
309        address: Address,
310        key: StorageKey,
311        value: Option<StorageValue>,
312    ) {
313        let account_cache = self.storage_cache.get(&address).unwrap_or_else(|| {
314            let account_cache = AccountStorageCache::default();
315            self.storage_cache.insert(address, account_cache.clone());
316            account_cache
317        });
318        account_cache.insert_storage(key, value);
319    }
320
321    /// Invalidate storage for specific account
322    pub(crate) fn invalidate_account_storage(&self, address: &Address) {
323        self.storage_cache.invalidate(address);
324    }
325
326    /// Returns the total number of storage slots cached across all accounts
327    pub(crate) fn total_storage_slots(&self) -> usize {
328        self.storage_cache.iter().map(|addr| addr.len()).sum()
329    }
330
331    /// Inserts the [`BundleState`] entries into the cache.
332    ///
333    /// Entries are inserted in the following order:
334    /// 1. Bytecodes
335    /// 2. Storage slots
336    /// 3. Accounts
337    ///
338    /// The order is important, because the access patterns are Account -> Bytecode and Account ->
339    /// Storage slot. If we update the account first, it may point to a code hash that doesn't have
340    /// the associated bytecode anywhere yet.
341    ///
342    /// Returns an error if the state can't be cached and should be discarded.
343    pub(crate) fn insert_state(&self, state_updates: &BundleState) -> Result<(), ()> {
344        // Insert bytecodes
345        for (code_hash, bytecode) in &state_updates.contracts {
346            self.code_cache.insert(*code_hash, Some(Bytecode(bytecode.clone())));
347        }
348
349        for (addr, account) in &state_updates.state {
350            // If the account was not modified, as in not changed and not destroyed, then we have
351            // nothing to do w.r.t. this particular account and can move on
352            if account.status.is_not_modified() {
353                continue
354            }
355
356            // If the account was destroyed, invalidate from the account / storage caches
357            if account.was_destroyed() {
358                // Invalidate the account cache entry if destroyed
359                self.account_cache.invalidate(addr);
360
361                self.invalidate_account_storage(addr);
362                continue
363            }
364
365            // If we have an account that was modified, but it has a `None` account info, some wild
366            // error has occurred because this state should be unrepresentable. An account with
367            // `None` current info, should be destroyed.
368            let Some(ref account_info) = account.info else {
369                trace!(target: "engine::caching", ?account, "Account with None account info found in state updates");
370                return Err(())
371            };
372
373            // Now we iterate over all storage and make updates to the cached storage values
374            for (storage_key, slot) in &account.storage {
375                // We convert the storage key from U256 to B256 because that is how it's represented
376                // in the cache
377                self.insert_storage(*addr, (*storage_key).into(), Some(slot.present_value));
378            }
379
380            // Insert will update if present, so we just use the new account info as the new value
381            // for the account cache
382            self.account_cache.insert(*addr, Some(Account::from(account_info)));
383        }
384
385        Ok(())
386    }
387}
388
389/// A builder for [`ProviderCaches`].
390#[derive(Debug)]
391pub(crate) struct ProviderCacheBuilder {
392    /// Code cache entries
393    code_cache_entries: u64,
394
395    /// Storage cache entries
396    storage_cache_entries: u64,
397
398    /// Account cache entries
399    account_cache_entries: u64,
400}
401
402impl ProviderCacheBuilder {
403    /// Build a [`ProviderCaches`] struct, so that provider caches can be easily cloned.
404    pub(crate) fn build_caches(self, total_cache_size: u64) -> ProviderCaches {
405        let storage_cache_size = (total_cache_size * 8888) / 10000; // 88.88% of total
406        let account_cache_size = (total_cache_size * 556) / 10000; // 5.56% of total
407        let code_cache_size = (total_cache_size * 556) / 10000; // 5.56% of total
408
409        const EXPIRY_TIME: Duration = Duration::from_secs(7200); // 2 hours
410        const TIME_TO_IDLE: Duration = Duration::from_secs(3600); // 1 hour
411
412        let storage_cache = CacheBuilder::new(self.storage_cache_entries)
413            .weigher(|_key: &Address, value: &AccountStorageCache| -> u32 {
414                // values based on results from measure_storage_cache_overhead test
415                let base_weight = 39_000;
416                let slots_weight = value.len() * 218;
417                (base_weight + slots_weight) as u32
418            })
419            .max_capacity(storage_cache_size)
420            .time_to_live(EXPIRY_TIME)
421            .time_to_idle(TIME_TO_IDLE)
422            .build_with_hasher(DefaultHashBuilder::default());
423
424        let account_cache = CacheBuilder::new(self.account_cache_entries)
425            .weigher(|_key: &Address, value: &Option<Account>| -> u32 {
426                match value {
427                    Some(account) => {
428                        let mut weight = 40;
429                        if account.nonce != 0 {
430                            weight += 32;
431                        }
432                        if !account.balance.is_zero() {
433                            weight += 32;
434                        }
435                        if account.bytecode_hash.is_some() {
436                            weight += 33; // size of Option<B256>
437                        } else {
438                            weight += 8; // size of None variant
439                        }
440                        weight as u32
441                    }
442                    None => 8, // size of None variant
443                }
444            })
445            .max_capacity(account_cache_size)
446            .time_to_live(EXPIRY_TIME)
447            .time_to_idle(TIME_TO_IDLE)
448            .build_with_hasher(DefaultHashBuilder::default());
449
450        let code_cache = CacheBuilder::new(self.code_cache_entries)
451            .weigher(|_key: &B256, value: &Option<Bytecode>| -> u32 {
452                match value {
453                    Some(bytecode) => {
454                        // base weight + actual bytecode size
455                        (40 + bytecode.len()) as u32
456                    }
457                    None => 8, // size of None variant
458                }
459            })
460            .max_capacity(code_cache_size)
461            .time_to_live(EXPIRY_TIME)
462            .time_to_idle(TIME_TO_IDLE)
463            .build_with_hasher(DefaultHashBuilder::default());
464
465        ProviderCaches { code_cache, storage_cache, account_cache }
466    }
467}
468
469impl Default for ProviderCacheBuilder {
470    fn default() -> Self {
471        // With weigher and max_capacity in place, these numbers represent
472        // the maximum number of entries that can be stored, not the actual
473        // memory usage which is controlled by max_capacity.
474        //
475        // Code cache: up to 10M entries but limited to 0.5GB
476        // Storage cache: up to 10M accounts but limited to 8GB
477        // Account cache: up to 10M accounts but limited to 0.5GB
478        Self {
479            code_cache_entries: 10_000_000,
480            storage_cache_entries: 10_000_000,
481            account_cache_entries: 10_000_000,
482        }
483    }
484}
485
486/// A saved cache that has been used for executing a specific block, which has been updated for its
487/// execution.
488#[derive(Debug, Clone)]
489pub(crate) struct SavedCache {
490    /// The hash of the block these caches were used to execute.
491    hash: B256,
492
493    /// The caches used for the provider.
494    caches: ProviderCaches,
495
496    /// Metrics for the cached state provider
497    metrics: CachedStateMetrics,
498}
499
500impl SavedCache {
501    /// Creates a new instance with the internals
502    pub(super) const fn new(
503        hash: B256,
504        caches: ProviderCaches,
505        metrics: CachedStateMetrics,
506    ) -> Self {
507        Self { hash, caches, metrics }
508    }
509
510    /// Returns the hash for this cache
511    pub(crate) const fn executed_block_hash(&self) -> B256 {
512        self.hash
513    }
514
515    /// Splits the cache into its caches and metrics, consuming it.
516    pub(crate) fn split(self) -> (ProviderCaches, CachedStateMetrics) {
517        (self.caches, self.metrics)
518    }
519
520    /// Returns the [`ProviderCaches`] belonging to the tracked hash.
521    pub(crate) const fn cache(&self) -> &ProviderCaches {
522        &self.caches
523    }
524
525    /// Updates the metrics for the [`ProviderCaches`].
526    pub(crate) fn update_metrics(&self) {
527        self.metrics.storage_cache_size.set(self.caches.total_storage_slots() as f64);
528        self.metrics.account_cache_size.set(self.caches.account_cache.entry_count() as f64);
529        self.metrics.code_cache_size.set(self.caches.code_cache.entry_count() as f64);
530    }
531}
532
533/// Cache for an account's storage slots
534#[derive(Debug, Clone)]
535pub(crate) struct AccountStorageCache {
536    /// The storage slots for this account
537    slots: Cache<StorageKey, Option<StorageValue>>,
538}
539
540impl AccountStorageCache {
541    /// Create a new [`AccountStorageCache`]
542    pub(crate) fn new(max_slots: u64) -> Self {
543        Self {
544            slots: CacheBuilder::new(max_slots).build_with_hasher(DefaultHashBuilder::default()),
545        }
546    }
547
548    /// Get a storage value from this account's cache.
549    /// - `NotCached`: The slot is not in the cache
550    /// - `Empty`: The slot is empty
551    /// - `Value`: The slot has a specific value
552    pub(crate) fn get_storage(&self, key: &StorageKey) -> SlotStatus {
553        match self.slots.get(key) {
554            None => SlotStatus::NotCached,
555            Some(None) => SlotStatus::Empty,
556            Some(Some(value)) => SlotStatus::Value(value),
557        }
558    }
559
560    /// Insert a storage value
561    pub(crate) fn insert_storage(&self, key: StorageKey, value: Option<StorageValue>) {
562        self.slots.insert(key, value);
563    }
564
565    /// Returns the number of slots in the cache
566    pub(crate) fn len(&self) -> usize {
567        self.slots.entry_count() as usize
568    }
569}
570
571impl Default for AccountStorageCache {
572    fn default() -> Self {
573        // With weigher and max_capacity in place, this number represents
574        // the maximum number of entries that can be stored, not the actual
575        // memory usage which is controlled by storage cache's max_capacity.
576        Self::new(1_000_000)
577    }
578}
579
580#[cfg(test)]
581mod tests {
582    use super::*;
583    use alloy_primitives::{B256, U256};
584    use rand::Rng;
585    use reth_provider::test_utils::{ExtendedAccount, MockEthProvider};
586    use std::mem::size_of;
587
588    mod tracking_allocator {
589        use std::{
590            alloc::{GlobalAlloc, Layout, System},
591            sync::atomic::{AtomicUsize, Ordering},
592        };
593
594        #[derive(Debug)]
595        pub(crate) struct TrackingAllocator {
596            allocated: AtomicUsize,
597            total_allocated: AtomicUsize,
598            inner: System,
599        }
600
601        impl TrackingAllocator {
602            pub(crate) const fn new() -> Self {
603                Self {
604                    allocated: AtomicUsize::new(0),
605                    total_allocated: AtomicUsize::new(0),
606                    inner: System,
607                }
608            }
609
610            pub(crate) fn reset(&self) {
611                self.allocated.store(0, Ordering::SeqCst);
612                self.total_allocated.store(0, Ordering::SeqCst);
613            }
614
615            pub(crate) fn total_allocated(&self) -> usize {
616                self.total_allocated.load(Ordering::SeqCst)
617            }
618        }
619
620        unsafe impl GlobalAlloc for TrackingAllocator {
621            unsafe fn alloc(&self, layout: Layout) -> *mut u8 {
622                let ret = self.inner.alloc(layout);
623                if !ret.is_null() {
624                    self.allocated.fetch_add(layout.size(), Ordering::SeqCst);
625                    self.total_allocated.fetch_add(layout.size(), Ordering::SeqCst);
626                }
627                ret
628            }
629
630            unsafe fn dealloc(&self, ptr: *mut u8, layout: Layout) {
631                self.allocated.fetch_sub(layout.size(), Ordering::SeqCst);
632                self.inner.dealloc(ptr, layout)
633            }
634        }
635    }
636
637    use tracking_allocator::TrackingAllocator;
638
639    #[global_allocator]
640    static ALLOCATOR: TrackingAllocator = TrackingAllocator::new();
641
642    fn measure_allocation<T, F>(f: F) -> (usize, T)
643    where
644        F: FnOnce() -> T,
645    {
646        ALLOCATOR.reset();
647        let result = f();
648        let total = ALLOCATOR.total_allocated();
649        (total, result)
650    }
651
652    #[test]
653    fn measure_storage_cache_overhead() {
654        let (base_overhead, cache) = measure_allocation(|| AccountStorageCache::new(1000));
655        println!("Base AccountStorageCache overhead: {} bytes", base_overhead);
656        let mut rng = rand::rng();
657
658        let key = StorageKey::random();
659        let value = StorageValue::from(rng.random::<u128>());
660        let (first_slot, _) = measure_allocation(|| {
661            cache.insert_storage(key, Some(value));
662        });
663        println!("First slot insertion overhead: {} bytes", first_slot);
664
665        const TOTAL_SLOTS: usize = 10_000;
666        let (test_slots, _) = measure_allocation(|| {
667            for _ in 0..TOTAL_SLOTS {
668                let key = StorageKey::random();
669                let value = StorageValue::from(rng.random::<u128>());
670                cache.insert_storage(key, Some(value));
671            }
672        });
673        println!("Average overhead over {} slots: {} bytes", TOTAL_SLOTS, test_slots / TOTAL_SLOTS);
674
675        println!("\nTheoretical sizes:");
676        println!("StorageKey size: {} bytes", size_of::<StorageKey>());
677        println!("StorageValue size: {} bytes", size_of::<StorageValue>());
678        println!("Option<StorageValue> size: {} bytes", size_of::<Option<StorageValue>>());
679        println!("Option<B256> size: {} bytes", size_of::<Option<B256>>());
680    }
681
682    #[test]
683    fn test_empty_storage_cached_state_provider() {
684        // make sure when we have an empty value in storage, we return `Empty` and not `NotCached`
685        let address = Address::random();
686        let storage_key = StorageKey::random();
687        let account = ExtendedAccount::new(0, U256::ZERO);
688
689        // note there is no storage here
690        let provider = MockEthProvider::default();
691        provider.extend_accounts(vec![(address, account)]);
692
693        let caches = ProviderCacheBuilder::default().build_caches(1000);
694        let state_provider =
695            CachedStateProvider::new_with_caches(provider, caches, CachedStateMetrics::zeroed());
696
697        // check that the storage is empty
698        let res = state_provider.storage(address, storage_key);
699        assert!(res.is_ok());
700        assert_eq!(res.unwrap(), None);
701    }
702
703    #[test]
704    fn test_uncached_storage_cached_state_provider() {
705        // make sure when we have something uncached, we get the cached value
706        let address = Address::random();
707        let storage_key = StorageKey::random();
708        let storage_value = U256::from(1);
709        let account =
710            ExtendedAccount::new(0, U256::ZERO).extend_storage(vec![(storage_key, storage_value)]);
711
712        // note that we extend storage here with one value
713        let provider = MockEthProvider::default();
714        provider.extend_accounts(vec![(address, account)]);
715
716        let caches = ProviderCacheBuilder::default().build_caches(1000);
717        let state_provider =
718            CachedStateProvider::new_with_caches(provider, caches, CachedStateMetrics::zeroed());
719
720        // check that the storage is empty
721        let res = state_provider.storage(address, storage_key);
722        assert!(res.is_ok());
723        assert_eq!(res.unwrap(), Some(storage_value));
724    }
725
726    #[test]
727    fn test_get_storage_populated() {
728        // make sure when we have something cached, we get the cached value in the `SlotStatus`
729        let address = Address::random();
730        let storage_key = StorageKey::random();
731        let storage_value = U256::from(1);
732
733        // insert into caches directly
734        let caches = ProviderCacheBuilder::default().build_caches(1000);
735        caches.insert_storage(address, storage_key, Some(storage_value));
736
737        // check that the storage is empty
738        let slot_status = caches.get_storage(&address, &storage_key);
739        assert_eq!(slot_status, SlotStatus::Value(storage_value));
740    }
741
742    #[test]
743    fn test_get_storage_not_cached() {
744        // make sure when we have nothing cached, we get the `NotCached` value in the `SlotStatus`
745        let storage_key = StorageKey::random();
746        let address = Address::random();
747
748        // just create empty caches
749        let caches = ProviderCacheBuilder::default().build_caches(1000);
750
751        // check that the storage is empty
752        let slot_status = caches.get_storage(&address, &storage_key);
753        assert_eq!(slot_status, SlotStatus::NotCached);
754    }
755
756    #[test]
757    fn test_get_storage_empty() {
758        // make sure when we insert an empty value to the cache, we get the `Empty` value in the
759        // `SlotStatus`
760        let address = Address::random();
761        let storage_key = StorageKey::random();
762
763        // insert into caches directly
764        let caches = ProviderCacheBuilder::default().build_caches(1000);
765        caches.insert_storage(address, storage_key, None);
766
767        // check that the storage is empty
768        let slot_status = caches.get_storage(&address, &storage_key);
769        assert_eq!(slot_status, SlotStatus::Empty);
770    }
771}