1use crate::{
45 index::{Cursor, Unordered as Index},
46 journal::{
47 contiguous::{Contiguous, Mutable},
48 Error as JournalError,
49 },
50 merkle::{
51 hasher::{Hasher as MerkleHasher, Standard as StandardHasher},
52 Bagging, Family, Location,
53 },
54 qmdb::operation::Operation,
55};
56use commonware_codec::Encode;
57use commonware_cryptography::Hasher;
58use commonware_utils::{cache::Clock, NZUsize};
59use core::num::NonZeroUsize;
60use futures::{pin_mut, StreamExt as _};
61use thiserror::Error;
62
63pub mod any;
64pub mod batch_chain;
65pub(crate) mod bitmap;
66pub(crate) mod compact;
67#[cfg(test)]
68mod conformance;
69pub mod current;
70pub mod immutable;
71pub mod keyless;
72mod metrics;
73pub mod operation;
74pub mod store;
75pub mod sync;
76pub mod verify;
77
78pub use verify::{
79 create_multi_proof, create_proof_store, verify_multi_proof, verify_proof,
80 verify_proof_and_extract_digests, verify_proof_and_pinned_nodes,
81};
82
83pub(crate) const ROOT_BAGGING: Bagging = Bagging::BackwardFold;
85
86pub const fn hasher<H: Hasher>() -> StandardHasher<H> {
88 StandardHasher::new(ROOT_BAGGING)
89}
90
91fn single_operation_root<F: Family, H: Hasher>(operation: &impl Encode) -> H::Digest {
96 let hasher = hasher::<H>();
97 let leaf = MerkleHasher::<F>::leaf_digest(
98 &hasher,
99 F::location_to_position(Location::new(0)),
100 &operation.encode(),
101 );
102 MerkleHasher::<F>::root(&hasher, Location::new(1), 0, [&leaf])
103 .expect("a single-leaf Merkle root is always valid")
104}
105
106pub(crate) async fn find_inactivity_floor_at<F, R>(
119 reader: &R,
120 op_count: Location<F>,
121 floor_of: impl Fn(&R::Item) -> Option<Location<F>>,
122) -> Result<Location<F>, Error<F>>
123where
124 F: Family,
125 R: Contiguous,
126{
127 let Some(last_op) = op_count.checked_sub(1) else {
128 return Err(Error::HistoricalFloorPruned(op_count));
129 };
130 let last_op = *last_op;
131 let bounds = reader.bounds();
132 if last_op < bounds.start {
133 return Err(JournalError::ItemPruned(last_op).into());
134 }
135
136 let op = reader.read(last_op).await?;
137 let floor = floor_of(&op).ok_or(Error::HistoricalFloorPruned(op_count))?;
138 if floor > Location::new(last_op) {
139 return Err(Error::DataCorrupted(
140 "inactivity floor exceeds commit location",
141 ));
142 }
143 Ok(floor)
144}
145
146pub(crate) async fn inactive_peaks_at<F, R>(
148 reader: &R,
149 op_count: Location<F>,
150 floor_of: impl Fn(&R::Item) -> Option<Location<F>>,
151) -> Result<usize, Error<F>>
152where
153 F: Family,
154 R: Contiguous,
155{
156 if op_count == Location::new(0) {
157 return Ok(0);
158 }
159
160 let floor = find_inactivity_floor_at::<F, _>(reader, op_count, floor_of).await?;
161 Ok(F::inactive_peaks(F::location_to_position(op_count), floor))
162}
163
164#[derive(Error, Debug)]
166pub enum Error<F: Family> {
167 #[error("data corrupted: {0}")]
168 DataCorrupted(&'static str),
169
170 #[error("merkle error: {0}")]
171 Merkle(#[from] crate::merkle::Error<F>),
172
173 #[error("metadata error: {0}")]
174 Metadata(#[from] crate::metadata::Error),
175
176 #[error("journal error: {0}")]
177 Journal(#[from] crate::journal::Error),
178
179 #[error("runtime error: {0}")]
180 Runtime(#[from] commonware_runtime::Error),
181
182 #[error("operation pruned: {0}")]
183 OperationPruned(Location<F>),
184
185 #[error("key not found")]
187 KeyNotFound,
188
189 #[error("key exists")]
191 KeyExists,
192
193 #[error("unexpected data at location: {0}")]
194 UnexpectedData(Location<F>),
195
196 #[error("location out of bounds: {0} >= {1}")]
197 LocationOutOfBounds(Location<F>, Location<F>),
198
199 #[error("prune location {0} beyond minimum required location {1}")]
200 PruneBeyondMinRequired(Location<F>, Location<F>),
201
202 #[error(
206 "stale batch: db has {db_size} ops, batch requires {batch_db_size}, {batch_base_size}, or an ancestor boundary"
207 )]
208 StaleBatch {
209 db_size: u64,
210 batch_db_size: u64,
211 batch_base_size: u64,
212 },
213
214 #[error("floor regressed: batch floor {0} < current floor {1}")]
216 FloorRegressed(Location<F>, Location<F>),
217
218 #[error("floor beyond commit location: floor {0} > commit loc {1}")]
222 FloorBeyondSize(Location<F>, Location<F>),
223
224 #[error("historical floor pruned for size: {0}")]
233 HistoricalFloorPruned(Location<F>),
234}
235
236impl<F: Family> From<crate::journal::authenticated::Error<F>> for Error<F> {
237 fn from(e: crate::journal::authenticated::Error<F>) -> Self {
238 match e {
239 crate::journal::authenticated::Error::Journal(j) => Self::Journal(j),
240 crate::journal::authenticated::Error::Merkle(m) => Self::Merkle(m),
241 }
242 }
243}
244
245const SNAPSHOT_READ_BUFFER_SIZE: NonZeroUsize = NZUsize!(1 << 16);
248
249pub(super) async fn build_snapshot_from_log<F, C, I, Fn>(
258 inactivity_floor_loc: crate::merkle::Location<F>,
259 reader: &C,
260 snapshot: &mut I,
261 cache_size: Option<NonZeroUsize>,
262 mut callback: Fn,
263) -> Result<usize, Error<F>>
264where
265 F: crate::merkle::Family,
266 C: Contiguous<Item: Operation<F>>,
267 I: Index<Value = crate::merkle::Location<F>>,
268 Fn: FnMut(bool, Option<crate::merkle::Location<F>>),
269{
270 let bounds = reader.bounds();
271 let stream = reader
272 .replay(*inactivity_floor_loc, SNAPSHOT_READ_BUFFER_SIZE)
273 .await?;
274 pin_mut!(stream);
275 let last_commit_loc = bounds.end.saturating_sub(1);
276
277 let mut cache = cache_size.map(Clock::<u64, <C::Item as Operation<F>>::Key>::new);
281
282 let mut active_keys: usize = 0;
283 while let Some(result) = stream.next().await {
284 let (loc, op) = result?;
285 if let Some(key) = op.key() {
286 if op.is_delete() {
287 let old_loc = delete_key(snapshot, reader, key, cache.as_mut()).await?;
288 callback(false, old_loc);
289 if old_loc.is_some() {
290 active_keys -= 1;
291 }
292 } else if op.is_update() {
293 let new_loc = crate::merkle::Location::new(loc);
294 let old_loc = update_key(snapshot, reader, key, new_loc, cache.as_mut()).await?;
295 callback(true, old_loc);
296 if old_loc.is_none() {
297 active_keys += 1;
298 }
299
300 if let Some(cache) = cache.as_mut() {
302 cache.put(loc, key.clone());
303 }
304 }
305 } else if op.has_floor().is_some() {
306 callback(loc == last_commit_loc, None);
307 }
308 }
309
310 Ok(active_keys)
311}
312
313async fn delete_key<F, I, R>(
316 snapshot: &mut I,
317 reader: &R,
318 key: &<R::Item as Operation<F>>::Key,
319 cache: Option<&mut Clock<u64, <R::Item as Operation<F>>::Key>>,
320) -> Result<Option<Location<F>>, Error<F>>
321where
322 F: Family,
323 I: Index<Value = Location<F>>,
324 R: Contiguous,
325 R::Item: Operation<F>,
326{
327 let Some(mut cursor) = snapshot.get_mut(key) else {
329 return Ok(None);
330 };
331
332 let Some(loc) = find_update_op::<F, _>(reader, &mut cursor, key, cache).await? else {
334 return Ok(None);
335 };
336 cursor.delete();
337
338 Ok(Some(loc))
339}
340
341async fn update_key<F, I, R>(
343 snapshot: &mut I,
344 reader: &R,
345 key: &<R::Item as Operation<F>>::Key,
346 new_loc: Location<F>,
347 cache: Option<&mut Clock<u64, <R::Item as Operation<F>>::Key>>,
348) -> Result<Option<Location<F>>, Error<F>>
349where
350 F: Family,
351 I: Index<Value = Location<F>>,
352 R: Contiguous,
353 R::Item: Operation<F>,
354{
355 let Some(mut cursor) = snapshot.get_mut_or_insert(key, new_loc) else {
358 return Ok(None);
359 };
360
361 if let Some(loc) = find_update_op::<F, _>(reader, &mut cursor, key, cache).await? {
363 assert!(new_loc > loc);
364 cursor.update(new_loc);
365 return Ok(Some(loc));
366 }
367
368 cursor.insert(new_loc);
370
371 Ok(None)
372}
373
374async fn find_update_op<F, R>(
377 reader: &R,
378 cursor: &mut impl Cursor<Value = Location<F>>,
379 key: &<R::Item as Operation<F>>::Key,
380 mut cache: Option<&mut Clock<u64, <R::Item as Operation<F>>::Key>>,
381) -> Result<Option<Location<F>>, Error<F>>
382where
383 F: Family,
384 R: Contiguous,
385 R::Item: Operation<F>,
386{
387 while let Some(&loc) = cursor.next() {
388 let matches = if let Some(k) = cache.as_deref().and_then(|c| c.get(&*loc)) {
390 *k == *key
391 } else {
392 let op = reader.read(*loc).await?;
393 let k = op.key().expect("operation without key");
394 let matches = *k == *key;
395 if let Some(cache) = cache.as_deref_mut() {
396 cache.put(*loc, k.clone());
397 }
398 matches
399 };
400 if matches {
401 return Ok(Some(loc));
402 }
403 }
404
405 Ok(None)
406}
407
408fn update_known_loc<F: Family, I: Index<Value = Location<F>>>(
415 snapshot: &mut I,
416 key: &[u8],
417 old_loc: Location<F>,
418 new_loc: Location<F>,
419) {
420 let mut cursor = snapshot.get_mut(key).expect("key should be known to exist");
421 assert!(
422 cursor.find(|&loc| *loc == old_loc),
423 "known key with given old_loc should have been found"
424 );
425 cursor.update(new_loc);
426}
427
428fn delete_known_loc<F: Family, I: Index<Value = Location<F>>>(
435 snapshot: &mut I,
436 key: &[u8],
437 old_loc: Location<F>,
438) {
439 let mut cursor = snapshot.get_mut(key).expect("key should be known to exist");
440 assert!(
441 cursor.find(|&loc| *loc == old_loc),
442 "known key with given old_loc should have been found"
443 );
444 cursor.delete();
445}
446
447pub(crate) struct FloorHelper<
449 'a,
450 F: Family,
451 I: Index<Value = Location<F>>,
452 C: Mutable<Item: Operation<F>>,
453> {
454 pub snapshot: &'a mut I,
455 pub log: &'a mut C,
456}
457
458impl<F, I, C> FloorHelper<'_, F, I, C>
459where
460 F: Family,
461 I: Index<Value = Location<F>>,
462 C: Mutable<Item: Operation<F>>,
463{
464 async fn move_op_if_active(
468 &mut self,
469 op: C::Item,
470 old_loc: Location<F>,
471 ) -> Result<bool, Error<F>> {
472 let Some(key) = op.key() else {
473 return Ok(false); };
475
476 {
478 let Some(mut cursor) = self.snapshot.get_mut(key) else {
479 return Ok(false);
480 };
481 if !cursor.find(|&loc| loc == old_loc) {
482 return Ok(false);
483 }
484
485 cursor.update(Location::<F>::new(self.log.bounds().end));
487 }
488
489 self.log.append(&op).await?;
491
492 Ok(true)
493 }
494
495 async fn raise_floor(
505 &mut self,
506 mut inactivity_floor_loc: Location<F>,
507 ) -> Result<Location<F>, Error<F>> {
508 let tip_loc: Location<F> = Location::new(self.log.bounds().end);
509 loop {
510 assert!(
511 *inactivity_floor_loc < tip_loc,
512 "no active operations above the inactivity floor"
513 );
514 let old_loc = inactivity_floor_loc;
515 inactivity_floor_loc += 1;
516 let op = self.log.read(*old_loc).await?;
517 if self.move_op_if_active(op, old_loc).await? {
518 return Ok(inactivity_floor_loc);
519 }
520 }
521 }
522}