1use alloy_primitives::{
3 map::{DefaultHashBuilder, FbBuildHasher},
4 Address, StorageKey, StorageValue, B256,
5};
6use fixed_cache::{AnyRef, CacheConfig, Stats, StatsHandler};
7use metrics::{Counter, Gauge, Histogram};
8use parking_lot::Once;
9use reth_errors::ProviderResult;
10use reth_metrics::Metrics;
11use reth_primitives_traits::{Account, Bytecode};
12use reth_provider::{
13 AccountReader, BlockHashReader, BytecodeReader, HashedPostStateProvider, StateProofProvider,
14 StateProvider, StateRootProvider, StorageRootProvider,
15};
16use reth_revm::db::BundleState;
17use reth_trie::{
18 updates::TrieUpdates, AccountProof, HashedPostState, HashedStorage, MultiProof,
19 MultiProofTargets, StorageMultiProof, StorageProof, TrieInput,
20};
21use revm_primitives::eip7907::MAX_CODE_SIZE;
22use std::{
23 mem::size_of,
24 sync::{
25 atomic::{AtomicU64, AtomicUsize, Ordering},
26 Arc,
27 },
28 time::Duration,
29};
30use tracing::{debug_span, instrument, trace, warn};
31
32const FIXED_CACHE_ALIGNMENT: usize = 128;
37
38const FIXED_CACHE_ENTRY_OVERHEAD: usize = size_of::<usize>();
40
41const fn fixed_cache_entry_size<K, V>() -> usize {
46 fixed_cache_key_size_with_value::<K>(size_of::<V>())
47}
48
49const fn fixed_cache_key_size_with_value<K>(value: usize) -> usize {
54 let raw_size = FIXED_CACHE_ENTRY_OVERHEAD + size_of::<K>() + value;
55 raw_size.div_ceil(FIXED_CACHE_ALIGNMENT) * FIXED_CACHE_ALIGNMENT
57}
58
59const CODE_CACHE_ENTRY_SIZE: usize = fixed_cache_key_size_with_value::<Address>(MAX_CODE_SIZE);
61
62const STORAGE_CACHE_ENTRY_SIZE: usize =
64 fixed_cache_entry_size::<(Address, StorageKey), StorageValue>();
65
66const ACCOUNT_CACHE_ENTRY_SIZE: usize = fixed_cache_entry_size::<Address, Option<Account>>();
68
69struct EpochCacheConfig;
71impl CacheConfig for EpochCacheConfig {
72 const EPOCHS: bool = true;
73}
74
75type FixedCache<K, V, H = DefaultHashBuilder> = fixed_cache::Cache<K, V, H, EpochCacheConfig>;
77
78#[derive(Debug)]
88pub struct CachedStateProvider<S, const PREWARM: bool = false> {
89 state_provider: S,
91
92 caches: ExecutionCache,
94
95 metrics: CachedStateMetrics,
97}
98
99impl<S> CachedStateProvider<S> {
100 pub const fn new(
103 state_provider: S,
104 caches: ExecutionCache,
105 metrics: CachedStateMetrics,
106 ) -> Self {
107 Self { state_provider, caches, metrics }
108 }
109}
110
111impl<S> CachedStateProvider<S, true> {
112 pub const fn new_prewarm(
114 state_provider: S,
115 caches: ExecutionCache,
116 metrics: CachedStateMetrics,
117 ) -> Self {
118 Self { state_provider, caches, metrics }
119 }
120}
121
122#[derive(Metrics, Clone)]
127#[metrics(scope = "sync.caching")]
128pub struct CachedStateMetrics {
129 execution_cache_created_total: Counter,
131
132 execution_cache_creation_duration_seconds: Histogram,
134
135 code_cache_hits: Gauge,
137
138 code_cache_misses: Gauge,
140
141 code_cache_size: Gauge,
143
144 code_cache_capacity: Gauge,
146
147 code_cache_collisions: Gauge,
149
150 storage_cache_hits: Gauge,
152
153 storage_cache_misses: Gauge,
155
156 storage_cache_size: Gauge,
158
159 storage_cache_capacity: Gauge,
161
162 storage_cache_collisions: Gauge,
164
165 account_cache_hits: Gauge,
167
168 account_cache_misses: Gauge,
170
171 account_cache_size: Gauge,
173
174 account_cache_capacity: Gauge,
176
177 account_cache_collisions: Gauge,
179}
180
181impl CachedStateMetrics {
182 pub fn reset(&self) {
184 self.code_cache_hits.set(0);
186 self.code_cache_misses.set(0);
187 self.code_cache_collisions.set(0);
188
189 self.storage_cache_hits.set(0);
191 self.storage_cache_misses.set(0);
192 self.storage_cache_collisions.set(0);
193
194 self.account_cache_hits.set(0);
196 self.account_cache_misses.set(0);
197 self.account_cache_collisions.set(0);
198 }
199
200 pub fn zeroed() -> Self {
202 let zeroed = Self::default();
203 zeroed.reset();
204 zeroed
205 }
206
207 pub(crate) fn record_cache_creation(&self, duration: Duration) {
209 self.execution_cache_created_total.increment(1);
210 self.execution_cache_creation_duration_seconds.record(duration.as_secs_f64());
211 }
212}
213
214#[derive(Debug)]
229pub(crate) struct CacheStatsHandler {
230 collisions: AtomicU64,
231 size: AtomicUsize,
232 capacity: usize,
233}
234
235impl CacheStatsHandler {
236 pub(crate) const fn new(capacity: usize) -> Self {
238 Self { collisions: AtomicU64::new(0), size: AtomicUsize::new(0), capacity }
239 }
240
241 pub(crate) fn collisions(&self) -> u64 {
243 self.collisions.load(Ordering::Relaxed)
244 }
245
246 pub(crate) fn size(&self) -> usize {
248 self.size.load(Ordering::Relaxed)
249 }
250
251 pub(crate) const fn capacity(&self) -> usize {
253 self.capacity
254 }
255
256 pub(crate) fn increment_size(&self) {
258 let _ = self.size.fetch_add(1, Ordering::Relaxed);
259 }
260
261 pub(crate) fn decrement_size(&self) {
263 let _ = self.size.fetch_sub(1, Ordering::Relaxed);
264 }
265
266 pub(crate) fn reset_size(&self) {
268 self.size.store(0, Ordering::Relaxed);
269 }
270
271 pub(crate) fn reset_stats(&self) {
273 self.collisions.store(0, Ordering::Relaxed);
274 }
275}
276
277impl<K: PartialEq, V> StatsHandler<K, V> for CacheStatsHandler {
278 fn on_hit(&self, _key: &K, _value: &V) {}
279
280 fn on_miss(&self, _key: AnyRef<'_>) {}
281
282 fn on_insert(&self, key: &K, _value: &V, evicted: Option<(&K, &V)>) {
283 match evicted {
284 None => {
285 self.increment_size();
287 }
288 Some((evicted_key, _)) if evicted_key != key => {
289 self.collisions.fetch_add(1, Ordering::Relaxed);
291 }
292 Some(_) => {
293 }
295 }
296 }
297
298 fn on_remove(&self, _key: &K, _value: &V) {
299 self.decrement_size();
300 }
301}
302
303impl<S: AccountReader, const PREWARM: bool> AccountReader for CachedStateProvider<S, PREWARM> {
304 fn basic_account(&self, address: &Address) -> ProviderResult<Option<Account>> {
305 if PREWARM {
306 match self.caches.get_or_try_insert_account_with(*address, || {
307 self.state_provider.basic_account(address)
308 })? {
309 CachedStatus::NotCached(value) | CachedStatus::Cached(value) => Ok(value),
310 }
311 } else if let Some(account) = self.caches.0.account_cache.get(address) {
312 self.metrics.account_cache_hits.increment(1);
313 Ok(account)
314 } else {
315 self.metrics.account_cache_misses.increment(1);
316 self.state_provider.basic_account(address)
317 }
318 }
319}
320
321#[derive(Debug, Clone, PartialEq, Eq)]
323pub enum CachedStatus<T> {
324 NotCached(T),
326 Cached(T),
328}
329
330impl<S: StateProvider, const PREWARM: bool> StateProvider for CachedStateProvider<S, PREWARM> {
331 fn storage(
332 &self,
333 account: Address,
334 storage_key: StorageKey,
335 ) -> ProviderResult<Option<StorageValue>> {
336 if PREWARM {
337 match self.caches.get_or_try_insert_storage_with(account, storage_key, || {
338 self.state_provider.storage(account, storage_key).map(Option::unwrap_or_default)
339 })? {
340 CachedStatus::NotCached(value) | CachedStatus::Cached(value) => {
341 Ok(Some(value).filter(|v| !v.is_zero()))
344 }
345 }
346 } else if let Some(value) = self.caches.0.storage_cache.get(&(account, storage_key)) {
347 self.metrics.storage_cache_hits.increment(1);
348 Ok(Some(value).filter(|v| !v.is_zero()))
349 } else {
350 self.metrics.storage_cache_misses.increment(1);
351 self.state_provider.storage(account, storage_key)
352 }
353 }
354
355 fn storage_by_hashed_key(
356 &self,
357 address: Address,
358 hashed_storage_key: StorageKey,
359 ) -> ProviderResult<Option<StorageValue>> {
360 self.state_provider.storage_by_hashed_key(address, hashed_storage_key)
361 }
362}
363
364impl<S: BytecodeReader, const PREWARM: bool> BytecodeReader for CachedStateProvider<S, PREWARM> {
365 fn bytecode_by_hash(&self, code_hash: &B256) -> ProviderResult<Option<Bytecode>> {
366 if PREWARM {
367 match self.caches.get_or_try_insert_code_with(*code_hash, || {
368 self.state_provider.bytecode_by_hash(code_hash)
369 })? {
370 CachedStatus::NotCached(code) | CachedStatus::Cached(code) => Ok(code),
371 }
372 } else if let Some(code) = self.caches.0.code_cache.get(code_hash) {
373 self.metrics.code_cache_hits.increment(1);
374 Ok(code)
375 } else {
376 self.metrics.code_cache_misses.increment(1);
377 self.state_provider.bytecode_by_hash(code_hash)
378 }
379 }
380}
381
382impl<S: StateRootProvider, const PREWARM: bool> StateRootProvider
383 for CachedStateProvider<S, PREWARM>
384{
385 fn state_root(&self, hashed_state: HashedPostState) -> ProviderResult<B256> {
386 self.state_provider.state_root(hashed_state)
387 }
388
389 fn state_root_from_nodes(&self, input: TrieInput) -> ProviderResult<B256> {
390 self.state_provider.state_root_from_nodes(input)
391 }
392
393 fn state_root_with_updates(
394 &self,
395 hashed_state: HashedPostState,
396 ) -> ProviderResult<(B256, TrieUpdates)> {
397 self.state_provider.state_root_with_updates(hashed_state)
398 }
399
400 fn state_root_from_nodes_with_updates(
401 &self,
402 input: TrieInput,
403 ) -> ProviderResult<(B256, TrieUpdates)> {
404 self.state_provider.state_root_from_nodes_with_updates(input)
405 }
406}
407
408impl<S: StateProofProvider, const PREWARM: bool> StateProofProvider
409 for CachedStateProvider<S, PREWARM>
410{
411 fn proof(
412 &self,
413 input: TrieInput,
414 address: Address,
415 slots: &[B256],
416 ) -> ProviderResult<AccountProof> {
417 self.state_provider.proof(input, address, slots)
418 }
419
420 fn multiproof(
421 &self,
422 input: TrieInput,
423 targets: MultiProofTargets,
424 ) -> ProviderResult<MultiProof> {
425 self.state_provider.multiproof(input, targets)
426 }
427
428 fn witness(
429 &self,
430 input: TrieInput,
431 target: HashedPostState,
432 ) -> ProviderResult<Vec<alloy_primitives::Bytes>> {
433 self.state_provider.witness(input, target)
434 }
435}
436
437impl<S: StorageRootProvider, const PREWARM: bool> StorageRootProvider
438 for CachedStateProvider<S, PREWARM>
439{
440 fn storage_root(
441 &self,
442 address: Address,
443 hashed_storage: HashedStorage,
444 ) -> ProviderResult<B256> {
445 self.state_provider.storage_root(address, hashed_storage)
446 }
447
448 fn storage_proof(
449 &self,
450 address: Address,
451 slot: B256,
452 hashed_storage: HashedStorage,
453 ) -> ProviderResult<StorageProof> {
454 self.state_provider.storage_proof(address, slot, hashed_storage)
455 }
456
457 fn storage_multiproof(
458 &self,
459 address: Address,
460 slots: &[B256],
461 hashed_storage: HashedStorage,
462 ) -> ProviderResult<StorageMultiProof> {
463 self.state_provider.storage_multiproof(address, slots, hashed_storage)
464 }
465}
466
467impl<S: BlockHashReader, const PREWARM: bool> BlockHashReader for CachedStateProvider<S, PREWARM> {
468 fn block_hash(&self, number: alloy_primitives::BlockNumber) -> ProviderResult<Option<B256>> {
469 self.state_provider.block_hash(number)
470 }
471
472 fn canonical_hashes_range(
473 &self,
474 start: alloy_primitives::BlockNumber,
475 end: alloy_primitives::BlockNumber,
476 ) -> ProviderResult<Vec<B256>> {
477 self.state_provider.canonical_hashes_range(start, end)
478 }
479}
480
481impl<S: HashedPostStateProvider, const PREWARM: bool> HashedPostStateProvider
482 for CachedStateProvider<S, PREWARM>
483{
484 fn hashed_post_state(&self, bundle_state: &reth_revm::db::BundleState) -> HashedPostState {
485 self.state_provider.hashed_post_state(bundle_state)
486 }
487}
488
489#[derive(Debug, Clone)]
500pub struct ExecutionCache(Arc<ExecutionCacheInner>);
501
502#[derive(Debug)]
504struct ExecutionCacheInner {
505 code_cache: FixedCache<B256, Option<Bytecode>, FbBuildHasher<32>>,
507
508 storage_cache: FixedCache<(Address, StorageKey), StorageValue>,
510
511 account_cache: FixedCache<Address, Option<Account>, FbBuildHasher<20>>,
513
514 code_stats: Arc<CacheStatsHandler>,
516
517 storage_stats: Arc<CacheStatsHandler>,
519
520 account_stats: Arc<CacheStatsHandler>,
522
523 selfdestruct_encountered: Once,
525}
526
527impl ExecutionCache {
528 const MIN_CACHE_SIZE_WITH_EPOCHS: usize = 1 << 12; pub const fn bytes_to_entries(size_bytes: usize, entry_size: usize) -> usize {
537 let entries = size_bytes / entry_size;
538 let rounded = if entries == 0 { 1 } else { (entries + 1).next_power_of_two() >> 1 };
540 if rounded < Self::MIN_CACHE_SIZE_WITH_EPOCHS {
542 Self::MIN_CACHE_SIZE_WITH_EPOCHS
543 } else {
544 rounded
545 }
546 }
547
548 pub fn new(total_cache_size: usize) -> Self {
550 let code_cache_size = (total_cache_size * 556) / 10000; let storage_cache_size = (total_cache_size * 8888) / 10000; let account_cache_size = (total_cache_size * 556) / 10000; let code_capacity = Self::bytes_to_entries(code_cache_size, CODE_CACHE_ENTRY_SIZE);
555 let storage_capacity = Self::bytes_to_entries(storage_cache_size, STORAGE_CACHE_ENTRY_SIZE);
556 let account_capacity = Self::bytes_to_entries(account_cache_size, ACCOUNT_CACHE_ENTRY_SIZE);
557
558 let code_stats = Arc::new(CacheStatsHandler::new(code_capacity));
559 let storage_stats = Arc::new(CacheStatsHandler::new(storage_capacity));
560 let account_stats = Arc::new(CacheStatsHandler::new(account_capacity));
561
562 Self(Arc::new(ExecutionCacheInner {
563 code_cache: FixedCache::new(code_capacity, FbBuildHasher::<32>::default())
564 .with_stats(Some(Stats::new(code_stats.clone()))),
565 storage_cache: FixedCache::new(storage_capacity, DefaultHashBuilder::default())
566 .with_stats(Some(Stats::new(storage_stats.clone()))),
567 account_cache: FixedCache::new(account_capacity, FbBuildHasher::<20>::default())
568 .with_stats(Some(Stats::new(account_stats.clone()))),
569 code_stats,
570 storage_stats,
571 account_stats,
572 selfdestruct_encountered: Once::new(),
573 }))
574 }
575
576 pub fn get_or_try_insert_code_with<E>(
578 &self,
579 hash: B256,
580 f: impl FnOnce() -> Result<Option<Bytecode>, E>,
581 ) -> Result<CachedStatus<Option<Bytecode>>, E> {
582 let mut miss = false;
583 let result = self.0.code_cache.get_or_try_insert_with(hash, |_| {
584 miss = true;
585 f()
586 })?;
587
588 if miss {
589 Ok(CachedStatus::NotCached(result))
590 } else {
591 Ok(CachedStatus::Cached(result))
592 }
593 }
594
595 pub fn get_or_try_insert_storage_with<E>(
597 &self,
598 address: Address,
599 key: StorageKey,
600 f: impl FnOnce() -> Result<StorageValue, E>,
601 ) -> Result<CachedStatus<StorageValue>, E> {
602 let mut miss = false;
603 let result = self.0.storage_cache.get_or_try_insert_with((address, key), |_| {
604 miss = true;
605 f()
606 })?;
607
608 if miss {
609 Ok(CachedStatus::NotCached(result))
610 } else {
611 Ok(CachedStatus::Cached(result))
612 }
613 }
614
615 pub fn get_or_try_insert_account_with<E>(
617 &self,
618 address: Address,
619 f: impl FnOnce() -> Result<Option<Account>, E>,
620 ) -> Result<CachedStatus<Option<Account>>, E> {
621 let mut miss = false;
622 let result = self.0.account_cache.get_or_try_insert_with(address, |_| {
623 miss = true;
624 f()
625 })?;
626
627 if miss {
628 Ok(CachedStatus::NotCached(result))
629 } else {
630 Ok(CachedStatus::Cached(result))
631 }
632 }
633
634 pub fn insert_storage(&self, address: Address, key: StorageKey, value: Option<StorageValue>) {
636 self.0.storage_cache.insert((address, key), value.unwrap_or_default());
637 }
638
639 fn insert_code(&self, hash: B256, code: Option<Bytecode>) {
641 self.0.code_cache.insert(hash, code);
642 }
643
644 fn insert_account(&self, address: Address, account: Option<Account>) {
646 self.0.account_cache.insert(address, account);
647 }
648
649 #[instrument(level = "debug", target = "engine::caching", skip_all)]
668 #[expect(clippy::result_unit_err)]
669 pub fn insert_state(&self, state_updates: &BundleState) -> Result<(), ()> {
670 let _enter =
671 debug_span!(target: "engine::tree", "contracts", len = state_updates.contracts.len())
672 .entered();
673 for (code_hash, bytecode) in &state_updates.contracts {
675 self.insert_code(*code_hash, Some(Bytecode(bytecode.clone())));
676 }
677 drop(_enter);
678
679 let _enter = debug_span!(
680 target: "engine::tree",
681 "accounts",
682 accounts = state_updates.state.len(),
683 storages =
684 state_updates.state.values().map(|account| account.storage.len()).sum::<usize>()
685 )
686 .entered();
687 for (addr, account) in &state_updates.state {
688 if account.status.is_not_modified() {
691 continue
692 }
693
694 if account.was_destroyed() {
701 let had_code =
702 account.original_info.as_ref().is_some_and(|info| !info.is_empty_code_hash());
703 if had_code {
704 self.0.selfdestruct_encountered.call_once(|| {
705 warn!(
706 target: "engine::caching",
707 address = ?addr,
708 info = ?account.info,
709 original_info = ?account.original_info,
710 "Encountered an inter-transaction SELFDESTRUCT that reset the storage cache. Are you running a pre-Dencun network?"
711 );
712 });
713 self.clear();
714 return Ok(())
715 }
716
717 self.0.account_cache.remove(addr);
718 continue
719 }
720
721 let Some(ref account_info) = account.info else {
725 trace!(target: "engine::caching", ?account, "Account with None account info found in state updates");
726 return Err(())
727 };
728
729 for (key, slot) in &account.storage {
731 self.insert_storage(*addr, (*key).into(), Some(slot.present_value));
732 }
733
734 self.insert_account(*addr, Some(Account::from(account_info)));
737 }
738
739 Ok(())
740 }
741
742 pub(crate) fn clear(&self) {
747 self.0.storage_cache.clear();
748 self.0.account_cache.clear();
749
750 self.0.storage_stats.reset_size();
751 self.0.account_stats.reset_size();
752 }
753
754 pub(crate) fn update_metrics(&self, metrics: &CachedStateMetrics) {
757 metrics.code_cache_size.set(self.0.code_stats.size() as f64);
758 metrics.code_cache_capacity.set(self.0.code_stats.capacity() as f64);
759 metrics.code_cache_collisions.set(self.0.code_stats.collisions() as f64);
760 self.0.code_stats.reset_stats();
761
762 metrics.storage_cache_size.set(self.0.storage_stats.size() as f64);
763 metrics.storage_cache_capacity.set(self.0.storage_stats.capacity() as f64);
764 metrics.storage_cache_collisions.set(self.0.storage_stats.collisions() as f64);
765 self.0.storage_stats.reset_stats();
766
767 metrics.account_cache_size.set(self.0.account_stats.size() as f64);
768 metrics.account_cache_capacity.set(self.0.account_stats.capacity() as f64);
769 metrics.account_cache_collisions.set(self.0.account_stats.collisions() as f64);
770 self.0.account_stats.reset_stats();
771 }
772}
773
774#[derive(Debug, Clone)]
777pub struct SavedCache {
778 hash: B256,
780
781 caches: ExecutionCache,
783
784 metrics: CachedStateMetrics,
786
787 usage_guard: Arc<()>,
790
791 disable_cache_metrics: bool,
793}
794
795impl SavedCache {
796 pub fn new(hash: B256, caches: ExecutionCache, metrics: CachedStateMetrics) -> Self {
798 Self { hash, caches, metrics, usage_guard: Arc::new(()), disable_cache_metrics: false }
799 }
800
801 pub const fn with_disable_cache_metrics(mut self, disable: bool) -> Self {
803 self.disable_cache_metrics = disable;
804 self
805 }
806
807 pub const fn executed_block_hash(&self) -> B256 {
809 self.hash
810 }
811
812 pub fn split(self) -> (ExecutionCache, CachedStateMetrics, bool) {
814 (self.caches, self.metrics, self.disable_cache_metrics)
815 }
816
817 pub fn is_available(&self) -> bool {
819 Arc::strong_count(&self.usage_guard) == 1
820 }
821
822 pub fn usage_count(&self) -> usize {
824 Arc::strong_count(&self.usage_guard)
825 }
826
827 pub const fn cache(&self) -> &ExecutionCache {
829 &self.caches
830 }
831
832 pub const fn metrics(&self) -> &CachedStateMetrics {
834 &self.metrics
835 }
836
837 pub(crate) fn update_metrics(&self) {
842 if self.disable_cache_metrics {
843 return
844 }
845 self.caches.update_metrics(&self.metrics);
846 }
847
848 pub(crate) fn clear(&self) {
850 self.caches.clear();
851 }
852}
853
854#[cfg(test)]
855impl SavedCache {
856 fn clone_guard_for_test(&self) -> Arc<()> {
857 self.usage_guard.clone()
858 }
859}
860
861#[cfg(test)]
862mod tests {
863 use super::*;
864 use alloy_primitives::{map::HashMap, U256};
865 use reth_provider::test_utils::{ExtendedAccount, MockEthProvider};
866 use reth_revm::db::{AccountStatus, BundleAccount};
867 use revm_state::AccountInfo;
868
869 #[test]
870 fn test_empty_storage_cached_state_provider() {
871 let address = Address::random();
872 let storage_key = StorageKey::random();
873 let account = ExtendedAccount::new(0, U256::ZERO);
874
875 let provider = MockEthProvider::default();
876 provider.extend_accounts(vec![(address, account)]);
877
878 let caches = ExecutionCache::new(1000);
879 let state_provider =
880 CachedStateProvider::new(provider, caches, CachedStateMetrics::zeroed());
881
882 let res = state_provider.storage(address, storage_key);
883 assert!(res.is_ok());
884 assert_eq!(res.unwrap(), None);
885 }
886
887 #[test]
888 fn test_uncached_storage_cached_state_provider() {
889 let address = Address::random();
890 let storage_key = StorageKey::random();
891 let storage_value = U256::from(1);
892 let account =
893 ExtendedAccount::new(0, U256::ZERO).extend_storage(vec![(storage_key, storage_value)]);
894
895 let provider = MockEthProvider::default();
896 provider.extend_accounts(vec![(address, account)]);
897
898 let caches = ExecutionCache::new(1000);
899 let state_provider =
900 CachedStateProvider::new(provider, caches, CachedStateMetrics::zeroed());
901
902 let res = state_provider.storage(address, storage_key);
903 assert!(res.is_ok());
904 assert_eq!(res.unwrap(), Some(storage_value));
905 }
906
907 #[test]
908 fn test_get_storage_populated() {
909 let address = Address::random();
910 let storage_key = StorageKey::random();
911 let storage_value = U256::from(1);
912
913 let caches = ExecutionCache::new(1000);
914 caches.insert_storage(address, storage_key, Some(storage_value));
915
916 let result = caches
917 .get_or_try_insert_storage_with(address, storage_key, || Ok::<_, ()>(U256::from(999)));
918 assert_eq!(result.unwrap(), CachedStatus::Cached(storage_value));
919 }
920
921 #[test]
922 fn test_get_storage_empty() {
923 let address = Address::random();
924 let storage_key = StorageKey::random();
925
926 let caches = ExecutionCache::new(1000);
927 caches.insert_storage(address, storage_key, None);
928
929 let result = caches
930 .get_or_try_insert_storage_with(address, storage_key, || Ok::<_, ()>(U256::from(999)));
931 assert_eq!(result.unwrap(), CachedStatus::Cached(U256::ZERO));
932 }
933
934 #[test]
935 fn test_saved_cache_is_available() {
936 let execution_cache = ExecutionCache::new(1000);
937 let cache = SavedCache::new(B256::ZERO, execution_cache, CachedStateMetrics::zeroed());
938
939 assert!(cache.is_available(), "Cache should be available initially");
940
941 let _guard = cache.clone_guard_for_test();
942
943 assert!(!cache.is_available(), "Cache should not be available with active guard");
944 }
945
946 #[test]
947 fn test_saved_cache_multiple_references() {
948 let execution_cache = ExecutionCache::new(1000);
949 let cache =
950 SavedCache::new(B256::from([2u8; 32]), execution_cache, CachedStateMetrics::zeroed());
951
952 let guard1 = cache.clone_guard_for_test();
953 let guard2 = cache.clone_guard_for_test();
954 let guard3 = guard1.clone();
955
956 assert!(!cache.is_available());
957
958 drop(guard1);
959 assert!(!cache.is_available());
960
961 drop(guard2);
962 assert!(!cache.is_available());
963
964 drop(guard3);
965 assert!(cache.is_available());
966 }
967
968 #[test]
969 fn test_insert_state_destroyed_account_with_code_clears_cache() {
970 let caches = ExecutionCache::new(1000);
971
972 let addr1 = Address::random();
974 let addr2 = Address::random();
975 let storage_key = StorageKey::random();
976 caches.insert_account(addr1, Some(Account::default()));
977 caches.insert_account(addr2, Some(Account::default()));
978 caches.insert_storage(addr1, storage_key, Some(U256::from(42)));
979
980 assert!(caches.0.account_cache.get(&addr1).is_some());
982 assert!(caches.0.account_cache.get(&addr2).is_some());
983 assert!(caches.0.storage_cache.get(&(addr1, storage_key)).is_some());
984
985 let bundle = BundleState {
986 state: HashMap::from_iter([(
988 Address::random(),
989 BundleAccount::new(
990 Some(AccountInfo {
991 balance: U256::ZERO,
992 nonce: 1,
993 code_hash: B256::random(), code: None,
995 account_id: None,
996 }),
997 None, Default::default(),
999 AccountStatus::Destroyed,
1000 ),
1001 )]),
1002 contracts: Default::default(),
1003 reverts: Default::default(),
1004 state_size: 0,
1005 reverts_size: 0,
1006 };
1007
1008 let result = caches.insert_state(&bundle);
1010 assert!(result.is_ok());
1011
1012 assert!(caches.0.account_cache.get(&addr1).is_none());
1014 assert!(caches.0.account_cache.get(&addr2).is_none());
1015 assert!(caches.0.storage_cache.get(&(addr1, storage_key)).is_none());
1016 }
1017
1018 #[test]
1019 fn test_insert_state_destroyed_account_without_code_removes_only_account() {
1020 let caches = ExecutionCache::new(1000);
1021
1022 let addr1 = Address::random();
1024 let addr2 = Address::random();
1025 let storage_key = StorageKey::random();
1026 caches.insert_account(addr1, Some(Account::default()));
1027 caches.insert_account(addr2, Some(Account::default()));
1028 caches.insert_storage(addr1, storage_key, Some(U256::from(42)));
1029
1030 let bundle = BundleState {
1031 state: HashMap::from_iter([(
1033 addr1,
1034 BundleAccount::new(
1035 Some(AccountInfo {
1036 balance: U256::from(100),
1037 nonce: 1,
1038 code_hash: alloy_primitives::KECCAK256_EMPTY, code: None,
1040 account_id: None,
1041 }),
1042 None, Default::default(),
1044 AccountStatus::Destroyed,
1045 ),
1046 )]),
1047 contracts: Default::default(),
1048 reverts: Default::default(),
1049 state_size: 0,
1050 reverts_size: 0,
1051 };
1052
1053 assert!(caches.insert_state(&bundle).is_ok());
1055
1056 assert!(caches.0.account_cache.get(&addr1).is_none());
1058 assert!(caches.0.account_cache.get(&addr2).is_some());
1059 assert!(caches.0.storage_cache.get(&(addr1, storage_key)).is_some());
1060 }
1061
1062 #[test]
1063 fn test_insert_state_destroyed_account_no_original_info_removes_only_account() {
1064 let caches = ExecutionCache::new(1000);
1065
1066 let addr1 = Address::random();
1068 let addr2 = Address::random();
1069 caches.insert_account(addr1, Some(Account::default()));
1070 caches.insert_account(addr2, Some(Account::default()));
1071
1072 let bundle = BundleState {
1073 state: HashMap::from_iter([(
1075 addr1,
1076 BundleAccount::new(
1077 None, None, Default::default(),
1080 AccountStatus::Destroyed,
1081 ),
1082 )]),
1083 contracts: Default::default(),
1084 reverts: Default::default(),
1085 state_size: 0,
1086 reverts_size: 0,
1087 };
1088
1089 assert!(caches.insert_state(&bundle).is_ok());
1091
1092 assert!(caches.0.account_cache.get(&addr1).is_none());
1094 assert!(caches.0.account_cache.get(&addr2).is_some());
1095 }
1096}