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 std::{
22 sync::{
23 atomic::{AtomicU64, AtomicUsize, Ordering},
24 Arc,
25 },
26 time::Duration,
27};
28use tracing::{debug_span, instrument, trace, warn};
29
30const FIXED_CACHE_ALIGNMENT: usize = 128;
35
36const FIXED_CACHE_ENTRY_OVERHEAD: usize = size_of::<usize>();
38
39const fn fixed_cache_entry_size<K, V>() -> usize {
44 fixed_cache_key_size_with_value::<K>(size_of::<V>())
45}
46
47const fn fixed_cache_key_size_with_value<K>(value: usize) -> usize {
52 let raw_size = FIXED_CACHE_ENTRY_OVERHEAD + size_of::<K>() + value;
53 raw_size.div_ceil(FIXED_CACHE_ALIGNMENT) * FIXED_CACHE_ALIGNMENT
55}
56
57const ESTIMATED_AVG_CODE_SIZE: usize = 8 * 1024;
64
65const CODE_CACHE_ENTRY_SIZE: usize =
67 fixed_cache_key_size_with_value::<Address>(ESTIMATED_AVG_CODE_SIZE);
68
69const STORAGE_CACHE_ENTRY_SIZE: usize =
71 fixed_cache_entry_size::<(Address, StorageKey), StorageValue>();
72
73const ACCOUNT_CACHE_ENTRY_SIZE: usize = fixed_cache_entry_size::<Address, Option<Account>>();
75
76struct EpochCacheConfig;
78impl CacheConfig for EpochCacheConfig {
79 const EPOCHS: bool = true;
80}
81
82type FixedCache<K, V, H = DefaultHashBuilder> = fixed_cache::Cache<K, V, H, EpochCacheConfig>;
84
85#[derive(Debug)]
95pub struct CachedStateProvider<S, const PREWARM: bool = false> {
96 state_provider: S,
98
99 caches: ExecutionCache,
101
102 metrics: CachedStateMetrics,
104
105 cache_stats: Option<Arc<CacheStats>>,
108}
109
110impl<S> CachedStateProvider<S> {
111 pub const fn new(
114 state_provider: S,
115 caches: ExecutionCache,
116 metrics: CachedStateMetrics,
117 ) -> Self {
118 Self { state_provider, caches, metrics, cache_stats: None }
119 }
120}
121
122impl<S> CachedStateProvider<S, true> {
123 pub const fn new_prewarm(
125 state_provider: S,
126 caches: ExecutionCache,
127 metrics: CachedStateMetrics,
128 ) -> Self {
129 Self { state_provider, caches, metrics, cache_stats: None }
130 }
131}
132
133impl<S, const PREWARM: bool> CachedStateProvider<S, PREWARM> {
134 pub fn with_cache_stats(mut self, stats: Option<Arc<CacheStats>>) -> Self {
136 self.cache_stats = stats;
137 self
138 }
139}
140
141#[derive(Debug, Clone, PartialEq, Eq)]
143pub enum CachedStatus<T> {
144 NotCached(T),
146 Cached(T),
148}
149
150#[derive(Metrics, Clone)]
152#[metrics(scope = "sync.caching")]
153pub struct CachedStateMetrics {
154 execution_cache_created_total: Counter,
156
157 execution_cache_creation_duration_seconds: Histogram,
159
160 code_cache_hits: Gauge,
162
163 code_cache_misses: Gauge,
165
166 code_cache_size: Gauge,
168
169 code_cache_capacity: Gauge,
171
172 code_cache_collisions: Gauge,
174
175 storage_cache_hits: Gauge,
177
178 storage_cache_misses: Gauge,
180
181 storage_cache_size: Gauge,
183
184 storage_cache_capacity: Gauge,
186
187 storage_cache_collisions: Gauge,
189
190 account_cache_hits: Gauge,
192
193 account_cache_misses: Gauge,
195
196 account_cache_size: Gauge,
198
199 account_cache_capacity: Gauge,
201
202 account_cache_collisions: Gauge,
204}
205
206impl CachedStateMetrics {
207 pub fn reset(&self) {
209 self.code_cache_hits.set(0);
211 self.code_cache_misses.set(0);
212 self.code_cache_collisions.set(0);
213
214 self.storage_cache_hits.set(0);
216 self.storage_cache_misses.set(0);
217 self.storage_cache_collisions.set(0);
218
219 self.account_cache_hits.set(0);
221 self.account_cache_misses.set(0);
222 self.account_cache_collisions.set(0);
223 }
224
225 pub fn zeroed() -> Self {
227 let zeroed = Self::default();
228 zeroed.reset();
229 zeroed
230 }
231
232 pub fn record_cache_creation(&self, duration: Duration) {
234 self.execution_cache_created_total.increment(1);
235 self.execution_cache_creation_duration_seconds.record(duration.as_secs_f64());
236 }
237}
238
239#[derive(Debug, Default)]
241pub struct CacheStats {
242 account_hits: AtomicUsize,
244 account_misses: AtomicUsize,
246 storage_hits: AtomicUsize,
248 storage_misses: AtomicUsize,
250 code_hits: AtomicUsize,
252 code_misses: AtomicUsize,
254}
255
256impl CacheStats {
257 pub fn record_account_hit(&self) {
259 self.account_hits.fetch_add(1, Ordering::Relaxed);
260 }
261
262 pub fn record_account_miss(&self) {
264 self.account_misses.fetch_add(1, Ordering::Relaxed);
265 }
266
267 pub fn account_hits(&self) -> usize {
269 self.account_hits.load(Ordering::Relaxed)
270 }
271
272 pub fn account_misses(&self) -> usize {
274 self.account_misses.load(Ordering::Relaxed)
275 }
276
277 pub fn record_storage_hit(&self) {
279 self.storage_hits.fetch_add(1, Ordering::Relaxed);
280 }
281
282 pub fn record_storage_miss(&self) {
284 self.storage_misses.fetch_add(1, Ordering::Relaxed);
285 }
286
287 pub fn storage_hits(&self) -> usize {
289 self.storage_hits.load(Ordering::Relaxed)
290 }
291
292 pub fn storage_misses(&self) -> usize {
294 self.storage_misses.load(Ordering::Relaxed)
295 }
296
297 pub fn record_code_hit(&self) {
299 self.code_hits.fetch_add(1, Ordering::Relaxed);
300 }
301
302 pub fn record_code_miss(&self) {
304 self.code_misses.fetch_add(1, Ordering::Relaxed);
305 }
306
307 pub fn code_hits(&self) -> usize {
309 self.code_hits.load(Ordering::Relaxed)
310 }
311
312 pub fn code_misses(&self) -> usize {
314 self.code_misses.load(Ordering::Relaxed)
315 }
316}
317
318#[derive(Debug)]
333pub struct CacheStatsHandler {
334 collisions: AtomicU64,
335 size: AtomicUsize,
336 capacity: usize,
337}
338
339impl CacheStatsHandler {
340 pub const fn new(capacity: usize) -> Self {
342 Self { collisions: AtomicU64::new(0), size: AtomicUsize::new(0), capacity }
343 }
344
345 pub fn collisions(&self) -> u64 {
347 self.collisions.load(Ordering::Relaxed)
348 }
349
350 pub fn size(&self) -> usize {
352 self.size.load(Ordering::Relaxed)
353 }
354
355 pub const fn capacity(&self) -> usize {
357 self.capacity
358 }
359
360 pub fn increment_size(&self) {
362 let _ = self.size.fetch_add(1, Ordering::Relaxed);
363 }
364
365 pub fn decrement_size(&self) {
367 let _ = self.size.fetch_sub(1, Ordering::Relaxed);
368 }
369
370 pub fn reset_size(&self) {
372 self.size.store(0, Ordering::Relaxed);
373 }
374
375 pub fn reset_stats(&self) {
377 self.collisions.store(0, Ordering::Relaxed);
378 }
379}
380
381impl<K: PartialEq, V> StatsHandler<K, V> for CacheStatsHandler {
382 fn on_hit(&self, _key: &K, _value: &V) {}
383
384 fn on_miss(&self, _key: AnyRef<'_>) {}
385
386 fn on_insert(&self, key: &K, _value: &V, evicted: Option<(&K, &V)>) {
387 match evicted {
388 None => {
389 self.increment_size();
391 }
392 Some((evicted_key, _)) if evicted_key != key => {
393 self.collisions.fetch_add(1, Ordering::Relaxed);
395 }
396 Some(_) => {
397 }
399 }
400 }
401
402 fn on_remove(&self, _key: &K, _value: &V) {
403 self.decrement_size();
404 }
405}
406
407impl<S: AccountReader, const PREWARM: bool> AccountReader for CachedStateProvider<S, PREWARM> {
408 fn basic_account(&self, address: &Address) -> ProviderResult<Option<Account>> {
409 if PREWARM {
410 match self.caches.get_or_try_insert_account_with(*address, || {
411 self.state_provider.basic_account(address)
412 })? {
413 CachedStatus::NotCached(value) => {
415 if let Some(stats) = &self.cache_stats {
416 stats.record_account_miss();
417 }
418 Ok(value)
419 }
420 CachedStatus::Cached(value) => {
421 if let Some(stats) = &self.cache_stats {
422 stats.record_account_hit();
423 }
424 Ok(value)
425 }
426 }
427 } else if let Some(account) = self.caches.0.account_cache.get(address) {
428 self.metrics.account_cache_hits.increment(1);
429 if let Some(stats) = &self.cache_stats {
430 stats.record_account_hit();
431 }
432 Ok(account)
433 } else {
434 self.metrics.account_cache_misses.increment(1);
435 if let Some(stats) = &self.cache_stats {
436 stats.record_account_miss();
437 }
438 self.state_provider.basic_account(address)
439 }
440 }
441}
442
443impl<S: StateProvider, const PREWARM: bool> StateProvider for CachedStateProvider<S, PREWARM> {
444 fn storage(
445 &self,
446 account: Address,
447 storage_key: StorageKey,
448 ) -> ProviderResult<Option<StorageValue>> {
449 if PREWARM {
450 match self.caches.get_or_try_insert_storage_with(account, storage_key, || {
451 self.state_provider.storage(account, storage_key).map(Option::unwrap_or_default)
452 })? {
453 CachedStatus::NotCached(value) => {
455 if let Some(stats) = &self.cache_stats {
456 stats.record_storage_miss();
457 }
458 Ok(Some(value).filter(|v| !v.is_zero()))
459 }
460 CachedStatus::Cached(value) => {
461 if let Some(stats) = &self.cache_stats {
462 stats.record_storage_hit();
463 }
464 Ok(Some(value).filter(|v| !v.is_zero()))
465 }
466 }
467 } else if let Some(value) = self.caches.0.storage_cache.get(&(account, storage_key)) {
468 self.metrics.storage_cache_hits.increment(1);
469 if let Some(stats) = &self.cache_stats {
470 stats.record_storage_hit();
471 }
472 Ok(Some(value).filter(|v| !v.is_zero()))
473 } else {
474 self.metrics.storage_cache_misses.increment(1);
475 if let Some(stats) = &self.cache_stats {
476 stats.record_storage_miss();
477 }
478 self.state_provider.storage(account, storage_key)
479 }
480 }
481}
482
483impl<S: BytecodeReader, const PREWARM: bool> BytecodeReader for CachedStateProvider<S, PREWARM> {
484 fn bytecode_by_hash(&self, code_hash: &B256) -> ProviderResult<Option<Bytecode>> {
485 if PREWARM {
486 match self.caches.get_or_try_insert_code_with(*code_hash, || {
487 self.state_provider.bytecode_by_hash(code_hash)
488 })? {
489 CachedStatus::NotCached(code) => {
491 if let Some(stats) = &self.cache_stats {
492 stats.record_code_miss();
493 }
494 Ok(code)
495 }
496 CachedStatus::Cached(code) => {
497 if let Some(stats) = &self.cache_stats {
498 stats.record_code_hit();
499 }
500 Ok(code)
501 }
502 }
503 } else if let Some(code) = self.caches.0.code_cache.get(code_hash) {
504 self.metrics.code_cache_hits.increment(1);
505 if let Some(stats) = &self.cache_stats {
506 stats.record_code_hit();
507 }
508 Ok(code)
509 } else {
510 self.metrics.code_cache_misses.increment(1);
511 if let Some(stats) = &self.cache_stats {
512 stats.record_code_miss();
513 }
514 self.state_provider.bytecode_by_hash(code_hash)
515 }
516 }
517}
518
519impl<S: StateRootProvider, const PREWARM: bool> StateRootProvider
520 for CachedStateProvider<S, PREWARM>
521{
522 fn state_root(&self, hashed_state: HashedPostState) -> ProviderResult<B256> {
523 self.state_provider.state_root(hashed_state)
524 }
525
526 fn state_root_from_nodes(&self, input: TrieInput) -> ProviderResult<B256> {
527 self.state_provider.state_root_from_nodes(input)
528 }
529
530 fn state_root_with_updates(
531 &self,
532 hashed_state: HashedPostState,
533 ) -> ProviderResult<(B256, TrieUpdates)> {
534 self.state_provider.state_root_with_updates(hashed_state)
535 }
536
537 fn state_root_from_nodes_with_updates(
538 &self,
539 input: TrieInput,
540 ) -> ProviderResult<(B256, TrieUpdates)> {
541 self.state_provider.state_root_from_nodes_with_updates(input)
542 }
543}
544
545impl<S: StateProofProvider, const PREWARM: bool> StateProofProvider
546 for CachedStateProvider<S, PREWARM>
547{
548 fn proof(
549 &self,
550 input: TrieInput,
551 address: Address,
552 slots: &[B256],
553 ) -> ProviderResult<AccountProof> {
554 self.state_provider.proof(input, address, slots)
555 }
556
557 fn multiproof(
558 &self,
559 input: TrieInput,
560 targets: MultiProofTargets,
561 ) -> ProviderResult<MultiProof> {
562 self.state_provider.multiproof(input, targets)
563 }
564
565 fn witness(
566 &self,
567 input: TrieInput,
568 target: HashedPostState,
569 ) -> ProviderResult<Vec<alloy_primitives::Bytes>> {
570 self.state_provider.witness(input, target)
571 }
572}
573
574impl<S: StorageRootProvider, const PREWARM: bool> StorageRootProvider
575 for CachedStateProvider<S, PREWARM>
576{
577 fn storage_root(
578 &self,
579 address: Address,
580 hashed_storage: HashedStorage,
581 ) -> ProviderResult<B256> {
582 self.state_provider.storage_root(address, hashed_storage)
583 }
584
585 fn storage_proof(
586 &self,
587 address: Address,
588 slot: B256,
589 hashed_storage: HashedStorage,
590 ) -> ProviderResult<StorageProof> {
591 self.state_provider.storage_proof(address, slot, hashed_storage)
592 }
593
594 fn storage_multiproof(
595 &self,
596 address: Address,
597 slots: &[B256],
598 hashed_storage: HashedStorage,
599 ) -> ProviderResult<StorageMultiProof> {
600 self.state_provider.storage_multiproof(address, slots, hashed_storage)
601 }
602}
603
604impl<S: BlockHashReader, const PREWARM: bool> BlockHashReader for CachedStateProvider<S, PREWARM> {
605 fn block_hash(&self, number: alloy_primitives::BlockNumber) -> ProviderResult<Option<B256>> {
606 self.state_provider.block_hash(number)
607 }
608
609 fn canonical_hashes_range(
610 &self,
611 start: alloy_primitives::BlockNumber,
612 end: alloy_primitives::BlockNumber,
613 ) -> ProviderResult<Vec<B256>> {
614 self.state_provider.canonical_hashes_range(start, end)
615 }
616}
617
618impl<S: HashedPostStateProvider, const PREWARM: bool> HashedPostStateProvider
619 for CachedStateProvider<S, PREWARM>
620{
621 fn hashed_post_state(&self, bundle_state: &reth_revm::db::BundleState) -> HashedPostState {
622 self.state_provider.hashed_post_state(bundle_state)
623 }
624}
625
626#[derive(Debug, Clone)]
637pub struct ExecutionCache(Arc<ExecutionCacheInner>);
638
639#[derive(Debug)]
641struct ExecutionCacheInner {
642 code_cache: FixedCache<B256, Option<Bytecode>, FbBuildHasher<32>>,
644
645 storage_cache: FixedCache<(Address, StorageKey), StorageValue>,
647
648 account_cache: FixedCache<Address, Option<Account>, FbBuildHasher<20>>,
650
651 code_stats: Arc<CacheStatsHandler>,
653
654 storage_stats: Arc<CacheStatsHandler>,
656
657 account_stats: Arc<CacheStatsHandler>,
659
660 selfdestruct_encountered: Once,
662}
663
664impl ExecutionCache {
665 const MIN_CACHE_SIZE_WITH_EPOCHS: usize = 1 << 12; pub const fn bytes_to_entries(size_bytes: usize, entry_size: usize) -> usize {
674 let entries = size_bytes / entry_size;
675 let rounded = if entries == 0 { 1 } else { (entries + 1).next_power_of_two() >> 1 };
677 if rounded < Self::MIN_CACHE_SIZE_WITH_EPOCHS {
679 Self::MIN_CACHE_SIZE_WITH_EPOCHS
680 } else {
681 rounded
682 }
683 }
684
685 pub fn new(total_cache_size: usize) -> Self {
687 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);
692 let storage_capacity = Self::bytes_to_entries(storage_cache_size, STORAGE_CACHE_ENTRY_SIZE);
693 let account_capacity = Self::bytes_to_entries(account_cache_size, ACCOUNT_CACHE_ENTRY_SIZE);
694
695 let code_stats = Arc::new(CacheStatsHandler::new(code_capacity));
696 let storage_stats = Arc::new(CacheStatsHandler::new(storage_capacity));
697 let account_stats = Arc::new(CacheStatsHandler::new(account_capacity));
698
699 Self(Arc::new(ExecutionCacheInner {
700 code_cache: FixedCache::new(code_capacity, FbBuildHasher::<32>::default())
701 .with_stats(Some(Stats::new(code_stats.clone()))),
702 storage_cache: FixedCache::new(storage_capacity, DefaultHashBuilder::default())
703 .with_stats(Some(Stats::new(storage_stats.clone()))),
704 account_cache: FixedCache::new(account_capacity, FbBuildHasher::<20>::default())
705 .with_stats(Some(Stats::new(account_stats.clone()))),
706 code_stats,
707 storage_stats,
708 account_stats,
709 selfdestruct_encountered: Once::new(),
710 }))
711 }
712
713 pub fn get_or_try_insert_code_with<E>(
715 &self,
716 hash: B256,
717 f: impl FnOnce() -> Result<Option<Bytecode>, E>,
718 ) -> Result<CachedStatus<Option<Bytecode>>, E> {
719 let mut miss = false;
720 let result = self.0.code_cache.get_or_try_insert_with(hash, |_| {
721 miss = true;
722 f()
723 })?;
724
725 if miss {
726 Ok(CachedStatus::NotCached(result))
727 } else {
728 Ok(CachedStatus::Cached(result))
729 }
730 }
731
732 pub fn get_or_try_insert_storage_with<E>(
734 &self,
735 address: Address,
736 key: StorageKey,
737 f: impl FnOnce() -> Result<StorageValue, E>,
738 ) -> Result<CachedStatus<StorageValue>, E> {
739 let mut miss = false;
740 let result = self.0.storage_cache.get_or_try_insert_with((address, key), |_| {
741 miss = true;
742 f()
743 })?;
744
745 if miss {
746 Ok(CachedStatus::NotCached(result))
747 } else {
748 Ok(CachedStatus::Cached(result))
749 }
750 }
751
752 pub fn get_or_try_insert_account_with<E>(
754 &self,
755 address: Address,
756 f: impl FnOnce() -> Result<Option<Account>, E>,
757 ) -> Result<CachedStatus<Option<Account>>, E> {
758 let mut miss = false;
759 let result = self.0.account_cache.get_or_try_insert_with(address, |_| {
760 miss = true;
761 f()
762 })?;
763
764 if miss {
765 Ok(CachedStatus::NotCached(result))
766 } else {
767 Ok(CachedStatus::Cached(result))
768 }
769 }
770
771 pub fn insert_storage(&self, address: Address, key: StorageKey, value: Option<StorageValue>) {
773 self.0.storage_cache.insert((address, key), value.unwrap_or_default());
774 }
775
776 pub fn insert_code(&self, hash: B256, code: Option<Bytecode>) {
778 self.0.code_cache.insert(hash, code);
779 }
780
781 pub fn insert_account(&self, address: Address, account: Option<Account>) {
783 self.0.account_cache.insert(address, account);
784 }
785
786 #[instrument(level = "debug", target = "engine::caching", skip_all)]
805 #[expect(clippy::result_unit_err)]
806 pub fn insert_state(&self, state_updates: &BundleState) -> Result<(), ()> {
807 let _enter =
808 debug_span!(target: "engine::tree", "contracts", len = state_updates.contracts.len())
809 .entered();
810 for (code_hash, bytecode) in &state_updates.contracts {
812 self.insert_code(*code_hash, Some(Bytecode(bytecode.clone())));
813 }
814 drop(_enter);
815
816 let _enter = debug_span!(
817 target: "engine::tree",
818 "accounts",
819 accounts = state_updates.state.len(),
820 storages =
821 state_updates.state.values().map(|account| account.storage.len()).sum::<usize>()
822 )
823 .entered();
824 for (addr, account) in &state_updates.state {
825 if account.status.is_not_modified() {
828 continue
829 }
830
831 if account.was_destroyed() {
838 let had_code =
839 account.original_info.as_ref().is_some_and(|info| !info.is_empty_code_hash());
840 if had_code {
841 self.0.selfdestruct_encountered.call_once(|| {
842 warn!(
843 target: "engine::caching",
844 address = ?addr,
845 info = ?account.info,
846 original_info = ?account.original_info,
847 "Encountered an inter-transaction SELFDESTRUCT that reset the storage cache. Are you running a pre-Dencun network?"
848 );
849 });
850 self.clear();
851 return Ok(())
852 }
853
854 self.0.account_cache.remove(addr);
855 self.0.account_stats.decrement_size();
856 continue;
857 }
858
859 let Some(ref account_info) = account.info else {
863 trace!(target: "engine::caching", ?account, "Account with None account info found in state updates");
864 return Err(())
865 };
866
867 for (key, slot) in &account.storage {
869 self.insert_storage(*addr, (*key).into(), Some(slot.present_value));
870 }
871
872 self.insert_account(*addr, Some(Account::from(account_info)));
875 }
876
877 Ok(())
878 }
879
880 pub fn clear(&self) {
885 self.0.storage_cache.clear();
886 self.0.account_cache.clear();
887
888 self.0.storage_stats.reset_size();
889 self.0.account_stats.reset_size();
890 }
891
892 pub fn update_metrics(&self, metrics: &CachedStateMetrics) {
895 metrics.code_cache_size.set(self.0.code_stats.size() as f64);
896 metrics.code_cache_capacity.set(self.0.code_stats.capacity() as f64);
897 metrics.code_cache_collisions.set(self.0.code_stats.collisions() as f64);
898 self.0.code_stats.reset_stats();
899
900 metrics.storage_cache_size.set(self.0.storage_stats.size() as f64);
901 metrics.storage_cache_capacity.set(self.0.storage_stats.capacity() as f64);
902 metrics.storage_cache_collisions.set(self.0.storage_stats.collisions() as f64);
903 self.0.storage_stats.reset_stats();
904
905 metrics.account_cache_size.set(self.0.account_stats.size() as f64);
906 metrics.account_cache_capacity.set(self.0.account_stats.capacity() as f64);
907 metrics.account_cache_collisions.set(self.0.account_stats.collisions() as f64);
908 self.0.account_stats.reset_stats();
909 }
910}
911
912#[derive(Debug, Clone)]
915pub struct SavedCache {
916 hash: B256,
918
919 caches: ExecutionCache,
921
922 metrics: CachedStateMetrics,
924
925 usage_guard: Arc<()>,
928
929 disable_cache_metrics: bool,
931}
932
933impl SavedCache {
934 pub fn new(hash: B256, caches: ExecutionCache, metrics: CachedStateMetrics) -> Self {
936 Self { hash, caches, metrics, usage_guard: Arc::new(()), disable_cache_metrics: false }
937 }
938
939 pub const fn with_disable_cache_metrics(mut self, disable: bool) -> Self {
941 self.disable_cache_metrics = disable;
942 self
943 }
944
945 pub const fn executed_block_hash(&self) -> B256 {
947 self.hash
948 }
949
950 pub fn split(self) -> (ExecutionCache, CachedStateMetrics, bool) {
952 (self.caches, self.metrics, self.disable_cache_metrics)
953 }
954
955 pub fn is_available(&self) -> bool {
957 Arc::strong_count(&self.usage_guard) == 1
958 }
959
960 pub fn usage_count(&self) -> usize {
962 Arc::strong_count(&self.usage_guard)
963 }
964
965 pub const fn cache(&self) -> &ExecutionCache {
967 &self.caches
968 }
969
970 pub const fn metrics(&self) -> &CachedStateMetrics {
972 &self.metrics
973 }
974
975 pub fn update_metrics(&self) {
980 if self.disable_cache_metrics {
981 return
982 }
983 self.caches.update_metrics(&self.metrics);
984 }
985
986 pub fn clear_with_hash(&mut self, hash: B256) {
989 self.hash = hash;
990 self.caches.clear();
991 }
992}
993
994#[cfg(any(test, feature = "test-utils"))]
995impl SavedCache {
996 pub fn clone_guard_for_test(&self) -> Arc<()> {
998 self.usage_guard.clone()
999 }
1000}
1001
1002#[cfg(test)]
1003mod tests {
1004 use super::*;
1005 use alloy_primitives::{map::HashMap, U256};
1006 use reth_provider::test_utils::{ExtendedAccount, MockEthProvider};
1007 use reth_revm::db::{AccountStatus, BundleAccount};
1008 use revm_state::AccountInfo;
1009
1010 #[test]
1011 fn test_empty_storage_cached_state_provider() {
1012 let address = Address::random();
1013 let storage_key = StorageKey::random();
1014 let account = ExtendedAccount::new(0, U256::ZERO);
1015
1016 let provider = MockEthProvider::default();
1017 provider.extend_accounts(vec![(address, account)]);
1018
1019 let caches = ExecutionCache::new(1000);
1020 let state_provider =
1021 CachedStateProvider::new(provider, caches, CachedStateMetrics::zeroed());
1022
1023 let res = state_provider.storage(address, storage_key);
1024 assert!(res.is_ok());
1025 assert_eq!(res.unwrap(), None);
1026 }
1027
1028 #[test]
1029 fn test_uncached_storage_cached_state_provider() {
1030 let address = Address::random();
1031 let storage_key = StorageKey::random();
1032 let storage_value = U256::from(1);
1033 let account =
1034 ExtendedAccount::new(0, U256::ZERO).extend_storage(vec![(storage_key, storage_value)]);
1035
1036 let provider = MockEthProvider::default();
1037 provider.extend_accounts(vec![(address, account)]);
1038
1039 let caches = ExecutionCache::new(1000);
1040 let state_provider =
1041 CachedStateProvider::new(provider, caches, CachedStateMetrics::zeroed());
1042
1043 let res = state_provider.storage(address, storage_key);
1044 assert!(res.is_ok());
1045 assert_eq!(res.unwrap(), Some(storage_value));
1046 }
1047
1048 #[test]
1049 fn test_get_storage_populated() {
1050 let address = Address::random();
1051 let storage_key = StorageKey::random();
1052 let storage_value = U256::from(1);
1053
1054 let caches = ExecutionCache::new(1000);
1055 caches.insert_storage(address, storage_key, Some(storage_value));
1056
1057 let result = caches
1058 .get_or_try_insert_storage_with(address, storage_key, || Ok::<_, ()>(U256::from(999)));
1059 assert_eq!(result.unwrap(), CachedStatus::Cached(storage_value));
1060 }
1061
1062 #[test]
1063 fn test_get_storage_empty() {
1064 let address = Address::random();
1065 let storage_key = StorageKey::random();
1066
1067 let caches = ExecutionCache::new(1000);
1068 caches.insert_storage(address, storage_key, None);
1069
1070 let result = caches
1071 .get_or_try_insert_storage_with(address, storage_key, || Ok::<_, ()>(U256::from(999)));
1072 assert_eq!(result.unwrap(), CachedStatus::Cached(U256::ZERO));
1073 }
1074
1075 #[test]
1076 fn test_saved_cache_is_available() {
1077 let execution_cache = ExecutionCache::new(1000);
1078 let cache = SavedCache::new(B256::ZERO, execution_cache, CachedStateMetrics::zeroed());
1079
1080 assert!(cache.is_available(), "Cache should be available initially");
1081
1082 let _guard = cache.clone_guard_for_test();
1083
1084 assert!(!cache.is_available(), "Cache should not be available with active guard");
1085 }
1086
1087 #[test]
1088 fn test_saved_cache_multiple_references() {
1089 let execution_cache = ExecutionCache::new(1000);
1090 let cache =
1091 SavedCache::new(B256::from([2u8; 32]), execution_cache, CachedStateMetrics::zeroed());
1092
1093 let guard1 = cache.clone_guard_for_test();
1094 let guard2 = cache.clone_guard_for_test();
1095 let guard3 = guard1.clone();
1096
1097 assert!(!cache.is_available());
1098
1099 drop(guard1);
1100 assert!(!cache.is_available());
1101
1102 drop(guard2);
1103 assert!(!cache.is_available());
1104
1105 drop(guard3);
1106 assert!(cache.is_available());
1107 }
1108
1109 #[test]
1110 fn test_insert_state_destroyed_account_with_code_clears_cache() {
1111 let caches = ExecutionCache::new(1000);
1112
1113 let addr1 = Address::random();
1115 let addr2 = Address::random();
1116 let storage_key = StorageKey::random();
1117 caches.insert_account(addr1, Some(Account::default()));
1118 caches.insert_account(addr2, Some(Account::default()));
1119 caches.insert_storage(addr1, storage_key, Some(U256::from(42)));
1120
1121 assert!(caches.0.account_cache.get(&addr1).is_some());
1123 assert!(caches.0.account_cache.get(&addr2).is_some());
1124 assert!(caches.0.storage_cache.get(&(addr1, storage_key)).is_some());
1125
1126 let bundle = BundleState {
1127 state: HashMap::from_iter([(
1129 Address::random(),
1130 BundleAccount::new(
1131 Some(AccountInfo {
1132 balance: U256::ZERO,
1133 nonce: 1,
1134 code_hash: B256::random(), code: None,
1136 account_id: None,
1137 }),
1138 None, Default::default(),
1140 AccountStatus::Destroyed,
1141 ),
1142 )]),
1143 contracts: Default::default(),
1144 reverts: Default::default(),
1145 state_size: 0,
1146 reverts_size: 0,
1147 };
1148
1149 let result = caches.insert_state(&bundle);
1151 assert!(result.is_ok());
1152
1153 assert!(caches.0.account_cache.get(&addr1).is_none());
1155 assert!(caches.0.account_cache.get(&addr2).is_none());
1156 assert!(caches.0.storage_cache.get(&(addr1, storage_key)).is_none());
1157 }
1158
1159 #[test]
1160 fn test_insert_state_destroyed_account_without_code_removes_only_account() {
1161 let caches = ExecutionCache::new(1000);
1162
1163 let addr1 = Address::random();
1165 let addr2 = Address::random();
1166 let storage_key = StorageKey::random();
1167 caches.insert_account(addr1, Some(Account::default()));
1168 caches.insert_account(addr2, Some(Account::default()));
1169 caches.insert_storage(addr1, storage_key, Some(U256::from(42)));
1170
1171 let bundle = BundleState {
1172 state: HashMap::from_iter([(
1174 addr1,
1175 BundleAccount::new(
1176 Some(AccountInfo {
1177 balance: U256::from(100),
1178 nonce: 1,
1179 code_hash: alloy_primitives::KECCAK256_EMPTY, code: None,
1181 account_id: None,
1182 }),
1183 None, Default::default(),
1185 AccountStatus::Destroyed,
1186 ),
1187 )]),
1188 contracts: Default::default(),
1189 reverts: Default::default(),
1190 state_size: 0,
1191 reverts_size: 0,
1192 };
1193
1194 assert!(caches.insert_state(&bundle).is_ok());
1196
1197 assert!(caches.0.account_cache.get(&addr1).is_none());
1199 assert!(caches.0.account_cache.get(&addr2).is_some());
1200 assert!(caches.0.storage_cache.get(&(addr1, storage_key)).is_some());
1201 }
1202
1203 #[test]
1204 fn test_insert_state_destroyed_account_no_original_info_removes_only_account() {
1205 let caches = ExecutionCache::new(1000);
1206
1207 let addr1 = Address::random();
1209 let addr2 = Address::random();
1210 caches.insert_account(addr1, Some(Account::default()));
1211 caches.insert_account(addr2, Some(Account::default()));
1212
1213 let bundle = BundleState {
1214 state: HashMap::from_iter([(
1216 addr1,
1217 BundleAccount::new(
1218 None, None, Default::default(),
1221 AccountStatus::Destroyed,
1222 ),
1223 )]),
1224 contracts: Default::default(),
1225 reverts: Default::default(),
1226 state_size: 0,
1227 reverts_size: 0,
1228 };
1229
1230 assert!(caches.insert_state(&bundle).is_ok());
1232
1233 assert!(caches.0.account_cache.get(&addr1).is_none());
1235 assert!(caches.0.account_cache.get(&addr2).is_some());
1236 }
1237
1238 #[test]
1239 fn test_code_cache_capacity_with_default_budget() {
1240 let total_cache_size = 4 * 1024 * 1024 * 1024; let code_budget = (total_cache_size * 556) / 10000; let capacity = ExecutionCache::bytes_to_entries(code_budget, CODE_CACHE_ENTRY_SIZE);
1245
1246 assert_eq!(
1249 capacity, 16384,
1250 "code cache should have 16384 entries with default 4 GB budget"
1251 );
1252 }
1253}