Skip to main content

reth_execution_cache/
txpool.rs

1//! Immutable snapshots produced by txpool-driven state prewarming.
2
3use alloy_primitives::{Address, StorageKey, StorageValue, B256, U256};
4use reth_primitives_traits::{Account, Bytecode};
5use reth_revm::cached::CachedReads;
6use std::sync::Arc;
7
8/// A deep, immutable txpool-prewarm cache snapshot for one parent state.
9///
10/// Wraps the [`CachedReads`] collected by speculative execution so cache tiers can serve lookups
11/// from it directly.
12#[derive(Clone, Debug)]
13pub struct TxPoolPrewarmCacheSnapshot {
14    parent_hash: B256,
15    reads: Arc<CachedReads>,
16}
17
18impl TxPoolPrewarmCacheSnapshot {
19    /// Creates a snapshot of `reads` collected against `parent_hash`'s state.
20    pub const fn new(parent_hash: B256, reads: Arc<CachedReads>) -> Self {
21        Self { parent_hash, reads }
22    }
23
24    /// Returns the hash of the state this snapshot was warmed against.
25    pub const fn parent_hash(&self) -> B256 {
26        self.parent_hash
27    }
28
29    /// Returns a cached account, preserving cached non-existence.
30    pub fn account(&self, address: &Address) -> Option<Option<Account>> {
31        self.reads.accounts.get(address).map(|account| account.info.as_ref().map(Account::from))
32    }
33
34    /// Returns a cached storage value, preserving cached zero values.
35    pub fn storage(&self, address: Address, key: StorageKey) -> Option<StorageValue> {
36        self.reads.accounts.get(&address)?.storage.get(&U256::from_be_bytes(key.0)).copied()
37    }
38
39    /// Returns cached bytecode.
40    ///
41    /// [`CachedReads`] stores empty bytecode for code it could not find, so an empty entry reads
42    /// as a miss and the caller's fallback tier decides.
43    pub fn bytecode(&self, code_hash: &B256) -> Option<Option<Bytecode>> {
44        let code = self.reads.contracts.get(code_hash)?;
45        (!code.is_empty()).then(|| Some(Bytecode(code.clone())))
46    }
47
48    /// Returns `(accounts, storage slots, bytecodes)` in the snapshot.
49    pub fn entry_counts(&self) -> (usize, usize, usize) {
50        (
51            self.reads.accounts.len(),
52            self.reads.accounts.values().map(|account| account.storage.len()).sum(),
53            self.reads.contracts.len(),
54        )
55    }
56}
57
58#[cfg(test)]
59mod tests {
60    use super::*;
61    use alloy_primitives::map::U256Map;
62    use reth_revm::{
63        cached::CachedAccount,
64        revm::{bytecode::Bytecode as RevmBytecode, state::AccountInfo},
65    };
66
67    #[test]
68    fn lookups_preserve_cache_semantics() {
69        let owner = Address::repeat_byte(0x01);
70        let missing = Address::repeat_byte(0x02);
71        let code_hash = B256::repeat_byte(0x0C);
72        let empty_code_hash = B256::repeat_byte(0x0D);
73
74        let mut reads = CachedReads::default();
75        let mut storage = U256Map::default();
76        storage.insert(U256::from(1), U256::from(7));
77        storage.insert(U256::from(2), U256::ZERO);
78        reads.insert_account(owner, AccountInfo { nonce: 3, ..Default::default() }, storage);
79        reads.accounts.insert(missing, CachedAccount { info: None, storage: Default::default() });
80        reads.contracts.insert(code_hash, RevmBytecode::new_raw([0x60, 0x01].into()));
81        reads.contracts.insert(empty_code_hash, RevmBytecode::default());
82
83        let snapshot = TxPoolPrewarmCacheSnapshot::new(B256::ZERO, Arc::new(reads));
84
85        assert_eq!(snapshot.account(&owner).unwrap().unwrap().nonce, 3);
86        assert_eq!(snapshot.account(&missing), Some(None), "non-existence is a cacheable fact");
87        assert_eq!(snapshot.account(&Address::repeat_byte(0x03)), None);
88
89        let slot = |n: u64| B256::from(U256::from(n));
90        assert_eq!(snapshot.storage(owner, slot(1)), Some(U256::from(7)));
91        assert_eq!(snapshot.storage(owner, slot(2)), Some(U256::ZERO), "zero values are hits");
92        assert_eq!(snapshot.storage(owner, slot(9)), None);
93        assert_eq!(snapshot.storage(missing, slot(1)), None);
94
95        assert!(snapshot.bytecode(&code_hash).unwrap().is_some());
96        assert_eq!(snapshot.bytecode(&empty_code_hash), None, "empty code reads as a miss");
97        assert_eq!(snapshot.bytecode(&B256::repeat_byte(0x0E)), None);
98
99        assert_eq!(snapshot.entry_counts(), (2, 2, 2));
100    }
101}