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 below one direct child of an already-revealed parent and the bounded range
22/// traversed to calculate their proofs.
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 the direct child of their already-revealed parent. Targets without a known parent
39/// remain grouped together to calculate the root. A group never traverses another child of a
40/// revealed parent.
41pub(crate) fn iter_sub_trie_targets(
42 targets: &mut [ProofV2Target],
43) -> impl Iterator<Item = SubTrieTargets<'_>> {
44 // Sort by parent context first so equal parents are contiguous, then by target key so targets
45 // below the same direct child are contiguous. `None` and a known root 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 // A revealed sibling can contain updates that are not present in the database view.
53 targets
54 .chunk_by_mut(|current, next| target_child_prefix(current) == target_child_prefix(next))
55 .map(|targets| {
56 let parent_prefix = known_parent_prefix(&targets[0]);
57 let lower_bound = target_child_prefix(&targets[0]);
58 let upper_bound = lower_bound.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 use separate ranges.
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("21"),
116 vec!["2020202020202020202020202020202020202020202020202020202020202020"],
117 ),
118 (
119 Some("2"),
120 "2f",
121 Some("3"),
122 vec!["2f2f2f2f2f2f2f2f2f2f2f2f2f2f2f2f2f2f2f2f2f2f2f2f2f2f2f2f2f2f2f2f"],
123 ),
124 (
125 Some("4"),
126 "40",
127 Some("41"),
128 vec!["4040404040404040404040404040404040404040404040404040404040404040"],
129 ),
130 ],
131 ),
132 // Nested parent paths remain separate groups.
133 (
134 vec![
135 ProofV2Target::new(B256::repeat_byte(0x20))
136 .with_parent(ProofV2TargetParent::new(1)),
137 ProofV2Target::new(B256::repeat_byte(0x2f))
138 .with_parent(ProofV2TargetParent::new(2)),
139 ],
140 vec![
141 (
142 Some("2"),
143 "20",
144 Some("21"),
145 vec!["2020202020202020202020202020202020202020202020202020202020202020"],
146 ),
147 (
148 Some("2f"),
149 "2f2",
150 Some("2f3"),
151 vec!["2f2f2f2f2f2f2f2f2f2f2f2f2f2f2f2f2f2f2f2f2f2f2f2f2f2f2f2f2f2f2f2f"],
152 ),
153 ],
154 ),
155 // A known-root child f range is unbounded.
156 (
157 vec![
158 ProofV2Target::new(B256::repeat_byte(0x20))
159 .with_parent(ProofV2TargetParent::new(0)),
160 ProofV2Target::new(B256::repeat_byte(0xf0))
161 .with_parent(ProofV2TargetParent::new(0)),
162 ],
163 vec![
164 (
165 Some(""),
166 "2",
167 Some("3"),
168 vec!["2020202020202020202020202020202020202020202020202020202020202020"],
169 ),
170 (
171 Some(""),
172 "f",
173 None,
174 vec!["f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0"],
175 ),
176 ],
177 ),
178 // Parent ordering can make traversal ranges move backwards (4 then 20).
179 (
180 vec![
181 ProofV2Target::new(B256::repeat_byte(0x20))
182 .with_parent(ProofV2TargetParent::new(1)),
183 ProofV2Target::new(B256::repeat_byte(0x40))
184 .with_parent(ProofV2TargetParent::new(0)),
185 ],
186 vec![
187 (
188 Some(""),
189 "4",
190 Some("5"),
191 vec!["4040404040404040404040404040404040404040404040404040404040404040"],
192 ),
193 (
194 Some("2"),
195 "20",
196 Some("21"),
197 vec!["2020202020202020202020202020202020202020202020202020202020202020"],
198 ),
199 ],
200 ),
201 // Root and root-parent targets are distinct despite sharing a prefix.
202 (
203 vec![
204 ProofV2Target::new(B256::repeat_byte(0x20)),
205 ProofV2Target::new(B256::repeat_byte(0x40))
206 .with_parent(ProofV2TargetParent::new(0)),
207 ],
208 vec![
209 (
210 None,
211 "",
212 None,
213 vec!["2020202020202020202020202020202020202020202020202020202020202020"],
214 ),
215 (
216 Some(""),
217 "4",
218 Some("5"),
219 vec!["4040404040404040404040404040404040404040404040404040404040404040"],
220 ),
221 ],
222 ),
223 ];
224
225 for (i, (mut input_targets, expected)) in test_cases.into_iter().enumerate() {
226 let actual = iter_sub_trie_targets(&mut input_targets)
227 .map(|sub_trie| {
228 (
229 sub_trie.parent_prefix,
230 sub_trie.lower_bound,
231 sub_trie.upper_bound,
232 sub_trie
233 .targets
234 .iter()
235 .map(|target| target.key_nibbles)
236 .collect::<Vec<_>>(),
237 )
238 })
239 .collect::<Vec<_>>();
240 let expected = expected
241 .into_iter()
242 .map(|(parent_prefix, lower_bound, upper_bound, keys)| {
243 (
244 parent_prefix.map(nibbles),
245 nibbles(lower_bound),
246 upper_bound.map(nibbles),
247 keys.into_iter().map(nibbles).collect::<Vec<_>>(),
248 )
249 })
250 .collect::<Vec<_>>();
251
252 assert_eq!(actual, expected, "test case {}", i + 1);
253 }
254 }
255}