1use crate::{
56 index::{
57 Cursor, Unordered as Index,
58 partitioned::{PartitionRange, Partitioned},
59 },
60 journal::{
61 Error as JournalError,
62 contiguous::{Contiguous, Mutable},
63 },
64 merkle::{
65 Bagging, Family, Location,
66 hasher::{Hasher as MerkleHasher, Standard as StandardHasher},
67 },
68 qmdb::operation::{Floored, Operation},
69 translator::Translator,
70};
71use commonware_codec::Encode;
72use commonware_cryptography::Hasher;
73use commonware_runtime::{ReadOptions, Spawner};
74use commonware_utils::{
75 bitmap::{Atomic, BitMap},
76 cache::Clock,
77 channel::mpsc,
78};
79use core::{num::NonZeroUsize, ops::Range};
80use futures::{StreamExt as _, future::join_all, pin_mut};
81use std::sync::Arc;
82use thiserror::Error;
83
84pub mod any;
85pub mod batch_chain;
86pub(crate) mod bitmap;
87pub(crate) mod compact;
88#[cfg(test)]
89mod conformance;
90pub mod current;
91pub mod immutable;
92pub mod keyless;
93mod metrics;
94pub mod operation;
95pub mod store;
96pub mod sync;
97pub mod verify;
98
99pub use verify::{
100 create_multi_proof, create_proof_store, verify_multi_proof, verify_proof,
101 verify_proof_and_extract_digests, verify_proof_and_pinned_nodes,
102};
103
104pub(crate) const ROOT_BAGGING: Bagging = Bagging::BackwardFold;
106
107pub const fn hasher<H: Hasher>() -> StandardHasher<H> {
109 StandardHasher::new(ROOT_BAGGING)
110}
111
112fn single_operation_root<F: Family, H: Hasher>(operation: &impl Encode) -> H::Digest {
117 let hasher = hasher::<H>();
118 let leaf = MerkleHasher::<F>::leaf_digest(
119 &hasher,
120 F::location_to_position(Location::new(0)),
121 &operation.encode(),
122 );
123 MerkleHasher::<F>::root(&hasher, Location::new(1), 0, [&leaf])
124 .expect("a single-leaf Merkle root is always valid")
125}
126
127pub(crate) async fn find_inactivity_floor_at<F, R>(
140 reader: &R,
141 op_count: Location<F>,
142) -> Result<Location<F>, Error<F>>
143where
144 F: Family,
145 R: Contiguous<Item: Floored<F>>,
146{
147 let Some(last_op) = op_count.checked_sub(1) else {
148 return Err(Error::HistoricalFloorPruned(op_count));
149 };
150 let last_op = *last_op;
151 let bounds = reader.bounds();
152 if last_op < bounds.start {
153 return Err(JournalError::ItemPruned(last_op).into());
154 }
155
156 let op = reader.read(last_op).await?;
157 let floor = op
158 .has_floor()
159 .ok_or(Error::HistoricalFloorPruned(op_count))?;
160 if floor > Location::new(last_op) {
161 return Err(Error::DataCorrupted(
162 "inactivity floor exceeds commit location",
163 ));
164 }
165 Ok(floor)
166}
167
168pub(crate) async fn inactive_peaks_at<F, R>(
170 reader: &R,
171 op_count: Location<F>,
172) -> Result<usize, Error<F>>
173where
174 F: Family,
175 R: Contiguous<Item: Floored<F>>,
176{
177 if op_count == Location::new(0) {
178 return Ok(0);
179 }
180
181 let floor = find_inactivity_floor_at::<F, _>(reader, op_count).await?;
182 Ok(F::inactive_peaks(op_count, floor))
183}
184
185#[derive(Error, Debug)]
187pub enum Error<F: Family> {
188 #[error("data corrupted: {0}")]
189 DataCorrupted(&'static str),
190
191 #[error("merkle error: {0}")]
192 Merkle(#[from] crate::merkle::Error<F>),
193
194 #[error("metadata error: {0}")]
195 Metadata(#[from] crate::metadata::Error),
196
197 #[error("journal error: {0}")]
198 Journal(#[from] crate::journal::Error),
199
200 #[error("runtime error: {0}")]
201 Runtime(#[from] commonware_runtime::Error),
202
203 #[error("operation pruned: {0}")]
204 OperationPruned(Location<F>),
205
206 #[error("key not found")]
208 KeyNotFound,
209
210 #[error("key exists")]
212 KeyExists,
213
214 #[error("unexpected data at location: {0}")]
215 UnexpectedData(Location<F>),
216
217 #[error("location out of bounds: {0} >= {1}")]
218 LocationOutOfBounds(Location<F>, Location<F>),
219
220 #[error("prune location {0} beyond minimum required location {1}")]
221 PruneBeyondMinRequired(Location<F>, Location<F>),
222
223 #[error("stale batch: current database state does not match the batch")]
227 StaleBatch,
228
229 #[error("floor regressed: batch floor {0} < current floor {1}")]
231 FloorRegressed(Location<F>, Location<F>),
232
233 #[error("floor beyond commit location: floor {0} > commit loc {1}")]
237 FloorBeyondSize(Location<F>, Location<F>),
238
239 #[error("historical floor pruned for size: {0}")]
248 HistoricalFloorPruned(Location<F>),
249}
250
251impl<F: Family> From<crate::journal::authenticated::Error<F>> for Error<F> {
252 fn from(e: crate::journal::authenticated::Error<F>) -> Self {
253 match e {
254 crate::journal::authenticated::Error::Journal(j) => Self::Journal(j),
255 crate::journal::authenticated::Error::Merkle(m) => Self::Merkle(m),
256 }
257 }
258}
259
260pub(super) async fn build_snapshot_from_log<F, C, I, Fn>(
270 inactivity_floor_loc: crate::merkle::Location<F>,
271 reader: &C,
272 snapshot: &mut I,
273 init_buffer: NonZeroUsize,
274 cache_size: Option<NonZeroUsize>,
275 mut callback: Fn,
276) -> Result<usize, Error<F>>
277where
278 F: crate::merkle::Family,
279 C: Contiguous<Item: Operation<F>>,
280 I: Index<Value = crate::merkle::Location<F>>,
281 Fn: FnMut(bool, Option<crate::merkle::Location<F>>),
282{
283 let bounds = reader.bounds();
284 let stream = reader
285 .replay(*inactivity_floor_loc, init_buffer, ReadOptions::default())
286 .await?;
287 pin_mut!(stream);
288 let last_commit_loc = bounds.end.saturating_sub(1);
289
290 let mut cache = cache_size.map(Clock::<u64, <C::Item as Operation<F>>::Key>::new);
294
295 let mut active_keys: usize = 0;
296 while let Some(result) = stream.next().await {
297 let (loc, op) = result?;
298 if let Some(key) = op.key() {
299 if op.is_delete() {
300 let old_loc = delete_key(snapshot, reader, key, cache.as_mut()).await?;
301 callback(false, old_loc);
302 if old_loc.is_some() {
303 active_keys -= 1;
304 }
305 } else if op.is_update() {
306 let new_loc = crate::merkle::Location::new(loc);
307 let old_loc = update_key(snapshot, reader, key, new_loc, cache.as_mut()).await?;
308 callback(true, old_loc);
309 if old_loc.is_none() {
310 active_keys += 1;
311 }
312
313 if let Some(cache) = cache.as_mut() {
315 cache.put(loc, key.clone());
316 }
317 }
318 } else if op.has_floor().is_some() {
319 callback(loc == last_commit_loc, None);
320 }
321 }
322
323 Ok(active_keys)
324}
325
326async fn delete_key<F, I, R>(
329 snapshot: &mut I,
330 reader: &R,
331 key: &<R::Item as Operation<F>>::Key,
332 cache: Option<&mut Clock<u64, <R::Item as Operation<F>>::Key>>,
333) -> Result<Option<Location<F>>, Error<F>>
334where
335 F: Family,
336 I: Index<Value = Location<F>>,
337 R: Contiguous,
338 R::Item: Operation<F>,
339{
340 let Some(cursor) = snapshot.get_mut(key) else {
342 return Ok(None);
343 };
344 delete_at_cursor::<F, _, _>(cursor, reader, key, cache).await
345}
346
347async fn delete_at_cursor<F, C, R>(
351 mut cursor: C,
352 reader: &R,
353 key: &<R::Item as Operation<F>>::Key,
354 mut cache: Option<&mut Clock<u64, <R::Item as Operation<F>>::Key>>,
355) -> Result<Option<Location<F>>, Error<F>>
356where
357 F: Family,
358 C: Cursor<Value = Location<F>>,
359 R: Contiguous,
360 R::Item: Operation<F>,
361{
362 let Some(loc) = find_update_op::<F, _>(reader, &mut cursor, key, cache.as_deref_mut()).await?
364 else {
365 return Ok(None);
366 };
367
368 cursor.delete();
371 if let Some(cache) = cache {
372 cache.remove(&*loc);
373 }
374
375 Ok(Some(loc))
376}
377
378async fn update_key<F, I, R>(
380 snapshot: &mut I,
381 reader: &R,
382 key: &<R::Item as Operation<F>>::Key,
383 new_loc: Location<F>,
384 cache: Option<&mut Clock<u64, <R::Item as Operation<F>>::Key>>,
385) -> Result<Option<Location<F>>, Error<F>>
386where
387 F: Family,
388 I: Index<Value = Location<F>>,
389 R: Contiguous,
390 R::Item: Operation<F>,
391{
392 let Some(cursor) = snapshot.get_mut_or_insert(key, new_loc) else {
395 return Ok(None);
396 };
397 update_at_cursor::<F, _, _>(cursor, reader, key, new_loc, cache).await
398}
399
400async fn update_at_cursor<F, C, R>(
405 mut cursor: C,
406 reader: &R,
407 key: &<R::Item as Operation<F>>::Key,
408 new_loc: Location<F>,
409 mut cache: Option<&mut Clock<u64, <R::Item as Operation<F>>::Key>>,
410) -> Result<Option<Location<F>>, Error<F>>
411where
412 F: Family,
413 C: Cursor<Value = Location<F>>,
414 R: Contiguous,
415 R::Item: Operation<F>,
416{
417 if let Some(loc) =
419 find_update_op::<F, _>(reader, &mut cursor, key, cache.as_deref_mut()).await?
420 {
421 assert!(new_loc > loc);
424 cursor.update(new_loc);
425 if let Some(cache) = cache {
426 cache.remove(&*loc);
427 }
428 return Ok(Some(loc));
429 }
430
431 cursor.insert(new_loc);
433
434 Ok(None)
435}
436
437async fn find_update_op<F, R>(
440 reader: &R,
441 cursor: &mut impl Cursor<Value = Location<F>>,
442 key: &<R::Item as Operation<F>>::Key,
443 mut cache: Option<&mut Clock<u64, <R::Item as Operation<F>>::Key>>,
444) -> Result<Option<Location<F>>, Error<F>>
445where
446 F: Family,
447 R: Contiguous,
448 R::Item: Operation<F>,
449{
450 while let Some(&loc) = cursor.next() {
451 let matches = if let Some(k) = cache.as_deref().and_then(|c| c.get(&*loc)) {
453 *k == *key
454 } else {
455 let op = reader.read(*loc).await?;
456 let k = op.key().expect("operation without key");
457 let matches = *k == *key;
458
459 if !matches && let Some(cache) = cache.as_deref_mut() {
462 cache.put(*loc, k.clone());
463 }
464 matches
465 };
466 if matches {
467 return Ok(Some(loc));
468 }
469 }
470
471 Ok(None)
472}
473
474const SNAPSHOT_ROUTE_BATCH: usize = 4096;
476
477const SNAPSHOT_CHANNEL_DEPTH: usize = 4;
480
481type RoutedBatch<K> = Vec<(K, u64, bool)>;
484
485async fn build_snapshot_worker<F, C, R>(
491 log: Arc<C>,
492 mut rx: mpsc::Receiver<RoutedBatch<<C::Item as Operation<F>>::Key>>,
493 mut index: R,
494 activity: Range<u64>,
495 active: Arc<Atomic>,
496 cache_size: Option<NonZeroUsize>,
497) -> Result<(R, usize), Error<F>>
498where
499 F: Family,
500 C: Contiguous<Item: Operation<F>>,
501 R: PartitionRange<Value = Location<F>>,
502{
503 let mut cache = cache_size.map(Clock::<u64, <C::Item as Operation<F>>::Key>::new);
504 while let Some(batch) = rx.recv().await {
505 for (key, loc, is_delete) in batch {
506 if is_delete {
507 if let Some(cursor) = index.get_mut(&key) {
508 delete_at_cursor::<F, _, _>(cursor, &*log, &key, cache.as_mut()).await?;
509 }
510 } else {
511 let new_loc = Location::new(loc);
512 if let Some(cursor) = index.get_mut_or_insert(&key, new_loc) {
513 update_at_cursor::<F, _, _>(cursor, &*log, &key, new_loc, cache.as_mut())
514 .await?;
515 }
516
517 if let Some(cache) = cache.as_mut() {
520 cache.put(loc, key);
521 }
522 }
523 }
524 }
525
526 let mut active_keys = 0;
530 index.for_each_value(|loc| {
531 active.set(**loc - activity.start);
532 active_keys += 1;
533 });
534 Ok((index, active_keys))
535}
536
537async fn build_snapshot_serial<F, C, I>(
541 inactivity_floor_loc: Location<F>,
542 reader: &C,
543 snapshot: &mut I,
544 init_buffer: NonZeroUsize,
545 cache_size: Option<NonZeroUsize>,
546) -> Result<(usize, BitMap), Error<F>>
547where
548 F: Family,
549 C: Contiguous<Item: Operation<F>>,
550 I: Index<Value = Location<F>>,
551{
552 let mut activity = BitMap::new();
555 let floor = *inactivity_floor_loc;
556 let active_keys = build_snapshot_from_log(
557 inactivity_floor_loc,
558 reader,
559 snapshot,
560 init_buffer,
561 cache_size,
562 |is_active, old_loc| {
563 activity.push(is_active);
564 if let Some(loc) = old_loc {
565 activity.set(*loc - floor, false);
566 }
567 },
568 )
569 .await?;
570 Ok((active_keys, activity))
571}
572
573async fn build_snapshot_parallel<F, E, C, I>(
577 snapshot: &mut I,
578 context: E,
579 inactivity_floor_loc: Location<F>,
580 log: &Arc<C>,
581 init_concurrency: NonZeroUsize,
582 init_buffer: NonZeroUsize,
583 cache_size: Option<NonZeroUsize>,
584) -> Result<(usize, BitMap), Error<F>>
585where
586 F: Family,
587 E: Spawner,
588 C: Contiguous<Item: Operation<F>> + 'static,
589 I: Partitioned + Index<Value = Location<F>>,
590{
591 let count = snapshot.partition_count();
592 let workers = (init_concurrency.get() - 1).min(count);
593
594 if workers == 0 {
596 return build_snapshot_serial(
597 inactivity_floor_loc,
598 &**log,
599 snapshot,
600 init_buffer,
601 cache_size,
602 )
603 .await;
604 }
605
606 let floor = *inactivity_floor_loc;
607 let range_size = count.div_ceil(workers);
608
609 let workers = count.div_ceil(range_size);
613 let per_worker_cache = cache_size.and_then(|n| NonZeroUsize::new(n.get() / workers));
614 let end = log.bounds().end;
615
616 let active = Arc::new(Atomic::zeroes(end - floor));
618
619 let mut senders = Vec::with_capacity(workers);
621 let mut handles = Vec::with_capacity(workers);
622 for w in 0..workers {
623 let (tx, rx) = mpsc::channel(SNAPSHOT_CHANNEL_DEPTH);
624 senders.push(tx);
625 let log = log.clone();
626
627 let lo = w * range_size;
630 let range_len = range_size.min(count - lo);
631 let worker_index = snapshot.new_range(lo, range_len);
632 let active = active.clone();
633 let handle = context
634 .child("snapshot_worker")
635 .with_attribute("worker", w)
636 .dedicated()
637 .spawn(move |_| {
638 build_snapshot_worker::<F, C, I::Range>(
639 log,
640 rx,
641 worker_index,
642 floor..end,
643 active,
644 per_worker_cache,
645 )
646 });
647 handles.push(handle);
648 }
649
650 let routing_result: Result<(), Error<F>> = async {
656 let stream = log
657 .replay(floor, init_buffer, ReadOptions::default())
658 .await?;
659 pin_mut!(stream);
660 let mut batches: Vec<RoutedBatch<_>> = (0..workers)
661 .map(|_| Vec::with_capacity(SNAPSHOT_ROUTE_BATCH))
662 .collect();
663
664 while let Some(result) = stream.next().await {
669 let (loc, op) = result?;
670 let is_delete = op.is_delete();
671 let Some(key) = op.into_key() else { continue };
672 let w = I::partition_of(key.as_ref()) / range_size;
673 batches[w].push((key, loc, is_delete));
674 if batches[w].len() >= SNAPSHOT_ROUTE_BATCH {
675 let batch =
676 std::mem::replace(&mut batches[w], Vec::with_capacity(SNAPSHOT_ROUTE_BATCH));
677 if senders[w].send(batch).await.is_err() {
678 return Ok(());
679 }
680 }
681 }
682
683 for (w, batch) in batches.into_iter().enumerate() {
685 if !batch.is_empty() && senders[w].send(batch).await.is_err() {
686 break;
687 }
688 }
689 Ok(())
690 }
691 .await;
692
693 drop(senders);
695
696 let joined = join_all(handles).await;
698 routing_result?;
699
700 let mut total_items = 0;
702 for handle in joined {
703 let (worker_index, worker_keys) = handle??;
704 snapshot.install_range(worker_index);
705 total_items += worker_keys;
706 }
707
708 let mut active = Arc::into_inner(active)
710 .expect("workers were joined")
711 .into_bitmap();
712
713 if let Some(last_commit) = end.checked_sub(1)
716 && last_commit >= floor
717 {
718 active.set(last_commit - floor, true);
719 }
720
721 Ok((total_items, active))
722}
723
724pub trait SnapshotBuild<F: Family>:
733 sealed::SnapshotBuildSealed + Index<Value = Location<F>> + Sized + 'static
734{
735 type Concurrency: Copy + Send + 'static;
738
739 #[allow(async_fn_in_trait)]
748 async fn build_snapshot<E, C>(
749 &mut self,
750 _context: E,
751 inactivity_floor_loc: Location<F>,
752 log: &Arc<C>,
753 _init_concurrency: Self::Concurrency,
754 init_buffer: NonZeroUsize,
755 cache_size: Option<NonZeroUsize>,
756 ) -> Result<(usize, BitMap), Error<F>>
757 where
758 E: Spawner,
759 C: Contiguous<Item: Operation<F>> + 'static,
760 {
761 build_snapshot_serial(inactivity_floor_loc, &**log, self, init_buffer, cache_size).await
762 }
763}
764
765mod sealed {
766 use crate::translator::Translator;
767
768 pub trait SnapshotBuildSealed {}
769 impl<T: Translator, V: Send + Sync> SnapshotBuildSealed for crate::index::unordered::Index<T, V> {}
770 impl<T: Translator, V: Send + Sync> SnapshotBuildSealed for crate::index::ordered::Index<T, V> {}
771 impl<T: Translator, V: Send + Sync, const P: usize> SnapshotBuildSealed
772 for crate::index::partitioned::unordered::Index<T, V, P>
773 {
774 }
775 impl<T: Translator, V: Send + Sync, const P: usize> SnapshotBuildSealed
776 for crate::index::partitioned::ordered::Index<T, V, P>
777 {
778 }
779}
780
781impl<F: Family, T: Translator> SnapshotBuild<F> for crate::index::unordered::Index<T, Location<F>> {
782 type Concurrency = ();
783}
784impl<F: Family, T: Translator> SnapshotBuild<F> for crate::index::ordered::Index<T, Location<F>> {
785 type Concurrency = ();
786}
787
788impl<F: Family, T: Translator, const P: usize> SnapshotBuild<F>
789 for crate::index::partitioned::unordered::Index<T, Location<F>, P>
790{
791 type Concurrency = NonZeroUsize;
792
793 async fn build_snapshot<E, C>(
794 &mut self,
795 context: E,
796 inactivity_floor_loc: Location<F>,
797 log: &Arc<C>,
798 init_concurrency: NonZeroUsize,
799 init_buffer: NonZeroUsize,
800 cache_size: Option<NonZeroUsize>,
801 ) -> Result<(usize, BitMap), Error<F>>
802 where
803 E: Spawner,
804 C: Contiguous<Item: Operation<F>> + 'static,
805 {
806 build_snapshot_parallel(
807 self,
808 context,
809 inactivity_floor_loc,
810 log,
811 init_concurrency,
812 init_buffer,
813 cache_size,
814 )
815 .await
816 }
817}
818
819impl<F: Family, T: Translator, const P: usize> SnapshotBuild<F>
820 for crate::index::partitioned::ordered::Index<T, Location<F>, P>
821{
822 type Concurrency = NonZeroUsize;
823
824 async fn build_snapshot<E, C>(
825 &mut self,
826 context: E,
827 inactivity_floor_loc: Location<F>,
828 log: &Arc<C>,
829 init_concurrency: NonZeroUsize,
830 init_buffer: NonZeroUsize,
831 cache_size: Option<NonZeroUsize>,
832 ) -> Result<(usize, BitMap), Error<F>>
833 where
834 E: Spawner,
835 C: Contiguous<Item: Operation<F>> + 'static,
836 {
837 build_snapshot_parallel(
838 self,
839 context,
840 inactivity_floor_loc,
841 log,
842 init_concurrency,
843 init_buffer,
844 cache_size,
845 )
846 .await
847 }
848}
849
850fn update_known_loc<F: Family, I: Index<Value = Location<F>>>(
857 snapshot: &mut I,
858 key: &[u8],
859 old_loc: Location<F>,
860 new_loc: Location<F>,
861) {
862 let mut cursor = snapshot.get_mut(key).expect("key should be known to exist");
863 assert!(
864 cursor.find(|&loc| *loc == old_loc),
865 "known key with given old_loc should have been found"
866 );
867 cursor.update(new_loc);
868}
869
870fn delete_known_loc<F: Family, I: Index<Value = Location<F>>>(
877 snapshot: &mut I,
878 key: &[u8],
879 old_loc: Location<F>,
880) {
881 let mut cursor = snapshot.get_mut(key).expect("key should be known to exist");
882 assert!(
883 cursor.find(|&loc| *loc == old_loc),
884 "known key with given old_loc should have been found"
885 );
886 cursor.delete();
887}
888
889pub(crate) struct FloorHelper<
891 'a,
892 F: Family,
893 I: Index<Value = Location<F>>,
894 C: Mutable<Item: Operation<F>>,
895> {
896 pub snapshot: &'a mut I,
897 pub log: C,
898}
899
900impl<F, I, C> FloorHelper<'_, F, I, C>
901where
902 F: Family,
903 I: Index<Value = Location<F>>,
904 C: Mutable<Item: Operation<F>>,
905{
906 async fn move_op_if_active(
910 mut self,
911 op: C::Item,
912 old_loc: Location<F>,
913 ) -> Result<(Self, bool), Error<F>> {
914 let Some(key) = op.key() else {
915 return Ok((self, false)); };
917
918 let active = {
920 let Some(mut cursor) = self.snapshot.get_mut(key) else {
921 return Ok((self, false));
922 };
923 if cursor.find(|&loc| loc == old_loc) {
924 cursor.update(Location::<F>::new(self.log.bounds().end));
926 true
927 } else {
928 false
929 }
930 };
931 if !active {
932 return Ok((self, false));
933 }
934
935 (self.log, _) = self.log.append(&op).await?;
937
938 Ok((self, true))
939 }
940
941 async fn raise_floor(
952 mut self,
953 mut inactivity_floor_loc: Location<F>,
954 ) -> Result<(Self, Location<F>), Error<F>> {
955 let tip_loc: Location<F> = Location::new(self.log.bounds().end);
956 loop {
957 assert!(
958 *inactivity_floor_loc < tip_loc,
959 "no active operations above the inactivity floor"
960 );
961 let old_loc = inactivity_floor_loc;
962 inactivity_floor_loc += 1;
963 let op = self.log.read(*old_loc).await?;
964 let moved;
965 (self, moved) = self.move_op_if_active(op, old_loc).await?;
966 if moved {
967 return Ok((self, inactivity_floor_loc));
968 }
969 }
970 }
971}