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, sync::Arc};
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 {
116 self.prefix_set = prefix_set;
117 self
118 }
119}
120
121impl<TC, HC, VE> ProofCalculator<TC, HC, VE>
122where
123 TC: TrieCursor,
124 HC: HashedCursor,
125 VE: LeafValueEncoder<Value = HC::Value>,
126{
127 fn take_rlp_nodes_buf(&mut self) -> Vec<RlpNode> {
132 self.rlp_nodes_bufs
133 .pop()
134 .map(|mut buf| {
135 buf.clear();
136 buf
137 })
138 .unwrap_or_else(|| Vec::with_capacity(16))
139 }
140
141 #[inline]
148 const fn maybe_parent_nibble(&self) -> usize {
149 !self.branch_stack.is_empty() as usize
150 }
151
152 #[instrument(
188 target = TRACE_TARGET,
189 level = "trace",
190 skip_all,
191 fields(?path, ?check_parent_path),
192 ret,
193 )]
194 fn should_retain<'a>(
195 &self,
196 targets: &mut Option<TargetsCursor<'a>>,
197 path: &Nibbles,
198 check_parent_path: bool,
199 ) -> bool {
200 let Some(targets) = targets.as_mut() else { return false };
202
203 let (mut lower, mut upper) = targets.current();
204
205 loop {
206 if lower.key_nibbles.starts_with(path) {
227 let is_below_parent = |target: &ProofV2Target| {
228 target.parent.path_len().is_none_or(|len| path.len() > len)
229 };
230 return !check_parent_path ||
231 (is_below_parent(lower) ||
232 targets
233 .skip_iter()
234 .take_while(|target| target.key_nibbles.starts_with(path))
235 .any(is_below_parent) ||
236 targets
237 .rev_iter()
238 .take_while(|target| target.key_nibbles.starts_with(path))
239 .any(is_below_parent))
240 }
241
242 if upper
245 .is_some_and(|upper| depth_first::cmp(path, &upper.key_nibbles) != Ordering::Less)
246 {
247 (lower, upper) = targets.next();
248 trace!(target: TRACE_TARGET, target = ?lower, "upper target <= path, next target");
249 } else {
250 return false
251 }
252 }
253 }
254
255 fn commit_child(
264 &mut self,
265 child_path: Nibbles,
266 child: ProofTrieBranchChild<VE::DeferredEncoder>,
267 ) -> Result<RlpNode, StateProofError> {
268 if matches!(&child, ProofTrieBranchChild::RlpNode { .. }) {
270 self.rlp_encode_buf.clear();
271 return child.into_rlp(&mut self.rlp_encode_buf).map(|(node, _)| node)
272 }
273
274 trace!(target: TRACE_TARGET, ?child_path, "Retaining child");
275
276 self.rlp_encode_buf.clear();
281 let proof_node = child.into_proof_trie_node(child_path, &mut self.rlp_encode_buf)?;
282
283 self.rlp_encode_buf.clear();
286 proof_node.node.encode(&mut self.rlp_encode_buf);
287
288 self.retained_proofs.push(proof_node);
289 Ok(RlpNode::from_rlp(&self.rlp_encode_buf))
290 }
291
292 #[inline]
295 fn child_path_at(&self, nibble: u8) -> Nibbles {
296 let mut child_path = self.branch_path;
297 debug_assert!(child_path.len() < 64);
298 child_path.push_unchecked(nibble);
299 child_path
300 }
301
302 #[inline]
308 fn highest_set_nibble(mask: TrieMask) -> u8 {
309 debug_assert!(!mask.is_empty());
310 (u16::BITS - mask.leading_zeros() - 1) as u8
311 }
312
313 fn last_child_path(&self) -> Option<Nibbles> {
316 let Some(branch) = self.branch_stack.last() else {
318 return Some(Nibbles::new());
319 };
320
321 (!branch.state_mask.is_empty())
322 .then(|| self.child_path_at(Self::highest_set_nibble(branch.state_mask)))
323 }
324
325 #[instrument(
336 target = TRACE_TARGET,
337 level = "trace",
338 skip_all,
339 fields(child_path = ?self.last_child_path()),
340 )]
341 fn commit_last_child<'a>(
342 &mut self,
343 targets: &mut Option<TargetsCursor<'a>>,
344 ) -> Result<(), StateProofError> {
345 if matches!(self.child_stack.last(), Some(ProofTrieBranchChild::RlpNode { .. })) {
346 trace!(target: TRACE_TARGET, "Last child already committed, leaving stack unchanged");
347 return Ok(())
348 }
349
350 let Some(child_path) = self.last_child_path() else { return Ok(()) };
351 let child =
352 self.child_stack.pop().expect("child_stack can't be empty if there's a child path");
353
354 if self.should_retain(targets, &child_path, true) {
357 let (hash_mask_bit, tree_mask_bit) = child.mask_bits();
358 let child_rlp_node = self.commit_child(child_path, child)?;
359 trace!(target: TRACE_TARGET, ?child_rlp_node, "Pushing committed child RlpNode onto stack");
360 self.child_stack.push(ProofTrieBranchChild::RlpNode {
361 node: child_rlp_node,
362 short_key: Nibbles::new(),
363 hash_mask_bit,
364 tree_mask_bit,
365 });
366 } else {
367 trace!(target: TRACE_TARGET, "Pushing uncommitted child onto stack");
368 self.child_stack.push(child);
369 }
370
371 Ok(())
372 }
373
374 fn push_child<'a>(
377 &mut self,
378 targets: &mut Option<TargetsCursor<'a>>,
379 mut child: ProofTrieBranchChild<VE::DeferredEncoder>,
380 ) -> Result<(), StateProofError> {
381 let path = *child.short_key();
382
383 loop {
384 trace!(
385 target: TRACE_TARGET,
386 ?path,
387 branch_stack_len = ?self.branch_stack.len(),
388 branch_path = ?self.branch_path,
389 child_stack_len = ?self.child_stack.len(),
390 "push_child: loop",
391 );
392
393 let (nibble, short_key) = match self.branch_stack.last().map(|branch| branch.state_mask)
396 {
397 None if self.child_stack.is_empty() => {
398 self.child_stack.push(child);
400 return Ok(())
401 }
402 None => {
403 debug_assert_eq!(self.child_stack.len(), 1);
405 debug_assert!(!self
406 .child_stack
407 .last()
408 .expect("already checked for emptiness")
409 .short_key()
410 .is_empty());
411 self.push_new_branch(path)
412 }
413 Some(state_mask) => {
414 let common_prefix_len = self.branch_path.common_prefix_length(&path);
416
417 if common_prefix_len < self.branch_path.len() {
420 self.pop_branch(targets)?;
421 continue
422 }
423
424 let nibble = path.get_unchecked(common_prefix_len);
427 if state_mask.is_bit_set(nibble) {
428 self.push_new_branch(path)
429 } else {
430 (nibble, trim_nibbles_prefix(&path, common_prefix_len + 1))
431 }
432 }
433 };
434
435 child.trim_short_key_prefix(path.len() - short_key.len());
437
438 self.commit_last_child(targets)?;
441
442 let branch = self.branch_stack.last_mut().expect("branch_stack cannot be empty");
443 debug_assert!(!branch.state_mask.is_bit_set(nibble));
444
445 branch.state_mask.set_bit(nibble);
448
449 self.child_stack.push(child);
451 return Ok(())
452 }
453 }
454
455 fn push_new_branch(&mut self, new_child_path: Nibbles) -> (u8, Nibbles) {
462 let first_child_path = self
465 .last_child_path()
466 .expect("push_new_branch requires the current branch to have a child");
467
468 let new_child_short_key = trim_nibbles_prefix(&new_child_path, first_child_path.len());
471 let first_child_short_key = *self
472 .child_stack
473 .last()
474 .expect("push_new_branch can't be called with empty child_stack")
475 .short_key();
476 debug_assert!(!first_child_short_key.is_empty());
477
478 let common_prefix_len = first_child_short_key.common_prefix_length(&new_child_short_key);
481 let first_child_nibble = first_child_short_key.get_unchecked(common_prefix_len);
482 let new_child_nibble = new_child_short_key.get_unchecked(common_prefix_len);
483
484 let new_child_short_key = trim_nibbles_prefix(&new_child_short_key, common_prefix_len + 1);
486
487 let branch_path_len = first_child_path.len() + common_prefix_len;
489 self.branch_path = new_child_path.slice_unchecked(0, branch_path_len);
490
491 let first_child = self
494 .child_stack
495 .last_mut()
496 .expect("push_new_branch can't be called with empty child_stack");
497 first_child.trim_short_key_prefix(common_prefix_len + 1);
498
499 self.branch_stack.push(ProofTrieBranch {
503 ext_len: common_prefix_len as u8,
504 state_mask: TrieMask::new(1 << first_child_nibble),
505 });
506
507 trace!(
508 target: TRACE_TARGET,
509 ?new_child_path,
510 ext_len = ?common_prefix_len,
511 ?first_child_nibble,
512 branch_path = ?self.branch_path,
513 "Pushed new branch",
514 );
515
516 (new_child_nibble, new_child_short_key)
517 }
518
519 #[instrument(target = TRACE_TARGET, level = "trace", skip_all)]
527 fn pop_branch<'a>(
528 &mut self,
529 targets: &mut Option<TargetsCursor<'a>>,
530 ) -> Result<(), StateProofError> {
531 trace!(
532 target: TRACE_TARGET,
533 branch = ?self.branch_stack.last(),
534 branch_path = ?self.branch_path,
535 child_stack_len = ?self.child_stack.len(),
536 "called",
537 );
538
539 self.commit_last_child(targets)?;
542
543 let mut rlp_nodes_buf = self.take_rlp_nodes_buf();
544 let mut masks = BranchNodeMasks::default();
545 let branch = self.branch_stack.pop().expect("branch_stack cannot be empty");
546
547 let num_children = branch.state_mask.count_ones() as usize;
550 debug_assert!(
551 self.child_stack.len() >= num_children,
552 "Stack is missing necessary children ({num_children:?})"
553 );
554 debug_assert!(
555 num_children >= 2,
556 "A branch must have at least two children, got {num_children}"
557 );
558
559 rlp_nodes_buf.reserve(num_children);
561 for (nibble, child) in branch
562 .state_mask
563 .iter()
564 .zip(self.child_stack.drain(self.child_stack.len() - num_children..))
565 {
566 let (hash_mask_bit, tree_mask_bit) = child.mask_bits();
567 masks.set_child_bits(nibble, hash_mask_bit, tree_mask_bit);
568
569 self.rlp_encode_buf.clear();
570 let (child_rlp_node, freed_buf) = child.into_rlp(&mut self.rlp_encode_buf)?;
571 if let Some(buf) = freed_buf {
572 self.rlp_nodes_bufs.push(buf);
573 }
574 rlp_nodes_buf.push(child_rlp_node);
575 }
576
577 debug_assert_eq!(
578 rlp_nodes_buf.len(),
579 num_children,
580 "children length must match number of bits set in state_mask"
581 );
582
583 let short_key = trim_nibbles_prefix(
586 &self.branch_path,
587 self.branch_path.len() - branch.ext_len as usize,
588 );
589
590 let rlp_node = if short_key.is_empty() {
592 None
593 } else {
594 self.rlp_encode_buf.clear();
595 BranchNodeRef::new(&rlp_nodes_buf, branch.state_mask).encode(&mut self.rlp_encode_buf);
596 Some(RlpNode::from_rlp(&self.rlp_encode_buf))
597 };
598
599 let new_path_len =
602 self.branch_path.len() - branch.ext_len as usize - self.maybe_parent_nibble();
603
604 let branch_as_child = ProofTrieBranchChild::Branch {
606 node: BranchNodeV2::new(short_key, rlp_nodes_buf, branch.state_mask, rlp_node),
607 masks: (!masks.is_empty()).then_some(masks),
608 };
609
610 debug_assert!(self.branch_path.len() >= new_path_len);
611 self.branch_path = self.branch_path.slice_unchecked(0, new_path_len);
612
613 self.child_stack.push(branch_as_child);
614
615 Ok(())
616 }
617
618 #[instrument(
625 target = TRACE_TARGET,
626 level = "trace",
627 skip_all,
628 fields(?lower_bound, ?upper_bound),
629 )]
630 fn calculate_key_range<'a>(
631 &mut self,
632 value_encoder: &mut VE,
633 targets: &mut Option<TargetsCursor<'a>>,
634 hashed_cursor_state: &mut HashedCursorState<VE::DeferredEncoder>,
635 lower_bound: Nibbles,
636 upper_bound: Option<Nibbles>,
637 ) -> Result<(), StateProofError> {
638 let mut map_hashed_cursor_entry = |(key_b256, val): (B256, _)| {
642 debug_assert_eq!(key_b256.len(), 32);
643 let key = Nibbles::unpack_array(key_b256.as_ref());
644 let val = value_encoder.deferred_encoder(key_b256, val);
645 (key, val)
646 };
647
648 if hashed_cursor_state.needs_seek_to(&lower_bound) {
651 trace!(
652 target: TRACE_TARGET,
653 current=?hashed_cursor_state.path(),
654 "Seeking hashed cursor to meet lower bound",
655 );
656
657 let lower_key = B256::right_padding_from(&lower_bound.pack());
658 *hashed_cursor_state = HashedCursorState::seeked(
659 lower_bound,
660 self.hashed_cursor.seek(lower_key)?.map(&mut map_hashed_cursor_entry),
661 );
662 }
663
664 while hashed_cursor_state
666 .path()
667 .is_some_and(|key| upper_bound.is_none_or(|upper_bound| key < &upper_bound))
668 {
669 let (key, val) = hashed_cursor_state.take();
670 self.push_child(targets, ProofTrieBranchChild::Leaf { short_key: key, value: val })?;
671 *hashed_cursor_state = HashedCursorState::seeked(
672 key,
673 self.hashed_cursor.next()?.map(&mut map_hashed_cursor_entry),
674 );
675 }
676
677 trace!(target: TRACE_TARGET, "No further keys within range");
678 Ok(())
679 }
680
681 fn take_cached_branch(
683 &mut self,
684 trie_cursor_state: &mut TrieCursorState,
685 ) -> (Nibbles, BranchNodeCompact) {
686 let (cached_path, mut cached_branch) = trie_cursor_state.take();
687
688 if self.prefix_set.contains(&cached_path) {
689 let mut unchanged_children = 0;
690 let mut child_path = cached_path;
691 for nibble in cached_branch.state_mask.iter() {
692 child_path.truncate(cached_path.len());
693 child_path.push_unchecked(nibble);
694 if !self.prefix_set.contains(&child_path) {
695 unchanged_children += 1;
696 if unchanged_children > 1 {
697 break
698 }
699 }
700 }
701
702 if unchanged_children <= 1 {
706 Arc::make_mut(&mut cached_branch.hashes).fill(B256::ZERO);
707 trace!(
708 target: TRACE_TARGET,
709 ?cached_path,
710 ?unchanged_children,
711 "Invalidated cached hashes because branch may collapse",
712 );
713 }
714 }
715
716 (cached_path, cached_branch)
717 }
718
719 #[inline]
726 fn try_pop_cached_branch(
727 &mut self,
728 trie_cursor_state: &mut TrieCursorState,
729 traversal_upper_bound: Option<&Nibbles>,
730 uncalculated_lower_bound: &Option<Nibbles>,
731 ) -> Result<PopCachedBranchOutcome, StateProofError> {
732 let Some(uncalculated_lower_bound) = uncalculated_lower_bound else {
735 return Ok(PopCachedBranchOutcome::Exhausted)
736 };
737
738 while let Some(cached) = self.cached_branch_stack.pop() {
741 if cached
742 .0
743 .next_without_prefix()
744 .is_some_and(|upper_bound| upper_bound <= *uncalculated_lower_bound)
745 {
746 trace!(target: TRACE_TARGET, cached_path=?cached.0, ?uncalculated_lower_bound, "Skipping covered cached branch");
747 continue
748 }
749 return Ok(PopCachedBranchOutcome::Popped(cached));
750 }
751
752 let Some(mut trie_cursor_path) = trie_cursor_state.path() else {
759 return Ok(PopCachedBranchOutcome::Exhausted)
760 };
761
762 if trie_cursor_path < uncalculated_lower_bound {
765 *trie_cursor_state = TrieCursorState::seeked(
766 *uncalculated_lower_bound,
767 self.trie_cursor.seek(*uncalculated_lower_bound)?,
768 );
769
770 if let Some(new_trie_cursor_path) = trie_cursor_state.path() {
773 trie_cursor_path = new_trie_cursor_path
774 } else {
775 return Ok(PopCachedBranchOutcome::Exhausted)
776 };
777 }
778
779 if traversal_upper_bound.is_some_and(|upper_bound| trie_cursor_path >= upper_bound) {
782 return Ok(PopCachedBranchOutcome::Exhausted)
783 }
784
785 let cached = self.take_cached_branch(trie_cursor_state);
792 trace!(target: TRACE_TARGET, cached=?cached, "Pushed next trie node onto cached_branch_stack");
793
794 let cached_path = &cached.0;
801 if uncalculated_lower_bound < cached_path && !cached_path.is_zeroes() {
802 let range = (*uncalculated_lower_bound, Some(*cached_path));
803 trace!(target: TRACE_TARGET, ?range, "Returning key range to calculate in order to catch up to cached branch");
804
805 self.cached_branch_stack.push(cached);
808
809 return Ok(PopCachedBranchOutcome::CalculateLeaves(range));
810 }
811
812 Ok(PopCachedBranchOutcome::Popped(cached))
813 }
814
815 fn commit_branches<'a>(
824 &mut self,
825 targets: &mut Option<TargetsCursor<'a>>,
826 next_path: &Nibbles,
827 uncalculated_lower_bound: Option<&Nibbles>,
828 ) -> Result<Option<(Nibbles, Option<Nibbles>)>, StateProofError> {
829 let dirty_range = |prefix_set: &mut PrefixSet, upper_bound: Option<Nibbles>| {
830 let uncalculated_lower_bound = uncalculated_lower_bound?;
831
832 if upper_bound.as_ref().is_some_and(|upper| uncalculated_lower_bound >= upper) {
833 return None
834 }
835
836 match upper_bound {
837 Some(upper_bound) => prefix_set
838 .contains_range(uncalculated_lower_bound..&upper_bound)
839 .then_some((*uncalculated_lower_bound, Some(upper_bound))),
840 None => prefix_set
841 .contains_from(uncalculated_lower_bound)
842 .then_some((*uncalculated_lower_bound, None)),
843 }
844 };
845
846 let mut popped_child_path_upper = None;
847 while !next_path.starts_with(&self.branch_path) {
848 if uncalculated_lower_bound.is_some_and(|lower| lower.starts_with(&self.branch_path)) &&
851 let Some(range) =
852 dirty_range(&mut self.prefix_set, self.branch_path.next_without_prefix())
853 {
854 return Ok(Some(range))
855 }
856
857 let branch = self.branch_stack.last().expect("branch_stack cannot be empty");
858 popped_child_path_upper = Some(
861 self.branch_path
862 .slice_unchecked(0, self.branch_path.len() - branch.ext_len as usize)
863 .next_without_prefix(),
864 );
865
866 self.pop_branch(targets)?;
867 }
868
869 if !self.branch_stack.is_empty() &&
873 let Some(upper_bound) = popped_child_path_upper &&
874 let Some(range) = dirty_range(&mut self.prefix_set, upper_bound)
875 {
876 return Ok(Some(range))
877 }
878
879 Ok(None)
880 }
881
882 fn next_uncached_child_nibble(
885 prefix_set: &mut PrefixSet,
886 branch_path: &Nibbles,
887 uncalculated_lower_bound_ref: &Nibbles,
888 cached_state_mask: TrieMask,
889 ) -> Option<u8> {
890 let mut next_child_nibbles = cached_state_mask;
891
892 if prefix_set.contains(branch_path) {
896 let branch_path_len = branch_path.len();
897 let mut child_path = *branch_path;
898 for nibble in 0u8..16 {
899 child_path.truncate(branch_path_len);
900 child_path.push_unchecked(nibble);
901 if prefix_set.contains(&child_path) {
902 next_child_nibbles.set_bit(nibble);
903 }
904 }
905 }
906
907 let _orig_next_child_nibbles = next_child_nibbles;
908
909 if uncalculated_lower_bound_ref.starts_with(branch_path) &&
914 uncalculated_lower_bound_ref.len() > branch_path.len()
915 {
916 let lower_nibble = uncalculated_lower_bound_ref.get_unchecked(branch_path.len());
917 let already_processed_mask = TrieMask::new((1u16 << lower_nibble) - 1);
920 next_child_nibbles &= !already_processed_mask;
921 trace!(
922 target: TRACE_TARGET,
923 ?branch_path,
924 ?_orig_next_child_nibbles,
925 ?already_processed_mask,
926 ?next_child_nibbles,
927 "Unset already processed key nibbles from next_child_nibbles",
928 );
929 } else if !uncalculated_lower_bound_ref.starts_with(branch_path) &&
930 uncalculated_lower_bound_ref > branch_path
931 {
932 next_child_nibbles = TrieMask::default();
935 trace!(
936 target: TRACE_TARGET,
937 ?branch_path,
938 ?_orig_next_child_nibbles,
939 ?next_child_nibbles,
940 "Unset all nibbles from next_child_nibbles due to branch_path being outside this subtrie",
941 );
942 }
943
944 next_child_nibbles.first_set_bit_index()
945 }
946
947 #[instrument(target = TRACE_TARGET, level = "trace", skip_all)]
966 fn next_uncached_key_range<'a>(
967 &mut self,
968 targets: &mut Option<TargetsCursor<'a>>,
969 trie_cursor_state: &mut TrieCursorState,
970 traversal_upper_bound: Option<&Nibbles>,
971 mut uncalculated_lower_bound: Option<Nibbles>,
972 ) -> Result<Option<(Nibbles, Option<Nibbles>)>, StateProofError> {
973 loop {
974 if let (Some(lower_bound), Some(upper_bound)) =
975 (uncalculated_lower_bound.as_ref(), traversal_upper_bound) &&
976 lower_bound >= upper_bound
977 {
978 return Ok(None)
979 }
980
981 let (cached_path, cached_branch) = match self.try_pop_cached_branch(
987 trie_cursor_state,
988 traversal_upper_bound,
989 &uncalculated_lower_bound,
990 )? {
991 PopCachedBranchOutcome::Popped(cached) => cached,
992 PopCachedBranchOutcome::Exhausted => {
993 trace!(target: TRACE_TARGET, ?uncalculated_lower_bound, "Exhausted cached trie nodes");
997 if let Some(lower) = uncalculated_lower_bound {
998 self.commit_branches(targets, &lower, None)?;
999 return Ok(Some((lower, traversal_upper_bound.copied())));
1000 }
1001 return Ok(None)
1002 }
1003 PopCachedBranchOutcome::CalculateLeaves(range) => {
1004 self.commit_branches(targets, &range.0, None)?;
1005 return Ok(Some(range));
1006 }
1007 };
1008
1009 let uncalculated_lower_bound_ref = uncalculated_lower_bound
1010 .as_ref()
1011 .expect("try_pop_cached_branch would return Exhausted if this were None");
1012
1013 trace!(
1014 target: TRACE_TARGET,
1015 branch_path = ?self.branch_path,
1016 branch_state_mask = ?self.branch_stack.last().map(|b| b.state_mask),
1017 ?cached_path,
1018 cached_branch_state_mask = ?cached_branch.state_mask,
1019 cached_branch_hash_mask = ?cached_branch.hash_mask,
1020 "loop",
1021 );
1022
1023 if let Some(range) =
1024 self.commit_branches(targets, &cached_path, Some(uncalculated_lower_bound_ref))?
1025 {
1026 self.cached_branch_stack.push((cached_path, cached_branch));
1027 return Ok(Some(range))
1028 }
1029
1030 debug_assert!(
1033 self.branch_path.len() < cached_path.len() || self.branch_path == cached_path,
1034 "branch_path {:?} is different-or-longer-than cached_path {cached_path:?}",
1035 self.branch_path
1036 );
1037
1038 if uncalculated_lower_bound_ref < &cached_path &&
1041 self.prefix_set.contains_range(uncalculated_lower_bound_ref..&cached_path)
1042 {
1043 self.cached_branch_stack.push((cached_path, cached_branch));
1044 return Ok(Some((*uncalculated_lower_bound_ref, Some(cached_path))))
1045 }
1046
1047 let child_nibble = Self::next_uncached_child_nibble(
1048 &mut self.prefix_set,
1049 &cached_path,
1050 uncalculated_lower_bound_ref,
1051 cached_branch.state_mask,
1052 );
1053
1054 let Some(child_nibble) = child_nibble else {
1055 trace!(
1056 target: TRACE_TARGET,
1057 path=?cached_path,
1058 ?cached_branch,
1059 "No further cached children",
1060 );
1061
1062 uncalculated_lower_bound = cached_path.next_without_prefix();
1068
1069 continue
1070 };
1071
1072 let mut child_path = cached_path;
1073 child_path.push_unchecked(child_nibble);
1074 let child_lower_bound = (*uncalculated_lower_bound_ref).max(child_path);
1075
1076 if cached_branch.hash_mask.is_bit_set(child_nibble) &&
1086 child_lower_bound == child_path &&
1087 !self.prefix_set.contains(&child_path)
1088 {
1089 let lower_bits = TrieMask::new((1u16 << child_nibble) - 1);
1092 let hash_idx = (cached_branch.hash_mask & lower_bits).count_ones() as usize;
1093 let hash = cached_branch.hashes[hash_idx];
1094
1095 if hash != B256::ZERO {
1098 let mut probed_targets = targets.clone();
1099 if !self.should_retain(&mut probed_targets, &child_path, false) {
1100 trace!(
1101 target: TRACE_TARGET,
1102 ?child_path,
1103 ?hash_idx,
1104 ?hash,
1105 "Using cached hash for child",
1106 );
1107
1108 let tree_mask_bit = cached_branch.tree_mask.is_bit_set(child_nibble);
1111 self.cached_branch_stack.push((cached_path, cached_branch));
1112 self.push_child(
1113 targets,
1114 ProofTrieBranchChild::RlpNode {
1115 node: RlpNode::word_rlp(&hash),
1116 short_key: child_path,
1117 hash_mask_bit: true,
1118 tree_mask_bit,
1119 },
1120 )?;
1121
1122 if let (Some(targets), Some(probed_targets)) =
1123 (targets.as_mut(), probed_targets)
1124 {
1125 targets.i = targets.i.max(probed_targets.i);
1126 }
1127
1128 uncalculated_lower_bound = child_path.next_without_prefix();
1131
1132 continue
1133 }
1134 }
1135 }
1136
1137 if trie_cursor_state.path().is_some_and(|path| path < &child_lower_bound) {
1144 trace!(target: TRACE_TARGET, ?child_lower_bound, "Seeking trie cursor to child lower bound");
1145 *trie_cursor_state = TrieCursorState::seeked(
1146 child_lower_bound,
1147 self.trie_cursor.seek(child_lower_bound)?,
1148 );
1149 }
1150
1151 if let TrieCursorState::Available(next_cached_path, next_cached_branch) =
1155 &trie_cursor_state &&
1156 next_cached_path.starts_with(&child_path)
1157 {
1158 self.cached_branch_stack.push((cached_path, cached_branch));
1160
1161 trace!(
1162 target: TRACE_TARGET,
1163 ?child_path,
1164 ?next_cached_path,
1165 ?next_cached_branch,
1166 "Pushing cached branch for child",
1167 );
1168 let cached = self.take_cached_branch(trie_cursor_state);
1169 self.cached_branch_stack.push(cached);
1170 continue;
1171 }
1172
1173 let child_upper_bound = child_path.next_without_prefix();
1177 trace!(
1178 target: TRACE_TARGET,
1179 lower=?child_lower_bound,
1180 upper=?child_upper_bound,
1181 "Returning sub-trie's key range to calculate",
1182 );
1183
1184 self.cached_branch_stack.push((cached_path, cached_branch));
1186
1187 return Ok(Some((child_lower_bound, child_upper_bound)));
1188 }
1189 }
1190
1191 #[instrument(
1195 target = TRACE_TARGET,
1196 level = "trace",
1197 skip_all,
1198 fields(
1199 parent_prefix=?sub_trie_targets.parent_prefix,
1200 lower_bound=?sub_trie_targets.lower_bound,
1201 upper_bound=?sub_trie_targets.upper_bound,
1202 ),
1203 )]
1204 fn proof_subtrie<'a>(
1205 &mut self,
1206 value_encoder: &mut VE,
1207 trie_cursor_state: &mut TrieCursorState,
1208 hashed_cursor_state: &mut HashedCursorState<VE::DeferredEncoder>,
1209 sub_trie_targets: SubTrieTargets<'a>,
1210 ) -> Result<(), StateProofError> {
1211 let traversal_lower_bound = sub_trie_targets.lower_bound;
1212 let traversal_upper_bound = sub_trie_targets.upper_bound;
1213
1214 let mut targets = if sub_trie_targets.targets.is_empty() {
1217 None
1218 } else {
1219 Some(TargetsCursor::new(sub_trie_targets.targets))
1220 };
1221
1222 debug_assert!(self.cached_branch_stack.is_empty());
1225 debug_assert!(self.branch_stack.is_empty());
1226 debug_assert!(self.branch_path.is_empty());
1227 debug_assert!(self.child_stack.is_empty());
1228
1229 if trie_cursor_state.needs_seek_to(&traversal_lower_bound) {
1234 trace!(target: TRACE_TARGET, "Doing initial seek of trie cursor");
1235 *trie_cursor_state = TrieCursorState::seeked(
1236 traversal_lower_bound,
1237 self.trie_cursor.seek(traversal_lower_bound)?,
1238 );
1239 }
1240
1241 let mut uncalculated_lower_bound = Some(traversal_lower_bound);
1246
1247 trace!(target: TRACE_TARGET, "Starting loop");
1248 loop {
1249 let prev_uncalculated_lower_bound = uncalculated_lower_bound;
1251
1252 let Some((calc_lower_bound, calc_upper_bound)) = self.next_uncached_key_range(
1254 &mut targets,
1255 trie_cursor_state,
1256 traversal_upper_bound.as_ref(),
1257 prev_uncalculated_lower_bound,
1258 )?
1259 else {
1260 break;
1263 };
1264
1265 if let Some(prev_lower) = prev_uncalculated_lower_bound.as_ref() &&
1272 calc_lower_bound < *prev_lower
1273 {
1274 let msg = format!(
1275 "next_uncached_key_range went backwards: calc_lower={calc_lower_bound:?} < \
1276 prev_lower={prev_lower:?}, calc_upper={calc_upper_bound:?}, \
1277 lower_bound={traversal_lower_bound:?}, \
1278 upper_bound={traversal_upper_bound:?}",
1279 );
1280 error!(target: TRACE_TARGET, "{msg}");
1281 return Err(StateProofError::TrieInconsistency(msg));
1282 }
1283
1284 self.calculate_key_range(
1286 value_encoder,
1287 &mut targets,
1288 hashed_cursor_state,
1289 calc_lower_bound,
1290 calc_upper_bound,
1291 )?;
1292
1293 if hashed_cursor_state.path().is_none_or(|key| {
1299 traversal_upper_bound.is_some_and(|upper_bound| key >= &upper_bound)
1300 }) {
1301 break;
1302 }
1303
1304 uncalculated_lower_bound = calc_upper_bound;
1307 }
1308
1309 trace!(target: TRACE_TARGET, "Exited loop, popping remaining branches");
1311 while !self.branch_stack.is_empty() {
1312 self.pop_branch(&mut targets)?;
1313 }
1314
1315 debug_assert!(self.branch_stack.is_empty());
1319 debug_assert!(self.branch_path.is_empty());
1320 debug_assert!(self.child_stack.len() < 2);
1321
1322 self.cached_branch_stack.clear();
1325
1326 trace!(
1330 target: TRACE_TARGET,
1331 parent_prefix = ?sub_trie_targets.parent_prefix,
1332 child_stack_empty = self.child_stack.is_empty(),
1333 "Maybe retaining local root",
1334 );
1335 let root_node = self.child_stack.pop();
1336
1337 let Some(parent_prefix) = sub_trie_targets.parent_prefix else {
1340 let mut root_node = if let Some(root_node) = root_node {
1341 self.rlp_encode_buf.clear();
1342 root_node.into_proof_trie_node(Nibbles::new(), &mut self.rlp_encode_buf)?
1343 } else {
1344 ProofTrieNodeV2::empty()
1345 };
1346
1347 if matches!(&root_node.node, TrieNodeV2::Branch(branch) if branch.key.is_empty()) {
1350 root_node.masks = None;
1351 }
1352
1353 self.retained_proofs.push(root_node);
1354 return Ok(())
1355 };
1356
1357 let Some(mut root_node) = root_node else { return Ok(()) };
1359
1360 let root_full_path = *root_node.short_key();
1361
1362 if root_full_path == parent_prefix {
1365 return Ok(())
1366 }
1367
1368 if !root_full_path.starts_with(&parent_prefix) {
1374 return Err(StateProofError::TrieInconsistency(format!(
1375 "local root path {root_full_path:?} does not start with parent prefix \
1376 {parent_prefix:?}",
1377 )))
1378 }
1379
1380 let child_path_len = parent_prefix.len() + 1;
1383 let child_path = root_full_path.slice_unchecked(0, child_path_len);
1384
1385 if !sub_trie_targets
1387 .targets
1388 .iter()
1389 .any(|target| target.key_nibbles.starts_with(&child_path))
1390 {
1391 return Ok(())
1392 }
1393
1394 root_node.trim_short_key_prefix(child_path_len);
1396 self.rlp_encode_buf.clear();
1397 let root_node = root_node.into_proof_trie_node(child_path, &mut self.rlp_encode_buf)?;
1398 self.retained_proofs.push(root_node);
1399
1400 Ok(())
1401 }
1402
1403 fn clear_computation_state(&mut self) {
1406 self.branch_stack.clear();
1407 self.branch_path = Nibbles::new();
1408 self.child_stack.clear();
1409 self.cached_branch_stack.clear();
1410 self.retained_proofs.clear();
1411 }
1412
1413 fn proof_inner(
1416 &mut self,
1417 value_encoder: &mut VE,
1418 targets: &mut [ProofV2Target],
1419 ) -> Result<Vec<ProofTrieNodeV2>, StateProofError> {
1420 if targets.is_empty() {
1422 trace!(target: TRACE_TARGET, "Empty targets, returning");
1423 return Ok(Vec::new())
1424 }
1425
1426 let mut trie_cursor_state = TrieCursorState::unseeked();
1429 let mut hashed_cursor_state = HashedCursorState::unseeked();
1430 let mut previous_traversal_bounds: Option<(Nibbles, Option<Nibbles>)> = None;
1431
1432 for sub_trie_targets in iter_sub_trie_targets(targets) {
1435 let traversal_lower_bound = sub_trie_targets.lower_bound;
1436 let traversal_upper_bound = sub_trie_targets.upper_bound;
1437 if previous_traversal_bounds.is_some_and(|(_, previous_upper_bound)| {
1438 previous_upper_bound.is_none_or(|upper_bound| upper_bound > traversal_lower_bound)
1439 }) {
1440 if trie_cursor_state.needs_reset_before_seek(&traversal_lower_bound) {
1441 trace!(
1442 target: TRACE_TARGET,
1443 ?previous_traversal_bounds,
1444 ?traversal_lower_bound,
1445 ?traversal_upper_bound,
1446 "Resetting trie cursor before overlapping or backward traversal range",
1447 );
1448 self.trie_cursor.reset();
1449 trie_cursor_state = TrieCursorState::unseeked();
1450 }
1451 if hashed_cursor_state.needs_reset_before_seek(&traversal_lower_bound) {
1452 trace!(
1453 target: TRACE_TARGET,
1454 ?previous_traversal_bounds,
1455 ?traversal_lower_bound,
1456 ?traversal_upper_bound,
1457 "Resetting hashed cursor before overlapping or backward traversal range",
1458 );
1459 self.hashed_cursor.reset();
1460 hashed_cursor_state = HashedCursorState::unseeked();
1461 }
1462 }
1463
1464 if let Err(err) = self.proof_subtrie(
1465 value_encoder,
1466 &mut trie_cursor_state,
1467 &mut hashed_cursor_state,
1468 sub_trie_targets,
1469 ) {
1470 self.clear_computation_state();
1471 return Err(err);
1472 }
1473
1474 previous_traversal_bounds = Some((traversal_lower_bound, traversal_upper_bound));
1475 }
1476
1477 trace!(
1478 target: TRACE_TARGET,
1479 retained_proofs_len = ?self.retained_proofs.len(),
1480 "proof_inner: returning",
1481 );
1482 self.retained_proofs.sort_unstable_by(|a, b| depth_first::cmp(&a.path, &b.path));
1483 self.retained_proofs.dedup_by(|a, b| a.path == b.path);
1484 Ok(core::mem::take(&mut self.retained_proofs))
1485 }
1486
1487 #[instrument(target = TRACE_TARGET, level = "trace", skip_all)]
1496 pub fn proof(
1497 &mut self,
1498 value_encoder: &mut VE,
1499 targets: &mut [ProofV2Target],
1500 ) -> Result<Vec<ProofTrieNodeV2>, StateProofError> {
1501 self.trie_cursor.reset();
1502 self.hashed_cursor.reset();
1503 self.proof_inner(value_encoder, targets)
1504 }
1505
1506 pub fn compute_root_hash(
1513 &mut self,
1514 proof_nodes: &[ProofTrieNodeV2],
1515 ) -> Result<Option<B256>, StateProofError> {
1516 let root_node = proof_nodes.iter().find(|node| node.path.is_empty());
1518
1519 let Some(root) = root_node else {
1520 return Ok(None);
1521 };
1522
1523 self.rlp_encode_buf.clear();
1525 root.node.encode(&mut self.rlp_encode_buf);
1526 let root_hash = keccak256(&self.rlp_encode_buf);
1527
1528 Ok(Some(root_hash))
1529 }
1530
1531 #[instrument(target = TRACE_TARGET, level = "trace", skip(self, value_encoder))]
1536 pub fn root_node(
1537 &mut self,
1538 value_encoder: &mut VE,
1539 ) -> Result<ProofTrieNodeV2, StateProofError> {
1540 self.trie_cursor.reset();
1541 self.hashed_cursor.reset();
1542
1543 let mut trie_cursor_state = TrieCursorState::unseeked();
1546 let mut hashed_cursor_state = HashedCursorState::unseeked();
1547
1548 static EMPTY_TARGETS: [ProofV2Target; 0] = [];
1549 let sub_trie_targets = SubTrieTargets {
1550 lower_bound: Nibbles::new(),
1551 upper_bound: None,
1552 parent_prefix: None,
1553 targets: &EMPTY_TARGETS,
1554 };
1555
1556 if let Err(err) = self.proof_subtrie(
1557 value_encoder,
1558 &mut trie_cursor_state,
1559 &mut hashed_cursor_state,
1560 sub_trie_targets,
1561 ) {
1562 self.clear_computation_state();
1563 return Err(err);
1564 }
1565
1566 let mut proofs = core::mem::take(&mut self.retained_proofs);
1569 trace!(
1570 target: TRACE_TARGET,
1571 proofs_len = ?proofs.len(),
1572 "root_node: extracting root",
1573 );
1574
1575 debug_assert_eq!(
1578 proofs.len(), 1,
1579 "prefix is empty, parent path is None, and targets is empty, so there must be only the root node"
1580 );
1581
1582 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");
1584
1585 Ok(root_node)
1586 }
1587}
1588
1589pub type StorageProofCalculator<TC, HC> = ProofCalculator<TC, HC, StorageValueEncoder>;
1591
1592impl<TC, HC> StorageProofCalculator<TC, HC>
1593where
1594 TC: TrieStorageCursor,
1595 HC: HashedStorageCursor<Value = U256>,
1596{
1597 pub fn new_storage(trie_cursor: TC, hashed_cursor: HC) -> Self {
1599 Self::new(trie_cursor, hashed_cursor)
1600 }
1601
1602 #[instrument(target = TRACE_TARGET, level = "trace", skip(self, targets))]
1611 pub fn storage_proof(
1612 &mut self,
1613 hashed_address: B256,
1614 targets: &mut [ProofV2Target],
1615 ) -> Result<Vec<ProofTrieNodeV2>, StateProofError> {
1616 self.hashed_cursor.set_hashed_address(hashed_address);
1617
1618 if self.hashed_cursor.is_storage_empty()? {
1620 return Ok(if targets.iter().any(|target| !target.parent.is_known()) {
1621 vec![ProofTrieNodeV2 {
1622 path: Nibbles::default(),
1623 node: TrieNodeV2::EmptyRoot,
1624 masks: None,
1625 }]
1626 } else {
1627 Vec::new()
1628 })
1629 }
1630
1631 self.trie_cursor.set_hashed_address(hashed_address);
1634
1635 let mut storage_value_encoder = StorageValueEncoder;
1637 self.proof_inner(&mut storage_value_encoder, targets)
1638 }
1639
1640 #[instrument(target = TRACE_TARGET, level = "trace", skip(self))]
1645 pub fn storage_root_node(
1646 &mut self,
1647 hashed_address: B256,
1648 ) -> Result<ProofTrieNodeV2, StateProofError> {
1649 self.hashed_cursor.set_hashed_address(hashed_address);
1650
1651 if self.hashed_cursor.is_storage_empty()? {
1652 return Ok(ProofTrieNodeV2 {
1653 path: Nibbles::default(),
1654 node: TrieNodeV2::EmptyRoot,
1655 masks: None,
1656 })
1657 }
1658
1659 self.trie_cursor.set_hashed_address(hashed_address);
1662
1663 let mut storage_value_encoder = StorageValueEncoder;
1665 self.root_node(&mut storage_value_encoder)
1666 }
1667}
1668
1669#[derive(Clone)]
1675struct TargetsCursor<'a> {
1676 targets: &'a [ProofV2Target],
1677 i: usize,
1678}
1679
1680impl<'a> TargetsCursor<'a> {
1681 fn new(targets: &'a [ProofV2Target]) -> Self {
1687 debug_assert!(!targets.is_empty());
1688 Self { targets, i: 0 }
1689 }
1690
1691 fn current(&self) -> (&'a ProofV2Target, Option<&'a ProofV2Target>) {
1693 (&self.targets[self.i], self.targets.get(self.i + 1))
1694 }
1695
1696 fn next(&mut self) -> (&'a ProofV2Target, Option<&'a ProofV2Target>) {
1702 self.i += 1;
1703 debug_assert!(self.i < self.targets.len());
1704 self.current()
1705 }
1706
1707 fn skip_iter(&self) -> impl Iterator<Item = &'a ProofV2Target> {
1709 self.targets[self.i + 1..].iter()
1710 }
1711
1712 fn rev_iter(&self) -> impl Iterator<Item = &'a ProofV2Target> {
1715 self.targets[..self.i].iter().rev()
1716 }
1717}
1718
1719#[derive(Debug)]
1722enum TrieCursorState {
1723 Unseeked,
1725 Available(Nibbles, BranchNodeCompact),
1727 Taken(Nibbles),
1729 Exhausted(Nibbles),
1731}
1732
1733impl TrieCursorState {
1734 const fn unseeked() -> Self {
1736 Self::Unseeked
1737 }
1738
1739 fn seeked(key: Nibbles, entry: Option<(Nibbles, BranchNodeCompact)>) -> Self {
1741 entry.map_or(Self::Exhausted(key), |(path, node)| Self::Available(path, node))
1742 }
1743
1744 const fn path(&self) -> Option<&Nibbles> {
1750 match self {
1751 Self::Unseeked => panic!("cursor is unseeked"),
1752 Self::Available(path, _) | Self::Taken(path) => Some(path),
1753 Self::Exhausted(_) => None,
1754 }
1755 }
1756
1757 fn needs_seek_to(&self, path: &Nibbles) -> bool {
1759 match self {
1760 Self::Unseeked | Self::Taken(_) => true,
1761 Self::Available(current_path, _) => current_path < path,
1762 Self::Exhausted(_) => false,
1763 }
1764 }
1765
1766 fn needs_reset_before_seek(&self, key: &Nibbles) -> bool {
1768 match self {
1769 Self::Unseeked => false,
1770 Self::Available(path, _) | Self::Taken(path) => path > key,
1771 Self::Exhausted(exhausted_at) => exhausted_at > key,
1772 }
1773 }
1774
1775 fn take(&mut self) -> (Nibbles, BranchNodeCompact) {
1777 let Self::Available(path, _) = self else {
1778 panic!("take called on non-Available: {self:?}")
1779 };
1780
1781 let path = *path;
1782 let Self::Available(path, node) = core::mem::replace(self, Self::Taken(path)) else {
1783 unreachable!("already checked that self is Self::Available");
1784 };
1785
1786 (path, node)
1787 }
1788}
1789
1790enum HashedCursorState<V> {
1792 Unseeked,
1794 Available(Nibbles, V),
1796 Exhausted(Nibbles),
1798}
1799
1800impl<V> HashedCursorState<V> {
1801 const fn unseeked() -> Self {
1803 Self::Unseeked
1804 }
1805
1806 fn seeked(key: Nibbles, entry: Option<(Nibbles, V)>) -> Self {
1808 entry.map_or(Self::Exhausted(key), |(path, value)| Self::Available(path, value))
1809 }
1810
1811 const fn path(&self) -> Option<&Nibbles> {
1813 match self {
1814 Self::Available(path, _) => Some(path),
1815 Self::Unseeked | Self::Exhausted(_) => None,
1816 }
1817 }
1818
1819 fn needs_seek_to(&self, key: &Nibbles) -> bool {
1821 match self {
1822 Self::Unseeked => true,
1823 Self::Available(path, _) => path < key,
1824 Self::Exhausted(exhausted_at) => exhausted_at > key,
1825 }
1826 }
1827
1828 fn needs_reset_before_seek(&self, key: &Nibbles) -> bool {
1830 match self {
1831 Self::Unseeked => false,
1832 Self::Available(path, _) => path > key,
1833 Self::Exhausted(exhausted_at) => exhausted_at >= key,
1834 }
1835 }
1836
1837 fn take(&mut self) -> (Nibbles, V) {
1839 match core::mem::replace(self, Self::Unseeked) {
1840 Self::Available(path, value) => (path, value),
1841 _ => panic!("take called on non-Available hashed cursor state"),
1842 }
1843 }
1844}
1845
1846enum PopCachedBranchOutcome {
1848 Popped((Nibbles, BranchNodeCompact)),
1850 Exhausted,
1852 CalculateLeaves((Nibbles, Option<Nibbles>)),
1855}
1856
1857#[cfg(test)]
1858mod tests {
1859 use super::*;
1860 use crate::{
1861 hashed_cursor::{
1862 mock::MockHashedCursorFactory, noop::NoopHashedCursor, HashedCursorFactory,
1863 HashedPostStateCursor,
1864 },
1865 proof::StorageProof as LegacyStorageProof,
1866 test_utils::TrieTestHarness,
1867 trie_cursor::{
1868 depth_first, noop::NoopStorageTrieCursor, InMemoryTrieCursor, TrieCursorFactory,
1869 },
1870 };
1871 use alloy_primitives::map::B256Set;
1872 use alloy_rlp::Decodable;
1873 use alloy_trie::proof::AddedRemovedKeys;
1874 use itertools::Itertools;
1875 use reth_trie_common::{
1876 prefix_set::{PrefixSet, PrefixSetMut},
1877 updates::TrieUpdatesSorted,
1878 HashedPostState, HashedStorage, ProofTrieNode, ProofV2TargetParent, TrieNode,
1879 EMPTY_ROOT_HASH,
1880 };
1881 use std::collections::BTreeMap;
1882
1883 fn convert_legacy_proofs_to_v2(legacy_proofs: &[ProofTrieNode]) -> Vec<ProofTrieNodeV2> {
1896 ProofTrieNodeV2::from_sorted_trie_nodes(
1897 legacy_proofs.iter().map(|p| (p.path, p.node.clone(), p.masks)),
1898 )
1899 }
1900
1901 fn project_legacy_proof_node(
1903 node: &ProofTrieNodeV2,
1904 target: &ProofV2Target,
1905 ) -> Option<ProofTrieNodeV2> {
1906 let Some(parent_path_len) = target.parent.path_len() else {
1907 return target.key_nibbles.starts_with(&node.path).then(|| node.clone())
1908 };
1909
1910 if node.path.len() > parent_path_len {
1911 return target.key_nibbles.starts_with(&node.path).then(|| node.clone())
1912 }
1913
1914 let logical_path = match &node.node {
1915 TrieNodeV2::Leaf(leaf) => node.path.join(&leaf.key),
1916 TrieNodeV2::Branch(branch) => node.path.join(&branch.key),
1917 TrieNodeV2::EmptyRoot | TrieNodeV2::Extension(_) => return None,
1918 };
1919 let child_path_len = parent_path_len + 1;
1920 if logical_path.len() < child_path_len {
1921 return None
1922 }
1923
1924 let child_path = logical_path.slice(0..child_path_len);
1925 if !target.key_nibbles.starts_with(&child_path) {
1926 return None
1927 }
1928
1929 let trim_len = child_path_len - node.path.len();
1930 let mut projected = node.clone();
1931 projected.path = child_path;
1932 match &mut projected.node {
1933 TrieNodeV2::Leaf(leaf) => leaf.key = leaf.key.slice(trim_len..),
1934 TrieNodeV2::Branch(branch) => {
1935 branch.key = branch.key.slice(trim_len..);
1936 if branch.key.is_empty() {
1937 branch.branch_rlp_node = None;
1938 }
1939 }
1940 TrieNodeV2::EmptyRoot | TrieNodeV2::Extension(_) => unreachable!(),
1941 }
1942 Some(projected)
1943 }
1944
1945 fn project_legacy_proof(
1947 legacy_nodes: &[ProofTrieNodeV2],
1948 targets: &[ProofV2Target],
1949 ) -> Vec<ProofTrieNodeV2> {
1950 let mut projected = targets
1951 .iter()
1952 .flat_map(|target| {
1953 legacy_nodes.iter().filter_map(move |node| project_legacy_proof_node(node, target))
1954 })
1955 .collect::<Vec<_>>();
1956 projected.sort_unstable_by(|a, b| depth_first::cmp(&a.path, &b.path));
1957 projected.dedup_by(|a, b| {
1958 if a.path != b.path {
1959 return false
1960 }
1961 assert_eq!(a, b, "target projections disagree at path {:?}", a.path);
1962 true
1963 });
1964 projected
1965 }
1966
1967 struct ProofTestHarness {
1973 inner: TrieTestHarness,
1974 }
1975
1976 impl std::ops::Deref for ProofTestHarness {
1977 type Target = TrieTestHarness;
1978 fn deref(&self) -> &Self::Target {
1979 &self.inner
1980 }
1981 }
1982
1983 impl ProofTestHarness {
1984 fn new(storage: BTreeMap<B256, U256>) -> Self {
1986 Self { inner: TrieTestHarness::new(storage) }
1987 }
1988
1989 fn root_with_prefix_set(&self, prefix_set: PrefixSet) -> Option<B256> {
1991 let trie_cursor =
1992 self.trie_cursor_factory().storage_trie_cursor(self.hashed_address()).unwrap();
1993 let hashed_cursor =
1994 self.hashed_cursor_factory().hashed_storage_cursor(self.hashed_address()).unwrap();
1995 let mut calculator = StorageProofCalculator::new_storage(trie_cursor, hashed_cursor)
1996 .with_prefix_set(prefix_set);
1997
1998 let mut targets = [ProofV2Target::new(B256::ZERO)];
1999 let proof = calculator.storage_proof(self.hashed_address(), &mut targets).unwrap();
2000 calculator.compute_root_hash(&proof).unwrap()
2001 }
2002
2003 fn assert_proof(
2006 &self,
2007 targets: impl IntoIterator<Item = ProofV2Target>,
2008 ) -> Result<(), StateProofError> {
2009 let mut targets_vec = targets.into_iter().collect::<Vec<_>>();
2010
2011 let (proof_v2_result, root_hash) = self.proof_v2(&mut targets_vec);
2013
2014 if let Some(root_hash) = root_hash {
2017 pretty_assertions::assert_eq!(self.original_root(), root_hash);
2018 }
2019
2020 let legacy_targets = targets_vec
2023 .iter()
2024 .map(|target| B256::from_slice(&target.key_nibbles.pack()))
2025 .chain(self.storage().keys().copied())
2026 .collect::<B256Set>();
2027
2028 let proof_legacy_result = LegacyStorageProof::new_hashed(
2030 self.trie_cursor_factory(),
2031 self.hashed_cursor_factory(),
2032 self.hashed_address(),
2033 )
2034 .with_branch_node_masks(true)
2035 .with_added_removed_keys(Some(AddedRemovedKeys::default().with_assume_added(true)))
2036 .storage_multiproof(legacy_targets)?;
2037
2038 let proof_legacy_nodes = proof_legacy_result
2040 .subtree
2041 .iter()
2042 .map(|(path, node_enc)| {
2043 let mut buf = node_enc.as_ref();
2044 let node = TrieNode::decode(&mut buf)
2045 .expect("legacy implementation should not produce malformed proof nodes");
2046
2047 let masks = if path.is_empty() {
2048 None
2049 } else {
2050 proof_legacy_result.branch_node_masks.get(path).copied()
2051 };
2052
2053 ProofTrieNode { path: *path, node, masks }
2054 })
2055 .sorted_by(|a, b| depth_first::cmp(&a.path, &b.path))
2056 .collect::<Vec<_>>();
2057
2058 let all_legacy_nodes_v2 = convert_legacy_proofs_to_v2(&proof_legacy_nodes);
2060
2061 let expected_v2 = project_legacy_proof(&all_legacy_nodes_v2, &targets_vec);
2062 pretty_assertions::assert_eq!(expected_v2, proof_v2_result);
2063
2064 Ok(())
2065 }
2066 }
2067
2068 #[test]
2073 fn test_proof_calculator_reuse_after_error() {
2074 reth_tracing::init_test_tracing();
2075
2076 let slots = [
2077 B256::right_padding_from(&[0x10]),
2078 B256::right_padding_from(&[0x20]),
2079 B256::right_padding_from(&[0x30]),
2080 B256::right_padding_from(&[0x40]),
2081 ];
2082 let storage: BTreeMap<B256, U256> =
2083 slots.iter().map(|&s| (s, U256::from(100u64))).collect();
2084
2085 let harness = ProofTestHarness::new(storage);
2086
2087 let trie_cursor_factory = harness.trie_cursor_factory();
2088 let hashed_cursor_factory = harness.hashed_cursor_factory();
2089
2090 let hashed_address = harness.hashed_address();
2091 let trie_cursor = trie_cursor_factory.storage_trie_cursor(hashed_address).unwrap();
2092 let hashed_cursor = hashed_cursor_factory.hashed_storage_cursor(hashed_address).unwrap();
2093 let mut proof_calculator = StorageProofCalculator::new_storage(trie_cursor, hashed_cursor);
2094
2095 proof_calculator
2098 .branch_stack
2099 .push(ProofTrieBranch { ext_len: 2, state_mask: TrieMask::new(0b1111) });
2100 proof_calculator
2101 .branch_stack
2102 .push(ProofTrieBranch { ext_len: 0, state_mask: TrieMask::new(0b11) });
2103 proof_calculator.child_stack.push(ProofTrieBranchChild::RlpNode {
2104 node: RlpNode::word_rlp(&B256::ZERO),
2105 short_key: Nibbles::new(),
2106 hash_mask_bit: false,
2107 tree_mask_bit: false,
2108 });
2109 proof_calculator.branch_path = Nibbles::from_nibbles([0x1, 0x2, 0x3]);
2110
2111 proof_calculator.clear_computation_state();
2113
2114 let mut sorted_slots = slots.to_vec();
2115 sorted_slots.sort();
2116 let mut targets: Vec<ProofV2Target> =
2117 sorted_slots.iter().copied().map(ProofV2Target::new).collect();
2118
2119 let result = proof_calculator.storage_proof(hashed_address, &mut targets).unwrap();
2120
2121 let trie_cursor = trie_cursor_factory.storage_trie_cursor(hashed_address).unwrap();
2123 let hashed_cursor = hashed_cursor_factory.hashed_storage_cursor(hashed_address).unwrap();
2124 let mut fresh_calculator = StorageProofCalculator::new_storage(trie_cursor, hashed_cursor);
2125 let fresh_result = fresh_calculator.storage_proof(hashed_address, &mut targets).unwrap();
2126
2127 pretty_assertions::assert_eq!(fresh_result, result);
2128 }
2129
2130 #[test]
2131 fn test_root_node_reuse_with_overlay() {
2132 let storage = BTreeMap::from([
2133 (B256::right_padding_from(&[0x10]), U256::from(1)),
2134 (B256::right_padding_from(&[0x20]), U256::from(2)),
2135 ]);
2136 let harness = ProofTestHarness::new(storage.clone());
2137 let hashed_address = harness.hashed_address();
2138 let post_state =
2139 HashedPostState::from_hashed_storage(hashed_address, HashedStorage::from_iter(storage))
2140 .into_sorted();
2141 let trie_updates = TrieUpdatesSorted::default();
2142 let trie_cursor = InMemoryTrieCursor::new_storage(
2143 NoopStorageTrieCursor::default(),
2144 &trie_updates,
2145 hashed_address,
2146 );
2147 let hashed_cursor = HashedPostStateCursor::new_storage(
2148 NoopHashedCursor::default(),
2149 &post_state,
2150 hashed_address,
2151 );
2152 let mut calculator = StorageProofCalculator::new_storage(trie_cursor, hashed_cursor);
2153
2154 let first_root = calculator.root_node(&mut StorageValueEncoder).unwrap();
2155 assert!(matches!(first_root.node, TrieNodeV2::Branch(_)));
2156 assert_eq!(
2157 calculator.compute_root_hash(core::slice::from_ref(&first_root)).unwrap(),
2158 Some(harness.original_root())
2159 );
2160
2161 let second_root = calculator.root_node(&mut StorageValueEncoder).unwrap();
2162 pretty_assertions::assert_eq!(first_root, second_root);
2163 }
2164
2165 #[test]
2166 fn test_partial_storage_proof_after_root_calculation() {
2167 let slot_a = B256::right_padding_from(&[0xae, 0xd4, 0x00]);
2168 let slot_b = B256::right_padding_from(&[0xae, 0xd4, 0x10]);
2169 let harness = ProofTestHarness::new(BTreeMap::from([
2170 (slot_a, U256::from(1)),
2171 (slot_b, U256::from(2)),
2172 ]));
2173 let hashed_address = harness.hashed_address();
2174 let trie_cursor =
2175 harness.trie_cursor_factory().storage_trie_cursor(hashed_address).unwrap();
2176 let hashed_cursor =
2177 harness.hashed_cursor_factory().hashed_storage_cursor(hashed_address).unwrap();
2178 let mut calculator = StorageProofCalculator::new_storage(trie_cursor, hashed_cursor);
2179
2180 let root_node = calculator.storage_root_node(hashed_address).unwrap();
2181 assert_eq!(
2182 calculator.compute_root_hash(core::slice::from_ref(&root_node)).unwrap(),
2183 Some(harness.original_root())
2184 );
2185
2186 let target = ProofV2Target::new(slot_a).with_parent(ProofV2TargetParent::new(3));
2187 let mut actual_targets = [target];
2188 let actual = calculator.storage_proof(hashed_address, &mut actual_targets).unwrap();
2189 let mut expected_targets = [target];
2190 let (expected, root) = harness.proof_v2(&mut expected_targets);
2191
2192 assert!(root.is_none());
2193 pretty_assertions::assert_eq!(expected, actual);
2194 }
2195
2196 mod proptest_tests {
2197 use super::*;
2198 use proptest::prelude::*;
2199
2200 fn storage_strategy() -> impl Strategy<Value = BTreeMap<B256, U256>> {
2202 prop::collection::vec((any::<[u8; 32]>(), any::<u64>()), 0..=100).prop_map(|slots| {
2203 slots
2204 .into_iter()
2205 .map(|(slot_bytes, value)| (B256::from(slot_bytes), U256::from(value)))
2206 .filter(|(_, v)| *v != U256::ZERO)
2207 .collect()
2208 })
2209 }
2210
2211 fn proof_targets_strategy(
2214 slot_keys: Vec<B256>,
2215 ) -> impl Strategy<Value = Vec<ProofV2Target>> {
2216 let num_slots = slot_keys.len();
2217
2218 let target_count = 0..=(num_slots + 5);
2219
2220 target_count.prop_flat_map(move |count| {
2221 let slot_keys = slot_keys.clone();
2222 prop::collection::vec(
2223 (
2224 prop::bool::weighted(0.8).prop_flat_map(move |from_slots| {
2225 if from_slots && !slot_keys.is_empty() {
2226 prop::sample::select(slot_keys.clone()).boxed()
2227 } else {
2228 any::<[u8; 32]>().prop_map(B256::from).boxed()
2229 }
2230 }),
2231 0u8..16u8,
2232 )
2233 .prop_map(|(key, encoded_parent_path_len)| {
2234 let parent = encoded_parent_path_len.checked_sub(1).map_or(
2235 ProofV2TargetParent::NONE,
2236 |parent_path_len| {
2237 ProofV2TargetParent::new(usize::from(parent_path_len))
2238 },
2239 );
2240 ProofV2Target::new(key).with_parent(parent)
2241 }),
2242 count,
2243 )
2244 })
2245 }
2246
2247 proptest! {
2248 #![proptest_config(ProptestConfig::with_cases(4000))]
2249 #[test]
2250 fn proptest_proof_with_targets(
2253 (storage, targets) in storage_strategy()
2254 .prop_flat_map(|storage| {
2255 let mut slot_keys: Vec<B256> = storage.keys().copied().collect();
2256 slot_keys.sort_unstable();
2257 let targets_strategy = proof_targets_strategy(slot_keys);
2258 (Just(storage), targets_strategy)
2259 })
2260 ) {
2261 reth_tracing::init_test_tracing();
2262 let harness = ProofTestHarness::new(storage);
2263
2264 harness.assert_proof(targets).expect("Proof generation failed");
2265 }
2266 }
2267 }
2268
2269 #[test]
2270 fn test_exact_subtrie_targets_with_root_target() {
2271 reth_tracing::init_test_tracing();
2272
2273 let slot_80 = B256::right_padding_from(&[0x80]);
2274 let slot_82 = B256::right_padding_from(&[0x82]);
2275 let slot_f0 = B256::right_padding_from(&[0xf0]);
2276 let storage = BTreeMap::from([
2277 (slot_80, U256::from(1)),
2278 (slot_82, U256::from(2)),
2279 (slot_f0, U256::from(3)),
2280 ]);
2281 let targets = [
2282 ProofV2Target::new(B256::ZERO),
2283 ProofV2Target::new(slot_80).with_parent(ProofV2TargetParent::new(1)),
2284 ];
2285
2286 let harness = ProofTestHarness::new(storage);
2287 harness.assert_proof(targets).expect("Proof generation failed");
2288 }
2289
2290 #[test]
2291 fn test_rebases_singleton_subtrie_root_below_known_parent() {
2292 let slot = B256::right_padding_from(&[0xae, 0xd4, 0x09]);
2293 let slot_nibbles = Nibbles::unpack(slot);
2294 let harness = ProofTestHarness::new(BTreeMap::from([(slot, U256::from(1))]));
2295 let mut targets = [ProofV2Target::new(slot).with_parent(ProofV2TargetParent::new(3))];
2296
2297 let (proof, root) = harness.proof_v2(&mut targets);
2298
2299 assert!(root.is_none());
2300 assert_eq!(proof.len(), 1);
2301 assert_eq!(proof[0].path, slot_nibbles.slice(0..4));
2302 let TrieNodeV2::Leaf(leaf) = &proof[0].node else {
2303 panic!("singleton subtrie root should remain a leaf")
2304 };
2305 assert_eq!(leaf.key, slot_nibbles.slice(4..));
2306 }
2307
2308 #[test]
2309 fn test_rebases_singleton_leaf_at_max_parent_depth() {
2310 let slot = B256::repeat_byte(0xae);
2311 let slot_nibbles = Nibbles::unpack(slot);
2312 let harness = ProofTestHarness::new(BTreeMap::from([(slot, U256::from(1))]));
2313 let mut targets = [ProofV2Target::new(slot).with_parent(ProofV2TargetParent::new(63))];
2314
2315 let (proof, root) = harness.proof_v2(&mut targets);
2316
2317 assert!(root.is_none());
2318 assert_eq!(proof.len(), 1);
2319 assert_eq!(proof[0].path, slot_nibbles);
2320 let TrieNodeV2::Leaf(leaf) = &proof[0].node else {
2321 panic!("singleton subtrie root should remain a leaf")
2322 };
2323 assert!(leaf.key.is_empty());
2324 }
2325
2326 #[test]
2327 fn test_root_and_root_parent_targets_retain_both_singleton_representations() {
2328 let slot = B256::right_padding_from(&[0x20]);
2329 let slot_nibbles = Nibbles::unpack(slot);
2330 let harness = ProofTestHarness::new(BTreeMap::from([(slot, U256::from(1))]));
2331 let mut targets = [
2332 ProofV2Target::new(slot),
2333 ProofV2Target::new(slot).with_parent(ProofV2TargetParent::new(0)),
2334 ];
2335
2336 let (proof, root) = harness.proof_v2(&mut targets);
2337
2338 assert_eq!(root, Some(harness.original_root()));
2339 let root_node = proof.iter().find(|node| node.path.is_empty()).expect("root proof");
2340 let TrieNodeV2::Leaf(root_leaf) = &root_node.node else { panic!("root should be a leaf") };
2341 assert_eq!(root_leaf.key, slot_nibbles);
2342
2343 let child_path = slot_nibbles.slice(0..1);
2344 let child_node =
2345 proof.iter().find(|node| node.path == child_path).expect("rebased root child proof");
2346 let TrieNodeV2::Leaf(child_leaf) = &child_node.node else {
2347 panic!("root child should be a leaf")
2348 };
2349 assert_eq!(child_leaf.key, slot_nibbles.slice(1..));
2350 }
2351
2352 #[test]
2353 fn test_exhausted_cursor_resets_at_equal_target_boundary() {
2354 let first = B256::ZERO;
2355 let last = B256::with_last_byte(1);
2356 let last_nibbles = Nibbles::unpack(last);
2357 let harness =
2358 ProofTestHarness::new(BTreeMap::from([(first, U256::from(1)), (last, U256::from(2))]));
2359 let mut targets = [
2360 ProofV2Target::new(first),
2361 ProofV2Target::new(last).with_parent(ProofV2TargetParent::new(63)),
2362 ];
2363
2364 let (proof, root) = harness.proof_v2(&mut targets);
2365
2366 assert_eq!(root, Some(harness.original_root()));
2367 let child = proof
2368 .iter()
2369 .find(|node| node.path == last_nibbles)
2370 .expect("depth-63 target child proof");
2371 let TrieNodeV2::Leaf(leaf) = &child.node else { panic!("target child should be a leaf") };
2372 assert!(leaf.key.is_empty());
2373 }
2374
2375 #[test]
2376 fn test_rebases_compressed_branch_subtrie_root() {
2377 let slot_a = B256::right_padding_from(&[0xae, 0xd4, 0x00]);
2378 let slot_b = B256::right_padding_from(&[0xae, 0xd4, 0x10]);
2379 let slot_nibbles = Nibbles::unpack(slot_a);
2380 let harness = ProofTestHarness::new(BTreeMap::from([
2381 (slot_a, U256::from(1)),
2382 (slot_b, U256::from(2)),
2383 ]));
2384 let mut targets = [ProofV2Target::new(slot_a).with_parent(ProofV2TargetParent::new(3))];
2385
2386 let (proof, root) = harness.proof_v2(&mut targets);
2387
2388 assert!(root.is_none());
2389 let branch_path = slot_nibbles.slice(0..4);
2390 let branch_node =
2391 proof.iter().find(|node| node.path == branch_path).expect("rebased compressed branch");
2392 let TrieNodeV2::Branch(branch) = &branch_node.node else {
2393 panic!("rebased node should be a branch")
2394 };
2395 assert!(branch.key.is_empty());
2396 assert!(branch.branch_rlp_node.is_none());
2397 }
2398
2399 #[test]
2400 fn test_discards_reconstructed_known_parent_branch() {
2401 let slot_a = B256::right_padding_from(&[0xae, 0xd2]);
2402 let slot_b = B256::right_padding_from(&[0xae, 0xd4]);
2403 let slot_nibbles = Nibbles::unpack(slot_a);
2404 let harness = ProofTestHarness::new(BTreeMap::from([
2405 (slot_a, U256::from(1)),
2406 (slot_b, U256::from(2)),
2407 ]));
2408 let mut targets = [ProofV2Target::new(slot_a).with_parent(ProofV2TargetParent::new(3))];
2409
2410 let (proof, root) = harness.proof_v2(&mut targets);
2411
2412 assert!(root.is_none());
2413 assert!(!proof.iter().any(|node| node.path == slot_nibbles.slice(0..3)));
2414 assert!(proof.iter().any(|node| node.path == slot_nibbles.slice(0..4)));
2415 }
2416
2417 #[test]
2418 fn test_rebased_root_matches_direct_child_not_full_short_key() {
2419 let stored_slot = B256::right_padding_from(&[0xae, 0xd4, 0x09]);
2420 let same_child_target = B256::right_padding_from(&[0xae, 0xd4, 0xff]);
2421 let other_child_target = B256::right_padding_from(&[0xae, 0xd5]);
2422 let harness = ProofTestHarness::new(BTreeMap::from([(stored_slot, U256::from(1))]));
2423
2424 let mut same_child =
2425 [ProofV2Target::new(same_child_target).with_parent(ProofV2TargetParent::new(3))];
2426 let (proof, _) = harness.proof_v2(&mut same_child);
2427 assert_eq!(proof.len(), 1, "divergent leaf proves absence below the same child");
2428
2429 let mut other_child =
2430 [ProofV2Target::new(other_child_target).with_parent(ProofV2TargetParent::new(3))];
2431 let (proof, _) = harness.proof_v2(&mut other_child);
2432 assert!(proof.is_empty(), "a different direct child is unrelated to the target");
2433 }
2434
2435 #[test]
2436 fn test_known_parent_sibling_span_retains_only_target_children() {
2437 let stored_slot_a = B256::right_padding_from(&[0xea, 0x53]);
2438 let stored_slot_b = B256::right_padding_from(&[0xeb, 0x53]);
2439 let stored_slot_c = B256::right_padding_from(&[0xec, 0x53]);
2440 let target_a = B256::right_padding_from(&[0xea, 0x1f]);
2441 let target_c = B256::right_padding_from(&[0xec, 0x1f]);
2442 let harness = ProofTestHarness::new(BTreeMap::from([
2443 (stored_slot_a, U256::from(1)),
2444 (stored_slot_b, U256::from(2)),
2445 (stored_slot_c, U256::from(3)),
2446 ]));
2447 let mut targets = [target_a, target_c]
2448 .map(|target| ProofV2Target::new(target).with_parent(ProofV2TargetParent::new(1)));
2449
2450 let (proof, root) = harness.proof_v2(&mut targets);
2451
2452 assert!(root.is_none());
2453 assert_eq!(
2454 proof.iter().map(|node| node.path).collect::<Vec<_>>(),
2455 [Nibbles::from_nibbles([0xe, 0xa]), Nibbles::from_nibbles([0xe, 0xc])]
2456 );
2457 }
2458
2459 #[test]
2460 fn test_known_parent_does_not_use_stale_parent_mask() {
2461 let stored_slot_a = B256::right_padding_from(&[0xea, 0x53]);
2462 let stored_slot = B256::right_padding_from(&[0xeb, 0x53]);
2463 let stored_slot_c = B256::right_padding_from(&[0xec, 0x53]);
2464 let target = B256::right_padding_from(&[0xeb, 0x1f]);
2465 let stored_slot_nibbles = Nibbles::unpack(stored_slot);
2466
2467 let stale_parent_mask = TrieMask::new((1 << 0xa) | (1 << 0xc));
2471 let stale_parent = BranchNodeCompact::new(
2472 stale_parent_mask,
2473 TrieMask::new(0),
2474 TrieMask::new(0),
2475 Vec::new(),
2476 None,
2477 );
2478 let storage_nodes = BTreeMap::from([(Nibbles::from_nibbles([0xe]), stale_parent)]);
2479
2480 let mut harness = TrieTestHarness::new(BTreeMap::from([
2481 (stored_slot_a, U256::from(1)),
2482 (stored_slot, U256::from(2)),
2483 (stored_slot_c, U256::from(3)),
2484 ]));
2485 harness.set_trie_nodes(storage_nodes);
2486
2487 let mut targets = [ProofV2Target::new(target).with_parent(ProofV2TargetParent::new(1))];
2488 let (proof, root) = harness.proof_v2(&mut targets);
2489
2490 assert!(root.is_none());
2491 assert_eq!(proof.len(), 1);
2492 assert_eq!(proof[0].path, stored_slot_nibbles.slice(0..2));
2493 let TrieNodeV2::Leaf(leaf) = &proof[0].node else {
2494 panic!("live direct child should be reconstructed as a leaf")
2495 };
2496 assert_eq!(leaf.key, stored_slot_nibbles.slice(2..));
2497 }
2498
2499 #[test]
2500 fn test_empty_storage_respects_parent_context() {
2501 let harness = ProofTestHarness::new(BTreeMap::new());
2502 let slot = B256::ZERO;
2503
2504 let mut partial_target =
2505 [ProofV2Target::new(slot).with_parent(ProofV2TargetParent::new(0))];
2506 let (partial_proof, partial_root) = harness.proof_v2(&mut partial_target);
2507 assert!(partial_proof.is_empty());
2508 assert!(partial_root.is_none());
2509
2510 let mut root_target = [ProofV2Target::new(slot)];
2511 let (root_proof, root) = harness.proof_v2(&mut root_target);
2512 assert_eq!(root_proof.len(), 1);
2513 assert!(matches!(root_proof[0].node, TrieNodeV2::EmptyRoot));
2514 assert_eq!(root, Some(EMPTY_ROOT_HASH));
2515 }
2516
2517 #[test]
2518 fn test_big_trie() {
2519 use rand::prelude::*;
2520
2521 reth_tracing::init_test_tracing();
2522 let mut rng = rand::rngs::SmallRng::seed_from_u64(1);
2523
2524 let mut rand_b256 = || {
2525 let mut buf: [u8; 32] = [0; 32];
2526 rng.fill_bytes(&mut buf);
2527 B256::from_slice(&buf)
2528 };
2529
2530 let mut storage = BTreeMap::new();
2532 for _ in 0..10240 {
2533 let hashed_slot = rand_b256();
2534 storage.insert(hashed_slot, U256::from(1u64));
2535 }
2536
2537 let mut targets = storage.keys().copied().collect::<Vec<_>>();
2540 for _ in 0..storage.len() / 5 {
2541 targets.push(rand_b256());
2542 }
2543 targets.sort();
2544
2545 let harness = ProofTestHarness::new(storage);
2547
2548 harness
2549 .assert_proof(targets.into_iter().map(ProofV2Target::new))
2550 .expect("Proof generation failed");
2551 }
2552
2553 #[test]
2554 fn test_node_with_masked_empty_child() {
2555 reth_tracing::init_test_tracing();
2556
2557 let val = U256::from(42u64);
2558
2559 let slot_60 = B256::right_padding_from(&[0x60]);
2562 let slot_61 = B256::right_padding_from(&[0x61]);
2563 let slot_65 = B256::right_padding_from(&[0x65]);
2564 let slot_67 = B256::right_padding_from(&[0x67]);
2565
2566 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];
2572 let branch = BranchNodeCompact::new(state_mask, TrieMask::new(0), hash_mask, hashes, None);
2573
2574 let storage_nodes: BTreeMap<Nibbles, BranchNodeCompact> =
2575 std::iter::once((Nibbles::from_nibbles([0x6]), branch)).collect();
2576
2577 let mut harness = TrieTestHarness::new(
2580 [slot_60, slot_61, slot_65, slot_67].iter().map(|s| (*s, val)).collect(),
2581 );
2582 harness.set_trie_nodes(storage_nodes);
2583
2584 let storage_trie_cursor =
2585 harness.trie_cursor_factory().storage_trie_cursor(harness.hashed_address()).unwrap();
2586 let hashed_storage_cursor = harness
2587 .hashed_cursor_factory()
2588 .hashed_storage_cursor(harness.hashed_address())
2589 .unwrap();
2590 let mut calculator =
2591 StorageProofCalculator::new_storage(storage_trie_cursor, hashed_storage_cursor);
2592 let root_node = calculator
2593 .storage_root_node(harness.hashed_address())
2594 .expect("storage_root_node should succeed with masked empty child");
2595
2596 let root_hash = calculator.compute_root_hash(core::slice::from_ref(&root_node)).unwrap();
2597 assert!(root_hash.is_some(), "should produce a root hash");
2598 }
2599
2600 #[test]
2610 fn test_node_with_masked_empty_child_lower_bound_past_branch() {
2611 reth_tracing::init_test_tracing();
2612
2613 let val = U256::from(42u64);
2614
2615 let slot_60 = B256::right_padding_from(&[0x60]);
2617 let slot_61 = B256::right_padding_from(&[0x61]);
2618 let slot_6f = B256::right_padding_from(&[0x6f]);
2619 let slot_70 = B256::right_padding_from(&[0x70]);
2620
2621 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];
2627 let branch = BranchNodeCompact::new(state_mask, TrieMask::new(0), hash_mask, hashes, None);
2628
2629 let storage_nodes: BTreeMap<Nibbles, BranchNodeCompact> =
2630 std::iter::once((Nibbles::from_nibbles([0x6]), branch)).collect();
2631
2632 let mut harness = TrieTestHarness::new(
2634 [slot_60, slot_61, slot_6f, slot_70].iter().map(|s| (*s, val)).collect(),
2635 );
2636 harness.set_trie_nodes(storage_nodes);
2637
2638 let storage_trie_cursor =
2639 harness.trie_cursor_factory().storage_trie_cursor(harness.hashed_address()).unwrap();
2640 let hashed_storage_cursor = harness
2641 .hashed_cursor_factory()
2642 .hashed_storage_cursor(harness.hashed_address())
2643 .unwrap();
2644 let mut calculator =
2645 StorageProofCalculator::new_storage(storage_trie_cursor, hashed_storage_cursor);
2646 let root_node = calculator
2647 .storage_root_node(harness.hashed_address())
2648 .expect("storage_root_node should succeed when lower bound advances past branch");
2649
2650 let root_hash = calculator.compute_root_hash(core::slice::from_ref(&root_node)).unwrap();
2651 assert!(root_hash.is_some(), "should produce a root hash");
2652 }
2653
2654 #[test]
2664 fn test_prefix_set_adds_child_nibbles() {
2665 reth_tracing::init_test_tracing();
2666
2667 let val = U256::from(42u64);
2668 let slot_60 = B256::right_padding_from(&[0x60]);
2669 let slot_61 = B256::right_padding_from(&[0x61]);
2670 let slot_63 = B256::right_padding_from(&[0x63]);
2671
2672 let harness = TrieTestHarness::new([(slot_60, val), (slot_61, val)].into_iter().collect());
2673
2674 let changeset: BTreeMap<B256, U256> = std::iter::once((slot_63, val)).collect();
2675 let (expected_root, _) = harness.get_root_with_updates(&changeset);
2676
2677 let mut updated_storage = harness.storage().clone();
2678 updated_storage.insert(slot_63, val);
2679
2680 let updated_hashed = MockHashedCursorFactory::new(
2681 BTreeMap::new(),
2682 std::iter::once((harness.hashed_address(), updated_storage)).collect(),
2683 );
2684
2685 let mut prefix_set = PrefixSetMut::default();
2686 prefix_set.insert(Nibbles::unpack(slot_63));
2687
2688 let trie_cursor =
2689 harness.trie_cursor_factory().storage_trie_cursor(harness.hashed_address()).unwrap();
2690 let hashed_cursor = updated_hashed.hashed_storage_cursor(harness.hashed_address()).unwrap();
2691 let mut calculator = StorageProofCalculator::new_storage(trie_cursor, hashed_cursor)
2692 .with_prefix_set(prefix_set.freeze());
2693 let root_node = calculator
2694 .storage_root_node(harness.hashed_address())
2695 .expect("storage_root_node should succeed with prefix set adding child nibbles");
2696 let got_root =
2697 calculator.compute_root_hash(core::slice::from_ref(&root_node)).unwrap().unwrap();
2698
2699 pretty_assertions::assert_eq!(
2700 expected_root,
2701 got_root,
2702 "Root hash with prefix set should match fresh computation"
2703 );
2704 }
2705
2706 #[test]
2715 fn test_prefix_set_invalidates_cached_hash() {
2716 reth_tracing::init_test_tracing();
2717
2718 let original_val = U256::from(42u64);
2719 let updated_val = U256::from(9999u64);
2720 let slot_60 = B256::right_padding_from(&[0x60]);
2721 let slot_61 = B256::right_padding_from(&[0x61]);
2722 let slot_65 = B256::right_padding_from(&[0x65]);
2723
2724 let harness = TrieTestHarness::new(
2725 [(slot_60, original_val), (slot_61, original_val), (slot_65, original_val)]
2726 .into_iter()
2727 .collect(),
2728 );
2729
2730 let changeset: BTreeMap<B256, U256> = std::iter::once((slot_65, updated_val)).collect();
2731 let (expected_root, _) = harness.get_root_with_updates(&changeset);
2732
2733 let mut updated_storage = harness.storage().clone();
2734 updated_storage.insert(slot_65, updated_val);
2735
2736 let updated_hashed = MockHashedCursorFactory::new(
2737 BTreeMap::new(),
2738 std::iter::once((harness.hashed_address(), updated_storage)).collect(),
2739 );
2740
2741 let mut prefix_set = PrefixSetMut::default();
2742 prefix_set.insert(Nibbles::unpack(slot_65));
2743
2744 let trie_cursor =
2745 harness.trie_cursor_factory().storage_trie_cursor(harness.hashed_address()).unwrap();
2746 let hashed_cursor = updated_hashed.hashed_storage_cursor(harness.hashed_address()).unwrap();
2747 let mut calculator = StorageProofCalculator::new_storage(trie_cursor, hashed_cursor)
2748 .with_prefix_set(prefix_set.freeze());
2749 let root_node = calculator
2750 .storage_root_node(harness.hashed_address())
2751 .expect("storage_root_node should succeed with prefix set invalidating cached hash");
2752 let got_root =
2753 calculator.compute_root_hash(core::slice::from_ref(&root_node)).unwrap().unwrap();
2754
2755 pretty_assertions::assert_eq!(
2756 expected_root,
2757 got_root,
2758 "Root hash with prefix set invalidation should match fresh computation"
2759 );
2760 }
2761
2762 fn b256(s: &str) -> B256 {
2763 B256::from_slice(&alloy_primitives::hex::decode(s).expect("valid hex string"))
2764 }
2765
2766 #[test]
2767 fn test_prefix_set_root_proof_processes_sibling_after_cached_descendant() {
2768 reth_tracing::init_test_tracing();
2769
2770 let storage = [
2771 ("1022c69e9d900e40775cd387c134899f465f291dbc3c97899ff6bfb8dc972b37", 45u64),
2772 ("1111ad8083c8a3a398b2b781217b989ff4d1ed182f46cc765eda49a7b316139d", 60),
2773 ("12012d20943649899b2fc0f87b9840b70ef68e93613aac17c269bf8c5a78a712", 17),
2774 ("12014b57b9a162c03d072eb6acd4e936f1c4bc23b803a054347c5ee9a9bcfb9a", 49),
2775 ("1203f800840af3f898ab4572f2750106a7c4bd2b3e844b6e7fa72704673cc2c6", 76),
2776 ("12208f18fbcd6971c92808721392acbf11d5af58e9143a374cc86e70bdd1f097", 10),
2777 ]
2778 .into_iter()
2779 .map(|(key, value)| (b256(key), U256::from(value)))
2780 .collect();
2781
2782 let dirty = b256("12208f18fbcd6971c92808721392acbf11d5af58e9143a374cc86e70bdd1f097");
2783 let harness = ProofTestHarness::new(storage);
2784 let expected_root = harness.original_root();
2785
2786 let mut prefix_set = PrefixSetMut::default();
2787 prefix_set.insert(Nibbles::unpack(dirty));
2788
2789 pretty_assertions::assert_eq!(
2790 Some(expected_root),
2791 harness.root_with_prefix_set(prefix_set.freeze()),
2792 "root proof must process a prefix-set sibling after a cached descendant",
2793 );
2794 }
2795
2796 #[test]
2797 fn test_prefix_set_root_proof_processes_trailing_dirty_sibling() {
2798 reth_tracing::init_test_tracing();
2799
2800 let keys = [
2801 "0022001020000000000000000000000000000000000000000000000000000000",
2802 "0110212112000000000000000000000000000000000000000000000000000000",
2803 "0202210210000000000000000000000000000000000000000000000000000000",
2804 "0211020211000000000000000000000000000000000000000000000000000000",
2805 "0211211002000000000000000000000000000000000000000000000000000000",
2806 "0212221010000000000000000000000000000000000000000000000000000000",
2807 "0222011102000000000000000000000000000000000000000000000000000000",
2808 ];
2809 let storage =
2810 keys.iter().enumerate().map(|(i, key)| (b256(key), U256::from(i as u64 + 1))).collect();
2811 let harness = ProofTestHarness::new(storage);
2812 let expected_root = harness.original_root();
2813
2814 let mut prefix_set = PrefixSetMut::default();
2817 prefix_set.insert(Nibbles::unpack(b256(keys[2])));
2818 prefix_set.insert(Nibbles::unpack(b256(keys[6])));
2819
2820 pretty_assertions::assert_eq!(
2821 Some(expected_root),
2822 harness.root_with_prefix_set(prefix_set.freeze()),
2823 );
2824 }
2825
2826 fn storage_leaf_hash(short_key: &Nibbles, value: &U256) -> B256 {
2829 let mut buf = Vec::new();
2830 alloy_trie::nodes::LeafNodeRef::new(short_key, &alloy_rlp::encode_fixed_size(value))
2831 .encode(&mut buf);
2832 keccak256(&buf)
2833 }
2834
2835 fn assert_branch_collapse(remaining_nibble: u8, removed_nibble: u8) {
2837 reth_tracing::init_test_tracing();
2838
2839 let val = U256::from(1u64);
2840 let child_keys = |nibble| {
2841 [
2842 B256::right_padding_from(&[0x20 | nibble, 0x00]),
2843 B256::right_padding_from(&[0x20 | nibble, 0x10]),
2844 ]
2845 };
2846 let [remaining_a, remaining_b] = child_keys(remaining_nibble);
2847 let [removed_a, removed_b] = child_keys(removed_nibble);
2848
2849 let initial_storage = BTreeMap::from([
2852 (remaining_a, val),
2853 (remaining_b, val),
2854 (removed_a, val),
2855 (removed_b, val),
2856 ]);
2857 let harness = TrieTestHarness::new(initial_storage);
2858
2859 let cached_branch = &harness
2860 .storage_trie_updates()
2861 .storage_nodes
2862 .get(&Nibbles::from_nibbles([0x2]))
2863 .expect("branch at 0x2");
2864 let child_mask =
2865 TrieMask::from_nibble(remaining_nibble) | TrieMask::from_nibble(removed_nibble);
2866 assert_eq!(cached_branch.state_mask, child_mask);
2867 assert_eq!(cached_branch.hash_mask, child_mask);
2868 assert!(cached_branch.tree_mask.is_empty());
2869
2870 let final_storage = BTreeMap::from([(remaining_a, val), (remaining_b, val)]);
2871 let expected_root = TrieTestHarness::new(final_storage.clone()).original_root();
2872 let updated_hashed = MockHashedCursorFactory::new(
2873 BTreeMap::new(),
2874 std::iter::once((harness.hashed_address(), final_storage)).collect(),
2875 );
2876
2877 let mut prefix_set = PrefixSetMut::default();
2879 prefix_set.insert(Nibbles::unpack(removed_a));
2880 prefix_set.insert(Nibbles::unpack(removed_b));
2881
2882 let trie_cursor =
2883 harness.trie_cursor_factory().storage_trie_cursor(harness.hashed_address()).unwrap();
2884 let hashed_cursor = updated_hashed.hashed_storage_cursor(harness.hashed_address()).unwrap();
2885 let mut calculator = StorageProofCalculator::new_storage(trie_cursor, hashed_cursor)
2886 .with_prefix_set(prefix_set.freeze());
2887 let root_node = calculator
2888 .storage_root_node(harness.hashed_address())
2889 .expect("storage_root_node should succeed after branch collapse");
2890 let root =
2891 calculator.compute_root_hash(core::slice::from_ref(&root_node)).unwrap().unwrap();
2892
2893 pretty_assertions::assert_eq!(expected_root, root);
2894 }
2895
2896 #[test]
2897 fn test_branch_collapse_removed_child_before_remaining() {
2898 assert_branch_collapse(1, 0);
2899 }
2900
2901 #[test]
2902 fn test_branch_collapse_removed_child_after_remaining() {
2903 assert_branch_collapse(4, 9);
2904 }
2905
2906 #[test]
2907 fn test_prefix_set_root_proof_preserves_clean_sibling_after_cached_branch_collapse() {
2908 reth_tracing::init_test_tracing();
2909
2910 let dirty = B256::right_padding_from(&[0x10, 0x00, 0x10]);
2911 let clean_sibling = B256::right_padding_from(&[0x10, 0x10]);
2912 let storage = [
2913 B256::right_padding_from(&[0x10]),
2914 dirty,
2915 B256::right_padding_from(&[0x10, 0x01]),
2916 B256::right_padding_from(&[0x10, 0x02]),
2917 clean_sibling,
2918 B256::right_padding_from(&[0x11]),
2919 B256::right_padding_from(&[0x12]),
2920 ]
2921 .into_iter()
2922 .map(|key| (key, U256::from(1u64)))
2923 .collect();
2924
2925 let harness = ProofTestHarness::new(storage);
2926 let expected_root = harness.original_root();
2927
2928 let mut prefix_set = PrefixSetMut::default();
2929 prefix_set.insert(Nibbles::unpack(dirty));
2930
2931 let mut prefix_set_with_sibling = PrefixSetMut::default();
2932 prefix_set_with_sibling.insert(Nibbles::unpack(dirty));
2933 prefix_set_with_sibling.insert(Nibbles::unpack(clean_sibling));
2934
2935 pretty_assertions::assert_eq!(
2936 Some(expected_root),
2937 harness.root_with_prefix_set(prefix_set_with_sibling.freeze()),
2938 );
2939 pretty_assertions::assert_eq!(
2940 Some(expected_root),
2941 harness.root_with_prefix_set(prefix_set.freeze()),
2942 "a dirty prefix must not omit a clean sibling after collapsing a cached branch",
2943 );
2944 }
2945
2946 #[test]
2947 fn test_prefix_set_range_skips_covered_cached_branch() {
2948 reth_tracing::init_test_tracing();
2949
2950 let before = B256::right_padding_from(&[0x30]);
2951 let cached_a = B256::right_padding_from(&[0x80, 0x10]);
2952 let cached_b = B256::right_padding_from(&[0x80, 0x15]);
2953 let dirty = B256::right_padding_from(&[0x80, 0xc0]);
2954 let after = B256::right_padding_from(&[0x90]);
2955
2956 let storage = [before, cached_a, cached_b, dirty, after]
2959 .into_iter()
2960 .enumerate()
2961 .map(|(i, key)| (key, U256::from(i + 1)))
2962 .collect();
2963
2964 let harness = ProofTestHarness::new(storage);
2965 let expected_root = harness.original_root();
2966 let mut prefix_set = PrefixSetMut::default();
2967 prefix_set.insert(Nibbles::unpack(dirty));
2968
2969 pretty_assertions::assert_eq!(
2970 Some(expected_root),
2971 harness.root_with_prefix_set(prefix_set.freeze()),
2972 );
2973 }
2974
2975 #[test]
2976 fn test_cached_branch_extension_skips_diverging_target() {
2977 reth_tracing::init_test_tracing();
2978
2979 let val = U256::from(100u64);
2980
2981 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> =
2990 [(key_a0, val), (key_a1, val), (key_c, val), (key_d, val), (key_e, val)]
2991 .into_iter()
2992 .collect();
2993 let correct_harness = TrieTestHarness::new(all_storage.clone());
2994 let expected_root = correct_harness.original_root();
2995
2996 let leaf_hash_a0 = storage_leaf_hash(&Nibbles::unpack(key_a0).slice(4..), &val);
2998 let leaf_hash_a1 = storage_leaf_hash(&Nibbles::unpack(key_a1).slice(4..), &val);
2999 let leaf_hash_d = storage_leaf_hash(&Nibbles::unpack(key_d).slice(2..), &val);
3000 let leaf_hash_e = storage_leaf_hash(&Nibbles::unpack(key_e).slice(2..), &val);
3001
3002 let branch_6_state_mask = TrieMask::new((1 << 0xa) | (1 << 0xb) | (1 << 0xc));
3009 let branch_6_hash_mask = TrieMask::new((1 << 0xb) | (1 << 0xc));
3010 let branch_6 = BranchNodeCompact::new(
3011 branch_6_state_mask,
3012 TrieMask::new(0),
3013 branch_6_hash_mask,
3014 vec![leaf_hash_d, leaf_hash_e],
3015 None,
3016 );
3017
3018 let branch_6a3_state_mask = TrieMask::new((1 << 0) | (1 << 1));
3022 let branch_6a3 = BranchNodeCompact::new(
3023 branch_6a3_state_mask,
3024 TrieMask::new(0),
3025 branch_6a3_state_mask,
3026 vec![leaf_hash_a0, leaf_hash_a1],
3027 None,
3028 );
3029
3030 let inconsistent_nodes: BTreeMap<Nibbles, BranchNodeCompact> = [
3032 (Nibbles::from_nibbles([0x6]), branch_6),
3033 (Nibbles::from_nibbles([0x6, 0xa, 0x3]), branch_6a3),
3034 ]
3035 .into_iter()
3036 .collect();
3037
3038 let mut harness = TrieTestHarness::new(all_storage);
3040 harness.set_trie_nodes(inconsistent_nodes);
3041
3042 let mut prefix_set = PrefixSetMut::default();
3045 prefix_set.insert(Nibbles::unpack(key_c));
3046
3047 let trie_cursor =
3049 harness.trie_cursor_factory().storage_trie_cursor(harness.hashed_address()).unwrap();
3050 let hashed_cursor = harness
3051 .hashed_cursor_factory()
3052 .hashed_storage_cursor(harness.hashed_address())
3053 .unwrap();
3054 let mut calculator = StorageProofCalculator::new_storage(trie_cursor, hashed_cursor)
3055 .with_prefix_set(prefix_set.freeze());
3056
3057 let root_node = calculator
3058 .storage_root_node(harness.hashed_address())
3059 .expect("storage_root_node should succeed");
3060 let got_root = calculator
3061 .compute_root_hash(core::slice::from_ref(&root_node))
3062 .unwrap()
3063 .expect("should produce a root hash");
3064
3065 pretty_assertions::assert_eq!(
3067 expected_root,
3068 got_root,
3069 "Root hash should match correct trie; cached extension must not skip diverging leaves"
3070 );
3071
3072 let mut targets = vec![ProofV2Target::new(key_c)];
3074 let proofs = calculator
3075 .storage_proof(harness.hashed_address(), &mut targets)
3076 .expect("storage_proof should succeed");
3077
3078 let key_c_nibbles = Nibbles::unpack(key_c);
3079 let has_matching_node = proofs.iter().any(|node| key_c_nibbles.starts_with(&node.path));
3080 assert!(
3081 has_matching_node,
3082 "Proof for key_c should contain at least one node on key_c's path, got: {proofs:?}"
3083 );
3084 }
3085
3086 #[test]
3087 fn test_cached_branch_extension_skips_diverging_target_before() {
3088 reth_tracing::init_test_tracing();
3089
3090 let val = U256::from(100u64);
3091
3092 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> =
3102 [(key_a0, val), (key_a1, val), (key_c, val), (key_d, val), (key_e, val)]
3103 .into_iter()
3104 .collect();
3105 let correct_harness = TrieTestHarness::new(all_storage.clone());
3106 let expected_root = correct_harness.original_root();
3107
3108 let leaf_hash_a0 = storage_leaf_hash(&Nibbles::unpack(key_a0).slice(4..), &val);
3110 let leaf_hash_a1 = storage_leaf_hash(&Nibbles::unpack(key_a1).slice(4..), &val);
3111 let leaf_hash_d = storage_leaf_hash(&Nibbles::unpack(key_d).slice(2..), &val);
3112 let leaf_hash_e = storage_leaf_hash(&Nibbles::unpack(key_e).slice(2..), &val);
3113
3114 let branch_6_state_mask = TrieMask::new((1 << 0xa) | (1 << 0xb) | (1 << 0xc));
3121 let branch_6_hash_mask = TrieMask::new((1 << 0xb) | (1 << 0xc));
3122 let branch_6 = BranchNodeCompact::new(
3123 branch_6_state_mask,
3124 TrieMask::new(0),
3125 branch_6_hash_mask,
3126 vec![leaf_hash_d, leaf_hash_e],
3127 None,
3128 );
3129
3130 let branch_6a8_state_mask = TrieMask::new((1 << 0) | (1 << 1));
3134 let branch_6a8 = BranchNodeCompact::new(
3135 branch_6a8_state_mask,
3136 TrieMask::new(0),
3137 branch_6a8_state_mask,
3138 vec![leaf_hash_a0, leaf_hash_a1],
3139 None,
3140 );
3141
3142 let inconsistent_nodes: BTreeMap<Nibbles, BranchNodeCompact> = [
3144 (Nibbles::from_nibbles([0x6]), branch_6),
3145 (Nibbles::from_nibbles([0x6, 0xa, 0x8]), branch_6a8),
3146 ]
3147 .into_iter()
3148 .collect();
3149
3150 let mut harness = TrieTestHarness::new(all_storage);
3152 harness.set_trie_nodes(inconsistent_nodes);
3153
3154 let mut prefix_set = PrefixSetMut::default();
3156 prefix_set.insert(Nibbles::unpack(key_c));
3157
3158 let trie_cursor =
3160 harness.trie_cursor_factory().storage_trie_cursor(harness.hashed_address()).unwrap();
3161 let hashed_cursor = harness
3162 .hashed_cursor_factory()
3163 .hashed_storage_cursor(harness.hashed_address())
3164 .unwrap();
3165 let mut calculator = StorageProofCalculator::new_storage(trie_cursor, hashed_cursor)
3166 .with_prefix_set(prefix_set.freeze());
3167
3168 let root_node = calculator
3169 .storage_root_node(harness.hashed_address())
3170 .expect("storage_root_node should succeed");
3171 let got_root = calculator
3172 .compute_root_hash(core::slice::from_ref(&root_node))
3173 .unwrap()
3174 .expect("should produce a root hash");
3175
3176 pretty_assertions::assert_eq!(
3178 expected_root,
3179 got_root,
3180 "Root hash should match correct trie; cached extension must not skip diverging leaves before cached branch"
3181 );
3182
3183 let mut targets = vec![ProofV2Target::new(key_c)];
3185 let proofs = calculator
3186 .storage_proof(harness.hashed_address(), &mut targets)
3187 .expect("storage_proof should succeed");
3188
3189 let key_c_nibbles = Nibbles::unpack(key_c);
3190 let has_matching_node = proofs.iter().any(|node| key_c_nibbles.starts_with(&node.path));
3191 assert!(
3192 has_matching_node,
3193 "Proof for key_c should contain at least one node on key_c's path, got: {proofs:?}"
3194 );
3195 }
3196
3197 #[test]
3198 fn test_skipped_parent_branch_with_unskipped_child() {
3199 reth_tracing::init_test_tracing();
3200
3201 let val = U256::from(1u64);
3202 let updated_val = U256::from(2u64);
3203
3204 let key_2 = B256::right_padding_from(&[0x20]);
3206 let key_2f00 = B256::right_padding_from(&[0x2f, 0x00]);
3207 let key_2f01 = B256::right_padding_from(&[0x2f, 0x01]);
3208 let key_2f10 = B256::right_padding_from(&[0x2f, 0x10]);
3209 let key_2f11 = B256::right_padding_from(&[0x2f, 0x11]);
3210 let key_300 = B256::right_padding_from(&[0x30, 0x00]);
3211 let key_301 = B256::right_padding_from(&[0x30, 0x10]);
3212 let key_310 = B256::right_padding_from(&[0x31, 0x00]);
3213 let key_311 = B256::right_padding_from(&[0x31, 0x10]);
3214 let key_500 = B256::right_padding_from(&[0x50, 0x00]);
3215 let key_501 = B256::right_padding_from(&[0x50, 0x10]);
3216 let key_510 = B256::right_padding_from(&[0x51, 0x00]);
3217 let key_511 = B256::right_padding_from(&[0x51, 0x10]);
3218
3219 let all_keys = [
3220 key_2, key_2f00, key_2f01, key_2f10, key_2f11, key_300, key_301, key_310, key_311,
3221 key_500, key_501, key_510, key_511,
3222 ];
3223
3224 let original_storage: BTreeMap<B256, U256> = all_keys.iter().map(|k| (*k, val)).collect();
3225 let harness = TrieTestHarness::new(original_storage);
3226
3227 let trie_updates = harness.storage_trie_updates();
3229 assert!(trie_updates.storage_nodes.contains_key(&Nibbles::from_nibbles([0x2])));
3230 assert!(trie_updates.storage_nodes.contains_key(&Nibbles::from_nibbles([0x2, 0xf])));
3231 assert!(trie_updates.storage_nodes.contains_key(&Nibbles::from_nibbles([0x3])));
3232
3233 let changeset: BTreeMap<B256, U256> = std::iter::once((key_2, updated_val)).collect();
3236 let (expected_root, _) = harness.get_root_with_updates(&changeset);
3237
3238 let mut updated_storage = harness.storage().clone();
3239 updated_storage.insert(key_2, updated_val);
3240
3241 let updated_hashed = MockHashedCursorFactory::new(
3242 BTreeMap::new(),
3243 std::iter::once((harness.hashed_address(), updated_storage)).collect(),
3244 );
3245
3246 let mut prefix_set = PrefixSetMut::default();
3247 prefix_set.insert(Nibbles::unpack(key_2));
3248
3249 let trie_cursor =
3250 harness.trie_cursor_factory().storage_trie_cursor(harness.hashed_address()).unwrap();
3251 let hashed_cursor = updated_hashed.hashed_storage_cursor(harness.hashed_address()).unwrap();
3252 let mut calculator = StorageProofCalculator::new_storage(trie_cursor, hashed_cursor)
3253 .with_prefix_set(prefix_set.freeze());
3254 let root_node = calculator
3255 .storage_root_node(harness.hashed_address())
3256 .expect("storage_root_node should succeed");
3257
3258 let got_root = calculator
3259 .compute_root_hash(&[root_node])
3260 .expect("root hash should succeed")
3261 .expect("root should get hashed");
3262 pretty_assertions::assert_eq!(expected_root, got_root);
3263 }
3264
3265 #[test]
3266 fn test_blinded_local_root_returns_trie_inconsistency() {
3267 let key = B256::right_padding_from(&[0x63, 0xaa]);
3268 let value = U256::from(1);
3269 let hash = storage_leaf_hash(&Nibbles::unpack(key).slice(2..), &value);
3270 let mask = TrieMask::from_nibble(3);
3271 let cached_branch =
3272 BranchNodeCompact::new(mask, TrieMask::default(), mask, vec![hash], None);
3273
3274 let mut harness = TrieTestHarness::new(BTreeMap::from([(key, value)]));
3275 harness.set_trie_nodes(BTreeMap::from([(Nibbles::from_nibbles([6]), cached_branch)]));
3276
3277 let trie_cursor =
3278 harness.trie_cursor_factory().storage_trie_cursor(harness.hashed_address()).unwrap();
3279 let hashed_cursor = harness
3280 .hashed_cursor_factory()
3281 .hashed_storage_cursor(harness.hashed_address())
3282 .unwrap();
3283 let mut calculator = StorageProofCalculator::new_storage(trie_cursor, hashed_cursor);
3284
3285 assert!(matches!(
3286 calculator.storage_root_node(harness.hashed_address()),
3287 Err(StateProofError::TrieInconsistency(_))
3288 ));
3289 }
3290
3291 #[test]
3292 fn test_cached_hash_with_deleted_leaf() {
3293 reth_tracing::init_test_tracing();
3294
3295 let val_3 = U256::from(111u64);
3297 let val_5 = U256::from(222u64);
3298 let val_8 = U256::from(333u64);
3299
3300 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);
3309 let leaf_hash_5 = storage_leaf_hash(&Nibbles::unpack(key_65).slice(2..), &val_5);
3310 let leaf_hash_8 = storage_leaf_hash(&Nibbles::unpack(key_68).slice(2..), &val_8);
3311
3312 let state_mask = TrieMask::new((1 << 3) | (1 << 5) | (1 << 8));
3314 let cached_branch = BranchNodeCompact::new(
3315 state_mask,
3316 TrieMask::new(0),
3317 state_mask, vec![leaf_hash_3, leaf_hash_5, leaf_hash_8],
3319 None,
3320 );
3321
3322 let storage_nodes: BTreeMap<Nibbles, BranchNodeCompact> =
3323 std::iter::once((Nibbles::from_nibbles([0x6]), cached_branch)).collect();
3324
3325 let mut harness =
3327 TrieTestHarness::new([(key_65, val_5), (key_68, val_8)].into_iter().collect());
3328 let expected_root = harness.original_root();
3329
3330 harness.set_trie_nodes(storage_nodes);
3332
3333 let mut prefix_set = PrefixSetMut::default();
3336 prefix_set.insert(Nibbles::unpack(key_63));
3337
3338 let mut targets = vec![ProofV2Target::new(key_63)];
3342
3343 let trie_cursor =
3344 harness.trie_cursor_factory().storage_trie_cursor(harness.hashed_address()).unwrap();
3345 let hashed_cursor = harness
3346 .hashed_cursor_factory()
3347 .hashed_storage_cursor(harness.hashed_address())
3348 .unwrap();
3349 let mut calculator = StorageProofCalculator::new_storage(trie_cursor, hashed_cursor)
3350 .with_prefix_set(prefix_set.freeze());
3351
3352 let proofs = calculator
3353 .storage_proof(harness.hashed_address(), &mut targets)
3354 .expect("storage_proof should succeed");
3355 assert_eq!(1, proofs.len());
3356 let got_root = calculator
3357 .compute_root_hash(&proofs)
3358 .expect("compute_root_hash should succeed")
3359 .expect("should produce a root hash (proof contains root node)");
3360
3361 pretty_assertions::assert_eq!(
3364 expected_root,
3365 got_root,
3366 "Root hash should match trie without key_63; cached hash index is off when \
3367 an earlier hashed child has no leaves (absence proof target)"
3368 );
3369 }
3370}