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}