Skip to main content

reth_trie_sparse/arena/
cursor.rs

1use super::{
2    branch_child_idx::{BranchChildIdx, BranchChildIter},
3    ArenaSparseNode, ArenaSparseNodeBranchChild, ArenaSparseNodeState, Index, NodeArena,
4};
5use alloc::vec::Vec;
6use reth_trie_common::Nibbles;
7use tracing::{instrument, trace};
8
9const TRACE_TARGET: &str = "trie::arena::cursor";
10
11/// An entry on the cursor's traversal stack, tracking an ancestor node during trie walks.
12#[derive(Debug, Clone)]
13pub(super) struct ArenaCursorStackEntry {
14    /// The arena index of this node.
15    pub(super) index: Index,
16    /// The absolute path of this node in the trie (not including its `short_key`).
17    pub(super) path: Nibbles,
18    /// The dense index at which to resume child iteration in [`ArenaCursor::next`].
19    /// Only meaningful when this entry's node is a branch.
20    pub(super) next_dense_idx: usize,
21}
22
23/// Result of [`ArenaCursor::seek`] describing the state at the deepest ancestor node.
24#[derive(Debug)]
25pub(super) enum SeekResult {
26    /// The stack head is an empty root node.
27    EmptyRoot,
28    /// The stack head is a leaf whose full path matches the target exactly.
29    RevealedLeaf,
30    /// The next child along the path is blinded (unrevealed).
31    Blinded,
32    /// The target path diverges from the stack head's `short_key` (branch or leaf).
33    Diverged,
34    /// The target nibble has no child in the branch's `state_mask`.
35    NoChild { child_nibble: u8 },
36    /// The target nibble has a revealed subtrie child (now pushed onto the stack).
37    RevealedSubtrie,
38}
39
40/// Result of [`ArenaCursor::next`] describing what the cursor did.
41#[derive(Debug)]
42pub(super) enum NextResult {
43    /// The head is a non-branch node (subtrie, taken-subtrie, leaf, etc.).
44    /// The caller should process it; the next call to [`ArenaCursor::next`] will pop it.
45    NonBranch,
46    /// The head branch has no more qualifying children. It is still on the stack;
47    /// the caller should process it. The next call to [`ArenaCursor::next`] will pop it.
48    Branch,
49    /// The stack is empty — the traversal is complete.
50    Done,
51}
52
53/// A cursor for depth-first traversal of an arena-based sparse trie.
54///
55/// Wraps a stack of [`ArenaCursorStackEntry`]s and provides methods for navigating
56/// the trie: pushing children, popping with dirty-state propagation, seeking
57/// to ancestors, and computing child paths.
58///
59/// The cursor borrows the arena on each method call rather than holding a
60/// reference, so the caller retains full ownership of the arena between calls.
61#[derive(Debug, Default, Clone)]
62pub(super) struct ArenaCursor {
63    stack: Vec<ArenaCursorStackEntry>,
64    /// Whether the head entry should be popped at the start of the next [`Self::next`] call.
65    /// Set when `next` returns [`NextResult::NonBranch`] or [`NextResult::Branch`].
66    needs_pop: bool,
67}
68
69impl ArenaCursor {
70    /// Returns the entry at the top of the stack, or `None` if empty.
71    pub(super) fn head(&self) -> Option<&ArenaCursorStackEntry> {
72        self.stack.last()
73    }
74
75    /// Returns the entry below the top of the stack (the parent of the head), or `None`.
76    pub(super) fn parent(&self) -> Option<&ArenaCursorStackEntry> {
77        let len = self.stack.len();
78        (len >= 2).then(|| &self.stack[len - 2])
79    }
80
81    /// Returns the depth of the head node (0 for the root).
82    ///
83    /// # Panics
84    ///
85    /// Panics if the stack is empty.
86    pub(super) const fn depth(&self) -> usize {
87        self.stack.len() - 1
88    }
89
90    /// Replaces the root entry on the stack with a new one.
91    ///
92    /// The stack must contain exactly the root (depth 0) or be empty (freshly constructed).
93    #[instrument(level = "trace", target = TRACE_TARGET, skip(self, arena))]
94    pub(super) fn reset(&mut self, arena: &NodeArena, idx: Index, path: Nibbles) {
95        debug_assert!(
96            self.stack.len() <= 1 && !self.needs_pop,
97            "cursor must be drained before reset; stack has {} entries, needs_pop={}",
98            self.stack.len(),
99            self.needs_pop,
100        );
101        self.stack.clear();
102        self.needs_pop = false;
103        self.push(arena, idx, path);
104    }
105
106    /// Pushes an entry onto the stack for the node at the given index and path.
107    fn push(&mut self, arena: &NodeArena, idx: Index, path: Nibbles) {
108        debug_assert!(arena.contains_key(idx), "push called with invalid arena index");
109        self.stack.push(ArenaCursorStackEntry { index: idx, path, next_dense_idx: 0 });
110        trace!(target: TRACE_TARGET, entry = ?self.stack.last().expect("just pushed"), "Pushed stack entry");
111    }
112
113    /// Pops the top entry from the stack and propagates dirty state to the parent.
114    /// Returns the popped entry.
115    ///
116    /// Uses `arena.get()` for the popped node because callers (e.g. pruning) may remove
117    /// the node from the arena between the time it was pushed and the time it is popped.
118    #[instrument(level = "trace", target = TRACE_TARGET, skip(self, arena))]
119    pub(super) fn pop(&mut self, arena: &mut NodeArena) -> ArenaCursorStackEntry {
120        let entry = self.stack.pop().expect("pop can't be called on empty stack");
121        trace!(target: TRACE_TARGET, entry = ?entry, "Popped stack entry");
122
123        #[cfg(debug_assertions)]
124        if let Some(ArenaSparseNode::Subtrie(s)) = arena.get(entry.index) {
125            debug_assert_eq!(
126                s.path, entry.path,
127                "subtrie cached path {:?} does not match stack entry path {:?}",
128                s.path, entry.path,
129            );
130        }
131
132        if let Some(parent) = self.stack.last() {
133            let child_is_dirty = arena.get(entry.index).is_some_and(|node| match node {
134                ArenaSparseNode::Branch(b) => matches!(b.state, ArenaSparseNodeState::Dirty),
135                ArenaSparseNode::Leaf { state, .. } => matches!(state, ArenaSparseNodeState::Dirty),
136                ArenaSparseNode::Subtrie(s) => {
137                    let root = &s.arena[s.root];
138                    matches!(root.state_ref(), Some(ArenaSparseNodeState::Dirty))
139                }
140                _ => false,
141            });
142            if child_is_dirty {
143                *arena[parent.index].state_mut() = ArenaSparseNodeState::Dirty;
144            }
145        }
146
147        entry
148    }
149
150    /// Drains the stack down to the root, propagating dirty state from each popped entry
151    /// to its parent. The root entry remains on the stack (there is no parent to propagate to).
152    #[instrument(level = "trace", target = TRACE_TARGET, skip_all)]
153    pub(super) fn drain(&mut self, arena: &mut NodeArena) {
154        trace!(target: TRACE_TARGET, "Draining stack");
155        self.needs_pop = false;
156        while self.stack.len() > 1 {
157            self.pop(arena);
158        }
159    }
160
161    /// Returns the logical path of the branch at the top of the stack.
162    /// The logical path is `entry.path + branch.short_key`.
163    pub(super) fn head_logical_branch_path(&self, arena: &NodeArena) -> Nibbles {
164        logical_branch_path(arena, self.stack.last().expect("cursor is non-empty"))
165    }
166
167    /// Returns the length of the logical path of the branch at the top of the stack.
168    /// Equivalent to `head_logical_branch_path(arena).len()` but avoids constructing the path.
169    pub(super) fn head_logical_branch_path_len(&self, arena: &NodeArena) -> usize {
170        logical_branch_path_len(arena, self.stack.last().expect("cursor is non-empty"))
171    }
172
173    /// Returns the absolute path of a child at `child_nibble` under the branch at the top of
174    /// the stack. The result is `stack_head.path + branch.short_key + child_nibble`.
175    pub(super) fn child_path(&self, arena: &NodeArena, child_nibble: u8) -> Nibbles {
176        let mut path = logical_branch_path(arena, self.stack.last().expect("cursor is non-empty"));
177        path.push_unchecked(child_nibble);
178        path
179    }
180
181    /// Returns the logical path of the parent branch entry (second from top of the stack).
182    /// Panics if the stack has fewer than 2 entries.
183    pub(super) fn parent_logical_branch_path(&self, arena: &NodeArena) -> Nibbles {
184        logical_branch_path(arena, self.parent().expect("cursor must have a parent"))
185    }
186
187    /// Replaces the arena index stored in the head entry with `new_idx`, and updates the
188    /// parent branch's children array to point to the new index. If the head is the root
189    /// (stack has one entry), `root` is updated instead.
190    pub(super) fn replace_head_index(
191        &mut self,
192        arena: &mut NodeArena,
193        root: &mut Index,
194        new_idx: Index,
195    ) {
196        let head = self.stack.last_mut().expect("cursor must have head");
197        let old_idx = head.index;
198        let child_nibble = head.path.last();
199        head.index = new_idx;
200
201        let Some(parent) = self.parent() else {
202            *root = new_idx;
203            return;
204        };
205
206        let child_nibble =
207            child_nibble.expect("if cursor has a parent then the head path can't be empty");
208
209        let parent_branch = arena[parent.index].branch_mut();
210        let child_idx = BranchChildIdx::new(parent_branch.state_mask, child_nibble)
211            .expect("child nibble not found in parent state_mask");
212
213        debug_assert!(
214            matches!(
215                parent_branch.children[child_idx],
216                ArenaSparseNodeBranchChild::Revealed(idx)
217                if idx == old_idx
218            ),
219            "parent child at nibble {child_nibble} does not match old_idx",
220        );
221
222        parent_branch.children[child_idx] = ArenaSparseNodeBranchChild::Revealed(new_idx);
223    }
224
225    /// Advances the DFS traversal to the next actionable node.
226    ///
227    /// If a previous call returned [`NextResult::NonBranch`] or [`NextResult::Branch`],
228    /// the head entry is automatically popped (with dirty-state propagation) before
229    /// descending further. This means callers never need to call [`Self::pop`] after
230    /// `next` — it is handled internally on the subsequent call.
231    ///
232    /// Returns [`NextResult::NonBranch`] when the head is a non-branch node the caller
233    /// should process, or [`NextResult::Branch`] when a branch has exhausted its
234    /// qualifying children. In both cases the node is still on the stack so the caller
235    /// can read it via [`Self::head`].
236    ///
237    /// Returns [`NextResult::Done`] when the stack is empty (traversal complete).
238    #[instrument(level = "trace", target = TRACE_TARGET, skip_all, ret)]
239    pub(super) fn next(
240        &mut self,
241        arena: &mut NodeArena,
242        should_descend: impl Fn(usize, &ArenaSparseNode) -> bool,
243    ) -> NextResult {
244        if self.needs_pop {
245            self.pop(arena);
246            self.needs_pop = false;
247        }
248
249        loop {
250            let Some(head) = self.stack.last_mut() else {
251                return NextResult::Done;
252            };
253            let head_idx = head.index;
254
255            let ArenaSparseNode::Branch(branch) = &arena[head_idx] else {
256                self.needs_pop = true;
257                return NextResult::NonBranch;
258            };
259
260            let state_mask = branch.state_mask;
261            let start = head.next_dense_idx;
262            let child_depth = self.stack.len();
263
264            let mut descended = false;
265            for (branch_child_idx, nibble) in BranchChildIter::new(state_mask) {
266                if branch_child_idx.get() < start {
267                    continue;
268                }
269
270                let child_idx = match &arena[head_idx].branch_ref().children[branch_child_idx] {
271                    ArenaSparseNodeBranchChild::Revealed(child_idx) => *child_idx,
272                    ArenaSparseNodeBranchChild::Blinded(_) => continue,
273                };
274
275                if should_descend(child_depth, &arena[child_idx]) {
276                    // Record where to resume iteration when we return to this entry.
277                    self.stack.last_mut().expect("head exists").next_dense_idx =
278                        branch_child_idx.get() + 1;
279                    let path = self.child_path(arena, nibble);
280                    self.push(arena, child_idx, path);
281                    descended = true;
282                    break;
283                }
284            }
285
286            if !descended {
287                self.needs_pop = true;
288                return NextResult::Branch;
289            }
290        }
291    }
292
293    /// Pops the stack until the head is an ancestor of `full_path`, then descends from that head
294    /// toward `full_path`, pushing revealed branch (and leaf) children onto the stack until the
295    /// deepest ancestor is reached.
296    ///
297    /// Returns a [`SeekResult`] describing the state at the stack head.
298    #[instrument(level = "trace", target = TRACE_TARGET, skip(self, arena), ret)]
299    pub(super) fn seek(&mut self, arena: &mut NodeArena, full_path: &Nibbles) -> SeekResult {
300        // Pop stack until head is ancestor of full_path.
301        while self.stack.len() > 1 &&
302            !full_path.starts_with(&self.stack.last().expect("cursor has root").path)
303        {
304            self.pop(arena);
305        }
306
307        loop {
308            let head = self.stack.last().expect("cursor has root");
309            let head_idx = head.index;
310
311            let head_branch = match &arena[head_idx] {
312                ArenaSparseNode::EmptyRoot { .. } => {
313                    return SeekResult::EmptyRoot;
314                }
315                ArenaSparseNode::Leaf { key, .. } => {
316                    let mut leaf_full_path = head.path;
317                    leaf_full_path.extend(key);
318                    return if &leaf_full_path == full_path {
319                        SeekResult::RevealedLeaf
320                    } else {
321                        SeekResult::Diverged
322                    };
323                }
324                ArenaSparseNode::Branch(b) => b,
325                ArenaSparseNode::Subtrie(_) => {
326                    return SeekResult::RevealedSubtrie;
327                }
328                _ => unreachable!("unexpected node type on stack: {:?}", arena[head_idx]),
329            };
330
331            let head_branch_logical_path = logical_branch_path(arena, head);
332
333            // If full_path doesn't extend past the branch's logical path, the target is at or
334            // within the branch's short_key — treat as diverged.
335            if full_path.len() <= head_branch_logical_path.len() ||
336                !full_path.starts_with(&head_branch_logical_path)
337            {
338                return SeekResult::Diverged;
339            }
340
341            let child_nibble = full_path.get_unchecked(head_branch_logical_path.len());
342            let Some(branch_child_idx) = BranchChildIdx::new(head_branch.state_mask, child_nibble)
343            else {
344                return SeekResult::NoChild { child_nibble };
345            };
346
347            match &head_branch.children[branch_child_idx] {
348                ArenaSparseNodeBranchChild::Blinded(_) => {
349                    return SeekResult::Blinded;
350                }
351                ArenaSparseNodeBranchChild::Revealed(child_idx) => {
352                    let child_idx = *child_idx;
353                    let path = self.child_path(arena, child_nibble);
354                    self.push(arena, child_idx, path);
355                }
356            }
357        }
358    }
359}
360
361/// Returns the logical path of a branch stack entry. The logical path is
362/// `entry.path + branch.short_key`.
363fn logical_branch_path(arena: &NodeArena, entry: &ArenaCursorStackEntry) -> Nibbles {
364    let mut path = entry.path;
365    path.extend(&arena[entry.index].branch_ref().short_key);
366    path
367}
368
369/// Returns the length of the logical path of a branch stack entry.
370/// Equivalent to `logical_branch_path(arena, entry).len()` but avoids constructing the path.
371fn logical_branch_path_len(arena: &NodeArena, entry: &ArenaCursorStackEntry) -> usize {
372    entry.path.len() + arena[entry.index].branch_ref().short_key.len()
373}