arb_node/
coalesced_state.rs

1//! State provider wrapper that pre-coalesces the in-memory block storage
2//! into a single hashmap keyed by `(address, slot)`, so each SLOAD is one
3//! lookup instead of a linear walk over every unflushed in-memory block.
4
5use std::sync::Arc;
6
7use alloy_primitives::{Address, BlockNumber, Bytes, StorageKey, StorageValue, B256, U256};
8use reth_chain_state::BlockState;
9use reth_primitives_traits::{Account, Bytecode, NodePrimitives};
10use reth_storage_api::{
11    errors::provider::ProviderResult, AccountReader, BlockHashReader, BytecodeReader,
12    HashedPostStateProvider, StateProofProvider, StateProvider, StateProviderBox,
13    StateRootProvider, StorageRootProvider,
14};
15use reth_trie::{
16    updates::TrieUpdates, AccountProof, HashedPostState, HashedStorage, MultiProof,
17    MultiProofTargets, StorageMultiProof, TrieInput,
18};
19use revm_database::BundleState;
20use rustc_hash::{FxHashMap, FxHashSet};
21
22/// Coalesced view of the in-memory block storage overlay.
23///
24/// Every `(address, slot)` explicit write from any unflushed block is
25/// collapsed to its newest value. Accounts whose newest state has
26/// "storage known" semantics (newly created or destroyed, matching
27/// `BundleAccount::storage_slot`) are tracked separately so slot reads
28/// that miss the explicit map still return `Some(U256::ZERO)` for them.
29#[derive(Default, Clone)]
30pub struct CoalescedOverlay {
31    /// Explicit `(address, slot) -> value` from the newest block that
32    /// touched each pair, after wipes have been applied.
33    slots: FxHashMap<(Address, B256), U256>,
34    /// Addresses whose newest state is "storage known" with no subsequent
35    /// explicit write. Reads for slots not in `slots` return zero.
36    wiped: FxHashSet<Address>,
37}
38
39impl CoalescedOverlay {
40    /// Builds the coalesced overlay by iterating the in-memory chain from
41    /// oldest to newest. A wipe (status == storage_known) for an account
42    /// drops all older explicit entries for that address; later explicit
43    /// writes reinstate specific slots.
44    pub fn from_chain<N: NodePrimitives>(head: &BlockState<N>) -> Self {
45        let mut overlay = Self::default();
46        // `BlockState::chain` yields newest-first; apply oldest-first so
47        // newer writes overwrite older ones.
48        let blocks: Vec<&BlockState<N>> = head.chain().collect();
49        for block_state in blocks.into_iter().rev() {
50            overlay.extend_with_block(&block_state.block_ref().execution_output.state);
51        }
52        overlay
53    }
54
55    /// Folds one block's bundle into the overlay using the same
56    /// wipe-then-write semantics as `from_chain`. A wipe is sticky for
57    /// the account: a later non-wipe touch does not "unwipe" other slots.
58    pub fn extend_with_block(&mut self, bundle: &BundleState) {
59        for (addr, account) in bundle.state.iter() {
60            if account.status.is_storage_known() {
61                self.wiped.insert(*addr);
62                self.slots.retain(|(a, _), _| a != addr);
63            }
64            for (slot_u256, slot_entry) in account.storage.iter() {
65                let key = B256::from(*slot_u256);
66                self.slots.insert((*addr, key), slot_entry.present_value);
67            }
68        }
69    }
70
71    pub fn is_empty(&self) -> bool {
72        self.slots.is_empty() && self.wiped.is_empty()
73    }
74
75    fn lookup(&self, address: Address, key: B256) -> Option<Option<U256>> {
76        if let Some(&val) = self.slots.get(&(address, key)) {
77            return Some(Some(val));
78        }
79        if self.wiped.contains(&address) {
80            return Some(Some(U256::ZERO));
81        }
82        None
83    }
84}
85
86/// Wraps an existing `StateProvider` (typically a `MemoryOverlayStateProvider`)
87/// with a precomputed storage overlay so SLOADs served from the overlay
88/// cost a single hashmap probe instead of scanning every in-memory block.
89pub struct CoalescedStateProvider {
90    inner: StateProviderBox,
91    overlay: Arc<CoalescedOverlay>,
92}
93
94impl CoalescedStateProvider {
95    pub fn new(inner: StateProviderBox, overlay: Arc<CoalescedOverlay>) -> Self {
96        Self { inner, overlay }
97    }
98
99    pub fn boxed(self) -> StateProviderBox {
100        Box::new(self)
101    }
102}
103
104impl BlockHashReader for CoalescedStateProvider {
105    fn block_hash(&self, number: BlockNumber) -> ProviderResult<Option<B256>> {
106        self.inner.block_hash(number)
107    }
108
109    fn canonical_hashes_range(
110        &self,
111        start: BlockNumber,
112        end: BlockNumber,
113    ) -> ProviderResult<Vec<B256>> {
114        self.inner.canonical_hashes_range(start, end)
115    }
116}
117
118impl AccountReader for CoalescedStateProvider {
119    fn basic_account(&self, address: &Address) -> ProviderResult<Option<Account>> {
120        self.inner.basic_account(address)
121    }
122}
123
124impl BytecodeReader for CoalescedStateProvider {
125    fn bytecode_by_hash(&self, code_hash: &B256) -> ProviderResult<Option<Bytecode>> {
126        self.inner.bytecode_by_hash(code_hash)
127    }
128}
129
130impl StateRootProvider for CoalescedStateProvider {
131    fn state_root(&self, state: HashedPostState) -> ProviderResult<B256> {
132        self.inner.state_root(state)
133    }
134
135    fn state_root_from_nodes(&self, input: TrieInput) -> ProviderResult<B256> {
136        self.inner.state_root_from_nodes(input)
137    }
138
139    fn state_root_with_updates(
140        &self,
141        state: HashedPostState,
142    ) -> ProviderResult<(B256, TrieUpdates)> {
143        self.inner.state_root_with_updates(state)
144    }
145
146    fn state_root_from_nodes_with_updates(
147        &self,
148        input: TrieInput,
149    ) -> ProviderResult<(B256, TrieUpdates)> {
150        self.inner.state_root_from_nodes_with_updates(input)
151    }
152}
153
154impl StorageRootProvider for CoalescedStateProvider {
155    fn storage_root(&self, address: Address, storage: HashedStorage) -> ProviderResult<B256> {
156        self.inner.storage_root(address, storage)
157    }
158
159    fn storage_proof(
160        &self,
161        address: Address,
162        slot: B256,
163        storage: HashedStorage,
164    ) -> ProviderResult<reth_trie::StorageProof> {
165        self.inner.storage_proof(address, slot, storage)
166    }
167
168    fn storage_multiproof(
169        &self,
170        address: Address,
171        slots: &[B256],
172        storage: HashedStorage,
173    ) -> ProviderResult<StorageMultiProof> {
174        self.inner.storage_multiproof(address, slots, storage)
175    }
176}
177
178impl StateProofProvider for CoalescedStateProvider {
179    fn proof(
180        &self,
181        input: TrieInput,
182        address: Address,
183        slots: &[B256],
184    ) -> ProviderResult<AccountProof> {
185        self.inner.proof(input, address, slots)
186    }
187
188    fn multiproof(
189        &self,
190        input: TrieInput,
191        targets: MultiProofTargets,
192    ) -> ProviderResult<MultiProof> {
193        self.inner.multiproof(input, targets)
194    }
195
196    fn witness(&self, input: TrieInput, target: HashedPostState) -> ProviderResult<Vec<Bytes>> {
197        self.inner.witness(input, target)
198    }
199}
200
201impl HashedPostStateProvider for CoalescedStateProvider {
202    fn hashed_post_state(&self, bundle_state: &BundleState) -> HashedPostState {
203        self.inner.hashed_post_state(bundle_state)
204    }
205}
206
207impl StateProvider for CoalescedStateProvider {
208    fn storage(
209        &self,
210        address: Address,
211        storage_key: StorageKey,
212    ) -> ProviderResult<Option<StorageValue>> {
213        if let Some(val) = self.overlay.lookup(address, storage_key) {
214            return Ok(val);
215        }
216        self.inner.storage(address, storage_key)
217    }
218}