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}