Skip to main content

reth_trie_parallel/
targets_v2.rs

1//! V2 multiproof targets and chunking.
2
3use alloy_primitives::{map::B256Map, B256};
4use reth_trie::proof_v2;
5
6/// A set of account and storage V2 proof targets. The account and storage targets do not need to
7/// necessarily overlap.
8#[derive(Debug, Default)]
9pub struct MultiProofTargetsV2 {
10    /// The set of account proof targets to generate proofs for.
11    pub account_targets: Vec<proof_v2::Target>,
12    /// The sets of storage proof targets to generate proofs for.
13    pub storage_targets: B256Map<Vec<proof_v2::Target>>,
14}
15
16impl MultiProofTargetsV2 {
17    /// Returns true is there are no account or storage targets.
18    pub fn is_empty(&self) -> bool {
19        self.account_targets.is_empty() && self.storage_targets.is_empty()
20    }
21
22    /// Returns the number of items that will be considered during chunking.
23    pub fn chunking_length(&self) -> usize {
24        self.account_targets.len() +
25            self.storage_targets.values().map(|slots| slots.len()).sum::<usize>()
26    }
27
28    /// Returns an iterator that yields chunks of the specified size.
29    pub fn chunks(self, chunk_size: usize) -> impl Iterator<Item = Self> {
30        ChunkedMultiProofTargetsV2::new(self, chunk_size)
31    }
32}
33
34/// An iterator that yields chunks of V2 proof targets of at most `size` account and storage
35/// targets.
36///
37/// Unlike legacy chunking, V2 preserves account targets exactly as they were (with their `min_len`
38/// metadata). Account targets must appear in a chunk. Storage targets for those accounts are
39/// chunked together, but if they exceed the chunk size, subsequent chunks contain only the
40/// remaining storage targets without repeating the account target.
41#[derive(Debug)]
42pub struct ChunkedMultiProofTargetsV2 {
43    /// Remaining account targets to process
44    account_targets: std::vec::IntoIter<proof_v2::Target>,
45    /// Storage targets by account address
46    storage_targets: B256Map<Vec<proof_v2::Target>>,
47    /// Current account being processed (if any storage slots remain)
48    current_account_storage: Option<(B256, std::vec::IntoIter<proof_v2::Target>)>,
49    /// Chunk size
50    size: usize,
51}
52
53impl ChunkedMultiProofTargetsV2 {
54    /// Creates a new chunked iterator for the given targets.
55    pub fn new(targets: MultiProofTargetsV2, size: usize) -> Self {
56        Self {
57            account_targets: targets.account_targets.into_iter(),
58            storage_targets: targets.storage_targets,
59            current_account_storage: None,
60            size,
61        }
62    }
63}
64
65impl Iterator for ChunkedMultiProofTargetsV2 {
66    type Item = MultiProofTargetsV2;
67
68    fn next(&mut self) -> Option<Self::Item> {
69        let mut chunk = MultiProofTargetsV2::default();
70        let mut count = 0;
71
72        // First, finish any remaining storage slots from previous account
73        if let Some((account_addr, ref mut storage_iter)) = self.current_account_storage {
74            let remaining_capacity = self.size - count;
75            let slots: Vec<_> = storage_iter.by_ref().take(remaining_capacity).collect();
76
77            count += slots.len();
78            chunk.storage_targets.insert(account_addr, slots);
79
80            // If iterator is exhausted, clear current_account_storage
81            if storage_iter.len() == 0 {
82                self.current_account_storage = None;
83            }
84        }
85
86        // Process account targets and their storage
87        while count < self.size {
88            let Some(account_target) = self.account_targets.next() else {
89                break;
90            };
91
92            // Add the account target
93            chunk.account_targets.push(account_target);
94            count += 1;
95
96            // Check if this account has storage targets
97            let account_addr = account_target.key();
98            if let Some(storage_slots) = self.storage_targets.remove(&account_addr) {
99                let remaining_capacity = self.size - count;
100
101                if storage_slots.len() <= remaining_capacity {
102                    // Optimization: We can take all slots, just move the vec
103                    count += storage_slots.len();
104                    chunk.storage_targets.insert(account_addr, storage_slots);
105                } else {
106                    // We need to split the storage slots
107                    let mut storage_iter = storage_slots.into_iter();
108                    let slots_in_chunk: Vec<_> =
109                        storage_iter.by_ref().take(remaining_capacity).collect();
110                    count += slots_in_chunk.len();
111
112                    chunk.storage_targets.insert(account_addr, slots_in_chunk);
113
114                    // Save remaining storage slots for next chunk
115                    self.current_account_storage = Some((account_addr, storage_iter));
116                    break;
117                }
118            }
119        }
120
121        // Process any remaining storage-only entries (accounts not in account_targets)
122        while let Some((account_addr, storage_slots)) = self.storage_targets.iter_mut().next() &&
123            count < self.size
124        {
125            let account_addr = *account_addr;
126            let storage_slots = std::mem::take(storage_slots);
127            let remaining_capacity = self.size - count;
128
129            // Always remove from the map - if there are remaining slots they go to
130            // current_account_storage
131            self.storage_targets.remove(&account_addr);
132
133            if storage_slots.len() <= remaining_capacity {
134                // Optimization: We can take all slots, just move the vec
135                count += storage_slots.len();
136                chunk.storage_targets.insert(account_addr, storage_slots);
137            } else {
138                // We need to split the storage slots
139                let mut storage_iter = storage_slots.into_iter();
140                let slots_in_chunk: Vec<_> =
141                    storage_iter.by_ref().take(remaining_capacity).collect();
142
143                chunk.storage_targets.insert(account_addr, slots_in_chunk);
144
145                // Save remaining storage slots for next chunk
146                if storage_iter.len() > 0 {
147                    self.current_account_storage = Some((account_addr, storage_iter));
148                }
149                break;
150            }
151        }
152
153        if chunk.account_targets.is_empty() && chunk.storage_targets.is_empty() {
154            None
155        } else {
156            Some(chunk)
157        }
158    }
159}