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 if let Some(other_hashed_slots) = other.get(hashed_address) {
61 hashed_slots.retain(|hashed_slot| !other_hashed_slots.contains(hashed_slot));
62 !hashed_slots.is_empty()
63 } else {
64 true
65 }
66 });
67 }
68
69 pub fn extend(&mut self, other: Self) {
71 self.extend_inner(Cow::Owned(other));
72 }
73
74 pub fn extend_ref(&mut self, other: &Self) {
78 self.extend_inner(Cow::Borrowed(other));
79 }
80
81 fn extend_inner(&mut self, other: Cow<'_, Self>) {
82 for (hashed_address, hashed_slots) in other.iter() {
83 match self.entry(*hashed_address) {
84 hash_map::Entry::Vacant(entry) => {
85 entry.insert(hashed_slots.clone());
86 }
87 hash_map::Entry::Occupied(mut entry) => {
88 entry.get_mut().extend(hashed_slots);
89 }
90 }
91 }
92 }
93
94 pub fn chunks(self, size: usize) -> ChunkedMultiProofTargets {
102 ChunkedMultiProofTargets::new(self, size)
103 }
104
105 pub fn chunking_length(&self) -> usize {
107 self.values().map(|slots| 1 + slots.len().saturating_sub(1)).sum::<usize>()
108 }
109}
110
111#[derive(Debug)]
132pub struct ChunkedMultiProofTargets {
133 flattened_targets: alloc::vec::IntoIter<(B256, Option<B256>)>,
134 size: usize,
135}
136
137impl ChunkedMultiProofTargets {
138 fn new(targets: MultiProofTargets, size: usize) -> Self {
139 assert!(size > 0, "chunk size must be non-zero");
140 let flattened_targets = targets
141 .into_iter()
142 .flat_map(|(address, slots)| {
143 if slots.is_empty() {
144 itertools::Either::Left(core::iter::once((address, None)))
148 } else {
149 itertools::Either::Right(
150 slots.into_iter().map(move |slot| (address, Some(slot))),
151 )
152 }
153 })
154 .sorted_unstable();
155 Self { flattened_targets, size }
156 }
157}
158
159impl Iterator for ChunkedMultiProofTargets {
160 type Item = MultiProofTargets;
161
162 fn next(&mut self) -> Option<Self::Item> {
163 let chunk = self.flattened_targets.by_ref().take(self.size).fold(
164 MultiProofTargets::default(),
165 |mut acc, (address, slot)| {
166 let entry = acc.entry(address).or_default();
167 if let Some(slot) = slot {
168 entry.insert(slot);
169 }
170 acc
171 },
172 );
173
174 if chunk.is_empty() {
175 None
176 } else {
177 Some(chunk)
178 }
179 }
180}
181
182#[derive(Clone, Default, Debug, PartialEq, Eq)]
186pub struct MultiProof {
187 pub account_subtree: ProofNodes,
189 pub branch_node_masks: BranchNodeMasksMap,
192 pub storages: B256Map<StorageMultiProof>,
194}
195
196impl MultiProof {
197 pub fn is_empty(&self) -> bool {
199 self.account_subtree.is_empty() &&
200 self.branch_node_masks.is_empty() &&
201 self.storages.is_empty()
202 }
203
204 pub fn account_proof_nodes(&self, path: &Nibbles) -> Vec<(Nibbles, Bytes)> {
206 self.account_subtree.matching_nodes_sorted(path)
207 }
208
209 pub fn storage_proof_nodes(
211 &self,
212 hashed_address: B256,
213 slots: impl IntoIterator<Item = B256>,
214 ) -> Vec<(B256, Vec<(Nibbles, Bytes)>)> {
215 self.storages
216 .get(&hashed_address)
217 .map(|storage_mp| {
218 slots
219 .into_iter()
220 .map(|slot| {
221 let nibbles = Nibbles::unpack(slot);
222 (slot, storage_mp.subtree.matching_nodes_sorted(&nibbles))
223 })
224 .collect()
225 })
226 .unwrap_or_default()
227 }
228
229 pub fn account_proof(
231 &self,
232 address: Address,
233 slots: &[B256],
234 ) -> Result<AccountProof, alloy_rlp::Error> {
235 let hashed_address = keccak256(address);
236 let nibbles = Nibbles::unpack(hashed_address);
237
238 let proof = self
240 .account_proof_nodes(&nibbles)
241 .into_iter()
242 .map(|(_, node)| node)
243 .collect::<Vec<_>>();
244
245 let info = 'info: {
248 if let Some(last) = proof.last() &&
249 let TrieNode::Leaf(leaf) = TrieNode::decode(&mut &last[..])? &&
250 nibbles.ends_with(&leaf.key)
251 {
252 let account = TrieAccount::decode(&mut &leaf.value[..])?;
253 break 'info Some(Account::from(account))
254 }
255 None
256 };
257
258 let storage_multiproof = self.storages.get(&hashed_address);
260 let storage_root = storage_multiproof.map(|m| m.root).unwrap_or(EMPTY_ROOT_HASH);
261 let mut storage_proofs = Vec::with_capacity(slots.len());
262 for slot in slots {
263 let proof = if let Some(multiproof) = &storage_multiproof {
264 multiproof.storage_proof(*slot)?
265 } else {
266 StorageProof::new(*slot)
267 };
268 storage_proofs.push(proof);
269 }
270 Ok(AccountProof { address, info, proof, storage_root, storage_proofs })
271 }
272
273 pub fn extend(&mut self, other: Self) {
276 self.account_subtree.extend_from(other.account_subtree);
277 self.branch_node_masks.extend(other.branch_node_masks);
278
279 let reserve = if self.storages.is_empty() {
280 other.storages.len()
281 } else {
282 other.storages.len().div_ceil(2)
283 };
284 self.storages.reserve(reserve);
285 for (hashed_address, storage) in other.storages {
286 match self.storages.entry(hashed_address) {
287 hash_map::Entry::Occupied(mut entry) => {
288 debug_assert_eq!(entry.get().root, storage.root);
289 let entry = entry.get_mut();
290 entry.subtree.extend_from(storage.subtree);
291 entry.branch_node_masks.extend(storage.branch_node_masks);
292 }
293 hash_map::Entry::Vacant(entry) => {
294 entry.insert(storage);
295 }
296 }
297 }
298 }
299
300 pub fn from_storage_proof(hashed_address: B256, storage_proof: StorageMultiProof) -> Self {
302 Self {
303 storages: B256Map::from_iter([(hashed_address, storage_proof)]),
304 ..Default::default()
305 }
306 }
307}
308
309#[derive(Clone, Default, Debug, PartialEq, Eq)]
312pub struct DecodedMultiProof {
313 pub account_subtree: DecodedProofNodes,
315 pub branch_node_masks: BranchNodeMasksMap,
318 pub storages: B256Map<DecodedStorageMultiProof>,
320}
321
322impl DecodedMultiProof {
323 pub fn is_empty(&self) -> bool {
325 self.account_subtree.is_empty() &&
326 self.branch_node_masks.is_empty() &&
327 self.storages.is_empty()
328 }
329
330 pub fn account_proof_nodes(&self, path: &Nibbles) -> Vec<(Nibbles, TrieNode)> {
332 self.account_subtree.matching_nodes_sorted(path)
333 }
334
335 pub fn storage_proof_nodes(
337 &self,
338 hashed_address: B256,
339 slots: impl IntoIterator<Item = B256>,
340 ) -> Vec<(B256, Vec<(Nibbles, TrieNode)>)> {
341 self.storages
342 .get(&hashed_address)
343 .map(|storage_mp| {
344 slots
345 .into_iter()
346 .map(|slot| {
347 let nibbles = Nibbles::unpack(slot);
348 (slot, storage_mp.subtree.matching_nodes_sorted(&nibbles))
349 })
350 .collect()
351 })
352 .unwrap_or_default()
353 }
354
355 pub fn account_proof(
357 &self,
358 address: Address,
359 slots: &[B256],
360 ) -> Result<DecodedAccountProof, alloy_rlp::Error> {
361 let hashed_address = keccak256(address);
362 let nibbles = Nibbles::unpack(hashed_address);
363
364 let proof = self
366 .account_proof_nodes(&nibbles)
367 .into_iter()
368 .map(|(_, node)| node)
369 .collect::<Vec<_>>();
370
371 let info = 'info: {
374 if let Some(TrieNode::Leaf(leaf)) = proof.last() &&
375 nibbles.ends_with(&leaf.key)
376 {
377 let account = TrieAccount::decode(&mut &leaf.value[..])?;
378 break 'info Some(Account::from(account))
379 }
380 None
381 };
382
383 let storage_multiproof = self.storages.get(&hashed_address);
385 let storage_root = storage_multiproof.map(|m| m.root).unwrap_or(EMPTY_ROOT_HASH);
386 let mut storage_proofs = Vec::with_capacity(slots.len());
387 for slot in slots {
388 let proof = if let Some(multiproof) = &storage_multiproof {
389 multiproof.storage_proof(*slot)?
390 } else {
391 DecodedStorageProof::new(*slot)
392 };
393 storage_proofs.push(proof);
394 }
395 Ok(DecodedAccountProof { address, info, proof, storage_root, storage_proofs })
396 }
397
398 pub fn extend(&mut self, other: Self) {
401 self.account_subtree.extend_from(other.account_subtree);
402 self.branch_node_masks.extend(other.branch_node_masks);
403
404 let reserve = if self.storages.is_empty() {
405 other.storages.len()
406 } else {
407 other.storages.len().div_ceil(2)
408 };
409 self.storages.reserve(reserve);
410 for (hashed_address, storage) in other.storages {
411 match self.storages.entry(hashed_address) {
412 hash_map::Entry::Occupied(mut entry) => {
413 debug_assert_eq!(entry.get().root, storage.root);
414 let entry = entry.get_mut();
415 entry.subtree.extend_from(storage.subtree);
416 entry.branch_node_masks.extend(storage.branch_node_masks);
417 }
418 hash_map::Entry::Vacant(entry) => {
419 entry.insert(storage);
420 }
421 }
422 }
423 }
424
425 pub fn from_storage_proof(
427 hashed_address: B256,
428 storage_proof: DecodedStorageMultiProof,
429 ) -> Self {
430 Self {
431 storages: B256Map::from_iter([(hashed_address, storage_proof)]),
432 ..Default::default()
433 }
434 }
435}
436
437impl TryFrom<MultiProof> for DecodedMultiProof {
438 type Error = alloy_rlp::Error;
439
440 fn try_from(multi_proof: MultiProof) -> Result<Self, Self::Error> {
441 let account_subtree = DecodedProofNodes::try_from(multi_proof.account_subtree)?;
442 let storages = multi_proof
443 .storages
444 .into_iter()
445 .map(|(address, storage)| Ok((address, storage.try_into()?)))
446 .collect::<Result<B256Map<_>, alloy_rlp::Error>>()?;
447 Ok(Self { account_subtree, branch_node_masks: multi_proof.branch_node_masks, storages })
448 }
449}
450
451#[derive(Clone, Debug, PartialEq, Eq, Default)]
454pub struct DecodedMultiProofV2 {
455 pub account_proofs: Vec<ProofTrieNodeV2>,
457 pub storage_proofs: B256Map<Vec<ProofTrieNodeV2>>,
459}
460
461impl DecodedMultiProofV2 {
462 pub fn is_empty(&self) -> bool {
464 self.account_proofs.is_empty() && self.storage_proofs.is_empty()
465 }
466
467 pub fn account_proof(
469 &self,
470 address: Address,
471 slots: &[B256],
472 ) -> Result<AccountProof, alloy_rlp::Error> {
473 let hashed_address = keccak256(address);
474 let nibbles = Nibbles::unpack(hashed_address);
475 let account_nodes = matching_v2_proof_nodes(&self.account_proofs, &nibbles);
476
477 let (info, storage_root) = 'account: {
478 if let Some(ProofTrieNodeV2 { node: TrieNodeV2::Leaf(leaf), .. }) =
479 account_nodes.clone().last() &&
480 nibbles.ends_with(&leaf.key)
481 {
482 let account = TrieAccount::decode(&mut &leaf.value[..])?;
483 let storage_root = account.storage_root;
484 break 'account (Some(Account::from(account)), storage_root)
485 }
486 (None, EMPTY_ROOT_HASH)
487 };
488
489 let proof = encode_v2_proof_nodes(account_nodes);
490 let storage_nodes = self.storage_proofs.get(&hashed_address);
491 let mut storage_proofs = Vec::with_capacity(slots.len());
492 for slot in slots {
493 let nibbles = Nibbles::unpack(keccak256(slot));
494 let nodes =
495 storage_nodes.iter().flat_map(|nodes| matching_v2_proof_nodes(nodes, &nibbles));
496 let value = 'value: {
497 if let Some(ProofTrieNodeV2 { node: TrieNodeV2::Leaf(leaf), .. }) =
498 nodes.clone().last() &&
499 nibbles.ends_with(&leaf.key)
500 {
501 break 'value U256::decode(&mut &leaf.value[..])?
502 }
503 U256::ZERO
504 };
505 storage_proofs.push(StorageProof {
506 key: *slot,
507 nibbles,
508 value,
509 proof: encode_v2_proof_nodes(nodes),
510 });
511 }
512
513 Ok(AccountProof { address, info, proof, storage_root, storage_proofs })
514 }
515
516 pub fn from_witness(
523 state_root: B256,
524 witness: &B256Map<impl AsRef<[u8]>>,
525 ) -> Result<Self, alloy_rlp::Error> {
526 let mut account_nodes: Vec<(Nibbles, TrieNode, Option<BranchNodeMasks>)> = Vec::new();
527 let mut storage_nodes: B256Map<Vec<(Nibbles, TrieNode, Option<BranchNodeMasks>)>> =
528 B256Map::default();
529
530 let Some(root) = witness.get(&state_root) else { return Ok(Self::default()) };
531 let mut queue =
532 VecDeque::from([(TrieNode::decode(&mut root.as_ref())?, Nibbles::default(), None)]);
533
534 while let Some((trie_node, path, maybe_account)) = queue.pop_front() {
535 match &trie_node {
536 TrieNode::Branch(branch) => {
537 if path.len() >= 64 {
538 return Err(alloy_rlp::Error::Custom("branch path exceeds 64 nibbles"));
539 }
540 for (idx, maybe_child) in branch.as_ref().children() {
541 if let Some(child) = maybe_child {
542 let mut child_path = path;
543 child_path.push_unchecked(idx);
544 let child = if let Some(hash) = child.as_hash() {
545 let Some(bytes) = witness.get(&hash) else { continue };
546 TrieNode::decode(&mut bytes.as_ref())?
547 } else {
548 TrieNode::decode(&mut child.as_slice())?
549 };
550 queue.push_back((child, child_path, maybe_account));
551 }
552 }
553 }
554 TrieNode::Extension(ext) => {
555 if path.len() + ext.key.len() > 64 {
556 return Err(alloy_rlp::Error::Custom("extension path exceeds 64 nibbles"));
557 }
558 let mut child_path = path;
559 child_path.extend(&ext.key);
560 let child = if let Some(hash) = ext.child.as_hash() {
561 let Some(bytes) = witness.get(&hash) else { continue };
562 TrieNode::decode(&mut bytes.as_ref())?
563 } else {
564 TrieNode::decode(&mut ext.child.as_slice())?
565 };
566 queue.push_back((child, child_path, maybe_account));
567 }
568 TrieNode::Leaf(leaf) => {
569 if path.len() + leaf.key.len() != 64 {
570 return Err(alloy_rlp::Error::Custom("leaf path must contain 64 nibbles"));
571 }
572 if maybe_account.is_none() {
573 let mut full_path = path;
574 full_path.extend(&leaf.key);
575 let hashed_address = B256::from_slice(&full_path.pack());
576 let account = TrieAccount::decode(&mut &leaf.value[..])?;
577 if account.storage_root != EMPTY_ROOT_HASH &&
578 let Some(bytes) = witness.get(&account.storage_root)
579 {
580 queue.push_back((
581 TrieNode::decode(&mut bytes.as_ref())?,
582 Nibbles::default(),
583 Some(hashed_address),
584 ));
585 }
586 }
587 }
588 TrieNode::EmptyRoot => {}
589 }
590
591 if let Some(account) = maybe_account {
592 storage_nodes.entry(account).or_default().push((path, trie_node, None));
593 } else {
594 account_nodes.push((path, trie_node, None));
595 }
596 }
597
598 account_nodes.sort_by(|(a, _, _), (b, _, _)| crate::depth_first_cmp(a, b));
599 let account_proofs = ProofTrieNodeV2::from_sorted_trie_nodes(account_nodes);
600
601 let mut storage_proofs = B256Map::default();
602 for (account, mut nodes) in storage_nodes {
603 nodes.sort_by(|(a, _, _), (b, _, _)| crate::depth_first_cmp(a, b));
604 storage_proofs.insert(account, ProofTrieNodeV2::from_sorted_trie_nodes(nodes));
605 }
606
607 Ok(Self { account_proofs, storage_proofs })
608 }
609
610 pub fn extend(&mut self, other: Self) {
614 self.account_proofs.extend(other.account_proofs);
615 for (hashed_address, other_storage_proofs) in other.storage_proofs {
616 match self.storage_proofs.entry(hashed_address) {
617 hash_map::Entry::Vacant(entry) => {
618 entry.insert(other_storage_proofs);
619 }
620 hash_map::Entry::Occupied(mut entry) => {
621 entry.get_mut().extend(other_storage_proofs);
622 }
623 }
624 }
625 }
626}
627
628fn matching_v2_proof_nodes<'a>(
630 nodes: &'a [ProofTrieNodeV2],
631 path: &'a Nibbles,
632) -> impl Iterator<Item = &'a ProofTrieNodeV2> + Clone {
633 nodes.iter().rev().filter(move |node| path.starts_with(&node.path))
634}
635
636fn encode_v2_proof_nodes<'a>(nodes: impl Iterator<Item = &'a ProofTrieNodeV2>) -> Vec<Bytes> {
641 let mut proof = Vec::new();
642 for proof_node in nodes {
643 let mut encoded = Vec::new();
644 proof_node.node.encode(&mut encoded);
645 if proof_node.path.is_empty() || encoded.len() >= B256::len_bytes() {
646 proof.push(Bytes::from(encoded));
647 }
648
649 if let TrieNodeV2::Branch(branch) = &proof_node.node &&
650 !branch.key.is_empty() &&
651 branch.branch_rlp_node.as_ref().is_some_and(|node| node.is_hash())
652 {
653 let mut encoded = Vec::new();
654 BranchNodeRef::new(&branch.stack, branch.state_mask).encode(&mut encoded);
655 proof.push(Bytes::from(encoded));
656 }
657 }
658 proof
659}
660
661impl From<DecodedMultiProof> for DecodedMultiProofV2 {
662 fn from(proof: DecodedMultiProof) -> Self {
663 let account_proofs =
664 decoded_proof_nodes_to_v2(proof.account_subtree, &proof.branch_node_masks);
665 let storage_proofs = proof
666 .storages
667 .into_iter()
668 .map(|(address, storage)| {
669 (address, decoded_proof_nodes_to_v2(storage.subtree, &storage.branch_node_masks))
670 })
671 .collect();
672 Self { account_proofs, storage_proofs }
673 }
674}
675
676fn decoded_proof_nodes_to_v2(
679 nodes: DecodedProofNodes,
680 masks: &BranchNodeMasksMap,
681) -> Vec<ProofTrieNodeV2> {
682 let mut sorted: Vec<_> = nodes.into_inner().into_iter().collect();
683 sorted.sort_unstable_by(|a, b| crate::depth_first_cmp(&a.0, &b.0));
684 ProofTrieNodeV2::from_sorted_trie_nodes(
685 sorted.into_iter().map(|(path, node)| (path, node, masks.get(&path).copied())),
686 )
687}
688
689#[derive(Clone, Debug, PartialEq, Eq)]
691pub struct StorageMultiProof {
692 pub root: B256,
694 pub subtree: ProofNodes,
696 pub branch_node_masks: BranchNodeMasksMap,
699}
700
701impl StorageMultiProof {
702 pub fn empty() -> Self {
704 Self {
705 root: EMPTY_ROOT_HASH,
706 subtree: ProofNodes::from_iter([(
707 Nibbles::default(),
708 Bytes::from([EMPTY_STRING_CODE]),
709 )]),
710 branch_node_masks: BranchNodeMasksMap::default(),
711 }
712 }
713
714 pub fn storage_proof(&self, slot: B256) -> Result<StorageProof, alloy_rlp::Error> {
716 let nibbles = Nibbles::unpack(keccak256(slot));
717
718 let proof = self
720 .subtree
721 .matching_nodes_iter(&nibbles)
722 .sorted_by(|a, b| a.0.cmp(b.0))
723 .map(|(_, node)| node.clone())
724 .collect::<Vec<_>>();
725
726 let value = 'value: {
729 if let Some(last) = proof.last() &&
730 let TrieNode::Leaf(leaf) = TrieNode::decode(&mut &last[..])? &&
731 nibbles.ends_with(&leaf.key)
732 {
733 break 'value U256::decode(&mut &leaf.value[..])?
734 }
735 U256::ZERO
736 };
737
738 Ok(StorageProof { key: slot, nibbles, value, proof })
739 }
740}
741
742#[derive(Clone, Debug, PartialEq, Eq)]
744pub struct DecodedStorageMultiProof {
745 pub root: B256,
747 pub subtree: DecodedProofNodes,
749 pub branch_node_masks: BranchNodeMasksMap,
752}
753
754impl DecodedStorageMultiProof {
755 pub fn empty() -> Self {
757 Self {
758 root: EMPTY_ROOT_HASH,
759 subtree: DecodedProofNodes::from_iter([(Nibbles::default(), TrieNode::EmptyRoot)]),
760 branch_node_masks: BranchNodeMasksMap::default(),
761 }
762 }
763
764 pub fn storage_proof(&self, slot: B256) -> Result<DecodedStorageProof, alloy_rlp::Error> {
766 let nibbles = Nibbles::unpack(keccak256(slot));
767
768 let proof = self
770 .subtree
771 .matching_nodes_iter(&nibbles)
772 .sorted_by(|a, b| a.0.cmp(b.0))
773 .map(|(_, node)| node.clone())
774 .collect::<Vec<_>>();
775
776 let value = 'value: {
779 if let Some(TrieNode::Leaf(leaf)) = proof.last() &&
780 nibbles.ends_with(&leaf.key)
781 {
782 break 'value U256::decode(&mut &leaf.value[..])?
783 }
784 U256::ZERO
785 };
786
787 Ok(DecodedStorageProof { key: slot, nibbles, value, proof })
788 }
789}
790
791impl TryFrom<StorageMultiProof> for DecodedStorageMultiProof {
792 type Error = alloy_rlp::Error;
793
794 fn try_from(multi_proof: StorageMultiProof) -> Result<Self, Self::Error> {
795 let subtree = DecodedProofNodes::try_from(multi_proof.subtree)?;
796 Ok(Self {
797 root: multi_proof.root,
798 subtree,
799 branch_node_masks: multi_proof.branch_node_masks,
800 })
801 }
802}
803
804#[derive(Clone, PartialEq, Eq, Debug)]
806#[cfg_attr(any(test, feature = "serde"), derive(serde::Serialize, serde::Deserialize))]
807#[cfg_attr(any(test, feature = "serde"), serde(rename_all = "camelCase"))]
808pub struct AccountProof {
809 pub address: Address,
811 pub info: Option<Account>,
813 pub proof: Vec<Bytes>,
816 pub storage_root: B256,
818 pub storage_proofs: Vec<StorageProof>,
820}
821
822#[cfg(feature = "eip1186")]
830fn normalize_eip1186_empty_trie_proof(proof: Vec<Bytes>) -> Vec<Bytes> {
831 if proof.len() == 1 && proof[0].as_ref() == [EMPTY_STRING_CODE] {
832 Vec::new()
833 } else {
834 proof
835 }
836}
837
838#[cfg(feature = "eip1186")]
839impl AccountProof {
840 pub fn into_eip1186_response(
848 self,
849 slots: Vec<alloy_serde::JsonStorageKey>,
850 ) -> alloy_rpc_types_eth::EIP1186AccountProofResponse {
851 self.into_eip1186_response_with(slots, false)
852 }
853
854 pub fn into_eip1186_response_with(
865 self,
866 slots: Vec<alloy_serde::JsonStorageKey>,
867 zero_empty_account: bool,
868 ) -> alloy_rpc_types_eth::EIP1186AccountProofResponse {
869 let is_non_existent = self.info.is_none();
871 let info = self.info.unwrap_or_default();
872 let (code_hash, storage_hash) = if is_non_existent && zero_empty_account {
873 (B256::ZERO, B256::ZERO)
874 } else {
875 (info.get_bytecode_hash(), self.storage_root)
876 };
877 alloy_rpc_types_eth::EIP1186AccountProofResponse {
878 address: self.address,
879 balance: info.balance,
880 code_hash,
881 nonce: info.nonce,
882 storage_hash,
883 account_proof: normalize_eip1186_empty_trie_proof(self.proof),
884 storage_proof: self
885 .storage_proofs
886 .into_iter()
887 .filter_map(|proof| {
888 let input_slot = slots.iter().find(|s| s.as_b256() == proof.key)?;
889 Some(proof.into_eip1186_proof(*input_slot))
890 })
891 .collect(),
892 }
893 }
894
895 #[allow(clippy::needless_update)]
901 pub fn from_eip1186_proof(proof: alloy_rpc_types_eth::EIP1186AccountProofResponse) -> Self {
902 let alloy_rpc_types_eth::EIP1186AccountProofResponse {
903 nonce,
904 address,
905 balance,
906 code_hash,
907 storage_hash,
908 account_proof,
909 storage_proof,
910 ..
911 } = proof;
912 let storage_proofs = storage_proof.into_iter().map(Into::into).collect();
913
914 let leaf_account = Account::EXTENSIONS_ENABLED
917 .then(|| inclusion_proof_account(address, &account_proof))
918 .flatten();
919
920 let (storage_root, info) = if nonce == 0 &&
921 balance.is_zero() &&
922 (storage_hash.is_zero() || storage_hash == EMPTY_ROOT_HASH) &&
923 (code_hash == alloy_consensus::constants::KECCAK_EMPTY || code_hash.is_zero()) &&
924 !leaf_account.as_ref().is_some_and(Account::has_extension)
925 {
926 (EMPTY_ROOT_HASH, None)
935 } else {
936 (
937 storage_hash,
938 Some(Account {
939 nonce,
940 balance,
941 bytecode_hash: code_hash.into(),
942 ..leaf_account.unwrap_or_default()
943 }),
944 )
945 };
946
947 Self { address, info, proof: account_proof, storage_root, storage_proofs }
948 }
949}
950
951#[cfg(feature = "eip1186")]
952impl From<alloy_rpc_types_eth::EIP1186AccountProofResponse> for AccountProof {
953 fn from(proof: alloy_rpc_types_eth::EIP1186AccountProofResponse) -> Self {
954 Self::from_eip1186_proof(proof)
955 }
956}
957
958#[cfg(feature = "eip1186")]
964fn inclusion_proof_account(address: Address, proof: &[Bytes]) -> Option<Account> {
965 let root = keccak256(proof.first()?);
966 let TrieNode::Leaf(leaf) = alloy_rlp::decode_exact::<TrieNode>(proof.last()?).ok()? else {
967 return None
968 };
969 verify_proof(root, Nibbles::unpack(keccak256(address)), Some(leaf.value.clone()), proof)
970 .ok()?;
971 alloy_rlp::decode_exact::<TrieAccount>(&leaf.value).ok().map(Account::from)
972}
973
974impl Default for AccountProof {
975 fn default() -> Self {
976 Self::new(Address::default())
977 }
978}
979
980impl AccountProof {
981 pub const fn new(address: Address) -> Self {
983 Self {
984 address,
985 info: None,
986 proof: Vec::new(),
987 storage_root: EMPTY_ROOT_HASH,
988 storage_proofs: Vec::new(),
989 }
990 }
991
992 #[allow(clippy::clone_on_copy)]
994 pub fn verify(&self, root: B256) -> Result<(), ProofVerificationError> {
995 for storage_proof in &self.storage_proofs {
997 storage_proof.verify(self.storage_root)?;
998 }
999
1000 let expected = if self.info.is_none() && self.storage_root == EMPTY_ROOT_HASH {
1002 None
1003 } else {
1004 Some(alloy_rlp::encode(
1005 self.info.clone().unwrap_or_default().into_trie_account(self.storage_root),
1006 ))
1007 };
1008 let nibbles = Nibbles::unpack(keccak256(self.address));
1009 verify_proof(root, nibbles, expected, &self.proof)
1010 }
1011}
1012
1013#[derive(Clone, PartialEq, Eq, Debug)]
1015pub struct DecodedAccountProof {
1016 pub address: Address,
1018 pub info: Option<Account>,
1020 pub proof: Vec<TrieNode>,
1023 pub storage_root: B256,
1025 pub storage_proofs: Vec<DecodedStorageProof>,
1027}
1028
1029impl Default for DecodedAccountProof {
1030 fn default() -> Self {
1031 Self::new(Address::default())
1032 }
1033}
1034
1035impl DecodedAccountProof {
1036 pub const fn new(address: Address) -> Self {
1038 Self {
1039 address,
1040 info: None,
1041 proof: Vec::new(),
1042 storage_root: EMPTY_ROOT_HASH,
1043 storage_proofs: Vec::new(),
1044 }
1045 }
1046}
1047
1048#[derive(Clone, PartialEq, Eq, Default, Debug)]
1050#[cfg_attr(any(test, feature = "serde"), derive(serde::Serialize, serde::Deserialize))]
1051pub struct StorageProof {
1052 pub key: B256,
1054 pub nibbles: Nibbles,
1056 pub value: U256,
1058 pub proof: Vec<Bytes>,
1061}
1062
1063impl StorageProof {
1064 pub fn new(key: B256) -> Self {
1066 let nibbles = Nibbles::unpack(keccak256(key));
1067 Self { key, nibbles, ..Default::default() }
1068 }
1069
1070 pub fn new_with_hashed(key: B256, hashed_key: B256) -> Self {
1072 Self { key, nibbles: Nibbles::unpack(hashed_key), ..Default::default() }
1073 }
1074
1075 pub fn new_with_nibbles(key: B256, nibbles: Nibbles) -> Self {
1077 Self { key, nibbles, ..Default::default() }
1078 }
1079
1080 pub fn with_proof(mut self, proof: Vec<Bytes>) -> Self {
1082 self.proof = proof;
1083 self
1084 }
1085
1086 pub fn verify(&self, root: B256) -> Result<(), ProofVerificationError> {
1088 let expected =
1089 if self.value.is_zero() { None } else { Some(encode_fixed_size(&self.value).to_vec()) };
1090 verify_proof(root, self.nibbles, expected, &self.proof)
1091 }
1092}
1093
1094#[cfg(feature = "eip1186")]
1095impl StorageProof {
1096 pub fn into_eip1186_proof(
1098 self,
1099 slot: alloy_serde::JsonStorageKey,
1100 ) -> alloy_rpc_types_eth::EIP1186StorageProof {
1101 alloy_rpc_types_eth::EIP1186StorageProof {
1102 key: slot,
1103 value: self.value,
1104 proof: normalize_eip1186_empty_trie_proof(self.proof),
1105 }
1106 }
1107
1108 pub fn from_eip1186_proof(storage_proof: alloy_rpc_types_eth::EIP1186StorageProof) -> Self {
1113 Self {
1114 value: storage_proof.value,
1115 proof: storage_proof.proof,
1116 ..Self::new(storage_proof.key.as_b256())
1117 }
1118 }
1119}
1120
1121#[cfg(feature = "eip1186")]
1122impl From<alloy_rpc_types_eth::EIP1186StorageProof> for StorageProof {
1123 fn from(proof: alloy_rpc_types_eth::EIP1186StorageProof) -> Self {
1124 Self::from_eip1186_proof(proof)
1125 }
1126}
1127
1128#[derive(Clone, PartialEq, Eq, Default, Debug)]
1130pub struct DecodedStorageProof {
1131 pub key: B256,
1133 pub nibbles: Nibbles,
1135 pub value: U256,
1137 pub proof: Vec<TrieNode>,
1140}
1141
1142impl DecodedStorageProof {
1143 pub fn new(key: B256) -> Self {
1145 let nibbles = Nibbles::unpack(keccak256(key));
1146 Self { key, nibbles, ..Default::default() }
1147 }
1148
1149 pub fn new_with_hashed(key: B256, hashed_key: B256) -> Self {
1151 Self { key, nibbles: Nibbles::unpack(hashed_key), ..Default::default() }
1152 }
1153
1154 pub fn new_with_nibbles(key: B256, nibbles: Nibbles) -> Self {
1156 Self { key, nibbles, ..Default::default() }
1157 }
1158
1159 pub fn with_proof(mut self, proof: Vec<TrieNode>) -> Self {
1161 self.proof = proof;
1162 self
1163 }
1164}
1165
1166#[cfg(any(test, feature = "test-utils"))]
1169pub mod triehash {
1170 use alloy_primitives::{keccak256, B256};
1171 use alloy_rlp::RlpEncodable;
1172 use hash_db::Hasher;
1173 use plain_hasher::PlainHasher;
1174
1175 #[derive(Default, Debug, Clone, PartialEq, Eq, RlpEncodable)]
1177 #[non_exhaustive]
1178 pub struct KeccakHasher;
1179
1180 #[cfg(any(test, feature = "test-utils"))]
1181 impl Hasher for KeccakHasher {
1182 type Out = B256;
1183 type StdHasher = PlainHasher;
1184
1185 const LENGTH: usize = 32;
1186
1187 fn hash(x: &[u8]) -> Self::Out {
1188 keccak256(x)
1189 }
1190 }
1191}
1192
1193#[cfg(test)]
1194mod tests {
1195 use super::*;
1196 use alloy_consensus::constants::KECCAK_EMPTY;
1197 use alloy_trie::{
1198 nodes::{BranchNode, ExtensionNode, LeafNode, RlpNode},
1199 TrieMask,
1200 };
1201
1202 #[test]
1203 fn v2_account_proof_expands_extension_branch() {
1204 let branch =
1205 BranchNode::new(vec![RlpNode::word_rlp(&B256::repeat_byte(0x11))], TrieMask::from(1));
1206 let branch_rlp = alloy_rlp::encode(&branch);
1207 let combined = TrieNodeV2::Branch(crate::BranchNodeV2::new(
1208 Nibbles::from_nibbles([0xa, 0xb]),
1209 branch.stack,
1210 branch.state_mask,
1211 Some(RlpNode::from_rlp(&branch_rlp)),
1212 ));
1213 let extension_rlp = alloy_rlp::encode(&combined);
1214 let multiproof = DecodedMultiProofV2 {
1215 account_proofs: vec![ProofTrieNodeV2 {
1216 path: Nibbles::default(),
1217 node: combined,
1218 masks: None,
1219 }],
1220 ..Default::default()
1221 };
1222
1223 let proof = multiproof.account_proof(Address::ZERO, &[]).unwrap();
1224
1225 assert_eq!(proof.proof, vec![Bytes::from(extension_rlp), Bytes::from(branch_rlp)]);
1226 assert!(proof.info.is_none());
1227 }
1228
1229 #[test]
1230 fn witness_nodes_are_depth_first_ordered() {
1231 fn insert_node(witness: &mut B256Map<Bytes>, node: impl alloy_rlp::Encodable) -> RlpNode {
1232 let encoded = alloy_rlp::encode(node);
1233 witness.insert(keccak256(&encoded), encoded.clone().into());
1234 RlpNode::from_rlp(&encoded)
1235 }
1236
1237 let mut witness = B256Map::default();
1238 let leaf_key = Nibbles::from_nibbles([0; 63]);
1239
1240 let storage_leaf_0 = insert_node(
1241 &mut witness,
1242 LeafNode::new(leaf_key, encode_fixed_size(&U256::ONE).to_vec()),
1243 );
1244 let storage_leaf_1 = insert_node(
1245 &mut witness,
1246 LeafNode::new(leaf_key, encode_fixed_size(&U256::from(2)).to_vec()),
1247 );
1248 let storage_root = insert_node(
1249 &mut witness,
1250 BranchNode::new(vec![storage_leaf_0, storage_leaf_1], TrieMask::new(0b11)),
1251 );
1252
1253 let account_leaf_0 = insert_node(
1254 &mut witness,
1255 LeafNode::new(
1256 leaf_key,
1257 alloy_rlp::encode(TrieAccount {
1258 storage_root: storage_root.as_hash().expect("storage root is hashed"),
1259 ..Default::default()
1260 }),
1261 ),
1262 );
1263 let account_leaf_1 = insert_node(
1264 &mut witness,
1265 LeafNode::new(leaf_key, alloy_rlp::encode(TrieAccount::default())),
1266 );
1267 let state_root = insert_node(
1268 &mut witness,
1269 BranchNode::new(vec![account_leaf_0, account_leaf_1], TrieMask::new(0b11)),
1270 )
1271 .as_hash()
1272 .expect("state root is hashed");
1273
1274 let proof = DecodedMultiProofV2::from_witness(state_root, &witness).unwrap();
1275 let expected_paths =
1276 [Nibbles::from_nibbles([0]), Nibbles::from_nibbles([1]), Nibbles::default()];
1277
1278 assert_eq!(
1279 proof.account_proofs.iter().map(|node| node.path).collect::<Vec<_>>(),
1280 expected_paths
1281 );
1282 assert_eq!(
1283 proof.storage_proofs[&B256::ZERO].iter().map(|node| node.path).collect::<Vec<_>>(),
1284 expected_paths
1285 );
1286 }
1287
1288 #[test]
1289 fn witness_nodes_follow_inline_children() {
1290 let leaf_key = Nibbles::from_nibbles([0]);
1291 let leaf_0 =
1292 alloy_rlp::encode(LeafNode::new(leaf_key, encode_fixed_size(&U256::ONE).to_vec()));
1293 let leaf_1 =
1294 alloy_rlp::encode(LeafNode::new(leaf_key, encode_fixed_size(&U256::from(2)).to_vec()));
1295 assert!(leaf_0.len() < B256::len_bytes());
1296 assert!(leaf_1.len() < B256::len_bytes());
1297
1298 let branch = alloy_rlp::encode(BranchNode::new(
1299 vec![RlpNode::from_rlp(&leaf_0), RlpNode::from_rlp(&leaf_1)],
1300 TrieMask::new(0b11),
1301 ));
1302 assert!(branch.len() < B256::len_bytes());
1303
1304 let extension_key = Nibbles::from_nibbles([0; 62]);
1305 let storage_root_node =
1306 alloy_rlp::encode(ExtensionNode::new(extension_key, RlpNode::from_rlp(&branch)));
1307 let storage_root = keccak256(&storage_root_node);
1308
1309 let account_root_node = alloy_rlp::encode(LeafNode::new(
1310 Nibbles::from_nibbles([0; 64]),
1311 alloy_rlp::encode(TrieAccount { storage_root, ..Default::default() }),
1312 ));
1313 let state_root = keccak256(&account_root_node);
1314 let witness = B256Map::from_iter([
1315 (state_root, Bytes::from(account_root_node)),
1316 (storage_root, Bytes::from(storage_root_node)),
1317 ]);
1318
1319 let proof = DecodedMultiProofV2::from_witness(state_root, &witness).unwrap();
1320 let mut leaf_0_path = extension_key;
1321 leaf_0_path.push_unchecked(0);
1322 let mut leaf_1_path = extension_key;
1323 leaf_1_path.push_unchecked(1);
1324
1325 assert_eq!(
1326 proof.storage_proofs[&B256::ZERO].iter().map(|node| node.path).collect::<Vec<_>>(),
1327 [leaf_0_path, leaf_1_path, Nibbles::default()]
1328 );
1329
1330 let nodes = &proof.storage_proofs[&B256::ZERO];
1331 for (path, value) in [(leaf_0_path, 1), (leaf_1_path, 2)] {
1332 let key = path.join(&leaf_key);
1333 let encoded = encode_v2_proof_nodes(matching_v2_proof_nodes(nodes, &key));
1334 assert_eq!(encoded, vec![witness[&storage_root].clone()]);
1335 verify_proof(storage_root, key, Some(alloy_rlp::encode(U256::from(value))), &encoded)
1336 .unwrap();
1337 }
1338 }
1339
1340 #[test]
1341 fn v2_proof_encoding_keeps_short_root() {
1342 let leaf = LeafNode::new(Nibbles::default(), vec![1]);
1343 let encoded = alloy_rlp::encode(&leaf);
1344 assert!(encoded.len() < 32);
1345 let nodes = [ProofTrieNodeV2 {
1346 path: Nibbles::default(),
1347 node: TrieNodeV2::Leaf(leaf),
1348 masks: None,
1349 }];
1350 assert_eq!(encode_v2_proof_nodes(nodes.iter()), vec![Bytes::from(encoded)]);
1351 }
1352
1353 #[test]
1354 fn witness_rejects_invalid_path_lengths() {
1355 let leaf = alloy_rlp::encode(LeafNode::new(Nibbles::default(), vec![1]));
1356 let branch = alloy_rlp::encode(BranchNode::new(
1357 vec![RlpNode::from_rlp(&leaf), RlpNode::from_rlp(&leaf)],
1358 TrieMask::new(0b11),
1359 ));
1360 let extension = alloy_rlp::encode(ExtensionNode::new(
1361 Nibbles::from_nibbles([0]),
1362 RlpNode::from_rlp(&branch),
1363 ));
1364
1365 let long_leaf = alloy_rlp::encode(LeafNode::new(Nibbles::from_nibbles([0]), vec![1]));
1366 for (child, depth, expected) in [
1367 (&branch, 64, "branch path exceeds 64 nibbles"),
1368 (&extension, 64, "extension path exceeds 64 nibbles"),
1369 (&leaf, 63, "leaf path must contain 64 nibbles"),
1370 (&long_leaf, 64, "leaf path must contain 64 nibbles"),
1371 ] {
1372 let storage = alloy_rlp::encode(ExtensionNode::new(
1373 Nibbles::from_nibbles(vec![0; depth]),
1374 RlpNode::from_rlp(child),
1375 ));
1376 let storage_root = keccak256(&storage);
1377 let account = alloy_rlp::encode(LeafNode::new(
1378 Nibbles::from_nibbles([0; 64]),
1379 alloy_rlp::encode(TrieAccount { storage_root, ..Default::default() }),
1380 ));
1381 let state_root = keccak256(&account);
1382 let witness = B256Map::from_iter([(state_root, account), (storage_root, storage)]);
1383 assert_eq!(
1384 DecodedMultiProofV2::from_witness(state_root, &witness),
1385 Err(alloy_rlp::Error::Custom(expected)),
1386 );
1387 }
1388
1389 for length in [63, 65] {
1390 let account = alloy_rlp::encode(LeafNode::new(
1392 Nibbles::from_nibbles(vec![0; length - 1]),
1393 alloy_rlp::encode(TrieAccount::default()),
1394 ));
1395 let hash = keccak256(&account);
1396 let root = alloy_rlp::encode(BranchNode::new(
1397 vec![RlpNode::word_rlp(&hash), RlpNode::word_rlp(&hash)],
1398 TrieMask::new(0b11),
1399 ));
1400 let state_root = keccak256(&root);
1401 let witness = B256Map::from_iter([(state_root, root), (hash, account)]);
1402 assert_eq!(
1403 DecodedMultiProofV2::from_witness(state_root, &witness),
1404 Err(alloy_rlp::Error::Custom("leaf path must contain 64 nibbles")),
1405 );
1406 }
1407 }
1408
1409 #[test]
1410 fn test_multiproof_extend_account_proofs() {
1411 let mut proof1 = MultiProof::default();
1412 let mut proof2 = MultiProof::default();
1413
1414 let addr1 = B256::random();
1415 let addr2 = B256::random();
1416
1417 proof1.account_subtree.insert(
1418 Nibbles::unpack(addr1),
1419 alloy_rlp::encode_fixed_size(&U256::from(42)).to_vec().into(),
1420 );
1421 proof2.account_subtree.insert(
1422 Nibbles::unpack(addr2),
1423 alloy_rlp::encode_fixed_size(&U256::from(43)).to_vec().into(),
1424 );
1425
1426 proof1.extend(proof2);
1427
1428 assert!(proof1.account_subtree.contains_key(&Nibbles::unpack(addr1)));
1429 assert!(proof1.account_subtree.contains_key(&Nibbles::unpack(addr2)));
1430 }
1431
1432 #[test]
1433 fn test_multiproof_extend_storage_proofs() {
1434 let mut proof1 = MultiProof::default();
1435 let mut proof2 = MultiProof::default();
1436
1437 let addr = B256::random();
1438 let root = B256::random();
1439
1440 let mut subtree1 = ProofNodes::default();
1441 subtree1.insert(
1442 Nibbles::from_nibbles(vec![0]),
1443 alloy_rlp::encode_fixed_size(&U256::from(42)).to_vec().into(),
1444 );
1445 proof1.storages.insert(
1446 addr,
1447 StorageMultiProof {
1448 root,
1449 subtree: subtree1,
1450 branch_node_masks: BranchNodeMasksMap::default(),
1451 },
1452 );
1453
1454 let mut subtree2 = ProofNodes::default();
1455 subtree2.insert(
1456 Nibbles::from_nibbles(vec![1]),
1457 alloy_rlp::encode_fixed_size(&U256::from(43)).to_vec().into(),
1458 );
1459 proof2.storages.insert(
1460 addr,
1461 StorageMultiProof {
1462 root,
1463 subtree: subtree2,
1464 branch_node_masks: BranchNodeMasksMap::default(),
1465 },
1466 );
1467
1468 proof1.extend(proof2);
1469
1470 let storage = proof1.storages.get(&addr).unwrap();
1471 assert_eq!(storage.root, root);
1472 assert!(storage.subtree.contains_key(&Nibbles::from_nibbles(vec![0])));
1473 assert!(storage.subtree.contains_key(&Nibbles::from_nibbles(vec![1])));
1474 }
1475
1476 #[test]
1477 fn test_multi_proof_retain_difference() {
1478 let mut empty = MultiProofTargets::default();
1479 empty.retain_difference(&Default::default());
1480 assert!(empty.is_empty());
1481
1482 let targets = MultiProofTargets::accounts((0..10).map(B256::with_last_byte));
1483
1484 let mut diffed = targets.clone();
1485 diffed.retain_difference(&MultiProofTargets::account(B256::with_last_byte(11)));
1486 assert_eq!(diffed, targets);
1487
1488 diffed.retain_difference(&MultiProofTargets::accounts((0..5).map(B256::with_last_byte)));
1489 assert_eq!(diffed, MultiProofTargets::accounts((5..10).map(B256::with_last_byte)));
1490
1491 diffed.retain_difference(&targets);
1492 assert!(diffed.is_empty());
1493
1494 let mut targets = MultiProofTargets::default();
1495 let (account1, account2, account3) =
1496 (1..=3).map(B256::with_last_byte).collect_tuple().unwrap();
1497 let account2_slots = (1..5).map(B256::with_last_byte).collect::<B256Set>();
1498 targets.insert(account1, B256Set::from_iter([B256::with_last_byte(1)]));
1499 targets.insert(account2, account2_slots.clone());
1500 targets.insert(account3, B256Set::from_iter([B256::with_last_byte(1)]));
1501
1502 let mut diffed = targets.clone();
1503 diffed.retain_difference(&MultiProofTargets::accounts((1..=3).map(B256::with_last_byte)));
1504 assert_eq!(diffed, targets);
1505
1506 let mut account2_slots_expected_len = account2_slots.len();
1508 for slot in account2_slots.iter().skip(1) {
1509 diffed.retain_difference(&MultiProofTargets::account_with_slots(account2, [*slot]));
1510 account2_slots_expected_len -= 1;
1511 assert_eq!(
1512 diffed.get(&account2).map(|slots| slots.len()),
1513 Some(account2_slots_expected_len)
1514 );
1515 }
1516
1517 diffed.retain_difference(&targets);
1518 assert!(diffed.is_empty());
1519 }
1520
1521 #[test]
1522 fn test_multi_proof_retain_difference_no_overlap() {
1523 let mut targets = MultiProofTargets::default();
1524
1525 let (addr1, addr2) = (B256::random(), B256::random());
1527 let (slot1, slot2) = (B256::random(), B256::random());
1528 targets.insert(addr1, std::iter::once(slot1).collect());
1529 targets.insert(addr2, std::iter::once(slot2).collect());
1530
1531 let mut retained = targets.clone();
1532 retained.retain_difference(&Default::default());
1533 assert_eq!(retained, targets);
1534
1535 let mut other_targets = MultiProofTargets::default();
1537 let addr3 = B256::random();
1538 let slot3 = B256::random();
1539 other_targets.insert(addr3, B256Set::from_iter([slot3]));
1540
1541 let mut retained = targets.clone();
1544 retained.retain_difference(&other_targets);
1545 assert_eq!(retained, targets);
1546 }
1547
1548 #[test]
1549 fn test_get_prefetch_proof_targets_remove_subset() {
1550 let mut targets = MultiProofTargets::default();
1552 let (addr1, addr2) = (B256::random(), B256::random());
1553 let (slot1, slot2) = (B256::random(), B256::random());
1554 targets.insert(addr1, B256Set::from_iter([slot1]));
1555 targets.insert(addr2, B256Set::from_iter([slot2]));
1556
1557 let other_targets = MultiProofTargets::account_with_slots(addr1, [slot1]);
1559
1560 let mut retained = targets.clone();
1561 retained.retain_difference(&other_targets);
1562
1563 assert_eq!(retained.len(), 1);
1565 assert!(!retained.contains_key(&addr1));
1566 assert!(retained.contains_key(&addr2));
1567
1568 let slot3 = B256::random();
1570 targets.get_mut(&addr1).unwrap().insert(slot3);
1571
1572 let mut retained = targets.clone();
1573 retained.retain_difference(&other_targets);
1574
1575 assert_eq!(retained.len(), 2);
1578 assert!(retained.contains_key(&addr1));
1579 assert_eq!(retained.get(&addr1), Some(&B256Set::from_iter([slot3])));
1580 assert!(retained.contains_key(&addr2));
1581 assert_eq!(retained.get(&addr2), Some(&B256Set::from_iter([slot2])));
1582 }
1583
1584 #[test]
1585 #[cfg(feature = "eip1186")]
1586 fn eip_1186_roundtrip() {
1587 let mut acc = AccountProof {
1588 address: Address::random(),
1589 info: Some(
1590 Account { nonce: 100, bytecode_hash: Some(KECCAK_EMPTY), ..Default::default() },
1592 ),
1593 proof: vec![],
1594 storage_root: B256::ZERO,
1595 storage_proofs: vec![],
1596 };
1597
1598 let rpc_proof = acc.clone().into_eip1186_response(Vec::new());
1599 let inverse: AccountProof = rpc_proof.into();
1600 assert_eq!(acc, inverse);
1601
1602 acc.info.as_mut().unwrap().nonce = 0;
1604 let rpc_proof = acc.clone().into_eip1186_response(Vec::new());
1605 let inverse: AccountProof = rpc_proof.into();
1606 acc.info.take();
1607 acc.storage_root = EMPTY_ROOT_HASH;
1608 assert_eq!(acc, inverse);
1609 }
1610
1611 #[test]
1612 #[cfg(feature = "eip1186")]
1613 fn from_eip1186_proof_accepts_geth_zero_hashes() {
1614 let geth_proof = alloy_rpc_types_eth::EIP1186AccountProofResponse {
1619 address: Address::random(),
1620 balance: U256::ZERO,
1621 code_hash: B256::ZERO,
1622 nonce: 0,
1623 storage_hash: B256::ZERO,
1624 account_proof: vec![],
1625 storage_proof: vec![],
1626 };
1627
1628 let acc: AccountProof = geth_proof.into();
1629 assert!(acc.info.is_none());
1631 assert_eq!(acc.storage_root, EMPTY_ROOT_HASH);
1632 }
1633
1634 #[test]
1635 #[cfg(feature = "eip1186")]
1636 fn from_eip1186_proof_recovers_account_extension() {
1637 if !Account::EXTENSIONS_ENABLED {
1638 return;
1639 }
1640
1641 let extension = [0x82, 0xaa];
1643 let payload_length = 0u64.length() +
1644 U256::ZERO.length() +
1645 EMPTY_ROOT_HASH.length() +
1646 KECCAK_EMPTY.length() +
1647 extension[..].length();
1648 let mut value = Vec::new();
1649 alloy_rlp::Header { list: true, payload_length }.encode(&mut value);
1650 0u64.encode(&mut value);
1651 U256::ZERO.encode(&mut value);
1652 EMPTY_ROOT_HASH.encode(&mut value);
1653 KECCAK_EMPTY.encode(&mut value);
1654 extension[..].encode(&mut value);
1655
1656 let address = Address::with_last_byte(1);
1658 let leaf = alloy_rlp::encode(LeafNode::new(Nibbles::unpack(keccak256(address)), value));
1659 let root = keccak256(&leaf);
1660 let response = alloy_rpc_types_eth::EIP1186AccountProofResponse {
1661 address,
1662 balance: U256::ZERO,
1663 code_hash: KECCAK_EMPTY,
1664 nonce: 0,
1665 storage_hash: EMPTY_ROOT_HASH,
1666 account_proof: vec![leaf.into()],
1667 storage_proof: vec![],
1668 };
1669
1670 let proof = AccountProof::from_eip1186_proof(response.clone());
1671 assert!(proof.info.as_ref().is_some_and(Account::has_extension));
1672 assert_eq!(proof.verify(root), Ok(()));
1673
1674 let other =
1676 AccountProof::from_eip1186_proof(alloy_rpc_types_eth::EIP1186AccountProofResponse {
1677 address: Address::with_last_byte(2),
1678 ..response
1679 });
1680 assert_eq!(other.info, None);
1681 assert_eq!(other.verify(root), Ok(()));
1682 }
1683
1684 #[test]
1685 #[cfg(feature = "eip1186")]
1686 fn from_eip1186_proof_accepts_empty_hashes() {
1687 let proof = alloy_rpc_types_eth::EIP1186AccountProofResponse {
1688 address: Address::random(),
1689 balance: U256::ZERO,
1690 code_hash: KECCAK_EMPTY,
1691 nonce: 0,
1692 storage_hash: EMPTY_ROOT_HASH,
1693 account_proof: vec![],
1694 storage_proof: vec![],
1695 };
1696
1697 let acc: AccountProof = proof.into();
1698 assert!(acc.info.is_none());
1699 assert_eq!(acc.storage_root, EMPTY_ROOT_HASH);
1700 }
1701
1702 #[test]
1703 #[cfg(feature = "eip1186")]
1704 #[allow(clippy::needless_update)]
1705 fn into_eip1186_response_zero_empty_account() {
1706 let acc = AccountProof {
1708 address: Address::random(),
1709 info: None,
1710 proof: vec![],
1711 storage_root: EMPTY_ROOT_HASH,
1712 storage_proofs: vec![],
1713 };
1714
1715 let rpc_default = acc.clone().into_eip1186_response(Vec::new());
1717 assert_eq!(rpc_default.code_hash, KECCAK_EMPTY);
1718 assert_eq!(rpc_default.storage_hash, EMPTY_ROOT_HASH);
1719
1720 let rpc_compat_off = acc.clone().into_eip1186_response_with(Vec::new(), false);
1722 assert_eq!(rpc_compat_off.code_hash, KECCAK_EMPTY);
1723 assert_eq!(rpc_compat_off.storage_hash, EMPTY_ROOT_HASH);
1724
1725 let rpc_compat_on = acc.into_eip1186_response_with(Vec::new(), true);
1727 assert_eq!(rpc_compat_on.code_hash, B256::ZERO);
1728 assert_eq!(rpc_compat_on.storage_hash, B256::ZERO);
1729
1730 let existing_acc = AccountProof {
1732 address: Address::random(),
1733 info: Some(Account {
1734 nonce: 42,
1735 balance: U256::from(100),
1736 bytecode_hash: Some(KECCAK_EMPTY),
1737 ..Default::default()
1738 }),
1739 proof: vec![],
1740 storage_root: B256::random(),
1741 storage_proofs: vec![],
1742 };
1743 let rpc_existing = existing_acc.clone().into_eip1186_response_with(Vec::new(), true);
1744 assert_eq!(rpc_existing.code_hash, KECCAK_EMPTY);
1745 assert_eq!(rpc_existing.storage_hash, existing_acc.storage_root);
1746 }
1747
1748 #[test]
1749 fn test_multiproof_targets_chunking_length() {
1750 let mut targets = MultiProofTargets::default();
1751 targets.insert(B256::with_last_byte(1), B256Set::default());
1752 targets.insert(
1753 B256::with_last_byte(2),
1754 B256Set::from_iter([B256::with_last_byte(10), B256::with_last_byte(20)]),
1755 );
1756 targets.insert(
1757 B256::with_last_byte(3),
1758 B256Set::from_iter([
1759 B256::with_last_byte(30),
1760 B256::with_last_byte(31),
1761 B256::with_last_byte(32),
1762 ]),
1763 );
1764
1765 let chunking_length = targets.chunking_length();
1766 for size in 1..=targets.clone().chunks(1).count() {
1767 let chunk_count = targets.clone().chunks(size).count();
1768 let expected_count = chunking_length.div_ceil(size);
1769 assert_eq!(
1770 chunk_count, expected_count,
1771 "chunking_length: {}, size: {}",
1772 chunking_length, size
1773 );
1774 }
1775 }
1776
1777 #[test]
1778 fn test_nonempty_storage_trie_returns_nonempty_proof() {
1779 let slot = B256::with_last_byte(1);
1780 let nibbles = Nibbles::unpack(keccak256(slot));
1781 let value = U256::from(999);
1782 let leaf = alloy_trie::nodes::LeafNode::new(nibbles, encode_fixed_size(&value).to_vec());
1783 let mut encoded = vec![];
1784 alloy_rlp::Encodable::encode(&leaf, &mut encoded);
1785
1786 let mut subtree = ProofNodes::default();
1787 subtree.insert(nibbles, encoded.into());
1788
1789 let multiproof = StorageMultiProof {
1790 root: B256::with_last_byte(0xFF),
1791 subtree,
1792 branch_node_masks: BranchNodeMasksMap::default(),
1793 };
1794
1795 let proof = multiproof.storage_proof(slot).unwrap();
1796 assert!(!proof.proof.is_empty(), "non-empty trie must return non-empty proof");
1797 assert_eq!(proof.value, value);
1798 }
1799
1800 #[cfg(feature = "eip1186")]
1801 #[test]
1802 fn eip1186_response_normalizes_empty_trie_proof() {
1803 let slot = B256::with_last_byte(1);
1804 let sentinel = || vec![Bytes::from([EMPTY_STRING_CODE])];
1805
1806 let account = AccountProof {
1808 address: Address::ZERO,
1809 info: None,
1810 proof: sentinel(),
1811 storage_root: EMPTY_ROOT_HASH,
1812 storage_proofs: vec![StorageProof::new(slot).with_proof(sentinel())],
1813 };
1814
1815 let resp = account.into_eip1186_response(vec![alloy_serde::JsonStorageKey::from(slot)]);
1816
1817 assert!(
1818 resp.account_proof.is_empty(),
1819 "empty account trie must yield empty account_proof, got {:?}",
1820 resp.account_proof
1821 );
1822 assert_eq!(resp.storage_proof.len(), 1);
1823 assert!(
1824 resp.storage_proof[0].proof.is_empty(),
1825 "empty storage trie must yield empty storage proof, got {:?}",
1826 resp.storage_proof[0].proof
1827 );
1828 }
1829
1830 #[cfg(feature = "eip1186")]
1831 #[test]
1832 fn eip1186_response_keeps_nonempty_proof() {
1833 let multi = vec![Bytes::from([0x01, 0x02]), Bytes::from([0x03])];
1836 let single_non_sentinel = vec![Bytes::from([0xf8, 0x44])];
1837
1838 let account = AccountProof {
1839 address: Address::ZERO,
1840 info: None,
1841 proof: multi.clone(),
1842 storage_root: EMPTY_ROOT_HASH,
1843 storage_proofs: vec![
1844 StorageProof::new(B256::with_last_byte(1)).with_proof(single_non_sentinel.clone())
1845 ],
1846 };
1847
1848 let resp = account.into_eip1186_response(vec![alloy_serde::JsonStorageKey::from(
1849 B256::with_last_byte(1),
1850 )]);
1851
1852 assert_eq!(resp.account_proof, multi);
1853 assert_eq!(resp.storage_proof[0].proof, single_non_sentinel);
1854 }
1855}