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(
1055 &self,
1056 bundle_state: &reth_revm::db::BundleState,
1057 ) -> ProviderResult<HashedPostState> {
1058 self.state_provider.hashed_post_state(bundle_state)
1059 }
1060}
1061
1062#[derive(Debug, Clone)]
1073pub struct ExecutionCache(Arc<ExecutionCacheInner>);
1074
1075#[derive(Debug)]
1077struct ExecutionCacheInner {
1078 code_cache: FixedCache<B256, Option<Bytecode>, FbBuildHasher<32>>,
1080
1081 storage_cache: FixedCache<(Address, StorageKey), StorageValue>,
1083
1084 account_cache: FixedCache<Address, Option<Account>, FbBuildHasher<20>>,
1086
1087 code_stats: Arc<CacheStatsHandler>,
1089
1090 storage_stats: Arc<CacheStatsHandler>,
1092
1093 account_stats: Arc<CacheStatsHandler>,
1095
1096 selfdestruct_encountered: Once,
1098}
1099
1100impl ExecutionCache {
1101 const MIN_CACHE_SIZE_WITH_EPOCHS: usize = 1 << 12; pub const fn bytes_to_entries(size_bytes: usize, entry_size: usize) -> usize {
1110 let entries = size_bytes / entry_size;
1111 let rounded = if entries == 0 { 1 } else { (entries + 1).next_power_of_two() >> 1 };
1113 if rounded < Self::MIN_CACHE_SIZE_WITH_EPOCHS {
1115 Self::MIN_CACHE_SIZE_WITH_EPOCHS
1116 } else {
1117 rounded
1118 }
1119 }
1120
1121 pub fn new(total_cache_size: usize) -> Self {
1123 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);
1128 let storage_capacity = Self::bytes_to_entries(storage_cache_size, STORAGE_CACHE_ENTRY_SIZE);
1129 let account_capacity = Self::bytes_to_entries(account_cache_size, ACCOUNT_CACHE_ENTRY_SIZE);
1130
1131 let code_stats = Arc::new(CacheStatsHandler::new(code_capacity));
1132 let storage_stats = Arc::new(CacheStatsHandler::new(storage_capacity));
1133 let account_stats = Arc::new(CacheStatsHandler::new(account_capacity));
1134
1135 Self(Arc::new(ExecutionCacheInner {
1136 code_cache: FixedCache::new(code_capacity, FbBuildHasher::<32>::default())
1137 .with_stats(Some(Stats::new(code_stats.clone()))),
1138 storage_cache: FixedCache::new(storage_capacity, DefaultHashBuilder::default())
1139 .with_stats(Some(Stats::new(storage_stats.clone()))),
1140 account_cache: FixedCache::new(account_capacity, FbBuildHasher::<20>::default())
1141 .with_stats(Some(Stats::new(account_stats.clone()))),
1142 code_stats,
1143 storage_stats,
1144 account_stats,
1145 selfdestruct_encountered: Once::new(),
1146 }))
1147 }
1148
1149 fn usage_count(&self) -> usize {
1151 Arc::strong_count(&self.0)
1152 }
1153
1154 pub fn get_or_try_insert_code_with<E>(
1156 &self,
1157 hash: B256,
1158 f: impl FnOnce() -> Result<Option<Bytecode>, E>,
1159 ) -> Result<CachedStatus<Option<Bytecode>>, E> {
1160 let mut miss = false;
1161 let result = self.0.code_cache.get_or_try_insert_with(hash, |_| {
1162 miss = true;
1163 f()
1164 })?;
1165
1166 if miss {
1167 Ok(CachedStatus::NotCached(result))
1168 } else {
1169 Ok(CachedStatus::Cached(result))
1170 }
1171 }
1172
1173 pub fn get_or_try_insert_storage_with<E>(
1175 &self,
1176 address: Address,
1177 key: StorageKey,
1178 f: impl FnOnce() -> Result<StorageValue, E>,
1179 ) -> Result<CachedStatus<StorageValue>, E> {
1180 let mut miss = false;
1181 let result = self.0.storage_cache.get_or_try_insert_with((address, key), |_| {
1182 miss = true;
1183 f()
1184 })?;
1185
1186 if miss {
1187 Ok(CachedStatus::NotCached(result))
1188 } else {
1189 Ok(CachedStatus::Cached(result))
1190 }
1191 }
1192
1193 pub fn get_or_try_insert_account_with<E>(
1195 &self,
1196 address: Address,
1197 f: impl FnOnce() -> Result<Option<Account>, E>,
1198 ) -> Result<CachedStatus<Option<Account>>, E> {
1199 let mut miss = false;
1200 let result = self.0.account_cache.get_or_try_insert_with(address, |_| {
1201 miss = true;
1202 f()
1203 })?;
1204
1205 if miss {
1206 Ok(CachedStatus::NotCached(result))
1207 } else {
1208 Ok(CachedStatus::Cached(result))
1209 }
1210 }
1211
1212 pub fn insert_storage(&self, address: Address, key: StorageKey, value: Option<StorageValue>) {
1214 self.0.storage_cache.insert((address, key), value.unwrap_or_default());
1215 }
1216
1217 pub fn insert_code(&self, hash: B256, code: Option<Bytecode>) {
1219 self.0.code_cache.insert(hash, code);
1220 }
1221
1222 pub fn insert_account(&self, address: Address, account: Option<Account>) {
1224 self.0.account_cache.insert(address, account);
1225 }
1226
1227 #[instrument(level = "debug", target = "engine::caching", skip_all)]
1246 #[expect(clippy::result_unit_err)]
1247 pub fn insert_state(&self, state_updates: &BundleState) -> Result<(), ()> {
1248 let _enter =
1249 debug_span!(target: "engine::tree", "contracts", len = state_updates.contracts.len())
1250 .entered();
1251 for (code_hash, bytecode) in &state_updates.contracts {
1253 self.insert_code(*code_hash, Some(Bytecode(bytecode.clone())));
1254 }
1255 drop(_enter);
1256
1257 let _enter = debug_span!(
1258 target: "engine::tree",
1259 "accounts",
1260 accounts = state_updates.state.len(),
1261 storages =
1262 state_updates.state.values().map(|account| account.storage.len()).sum::<usize>()
1263 )
1264 .entered();
1265 for (addr, account) in &state_updates.state {
1266 if account.status.is_not_modified() {
1269 continue
1270 }
1271
1272 if account.was_destroyed() {
1279 let had_code =
1280 account.original_info.as_ref().is_some_and(|info| !info.is_empty_code_hash());
1281 if had_code {
1282 self.0.selfdestruct_encountered.call_once(|| {
1283 warn!(
1284 target: "engine::caching",
1285 address = ?addr,
1286 info = ?account.info,
1287 original_info = ?account.original_info,
1288 "Encountered an inter-transaction SELFDESTRUCT that reset the storage cache. Are you running a pre-Dencun network?"
1289 );
1290 });
1291 self.clear();
1292 return Ok(())
1293 }
1294
1295 self.0.account_cache.remove(addr);
1296 continue;
1297 }
1298
1299 let Some(ref account_info) = account.info else {
1303 trace!(target: "engine::caching", ?account, "Account with None account info found in state updates");
1304 return Err(())
1305 };
1306
1307 for (key, slot) in &account.storage {
1309 self.insert_storage(*addr, (*key).into(), Some(slot.present_value));
1310 }
1311
1312 self.insert_account(*addr, Some(Account::from(account_info)));
1315 }
1316
1317 Ok(())
1318 }
1319
1320 pub fn clear(&self) {
1325 self.0.storage_cache.clear();
1326 self.0.account_cache.clear();
1327
1328 self.0.storage_stats.reset_size();
1329 self.0.account_stats.reset_size();
1330 }
1331
1332 pub fn update_metrics(&self, metrics: &CachedStateCacheMetrics) {
1335 metrics.code_cache_size.set(self.0.code_stats.size() as f64);
1336 metrics.code_cache_capacity.set(self.0.code_stats.capacity() as f64);
1337 metrics.code_cache_collisions.set(self.0.code_stats.collisions() as f64);
1338 self.0.code_stats.reset_stats();
1339
1340 metrics.storage_cache_size.set(self.0.storage_stats.size() as f64);
1341 metrics.storage_cache_capacity.set(self.0.storage_stats.capacity() as f64);
1342 metrics.storage_cache_collisions.set(self.0.storage_stats.collisions() as f64);
1343 self.0.storage_stats.reset_stats();
1344
1345 metrics.account_cache_size.set(self.0.account_stats.size() as f64);
1346 metrics.account_cache_capacity.set(self.0.account_stats.capacity() as f64);
1347 metrics.account_cache_collisions.set(self.0.account_stats.collisions() as f64);
1348 self.0.account_stats.reset_stats();
1349 }
1350}
1351
1352#[derive(Debug, Clone)]
1355pub struct SavedCache {
1356 hash: B256,
1358
1359 caches: ExecutionCache,
1361}
1362
1363impl SavedCache {
1364 pub const fn new(hash: B256, caches: ExecutionCache) -> Self {
1366 Self { hash, caches }
1367 }
1368
1369 pub const fn executed_block_hash(&self) -> B256 {
1371 self.hash
1372 }
1373
1374 pub fn is_available(&self) -> bool {
1376 self.caches.usage_count() == 1
1377 }
1378
1379 pub fn usage_count(&self) -> usize {
1381 self.caches.usage_count()
1382 }
1383
1384 pub const fn cache(&self) -> &ExecutionCache {
1386 &self.caches
1387 }
1388
1389 pub fn update_metrics(&self, metrics: Option<&CachedStateCacheMetrics>) {
1391 if let Some(metrics) = metrics {
1392 self.caches.update_metrics(metrics);
1393 }
1394 }
1395
1396 pub fn clear_with_hash(&mut self, hash: B256) {
1399 self.hash = hash;
1400 self.caches.clear();
1401 }
1402}
1403
1404#[cfg(any(test, feature = "test-utils"))]
1405impl SavedCache {
1406 pub fn clone_guard_for_test(&self) -> ExecutionCache {
1408 self.caches.clone()
1409 }
1410}
1411
1412#[cfg(test)]
1413mod tests {
1414 use super::*;
1415 use alloy_primitives::{map::HashMap, U256};
1416 use reth_provider::test_utils::{ExtendedAccount, MockEthProvider};
1417 use reth_revm::db::{AccountStatus, BundleAccount};
1418 use revm::state::AccountInfo;
1419
1420 #[test]
1421 fn test_empty_storage_cached_state_provider() {
1422 let address = Address::random();
1423 let storage_key = StorageKey::random();
1424 let account = ExtendedAccount::new(0, U256::ZERO);
1425
1426 let provider = MockEthProvider::default();
1427 provider.extend_accounts(vec![(address, account)]);
1428
1429 let caches = ExecutionCache::new(1000);
1430 let state_provider = CachedStateProvider::new(
1431 provider,
1432 caches,
1433 Some(CachedStateMetrics::zeroed(CachedStateMetricsSource::Test)),
1434 );
1435
1436 let res = state_provider.storage(address, storage_key);
1437 assert!(res.is_ok());
1438 assert_eq!(res.unwrap(), None);
1439 }
1440
1441 #[test]
1442 fn test_uncached_storage_cached_state_provider() {
1443 let address = Address::random();
1444 let storage_key = StorageKey::random();
1445 let storage_value = U256::from(1);
1446 let account =
1447 ExtendedAccount::new(0, U256::ZERO).extend_storage(vec![(storage_key, storage_value)]);
1448
1449 let provider = MockEthProvider::default();
1450 provider.extend_accounts(vec![(address, account)]);
1451
1452 let caches = ExecutionCache::new(1000);
1453 let state_provider = CachedStateProvider::new(
1454 provider,
1455 caches,
1456 Some(CachedStateMetrics::zeroed(CachedStateMetricsSource::Test)),
1457 );
1458
1459 let res = state_provider.storage(address, storage_key);
1460 assert!(res.is_ok());
1461 assert_eq!(res.unwrap(), Some(storage_value));
1462 }
1463
1464 #[test]
1465 fn test_get_storage_populated() {
1466 let address = Address::random();
1467 let storage_key = StorageKey::random();
1468 let storage_value = U256::from(1);
1469
1470 let caches = ExecutionCache::new(1000);
1471 caches.insert_storage(address, storage_key, Some(storage_value));
1472
1473 let result = caches
1474 .get_or_try_insert_storage_with(address, storage_key, || Ok::<_, ()>(U256::from(999)));
1475 assert_eq!(result.unwrap(), CachedStatus::Cached(storage_value));
1476 }
1477
1478 #[test]
1479 fn test_get_storage_empty() {
1480 let address = Address::random();
1481 let storage_key = StorageKey::random();
1482
1483 let caches = ExecutionCache::new(1000);
1484 caches.insert_storage(address, storage_key, None);
1485
1486 let result = caches
1487 .get_or_try_insert_storage_with(address, storage_key, || Ok::<_, ()>(U256::from(999)));
1488 assert_eq!(result.unwrap(), CachedStatus::Cached(U256::ZERO));
1489 }
1490
1491 #[test]
1492 fn test_saved_cache_is_available() {
1493 let execution_cache = ExecutionCache::new(1000);
1494 let cache = SavedCache::new(B256::ZERO, execution_cache);
1495
1496 assert!(cache.is_available(), "Cache should be available initially");
1497
1498 let _cache = cache.clone_guard_for_test();
1499
1500 assert!(!cache.is_available(), "Cache should not be available with active handle");
1501 }
1502
1503 #[test]
1504 fn test_saved_cache_multiple_references() {
1505 let execution_cache = ExecutionCache::new(1000);
1506 let cache = SavedCache::new(B256::from([2u8; 32]), execution_cache);
1507
1508 let cache1 = cache.clone_guard_for_test();
1509 let cache2 = cache.clone_guard_for_test();
1510 let cache3 = cache1.clone();
1511
1512 assert!(!cache.is_available());
1513
1514 drop(cache1);
1515 assert!(!cache.is_available());
1516
1517 drop(cache2);
1518 assert!(!cache.is_available());
1519
1520 drop(cache3);
1521 assert!(cache.is_available());
1522 }
1523
1524 #[test]
1525 fn test_insert_state_destroyed_account_with_code_clears_cache() {
1526 let caches = ExecutionCache::new(1000);
1527
1528 let addr1 = Address::random();
1530 let addr2 = Address::random();
1531 let storage_key = StorageKey::random();
1532 caches.insert_account(addr1, Some(Account::default()));
1533 caches.insert_account(addr2, Some(Account::default()));
1534 caches.insert_storage(addr1, storage_key, Some(U256::from(42)));
1535
1536 assert!(caches.0.account_cache.get(&addr1).is_some());
1538 assert!(caches.0.account_cache.get(&addr2).is_some());
1539 assert!(caches.0.storage_cache.get(&(addr1, storage_key)).is_some());
1540
1541 let bundle = BundleState {
1542 state: HashMap::from_iter([(
1544 Address::random(),
1545 BundleAccount::new(
1546 Some(AccountInfo {
1547 balance: U256::ZERO,
1548 nonce: 1,
1549 code_hash: B256::random(), code: None,
1551 account_id: None,
1552 }),
1553 None, Default::default(),
1555 AccountStatus::Destroyed,
1556 ),
1557 )]),
1558 contracts: Default::default(),
1559 reverts: Default::default(),
1560 state_size: 0,
1561 reverts_size: 0,
1562 };
1563
1564 let result = caches.insert_state(&bundle);
1566 assert!(result.is_ok());
1567
1568 assert!(caches.0.account_cache.get(&addr1).is_none());
1570 assert!(caches.0.account_cache.get(&addr2).is_none());
1571 assert!(caches.0.storage_cache.get(&(addr1, storage_key)).is_none());
1572 }
1573
1574 #[test]
1575 fn test_insert_state_destroyed_account_without_code_removes_only_account() {
1576 let caches = ExecutionCache::new(1000);
1577
1578 let addr1 = Address::random();
1580 let addr2 = Address::random();
1581 let storage_key = StorageKey::random();
1582 caches.insert_account(addr1, Some(Account::default()));
1583 caches.insert_account(addr2, Some(Account::default()));
1584 caches.insert_storage(addr1, storage_key, Some(U256::from(42)));
1585
1586 let bundle = BundleState {
1587 state: HashMap::from_iter([(
1589 addr1,
1590 BundleAccount::new(
1591 Some(AccountInfo {
1592 balance: U256::from(100),
1593 nonce: 1,
1594 code_hash: alloy_primitives::KECCAK256_EMPTY, code: None,
1596 account_id: None,
1597 }),
1598 None, Default::default(),
1600 AccountStatus::Destroyed,
1601 ),
1602 )]),
1603 contracts: Default::default(),
1604 reverts: Default::default(),
1605 state_size: 0,
1606 reverts_size: 0,
1607 };
1608
1609 assert!(caches.insert_state(&bundle).is_ok());
1611
1612 assert!(caches.0.account_cache.get(&addr1).is_none());
1614 assert!(caches.0.account_cache.get(&addr2).is_some());
1615 assert!(caches.0.storage_cache.get(&(addr1, storage_key)).is_some());
1616 }
1617
1618 #[test]
1619 fn test_insert_state_destroyed_account_no_original_info_removes_only_account() {
1620 let caches = ExecutionCache::new(1000);
1621
1622 let addr1 = Address::random();
1624 let addr2 = Address::random();
1625 caches.insert_account(addr1, Some(Account::default()));
1626 caches.insert_account(addr2, Some(Account::default()));
1627
1628 let bundle = BundleState {
1629 state: HashMap::from_iter([(
1631 addr1,
1632 BundleAccount::new(
1633 None, None, Default::default(),
1636 AccountStatus::Destroyed,
1637 ),
1638 )]),
1639 contracts: Default::default(),
1640 reverts: Default::default(),
1641 state_size: 0,
1642 reverts_size: 0,
1643 };
1644
1645 assert!(caches.insert_state(&bundle).is_ok());
1647
1648 assert!(caches.0.account_cache.get(&addr1).is_none());
1650 assert!(caches.0.account_cache.get(&addr2).is_some());
1651 }
1652
1653 #[test]
1654 fn test_insert_state_destroyed_uncached_account_keeps_size_zero() {
1655 let caches = ExecutionCache::new(1000);
1656 assert_eq!(caches.0.account_stats.size(), 0);
1657
1658 let addr = Address::random();
1659 let bundle = BundleState {
1660 state: HashMap::from_iter([(
1661 addr,
1662 BundleAccount::new(
1663 None, None, Default::default(),
1666 AccountStatus::Destroyed,
1667 ),
1668 )]),
1669 contracts: Default::default(),
1670 reverts: Default::default(),
1671 state_size: 0,
1672 reverts_size: 0,
1673 };
1674
1675 assert!(caches.insert_state(&bundle).is_ok());
1676 assert_eq!(caches.0.account_stats.size(), 0);
1677 assert!(caches.0.account_cache.get(&addr).is_none());
1678 }
1679
1680 #[test]
1681 fn test_code_cache_capacity_with_default_budget() {
1682 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);
1687
1688 assert_eq!(
1691 capacity, 16384,
1692 "code cache should have 16384 entries with default 4 GB budget"
1693 );
1694 }
1695}