stratus/eth/storage/
cache.rs

1use std::hash::Hash;
2
3use clap::Parser;
4use display_json::DebugAsJson;
5use indexmap::Equivalent;
6use quick_cache::UnitWeighter;
7use quick_cache::sync::Cache;
8use quick_cache::sync::DefaultLifecycle;
9use quick_cache::sync::GuardResult;
10use rustc_hash::FxBuildHasher;
11
12use crate::eth::primitives::Account;
13use crate::eth::primitives::Address;
14use crate::eth::primitives::ExecutionChanges;
15use crate::eth::primitives::Slot;
16use crate::eth::primitives::SlotIndex;
17use crate::eth::primitives::SlotValue;
18
19pub struct StorageCache {
20    slot_cache: Cache<(Address, SlotIndex), SlotValue, UnitWeighter, FxBuildHasher>,
21    account_cache: Cache<Address, Account, UnitWeighter, FxBuildHasher>,
22    account_latest_cache: Cache<Address, Account, UnitWeighter, FxBuildHasher>,
23    slot_latest_cache: Cache<(Address, SlotIndex), SlotValue, UnitWeighter, FxBuildHasher>,
24}
25
26#[derive(DebugAsJson, Clone, Parser, serde::Serialize)]
27pub struct CacheConfig {
28    /// Capacity of slot cache
29    #[arg(long = "slot-cache-capacity", env = "SLOT_CACHE_CAPACITY", default_value = "100000")]
30    pub slot_cache_capacity: usize,
31
32    /// Capacity of account cache
33    #[arg(long = "account-cache-capacity", env = "ACCOUNT_CACHE_CAPACITY", default_value = "20000")]
34    pub account_cache_capacity: usize,
35
36    /// Capacity of account history cache
37    #[arg(long = "account-history-cache-capacity", env = "ACCOUNT_HISTORY_CACHE_CAPACITY", default_value = "20000")]
38    pub account_history_cache_capacity: usize,
39
40    /// Capacity of slot history cache
41    #[arg(long = "slot-history-cache-capacity", env = "SLOT_HISTORY_CACHE_CAPACITY", default_value = "100000")]
42    pub slot_history_cache_capacity: usize,
43}
44
45impl CacheConfig {
46    pub fn init(&self) -> StorageCache {
47        StorageCache::new(self)
48    }
49}
50
51impl StorageCache {
52    pub fn new(config: &CacheConfig) -> Self {
53        Self {
54            slot_cache: Cache::with(
55                config.slot_cache_capacity,
56                config.slot_cache_capacity as u64,
57                UnitWeighter,
58                FxBuildHasher,
59                DefaultLifecycle::default(),
60            ),
61            account_cache: Cache::with(
62                config.account_cache_capacity,
63                config.account_cache_capacity as u64,
64                UnitWeighter,
65                FxBuildHasher,
66                DefaultLifecycle::default(),
67            ),
68            account_latest_cache: Cache::with(
69                config.account_history_cache_capacity,
70                config.account_history_cache_capacity as u64,
71                UnitWeighter,
72                FxBuildHasher,
73                DefaultLifecycle::default(),
74            ),
75            slot_latest_cache: Cache::with(
76                config.slot_history_cache_capacity,
77                config.slot_history_cache_capacity as u64,
78                UnitWeighter,
79                FxBuildHasher,
80                DefaultLifecycle::default(),
81            ),
82        }
83    }
84
85    pub fn clear(&self) {
86        self.slot_cache.clear();
87        self.account_cache.clear();
88        self.account_latest_cache.clear();
89        self.slot_latest_cache.clear();
90    }
91
92    pub fn cache_slot_if_missing(&self, address: Address, slot: Slot) {
93        self.slot_cache.insert_if_missing((address, slot.index), slot.value);
94    }
95
96    pub fn cache_account_if_missing(&self, account: Account) {
97        self.account_cache.insert_if_missing(account.address, account);
98    }
99
100    fn _cache_account_and_slots_from_changes_impl(
101        changes: ExecutionChanges,
102        account_cache: &Cache<Address, Account, UnitWeighter, FxBuildHasher>,
103        slot_cache: &Cache<(Address, SlotIndex), SlotValue, UnitWeighter, FxBuildHasher>,
104    ) {
105        // cache accounts
106        for (address, change) in changes.accounts {
107            let account = (address, change).into();
108            account_cache.insert(address, account);
109        }
110
111        // cache slots
112        for ((address, index), value) in changes.slots {
113            slot_cache.insert((address, index), value);
114        }
115    }
116
117    pub fn cache_account_and_slots_from_changes(&self, changes: ExecutionChanges) {
118        Self::_cache_account_and_slots_from_changes_impl(changes, &self.account_cache, &self.slot_cache);
119    }
120
121    pub fn cache_account_and_slots_latest_from_changes(&self, changes: ExecutionChanges) {
122        Self::_cache_account_and_slots_from_changes_impl(changes, &self.account_latest_cache, &self.slot_latest_cache);
123    }
124
125    pub fn get_slot(&self, address: Address, index: SlotIndex) -> Option<Slot> {
126        self.slot_cache.get(&(address, index)).map(|value| Slot { value, index })
127    }
128
129    pub fn get_account(&self, address: Address) -> Option<Account> {
130        self.account_cache.get(&address)
131    }
132
133    pub fn cache_account_latest_if_missing(&self, address: Address, account: Account) {
134        self.account_latest_cache.insert_if_missing(address, account);
135    }
136
137    pub fn cache_slot_latest_if_missing(&self, address: Address, slot: Slot) {
138        self.slot_latest_cache.insert_if_missing((address, slot.index), slot.value);
139    }
140
141    pub fn get_account_latest(&self, address: Address) -> Option<Account> {
142        self.account_latest_cache.get(&address)
143    }
144
145    pub fn get_slot_latest(&self, address: Address, index: SlotIndex) -> Option<Slot> {
146        self.slot_latest_cache.get(&(address, index)).map(|value| Slot { value, index })
147    }
148}
149
150trait CacheExt<Key, Val> {
151    fn insert_if_missing(&self, key: Key, val: Val);
152}
153
154impl<Key, Val, We, B, L> CacheExt<Key, Val> for Cache<Key, Val, We, B, L>
155where
156    Key: Hash + Equivalent<Key> + ToOwned<Owned = Key> + std::cmp::Eq,
157    Val: Clone,
158    We: quick_cache::Weighter<Key, Val> + Clone,
159    B: std::hash::BuildHasher + Clone,
160    L: quick_cache::Lifecycle<Key, Val> + Clone,
161{
162    fn insert_if_missing(&self, key: Key, val: Val) {
163        match self.get_value_or_guard(&key, None) {
164            GuardResult::Value(_) => (),
165            GuardResult::Guard(g) => {
166                // this fails if an unguarded insert already inserted to this key
167                let _ = g.insert(val);
168            }
169            GuardResult::Timeout => unreachable!(),
170        }
171    }
172}