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