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_provider::{
14 AccountReader, BlockHashReader, BytecodeReader, HashedPostStateProvider, StateProofProvider,
15 StateProvider, StateRootProvider, StorageRootProvider,
16};
17use reth_revm::db::BundleState;
18use reth_trie::{
19 updates::TrieUpdates, AccountProof, HashedPostState, HashedStorage, MultiProof,
20 MultiProofTargets, StorageMultiProof, StorageProof, TrieInput,
21};
22use std::{
23 cell::Cell,
24 fmt,
25 sync::{
26 atomic::{AtomicU64, AtomicUsize, Ordering},
27 Arc,
28 },
29 time::Duration,
30};
31use tracing::{debug_span, instrument, trace, warn};
32
33const FIXED_CACHE_ALIGNMENT: usize = 128;
38
39const FIXED_CACHE_ENTRY_OVERHEAD: usize = size_of::<usize>();
41
42const fn fixed_cache_entry_size<K, V>() -> usize {
47 fixed_cache_key_size_with_value::<K>(size_of::<V>())
48}
49
50const fn fixed_cache_key_size_with_value<K>(value: usize) -> usize {
55 let raw_size = FIXED_CACHE_ENTRY_OVERHEAD + size_of::<K>() + value;
56 raw_size.div_ceil(FIXED_CACHE_ALIGNMENT) * FIXED_CACHE_ALIGNMENT
58}
59
60const ESTIMATED_AVG_CODE_SIZE: usize = 8 * 1024;
67
68const CODE_CACHE_ENTRY_SIZE: usize =
70 fixed_cache_key_size_with_value::<Address>(ESTIMATED_AVG_CODE_SIZE);
71
72const STORAGE_CACHE_ENTRY_SIZE: usize =
74 fixed_cache_entry_size::<(Address, StorageKey), StorageValue>();
75
76const ACCOUNT_CACHE_ENTRY_SIZE: usize = fixed_cache_entry_size::<Address, Option<Account>>();
78
79struct EpochCacheConfig;
81impl CacheConfig for EpochCacheConfig {
82 const EPOCHS: bool = true;
83}
84
85type FixedCache<K, V, H = DefaultHashBuilder> = fixed_cache::Cache<K, V, H, EpochCacheConfig>;
87
88#[derive(Debug)]
100pub struct CachedStateProvider<S> {
101 state_provider: S,
103
104 caches: ExecutionCache,
106
107 txpool_snapshot: Option<TxPoolPrewarmCacheSnapshot>,
109
110 metrics: Option<CachedStateMetrics>,
112
113 execution_metric_counts: CacheMetricCounts,
115
116 txpool_metric_counts: CacheMetricCounts,
118
119 fill_mode: CacheFillMode,
121
122 cache_stats: Option<Arc<CacheStats>>,
125}
126
127impl<S> CachedStateProvider<S> {
128 pub const fn new(
131 state_provider: S,
132 caches: ExecutionCache,
133 metrics: Option<CachedStateMetrics>,
134 ) -> Self {
135 Self::new_with_mode(state_provider, caches, CacheFillMode::LookupOnly, metrics, None)
136 }
137
138 pub const fn new_prewarm(state_provider: S, caches: ExecutionCache) -> Self {
142 Self::new_with_mode(state_provider, caches, CacheFillMode::FillOnMiss, None, None)
143 }
144
145 pub const fn new_with_mode(
148 state_provider: S,
149 caches: ExecutionCache,
150 fill_mode: CacheFillMode,
151 metrics: Option<CachedStateMetrics>,
152 cache_stats: Option<Arc<CacheStats>>,
153 ) -> Self {
154 Self {
155 state_provider,
156 caches,
157 txpool_snapshot: None,
158 metrics,
159 execution_metric_counts: CacheMetricCounts::new(),
160 txpool_metric_counts: CacheMetricCounts::new(),
161 fill_mode,
162 cache_stats,
163 }
164 }
165
166 pub fn with_txpool_snapshot(mut self, snapshot: Option<TxPoolPrewarmCacheSnapshot>) -> Self {
168 self.txpool_snapshot = snapshot;
169 self
170 }
171
172 fn record_account_hit(&self) {
173 self.record_metric(CacheMetricKind::AccountHit);
174 if let Some(stats) = &self.cache_stats {
175 stats.record_account_hit();
176 }
177 }
178
179 fn record_account_miss(&self) {
180 self.record_metric(CacheMetricKind::AccountMiss);
181 if let Some(stats) = &self.cache_stats {
182 stats.record_account_miss();
183 }
184 }
185
186 fn record_storage_hit(&self) {
187 self.record_metric(CacheMetricKind::StorageHit);
188 if let Some(stats) = &self.cache_stats {
189 stats.record_storage_hit();
190 }
191 }
192
193 fn record_storage_miss(&self) {
194 self.record_metric(CacheMetricKind::StorageMiss);
195 if let Some(stats) = &self.cache_stats {
196 stats.record_storage_miss();
197 }
198 }
199
200 fn record_code_hit(&self) {
201 self.record_metric(CacheMetricKind::CodeHit);
202 if let Some(stats) = &self.cache_stats {
203 stats.record_code_hit();
204 }
205 }
206
207 fn record_code_miss(&self) {
208 self.record_metric(CacheMetricKind::CodeMiss);
209 if let Some(stats) = &self.cache_stats {
210 stats.record_code_miss();
211 }
212 }
213
214 fn record_txpool_account_hit(&self) {
215 self.record_txpool_metric(CacheMetricKind::AccountHit);
216 if let Some(stats) = &self.cache_stats {
217 stats.record_txpool_snapshot_account_hit();
218 }
219 }
220
221 fn record_txpool_account_miss(&self) {
222 self.record_txpool_metric(CacheMetricKind::AccountMiss);
223 if let Some(stats) = &self.cache_stats {
224 stats.record_txpool_snapshot_account_miss();
225 }
226 }
227
228 fn record_txpool_storage_hit(&self) {
229 self.record_txpool_metric(CacheMetricKind::StorageHit);
230 if let Some(stats) = &self.cache_stats {
231 stats.record_txpool_snapshot_storage_hit();
232 }
233 }
234
235 fn record_txpool_storage_miss(&self) {
236 self.record_txpool_metric(CacheMetricKind::StorageMiss);
237 if let Some(stats) = &self.cache_stats {
238 stats.record_txpool_snapshot_storage_miss();
239 }
240 }
241
242 fn record_txpool_code_hit(&self) {
243 self.record_txpool_metric(CacheMetricKind::CodeHit);
244 if let Some(stats) = &self.cache_stats {
245 stats.record_txpool_snapshot_code_hit();
246 }
247 }
248
249 fn record_txpool_code_miss(&self) {
250 self.record_txpool_metric(CacheMetricKind::CodeMiss);
251 if let Some(stats) = &self.cache_stats {
252 stats.record_txpool_snapshot_code_miss();
253 }
254 }
255
256 #[inline]
257 fn record_metric(&self, kind: CacheMetricKind) {
258 if self.metrics.is_some() {
259 self.execution_metric_counts.record(kind);
260 }
261 }
262
263 #[inline]
264 fn record_txpool_metric(&self, kind: CacheMetricKind) {
265 if self.metrics.is_some() {
266 self.txpool_metric_counts.record(kind);
267 }
268 }
269
270 fn flush_buffered_metrics(&self) {
271 let execution_counts = self.execution_metric_counts.take();
272 let txpool_counts = self.txpool_metric_counts.take();
273 if execution_counts.is_empty() && txpool_counts.is_empty() {
274 return;
275 }
276
277 if let Some(metrics) = &self.metrics {
278 metrics.record_access_counts(execution_counts);
279 metrics.record_txpool_access_counts(txpool_counts);
280 }
281 }
282
283 const fn should_fill_on_miss(&self) -> bool {
284 matches!(self.fill_mode, CacheFillMode::FillOnMiss)
285 }
286}
287
288impl<S> Drop for CachedStateProvider<S> {
289 fn drop(&mut self) {
290 self.flush_buffered_metrics();
291 }
292}
293
294#[derive(Debug, Clone, Copy, PartialEq, Eq)]
296pub enum CacheFillMode {
297 LookupOnly,
299 FillOnMiss,
301}
302
303#[derive(Debug, Clone, Copy, PartialEq, Eq)]
304enum CacheMetricKind {
305 AccountHit,
306 AccountMiss,
307 StorageHit,
308 StorageMiss,
309 CodeHit,
310 CodeMiss,
311}
312
313#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
314struct CacheMetricSnapshot {
315 account_hits: u64,
316 account_misses: u64,
317 storage_hits: u64,
318 storage_misses: u64,
319 code_hits: u64,
320 code_misses: u64,
321}
322
323impl CacheMetricSnapshot {
324 const fn is_empty(&self) -> bool {
325 self.account_hits == 0 &&
326 self.account_misses == 0 &&
327 self.storage_hits == 0 &&
328 self.storage_misses == 0 &&
329 self.code_hits == 0 &&
330 self.code_misses == 0
331 }
332}
333
334#[derive(Debug, Default)]
335struct CacheMetricCounts {
336 account_hits: Cell<u64>,
337 account_misses: Cell<u64>,
338 storage_hits: Cell<u64>,
339 storage_misses: Cell<u64>,
340 code_hits: Cell<u64>,
341 code_misses: Cell<u64>,
342}
343
344impl CacheMetricCounts {
345 const fn new() -> Self {
346 Self {
347 account_hits: Cell::new(0),
348 account_misses: Cell::new(0),
349 storage_hits: Cell::new(0),
350 storage_misses: Cell::new(0),
351 code_hits: Cell::new(0),
352 code_misses: Cell::new(0),
353 }
354 }
355
356 #[inline]
357 fn record(&self, kind: CacheMetricKind) {
358 let counter = match kind {
359 CacheMetricKind::AccountHit => &self.account_hits,
360 CacheMetricKind::AccountMiss => &self.account_misses,
361 CacheMetricKind::StorageHit => &self.storage_hits,
362 CacheMetricKind::StorageMiss => &self.storage_misses,
363 CacheMetricKind::CodeHit => &self.code_hits,
364 CacheMetricKind::CodeMiss => &self.code_misses,
365 };
366 counter.set(counter.get() + 1);
367 }
368
369 const fn take(&self) -> CacheMetricSnapshot {
370 CacheMetricSnapshot {
371 account_hits: self.account_hits.replace(0),
372 account_misses: self.account_misses.replace(0),
373 storage_hits: self.storage_hits.replace(0),
374 storage_misses: self.storage_misses.replace(0),
375 code_hits: self.code_hits.replace(0),
376 code_misses: self.code_misses.replace(0),
377 }
378 }
379}
380
381#[derive(Debug, Clone, PartialEq, Eq)]
383pub enum CachedStatus<T> {
384 NotCached(T),
386 Cached(T),
388}
389
390#[derive(Debug, Clone, Copy, PartialEq, Eq)]
392pub enum CachedStateMetricsSource {
393 Engine,
395 Builder,
397 #[cfg(any(test, feature = "test-utils"))]
399 Test,
400}
401
402impl fmt::Display for CachedStateMetricsSource {
403 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
404 match self {
405 Self::Engine => f.write_str("engine"),
406 Self::Builder => f.write_str("builder"),
407 #[cfg(any(test, feature = "test-utils"))]
408 Self::Test => f.write_str("test"),
409 }
410 }
411}
412
413#[derive(Metrics, Clone)]
415#[metrics(scope = "sync.caching")]
416pub struct CachedStateMetrics {
417 execution_cache_created_total: Counter,
419
420 execution_cache_creation_duration_seconds: Histogram,
422
423 code_cache_hits: Gauge,
425
426 code_cache_misses: Gauge,
428
429 storage_cache_hits: Gauge,
431
432 storage_cache_misses: Gauge,
434
435 account_cache_hits: Gauge,
437
438 account_cache_misses: Gauge,
440
441 txpool_snapshot_code_hits: Gauge,
443
444 txpool_snapshot_code_misses: Gauge,
446
447 txpool_snapshot_storage_hits: Gauge,
449
450 txpool_snapshot_storage_misses: Gauge,
452
453 txpool_snapshot_account_hits: Gauge,
455
456 txpool_snapshot_account_misses: Gauge,
458}
459
460#[derive(Metrics, Clone)]
462#[metrics(scope = "sync.caching")]
463pub struct CachedStateCacheMetrics {
464 code_cache_size: Gauge,
466
467 code_cache_capacity: Gauge,
469
470 code_cache_collisions: Gauge,
472
473 storage_cache_size: Gauge,
475
476 storage_cache_capacity: Gauge,
478
479 storage_cache_collisions: Gauge,
481
482 account_cache_size: Gauge,
484
485 account_cache_capacity: Gauge,
487
488 account_cache_collisions: Gauge,
490}
491
492impl CachedStateMetrics {
493 pub fn reset(&self) {
495 self.code_cache_hits.set(0);
497 self.code_cache_misses.set(0);
498
499 self.storage_cache_hits.set(0);
501 self.storage_cache_misses.set(0);
502
503 self.account_cache_hits.set(0);
505 self.account_cache_misses.set(0);
506
507 self.txpool_snapshot_code_hits.set(0);
509 self.txpool_snapshot_code_misses.set(0);
510
511 self.txpool_snapshot_storage_hits.set(0);
513 self.txpool_snapshot_storage_misses.set(0);
514
515 self.txpool_snapshot_account_hits.set(0);
517 self.txpool_snapshot_account_misses.set(0);
518 }
519
520 pub fn zeroed(source: CachedStateMetricsSource) -> Self {
523 let zeroed = Self::new_with_labels(&[("source", source.to_string())]);
524 zeroed.reset();
525 zeroed
526 }
527
528 fn record_access(&self, kind: CacheMetricKind, count: u64) {
529 match kind {
530 CacheMetricKind::AccountHit => self.account_cache_hits.increment(count as f64),
531 CacheMetricKind::AccountMiss => self.account_cache_misses.increment(count as f64),
532 CacheMetricKind::StorageHit => self.storage_cache_hits.increment(count as f64),
533 CacheMetricKind::StorageMiss => self.storage_cache_misses.increment(count as f64),
534 CacheMetricKind::CodeHit => self.code_cache_hits.increment(count as f64),
535 CacheMetricKind::CodeMiss => self.code_cache_misses.increment(count as f64),
536 }
537 }
538
539 fn record_access_counts(&self, counts: CacheMetricSnapshot) {
540 if counts.account_hits != 0 {
541 self.record_access(CacheMetricKind::AccountHit, counts.account_hits);
542 }
543 if counts.account_misses != 0 {
544 self.record_access(CacheMetricKind::AccountMiss, counts.account_misses);
545 }
546 if counts.storage_hits != 0 {
547 self.record_access(CacheMetricKind::StorageHit, counts.storage_hits);
548 }
549 if counts.storage_misses != 0 {
550 self.record_access(CacheMetricKind::StorageMiss, counts.storage_misses);
551 }
552 if counts.code_hits != 0 {
553 self.record_access(CacheMetricKind::CodeHit, counts.code_hits);
554 }
555 if counts.code_misses != 0 {
556 self.record_access(CacheMetricKind::CodeMiss, counts.code_misses);
557 }
558 }
559
560 fn record_txpool_access(&self, kind: CacheMetricKind, count: u64) {
561 match kind {
562 CacheMetricKind::AccountHit => {
563 self.txpool_snapshot_account_hits.increment(count as f64)
564 }
565 CacheMetricKind::AccountMiss => {
566 self.txpool_snapshot_account_misses.increment(count as f64)
567 }
568 CacheMetricKind::StorageHit => {
569 self.txpool_snapshot_storage_hits.increment(count as f64)
570 }
571 CacheMetricKind::StorageMiss => {
572 self.txpool_snapshot_storage_misses.increment(count as f64)
573 }
574 CacheMetricKind::CodeHit => self.txpool_snapshot_code_hits.increment(count as f64),
575 CacheMetricKind::CodeMiss => self.txpool_snapshot_code_misses.increment(count as f64),
576 }
577 }
578
579 fn record_txpool_access_counts(&self, counts: CacheMetricSnapshot) {
580 if counts.account_hits != 0 {
581 self.record_txpool_access(CacheMetricKind::AccountHit, counts.account_hits);
582 }
583 if counts.account_misses != 0 {
584 self.record_txpool_access(CacheMetricKind::AccountMiss, counts.account_misses);
585 }
586 if counts.storage_hits != 0 {
587 self.record_txpool_access(CacheMetricKind::StorageHit, counts.storage_hits);
588 }
589 if counts.storage_misses != 0 {
590 self.record_txpool_access(CacheMetricKind::StorageMiss, counts.storage_misses);
591 }
592 if counts.code_hits != 0 {
593 self.record_txpool_access(CacheMetricKind::CodeHit, counts.code_hits);
594 }
595 if counts.code_misses != 0 {
596 self.record_txpool_access(CacheMetricKind::CodeMiss, counts.code_misses);
597 }
598 }
599
600 pub fn record_cache_creation(&self, duration: Duration) {
602 self.execution_cache_created_total.increment(1);
603 self.execution_cache_creation_duration_seconds.record(duration.as_secs_f64());
604 }
605}
606
607#[derive(Debug, Default)]
609pub struct CacheStats {
610 account_hits: AtomicUsize,
612 account_misses: AtomicUsize,
614 storage_hits: AtomicUsize,
616 storage_misses: AtomicUsize,
618 code_hits: AtomicUsize,
620 code_misses: AtomicUsize,
622 txpool_snapshot_account_hits: AtomicUsize,
624 txpool_snapshot_account_misses: AtomicUsize,
626 txpool_snapshot_storage_hits: AtomicUsize,
628 txpool_snapshot_storage_misses: AtomicUsize,
630 txpool_snapshot_code_hits: AtomicUsize,
632 txpool_snapshot_code_misses: AtomicUsize,
634}
635
636impl CacheStats {
637 pub fn record_account_hit(&self) {
639 self.account_hits.fetch_add(1, Ordering::Relaxed);
640 }
641
642 pub fn record_account_miss(&self) {
644 self.account_misses.fetch_add(1, Ordering::Relaxed);
645 }
646
647 pub fn account_hits(&self) -> usize {
649 self.account_hits.load(Ordering::Relaxed)
650 }
651
652 pub fn account_misses(&self) -> usize {
654 self.account_misses.load(Ordering::Relaxed)
655 }
656
657 pub fn record_storage_hit(&self) {
659 self.storage_hits.fetch_add(1, Ordering::Relaxed);
660 }
661
662 pub fn record_storage_miss(&self) {
664 self.storage_misses.fetch_add(1, Ordering::Relaxed);
665 }
666
667 pub fn storage_hits(&self) -> usize {
669 self.storage_hits.load(Ordering::Relaxed)
670 }
671
672 pub fn storage_misses(&self) -> usize {
674 self.storage_misses.load(Ordering::Relaxed)
675 }
676
677 pub fn record_code_hit(&self) {
679 self.code_hits.fetch_add(1, Ordering::Relaxed);
680 }
681
682 pub fn record_code_miss(&self) {
684 self.code_misses.fetch_add(1, Ordering::Relaxed);
685 }
686
687 pub fn code_hits(&self) -> usize {
689 self.code_hits.load(Ordering::Relaxed)
690 }
691
692 pub fn code_misses(&self) -> usize {
694 self.code_misses.load(Ordering::Relaxed)
695 }
696
697 pub fn record_txpool_snapshot_account_hit(&self) {
699 self.txpool_snapshot_account_hits.fetch_add(1, Ordering::Relaxed);
700 }
701
702 pub fn record_txpool_snapshot_account_miss(&self) {
704 self.txpool_snapshot_account_misses.fetch_add(1, Ordering::Relaxed);
705 }
706
707 pub fn txpool_snapshot_account_hits(&self) -> usize {
709 self.txpool_snapshot_account_hits.load(Ordering::Relaxed)
710 }
711
712 pub fn txpool_snapshot_account_misses(&self) -> usize {
714 self.txpool_snapshot_account_misses.load(Ordering::Relaxed)
715 }
716
717 pub fn record_txpool_snapshot_storage_hit(&self) {
719 self.txpool_snapshot_storage_hits.fetch_add(1, Ordering::Relaxed);
720 }
721
722 pub fn record_txpool_snapshot_storage_miss(&self) {
724 self.txpool_snapshot_storage_misses.fetch_add(1, Ordering::Relaxed);
725 }
726
727 pub fn txpool_snapshot_storage_hits(&self) -> usize {
729 self.txpool_snapshot_storage_hits.load(Ordering::Relaxed)
730 }
731
732 pub fn txpool_snapshot_storage_misses(&self) -> usize {
734 self.txpool_snapshot_storage_misses.load(Ordering::Relaxed)
735 }
736
737 pub fn record_txpool_snapshot_code_hit(&self) {
739 self.txpool_snapshot_code_hits.fetch_add(1, Ordering::Relaxed);
740 }
741
742 pub fn record_txpool_snapshot_code_miss(&self) {
744 self.txpool_snapshot_code_misses.fetch_add(1, Ordering::Relaxed);
745 }
746
747 pub fn txpool_snapshot_code_hits(&self) -> usize {
749 self.txpool_snapshot_code_hits.load(Ordering::Relaxed)
750 }
751
752 pub fn txpool_snapshot_code_misses(&self) -> usize {
754 self.txpool_snapshot_code_misses.load(Ordering::Relaxed)
755 }
756}
757
758#[derive(Debug)]
773pub struct CacheStatsHandler {
774 collisions: AtomicU64,
775 size: AtomicUsize,
776 capacity: usize,
777}
778
779impl CacheStatsHandler {
780 pub const fn new(capacity: usize) -> Self {
782 Self { collisions: AtomicU64::new(0), size: AtomicUsize::new(0), capacity }
783 }
784
785 pub fn collisions(&self) -> u64 {
787 self.collisions.load(Ordering::Relaxed)
788 }
789
790 pub fn size(&self) -> usize {
792 self.size.load(Ordering::Relaxed)
793 }
794
795 pub const fn capacity(&self) -> usize {
797 self.capacity
798 }
799
800 pub fn increment_size(&self) {
802 let _ = self.size.fetch_add(1, Ordering::Relaxed);
803 }
804
805 pub fn decrement_size(&self) {
807 let _ = self.size.fetch_sub(1, Ordering::Relaxed);
808 }
809
810 pub fn reset_size(&self) {
812 self.size.store(0, Ordering::Relaxed);
813 }
814
815 pub fn reset_stats(&self) {
817 self.collisions.store(0, Ordering::Relaxed);
818 }
819}
820
821impl<K: PartialEq, V> StatsHandler<K, V> for CacheStatsHandler {
822 fn on_hit(&self, _key: &K, _value: &V) {}
823
824 fn on_miss(&self, _key: AnyRef<'_>) {}
825
826 fn on_insert(&self, key: &K, _value: &V, evicted: Option<(&K, &V)>) {
827 match evicted {
828 None => {
829 self.increment_size();
831 }
832 Some((evicted_key, _)) if evicted_key != key => {
833 self.collisions.fetch_add(1, Ordering::Relaxed);
835 }
836 Some(_) => {
837 }
839 }
840 }
841
842 fn on_remove(&self, _key: &K, _value: &V) {
843 self.decrement_size();
844 }
845}
846
847impl<S: AccountReader> AccountReader for CachedStateProvider<S> {
848 fn basic_account(&self, address: &Address) -> ProviderResult<Option<Account>> {
849 if let Some(snapshot) = &self.txpool_snapshot {
850 if let Some(account) = snapshot.account(address) {
851 self.record_txpool_account_hit();
852 return Ok(account)
853 }
854 self.record_txpool_account_miss();
855 }
856
857 if self.should_fill_on_miss() {
858 match self.caches.get_or_try_insert_account_with(*address, || {
859 self.state_provider.basic_account(address)
860 })? {
861 CachedStatus::NotCached(value) => {
862 self.record_account_miss();
863 Ok(value)
864 }
865 CachedStatus::Cached(value) => {
866 self.record_account_hit();
867 Ok(value)
868 }
869 }
870 } else if let Some(account) = self.caches.0.account_cache.get(address) {
871 self.record_account_hit();
872 Ok(account)
873 } else {
874 self.record_account_miss();
875 self.state_provider.basic_account(address)
876 }
877 }
878}
879
880#[inline]
881fn nonzero_storage_value(value: StorageValue) -> Option<StorageValue> {
882 if value.is_zero() {
883 None
884 } else {
885 Some(value)
886 }
887}
888
889impl<S: StateProvider> StateProvider for CachedStateProvider<S> {
890 fn storage(
891 &self,
892 account: Address,
893 storage_key: StorageKey,
894 ) -> ProviderResult<Option<StorageValue>> {
895 if let Some(snapshot) = &self.txpool_snapshot {
896 if let Some(value) = snapshot.storage(account, storage_key) {
897 self.record_txpool_storage_hit();
898 return Ok(nonzero_storage_value(value))
899 }
900 self.record_txpool_storage_miss();
901 }
902
903 if self.should_fill_on_miss() {
904 match self.caches.get_or_try_insert_storage_with(account, storage_key, || {
905 self.state_provider.storage(account, storage_key).map(Option::unwrap_or_default)
906 })? {
907 CachedStatus::NotCached(value) => {
908 self.record_storage_miss();
909 Ok(nonzero_storage_value(value))
910 }
911 CachedStatus::Cached(value) => {
912 self.record_storage_hit();
913 Ok(nonzero_storage_value(value))
914 }
915 }
916 } else if let Some(value) = self.caches.0.storage_cache.get(&(account, storage_key)) {
917 self.record_storage_hit();
918 Ok(nonzero_storage_value(value))
919 } else {
920 self.record_storage_miss();
921 self.state_provider.storage(account, storage_key)
922 }
923 }
924}
925
926impl<S: BytecodeReader> BytecodeReader for CachedStateProvider<S> {
927 fn bytecode_by_hash(&self, code_hash: &B256) -> ProviderResult<Option<Bytecode>> {
928 if let Some(snapshot) = &self.txpool_snapshot {
929 if let Some(code) = snapshot.bytecode(code_hash) {
930 self.record_txpool_code_hit();
931 return Ok(code)
932 }
933 self.record_txpool_code_miss();
934 }
935
936 if self.should_fill_on_miss() {
937 match self.caches.get_or_try_insert_code_with(*code_hash, || {
938 self.state_provider.bytecode_by_hash(code_hash)
939 })? {
940 CachedStatus::NotCached(code) => {
941 self.record_code_miss();
942 Ok(code)
943 }
944 CachedStatus::Cached(code) => {
945 self.record_code_hit();
946 Ok(code)
947 }
948 }
949 } else if let Some(code) = self.caches.0.code_cache.get(code_hash) {
950 self.record_code_hit();
951 Ok(code)
952 } else {
953 self.record_code_miss();
954 self.state_provider.bytecode_by_hash(code_hash)
955 }
956 }
957}
958
959impl<S: StateRootProvider> StateRootProvider for CachedStateProvider<S> {
960 fn state_root(&self, hashed_state: HashedPostState) -> ProviderResult<B256> {
961 self.state_provider.state_root(hashed_state)
962 }
963
964 fn state_root_from_nodes(&self, input: TrieInput) -> ProviderResult<B256> {
965 self.state_provider.state_root_from_nodes(input)
966 }
967
968 fn state_root_with_updates(
969 &self,
970 hashed_state: HashedPostState,
971 ) -> ProviderResult<(B256, TrieUpdates)> {
972 self.state_provider.state_root_with_updates(hashed_state)
973 }
974
975 fn state_root_from_nodes_with_updates(
976 &self,
977 input: TrieInput,
978 ) -> ProviderResult<(B256, TrieUpdates)> {
979 self.state_provider.state_root_from_nodes_with_updates(input)
980 }
981}
982
983impl<S: StateProofProvider> StateProofProvider for CachedStateProvider<S> {
984 fn proof(
985 &self,
986 input: TrieInput,
987 address: Address,
988 slots: &[B256],
989 ) -> ProviderResult<AccountProof> {
990 self.state_provider.proof(input, address, slots)
991 }
992
993 fn multiproof(
994 &self,
995 input: TrieInput,
996 targets: MultiProofTargets,
997 ) -> ProviderResult<MultiProof> {
998 self.state_provider.multiproof(input, targets)
999 }
1000
1001 fn witness(
1002 &self,
1003 input: TrieInput,
1004 target: HashedPostState,
1005 mode: reth_trie::ExecutionWitnessMode,
1006 ) -> ProviderResult<Vec<alloy_primitives::Bytes>> {
1007 self.state_provider.witness(input, target, mode)
1008 }
1009}
1010
1011impl<S: StorageRootProvider> StorageRootProvider for CachedStateProvider<S> {
1012 fn storage_root(
1013 &self,
1014 address: Address,
1015 hashed_storage: HashedStorage,
1016 ) -> ProviderResult<B256> {
1017 self.state_provider.storage_root(address, hashed_storage)
1018 }
1019
1020 fn storage_proof(
1021 &self,
1022 address: Address,
1023 slot: B256,
1024 hashed_storage: HashedStorage,
1025 ) -> ProviderResult<StorageProof> {
1026 self.state_provider.storage_proof(address, slot, hashed_storage)
1027 }
1028
1029 fn storage_multiproof(
1030 &self,
1031 address: Address,
1032 slots: &[B256],
1033 hashed_storage: HashedStorage,
1034 ) -> ProviderResult<StorageMultiProof> {
1035 self.state_provider.storage_multiproof(address, slots, hashed_storage)
1036 }
1037}
1038
1039impl<S: BlockHashReader> BlockHashReader for CachedStateProvider<S> {
1040 fn block_hash(&self, number: alloy_primitives::BlockNumber) -> ProviderResult<Option<B256>> {
1041 self.state_provider.block_hash(number)
1042 }
1043
1044 fn canonical_hashes_range(
1045 &self,
1046 start: alloy_primitives::BlockNumber,
1047 end: alloy_primitives::BlockNumber,
1048 ) -> ProviderResult<Vec<B256>> {
1049 self.state_provider.canonical_hashes_range(start, end)
1050 }
1051}
1052
1053impl<S: HashedPostStateProvider> HashedPostStateProvider for CachedStateProvider<S> {
1054 fn hashed_post_state(&self, bundle_state: &reth_revm::db::BundleState) -> HashedPostState {
1055 self.state_provider.hashed_post_state(bundle_state)
1056 }
1057}
1058
1059#[derive(Debug, Clone)]
1070pub struct ExecutionCache(Arc<ExecutionCacheInner>);
1071
1072#[derive(Debug)]
1074struct ExecutionCacheInner {
1075 code_cache: FixedCache<B256, Option<Bytecode>, FbBuildHasher<32>>,
1077
1078 storage_cache: FixedCache<(Address, StorageKey), StorageValue>,
1080
1081 account_cache: FixedCache<Address, Option<Account>, FbBuildHasher<20>>,
1083
1084 code_stats: Arc<CacheStatsHandler>,
1086
1087 storage_stats: Arc<CacheStatsHandler>,
1089
1090 account_stats: Arc<CacheStatsHandler>,
1092
1093 selfdestruct_encountered: Once,
1095}
1096
1097impl ExecutionCache {
1098 const MIN_CACHE_SIZE_WITH_EPOCHS: usize = 1 << 12; pub const fn bytes_to_entries(size_bytes: usize, entry_size: usize) -> usize {
1107 let entries = size_bytes / entry_size;
1108 let rounded = if entries == 0 { 1 } else { (entries + 1).next_power_of_two() >> 1 };
1110 if rounded < Self::MIN_CACHE_SIZE_WITH_EPOCHS {
1112 Self::MIN_CACHE_SIZE_WITH_EPOCHS
1113 } else {
1114 rounded
1115 }
1116 }
1117
1118 pub fn new(total_cache_size: usize) -> Self {
1120 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);
1125 let storage_capacity = Self::bytes_to_entries(storage_cache_size, STORAGE_CACHE_ENTRY_SIZE);
1126 let account_capacity = Self::bytes_to_entries(account_cache_size, ACCOUNT_CACHE_ENTRY_SIZE);
1127
1128 let code_stats = Arc::new(CacheStatsHandler::new(code_capacity));
1129 let storage_stats = Arc::new(CacheStatsHandler::new(storage_capacity));
1130 let account_stats = Arc::new(CacheStatsHandler::new(account_capacity));
1131
1132 Self(Arc::new(ExecutionCacheInner {
1133 code_cache: FixedCache::new(code_capacity, FbBuildHasher::<32>::default())
1134 .with_stats(Some(Stats::new(code_stats.clone()))),
1135 storage_cache: FixedCache::new(storage_capacity, DefaultHashBuilder::default())
1136 .with_stats(Some(Stats::new(storage_stats.clone()))),
1137 account_cache: FixedCache::new(account_capacity, FbBuildHasher::<20>::default())
1138 .with_stats(Some(Stats::new(account_stats.clone()))),
1139 code_stats,
1140 storage_stats,
1141 account_stats,
1142 selfdestruct_encountered: Once::new(),
1143 }))
1144 }
1145
1146 fn usage_count(&self) -> usize {
1148 Arc::strong_count(&self.0)
1149 }
1150
1151 pub fn get_or_try_insert_code_with<E>(
1153 &self,
1154 hash: B256,
1155 f: impl FnOnce() -> Result<Option<Bytecode>, E>,
1156 ) -> Result<CachedStatus<Option<Bytecode>>, E> {
1157 let mut miss = false;
1158 let result = self.0.code_cache.get_or_try_insert_with(hash, |_| {
1159 miss = true;
1160 f()
1161 })?;
1162
1163 if miss {
1164 Ok(CachedStatus::NotCached(result))
1165 } else {
1166 Ok(CachedStatus::Cached(result))
1167 }
1168 }
1169
1170 pub fn get_or_try_insert_storage_with<E>(
1172 &self,
1173 address: Address,
1174 key: StorageKey,
1175 f: impl FnOnce() -> Result<StorageValue, E>,
1176 ) -> Result<CachedStatus<StorageValue>, E> {
1177 let mut miss = false;
1178 let result = self.0.storage_cache.get_or_try_insert_with((address, key), |_| {
1179 miss = true;
1180 f()
1181 })?;
1182
1183 if miss {
1184 Ok(CachedStatus::NotCached(result))
1185 } else {
1186 Ok(CachedStatus::Cached(result))
1187 }
1188 }
1189
1190 pub fn get_or_try_insert_account_with<E>(
1192 &self,
1193 address: Address,
1194 f: impl FnOnce() -> Result<Option<Account>, E>,
1195 ) -> Result<CachedStatus<Option<Account>>, E> {
1196 let mut miss = false;
1197 let result = self.0.account_cache.get_or_try_insert_with(address, |_| {
1198 miss = true;
1199 f()
1200 })?;
1201
1202 if miss {
1203 Ok(CachedStatus::NotCached(result))
1204 } else {
1205 Ok(CachedStatus::Cached(result))
1206 }
1207 }
1208
1209 pub fn insert_storage(&self, address: Address, key: StorageKey, value: Option<StorageValue>) {
1211 self.0.storage_cache.insert((address, key), value.unwrap_or_default());
1212 }
1213
1214 pub fn insert_code(&self, hash: B256, code: Option<Bytecode>) {
1216 self.0.code_cache.insert(hash, code);
1217 }
1218
1219 pub fn insert_account(&self, address: Address, account: Option<Account>) {
1221 self.0.account_cache.insert(address, account);
1222 }
1223
1224 #[instrument(level = "debug", target = "engine::caching", skip_all)]
1243 #[expect(clippy::result_unit_err)]
1244 pub fn insert_state(&self, state_updates: &BundleState) -> Result<(), ()> {
1245 let _enter =
1246 debug_span!(target: "engine::tree", "contracts", len = state_updates.contracts.len())
1247 .entered();
1248 for (code_hash, bytecode) in &state_updates.contracts {
1250 self.insert_code(*code_hash, Some(Bytecode(bytecode.clone())));
1251 }
1252 drop(_enter);
1253
1254 let _enter = debug_span!(
1255 target: "engine::tree",
1256 "accounts",
1257 accounts = state_updates.state.len(),
1258 storages =
1259 state_updates.state.values().map(|account| account.storage.len()).sum::<usize>()
1260 )
1261 .entered();
1262 for (addr, account) in &state_updates.state {
1263 if account.status.is_not_modified() {
1266 continue
1267 }
1268
1269 if account.was_destroyed() {
1276 let had_code =
1277 account.original_info.as_ref().is_some_and(|info| !info.is_empty_code_hash());
1278 if had_code {
1279 self.0.selfdestruct_encountered.call_once(|| {
1280 warn!(
1281 target: "engine::caching",
1282 address = ?addr,
1283 info = ?account.info,
1284 original_info = ?account.original_info,
1285 "Encountered an inter-transaction SELFDESTRUCT that reset the storage cache. Are you running a pre-Dencun network?"
1286 );
1287 });
1288 self.clear();
1289 return Ok(())
1290 }
1291
1292 self.0.account_cache.remove(addr);
1293 continue;
1294 }
1295
1296 let Some(ref account_info) = account.info else {
1300 trace!(target: "engine::caching", ?account, "Account with None account info found in state updates");
1301 return Err(())
1302 };
1303
1304 for (key, slot) in &account.storage {
1306 self.insert_storage(*addr, (*key).into(), Some(slot.present_value));
1307 }
1308
1309 self.insert_account(*addr, Some(Account::from(account_info)));
1312 }
1313
1314 Ok(())
1315 }
1316
1317 pub fn clear(&self) {
1322 self.0.storage_cache.clear();
1323 self.0.account_cache.clear();
1324
1325 self.0.storage_stats.reset_size();
1326 self.0.account_stats.reset_size();
1327 }
1328
1329 pub fn update_metrics(&self, metrics: &CachedStateCacheMetrics) {
1332 metrics.code_cache_size.set(self.0.code_stats.size() as f64);
1333 metrics.code_cache_capacity.set(self.0.code_stats.capacity() as f64);
1334 metrics.code_cache_collisions.set(self.0.code_stats.collisions() as f64);
1335 self.0.code_stats.reset_stats();
1336
1337 metrics.storage_cache_size.set(self.0.storage_stats.size() as f64);
1338 metrics.storage_cache_capacity.set(self.0.storage_stats.capacity() as f64);
1339 metrics.storage_cache_collisions.set(self.0.storage_stats.collisions() as f64);
1340 self.0.storage_stats.reset_stats();
1341
1342 metrics.account_cache_size.set(self.0.account_stats.size() as f64);
1343 metrics.account_cache_capacity.set(self.0.account_stats.capacity() as f64);
1344 metrics.account_cache_collisions.set(self.0.account_stats.collisions() as f64);
1345 self.0.account_stats.reset_stats();
1346 }
1347}
1348
1349#[derive(Debug, Clone)]
1352pub struct SavedCache {
1353 hash: B256,
1355
1356 caches: ExecutionCache,
1358}
1359
1360impl SavedCache {
1361 pub const fn new(hash: B256, caches: ExecutionCache) -> Self {
1363 Self { hash, caches }
1364 }
1365
1366 pub const fn executed_block_hash(&self) -> B256 {
1368 self.hash
1369 }
1370
1371 pub fn is_available(&self) -> bool {
1373 self.caches.usage_count() == 1
1374 }
1375
1376 pub fn usage_count(&self) -> usize {
1378 self.caches.usage_count()
1379 }
1380
1381 pub const fn cache(&self) -> &ExecutionCache {
1383 &self.caches
1384 }
1385
1386 pub fn update_metrics(&self, metrics: Option<&CachedStateCacheMetrics>) {
1388 if let Some(metrics) = metrics {
1389 self.caches.update_metrics(metrics);
1390 }
1391 }
1392
1393 pub fn clear_with_hash(&mut self, hash: B256) {
1396 self.hash = hash;
1397 self.caches.clear();
1398 }
1399}
1400
1401#[cfg(any(test, feature = "test-utils"))]
1402impl SavedCache {
1403 pub fn clone_guard_for_test(&self) -> ExecutionCache {
1405 self.caches.clone()
1406 }
1407}
1408
1409#[cfg(test)]
1410mod tests {
1411 use super::*;
1412 use alloy_primitives::{map::HashMap, U256};
1413 use reth_provider::test_utils::{ExtendedAccount, MockEthProvider};
1414 use reth_revm::db::{AccountStatus, BundleAccount};
1415 use revm::state::AccountInfo;
1416
1417 #[test]
1418 fn test_empty_storage_cached_state_provider() {
1419 let address = Address::random();
1420 let storage_key = StorageKey::random();
1421 let account = ExtendedAccount::new(0, U256::ZERO);
1422
1423 let provider = MockEthProvider::default();
1424 provider.extend_accounts(vec![(address, account)]);
1425
1426 let caches = ExecutionCache::new(1000);
1427 let state_provider = CachedStateProvider::new(
1428 provider,
1429 caches,
1430 Some(CachedStateMetrics::zeroed(CachedStateMetricsSource::Test)),
1431 );
1432
1433 let res = state_provider.storage(address, storage_key);
1434 assert!(res.is_ok());
1435 assert_eq!(res.unwrap(), None);
1436 }
1437
1438 #[test]
1439 fn test_uncached_storage_cached_state_provider() {
1440 let address = Address::random();
1441 let storage_key = StorageKey::random();
1442 let storage_value = U256::from(1);
1443 let account =
1444 ExtendedAccount::new(0, U256::ZERO).extend_storage(vec![(storage_key, storage_value)]);
1445
1446 let provider = MockEthProvider::default();
1447 provider.extend_accounts(vec![(address, account)]);
1448
1449 let caches = ExecutionCache::new(1000);
1450 let state_provider = CachedStateProvider::new(
1451 provider,
1452 caches,
1453 Some(CachedStateMetrics::zeroed(CachedStateMetricsSource::Test)),
1454 );
1455
1456 let res = state_provider.storage(address, storage_key);
1457 assert!(res.is_ok());
1458 assert_eq!(res.unwrap(), Some(storage_value));
1459 }
1460
1461 #[test]
1462 fn test_get_storage_populated() {
1463 let address = Address::random();
1464 let storage_key = StorageKey::random();
1465 let storage_value = U256::from(1);
1466
1467 let caches = ExecutionCache::new(1000);
1468 caches.insert_storage(address, storage_key, Some(storage_value));
1469
1470 let result = caches
1471 .get_or_try_insert_storage_with(address, storage_key, || Ok::<_, ()>(U256::from(999)));
1472 assert_eq!(result.unwrap(), CachedStatus::Cached(storage_value));
1473 }
1474
1475 #[test]
1476 fn test_get_storage_empty() {
1477 let address = Address::random();
1478 let storage_key = StorageKey::random();
1479
1480 let caches = ExecutionCache::new(1000);
1481 caches.insert_storage(address, storage_key, None);
1482
1483 let result = caches
1484 .get_or_try_insert_storage_with(address, storage_key, || Ok::<_, ()>(U256::from(999)));
1485 assert_eq!(result.unwrap(), CachedStatus::Cached(U256::ZERO));
1486 }
1487
1488 #[test]
1489 fn test_saved_cache_is_available() {
1490 let execution_cache = ExecutionCache::new(1000);
1491 let cache = SavedCache::new(B256::ZERO, execution_cache);
1492
1493 assert!(cache.is_available(), "Cache should be available initially");
1494
1495 let _cache = cache.clone_guard_for_test();
1496
1497 assert!(!cache.is_available(), "Cache should not be available with active handle");
1498 }
1499
1500 #[test]
1501 fn test_saved_cache_multiple_references() {
1502 let execution_cache = ExecutionCache::new(1000);
1503 let cache = SavedCache::new(B256::from([2u8; 32]), execution_cache);
1504
1505 let cache1 = cache.clone_guard_for_test();
1506 let cache2 = cache.clone_guard_for_test();
1507 let cache3 = cache1.clone();
1508
1509 assert!(!cache.is_available());
1510
1511 drop(cache1);
1512 assert!(!cache.is_available());
1513
1514 drop(cache2);
1515 assert!(!cache.is_available());
1516
1517 drop(cache3);
1518 assert!(cache.is_available());
1519 }
1520
1521 #[test]
1522 fn test_insert_state_destroyed_account_with_code_clears_cache() {
1523 let caches = ExecutionCache::new(1000);
1524
1525 let addr1 = Address::random();
1527 let addr2 = Address::random();
1528 let storage_key = StorageKey::random();
1529 caches.insert_account(addr1, Some(Account::default()));
1530 caches.insert_account(addr2, Some(Account::default()));
1531 caches.insert_storage(addr1, storage_key, Some(U256::from(42)));
1532
1533 assert!(caches.0.account_cache.get(&addr1).is_some());
1535 assert!(caches.0.account_cache.get(&addr2).is_some());
1536 assert!(caches.0.storage_cache.get(&(addr1, storage_key)).is_some());
1537
1538 let bundle = BundleState {
1539 state: HashMap::from_iter([(
1541 Address::random(),
1542 BundleAccount::new(
1543 Some(AccountInfo {
1544 balance: U256::ZERO,
1545 nonce: 1,
1546 code_hash: B256::random(), code: None,
1548 account_id: None,
1549 }),
1550 None, Default::default(),
1552 AccountStatus::Destroyed,
1553 ),
1554 )]),
1555 contracts: Default::default(),
1556 reverts: Default::default(),
1557 state_size: 0,
1558 reverts_size: 0,
1559 };
1560
1561 let result = caches.insert_state(&bundle);
1563 assert!(result.is_ok());
1564
1565 assert!(caches.0.account_cache.get(&addr1).is_none());
1567 assert!(caches.0.account_cache.get(&addr2).is_none());
1568 assert!(caches.0.storage_cache.get(&(addr1, storage_key)).is_none());
1569 }
1570
1571 #[test]
1572 fn test_insert_state_destroyed_account_without_code_removes_only_account() {
1573 let caches = ExecutionCache::new(1000);
1574
1575 let addr1 = Address::random();
1577 let addr2 = Address::random();
1578 let storage_key = StorageKey::random();
1579 caches.insert_account(addr1, Some(Account::default()));
1580 caches.insert_account(addr2, Some(Account::default()));
1581 caches.insert_storage(addr1, storage_key, Some(U256::from(42)));
1582
1583 let bundle = BundleState {
1584 state: HashMap::from_iter([(
1586 addr1,
1587 BundleAccount::new(
1588 Some(AccountInfo {
1589 balance: U256::from(100),
1590 nonce: 1,
1591 code_hash: alloy_primitives::KECCAK256_EMPTY, code: None,
1593 account_id: None,
1594 }),
1595 None, Default::default(),
1597 AccountStatus::Destroyed,
1598 ),
1599 )]),
1600 contracts: Default::default(),
1601 reverts: Default::default(),
1602 state_size: 0,
1603 reverts_size: 0,
1604 };
1605
1606 assert!(caches.insert_state(&bundle).is_ok());
1608
1609 assert!(caches.0.account_cache.get(&addr1).is_none());
1611 assert!(caches.0.account_cache.get(&addr2).is_some());
1612 assert!(caches.0.storage_cache.get(&(addr1, storage_key)).is_some());
1613 }
1614
1615 #[test]
1616 fn test_insert_state_destroyed_account_no_original_info_removes_only_account() {
1617 let caches = ExecutionCache::new(1000);
1618
1619 let addr1 = Address::random();
1621 let addr2 = Address::random();
1622 caches.insert_account(addr1, Some(Account::default()));
1623 caches.insert_account(addr2, Some(Account::default()));
1624
1625 let bundle = BundleState {
1626 state: HashMap::from_iter([(
1628 addr1,
1629 BundleAccount::new(
1630 None, None, Default::default(),
1633 AccountStatus::Destroyed,
1634 ),
1635 )]),
1636 contracts: Default::default(),
1637 reverts: Default::default(),
1638 state_size: 0,
1639 reverts_size: 0,
1640 };
1641
1642 assert!(caches.insert_state(&bundle).is_ok());
1644
1645 assert!(caches.0.account_cache.get(&addr1).is_none());
1647 assert!(caches.0.account_cache.get(&addr2).is_some());
1648 }
1649
1650 #[test]
1651 fn test_insert_state_destroyed_uncached_account_keeps_size_zero() {
1652 let caches = ExecutionCache::new(1000);
1653 assert_eq!(caches.0.account_stats.size(), 0);
1654
1655 let addr = Address::random();
1656 let bundle = BundleState {
1657 state: HashMap::from_iter([(
1658 addr,
1659 BundleAccount::new(
1660 None, None, Default::default(),
1663 AccountStatus::Destroyed,
1664 ),
1665 )]),
1666 contracts: Default::default(),
1667 reverts: Default::default(),
1668 state_size: 0,
1669 reverts_size: 0,
1670 };
1671
1672 assert!(caches.insert_state(&bundle).is_ok());
1673 assert_eq!(caches.0.account_stats.size(), 0);
1674 assert!(caches.0.account_cache.get(&addr).is_none());
1675 }
1676
1677 #[test]
1678 fn test_code_cache_capacity_with_default_budget() {
1679 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);
1684
1685 assert_eq!(
1688 capacity, 16384,
1689 "code cache should have 16384 entries with default 4 GB budget"
1690 );
1691 }
1692}