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 cell::Cell,
23 fmt,
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 ESTIMATED_AVG_CODE_SIZE: usize = 8 * 1024;
66
67const CODE_CACHE_ENTRY_SIZE: usize =
69 fixed_cache_key_size_with_value::<Address>(ESTIMATED_AVG_CODE_SIZE);
70
71const STORAGE_CACHE_ENTRY_SIZE: usize =
73 fixed_cache_entry_size::<(Address, StorageKey), StorageValue>();
74
75const ACCOUNT_CACHE_ENTRY_SIZE: usize = fixed_cache_entry_size::<Address, Option<Account>>();
77
78struct EpochCacheConfig;
80impl CacheConfig for EpochCacheConfig {
81 const EPOCHS: bool = true;
82}
83
84type FixedCache<K, V, H = DefaultHashBuilder> = fixed_cache::Cache<K, V, H, EpochCacheConfig>;
86
87#[derive(Debug)]
98pub struct CachedStateProvider<S> {
99 state_provider: S,
101
102 caches: ExecutionCache,
104
105 metrics: Option<CachedStateMetrics>,
107
108 metric_counts: CacheMetricCounts,
110
111 fill_mode: CacheFillMode,
113
114 cache_stats: Option<Arc<CacheStats>>,
117}
118
119impl<S> CachedStateProvider<S> {
120 pub const fn new(
123 state_provider: S,
124 caches: ExecutionCache,
125 metrics: Option<CachedStateMetrics>,
126 ) -> Self {
127 Self::new_with_mode(state_provider, caches, CacheFillMode::LookupOnly, metrics, None)
128 }
129
130 pub const fn new_prewarm(state_provider: S, caches: ExecutionCache) -> Self {
134 Self::new_with_mode(state_provider, caches, CacheFillMode::FillOnMiss, None, None)
135 }
136
137 pub const fn new_with_mode(
140 state_provider: S,
141 caches: ExecutionCache,
142 fill_mode: CacheFillMode,
143 metrics: Option<CachedStateMetrics>,
144 cache_stats: Option<Arc<CacheStats>>,
145 ) -> Self {
146 Self {
147 state_provider,
148 caches,
149 metrics,
150 metric_counts: CacheMetricCounts::new(),
151 fill_mode,
152 cache_stats,
153 }
154 }
155
156 fn record_account_hit(&self) {
157 self.record_metric(CacheMetricKind::AccountHit);
158 if let Some(stats) = &self.cache_stats {
159 stats.record_account_hit();
160 }
161 }
162
163 fn record_account_miss(&self) {
164 self.record_metric(CacheMetricKind::AccountMiss);
165 if let Some(stats) = &self.cache_stats {
166 stats.record_account_miss();
167 }
168 }
169
170 fn record_storage_hit(&self) {
171 self.record_metric(CacheMetricKind::StorageHit);
172 if let Some(stats) = &self.cache_stats {
173 stats.record_storage_hit();
174 }
175 }
176
177 fn record_storage_miss(&self) {
178 self.record_metric(CacheMetricKind::StorageMiss);
179 if let Some(stats) = &self.cache_stats {
180 stats.record_storage_miss();
181 }
182 }
183
184 fn record_code_hit(&self) {
185 self.record_metric(CacheMetricKind::CodeHit);
186 if let Some(stats) = &self.cache_stats {
187 stats.record_code_hit();
188 }
189 }
190
191 fn record_code_miss(&self) {
192 self.record_metric(CacheMetricKind::CodeMiss);
193 if let Some(stats) = &self.cache_stats {
194 stats.record_code_miss();
195 }
196 }
197
198 #[inline]
199 fn record_metric(&self, kind: CacheMetricKind) {
200 if self.metrics.is_some() {
201 self.metric_counts.record(kind);
202 }
203 }
204
205 fn flush_buffered_metrics(&self) {
206 let counts = self.metric_counts.take();
207 if counts.is_empty() {
208 return;
209 }
210
211 if let Some(metrics) = &self.metrics {
212 metrics.record_access_counts(counts);
213 }
214 }
215
216 const fn should_fill_on_miss(&self) -> bool {
217 matches!(self.fill_mode, CacheFillMode::FillOnMiss)
218 }
219}
220
221impl<S> Drop for CachedStateProvider<S> {
222 fn drop(&mut self) {
223 self.flush_buffered_metrics();
224 }
225}
226
227#[derive(Debug, Clone, Copy, PartialEq, Eq)]
229pub enum CacheFillMode {
230 LookupOnly,
232 FillOnMiss,
234}
235
236#[derive(Debug, Clone, Copy, PartialEq, Eq)]
237enum CacheMetricKind {
238 AccountHit,
239 AccountMiss,
240 StorageHit,
241 StorageMiss,
242 CodeHit,
243 CodeMiss,
244}
245
246#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
247struct CacheMetricSnapshot {
248 account_hits: u64,
249 account_misses: u64,
250 storage_hits: u64,
251 storage_misses: u64,
252 code_hits: u64,
253 code_misses: u64,
254}
255
256impl CacheMetricSnapshot {
257 const fn is_empty(&self) -> bool {
258 self.account_hits == 0 &&
259 self.account_misses == 0 &&
260 self.storage_hits == 0 &&
261 self.storage_misses == 0 &&
262 self.code_hits == 0 &&
263 self.code_misses == 0
264 }
265}
266
267#[derive(Debug, Default)]
268struct CacheMetricCounts {
269 account_hits: Cell<u64>,
270 account_misses: Cell<u64>,
271 storage_hits: Cell<u64>,
272 storage_misses: Cell<u64>,
273 code_hits: Cell<u64>,
274 code_misses: Cell<u64>,
275}
276
277impl CacheMetricCounts {
278 const fn new() -> Self {
279 Self {
280 account_hits: Cell::new(0),
281 account_misses: Cell::new(0),
282 storage_hits: Cell::new(0),
283 storage_misses: Cell::new(0),
284 code_hits: Cell::new(0),
285 code_misses: Cell::new(0),
286 }
287 }
288
289 #[inline]
290 fn record(&self, kind: CacheMetricKind) {
291 let counter = match kind {
292 CacheMetricKind::AccountHit => &self.account_hits,
293 CacheMetricKind::AccountMiss => &self.account_misses,
294 CacheMetricKind::StorageHit => &self.storage_hits,
295 CacheMetricKind::StorageMiss => &self.storage_misses,
296 CacheMetricKind::CodeHit => &self.code_hits,
297 CacheMetricKind::CodeMiss => &self.code_misses,
298 };
299 counter.set(counter.get() + 1);
300 }
301
302 const fn take(&self) -> CacheMetricSnapshot {
303 CacheMetricSnapshot {
304 account_hits: self.account_hits.replace(0),
305 account_misses: self.account_misses.replace(0),
306 storage_hits: self.storage_hits.replace(0),
307 storage_misses: self.storage_misses.replace(0),
308 code_hits: self.code_hits.replace(0),
309 code_misses: self.code_misses.replace(0),
310 }
311 }
312}
313
314#[derive(Debug, Clone, PartialEq, Eq)]
316pub enum CachedStatus<T> {
317 NotCached(T),
319 Cached(T),
321}
322
323#[derive(Debug, Clone, Copy, PartialEq, Eq)]
325pub enum CachedStateMetricsSource {
326 Engine,
328 Builder,
330 #[cfg(any(test, feature = "test-utils"))]
332 Test,
333}
334
335impl fmt::Display for CachedStateMetricsSource {
336 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
337 match self {
338 Self::Engine => f.write_str("engine"),
339 Self::Builder => f.write_str("builder"),
340 #[cfg(any(test, feature = "test-utils"))]
341 Self::Test => f.write_str("test"),
342 }
343 }
344}
345
346#[derive(Metrics, Clone)]
348#[metrics(scope = "sync.caching")]
349pub struct CachedStateMetrics {
350 execution_cache_created_total: Counter,
352
353 execution_cache_creation_duration_seconds: Histogram,
355
356 code_cache_hits: Gauge,
358
359 code_cache_misses: Gauge,
361
362 storage_cache_hits: Gauge,
364
365 storage_cache_misses: Gauge,
367
368 account_cache_hits: Gauge,
370
371 account_cache_misses: Gauge,
373}
374
375#[derive(Metrics, Clone)]
377#[metrics(scope = "sync.caching")]
378pub struct CachedStateCacheMetrics {
379 code_cache_size: Gauge,
381
382 code_cache_capacity: Gauge,
384
385 code_cache_collisions: Gauge,
387
388 storage_cache_size: Gauge,
390
391 storage_cache_capacity: Gauge,
393
394 storage_cache_collisions: Gauge,
396
397 account_cache_size: Gauge,
399
400 account_cache_capacity: Gauge,
402
403 account_cache_collisions: Gauge,
405}
406
407impl CachedStateMetrics {
408 pub fn reset(&self) {
410 self.code_cache_hits.set(0);
412 self.code_cache_misses.set(0);
413
414 self.storage_cache_hits.set(0);
416 self.storage_cache_misses.set(0);
417
418 self.account_cache_hits.set(0);
420 self.account_cache_misses.set(0);
421 }
422
423 pub fn zeroed(source: CachedStateMetricsSource) -> Self {
426 let zeroed = Self::new_with_labels(&[("source", source.to_string())]);
427 zeroed.reset();
428 zeroed
429 }
430
431 fn record_access(&self, kind: CacheMetricKind, count: u64) {
432 match kind {
433 CacheMetricKind::AccountHit => self.account_cache_hits.increment(count as f64),
434 CacheMetricKind::AccountMiss => self.account_cache_misses.increment(count as f64),
435 CacheMetricKind::StorageHit => self.storage_cache_hits.increment(count as f64),
436 CacheMetricKind::StorageMiss => self.storage_cache_misses.increment(count as f64),
437 CacheMetricKind::CodeHit => self.code_cache_hits.increment(count as f64),
438 CacheMetricKind::CodeMiss => self.code_cache_misses.increment(count as f64),
439 }
440 }
441
442 fn record_access_counts(&self, counts: CacheMetricSnapshot) {
443 if counts.account_hits != 0 {
444 self.record_access(CacheMetricKind::AccountHit, counts.account_hits);
445 }
446 if counts.account_misses != 0 {
447 self.record_access(CacheMetricKind::AccountMiss, counts.account_misses);
448 }
449 if counts.storage_hits != 0 {
450 self.record_access(CacheMetricKind::StorageHit, counts.storage_hits);
451 }
452 if counts.storage_misses != 0 {
453 self.record_access(CacheMetricKind::StorageMiss, counts.storage_misses);
454 }
455 if counts.code_hits != 0 {
456 self.record_access(CacheMetricKind::CodeHit, counts.code_hits);
457 }
458 if counts.code_misses != 0 {
459 self.record_access(CacheMetricKind::CodeMiss, counts.code_misses);
460 }
461 }
462
463 pub fn record_cache_creation(&self, duration: Duration) {
465 self.execution_cache_created_total.increment(1);
466 self.execution_cache_creation_duration_seconds.record(duration.as_secs_f64());
467 }
468}
469
470#[derive(Debug, Default)]
472pub struct CacheStats {
473 account_hits: AtomicUsize,
475 account_misses: AtomicUsize,
477 storage_hits: AtomicUsize,
479 storage_misses: AtomicUsize,
481 code_hits: AtomicUsize,
483 code_misses: AtomicUsize,
485}
486
487impl CacheStats {
488 pub fn record_account_hit(&self) {
490 self.account_hits.fetch_add(1, Ordering::Relaxed);
491 }
492
493 pub fn record_account_miss(&self) {
495 self.account_misses.fetch_add(1, Ordering::Relaxed);
496 }
497
498 pub fn account_hits(&self) -> usize {
500 self.account_hits.load(Ordering::Relaxed)
501 }
502
503 pub fn account_misses(&self) -> usize {
505 self.account_misses.load(Ordering::Relaxed)
506 }
507
508 pub fn record_storage_hit(&self) {
510 self.storage_hits.fetch_add(1, Ordering::Relaxed);
511 }
512
513 pub fn record_storage_miss(&self) {
515 self.storage_misses.fetch_add(1, Ordering::Relaxed);
516 }
517
518 pub fn storage_hits(&self) -> usize {
520 self.storage_hits.load(Ordering::Relaxed)
521 }
522
523 pub fn storage_misses(&self) -> usize {
525 self.storage_misses.load(Ordering::Relaxed)
526 }
527
528 pub fn record_code_hit(&self) {
530 self.code_hits.fetch_add(1, Ordering::Relaxed);
531 }
532
533 pub fn record_code_miss(&self) {
535 self.code_misses.fetch_add(1, Ordering::Relaxed);
536 }
537
538 pub fn code_hits(&self) -> usize {
540 self.code_hits.load(Ordering::Relaxed)
541 }
542
543 pub fn code_misses(&self) -> usize {
545 self.code_misses.load(Ordering::Relaxed)
546 }
547}
548
549#[derive(Debug)]
564pub struct CacheStatsHandler {
565 collisions: AtomicU64,
566 size: AtomicUsize,
567 capacity: usize,
568}
569
570impl CacheStatsHandler {
571 pub const fn new(capacity: usize) -> Self {
573 Self { collisions: AtomicU64::new(0), size: AtomicUsize::new(0), capacity }
574 }
575
576 pub fn collisions(&self) -> u64 {
578 self.collisions.load(Ordering::Relaxed)
579 }
580
581 pub fn size(&self) -> usize {
583 self.size.load(Ordering::Relaxed)
584 }
585
586 pub const fn capacity(&self) -> usize {
588 self.capacity
589 }
590
591 pub fn increment_size(&self) {
593 let _ = self.size.fetch_add(1, Ordering::Relaxed);
594 }
595
596 pub fn decrement_size(&self) {
598 let _ = self.size.fetch_sub(1, Ordering::Relaxed);
599 }
600
601 pub fn reset_size(&self) {
603 self.size.store(0, Ordering::Relaxed);
604 }
605
606 pub fn reset_stats(&self) {
608 self.collisions.store(0, Ordering::Relaxed);
609 }
610}
611
612impl<K: PartialEq, V> StatsHandler<K, V> for CacheStatsHandler {
613 fn on_hit(&self, _key: &K, _value: &V) {}
614
615 fn on_miss(&self, _key: AnyRef<'_>) {}
616
617 fn on_insert(&self, key: &K, _value: &V, evicted: Option<(&K, &V)>) {
618 match evicted {
619 None => {
620 self.increment_size();
622 }
623 Some((evicted_key, _)) if evicted_key != key => {
624 self.collisions.fetch_add(1, Ordering::Relaxed);
626 }
627 Some(_) => {
628 }
630 }
631 }
632
633 fn on_remove(&self, _key: &K, _value: &V) {
634 self.decrement_size();
635 }
636}
637
638impl<S: AccountReader> AccountReader for CachedStateProvider<S> {
639 fn basic_account(&self, address: &Address) -> ProviderResult<Option<Account>> {
640 if self.should_fill_on_miss() {
641 match self.caches.get_or_try_insert_account_with(*address, || {
642 self.state_provider.basic_account(address)
643 })? {
644 CachedStatus::NotCached(value) => {
645 self.record_account_miss();
646 Ok(value)
647 }
648 CachedStatus::Cached(value) => {
649 self.record_account_hit();
650 Ok(value)
651 }
652 }
653 } else if let Some(account) = self.caches.0.account_cache.get(address) {
654 self.record_account_hit();
655 Ok(account)
656 } else {
657 self.record_account_miss();
658 self.state_provider.basic_account(address)
659 }
660 }
661}
662
663#[inline]
664fn nonzero_storage_value(value: StorageValue) -> Option<StorageValue> {
665 if value.is_zero() {
666 None
667 } else {
668 Some(value)
669 }
670}
671
672impl<S: StateProvider> StateProvider for CachedStateProvider<S> {
673 fn storage(
674 &self,
675 account: Address,
676 storage_key: StorageKey,
677 ) -> ProviderResult<Option<StorageValue>> {
678 if self.should_fill_on_miss() {
679 match self.caches.get_or_try_insert_storage_with(account, storage_key, || {
680 self.state_provider.storage(account, storage_key).map(Option::unwrap_or_default)
681 })? {
682 CachedStatus::NotCached(value) => {
683 self.record_storage_miss();
684 Ok(nonzero_storage_value(value))
685 }
686 CachedStatus::Cached(value) => {
687 self.record_storage_hit();
688 Ok(nonzero_storage_value(value))
689 }
690 }
691 } else if let Some(value) = self.caches.0.storage_cache.get(&(account, storage_key)) {
692 self.record_storage_hit();
693 Ok(nonzero_storage_value(value))
694 } else {
695 self.record_storage_miss();
696 self.state_provider.storage(account, storage_key)
697 }
698 }
699}
700
701impl<S: BytecodeReader> BytecodeReader for CachedStateProvider<S> {
702 fn bytecode_by_hash(&self, code_hash: &B256) -> ProviderResult<Option<Bytecode>> {
703 if self.should_fill_on_miss() {
704 match self.caches.get_or_try_insert_code_with(*code_hash, || {
705 self.state_provider.bytecode_by_hash(code_hash)
706 })? {
707 CachedStatus::NotCached(code) => {
708 self.record_code_miss();
709 Ok(code)
710 }
711 CachedStatus::Cached(code) => {
712 self.record_code_hit();
713 Ok(code)
714 }
715 }
716 } else if let Some(code) = self.caches.0.code_cache.get(code_hash) {
717 self.record_code_hit();
718 Ok(code)
719 } else {
720 self.record_code_miss();
721 self.state_provider.bytecode_by_hash(code_hash)
722 }
723 }
724}
725
726impl<S: StateRootProvider> StateRootProvider for CachedStateProvider<S> {
727 fn state_root(&self, hashed_state: HashedPostState) -> ProviderResult<B256> {
728 self.state_provider.state_root(hashed_state)
729 }
730
731 fn state_root_from_nodes(&self, input: TrieInput) -> ProviderResult<B256> {
732 self.state_provider.state_root_from_nodes(input)
733 }
734
735 fn state_root_with_updates(
736 &self,
737 hashed_state: HashedPostState,
738 ) -> ProviderResult<(B256, TrieUpdates)> {
739 self.state_provider.state_root_with_updates(hashed_state)
740 }
741
742 fn state_root_from_nodes_with_updates(
743 &self,
744 input: TrieInput,
745 ) -> ProviderResult<(B256, TrieUpdates)> {
746 self.state_provider.state_root_from_nodes_with_updates(input)
747 }
748}
749
750impl<S: StateProofProvider> StateProofProvider for CachedStateProvider<S> {
751 fn proof(
752 &self,
753 input: TrieInput,
754 address: Address,
755 slots: &[B256],
756 ) -> ProviderResult<AccountProof> {
757 self.state_provider.proof(input, address, slots)
758 }
759
760 fn multiproof(
761 &self,
762 input: TrieInput,
763 targets: MultiProofTargets,
764 ) -> ProviderResult<MultiProof> {
765 self.state_provider.multiproof(input, targets)
766 }
767
768 fn witness(
769 &self,
770 input: TrieInput,
771 target: HashedPostState,
772 mode: reth_trie::ExecutionWitnessMode,
773 ) -> ProviderResult<Vec<alloy_primitives::Bytes>> {
774 self.state_provider.witness(input, target, mode)
775 }
776}
777
778impl<S: StorageRootProvider> StorageRootProvider for CachedStateProvider<S> {
779 fn storage_root(
780 &self,
781 address: Address,
782 hashed_storage: HashedStorage,
783 ) -> ProviderResult<B256> {
784 self.state_provider.storage_root(address, hashed_storage)
785 }
786
787 fn storage_proof(
788 &self,
789 address: Address,
790 slot: B256,
791 hashed_storage: HashedStorage,
792 ) -> ProviderResult<StorageProof> {
793 self.state_provider.storage_proof(address, slot, hashed_storage)
794 }
795
796 fn storage_multiproof(
797 &self,
798 address: Address,
799 slots: &[B256],
800 hashed_storage: HashedStorage,
801 ) -> ProviderResult<StorageMultiProof> {
802 self.state_provider.storage_multiproof(address, slots, hashed_storage)
803 }
804}
805
806impl<S: BlockHashReader> BlockHashReader for CachedStateProvider<S> {
807 fn block_hash(&self, number: alloy_primitives::BlockNumber) -> ProviderResult<Option<B256>> {
808 self.state_provider.block_hash(number)
809 }
810
811 fn canonical_hashes_range(
812 &self,
813 start: alloy_primitives::BlockNumber,
814 end: alloy_primitives::BlockNumber,
815 ) -> ProviderResult<Vec<B256>> {
816 self.state_provider.canonical_hashes_range(start, end)
817 }
818}
819
820impl<S: HashedPostStateProvider> HashedPostStateProvider for CachedStateProvider<S> {
821 fn hashed_post_state(&self, bundle_state: &reth_revm::db::BundleState) -> HashedPostState {
822 self.state_provider.hashed_post_state(bundle_state)
823 }
824}
825
826#[derive(Debug, Clone)]
837pub struct ExecutionCache(Arc<ExecutionCacheInner>);
838
839#[derive(Debug)]
841struct ExecutionCacheInner {
842 code_cache: FixedCache<B256, Option<Bytecode>, FbBuildHasher<32>>,
844
845 storage_cache: FixedCache<(Address, StorageKey), StorageValue>,
847
848 account_cache: FixedCache<Address, Option<Account>, FbBuildHasher<20>>,
850
851 code_stats: Arc<CacheStatsHandler>,
853
854 storage_stats: Arc<CacheStatsHandler>,
856
857 account_stats: Arc<CacheStatsHandler>,
859
860 selfdestruct_encountered: Once,
862}
863
864impl ExecutionCache {
865 const MIN_CACHE_SIZE_WITH_EPOCHS: usize = 1 << 12; pub const fn bytes_to_entries(size_bytes: usize, entry_size: usize) -> usize {
874 let entries = size_bytes / entry_size;
875 let rounded = if entries == 0 { 1 } else { (entries + 1).next_power_of_two() >> 1 };
877 if rounded < Self::MIN_CACHE_SIZE_WITH_EPOCHS {
879 Self::MIN_CACHE_SIZE_WITH_EPOCHS
880 } else {
881 rounded
882 }
883 }
884
885 pub fn new(total_cache_size: usize) -> Self {
887 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);
892 let storage_capacity = Self::bytes_to_entries(storage_cache_size, STORAGE_CACHE_ENTRY_SIZE);
893 let account_capacity = Self::bytes_to_entries(account_cache_size, ACCOUNT_CACHE_ENTRY_SIZE);
894
895 let code_stats = Arc::new(CacheStatsHandler::new(code_capacity));
896 let storage_stats = Arc::new(CacheStatsHandler::new(storage_capacity));
897 let account_stats = Arc::new(CacheStatsHandler::new(account_capacity));
898
899 Self(Arc::new(ExecutionCacheInner {
900 code_cache: FixedCache::new(code_capacity, FbBuildHasher::<32>::default())
901 .with_stats(Some(Stats::new(code_stats.clone()))),
902 storage_cache: FixedCache::new(storage_capacity, DefaultHashBuilder::default())
903 .with_stats(Some(Stats::new(storage_stats.clone()))),
904 account_cache: FixedCache::new(account_capacity, FbBuildHasher::<20>::default())
905 .with_stats(Some(Stats::new(account_stats.clone()))),
906 code_stats,
907 storage_stats,
908 account_stats,
909 selfdestruct_encountered: Once::new(),
910 }))
911 }
912
913 fn usage_count(&self) -> usize {
915 Arc::strong_count(&self.0)
916 }
917
918 pub fn get_or_try_insert_code_with<E>(
920 &self,
921 hash: B256,
922 f: impl FnOnce() -> Result<Option<Bytecode>, E>,
923 ) -> Result<CachedStatus<Option<Bytecode>>, E> {
924 let mut miss = false;
925 let result = self.0.code_cache.get_or_try_insert_with(hash, |_| {
926 miss = true;
927 f()
928 })?;
929
930 if miss {
931 Ok(CachedStatus::NotCached(result))
932 } else {
933 Ok(CachedStatus::Cached(result))
934 }
935 }
936
937 pub fn get_or_try_insert_storage_with<E>(
939 &self,
940 address: Address,
941 key: StorageKey,
942 f: impl FnOnce() -> Result<StorageValue, E>,
943 ) -> Result<CachedStatus<StorageValue>, E> {
944 let mut miss = false;
945 let result = self.0.storage_cache.get_or_try_insert_with((address, key), |_| {
946 miss = true;
947 f()
948 })?;
949
950 if miss {
951 Ok(CachedStatus::NotCached(result))
952 } else {
953 Ok(CachedStatus::Cached(result))
954 }
955 }
956
957 pub fn get_or_try_insert_account_with<E>(
959 &self,
960 address: Address,
961 f: impl FnOnce() -> Result<Option<Account>, E>,
962 ) -> Result<CachedStatus<Option<Account>>, E> {
963 let mut miss = false;
964 let result = self.0.account_cache.get_or_try_insert_with(address, |_| {
965 miss = true;
966 f()
967 })?;
968
969 if miss {
970 Ok(CachedStatus::NotCached(result))
971 } else {
972 Ok(CachedStatus::Cached(result))
973 }
974 }
975
976 pub fn insert_storage(&self, address: Address, key: StorageKey, value: Option<StorageValue>) {
978 self.0.storage_cache.insert((address, key), value.unwrap_or_default());
979 }
980
981 pub fn insert_code(&self, hash: B256, code: Option<Bytecode>) {
983 self.0.code_cache.insert(hash, code);
984 }
985
986 pub fn insert_account(&self, address: Address, account: Option<Account>) {
988 self.0.account_cache.insert(address, account);
989 }
990
991 #[instrument(level = "debug", target = "engine::caching", skip_all)]
1010 #[expect(clippy::result_unit_err)]
1011 pub fn insert_state(&self, state_updates: &BundleState) -> Result<(), ()> {
1012 let _enter =
1013 debug_span!(target: "engine::tree", "contracts", len = state_updates.contracts.len())
1014 .entered();
1015 for (code_hash, bytecode) in &state_updates.contracts {
1017 self.insert_code(*code_hash, Some(Bytecode(bytecode.clone())));
1018 }
1019 drop(_enter);
1020
1021 let _enter = debug_span!(
1022 target: "engine::tree",
1023 "accounts",
1024 accounts = state_updates.state.len(),
1025 storages =
1026 state_updates.state.values().map(|account| account.storage.len()).sum::<usize>()
1027 )
1028 .entered();
1029 for (addr, account) in &state_updates.state {
1030 if account.status.is_not_modified() {
1033 continue
1034 }
1035
1036 if account.was_destroyed() {
1043 let had_code =
1044 account.original_info.as_ref().is_some_and(|info| !info.is_empty_code_hash());
1045 if had_code {
1046 self.0.selfdestruct_encountered.call_once(|| {
1047 warn!(
1048 target: "engine::caching",
1049 address = ?addr,
1050 info = ?account.info,
1051 original_info = ?account.original_info,
1052 "Encountered an inter-transaction SELFDESTRUCT that reset the storage cache. Are you running a pre-Dencun network?"
1053 );
1054 });
1055 self.clear();
1056 return Ok(())
1057 }
1058
1059 self.0.account_cache.remove(addr);
1060 continue;
1061 }
1062
1063 let Some(ref account_info) = account.info else {
1067 trace!(target: "engine::caching", ?account, "Account with None account info found in state updates");
1068 return Err(())
1069 };
1070
1071 for (key, slot) in &account.storage {
1073 self.insert_storage(*addr, (*key).into(), Some(slot.present_value));
1074 }
1075
1076 self.insert_account(*addr, Some(Account::from(account_info)));
1079 }
1080
1081 Ok(())
1082 }
1083
1084 pub fn clear(&self) {
1089 self.0.storage_cache.clear();
1090 self.0.account_cache.clear();
1091
1092 self.0.storage_stats.reset_size();
1093 self.0.account_stats.reset_size();
1094 }
1095
1096 pub fn update_metrics(&self, metrics: &CachedStateCacheMetrics) {
1099 metrics.code_cache_size.set(self.0.code_stats.size() as f64);
1100 metrics.code_cache_capacity.set(self.0.code_stats.capacity() as f64);
1101 metrics.code_cache_collisions.set(self.0.code_stats.collisions() as f64);
1102 self.0.code_stats.reset_stats();
1103
1104 metrics.storage_cache_size.set(self.0.storage_stats.size() as f64);
1105 metrics.storage_cache_capacity.set(self.0.storage_stats.capacity() as f64);
1106 metrics.storage_cache_collisions.set(self.0.storage_stats.collisions() as f64);
1107 self.0.storage_stats.reset_stats();
1108
1109 metrics.account_cache_size.set(self.0.account_stats.size() as f64);
1110 metrics.account_cache_capacity.set(self.0.account_stats.capacity() as f64);
1111 metrics.account_cache_collisions.set(self.0.account_stats.collisions() as f64);
1112 self.0.account_stats.reset_stats();
1113 }
1114}
1115
1116#[derive(Debug, Clone)]
1119pub struct SavedCache {
1120 hash: B256,
1122
1123 caches: ExecutionCache,
1125}
1126
1127impl SavedCache {
1128 pub const fn new(hash: B256, caches: ExecutionCache) -> Self {
1130 Self { hash, caches }
1131 }
1132
1133 pub const fn executed_block_hash(&self) -> B256 {
1135 self.hash
1136 }
1137
1138 pub fn is_available(&self) -> bool {
1140 self.caches.usage_count() == 1
1141 }
1142
1143 pub fn usage_count(&self) -> usize {
1145 self.caches.usage_count()
1146 }
1147
1148 pub const fn cache(&self) -> &ExecutionCache {
1150 &self.caches
1151 }
1152
1153 pub fn update_metrics(&self, metrics: Option<&CachedStateCacheMetrics>) {
1155 if let Some(metrics) = metrics {
1156 self.caches.update_metrics(metrics);
1157 }
1158 }
1159
1160 pub fn clear_with_hash(&mut self, hash: B256) {
1163 self.hash = hash;
1164 self.caches.clear();
1165 }
1166}
1167
1168#[cfg(any(test, feature = "test-utils"))]
1169impl SavedCache {
1170 pub fn clone_guard_for_test(&self) -> ExecutionCache {
1172 self.caches.clone()
1173 }
1174}
1175
1176#[cfg(test)]
1177mod tests {
1178 use super::*;
1179 use alloy_primitives::{map::HashMap, U256};
1180 use reth_provider::test_utils::{ExtendedAccount, MockEthProvider};
1181 use reth_revm::db::{AccountStatus, BundleAccount};
1182 use revm_state::AccountInfo;
1183
1184 #[test]
1185 fn test_empty_storage_cached_state_provider() {
1186 let address = Address::random();
1187 let storage_key = StorageKey::random();
1188 let account = ExtendedAccount::new(0, U256::ZERO);
1189
1190 let provider = MockEthProvider::default();
1191 provider.extend_accounts(vec![(address, account)]);
1192
1193 let caches = ExecutionCache::new(1000);
1194 let state_provider = CachedStateProvider::new(
1195 provider,
1196 caches,
1197 Some(CachedStateMetrics::zeroed(CachedStateMetricsSource::Test)),
1198 );
1199
1200 let res = state_provider.storage(address, storage_key);
1201 assert!(res.is_ok());
1202 assert_eq!(res.unwrap(), None);
1203 }
1204
1205 #[test]
1206 fn test_uncached_storage_cached_state_provider() {
1207 let address = Address::random();
1208 let storage_key = StorageKey::random();
1209 let storage_value = U256::from(1);
1210 let account =
1211 ExtendedAccount::new(0, U256::ZERO).extend_storage(vec![(storage_key, storage_value)]);
1212
1213 let provider = MockEthProvider::default();
1214 provider.extend_accounts(vec![(address, account)]);
1215
1216 let caches = ExecutionCache::new(1000);
1217 let state_provider = CachedStateProvider::new(
1218 provider,
1219 caches,
1220 Some(CachedStateMetrics::zeroed(CachedStateMetricsSource::Test)),
1221 );
1222
1223 let res = state_provider.storage(address, storage_key);
1224 assert!(res.is_ok());
1225 assert_eq!(res.unwrap(), Some(storage_value));
1226 }
1227
1228 #[test]
1229 fn test_get_storage_populated() {
1230 let address = Address::random();
1231 let storage_key = StorageKey::random();
1232 let storage_value = U256::from(1);
1233
1234 let caches = ExecutionCache::new(1000);
1235 caches.insert_storage(address, storage_key, Some(storage_value));
1236
1237 let result = caches
1238 .get_or_try_insert_storage_with(address, storage_key, || Ok::<_, ()>(U256::from(999)));
1239 assert_eq!(result.unwrap(), CachedStatus::Cached(storage_value));
1240 }
1241
1242 #[test]
1243 fn test_get_storage_empty() {
1244 let address = Address::random();
1245 let storage_key = StorageKey::random();
1246
1247 let caches = ExecutionCache::new(1000);
1248 caches.insert_storage(address, storage_key, None);
1249
1250 let result = caches
1251 .get_or_try_insert_storage_with(address, storage_key, || Ok::<_, ()>(U256::from(999)));
1252 assert_eq!(result.unwrap(), CachedStatus::Cached(U256::ZERO));
1253 }
1254
1255 #[test]
1256 fn test_saved_cache_is_available() {
1257 let execution_cache = ExecutionCache::new(1000);
1258 let cache = SavedCache::new(B256::ZERO, execution_cache);
1259
1260 assert!(cache.is_available(), "Cache should be available initially");
1261
1262 let _cache = cache.clone_guard_for_test();
1263
1264 assert!(!cache.is_available(), "Cache should not be available with active handle");
1265 }
1266
1267 #[test]
1268 fn test_saved_cache_multiple_references() {
1269 let execution_cache = ExecutionCache::new(1000);
1270 let cache = SavedCache::new(B256::from([2u8; 32]), execution_cache);
1271
1272 let cache1 = cache.clone_guard_for_test();
1273 let cache2 = cache.clone_guard_for_test();
1274 let cache3 = cache1.clone();
1275
1276 assert!(!cache.is_available());
1277
1278 drop(cache1);
1279 assert!(!cache.is_available());
1280
1281 drop(cache2);
1282 assert!(!cache.is_available());
1283
1284 drop(cache3);
1285 assert!(cache.is_available());
1286 }
1287
1288 #[test]
1289 fn test_insert_state_destroyed_account_with_code_clears_cache() {
1290 let caches = ExecutionCache::new(1000);
1291
1292 let addr1 = Address::random();
1294 let addr2 = Address::random();
1295 let storage_key = StorageKey::random();
1296 caches.insert_account(addr1, Some(Account::default()));
1297 caches.insert_account(addr2, Some(Account::default()));
1298 caches.insert_storage(addr1, storage_key, Some(U256::from(42)));
1299
1300 assert!(caches.0.account_cache.get(&addr1).is_some());
1302 assert!(caches.0.account_cache.get(&addr2).is_some());
1303 assert!(caches.0.storage_cache.get(&(addr1, storage_key)).is_some());
1304
1305 let bundle = BundleState {
1306 state: HashMap::from_iter([(
1308 Address::random(),
1309 BundleAccount::new(
1310 Some(AccountInfo {
1311 balance: U256::ZERO,
1312 nonce: 1,
1313 code_hash: B256::random(), code: None,
1315 account_id: None,
1316 }),
1317 None, Default::default(),
1319 AccountStatus::Destroyed,
1320 ),
1321 )]),
1322 contracts: Default::default(),
1323 reverts: Default::default(),
1324 state_size: 0,
1325 reverts_size: 0,
1326 };
1327
1328 let result = caches.insert_state(&bundle);
1330 assert!(result.is_ok());
1331
1332 assert!(caches.0.account_cache.get(&addr1).is_none());
1334 assert!(caches.0.account_cache.get(&addr2).is_none());
1335 assert!(caches.0.storage_cache.get(&(addr1, storage_key)).is_none());
1336 }
1337
1338 #[test]
1339 fn test_insert_state_destroyed_account_without_code_removes_only_account() {
1340 let caches = ExecutionCache::new(1000);
1341
1342 let addr1 = Address::random();
1344 let addr2 = Address::random();
1345 let storage_key = StorageKey::random();
1346 caches.insert_account(addr1, Some(Account::default()));
1347 caches.insert_account(addr2, Some(Account::default()));
1348 caches.insert_storage(addr1, storage_key, Some(U256::from(42)));
1349
1350 let bundle = BundleState {
1351 state: HashMap::from_iter([(
1353 addr1,
1354 BundleAccount::new(
1355 Some(AccountInfo {
1356 balance: U256::from(100),
1357 nonce: 1,
1358 code_hash: alloy_primitives::KECCAK256_EMPTY, code: None,
1360 account_id: None,
1361 }),
1362 None, Default::default(),
1364 AccountStatus::Destroyed,
1365 ),
1366 )]),
1367 contracts: Default::default(),
1368 reverts: Default::default(),
1369 state_size: 0,
1370 reverts_size: 0,
1371 };
1372
1373 assert!(caches.insert_state(&bundle).is_ok());
1375
1376 assert!(caches.0.account_cache.get(&addr1).is_none());
1378 assert!(caches.0.account_cache.get(&addr2).is_some());
1379 assert!(caches.0.storage_cache.get(&(addr1, storage_key)).is_some());
1380 }
1381
1382 #[test]
1383 fn test_insert_state_destroyed_account_no_original_info_removes_only_account() {
1384 let caches = ExecutionCache::new(1000);
1385
1386 let addr1 = Address::random();
1388 let addr2 = Address::random();
1389 caches.insert_account(addr1, Some(Account::default()));
1390 caches.insert_account(addr2, Some(Account::default()));
1391
1392 let bundle = BundleState {
1393 state: HashMap::from_iter([(
1395 addr1,
1396 BundleAccount::new(
1397 None, None, Default::default(),
1400 AccountStatus::Destroyed,
1401 ),
1402 )]),
1403 contracts: Default::default(),
1404 reverts: Default::default(),
1405 state_size: 0,
1406 reverts_size: 0,
1407 };
1408
1409 assert!(caches.insert_state(&bundle).is_ok());
1411
1412 assert!(caches.0.account_cache.get(&addr1).is_none());
1414 assert!(caches.0.account_cache.get(&addr2).is_some());
1415 }
1416
1417 #[test]
1418 fn test_insert_state_destroyed_uncached_account_keeps_size_zero() {
1419 let caches = ExecutionCache::new(1000);
1420 assert_eq!(caches.0.account_stats.size(), 0);
1421
1422 let addr = Address::random();
1423 let bundle = BundleState {
1424 state: HashMap::from_iter([(
1425 addr,
1426 BundleAccount::new(
1427 None, None, Default::default(),
1430 AccountStatus::Destroyed,
1431 ),
1432 )]),
1433 contracts: Default::default(),
1434 reverts: Default::default(),
1435 state_size: 0,
1436 reverts_size: 0,
1437 };
1438
1439 assert!(caches.insert_state(&bundle).is_ok());
1440 assert_eq!(caches.0.account_stats.size(), 0);
1441 assert!(caches.0.account_cache.get(&addr).is_none());
1442 }
1443
1444 #[test]
1445 fn test_code_cache_capacity_with_default_budget() {
1446 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);
1451
1452 assert_eq!(
1455 capacity, 16384,
1456 "code cache should have 16384 entries with default 4 GB budget"
1457 );
1458 }
1459}