1mod branch_child_idx;
2mod cursor;
3mod node_arena;
4mod nodes;
5
6use branch_child_idx::{BranchChildIdx, BranchChildIter};
7use cursor::{ArenaCursor, NextResult, SeekResult};
8use node_arena::{BranchChild, Index, NodeArena};
9use nodes::{ArenaSparseNode, ArenaSparseNodeBranch, ArenaSparseNodeState};
10
11use crate::{
12 LeafLookup, LeafLookupError, LeafUpdate, SparseTrie, SparseTrieUpdates, TrieNodeEpoch,
13};
14use alloc::{borrow::Cow, boxed::Box, vec::Vec};
15use alloy_primitives::{keccak256, map::B256Map, B256};
16use alloy_trie::TrieMask;
17use core::{cmp::Reverse, mem};
18use reth_execution_errors::{SparseTrieErrorKind, SparseTrieResult};
19use reth_trie_common::{
20 BranchNodeMasks, BranchNodeRef, ExtensionNodeRef, LeafNodeRef, Nibbles, ProofTrieNodeV2,
21 ProofV2TargetParent, RlpNode, TrieNodeV2, EMPTY_ROOT_HASH,
22};
23use smallvec::SmallVec;
24use tracing::{instrument, trace};
25
26const TRACE_TARGET: &str = "trie::arena";
27
28const UPPER_TRIE_MAX_DEPTH: usize = 2;
31
32fn compact_arena(arena: &mut NodeArena, root: &mut Index) {
36 let mut new_arena = NodeArena::with_capacity(arena.len());
37 let mut marks = new_arena.adopt_blinded(arena);
38 let root_node = arena.drain_node(*root);
39 let new_root = new_arena.insert(root_node);
40
41 let mut next = 0;
44 while next < new_arena.len() {
45 let new_idx = Index::new(next);
46 next += 1;
47 let old_children: SmallVec<[(usize, Index); 16]> = match &new_arena[new_idx] {
48 ArenaSparseNode::Branch(b) => b
49 .children
50 .iter()
51 .enumerate()
52 .filter_map(|(pos, child)| match child.revealed_index() {
53 Some(old_child_idx) => Some((pos, old_child_idx)),
54 None => {
56 marks.mark(*child);
57 None
58 }
59 })
60 .collect(),
61 _ => continue,
62 };
63
64 for (child_pos, old_child_idx) in old_children {
65 let child_node = arena.drain_node(old_child_idx);
66 let new_child_idx = new_arena.insert(child_node);
67 debug_assert_eq!(
68 new_child_idx.get() + 1,
69 new_arena.len(),
70 "compaction must append, the destination doubles as the BFS queue",
71 );
72 let ArenaSparseNode::Branch(b) = &mut new_arena[new_idx] else { unreachable!() };
73 b.children[child_pos] = BranchChild::revealed(new_child_idx);
74 }
75 }
76
77 debug_assert!(
78 arena.iter().next().is_none(),
79 "compact_arena: {} orphaned nodes remaining after BFS drain",
80 arena.iter().count(),
81 );
82
83 new_arena.sweep_blinded(marks);
84 *arena = new_arena;
85 *root = new_root;
86}
87
88#[derive(Debug, Default, Clone)]
91struct ArenaTrieBuffers {
92 cursor: ArenaCursor,
94 updates: Option<SparseTrieUpdates>,
97 rlp_buf: Vec<u8>,
99 rlp_node_buf: Vec<RlpNode>,
101}
102
103impl ArenaTrieBuffers {
104 fn clear(&mut self) {
105 if let Some(updates) = self.updates.as_mut() {
106 updates.clear();
107 }
108 self.rlp_buf.clear();
109 self.rlp_node_buf.clear();
110 }
111}
112
113#[derive(Debug, Clone)]
117struct ArenaSparseSubtrie {
118 arena: NodeArena,
120 root: Index,
122 path: Nibbles,
124 buffers: ArenaTrieBuffers,
126 required_proofs: Vec<(usize, ArenaRequiredProof)>,
130 num_leaves: u64,
132 num_dirty_leaves: u64,
134}
135
136impl ArenaSparseSubtrie {
137 fn new(record_updates: bool) -> Box<Self> {
141 let mut arena = NodeArena::new();
142 let root =
143 arena.insert(ArenaSparseNode::EmptyRoot { state: ArenaSparseNodeState::Revealed });
144 let buffers = ArenaTrieBuffers {
145 updates: record_updates.then(SparseTrieUpdates::default),
146 ..Default::default()
147 };
148 Box::new(Self {
149 arena,
150 root,
151 path: Nibbles::default(),
152 buffers,
153 required_proofs: Vec::new(),
154 num_leaves: 0,
155 num_dirty_leaves: 0,
156 })
157 }
158
159 #[cfg(all(debug_assertions, not(feature = "dst")))]
161 fn debug_assert_counters(&self) {
162 let (actual_leaves, actual_dirty) =
163 ArenaParallelSparseTrie::count_leaves_and_dirty(&self.arena, self.root);
164 debug_assert_eq!(
165 self.num_leaves, actual_leaves,
166 "subtrie {:?} num_leaves mismatch: stored {} vs actual {}",
167 self.path, self.num_leaves, actual_leaves,
168 );
169 debug_assert_eq!(
170 self.num_dirty_leaves, actual_dirty,
171 "subtrie {:?} num_dirty_leaves mismatch: stored {} vs actual {}",
172 self.path, self.num_dirty_leaves, actual_dirty,
173 );
174 }
175
176 fn prune(&mut self, prune_before: TrieNodeEpoch) -> usize {
181 if !matches!(&self.arena[self.root], ArenaSparseNode::Branch(_)) {
183 return 0;
184 }
185
186 debug_assert_eq!(self.num_dirty_leaves, 0, "prune must run after hashing");
187
188 if prune_before == TrieNodeEpoch::UNMODIFIED {
189 return 0;
190 }
191
192 let old_count = self.arena.len();
193 let mut new_arena = NodeArena::with_capacity(old_count);
196 let mut marks = new_arena.adopt_blinded(&mut self.arena);
197 let mut new_num_leaves = 0u64;
198
199 let root_node = self.arena.drain_node(self.root);
201 let new_root = new_arena.insert(root_node);
202 let mut stack = Vec::new();
203 if let Some(frame) =
204 prepare_retained_node(&new_arena, new_root, self.path, &mut new_num_leaves)
205 {
206 stack.push(frame);
207 }
208
209 while let Some(frame) = stack.last_mut() {
210 let Some((child_pos, nibble, child)) = frame.next_child(&new_arena) else {
211 stack.pop();
212 continue;
213 };
214
215 let Some(old_child_idx) = child.revealed_index() else {
216 marks.mark(child);
218 continue;
219 };
220
221 let parent_new_idx = frame.new_idx;
222 let mut child_path = frame.branch_logical_path;
223 child_path.push(nibble);
224
225 let child_epoch = self.arena[old_child_idx]
226 .state_ref()
227 .and_then(ArenaSparseNodeState::cached_epoch)
228 .expect("prune must run after hashing");
229
230 if child_epoch.should_prune(prune_before) {
231 let node = &self.arena[old_child_idx];
232 let rlp_node = node
233 .state_ref()
234 .and_then(ArenaSparseNodeState::cached_rlp_node)
235 .cloned()
236 .expect("prune must run after hashing");
237 trace!(
238 target: TRACE_TARGET,
239 path = ?child_path,
240 variant = %AsRef::<str>::as_ref(node),
241 cached_rlp_node = ?rlp_node,
242 "pruning node",
243 );
244 let new_child = new_arena.insert_blinded(rlp_node);
245 marks.mark(new_child);
246 let ArenaSparseNode::Branch(b) = &mut new_arena[parent_new_idx] else {
247 unreachable!()
248 };
249 b.children[child_pos] = new_child;
250 } else {
251 let child_node = self.arena.drain_node(old_child_idx);
252 let new_child_idx = new_arena.insert(child_node);
253 if let Some(frame) = prepare_retained_node(
254 &new_arena,
255 new_child_idx,
256 child_path,
257 &mut new_num_leaves,
258 ) {
259 stack.push(frame);
260 }
261 let ArenaSparseNode::Branch(b) = &mut new_arena[parent_new_idx] else {
262 unreachable!()
263 };
264 b.children[child_pos] = BranchChild::revealed(new_child_idx);
265 }
266 }
267
268 new_arena.sweep_blinded(marks);
269 new_arena.shrink_nodes_to_fit();
270 let pruned = old_count - new_arena.len();
271 self.num_leaves = new_num_leaves;
272 self.num_dirty_leaves = 0;
273 self.arena = new_arena;
274 self.root = new_root;
275
276 #[cfg(all(debug_assertions, not(feature = "dst")))]
277 self.debug_assert_counters();
278 return pruned;
279
280 struct CopyFrame {
281 new_idx: Index,
282 branch_logical_path: Nibbles,
283 state_mask: TrieMask,
284 remaining_child_mask: TrieMask,
285 }
286
287 impl CopyFrame {
288 fn next_child(&mut self, new_arena: &NodeArena) -> Option<(usize, u8, BranchChild)> {
289 let ArenaSparseNode::Branch(b) = &new_arena[self.new_idx] else { unreachable!() };
290
291 let nibble = self.remaining_child_mask.first_set_bit_index()?;
292 self.remaining_child_mask.unset_bit(nibble);
293 let child_idx = BranchChildIdx::new(self.state_mask, nibble)
294 .expect("remaining_child_mask must be a subset of state_mask");
295
296 Some((child_idx.get(), nibble, b.children[child_idx]))
297 }
298 }
299
300 fn prepare_retained_node(
303 new_arena: &NodeArena,
304 new_idx: Index,
305 node_path: Nibbles,
306 new_num_leaves: &mut u64,
307 ) -> Option<CopyFrame> {
308 let ArenaSparseNode::Branch(b) = &new_arena[new_idx] else {
309 if matches!(&new_arena[new_idx], ArenaSparseNode::Leaf { .. }) {
310 *new_num_leaves += 1;
311 }
312 return None;
313 };
314
315 let mut branch_logical_path = node_path;
316 branch_logical_path.extend(&b.short_key);
317
318 Some(CopyFrame {
319 new_idx,
320 branch_logical_path,
321 state_mask: b.state_mask,
322 remaining_child_mask: b.state_mask,
323 })
324 }
325 }
326
327 #[instrument(
335 level = "trace",
336 target = TRACE_TARGET,
337 skip_all,
338 fields(
339 subtrie = ?self.path,
340 num_updates = sorted_updates.len(),
341 ),
342 )]
343 fn update_leaves(&mut self, sorted_updates: &[(B256, Nibbles, LeafUpdate)]) {
344 if sorted_updates.is_empty() {
345 return;
346 }
347 trace!(target: TRACE_TARGET, "Subtrie update_leaves");
348
349 debug_assert!(
350 !matches!(self.arena[self.root], ArenaSparseNode::EmptyRoot { .. }),
351 "subtrie root must not be EmptyRoot at start of update_leaves"
352 );
353
354 self.buffers.cursor.reset(&self.arena, self.root, self.path);
355
356 for (idx, &(key, ref full_path, ref update)) in sorted_updates.iter().enumerate() {
357 let find_result = self.buffers.cursor.seek(&mut self.arena, full_path);
358
359 if matches!(find_result, SeekResult::Blinded) {
361 let logical_len = self.buffers.cursor.head_logical_branch_path_len(&self.arena);
362 self.required_proofs.push((
363 idx,
364 ArenaRequiredProof { key, parent: ProofV2TargetParent::new(logical_len) },
365 ));
366 continue;
367 }
368
369 match update {
370 LeafUpdate::Changed(value) if !value.is_empty() => {
371 let (_result, deltas) = ArenaParallelSparseTrie::upsert_leaf(
373 &mut self.arena,
374 &mut self.buffers.cursor,
375 &mut self.root,
376 full_path,
377 value,
378 find_result,
379 );
380 self.num_leaves = (self.num_leaves as i64 + deltas.num_leaves_delta) as u64;
381 self.num_dirty_leaves =
382 (self.num_dirty_leaves as i64 + deltas.num_dirty_leaves_delta) as u64;
383 }
384 LeafUpdate::Changed(_) => {
385 let (result, deltas) = ArenaParallelSparseTrie::remove_leaf(
386 &mut self.arena,
387 &mut self.buffers.cursor,
388 &mut self.root,
389 key,
390 full_path,
391 find_result,
392 &mut self.buffers.updates,
393 );
394 self.num_leaves = (self.num_leaves as i64 + deltas.num_leaves_delta) as u64;
395 self.num_dirty_leaves =
396 (self.num_dirty_leaves as i64 + deltas.num_dirty_leaves_delta) as u64;
397
398 if let RemoveLeafResult::NeedsProof { key, proof_key, parent } = result {
399 self.required_proofs
400 .push((idx, ArenaRequiredProof { key: proof_key, parent }));
401 self.required_proofs.push((idx, ArenaRequiredProof { key, parent }));
402 }
403 }
404 LeafUpdate::Touched => {}
405 }
406 }
407
408 self.buffers.cursor.drain(&mut self.arena);
410
411 #[cfg(all(debug_assertions, not(feature = "dst")))]
412 self.debug_assert_counters();
413 }
414
415 fn reveal_nodes(&mut self, nodes: &mut [ProofTrieNodeV2]) -> SparseTrieResult<()> {
418 if nodes.is_empty() {
419 return Ok(());
420 }
421 trace!(target: TRACE_TARGET, path = ?self.path, num_nodes = nodes.len(), "Subtrie reveal_nodes");
422
423 debug_assert!(
424 !matches!(self.arena[self.root], ArenaSparseNode::EmptyRoot { .. }),
425 "subtrie root must not be EmptyRoot in reveal_nodes"
426 );
427
428 self.buffers.cursor.reset(&self.arena, self.root, self.path);
429
430 for node in nodes.iter_mut() {
431 let find_result = self.buffers.cursor.seek(&mut self.arena, &node.path);
432 if ArenaParallelSparseTrie::reveal_node(
433 &mut self.arena,
434 &self.buffers.cursor,
435 node,
436 find_result,
437 )
438 .is_some_and(|child_idx| matches!(self.arena[child_idx], ArenaSparseNode::Leaf { .. }))
439 {
440 self.num_leaves += 1;
441 }
442 }
443
444 self.buffers.cursor.drain(&mut self.arena);
446
447 #[cfg(all(debug_assertions, not(feature = "dst")))]
448 self.debug_assert_counters();
449
450 Ok(())
451 }
452
453 fn update_cached_rlp(&mut self, new_epoch: TrieNodeEpoch) {
458 ArenaParallelSparseTrie::update_cached_rlp(
459 &mut self.arena,
460 self.root,
461 self.path,
462 &mut self.buffers,
463 new_epoch,
464 );
465 self.num_dirty_leaves = 0;
466 #[cfg(all(debug_assertions, not(feature = "dst")))]
467 self.debug_assert_counters();
468 }
469}
470
471#[derive(Debug, Default)]
475struct SubtrieCounterDeltas {
476 num_leaves_delta: i64,
477 num_dirty_leaves_delta: i64,
478}
479
480#[derive(Debug)]
483enum UpsertLeafResult {
484 Updated,
486 NewLeaf,
488 NewChild,
491}
492
493#[derive(Debug)]
496enum RemoveLeafResult {
497 Removed,
499 NotFound,
501 NeedsProof { key: B256, proof_key: B256, parent: ProofV2TargetParent },
504}
505
506#[derive(Debug, Clone)]
508struct ArenaRequiredProof {
509 key: B256,
511 parent: ProofV2TargetParent,
513}
514
515#[derive(Debug, Clone, Copy, PartialEq, Eq)]
520pub struct ArenaParallelismThresholds {
521 pub min_dirty_leaves: u64,
525 pub min_revealed_nodes: usize,
529 pub min_updates: usize,
533 pub min_leaves_for_prune: u64,
537}
538
539impl Default for ArenaParallelismThresholds {
540 fn default() -> Self {
541 Self {
542 min_dirty_leaves: 64,
543 min_revealed_nodes: 16,
544 min_updates: 128,
545 min_leaves_for_prune: 128,
546 }
547 }
548}
549
550#[derive(Debug, Clone)]
607pub struct ArenaParallelSparseTrie {
608 upper_arena: NodeArena,
610 root: Index,
612 buffers: ArenaTrieBuffers,
614 parallelism_thresholds: ArenaParallelismThresholds,
616}
617
618impl ArenaParallelSparseTrie {
619 pub const fn with_parallelism_thresholds(
621 mut self,
622 thresholds: ArenaParallelismThresholds,
623 ) -> Self {
624 self.parallelism_thresholds = thresholds;
625 self
626 }
627
628 const fn should_be_subtrie(path_len: usize) -> bool {
631 path_len == UPPER_TRIE_MAX_DEPTH
632 }
633
634 fn maybe_wrap_in_subtrie(&mut self, child_idx: Index, child_path: &Nibbles) {
639 if !Self::should_be_subtrie(child_path.len()) {
640 return;
641 }
642
643 if !matches!(
645 self.upper_arena[child_idx],
646 ArenaSparseNode::Branch(_) | ArenaSparseNode::Leaf { .. }
647 ) {
648 return;
649 }
650
651 trace!(target: TRACE_TARGET, ?child_path, "Wrapping child into subtrie");
652 let mut subtrie = ArenaSparseSubtrie::new(self.buffers.updates.is_some());
653 subtrie.path = *child_path;
654 let mut root_node =
655 mem::replace(&mut self.upper_arena[child_idx], ArenaSparseNode::TakenSubtrie);
656
657 Self::migrate_children(&mut subtrie.arena, &mut self.upper_arena, &mut root_node);
659
660 subtrie.arena[subtrie.root] = root_node;
661 let (leaves, dirty) = Self::count_leaves_and_dirty(&subtrie.arena, subtrie.root);
662 subtrie.num_leaves = leaves;
663 subtrie.num_dirty_leaves = dirty;
664 #[cfg(all(debug_assertions, not(feature = "dst")))]
665 subtrie.debug_assert_counters();
666 self.upper_arena[child_idx] = ArenaSparseNode::Subtrie(subtrie);
667 }
668
669 fn maybe_wrap_branch_children(&mut self, cursor: &ArenaCursor) {
674 let head_idx = cursor.head().expect("cursor is non-empty").index;
675 let head_path = cursor.head_path();
676
677 let ArenaSparseNode::Branch(b) = &self.upper_arena[head_idx] else { return };
678 let short_key = b.short_key;
679 let children: SmallVec<[_; 4]> = b
680 .child_iter()
681 .filter_map(|(nibble, child)| Some((nibble, child.revealed_index()?)))
682 .collect();
683
684 for (nibble, child_idx) in children {
685 let mut child_path = head_path;
686 child_path.extend(&short_key);
687 child_path.push_unchecked(nibble);
688 self.maybe_wrap_in_subtrie(child_idx, &child_path);
689 }
690 }
691
692 #[instrument(
701 level = "trace",
702 target = TRACE_TARGET,
703 skip_all,
704 fields(subtrie_path = ?cursor.head_path()),
705 )]
706 fn maybe_unwrap_subtrie(&mut self, cursor: &mut ArenaCursor) {
707 let subtrie_idx = cursor.head().expect("cursor is non-empty").index;
708
709 let ArenaSparseNode::Subtrie(subtrie) = &self.upper_arena[subtrie_idx] else {
710 return;
711 };
712
713 if !matches!(subtrie.arena[subtrie.root], ArenaSparseNode::EmptyRoot { .. }) {
714 return;
715 }
716
717 let child_nibble =
718 cursor.head_last_nibble().expect("subtrie path must have at least one nibble");
719 let parent_idx = cursor.parent().expect("cursor has parent").index;
720
721 cursor.pop(&mut self.upper_arena);
724
725 self.recycle_subtrie_from_idx(subtrie_idx);
726
727 trace!(target: TRACE_TARGET, "Unwrapping empty subtrie, removing child slot");
728 let parent_branch = self.upper_arena[parent_idx].branch_mut();
729 let child_idx = BranchChildIdx::new(parent_branch.state_mask, child_nibble)
730 .expect("child nibble not found in parent state_mask");
731
732 parent_branch.children.remove(child_idx.get());
733 parent_branch.unset_child_bit(child_nibble);
734 parent_branch.state = parent_branch.state.to_dirty();
736
737 self.maybe_collapse_or_remove_branch(cursor);
738 }
739
740 fn recycle_subtrie(&mut self, node: ArenaSparseNode) {
746 let ArenaSparseNode::Subtrie(mut subtrie) = node else {
747 unreachable!("recycle_subtrie called on non-Subtrie node")
748 };
749 Self::merge_subtrie_updates(&mut self.buffers.updates, &mut subtrie.buffers.updates);
750 }
751
752 fn recycle_subtrie_from_idx(&mut self, idx: Index) {
754 let node = self.upper_arena.remove(idx).expect("subtrie exists in arena");
755 self.recycle_subtrie(node);
756 }
757
758 fn restore_taken_subtrie(&mut self, idx: Index, subtrie: Box<ArenaSparseSubtrie>) {
764 debug_assert!(
765 matches!(self.upper_arena[idx], ArenaSparseNode::TakenSubtrie),
766 "taken subtrie slot {idx:?} was removed or reused before restoration",
767 );
768 self.upper_arena[idx] = ArenaSparseNode::Subtrie(subtrie);
769 }
770
771 fn maybe_collapse_or_remove_branch(&mut self, cursor: &mut ArenaCursor) {
782 loop {
783 let branch_idx = cursor.head().expect("cursor is non-empty").index;
784
785 let count = {
788 let ArenaSparseNode::Branch(b) = &self.upper_arena[branch_idx] else {
789 return;
790 };
791 b.state_mask.count_bits()
792 };
793
794 if count >= 2 {
795 return;
796 }
797
798 if count == 0 {
799 if branch_idx == self.root {
800 self.upper_arena[branch_idx] =
801 ArenaSparseNode::EmptyRoot { state: ArenaSparseNodeState::Dirty };
802 return;
803 }
804 let branch_nibble = cursor.head_last_nibble().expect("non-root branch");
806 cursor.pop(&mut self.upper_arena);
807 self.upper_arena.remove(branch_idx);
808 let parent_idx = cursor.head().expect("cursor is non-empty").index;
809 let parent_branch = self.upper_arena[parent_idx].branch_mut();
810 let child_idx = BranchChildIdx::new(parent_branch.state_mask, branch_nibble)
811 .expect("child nibble not found in parent state_mask");
812 parent_branch.children.remove(child_idx.get());
813 parent_branch.unset_child_bit(branch_nibble);
814 parent_branch.state = parent_branch.state.to_dirty();
815 continue; }
817
818 let (remaining_nibble, remaining_child_idx) = {
820 let b = self.upper_arena[branch_idx].branch_ref();
821 let nibble = b.state_mask.iter().next().expect("branch has at least one child");
822 (nibble, b.children[0].revealed_index())
823 };
824
825 let Some(child_idx) = remaining_child_idx else {
826 debug_assert!(false, "single remaining child is blinded — should have been caught by check_subtrie_collapse_needs_proof");
827 return;
828 };
829
830 if matches!(self.upper_arena[child_idx], ArenaSparseNode::TakenSubtrie) {
831 return;
834 }
835
836 let is_empty_subtrie = matches!(
838 &self.upper_arena[child_idx],
839 ArenaSparseNode::Subtrie(s) if matches!(s.arena[s.root], ArenaSparseNode::EmptyRoot { .. })
840 );
841
842 if is_empty_subtrie {
843 self.recycle_subtrie_from_idx(child_idx);
844 let branch = self.upper_arena[branch_idx].branch_mut();
845 branch.children.remove(0);
846 branch.unset_child_bit(remaining_nibble);
847 branch.state = branch.state.to_dirty();
848 continue; }
850
851 Self::collapse_branch(
853 &mut self.upper_arena,
854 cursor,
855 &mut self.root,
856 &mut self.buffers.updates,
857 );
858
859 let child_idx = cursor.head().expect("cursor is non-empty").index;
864 if let ArenaSparseNode::Subtrie(_) = &self.upper_arena[child_idx] {
865 let ArenaSparseNode::Subtrie(mut subtrie) =
866 mem::replace(&mut self.upper_arena[child_idx], ArenaSparseNode::TakenSubtrie)
867 else {
868 unreachable!()
869 };
870 Self::migrate_nodes(
871 &mut self.upper_arena,
872 &mut subtrie.arena,
873 subtrie.root,
874 Some(child_idx),
875 );
876 Self::merge_subtrie_updates(
877 &mut self.buffers.updates,
878 &mut subtrie.buffers.updates,
879 );
880
881 self.maybe_wrap_branch_children(cursor);
885 }
886 return;
887 }
888 }
889
890 fn merge_subtrie_updates(
897 dst: &mut Option<SparseTrieUpdates>,
898 src: &mut Option<SparseTrieUpdates>,
899 ) {
900 if let Some(dst_updates) = dst.as_mut() {
901 let src_updates = src.as_mut().expect("updates are enabled");
902
903 for path in src_updates.updated_nodes.keys() {
905 dst_updates.removed_nodes.remove(path);
906 }
907 dst_updates.updated_nodes.extend(src_updates.updated_nodes.drain());
908
909 for path in &src_updates.removed_nodes {
911 dst_updates.updated_nodes.remove(path);
912 }
913 dst_updates.removed_nodes.extend(src_updates.removed_nodes.drain());
914 }
915 }
916
917 fn nibbles_to_padded_b256(path: &Nibbles) -> B256 {
919 let mut bytes = [0u8; 32];
920 path.pack_to(&mut bytes);
921 B256::from(bytes)
922 }
923
924 fn get_branch_masks(arena: &NodeArena, branch: &ArenaSparseNodeBranch) -> BranchNodeMasks {
926 let mut masks = BranchNodeMasks::default();
927
928 for (nibble, child) in branch.child_iter() {
929 let (hash_bit, tree_bit) = match child.revealed_index() {
930 Some(child_idx) => {
931 let child = &arena[child_idx];
932 (child.hash_mask_bit(), child.tree_mask_bit())
933 }
934 None => (
935 branch.branch_masks.hash_mask.is_bit_set(nibble),
936 branch.branch_masks.tree_mask.is_bit_set(nibble),
937 ),
938 };
939
940 masks.set_child_bits(nibble, hash_bit, tree_bit);
941 }
942
943 masks
944 }
945
946 #[instrument(level = "trace", target = TRACE_TARGET, skip_all, fields(base_path = ?base_path), ret)]
959 fn update_cached_rlp(
960 arena: &mut NodeArena,
961 root: Index,
962 base_path: Nibbles,
963 buffers: &mut ArenaTrieBuffers,
964 new_epoch: TrieNodeEpoch,
965 ) -> RlpNode {
966 let cursor = &mut buffers.cursor;
967 let rlp_buf = &mut buffers.rlp_buf;
968 let rlp_node_buf = &mut buffers.rlp_node_buf;
969 let updates = &mut buffers.updates;
970
971 rlp_node_buf.clear();
972
973 match &arena[root] {
977 ArenaSparseNode::EmptyRoot { state } => {
978 let node_epoch = match state {
979 ArenaSparseNodeState::Cached { epoch, .. } => *epoch,
980 ArenaSparseNodeState::Revealed => TrieNodeEpoch::UNMODIFIED,
981 ArenaSparseNodeState::Dirty => new_epoch,
982 };
983 let rlp_node = RlpNode::word_rlp(&EMPTY_ROOT_HASH);
984 *arena[root].state_mut() =
985 ArenaSparseNodeState::Cached { rlp_node: rlp_node.clone(), epoch: node_epoch };
986 return rlp_node
987 }
988 ArenaSparseNode::Leaf { .. } => {
989 Self::encode_leaf(arena, root, rlp_buf, rlp_node_buf, new_epoch);
990 return rlp_node_buf.pop().expect("encode_leaf must push an RlpNode");
991 }
992 ArenaSparseNode::Branch(b) => {
993 if let ArenaSparseNodeState::Cached { rlp_node, .. } = &b.state {
994 let rlp_node = rlp_node.clone();
995 return rlp_node;
996 }
997 }
998 ArenaSparseNode::Subtrie(_) | ArenaSparseNode::TakenSubtrie | ArenaSparseNode::Free => {
999 unreachable!("Subtrie/TakenSubtrie/Free cannot be a subtrie's root");
1000 }
1001 }
1002
1003 cursor.reset(arena, root, base_path);
1004
1005 loop {
1009 let result = cursor.next(&mut *arena, |_, node| {
1010 matches!(
1011 node,
1012 ArenaSparseNode::Branch(b) if matches!(b.state, ArenaSparseNodeState::Dirty)
1013 )
1014 });
1015
1016 match result {
1017 NextResult::Done => break,
1018 NextResult::NonBranch => {
1019 unreachable!("should_descend only returns true for dirty branches")
1020 }
1021 NextResult::Branch => {}
1022 };
1023
1024 let head_idx = cursor.head().expect("cursor is non-empty").index;
1025 let head_path = cursor.head_path();
1026
1027 trace!(
1031 target: TRACE_TARGET,
1032 branch_path = ?head_path,
1033 branch_short_key = ?arena[head_idx].short_key().expect("head is a branch"),
1034 state_mask = ?arena[head_idx].branch_ref().state_mask,
1035 "Calculating branch RlpNode",
1036 );
1037
1038 rlp_node_buf.clear();
1039 let mut node_epoch = TrieNodeEpoch::UNMODIFIED;
1040 let state_mask = arena[head_idx].branch_ref().state_mask;
1041 for (dense_idx, _nibble) in BranchChildIter::new(state_mask) {
1042 let child = arena[head_idx].branch_ref().children[dense_idx];
1043 match child.revealed_index() {
1044 None => {
1045 rlp_node_buf.push(arena.blinded(child).clone());
1046 }
1047 Some(child_idx) => {
1048 match &arena[child_idx] {
1049 ArenaSparseNode::Leaf { .. } => {
1050 Self::encode_leaf(
1051 arena,
1052 child_idx,
1053 rlp_buf,
1054 rlp_node_buf,
1055 new_epoch,
1056 );
1057 }
1058 ArenaSparseNode::Branch(child_b) => {
1059 let ArenaSparseNodeState::Cached { rlp_node, .. } = &child_b.state
1060 else {
1061 panic!("child branch must be cached after DFS");
1062 };
1063 let rlp_node = rlp_node.clone();
1064 rlp_node_buf.push(rlp_node);
1065 }
1066 ArenaSparseNode::Subtrie(subtrie) => {
1067 let subtrie_root = &subtrie.arena[subtrie.root];
1068 match subtrie_root {
1069 ArenaSparseNode::Branch(ArenaSparseNodeBranch {
1070 state: ArenaSparseNodeState::Cached { rlp_node, .. },
1071 ..
1072 }) |
1073 ArenaSparseNode::Leaf {
1074 state: ArenaSparseNodeState::Cached { rlp_node, .. },
1075 ..
1076 } => {
1077 rlp_node_buf.push(rlp_node.clone());
1078 }
1079 _ => panic!("subtrie root must be a cached Branch or Leaf"),
1080 }
1081 }
1082 ArenaSparseNode::TakenSubtrie |
1083 ArenaSparseNode::EmptyRoot { .. } |
1084 ArenaSparseNode::Free => {
1085 unreachable!("Unexpected child {:?}", arena[child_idx]);
1086 }
1087 }
1088 let Some(ArenaSparseNodeState::Cached { epoch: child_epoch, .. }) =
1089 arena[child_idx].state_ref()
1090 else {
1091 panic!("revealed child must be cached after encoding");
1092 };
1093 node_epoch = node_epoch.max(*child_epoch);
1094 }
1095 }
1096 }
1097
1098 let b = arena[head_idx].branch_ref();
1100 let short_key = b.short_key;
1101 let state_mask = b.state_mask;
1102 let prev_branch_masks = b.branch_masks;
1103 let new_branch_masks = Self::get_branch_masks(arena, b);
1104 let was_dirty = matches!(b.state, ArenaSparseNodeState::Dirty);
1105 if was_dirty {
1106 node_epoch = node_epoch.max(new_epoch);
1107 }
1108
1109 rlp_buf.clear();
1110 let rlp_node = BranchNodeRef::new(rlp_node_buf, state_mask).rlp(rlp_buf);
1111
1112 let rlp_node = if short_key.is_empty() {
1113 rlp_node
1114 } else {
1115 rlp_buf.clear();
1116 ExtensionNodeRef::new(&short_key, &rlp_node).rlp(rlp_buf)
1117 };
1118
1119 trace!(
1120 target: TRACE_TARGET,
1121 path = ?head_path,
1122 short_key = ?arena[head_idx].short_key(),
1123 children = ?state_mask.iter().zip(rlp_node_buf.iter()).collect::<Vec<_>>(),
1124 rlp_node = ?rlp_node,
1125 "Calculated branch RlpNode",
1126 );
1127
1128 let branch = arena[head_idx].branch_mut();
1129 branch.state = ArenaSparseNodeState::Cached { rlp_node, epoch: node_epoch };
1130 branch.branch_masks = new_branch_masks;
1131
1132 if let Some(trie_updates) = updates.as_mut().filter(|_| was_dirty) {
1135 let mut logical_path = head_path;
1136 logical_path.extend(&short_key);
1137
1138 if !logical_path.is_empty() {
1139 if !prev_branch_masks.is_empty() && new_branch_masks.is_empty() {
1140 trie_updates.updated_nodes.remove(&logical_path);
1141 trie_updates.removed_nodes.insert(logical_path);
1142 } else if !new_branch_masks.is_empty() {
1143 let compact = arena[head_idx].branch_ref().branch_node_compact(arena);
1144 trie_updates.updated_nodes.insert(logical_path, compact);
1145 trie_updates.removed_nodes.remove(&logical_path);
1146 }
1147 }
1148 }
1149 }
1150
1151 let ArenaSparseNodeState::Cached { rlp_node, .. } = &arena[root].branch_ref().state else {
1152 panic!("root must be cached after update_cached_rlp");
1153 };
1154 rlp_node.clone()
1155 }
1156
1157 fn get_leaf_value_in_arena<'a>(
1160 arena: &'a NodeArena,
1161 mut current: Index,
1162 full_path: &Nibbles,
1163 mut path_offset: usize,
1164 ) -> Option<&'a Vec<u8>> {
1165 loop {
1166 match &arena[current] {
1167 ArenaSparseNode::EmptyRoot { .. } |
1168 ArenaSparseNode::TakenSubtrie |
1169 ArenaSparseNode::Free => return None,
1170 ArenaSparseNode::Leaf { key, value, .. } => {
1171 let remaining = full_path.slice_unchecked(path_offset, full_path.len());
1172 return (remaining == *key).then_some(value);
1173 }
1174 ArenaSparseNode::Branch(b) => {
1175 let short_key = &b.short_key;
1176 let logical_end = path_offset + short_key.len();
1177 if full_path.len() <= logical_end ||
1178 (!short_key.is_empty() &&
1179 full_path.slice_unchecked(path_offset, logical_end) != *short_key)
1180 {
1181 return None;
1182 }
1183
1184 let child_nibble = full_path.get_unchecked(logical_end);
1185 let child_idx = BranchChildIdx::new(b.state_mask, child_nibble)?;
1186 current = b.children[child_idx].revealed_index()?;
1187 path_offset = logical_end + 1;
1188 }
1189 ArenaSparseNode::Subtrie(subtrie) => {
1190 return Self::get_leaf_value_in_arena(
1191 &subtrie.arena,
1192 subtrie.root,
1193 full_path,
1194 path_offset,
1195 );
1196 }
1197 }
1198 }
1199 }
1200
1201 fn find_leaf_in_arena(
1205 arena: &NodeArena,
1206 mut current: Index,
1207 full_path: &Nibbles,
1208 mut path_offset: usize,
1209 expected_value: Option<&Vec<u8>>,
1210 ) -> Result<LeafLookup, LeafLookupError> {
1211 loop {
1212 match &arena[current] {
1213 ArenaSparseNode::EmptyRoot { .. } |
1214 ArenaSparseNode::TakenSubtrie |
1215 ArenaSparseNode::Free => {
1216 return Ok(LeafLookup::NonExistent);
1217 }
1218 ArenaSparseNode::Leaf { key, value, .. } => {
1219 let remaining = full_path.slice(path_offset..);
1220 if remaining != *key {
1221 return Ok(LeafLookup::NonExistent);
1222 }
1223 if let Some(expected) = expected_value &&
1224 *expected != *value
1225 {
1226 return Err(LeafLookupError::ValueMismatch {
1227 path: *full_path,
1228 expected: Some(expected.clone()),
1229 actual: value.clone(),
1230 });
1231 }
1232 return Ok(LeafLookup::Exists);
1233 }
1234 ArenaSparseNode::Branch(b) => {
1235 let short_key = &b.short_key;
1236 let logical_end = path_offset + short_key.len();
1237
1238 if full_path.len() <= logical_end {
1239 return Ok(LeafLookup::NonExistent);
1240 }
1241
1242 if full_path.slice(path_offset..logical_end) != *short_key {
1243 return Ok(LeafLookup::NonExistent);
1244 }
1245
1246 let child_nibble = full_path.get_unchecked(logical_end);
1247 let Some(child_idx) = BranchChildIdx::new(b.state_mask, child_nibble) else {
1248 return Ok(LeafLookup::NonExistent);
1249 };
1250
1251 let child = b.children[child_idx];
1252 match child.revealed_index() {
1253 None => {
1254 let rlp_node = arena.blinded(child);
1255 let hash = rlp_node
1256 .as_hash()
1257 .unwrap_or_else(|| keccak256(rlp_node.as_slice()));
1258 let mut blinded_path = full_path.slice(..logical_end);
1259 blinded_path.push_unchecked(child_nibble);
1260 return Err(LeafLookupError::BlindedNode { path: blinded_path, hash });
1261 }
1262 Some(child_idx) => {
1263 current = child_idx;
1264 path_offset = logical_end + 1;
1265 }
1266 }
1267 }
1268 ArenaSparseNode::Subtrie(subtrie) => {
1269 return Self::find_leaf_in_arena(
1270 &subtrie.arena,
1271 subtrie.root,
1272 full_path,
1273 path_offset,
1274 expected_value,
1275 );
1276 }
1277 }
1278 }
1279 }
1280
1281 fn encode_leaf(
1285 arena: &mut NodeArena,
1286 idx: Index,
1287 rlp_buf: &mut Vec<u8>,
1288 rlp_node_buf: &mut Vec<RlpNode>,
1289 new_epoch: TrieNodeEpoch,
1290 ) {
1291 let (key, value, state) = match &arena[idx] {
1292 ArenaSparseNode::Leaf { key, value, state } => (key, value, state),
1293 _ => unreachable!("encode_leaf called on non-Leaf node"),
1294 };
1295
1296 let epoch = match state {
1297 ArenaSparseNodeState::Cached { rlp_node, .. } => {
1298 rlp_node_buf.push(rlp_node.clone());
1299 return;
1300 }
1301 ArenaSparseNodeState::Revealed => TrieNodeEpoch::UNMODIFIED,
1302 ArenaSparseNodeState::Dirty => new_epoch,
1303 };
1304
1305 rlp_buf.clear();
1306 let rlp_node = LeafNodeRef { key, value }.rlp(rlp_buf);
1307
1308 *arena[idx].state_mut() =
1309 ArenaSparseNodeState::Cached { rlp_node: rlp_node.clone(), epoch };
1310 rlp_node_buf.push(rlp_node);
1311 }
1312
1313 fn split_and_insert_leaf(
1327 arena: &mut NodeArena,
1328 cursor: &mut ArenaCursor,
1329 root: &mut Index,
1330 new_leaf_path: Nibbles,
1331 value: &[u8],
1332 ) -> bool {
1333 let old_child_idx = cursor.head().expect("cursor must have head").index;
1334 let old_child_short_key = arena[old_child_idx].short_key().expect("top of stack is a leaf");
1335 let diverge_len = new_leaf_path.common_prefix_length(old_child_short_key);
1336
1337 trace!(
1338 target: TRACE_TARGET,
1339 path = ?cursor.head_path(),
1340 ?new_leaf_path,
1341 ?old_child_short_key,
1342 diverge_len,
1343 "Splitting node and inserting new leaf",
1344 );
1345
1346 let old_child_nibble = old_child_short_key.get_unchecked(diverge_len);
1347 let old_child_suffix = old_child_short_key.slice(diverge_len + 1..);
1348
1349 let newly_dirtied_existing = match &mut arena[old_child_idx] {
1352 ArenaSparseNode::Leaf { key, state, .. } => {
1353 *key = old_child_suffix;
1354 let was_clean = !matches!(state, ArenaSparseNodeState::Dirty);
1355 *state = ArenaSparseNodeState::Dirty;
1356 was_clean
1357 }
1358 ArenaSparseNode::Branch(b) => {
1359 b.short_key = old_child_suffix;
1360 b.state = b.state.to_dirty();
1361 false
1363 }
1364 _ => unreachable!("split_and_insert_leaf called on non-Leaf/Branch node"),
1365 };
1366
1367 let short_key = new_leaf_path.slice(..diverge_len);
1368 let new_leaf_nibble = new_leaf_path.get_unchecked(diverge_len);
1369 debug_assert_ne!(old_child_nibble, new_leaf_nibble);
1370
1371 let new_leaf_idx = arena.insert(ArenaSparseNode::Leaf {
1372 state: ArenaSparseNodeState::Dirty,
1373 key: new_leaf_path.slice(diverge_len + 1..),
1374 value: value.to_vec(),
1375 });
1376
1377 let (first_nibble, first_child, second_nibble, second_child) =
1378 if old_child_nibble < new_leaf_nibble {
1379 (old_child_nibble, old_child_idx, new_leaf_nibble, new_leaf_idx)
1380 } else {
1381 (new_leaf_nibble, new_leaf_idx, old_child_nibble, old_child_idx)
1382 };
1383
1384 let state_mask = TrieMask::from_nibble(first_nibble) | TrieMask::from_nibble(second_nibble);
1385 let mut children = SmallVec::new();
1386 children.push(BranchChild::revealed(first_child));
1387 children.push(BranchChild::revealed(second_child));
1388
1389 let new_branch_idx = arena.insert(ArenaSparseNode::Branch(ArenaSparseNodeBranch {
1390 state: ArenaSparseNodeState::Dirty,
1391 children,
1392 state_mask,
1393 short_key,
1394 branch_masks: BranchNodeMasks::default(),
1395 }));
1396
1397 cursor.replace_head_index(arena, root, new_branch_idx);
1398 newly_dirtied_existing
1399 }
1400
1401 #[instrument(level = "trace", target = TRACE_TARGET, skip_all, fields(full_path = ?full_path))]
1416 fn upsert_leaf(
1417 arena: &mut NodeArena,
1418 cursor: &mut ArenaCursor,
1419 root: &mut Index,
1420 full_path: &Nibbles,
1421 value: &[u8],
1422 find_result: SeekResult,
1423 ) -> (UpsertLeafResult, SubtrieCounterDeltas) {
1424 trace!(target: TRACE_TARGET, ?find_result, "Upserting leaf");
1425 let head = cursor.head().expect("cursor is non-empty");
1426
1427 match find_result {
1428 SeekResult::Blinded => {
1429 unreachable!("Blinded case must be handled by caller")
1430 }
1431 SeekResult::EmptyRoot => {
1432 let head_idx = head.index;
1433 arena[head_idx] = ArenaSparseNode::Leaf {
1434 state: ArenaSparseNodeState::Dirty,
1435 key: full_path.slice(cursor.head_path_len()..),
1436 value: value.to_vec(),
1437 };
1438 (
1439 UpsertLeafResult::NewLeaf,
1440 SubtrieCounterDeltas { num_leaves_delta: 1, num_dirty_leaves_delta: 1 },
1441 )
1442 }
1443 SeekResult::RevealedLeaf => {
1444 let head_idx = head.index;
1446 let was_clean =
1447 if let ArenaSparseNode::Leaf { value: v, state, .. } = &mut arena[head_idx] {
1448 v.clear();
1449 v.extend_from_slice(value);
1450 let was_clean = !matches!(state, ArenaSparseNodeState::Dirty);
1451 *state = ArenaSparseNodeState::Dirty;
1452 was_clean
1453 } else {
1454 unreachable!("RevealedLeaf but cursor head is not a leaf")
1455 };
1456 (
1457 UpsertLeafResult::Updated,
1458 SubtrieCounterDeltas {
1459 num_leaves_delta: 0,
1460 num_dirty_leaves_delta: was_clean as i64,
1461 },
1462 )
1463 }
1464 SeekResult::Diverged => {
1465 let full_path_from_head = full_path.slice(cursor.head_path_len()..);
1466
1467 let split_dirtied_existing =
1468 Self::split_and_insert_leaf(arena, cursor, root, full_path_from_head, value);
1469
1470 let result = if cursor.depth() >= 1 {
1471 UpsertLeafResult::NewChild
1472 } else {
1473 UpsertLeafResult::NewLeaf
1474 };
1475 (
1476 result,
1477 SubtrieCounterDeltas {
1478 num_leaves_delta: 1,
1479 num_dirty_leaves_delta: 1 + split_dirtied_existing as i64,
1480 },
1481 )
1482 }
1483 SeekResult::NoChild { child_nibble } => {
1484 let head_idx = head.index;
1485
1486 let leaf_key = full_path.slice(cursor.head_logical_branch_path_len(arena) + 1..);
1487 let new_leaf = arena.insert(ArenaSparseNode::Leaf {
1488 state: ArenaSparseNodeState::Dirty,
1489 key: leaf_key,
1490 value: value.to_vec(),
1491 });
1492
1493 let branch = arena[head_idx].branch_mut();
1494 branch.set_child(child_nibble, BranchChild::revealed(new_leaf));
1495
1496 cursor.seek(arena, full_path);
1498
1499 (
1500 UpsertLeafResult::NewChild,
1501 SubtrieCounterDeltas { num_leaves_delta: 1, num_dirty_leaves_delta: 1 },
1502 )
1503 }
1504 SeekResult::RevealedSubtrie => {
1505 unreachable!("RevealedSubtrie must be handled by caller")
1506 }
1507 }
1508 }
1509
1510 fn remove_leaf(
1526 arena: &mut NodeArena,
1527 cursor: &mut ArenaCursor,
1528 root: &mut Index,
1529 key: B256,
1530 full_path: &Nibbles,
1531 find_result: SeekResult,
1532 updates: &mut Option<SparseTrieUpdates>,
1533 ) -> (RemoveLeafResult, SubtrieCounterDeltas) {
1534 match find_result {
1535 SeekResult::Blinded | SeekResult::RevealedSubtrie => {
1536 unreachable!("Blinded/RevealedSubtrie must be handled by caller")
1537 }
1538 SeekResult::EmptyRoot | SeekResult::Diverged | SeekResult::NoChild { .. } => {
1539 (RemoveLeafResult::NotFound, SubtrieCounterDeltas::default())
1540 }
1541 SeekResult::RevealedLeaf => {
1542 let head_idx = cursor.head().expect("cursor is non-empty").index;
1544 let head_path = cursor.head_path();
1545
1546 trace!(
1547 target: TRACE_TARGET,
1548 path = ?head_path,
1549 ?full_path,
1550 "Removing leaf",
1551 );
1552
1553 if let Some(parent_entry) = cursor.parent() {
1556 let parent_idx = parent_entry.index;
1557 let child_nibble = head_path.last().expect("non-root leaf");
1558 let parent_branch = arena[parent_idx].branch_ref();
1559
1560 if parent_branch.state_mask.count_bits() == 2 &&
1561 parent_branch.sibling_child(child_nibble).is_blinded()
1562 {
1563 let sibling_nibble = parent_branch
1564 .state_mask
1565 .iter()
1566 .find(|&n| n != child_nibble)
1567 .expect("branch has two children");
1568 let mut sibling_path = cursor.parent_logical_branch_path(arena);
1569 sibling_path.push_unchecked(sibling_nibble);
1570 trace!(target: TRACE_TARGET, ?full_path, ?sibling_path, "Removal would collapse branch onto blinded sibling, requesting proof");
1571 return (
1572 RemoveLeafResult::NeedsProof {
1573 key,
1574 proof_key: Self::nibbles_to_padded_b256(&sibling_path),
1575 parent: ProofV2TargetParent::new(
1576 sibling_path
1577 .len()
1578 .checked_sub(1)
1579 .expect("sibling path has a child nibble"),
1580 ),
1581 },
1582 SubtrieCounterDeltas::default(),
1583 );
1584 }
1585 }
1586
1587 let removed_was_dirty =
1589 matches!(arena[head_idx].state_ref(), Some(ArenaSparseNodeState::Dirty));
1590
1591 if cursor.depth() == 0 {
1592 arena.remove(head_idx);
1595 *root = arena
1596 .insert(ArenaSparseNode::EmptyRoot { state: ArenaSparseNodeState::Dirty });
1597 cursor.reset(arena, *root, head_path);
1598 return (
1599 RemoveLeafResult::Removed,
1600 SubtrieCounterDeltas {
1601 num_leaves_delta: -1,
1602 num_dirty_leaves_delta: -(removed_was_dirty as i64),
1603 },
1604 );
1605 }
1606
1607 cursor.pop(arena);
1609
1610 let parent_entry = cursor.head().expect("cursor is non-empty");
1612 let parent_idx = parent_entry.index;
1613 let child_nibble = head_path.last().expect("non-root leaf");
1614
1615 arena.remove(head_idx);
1617 let parent_branch = arena[parent_idx].branch_mut();
1618 parent_branch.remove_child(child_nibble);
1619
1620 let collapse_dirtied_leaf = if parent_branch.state_mask.count_bits() == 1 {
1623 Self::collapse_branch(arena, cursor, root, updates)
1624 } else {
1625 false
1626 };
1627 (
1628 RemoveLeafResult::Removed,
1629 SubtrieCounterDeltas {
1630 num_leaves_delta: -1,
1631 num_dirty_leaves_delta: (collapse_dirtied_leaf as i64) -
1632 (removed_was_dirty as i64),
1633 },
1634 )
1635 }
1636 }
1637 }
1638
1639 fn check_subtrie_collapse_needs_proof(
1645 arena: &NodeArena,
1646 cursor: &ArenaCursor,
1647 subtrie_updates: &[(B256, Nibbles, LeafUpdate)],
1648 ) -> Option<ArenaRequiredProof> {
1649 let num_removals = subtrie_updates
1650 .iter()
1651 .filter(|(_, _, u)| matches!(u, LeafUpdate::Changed(v) if v.is_empty()))
1652 .count() as u64;
1653
1654 let num_changed = subtrie_updates.iter().filter(|(_, _, u)| u.is_changed()).count() as u64;
1662
1663 if num_removals == 0 || num_removals != num_changed {
1664 return None;
1665 }
1666
1667 let subtrie_entry = cursor.head()?;
1669 let subtrie_num_leaves = match &arena[subtrie_entry.index] {
1670 ArenaSparseNode::Subtrie(s) => s.num_leaves,
1671 _ => return None,
1672 };
1673 if num_removals < subtrie_num_leaves {
1674 return None;
1675 }
1676
1677 let child_nibble =
1678 cursor.head_last_nibble().expect("subtrie path must have at least one nibble");
1679
1680 let parent_entry = cursor.parent()?;
1681 let parent_branch = arena[parent_entry.index].branch_ref();
1682 if parent_branch.state_mask.count_bits() != 2 {
1683 return None;
1684 }
1685
1686 if !parent_branch.sibling_child(child_nibble).is_blinded() {
1687 return None;
1688 }
1689
1690 let sibling_nibble = parent_branch
1691 .state_mask
1692 .iter()
1693 .find(|&n| n != child_nibble)
1694 .expect("branch has two children");
1695 let mut sibling_path = cursor.parent_logical_branch_path(arena);
1696 sibling_path.push_unchecked(sibling_nibble);
1697
1698 Some(ArenaRequiredProof {
1699 key: Self::nibbles_to_padded_b256(&sibling_path),
1700 parent: ProofV2TargetParent::new(
1701 sibling_path.len().checked_sub(1).expect("sibling path has a child nibble"),
1702 ),
1703 })
1704 }
1705
1706 fn collapse_branch(
1717 arena: &mut NodeArena,
1718 cursor: &mut ArenaCursor,
1719 root: &mut Index,
1720 updates: &mut Option<SparseTrieUpdates>,
1721 ) -> bool {
1722 let branch_idx = cursor.head().expect("cursor is non-empty").index;
1723 let branch_path = cursor.head_path();
1724 let branch = arena[branch_idx].branch_ref();
1725 let remaining_nibble =
1726 branch.state_mask.iter().next().expect("branch has at least one child");
1727 let branch_short_key = branch.short_key;
1728
1729 debug_assert_eq!(
1730 branch.state_mask.count_bits(),
1731 1,
1732 "collapse_branch requires exactly 1 child"
1733 );
1734 debug_assert!(
1735 !branch.children[0].is_blinded(),
1736 "collapse_branch called with a blinded remaining child"
1737 );
1738
1739 trace!(
1740 target: TRACE_TARGET,
1741 path = ?branch_path,
1742 short_key = ?branch_short_key,
1743 branch_masks = ?branch.branch_masks,
1744 ?remaining_nibble,
1745 "Collapsing single-child branch",
1746 );
1747
1748 if let Some(trie_updates) = updates.as_mut() &&
1751 !branch.branch_masks.is_empty()
1752 {
1753 let logical_path = cursor.head_logical_branch_path(arena);
1754 if !logical_path.is_empty() {
1755 trie_updates.updated_nodes.remove(&logical_path);
1756 trie_updates.removed_nodes.insert(logical_path);
1757 }
1758 }
1759
1760 let mut prefix = branch_short_key;
1762 prefix.push_unchecked(remaining_nibble);
1763
1764 let child_idx = branch.children[0].revealed_index().expect("remaining child is revealed");
1765
1766 let newly_dirtied_leaf = match &mut arena[child_idx] {
1769 ArenaSparseNode::Leaf { key, state, .. } => {
1770 let mut new_key = prefix;
1771 new_key.extend(key);
1772 *key = new_key;
1773 let was_clean = !matches!(state, ArenaSparseNodeState::Dirty);
1774 *state = ArenaSparseNodeState::Dirty;
1775 was_clean
1776 }
1777 ArenaSparseNode::Branch(b) => {
1778 let mut new_short_key = prefix;
1779 new_short_key.extend(&b.short_key);
1780 b.short_key = new_short_key;
1781 b.state = b.state.to_dirty();
1782 false
1783 }
1784 ArenaSparseNode::Subtrie(subtrie) => {
1785 subtrie.path = branch_path;
1786 match &mut subtrie.arena[subtrie.root] {
1787 ArenaSparseNode::Branch(b) => {
1788 let mut new_short_key = prefix;
1789 new_short_key.extend(&b.short_key);
1790 b.short_key = new_short_key;
1791 b.state = b.state.to_dirty();
1792 }
1793 ArenaSparseNode::Leaf { key, state, .. } => {
1794 let mut new_key = prefix;
1795 new_key.extend(key);
1796 *key = new_key;
1797 let was_clean = !matches!(state, ArenaSparseNodeState::Dirty);
1798 *state = ArenaSparseNodeState::Dirty;
1799 if was_clean {
1800 subtrie.num_dirty_leaves += 1;
1801 }
1802 }
1803 _ => {
1804 unreachable!("subtrie root must be a Branch or Leaf during collapse_branch")
1805 }
1806 }
1807 false
1808 }
1809 _ => unreachable!("remaining child must be Leaf, Branch, or Subtrie"),
1810 };
1811
1812 cursor.replace_head_index(arena, root, child_idx);
1814
1815 arena.remove(branch_idx);
1817 newly_dirtied_leaf
1818 }
1819
1820 fn count_leaves_and_dirty(arena: &NodeArena, idx: Index) -> (u64, u64) {
1822 match &arena[idx] {
1823 ArenaSparseNode::Leaf { state, .. } => {
1824 let dirty = matches!(state, ArenaSparseNodeState::Dirty) as u64;
1825 (1, dirty)
1826 }
1827 ArenaSparseNode::Branch(b) => {
1828 let mut leaves = 0u64;
1829 let mut dirty = 0u64;
1830 for c in &b.children {
1831 if let Some(child_idx) = c.revealed_index() {
1832 let (l, d) = Self::count_leaves_and_dirty(arena, child_idx);
1833 leaves += l;
1834 dirty += d;
1835 }
1836 }
1837 (leaves, dirty)
1838 }
1839 _ => (0, 0),
1840 }
1841 }
1842
1843 #[instrument(level = "trace", target = TRACE_TARGET, skip_all)]
1849 #[cfg(all(debug_assertions, not(feature = "dst")))]
1850 fn debug_assert_subtrie_structure(&mut self) {
1851 let mut cursor = mem::take(&mut self.buffers.cursor);
1852 cursor.reset(&self.upper_arena, self.root, Nibbles::default());
1853
1854 loop {
1855 let result = cursor.next(&mut self.upper_arena, |_, _| true);
1856 match result {
1857 NextResult::Done => break,
1858 NextResult::NonBranch | NextResult::Branch => {
1859 let path_len = cursor.head_path_len();
1860 let node = &self.upper_arena[cursor.head().expect("cursor is non-empty").index];
1861
1862 if Self::should_be_subtrie(path_len) {
1863 debug_assert!(
1864 matches!(
1865 node,
1866 ArenaSparseNode::Subtrie(_) | ArenaSparseNode::TakenSubtrie
1867 ),
1868 "node at path_len={path_len} should be a Subtrie but is {node:?}",
1869 );
1870 } else {
1871 debug_assert!(
1872 !matches!(node, ArenaSparseNode::Subtrie(_)),
1873 "node at path_len={path_len} should NOT be a Subtrie but is",
1874 );
1875 }
1876 }
1877 }
1878 }
1879
1880 self.buffers.cursor = cursor;
1881 }
1882
1883 fn migrate_nodes(
1891 dst: &mut NodeArena,
1892 src: &mut NodeArena,
1893 src_idx: Index,
1894 dst_slot: Option<Index>,
1895 ) -> Index {
1896 let mut node = src.remove(src_idx).expect("node exists in source arena");
1897
1898 Self::migrate_children(dst, src, &mut node);
1900
1901 if let Some(slot) = dst_slot {
1902 dst[slot] = node;
1903 slot
1904 } else {
1905 dst.insert(node)
1906 }
1907 }
1908
1909 fn migrate_children(dst: &mut NodeArena, src: &mut NodeArena, node: &mut ArenaSparseNode) {
1913 let ArenaSparseNode::Branch(b) = node else { return };
1914 for child in &mut b.children {
1915 *child = match child.revealed_index() {
1916 Some(child_idx) => {
1917 BranchChild::revealed(Self::migrate_nodes(dst, src, child_idx, None))
1918 }
1919 None => dst.insert_blinded(src.take_blinded(*child)),
1920 };
1921 }
1922 }
1923
1924 fn remove_pruned_node(arena: &mut NodeArena, cursor: &mut ArenaCursor) -> ArenaSparseNode {
1927 let path = cursor.head_path();
1929 let entry = cursor.pop(arena);
1930 let node = arena.remove(entry.index).expect("node must exist to be pruned");
1931 let rlp_node = node
1932 .state_ref()
1933 .and_then(ArenaSparseNodeState::cached_rlp_node)
1934 .cloned()
1935 .expect("prune must run after hashing");
1936 trace!(
1937 target: TRACE_TARGET,
1938 ?path,
1939 variant = %AsRef::<str>::as_ref(&node),
1940 cached_rlp_node = ?rlp_node,
1941 "pruning node",
1942 );
1943
1944 let parent_idx = cursor.head().expect("pruned child has parent").index;
1945 let child_nibble = path.last().expect("non-root child");
1946 let blinded = arena.insert_blinded(rlp_node);
1947 let parent_branch = arena[parent_idx].branch_mut();
1948 let child_idx = BranchChildIdx::new(parent_branch.state_mask, child_nibble)
1949 .expect("child nibble not found in parent state_mask");
1950 parent_branch.children[child_idx] = blinded;
1951
1952 node
1953 }
1954
1955 #[instrument(level = "trace", target = TRACE_TARGET, skip_all)]
1964 fn reveal_node(
1965 arena: &mut NodeArena,
1966 cursor: &ArenaCursor,
1967 node: &mut ProofTrieNodeV2,
1968 find_result: SeekResult,
1969 ) -> Option<Index> {
1970 let SeekResult::Blinded = find_result else {
1971 return None;
1973 };
1974
1975 let head = cursor.head().expect("cursor is non-empty");
1976 let head_idx = head.index;
1977 let head_branch_logical_path = cursor.head_logical_branch_path(arena);
1978
1979 debug_assert_eq!(
1980 node.path.len(),
1981 head_branch_logical_path.len() + 1,
1982 "proof node path {:?} is not a direct child of branch at {:?} (expected depth {})",
1983 node.path,
1984 head_branch_logical_path,
1985 head_branch_logical_path.len() + 1,
1986 );
1987
1988 let child_nibble = node.path.get_unchecked(head_branch_logical_path.len());
1989 let head_branch = arena[head_idx].branch_ref();
1990 let dense_child_idx = BranchChildIdx::new(head_branch.state_mask, child_nibble)
1991 .expect("Blinded result but child nibble not in state_mask");
1992
1993 let child = head_branch.children[dense_child_idx];
1994 if !child.is_blinded() {
1995 return None;
1996 }
1997 let cached_rlp = arena.blinded(child).clone();
1998
1999 trace!(
2000 target: TRACE_TARGET,
2001 path = ?node.path,
2002 rlp_node = ?cached_rlp,
2003 "Revealing node",
2004 );
2005
2006 let proof_node = mem::replace(node, ProofTrieNodeV2::empty());
2007 let mut arena_node = ArenaSparseNode::from_proof_node(arena, proof_node);
2008
2009 let state = arena_node.state_mut();
2010 *state =
2011 ArenaSparseNodeState::Cached { rlp_node: cached_rlp, epoch: TrieNodeEpoch::UNMODIFIED };
2012
2013 let child_idx = arena.insert(arena_node);
2014 arena[head_idx].branch_mut().children[dense_child_idx] = BranchChild::revealed(child_idx);
2015 arena.take_blinded(child);
2016
2017 Some(child_idx)
2018 }
2019
2020 #[cfg(all(debug_assertions, not(feature = "dst")))]
2021 fn collect_reachable_nodes(
2022 arena: &NodeArena,
2023 idx: Index,
2024 reachable: &mut alloy_primitives::map::HashSet<Index>,
2025 ) {
2026 if !reachable.insert(idx) {
2027 return;
2028 }
2029 if let ArenaSparseNode::Branch(b) = &arena[idx] {
2030 for child in &b.children {
2031 if let Some(child_idx) = child.revealed_index() {
2032 Self::collect_reachable_nodes(arena, child_idx, reachable);
2033 }
2034 }
2035 }
2036 }
2037
2038 #[cfg(all(debug_assertions, not(feature = "dst")))]
2039 fn assert_no_orphaned_nodes(arena: &NodeArena, root: Index, label: &str) {
2040 let mut reachable = alloy_primitives::map::HashSet::default();
2041 Self::collect_reachable_nodes(arena, root, &mut reachable);
2042 let all_indices: alloy_primitives::map::HashSet<Index> =
2043 arena.iter().map(|(idx, _)| idx).collect();
2044 let orphaned: Vec<_> = all_indices.difference(&reachable).collect();
2045 debug_assert!(
2046 orphaned.is_empty(),
2047 "{label} has {} orphaned node(s): {orphaned:?}",
2048 orphaned.len(),
2049 );
2050 }
2051}
2052
2053#[cfg(all(debug_assertions, not(feature = "dst")))]
2054impl Drop for ArenaParallelSparseTrie {
2055 fn drop(&mut self) {
2056 Self::assert_no_orphaned_nodes(&self.upper_arena, self.root, "upper arena");
2057
2058 for (_, node) in self.upper_arena.iter() {
2059 if let Some(subtrie) = node.as_subtrie() {
2060 Self::assert_no_orphaned_nodes(
2061 &subtrie.arena,
2062 subtrie.root,
2063 &alloc::format!("subtrie {:?}", subtrie.path),
2064 );
2065 }
2066 }
2067 }
2068}
2069
2070impl Default for ArenaParallelSparseTrie {
2071 fn default() -> Self {
2072 let mut upper_arena = NodeArena::new();
2073 let root = upper_arena
2074 .insert(ArenaSparseNode::EmptyRoot { state: ArenaSparseNodeState::Revealed });
2075 Self {
2076 upper_arena,
2077 root,
2078 buffers: ArenaTrieBuffers::default(),
2079 parallelism_thresholds: ArenaParallelismThresholds::default(),
2080 }
2081 }
2082}
2083
2084impl ArenaParallelSparseTrie {
2085 fn update_upper_subtrie(&mut self, head_idx: Index, new_epoch: TrieNodeEpoch) {
2087 let ArenaSparseNode::Subtrie(subtrie) = &mut self.upper_arena[head_idx] else {
2088 unreachable!()
2089 };
2090
2091 if !subtrie.arena[subtrie.root].is_cached() {
2092 subtrie.update_cached_rlp(new_epoch);
2093 }
2094
2095 Self::merge_subtrie_updates(&mut self.buffers.updates, &mut subtrie.buffers.updates);
2096 }
2097}
2098
2099impl SparseTrie for ArenaParallelSparseTrie {
2100 #[instrument(level = "trace", target = TRACE_TARGET, skip_all)]
2101 fn set_root(
2102 &mut self,
2103 root: TrieNodeV2,
2104 masks: Option<BranchNodeMasks>,
2105 retain_updates: bool,
2106 ) -> SparseTrieResult<()> {
2107 debug_assert!(
2108 matches!(self.upper_arena[self.root], ArenaSparseNode::EmptyRoot { .. }),
2109 "set_root called on a trie that already has revealed nodes"
2110 );
2111
2112 match root {
2113 TrieNodeV2::EmptyRoot => {
2114 trace!(target: TRACE_TARGET, "Setting empty root");
2115 self.upper_arena[self.root] =
2116 ArenaSparseNode::EmptyRoot { state: ArenaSparseNodeState::Revealed };
2117 }
2118 TrieNodeV2::Leaf(leaf) => {
2119 trace!(target: TRACE_TARGET, key = ?leaf.key, "Setting leaf root");
2120 self.upper_arena[self.root] = ArenaSparseNode::Leaf {
2121 state: ArenaSparseNodeState::Revealed,
2122 key: leaf.key,
2123 value: leaf.value,
2124 };
2125 }
2126 TrieNodeV2::Branch(branch) => {
2127 trace!(target: TRACE_TARGET, state_mask = ?branch.state_mask, num_children = branch.state_mask.count_bits(), "Setting branch root");
2128 let mut children = SmallVec::with_capacity(branch.state_mask.count_bits() as usize);
2129 for (stack_ptr, _nibble) in branch.state_mask.iter().enumerate() {
2130 let child = self.upper_arena.insert_blinded(branch.stack[stack_ptr].clone());
2131 children.push(child);
2132 }
2133
2134 self.upper_arena[self.root] = ArenaSparseNode::Branch(ArenaSparseNodeBranch {
2135 state: ArenaSparseNodeState::Revealed,
2136 children,
2137 state_mask: branch.state_mask,
2138 short_key: branch.key,
2139 branch_masks: masks.unwrap_or_default(),
2140 });
2141 }
2142 TrieNodeV2::Extension(node) => {
2143 return Err(SparseTrieErrorKind::Reveal {
2144 path: Nibbles::new(),
2145 node: Box::new(node),
2146 }
2147 .into());
2148 }
2149 }
2150
2151 self.set_updates(retain_updates);
2152 Ok(())
2153 }
2154
2155 fn set_updates(&mut self, retain_updates: bool) {
2156 if retain_updates {
2157 self.buffers.updates.get_or_insert_with(SparseTrieUpdates::default).clear();
2158 } else {
2159 self.buffers.updates = None;
2160 }
2161 }
2162
2163 #[instrument(level = "trace", target = TRACE_TARGET, skip_all, fields(num_nodes = nodes.len()))]
2164 fn reveal_nodes(&mut self, nodes: &mut [ProofTrieNodeV2]) -> SparseTrieResult<()> {
2165 if nodes.is_empty() {
2166 return Ok(());
2167 }
2168
2169 if matches!(self.upper_arena[self.root], ArenaSparseNode::EmptyRoot { .. }) {
2170 trace!(target: TRACE_TARGET, "Skipping reveal_nodes on empty root");
2171 return Ok(());
2172 }
2173
2174 nodes.sort_unstable_by_key(|n| n.path);
2176
2177 let threshold = self.parallelism_thresholds.min_revealed_nodes;
2178
2179 let mut cursor = mem::take(&mut self.buffers.cursor);
2181 cursor.reset(&self.upper_arena, self.root, Nibbles::default());
2182
2183 let mut node_idx = if nodes[0].path.is_empty() { 1 } else { 0 };
2185
2186 let mut taken: Vec<(Index, Box<ArenaSparseSubtrie>, Vec<ProofTrieNodeV2>)> = Vec::new();
2190
2191 while node_idx < nodes.len() {
2192 let find_result = cursor.seek(&mut self.upper_arena, &nodes[node_idx].path);
2193
2194 match find_result {
2195 SeekResult::RevealedLeaf => {
2196 trace!(target: TRACE_TARGET, path = ?nodes[node_idx].path, "Skipping reveal: leaf head");
2197 node_idx += 1;
2198 }
2199 SeekResult::Blinded => {
2200 let child_path = nodes[node_idx].path;
2202 let child_idx = Self::reveal_node(
2203 &mut self.upper_arena,
2204 &cursor,
2205 &mut nodes[node_idx],
2206 SeekResult::Blinded,
2207 );
2208 node_idx += 1;
2209
2210 if let Some(child_idx) = child_idx {
2211 self.maybe_wrap_in_subtrie(child_idx, &child_path);
2212 }
2213 }
2214 SeekResult::RevealedSubtrie => {
2215 let child_idx = cursor.head().expect("cursor is non-empty").index;
2216 let prefix = cursor.head_path();
2217
2218 let subtrie_start = node_idx;
2219 while node_idx < nodes.len() && nodes[node_idx].path.starts_with(&prefix) {
2220 node_idx += 1;
2221 }
2222 let num_subtrie_nodes = node_idx - subtrie_start;
2223
2224 if num_subtrie_nodes >= threshold {
2225 trace!(target: TRACE_TARGET, ?prefix, num_subtrie_nodes, "Taking subtrie for parallel reveal");
2227 let ArenaSparseNode::Subtrie(subtrie) = mem::replace(
2228 &mut self.upper_arena[child_idx],
2229 ArenaSparseNode::TakenSubtrie,
2230 ) else {
2231 unreachable!("RevealedSubtrie must point to a Subtrie node")
2232 };
2233 let node_vec: Vec<ProofTrieNodeV2> = (subtrie_start..node_idx)
2234 .map(|i| mem::replace(&mut nodes[i], ProofTrieNodeV2::empty()))
2235 .collect();
2236 taken.push((child_idx, subtrie, node_vec));
2237 } else {
2238 trace!(target: TRACE_TARGET, ?prefix, num_subtrie_nodes, "Revealing subtrie inline");
2240 let ArenaSparseNode::Subtrie(subtrie) = &mut self.upper_arena[child_idx]
2241 else {
2242 unreachable!("RevealedSubtrie must point to a Subtrie node")
2243 };
2244 let mut subtrie_nodes: Vec<ProofTrieNodeV2> = (subtrie_start..node_idx)
2245 .map(|i| mem::replace(&mut nodes[i], ProofTrieNodeV2::empty()))
2246 .collect();
2247 subtrie.reveal_nodes(&mut subtrie_nodes)?;
2248 }
2249 }
2250 _ => {
2251 trace!(target: TRACE_TARGET, path = ?nodes[node_idx].path, ?find_result, "Skipping reveal: no blinded child");
2252 node_idx += 1;
2253 }
2254 }
2255 }
2256
2257 cursor.drain(&mut self.upper_arena);
2259 self.buffers.cursor = cursor;
2260
2261 if taken.is_empty() {
2262 return Ok(());
2263 }
2264
2265 if taken.len() == 1 {
2267 let (_, subtrie, node_vec) = &mut taken[0];
2268 subtrie.reveal_nodes(node_vec)?;
2269 } else {
2270 use rayon::iter::{IntoParallelRefMutIterator, ParallelIterator};
2271
2272 let parent_span = tracing::Span::current();
2273 let results: Vec<SparseTrieResult<()>> = taken
2274 .par_iter_mut()
2275 .map(|(_, subtrie, node_vec)| {
2276 let _guard = parent_span.enter();
2277 subtrie.reveal_nodes(node_vec)
2278 })
2279 .collect();
2280
2281 if let Some(err) = results.into_iter().find(|r| r.is_err()) {
2282 for (idx, subtrie, _) in taken {
2284 self.restore_taken_subtrie(idx, subtrie);
2285 }
2286 return err;
2287 }
2288 }
2289
2290 for (idx, subtrie, _) in taken {
2292 self.restore_taken_subtrie(idx, subtrie);
2293 }
2294
2295 #[cfg(all(debug_assertions, not(feature = "dst")))]
2296 self.debug_assert_subtrie_structure();
2297
2298 Ok(())
2299 }
2300
2301 #[instrument(level = "trace", target = TRACE_TARGET, skip_all, ret)]
2302 fn root(&mut self, new_epoch: TrieNodeEpoch) -> B256 {
2303 self.update_subtrie_hashes(new_epoch);
2304
2305 let rlp_node = Self::update_cached_rlp(
2306 &mut self.upper_arena,
2307 self.root,
2308 Nibbles::default(),
2309 &mut self.buffers,
2310 new_epoch,
2311 );
2312
2313 rlp_node.as_hash().expect("root RlpNode must be a hash")
2314 }
2315
2316 fn is_root_cached(&self) -> bool {
2317 self.upper_arena[self.root].is_cached()
2318 }
2319
2320 fn root_epoch(&self) -> Option<TrieNodeEpoch> {
2321 match self.upper_arena[self.root].state_ref()? {
2322 ArenaSparseNodeState::Revealed => Some(TrieNodeEpoch::UNMODIFIED),
2323 ArenaSparseNodeState::Cached { epoch, .. } => Some(*epoch),
2324 ArenaSparseNodeState::Dirty => None,
2325 }
2326 }
2327
2328 #[instrument(level = "trace", target = TRACE_TARGET, skip_all)]
2329 fn update_subtrie_hashes(&mut self, new_epoch: TrieNodeEpoch) {
2330 trace!(target: TRACE_TARGET, "Updating subtrie hashes");
2331
2332 if !matches!(&self.upper_arena[self.root], ArenaSparseNode::Branch(_)) {
2334 return;
2335 }
2336
2337 let mut total_dirty_leaves: u64 = 0;
2339 let mut taken: Vec<(Index, Box<ArenaSparseSubtrie>)> = Vec::new();
2340 for (idx, node) in self.upper_arena.iter_mut() {
2341 let ArenaSparseNode::Subtrie(s) = node else { continue };
2342 if s.num_dirty_leaves == 0 {
2343 continue;
2344 }
2345 total_dirty_leaves += s.num_dirty_leaves;
2346 let ArenaSparseNode::Subtrie(subtrie) =
2347 mem::replace(node, ArenaSparseNode::TakenSubtrie)
2348 else {
2349 unreachable!()
2350 };
2351 taken.push((idx, subtrie));
2352 }
2353
2354 if !taken.is_empty() {
2356 if taken.len() == 1 || total_dirty_leaves < self.parallelism_thresholds.min_dirty_leaves
2357 {
2358 for (_, subtrie) in &mut taken {
2359 subtrie.update_cached_rlp(new_epoch);
2360 }
2361 } else {
2362 use rayon::iter::{IntoParallelIterator, ParallelIterator};
2363
2364 let parent_span = tracing::Span::current();
2365 taken = taken
2366 .into_par_iter()
2367 .map(|(idx, mut subtrie)| {
2368 let _guard = parent_span.enter();
2369 subtrie.update_cached_rlp(new_epoch);
2370 (idx, subtrie)
2371 })
2372 .collect();
2373 }
2374 }
2375
2376 if taken.is_empty() && self.upper_arena[self.root].is_cached() {
2379 return;
2380 }
2381
2382 taken.sort_unstable_by_key(|(_, b)| Reverse(b.path));
2386
2387 self.buffers.cursor.reset(&self.upper_arena, self.root, Nibbles::default());
2388
2389 loop {
2390 let result = self.buffers.cursor.next(&mut self.upper_arena, |_, child| match child {
2391 ArenaSparseNode::Branch(_) | ArenaSparseNode::Subtrie(_) => !child.is_cached(),
2392 ArenaSparseNode::TakenSubtrie => true,
2393 _ => false,
2394 });
2395
2396 match result {
2397 NextResult::Done => break,
2398 NextResult::Branch => continue,
2399 NextResult::NonBranch => {}
2400 }
2401
2402 let head_idx = self.buffers.cursor.head().expect("cursor is non-empty").index;
2404
2405 if matches!(&self.upper_arena[head_idx], ArenaSparseNode::TakenSubtrie) {
2406 let (_, subtrie) = taken.pop().expect("taken subtries must not be exhausted");
2407 debug_assert_eq!(
2408 subtrie.path,
2409 self.buffers.cursor.head_path(),
2410 "taken subtrie path mismatch",
2411 );
2412 self.upper_arena[head_idx] = ArenaSparseNode::Subtrie(subtrie);
2413 }
2414
2415 self.update_upper_subtrie(head_idx, new_epoch);
2416 }
2417 }
2418
2419 fn get_leaf_value(&self, full_path: &Nibbles) -> Option<&Vec<u8>> {
2420 Self::get_leaf_value_in_arena(&self.upper_arena, self.root, full_path, 0)
2421 }
2422
2423 fn find_leaf(
2424 &self,
2425 full_path: &Nibbles,
2426 expected_value: Option<&Vec<u8>>,
2427 ) -> Result<LeafLookup, LeafLookupError> {
2428 Self::find_leaf_in_arena(&self.upper_arena, self.root, full_path, 0, expected_value)
2429 }
2430
2431 fn updates_ref(&self) -> Cow<'_, SparseTrieUpdates> {
2432 self.buffers
2433 .updates
2434 .as_ref()
2435 .map_or(Cow::Owned(SparseTrieUpdates::default()), Cow::Borrowed)
2436 }
2437
2438 fn take_updates(&mut self) -> SparseTrieUpdates {
2439 match self.buffers.updates.take() {
2440 Some(updates) => {
2441 self.buffers.updates = Some(SparseTrieUpdates::with_capacity(
2442 updates.updated_nodes.len(),
2443 updates.removed_nodes.len(),
2444 ));
2445 updates
2446 }
2447 None => SparseTrieUpdates::default(),
2448 }
2449 }
2450
2451 #[instrument(level = "trace", target = TRACE_TARGET, skip_all)]
2452 fn clear(&mut self) {
2453 self.upper_arena = NodeArena::new();
2454 self.root = self
2455 .upper_arena
2456 .insert(ArenaSparseNode::EmptyRoot { state: ArenaSparseNodeState::Revealed });
2457 self.buffers.clear();
2458 }
2459
2460 #[instrument(
2461 level = "trace",
2462 target = TRACE_TARGET,
2463 skip_all,
2464 fields(prune_before = prune_before.get()),
2465 )]
2466 fn prune(&mut self, prune_before: TrieNodeEpoch) -> usize {
2467 assert!(self.root_epoch().is_some(), "prune cannot run on a dirty trie");
2468
2469 if !matches!(&self.upper_arena[self.root], ArenaSparseNode::Branch(_)) {
2471 return 0;
2472 }
2473
2474 let threshold = self.parallelism_thresholds.min_leaves_for_prune;
2475
2476 let mut cursor = mem::take(&mut self.buffers.cursor);
2477 cursor.reset(&self.upper_arena, self.root, Nibbles::default());
2478
2479 let mut taken: Vec<(Index, Box<ArenaSparseSubtrie>)> = Vec::new();
2481
2482 let mut pruned = 0;
2483
2484 loop {
2485 let result = cursor.next(&mut self.upper_arena, |_, child| {
2486 matches!(
2487 child,
2488 ArenaSparseNode::Branch(_) |
2489 ArenaSparseNode::Subtrie(_) |
2490 ArenaSparseNode::Leaf { .. }
2491 )
2492 });
2493
2494 if matches!(result, NextResult::Done) {
2495 break
2496 }
2497
2498 let head_idx = cursor.head().expect("cursor is non-empty").index;
2499
2500 match &self.upper_arena[head_idx] {
2501 ArenaSparseNode::Branch(_) | ArenaSparseNode::Leaf { .. } => {
2502 if cursor.depth() == 0 {
2504 continue;
2505 }
2506
2507 let node_epoch = self.upper_arena[head_idx]
2508 .state_ref()
2509 .and_then(ArenaSparseNodeState::cached_epoch)
2510 .expect("prune must run after hashing");
2511 if !node_epoch.should_prune(prune_before) {
2512 continue;
2513 }
2514
2515 Self::remove_pruned_node(&mut self.upper_arena, &mut cursor);
2516 pruned += 1;
2517 }
2518 ArenaSparseNode::Subtrie(_) => {
2519 let root_epoch = self.upper_arena[head_idx]
2520 .state_ref()
2521 .and_then(ArenaSparseNodeState::cached_epoch)
2522 .expect("prune must run after hashing");
2523 if root_epoch.should_prune(prune_before) {
2524 let removed = Self::remove_pruned_node(&mut self.upper_arena, &mut cursor);
2525 let ArenaSparseNode::Subtrie(s) = &removed else { unreachable!() };
2526 pruned += s.arena.len();
2527 self.recycle_subtrie(removed);
2528 continue;
2529 }
2530
2531 let ArenaSparseNode::Subtrie(subtrie) = &self.upper_arena[head_idx] else {
2532 unreachable!()
2533 };
2534 if subtrie.num_leaves >= threshold {
2535 let ArenaSparseNode::Subtrie(subtrie) = mem::replace(
2536 &mut self.upper_arena[head_idx],
2537 ArenaSparseNode::TakenSubtrie,
2538 ) else {
2539 unreachable!()
2540 };
2541 taken.push((head_idx, subtrie));
2542 } else {
2543 let ArenaSparseNode::Subtrie(subtrie) = &mut self.upper_arena[head_idx]
2544 else {
2545 unreachable!()
2546 };
2547 pruned += subtrie.prune(prune_before);
2548 }
2549 }
2550 _ => unreachable!("NonBranch in prune walk must be Subtrie, Leaf, or Branch"),
2551 }
2552 }
2553
2554 self.buffers.cursor = cursor;
2555
2556 if !taken.is_empty() {
2557 if taken.len() == 1 {
2559 let (_, ref mut subtrie) = taken[0];
2560 pruned += subtrie.prune(prune_before);
2561 } else {
2562 use rayon::iter::{IntoParallelRefMutIterator, ParallelIterator};
2563
2564 let parent_span = tracing::Span::current();
2565 pruned += taken
2566 .par_iter_mut()
2567 .map(|(_, subtrie)| {
2568 let _guard = parent_span.enter();
2569 let _span = tracing::trace_span!(
2570 target: TRACE_TARGET,
2571 "subtrie_prune",
2572 subtrie = ?subtrie.path,
2573 )
2574 .entered();
2575
2576 subtrie.prune(prune_before)
2577 })
2578 .sum::<usize>();
2579 }
2580
2581 for (child_idx, subtrie) in taken {
2583 self.restore_taken_subtrie(child_idx, subtrie);
2584 }
2585 }
2586
2587 if pruned > 0 {
2588 compact_arena(&mut self.upper_arena, &mut self.root);
2589 }
2590
2591 pruned
2592 }
2593
2594 #[instrument(
2595 level = "trace",
2596 target = TRACE_TARGET,
2597 skip_all,
2598 fields(num_updates = updates.len()),
2599 )]
2600 fn update_leaves(
2601 &mut self,
2602 updates: &mut B256Map<LeafUpdate>,
2603 mut proof_required_fn: impl FnMut(B256, ProofV2TargetParent),
2604 ) -> SparseTrieResult<()> {
2605 if updates.is_empty() {
2606 return Ok(());
2607 }
2608
2609 let mut sorted: Vec<_> =
2611 updates.drain().map(|(key, update)| (key, Nibbles::unpack(key), update)).collect();
2612 sorted.sort_unstable_by_key(|entry| entry.1);
2613
2614 let threshold = self.parallelism_thresholds.min_updates;
2615 let parallelize_distributed_updates = sorted.len() >= threshold.saturating_mul(4);
2616
2617 let mut cursor = mem::take(&mut self.buffers.cursor);
2618 cursor.reset(&self.upper_arena, self.root, Nibbles::default());
2619
2620 let mut taken: Vec<(Index, Box<ArenaSparseSubtrie>, core::ops::Range<usize>)> = Vec::new();
2622
2623 let mut update_idx = 0;
2624 while update_idx < sorted.len() {
2625 let (key, ref full_path, ref update) = sorted[update_idx];
2626
2627 let find_result = cursor.seek(&mut self.upper_arena, full_path);
2628
2629 match find_result {
2630 SeekResult::Blinded => {
2632 let logical_len = cursor.head_logical_branch_path_len(&self.upper_arena);
2633 let parent = ProofV2TargetParent::new(logical_len);
2634 trace!(target: TRACE_TARGET, ?key, ?parent, "Update hit blinded node, requesting proof");
2635 proof_required_fn(key, parent);
2636 updates.insert(key, update.clone());
2637 }
2638 SeekResult::RevealedSubtrie => {
2640 let child_idx = cursor.head().expect("cursor is non-empty").index;
2641 let subtrie_root_path = cursor.head_path();
2642
2643 let subtrie_start = update_idx;
2644 while update_idx < sorted.len() &&
2645 sorted[update_idx].1.starts_with(&subtrie_root_path)
2646 {
2647 update_idx += 1;
2648 }
2649
2650 let subtrie_updates = &sorted[subtrie_start..update_idx];
2651
2652 if let Some(proof) = Self::check_subtrie_collapse_needs_proof(
2656 &self.upper_arena,
2657 &cursor,
2658 subtrie_updates,
2659 ) {
2660 trace!(target: TRACE_TARGET, proof_key = ?proof.key, proof_parent = ?proof.parent, "Subtrie collapse would need blinded sibling, requesting proof");
2661 proof_required_fn(proof.key, proof.parent);
2662 for &(key, _, ref update) in subtrie_updates {
2663 updates.insert(key, update.clone());
2664 }
2665 continue;
2667 }
2668
2669 let num_subtrie_updates = update_idx - subtrie_start;
2670
2671 let all_removals = subtrie_updates
2675 .iter()
2676 .filter(|(_, _, u)| u.is_changed())
2680 .all(|(_, _, u)| matches!(u, LeafUpdate::Changed(v) if v.is_empty()));
2681 let subtrie_num_leaves = match &self.upper_arena[child_idx] {
2682 ArenaSparseNode::Subtrie(s) => s.num_leaves,
2683 _ => 0,
2684 };
2685 let might_empty_subtrie =
2686 all_removals && num_subtrie_updates as u64 >= subtrie_num_leaves;
2687
2688 if (num_subtrie_updates >= threshold || parallelize_distributed_updates) &&
2689 !might_empty_subtrie
2690 {
2691 trace!(target: TRACE_TARGET, ?subtrie_root_path, num_subtrie_updates, "Taking subtrie for parallel update");
2693 let ArenaSparseNode::Subtrie(subtrie) = mem::replace(
2694 &mut self.upper_arena[child_idx],
2695 ArenaSparseNode::TakenSubtrie,
2696 ) else {
2697 unreachable!()
2698 };
2699 taken.push((child_idx, subtrie, subtrie_start..update_idx));
2700 } else {
2701 trace!(target: TRACE_TARGET, ?subtrie_root_path, num_subtrie_updates, "Updating subtrie inline");
2703 let ArenaSparseNode::Subtrie(subtrie) = &mut self.upper_arena[child_idx]
2704 else {
2705 unreachable!()
2706 };
2707
2708 subtrie.update_leaves(subtrie_updates);
2709
2710 for (target_idx, proof) in subtrie.required_proofs.drain(..) {
2711 proof_required_fn(proof.key, proof.parent);
2712 let (key, _, ref update) = subtrie_updates[target_idx];
2713 updates.insert(key, update.clone());
2714 }
2715
2716 self.maybe_unwrap_subtrie(&mut cursor);
2718 }
2719
2720 continue;
2722 }
2723 find_result @ (SeekResult::EmptyRoot |
2725 SeekResult::RevealedLeaf |
2726 SeekResult::Diverged |
2727 SeekResult::NoChild { .. }) => match update {
2728 LeafUpdate::Changed(v) if !v.is_empty() => {
2729 let (result, _deltas) = Self::upsert_leaf(
2730 &mut self.upper_arena,
2731 &mut cursor,
2732 &mut self.root,
2733 full_path,
2734 v,
2735 find_result,
2736 );
2737 match result {
2738 UpsertLeafResult::NewChild => {
2739 if Self::should_be_subtrie(cursor.head_path_len()) {
2740 let head_idx =
2743 cursor.head().expect("cursor is non-empty").index;
2744 self.maybe_wrap_in_subtrie(head_idx, &cursor.head_path());
2745 } else {
2746 self.maybe_wrap_branch_children(&cursor);
2750 }
2751 }
2752 UpsertLeafResult::NewLeaf => {
2753 self.maybe_wrap_branch_children(&cursor);
2756 }
2757 UpsertLeafResult::Updated => {}
2758 }
2759 }
2760 LeafUpdate::Changed(_) => {
2761 let (result, _deltas) = Self::remove_leaf(
2762 &mut self.upper_arena,
2763 &mut cursor,
2764 &mut self.root,
2765 key,
2766 full_path,
2767 find_result,
2768 &mut self.buffers.updates,
2769 );
2770 match result {
2771 RemoveLeafResult::NeedsProof { key, proof_key, parent } => {
2772 proof_required_fn(proof_key, parent);
2773 let update =
2774 mem::replace(&mut sorted[update_idx].2, LeafUpdate::Touched);
2775 updates.insert(key, update);
2776 }
2777 RemoveLeafResult::Removed => {
2778 self.maybe_collapse_or_remove_branch(&mut cursor);
2787 let head_idx = cursor
2788 .head()
2789 .expect("cursor always has root after collapse")
2790 .index;
2791 self.maybe_wrap_in_subtrie(head_idx, &cursor.head_path());
2792 }
2793 RemoveLeafResult::NotFound => {}
2794 }
2795 }
2796 LeafUpdate::Touched => {}
2797 },
2798 }
2799
2800 update_idx += 1;
2801 }
2802
2803 cursor.drain(&mut self.upper_arena);
2805 self.buffers.cursor = cursor;
2806
2807 if taken.is_empty() {
2808 #[cfg(all(debug_assertions, not(feature = "dst")))]
2809 self.debug_assert_subtrie_structure();
2810
2811 return Ok(());
2812 }
2813
2814 if taken.len() == 1 {
2816 let (_, ref mut subtrie, ref range) = taken[0];
2817 subtrie.update_leaves(&sorted[range.clone()]);
2818 } else {
2819 use rayon::iter::{IntoParallelRefMutIterator, ParallelIterator};
2820
2821 let parent_span = tracing::Span::current();
2822 taken.par_iter_mut().for_each(|(_, subtrie, range)| {
2823 let _guard = parent_span.enter();
2824 subtrie.update_leaves(&sorted[range.clone()]);
2825 });
2826 }
2827
2828 let taken_paths: Vec<Nibbles> = taken.iter().map(|(_, s, _)| s.path).collect();
2831 for (child_idx, mut subtrie, range) in taken {
2832 let subtrie_updates = &sorted[range];
2833 for (target_idx, proof) in subtrie.required_proofs.drain(..) {
2834 proof_required_fn(proof.key, proof.parent);
2835 let (key, _, ref update) = subtrie_updates[target_idx];
2836 updates.insert(key, update.clone());
2837 }
2838
2839 self.restore_taken_subtrie(child_idx, subtrie);
2841 }
2842
2843 {
2849 let mut cursor = mem::take(&mut self.buffers.cursor);
2850 cursor.reset(&self.upper_arena, self.root, Nibbles::default());
2851
2852 for path in &taken_paths {
2853 let find_result = cursor.seek(&mut self.upper_arena, path);
2854 match find_result {
2855 SeekResult::RevealedSubtrie => {
2856 debug_assert!(
2857 {
2858 let head_idx = cursor.head().expect("cursor is non-empty").index;
2859 !matches!(
2860 &self.upper_arena[head_idx],
2861 ArenaSparseNode::Subtrie(s) if matches!(s.arena[s.root], ArenaSparseNode::EmptyRoot { .. })
2862 )
2863 },
2864 "taken subtrie became EmptyRoot — should have been forced inline"
2865 );
2866
2867 cursor.pop(&mut self.upper_arena);
2868
2869 self.maybe_collapse_or_remove_branch(&mut cursor);
2873 }
2874 _ => {
2875 }
2878 }
2879 }
2880
2881 cursor.drain(&mut self.upper_arena);
2882 self.buffers.cursor = cursor;
2883 }
2884
2885 #[cfg(all(debug_assertions, not(feature = "dst")))]
2886 self.debug_assert_subtrie_structure();
2887
2888 Ok(())
2889 }
2890}
2891
2892#[cfg(test)]
2893mod tests {
2894 use super::*;
2895 use crate::{
2896 ArenaParallelSparseTrie, ArenaParallelismThresholds, LeafUpdate, SparseTrie, TrieNodeEpoch,
2897 };
2898 use alloy_primitives::{map::B256Map, B256, U256};
2899 use rand::{seq::SliceRandom, Rng, SeedableRng};
2900 use reth_trie::test_utils::TrieTestHarness;
2901 use reth_trie_common::ProofV2Target;
2902 use std::collections::BTreeMap;
2903 use tracing::{info, trace};
2904
2905 const fn epoch(value: u64) -> TrieNodeEpoch {
2906 TrieNodeEpoch::new(value)
2907 }
2908
2909 #[test]
2910 fn unsupported_root_preserves_update_retention_and_allows_valid_root() {
2911 let mut trie = ArenaParallelSparseTrie::default();
2912 let root = TrieNodeV2::Extension(reth_trie_common::ExtensionNode::new(
2913 Nibbles::from_nibbles([1]),
2914 RlpNode::word_rlp(&B256::ZERO),
2915 ));
2916 let error = trie.set_root(root, None, true).unwrap_err();
2917 assert!(
2918 matches!(error.kind(), SparseTrieErrorKind::Reveal { path, .. } if path.is_empty())
2919 );
2920 assert!(trie.buffers.updates.is_none());
2921
2922 trie.set_root(TrieNodeV2::EmptyRoot, None, true).unwrap();
2923 assert_eq!(trie.root(epoch(1)), EMPTY_ROOT_HASH);
2924 assert!(trie.buffers.updates.is_some());
2925 }
2926
2927 #[test]
2928 fn pruning_pops_cursor_before_reusing_node_slot() {
2929 use super::{
2930 ArenaCursor, ArenaSparseNode, ArenaSparseNodeBranch, ArenaSparseNodeState, BranchChild,
2931 NextResult, NodeArena,
2932 };
2933 use reth_trie_common::RlpNode;
2934
2935 let mut arena = NodeArena::new();
2936 let rlp = RlpNode::word_rlp(&B256::repeat_byte(1));
2937 let state = ArenaSparseNodeState::Cached { rlp_node: rlp.clone(), epoch: epoch(0) };
2938 let leaf =
2939 ArenaSparseNode::Leaf { state: state.clone(), key: Default::default(), value: vec![1] };
2940 let first = arena.insert(leaf.clone());
2941 let second = arena.insert(leaf);
2942 let root = arena.insert(ArenaSparseNode::Branch(ArenaSparseNodeBranch {
2943 state: state.clone(),
2944 children: [BranchChild::revealed(first), BranchChild::revealed(second)]
2945 .into_iter()
2946 .collect(),
2947 state_mask: alloy_trie::TrieMask::new(3),
2948 short_key: Default::default(),
2949 branch_masks: Default::default(),
2950 }));
2951 let mut cursor = ArenaCursor::default();
2952 cursor.reset(&arena, root, Default::default());
2953 assert!(matches!(cursor.next(&mut arena, |_, _| true), NextResult::NonBranch));
2954 assert_eq!(cursor.head().unwrap().index, first);
2955
2956 ArenaParallelSparseTrie::remove_pruned_node(&mut arena, &mut cursor);
2957 assert_eq!(cursor.head().unwrap().index, root);
2958 let reused = arena.insert(ArenaSparseNode::Leaf {
2959 state: ArenaSparseNodeState::Dirty,
2960 key: Default::default(),
2961 value: vec![2],
2962 });
2963 assert_eq!(reused, first);
2964
2965 assert!(matches!(cursor.next(&mut arena, |_, _| true), NextResult::NonBranch));
2966 assert_eq!(cursor.head().unwrap().index, second);
2967 assert!(matches!(cursor.next(&mut arena, |_, _| true), NextResult::Branch));
2968 assert_eq!(arena[root].state_ref(), Some(&state));
2969 assert_eq!(arena.blinded(arena[root].branch_ref().children[0]), &rlp);
2970 assert!(matches!(cursor.next(&mut arena, |_, _| true), NextResult::Done));
2971 }
2972
2973 struct ArenaTrieTestHarness {
2978 inner: TrieTestHarness,
2980 }
2981
2982 impl std::ops::Deref for ArenaTrieTestHarness {
2983 type Target = TrieTestHarness;
2984 fn deref(&self) -> &Self::Target {
2985 &self.inner
2986 }
2987 }
2988
2989 impl std::ops::DerefMut for ArenaTrieTestHarness {
2990 fn deref_mut(&mut self) -> &mut Self::Target {
2991 &mut self.inner
2992 }
2993 }
2994
2995 impl ArenaTrieTestHarness {
2996 fn new(storage: BTreeMap<B256, U256>) -> Self {
2998 Self { inner: TrieTestHarness::new(storage) }
2999 }
3000
3001 fn assert_changes(
3005 &self,
3006 apst: &mut ArenaParallelSparseTrie,
3007 changes: BTreeMap<B256, U256>,
3008 ) {
3009 let (expected_root, mut expected_trie_updates) = if changes.is_empty() {
3011 (self.original_root(), Default::default())
3012 } else {
3013 self.get_root_with_updates(&changes)
3014 };
3015
3016 self.minimize_trie_updates(&mut expected_trie_updates);
3017
3018 let mut leaf_updates: B256Map<LeafUpdate> = changes
3021 .iter()
3022 .map(|(&slot, &value)| {
3023 let rlp_value = if value.is_zero() {
3024 Vec::new()
3025 } else {
3026 alloy_rlp::encode_fixed_size(&value).to_vec()
3027 };
3028 (slot, LeafUpdate::Changed(rlp_value))
3029 })
3030 .collect();
3031
3032 loop {
3035 let mut targets: Vec<ProofV2Target> = Vec::new();
3036 apst.update_leaves(&mut leaf_updates, |key, parent| {
3037 targets.push(ProofV2Target::new(key).with_parent(parent));
3038 })
3039 .expect("update_leaves should succeed");
3040
3041 if targets.is_empty() {
3042 break;
3043 }
3044
3045 let (mut proof_nodes, _) = self.proof_v2(&mut targets);
3046 apst.reveal_nodes(&mut proof_nodes).expect("reveal_nodes should succeed");
3047 }
3048
3049 let actual_root = apst.root(epoch(0));
3051 let mut actual_updates = apst.take_updates();
3052
3053 actual_updates.updated_nodes.retain(|path, node| {
3056 self.storage_trie_updates().storage_nodes.get(path) != Some(node)
3057 });
3058 actual_updates
3059 .removed_nodes
3060 .retain(|path| self.storage_trie_updates().storage_nodes.contains_key(path));
3061
3062 let mut expected_updated_nodes =
3063 expected_trie_updates.storage_nodes.into_iter().collect::<Vec<_>>();
3064 let mut actual_updated_nodes =
3065 actual_updates.updated_nodes.into_iter().collect::<Vec<_>>();
3066 expected_updated_nodes.sort();
3067 actual_updated_nodes.sort();
3068 pretty_assertions::assert_eq!(
3069 expected_updated_nodes,
3070 actual_updated_nodes,
3071 "updated nodes mismatch"
3072 );
3073
3074 let mut expected_removed_nodes =
3075 expected_trie_updates.removed_nodes.into_iter().collect::<Vec<_>>();
3076 let mut actual_removed_nodes =
3077 actual_updates.removed_nodes.into_iter().collect::<Vec<_>>();
3078 expected_removed_nodes.sort();
3079 actual_removed_nodes.sort();
3080 pretty_assertions::assert_eq!(
3081 expected_removed_nodes,
3082 actual_removed_nodes,
3083 "removed nodes mismatch"
3084 );
3085 assert_eq!(expected_root, actual_root, "storage root mismatch");
3086 }
3087 }
3088
3089 use proptest::prelude::*;
3090 use proptest_arbitrary_interop::arb;
3091
3092 fn build_changeset(
3099 base: &BTreeMap<B256, U256>,
3100 new_keys: BTreeMap<B256, U256>,
3101 overlap_pct: f64,
3102 delete_pct: f64,
3103 rng: &mut rand::rngs::StdRng,
3104 ) -> BTreeMap<B256, U256> {
3105 let num_overlap = (base.len() as f64 * overlap_pct) as usize;
3106 let num_delete = (num_overlap as f64 * delete_pct) as usize;
3107
3108 let mut all_keys: Vec<B256> = base.keys().copied().collect();
3109 all_keys.shuffle(rng);
3110 let overlap_keys = &all_keys[..num_overlap];
3111
3112 let mut changeset = new_keys;
3113 for (i, &key) in overlap_keys.iter().enumerate() {
3114 let value =
3115 if i < num_delete { U256::ZERO } else { U256::from(rng.random::<u64>() | 1) };
3116 changeset.entry(key).or_insert(value);
3117 }
3118 changeset
3119 }
3120
3121 proptest! {
3122 #![proptest_config(ProptestConfig::with_cases(1000))]
3123 #[test]
3124 fn arena_trie_proptest(
3125 initial in proptest::collection::btree_map(arb::<B256>(), arb::<U256>(), 0..=100usize),
3126 changeset1_new_keys in proptest::collection::btree_map(arb::<B256>(), arb::<U256>(), 0..=30usize),
3127 changeset2_new_keys in proptest::collection::btree_map(arb::<B256>(), arb::<U256>(), 0..=30usize),
3128 overlap_pct in 0.0..=0.5f64,
3129 delete_pct in 0.0..=0.33f64, shuffle_seed in arb::<u64>(),
3131 ) {
3132 reth_tracing::init_test_tracing();
3133 info!(target: TRACE_TARGET, ?shuffle_seed, "PROPTEST START");
3134
3135 let initial: BTreeMap<B256, U256> = initial.into_iter()
3137 .filter(|(_, v)| !v.is_zero())
3138 .collect();
3139
3140 let mut rng = rand::rngs::StdRng::seed_from_u64(shuffle_seed);
3141
3142 let changeset1 = build_changeset(&initial, changeset1_new_keys, overlap_pct, delete_pct, &mut rng);
3143 for (i, (k, v)) in changeset1.iter().enumerate() {
3144 trace!(target: TRACE_TARGET, ?i, ?k, ?v, "Changeset 1 entry");
3145 }
3146
3147 let mut harness = ArenaTrieTestHarness::new(initial);
3148
3149 let root_node = harness.root_node();
3151 let mut apst = ArenaParallelSparseTrie::default().with_parallelism_thresholds(
3152 ArenaParallelismThresholds {
3153 min_dirty_leaves: 3,
3154 min_revealed_nodes: 3,
3155 min_updates: 3,
3156 min_leaves_for_prune: 3,
3157 },
3158 );
3159 apst.set_root(root_node.node, root_node.masks, true).expect("set_root should succeed");
3160
3161 harness.assert_changes(&mut apst, changeset1.clone());
3162
3163 harness.apply_changeset(changeset1);
3165
3166 apst.prune(epoch(1));
3169
3170 let changeset2 = build_changeset(harness.storage(), changeset2_new_keys, overlap_pct, delete_pct, &mut rng);
3171 for (i, (k, v)) in changeset2.iter().enumerate() {
3172 trace!(target: TRACE_TARGET, ?i, ?k, ?v, "Changeset 2 entry");
3173 }
3174
3175 harness.assert_changes(&mut apst, changeset2);
3176 }
3177 }
3178}