kabu_evm_db/
fast_cache_db.rs

1use std::vec::Vec;
2
3use crate::fast_hasher::SimpleBuildHasher;
4use alloy::primitives::BlockNumber;
5use alloy::primitives::map::{Entry, HashMap};
6use alloy::{
7    consensus::constants::KECCAK_EMPTY,
8    primitives::{Address, B256, Log, U256},
9};
10use revm::database::AccountState;
11use revm::state::{Account, AccountInfo, Bytecode};
12use revm::{Database, DatabaseCommit, DatabaseRef};
13
14/// A [Database] implementation that stores all state changes in memory.
15///
16/// This implementation wraps a [DatabaseRef] that is used to load data ([AccountInfo]).
17///
18/// Accounts and code are stored in two separate maps, the `accounts` map maps addresses to [FastDbAccount],
19/// whereas contracts are identified by their code hash, and are stored in the `contracts` map.
20/// The [FastDbAccount] holds the code hash of the contract, which is used to look up the contract in the `contracts` map.
21#[derive(Debug, Clone)]
22pub struct FastCacheDB<ExtDB> {
23    /// Account info where None means it is not existing. Not existing state is needed for Pre TANGERINE forks.
24    /// `code` is always `None`, and bytecode can be found in `contracts`.
25    pub accounts: HashMap<Address, FastDbAccount>,
26    /// Tracks all contracts by their code hash.
27    pub contracts: HashMap<B256, Bytecode, SimpleBuildHasher>,
28    /// All logs that were committed via [DatabaseCommit::commit].
29    pub logs: Vec<Log>,
30    /// All cached block hashes from the [DatabaseRef].
31    pub block_hashes: HashMap<BlockNumber, B256>,
32    /// The underlying database ([DatabaseRef]) that is used to load data.
33    ///
34    /// Note: this is read-only, data is never written to this database.
35    pub db: ExtDB,
36}
37
38impl<ExtDB: Default + DatabaseRef> Default for FastCacheDB<ExtDB> {
39    fn default() -> Self {
40        Self::new(ExtDB::default())
41    }
42}
43
44impl<ExtDB> FastCacheDB<ExtDB> {
45    pub fn new(db: ExtDB) -> Self
46    where
47        ExtDB: DatabaseRef,
48    {
49        let mut contracts = HashMap::with_hasher(SimpleBuildHasher::default());
50        contracts.insert(KECCAK_EMPTY, Bytecode::default());
51        contracts.insert(B256::ZERO, Bytecode::default());
52        Self { accounts: HashMap::default(), contracts, logs: Vec::default(), block_hashes: HashMap::default(), db }
53    }
54
55    /// Inserts the account's code into the cache.
56    ///
57    /// Accounts objects and code are stored separately in the cache, this will take the code from the account and instead map it to the code hash.
58    ///
59    /// Note: This will not insert into the underlying external database.
60    pub fn insert_contract(&mut self, account: &mut AccountInfo) {
61        if let Some(code) = &account.code
62            && !code.is_empty()
63        {
64            if account.code_hash == KECCAK_EMPTY {
65                account.code_hash = code.hash_slow();
66            }
67            self.contracts.entry(account.code_hash).or_insert_with(|| code.clone());
68        }
69        if account.code_hash == B256::ZERO {
70            account.code_hash = KECCAK_EMPTY;
71        }
72    }
73
74    /// Insert account info but not override storage
75    pub fn insert_account_info(&mut self, address: Address, mut info: AccountInfo) {
76        self.insert_contract(&mut info);
77        self.accounts.entry(address).or_default().info = info;
78    }
79}
80
81impl<ExtDB: DatabaseRef> FastCacheDB<ExtDB> {
82    /// Returns the account for the given address.
83    ///
84    /// If the account was not found in the cache, it will be loaded from the underlying database.
85    pub fn load_account(&mut self, address: Address) -> Result<&mut FastDbAccount, ExtDB::Error> {
86        let db = &self.db;
87        match self.accounts.entry(address) {
88            Entry::Occupied(entry) => Ok(entry.into_mut()),
89            Entry::Vacant(entry) => Ok(entry.insert(
90                db.basic_ref(address)?
91                    .map(|info| FastDbAccount { info, ..Default::default() })
92                    .unwrap_or_else(FastDbAccount::new_not_existing),
93            )),
94        }
95    }
96
97    /// insert account storage without overriding account info
98    pub fn insert_account_storage(&mut self, address: Address, slot: U256, value: U256) -> Result<(), ExtDB::Error> {
99        let account = self.load_account(address)?;
100        account.storage.insert(slot, value);
101        Ok(())
102    }
103
104    /// replace account storage without overriding account info
105    pub fn replace_account_storage(&mut self, address: Address, storage: HashMap<U256, U256>) -> Result<(), ExtDB::Error> {
106        let account = self.load_account(address)?;
107        account.account_state = AccountState::StorageCleared;
108        account.storage = storage.into_iter().collect();
109        Ok(())
110    }
111}
112
113impl<ExtDB> DatabaseCommit for FastCacheDB<ExtDB> {
114    fn commit(&mut self, changes: HashMap<Address, Account>) {
115        for (address, mut account) in changes {
116            if !account.is_touched() {
117                continue;
118            }
119            if account.is_selfdestructed() {
120                let db_account = self.accounts.entry(address).or_default();
121                db_account.storage.clear();
122                db_account.account_state = AccountState::NotExisting;
123                db_account.info = AccountInfo::default();
124                continue;
125            }
126            let is_newly_created = account.is_created();
127            self.insert_contract(&mut account.info);
128
129            let db_account = self.accounts.entry(address).or_default();
130            db_account.info = account.info;
131
132            db_account.account_state = if is_newly_created {
133                db_account.storage.clear();
134                AccountState::StorageCleared
135            } else if db_account.account_state.is_storage_cleared() {
136                // Preserve old account state if it already exists
137                AccountState::StorageCleared
138            } else {
139                AccountState::Touched
140            };
141            db_account.storage.extend(account.storage.into_iter().map(|(key, value)| (key, value.present_value())));
142        }
143    }
144}
145
146impl<ExtDB: DatabaseRef> Database for FastCacheDB<ExtDB> {
147    type Error = ExtDB::Error;
148
149    fn basic(&mut self, address: Address) -> Result<Option<AccountInfo>, Self::Error> {
150        let basic = match self.accounts.entry(address) {
151            Entry::Occupied(entry) => entry.into_mut(),
152            Entry::Vacant(entry) => entry.insert(
153                self.db
154                    .basic_ref(address)?
155                    .map(|info| FastDbAccount { info, ..Default::default() })
156                    .unwrap_or_else(FastDbAccount::new_not_existing),
157            ),
158        };
159        Ok(basic.info())
160    }
161
162    fn code_by_hash(&mut self, code_hash: B256) -> Result<Bytecode, Self::Error> {
163        match self.contracts.entry(code_hash) {
164            Entry::Occupied(entry) => Ok(entry.get().clone()),
165            Entry::Vacant(entry) => {
166                // if you return code bytes when basic fn is called this function is not needed.
167                Ok(entry.insert(self.db.code_by_hash_ref(code_hash)?).clone())
168            }
169        }
170    }
171
172    /// Get the value in an account's storage slot.
173    ///
174    /// It is assumed that account is already loaded.
175    fn storage(&mut self, address: Address, index: U256) -> Result<U256, Self::Error> {
176        match self.accounts.entry(address) {
177            Entry::Occupied(mut acc_entry) => {
178                let acc_entry = acc_entry.get_mut();
179                match acc_entry.storage.entry(index) {
180                    Entry::Occupied(entry) => Ok(*entry.get()),
181                    Entry::Vacant(entry) => {
182                        if matches!(acc_entry.account_state, AccountState::StorageCleared | AccountState::NotExisting) {
183                            Ok(U256::ZERO)
184                        } else {
185                            let slot = self.db.storage_ref(address, index)?;
186                            entry.insert(slot);
187                            Ok(slot)
188                        }
189                    }
190                }
191            }
192            Entry::Vacant(acc_entry) => {
193                // acc needs to be loaded for us to access slots.
194                let info = self.db.basic_ref(address)?;
195                let (account, value) = if info.is_some() {
196                    let value = self.db.storage_ref(address, index)?;
197                    let mut account: FastDbAccount = info.into();
198                    account.storage.insert(index, value);
199                    (account, value)
200                } else {
201                    (info.into(), U256::ZERO)
202                };
203                acc_entry.insert(account);
204                Ok(value)
205            }
206        }
207    }
208
209    fn block_hash(&mut self, number: BlockNumber) -> Result<B256, Self::Error> {
210        match self.block_hashes.entry(number) {
211            Entry::Occupied(entry) => Ok(*entry.get()),
212            Entry::Vacant(entry) => {
213                let hash = self.db.block_hash_ref(number)?;
214                entry.insert(hash);
215                Ok(hash)
216            }
217        }
218    }
219}
220
221impl<ExtDB: DatabaseRef> DatabaseRef for FastCacheDB<ExtDB> {
222    type Error = ExtDB::Error;
223
224    fn basic_ref(&self, address: Address) -> Result<Option<AccountInfo>, Self::Error> {
225        match self.accounts.get(&address) {
226            Some(acc) => Ok(acc.info()),
227            None => self.db.basic_ref(address),
228        }
229    }
230
231    fn code_by_hash_ref(&self, code_hash: B256) -> Result<Bytecode, Self::Error> {
232        match self.contracts.get(&code_hash) {
233            Some(entry) => Ok(entry.clone()),
234            None => self.db.code_by_hash_ref(code_hash),
235        }
236    }
237
238    fn storage_ref(&self, address: Address, index: U256) -> Result<U256, Self::Error> {
239        match self.accounts.get(&address) {
240            Some(acc_entry) => match acc_entry.storage.get(&index) {
241                Some(entry) => Ok(*entry),
242                None => {
243                    if matches!(acc_entry.account_state, AccountState::StorageCleared | AccountState::NotExisting) {
244                        Ok(U256::ZERO)
245                    } else {
246                        self.db.storage_ref(address, index)
247                    }
248                }
249            },
250            None => self.db.storage_ref(address, index),
251        }
252    }
253
254    fn block_hash_ref(&self, number: BlockNumber) -> Result<B256, Self::Error> {
255        match self.block_hashes.get(&number) {
256            Some(entry) => Ok(*entry),
257            None => self.db.block_hash_ref(number),
258        }
259    }
260}
261
262#[derive(Debug, Clone, Default)]
263pub struct FastDbAccount {
264    pub info: AccountInfo,
265    /// If account is selfdestructed or newly created, storage will be cleared.
266    pub account_state: AccountState,
267    /// storage slots
268    pub storage: HashMap<U256, U256, SimpleBuildHasher>,
269}
270
271impl FastDbAccount {
272    pub fn new_not_existing() -> Self {
273        Self { account_state: AccountState::NotExisting, ..Default::default() }
274    }
275
276    pub fn info(&self) -> Option<AccountInfo> {
277        if matches!(self.account_state, AccountState::NotExisting) { None } else { Some(self.info.clone()) }
278    }
279}
280
281impl From<Option<AccountInfo>> for FastDbAccount {
282    fn from(from: Option<AccountInfo>) -> Self {
283        from.map(Self::from).unwrap_or_else(Self::new_not_existing)
284    }
285}
286
287impl From<AccountInfo> for FastDbAccount {
288    fn from(info: AccountInfo) -> Self {
289        Self { info, account_state: AccountState::None, ..Default::default() }
290    }
291}
292
293#[cfg(test)]
294mod tests {
295    use alloy::primitives::map::HashMap;
296    use std::collections::BTreeMap;
297    use std::sync::Arc;
298
299    use super::FastCacheDB;
300    use crate::in_memory_db::LoomInMemoryDB;
301    use alloy::primitives::{B256, Bytes};
302    use alloy::rpc::types::trace::geth::AccountState as GethAccountState;
303    use revm::database::EmptyDB;
304    use revm::primitives::{Address, I256, KECCAK_EMPTY, U256};
305    use revm::state::{AccountInfo, Bytecode};
306    use revm::{Database, DatabaseRef};
307
308    #[test]
309    fn test_insert_account_storage() {
310        let account = Address::with_last_byte(42);
311        let nonce = 42;
312        let mut init_state = FastCacheDB::new(EmptyDB::default());
313        init_state.insert_account_info(account, AccountInfo { nonce, ..Default::default() });
314
315        let (key, value) = (U256::from(123), U256::from(456));
316        let mut new_state = FastCacheDB::new(init_state);
317        new_state.insert_account_storage(account, key, value).unwrap();
318
319        assert_eq!(new_state.basic(account).unwrap().unwrap().nonce, nonce);
320        assert_eq!(new_state.storage(account, key), Ok(value));
321    }
322
323    #[test]
324    fn test_insert_account_storage_inherited() {
325        let account = Address::with_last_byte(42);
326        let nonce = 42;
327        let mut init_state = FastCacheDB::new(EmptyDB::default());
328        init_state.insert_account_info(account, AccountInfo { nonce, ..Default::default() });
329
330        let (key, value) = (U256::from(123), U256::from(456));
331        let mut new_state = FastCacheDB::new(init_state);
332        new_state.insert_account_storage(account, key, value).unwrap();
333
334        assert_eq!(new_state.basic(account).unwrap().unwrap().nonce, nonce);
335        assert_eq!(new_state.storage(account, key), Ok(value));
336    }
337
338    #[test]
339    fn test_replace_account_storage() {
340        let account = Address::with_last_byte(42);
341        let nonce = 42;
342        let mut init_state = FastCacheDB::new(EmptyDB::default());
343        init_state.insert_account_info(account, AccountInfo { nonce, ..Default::default() });
344
345        let (key0, value0) = (U256::from(123), U256::from(456));
346        let (key1, value1) = (U256::from(789), U256::from(999));
347        init_state.insert_account_storage(account, key0, value0).unwrap();
348
349        let mut new_state = LoomInMemoryDB::new(Arc::new(init_state));
350        assert_eq!(new_state.accounts.len(), 0);
351        let mut hm: HashMap<U256, U256> = Default::default();
352        hm.insert(key1, value1);
353
354        new_state.replace_account_storage(account, hm).unwrap();
355
356        let mut new_state = new_state.merge();
357
358        assert_eq!(new_state.basic(account).unwrap().unwrap().nonce, nonce);
359        assert_eq!(new_state.storage(account, key0).unwrap(), value0);
360        assert_eq!(new_state.storage(account, key1).unwrap(), value1);
361        assert_eq!(new_state.accounts.len(), 1);
362    }
363
364    #[test]
365    fn test_apply_geth_update() {
366        let account = Address::with_last_byte(42);
367        let nonce = 42;
368        let code = Bytecode::new_raw(Bytes::from(vec![1, 2, 3]));
369        let mut init_state = FastCacheDB::new(EmptyDB::new());
370        init_state.insert_account_info(account, AccountInfo { nonce, code: Some(code.clone()), ..Default::default() });
371
372        let (key0, value0) = (U256::from(123), U256::from(456));
373        let (key1, value1) = (U256::from(789), U256::from(999));
374        init_state.insert_account_storage(account, key0, value0).unwrap();
375        init_state.insert_account_storage(account, key1, value1).unwrap();
376
377        let mut new_state = LoomInMemoryDB::new(Arc::new(init_state));
378        assert_eq!(new_state.accounts.len(), 0);
379
380        let update_record = GethAccountState {
381            balance: None,
382            code: Some(Bytes::from(vec![1, 2, 3])),
383            nonce: Some(nonce + 1),
384            storage: [(B256::from(I256::try_from(123).unwrap()), B256::from(I256::try_from(333).unwrap()))].into(),
385        };
386
387        let update: BTreeMap<Address, GethAccountState> = [(account, update_record)].into();
388
389        new_state.apply_geth_update(update);
390
391        assert_eq!(new_state.basic(account).unwrap().unwrap().code, Some(code.clone()));
392        assert_eq!(new_state.basic(account).unwrap().unwrap().nonce, nonce + 1);
393        assert_eq!(new_state.storage_ref(account, key0), Ok(U256::from(333)));
394        assert_eq!(new_state.storage_ref(account, key1), Ok(value1));
395        assert_eq!(new_state.accounts.len(), 1);
396
397        let mut new_state = new_state.merge();
398
399        assert_eq!(new_state.basic(account).unwrap().unwrap().code, Some(code.clone()));
400        assert_eq!(new_state.basic(account).unwrap().unwrap().nonce, nonce + 1);
401        assert_eq!(new_state.storage_ref(account, key0).unwrap(), U256::from(333));
402        assert_eq!(new_state.storage_ref(account, key1).unwrap(), value1);
403        assert_eq!(new_state.accounts.len(), 1);
404    }
405
406    #[test]
407    fn test_merge() {
408        let account = Address::with_last_byte(42);
409        let nonce = 42;
410        let code = Bytecode::new_raw(Bytes::from(vec![1, 2, 3]));
411        let mut init_state = FastCacheDB::new(EmptyDB::default());
412        init_state.insert_account_info(account, AccountInfo { nonce, code: Some(code.clone()), ..Default::default() });
413
414        let (key0, value0) = (U256::from(123), U256::from(456));
415        let (key1, value1) = (U256::from(789), U256::from(999));
416        let (key2, value2) = (U256::from(999), U256::from(111));
417        init_state.insert_account_storage(account, key0, value0).unwrap();
418        init_state.insert_account_storage(account, key1, value1).unwrap();
419
420        let mut new_state = LoomInMemoryDB::new(Arc::new(init_state));
421        assert_eq!(new_state.accounts.len(), 0);
422
423        new_state.insert_account_info(
424            account,
425            AccountInfo {
426                balance: U256::ZERO,
427                code: Some(Bytecode::new_raw(Bytes::from(vec![1, 2, 2]))),
428                nonce: nonce + 1,
429                code_hash: KECCAK_EMPTY,
430            },
431        );
432
433        new_state.insert_account_storage(account, key0, U256::from(333)).unwrap();
434        new_state.insert_account_storage(account, key2, value2).unwrap();
435
436        let mut new_state = new_state.merge();
437
438        assert_eq!(new_state.basic(account).unwrap().unwrap().code, Some(Bytecode::new_raw(Bytes::from(vec![1, 2, 2]))));
439        assert_eq!(new_state.basic(account).unwrap().unwrap().nonce, nonce + 1);
440        assert_eq!(new_state.storage_ref(account, key0).unwrap(), U256::from(333));
441        assert_eq!(new_state.storage_ref(account, key1).unwrap(), value1);
442        assert_eq!(new_state.storage_ref(account, key2).unwrap(), value2);
443        assert_eq!(new_state.accounts.len(), 1);
444    }
445
446    #[test]
447    fn test_update_cell() {
448        let account = Address::with_last_byte(42);
449        let account2 = Address::with_last_byte(43);
450        let nonce = 42;
451        let code = Bytecode::new_raw(Bytes::from(vec![1, 2, 3]));
452        let mut init_state = FastCacheDB::new(EmptyDB::default());
453        init_state.insert_account_info(account, AccountInfo { nonce, code: Some(code.clone()), ..Default::default() });
454
455        let (key0, value0) = (U256::from(123), U256::from(456));
456        let (key1, value1) = (U256::from(789), U256::from(999));
457        let (key2, value2) = (U256::from(999), U256::from(111));
458        init_state.insert_account_storage(account, key0, value0).unwrap();
459        init_state.insert_account_storage(account, key1, value1).unwrap();
460
461        let mut new_state = LoomInMemoryDB::new(Arc::new(init_state));
462        assert_eq!(new_state.accounts.len(), 0);
463
464        new_state.insert_account_info(
465            account,
466            AccountInfo {
467                balance: U256::ZERO,
468                code: Some(Bytecode::new_raw(Bytes::from(vec![1, 2, 2]))),
469                nonce: nonce + 1,
470                code_hash: KECCAK_EMPTY,
471            },
472        );
473
474        new_state.insert_account_info(
475            account2,
476            AccountInfo {
477                balance: U256::ZERO,
478                code: Some(Bytecode::new_raw(Bytes::from(vec![1, 2, 2]))),
479                nonce: nonce + 1,
480                code_hash: KECCAK_EMPTY,
481            },
482        );
483
484        new_state.insert_account_storage(account, key0, U256::from(333)).unwrap();
485        new_state.insert_account_storage(account, key2, value2).unwrap();
486
487        let mut new_state = new_state.update_cells();
488
489        assert_eq!(new_state.basic(account).unwrap().unwrap().code, Some(Bytecode::new_raw(Bytes::from(vec![1, 2, 2]))));
490        assert_eq!(new_state.basic(account).unwrap().unwrap().nonce, nonce + 1);
491        assert_eq!(new_state.storage_ref(account, key0).unwrap(), U256::from(333));
492        assert_eq!(new_state.storage_ref(account, key1).unwrap(), value1);
493        assert_eq!(new_state.storage_ref(account, key2).unwrap(), U256::ZERO);
494        assert_eq!(new_state.storage_ref(account2, key0).unwrap(), U256::ZERO);
495        assert_eq!(new_state.accounts.len(), 1);
496        assert_eq!(new_state.basic(account2).unwrap(), None);
497        assert_eq!(new_state.accounts.len(), 2);
498        assert_eq!(new_state.basic(account2).unwrap(), None);
499        assert_eq!(new_state.accounts.len(), 2);
500    }
501
502    // #[ignore]
503    // #[test]
504    // fn test_serialize_deserialize_cachedb() {
505    //     let account = Address::with_last_byte(69);
506    //     let nonce = 420;
507    //     let mut init_state = FastCacheDB::new(EmptyDB::default());
508    //     init_state.insert_account_info(account, AccountInfo { nonce, ..Default::default() });
509    //
510    //     let serialized = serde_json::to_string(&init_state).unwrap();
511    //     let deserialized: FastCacheDB<EmptyDB> = serde_json::from_str(&serialized).unwrap();
512    //
513    //     assert!(deserialized.accounts.contains_key(&account));
514    //     assert_eq!(deserialized.accounts.get(&account).unwrap().info.nonce, nonce);
515    // }
516}