reth_chain_state/
deferred_trie.rs

1use alloy_primitives::B256;
2use parking_lot::Mutex;
3use reth_metrics::{metrics::Counter, Metrics};
4use reth_trie::{
5    updates::{TrieUpdates, TrieUpdatesSorted},
6    HashedPostState, HashedPostStateSorted, TrieInputSorted,
7};
8use std::{
9    fmt,
10    sync::{Arc, LazyLock},
11};
12use tracing::instrument;
13
14/// Shared handle to asynchronously populated trie data.
15///
16/// Uses a try-lock + fallback computation approach for deadlock-free access.
17/// If the deferred task hasn't completed, computes trie data synchronously
18/// from stored unsorted inputs rather than blocking.
19#[derive(Clone)]
20pub struct DeferredTrieData {
21    /// Shared deferred state holding either raw inputs (pending) or computed result (ready).
22    state: Arc<Mutex<DeferredState>>,
23}
24
25/// Sorted trie data computed for an executed block.
26/// These represent the complete set of sorted trie data required to persist
27/// block state for, and generate proofs on top of, a block.
28#[derive(Clone, Debug, Default)]
29pub struct ComputedTrieData {
30    /// Sorted hashed post-state produced by execution.
31    pub hashed_state: Arc<HashedPostStateSorted>,
32    /// Sorted trie updates produced by state root computation.
33    pub trie_updates: Arc<TrieUpdatesSorted>,
34    /// Trie input bundled with its anchor hash, if available.
35    pub anchored_trie_input: Option<AnchoredTrieInput>,
36}
37
38/// Trie input bundled with its anchor hash.
39///
40/// This is used to store the trie input and anchor hash for a block together.
41#[derive(Clone, Debug)]
42pub struct AnchoredTrieInput {
43    /// The persisted ancestor hash this trie input is anchored to.
44    pub anchor_hash: B256,
45    /// Trie input constructed from in-memory overlays.
46    pub trie_input: Arc<TrieInputSorted>,
47}
48
49/// Metrics for deferred trie computation.
50#[derive(Metrics)]
51#[metrics(scope = "sync.block_validation")]
52struct DeferredTrieMetrics {
53    /// Number of times deferred trie data was ready (async task completed first).
54    deferred_trie_async_ready: Counter,
55    /// Number of times deferred trie data required synchronous computation (fallback path).
56    deferred_trie_sync_fallback: Counter,
57}
58
59static DEFERRED_TRIE_METRICS: LazyLock<DeferredTrieMetrics> =
60    LazyLock::new(DeferredTrieMetrics::default);
61
62/// Internal state for deferred trie data.
63enum DeferredState {
64    /// Data is not yet available; raw inputs stored for fallback computation.
65    Pending(PendingInputs),
66    /// Data has been computed and is ready.
67    Ready(ComputedTrieData),
68}
69
70/// Inputs kept while a deferred trie computation is pending.
71#[derive(Clone, Debug)]
72struct PendingInputs {
73    /// Unsorted hashed post-state from execution.
74    hashed_state: Arc<HashedPostState>,
75    /// Unsorted trie updates from state root computation.
76    trie_updates: Arc<TrieUpdates>,
77    /// The persisted ancestor hash this trie input is anchored to.
78    anchor_hash: B256,
79    /// Deferred trie data from ancestor blocks for merging.
80    ancestors: Vec<DeferredTrieData>,
81}
82
83impl fmt::Debug for DeferredTrieData {
84    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
85        let state = self.state.lock();
86        match &*state {
87            DeferredState::Pending(_) => {
88                f.debug_struct("DeferredTrieData").field("state", &"pending").finish()
89            }
90            DeferredState::Ready(_) => {
91                f.debug_struct("DeferredTrieData").field("state", &"ready").finish()
92            }
93        }
94    }
95}
96
97impl DeferredTrieData {
98    /// Create a new pending handle with fallback inputs for synchronous computation.
99    ///
100    /// If the async task hasn't completed when `wait_cloned` is called, the trie data
101    /// will be computed synchronously from these inputs. This eliminates deadlock risk.
102    ///
103    /// # Arguments
104    /// * `hashed_state` - Unsorted hashed post-state from execution
105    /// * `trie_updates` - Unsorted trie updates from state root computation
106    /// * `anchor_hash` - The persisted ancestor hash this trie input is anchored to
107    /// * `ancestors` - Deferred trie data from ancestor blocks for merging
108    pub fn pending(
109        hashed_state: Arc<HashedPostState>,
110        trie_updates: Arc<TrieUpdates>,
111        anchor_hash: B256,
112        ancestors: Vec<Self>,
113    ) -> Self {
114        Self {
115            state: Arc::new(Mutex::new(DeferredState::Pending(PendingInputs {
116                hashed_state,
117                trie_updates,
118                anchor_hash,
119                ancestors,
120            }))),
121        }
122    }
123
124    /// Create a handle that is already populated with the given [`ComputedTrieData`].
125    ///
126    /// Useful when trie data is available immediately.
127    /// [`Self::wait_cloned`] will return without any computation.
128    pub fn ready(bundle: ComputedTrieData) -> Self {
129        Self { state: Arc::new(Mutex::new(DeferredState::Ready(bundle))) }
130    }
131
132    /// Sort block execution outputs and build a [`TrieInputSorted`] overlay.
133    ///
134    /// The trie input overlay accumulates sorted hashed state (account/storage changes) and
135    /// trie node updates from all in-memory ancestor blocks. This overlay is required for:
136    /// - Computing state roots on top of in-memory blocks
137    /// - Generating storage/account proofs for unpersisted state
138    ///
139    /// # Process
140    /// 1. Sort the current block's hashed state and trie updates
141    /// 2. Merge ancestor overlays (oldest -> newest, so later state takes precedence)
142    /// 3. Extend the merged overlay with this block's sorted data
143    ///
144    /// Used by both the async background task and the synchronous fallback path.
145    ///
146    /// # Arguments
147    /// * `hashed_state` - Unsorted hashed post-state (account/storage changes) from execution
148    /// * `trie_updates` - Unsorted trie node updates from state root computation
149    /// * `anchor_hash` - The persisted ancestor hash this trie input is anchored to
150    /// * `ancestors` - Deferred trie data from ancestor blocks for merging
151    pub fn sort_and_build_trie_input(
152        hashed_state: &HashedPostState,
153        trie_updates: &TrieUpdates,
154        anchor_hash: B256,
155        ancestors: &[Self],
156    ) -> ComputedTrieData {
157        // Sort the current block's hashed state and trie updates
158        let sorted_hashed_state = Arc::new(hashed_state.clone_into_sorted());
159        let sorted_trie_updates = Arc::new(trie_updates.clone().into_sorted());
160
161        // Merge trie data from ancestors (oldest -> newest so later state takes precedence)
162        let mut overlay = TrieInputSorted::default();
163        for ancestor in ancestors {
164            let ancestor_data = ancestor.wait_cloned();
165            {
166                let state_mut = Arc::make_mut(&mut overlay.state);
167                state_mut.extend_ref(ancestor_data.hashed_state.as_ref());
168            }
169            {
170                let nodes_mut = Arc::make_mut(&mut overlay.nodes);
171                nodes_mut.extend_ref(ancestor_data.trie_updates.as_ref());
172            }
173        }
174
175        // Extend overlay with current block's sorted data
176        {
177            let state_mut = Arc::make_mut(&mut overlay.state);
178            state_mut.extend_ref(sorted_hashed_state.as_ref());
179        }
180        {
181            let nodes_mut = Arc::make_mut(&mut overlay.nodes);
182            nodes_mut.extend_ref(sorted_trie_updates.as_ref());
183        }
184
185        ComputedTrieData::with_trie_input(
186            sorted_hashed_state,
187            sorted_trie_updates,
188            anchor_hash,
189            Arc::new(overlay),
190        )
191    }
192
193    /// Returns trie data, computing synchronously if the async task hasn't completed.
194    ///
195    /// - If the async task has completed (`Ready`), returns the cached result.
196    /// - If pending, computes synchronously from stored inputs.
197    ///
198    /// Deadlock is avoided as long as the provided ancestors form a true ancestor chain (a DAG):
199    /// - Each block only waits on its ancestors (blocks on the path to the persisted root)
200    /// - Sibling blocks (forks) are never in each other's ancestor lists
201    /// - A block never waits on its descendants
202    ///
203    /// Given that invariant, circular wait dependencies are impossible.
204    #[instrument(level = "debug", target = "engine::tree::deferred_trie", skip_all)]
205    pub fn wait_cloned(&self) -> ComputedTrieData {
206        let mut state = self.state.lock();
207        match &*state {
208            // If the deferred trie data is ready, return the cached result.
209            DeferredState::Ready(bundle) => {
210                DEFERRED_TRIE_METRICS.deferred_trie_async_ready.increment(1);
211                bundle.clone()
212            }
213            // If the deferred trie data is pending, compute the trie data synchronously and return
214            // the result. This is the fallback path if the async task hasn't completed.
215            DeferredState::Pending(inputs) => {
216                DEFERRED_TRIE_METRICS.deferred_trie_sync_fallback.increment(1);
217                let computed = Self::sort_and_build_trie_input(
218                    &inputs.hashed_state,
219                    &inputs.trie_updates,
220                    inputs.anchor_hash,
221                    &inputs.ancestors,
222                );
223                *state = DeferredState::Ready(computed.clone());
224                computed
225            }
226        }
227    }
228}
229
230impl ComputedTrieData {
231    /// Construct a bundle that includes trie input anchored to a persisted ancestor.
232    pub const fn with_trie_input(
233        hashed_state: Arc<HashedPostStateSorted>,
234        trie_updates: Arc<TrieUpdatesSorted>,
235        anchor_hash: B256,
236        trie_input: Arc<TrieInputSorted>,
237    ) -> Self {
238        Self {
239            hashed_state,
240            trie_updates,
241            anchored_trie_input: Some(AnchoredTrieInput { anchor_hash, trie_input }),
242        }
243    }
244
245    /// Construct a bundle without trie input or anchor information.
246    ///
247    /// Unlike [`Self::with_trie_input`], this constructor omits the accumulated trie input overlay
248    /// and its anchor hash. Use this when the trie input is not needed, such as in block builders
249    /// or sequencers that don't require proof generation on top of in-memory state.
250    ///
251    /// The trie input anchor identifies the persisted block hash from which the in-memory overlay
252    /// was built. Without it, consumers cannot determine which on-disk state to combine with.
253    pub const fn without_trie_input(
254        hashed_state: Arc<HashedPostStateSorted>,
255        trie_updates: Arc<TrieUpdatesSorted>,
256    ) -> Self {
257        Self { hashed_state, trie_updates, anchored_trie_input: None }
258    }
259
260    /// Returns the anchor hash, if present.
261    pub fn anchor_hash(&self) -> Option<B256> {
262        self.anchored_trie_input.as_ref().map(|anchored| anchored.anchor_hash)
263    }
264
265    /// Returns the trie input, if present.
266    pub fn trie_input(&self) -> Option<&Arc<TrieInputSorted>> {
267        self.anchored_trie_input.as_ref().map(|anchored| &anchored.trie_input)
268    }
269}
270
271#[cfg(test)]
272mod tests {
273    use super::*;
274    use alloy_primitives::{map::B256Map, U256};
275    use reth_primitives_traits::Account;
276    use reth_trie::updates::TrieUpdates;
277    use std::{
278        sync::Arc,
279        thread,
280        time::{Duration, Instant},
281    };
282
283    fn empty_bundle() -> ComputedTrieData {
284        ComputedTrieData {
285            hashed_state: Arc::default(),
286            trie_updates: Arc::default(),
287            anchored_trie_input: None,
288        }
289    }
290
291    fn empty_pending() -> DeferredTrieData {
292        empty_pending_with_anchor(B256::ZERO)
293    }
294
295    fn empty_pending_with_anchor(anchor: B256) -> DeferredTrieData {
296        DeferredTrieData::pending(
297            Arc::new(HashedPostState::default()),
298            Arc::new(TrieUpdates::default()),
299            anchor,
300            Vec::new(),
301        )
302    }
303
304    /// Verifies that a ready handle returns immediately without computation.
305    #[test]
306    fn ready_returns_immediately() {
307        let bundle = empty_bundle();
308        let deferred = DeferredTrieData::ready(bundle.clone());
309
310        let start = Instant::now();
311        let result = deferred.wait_cloned();
312        let elapsed = start.elapsed();
313
314        assert_eq!(result.hashed_state, bundle.hashed_state);
315        assert_eq!(result.trie_updates, bundle.trie_updates);
316        assert_eq!(result.anchor_hash(), bundle.anchor_hash());
317        assert!(elapsed < Duration::from_millis(20));
318    }
319
320    /// Verifies that a pending handle computes trie data synchronously via fallback.
321    #[test]
322    fn pending_computes_fallback() {
323        let deferred = empty_pending();
324
325        // wait_cloned should compute from inputs without blocking
326        let start = Instant::now();
327        let result = deferred.wait_cloned();
328        let elapsed = start.elapsed();
329
330        // Should return quickly (fallback computation)
331        assert!(elapsed < Duration::from_millis(100));
332        assert!(result.hashed_state.is_empty());
333    }
334
335    /// Verifies that fallback computation result is cached for subsequent calls.
336    #[test]
337    fn fallback_result_is_cached() {
338        let deferred = empty_pending();
339
340        // First call computes and should stash the result
341        let first = deferred.wait_cloned();
342        // Second call should reuse the cached result (same Arc pointer)
343        let second = deferred.wait_cloned();
344
345        assert!(Arc::ptr_eq(&first.hashed_state, &second.hashed_state));
346        assert!(Arc::ptr_eq(&first.trie_updates, &second.trie_updates));
347        assert_eq!(first.anchor_hash(), second.anchor_hash());
348    }
349
350    /// Verifies that concurrent `wait_cloned` calls result in only one computation,
351    /// with all callers receiving the same cached result.
352    #[test]
353    fn concurrent_wait_cloned_computes_once() {
354        let deferred = empty_pending();
355
356        // Spawn multiple threads that all call wait_cloned concurrently
357        let handles: Vec<_> = (0..10)
358            .map(|_| {
359                let d = deferred.clone();
360                thread::spawn(move || d.wait_cloned())
361            })
362            .collect();
363
364        // Collect all results
365        let results: Vec<_> = handles.into_iter().map(|h| h.join().unwrap()).collect();
366
367        // All results should share the same Arc pointers (same computed result)
368        let first = &results[0];
369        for result in &results[1..] {
370            assert!(Arc::ptr_eq(&first.hashed_state, &result.hashed_state));
371            assert!(Arc::ptr_eq(&first.trie_updates, &result.trie_updates));
372        }
373    }
374
375    /// Tests that ancestor trie data is merged during fallback computation and that the
376    /// resulting `ComputedTrieData` uses the current block's anchor hash, not the ancestor's.
377    #[test]
378    fn ancestors_are_merged() {
379        // Create ancestor with some data
380        let ancestor_bundle = ComputedTrieData {
381            hashed_state: Arc::default(),
382            trie_updates: Arc::default(),
383            anchored_trie_input: Some(AnchoredTrieInput {
384                anchor_hash: B256::with_last_byte(1),
385                trie_input: Arc::new(TrieInputSorted::default()),
386            }),
387        };
388        let ancestor = DeferredTrieData::ready(ancestor_bundle);
389
390        // Create pending with ancestor
391        let deferred = DeferredTrieData::pending(
392            Arc::new(HashedPostState::default()),
393            Arc::new(TrieUpdates::default()),
394            B256::with_last_byte(2),
395            vec![ancestor],
396        );
397
398        let result = deferred.wait_cloned();
399        // Should have the current block's anchor, not the ancestor's
400        assert_eq!(result.anchor_hash(), Some(B256::with_last_byte(2)));
401    }
402
403    /// Ensures ancestor overlays are merged oldest -> newest so latest state wins (no overwrite by
404    /// older ancestors).
405    #[test]
406    fn ancestors_merge_in_chronological_order() {
407        let key = B256::with_last_byte(1);
408        // Oldest ancestor sets nonce to 1
409        let oldest_state = HashedPostStateSorted::new(
410            vec![(key, Some(Account { nonce: 1, balance: U256::ZERO, bytecode_hash: None }))],
411            B256Map::default(),
412        );
413        // Newest ancestor overwrites nonce to 2
414        let newest_state = HashedPostStateSorted::new(
415            vec![(key, Some(Account { nonce: 2, balance: U256::ZERO, bytecode_hash: None }))],
416            B256Map::default(),
417        );
418
419        let oldest = ComputedTrieData {
420            hashed_state: Arc::new(oldest_state),
421            trie_updates: Arc::default(),
422            anchored_trie_input: None,
423        };
424        let newest = ComputedTrieData {
425            hashed_state: Arc::new(newest_state),
426            trie_updates: Arc::default(),
427            anchored_trie_input: None,
428        };
429
430        // Pass ancestors oldest -> newest; newest should take precedence
431        let deferred = DeferredTrieData::pending(
432            Arc::new(HashedPostState::default()),
433            Arc::new(TrieUpdates::default()),
434            B256::ZERO,
435            vec![DeferredTrieData::ready(oldest), DeferredTrieData::ready(newest)],
436        );
437
438        let result = deferred.wait_cloned();
439        let overlay_state = &result.anchored_trie_input.as_ref().unwrap().trie_input.state.accounts;
440        assert_eq!(overlay_state.len(), 1);
441        let (_, account) = &overlay_state[0];
442        assert_eq!(account.unwrap().nonce, 2);
443    }
444}