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, ArenaSparseNode::EmptyRoot) ||
139                        matches!(root.state_ref(), Some(ArenaSparseNodeState::Dirty))
140                }
141                _ => false,
142            });
143            if child_is_dirty {
144                *arena[parent.index].state_mut() = ArenaSparseNodeState::Dirty;
145            }
146        }
147
148        entry
149    }
150
151    /// Drains the stack down to the root, propagating dirty state from each popped entry
152    /// to its parent. The root entry remains on the stack (there is no parent to propagate to).
153    #[instrument(level = "trace", target = TRACE_TARGET, skip_all)]
154    pub(super) fn drain(&mut self, arena: &mut NodeArena) {
155        trace!(target: TRACE_TARGET, "Draining stack");
156        self.needs_pop = false;
157        while self.stack.len() > 1 {
158            self.pop(arena);
159        }
160    }
161
162    /// Returns the logical path of the branch at the top of the stack.
163    /// The logical path is `entry.path + branch.short_key`.
164    pub(super) fn head_logical_branch_path(&self, arena: &NodeArena) -> Nibbles {
165        logical_branch_path(arena, self.stack.last().expect("cursor is non-empty"))
166    }
167
168    /// Returns the length of the logical path of the branch at the top of the stack.
169    /// Equivalent to `head_logical_branch_path(arena).len()` but avoids constructing the path.
170    pub(super) fn head_logical_branch_path_len(&self, arena: &NodeArena) -> usize {
171        logical_branch_path_len(arena, self.stack.last().expect("cursor is non-empty"))
172    }
173
174    /// Returns the absolute path of a child at `child_nibble` under the branch at the top of
175    /// the stack. The result is `stack_head.path + branch.short_key + child_nibble`.
176    pub(super) fn child_path(&self, arena: &NodeArena, child_nibble: u8) -> Nibbles {
177        let mut path = logical_branch_path(arena, self.stack.last().expect("cursor is non-empty"));
178        path.push_unchecked(child_nibble);
179        path
180    }
181
182    /// Returns the logical path of the parent branch entry (second from top of the stack).
183    /// Panics if the stack has fewer than 2 entries.
184    pub(super) fn parent_logical_branch_path(&self, arena: &NodeArena) -> Nibbles {
185        logical_branch_path(arena, self.parent().expect("cursor must have a parent"))
186    }
187
188    /// Replaces the arena index stored in the head entry with `new_idx`, and updates the
189    /// parent branch's children array to point to the new index. If the head is the root
190    /// (stack has one entry), `root` is updated instead.
191    pub(super) fn replace_head_index(
192        &mut self,
193        arena: &mut NodeArena,
194        root: &mut Index,
195        new_idx: Index,
196    ) {
197        let head = self.stack.last_mut().expect("cursor must have head");
198        let old_idx = head.index;
199        let child_nibble = head.path.last();
200        head.index = new_idx;
201
202        let Some(parent) = self.parent() else {
203            *root = new_idx;
204            return;
205        };
206
207        let child_nibble =
208            child_nibble.expect("if cursor has a parent then the head path can't be empty");
209
210        let parent_branch = arena[parent.index].branch_mut();
211        let child_idx = BranchChildIdx::new(parent_branch.state_mask, child_nibble)
212            .expect("child nibble not found in parent state_mask");
213
214        debug_assert!(
215            matches!(
216                parent_branch.children[child_idx],
217                ArenaSparseNodeBranchChild::Revealed(idx)
218                if idx == old_idx
219            ),
220            "parent child at nibble {child_nibble} does not match old_idx",
221        );
222
223        parent_branch.children[child_idx] = ArenaSparseNodeBranchChild::Revealed(new_idx);
224    }
225
226    /// Advances the DFS traversal to the next actionable node.
227    ///
228    /// If a previous call returned [`NextResult::NonBranch`] or [`NextResult::Branch`],
229    /// the head entry is automatically popped (with dirty-state propagation) before
230    /// descending further. This means callers never need to call [`Self::pop`] after
231    /// `next` — it is handled internally on the subsequent call.
232    ///
233    /// Returns [`NextResult::NonBranch`] when the head is a non-branch node the caller
234    /// should process, or [`NextResult::Branch`] when a branch has exhausted its
235    /// qualifying children. In both cases the node is still on the stack so the caller
236    /// can read it via [`Self::head`].
237    ///
238    /// Returns [`NextResult::Done`] when the stack is empty (traversal complete).
239    #[instrument(level = "trace", target = TRACE_TARGET, skip_all, ret)]
240    pub(super) fn next(
241        &mut self,
242        arena: &mut NodeArena,
243        should_descend: impl Fn(usize, &ArenaSparseNode) -> bool,
244    ) -> NextResult {
245        if self.needs_pop {
246            self.pop(arena);
247            self.needs_pop = false;
248        }
249
250        loop {
251            let Some(head) = self.stack.last_mut() else {
252                return NextResult::Done;
253            };
254            let head_idx = head.index;
255
256            let ArenaSparseNode::Branch(branch) = &arena[head_idx] else {
257                self.needs_pop = true;
258                return NextResult::NonBranch;
259            };
260
261            let state_mask = branch.state_mask;
262            let start = head.next_dense_idx;
263            let child_depth = self.stack.len();
264
265            let mut descended = false;
266            for (branch_child_idx, nibble) in BranchChildIter::new(state_mask) {
267                if branch_child_idx.get() < start {
268                    continue;
269                }
270
271                let child_idx = match &arena[head_idx].branch_ref().children[branch_child_idx] {
272                    ArenaSparseNodeBranchChild::Revealed(child_idx) => *child_idx,
273                    ArenaSparseNodeBranchChild::Blinded(_) => continue,
274                };
275
276                if should_descend(child_depth, &arena[child_idx]) {
277                    // Record where to resume iteration when we return to this entry.
278                    self.stack.last_mut().expect("head exists").next_dense_idx =
279                        branch_child_idx.get() + 1;
280                    let path = self.child_path(arena, nibble);
281                    self.push(arena, child_idx, path);
282                    descended = true;
283                    break;
284                }
285            }
286
287            if !descended {
288                self.needs_pop = true;
289                return NextResult::Branch;
290            }
291        }
292    }
293
294    /// Pops the stack until the head is an ancestor of `full_path`, then descends from that head
295    /// toward `full_path`, pushing revealed branch (and leaf) children onto the stack until the
296    /// deepest ancestor is reached.
297    ///
298    /// Returns a [`SeekResult`] describing the state at the stack head.
299    #[instrument(level = "trace", target = TRACE_TARGET, skip(self, arena), ret)]
300    pub(super) fn seek(&mut self, arena: &mut NodeArena, full_path: &Nibbles) -> SeekResult {
301        // Pop stack until head is ancestor of full_path.
302        while self.stack.len() > 1 &&
303            !full_path.starts_with(&self.stack.last().expect("cursor has root").path)
304        {
305            self.pop(arena);
306        }
307
308        loop {
309            let head = self.stack.last().expect("cursor has root");
310            let head_idx = head.index;
311
312            let head_branch = match &arena[head_idx] {
313                ArenaSparseNode::EmptyRoot => {
314                    return SeekResult::EmptyRoot;
315                }
316                ArenaSparseNode::Leaf { key, .. } => {
317                    let mut leaf_full_path = head.path;
318                    leaf_full_path.extend(key);
319                    return if &leaf_full_path == full_path {
320                        SeekResult::RevealedLeaf
321                    } else {
322                        SeekResult::Diverged
323                    };
324                }
325                ArenaSparseNode::Branch(b) => b,
326                ArenaSparseNode::Subtrie(_) => {
327                    return SeekResult::RevealedSubtrie;
328                }
329                _ => unreachable!("unexpected node type on stack: {:?}", arena[head_idx]),
330            };
331
332            let head_branch_logical_path = logical_branch_path(arena, head);
333
334            // If full_path doesn't extend past the branch's logical path, the target is at or
335            // within the branch's short_key — treat as diverged.
336            if full_path.len() <= head_branch_logical_path.len() ||
337                !full_path.starts_with(&head_branch_logical_path)
338            {
339                return SeekResult::Diverged;
340            }
341
342            let child_nibble = full_path.get_unchecked(head_branch_logical_path.len());
343            let Some(branch_child_idx) = BranchChildIdx::new(head_branch.state_mask, child_nibble)
344            else {
345                return SeekResult::NoChild { child_nibble };
346            };
347
348            match &head_branch.children[branch_child_idx] {
349                ArenaSparseNodeBranchChild::Blinded(_) => {
350                    return SeekResult::Blinded;
351                }
352                ArenaSparseNodeBranchChild::Revealed(child_idx) => {
353                    let child_idx = *child_idx;
354                    let path = self.child_path(arena, child_nibble);
355                    self.push(arena, child_idx, path);
356                }
357            }
358        }
359    }
360}
361
362/// Returns the logical path of a branch stack entry. The logical path is
363/// `entry.path + branch.short_key`.
364fn logical_branch_path(arena: &NodeArena, entry: &ArenaCursorStackEntry) -> Nibbles {
365    let mut path = entry.path;
366    path.extend(&arena[entry.index].branch_ref().short_key);
367    path
368}
369
370/// Returns the length of the logical path of a branch stack entry.
371/// Equivalent to `logical_branch_path(arena, entry).len()` but avoids constructing the path.
372fn logical_branch_path_len(arena: &NodeArena, entry: &ArenaCursorStackEntry) -> usize {
373    entry.path.len() + arena[entry.index].branch_ref().short_key.len()
374}