Skip to main content

reth_rpc_eth_types/cache/
db.rs

1//! EVM database types used by the RPC state cache.
2
3use alloy_eip7928::{bal::DecodedBal, BlockAccessIndex};
4use reth_revm::database::StateProviderDatabase;
5use reth_storage_api::{EvmStateProviderAdapter, StateProviderBox};
6use revm::{database::State, state::bal::Bal as RevmBal, Database};
7use std::sync::Arc;
8
9/// Helper alias type for the state's [`State`]
10pub type StateCacheDb = State<StateProviderDatabase<EvmStateProviderAdapter<StateProviderBox>>>;
11
12/// Attaches `bal` to the database, positioned at the state right before the transaction at
13/// `tx_index`.
14///
15/// Reads served by the attached BAL reflect all writes prior to the transaction, including the
16/// block's pre-execution system calls. Reads not covered by the BAL fall back to the underlying
17/// database, which holds the correct values for all state the block does not touch.
18///
19/// Note: changes must not be committed to the database afterwards, because the attached BAL takes
20/// precedence over committed state when serving reads.
21#[inline]
22pub fn attach_bal_before_tx<DB: Database>(
23    db: &mut State<DB>,
24    bal: &DecodedBal<Arc<RevmBal>>,
25    tx_index: usize,
26) {
27    db.set_bal(Some(bal.as_bal().clone()));
28    db.set_allow_bal_db_fallback(true);
29    db.set_bal_index(BlockAccessIndex::from_tx_index(tx_index as u64));
30}
31
32#[cfg(test)]
33mod tests {
34
35    use super::*;
36    use alloy_primitives::{Address, Bytes, U256};
37    use revm::{
38        database::InMemoryDB,
39        state::{
40            bal::{AccountBal, Bal, BalWrites, BlockAccessIndex},
41            AccountInfo,
42        },
43    };
44
45    #[test]
46    fn attach_bal_before_tx_serves_positioned_reads() {
47        let covered = Address::with_last_byte(1);
48        let uncovered = Address::with_last_byte(2);
49        let written_slot = U256::ONE;
50        let read_slot = U256::from(2);
51
52        // pre-block state
53        let mut db = InMemoryDB::default();
54        db.insert_account_info(
55            covered,
56            AccountInfo { balance: U256::from(7), nonce: 5, ..Default::default() },
57        );
58        db.insert_account_storage(covered, written_slot, U256::from(11)).unwrap();
59        db.insert_account_storage(covered, read_slot, U256::from(99)).unwrap();
60        db.insert_account_info(
61            uncovered,
62            AccountInfo { balance: U256::from(3), ..Default::default() },
63        );
64
65        // tx 1 writes the slot, tx 2 changes the balance
66        let mut account = AccountBal::default();
67        account.storage.storage.insert(
68            written_slot,
69            BalWrites::new(vec![(BlockAccessIndex::from_tx_index(1), U256::from(42))]),
70        );
71        account.account_info.balance =
72            BalWrites::new(vec![(BlockAccessIndex::from_tx_index(2), U256::from(1000))]);
73        let mut bal = Bal::default();
74        bal.accounts.insert(covered, account);
75        let bal = DecodedBal::new(Arc::new(bal), Bytes::new());
76
77        let mut state = State::builder().with_database(db).build();
78
79        // before tx 0, none of the block's writes are visible
80        attach_bal_before_tx(&mut state, &bal, 0);
81        assert_eq!(Database::storage(&mut state, covered, written_slot).unwrap(), U256::from(11));
82        assert_eq!(Database::basic(&mut state, covered).unwrap().unwrap().balance, U256::from(7));
83
84        // before tx 2, the storage write of tx 1 is visible, the balance change of tx 2 is not
85        attach_bal_before_tx(&mut state, &bal, 2);
86        assert_eq!(Database::storage(&mut state, covered, written_slot).unwrap(), U256::from(42));
87        assert_eq!(Database::basic(&mut state, covered).unwrap().unwrap().balance, U256::from(7));
88
89        // before tx 3, the balance change of tx 2 is visible
90        attach_bal_before_tx(&mut state, &bal, 3);
91        assert_eq!(
92            Database::basic(&mut state, covered).unwrap().unwrap().balance,
93            U256::from(1000)
94        );
95
96        // reads not covered by the BAL fall back to the underlying database
97        assert_eq!(Database::storage(&mut state, covered, read_slot).unwrap(), U256::from(99));
98        assert_eq!(Database::basic(&mut state, uncovered).unwrap().unwrap().balance, U256::from(3));
99    }
100}