Skip to main content

reth_trie_sparse/arena/
cursor.rs

1use super::{
2    branch_child_idx::{BranchChildIdx, BranchChildIter},
3    ArenaSparseNode, ArenaSparseNodeState, BranchChild, 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///
13/// The node's absolute path is not stored: it is the first `path_len` nibbles of the cursor's
14/// [`ArenaCursor::path`], which every entry on the stack is a prefix of.
15#[derive(Debug, Clone)]
16pub(super) struct ArenaCursorStackEntry {
17    /// The arena index of this node.
18    pub(super) index: Index,
19    /// The nibble length of the absolute path of this node (not including an extension key).
20    /// A [`Nibbles`] holds at most 64 nibbles, so this always fits in a `u8`.
21    path_len: u8,
22    /// The dense index at which to resume child iteration in [`ArenaCursor::next`].
23    /// Only meaningful when this entry's node is a branch. Ranges over `0..=16`, where 16 means
24    /// the last child of a full branch has been visited.
25    next_dense_idx: u8,
26}
27
28/// Result of [`ArenaCursor::seek`] describing the state at the deepest ancestor node.
29#[derive(Debug)]
30pub(super) enum SeekResult {
31    /// The stack head is an empty root node.
32    EmptyRoot,
33    /// The stack head is a leaf whose full path matches the target exactly.
34    RevealedLeaf,
35    /// The next child along the path is blinded (unrevealed).
36    Blinded,
37    /// The target path diverges from the stack head's extension or leaf key.
38    Diverged,
39    /// The target nibble has no child in the branch's `state_mask`.
40    NoChild { child_nibble: u8 },
41    /// The target nibble has a revealed subtrie child (now pushed onto the stack).
42    RevealedSubtrie,
43}
44
45/// Result of [`ArenaCursor::next`] describing what the cursor did.
46#[derive(Debug)]
47pub(super) enum NextResult {
48    /// The head is a non-branch node (subtrie, taken-subtrie, leaf, etc.).
49    /// The caller should process it; the next call to [`ArenaCursor::next`] will pop it.
50    NonBranch,
51    /// The head branch has no more qualifying children. It is still on the stack;
52    /// the caller should process it. The next call to [`ArenaCursor::next`] will pop it.
53    Branch,
54    /// The stack is empty — the traversal is complete.
55    Done,
56}
57
58/// Returns the branch node represented by an arena node.
59pub(super) fn branch_idx(arena: &NodeArena, idx: Index) -> Option<Index> {
60    match &arena[idx] {
61        ArenaSparseNode::Branch(_) => Some(idx),
62        ArenaSparseNode::Extension { child, .. } => Some(*child),
63        _ => None,
64    }
65}
66
67/// A cursor for depth-first traversal of an arena-based sparse trie.
68///
69/// Wraps a stack of [`ArenaCursorStackEntry`]s and provides methods for navigating
70/// the trie: pushing children, popping with dirty-state propagation, seeking
71/// to ancestors, and computing child paths.
72///
73/// The cursor borrows the arena on each method call rather than holding a
74/// reference, so the caller retains full ownership of the arena between calls.
75#[derive(Debug, Default, Clone)]
76pub(super) struct ArenaCursor {
77    stack: Vec<ArenaCursorStackEntry>,
78    /// A path that every stack entry's path is a prefix of: the entry with `path_len` nibbles
79    /// has the absolute path `path[..path_len]`.
80    ///
81    /// It holds the last seeked target, or the path of the deepest node a [`Self::next`] walk
82    /// descended to, and may therefore be longer than the head's own path. Popping never
83    /// shortens it, which is what keeps the ancestor test in [`Self::seek`] to a single
84    /// [`Nibbles::common_prefix_length`].
85    path: Nibbles,
86    /// Whether the head entry should be popped at the start of the next [`Self::next`] call.
87    /// Set when `next` returns [`NextResult::NonBranch`] or [`NextResult::Branch`].
88    needs_pop: bool,
89}
90
91impl ArenaCursor {
92    /// Returns the entry at the top of the stack, or `None` if empty.
93    pub(super) fn head(&self) -> Option<&ArenaCursorStackEntry> {
94        self.stack.last()
95    }
96
97    /// Returns the entry below the top of the stack (the parent of the head), or `None`.
98    pub(super) fn parent(&self) -> Option<&ArenaCursorStackEntry> {
99        let len = self.stack.len();
100        (len >= 2).then(|| &self.stack[len - 2])
101    }
102
103    /// Returns the depth of the head node (0 for the root).
104    ///
105    /// # Panics
106    ///
107    /// Panics if the stack is empty.
108    pub(super) const fn depth(&self) -> usize {
109        self.stack.len() - 1
110    }
111
112    /// Returns the absolute path of the node at the top of the stack.
113    pub(super) fn head_path(&self) -> Nibbles {
114        self.entry_path(self.head_entry())
115    }
116
117    /// Returns the nibble length of the absolute path of the node at the top of the stack.
118    /// Equivalent to `head_path().len()` but avoids constructing the path.
119    pub(super) fn head_path_len(&self) -> usize {
120        self.head_entry().path_len as usize
121    }
122
123    /// Returns the last nibble of the head node's absolute path, or `None` if that path is
124    /// empty.
125    pub(super) fn head_last_nibble(&self) -> Option<u8> {
126        self.head_path_len().checked_sub(1).map(|i| self.path.get_unchecked(i))
127    }
128
129    /// Replaces the root entry on the stack with a new one.
130    ///
131    /// The stack must contain exactly the root (depth 0) or be empty (freshly constructed).
132    #[instrument(level = "trace", target = TRACE_TARGET, skip(self, arena))]
133    pub(super) fn reset(&mut self, arena: &NodeArena, idx: Index, path: Nibbles) {
134        debug_assert!(
135            self.stack.len() <= 1 && !self.needs_pop,
136            "cursor must be drained before reset; stack has {} entries, needs_pop={}",
137            self.stack.len(),
138            self.needs_pop,
139        );
140        self.stack.clear();
141        self.needs_pop = false;
142        self.path = path;
143        self.push(arena, idx, path.len());
144    }
145
146    /// Pushes an entry onto the stack for the node at the given index, whose absolute path is
147    /// the first `path_len` nibbles of [`Self::path`].
148    fn push(&mut self, arena: &NodeArena, idx: Index, path_len: usize) {
149        debug_assert!(arena.contains_key(idx), "push called with invalid arena index");
150        debug_assert!(
151            path_len <= self.path.len(),
152            "pushed path length {path_len} exceeds cursor path {:?}",
153            self.path,
154        );
155        self.stack.push(ArenaCursorStackEntry {
156            index: idx,
157            path_len: path_len as u8,
158            next_dense_idx: 0,
159        });
160        trace!(
161            target: TRACE_TARGET,
162            ?idx,
163            path = ?self.path.slice_unchecked(0, path_len),
164            "Pushed stack entry",
165        );
166    }
167
168    /// Pops the top entry from the stack and propagates dirty state to the parent.
169    /// Returns the popped entry.
170    ///
171    /// The node must still be in the arena. Pop its entry before removing it, since a new
172    /// insertion can immediately reuse its index.
173    #[instrument(level = "trace", target = TRACE_TARGET, skip(self, arena))]
174    pub(super) fn pop(&mut self, arena: &mut NodeArena) -> ArenaCursorStackEntry {
175        let entry = self.stack.pop().expect("pop can't be called on empty stack");
176        self.needs_pop = false;
177        trace!(
178            target: TRACE_TARGET,
179            idx = ?entry.index,
180            path = ?self.entry_path(&entry),
181            "Popped stack entry",
182        );
183        let node = &arena[entry.index];
184
185        #[cfg(debug_assertions)]
186        if let ArenaSparseNode::Subtrie(s) = node {
187            let entry_path = self.path.slice_unchecked(0, entry.path_len as usize);
188            debug_assert_eq!(
189                s.path, entry_path,
190                "subtrie cached path {:?} does not match stack entry path {:?}",
191                s.path, entry_path,
192            );
193        }
194
195        if let Some(parent) = self.stack.last() {
196            let child_is_dirty = match node {
197                ArenaSparseNode::Branch(b) => matches!(b.state, ArenaSparseNodeState::Dirty),
198                ArenaSparseNode::Extension { child, .. } => {
199                    matches!(arena[*child].state_ref(), Some(ArenaSparseNodeState::Dirty))
200                }
201                ArenaSparseNode::Leaf { state, .. } => matches!(state, ArenaSparseNodeState::Dirty),
202                ArenaSparseNode::Subtrie(_) => {
203                    matches!(node.state_ref(), Some(ArenaSparseNodeState::Dirty))
204                }
205                _ => false,
206            };
207            if child_is_dirty {
208                let parent_branch_idx =
209                    branch_idx(arena, parent.index).expect("parent must be a branch or extension");
210                *arena[parent_branch_idx].state_mut() = ArenaSparseNodeState::Dirty;
211            }
212        }
213
214        entry
215    }
216
217    /// Drains the stack down to the root, propagating dirty state from each popped entry
218    /// to its parent. The root entry remains on the stack (there is no parent to propagate to).
219    #[instrument(level = "trace", target = TRACE_TARGET, skip_all)]
220    pub(super) fn drain(&mut self, arena: &mut NodeArena) {
221        trace!(target: TRACE_TARGET, "Draining stack");
222        self.needs_pop = false;
223        while self.stack.len() > 1 {
224            self.pop(arena);
225        }
226    }
227
228    /// Returns the logical path of the branch at the top of the stack.
229    /// The logical path is `head_path() + extension.key`, when the head is an extension.
230    pub(super) fn head_logical_branch_path(&self, arena: &NodeArena) -> Nibbles {
231        self.logical_branch_path(arena, self.head_entry())
232    }
233
234    /// Returns the length of the logical path of the branch at the top of the stack.
235    /// Equivalent to `head_logical_branch_path(arena).len()` but avoids constructing the path.
236    pub(super) fn head_logical_branch_path_len(&self, arena: &NodeArena) -> usize {
237        let head = self.head_entry();
238        head.path_len as usize + arena[head.index].extension_key().map_or(0, Nibbles::len)
239    }
240
241    /// Returns the logical path of the parent branch entry (second from top of the stack).
242    /// Panics if the stack has fewer than 2 entries.
243    pub(super) fn parent_logical_branch_path(&self, arena: &NodeArena) -> Nibbles {
244        self.logical_branch_path(arena, self.parent().expect("cursor must have a parent"))
245    }
246
247    /// Replaces the arena index stored in the head entry with `new_idx`, and updates the
248    /// parent branch's children array to point to the new index. If the head is the root
249    /// (stack has one entry), `root` is updated instead.
250    pub(super) fn replace_head_index(
251        &mut self,
252        arena: &mut NodeArena,
253        root: &mut Index,
254        new_idx: Index,
255    ) {
256        let child_nibble = self.head_last_nibble();
257        let head = self.stack.last_mut().expect("cursor must have head");
258        let old_idx = head.index;
259        head.index = new_idx;
260
261        let Some(parent) = self.parent() else {
262            *root = new_idx;
263            return;
264        };
265
266        let child_nibble =
267            child_nibble.expect("if cursor has a parent then the head path can't be empty");
268
269        let parent_branch_idx =
270            branch_idx(arena, parent.index).expect("parent must be a branch or extension");
271        let parent_branch = arena[parent_branch_idx].branch_mut();
272        let child_idx = BranchChildIdx::new(parent_branch.state_mask, child_nibble)
273            .expect("child nibble not found in parent state_mask");
274
275        debug_assert_eq!(
276            parent_branch.children[child_idx].revealed_index(),
277            Some(old_idx),
278            "parent child at nibble {child_nibble} does not match old_idx",
279        );
280
281        parent_branch.children[child_idx] = BranchChild::revealed(new_idx);
282    }
283
284    /// Advances the DFS traversal to the next actionable node.
285    ///
286    /// If a previous call returned [`NextResult::NonBranch`] or [`NextResult::Branch`],
287    /// the head entry is automatically popped (with dirty-state propagation) before
288    /// descending further, unless the caller already popped it. Callers removing the head
289    /// node must call [`Self::pop`] before the removal.
290    ///
291    /// Returns [`NextResult::NonBranch`] when the head is a non-branch node the caller
292    /// should process, or [`NextResult::Branch`] when a branch has exhausted its
293    /// qualifying children. In both cases the node is still on the stack so the caller
294    /// can read it via [`Self::head`].
295    ///
296    /// Returns [`NextResult::Done`] when the stack is empty (traversal complete).
297    #[instrument(level = "trace", target = TRACE_TARGET, skip_all, ret)]
298    pub(super) fn next(
299        &mut self,
300        arena: &mut NodeArena,
301        should_descend: impl Fn(usize, &ArenaSparseNode) -> bool,
302    ) -> NextResult {
303        if self.needs_pop {
304            self.pop(arena);
305        }
306
307        loop {
308            let Some(head) = self.stack.last_mut() else {
309                return NextResult::Done;
310            };
311            let head_idx = head.index;
312            let head_path_len = head.path_len as usize;
313
314            let Some(branch_idx) = branch_idx(arena, head_idx) else {
315                self.needs_pop = true;
316                return NextResult::NonBranch;
317            };
318            let branch = arena[branch_idx].branch_ref();
319
320            let state_mask = branch.state_mask;
321            let short_key = arena[head_idx].extension_key().copied().unwrap_or_default();
322            let start = head.next_dense_idx as usize;
323            let child_depth = self.stack.len();
324
325            let mut descended = false;
326            for (branch_child_idx, nibble) in BranchChildIter::new(state_mask) {
327                if branch_child_idx.get() < start {
328                    continue;
329                }
330
331                let Some(child_idx) =
332                    arena[branch_idx].branch_ref().children[branch_child_idx].revealed_index()
333                else {
334                    continue;
335                };
336
337                if should_descend(child_depth, &arena[child_idx]) {
338                    // Record where to resume iteration when we return to this entry.
339                    self.stack.last_mut().expect("head exists").next_dense_idx =
340                        branch_child_idx.get() as u8 + 1;
341                    self.path.truncate(head_path_len);
342                    self.path.extend(&short_key);
343                    self.path.push_unchecked(nibble);
344                    self.push(arena, child_idx, self.path.len());
345                    descended = true;
346                    break;
347                }
348            }
349
350            if !descended {
351                self.needs_pop = true;
352                return NextResult::Branch;
353            }
354        }
355    }
356
357    /// Pops the stack until the head is an ancestor of `full_path`, then descends from that head
358    /// toward `full_path`, pushing revealed branch (and leaf) children onto the stack until the
359    /// deepest ancestor is reached.
360    ///
361    /// Returns a [`SeekResult`] describing the state at the stack head.
362    #[instrument(level = "trace", target = TRACE_TARGET, skip(self, arena), ret)]
363    pub(super) fn seek(&mut self, arena: &mut NodeArena, full_path: &Nibbles) -> SeekResult {
364        // Every entry's path is a prefix of `self.path`, so an entry is an ancestor of
365        // `full_path` exactly when its length fits within the common prefix of the previous
366        // target and this one.
367        let common = self.path.common_prefix_length(full_path);
368        while self.stack.len() > 1 && self.head_entry().path_len as usize > common {
369            self.pop(arena);
370        }
371
372        if self.head_entry().path_len as usize > common {
373            // The target is not below the walk's root. Callers only seek within the root's
374            // prefix, so this cannot happen for a revealed root; leaving `self.path` alone
375            // keeps the entry paths derivable.
376            debug_assert!(
377                false,
378                "seek target {full_path:?} is not below the walk's root {:?}",
379                self.head_path(),
380            );
381            return SeekResult::Diverged;
382        }
383        self.path = *full_path;
384
385        loop {
386            let head = self.head_entry();
387            let head_idx = head.index;
388            let head_path_len = head.path_len as usize;
389
390            let head_branch = match &arena[head_idx] {
391                ArenaSparseNode::EmptyRoot { .. } => {
392                    return SeekResult::EmptyRoot;
393                }
394                ArenaSparseNode::Leaf { key, .. } => {
395                    return if full_path.slice_unchecked(head_path_len, full_path.len()) == *key {
396                        SeekResult::RevealedLeaf
397                    } else {
398                        SeekResult::Diverged
399                    };
400                }
401                ArenaSparseNode::Branch(b) => b,
402                ArenaSparseNode::Extension { child, .. } => arena[*child].branch_ref(),
403                ArenaSparseNode::Subtrie(_) => {
404                    return SeekResult::RevealedSubtrie;
405                }
406                _ => unreachable!("unexpected node type on stack: {:?}", arena[head_idx]),
407            };
408
409            let short_key = arena[head_idx].extension_key().copied().unwrap_or_default();
410            let logical_len = head_path_len + short_key.len();
411
412            // If full_path doesn't extend past the branch's logical path, the target is at or
413            // within the extension's key — treat as diverged.
414            if full_path.len() <= logical_len ||
415                (!short_key.is_empty() &&
416                    full_path.slice_unchecked(head_path_len, logical_len) != short_key)
417            {
418                return SeekResult::Diverged;
419            }
420
421            let child_nibble = full_path.get_unchecked(logical_len);
422            let Some(branch_child_idx) = BranchChildIdx::new(head_branch.state_mask, child_nibble)
423            else {
424                return SeekResult::NoChild { child_nibble };
425            };
426
427            let Some(child_idx) = head_branch.children[branch_child_idx].revealed_index() else {
428                return SeekResult::Blinded;
429            };
430            self.push(arena, child_idx, logical_len + 1);
431        }
432    }
433
434    /// Returns the entry at the top of the stack, which must be non-empty.
435    fn head_entry(&self) -> &ArenaCursorStackEntry {
436        self.stack.last().expect("cursor is non-empty")
437    }
438
439    /// Returns the absolute path of a stack entry.
440    fn entry_path(&self, entry: &ArenaCursorStackEntry) -> Nibbles {
441        self.path.slice_unchecked(0, entry.path_len as usize)
442    }
443
444    /// Returns the logical path of a branch stack entry: `entry path + extension.key`.
445    fn logical_branch_path(&self, arena: &NodeArena, entry: &ArenaCursorStackEntry) -> Nibbles {
446        let mut path = self.entry_path(entry);
447        if let Some(key) = arena[entry.index].extension_key() {
448            path.extend(key);
449        }
450        path
451    }
452}