Skip to main content

reth_trie/proof_v2/
target.rs

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