1use crate::{
23 merkle::{Family, Location},
24 qmdb::Error,
25};
26use commonware_cryptography::Digest;
27use core::iter;
28use std::sync::{Arc, Weak};
29
30#[derive(Clone, Copy, Debug)]
32pub struct Commitment<F: Family, D: Digest> {
33 pub size: Location<F>,
35 pub root: D,
37}
38
39impl<F: Family, D: Digest> Commitment<F, D> {
40 pub(crate) const fn new(size: Location<F>, root: D) -> Self {
42 Self { size, root }
43 }
44}
45
46impl<F: Family, D: Digest> PartialEq for Commitment<F, D> {
49 fn eq(&self, other: &Self) -> bool {
50 self.size == other.size && self.root == other.root
51 }
52}
53
54impl<F: Family, D: Digest> Eq for Commitment<F, D> {}
55
56#[derive(Clone)]
58pub struct AncestorBounds<F: Family, D: Digest> {
59 pub floor: Location<F>,
61 pub state: Commitment<F, D>,
63}
64
65#[derive(Clone)]
67pub struct Bounds<F: Family, D: Digest> {
68 pub base: Commitment<F, D>,
70 pub db: Commitment<F, D>,
75 pub tip: Commitment<F, D>,
77 pub ancestors: Vec<AncestorBounds<F, D>>,
79 pub inactivity_floor: Location<F>,
81}
82
83impl<F: Family, D: Digest> Bounds<F, D> {
84 pub(crate) const fn from_db(state: Commitment<F, D>, inactivity_floor: Location<F>) -> Self {
88 Self {
89 base: state,
90 db: state,
91 tip: state,
92 ancestors: Vec::new(),
93 inactivity_floor,
94 }
95 }
96
97 pub(crate) fn validate_apply_to(
99 &self,
100 current: Commitment<F, D>,
101 current_floor: Location<F>,
102 ) -> Result<(), Error<F>> {
103 validate_batch_applicable(current, self.db, &self.ancestors)?;
104 validate_commit_floors(
105 current_floor,
106 current.size,
107 &self.ancestors,
108 self.inactivity_floor,
109 self.tip
110 .size
111 .checked_sub(1)
112 .expect("merkleized batch includes a commit"),
113 )
114 }
115}
116
117pub(crate) fn ancestors<T, P>(
121 parent: Option<Weak<T>>,
122 mut parent_of: P,
123) -> impl Iterator<Item = Arc<T>>
124where
125 P: for<'a> FnMut(&'a T) -> Option<&'a Weak<T>>,
126{
127 let mut next = parent.as_ref().and_then(Weak::upgrade);
128 iter::from_fn(move || {
129 let batch = next.take()?;
130 next = parent_of(&batch).and_then(Weak::upgrade);
131 Some(batch)
132 })
133}
134
135pub(crate) fn parent_and_ancestors<T, P, I>(
137 parent: Option<&Arc<T>>,
138 mut ancestors_of: P,
139) -> impl Iterator<Item = Arc<T>> + use<T, P, I>
140where
141 P: FnMut(&Arc<T>) -> I,
142 I: IntoIterator<Item = Arc<T>>,
143{
144 parent.cloned().into_iter().flat_map(move |parent| {
145 let ancestors = ancestors_of(&parent);
146 iter::once(parent).chain(ancestors)
147 })
148}
149
150pub(crate) fn collect_ancestor_bounds<T, F, D, I, L, C>(
152 ancestors: I,
153 floor: L,
154 state: C,
155) -> Vec<AncestorBounds<F, D>>
156where
157 F: Family,
158 D: Digest,
159 I: IntoIterator<Item = Arc<T>>,
160 L: Fn(&T) -> Location<F>,
161 C: Fn(&T) -> Commitment<F, D>,
162{
163 ancestors
164 .into_iter()
165 .map(|batch| AncestorBounds {
166 floor: floor(&batch),
167 state: state(&batch),
168 })
169 .collect()
170}
171
172pub(crate) fn effective_boundary<F: Family, D: Digest>(
175 inherited: Commitment<F, D>,
176 oldest_live_base: Option<Commitment<F, D>>,
177) -> Commitment<F, D> {
178 oldest_live_base
179 .filter(|base| base.size > inherited.size)
180 .unwrap_or(inherited)
181}
182
183pub(crate) fn validate_batch_applicable<F: Family, D: Digest>(
189 current: Commitment<F, D>,
190 batch_db: Commitment<F, D>,
191 ancestors: &[AncestorBounds<F, D>],
192) -> Result<(), Error<F>> {
193 if current == batch_db || ancestors.iter().any(|ancestor| ancestor.state == current) {
196 return Ok(());
197 }
198
199 Err(Error::StaleBatch)
200}
201
202pub(crate) fn validate_commit_floors<F: Family, D: Digest>(
208 starting_floor: Location<F>,
209 db_size: Location<F>,
210 ancestors: &[AncestorBounds<F, D>],
211 tip_floor: Location<F>,
212 tip_commit_loc: Location<F>,
213) -> Result<(), Error<F>> {
214 let mut prev_floor = starting_floor;
215 for ancestor in ancestors.iter().rev() {
216 if ancestor.state.size <= db_size {
217 continue;
218 }
219
220 let ancestor_commit_loc = ancestor.state.size - 1;
221 if ancestor.floor < prev_floor {
222 return Err(Error::FloorRegressed(ancestor.floor, prev_floor));
223 }
224 if ancestor.floor > ancestor_commit_loc {
225 return Err(Error::FloorBeyondSize(ancestor.floor, ancestor_commit_loc));
226 }
227 prev_floor = ancestor.floor;
228 }
229
230 if tip_floor < prev_floor {
231 return Err(Error::FloorRegressed(tip_floor, prev_floor));
232 }
233 if tip_floor > tip_commit_loc {
234 return Err(Error::FloorBeyondSize(tip_floor, tip_commit_loc));
235 }
236 Ok(())
237}
238
239#[cfg(test)]
240mod tests {
241 use super::*;
242 use crate::merkle::mmr;
243 use commonware_cryptography::sha256;
244 use std::sync::{Arc, Weak};
245
246 type F = mmr::Family;
247 type D = sha256::Digest;
248
249 struct TestBatch {
250 id: u8,
251 bounds: Bounds<F, D>,
252 parent: Option<Weak<Self>>,
253 }
254
255 const fn loc(n: u64) -> Location<F> {
256 Location::new(n)
257 }
258
259 fn state(size: u64, marker: u8) -> Commitment<F, D> {
260 Commitment::new(Location::new(size), D::from([marker; 32]))
261 }
262
263 fn ancestor(floor: Location<F>, end: u64, marker: u8) -> AncestorBounds<F, D> {
264 AncestorBounds {
265 floor,
266 state: state(end, marker),
267 }
268 }
269
270 #[test]
271 fn validate_batch_applicable_accepts_valid_boundaries() {
272 let ancestors = vec![ancestor(loc(10), 12, 12), ancestor(loc(14), 16, 16)];
273 assert!(validate_batch_applicable::<F, D>(state(10, 1), state(10, 1), &ancestors).is_ok());
275 assert!(validate_batch_applicable::<F, D>(state(16, 16), state(10, 1), &ancestors).is_ok());
277 }
278
279 #[test]
280 fn validate_batch_applicable_rejects_stale_batch() {
281 let ancestors = vec![ancestor(loc(10), 12, 12), ancestor(loc(14), 16, 16)];
282 let result = validate_batch_applicable::<F, D>(state(18, 18), state(10, 1), &ancestors);
283 assert!(matches!(result, Err(Error::StaleBatch)));
284 }
285
286 #[test]
287 fn validate_batch_applicable_rejects_equal_size_sibling() {
288 let ancestors = vec![ancestor(loc(14), 16, 16)];
289 let result = validate_batch_applicable::<F, D>(state(16, 99), state(10, 1), &ancestors);
290 assert!(matches!(result, Err(Error::StaleBatch)));
291 }
292
293 #[test]
294 fn ancestors_iterates_parent_first() {
295 let grandparent = Arc::new(TestBatch {
296 id: 1,
297 bounds: Bounds {
298 base: state(0, 0),
299 db: state(0, 0),
300 tip: state(5, 5),
301 ancestors: Vec::new(),
302 inactivity_floor: loc(3),
303 },
304 parent: None,
305 });
306 let parent = Arc::new(TestBatch {
307 id: 2,
308 bounds: Bounds {
309 base: state(5, 5),
310 db: state(0, 0),
311 tip: state(7, 7),
312 ancestors: vec![ancestor(loc(3), 5, 5)],
313 inactivity_floor: loc(6),
314 },
315 parent: Some(Arc::downgrade(&grandparent)),
316 });
317
318 let ids: Vec<_> = ancestors(Some(Arc::downgrade(&parent)), |batch| batch.parent.as_ref())
319 .map(|batch| batch.id)
320 .collect();
321
322 assert_eq!(ids, vec![2, 1]);
323 }
324
325 #[test]
326 fn collect_ancestor_bounds_preserves_pairing_and_order() {
327 let parent = Arc::new(TestBatch {
328 id: 1,
329 bounds: Bounds {
330 base: state(0, 0),
331 db: state(0, 0),
332 tip: state(12, 12),
333 ancestors: Vec::new(),
334 inactivity_floor: loc(10),
335 },
336 parent: None,
337 });
338 let grandparent = Arc::new(TestBatch {
339 id: 2,
340 bounds: Bounds {
341 base: state(0, 0),
342 db: state(0, 0),
343 tip: state(8, 8),
344 ancestors: Vec::new(),
345 inactivity_floor: loc(6),
346 },
347 parent: None,
348 });
349
350 let bounds = collect_ancestor_bounds(
351 vec![Arc::clone(&parent), Arc::clone(&grandparent)],
352 |batch| batch.bounds.inactivity_floor,
353 |batch| state(*batch.bounds.tip.size, *batch.bounds.tip.size as u8),
354 );
355
356 assert_eq!(bounds.len(), 2);
357 assert_eq!(bounds[0].floor, loc(10));
358 assert_eq!(bounds[0].state, state(12, 12));
359 assert_eq!(bounds[1].floor, loc(6));
360 assert_eq!(bounds[1].state, state(8, 8));
361 }
362
363 #[test]
364 fn bounds_validates_apply_to_current_state() {
365 let bounds = Bounds::<F, D> {
366 base: state(10, 1),
367 db: state(10, 1),
368 tip: state(14, 14),
369 ancestors: vec![ancestor(loc(10), 12, 12)],
370 inactivity_floor: loc(11),
371 };
372 assert!(bounds.validate_apply_to(state(10, 1), loc(9)).is_ok());
373
374 let result = bounds.validate_apply_to(state(11, 11), loc(9));
375 assert!(matches!(result, Err(Error::StaleBatch)));
376 }
377
378 #[test]
379 fn validate_commit_floors_accepts_monotonic_chain() {
380 let ancestors = vec![ancestor(loc(6), 7, 7), ancestor(loc(4), 5, 5)];
381 assert!(
382 validate_commit_floors::<F, D>(loc(2), loc(1), &ancestors, loc(8), loc(9),).is_ok()
383 );
384 }
385
386 #[test]
387 fn validate_commit_floors_skips_committed_ancestors() {
388 let ancestors = vec![ancestor(loc(1), 7, 7), ancestor(loc(1), 5, 5)];
389 assert!(
390 validate_commit_floors::<F, D>(loc(6), loc(7), &ancestors, loc(8), loc(9),).is_ok()
391 );
392 }
393
394 #[test]
395 fn validate_commit_floors_rejects_ancestor_regression() {
396 let ancestors = vec![ancestor(loc(6), 7, 7), ancestor(loc(3), 5, 5)];
397 let result = validate_commit_floors::<F, D>(loc(4), loc(1), &ancestors, loc(8), loc(9));
398 assert!(matches!(
399 result,
400 Err(Error::FloorRegressed(floor, previous)) if floor == loc(3) && previous == loc(4)
401 ));
402 }
403
404 #[test]
405 fn validate_commit_floors_rejects_ancestor_floor_beyond_commit() {
406 let ancestors = vec![ancestor(loc(8), 7, 7), ancestor(loc(4), 5, 5)];
407 let result = validate_commit_floors::<F, D>(loc(2), loc(1), &ancestors, loc(9), loc(9));
408 assert!(matches!(
409 result,
410 Err(Error::FloorBeyondSize(floor, commit)) if floor == loc(8) && commit == loc(6)
411 ));
412 }
413
414 #[test]
415 fn validate_commit_floors_rejects_tip_regression() {
416 let ancestors = vec![ancestor(loc(4), 5, 5)];
417 let result = validate_commit_floors::<F, D>(loc(2), loc(1), &ancestors, loc(3), loc(9));
418 assert!(matches!(
419 result,
420 Err(Error::FloorRegressed(floor, previous)) if floor == loc(3) && previous == loc(4)
421 ));
422 }
423
424 #[test]
425 fn validate_commit_floors_rejects_tip_floor_beyond_commit() {
426 let ancestors = vec![ancestor(loc(4), 5, 5)];
427 let result = validate_commit_floors::<F, D>(loc(2), loc(1), &ancestors, loc(10), loc(9));
428 assert!(matches!(
429 result,
430 Err(Error::FloorBeyondSize(floor, commit)) if floor == loc(10) && commit == loc(9)
431 ));
432 }
433}