Skip to main content

reth_evm/
cached.rs

1//! Database adapters for payload building.
2use alloy_primitives::{
3    map::{AddressMap, B256Map, Entry, HashMap, U256Map},
4    Address, B256, U256,
5};
6pub use evm2::{bytecode::Bytecode, evm::AccountInfo};
7use evm2::{evm::Database, interpreter::Word};
8
9/// A container type that caches reads from an underlying [`Database`].
10///
11/// This is intended to be used in conjunction with block executors and builders
12/// during payload building which repeatedly accesses the same data.
13///
14/// [`CachedReads::as_db_mut`] transforms this type into a [`Database`] implementation that uses
15/// [`CachedReads`] as a caching layer for operations, and records any cache misses.
16///
17/// # Example
18///
19/// ```
20/// use evm2::evm::Database;
21/// use reth_evm::cached::CachedReads;
22///
23/// fn build_payload<DB: Database>(db: DB) {
24///     let mut cached_reads = CachedReads::default();
25///     let db = cached_reads.as_db_mut(db);
26///     // this is `Database` and can be used to build a payload, it never commits to `CachedReads` or the underlying database, but all reads from the underlying database are cached in `CachedReads`.
27///     // Subsequent payload build attempts can use cached reads and avoid hitting the underlying database.
28///     // Note: `cached_reads` must outlive `db` to satisfy lifetime requirements.
29/// }
30/// ```
31#[derive(Debug, Clone, Default)]
32pub struct CachedReads {
33    /// Block state account with storage.
34    pub accounts: AddressMap<CachedAccount>,
35    /// Created contracts.
36    pub contracts: B256Map<Bytecode>,
37    /// Block hash mapped to the block number.
38    pub block_hashes: HashMap<u64, B256>,
39}
40
41// === impl CachedReads ===
42
43impl CachedReads {
44    /// Creates a new [`CachedReads`] with capacity for account entries.
45    pub fn with_account_capacity(capacity: usize) -> Self {
46        Self {
47            accounts: AddressMap::with_capacity_and_hasher(capacity, Default::default()),
48            ..Default::default()
49        }
50    }
51
52    /// Gets a mutable [`Database`] that will cache reads from the underlying database.
53    pub const fn as_db_mut<DB>(&mut self, db: DB) -> CachedReadsDbMut<'_, DB> {
54        CachedReadsDbMut { cached: self, db }
55    }
56
57    /// Inserts an account info into the cache.
58    pub fn insert_account(&mut self, address: Address, info: AccountInfo, storage: U256Map<U256>) {
59        self.accounts.insert(address, CachedAccount { info: Some(info), storage });
60    }
61
62    /// Extends current cache with entries from another [`CachedReads`] instance.
63    ///
64    /// Note: It is expected that both instances are based on the exact same state.
65    pub fn extend(&mut self, other: Self) {
66        self.accounts.extend(other.accounts);
67        self.contracts.extend(other.contracts);
68        self.block_hashes.extend(other.block_hashes);
69    }
70}
71
72/// A [Database] that caches reads inside [`CachedReads`].
73///
74/// The lifetime parameter `'a` is tied to the lifetime of the underlying [`CachedReads`] instance.
75/// This ensures that the cache remains valid for the entire duration this wrapper is used.
76/// The original [`CachedReads`] must outlive this wrapper to prevent use-after-free.
77#[derive(Debug)]
78pub struct CachedReadsDbMut<'a, DB> {
79    /// The cache of reads.
80    pub cached: &'a mut CachedReads,
81    /// The underlying database.
82    pub db: DB,
83}
84
85impl<'a, DB> CachedReadsDbMut<'a, DB> {
86    /// Returns access to wrapped [`Database`].
87    pub const fn inner(&self) -> &DB {
88        &self.db
89    }
90}
91
92impl<DB, T> AsRef<T> for CachedReadsDbMut<'_, DB>
93where
94    DB: AsRef<T>,
95{
96    fn as_ref(&self) -> &T {
97        self.inner().as_ref()
98    }
99}
100
101impl<DB: Database> Database for CachedReadsDbMut<'_, DB> {
102    type Error = DB::Error;
103
104    fn is_fatal(error: &Self::Error) -> bool {
105        DB::is_fatal(error)
106    }
107
108    fn get_account(&mut self, address: &Address) -> Result<Option<AccountInfo>, Self::Error> {
109        let basic = match self.cached.accounts.entry(*address) {
110            Entry::Occupied(entry) => entry.get().info.clone(),
111            Entry::Vacant(entry) => {
112                entry.insert(CachedAccount::new(self.db.get_account(address)?)).info.clone()
113            }
114        };
115        Ok(basic)
116    }
117
118    fn get_code_by_hash(&mut self, code_hash: &B256) -> Result<Bytecode, Self::Error> {
119        let code = match self.cached.contracts.entry(*code_hash) {
120            Entry::Occupied(entry) => entry.get().clone(),
121            Entry::Vacant(entry) => entry.insert(self.db.get_code_by_hash(code_hash)?).clone(),
122        };
123        Ok(code)
124    }
125
126    fn get_storage(&mut self, address: &Address, index: &Word) -> Result<Word, Self::Error> {
127        match self.cached.accounts.entry(*address) {
128            Entry::Occupied(mut acc_entry) => match acc_entry.get_mut().storage.entry(*index) {
129                Entry::Occupied(entry) => Ok(*entry.get()),
130                Entry::Vacant(entry) => Ok(*entry.insert(self.db.get_storage(address, index)?)),
131            },
132            Entry::Vacant(acc_entry) => {
133                // acc needs to be loaded for us to access slots.
134                let info = self.db.get_account(address)?;
135                let (account, value) = if info.is_some() {
136                    let value = self.db.get_storage(address, index)?;
137                    let mut account = CachedAccount::new(info);
138                    account.storage.insert(*index, value);
139                    (account, value)
140                } else {
141                    (CachedAccount::new(info), U256::ZERO)
142                };
143                acc_entry.insert(account);
144                Ok(value)
145            }
146        }
147    }
148
149    fn get_block_hash(&mut self, number: &Word) -> Result<B256, Self::Error> {
150        let number = number.saturating_to();
151        let hash = match self.cached.block_hashes.entry(number) {
152            Entry::Occupied(entry) => *entry.get(),
153            Entry::Vacant(entry) => *entry.insert(self.db.get_block_hash(&U256::from(number))?),
154        };
155        Ok(hash)
156    }
157}
158
159/// Cached account contains the account state with storage
160/// but lacks the account status.
161#[derive(Debug, Clone)]
162pub struct CachedAccount {
163    /// Account state.
164    pub info: Option<AccountInfo>,
165    /// Account's storage.
166    pub storage: U256Map<U256>,
167}
168
169impl CachedAccount {
170    fn new(info: Option<AccountInfo>) -> Self {
171        Self { info, storage: U256Map::default() }
172    }
173}
174
175#[cfg(test)]
176mod tests {
177    use super::*;
178    use core::cell::Cell;
179    use std::{error::Error, fmt, rc::Rc};
180
181    #[test]
182    fn test_extend_with_two_cached_reads() {
183        // Setup test data
184        let hash1 = B256::from_slice(&[1u8; 32]);
185        let hash2 = B256::from_slice(&[2u8; 32]);
186        let address1 = Address::from_slice(&[1u8; 20]);
187        let address2 = Address::from_slice(&[2u8; 20]);
188
189        // Create primary cache
190        let mut primary = {
191            let mut cache = CachedReads::default();
192            cache.accounts.insert(address1, CachedAccount::new(Some(AccountInfo::default())));
193            cache.contracts.insert(hash1, Bytecode::default());
194            cache.block_hashes.insert(1, hash1);
195            cache
196        };
197
198        // Create additional cache
199        let additional = {
200            let mut cache = CachedReads::default();
201            cache.accounts.insert(address2, CachedAccount::new(Some(AccountInfo::default())));
202            cache.contracts.insert(hash2, Bytecode::default());
203            cache.block_hashes.insert(2, hash2);
204            cache
205        };
206
207        // Extending primary with additional cache
208        primary.extend(additional);
209
210        // Verify the combined state
211        assert!(
212            primary.accounts.len() == 2 &&
213                primary.contracts.len() == 2 &&
214                primary.block_hashes.len() == 2,
215            "All maps should contain 2 entries"
216        );
217
218        // Verify specific entries
219        assert!(
220            primary.accounts.contains_key(&address1) &&
221                primary.accounts.contains_key(&address2) &&
222                primary.contracts.contains_key(&hash1) &&
223                primary.contracts.contains_key(&hash2) &&
224                primary.block_hashes.get(&1) == Some(&hash1) &&
225                primary.block_hashes.get(&2) == Some(&hash2),
226            "All expected entries should be present"
227        );
228    }
229
230    #[derive(Clone, Debug)]
231    struct CountingDb {
232        account: Option<AccountInfo>,
233        code: Bytecode,
234        storage: Word,
235        block_hash: B256,
236        reads: Rc<ReadCounts>,
237    }
238
239    #[derive(Debug, Default)]
240    struct ReadCounts {
241        accounts: Cell<usize>,
242        code: Cell<usize>,
243        storage: Cell<usize>,
244        block_hashes: Cell<usize>,
245    }
246
247    #[derive(Debug)]
248    struct TestError;
249
250    impl fmt::Display for TestError {
251        fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
252            f.write_str("test database error")
253        }
254    }
255
256    impl Error for TestError {}
257
258    impl Database for CountingDb {
259        type Error = TestError;
260
261        fn get_account(&mut self, _address: &Address) -> Result<Option<AccountInfo>, Self::Error> {
262            self.reads.accounts.set(self.reads.accounts.get() + 1);
263            Ok(self.account.clone())
264        }
265
266        fn get_code_by_hash(&mut self, _code_hash: &B256) -> Result<Bytecode, Self::Error> {
267            self.reads.code.set(self.reads.code.get() + 1);
268            Ok(self.code.clone())
269        }
270
271        fn get_storage(&mut self, _address: &Address, _key: &Word) -> Result<Word, Self::Error> {
272            self.reads.storage.set(self.reads.storage.get() + 1);
273            Ok(self.storage)
274        }
275
276        fn get_block_hash(&mut self, _number: &Word) -> Result<B256, Self::Error> {
277            self.reads.block_hashes.set(self.reads.block_hashes.get() + 1);
278            Ok(self.block_hash)
279        }
280    }
281
282    #[test]
283    fn cached_reads_database_avoids_repeated_backing_reads() {
284        let address = Address::repeat_byte(1);
285        let code_hash = B256::repeat_byte(2);
286        let block_hash = B256::repeat_byte(3);
287        let key = U256::from(4);
288        let value = U256::from(5);
289        let reads = Rc::new(ReadCounts::default());
290        let db = CountingDb {
291            account: Some(AccountInfo::new(U256::from(1), 2, code_hash, None)),
292            code: Bytecode::new_raw([0x00].as_slice().into()),
293            storage: value,
294            block_hash,
295            reads: reads.clone(),
296        };
297
298        let mut cached = CachedReads::default();
299        let mut db = cached.as_db_mut(db);
300
301        assert!(db.get_account(&address).unwrap().is_some());
302        assert!(db.get_account(&address).unwrap().is_some());
303        assert_eq!(db.get_code_by_hash(&code_hash).unwrap().original_bytes().as_ref(), [0x00]);
304        assert_eq!(db.get_code_by_hash(&code_hash).unwrap().original_bytes().as_ref(), [0x00]);
305        assert_eq!(db.get_storage(&address, &key).unwrap(), value);
306        assert_eq!(db.get_storage(&address, &key).unwrap(), value);
307        assert_eq!(db.get_block_hash(&U256::from(1)).unwrap(), block_hash);
308        assert_eq!(db.get_block_hash(&U256::from(1)).unwrap(), block_hash);
309
310        assert_eq!(reads.accounts.get(), 1);
311        assert_eq!(reads.code.get(), 1);
312        assert_eq!(reads.storage.get(), 1);
313        assert_eq!(reads.block_hashes.get(), 1);
314
315        assert!(cached.accounts.contains_key(&address));
316        assert!(cached.contracts.contains_key(&code_hash));
317        assert_eq!(cached.block_hashes.get(&1), Some(&block_hash));
318    }
319}