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::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<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    use super::*;
35    use alloy_primitives::{address, Bytes, U256};
36    use revm::{
37        database::{CacheDB, EmptyDB},
38        state::{
39            bal::{AccountBal, Bal, BalWrites, BlockAccessIndex},
40            AccountInfo,
41        },
42    };
43
44    #[test]
45    fn attach_bal_before_tx_serves_positioned_reads() {
46        let covered = address!("0x0000000000000000000000000000000000000001");
47        let uncovered = address!("0x0000000000000000000000000000000000000002");
48        let written_slot = U256::from(1);
49        let read_slot = U256::from(2);
50
51        // pre-block state
52        let mut db = CacheDB::new(EmptyDB::default());
53        db.insert_account_info(
54            covered,
55            AccountInfo { balance: U256::from(7), nonce: 5, ..Default::default() },
56        );
57        db.insert_account_storage(covered, written_slot, U256::from(11)).unwrap();
58        db.insert_account_storage(covered, read_slot, U256::from(99)).unwrap();
59        db.insert_account_info(
60            uncovered,
61            AccountInfo { balance: U256::from(3), ..Default::default() },
62        );
63
64        // tx 1 writes the slot, tx 2 changes the balance
65        let mut account = AccountBal::default();
66        account.storage.storage.insert(
67            written_slot,
68            BalWrites::new(vec![(BlockAccessIndex::from_tx_index(1), U256::from(42))]),
69        );
70        account.account_info.balance =
71            BalWrites::new(vec![(BlockAccessIndex::from_tx_index(2), U256::from(1000))]);
72        let mut bal = Bal::default();
73        bal.accounts.insert(covered, account);
74        let bal = DecodedBal::new(Arc::new(bal), Bytes::new());
75
76        let mut state = State::builder().with_database(db).build();
77
78        // before tx 0, none of the block's writes are visible
79        attach_bal_before_tx(&mut state, &bal, 0);
80        assert_eq!(Database::storage(&mut state, covered, written_slot).unwrap(), U256::from(11));
81        assert_eq!(Database::basic(&mut state, covered).unwrap().unwrap().balance, U256::from(7));
82
83        // before tx 2, the storage write of tx 1 is visible, the balance change of tx 2 is not
84        attach_bal_before_tx(&mut state, &bal, 2);
85        assert_eq!(Database::storage(&mut state, covered, written_slot).unwrap(), U256::from(42));
86        assert_eq!(Database::basic(&mut state, covered).unwrap().unwrap().balance, U256::from(7));
87
88        // before tx 3, the balance change of tx 2 is visible
89        attach_bal_before_tx(&mut state, &bal, 3);
90        assert_eq!(
91            Database::basic(&mut state, covered).unwrap().unwrap().balance,
92            U256::from(1000)
93        );
94
95        // reads not covered by the BAL fall back to the underlying database
96        assert_eq!(Database::storage(&mut state, covered, read_slot).unwrap(), U256::from(99));
97        assert_eq!(Database::basic(&mut state, uncovered).unwrap().unwrap().balance, U256::from(3));
98    }
99}