Skip to main content

reth_rpc_eth_types/cache/
db.rs

1//! EVM database types and state/block override helpers used by the RPC state cache.
2
3use crate::error::StateOverrideError;
4use alloy_eip7928::{bal::DecodedBal, BlockAccessIndex};
5use alloy_primitives::{map::DefaultHashBuilder, Bytes, U256};
6use alloy_rpc_types_eth::{state::StateOverride, BlockOverrides};
7use evm2::{
8    bytecode::{Bytecode, BytecodeDecodeError},
9    constants::MAX_CODE_SIZE_AMSTERDAM,
10    evm::{
11        AccountChangeRef, Bal as EvmBal, CacheDB, Db, DynDatabase, StateChangeSink, StorageChange,
12    },
13};
14use fixed_cache::Cache;
15use reth_evm::{database::StateProviderDatabase, BundleState, EvmEnv};
16use reth_execution_types::{BlockState, TransactionChanges};
17use reth_storage_api::{EvmStateProviderAdapter, StateProviderBox};
18use std::sync::{Arc, LazyLock};
19
20/// Helper alias type for cached state access.
21pub type StateCacheDb =
22    CacheDB<Db<StateProviderDatabase<EvmStateProviderAdapter<StateProviderBox>>>>;
23
24/// Number of code overrides kept in [`OVERRIDE_BYTECODES`].
25const OVERRIDE_BYTECODES_CAPACITY: usize = 256;
26
27/// Bytecode of recently applied code overrides, keyed by the override code.
28///
29/// Callers often override the same contract code on every request. Reusing its [`Bytecode`] keeps
30/// the code hash and jump table, which are otherwise recomputed for every request. Each code maps
31/// to one slot: a different code hashing to the same slot replaces it, and a lookup that races
32/// another access to its slot misses. Only code up to [`MAX_CODE_SIZE_AMSTERDAM`] is cached, which
33/// bounds the cache to about 18 MiB.
34static OVERRIDE_BYTECODES: LazyLock<Cache<Bytes, Bytecode, DefaultHashBuilder>> =
35    LazyLock::new(|| Cache::new(OVERRIDE_BYTECODES_CAPACITY, DefaultHashBuilder::default()));
36
37/// Applies RPC block overrides to an evm2 environment and state cache.
38pub fn apply_block_overrides<DB>(
39    overrides: BlockOverrides,
40    db: &mut CacheDB<DB>,
41    evm_env: &mut impl EvmEnv,
42) {
43    let BlockOverrides {
44        number,
45        difficulty,
46        time,
47        gas_limit,
48        coinbase,
49        random,
50        base_fee,
51        blob_base_fee,
52        block_hash,
53        ..
54    } = overrides;
55
56    if let Some(block_hashes) = block_hash {
57        for (number, hash) in block_hashes {
58            db.insert_block_hash(&U256::from(number), &hash);
59        }
60    }
61
62    let block = evm_env.block_env_mut();
63    if let Some(number) = number {
64        block.number = number;
65    }
66    if let Some(difficulty) = difficulty {
67        block.difficulty = difficulty;
68    }
69    if let Some(time) = time {
70        block.timestamp = U256::from(time);
71    }
72    if let Some(gas_limit) = gas_limit {
73        block.gas_limit = U256::from(gas_limit);
74    }
75    if let Some(coinbase) = coinbase {
76        block.beneficiary = coinbase;
77    }
78    if let Some(random) = random {
79        block.prevrandao = U256::from_be_slice(random.as_slice());
80    }
81    if let Some(base_fee) = base_fee {
82        block.basefee = U256::from(base_fee.saturating_to::<u64>());
83    }
84    if let Some(blob_base_fee) = blob_base_fee {
85        block.blob_basefee = U256::from(blob_base_fee.saturating_to::<u128>());
86    }
87}
88
89/// Applies RPC state overrides to an evm2 state cache.
90pub fn apply_state_overrides<DB: DynDatabase>(
91    overrides: StateOverride,
92    db: &mut CacheDB<DB>,
93) -> Result<(), StateOverrideError<evm2::DatabaseError>> {
94    apply_state_overrides_with_state(overrides, db, None)
95}
96
97/// Applies overrides and optionally records them for simulated state-root computation.
98pub fn apply_state_overrides_with_state<DB: DynDatabase>(
99    overrides: StateOverride,
100    db: &mut CacheDB<DB>,
101    state: Option<&mut BundleState>,
102) -> Result<(), StateOverrideError<evm2::DatabaseError>> {
103    let mut changes = state.is_some().then(TransactionChanges::default);
104    for (address, account_override) in overrides {
105        let original = db.get_account(&address).map_err(StateOverrideError::Database)?;
106        let mut account = original.clone().unwrap_or_default();
107
108        if let Some(nonce) = account_override.nonce {
109            account.nonce = nonce;
110        }
111        if let Some(code) = account_override.code {
112            account.set_code(override_bytecode(code)?);
113        }
114        if let Some(balance) = account_override.balance {
115            account.balance = balance;
116        }
117
118        let replaces_storage = account_override.state.is_some();
119        let storage = match (account_override.state, account_override.state_diff) {
120            (Some(_), Some(_)) => return Err(StateOverrideError::BothStateAndStateDiff(address)),
121            (Some(state), None) => {
122                db.cache.storage.entry(address).or_default().wipe();
123                if let Some(changes) = changes.as_mut() {
124                    let Ok(()) = changes.storage_wipe(address);
125                }
126                Some(state)
127            }
128            (None, Some(state)) => Some(state),
129            (None, None) => None,
130        };
131
132        // Account existence affects authorization refunds and new-account gas charges. The
133        // override touches the account, so under EIP-161 an empty result is removed regardless of
134        // whether the account existed before, unless storage materializes it. A full storage
135        // replacement always materializes the account, even with no slots.
136        if !account.is_empty() ||
137            replaces_storage ||
138            storage.as_ref().is_some_and(|state| !state.is_empty())
139        {
140            if let Some(changes) = changes.as_mut() {
141                let Ok(()) = changes.account(AccountChangeRef {
142                    address,
143                    original: original.as_ref(),
144                    current: Some(&account),
145                    created: original.is_none(),
146                    selfdestructed: false,
147                });
148            }
149            db.insert_account_info(&address, account);
150        } else if original.is_some() {
151            if let Some(changes) = changes.as_mut() {
152                let Ok(()) = changes.storage_wipe(address);
153                let Ok(()) = changes.account(AccountChangeRef {
154                    address,
155                    original: original.as_ref(),
156                    current: None,
157                    created: false,
158                    selfdestructed: true,
159                });
160            }
161            db.cache.accounts.insert(address, None);
162            db.cache.storage.entry(address).or_default().wipe();
163        }
164        if let Some(storage) = storage {
165            for (key, value) in storage {
166                let key = U256::from_be_bytes(key.0);
167                let current = U256::from_be_bytes(value.0);
168                if let Some(changes) = changes.as_mut() {
169                    let original =
170                        db.get_storage(&address, &key).map_err(StateOverrideError::Database)?;
171                    let Ok(()) = changes.storage(StorageChange { address, key, original, current });
172                }
173                db.insert_account_storage(&address, &key, &current);
174            }
175        }
176    }
177
178    if let Some(state) = state &&
179        let Some(changes) = changes
180    {
181        let mut block = BlockState::new();
182        block.commit(&changes);
183        state.extend(block.into_bundle());
184    }
185
186    Ok(())
187}
188
189/// Returns the [`Bytecode`] for override `code`, reusing the one of an earlier override of the same
190/// code.
191fn override_bytecode(code: Bytes) -> Result<Bytecode, BytecodeDecodeError> {
192    if code.is_empty() || code.len() > MAX_CODE_SIZE_AMSTERDAM {
193        return Bytecode::new_raw_checked(code)
194    }
195    if let Some(bytecode) = OVERRIDE_BYTECODES.get(&code) {
196        return Ok(bytecode)
197    }
198
199    let bytecode = Bytecode::new_raw_checked(code)?;
200    bytecode.hash_slow();
201    // The key shares the bytecode's buffer, so the cache holds each code once.
202    OVERRIDE_BYTECODES.insert(bytecode.original_bytes(), bytecode.clone());
203    Ok(bytecode)
204}
205
206/// Attaches `bal` to the database, positioned at the state right before the transaction at
207/// `tx_index`.
208///
209/// Reads served by the attached BAL reflect all writes prior to the transaction, including the
210/// block's pre-execution system calls. Reads not covered by the BAL fall back to the underlying
211/// database, which holds the correct values for all state the block does not touch.
212///
213/// Note: changes must not be committed to the database afterwards, because the attached BAL takes
214/// precedence over committed state when serving reads.
215#[inline]
216pub fn attach_bal_before_tx<DB: DynDatabase>(
217    db: &mut CacheDB<DB>,
218    bal: &DecodedBal<Arc<EvmBal>>,
219    tx_index: usize,
220) {
221    db.bal_context.set_bal(bal.as_bal().clone());
222    db.bal_context.set_allow_db_fallback(true);
223    db.bal_context.set_bal_index(BlockAccessIndex::from_tx_index(tx_index as u64));
224}
225
226#[cfg(test)]
227mod tests {
228
229    use super::*;
230    use alloy_eip7928::{BalanceChange, StorageChange};
231    use alloy_primitives::{keccak256, Address, Bytes, B256, U256};
232    use alloy_rpc_types_eth::state::AccountOverride;
233    use evm2::evm::{AccountBal, AccountInfo, Bal, BalChanges, InMemoryDB};
234
235    #[test]
236    fn state_overrides_remove_accounts_left_empty() {
237        let emptied = Address::with_last_byte(1);
238        let absent = Address::with_last_byte(2);
239        let replaced = Address::with_last_byte(3);
240        let slot = U256::ONE;
241
242        let mut db = InMemoryDB::default();
243        for address in [emptied, replaced] {
244            db.insert_account_info(
245                &address,
246                AccountInfo { balance: U256::from(5), ..Default::default() },
247            );
248            db.insert_account_storage(&address, &slot, &U256::from(9));
249        }
250        let mut state = CacheDB::new(db);
251
252        let overrides = StateOverride::from_iter([
253            (emptied, AccountOverride::default().with_balance(U256::ZERO)),
254            (absent, AccountOverride::default()),
255            (
256                replaced,
257                AccountOverride::default()
258                    .with_balance(U256::ZERO)
259                    .with_state(std::iter::empty::<(B256, B256)>()),
260            ),
261        ]);
262        let mut bundle = BundleState::default();
263        apply_state_overrides_with_state(overrides, &mut state, Some(&mut bundle)).unwrap();
264
265        // An existing account touched into emptiness is removed under EIP-161, with its storage.
266        assert_eq!(state.get_account(&emptied).unwrap(), None);
267        assert_eq!(state.get_storage(&emptied, &slot).unwrap(), U256::ZERO);
268        let removed = bundle.account(&emptied).unwrap();
269        assert_eq!(removed.info, None);
270        assert!(removed.was_destroyed());
271        assert_eq!(state.get_account(&absent).unwrap(), None);
272        assert!(bundle.account(&absent).is_none());
273        // A full storage replacement materializes the account even when it is left empty.
274        assert_eq!(state.get_account(&replaced).unwrap(), Some(AccountInfo::default()));
275        assert_eq!(state.get_storage(&replaced, &slot).unwrap(), U256::ZERO);
276        assert!(bundle
277            .account(&replaced)
278            .unwrap()
279            .info
280            .as_ref()
281            .is_some_and(|info| info.is_empty()));
282    }
283
284    #[test]
285    fn code_overrides_reuse_bytecode() {
286        let code = Bytes::from_static(&[0x60, 0x01, 0x5b, 0x00]);
287        let first = override_bytecode(code.clone()).unwrap();
288        let second = override_bytecode(code.clone()).unwrap();
289
290        // Both overrides share one allocation, and with it the code hash and jump table.
291        assert_eq!(first.original_byte_slice().as_ptr(), second.original_byte_slice().as_ptr());
292        assert_eq!(second.hash_slow(), keccak256(&code));
293    }
294
295    #[test]
296    fn attach_bal_before_tx_serves_positioned_reads() {
297        let covered = Address::with_last_byte(1);
298        let uncovered = Address::with_last_byte(2);
299        let written_slot = U256::ONE;
300        let read_slot = U256::from(2);
301
302        // pre-block state
303        let mut db = InMemoryDB::default();
304        db.insert_account_info(
305            &covered,
306            AccountInfo { balance: U256::from(7), nonce: 5, ..Default::default() },
307        );
308        db.insert_account_storage(&covered, &written_slot, &U256::from(11));
309        db.insert_account_storage(&covered, &read_slot, &U256::from(99));
310        db.insert_account_info(
311            &uncovered,
312            AccountInfo { balance: U256::from(3), ..Default::default() },
313        );
314
315        // tx 1 writes the slot, tx 2 changes the balance
316        let mut account = AccountBal::default();
317        account.storage.storage.insert(
318            written_slot,
319            BalChanges::new(vec![StorageChange::new(
320                BlockAccessIndex::from_tx_index(1),
321                U256::from(42),
322            )]),
323        );
324        account.account_info.balance = BalChanges::new(vec![BalanceChange::new(
325            BlockAccessIndex::from_tx_index(2),
326            U256::from(1000),
327        )]);
328        let mut bal = Bal::default();
329        bal.accounts.insert(covered, account);
330        let bal = DecodedBal::new(Arc::new(bal), Bytes::new());
331
332        let mut state = CacheDB::new(db);
333
334        // before tx 0, none of the block's writes are visible
335        attach_bal_before_tx(&mut state, &bal, 0);
336        assert_eq!(state.get_storage(&covered, &written_slot).unwrap(), U256::from(11));
337        assert_eq!(state.get_account(&covered).unwrap().unwrap().balance, U256::from(7));
338
339        // before tx 2, the storage write of tx 1 is visible, the balance change of tx 2 is not
340        attach_bal_before_tx(&mut state, &bal, 2);
341        assert_eq!(state.get_storage(&covered, &written_slot).unwrap(), U256::from(42));
342        assert_eq!(state.get_account(&covered).unwrap().unwrap().balance, U256::from(7));
343
344        // before tx 3, the balance change of tx 2 is visible
345        attach_bal_before_tx(&mut state, &bal, 3);
346        assert_eq!(state.get_account(&covered).unwrap().unwrap().balance, U256::from(1000));
347
348        // reads not covered by the BAL fall back to the underlying database
349        assert_eq!(state.get_storage(&covered, &read_slot).unwrap(), U256::from(99));
350        assert_eq!(state.get_account(&uncovered).unwrap().unwrap().balance, U256::from(3));
351    }
352}