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