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, HashedPostState, HashedStorage, MultiProof,
14    MultiProofTargets, 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 witness(
203        &self,
204        mut input: TrieInput,
205        target: HashedPostState,
206        mode: reth_trie::ExecutionWitnessMode,
207    ) -> ProviderResult<Vec<Bytes>> {
208        input.prepend_self(self.trie_input().clone());
209        self.historical.witness(input, target, mode)
210    }
211}
212
213impl<N: NodePrimitives> HashedPostStateProvider for MemoryOverlayStateProviderRef<'_, N> {
214    fn hashed_post_state(&self, bundle_state: &BundleState) -> ProviderResult<HashedPostState> {
215        let mut hashed_state = self.historical.hashed_post_state(bundle_state)?;
216
217        for (address, account) in bundle_state.state() {
218            // Accounts created in this bundle cannot have parent storage to zero.
219            if !account.was_destroyed() || account.original_info.is_none() {
220                continue
221            }
222
223            let hashed_address = keccak256(address);
224            let Some(parent_storage) = self.trie_input().state.storages.get(&hashed_address) else {
225                continue
226            };
227            let storage = &mut hashed_state.storages.entry(hashed_address).or_default().storage;
228            for hashed_slot in parent_storage.storage.keys() {
229                storage.entry(*hashed_slot).or_insert(U256::ZERO);
230            }
231        }
232
233        Ok(hashed_state)
234    }
235}
236
237impl<N: NodePrimitives> StateProvider for MemoryOverlayStateProviderRef<'_, N> {
238    fn storage(
239        &self,
240        address: Address,
241        storage_key: StorageKey,
242    ) -> ProviderResult<Option<StorageValue>> {
243        for block in self.in_memory.iter() {
244            if let Some(value) = block.execution_output.storage(&address, storage_key.into()) {
245                return Ok(Some(value));
246            }
247        }
248
249        self.historical.storage(address, storage_key)
250    }
251}
252
253impl<N: NodePrimitives> BytecodeReader for MemoryOverlayStateProviderRef<'_, N> {
254    fn bytecode_by_hash(&self, code_hash: &B256) -> ProviderResult<Option<Bytecode>> {
255        for block in self.in_memory.iter() {
256            if let Some(contract) = block.execution_output.bytecode(code_hash) {
257                return Ok(Some(contract));
258            }
259        }
260
261        self.historical.bytecode_by_hash(code_hash)
262    }
263}
264
265/// An owned state provider that stores references to in-memory blocks along with their state as
266/// well as a reference of the historical state provider for fallback lookups.
267#[expect(missing_debug_implementations)]
268pub struct MemoryOverlayStateProvider<N: NodePrimitives = reth_ethereum_primitives::EthPrimitives> {
269    /// Historical state provider for state lookups that are not found in memory blocks.
270    pub(crate) historical: StateProviderBox,
271    /// The collection of executed parent blocks. Expected order is newest to oldest.
272    pub(crate) in_memory: Vec<ExecutedBlock<N>>,
273    /// Lazy-loaded in-memory trie data.
274    pub(crate) trie_input: OnceLock<TrieInput>,
275}
276
277impl<N: NodePrimitives> MemoryOverlayStateProvider<N> {
278    /// Create new memory overlay state provider.
279    ///
280    /// ## Arguments
281    ///
282    /// - `in_memory` - the collection of executed ancestor blocks in reverse.
283    /// - `historical` - a historical state provider for the latest ancestor block stored in the
284    ///   database.
285    pub fn new(historical: StateProviderBox, in_memory: Vec<ExecutedBlock<N>>) -> Self {
286        Self { historical, in_memory, trie_input: OnceLock::new() }
287    }
288
289    /// Returns a new provider that takes the `TX` as reference
290    #[inline(always)]
291    fn as_ref(&self) -> MemoryOverlayStateProviderRef<'_, N> {
292        MemoryOverlayStateProviderRef {
293            historical: Box::new(self.historical.as_ref()),
294            in_memory: Cow::Borrowed(&self.in_memory),
295            trie_input: self.trie_input.clone(),
296        }
297    }
298
299    /// Wraps the [`Self`] in a `Box`.
300    pub fn boxed(self) -> StateProviderBox {
301        Box::new(self)
302    }
303}
304
305// Delegates all provider impls to [`MemoryOverlayStateProviderRef`]
306reth_storage_api::macros::delegate_provider_impls!(MemoryOverlayStateProvider<N> where [N: NodePrimitives]);
307
308#[cfg(test)]
309mod tests {
310    use super::*;
311    use reth_ethereum_primitives::EthPrimitives;
312    use reth_storage_api::noop::NoopProvider;
313    use revm::database::{AccountStatus, BundleAccount};
314
315    #[test]
316    fn created_and_destroyed_account_skips_in_memory_trie_aggregation() {
317        let address = Address::with_last_byte(1);
318        let provider = MemoryOverlayStateProviderRef::<EthPrimitives>::new(
319            Box::new(NoopProvider::default()),
320            Vec::new(),
321        );
322        let mut bundle_state = BundleState::default();
323        bundle_state.state.insert(
324            address,
325            BundleAccount::new(None, None, Default::default(), AccountStatus::Destroyed),
326        );
327
328        provider.hashed_post_state(&bundle_state).unwrap();
329
330        assert!(provider.trie_input.get().is_none());
331    }
332}