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, HashedPostStateProvider, StateProofProvider, StateProvider,
10 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::time::Duration;
19use tracing::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: ProviderCaches,
31
32 metrics: CachedStateMetrics,
34}
35
36impl<S> CachedStateProvider<S>
37where
38 S: StateProvider,
39{
40 pub(crate) const fn new_with_caches(
43 state_provider: S,
44 caches: ProviderCaches,
45 metrics: CachedStateMetrics,
46 ) -> Self {
47 Self { state_provider, caches, metrics }
48 }
49}
50
51#[derive(Metrics, Clone)]
53#[metrics(scope = "sync.caching")]
54pub(crate) struct CachedStateMetrics {
55 code_cache_hits: Gauge,
57
58 code_cache_misses: Gauge,
60
61 code_cache_size: Gauge,
66
67 storage_cache_hits: Gauge,
69
70 storage_cache_misses: Gauge,
72
73 storage_cache_size: Gauge,
78
79 account_cache_hits: Gauge,
81
82 account_cache_misses: Gauge,
84
85 account_cache_size: Gauge,
90}
91
92impl CachedStateMetrics {
93 pub(crate) fn reset(&self) {
95 self.code_cache_hits.set(0);
97 self.code_cache_misses.set(0);
98
99 self.storage_cache_hits.set(0);
101 self.storage_cache_misses.set(0);
102
103 self.account_cache_hits.set(0);
105 self.account_cache_misses.set(0);
106 }
107
108 pub(crate) fn zeroed() -> Self {
110 let zeroed = Self::default();
111 zeroed.reset();
112 zeroed
113 }
114}
115
116impl<S: AccountReader> AccountReader for CachedStateProvider<S> {
117 fn basic_account(&self, address: &Address) -> ProviderResult<Option<Account>> {
118 if let Some(res) = self.caches.account_cache.get(address) {
119 self.metrics.account_cache_hits.increment(1);
120 return Ok(res)
121 }
122
123 self.metrics.account_cache_misses.increment(1);
124
125 let res = self.state_provider.basic_account(address)?;
126 self.caches.account_cache.insert(*address, res);
127 Ok(res)
128 }
129}
130
131#[derive(Debug, Clone, PartialEq, Eq)]
133pub(crate) enum SlotStatus {
134 NotCached,
136 Empty,
138 Value(StorageValue),
140}
141
142impl<S: StateProvider> StateProvider for CachedStateProvider<S> {
143 fn storage(
144 &self,
145 account: Address,
146 storage_key: StorageKey,
147 ) -> ProviderResult<Option<StorageValue>> {
148 match self.caches.get_storage(&account, &storage_key) {
149 SlotStatus::NotCached => {
150 self.metrics.storage_cache_misses.increment(1);
151 let final_res = self.state_provider.storage(account, storage_key)?;
152 self.caches.insert_storage(account, storage_key, final_res);
153 Ok(final_res)
154 }
155 SlotStatus::Empty => {
156 self.metrics.storage_cache_hits.increment(1);
157 Ok(None)
158 }
159 SlotStatus::Value(value) => {
160 self.metrics.storage_cache_hits.increment(1);
161 Ok(Some(value))
162 }
163 }
164 }
165
166 fn bytecode_by_hash(&self, code_hash: &B256) -> ProviderResult<Option<Bytecode>> {
167 if let Some(res) = self.caches.code_cache.get(code_hash) {
168 self.metrics.code_cache_hits.increment(1);
169 return Ok(res)
170 }
171
172 self.metrics.code_cache_misses.increment(1);
173
174 let final_res = self.state_provider.bytecode_by_hash(code_hash)?;
175 self.caches.code_cache.insert(*code_hash, final_res.clone());
176 Ok(final_res)
177 }
178}
179
180impl<S: StateRootProvider> StateRootProvider for CachedStateProvider<S> {
181 fn state_root(&self, hashed_state: HashedPostState) -> ProviderResult<B256> {
182 self.state_provider.state_root(hashed_state)
183 }
184
185 fn state_root_from_nodes(&self, input: TrieInput) -> ProviderResult<B256> {
186 self.state_provider.state_root_from_nodes(input)
187 }
188
189 fn state_root_with_updates(
190 &self,
191 hashed_state: HashedPostState,
192 ) -> ProviderResult<(B256, TrieUpdates)> {
193 self.state_provider.state_root_with_updates(hashed_state)
194 }
195
196 fn state_root_from_nodes_with_updates(
197 &self,
198 input: TrieInput,
199 ) -> ProviderResult<(B256, TrieUpdates)> {
200 self.state_provider.state_root_from_nodes_with_updates(input)
201 }
202}
203
204impl<S: StateProofProvider> StateProofProvider for CachedStateProvider<S> {
205 fn proof(
206 &self,
207 input: TrieInput,
208 address: Address,
209 slots: &[B256],
210 ) -> ProviderResult<AccountProof> {
211 self.state_provider.proof(input, address, slots)
212 }
213
214 fn multiproof(
215 &self,
216 input: TrieInput,
217 targets: MultiProofTargets,
218 ) -> ProviderResult<MultiProof> {
219 self.state_provider.multiproof(input, targets)
220 }
221
222 fn witness(
223 &self,
224 input: TrieInput,
225 target: HashedPostState,
226 ) -> ProviderResult<Vec<alloy_primitives::Bytes>> {
227 self.state_provider.witness(input, target)
228 }
229}
230
231impl<S: StorageRootProvider> StorageRootProvider for CachedStateProvider<S> {
232 fn storage_root(
233 &self,
234 address: Address,
235 hashed_storage: HashedStorage,
236 ) -> ProviderResult<B256> {
237 self.state_provider.storage_root(address, hashed_storage)
238 }
239
240 fn storage_proof(
241 &self,
242 address: Address,
243 slot: B256,
244 hashed_storage: HashedStorage,
245 ) -> ProviderResult<StorageProof> {
246 self.state_provider.storage_proof(address, slot, hashed_storage)
247 }
248
249 fn storage_multiproof(
250 &self,
251 address: Address,
252 slots: &[B256],
253 hashed_storage: HashedStorage,
254 ) -> ProviderResult<StorageMultiProof> {
255 self.state_provider.storage_multiproof(address, slots, hashed_storage)
256 }
257}
258
259impl<S: BlockHashReader> BlockHashReader for CachedStateProvider<S> {
260 fn block_hash(&self, number: alloy_primitives::BlockNumber) -> ProviderResult<Option<B256>> {
261 self.state_provider.block_hash(number)
262 }
263
264 fn canonical_hashes_range(
265 &self,
266 start: alloy_primitives::BlockNumber,
267 end: alloy_primitives::BlockNumber,
268 ) -> ProviderResult<Vec<B256>> {
269 self.state_provider.canonical_hashes_range(start, end)
270 }
271}
272
273impl<S: HashedPostStateProvider> HashedPostStateProvider for CachedStateProvider<S> {
274 fn hashed_post_state(&self, bundle_state: &reth_revm::db::BundleState) -> HashedPostState {
275 self.state_provider.hashed_post_state(bundle_state)
276 }
277}
278
279#[derive(Debug, Clone)]
281pub(crate) struct ProviderCaches {
282 code_cache: Cache<B256, Option<Bytecode>>,
284
285 storage_cache: Cache<Address, AccountStorageCache>,
287
288 account_cache: Cache<Address, Option<Account>>,
290}
291
292impl ProviderCaches {
293 pub(crate) fn get_storage(&self, address: &Address, key: &StorageKey) -> SlotStatus {
300 match self.storage_cache.get(address) {
301 None => SlotStatus::NotCached,
302 Some(account_cache) => account_cache.get_storage(key),
303 }
304 }
305
306 pub(crate) fn insert_storage(
308 &self,
309 address: Address,
310 key: StorageKey,
311 value: Option<StorageValue>,
312 ) {
313 let account_cache = self.storage_cache.get(&address).unwrap_or_else(|| {
314 let account_cache = AccountStorageCache::default();
315 self.storage_cache.insert(address, account_cache.clone());
316 account_cache
317 });
318 account_cache.insert_storage(key, value);
319 }
320
321 pub(crate) fn invalidate_account_storage(&self, address: &Address) {
323 self.storage_cache.invalidate(address);
324 }
325
326 pub(crate) fn total_storage_slots(&self) -> usize {
328 self.storage_cache.iter().map(|addr| addr.len()).sum()
329 }
330
331 pub(crate) fn insert_state(&self, state_updates: &BundleState) -> Result<(), ()> {
344 for (code_hash, bytecode) in &state_updates.contracts {
346 self.code_cache.insert(*code_hash, Some(Bytecode(bytecode.clone())));
347 }
348
349 for (addr, account) in &state_updates.state {
350 if account.status.is_not_modified() {
353 continue
354 }
355
356 if account.was_destroyed() {
358 self.account_cache.invalidate(addr);
360
361 self.invalidate_account_storage(addr);
362 continue
363 }
364
365 let Some(ref account_info) = account.info else {
369 trace!(target: "engine::caching", ?account, "Account with None account info found in state updates");
370 return Err(())
371 };
372
373 for (storage_key, slot) in &account.storage {
375 self.insert_storage(*addr, (*storage_key).into(), Some(slot.present_value));
378 }
379
380 self.account_cache.insert(*addr, Some(Account::from(account_info)));
383 }
384
385 Ok(())
386 }
387}
388
389#[derive(Debug)]
391pub(crate) struct ProviderCacheBuilder {
392 code_cache_entries: u64,
394
395 storage_cache_entries: u64,
397
398 account_cache_entries: u64,
400}
401
402impl ProviderCacheBuilder {
403 pub(crate) fn build_caches(self, total_cache_size: u64) -> ProviderCaches {
405 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)
413 .weigher(|_key: &Address, value: &AccountStorageCache| -> u32 {
414 let base_weight = 39_000;
416 let slots_weight = value.len() * 218;
417 (base_weight + slots_weight) as u32
418 })
419 .max_capacity(storage_cache_size)
420 .time_to_live(EXPIRY_TIME)
421 .time_to_idle(TIME_TO_IDLE)
422 .build_with_hasher(DefaultHashBuilder::default());
423
424 let account_cache = CacheBuilder::new(self.account_cache_entries)
425 .weigher(|_key: &Address, value: &Option<Account>| -> u32 {
426 match value {
427 Some(account) => {
428 let mut weight = 40;
429 if account.nonce != 0 {
430 weight += 32;
431 }
432 if !account.balance.is_zero() {
433 weight += 32;
434 }
435 if account.bytecode_hash.is_some() {
436 weight += 33; } else {
438 weight += 8; }
440 weight as u32
441 }
442 None => 8, }
444 })
445 .max_capacity(account_cache_size)
446 .time_to_live(EXPIRY_TIME)
447 .time_to_idle(TIME_TO_IDLE)
448 .build_with_hasher(DefaultHashBuilder::default());
449
450 let code_cache = CacheBuilder::new(self.code_cache_entries)
451 .weigher(|_key: &B256, value: &Option<Bytecode>| -> u32 {
452 match value {
453 Some(bytecode) => {
454 (40 + bytecode.len()) as u32
456 }
457 None => 8, }
459 })
460 .max_capacity(code_cache_size)
461 .time_to_live(EXPIRY_TIME)
462 .time_to_idle(TIME_TO_IDLE)
463 .build_with_hasher(DefaultHashBuilder::default());
464
465 ProviderCaches { code_cache, storage_cache, account_cache }
466 }
467}
468
469impl Default for ProviderCacheBuilder {
470 fn default() -> Self {
471 Self {
479 code_cache_entries: 10_000_000,
480 storage_cache_entries: 10_000_000,
481 account_cache_entries: 10_000_000,
482 }
483 }
484}
485
486#[derive(Debug, Clone)]
489pub(crate) struct SavedCache {
490 hash: B256,
492
493 caches: ProviderCaches,
495
496 metrics: CachedStateMetrics,
498}
499
500impl SavedCache {
501 pub(super) const fn new(
503 hash: B256,
504 caches: ProviderCaches,
505 metrics: CachedStateMetrics,
506 ) -> Self {
507 Self { hash, caches, metrics }
508 }
509
510 pub(crate) const fn executed_block_hash(&self) -> B256 {
512 self.hash
513 }
514
515 pub(crate) fn split(self) -> (ProviderCaches, CachedStateMetrics) {
517 (self.caches, self.metrics)
518 }
519
520 pub(crate) const fn cache(&self) -> &ProviderCaches {
522 &self.caches
523 }
524
525 pub(crate) fn update_metrics(&self) {
527 self.metrics.storage_cache_size.set(self.caches.total_storage_slots() as f64);
528 self.metrics.account_cache_size.set(self.caches.account_cache.entry_count() as f64);
529 self.metrics.code_cache_size.set(self.caches.code_cache.entry_count() as f64);
530 }
531}
532
533#[derive(Debug, Clone)]
535pub(crate) struct AccountStorageCache {
536 slots: Cache<StorageKey, Option<StorageValue>>,
538}
539
540impl AccountStorageCache {
541 pub(crate) fn new(max_slots: u64) -> Self {
543 Self {
544 slots: CacheBuilder::new(max_slots).build_with_hasher(DefaultHashBuilder::default()),
545 }
546 }
547
548 pub(crate) fn get_storage(&self, key: &StorageKey) -> SlotStatus {
553 match self.slots.get(key) {
554 None => SlotStatus::NotCached,
555 Some(None) => SlotStatus::Empty,
556 Some(Some(value)) => SlotStatus::Value(value),
557 }
558 }
559
560 pub(crate) fn insert_storage(&self, key: StorageKey, value: Option<StorageValue>) {
562 self.slots.insert(key, value);
563 }
564
565 pub(crate) fn len(&self) -> usize {
567 self.slots.entry_count() as usize
568 }
569}
570
571impl Default for AccountStorageCache {
572 fn default() -> Self {
573 Self::new(1_000_000)
577 }
578}
579
580#[cfg(test)]
581mod tests {
582 use super::*;
583 use alloy_primitives::{B256, U256};
584 use rand::Rng;
585 use reth_provider::test_utils::{ExtendedAccount, MockEthProvider};
586 use std::mem::size_of;
587
588 mod tracking_allocator {
589 use std::{
590 alloc::{GlobalAlloc, Layout, System},
591 sync::atomic::{AtomicUsize, Ordering},
592 };
593
594 #[derive(Debug)]
595 pub(crate) struct TrackingAllocator {
596 allocated: AtomicUsize,
597 total_allocated: AtomicUsize,
598 inner: System,
599 }
600
601 impl TrackingAllocator {
602 pub(crate) const fn new() -> Self {
603 Self {
604 allocated: AtomicUsize::new(0),
605 total_allocated: AtomicUsize::new(0),
606 inner: System,
607 }
608 }
609
610 pub(crate) fn reset(&self) {
611 self.allocated.store(0, Ordering::SeqCst);
612 self.total_allocated.store(0, Ordering::SeqCst);
613 }
614
615 pub(crate) fn total_allocated(&self) -> usize {
616 self.total_allocated.load(Ordering::SeqCst)
617 }
618 }
619
620 unsafe impl GlobalAlloc for TrackingAllocator {
621 unsafe fn alloc(&self, layout: Layout) -> *mut u8 {
622 let ret = self.inner.alloc(layout);
623 if !ret.is_null() {
624 self.allocated.fetch_add(layout.size(), Ordering::SeqCst);
625 self.total_allocated.fetch_add(layout.size(), Ordering::SeqCst);
626 }
627 ret
628 }
629
630 unsafe fn dealloc(&self, ptr: *mut u8, layout: Layout) {
631 self.allocated.fetch_sub(layout.size(), Ordering::SeqCst);
632 self.inner.dealloc(ptr, layout)
633 }
634 }
635 }
636
637 use tracking_allocator::TrackingAllocator;
638
639 #[global_allocator]
640 static ALLOCATOR: TrackingAllocator = TrackingAllocator::new();
641
642 fn measure_allocation<T, F>(f: F) -> (usize, T)
643 where
644 F: FnOnce() -> T,
645 {
646 ALLOCATOR.reset();
647 let result = f();
648 let total = ALLOCATOR.total_allocated();
649 (total, result)
650 }
651
652 #[test]
653 fn measure_storage_cache_overhead() {
654 let (base_overhead, cache) = measure_allocation(|| AccountStorageCache::new(1000));
655 println!("Base AccountStorageCache overhead: {} bytes", base_overhead);
656 let mut rng = rand::rng();
657
658 let key = StorageKey::random();
659 let value = StorageValue::from(rng.random::<u128>());
660 let (first_slot, _) = measure_allocation(|| {
661 cache.insert_storage(key, Some(value));
662 });
663 println!("First slot insertion overhead: {} bytes", first_slot);
664
665 const TOTAL_SLOTS: usize = 10_000;
666 let (test_slots, _) = measure_allocation(|| {
667 for _ in 0..TOTAL_SLOTS {
668 let key = StorageKey::random();
669 let value = StorageValue::from(rng.random::<u128>());
670 cache.insert_storage(key, Some(value));
671 }
672 });
673 println!("Average overhead over {} slots: {} bytes", TOTAL_SLOTS, test_slots / TOTAL_SLOTS);
674
675 println!("\nTheoretical sizes:");
676 println!("StorageKey size: {} bytes", size_of::<StorageKey>());
677 println!("StorageValue size: {} bytes", size_of::<StorageValue>());
678 println!("Option<StorageValue> size: {} bytes", size_of::<Option<StorageValue>>());
679 println!("Option<B256> size: {} bytes", size_of::<Option<B256>>());
680 }
681
682 #[test]
683 fn test_empty_storage_cached_state_provider() {
684 let address = Address::random();
686 let storage_key = StorageKey::random();
687 let account = ExtendedAccount::new(0, U256::ZERO);
688
689 let provider = MockEthProvider::default();
691 provider.extend_accounts(vec![(address, account)]);
692
693 let caches = ProviderCacheBuilder::default().build_caches(1000);
694 let state_provider =
695 CachedStateProvider::new_with_caches(provider, caches, CachedStateMetrics::zeroed());
696
697 let res = state_provider.storage(address, storage_key);
699 assert!(res.is_ok());
700 assert_eq!(res.unwrap(), None);
701 }
702
703 #[test]
704 fn test_uncached_storage_cached_state_provider() {
705 let address = Address::random();
707 let storage_key = StorageKey::random();
708 let storage_value = U256::from(1);
709 let account =
710 ExtendedAccount::new(0, U256::ZERO).extend_storage(vec![(storage_key, storage_value)]);
711
712 let provider = MockEthProvider::default();
714 provider.extend_accounts(vec![(address, account)]);
715
716 let caches = ProviderCacheBuilder::default().build_caches(1000);
717 let state_provider =
718 CachedStateProvider::new_with_caches(provider, caches, CachedStateMetrics::zeroed());
719
720 let res = state_provider.storage(address, storage_key);
722 assert!(res.is_ok());
723 assert_eq!(res.unwrap(), Some(storage_value));
724 }
725
726 #[test]
727 fn test_get_storage_populated() {
728 let address = Address::random();
730 let storage_key = StorageKey::random();
731 let storage_value = U256::from(1);
732
733 let caches = ProviderCacheBuilder::default().build_caches(1000);
735 caches.insert_storage(address, storage_key, Some(storage_value));
736
737 let slot_status = caches.get_storage(&address, &storage_key);
739 assert_eq!(slot_status, SlotStatus::Value(storage_value));
740 }
741
742 #[test]
743 fn test_get_storage_not_cached() {
744 let storage_key = StorageKey::random();
746 let address = Address::random();
747
748 let caches = ProviderCacheBuilder::default().build_caches(1000);
750
751 let slot_status = caches.get_storage(&address, &storage_key);
753 assert_eq!(slot_status, SlotStatus::NotCached);
754 }
755
756 #[test]
757 fn test_get_storage_empty() {
758 let address = Address::random();
761 let storage_key = StorageKey::random();
762
763 let caches = ProviderCacheBuilder::default().build_caches(1000);
765 caches.insert_storage(address, storage_key, None);
766
767 let slot_status = caches.get_storage(&address, &storage_key);
769 assert_eq!(slot_status, SlotStatus::Empty);
770 }
771}