Skip to main content

reth_chain_state/
memory_overlay.rs

1use super::ExecutedBlock;
2use alloy_consensus::BlockHeader;
3use alloy_primitives::{
4    keccak256, Address, BlockNumber, Bytes, StorageKey, StorageValue, B256, U256,
5};
6use reth_errors::ProviderResult;
7use reth_primitives_traits::{Account, Bytecode, NodePrimitives};
8use reth_storage_api::{
9    AccountReader, BlockHashReader, BytecodeReader, HashedPostStateProvider, StateProofProvider,
10    StateProvider, StateProviderBox, StateRootProvider, StorageRootProvider,
11};
12use reth_trie::{
13    updates::TrieUpdates, AccountProof, DecodedMultiProofV2, HashedPostState, HashedStorage,
14    MultiProof, MultiProofTargets, MultiProofTargetsV2, StorageMultiProof, TrieInput,
15};
16use revm::database::BundleState;
17use std::{borrow::Cow, sync::OnceLock};
18
19/// A state provider that stores references to in-memory blocks along with their state as well as a
20/// reference of the historical state provider for fallback lookups.
21#[expect(missing_debug_implementations)]
22pub struct MemoryOverlayStateProviderRef<
23    'a,
24    N: NodePrimitives = reth_ethereum_primitives::EthPrimitives,
25> {
26    /// Historical state provider for state lookups that are not found in memory blocks.
27    pub(crate) historical: Box<dyn StateProvider + 'a>,
28    /// The collection of executed parent blocks. Expected order is newest to oldest.
29    pub(crate) in_memory: Cow<'a, [ExecutedBlock<N>]>,
30    /// Lazy-loaded in-memory trie data.
31    pub(crate) trie_input: OnceLock<TrieInput>,
32}
33
34impl<'a, N: NodePrimitives> MemoryOverlayStateProviderRef<'a, N> {
35    /// Create new memory overlay state provider.
36    ///
37    /// ## Arguments
38    ///
39    /// - `in_memory` - the collection of executed ancestor blocks in reverse.
40    /// - `historical` - a historical state provider for the latest ancestor block stored in the
41    ///   database.
42    pub fn new(historical: Box<dyn StateProvider + 'a>, in_memory: Vec<ExecutedBlock<N>>) -> Self {
43        Self { historical, in_memory: Cow::Owned(in_memory), trie_input: OnceLock::new() }
44    }
45
46    /// Turn this state provider into a state provider
47    pub fn boxed(self) -> Box<dyn StateProvider + 'a> {
48        Box::new(self)
49    }
50
51    /// Return lazy-loaded trie state aggregated from in-memory blocks.
52    fn trie_input(&self) -> &TrieInput {
53        self.trie_input.get_or_init(|| {
54            let mut input = TrieInput::default();
55            // Iterate from oldest to newest
56            for block in self.in_memory.iter().rev() {
57                let data = block.trie_data();
58                input.nodes.extend_from_sorted(&data.sorted.trie_updates);
59                input.state.extend_from_sorted(&data.sorted.hashed_state);
60            }
61            input
62        })
63    }
64
65    fn merged_hashed_storage(&self, address: Address, storage: HashedStorage) -> HashedStorage {
66        let state = &self.trie_input().state;
67        let mut hashed = state.storages.get(&keccak256(address)).cloned().unwrap_or_default();
68        hashed.extend(&storage);
69        hashed
70    }
71}
72
73impl<N: NodePrimitives> BlockHashReader for MemoryOverlayStateProviderRef<'_, N> {
74    fn block_hash(&self, number: BlockNumber) -> ProviderResult<Option<B256>> {
75        for block in self.in_memory.iter() {
76            if block.recovered_block().number() == number {
77                return Ok(Some(block.recovered_block().hash()));
78            }
79        }
80
81        self.historical.block_hash(number)
82    }
83
84    fn canonical_hashes_range(
85        &self,
86        start: BlockNumber,
87        end: BlockNumber,
88    ) -> ProviderResult<Vec<B256>> {
89        let range = start..end;
90        let mut earliest_block_number = None;
91        let mut in_memory_hashes = Vec::with_capacity(range.size_hint().0);
92
93        // iterate in ascending order (oldest to newest = low to high)
94        for block in self.in_memory.iter() {
95            let block_num = block.recovered_block().number();
96            if range.contains(&block_num) {
97                in_memory_hashes.push(block.recovered_block().hash());
98                earliest_block_number = Some(block_num);
99            }
100        }
101
102        // `self.in_memory` stores executed blocks in ascending order (oldest to newest).
103        // However, `in_memory_hashes` should be constructed in descending order (newest to oldest),
104        // so we reverse the vector after collecting the hashes.
105        in_memory_hashes.reverse();
106
107        let mut hashes =
108            self.historical.canonical_hashes_range(start, earliest_block_number.unwrap_or(end))?;
109        hashes.append(&mut in_memory_hashes);
110        Ok(hashes)
111    }
112}
113
114impl<N: NodePrimitives> AccountReader for MemoryOverlayStateProviderRef<'_, N> {
115    fn basic_account(&self, address: &Address) -> ProviderResult<Option<Account>> {
116        for block in self.in_memory.iter() {
117            if let Some(account) = block.execution_output.account(address) {
118                return Ok(account);
119            }
120        }
121
122        self.historical.basic_account(address)
123    }
124}
125
126impl<N: NodePrimitives> StateRootProvider for MemoryOverlayStateProviderRef<'_, N> {
127    fn state_root(&self, state: HashedPostState) -> ProviderResult<B256> {
128        self.state_root_from_nodes(TrieInput::from_state(state))
129    }
130
131    fn state_root_from_nodes(&self, mut input: TrieInput) -> ProviderResult<B256> {
132        input.prepend_self(self.trie_input().clone());
133        self.historical.state_root_from_nodes(input)
134    }
135
136    fn state_root_with_updates(
137        &self,
138        state: HashedPostState,
139    ) -> ProviderResult<(B256, TrieUpdates)> {
140        self.state_root_from_nodes_with_updates(TrieInput::from_state(state))
141    }
142
143    fn state_root_from_nodes_with_updates(
144        &self,
145        mut input: TrieInput,
146    ) -> ProviderResult<(B256, TrieUpdates)> {
147        input.prepend_self(self.trie_input().clone());
148        self.historical.state_root_from_nodes_with_updates(input)
149    }
150}
151
152impl<N: NodePrimitives> StorageRootProvider for MemoryOverlayStateProviderRef<'_, N> {
153    // TODO: Currently this does not reuse available in-memory trie nodes.
154    fn storage_root(&self, address: Address, storage: HashedStorage) -> ProviderResult<B256> {
155        let merged = self.merged_hashed_storage(address, storage);
156        self.historical.storage_root(address, merged)
157    }
158
159    // TODO: Currently this does not reuse available in-memory trie nodes.
160    fn storage_proof(
161        &self,
162        address: Address,
163        slot: B256,
164        storage: HashedStorage,
165    ) -> ProviderResult<reth_trie::StorageProof> {
166        let merged = self.merged_hashed_storage(address, storage);
167        self.historical.storage_proof(address, slot, merged)
168    }
169
170    // TODO: Currently this does not reuse available in-memory trie nodes.
171    fn storage_multiproof(
172        &self,
173        address: Address,
174        slots: &[B256],
175        storage: HashedStorage,
176    ) -> ProviderResult<StorageMultiProof> {
177        let merged = self.merged_hashed_storage(address, storage);
178        self.historical.storage_multiproof(address, slots, merged)
179    }
180}
181
182impl<N: NodePrimitives> StateProofProvider for MemoryOverlayStateProviderRef<'_, N> {
183    fn proof(
184        &self,
185        mut input: TrieInput,
186        address: Address,
187        slots: &[B256],
188    ) -> ProviderResult<AccountProof> {
189        input.prepend_self(self.trie_input().clone());
190        self.historical.proof(input, address, slots)
191    }
192
193    fn multiproof(
194        &self,
195        mut input: TrieInput,
196        targets: MultiProofTargets,
197    ) -> ProviderResult<MultiProof> {
198        input.prepend_self(self.trie_input().clone());
199        self.historical.multiproof(input, targets)
200    }
201
202    fn multiproof_v2(
203        &self,
204        mut input: TrieInput,
205        targets: MultiProofTargetsV2,
206    ) -> ProviderResult<DecodedMultiProofV2> {
207        input.prepend_self(self.trie_input().clone());
208        self.historical.multiproof_v2(input, targets)
209    }
210
211    fn witness(
212        &self,
213        mut input: TrieInput,
214        target: HashedPostState,
215        mode: reth_trie::ExecutionWitnessMode,
216    ) -> ProviderResult<Vec<Bytes>> {
217        input.prepend_self(self.trie_input().clone());
218        self.historical.witness(input, target, mode)
219    }
220}
221
222impl<N: NodePrimitives> HashedPostStateProvider for MemoryOverlayStateProviderRef<'_, N> {
223    fn hashed_post_state(&self, bundle_state: &BundleState) -> ProviderResult<HashedPostState> {
224        let mut hashed_state = self.historical.hashed_post_state(bundle_state)?;
225
226        for (address, account) in bundle_state.state() {
227            // Accounts created in this bundle cannot have parent storage to zero.
228            if !account.was_destroyed() || account.original_info.is_none() {
229                continue
230            }
231
232            let hashed_address = keccak256(address);
233            let Some(parent_storage) = self.trie_input().state.storages.get(&hashed_address) else {
234                continue
235            };
236            let storage = &mut hashed_state.storages.entry(hashed_address).or_default().storage;
237            for hashed_slot in parent_storage.storage.keys() {
238                storage.entry(*hashed_slot).or_insert(U256::ZERO);
239            }
240        }
241
242        Ok(hashed_state)
243    }
244}
245
246impl<N: NodePrimitives> StateProvider for MemoryOverlayStateProviderRef<'_, N> {
247    fn storage(
248        &self,
249        address: Address,
250        storage_key: StorageKey,
251    ) -> ProviderResult<Option<StorageValue>> {
252        for block in self.in_memory.iter() {
253            if let Some(value) = block.execution_output.storage(&address, storage_key.into()) {
254                return Ok(Some(value));
255            }
256        }
257
258        self.historical.storage(address, storage_key)
259    }
260}
261
262impl<N: NodePrimitives> BytecodeReader for MemoryOverlayStateProviderRef<'_, N> {
263    fn bytecode_by_hash(&self, code_hash: &B256) -> ProviderResult<Option<Bytecode>> {
264        for block in self.in_memory.iter() {
265            if let Some(contract) = block.execution_output.bytecode(code_hash) {
266                return Ok(Some(contract));
267            }
268        }
269
270        self.historical.bytecode_by_hash(code_hash)
271    }
272}
273
274/// An owned state provider that stores references to in-memory blocks along with their state as
275/// well as a reference of the historical state provider for fallback lookups.
276#[expect(missing_debug_implementations)]
277pub struct MemoryOverlayStateProvider<N: NodePrimitives = reth_ethereum_primitives::EthPrimitives> {
278    /// Historical state provider for state lookups that are not found in memory blocks.
279    pub(crate) historical: StateProviderBox,
280    /// The collection of executed parent blocks. Expected order is newest to oldest.
281    pub(crate) in_memory: Vec<ExecutedBlock<N>>,
282    /// Lazy-loaded in-memory trie data.
283    pub(crate) trie_input: OnceLock<TrieInput>,
284}
285
286impl<N: NodePrimitives> MemoryOverlayStateProvider<N> {
287    /// Create new memory overlay state provider.
288    ///
289    /// ## Arguments
290    ///
291    /// - `in_memory` - the collection of executed ancestor blocks in reverse.
292    /// - `historical` - a historical state provider for the latest ancestor block stored in the
293    ///   database.
294    pub fn new(historical: StateProviderBox, in_memory: Vec<ExecutedBlock<N>>) -> Self {
295        Self { historical, in_memory, trie_input: OnceLock::new() }
296    }
297
298    /// Returns a new provider that takes the `TX` as reference
299    #[inline(always)]
300    fn as_ref(&self) -> MemoryOverlayStateProviderRef<'_, N> {
301        MemoryOverlayStateProviderRef {
302            historical: Box::new(self.historical.as_ref()),
303            in_memory: Cow::Borrowed(&self.in_memory),
304            trie_input: self.trie_input.clone(),
305        }
306    }
307
308    /// Wraps the [`Self`] in a `Box`.
309    pub fn boxed(self) -> StateProviderBox {
310        Box::new(self)
311    }
312}
313
314// Delegates all provider impls to [`MemoryOverlayStateProviderRef`]
315reth_storage_api::macros::delegate_provider_impls!(MemoryOverlayStateProvider<N> where [N: NodePrimitives]);
316
317#[cfg(test)]
318mod tests {
319    use super::*;
320    use reth_ethereum_primitives::EthPrimitives;
321    use reth_storage_api::noop::NoopProvider;
322    use revm::database::{AccountStatus, BundleAccount};
323
324    #[test]
325    fn created_and_destroyed_account_skips_in_memory_trie_aggregation() {
326        let address = Address::with_last_byte(1);
327        let provider = MemoryOverlayStateProviderRef::<EthPrimitives>::new(
328            Box::new(NoopProvider::default()),
329            Vec::new(),
330        );
331        let mut bundle_state = BundleState::default();
332        bundle_state.state.insert(
333            address,
334            BundleAccount::new(None, None, Default::default(), AccountStatus::Destroyed),
335        );
336
337        provider.hashed_post_state(&bundle_state).unwrap();
338
339        assert!(provider.trie_input.get().is_none());
340    }
341}