reth_trie/proof_v2/target.rs
1use reth_trie_common::{Nibbles, ProofV2Target};
2
3// Returns the path of the already-revealed parent branch for a target. `None` means the target
4// needs the actual trie root, while `Some(Nibbles::new())` means the root branch is already
5// revealed.
6#[inline]
7pub(crate) fn known_parent_prefix(target: &ProofV2Target) -> Option<Nibbles> {
8 target.parent.path(target.key_nibbles)
9}
10
11// Returns the direct child of the known parent which contains the target. If there is no known
12// parent then the target requires the full trie.
13#[inline]
14fn target_child_prefix(target: &ProofV2Target) -> Nibbles {
15 target
16 .parent
17 .path_len()
18 .map_or_else(Nibbles::new, |parent_len| target.key_nibbles.slice(0..parent_len + 1))
19}
20
21/// Describes targets with the same already-revealed parent and the bounded range traversed to
22/// calculate their direct children.
23pub(crate) struct SubTrieTargets<'a> {
24 /// The first path traversed for these targets.
25 pub(crate) lower_bound: Nibbles,
26 /// The first path after the traversal range, or `None` if it extends through the end of the
27 /// trie.
28 pub(crate) upper_bound: Option<Nibbles>,
29 /// The path of the already-revealed parent branch. `None` means the actual trie root is
30 /// requested, while `Some(Nibbles::new())` means the root branch is already revealed.
31 pub(crate) parent_prefix: Option<Nibbles>,
32 /// The targets belonging to this sub-trie. These will be sorted by their `key` field,
33 /// lexicographically.
34 pub(crate) targets: &'a [ProofV2Target],
35}
36
37/// Given a set of [`ProofV2Target`]s, returns an iterator over those same [`ProofV2Target`]s
38/// grouped by their already-revealed parent. Each group traverses the bounded span from its first
39/// targeted direct child through its last targeted direct child.
40pub(crate) fn iter_sub_trie_targets(
41 targets: &mut [ProofV2Target],
42) -> impl Iterator<Item = SubTrieTargets<'_>> {
43 // Sort by parent context first so equal parents are contiguous, then by target key so the
44 // first and last targets determine the traversal bounds for the group. `None` and a known root
45 // parent remain distinct.
46 targets.sort_unstable_by(|a, b| {
47 known_parent_prefix(a)
48 .cmp(&known_parent_prefix(b))
49 .then_with(|| a.key_nibbles.cmp(&b.key_nibbles))
50 });
51
52 targets
53 .chunk_by_mut(|current, next| known_parent_prefix(current) == known_parent_prefix(next))
54 .map(|targets| {
55 let parent_prefix = known_parent_prefix(&targets[0]);
56 let lower_bound = target_child_prefix(&targets[0]);
57 let upper_bound = target_child_prefix(targets.last().expect("chunk is non-empty"))
58 .next_without_prefix();
59 SubTrieTargets { lower_bound, upper_bound, parent_prefix, targets }
60 })
61}
62
63#[cfg(test)]
64mod tests {
65 use super::*;
66 use alloy_primitives::B256;
67 use reth_trie_common::ProofV2TargetParent;
68
69 #[test]
70 fn test_iter_sub_trie_targets() {
71 // Helper to create nibbles from hex string (each character is a nibble)
72 let nibbles = |hex: &str| -> Nibbles {
73 if hex.is_empty() {
74 return Nibbles::new();
75 }
76 format!("0x{}", hex).parse().expect("valid nibbles hex string")
77 };
78
79 // Test cases: (input_targets, expected_output)
80 // Expected output format:
81 // Vec<(known_parent_prefix_hex, lower_bound_hex, upper_bound_hex, Vec<key_hex>)>
82 let test_cases = vec![
83 // Empty targets.
84 (vec![], vec![]),
85 // A root traversal stays unbounded and sorts its targets.
86 (
87 vec![
88 ProofV2Target::new(B256::repeat_byte(0x21)),
89 ProofV2Target::new(B256::repeat_byte(0x20)),
90 ],
91 vec![(
92 None,
93 "",
94 None,
95 vec![
96 "2020202020202020202020202020202020202020202020202020202020202020",
97 "2121212121212121212121212121212121212121212121212121212121212121",
98 ],
99 )],
100 ),
101 // Targets below children 0 and f of parent 2 span [20, 3).
102 (
103 vec![
104 ProofV2Target::new(B256::repeat_byte(0x20))
105 .with_parent(ProofV2TargetParent::new(1)),
106 ProofV2Target::new(B256::repeat_byte(0x2f))
107 .with_parent(ProofV2TargetParent::new(1)),
108 ProofV2Target::new(B256::repeat_byte(0x40))
109 .with_parent(ProofV2TargetParent::new(1)),
110 ],
111 vec![
112 (
113 Some("2"),
114 "20",
115 Some("3"),
116 vec![
117 "2020202020202020202020202020202020202020202020202020202020202020",
118 "2f2f2f2f2f2f2f2f2f2f2f2f2f2f2f2f2f2f2f2f2f2f2f2f2f2f2f2f2f2f2f2f",
119 ],
120 ),
121 (
122 Some("4"),
123 "40",
124 Some("41"),
125 vec!["4040404040404040404040404040404040404040404040404040404040404040"],
126 ),
127 ],
128 ),
129 // Nested parent paths remain separate groups.
130 (
131 vec![
132 ProofV2Target::new(B256::repeat_byte(0x20))
133 .with_parent(ProofV2TargetParent::new(1)),
134 ProofV2Target::new(B256::repeat_byte(0x2f))
135 .with_parent(ProofV2TargetParent::new(2)),
136 ],
137 vec![
138 (
139 Some("2"),
140 "20",
141 Some("21"),
142 vec!["2020202020202020202020202020202020202020202020202020202020202020"],
143 ),
144 (
145 Some("2f"),
146 "2f2",
147 Some("2f3"),
148 vec!["2f2f2f2f2f2f2f2f2f2f2f2f2f2f2f2f2f2f2f2f2f2f2f2f2f2f2f2f2f2f2f2f"],
149 ),
150 ],
151 ),
152 // A known-root span ending in child f is unbounded.
153 (
154 vec![
155 ProofV2Target::new(B256::repeat_byte(0x20))
156 .with_parent(ProofV2TargetParent::new(0)),
157 ProofV2Target::new(B256::repeat_byte(0xf0))
158 .with_parent(ProofV2TargetParent::new(0)),
159 ],
160 vec![(
161 Some(""),
162 "2",
163 None,
164 vec![
165 "2020202020202020202020202020202020202020202020202020202020202020",
166 "f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0",
167 ],
168 )],
169 ),
170 // Parent ordering can make traversal ranges move backwards (4 then 20).
171 (
172 vec![
173 ProofV2Target::new(B256::repeat_byte(0x20))
174 .with_parent(ProofV2TargetParent::new(1)),
175 ProofV2Target::new(B256::repeat_byte(0x40))
176 .with_parent(ProofV2TargetParent::new(0)),
177 ],
178 vec![
179 (
180 Some(""),
181 "4",
182 Some("5"),
183 vec!["4040404040404040404040404040404040404040404040404040404040404040"],
184 ),
185 (
186 Some("2"),
187 "20",
188 Some("21"),
189 vec!["2020202020202020202020202020202020202020202020202020202020202020"],
190 ),
191 ],
192 ),
193 // Root and root-parent targets are distinct despite sharing a prefix.
194 (
195 vec![
196 ProofV2Target::new(B256::repeat_byte(0x20)),
197 ProofV2Target::new(B256::repeat_byte(0x40))
198 .with_parent(ProofV2TargetParent::new(0)),
199 ],
200 vec![
201 (
202 None,
203 "",
204 None,
205 vec!["2020202020202020202020202020202020202020202020202020202020202020"],
206 ),
207 (
208 Some(""),
209 "4",
210 Some("5"),
211 vec!["4040404040404040404040404040404040404040404040404040404040404040"],
212 ),
213 ],
214 ),
215 ];
216
217 for (i, (mut input_targets, expected)) in test_cases.into_iter().enumerate() {
218 let actual = iter_sub_trie_targets(&mut input_targets)
219 .map(|sub_trie| {
220 (
221 sub_trie.parent_prefix,
222 sub_trie.lower_bound,
223 sub_trie.upper_bound,
224 sub_trie
225 .targets
226 .iter()
227 .map(|target| target.key_nibbles)
228 .collect::<Vec<_>>(),
229 )
230 })
231 .collect::<Vec<_>>();
232 let expected = expected
233 .into_iter()
234 .map(|(parent_prefix, lower_bound, upper_bound, keys)| {
235 (
236 parent_prefix.map(nibbles),
237 nibbles(lower_bound),
238 upper_bound.map(nibbles),
239 keys.into_iter().map(nibbles).collect::<Vec<_>>(),
240 )
241 })
242 .collect::<Vec<_>>();
243
244 assert_eq!(actual, expected, "test case {}", i + 1);
245 }
246 }
247}