1use crate::{
4 BranchNodeMasks, BranchNodeMasksMap, Nibbles, ProofTrieNodeV2, TrieAccount, TrieNodeV2,
5};
6use alloc::{borrow::Cow, collections::VecDeque, vec::Vec};
7use alloy_primitives::{
8 keccak256,
9 map::{hash_map, B256Map, B256Set},
10 Address, Bytes, B256, U256,
11};
12use alloy_rlp::{encode_fixed_size, Decodable, Encodable, EMPTY_STRING_CODE};
13use alloy_trie::{
14 nodes::{BranchNodeRef, TrieNode},
15 proof::{verify_proof, DecodedProofNodes, ProofNodes, ProofVerificationError},
16 EMPTY_ROOT_HASH,
17};
18use derive_more::{Deref, DerefMut, IntoIterator};
19use itertools::Itertools;
20use reth_primitives_traits::Account;
21
22#[derive(Deref, DerefMut, IntoIterator, Clone, PartialEq, Eq, Default, Debug)]
24pub struct MultiProofTargets(B256Map<B256Set>);
25
26impl FromIterator<(B256, B256Set)> for MultiProofTargets {
27 fn from_iter<T: IntoIterator<Item = (B256, B256Set)>>(iter: T) -> Self {
28 Self(B256Map::from_iter(iter))
29 }
30}
31
32impl MultiProofTargets {
33 pub fn with_capacity(capacity: usize) -> Self {
35 Self(B256Map::with_capacity_and_hasher(capacity, Default::default()))
36 }
37
38 pub fn account(hashed_address: B256) -> Self {
40 Self::accounts([hashed_address])
41 }
42
43 pub fn account_with_slots<I: IntoIterator<Item = B256>>(
45 hashed_address: B256,
46 slots_iter: I,
47 ) -> Self {
48 Self(B256Map::from_iter([(hashed_address, slots_iter.into_iter().collect())]))
49 }
50
51 pub fn accounts<I: IntoIterator<Item = B256>>(iter: I) -> Self {
53 Self(iter.into_iter().map(|hashed_address| (hashed_address, Default::default())).collect())
54 }
55
56 pub fn retain_difference(&mut self, other: &Self) {
59 self.0.retain(|hashed_address, hashed_slots| {
60 other.get(hashed_address).is_none_or(|other_hashed_slots| {
61 hashed_slots.retain(|hashed_slot| !other_hashed_slots.contains(hashed_slot));
62 !hashed_slots.is_empty()
63 })
64 });
65 }
66
67 pub fn extend(&mut self, other: Self) {
69 self.extend_inner(Cow::Owned(other));
70 }
71
72 pub fn extend_ref(&mut self, other: &Self) {
76 self.extend_inner(Cow::Borrowed(other));
77 }
78
79 fn extend_inner(&mut self, other: Cow<'_, Self>) {
80 for (hashed_address, hashed_slots) in other.iter() {
81 match self.entry(*hashed_address) {
82 hash_map::Entry::Vacant(entry) => {
83 entry.insert(hashed_slots.clone());
84 }
85 hash_map::Entry::Occupied(mut entry) => {
86 entry.get_mut().extend(hashed_slots);
87 }
88 }
89 }
90 }
91
92 pub fn chunks(self, size: usize) -> ChunkedMultiProofTargets {
100 ChunkedMultiProofTargets::new(self, size)
101 }
102
103 pub fn chunking_length(&self) -> usize {
105 self.values().map(|slots| 1 + slots.len().saturating_sub(1)).sum::<usize>()
106 }
107}
108
109#[derive(Debug)]
130pub struct ChunkedMultiProofTargets {
131 flattened_targets: alloc::vec::IntoIter<(B256, Option<B256>)>,
132 size: usize,
133}
134
135impl ChunkedMultiProofTargets {
136 fn new(targets: MultiProofTargets, size: usize) -> Self {
137 assert!(size > 0, "chunk size must be non-zero");
138 let flattened_targets = targets
139 .into_iter()
140 .flat_map(|(address, slots)| {
141 if slots.is_empty() {
142 itertools::Either::Left(core::iter::once((address, None)))
146 } else {
147 itertools::Either::Right(
148 slots.into_iter().map(move |slot| (address, Some(slot))),
149 )
150 }
151 })
152 .sorted_unstable();
153 Self { flattened_targets, size }
154 }
155}
156
157impl Iterator for ChunkedMultiProofTargets {
158 type Item = MultiProofTargets;
159
160 fn next(&mut self) -> Option<Self::Item> {
161 let chunk = self.flattened_targets.by_ref().take(self.size).fold(
162 MultiProofTargets::default(),
163 |mut acc, (address, slot)| {
164 let entry = acc.entry(address).or_default();
165 if let Some(slot) = slot {
166 entry.insert(slot);
167 }
168 acc
169 },
170 );
171
172 if chunk.is_empty() {
173 None
174 } else {
175 Some(chunk)
176 }
177 }
178}
179
180#[derive(Clone, Default, Debug, PartialEq, Eq)]
184pub struct MultiProof {
185 pub account_subtree: ProofNodes,
187 pub branch_node_masks: BranchNodeMasksMap,
190 pub storages: B256Map<StorageMultiProof>,
192}
193
194impl MultiProof {
195 pub fn is_empty(&self) -> bool {
197 self.account_subtree.is_empty() &&
198 self.branch_node_masks.is_empty() &&
199 self.storages.is_empty()
200 }
201
202 pub fn account_proof_nodes(&self, path: &Nibbles) -> Vec<(Nibbles, Bytes)> {
204 self.account_subtree.matching_nodes_sorted(path)
205 }
206
207 pub fn storage_proof_nodes(
209 &self,
210 hashed_address: B256,
211 slots: impl IntoIterator<Item = B256>,
212 ) -> Vec<(B256, Vec<(Nibbles, Bytes)>)> {
213 self.storages
214 .get(&hashed_address)
215 .map(|storage_mp| {
216 slots
217 .into_iter()
218 .map(|slot| {
219 let nibbles = Nibbles::unpack(slot);
220 (slot, storage_mp.subtree.matching_nodes_sorted(&nibbles))
221 })
222 .collect()
223 })
224 .unwrap_or_default()
225 }
226
227 pub fn account_proof(
229 &self,
230 address: Address,
231 slots: &[B256],
232 ) -> Result<AccountProof, alloy_rlp::Error> {
233 let hashed_address = keccak256(address);
234 let nibbles = Nibbles::unpack(hashed_address);
235
236 let proof = self
238 .account_proof_nodes(&nibbles)
239 .into_iter()
240 .map(|(_, node)| node)
241 .collect::<Vec<_>>();
242
243 let info = 'info: {
246 if let Some(last) = proof.last() &&
247 let TrieNode::Leaf(leaf) = TrieNode::decode(&mut &last[..])? &&
248 nibbles.ends_with(&leaf.key)
249 {
250 let account = TrieAccount::decode(&mut &leaf.value[..])?;
251 break 'info Some(Account::from(account))
252 }
253 None
254 };
255
256 let storage_multiproof = self.storages.get(&hashed_address);
258 let storage_root = storage_multiproof.map(|m| m.root).unwrap_or(EMPTY_ROOT_HASH);
259 let mut storage_proofs = Vec::with_capacity(slots.len());
260 for slot in slots {
261 let proof = if let Some(multiproof) = &storage_multiproof {
262 multiproof.storage_proof(*slot)?
263 } else {
264 StorageProof::new(*slot)
265 };
266 storage_proofs.push(proof);
267 }
268 Ok(AccountProof { address, info, proof, storage_root, storage_proofs })
269 }
270
271 pub fn extend(&mut self, other: Self) {
274 self.account_subtree.extend_from(other.account_subtree);
275 self.branch_node_masks.extend(other.branch_node_masks);
276
277 let reserve = if self.storages.is_empty() {
278 other.storages.len()
279 } else {
280 other.storages.len().div_ceil(2)
281 };
282 self.storages.reserve(reserve);
283 for (hashed_address, storage) in other.storages {
284 match self.storages.entry(hashed_address) {
285 hash_map::Entry::Occupied(mut entry) => {
286 debug_assert_eq!(entry.get().root, storage.root);
287 let entry = entry.get_mut();
288 entry.subtree.extend_from(storage.subtree);
289 entry.branch_node_masks.extend(storage.branch_node_masks);
290 }
291 hash_map::Entry::Vacant(entry) => {
292 entry.insert(storage);
293 }
294 }
295 }
296 }
297
298 pub fn from_storage_proof(hashed_address: B256, storage_proof: StorageMultiProof) -> Self {
300 Self {
301 storages: B256Map::from_iter([(hashed_address, storage_proof)]),
302 ..Default::default()
303 }
304 }
305}
306
307#[derive(Clone, Default, Debug, PartialEq, Eq)]
310pub struct DecodedMultiProof {
311 pub account_subtree: DecodedProofNodes,
313 pub branch_node_masks: BranchNodeMasksMap,
316 pub storages: B256Map<DecodedStorageMultiProof>,
318}
319
320impl DecodedMultiProof {
321 pub fn is_empty(&self) -> bool {
323 self.account_subtree.is_empty() &&
324 self.branch_node_masks.is_empty() &&
325 self.storages.is_empty()
326 }
327
328 pub fn account_proof_nodes(&self, path: &Nibbles) -> Vec<(Nibbles, TrieNode)> {
330 self.account_subtree.matching_nodes_sorted(path)
331 }
332
333 pub fn storage_proof_nodes(
335 &self,
336 hashed_address: B256,
337 slots: impl IntoIterator<Item = B256>,
338 ) -> Vec<(B256, Vec<(Nibbles, TrieNode)>)> {
339 self.storages
340 .get(&hashed_address)
341 .map(|storage_mp| {
342 slots
343 .into_iter()
344 .map(|slot| {
345 let nibbles = Nibbles::unpack(slot);
346 (slot, storage_mp.subtree.matching_nodes_sorted(&nibbles))
347 })
348 .collect()
349 })
350 .unwrap_or_default()
351 }
352
353 pub fn account_proof(
355 &self,
356 address: Address,
357 slots: &[B256],
358 ) -> Result<DecodedAccountProof, alloy_rlp::Error> {
359 let hashed_address = keccak256(address);
360 let nibbles = Nibbles::unpack(hashed_address);
361
362 let proof = self
364 .account_proof_nodes(&nibbles)
365 .into_iter()
366 .map(|(_, node)| node)
367 .collect::<Vec<_>>();
368
369 let info = 'info: {
372 if let Some(TrieNode::Leaf(leaf)) = proof.last() &&
373 nibbles.ends_with(&leaf.key)
374 {
375 let account = TrieAccount::decode(&mut &leaf.value[..])?;
376 break 'info Some(Account::from(account))
377 }
378 None
379 };
380
381 let storage_multiproof = self.storages.get(&hashed_address);
383 let storage_root = storage_multiproof.map(|m| m.root).unwrap_or(EMPTY_ROOT_HASH);
384 let mut storage_proofs = Vec::with_capacity(slots.len());
385 for slot in slots {
386 let proof = if let Some(multiproof) = &storage_multiproof {
387 multiproof.storage_proof(*slot)?
388 } else {
389 DecodedStorageProof::new(*slot)
390 };
391 storage_proofs.push(proof);
392 }
393 Ok(DecodedAccountProof { address, info, proof, storage_root, storage_proofs })
394 }
395
396 pub fn extend(&mut self, other: Self) {
399 self.account_subtree.extend_from(other.account_subtree);
400 self.branch_node_masks.extend(other.branch_node_masks);
401
402 let reserve = if self.storages.is_empty() {
403 other.storages.len()
404 } else {
405 other.storages.len().div_ceil(2)
406 };
407 self.storages.reserve(reserve);
408 for (hashed_address, storage) in other.storages {
409 match self.storages.entry(hashed_address) {
410 hash_map::Entry::Occupied(mut entry) => {
411 debug_assert_eq!(entry.get().root, storage.root);
412 let entry = entry.get_mut();
413 entry.subtree.extend_from(storage.subtree);
414 entry.branch_node_masks.extend(storage.branch_node_masks);
415 }
416 hash_map::Entry::Vacant(entry) => {
417 entry.insert(storage);
418 }
419 }
420 }
421 }
422
423 pub fn from_storage_proof(
425 hashed_address: B256,
426 storage_proof: DecodedStorageMultiProof,
427 ) -> Self {
428 Self {
429 storages: B256Map::from_iter([(hashed_address, storage_proof)]),
430 ..Default::default()
431 }
432 }
433}
434
435impl TryFrom<MultiProof> for DecodedMultiProof {
436 type Error = alloy_rlp::Error;
437
438 fn try_from(multi_proof: MultiProof) -> Result<Self, Self::Error> {
439 let account_subtree = DecodedProofNodes::try_from(multi_proof.account_subtree)?;
440 let storages = multi_proof
441 .storages
442 .into_iter()
443 .map(|(address, storage)| Ok((address, storage.try_into()?)))
444 .collect::<Result<B256Map<_>, alloy_rlp::Error>>()?;
445 Ok(Self { account_subtree, branch_node_masks: multi_proof.branch_node_masks, storages })
446 }
447}
448
449#[derive(Clone, Debug, PartialEq, Eq, Default)]
452pub struct DecodedMultiProofV2 {
453 pub account_proofs: Vec<ProofTrieNodeV2>,
455 pub storage_proofs: B256Map<Vec<ProofTrieNodeV2>>,
457}
458
459impl DecodedMultiProofV2 {
460 pub fn is_empty(&self) -> bool {
462 self.account_proofs.is_empty() && self.storage_proofs.is_empty()
463 }
464
465 pub fn account_proof(
467 &self,
468 address: Address,
469 slots: &[B256],
470 ) -> Result<AccountProof, alloy_rlp::Error> {
471 let hashed_address = keccak256(address);
472 let nibbles = Nibbles::unpack(hashed_address);
473 let account_nodes = matching_v2_proof_nodes(&self.account_proofs, &nibbles);
474
475 let (info, storage_root) = 'account: {
476 if let Some(ProofTrieNodeV2 { node: TrieNodeV2::Leaf(leaf), .. }) =
477 account_nodes.clone().last() &&
478 nibbles.ends_with(&leaf.key)
479 {
480 let account = TrieAccount::decode(&mut &leaf.value[..])?;
481 let storage_root = account.storage_root;
482 break 'account (Some(Account::from(account)), storage_root)
483 }
484 (None, EMPTY_ROOT_HASH)
485 };
486
487 let proof = encode_v2_proof_nodes(account_nodes);
488 let storage_nodes = self.storage_proofs.get(&hashed_address);
489 let mut storage_proofs = Vec::with_capacity(slots.len());
490 for slot in slots {
491 let nibbles = Nibbles::unpack(keccak256(slot));
492 let nodes =
493 storage_nodes.iter().flat_map(|nodes| matching_v2_proof_nodes(nodes, &nibbles));
494 let value = 'value: {
495 if let Some(ProofTrieNodeV2 { node: TrieNodeV2::Leaf(leaf), .. }) =
496 nodes.clone().last() &&
497 nibbles.ends_with(&leaf.key)
498 {
499 break 'value U256::decode(&mut &leaf.value[..])?
500 }
501 U256::ZERO
502 };
503 storage_proofs.push(StorageProof {
504 key: *slot,
505 nibbles,
506 value,
507 proof: encode_v2_proof_nodes(nodes),
508 });
509 }
510
511 Ok(AccountProof { address, info, proof, storage_root, storage_proofs })
512 }
513
514 pub fn from_witness(
521 state_root: B256,
522 witness: &B256Map<impl AsRef<[u8]>>,
523 ) -> Result<Self, alloy_rlp::Error> {
524 Self::from_witness_with(state_root, |hash| {
525 witness.get(hash).map(|bytes| TrieNode::decode(&mut bytes.as_ref())).transpose()
526 })
527 }
528
529 pub fn from_witness_decoded(
536 state_root: B256,
537 witness: &B256Map<TrieNode>,
538 ) -> Result<Self, alloy_rlp::Error> {
539 Self::from_witness_with(state_root, |hash| Ok(witness.get(hash).cloned()))
540 }
541
542 pub fn from_witness_with(
548 state_root: B256,
549 mut node_by_hash: impl FnMut(&B256) -> Result<Option<TrieNode>, alloy_rlp::Error>,
550 ) -> Result<Self, alloy_rlp::Error> {
551 let mut account_nodes: Vec<(Nibbles, TrieNode, Option<BranchNodeMasks>)> = Vec::new();
552 let mut storage_nodes: B256Map<Vec<(Nibbles, TrieNode, Option<BranchNodeMasks>)>> =
553 B256Map::default();
554
555 let Some(root) = node_by_hash(&state_root)? else { return Ok(Self::default()) };
556 let mut queue = VecDeque::from([(root, Nibbles::default(), None)]);
557
558 while let Some((trie_node, path, maybe_account)) = queue.pop_front() {
559 match &trie_node {
560 TrieNode::Branch(branch) => {
561 if path.len() >= 64 {
562 return Err(alloy_rlp::Error::Custom("branch path exceeds 64 nibbles"));
563 }
564 for (idx, maybe_child) in branch.as_ref().children() {
565 if let Some(child) = maybe_child {
566 let mut child_path = path;
567 child_path.push_unchecked(idx);
568 let child = if let Some(hash) = child.as_hash() {
569 let Some(node) = node_by_hash(&hash)? else { continue };
570 node
571 } else {
572 TrieNode::decode(&mut child.as_slice())?
573 };
574 queue.push_back((child, child_path, maybe_account));
575 }
576 }
577 }
578 TrieNode::Extension(ext) => {
579 if path.len() + ext.key.len() > 64 {
580 return Err(alloy_rlp::Error::Custom("extension path exceeds 64 nibbles"));
581 }
582 let mut child_path = path;
583 child_path.extend(&ext.key);
584 let child = if let Some(hash) = ext.child.as_hash() {
585 let Some(node) = node_by_hash(&hash)? else { continue };
586 node
587 } else {
588 TrieNode::decode(&mut ext.child.as_slice())?
589 };
590 queue.push_back((child, child_path, maybe_account));
591 }
592 TrieNode::Leaf(leaf) => {
593 if path.len() + leaf.key.len() != 64 {
594 return Err(alloy_rlp::Error::Custom("leaf path must contain 64 nibbles"));
595 }
596 if maybe_account.is_none() {
597 let mut full_path = path;
598 full_path.extend(&leaf.key);
599 let hashed_address = B256::from_slice(&full_path.pack());
600 let account = TrieAccount::decode(&mut &leaf.value[..])?;
601 if account.storage_root != EMPTY_ROOT_HASH &&
602 let Some(node) = node_by_hash(&account.storage_root)?
603 {
604 queue.push_back((node, Nibbles::default(), Some(hashed_address)));
605 }
606 }
607 }
608 TrieNode::EmptyRoot => {}
609 }
610
611 if let Some(account) = maybe_account {
612 storage_nodes.entry(account).or_default().push((path, trie_node, None));
613 } else {
614 account_nodes.push((path, trie_node, None));
615 }
616 }
617
618 account_nodes.sort_by(|(a, _, _), (b, _, _)| crate::depth_first_cmp(a, b));
619 let account_proofs = ProofTrieNodeV2::from_sorted_trie_nodes(account_nodes);
620
621 let mut storage_proofs = B256Map::default();
622 for (account, mut nodes) in storage_nodes {
623 nodes.sort_by(|(a, _, _), (b, _, _)| crate::depth_first_cmp(a, b));
624 storage_proofs.insert(account, ProofTrieNodeV2::from_sorted_trie_nodes(nodes));
625 }
626
627 Ok(Self { account_proofs, storage_proofs })
628 }
629
630 pub fn extend(&mut self, other: Self) {
634 self.account_proofs.extend(other.account_proofs);
635 for (hashed_address, other_storage_proofs) in other.storage_proofs {
636 match self.storage_proofs.entry(hashed_address) {
637 hash_map::Entry::Vacant(entry) => {
638 entry.insert(other_storage_proofs);
639 }
640 hash_map::Entry::Occupied(mut entry) => {
641 entry.get_mut().extend(other_storage_proofs);
642 }
643 }
644 }
645 }
646}
647
648fn matching_v2_proof_nodes<'a>(
650 nodes: &'a [ProofTrieNodeV2],
651 path: &'a Nibbles,
652) -> impl Iterator<Item = &'a ProofTrieNodeV2> + Clone {
653 nodes.iter().rev().filter(move |node| path.starts_with(&node.path))
654}
655
656fn encode_v2_proof_nodes<'a>(nodes: impl Iterator<Item = &'a ProofTrieNodeV2>) -> Vec<Bytes> {
661 let mut proof = Vec::new();
662 for proof_node in nodes {
663 let mut encoded = Vec::new();
664 proof_node.node.encode(&mut encoded);
665 if proof_node.path.is_empty() || encoded.len() >= B256::len_bytes() {
666 proof.push(Bytes::from(encoded));
667 }
668
669 if let TrieNodeV2::Branch(branch) = &proof_node.node &&
670 !branch.key.is_empty() &&
671 branch.branch_rlp_node.as_ref().is_some_and(|node| node.is_hash())
672 {
673 let mut encoded = Vec::new();
674 BranchNodeRef::new(&branch.stack, branch.state_mask).encode(&mut encoded);
675 proof.push(Bytes::from(encoded));
676 }
677 }
678 proof
679}
680
681impl From<DecodedMultiProof> for DecodedMultiProofV2 {
682 fn from(proof: DecodedMultiProof) -> Self {
683 let account_proofs =
684 decoded_proof_nodes_to_v2(proof.account_subtree, &proof.branch_node_masks);
685 let storage_proofs = proof
686 .storages
687 .into_iter()
688 .map(|(address, storage)| {
689 (address, decoded_proof_nodes_to_v2(storage.subtree, &storage.branch_node_masks))
690 })
691 .collect();
692 Self { account_proofs, storage_proofs }
693 }
694}
695
696fn decoded_proof_nodes_to_v2(
699 nodes: DecodedProofNodes,
700 masks: &BranchNodeMasksMap,
701) -> Vec<ProofTrieNodeV2> {
702 let mut sorted: Vec<_> = nodes.into_inner().into_iter().collect();
703 sorted.sort_unstable_by(|a, b| crate::depth_first_cmp(&a.0, &b.0));
704 ProofTrieNodeV2::from_sorted_trie_nodes(
705 sorted.into_iter().map(|(path, node)| (path, node, masks.get(&path).copied())),
706 )
707}
708
709#[derive(Clone, Debug, PartialEq, Eq)]
711pub struct StorageMultiProof {
712 pub root: B256,
714 pub subtree: ProofNodes,
716 pub branch_node_masks: BranchNodeMasksMap,
719}
720
721impl StorageMultiProof {
722 pub fn empty() -> Self {
724 Self {
725 root: EMPTY_ROOT_HASH,
726 subtree: ProofNodes::from_iter([(
727 Nibbles::default(),
728 Bytes::from([EMPTY_STRING_CODE]),
729 )]),
730 branch_node_masks: BranchNodeMasksMap::default(),
731 }
732 }
733
734 pub fn storage_proof(&self, slot: B256) -> Result<StorageProof, alloy_rlp::Error> {
736 let nibbles = Nibbles::unpack(keccak256(slot));
737
738 let proof = self
740 .subtree
741 .matching_nodes_iter(&nibbles)
742 .sorted_by(|a, b| a.0.cmp(b.0))
743 .map(|(_, node)| node.clone())
744 .collect::<Vec<_>>();
745
746 let value = 'value: {
749 if let Some(last) = proof.last() &&
750 let TrieNode::Leaf(leaf) = TrieNode::decode(&mut &last[..])? &&
751 nibbles.ends_with(&leaf.key)
752 {
753 break 'value U256::decode(&mut &leaf.value[..])?
754 }
755 U256::ZERO
756 };
757
758 Ok(StorageProof { key: slot, nibbles, value, proof })
759 }
760}
761
762#[derive(Clone, Debug, PartialEq, Eq)]
764pub struct DecodedStorageMultiProof {
765 pub root: B256,
767 pub subtree: DecodedProofNodes,
769 pub branch_node_masks: BranchNodeMasksMap,
772}
773
774impl DecodedStorageMultiProof {
775 pub fn empty() -> Self {
777 Self {
778 root: EMPTY_ROOT_HASH,
779 subtree: DecodedProofNodes::from_iter([(Nibbles::default(), TrieNode::EmptyRoot)]),
780 branch_node_masks: BranchNodeMasksMap::default(),
781 }
782 }
783
784 pub fn storage_proof(&self, slot: B256) -> Result<DecodedStorageProof, alloy_rlp::Error> {
786 let nibbles = Nibbles::unpack(keccak256(slot));
787
788 let proof = self
790 .subtree
791 .matching_nodes_iter(&nibbles)
792 .sorted_by(|a, b| a.0.cmp(b.0))
793 .map(|(_, node)| node.clone())
794 .collect::<Vec<_>>();
795
796 let value = 'value: {
799 if let Some(TrieNode::Leaf(leaf)) = proof.last() &&
800 nibbles.ends_with(&leaf.key)
801 {
802 break 'value U256::decode(&mut &leaf.value[..])?
803 }
804 U256::ZERO
805 };
806
807 Ok(DecodedStorageProof { key: slot, nibbles, value, proof })
808 }
809}
810
811impl TryFrom<StorageMultiProof> for DecodedStorageMultiProof {
812 type Error = alloy_rlp::Error;
813
814 fn try_from(multi_proof: StorageMultiProof) -> Result<Self, Self::Error> {
815 let subtree = DecodedProofNodes::try_from(multi_proof.subtree)?;
816 Ok(Self {
817 root: multi_proof.root,
818 subtree,
819 branch_node_masks: multi_proof.branch_node_masks,
820 })
821 }
822}
823
824#[derive(Clone, PartialEq, Eq, Debug)]
826#[cfg_attr(any(test, feature = "serde"), derive(serde::Serialize, serde::Deserialize))]
827#[cfg_attr(any(test, feature = "serde"), serde(rename_all = "camelCase"))]
828pub struct AccountProof {
829 pub address: Address,
831 pub info: Option<Account>,
833 pub proof: Vec<Bytes>,
836 pub storage_root: B256,
838 pub storage_proofs: Vec<StorageProof>,
840}
841
842#[cfg(feature = "eip1186")]
850fn normalize_eip1186_empty_trie_proof(proof: Vec<Bytes>) -> Vec<Bytes> {
851 if proof.len() == 1 && proof[0].as_ref() == [EMPTY_STRING_CODE] {
852 Vec::new()
853 } else {
854 proof
855 }
856}
857
858#[cfg(feature = "eip1186")]
859impl AccountProof {
860 pub fn into_eip1186_response(
868 self,
869 slots: Vec<alloy_serde::JsonStorageKey>,
870 ) -> alloy_rpc_types_eth::EIP1186AccountProofResponse {
871 self.into_eip1186_response_with(slots, false)
872 }
873
874 pub fn into_eip1186_response_with(
885 self,
886 slots: Vec<alloy_serde::JsonStorageKey>,
887 zero_empty_account: bool,
888 ) -> alloy_rpc_types_eth::EIP1186AccountProofResponse {
889 let is_non_existent = self.info.is_none();
891 let info = self.info.unwrap_or_default();
892 let (code_hash, storage_hash) = if is_non_existent && zero_empty_account {
893 (B256::ZERO, B256::ZERO)
894 } else {
895 (info.get_bytecode_hash(), self.storage_root)
896 };
897 alloy_rpc_types_eth::EIP1186AccountProofResponse {
898 address: self.address,
899 balance: info.balance,
900 code_hash,
901 nonce: info.nonce,
902 storage_hash,
903 account_proof: normalize_eip1186_empty_trie_proof(self.proof),
904 storage_proof: self
905 .storage_proofs
906 .into_iter()
907 .zip(slots)
908 .map(|(proof, slot)| proof.into_eip1186_proof(slot))
909 .collect(),
910 }
911 }
912
913 #[allow(clippy::needless_update)]
919 pub fn from_eip1186_proof(proof: alloy_rpc_types_eth::EIP1186AccountProofResponse) -> Self {
920 let alloy_rpc_types_eth::EIP1186AccountProofResponse {
921 nonce,
922 address,
923 balance,
924 code_hash,
925 storage_hash,
926 account_proof,
927 storage_proof,
928 ..
929 } = proof;
930 let storage_proofs = storage_proof.into_iter().map(Into::into).collect();
931
932 let leaf_account = Account::EXTENSIONS_ENABLED
935 .then(|| inclusion_proof_account(address, &account_proof))
936 .flatten();
937
938 let (storage_root, info) = if nonce == 0 &&
939 balance.is_zero() &&
940 (storage_hash.is_zero() || storage_hash == EMPTY_ROOT_HASH) &&
941 (code_hash == alloy_consensus::constants::KECCAK_EMPTY || code_hash.is_zero()) &&
942 !leaf_account.as_ref().is_some_and(Account::has_extension)
943 {
944 (EMPTY_ROOT_HASH, None)
953 } else {
954 (
955 storage_hash,
956 Some(Account {
957 nonce,
958 balance,
959 bytecode_hash: code_hash.into(),
960 ..leaf_account.unwrap_or_default()
961 }),
962 )
963 };
964
965 Self { address, info, proof: account_proof, storage_root, storage_proofs }
966 }
967}
968
969#[cfg(feature = "eip1186")]
970impl From<alloy_rpc_types_eth::EIP1186AccountProofResponse> for AccountProof {
971 fn from(proof: alloy_rpc_types_eth::EIP1186AccountProofResponse) -> Self {
972 Self::from_eip1186_proof(proof)
973 }
974}
975
976#[cfg(feature = "eip1186")]
982fn inclusion_proof_account(address: Address, proof: &[Bytes]) -> Option<Account> {
983 let root = keccak256(proof.first()?);
984 let TrieNode::Leaf(leaf) = alloy_rlp::decode_exact::<TrieNode>(proof.last()?).ok()? else {
985 return None
986 };
987 verify_proof(root, Nibbles::unpack(keccak256(address)), Some(leaf.value.clone()), proof)
988 .ok()?;
989 alloy_rlp::decode_exact::<TrieAccount>(&leaf.value).ok().map(Account::from)
990}
991
992impl Default for AccountProof {
993 fn default() -> Self {
994 Self::new(Address::default())
995 }
996}
997
998impl AccountProof {
999 pub const fn new(address: Address) -> Self {
1001 Self {
1002 address,
1003 info: None,
1004 proof: Vec::new(),
1005 storage_root: EMPTY_ROOT_HASH,
1006 storage_proofs: Vec::new(),
1007 }
1008 }
1009
1010 #[allow(clippy::clone_on_copy)]
1012 pub fn verify(&self, root: B256) -> Result<(), ProofVerificationError> {
1013 for storage_proof in &self.storage_proofs {
1015 storage_proof.verify(self.storage_root)?;
1016 }
1017
1018 let expected = if self.info.is_none() && self.storage_root == EMPTY_ROOT_HASH {
1020 None
1021 } else {
1022 Some(alloy_rlp::encode(
1023 self.info.clone().unwrap_or_default().into_trie_account(self.storage_root),
1024 ))
1025 };
1026 let nibbles = Nibbles::unpack(keccak256(self.address));
1027 verify_proof(root, nibbles, expected, &self.proof)
1028 }
1029}
1030
1031#[derive(Clone, PartialEq, Eq, Debug)]
1033pub struct DecodedAccountProof {
1034 pub address: Address,
1036 pub info: Option<Account>,
1038 pub proof: Vec<TrieNode>,
1041 pub storage_root: B256,
1043 pub storage_proofs: Vec<DecodedStorageProof>,
1045}
1046
1047impl Default for DecodedAccountProof {
1048 fn default() -> Self {
1049 Self::new(Address::default())
1050 }
1051}
1052
1053impl DecodedAccountProof {
1054 pub const fn new(address: Address) -> Self {
1056 Self {
1057 address,
1058 info: None,
1059 proof: Vec::new(),
1060 storage_root: EMPTY_ROOT_HASH,
1061 storage_proofs: Vec::new(),
1062 }
1063 }
1064}
1065
1066#[derive(Clone, PartialEq, Eq, Default, Debug)]
1068#[cfg_attr(any(test, feature = "serde"), derive(serde::Serialize, serde::Deserialize))]
1069pub struct StorageProof {
1070 pub key: B256,
1072 pub nibbles: Nibbles,
1074 pub value: U256,
1076 pub proof: Vec<Bytes>,
1079}
1080
1081impl StorageProof {
1082 pub fn new(key: B256) -> Self {
1084 let nibbles = Nibbles::unpack(keccak256(key));
1085 Self { key, nibbles, ..Default::default() }
1086 }
1087
1088 pub fn new_with_hashed(key: B256, hashed_key: B256) -> Self {
1090 Self { key, nibbles: Nibbles::unpack(hashed_key), ..Default::default() }
1091 }
1092
1093 pub fn new_with_nibbles(key: B256, nibbles: Nibbles) -> Self {
1095 Self { key, nibbles, ..Default::default() }
1096 }
1097
1098 pub fn with_proof(mut self, proof: Vec<Bytes>) -> Self {
1100 self.proof = proof;
1101 self
1102 }
1103
1104 pub fn verify(&self, root: B256) -> Result<(), ProofVerificationError> {
1106 let expected =
1107 if self.value.is_zero() { None } else { Some(encode_fixed_size(&self.value).to_vec()) };
1108 verify_proof(root, self.nibbles, expected, &self.proof)
1109 }
1110}
1111
1112#[cfg(feature = "eip1186")]
1113impl StorageProof {
1114 pub fn into_eip1186_proof(
1116 self,
1117 slot: alloy_serde::JsonStorageKey,
1118 ) -> alloy_rpc_types_eth::EIP1186StorageProof {
1119 alloy_rpc_types_eth::EIP1186StorageProof {
1120 key: slot,
1121 value: self.value,
1122 proof: normalize_eip1186_empty_trie_proof(self.proof),
1123 }
1124 }
1125
1126 pub fn from_eip1186_proof(storage_proof: alloy_rpc_types_eth::EIP1186StorageProof) -> Self {
1131 Self {
1132 value: storage_proof.value,
1133 proof: storage_proof.proof,
1134 ..Self::new(storage_proof.key.as_b256())
1135 }
1136 }
1137}
1138
1139#[cfg(feature = "eip1186")]
1140impl From<alloy_rpc_types_eth::EIP1186StorageProof> for StorageProof {
1141 fn from(proof: alloy_rpc_types_eth::EIP1186StorageProof) -> Self {
1142 Self::from_eip1186_proof(proof)
1143 }
1144}
1145
1146#[derive(Clone, PartialEq, Eq, Default, Debug)]
1148pub struct DecodedStorageProof {
1149 pub key: B256,
1151 pub nibbles: Nibbles,
1153 pub value: U256,
1155 pub proof: Vec<TrieNode>,
1158}
1159
1160impl DecodedStorageProof {
1161 pub fn new(key: B256) -> Self {
1163 let nibbles = Nibbles::unpack(keccak256(key));
1164 Self { key, nibbles, ..Default::default() }
1165 }
1166
1167 pub fn new_with_hashed(key: B256, hashed_key: B256) -> Self {
1169 Self { key, nibbles: Nibbles::unpack(hashed_key), ..Default::default() }
1170 }
1171
1172 pub fn new_with_nibbles(key: B256, nibbles: Nibbles) -> Self {
1174 Self { key, nibbles, ..Default::default() }
1175 }
1176
1177 pub fn with_proof(mut self, proof: Vec<TrieNode>) -> Self {
1179 self.proof = proof;
1180 self
1181 }
1182}
1183
1184#[cfg(any(test, feature = "test-utils"))]
1187pub mod triehash {
1188 use alloy_primitives::{keccak256, B256};
1189 use alloy_rlp::RlpEncodable;
1190 use hash_db::Hasher;
1191 use plain_hasher::PlainHasher;
1192
1193 #[derive(Default, Debug, Clone, PartialEq, Eq, RlpEncodable)]
1195 #[non_exhaustive]
1196 pub struct KeccakHasher;
1197
1198 #[cfg(any(test, feature = "test-utils"))]
1199 impl Hasher for KeccakHasher {
1200 type Out = B256;
1201 type StdHasher = PlainHasher;
1202
1203 const LENGTH: usize = 32;
1204
1205 fn hash(x: &[u8]) -> Self::Out {
1206 keccak256(x)
1207 }
1208 }
1209}
1210
1211#[cfg(test)]
1212mod tests {
1213 use super::*;
1214 use alloy_consensus::constants::KECCAK_EMPTY;
1215 use alloy_trie::{
1216 nodes::{BranchNode, ExtensionNode, LeafNode, RlpNode},
1217 TrieMask,
1218 };
1219
1220 #[test]
1221 fn v2_account_proof_expands_extension_branch() {
1222 let branch =
1223 BranchNode::new(vec![RlpNode::word_rlp(&B256::repeat_byte(0x11))], TrieMask::from(1));
1224 let branch_rlp = alloy_rlp::encode(&branch);
1225 let combined = TrieNodeV2::Branch(crate::BranchNodeV2::new(
1226 Nibbles::from_nibbles([0xa, 0xb]),
1227 branch.stack,
1228 branch.state_mask,
1229 Some(RlpNode::from_rlp(&branch_rlp)),
1230 ));
1231 let extension_rlp = alloy_rlp::encode(&combined);
1232 let multiproof = DecodedMultiProofV2 {
1233 account_proofs: vec![ProofTrieNodeV2 {
1234 path: Nibbles::default(),
1235 node: combined,
1236 masks: None,
1237 }],
1238 ..Default::default()
1239 };
1240
1241 let proof = multiproof.account_proof(Address::ZERO, &[]).unwrap();
1242
1243 assert_eq!(proof.proof, vec![Bytes::from(extension_rlp), Bytes::from(branch_rlp)]);
1244 assert!(proof.info.is_none());
1245 }
1246
1247 fn decode_witness(witness: &B256Map<impl AsRef<[u8]>>) -> B256Map<TrieNode> {
1248 witness
1249 .iter()
1250 .map(|(hash, bytes)| (*hash, TrieNode::decode(&mut bytes.as_ref()).unwrap()))
1251 .collect()
1252 }
1253
1254 #[test]
1255 fn witness_nodes_are_depth_first_ordered() {
1256 fn insert_node(witness: &mut B256Map<Bytes>, node: impl alloy_rlp::Encodable) -> RlpNode {
1257 let encoded = alloy_rlp::encode(node);
1258 witness.insert(keccak256(&encoded), encoded.clone().into());
1259 RlpNode::from_rlp(&encoded)
1260 }
1261
1262 let mut witness = B256Map::default();
1263 let leaf_key = Nibbles::from_nibbles([0; 63]);
1264
1265 let storage_leaf_0 = insert_node(
1266 &mut witness,
1267 LeafNode::new(leaf_key, encode_fixed_size(&U256::ONE).to_vec()),
1268 );
1269 let storage_leaf_1 = insert_node(
1270 &mut witness,
1271 LeafNode::new(leaf_key, encode_fixed_size(&U256::from(2)).to_vec()),
1272 );
1273 let storage_root = insert_node(
1274 &mut witness,
1275 BranchNode::new(vec![storage_leaf_0, storage_leaf_1], TrieMask::new(0b11)),
1276 );
1277
1278 let account_leaf_0 = insert_node(
1279 &mut witness,
1280 LeafNode::new(
1281 leaf_key,
1282 alloy_rlp::encode(TrieAccount {
1283 storage_root: storage_root.as_hash().expect("storage root is hashed"),
1284 ..Default::default()
1285 }),
1286 ),
1287 );
1288 let account_leaf_1 = insert_node(
1289 &mut witness,
1290 LeafNode::new(leaf_key, alloy_rlp::encode(TrieAccount::default())),
1291 );
1292 let state_root = insert_node(
1293 &mut witness,
1294 BranchNode::new(vec![account_leaf_0, account_leaf_1], TrieMask::new(0b11)),
1295 )
1296 .as_hash()
1297 .expect("state root is hashed");
1298
1299 let proof = DecodedMultiProofV2::from_witness(state_root, &witness).unwrap();
1300 assert_eq!(
1301 DecodedMultiProofV2::from_witness_decoded(state_root, &decode_witness(&witness)),
1302 Ok(proof.clone())
1303 );
1304 let expected_paths =
1305 [Nibbles::from_nibbles([0]), Nibbles::from_nibbles([1]), Nibbles::default()];
1306
1307 assert_eq!(
1308 proof.account_proofs.iter().map(|node| node.path).collect::<Vec<_>>(),
1309 expected_paths
1310 );
1311 assert_eq!(
1312 proof.storage_proofs[&B256::ZERO].iter().map(|node| node.path).collect::<Vec<_>>(),
1313 expected_paths
1314 );
1315 }
1316
1317 #[test]
1318 fn witness_nodes_follow_inline_children() {
1319 let leaf_key = Nibbles::from_nibbles([0]);
1320 let leaf_0 =
1321 alloy_rlp::encode(LeafNode::new(leaf_key, encode_fixed_size(&U256::ONE).to_vec()));
1322 let leaf_1 =
1323 alloy_rlp::encode(LeafNode::new(leaf_key, encode_fixed_size(&U256::from(2)).to_vec()));
1324 assert!(leaf_0.len() < B256::len_bytes());
1325 assert!(leaf_1.len() < B256::len_bytes());
1326
1327 let branch = alloy_rlp::encode(BranchNode::new(
1328 vec![RlpNode::from_rlp(&leaf_0), RlpNode::from_rlp(&leaf_1)],
1329 TrieMask::new(0b11),
1330 ));
1331 assert!(branch.len() < B256::len_bytes());
1332
1333 let extension_key = Nibbles::from_nibbles([0; 62]);
1334 let storage_root_node =
1335 alloy_rlp::encode(ExtensionNode::new(extension_key, RlpNode::from_rlp(&branch)));
1336 let storage_root = keccak256(&storage_root_node);
1337
1338 let account_root_node = alloy_rlp::encode(LeafNode::new(
1339 Nibbles::from_nibbles([0; 64]),
1340 alloy_rlp::encode(TrieAccount { storage_root, ..Default::default() }),
1341 ));
1342 let state_root = keccak256(&account_root_node);
1343 let witness = B256Map::from_iter([
1344 (state_root, Bytes::from(account_root_node)),
1345 (storage_root, Bytes::from(storage_root_node)),
1346 ]);
1347
1348 let proof = DecodedMultiProofV2::from_witness(state_root, &witness).unwrap();
1349 assert_eq!(
1350 DecodedMultiProofV2::from_witness_decoded(state_root, &decode_witness(&witness)),
1351 Ok(proof.clone())
1352 );
1353 let mut leaf_0_path = extension_key;
1354 leaf_0_path.push_unchecked(0);
1355 let mut leaf_1_path = extension_key;
1356 leaf_1_path.push_unchecked(1);
1357
1358 assert_eq!(
1359 proof.storage_proofs[&B256::ZERO].iter().map(|node| node.path).collect::<Vec<_>>(),
1360 [leaf_0_path, leaf_1_path, Nibbles::default()]
1361 );
1362
1363 let nodes = &proof.storage_proofs[&B256::ZERO];
1364 for (path, value) in [(leaf_0_path, 1), (leaf_1_path, 2)] {
1365 let key = path.join(&leaf_key);
1366 let encoded = encode_v2_proof_nodes(matching_v2_proof_nodes(nodes, &key));
1367 assert_eq!(encoded, vec![witness[&storage_root].clone()]);
1368 verify_proof(storage_root, key, Some(alloy_rlp::encode(U256::from(value))), &encoded)
1369 .unwrap();
1370 }
1371 }
1372
1373 #[test]
1374 fn v2_proof_encoding_keeps_short_root() {
1375 let leaf = LeafNode::new(Nibbles::default(), vec![1]);
1376 let encoded = alloy_rlp::encode(&leaf);
1377 assert!(encoded.len() < 32);
1378 let nodes = [ProofTrieNodeV2 {
1379 path: Nibbles::default(),
1380 node: TrieNodeV2::Leaf(leaf),
1381 masks: None,
1382 }];
1383 assert_eq!(encode_v2_proof_nodes(nodes.iter()), vec![Bytes::from(encoded)]);
1384 }
1385
1386 #[test]
1387 fn witness_rejects_invalid_path_lengths() {
1388 let leaf = alloy_rlp::encode(LeafNode::new(Nibbles::default(), vec![1]));
1389 let branch = alloy_rlp::encode(BranchNode::new(
1390 vec![RlpNode::from_rlp(&leaf), RlpNode::from_rlp(&leaf)],
1391 TrieMask::new(0b11),
1392 ));
1393 let extension = alloy_rlp::encode(ExtensionNode::new(
1394 Nibbles::from_nibbles([0]),
1395 RlpNode::from_rlp(&branch),
1396 ));
1397
1398 let long_leaf = alloy_rlp::encode(LeafNode::new(Nibbles::from_nibbles([0]), vec![1]));
1399 for (child, depth, expected) in [
1400 (&branch, 64, "branch path exceeds 64 nibbles"),
1401 (&extension, 64, "extension path exceeds 64 nibbles"),
1402 (&leaf, 63, "leaf path must contain 64 nibbles"),
1403 (&long_leaf, 64, "leaf path must contain 64 nibbles"),
1404 ] {
1405 let storage = alloy_rlp::encode(ExtensionNode::new(
1406 Nibbles::from_nibbles(vec![0; depth]),
1407 RlpNode::from_rlp(child),
1408 ));
1409 let storage_root = keccak256(&storage);
1410 let account = alloy_rlp::encode(LeafNode::new(
1411 Nibbles::from_nibbles([0; 64]),
1412 alloy_rlp::encode(TrieAccount { storage_root, ..Default::default() }),
1413 ));
1414 let state_root = keccak256(&account);
1415 let witness = B256Map::from_iter([(state_root, account), (storage_root, storage)]);
1416 assert_eq!(
1417 DecodedMultiProofV2::from_witness(state_root, &witness),
1418 Err(alloy_rlp::Error::Custom(expected)),
1419 );
1420 assert_eq!(
1421 DecodedMultiProofV2::from_witness_decoded(state_root, &decode_witness(&witness)),
1422 Err(alloy_rlp::Error::Custom(expected)),
1423 );
1424 }
1425
1426 for length in [63, 65] {
1427 let account = alloy_rlp::encode(LeafNode::new(
1429 Nibbles::from_nibbles(vec![0; length - 1]),
1430 alloy_rlp::encode(TrieAccount::default()),
1431 ));
1432 let hash = keccak256(&account);
1433 let root = alloy_rlp::encode(BranchNode::new(
1434 vec![RlpNode::word_rlp(&hash), RlpNode::word_rlp(&hash)],
1435 TrieMask::new(0b11),
1436 ));
1437 let state_root = keccak256(&root);
1438 let witness = B256Map::from_iter([(state_root, root), (hash, account)]);
1439 assert_eq!(
1440 DecodedMultiProofV2::from_witness(state_root, &witness),
1441 Err(alloy_rlp::Error::Custom("leaf path must contain 64 nibbles")),
1442 );
1443 assert_eq!(
1444 DecodedMultiProofV2::from_witness_decoded(state_root, &decode_witness(&witness)),
1445 Err(alloy_rlp::Error::Custom("leaf path must contain 64 nibbles")),
1446 );
1447 }
1448 }
1449
1450 #[test]
1451 fn test_multiproof_extend_account_proofs() {
1452 let mut proof1 = MultiProof::default();
1453 let mut proof2 = MultiProof::default();
1454
1455 let addr1 = B256::random();
1456 let addr2 = B256::random();
1457
1458 proof1.account_subtree.insert(
1459 Nibbles::unpack(addr1),
1460 alloy_rlp::encode_fixed_size(&U256::from(42)).to_vec().into(),
1461 );
1462 proof2.account_subtree.insert(
1463 Nibbles::unpack(addr2),
1464 alloy_rlp::encode_fixed_size(&U256::from(43)).to_vec().into(),
1465 );
1466
1467 proof1.extend(proof2);
1468
1469 assert!(proof1.account_subtree.contains_key(&Nibbles::unpack(addr1)));
1470 assert!(proof1.account_subtree.contains_key(&Nibbles::unpack(addr2)));
1471 }
1472
1473 #[test]
1474 fn test_multiproof_extend_storage_proofs() {
1475 let mut proof1 = MultiProof::default();
1476 let mut proof2 = MultiProof::default();
1477
1478 let addr = B256::random();
1479 let root = B256::random();
1480
1481 let mut subtree1 = ProofNodes::default();
1482 subtree1.insert(
1483 Nibbles::from_nibbles(vec![0]),
1484 alloy_rlp::encode_fixed_size(&U256::from(42)).to_vec().into(),
1485 );
1486 proof1.storages.insert(
1487 addr,
1488 StorageMultiProof {
1489 root,
1490 subtree: subtree1,
1491 branch_node_masks: BranchNodeMasksMap::default(),
1492 },
1493 );
1494
1495 let mut subtree2 = ProofNodes::default();
1496 subtree2.insert(
1497 Nibbles::from_nibbles(vec![1]),
1498 alloy_rlp::encode_fixed_size(&U256::from(43)).to_vec().into(),
1499 );
1500 proof2.storages.insert(
1501 addr,
1502 StorageMultiProof {
1503 root,
1504 subtree: subtree2,
1505 branch_node_masks: BranchNodeMasksMap::default(),
1506 },
1507 );
1508
1509 proof1.extend(proof2);
1510
1511 let storage = proof1.storages.get(&addr).unwrap();
1512 assert_eq!(storage.root, root);
1513 assert!(storage.subtree.contains_key(&Nibbles::from_nibbles(vec![0])));
1514 assert!(storage.subtree.contains_key(&Nibbles::from_nibbles(vec![1])));
1515 }
1516
1517 #[test]
1518 fn test_multi_proof_retain_difference() {
1519 let mut empty = MultiProofTargets::default();
1520 empty.retain_difference(&Default::default());
1521 assert!(empty.is_empty());
1522
1523 let targets = MultiProofTargets::accounts((0..10).map(B256::with_last_byte));
1524
1525 let mut diffed = targets.clone();
1526 diffed.retain_difference(&MultiProofTargets::account(B256::with_last_byte(11)));
1527 assert_eq!(diffed, targets);
1528
1529 diffed.retain_difference(&MultiProofTargets::accounts((0..5).map(B256::with_last_byte)));
1530 assert_eq!(diffed, MultiProofTargets::accounts((5..10).map(B256::with_last_byte)));
1531
1532 diffed.retain_difference(&targets);
1533 assert!(diffed.is_empty());
1534
1535 let mut targets = MultiProofTargets::default();
1536 let (account1, account2, account3) =
1537 (1..=3).map(B256::with_last_byte).collect_tuple().unwrap();
1538 let account2_slots = (1..5).map(B256::with_last_byte).collect::<B256Set>();
1539 targets.insert(account1, B256Set::from_iter([B256::with_last_byte(1)]));
1540 targets.insert(account2, account2_slots.clone());
1541 targets.insert(account3, B256Set::from_iter([B256::with_last_byte(1)]));
1542
1543 let mut diffed = targets.clone();
1544 diffed.retain_difference(&MultiProofTargets::accounts((1..=3).map(B256::with_last_byte)));
1545 assert_eq!(diffed, targets);
1546
1547 let mut account2_slots_expected_len = account2_slots.len();
1549 for slot in account2_slots.iter().skip(1) {
1550 diffed.retain_difference(&MultiProofTargets::account_with_slots(account2, [*slot]));
1551 account2_slots_expected_len -= 1;
1552 assert_eq!(
1553 diffed.get(&account2).map(|slots| slots.len()),
1554 Some(account2_slots_expected_len)
1555 );
1556 }
1557
1558 diffed.retain_difference(&targets);
1559 assert!(diffed.is_empty());
1560 }
1561
1562 #[test]
1563 fn test_multi_proof_retain_difference_no_overlap() {
1564 let mut targets = MultiProofTargets::default();
1565
1566 let (addr1, addr2) = (B256::random(), B256::random());
1568 let (slot1, slot2) = (B256::random(), B256::random());
1569 targets.insert(addr1, std::iter::once(slot1).collect());
1570 targets.insert(addr2, std::iter::once(slot2).collect());
1571
1572 let mut retained = targets.clone();
1573 retained.retain_difference(&Default::default());
1574 assert_eq!(retained, targets);
1575
1576 let mut other_targets = MultiProofTargets::default();
1578 let addr3 = B256::random();
1579 let slot3 = B256::random();
1580 other_targets.insert(addr3, B256Set::from_iter([slot3]));
1581
1582 let mut retained = targets.clone();
1585 retained.retain_difference(&other_targets);
1586 assert_eq!(retained, targets);
1587 }
1588
1589 #[test]
1590 fn test_get_prefetch_proof_targets_remove_subset() {
1591 let mut targets = MultiProofTargets::default();
1593 let (addr1, addr2) = (B256::random(), B256::random());
1594 let (slot1, slot2) = (B256::random(), B256::random());
1595 targets.insert(addr1, B256Set::from_iter([slot1]));
1596 targets.insert(addr2, B256Set::from_iter([slot2]));
1597
1598 let other_targets = MultiProofTargets::account_with_slots(addr1, [slot1]);
1600
1601 let mut retained = targets.clone();
1602 retained.retain_difference(&other_targets);
1603
1604 assert_eq!(retained.len(), 1);
1606 assert!(!retained.contains_key(&addr1));
1607 assert!(retained.contains_key(&addr2));
1608
1609 let slot3 = B256::random();
1611 targets.get_mut(&addr1).unwrap().insert(slot3);
1612
1613 let mut retained = targets.clone();
1614 retained.retain_difference(&other_targets);
1615
1616 assert_eq!(retained.len(), 2);
1619 assert!(retained.contains_key(&addr1));
1620 assert_eq!(retained.get(&addr1), Some(&B256Set::from_iter([slot3])));
1621 assert!(retained.contains_key(&addr2));
1622 assert_eq!(retained.get(&addr2), Some(&B256Set::from_iter([slot2])));
1623 }
1624
1625 #[test]
1626 #[cfg(feature = "eip1186")]
1627 fn eip_1186_roundtrip() {
1628 let mut acc = AccountProof {
1629 address: Address::random(),
1630 info: Some(
1631 Account { nonce: 100, bytecode_hash: Some(KECCAK_EMPTY), ..Default::default() },
1633 ),
1634 proof: vec![],
1635 storage_root: B256::ZERO,
1636 storage_proofs: vec![],
1637 };
1638
1639 let rpc_proof = acc.clone().into_eip1186_response(Vec::new());
1640 let inverse: AccountProof = rpc_proof.into();
1641 assert_eq!(acc, inverse);
1642
1643 acc.info.as_mut().unwrap().nonce = 0;
1645 let rpc_proof = acc.clone().into_eip1186_response(Vec::new());
1646 let inverse: AccountProof = rpc_proof.into();
1647 acc.info.take();
1648 acc.storage_root = EMPTY_ROOT_HASH;
1649 assert_eq!(acc, inverse);
1650 }
1651
1652 #[test]
1653 #[cfg(feature = "eip1186")]
1654 fn from_eip1186_proof_accepts_geth_zero_hashes() {
1655 let geth_proof = alloy_rpc_types_eth::EIP1186AccountProofResponse {
1660 address: Address::random(),
1661 balance: U256::ZERO,
1662 code_hash: B256::ZERO,
1663 nonce: 0,
1664 storage_hash: B256::ZERO,
1665 account_proof: vec![],
1666 storage_proof: vec![],
1667 };
1668
1669 let acc: AccountProof = geth_proof.into();
1670 assert!(acc.info.is_none());
1672 assert_eq!(acc.storage_root, EMPTY_ROOT_HASH);
1673 }
1674
1675 #[test]
1676 #[cfg(feature = "eip1186")]
1677 fn from_eip1186_proof_recovers_account_extension() {
1678 if !Account::EXTENSIONS_ENABLED {
1679 return;
1680 }
1681
1682 let extension = [0x82, 0xaa];
1684 let payload_length = 0u64.length() +
1685 U256::ZERO.length() +
1686 EMPTY_ROOT_HASH.length() +
1687 KECCAK_EMPTY.length() +
1688 extension[..].length();
1689 let mut value = Vec::new();
1690 alloy_rlp::Header { list: true, payload_length }.encode(&mut value);
1691 0u64.encode(&mut value);
1692 U256::ZERO.encode(&mut value);
1693 EMPTY_ROOT_HASH.encode(&mut value);
1694 KECCAK_EMPTY.encode(&mut value);
1695 extension[..].encode(&mut value);
1696
1697 let address = Address::with_last_byte(1);
1699 let leaf = alloy_rlp::encode(LeafNode::new(Nibbles::unpack(keccak256(address)), value));
1700 let root = keccak256(&leaf);
1701 let response = alloy_rpc_types_eth::EIP1186AccountProofResponse {
1702 address,
1703 balance: U256::ZERO,
1704 code_hash: KECCAK_EMPTY,
1705 nonce: 0,
1706 storage_hash: EMPTY_ROOT_HASH,
1707 account_proof: vec![leaf.into()],
1708 storage_proof: vec![],
1709 };
1710
1711 let proof = AccountProof::from_eip1186_proof(response.clone());
1712 assert!(proof.info.as_ref().is_some_and(Account::has_extension));
1713 assert_eq!(proof.verify(root), Ok(()));
1714
1715 let other =
1717 AccountProof::from_eip1186_proof(alloy_rpc_types_eth::EIP1186AccountProofResponse {
1718 address: Address::with_last_byte(2),
1719 ..response
1720 });
1721 assert_eq!(other.info, None);
1722 assert_eq!(other.verify(root), Ok(()));
1723 }
1724
1725 #[test]
1726 #[cfg(feature = "eip1186")]
1727 fn from_eip1186_proof_accepts_empty_hashes() {
1728 let proof = alloy_rpc_types_eth::EIP1186AccountProofResponse {
1729 address: Address::random(),
1730 balance: U256::ZERO,
1731 code_hash: KECCAK_EMPTY,
1732 nonce: 0,
1733 storage_hash: EMPTY_ROOT_HASH,
1734 account_proof: vec![],
1735 storage_proof: vec![],
1736 };
1737
1738 let acc: AccountProof = proof.into();
1739 assert!(acc.info.is_none());
1740 assert_eq!(acc.storage_root, EMPTY_ROOT_HASH);
1741 }
1742
1743 #[test]
1744 #[cfg(feature = "eip1186")]
1745 #[allow(clippy::needless_update)]
1746 fn into_eip1186_response_zero_empty_account() {
1747 let acc = AccountProof {
1749 address: Address::random(),
1750 info: None,
1751 proof: vec![],
1752 storage_root: EMPTY_ROOT_HASH,
1753 storage_proofs: vec![],
1754 };
1755
1756 let rpc_default = acc.clone().into_eip1186_response(Vec::new());
1758 assert_eq!(rpc_default.code_hash, KECCAK_EMPTY);
1759 assert_eq!(rpc_default.storage_hash, EMPTY_ROOT_HASH);
1760
1761 let rpc_compat_off = acc.clone().into_eip1186_response_with(Vec::new(), false);
1763 assert_eq!(rpc_compat_off.code_hash, KECCAK_EMPTY);
1764 assert_eq!(rpc_compat_off.storage_hash, EMPTY_ROOT_HASH);
1765
1766 let rpc_compat_on = acc.into_eip1186_response_with(Vec::new(), true);
1768 assert_eq!(rpc_compat_on.code_hash, B256::ZERO);
1769 assert_eq!(rpc_compat_on.storage_hash, B256::ZERO);
1770
1771 let existing_acc = AccountProof {
1773 address: Address::random(),
1774 info: Some(Account {
1775 nonce: 42,
1776 balance: U256::from(100),
1777 bytecode_hash: Some(KECCAK_EMPTY),
1778 ..Default::default()
1779 }),
1780 proof: vec![],
1781 storage_root: B256::random(),
1782 storage_proofs: vec![],
1783 };
1784 let rpc_existing = existing_acc.clone().into_eip1186_response_with(Vec::new(), true);
1785 assert_eq!(rpc_existing.code_hash, KECCAK_EMPTY);
1786 assert_eq!(rpc_existing.storage_hash, existing_acc.storage_root);
1787 }
1788
1789 #[test]
1790 fn test_multiproof_targets_chunking_length() {
1791 let mut targets = MultiProofTargets::default();
1792 targets.insert(B256::with_last_byte(1), B256Set::default());
1793 targets.insert(
1794 B256::with_last_byte(2),
1795 B256Set::from_iter([B256::with_last_byte(10), B256::with_last_byte(20)]),
1796 );
1797 targets.insert(
1798 B256::with_last_byte(3),
1799 B256Set::from_iter([
1800 B256::with_last_byte(30),
1801 B256::with_last_byte(31),
1802 B256::with_last_byte(32),
1803 ]),
1804 );
1805
1806 let chunking_length = targets.chunking_length();
1807 for size in 1..=targets.clone().chunks(1).count() {
1808 let chunk_count = targets.clone().chunks(size).count();
1809 let expected_count = chunking_length.div_ceil(size);
1810 assert_eq!(
1811 chunk_count, expected_count,
1812 "chunking_length: {}, size: {}",
1813 chunking_length, size
1814 );
1815 }
1816 }
1817
1818 #[test]
1819 fn test_nonempty_storage_trie_returns_nonempty_proof() {
1820 let slot = B256::with_last_byte(1);
1821 let nibbles = Nibbles::unpack(keccak256(slot));
1822 let value = U256::from(999);
1823 let leaf = alloy_trie::nodes::LeafNode::new(nibbles, encode_fixed_size(&value).to_vec());
1824 let mut encoded = vec![];
1825 alloy_rlp::Encodable::encode(&leaf, &mut encoded);
1826
1827 let mut subtree = ProofNodes::default();
1828 subtree.insert(nibbles, encoded.into());
1829
1830 let multiproof = StorageMultiProof {
1831 root: B256::with_last_byte(0xFF),
1832 subtree,
1833 branch_node_masks: BranchNodeMasksMap::default(),
1834 };
1835
1836 let proof = multiproof.storage_proof(slot).unwrap();
1837 assert!(!proof.proof.is_empty(), "non-empty trie must return non-empty proof");
1838 assert_eq!(proof.value, value);
1839 }
1840
1841 #[cfg(feature = "eip1186")]
1842 #[test]
1843 fn eip1186_response_normalizes_empty_trie_proof() {
1844 let slot = B256::with_last_byte(1);
1845 let sentinel = || vec![Bytes::from([EMPTY_STRING_CODE])];
1846
1847 let account = AccountProof {
1849 address: Address::ZERO,
1850 info: None,
1851 proof: sentinel(),
1852 storage_root: EMPTY_ROOT_HASH,
1853 storage_proofs: vec![StorageProof::new(slot).with_proof(sentinel())],
1854 };
1855
1856 let resp = account.into_eip1186_response(vec![alloy_serde::JsonStorageKey::from(slot)]);
1857
1858 assert!(
1859 resp.account_proof.is_empty(),
1860 "empty account trie must yield empty account_proof, got {:?}",
1861 resp.account_proof
1862 );
1863 assert_eq!(resp.storage_proof.len(), 1);
1864 assert!(
1865 resp.storage_proof[0].proof.is_empty(),
1866 "empty storage trie must yield empty storage proof, got {:?}",
1867 resp.storage_proof[0].proof
1868 );
1869 }
1870
1871 #[cfg(feature = "eip1186")]
1872 #[test]
1873 fn eip1186_response_keeps_nonempty_proof() {
1874 let multi = vec![Bytes::from([0x01, 0x02]), Bytes::from([0x03])];
1877 let single_non_sentinel = vec![Bytes::from([0xf8, 0x44])];
1878
1879 let account = AccountProof {
1880 address: Address::ZERO,
1881 info: None,
1882 proof: multi.clone(),
1883 storage_root: EMPTY_ROOT_HASH,
1884 storage_proofs: vec![
1885 StorageProof::new(B256::with_last_byte(1)).with_proof(single_non_sentinel.clone())
1886 ],
1887 };
1888
1889 let resp = account.into_eip1186_response(vec![alloy_serde::JsonStorageKey::from(
1890 B256::with_last_byte(1),
1891 )]);
1892
1893 assert_eq!(resp.account_proof, multi);
1894 assert_eq!(resp.storage_proof[0].proof, single_non_sentinel);
1895 }
1896
1897 #[cfg(feature = "eip1186")]
1898 #[test]
1899 fn eip1186_response_keeps_key_of_each_requested_slot() {
1900 let slot = B256::with_last_byte(1);
1901 let keys = vec![
1902 alloy_serde::JsonStorageKey::Number(U256::from(1)),
1903 alloy_serde::JsonStorageKey::Hash(slot),
1904 ];
1905 let mut account = AccountProof::new(Address::ZERO);
1906 account.storage_proofs = vec![StorageProof::new(slot); 2];
1907
1908 let resp = account.into_eip1186_response(keys.clone());
1909
1910 assert_eq!(resp.storage_proof.iter().map(|p| p.key).collect::<Vec<_>>(), keys);
1911 }
1912}