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