1use crate::TxPoolPrewarmCacheSnapshot;
3use alloy_primitives::{
4 map::{DefaultHashBuilder, FbBuildHasher},
5 Address, StorageKey, StorageValue, B256,
6};
7use fixed_cache::{AnyRef, CacheConfig, Stats, StatsHandler};
8use metrics::{Counter, Gauge, Histogram};
9use parking_lot::Once;
10use reth_errors::ProviderResult;
11use reth_metrics::Metrics;
12use reth_primitives_traits::{Account, Bytecode};
13use reth_revm::db::BundleState;
14use reth_storage_api::EvmStateProvider;
15use std::{
16 cell::Cell,
17 fmt,
18 sync::{
19 atomic::{AtomicU64, AtomicUsize, Ordering},
20 Arc,
21 },
22 time::Duration,
23};
24use tracing::{debug_span, instrument, trace, warn};
25
26const FIXED_CACHE_ALIGNMENT: usize = 128;
31
32const FIXED_CACHE_ENTRY_OVERHEAD: usize = size_of::<usize>();
34
35const fn fixed_cache_entry_size<K, V>() -> usize {
40 fixed_cache_key_size_with_value::<K>(size_of::<V>())
41}
42
43const fn fixed_cache_key_size_with_value<K>(value: usize) -> usize {
48 let raw_size = FIXED_CACHE_ENTRY_OVERHEAD + size_of::<K>() + value;
49 raw_size.div_ceil(FIXED_CACHE_ALIGNMENT) * FIXED_CACHE_ALIGNMENT
51}
52
53const ESTIMATED_AVG_CODE_SIZE: usize = 8 * 1024;
60
61const CODE_CACHE_ENTRY_SIZE: usize =
63 fixed_cache_key_size_with_value::<Address>(ESTIMATED_AVG_CODE_SIZE);
64
65const STORAGE_CACHE_ENTRY_SIZE: usize =
67 fixed_cache_entry_size::<(Address, StorageKey), StorageValue>();
68
69const ACCOUNT_CACHE_ENTRY_SIZE: usize = fixed_cache_entry_size::<Address, Option<Account>>();
71
72struct EpochCacheConfig;
74impl CacheConfig for EpochCacheConfig {
75 const EPOCHS: bool = true;
76}
77
78type FixedCache<K, V, H = DefaultHashBuilder> = fixed_cache::Cache<K, V, H, EpochCacheConfig>;
80
81#[derive(Debug)]
93pub struct CachedStateProvider<S> {
94 state_provider: S,
96
97 caches: ExecutionCache,
99
100 txpool_snapshot: Option<TxPoolPrewarmCacheSnapshot>,
102
103 metrics: Option<CachedStateMetrics>,
105
106 execution_metric_counts: CacheMetricCounts,
108
109 txpool_metric_counts: CacheMetricCounts,
111
112 fill_mode: CacheFillMode,
114
115 cache_stats: Option<Arc<CacheStats>>,
118}
119
120impl<S> CachedStateProvider<S> {
121 pub const fn new(
124 state_provider: S,
125 caches: ExecutionCache,
126 metrics: Option<CachedStateMetrics>,
127 ) -> Self {
128 Self::new_with_mode(state_provider, caches, CacheFillMode::LookupOnly, metrics, None)
129 }
130
131 pub const fn new_prewarm(state_provider: S, caches: ExecutionCache) -> Self {
135 Self::new_with_mode(state_provider, caches, CacheFillMode::FillOnMiss, None, None)
136 }
137
138 pub const fn new_with_mode(
141 state_provider: S,
142 caches: ExecutionCache,
143 fill_mode: CacheFillMode,
144 metrics: Option<CachedStateMetrics>,
145 cache_stats: Option<Arc<CacheStats>>,
146 ) -> Self {
147 Self {
148 state_provider,
149 caches,
150 txpool_snapshot: None,
151 metrics,
152 execution_metric_counts: CacheMetricCounts::new(),
153 txpool_metric_counts: CacheMetricCounts::new(),
154 fill_mode,
155 cache_stats,
156 }
157 }
158
159 pub fn with_txpool_snapshot(mut self, snapshot: Option<TxPoolPrewarmCacheSnapshot>) -> Self {
161 self.txpool_snapshot = snapshot;
162 self
163 }
164
165 fn record_account_hit(&self) {
166 self.record_metric(CacheMetricKind::AccountHit);
167 if let Some(stats) = &self.cache_stats {
168 stats.record_account_hit();
169 }
170 }
171
172 fn record_account_miss(&self) {
173 self.record_metric(CacheMetricKind::AccountMiss);
174 if let Some(stats) = &self.cache_stats {
175 stats.record_account_miss();
176 }
177 }
178
179 fn record_storage_hit(&self) {
180 self.record_metric(CacheMetricKind::StorageHit);
181 if let Some(stats) = &self.cache_stats {
182 stats.record_storage_hit();
183 }
184 }
185
186 fn record_storage_miss(&self) {
187 self.record_metric(CacheMetricKind::StorageMiss);
188 if let Some(stats) = &self.cache_stats {
189 stats.record_storage_miss();
190 }
191 }
192
193 fn record_code_hit(&self) {
194 self.record_metric(CacheMetricKind::CodeHit);
195 if let Some(stats) = &self.cache_stats {
196 stats.record_code_hit();
197 }
198 }
199
200 fn record_code_miss(&self) {
201 self.record_metric(CacheMetricKind::CodeMiss);
202 if let Some(stats) = &self.cache_stats {
203 stats.record_code_miss();
204 }
205 }
206
207 fn record_txpool_account_hit(&self) {
208 self.record_txpool_metric(CacheMetricKind::AccountHit);
209 if let Some(stats) = &self.cache_stats {
210 stats.record_txpool_snapshot_account_hit();
211 }
212 }
213
214 fn record_txpool_account_miss(&self) {
215 self.record_txpool_metric(CacheMetricKind::AccountMiss);
216 if let Some(stats) = &self.cache_stats {
217 stats.record_txpool_snapshot_account_miss();
218 }
219 }
220
221 fn record_txpool_storage_hit(&self) {
222 self.record_txpool_metric(CacheMetricKind::StorageHit);
223 if let Some(stats) = &self.cache_stats {
224 stats.record_txpool_snapshot_storage_hit();
225 }
226 }
227
228 fn record_txpool_storage_miss(&self) {
229 self.record_txpool_metric(CacheMetricKind::StorageMiss);
230 if let Some(stats) = &self.cache_stats {
231 stats.record_txpool_snapshot_storage_miss();
232 }
233 }
234
235 fn record_txpool_code_hit(&self) {
236 self.record_txpool_metric(CacheMetricKind::CodeHit);
237 if let Some(stats) = &self.cache_stats {
238 stats.record_txpool_snapshot_code_hit();
239 }
240 }
241
242 fn record_txpool_code_miss(&self) {
243 self.record_txpool_metric(CacheMetricKind::CodeMiss);
244 if let Some(stats) = &self.cache_stats {
245 stats.record_txpool_snapshot_code_miss();
246 }
247 }
248
249 #[inline]
250 fn record_metric(&self, kind: CacheMetricKind) {
251 if self.metrics.is_some() {
252 self.execution_metric_counts.record(kind);
253 }
254 }
255
256 #[inline]
257 fn record_txpool_metric(&self, kind: CacheMetricKind) {
258 if self.metrics.is_some() {
259 self.txpool_metric_counts.record(kind);
260 }
261 }
262
263 fn flush_buffered_metrics(&self) {
264 let execution_counts = self.execution_metric_counts.take();
265 let txpool_counts = self.txpool_metric_counts.take();
266 if execution_counts.is_empty() && txpool_counts.is_empty() {
267 return;
268 }
269
270 if let Some(metrics) = &self.metrics {
271 metrics.record_access_counts(execution_counts);
272 metrics.record_txpool_access_counts(txpool_counts);
273 }
274 }
275
276 const fn should_fill_on_miss(&self) -> bool {
277 matches!(self.fill_mode, CacheFillMode::FillOnMiss)
278 }
279}
280
281impl<S> Drop for CachedStateProvider<S> {
282 fn drop(&mut self) {
283 self.flush_buffered_metrics();
284 }
285}
286
287#[derive(Debug, Clone, Copy, PartialEq, Eq)]
289pub enum CacheFillMode {
290 LookupOnly,
292 FillOnMiss,
294}
295
296#[derive(Debug, Clone, Copy, PartialEq, Eq)]
297enum CacheMetricKind {
298 AccountHit,
299 AccountMiss,
300 StorageHit,
301 StorageMiss,
302 CodeHit,
303 CodeMiss,
304}
305
306#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
307struct CacheMetricSnapshot {
308 account_hits: u64,
309 account_misses: u64,
310 storage_hits: u64,
311 storage_misses: u64,
312 code_hits: u64,
313 code_misses: u64,
314}
315
316impl CacheMetricSnapshot {
317 const fn is_empty(&self) -> bool {
318 self.account_hits == 0 &&
319 self.account_misses == 0 &&
320 self.storage_hits == 0 &&
321 self.storage_misses == 0 &&
322 self.code_hits == 0 &&
323 self.code_misses == 0
324 }
325}
326
327#[derive(Debug, Default)]
328struct CacheMetricCounts {
329 account_hits: Cell<u64>,
330 account_misses: Cell<u64>,
331 storage_hits: Cell<u64>,
332 storage_misses: Cell<u64>,
333 code_hits: Cell<u64>,
334 code_misses: Cell<u64>,
335}
336
337impl CacheMetricCounts {
338 const fn new() -> Self {
339 Self {
340 account_hits: Cell::new(0),
341 account_misses: Cell::new(0),
342 storage_hits: Cell::new(0),
343 storage_misses: Cell::new(0),
344 code_hits: Cell::new(0),
345 code_misses: Cell::new(0),
346 }
347 }
348
349 #[inline]
350 fn record(&self, kind: CacheMetricKind) {
351 let counter = match kind {
352 CacheMetricKind::AccountHit => &self.account_hits,
353 CacheMetricKind::AccountMiss => &self.account_misses,
354 CacheMetricKind::StorageHit => &self.storage_hits,
355 CacheMetricKind::StorageMiss => &self.storage_misses,
356 CacheMetricKind::CodeHit => &self.code_hits,
357 CacheMetricKind::CodeMiss => &self.code_misses,
358 };
359 counter.set(counter.get() + 1);
360 }
361
362 const fn take(&self) -> CacheMetricSnapshot {
363 CacheMetricSnapshot {
364 account_hits: self.account_hits.replace(0),
365 account_misses: self.account_misses.replace(0),
366 storage_hits: self.storage_hits.replace(0),
367 storage_misses: self.storage_misses.replace(0),
368 code_hits: self.code_hits.replace(0),
369 code_misses: self.code_misses.replace(0),
370 }
371 }
372}
373
374#[derive(Debug, Clone, PartialEq, Eq)]
376pub enum CachedStatus<T> {
377 NotCached(T),
379 Cached(T),
381}
382
383#[derive(Debug, Clone, Copy, PartialEq, Eq)]
385pub enum CachedStateMetricsSource {
386 Engine,
388 Builder,
390 #[cfg(any(test, feature = "test-utils"))]
392 Test,
393}
394
395impl fmt::Display for CachedStateMetricsSource {
396 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
397 match self {
398 Self::Engine => f.write_str("engine"),
399 Self::Builder => f.write_str("builder"),
400 #[cfg(any(test, feature = "test-utils"))]
401 Self::Test => f.write_str("test"),
402 }
403 }
404}
405
406#[derive(Metrics, Clone)]
408#[metrics(scope = "sync.caching")]
409pub struct CachedStateMetrics {
410 execution_cache_created_total: Counter,
412
413 execution_cache_creation_duration_seconds: Histogram,
415
416 code_cache_hits: Gauge,
418
419 code_cache_misses: Gauge,
421
422 storage_cache_hits: Gauge,
424
425 storage_cache_misses: Gauge,
427
428 account_cache_hits: Gauge,
430
431 account_cache_misses: Gauge,
433
434 txpool_snapshot_code_hits: Gauge,
436
437 txpool_snapshot_code_misses: Gauge,
439
440 txpool_snapshot_storage_hits: Gauge,
442
443 txpool_snapshot_storage_misses: Gauge,
445
446 txpool_snapshot_account_hits: Gauge,
448
449 txpool_snapshot_account_misses: Gauge,
451}
452
453#[derive(Metrics, Clone)]
455#[metrics(scope = "sync.caching")]
456pub struct CachedStateCacheMetrics {
457 code_cache_size: Gauge,
459
460 code_cache_capacity: Gauge,
462
463 code_cache_collisions: Gauge,
465
466 storage_cache_size: Gauge,
468
469 storage_cache_capacity: Gauge,
471
472 storage_cache_collisions: Gauge,
474
475 account_cache_size: Gauge,
477
478 account_cache_capacity: Gauge,
480
481 account_cache_collisions: Gauge,
483}
484
485impl CachedStateMetrics {
486 pub fn reset(&self) {
488 self.code_cache_hits.set(0);
490 self.code_cache_misses.set(0);
491
492 self.storage_cache_hits.set(0);
494 self.storage_cache_misses.set(0);
495
496 self.account_cache_hits.set(0);
498 self.account_cache_misses.set(0);
499
500 self.txpool_snapshot_code_hits.set(0);
502 self.txpool_snapshot_code_misses.set(0);
503
504 self.txpool_snapshot_storage_hits.set(0);
506 self.txpool_snapshot_storage_misses.set(0);
507
508 self.txpool_snapshot_account_hits.set(0);
510 self.txpool_snapshot_account_misses.set(0);
511 }
512
513 pub fn zeroed(source: CachedStateMetricsSource) -> Self {
516 let zeroed = Self::new_with_labels(&[("source", source.to_string())]);
517 zeroed.reset();
518 zeroed
519 }
520
521 fn record_access(&self, kind: CacheMetricKind, count: u64) {
522 match kind {
523 CacheMetricKind::AccountHit => self.account_cache_hits.increment(count as f64),
524 CacheMetricKind::AccountMiss => self.account_cache_misses.increment(count as f64),
525 CacheMetricKind::StorageHit => self.storage_cache_hits.increment(count as f64),
526 CacheMetricKind::StorageMiss => self.storage_cache_misses.increment(count as f64),
527 CacheMetricKind::CodeHit => self.code_cache_hits.increment(count as f64),
528 CacheMetricKind::CodeMiss => self.code_cache_misses.increment(count as f64),
529 }
530 }
531
532 fn record_access_counts(&self, counts: CacheMetricSnapshot) {
533 if counts.account_hits != 0 {
534 self.record_access(CacheMetricKind::AccountHit, counts.account_hits);
535 }
536 if counts.account_misses != 0 {
537 self.record_access(CacheMetricKind::AccountMiss, counts.account_misses);
538 }
539 if counts.storage_hits != 0 {
540 self.record_access(CacheMetricKind::StorageHit, counts.storage_hits);
541 }
542 if counts.storage_misses != 0 {
543 self.record_access(CacheMetricKind::StorageMiss, counts.storage_misses);
544 }
545 if counts.code_hits != 0 {
546 self.record_access(CacheMetricKind::CodeHit, counts.code_hits);
547 }
548 if counts.code_misses != 0 {
549 self.record_access(CacheMetricKind::CodeMiss, counts.code_misses);
550 }
551 }
552
553 fn record_txpool_access(&self, kind: CacheMetricKind, count: u64) {
554 match kind {
555 CacheMetricKind::AccountHit => {
556 self.txpool_snapshot_account_hits.increment(count as f64)
557 }
558 CacheMetricKind::AccountMiss => {
559 self.txpool_snapshot_account_misses.increment(count as f64)
560 }
561 CacheMetricKind::StorageHit => {
562 self.txpool_snapshot_storage_hits.increment(count as f64)
563 }
564 CacheMetricKind::StorageMiss => {
565 self.txpool_snapshot_storage_misses.increment(count as f64)
566 }
567 CacheMetricKind::CodeHit => self.txpool_snapshot_code_hits.increment(count as f64),
568 CacheMetricKind::CodeMiss => self.txpool_snapshot_code_misses.increment(count as f64),
569 }
570 }
571
572 fn record_txpool_access_counts(&self, counts: CacheMetricSnapshot) {
573 if counts.account_hits != 0 {
574 self.record_txpool_access(CacheMetricKind::AccountHit, counts.account_hits);
575 }
576 if counts.account_misses != 0 {
577 self.record_txpool_access(CacheMetricKind::AccountMiss, counts.account_misses);
578 }
579 if counts.storage_hits != 0 {
580 self.record_txpool_access(CacheMetricKind::StorageHit, counts.storage_hits);
581 }
582 if counts.storage_misses != 0 {
583 self.record_txpool_access(CacheMetricKind::StorageMiss, counts.storage_misses);
584 }
585 if counts.code_hits != 0 {
586 self.record_txpool_access(CacheMetricKind::CodeHit, counts.code_hits);
587 }
588 if counts.code_misses != 0 {
589 self.record_txpool_access(CacheMetricKind::CodeMiss, counts.code_misses);
590 }
591 }
592
593 pub fn record_cache_creation(&self, duration: Duration) {
595 self.execution_cache_created_total.increment(1);
596 self.execution_cache_creation_duration_seconds.record(duration.as_secs_f64());
597 }
598}
599
600#[derive(Debug, Default)]
602pub struct CacheStats {
603 account_hits: AtomicUsize,
605 account_misses: AtomicUsize,
607 storage_hits: AtomicUsize,
609 storage_misses: AtomicUsize,
611 code_hits: AtomicUsize,
613 code_misses: AtomicUsize,
615 txpool_snapshot_account_hits: AtomicUsize,
617 txpool_snapshot_account_misses: AtomicUsize,
619 txpool_snapshot_storage_hits: AtomicUsize,
621 txpool_snapshot_storage_misses: AtomicUsize,
623 txpool_snapshot_code_hits: AtomicUsize,
625 txpool_snapshot_code_misses: AtomicUsize,
627}
628
629impl CacheStats {
630 pub fn record_account_hit(&self) {
632 self.account_hits.fetch_add(1, Ordering::Relaxed);
633 }
634
635 pub fn record_account_miss(&self) {
637 self.account_misses.fetch_add(1, Ordering::Relaxed);
638 }
639
640 pub fn account_hits(&self) -> usize {
642 self.account_hits.load(Ordering::Relaxed)
643 }
644
645 pub fn account_misses(&self) -> usize {
647 self.account_misses.load(Ordering::Relaxed)
648 }
649
650 pub fn record_storage_hit(&self) {
652 self.storage_hits.fetch_add(1, Ordering::Relaxed);
653 }
654
655 pub fn record_storage_miss(&self) {
657 self.storage_misses.fetch_add(1, Ordering::Relaxed);
658 }
659
660 pub fn storage_hits(&self) -> usize {
662 self.storage_hits.load(Ordering::Relaxed)
663 }
664
665 pub fn storage_misses(&self) -> usize {
667 self.storage_misses.load(Ordering::Relaxed)
668 }
669
670 pub fn record_code_hit(&self) {
672 self.code_hits.fetch_add(1, Ordering::Relaxed);
673 }
674
675 pub fn record_code_miss(&self) {
677 self.code_misses.fetch_add(1, Ordering::Relaxed);
678 }
679
680 pub fn code_hits(&self) -> usize {
682 self.code_hits.load(Ordering::Relaxed)
683 }
684
685 pub fn code_misses(&self) -> usize {
687 self.code_misses.load(Ordering::Relaxed)
688 }
689
690 pub fn record_txpool_snapshot_account_hit(&self) {
692 self.txpool_snapshot_account_hits.fetch_add(1, Ordering::Relaxed);
693 }
694
695 pub fn record_txpool_snapshot_account_miss(&self) {
697 self.txpool_snapshot_account_misses.fetch_add(1, Ordering::Relaxed);
698 }
699
700 pub fn txpool_snapshot_account_hits(&self) -> usize {
702 self.txpool_snapshot_account_hits.load(Ordering::Relaxed)
703 }
704
705 pub fn txpool_snapshot_account_misses(&self) -> usize {
707 self.txpool_snapshot_account_misses.load(Ordering::Relaxed)
708 }
709
710 pub fn record_txpool_snapshot_storage_hit(&self) {
712 self.txpool_snapshot_storage_hits.fetch_add(1, Ordering::Relaxed);
713 }
714
715 pub fn record_txpool_snapshot_storage_miss(&self) {
717 self.txpool_snapshot_storage_misses.fetch_add(1, Ordering::Relaxed);
718 }
719
720 pub fn txpool_snapshot_storage_hits(&self) -> usize {
722 self.txpool_snapshot_storage_hits.load(Ordering::Relaxed)
723 }
724
725 pub fn txpool_snapshot_storage_misses(&self) -> usize {
727 self.txpool_snapshot_storage_misses.load(Ordering::Relaxed)
728 }
729
730 pub fn record_txpool_snapshot_code_hit(&self) {
732 self.txpool_snapshot_code_hits.fetch_add(1, Ordering::Relaxed);
733 }
734
735 pub fn record_txpool_snapshot_code_miss(&self) {
737 self.txpool_snapshot_code_misses.fetch_add(1, Ordering::Relaxed);
738 }
739
740 pub fn txpool_snapshot_code_hits(&self) -> usize {
742 self.txpool_snapshot_code_hits.load(Ordering::Relaxed)
743 }
744
745 pub fn txpool_snapshot_code_misses(&self) -> usize {
747 self.txpool_snapshot_code_misses.load(Ordering::Relaxed)
748 }
749}
750
751#[derive(Debug)]
766pub struct CacheStatsHandler {
767 collisions: AtomicU64,
768 size: AtomicUsize,
769 capacity: usize,
770}
771
772impl CacheStatsHandler {
773 pub const fn new(capacity: usize) -> Self {
775 Self { collisions: AtomicU64::new(0), size: AtomicUsize::new(0), capacity }
776 }
777
778 pub fn collisions(&self) -> u64 {
780 self.collisions.load(Ordering::Relaxed)
781 }
782
783 pub fn size(&self) -> usize {
785 self.size.load(Ordering::Relaxed)
786 }
787
788 pub const fn capacity(&self) -> usize {
790 self.capacity
791 }
792
793 pub fn increment_size(&self) {
795 let _ = self.size.fetch_add(1, Ordering::Relaxed);
796 }
797
798 pub fn decrement_size(&self) {
800 let _ = self.size.fetch_sub(1, Ordering::Relaxed);
801 }
802
803 pub fn reset_size(&self) {
805 self.size.store(0, Ordering::Relaxed);
806 }
807
808 pub fn reset_stats(&self) {
810 self.collisions.store(0, Ordering::Relaxed);
811 }
812}
813
814impl<K: PartialEq, V> StatsHandler<K, V> for CacheStatsHandler {
815 fn on_hit(&self, _key: &K, _value: &V) {}
816
817 fn on_miss(&self, _key: AnyRef<'_>) {}
818
819 fn on_insert(&self, key: &K, _value: &V, evicted: Option<(&K, &V)>) {
820 match evicted {
821 None => {
822 self.increment_size();
824 }
825 Some((evicted_key, _)) if evicted_key != key => {
826 self.collisions.fetch_add(1, Ordering::Relaxed);
828 }
829 Some(_) => {
830 }
832 }
833 }
834
835 fn on_remove(&self, _key: &K, _value: &V) {
836 self.decrement_size();
837 }
838}
839
840#[inline]
841fn nonzero_storage_value(value: StorageValue) -> Option<StorageValue> {
842 (!value.is_zero()).then_some(value)
843}
844
845impl<S: EvmStateProvider> EvmStateProvider for CachedStateProvider<S> {
846 fn basic_account(&self, address: &Address) -> ProviderResult<Option<Account>> {
847 if let Some(snapshot) = &self.txpool_snapshot {
848 if let Some(account) = snapshot.account(address) {
849 self.record_txpool_account_hit();
850 return Ok(account)
851 }
852 self.record_txpool_account_miss();
853 }
854
855 if self.should_fill_on_miss() {
856 match self.caches.get_or_try_insert_account_with(*address, || {
857 self.state_provider.basic_account(address)
858 })? {
859 CachedStatus::NotCached(value) => {
860 self.record_account_miss();
861 Ok(value)
862 }
863 CachedStatus::Cached(value) => {
864 self.record_account_hit();
865 Ok(value)
866 }
867 }
868 } else if let Some(account) = self.caches.0.account_cache.get(address) {
869 self.record_account_hit();
870 Ok(account)
871 } else {
872 self.record_account_miss();
873 self.state_provider.basic_account(address)
874 }
875 }
876
877 fn storage(
878 &self,
879 account: Address,
880 storage_key: StorageKey,
881 ) -> ProviderResult<Option<StorageValue>> {
882 if let Some(snapshot) = &self.txpool_snapshot {
883 if let Some(value) = snapshot.storage(account, storage_key) {
884 self.record_txpool_storage_hit();
885 return Ok(nonzero_storage_value(value))
886 }
887 self.record_txpool_storage_miss();
888 }
889
890 if self.should_fill_on_miss() {
891 match self.caches.get_or_try_insert_storage_with(account, storage_key, || {
892 self.state_provider.storage(account, storage_key).map(Option::unwrap_or_default)
893 })? {
894 CachedStatus::NotCached(value) => {
895 self.record_storage_miss();
896 Ok(nonzero_storage_value(value))
897 }
898 CachedStatus::Cached(value) => {
899 self.record_storage_hit();
900 Ok(nonzero_storage_value(value))
901 }
902 }
903 } else if let Some(value) = self.caches.0.storage_cache.get(&(account, storage_key)) {
904 self.record_storage_hit();
905 Ok(nonzero_storage_value(value))
906 } else {
907 self.record_storage_miss();
908 self.state_provider.storage(account, storage_key)
909 }
910 }
911
912 fn bytecode_by_hash(&self, code_hash: &B256) -> ProviderResult<Option<Bytecode>> {
913 if let Some(snapshot) = &self.txpool_snapshot {
914 if let Some(code) = snapshot.bytecode(code_hash) {
915 self.record_txpool_code_hit();
916 return Ok(code)
917 }
918 self.record_txpool_code_miss();
919 }
920
921 if self.should_fill_on_miss() {
922 match self.caches.get_or_try_insert_code_with(*code_hash, || {
923 self.state_provider.bytecode_by_hash(code_hash)
924 })? {
925 CachedStatus::NotCached(code) => {
926 self.record_code_miss();
927 Ok(code)
928 }
929 CachedStatus::Cached(code) => {
930 self.record_code_hit();
931 Ok(code)
932 }
933 }
934 } else if let Some(code) = self.caches.0.code_cache.get(code_hash) {
935 self.record_code_hit();
936 Ok(code)
937 } else {
938 self.record_code_miss();
939 self.state_provider.bytecode_by_hash(code_hash)
940 }
941 }
942
943 fn block_hash(&self, number: alloy_primitives::BlockNumber) -> ProviderResult<Option<B256>> {
944 self.state_provider.block_hash(number)
945 }
946}
947
948#[derive(Debug, Clone)]
959pub struct ExecutionCache(Arc<ExecutionCacheInner>);
960
961#[derive(Debug)]
963struct ExecutionCacheInner {
964 code_cache: FixedCache<B256, Option<Bytecode>, FbBuildHasher<32>>,
966
967 storage_cache: FixedCache<(Address, StorageKey), StorageValue>,
969
970 account_cache: FixedCache<Address, Option<Account>, FbBuildHasher<20>>,
972
973 code_stats: Arc<CacheStatsHandler>,
975
976 storage_stats: Arc<CacheStatsHandler>,
978
979 account_stats: Arc<CacheStatsHandler>,
981
982 selfdestruct_encountered: Once,
984}
985
986impl ExecutionCache {
987 const MIN_CACHE_SIZE_WITH_EPOCHS: usize = 1 << 12; pub const fn bytes_to_entries(size_bytes: usize, entry_size: usize) -> usize {
996 let entries = size_bytes / entry_size;
997 let rounded = if entries == 0 { 1 } else { (entries + 1).next_power_of_two() >> 1 };
999 if rounded < Self::MIN_CACHE_SIZE_WITH_EPOCHS {
1001 Self::MIN_CACHE_SIZE_WITH_EPOCHS
1002 } else {
1003 rounded
1004 }
1005 }
1006
1007 pub fn new(total_cache_size: usize) -> Self {
1009 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);
1014 let storage_capacity = Self::bytes_to_entries(storage_cache_size, STORAGE_CACHE_ENTRY_SIZE);
1015 let account_capacity = Self::bytes_to_entries(account_cache_size, ACCOUNT_CACHE_ENTRY_SIZE);
1016
1017 let code_stats = Arc::new(CacheStatsHandler::new(code_capacity));
1018 let storage_stats = Arc::new(CacheStatsHandler::new(storage_capacity));
1019 let account_stats = Arc::new(CacheStatsHandler::new(account_capacity));
1020
1021 Self(Arc::new(ExecutionCacheInner {
1022 code_cache: FixedCache::new(code_capacity, FbBuildHasher::<32>::default())
1023 .with_stats(Some(Stats::new(code_stats.clone()))),
1024 storage_cache: FixedCache::new(storage_capacity, DefaultHashBuilder::default())
1025 .with_stats(Some(Stats::new(storage_stats.clone()))),
1026 account_cache: FixedCache::new(account_capacity, FbBuildHasher::<20>::default())
1027 .with_stats(Some(Stats::new(account_stats.clone()))),
1028 code_stats,
1029 storage_stats,
1030 account_stats,
1031 selfdestruct_encountered: Once::new(),
1032 }))
1033 }
1034
1035 fn usage_count(&self) -> usize {
1037 Arc::strong_count(&self.0)
1038 }
1039
1040 pub fn get_or_try_insert_code_with<E>(
1042 &self,
1043 hash: B256,
1044 f: impl FnOnce() -> Result<Option<Bytecode>, E>,
1045 ) -> Result<CachedStatus<Option<Bytecode>>, E> {
1046 let mut miss = false;
1047 let result = self.0.code_cache.get_or_try_insert_with(hash, |_| {
1048 miss = true;
1049 f()
1050 })?;
1051
1052 if miss {
1053 Ok(CachedStatus::NotCached(result))
1054 } else {
1055 Ok(CachedStatus::Cached(result))
1056 }
1057 }
1058
1059 pub fn get_or_try_insert_storage_with<E>(
1061 &self,
1062 address: Address,
1063 key: StorageKey,
1064 f: impl FnOnce() -> Result<StorageValue, E>,
1065 ) -> Result<CachedStatus<StorageValue>, E> {
1066 let mut miss = false;
1067 let result = self.0.storage_cache.get_or_try_insert_with((address, key), |_| {
1068 miss = true;
1069 f()
1070 })?;
1071
1072 if miss {
1073 Ok(CachedStatus::NotCached(result))
1074 } else {
1075 Ok(CachedStatus::Cached(result))
1076 }
1077 }
1078
1079 pub fn get_or_try_insert_account_with<E>(
1081 &self,
1082 address: Address,
1083 f: impl FnOnce() -> Result<Option<Account>, E>,
1084 ) -> Result<CachedStatus<Option<Account>>, E> {
1085 let mut miss = false;
1086 let result = self.0.account_cache.get_or_try_insert_with(address, |_| {
1087 miss = true;
1088 f()
1089 })?;
1090
1091 if miss {
1092 Ok(CachedStatus::NotCached(result))
1093 } else {
1094 Ok(CachedStatus::Cached(result))
1095 }
1096 }
1097
1098 pub fn insert_storage(&self, address: Address, key: StorageKey, value: Option<StorageValue>) {
1100 self.0.storage_cache.insert((address, key), value.unwrap_or_default());
1101 }
1102
1103 pub fn insert_code(&self, hash: B256, code: Option<Bytecode>) {
1105 self.0.code_cache.insert(hash, code);
1106 }
1107
1108 pub fn insert_account(&self, address: Address, account: Option<Account>) {
1110 self.0.account_cache.insert(address, account);
1111 }
1112
1113 #[instrument(level = "debug", target = "engine::caching", skip_all)]
1132 #[expect(clippy::result_unit_err)]
1133 pub fn insert_state(&self, state_updates: &BundleState) -> Result<(), ()> {
1134 let _enter =
1135 debug_span!(target: "engine::tree", "contracts", len = state_updates.contracts.len())
1136 .entered();
1137 for (code_hash, bytecode) in &state_updates.contracts {
1139 self.insert_code(*code_hash, Some(Bytecode(bytecode.clone())));
1140 }
1141 drop(_enter);
1142
1143 let _enter = debug_span!(
1144 target: "engine::tree",
1145 "accounts",
1146 accounts = state_updates.state.len(),
1147 storages =
1148 state_updates.state.values().map(|account| account.storage.len()).sum::<usize>()
1149 )
1150 .entered();
1151 for (addr, account) in &state_updates.state {
1152 if account.status.is_not_modified() {
1155 continue
1156 }
1157
1158 if account.was_destroyed() {
1165 let had_code =
1166 account.original_info.as_ref().is_some_and(|info| !info.is_empty_code_hash());
1167 if had_code {
1168 self.0.selfdestruct_encountered.call_once(|| {
1169 warn!(
1170 target: "engine::caching",
1171 address = ?addr,
1172 info = ?account.info,
1173 original_info = ?account.original_info,
1174 "Encountered an inter-transaction SELFDESTRUCT that reset the storage cache. Are you running a pre-Dencun network?"
1175 );
1176 });
1177 self.clear();
1178 return Ok(())
1179 }
1180
1181 self.0.account_cache.remove(addr);
1182 continue;
1183 }
1184
1185 let Some(ref account_info) = account.info else {
1189 trace!(target: "engine::caching", ?account, "Account with None account info found in state updates");
1190 return Err(())
1191 };
1192
1193 for (key, slot) in &account.storage {
1195 self.insert_storage(*addr, (*key).into(), Some(slot.present_value));
1196 }
1197
1198 self.insert_account(*addr, Some(Account::from(account_info)));
1201 }
1202
1203 Ok(())
1204 }
1205
1206 pub fn clear(&self) {
1211 self.0.storage_cache.clear();
1212 self.0.account_cache.clear();
1213
1214 self.0.storage_stats.reset_size();
1215 self.0.account_stats.reset_size();
1216 }
1217
1218 pub fn update_metrics(&self, metrics: &CachedStateCacheMetrics) {
1221 metrics.code_cache_size.set(self.0.code_stats.size() as f64);
1222 metrics.code_cache_capacity.set(self.0.code_stats.capacity() as f64);
1223 metrics.code_cache_collisions.set(self.0.code_stats.collisions() as f64);
1224 self.0.code_stats.reset_stats();
1225
1226 metrics.storage_cache_size.set(self.0.storage_stats.size() as f64);
1227 metrics.storage_cache_capacity.set(self.0.storage_stats.capacity() as f64);
1228 metrics.storage_cache_collisions.set(self.0.storage_stats.collisions() as f64);
1229 self.0.storage_stats.reset_stats();
1230
1231 metrics.account_cache_size.set(self.0.account_stats.size() as f64);
1232 metrics.account_cache_capacity.set(self.0.account_stats.capacity() as f64);
1233 metrics.account_cache_collisions.set(self.0.account_stats.collisions() as f64);
1234 self.0.account_stats.reset_stats();
1235 }
1236}
1237
1238#[derive(Debug, Clone)]
1241pub struct SavedCache {
1242 hash: B256,
1244
1245 caches: ExecutionCache,
1247}
1248
1249impl SavedCache {
1250 pub const fn new(hash: B256, caches: ExecutionCache) -> Self {
1252 Self { hash, caches }
1253 }
1254
1255 pub const fn executed_block_hash(&self) -> B256 {
1257 self.hash
1258 }
1259
1260 pub fn is_available(&self) -> bool {
1262 self.caches.usage_count() == 1
1263 }
1264
1265 pub fn usage_count(&self) -> usize {
1267 self.caches.usage_count()
1268 }
1269
1270 pub const fn cache(&self) -> &ExecutionCache {
1272 &self.caches
1273 }
1274
1275 pub fn into_cache(self) -> ExecutionCache {
1277 self.caches
1278 }
1279
1280 pub fn shares_cache_with(&self, other: &Self) -> bool {
1283 Arc::ptr_eq(&self.caches.0, &other.caches.0)
1284 }
1285
1286 pub fn update_metrics(&self, metrics: Option<&CachedStateCacheMetrics>) {
1288 if let Some(metrics) = metrics {
1289 self.caches.update_metrics(metrics);
1290 }
1291 }
1292
1293 pub fn clear_with_hash(&mut self, hash: B256) {
1296 self.hash = hash;
1297 self.caches.clear();
1298 }
1299}
1300
1301#[cfg(any(test, feature = "test-utils"))]
1302impl SavedCache {
1303 pub fn clone_guard_for_test(&self) -> ExecutionCache {
1305 self.caches.clone()
1306 }
1307}
1308
1309#[cfg(test)]
1310mod tests {
1311 use super::*;
1312 use alloy_primitives::{map::HashMap, U256};
1313 use reth_provider::test_utils::{ExtendedAccount, MockEthProvider};
1314 use reth_revm::db::{AccountStatus, BundleAccount};
1315 use reth_storage_api::StateProvider;
1316 use revm::state::AccountInfo;
1317
1318 #[test]
1319 fn test_empty_storage_cached_state_provider() {
1320 let address = Address::random();
1321 let storage_key = StorageKey::random();
1322 let account = ExtendedAccount::new(0, U256::ZERO);
1323
1324 let provider = MockEthProvider::default();
1325 provider.extend_accounts(vec![(address, account)]);
1326
1327 let caches = ExecutionCache::new(1000);
1328 let state_provider = CachedStateProvider::new(
1329 provider.into_evm_state_provider(),
1330 caches,
1331 Some(CachedStateMetrics::zeroed(CachedStateMetricsSource::Test)),
1332 );
1333
1334 let res = state_provider.storage(address, storage_key);
1335 assert!(res.is_ok());
1336 assert_eq!(res.unwrap(), None);
1337 }
1338
1339 #[test]
1340 fn test_uncached_storage_cached_state_provider() {
1341 let address = Address::random();
1342 let storage_key = StorageKey::random();
1343 let storage_value = U256::from(1);
1344 let account =
1345 ExtendedAccount::new(0, U256::ZERO).extend_storage(vec![(storage_key, storage_value)]);
1346
1347 let provider = MockEthProvider::default();
1348 provider.extend_accounts(vec![(address, account)]);
1349
1350 let caches = ExecutionCache::new(1000);
1351 let state_provider = CachedStateProvider::new(
1352 provider.into_evm_state_provider(),
1353 caches,
1354 Some(CachedStateMetrics::zeroed(CachedStateMetricsSource::Test)),
1355 );
1356
1357 let res = state_provider.storage(address, storage_key);
1358 assert!(res.is_ok());
1359 assert_eq!(res.unwrap(), Some(storage_value));
1360 }
1361
1362 #[test]
1363 fn test_get_storage_populated() {
1364 let address = Address::random();
1365 let storage_key = StorageKey::random();
1366 let storage_value = U256::from(1);
1367
1368 let caches = ExecutionCache::new(1000);
1369 caches.insert_storage(address, storage_key, Some(storage_value));
1370
1371 let result = caches
1372 .get_or_try_insert_storage_with(address, storage_key, || Ok::<_, ()>(U256::from(999)));
1373 assert_eq!(result.unwrap(), CachedStatus::Cached(storage_value));
1374 }
1375
1376 #[test]
1377 fn test_get_storage_empty() {
1378 let address = Address::random();
1379 let storage_key = StorageKey::random();
1380
1381 let caches = ExecutionCache::new(1000);
1382 caches.insert_storage(address, storage_key, None);
1383
1384 let result = caches
1385 .get_or_try_insert_storage_with(address, storage_key, || Ok::<_, ()>(U256::from(999)));
1386 assert_eq!(result.unwrap(), CachedStatus::Cached(U256::ZERO));
1387 }
1388
1389 #[test]
1390 fn test_saved_cache_is_available() {
1391 let execution_cache = ExecutionCache::new(1000);
1392 let cache = SavedCache::new(B256::ZERO, execution_cache);
1393
1394 assert!(cache.is_available(), "Cache should be available initially");
1395
1396 let _cache = cache.clone_guard_for_test();
1397
1398 assert!(!cache.is_available(), "Cache should not be available with active handle");
1399 }
1400
1401 #[test]
1402 fn test_saved_cache_multiple_references() {
1403 let execution_cache = ExecutionCache::new(1000);
1404 let cache = SavedCache::new(B256::from([2u8; 32]), execution_cache);
1405
1406 let cache1 = cache.clone_guard_for_test();
1407 let cache2 = cache.clone_guard_for_test();
1408 let cache3 = cache1.clone();
1409
1410 assert!(!cache.is_available());
1411
1412 drop(cache1);
1413 assert!(!cache.is_available());
1414
1415 drop(cache2);
1416 assert!(!cache.is_available());
1417
1418 drop(cache3);
1419 assert!(cache.is_available());
1420 }
1421
1422 #[test]
1423 fn test_insert_state_destroyed_account_with_code_clears_cache() {
1424 let caches = ExecutionCache::new(1000);
1425
1426 let addr1 = Address::random();
1428 let addr2 = Address::random();
1429 let storage_key = StorageKey::random();
1430 caches.insert_account(addr1, Some(Account::default()));
1431 caches.insert_account(addr2, Some(Account::default()));
1432 caches.insert_storage(addr1, storage_key, Some(U256::from(42)));
1433
1434 assert!(caches.0.account_cache.get(&addr1).is_some());
1436 assert!(caches.0.account_cache.get(&addr2).is_some());
1437 assert!(caches.0.storage_cache.get(&(addr1, storage_key)).is_some());
1438
1439 let bundle = BundleState {
1440 state: HashMap::from_iter([(
1442 Address::random(),
1443 BundleAccount::new(
1444 Some(AccountInfo {
1445 nonce: 1,
1446 code_hash: B256::random(), code: None,
1448 ..Default::default()
1449 }),
1450 None, Default::default(),
1452 AccountStatus::Destroyed,
1453 ),
1454 )]),
1455 contracts: Default::default(),
1456 reverts: Default::default(),
1457 state_size: 0,
1458 reverts_size: 0,
1459 };
1460
1461 let result = caches.insert_state(&bundle);
1463 assert!(result.is_ok());
1464
1465 assert!(caches.0.account_cache.get(&addr1).is_none());
1467 assert!(caches.0.account_cache.get(&addr2).is_none());
1468 assert!(caches.0.storage_cache.get(&(addr1, storage_key)).is_none());
1469 }
1470
1471 #[test]
1472 fn test_insert_state_destroyed_account_without_code_removes_only_account() {
1473 let caches = ExecutionCache::new(1000);
1474
1475 let addr1 = Address::random();
1477 let addr2 = Address::random();
1478 let storage_key = StorageKey::random();
1479 caches.insert_account(addr1, Some(Account::default()));
1480 caches.insert_account(addr2, Some(Account::default()));
1481 caches.insert_storage(addr1, storage_key, Some(U256::from(42)));
1482
1483 let bundle = BundleState {
1484 state: HashMap::from_iter([(
1486 addr1,
1487 BundleAccount::new(
1488 Some(AccountInfo {
1489 balance: U256::from(100),
1490 nonce: 1,
1491 code_hash: alloy_primitives::KECCAK256_EMPTY, code: None,
1493 ..Default::default()
1494 }),
1495 None, Default::default(),
1497 AccountStatus::Destroyed,
1498 ),
1499 )]),
1500 contracts: Default::default(),
1501 reverts: Default::default(),
1502 state_size: 0,
1503 reverts_size: 0,
1504 };
1505
1506 assert!(caches.insert_state(&bundle).is_ok());
1508
1509 assert!(caches.0.account_cache.get(&addr1).is_none());
1511 assert!(caches.0.account_cache.get(&addr2).is_some());
1512 assert!(caches.0.storage_cache.get(&(addr1, storage_key)).is_some());
1513 }
1514
1515 #[test]
1516 fn test_insert_state_destroyed_account_no_original_info_removes_only_account() {
1517 let caches = ExecutionCache::new(1000);
1518
1519 let addr1 = Address::random();
1521 let addr2 = Address::random();
1522 caches.insert_account(addr1, Some(Account::default()));
1523 caches.insert_account(addr2, Some(Account::default()));
1524
1525 let bundle = BundleState {
1526 state: HashMap::from_iter([(
1528 addr1,
1529 BundleAccount::new(
1530 None, None, Default::default(),
1533 AccountStatus::Destroyed,
1534 ),
1535 )]),
1536 contracts: Default::default(),
1537 reverts: Default::default(),
1538 state_size: 0,
1539 reverts_size: 0,
1540 };
1541
1542 assert!(caches.insert_state(&bundle).is_ok());
1544
1545 assert!(caches.0.account_cache.get(&addr1).is_none());
1547 assert!(caches.0.account_cache.get(&addr2).is_some());
1548 }
1549
1550 #[test]
1551 fn test_insert_state_destroyed_uncached_account_keeps_size_zero() {
1552 let caches = ExecutionCache::new(1000);
1553 assert_eq!(caches.0.account_stats.size(), 0);
1554
1555 let addr = Address::random();
1556 let bundle = BundleState {
1557 state: HashMap::from_iter([(
1558 addr,
1559 BundleAccount::new(
1560 None, None, Default::default(),
1563 AccountStatus::Destroyed,
1564 ),
1565 )]),
1566 contracts: Default::default(),
1567 reverts: Default::default(),
1568 state_size: 0,
1569 reverts_size: 0,
1570 };
1571
1572 assert!(caches.insert_state(&bundle).is_ok());
1573 assert_eq!(caches.0.account_stats.size(), 0);
1574 assert!(caches.0.account_cache.get(&addr).is_none());
1575 }
1576
1577 #[test]
1578 fn test_code_cache_capacity_with_default_budget() {
1579 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);
1584
1585 assert_eq!(
1588 capacity, 16384,
1589 "code cache should have 16384 entries with default 4 GB budget"
1590 );
1591 }
1592}