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>(
982 &mut self,
983 targets: &mut Option<TargetsCursor<'a>>,
984 next_path: &Nibbles,
985 ) -> Result<(), StateProofError> {
986 while !next_path.starts_with(&self.branch_path) {
987 self.pop_branch(targets)?;
988 }
989 Ok(())
990 }
991
992 #[instrument(target = TRACE_TARGET, level = "trace", skip_all)]
1011 fn next_uncached_key_range<'a>(
1012 &mut self,
1013 targets: &mut Option<TargetsCursor<'a>>,
1014 trie_cursor_state: &mut TrieCursorState,
1015 traversal_upper_bound: Option<&Nibbles>,
1016 mut uncalculated_lower_bound: Option<Nibbles>,
1017 ) -> Result<Option<(Nibbles, Option<Nibbles>)>, StateProofError> {
1018 loop {
1019 if let (Some(lower_bound), Some(upper_bound)) =
1020 (uncalculated_lower_bound.as_ref(), traversal_upper_bound) &&
1021 lower_bound >= upper_bound
1022 {
1023 return Ok(None)
1024 }
1025
1026 let (cached_path, cached_branch) = match self.try_pop_cached_branch(
1032 trie_cursor_state,
1033 traversal_upper_bound,
1034 &uncalculated_lower_bound,
1035 )? {
1036 PopCachedBranchOutcome::Popped(cached) => cached,
1037 PopCachedBranchOutcome::Exhausted => {
1038 trace!(target: TRACE_TARGET, ?uncalculated_lower_bound, "Exhausted cached trie nodes");
1042 if let Some(lower) = uncalculated_lower_bound {
1043 self.commit_branches(targets, &lower)?;
1044 return Ok(Some((lower, traversal_upper_bound.copied())));
1045 }
1046 return Ok(None)
1047 }
1048 PopCachedBranchOutcome::CalculateLeaves(range) => {
1049 self.commit_branches(targets, &range.0)?;
1050 return Ok(Some(range));
1051 }
1052 };
1053
1054 let uncalculated_lower_bound_ref = uncalculated_lower_bound
1055 .as_ref()
1056 .expect("try_pop_cached_branch would return Exhausted if this were None");
1057
1058 trace!(
1059 target: TRACE_TARGET,
1060 branch_path = ?self.branch_path,
1061 branch_state_mask = ?self.branch_stack.last().map(|b| b.state_mask),
1062 ?cached_path,
1063 cached_branch_state_mask = ?cached_branch.state_mask,
1064 cached_branch_hash_mask = ?cached_branch.hash_mask,
1065 "loop",
1066 );
1067
1068 self.commit_branches(targets, &cached_path)?;
1069
1070 debug_assert!(
1073 self.branch_path.len() < cached_path.len() || self.branch_path == cached_path,
1074 "branch_path {:?} is different-or-longer-than cached_path {cached_path:?}",
1075 self.branch_path
1076 );
1077
1078 if self.branch_path != cached_path {
1082 self.push_cached_branch(targets, cached_path, &cached_branch)?;
1083 }
1084
1085 let curr_branch =
1088 self.branch_stack.last().expect("top of branch_stack corresponds to cached branch");
1089
1090 let cached_state_mask = cached_branch.state_mask;
1091 let curr_state_mask = curr_branch.state_mask;
1092
1093 let mut next_child_nibbles = curr_state_mask ^ cached_state_mask;
1096
1097 if self.prefix_set.contains(&self.branch_path) {
1102 let branch_path_len = self.branch_path.len();
1103 let mut child_path = self.branch_path;
1104 for nibble in 0u8..16 {
1105 if !curr_state_mask.is_bit_set(nibble) {
1106 child_path.truncate(branch_path_len);
1107 child_path.push_unchecked(nibble);
1108 if self.prefix_set.contains(&child_path) {
1109 next_child_nibbles.set_bit(nibble);
1110 }
1111 }
1112 }
1113 }
1114
1115 let _orig_next_child_nibbles = next_child_nibbles;
1116
1117 if uncalculated_lower_bound_ref.starts_with(&self.branch_path) &&
1122 uncalculated_lower_bound_ref.len() > self.branch_path.len()
1123 {
1124 let lower_nibble =
1125 uncalculated_lower_bound_ref.get_unchecked(self.branch_path.len());
1126 let already_processed_mask = TrieMask::new((1u16 << lower_nibble) - 1);
1128 next_child_nibbles &= !already_processed_mask;
1129 trace!(
1130 target: TRACE_TARGET,
1131 branch_path = ?self.branch_path,
1132 ?_orig_next_child_nibbles,
1133 ?already_processed_mask,
1134 ?next_child_nibbles,
1135 "Unset already processed key nibbles from next_child_nibbles",
1136 );
1137 } else if !uncalculated_lower_bound_ref.starts_with(&self.branch_path) &&
1138 uncalculated_lower_bound_ref > &self.branch_path
1139 {
1140 next_child_nibbles = TrieMask::default();
1143 trace!(
1144 target: TRACE_TARGET,
1145 branch_path = ?self.branch_path,
1146 ?_orig_next_child_nibbles,
1147 ?next_child_nibbles,
1148 "Unset all nibbles from next_child_nibbles due to branch_path being outside this subtrie",
1149 );
1150 }
1151
1152 if next_child_nibbles.is_empty() {
1155 trace!(
1156 target: TRACE_TARGET,
1157 path=?cached_path,
1158 ?curr_branch,
1159 ?cached_branch,
1160 "No further children, popping branch",
1161 );
1162 self.pop_branch(targets)?;
1163
1164 uncalculated_lower_bound = cached_path.next_without_prefix();
1171
1172 continue
1173 }
1174
1175 let child_nibble = next_child_nibbles.trailing_zeros() as u8;
1178 let child_path = self.child_path_at(child_nibble);
1179
1180 if uncalculated_lower_bound_ref < &child_path &&
1186 self.prefix_set.contains_range(uncalculated_lower_bound_ref..&child_path)
1187 {
1188 self.cached_branch_stack.push((cached_path, cached_branch));
1189 return Ok(Some((*uncalculated_lower_bound_ref, Some(child_path))));
1190 }
1191
1192 if cached_branch.hash_mask.is_bit_set(child_nibble) &&
1202 !self.prefix_set.contains(&child_path)
1203 {
1204 self.commit_last_child(targets)?;
1211
1212 if !self.should_retain(targets, &child_path, false) {
1213 let lower_bits = TrieMask::new((1u16 << child_nibble) - 1);
1216 let hash_idx = (cached_branch.hash_mask & lower_bits).count_ones() as usize;
1217 let hash = cached_branch.hashes[hash_idx];
1218
1219 trace!(
1220 target: TRACE_TARGET,
1221 ?child_path,
1222 ?hash_idx,
1223 ?hash,
1224 "Using cached hash for child",
1225 );
1226
1227 self.child_stack.push(ProofTrieBranchChild::RlpNode(RlpNode::word_rlp(&hash)));
1228 self.branch_stack
1229 .last_mut()
1230 .expect("already asserted there is a last branch")
1231 .state_mask
1232 .set_bit(child_nibble);
1233
1234 uncalculated_lower_bound = child_path.next_without_prefix();
1237
1238 self.cached_branch_stack.push((cached_path, cached_branch));
1240
1241 continue
1242 }
1243 }
1244
1245 if trie_cursor_state.path().is_some_and(|path| path < &child_path) {
1252 trace!(target: TRACE_TARGET, ?child_path, "Seeking trie cursor to child path");
1253 *trie_cursor_state =
1254 TrieCursorState::seeked(child_path, self.trie_cursor_seek(child_path)?);
1255 }
1256
1257 if let TrieCursorState::Available(next_cached_path, next_cached_branch) =
1261 &trie_cursor_state &&
1262 next_cached_path.starts_with(&child_path)
1263 {
1264 self.cached_branch_stack.push((cached_path, cached_branch));
1266
1267 if self.prefix_set.contains(&child_path) {
1272 let gap_upper = Some(*next_cached_path);
1273 self.cached_branch_stack.push(trie_cursor_state.take());
1274 return Ok(Some((*uncalculated_lower_bound_ref, gap_upper)));
1275 }
1276
1277 trace!(
1278 target: TRACE_TARGET,
1279 ?child_path,
1280 ?next_cached_path,
1281 ?next_cached_branch,
1282 "Pushing cached branch for child",
1283 );
1284 self.cached_branch_stack.push(trie_cursor_state.take());
1285 continue;
1286 }
1287
1288 let child_path_upper = child_path.next_without_prefix();
1292 trace!(
1293 target: TRACE_TARGET,
1294 lower=?child_path,
1295 upper=?child_path_upper,
1296 "Returning sub-trie's key range to calculate",
1297 );
1298
1299 self.cached_branch_stack.push((cached_path, cached_branch));
1301
1302 return Ok(Some((child_path, child_path_upper)));
1303 }
1304 }
1305
1306 #[instrument(
1310 target = TRACE_TARGET,
1311 level = "trace",
1312 skip_all,
1313 fields(
1314 parent_prefix=?sub_trie_targets.parent_prefix,
1315 lower_bound=?sub_trie_targets.lower_bound,
1316 upper_bound=?sub_trie_targets.upper_bound,
1317 ),
1318 )]
1319 fn proof_subtrie<'a>(
1320 &mut self,
1321 value_encoder: &mut VE,
1322 trie_cursor_state: &mut TrieCursorState,
1323 hashed_cursor_state: &mut HashedCursorState<VE::DeferredEncoder>,
1324 sub_trie_targets: SubTrieTargets<'a>,
1325 ) -> Result<(), StateProofError> {
1326 let traversal_lower_bound = sub_trie_targets.lower_bound;
1327 let traversal_upper_bound = sub_trie_targets.upper_bound;
1328
1329 let mut targets = if sub_trie_targets.targets.is_empty() {
1332 None
1333 } else {
1334 Some(TargetsCursor::new(sub_trie_targets.targets))
1335 };
1336
1337 debug_assert!(self.cached_branch_stack.is_empty());
1340 debug_assert!(self.branch_stack.is_empty());
1341 debug_assert!(self.branch_path.is_empty());
1342 debug_assert!(self.child_stack.is_empty());
1343
1344 if trie_cursor_state.needs_seek_to(&traversal_lower_bound) {
1349 trace!(target: TRACE_TARGET, "Doing initial seek of trie cursor");
1350 *trie_cursor_state = TrieCursorState::seeked(
1351 traversal_lower_bound,
1352 self.trie_cursor_seek(traversal_lower_bound)?,
1353 );
1354 }
1355
1356 let mut uncalculated_lower_bound = Some(traversal_lower_bound);
1361
1362 trace!(target: TRACE_TARGET, "Starting loop");
1363 loop {
1364 let prev_uncalculated_lower_bound = uncalculated_lower_bound;
1366
1367 let Some((calc_lower_bound, calc_upper_bound)) = self.next_uncached_key_range(
1369 &mut targets,
1370 trie_cursor_state,
1371 traversal_upper_bound.as_ref(),
1372 prev_uncalculated_lower_bound,
1373 )?
1374 else {
1375 break;
1378 };
1379
1380 if let Some(prev_lower) = prev_uncalculated_lower_bound.as_ref() &&
1387 calc_lower_bound < *prev_lower
1388 {
1389 let msg = format!(
1390 "next_uncached_key_range went backwards: calc_lower={calc_lower_bound:?} < \
1391 prev_lower={prev_lower:?}, calc_upper={calc_upper_bound:?}, \
1392 lower_bound={traversal_lower_bound:?}, \
1393 upper_bound={traversal_upper_bound:?}",
1394 );
1395 error!(target: TRACE_TARGET, "{msg}");
1396 return Err(StateProofError::TrieInconsistency(msg));
1397 }
1398
1399 self.calculate_key_range(
1401 value_encoder,
1402 &mut targets,
1403 hashed_cursor_state,
1404 calc_lower_bound,
1405 calc_upper_bound,
1406 )?;
1407
1408 if hashed_cursor_state.path().is_none_or(|key| {
1414 traversal_upper_bound.is_some_and(|upper_bound| key >= &upper_bound)
1415 }) {
1416 break;
1417 }
1418
1419 uncalculated_lower_bound = calc_upper_bound;
1422 }
1423
1424 trace!(target: TRACE_TARGET, "Exited loop, popping remaining branches");
1426 while !self.branch_stack.is_empty() {
1427 self.pop_branch(&mut targets)?;
1428 }
1429
1430 debug_assert!(self.branch_stack.is_empty());
1434 debug_assert!(self.branch_path.is_empty());
1435 debug_assert!(self.child_stack.len() < 2);
1436
1437 self.cached_branch_stack.clear();
1440
1441 trace!(
1445 target: TRACE_TARGET,
1446 parent_prefix = ?sub_trie_targets.parent_prefix,
1447 child_stack_empty = self.child_stack.is_empty(),
1448 "Maybe retaining local root",
1449 );
1450 let root_node = match self.child_stack.pop() {
1454 Some(ProofTrieBranchChild::RlpNode(_)) => {
1455 unreachable!("local root cannot be an encoded RLP node")
1456 }
1457 root_node => root_node,
1458 };
1459
1460 let Some(parent_prefix) = sub_trie_targets.parent_prefix else {
1463 let root_node = if let Some(root_node) = root_node {
1464 self.rlp_encode_buf.clear();
1465 root_node.into_proof_trie_node(Nibbles::new(), &mut self.rlp_encode_buf)?
1466 } else {
1467 ProofTrieNodeV2::empty()
1468 };
1469 self.retained_proofs.push(root_node);
1470 return Ok(())
1471 };
1472
1473 let Some(mut root_node) = root_node else { return Ok(()) };
1475
1476 let root_short_key = *root_node.short_key();
1477
1478 if root_short_key == parent_prefix {
1481 return Ok(())
1482 }
1483
1484 if !root_short_key.starts_with(&parent_prefix) {
1490 return Err(StateProofError::TrieInconsistency(format!(
1491 "local root short key {root_short_key:?} does not start with parent prefix \
1492 {parent_prefix:?}",
1493 )))
1494 }
1495
1496 let child_path_len = parent_prefix.len() + 1;
1499 let child_path = root_short_key.slice_unchecked(0, child_path_len);
1500
1501 if !sub_trie_targets
1503 .targets
1504 .iter()
1505 .any(|target| target.key_nibbles.starts_with(&child_path))
1506 {
1507 return Ok(())
1508 }
1509
1510 root_node.trim_short_key_prefix(child_path_len);
1512 self.rlp_encode_buf.clear();
1513 let root_node = root_node.into_proof_trie_node(child_path, &mut self.rlp_encode_buf)?;
1514 self.retained_proofs.push(root_node);
1515
1516 Ok(())
1517 }
1518
1519 fn clear_computation_state(&mut self) {
1522 self.branch_stack.clear();
1523 self.branch_path = Nibbles::new();
1524 self.child_stack.clear();
1525 self.cached_branch_stack.clear();
1526 self.retained_proofs.clear();
1527 }
1528
1529 fn proof_inner(
1532 &mut self,
1533 value_encoder: &mut VE,
1534 targets: &mut [ProofV2Target],
1535 ) -> Result<Vec<ProofTrieNodeV2>, StateProofError> {
1536 if targets.is_empty() {
1538 trace!(target: TRACE_TARGET, "Empty targets, returning");
1539 return Ok(Vec::new())
1540 }
1541
1542 let mut trie_cursor_state = TrieCursorState::unseeked();
1545 let mut hashed_cursor_state = HashedCursorState::unseeked();
1546 let mut previous_traversal_bounds: Option<(Nibbles, Option<Nibbles>)> = None;
1547
1548 for sub_trie_targets in iter_sub_trie_targets(targets) {
1551 let traversal_lower_bound = sub_trie_targets.lower_bound;
1552 let traversal_upper_bound = sub_trie_targets.upper_bound;
1553 if previous_traversal_bounds.is_some_and(|(_, previous_upper_bound)| {
1554 previous_upper_bound.is_none_or(|upper_bound| upper_bound > traversal_lower_bound)
1555 }) {
1556 if trie_cursor_state.needs_reset_before_seek(&traversal_lower_bound) {
1557 trace!(
1558 target: TRACE_TARGET,
1559 ?previous_traversal_bounds,
1560 ?traversal_lower_bound,
1561 ?traversal_upper_bound,
1562 "Resetting trie cursor before overlapping or backward traversal range",
1563 );
1564 self.trie_cursor.reset();
1565 trie_cursor_state = TrieCursorState::unseeked();
1566 }
1567 if hashed_cursor_state.needs_reset_before_seek(&traversal_lower_bound) {
1568 trace!(
1569 target: TRACE_TARGET,
1570 ?previous_traversal_bounds,
1571 ?traversal_lower_bound,
1572 ?traversal_upper_bound,
1573 "Resetting hashed cursor before overlapping or backward traversal range",
1574 );
1575 self.hashed_cursor.reset();
1576 hashed_cursor_state = HashedCursorState::unseeked();
1577 }
1578 }
1579
1580 if let Err(err) = self.proof_subtrie(
1581 value_encoder,
1582 &mut trie_cursor_state,
1583 &mut hashed_cursor_state,
1584 sub_trie_targets,
1585 ) {
1586 self.clear_computation_state();
1587 return Err(err);
1588 }
1589
1590 previous_traversal_bounds = Some((traversal_lower_bound, traversal_upper_bound));
1591 }
1592
1593 trace!(
1594 target: TRACE_TARGET,
1595 retained_proofs_len = ?self.retained_proofs.len(),
1596 "proof_inner: returning",
1597 );
1598 self.retained_proofs.sort_unstable_by(|a, b| depth_first::cmp(&a.path, &b.path));
1599 self.retained_proofs.dedup_by(|a, b| a.path == b.path);
1600 Ok(core::mem::take(&mut self.retained_proofs))
1601 }
1602
1603 #[instrument(target = TRACE_TARGET, level = "trace", skip_all)]
1612 pub fn proof(
1613 &mut self,
1614 value_encoder: &mut VE,
1615 targets: &mut [ProofV2Target],
1616 ) -> Result<Vec<ProofTrieNodeV2>, StateProofError> {
1617 self.trie_cursor.reset();
1618 self.hashed_cursor.reset();
1619 self.proof_inner(value_encoder, targets)
1620 }
1621
1622 pub fn compute_root_hash(
1629 &mut self,
1630 proof_nodes: &[ProofTrieNodeV2],
1631 ) -> Result<Option<B256>, StateProofError> {
1632 let root_node = proof_nodes.iter().find(|node| node.path.is_empty());
1634
1635 let Some(root) = root_node else {
1636 return Ok(None);
1637 };
1638
1639 self.rlp_encode_buf.clear();
1641 root.node.encode(&mut self.rlp_encode_buf);
1642 let root_hash = keccak256(&self.rlp_encode_buf);
1643
1644 Ok(Some(root_hash))
1645 }
1646
1647 #[instrument(target = TRACE_TARGET, level = "trace", skip(self, value_encoder))]
1652 pub fn root_node(
1653 &mut self,
1654 value_encoder: &mut VE,
1655 ) -> Result<ProofTrieNodeV2, StateProofError> {
1656 let mut trie_cursor_state = TrieCursorState::unseeked();
1659 let mut hashed_cursor_state = HashedCursorState::unseeked();
1660
1661 static EMPTY_TARGETS: [ProofV2Target; 0] = [];
1662 let sub_trie_targets = SubTrieTargets {
1663 lower_bound: Nibbles::new(),
1664 upper_bound: None,
1665 parent_prefix: None,
1666 targets: &EMPTY_TARGETS,
1667 };
1668
1669 if let Err(err) = self.proof_subtrie(
1670 value_encoder,
1671 &mut trie_cursor_state,
1672 &mut hashed_cursor_state,
1673 sub_trie_targets,
1674 ) {
1675 self.clear_computation_state();
1676 return Err(err);
1677 }
1678
1679 let mut proofs = core::mem::take(&mut self.retained_proofs);
1682 trace!(
1683 target: TRACE_TARGET,
1684 proofs_len = ?proofs.len(),
1685 "root_node: extracting root",
1686 );
1687
1688 debug_assert_eq!(
1691 proofs.len(), 1,
1692 "prefix is empty, parent path is None, and targets is empty, so there must be only the root node"
1693 );
1694
1695 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");
1697
1698 Ok(root_node)
1699 }
1700}
1701
1702pub type StorageProofCalculator<TC, HC> = ProofCalculator<TC, HC, StorageValueEncoder>;
1704
1705impl<TC, HC> StorageProofCalculator<TC, HC>
1706where
1707 TC: TrieStorageCursor,
1708 HC: HashedStorageCursor<Value = U256>,
1709{
1710 pub fn new_storage(trie_cursor: TC, hashed_cursor: HC) -> Self {
1712 Self::new(trie_cursor, hashed_cursor)
1713 }
1714
1715 #[instrument(target = TRACE_TARGET, level = "trace", skip(self, targets))]
1724 pub fn storage_proof(
1725 &mut self,
1726 hashed_address: B256,
1727 targets: &mut [ProofV2Target],
1728 ) -> Result<Vec<ProofTrieNodeV2>, StateProofError> {
1729 self.hashed_cursor.set_hashed_address(hashed_address);
1730
1731 if self.hashed_cursor.is_storage_empty()? {
1733 return Ok(if targets.iter().any(|target| !target.parent.is_known()) {
1734 vec![ProofTrieNodeV2 {
1735 path: Nibbles::default(),
1736 node: TrieNodeV2::EmptyRoot,
1737 masks: None,
1738 }]
1739 } else {
1740 Vec::new()
1741 })
1742 }
1743
1744 self.trie_cursor.set_hashed_address(hashed_address);
1747
1748 let mut storage_value_encoder = StorageValueEncoder;
1750 self.proof_inner(&mut storage_value_encoder, targets)
1751 }
1752
1753 #[instrument(target = TRACE_TARGET, level = "trace", skip(self))]
1758 pub fn storage_root_node(
1759 &mut self,
1760 hashed_address: B256,
1761 ) -> Result<ProofTrieNodeV2, StateProofError> {
1762 self.hashed_cursor.set_hashed_address(hashed_address);
1763
1764 if self.hashed_cursor.is_storage_empty()? {
1765 return Ok(ProofTrieNodeV2 {
1766 path: Nibbles::default(),
1767 node: TrieNodeV2::EmptyRoot,
1768 masks: None,
1769 })
1770 }
1771
1772 self.trie_cursor.set_hashed_address(hashed_address);
1775
1776 let mut storage_value_encoder = StorageValueEncoder;
1778 self.root_node(&mut storage_value_encoder)
1779 }
1780}
1781
1782struct TargetsCursor<'a> {
1788 targets: &'a [ProofV2Target],
1789 i: usize,
1790}
1791
1792impl<'a> TargetsCursor<'a> {
1793 fn new(targets: &'a [ProofV2Target]) -> Self {
1799 debug_assert!(!targets.is_empty());
1800 Self { targets, i: 0 }
1801 }
1802
1803 fn current(&self) -> (&'a ProofV2Target, Option<&'a ProofV2Target>) {
1805 (&self.targets[self.i], self.targets.get(self.i + 1))
1806 }
1807
1808 fn next(&mut self) -> (&'a ProofV2Target, Option<&'a ProofV2Target>) {
1814 self.i += 1;
1815 debug_assert!(self.i < self.targets.len());
1816 self.current()
1817 }
1818
1819 fn skip_iter(&self) -> impl Iterator<Item = &'a ProofV2Target> {
1821 self.targets[self.i + 1..].iter()
1822 }
1823
1824 fn rev_iter(&self) -> impl Iterator<Item = &'a ProofV2Target> {
1827 self.targets[..self.i].iter().rev()
1828 }
1829}
1830
1831#[derive(Debug)]
1834enum TrieCursorState {
1835 Unseeked,
1837 Available(Nibbles, BranchNodeCompact),
1839 Taken(Nibbles),
1841 Exhausted(Nibbles),
1843}
1844
1845impl TrieCursorState {
1846 const fn unseeked() -> Self {
1848 Self::Unseeked
1849 }
1850
1851 fn seeked(key: Nibbles, entry: Option<(Nibbles, BranchNodeCompact)>) -> Self {
1853 entry.map_or(Self::Exhausted(key), |(path, node)| Self::Available(path, node))
1854 }
1855
1856 const fn path(&self) -> Option<&Nibbles> {
1862 match self {
1863 Self::Unseeked => panic!("cursor is unseeked"),
1864 Self::Available(path, _) | Self::Taken(path) => Some(path),
1865 Self::Exhausted(_) => None,
1866 }
1867 }
1868
1869 fn needs_seek_to(&self, path: &Nibbles) -> bool {
1871 match self {
1872 Self::Unseeked | Self::Taken(_) => true,
1873 Self::Available(current_path, _) => current_path < path,
1874 Self::Exhausted(_) => false,
1875 }
1876 }
1877
1878 fn needs_reset_before_seek(&self, key: &Nibbles) -> bool {
1880 match self {
1881 Self::Unseeked => false,
1882 Self::Available(path, _) | Self::Taken(path) => path > key,
1883 Self::Exhausted(exhausted_at) => exhausted_at > key,
1884 }
1885 }
1886
1887 fn take(&mut self) -> (Nibbles, BranchNodeCompact) {
1889 let Self::Available(path, _) = self else {
1890 panic!("take called on non-Available: {self:?}")
1891 };
1892
1893 let path = *path;
1894 let Self::Available(path, node) = core::mem::replace(self, Self::Taken(path)) else {
1895 unreachable!("already checked that self is Self::Available");
1896 };
1897
1898 (path, node)
1899 }
1900}
1901
1902enum HashedCursorState<V> {
1904 Unseeked,
1906 Available(Nibbles, V),
1908 Exhausted(Nibbles),
1910}
1911
1912impl<V> HashedCursorState<V> {
1913 const fn unseeked() -> Self {
1915 Self::Unseeked
1916 }
1917
1918 fn seeked(key: Nibbles, entry: Option<(Nibbles, V)>) -> Self {
1920 entry.map_or(Self::Exhausted(key), |(path, value)| Self::Available(path, value))
1921 }
1922
1923 const fn path(&self) -> Option<&Nibbles> {
1925 match self {
1926 Self::Available(path, _) => Some(path),
1927 Self::Unseeked | Self::Exhausted(_) => None,
1928 }
1929 }
1930
1931 fn needs_seek_to(&self, key: &Nibbles) -> bool {
1933 match self {
1934 Self::Unseeked => true,
1935 Self::Available(path, _) => path < key,
1936 Self::Exhausted(exhausted_at) => exhausted_at > key,
1937 }
1938 }
1939
1940 fn needs_reset_before_seek(&self, key: &Nibbles) -> bool {
1942 match self {
1943 Self::Unseeked => false,
1944 Self::Available(path, _) => path > key,
1945 Self::Exhausted(exhausted_at) => exhausted_at > key,
1946 }
1947 }
1948
1949 fn take(&mut self) -> (Nibbles, V) {
1951 match core::mem::replace(self, Self::Unseeked) {
1952 Self::Available(path, value) => (path, value),
1953 _ => panic!("take called on non-Available hashed cursor state"),
1954 }
1955 }
1956}
1957
1958enum PopCachedBranchOutcome {
1960 Popped((Nibbles, BranchNodeCompact)),
1962 Exhausted,
1964 CalculateLeaves((Nibbles, Option<Nibbles>)),
1967}
1968
1969#[cfg(test)]
1970mod tests {
1971 use super::*;
1972 use crate::{
1973 hashed_cursor::{mock::MockHashedCursorFactory, HashedCursorFactory},
1974 proof::StorageProof as LegacyStorageProof,
1975 test_utils::TrieTestHarness,
1976 trie_cursor::{depth_first, TrieCursorFactory},
1977 };
1978 use alloy_primitives::map::B256Set;
1979 use alloy_rlp::Decodable;
1980 use alloy_trie::proof::AddedRemovedKeys;
1981 use itertools::Itertools;
1982 use reth_trie_common::{
1983 prefix_set::PrefixSetMut, ProofTrieNode, ProofV2TargetParent, TrieNode, EMPTY_ROOT_HASH,
1984 };
1985 use std::collections::BTreeMap;
1986
1987 fn convert_legacy_proofs_to_v2(legacy_proofs: &[ProofTrieNode]) -> Vec<ProofTrieNodeV2> {
2000 ProofTrieNodeV2::from_sorted_trie_nodes(
2001 legacy_proofs.iter().map(|p| (p.path, p.node.clone(), p.masks)),
2002 )
2003 }
2004
2005 fn project_legacy_proof_node(
2007 node: &ProofTrieNodeV2,
2008 target: &ProofV2Target,
2009 ) -> Option<ProofTrieNodeV2> {
2010 let Some(parent_path_len) = target.parent.path_len() else {
2011 return target.key_nibbles.starts_with(&node.path).then(|| node.clone())
2012 };
2013
2014 if node.path.len() > parent_path_len {
2015 return target.key_nibbles.starts_with(&node.path).then(|| node.clone())
2016 }
2017
2018 let logical_path = match &node.node {
2019 TrieNodeV2::Leaf(leaf) => node.path.join(&leaf.key),
2020 TrieNodeV2::Branch(branch) => node.path.join(&branch.key),
2021 TrieNodeV2::EmptyRoot | TrieNodeV2::Extension(_) => return None,
2022 };
2023 let child_path_len = parent_path_len + 1;
2024 if logical_path.len() < child_path_len {
2025 return None
2026 }
2027
2028 let child_path = logical_path.slice(0..child_path_len);
2029 if !target.key_nibbles.starts_with(&child_path) {
2030 return None
2031 }
2032
2033 let trim_len = child_path_len - node.path.len();
2034 let mut projected = node.clone();
2035 projected.path = child_path;
2036 match &mut projected.node {
2037 TrieNodeV2::Leaf(leaf) => leaf.key = leaf.key.slice(trim_len..),
2038 TrieNodeV2::Branch(branch) => {
2039 branch.key = branch.key.slice(trim_len..);
2040 if branch.key.is_empty() {
2041 branch.branch_rlp_node = None;
2042 }
2043 }
2044 TrieNodeV2::EmptyRoot | TrieNodeV2::Extension(_) => unreachable!(),
2045 }
2046 Some(projected)
2047 }
2048
2049 fn project_legacy_proof(
2051 legacy_nodes: &[ProofTrieNodeV2],
2052 targets: &[ProofV2Target],
2053 ) -> Vec<ProofTrieNodeV2> {
2054 let mut projected = targets
2055 .iter()
2056 .flat_map(|target| {
2057 legacy_nodes.iter().filter_map(move |node| project_legacy_proof_node(node, target))
2058 })
2059 .collect::<Vec<_>>();
2060 projected.sort_unstable_by(|a, b| depth_first::cmp(&a.path, &b.path));
2061 projected.dedup_by(|a, b| {
2062 if a.path != b.path {
2063 return false
2064 }
2065 assert_eq!(a, b, "target projections disagree at path {:?}", a.path);
2066 true
2067 });
2068 projected
2069 }
2070
2071 struct ProofTestHarness {
2077 inner: TrieTestHarness,
2078 }
2079
2080 impl std::ops::Deref for ProofTestHarness {
2081 type Target = TrieTestHarness;
2082 fn deref(&self) -> &Self::Target {
2083 &self.inner
2084 }
2085 }
2086
2087 impl ProofTestHarness {
2088 fn new(storage: BTreeMap<B256, U256>) -> Self {
2090 Self { inner: TrieTestHarness::new(storage) }
2091 }
2092
2093 fn assert_proof(
2096 &self,
2097 targets: impl IntoIterator<Item = ProofV2Target>,
2098 ) -> Result<(), StateProofError> {
2099 let mut targets_vec = targets.into_iter().collect::<Vec<_>>();
2100
2101 let (proof_v2_result, root_hash) = self.proof_v2(&mut targets_vec);
2103
2104 if let Some(root_hash) = root_hash {
2107 pretty_assertions::assert_eq!(self.original_root(), root_hash);
2108 }
2109
2110 let legacy_targets = targets_vec
2113 .iter()
2114 .map(|target| B256::from_slice(&target.key_nibbles.pack()))
2115 .chain(self.storage().keys().copied())
2116 .collect::<B256Set>();
2117
2118 let proof_legacy_result = LegacyStorageProof::new_hashed(
2120 self.trie_cursor_factory(),
2121 self.hashed_cursor_factory(),
2122 self.hashed_address(),
2123 )
2124 .with_branch_node_masks(true)
2125 .with_added_removed_keys(Some(AddedRemovedKeys::default().with_assume_added(true)))
2126 .storage_multiproof(legacy_targets)?;
2127
2128 let proof_legacy_nodes = proof_legacy_result
2130 .subtree
2131 .iter()
2132 .map(|(path, node_enc)| {
2133 let mut buf = node_enc.as_ref();
2134 let node = TrieNode::decode(&mut buf)
2135 .expect("legacy implementation should not produce malformed proof nodes");
2136
2137 let masks = if path.is_empty() {
2138 None
2139 } else {
2140 proof_legacy_result.branch_node_masks.get(path).copied()
2141 };
2142
2143 ProofTrieNode { path: *path, node, masks }
2144 })
2145 .sorted_by(|a, b| depth_first::cmp(&a.path, &b.path))
2146 .collect::<Vec<_>>();
2147
2148 let all_legacy_nodes_v2 = convert_legacy_proofs_to_v2(&proof_legacy_nodes);
2150
2151 let expected_v2 = project_legacy_proof(&all_legacy_nodes_v2, &targets_vec);
2152 pretty_assertions::assert_eq!(expected_v2, proof_v2_result);
2153
2154 Ok(())
2155 }
2156 }
2157
2158 #[test]
2163 fn test_proof_calculator_reuse_after_error() {
2164 reth_tracing::init_test_tracing();
2165
2166 let slots = [
2167 B256::right_padding_from(&[0x10]),
2168 B256::right_padding_from(&[0x20]),
2169 B256::right_padding_from(&[0x30]),
2170 B256::right_padding_from(&[0x40]),
2171 ];
2172 let storage: BTreeMap<B256, U256> =
2173 slots.iter().map(|&s| (s, U256::from(100u64))).collect();
2174
2175 let harness = ProofTestHarness::new(storage);
2176
2177 let trie_cursor_factory = harness.trie_cursor_factory();
2178 let hashed_cursor_factory = harness.hashed_cursor_factory();
2179
2180 let hashed_address = harness.hashed_address();
2181 let trie_cursor = trie_cursor_factory.storage_trie_cursor(hashed_address).unwrap();
2182 let hashed_cursor = hashed_cursor_factory.hashed_storage_cursor(hashed_address).unwrap();
2183 let mut proof_calculator = StorageProofCalculator::new_storage(trie_cursor, hashed_cursor);
2184
2185 proof_calculator.branch_stack.push(ProofTrieBranch {
2188 ext_len: 2,
2189 state_mask: TrieMask::new(0b1111),
2190 masks: None,
2191 });
2192 proof_calculator.branch_stack.push(ProofTrieBranch {
2193 ext_len: 0,
2194 state_mask: TrieMask::new(0b11),
2195 masks: None,
2196 });
2197 proof_calculator
2198 .child_stack
2199 .push(ProofTrieBranchChild::RlpNode(RlpNode::word_rlp(&B256::ZERO)));
2200 proof_calculator.branch_path = Nibbles::from_nibbles([0x1, 0x2, 0x3]);
2201
2202 proof_calculator.clear_computation_state();
2204
2205 let mut sorted_slots = slots.to_vec();
2206 sorted_slots.sort();
2207 let mut targets: Vec<ProofV2Target> =
2208 sorted_slots.iter().copied().map(ProofV2Target::new).collect();
2209
2210 let result = proof_calculator.storage_proof(hashed_address, &mut targets).unwrap();
2211
2212 let trie_cursor = trie_cursor_factory.storage_trie_cursor(hashed_address).unwrap();
2214 let hashed_cursor = hashed_cursor_factory.hashed_storage_cursor(hashed_address).unwrap();
2215 let mut fresh_calculator = StorageProofCalculator::new_storage(trie_cursor, hashed_cursor);
2216 let fresh_result = fresh_calculator.storage_proof(hashed_address, &mut targets).unwrap();
2217
2218 pretty_assertions::assert_eq!(fresh_result, result);
2219 }
2220
2221 #[test]
2222 fn test_partial_storage_proof_after_root_calculation() {
2223 let slot_a = B256::right_padding_from(&[0xae, 0xd4, 0x00]);
2224 let slot_b = B256::right_padding_from(&[0xae, 0xd4, 0x10]);
2225 let harness = ProofTestHarness::new(BTreeMap::from([
2226 (slot_a, U256::from(1)),
2227 (slot_b, U256::from(2)),
2228 ]));
2229 let hashed_address = harness.hashed_address();
2230 let trie_cursor =
2231 harness.trie_cursor_factory().storage_trie_cursor(hashed_address).unwrap();
2232 let hashed_cursor =
2233 harness.hashed_cursor_factory().hashed_storage_cursor(hashed_address).unwrap();
2234 let mut calculator = StorageProofCalculator::new_storage(trie_cursor, hashed_cursor);
2235
2236 let root_node = calculator.storage_root_node(hashed_address).unwrap();
2237 assert_eq!(
2238 calculator.compute_root_hash(core::slice::from_ref(&root_node)).unwrap(),
2239 Some(harness.original_root())
2240 );
2241
2242 let target = ProofV2Target::new(slot_a).with_parent(ProofV2TargetParent::new(3));
2243 let mut actual_targets = [target];
2244 let actual = calculator.storage_proof(hashed_address, &mut actual_targets).unwrap();
2245 let mut expected_targets = [target];
2246 let (expected, root) = harness.proof_v2(&mut expected_targets);
2247
2248 assert!(root.is_none());
2249 pretty_assertions::assert_eq!(expected, actual);
2250 }
2251
2252 mod proptest_tests {
2253 use super::*;
2254 use proptest::prelude::*;
2255
2256 fn storage_strategy() -> impl Strategy<Value = BTreeMap<B256, U256>> {
2258 prop::collection::vec((any::<[u8; 32]>(), any::<u64>()), 0..=100).prop_map(|slots| {
2259 slots
2260 .into_iter()
2261 .map(|(slot_bytes, value)| (B256::from(slot_bytes), U256::from(value)))
2262 .filter(|(_, v)| *v != U256::ZERO)
2263 .collect()
2264 })
2265 }
2266
2267 fn proof_targets_strategy(
2270 slot_keys: Vec<B256>,
2271 ) -> impl Strategy<Value = Vec<ProofV2Target>> {
2272 let num_slots = slot_keys.len();
2273
2274 let target_count = 0..=(num_slots + 5);
2275
2276 target_count.prop_flat_map(move |count| {
2277 let slot_keys = slot_keys.clone();
2278 prop::collection::vec(
2279 (
2280 prop::bool::weighted(0.8).prop_flat_map(move |from_slots| {
2281 if from_slots && !slot_keys.is_empty() {
2282 prop::sample::select(slot_keys.clone()).boxed()
2283 } else {
2284 any::<[u8; 32]>().prop_map(B256::from).boxed()
2285 }
2286 }),
2287 0u8..16u8,
2288 )
2289 .prop_map(|(key, encoded_parent_path_len)| {
2290 let parent = encoded_parent_path_len.checked_sub(1).map_or(
2291 ProofV2TargetParent::NONE,
2292 |parent_path_len| {
2293 ProofV2TargetParent::new(usize::from(parent_path_len))
2294 },
2295 );
2296 ProofV2Target::new(key).with_parent(parent)
2297 }),
2298 count,
2299 )
2300 })
2301 }
2302
2303 proptest! {
2304 #![proptest_config(ProptestConfig::with_cases(4000))]
2305 #[test]
2306 fn proptest_proof_with_targets(
2309 (storage, targets) in storage_strategy()
2310 .prop_flat_map(|storage| {
2311 let mut slot_keys: Vec<B256> = storage.keys().copied().collect();
2312 slot_keys.sort_unstable();
2313 let targets_strategy = proof_targets_strategy(slot_keys);
2314 (Just(storage), targets_strategy)
2315 })
2316 ) {
2317 reth_tracing::init_test_tracing();
2318 let harness = ProofTestHarness::new(storage);
2319
2320 harness.assert_proof(targets).expect("Proof generation failed");
2321 }
2322 }
2323 }
2324
2325 #[test]
2326 fn test_exact_subtrie_targets_with_root_target() {
2327 reth_tracing::init_test_tracing();
2328
2329 let slot_80 = B256::right_padding_from(&[0x80]);
2330 let slot_82 = B256::right_padding_from(&[0x82]);
2331 let slot_f0 = B256::right_padding_from(&[0xf0]);
2332 let storage = BTreeMap::from([
2333 (slot_80, U256::from(1)),
2334 (slot_82, U256::from(2)),
2335 (slot_f0, U256::from(3)),
2336 ]);
2337 let targets = [
2338 ProofV2Target::new(B256::ZERO),
2339 ProofV2Target::new(slot_80).with_parent(ProofV2TargetParent::new(1)),
2340 ];
2341
2342 let harness = ProofTestHarness::new(storage);
2343 harness.assert_proof(targets).expect("Proof generation failed");
2344 }
2345
2346 #[test]
2347 fn test_rebases_singleton_subtrie_root_below_known_parent() {
2348 let slot = B256::right_padding_from(&[0xae, 0xd4, 0x09]);
2349 let slot_nibbles = Nibbles::unpack(slot);
2350 let harness = ProofTestHarness::new(BTreeMap::from([(slot, U256::from(1))]));
2351 let mut targets = [ProofV2Target::new(slot).with_parent(ProofV2TargetParent::new(3))];
2352
2353 let (proof, root) = harness.proof_v2(&mut targets);
2354
2355 assert!(root.is_none());
2356 assert_eq!(proof.len(), 1);
2357 assert_eq!(proof[0].path, slot_nibbles.slice(0..4));
2358 let TrieNodeV2::Leaf(leaf) = &proof[0].node else {
2359 panic!("singleton subtrie root should remain a leaf")
2360 };
2361 assert_eq!(leaf.key, slot_nibbles.slice(4..));
2362 }
2363
2364 #[test]
2365 fn test_rebases_singleton_leaf_at_max_parent_depth() {
2366 let slot = B256::repeat_byte(0xae);
2367 let slot_nibbles = Nibbles::unpack(slot);
2368 let harness = ProofTestHarness::new(BTreeMap::from([(slot, U256::from(1))]));
2369 let mut targets = [ProofV2Target::new(slot).with_parent(ProofV2TargetParent::new(63))];
2370
2371 let (proof, root) = harness.proof_v2(&mut targets);
2372
2373 assert!(root.is_none());
2374 assert_eq!(proof.len(), 1);
2375 assert_eq!(proof[0].path, slot_nibbles);
2376 let TrieNodeV2::Leaf(leaf) = &proof[0].node else {
2377 panic!("singleton subtrie root should remain a leaf")
2378 };
2379 assert!(leaf.key.is_empty());
2380 }
2381
2382 #[test]
2383 fn test_root_and_root_parent_targets_retain_both_singleton_representations() {
2384 let slot = B256::right_padding_from(&[0x20]);
2385 let slot_nibbles = Nibbles::unpack(slot);
2386 let harness = ProofTestHarness::new(BTreeMap::from([(slot, U256::from(1))]));
2387 let mut targets = [
2388 ProofV2Target::new(slot),
2389 ProofV2Target::new(slot).with_parent(ProofV2TargetParent::new(0)),
2390 ];
2391
2392 let (proof, root) = harness.proof_v2(&mut targets);
2393
2394 assert_eq!(root, Some(harness.original_root()));
2395 let root_node = proof.iter().find(|node| node.path.is_empty()).expect("root proof");
2396 let TrieNodeV2::Leaf(root_leaf) = &root_node.node else { panic!("root should be a leaf") };
2397 assert_eq!(root_leaf.key, slot_nibbles);
2398
2399 let child_path = slot_nibbles.slice(0..1);
2400 let child_node =
2401 proof.iter().find(|node| node.path == child_path).expect("rebased root child proof");
2402 let TrieNodeV2::Leaf(child_leaf) = &child_node.node else {
2403 panic!("root child should be a leaf")
2404 };
2405 assert_eq!(child_leaf.key, slot_nibbles.slice(1..));
2406 }
2407
2408 #[test]
2409 fn test_rebases_compressed_branch_subtrie_root() {
2410 let slot_a = B256::right_padding_from(&[0xae, 0xd4, 0x00]);
2411 let slot_b = B256::right_padding_from(&[0xae, 0xd4, 0x10]);
2412 let slot_nibbles = Nibbles::unpack(slot_a);
2413 let harness = ProofTestHarness::new(BTreeMap::from([
2414 (slot_a, U256::from(1)),
2415 (slot_b, U256::from(2)),
2416 ]));
2417 let mut targets = [ProofV2Target::new(slot_a).with_parent(ProofV2TargetParent::new(3))];
2418
2419 let (proof, root) = harness.proof_v2(&mut targets);
2420
2421 assert!(root.is_none());
2422 let branch_path = slot_nibbles.slice(0..4);
2423 let branch_node =
2424 proof.iter().find(|node| node.path == branch_path).expect("rebased compressed branch");
2425 let TrieNodeV2::Branch(branch) = &branch_node.node else {
2426 panic!("rebased node should be a branch")
2427 };
2428 assert!(branch.key.is_empty());
2429 assert!(branch.branch_rlp_node.is_none());
2430 }
2431
2432 #[test]
2433 fn test_discards_reconstructed_known_parent_branch() {
2434 let slot_a = B256::right_padding_from(&[0xae, 0xd2]);
2435 let slot_b = B256::right_padding_from(&[0xae, 0xd4]);
2436 let slot_nibbles = Nibbles::unpack(slot_a);
2437 let harness = ProofTestHarness::new(BTreeMap::from([
2438 (slot_a, U256::from(1)),
2439 (slot_b, U256::from(2)),
2440 ]));
2441 let mut targets = [ProofV2Target::new(slot_a).with_parent(ProofV2TargetParent::new(3))];
2442
2443 let (proof, root) = harness.proof_v2(&mut targets);
2444
2445 assert!(root.is_none());
2446 assert!(!proof.iter().any(|node| node.path == slot_nibbles.slice(0..3)));
2447 assert!(proof.iter().any(|node| node.path == slot_nibbles.slice(0..4)));
2448 }
2449
2450 #[test]
2451 fn test_rebased_root_matches_direct_child_not_full_short_key() {
2452 let stored_slot = B256::right_padding_from(&[0xae, 0xd4, 0x09]);
2453 let same_child_target = B256::right_padding_from(&[0xae, 0xd4, 0xff]);
2454 let other_child_target = B256::right_padding_from(&[0xae, 0xd5]);
2455 let harness = ProofTestHarness::new(BTreeMap::from([(stored_slot, U256::from(1))]));
2456
2457 let mut same_child =
2458 [ProofV2Target::new(same_child_target).with_parent(ProofV2TargetParent::new(3))];
2459 let (proof, _) = harness.proof_v2(&mut same_child);
2460 assert_eq!(proof.len(), 1, "divergent leaf proves absence below the same child");
2461
2462 let mut other_child =
2463 [ProofV2Target::new(other_child_target).with_parent(ProofV2TargetParent::new(3))];
2464 let (proof, _) = harness.proof_v2(&mut other_child);
2465 assert!(proof.is_empty(), "a different direct child is unrelated to the target");
2466 }
2467
2468 #[test]
2469 fn test_known_parent_sibling_span_retains_only_target_children() {
2470 let stored_slot_a = B256::right_padding_from(&[0xea, 0x53]);
2471 let stored_slot_b = B256::right_padding_from(&[0xeb, 0x53]);
2472 let stored_slot_c = B256::right_padding_from(&[0xec, 0x53]);
2473 let target_a = B256::right_padding_from(&[0xea, 0x1f]);
2474 let target_c = B256::right_padding_from(&[0xec, 0x1f]);
2475 let harness = ProofTestHarness::new(BTreeMap::from([
2476 (stored_slot_a, U256::from(1)),
2477 (stored_slot_b, U256::from(2)),
2478 (stored_slot_c, U256::from(3)),
2479 ]));
2480 let mut targets = [target_a, target_c]
2481 .map(|target| ProofV2Target::new(target).with_parent(ProofV2TargetParent::new(1)));
2482
2483 let (proof, root) = harness.proof_v2(&mut targets);
2484
2485 assert!(root.is_none());
2486 assert_eq!(
2487 proof.iter().map(|node| node.path).collect::<Vec<_>>(),
2488 [Nibbles::from_nibbles([0xe, 0xa]), Nibbles::from_nibbles([0xe, 0xc])]
2489 );
2490 }
2491
2492 #[test]
2493 fn test_known_parent_does_not_use_stale_parent_mask() {
2494 let stored_slot_a = B256::right_padding_from(&[0xea, 0x53]);
2495 let stored_slot = B256::right_padding_from(&[0xeb, 0x53]);
2496 let stored_slot_c = B256::right_padding_from(&[0xec, 0x53]);
2497 let target = B256::right_padding_from(&[0xeb, 0x1f]);
2498 let stored_slot_nibbles = Nibbles::unpack(stored_slot);
2499
2500 let stale_parent_mask = TrieMask::new((1 << 0xa) | (1 << 0xc));
2504 let stale_parent = BranchNodeCompact::new(
2505 stale_parent_mask,
2506 TrieMask::new(0),
2507 TrieMask::new(0),
2508 Vec::new(),
2509 None,
2510 );
2511 let storage_nodes = BTreeMap::from([(Nibbles::from_nibbles([0xe]), stale_parent)]);
2512
2513 let mut harness = TrieTestHarness::new(BTreeMap::from([
2514 (stored_slot_a, U256::from(1)),
2515 (stored_slot, U256::from(2)),
2516 (stored_slot_c, U256::from(3)),
2517 ]));
2518 harness.set_trie_nodes(storage_nodes);
2519
2520 let mut targets = [ProofV2Target::new(target).with_parent(ProofV2TargetParent::new(1))];
2521 let (proof, root) = harness.proof_v2(&mut targets);
2522
2523 assert!(root.is_none());
2524 assert_eq!(proof.len(), 1);
2525 assert_eq!(proof[0].path, stored_slot_nibbles.slice(0..2));
2526 let TrieNodeV2::Leaf(leaf) = &proof[0].node else {
2527 panic!("live direct child should be reconstructed as a leaf")
2528 };
2529 assert_eq!(leaf.key, stored_slot_nibbles.slice(2..));
2530 }
2531
2532 #[test]
2533 fn test_empty_storage_respects_parent_context() {
2534 let harness = ProofTestHarness::new(BTreeMap::new());
2535 let slot = B256::ZERO;
2536
2537 let mut partial_target =
2538 [ProofV2Target::new(slot).with_parent(ProofV2TargetParent::new(0))];
2539 let (partial_proof, partial_root) = harness.proof_v2(&mut partial_target);
2540 assert!(partial_proof.is_empty());
2541 assert!(partial_root.is_none());
2542
2543 let mut root_target = [ProofV2Target::new(slot)];
2544 let (root_proof, root) = harness.proof_v2(&mut root_target);
2545 assert_eq!(root_proof.len(), 1);
2546 assert!(matches!(root_proof[0].node, TrieNodeV2::EmptyRoot));
2547 assert_eq!(root, Some(EMPTY_ROOT_HASH));
2548 }
2549
2550 #[test]
2551 fn test_big_trie() {
2552 use rand::prelude::*;
2553
2554 reth_tracing::init_test_tracing();
2555 let mut rng = rand::rngs::SmallRng::seed_from_u64(1);
2556
2557 let mut rand_b256 = || {
2558 let mut buf: [u8; 32] = [0; 32];
2559 rng.fill_bytes(&mut buf);
2560 B256::from_slice(&buf)
2561 };
2562
2563 let mut storage = BTreeMap::new();
2565 for _ in 0..10240 {
2566 let hashed_slot = rand_b256();
2567 storage.insert(hashed_slot, U256::from(1u64));
2568 }
2569
2570 let mut targets = storage.keys().copied().collect::<Vec<_>>();
2573 for _ in 0..storage.len() / 5 {
2574 targets.push(rand_b256());
2575 }
2576 targets.sort();
2577
2578 let harness = ProofTestHarness::new(storage);
2580
2581 harness
2582 .assert_proof(targets.into_iter().map(ProofV2Target::new))
2583 .expect("Proof generation failed");
2584 }
2585
2586 #[test]
2587 fn test_node_with_masked_empty_child() {
2588 reth_tracing::init_test_tracing();
2589
2590 let val = U256::from(42u64);
2591
2592 let slot_60 = B256::right_padding_from(&[0x60]);
2595 let slot_61 = B256::right_padding_from(&[0x61]);
2596 let slot_65 = B256::right_padding_from(&[0x65]);
2597 let slot_67 = B256::right_padding_from(&[0x67]);
2598
2599 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];
2605 let branch = BranchNodeCompact::new(state_mask, TrieMask::new(0), hash_mask, hashes, None);
2606
2607 let storage_nodes: BTreeMap<Nibbles, BranchNodeCompact> =
2608 std::iter::once((Nibbles::from_nibbles([0x6]), branch)).collect();
2609
2610 let mut harness = TrieTestHarness::new(
2613 [slot_60, slot_61, slot_65, slot_67].iter().map(|s| (*s, val)).collect(),
2614 );
2615 harness.set_trie_nodes(storage_nodes);
2616
2617 let storage_trie_cursor =
2618 harness.trie_cursor_factory().storage_trie_cursor(harness.hashed_address()).unwrap();
2619 let hashed_storage_cursor = harness
2620 .hashed_cursor_factory()
2621 .hashed_storage_cursor(harness.hashed_address())
2622 .unwrap();
2623 let mut calculator =
2624 StorageProofCalculator::new_storage(storage_trie_cursor, hashed_storage_cursor);
2625 let root_node = calculator
2626 .storage_root_node(harness.hashed_address())
2627 .expect("storage_root_node should succeed with masked empty child");
2628
2629 let root_hash = calculator.compute_root_hash(core::slice::from_ref(&root_node)).unwrap();
2630 assert!(root_hash.is_some(), "should produce a root hash");
2631 }
2632
2633 #[test]
2643 fn test_node_with_masked_empty_child_lower_bound_past_branch() {
2644 reth_tracing::init_test_tracing();
2645
2646 let val = U256::from(42u64);
2647
2648 let slot_60 = B256::right_padding_from(&[0x60]);
2650 let slot_61 = B256::right_padding_from(&[0x61]);
2651 let slot_6f = B256::right_padding_from(&[0x6f]);
2652 let slot_70 = B256::right_padding_from(&[0x70]);
2653
2654 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];
2660 let branch = BranchNodeCompact::new(state_mask, TrieMask::new(0), hash_mask, hashes, None);
2661
2662 let storage_nodes: BTreeMap<Nibbles, BranchNodeCompact> =
2663 std::iter::once((Nibbles::from_nibbles([0x6]), branch)).collect();
2664
2665 let mut harness = TrieTestHarness::new(
2667 [slot_60, slot_61, slot_6f, slot_70].iter().map(|s| (*s, val)).collect(),
2668 );
2669 harness.set_trie_nodes(storage_nodes);
2670
2671 let storage_trie_cursor =
2672 harness.trie_cursor_factory().storage_trie_cursor(harness.hashed_address()).unwrap();
2673 let hashed_storage_cursor = harness
2674 .hashed_cursor_factory()
2675 .hashed_storage_cursor(harness.hashed_address())
2676 .unwrap();
2677 let mut calculator =
2678 StorageProofCalculator::new_storage(storage_trie_cursor, hashed_storage_cursor);
2679 let root_node = calculator
2680 .storage_root_node(harness.hashed_address())
2681 .expect("storage_root_node should succeed when lower bound advances past branch");
2682
2683 let root_hash = calculator.compute_root_hash(core::slice::from_ref(&root_node)).unwrap();
2684 assert!(root_hash.is_some(), "should produce a root hash");
2685 }
2686
2687 #[test]
2697 fn test_prefix_set_adds_child_nibbles() {
2698 reth_tracing::init_test_tracing();
2699
2700 let val = U256::from(42u64);
2701 let slot_60 = B256::right_padding_from(&[0x60]);
2702 let slot_61 = B256::right_padding_from(&[0x61]);
2703 let slot_63 = B256::right_padding_from(&[0x63]);
2704
2705 let harness = TrieTestHarness::new([(slot_60, val), (slot_61, val)].into_iter().collect());
2706
2707 let changeset: BTreeMap<B256, U256> = std::iter::once((slot_63, val)).collect();
2708 let (expected_root, _) = harness.get_root_with_updates(&changeset);
2709
2710 let mut updated_storage = harness.storage().clone();
2711 updated_storage.insert(slot_63, val);
2712
2713 let updated_hashed = MockHashedCursorFactory::new(
2714 BTreeMap::new(),
2715 std::iter::once((harness.hashed_address(), updated_storage)).collect(),
2716 );
2717
2718 let mut prefix_set = PrefixSetMut::default();
2719 prefix_set.insert(Nibbles::unpack(slot_63));
2720
2721 let trie_cursor =
2722 harness.trie_cursor_factory().storage_trie_cursor(harness.hashed_address()).unwrap();
2723 let hashed_cursor = updated_hashed.hashed_storage_cursor(harness.hashed_address()).unwrap();
2724 let mut calculator = StorageProofCalculator::new_storage(trie_cursor, hashed_cursor)
2725 .with_prefix_set(prefix_set.freeze());
2726 let root_node = calculator
2727 .storage_root_node(harness.hashed_address())
2728 .expect("storage_root_node should succeed with prefix set adding child nibbles");
2729 let got_root =
2730 calculator.compute_root_hash(core::slice::from_ref(&root_node)).unwrap().unwrap();
2731
2732 pretty_assertions::assert_eq!(
2733 expected_root,
2734 got_root,
2735 "Root hash with prefix set should match fresh computation"
2736 );
2737 }
2738
2739 #[test]
2748 fn test_prefix_set_invalidates_cached_hash() {
2749 reth_tracing::init_test_tracing();
2750
2751 let original_val = U256::from(42u64);
2752 let updated_val = U256::from(9999u64);
2753 let slot_60 = B256::right_padding_from(&[0x60]);
2754 let slot_61 = B256::right_padding_from(&[0x61]);
2755 let slot_65 = B256::right_padding_from(&[0x65]);
2756
2757 let harness = TrieTestHarness::new(
2758 [(slot_60, original_val), (slot_61, original_val), (slot_65, original_val)]
2759 .into_iter()
2760 .collect(),
2761 );
2762
2763 let changeset: BTreeMap<B256, U256> = std::iter::once((slot_65, updated_val)).collect();
2764 let (expected_root, _) = harness.get_root_with_updates(&changeset);
2765
2766 let mut updated_storage = harness.storage().clone();
2767 updated_storage.insert(slot_65, updated_val);
2768
2769 let updated_hashed = MockHashedCursorFactory::new(
2770 BTreeMap::new(),
2771 std::iter::once((harness.hashed_address(), updated_storage)).collect(),
2772 );
2773
2774 let mut prefix_set = PrefixSetMut::default();
2775 prefix_set.insert(Nibbles::unpack(slot_65));
2776
2777 let trie_cursor =
2778 harness.trie_cursor_factory().storage_trie_cursor(harness.hashed_address()).unwrap();
2779 let hashed_cursor = updated_hashed.hashed_storage_cursor(harness.hashed_address()).unwrap();
2780 let mut calculator = StorageProofCalculator::new_storage(trie_cursor, hashed_cursor)
2781 .with_prefix_set(prefix_set.freeze());
2782 let root_node = calculator
2783 .storage_root_node(harness.hashed_address())
2784 .expect("storage_root_node should succeed with prefix set invalidating cached hash");
2785 let got_root =
2786 calculator.compute_root_hash(core::slice::from_ref(&root_node)).unwrap().unwrap();
2787
2788 pretty_assertions::assert_eq!(
2789 expected_root,
2790 got_root,
2791 "Root hash with prefix set invalidation should match fresh computation"
2792 );
2793 }
2794
2795 fn storage_leaf_hash(short_key: &Nibbles, value: &U256) -> B256 {
2798 let mut buf = Vec::new();
2799 alloy_trie::nodes::LeafNodeRef::new(short_key, &alloy_rlp::encode_fixed_size(value))
2800 .encode(&mut buf);
2801 keccak256(&buf)
2802 }
2803
2804 #[test]
2819 fn test_branch_collapse_removed_child_before_remaining() {
2820 reth_tracing::init_test_tracing();
2821
2822 let val = U256::from(1u64);
2823
2824 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);
2831 let leaf_hash_b = storage_leaf_hash(&Nibbles::unpack(key_b).slice(2..), &val);
2832
2833 let sub_branch_state_mask = TrieMask::new((1 << 0) | (1 << 1));
2836 let cached_sub_branch = BranchNodeCompact::new(
2837 sub_branch_state_mask,
2838 TrieMask::new(0),
2839 sub_branch_state_mask,
2840 vec![leaf_hash_a, leaf_hash_b],
2841 None,
2842 );
2843
2844 let storage_nodes: BTreeMap<Nibbles, BranchNodeCompact> =
2845 std::iter::once((Nibbles::from_nibbles([0x2]), cached_sub_branch)).collect();
2846
2847 let mut harness = TrieTestHarness::new([(key_b, val), (key_c, val)].into_iter().collect());
2850 harness.set_trie_nodes(storage_nodes);
2851
2852 let mut prefix_set_mut = PrefixSetMut::default();
2854 prefix_set_mut.insert(Nibbles::unpack(key_a));
2855 let prefix_set = prefix_set_mut.freeze();
2856
2857 let storage_trie_cursor =
2859 harness.trie_cursor_factory().storage_trie_cursor(harness.hashed_address()).unwrap();
2860 let hashed_storage_cursor = harness
2861 .hashed_cursor_factory()
2862 .hashed_storage_cursor(harness.hashed_address())
2863 .unwrap();
2864 let mut calculator =
2865 StorageProofCalculator::new_storage(storage_trie_cursor, hashed_storage_cursor)
2866 .with_prefix_set(prefix_set);
2867 let root_node = calculator
2868 .storage_root_node(harness.hashed_address())
2869 .expect("storage_root_node should succeed after branch collapse");
2870 let root_with_collapse =
2871 calculator.compute_root_hash(core::slice::from_ref(&root_node)).unwrap().unwrap();
2872
2873 let mut fresh_harness =
2875 TrieTestHarness::new([(key_b, val), (key_c, val)].into_iter().collect());
2876 fresh_harness.set_trie_nodes(BTreeMap::new());
2877 let storage_trie_cursor = fresh_harness
2878 .trie_cursor_factory()
2879 .storage_trie_cursor(fresh_harness.hashed_address())
2880 .unwrap();
2881 let hashed_storage_cursor = fresh_harness
2882 .hashed_cursor_factory()
2883 .hashed_storage_cursor(fresh_harness.hashed_address())
2884 .unwrap();
2885 let mut fresh_calculator =
2886 StorageProofCalculator::new_storage(storage_trie_cursor, hashed_storage_cursor);
2887 let fresh_root_node = fresh_calculator
2888 .storage_root_node(fresh_harness.hashed_address())
2889 .expect("fresh storage_root_node should succeed");
2890 let expected_root = fresh_calculator
2891 .compute_root_hash(core::slice::from_ref(&fresh_root_node))
2892 .unwrap()
2893 .unwrap();
2894
2895 pretty_assertions::assert_eq!(
2896 expected_root,
2897 root_with_collapse,
2898 "Root hash after collapsing branch (removed child before remaining) should match fresh computation"
2899 );
2900 }
2901
2902 #[test]
2908 fn test_branch_collapse_removed_child_after_remaining() {
2909 reth_tracing::init_test_tracing();
2910
2911 let val = U256::from(1u64);
2912
2913 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);
2919 let leaf_hash_b = storage_leaf_hash(&Nibbles::unpack(key_b).slice(2..), &val);
2920
2921 let sub_branch_state_mask = TrieMask::new((1 << 4) | (1 << 9));
2923 let cached_sub_branch = BranchNodeCompact::new(
2924 sub_branch_state_mask,
2925 TrieMask::new(0),
2926 sub_branch_state_mask,
2927 vec![leaf_hash_a, leaf_hash_b],
2928 None,
2929 );
2930
2931 let storage_nodes: BTreeMap<Nibbles, BranchNodeCompact> =
2932 std::iter::once((Nibbles::from_nibbles([0x2]), cached_sub_branch)).collect();
2933
2934 let mut harness = TrieTestHarness::new([(key_a, val), (key_c, val)].into_iter().collect());
2936 harness.set_trie_nodes(storage_nodes);
2937
2938 let mut prefix_set_mut = PrefixSetMut::default();
2940 prefix_set_mut.insert(Nibbles::unpack(key_b));
2941 let prefix_set = prefix_set_mut.freeze();
2942
2943 let storage_trie_cursor =
2945 harness.trie_cursor_factory().storage_trie_cursor(harness.hashed_address()).unwrap();
2946 let hashed_storage_cursor = harness
2947 .hashed_cursor_factory()
2948 .hashed_storage_cursor(harness.hashed_address())
2949 .unwrap();
2950 let mut calculator =
2951 StorageProofCalculator::new_storage(storage_trie_cursor, hashed_storage_cursor)
2952 .with_prefix_set(prefix_set);
2953 let root_node = calculator
2954 .storage_root_node(harness.hashed_address())
2955 .expect("storage_root_node should succeed after branch collapse");
2956 let root_with_collapse =
2957 calculator.compute_root_hash(core::slice::from_ref(&root_node)).unwrap().unwrap();
2958
2959 let mut fresh_harness =
2961 TrieTestHarness::new([(key_a, val), (key_c, val)].into_iter().collect());
2962 fresh_harness.set_trie_nodes(BTreeMap::new());
2963 let storage_trie_cursor = fresh_harness
2964 .trie_cursor_factory()
2965 .storage_trie_cursor(fresh_harness.hashed_address())
2966 .unwrap();
2967 let hashed_storage_cursor = fresh_harness
2968 .hashed_cursor_factory()
2969 .hashed_storage_cursor(fresh_harness.hashed_address())
2970 .unwrap();
2971 let mut fresh_calculator =
2972 StorageProofCalculator::new_storage(storage_trie_cursor, hashed_storage_cursor);
2973 let fresh_root_node = fresh_calculator
2974 .storage_root_node(fresh_harness.hashed_address())
2975 .expect("fresh storage_root_node should succeed");
2976 let expected_root = fresh_calculator
2977 .compute_root_hash(core::slice::from_ref(&fresh_root_node))
2978 .unwrap()
2979 .unwrap();
2980
2981 pretty_assertions::assert_eq!(
2982 expected_root,
2983 root_with_collapse,
2984 "Root hash after collapsing branch (removed child after remaining) should match fresh computation"
2985 );
2986 }
2987
2988 #[test]
2989 fn test_cached_branch_extension_skips_diverging_target() {
2990 reth_tracing::init_test_tracing();
2991
2992 let val = U256::from(100u64);
2993
2994 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> =
3003 [(key_a0, val), (key_a1, val), (key_c, val), (key_d, val), (key_e, val)]
3004 .into_iter()
3005 .collect();
3006 let correct_harness = TrieTestHarness::new(all_storage.clone());
3007 let expected_root = correct_harness.original_root();
3008
3009 let leaf_hash_a0 = storage_leaf_hash(&Nibbles::unpack(key_a0).slice(4..), &val);
3011 let leaf_hash_a1 = storage_leaf_hash(&Nibbles::unpack(key_a1).slice(4..), &val);
3012 let leaf_hash_d = storage_leaf_hash(&Nibbles::unpack(key_d).slice(2..), &val);
3013 let leaf_hash_e = storage_leaf_hash(&Nibbles::unpack(key_e).slice(2..), &val);
3014
3015 let branch_6_state_mask = TrieMask::new((1 << 0xa) | (1 << 0xb) | (1 << 0xc));
3023 let branch_6_hash_mask = TrieMask::new((1 << 0xb) | (1 << 0xc));
3024 let branch_6 = BranchNodeCompact::new(
3025 branch_6_state_mask,
3026 TrieMask::new(0),
3027 branch_6_hash_mask,
3028 vec![leaf_hash_d, leaf_hash_e],
3029 None,
3030 );
3031
3032 let branch_6a3_state_mask = TrieMask::new((1 << 0) | (1 << 1));
3036 let branch_6a3 = BranchNodeCompact::new(
3037 branch_6a3_state_mask,
3038 TrieMask::new(0),
3039 branch_6a3_state_mask,
3040 vec![leaf_hash_a0, leaf_hash_a1],
3041 None,
3042 );
3043
3044 let inconsistent_nodes: BTreeMap<Nibbles, BranchNodeCompact> = [
3046 (Nibbles::from_nibbles([0x6]), branch_6),
3047 (Nibbles::from_nibbles([0x6, 0xa, 0x3]), branch_6a3),
3048 ]
3049 .into_iter()
3050 .collect();
3051
3052 let mut harness = TrieTestHarness::new(all_storage);
3054 harness.set_trie_nodes(inconsistent_nodes);
3055
3056 let mut prefix_set = PrefixSetMut::default();
3062 prefix_set.insert(Nibbles::unpack(key_c));
3063
3064 let trie_cursor =
3066 harness.trie_cursor_factory().storage_trie_cursor(harness.hashed_address()).unwrap();
3067 let hashed_cursor = harness
3068 .hashed_cursor_factory()
3069 .hashed_storage_cursor(harness.hashed_address())
3070 .unwrap();
3071 let mut calculator = StorageProofCalculator::new_storage(trie_cursor, hashed_cursor)
3072 .with_prefix_set(prefix_set.freeze());
3073
3074 let root_node = calculator
3075 .storage_root_node(harness.hashed_address())
3076 .expect("storage_root_node should succeed");
3077 let got_root = calculator
3078 .compute_root_hash(core::slice::from_ref(&root_node))
3079 .unwrap()
3080 .expect("should produce a root hash");
3081
3082 pretty_assertions::assert_eq!(
3084 expected_root,
3085 got_root,
3086 "Root hash should match correct trie; cached extension must not skip diverging leaves"
3087 );
3088
3089 let mut targets = vec![ProofV2Target::new(key_c)];
3091 let proofs = calculator
3092 .storage_proof(harness.hashed_address(), &mut targets)
3093 .expect("storage_proof should succeed");
3094
3095 let key_c_nibbles = Nibbles::unpack(key_c);
3096 let has_matching_node = proofs.iter().any(|node| key_c_nibbles.starts_with(&node.path));
3097 assert!(
3098 has_matching_node,
3099 "Proof for key_c should contain at least one node on key_c's path, got: {proofs:?}"
3100 );
3101 }
3102
3103 #[test]
3104 fn test_cached_branch_extension_skips_diverging_target_before() {
3105 reth_tracing::init_test_tracing();
3106
3107 let val = U256::from(100u64);
3108
3109 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> =
3118 [(key_a0, val), (key_a1, val), (key_c, val), (key_d, val), (key_e, val)]
3119 .into_iter()
3120 .collect();
3121 let correct_harness = TrieTestHarness::new(all_storage.clone());
3122 let expected_root = correct_harness.original_root();
3123
3124 let leaf_hash_a0 = storage_leaf_hash(&Nibbles::unpack(key_a0).slice(4..), &val);
3126 let leaf_hash_a1 = storage_leaf_hash(&Nibbles::unpack(key_a1).slice(4..), &val);
3127 let leaf_hash_d = storage_leaf_hash(&Nibbles::unpack(key_d).slice(2..), &val);
3128 let leaf_hash_e = storage_leaf_hash(&Nibbles::unpack(key_e).slice(2..), &val);
3129
3130 let branch_6_state_mask = TrieMask::new((1 << 0xa) | (1 << 0xb) | (1 << 0xc));
3138 let branch_6_hash_mask = TrieMask::new((1 << 0xb) | (1 << 0xc));
3139 let branch_6 = BranchNodeCompact::new(
3140 branch_6_state_mask,
3141 TrieMask::new(0),
3142 branch_6_hash_mask,
3143 vec![leaf_hash_d, leaf_hash_e],
3144 None,
3145 );
3146
3147 let branch_6a8_state_mask = TrieMask::new((1 << 0) | (1 << 1));
3151 let branch_6a8 = BranchNodeCompact::new(
3152 branch_6a8_state_mask,
3153 TrieMask::new(0),
3154 branch_6a8_state_mask,
3155 vec![leaf_hash_a0, leaf_hash_a1],
3156 None,
3157 );
3158
3159 let inconsistent_nodes: BTreeMap<Nibbles, BranchNodeCompact> = [
3161 (Nibbles::from_nibbles([0x6]), branch_6),
3162 (Nibbles::from_nibbles([0x6, 0xa, 0x8]), branch_6a8),
3163 ]
3164 .into_iter()
3165 .collect();
3166
3167 let mut harness = TrieTestHarness::new(all_storage);
3169 harness.set_trie_nodes(inconsistent_nodes);
3170
3171 let mut prefix_set = PrefixSetMut::default();
3173 prefix_set.insert(Nibbles::unpack(key_c));
3174
3175 let trie_cursor =
3177 harness.trie_cursor_factory().storage_trie_cursor(harness.hashed_address()).unwrap();
3178 let hashed_cursor = harness
3179 .hashed_cursor_factory()
3180 .hashed_storage_cursor(harness.hashed_address())
3181 .unwrap();
3182 let mut calculator = StorageProofCalculator::new_storage(trie_cursor, hashed_cursor)
3183 .with_prefix_set(prefix_set.freeze());
3184
3185 let root_node = calculator
3186 .storage_root_node(harness.hashed_address())
3187 .expect("storage_root_node should succeed");
3188 let got_root = calculator
3189 .compute_root_hash(core::slice::from_ref(&root_node))
3190 .unwrap()
3191 .expect("should produce a root hash");
3192
3193 pretty_assertions::assert_eq!(
3195 expected_root,
3196 got_root,
3197 "Root hash should match correct trie; cached extension must not skip diverging leaves before cached branch"
3198 );
3199
3200 let mut targets = vec![ProofV2Target::new(key_c)];
3202 let proofs = calculator
3203 .storage_proof(harness.hashed_address(), &mut targets)
3204 .expect("storage_proof should succeed");
3205
3206 let key_c_nibbles = Nibbles::unpack(key_c);
3207 let has_matching_node = proofs.iter().any(|node| key_c_nibbles.starts_with(&node.path));
3208 assert!(
3209 has_matching_node,
3210 "Proof for key_c should contain at least one node on key_c's path, got: {proofs:?}"
3211 );
3212 }
3213
3214 #[test]
3215 fn test_skipped_parent_branch_with_unskipped_child() {
3216 reth_tracing::init_test_tracing();
3217
3218 let val = U256::from(1u64);
3219 let updated_val = U256::from(2u64);
3220
3221 let key_2 = B256::right_padding_from(&[0x20]);
3223 let key_2f00 = B256::right_padding_from(&[0x2f, 0x00]);
3224 let key_2f01 = B256::right_padding_from(&[0x2f, 0x01]);
3225 let key_2f10 = B256::right_padding_from(&[0x2f, 0x10]);
3226 let key_2f11 = B256::right_padding_from(&[0x2f, 0x11]);
3227 let key_300 = B256::right_padding_from(&[0x30, 0x00]);
3228 let key_301 = B256::right_padding_from(&[0x30, 0x10]);
3229 let key_310 = B256::right_padding_from(&[0x31, 0x00]);
3230 let key_311 = B256::right_padding_from(&[0x31, 0x10]);
3231 let key_500 = B256::right_padding_from(&[0x50, 0x00]);
3232 let key_501 = B256::right_padding_from(&[0x50, 0x10]);
3233 let key_510 = B256::right_padding_from(&[0x51, 0x00]);
3234 let key_511 = B256::right_padding_from(&[0x51, 0x10]);
3235
3236 let all_keys = [
3237 key_2, key_2f00, key_2f01, key_2f10, key_2f11, key_300, key_301, key_310, key_311,
3238 key_500, key_501, key_510, key_511,
3239 ];
3240
3241 let original_storage: BTreeMap<B256, U256> = all_keys.iter().map(|k| (*k, val)).collect();
3242 let harness = TrieTestHarness::new(original_storage);
3243
3244 let trie_updates = harness.storage_trie_updates();
3246 assert!(trie_updates.storage_nodes.contains_key(&Nibbles::from_nibbles([0x2])));
3247 assert!(trie_updates.storage_nodes.contains_key(&Nibbles::from_nibbles([0x2, 0xf])));
3248 assert!(trie_updates.storage_nodes.contains_key(&Nibbles::from_nibbles([0x3])));
3249
3250 let changeset: BTreeMap<B256, U256> = std::iter::once((key_2, updated_val)).collect();
3253 let (expected_root, _) = harness.get_root_with_updates(&changeset);
3254
3255 let mut updated_storage = harness.storage().clone();
3256 updated_storage.insert(key_2, updated_val);
3257
3258 let updated_hashed = MockHashedCursorFactory::new(
3259 BTreeMap::new(),
3260 std::iter::once((harness.hashed_address(), updated_storage)).collect(),
3261 );
3262
3263 let mut prefix_set = PrefixSetMut::default();
3264 prefix_set.insert(Nibbles::unpack(key_2));
3265
3266 let trie_cursor =
3267 harness.trie_cursor_factory().storage_trie_cursor(harness.hashed_address()).unwrap();
3268 let hashed_cursor = updated_hashed.hashed_storage_cursor(harness.hashed_address()).unwrap();
3269 let mut calculator = StorageProofCalculator::new_storage(trie_cursor, hashed_cursor)
3270 .with_prefix_set(prefix_set.freeze());
3271 let root_node = calculator
3272 .storage_root_node(harness.hashed_address())
3273 .expect("storage_root_node should succeed");
3274
3275 let got_root = calculator
3276 .compute_root_hash(&[root_node])
3277 .expect("root hash should succeed")
3278 .expect("root should get hashed");
3279 pretty_assertions::assert_eq!(expected_root, got_root);
3280 }
3281
3282 #[test]
3283 fn test_cached_hash_with_deleted_leaf() {
3284 reth_tracing::init_test_tracing();
3285
3286 let val_3 = U256::from(111u64);
3288 let val_5 = U256::from(222u64);
3289 let val_8 = U256::from(333u64);
3290
3291 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);
3300 let leaf_hash_5 = storage_leaf_hash(&Nibbles::unpack(key_65).slice(2..), &val_5);
3301 let leaf_hash_8 = storage_leaf_hash(&Nibbles::unpack(key_68).slice(2..), &val_8);
3302
3303 let state_mask = TrieMask::new((1 << 3) | (1 << 5) | (1 << 8));
3305 let cached_branch = BranchNodeCompact::new(
3306 state_mask,
3307 TrieMask::new(0),
3308 state_mask, vec![leaf_hash_3, leaf_hash_5, leaf_hash_8],
3310 None,
3311 );
3312
3313 let storage_nodes: BTreeMap<Nibbles, BranchNodeCompact> =
3314 std::iter::once((Nibbles::from_nibbles([0x6]), cached_branch)).collect();
3315
3316 let mut harness =
3318 TrieTestHarness::new([(key_65, val_5), (key_68, val_8)].into_iter().collect());
3319 let expected_root = harness.original_root();
3320
3321 harness.set_trie_nodes(storage_nodes);
3323
3324 let mut prefix_set = PrefixSetMut::default();
3327 prefix_set.insert(Nibbles::unpack(key_63));
3328
3329 let mut targets = vec![ProofV2Target::new(key_63)];
3333
3334 let trie_cursor =
3335 harness.trie_cursor_factory().storage_trie_cursor(harness.hashed_address()).unwrap();
3336 let hashed_cursor = harness
3337 .hashed_cursor_factory()
3338 .hashed_storage_cursor(harness.hashed_address())
3339 .unwrap();
3340 let mut calculator = StorageProofCalculator::new_storage(trie_cursor, hashed_cursor)
3341 .with_prefix_set(prefix_set.freeze());
3342
3343 let proofs = calculator
3344 .storage_proof(harness.hashed_address(), &mut targets)
3345 .expect("storage_proof should succeed");
3346 assert_eq!(1, proofs.len());
3347 let got_root = calculator
3348 .compute_root_hash(&proofs)
3349 .expect("compute_root_hash should succeed")
3350 .expect("should produce a root hash (proof contains root node)");
3351
3352 pretty_assertions::assert_eq!(
3355 expected_root,
3356 got_root,
3357 "Root hash should match trie without key_63; cached hash index is off when \
3358 an earlier hashed child has no leaves (absence proof target)"
3359 );
3360 }
3361}