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 if sub_trie_targets.parent_prefix.is_none() {
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 let child_path = sub_trie_targets.lower_bound;
1367
1368 if !root_full_path.starts_with(&child_path) {
1370 return Err(StateProofError::TrieInconsistency(format!(
1371 "local root path {root_full_path:?} does not start with child path \
1372 {child_path:?}",
1373 )))
1374 }
1375
1376 let child_path_len = child_path.len();
1379
1380 root_node.trim_short_key_prefix(child_path_len);
1382 self.rlp_encode_buf.clear();
1383 let root_node = root_node.into_proof_trie_node(child_path, &mut self.rlp_encode_buf)?;
1384 self.retained_proofs.push(root_node);
1385
1386 Ok(())
1387 }
1388
1389 fn clear_computation_state(&mut self) {
1392 self.branch_stack.clear();
1393 self.branch_path = Nibbles::new();
1394 self.child_stack.clear();
1395 self.cached_branch_stack.clear();
1396 self.retained_proofs.clear();
1397 }
1398
1399 fn proof_inner(
1402 &mut self,
1403 value_encoder: &mut VE,
1404 targets: &mut [ProofV2Target],
1405 ) -> Result<Vec<ProofTrieNodeV2>, StateProofError> {
1406 if targets.is_empty() {
1408 trace!(target: TRACE_TARGET, "Empty targets, returning");
1409 return Ok(Vec::new())
1410 }
1411
1412 let mut trie_cursor_state = TrieCursorState::unseeked();
1415 let mut hashed_cursor_state = HashedCursorState::unseeked();
1416 let mut previous_traversal_bounds: Option<(Nibbles, Option<Nibbles>)> = None;
1417
1418 for sub_trie_targets in iter_sub_trie_targets(targets) {
1421 let traversal_lower_bound = sub_trie_targets.lower_bound;
1422 let traversal_upper_bound = sub_trie_targets.upper_bound;
1423 if previous_traversal_bounds.is_some_and(|(_, previous_upper_bound)| {
1424 previous_upper_bound.is_none_or(|upper_bound| upper_bound > traversal_lower_bound)
1425 }) {
1426 if trie_cursor_state.needs_reset_before_seek(&traversal_lower_bound) {
1427 trace!(
1428 target: TRACE_TARGET,
1429 ?previous_traversal_bounds,
1430 ?traversal_lower_bound,
1431 ?traversal_upper_bound,
1432 "Resetting trie cursor before overlapping or backward traversal range",
1433 );
1434 self.trie_cursor.reset();
1435 trie_cursor_state = TrieCursorState::unseeked();
1436 }
1437 if hashed_cursor_state.needs_reset_before_seek(&traversal_lower_bound) {
1438 trace!(
1439 target: TRACE_TARGET,
1440 ?previous_traversal_bounds,
1441 ?traversal_lower_bound,
1442 ?traversal_upper_bound,
1443 "Resetting hashed cursor before overlapping or backward traversal range",
1444 );
1445 self.hashed_cursor.reset();
1446 hashed_cursor_state = HashedCursorState::unseeked();
1447 }
1448 }
1449
1450 if let Err(err) = self.proof_subtrie(
1451 value_encoder,
1452 &mut trie_cursor_state,
1453 &mut hashed_cursor_state,
1454 sub_trie_targets,
1455 ) {
1456 self.clear_computation_state();
1457 return Err(err);
1458 }
1459
1460 previous_traversal_bounds = Some((traversal_lower_bound, traversal_upper_bound));
1461 }
1462
1463 trace!(
1464 target: TRACE_TARGET,
1465 retained_proofs_len = ?self.retained_proofs.len(),
1466 "proof_inner: returning",
1467 );
1468 self.retained_proofs.sort_unstable_by(|a, b| depth_first::cmp(&a.path, &b.path));
1469 self.retained_proofs.dedup_by(|a, b| a.path == b.path);
1470 Ok(core::mem::take(&mut self.retained_proofs))
1471 }
1472
1473 #[instrument(target = TRACE_TARGET, level = "trace", skip_all)]
1481 pub fn proof(
1482 &mut self,
1483 value_encoder: &mut VE,
1484 targets: &mut [ProofV2Target],
1485 ) -> Result<Vec<ProofTrieNodeV2>, StateProofError> {
1486 self.trie_cursor.reset();
1487 self.hashed_cursor.reset();
1488 self.proof_inner(value_encoder, targets)
1489 }
1490
1491 pub fn compute_root_hash(
1498 &mut self,
1499 proof_nodes: &[ProofTrieNodeV2],
1500 ) -> Result<Option<B256>, StateProofError> {
1501 let root_node = proof_nodes.iter().find(|node| node.path.is_empty());
1503
1504 let Some(root) = root_node else {
1505 return Ok(None);
1506 };
1507
1508 self.rlp_encode_buf.clear();
1510 root.node.encode(&mut self.rlp_encode_buf);
1511 let root_hash = keccak256(&self.rlp_encode_buf);
1512
1513 Ok(Some(root_hash))
1514 }
1515
1516 #[instrument(target = TRACE_TARGET, level = "trace", skip(self, value_encoder))]
1521 pub fn root_node(
1522 &mut self,
1523 value_encoder: &mut VE,
1524 ) -> Result<ProofTrieNodeV2, StateProofError> {
1525 self.trie_cursor.reset();
1526 self.hashed_cursor.reset();
1527
1528 let mut trie_cursor_state = TrieCursorState::unseeked();
1531 let mut hashed_cursor_state = HashedCursorState::unseeked();
1532
1533 static EMPTY_TARGETS: [ProofV2Target; 0] = [];
1534 let sub_trie_targets = SubTrieTargets {
1535 lower_bound: Nibbles::new(),
1536 upper_bound: None,
1537 parent_prefix: None,
1538 targets: &EMPTY_TARGETS,
1539 };
1540
1541 if let Err(err) = self.proof_subtrie(
1542 value_encoder,
1543 &mut trie_cursor_state,
1544 &mut hashed_cursor_state,
1545 sub_trie_targets,
1546 ) {
1547 self.clear_computation_state();
1548 return Err(err);
1549 }
1550
1551 let mut proofs = core::mem::take(&mut self.retained_proofs);
1554 trace!(
1555 target: TRACE_TARGET,
1556 proofs_len = ?proofs.len(),
1557 "root_node: extracting root",
1558 );
1559
1560 debug_assert_eq!(
1563 proofs.len(), 1,
1564 "prefix is empty, parent path is None, and targets is empty, so there must be only the root node"
1565 );
1566
1567 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");
1569
1570 Ok(root_node)
1571 }
1572}
1573
1574pub type StorageProofCalculator<TC, HC> = ProofCalculator<TC, HC, StorageValueEncoder>;
1576
1577impl<TC, HC> StorageProofCalculator<TC, HC>
1578where
1579 TC: TrieStorageCursor,
1580 HC: HashedStorageCursor<Value = U256>,
1581{
1582 pub fn new_storage(trie_cursor: TC, hashed_cursor: HC) -> Self {
1584 Self::new(trie_cursor, hashed_cursor)
1585 }
1586
1587 #[instrument(target = TRACE_TARGET, level = "trace", skip(self, targets))]
1595 pub fn storage_proof(
1596 &mut self,
1597 hashed_address: B256,
1598 targets: &mut [ProofV2Target],
1599 ) -> Result<Vec<ProofTrieNodeV2>, StateProofError> {
1600 self.hashed_cursor.set_hashed_address(hashed_address);
1601
1602 if self.hashed_cursor.is_storage_empty()? {
1604 return Ok(if targets.iter().any(|target| !target.parent.is_known()) {
1605 vec![ProofTrieNodeV2 {
1606 path: Nibbles::default(),
1607 node: TrieNodeV2::EmptyRoot,
1608 masks: None,
1609 }]
1610 } else {
1611 Vec::new()
1612 })
1613 }
1614
1615 self.trie_cursor.set_hashed_address(hashed_address);
1618
1619 let mut storage_value_encoder = StorageValueEncoder;
1621 self.proof_inner(&mut storage_value_encoder, targets)
1622 }
1623
1624 #[instrument(target = TRACE_TARGET, level = "trace", skip(self))]
1629 pub fn storage_root_node(
1630 &mut self,
1631 hashed_address: B256,
1632 ) -> Result<ProofTrieNodeV2, StateProofError> {
1633 self.hashed_cursor.set_hashed_address(hashed_address);
1634
1635 if self.hashed_cursor.is_storage_empty()? {
1636 return Ok(ProofTrieNodeV2 {
1637 path: Nibbles::default(),
1638 node: TrieNodeV2::EmptyRoot,
1639 masks: None,
1640 })
1641 }
1642
1643 self.trie_cursor.set_hashed_address(hashed_address);
1646
1647 let mut storage_value_encoder = StorageValueEncoder;
1649 self.root_node(&mut storage_value_encoder)
1650 }
1651
1652 pub fn storage_root(&mut self, hashed_address: B256) -> Result<B256, StateProofError> {
1654 let root_node = self.storage_root_node(hashed_address)?;
1655 Ok(self
1656 .compute_root_hash(core::slice::from_ref(&root_node))?
1657 .expect("storage_root_node returns a node at empty path"))
1658 }
1659}
1660
1661#[derive(Clone)]
1667struct TargetsCursor<'a> {
1668 targets: &'a [ProofV2Target],
1669 i: usize,
1670}
1671
1672impl<'a> TargetsCursor<'a> {
1673 fn new(targets: &'a [ProofV2Target]) -> Self {
1679 debug_assert!(!targets.is_empty());
1680 Self { targets, i: 0 }
1681 }
1682
1683 fn current(&self) -> (&'a ProofV2Target, Option<&'a ProofV2Target>) {
1685 (&self.targets[self.i], self.targets.get(self.i + 1))
1686 }
1687
1688 fn next(&mut self) -> (&'a ProofV2Target, Option<&'a ProofV2Target>) {
1694 self.i += 1;
1695 debug_assert!(self.i < self.targets.len());
1696 self.current()
1697 }
1698
1699 fn skip_iter(&self) -> impl Iterator<Item = &'a ProofV2Target> {
1701 self.targets[self.i + 1..].iter()
1702 }
1703
1704 fn rev_iter(&self) -> impl Iterator<Item = &'a ProofV2Target> {
1707 self.targets[..self.i].iter().rev()
1708 }
1709}
1710
1711#[derive(Debug)]
1714enum TrieCursorState {
1715 Unseeked,
1717 Available(Nibbles, BranchNodeCompact),
1719 Taken(Nibbles),
1721 Exhausted(Nibbles),
1723}
1724
1725impl TrieCursorState {
1726 const fn unseeked() -> Self {
1728 Self::Unseeked
1729 }
1730
1731 fn seeked(key: Nibbles, entry: Option<(Nibbles, BranchNodeCompact)>) -> Self {
1733 entry.map_or(Self::Exhausted(key), |(path, node)| Self::Available(path, node))
1734 }
1735
1736 const fn path(&self) -> Option<&Nibbles> {
1742 match self {
1743 Self::Unseeked => panic!("cursor is unseeked"),
1744 Self::Available(path, _) | Self::Taken(path) => Some(path),
1745 Self::Exhausted(_) => None,
1746 }
1747 }
1748
1749 fn needs_seek_to(&self, path: &Nibbles) -> bool {
1751 match self {
1752 Self::Unseeked | Self::Taken(_) => true,
1753 Self::Available(current_path, _) => current_path < path,
1754 Self::Exhausted(_) => false,
1755 }
1756 }
1757
1758 fn needs_reset_before_seek(&self, key: &Nibbles) -> bool {
1760 match self {
1761 Self::Unseeked => false,
1762 Self::Available(path, _) | Self::Taken(path) => path > key,
1763 Self::Exhausted(exhausted_at) => exhausted_at > key,
1764 }
1765 }
1766
1767 fn take(&mut self) -> (Nibbles, BranchNodeCompact) {
1769 let Self::Available(path, _) = self else {
1770 panic!("take called on non-Available: {self:?}")
1771 };
1772
1773 let path = *path;
1774 let Self::Available(path, node) = core::mem::replace(self, Self::Taken(path)) else {
1775 unreachable!("already checked that self is Self::Available");
1776 };
1777
1778 (path, node)
1779 }
1780}
1781
1782enum HashedCursorState<V> {
1784 Unseeked,
1786 Available(Nibbles, V),
1788 Exhausted(Nibbles),
1790}
1791
1792impl<V> HashedCursorState<V> {
1793 const fn unseeked() -> Self {
1795 Self::Unseeked
1796 }
1797
1798 fn seeked(key: Nibbles, entry: Option<(Nibbles, V)>) -> Self {
1800 entry.map_or(Self::Exhausted(key), |(path, value)| Self::Available(path, value))
1801 }
1802
1803 const fn path(&self) -> Option<&Nibbles> {
1805 match self {
1806 Self::Available(path, _) => Some(path),
1807 Self::Unseeked | Self::Exhausted(_) => None,
1808 }
1809 }
1810
1811 fn needs_seek_to(&self, key: &Nibbles) -> bool {
1813 match self {
1814 Self::Unseeked => true,
1815 Self::Available(path, _) => path < key,
1816 Self::Exhausted(exhausted_at) => exhausted_at > key,
1817 }
1818 }
1819
1820 fn needs_reset_before_seek(&self, key: &Nibbles) -> bool {
1822 match self {
1823 Self::Unseeked => false,
1824 Self::Available(path, _) => path > key,
1825 Self::Exhausted(exhausted_at) => exhausted_at >= key,
1826 }
1827 }
1828
1829 fn take(&mut self) -> (Nibbles, V) {
1831 match core::mem::replace(self, Self::Unseeked) {
1832 Self::Available(path, value) => (path, value),
1833 _ => panic!("take called on non-Available hashed cursor state"),
1834 }
1835 }
1836}
1837
1838enum PopCachedBranchOutcome {
1840 Popped((Nibbles, BranchNodeCompact)),
1842 Exhausted,
1844 CalculateLeaves((Nibbles, Option<Nibbles>)),
1847}
1848
1849#[cfg(test)]
1850mod tests {
1851 use super::*;
1852 use crate::{
1853 hashed_cursor::{
1854 mock::MockHashedCursorFactory, noop::NoopHashedCursor, HashedCursorFactory,
1855 HashedPostStateCursor,
1856 },
1857 proof::StorageProof as LegacyStorageProof,
1858 test_utils::TrieTestHarness,
1859 trie_cursor::{
1860 depth_first, noop::NoopStorageTrieCursor, InMemoryTrieCursor, TrieCursorFactory,
1861 },
1862 };
1863 use alloy_primitives::map::B256Set;
1864 use alloy_rlp::Decodable;
1865 use alloy_trie::proof::AddedRemovedKeys;
1866 use itertools::Itertools;
1867 use reth_trie_common::{
1868 prefix_set::{PrefixSet, PrefixSetMut},
1869 updates::TrieUpdatesSorted,
1870 HashedPostState, HashedStorage, ProofTrieNode, ProofV2TargetParent, TrieNode,
1871 EMPTY_ROOT_HASH,
1872 };
1873 use std::collections::BTreeMap;
1874
1875 fn convert_legacy_proofs_to_v2(legacy_proofs: &[ProofTrieNode]) -> Vec<ProofTrieNodeV2> {
1888 ProofTrieNodeV2::from_sorted_trie_nodes(
1889 legacy_proofs.iter().map(|p| (p.path, p.node.clone(), p.masks)),
1890 )
1891 }
1892
1893 fn project_legacy_proof_node(
1895 node: &ProofTrieNodeV2,
1896 target: &ProofV2Target,
1897 ) -> Option<ProofTrieNodeV2> {
1898 let Some(parent_path_len) = target.parent.path_len() else {
1899 return target.key_nibbles.starts_with(&node.path).then(|| node.clone())
1900 };
1901
1902 if node.path.len() > parent_path_len {
1903 return target.key_nibbles.starts_with(&node.path).then(|| node.clone())
1904 }
1905
1906 let logical_path = match &node.node {
1907 TrieNodeV2::Leaf(leaf) => node.path.join(&leaf.key),
1908 TrieNodeV2::Branch(branch) => node.path.join(&branch.key),
1909 TrieNodeV2::EmptyRoot | TrieNodeV2::Extension(_) => return None,
1910 };
1911 let child_path_len = parent_path_len + 1;
1912 if logical_path.len() < child_path_len {
1913 return None
1914 }
1915
1916 let child_path = logical_path.slice(0..child_path_len);
1917 if !target.key_nibbles.starts_with(&child_path) {
1918 return None
1919 }
1920
1921 let trim_len = child_path_len - node.path.len();
1922 let mut projected = node.clone();
1923 projected.path = child_path;
1924 match &mut projected.node {
1925 TrieNodeV2::Leaf(leaf) => leaf.key = leaf.key.slice(trim_len..),
1926 TrieNodeV2::Branch(branch) => {
1927 branch.key = branch.key.slice(trim_len..);
1928 if branch.key.is_empty() {
1929 branch.branch_rlp_node = None;
1930 }
1931 }
1932 TrieNodeV2::EmptyRoot | TrieNodeV2::Extension(_) => unreachable!(),
1933 }
1934 Some(projected)
1935 }
1936
1937 fn project_legacy_proof(
1939 legacy_nodes: &[ProofTrieNodeV2],
1940 targets: &[ProofV2Target],
1941 ) -> Vec<ProofTrieNodeV2> {
1942 let mut projected = targets
1943 .iter()
1944 .flat_map(|target| {
1945 legacy_nodes.iter().filter_map(move |node| project_legacy_proof_node(node, target))
1946 })
1947 .collect::<Vec<_>>();
1948 projected.sort_unstable_by(|a, b| depth_first::cmp(&a.path, &b.path));
1949 projected.dedup_by(|a, b| {
1950 if a.path != b.path {
1951 return false
1952 }
1953 assert_eq!(a, b, "target projections disagree at path {:?}", a.path);
1954 true
1955 });
1956 projected
1957 }
1958
1959 struct ProofTestHarness {
1965 inner: TrieTestHarness,
1966 }
1967
1968 impl std::ops::Deref for ProofTestHarness {
1969 type Target = TrieTestHarness;
1970 fn deref(&self) -> &Self::Target {
1971 &self.inner
1972 }
1973 }
1974
1975 impl ProofTestHarness {
1976 fn new(storage: BTreeMap<B256, U256>) -> Self {
1978 Self { inner: TrieTestHarness::new(storage) }
1979 }
1980
1981 fn root_with_prefix_set(&self, prefix_set: PrefixSet) -> Option<B256> {
1983 let trie_cursor =
1984 self.trie_cursor_factory().storage_trie_cursor(self.hashed_address()).unwrap();
1985 let hashed_cursor =
1986 self.hashed_cursor_factory().hashed_storage_cursor(self.hashed_address()).unwrap();
1987 let mut calculator = StorageProofCalculator::new_storage(trie_cursor, hashed_cursor)
1988 .with_prefix_set(prefix_set);
1989
1990 let mut targets = [ProofV2Target::new(B256::ZERO)];
1991 let proof = calculator.storage_proof(self.hashed_address(), &mut targets).unwrap();
1992 calculator.compute_root_hash(&proof).unwrap()
1993 }
1994
1995 fn assert_proof(
1998 &self,
1999 targets: impl IntoIterator<Item = ProofV2Target>,
2000 ) -> Result<(), StateProofError> {
2001 let mut targets_vec = targets.into_iter().collect::<Vec<_>>();
2002
2003 let (proof_v2_result, root_hash) = self.proof_v2(&mut targets_vec);
2005
2006 if let Some(root_hash) = root_hash {
2009 pretty_assertions::assert_eq!(self.original_root(), root_hash);
2010 }
2011
2012 let legacy_targets = targets_vec
2015 .iter()
2016 .map(|target| B256::from_slice(&target.key_nibbles.pack()))
2017 .chain(self.storage().keys().copied())
2018 .collect::<B256Set>();
2019
2020 let proof_legacy_result = LegacyStorageProof::new_hashed(
2022 self.trie_cursor_factory(),
2023 self.hashed_cursor_factory(),
2024 self.hashed_address(),
2025 )
2026 .with_branch_node_masks(true)
2027 .with_added_removed_keys(Some(AddedRemovedKeys::default().with_assume_added(true)))
2028 .storage_multiproof(legacy_targets)?;
2029
2030 let proof_legacy_nodes = proof_legacy_result
2032 .subtree
2033 .iter()
2034 .map(|(path, node_enc)| {
2035 let mut buf = node_enc.as_ref();
2036 let node = TrieNode::decode(&mut buf)
2037 .expect("legacy implementation should not produce malformed proof nodes");
2038
2039 let masks = if path.is_empty() {
2040 None
2041 } else {
2042 proof_legacy_result.branch_node_masks.get(path).copied()
2043 };
2044
2045 ProofTrieNode { path: *path, node, masks }
2046 })
2047 .sorted_by(|a, b| depth_first::cmp(&a.path, &b.path))
2048 .collect::<Vec<_>>();
2049
2050 let all_legacy_nodes_v2 = convert_legacy_proofs_to_v2(&proof_legacy_nodes);
2052
2053 let expected_v2 = project_legacy_proof(&all_legacy_nodes_v2, &targets_vec);
2054 pretty_assertions::assert_eq!(expected_v2, proof_v2_result);
2055
2056 Ok(())
2057 }
2058 }
2059
2060 #[test]
2065 fn test_proof_calculator_reuse_after_error() {
2066 reth_tracing::init_test_tracing();
2067
2068 let slots = [
2069 B256::right_padding_from(&[0x10]),
2070 B256::right_padding_from(&[0x20]),
2071 B256::right_padding_from(&[0x30]),
2072 B256::right_padding_from(&[0x40]),
2073 ];
2074 let storage: BTreeMap<B256, U256> =
2075 slots.iter().map(|&s| (s, U256::from(100u64))).collect();
2076
2077 let harness = ProofTestHarness::new(storage);
2078
2079 let trie_cursor_factory = harness.trie_cursor_factory();
2080 let hashed_cursor_factory = harness.hashed_cursor_factory();
2081
2082 let hashed_address = harness.hashed_address();
2083 let trie_cursor = trie_cursor_factory.storage_trie_cursor(hashed_address).unwrap();
2084 let hashed_cursor = hashed_cursor_factory.hashed_storage_cursor(hashed_address).unwrap();
2085 let mut proof_calculator = StorageProofCalculator::new_storage(trie_cursor, hashed_cursor);
2086
2087 proof_calculator
2090 .branch_stack
2091 .push(ProofTrieBranch { ext_len: 2, state_mask: TrieMask::new(0b1111) });
2092 proof_calculator
2093 .branch_stack
2094 .push(ProofTrieBranch { ext_len: 0, state_mask: TrieMask::new(0b11) });
2095 proof_calculator.child_stack.push(ProofTrieBranchChild::RlpNode {
2096 node: RlpNode::word_rlp(&B256::ZERO),
2097 short_key: Nibbles::new(),
2098 hash_mask_bit: false,
2099 tree_mask_bit: false,
2100 });
2101 proof_calculator.branch_path = Nibbles::from_nibbles([0x1, 0x2, 0x3]);
2102
2103 proof_calculator.clear_computation_state();
2105
2106 let mut sorted_slots = slots.to_vec();
2107 sorted_slots.sort();
2108 let mut targets: Vec<ProofV2Target> =
2109 sorted_slots.iter().copied().map(ProofV2Target::new).collect();
2110
2111 let result = proof_calculator.storage_proof(hashed_address, &mut targets).unwrap();
2112
2113 let trie_cursor = trie_cursor_factory.storage_trie_cursor(hashed_address).unwrap();
2115 let hashed_cursor = hashed_cursor_factory.hashed_storage_cursor(hashed_address).unwrap();
2116 let mut fresh_calculator = StorageProofCalculator::new_storage(trie_cursor, hashed_cursor);
2117 let fresh_result = fresh_calculator.storage_proof(hashed_address, &mut targets).unwrap();
2118
2119 pretty_assertions::assert_eq!(fresh_result, result);
2120 }
2121
2122 #[test]
2123 fn test_root_node_reuse_with_overlay() {
2124 let storage = BTreeMap::from([
2125 (B256::right_padding_from(&[0x10]), U256::ONE),
2126 (B256::right_padding_from(&[0x20]), U256::from(2)),
2127 ]);
2128 let harness = ProofTestHarness::new(storage.clone());
2129 let hashed_address = harness.hashed_address();
2130 let post_state =
2131 HashedPostState::from_hashed_storage(hashed_address, HashedStorage::from_iter(storage))
2132 .into_sorted();
2133 let trie_updates = TrieUpdatesSorted::default();
2134 let trie_cursor = InMemoryTrieCursor::new_storage(
2135 NoopStorageTrieCursor::default(),
2136 &trie_updates,
2137 hashed_address,
2138 );
2139 let hashed_cursor = HashedPostStateCursor::new_storage(
2140 NoopHashedCursor::default(),
2141 &post_state,
2142 hashed_address,
2143 );
2144 let mut calculator = StorageProofCalculator::new_storage(trie_cursor, hashed_cursor);
2145
2146 let first_root = calculator.root_node(&mut StorageValueEncoder).unwrap();
2147 assert!(matches!(first_root.node, TrieNodeV2::Branch(_)));
2148 assert_eq!(
2149 calculator.compute_root_hash(core::slice::from_ref(&first_root)).unwrap(),
2150 Some(harness.original_root())
2151 );
2152
2153 let second_root = calculator.root_node(&mut StorageValueEncoder).unwrap();
2154 pretty_assertions::assert_eq!(first_root, second_root);
2155 }
2156
2157 #[test]
2158 fn test_partial_storage_proof_after_root_calculation() {
2159 let slot_a = B256::right_padding_from(&[0xae, 0xd4, 0x00]);
2160 let slot_b = B256::right_padding_from(&[0xae, 0xd4, 0x10]);
2161 let harness =
2162 ProofTestHarness::new(BTreeMap::from([(slot_a, U256::ONE), (slot_b, U256::from(2))]));
2163 let hashed_address = harness.hashed_address();
2164 let trie_cursor =
2165 harness.trie_cursor_factory().storage_trie_cursor(hashed_address).unwrap();
2166 let hashed_cursor =
2167 harness.hashed_cursor_factory().hashed_storage_cursor(hashed_address).unwrap();
2168 let mut calculator = StorageProofCalculator::new_storage(trie_cursor, hashed_cursor);
2169
2170 let root_node = calculator.storage_root_node(hashed_address).unwrap();
2171 assert_eq!(
2172 calculator.compute_root_hash(core::slice::from_ref(&root_node)).unwrap(),
2173 Some(harness.original_root())
2174 );
2175
2176 let target = ProofV2Target::new(slot_a).with_parent(ProofV2TargetParent::new(3));
2177 let mut actual_targets = [target];
2178 let actual = calculator.storage_proof(hashed_address, &mut actual_targets).unwrap();
2179 let mut expected_targets = [target];
2180 let (expected, root) = harness.proof_v2(&mut expected_targets);
2181
2182 assert!(root.is_none());
2183 pretty_assertions::assert_eq!(expected, actual);
2184 }
2185
2186 mod proptest_tests {
2187 use super::*;
2188 use proptest::prelude::*;
2189
2190 fn storage_strategy() -> impl Strategy<Value = BTreeMap<B256, U256>> {
2192 prop::collection::vec((any::<[u8; 32]>(), any::<u64>()), 0..=100).prop_map(|slots| {
2193 slots
2194 .into_iter()
2195 .map(|(slot_bytes, value)| (B256::from(slot_bytes), U256::from(value)))
2196 .filter(|(_, v)| !v.is_zero())
2197 .collect()
2198 })
2199 }
2200
2201 fn proof_targets_strategy(
2204 slot_keys: Vec<B256>,
2205 ) -> impl Strategy<Value = Vec<ProofV2Target>> {
2206 let num_slots = slot_keys.len();
2207
2208 let target_count = 0..=(num_slots + 5);
2209
2210 target_count.prop_flat_map(move |count| {
2211 let slot_keys = slot_keys.clone();
2212 prop::collection::vec(
2213 (
2214 prop::bool::weighted(0.8).prop_flat_map(move |from_slots| {
2215 if from_slots && !slot_keys.is_empty() {
2216 prop::sample::select(slot_keys.clone()).boxed()
2217 } else {
2218 any::<[u8; 32]>().prop_map(B256::from).boxed()
2219 }
2220 }),
2221 0u8..16u8,
2222 )
2223 .prop_map(|(key, encoded_parent_path_len)| {
2224 let parent = encoded_parent_path_len.checked_sub(1).map_or(
2225 ProofV2TargetParent::NONE,
2226 |parent_path_len| {
2227 ProofV2TargetParent::new(usize::from(parent_path_len))
2228 },
2229 );
2230 ProofV2Target::new(key).with_parent(parent)
2231 }),
2232 count,
2233 )
2234 })
2235 }
2236
2237 fn dirty_key_strategy() -> impl Strategy<Value = B256> {
2238 prop::array::uniform8(0u8..4).prop_map(|nibbles| {
2240 B256::right_padding_from(&Nibbles::from_nibbles(nibbles).pack())
2241 })
2242 }
2243
2244 proptest! {
2245 #![proptest_config(ProptestConfig::with_cases(4000))]
2246 #[test]
2247 fn proptest_proof_with_targets(
2250 (storage, targets) in storage_strategy()
2251 .prop_flat_map(|storage| {
2252 let mut slot_keys: Vec<B256> = storage.keys().copied().collect();
2253 slot_keys.sort_unstable();
2254 let targets_strategy = proof_targets_strategy(slot_keys);
2255 (Just(storage), targets_strategy)
2256 })
2257 ) {
2258 reth_tracing::init_test_tracing();
2259 let harness = ProofTestHarness::new(storage);
2260
2261 harness.assert_proof(targets).expect("Proof generation failed");
2262 }
2263
2264 #[test]
2265 fn proptest_proof_with_dirty_prefixes(
2266 before in prop::collection::btree_map(dirty_key_strategy(), 1u64..100, 0..80),
2267 changes in prop::collection::btree_map(dirty_key_strategy(), 0u64..100, 0..8),
2268 retain_unchanged in any::<bool>(),
2269 target_indices in prop::collection::vec((0usize..100, 0usize..9), 0..10),
2270 ) {
2271 let before = before.into_iter()
2272 .map(|(key, value)| (key, U256::from(value)))
2273 .collect::<BTreeMap<_, _>>();
2274 let mut after = if retain_unchanged { before.clone() } else { BTreeMap::new() };
2275 for (key, value) in changes {
2276 if value == 0 {
2277 after.remove(&key);
2278 } else {
2279 after.insert(key, U256::from(value));
2280 }
2281 }
2282 let prefix_set = PrefixSetMut::from(
2283 before.keys().chain(after.keys())
2284 .filter(|key| before.get(*key) != after.get(*key))
2285 .map(Nibbles::unpack),
2286 ).freeze();
2287 let expected_root = crate::test_utils::storage_root_prehashed(
2288 after.iter().map(|(key, value)| (*key, *value)),
2289 );
2290 let mut targets = vec![ProofV2Target::new(B256::ZERO)];
2291 let keys = before.keys().chain(after.keys()).copied().collect::<Vec<_>>();
2292 if !keys.is_empty() {
2293 for (index, len) in target_indices {
2294 targets.push(ProofV2Target::new(keys[index % keys.len()])
2295 .with_parent(ProofV2TargetParent::new(len)));
2296 }
2297 }
2298 let harness = TrieTestHarness::new(before);
2299 let hashed_factory = MockHashedCursorFactory::new(
2300 BTreeMap::new(),
2301 std::iter::once((harness.hashed_address(), after)).collect(),
2302 );
2303 let trie_cursor = harness.trie_cursor_factory()
2304 .storage_trie_cursor(harness.hashed_address()).unwrap();
2305 let hashed_cursor = hashed_factory
2306 .hashed_storage_cursor(harness.hashed_address()).unwrap();
2307 let mut calculator = StorageProofCalculator::new_storage(trie_cursor, hashed_cursor)
2308 .with_prefix_set(prefix_set);
2309 let proof = calculator.storage_proof(harness.hashed_address(), &mut targets).unwrap();
2310
2311 prop_assert_eq!(calculator.compute_root_hash(&proof).unwrap(), Some(expected_root));
2312 }
2313 }
2314 }
2315
2316 #[test]
2317 fn test_exact_subtrie_targets_with_root_target() {
2318 reth_tracing::init_test_tracing();
2319
2320 let slot_80 = B256::right_padding_from(&[0x80]);
2321 let slot_82 = B256::right_padding_from(&[0x82]);
2322 let slot_f0 = B256::right_padding_from(&[0xf0]);
2323 let storage = BTreeMap::from([
2324 (slot_80, U256::ONE),
2325 (slot_82, U256::from(2)),
2326 (slot_f0, U256::from(3)),
2327 ]);
2328 let targets = [
2329 ProofV2Target::new(B256::ZERO),
2330 ProofV2Target::new(slot_80).with_parent(ProofV2TargetParent::new(1)),
2331 ];
2332
2333 let harness = ProofTestHarness::new(storage);
2334 harness.assert_proof(targets).expect("Proof generation failed");
2335 }
2336
2337 #[test]
2338 fn test_rebases_singleton_subtrie_root_below_known_parent() {
2339 let slot = B256::right_padding_from(&[0xae, 0xd4, 0x09]);
2340 let slot_nibbles = Nibbles::unpack(slot);
2341 let harness = ProofTestHarness::new(BTreeMap::from([(slot, U256::ONE)]));
2342 let mut targets = [ProofV2Target::new(slot).with_parent(ProofV2TargetParent::new(3))];
2343
2344 let (proof, root) = harness.proof_v2(&mut targets);
2345
2346 assert!(root.is_none());
2347 assert_eq!(proof.len(), 1);
2348 assert_eq!(proof[0].path, slot_nibbles.slice(0..4));
2349 let TrieNodeV2::Leaf(leaf) = &proof[0].node else {
2350 panic!("singleton subtrie root should remain a leaf")
2351 };
2352 assert_eq!(leaf.key, slot_nibbles.slice(4..));
2353 }
2354
2355 #[test]
2356 fn test_rebases_singleton_leaf_at_max_parent_depth() {
2357 let slot = B256::repeat_byte(0xae);
2358 let slot_nibbles = Nibbles::unpack(slot);
2359 let harness = ProofTestHarness::new(BTreeMap::from([(slot, U256::ONE)]));
2360 let mut targets = [ProofV2Target::new(slot).with_parent(ProofV2TargetParent::new(63))];
2361
2362 let (proof, root) = harness.proof_v2(&mut targets);
2363
2364 assert!(root.is_none());
2365 assert_eq!(proof.len(), 1);
2366 assert_eq!(proof[0].path, slot_nibbles);
2367 let TrieNodeV2::Leaf(leaf) = &proof[0].node else {
2368 panic!("singleton subtrie root should remain a leaf")
2369 };
2370 assert!(leaf.key.is_empty());
2371 }
2372
2373 #[test]
2374 fn test_root_and_root_parent_targets_retain_both_singleton_representations() {
2375 let slot = B256::right_padding_from(&[0x20]);
2376 let slot_nibbles = Nibbles::unpack(slot);
2377 let harness = ProofTestHarness::new(BTreeMap::from([(slot, U256::ONE)]));
2378 let mut targets = [
2379 ProofV2Target::new(slot),
2380 ProofV2Target::new(slot).with_parent(ProofV2TargetParent::new(0)),
2381 ];
2382
2383 let (proof, root) = harness.proof_v2(&mut targets);
2384
2385 assert_eq!(root, Some(harness.original_root()));
2386 let root_node = proof.iter().find(|node| node.path.is_empty()).expect("root proof");
2387 let TrieNodeV2::Leaf(root_leaf) = &root_node.node else { panic!("root should be a leaf") };
2388 assert_eq!(root_leaf.key, slot_nibbles);
2389
2390 let child_path = slot_nibbles.slice(0..1);
2391 let child_node =
2392 proof.iter().find(|node| node.path == child_path).expect("rebased root child proof");
2393 let TrieNodeV2::Leaf(child_leaf) = &child_node.node else {
2394 panic!("root child should be a leaf")
2395 };
2396 assert_eq!(child_leaf.key, slot_nibbles.slice(1..));
2397 }
2398
2399 #[test]
2400 fn test_exhausted_cursor_resets_at_equal_target_boundary() {
2401 let first = B256::ZERO;
2402 let last = B256::with_last_byte(1);
2403 let last_nibbles = Nibbles::unpack(last);
2404 let harness =
2405 ProofTestHarness::new(BTreeMap::from([(first, U256::ONE), (last, U256::from(2))]));
2406 let mut targets = [
2407 ProofV2Target::new(first),
2408 ProofV2Target::new(last).with_parent(ProofV2TargetParent::new(63)),
2409 ];
2410
2411 let (proof, root) = harness.proof_v2(&mut targets);
2412
2413 assert_eq!(root, Some(harness.original_root()));
2414 let child = proof
2415 .iter()
2416 .find(|node| node.path == last_nibbles)
2417 .expect("depth-63 target child proof");
2418 let TrieNodeV2::Leaf(leaf) = &child.node else { panic!("target child should be a leaf") };
2419 assert!(leaf.key.is_empty());
2420 }
2421
2422 #[test]
2423 fn test_rebases_compressed_branch_subtrie_root() {
2424 let slot_a = B256::right_padding_from(&[0xae, 0xd4, 0x00]);
2425 let slot_b = B256::right_padding_from(&[0xae, 0xd4, 0x10]);
2426 let slot_nibbles = Nibbles::unpack(slot_a);
2427 let harness =
2428 ProofTestHarness::new(BTreeMap::from([(slot_a, U256::ONE), (slot_b, U256::from(2))]));
2429 let mut targets = [ProofV2Target::new(slot_a).with_parent(ProofV2TargetParent::new(3))];
2430
2431 let (proof, root) = harness.proof_v2(&mut targets);
2432
2433 assert!(root.is_none());
2434 let branch_path = slot_nibbles.slice(0..4);
2435 let branch_node =
2436 proof.iter().find(|node| node.path == branch_path).expect("rebased compressed branch");
2437 let TrieNodeV2::Branch(branch) = &branch_node.node else {
2438 panic!("rebased node should be a branch")
2439 };
2440 assert!(branch.key.is_empty());
2441 assert!(branch.branch_rlp_node.is_none());
2442 }
2443
2444 #[test]
2445 fn test_known_parent_retains_child() {
2446 let slot_a = B256::right_padding_from(&[0xae, 0xd2]);
2447 let slot_b = B256::right_padding_from(&[0xae, 0xd4]);
2448 let slot_nibbles = Nibbles::unpack(slot_a);
2449 let harness =
2450 ProofTestHarness::new(BTreeMap::from([(slot_a, U256::ONE), (slot_b, U256::from(2))]));
2451 let mut targets = [ProofV2Target::new(slot_a).with_parent(ProofV2TargetParent::new(3))];
2452
2453 let (proof, root) = harness.proof_v2(&mut targets);
2454
2455 assert!(root.is_none());
2456 assert!(!proof.iter().any(|node| node.path == slot_nibbles.slice(0..3)));
2457 assert!(proof.iter().any(|node| node.path == slot_nibbles.slice(0..4)));
2458 }
2459
2460 #[test]
2461 fn test_rebased_root_matches_direct_child_not_full_short_key() {
2462 let stored_slot = B256::right_padding_from(&[0xae, 0xd4, 0x09]);
2463 let same_child_target = B256::right_padding_from(&[0xae, 0xd4, 0xff]);
2464 let other_child_target = B256::right_padding_from(&[0xae, 0xd5]);
2465 let harness = ProofTestHarness::new(BTreeMap::from([(stored_slot, U256::ONE)]));
2466
2467 let mut same_child =
2468 [ProofV2Target::new(same_child_target).with_parent(ProofV2TargetParent::new(3))];
2469 let (proof, _) = harness.proof_v2(&mut same_child);
2470 assert_eq!(proof.len(), 1, "divergent leaf proves absence below the same child");
2471
2472 let mut other_child =
2473 [ProofV2Target::new(other_child_target).with_parent(ProofV2TargetParent::new(3))];
2474 let (proof, _) = harness.proof_v2(&mut other_child);
2475 assert!(proof.is_empty(), "a different direct child is unrelated to the target");
2476 }
2477
2478 #[test]
2479 fn test_known_parent_separate_child_ranges() {
2480 let stored_slot_a = B256::right_padding_from(&[0xea, 0x53]);
2481 let stored_slot_b = B256::right_padding_from(&[0xeb, 0x53]);
2482 let stored_slot_c = B256::right_padding_from(&[0xec, 0x53]);
2483 let target_a = B256::right_padding_from(&[0xea, 0x1f]);
2484 let target_c = B256::right_padding_from(&[0xec, 0x1f]);
2485 let harness = ProofTestHarness::new(BTreeMap::from([
2486 (stored_slot_a, U256::ONE),
2487 (stored_slot_b, U256::from(2)),
2488 (stored_slot_c, U256::from(3)),
2489 ]));
2490 let mut targets = [target_a, target_c]
2491 .map(|target| ProofV2Target::new(target).with_parent(ProofV2TargetParent::new(1)));
2492
2493 let (proof, root) = harness.proof_v2(&mut targets);
2494
2495 assert!(root.is_none());
2496 assert_eq!(
2497 proof.iter().map(|node| node.path).collect::<Vec<_>>(),
2498 [Nibbles::from_nibbles([0xe, 0xa]), Nibbles::from_nibbles([0xe, 0xc])]
2499 );
2500 }
2501
2502 #[test]
2503 fn test_known_parent_does_not_use_stale_parent_mask() {
2504 let stored_slot_a = B256::right_padding_from(&[0xea, 0x53]);
2505 let stored_slot = B256::right_padding_from(&[0xeb, 0x53]);
2506 let stored_slot_c = B256::right_padding_from(&[0xec, 0x53]);
2507 let target = B256::right_padding_from(&[0xeb, 0x1f]);
2508 let stored_slot_nibbles = Nibbles::unpack(stored_slot);
2509
2510 let stale_parent_mask = TrieMask::new((1 << 0xa) | (1 << 0xc));
2514 let stale_parent = BranchNodeCompact::new(
2515 stale_parent_mask,
2516 TrieMask::new(0),
2517 TrieMask::new(0),
2518 Vec::new(),
2519 None,
2520 );
2521 let storage_nodes = BTreeMap::from([(Nibbles::from_nibbles([0xe]), stale_parent)]);
2522
2523 let mut harness = TrieTestHarness::new(BTreeMap::from([
2524 (stored_slot_a, U256::ONE),
2525 (stored_slot, U256::from(2)),
2526 (stored_slot_c, U256::from(3)),
2527 ]));
2528 harness.set_trie_nodes(storage_nodes);
2529
2530 let mut targets = [ProofV2Target::new(target).with_parent(ProofV2TargetParent::new(1))];
2531 let (proof, root) = harness.proof_v2(&mut targets);
2532
2533 assert!(root.is_none());
2534 assert_eq!(proof.len(), 1);
2535 assert_eq!(proof[0].path, stored_slot_nibbles.slice(0..2));
2536 let TrieNodeV2::Leaf(leaf) = &proof[0].node else {
2537 panic!("live direct child should be reconstructed as a leaf")
2538 };
2539 assert_eq!(leaf.key, stored_slot_nibbles.slice(2..));
2540 }
2541
2542 #[test]
2543 fn test_empty_storage_respects_parent_context() {
2544 let harness = ProofTestHarness::new(BTreeMap::new());
2545 let slot = B256::ZERO;
2546
2547 let mut partial_target =
2548 [ProofV2Target::new(slot).with_parent(ProofV2TargetParent::new(0))];
2549 let (partial_proof, partial_root) = harness.proof_v2(&mut partial_target);
2550 assert!(partial_proof.is_empty());
2551 assert!(partial_root.is_none());
2552
2553 let mut root_target = [ProofV2Target::new(slot)];
2554 let (root_proof, root) = harness.proof_v2(&mut root_target);
2555 assert_eq!(root_proof.len(), 1);
2556 assert!(matches!(root_proof[0].node, TrieNodeV2::EmptyRoot));
2557 assert_eq!(root, Some(EMPTY_ROOT_HASH));
2558 }
2559
2560 #[test]
2561 fn test_big_trie() {
2562 use rand::prelude::*;
2563
2564 reth_tracing::init_test_tracing();
2565 let mut rng = rand::rngs::SmallRng::seed_from_u64(1);
2566
2567 let mut rand_b256 = || {
2568 let mut buf: [u8; 32] = [0; 32];
2569 rng.fill_bytes(&mut buf);
2570 B256::from_slice(&buf)
2571 };
2572
2573 let mut storage = BTreeMap::new();
2575 for _ in 0..10240 {
2576 let hashed_slot = rand_b256();
2577 storage.insert(hashed_slot, U256::ONE);
2578 }
2579
2580 let mut targets = storage.keys().copied().collect::<Vec<_>>();
2583 for _ in 0..storage.len() / 5 {
2584 targets.push(rand_b256());
2585 }
2586 targets.sort();
2587
2588 let harness = ProofTestHarness::new(storage);
2590
2591 harness
2592 .assert_proof(targets.into_iter().map(ProofV2Target::new))
2593 .expect("Proof generation failed");
2594 }
2595
2596 #[test]
2597 fn test_node_with_masked_empty_child() {
2598 reth_tracing::init_test_tracing();
2599
2600 let val = U256::from(42u64);
2601
2602 let slot_60 = B256::right_padding_from(&[0x60]);
2605 let slot_61 = B256::right_padding_from(&[0x61]);
2606 let slot_65 = B256::right_padding_from(&[0x65]);
2607 let slot_67 = B256::right_padding_from(&[0x67]);
2608
2609 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];
2615 let branch = BranchNodeCompact::new(state_mask, TrieMask::new(0), hash_mask, hashes, None);
2616
2617 let storage_nodes: BTreeMap<Nibbles, BranchNodeCompact> =
2618 std::iter::once((Nibbles::from_nibbles([0x6]), branch)).collect();
2619
2620 let mut harness = TrieTestHarness::new(
2623 [slot_60, slot_61, slot_65, slot_67].iter().map(|s| (*s, val)).collect(),
2624 );
2625 harness.set_trie_nodes(storage_nodes);
2626
2627 let storage_trie_cursor =
2628 harness.trie_cursor_factory().storage_trie_cursor(harness.hashed_address()).unwrap();
2629 let hashed_storage_cursor = harness
2630 .hashed_cursor_factory()
2631 .hashed_storage_cursor(harness.hashed_address())
2632 .unwrap();
2633 let mut calculator =
2634 StorageProofCalculator::new_storage(storage_trie_cursor, hashed_storage_cursor);
2635 let root_node = calculator
2636 .storage_root_node(harness.hashed_address())
2637 .expect("storage_root_node should succeed with masked empty child");
2638
2639 let root_hash = calculator.compute_root_hash(core::slice::from_ref(&root_node)).unwrap();
2640 assert!(root_hash.is_some(), "should produce a root hash");
2641 }
2642
2643 #[test]
2653 fn test_node_with_masked_empty_child_lower_bound_past_branch() {
2654 reth_tracing::init_test_tracing();
2655
2656 let val = U256::from(42u64);
2657
2658 let slot_60 = B256::right_padding_from(&[0x60]);
2660 let slot_61 = B256::right_padding_from(&[0x61]);
2661 let slot_6f = B256::right_padding_from(&[0x6f]);
2662 let slot_70 = B256::right_padding_from(&[0x70]);
2663
2664 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];
2670 let branch = BranchNodeCompact::new(state_mask, TrieMask::new(0), hash_mask, hashes, None);
2671
2672 let storage_nodes: BTreeMap<Nibbles, BranchNodeCompact> =
2673 std::iter::once((Nibbles::from_nibbles([0x6]), branch)).collect();
2674
2675 let mut harness = TrieTestHarness::new(
2677 [slot_60, slot_61, slot_6f, slot_70].iter().map(|s| (*s, val)).collect(),
2678 );
2679 harness.set_trie_nodes(storage_nodes);
2680
2681 let storage_trie_cursor =
2682 harness.trie_cursor_factory().storage_trie_cursor(harness.hashed_address()).unwrap();
2683 let hashed_storage_cursor = harness
2684 .hashed_cursor_factory()
2685 .hashed_storage_cursor(harness.hashed_address())
2686 .unwrap();
2687 let mut calculator =
2688 StorageProofCalculator::new_storage(storage_trie_cursor, hashed_storage_cursor);
2689 let root_node = calculator
2690 .storage_root_node(harness.hashed_address())
2691 .expect("storage_root_node should succeed when lower bound advances past branch");
2692
2693 let root_hash = calculator.compute_root_hash(core::slice::from_ref(&root_node)).unwrap();
2694 assert!(root_hash.is_some(), "should produce a root hash");
2695 }
2696
2697 #[test]
2707 fn test_prefix_set_adds_child_nibbles() {
2708 reth_tracing::init_test_tracing();
2709
2710 let val = U256::from(42u64);
2711 let slot_60 = B256::right_padding_from(&[0x60]);
2712 let slot_61 = B256::right_padding_from(&[0x61]);
2713 let slot_63 = B256::right_padding_from(&[0x63]);
2714
2715 let harness = TrieTestHarness::new([(slot_60, val), (slot_61, val)].into_iter().collect());
2716
2717 let changeset: BTreeMap<B256, U256> = std::iter::once((slot_63, val)).collect();
2718 let (expected_root, _) = harness.get_root_with_updates(&changeset);
2719
2720 let mut updated_storage = harness.storage().clone();
2721 updated_storage.insert(slot_63, val);
2722
2723 let updated_hashed = MockHashedCursorFactory::new(
2724 BTreeMap::new(),
2725 std::iter::once((harness.hashed_address(), updated_storage)).collect(),
2726 );
2727
2728 let mut prefix_set = PrefixSetMut::default();
2729 prefix_set.insert(Nibbles::unpack(slot_63));
2730
2731 let trie_cursor =
2732 harness.trie_cursor_factory().storage_trie_cursor(harness.hashed_address()).unwrap();
2733 let hashed_cursor = updated_hashed.hashed_storage_cursor(harness.hashed_address()).unwrap();
2734 let mut calculator = StorageProofCalculator::new_storage(trie_cursor, hashed_cursor)
2735 .with_prefix_set(prefix_set.freeze());
2736 let root_node = calculator
2737 .storage_root_node(harness.hashed_address())
2738 .expect("storage_root_node should succeed with prefix set adding child nibbles");
2739 let got_root =
2740 calculator.compute_root_hash(core::slice::from_ref(&root_node)).unwrap().unwrap();
2741
2742 pretty_assertions::assert_eq!(
2743 expected_root,
2744 got_root,
2745 "Root hash with prefix set should match fresh computation"
2746 );
2747 }
2748
2749 #[test]
2758 fn test_prefix_set_invalidates_cached_hash() {
2759 reth_tracing::init_test_tracing();
2760
2761 let original_val = U256::from(42u64);
2762 let updated_val = U256::from(9999u64);
2763 let slot_60 = B256::right_padding_from(&[0x60]);
2764 let slot_61 = B256::right_padding_from(&[0x61]);
2765 let slot_65 = B256::right_padding_from(&[0x65]);
2766
2767 let harness = TrieTestHarness::new(
2768 [(slot_60, original_val), (slot_61, original_val), (slot_65, original_val)]
2769 .into_iter()
2770 .collect(),
2771 );
2772
2773 let changeset: BTreeMap<B256, U256> = std::iter::once((slot_65, updated_val)).collect();
2774 let (expected_root, _) = harness.get_root_with_updates(&changeset);
2775
2776 let mut updated_storage = harness.storage().clone();
2777 updated_storage.insert(slot_65, updated_val);
2778
2779 let updated_hashed = MockHashedCursorFactory::new(
2780 BTreeMap::new(),
2781 std::iter::once((harness.hashed_address(), updated_storage)).collect(),
2782 );
2783
2784 let mut prefix_set = PrefixSetMut::default();
2785 prefix_set.insert(Nibbles::unpack(slot_65));
2786
2787 let trie_cursor =
2788 harness.trie_cursor_factory().storage_trie_cursor(harness.hashed_address()).unwrap();
2789 let hashed_cursor = updated_hashed.hashed_storage_cursor(harness.hashed_address()).unwrap();
2790 let mut calculator = StorageProofCalculator::new_storage(trie_cursor, hashed_cursor)
2791 .with_prefix_set(prefix_set.freeze());
2792 let root_node = calculator
2793 .storage_root_node(harness.hashed_address())
2794 .expect("storage_root_node should succeed with prefix set invalidating cached hash");
2795 let got_root =
2796 calculator.compute_root_hash(core::slice::from_ref(&root_node)).unwrap().unwrap();
2797
2798 pretty_assertions::assert_eq!(
2799 expected_root,
2800 got_root,
2801 "Root hash with prefix set invalidation should match fresh computation"
2802 );
2803 }
2804
2805 fn b256(s: &str) -> B256 {
2806 B256::from_slice(&alloy_primitives::hex::decode(s).expect("valid hex string"))
2807 }
2808
2809 #[test]
2810 fn test_prefix_set_root_proof_processes_sibling_after_cached_descendant() {
2811 reth_tracing::init_test_tracing();
2812
2813 let storage = [
2814 ("1022c69e9d900e40775cd387c134899f465f291dbc3c97899ff6bfb8dc972b37", 45u64),
2815 ("1111ad8083c8a3a398b2b781217b989ff4d1ed182f46cc765eda49a7b316139d", 60),
2816 ("12012d20943649899b2fc0f87b9840b70ef68e93613aac17c269bf8c5a78a712", 17),
2817 ("12014b57b9a162c03d072eb6acd4e936f1c4bc23b803a054347c5ee9a9bcfb9a", 49),
2818 ("1203f800840af3f898ab4572f2750106a7c4bd2b3e844b6e7fa72704673cc2c6", 76),
2819 ("12208f18fbcd6971c92808721392acbf11d5af58e9143a374cc86e70bdd1f097", 10),
2820 ]
2821 .into_iter()
2822 .map(|(key, value)| (b256(key), U256::from(value)))
2823 .collect();
2824
2825 let dirty = b256("12208f18fbcd6971c92808721392acbf11d5af58e9143a374cc86e70bdd1f097");
2826 let harness = ProofTestHarness::new(storage);
2827 let expected_root = harness.original_root();
2828
2829 let mut prefix_set = PrefixSetMut::default();
2830 prefix_set.insert(Nibbles::unpack(dirty));
2831
2832 pretty_assertions::assert_eq!(
2833 Some(expected_root),
2834 harness.root_with_prefix_set(prefix_set.freeze()),
2835 "root proof must process a prefix-set sibling after a cached descendant",
2836 );
2837 }
2838
2839 #[test]
2840 fn test_prefix_set_root_proof_processes_trailing_dirty_sibling() {
2841 reth_tracing::init_test_tracing();
2842
2843 let keys = [
2844 "0022001020000000000000000000000000000000000000000000000000000000",
2845 "0110212112000000000000000000000000000000000000000000000000000000",
2846 "0202210210000000000000000000000000000000000000000000000000000000",
2847 "0211020211000000000000000000000000000000000000000000000000000000",
2848 "0211211002000000000000000000000000000000000000000000000000000000",
2849 "0212221010000000000000000000000000000000000000000000000000000000",
2850 "0222011102000000000000000000000000000000000000000000000000000000",
2851 ];
2852 let storage =
2853 keys.iter().enumerate().map(|(i, key)| (b256(key), U256::from(i as u64 + 1))).collect();
2854 let harness = ProofTestHarness::new(storage);
2855 let expected_root = harness.original_root();
2856
2857 let mut prefix_set = PrefixSetMut::default();
2860 prefix_set.insert(Nibbles::unpack(b256(keys[2])));
2861 prefix_set.insert(Nibbles::unpack(b256(keys[6])));
2862
2863 pretty_assertions::assert_eq!(
2864 Some(expected_root),
2865 harness.root_with_prefix_set(prefix_set.freeze()),
2866 );
2867 }
2868
2869 #[test]
2870 fn test_prefix_set_preserves_cached_siblings() {
2871 let storage = (0..4096u16)
2872 .map(|key| (B256::right_padding_from(&key.to_be_bytes()), U256::from(1)))
2873 .collect();
2874 let harness = TrieTestHarness::new(storage);
2875 let changes = BTreeMap::from([
2876 (B256::right_padding_from(&[0x04, 0x00]), U256::from(2)),
2877 (B256::right_padding_from(&[0x04, 0xf0]), U256::from(2)),
2878 ]);
2879 let (expected_root, _) = harness.get_root_with_updates(&changes);
2880 let mut updated_storage = harness.storage().clone();
2881 updated_storage.extend(changes.clone());
2882 let hashed_factory = MockHashedCursorFactory::new(
2883 BTreeMap::new(),
2884 std::iter::once((harness.hashed_address(), updated_storage)).collect(),
2885 );
2886 let prefix_set = PrefixSetMut::from(changes.keys().map(Nibbles::unpack)).freeze();
2887 let trie_cursor =
2888 harness.trie_cursor_factory().storage_trie_cursor(harness.hashed_address()).unwrap();
2889 let hashed_cursor = hashed_factory.hashed_storage_cursor(harness.hashed_address()).unwrap();
2890 let mut calculator = StorageProofCalculator::new_storage(trie_cursor, hashed_cursor)
2891 .with_prefix_set(prefix_set);
2892 let mut targets = [ProofV2Target::new(B256::ZERO)];
2893 let proof = calculator.storage_proof(harness.hashed_address(), &mut targets).unwrap();
2894
2895 assert_eq!(calculator.compute_root_hash(&proof).unwrap(), Some(expected_root));
2896
2897 let clean_key = B256::right_padding_from(&[0x04, 0x88]);
2899 assert!(!hashed_factory
2900 .visited_storage_keys(harness.hashed_address())
2901 .iter()
2902 .any(|visit| visit.visited_key == Some(clean_key)));
2903 }
2904
2905 #[test]
2906 fn test_prefix_set_finishes_branch_before_returning_to_ancestor() {
2907 let storage =
2908 [[0x10, 0x00, 0x00], [0x12, 0x10, 0x00], [0x12, 0x10, 0x20], [0x12, 0x11, 0x00]]
2909 .into_iter()
2910 .map(|key| (B256::right_padding_from(&key), U256::from(1)))
2911 .collect();
2912 let harness = TrieTestHarness::new(storage);
2913 let changes = BTreeMap::from([
2914 (B256::right_padding_from(&[0x12, 0x00]), U256::from(1)),
2915 (B256::right_padding_from(&[0x12, 0x20]), U256::from(1)),
2916 ]);
2917 let mut after = harness.storage().clone();
2918 after.extend(changes.clone());
2919 let expected_root = crate::test_utils::storage_root_prehashed(
2920 after.iter().map(|(key, value)| (*key, *value)),
2921 );
2922 let hashed_factory = MockHashedCursorFactory::new(
2923 BTreeMap::new(),
2924 std::iter::once((harness.hashed_address(), after)).collect(),
2925 );
2926 let trie_cursor =
2927 harness.trie_cursor_factory().storage_trie_cursor(harness.hashed_address()).unwrap();
2928 let hashed_cursor = hashed_factory.hashed_storage_cursor(harness.hashed_address()).unwrap();
2929 let mut calculator = StorageProofCalculator::new_storage(trie_cursor, hashed_cursor)
2930 .with_prefix_set(PrefixSetMut::from(changes.keys().map(Nibbles::unpack)).freeze());
2931 let mut targets = [
2932 ProofV2Target::new(B256::ZERO),
2933 ProofV2Target::new(B256::right_padding_from(&[0x10]))
2934 .with_parent(ProofV2TargetParent::new(0)),
2935 ];
2936
2937 let proof = calculator.storage_proof(harness.hashed_address(), &mut targets).unwrap();
2939 assert_eq!(calculator.compute_root_hash(&proof).unwrap(), Some(expected_root));
2940 }
2941
2942 fn storage_leaf_hash(short_key: &Nibbles, value: &U256) -> B256 {
2945 let mut buf = Vec::new();
2946 alloy_trie::nodes::LeafNodeRef::new(short_key, &alloy_rlp::encode_fixed_size(value))
2947 .encode(&mut buf);
2948 keccak256(&buf)
2949 }
2950
2951 fn assert_branch_collapse(remaining_nibble: u8, removed_nibble: u8) {
2953 reth_tracing::init_test_tracing();
2954
2955 let val = U256::ONE;
2956 let child_keys = |nibble| {
2957 [
2958 B256::right_padding_from(&[0x20 | nibble, 0x00]),
2959 B256::right_padding_from(&[0x20 | nibble, 0x10]),
2960 ]
2961 };
2962 let [remaining_a, remaining_b] = child_keys(remaining_nibble);
2963 let [removed_a, removed_b] = child_keys(removed_nibble);
2964
2965 let initial_storage = BTreeMap::from([
2968 (remaining_a, val),
2969 (remaining_b, val),
2970 (removed_a, val),
2971 (removed_b, val),
2972 ]);
2973 let harness = TrieTestHarness::new(initial_storage);
2974
2975 let cached_branch = &harness
2976 .storage_trie_updates()
2977 .storage_nodes
2978 .get(&Nibbles::from_nibbles([0x2]))
2979 .expect("branch at 0x2");
2980 let child_mask =
2981 TrieMask::from_nibble(remaining_nibble) | TrieMask::from_nibble(removed_nibble);
2982 assert_eq!(cached_branch.state_mask, child_mask);
2983 assert_eq!(cached_branch.hash_mask, child_mask);
2984 assert!(cached_branch.tree_mask.is_empty());
2985
2986 let final_storage = BTreeMap::from([(remaining_a, val), (remaining_b, val)]);
2987 let expected_root = TrieTestHarness::new(final_storage.clone()).original_root();
2988 let updated_hashed = MockHashedCursorFactory::new(
2989 BTreeMap::new(),
2990 std::iter::once((harness.hashed_address(), final_storage)).collect(),
2991 );
2992
2993 let mut prefix_set = PrefixSetMut::default();
2995 prefix_set.insert(Nibbles::unpack(removed_a));
2996 prefix_set.insert(Nibbles::unpack(removed_b));
2997
2998 let trie_cursor =
2999 harness.trie_cursor_factory().storage_trie_cursor(harness.hashed_address()).unwrap();
3000 let hashed_cursor = updated_hashed.hashed_storage_cursor(harness.hashed_address()).unwrap();
3001 let mut calculator = StorageProofCalculator::new_storage(trie_cursor, hashed_cursor)
3002 .with_prefix_set(prefix_set.freeze());
3003 let root_node = calculator
3004 .storage_root_node(harness.hashed_address())
3005 .expect("storage_root_node should succeed after branch collapse");
3006 let root =
3007 calculator.compute_root_hash(core::slice::from_ref(&root_node)).unwrap().unwrap();
3008
3009 pretty_assertions::assert_eq!(expected_root, root);
3010 }
3011
3012 #[test]
3013 fn test_branch_collapse_removed_child_before_remaining() {
3014 assert_branch_collapse(1, 0);
3015 }
3016
3017 #[test]
3018 fn test_branch_collapse_removed_child_after_remaining() {
3019 assert_branch_collapse(4, 9);
3020 }
3021
3022 #[test]
3023 fn test_prefix_set_root_proof_preserves_clean_sibling_after_cached_branch_collapse() {
3024 reth_tracing::init_test_tracing();
3025
3026 let dirty = B256::right_padding_from(&[0x10, 0x00, 0x10]);
3027 let clean_sibling = B256::right_padding_from(&[0x10, 0x10]);
3028 let storage = [
3029 B256::right_padding_from(&[0x10]),
3030 dirty,
3031 B256::right_padding_from(&[0x10, 0x01]),
3032 B256::right_padding_from(&[0x10, 0x02]),
3033 clean_sibling,
3034 B256::right_padding_from(&[0x11]),
3035 B256::right_padding_from(&[0x12]),
3036 ]
3037 .into_iter()
3038 .map(|key| (key, U256::ONE))
3039 .collect();
3040
3041 let harness = ProofTestHarness::new(storage);
3042 let expected_root = harness.original_root();
3043
3044 let mut prefix_set = PrefixSetMut::default();
3045 prefix_set.insert(Nibbles::unpack(dirty));
3046
3047 let mut prefix_set_with_sibling = PrefixSetMut::default();
3048 prefix_set_with_sibling.insert(Nibbles::unpack(dirty));
3049 prefix_set_with_sibling.insert(Nibbles::unpack(clean_sibling));
3050
3051 pretty_assertions::assert_eq!(
3052 Some(expected_root),
3053 harness.root_with_prefix_set(prefix_set_with_sibling.freeze()),
3054 );
3055 pretty_assertions::assert_eq!(
3056 Some(expected_root),
3057 harness.root_with_prefix_set(prefix_set.freeze()),
3058 "a dirty prefix must not omit a clean sibling after collapsing a cached branch",
3059 );
3060 }
3061
3062 #[test]
3063 fn test_prefix_set_range_skips_covered_cached_branch() {
3064 reth_tracing::init_test_tracing();
3065
3066 let before = B256::right_padding_from(&[0x30]);
3067 let cached_a = B256::right_padding_from(&[0x80, 0x10]);
3068 let cached_b = B256::right_padding_from(&[0x80, 0x15]);
3069 let dirty = B256::right_padding_from(&[0x80, 0xc0]);
3070 let after = B256::right_padding_from(&[0x90]);
3071
3072 let storage = [before, cached_a, cached_b, dirty, after]
3075 .into_iter()
3076 .enumerate()
3077 .map(|(i, key)| (key, U256::from(i + 1)))
3078 .collect();
3079
3080 let harness = ProofTestHarness::new(storage);
3081 let expected_root = harness.original_root();
3082 let mut prefix_set = PrefixSetMut::default();
3083 prefix_set.insert(Nibbles::unpack(dirty));
3084
3085 pretty_assertions::assert_eq!(
3086 Some(expected_root),
3087 harness.root_with_prefix_set(prefix_set.freeze()),
3088 );
3089 }
3090
3091 #[test]
3092 fn test_cached_branch_extension_skips_diverging_target() {
3093 reth_tracing::init_test_tracing();
3094
3095 let val = U256::from(100u64);
3096
3097 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> =
3106 [(key_a0, val), (key_a1, val), (key_c, val), (key_d, val), (key_e, val)]
3107 .into_iter()
3108 .collect();
3109 let correct_harness = TrieTestHarness::new(all_storage.clone());
3110 let expected_root = correct_harness.original_root();
3111
3112 let leaf_hash_a0 = storage_leaf_hash(&Nibbles::unpack(key_a0).slice(4..), &val);
3114 let leaf_hash_a1 = storage_leaf_hash(&Nibbles::unpack(key_a1).slice(4..), &val);
3115 let leaf_hash_d = storage_leaf_hash(&Nibbles::unpack(key_d).slice(2..), &val);
3116 let leaf_hash_e = storage_leaf_hash(&Nibbles::unpack(key_e).slice(2..), &val);
3117
3118 let branch_6_state_mask = TrieMask::new((1 << 0xa) | (1 << 0xb) | (1 << 0xc));
3125 let branch_6_hash_mask = TrieMask::new((1 << 0xb) | (1 << 0xc));
3126 let branch_6 = BranchNodeCompact::new(
3127 branch_6_state_mask,
3128 TrieMask::new(0),
3129 branch_6_hash_mask,
3130 vec![leaf_hash_d, leaf_hash_e],
3131 None,
3132 );
3133
3134 let branch_6a3_state_mask = TrieMask::new((1 << 0) | (1 << 1));
3138 let branch_6a3 = BranchNodeCompact::new(
3139 branch_6a3_state_mask,
3140 TrieMask::new(0),
3141 branch_6a3_state_mask,
3142 vec![leaf_hash_a0, leaf_hash_a1],
3143 None,
3144 );
3145
3146 let inconsistent_nodes: BTreeMap<Nibbles, BranchNodeCompact> = [
3148 (Nibbles::from_nibbles([0x6]), branch_6),
3149 (Nibbles::from_nibbles([0x6, 0xa, 0x3]), branch_6a3),
3150 ]
3151 .into_iter()
3152 .collect();
3153
3154 let mut harness = TrieTestHarness::new(all_storage);
3156 harness.set_trie_nodes(inconsistent_nodes);
3157
3158 let mut prefix_set = PrefixSetMut::default();
3161 prefix_set.insert(Nibbles::unpack(key_c));
3162
3163 let trie_cursor =
3165 harness.trie_cursor_factory().storage_trie_cursor(harness.hashed_address()).unwrap();
3166 let hashed_cursor = harness
3167 .hashed_cursor_factory()
3168 .hashed_storage_cursor(harness.hashed_address())
3169 .unwrap();
3170 let mut calculator = StorageProofCalculator::new_storage(trie_cursor, hashed_cursor)
3171 .with_prefix_set(prefix_set.freeze());
3172
3173 let root_node = calculator
3174 .storage_root_node(harness.hashed_address())
3175 .expect("storage_root_node should succeed");
3176 let got_root = calculator
3177 .compute_root_hash(core::slice::from_ref(&root_node))
3178 .unwrap()
3179 .expect("should produce a root hash");
3180
3181 pretty_assertions::assert_eq!(
3183 expected_root,
3184 got_root,
3185 "Root hash should match correct trie; cached extension must not skip diverging leaves"
3186 );
3187
3188 let mut targets = vec![ProofV2Target::new(key_c)];
3190 let proofs = calculator
3191 .storage_proof(harness.hashed_address(), &mut targets)
3192 .expect("storage_proof should succeed");
3193
3194 let key_c_nibbles = Nibbles::unpack(key_c);
3195 let has_matching_node = proofs.iter().any(|node| key_c_nibbles.starts_with(&node.path));
3196 assert!(
3197 has_matching_node,
3198 "Proof for key_c should contain at least one node on key_c's path, got: {proofs:?}"
3199 );
3200 }
3201
3202 #[test]
3203 fn test_cached_branch_extension_skips_diverging_target_before() {
3204 reth_tracing::init_test_tracing();
3205
3206 let val = U256::from(100u64);
3207
3208 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> =
3218 [(key_a0, val), (key_a1, val), (key_c, val), (key_d, val), (key_e, val)]
3219 .into_iter()
3220 .collect();
3221 let correct_harness = TrieTestHarness::new(all_storage.clone());
3222 let expected_root = correct_harness.original_root();
3223
3224 let leaf_hash_a0 = storage_leaf_hash(&Nibbles::unpack(key_a0).slice(4..), &val);
3226 let leaf_hash_a1 = storage_leaf_hash(&Nibbles::unpack(key_a1).slice(4..), &val);
3227 let leaf_hash_d = storage_leaf_hash(&Nibbles::unpack(key_d).slice(2..), &val);
3228 let leaf_hash_e = storage_leaf_hash(&Nibbles::unpack(key_e).slice(2..), &val);
3229
3230 let branch_6_state_mask = TrieMask::new((1 << 0xa) | (1 << 0xb) | (1 << 0xc));
3237 let branch_6_hash_mask = TrieMask::new((1 << 0xb) | (1 << 0xc));
3238 let branch_6 = BranchNodeCompact::new(
3239 branch_6_state_mask,
3240 TrieMask::new(0),
3241 branch_6_hash_mask,
3242 vec![leaf_hash_d, leaf_hash_e],
3243 None,
3244 );
3245
3246 let branch_6a8_state_mask = TrieMask::new((1 << 0) | (1 << 1));
3250 let branch_6a8 = BranchNodeCompact::new(
3251 branch_6a8_state_mask,
3252 TrieMask::new(0),
3253 branch_6a8_state_mask,
3254 vec![leaf_hash_a0, leaf_hash_a1],
3255 None,
3256 );
3257
3258 let inconsistent_nodes: BTreeMap<Nibbles, BranchNodeCompact> = [
3260 (Nibbles::from_nibbles([0x6]), branch_6),
3261 (Nibbles::from_nibbles([0x6, 0xa, 0x8]), branch_6a8),
3262 ]
3263 .into_iter()
3264 .collect();
3265
3266 let mut harness = TrieTestHarness::new(all_storage);
3268 harness.set_trie_nodes(inconsistent_nodes);
3269
3270 let mut prefix_set = PrefixSetMut::default();
3272 prefix_set.insert(Nibbles::unpack(key_c));
3273
3274 let trie_cursor =
3276 harness.trie_cursor_factory().storage_trie_cursor(harness.hashed_address()).unwrap();
3277 let hashed_cursor = harness
3278 .hashed_cursor_factory()
3279 .hashed_storage_cursor(harness.hashed_address())
3280 .unwrap();
3281 let mut calculator = StorageProofCalculator::new_storage(trie_cursor, hashed_cursor)
3282 .with_prefix_set(prefix_set.freeze());
3283
3284 let root_node = calculator
3285 .storage_root_node(harness.hashed_address())
3286 .expect("storage_root_node should succeed");
3287 let got_root = calculator
3288 .compute_root_hash(core::slice::from_ref(&root_node))
3289 .unwrap()
3290 .expect("should produce a root hash");
3291
3292 pretty_assertions::assert_eq!(
3294 expected_root,
3295 got_root,
3296 "Root hash should match correct trie; cached extension must not skip diverging leaves before cached branch"
3297 );
3298
3299 let mut targets = vec![ProofV2Target::new(key_c)];
3301 let proofs = calculator
3302 .storage_proof(harness.hashed_address(), &mut targets)
3303 .expect("storage_proof should succeed");
3304
3305 let key_c_nibbles = Nibbles::unpack(key_c);
3306 let has_matching_node = proofs.iter().any(|node| key_c_nibbles.starts_with(&node.path));
3307 assert!(
3308 has_matching_node,
3309 "Proof for key_c should contain at least one node on key_c's path, got: {proofs:?}"
3310 );
3311 }
3312
3313 #[test]
3314 fn test_skipped_parent_branch_with_unskipped_child() {
3315 reth_tracing::init_test_tracing();
3316
3317 let val = U256::ONE;
3318 let updated_val = U256::from(2u64);
3319
3320 let key_2 = B256::right_padding_from(&[0x20]);
3322 let key_2f00 = B256::right_padding_from(&[0x2f, 0x00]);
3323 let key_2f01 = B256::right_padding_from(&[0x2f, 0x01]);
3324 let key_2f10 = B256::right_padding_from(&[0x2f, 0x10]);
3325 let key_2f11 = B256::right_padding_from(&[0x2f, 0x11]);
3326 let key_300 = B256::right_padding_from(&[0x30, 0x00]);
3327 let key_301 = B256::right_padding_from(&[0x30, 0x10]);
3328 let key_310 = B256::right_padding_from(&[0x31, 0x00]);
3329 let key_311 = B256::right_padding_from(&[0x31, 0x10]);
3330 let key_500 = B256::right_padding_from(&[0x50, 0x00]);
3331 let key_501 = B256::right_padding_from(&[0x50, 0x10]);
3332 let key_510 = B256::right_padding_from(&[0x51, 0x00]);
3333 let key_511 = B256::right_padding_from(&[0x51, 0x10]);
3334
3335 let all_keys = [
3336 key_2, key_2f00, key_2f01, key_2f10, key_2f11, key_300, key_301, key_310, key_311,
3337 key_500, key_501, key_510, key_511,
3338 ];
3339
3340 let original_storage: BTreeMap<B256, U256> = all_keys.iter().map(|k| (*k, val)).collect();
3341 let harness = TrieTestHarness::new(original_storage);
3342
3343 let trie_updates = harness.storage_trie_updates();
3345 assert!(trie_updates.storage_nodes.contains_key(&Nibbles::from_nibbles([0x2])));
3346 assert!(trie_updates.storage_nodes.contains_key(&Nibbles::from_nibbles([0x2, 0xf])));
3347 assert!(trie_updates.storage_nodes.contains_key(&Nibbles::from_nibbles([0x3])));
3348
3349 let changeset: BTreeMap<B256, U256> = std::iter::once((key_2, updated_val)).collect();
3352 let (expected_root, _) = harness.get_root_with_updates(&changeset);
3353
3354 let mut updated_storage = harness.storage().clone();
3355 updated_storage.insert(key_2, updated_val);
3356
3357 let updated_hashed = MockHashedCursorFactory::new(
3358 BTreeMap::new(),
3359 std::iter::once((harness.hashed_address(), updated_storage)).collect(),
3360 );
3361
3362 let mut prefix_set = PrefixSetMut::default();
3363 prefix_set.insert(Nibbles::unpack(key_2));
3364
3365 let trie_cursor =
3366 harness.trie_cursor_factory().storage_trie_cursor(harness.hashed_address()).unwrap();
3367 let hashed_cursor = updated_hashed.hashed_storage_cursor(harness.hashed_address()).unwrap();
3368 let mut calculator = StorageProofCalculator::new_storage(trie_cursor, hashed_cursor)
3369 .with_prefix_set(prefix_set.freeze());
3370 let root_node = calculator
3371 .storage_root_node(harness.hashed_address())
3372 .expect("storage_root_node should succeed");
3373
3374 let got_root = calculator
3375 .compute_root_hash(&[root_node])
3376 .expect("root hash should succeed")
3377 .expect("root should get hashed");
3378 pretty_assertions::assert_eq!(expected_root, got_root);
3379 }
3380
3381 #[test]
3382 fn test_blinded_local_root_returns_trie_inconsistency() {
3383 let key = B256::right_padding_from(&[0x63, 0xaa]);
3384 let value = U256::ONE;
3385 let hash = storage_leaf_hash(&Nibbles::unpack(key).slice(2..), &value);
3386 let mask = TrieMask::from_nibble(3);
3387 let cached_branch =
3388 BranchNodeCompact::new(mask, TrieMask::default(), mask, vec![hash], None);
3389
3390 let mut harness = TrieTestHarness::new(BTreeMap::from([(key, value)]));
3391 harness.set_trie_nodes(BTreeMap::from([(Nibbles::from_nibbles([6]), cached_branch)]));
3392
3393 let trie_cursor =
3394 harness.trie_cursor_factory().storage_trie_cursor(harness.hashed_address()).unwrap();
3395 let hashed_cursor = harness
3396 .hashed_cursor_factory()
3397 .hashed_storage_cursor(harness.hashed_address())
3398 .unwrap();
3399 let mut calculator = StorageProofCalculator::new_storage(trie_cursor, hashed_cursor);
3400
3401 assert!(matches!(
3402 calculator.storage_root_node(harness.hashed_address()),
3403 Err(StateProofError::TrieInconsistency(_))
3404 ));
3405 }
3406
3407 #[test]
3408 fn test_cached_hash_with_deleted_leaf() {
3409 reth_tracing::init_test_tracing();
3410
3411 let val_3 = U256::from(111u64);
3413 let val_5 = U256::from(222u64);
3414 let val_8 = U256::from(333u64);
3415
3416 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);
3425 let leaf_hash_5 = storage_leaf_hash(&Nibbles::unpack(key_65).slice(2..), &val_5);
3426 let leaf_hash_8 = storage_leaf_hash(&Nibbles::unpack(key_68).slice(2..), &val_8);
3427
3428 let state_mask = TrieMask::new((1 << 3) | (1 << 5) | (1 << 8));
3430 let cached_branch = BranchNodeCompact::new(
3431 state_mask,
3432 TrieMask::new(0),
3433 state_mask, vec![leaf_hash_3, leaf_hash_5, leaf_hash_8],
3435 None,
3436 );
3437
3438 let storage_nodes: BTreeMap<Nibbles, BranchNodeCompact> =
3439 std::iter::once((Nibbles::from_nibbles([0x6]), cached_branch)).collect();
3440
3441 let mut harness =
3443 TrieTestHarness::new([(key_65, val_5), (key_68, val_8)].into_iter().collect());
3444 let expected_root = harness.original_root();
3445
3446 harness.set_trie_nodes(storage_nodes);
3448
3449 let mut prefix_set = PrefixSetMut::default();
3452 prefix_set.insert(Nibbles::unpack(key_63));
3453
3454 let mut targets = vec![ProofV2Target::new(key_63)];
3458
3459 let trie_cursor =
3460 harness.trie_cursor_factory().storage_trie_cursor(harness.hashed_address()).unwrap();
3461 let hashed_cursor = harness
3462 .hashed_cursor_factory()
3463 .hashed_storage_cursor(harness.hashed_address())
3464 .unwrap();
3465 let mut calculator = StorageProofCalculator::new_storage(trie_cursor, hashed_cursor)
3466 .with_prefix_set(prefix_set.freeze());
3467
3468 let proofs = calculator
3469 .storage_proof(harness.hashed_address(), &mut targets)
3470 .expect("storage_proof should succeed");
3471 assert_eq!(1, proofs.len());
3472 let got_root = calculator
3473 .compute_root_hash(&proofs)
3474 .expect("compute_root_hash should succeed")
3475 .expect("should produce a root hash (proof contains root node)");
3476
3477 pretty_assertions::assert_eq!(
3480 expected_root,
3481 got_root,
3482 "Root hash should match trie without key_63; cached hash index is off when \
3483 an earlier hashed child has no leaves (absence proof target)"
3484 );
3485 }
3486}