1use crate::{
6 Context,
7 index::Unordered as UnorderedIndex,
8 journal::{
9 Error as JournalError,
10 contiguous::{Contiguous, Mutable},
11 },
12 merkle::{
13 self, Graftable, Location, Position, hasher::Hasher as _, mem::Mem,
14 storage::Storage as MerkleStorage,
15 },
16 metadata::{Config as MConfig, Metadata},
17 qmdb::{
18 self, Error,
19 any::{
20 self,
21 operation::{Operation, update::Update},
22 },
23 current::{
24 batch::BitmapBatch,
25 grafting,
26 proof::{OperationProof, OpsRootWitness, RangeProof, RangeProofSpec},
27 },
28 operation::Floored as _,
29 },
30};
31use commonware_codec::{Codec, CodecShared, DecodeExt};
32use commonware_cryptography::{Digest, DigestOf, Hasher};
33use commonware_macros::boxed;
34use commonware_parallel::Strategy;
35use commonware_runtime::{
36 Handle,
37 telemetry::metrics::{
38 Counter, Gauge, GaugeExt as _, MetricsExt as _,
39 histogram::{ScopedTimer, Timed},
40 },
41};
42use commonware_utils::{
43 bitmap::{self, Readable as _},
44 sequence::prefixed_u64::U64,
45};
46use core::{num::NonZeroU64, ops::Range};
47use std::{collections::BTreeMap, sync::Arc};
48use tracing::{error, warn};
49
50const NODE_PREFIX: u8 = 0;
52
53const PRUNED_CHUNKS_PREFIX: u8 = 1;
55
56type GraftedPinnedNodes<F, D> = Vec<(Position<F>, D)>;
58
59pub(crate) struct Metrics<E: Context> {
61 pruned_chunks: Gauge,
63 sync_boundary: Gauge,
65 pub apply_batch_calls: Counter,
67 apply_batch_duration: Timed,
69 pub sync_calls: Counter,
71 sync_duration: Timed,
73 pub prune_calls: Counter,
75 prune_duration: Timed,
77 clock: Arc<E>,
79}
80
81impl<E: Context> Metrics<E> {
82 pub fn new(context: E) -> Self {
84 Self {
85 pruned_chunks: context.gauge("pruned_chunks", "Number of pruned bitmap chunks"),
86 sync_boundary: context
87 .gauge("sync_boundary", "Most recent safe sync boundary location"),
88 apply_batch_calls: context.counter("apply_batch_calls", "Number of apply-batch calls"),
89 apply_batch_duration: Timed::register(
90 &context,
91 "apply_batch_duration",
92 "Duration of apply-batch calls",
93 ),
94 sync_calls: context.counter("sync_calls", "Number of sync calls"),
95 sync_duration: Timed::register(&context, "sync_duration", "Duration of sync calls"),
96 prune_calls: context.counter("prune_calls", "Number of prune calls"),
97 prune_duration: Timed::register(&context, "prune_duration", "Duration of prune calls"),
98 clock: Arc::new(context),
99 }
100 }
101
102 pub fn apply_batch_timer(&self) -> ScopedTimer<E> {
103 self.apply_batch_duration.scoped(&self.clock)
104 }
105
106 pub fn sync_timer(&self) -> ScopedTimer<E> {
107 self.sync_duration.scoped(&self.clock)
108 }
109
110 pub fn prune_timer(&self) -> ScopedTimer<E> {
111 self.prune_duration.scoped(&self.clock)
112 }
113
114 pub fn update(&self, pruned_chunks: u64, sync_boundary: u64) {
116 let _ = self.pruned_chunks.try_set(pruned_chunks);
117 let _ = self.sync_boundary.try_set(sync_boundary);
118 }
119}
120
121pub struct Db<
123 F: merkle::Graftable,
124 E: Context,
125 C: Contiguous<Item: CodecShared>,
126 I: UnorderedIndex<Value = Location<F>>,
127 H: Hasher,
128 U: Send + Sync,
129 const N: usize,
130 S: Strategy,
131> {
132 pub(super) any: any::db::Db<F, E, C, I, H, U, N, S>,
136
137 pub(super) grafted_tree: Arc<Mem<F, H::Digest>>,
148
149 pub(super) metadata: Metadata<E, U64, Vec<u8>>,
153
154 pub(super) strategy: S,
157
158 pub(super) root: DigestOf<H>,
161
162 pub(super) metrics: Metrics<E>,
164
165 #[cfg(test)]
168 pub(super) halt_before_prune_log: bool,
169}
170
171impl<F, E, C, I, H, U, const N: usize, S> std::fmt::Debug for Db<F, E, C, I, H, U, N, S>
172where
173 F: merkle::Graftable,
174 E: Context,
175 C: Contiguous<Item = Operation<F, U>>,
176 I: UnorderedIndex<Value = Location<F>>,
177 H: Hasher,
178 U: Update,
179 S: Strategy,
180 Operation<F, U>: Codec,
181{
182 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
183 f.debug_struct("Db")
184 .field("bounds", &self.bounds())
185 .field("inactivity_floor_loc", &self.any.inactivity_floor_loc)
186 .finish_non_exhaustive()
187 }
188}
189
190impl<F, E, C, I, H, U, const N: usize, S> Db<F, E, C, I, H, U, N, S>
192where
193 F: merkle::Graftable,
194 E: Context,
195 C: Contiguous<Item = Operation<F, U>>,
196 I: UnorderedIndex<Value = Location<F>>,
197 H: Hasher,
198 U: Update,
199 S: Strategy,
200 Operation<F, U>: Codec,
201{
202 #[cfg(any(test, feature = "test-traits"))]
205 pub(crate) const fn inactivity_floor_loc(&self) -> Location<F> {
206 self.any.inactivity_floor_loc()
207 }
208
209 pub const fn is_empty(&self) -> bool {
211 self.any.is_empty()
212 }
213
214 pub async fn get_metadata(&self) -> Result<Option<U::Value>, Error<F>> {
216 self.any.get_metadata().await
217 }
218
219 pub async fn get_many(&self, keys: &[&U::Key]) -> Result<Vec<Option<U::Value>>, Error<F>> {
221 self.any.get_many(keys).await
222 }
223
224 pub fn bounds(&self) -> std::ops::Range<Location<F>> {
227 self.any.bounds()
228 }
229
230 pub fn bitmap(&self) -> &impl bitmap::Readable<N> {
236 self.any.bitmap.as_ref()
237 }
238
239 pub fn verify_range_proof(
242 proof: &RangeProof<F, H::Digest>,
243 start_loc: Location<F>,
244 ops: &[Operation<F, U>],
245 chunks: &[[u8; N]],
246 root: &H::Digest,
247 ) -> bool {
248 proof.verify::<H, _, N>(start_loc, ops, chunks, root)
249 }
250
251 pub fn grafted_storage(&self) -> impl MerkleStorage<F, Digest = H::Digest> + '_ {
257 grafting::Storage::<F, H, _, _>::new(
258 &self.grafted_tree,
259 grafting::height::<N>(),
260 &self.any.log.merkle,
261 )
262 }
263
264 pub const fn root(&self) -> H::Digest {
267 self.root
268 }
269
270 pub const fn strategy(&self) -> &S {
272 &self.strategy
273 }
274
275 pub const fn ops_root(&self) -> H::Digest {
286 self.any.root()
287 }
288
289 pub async fn ops_root_witness(&self) -> Result<OpsRootWitness<F, H::Digest>, Error<F>> {
293 let storage = self.grafted_storage();
294 let ops_size = storage.size();
295 let ops_leaves = Location::<F>::try_from(ops_size)?;
296 let grafted_root = compute_grafted_root::<F, H, _, _, N>(
297 self.any.bitmap.as_ref(),
298 &storage,
299 ops_leaves,
300 self.any.inactivity_floor_loc,
301 )
302 .await?;
303 let hasher = qmdb::hasher::<H>();
304 let partial_chunk = partial_chunk::<_, N>(self.any.bitmap.as_ref())
305 .map(|(chunk, next_bit)| (next_bit, hasher.digest(chunk.as_slice())));
306 let pending_chunk_digest: F::PendingChunk<H::Digest> = pending_chunk::<F, _, N>(
307 self.any.bitmap.as_ref(),
308 ops_leaves,
309 grafting::height::<N>(),
310 )?
311 .map(|chunk| hasher.digest(chunk.as_slice()))
312 .try_into()
313 .expect("pending_chunk must be consistent with family");
314 Ok(OpsRootWitness {
315 grafted_root,
316 pending_chunk_digest,
317 partial_chunk,
318 })
319 }
320
321 pub(super) fn grafted_snapshot(&self) -> Arc<merkle::batch::MerkleizedBatch<F, H::Digest, S>> {
323 merkle::batch::MerkleizedBatch::from_mem_with_strategy(
324 &self.grafted_tree,
325 self.strategy.clone(),
326 )
327 }
328
329 pub fn new_batch(&self) -> super::batch::UnmerkleizedBatch<F, H, U, N, S> {
331 super::batch::UnmerkleizedBatch::new(
332 self.any.new_batch(),
333 self.grafted_snapshot(),
334 BitmapBatch::Base(Arc::clone(&self.any.bitmap)),
335 )
336 }
337
338 pub(super) async fn operation_proof(
340 &self,
341 loc: Location<F>,
342 ) -> Result<OperationProof<F, H::Digest, N>, Error<F>> {
343 let storage = self.grafted_storage();
344 let ops_root = self.any.root();
345 OperationProof::new::<H, _>(
346 self.any.bitmap.as_ref(),
347 &storage,
348 self.any.inactivity_floor_loc,
349 loc,
350 ops_root,
351 )
352 .await
353 }
354
355 #[allow(clippy::type_complexity)]
367 #[tracing::instrument(
368 name = "qmdb.current.db.range_proof",
369 level = "info",
370 skip_all,
371 fields(
372 start_loc = *start_loc,
373 max_ops = max_ops.get(),
374 ),
375 )]
376 pub async fn range_proof(
377 &self,
378 start_loc: Location<F>,
379 max_ops: NonZeroU64,
380 ) -> Result<(RangeProof<F, H::Digest>, Vec<Operation<F, U>>, Vec<[u8; N]>), Error<F>> {
381 let storage = self.grafted_storage();
382 let ops_root = self.any.root();
383 RangeProof::new_with_ops::<H, _, _, N>(
384 self.any.bitmap.as_ref(),
385 &storage,
386 &self.any.log,
387 RangeProofSpec {
388 start_loc,
389 max_ops,
390 inactivity_floor: self.any.inactivity_floor_loc,
391 ops_root,
392 },
393 )
394 .await
395 }
396}
397
398impl<F, E, C, I, H, U, const N: usize, S> Db<F, E, C, I, H, U, N, S>
400where
401 F: merkle::Graftable,
402 E: Context,
403 C: Mutable<Item = Operation<F, U>>,
404 I: UnorderedIndex<Value = Location<F>>,
405 H: Hasher,
406 U: Update,
407 S: Strategy,
408 Operation<F, U>: Codec,
409{
410 pub async fn ops_historical_proof(
416 &self,
417 historical_size: Location<F>,
418 start_loc: Location<F>,
419 max_ops: NonZeroU64,
420 ) -> Result<(merkle::Proof<F, H::Digest>, Vec<Operation<F, U>>), Error<F>> {
421 self.any
422 .historical_proof(historical_size, start_loc, max_ops)
423 .await
424 }
425
426 pub async fn pinned_nodes_at(&self, loc: Location<F>) -> Result<Vec<H::Digest>, Error<F>> {
428 self.any.pinned_nodes_at(loc).await
429 }
430
431 pub fn sync_boundary(&self) -> Location<F> {
460 sync_boundary::<F, N>(
461 *self.any.inactivity_floor_loc / bitmap::Prunable::<N>::CHUNK_SIZE_BITS,
462 *self.any.last_commit_loc + 1,
463 )
464 }
465
466 pub(super) fn update_metrics(&self) {
468 self.metrics.update(
469 self.any.bitmap.pruned_chunks() as u64,
470 *self.sync_boundary(),
471 );
472 }
473
474 fn delayed_merge_rewind_floor(&self) -> Option<u64> {
485 pair_absorption_threshold::<F, N>(self.any.bitmap.pruned_chunks() as u64)
486 }
487
488 fn grafted_pinned_nodes(
493 &self,
494 loc: Location<F>,
495 ) -> Result<GraftedPinnedNodes<F, H::Digest>, Error<F>> {
496 F::nodes_to_pin(loc)
497 .map(|pos| {
498 let digest = self
499 .grafted_tree
500 .get_node(pos)
501 .ok_or(Error::<F>::DataCorrupted("missing grafted pinned node"))?;
502 Ok((pos, digest))
503 })
504 .collect()
505 }
506
507 fn prune_grafted_tree_to_bitmap(&mut self) -> Result<(), Error<F>> {
509 let pruned_chunks = self.any.bitmap.pruned_chunks() as u64;
510 if pruned_chunks == 0 {
511 return Ok(());
512 }
513
514 let prune_loc = Location::<F>::new(pruned_chunks);
515 if prune_loc <= self.grafted_tree.bounds().start {
516 return Ok(());
517 }
518
519 let prune_pos = Position::try_from(prune_loc)
520 .map_err(|_| Error::<F>::DataCorrupted("prune location overflow"))?;
521 let size = self.grafted_tree.size();
522
523 let pinned: BTreeMap<_, _> = self.grafted_pinned_nodes(prune_loc)?.into_iter().collect();
524
525 let mut retained = Vec::with_capacity((*size - *prune_pos) as usize);
526 for p in *prune_pos..*size {
527 let digest = self
528 .grafted_tree
529 .get_node(Position::new(p))
530 .ok_or(Error::<F>::DataCorrupted("missing retained grafted node"))?;
531 retained.push(digest);
532 }
533
534 self.grafted_tree = Arc::new(Mem::from_pruned_with_retained(prune_pos, pinned, retained));
535 Ok(())
536 }
537
538 #[tracing::instrument(name = "qmdb.current.db.prune", level = "info", skip_all)]
556 #[boxed]
557 pub async fn prune(mut self, prune_loc: Location<F>) -> Result<Self, Error<F>> {
558 let _timer = self.metrics.prune_timer();
559 self.metrics.prune_calls.inc();
560 let sync_boundary = self.sync_boundary();
561 if prune_loc > sync_boundary {
562 return Err(Error::PruneBeyondMinRequired(prune_loc, sync_boundary));
563 }
564
565 self.any.log = self.any.log.commit().await?;
571
572 self.any.prune_bitmap(sync_boundary);
574 self.prune_grafted_tree_to_bitmap()?;
575
576 self = self.sync_metadata().await?;
582
583 #[cfg(test)]
584 if self.halt_before_prune_log {
585 std::future::pending::<()>().await;
586 }
587
588 (self.any, _) = self.any.prune_log(prune_loc).await?;
589 self.any.update_metrics();
590 self.update_metrics();
591 Ok(self)
592 }
593
594 #[tracing::instrument(name = "qmdb.current.db.rewind", level = "info", skip_all)]
616 #[boxed]
617 pub async fn rewind(mut self, size: Location<F>) -> Result<Self, Error<F>> {
618 let rewind_size = *size;
619 let current_size = *self.any.last_commit_loc + 1;
620 if rewind_size == current_size {
623 return Ok(self);
624 }
625 if rewind_size == 0 || rewind_size > current_size {
629 return Err(Error::Journal(JournalError::InvalidRewind(rewind_size)));
630 }
631
632 let pruned_chunks = self.any.bitmap.pruned_chunks();
633 let pruned_bits = (pruned_chunks as u64)
634 .checked_mul(bitmap::Prunable::<N>::CHUNK_SIZE_BITS)
635 .ok_or_else(|| Error::DataCorrupted("pruned ops leaves overflow"))?;
636 if rewind_size < pruned_bits {
637 return Err(Error::Journal(JournalError::ItemPruned(rewind_size - 1)));
638 }
639 if let Some(rewind_floor) = self.delayed_merge_rewind_floor()
640 && rewind_size < rewind_floor
641 {
642 return Err(Error::Journal(JournalError::ItemPruned(rewind_size - 1)));
643 }
644
645 {
650 let rewind_last_loc = Location::<F>::new(rewind_size - 1);
651 let rewind_last_op = self.any.log.read(*rewind_last_loc).await?;
652 let Some(rewind_floor) = rewind_last_op.has_floor() else {
653 return Err(Error::<F>::UnexpectedData(rewind_last_loc));
654 };
655 if *rewind_floor < pruned_bits {
656 return Err(Error::<F>::Journal(JournalError::ItemPruned(*rewind_floor)));
657 }
658 }
659
660 let pinned_nodes: Vec<H::Digest> = if pruned_chunks > 0 {
662 let grafted_leaves = Location::<F>::new(pruned_chunks as u64);
663 self.grafted_pinned_nodes(grafted_leaves)?
664 .into_iter()
665 .map(|(_, digest)| digest)
666 .collect()
667 } else {
668 Vec::new()
669 };
670
671 self.any = self.any.rewind(size).await?;
675
676 let (grafted_tree, root) = rebuild_grafted_tree::<F, H, S, N>(
678 self.any.bitmap.as_ref(),
679 &pinned_nodes,
680 &self.any.log.merkle,
681 self.any.inactivity_floor_loc,
682 self.any.root(),
683 &self.strategy,
684 )
685 .await?;
686
687 self.grafted_tree = Arc::new(grafted_tree);
688 self.root = root;
689 self.update_metrics();
690
691 Ok(self)
692 }
693
694 pub(crate) async fn sync_metadata(mut self) -> Result<Self, Error<F>> {
696 self.metadata.clear();
697
698 let pruned_chunks_u64 = self.any.bitmap.pruned_chunks() as u64;
700
701 let key = U64::new(PRUNED_CHUNKS_PREFIX, 0);
703 self.metadata
704 .put(key, pruned_chunks_u64.to_be_bytes().to_vec());
705
706 let pruned_chunks = Location::<F>::new(pruned_chunks_u64);
708 for (i, (_, digest)) in self
709 .grafted_pinned_nodes(pruned_chunks)?
710 .into_iter()
711 .enumerate()
712 {
713 let key = U64::new(NODE_PREFIX, i as u64);
714 self.metadata.put(key, digest.to_vec());
715 }
716
717 self.metadata = self.metadata.sync().await?;
718
719 Ok(self)
720 }
721
722 #[tracing::instrument(name = "qmdb.current.db.start_sync", level = "info", skip_all)]
730 #[boxed]
731 pub async fn start_sync(mut self) -> Result<(Self, Handle<()>), Error<F>> {
732 let (any, handle) = self.any.start_sync().await?;
733 self.any = any;
734 Ok((self, handle))
735 }
736
737 #[tracing::instrument(name = "qmdb.current.db.commit", level = "info", skip_all)]
740 #[boxed]
741 pub async fn commit(mut self) -> Result<Self, Error<F>> {
742 self.any = self.any.commit().await?;
743 Ok(self)
744 }
745
746 #[tracing::instrument(name = "qmdb.current.db.sync", level = "info", skip_all)]
748 #[boxed]
749 pub async fn sync(mut self) -> Result<Self, Error<F>> {
750 let _timer = self.metrics.sync_timer();
751 self.metrics.sync_calls.inc();
752 self.any = self.any.sync().await?;
753
754 self = self.sync_metadata().await?;
757 self.update_metrics();
758 Ok(self)
759 }
760
761 #[boxed]
763 pub async fn destroy(self) -> Result<(), Error<F>> {
764 let Self { any, metadata, .. } = self;
767 metadata.destroy().await?;
768 any.destroy().await
769 }
770
771 pub fn validate_batch(
777 &self,
778 batch: &super::batch::MerkleizedBatch<F, H::Digest, U, N, S>,
779 ) -> Result<(), Error<F>> {
780 self.any.validate_batch(&batch.inner)
781 }
782
783 #[tracing::instrument(name = "qmdb.current.db.apply_batch", level = "info", skip_all)]
794 #[boxed]
795 pub async fn apply_batch(
796 mut self,
797 batch: Arc<super::batch::MerkleizedBatch<F, H::Digest, U, N, S>>,
798 ) -> Result<(Self, Range<Location<F>>), Error<F>> {
799 let _timer = self.metrics.apply_batch_timer();
800 self.metrics.apply_batch_calls.inc();
801 let range;
802 (self.any, range) = self.any.apply_batch(Arc::clone(&batch.inner)).await?;
803 Arc::make_mut(&mut self.grafted_tree).apply_batch(&batch.grafted)?;
804 self.root = batch.canonical_root;
805 self.update_metrics();
806 Ok((self, range))
807 }
808}
809
810pub(crate) fn sync_boundary<F: Graftable, const N: usize>(
817 mut floor_chunks: u64,
818 ops_leaves: u64,
819) -> Location<F> {
820 let chunk_bits = bitmap::Prunable::<N>::CHUNK_SIZE_BITS;
821 let grafting_height = grafting::height::<N>();
822
823 while floor_chunks > 0 {
824 let required_ops = pair_absorption_threshold::<F, N>(floor_chunks).unwrap_or_else(|| {
825 let youngest_start = (floor_chunks - 1) * chunk_bits;
826 let pos = F::subtree_root_position(Location::<F>::new(youngest_start), grafting_height);
827 F::peak_birth_size(pos, grafting_height)
828 });
829
830 if ops_leaves >= required_ops {
831 break;
832 }
833 floor_chunks -= 1;
834 }
835
836 Location::new(floor_chunks * chunk_bits)
837}
838
839fn pair_absorption_threshold<F: Graftable, const N: usize>(chunk_count: u64) -> Option<u64> {
843 if chunk_count == 0 {
844 return None;
845 }
846
847 let grafting_height = grafting::height::<N>();
848 let youngest = chunk_count - 1;
849 let youngest_start = youngest << grafting_height;
850 let youngest_end = (youngest + 1) << grafting_height;
851 let youngest_pos =
852 F::subtree_root_position(Location::<F>::new(youngest_start), grafting_height);
853
854 if F::peak_birth_size(youngest_pos, grafting_height) <= youngest_end {
855 return None;
856 }
857
858 let pair_chunk = youngest & !1;
859 let pair_start = pair_chunk << grafting_height;
860 let pair_pos = F::subtree_root_position(Location::<F>::new(pair_start), grafting_height + 1);
861 Some(F::peak_birth_size(pair_pos, grafting_height + 1))
862}
863
864pub(super) fn partial_chunk<B: bitmap::Readable<N>, const N: usize>(
867 bitmap: &B,
868) -> Option<([u8; N], u64)> {
869 let next_bit = bitmap.len() % bitmap::Prunable::<N>::CHUNK_SIZE_BITS;
870 if next_bit == 0 {
871 return None;
872 }
873 let (last_chunk, _) = bitmap.last_chunk();
874 Some((last_chunk, next_bit))
875}
876
877fn graftable_chunk_window<F: merkle::Graftable, B: bitmap::Readable<N>, const N: usize>(
882 bitmap: &B,
883 ops_leaves: Location<F>,
884 grafting_height: u32,
885) -> Result<(u64, u64), Error<F>> {
886 let complete = bitmap.complete_chunks() as u64;
887 let graftable = grafting::graftable_chunks::<F>(*ops_leaves, grafting_height).min(complete);
888 let pending = complete - graftable;
889 if pending > 1 {
890 return Err(Error::DataCorrupted("multiple pending bitmap chunks"));
891 }
892
893 let pruned = bitmap.pruned_chunks() as u64;
894 if pruned > graftable {
895 return Err(Error::DataCorrupted(
896 "pruned chunks exceed graftable chunks",
897 ));
898 }
899
900 Ok((complete, graftable))
901}
902
903pub(super) fn pending_chunk<F: merkle::Graftable, B: bitmap::Readable<N>, const N: usize>(
916 bitmap: &B,
917 ops_leaves: Location<F>,
918 grafting_height: u32,
919) -> Result<Option<[u8; N]>, Error<F>> {
920 let (complete, graftable) =
921 graftable_chunk_window::<F, B, N>(bitmap, ops_leaves, grafting_height)?;
922 if complete - graftable != 1 {
923 return Ok(None);
924 }
925 Ok(Some(bitmap.get_chunk(graftable as usize)))
926}
927
928pub(super) fn combine_roots<H: Hasher>(
941 ops_root: &H::Digest,
942 grafted_root: &H::Digest,
943 pending: Option<&H::Digest>,
944 partial: Option<(u64, &H::Digest)>,
945) -> H::Digest {
946 let hasher = qmdb::hasher::<H>();
947 match (pending, partial) {
948 (None, None) => hasher.hash(&[ops_root.as_ref(), grafted_root.as_ref()]),
949 (Some(pe), None) => hasher.hash(&[ops_root.as_ref(), grafted_root.as_ref(), pe.as_ref()]),
950 (None, Some((nb, p))) => {
951 let nb_bytes = nb.to_be_bytes();
952 hasher.hash(&[
953 ops_root.as_ref(),
954 grafted_root.as_ref(),
955 nb_bytes.as_slice(),
956 p.as_ref(),
957 ])
958 }
959 (Some(pe), Some((nb, p))) => {
960 let nb_bytes = nb.to_be_bytes();
961 hasher.hash(&[
962 ops_root.as_ref(),
963 grafted_root.as_ref(),
964 pe.as_ref(),
965 nb_bytes.as_slice(),
966 p.as_ref(),
967 ])
968 }
969 }
970}
971
972#[allow(clippy::too_many_arguments)]
982pub(super) async fn compute_db_root<
983 F: merkle::Graftable,
984 H: Hasher,
985 B: bitmap::Readable<N>,
986 S: MerkleStorage<F, Digest = H::Digest>,
987 const N: usize,
988>(
989 status: &B,
990 storage: &S,
991 ops_leaves: Location<F>,
992 partial_chunk: Option<([u8; N], u64)>,
993 inactivity_floor: Location<F>,
994 ops_root: &H::Digest,
995) -> Result<H::Digest, Error<F>> {
996 let grafted_root =
997 compute_grafted_root::<F, H, B, S, N>(status, storage, ops_leaves, inactivity_floor)
998 .await?;
999 let hasher = qmdb::hasher::<H>();
1000 let pending = pending_chunk::<F, B, N>(status, ops_leaves, grafting::height::<N>())?
1001 .map(|chunk| hasher.digest(&chunk));
1002 let partial = partial_chunk.map(|(chunk, next_bit)| {
1003 let digest = hasher.digest(&chunk);
1004 (next_bit, digest)
1005 });
1006 Ok(combine_roots::<H>(
1007 ops_root,
1008 &grafted_root,
1009 pending.as_ref(),
1010 partial.as_ref().map(|(nb, d)| (*nb, d)),
1011 ))
1012}
1013
1014pub(super) async fn rebuild_grafted_tree<F, H, S, const N: usize>(
1017 bitmap: &impl bitmap::Readable<N>,
1018 pinned_nodes: &[H::Digest],
1019 ops_tree: &impl MerkleStorage<F, Digest = H::Digest>,
1020 inactivity_floor: Location<F>,
1021 ops_root: H::Digest,
1022 strategy: &S,
1023) -> Result<(Mem<F, H::Digest>, H::Digest), Error<F>>
1024where
1025 F: merkle::Graftable,
1026 H: Hasher,
1027 S: Strategy,
1028{
1029 let ops_leaves = Location::<F>::try_from(ops_tree.size())?;
1030 let grafted_tree =
1031 build_grafted_tree::<F, H, S, N>(bitmap, pinned_nodes, ops_tree, ops_leaves, strategy)
1032 .await?;
1033 let storage =
1034 grafting::Storage::<F, H, _, _>::new(&grafted_tree, grafting::height::<N>(), ops_tree);
1035 let partial_chunk = partial_chunk(bitmap);
1036 let root = compute_db_root::<F, H, _, _, N>(
1037 bitmap,
1038 &storage,
1039 ops_leaves,
1040 partial_chunk,
1041 inactivity_floor,
1042 &ops_root,
1043 )
1044 .await?;
1045 Ok((grafted_tree, root))
1046}
1047
1048pub(super) async fn compute_grafted_root<
1058 F: merkle::Graftable,
1059 H: Hasher,
1060 B: bitmap::Readable<N>,
1061 S: MerkleStorage<F, Digest = H::Digest>,
1062 const N: usize,
1063>(
1064 status: &B,
1065 storage: &S,
1066 ops_leaves: Location<F>,
1067 inactivity_floor: Location<F>,
1068) -> Result<H::Digest, Error<F>> {
1069 let size = storage.size();
1070 let leaves = Location::try_from(size)?;
1071
1072 let mut peaks: Vec<H::Digest> = Vec::new();
1074 for (peak_pos, _) in F::peaks(size) {
1075 let digest = storage
1076 .get_node(peak_pos)
1077 .await?
1078 .ok_or_else(|| merkle::Error::<F>::MissingNode(peak_pos))?;
1079 peaks.push(digest);
1080 }
1081
1082 let grafting_height = grafting::height::<N>();
1084 let (_complete_chunks, _graftable_chunks) =
1085 graftable_chunk_window::<F, B, N>(status, ops_leaves, grafting_height)?;
1086
1087 let inactive_peaks =
1088 grafting::chunk_aligned_inactive_peaks::<F>(leaves, inactivity_floor, grafting_height)?;
1089 let hasher = qmdb::hasher::<H>();
1090
1091 Ok(hasher.root(leaves, inactive_peaks, peaks.iter())?)
1097}
1098
1099pub(super) async fn read_graft_inputs<F: merkle::Graftable, D: Digest, const N: usize>(
1107 ops_tree: &impl MerkleStorage<F, Digest = D>,
1108 chunks: impl IntoIterator<Item = (usize, [u8; N])>,
1109) -> Result<Vec<(usize, D, [u8; N])>, Error<F>> {
1110 let grafting_height = grafting::height::<N>();
1111
1112 let chunks: Vec<(usize, [u8; N])> = chunks.into_iter().collect();
1115 let positions: Vec<Position<F>> = chunks
1116 .iter()
1117 .map(|&(chunk_idx, _)| {
1118 let leaf_start = Location::<F>::new((chunk_idx as u64) << grafting_height);
1119 F::subtree_root_position(leaf_start, grafting_height)
1120 })
1121 .collect();
1122
1123 let nodes = ops_tree.get_nodes(&positions).await?;
1126 Ok(chunks
1127 .into_iter()
1128 .zip(nodes)
1129 .map(|((chunk_idx, chunk), chunk_ops_digest)| (chunk_idx, chunk_ops_digest, chunk))
1130 .collect())
1131}
1132
1133pub(super) async fn compute_grafted_leaves<
1141 F: merkle::Graftable,
1142 H: Hasher,
1143 S: Strategy,
1144 const N: usize,
1145>(
1146 ops_tree: &impl MerkleStorage<F, Digest = H::Digest>,
1147 chunks: impl IntoIterator<Item = (usize, [u8; N])>,
1148 strategy: &S,
1149) -> Result<Vec<(usize, H::Digest)>, Error<F>> {
1150 let inputs = read_graft_inputs::<F, _, N>(ops_tree, chunks).await?;
1151 Ok(grafting::graft_chunk_digests::<H, _, N>(strategy, inputs))
1152}
1153
1154pub(super) async fn build_grafted_tree<
1169 F: merkle::Graftable,
1170 H: Hasher,
1171 S: Strategy,
1172 const N: usize,
1173>(
1174 bitmap: &impl bitmap::Readable<N>,
1175 pinned_nodes: &[H::Digest],
1176 ops_tree: &impl MerkleStorage<F, Digest = H::Digest>,
1177 ops_leaves: Location<F>,
1178 strategy: &S,
1179) -> Result<Mem<F, H::Digest>, Error<F>> {
1180 let grafting_height = grafting::height::<N>();
1181 let pruned_chunks = bitmap.pruned_chunks();
1182 let complete_chunks = bitmap.complete_chunks();
1183 let graftable_chunks = grafting::graftable_chunks::<F>(*ops_leaves, grafting_height)
1184 .min(complete_chunks as u64) as usize;
1185 assert!(
1186 pruned_chunks <= graftable_chunks && graftable_chunks <= complete_chunks,
1187 "invariant violated: pruned={pruned_chunks} graftable={graftable_chunks} complete={complete_chunks}"
1188 );
1189
1190 let leaves = compute_grafted_leaves::<F, H, S, N>(
1194 ops_tree,
1195 (pruned_chunks..graftable_chunks).map(|chunk_idx| (chunk_idx, bitmap.get_chunk(chunk_idx))),
1196 strategy,
1197 )
1198 .await?;
1199
1200 let mut grafted_tree = if pruned_chunks > 0 {
1202 let grafted_pruning_boundary = Location::<F>::new(pruned_chunks as u64);
1203 Mem::from_components(Vec::new(), grafted_pruning_boundary, pinned_nodes.to_vec())
1204 .map_err(|_| Error::<F>::DataCorrupted("grafted tree rebuild failed"))?
1205 } else {
1206 Mem::new()
1207 };
1208
1209 if !leaves.is_empty() {
1211 let batch = {
1212 let batch = grafted_tree.new_batch_with_strategy(strategy.clone());
1213 let batch = batch.add_leaf_digests(leaves.iter().map(|&(_, digest)| digest));
1214 let grafted_hasher = grafting::hasher::<F, H>(grafting_height);
1215 batch.merkleize(&grafted_tree, &grafted_hasher)
1216 };
1217 grafted_tree.apply_batch(&batch)?;
1218 }
1219
1220 Ok(grafted_tree)
1221}
1222
1223pub(super) async fn init_metadata<F: merkle::Graftable, E: Context, D: Digest>(
1234 context: E,
1235 partition: &str,
1236) -> Result<(Metadata<E, U64, Vec<u8>>, usize, Vec<D>), Error<F>> {
1237 let metadata_cfg = MConfig {
1238 partition: partition.into(),
1239 codec_config: ((0..).into(), ()),
1240 };
1241 let metadata =
1242 Metadata::<_, U64, Vec<u8>>::init(context.child("metadata"), metadata_cfg).await?;
1243
1244 let key = U64::new(PRUNED_CHUNKS_PREFIX, 0);
1245 let pruned_chunks = match metadata.get(&key) {
1246 Some(bytes) => u64::from_be_bytes(bytes.as_slice().try_into().map_err(|_| {
1247 error!("pruned chunks value not a valid u64");
1248 Error::<F>::DataCorrupted("pruned chunks value not a valid u64")
1249 })?),
1250 None => {
1251 warn!("bitmap metadata does not contain pruned chunks, initializing as empty");
1252 0
1253 }
1254 } as usize;
1255
1256 let pinned_nodes = if pruned_chunks > 0 {
1260 let pruned_loc = Location::<F>::new(pruned_chunks as u64);
1261 if !pruned_loc.is_valid() {
1262 return Err(Error::DataCorrupted("pruned chunks exceeds MAX_LEAVES"));
1263 }
1264 let mut pinned = Vec::new();
1265 for (index, _pos) in F::nodes_to_pin(pruned_loc).enumerate() {
1266 let metadata_key = U64::new(NODE_PREFIX, index as u64);
1267 let Some(bytes) = metadata.get(&metadata_key) else {
1268 return Err(Error::DataCorrupted(
1269 "missing pinned node in grafted tree metadata",
1270 ));
1271 };
1272 let digest = D::decode(bytes.as_ref())
1273 .map_err(|_| Error::<F>::DataCorrupted("invalid pinned node digest"))?;
1274 pinned.push(digest);
1275 }
1276 pinned
1277 } else {
1278 Vec::new()
1279 };
1280
1281 Ok((metadata, pruned_chunks, pinned_nodes))
1282}
1283
1284#[cfg(test)]
1285mod tests {
1286 use super::*;
1287 use crate::{
1288 merkle::{Bagging::ForwardFold, hasher::Standard as StandardHasher, mmb, mmr},
1289 qmdb::{
1290 any::traits::{DbAny, UnmerkleizedBatch as _},
1291 current::{tests::fixed_config, unordered::fixed},
1292 },
1293 translator::OneCap,
1294 };
1295 use commonware_codec::FixedSize;
1296 use commonware_cryptography::{Sha256, sha256};
1297 use commonware_macros::test_traced;
1298 use commonware_runtime::{Runner as _, Supervisor as _, deterministic};
1299 use commonware_utils::bitmap::Prunable as PrunableBitMap;
1300
1301 const N: usize = sha256::Digest::SIZE;
1302
1303 #[test]
1304 fn partial_chunk_single_bit() {
1305 let mut bm = PrunableBitMap::<N>::new();
1306 bm.push(true);
1307 let result = partial_chunk::<PrunableBitMap<N>, N>(&bm);
1308 assert!(result.is_some());
1309 let (chunk, next_bit) = result.unwrap();
1310 assert_eq!(next_bit, 1);
1311 assert_eq!(chunk[0], 1); }
1313
1314 #[test]
1315 fn partial_chunk_aligned() {
1316 let mut bm = PrunableBitMap::<N>::new();
1317 for _ in 0..PrunableBitMap::<N>::CHUNK_SIZE_BITS {
1318 bm.push(true);
1319 }
1320 let result = partial_chunk::<PrunableBitMap<N>, N>(&bm);
1321 assert!(result.is_none());
1322 }
1323
1324 #[test]
1325 fn partial_chunk_partial() {
1326 let mut bm = PrunableBitMap::<N>::new();
1327 for _ in 0..(PrunableBitMap::<N>::CHUNK_SIZE_BITS + 5) {
1328 bm.push(true);
1329 }
1330 let result = partial_chunk::<PrunableBitMap<N>, N>(&bm);
1331 assert!(result.is_some());
1332 let (_chunk, next_bit) = result.unwrap();
1333 assert_eq!(next_bit, 5);
1334 }
1335
1336 #[test]
1337 fn partial_chunk_empty() {
1338 let bm = PrunableBitMap::<N>::new();
1340 assert!(partial_chunk::<PrunableBitMap<N>, N>(&bm).is_none());
1341 }
1342
1343 #[test]
1344 fn partial_chunk_fully_pruned() {
1345 let bm = PrunableBitMap::<N>::new_with_pruned_chunks(1).unwrap();
1348 assert!(partial_chunk::<PrunableBitMap<N>, N>(&bm).is_none());
1349 }
1350
1351 #[test]
1352 fn combine_roots_deterministic() {
1353 let ops = Sha256::hash(&[b"ops"]);
1354 let grafted = Sha256::hash(&[b"grafted"]);
1355 let r1 = combine_roots::<Sha256>(&ops, &grafted, None, None);
1356 let r2 = combine_roots::<Sha256>(&ops, &grafted, None, None);
1357 assert_eq!(r1, r2);
1358 }
1359
1360 #[test]
1361 fn combine_roots_with_partial_differs() {
1362 let ops = Sha256::hash(&[b"ops"]);
1363 let grafted = Sha256::hash(&[b"grafted"]);
1364 let partial_digest = Sha256::hash(&[b"partial"]);
1365
1366 let without = combine_roots::<Sha256>(&ops, &grafted, None, None);
1367 let with = combine_roots::<Sha256>(&ops, &grafted, None, Some((5, &partial_digest)));
1368 assert_ne!(without, with);
1369 }
1370
1371 #[test]
1372 fn combine_roots_with_pending_differs() {
1373 let ops = Sha256::hash(&[b"ops"]);
1374 let grafted = Sha256::hash(&[b"grafted"]);
1375 let pending_digest = Sha256::hash(&[b"pending"]);
1376
1377 let without = combine_roots::<Sha256>(&ops, &grafted, None, None);
1378 let with = combine_roots::<Sha256>(&ops, &grafted, Some(&pending_digest), None);
1379 assert_ne!(without, with);
1380 }
1381
1382 #[test]
1383 fn combine_roots_pending_and_partial_independent() {
1384 let ops = Sha256::hash(&[b"ops"]);
1385 let grafted = Sha256::hash(&[b"grafted"]);
1386 let pending_digest = Sha256::hash(&[b"pending"]);
1387 let partial_digest = Sha256::hash(&[b"partial"]);
1388
1389 let only_pending = combine_roots::<Sha256>(&ops, &grafted, Some(&pending_digest), None);
1390 let only_partial =
1391 combine_roots::<Sha256>(&ops, &grafted, None, Some((5, &partial_digest)));
1392 let both = combine_roots::<Sha256>(
1393 &ops,
1394 &grafted,
1395 Some(&pending_digest),
1396 Some((5, &partial_digest)),
1397 );
1398 assert_ne!(only_pending, only_partial);
1399 assert_ne!(only_pending, both);
1400 assert_ne!(only_partial, both);
1401 }
1402
1403 #[test]
1404 fn combine_roots_different_ops_root() {
1405 let ops_a = Sha256::hash(&[b"ops_a"]);
1406 let ops_b = Sha256::hash(&[b"ops_b"]);
1407 let grafted = Sha256::hash(&[b"grafted"]);
1408
1409 let r1 = combine_roots::<Sha256>(&ops_a, &grafted, None, None);
1410 let r2 = combine_roots::<Sha256>(&ops_b, &grafted, None, None);
1411 assert_ne!(r1, r2);
1412 }
1413
1414 #[test]
1418 fn combine_roots_format_golden() {
1419 let hasher = StandardHasher::<Sha256>::new(ForwardFold);
1420 let ops = Sha256::hash(&[b"ops"]);
1421 let grafted = Sha256::hash(&[b"grafted"]);
1422 let pending = Sha256::hash(&[b"pending"]);
1423 let partial = Sha256::hash(&[b"partial"]);
1424 let next_bit: u64 = 0x1122_3344_5566_7788;
1425
1426 assert_eq!(
1428 combine_roots::<Sha256>(&ops, &grafted, None, None),
1429 hasher.hash(&[ops.as_ref(), grafted.as_ref()])
1430 );
1431
1432 assert_eq!(
1434 combine_roots::<Sha256>(&ops, &grafted, Some(&pending), None),
1435 hasher.hash(&[ops.as_ref(), grafted.as_ref(), pending.as_ref()])
1436 );
1437
1438 assert_eq!(
1440 combine_roots::<Sha256>(&ops, &grafted, None, Some((next_bit, &partial))),
1441 hasher.hash(&[
1442 ops.as_ref(),
1443 grafted.as_ref(),
1444 next_bit.to_be_bytes().as_slice(),
1445 partial.as_ref(),
1446 ])
1447 );
1448
1449 assert_eq!(
1451 combine_roots::<Sha256>(&ops, &grafted, Some(&pending), Some((next_bit, &partial))),
1452 hasher.hash(&[
1453 ops.as_ref(),
1454 grafted.as_ref(),
1455 pending.as_ref(),
1456 next_bit.to_be_bytes().as_slice(),
1457 partial.as_ref(),
1458 ])
1459 );
1460 }
1461
1462 type MmrDb = fixed::Db<
1463 mmr::Family,
1464 deterministic::Context,
1465 sha256::Digest,
1466 sha256::Digest,
1467 Sha256,
1468 OneCap,
1469 32,
1470 commonware_parallel::Sequential,
1471 >;
1472 type MmbDb = fixed::Db<
1473 mmb::Family,
1474 deterministic::Context,
1475 sha256::Digest,
1476 sha256::Digest,
1477 Sha256,
1478 OneCap,
1479 32,
1480 commonware_parallel::Sequential,
1481 >;
1482
1483 #[boxed]
1484 async fn populate_fixed_db<F, DB>(db: DB, start: u64, count: u64) -> DB
1485 where
1486 F: merkle::Graftable,
1487 DB: DbAny<F, Key = sha256::Digest, Value = sha256::Digest>,
1488 {
1489 let mut batch = db.new_batch();
1490 for idx in start..start + count {
1491 let key = Sha256::hash(&[&idx.to_be_bytes()]);
1492 let value = Sha256::hash(&[&(idx + count).to_be_bytes()]);
1493 batch = batch.write(key, Some(value));
1494 }
1495 let merkleized = batch.merkleize(&db, None).await.unwrap();
1496 let (db, _) = db.apply_batch(merkleized).await.unwrap();
1497 db.commit().await.unwrap()
1498 }
1499
1500 #[test_traced]
1503 fn test_operations_match_applied_range() {
1504 let executor = deterministic::Runner::default();
1505 executor.start(|ctx| async move {
1506 let db = MmrDb::init(
1507 ctx.child("db"),
1508 fixed_config::<OneCap>("operations-match-applied-range", &ctx),
1509 )
1510 .await
1511 .unwrap();
1512 let db = populate_fixed_db::<mmr::Family, _>(db, 0, 8).await;
1513
1514 let mut batch = db.new_batch();
1515 for idx in 0..4u64 {
1516 let key = Sha256::hash(&[&idx.to_be_bytes()]);
1517 let value = Sha256::hash(&[&(idx + 100).to_be_bytes()]);
1518 batch = batch.write(key, Some(value));
1519 }
1520 let merkleized = batch.merkleize(&db, None).await.unwrap();
1521 let (start, ops) = merkleized.operations();
1522 let (db, range) = db.apply_batch(merkleized).await.unwrap();
1523 assert_eq!(start, range.start);
1524 assert_eq!(*start + ops.len() as u64, *range.end);
1525 db.destroy().await.unwrap();
1526 });
1527 }
1528
1529 #[test_traced]
1532 fn test_start_sync_recovery() {
1533 let executor = deterministic::Runner::default();
1534 executor.start(|ctx| async move {
1535 let db = MmrDb::init(
1536 ctx.child("first"),
1537 fixed_config::<OneCap>("start-sync-recovery", &ctx),
1538 )
1539 .await
1540 .unwrap();
1541 let key = Sha256::hash(&[&0u64.to_be_bytes()]);
1542 let value = Sha256::hash(&[&1u64.to_be_bytes()]);
1543 let merkleized = db
1544 .new_batch()
1545 .write(key, Some(value))
1546 .merkleize(&db, None)
1547 .await
1548 .unwrap();
1549 let (db, _) = db.apply_batch(merkleized).await.unwrap();
1550 let (db, handle) = db.start_sync().await.unwrap();
1551 handle.await.unwrap();
1552 let root = db.root();
1553 drop(db);
1554
1555 let db = MmrDb::init(
1556 ctx.child("second"),
1557 fixed_config::<OneCap>("start-sync-recovery", &ctx),
1558 )
1559 .await
1560 .unwrap();
1561 assert_eq!(db.root(), root);
1562 assert_eq!(db.get(&key).await.unwrap(), Some(value));
1563 db.destroy().await.unwrap();
1564 });
1565 }
1566
1567 #[test_traced]
1572 fn test_current_prune_dropped_before_log_prune() {
1573 let executor = deterministic::Runner::default();
1574 executor.start(|ctx| async move {
1575 let db = MmrDb::init(
1576 ctx.child("storage"),
1577 fixed_config::<OneCap>("prune-park", &ctx),
1578 )
1579 .await
1580 .unwrap();
1581
1582 let db = populate_fixed_db::<mmr::Family, _>(db, 0, 512).await;
1585 let durable_floor = db.inactivity_floor_loc();
1586 let mut batch = db.new_batch();
1587 for idx in 0..512u64 {
1588 let key = Sha256::hash(&[&idx.to_be_bytes()]);
1589 let value = Sha256::hash(&[&(idx + 1024).to_be_bytes()]);
1590 batch = batch.write(key, Some(value));
1591 }
1592 let merkleized = batch.merkleize(&db, None).await.unwrap();
1593 let (mut db, _) = db.apply_batch(merkleized).await.unwrap();
1594 assert!(db.sync_boundary() > durable_floor);
1595 let bounds = db.bounds();
1596 let floor = db.inactivity_floor_loc();
1597 let root = db.root();
1598
1599 db.halt_before_prune_log = true;
1602 let boundary = db.sync_boundary();
1603 {
1604 let fut = db.prune(boundary);
1605 futures::pin_mut!(fut);
1606 assert!(
1607 futures::poll!(fut.as_mut()).is_pending(),
1608 "prune must park before the log prune"
1609 );
1610 }
1611
1612 let db = MmrDb::init(
1617 ctx.child("reopen"),
1618 fixed_config::<OneCap>("prune-park", &ctx),
1619 )
1620 .await
1621 .expect("prune crash must leave the db recoverable");
1622 assert_eq!(db.bounds(), bounds);
1623 assert_eq!(db.inactivity_floor_loc(), floor);
1624 assert_eq!(db.root(), root);
1625 assert!(db.any.bitmap.pruned_bits() > *durable_floor);
1626 db.destroy().await.unwrap();
1627 });
1628 }
1629
1630 #[test_traced]
1631 fn test_ops_root_witness_verifies_without_partial_chunk() {
1632 let executor = deterministic::Runner::default();
1633 executor.start(|ctx| async move {
1634 let mut db = MmrDb::init(
1635 ctx.child("storage"),
1636 fixed_config::<OneCap>("ops-root-witness-full", &ctx),
1637 )
1638 .await
1639 .unwrap();
1640 let mut next_idx = 0;
1641 db = populate_fixed_db::<mmr::Family, _>(db, next_idx, 256).await;
1642 next_idx += 256;
1643 while partial_chunk::<_, 32>(db.any.bitmap.as_ref()).is_some() {
1644 db = populate_fixed_db::<mmr::Family, _>(db, next_idx, 1).await;
1645 next_idx += 1;
1646 }
1647 let witness = db.ops_root_witness().await.unwrap();
1648 let ops_root = db.ops_root();
1649 let canonical_root = db.root();
1650
1651 assert!(witness.partial_chunk.is_none());
1652 assert!(witness.verify::<Sha256>(&ops_root, &canonical_root));
1653
1654 let wrong_ops_root = Sha256::hash(&[b"wrong ops root"]);
1655 assert!(!witness.verify::<Sha256>(&wrong_ops_root, &canonical_root));
1656
1657 let wrong_canonical_root = Sha256::hash(&[b"wrong canonical root"]);
1658 assert!(!witness.verify::<Sha256>(&ops_root, &wrong_canonical_root));
1659
1660 let mut tampered = witness;
1661 tampered.grafted_root = Sha256::hash(&[b"wrong grafted root"]);
1662 assert!(!tampered.verify::<Sha256>(&ops_root, &canonical_root));
1663 });
1664 }
1665
1666 #[test_traced]
1667 fn test_ops_root_witness_verifies_with_partial_chunk() {
1668 let executor = deterministic::Runner::default();
1669 executor.start(|ctx| async move {
1670 let db = MmbDb::init(
1671 ctx.child("storage"),
1672 fixed_config::<OneCap>("ops-root-witness-partial", &ctx),
1673 )
1674 .await
1675 .unwrap();
1676 let db = populate_fixed_db::<mmb::Family, _>(db, 0, 260).await;
1677 let witness = db.ops_root_witness().await.unwrap();
1678 let ops_root = db.ops_root();
1679 let canonical_root = db.root();
1680
1681 assert!(witness.partial_chunk.is_some());
1682 assert!(witness.verify::<Sha256>(&ops_root, &canonical_root));
1683
1684 let wrong_ops_root = Sha256::hash(&[b"wrong ops root"]);
1685 assert!(!witness.verify::<Sha256>(&wrong_ops_root, &canonical_root));
1686
1687 let wrong_canonical_root = Sha256::hash(&[b"wrong canonical root"]);
1688 assert!(!witness.verify::<Sha256>(&ops_root, &wrong_canonical_root));
1689
1690 let mut tampered = witness.clone();
1691 tampered.grafted_root = Sha256::hash(&[b"wrong grafted root"]);
1692 assert!(!tampered.verify::<Sha256>(&ops_root, &canonical_root));
1693
1694 let mut tampered = witness.clone();
1695 tampered.partial_chunk.as_mut().unwrap().0 += 1;
1696 assert!(!tampered.verify::<Sha256>(&ops_root, &canonical_root));
1697
1698 let mut tampered = witness;
1699 tampered.partial_chunk.as_mut().unwrap().1 = Sha256::hash(&[b"wrong partial chunk"]);
1700 assert!(!tampered.verify::<Sha256>(&ops_root, &canonical_root));
1701 });
1702 }
1703
1704 #[test_traced]
1705 fn test_ops_root_witness_verifies_with_pruned_db() {
1706 let executor = deterministic::Runner::default();
1707 executor.start(|ctx| async move {
1708 let mut db = MmrDb::init(
1709 ctx.child("storage"),
1710 fixed_config::<OneCap>("ops-root-witness-pruned", &ctx),
1711 )
1712 .await
1713 .unwrap();
1714
1715 for _ in 0..5 {
1717 db = populate_fixed_db::<mmr::Family, _>(db, 0, 512).await;
1718 }
1719 let boundary = db.sync_boundary();
1720 let db = db.prune(boundary).await.unwrap();
1721 assert!(
1722 db.any.bitmap.pruned_chunks() > 0,
1723 "test requires at least one pruned chunk to exercise the zero-chunk path"
1724 );
1725 let witness = db.ops_root_witness().await.unwrap();
1726 let ops_root = db.ops_root();
1727 let canonical_root = db.root();
1728
1729 assert!(witness.verify::<Sha256>(&ops_root, &canonical_root));
1730
1731 let wrong_canonical_root = Sha256::hash(&[b"wrong canonical root"]);
1732 assert!(!witness.verify::<Sha256>(&ops_root, &wrong_canonical_root));
1733
1734 let mut tampered = witness;
1735 tampered.grafted_root = Sha256::hash(&[b"wrong grafted root"]);
1736 assert!(!tampered.verify::<Sha256>(&ops_root, &canonical_root));
1737 });
1738 }
1739
1740 #[test_traced]
1741 fn test_ops_root_witness_verifies_on_fresh_db() {
1742 let executor = deterministic::Runner::default();
1743 executor.start(|ctx| async move {
1744 let db = MmrDb::init(
1745 ctx.child("storage"),
1746 fixed_config::<OneCap>("ops-root-witness-fresh", &ctx),
1747 )
1748 .await
1749 .unwrap();
1750 let witness = db.ops_root_witness().await.unwrap();
1751 let ops_root = db.ops_root();
1752 let canonical_root = db.root();
1753
1754 assert!(witness.verify::<Sha256>(&ops_root, &canonical_root));
1755 });
1756 }
1757}