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_storage_api::EvmStateProvider;
14use reth_trie_common::bundle_state::BundleState;
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() {
1166 let had_code =
1167 account.original_info.as_ref().is_some_and(|info| !info.is_empty_code_hash());
1168 if had_code {
1169 self.0.selfdestruct_encountered.call_once(|| {
1170 warn!(
1171 target: "engine::caching",
1172 address = ?addr,
1173 info = ?account.info,
1174 original_info = ?account.original_info,
1175 "Encountered an inter-transaction SELFDESTRUCT that reset the storage cache. Are you running a pre-Dencun network?"
1176 );
1177 });
1178 self.clear();
1179 return Ok(())
1180 }
1181
1182 if account.info.is_none() {
1183 self.0.account_cache.remove(addr);
1184 continue;
1185 }
1186 }
1187
1188 let Some(ref account_info) = account.info else {
1192 trace!(target: "engine::caching", ?account, "Account with None account info found in state updates");
1193 return Err(())
1194 };
1195
1196 for (key, slot) in &account.storage {
1198 self.insert_storage(*addr, (*key).into(), Some(slot.present_value));
1199 }
1200
1201 self.insert_account(*addr, Some(Account::from(account_info)));
1204 }
1205
1206 Ok(())
1207 }
1208
1209 pub fn clear(&self) {
1214 self.0.storage_cache.clear();
1215 self.0.account_cache.clear();
1216
1217 self.0.storage_stats.reset_size();
1218 self.0.account_stats.reset_size();
1219 }
1220
1221 pub fn update_metrics(&self, metrics: &CachedStateCacheMetrics) {
1224 metrics.code_cache_size.set(self.0.code_stats.size() as f64);
1225 metrics.code_cache_capacity.set(self.0.code_stats.capacity() as f64);
1226 metrics.code_cache_collisions.set(self.0.code_stats.collisions() as f64);
1227 self.0.code_stats.reset_stats();
1228
1229 metrics.storage_cache_size.set(self.0.storage_stats.size() as f64);
1230 metrics.storage_cache_capacity.set(self.0.storage_stats.capacity() as f64);
1231 metrics.storage_cache_collisions.set(self.0.storage_stats.collisions() as f64);
1232 self.0.storage_stats.reset_stats();
1233
1234 metrics.account_cache_size.set(self.0.account_stats.size() as f64);
1235 metrics.account_cache_capacity.set(self.0.account_stats.capacity() as f64);
1236 metrics.account_cache_collisions.set(self.0.account_stats.collisions() as f64);
1237 self.0.account_stats.reset_stats();
1238 }
1239}
1240
1241#[derive(Debug, Clone)]
1244pub struct SavedCache {
1245 hash: B256,
1247
1248 caches: ExecutionCache,
1250}
1251
1252impl SavedCache {
1253 pub const fn new(hash: B256, caches: ExecutionCache) -> Self {
1255 Self { hash, caches }
1256 }
1257
1258 pub const fn executed_block_hash(&self) -> B256 {
1260 self.hash
1261 }
1262
1263 pub fn is_available(&self) -> bool {
1265 self.caches.usage_count() == 1
1266 }
1267
1268 pub fn usage_count(&self) -> usize {
1270 self.caches.usage_count()
1271 }
1272
1273 pub const fn cache(&self) -> &ExecutionCache {
1275 &self.caches
1276 }
1277
1278 pub fn into_cache(self) -> ExecutionCache {
1280 self.caches
1281 }
1282
1283 pub fn shares_cache_with(&self, other: &Self) -> bool {
1286 Arc::ptr_eq(&self.caches.0, &other.caches.0)
1287 }
1288
1289 pub fn update_metrics(&self, metrics: Option<&CachedStateCacheMetrics>) {
1291 if let Some(metrics) = metrics {
1292 self.caches.update_metrics(metrics);
1293 }
1294 }
1295
1296 pub fn clear_with_hash(&mut self, hash: B256) {
1299 self.hash = hash;
1300 self.caches.clear();
1301 }
1302}
1303
1304#[cfg(any(test, feature = "test-utils"))]
1305impl SavedCache {
1306 pub fn clone_guard_for_test(&self) -> ExecutionCache {
1308 self.caches.clone()
1309 }
1310}
1311
1312#[cfg(test)]
1313mod tests {
1314 use super::*;
1315 use alloy_primitives::{map::HashMap, U256};
1316 use evm2::evm::AccountInfo;
1317 use reth_provider::test_utils::{ExtendedAccount, MockEthProvider};
1318 use reth_storage_api::StateProvider;
1319 use reth_trie_common::bundle_state::{AccountStatus, BundleAccount, StorageSlot};
1320
1321 #[test]
1322 fn test_empty_storage_cached_state_provider() {
1323 let address = Address::random();
1324 let storage_key = StorageKey::random();
1325 let account = ExtendedAccount::new(0, U256::ZERO);
1326
1327 let provider = MockEthProvider::default();
1328 provider.extend_accounts(vec![(address, account)]);
1329
1330 let caches = ExecutionCache::new(1000);
1331 let state_provider = CachedStateProvider::new(
1332 provider.into_evm_state_provider(),
1333 caches,
1334 Some(CachedStateMetrics::zeroed(CachedStateMetricsSource::Test)),
1335 );
1336
1337 let res = state_provider.storage(address, storage_key);
1338 assert!(res.is_ok());
1339 assert_eq!(res.unwrap(), None);
1340 }
1341
1342 #[test]
1343 fn test_uncached_storage_cached_state_provider() {
1344 let address = Address::random();
1345 let storage_key = StorageKey::random();
1346 let storage_value = U256::ONE;
1347 let account =
1348 ExtendedAccount::new(0, U256::ZERO).extend_storage(vec![(storage_key, storage_value)]);
1349
1350 let provider = MockEthProvider::default();
1351 provider.extend_accounts(vec![(address, account)]);
1352
1353 let caches = ExecutionCache::new(1000);
1354 let state_provider = CachedStateProvider::new(
1355 provider.into_evm_state_provider(),
1356 caches,
1357 Some(CachedStateMetrics::zeroed(CachedStateMetricsSource::Test)),
1358 );
1359
1360 let res = state_provider.storage(address, storage_key);
1361 assert!(res.is_ok());
1362 assert_eq!(res.unwrap(), Some(storage_value));
1363 }
1364
1365 #[test]
1366 fn test_get_storage_populated() {
1367 let address = Address::random();
1368 let storage_key = StorageKey::random();
1369 let storage_value = U256::ONE;
1370
1371 let caches = ExecutionCache::new(1000);
1372 caches.insert_storage(address, storage_key, Some(storage_value));
1373
1374 let result = caches
1375 .get_or_try_insert_storage_with(address, storage_key, || Ok::<_, ()>(U256::from(999)));
1376 assert_eq!(result.unwrap(), CachedStatus::Cached(storage_value));
1377 }
1378
1379 #[test]
1380 fn test_get_storage_empty() {
1381 let address = Address::random();
1382 let storage_key = StorageKey::random();
1383
1384 let caches = ExecutionCache::new(1000);
1385 caches.insert_storage(address, storage_key, None);
1386
1387 let result = caches
1388 .get_or_try_insert_storage_with(address, storage_key, || Ok::<_, ()>(U256::from(999)));
1389 assert_eq!(result.unwrap(), CachedStatus::Cached(U256::ZERO));
1390 }
1391
1392 #[test]
1393 fn test_saved_cache_is_available() {
1394 let execution_cache = ExecutionCache::new(1000);
1395 let cache = SavedCache::new(B256::ZERO, execution_cache);
1396
1397 assert!(cache.is_available(), "Cache should be available initially");
1398
1399 let _cache = cache.clone_guard_for_test();
1400
1401 assert!(!cache.is_available(), "Cache should not be available with active handle");
1402 }
1403
1404 #[test]
1405 fn test_saved_cache_multiple_references() {
1406 let execution_cache = ExecutionCache::new(1000);
1407 let cache = SavedCache::new(B256::repeat_byte(2u8), execution_cache);
1408
1409 let cache1 = cache.clone_guard_for_test();
1410 let cache2 = cache.clone_guard_for_test();
1411 let cache3 = cache1.clone();
1412
1413 assert!(!cache.is_available());
1414
1415 drop(cache1);
1416 assert!(!cache.is_available());
1417
1418 drop(cache2);
1419 assert!(!cache.is_available());
1420
1421 drop(cache3);
1422 assert!(cache.is_available());
1423 }
1424
1425 #[test]
1426 fn test_insert_state_destroyed_account_with_code_clears_cache() {
1427 let caches = ExecutionCache::new(1000);
1428
1429 let addr1 = Address::random();
1431 let addr2 = Address::random();
1432 let storage_key = StorageKey::random();
1433 caches.insert_account(addr1, Some(Account::default()));
1434 caches.insert_account(addr2, Some(Account::default()));
1435 caches.insert_storage(addr1, storage_key, Some(U256::from(42)));
1436
1437 assert!(caches.0.account_cache.get(&addr1).is_some());
1439 assert!(caches.0.account_cache.get(&addr2).is_some());
1440 assert!(caches.0.storage_cache.get(&(addr1, storage_key)).is_some());
1441
1442 let bundle = BundleState {
1443 state: HashMap::from_iter([(
1445 Address::random(),
1446 BundleAccount::new(
1447 Some(AccountInfo {
1448 nonce: 1,
1449 code_hash: B256::random(), code: None,
1451 ..Default::default()
1452 }),
1453 None, Default::default(),
1455 AccountStatus::Destroyed,
1456 ),
1457 )]),
1458 contracts: Default::default(),
1459 reverts: Default::default(),
1460 state_size: 0,
1461 reverts_size: 0,
1462 };
1463
1464 let result = caches.insert_state(&bundle);
1466 assert!(result.is_ok());
1467
1468 assert!(caches.0.account_cache.get(&addr1).is_none());
1470 assert!(caches.0.account_cache.get(&addr2).is_none());
1471 assert!(caches.0.storage_cache.get(&(addr1, storage_key)).is_none());
1472 }
1473
1474 #[test]
1475 fn test_insert_state_destroyed_account_without_code_removes_only_account() {
1476 let caches = ExecutionCache::new(1000);
1477
1478 let addr1 = Address::random();
1480 let addr2 = Address::random();
1481 let storage_key = StorageKey::random();
1482 caches.insert_account(addr1, Some(Account::default()));
1483 caches.insert_account(addr2, Some(Account::default()));
1484 caches.insert_storage(addr1, storage_key, Some(U256::from(42)));
1485
1486 let bundle = BundleState {
1487 state: HashMap::from_iter([(
1489 addr1,
1490 BundleAccount::new(
1491 Some(AccountInfo {
1492 balance: U256::from(100),
1493 nonce: 1,
1494 code_hash: alloy_primitives::KECCAK256_EMPTY, code: None,
1496 ..Default::default()
1497 }),
1498 None, Default::default(),
1500 AccountStatus::Destroyed,
1501 ),
1502 )]),
1503 contracts: Default::default(),
1504 reverts: Default::default(),
1505 state_size: 0,
1506 reverts_size: 0,
1507 };
1508
1509 assert!(caches.insert_state(&bundle).is_ok());
1511
1512 assert!(caches.0.account_cache.get(&addr1).is_none());
1514 assert!(caches.0.account_cache.get(&addr2).is_some());
1515 assert!(caches.0.storage_cache.get(&(addr1, storage_key)).is_some());
1516 }
1517
1518 #[test]
1519 fn test_insert_state_recreated_account_updates_cached_storage() {
1520 let caches = ExecutionCache::new(1000);
1521 let addr = Address::random();
1522 let other_addr = Address::random();
1523 let key = StorageKey::from(U256::from(1));
1524 let other_key = StorageKey::from(U256::from(2));
1525 let provider = MockEthProvider::default();
1526 provider.extend_accounts(vec![(addr, ExtendedAccount::new(0, U256::from(100)))]);
1527 let state_provider =
1528 CachedStateProvider::new_prewarm(provider.into_evm_state_provider(), caches.clone());
1529 assert_eq!(state_provider.storage(addr, key).unwrap(), None);
1530 assert_eq!(caches.0.storage_cache.get(&(addr, key)), Some(U256::ZERO));
1531 drop(state_provider);
1532 caches.insert_storage(other_addr, other_key, Some(U256::from(42)));
1533
1534 let current_info =
1535 AccountInfo { nonce: 1, code_hash: B256::random(), ..Default::default() };
1536 let bundle = BundleState {
1537 state: HashMap::from_iter([(
1538 addr,
1539 BundleAccount::new(
1540 Some(AccountInfo {
1541 balance: U256::from(100),
1542 code_hash: alloy_primitives::KECCAK256_EMPTY,
1543 ..Default::default()
1544 }),
1545 Some(current_info.clone()),
1546 HashMap::from_iter([(
1547 U256::from(1),
1548 StorageSlot::new_changed(U256::ZERO, U256::from(7)),
1549 )]),
1550 AccountStatus::DestroyedChanged,
1551 ),
1552 )]),
1553 ..Default::default()
1554 };
1555
1556 assert!(caches.insert_state(&bundle).is_ok());
1557 assert_eq!(caches.0.storage_cache.get(&(addr, key)), Some(U256::from(7)));
1558 let cached_account = caches.0.account_cache.get(&addr).flatten().unwrap();
1559 assert_eq!(cached_account.bytecode_hash, Some(current_info.code_hash));
1560 assert_eq!(caches.0.storage_cache.get(&(other_addr, other_key)), Some(U256::from(42)));
1561 }
1562
1563 #[test]
1564 fn test_insert_state_destroyed_account_no_original_info_removes_only_account() {
1565 let caches = ExecutionCache::new(1000);
1566
1567 let addr1 = Address::random();
1569 let addr2 = Address::random();
1570 caches.insert_account(addr1, Some(Account::default()));
1571 caches.insert_account(addr2, Some(Account::default()));
1572
1573 let bundle = BundleState {
1574 state: HashMap::from_iter([(
1576 addr1,
1577 BundleAccount::new(
1578 None, None, Default::default(),
1581 AccountStatus::Destroyed,
1582 ),
1583 )]),
1584 contracts: Default::default(),
1585 reverts: Default::default(),
1586 state_size: 0,
1587 reverts_size: 0,
1588 };
1589
1590 assert!(caches.insert_state(&bundle).is_ok());
1592
1593 assert!(caches.0.account_cache.get(&addr1).is_none());
1595 assert!(caches.0.account_cache.get(&addr2).is_some());
1596 }
1597
1598 #[test]
1599 fn test_insert_state_destroyed_uncached_account_keeps_size_zero() {
1600 let caches = ExecutionCache::new(1000);
1601 assert_eq!(caches.0.account_stats.size(), 0);
1602
1603 let addr = Address::random();
1604 let bundle = BundleState {
1605 state: HashMap::from_iter([(
1606 addr,
1607 BundleAccount::new(
1608 None, None, Default::default(),
1611 AccountStatus::Destroyed,
1612 ),
1613 )]),
1614 contracts: Default::default(),
1615 reverts: Default::default(),
1616 state_size: 0,
1617 reverts_size: 0,
1618 };
1619
1620 assert!(caches.insert_state(&bundle).is_ok());
1621 assert_eq!(caches.0.account_stats.size(), 0);
1622 assert!(caches.0.account_cache.get(&addr).is_none());
1623 }
1624
1625 #[test]
1626 fn test_code_cache_capacity_with_default_budget() {
1627 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);
1632
1633 assert_eq!(
1636 capacity, 16384,
1637 "code cache should have 16384 entries with default 4 GB budget"
1638 );
1639 }
1640}