1use crate::{
4 BranchNodeMasks, BranchNodeMasksMap, Nibbles, ProofTrieNodeV2, TrieAccount, TrieNodeV2,
5};
6use alloc::{borrow::Cow, collections::VecDeque, vec::Vec};
7use alloy_consensus::constants::KECCAK_EMPTY;
8use alloy_primitives::{
9 keccak256,
10 map::{hash_map, B256Map, B256Set},
11 Address, Bytes, B256, U256,
12};
13use alloy_rlp::{encode_fixed_size, Decodable, Encodable, EMPTY_STRING_CODE};
14use alloy_trie::{
15 nodes::{BranchNodeRef, TrieNode},
16 proof::{verify_proof, DecodedProofNodes, ProofNodes, ProofVerificationError},
17 EMPTY_ROOT_HASH,
18};
19use derive_more::{Deref, DerefMut, IntoIterator};
20use itertools::Itertools;
21use reth_primitives_traits::Account;
22
23#[derive(Deref, DerefMut, IntoIterator, Clone, PartialEq, Eq, Default, Debug)]
25pub struct MultiProofTargets(B256Map<B256Set>);
26
27impl FromIterator<(B256, B256Set)> for MultiProofTargets {
28 fn from_iter<T: IntoIterator<Item = (B256, B256Set)>>(iter: T) -> Self {
29 Self(B256Map::from_iter(iter))
30 }
31}
32
33impl MultiProofTargets {
34 pub fn with_capacity(capacity: usize) -> Self {
36 Self(B256Map::with_capacity_and_hasher(capacity, Default::default()))
37 }
38
39 pub fn account(hashed_address: B256) -> Self {
41 Self::accounts([hashed_address])
42 }
43
44 pub fn account_with_slots<I: IntoIterator<Item = B256>>(
46 hashed_address: B256,
47 slots_iter: I,
48 ) -> Self {
49 Self(B256Map::from_iter([(hashed_address, slots_iter.into_iter().collect())]))
50 }
51
52 pub fn accounts<I: IntoIterator<Item = B256>>(iter: I) -> Self {
54 Self(iter.into_iter().map(|hashed_address| (hashed_address, Default::default())).collect())
55 }
56
57 pub fn retain_difference(&mut self, other: &Self) {
60 self.0.retain(|hashed_address, hashed_slots| {
61 if let Some(other_hashed_slots) = other.get(hashed_address) {
62 hashed_slots.retain(|hashed_slot| !other_hashed_slots.contains(hashed_slot));
63 !hashed_slots.is_empty()
64 } else {
65 true
66 }
67 });
68 }
69
70 pub fn extend(&mut self, other: Self) {
72 self.extend_inner(Cow::Owned(other));
73 }
74
75 pub fn extend_ref(&mut self, other: &Self) {
79 self.extend_inner(Cow::Borrowed(other));
80 }
81
82 fn extend_inner(&mut self, other: Cow<'_, Self>) {
83 for (hashed_address, hashed_slots) in other.iter() {
84 match self.entry(*hashed_address) {
85 hash_map::Entry::Vacant(entry) => {
86 entry.insert(hashed_slots.clone());
87 }
88 hash_map::Entry::Occupied(mut entry) => {
89 entry.get_mut().extend(hashed_slots);
90 }
91 }
92 }
93 }
94
95 pub fn chunks(self, size: usize) -> ChunkedMultiProofTargets {
99 ChunkedMultiProofTargets::new(self, size)
100 }
101
102 pub fn chunking_length(&self) -> usize {
104 self.values().map(|slots| 1 + slots.len().saturating_sub(1)).sum::<usize>()
105 }
106}
107
108#[derive(Debug)]
129pub struct ChunkedMultiProofTargets {
130 flattened_targets: alloc::vec::IntoIter<(B256, Option<B256>)>,
131 size: usize,
132}
133
134impl ChunkedMultiProofTargets {
135 fn new(targets: MultiProofTargets, size: usize) -> Self {
136 let flattened_targets = targets
137 .into_iter()
138 .flat_map(|(address, slots)| {
139 if slots.is_empty() {
140 itertools::Either::Left(core::iter::once((address, None)))
144 } else {
145 itertools::Either::Right(
146 slots.into_iter().map(move |slot| (address, Some(slot))),
147 )
148 }
149 })
150 .sorted_unstable();
151 Self { flattened_targets, size }
152 }
153}
154
155impl Iterator for ChunkedMultiProofTargets {
156 type Item = MultiProofTargets;
157
158 fn next(&mut self) -> Option<Self::Item> {
159 let chunk = self.flattened_targets.by_ref().take(self.size).fold(
160 MultiProofTargets::default(),
161 |mut acc, (address, slot)| {
162 let entry = acc.entry(address).or_default();
163 if let Some(slot) = slot {
164 entry.insert(slot);
165 }
166 acc
167 },
168 );
169
170 if chunk.is_empty() {
171 None
172 } else {
173 Some(chunk)
174 }
175 }
176}
177
178#[derive(Clone, Default, Debug, PartialEq, Eq)]
182pub struct MultiProof {
183 pub account_subtree: ProofNodes,
185 pub branch_node_masks: BranchNodeMasksMap,
188 pub storages: B256Map<StorageMultiProof>,
190}
191
192impl MultiProof {
193 pub fn is_empty(&self) -> bool {
195 self.account_subtree.is_empty() &&
196 self.branch_node_masks.is_empty() &&
197 self.storages.is_empty()
198 }
199
200 pub fn account_proof_nodes(&self, path: &Nibbles) -> Vec<(Nibbles, Bytes)> {
202 self.account_subtree.matching_nodes_sorted(path)
203 }
204
205 pub fn storage_proof_nodes(
207 &self,
208 hashed_address: B256,
209 slots: impl IntoIterator<Item = B256>,
210 ) -> Vec<(B256, Vec<(Nibbles, Bytes)>)> {
211 self.storages
212 .get(&hashed_address)
213 .map(|storage_mp| {
214 slots
215 .into_iter()
216 .map(|slot| {
217 let nibbles = Nibbles::unpack(slot);
218 (slot, storage_mp.subtree.matching_nodes_sorted(&nibbles))
219 })
220 .collect()
221 })
222 .unwrap_or_default()
223 }
224
225 pub fn account_proof(
227 &self,
228 address: Address,
229 slots: &[B256],
230 ) -> Result<AccountProof, alloy_rlp::Error> {
231 let hashed_address = keccak256(address);
232 let nibbles = Nibbles::unpack(hashed_address);
233
234 let proof = self
236 .account_proof_nodes(&nibbles)
237 .into_iter()
238 .map(|(_, node)| node)
239 .collect::<Vec<_>>();
240
241 let info = 'info: {
244 if let Some(last) = proof.last() &&
245 let TrieNode::Leaf(leaf) = TrieNode::decode(&mut &last[..])? &&
246 nibbles.ends_with(&leaf.key)
247 {
248 let account = TrieAccount::decode(&mut &leaf.value[..])?;
249 break 'info Some(Account {
250 balance: account.balance,
251 nonce: account.nonce,
252 bytecode_hash: (account.code_hash != KECCAK_EMPTY).then_some(account.code_hash),
253 })
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 {
379 balance: account.balance,
380 nonce: account.nonce,
381 bytecode_hash: (account.code_hash != KECCAK_EMPTY).then_some(account.code_hash),
382 })
383 }
384 None
385 };
386
387 let storage_multiproof = self.storages.get(&hashed_address);
389 let storage_root = storage_multiproof.map(|m| m.root).unwrap_or(EMPTY_ROOT_HASH);
390 let mut storage_proofs = Vec::with_capacity(slots.len());
391 for slot in slots {
392 let proof = if let Some(multiproof) = &storage_multiproof {
393 multiproof.storage_proof(*slot)?
394 } else {
395 DecodedStorageProof::new(*slot)
396 };
397 storage_proofs.push(proof);
398 }
399 Ok(DecodedAccountProof { address, info, proof, storage_root, storage_proofs })
400 }
401
402 pub fn extend(&mut self, other: Self) {
405 self.account_subtree.extend_from(other.account_subtree);
406 self.branch_node_masks.extend(other.branch_node_masks);
407
408 let reserve = if self.storages.is_empty() {
409 other.storages.len()
410 } else {
411 other.storages.len().div_ceil(2)
412 };
413 self.storages.reserve(reserve);
414 for (hashed_address, storage) in other.storages {
415 match self.storages.entry(hashed_address) {
416 hash_map::Entry::Occupied(mut entry) => {
417 debug_assert_eq!(entry.get().root, storage.root);
418 let entry = entry.get_mut();
419 entry.subtree.extend_from(storage.subtree);
420 entry.branch_node_masks.extend(storage.branch_node_masks);
421 }
422 hash_map::Entry::Vacant(entry) => {
423 entry.insert(storage);
424 }
425 }
426 }
427 }
428
429 pub fn from_storage_proof(
431 hashed_address: B256,
432 storage_proof: DecodedStorageMultiProof,
433 ) -> Self {
434 Self {
435 storages: B256Map::from_iter([(hashed_address, storage_proof)]),
436 ..Default::default()
437 }
438 }
439}
440
441impl TryFrom<MultiProof> for DecodedMultiProof {
442 type Error = alloy_rlp::Error;
443
444 fn try_from(multi_proof: MultiProof) -> Result<Self, Self::Error> {
445 let account_subtree = DecodedProofNodes::try_from(multi_proof.account_subtree)?;
446 let storages = multi_proof
447 .storages
448 .into_iter()
449 .map(|(address, storage)| Ok((address, storage.try_into()?)))
450 .collect::<Result<B256Map<_>, alloy_rlp::Error>>()?;
451 Ok(Self { account_subtree, branch_node_masks: multi_proof.branch_node_masks, storages })
452 }
453}
454
455#[derive(Clone, Debug, PartialEq, Eq, Default)]
458pub struct DecodedMultiProofV2 {
459 pub account_proofs: Vec<ProofTrieNodeV2>,
461 pub storage_proofs: B256Map<Vec<ProofTrieNodeV2>>,
463}
464
465impl DecodedMultiProofV2 {
466 pub fn is_empty(&self) -> bool {
468 self.account_proofs.is_empty() && self.storage_proofs.is_empty()
469 }
470
471 pub fn account_proof(
473 &self,
474 address: Address,
475 slots: &[B256],
476 ) -> Result<AccountProof, alloy_rlp::Error> {
477 let hashed_address = keccak256(address);
478 let nibbles = Nibbles::unpack(hashed_address);
479 let account_nodes = matching_v2_proof_nodes(&self.account_proofs, &nibbles);
480
481 let (info, storage_root) = 'account: {
482 if let Some(ProofTrieNodeV2 { node: TrieNodeV2::Leaf(leaf), .. }) =
483 account_nodes.clone().last() &&
484 nibbles.ends_with(&leaf.key)
485 {
486 let account = TrieAccount::decode(&mut &leaf.value[..])?;
487 break 'account (
488 Some(Account {
489 balance: account.balance,
490 nonce: account.nonce,
491 bytecode_hash: (account.code_hash != KECCAK_EMPTY)
492 .then_some(account.code_hash),
493 }),
494 account.storage_root,
495 )
496 }
497 (None, EMPTY_ROOT_HASH)
498 };
499
500 let proof = encode_v2_proof_nodes(account_nodes);
501 let storage_nodes = self.storage_proofs.get(&hashed_address);
502 let mut storage_proofs = Vec::with_capacity(slots.len());
503 for slot in slots {
504 let nibbles = Nibbles::unpack(keccak256(slot));
505 let nodes =
506 storage_nodes.iter().flat_map(|nodes| matching_v2_proof_nodes(nodes, &nibbles));
507 let value = 'value: {
508 if let Some(ProofTrieNodeV2 { node: TrieNodeV2::Leaf(leaf), .. }) =
509 nodes.clone().last() &&
510 nibbles.ends_with(&leaf.key)
511 {
512 break 'value U256::decode(&mut &leaf.value[..])?
513 }
514 U256::ZERO
515 };
516 storage_proofs.push(StorageProof {
517 key: *slot,
518 nibbles,
519 value,
520 proof: encode_v2_proof_nodes(nodes),
521 });
522 }
523
524 Ok(AccountProof { address, info, proof, storage_root, storage_proofs })
525 }
526
527 pub fn from_witness(
534 state_root: B256,
535 witness: &B256Map<impl AsRef<[u8]>>,
536 ) -> Result<Self, alloy_rlp::Error> {
537 let mut account_nodes: Vec<(Nibbles, TrieNode, Option<BranchNodeMasks>)> = Vec::new();
538 let mut storage_nodes: B256Map<Vec<(Nibbles, TrieNode, Option<BranchNodeMasks>)>> =
539 B256Map::default();
540
541 let mut queue: VecDeque<(B256, Nibbles, Option<B256>)> =
542 VecDeque::from([(state_root, Nibbles::default(), None)]);
543
544 while let Some((hash, path, maybe_account)) = queue.pop_front() {
545 let Some(rlp_bytes) = witness.get(&hash) else { continue };
546 let trie_node = TrieNode::decode(&mut rlp_bytes.as_ref())?;
547
548 match &trie_node {
549 TrieNode::Branch(branch) => {
550 for (idx, maybe_child) in branch.as_ref().children() {
551 if let Some(child_hash) =
552 maybe_child.and_then(alloy_trie::nodes::RlpNode::as_hash)
553 {
554 let mut child_path = path;
555 child_path.push_unchecked(idx);
556 queue.push_back((child_hash, child_path, maybe_account));
557 }
558 }
559 }
560 TrieNode::Extension(ext) => {
561 if let Some(child_hash) = ext.child.as_hash() {
562 let mut child_path = path;
563 child_path.extend(&ext.key);
564 queue.push_back((child_hash, child_path, maybe_account));
565 }
566 }
567 TrieNode::Leaf(leaf) => {
568 if maybe_account.is_none() {
569 let mut full_path = path;
570 full_path.extend(&leaf.key);
571 let hashed_address = B256::from_slice(&full_path.pack());
572 let account = TrieAccount::decode(&mut &leaf.value[..])?;
573 if account.storage_root != EMPTY_ROOT_HASH {
574 queue.push_back((
575 account.storage_root,
576 Nibbles::default(),
577 Some(hashed_address),
578 ));
579 }
580 }
581 }
582 TrieNode::EmptyRoot => {}
583 }
584
585 if let Some(account) = maybe_account {
586 storage_nodes.entry(account).or_default().push((path, trie_node, None));
587 } else {
588 account_nodes.push((path, trie_node, None));
589 }
590 }
591
592 account_nodes.sort_by(|(a, _, _), (b, _, _)| crate::depth_first_cmp(a, b));
593 let account_proofs = ProofTrieNodeV2::from_sorted_trie_nodes(account_nodes);
594
595 let mut storage_proofs = B256Map::default();
596 for (account, mut nodes) in storage_nodes {
597 nodes.sort_by(|(a, _, _), (b, _, _)| crate::depth_first_cmp(a, b));
598 storage_proofs.insert(account, ProofTrieNodeV2::from_sorted_trie_nodes(nodes));
599 }
600
601 Ok(Self { account_proofs, storage_proofs })
602 }
603
604 pub fn extend(&mut self, other: Self) {
608 self.account_proofs.extend(other.account_proofs);
609 for (hashed_address, other_storage_proofs) in other.storage_proofs {
610 match self.storage_proofs.entry(hashed_address) {
611 hash_map::Entry::Vacant(entry) => {
612 entry.insert(other_storage_proofs);
613 }
614 hash_map::Entry::Occupied(mut entry) => {
615 entry.get_mut().extend(other_storage_proofs);
616 }
617 }
618 }
619 }
620}
621
622fn matching_v2_proof_nodes<'a>(
624 nodes: &'a [ProofTrieNodeV2],
625 path: &'a Nibbles,
626) -> impl Iterator<Item = &'a ProofTrieNodeV2> + Clone {
627 nodes.iter().rev().filter(move |node| path.starts_with(&node.path))
628}
629
630fn encode_v2_proof_nodes<'a>(nodes: impl Iterator<Item = &'a ProofTrieNodeV2>) -> Vec<Bytes> {
635 let mut proof = Vec::new();
636 for proof_node in nodes {
637 let mut encoded = Vec::new();
638 proof_node.node.encode(&mut encoded);
639 proof.push(Bytes::from(encoded));
640
641 if let TrieNodeV2::Branch(branch) = &proof_node.node &&
642 !branch.key.is_empty()
643 {
644 let mut encoded = Vec::new();
645 BranchNodeRef::new(&branch.stack, branch.state_mask).encode(&mut encoded);
646 proof.push(Bytes::from(encoded));
647 }
648 }
649 proof
650}
651
652impl From<DecodedMultiProof> for DecodedMultiProofV2 {
653 fn from(proof: DecodedMultiProof) -> Self {
654 let account_proofs =
655 decoded_proof_nodes_to_v2(proof.account_subtree, &proof.branch_node_masks);
656 let storage_proofs = proof
657 .storages
658 .into_iter()
659 .map(|(address, storage)| {
660 (address, decoded_proof_nodes_to_v2(storage.subtree, &storage.branch_node_masks))
661 })
662 .collect();
663 Self { account_proofs, storage_proofs }
664 }
665}
666
667fn decoded_proof_nodes_to_v2(
670 nodes: DecodedProofNodes,
671 masks: &BranchNodeMasksMap,
672) -> Vec<ProofTrieNodeV2> {
673 let mut sorted: Vec<_> = nodes.into_inner().into_iter().collect();
674 sorted.sort_unstable_by(|a, b| crate::depth_first_cmp(&a.0, &b.0));
675 ProofTrieNodeV2::from_sorted_trie_nodes(
676 sorted.into_iter().map(|(path, node)| (path, node, masks.get(&path).copied())),
677 )
678}
679
680#[derive(Clone, Debug, PartialEq, Eq)]
682pub struct StorageMultiProof {
683 pub root: B256,
685 pub subtree: ProofNodes,
687 pub branch_node_masks: BranchNodeMasksMap,
690}
691
692impl StorageMultiProof {
693 pub fn empty() -> Self {
695 Self {
696 root: EMPTY_ROOT_HASH,
697 subtree: ProofNodes::from_iter([(
698 Nibbles::default(),
699 Bytes::from([EMPTY_STRING_CODE]),
700 )]),
701 branch_node_masks: BranchNodeMasksMap::default(),
702 }
703 }
704
705 pub fn storage_proof(&self, slot: B256) -> Result<StorageProof, alloy_rlp::Error> {
707 let nibbles = Nibbles::unpack(keccak256(slot));
708
709 let proof = self
711 .subtree
712 .matching_nodes_iter(&nibbles)
713 .sorted_by(|a, b| a.0.cmp(b.0))
714 .map(|(_, node)| node.clone())
715 .collect::<Vec<_>>();
716
717 let value = 'value: {
720 if let Some(last) = proof.last() &&
721 let TrieNode::Leaf(leaf) = TrieNode::decode(&mut &last[..])? &&
722 nibbles.ends_with(&leaf.key)
723 {
724 break 'value U256::decode(&mut &leaf.value[..])?
725 }
726 U256::ZERO
727 };
728
729 Ok(StorageProof { key: slot, nibbles, value, proof })
730 }
731}
732
733#[derive(Clone, Debug, PartialEq, Eq)]
735pub struct DecodedStorageMultiProof {
736 pub root: B256,
738 pub subtree: DecodedProofNodes,
740 pub branch_node_masks: BranchNodeMasksMap,
743}
744
745impl DecodedStorageMultiProof {
746 pub fn empty() -> Self {
748 Self {
749 root: EMPTY_ROOT_HASH,
750 subtree: DecodedProofNodes::from_iter([(Nibbles::default(), TrieNode::EmptyRoot)]),
751 branch_node_masks: BranchNodeMasksMap::default(),
752 }
753 }
754
755 pub fn storage_proof(&self, slot: B256) -> Result<DecodedStorageProof, alloy_rlp::Error> {
757 let nibbles = Nibbles::unpack(keccak256(slot));
758
759 let proof = self
761 .subtree
762 .matching_nodes_iter(&nibbles)
763 .sorted_by(|a, b| a.0.cmp(b.0))
764 .map(|(_, node)| node.clone())
765 .collect::<Vec<_>>();
766
767 let value = 'value: {
770 if let Some(TrieNode::Leaf(leaf)) = proof.last() &&
771 nibbles.ends_with(&leaf.key)
772 {
773 break 'value U256::decode(&mut &leaf.value[..])?
774 }
775 U256::ZERO
776 };
777
778 Ok(DecodedStorageProof { key: slot, nibbles, value, proof })
779 }
780}
781
782impl TryFrom<StorageMultiProof> for DecodedStorageMultiProof {
783 type Error = alloy_rlp::Error;
784
785 fn try_from(multi_proof: StorageMultiProof) -> Result<Self, Self::Error> {
786 let subtree = DecodedProofNodes::try_from(multi_proof.subtree)?;
787 Ok(Self {
788 root: multi_proof.root,
789 subtree,
790 branch_node_masks: multi_proof.branch_node_masks,
791 })
792 }
793}
794
795#[derive(Clone, PartialEq, Eq, Debug)]
797#[cfg_attr(any(test, feature = "serde"), derive(serde::Serialize, serde::Deserialize))]
798#[cfg_attr(any(test, feature = "serde"), serde(rename_all = "camelCase"))]
799pub struct AccountProof {
800 pub address: Address,
802 pub info: Option<Account>,
804 pub proof: Vec<Bytes>,
807 pub storage_root: B256,
809 pub storage_proofs: Vec<StorageProof>,
811}
812
813#[cfg(feature = "eip1186")]
821fn normalize_eip1186_empty_trie_proof(proof: Vec<Bytes>) -> Vec<Bytes> {
822 if proof.len() == 1 && proof[0].as_ref() == [EMPTY_STRING_CODE] {
823 Vec::new()
824 } else {
825 proof
826 }
827}
828
829#[cfg(feature = "eip1186")]
830impl AccountProof {
831 pub fn into_eip1186_response(
839 self,
840 slots: Vec<alloy_serde::JsonStorageKey>,
841 ) -> alloy_rpc_types_eth::EIP1186AccountProofResponse {
842 self.into_eip1186_response_with(slots, false)
843 }
844
845 pub fn into_eip1186_response_with(
856 self,
857 slots: Vec<alloy_serde::JsonStorageKey>,
858 zero_empty_account: bool,
859 ) -> alloy_rpc_types_eth::EIP1186AccountProofResponse {
860 let is_non_existent = self.info.is_none();
861 let info = self.info.unwrap_or_default();
862 let (code_hash, storage_hash) = if is_non_existent && zero_empty_account {
863 (B256::ZERO, B256::ZERO)
864 } else {
865 (info.get_bytecode_hash(), self.storage_root)
866 };
867 alloy_rpc_types_eth::EIP1186AccountProofResponse {
868 address: self.address,
869 balance: info.balance,
870 code_hash,
871 nonce: info.nonce,
872 storage_hash,
873 account_proof: normalize_eip1186_empty_trie_proof(self.proof),
874 storage_proof: self
875 .storage_proofs
876 .into_iter()
877 .filter_map(|proof| {
878 let input_slot = slots.iter().find(|s| s.as_b256() == proof.key)?;
879 Some(proof.into_eip1186_proof(*input_slot))
880 })
881 .collect(),
882 }
883 }
884
885 pub fn from_eip1186_proof(proof: alloy_rpc_types_eth::EIP1186AccountProofResponse) -> Self {
891 let alloy_rpc_types_eth::EIP1186AccountProofResponse {
892 nonce,
893 address,
894 balance,
895 code_hash,
896 storage_hash,
897 account_proof,
898 storage_proof,
899 ..
900 } = proof;
901 let storage_proofs = storage_proof.into_iter().map(Into::into).collect();
902
903 let (storage_root, info) = if nonce == 0 &&
904 balance.is_zero() &&
905 (storage_hash.is_zero() || storage_hash == EMPTY_ROOT_HASH) &&
906 (code_hash == KECCAK_EMPTY || code_hash.is_zero())
907 {
908 (EMPTY_ROOT_HASH, None)
917 } else {
918 (storage_hash, Some(Account { nonce, balance, bytecode_hash: code_hash.into() }))
919 };
920
921 Self { address, info, proof: account_proof, storage_root, storage_proofs }
922 }
923}
924
925#[cfg(feature = "eip1186")]
926impl From<alloy_rpc_types_eth::EIP1186AccountProofResponse> for AccountProof {
927 fn from(proof: alloy_rpc_types_eth::EIP1186AccountProofResponse) -> Self {
928 Self::from_eip1186_proof(proof)
929 }
930}
931
932impl Default for AccountProof {
933 fn default() -> Self {
934 Self::new(Address::default())
935 }
936}
937
938impl AccountProof {
939 pub const fn new(address: Address) -> Self {
941 Self {
942 address,
943 info: None,
944 proof: Vec::new(),
945 storage_root: EMPTY_ROOT_HASH,
946 storage_proofs: Vec::new(),
947 }
948 }
949
950 pub fn verify(&self, root: B256) -> Result<(), ProofVerificationError> {
952 for storage_proof in &self.storage_proofs {
954 storage_proof.verify(self.storage_root)?;
955 }
956
957 let expected = if self.info.is_none() && self.storage_root == EMPTY_ROOT_HASH {
959 None
960 } else {
961 Some(alloy_rlp::encode(
962 self.info.unwrap_or_default().into_trie_account(self.storage_root),
963 ))
964 };
965 let nibbles = Nibbles::unpack(keccak256(self.address));
966 verify_proof(root, nibbles, expected, &self.proof)
967 }
968}
969
970#[derive(Clone, PartialEq, Eq, Debug)]
972pub struct DecodedAccountProof {
973 pub address: Address,
975 pub info: Option<Account>,
977 pub proof: Vec<TrieNode>,
980 pub storage_root: B256,
982 pub storage_proofs: Vec<DecodedStorageProof>,
984}
985
986impl Default for DecodedAccountProof {
987 fn default() -> Self {
988 Self::new(Address::default())
989 }
990}
991
992impl DecodedAccountProof {
993 pub const fn new(address: Address) -> Self {
995 Self {
996 address,
997 info: None,
998 proof: Vec::new(),
999 storage_root: EMPTY_ROOT_HASH,
1000 storage_proofs: Vec::new(),
1001 }
1002 }
1003}
1004
1005#[derive(Clone, PartialEq, Eq, Default, Debug)]
1007#[cfg_attr(any(test, feature = "serde"), derive(serde::Serialize, serde::Deserialize))]
1008pub struct StorageProof {
1009 pub key: B256,
1011 pub nibbles: Nibbles,
1013 pub value: U256,
1015 pub proof: Vec<Bytes>,
1018}
1019
1020impl StorageProof {
1021 pub fn new(key: B256) -> Self {
1023 let nibbles = Nibbles::unpack(keccak256(key));
1024 Self { key, nibbles, ..Default::default() }
1025 }
1026
1027 pub fn new_with_hashed(key: B256, hashed_key: B256) -> Self {
1029 Self { key, nibbles: Nibbles::unpack(hashed_key), ..Default::default() }
1030 }
1031
1032 pub fn new_with_nibbles(key: B256, nibbles: Nibbles) -> Self {
1034 Self { key, nibbles, ..Default::default() }
1035 }
1036
1037 pub fn with_proof(mut self, proof: Vec<Bytes>) -> Self {
1039 self.proof = proof;
1040 self
1041 }
1042
1043 pub fn verify(&self, root: B256) -> Result<(), ProofVerificationError> {
1045 let expected =
1046 if self.value.is_zero() { None } else { Some(encode_fixed_size(&self.value).to_vec()) };
1047 verify_proof(root, self.nibbles, expected, &self.proof)
1048 }
1049}
1050
1051#[cfg(feature = "eip1186")]
1052impl StorageProof {
1053 pub fn into_eip1186_proof(
1055 self,
1056 slot: alloy_serde::JsonStorageKey,
1057 ) -> alloy_rpc_types_eth::EIP1186StorageProof {
1058 alloy_rpc_types_eth::EIP1186StorageProof {
1059 key: slot,
1060 value: self.value,
1061 proof: normalize_eip1186_empty_trie_proof(self.proof),
1062 }
1063 }
1064
1065 pub fn from_eip1186_proof(storage_proof: alloy_rpc_types_eth::EIP1186StorageProof) -> Self {
1070 Self {
1071 value: storage_proof.value,
1072 proof: storage_proof.proof,
1073 ..Self::new(storage_proof.key.as_b256())
1074 }
1075 }
1076}
1077
1078#[cfg(feature = "eip1186")]
1079impl From<alloy_rpc_types_eth::EIP1186StorageProof> for StorageProof {
1080 fn from(proof: alloy_rpc_types_eth::EIP1186StorageProof) -> Self {
1081 Self::from_eip1186_proof(proof)
1082 }
1083}
1084
1085#[derive(Clone, PartialEq, Eq, Default, Debug)]
1087pub struct DecodedStorageProof {
1088 pub key: B256,
1090 pub nibbles: Nibbles,
1092 pub value: U256,
1094 pub proof: Vec<TrieNode>,
1097}
1098
1099impl DecodedStorageProof {
1100 pub fn new(key: B256) -> Self {
1102 let nibbles = Nibbles::unpack(keccak256(key));
1103 Self { key, nibbles, ..Default::default() }
1104 }
1105
1106 pub fn new_with_hashed(key: B256, hashed_key: B256) -> Self {
1108 Self { key, nibbles: Nibbles::unpack(hashed_key), ..Default::default() }
1109 }
1110
1111 pub fn new_with_nibbles(key: B256, nibbles: Nibbles) -> Self {
1113 Self { key, nibbles, ..Default::default() }
1114 }
1115
1116 pub fn with_proof(mut self, proof: Vec<TrieNode>) -> Self {
1118 self.proof = proof;
1119 self
1120 }
1121}
1122
1123#[cfg(any(test, feature = "test-utils"))]
1126pub mod triehash {
1127 use alloy_primitives::{keccak256, B256};
1128 use alloy_rlp::RlpEncodable;
1129 use hash_db::Hasher;
1130 use plain_hasher::PlainHasher;
1131
1132 #[derive(Default, Debug, Clone, PartialEq, Eq, RlpEncodable)]
1134 #[non_exhaustive]
1135 pub struct KeccakHasher;
1136
1137 #[cfg(any(test, feature = "test-utils"))]
1138 impl Hasher for KeccakHasher {
1139 type Out = B256;
1140 type StdHasher = PlainHasher;
1141
1142 const LENGTH: usize = 32;
1143
1144 fn hash(x: &[u8]) -> Self::Out {
1145 keccak256(x)
1146 }
1147 }
1148}
1149
1150#[cfg(test)]
1151mod tests {
1152 use super::*;
1153 use alloy_trie::{
1154 nodes::{BranchNode, LeafNode, RlpNode},
1155 TrieMask,
1156 };
1157
1158 #[test]
1159 fn v2_account_proof_expands_extension_branch() {
1160 let branch =
1161 BranchNode::new(vec![RlpNode::word_rlp(&B256::repeat_byte(0x11))], TrieMask::from(1));
1162 let branch_rlp = alloy_rlp::encode(&branch);
1163 let combined = TrieNodeV2::Branch(crate::BranchNodeV2::new(
1164 Nibbles::from_nibbles([0xa, 0xb]),
1165 branch.stack,
1166 branch.state_mask,
1167 Some(RlpNode::from_rlp(&branch_rlp)),
1168 ));
1169 let extension_rlp = alloy_rlp::encode(&combined);
1170 let multiproof = DecodedMultiProofV2 {
1171 account_proofs: vec![ProofTrieNodeV2 {
1172 path: Nibbles::default(),
1173 node: combined,
1174 masks: None,
1175 }],
1176 ..Default::default()
1177 };
1178
1179 let proof = multiproof.account_proof(Address::ZERO, &[]).unwrap();
1180
1181 assert_eq!(proof.proof, vec![Bytes::from(extension_rlp), Bytes::from(branch_rlp)]);
1182 assert!(proof.info.is_none());
1183 }
1184
1185 #[test]
1186 fn witness_nodes_are_depth_first_ordered() {
1187 fn insert_node(witness: &mut B256Map<Bytes>, node: impl alloy_rlp::Encodable) -> RlpNode {
1188 let encoded = alloy_rlp::encode(node);
1189 witness.insert(keccak256(&encoded), encoded.clone().into());
1190 RlpNode::from_rlp(&encoded)
1191 }
1192
1193 let mut witness = B256Map::default();
1194 let leaf_key = Nibbles::from_nibbles([0; 63]);
1195
1196 let storage_leaf_0 = insert_node(
1197 &mut witness,
1198 LeafNode::new(leaf_key, encode_fixed_size(&U256::from(1)).to_vec()),
1199 );
1200 let storage_leaf_1 = insert_node(
1201 &mut witness,
1202 LeafNode::new(leaf_key, encode_fixed_size(&U256::from(2)).to_vec()),
1203 );
1204 let storage_root = insert_node(
1205 &mut witness,
1206 BranchNode::new(vec![storage_leaf_0, storage_leaf_1], TrieMask::new(0b11)),
1207 );
1208
1209 let account_leaf_0 = insert_node(
1210 &mut witness,
1211 LeafNode::new(
1212 leaf_key,
1213 alloy_rlp::encode(TrieAccount {
1214 storage_root: storage_root.as_hash().expect("storage root is hashed"),
1215 ..Default::default()
1216 }),
1217 ),
1218 );
1219 let account_leaf_1 = insert_node(
1220 &mut witness,
1221 LeafNode::new(leaf_key, alloy_rlp::encode(TrieAccount::default())),
1222 );
1223 let state_root = insert_node(
1224 &mut witness,
1225 BranchNode::new(vec![account_leaf_0, account_leaf_1], TrieMask::new(0b11)),
1226 )
1227 .as_hash()
1228 .expect("state root is hashed");
1229
1230 let proof = DecodedMultiProofV2::from_witness(state_root, &witness).unwrap();
1231 let expected_paths =
1232 [Nibbles::from_nibbles([0]), Nibbles::from_nibbles([1]), Nibbles::default()];
1233
1234 assert_eq!(
1235 proof.account_proofs.iter().map(|node| node.path).collect::<Vec<_>>(),
1236 expected_paths
1237 );
1238 assert_eq!(
1239 proof.storage_proofs[&B256::ZERO].iter().map(|node| node.path).collect::<Vec<_>>(),
1240 expected_paths
1241 );
1242 }
1243
1244 #[test]
1245 fn test_multiproof_extend_account_proofs() {
1246 let mut proof1 = MultiProof::default();
1247 let mut proof2 = MultiProof::default();
1248
1249 let addr1 = B256::random();
1250 let addr2 = B256::random();
1251
1252 proof1.account_subtree.insert(
1253 Nibbles::unpack(addr1),
1254 alloy_rlp::encode_fixed_size(&U256::from(42)).to_vec().into(),
1255 );
1256 proof2.account_subtree.insert(
1257 Nibbles::unpack(addr2),
1258 alloy_rlp::encode_fixed_size(&U256::from(43)).to_vec().into(),
1259 );
1260
1261 proof1.extend(proof2);
1262
1263 assert!(proof1.account_subtree.contains_key(&Nibbles::unpack(addr1)));
1264 assert!(proof1.account_subtree.contains_key(&Nibbles::unpack(addr2)));
1265 }
1266
1267 #[test]
1268 fn test_multiproof_extend_storage_proofs() {
1269 let mut proof1 = MultiProof::default();
1270 let mut proof2 = MultiProof::default();
1271
1272 let addr = B256::random();
1273 let root = B256::random();
1274
1275 let mut subtree1 = ProofNodes::default();
1276 subtree1.insert(
1277 Nibbles::from_nibbles(vec![0]),
1278 alloy_rlp::encode_fixed_size(&U256::from(42)).to_vec().into(),
1279 );
1280 proof1.storages.insert(
1281 addr,
1282 StorageMultiProof {
1283 root,
1284 subtree: subtree1,
1285 branch_node_masks: BranchNodeMasksMap::default(),
1286 },
1287 );
1288
1289 let mut subtree2 = ProofNodes::default();
1290 subtree2.insert(
1291 Nibbles::from_nibbles(vec![1]),
1292 alloy_rlp::encode_fixed_size(&U256::from(43)).to_vec().into(),
1293 );
1294 proof2.storages.insert(
1295 addr,
1296 StorageMultiProof {
1297 root,
1298 subtree: subtree2,
1299 branch_node_masks: BranchNodeMasksMap::default(),
1300 },
1301 );
1302
1303 proof1.extend(proof2);
1304
1305 let storage = proof1.storages.get(&addr).unwrap();
1306 assert_eq!(storage.root, root);
1307 assert!(storage.subtree.contains_key(&Nibbles::from_nibbles(vec![0])));
1308 assert!(storage.subtree.contains_key(&Nibbles::from_nibbles(vec![1])));
1309 }
1310
1311 #[test]
1312 fn test_multi_proof_retain_difference() {
1313 let mut empty = MultiProofTargets::default();
1314 empty.retain_difference(&Default::default());
1315 assert!(empty.is_empty());
1316
1317 let targets = MultiProofTargets::accounts((0..10).map(B256::with_last_byte));
1318
1319 let mut diffed = targets.clone();
1320 diffed.retain_difference(&MultiProofTargets::account(B256::with_last_byte(11)));
1321 assert_eq!(diffed, targets);
1322
1323 diffed.retain_difference(&MultiProofTargets::accounts((0..5).map(B256::with_last_byte)));
1324 assert_eq!(diffed, MultiProofTargets::accounts((5..10).map(B256::with_last_byte)));
1325
1326 diffed.retain_difference(&targets);
1327 assert!(diffed.is_empty());
1328
1329 let mut targets = MultiProofTargets::default();
1330 let (account1, account2, account3) =
1331 (1..=3).map(B256::with_last_byte).collect_tuple().unwrap();
1332 let account2_slots = (1..5).map(B256::with_last_byte).collect::<B256Set>();
1333 targets.insert(account1, B256Set::from_iter([B256::with_last_byte(1)]));
1334 targets.insert(account2, account2_slots.clone());
1335 targets.insert(account3, B256Set::from_iter([B256::with_last_byte(1)]));
1336
1337 let mut diffed = targets.clone();
1338 diffed.retain_difference(&MultiProofTargets::accounts((1..=3).map(B256::with_last_byte)));
1339 assert_eq!(diffed, targets);
1340
1341 let mut account2_slots_expected_len = account2_slots.len();
1343 for slot in account2_slots.iter().skip(1) {
1344 diffed.retain_difference(&MultiProofTargets::account_with_slots(account2, [*slot]));
1345 account2_slots_expected_len -= 1;
1346 assert_eq!(
1347 diffed.get(&account2).map(|slots| slots.len()),
1348 Some(account2_slots_expected_len)
1349 );
1350 }
1351
1352 diffed.retain_difference(&targets);
1353 assert!(diffed.is_empty());
1354 }
1355
1356 #[test]
1357 fn test_multi_proof_retain_difference_no_overlap() {
1358 let mut targets = MultiProofTargets::default();
1359
1360 let (addr1, addr2) = (B256::random(), B256::random());
1362 let (slot1, slot2) = (B256::random(), B256::random());
1363 targets.insert(addr1, std::iter::once(slot1).collect());
1364 targets.insert(addr2, std::iter::once(slot2).collect());
1365
1366 let mut retained = targets.clone();
1367 retained.retain_difference(&Default::default());
1368 assert_eq!(retained, targets);
1369
1370 let mut other_targets = MultiProofTargets::default();
1372 let addr3 = B256::random();
1373 let slot3 = B256::random();
1374 other_targets.insert(addr3, B256Set::from_iter([slot3]));
1375
1376 let mut retained = targets.clone();
1379 retained.retain_difference(&other_targets);
1380 assert_eq!(retained, targets);
1381 }
1382
1383 #[test]
1384 fn test_get_prefetch_proof_targets_remove_subset() {
1385 let mut targets = MultiProofTargets::default();
1387 let (addr1, addr2) = (B256::random(), B256::random());
1388 let (slot1, slot2) = (B256::random(), B256::random());
1389 targets.insert(addr1, B256Set::from_iter([slot1]));
1390 targets.insert(addr2, B256Set::from_iter([slot2]));
1391
1392 let other_targets = MultiProofTargets::account_with_slots(addr1, [slot1]);
1394
1395 let mut retained = targets.clone();
1396 retained.retain_difference(&other_targets);
1397
1398 assert_eq!(retained.len(), 1);
1400 assert!(!retained.contains_key(&addr1));
1401 assert!(retained.contains_key(&addr2));
1402
1403 let slot3 = B256::random();
1405 targets.get_mut(&addr1).unwrap().insert(slot3);
1406
1407 let mut retained = targets.clone();
1408 retained.retain_difference(&other_targets);
1409
1410 assert_eq!(retained.len(), 2);
1413 assert!(retained.contains_key(&addr1));
1414 assert_eq!(retained.get(&addr1), Some(&B256Set::from_iter([slot3])));
1415 assert!(retained.contains_key(&addr2));
1416 assert_eq!(retained.get(&addr2), Some(&B256Set::from_iter([slot2])));
1417 }
1418
1419 #[test]
1420 #[cfg(feature = "eip1186")]
1421 fn eip_1186_roundtrip() {
1422 let mut acc = AccountProof {
1423 address: Address::random(),
1424 info: Some(
1425 Account { nonce: 100, balance: U256::ZERO, bytecode_hash: Some(KECCAK_EMPTY) },
1427 ),
1428 proof: vec![],
1429 storage_root: B256::ZERO,
1430 storage_proofs: vec![],
1431 };
1432
1433 let rpc_proof = acc.clone().into_eip1186_response(Vec::new());
1434 let inverse: AccountProof = rpc_proof.into();
1435 assert_eq!(acc, inverse);
1436
1437 acc.info.as_mut().unwrap().nonce = 0;
1439 let rpc_proof = acc.clone().into_eip1186_response(Vec::new());
1440 let inverse: AccountProof = rpc_proof.into();
1441 acc.info.take();
1442 acc.storage_root = EMPTY_ROOT_HASH;
1443 assert_eq!(acc, inverse);
1444 }
1445
1446 #[test]
1447 #[cfg(feature = "eip1186")]
1448 fn from_eip1186_proof_accepts_geth_zero_hashes() {
1449 let geth_proof = alloy_rpc_types_eth::EIP1186AccountProofResponse {
1454 address: Address::random(),
1455 balance: U256::ZERO,
1456 code_hash: B256::ZERO,
1457 nonce: 0,
1458 storage_hash: B256::ZERO,
1459 account_proof: vec![],
1460 storage_proof: vec![],
1461 };
1462
1463 let acc: AccountProof = geth_proof.into();
1464 assert!(acc.info.is_none());
1466 assert_eq!(acc.storage_root, EMPTY_ROOT_HASH);
1467 }
1468
1469 #[test]
1470 #[cfg(feature = "eip1186")]
1471 fn from_eip1186_proof_accepts_empty_hashes() {
1472 let proof = alloy_rpc_types_eth::EIP1186AccountProofResponse {
1473 address: Address::random(),
1474 balance: U256::ZERO,
1475 code_hash: KECCAK_EMPTY,
1476 nonce: 0,
1477 storage_hash: EMPTY_ROOT_HASH,
1478 account_proof: vec![],
1479 storage_proof: vec![],
1480 };
1481
1482 let acc: AccountProof = proof.into();
1483 assert!(acc.info.is_none());
1484 assert_eq!(acc.storage_root, EMPTY_ROOT_HASH);
1485 }
1486
1487 #[test]
1488 #[cfg(feature = "eip1186")]
1489 fn into_eip1186_response_zero_empty_account() {
1490 let acc = AccountProof {
1492 address: Address::random(),
1493 info: None,
1494 proof: vec![],
1495 storage_root: EMPTY_ROOT_HASH,
1496 storage_proofs: vec![],
1497 };
1498
1499 let rpc_default = acc.clone().into_eip1186_response(Vec::new());
1501 assert_eq!(rpc_default.code_hash, KECCAK_EMPTY);
1502 assert_eq!(rpc_default.storage_hash, EMPTY_ROOT_HASH);
1503
1504 let rpc_compat_off = acc.clone().into_eip1186_response_with(Vec::new(), false);
1506 assert_eq!(rpc_compat_off.code_hash, KECCAK_EMPTY);
1507 assert_eq!(rpc_compat_off.storage_hash, EMPTY_ROOT_HASH);
1508
1509 let rpc_compat_on = acc.into_eip1186_response_with(Vec::new(), true);
1511 assert_eq!(rpc_compat_on.code_hash, B256::ZERO);
1512 assert_eq!(rpc_compat_on.storage_hash, B256::ZERO);
1513
1514 let existing_acc = AccountProof {
1516 address: Address::random(),
1517 info: Some(Account {
1518 nonce: 42,
1519 balance: U256::from(100),
1520 bytecode_hash: Some(KECCAK_EMPTY),
1521 }),
1522 proof: vec![],
1523 storage_root: B256::random(),
1524 storage_proofs: vec![],
1525 };
1526 let rpc_existing = existing_acc.clone().into_eip1186_response_with(Vec::new(), true);
1527 assert_eq!(rpc_existing.code_hash, KECCAK_EMPTY);
1528 assert_eq!(rpc_existing.storage_hash, existing_acc.storage_root);
1529 }
1530
1531 #[test]
1532 fn test_multiproof_targets_chunking_length() {
1533 let mut targets = MultiProofTargets::default();
1534 targets.insert(B256::with_last_byte(1), B256Set::default());
1535 targets.insert(
1536 B256::with_last_byte(2),
1537 B256Set::from_iter([B256::with_last_byte(10), B256::with_last_byte(20)]),
1538 );
1539 targets.insert(
1540 B256::with_last_byte(3),
1541 B256Set::from_iter([
1542 B256::with_last_byte(30),
1543 B256::with_last_byte(31),
1544 B256::with_last_byte(32),
1545 ]),
1546 );
1547
1548 let chunking_length = targets.chunking_length();
1549 for size in 1..=targets.clone().chunks(1).count() {
1550 let chunk_count = targets.clone().chunks(size).count();
1551 let expected_count = chunking_length.div_ceil(size);
1552 assert_eq!(
1553 chunk_count, expected_count,
1554 "chunking_length: {}, size: {}",
1555 chunking_length, size
1556 );
1557 }
1558 }
1559
1560 #[test]
1561 fn test_nonempty_storage_trie_returns_nonempty_proof() {
1562 let slot = B256::with_last_byte(1);
1563 let nibbles = Nibbles::unpack(keccak256(slot));
1564 let value = U256::from(999);
1565 let leaf = alloy_trie::nodes::LeafNode::new(nibbles, encode_fixed_size(&value).to_vec());
1566 let mut encoded = vec![];
1567 alloy_rlp::Encodable::encode(&leaf, &mut encoded);
1568
1569 let mut subtree = ProofNodes::default();
1570 subtree.insert(nibbles, encoded.into());
1571
1572 let multiproof = StorageMultiProof {
1573 root: B256::with_last_byte(0xFF),
1574 subtree,
1575 branch_node_masks: BranchNodeMasksMap::default(),
1576 };
1577
1578 let proof = multiproof.storage_proof(slot).unwrap();
1579 assert!(!proof.proof.is_empty(), "non-empty trie must return non-empty proof");
1580 assert_eq!(proof.value, value);
1581 }
1582
1583 #[cfg(feature = "eip1186")]
1584 #[test]
1585 fn eip1186_response_normalizes_empty_trie_proof() {
1586 let slot = B256::with_last_byte(1);
1587 let sentinel = || vec![Bytes::from([EMPTY_STRING_CODE])];
1588
1589 let account = AccountProof {
1591 address: Address::ZERO,
1592 info: None,
1593 proof: sentinel(),
1594 storage_root: EMPTY_ROOT_HASH,
1595 storage_proofs: vec![StorageProof::new(slot).with_proof(sentinel())],
1596 };
1597
1598 let resp = account.into_eip1186_response(vec![alloy_serde::JsonStorageKey::from(slot)]);
1599
1600 assert!(
1601 resp.account_proof.is_empty(),
1602 "empty account trie must yield empty account_proof, got {:?}",
1603 resp.account_proof
1604 );
1605 assert_eq!(resp.storage_proof.len(), 1);
1606 assert!(
1607 resp.storage_proof[0].proof.is_empty(),
1608 "empty storage trie must yield empty storage proof, got {:?}",
1609 resp.storage_proof[0].proof
1610 );
1611 }
1612
1613 #[cfg(feature = "eip1186")]
1614 #[test]
1615 fn eip1186_response_keeps_nonempty_proof() {
1616 let multi = vec![Bytes::from([0x01, 0x02]), Bytes::from([0x03])];
1619 let single_non_sentinel = vec![Bytes::from([0xf8, 0x44])];
1620
1621 let account = AccountProof {
1622 address: Address::ZERO,
1623 info: None,
1624 proof: multi.clone(),
1625 storage_root: EMPTY_ROOT_HASH,
1626 storage_proofs: vec![
1627 StorageProof::new(B256::with_last_byte(1)).with_proof(single_non_sentinel.clone())
1628 ],
1629 };
1630
1631 let resp = account.into_eip1186_response(vec![alloy_serde::JsonStorageKey::from(
1632 B256::with_last_byte(1),
1633 )]);
1634
1635 assert_eq!(resp.account_proof, multi);
1636 assert_eq!(resp.storage_proof[0].proof, single_non_sentinel);
1637 }
1638}