Skip to main content

reth_trie_common/
proofs.rs

1//! Merkle trie proofs.
2
3use 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/// Proof targets map.
24#[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    /// Creates an empty `MultiProofTargets` with at least the specified capacity.
35    pub fn with_capacity(capacity: usize) -> Self {
36        Self(B256Map::with_capacity_and_hasher(capacity, Default::default()))
37    }
38
39    /// Create `MultiProofTargets` with a single account as a target.
40    pub fn account(hashed_address: B256) -> Self {
41        Self::accounts([hashed_address])
42    }
43
44    /// Create `MultiProofTargets` with a single account and slots as targets.
45    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    /// Create `MultiProofTargets` only from accounts.
53    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    /// Retains the targets representing the difference,
58    /// i.e., the values that are in `self` but not in `other`.
59    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    /// Extend multi proof targets with contents of other.
71    pub fn extend(&mut self, other: Self) {
72        self.extend_inner(Cow::Owned(other));
73    }
74
75    /// Extend multi proof targets with contents of other.
76    ///
77    /// Slightly less efficient than [`Self::extend`], but preferred to `extend(other.clone())`.
78    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    /// Returns an iterator that yields chunks of the specified size.
96    ///
97    /// See [`ChunkedMultiProofTargets`] for more information.
98    pub fn chunks(self, size: usize) -> ChunkedMultiProofTargets {
99        ChunkedMultiProofTargets::new(self, size)
100    }
101
102    /// Returns the number of items that will be considered during chunking in `[Self::chunks]`.
103    pub fn chunking_length(&self) -> usize {
104        self.values().map(|slots| 1 + slots.len().saturating_sub(1)).sum::<usize>()
105    }
106}
107
108/// An iterator that yields chunks of the proof targets of at most `size` account and storage
109/// targets.
110///
111/// For example, for the following proof targets:
112/// ```text
113/// - 0x1: [0x10, 0x20, 0x30]
114/// - 0x2: [0x40]
115/// - 0x3: []
116/// ```
117///
118/// and `size = 2`, the iterator will yield the following chunks:
119/// ```text
120/// - { 0x1: [0x10, 0x20] }
121/// - { 0x1: [0x30], 0x2: [0x40] }
122/// - { 0x3: [] }
123/// ```
124///
125/// It follows two rules:
126/// - If account has associated storage slots, each storage slot is counted towards the chunk size.
127/// - If account has no associated storage slots, the account is counted towards the chunk size.
128#[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                    // If the account has no storage slots, we still need to yield the account
141                    // address with empty storage slots. `None` here means that
142                    // there's no storage slot to fetch.
143                    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/// The state multiproof of target accounts and multiproofs of their storage tries.
179/// Multiproof is effectively a state subtrie that only contains the nodes
180/// in the paths of target accounts.
181#[derive(Clone, Default, Debug, PartialEq, Eq)]
182pub struct MultiProof {
183    /// State trie multiproof for requested accounts.
184    pub account_subtree: ProofNodes,
185    /// Consolidated branch node masks (`hash_mask`, `tree_mask`) for each path in the account
186    /// proof.
187    pub branch_node_masks: BranchNodeMasksMap,
188    /// Storage trie multiproofs.
189    pub storages: B256Map<StorageMultiProof>,
190}
191
192impl MultiProof {
193    /// Returns true if the multiproof is empty.
194    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    /// Return the account proof nodes for the given account path.
201    pub fn account_proof_nodes(&self, path: &Nibbles) -> Vec<(Nibbles, Bytes)> {
202        self.account_subtree.matching_nodes_sorted(path)
203    }
204
205    /// Return the storage proof nodes for the given storage slots of the account path.
206    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    /// Construct the account proof from the multiproof.
226    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        // Retrieve the account proof.
235        let proof = self
236            .account_proof_nodes(&nibbles)
237            .into_iter()
238            .map(|(_, node)| node)
239            .collect::<Vec<_>>();
240
241        // Inspect the last node in the proof. If it's a leaf node with matching suffix,
242        // then the node contains the encoded trie account.
243        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        // Retrieve proofs for requested storage slots.
259        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    /// Extends this multiproof with another one, merging both account and storage
274    /// proofs.
275    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    /// Create a [`MultiProof`] from a [`StorageMultiProof`].
301    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/// This is a type of [`MultiProof`] that uses decoded proofs, meaning these proofs are stored as a
310/// collection of [`TrieNode`]s instead of RLP-encoded bytes.
311#[derive(Clone, Default, Debug, PartialEq, Eq)]
312pub struct DecodedMultiProof {
313    /// State trie multiproof for requested accounts.
314    pub account_subtree: DecodedProofNodes,
315    /// Consolidated branch node masks (`hash_mask`, `tree_mask`) for each path in the account
316    /// proof.
317    pub branch_node_masks: BranchNodeMasksMap,
318    /// Storage trie multiproofs.
319    pub storages: B256Map<DecodedStorageMultiProof>,
320}
321
322impl DecodedMultiProof {
323    /// Returns true if the multiproof is empty.
324    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    /// Return the account proof nodes for the given account path.
331    pub fn account_proof_nodes(&self, path: &Nibbles) -> Vec<(Nibbles, TrieNode)> {
332        self.account_subtree.matching_nodes_sorted(path)
333    }
334
335    /// Return the storage proof nodes for the given storage slots of the account path.
336    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    /// Construct the account proof from the multiproof.
356    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        // Retrieve the account proof.
365        let proof = self
366            .account_proof_nodes(&nibbles)
367            .into_iter()
368            .map(|(_, node)| node)
369            .collect::<Vec<_>>();
370
371        // Inspect the last node in the proof. If it's a leaf node with matching suffix,
372        // then the node contains the encoded trie account.
373        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        // Retrieve proofs for requested storage slots.
388        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    /// Extends this multiproof with another one, merging both account and storage
403    /// proofs.
404    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    /// Create a [`DecodedMultiProof`] from a [`DecodedStorageMultiProof`].
430    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/// V2 decoded multiproof which contains the results of both account and storage V2 proof
456/// calculations.
457#[derive(Clone, Debug, PartialEq, Eq, Default)]
458pub struct DecodedMultiProofV2 {
459    /// Account trie proof nodes
460    pub account_proofs: Vec<ProofTrieNodeV2>,
461    /// Storage trie proof nodes indexed by account
462    pub storage_proofs: B256Map<Vec<ProofTrieNodeV2>>,
463}
464
465impl DecodedMultiProofV2 {
466    /// Returns true if there are no proofs
467    pub fn is_empty(&self) -> bool {
468        self.account_proofs.is_empty() && self.storage_proofs.is_empty()
469    }
470
471    /// Construct an account proof from the V2 multiproof.
472    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    /// Builds a `DecodedMultiProofV2` from a flat witness map (hash → RLP-encoded trie node).
528    ///
529    /// This performs a BFS traversal starting from `state_root`, decoding each witness entry
530    /// as a trie node and organizing them into account and storage proof vectors. This is the
531    /// inverse of witness generation — it reconstructs the structured multiproof from the flat
532    /// format used in `ExecutionWitness`.
533    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    /// Appends the given multiproof's data to this one.
605    ///
606    /// This implementation does not deduplicate redundant proofs.
607    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
622/// Returns proof nodes matching `path` in root-to-leaf order.
623fn 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
630/// Encodes V2 proof nodes as standard MPT proof nodes.
631///
632/// V2 combines an extension and its child branch in one node. Both nodes need to be emitted for
633/// consumers that reconstruct a sparse trie from the returned proof.
634fn 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
667/// Converts a [`DecodedProofNodes`] (path → [`TrieNode`] map) into a `Vec<ProofTrieNodeV2>`,
668/// merging extension nodes into their child branch nodes.
669fn 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/// The merkle multiproof of storage trie.
681#[derive(Clone, Debug, PartialEq, Eq)]
682pub struct StorageMultiProof {
683    /// Storage trie root.
684    pub root: B256,
685    /// Storage multiproof for requested slots.
686    pub subtree: ProofNodes,
687    /// Consolidated branch node masks (`hash_mask`, `tree_mask`) for each path in the storage
688    /// proof.
689    pub branch_node_masks: BranchNodeMasksMap,
690}
691
692impl StorageMultiProof {
693    /// Create new storage multiproof for empty trie.
694    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    /// Return storage proofs for the target storage slot (unhashed).
706    pub fn storage_proof(&self, slot: B256) -> Result<StorageProof, alloy_rlp::Error> {
707        let nibbles = Nibbles::unpack(keccak256(slot));
708
709        // Retrieve the storage proof.
710        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        // Inspect the last node in the proof. If it's a leaf node with matching suffix,
718        // then the node contains the encoded slot value.
719        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/// The decoded merkle multiproof for a storage trie.
734#[derive(Clone, Debug, PartialEq, Eq)]
735pub struct DecodedStorageMultiProof {
736    /// Storage trie root.
737    pub root: B256,
738    /// Storage multiproof for requested slots.
739    pub subtree: DecodedProofNodes,
740    /// Consolidated branch node masks (`hash_mask`, `tree_mask`) for each path in the storage
741    /// proof.
742    pub branch_node_masks: BranchNodeMasksMap,
743}
744
745impl DecodedStorageMultiProof {
746    /// Create new storage multiproof for empty trie.
747    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    /// Return storage proofs for the target storage slot (unhashed).
756    pub fn storage_proof(&self, slot: B256) -> Result<DecodedStorageProof, alloy_rlp::Error> {
757        let nibbles = Nibbles::unpack(keccak256(slot));
758
759        // Retrieve the storage proof.
760        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        // Inspect the last node in the proof. If it's a leaf node with matching suffix,
768        // then the node contains the encoded slot value.
769        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/// The merkle proof with the relevant account info.
796#[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    /// The address associated with the account.
801    pub address: Address,
802    /// Account info, if any.
803    pub info: Option<Account>,
804    /// Array of rlp-serialized merkle trie nodes which starting from the root node and
805    /// following the path of the hashed address as key.
806    pub proof: Vec<Bytes>,
807    /// The storage trie root.
808    pub storage_root: B256,
809    /// Array of storage proofs as requested.
810    pub storage_proofs: Vec<StorageProof>,
811}
812
813/// Normalize an empty-trie proof for the EIP-1186 (`eth_getProof`) response.
814///
815/// An empty trie is internally represented by a single empty-root sentinel node (`0x80`, the
816/// RLP empty string whose hash is `EMPTY_ROOT_HASH`). EIP-1186 defines the proof field as the
817/// array of trie nodes along the key path; an empty trie has none, and geth returns `[]`. This
818/// strips that lone sentinel so the response matches geth and the spec. It is applied only at
819/// the response boundary, leaving the underlying proof construction unchanged.
820#[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    /// Convert into an EIP-1186 account proof response.
832    ///
833    /// For non-existent accounts, this returns `KECCAK_EMPTY` for `codeHash` and
834    /// `EMPTY_ROOT_HASH` for `storageHash`, matching reth's default behavior.
835    ///
836    /// Use [`Self::into_eip1186_response_with`] to customize the behavior for
837    /// non-existent accounts (e.g. returning `B256::ZERO` for geth compatibility).
838    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    /// Convert into an EIP-1186 account proof response, with optional geth-compatible
846    /// zero hashes for non-existent accounts.
847    ///
848    /// When `zero_empty_account` is `true`, non-existent accounts return `B256::ZERO`
849    /// for both `codeHash` and `storageHash`, matching geth's behavior since v1.13.4
850    /// ([go-ethereum#28357](https://github.com/ethereum/go-ethereum/pull/28357)).
851    ///
852    /// When `false`, returns `KECCAK_EMPTY` / `EMPTY_ROOT_HASH` (reth default).
853    ///
854    /// See: <https://github.com/ethereum/go-ethereum/issues/28441>
855    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    /// Converts an
886    /// [`EIP1186AccountProofResponse`](alloy_rpc_types_eth::EIP1186AccountProofResponse) to an
887    /// [`AccountProof`].
888    ///
889    /// This is the inverse of [`Self::into_eip1186_response`]
890    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            // Account does not exist in state. Return `None` here to prevent proof
909            // verification.
910            //
911            // Note: geth (since v1.13.4, go-ethereum#28357) returns `B256::ZERO` for
912            // both `codeHash` and `storageHash` in exclusion proofs, while reth
913            // returns `KECCAK_EMPTY` / `EMPTY_ROOT_HASH`. We accept both formats here
914            // so that proofs obtained from any client can be deserialized correctly.
915            // See: https://github.com/ethereum/go-ethereum/issues/28441
916            (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    /// Create new account proof entity.
940    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    /// Verify the storage proofs and account proof against the provided state root.
951    pub fn verify(&self, root: B256) -> Result<(), ProofVerificationError> {
952        // Verify storage proofs.
953        for storage_proof in &self.storage_proofs {
954            storage_proof.verify(self.storage_root)?;
955        }
956
957        // Verify the account proof.
958        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/// The merkle proof with the relevant account info.
971#[derive(Clone, PartialEq, Eq, Debug)]
972pub struct DecodedAccountProof {
973    /// The address associated with the account.
974    pub address: Address,
975    /// Account info.
976    pub info: Option<Account>,
977    /// Array of merkle trie nodes which starting from the root node and following the path of the
978    /// hashed address as key.
979    pub proof: Vec<TrieNode>,
980    /// The storage trie root.
981    pub storage_root: B256,
982    /// Array of storage proofs as requested.
983    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    /// Create new account proof entity.
994    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/// The merkle proof of the storage entry.
1006#[derive(Clone, PartialEq, Eq, Default, Debug)]
1007#[cfg_attr(any(test, feature = "serde"), derive(serde::Serialize, serde::Deserialize))]
1008pub struct StorageProof {
1009    /// The raw storage key.
1010    pub key: B256,
1011    /// The hashed storage key nibbles.
1012    pub nibbles: Nibbles,
1013    /// The storage value.
1014    pub value: U256,
1015    /// Array of rlp-serialized merkle trie nodes which starting from the storage root node and
1016    /// following the path of the hashed storage slot as key.
1017    pub proof: Vec<Bytes>,
1018}
1019
1020impl StorageProof {
1021    /// Create new storage proof from the storage slot.
1022    pub fn new(key: B256) -> Self {
1023        let nibbles = Nibbles::unpack(keccak256(key));
1024        Self { key, nibbles, ..Default::default() }
1025    }
1026
1027    /// Create new storage proof from the storage slot and its pre-hashed image.
1028    pub fn new_with_hashed(key: B256, hashed_key: B256) -> Self {
1029        Self { key, nibbles: Nibbles::unpack(hashed_key), ..Default::default() }
1030    }
1031
1032    /// Create new storage proof from the storage slot and its pre-hashed image.
1033    pub fn new_with_nibbles(key: B256, nibbles: Nibbles) -> Self {
1034        Self { key, nibbles, ..Default::default() }
1035    }
1036
1037    /// Set proof nodes on storage proof.
1038    pub fn with_proof(mut self, proof: Vec<Bytes>) -> Self {
1039        self.proof = proof;
1040        self
1041    }
1042
1043    /// Verify the proof against the provided storage root.
1044    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    /// Convert into an EIP-1186 storage proof
1054    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    /// Convert from an
1066    /// [`EIP1186StorageProof`](alloy_rpc_types_eth::EIP1186StorageProof)
1067    ///
1068    /// This is the inverse of [`Self::into_eip1186_proof`].
1069    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/// The merkle proof of the storage entry, using decoded proofs.
1086#[derive(Clone, PartialEq, Eq, Default, Debug)]
1087pub struct DecodedStorageProof {
1088    /// The raw storage key.
1089    pub key: B256,
1090    /// The hashed storage key nibbles.
1091    pub nibbles: Nibbles,
1092    /// The storage value.
1093    pub value: U256,
1094    /// Array of merkle trie nodes which starting from the storage root node and following the path
1095    /// of the hashed storage slot as key.
1096    pub proof: Vec<TrieNode>,
1097}
1098
1099impl DecodedStorageProof {
1100    /// Create new storage proof from the storage slot.
1101    pub fn new(key: B256) -> Self {
1102        let nibbles = Nibbles::unpack(keccak256(key));
1103        Self { key, nibbles, ..Default::default() }
1104    }
1105
1106    /// Create new storage proof from the storage slot and its pre-hashed image.
1107    pub fn new_with_hashed(key: B256, hashed_key: B256) -> Self {
1108        Self { key, nibbles: Nibbles::unpack(hashed_key), ..Default::default() }
1109    }
1110
1111    /// Create new storage proof from the storage slot and its pre-hashed image.
1112    pub fn new_with_nibbles(key: B256, nibbles: Nibbles) -> Self {
1113        Self { key, nibbles, ..Default::default() }
1114    }
1115
1116    /// Set proof nodes on storage proof.
1117    pub fn with_proof(mut self, proof: Vec<TrieNode>) -> Self {
1118        self.proof = proof;
1119        self
1120    }
1121}
1122
1123/// Implementation of hasher using our keccak256 hashing function
1124/// for compatibility with `triehash` crate.
1125#[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    /// A [Hasher] that calculates a keccak256 hash of the given data.
1133    #[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        // remove last 3 slots for account 2
1342        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        // populate some targets
1361        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        // add a different addr and slot to fetched proof targets
1371        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        // check that the prefetch proof targets are the same because the fetched proof targets
1377        // don't overlap with the prefetch targets
1378        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        // populate some targets
1386        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        // add a subset of the first target to other proof targets
1393        let other_targets = MultiProofTargets::account_with_slots(addr1, [slot1]);
1394
1395        let mut retained = targets.clone();
1396        retained.retain_difference(&other_targets);
1397
1398        // check that the prefetch proof targets do not include the subset
1399        assert_eq!(retained.len(), 1);
1400        assert!(!retained.contains_key(&addr1));
1401        assert!(retained.contains_key(&addr2));
1402
1403        // now add one more slot to the prefetch targets
1404        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        // check that the prefetch proof targets do not include the subset
1411        // but include the new slot
1412        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                // non-empty account
1426                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        // make account empty
1438        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        // geth (since v1.13.4) returns B256::ZERO for codeHash and storageHash
1450        // in exclusion proofs for non-existent accounts, instead of
1451        // KECCAK_EMPTY / EMPTY_ROOT_HASH. Verify that from_eip1186_proof
1452        // correctly recognizes this format as a non-existent account.
1453        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        // Should be interpreted as a non-existent account (info = None)
1465        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        // Non-existent account (info = None)
1491        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        // Default behavior: KECCAK_EMPTY / EMPTY_ROOT_HASH
1500        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        // zero_empty_account = false: same as default
1505        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        // zero_empty_account = true: B256::ZERO (geth-compat)
1510        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        // Existing account should NOT be affected by zero_empty_account
1515        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        // Empty account trie + empty storage trie: both proofs are the lone `0x80` sentinel.
1590        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        // A real (non-empty) proof must pass through unchanged: multiple nodes, and a single
1617        // node that is not the `0x80` sentinel.
1618        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}