Skip to main content

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/// The `trie_input` contains the **cumulative** overlay of all in-memory ancestor blocks,
41/// not just this block's changes. Child blocks reuse the parent's overlay in O(1) by
42/// cloning the Arc-wrapped data.
43///
44/// The `anchor_hash` is metadata indicating which persisted base state this overlay
45/// sits on top of. It is CRITICAL for overlay reuse decisions: an overlay built on top
46/// of Anchor A cannot be reused for a block anchored to Anchor B, as it would result
47/// in an incorrect state.
48#[derive(Clone, Debug)]
49pub struct AnchoredTrieInput {
50    /// The persisted ancestor hash this trie input is anchored to.
51    pub anchor_hash: B256,
52    /// Cumulative trie input overlay from all in-memory ancestors.
53    pub trie_input: Arc<TrieInputSorted>,
54}
55
56/// Metrics for deferred trie computation.
57#[derive(Metrics)]
58#[metrics(scope = "sync.block_validation")]
59struct DeferredTrieMetrics {
60    /// Number of times deferred trie data was ready (async task completed first).
61    deferred_trie_async_ready: Counter,
62    /// Number of times deferred trie data required synchronous computation (fallback path).
63    deferred_trie_sync_fallback: Counter,
64}
65
66static DEFERRED_TRIE_METRICS: LazyLock<DeferredTrieMetrics> =
67    LazyLock::new(DeferredTrieMetrics::default);
68
69/// Internal state for deferred trie data.
70enum DeferredState {
71    /// Data is not yet available; raw inputs stored for fallback computation.
72    /// Wrapped in `Option` to allow taking ownership during computation.
73    Pending(Option<PendingInputs>),
74    /// Data has been computed and is ready.
75    Ready(ComputedTrieData),
76}
77
78/// Inputs kept while a deferred trie computation is pending.
79#[derive(Clone, Debug)]
80struct PendingInputs {
81    /// Unsorted hashed post-state from execution.
82    hashed_state: Arc<HashedPostState>,
83    /// Unsorted trie updates from state root computation.
84    trie_updates: Arc<TrieUpdates>,
85    /// The persisted ancestor hash this trie input is anchored to.
86    anchor_hash: B256,
87    /// Deferred trie data from ancestor blocks for merging.
88    ancestors: Vec<DeferredTrieData>,
89}
90
91impl fmt::Debug for DeferredTrieData {
92    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
93        let state = self.state.lock();
94        match &*state {
95            DeferredState::Pending(_) => {
96                f.debug_struct("DeferredTrieData").field("state", &"pending").finish()
97            }
98            DeferredState::Ready(_) => {
99                f.debug_struct("DeferredTrieData").field("state", &"ready").finish()
100            }
101        }
102    }
103}
104
105impl DeferredTrieData {
106    /// Create a new pending handle with fallback inputs for synchronous computation.
107    ///
108    /// If the async task hasn't completed when `wait_cloned` is called, the trie data
109    /// will be computed synchronously from these inputs. This eliminates deadlock risk.
110    ///
111    /// # Arguments
112    /// * `hashed_state` - Unsorted hashed post-state from execution
113    /// * `trie_updates` - Unsorted trie updates from state root computation
114    /// * `anchor_hash` - The persisted ancestor hash this trie input is anchored to
115    /// * `ancestors` - Deferred trie data from ancestor blocks for merging
116    pub fn pending(
117        hashed_state: Arc<HashedPostState>,
118        trie_updates: Arc<TrieUpdates>,
119        anchor_hash: B256,
120        ancestors: Vec<Self>,
121    ) -> Self {
122        Self {
123            state: Arc::new(Mutex::new(DeferredState::Pending(Some(PendingInputs {
124                hashed_state,
125                trie_updates,
126                anchor_hash,
127                ancestors,
128            })))),
129        }
130    }
131
132    /// Create a handle that is already populated with the given [`ComputedTrieData`].
133    ///
134    /// Useful when trie data is available immediately.
135    /// [`Self::wait_cloned`] will return without any computation.
136    pub fn ready(bundle: ComputedTrieData) -> Self {
137        Self { state: Arc::new(Mutex::new(DeferredState::Ready(bundle))) }
138    }
139
140    /// Sort block execution outputs and build a [`TrieInputSorted`] overlay.
141    ///
142    /// The trie input overlay accumulates sorted hashed state (account/storage changes) and
143    /// trie node updates from all in-memory ancestor blocks. This overlay is required for:
144    /// - Computing state roots on top of in-memory blocks
145    /// - Generating storage/account proofs for unpersisted state
146    ///
147    /// # Process
148    /// 1. Sort the current block's hashed state and trie updates
149    /// 2. Reuse parent's cached overlay if available (O(1) - the common case)
150    /// 3. Otherwise, rebuild overlay from ancestors (rare fallback)
151    /// 4. Extend the overlay with this block's sorted data
152    ///
153    /// Used by both the async background task and the synchronous fallback path.
154    ///
155    /// # Arguments
156    /// * `hashed_state` - Unsorted hashed post-state (account/storage changes) from execution
157    /// * `trie_updates` - Unsorted trie node updates from state root computation
158    /// * `anchor_hash` - The persisted ancestor hash this trie input is anchored to
159    /// * `ancestors` - Deferred trie data from ancestor blocks for merging (oldest -> newest)
160    pub fn sort_and_build_trie_input(
161        hashed_state: Arc<HashedPostState>,
162        trie_updates: Arc<TrieUpdates>,
163        anchor_hash: B256,
164        ancestors: &[Self],
165    ) -> ComputedTrieData {
166        #[cfg(feature = "rayon")]
167        let (sorted_hashed_state, sorted_trie_updates) = rayon::join(
168            || match Arc::try_unwrap(hashed_state) {
169                Ok(state) => state.into_sorted(),
170                Err(arc) => arc.clone_into_sorted(),
171            },
172            || match Arc::try_unwrap(trie_updates) {
173                Ok(updates) => updates.into_sorted(),
174                Err(arc) => arc.clone_into_sorted(),
175            },
176        );
177
178        #[cfg(not(feature = "rayon"))]
179        let (sorted_hashed_state, sorted_trie_updates) = (
180            match Arc::try_unwrap(hashed_state) {
181                Ok(state) => state.into_sorted(),
182                Err(arc) => arc.clone_into_sorted(),
183            },
184            match Arc::try_unwrap(trie_updates) {
185                Ok(updates) => updates.into_sorted(),
186                Err(arc) => arc.clone_into_sorted(),
187            },
188        );
189
190        // Reuse parent's overlay if available and anchors match.
191        // We can only reuse the parent's overlay if it was built on top of the same
192        // persisted anchor. If the anchor has changed (e.g., due to persistence),
193        // the parent's overlay is relative to an old state and cannot be used.
194        let overlay = if let Some(parent) = ancestors.last() {
195            let parent_data = parent.wait_cloned();
196
197            match &parent_data.anchored_trie_input {
198                // Case 1: Parent has cached overlay AND anchors match.
199                Some(AnchoredTrieInput { anchor_hash: parent_anchor, trie_input })
200                    if *parent_anchor == anchor_hash =>
201                {
202                    // O(1): Reuse parent's overlay, extend with current block's data.
203                    let mut overlay = TrieInputSorted::new(
204                        Arc::clone(&trie_input.nodes),
205                        Arc::clone(&trie_input.state),
206                        Default::default(), // prefix_sets are per-block, not cumulative
207                    );
208                    // Only trigger COW clone if there's actually data to add.
209                    #[cfg(feature = "rayon")]
210                    {
211                        rayon::join(
212                            || {
213                                if !sorted_hashed_state.is_empty() {
214                                    Arc::make_mut(&mut overlay.state)
215                                        .extend_ref_and_sort(&sorted_hashed_state);
216                                }
217                            },
218                            || {
219                                if !sorted_trie_updates.is_empty() {
220                                    Arc::make_mut(&mut overlay.nodes)
221                                        .extend_ref_and_sort(&sorted_trie_updates);
222                                }
223                            },
224                        );
225                    }
226                    #[cfg(not(feature = "rayon"))]
227                    {
228                        if !sorted_hashed_state.is_empty() {
229                            Arc::make_mut(&mut overlay.state)
230                                .extend_ref_and_sort(&sorted_hashed_state);
231                        }
232                        if !sorted_trie_updates.is_empty() {
233                            Arc::make_mut(&mut overlay.nodes)
234                                .extend_ref_and_sort(&sorted_trie_updates);
235                        }
236                    }
237                    overlay
238                }
239                // Case 2: Parent exists but anchor mismatch or no cached overlay.
240                // We must rebuild from the ancestors list (which only contains unpersisted blocks).
241                _ => Self::merge_ancestors_into_overlay(
242                    ancestors,
243                    &sorted_hashed_state,
244                    &sorted_trie_updates,
245                ),
246            }
247        } else {
248            // Case 3: No in-memory ancestors (first block after persisted anchor).
249            // Build overlay with just this block's data.
250            Self::merge_ancestors_into_overlay(&[], &sorted_hashed_state, &sorted_trie_updates)
251        };
252
253        ComputedTrieData::with_trie_input(
254            Arc::new(sorted_hashed_state),
255            Arc::new(sorted_trie_updates),
256            anchor_hash,
257            Arc::new(overlay),
258        )
259    }
260
261    /// Merge all ancestors and current block's data into a single overlay.
262    ///
263    /// This is a rare fallback path, only used when no ancestor has a cached
264    /// `anchored_trie_input` (e.g., blocks created via alternative constructors).
265    /// In normal operation, the parent always has a cached overlay and this
266    /// function is never called.
267    ///
268    /// Iterates ancestors oldest -> newest, then extends with current block's data,
269    /// so later state takes precedence.
270    fn merge_ancestors_into_overlay(
271        ancestors: &[Self],
272        sorted_hashed_state: &HashedPostStateSorted,
273        sorted_trie_updates: &TrieUpdatesSorted,
274    ) -> TrieInputSorted {
275        let mut overlay = TrieInputSorted::default();
276
277        let state_mut = Arc::make_mut(&mut overlay.state);
278        let nodes_mut = Arc::make_mut(&mut overlay.nodes);
279
280        for ancestor in ancestors {
281            let ancestor_data = ancestor.wait_cloned();
282            state_mut.extend_ref_and_sort(ancestor_data.hashed_state.as_ref());
283            nodes_mut.extend_ref_and_sort(ancestor_data.trie_updates.as_ref());
284        }
285
286        // Extend with current block's sorted data last (takes precedence)
287        #[cfg(feature = "rayon")]
288        rayon::join(
289            || state_mut.extend_ref_and_sort(sorted_hashed_state),
290            || nodes_mut.extend_ref_and_sort(sorted_trie_updates),
291        );
292
293        #[cfg(not(feature = "rayon"))]
294        {
295            state_mut.extend_ref_and_sort(sorted_hashed_state);
296            nodes_mut.extend_ref_and_sort(sorted_trie_updates);
297        }
298
299        overlay
300    }
301
302    /// Returns trie data, computing synchronously if the async task hasn't completed.
303    ///
304    /// - If the async task has completed (`Ready`), returns the cached result.
305    /// - If pending, computes synchronously from stored inputs.
306    ///
307    /// Deadlock is avoided as long as the provided ancestors form a true ancestor chain (a DAG):
308    /// - Each block only waits on its ancestors (blocks on the path to the persisted root)
309    /// - Sibling blocks (forks) are never in each other's ancestor lists
310    /// - A block never waits on its descendants
311    ///
312    /// Given that invariant, circular wait dependencies are impossible.
313    #[instrument(level = "debug", target = "engine::tree::deferred_trie", skip_all)]
314    pub fn wait_cloned(&self) -> ComputedTrieData {
315        let mut state = self.state.lock();
316        match &mut *state {
317            // If the deferred trie data is ready, return the cached result.
318            DeferredState::Ready(bundle) => {
319                DEFERRED_TRIE_METRICS.deferred_trie_async_ready.increment(1);
320                bundle.clone()
321            }
322            // If the deferred trie data is pending, compute the trie data synchronously and return
323            // the result. This is the fallback path if the async task hasn't completed.
324            DeferredState::Pending(maybe_inputs) => {
325                DEFERRED_TRIE_METRICS.deferred_trie_sync_fallback.increment(1);
326
327                let inputs = maybe_inputs.take().expect("inputs must be present in Pending state");
328
329                let computed = Self::sort_and_build_trie_input(
330                    inputs.hashed_state,
331                    inputs.trie_updates,
332                    inputs.anchor_hash,
333                    &inputs.ancestors,
334                );
335                *state = DeferredState::Ready(computed.clone());
336
337                // Release lock before inputs (and its ancestors) drop to avoid holding it
338                // while their potential last Arc refs drop (which could trigger recursive locking)
339                drop(state);
340
341                computed
342            }
343        }
344    }
345}
346
347impl ComputedTrieData {
348    /// Construct a bundle that includes trie input anchored to a persisted ancestor.
349    pub const fn with_trie_input(
350        hashed_state: Arc<HashedPostStateSorted>,
351        trie_updates: Arc<TrieUpdatesSorted>,
352        anchor_hash: B256,
353        trie_input: Arc<TrieInputSorted>,
354    ) -> Self {
355        Self {
356            hashed_state,
357            trie_updates,
358            anchored_trie_input: Some(AnchoredTrieInput { anchor_hash, trie_input }),
359        }
360    }
361
362    /// Construct a bundle without trie input or anchor information.
363    ///
364    /// Unlike [`Self::with_trie_input`], this constructor omits the accumulated trie input overlay
365    /// and its anchor hash. Use this when the trie input is not needed, such as in block builders
366    /// or sequencers that don't require proof generation on top of in-memory state.
367    ///
368    /// The trie input anchor identifies the persisted block hash from which the in-memory overlay
369    /// was built. Without it, consumers cannot determine which on-disk state to combine with.
370    pub const fn without_trie_input(
371        hashed_state: Arc<HashedPostStateSorted>,
372        trie_updates: Arc<TrieUpdatesSorted>,
373    ) -> Self {
374        Self { hashed_state, trie_updates, anchored_trie_input: None }
375    }
376
377    /// Returns the anchor hash, if present.
378    pub fn anchor_hash(&self) -> Option<B256> {
379        self.anchored_trie_input.as_ref().map(|anchored| anchored.anchor_hash)
380    }
381
382    /// Returns the trie input, if present.
383    pub fn trie_input(&self) -> Option<&Arc<TrieInputSorted>> {
384        self.anchored_trie_input.as_ref().map(|anchored| &anchored.trie_input)
385    }
386}
387
388#[cfg(test)]
389mod tests {
390    use super::*;
391    use alloy_primitives::{map::B256Map, U256};
392    use reth_primitives_traits::Account;
393    use reth_trie::updates::TrieUpdates;
394    use std::{
395        sync::Arc,
396        thread,
397        time::{Duration, Instant},
398    };
399
400    fn empty_bundle() -> ComputedTrieData {
401        ComputedTrieData {
402            hashed_state: Arc::default(),
403            trie_updates: Arc::default(),
404            anchored_trie_input: None,
405        }
406    }
407
408    fn empty_pending() -> DeferredTrieData {
409        empty_pending_with_anchor(B256::ZERO)
410    }
411
412    fn empty_pending_with_anchor(anchor: B256) -> DeferredTrieData {
413        DeferredTrieData::pending(
414            Arc::new(HashedPostState::default()),
415            Arc::new(TrieUpdates::default()),
416            anchor,
417            Vec::new(),
418        )
419    }
420
421    /// Verifies that a ready handle returns immediately without computation.
422    #[test]
423    fn ready_returns_immediately() {
424        let bundle = empty_bundle();
425        let deferred = DeferredTrieData::ready(bundle.clone());
426
427        let start = Instant::now();
428        let result = deferred.wait_cloned();
429        let elapsed = start.elapsed();
430
431        assert_eq!(result.hashed_state, bundle.hashed_state);
432        assert_eq!(result.trie_updates, bundle.trie_updates);
433        assert_eq!(result.anchor_hash(), bundle.anchor_hash());
434        assert!(elapsed < Duration::from_millis(20));
435    }
436
437    /// Verifies that a pending handle computes trie data synchronously via fallback.
438    #[test]
439    fn pending_computes_fallback() {
440        let deferred = empty_pending();
441
442        // wait_cloned should compute from inputs without blocking
443        let start = Instant::now();
444        let result = deferred.wait_cloned();
445        let elapsed = start.elapsed();
446
447        // Should return quickly (fallback computation)
448        assert!(elapsed < Duration::from_millis(100));
449        assert!(result.hashed_state.is_empty());
450    }
451
452    /// Verifies that fallback computation result is cached for subsequent calls.
453    #[test]
454    fn fallback_result_is_cached() {
455        let deferred = empty_pending();
456
457        // First call computes and should stash the result
458        let first = deferred.wait_cloned();
459        // Second call should reuse the cached result (same Arc pointer)
460        let second = deferred.wait_cloned();
461
462        assert!(Arc::ptr_eq(&first.hashed_state, &second.hashed_state));
463        assert!(Arc::ptr_eq(&first.trie_updates, &second.trie_updates));
464        assert_eq!(first.anchor_hash(), second.anchor_hash());
465    }
466
467    /// Verifies that concurrent `wait_cloned` calls result in only one computation,
468    /// with all callers receiving the same cached result.
469    #[test]
470    fn concurrent_wait_cloned_computes_once() {
471        let deferred = empty_pending();
472
473        // Spawn multiple threads that all call wait_cloned concurrently
474        let handles: Vec<_> = (0..10)
475            .map(|_| {
476                let d = deferred.clone();
477                thread::spawn(move || d.wait_cloned())
478            })
479            .collect();
480
481        // Collect all results
482        let results: Vec<_> = handles.into_iter().map(|h| h.join().unwrap()).collect();
483
484        // All results should share the same Arc pointers (same computed result)
485        let first = &results[0];
486        for result in &results[1..] {
487            assert!(Arc::ptr_eq(&first.hashed_state, &result.hashed_state));
488            assert!(Arc::ptr_eq(&first.trie_updates, &result.trie_updates));
489        }
490    }
491
492    /// Tests that ancestor trie data is merged during fallback computation and that the
493    /// resulting `ComputedTrieData` uses the current block's anchor hash, not the ancestor's.
494    #[test]
495    fn ancestors_are_merged() {
496        // Create ancestor with some data
497        let ancestor_bundle = ComputedTrieData {
498            hashed_state: Arc::default(),
499            trie_updates: Arc::default(),
500            anchored_trie_input: Some(AnchoredTrieInput {
501                anchor_hash: B256::with_last_byte(1),
502                trie_input: Arc::new(TrieInputSorted::default()),
503            }),
504        };
505        let ancestor = DeferredTrieData::ready(ancestor_bundle);
506
507        // Create pending with ancestor
508        let deferred = DeferredTrieData::pending(
509            Arc::new(HashedPostState::default()),
510            Arc::new(TrieUpdates::default()),
511            B256::with_last_byte(2),
512            vec![ancestor],
513        );
514
515        let result = deferred.wait_cloned();
516        // Should have the current block's anchor, not the ancestor's
517        assert_eq!(result.anchor_hash(), Some(B256::with_last_byte(2)));
518    }
519
520    /// Ensures ancestor overlays are merged oldest -> newest so latest state wins (no overwrite by
521    /// older ancestors).
522    #[test]
523    fn ancestors_merge_in_chronological_order() {
524        let key = B256::with_last_byte(1);
525        // Oldest ancestor sets nonce to 1
526        let oldest_state = HashedPostStateSorted::new(
527            vec![(key, Some(Account { nonce: 1, balance: U256::ZERO, bytecode_hash: None }))],
528            B256Map::default(),
529        );
530        // Newest ancestor overwrites nonce to 2
531        let newest_state = HashedPostStateSorted::new(
532            vec![(key, Some(Account { nonce: 2, balance: U256::ZERO, bytecode_hash: None }))],
533            B256Map::default(),
534        );
535
536        let oldest = ComputedTrieData {
537            hashed_state: Arc::new(oldest_state),
538            trie_updates: Arc::default(),
539            anchored_trie_input: None,
540        };
541        let newest = ComputedTrieData {
542            hashed_state: Arc::new(newest_state),
543            trie_updates: Arc::default(),
544            anchored_trie_input: None,
545        };
546
547        // Pass ancestors oldest -> newest; newest should take precedence
548        let deferred = DeferredTrieData::pending(
549            Arc::new(HashedPostState::default()),
550            Arc::new(TrieUpdates::default()),
551            B256::ZERO,
552            vec![DeferredTrieData::ready(oldest), DeferredTrieData::ready(newest)],
553        );
554
555        let result = deferred.wait_cloned();
556        let overlay_state = &result.anchored_trie_input.as_ref().unwrap().trie_input.state.accounts;
557        assert_eq!(overlay_state.len(), 1);
558        let (_, account) = &overlay_state[0];
559        assert_eq!(account.unwrap().nonce, 2);
560    }
561
562    /// Helper to create a ready block with anchored trie input containing specific state.
563    fn ready_block_with_state(
564        anchor_hash: B256,
565        accounts: Vec<(B256, Option<Account>)>,
566    ) -> DeferredTrieData {
567        let hashed_state = Arc::new(HashedPostStateSorted::new(accounts, B256Map::default()));
568        let trie_updates = Arc::default();
569        let mut overlay = TrieInputSorted::default();
570        Arc::make_mut(&mut overlay.state).extend_ref_and_sort(hashed_state.as_ref());
571
572        DeferredTrieData::ready(ComputedTrieData {
573            hashed_state,
574            trie_updates,
575            anchored_trie_input: Some(AnchoredTrieInput {
576                anchor_hash,
577                trie_input: Arc::new(overlay),
578            }),
579        })
580    }
581
582    /// Verifies that first block after anchor (no ancestors) creates empty base overlay.
583    #[test]
584    fn first_block_after_anchor_creates_empty_base() {
585        let anchor = B256::with_last_byte(1);
586        let key = B256::with_last_byte(42);
587        let account = Account { nonce: 1, balance: U256::ZERO, bytecode_hash: None };
588
589        // First block after anchor - no ancestors
590        let first_block = DeferredTrieData::pending(
591            Arc::new(HashedPostState::default().with_accounts([(key, Some(account))])),
592            Arc::new(TrieUpdates::default()),
593            anchor,
594            vec![], // No ancestors
595        );
596
597        let result = first_block.wait_cloned();
598
599        // Should have overlay with just this block's data
600        let overlay = result.anchored_trie_input.as_ref().unwrap();
601        assert_eq!(overlay.anchor_hash, anchor);
602        assert_eq!(overlay.trie_input.state.accounts.len(), 1);
603        let (found_key, found_account) = &overlay.trie_input.state.accounts[0];
604        assert_eq!(*found_key, key);
605        assert_eq!(found_account.unwrap().nonce, 1);
606    }
607
608    /// Verifies that parent's overlay is reused regardless of anchor.
609    #[test]
610    fn reuses_parent_overlay() {
611        let anchor = B256::with_last_byte(1);
612        let key = B256::with_last_byte(42);
613        let account = Account { nonce: 100, balance: U256::ZERO, bytecode_hash: None };
614
615        // Create parent with anchored trie input
616        let parent = ready_block_with_state(anchor, vec![(key, Some(account))]);
617
618        // Create child - should reuse parent's overlay
619        let child = DeferredTrieData::pending(
620            Arc::new(HashedPostState::default()),
621            Arc::new(TrieUpdates::default()),
622            anchor,
623            vec![parent],
624        );
625
626        let result = child.wait_cloned();
627
628        // Verify parent's account is in the overlay
629        let overlay = result.anchored_trie_input.as_ref().unwrap();
630        assert_eq!(overlay.anchor_hash, anchor);
631        assert_eq!(overlay.trie_input.state.accounts.len(), 1);
632        let (found_key, found_account) = &overlay.trie_input.state.accounts[0];
633        assert_eq!(*found_key, key);
634        assert_eq!(found_account.unwrap().nonce, 100);
635    }
636
637    /// Verifies that parent's overlay is NOT reused when anchor changes (after persist).
638    /// The overlay data is dependent on the anchor, so it must be rebuilt from the
639    /// remaining ancestors.
640    #[test]
641    fn rebuilds_overlay_when_anchor_changes() {
642        let old_anchor = B256::with_last_byte(1);
643        let new_anchor = B256::with_last_byte(2);
644        let key = B256::with_last_byte(42);
645        let account = Account { nonce: 50, balance: U256::ZERO, bytecode_hash: None };
646
647        // Create parent with OLD anchor
648        let parent = ready_block_with_state(old_anchor, vec![(key, Some(account))]);
649
650        // Create child with NEW anchor (simulates after persist)
651        // Should NOT reuse parent's overlay because anchor changed
652        let child = DeferredTrieData::pending(
653            Arc::new(HashedPostState::default()),
654            Arc::new(TrieUpdates::default()),
655            new_anchor,
656            vec![parent],
657        );
658
659        let result = child.wait_cloned();
660
661        // Verify result uses new anchor
662        let overlay = result.anchored_trie_input.as_ref().unwrap();
663        assert_eq!(overlay.anchor_hash, new_anchor);
664
665        // Crucially, since we provided `parent` in ancestors but it has a different anchor,
666        // the code falls back to `merge_ancestors_into_overlay`.
667        // `merge_ancestors_into_overlay` reads `parent.hashed_state` (which has the account).
668        // So the account IS present, but it was obtained via REBUILD, not REUSE.
669        // We can check `DEFERRED_TRIE_METRICS` if we want to be sure, but functionally:
670        assert_eq!(overlay.trie_input.state.accounts.len(), 1);
671        let (found_key, found_account) = &overlay.trie_input.state.accounts[0];
672        assert_eq!(*found_key, key);
673        assert_eq!(found_account.unwrap().nonce, 50);
674    }
675
676    /// Verifies that parent without `anchored_trie_input` triggers rebuild path.
677    #[test]
678    fn rebuilds_when_parent_has_no_anchored_input() {
679        let anchor = B256::with_last_byte(1);
680        let key = B256::with_last_byte(42);
681        let account = Account { nonce: 25, balance: U256::ZERO, bytecode_hash: None };
682
683        // Create parent WITHOUT anchored trie input (e.g., from without_trie_input constructor)
684        let parent_state =
685            HashedPostStateSorted::new(vec![(key, Some(account))], B256Map::default());
686        let parent = DeferredTrieData::ready(ComputedTrieData {
687            hashed_state: Arc::new(parent_state),
688            trie_updates: Arc::default(),
689            anchored_trie_input: None, // No anchored input
690        });
691
692        // Create child - should rebuild from parent's hashed_state
693        let child = DeferredTrieData::pending(
694            Arc::new(HashedPostState::default()),
695            Arc::new(TrieUpdates::default()),
696            anchor,
697            vec![parent],
698        );
699
700        let result = child.wait_cloned();
701
702        // Verify overlay is built and contains parent's data
703        let overlay = result.anchored_trie_input.as_ref().unwrap();
704        assert_eq!(overlay.anchor_hash, anchor);
705        assert_eq!(overlay.trie_input.state.accounts.len(), 1);
706    }
707
708    /// Verifies that a chain of blocks with matching anchors builds correct cumulative overlay.
709    #[test]
710    fn chain_of_blocks_builds_cumulative_overlay() {
711        let anchor = B256::with_last_byte(1);
712        let key1 = B256::with_last_byte(1);
713        let key2 = B256::with_last_byte(2);
714        let key3 = B256::with_last_byte(3);
715
716        // Block 1: sets account at key1
717        let block1 = ready_block_with_state(
718            anchor,
719            vec![(key1, Some(Account { nonce: 1, balance: U256::ZERO, bytecode_hash: None }))],
720        );
721
722        // Block 2: adds account at key2, ancestor is block1
723        let block2_hashed = HashedPostState::default().with_accounts([(
724            key2,
725            Some(Account { nonce: 2, balance: U256::ZERO, bytecode_hash: None }),
726        )]);
727        let block2 = DeferredTrieData::pending(
728            Arc::new(block2_hashed),
729            Arc::new(TrieUpdates::default()),
730            anchor,
731            vec![block1.clone()],
732        );
733        // Compute block2's trie data
734        let block2_computed = block2.wait_cloned();
735        let block2_ready = DeferredTrieData::ready(block2_computed);
736
737        // Block 3: adds account at key3, ancestor is block2 (which includes block1)
738        let block3_hashed = HashedPostState::default().with_accounts([(
739            key3,
740            Some(Account { nonce: 3, balance: U256::ZERO, bytecode_hash: None }),
741        )]);
742        let block3 = DeferredTrieData::pending(
743            Arc::new(block3_hashed),
744            Arc::new(TrieUpdates::default()),
745            anchor,
746            vec![block1, block2_ready],
747        );
748
749        let result = block3.wait_cloned();
750
751        // Verify all three accounts are in the cumulative overlay
752        let overlay = result.anchored_trie_input.as_ref().unwrap();
753        assert_eq!(overlay.trie_input.state.accounts.len(), 3);
754
755        // Accounts should be sorted by key (B256 ordering)
756        let accounts = &overlay.trie_input.state.accounts;
757        assert!(accounts.iter().any(|(k, a)| *k == key1 && a.unwrap().nonce == 1));
758        assert!(accounts.iter().any(|(k, a)| *k == key2 && a.unwrap().nonce == 2));
759        assert!(accounts.iter().any(|(k, a)| *k == key3 && a.unwrap().nonce == 3));
760    }
761
762    /// Verifies that child block's state overwrites parent's state for the same key.
763    #[test]
764    fn child_state_overwrites_parent() {
765        let anchor = B256::with_last_byte(1);
766        let key = B256::with_last_byte(42);
767
768        // Parent sets nonce to 10
769        let parent = ready_block_with_state(
770            anchor,
771            vec![(key, Some(Account { nonce: 10, balance: U256::ZERO, bytecode_hash: None }))],
772        );
773
774        // Child overwrites nonce to 99
775        let child_hashed = HashedPostState::default().with_accounts([(
776            key,
777            Some(Account { nonce: 99, balance: U256::ZERO, bytecode_hash: None }),
778        )]);
779        let child = DeferredTrieData::pending(
780            Arc::new(child_hashed),
781            Arc::new(TrieUpdates::default()),
782            anchor,
783            vec![parent],
784        );
785
786        let result = child.wait_cloned();
787
788        // Verify child's value wins (extend_ref uses later value)
789        let overlay = result.anchored_trie_input.as_ref().unwrap();
790        // Note: extend_ref may result in duplicate keys; check the last occurrence
791        let accounts = &overlay.trie_input.state.accounts;
792        let last_account = accounts.iter().rfind(|(k, _)| *k == key).unwrap();
793        assert_eq!(last_account.1.unwrap().nonce, 99);
794    }
795
796    /// Stress test: verify O(N) behavior by building a chain of many blocks.
797    /// This test ensures the fix doesn't regress - previously this would be O(N²).
798    #[test]
799    fn long_chain_builds_in_linear_time() {
800        let anchor = B256::with_last_byte(1);
801        let num_blocks = 50; // Enough to notice O(N²) vs O(N) difference
802
803        let mut ancestors: Vec<DeferredTrieData> = Vec::new();
804
805        let start = Instant::now();
806
807        for i in 0..num_blocks {
808            let key = B256::with_last_byte(i as u8);
809            let account = Account { nonce: i as u64, balance: U256::ZERO, bytecode_hash: None };
810            let hashed = HashedPostState::default().with_accounts([(key, Some(account))]);
811
812            let block = DeferredTrieData::pending(
813                Arc::new(hashed),
814                Arc::new(TrieUpdates::default()),
815                anchor,
816                ancestors.clone(),
817            );
818
819            // Compute and add to ancestors for next iteration
820            let computed = block.wait_cloned();
821            ancestors.push(DeferredTrieData::ready(computed));
822        }
823
824        let elapsed = start.elapsed();
825
826        // With O(N) fix, 50 blocks should complete quickly (< 1 second)
827        // With O(N²), this would take significantly longer
828        assert!(
829            elapsed < Duration::from_secs(2),
830            "Chain of {num_blocks} blocks took {:?}, possible O(N²) regression",
831            elapsed
832        );
833
834        // Verify final overlay has all accounts
835        let final_result = ancestors.last().unwrap().wait_cloned();
836        let overlay = final_result.anchored_trie_input.as_ref().unwrap();
837        assert_eq!(overlay.trie_input.state.accounts.len(), num_blocks);
838    }
839
840    /// Verifies that a multi-ancestor overlay is rebuilt when anchor changes.
841    /// This simulates the "persist prefix then keep building" scenario where:
842    /// 1. A chain of blocks is built with anchor A
843    /// 2. Some blocks are persisted, changing anchor to B
844    /// 3. New blocks must rebuild the overlay from the remaining ancestors
845    #[test]
846    fn multi_ancestor_overlay_rebuilt_after_anchor_change() {
847        let old_anchor = B256::with_last_byte(1);
848        let new_anchor = B256::with_last_byte(2);
849        let key1 = B256::with_last_byte(1);
850        let key2 = B256::with_last_byte(2);
851        let key3 = B256::with_last_byte(3);
852        let key4 = B256::with_last_byte(4);
853
854        // Build a chain of 3 blocks with old_anchor
855        let block1 = ready_block_with_state(
856            old_anchor,
857            vec![(key1, Some(Account { nonce: 1, balance: U256::ZERO, bytecode_hash: None }))],
858        );
859
860        let block2_hashed = HashedPostState::default().with_accounts([(
861            key2,
862            Some(Account { nonce: 2, balance: U256::ZERO, bytecode_hash: None }),
863        )]);
864        let block2 = DeferredTrieData::pending(
865            Arc::new(block2_hashed),
866            Arc::new(TrieUpdates::default()),
867            old_anchor,
868            vec![block1.clone()],
869        );
870        let block2_ready = DeferredTrieData::ready(block2.wait_cloned());
871
872        let block3_hashed = HashedPostState::default().with_accounts([(
873            key3,
874            Some(Account { nonce: 3, balance: U256::ZERO, bytecode_hash: None }),
875        )]);
876        let block3 = DeferredTrieData::pending(
877            Arc::new(block3_hashed),
878            Arc::new(TrieUpdates::default()),
879            old_anchor,
880            vec![block1.clone(), block2_ready.clone()],
881        );
882        let block3_ready = DeferredTrieData::ready(block3.wait_cloned());
883
884        // Verify block3's overlay has all 3 accounts with old_anchor
885        let block3_overlay = block3_ready.wait_cloned().anchored_trie_input.unwrap();
886        assert_eq!(block3_overlay.anchor_hash, old_anchor);
887        assert_eq!(block3_overlay.trie_input.state.accounts.len(), 3);
888
889        // Now simulate persist: create block4 with NEW anchor but same ancestors.
890        // To verify correct rebuilding, we must provide ALL unpersisted ancestors.
891        // If we only provided block3, the rebuild would only see block3's state.
892        // We pass block1, block2, block3 to simulate that they are all still in memory
893        // but the anchor check forces a rebuild (e.g. artificial anchor change).
894        let block4_hashed = HashedPostState::default().with_accounts([(
895            key4,
896            Some(Account { nonce: 4, balance: U256::ZERO, bytecode_hash: None }),
897        )]);
898        let block4 = DeferredTrieData::pending(
899            Arc::new(block4_hashed),
900            Arc::new(TrieUpdates::default()),
901            new_anchor, // Different anchor - simulates post-persist
902            vec![block1, block2_ready, block3_ready],
903        );
904
905        let result = block4.wait_cloned();
906
907        // Verify:
908        // 1. New anchor is used in result
909        assert_eq!(result.anchor_hash(), Some(new_anchor));
910
911        // 2. All 4 accounts are in the overlay (rebuilt from ancestors + extended)
912        let overlay = result.anchored_trie_input.as_ref().unwrap();
913        assert_eq!(overlay.trie_input.state.accounts.len(), 4);
914
915        // 3. All accounts have correct values
916        let accounts = &overlay.trie_input.state.accounts;
917        assert!(accounts.iter().any(|(k, a)| *k == key1 && a.unwrap().nonce == 1));
918        assert!(accounts.iter().any(|(k, a)| *k == key2 && a.unwrap().nonce == 2));
919        assert!(accounts.iter().any(|(k, a)| *k == key3 && a.unwrap().nonce == 3));
920        assert!(accounts.iter().any(|(k, a)| *k == key4 && a.unwrap().nonce == 4));
921    }
922}