Skip to main content

reth_trie_common/
proofs.rs

1//! Merkle trie proofs.
2
3use crate::{BranchNodeMasks, BranchNodeMasksMap, Nibbles, ProofTrieNodeV2, TrieAccount};
4use alloc::{borrow::Cow, collections::VecDeque, vec::Vec};
5use alloy_consensus::constants::KECCAK_EMPTY;
6use alloy_primitives::{
7    keccak256,
8    map::{hash_map, B256Map, B256Set},
9    Address, Bytes, B256, U256,
10};
11use alloy_rlp::{encode_fixed_size, Decodable, EMPTY_STRING_CODE};
12use alloy_trie::{
13    nodes::TrieNode,
14    proof::{verify_proof, DecodedProofNodes, ProofNodes, ProofVerificationError},
15    EMPTY_ROOT_HASH,
16};
17use derive_more::{Deref, DerefMut, IntoIterator};
18use itertools::Itertools;
19use reth_primitives_traits::Account;
20
21/// Proof targets map.
22#[derive(Deref, DerefMut, IntoIterator, Clone, PartialEq, Eq, Default, Debug)]
23pub struct MultiProofTargets(B256Map<B256Set>);
24
25impl FromIterator<(B256, B256Set)> for MultiProofTargets {
26    fn from_iter<T: IntoIterator<Item = (B256, B256Set)>>(iter: T) -> Self {
27        Self(B256Map::from_iter(iter))
28    }
29}
30
31impl MultiProofTargets {
32    /// Creates an empty `MultiProofTargets` with at least the specified capacity.
33    pub fn with_capacity(capacity: usize) -> Self {
34        Self(B256Map::with_capacity_and_hasher(capacity, Default::default()))
35    }
36
37    /// Create `MultiProofTargets` with a single account as a target.
38    pub fn account(hashed_address: B256) -> Self {
39        Self::accounts([hashed_address])
40    }
41
42    /// Create `MultiProofTargets` with a single account and slots as targets.
43    pub fn account_with_slots<I: IntoIterator<Item = B256>>(
44        hashed_address: B256,
45        slots_iter: I,
46    ) -> Self {
47        Self(B256Map::from_iter([(hashed_address, slots_iter.into_iter().collect())]))
48    }
49
50    /// Create `MultiProofTargets` only from accounts.
51    pub fn accounts<I: IntoIterator<Item = B256>>(iter: I) -> Self {
52        Self(iter.into_iter().map(|hashed_address| (hashed_address, Default::default())).collect())
53    }
54
55    /// Retains the targets representing the difference,
56    /// i.e., the values that are in `self` but not in `other`.
57    pub fn retain_difference(&mut self, other: &Self) {
58        self.0.retain(|hashed_address, hashed_slots| {
59            if let Some(other_hashed_slots) = other.get(hashed_address) {
60                hashed_slots.retain(|hashed_slot| !other_hashed_slots.contains(hashed_slot));
61                !hashed_slots.is_empty()
62            } else {
63                true
64            }
65        });
66    }
67
68    /// Extend multi proof targets with contents of other.
69    pub fn extend(&mut self, other: Self) {
70        self.extend_inner(Cow::Owned(other));
71    }
72
73    /// Extend multi proof targets with contents of other.
74    ///
75    /// Slightly less efficient than [`Self::extend`], but preferred to `extend(other.clone())`.
76    pub fn extend_ref(&mut self, other: &Self) {
77        self.extend_inner(Cow::Borrowed(other));
78    }
79
80    fn extend_inner(&mut self, other: Cow<'_, Self>) {
81        for (hashed_address, hashed_slots) in other.iter() {
82            match self.entry(*hashed_address) {
83                hash_map::Entry::Vacant(entry) => {
84                    entry.insert(hashed_slots.clone());
85                }
86                hash_map::Entry::Occupied(mut entry) => {
87                    entry.get_mut().extend(hashed_slots);
88                }
89            }
90        }
91    }
92
93    /// Returns an iterator that yields chunks of the specified size.
94    ///
95    /// See [`ChunkedMultiProofTargets`] for more information.
96    pub fn chunks(self, size: usize) -> ChunkedMultiProofTargets {
97        ChunkedMultiProofTargets::new(self, size)
98    }
99
100    /// Returns the number of items that will be considered during chunking in `[Self::chunks]`.
101    pub fn chunking_length(&self) -> usize {
102        self.values().map(|slots| 1 + slots.len().saturating_sub(1)).sum::<usize>()
103    }
104}
105
106/// An iterator that yields chunks of the proof targets of at most `size` account and storage
107/// targets.
108///
109/// For example, for the following proof targets:
110/// ```text
111/// - 0x1: [0x10, 0x20, 0x30]
112/// - 0x2: [0x40]
113/// - 0x3: []
114/// ```
115///
116/// and `size = 2`, the iterator will yield the following chunks:
117/// ```text
118/// - { 0x1: [0x10, 0x20] }
119/// - { 0x1: [0x30], 0x2: [0x40] }
120/// - { 0x3: [] }
121/// ```
122///
123/// It follows two rules:
124/// - If account has associated storage slots, each storage slot is counted towards the chunk size.
125/// - If account has no associated storage slots, the account is counted towards the chunk size.
126#[derive(Debug)]
127pub struct ChunkedMultiProofTargets {
128    flattened_targets: alloc::vec::IntoIter<(B256, Option<B256>)>,
129    size: usize,
130}
131
132impl ChunkedMultiProofTargets {
133    fn new(targets: MultiProofTargets, size: usize) -> Self {
134        let flattened_targets = targets
135            .into_iter()
136            .flat_map(|(address, slots)| {
137                if slots.is_empty() {
138                    // If the account has no storage slots, we still need to yield the account
139                    // address with empty storage slots. `None` here means that
140                    // there's no storage slot to fetch.
141                    itertools::Either::Left(core::iter::once((address, None)))
142                } else {
143                    itertools::Either::Right(
144                        slots.into_iter().map(move |slot| (address, Some(slot))),
145                    )
146                }
147            })
148            .sorted_unstable();
149        Self { flattened_targets, size }
150    }
151}
152
153impl Iterator for ChunkedMultiProofTargets {
154    type Item = MultiProofTargets;
155
156    fn next(&mut self) -> Option<Self::Item> {
157        let chunk = self.flattened_targets.by_ref().take(self.size).fold(
158            MultiProofTargets::default(),
159            |mut acc, (address, slot)| {
160                let entry = acc.entry(address).or_default();
161                if let Some(slot) = slot {
162                    entry.insert(slot);
163                }
164                acc
165            },
166        );
167
168        if chunk.is_empty() {
169            None
170        } else {
171            Some(chunk)
172        }
173    }
174}
175
176/// The state multiproof of target accounts and multiproofs of their storage tries.
177/// Multiproof is effectively a state subtrie that only contains the nodes
178/// in the paths of target accounts.
179#[derive(Clone, Default, Debug, PartialEq, Eq)]
180pub struct MultiProof {
181    /// State trie multiproof for requested accounts.
182    pub account_subtree: ProofNodes,
183    /// Consolidated branch node masks (`hash_mask`, `tree_mask`) for each path in the account
184    /// proof.
185    pub branch_node_masks: BranchNodeMasksMap,
186    /// Storage trie multiproofs.
187    pub storages: B256Map<StorageMultiProof>,
188}
189
190impl MultiProof {
191    /// Returns true if the multiproof is empty.
192    pub fn is_empty(&self) -> bool {
193        self.account_subtree.is_empty() &&
194            self.branch_node_masks.is_empty() &&
195            self.storages.is_empty()
196    }
197
198    /// Return the account proof nodes for the given account path.
199    pub fn account_proof_nodes(&self, path: &Nibbles) -> Vec<(Nibbles, Bytes)> {
200        self.account_subtree.matching_nodes_sorted(path)
201    }
202
203    /// Return the storage proof nodes for the given storage slots of the account path.
204    pub fn storage_proof_nodes(
205        &self,
206        hashed_address: B256,
207        slots: impl IntoIterator<Item = B256>,
208    ) -> Vec<(B256, Vec<(Nibbles, Bytes)>)> {
209        self.storages
210            .get(&hashed_address)
211            .map(|storage_mp| {
212                slots
213                    .into_iter()
214                    .map(|slot| {
215                        let nibbles = Nibbles::unpack(slot);
216                        (slot, storage_mp.subtree.matching_nodes_sorted(&nibbles))
217                    })
218                    .collect()
219            })
220            .unwrap_or_default()
221    }
222
223    /// Construct the account proof from the multiproof.
224    pub fn account_proof(
225        &self,
226        address: Address,
227        slots: &[B256],
228    ) -> Result<AccountProof, alloy_rlp::Error> {
229        let hashed_address = keccak256(address);
230        let nibbles = Nibbles::unpack(hashed_address);
231
232        // Retrieve the account proof.
233        let proof = self
234            .account_proof_nodes(&nibbles)
235            .into_iter()
236            .map(|(_, node)| node)
237            .collect::<Vec<_>>();
238
239        // Inspect the last node in the proof. If it's a leaf node with matching suffix,
240        // then the node contains the encoded trie account.
241        let info = 'info: {
242            if let Some(last) = proof.last() &&
243                let TrieNode::Leaf(leaf) = TrieNode::decode(&mut &last[..])? &&
244                nibbles.ends_with(&leaf.key)
245            {
246                let account = TrieAccount::decode(&mut &leaf.value[..])?;
247                break 'info Some(Account {
248                    balance: account.balance,
249                    nonce: account.nonce,
250                    bytecode_hash: (account.code_hash != KECCAK_EMPTY).then_some(account.code_hash),
251                })
252            }
253            None
254        };
255
256        // Retrieve proofs for requested storage slots.
257        let storage_multiproof = self.storages.get(&hashed_address);
258        let storage_root = storage_multiproof.map(|m| m.root).unwrap_or(EMPTY_ROOT_HASH);
259        let mut storage_proofs = Vec::with_capacity(slots.len());
260        for slot in slots {
261            let proof = if let Some(multiproof) = &storage_multiproof {
262                multiproof.storage_proof(*slot)?
263            } else {
264                StorageProof::new(*slot)
265            };
266            storage_proofs.push(proof);
267        }
268        Ok(AccountProof { address, info, proof, storage_root, storage_proofs })
269    }
270
271    /// Extends this multiproof with another one, merging both account and storage
272    /// proofs.
273    pub fn extend(&mut self, other: Self) {
274        self.account_subtree.extend_from(other.account_subtree);
275        self.branch_node_masks.extend(other.branch_node_masks);
276
277        let reserve = if self.storages.is_empty() {
278            other.storages.len()
279        } else {
280            other.storages.len().div_ceil(2)
281        };
282        self.storages.reserve(reserve);
283        for (hashed_address, storage) in other.storages {
284            match self.storages.entry(hashed_address) {
285                hash_map::Entry::Occupied(mut entry) => {
286                    debug_assert_eq!(entry.get().root, storage.root);
287                    let entry = entry.get_mut();
288                    entry.subtree.extend_from(storage.subtree);
289                    entry.branch_node_masks.extend(storage.branch_node_masks);
290                }
291                hash_map::Entry::Vacant(entry) => {
292                    entry.insert(storage);
293                }
294            }
295        }
296    }
297
298    /// Create a [`MultiProof`] from a [`StorageMultiProof`].
299    pub fn from_storage_proof(hashed_address: B256, storage_proof: StorageMultiProof) -> Self {
300        Self {
301            storages: B256Map::from_iter([(hashed_address, storage_proof)]),
302            ..Default::default()
303        }
304    }
305}
306
307/// This is a type of [`MultiProof`] that uses decoded proofs, meaning these proofs are stored as a
308/// collection of [`TrieNode`]s instead of RLP-encoded bytes.
309#[derive(Clone, Default, Debug, PartialEq, Eq)]
310pub struct DecodedMultiProof {
311    /// State trie multiproof for requested accounts.
312    pub account_subtree: DecodedProofNodes,
313    /// Consolidated branch node masks (`hash_mask`, `tree_mask`) for each path in the account
314    /// proof.
315    pub branch_node_masks: BranchNodeMasksMap,
316    /// Storage trie multiproofs.
317    pub storages: B256Map<DecodedStorageMultiProof>,
318}
319
320impl DecodedMultiProof {
321    /// Returns true if the multiproof is empty.
322    pub fn is_empty(&self) -> bool {
323        self.account_subtree.is_empty() &&
324            self.branch_node_masks.is_empty() &&
325            self.storages.is_empty()
326    }
327
328    /// Return the account proof nodes for the given account path.
329    pub fn account_proof_nodes(&self, path: &Nibbles) -> Vec<(Nibbles, TrieNode)> {
330        self.account_subtree.matching_nodes_sorted(path)
331    }
332
333    /// Return the storage proof nodes for the given storage slots of the account path.
334    pub fn storage_proof_nodes(
335        &self,
336        hashed_address: B256,
337        slots: impl IntoIterator<Item = B256>,
338    ) -> Vec<(B256, Vec<(Nibbles, TrieNode)>)> {
339        self.storages
340            .get(&hashed_address)
341            .map(|storage_mp| {
342                slots
343                    .into_iter()
344                    .map(|slot| {
345                        let nibbles = Nibbles::unpack(slot);
346                        (slot, storage_mp.subtree.matching_nodes_sorted(&nibbles))
347                    })
348                    .collect()
349            })
350            .unwrap_or_default()
351    }
352
353    /// Construct the account proof from the multiproof.
354    pub fn account_proof(
355        &self,
356        address: Address,
357        slots: &[B256],
358    ) -> Result<DecodedAccountProof, alloy_rlp::Error> {
359        let hashed_address = keccak256(address);
360        let nibbles = Nibbles::unpack(hashed_address);
361
362        // Retrieve the account proof.
363        let proof = self
364            .account_proof_nodes(&nibbles)
365            .into_iter()
366            .map(|(_, node)| node)
367            .collect::<Vec<_>>();
368
369        // Inspect the last node in the proof. If it's a leaf node with matching suffix,
370        // then the node contains the encoded trie account.
371        let info = 'info: {
372            if let Some(TrieNode::Leaf(leaf)) = proof.last() &&
373                nibbles.ends_with(&leaf.key)
374            {
375                let account = TrieAccount::decode(&mut &leaf.value[..])?;
376                break 'info Some(Account {
377                    balance: account.balance,
378                    nonce: account.nonce,
379                    bytecode_hash: (account.code_hash != KECCAK_EMPTY).then_some(account.code_hash),
380                })
381            }
382            None
383        };
384
385        // Retrieve proofs for requested storage slots.
386        let storage_multiproof = self.storages.get(&hashed_address);
387        let storage_root = storage_multiproof.map(|m| m.root).unwrap_or(EMPTY_ROOT_HASH);
388        let mut storage_proofs = Vec::with_capacity(slots.len());
389        for slot in slots {
390            let proof = if let Some(multiproof) = &storage_multiproof {
391                multiproof.storage_proof(*slot)?
392            } else {
393                DecodedStorageProof::new(*slot)
394            };
395            storage_proofs.push(proof);
396        }
397        Ok(DecodedAccountProof { address, info, proof, storage_root, storage_proofs })
398    }
399
400    /// Extends this multiproof with another one, merging both account and storage
401    /// proofs.
402    pub fn extend(&mut self, other: Self) {
403        self.account_subtree.extend_from(other.account_subtree);
404        self.branch_node_masks.extend(other.branch_node_masks);
405
406        let reserve = if self.storages.is_empty() {
407            other.storages.len()
408        } else {
409            other.storages.len().div_ceil(2)
410        };
411        self.storages.reserve(reserve);
412        for (hashed_address, storage) in other.storages {
413            match self.storages.entry(hashed_address) {
414                hash_map::Entry::Occupied(mut entry) => {
415                    debug_assert_eq!(entry.get().root, storage.root);
416                    let entry = entry.get_mut();
417                    entry.subtree.extend_from(storage.subtree);
418                    entry.branch_node_masks.extend(storage.branch_node_masks);
419                }
420                hash_map::Entry::Vacant(entry) => {
421                    entry.insert(storage);
422                }
423            }
424        }
425    }
426
427    /// Create a [`DecodedMultiProof`] from a [`DecodedStorageMultiProof`].
428    pub fn from_storage_proof(
429        hashed_address: B256,
430        storage_proof: DecodedStorageMultiProof,
431    ) -> Self {
432        Self {
433            storages: B256Map::from_iter([(hashed_address, storage_proof)]),
434            ..Default::default()
435        }
436    }
437}
438
439impl TryFrom<MultiProof> for DecodedMultiProof {
440    type Error = alloy_rlp::Error;
441
442    fn try_from(multi_proof: MultiProof) -> Result<Self, Self::Error> {
443        let account_subtree = DecodedProofNodes::try_from(multi_proof.account_subtree)?;
444        let storages = multi_proof
445            .storages
446            .into_iter()
447            .map(|(address, storage)| Ok((address, storage.try_into()?)))
448            .collect::<Result<B256Map<_>, alloy_rlp::Error>>()?;
449        Ok(Self { account_subtree, branch_node_masks: multi_proof.branch_node_masks, storages })
450    }
451}
452
453/// V2 decoded multiproof which contains the results of both account and storage V2 proof
454/// calculations.
455#[derive(Clone, Debug, PartialEq, Eq, Default)]
456pub struct DecodedMultiProofV2 {
457    /// Account trie proof nodes
458    pub account_proofs: Vec<ProofTrieNodeV2>,
459    /// Storage trie proof nodes indexed by account
460    pub storage_proofs: B256Map<Vec<ProofTrieNodeV2>>,
461}
462
463impl DecodedMultiProofV2 {
464    /// Returns true if there are no proofs
465    pub fn is_empty(&self) -> bool {
466        self.account_proofs.is_empty() && self.storage_proofs.is_empty()
467    }
468
469    /// Builds a `DecodedMultiProofV2` from a flat witness map (hash → RLP-encoded trie node).
470    ///
471    /// This performs a BFS traversal starting from `state_root`, decoding each witness entry
472    /// as a trie node and organizing them into account and storage proof vectors. This is the
473    /// inverse of witness generation — it reconstructs the structured multiproof from the flat
474    /// format used in `ExecutionWitness`.
475    pub fn from_witness(
476        state_root: B256,
477        witness: &B256Map<impl AsRef<[u8]>>,
478    ) -> Result<Self, alloy_rlp::Error> {
479        let mut account_nodes: Vec<(Nibbles, TrieNode, Option<BranchNodeMasks>)> = Vec::new();
480        let mut storage_nodes: B256Map<Vec<(Nibbles, TrieNode, Option<BranchNodeMasks>)>> =
481            B256Map::default();
482
483        let mut queue: VecDeque<(B256, Nibbles, Option<B256>)> =
484            VecDeque::from([(state_root, Nibbles::default(), None)]);
485
486        while let Some((hash, path, maybe_account)) = queue.pop_front() {
487            let Some(rlp_bytes) = witness.get(&hash) else { continue };
488            let trie_node = TrieNode::decode(&mut rlp_bytes.as_ref())?;
489
490            match &trie_node {
491                TrieNode::Branch(branch) => {
492                    for (idx, maybe_child) in branch.as_ref().children() {
493                        if let Some(child_hash) =
494                            maybe_child.and_then(alloy_trie::nodes::RlpNode::as_hash)
495                        {
496                            let mut child_path = path;
497                            child_path.push_unchecked(idx);
498                            queue.push_back((child_hash, child_path, maybe_account));
499                        }
500                    }
501                }
502                TrieNode::Extension(ext) => {
503                    if let Some(child_hash) = ext.child.as_hash() {
504                        let mut child_path = path;
505                        child_path.extend(&ext.key);
506                        queue.push_back((child_hash, child_path, maybe_account));
507                    }
508                }
509                TrieNode::Leaf(leaf) => {
510                    if maybe_account.is_none() {
511                        let mut full_path = path;
512                        full_path.extend(&leaf.key);
513                        let hashed_address = B256::from_slice(&full_path.pack());
514                        let account = TrieAccount::decode(&mut &leaf.value[..])?;
515                        if account.storage_root != EMPTY_ROOT_HASH {
516                            queue.push_back((
517                                account.storage_root,
518                                Nibbles::default(),
519                                Some(hashed_address),
520                            ));
521                        }
522                    }
523                }
524                TrieNode::EmptyRoot => {}
525            }
526
527            if let Some(account) = maybe_account {
528                storage_nodes.entry(account).or_default().push((path, trie_node, None));
529            } else {
530                account_nodes.push((path, trie_node, None));
531            }
532        }
533
534        account_nodes.sort_by(|(a, _, _), (b, _, _)| crate::depth_first_cmp(a, b));
535        let account_proofs = ProofTrieNodeV2::from_sorted_trie_nodes(account_nodes);
536
537        let mut storage_proofs = B256Map::default();
538        for (account, mut nodes) in storage_nodes {
539            nodes.sort_by(|(a, _, _), (b, _, _)| crate::depth_first_cmp(a, b));
540            storage_proofs.insert(account, ProofTrieNodeV2::from_sorted_trie_nodes(nodes));
541        }
542
543        Ok(Self { account_proofs, storage_proofs })
544    }
545
546    /// Appends the given multiproof's data to this one.
547    ///
548    /// This implementation does not deduplicate redundant proofs.
549    pub fn extend(&mut self, other: Self) {
550        self.account_proofs.extend(other.account_proofs);
551        for (hashed_address, other_storage_proofs) in other.storage_proofs {
552            match self.storage_proofs.entry(hashed_address) {
553                hash_map::Entry::Vacant(entry) => {
554                    entry.insert(other_storage_proofs);
555                }
556                hash_map::Entry::Occupied(mut entry) => {
557                    entry.get_mut().extend(other_storage_proofs);
558                }
559            }
560        }
561    }
562}
563
564impl From<DecodedMultiProof> for DecodedMultiProofV2 {
565    fn from(proof: DecodedMultiProof) -> Self {
566        let account_proofs =
567            decoded_proof_nodes_to_v2(proof.account_subtree, &proof.branch_node_masks);
568        let storage_proofs = proof
569            .storages
570            .into_iter()
571            .map(|(address, storage)| {
572                (address, decoded_proof_nodes_to_v2(storage.subtree, &storage.branch_node_masks))
573            })
574            .collect();
575        Self { account_proofs, storage_proofs }
576    }
577}
578
579/// Converts a [`DecodedProofNodes`] (path → [`TrieNode`] map) into a `Vec<ProofTrieNodeV2>`,
580/// merging extension nodes into their child branch nodes.
581fn decoded_proof_nodes_to_v2(
582    nodes: DecodedProofNodes,
583    masks: &BranchNodeMasksMap,
584) -> Vec<ProofTrieNodeV2> {
585    let mut sorted: Vec<_> = nodes.into_inner().into_iter().collect();
586    sorted.sort_unstable_by(|a, b| crate::depth_first_cmp(&a.0, &b.0));
587    ProofTrieNodeV2::from_sorted_trie_nodes(
588        sorted.into_iter().map(|(path, node)| (path, node, masks.get(&path).copied())),
589    )
590}
591
592/// The merkle multiproof of storage trie.
593#[derive(Clone, Debug, PartialEq, Eq)]
594pub struct StorageMultiProof {
595    /// Storage trie root.
596    pub root: B256,
597    /// Storage multiproof for requested slots.
598    pub subtree: ProofNodes,
599    /// Consolidated branch node masks (`hash_mask`, `tree_mask`) for each path in the storage
600    /// proof.
601    pub branch_node_masks: BranchNodeMasksMap,
602}
603
604impl StorageMultiProof {
605    /// Create new storage multiproof for empty trie.
606    pub fn empty() -> Self {
607        Self {
608            root: EMPTY_ROOT_HASH,
609            subtree: ProofNodes::from_iter([(
610                Nibbles::default(),
611                Bytes::from([EMPTY_STRING_CODE]),
612            )]),
613            branch_node_masks: BranchNodeMasksMap::default(),
614        }
615    }
616
617    /// Return storage proofs for the target storage slot (unhashed).
618    pub fn storage_proof(&self, slot: B256) -> Result<StorageProof, alloy_rlp::Error> {
619        let nibbles = Nibbles::unpack(keccak256(slot));
620
621        // Retrieve the storage proof.
622        let proof = self
623            .subtree
624            .matching_nodes_iter(&nibbles)
625            .sorted_by(|a, b| a.0.cmp(b.0))
626            .map(|(_, node)| node.clone())
627            .collect::<Vec<_>>();
628
629        // Inspect the last node in the proof. If it's a leaf node with matching suffix,
630        // then the node contains the encoded slot value.
631        let value = 'value: {
632            if let Some(last) = proof.last() &&
633                let TrieNode::Leaf(leaf) = TrieNode::decode(&mut &last[..])? &&
634                nibbles.ends_with(&leaf.key)
635            {
636                break 'value U256::decode(&mut &leaf.value[..])?
637            }
638            U256::ZERO
639        };
640
641        Ok(StorageProof { key: slot, nibbles, value, proof })
642    }
643}
644
645/// The decoded merkle multiproof for a storage trie.
646#[derive(Clone, Debug, PartialEq, Eq)]
647pub struct DecodedStorageMultiProof {
648    /// Storage trie root.
649    pub root: B256,
650    /// Storage multiproof for requested slots.
651    pub subtree: DecodedProofNodes,
652    /// Consolidated branch node masks (`hash_mask`, `tree_mask`) for each path in the storage
653    /// proof.
654    pub branch_node_masks: BranchNodeMasksMap,
655}
656
657impl DecodedStorageMultiProof {
658    /// Create new storage multiproof for empty trie.
659    pub fn empty() -> Self {
660        Self {
661            root: EMPTY_ROOT_HASH,
662            subtree: DecodedProofNodes::from_iter([(Nibbles::default(), TrieNode::EmptyRoot)]),
663            branch_node_masks: BranchNodeMasksMap::default(),
664        }
665    }
666
667    /// Return storage proofs for the target storage slot (unhashed).
668    pub fn storage_proof(&self, slot: B256) -> Result<DecodedStorageProof, alloy_rlp::Error> {
669        let nibbles = Nibbles::unpack(keccak256(slot));
670
671        // Retrieve the storage proof.
672        let proof = self
673            .subtree
674            .matching_nodes_iter(&nibbles)
675            .sorted_by(|a, b| a.0.cmp(b.0))
676            .map(|(_, node)| node.clone())
677            .collect::<Vec<_>>();
678
679        // Inspect the last node in the proof. If it's a leaf node with matching suffix,
680        // then the node contains the encoded slot value.
681        let value = 'value: {
682            if let Some(TrieNode::Leaf(leaf)) = proof.last() &&
683                nibbles.ends_with(&leaf.key)
684            {
685                break 'value U256::decode(&mut &leaf.value[..])?
686            }
687            U256::ZERO
688        };
689
690        Ok(DecodedStorageProof { key: slot, nibbles, value, proof })
691    }
692}
693
694impl TryFrom<StorageMultiProof> for DecodedStorageMultiProof {
695    type Error = alloy_rlp::Error;
696
697    fn try_from(multi_proof: StorageMultiProof) -> Result<Self, Self::Error> {
698        let subtree = DecodedProofNodes::try_from(multi_proof.subtree)?;
699        Ok(Self {
700            root: multi_proof.root,
701            subtree,
702            branch_node_masks: multi_proof.branch_node_masks,
703        })
704    }
705}
706
707/// The merkle proof with the relevant account info.
708#[derive(Clone, PartialEq, Eq, Debug)]
709#[cfg_attr(any(test, feature = "serde"), derive(serde::Serialize, serde::Deserialize))]
710#[cfg_attr(any(test, feature = "serde"), serde(rename_all = "camelCase"))]
711pub struct AccountProof {
712    /// The address associated with the account.
713    pub address: Address,
714    /// Account info, if any.
715    pub info: Option<Account>,
716    /// Array of rlp-serialized merkle trie nodes which starting from the root node and
717    /// following the path of the hashed address as key.
718    pub proof: Vec<Bytes>,
719    /// The storage trie root.
720    pub storage_root: B256,
721    /// Array of storage proofs as requested.
722    pub storage_proofs: Vec<StorageProof>,
723}
724
725/// Normalize an empty-trie proof for the EIP-1186 (`eth_getProof`) response.
726///
727/// An empty trie is internally represented by a single empty-root sentinel node (`0x80`, the
728/// RLP empty string whose hash is `EMPTY_ROOT_HASH`). EIP-1186 defines the proof field as the
729/// array of trie nodes along the key path; an empty trie has none, and geth returns `[]`. This
730/// strips that lone sentinel so the response matches geth and the spec. It is applied only at
731/// the response boundary, leaving the underlying proof construction unchanged.
732#[cfg(feature = "eip1186")]
733fn normalize_eip1186_empty_trie_proof(proof: Vec<Bytes>) -> Vec<Bytes> {
734    if proof.len() == 1 && proof[0].as_ref() == [EMPTY_STRING_CODE] {
735        Vec::new()
736    } else {
737        proof
738    }
739}
740
741#[cfg(feature = "eip1186")]
742impl AccountProof {
743    /// Convert into an EIP-1186 account proof response.
744    ///
745    /// For non-existent accounts, this returns `KECCAK_EMPTY` for `codeHash` and
746    /// `EMPTY_ROOT_HASH` for `storageHash`, matching reth's default behavior.
747    ///
748    /// Use [`Self::into_eip1186_response_with`] to customize the behavior for
749    /// non-existent accounts (e.g. returning `B256::ZERO` for geth compatibility).
750    pub fn into_eip1186_response(
751        self,
752        slots: Vec<alloy_serde::JsonStorageKey>,
753    ) -> alloy_rpc_types_eth::EIP1186AccountProofResponse {
754        self.into_eip1186_response_with(slots, false)
755    }
756
757    /// Convert into an EIP-1186 account proof response, with optional geth-compatible
758    /// zero hashes for non-existent accounts.
759    ///
760    /// When `zero_empty_account` is `true`, non-existent accounts return `B256::ZERO`
761    /// for both `codeHash` and `storageHash`, matching geth's behavior since v1.13.4
762    /// ([go-ethereum#28357](https://github.com/ethereum/go-ethereum/pull/28357)).
763    ///
764    /// When `false`, returns `KECCAK_EMPTY` / `EMPTY_ROOT_HASH` (reth default).
765    ///
766    /// See: <https://github.com/ethereum/go-ethereum/issues/28441>
767    pub fn into_eip1186_response_with(
768        self,
769        slots: Vec<alloy_serde::JsonStorageKey>,
770        zero_empty_account: bool,
771    ) -> alloy_rpc_types_eth::EIP1186AccountProofResponse {
772        let is_non_existent = self.info.is_none();
773        let info = self.info.unwrap_or_default();
774        let (code_hash, storage_hash) = if is_non_existent && zero_empty_account {
775            (B256::ZERO, B256::ZERO)
776        } else {
777            (info.get_bytecode_hash(), self.storage_root)
778        };
779        alloy_rpc_types_eth::EIP1186AccountProofResponse {
780            address: self.address,
781            balance: info.balance,
782            code_hash,
783            nonce: info.nonce,
784            storage_hash,
785            account_proof: normalize_eip1186_empty_trie_proof(self.proof),
786            storage_proof: self
787                .storage_proofs
788                .into_iter()
789                .filter_map(|proof| {
790                    let input_slot = slots.iter().find(|s| s.as_b256() == proof.key)?;
791                    Some(proof.into_eip1186_proof(*input_slot))
792                })
793                .collect(),
794        }
795    }
796
797    /// Converts an
798    /// [`EIP1186AccountProofResponse`](alloy_rpc_types_eth::EIP1186AccountProofResponse) to an
799    /// [`AccountProof`].
800    ///
801    /// This is the inverse of [`Self::into_eip1186_response`]
802    pub fn from_eip1186_proof(proof: alloy_rpc_types_eth::EIP1186AccountProofResponse) -> Self {
803        let alloy_rpc_types_eth::EIP1186AccountProofResponse {
804            nonce,
805            address,
806            balance,
807            code_hash,
808            storage_hash,
809            account_proof,
810            storage_proof,
811            ..
812        } = proof;
813        let storage_proofs = storage_proof.into_iter().map(Into::into).collect();
814
815        let (storage_root, info) = if nonce == 0 &&
816            balance.is_zero() &&
817            (storage_hash.is_zero() || storage_hash == EMPTY_ROOT_HASH) &&
818            (code_hash == KECCAK_EMPTY || code_hash.is_zero())
819        {
820            // Account does not exist in state. Return `None` here to prevent proof
821            // verification.
822            //
823            // Note: geth (since v1.13.4, go-ethereum#28357) returns `B256::ZERO` for
824            // both `codeHash` and `storageHash` in exclusion proofs, while reth
825            // returns `KECCAK_EMPTY` / `EMPTY_ROOT_HASH`. We accept both formats here
826            // so that proofs obtained from any client can be deserialized correctly.
827            // See: https://github.com/ethereum/go-ethereum/issues/28441
828            (EMPTY_ROOT_HASH, None)
829        } else {
830            (storage_hash, Some(Account { nonce, balance, bytecode_hash: code_hash.into() }))
831        };
832
833        Self { address, info, proof: account_proof, storage_root, storage_proofs }
834    }
835}
836
837#[cfg(feature = "eip1186")]
838impl From<alloy_rpc_types_eth::EIP1186AccountProofResponse> for AccountProof {
839    fn from(proof: alloy_rpc_types_eth::EIP1186AccountProofResponse) -> Self {
840        Self::from_eip1186_proof(proof)
841    }
842}
843
844impl Default for AccountProof {
845    fn default() -> Self {
846        Self::new(Address::default())
847    }
848}
849
850impl AccountProof {
851    /// Create new account proof entity.
852    pub const fn new(address: Address) -> Self {
853        Self {
854            address,
855            info: None,
856            proof: Vec::new(),
857            storage_root: EMPTY_ROOT_HASH,
858            storage_proofs: Vec::new(),
859        }
860    }
861
862    /// Verify the storage proofs and account proof against the provided state root.
863    pub fn verify(&self, root: B256) -> Result<(), ProofVerificationError> {
864        // Verify storage proofs.
865        for storage_proof in &self.storage_proofs {
866            storage_proof.verify(self.storage_root)?;
867        }
868
869        // Verify the account proof.
870        let expected = if self.info.is_none() && self.storage_root == EMPTY_ROOT_HASH {
871            None
872        } else {
873            Some(alloy_rlp::encode(
874                self.info.unwrap_or_default().into_trie_account(self.storage_root),
875            ))
876        };
877        let nibbles = Nibbles::unpack(keccak256(self.address));
878        verify_proof(root, nibbles, expected, &self.proof)
879    }
880}
881
882/// The merkle proof with the relevant account info.
883#[derive(Clone, PartialEq, Eq, Debug)]
884pub struct DecodedAccountProof {
885    /// The address associated with the account.
886    pub address: Address,
887    /// Account info.
888    pub info: Option<Account>,
889    /// Array of merkle trie nodes which starting from the root node and following the path of the
890    /// hashed address as key.
891    pub proof: Vec<TrieNode>,
892    /// The storage trie root.
893    pub storage_root: B256,
894    /// Array of storage proofs as requested.
895    pub storage_proofs: Vec<DecodedStorageProof>,
896}
897
898impl Default for DecodedAccountProof {
899    fn default() -> Self {
900        Self::new(Address::default())
901    }
902}
903
904impl DecodedAccountProof {
905    /// Create new account proof entity.
906    pub const fn new(address: Address) -> Self {
907        Self {
908            address,
909            info: None,
910            proof: Vec::new(),
911            storage_root: EMPTY_ROOT_HASH,
912            storage_proofs: Vec::new(),
913        }
914    }
915}
916
917/// The merkle proof of the storage entry.
918#[derive(Clone, PartialEq, Eq, Default, Debug)]
919#[cfg_attr(any(test, feature = "serde"), derive(serde::Serialize, serde::Deserialize))]
920pub struct StorageProof {
921    /// The raw storage key.
922    pub key: B256,
923    /// The hashed storage key nibbles.
924    pub nibbles: Nibbles,
925    /// The storage value.
926    pub value: U256,
927    /// Array of rlp-serialized merkle trie nodes which starting from the storage root node and
928    /// following the path of the hashed storage slot as key.
929    pub proof: Vec<Bytes>,
930}
931
932impl StorageProof {
933    /// Create new storage proof from the storage slot.
934    pub fn new(key: B256) -> Self {
935        let nibbles = Nibbles::unpack(keccak256(key));
936        Self { key, nibbles, ..Default::default() }
937    }
938
939    /// Create new storage proof from the storage slot and its pre-hashed image.
940    pub fn new_with_hashed(key: B256, hashed_key: B256) -> Self {
941        Self { key, nibbles: Nibbles::unpack(hashed_key), ..Default::default() }
942    }
943
944    /// Create new storage proof from the storage slot and its pre-hashed image.
945    pub fn new_with_nibbles(key: B256, nibbles: Nibbles) -> Self {
946        Self { key, nibbles, ..Default::default() }
947    }
948
949    /// Set proof nodes on storage proof.
950    pub fn with_proof(mut self, proof: Vec<Bytes>) -> Self {
951        self.proof = proof;
952        self
953    }
954
955    /// Verify the proof against the provided storage root.
956    pub fn verify(&self, root: B256) -> Result<(), ProofVerificationError> {
957        let expected =
958            if self.value.is_zero() { None } else { Some(encode_fixed_size(&self.value).to_vec()) };
959        verify_proof(root, self.nibbles, expected, &self.proof)
960    }
961}
962
963#[cfg(feature = "eip1186")]
964impl StorageProof {
965    /// Convert into an EIP-1186 storage proof
966    pub fn into_eip1186_proof(
967        self,
968        slot: alloy_serde::JsonStorageKey,
969    ) -> alloy_rpc_types_eth::EIP1186StorageProof {
970        alloy_rpc_types_eth::EIP1186StorageProof {
971            key: slot,
972            value: self.value,
973            proof: normalize_eip1186_empty_trie_proof(self.proof),
974        }
975    }
976
977    /// Convert from an
978    /// [`EIP1186StorageProof`](alloy_rpc_types_eth::EIP1186StorageProof)
979    ///
980    /// This is the inverse of [`Self::into_eip1186_proof`].
981    pub fn from_eip1186_proof(storage_proof: alloy_rpc_types_eth::EIP1186StorageProof) -> Self {
982        Self {
983            value: storage_proof.value,
984            proof: storage_proof.proof,
985            ..Self::new(storage_proof.key.as_b256())
986        }
987    }
988}
989
990#[cfg(feature = "eip1186")]
991impl From<alloy_rpc_types_eth::EIP1186StorageProof> for StorageProof {
992    fn from(proof: alloy_rpc_types_eth::EIP1186StorageProof) -> Self {
993        Self::from_eip1186_proof(proof)
994    }
995}
996
997/// The merkle proof of the storage entry, using decoded proofs.
998#[derive(Clone, PartialEq, Eq, Default, Debug)]
999pub struct DecodedStorageProof {
1000    /// The raw storage key.
1001    pub key: B256,
1002    /// The hashed storage key nibbles.
1003    pub nibbles: Nibbles,
1004    /// The storage value.
1005    pub value: U256,
1006    /// Array of merkle trie nodes which starting from the storage root node and following the path
1007    /// of the hashed storage slot as key.
1008    pub proof: Vec<TrieNode>,
1009}
1010
1011impl DecodedStorageProof {
1012    /// Create new storage proof from the storage slot.
1013    pub fn new(key: B256) -> Self {
1014        let nibbles = Nibbles::unpack(keccak256(key));
1015        Self { key, nibbles, ..Default::default() }
1016    }
1017
1018    /// Create new storage proof from the storage slot and its pre-hashed image.
1019    pub fn new_with_hashed(key: B256, hashed_key: B256) -> Self {
1020        Self { key, nibbles: Nibbles::unpack(hashed_key), ..Default::default() }
1021    }
1022
1023    /// Create new storage proof from the storage slot and its pre-hashed image.
1024    pub fn new_with_nibbles(key: B256, nibbles: Nibbles) -> Self {
1025        Self { key, nibbles, ..Default::default() }
1026    }
1027
1028    /// Set proof nodes on storage proof.
1029    pub fn with_proof(mut self, proof: Vec<TrieNode>) -> Self {
1030        self.proof = proof;
1031        self
1032    }
1033}
1034
1035/// Implementation of hasher using our keccak256 hashing function
1036/// for compatibility with `triehash` crate.
1037#[cfg(any(test, feature = "test-utils"))]
1038pub mod triehash {
1039    use alloy_primitives::{keccak256, B256};
1040    use alloy_rlp::RlpEncodable;
1041    use hash_db::Hasher;
1042    use plain_hasher::PlainHasher;
1043
1044    /// A [Hasher] that calculates a keccak256 hash of the given data.
1045    #[derive(Default, Debug, Clone, PartialEq, Eq, RlpEncodable)]
1046    #[non_exhaustive]
1047    pub struct KeccakHasher;
1048
1049    #[cfg(any(test, feature = "test-utils"))]
1050    impl Hasher for KeccakHasher {
1051        type Out = B256;
1052        type StdHasher = PlainHasher;
1053
1054        const LENGTH: usize = 32;
1055
1056        fn hash(x: &[u8]) -> Self::Out {
1057            keccak256(x)
1058        }
1059    }
1060}
1061
1062#[cfg(test)]
1063mod tests {
1064    use super::*;
1065    use alloy_trie::{
1066        nodes::{BranchNode, LeafNode, RlpNode},
1067        TrieMask,
1068    };
1069
1070    #[test]
1071    fn witness_nodes_are_depth_first_ordered() {
1072        fn insert_node(witness: &mut B256Map<Bytes>, node: impl alloy_rlp::Encodable) -> RlpNode {
1073            let encoded = alloy_rlp::encode(node);
1074            witness.insert(keccak256(&encoded), encoded.clone().into());
1075            RlpNode::from_rlp(&encoded)
1076        }
1077
1078        let mut witness = B256Map::default();
1079        let leaf_key = Nibbles::from_nibbles([0; 63]);
1080
1081        let storage_leaf_0 = insert_node(
1082            &mut witness,
1083            LeafNode::new(leaf_key, encode_fixed_size(&U256::from(1)).to_vec()),
1084        );
1085        let storage_leaf_1 = insert_node(
1086            &mut witness,
1087            LeafNode::new(leaf_key, encode_fixed_size(&U256::from(2)).to_vec()),
1088        );
1089        let storage_root = insert_node(
1090            &mut witness,
1091            BranchNode::new(vec![storage_leaf_0, storage_leaf_1], TrieMask::new(0b11)),
1092        );
1093
1094        let account_leaf_0 = insert_node(
1095            &mut witness,
1096            LeafNode::new(
1097                leaf_key,
1098                alloy_rlp::encode(TrieAccount {
1099                    storage_root: storage_root.as_hash().expect("storage root is hashed"),
1100                    ..Default::default()
1101                }),
1102            ),
1103        );
1104        let account_leaf_1 = insert_node(
1105            &mut witness,
1106            LeafNode::new(leaf_key, alloy_rlp::encode(TrieAccount::default())),
1107        );
1108        let state_root = insert_node(
1109            &mut witness,
1110            BranchNode::new(vec![account_leaf_0, account_leaf_1], TrieMask::new(0b11)),
1111        )
1112        .as_hash()
1113        .expect("state root is hashed");
1114
1115        let proof = DecodedMultiProofV2::from_witness(state_root, &witness).unwrap();
1116        let expected_paths =
1117            [Nibbles::from_nibbles([0]), Nibbles::from_nibbles([1]), Nibbles::default()];
1118
1119        assert_eq!(
1120            proof.account_proofs.iter().map(|node| node.path).collect::<Vec<_>>(),
1121            expected_paths
1122        );
1123        assert_eq!(
1124            proof.storage_proofs[&B256::ZERO].iter().map(|node| node.path).collect::<Vec<_>>(),
1125            expected_paths
1126        );
1127    }
1128
1129    #[test]
1130    fn test_multiproof_extend_account_proofs() {
1131        let mut proof1 = MultiProof::default();
1132        let mut proof2 = MultiProof::default();
1133
1134        let addr1 = B256::random();
1135        let addr2 = B256::random();
1136
1137        proof1.account_subtree.insert(
1138            Nibbles::unpack(addr1),
1139            alloy_rlp::encode_fixed_size(&U256::from(42)).to_vec().into(),
1140        );
1141        proof2.account_subtree.insert(
1142            Nibbles::unpack(addr2),
1143            alloy_rlp::encode_fixed_size(&U256::from(43)).to_vec().into(),
1144        );
1145
1146        proof1.extend(proof2);
1147
1148        assert!(proof1.account_subtree.contains_key(&Nibbles::unpack(addr1)));
1149        assert!(proof1.account_subtree.contains_key(&Nibbles::unpack(addr2)));
1150    }
1151
1152    #[test]
1153    fn test_multiproof_extend_storage_proofs() {
1154        let mut proof1 = MultiProof::default();
1155        let mut proof2 = MultiProof::default();
1156
1157        let addr = B256::random();
1158        let root = B256::random();
1159
1160        let mut subtree1 = ProofNodes::default();
1161        subtree1.insert(
1162            Nibbles::from_nibbles(vec![0]),
1163            alloy_rlp::encode_fixed_size(&U256::from(42)).to_vec().into(),
1164        );
1165        proof1.storages.insert(
1166            addr,
1167            StorageMultiProof {
1168                root,
1169                subtree: subtree1,
1170                branch_node_masks: BranchNodeMasksMap::default(),
1171            },
1172        );
1173
1174        let mut subtree2 = ProofNodes::default();
1175        subtree2.insert(
1176            Nibbles::from_nibbles(vec![1]),
1177            alloy_rlp::encode_fixed_size(&U256::from(43)).to_vec().into(),
1178        );
1179        proof2.storages.insert(
1180            addr,
1181            StorageMultiProof {
1182                root,
1183                subtree: subtree2,
1184                branch_node_masks: BranchNodeMasksMap::default(),
1185            },
1186        );
1187
1188        proof1.extend(proof2);
1189
1190        let storage = proof1.storages.get(&addr).unwrap();
1191        assert_eq!(storage.root, root);
1192        assert!(storage.subtree.contains_key(&Nibbles::from_nibbles(vec![0])));
1193        assert!(storage.subtree.contains_key(&Nibbles::from_nibbles(vec![1])));
1194    }
1195
1196    #[test]
1197    fn test_multi_proof_retain_difference() {
1198        let mut empty = MultiProofTargets::default();
1199        empty.retain_difference(&Default::default());
1200        assert!(empty.is_empty());
1201
1202        let targets = MultiProofTargets::accounts((0..10).map(B256::with_last_byte));
1203
1204        let mut diffed = targets.clone();
1205        diffed.retain_difference(&MultiProofTargets::account(B256::with_last_byte(11)));
1206        assert_eq!(diffed, targets);
1207
1208        diffed.retain_difference(&MultiProofTargets::accounts((0..5).map(B256::with_last_byte)));
1209        assert_eq!(diffed, MultiProofTargets::accounts((5..10).map(B256::with_last_byte)));
1210
1211        diffed.retain_difference(&targets);
1212        assert!(diffed.is_empty());
1213
1214        let mut targets = MultiProofTargets::default();
1215        let (account1, account2, account3) =
1216            (1..=3).map(B256::with_last_byte).collect_tuple().unwrap();
1217        let account2_slots = (1..5).map(B256::with_last_byte).collect::<B256Set>();
1218        targets.insert(account1, B256Set::from_iter([B256::with_last_byte(1)]));
1219        targets.insert(account2, account2_slots.clone());
1220        targets.insert(account3, B256Set::from_iter([B256::with_last_byte(1)]));
1221
1222        let mut diffed = targets.clone();
1223        diffed.retain_difference(&MultiProofTargets::accounts((1..=3).map(B256::with_last_byte)));
1224        assert_eq!(diffed, targets);
1225
1226        // remove last 3 slots for account 2
1227        let mut account2_slots_expected_len = account2_slots.len();
1228        for slot in account2_slots.iter().skip(1) {
1229            diffed.retain_difference(&MultiProofTargets::account_with_slots(account2, [*slot]));
1230            account2_slots_expected_len -= 1;
1231            assert_eq!(
1232                diffed.get(&account2).map(|slots| slots.len()),
1233                Some(account2_slots_expected_len)
1234            );
1235        }
1236
1237        diffed.retain_difference(&targets);
1238        assert!(diffed.is_empty());
1239    }
1240
1241    #[test]
1242    fn test_multi_proof_retain_difference_no_overlap() {
1243        let mut targets = MultiProofTargets::default();
1244
1245        // populate some targets
1246        let (addr1, addr2) = (B256::random(), B256::random());
1247        let (slot1, slot2) = (B256::random(), B256::random());
1248        targets.insert(addr1, std::iter::once(slot1).collect());
1249        targets.insert(addr2, std::iter::once(slot2).collect());
1250
1251        let mut retained = targets.clone();
1252        retained.retain_difference(&Default::default());
1253        assert_eq!(retained, targets);
1254
1255        // add a different addr and slot to fetched proof targets
1256        let mut other_targets = MultiProofTargets::default();
1257        let addr3 = B256::random();
1258        let slot3 = B256::random();
1259        other_targets.insert(addr3, B256Set::from_iter([slot3]));
1260
1261        // check that the prefetch proof targets are the same because the fetched proof targets
1262        // don't overlap with the prefetch targets
1263        let mut retained = targets.clone();
1264        retained.retain_difference(&other_targets);
1265        assert_eq!(retained, targets);
1266    }
1267
1268    #[test]
1269    fn test_get_prefetch_proof_targets_remove_subset() {
1270        // populate some targets
1271        let mut targets = MultiProofTargets::default();
1272        let (addr1, addr2) = (B256::random(), B256::random());
1273        let (slot1, slot2) = (B256::random(), B256::random());
1274        targets.insert(addr1, B256Set::from_iter([slot1]));
1275        targets.insert(addr2, B256Set::from_iter([slot2]));
1276
1277        // add a subset of the first target to other proof targets
1278        let other_targets = MultiProofTargets::account_with_slots(addr1, [slot1]);
1279
1280        let mut retained = targets.clone();
1281        retained.retain_difference(&other_targets);
1282
1283        // check that the prefetch proof targets do not include the subset
1284        assert_eq!(retained.len(), 1);
1285        assert!(!retained.contains_key(&addr1));
1286        assert!(retained.contains_key(&addr2));
1287
1288        // now add one more slot to the prefetch targets
1289        let slot3 = B256::random();
1290        targets.get_mut(&addr1).unwrap().insert(slot3);
1291
1292        let mut retained = targets.clone();
1293        retained.retain_difference(&other_targets);
1294
1295        // check that the prefetch proof targets do not include the subset
1296        // but include the new slot
1297        assert_eq!(retained.len(), 2);
1298        assert!(retained.contains_key(&addr1));
1299        assert_eq!(retained.get(&addr1), Some(&B256Set::from_iter([slot3])));
1300        assert!(retained.contains_key(&addr2));
1301        assert_eq!(retained.get(&addr2), Some(&B256Set::from_iter([slot2])));
1302    }
1303
1304    #[test]
1305    #[cfg(feature = "eip1186")]
1306    fn eip_1186_roundtrip() {
1307        let mut acc = AccountProof {
1308            address: Address::random(),
1309            info: Some(
1310                // non-empty account
1311                Account { nonce: 100, balance: U256::ZERO, bytecode_hash: Some(KECCAK_EMPTY) },
1312            ),
1313            proof: vec![],
1314            storage_root: B256::ZERO,
1315            storage_proofs: vec![],
1316        };
1317
1318        let rpc_proof = acc.clone().into_eip1186_response(Vec::new());
1319        let inverse: AccountProof = rpc_proof.into();
1320        assert_eq!(acc, inverse);
1321
1322        // make account empty
1323        acc.info.as_mut().unwrap().nonce = 0;
1324        let rpc_proof = acc.clone().into_eip1186_response(Vec::new());
1325        let inverse: AccountProof = rpc_proof.into();
1326        acc.info.take();
1327        acc.storage_root = EMPTY_ROOT_HASH;
1328        assert_eq!(acc, inverse);
1329    }
1330
1331    #[test]
1332    #[cfg(feature = "eip1186")]
1333    fn from_eip1186_proof_accepts_geth_zero_hashes() {
1334        // geth (since v1.13.4) returns B256::ZERO for codeHash and storageHash
1335        // in exclusion proofs for non-existent accounts, instead of
1336        // KECCAK_EMPTY / EMPTY_ROOT_HASH. Verify that from_eip1186_proof
1337        // correctly recognizes this format as a non-existent account.
1338        let geth_proof = alloy_rpc_types_eth::EIP1186AccountProofResponse {
1339            address: Address::random(),
1340            balance: U256::ZERO,
1341            code_hash: B256::ZERO,
1342            nonce: 0,
1343            storage_hash: B256::ZERO,
1344            account_proof: vec![],
1345            storage_proof: vec![],
1346        };
1347
1348        let acc: AccountProof = geth_proof.into();
1349        // Should be interpreted as a non-existent account (info = None)
1350        assert!(acc.info.is_none());
1351        assert_eq!(acc.storage_root, EMPTY_ROOT_HASH);
1352    }
1353
1354    #[test]
1355    #[cfg(feature = "eip1186")]
1356    fn from_eip1186_proof_accepts_empty_hashes() {
1357        let proof = alloy_rpc_types_eth::EIP1186AccountProofResponse {
1358            address: Address::random(),
1359            balance: U256::ZERO,
1360            code_hash: KECCAK_EMPTY,
1361            nonce: 0,
1362            storage_hash: EMPTY_ROOT_HASH,
1363            account_proof: vec![],
1364            storage_proof: vec![],
1365        };
1366
1367        let acc: AccountProof = proof.into();
1368        assert!(acc.info.is_none());
1369        assert_eq!(acc.storage_root, EMPTY_ROOT_HASH);
1370    }
1371
1372    #[test]
1373    #[cfg(feature = "eip1186")]
1374    fn into_eip1186_response_zero_empty_account() {
1375        // Non-existent account (info = None)
1376        let acc = AccountProof {
1377            address: Address::random(),
1378            info: None,
1379            proof: vec![],
1380            storage_root: EMPTY_ROOT_HASH,
1381            storage_proofs: vec![],
1382        };
1383
1384        // Default behavior: KECCAK_EMPTY / EMPTY_ROOT_HASH
1385        let rpc_default = acc.clone().into_eip1186_response(Vec::new());
1386        assert_eq!(rpc_default.code_hash, KECCAK_EMPTY);
1387        assert_eq!(rpc_default.storage_hash, EMPTY_ROOT_HASH);
1388
1389        // zero_empty_account = false: same as default
1390        let rpc_compat_off = acc.clone().into_eip1186_response_with(Vec::new(), false);
1391        assert_eq!(rpc_compat_off.code_hash, KECCAK_EMPTY);
1392        assert_eq!(rpc_compat_off.storage_hash, EMPTY_ROOT_HASH);
1393
1394        // zero_empty_account = true: B256::ZERO (geth-compat)
1395        let rpc_compat_on = acc.into_eip1186_response_with(Vec::new(), true);
1396        assert_eq!(rpc_compat_on.code_hash, B256::ZERO);
1397        assert_eq!(rpc_compat_on.storage_hash, B256::ZERO);
1398
1399        // Existing account should NOT be affected by zero_empty_account
1400        let existing_acc = AccountProof {
1401            address: Address::random(),
1402            info: Some(Account {
1403                nonce: 42,
1404                balance: U256::from(100),
1405                bytecode_hash: Some(KECCAK_EMPTY),
1406            }),
1407            proof: vec![],
1408            storage_root: B256::random(),
1409            storage_proofs: vec![],
1410        };
1411        let rpc_existing = existing_acc.clone().into_eip1186_response_with(Vec::new(), true);
1412        assert_eq!(rpc_existing.code_hash, KECCAK_EMPTY);
1413        assert_eq!(rpc_existing.storage_hash, existing_acc.storage_root);
1414    }
1415
1416    #[test]
1417    fn test_multiproof_targets_chunking_length() {
1418        let mut targets = MultiProofTargets::default();
1419        targets.insert(B256::with_last_byte(1), B256Set::default());
1420        targets.insert(
1421            B256::with_last_byte(2),
1422            B256Set::from_iter([B256::with_last_byte(10), B256::with_last_byte(20)]),
1423        );
1424        targets.insert(
1425            B256::with_last_byte(3),
1426            B256Set::from_iter([
1427                B256::with_last_byte(30),
1428                B256::with_last_byte(31),
1429                B256::with_last_byte(32),
1430            ]),
1431        );
1432
1433        let chunking_length = targets.chunking_length();
1434        for size in 1..=targets.clone().chunks(1).count() {
1435            let chunk_count = targets.clone().chunks(size).count();
1436            let expected_count = chunking_length.div_ceil(size);
1437            assert_eq!(
1438                chunk_count, expected_count,
1439                "chunking_length: {}, size: {}",
1440                chunking_length, size
1441            );
1442        }
1443    }
1444
1445    #[test]
1446    fn test_nonempty_storage_trie_returns_nonempty_proof() {
1447        let slot = B256::with_last_byte(1);
1448        let nibbles = Nibbles::unpack(keccak256(slot));
1449        let value = U256::from(999);
1450        let leaf = alloy_trie::nodes::LeafNode::new(nibbles, encode_fixed_size(&value).to_vec());
1451        let mut encoded = vec![];
1452        alloy_rlp::Encodable::encode(&leaf, &mut encoded);
1453
1454        let mut subtree = ProofNodes::default();
1455        subtree.insert(nibbles, encoded.into());
1456
1457        let multiproof = StorageMultiProof {
1458            root: B256::with_last_byte(0xFF),
1459            subtree,
1460            branch_node_masks: BranchNodeMasksMap::default(),
1461        };
1462
1463        let proof = multiproof.storage_proof(slot).unwrap();
1464        assert!(!proof.proof.is_empty(), "non-empty trie must return non-empty proof");
1465        assert_eq!(proof.value, value);
1466    }
1467
1468    #[cfg(feature = "eip1186")]
1469    #[test]
1470    fn eip1186_response_normalizes_empty_trie_proof() {
1471        let slot = B256::with_last_byte(1);
1472        let sentinel = || vec![Bytes::from([EMPTY_STRING_CODE])];
1473
1474        // Empty account trie + empty storage trie: both proofs are the lone `0x80` sentinel.
1475        let account = AccountProof {
1476            address: Address::ZERO,
1477            info: None,
1478            proof: sentinel(),
1479            storage_root: EMPTY_ROOT_HASH,
1480            storage_proofs: vec![StorageProof::new(slot).with_proof(sentinel())],
1481        };
1482
1483        let resp = account.into_eip1186_response(vec![alloy_serde::JsonStorageKey::from(slot)]);
1484
1485        assert!(
1486            resp.account_proof.is_empty(),
1487            "empty account trie must yield empty account_proof, got {:?}",
1488            resp.account_proof
1489        );
1490        assert_eq!(resp.storage_proof.len(), 1);
1491        assert!(
1492            resp.storage_proof[0].proof.is_empty(),
1493            "empty storage trie must yield empty storage proof, got {:?}",
1494            resp.storage_proof[0].proof
1495        );
1496    }
1497
1498    #[cfg(feature = "eip1186")]
1499    #[test]
1500    fn eip1186_response_keeps_nonempty_proof() {
1501        // A real (non-empty) proof must pass through unchanged: multiple nodes, and a single
1502        // node that is not the `0x80` sentinel.
1503        let multi = vec![Bytes::from([0x01, 0x02]), Bytes::from([0x03])];
1504        let single_non_sentinel = vec![Bytes::from([0xf8, 0x44])];
1505
1506        let account = AccountProof {
1507            address: Address::ZERO,
1508            info: None,
1509            proof: multi.clone(),
1510            storage_root: EMPTY_ROOT_HASH,
1511            storage_proofs: vec![
1512                StorageProof::new(B256::with_last_byte(1)).with_proof(single_non_sentinel.clone())
1513            ],
1514        };
1515
1516        let resp = account.into_eip1186_response(vec![alloy_serde::JsonStorageKey::from(
1517            B256::with_last_byte(1),
1518        )]);
1519
1520        assert_eq!(resp.account_proof, multi);
1521        assert_eq!(resp.storage_proof[0].proof, single_non_sentinel);
1522    }
1523}