Skip to main content

reth_trie_common/
target_v2.rs

1//! V2 proof targets and chunking.
2
3use crate::Nibbles;
4use alloc::vec::Vec;
5use alloy_primitives::{map::B256Map, B256};
6
7/// Target describes a proof target. For every proof target given, a proof calculator will calculate
8/// and return all nodes whose path is a prefix of the target's `key_nibbles`.
9#[derive(Debug, Copy, Clone)]
10pub struct ProofV2Target {
11    /// The key of the proof target, as nibbles.
12    pub key_nibbles: Nibbles,
13    /// The minimum length of a node's path for it to be retained.
14    pub min_len: u8,
15}
16
17impl ProofV2Target {
18    /// Returns a new [`ProofV2Target`] which matches all trie nodes whose path is a prefix of this
19    /// key.
20    pub fn new(key: B256) -> Self {
21        // SAFETY: key is a B256 and so is exactly 32-bytes.
22        let key_nibbles = unsafe { Nibbles::unpack_unchecked(key.as_slice()) };
23        Self { key_nibbles, min_len: 0 }
24    }
25
26    /// Returns the key the target was initialized with.
27    pub fn key(&self) -> B256 {
28        B256::from_slice(&self.key_nibbles.pack())
29    }
30
31    /// Only match trie nodes whose path is at least this long.
32    ///
33    /// # Panics
34    ///
35    /// This method panics if `min_len` is greater than 64.
36    pub fn with_min_len(mut self, min_len: u8) -> Self {
37        debug_assert!(min_len <= 64);
38        self.min_len = min_len;
39        self
40    }
41}
42
43impl From<B256> for ProofV2Target {
44    fn from(key: B256) -> Self {
45        Self::new(key)
46    }
47}
48
49/// A set of account and storage V2 proof targets. The account and storage targets do not need to
50/// necessarily overlap.
51#[derive(Debug, Default)]
52pub struct MultiProofTargetsV2 {
53    /// The set of account proof targets to generate proofs for.
54    pub account_targets: Vec<ProofV2Target>,
55    /// The sets of storage proof targets to generate proofs for.
56    pub storage_targets: B256Map<Vec<ProofV2Target>>,
57}
58
59impl MultiProofTargetsV2 {
60    /// Returns true is there are no account or storage targets.
61    pub fn is_empty(&self) -> bool {
62        self.account_targets.is_empty() && self.storage_targets.is_empty()
63    }
64
65    /// Returns the number of items that will be considered during chunking.
66    pub fn chunking_length(&self) -> usize {
67        self.account_targets.len() +
68            self.storage_targets.values().map(|slots| slots.len()).sum::<usize>()
69    }
70
71    /// Returns an iterator that yields chunks of the specified size.
72    pub fn chunks(self, chunk_size: usize) -> impl Iterator<Item = Self> {
73        ChunkedMultiProofTargetsV2::new(self, chunk_size)
74    }
75}
76
77/// An iterator that yields chunks of V2 proof targets of at most `size` account and storage
78/// targets.
79///
80/// Unlike legacy chunking, V2 preserves account targets exactly as they were (with their `min_len`
81/// metadata). Account targets must appear in a chunk. Storage targets for those accounts are
82/// chunked together, but if they exceed the chunk size, subsequent chunks contain only the
83/// remaining storage targets without repeating the account target.
84#[derive(Debug)]
85pub struct ChunkedMultiProofTargetsV2 {
86    /// Remaining account targets to process
87    account_targets: alloc::vec::IntoIter<ProofV2Target>,
88    /// Storage targets by account address
89    storage_targets: B256Map<Vec<ProofV2Target>>,
90    /// Current account being processed (if any storage slots remain)
91    current_account_storage: Option<(B256, alloc::vec::IntoIter<ProofV2Target>)>,
92    /// Chunk size
93    size: usize,
94}
95
96impl ChunkedMultiProofTargetsV2 {
97    /// Creates a new chunked iterator for the given targets.
98    pub fn new(targets: MultiProofTargetsV2, size: usize) -> Self {
99        Self {
100            account_targets: targets.account_targets.into_iter(),
101            storage_targets: targets.storage_targets,
102            current_account_storage: None,
103            size,
104        }
105    }
106}
107
108impl Iterator for ChunkedMultiProofTargetsV2 {
109    type Item = MultiProofTargetsV2;
110
111    fn next(&mut self) -> Option<Self::Item> {
112        let mut chunk = MultiProofTargetsV2::default();
113        let mut count = 0;
114
115        // First, finish any remaining storage slots from previous account
116        if let Some((account_addr, ref mut storage_iter)) = self.current_account_storage {
117            let remaining_capacity = self.size - count;
118            let slots: Vec<_> = storage_iter.by_ref().take(remaining_capacity).collect();
119
120            count += slots.len();
121            chunk.storage_targets.insert(account_addr, slots);
122
123            // If iterator is exhausted, clear current_account_storage
124            if storage_iter.len() == 0 {
125                self.current_account_storage = None;
126            }
127        }
128
129        // Process account targets and their storage
130        while count < self.size {
131            let Some(account_target) = self.account_targets.next() else {
132                break;
133            };
134
135            // Add the account target
136            chunk.account_targets.push(account_target);
137            count += 1;
138
139            // Check if this account has storage targets
140            let account_addr = account_target.key();
141            if let Some(storage_slots) = self.storage_targets.remove(&account_addr) {
142                let remaining_capacity = self.size - count;
143
144                if storage_slots.len() <= remaining_capacity {
145                    // Optimization: We can take all slots, just move the vec
146                    count += storage_slots.len();
147                    chunk.storage_targets.insert(account_addr, storage_slots);
148                } else {
149                    // We need to split the storage slots
150                    let mut storage_iter = storage_slots.into_iter();
151                    let slots_in_chunk: Vec<_> =
152                        storage_iter.by_ref().take(remaining_capacity).collect();
153                    count += slots_in_chunk.len();
154
155                    chunk.storage_targets.insert(account_addr, slots_in_chunk);
156
157                    // Save remaining storage slots for next chunk
158                    self.current_account_storage = Some((account_addr, storage_iter));
159                    break;
160                }
161            }
162        }
163
164        // Process any remaining storage-only entries (accounts not in account_targets)
165        while let Some((account_addr, storage_slots)) = self.storage_targets.iter_mut().next() &&
166            count < self.size
167        {
168            let account_addr = *account_addr;
169            let storage_slots = core::mem::take(storage_slots);
170            let remaining_capacity = self.size - count;
171
172            // Always remove from the map - if there are remaining slots they go to
173            // current_account_storage
174            self.storage_targets.remove(&account_addr);
175
176            if storage_slots.len() <= remaining_capacity {
177                // Optimization: We can take all slots, just move the vec
178                count += storage_slots.len();
179                chunk.storage_targets.insert(account_addr, storage_slots);
180            } else {
181                // We need to split the storage slots
182                let mut storage_iter = storage_slots.into_iter();
183                let slots_in_chunk: Vec<_> =
184                    storage_iter.by_ref().take(remaining_capacity).collect();
185
186                chunk.storage_targets.insert(account_addr, slots_in_chunk);
187
188                // Save remaining storage slots for next chunk
189                if storage_iter.len() > 0 {
190                    self.current_account_storage = Some((account_addr, storage_iter));
191                }
192                break;
193            }
194        }
195
196        if chunk.account_targets.is_empty() && chunk.storage_targets.is_empty() {
197            None
198        } else {
199            Some(chunk)
200        }
201    }
202}