1use crate::{
11 hashed_cursor::{HashedCursor, HashedStorageCursor},
12 trie_cursor::{depth_first, TrieCursor, TrieStorageCursor},
13};
14use alloy_primitives::{keccak256, B256, U256};
15use alloy_rlp::Encodable;
16use alloy_trie::{BranchNodeCompact, TrieMask};
17use reth_execution_errors::trie::StateProofError;
18use reth_trie_common::{
19 prefix_set::PrefixSet, BranchNodeMasks, BranchNodeRef, BranchNodeV2, Nibbles, ProofTrieNodeV2,
20 ProofV2Target, RlpNode, TrieNodeV2,
21};
22use std::cmp::Ordering;
23use tracing::{error, instrument, trace};
24
25mod value;
26pub use value::*;
27
28mod node;
29use node::*;
30
31mod target;
32pub(crate) use target::*;
33
34static TRACE_TARGET: &str = "trie::proof_v2";
36
37const RLP_ENCODE_BUF_SIZE: usize = 1024;
39
40#[derive(Debug)]
48pub struct ProofCalculator<TC, HC, VE: LeafValueEncoder> {
49 trie_cursor: TC,
51 hashed_cursor: HC,
53 branch_stack: Vec<ProofTrieBranch>,
56 branch_path: Nibbles,
58 child_stack: Vec<ProofTrieBranchChild<VE::DeferredEncoder>>,
75 cached_branch_stack: Vec<(Nibbles, BranchNodeCompact)>,
79 retained_proofs: Vec<ProofTrieNodeV2>,
82 rlp_nodes_bufs: Vec<Vec<RlpNode>>,
88 rlp_encode_buf: Vec<u8>,
90 prefix_set: PrefixSet,
92}
93
94impl<TC, HC, VE: LeafValueEncoder> ProofCalculator<TC, HC, VE> {
95 pub fn new(trie_cursor: TC, hashed_cursor: HC) -> Self {
97 Self {
98 trie_cursor,
99 hashed_cursor,
100 branch_stack: Vec::<_>::with_capacity(64),
101 branch_path: Nibbles::new(),
102 child_stack: Vec::<_>::with_capacity(64),
103 cached_branch_stack: Vec::<_>::with_capacity(64),
104 retained_proofs: Vec::<_>::with_capacity(32),
105 rlp_nodes_bufs: Vec::<_>::with_capacity(8),
106 rlp_encode_buf: Vec::<_>::with_capacity(RLP_ENCODE_BUF_SIZE),
107 prefix_set: PrefixSet::default(),
108 }
109 }
110
111 pub fn with_prefix_set(mut self, prefix_set: PrefixSet) -> Self {
119 self.prefix_set = prefix_set;
120 self
121 }
122}
123
124impl<TC, HC, VE> ProofCalculator<TC, HC, VE>
125where
126 TC: TrieCursor,
127 HC: HashedCursor,
128 VE: LeafValueEncoder<Value = HC::Value>,
129{
130 fn take_rlp_nodes_buf(&mut self) -> Vec<RlpNode> {
135 self.rlp_nodes_bufs
136 .pop()
137 .map(|mut buf| {
138 buf.clear();
139 buf
140 })
141 .unwrap_or_else(|| Vec::with_capacity(16))
142 }
143
144 #[inline]
151 const fn maybe_parent_nibble(&self) -> usize {
152 !self.branch_stack.is_empty() as usize
153 }
154
155 #[instrument(
191 target = TRACE_TARGET,
192 level = "trace",
193 skip_all,
194 fields(?path, ?check_parent_path),
195 ret,
196 )]
197 fn should_retain<'a>(
198 &self,
199 targets: &mut Option<TargetsCursor<'a>>,
200 path: &Nibbles,
201 check_parent_path: bool,
202 ) -> bool {
203 let Some(targets) = targets.as_mut() else { return false };
205
206 let (mut lower, mut upper) = targets.current();
207
208 loop {
209 if lower.key_nibbles.starts_with(path) {
230 let is_below_parent = |target: &ProofV2Target| {
231 target.parent.path_len().is_none_or(|len| path.len() > len)
232 };
233 return !check_parent_path ||
234 (is_below_parent(lower) ||
235 targets
236 .skip_iter()
237 .take_while(|target| target.key_nibbles.starts_with(path))
238 .any(is_below_parent) ||
239 targets
240 .rev_iter()
241 .take_while(|target| target.key_nibbles.starts_with(path))
242 .any(is_below_parent))
243 }
244
245 if upper
248 .is_some_and(|upper| depth_first::cmp(path, &upper.key_nibbles) != Ordering::Less)
249 {
250 (lower, upper) = targets.next();
251 trace!(target: TRACE_TARGET, target = ?lower, "upper target <= path, next target");
252 } else {
253 return false
254 }
255 }
256 }
257
258 fn commit_child<'a>(
264 &mut self,
265 targets: &mut Option<TargetsCursor<'a>>,
266 child_path: Nibbles,
267 child: ProofTrieBranchChild<VE::DeferredEncoder>,
268 ) -> Result<RlpNode, StateProofError> {
269 if let ProofTrieBranchChild::RlpNode(rlp_node) = child {
271 return Ok(rlp_node)
272 }
273
274 if self.should_retain(targets, &child_path, true) {
276 trace!(target: TRACE_TARGET, ?child_path, "Retaining child");
277
278 self.rlp_encode_buf.clear();
283 let proof_node = child.into_proof_trie_node(child_path, &mut self.rlp_encode_buf)?;
284
285 self.rlp_encode_buf.clear();
288 proof_node.node.encode(&mut self.rlp_encode_buf);
289
290 self.retained_proofs.push(proof_node);
291 return Ok(RlpNode::from_rlp(&self.rlp_encode_buf));
292 }
293
294 self.rlp_encode_buf.clear();
298 let (child_rlp_node, freed_rlp_nodes_buf) = child.into_rlp(&mut self.rlp_encode_buf)?;
299
300 if let Some(buf) = freed_rlp_nodes_buf {
302 self.rlp_nodes_bufs.push(buf);
303 }
304
305 Ok(child_rlp_node)
306 }
307
308 #[inline]
311 fn child_path_at(&self, nibble: u8) -> Nibbles {
312 let mut child_path = self.branch_path;
313 debug_assert!(child_path.len() < 64);
314 child_path.push_unchecked(nibble);
315 child_path
316 }
317
318 #[inline]
324 fn highest_set_nibble(mask: TrieMask) -> u8 {
325 debug_assert!(!mask.is_empty());
326 (u16::BITS - mask.leading_zeros() - 1) as u8
327 }
328
329 fn last_child_path(&self) -> Option<Nibbles> {
332 let Some(branch) = self.branch_stack.last() else {
334 return Some(Nibbles::new());
335 };
336
337 (!branch.state_mask.is_empty())
338 .then(|| self.child_path_at(Self::highest_set_nibble(branch.state_mask)))
339 }
340
341 #[instrument(
351 target = TRACE_TARGET,
352 level = "trace",
353 skip_all,
354 fields(child_path = ?self.last_child_path()),
355 )]
356 fn commit_last_child<'a>(
357 &mut self,
358 targets: &mut Option<TargetsCursor<'a>>,
359 ) -> Result<(), StateProofError> {
360 if matches!(self.child_stack.last(), Some(ProofTrieBranchChild::RlpNode(_))) {
361 trace!(target: TRACE_TARGET, "Last child already committed, leaving stack unchanged");
362 return Ok(())
363 }
364
365 let Some(child_path) = self.last_child_path() else { return Ok(()) };
366 let child =
367 self.child_stack.pop().expect("child_stack can't be empty if there's a child path");
368
369 if self.should_retain(targets, &child_path, true) {
372 let child_rlp_node = self.commit_child(targets, child_path, child)?;
373 trace!(target: TRACE_TARGET, ?child_rlp_node, "Pushing committed child RlpNode onto stack");
374 self.child_stack.push(ProofTrieBranchChild::RlpNode(child_rlp_node));
375 } else {
376 trace!(target: TRACE_TARGET, "Pushing uncommitted child onto stack");
377 self.child_stack.push(child);
378 }
379
380 Ok(())
381 }
382
383 fn push_new_leaf<'a>(
391 &mut self,
392 targets: &mut Option<TargetsCursor<'a>>,
393 leaf_nibble: u8,
394 leaf_short_key: Nibbles,
395 leaf_val: VE::DeferredEncoder,
396 ) -> Result<(), StateProofError> {
397 self.commit_last_child(targets)?;
400
401 let branch = self.branch_stack.last_mut().expect("branch_stack cannot be empty");
404
405 debug_assert!(!branch.state_mask.is_bit_set(leaf_nibble));
406 branch.state_mask.set_bit(leaf_nibble);
407
408 self.child_stack
409 .push(ProofTrieBranchChild::Leaf { short_key: leaf_short_key, value: leaf_val });
410
411 Ok(())
412 }
413
414 fn push_new_branch(&mut self, new_child_path: Nibbles) -> (u8, Nibbles) {
421 let new_child_short_key = if self.branch_stack.is_empty() {
424 new_child_path
425 } else {
426 trim_nibbles_prefix(&new_child_path, self.branch_path.len() + 1)
429 };
430
431 let first_child = self
434 .child_stack
435 .last_mut()
436 .expect("push_new_branch can't be called with empty child_stack");
437
438 let first_child_short_key = first_child.short_key();
439 debug_assert!(
440 !first_child_short_key.is_empty(),
441 "push_new_branch called when top child on stack is not a leaf or extension with a short key",
442 );
443
444 let common_prefix_len = first_child_short_key.common_prefix_length(&new_child_short_key);
447
448 let first_child_nibble = first_child_short_key.get_unchecked(common_prefix_len);
451 first_child.trim_short_key_prefix(common_prefix_len + 1);
452
453 let new_child_nibble = new_child_short_key.get_unchecked(common_prefix_len);
456 let new_child_short_key = trim_nibbles_prefix(&new_child_short_key, common_prefix_len + 1);
457
458 let branch_path_len =
466 self.branch_path.len() + common_prefix_len + self.maybe_parent_nibble();
467 self.branch_path = new_child_path.slice_unchecked(0, branch_path_len);
468
469 self.branch_stack.push(ProofTrieBranch {
473 ext_len: common_prefix_len as u8,
474 state_mask: TrieMask::new(1 << first_child_nibble),
475 masks: None,
476 });
477
478 trace!(
479 target: TRACE_TARGET,
480 ?new_child_path,
481 ?common_prefix_len,
482 ?first_child_nibble,
483 branch_path = ?self.branch_path,
484 "Pushed new branch",
485 );
486
487 (new_child_nibble, new_child_short_key)
488 }
489
490 #[instrument(target = TRACE_TARGET, level = "trace", skip_all)]
498 fn pop_branch<'a>(
499 &mut self,
500 targets: &mut Option<TargetsCursor<'a>>,
501 ) -> Result<(), StateProofError> {
502 trace!(
503 target: TRACE_TARGET,
504 branch = ?self.branch_stack.last(),
505 branch_path = ?self.branch_path,
506 child_stack_len = ?self.child_stack.len(),
507 "called",
508 );
509
510 self.commit_last_child(targets)?;
513
514 let mut rlp_nodes_buf = self.take_rlp_nodes_buf();
515 let branch = self.branch_stack.pop().expect("branch_stack cannot be empty");
516
517 let num_children = branch.state_mask.count_ones() as usize;
520 debug_assert!(
521 self.child_stack.len() >= num_children,
522 "Stack is missing necessary children ({num_children:?})"
523 );
524 debug_assert!(
525 num_children >= 2,
526 "A branch must have at least two children, got {num_children}"
527 );
528
529 rlp_nodes_buf.reserve(num_children);
531 for child in self.child_stack.drain(self.child_stack.len() - num_children..) {
532 let child_rlp_node = match child {
533 ProofTrieBranchChild::RlpNode(rlp_node) => rlp_node,
534 uncommitted_child => {
535 self.rlp_encode_buf.clear();
537 let (rlp_node, freed_buf) =
538 uncommitted_child.into_rlp(&mut self.rlp_encode_buf)?;
539 if let Some(buf) = freed_buf {
540 self.rlp_nodes_bufs.push(buf);
541 }
542 rlp_node
543 }
544 };
545 rlp_nodes_buf.push(child_rlp_node);
546 }
547
548 debug_assert_eq!(
549 rlp_nodes_buf.len(),
550 num_children,
551 "children length must match number of bits set in state_mask"
552 );
553
554 let short_key = trim_nibbles_prefix(
557 &self.branch_path,
558 self.branch_path.len() - branch.ext_len as usize,
559 );
560
561 let rlp_node = if short_key.is_empty() {
563 None
564 } else {
565 self.rlp_encode_buf.clear();
566 BranchNodeRef::new(&rlp_nodes_buf, branch.state_mask).encode(&mut self.rlp_encode_buf);
567 Some(RlpNode::from_rlp(&self.rlp_encode_buf))
568 };
569
570 let branch_as_child = ProofTrieBranchChild::Branch {
572 node: BranchNodeV2::new(short_key, rlp_nodes_buf, branch.state_mask, rlp_node),
573 masks: branch.masks,
574 };
575
576 self.child_stack.push(branch_as_child);
577
578 let new_path_len =
581 self.branch_path.len() - branch.ext_len as usize - self.maybe_parent_nibble();
582
583 debug_assert!(self.branch_path.len() >= new_path_len);
584 self.branch_path = self.branch_path.slice_unchecked(0, new_path_len);
585
586 Ok(())
587 }
588
589 fn push_leaf<'a>(
592 &mut self,
593 targets: &mut Option<TargetsCursor<'a>>,
594 key: Nibbles,
595 val: VE::DeferredEncoder,
596 ) -> Result<(), StateProofError> {
597 loop {
598 trace!(
599 target: TRACE_TARGET,
600 ?key,
601 branch_stack_len = ?self.branch_stack.len(),
602 branch_path = ?self.branch_path,
603 child_stack_len = ?self.child_stack.len(),
604 "push_leaf: loop",
605 );
606
607 let curr_branch_state_mask = match self.branch_stack.last() {
611 Some(curr_branch) => curr_branch.state_mask,
612 None if self.child_stack.is_empty() => {
613 self.child_stack
615 .push(ProofTrieBranchChild::Leaf { short_key: key, value: val });
616 return Ok(())
617 }
618 None => {
619 debug_assert_eq!(self.child_stack.len(), 1);
622 debug_assert!(!self
623 .child_stack
624 .last()
625 .expect("already checked for emptiness")
626 .short_key()
627 .is_empty());
628 let (nibble, short_key) = self.push_new_branch(key);
629 self.push_new_leaf(targets, nibble, short_key, val)?;
630 return Ok(())
631 }
632 };
633
634 let common_prefix_len = self.branch_path.common_prefix_length(&key);
637
638 if common_prefix_len < self.branch_path.len() {
642 self.pop_branch(targets)?;
643 continue
644 }
645
646 let nibble = key.get_unchecked(common_prefix_len);
651 if curr_branch_state_mask.is_bit_set(nibble) {
652 let (nibble, short_key) = self.push_new_branch(key);
655 self.push_new_leaf(targets, nibble, short_key, val)?;
657 } else {
658 let short_key = key.slice_unchecked(common_prefix_len + 1, key.len());
659 self.push_new_leaf(targets, nibble, short_key, val)?;
660 }
661
662 return Ok(())
663 }
664 }
665
666 #[instrument(
673 target = TRACE_TARGET,
674 level = "trace",
675 skip_all,
676 fields(?lower_bound, ?upper_bound),
677 )]
678 fn calculate_key_range<'a>(
679 &mut self,
680 value_encoder: &mut VE,
681 targets: &mut Option<TargetsCursor<'a>>,
682 hashed_cursor_state: &mut HashedCursorState<VE::DeferredEncoder>,
683 lower_bound: Nibbles,
684 upper_bound: Option<Nibbles>,
685 ) -> Result<(), StateProofError> {
686 let mut map_hashed_cursor_entry = |(key_b256, val): (B256, _)| {
690 debug_assert_eq!(key_b256.len(), 32);
691 let key = Nibbles::unpack_array(key_b256.as_ref());
692 let val = value_encoder.deferred_encoder(key_b256, val);
693 (key, val)
694 };
695
696 if hashed_cursor_state.needs_seek_to(&lower_bound) {
699 trace!(
700 target: TRACE_TARGET,
701 current=?hashed_cursor_state.path(),
702 "Seeking hashed cursor to meet lower bound",
703 );
704
705 let lower_key = B256::right_padding_from(&lower_bound.pack());
706 *hashed_cursor_state = HashedCursorState::seeked(
707 lower_bound,
708 self.hashed_cursor.seek(lower_key)?.map(&mut map_hashed_cursor_entry),
709 );
710 }
711
712 while hashed_cursor_state
714 .path()
715 .is_some_and(|key| upper_bound.is_none_or(|upper_bound| key < &upper_bound))
716 {
717 let (key, val) = hashed_cursor_state.take();
718 self.push_leaf(targets, key, val)?;
719 *hashed_cursor_state = HashedCursorState::seeked(
720 key,
721 self.hashed_cursor.next()?.map(&mut map_hashed_cursor_entry),
722 );
723 }
724
725 trace!(target: TRACE_TARGET, "No further keys within range");
726 Ok(())
727 }
728
729 #[inline]
731 const fn new_from_cached_branch(
732 cached_branch: &BranchNodeCompact,
733 ext_len: u8,
734 ) -> ProofTrieBranch {
735 ProofTrieBranch {
736 ext_len,
737 state_mask: TrieMask::new(0),
738 masks: Some(BranchNodeMasks {
739 tree_mask: cached_branch.tree_mask,
740 hash_mask: cached_branch.hash_mask,
741 }),
742 }
743 }
744
745 fn push_cached_branch<'a>(
752 &mut self,
753 targets: &mut Option<TargetsCursor<'a>>,
754 cached_path: Nibbles,
755 cached_branch: &BranchNodeCompact,
756 ) -> Result<(), StateProofError> {
757 debug_assert!(
758 cached_path.starts_with(&self.branch_path),
759 "push_cached_branch called with path {cached_path:?} which is not a child of current branch {:?}",
760 self.branch_path,
761 );
762
763 let parent_branch = self.branch_stack.last();
764
765 if self.child_stack.is_empty() && parent_branch.is_none() {
768 self.branch_path = cached_path;
769 self.branch_stack
770 .push(Self::new_from_cached_branch(cached_branch, cached_path.len() as u8));
771 return Ok(())
772 }
773
774 let cached_branch_nibble = cached_path.get_unchecked(self.branch_path.len());
777
778 let (cached_branch_nibble, ext_len) = if parent_branch
781 .is_none_or(|parent_branch| parent_branch.state_mask.is_bit_set(cached_branch_nibble))
782 {
783 debug_assert!(!self
792 .child_stack
793 .last()
794 .expect("already checked for emptiness")
795 .short_key()
796 .is_empty());
797
798 let (nibble, short_key) = self.push_new_branch(cached_path);
800 (nibble, short_key.len())
801 } else {
802 (cached_branch_nibble, cached_path.len() - self.branch_path.len() - 1)
806 };
807
808 self.commit_last_child(targets)?;
812
813 if let Some(parent_branch) = self.branch_stack.last_mut() {
816 parent_branch.state_mask.set_bit(cached_branch_nibble);
817 }
818
819 self.branch_path = cached_path;
821 self.branch_stack.push(Self::new_from_cached_branch(cached_branch, ext_len as u8));
822
823 trace!(
824 target: TRACE_TARGET,
825 branch=?self.branch_stack.last(),
826 branch_path=?self.branch_path,
827 "Pushed cached branch",
828 );
829
830 Ok(())
831 }
832
833 fn trie_cursor_seek(
841 &mut self,
842 key: Nibbles,
843 ) -> Result<Option<(Nibbles, BranchNodeCompact)>, StateProofError> {
844 let mut entry = self.trie_cursor.seek(key)?;
845 while let Some((ref path, ref branch)) = entry {
846 if !self.should_skip_cached_branch(path, branch) {
847 break
848 }
849 entry = self.trie_cursor.next()?;
850 }
851 Ok(entry)
852 }
853
854 fn should_skip_cached_branch(
857 &mut self,
858 cached_path: &Nibbles,
859 cached_branch: &BranchNodeCompact,
860 ) -> bool {
861 if !self.prefix_set.contains(cached_path) {
862 return false
863 }
864
865 let mut num_unmatched = 0u32;
866 let mut child_path = *cached_path;
867 for nibble in 0u8..16 {
868 if cached_branch.state_mask.is_bit_set(nibble) {
869 child_path.truncate(cached_path.len());
870 child_path.push_unchecked(nibble);
871 if !self.prefix_set.contains(&child_path) {
872 num_unmatched += 1;
873 }
874 }
875 }
876
877 if num_unmatched <= 1 {
878 trace!(
879 target: TRACE_TARGET,
880 ?cached_path,
881 ?num_unmatched,
882 "Skipping cached branch: all but <=1 children match prefix set, branch may collapse",
883 );
884 true
885 } else {
886 false
887 }
888 }
889
890 #[inline]
897 fn try_pop_cached_branch(
898 &mut self,
899 trie_cursor_state: &mut TrieCursorState,
900 traversal_upper_bound: Option<&Nibbles>,
901 uncalculated_lower_bound: &Option<Nibbles>,
902 ) -> Result<PopCachedBranchOutcome, StateProofError> {
903 let Some(uncalculated_lower_bound) = uncalculated_lower_bound else {
906 return Ok(PopCachedBranchOutcome::Exhausted)
907 };
908
909 if let Some(cached) = self.cached_branch_stack.pop() {
911 return Ok(PopCachedBranchOutcome::Popped(cached));
912 }
913
914 let Some(mut trie_cursor_path) = trie_cursor_state.path() else {
921 return Ok(PopCachedBranchOutcome::Exhausted)
922 };
923
924 if trie_cursor_path < uncalculated_lower_bound {
927 *trie_cursor_state = TrieCursorState::seeked(
928 *uncalculated_lower_bound,
929 self.trie_cursor_seek(*uncalculated_lower_bound)?,
930 );
931
932 if let Some(new_trie_cursor_path) = trie_cursor_state.path() {
935 trie_cursor_path = new_trie_cursor_path
936 } else {
937 return Ok(PopCachedBranchOutcome::Exhausted)
938 };
939 }
940
941 if traversal_upper_bound.is_some_and(|upper_bound| trie_cursor_path >= upper_bound) {
944 return Ok(PopCachedBranchOutcome::Exhausted)
945 }
946
947 let cached = trie_cursor_state.take();
954 trace!(target: TRACE_TARGET, cached=?cached, "Pushed next trie node onto cached_branch_stack");
955
956 let cached_path = &cached.0;
963 if uncalculated_lower_bound < cached_path && !cached_path.is_zeroes() {
964 let range = (*uncalculated_lower_bound, Some(*cached_path));
965 trace!(target: TRACE_TARGET, ?range, "Returning key range to calculate in order to catch up to cached branch");
966
967 self.cached_branch_stack.push(cached);
970
971 return Ok(PopCachedBranchOutcome::CalculateLeaves(range));
972 }
973
974 Ok(PopCachedBranchOutcome::Popped(cached))
975 }
976
977 fn commit_branches<'a>(
986 &mut self,
987 targets: &mut Option<TargetsCursor<'a>>,
988 next_path: &Nibbles,
989 uncalculated_lower_bound: Option<&Nibbles>,
990 ) -> Result<Option<(Nibbles, Option<Nibbles>)>, StateProofError> {
991 let dirty_range = |prefix_set: &mut PrefixSet, upper_bound: Option<Nibbles>| {
992 let uncalculated_lower_bound = uncalculated_lower_bound?;
993
994 if upper_bound.as_ref().is_some_and(|upper| uncalculated_lower_bound >= upper) {
995 return None
996 }
997
998 match upper_bound {
999 Some(upper_bound) => prefix_set
1000 .contains_range(uncalculated_lower_bound..&upper_bound)
1001 .then_some((*uncalculated_lower_bound, Some(upper_bound))),
1002 None => prefix_set
1003 .contains_from(uncalculated_lower_bound)
1004 .then_some((*uncalculated_lower_bound, None)),
1005 }
1006 };
1007
1008 let mut popped_child_path_upper = None;
1009 while !next_path.starts_with(&self.branch_path) {
1010 if uncalculated_lower_bound.is_some_and(|lower| lower.starts_with(&self.branch_path)) &&
1013 let Some(range) =
1014 dirty_range(&mut self.prefix_set, self.branch_path.next_without_prefix())
1015 {
1016 return Ok(Some(range))
1017 }
1018
1019 let branch = self.branch_stack.last().expect("branch_stack cannot be empty");
1020 popped_child_path_upper = Some(
1023 self.branch_path
1024 .slice_unchecked(0, self.branch_path.len() - branch.ext_len as usize)
1025 .next_without_prefix(),
1026 );
1027
1028 self.pop_branch(targets)?;
1029 }
1030
1031 if !self.branch_stack.is_empty() &&
1035 let Some(upper_bound) = popped_child_path_upper &&
1036 let Some(range) = dirty_range(&mut self.prefix_set, upper_bound)
1037 {
1038 return Ok(Some(range))
1039 }
1040
1041 Ok(None)
1042 }
1043
1044 #[instrument(target = TRACE_TARGET, level = "trace", skip_all)]
1063 fn next_uncached_key_range<'a>(
1064 &mut self,
1065 targets: &mut Option<TargetsCursor<'a>>,
1066 trie_cursor_state: &mut TrieCursorState,
1067 traversal_upper_bound: Option<&Nibbles>,
1068 mut uncalculated_lower_bound: Option<Nibbles>,
1069 ) -> Result<Option<(Nibbles, Option<Nibbles>)>, StateProofError> {
1070 loop {
1071 if let (Some(lower_bound), Some(upper_bound)) =
1072 (uncalculated_lower_bound.as_ref(), traversal_upper_bound) &&
1073 lower_bound >= upper_bound
1074 {
1075 return Ok(None)
1076 }
1077
1078 let (cached_path, cached_branch) = match self.try_pop_cached_branch(
1084 trie_cursor_state,
1085 traversal_upper_bound,
1086 &uncalculated_lower_bound,
1087 )? {
1088 PopCachedBranchOutcome::Popped(cached) => cached,
1089 PopCachedBranchOutcome::Exhausted => {
1090 trace!(target: TRACE_TARGET, ?uncalculated_lower_bound, "Exhausted cached trie nodes");
1094 if let Some(lower) = uncalculated_lower_bound {
1095 self.commit_branches(targets, &lower, None)?;
1096 return Ok(Some((lower, traversal_upper_bound.copied())));
1097 }
1098 return Ok(None)
1099 }
1100 PopCachedBranchOutcome::CalculateLeaves(range) => {
1101 self.commit_branches(targets, &range.0, None)?;
1102 return Ok(Some(range));
1103 }
1104 };
1105
1106 let uncalculated_lower_bound_ref = uncalculated_lower_bound
1107 .as_ref()
1108 .expect("try_pop_cached_branch would return Exhausted if this were None");
1109
1110 trace!(
1111 target: TRACE_TARGET,
1112 branch_path = ?self.branch_path,
1113 branch_state_mask = ?self.branch_stack.last().map(|b| b.state_mask),
1114 ?cached_path,
1115 cached_branch_state_mask = ?cached_branch.state_mask,
1116 cached_branch_hash_mask = ?cached_branch.hash_mask,
1117 "loop",
1118 );
1119
1120 if let Some(range) =
1121 self.commit_branches(targets, &cached_path, Some(uncalculated_lower_bound_ref))?
1122 {
1123 self.cached_branch_stack.push((cached_path, cached_branch));
1124 return Ok(Some(range))
1125 }
1126
1127 debug_assert!(
1130 self.branch_path.len() < cached_path.len() || self.branch_path == cached_path,
1131 "branch_path {:?} is different-or-longer-than cached_path {cached_path:?}",
1132 self.branch_path
1133 );
1134
1135 if self.branch_path != cached_path {
1139 if uncalculated_lower_bound_ref < &cached_path &&
1143 self.prefix_set.contains_range(uncalculated_lower_bound_ref..&cached_path)
1144 {
1145 self.cached_branch_stack.push((cached_path, cached_branch));
1146 return Ok(Some((*uncalculated_lower_bound_ref, Some(cached_path))))
1147 }
1148
1149 self.push_cached_branch(targets, cached_path, &cached_branch)?;
1150 }
1151
1152 let curr_branch =
1155 self.branch_stack.last().expect("top of branch_stack corresponds to cached branch");
1156
1157 let cached_state_mask = cached_branch.state_mask;
1158 let curr_state_mask = curr_branch.state_mask;
1159
1160 let mut next_child_nibbles = curr_state_mask ^ cached_state_mask;
1163
1164 if self.prefix_set.contains(&self.branch_path) {
1169 let branch_path_len = self.branch_path.len();
1170 let mut child_path = self.branch_path;
1171 for nibble in 0u8..16 {
1172 if !curr_state_mask.is_bit_set(nibble) {
1173 child_path.truncate(branch_path_len);
1174 child_path.push_unchecked(nibble);
1175 if self.prefix_set.contains(&child_path) {
1176 next_child_nibbles.set_bit(nibble);
1177 }
1178 }
1179 }
1180 }
1181
1182 let _orig_next_child_nibbles = next_child_nibbles;
1183
1184 if uncalculated_lower_bound_ref.starts_with(&self.branch_path) &&
1189 uncalculated_lower_bound_ref.len() > self.branch_path.len()
1190 {
1191 let lower_nibble =
1192 uncalculated_lower_bound_ref.get_unchecked(self.branch_path.len());
1193 let already_processed_mask = TrieMask::new((1u16 << lower_nibble) - 1);
1195 next_child_nibbles &= !already_processed_mask;
1196 trace!(
1197 target: TRACE_TARGET,
1198 branch_path = ?self.branch_path,
1199 ?_orig_next_child_nibbles,
1200 ?already_processed_mask,
1201 ?next_child_nibbles,
1202 "Unset already processed key nibbles from next_child_nibbles",
1203 );
1204 } else if !uncalculated_lower_bound_ref.starts_with(&self.branch_path) &&
1205 uncalculated_lower_bound_ref > &self.branch_path
1206 {
1207 next_child_nibbles = TrieMask::default();
1210 trace!(
1211 target: TRACE_TARGET,
1212 branch_path = ?self.branch_path,
1213 ?_orig_next_child_nibbles,
1214 ?next_child_nibbles,
1215 "Unset all nibbles from next_child_nibbles due to branch_path being outside this subtrie",
1216 );
1217 }
1218
1219 if next_child_nibbles.is_empty() {
1222 trace!(
1223 target: TRACE_TARGET,
1224 path=?cached_path,
1225 ?curr_branch,
1226 ?cached_branch,
1227 "No further children",
1228 );
1229
1230 uncalculated_lower_bound = cached_path.next_without_prefix();
1236
1237 continue
1238 }
1239
1240 let child_nibble = next_child_nibbles.trailing_zeros() as u8;
1243 let child_path = self.child_path_at(child_nibble);
1244
1245 if cached_branch.hash_mask.is_bit_set(child_nibble) &&
1255 !self.prefix_set.contains(&child_path)
1256 {
1257 self.commit_last_child(targets)?;
1264
1265 if !self.should_retain(targets, &child_path, false) {
1266 let lower_bits = TrieMask::new((1u16 << child_nibble) - 1);
1269 let hash_idx = (cached_branch.hash_mask & lower_bits).count_ones() as usize;
1270 let hash = cached_branch.hashes[hash_idx];
1271
1272 trace!(
1273 target: TRACE_TARGET,
1274 ?child_path,
1275 ?hash_idx,
1276 ?hash,
1277 "Using cached hash for child",
1278 );
1279
1280 self.child_stack.push(ProofTrieBranchChild::RlpNode(RlpNode::word_rlp(&hash)));
1281 self.branch_stack
1282 .last_mut()
1283 .expect("already asserted there is a last branch")
1284 .state_mask
1285 .set_bit(child_nibble);
1286
1287 uncalculated_lower_bound = child_path.next_without_prefix();
1290
1291 self.cached_branch_stack.push((cached_path, cached_branch));
1293
1294 continue
1295 }
1296 }
1297
1298 if trie_cursor_state.path().is_some_and(|path| path < &child_path) {
1305 trace!(target: TRACE_TARGET, ?child_path, "Seeking trie cursor to child path");
1306 *trie_cursor_state =
1307 TrieCursorState::seeked(child_path, self.trie_cursor_seek(child_path)?);
1308 }
1309
1310 if let TrieCursorState::Available(next_cached_path, next_cached_branch) =
1314 &trie_cursor_state &&
1315 next_cached_path.starts_with(&child_path)
1316 {
1317 self.cached_branch_stack.push((cached_path, cached_branch));
1319
1320 trace!(
1321 target: TRACE_TARGET,
1322 ?child_path,
1323 ?next_cached_path,
1324 ?next_cached_branch,
1325 "Pushing cached branch for child",
1326 );
1327 self.cached_branch_stack.push(trie_cursor_state.take());
1328 continue;
1329 }
1330
1331 let child_path_upper = child_path.next_without_prefix();
1335 trace!(
1336 target: TRACE_TARGET,
1337 lower=?child_path,
1338 upper=?child_path_upper,
1339 "Returning sub-trie's key range to calculate",
1340 );
1341
1342 self.cached_branch_stack.push((cached_path, cached_branch));
1344
1345 return Ok(Some((child_path, child_path_upper)));
1346 }
1347 }
1348
1349 #[instrument(
1353 target = TRACE_TARGET,
1354 level = "trace",
1355 skip_all,
1356 fields(
1357 parent_prefix=?sub_trie_targets.parent_prefix,
1358 lower_bound=?sub_trie_targets.lower_bound,
1359 upper_bound=?sub_trie_targets.upper_bound,
1360 ),
1361 )]
1362 fn proof_subtrie<'a>(
1363 &mut self,
1364 value_encoder: &mut VE,
1365 trie_cursor_state: &mut TrieCursorState,
1366 hashed_cursor_state: &mut HashedCursorState<VE::DeferredEncoder>,
1367 sub_trie_targets: SubTrieTargets<'a>,
1368 ) -> Result<(), StateProofError> {
1369 let traversal_lower_bound = sub_trie_targets.lower_bound;
1370 let traversal_upper_bound = sub_trie_targets.upper_bound;
1371
1372 let mut targets = if sub_trie_targets.targets.is_empty() {
1375 None
1376 } else {
1377 Some(TargetsCursor::new(sub_trie_targets.targets))
1378 };
1379
1380 debug_assert!(self.cached_branch_stack.is_empty());
1383 debug_assert!(self.branch_stack.is_empty());
1384 debug_assert!(self.branch_path.is_empty());
1385 debug_assert!(self.child_stack.is_empty());
1386
1387 if trie_cursor_state.needs_seek_to(&traversal_lower_bound) {
1392 trace!(target: TRACE_TARGET, "Doing initial seek of trie cursor");
1393 *trie_cursor_state = TrieCursorState::seeked(
1394 traversal_lower_bound,
1395 self.trie_cursor_seek(traversal_lower_bound)?,
1396 );
1397 }
1398
1399 let mut uncalculated_lower_bound = Some(traversal_lower_bound);
1404
1405 trace!(target: TRACE_TARGET, "Starting loop");
1406 loop {
1407 let prev_uncalculated_lower_bound = uncalculated_lower_bound;
1409
1410 let Some((calc_lower_bound, calc_upper_bound)) = self.next_uncached_key_range(
1412 &mut targets,
1413 trie_cursor_state,
1414 traversal_upper_bound.as_ref(),
1415 prev_uncalculated_lower_bound,
1416 )?
1417 else {
1418 break;
1421 };
1422
1423 if let Some(prev_lower) = prev_uncalculated_lower_bound.as_ref() &&
1430 calc_lower_bound < *prev_lower
1431 {
1432 let msg = format!(
1433 "next_uncached_key_range went backwards: calc_lower={calc_lower_bound:?} < \
1434 prev_lower={prev_lower:?}, calc_upper={calc_upper_bound:?}, \
1435 lower_bound={traversal_lower_bound:?}, \
1436 upper_bound={traversal_upper_bound:?}",
1437 );
1438 error!(target: TRACE_TARGET, "{msg}");
1439 return Err(StateProofError::TrieInconsistency(msg));
1440 }
1441
1442 self.calculate_key_range(
1444 value_encoder,
1445 &mut targets,
1446 hashed_cursor_state,
1447 calc_lower_bound,
1448 calc_upper_bound,
1449 )?;
1450
1451 if hashed_cursor_state.path().is_none_or(|key| {
1457 traversal_upper_bound.is_some_and(|upper_bound| key >= &upper_bound)
1458 }) {
1459 break;
1460 }
1461
1462 uncalculated_lower_bound = calc_upper_bound;
1465 }
1466
1467 trace!(target: TRACE_TARGET, "Exited loop, popping remaining branches");
1469 while !self.branch_stack.is_empty() {
1470 self.pop_branch(&mut targets)?;
1471 }
1472
1473 debug_assert!(self.branch_stack.is_empty());
1477 debug_assert!(self.branch_path.is_empty());
1478 debug_assert!(self.child_stack.len() < 2);
1479
1480 self.cached_branch_stack.clear();
1483
1484 trace!(
1488 target: TRACE_TARGET,
1489 parent_prefix = ?sub_trie_targets.parent_prefix,
1490 child_stack_empty = self.child_stack.is_empty(),
1491 "Maybe retaining local root",
1492 );
1493 let root_node = match self.child_stack.pop() {
1497 Some(ProofTrieBranchChild::RlpNode(_)) => {
1498 unreachable!("local root cannot be an encoded RLP node")
1499 }
1500 root_node => root_node,
1501 };
1502
1503 let Some(parent_prefix) = sub_trie_targets.parent_prefix else {
1506 let root_node = if let Some(root_node) = root_node {
1507 self.rlp_encode_buf.clear();
1508 root_node.into_proof_trie_node(Nibbles::new(), &mut self.rlp_encode_buf)?
1509 } else {
1510 ProofTrieNodeV2::empty()
1511 };
1512 self.retained_proofs.push(root_node);
1513 return Ok(())
1514 };
1515
1516 let Some(mut root_node) = root_node else { return Ok(()) };
1518
1519 let root_short_key = *root_node.short_key();
1520
1521 if root_short_key == parent_prefix {
1524 return Ok(())
1525 }
1526
1527 if !root_short_key.starts_with(&parent_prefix) {
1533 return Err(StateProofError::TrieInconsistency(format!(
1534 "local root short key {root_short_key:?} does not start with parent prefix \
1535 {parent_prefix:?}",
1536 )))
1537 }
1538
1539 let child_path_len = parent_prefix.len() + 1;
1542 let child_path = root_short_key.slice_unchecked(0, child_path_len);
1543
1544 if !sub_trie_targets
1546 .targets
1547 .iter()
1548 .any(|target| target.key_nibbles.starts_with(&child_path))
1549 {
1550 return Ok(())
1551 }
1552
1553 root_node.trim_short_key_prefix(child_path_len);
1555 self.rlp_encode_buf.clear();
1556 let root_node = root_node.into_proof_trie_node(child_path, &mut self.rlp_encode_buf)?;
1557 self.retained_proofs.push(root_node);
1558
1559 Ok(())
1560 }
1561
1562 fn clear_computation_state(&mut self) {
1565 self.branch_stack.clear();
1566 self.branch_path = Nibbles::new();
1567 self.child_stack.clear();
1568 self.cached_branch_stack.clear();
1569 self.retained_proofs.clear();
1570 }
1571
1572 fn proof_inner(
1575 &mut self,
1576 value_encoder: &mut VE,
1577 targets: &mut [ProofV2Target],
1578 ) -> Result<Vec<ProofTrieNodeV2>, StateProofError> {
1579 if targets.is_empty() {
1581 trace!(target: TRACE_TARGET, "Empty targets, returning");
1582 return Ok(Vec::new())
1583 }
1584
1585 let mut trie_cursor_state = TrieCursorState::unseeked();
1588 let mut hashed_cursor_state = HashedCursorState::unseeked();
1589 let mut previous_traversal_bounds: Option<(Nibbles, Option<Nibbles>)> = None;
1590
1591 for sub_trie_targets in iter_sub_trie_targets(targets) {
1594 let traversal_lower_bound = sub_trie_targets.lower_bound;
1595 let traversal_upper_bound = sub_trie_targets.upper_bound;
1596 if previous_traversal_bounds.is_some_and(|(_, previous_upper_bound)| {
1597 previous_upper_bound.is_none_or(|upper_bound| upper_bound > traversal_lower_bound)
1598 }) {
1599 if trie_cursor_state.needs_reset_before_seek(&traversal_lower_bound) {
1600 trace!(
1601 target: TRACE_TARGET,
1602 ?previous_traversal_bounds,
1603 ?traversal_lower_bound,
1604 ?traversal_upper_bound,
1605 "Resetting trie cursor before overlapping or backward traversal range",
1606 );
1607 self.trie_cursor.reset();
1608 trie_cursor_state = TrieCursorState::unseeked();
1609 }
1610 if hashed_cursor_state.needs_reset_before_seek(&traversal_lower_bound) {
1611 trace!(
1612 target: TRACE_TARGET,
1613 ?previous_traversal_bounds,
1614 ?traversal_lower_bound,
1615 ?traversal_upper_bound,
1616 "Resetting hashed cursor before overlapping or backward traversal range",
1617 );
1618 self.hashed_cursor.reset();
1619 hashed_cursor_state = HashedCursorState::unseeked();
1620 }
1621 }
1622
1623 if let Err(err) = self.proof_subtrie(
1624 value_encoder,
1625 &mut trie_cursor_state,
1626 &mut hashed_cursor_state,
1627 sub_trie_targets,
1628 ) {
1629 self.clear_computation_state();
1630 return Err(err);
1631 }
1632
1633 previous_traversal_bounds = Some((traversal_lower_bound, traversal_upper_bound));
1634 }
1635
1636 trace!(
1637 target: TRACE_TARGET,
1638 retained_proofs_len = ?self.retained_proofs.len(),
1639 "proof_inner: returning",
1640 );
1641 self.retained_proofs.sort_unstable_by(|a, b| depth_first::cmp(&a.path, &b.path));
1642 self.retained_proofs.dedup_by(|a, b| a.path == b.path);
1643 Ok(core::mem::take(&mut self.retained_proofs))
1644 }
1645
1646 #[instrument(target = TRACE_TARGET, level = "trace", skip_all)]
1655 pub fn proof(
1656 &mut self,
1657 value_encoder: &mut VE,
1658 targets: &mut [ProofV2Target],
1659 ) -> Result<Vec<ProofTrieNodeV2>, StateProofError> {
1660 self.trie_cursor.reset();
1661 self.hashed_cursor.reset();
1662 self.proof_inner(value_encoder, targets)
1663 }
1664
1665 pub fn compute_root_hash(
1672 &mut self,
1673 proof_nodes: &[ProofTrieNodeV2],
1674 ) -> Result<Option<B256>, StateProofError> {
1675 let root_node = proof_nodes.iter().find(|node| node.path.is_empty());
1677
1678 let Some(root) = root_node else {
1679 return Ok(None);
1680 };
1681
1682 self.rlp_encode_buf.clear();
1684 root.node.encode(&mut self.rlp_encode_buf);
1685 let root_hash = keccak256(&self.rlp_encode_buf);
1686
1687 Ok(Some(root_hash))
1688 }
1689
1690 #[instrument(target = TRACE_TARGET, level = "trace", skip(self, value_encoder))]
1695 pub fn root_node(
1696 &mut self,
1697 value_encoder: &mut VE,
1698 ) -> Result<ProofTrieNodeV2, StateProofError> {
1699 let mut trie_cursor_state = TrieCursorState::unseeked();
1702 let mut hashed_cursor_state = HashedCursorState::unseeked();
1703
1704 static EMPTY_TARGETS: [ProofV2Target; 0] = [];
1705 let sub_trie_targets = SubTrieTargets {
1706 lower_bound: Nibbles::new(),
1707 upper_bound: None,
1708 parent_prefix: None,
1709 targets: &EMPTY_TARGETS,
1710 };
1711
1712 if let Err(err) = self.proof_subtrie(
1713 value_encoder,
1714 &mut trie_cursor_state,
1715 &mut hashed_cursor_state,
1716 sub_trie_targets,
1717 ) {
1718 self.clear_computation_state();
1719 return Err(err);
1720 }
1721
1722 let mut proofs = core::mem::take(&mut self.retained_proofs);
1725 trace!(
1726 target: TRACE_TARGET,
1727 proofs_len = ?proofs.len(),
1728 "root_node: extracting root",
1729 );
1730
1731 debug_assert_eq!(
1734 proofs.len(), 1,
1735 "prefix is empty, parent path is None, and targets is empty, so there must be only the root node"
1736 );
1737
1738 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");
1740
1741 Ok(root_node)
1742 }
1743}
1744
1745pub type StorageProofCalculator<TC, HC> = ProofCalculator<TC, HC, StorageValueEncoder>;
1747
1748impl<TC, HC> StorageProofCalculator<TC, HC>
1749where
1750 TC: TrieStorageCursor,
1751 HC: HashedStorageCursor<Value = U256>,
1752{
1753 pub fn new_storage(trie_cursor: TC, hashed_cursor: HC) -> Self {
1755 Self::new(trie_cursor, hashed_cursor)
1756 }
1757
1758 #[instrument(target = TRACE_TARGET, level = "trace", skip(self, targets))]
1767 pub fn storage_proof(
1768 &mut self,
1769 hashed_address: B256,
1770 targets: &mut [ProofV2Target],
1771 ) -> Result<Vec<ProofTrieNodeV2>, StateProofError> {
1772 self.hashed_cursor.set_hashed_address(hashed_address);
1773
1774 if self.hashed_cursor.is_storage_empty()? {
1776 return Ok(if targets.iter().any(|target| !target.parent.is_known()) {
1777 vec![ProofTrieNodeV2 {
1778 path: Nibbles::default(),
1779 node: TrieNodeV2::EmptyRoot,
1780 masks: None,
1781 }]
1782 } else {
1783 Vec::new()
1784 })
1785 }
1786
1787 self.trie_cursor.set_hashed_address(hashed_address);
1790
1791 let mut storage_value_encoder = StorageValueEncoder;
1793 self.proof_inner(&mut storage_value_encoder, targets)
1794 }
1795
1796 #[instrument(target = TRACE_TARGET, level = "trace", skip(self))]
1801 pub fn storage_root_node(
1802 &mut self,
1803 hashed_address: B256,
1804 ) -> Result<ProofTrieNodeV2, StateProofError> {
1805 self.hashed_cursor.set_hashed_address(hashed_address);
1806
1807 if self.hashed_cursor.is_storage_empty()? {
1808 return Ok(ProofTrieNodeV2 {
1809 path: Nibbles::default(),
1810 node: TrieNodeV2::EmptyRoot,
1811 masks: None,
1812 })
1813 }
1814
1815 self.trie_cursor.set_hashed_address(hashed_address);
1818
1819 let mut storage_value_encoder = StorageValueEncoder;
1821 self.root_node(&mut storage_value_encoder)
1822 }
1823}
1824
1825struct TargetsCursor<'a> {
1831 targets: &'a [ProofV2Target],
1832 i: usize,
1833}
1834
1835impl<'a> TargetsCursor<'a> {
1836 fn new(targets: &'a [ProofV2Target]) -> Self {
1842 debug_assert!(!targets.is_empty());
1843 Self { targets, i: 0 }
1844 }
1845
1846 fn current(&self) -> (&'a ProofV2Target, Option<&'a ProofV2Target>) {
1848 (&self.targets[self.i], self.targets.get(self.i + 1))
1849 }
1850
1851 fn next(&mut self) -> (&'a ProofV2Target, Option<&'a ProofV2Target>) {
1857 self.i += 1;
1858 debug_assert!(self.i < self.targets.len());
1859 self.current()
1860 }
1861
1862 fn skip_iter(&self) -> impl Iterator<Item = &'a ProofV2Target> {
1864 self.targets[self.i + 1..].iter()
1865 }
1866
1867 fn rev_iter(&self) -> impl Iterator<Item = &'a ProofV2Target> {
1870 self.targets[..self.i].iter().rev()
1871 }
1872}
1873
1874#[derive(Debug)]
1877enum TrieCursorState {
1878 Unseeked,
1880 Available(Nibbles, BranchNodeCompact),
1882 Taken(Nibbles),
1884 Exhausted(Nibbles),
1886}
1887
1888impl TrieCursorState {
1889 const fn unseeked() -> Self {
1891 Self::Unseeked
1892 }
1893
1894 fn seeked(key: Nibbles, entry: Option<(Nibbles, BranchNodeCompact)>) -> Self {
1896 entry.map_or(Self::Exhausted(key), |(path, node)| Self::Available(path, node))
1897 }
1898
1899 const fn path(&self) -> Option<&Nibbles> {
1905 match self {
1906 Self::Unseeked => panic!("cursor is unseeked"),
1907 Self::Available(path, _) | Self::Taken(path) => Some(path),
1908 Self::Exhausted(_) => None,
1909 }
1910 }
1911
1912 fn needs_seek_to(&self, path: &Nibbles) -> bool {
1914 match self {
1915 Self::Unseeked | Self::Taken(_) => true,
1916 Self::Available(current_path, _) => current_path < path,
1917 Self::Exhausted(_) => false,
1918 }
1919 }
1920
1921 fn needs_reset_before_seek(&self, key: &Nibbles) -> bool {
1923 match self {
1924 Self::Unseeked => false,
1925 Self::Available(path, _) | Self::Taken(path) => path > key,
1926 Self::Exhausted(exhausted_at) => exhausted_at > key,
1927 }
1928 }
1929
1930 fn take(&mut self) -> (Nibbles, BranchNodeCompact) {
1932 let Self::Available(path, _) = self else {
1933 panic!("take called on non-Available: {self:?}")
1934 };
1935
1936 let path = *path;
1937 let Self::Available(path, node) = core::mem::replace(self, Self::Taken(path)) else {
1938 unreachable!("already checked that self is Self::Available");
1939 };
1940
1941 (path, node)
1942 }
1943}
1944
1945enum HashedCursorState<V> {
1947 Unseeked,
1949 Available(Nibbles, V),
1951 Exhausted(Nibbles),
1953}
1954
1955impl<V> HashedCursorState<V> {
1956 const fn unseeked() -> Self {
1958 Self::Unseeked
1959 }
1960
1961 fn seeked(key: Nibbles, entry: Option<(Nibbles, V)>) -> Self {
1963 entry.map_or(Self::Exhausted(key), |(path, value)| Self::Available(path, value))
1964 }
1965
1966 const fn path(&self) -> Option<&Nibbles> {
1968 match self {
1969 Self::Available(path, _) => Some(path),
1970 Self::Unseeked | Self::Exhausted(_) => None,
1971 }
1972 }
1973
1974 fn needs_seek_to(&self, key: &Nibbles) -> bool {
1976 match self {
1977 Self::Unseeked => true,
1978 Self::Available(path, _) => path < key,
1979 Self::Exhausted(exhausted_at) => exhausted_at > key,
1980 }
1981 }
1982
1983 fn needs_reset_before_seek(&self, key: &Nibbles) -> bool {
1985 match self {
1986 Self::Unseeked => false,
1987 Self::Available(path, _) => path > key,
1988 Self::Exhausted(exhausted_at) => exhausted_at > key,
1989 }
1990 }
1991
1992 fn take(&mut self) -> (Nibbles, V) {
1994 match core::mem::replace(self, Self::Unseeked) {
1995 Self::Available(path, value) => (path, value),
1996 _ => panic!("take called on non-Available hashed cursor state"),
1997 }
1998 }
1999}
2000
2001enum PopCachedBranchOutcome {
2003 Popped((Nibbles, BranchNodeCompact)),
2005 Exhausted,
2007 CalculateLeaves((Nibbles, Option<Nibbles>)),
2010}
2011
2012#[cfg(test)]
2013mod tests {
2014 use super::*;
2015 use crate::{
2016 hashed_cursor::{mock::MockHashedCursorFactory, HashedCursorFactory},
2017 proof::StorageProof as LegacyStorageProof,
2018 test_utils::TrieTestHarness,
2019 trie_cursor::{depth_first, TrieCursorFactory},
2020 };
2021 use alloy_primitives::map::B256Set;
2022 use alloy_rlp::Decodable;
2023 use alloy_trie::proof::AddedRemovedKeys;
2024 use itertools::Itertools;
2025 use reth_trie_common::{
2026 prefix_set::PrefixSetMut, ProofTrieNode, ProofV2TargetParent, TrieNode, EMPTY_ROOT_HASH,
2027 };
2028 use std::collections::BTreeMap;
2029
2030 fn convert_legacy_proofs_to_v2(legacy_proofs: &[ProofTrieNode]) -> Vec<ProofTrieNodeV2> {
2043 ProofTrieNodeV2::from_sorted_trie_nodes(
2044 legacy_proofs.iter().map(|p| (p.path, p.node.clone(), p.masks)),
2045 )
2046 }
2047
2048 fn project_legacy_proof_node(
2050 node: &ProofTrieNodeV2,
2051 target: &ProofV2Target,
2052 ) -> Option<ProofTrieNodeV2> {
2053 let Some(parent_path_len) = target.parent.path_len() else {
2054 return target.key_nibbles.starts_with(&node.path).then(|| node.clone())
2055 };
2056
2057 if node.path.len() > parent_path_len {
2058 return target.key_nibbles.starts_with(&node.path).then(|| node.clone())
2059 }
2060
2061 let logical_path = match &node.node {
2062 TrieNodeV2::Leaf(leaf) => node.path.join(&leaf.key),
2063 TrieNodeV2::Branch(branch) => node.path.join(&branch.key),
2064 TrieNodeV2::EmptyRoot | TrieNodeV2::Extension(_) => return None,
2065 };
2066 let child_path_len = parent_path_len + 1;
2067 if logical_path.len() < child_path_len {
2068 return None
2069 }
2070
2071 let child_path = logical_path.slice(0..child_path_len);
2072 if !target.key_nibbles.starts_with(&child_path) {
2073 return None
2074 }
2075
2076 let trim_len = child_path_len - node.path.len();
2077 let mut projected = node.clone();
2078 projected.path = child_path;
2079 match &mut projected.node {
2080 TrieNodeV2::Leaf(leaf) => leaf.key = leaf.key.slice(trim_len..),
2081 TrieNodeV2::Branch(branch) => {
2082 branch.key = branch.key.slice(trim_len..);
2083 if branch.key.is_empty() {
2084 branch.branch_rlp_node = None;
2085 }
2086 }
2087 TrieNodeV2::EmptyRoot | TrieNodeV2::Extension(_) => unreachable!(),
2088 }
2089 Some(projected)
2090 }
2091
2092 fn project_legacy_proof(
2094 legacy_nodes: &[ProofTrieNodeV2],
2095 targets: &[ProofV2Target],
2096 ) -> Vec<ProofTrieNodeV2> {
2097 let mut projected = targets
2098 .iter()
2099 .flat_map(|target| {
2100 legacy_nodes.iter().filter_map(move |node| project_legacy_proof_node(node, target))
2101 })
2102 .collect::<Vec<_>>();
2103 projected.sort_unstable_by(|a, b| depth_first::cmp(&a.path, &b.path));
2104 projected.dedup_by(|a, b| {
2105 if a.path != b.path {
2106 return false
2107 }
2108 assert_eq!(a, b, "target projections disagree at path {:?}", a.path);
2109 true
2110 });
2111 projected
2112 }
2113
2114 struct ProofTestHarness {
2120 inner: TrieTestHarness,
2121 }
2122
2123 impl std::ops::Deref for ProofTestHarness {
2124 type Target = TrieTestHarness;
2125 fn deref(&self) -> &Self::Target {
2126 &self.inner
2127 }
2128 }
2129
2130 impl ProofTestHarness {
2131 fn new(storage: BTreeMap<B256, U256>) -> Self {
2133 Self { inner: TrieTestHarness::new(storage) }
2134 }
2135
2136 fn root_with_prefix_set(&self, prefix_set: PrefixSet) -> Option<B256> {
2138 let trie_cursor =
2139 self.trie_cursor_factory().storage_trie_cursor(self.hashed_address()).unwrap();
2140 let hashed_cursor =
2141 self.hashed_cursor_factory().hashed_storage_cursor(self.hashed_address()).unwrap();
2142 let mut calculator = StorageProofCalculator::new_storage(trie_cursor, hashed_cursor)
2143 .with_prefix_set(prefix_set);
2144
2145 let mut targets = [ProofV2Target::new(B256::ZERO)];
2146 let proof = calculator.storage_proof(self.hashed_address(), &mut targets).unwrap();
2147 calculator.compute_root_hash(&proof).unwrap()
2148 }
2149
2150 fn assert_proof(
2153 &self,
2154 targets: impl IntoIterator<Item = ProofV2Target>,
2155 ) -> Result<(), StateProofError> {
2156 let mut targets_vec = targets.into_iter().collect::<Vec<_>>();
2157
2158 let (proof_v2_result, root_hash) = self.proof_v2(&mut targets_vec);
2160
2161 if let Some(root_hash) = root_hash {
2164 pretty_assertions::assert_eq!(self.original_root(), root_hash);
2165 }
2166
2167 let legacy_targets = targets_vec
2170 .iter()
2171 .map(|target| B256::from_slice(&target.key_nibbles.pack()))
2172 .chain(self.storage().keys().copied())
2173 .collect::<B256Set>();
2174
2175 let proof_legacy_result = LegacyStorageProof::new_hashed(
2177 self.trie_cursor_factory(),
2178 self.hashed_cursor_factory(),
2179 self.hashed_address(),
2180 )
2181 .with_branch_node_masks(true)
2182 .with_added_removed_keys(Some(AddedRemovedKeys::default().with_assume_added(true)))
2183 .storage_multiproof(legacy_targets)?;
2184
2185 let proof_legacy_nodes = proof_legacy_result
2187 .subtree
2188 .iter()
2189 .map(|(path, node_enc)| {
2190 let mut buf = node_enc.as_ref();
2191 let node = TrieNode::decode(&mut buf)
2192 .expect("legacy implementation should not produce malformed proof nodes");
2193
2194 let masks = if path.is_empty() {
2195 None
2196 } else {
2197 proof_legacy_result.branch_node_masks.get(path).copied()
2198 };
2199
2200 ProofTrieNode { path: *path, node, masks }
2201 })
2202 .sorted_by(|a, b| depth_first::cmp(&a.path, &b.path))
2203 .collect::<Vec<_>>();
2204
2205 let all_legacy_nodes_v2 = convert_legacy_proofs_to_v2(&proof_legacy_nodes);
2207
2208 let expected_v2 = project_legacy_proof(&all_legacy_nodes_v2, &targets_vec);
2209 pretty_assertions::assert_eq!(expected_v2, proof_v2_result);
2210
2211 Ok(())
2212 }
2213 }
2214
2215 #[test]
2220 fn test_proof_calculator_reuse_after_error() {
2221 reth_tracing::init_test_tracing();
2222
2223 let slots = [
2224 B256::right_padding_from(&[0x10]),
2225 B256::right_padding_from(&[0x20]),
2226 B256::right_padding_from(&[0x30]),
2227 B256::right_padding_from(&[0x40]),
2228 ];
2229 let storage: BTreeMap<B256, U256> =
2230 slots.iter().map(|&s| (s, U256::from(100u64))).collect();
2231
2232 let harness = ProofTestHarness::new(storage);
2233
2234 let trie_cursor_factory = harness.trie_cursor_factory();
2235 let hashed_cursor_factory = harness.hashed_cursor_factory();
2236
2237 let hashed_address = harness.hashed_address();
2238 let trie_cursor = trie_cursor_factory.storage_trie_cursor(hashed_address).unwrap();
2239 let hashed_cursor = hashed_cursor_factory.hashed_storage_cursor(hashed_address).unwrap();
2240 let mut proof_calculator = StorageProofCalculator::new_storage(trie_cursor, hashed_cursor);
2241
2242 proof_calculator.branch_stack.push(ProofTrieBranch {
2245 ext_len: 2,
2246 state_mask: TrieMask::new(0b1111),
2247 masks: None,
2248 });
2249 proof_calculator.branch_stack.push(ProofTrieBranch {
2250 ext_len: 0,
2251 state_mask: TrieMask::new(0b11),
2252 masks: None,
2253 });
2254 proof_calculator
2255 .child_stack
2256 .push(ProofTrieBranchChild::RlpNode(RlpNode::word_rlp(&B256::ZERO)));
2257 proof_calculator.branch_path = Nibbles::from_nibbles([0x1, 0x2, 0x3]);
2258
2259 proof_calculator.clear_computation_state();
2261
2262 let mut sorted_slots = slots.to_vec();
2263 sorted_slots.sort();
2264 let mut targets: Vec<ProofV2Target> =
2265 sorted_slots.iter().copied().map(ProofV2Target::new).collect();
2266
2267 let result = proof_calculator.storage_proof(hashed_address, &mut targets).unwrap();
2268
2269 let trie_cursor = trie_cursor_factory.storage_trie_cursor(hashed_address).unwrap();
2271 let hashed_cursor = hashed_cursor_factory.hashed_storage_cursor(hashed_address).unwrap();
2272 let mut fresh_calculator = StorageProofCalculator::new_storage(trie_cursor, hashed_cursor);
2273 let fresh_result = fresh_calculator.storage_proof(hashed_address, &mut targets).unwrap();
2274
2275 pretty_assertions::assert_eq!(fresh_result, result);
2276 }
2277
2278 #[test]
2279 fn test_partial_storage_proof_after_root_calculation() {
2280 let slot_a = B256::right_padding_from(&[0xae, 0xd4, 0x00]);
2281 let slot_b = B256::right_padding_from(&[0xae, 0xd4, 0x10]);
2282 let harness = ProofTestHarness::new(BTreeMap::from([
2283 (slot_a, U256::from(1)),
2284 (slot_b, U256::from(2)),
2285 ]));
2286 let hashed_address = harness.hashed_address();
2287 let trie_cursor =
2288 harness.trie_cursor_factory().storage_trie_cursor(hashed_address).unwrap();
2289 let hashed_cursor =
2290 harness.hashed_cursor_factory().hashed_storage_cursor(hashed_address).unwrap();
2291 let mut calculator = StorageProofCalculator::new_storage(trie_cursor, hashed_cursor);
2292
2293 let root_node = calculator.storage_root_node(hashed_address).unwrap();
2294 assert_eq!(
2295 calculator.compute_root_hash(core::slice::from_ref(&root_node)).unwrap(),
2296 Some(harness.original_root())
2297 );
2298
2299 let target = ProofV2Target::new(slot_a).with_parent(ProofV2TargetParent::new(3));
2300 let mut actual_targets = [target];
2301 let actual = calculator.storage_proof(hashed_address, &mut actual_targets).unwrap();
2302 let mut expected_targets = [target];
2303 let (expected, root) = harness.proof_v2(&mut expected_targets);
2304
2305 assert!(root.is_none());
2306 pretty_assertions::assert_eq!(expected, actual);
2307 }
2308
2309 mod proptest_tests {
2310 use super::*;
2311 use proptest::prelude::*;
2312
2313 fn storage_strategy() -> impl Strategy<Value = BTreeMap<B256, U256>> {
2315 prop::collection::vec((any::<[u8; 32]>(), any::<u64>()), 0..=100).prop_map(|slots| {
2316 slots
2317 .into_iter()
2318 .map(|(slot_bytes, value)| (B256::from(slot_bytes), U256::from(value)))
2319 .filter(|(_, v)| *v != U256::ZERO)
2320 .collect()
2321 })
2322 }
2323
2324 fn proof_targets_strategy(
2327 slot_keys: Vec<B256>,
2328 ) -> impl Strategy<Value = Vec<ProofV2Target>> {
2329 let num_slots = slot_keys.len();
2330
2331 let target_count = 0..=(num_slots + 5);
2332
2333 target_count.prop_flat_map(move |count| {
2334 let slot_keys = slot_keys.clone();
2335 prop::collection::vec(
2336 (
2337 prop::bool::weighted(0.8).prop_flat_map(move |from_slots| {
2338 if from_slots && !slot_keys.is_empty() {
2339 prop::sample::select(slot_keys.clone()).boxed()
2340 } else {
2341 any::<[u8; 32]>().prop_map(B256::from).boxed()
2342 }
2343 }),
2344 0u8..16u8,
2345 )
2346 .prop_map(|(key, encoded_parent_path_len)| {
2347 let parent = encoded_parent_path_len.checked_sub(1).map_or(
2348 ProofV2TargetParent::NONE,
2349 |parent_path_len| {
2350 ProofV2TargetParent::new(usize::from(parent_path_len))
2351 },
2352 );
2353 ProofV2Target::new(key).with_parent(parent)
2354 }),
2355 count,
2356 )
2357 })
2358 }
2359
2360 proptest! {
2361 #![proptest_config(ProptestConfig::with_cases(4000))]
2362 #[test]
2363 fn proptest_proof_with_targets(
2366 (storage, targets) in storage_strategy()
2367 .prop_flat_map(|storage| {
2368 let mut slot_keys: Vec<B256> = storage.keys().copied().collect();
2369 slot_keys.sort_unstable();
2370 let targets_strategy = proof_targets_strategy(slot_keys);
2371 (Just(storage), targets_strategy)
2372 })
2373 ) {
2374 reth_tracing::init_test_tracing();
2375 let harness = ProofTestHarness::new(storage);
2376
2377 harness.assert_proof(targets).expect("Proof generation failed");
2378 }
2379 }
2380 }
2381
2382 #[test]
2383 fn test_exact_subtrie_targets_with_root_target() {
2384 reth_tracing::init_test_tracing();
2385
2386 let slot_80 = B256::right_padding_from(&[0x80]);
2387 let slot_82 = B256::right_padding_from(&[0x82]);
2388 let slot_f0 = B256::right_padding_from(&[0xf0]);
2389 let storage = BTreeMap::from([
2390 (slot_80, U256::from(1)),
2391 (slot_82, U256::from(2)),
2392 (slot_f0, U256::from(3)),
2393 ]);
2394 let targets = [
2395 ProofV2Target::new(B256::ZERO),
2396 ProofV2Target::new(slot_80).with_parent(ProofV2TargetParent::new(1)),
2397 ];
2398
2399 let harness = ProofTestHarness::new(storage);
2400 harness.assert_proof(targets).expect("Proof generation failed");
2401 }
2402
2403 #[test]
2404 fn test_rebases_singleton_subtrie_root_below_known_parent() {
2405 let slot = B256::right_padding_from(&[0xae, 0xd4, 0x09]);
2406 let slot_nibbles = Nibbles::unpack(slot);
2407 let harness = ProofTestHarness::new(BTreeMap::from([(slot, U256::from(1))]));
2408 let mut targets = [ProofV2Target::new(slot).with_parent(ProofV2TargetParent::new(3))];
2409
2410 let (proof, root) = harness.proof_v2(&mut targets);
2411
2412 assert!(root.is_none());
2413 assert_eq!(proof.len(), 1);
2414 assert_eq!(proof[0].path, slot_nibbles.slice(0..4));
2415 let TrieNodeV2::Leaf(leaf) = &proof[0].node else {
2416 panic!("singleton subtrie root should remain a leaf")
2417 };
2418 assert_eq!(leaf.key, slot_nibbles.slice(4..));
2419 }
2420
2421 #[test]
2422 fn test_rebases_singleton_leaf_at_max_parent_depth() {
2423 let slot = B256::repeat_byte(0xae);
2424 let slot_nibbles = Nibbles::unpack(slot);
2425 let harness = ProofTestHarness::new(BTreeMap::from([(slot, U256::from(1))]));
2426 let mut targets = [ProofV2Target::new(slot).with_parent(ProofV2TargetParent::new(63))];
2427
2428 let (proof, root) = harness.proof_v2(&mut targets);
2429
2430 assert!(root.is_none());
2431 assert_eq!(proof.len(), 1);
2432 assert_eq!(proof[0].path, slot_nibbles);
2433 let TrieNodeV2::Leaf(leaf) = &proof[0].node else {
2434 panic!("singleton subtrie root should remain a leaf")
2435 };
2436 assert!(leaf.key.is_empty());
2437 }
2438
2439 #[test]
2440 fn test_root_and_root_parent_targets_retain_both_singleton_representations() {
2441 let slot = B256::right_padding_from(&[0x20]);
2442 let slot_nibbles = Nibbles::unpack(slot);
2443 let harness = ProofTestHarness::new(BTreeMap::from([(slot, U256::from(1))]));
2444 let mut targets = [
2445 ProofV2Target::new(slot),
2446 ProofV2Target::new(slot).with_parent(ProofV2TargetParent::new(0)),
2447 ];
2448
2449 let (proof, root) = harness.proof_v2(&mut targets);
2450
2451 assert_eq!(root, Some(harness.original_root()));
2452 let root_node = proof.iter().find(|node| node.path.is_empty()).expect("root proof");
2453 let TrieNodeV2::Leaf(root_leaf) = &root_node.node else { panic!("root should be a leaf") };
2454 assert_eq!(root_leaf.key, slot_nibbles);
2455
2456 let child_path = slot_nibbles.slice(0..1);
2457 let child_node =
2458 proof.iter().find(|node| node.path == child_path).expect("rebased root child proof");
2459 let TrieNodeV2::Leaf(child_leaf) = &child_node.node else {
2460 panic!("root child should be a leaf")
2461 };
2462 assert_eq!(child_leaf.key, slot_nibbles.slice(1..));
2463 }
2464
2465 #[test]
2466 fn test_rebases_compressed_branch_subtrie_root() {
2467 let slot_a = B256::right_padding_from(&[0xae, 0xd4, 0x00]);
2468 let slot_b = B256::right_padding_from(&[0xae, 0xd4, 0x10]);
2469 let slot_nibbles = Nibbles::unpack(slot_a);
2470 let harness = ProofTestHarness::new(BTreeMap::from([
2471 (slot_a, U256::from(1)),
2472 (slot_b, U256::from(2)),
2473 ]));
2474 let mut targets = [ProofV2Target::new(slot_a).with_parent(ProofV2TargetParent::new(3))];
2475
2476 let (proof, root) = harness.proof_v2(&mut targets);
2477
2478 assert!(root.is_none());
2479 let branch_path = slot_nibbles.slice(0..4);
2480 let branch_node =
2481 proof.iter().find(|node| node.path == branch_path).expect("rebased compressed branch");
2482 let TrieNodeV2::Branch(branch) = &branch_node.node else {
2483 panic!("rebased node should be a branch")
2484 };
2485 assert!(branch.key.is_empty());
2486 assert!(branch.branch_rlp_node.is_none());
2487 }
2488
2489 #[test]
2490 fn test_discards_reconstructed_known_parent_branch() {
2491 let slot_a = B256::right_padding_from(&[0xae, 0xd2]);
2492 let slot_b = B256::right_padding_from(&[0xae, 0xd4]);
2493 let slot_nibbles = Nibbles::unpack(slot_a);
2494 let harness = ProofTestHarness::new(BTreeMap::from([
2495 (slot_a, U256::from(1)),
2496 (slot_b, U256::from(2)),
2497 ]));
2498 let mut targets = [ProofV2Target::new(slot_a).with_parent(ProofV2TargetParent::new(3))];
2499
2500 let (proof, root) = harness.proof_v2(&mut targets);
2501
2502 assert!(root.is_none());
2503 assert!(!proof.iter().any(|node| node.path == slot_nibbles.slice(0..3)));
2504 assert!(proof.iter().any(|node| node.path == slot_nibbles.slice(0..4)));
2505 }
2506
2507 #[test]
2508 fn test_rebased_root_matches_direct_child_not_full_short_key() {
2509 let stored_slot = B256::right_padding_from(&[0xae, 0xd4, 0x09]);
2510 let same_child_target = B256::right_padding_from(&[0xae, 0xd4, 0xff]);
2511 let other_child_target = B256::right_padding_from(&[0xae, 0xd5]);
2512 let harness = ProofTestHarness::new(BTreeMap::from([(stored_slot, U256::from(1))]));
2513
2514 let mut same_child =
2515 [ProofV2Target::new(same_child_target).with_parent(ProofV2TargetParent::new(3))];
2516 let (proof, _) = harness.proof_v2(&mut same_child);
2517 assert_eq!(proof.len(), 1, "divergent leaf proves absence below the same child");
2518
2519 let mut other_child =
2520 [ProofV2Target::new(other_child_target).with_parent(ProofV2TargetParent::new(3))];
2521 let (proof, _) = harness.proof_v2(&mut other_child);
2522 assert!(proof.is_empty(), "a different direct child is unrelated to the target");
2523 }
2524
2525 #[test]
2526 fn test_known_parent_sibling_span_retains_only_target_children() {
2527 let stored_slot_a = B256::right_padding_from(&[0xea, 0x53]);
2528 let stored_slot_b = B256::right_padding_from(&[0xeb, 0x53]);
2529 let stored_slot_c = B256::right_padding_from(&[0xec, 0x53]);
2530 let target_a = B256::right_padding_from(&[0xea, 0x1f]);
2531 let target_c = B256::right_padding_from(&[0xec, 0x1f]);
2532 let harness = ProofTestHarness::new(BTreeMap::from([
2533 (stored_slot_a, U256::from(1)),
2534 (stored_slot_b, U256::from(2)),
2535 (stored_slot_c, U256::from(3)),
2536 ]));
2537 let mut targets = [target_a, target_c]
2538 .map(|target| ProofV2Target::new(target).with_parent(ProofV2TargetParent::new(1)));
2539
2540 let (proof, root) = harness.proof_v2(&mut targets);
2541
2542 assert!(root.is_none());
2543 assert_eq!(
2544 proof.iter().map(|node| node.path).collect::<Vec<_>>(),
2545 [Nibbles::from_nibbles([0xe, 0xa]), Nibbles::from_nibbles([0xe, 0xc])]
2546 );
2547 }
2548
2549 #[test]
2550 fn test_known_parent_does_not_use_stale_parent_mask() {
2551 let stored_slot_a = B256::right_padding_from(&[0xea, 0x53]);
2552 let stored_slot = B256::right_padding_from(&[0xeb, 0x53]);
2553 let stored_slot_c = B256::right_padding_from(&[0xec, 0x53]);
2554 let target = B256::right_padding_from(&[0xeb, 0x1f]);
2555 let stored_slot_nibbles = Nibbles::unpack(stored_slot);
2556
2557 let stale_parent_mask = TrieMask::new((1 << 0xa) | (1 << 0xc));
2561 let stale_parent = BranchNodeCompact::new(
2562 stale_parent_mask,
2563 TrieMask::new(0),
2564 TrieMask::new(0),
2565 Vec::new(),
2566 None,
2567 );
2568 let storage_nodes = BTreeMap::from([(Nibbles::from_nibbles([0xe]), stale_parent)]);
2569
2570 let mut harness = TrieTestHarness::new(BTreeMap::from([
2571 (stored_slot_a, U256::from(1)),
2572 (stored_slot, U256::from(2)),
2573 (stored_slot_c, U256::from(3)),
2574 ]));
2575 harness.set_trie_nodes(storage_nodes);
2576
2577 let mut targets = [ProofV2Target::new(target).with_parent(ProofV2TargetParent::new(1))];
2578 let (proof, root) = harness.proof_v2(&mut targets);
2579
2580 assert!(root.is_none());
2581 assert_eq!(proof.len(), 1);
2582 assert_eq!(proof[0].path, stored_slot_nibbles.slice(0..2));
2583 let TrieNodeV2::Leaf(leaf) = &proof[0].node else {
2584 panic!("live direct child should be reconstructed as a leaf")
2585 };
2586 assert_eq!(leaf.key, stored_slot_nibbles.slice(2..));
2587 }
2588
2589 #[test]
2590 fn test_empty_storage_respects_parent_context() {
2591 let harness = ProofTestHarness::new(BTreeMap::new());
2592 let slot = B256::ZERO;
2593
2594 let mut partial_target =
2595 [ProofV2Target::new(slot).with_parent(ProofV2TargetParent::new(0))];
2596 let (partial_proof, partial_root) = harness.proof_v2(&mut partial_target);
2597 assert!(partial_proof.is_empty());
2598 assert!(partial_root.is_none());
2599
2600 let mut root_target = [ProofV2Target::new(slot)];
2601 let (root_proof, root) = harness.proof_v2(&mut root_target);
2602 assert_eq!(root_proof.len(), 1);
2603 assert!(matches!(root_proof[0].node, TrieNodeV2::EmptyRoot));
2604 assert_eq!(root, Some(EMPTY_ROOT_HASH));
2605 }
2606
2607 #[test]
2608 fn test_big_trie() {
2609 use rand::prelude::*;
2610
2611 reth_tracing::init_test_tracing();
2612 let mut rng = rand::rngs::SmallRng::seed_from_u64(1);
2613
2614 let mut rand_b256 = || {
2615 let mut buf: [u8; 32] = [0; 32];
2616 rng.fill_bytes(&mut buf);
2617 B256::from_slice(&buf)
2618 };
2619
2620 let mut storage = BTreeMap::new();
2622 for _ in 0..10240 {
2623 let hashed_slot = rand_b256();
2624 storage.insert(hashed_slot, U256::from(1u64));
2625 }
2626
2627 let mut targets = storage.keys().copied().collect::<Vec<_>>();
2630 for _ in 0..storage.len() / 5 {
2631 targets.push(rand_b256());
2632 }
2633 targets.sort();
2634
2635 let harness = ProofTestHarness::new(storage);
2637
2638 harness
2639 .assert_proof(targets.into_iter().map(ProofV2Target::new))
2640 .expect("Proof generation failed");
2641 }
2642
2643 #[test]
2644 fn test_node_with_masked_empty_child() {
2645 reth_tracing::init_test_tracing();
2646
2647 let val = U256::from(42u64);
2648
2649 let slot_60 = B256::right_padding_from(&[0x60]);
2652 let slot_61 = B256::right_padding_from(&[0x61]);
2653 let slot_65 = B256::right_padding_from(&[0x65]);
2654 let slot_67 = B256::right_padding_from(&[0x67]);
2655
2656 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];
2662 let branch = BranchNodeCompact::new(state_mask, TrieMask::new(0), hash_mask, hashes, None);
2663
2664 let storage_nodes: BTreeMap<Nibbles, BranchNodeCompact> =
2665 std::iter::once((Nibbles::from_nibbles([0x6]), branch)).collect();
2666
2667 let mut harness = TrieTestHarness::new(
2670 [slot_60, slot_61, slot_65, slot_67].iter().map(|s| (*s, val)).collect(),
2671 );
2672 harness.set_trie_nodes(storage_nodes);
2673
2674 let storage_trie_cursor =
2675 harness.trie_cursor_factory().storage_trie_cursor(harness.hashed_address()).unwrap();
2676 let hashed_storage_cursor = harness
2677 .hashed_cursor_factory()
2678 .hashed_storage_cursor(harness.hashed_address())
2679 .unwrap();
2680 let mut calculator =
2681 StorageProofCalculator::new_storage(storage_trie_cursor, hashed_storage_cursor);
2682 let root_node = calculator
2683 .storage_root_node(harness.hashed_address())
2684 .expect("storage_root_node should succeed with masked empty child");
2685
2686 let root_hash = calculator.compute_root_hash(core::slice::from_ref(&root_node)).unwrap();
2687 assert!(root_hash.is_some(), "should produce a root hash");
2688 }
2689
2690 #[test]
2700 fn test_node_with_masked_empty_child_lower_bound_past_branch() {
2701 reth_tracing::init_test_tracing();
2702
2703 let val = U256::from(42u64);
2704
2705 let slot_60 = B256::right_padding_from(&[0x60]);
2707 let slot_61 = B256::right_padding_from(&[0x61]);
2708 let slot_6f = B256::right_padding_from(&[0x6f]);
2709 let slot_70 = B256::right_padding_from(&[0x70]);
2710
2711 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];
2717 let branch = BranchNodeCompact::new(state_mask, TrieMask::new(0), hash_mask, hashes, None);
2718
2719 let storage_nodes: BTreeMap<Nibbles, BranchNodeCompact> =
2720 std::iter::once((Nibbles::from_nibbles([0x6]), branch)).collect();
2721
2722 let mut harness = TrieTestHarness::new(
2724 [slot_60, slot_61, slot_6f, slot_70].iter().map(|s| (*s, val)).collect(),
2725 );
2726 harness.set_trie_nodes(storage_nodes);
2727
2728 let storage_trie_cursor =
2729 harness.trie_cursor_factory().storage_trie_cursor(harness.hashed_address()).unwrap();
2730 let hashed_storage_cursor = harness
2731 .hashed_cursor_factory()
2732 .hashed_storage_cursor(harness.hashed_address())
2733 .unwrap();
2734 let mut calculator =
2735 StorageProofCalculator::new_storage(storage_trie_cursor, hashed_storage_cursor);
2736 let root_node = calculator
2737 .storage_root_node(harness.hashed_address())
2738 .expect("storage_root_node should succeed when lower bound advances past branch");
2739
2740 let root_hash = calculator.compute_root_hash(core::slice::from_ref(&root_node)).unwrap();
2741 assert!(root_hash.is_some(), "should produce a root hash");
2742 }
2743
2744 #[test]
2754 fn test_prefix_set_adds_child_nibbles() {
2755 reth_tracing::init_test_tracing();
2756
2757 let val = U256::from(42u64);
2758 let slot_60 = B256::right_padding_from(&[0x60]);
2759 let slot_61 = B256::right_padding_from(&[0x61]);
2760 let slot_63 = B256::right_padding_from(&[0x63]);
2761
2762 let harness = TrieTestHarness::new([(slot_60, val), (slot_61, val)].into_iter().collect());
2763
2764 let changeset: BTreeMap<B256, U256> = std::iter::once((slot_63, val)).collect();
2765 let (expected_root, _) = harness.get_root_with_updates(&changeset);
2766
2767 let mut updated_storage = harness.storage().clone();
2768 updated_storage.insert(slot_63, val);
2769
2770 let updated_hashed = MockHashedCursorFactory::new(
2771 BTreeMap::new(),
2772 std::iter::once((harness.hashed_address(), updated_storage)).collect(),
2773 );
2774
2775 let mut prefix_set = PrefixSetMut::default();
2776 prefix_set.insert(Nibbles::unpack(slot_63));
2777
2778 let trie_cursor =
2779 harness.trie_cursor_factory().storage_trie_cursor(harness.hashed_address()).unwrap();
2780 let hashed_cursor = updated_hashed.hashed_storage_cursor(harness.hashed_address()).unwrap();
2781 let mut calculator = StorageProofCalculator::new_storage(trie_cursor, hashed_cursor)
2782 .with_prefix_set(prefix_set.freeze());
2783 let root_node = calculator
2784 .storage_root_node(harness.hashed_address())
2785 .expect("storage_root_node should succeed with prefix set adding child nibbles");
2786 let got_root =
2787 calculator.compute_root_hash(core::slice::from_ref(&root_node)).unwrap().unwrap();
2788
2789 pretty_assertions::assert_eq!(
2790 expected_root,
2791 got_root,
2792 "Root hash with prefix set should match fresh computation"
2793 );
2794 }
2795
2796 #[test]
2805 fn test_prefix_set_invalidates_cached_hash() {
2806 reth_tracing::init_test_tracing();
2807
2808 let original_val = U256::from(42u64);
2809 let updated_val = U256::from(9999u64);
2810 let slot_60 = B256::right_padding_from(&[0x60]);
2811 let slot_61 = B256::right_padding_from(&[0x61]);
2812 let slot_65 = B256::right_padding_from(&[0x65]);
2813
2814 let harness = TrieTestHarness::new(
2815 [(slot_60, original_val), (slot_61, original_val), (slot_65, original_val)]
2816 .into_iter()
2817 .collect(),
2818 );
2819
2820 let changeset: BTreeMap<B256, U256> = std::iter::once((slot_65, updated_val)).collect();
2821 let (expected_root, _) = harness.get_root_with_updates(&changeset);
2822
2823 let mut updated_storage = harness.storage().clone();
2824 updated_storage.insert(slot_65, updated_val);
2825
2826 let updated_hashed = MockHashedCursorFactory::new(
2827 BTreeMap::new(),
2828 std::iter::once((harness.hashed_address(), updated_storage)).collect(),
2829 );
2830
2831 let mut prefix_set = PrefixSetMut::default();
2832 prefix_set.insert(Nibbles::unpack(slot_65));
2833
2834 let trie_cursor =
2835 harness.trie_cursor_factory().storage_trie_cursor(harness.hashed_address()).unwrap();
2836 let hashed_cursor = updated_hashed.hashed_storage_cursor(harness.hashed_address()).unwrap();
2837 let mut calculator = StorageProofCalculator::new_storage(trie_cursor, hashed_cursor)
2838 .with_prefix_set(prefix_set.freeze());
2839 let root_node = calculator
2840 .storage_root_node(harness.hashed_address())
2841 .expect("storage_root_node should succeed with prefix set invalidating cached hash");
2842 let got_root =
2843 calculator.compute_root_hash(core::slice::from_ref(&root_node)).unwrap().unwrap();
2844
2845 pretty_assertions::assert_eq!(
2846 expected_root,
2847 got_root,
2848 "Root hash with prefix set invalidation should match fresh computation"
2849 );
2850 }
2851
2852 fn b256(s: &str) -> B256 {
2853 B256::from_slice(&alloy_primitives::hex::decode(s).expect("valid hex string"))
2854 }
2855
2856 #[test]
2857 fn test_prefix_set_root_proof_processes_sibling_after_cached_descendant() {
2858 reth_tracing::init_test_tracing();
2859
2860 let storage = [
2861 ("1022c69e9d900e40775cd387c134899f465f291dbc3c97899ff6bfb8dc972b37", 45u64),
2862 ("1111ad8083c8a3a398b2b781217b989ff4d1ed182f46cc765eda49a7b316139d", 60),
2863 ("12012d20943649899b2fc0f87b9840b70ef68e93613aac17c269bf8c5a78a712", 17),
2864 ("12014b57b9a162c03d072eb6acd4e936f1c4bc23b803a054347c5ee9a9bcfb9a", 49),
2865 ("1203f800840af3f898ab4572f2750106a7c4bd2b3e844b6e7fa72704673cc2c6", 76),
2866 ("12208f18fbcd6971c92808721392acbf11d5af58e9143a374cc86e70bdd1f097", 10),
2867 ]
2868 .into_iter()
2869 .map(|(key, value)| (b256(key), U256::from(value)))
2870 .collect();
2871
2872 let dirty = b256("12208f18fbcd6971c92808721392acbf11d5af58e9143a374cc86e70bdd1f097");
2873 let harness = ProofTestHarness::new(storage);
2874 let expected_root = harness.original_root();
2875
2876 let mut prefix_set = PrefixSetMut::default();
2877 prefix_set.insert(Nibbles::unpack(dirty));
2878
2879 pretty_assertions::assert_eq!(
2880 Some(expected_root),
2881 harness.root_with_prefix_set(prefix_set.freeze()),
2882 "root proof must process a prefix-set sibling after a cached descendant",
2883 );
2884 }
2885
2886 #[test]
2887 fn test_prefix_set_root_proof_processes_trailing_dirty_sibling() {
2888 reth_tracing::init_test_tracing();
2889
2890 let keys = [
2891 "0022001020000000000000000000000000000000000000000000000000000000",
2892 "0110212112000000000000000000000000000000000000000000000000000000",
2893 "0202210210000000000000000000000000000000000000000000000000000000",
2894 "0211020211000000000000000000000000000000000000000000000000000000",
2895 "0211211002000000000000000000000000000000000000000000000000000000",
2896 "0212221010000000000000000000000000000000000000000000000000000000",
2897 "0222011102000000000000000000000000000000000000000000000000000000",
2898 ];
2899 let storage =
2900 keys.iter().enumerate().map(|(i, key)| (b256(key), U256::from(i as u64 + 1))).collect();
2901 let harness = ProofTestHarness::new(storage);
2902 let expected_root = harness.original_root();
2903
2904 let mut prefix_set = PrefixSetMut::default();
2907 prefix_set.insert(Nibbles::unpack(b256(keys[2])));
2908 prefix_set.insert(Nibbles::unpack(b256(keys[6])));
2909
2910 pretty_assertions::assert_eq!(
2911 Some(expected_root),
2912 harness.root_with_prefix_set(prefix_set.freeze()),
2913 );
2914 }
2915
2916 fn storage_leaf_hash(short_key: &Nibbles, value: &U256) -> B256 {
2919 let mut buf = Vec::new();
2920 alloy_trie::nodes::LeafNodeRef::new(short_key, &alloy_rlp::encode_fixed_size(value))
2921 .encode(&mut buf);
2922 keccak256(&buf)
2923 }
2924
2925 #[test]
2940 fn test_branch_collapse_removed_child_before_remaining() {
2941 reth_tracing::init_test_tracing();
2942
2943 let val = U256::from(1u64);
2944
2945 let key_a = B256::right_padding_from(&[0x20]); let key_b = B256::right_padding_from(&[0x21]); let key_c = B256::right_padding_from(&[0xb0]); let leaf_hash_a = storage_leaf_hash(&Nibbles::unpack(key_a).slice(2..), &val);
2952 let leaf_hash_b = storage_leaf_hash(&Nibbles::unpack(key_b).slice(2..), &val);
2953
2954 let sub_branch_state_mask = TrieMask::new((1 << 0) | (1 << 1));
2957 let cached_sub_branch = BranchNodeCompact::new(
2958 sub_branch_state_mask,
2959 TrieMask::new(0),
2960 sub_branch_state_mask,
2961 vec![leaf_hash_a, leaf_hash_b],
2962 None,
2963 );
2964
2965 let storage_nodes: BTreeMap<Nibbles, BranchNodeCompact> =
2966 std::iter::once((Nibbles::from_nibbles([0x2]), cached_sub_branch)).collect();
2967
2968 let mut harness = TrieTestHarness::new([(key_b, val), (key_c, val)].into_iter().collect());
2971 harness.set_trie_nodes(storage_nodes);
2972
2973 let mut prefix_set_mut = PrefixSetMut::default();
2975 prefix_set_mut.insert(Nibbles::unpack(key_a));
2976 let prefix_set = prefix_set_mut.freeze();
2977
2978 let storage_trie_cursor =
2980 harness.trie_cursor_factory().storage_trie_cursor(harness.hashed_address()).unwrap();
2981 let hashed_storage_cursor = harness
2982 .hashed_cursor_factory()
2983 .hashed_storage_cursor(harness.hashed_address())
2984 .unwrap();
2985 let mut calculator =
2986 StorageProofCalculator::new_storage(storage_trie_cursor, hashed_storage_cursor)
2987 .with_prefix_set(prefix_set);
2988 let root_node = calculator
2989 .storage_root_node(harness.hashed_address())
2990 .expect("storage_root_node should succeed after branch collapse");
2991 let root_with_collapse =
2992 calculator.compute_root_hash(core::slice::from_ref(&root_node)).unwrap().unwrap();
2993
2994 let mut fresh_harness =
2996 TrieTestHarness::new([(key_b, val), (key_c, val)].into_iter().collect());
2997 fresh_harness.set_trie_nodes(BTreeMap::new());
2998 let storage_trie_cursor = fresh_harness
2999 .trie_cursor_factory()
3000 .storage_trie_cursor(fresh_harness.hashed_address())
3001 .unwrap();
3002 let hashed_storage_cursor = fresh_harness
3003 .hashed_cursor_factory()
3004 .hashed_storage_cursor(fresh_harness.hashed_address())
3005 .unwrap();
3006 let mut fresh_calculator =
3007 StorageProofCalculator::new_storage(storage_trie_cursor, hashed_storage_cursor);
3008 let fresh_root_node = fresh_calculator
3009 .storage_root_node(fresh_harness.hashed_address())
3010 .expect("fresh storage_root_node should succeed");
3011 let expected_root = fresh_calculator
3012 .compute_root_hash(core::slice::from_ref(&fresh_root_node))
3013 .unwrap()
3014 .unwrap();
3015
3016 pretty_assertions::assert_eq!(
3017 expected_root,
3018 root_with_collapse,
3019 "Root hash after collapsing branch (removed child before remaining) should match fresh computation"
3020 );
3021 }
3022
3023 #[test]
3029 fn test_branch_collapse_removed_child_after_remaining() {
3030 reth_tracing::init_test_tracing();
3031
3032 let val = U256::from(1u64);
3033
3034 let key_a = B256::right_padding_from(&[0x24]); let key_b = B256::right_padding_from(&[0x29]); let key_c = B256::right_padding_from(&[0xb0]); let leaf_hash_a = storage_leaf_hash(&Nibbles::unpack(key_a).slice(2..), &val);
3040 let leaf_hash_b = storage_leaf_hash(&Nibbles::unpack(key_b).slice(2..), &val);
3041
3042 let sub_branch_state_mask = TrieMask::new((1 << 4) | (1 << 9));
3044 let cached_sub_branch = BranchNodeCompact::new(
3045 sub_branch_state_mask,
3046 TrieMask::new(0),
3047 sub_branch_state_mask,
3048 vec![leaf_hash_a, leaf_hash_b],
3049 None,
3050 );
3051
3052 let storage_nodes: BTreeMap<Nibbles, BranchNodeCompact> =
3053 std::iter::once((Nibbles::from_nibbles([0x2]), cached_sub_branch)).collect();
3054
3055 let mut harness = TrieTestHarness::new([(key_a, val), (key_c, val)].into_iter().collect());
3057 harness.set_trie_nodes(storage_nodes);
3058
3059 let mut prefix_set_mut = PrefixSetMut::default();
3061 prefix_set_mut.insert(Nibbles::unpack(key_b));
3062 let prefix_set = prefix_set_mut.freeze();
3063
3064 let storage_trie_cursor =
3066 harness.trie_cursor_factory().storage_trie_cursor(harness.hashed_address()).unwrap();
3067 let hashed_storage_cursor = harness
3068 .hashed_cursor_factory()
3069 .hashed_storage_cursor(harness.hashed_address())
3070 .unwrap();
3071 let mut calculator =
3072 StorageProofCalculator::new_storage(storage_trie_cursor, hashed_storage_cursor)
3073 .with_prefix_set(prefix_set);
3074 let root_node = calculator
3075 .storage_root_node(harness.hashed_address())
3076 .expect("storage_root_node should succeed after branch collapse");
3077 let root_with_collapse =
3078 calculator.compute_root_hash(core::slice::from_ref(&root_node)).unwrap().unwrap();
3079
3080 let mut fresh_harness =
3082 TrieTestHarness::new([(key_a, val), (key_c, val)].into_iter().collect());
3083 fresh_harness.set_trie_nodes(BTreeMap::new());
3084 let storage_trie_cursor = fresh_harness
3085 .trie_cursor_factory()
3086 .storage_trie_cursor(fresh_harness.hashed_address())
3087 .unwrap();
3088 let hashed_storage_cursor = fresh_harness
3089 .hashed_cursor_factory()
3090 .hashed_storage_cursor(fresh_harness.hashed_address())
3091 .unwrap();
3092 let mut fresh_calculator =
3093 StorageProofCalculator::new_storage(storage_trie_cursor, hashed_storage_cursor);
3094 let fresh_root_node = fresh_calculator
3095 .storage_root_node(fresh_harness.hashed_address())
3096 .expect("fresh storage_root_node should succeed");
3097 let expected_root = fresh_calculator
3098 .compute_root_hash(core::slice::from_ref(&fresh_root_node))
3099 .unwrap()
3100 .unwrap();
3101
3102 pretty_assertions::assert_eq!(
3103 expected_root,
3104 root_with_collapse,
3105 "Root hash after collapsing branch (removed child after remaining) should match fresh computation"
3106 );
3107 }
3108
3109 #[test]
3110 fn test_cached_branch_extension_skips_diverging_target() {
3111 reth_tracing::init_test_tracing();
3112
3113 let val = U256::from(100u64);
3114
3115 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> =
3124 [(key_a0, val), (key_a1, val), (key_c, val), (key_d, val), (key_e, val)]
3125 .into_iter()
3126 .collect();
3127 let correct_harness = TrieTestHarness::new(all_storage.clone());
3128 let expected_root = correct_harness.original_root();
3129
3130 let leaf_hash_a0 = storage_leaf_hash(&Nibbles::unpack(key_a0).slice(4..), &val);
3132 let leaf_hash_a1 = storage_leaf_hash(&Nibbles::unpack(key_a1).slice(4..), &val);
3133 let leaf_hash_d = storage_leaf_hash(&Nibbles::unpack(key_d).slice(2..), &val);
3134 let leaf_hash_e = storage_leaf_hash(&Nibbles::unpack(key_e).slice(2..), &val);
3135
3136 let branch_6_state_mask = TrieMask::new((1 << 0xa) | (1 << 0xb) | (1 << 0xc));
3144 let branch_6_hash_mask = TrieMask::new((1 << 0xb) | (1 << 0xc));
3145 let branch_6 = BranchNodeCompact::new(
3146 branch_6_state_mask,
3147 TrieMask::new(0),
3148 branch_6_hash_mask,
3149 vec![leaf_hash_d, leaf_hash_e],
3150 None,
3151 );
3152
3153 let branch_6a3_state_mask = TrieMask::new((1 << 0) | (1 << 1));
3157 let branch_6a3 = BranchNodeCompact::new(
3158 branch_6a3_state_mask,
3159 TrieMask::new(0),
3160 branch_6a3_state_mask,
3161 vec![leaf_hash_a0, leaf_hash_a1],
3162 None,
3163 );
3164
3165 let inconsistent_nodes: BTreeMap<Nibbles, BranchNodeCompact> = [
3167 (Nibbles::from_nibbles([0x6]), branch_6),
3168 (Nibbles::from_nibbles([0x6, 0xa, 0x3]), branch_6a3),
3169 ]
3170 .into_iter()
3171 .collect();
3172
3173 let mut harness = TrieTestHarness::new(all_storage);
3175 harness.set_trie_nodes(inconsistent_nodes);
3176
3177 let mut prefix_set = PrefixSetMut::default();
3183 prefix_set.insert(Nibbles::unpack(key_c));
3184
3185 let trie_cursor =
3187 harness.trie_cursor_factory().storage_trie_cursor(harness.hashed_address()).unwrap();
3188 let hashed_cursor = harness
3189 .hashed_cursor_factory()
3190 .hashed_storage_cursor(harness.hashed_address())
3191 .unwrap();
3192 let mut calculator = StorageProofCalculator::new_storage(trie_cursor, hashed_cursor)
3193 .with_prefix_set(prefix_set.freeze());
3194
3195 let root_node = calculator
3196 .storage_root_node(harness.hashed_address())
3197 .expect("storage_root_node should succeed");
3198 let got_root = calculator
3199 .compute_root_hash(core::slice::from_ref(&root_node))
3200 .unwrap()
3201 .expect("should produce a root hash");
3202
3203 pretty_assertions::assert_eq!(
3205 expected_root,
3206 got_root,
3207 "Root hash should match correct trie; cached extension must not skip diverging leaves"
3208 );
3209
3210 let mut targets = vec![ProofV2Target::new(key_c)];
3212 let proofs = calculator
3213 .storage_proof(harness.hashed_address(), &mut targets)
3214 .expect("storage_proof should succeed");
3215
3216 let key_c_nibbles = Nibbles::unpack(key_c);
3217 let has_matching_node = proofs.iter().any(|node| key_c_nibbles.starts_with(&node.path));
3218 assert!(
3219 has_matching_node,
3220 "Proof for key_c should contain at least one node on key_c's path, got: {proofs:?}"
3221 );
3222 }
3223
3224 #[test]
3225 fn test_cached_branch_extension_skips_diverging_target_before() {
3226 reth_tracing::init_test_tracing();
3227
3228 let val = U256::from(100u64);
3229
3230 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> =
3240 [(key_a0, val), (key_a1, val), (key_c, val), (key_d, val), (key_e, val)]
3241 .into_iter()
3242 .collect();
3243 let correct_harness = TrieTestHarness::new(all_storage.clone());
3244 let expected_root = correct_harness.original_root();
3245
3246 let leaf_hash_a0 = storage_leaf_hash(&Nibbles::unpack(key_a0).slice(4..), &val);
3248 let leaf_hash_a1 = storage_leaf_hash(&Nibbles::unpack(key_a1).slice(4..), &val);
3249 let leaf_hash_d = storage_leaf_hash(&Nibbles::unpack(key_d).slice(2..), &val);
3250 let leaf_hash_e = storage_leaf_hash(&Nibbles::unpack(key_e).slice(2..), &val);
3251
3252 let branch_6_state_mask = TrieMask::new((1 << 0xa) | (1 << 0xb) | (1 << 0xc));
3260 let branch_6_hash_mask = TrieMask::new((1 << 0xb) | (1 << 0xc));
3261 let branch_6 = BranchNodeCompact::new(
3262 branch_6_state_mask,
3263 TrieMask::new(0),
3264 branch_6_hash_mask,
3265 vec![leaf_hash_d, leaf_hash_e],
3266 None,
3267 );
3268
3269 let branch_6a8_state_mask = TrieMask::new((1 << 0) | (1 << 1));
3273 let branch_6a8 = BranchNodeCompact::new(
3274 branch_6a8_state_mask,
3275 TrieMask::new(0),
3276 branch_6a8_state_mask,
3277 vec![leaf_hash_a0, leaf_hash_a1],
3278 None,
3279 );
3280
3281 let inconsistent_nodes: BTreeMap<Nibbles, BranchNodeCompact> = [
3283 (Nibbles::from_nibbles([0x6]), branch_6),
3284 (Nibbles::from_nibbles([0x6, 0xa, 0x8]), branch_6a8),
3285 ]
3286 .into_iter()
3287 .collect();
3288
3289 let mut harness = TrieTestHarness::new(all_storage);
3291 harness.set_trie_nodes(inconsistent_nodes);
3292
3293 let mut prefix_set = PrefixSetMut::default();
3295 prefix_set.insert(Nibbles::unpack(key_c));
3296
3297 let trie_cursor =
3299 harness.trie_cursor_factory().storage_trie_cursor(harness.hashed_address()).unwrap();
3300 let hashed_cursor = harness
3301 .hashed_cursor_factory()
3302 .hashed_storage_cursor(harness.hashed_address())
3303 .unwrap();
3304 let mut calculator = StorageProofCalculator::new_storage(trie_cursor, hashed_cursor)
3305 .with_prefix_set(prefix_set.freeze());
3306
3307 let root_node = calculator
3308 .storage_root_node(harness.hashed_address())
3309 .expect("storage_root_node should succeed");
3310 let got_root = calculator
3311 .compute_root_hash(core::slice::from_ref(&root_node))
3312 .unwrap()
3313 .expect("should produce a root hash");
3314
3315 pretty_assertions::assert_eq!(
3317 expected_root,
3318 got_root,
3319 "Root hash should match correct trie; cached extension must not skip diverging leaves before cached branch"
3320 );
3321
3322 let mut targets = vec![ProofV2Target::new(key_c)];
3324 let proofs = calculator
3325 .storage_proof(harness.hashed_address(), &mut targets)
3326 .expect("storage_proof should succeed");
3327
3328 let key_c_nibbles = Nibbles::unpack(key_c);
3329 let has_matching_node = proofs.iter().any(|node| key_c_nibbles.starts_with(&node.path));
3330 assert!(
3331 has_matching_node,
3332 "Proof for key_c should contain at least one node on key_c's path, got: {proofs:?}"
3333 );
3334 }
3335
3336 #[test]
3337 fn test_skipped_parent_branch_with_unskipped_child() {
3338 reth_tracing::init_test_tracing();
3339
3340 let val = U256::from(1u64);
3341 let updated_val = U256::from(2u64);
3342
3343 let key_2 = B256::right_padding_from(&[0x20]);
3345 let key_2f00 = B256::right_padding_from(&[0x2f, 0x00]);
3346 let key_2f01 = B256::right_padding_from(&[0x2f, 0x01]);
3347 let key_2f10 = B256::right_padding_from(&[0x2f, 0x10]);
3348 let key_2f11 = B256::right_padding_from(&[0x2f, 0x11]);
3349 let key_300 = B256::right_padding_from(&[0x30, 0x00]);
3350 let key_301 = B256::right_padding_from(&[0x30, 0x10]);
3351 let key_310 = B256::right_padding_from(&[0x31, 0x00]);
3352 let key_311 = B256::right_padding_from(&[0x31, 0x10]);
3353 let key_500 = B256::right_padding_from(&[0x50, 0x00]);
3354 let key_501 = B256::right_padding_from(&[0x50, 0x10]);
3355 let key_510 = B256::right_padding_from(&[0x51, 0x00]);
3356 let key_511 = B256::right_padding_from(&[0x51, 0x10]);
3357
3358 let all_keys = [
3359 key_2, key_2f00, key_2f01, key_2f10, key_2f11, key_300, key_301, key_310, key_311,
3360 key_500, key_501, key_510, key_511,
3361 ];
3362
3363 let original_storage: BTreeMap<B256, U256> = all_keys.iter().map(|k| (*k, val)).collect();
3364 let harness = TrieTestHarness::new(original_storage);
3365
3366 let trie_updates = harness.storage_trie_updates();
3368 assert!(trie_updates.storage_nodes.contains_key(&Nibbles::from_nibbles([0x2])));
3369 assert!(trie_updates.storage_nodes.contains_key(&Nibbles::from_nibbles([0x2, 0xf])));
3370 assert!(trie_updates.storage_nodes.contains_key(&Nibbles::from_nibbles([0x3])));
3371
3372 let changeset: BTreeMap<B256, U256> = std::iter::once((key_2, updated_val)).collect();
3375 let (expected_root, _) = harness.get_root_with_updates(&changeset);
3376
3377 let mut updated_storage = harness.storage().clone();
3378 updated_storage.insert(key_2, updated_val);
3379
3380 let updated_hashed = MockHashedCursorFactory::new(
3381 BTreeMap::new(),
3382 std::iter::once((harness.hashed_address(), updated_storage)).collect(),
3383 );
3384
3385 let mut prefix_set = PrefixSetMut::default();
3386 prefix_set.insert(Nibbles::unpack(key_2));
3387
3388 let trie_cursor =
3389 harness.trie_cursor_factory().storage_trie_cursor(harness.hashed_address()).unwrap();
3390 let hashed_cursor = updated_hashed.hashed_storage_cursor(harness.hashed_address()).unwrap();
3391 let mut calculator = StorageProofCalculator::new_storage(trie_cursor, hashed_cursor)
3392 .with_prefix_set(prefix_set.freeze());
3393 let root_node = calculator
3394 .storage_root_node(harness.hashed_address())
3395 .expect("storage_root_node should succeed");
3396
3397 let got_root = calculator
3398 .compute_root_hash(&[root_node])
3399 .expect("root hash should succeed")
3400 .expect("root should get hashed");
3401 pretty_assertions::assert_eq!(expected_root, got_root);
3402 }
3403
3404 #[test]
3405 fn test_cached_hash_with_deleted_leaf() {
3406 reth_tracing::init_test_tracing();
3407
3408 let val_3 = U256::from(111u64);
3410 let val_5 = U256::from(222u64);
3411 let val_8 = U256::from(333u64);
3412
3413 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);
3422 let leaf_hash_5 = storage_leaf_hash(&Nibbles::unpack(key_65).slice(2..), &val_5);
3423 let leaf_hash_8 = storage_leaf_hash(&Nibbles::unpack(key_68).slice(2..), &val_8);
3424
3425 let state_mask = TrieMask::new((1 << 3) | (1 << 5) | (1 << 8));
3427 let cached_branch = BranchNodeCompact::new(
3428 state_mask,
3429 TrieMask::new(0),
3430 state_mask, vec![leaf_hash_3, leaf_hash_5, leaf_hash_8],
3432 None,
3433 );
3434
3435 let storage_nodes: BTreeMap<Nibbles, BranchNodeCompact> =
3436 std::iter::once((Nibbles::from_nibbles([0x6]), cached_branch)).collect();
3437
3438 let mut harness =
3440 TrieTestHarness::new([(key_65, val_5), (key_68, val_8)].into_iter().collect());
3441 let expected_root = harness.original_root();
3442
3443 harness.set_trie_nodes(storage_nodes);
3445
3446 let mut prefix_set = PrefixSetMut::default();
3449 prefix_set.insert(Nibbles::unpack(key_63));
3450
3451 let mut targets = vec![ProofV2Target::new(key_63)];
3455
3456 let trie_cursor =
3457 harness.trie_cursor_factory().storage_trie_cursor(harness.hashed_address()).unwrap();
3458 let hashed_cursor = harness
3459 .hashed_cursor_factory()
3460 .hashed_storage_cursor(harness.hashed_address())
3461 .unwrap();
3462 let mut calculator = StorageProofCalculator::new_storage(trie_cursor, hashed_cursor)
3463 .with_prefix_set(prefix_set.freeze());
3464
3465 let proofs = calculator
3466 .storage_proof(harness.hashed_address(), &mut targets)
3467 .expect("storage_proof should succeed");
3468 assert_eq!(1, proofs.len());
3469 let got_root = calculator
3470 .compute_root_hash(&proofs)
3471 .expect("compute_root_hash should succeed")
3472 .expect("should produce a root hash (proof contains root node)");
3473
3474 pretty_assertions::assert_eq!(
3477 expected_root,
3478 got_root,
3479 "Root hash should match trie without key_63; cached hash index is off when \
3480 an earlier hashed child has no leaves (absence proof target)"
3481 );
3482 }
3483}