reth_trie/proof_v2/target.rs
1use crate::proof_v2::increment_and_strip_trailing_zeros;
2use alloy_primitives::B256;
3use reth_trie_common::Nibbles;
4
5/// Target describes a proof target. For every proof target given, the
6/// [`crate::proof_v2::ProofCalculator`] will calculate and return all nodes whose path is a prefix
7/// of the target's `key`.
8#[derive(Debug, Copy, Clone)]
9pub struct Target {
10 pub(crate) key: Nibbles,
11 pub(crate) min_len: u8,
12}
13
14impl Target {
15 /// Returns a new [`Target`] which matches all trie nodes whose path is a prefix of this key.
16 pub fn new(key: B256) -> Self {
17 // SAFETY: key is a B256 and so is exactly 32-bytes.
18 let key = unsafe { Nibbles::unpack_unchecked(key.as_slice()) };
19 Self { key, min_len: 0 }
20 }
21
22 /// Only match trie nodes whose path is at least this long.
23 ///
24 /// # Panics
25 ///
26 /// This method panics if `min_len` is greater than 64.
27 pub fn with_min_len(mut self, min_len: u8) -> Self {
28 debug_assert!(min_len <= 64);
29 self.min_len = min_len;
30 self
31 }
32
33 // A helper function for getting the largest prefix of the sub-trie which contains a particular
34 // target, based on its `min_len`.
35 //
36 // A target will only match nodes which share the target's prefix, where the target's prefix is
37 // the first `min_len` nibbles of its key. E.g. a target with `key` 0xabcd and `min_len` 2 will
38 // only match nodes with prefix 0xab.
39 //
40 // In general the target will only match within the sub-trie whose prefix is identical to the
41 // target's. However there is an exception:
42 //
43 // Given a trie with a node at 0xabc, there must be a branch at 0xab. A target with prefix 0xabc
44 // needs to match that node, but the branch at 0xab must be constructed order to know the node
45 // is at that path. Therefore the sub-trie prefix is the target prefix with a nibble truncated.
46 //
47 // For a target with an empty prefix (`min_len` of 0) we still use an empty sub-trie prefix;
48 // this will still construct the branch at the root node (if there is one). Targets with
49 // `min_len` of both 0 and 1 will therefore construct the root node, but only those with
50 // `min_len` of 0 will retain it.
51 #[inline]
52 fn sub_trie_prefix(&self) -> Nibbles {
53 let mut sub_trie_prefix = self.key;
54 sub_trie_prefix.truncate(self.min_len.saturating_sub(1) as usize);
55 sub_trie_prefix
56 }
57}
58
59impl From<B256> for Target {
60 fn from(key: B256) -> Self {
61 Self::new(key)
62 }
63}
64
65// A helper function which returns the first path following a sub-trie in lexicographical order.
66#[inline]
67fn sub_trie_upper_bound(sub_trie_prefix: &Nibbles) -> Option<Nibbles> {
68 increment_and_strip_trailing_zeros(sub_trie_prefix)
69}
70
71/// Describes a set of targets which all apply to a single sub-trie, ie a section of the overall
72/// trie whose nodes all share a prefix.
73pub(crate) struct SubTrieTargets<'a> {
74 /// The prefix which all nodes in the sub-trie share. This is also the first node in the trie
75 /// in lexicographic order.
76 pub(crate) prefix: Nibbles,
77 /// The targets belonging to this sub-trie. These will be sorted by their `key` field,
78 /// lexicographically.
79 pub(crate) targets: &'a [Target],
80 /// Will be true if at least one target in the set has a zero `min_len`.
81 ///
82 /// If this is true then `prefix.is_empty()`, though not necessarily vice-versa.
83 pub(crate) retain_root: bool,
84}
85
86impl<'a> SubTrieTargets<'a> {
87 // A helper function which returns the first path following a sub-trie in lexicographical order.
88 #[inline]
89 pub(crate) fn upper_bound(&self) -> Option<Nibbles> {
90 sub_trie_upper_bound(&self.prefix)
91 }
92}
93
94/// Given a set of [`Target`]s, returns an iterator over those same [`Target`]s chunked by the
95/// sub-tries they apply to within the overall trie.
96pub(crate) fn iter_sub_trie_targets<'a>(
97 targets: &'a mut [Target],
98) -> impl Iterator<Item = SubTrieTargets<'a>> {
99 // First sort by the sub-trie prefix of each target, falling back to the `min_len` in cases
100 // where the sub-trie prefixes are equal (to differentiate targets which match the root node and
101 // those which don't).
102 targets.sort_unstable_by(|a, b| {
103 a.sub_trie_prefix().cmp(&b.sub_trie_prefix()).then_with(|| a.min_len.cmp(&b.min_len))
104 });
105
106 // We now chunk targets, such that each chunk contains all targets belonging to the same
107 // sub-trie. We are taking advantage of the following properties:
108 //
109 // - The first target in the chunk has the shortest sub-trie prefix (see previous sorting step).
110 //
111 // - The upper bound of the first target in the chunk's sub-trie will therefore be the upper
112 // bound of the whole chunk.
113 // - For example, given a chunk with sub-trie prefixes [0x2, 0x2f, 0x2fa], the upper bounds
114 // will be [0x3, 0x3, 0x2fb]. Note that no target could match a trie node with path equal
115 // to or greater than 0x3.
116 //
117 // - If a target's sub-trie's prefix does not lie within the bounds of the current chunk, then
118 // that target must be the first target of the next chunk, lying in a separate sub-trie.
119 // - Example: given sub-trie prefixes of [0x2, 0x2fa, 0x4c, 0x4ce, 0x4e], we would end up
120 // with the following chunks:
121 // - [0x2, 0x2fa] w/ upper bound 0x3
122 // - [0x4c 0x4ce] w/ upper bound 0x4d
123 // - [0x4e] w/ upper bound 0x4f
124 let mut upper_bound = targets.first().and_then(|t| sub_trie_upper_bound(&t.sub_trie_prefix()));
125 let target_chunks = targets.chunk_by_mut(move |_, next| {
126 if let Some(some_upper_bound) = upper_bound {
127 let sub_trie_prefix = next.sub_trie_prefix();
128 let same_chunk = sub_trie_prefix < some_upper_bound;
129 if !same_chunk {
130 upper_bound = sub_trie_upper_bound(&sub_trie_prefix);
131 }
132 same_chunk
133 } else {
134 true
135 }
136 });
137
138 // Map the chunks to the return type. Within each chunk we want targets to be sorted by their
139 // key, as that will be the order they are checked by the `ProofCalculator`.
140 target_chunks.map(move |targets| {
141 let prefix = targets[0].sub_trie_prefix();
142 let retain_root = targets[0].min_len == 0;
143 targets.sort_unstable_by_key(|target| target.key);
144 SubTrieTargets { prefix, targets, retain_root }
145 })
146}
147
148#[cfg(test)]
149mod tests {
150 use super::*;
151
152 #[test]
153 fn test_iter_sub_trie_targets() {
154 // Helper to create nibbles from hex string (each character is a nibble)
155 let nibbles = |hex: &str| -> Nibbles {
156 if hex.is_empty() {
157 return Nibbles::new();
158 }
159 format!("0x{}", hex).parse().expect("valid nibbles hex string")
160 };
161
162 // Test cases: (input_targets, expected_output)
163 // Expected output format: Vec<(exp_prefix_hex, Vec<key_hex>)>
164 let test_cases = vec![
165 // Case 1: Empty targets
166 (vec![], vec![]),
167 // Case 2: Single target without min_len
168 (
169 vec![Target::new(B256::repeat_byte(0x20))],
170 vec![(
171 "",
172 vec!["2020202020202020202020202020202020202020202020202020202020202020"],
173 )],
174 ),
175 // Case 3: Multiple targets in same sub-trie (no min_len)
176 (
177 vec![Target::new(B256::repeat_byte(0x20)), Target::new(B256::repeat_byte(0x21))],
178 vec![(
179 "",
180 vec![
181 "2020202020202020202020202020202020202020202020202020202020202020",
182 "2121212121212121212121212121212121212121212121212121212121212121",
183 ],
184 )],
185 ),
186 // Case 4: Multiple targets in different sub-tries
187 (
188 vec![
189 Target::new(B256::repeat_byte(0x20)).with_min_len(2),
190 Target::new(B256::repeat_byte(0x40)).with_min_len(2),
191 ],
192 vec![
193 ("2", vec!["2020202020202020202020202020202020202020202020202020202020202020"]),
194 ("4", vec!["4040404040404040404040404040404040404040404040404040404040404040"]),
195 ],
196 ),
197 // Case 5: Three targets, two in same sub-trie, one separate
198 (
199 vec![
200 Target::new(B256::repeat_byte(0x20)).with_min_len(2),
201 Target::new(B256::repeat_byte(0x2f)).with_min_len(2),
202 Target::new(B256::repeat_byte(0x40)).with_min_len(2),
203 ],
204 vec![
205 (
206 "2",
207 vec![
208 "2020202020202020202020202020202020202020202020202020202020202020",
209 "2f2f2f2f2f2f2f2f2f2f2f2f2f2f2f2f2f2f2f2f2f2f2f2f2f2f2f2f2f2f2f2f",
210 ],
211 ),
212 ("4", vec!["4040404040404040404040404040404040404040404040404040404040404040"]),
213 ],
214 ),
215 // Case 6: Targets with different min_len values in same sub-trie
216 (
217 vec![
218 Target::new(B256::repeat_byte(0x20)).with_min_len(2),
219 Target::new(B256::repeat_byte(0x2f)).with_min_len(3),
220 ],
221 vec![(
222 "2",
223 vec![
224 "2020202020202020202020202020202020202020202020202020202020202020",
225 "2f2f2f2f2f2f2f2f2f2f2f2f2f2f2f2f2f2f2f2f2f2f2f2f2f2f2f2f2f2f2f2f",
226 ],
227 )],
228 ),
229 // Case 7: More complex chunking with multiple sub-tries
230 (
231 vec![
232 Target::new(B256::repeat_byte(0x20)).with_min_len(2),
233 Target::new(B256::repeat_byte(0x2f)).with_min_len(4),
234 Target::new(B256::repeat_byte(0x4c)).with_min_len(3),
235 Target::new(B256::repeat_byte(0x4c)).with_min_len(4),
236 Target::new(B256::repeat_byte(0x4e)).with_min_len(3),
237 ],
238 vec![
239 (
240 "2",
241 vec![
242 "2020202020202020202020202020202020202020202020202020202020202020",
243 "2f2f2f2f2f2f2f2f2f2f2f2f2f2f2f2f2f2f2f2f2f2f2f2f2f2f2f2f2f2f2f2f",
244 ],
245 ),
246 (
247 "4c",
248 vec![
249 "4c4c4c4c4c4c4c4c4c4c4c4c4c4c4c4c4c4c4c4c4c4c4c4c4c4c4c4c4c4c4c4c",
250 "4c4c4c4c4c4c4c4c4c4c4c4c4c4c4c4c4c4c4c4c4c4c4c4c4c4c4c4c4c4c4c4c",
251 ],
252 ),
253 (
254 "4e",
255 vec!["4e4e4e4e4e4e4e4e4e4e4e4e4e4e4e4e4e4e4e4e4e4e4e4e4e4e4e4e4e4e4e4e"],
256 ),
257 ],
258 ),
259 // Case 8: Min-len 1 should result in zero-length sub-trie prefix
260 (
261 vec![
262 Target::new(B256::repeat_byte(0x20)).with_min_len(1),
263 Target::new(B256::repeat_byte(0x40)).with_min_len(1),
264 ],
265 vec![(
266 "",
267 vec![
268 "2020202020202020202020202020202020202020202020202020202020202020",
269 "4040404040404040404040404040404040404040404040404040404040404040",
270 ],
271 )],
272 ),
273 // Case 9: Second target's sub-trie prefix is root
274 (
275 vec![
276 Target::new(B256::repeat_byte(0x20)).with_min_len(2),
277 Target::new(B256::repeat_byte(0x40)).with_min_len(1),
278 ],
279 vec![(
280 "",
281 vec![
282 "2020202020202020202020202020202020202020202020202020202020202020",
283 "4040404040404040404040404040404040404040404040404040404040404040",
284 ],
285 )],
286 ),
287 ];
288
289 for (i, (mut input_targets, expected)) in test_cases.into_iter().enumerate() {
290 let test_case = i + 1;
291 let sub_tries: Vec<_> = iter_sub_trie_targets(&mut input_targets).collect();
292
293 assert_eq!(
294 sub_tries.len(),
295 expected.len(),
296 "Test case {} failed: expected {} sub-tries, got {}",
297 test_case,
298 expected.len(),
299 sub_tries.len()
300 );
301
302 for (j, (sub_trie, (exp_prefix_hex, exp_keys))) in
303 sub_tries.iter().zip(expected.iter()).enumerate()
304 {
305 let exp_prefix = nibbles(exp_prefix_hex);
306
307 assert_eq!(
308 sub_trie.prefix, exp_prefix,
309 "Test case {} sub-trie {}: prefix mismatch",
310 test_case, j
311 );
312 assert_eq!(
313 sub_trie.targets.len(),
314 exp_keys.len(),
315 "Test case {} sub-trie {}: expected {} targets, got {}",
316 test_case,
317 j,
318 exp_keys.len(),
319 sub_trie.targets.len()
320 );
321
322 for (k, (target, exp_key_hex)) in
323 sub_trie.targets.iter().zip(exp_keys.iter()).enumerate()
324 {
325 let exp_key = nibbles(exp_key_hex);
326 assert_eq!(
327 target.key, exp_key,
328 "Test case {} sub-trie {} target {}: key mismatch",
329 test_case, j, k
330 );
331 }
332 }
333 }
334 }
335}