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 multiproof_v2(
1002 &self,
1003 input: TrieInput,
1004 targets: reth_trie::MultiProofTargetsV2,
1005 ) -> ProviderResult<reth_trie::DecodedMultiProofV2> {
1006 self.state_provider.multiproof_v2(input, targets)
1007 }
1008
1009 fn witness(
1010 &self,
1011 input: TrieInput,
1012 target: HashedPostState,
1013 mode: reth_trie::ExecutionWitnessMode,
1014 ) -> ProviderResult<Vec<alloy_primitives::Bytes>> {
1015 self.state_provider.witness(input, target, mode)
1016 }
1017}
1018
1019impl<S: StorageRootProvider> StorageRootProvider for CachedStateProvider<S> {
1020 fn storage_root(
1021 &self,
1022 address: Address,
1023 hashed_storage: HashedStorage,
1024 ) -> ProviderResult<B256> {
1025 self.state_provider.storage_root(address, hashed_storage)
1026 }
1027
1028 fn storage_proof(
1029 &self,
1030 address: Address,
1031 slot: B256,
1032 hashed_storage: HashedStorage,
1033 ) -> ProviderResult<StorageProof> {
1034 self.state_provider.storage_proof(address, slot, hashed_storage)
1035 }
1036
1037 fn storage_multiproof(
1038 &self,
1039 address: Address,
1040 slots: &[B256],
1041 hashed_storage: HashedStorage,
1042 ) -> ProviderResult<StorageMultiProof> {
1043 self.state_provider.storage_multiproof(address, slots, hashed_storage)
1044 }
1045}
1046
1047impl<S: BlockHashReader> BlockHashReader for CachedStateProvider<S> {
1048 fn block_hash(&self, number: alloy_primitives::BlockNumber) -> ProviderResult<Option<B256>> {
1049 self.state_provider.block_hash(number)
1050 }
1051
1052 fn canonical_hashes_range(
1053 &self,
1054 start: alloy_primitives::BlockNumber,
1055 end: alloy_primitives::BlockNumber,
1056 ) -> ProviderResult<Vec<B256>> {
1057 self.state_provider.canonical_hashes_range(start, end)
1058 }
1059}
1060
1061impl<S: HashedPostStateProvider> HashedPostStateProvider for CachedStateProvider<S> {
1062 fn hashed_post_state(
1063 &self,
1064 bundle_state: &reth_revm::db::BundleState,
1065 ) -> ProviderResult<HashedPostState> {
1066 self.state_provider.hashed_post_state(bundle_state)
1067 }
1068}
1069
1070#[derive(Debug, Clone)]
1081pub struct ExecutionCache(Arc<ExecutionCacheInner>);
1082
1083#[derive(Debug)]
1085struct ExecutionCacheInner {
1086 code_cache: FixedCache<B256, Option<Bytecode>, FbBuildHasher<32>>,
1088
1089 storage_cache: FixedCache<(Address, StorageKey), StorageValue>,
1091
1092 account_cache: FixedCache<Address, Option<Account>, FbBuildHasher<20>>,
1094
1095 code_stats: Arc<CacheStatsHandler>,
1097
1098 storage_stats: Arc<CacheStatsHandler>,
1100
1101 account_stats: Arc<CacheStatsHandler>,
1103
1104 selfdestruct_encountered: Once,
1106}
1107
1108impl ExecutionCache {
1109 const MIN_CACHE_SIZE_WITH_EPOCHS: usize = 1 << 12; pub const fn bytes_to_entries(size_bytes: usize, entry_size: usize) -> usize {
1118 let entries = size_bytes / entry_size;
1119 let rounded = if entries == 0 { 1 } else { (entries + 1).next_power_of_two() >> 1 };
1121 if rounded < Self::MIN_CACHE_SIZE_WITH_EPOCHS {
1123 Self::MIN_CACHE_SIZE_WITH_EPOCHS
1124 } else {
1125 rounded
1126 }
1127 }
1128
1129 pub fn new(total_cache_size: usize) -> Self {
1131 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);
1136 let storage_capacity = Self::bytes_to_entries(storage_cache_size, STORAGE_CACHE_ENTRY_SIZE);
1137 let account_capacity = Self::bytes_to_entries(account_cache_size, ACCOUNT_CACHE_ENTRY_SIZE);
1138
1139 let code_stats = Arc::new(CacheStatsHandler::new(code_capacity));
1140 let storage_stats = Arc::new(CacheStatsHandler::new(storage_capacity));
1141 let account_stats = Arc::new(CacheStatsHandler::new(account_capacity));
1142
1143 Self(Arc::new(ExecutionCacheInner {
1144 code_cache: FixedCache::new(code_capacity, FbBuildHasher::<32>::default())
1145 .with_stats(Some(Stats::new(code_stats.clone()))),
1146 storage_cache: FixedCache::new(storage_capacity, DefaultHashBuilder::default())
1147 .with_stats(Some(Stats::new(storage_stats.clone()))),
1148 account_cache: FixedCache::new(account_capacity, FbBuildHasher::<20>::default())
1149 .with_stats(Some(Stats::new(account_stats.clone()))),
1150 code_stats,
1151 storage_stats,
1152 account_stats,
1153 selfdestruct_encountered: Once::new(),
1154 }))
1155 }
1156
1157 fn usage_count(&self) -> usize {
1159 Arc::strong_count(&self.0)
1160 }
1161
1162 pub fn get_or_try_insert_code_with<E>(
1164 &self,
1165 hash: B256,
1166 f: impl FnOnce() -> Result<Option<Bytecode>, E>,
1167 ) -> Result<CachedStatus<Option<Bytecode>>, E> {
1168 let mut miss = false;
1169 let result = self.0.code_cache.get_or_try_insert_with(hash, |_| {
1170 miss = true;
1171 f()
1172 })?;
1173
1174 if miss {
1175 Ok(CachedStatus::NotCached(result))
1176 } else {
1177 Ok(CachedStatus::Cached(result))
1178 }
1179 }
1180
1181 pub fn get_or_try_insert_storage_with<E>(
1183 &self,
1184 address: Address,
1185 key: StorageKey,
1186 f: impl FnOnce() -> Result<StorageValue, E>,
1187 ) -> Result<CachedStatus<StorageValue>, E> {
1188 let mut miss = false;
1189 let result = self.0.storage_cache.get_or_try_insert_with((address, key), |_| {
1190 miss = true;
1191 f()
1192 })?;
1193
1194 if miss {
1195 Ok(CachedStatus::NotCached(result))
1196 } else {
1197 Ok(CachedStatus::Cached(result))
1198 }
1199 }
1200
1201 pub fn get_or_try_insert_account_with<E>(
1203 &self,
1204 address: Address,
1205 f: impl FnOnce() -> Result<Option<Account>, E>,
1206 ) -> Result<CachedStatus<Option<Account>>, E> {
1207 let mut miss = false;
1208 let result = self.0.account_cache.get_or_try_insert_with(address, |_| {
1209 miss = true;
1210 f()
1211 })?;
1212
1213 if miss {
1214 Ok(CachedStatus::NotCached(result))
1215 } else {
1216 Ok(CachedStatus::Cached(result))
1217 }
1218 }
1219
1220 pub fn insert_storage(&self, address: Address, key: StorageKey, value: Option<StorageValue>) {
1222 self.0.storage_cache.insert((address, key), value.unwrap_or_default());
1223 }
1224
1225 pub fn insert_code(&self, hash: B256, code: Option<Bytecode>) {
1227 self.0.code_cache.insert(hash, code);
1228 }
1229
1230 pub fn insert_account(&self, address: Address, account: Option<Account>) {
1232 self.0.account_cache.insert(address, account);
1233 }
1234
1235 #[instrument(level = "debug", target = "engine::caching", skip_all)]
1254 #[expect(clippy::result_unit_err)]
1255 pub fn insert_state(&self, state_updates: &BundleState) -> Result<(), ()> {
1256 let _enter =
1257 debug_span!(target: "engine::tree", "contracts", len = state_updates.contracts.len())
1258 .entered();
1259 for (code_hash, bytecode) in &state_updates.contracts {
1261 self.insert_code(*code_hash, Some(Bytecode(bytecode.clone())));
1262 }
1263 drop(_enter);
1264
1265 let _enter = debug_span!(
1266 target: "engine::tree",
1267 "accounts",
1268 accounts = state_updates.state.len(),
1269 storages =
1270 state_updates.state.values().map(|account| account.storage.len()).sum::<usize>()
1271 )
1272 .entered();
1273 for (addr, account) in &state_updates.state {
1274 if account.status.is_not_modified() {
1277 continue
1278 }
1279
1280 if account.was_destroyed() {
1287 let had_code =
1288 account.original_info.as_ref().is_some_and(|info| !info.is_empty_code_hash());
1289 if had_code {
1290 self.0.selfdestruct_encountered.call_once(|| {
1291 warn!(
1292 target: "engine::caching",
1293 address = ?addr,
1294 info = ?account.info,
1295 original_info = ?account.original_info,
1296 "Encountered an inter-transaction SELFDESTRUCT that reset the storage cache. Are you running a pre-Dencun network?"
1297 );
1298 });
1299 self.clear();
1300 return Ok(())
1301 }
1302
1303 self.0.account_cache.remove(addr);
1304 continue;
1305 }
1306
1307 let Some(ref account_info) = account.info else {
1311 trace!(target: "engine::caching", ?account, "Account with None account info found in state updates");
1312 return Err(())
1313 };
1314
1315 for (key, slot) in &account.storage {
1317 self.insert_storage(*addr, (*key).into(), Some(slot.present_value));
1318 }
1319
1320 self.insert_account(*addr, Some(Account::from(account_info)));
1323 }
1324
1325 Ok(())
1326 }
1327
1328 pub fn clear(&self) {
1333 self.0.storage_cache.clear();
1334 self.0.account_cache.clear();
1335
1336 self.0.storage_stats.reset_size();
1337 self.0.account_stats.reset_size();
1338 }
1339
1340 pub fn update_metrics(&self, metrics: &CachedStateCacheMetrics) {
1343 metrics.code_cache_size.set(self.0.code_stats.size() as f64);
1344 metrics.code_cache_capacity.set(self.0.code_stats.capacity() as f64);
1345 metrics.code_cache_collisions.set(self.0.code_stats.collisions() as f64);
1346 self.0.code_stats.reset_stats();
1347
1348 metrics.storage_cache_size.set(self.0.storage_stats.size() as f64);
1349 metrics.storage_cache_capacity.set(self.0.storage_stats.capacity() as f64);
1350 metrics.storage_cache_collisions.set(self.0.storage_stats.collisions() as f64);
1351 self.0.storage_stats.reset_stats();
1352
1353 metrics.account_cache_size.set(self.0.account_stats.size() as f64);
1354 metrics.account_cache_capacity.set(self.0.account_stats.capacity() as f64);
1355 metrics.account_cache_collisions.set(self.0.account_stats.collisions() as f64);
1356 self.0.account_stats.reset_stats();
1357 }
1358}
1359
1360#[derive(Debug, Clone)]
1363pub struct SavedCache {
1364 hash: B256,
1366
1367 caches: ExecutionCache,
1369}
1370
1371impl SavedCache {
1372 pub const fn new(hash: B256, caches: ExecutionCache) -> Self {
1374 Self { hash, caches }
1375 }
1376
1377 pub const fn executed_block_hash(&self) -> B256 {
1379 self.hash
1380 }
1381
1382 pub fn is_available(&self) -> bool {
1384 self.caches.usage_count() == 1
1385 }
1386
1387 pub fn usage_count(&self) -> usize {
1389 self.caches.usage_count()
1390 }
1391
1392 pub const fn cache(&self) -> &ExecutionCache {
1394 &self.caches
1395 }
1396
1397 pub fn update_metrics(&self, metrics: Option<&CachedStateCacheMetrics>) {
1399 if let Some(metrics) = metrics {
1400 self.caches.update_metrics(metrics);
1401 }
1402 }
1403
1404 pub fn clear_with_hash(&mut self, hash: B256) {
1407 self.hash = hash;
1408 self.caches.clear();
1409 }
1410}
1411
1412#[cfg(any(test, feature = "test-utils"))]
1413impl SavedCache {
1414 pub fn clone_guard_for_test(&self) -> ExecutionCache {
1416 self.caches.clone()
1417 }
1418}
1419
1420#[cfg(test)]
1421mod tests {
1422 use super::*;
1423 use alloy_primitives::{map::HashMap, U256};
1424 use reth_provider::test_utils::{ExtendedAccount, MockEthProvider};
1425 use reth_revm::db::{AccountStatus, BundleAccount};
1426 use revm::state::AccountInfo;
1427
1428 #[test]
1429 fn test_empty_storage_cached_state_provider() {
1430 let address = Address::random();
1431 let storage_key = StorageKey::random();
1432 let account = ExtendedAccount::new(0, U256::ZERO);
1433
1434 let provider = MockEthProvider::default();
1435 provider.extend_accounts(vec![(address, account)]);
1436
1437 let caches = ExecutionCache::new(1000);
1438 let state_provider = CachedStateProvider::new(
1439 provider,
1440 caches,
1441 Some(CachedStateMetrics::zeroed(CachedStateMetricsSource::Test)),
1442 );
1443
1444 let res = state_provider.storage(address, storage_key);
1445 assert!(res.is_ok());
1446 assert_eq!(res.unwrap(), None);
1447 }
1448
1449 #[test]
1450 fn test_uncached_storage_cached_state_provider() {
1451 let address = Address::random();
1452 let storage_key = StorageKey::random();
1453 let storage_value = U256::from(1);
1454 let account =
1455 ExtendedAccount::new(0, U256::ZERO).extend_storage(vec![(storage_key, storage_value)]);
1456
1457 let provider = MockEthProvider::default();
1458 provider.extend_accounts(vec![(address, account)]);
1459
1460 let caches = ExecutionCache::new(1000);
1461 let state_provider = CachedStateProvider::new(
1462 provider,
1463 caches,
1464 Some(CachedStateMetrics::zeroed(CachedStateMetricsSource::Test)),
1465 );
1466
1467 let res = state_provider.storage(address, storage_key);
1468 assert!(res.is_ok());
1469 assert_eq!(res.unwrap(), Some(storage_value));
1470 }
1471
1472 #[test]
1473 fn test_get_storage_populated() {
1474 let address = Address::random();
1475 let storage_key = StorageKey::random();
1476 let storage_value = U256::from(1);
1477
1478 let caches = ExecutionCache::new(1000);
1479 caches.insert_storage(address, storage_key, Some(storage_value));
1480
1481 let result = caches
1482 .get_or_try_insert_storage_with(address, storage_key, || Ok::<_, ()>(U256::from(999)));
1483 assert_eq!(result.unwrap(), CachedStatus::Cached(storage_value));
1484 }
1485
1486 #[test]
1487 fn test_get_storage_empty() {
1488 let address = Address::random();
1489 let storage_key = StorageKey::random();
1490
1491 let caches = ExecutionCache::new(1000);
1492 caches.insert_storage(address, storage_key, None);
1493
1494 let result = caches
1495 .get_or_try_insert_storage_with(address, storage_key, || Ok::<_, ()>(U256::from(999)));
1496 assert_eq!(result.unwrap(), CachedStatus::Cached(U256::ZERO));
1497 }
1498
1499 #[test]
1500 fn test_saved_cache_is_available() {
1501 let execution_cache = ExecutionCache::new(1000);
1502 let cache = SavedCache::new(B256::ZERO, execution_cache);
1503
1504 assert!(cache.is_available(), "Cache should be available initially");
1505
1506 let _cache = cache.clone_guard_for_test();
1507
1508 assert!(!cache.is_available(), "Cache should not be available with active handle");
1509 }
1510
1511 #[test]
1512 fn test_saved_cache_multiple_references() {
1513 let execution_cache = ExecutionCache::new(1000);
1514 let cache = SavedCache::new(B256::from([2u8; 32]), execution_cache);
1515
1516 let cache1 = cache.clone_guard_for_test();
1517 let cache2 = cache.clone_guard_for_test();
1518 let cache3 = cache1.clone();
1519
1520 assert!(!cache.is_available());
1521
1522 drop(cache1);
1523 assert!(!cache.is_available());
1524
1525 drop(cache2);
1526 assert!(!cache.is_available());
1527
1528 drop(cache3);
1529 assert!(cache.is_available());
1530 }
1531
1532 #[test]
1533 fn test_insert_state_destroyed_account_with_code_clears_cache() {
1534 let caches = ExecutionCache::new(1000);
1535
1536 let addr1 = Address::random();
1538 let addr2 = Address::random();
1539 let storage_key = StorageKey::random();
1540 caches.insert_account(addr1, Some(Account::default()));
1541 caches.insert_account(addr2, Some(Account::default()));
1542 caches.insert_storage(addr1, storage_key, Some(U256::from(42)));
1543
1544 assert!(caches.0.account_cache.get(&addr1).is_some());
1546 assert!(caches.0.account_cache.get(&addr2).is_some());
1547 assert!(caches.0.storage_cache.get(&(addr1, storage_key)).is_some());
1548
1549 let bundle = BundleState {
1550 state: HashMap::from_iter([(
1552 Address::random(),
1553 BundleAccount::new(
1554 Some(AccountInfo {
1555 balance: U256::ZERO,
1556 nonce: 1,
1557 code_hash: B256::random(), code: None,
1559 account_id: None,
1560 }),
1561 None, Default::default(),
1563 AccountStatus::Destroyed,
1564 ),
1565 )]),
1566 contracts: Default::default(),
1567 reverts: Default::default(),
1568 state_size: 0,
1569 reverts_size: 0,
1570 };
1571
1572 let result = caches.insert_state(&bundle);
1574 assert!(result.is_ok());
1575
1576 assert!(caches.0.account_cache.get(&addr1).is_none());
1578 assert!(caches.0.account_cache.get(&addr2).is_none());
1579 assert!(caches.0.storage_cache.get(&(addr1, storage_key)).is_none());
1580 }
1581
1582 #[test]
1583 fn test_insert_state_destroyed_account_without_code_removes_only_account() {
1584 let caches = ExecutionCache::new(1000);
1585
1586 let addr1 = Address::random();
1588 let addr2 = Address::random();
1589 let storage_key = StorageKey::random();
1590 caches.insert_account(addr1, Some(Account::default()));
1591 caches.insert_account(addr2, Some(Account::default()));
1592 caches.insert_storage(addr1, storage_key, Some(U256::from(42)));
1593
1594 let bundle = BundleState {
1595 state: HashMap::from_iter([(
1597 addr1,
1598 BundleAccount::new(
1599 Some(AccountInfo {
1600 balance: U256::from(100),
1601 nonce: 1,
1602 code_hash: alloy_primitives::KECCAK256_EMPTY, code: None,
1604 account_id: None,
1605 }),
1606 None, Default::default(),
1608 AccountStatus::Destroyed,
1609 ),
1610 )]),
1611 contracts: Default::default(),
1612 reverts: Default::default(),
1613 state_size: 0,
1614 reverts_size: 0,
1615 };
1616
1617 assert!(caches.insert_state(&bundle).is_ok());
1619
1620 assert!(caches.0.account_cache.get(&addr1).is_none());
1622 assert!(caches.0.account_cache.get(&addr2).is_some());
1623 assert!(caches.0.storage_cache.get(&(addr1, storage_key)).is_some());
1624 }
1625
1626 #[test]
1627 fn test_insert_state_destroyed_account_no_original_info_removes_only_account() {
1628 let caches = ExecutionCache::new(1000);
1629
1630 let addr1 = Address::random();
1632 let addr2 = Address::random();
1633 caches.insert_account(addr1, Some(Account::default()));
1634 caches.insert_account(addr2, Some(Account::default()));
1635
1636 let bundle = BundleState {
1637 state: HashMap::from_iter([(
1639 addr1,
1640 BundleAccount::new(
1641 None, None, Default::default(),
1644 AccountStatus::Destroyed,
1645 ),
1646 )]),
1647 contracts: Default::default(),
1648 reverts: Default::default(),
1649 state_size: 0,
1650 reverts_size: 0,
1651 };
1652
1653 assert!(caches.insert_state(&bundle).is_ok());
1655
1656 assert!(caches.0.account_cache.get(&addr1).is_none());
1658 assert!(caches.0.account_cache.get(&addr2).is_some());
1659 }
1660
1661 #[test]
1662 fn test_insert_state_destroyed_uncached_account_keeps_size_zero() {
1663 let caches = ExecutionCache::new(1000);
1664 assert_eq!(caches.0.account_stats.size(), 0);
1665
1666 let addr = Address::random();
1667 let bundle = BundleState {
1668 state: HashMap::from_iter([(
1669 addr,
1670 BundleAccount::new(
1671 None, None, Default::default(),
1674 AccountStatus::Destroyed,
1675 ),
1676 )]),
1677 contracts: Default::default(),
1678 reverts: Default::default(),
1679 state_size: 0,
1680 reverts_size: 0,
1681 };
1682
1683 assert!(caches.insert_state(&bundle).is_ok());
1684 assert_eq!(caches.0.account_stats.size(), 0);
1685 assert!(caches.0.account_cache.get(&addr).is_none());
1686 }
1687
1688 #[test]
1689 fn test_code_cache_capacity_with_default_budget() {
1690 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);
1695
1696 assert_eq!(
1699 capacity, 16384,
1700 "code cache should have 16384 entries with default 4 GB budget"
1701 );
1702 }
1703}