1use super::{iter_sub_trie_targets, node::*, value::*, SubTrieTargets, TRACE_TARGET};
12use crate::{
13 hashed_cursor::{HashedCursor, HashedStorageCursor},
14 trie_cursor::{depth_first, TrieCursor, TrieStorageCursor},
15};
16use alloy_primitives::{keccak256, B256, U256};
17use alloy_rlp::Encodable;
18use alloy_trie::{BranchNodeCompact, TrieMask};
19use reth_execution_errors::trie::StateProofError;
20use reth_trie_common::{
21 prefix_set::PrefixSet, BranchNodeMasks, BranchNodeRef, BranchNodeV2, Nibbles, ProofTrieNodeV2,
22 ProofV2Target, RlpNode, TrieNodeV2,
23};
24use std::{cmp::Ordering, sync::Arc};
25use tracing::{error, instrument, trace};
26
27const RLP_ENCODE_BUF_SIZE: usize = 1024;
29
30#[derive(Debug)]
42pub struct ProofCalculator<TC, HC, VE: LeafValueEncoder> {
43 trie_cursor: TC,
45 hashed_cursor: HC,
47 branch_stack: Vec<ProofTrieBranch>,
50 branch_path: Nibbles,
52 child_stack: Vec<ProofTrieBranchChild<VE::DeferredEncoder>>,
69 cached_branch_stack: Vec<(Nibbles, BranchNodeCompact)>,
73 retained_proofs: Vec<ProofTrieNodeV2>,
76 rlp_nodes_bufs: Vec<Vec<RlpNode>>,
82 rlp_encode_buf: Vec<u8>,
84 prefix_set: PrefixSet,
86}
87
88impl<TC, HC, VE: LeafValueEncoder> ProofCalculator<TC, HC, VE> {
89 pub fn new(trie_cursor: TC, hashed_cursor: HC) -> Self {
91 Self {
92 trie_cursor,
93 hashed_cursor,
94 branch_stack: Vec::<_>::with_capacity(64),
95 branch_path: Nibbles::new(),
96 child_stack: Vec::<_>::with_capacity(64),
97 cached_branch_stack: Vec::<_>::with_capacity(64),
98 retained_proofs: Vec::<_>::with_capacity(32),
99 rlp_nodes_bufs: Vec::<_>::with_capacity(8),
100 rlp_encode_buf: Vec::<_>::with_capacity(RLP_ENCODE_BUF_SIZE),
101 prefix_set: PrefixSet::default(),
102 }
103 }
104
105 pub fn with_prefix_set(mut self, prefix_set: PrefixSet) -> Self {
110 self.prefix_set = prefix_set;
111 self
112 }
113
114 pub(super) fn set_prefix_set(&mut self, prefix_set: PrefixSet) {
116 self.prefix_set = prefix_set;
117 }
118}
119
120impl<TC, HC, VE> ProofCalculator<TC, HC, VE>
121where
122 TC: TrieCursor,
123 HC: HashedCursor,
124 VE: LeafValueEncoder<Value = HC::Value>,
125{
126 fn take_rlp_nodes_buf(&mut self) -> Vec<RlpNode> {
131 self.rlp_nodes_bufs
132 .pop()
133 .map(|mut buf| {
134 buf.clear();
135 buf
136 })
137 .unwrap_or_else(|| Vec::with_capacity(16))
138 }
139
140 #[inline]
147 const fn maybe_parent_nibble(&self) -> usize {
148 !self.branch_stack.is_empty() as usize
149 }
150
151 #[instrument(
187 target = TRACE_TARGET,
188 level = "trace",
189 skip_all,
190 fields(?path, ?check_parent_path),
191 ret,
192 )]
193 fn should_retain<'a>(
194 &self,
195 targets: &mut Option<TargetsCursor<'a>>,
196 path: &Nibbles,
197 check_parent_path: bool,
198 ) -> bool {
199 let Some(targets) = targets.as_mut() else { return false };
201
202 let (mut lower, mut upper) = targets.current();
203
204 loop {
205 if lower.key_nibbles.starts_with(path) {
226 let is_below_parent = |target: &ProofV2Target| {
227 target.parent.path_len().is_none_or(|len| path.len() > len)
228 };
229 return !check_parent_path ||
230 (is_below_parent(lower) ||
231 targets
232 .skip_iter()
233 .take_while(|target| target.key_nibbles.starts_with(path))
234 .any(is_below_parent) ||
235 targets
236 .rev_iter()
237 .take_while(|target| target.key_nibbles.starts_with(path))
238 .any(is_below_parent))
239 }
240
241 if upper
244 .is_some_and(|upper| depth_first::cmp(path, &upper.key_nibbles) != Ordering::Less)
245 {
246 (lower, upper) = targets.next();
247 trace!(target: TRACE_TARGET, target = ?lower, "upper target <= path, next target");
248 } else {
249 return false
250 }
251 }
252 }
253
254 fn commit_child(
263 &mut self,
264 child_path: Nibbles,
265 child: ProofTrieBranchChild<VE::DeferredEncoder>,
266 ) -> Result<RlpNode, StateProofError> {
267 if matches!(&child, ProofTrieBranchChild::RlpNode { .. }) {
269 self.rlp_encode_buf.clear();
270 return child.into_rlp(&mut self.rlp_encode_buf).map(|(node, _)| node)
271 }
272
273 trace!(target: TRACE_TARGET, ?child_path, "Retaining child");
274
275 self.rlp_encode_buf.clear();
280 let proof_node = child.into_proof_trie_node(child_path, &mut self.rlp_encode_buf)?;
281
282 self.rlp_encode_buf.clear();
285 proof_node.node.encode(&mut self.rlp_encode_buf);
286
287 self.retained_proofs.push(proof_node);
288 Ok(RlpNode::from_rlp(&self.rlp_encode_buf))
289 }
290
291 #[inline]
294 fn child_path_at(&self, nibble: u8) -> Nibbles {
295 let mut child_path = self.branch_path;
296 debug_assert!(child_path.len() < 64);
297 child_path.push_unchecked(nibble);
298 child_path
299 }
300
301 #[inline]
307 fn highest_set_nibble(mask: TrieMask) -> u8 {
308 debug_assert!(!mask.is_empty());
309 (u16::BITS - mask.leading_zeros() - 1) as u8
310 }
311
312 fn last_child_path(&self) -> Option<Nibbles> {
315 let Some(branch) = self.branch_stack.last() else {
317 return Some(Nibbles::new());
318 };
319
320 (!branch.state_mask.is_empty())
321 .then(|| self.child_path_at(Self::highest_set_nibble(branch.state_mask)))
322 }
323
324 #[instrument(
335 target = TRACE_TARGET,
336 level = "trace",
337 skip_all,
338 fields(child_path = ?self.last_child_path()),
339 )]
340 fn commit_last_child<'a>(
341 &mut self,
342 targets: &mut Option<TargetsCursor<'a>>,
343 ) -> Result<(), StateProofError> {
344 if matches!(self.child_stack.last(), Some(ProofTrieBranchChild::RlpNode { .. })) {
345 trace!(target: TRACE_TARGET, "Last child already committed, leaving stack unchanged");
346 return Ok(())
347 }
348
349 let Some(child_path) = self.last_child_path() else { return Ok(()) };
350 let child =
351 self.child_stack.pop().expect("child_stack can't be empty if there's a child path");
352
353 if self.should_retain(targets, &child_path, true) {
356 let (hash_mask_bit, tree_mask_bit) = child.mask_bits();
357 let child_rlp_node = self.commit_child(child_path, child)?;
358 trace!(target: TRACE_TARGET, ?child_rlp_node, "Pushing committed child RlpNode onto stack");
359 self.child_stack.push(ProofTrieBranchChild::RlpNode {
360 node: child_rlp_node,
361 short_key: Nibbles::new(),
362 hash_mask_bit,
363 tree_mask_bit,
364 });
365 } else {
366 trace!(target: TRACE_TARGET, "Pushing uncommitted child onto stack");
367 self.child_stack.push(child);
368 }
369
370 Ok(())
371 }
372
373 fn push_child<'a>(
376 &mut self,
377 targets: &mut Option<TargetsCursor<'a>>,
378 mut child: ProofTrieBranchChild<VE::DeferredEncoder>,
379 ) -> Result<(), StateProofError> {
380 let path = *child.short_key();
381
382 loop {
383 trace!(
384 target: TRACE_TARGET,
385 ?path,
386 branch_stack_len = ?self.branch_stack.len(),
387 branch_path = ?self.branch_path,
388 child_stack_len = ?self.child_stack.len(),
389 "push_child: loop",
390 );
391
392 let (nibble, short_key) = match self.branch_stack.last().map(|branch| branch.state_mask)
395 {
396 None if self.child_stack.is_empty() => {
397 self.child_stack.push(child);
399 return Ok(())
400 }
401 None => {
402 debug_assert_eq!(self.child_stack.len(), 1);
404 debug_assert!(!self
405 .child_stack
406 .last()
407 .expect("already checked for emptiness")
408 .short_key()
409 .is_empty());
410 self.push_new_branch(path)
411 }
412 Some(state_mask) => {
413 let common_prefix_len = self.branch_path.common_prefix_length(&path);
415
416 if common_prefix_len < self.branch_path.len() {
419 self.pop_branch(targets)?;
420 continue
421 }
422
423 let nibble = path.get_unchecked(common_prefix_len);
426 if state_mask.is_bit_set(nibble) {
427 self.push_new_branch(path)
428 } else {
429 (nibble, trim_nibbles_prefix(&path, common_prefix_len + 1))
430 }
431 }
432 };
433
434 child.trim_short_key_prefix(path.len() - short_key.len());
436
437 self.commit_last_child(targets)?;
440
441 let branch = self.branch_stack.last_mut().expect("branch_stack cannot be empty");
442 debug_assert!(!branch.state_mask.is_bit_set(nibble));
443
444 branch.state_mask.set_bit(nibble);
447
448 self.child_stack.push(child);
450 return Ok(())
451 }
452 }
453
454 fn push_new_branch(&mut self, new_child_path: Nibbles) -> (u8, Nibbles) {
461 let first_child_path = self
464 .last_child_path()
465 .expect("push_new_branch requires the current branch to have a child");
466
467 let new_child_short_key = trim_nibbles_prefix(&new_child_path, first_child_path.len());
470 let first_child_short_key = *self
471 .child_stack
472 .last()
473 .expect("push_new_branch can't be called with empty child_stack")
474 .short_key();
475 debug_assert!(!first_child_short_key.is_empty());
476
477 let common_prefix_len = first_child_short_key.common_prefix_length(&new_child_short_key);
480 let first_child_nibble = first_child_short_key.get_unchecked(common_prefix_len);
481 let new_child_nibble = new_child_short_key.get_unchecked(common_prefix_len);
482
483 let new_child_short_key = trim_nibbles_prefix(&new_child_short_key, common_prefix_len + 1);
485
486 let branch_path_len = first_child_path.len() + common_prefix_len;
488 self.branch_path = new_child_path.slice_unchecked(0, branch_path_len);
489
490 let first_child = self
493 .child_stack
494 .last_mut()
495 .expect("push_new_branch can't be called with empty child_stack");
496 first_child.trim_short_key_prefix(common_prefix_len + 1);
497
498 self.branch_stack.push(ProofTrieBranch {
502 ext_len: common_prefix_len as u8,
503 state_mask: TrieMask::from_nibble(first_child_nibble),
504 });
505
506 trace!(
507 target: TRACE_TARGET,
508 ?new_child_path,
509 ext_len = ?common_prefix_len,
510 ?first_child_nibble,
511 branch_path = ?self.branch_path,
512 "Pushed new branch",
513 );
514
515 (new_child_nibble, new_child_short_key)
516 }
517
518 #[instrument(target = TRACE_TARGET, level = "trace", skip_all)]
526 fn pop_branch<'a>(
527 &mut self,
528 targets: &mut Option<TargetsCursor<'a>>,
529 ) -> Result<(), StateProofError> {
530 trace!(
531 target: TRACE_TARGET,
532 branch = ?self.branch_stack.last(),
533 branch_path = ?self.branch_path,
534 child_stack_len = ?self.child_stack.len(),
535 "called",
536 );
537
538 self.commit_last_child(targets)?;
541
542 let mut rlp_nodes_buf = self.take_rlp_nodes_buf();
543 let mut masks = BranchNodeMasks::default();
544 let branch = self.branch_stack.pop().expect("branch_stack cannot be empty");
545
546 let num_children = branch.state_mask.count_ones() as usize;
549 debug_assert!(
550 self.child_stack.len() >= num_children,
551 "Stack is missing necessary children ({num_children:?})"
552 );
553 debug_assert!(
554 num_children >= 2,
555 "A branch must have at least two children, got {num_children}"
556 );
557
558 rlp_nodes_buf.reserve(num_children);
560 for (nibble, child) in branch
561 .state_mask
562 .iter()
563 .zip(self.child_stack.drain(self.child_stack.len() - num_children..))
564 {
565 let (hash_mask_bit, tree_mask_bit) = child.mask_bits();
566 masks.set_child_bits(nibble, hash_mask_bit, tree_mask_bit);
567
568 self.rlp_encode_buf.clear();
569 let (child_rlp_node, freed_buf) = child.into_rlp(&mut self.rlp_encode_buf)?;
570 if let Some(buf) = freed_buf {
571 self.rlp_nodes_bufs.push(buf);
572 }
573 rlp_nodes_buf.push(child_rlp_node);
574 }
575
576 debug_assert_eq!(
577 rlp_nodes_buf.len(),
578 num_children,
579 "children length must match number of bits set in state_mask"
580 );
581
582 let short_key = trim_nibbles_prefix(
585 &self.branch_path,
586 self.branch_path.len() - branch.ext_len as usize,
587 );
588
589 let rlp_node = if short_key.is_empty() {
591 None
592 } else {
593 self.rlp_encode_buf.clear();
594 BranchNodeRef::new(&rlp_nodes_buf, branch.state_mask).encode(&mut self.rlp_encode_buf);
595 Some(RlpNode::from_rlp(&self.rlp_encode_buf))
596 };
597
598 let new_path_len =
601 self.branch_path.len() - branch.ext_len as usize - self.maybe_parent_nibble();
602
603 let branch_as_child = ProofTrieBranchChild::Branch {
605 node: BranchNodeV2::new(short_key, rlp_nodes_buf, branch.state_mask, rlp_node),
606 masks: (!masks.is_empty()).then_some(masks),
607 };
608
609 debug_assert!(self.branch_path.len() >= new_path_len);
610 self.branch_path = self.branch_path.slice_unchecked(0, new_path_len);
611
612 self.child_stack.push(branch_as_child);
613
614 Ok(())
615 }
616
617 #[instrument(
624 target = TRACE_TARGET,
625 level = "trace",
626 skip_all,
627 fields(?lower_bound, ?upper_bound),
628 )]
629 fn calculate_key_range<'a>(
630 &mut self,
631 value_encoder: &mut VE,
632 targets: &mut Option<TargetsCursor<'a>>,
633 hashed_cursor_state: &mut HashedCursorState<VE::DeferredEncoder>,
634 lower_bound: Nibbles,
635 upper_bound: Option<Nibbles>,
636 ) -> Result<(), StateProofError> {
637 let mut map_hashed_cursor_entry = |(key_b256, val): (B256, _)| {
641 debug_assert_eq!(key_b256.len(), 32);
642 let key = Nibbles::unpack_array(key_b256.as_ref());
643 let val = value_encoder.deferred_encoder(key_b256, val);
644 (key, val)
645 };
646
647 if hashed_cursor_state.needs_seek_to(&lower_bound) {
650 trace!(
651 target: TRACE_TARGET,
652 current=?hashed_cursor_state.path(),
653 "Seeking hashed cursor to meet lower bound",
654 );
655
656 let lower_key = B256::right_padding_from(&lower_bound.pack());
657 *hashed_cursor_state = HashedCursorState::seeked(
658 lower_bound,
659 self.hashed_cursor.seek(lower_key)?.map(&mut map_hashed_cursor_entry),
660 );
661 }
662
663 while hashed_cursor_state
665 .path()
666 .is_some_and(|key| upper_bound.is_none_or(|upper_bound| key < &upper_bound))
667 {
668 let (key, val) = hashed_cursor_state.take();
669 self.push_child(targets, ProofTrieBranchChild::Leaf { short_key: key, value: val })?;
670 *hashed_cursor_state = HashedCursorState::seeked(
671 key,
672 self.hashed_cursor.next()?.map(&mut map_hashed_cursor_entry),
673 );
674 }
675
676 trace!(target: TRACE_TARGET, "No further keys within range");
677 Ok(())
678 }
679
680 fn take_cached_branch(
682 &mut self,
683 trie_cursor_state: &mut TrieCursorState,
684 ) -> (Nibbles, BranchNodeCompact) {
685 let (cached_path, mut cached_branch) = trie_cursor_state.take();
686
687 if self.prefix_set.contains(&cached_path) {
688 let mut unchanged_children = 0;
689 let mut child_path = cached_path;
690 for nibble in cached_branch.state_mask.iter() {
691 child_path.truncate(cached_path.len());
692 child_path.push_unchecked(nibble);
693 if !self.prefix_set.contains(&child_path) {
694 unchanged_children += 1;
695 if unchanged_children > 1 {
696 break
697 }
698 }
699 }
700
701 if unchanged_children <= 1 {
705 Arc::make_mut(&mut cached_branch.hashes).fill(B256::ZERO);
706 trace!(
707 target: TRACE_TARGET,
708 ?cached_path,
709 ?unchanged_children,
710 "Invalidated cached hashes because branch may collapse",
711 );
712 }
713 }
714
715 (cached_path, cached_branch)
716 }
717
718 #[inline]
725 fn try_pop_cached_branch(
726 &mut self,
727 trie_cursor_state: &mut TrieCursorState,
728 traversal_upper_bound: Option<&Nibbles>,
729 uncalculated_lower_bound: &Option<Nibbles>,
730 ) -> Result<PopCachedBranchOutcome, StateProofError> {
731 let Some(uncalculated_lower_bound) = uncalculated_lower_bound else {
734 return Ok(PopCachedBranchOutcome::Exhausted)
735 };
736
737 while let Some(cached) = self.cached_branch_stack.pop() {
740 if cached
741 .0
742 .next_without_prefix()
743 .is_some_and(|upper_bound| upper_bound <= *uncalculated_lower_bound)
744 {
745 trace!(target: TRACE_TARGET, cached_path=?cached.0, ?uncalculated_lower_bound, "Skipping covered cached branch");
746 continue
747 }
748 return Ok(PopCachedBranchOutcome::Popped(cached));
749 }
750
751 let Some(mut trie_cursor_path) = trie_cursor_state.path() else {
758 return Ok(PopCachedBranchOutcome::Exhausted)
759 };
760
761 if trie_cursor_path < uncalculated_lower_bound {
764 *trie_cursor_state = TrieCursorState::seeked(
765 *uncalculated_lower_bound,
766 self.trie_cursor.seek(*uncalculated_lower_bound)?,
767 );
768
769 if let Some(new_trie_cursor_path) = trie_cursor_state.path() {
772 trie_cursor_path = new_trie_cursor_path
773 } else {
774 return Ok(PopCachedBranchOutcome::Exhausted)
775 };
776 }
777
778 if traversal_upper_bound.is_some_and(|upper_bound| trie_cursor_path >= upper_bound) {
781 return Ok(PopCachedBranchOutcome::Exhausted)
782 }
783
784 let cached = self.take_cached_branch(trie_cursor_state);
791 trace!(target: TRACE_TARGET, cached=?cached, "Pushed next trie node onto cached_branch_stack");
792
793 let cached_path = &cached.0;
800 if uncalculated_lower_bound < cached_path && !cached_path.is_zeroes() {
801 let range = (*uncalculated_lower_bound, Some(*cached_path));
802 trace!(target: TRACE_TARGET, ?range, "Returning key range to calculate in order to catch up to cached branch");
803
804 self.cached_branch_stack.push(cached);
807
808 return Ok(PopCachedBranchOutcome::CalculateLeaves(range));
809 }
810
811 Ok(PopCachedBranchOutcome::Popped(cached))
812 }
813
814 fn commit_branches<'a>(
823 &mut self,
824 targets: &mut Option<TargetsCursor<'a>>,
825 next_path: &Nibbles,
826 uncalculated_lower_bound: Option<&Nibbles>,
827 ) -> Result<Option<(Nibbles, Option<Nibbles>)>, StateProofError> {
828 let dirty_range = |prefix_set: &mut PrefixSet, upper_bound: Option<Nibbles>| {
829 let uncalculated_lower_bound = uncalculated_lower_bound?;
830
831 if upper_bound.as_ref().is_some_and(|upper| uncalculated_lower_bound >= upper) {
832 return None
833 }
834
835 match upper_bound {
836 Some(upper_bound) => prefix_set
837 .contains_range(uncalculated_lower_bound..&upper_bound)
838 .then_some((*uncalculated_lower_bound, Some(upper_bound))),
839 None => prefix_set
840 .contains_from(uncalculated_lower_bound)
841 .then_some((*uncalculated_lower_bound, None)),
842 }
843 };
844
845 let mut popped_child_path_upper = None;
846 while !next_path.starts_with(&self.branch_path) {
847 if uncalculated_lower_bound.is_some_and(|lower| lower.starts_with(&self.branch_path)) &&
850 let Some(range) =
851 dirty_range(&mut self.prefix_set, self.branch_path.next_without_prefix())
852 {
853 return Ok(Some(range))
854 }
855
856 let branch = self.branch_stack.last().expect("branch_stack cannot be empty");
857 popped_child_path_upper = Some(
860 self.branch_path
861 .slice_unchecked(0, self.branch_path.len() - branch.ext_len as usize)
862 .next_without_prefix(),
863 );
864
865 self.pop_branch(targets)?;
866 }
867
868 if !self.branch_stack.is_empty() &&
875 let Some(upper_bound) = popped_child_path_upper &&
876 let Some(range) = dirty_range(
877 &mut self.prefix_set,
878 Some(upper_bound.unwrap_or(*next_path).min(*next_path)),
879 )
880 {
881 return Ok(Some(range))
882 }
883
884 Ok(None)
885 }
886
887 fn next_uncached_child_nibble(
890 prefix_set: &mut PrefixSet,
891 branch_path: &Nibbles,
892 uncalculated_lower_bound_ref: &Nibbles,
893 cached_state_mask: TrieMask,
894 ) -> Option<u8> {
895 let mut next_child_nibbles = cached_state_mask;
896
897 if prefix_set.contains(branch_path) {
901 let branch_path_len = branch_path.len();
902 let mut child_path = *branch_path;
903 for nibble in 0u8..16 {
904 child_path.truncate(branch_path_len);
905 child_path.push_unchecked(nibble);
906 if prefix_set.contains(&child_path) {
907 next_child_nibbles.set_bit(nibble);
908 }
909 }
910 }
911
912 let _orig_next_child_nibbles = next_child_nibbles;
913
914 if uncalculated_lower_bound_ref.starts_with(branch_path) &&
919 uncalculated_lower_bound_ref.len() > branch_path.len()
920 {
921 let lower_nibble = uncalculated_lower_bound_ref.get_unchecked(branch_path.len());
922 let already_processed_mask = TrieMask::new((1u16 << lower_nibble) - 1);
925 next_child_nibbles &= !already_processed_mask;
926 trace!(
927 target: TRACE_TARGET,
928 ?branch_path,
929 ?_orig_next_child_nibbles,
930 ?already_processed_mask,
931 ?next_child_nibbles,
932 "Unset already processed key nibbles from next_child_nibbles",
933 );
934 } else if !uncalculated_lower_bound_ref.starts_with(branch_path) &&
935 uncalculated_lower_bound_ref > branch_path
936 {
937 next_child_nibbles = TrieMask::default();
940 trace!(
941 target: TRACE_TARGET,
942 ?branch_path,
943 ?_orig_next_child_nibbles,
944 ?next_child_nibbles,
945 "Unset all nibbles from next_child_nibbles due to branch_path being outside this subtrie",
946 );
947 }
948
949 next_child_nibbles.first_set_bit_index()
950 }
951
952 #[instrument(target = TRACE_TARGET, level = "trace", skip_all)]
971 fn next_uncached_key_range<'a>(
972 &mut self,
973 targets: &mut Option<TargetsCursor<'a>>,
974 trie_cursor_state: &mut TrieCursorState,
975 traversal_upper_bound: Option<&Nibbles>,
976 mut uncalculated_lower_bound: Option<Nibbles>,
977 ) -> Result<Option<(Nibbles, Option<Nibbles>)>, StateProofError> {
978 loop {
979 if let (Some(lower_bound), Some(upper_bound)) =
980 (uncalculated_lower_bound.as_ref(), traversal_upper_bound) &&
981 lower_bound >= upper_bound
982 {
983 return Ok(None)
984 }
985
986 let (cached_path, cached_branch) = match self.try_pop_cached_branch(
992 trie_cursor_state,
993 traversal_upper_bound,
994 &uncalculated_lower_bound,
995 )? {
996 PopCachedBranchOutcome::Popped(cached) => cached,
997 PopCachedBranchOutcome::Exhausted => {
998 trace!(target: TRACE_TARGET, ?uncalculated_lower_bound, "Exhausted cached trie nodes");
1002 if let Some(lower) = uncalculated_lower_bound {
1003 self.commit_branches(targets, &lower, None)?;
1004 return Ok(Some((lower, traversal_upper_bound.copied())));
1005 }
1006 return Ok(None)
1007 }
1008 PopCachedBranchOutcome::CalculateLeaves(range) => {
1009 self.commit_branches(targets, &range.0, None)?;
1010 return Ok(Some(range));
1011 }
1012 };
1013
1014 let uncalculated_lower_bound_ref = uncalculated_lower_bound
1015 .as_ref()
1016 .expect("try_pop_cached_branch would return Exhausted if this were None");
1017
1018 trace!(
1019 target: TRACE_TARGET,
1020 branch_path = ?self.branch_path,
1021 branch_state_mask = ?self.branch_stack.last().map(|b| b.state_mask),
1022 ?cached_path,
1023 cached_branch_state_mask = ?cached_branch.state_mask,
1024 cached_branch_hash_mask = ?cached_branch.hash_mask,
1025 "loop",
1026 );
1027
1028 if let Some(range) =
1029 self.commit_branches(targets, &cached_path, Some(uncalculated_lower_bound_ref))?
1030 {
1031 self.cached_branch_stack.push((cached_path, cached_branch));
1032 return Ok(Some(range))
1033 }
1034
1035 debug_assert!(
1038 self.branch_path.len() < cached_path.len() || self.branch_path == cached_path,
1039 "branch_path {:?} is different-or-longer-than cached_path {cached_path:?}",
1040 self.branch_path
1041 );
1042
1043 if uncalculated_lower_bound_ref < &cached_path &&
1046 self.prefix_set.contains_range(uncalculated_lower_bound_ref..&cached_path)
1047 {
1048 self.cached_branch_stack.push((cached_path, cached_branch));
1049 return Ok(Some((*uncalculated_lower_bound_ref, Some(cached_path))))
1050 }
1051
1052 let child_nibble = Self::next_uncached_child_nibble(
1053 &mut self.prefix_set,
1054 &cached_path,
1055 uncalculated_lower_bound_ref,
1056 cached_branch.state_mask,
1057 );
1058
1059 let Some(child_nibble) = child_nibble else {
1060 trace!(
1061 target: TRACE_TARGET,
1062 path=?cached_path,
1063 ?cached_branch,
1064 "No further cached children",
1065 );
1066
1067 uncalculated_lower_bound = cached_path.next_without_prefix();
1073
1074 continue
1075 };
1076
1077 let mut child_path = cached_path;
1078 child_path.push_unchecked(child_nibble);
1079 let child_lower_bound = (*uncalculated_lower_bound_ref).max(child_path);
1080
1081 if cached_branch.hash_mask.is_bit_set(child_nibble) &&
1091 child_lower_bound == child_path &&
1092 !self.prefix_set.contains(&child_path)
1093 {
1094 let lower_bits = TrieMask::new((1u16 << child_nibble) - 1);
1097 let hash_idx = (cached_branch.hash_mask & lower_bits).count_ones() as usize;
1098 let hash = cached_branch.hashes[hash_idx];
1099
1100 if !hash.is_zero() {
1103 let mut probed_targets = targets.clone();
1104 if !self.should_retain(&mut probed_targets, &child_path, false) {
1105 trace!(
1106 target: TRACE_TARGET,
1107 ?child_path,
1108 ?hash_idx,
1109 ?hash,
1110 "Using cached hash for child",
1111 );
1112
1113 let tree_mask_bit = cached_branch.tree_mask.is_bit_set(child_nibble);
1116 self.cached_branch_stack.push((cached_path, cached_branch));
1117 self.push_child(
1118 targets,
1119 ProofTrieBranchChild::RlpNode {
1120 node: RlpNode::word_rlp(&hash),
1121 short_key: child_path,
1122 hash_mask_bit: true,
1123 tree_mask_bit,
1124 },
1125 )?;
1126
1127 if let (Some(targets), Some(probed_targets)) =
1128 (targets.as_mut(), probed_targets)
1129 {
1130 targets.i = targets.i.max(probed_targets.i);
1131 }
1132
1133 uncalculated_lower_bound = child_path.next_without_prefix();
1136
1137 continue
1138 }
1139 }
1140 }
1141
1142 if trie_cursor_state.path().is_some_and(|path| path < &child_lower_bound) {
1149 trace!(target: TRACE_TARGET, ?child_lower_bound, "Seeking trie cursor to child lower bound");
1150 *trie_cursor_state = TrieCursorState::seeked(
1151 child_lower_bound,
1152 self.trie_cursor.seek(child_lower_bound)?,
1153 );
1154 }
1155
1156 if let TrieCursorState::Available(next_cached_path, next_cached_branch) =
1160 &trie_cursor_state &&
1161 next_cached_path.starts_with(&child_path)
1162 {
1163 self.cached_branch_stack.push((cached_path, cached_branch));
1165
1166 trace!(
1167 target: TRACE_TARGET,
1168 ?child_path,
1169 ?next_cached_path,
1170 ?next_cached_branch,
1171 "Pushing cached branch for child",
1172 );
1173 let cached = self.take_cached_branch(trie_cursor_state);
1174 self.cached_branch_stack.push(cached);
1175 continue;
1176 }
1177
1178 let child_upper_bound = child_path.next_without_prefix();
1182 trace!(
1183 target: TRACE_TARGET,
1184 lower=?child_lower_bound,
1185 upper=?child_upper_bound,
1186 "Returning sub-trie's key range to calculate",
1187 );
1188
1189 self.cached_branch_stack.push((cached_path, cached_branch));
1191
1192 return Ok(Some((child_lower_bound, child_upper_bound)));
1193 }
1194 }
1195
1196 #[instrument(
1200 target = TRACE_TARGET,
1201 level = "trace",
1202 skip_all,
1203 fields(
1204 parent_prefix=?sub_trie_targets.parent_prefix,
1205 lower_bound=?sub_trie_targets.lower_bound,
1206 upper_bound=?sub_trie_targets.upper_bound,
1207 ),
1208 )]
1209 fn proof_subtrie<'a>(
1210 &mut self,
1211 value_encoder: &mut VE,
1212 trie_cursor_state: &mut TrieCursorState,
1213 hashed_cursor_state: &mut HashedCursorState<VE::DeferredEncoder>,
1214 sub_trie_targets: SubTrieTargets<'a>,
1215 ) -> Result<(), StateProofError> {
1216 let traversal_lower_bound = sub_trie_targets.lower_bound;
1217 let traversal_upper_bound = sub_trie_targets.upper_bound;
1218
1219 let mut targets = if sub_trie_targets.targets.is_empty() {
1222 None
1223 } else {
1224 Some(TargetsCursor::new(sub_trie_targets.targets))
1225 };
1226
1227 debug_assert!(self.cached_branch_stack.is_empty());
1230 debug_assert!(self.branch_stack.is_empty());
1231 debug_assert!(self.branch_path.is_empty());
1232 debug_assert!(self.child_stack.is_empty());
1233
1234 if trie_cursor_state.needs_seek_to(&traversal_lower_bound) {
1239 trace!(target: TRACE_TARGET, "Doing initial seek of trie cursor");
1240 *trie_cursor_state = TrieCursorState::seeked(
1241 traversal_lower_bound,
1242 self.trie_cursor.seek(traversal_lower_bound)?,
1243 );
1244 }
1245
1246 let mut uncalculated_lower_bound = Some(traversal_lower_bound);
1251
1252 trace!(target: TRACE_TARGET, "Starting loop");
1253 loop {
1254 let prev_uncalculated_lower_bound = uncalculated_lower_bound;
1256
1257 let Some((calc_lower_bound, calc_upper_bound)) = self.next_uncached_key_range(
1259 &mut targets,
1260 trie_cursor_state,
1261 traversal_upper_bound.as_ref(),
1262 prev_uncalculated_lower_bound,
1263 )?
1264 else {
1265 break;
1268 };
1269
1270 if let Some(prev_lower) = prev_uncalculated_lower_bound.as_ref() &&
1277 calc_lower_bound < *prev_lower
1278 {
1279 let msg = format!(
1280 "next_uncached_key_range went backwards: calc_lower={calc_lower_bound:?} < \
1281 prev_lower={prev_lower:?}, calc_upper={calc_upper_bound:?}, \
1282 lower_bound={traversal_lower_bound:?}, \
1283 upper_bound={traversal_upper_bound:?}",
1284 );
1285 error!(target: TRACE_TARGET, "{msg}");
1286 return Err(StateProofError::TrieInconsistency(msg));
1287 }
1288
1289 self.calculate_key_range(
1291 value_encoder,
1292 &mut targets,
1293 hashed_cursor_state,
1294 calc_lower_bound,
1295 calc_upper_bound,
1296 )?;
1297
1298 if hashed_cursor_state.path().is_none_or(|key| {
1304 traversal_upper_bound.is_some_and(|upper_bound| key >= &upper_bound)
1305 }) {
1306 break;
1307 }
1308
1309 uncalculated_lower_bound = calc_upper_bound;
1312 }
1313
1314 trace!(target: TRACE_TARGET, "Exited loop, popping remaining branches");
1316 while !self.branch_stack.is_empty() {
1317 self.pop_branch(&mut targets)?;
1318 }
1319
1320 debug_assert!(self.branch_stack.is_empty());
1324 debug_assert!(self.branch_path.is_empty());
1325 debug_assert!(self.child_stack.len() < 2);
1326
1327 self.cached_branch_stack.clear();
1330
1331 trace!(
1335 target: TRACE_TARGET,
1336 parent_prefix = ?sub_trie_targets.parent_prefix,
1337 child_stack_empty = self.child_stack.is_empty(),
1338 "Maybe retaining local root",
1339 );
1340 let root_node = self.child_stack.pop();
1341
1342 let Some(parent_prefix) = sub_trie_targets.parent_prefix else {
1345 let mut root_node = if let Some(root_node) = root_node {
1346 self.rlp_encode_buf.clear();
1347 root_node.into_proof_trie_node(Nibbles::new(), &mut self.rlp_encode_buf)?
1348 } else {
1349 ProofTrieNodeV2::empty()
1350 };
1351
1352 if matches!(&root_node.node, TrieNodeV2::Branch(branch) if branch.key.is_empty()) {
1355 root_node.masks = None;
1356 }
1357
1358 self.retained_proofs.push(root_node);
1359 return Ok(())
1360 };
1361
1362 let Some(mut root_node) = root_node else { return Ok(()) };
1364
1365 let root_full_path = *root_node.short_key();
1366
1367 if root_full_path == parent_prefix {
1370 return Ok(())
1371 }
1372
1373 if !root_full_path.starts_with(&parent_prefix) {
1379 return Err(StateProofError::TrieInconsistency(format!(
1380 "local root path {root_full_path:?} does not start with parent prefix \
1381 {parent_prefix:?}",
1382 )))
1383 }
1384
1385 let child_path_len = parent_prefix.len() + 1;
1388 let child_path = root_full_path.slice_unchecked(0, child_path_len);
1389
1390 if !sub_trie_targets
1392 .targets
1393 .iter()
1394 .any(|target| target.key_nibbles.starts_with(&child_path))
1395 {
1396 return Ok(())
1397 }
1398
1399 root_node.trim_short_key_prefix(child_path_len);
1401 self.rlp_encode_buf.clear();
1402 let root_node = root_node.into_proof_trie_node(child_path, &mut self.rlp_encode_buf)?;
1403 self.retained_proofs.push(root_node);
1404
1405 Ok(())
1406 }
1407
1408 fn clear_computation_state(&mut self) {
1411 self.branch_stack.clear();
1412 self.branch_path = Nibbles::new();
1413 self.child_stack.clear();
1414 self.cached_branch_stack.clear();
1415 self.retained_proofs.clear();
1416 }
1417
1418 fn proof_inner(
1421 &mut self,
1422 value_encoder: &mut VE,
1423 targets: &mut [ProofV2Target],
1424 ) -> Result<Vec<ProofTrieNodeV2>, StateProofError> {
1425 if targets.is_empty() {
1427 trace!(target: TRACE_TARGET, "Empty targets, returning");
1428 return Ok(Vec::new())
1429 }
1430
1431 let mut trie_cursor_state = TrieCursorState::unseeked();
1434 let mut hashed_cursor_state = HashedCursorState::unseeked();
1435 let mut previous_traversal_bounds: Option<(Nibbles, Option<Nibbles>)> = None;
1436
1437 for sub_trie_targets in iter_sub_trie_targets(targets) {
1440 let traversal_lower_bound = sub_trie_targets.lower_bound;
1441 let traversal_upper_bound = sub_trie_targets.upper_bound;
1442 if previous_traversal_bounds.is_some_and(|(_, previous_upper_bound)| {
1443 previous_upper_bound.is_none_or(|upper_bound| upper_bound > traversal_lower_bound)
1444 }) {
1445 if trie_cursor_state.needs_reset_before_seek(&traversal_lower_bound) {
1446 trace!(
1447 target: TRACE_TARGET,
1448 ?previous_traversal_bounds,
1449 ?traversal_lower_bound,
1450 ?traversal_upper_bound,
1451 "Resetting trie cursor before overlapping or backward traversal range",
1452 );
1453 self.trie_cursor.reset();
1454 trie_cursor_state = TrieCursorState::unseeked();
1455 }
1456 if hashed_cursor_state.needs_reset_before_seek(&traversal_lower_bound) {
1457 trace!(
1458 target: TRACE_TARGET,
1459 ?previous_traversal_bounds,
1460 ?traversal_lower_bound,
1461 ?traversal_upper_bound,
1462 "Resetting hashed cursor before overlapping or backward traversal range",
1463 );
1464 self.hashed_cursor.reset();
1465 hashed_cursor_state = HashedCursorState::unseeked();
1466 }
1467 }
1468
1469 if let Err(err) = self.proof_subtrie(
1470 value_encoder,
1471 &mut trie_cursor_state,
1472 &mut hashed_cursor_state,
1473 sub_trie_targets,
1474 ) {
1475 self.clear_computation_state();
1476 return Err(err);
1477 }
1478
1479 previous_traversal_bounds = Some((traversal_lower_bound, traversal_upper_bound));
1480 }
1481
1482 trace!(
1483 target: TRACE_TARGET,
1484 retained_proofs_len = ?self.retained_proofs.len(),
1485 "proof_inner: returning",
1486 );
1487 self.retained_proofs.sort_unstable_by(|a, b| depth_first::cmp(&a.path, &b.path));
1488 self.retained_proofs.dedup_by(|a, b| a.path == b.path);
1489 Ok(core::mem::take(&mut self.retained_proofs))
1490 }
1491
1492 #[instrument(target = TRACE_TARGET, level = "trace", skip_all)]
1500 pub fn proof(
1501 &mut self,
1502 value_encoder: &mut VE,
1503 targets: &mut [ProofV2Target],
1504 ) -> Result<Vec<ProofTrieNodeV2>, StateProofError> {
1505 self.trie_cursor.reset();
1506 self.hashed_cursor.reset();
1507 self.proof_inner(value_encoder, targets)
1508 }
1509
1510 pub fn compute_root_hash(
1517 &mut self,
1518 proof_nodes: &[ProofTrieNodeV2],
1519 ) -> Result<Option<B256>, StateProofError> {
1520 let root_node = proof_nodes.iter().find(|node| node.path.is_empty());
1522
1523 let Some(root) = root_node else {
1524 return Ok(None);
1525 };
1526
1527 self.rlp_encode_buf.clear();
1529 root.node.encode(&mut self.rlp_encode_buf);
1530 let root_hash = keccak256(&self.rlp_encode_buf);
1531
1532 Ok(Some(root_hash))
1533 }
1534
1535 #[instrument(target = TRACE_TARGET, level = "trace", skip(self, value_encoder))]
1540 pub fn root_node(
1541 &mut self,
1542 value_encoder: &mut VE,
1543 ) -> Result<ProofTrieNodeV2, StateProofError> {
1544 self.trie_cursor.reset();
1545 self.hashed_cursor.reset();
1546
1547 let mut trie_cursor_state = TrieCursorState::unseeked();
1550 let mut hashed_cursor_state = HashedCursorState::unseeked();
1551
1552 static EMPTY_TARGETS: [ProofV2Target; 0] = [];
1553 let sub_trie_targets = SubTrieTargets {
1554 lower_bound: Nibbles::new(),
1555 upper_bound: None,
1556 parent_prefix: None,
1557 targets: &EMPTY_TARGETS,
1558 };
1559
1560 if let Err(err) = self.proof_subtrie(
1561 value_encoder,
1562 &mut trie_cursor_state,
1563 &mut hashed_cursor_state,
1564 sub_trie_targets,
1565 ) {
1566 self.clear_computation_state();
1567 return Err(err);
1568 }
1569
1570 let mut proofs = core::mem::take(&mut self.retained_proofs);
1573 trace!(
1574 target: TRACE_TARGET,
1575 proofs_len = ?proofs.len(),
1576 "root_node: extracting root",
1577 );
1578
1579 debug_assert_eq!(
1582 proofs.len(), 1,
1583 "prefix is empty, parent path is None, and targets is empty, so there must be only the root node"
1584 );
1585
1586 let root_node = proofs.pop().expect("prefix is empty, parent path is None, and targets is empty, so there must be only the root node");
1588
1589 Ok(root_node)
1590 }
1591}
1592
1593pub type StorageProofCalculator<TC, HC> = ProofCalculator<TC, HC, StorageValueEncoder>;
1595
1596impl<TC, HC> StorageProofCalculator<TC, HC>
1597where
1598 TC: TrieStorageCursor,
1599 HC: HashedStorageCursor<Value = U256>,
1600{
1601 pub fn new_storage(trie_cursor: TC, hashed_cursor: HC) -> Self {
1603 Self::new(trie_cursor, hashed_cursor)
1604 }
1605
1606 #[instrument(target = TRACE_TARGET, level = "trace", skip(self, targets))]
1614 pub fn storage_proof(
1615 &mut self,
1616 hashed_address: B256,
1617 targets: &mut [ProofV2Target],
1618 ) -> Result<Vec<ProofTrieNodeV2>, StateProofError> {
1619 self.hashed_cursor.set_hashed_address(hashed_address);
1620
1621 if self.hashed_cursor.is_storage_empty()? {
1623 return Ok(if targets.iter().any(|target| !target.parent.is_known()) {
1624 vec![ProofTrieNodeV2 {
1625 path: Nibbles::default(),
1626 node: TrieNodeV2::EmptyRoot,
1627 masks: None,
1628 }]
1629 } else {
1630 Vec::new()
1631 })
1632 }
1633
1634 self.trie_cursor.set_hashed_address(hashed_address);
1637
1638 let mut storage_value_encoder = StorageValueEncoder;
1640 self.proof_inner(&mut storage_value_encoder, targets)
1641 }
1642
1643 #[instrument(target = TRACE_TARGET, level = "trace", skip(self))]
1648 pub fn storage_root_node(
1649 &mut self,
1650 hashed_address: B256,
1651 ) -> Result<ProofTrieNodeV2, StateProofError> {
1652 self.hashed_cursor.set_hashed_address(hashed_address);
1653
1654 if self.hashed_cursor.is_storage_empty()? {
1655 return Ok(ProofTrieNodeV2 {
1656 path: Nibbles::default(),
1657 node: TrieNodeV2::EmptyRoot,
1658 masks: None,
1659 })
1660 }
1661
1662 self.trie_cursor.set_hashed_address(hashed_address);
1665
1666 let mut storage_value_encoder = StorageValueEncoder;
1668 self.root_node(&mut storage_value_encoder)
1669 }
1670
1671 pub fn storage_root(&mut self, hashed_address: B256) -> Result<B256, StateProofError> {
1673 let root_node = self.storage_root_node(hashed_address)?;
1674 Ok(self
1675 .compute_root_hash(core::slice::from_ref(&root_node))?
1676 .expect("storage_root_node returns a node at empty path"))
1677 }
1678}
1679
1680#[derive(Clone)]
1686struct TargetsCursor<'a> {
1687 targets: &'a [ProofV2Target],
1688 i: usize,
1689}
1690
1691impl<'a> TargetsCursor<'a> {
1692 fn new(targets: &'a [ProofV2Target]) -> Self {
1698 debug_assert!(!targets.is_empty());
1699 Self { targets, i: 0 }
1700 }
1701
1702 fn current(&self) -> (&'a ProofV2Target, Option<&'a ProofV2Target>) {
1704 (&self.targets[self.i], self.targets.get(self.i + 1))
1705 }
1706
1707 fn next(&mut self) -> (&'a ProofV2Target, Option<&'a ProofV2Target>) {
1713 self.i += 1;
1714 debug_assert!(self.i < self.targets.len());
1715 self.current()
1716 }
1717
1718 fn skip_iter(&self) -> impl Iterator<Item = &'a ProofV2Target> {
1720 self.targets[self.i + 1..].iter()
1721 }
1722
1723 fn rev_iter(&self) -> impl Iterator<Item = &'a ProofV2Target> {
1726 self.targets[..self.i].iter().rev()
1727 }
1728}
1729
1730#[derive(Debug)]
1733enum TrieCursorState {
1734 Unseeked,
1736 Available(Nibbles, BranchNodeCompact),
1738 Taken(Nibbles),
1740 Exhausted(Nibbles),
1742}
1743
1744impl TrieCursorState {
1745 const fn unseeked() -> Self {
1747 Self::Unseeked
1748 }
1749
1750 fn seeked(key: Nibbles, entry: Option<(Nibbles, BranchNodeCompact)>) -> Self {
1752 entry.map_or(Self::Exhausted(key), |(path, node)| Self::Available(path, node))
1753 }
1754
1755 const fn path(&self) -> Option<&Nibbles> {
1761 match self {
1762 Self::Unseeked => panic!("cursor is unseeked"),
1763 Self::Available(path, _) | Self::Taken(path) => Some(path),
1764 Self::Exhausted(_) => None,
1765 }
1766 }
1767
1768 fn needs_seek_to(&self, path: &Nibbles) -> bool {
1770 match self {
1771 Self::Unseeked | Self::Taken(_) => true,
1772 Self::Available(current_path, _) => current_path < path,
1773 Self::Exhausted(_) => false,
1774 }
1775 }
1776
1777 fn needs_reset_before_seek(&self, key: &Nibbles) -> bool {
1779 match self {
1780 Self::Unseeked => false,
1781 Self::Available(path, _) | Self::Taken(path) => path > key,
1782 Self::Exhausted(exhausted_at) => exhausted_at > key,
1783 }
1784 }
1785
1786 fn take(&mut self) -> (Nibbles, BranchNodeCompact) {
1788 let Self::Available(path, _) = self else {
1789 panic!("take called on non-Available: {self:?}")
1790 };
1791
1792 let path = *path;
1793 let Self::Available(path, node) = core::mem::replace(self, Self::Taken(path)) else {
1794 unreachable!("already checked that self is Self::Available");
1795 };
1796
1797 (path, node)
1798 }
1799}
1800
1801enum HashedCursorState<V> {
1803 Unseeked,
1805 Available(Nibbles, V),
1807 Exhausted(Nibbles),
1809}
1810
1811impl<V> HashedCursorState<V> {
1812 const fn unseeked() -> Self {
1814 Self::Unseeked
1815 }
1816
1817 fn seeked(key: Nibbles, entry: Option<(Nibbles, V)>) -> Self {
1819 entry.map_or(Self::Exhausted(key), |(path, value)| Self::Available(path, value))
1820 }
1821
1822 const fn path(&self) -> Option<&Nibbles> {
1824 match self {
1825 Self::Available(path, _) => Some(path),
1826 Self::Unseeked | Self::Exhausted(_) => None,
1827 }
1828 }
1829
1830 fn needs_seek_to(&self, key: &Nibbles) -> bool {
1832 match self {
1833 Self::Unseeked => true,
1834 Self::Available(path, _) => path < key,
1835 Self::Exhausted(exhausted_at) => exhausted_at > key,
1836 }
1837 }
1838
1839 fn needs_reset_before_seek(&self, key: &Nibbles) -> bool {
1841 match self {
1842 Self::Unseeked => false,
1843 Self::Available(path, _) => path > key,
1844 Self::Exhausted(exhausted_at) => exhausted_at >= key,
1845 }
1846 }
1847
1848 fn take(&mut self) -> (Nibbles, V) {
1850 match core::mem::replace(self, Self::Unseeked) {
1851 Self::Available(path, value) => (path, value),
1852 _ => panic!("take called on non-Available hashed cursor state"),
1853 }
1854 }
1855}
1856
1857enum PopCachedBranchOutcome {
1859 Popped((Nibbles, BranchNodeCompact)),
1861 Exhausted,
1863 CalculateLeaves((Nibbles, Option<Nibbles>)),
1866}
1867
1868#[cfg(test)]
1869mod tests {
1870 use super::*;
1871 use crate::{
1872 hashed_cursor::{
1873 mock::MockHashedCursorFactory, noop::NoopHashedCursor, HashedCursorFactory,
1874 HashedPostStateCursor,
1875 },
1876 proof::StorageProof as LegacyStorageProof,
1877 test_utils::TrieTestHarness,
1878 trie_cursor::{
1879 depth_first, noop::NoopStorageTrieCursor, InMemoryTrieCursor, TrieCursorFactory,
1880 },
1881 };
1882 use alloy_primitives::map::B256Set;
1883 use alloy_rlp::Decodable;
1884 use alloy_trie::proof::AddedRemovedKeys;
1885 use itertools::Itertools;
1886 use reth_trie_common::{
1887 prefix_set::{PrefixSet, PrefixSetMut},
1888 updates::TrieUpdatesSorted,
1889 HashedPostState, HashedStorage, ProofTrieNode, ProofV2TargetParent, TrieNode,
1890 EMPTY_ROOT_HASH,
1891 };
1892 use std::collections::BTreeMap;
1893
1894 fn convert_legacy_proofs_to_v2(legacy_proofs: &[ProofTrieNode]) -> Vec<ProofTrieNodeV2> {
1907 ProofTrieNodeV2::from_sorted_trie_nodes(
1908 legacy_proofs.iter().map(|p| (p.path, p.node.clone(), p.masks)),
1909 )
1910 }
1911
1912 fn project_legacy_proof_node(
1914 node: &ProofTrieNodeV2,
1915 target: &ProofV2Target,
1916 ) -> Option<ProofTrieNodeV2> {
1917 let Some(parent_path_len) = target.parent.path_len() else {
1918 return target.key_nibbles.starts_with(&node.path).then(|| node.clone())
1919 };
1920
1921 if node.path.len() > parent_path_len {
1922 return target.key_nibbles.starts_with(&node.path).then(|| node.clone())
1923 }
1924
1925 let logical_path = match &node.node {
1926 TrieNodeV2::Leaf(leaf) => node.path.join(&leaf.key),
1927 TrieNodeV2::Branch(branch) => node.path.join(&branch.key),
1928 TrieNodeV2::EmptyRoot | TrieNodeV2::Extension(_) => return None,
1929 };
1930 let child_path_len = parent_path_len + 1;
1931 if logical_path.len() < child_path_len {
1932 return None
1933 }
1934
1935 let child_path = logical_path.slice(0..child_path_len);
1936 if !target.key_nibbles.starts_with(&child_path) {
1937 return None
1938 }
1939
1940 let trim_len = child_path_len - node.path.len();
1941 let mut projected = node.clone();
1942 projected.path = child_path;
1943 match &mut projected.node {
1944 TrieNodeV2::Leaf(leaf) => leaf.key = leaf.key.slice(trim_len..),
1945 TrieNodeV2::Branch(branch) => {
1946 branch.key = branch.key.slice(trim_len..);
1947 if branch.key.is_empty() {
1948 branch.branch_rlp_node = None;
1949 }
1950 }
1951 TrieNodeV2::EmptyRoot | TrieNodeV2::Extension(_) => unreachable!(),
1952 }
1953 Some(projected)
1954 }
1955
1956 fn project_legacy_proof(
1958 legacy_nodes: &[ProofTrieNodeV2],
1959 targets: &[ProofV2Target],
1960 ) -> Vec<ProofTrieNodeV2> {
1961 let mut projected = targets
1962 .iter()
1963 .flat_map(|target| {
1964 legacy_nodes.iter().filter_map(move |node| project_legacy_proof_node(node, target))
1965 })
1966 .collect::<Vec<_>>();
1967 projected.sort_unstable_by(|a, b| depth_first::cmp(&a.path, &b.path));
1968 projected.dedup_by(|a, b| {
1969 if a.path != b.path {
1970 return false
1971 }
1972 assert_eq!(a, b, "target projections disagree at path {:?}", a.path);
1973 true
1974 });
1975 projected
1976 }
1977
1978 struct ProofTestHarness {
1984 inner: TrieTestHarness,
1985 }
1986
1987 impl std::ops::Deref for ProofTestHarness {
1988 type Target = TrieTestHarness;
1989 fn deref(&self) -> &Self::Target {
1990 &self.inner
1991 }
1992 }
1993
1994 impl ProofTestHarness {
1995 fn new(storage: BTreeMap<B256, U256>) -> Self {
1997 Self { inner: TrieTestHarness::new(storage) }
1998 }
1999
2000 fn root_with_prefix_set(&self, prefix_set: PrefixSet) -> Option<B256> {
2002 let trie_cursor =
2003 self.trie_cursor_factory().storage_trie_cursor(self.hashed_address()).unwrap();
2004 let hashed_cursor =
2005 self.hashed_cursor_factory().hashed_storage_cursor(self.hashed_address()).unwrap();
2006 let mut calculator = StorageProofCalculator::new_storage(trie_cursor, hashed_cursor)
2007 .with_prefix_set(prefix_set);
2008
2009 let mut targets = [ProofV2Target::new(B256::ZERO)];
2010 let proof = calculator.storage_proof(self.hashed_address(), &mut targets).unwrap();
2011 calculator.compute_root_hash(&proof).unwrap()
2012 }
2013
2014 fn assert_proof(
2017 &self,
2018 targets: impl IntoIterator<Item = ProofV2Target>,
2019 ) -> Result<(), StateProofError> {
2020 let mut targets_vec = targets.into_iter().collect::<Vec<_>>();
2021
2022 let (proof_v2_result, root_hash) = self.proof_v2(&mut targets_vec);
2024
2025 if let Some(root_hash) = root_hash {
2028 pretty_assertions::assert_eq!(self.original_root(), root_hash);
2029 }
2030
2031 let legacy_targets = targets_vec
2034 .iter()
2035 .map(|target| B256::from_slice(&target.key_nibbles.pack()))
2036 .chain(self.storage().keys().copied())
2037 .collect::<B256Set>();
2038
2039 let proof_legacy_result = LegacyStorageProof::new_hashed(
2041 self.trie_cursor_factory(),
2042 self.hashed_cursor_factory(),
2043 self.hashed_address(),
2044 )
2045 .with_branch_node_masks(true)
2046 .with_added_removed_keys(Some(AddedRemovedKeys::default().with_assume_added(true)))
2047 .storage_multiproof(legacy_targets)?;
2048
2049 let proof_legacy_nodes = proof_legacy_result
2051 .subtree
2052 .iter()
2053 .map(|(path, node_enc)| {
2054 let mut buf = node_enc.as_ref();
2055 let node = TrieNode::decode(&mut buf)
2056 .expect("legacy implementation should not produce malformed proof nodes");
2057
2058 let masks = if path.is_empty() {
2059 None
2060 } else {
2061 proof_legacy_result.branch_node_masks.get(path).copied()
2062 };
2063
2064 ProofTrieNode { path: *path, node, masks }
2065 })
2066 .sorted_by(|a, b| depth_first::cmp(&a.path, &b.path))
2067 .collect::<Vec<_>>();
2068
2069 let all_legacy_nodes_v2 = convert_legacy_proofs_to_v2(&proof_legacy_nodes);
2071
2072 let expected_v2 = project_legacy_proof(&all_legacy_nodes_v2, &targets_vec);
2073 pretty_assertions::assert_eq!(expected_v2, proof_v2_result);
2074
2075 Ok(())
2076 }
2077 }
2078
2079 #[test]
2084 fn test_proof_calculator_reuse_after_error() {
2085 reth_tracing::init_test_tracing();
2086
2087 let slots = [
2088 B256::right_padding_from(&[0x10]),
2089 B256::right_padding_from(&[0x20]),
2090 B256::right_padding_from(&[0x30]),
2091 B256::right_padding_from(&[0x40]),
2092 ];
2093 let storage: BTreeMap<B256, U256> =
2094 slots.iter().map(|&s| (s, U256::from(100u64))).collect();
2095
2096 let harness = ProofTestHarness::new(storage);
2097
2098 let trie_cursor_factory = harness.trie_cursor_factory();
2099 let hashed_cursor_factory = harness.hashed_cursor_factory();
2100
2101 let hashed_address = harness.hashed_address();
2102 let trie_cursor = trie_cursor_factory.storage_trie_cursor(hashed_address).unwrap();
2103 let hashed_cursor = hashed_cursor_factory.hashed_storage_cursor(hashed_address).unwrap();
2104 let mut proof_calculator = StorageProofCalculator::new_storage(trie_cursor, hashed_cursor);
2105
2106 proof_calculator
2109 .branch_stack
2110 .push(ProofTrieBranch { ext_len: 2, state_mask: TrieMask::new(0b1111) });
2111 proof_calculator
2112 .branch_stack
2113 .push(ProofTrieBranch { ext_len: 0, state_mask: TrieMask::new(0b11) });
2114 proof_calculator.child_stack.push(ProofTrieBranchChild::RlpNode {
2115 node: RlpNode::word_rlp(&B256::ZERO),
2116 short_key: Nibbles::new(),
2117 hash_mask_bit: false,
2118 tree_mask_bit: false,
2119 });
2120 proof_calculator.branch_path = Nibbles::from_nibbles([0x1, 0x2, 0x3]);
2121
2122 proof_calculator.clear_computation_state();
2124
2125 let mut sorted_slots = slots.to_vec();
2126 sorted_slots.sort();
2127 let mut targets: Vec<ProofV2Target> =
2128 sorted_slots.iter().copied().map(ProofV2Target::new).collect();
2129
2130 let result = proof_calculator.storage_proof(hashed_address, &mut targets).unwrap();
2131
2132 let trie_cursor = trie_cursor_factory.storage_trie_cursor(hashed_address).unwrap();
2134 let hashed_cursor = hashed_cursor_factory.hashed_storage_cursor(hashed_address).unwrap();
2135 let mut fresh_calculator = StorageProofCalculator::new_storage(trie_cursor, hashed_cursor);
2136 let fresh_result = fresh_calculator.storage_proof(hashed_address, &mut targets).unwrap();
2137
2138 pretty_assertions::assert_eq!(fresh_result, result);
2139 }
2140
2141 #[test]
2142 fn test_root_node_reuse_with_overlay() {
2143 let storage = BTreeMap::from([
2144 (B256::right_padding_from(&[0x10]), U256::ONE),
2145 (B256::right_padding_from(&[0x20]), U256::from(2)),
2146 ]);
2147 let harness = ProofTestHarness::new(storage.clone());
2148 let hashed_address = harness.hashed_address();
2149 let post_state =
2150 HashedPostState::from_hashed_storage(hashed_address, HashedStorage::from_iter(storage))
2151 .into_sorted();
2152 let trie_updates = TrieUpdatesSorted::default();
2153 let trie_cursor = InMemoryTrieCursor::new_storage(
2154 NoopStorageTrieCursor::default(),
2155 &trie_updates,
2156 hashed_address,
2157 );
2158 let hashed_cursor = HashedPostStateCursor::new_storage(
2159 NoopHashedCursor::default(),
2160 &post_state,
2161 hashed_address,
2162 );
2163 let mut calculator = StorageProofCalculator::new_storage(trie_cursor, hashed_cursor);
2164
2165 let first_root = calculator.root_node(&mut StorageValueEncoder).unwrap();
2166 assert!(matches!(first_root.node, TrieNodeV2::Branch(_)));
2167 assert_eq!(
2168 calculator.compute_root_hash(core::slice::from_ref(&first_root)).unwrap(),
2169 Some(harness.original_root())
2170 );
2171
2172 let second_root = calculator.root_node(&mut StorageValueEncoder).unwrap();
2173 pretty_assertions::assert_eq!(first_root, second_root);
2174 }
2175
2176 #[test]
2177 fn test_partial_storage_proof_after_root_calculation() {
2178 let slot_a = B256::right_padding_from(&[0xae, 0xd4, 0x00]);
2179 let slot_b = B256::right_padding_from(&[0xae, 0xd4, 0x10]);
2180 let harness =
2181 ProofTestHarness::new(BTreeMap::from([(slot_a, U256::ONE), (slot_b, U256::from(2))]));
2182 let hashed_address = harness.hashed_address();
2183 let trie_cursor =
2184 harness.trie_cursor_factory().storage_trie_cursor(hashed_address).unwrap();
2185 let hashed_cursor =
2186 harness.hashed_cursor_factory().hashed_storage_cursor(hashed_address).unwrap();
2187 let mut calculator = StorageProofCalculator::new_storage(trie_cursor, hashed_cursor);
2188
2189 let root_node = calculator.storage_root_node(hashed_address).unwrap();
2190 assert_eq!(
2191 calculator.compute_root_hash(core::slice::from_ref(&root_node)).unwrap(),
2192 Some(harness.original_root())
2193 );
2194
2195 let target = ProofV2Target::new(slot_a).with_parent(ProofV2TargetParent::new(3));
2196 let mut actual_targets = [target];
2197 let actual = calculator.storage_proof(hashed_address, &mut actual_targets).unwrap();
2198 let mut expected_targets = [target];
2199 let (expected, root) = harness.proof_v2(&mut expected_targets);
2200
2201 assert!(root.is_none());
2202 pretty_assertions::assert_eq!(expected, actual);
2203 }
2204
2205 mod proptest_tests {
2206 use super::*;
2207 use proptest::prelude::*;
2208
2209 fn storage_strategy() -> impl Strategy<Value = BTreeMap<B256, U256>> {
2211 prop::collection::vec((any::<[u8; 32]>(), any::<u64>()), 0..=100).prop_map(|slots| {
2212 slots
2213 .into_iter()
2214 .map(|(slot_bytes, value)| (B256::from(slot_bytes), U256::from(value)))
2215 .filter(|(_, v)| !v.is_zero())
2216 .collect()
2217 })
2218 }
2219
2220 fn proof_targets_strategy(
2223 slot_keys: Vec<B256>,
2224 ) -> impl Strategy<Value = Vec<ProofV2Target>> {
2225 let num_slots = slot_keys.len();
2226
2227 let target_count = 0..=(num_slots + 5);
2228
2229 target_count.prop_flat_map(move |count| {
2230 let slot_keys = slot_keys.clone();
2231 prop::collection::vec(
2232 (
2233 prop::bool::weighted(0.8).prop_flat_map(move |from_slots| {
2234 if from_slots && !slot_keys.is_empty() {
2235 prop::sample::select(slot_keys.clone()).boxed()
2236 } else {
2237 any::<[u8; 32]>().prop_map(B256::from).boxed()
2238 }
2239 }),
2240 0u8..16u8,
2241 )
2242 .prop_map(|(key, encoded_parent_path_len)| {
2243 let parent = encoded_parent_path_len.checked_sub(1).map_or(
2244 ProofV2TargetParent::NONE,
2245 |parent_path_len| {
2246 ProofV2TargetParent::new(usize::from(parent_path_len))
2247 },
2248 );
2249 ProofV2Target::new(key).with_parent(parent)
2250 }),
2251 count,
2252 )
2253 })
2254 }
2255
2256 fn dirty_key_strategy() -> impl Strategy<Value = B256> {
2257 prop::array::uniform8(0u8..4).prop_map(|nibbles| {
2259 B256::right_padding_from(&Nibbles::from_nibbles(nibbles).pack())
2260 })
2261 }
2262
2263 proptest! {
2264 #![proptest_config(ProptestConfig::with_cases(4000))]
2265 #[test]
2266 fn proptest_proof_with_targets(
2269 (storage, targets) in storage_strategy()
2270 .prop_flat_map(|storage| {
2271 let mut slot_keys: Vec<B256> = storage.keys().copied().collect();
2272 slot_keys.sort_unstable();
2273 let targets_strategy = proof_targets_strategy(slot_keys);
2274 (Just(storage), targets_strategy)
2275 })
2276 ) {
2277 reth_tracing::init_test_tracing();
2278 let harness = ProofTestHarness::new(storage);
2279
2280 harness.assert_proof(targets).expect("Proof generation failed");
2281 }
2282
2283 #[test]
2284 fn proptest_proof_with_dirty_prefixes(
2285 before in prop::collection::btree_map(dirty_key_strategy(), 1u64..100, 0..80),
2286 changes in prop::collection::btree_map(dirty_key_strategy(), 0u64..100, 0..8),
2287 retain_unchanged in any::<bool>(),
2288 target_indices in prop::collection::vec((0usize..100, 0usize..9), 0..10),
2289 ) {
2290 let before = before.into_iter()
2291 .map(|(key, value)| (key, U256::from(value)))
2292 .collect::<BTreeMap<_, _>>();
2293 let mut after = if retain_unchanged { before.clone() } else { BTreeMap::new() };
2294 for (key, value) in changes {
2295 if value == 0 {
2296 after.remove(&key);
2297 } else {
2298 after.insert(key, U256::from(value));
2299 }
2300 }
2301 let prefix_set = PrefixSetMut::from(
2302 before.keys().chain(after.keys())
2303 .filter(|key| before.get(*key) != after.get(*key))
2304 .map(Nibbles::unpack),
2305 ).freeze();
2306 let expected_root = crate::test_utils::storage_root_prehashed(
2307 after.iter().map(|(key, value)| (*key, *value)),
2308 );
2309 let mut targets = vec![ProofV2Target::new(B256::ZERO)];
2310 let keys = before.keys().chain(after.keys()).copied().collect::<Vec<_>>();
2311 if !keys.is_empty() {
2312 for (index, len) in target_indices {
2313 targets.push(ProofV2Target::new(keys[index % keys.len()])
2314 .with_parent(ProofV2TargetParent::new(len)));
2315 }
2316 }
2317 let harness = TrieTestHarness::new(before);
2318 let hashed_factory = MockHashedCursorFactory::new(
2319 BTreeMap::new(),
2320 std::iter::once((harness.hashed_address(), after)).collect(),
2321 );
2322 let trie_cursor = harness.trie_cursor_factory()
2323 .storage_trie_cursor(harness.hashed_address()).unwrap();
2324 let hashed_cursor = hashed_factory
2325 .hashed_storage_cursor(harness.hashed_address()).unwrap();
2326 let mut calculator = StorageProofCalculator::new_storage(trie_cursor, hashed_cursor)
2327 .with_prefix_set(prefix_set);
2328 let proof = calculator.storage_proof(harness.hashed_address(), &mut targets).unwrap();
2329
2330 prop_assert_eq!(calculator.compute_root_hash(&proof).unwrap(), Some(expected_root));
2331 }
2332 }
2333 }
2334
2335 #[test]
2336 fn test_exact_subtrie_targets_with_root_target() {
2337 reth_tracing::init_test_tracing();
2338
2339 let slot_80 = B256::right_padding_from(&[0x80]);
2340 let slot_82 = B256::right_padding_from(&[0x82]);
2341 let slot_f0 = B256::right_padding_from(&[0xf0]);
2342 let storage = BTreeMap::from([
2343 (slot_80, U256::ONE),
2344 (slot_82, U256::from(2)),
2345 (slot_f0, U256::from(3)),
2346 ]);
2347 let targets = [
2348 ProofV2Target::new(B256::ZERO),
2349 ProofV2Target::new(slot_80).with_parent(ProofV2TargetParent::new(1)),
2350 ];
2351
2352 let harness = ProofTestHarness::new(storage);
2353 harness.assert_proof(targets).expect("Proof generation failed");
2354 }
2355
2356 #[test]
2357 fn test_rebases_singleton_subtrie_root_below_known_parent() {
2358 let slot = B256::right_padding_from(&[0xae, 0xd4, 0x09]);
2359 let slot_nibbles = Nibbles::unpack(slot);
2360 let harness = ProofTestHarness::new(BTreeMap::from([(slot, U256::ONE)]));
2361 let mut targets = [ProofV2Target::new(slot).with_parent(ProofV2TargetParent::new(3))];
2362
2363 let (proof, root) = harness.proof_v2(&mut targets);
2364
2365 assert!(root.is_none());
2366 assert_eq!(proof.len(), 1);
2367 assert_eq!(proof[0].path, slot_nibbles.slice(0..4));
2368 let TrieNodeV2::Leaf(leaf) = &proof[0].node else {
2369 panic!("singleton subtrie root should remain a leaf")
2370 };
2371 assert_eq!(leaf.key, slot_nibbles.slice(4..));
2372 }
2373
2374 #[test]
2375 fn test_rebases_singleton_leaf_at_max_parent_depth() {
2376 let slot = B256::repeat_byte(0xae);
2377 let slot_nibbles = Nibbles::unpack(slot);
2378 let harness = ProofTestHarness::new(BTreeMap::from([(slot, U256::ONE)]));
2379 let mut targets = [ProofV2Target::new(slot).with_parent(ProofV2TargetParent::new(63))];
2380
2381 let (proof, root) = harness.proof_v2(&mut targets);
2382
2383 assert!(root.is_none());
2384 assert_eq!(proof.len(), 1);
2385 assert_eq!(proof[0].path, slot_nibbles);
2386 let TrieNodeV2::Leaf(leaf) = &proof[0].node else {
2387 panic!("singleton subtrie root should remain a leaf")
2388 };
2389 assert!(leaf.key.is_empty());
2390 }
2391
2392 #[test]
2393 fn test_root_and_root_parent_targets_retain_both_singleton_representations() {
2394 let slot = B256::right_padding_from(&[0x20]);
2395 let slot_nibbles = Nibbles::unpack(slot);
2396 let harness = ProofTestHarness::new(BTreeMap::from([(slot, U256::ONE)]));
2397 let mut targets = [
2398 ProofV2Target::new(slot),
2399 ProofV2Target::new(slot).with_parent(ProofV2TargetParent::new(0)),
2400 ];
2401
2402 let (proof, root) = harness.proof_v2(&mut targets);
2403
2404 assert_eq!(root, Some(harness.original_root()));
2405 let root_node = proof.iter().find(|node| node.path.is_empty()).expect("root proof");
2406 let TrieNodeV2::Leaf(root_leaf) = &root_node.node else { panic!("root should be a leaf") };
2407 assert_eq!(root_leaf.key, slot_nibbles);
2408
2409 let child_path = slot_nibbles.slice(0..1);
2410 let child_node =
2411 proof.iter().find(|node| node.path == child_path).expect("rebased root child proof");
2412 let TrieNodeV2::Leaf(child_leaf) = &child_node.node else {
2413 panic!("root child should be a leaf")
2414 };
2415 assert_eq!(child_leaf.key, slot_nibbles.slice(1..));
2416 }
2417
2418 #[test]
2419 fn test_exhausted_cursor_resets_at_equal_target_boundary() {
2420 let first = B256::ZERO;
2421 let last = B256::with_last_byte(1);
2422 let last_nibbles = Nibbles::unpack(last);
2423 let harness =
2424 ProofTestHarness::new(BTreeMap::from([(first, U256::ONE), (last, U256::from(2))]));
2425 let mut targets = [
2426 ProofV2Target::new(first),
2427 ProofV2Target::new(last).with_parent(ProofV2TargetParent::new(63)),
2428 ];
2429
2430 let (proof, root) = harness.proof_v2(&mut targets);
2431
2432 assert_eq!(root, Some(harness.original_root()));
2433 let child = proof
2434 .iter()
2435 .find(|node| node.path == last_nibbles)
2436 .expect("depth-63 target child proof");
2437 let TrieNodeV2::Leaf(leaf) = &child.node else { panic!("target child should be a leaf") };
2438 assert!(leaf.key.is_empty());
2439 }
2440
2441 #[test]
2442 fn test_rebases_compressed_branch_subtrie_root() {
2443 let slot_a = B256::right_padding_from(&[0xae, 0xd4, 0x00]);
2444 let slot_b = B256::right_padding_from(&[0xae, 0xd4, 0x10]);
2445 let slot_nibbles = Nibbles::unpack(slot_a);
2446 let harness =
2447 ProofTestHarness::new(BTreeMap::from([(slot_a, U256::ONE), (slot_b, U256::from(2))]));
2448 let mut targets = [ProofV2Target::new(slot_a).with_parent(ProofV2TargetParent::new(3))];
2449
2450 let (proof, root) = harness.proof_v2(&mut targets);
2451
2452 assert!(root.is_none());
2453 let branch_path = slot_nibbles.slice(0..4);
2454 let branch_node =
2455 proof.iter().find(|node| node.path == branch_path).expect("rebased compressed branch");
2456 let TrieNodeV2::Branch(branch) = &branch_node.node else {
2457 panic!("rebased node should be a branch")
2458 };
2459 assert!(branch.key.is_empty());
2460 assert!(branch.branch_rlp_node.is_none());
2461 }
2462
2463 #[test]
2464 fn test_discards_reconstructed_known_parent_branch() {
2465 let slot_a = B256::right_padding_from(&[0xae, 0xd2]);
2466 let slot_b = B256::right_padding_from(&[0xae, 0xd4]);
2467 let slot_nibbles = Nibbles::unpack(slot_a);
2468 let harness =
2469 ProofTestHarness::new(BTreeMap::from([(slot_a, U256::ONE), (slot_b, U256::from(2))]));
2470 let mut targets = [ProofV2Target::new(slot_a).with_parent(ProofV2TargetParent::new(3))];
2471
2472 let (proof, root) = harness.proof_v2(&mut targets);
2473
2474 assert!(root.is_none());
2475 assert!(!proof.iter().any(|node| node.path == slot_nibbles.slice(0..3)));
2476 assert!(proof.iter().any(|node| node.path == slot_nibbles.slice(0..4)));
2477 }
2478
2479 #[test]
2480 fn test_rebased_root_matches_direct_child_not_full_short_key() {
2481 let stored_slot = B256::right_padding_from(&[0xae, 0xd4, 0x09]);
2482 let same_child_target = B256::right_padding_from(&[0xae, 0xd4, 0xff]);
2483 let other_child_target = B256::right_padding_from(&[0xae, 0xd5]);
2484 let harness = ProofTestHarness::new(BTreeMap::from([(stored_slot, U256::ONE)]));
2485
2486 let mut same_child =
2487 [ProofV2Target::new(same_child_target).with_parent(ProofV2TargetParent::new(3))];
2488 let (proof, _) = harness.proof_v2(&mut same_child);
2489 assert_eq!(proof.len(), 1, "divergent leaf proves absence below the same child");
2490
2491 let mut other_child =
2492 [ProofV2Target::new(other_child_target).with_parent(ProofV2TargetParent::new(3))];
2493 let (proof, _) = harness.proof_v2(&mut other_child);
2494 assert!(proof.is_empty(), "a different direct child is unrelated to the target");
2495 }
2496
2497 #[test]
2498 fn test_known_parent_sibling_span_retains_only_target_children() {
2499 let stored_slot_a = B256::right_padding_from(&[0xea, 0x53]);
2500 let stored_slot_b = B256::right_padding_from(&[0xeb, 0x53]);
2501 let stored_slot_c = B256::right_padding_from(&[0xec, 0x53]);
2502 let target_a = B256::right_padding_from(&[0xea, 0x1f]);
2503 let target_c = B256::right_padding_from(&[0xec, 0x1f]);
2504 let harness = ProofTestHarness::new(BTreeMap::from([
2505 (stored_slot_a, U256::ONE),
2506 (stored_slot_b, U256::from(2)),
2507 (stored_slot_c, U256::from(3)),
2508 ]));
2509 let mut targets = [target_a, target_c]
2510 .map(|target| ProofV2Target::new(target).with_parent(ProofV2TargetParent::new(1)));
2511
2512 let (proof, root) = harness.proof_v2(&mut targets);
2513
2514 assert!(root.is_none());
2515 assert_eq!(
2516 proof.iter().map(|node| node.path).collect::<Vec<_>>(),
2517 [Nibbles::from_nibbles([0xe, 0xa]), Nibbles::from_nibbles([0xe, 0xc])]
2518 );
2519 }
2520
2521 #[test]
2522 fn test_known_parent_does_not_use_stale_parent_mask() {
2523 let stored_slot_a = B256::right_padding_from(&[0xea, 0x53]);
2524 let stored_slot = B256::right_padding_from(&[0xeb, 0x53]);
2525 let stored_slot_c = B256::right_padding_from(&[0xec, 0x53]);
2526 let target = B256::right_padding_from(&[0xeb, 0x1f]);
2527 let stored_slot_nibbles = Nibbles::unpack(stored_slot);
2528
2529 let stale_parent_mask = TrieMask::new((1 << 0xa) | (1 << 0xc));
2533 let stale_parent = BranchNodeCompact::new(
2534 stale_parent_mask,
2535 TrieMask::new(0),
2536 TrieMask::new(0),
2537 Vec::new(),
2538 None,
2539 );
2540 let storage_nodes = BTreeMap::from([(Nibbles::from_nibbles([0xe]), stale_parent)]);
2541
2542 let mut harness = TrieTestHarness::new(BTreeMap::from([
2543 (stored_slot_a, U256::ONE),
2544 (stored_slot, U256::from(2)),
2545 (stored_slot_c, U256::from(3)),
2546 ]));
2547 harness.set_trie_nodes(storage_nodes);
2548
2549 let mut targets = [ProofV2Target::new(target).with_parent(ProofV2TargetParent::new(1))];
2550 let (proof, root) = harness.proof_v2(&mut targets);
2551
2552 assert!(root.is_none());
2553 assert_eq!(proof.len(), 1);
2554 assert_eq!(proof[0].path, stored_slot_nibbles.slice(0..2));
2555 let TrieNodeV2::Leaf(leaf) = &proof[0].node else {
2556 panic!("live direct child should be reconstructed as a leaf")
2557 };
2558 assert_eq!(leaf.key, stored_slot_nibbles.slice(2..));
2559 }
2560
2561 #[test]
2562 fn test_empty_storage_respects_parent_context() {
2563 let harness = ProofTestHarness::new(BTreeMap::new());
2564 let slot = B256::ZERO;
2565
2566 let mut partial_target =
2567 [ProofV2Target::new(slot).with_parent(ProofV2TargetParent::new(0))];
2568 let (partial_proof, partial_root) = harness.proof_v2(&mut partial_target);
2569 assert!(partial_proof.is_empty());
2570 assert!(partial_root.is_none());
2571
2572 let mut root_target = [ProofV2Target::new(slot)];
2573 let (root_proof, root) = harness.proof_v2(&mut root_target);
2574 assert_eq!(root_proof.len(), 1);
2575 assert!(matches!(root_proof[0].node, TrieNodeV2::EmptyRoot));
2576 assert_eq!(root, Some(EMPTY_ROOT_HASH));
2577 }
2578
2579 #[test]
2580 fn test_big_trie() {
2581 use rand::prelude::*;
2582
2583 reth_tracing::init_test_tracing();
2584 let mut rng = rand::rngs::SmallRng::seed_from_u64(1);
2585
2586 let mut rand_b256 = || {
2587 let mut buf: [u8; 32] = [0; 32];
2588 rng.fill_bytes(&mut buf);
2589 B256::from_slice(&buf)
2590 };
2591
2592 let mut storage = BTreeMap::new();
2594 for _ in 0..10240 {
2595 let hashed_slot = rand_b256();
2596 storage.insert(hashed_slot, U256::ONE);
2597 }
2598
2599 let mut targets = storage.keys().copied().collect::<Vec<_>>();
2602 for _ in 0..storage.len() / 5 {
2603 targets.push(rand_b256());
2604 }
2605 targets.sort();
2606
2607 let harness = ProofTestHarness::new(storage);
2609
2610 harness
2611 .assert_proof(targets.into_iter().map(ProofV2Target::new))
2612 .expect("Proof generation failed");
2613 }
2614
2615 #[test]
2616 fn test_node_with_masked_empty_child() {
2617 reth_tracing::init_test_tracing();
2618
2619 let val = U256::from(42u64);
2620
2621 let slot_60 = B256::right_padding_from(&[0x60]);
2624 let slot_61 = B256::right_padding_from(&[0x61]);
2625 let slot_65 = B256::right_padding_from(&[0x65]);
2626 let slot_67 = B256::right_padding_from(&[0x67]);
2627
2628 let state_mask = TrieMask::new(0b10101011); let hash_mask = TrieMask::new(0b10100011); let hashes = vec![B256::repeat_byte(0xaa); hash_mask.count_ones() as usize];
2634 let branch = BranchNodeCompact::new(state_mask, TrieMask::new(0), hash_mask, hashes, None);
2635
2636 let storage_nodes: BTreeMap<Nibbles, BranchNodeCompact> =
2637 std::iter::once((Nibbles::from_nibbles([0x6]), branch)).collect();
2638
2639 let mut harness = TrieTestHarness::new(
2642 [slot_60, slot_61, slot_65, slot_67].iter().map(|s| (*s, val)).collect(),
2643 );
2644 harness.set_trie_nodes(storage_nodes);
2645
2646 let storage_trie_cursor =
2647 harness.trie_cursor_factory().storage_trie_cursor(harness.hashed_address()).unwrap();
2648 let hashed_storage_cursor = harness
2649 .hashed_cursor_factory()
2650 .hashed_storage_cursor(harness.hashed_address())
2651 .unwrap();
2652 let mut calculator =
2653 StorageProofCalculator::new_storage(storage_trie_cursor, hashed_storage_cursor);
2654 let root_node = calculator
2655 .storage_root_node(harness.hashed_address())
2656 .expect("storage_root_node should succeed with masked empty child");
2657
2658 let root_hash = calculator.compute_root_hash(core::slice::from_ref(&root_node)).unwrap();
2659 assert!(root_hash.is_some(), "should produce a root hash");
2660 }
2661
2662 #[test]
2672 fn test_node_with_masked_empty_child_lower_bound_past_branch() {
2673 reth_tracing::init_test_tracing();
2674
2675 let val = U256::from(42u64);
2676
2677 let slot_60 = B256::right_padding_from(&[0x60]);
2679 let slot_61 = B256::right_padding_from(&[0x61]);
2680 let slot_6f = B256::right_padding_from(&[0x6f]);
2681 let slot_70 = B256::right_padding_from(&[0x70]);
2682
2683 let state_mask = TrieMask::new(0b1000_0000_0010_0011); let hash_mask = TrieMask::new(0b0000_0000_0000_0011); let hashes = vec![B256::repeat_byte(0xaa); hash_mask.count_ones() as usize];
2689 let branch = BranchNodeCompact::new(state_mask, TrieMask::new(0), hash_mask, hashes, None);
2690
2691 let storage_nodes: BTreeMap<Nibbles, BranchNodeCompact> =
2692 std::iter::once((Nibbles::from_nibbles([0x6]), branch)).collect();
2693
2694 let mut harness = TrieTestHarness::new(
2696 [slot_60, slot_61, slot_6f, slot_70].iter().map(|s| (*s, val)).collect(),
2697 );
2698 harness.set_trie_nodes(storage_nodes);
2699
2700 let storage_trie_cursor =
2701 harness.trie_cursor_factory().storage_trie_cursor(harness.hashed_address()).unwrap();
2702 let hashed_storage_cursor = harness
2703 .hashed_cursor_factory()
2704 .hashed_storage_cursor(harness.hashed_address())
2705 .unwrap();
2706 let mut calculator =
2707 StorageProofCalculator::new_storage(storage_trie_cursor, hashed_storage_cursor);
2708 let root_node = calculator
2709 .storage_root_node(harness.hashed_address())
2710 .expect("storage_root_node should succeed when lower bound advances past branch");
2711
2712 let root_hash = calculator.compute_root_hash(core::slice::from_ref(&root_node)).unwrap();
2713 assert!(root_hash.is_some(), "should produce a root hash");
2714 }
2715
2716 #[test]
2726 fn test_prefix_set_adds_child_nibbles() {
2727 reth_tracing::init_test_tracing();
2728
2729 let val = U256::from(42u64);
2730 let slot_60 = B256::right_padding_from(&[0x60]);
2731 let slot_61 = B256::right_padding_from(&[0x61]);
2732 let slot_63 = B256::right_padding_from(&[0x63]);
2733
2734 let harness = TrieTestHarness::new([(slot_60, val), (slot_61, val)].into_iter().collect());
2735
2736 let changeset: BTreeMap<B256, U256> = std::iter::once((slot_63, val)).collect();
2737 let (expected_root, _) = harness.get_root_with_updates(&changeset);
2738
2739 let mut updated_storage = harness.storage().clone();
2740 updated_storage.insert(slot_63, val);
2741
2742 let updated_hashed = MockHashedCursorFactory::new(
2743 BTreeMap::new(),
2744 std::iter::once((harness.hashed_address(), updated_storage)).collect(),
2745 );
2746
2747 let mut prefix_set = PrefixSetMut::default();
2748 prefix_set.insert(Nibbles::unpack(slot_63));
2749
2750 let trie_cursor =
2751 harness.trie_cursor_factory().storage_trie_cursor(harness.hashed_address()).unwrap();
2752 let hashed_cursor = updated_hashed.hashed_storage_cursor(harness.hashed_address()).unwrap();
2753 let mut calculator = StorageProofCalculator::new_storage(trie_cursor, hashed_cursor)
2754 .with_prefix_set(prefix_set.freeze());
2755 let root_node = calculator
2756 .storage_root_node(harness.hashed_address())
2757 .expect("storage_root_node should succeed with prefix set adding child nibbles");
2758 let got_root =
2759 calculator.compute_root_hash(core::slice::from_ref(&root_node)).unwrap().unwrap();
2760
2761 pretty_assertions::assert_eq!(
2762 expected_root,
2763 got_root,
2764 "Root hash with prefix set should match fresh computation"
2765 );
2766 }
2767
2768 #[test]
2777 fn test_prefix_set_invalidates_cached_hash() {
2778 reth_tracing::init_test_tracing();
2779
2780 let original_val = U256::from(42u64);
2781 let updated_val = U256::from(9999u64);
2782 let slot_60 = B256::right_padding_from(&[0x60]);
2783 let slot_61 = B256::right_padding_from(&[0x61]);
2784 let slot_65 = B256::right_padding_from(&[0x65]);
2785
2786 let harness = TrieTestHarness::new(
2787 [(slot_60, original_val), (slot_61, original_val), (slot_65, original_val)]
2788 .into_iter()
2789 .collect(),
2790 );
2791
2792 let changeset: BTreeMap<B256, U256> = std::iter::once((slot_65, updated_val)).collect();
2793 let (expected_root, _) = harness.get_root_with_updates(&changeset);
2794
2795 let mut updated_storage = harness.storage().clone();
2796 updated_storage.insert(slot_65, updated_val);
2797
2798 let updated_hashed = MockHashedCursorFactory::new(
2799 BTreeMap::new(),
2800 std::iter::once((harness.hashed_address(), updated_storage)).collect(),
2801 );
2802
2803 let mut prefix_set = PrefixSetMut::default();
2804 prefix_set.insert(Nibbles::unpack(slot_65));
2805
2806 let trie_cursor =
2807 harness.trie_cursor_factory().storage_trie_cursor(harness.hashed_address()).unwrap();
2808 let hashed_cursor = updated_hashed.hashed_storage_cursor(harness.hashed_address()).unwrap();
2809 let mut calculator = StorageProofCalculator::new_storage(trie_cursor, hashed_cursor)
2810 .with_prefix_set(prefix_set.freeze());
2811 let root_node = calculator
2812 .storage_root_node(harness.hashed_address())
2813 .expect("storage_root_node should succeed with prefix set invalidating cached hash");
2814 let got_root =
2815 calculator.compute_root_hash(core::slice::from_ref(&root_node)).unwrap().unwrap();
2816
2817 pretty_assertions::assert_eq!(
2818 expected_root,
2819 got_root,
2820 "Root hash with prefix set invalidation should match fresh computation"
2821 );
2822 }
2823
2824 fn b256(s: &str) -> B256 {
2825 B256::from_slice(&alloy_primitives::hex::decode(s).expect("valid hex string"))
2826 }
2827
2828 #[test]
2829 fn test_prefix_set_root_proof_processes_sibling_after_cached_descendant() {
2830 reth_tracing::init_test_tracing();
2831
2832 let storage = [
2833 ("1022c69e9d900e40775cd387c134899f465f291dbc3c97899ff6bfb8dc972b37", 45u64),
2834 ("1111ad8083c8a3a398b2b781217b989ff4d1ed182f46cc765eda49a7b316139d", 60),
2835 ("12012d20943649899b2fc0f87b9840b70ef68e93613aac17c269bf8c5a78a712", 17),
2836 ("12014b57b9a162c03d072eb6acd4e936f1c4bc23b803a054347c5ee9a9bcfb9a", 49),
2837 ("1203f800840af3f898ab4572f2750106a7c4bd2b3e844b6e7fa72704673cc2c6", 76),
2838 ("12208f18fbcd6971c92808721392acbf11d5af58e9143a374cc86e70bdd1f097", 10),
2839 ]
2840 .into_iter()
2841 .map(|(key, value)| (b256(key), U256::from(value)))
2842 .collect();
2843
2844 let dirty = b256("12208f18fbcd6971c92808721392acbf11d5af58e9143a374cc86e70bdd1f097");
2845 let harness = ProofTestHarness::new(storage);
2846 let expected_root = harness.original_root();
2847
2848 let mut prefix_set = PrefixSetMut::default();
2849 prefix_set.insert(Nibbles::unpack(dirty));
2850
2851 pretty_assertions::assert_eq!(
2852 Some(expected_root),
2853 harness.root_with_prefix_set(prefix_set.freeze()),
2854 "root proof must process a prefix-set sibling after a cached descendant",
2855 );
2856 }
2857
2858 #[test]
2859 fn test_prefix_set_root_proof_processes_trailing_dirty_sibling() {
2860 reth_tracing::init_test_tracing();
2861
2862 let keys = [
2863 "0022001020000000000000000000000000000000000000000000000000000000",
2864 "0110212112000000000000000000000000000000000000000000000000000000",
2865 "0202210210000000000000000000000000000000000000000000000000000000",
2866 "0211020211000000000000000000000000000000000000000000000000000000",
2867 "0211211002000000000000000000000000000000000000000000000000000000",
2868 "0212221010000000000000000000000000000000000000000000000000000000",
2869 "0222011102000000000000000000000000000000000000000000000000000000",
2870 ];
2871 let storage =
2872 keys.iter().enumerate().map(|(i, key)| (b256(key), U256::from(i as u64 + 1))).collect();
2873 let harness = ProofTestHarness::new(storage);
2874 let expected_root = harness.original_root();
2875
2876 let mut prefix_set = PrefixSetMut::default();
2879 prefix_set.insert(Nibbles::unpack(b256(keys[2])));
2880 prefix_set.insert(Nibbles::unpack(b256(keys[6])));
2881
2882 pretty_assertions::assert_eq!(
2883 Some(expected_root),
2884 harness.root_with_prefix_set(prefix_set.freeze()),
2885 );
2886 }
2887
2888 #[test]
2889 fn test_prefix_set_preserves_cached_siblings() {
2890 let storage = (0..4096u16)
2891 .map(|key| (B256::right_padding_from(&key.to_be_bytes()), U256::from(1)))
2892 .collect();
2893 let harness = TrieTestHarness::new(storage);
2894 let changes = BTreeMap::from([
2895 (B256::right_padding_from(&[0x04, 0x00]), U256::from(2)),
2896 (B256::right_padding_from(&[0x04, 0xf0]), U256::from(2)),
2897 ]);
2898 let (expected_root, _) = harness.get_root_with_updates(&changes);
2899 let mut updated_storage = harness.storage().clone();
2900 updated_storage.extend(changes.clone());
2901 let hashed_factory = MockHashedCursorFactory::new(
2902 BTreeMap::new(),
2903 std::iter::once((harness.hashed_address(), updated_storage)).collect(),
2904 );
2905 let prefix_set = PrefixSetMut::from(changes.keys().map(Nibbles::unpack)).freeze();
2906 let trie_cursor =
2907 harness.trie_cursor_factory().storage_trie_cursor(harness.hashed_address()).unwrap();
2908 let hashed_cursor = hashed_factory.hashed_storage_cursor(harness.hashed_address()).unwrap();
2909 let mut calculator = StorageProofCalculator::new_storage(trie_cursor, hashed_cursor)
2910 .with_prefix_set(prefix_set);
2911 let mut targets = [ProofV2Target::new(B256::ZERO)];
2912 let proof = calculator.storage_proof(harness.hashed_address(), &mut targets).unwrap();
2913
2914 assert_eq!(calculator.compute_root_hash(&proof).unwrap(), Some(expected_root));
2915
2916 let clean_key = B256::right_padding_from(&[0x04, 0x88]);
2918 assert!(!hashed_factory
2919 .visited_storage_keys(harness.hashed_address())
2920 .iter()
2921 .any(|visit| visit.visited_key == Some(clean_key)));
2922 }
2923
2924 #[test]
2925 fn test_prefix_set_finishes_branch_before_returning_to_ancestor() {
2926 let storage =
2927 [[0x10, 0x00, 0x00], [0x12, 0x10, 0x00], [0x12, 0x10, 0x20], [0x12, 0x11, 0x00]]
2928 .into_iter()
2929 .map(|key| (B256::right_padding_from(&key), U256::from(1)))
2930 .collect();
2931 let harness = TrieTestHarness::new(storage);
2932 let changes = BTreeMap::from([
2933 (B256::right_padding_from(&[0x12, 0x00]), U256::from(1)),
2934 (B256::right_padding_from(&[0x12, 0x20]), U256::from(1)),
2935 ]);
2936 let mut after = harness.storage().clone();
2937 after.extend(changes.clone());
2938 let expected_root = crate::test_utils::storage_root_prehashed(
2939 after.iter().map(|(key, value)| (*key, *value)),
2940 );
2941 let hashed_factory = MockHashedCursorFactory::new(
2942 BTreeMap::new(),
2943 std::iter::once((harness.hashed_address(), after)).collect(),
2944 );
2945 let trie_cursor =
2946 harness.trie_cursor_factory().storage_trie_cursor(harness.hashed_address()).unwrap();
2947 let hashed_cursor = hashed_factory.hashed_storage_cursor(harness.hashed_address()).unwrap();
2948 let mut calculator = StorageProofCalculator::new_storage(trie_cursor, hashed_cursor)
2949 .with_prefix_set(PrefixSetMut::from(changes.keys().map(Nibbles::unpack)).freeze());
2950 let mut targets = [
2951 ProofV2Target::new(B256::ZERO),
2952 ProofV2Target::new(B256::right_padding_from(&[0x10]))
2953 .with_parent(ProofV2TargetParent::new(0)),
2954 ];
2955
2956 let proof = calculator.storage_proof(harness.hashed_address(), &mut targets).unwrap();
2958 assert_eq!(calculator.compute_root_hash(&proof).unwrap(), Some(expected_root));
2959 }
2960
2961 fn storage_leaf_hash(short_key: &Nibbles, value: &U256) -> B256 {
2964 let mut buf = Vec::new();
2965 alloy_trie::nodes::LeafNodeRef::new(short_key, &alloy_rlp::encode_fixed_size(value))
2966 .encode(&mut buf);
2967 keccak256(&buf)
2968 }
2969
2970 fn assert_branch_collapse(remaining_nibble: u8, removed_nibble: u8) {
2972 reth_tracing::init_test_tracing();
2973
2974 let val = U256::ONE;
2975 let child_keys = |nibble| {
2976 [
2977 B256::right_padding_from(&[0x20 | nibble, 0x00]),
2978 B256::right_padding_from(&[0x20 | nibble, 0x10]),
2979 ]
2980 };
2981 let [remaining_a, remaining_b] = child_keys(remaining_nibble);
2982 let [removed_a, removed_b] = child_keys(removed_nibble);
2983
2984 let initial_storage = BTreeMap::from([
2987 (remaining_a, val),
2988 (remaining_b, val),
2989 (removed_a, val),
2990 (removed_b, val),
2991 ]);
2992 let harness = TrieTestHarness::new(initial_storage);
2993
2994 let cached_branch = &harness
2995 .storage_trie_updates()
2996 .storage_nodes
2997 .get(&Nibbles::from_nibbles([0x2]))
2998 .expect("branch at 0x2");
2999 let child_mask =
3000 TrieMask::from_nibble(remaining_nibble) | TrieMask::from_nibble(removed_nibble);
3001 assert_eq!(cached_branch.state_mask, child_mask);
3002 assert_eq!(cached_branch.hash_mask, child_mask);
3003 assert!(cached_branch.tree_mask.is_empty());
3004
3005 let final_storage = BTreeMap::from([(remaining_a, val), (remaining_b, val)]);
3006 let expected_root = TrieTestHarness::new(final_storage.clone()).original_root();
3007 let updated_hashed = MockHashedCursorFactory::new(
3008 BTreeMap::new(),
3009 std::iter::once((harness.hashed_address(), final_storage)).collect(),
3010 );
3011
3012 let mut prefix_set = PrefixSetMut::default();
3014 prefix_set.insert(Nibbles::unpack(removed_a));
3015 prefix_set.insert(Nibbles::unpack(removed_b));
3016
3017 let trie_cursor =
3018 harness.trie_cursor_factory().storage_trie_cursor(harness.hashed_address()).unwrap();
3019 let hashed_cursor = updated_hashed.hashed_storage_cursor(harness.hashed_address()).unwrap();
3020 let mut calculator = StorageProofCalculator::new_storage(trie_cursor, hashed_cursor)
3021 .with_prefix_set(prefix_set.freeze());
3022 let root_node = calculator
3023 .storage_root_node(harness.hashed_address())
3024 .expect("storage_root_node should succeed after branch collapse");
3025 let root =
3026 calculator.compute_root_hash(core::slice::from_ref(&root_node)).unwrap().unwrap();
3027
3028 pretty_assertions::assert_eq!(expected_root, root);
3029 }
3030
3031 #[test]
3032 fn test_branch_collapse_removed_child_before_remaining() {
3033 assert_branch_collapse(1, 0);
3034 }
3035
3036 #[test]
3037 fn test_branch_collapse_removed_child_after_remaining() {
3038 assert_branch_collapse(4, 9);
3039 }
3040
3041 #[test]
3042 fn test_prefix_set_root_proof_preserves_clean_sibling_after_cached_branch_collapse() {
3043 reth_tracing::init_test_tracing();
3044
3045 let dirty = B256::right_padding_from(&[0x10, 0x00, 0x10]);
3046 let clean_sibling = B256::right_padding_from(&[0x10, 0x10]);
3047 let storage = [
3048 B256::right_padding_from(&[0x10]),
3049 dirty,
3050 B256::right_padding_from(&[0x10, 0x01]),
3051 B256::right_padding_from(&[0x10, 0x02]),
3052 clean_sibling,
3053 B256::right_padding_from(&[0x11]),
3054 B256::right_padding_from(&[0x12]),
3055 ]
3056 .into_iter()
3057 .map(|key| (key, U256::ONE))
3058 .collect();
3059
3060 let harness = ProofTestHarness::new(storage);
3061 let expected_root = harness.original_root();
3062
3063 let mut prefix_set = PrefixSetMut::default();
3064 prefix_set.insert(Nibbles::unpack(dirty));
3065
3066 let mut prefix_set_with_sibling = PrefixSetMut::default();
3067 prefix_set_with_sibling.insert(Nibbles::unpack(dirty));
3068 prefix_set_with_sibling.insert(Nibbles::unpack(clean_sibling));
3069
3070 pretty_assertions::assert_eq!(
3071 Some(expected_root),
3072 harness.root_with_prefix_set(prefix_set_with_sibling.freeze()),
3073 );
3074 pretty_assertions::assert_eq!(
3075 Some(expected_root),
3076 harness.root_with_prefix_set(prefix_set.freeze()),
3077 "a dirty prefix must not omit a clean sibling after collapsing a cached branch",
3078 );
3079 }
3080
3081 #[test]
3082 fn test_prefix_set_range_skips_covered_cached_branch() {
3083 reth_tracing::init_test_tracing();
3084
3085 let before = B256::right_padding_from(&[0x30]);
3086 let cached_a = B256::right_padding_from(&[0x80, 0x10]);
3087 let cached_b = B256::right_padding_from(&[0x80, 0x15]);
3088 let dirty = B256::right_padding_from(&[0x80, 0xc0]);
3089 let after = B256::right_padding_from(&[0x90]);
3090
3091 let storage = [before, cached_a, cached_b, dirty, after]
3094 .into_iter()
3095 .enumerate()
3096 .map(|(i, key)| (key, U256::from(i + 1)))
3097 .collect();
3098
3099 let harness = ProofTestHarness::new(storage);
3100 let expected_root = harness.original_root();
3101 let mut prefix_set = PrefixSetMut::default();
3102 prefix_set.insert(Nibbles::unpack(dirty));
3103
3104 pretty_assertions::assert_eq!(
3105 Some(expected_root),
3106 harness.root_with_prefix_set(prefix_set.freeze()),
3107 );
3108 }
3109
3110 #[test]
3111 fn test_cached_branch_extension_skips_diverging_target() {
3112 reth_tracing::init_test_tracing();
3113
3114 let val = U256::from(100u64);
3115
3116 let key_a0 = B256::right_padding_from(&[0x6a, 0x30]); let key_a1 = B256::right_padding_from(&[0x6a, 0x31]); let key_c = B256::right_padding_from(&[0x6a, 0x80]); let key_d = B256::right_padding_from(&[0x6b, 0x00]); let key_e = B256::right_padding_from(&[0x6c, 0x00]); let all_storage: BTreeMap<B256, U256> =
3125 [(key_a0, val), (key_a1, val), (key_c, val), (key_d, val), (key_e, val)]
3126 .into_iter()
3127 .collect();
3128 let correct_harness = TrieTestHarness::new(all_storage.clone());
3129 let expected_root = correct_harness.original_root();
3130
3131 let leaf_hash_a0 = storage_leaf_hash(&Nibbles::unpack(key_a0).slice(4..), &val);
3133 let leaf_hash_a1 = storage_leaf_hash(&Nibbles::unpack(key_a1).slice(4..), &val);
3134 let leaf_hash_d = storage_leaf_hash(&Nibbles::unpack(key_d).slice(2..), &val);
3135 let leaf_hash_e = storage_leaf_hash(&Nibbles::unpack(key_e).slice(2..), &val);
3136
3137 let branch_6_state_mask = TrieMask::new((1 << 0xa) | (1 << 0xb) | (1 << 0xc));
3144 let branch_6_hash_mask = TrieMask::new((1 << 0xb) | (1 << 0xc));
3145 let branch_6 = BranchNodeCompact::new(
3146 branch_6_state_mask,
3147 TrieMask::new(0),
3148 branch_6_hash_mask,
3149 vec![leaf_hash_d, leaf_hash_e],
3150 None,
3151 );
3152
3153 let branch_6a3_state_mask = TrieMask::new((1 << 0) | (1 << 1));
3157 let branch_6a3 = BranchNodeCompact::new(
3158 branch_6a3_state_mask,
3159 TrieMask::new(0),
3160 branch_6a3_state_mask,
3161 vec![leaf_hash_a0, leaf_hash_a1],
3162 None,
3163 );
3164
3165 let inconsistent_nodes: BTreeMap<Nibbles, BranchNodeCompact> = [
3167 (Nibbles::from_nibbles([0x6]), branch_6),
3168 (Nibbles::from_nibbles([0x6, 0xa, 0x3]), branch_6a3),
3169 ]
3170 .into_iter()
3171 .collect();
3172
3173 let mut harness = TrieTestHarness::new(all_storage);
3175 harness.set_trie_nodes(inconsistent_nodes);
3176
3177 let mut prefix_set = PrefixSetMut::default();
3180 prefix_set.insert(Nibbles::unpack(key_c));
3181
3182 let trie_cursor =
3184 harness.trie_cursor_factory().storage_trie_cursor(harness.hashed_address()).unwrap();
3185 let hashed_cursor = harness
3186 .hashed_cursor_factory()
3187 .hashed_storage_cursor(harness.hashed_address())
3188 .unwrap();
3189 let mut calculator = StorageProofCalculator::new_storage(trie_cursor, hashed_cursor)
3190 .with_prefix_set(prefix_set.freeze());
3191
3192 let root_node = calculator
3193 .storage_root_node(harness.hashed_address())
3194 .expect("storage_root_node should succeed");
3195 let got_root = calculator
3196 .compute_root_hash(core::slice::from_ref(&root_node))
3197 .unwrap()
3198 .expect("should produce a root hash");
3199
3200 pretty_assertions::assert_eq!(
3202 expected_root,
3203 got_root,
3204 "Root hash should match correct trie; cached extension must not skip diverging leaves"
3205 );
3206
3207 let mut targets = vec![ProofV2Target::new(key_c)];
3209 let proofs = calculator
3210 .storage_proof(harness.hashed_address(), &mut targets)
3211 .expect("storage_proof should succeed");
3212
3213 let key_c_nibbles = Nibbles::unpack(key_c);
3214 let has_matching_node = proofs.iter().any(|node| key_c_nibbles.starts_with(&node.path));
3215 assert!(
3216 has_matching_node,
3217 "Proof for key_c should contain at least one node on key_c's path, got: {proofs:?}"
3218 );
3219 }
3220
3221 #[test]
3222 fn test_cached_branch_extension_skips_diverging_target_before() {
3223 reth_tracing::init_test_tracing();
3224
3225 let val = U256::from(100u64);
3226
3227 let key_a0 = B256::right_padding_from(&[0x6a, 0x80]); let key_a1 = B256::right_padding_from(&[0x6a, 0x81]); let key_c = B256::right_padding_from(&[0x6a, 0x30]); let key_d = B256::right_padding_from(&[0x6b, 0x00]); let key_e = B256::right_padding_from(&[0x6c, 0x00]); let all_storage: BTreeMap<B256, U256> =
3237 [(key_a0, val), (key_a1, val), (key_c, val), (key_d, val), (key_e, val)]
3238 .into_iter()
3239 .collect();
3240 let correct_harness = TrieTestHarness::new(all_storage.clone());
3241 let expected_root = correct_harness.original_root();
3242
3243 let leaf_hash_a0 = storage_leaf_hash(&Nibbles::unpack(key_a0).slice(4..), &val);
3245 let leaf_hash_a1 = storage_leaf_hash(&Nibbles::unpack(key_a1).slice(4..), &val);
3246 let leaf_hash_d = storage_leaf_hash(&Nibbles::unpack(key_d).slice(2..), &val);
3247 let leaf_hash_e = storage_leaf_hash(&Nibbles::unpack(key_e).slice(2..), &val);
3248
3249 let branch_6_state_mask = TrieMask::new((1 << 0xa) | (1 << 0xb) | (1 << 0xc));
3256 let branch_6_hash_mask = TrieMask::new((1 << 0xb) | (1 << 0xc));
3257 let branch_6 = BranchNodeCompact::new(
3258 branch_6_state_mask,
3259 TrieMask::new(0),
3260 branch_6_hash_mask,
3261 vec![leaf_hash_d, leaf_hash_e],
3262 None,
3263 );
3264
3265 let branch_6a8_state_mask = TrieMask::new((1 << 0) | (1 << 1));
3269 let branch_6a8 = BranchNodeCompact::new(
3270 branch_6a8_state_mask,
3271 TrieMask::new(0),
3272 branch_6a8_state_mask,
3273 vec![leaf_hash_a0, leaf_hash_a1],
3274 None,
3275 );
3276
3277 let inconsistent_nodes: BTreeMap<Nibbles, BranchNodeCompact> = [
3279 (Nibbles::from_nibbles([0x6]), branch_6),
3280 (Nibbles::from_nibbles([0x6, 0xa, 0x8]), branch_6a8),
3281 ]
3282 .into_iter()
3283 .collect();
3284
3285 let mut harness = TrieTestHarness::new(all_storage);
3287 harness.set_trie_nodes(inconsistent_nodes);
3288
3289 let mut prefix_set = PrefixSetMut::default();
3291 prefix_set.insert(Nibbles::unpack(key_c));
3292
3293 let trie_cursor =
3295 harness.trie_cursor_factory().storage_trie_cursor(harness.hashed_address()).unwrap();
3296 let hashed_cursor = harness
3297 .hashed_cursor_factory()
3298 .hashed_storage_cursor(harness.hashed_address())
3299 .unwrap();
3300 let mut calculator = StorageProofCalculator::new_storage(trie_cursor, hashed_cursor)
3301 .with_prefix_set(prefix_set.freeze());
3302
3303 let root_node = calculator
3304 .storage_root_node(harness.hashed_address())
3305 .expect("storage_root_node should succeed");
3306 let got_root = calculator
3307 .compute_root_hash(core::slice::from_ref(&root_node))
3308 .unwrap()
3309 .expect("should produce a root hash");
3310
3311 pretty_assertions::assert_eq!(
3313 expected_root,
3314 got_root,
3315 "Root hash should match correct trie; cached extension must not skip diverging leaves before cached branch"
3316 );
3317
3318 let mut targets = vec![ProofV2Target::new(key_c)];
3320 let proofs = calculator
3321 .storage_proof(harness.hashed_address(), &mut targets)
3322 .expect("storage_proof should succeed");
3323
3324 let key_c_nibbles = Nibbles::unpack(key_c);
3325 let has_matching_node = proofs.iter().any(|node| key_c_nibbles.starts_with(&node.path));
3326 assert!(
3327 has_matching_node,
3328 "Proof for key_c should contain at least one node on key_c's path, got: {proofs:?}"
3329 );
3330 }
3331
3332 #[test]
3333 fn test_skipped_parent_branch_with_unskipped_child() {
3334 reth_tracing::init_test_tracing();
3335
3336 let val = U256::ONE;
3337 let updated_val = U256::from(2u64);
3338
3339 let key_2 = B256::right_padding_from(&[0x20]);
3341 let key_2f00 = B256::right_padding_from(&[0x2f, 0x00]);
3342 let key_2f01 = B256::right_padding_from(&[0x2f, 0x01]);
3343 let key_2f10 = B256::right_padding_from(&[0x2f, 0x10]);
3344 let key_2f11 = B256::right_padding_from(&[0x2f, 0x11]);
3345 let key_300 = B256::right_padding_from(&[0x30, 0x00]);
3346 let key_301 = B256::right_padding_from(&[0x30, 0x10]);
3347 let key_310 = B256::right_padding_from(&[0x31, 0x00]);
3348 let key_311 = B256::right_padding_from(&[0x31, 0x10]);
3349 let key_500 = B256::right_padding_from(&[0x50, 0x00]);
3350 let key_501 = B256::right_padding_from(&[0x50, 0x10]);
3351 let key_510 = B256::right_padding_from(&[0x51, 0x00]);
3352 let key_511 = B256::right_padding_from(&[0x51, 0x10]);
3353
3354 let all_keys = [
3355 key_2, key_2f00, key_2f01, key_2f10, key_2f11, key_300, key_301, key_310, key_311,
3356 key_500, key_501, key_510, key_511,
3357 ];
3358
3359 let original_storage: BTreeMap<B256, U256> = all_keys.iter().map(|k| (*k, val)).collect();
3360 let harness = TrieTestHarness::new(original_storage);
3361
3362 let trie_updates = harness.storage_trie_updates();
3364 assert!(trie_updates.storage_nodes.contains_key(&Nibbles::from_nibbles([0x2])));
3365 assert!(trie_updates.storage_nodes.contains_key(&Nibbles::from_nibbles([0x2, 0xf])));
3366 assert!(trie_updates.storage_nodes.contains_key(&Nibbles::from_nibbles([0x3])));
3367
3368 let changeset: BTreeMap<B256, U256> = std::iter::once((key_2, updated_val)).collect();
3371 let (expected_root, _) = harness.get_root_with_updates(&changeset);
3372
3373 let mut updated_storage = harness.storage().clone();
3374 updated_storage.insert(key_2, updated_val);
3375
3376 let updated_hashed = MockHashedCursorFactory::new(
3377 BTreeMap::new(),
3378 std::iter::once((harness.hashed_address(), updated_storage)).collect(),
3379 );
3380
3381 let mut prefix_set = PrefixSetMut::default();
3382 prefix_set.insert(Nibbles::unpack(key_2));
3383
3384 let trie_cursor =
3385 harness.trie_cursor_factory().storage_trie_cursor(harness.hashed_address()).unwrap();
3386 let hashed_cursor = updated_hashed.hashed_storage_cursor(harness.hashed_address()).unwrap();
3387 let mut calculator = StorageProofCalculator::new_storage(trie_cursor, hashed_cursor)
3388 .with_prefix_set(prefix_set.freeze());
3389 let root_node = calculator
3390 .storage_root_node(harness.hashed_address())
3391 .expect("storage_root_node should succeed");
3392
3393 let got_root = calculator
3394 .compute_root_hash(&[root_node])
3395 .expect("root hash should succeed")
3396 .expect("root should get hashed");
3397 pretty_assertions::assert_eq!(expected_root, got_root);
3398 }
3399
3400 #[test]
3401 fn test_blinded_local_root_returns_trie_inconsistency() {
3402 let key = B256::right_padding_from(&[0x63, 0xaa]);
3403 let value = U256::ONE;
3404 let hash = storage_leaf_hash(&Nibbles::unpack(key).slice(2..), &value);
3405 let mask = TrieMask::from_nibble(3);
3406 let cached_branch =
3407 BranchNodeCompact::new(mask, TrieMask::default(), mask, vec![hash], None);
3408
3409 let mut harness = TrieTestHarness::new(BTreeMap::from([(key, value)]));
3410 harness.set_trie_nodes(BTreeMap::from([(Nibbles::from_nibbles([6]), cached_branch)]));
3411
3412 let trie_cursor =
3413 harness.trie_cursor_factory().storage_trie_cursor(harness.hashed_address()).unwrap();
3414 let hashed_cursor = harness
3415 .hashed_cursor_factory()
3416 .hashed_storage_cursor(harness.hashed_address())
3417 .unwrap();
3418 let mut calculator = StorageProofCalculator::new_storage(trie_cursor, hashed_cursor);
3419
3420 assert!(matches!(
3421 calculator.storage_root_node(harness.hashed_address()),
3422 Err(StateProofError::TrieInconsistency(_))
3423 ));
3424 }
3425
3426 #[test]
3427 fn test_cached_hash_with_deleted_leaf() {
3428 reth_tracing::init_test_tracing();
3429
3430 let val_3 = U256::from(111u64);
3432 let val_5 = U256::from(222u64);
3433 let val_8 = U256::from(333u64);
3434
3435 let key_63 = B256::right_padding_from(&[0x63, 0xaa]); let key_65 = B256::right_padding_from(&[0x65, 0xbb]); let key_68 = B256::right_padding_from(&[0x68, 0xcc]); let leaf_hash_3 = storage_leaf_hash(&Nibbles::unpack(key_63).slice(2..), &val_3);
3444 let leaf_hash_5 = storage_leaf_hash(&Nibbles::unpack(key_65).slice(2..), &val_5);
3445 let leaf_hash_8 = storage_leaf_hash(&Nibbles::unpack(key_68).slice(2..), &val_8);
3446
3447 let state_mask = TrieMask::new((1 << 3) | (1 << 5) | (1 << 8));
3449 let cached_branch = BranchNodeCompact::new(
3450 state_mask,
3451 TrieMask::new(0),
3452 state_mask, vec![leaf_hash_3, leaf_hash_5, leaf_hash_8],
3454 None,
3455 );
3456
3457 let storage_nodes: BTreeMap<Nibbles, BranchNodeCompact> =
3458 std::iter::once((Nibbles::from_nibbles([0x6]), cached_branch)).collect();
3459
3460 let mut harness =
3462 TrieTestHarness::new([(key_65, val_5), (key_68, val_8)].into_iter().collect());
3463 let expected_root = harness.original_root();
3464
3465 harness.set_trie_nodes(storage_nodes);
3467
3468 let mut prefix_set = PrefixSetMut::default();
3471 prefix_set.insert(Nibbles::unpack(key_63));
3472
3473 let mut targets = vec![ProofV2Target::new(key_63)];
3477
3478 let trie_cursor =
3479 harness.trie_cursor_factory().storage_trie_cursor(harness.hashed_address()).unwrap();
3480 let hashed_cursor = harness
3481 .hashed_cursor_factory()
3482 .hashed_storage_cursor(harness.hashed_address())
3483 .unwrap();
3484 let mut calculator = StorageProofCalculator::new_storage(trie_cursor, hashed_cursor)
3485 .with_prefix_set(prefix_set.freeze());
3486
3487 let proofs = calculator
3488 .storage_proof(harness.hashed_address(), &mut targets)
3489 .expect("storage_proof should succeed");
3490 assert_eq!(1, proofs.len());
3491 let got_root = calculator
3492 .compute_root_hash(&proofs)
3493 .expect("compute_root_hash should succeed")
3494 .expect("should produce a root hash (proof contains root node)");
3495
3496 pretty_assertions::assert_eq!(
3499 expected_root,
3500 got_root,
3501 "Root hash should match trie without key_63; cached hash index is off when \
3502 an earlier hashed child has no leaves (absence proof target)"
3503 );
3504 }
3505}