1use alloy_primitives::{Address, StorageKey, StorageValue, B256};
3use metrics::Gauge;
4use mini_moka::sync::CacheBuilder;
5use reth_errors::ProviderResult;
6use reth_metrics::Metrics;
7use reth_primitives_traits::{Account, Bytecode};
8use reth_provider::{
9 AccountReader, BlockHashReader, BytecodeReader, HashedPostStateProvider, StateProofProvider,
10 StateProvider, StateRootProvider, StorageRootProvider,
11};
12use reth_revm::db::BundleState;
13use reth_trie::{
14 updates::TrieUpdates, AccountProof, HashedPostState, HashedStorage, MultiProof,
15 MultiProofTargets, StorageMultiProof, StorageProof, TrieInput,
16};
17use revm_primitives::map::DefaultHashBuilder;
18use std::{sync::Arc, time::Duration};
19use tracing::{debug_span, instrument, trace};
20
21pub(crate) type Cache<K, V> =
22 mini_moka::sync::Cache<K, V, alloy_primitives::map::DefaultHashBuilder>;
23
24pub(crate) struct CachedStateProvider<S> {
26 state_provider: S,
28
29 caches: ExecutionCache,
31
32 metrics: CachedStateMetrics,
34
35 prewarm: bool,
37}
38
39impl<S> CachedStateProvider<S>
40where
41 S: StateProvider,
42{
43 pub(crate) const fn new(
46 state_provider: S,
47 caches: ExecutionCache,
48 metrics: CachedStateMetrics,
49 ) -> Self {
50 Self { state_provider, caches, metrics, prewarm: false }
51 }
52}
53
54impl<S> CachedStateProvider<S> {
55 pub(crate) const fn prewarm(mut self) -> Self {
64 self.prewarm = true;
65 self
66 }
67
68 const fn is_prewarm(&self) -> bool {
70 self.prewarm
71 }
72}
73
74#[derive(Metrics, Clone)]
76#[metrics(scope = "sync.caching")]
77pub(crate) struct CachedStateMetrics {
78 code_cache_hits: Gauge,
80
81 code_cache_misses: Gauge,
83
84 code_cache_size: Gauge,
89
90 storage_cache_hits: Gauge,
92
93 storage_cache_misses: Gauge,
95
96 storage_cache_size: Gauge,
101
102 account_cache_hits: Gauge,
104
105 account_cache_misses: Gauge,
107
108 account_cache_size: Gauge,
113}
114
115impl CachedStateMetrics {
116 pub(crate) fn reset(&self) {
118 self.code_cache_hits.set(0);
120 self.code_cache_misses.set(0);
121
122 self.storage_cache_hits.set(0);
124 self.storage_cache_misses.set(0);
125
126 self.account_cache_hits.set(0);
128 self.account_cache_misses.set(0);
129 }
130
131 pub(crate) fn zeroed() -> Self {
133 let zeroed = Self::default();
134 zeroed.reset();
135 zeroed
136 }
137}
138
139impl<S: AccountReader> AccountReader for CachedStateProvider<S> {
140 fn basic_account(&self, address: &Address) -> ProviderResult<Option<Account>> {
141 if let Some(res) = self.caches.account_cache.get(address) {
142 self.metrics.account_cache_hits.increment(1);
143 return Ok(res)
144 }
145
146 self.metrics.account_cache_misses.increment(1);
147
148 let res = self.state_provider.basic_account(address)?;
149
150 if self.is_prewarm() {
151 self.caches.account_cache.insert(*address, res);
152 }
153 Ok(res)
154 }
155}
156
157#[derive(Debug, Clone, PartialEq, Eq)]
159pub(crate) enum SlotStatus {
160 NotCached,
162 Empty,
164 Value(StorageValue),
166}
167
168impl<S: StateProvider> StateProvider for CachedStateProvider<S> {
169 fn storage(
170 &self,
171 account: Address,
172 storage_key: StorageKey,
173 ) -> ProviderResult<Option<StorageValue>> {
174 match self.caches.get_storage(&account, &storage_key) {
175 (SlotStatus::NotCached, maybe_cache) => {
176 let final_res = self.state_provider.storage(account, storage_key)?;
177
178 if self.is_prewarm() {
179 let account_cache = maybe_cache.unwrap_or_default();
180 account_cache.insert_storage(storage_key, final_res);
181 self.caches.insert_storage_cache(account, account_cache);
189 }
190
191 self.metrics.storage_cache_misses.increment(1);
192 Ok(final_res)
193 }
194 (SlotStatus::Empty, _) => {
195 self.metrics.storage_cache_hits.increment(1);
196 Ok(None)
197 }
198 (SlotStatus::Value(value), _) => {
199 self.metrics.storage_cache_hits.increment(1);
200 Ok(Some(value))
201 }
202 }
203 }
204}
205
206impl<S: BytecodeReader> BytecodeReader for CachedStateProvider<S> {
207 fn bytecode_by_hash(&self, code_hash: &B256) -> ProviderResult<Option<Bytecode>> {
208 if let Some(res) = self.caches.code_cache.get(code_hash) {
209 self.metrics.code_cache_hits.increment(1);
210 return Ok(res)
211 }
212
213 self.metrics.code_cache_misses.increment(1);
214
215 let final_res = self.state_provider.bytecode_by_hash(code_hash)?;
216
217 if self.is_prewarm() {
218 self.caches.code_cache.insert(*code_hash, final_res.clone());
219 }
220
221 Ok(final_res)
222 }
223}
224
225impl<S: StateRootProvider> StateRootProvider for CachedStateProvider<S> {
226 fn state_root(&self, hashed_state: HashedPostState) -> ProviderResult<B256> {
227 self.state_provider.state_root(hashed_state)
228 }
229
230 fn state_root_from_nodes(&self, input: TrieInput) -> ProviderResult<B256> {
231 self.state_provider.state_root_from_nodes(input)
232 }
233
234 fn state_root_with_updates(
235 &self,
236 hashed_state: HashedPostState,
237 ) -> ProviderResult<(B256, TrieUpdates)> {
238 self.state_provider.state_root_with_updates(hashed_state)
239 }
240
241 fn state_root_from_nodes_with_updates(
242 &self,
243 input: TrieInput,
244 ) -> ProviderResult<(B256, TrieUpdates)> {
245 self.state_provider.state_root_from_nodes_with_updates(input)
246 }
247}
248
249impl<S: StateProofProvider> StateProofProvider for CachedStateProvider<S> {
250 fn proof(
251 &self,
252 input: TrieInput,
253 address: Address,
254 slots: &[B256],
255 ) -> ProviderResult<AccountProof> {
256 self.state_provider.proof(input, address, slots)
257 }
258
259 fn multiproof(
260 &self,
261 input: TrieInput,
262 targets: MultiProofTargets,
263 ) -> ProviderResult<MultiProof> {
264 self.state_provider.multiproof(input, targets)
265 }
266
267 fn witness(
268 &self,
269 input: TrieInput,
270 target: HashedPostState,
271 ) -> ProviderResult<Vec<alloy_primitives::Bytes>> {
272 self.state_provider.witness(input, target)
273 }
274}
275
276impl<S: StorageRootProvider> StorageRootProvider for CachedStateProvider<S> {
277 fn storage_root(
278 &self,
279 address: Address,
280 hashed_storage: HashedStorage,
281 ) -> ProviderResult<B256> {
282 self.state_provider.storage_root(address, hashed_storage)
283 }
284
285 fn storage_proof(
286 &self,
287 address: Address,
288 slot: B256,
289 hashed_storage: HashedStorage,
290 ) -> ProviderResult<StorageProof> {
291 self.state_provider.storage_proof(address, slot, hashed_storage)
292 }
293
294 fn storage_multiproof(
307 &self,
308 address: Address,
309 slots: &[B256],
310 hashed_storage: HashedStorage,
311 ) -> ProviderResult<StorageMultiProof> {
312 self.state_provider.storage_multiproof(address, slots, hashed_storage)
313 }
314}
315
316impl<S: BlockHashReader> BlockHashReader for CachedStateProvider<S> {
317 fn block_hash(&self, number: alloy_primitives::BlockNumber) -> ProviderResult<Option<B256>> {
318 self.state_provider.block_hash(number)
319 }
320
321 fn canonical_hashes_range(
322 &self,
323 start: alloy_primitives::BlockNumber,
324 end: alloy_primitives::BlockNumber,
325 ) -> ProviderResult<Vec<B256>> {
326 self.state_provider.canonical_hashes_range(start, end)
327 }
328}
329
330impl<S: HashedPostStateProvider> HashedPostStateProvider for CachedStateProvider<S> {
331 fn hashed_post_state(&self, bundle_state: &reth_revm::db::BundleState) -> HashedPostState {
332 self.state_provider.hashed_post_state(bundle_state)
333 }
334}
335
336#[derive(Debug, Clone)]
342pub(crate) struct ExecutionCache {
343 code_cache: Cache<B256, Option<Bytecode>>,
345
346 storage_cache: Cache<Address, Arc<AccountStorageCache>>,
349
350 account_cache: Cache<Address, Option<Account>>,
352}
353
354impl ExecutionCache {
355 pub(crate) fn get_storage(
364 &self,
365 address: &Address,
366 key: &StorageKey,
367 ) -> (SlotStatus, Option<Arc<AccountStorageCache>>) {
368 match self.storage_cache.get(address) {
369 None => (SlotStatus::NotCached, None),
370 Some(account_cache) => {
371 let status = account_cache.get_storage(key);
372 (status, Some(account_cache))
373 }
374 }
375 }
376
377 #[cfg(test)]
379 pub(crate) fn insert_storage(
380 &self,
381 address: Address,
382 key: StorageKey,
383 value: Option<StorageValue>,
384 ) {
385 self.insert_storage_bulk(address, [(key, value)]);
386 }
387
388 pub(crate) fn insert_storage_bulk<I>(&self, address: Address, storage_entries: I)
393 where
394 I: IntoIterator<Item = (StorageKey, Option<StorageValue>)>,
395 {
396 let account_cache = self.storage_cache.get(&address).unwrap_or_default();
397
398 for (key, value) in storage_entries {
399 account_cache.insert_storage(key, value);
400 }
401
402 self.storage_cache.insert(address, account_cache);
405 }
406
407 pub(crate) fn insert_storage_cache(
409 &self,
410 address: Address,
411 storage_cache: Arc<AccountStorageCache>,
412 ) {
413 self.storage_cache.insert(address, storage_cache);
414 }
415
416 pub(crate) fn invalidate_account_storage(&self, address: &Address) {
418 self.storage_cache.invalidate(address);
419 }
420
421 pub(crate) fn total_storage_slots(&self) -> usize {
423 self.storage_cache.iter().map(|addr| addr.len()).sum()
424 }
425
426 #[instrument(level = "debug", target = "engine::caching", skip_all)]
445 pub(crate) fn insert_state(&self, state_updates: &BundleState) -> Result<(), ()> {
446 let _enter =
447 debug_span!(target: "engine::tree", "contracts", len = state_updates.contracts.len())
448 .entered();
449 for (code_hash, bytecode) in &state_updates.contracts {
451 self.code_cache.insert(*code_hash, Some(Bytecode(bytecode.clone())));
452 }
453 drop(_enter);
454
455 let _enter = debug_span!(
456 target: "engine::tree",
457 "accounts",
458 accounts = state_updates.state.len(),
459 storages =
460 state_updates.state.values().map(|account| account.storage.len()).sum::<usize>()
461 )
462 .entered();
463 for (addr, account) in &state_updates.state {
464 if account.status.is_not_modified() {
467 continue
468 }
469
470 if account.was_destroyed() {
472 self.account_cache.invalidate(addr);
474
475 self.invalidate_account_storage(addr);
476 continue
477 }
478
479 let Some(ref account_info) = account.info else {
483 trace!(target: "engine::caching", ?account, "Account with None account info found in state updates");
484 return Err(())
485 };
486
487 let storage_entries = account.storage.iter().map(|(storage_key, slot)| {
491 ((*storage_key).into(), Some(slot.present_value))
494 });
495 self.insert_storage_bulk(*addr, storage_entries);
496
497 self.account_cache.insert(*addr, Some(Account::from(account_info)));
500 }
501
502 Ok(())
503 }
504}
505
506#[derive(Debug)]
508pub(crate) struct ExecutionCacheBuilder {
509 code_cache_entries: u64,
511
512 storage_cache_entries: u64,
514
515 account_cache_entries: u64,
517}
518
519impl ExecutionCacheBuilder {
520 pub(crate) fn build_caches(self, total_cache_size: u64) -> ExecutionCache {
522 let storage_cache_size = (total_cache_size * 8888) / 10000; let account_cache_size = (total_cache_size * 556) / 10000; let code_cache_size = (total_cache_size * 556) / 10000; const EXPIRY_TIME: Duration = Duration::from_secs(7200); const TIME_TO_IDLE: Duration = Duration::from_secs(3600); let storage_cache = CacheBuilder::new(self.storage_cache_entries)
530 .weigher(|_key: &Address, value: &Arc<AccountStorageCache>| -> u32 {
531 let base_weight = 39_000;
533 let slots_weight = value.len() * 218;
534 (base_weight + slots_weight) as u32
535 })
536 .max_capacity(storage_cache_size)
537 .time_to_live(EXPIRY_TIME)
538 .time_to_idle(TIME_TO_IDLE)
539 .build_with_hasher(DefaultHashBuilder::default());
540
541 let account_cache = CacheBuilder::new(self.account_cache_entries)
542 .weigher(|_key: &Address, value: &Option<Account>| -> u32 {
543 20 + size_of_val(value) as u32
545 })
546 .max_capacity(account_cache_size)
547 .time_to_live(EXPIRY_TIME)
548 .time_to_idle(TIME_TO_IDLE)
549 .build_with_hasher(DefaultHashBuilder::default());
550
551 let code_cache = CacheBuilder::new(self.code_cache_entries)
552 .weigher(|_key: &B256, value: &Option<Bytecode>| -> u32 {
553 let code_size = match value {
554 Some(bytecode) => {
555 (size_of_val(value) +
557 bytecode.bytecode().len() +
558 bytecode
559 .legacy_jump_table()
560 .map(|table| table.as_slice().len())
561 .unwrap_or_default()) as u32
562 }
563 None => size_of_val(value) as u32,
564 };
565 32 + code_size
566 })
567 .max_capacity(code_cache_size)
568 .time_to_live(EXPIRY_TIME)
569 .time_to_idle(TIME_TO_IDLE)
570 .build_with_hasher(DefaultHashBuilder::default());
571
572 ExecutionCache { code_cache, storage_cache, account_cache }
573 }
574}
575
576impl Default for ExecutionCacheBuilder {
577 fn default() -> Self {
578 Self {
586 code_cache_entries: 10_000_000,
587 storage_cache_entries: 10_000_000,
588 account_cache_entries: 10_000_000,
589 }
590 }
591}
592
593#[derive(Debug, Clone)]
596pub(crate) struct SavedCache {
597 hash: B256,
599
600 caches: ExecutionCache,
602
603 metrics: CachedStateMetrics,
605
606 usage_guard: Arc<()>,
609}
610
611impl SavedCache {
612 pub(super) fn new(hash: B256, caches: ExecutionCache, metrics: CachedStateMetrics) -> Self {
614 Self { hash, caches, metrics, usage_guard: Arc::new(()) }
615 }
616
617 pub(crate) const fn executed_block_hash(&self) -> B256 {
619 self.hash
620 }
621
622 pub(crate) fn split(self) -> (ExecutionCache, CachedStateMetrics) {
624 (self.caches, self.metrics)
625 }
626
627 pub(crate) fn is_available(&self) -> bool {
629 Arc::strong_count(&self.usage_guard) == 1
630 }
631
632 pub(crate) const fn cache(&self) -> &ExecutionCache {
634 &self.caches
635 }
636
637 pub(crate) const fn metrics(&self) -> &CachedStateMetrics {
639 &self.metrics
640 }
641
642 pub(crate) fn update_metrics(&self) {
644 self.metrics.storage_cache_size.set(self.caches.total_storage_slots() as f64);
645 self.metrics.account_cache_size.set(self.caches.account_cache.entry_count() as f64);
646 self.metrics.code_cache_size.set(self.caches.code_cache.entry_count() as f64);
647 }
648}
649
650#[cfg(test)]
651impl SavedCache {
652 fn clone_guard_for_test(&self) -> Arc<()> {
653 self.usage_guard.clone()
654 }
655}
656
657#[derive(Debug, Clone)]
662pub(crate) struct AccountStorageCache {
663 slots: Cache<StorageKey, Option<StorageValue>>,
665}
666
667impl AccountStorageCache {
668 pub(crate) fn new(max_slots: u64) -> Self {
670 Self {
671 slots: CacheBuilder::new(max_slots).build_with_hasher(DefaultHashBuilder::default()),
672 }
673 }
674
675 pub(crate) fn get_storage(&self, key: &StorageKey) -> SlotStatus {
680 match self.slots.get(key) {
681 None => SlotStatus::NotCached,
682 Some(None) => SlotStatus::Empty,
683 Some(Some(value)) => SlotStatus::Value(value),
684 }
685 }
686
687 pub(crate) fn insert_storage(&self, key: StorageKey, value: Option<StorageValue>) {
689 self.slots.insert(key, value);
690 }
691
692 pub(crate) fn len(&self) -> usize {
694 self.slots.entry_count() as usize
695 }
696}
697
698impl Default for AccountStorageCache {
699 fn default() -> Self {
700 Self::new(1_000_000)
704 }
705}
706
707#[cfg(test)]
708mod tests {
709 use super::*;
710 use alloy_primitives::{B256, U256};
711 use rand::Rng;
712 use reth_provider::test_utils::{ExtendedAccount, MockEthProvider};
713 use std::mem::size_of;
714
715 mod tracking_allocator {
716 use std::{
717 alloc::{GlobalAlloc, Layout, System},
718 sync::atomic::{AtomicUsize, Ordering},
719 };
720
721 #[derive(Debug)]
722 pub(crate) struct TrackingAllocator {
723 allocated: AtomicUsize,
724 total_allocated: AtomicUsize,
725 inner: System,
726 }
727
728 impl TrackingAllocator {
729 pub(crate) const fn new() -> Self {
730 Self {
731 allocated: AtomicUsize::new(0),
732 total_allocated: AtomicUsize::new(0),
733 inner: System,
734 }
735 }
736
737 pub(crate) fn reset(&self) {
738 self.allocated.store(0, Ordering::SeqCst);
739 self.total_allocated.store(0, Ordering::SeqCst);
740 }
741
742 pub(crate) fn total_allocated(&self) -> usize {
743 self.total_allocated.load(Ordering::SeqCst)
744 }
745 }
746
747 unsafe impl GlobalAlloc for TrackingAllocator {
748 unsafe fn alloc(&self, layout: Layout) -> *mut u8 {
749 let ret = unsafe { self.inner.alloc(layout) };
750 if !ret.is_null() {
751 self.allocated.fetch_add(layout.size(), Ordering::SeqCst);
752 self.total_allocated.fetch_add(layout.size(), Ordering::SeqCst);
753 }
754 ret
755 }
756
757 unsafe fn dealloc(&self, ptr: *mut u8, layout: Layout) {
758 self.allocated.fetch_sub(layout.size(), Ordering::SeqCst);
759 unsafe { self.inner.dealloc(ptr, layout) }
760 }
761 }
762 }
763
764 use tracking_allocator::TrackingAllocator;
765
766 #[global_allocator]
767 static ALLOCATOR: TrackingAllocator = TrackingAllocator::new();
768
769 fn measure_allocation<T, F>(f: F) -> (usize, T)
770 where
771 F: FnOnce() -> T,
772 {
773 ALLOCATOR.reset();
774 let result = f();
775 let total = ALLOCATOR.total_allocated();
776 (total, result)
777 }
778
779 #[test]
780 fn measure_storage_cache_overhead() {
781 let (base_overhead, cache) = measure_allocation(|| AccountStorageCache::new(1000));
782 println!("Base AccountStorageCache overhead: {base_overhead} bytes");
783 let mut rng = rand::rng();
784
785 let key = StorageKey::random();
786 let value = StorageValue::from(rng.random::<u128>());
787 let (first_slot, _) = measure_allocation(|| {
788 cache.insert_storage(key, Some(value));
789 });
790 println!("First slot insertion overhead: {first_slot} bytes");
791
792 const TOTAL_SLOTS: usize = 10_000;
793 let (test_slots, _) = measure_allocation(|| {
794 for _ in 0..TOTAL_SLOTS {
795 let key = StorageKey::random();
796 let value = StorageValue::from(rng.random::<u128>());
797 cache.insert_storage(key, Some(value));
798 }
799 });
800 println!("Average overhead over {} slots: {} bytes", TOTAL_SLOTS, test_slots / TOTAL_SLOTS);
801
802 println!("\nTheoretical sizes:");
803 println!("StorageKey size: {} bytes", size_of::<StorageKey>());
804 println!("StorageValue size: {} bytes", size_of::<StorageValue>());
805 println!("Option<StorageValue> size: {} bytes", size_of::<Option<StorageValue>>());
806 println!("Option<B256> size: {} bytes", size_of::<Option<B256>>());
807 }
808
809 #[test]
810 fn test_empty_storage_cached_state_provider() {
811 let address = Address::random();
813 let storage_key = StorageKey::random();
814 let account = ExtendedAccount::new(0, U256::ZERO);
815
816 let provider = MockEthProvider::default();
818 provider.extend_accounts(vec![(address, account)]);
819
820 let caches = ExecutionCacheBuilder::default().build_caches(1000);
821 let state_provider =
822 CachedStateProvider::new(provider, caches, CachedStateMetrics::zeroed());
823
824 let res = state_provider.storage(address, storage_key);
826 assert!(res.is_ok());
827 assert_eq!(res.unwrap(), None);
828 }
829
830 #[test]
831 fn test_uncached_storage_cached_state_provider() {
832 let address = Address::random();
834 let storage_key = StorageKey::random();
835 let storage_value = U256::from(1);
836 let account =
837 ExtendedAccount::new(0, U256::ZERO).extend_storage(vec![(storage_key, storage_value)]);
838
839 let provider = MockEthProvider::default();
841 provider.extend_accounts(vec![(address, account)]);
842
843 let caches = ExecutionCacheBuilder::default().build_caches(1000);
844 let state_provider =
845 CachedStateProvider::new(provider, caches, CachedStateMetrics::zeroed());
846
847 let res = state_provider.storage(address, storage_key);
849 assert!(res.is_ok());
850 assert_eq!(res.unwrap(), Some(storage_value));
851 }
852
853 #[test]
854 fn test_get_storage_populated() {
855 let address = Address::random();
857 let storage_key = StorageKey::random();
858 let storage_value = U256::from(1);
859
860 let caches = ExecutionCacheBuilder::default().build_caches(1000);
862 caches.insert_storage(address, storage_key, Some(storage_value));
863
864 let (slot_status, _) = caches.get_storage(&address, &storage_key);
866 assert_eq!(slot_status, SlotStatus::Value(storage_value));
867 }
868
869 #[test]
870 fn test_get_storage_not_cached() {
871 let storage_key = StorageKey::random();
873 let address = Address::random();
874
875 let caches = ExecutionCacheBuilder::default().build_caches(1000);
877
878 let (slot_status, _) = caches.get_storage(&address, &storage_key);
880 assert_eq!(slot_status, SlotStatus::NotCached);
881 }
882
883 #[test]
884 fn test_get_storage_empty() {
885 let address = Address::random();
888 let storage_key = StorageKey::random();
889
890 let caches = ExecutionCacheBuilder::default().build_caches(1000);
892 caches.insert_storage(address, storage_key, None);
893
894 let (slot_status, _) = caches.get_storage(&address, &storage_key);
896 assert_eq!(slot_status, SlotStatus::Empty);
897 }
898
899 #[test]
901 fn test_saved_cache_is_available() {
902 let execution_cache = ExecutionCacheBuilder::default().build_caches(1000);
903 let cache = SavedCache::new(B256::ZERO, execution_cache, CachedStateMetrics::zeroed());
904
905 assert!(cache.is_available(), "Cache should be available initially");
907
908 let _guard = cache.clone_guard_for_test();
910
911 assert!(!cache.is_available(), "Cache should not be available with active guard");
913 }
914
915 #[test]
916 fn test_saved_cache_multiple_references() {
917 let execution_cache = ExecutionCacheBuilder::default().build_caches(1000);
918 let cache =
919 SavedCache::new(B256::from([2u8; 32]), execution_cache, CachedStateMetrics::zeroed());
920
921 let guard1 = cache.clone_guard_for_test();
923 let guard2 = cache.clone_guard_for_test();
924 let guard3 = guard1.clone();
925
926 assert!(!cache.is_available());
928
929 drop(guard1);
931 assert!(!cache.is_available()); drop(guard2);
934 assert!(!cache.is_available()); drop(guard3);
937 assert!(cache.is_available()); }
939}