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