1use super::operation::Operation;
28pub use crate::qmdb::compact::Config;
29use crate::{
30 Context,
31 journal::contiguous::variable::{self, Config as JournalConfig},
32 merkle::{Family, Location, Proof, batch, compact as compact_merkle},
33 qmdb::{
34 self, Error,
35 any::value::ValueEncoding,
36 batch_chain::{self, Bounds, Commitment},
37 compact::{
38 batch as compact_batch,
39 witness::{self, VerifiedWitness},
40 },
41 sync::{CompactTarget, FeedbackTx, Request, Response, Source},
42 },
43};
44use commonware_codec::{Encode, EncodeShared, Read};
45use commonware_cryptography::{Digest, Hasher};
46use commonware_macros::boxed;
47use commonware_parallel::Strategy;
48use commonware_runtime::Handle;
49use std::sync::{Arc, Weak};
50
51pub struct Db<F, E, V, H, C, S: Strategy>
54where
55 F: Family,
56 E: Context,
57 V: ValueEncoding,
58 H: Hasher,
59 Operation<F, V>: EncodeShared,
60 Operation<F, V>: Read<Cfg = C>,
61 C: Clone + Send + Sync + 'static,
62{
63 merkle: compact_merkle::Merkle<F, H::Digest, S>,
64 root: H::Digest,
65 last_commit_loc: Location<F>,
66 last_commit_metadata: Option<V::Value>,
67 inactivity_floor_loc: Location<F>,
68 commit_codec_config: C,
69 witness: witness::Store<E, F, H::Digest>,
70}
71
72impl<F, E, V, H, C, S: Strategy> std::fmt::Debug for Db<F, E, V, H, C, S>
73where
74 F: Family,
75 E: Context,
76 V: ValueEncoding,
77 H: Hasher,
78 Operation<F, V>: EncodeShared,
79 Operation<F, V>: Read<Cfg = C>,
80 C: Clone + Send + Sync + 'static,
81{
82 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
83 f.debug_struct("Db")
84 .field("size", &self.size())
85 .field("inactivity_floor_loc", &self.inactivity_floor_loc())
86 .finish_non_exhaustive()
87 }
88}
89
90#[allow(clippy::type_complexity)]
92pub struct UnmerkleizedBatch<F, H, V, S: Strategy>
93where
94 F: Family,
95 V: ValueEncoding,
96 H: Hasher,
97 Operation<F, V>: EncodeShared,
98{
99 merkle_batch: compact_merkle::UnmerkleizedBatch<F, H::Digest, S>,
100 appends: Vec<V::Value>,
101 parent: Option<Arc<MerkleizedBatch<F, H::Digest, V, S>>>,
102 base: batch_chain::Commitment<F, H::Digest>,
103}
104
105#[derive(Clone)]
107pub struct MerkleizedBatch<F: Family, D: Digest, V: ValueEncoding, S: Strategy>
108where
109 Operation<F, V>: EncodeShared,
110{
111 pub(super) merkle_batch: Arc<batch::MerkleizedBatch<F, D, S>>,
112 operations: Arc<Vec<Operation<F, V>>>,
113 pub(super) commit_metadata: Option<V::Value>,
114 pub(super) parent: Option<Weak<Self>>,
115 pub(super) bounds: batch_chain::Bounds<F, D>,
116}
117
118impl<F: Family, D: Digest, V: ValueEncoding, S: Strategy> MerkleizedBatch<F, D, V, S>
119where
120 Operation<F, V>: EncodeShared,
121{
122 pub(super) fn ancestors(&self) -> impl Iterator<Item = Arc<Self>> + use<F, D, V, S> {
123 batch_chain::ancestors(self.parent.clone(), |batch| batch.parent.as_ref())
124 }
125
126 pub(super) const fn commitment(&self) -> Commitment<F, D> {
128 self.bounds.tip
129 }
130
131 pub const fn root(&self) -> D {
133 self.bounds.tip.root
134 }
135
136 pub const fn bounds(&self) -> &Bounds<F, D> {
138 &self.bounds
139 }
140
141 pub fn operations(&self) -> (Location<F>, Arc<Vec<Operation<F, V>>>) {
143 (self.bounds.base.size, Arc::clone(&self.operations))
144 }
145
146 pub fn proof<E, C, H>(&self, db: &Db<F, E, V, H, C, S>) -> Result<Proof<F, D>, Error<F>>
162 where
163 E: Context,
164 H: Hasher<Digest = D>,
165 C: Clone + Send + Sync + 'static,
166 Operation<F, V>: Read<Cfg = C>,
167 {
168 let inactive_peaks = F::inactive_peaks(self.bounds.tip.size, self.bounds.inactivity_floor);
169 let hasher = qmdb::hasher::<H>();
170 db.merkle
171 .with_mem(|base| {
172 self.merkle_batch.range_proof(
173 base,
174 &hasher,
175 self.bounds.base.size..self.bounds.tip.size,
176 inactive_peaks,
177 )
178 })
179 .map_err(Into::into)
180 }
181
182 pub fn pinned_nodes<E, C, H>(&self, db: &Db<F, E, V, H, C, S>) -> Result<Vec<D>, Error<F>>
197 where
198 E: Context,
199 H: Hasher<Digest = D>,
200 C: Clone + Send + Sync + 'static,
201 Operation<F, V>: Read<Cfg = C>,
202 {
203 db.merkle
204 .with_mem(|base| {
205 F::nodes_to_pin(self.bounds.base.size)
206 .map(|pos| {
207 self.merkle_batch
208 .get_node(pos)
209 .or_else(|| base.get_node(pos))
210 .ok_or(crate::merkle::Error::ElementPruned(pos))
211 })
212 .collect::<Result<Vec<_>, _>>()
213 })
214 .map_err(Into::into)
215 }
216
217 pub fn new_batch<H>(self: &Arc<Self>) -> UnmerkleizedBatch<F, H, V, S>
219 where
220 H: Hasher<Digest = D>,
221 {
222 UnmerkleizedBatch {
223 merkle_batch: compact_merkle::UnmerkleizedBatch::wrap(self.merkle_batch.new_batch()),
224 appends: Vec::new(),
225 parent: Some(Arc::clone(self)),
226 base: self.commitment(),
227 }
228 }
229}
230
231impl<F, H, V, S> UnmerkleizedBatch<F, H, V, S>
232where
233 F: Family,
234 V: ValueEncoding,
235 H: Hasher,
236 S: Strategy,
237 Operation<F, V>: EncodeShared,
238{
239 pub(super) fn new<E, C>(
240 db: &Db<F, E, V, H, C, S>,
241 base: batch_chain::Commitment<F, H::Digest>,
242 ) -> Self
243 where
244 E: Context,
245 C: Clone + Send + Sync + 'static,
246 Operation<F, V>: Read<Cfg = C>,
247 {
248 Self {
249 merkle_batch: db.merkle.new_batch(),
250 appends: Vec::new(),
251 parent: None,
252 base,
253 }
254 }
255
256 fn db(&self) -> Commitment<F, H::Digest> {
260 self.parent
261 .as_ref()
262 .map_or(self.base, |parent| parent.bounds.db)
263 }
264
265 pub fn append(mut self, value: V::Value) -> Self {
266 self.appends.push(value);
267 self
268 }
269
270 #[tracing::instrument(
278 name = "qmdb.keyless.compact.batch.merkleize",
279 level = "info",
280 skip_all
281 )]
282 pub async fn merkleize<E, C>(
283 self,
284 db: &Db<F, E, V, H, C, S>,
285 metadata: Option<V::Value>,
286 inactivity_floor: Location<F>,
287 ) -> Arc<MerkleizedBatch<F, H::Digest, V, S>>
288 where
289 F: Family,
290 E: Context,
291 C: Clone + Send + Sync + 'static,
292 Operation<F, V>: Read<Cfg = C>,
293 {
294 let live_ancestors: Vec<_> =
295 batch_chain::parent_and_ancestors(self.parent.as_ref(), |parent| parent.ancestors())
296 .collect();
297 let boundary = batch_chain::effective_boundary(
298 self.db(),
299 live_ancestors.last().map(|oldest| oldest.bounds.base),
300 );
301
302 let mut ops: Vec<Operation<F, V>> = Vec::with_capacity(self.appends.len() + 1);
303 for value in self.appends {
304 ops.push(Operation::Append(value));
305 }
306 ops.push(Operation::Commit(metadata.clone(), inactivity_floor));
307
308 let operations = Arc::new(ops);
309 let total_size = self.base.size + operations.len() as u64;
310 let inactive_peaks = F::inactive_peaks(total_size, inactivity_floor);
311 let (merkle, root) = compact_batch::merkleize_ops::<F, H, S, _>(
312 &db.merkle,
313 self.merkle_batch,
314 Arc::clone(&operations),
315 inactive_peaks,
316 )
317 .await
318 .expect("inactive_peaks computed from batch size");
319
320 let ancestors = batch_chain::collect_ancestor_bounds(
321 live_ancestors,
322 |batch| batch.bounds.inactivity_floor,
323 |batch| batch.commitment(),
324 );
325
326 Arc::new(MerkleizedBatch {
327 merkle_batch: merkle,
328 operations,
329 commit_metadata: metadata,
330 parent: self.parent.as_ref().map(Arc::downgrade),
331 bounds: batch_chain::Bounds {
332 base: self.base,
333 db: boundary,
334 tip: Commitment::new(total_size, root),
335 ancestors,
336 inactivity_floor,
337 },
338 })
339 }
340}
341
342impl<F, E, V, H, C, S> Db<F, E, V, H, C, S>
343where
344 F: Family,
345 E: Context,
346 V: ValueEncoding,
347 H: Hasher,
348 S: Strategy,
349 Operation<F, V>: EncodeShared,
350 Operation<F, V>: Read<Cfg = C>,
351 C: Clone + Send + Sync + 'static,
352{
353 fn encode_commit_op(metadata: Option<V::Value>, inactivity_floor_loc: Location<F>) -> Vec<u8> {
354 Operation::<F, V>::Commit(metadata, inactivity_floor_loc)
355 .encode()
356 .to_vec()
357 }
358
359 pub(crate) fn init_from_sync(
366 strategy: S,
367 journal: witness::Journal<E, F, H::Digest>,
368 commit_codec_config: C,
369 last_commit_loc: Location<F>,
370 pinned_nodes: Vec<H::Digest>,
371 last_commit_op: Operation<F, V>,
372 ) -> Result<Self, Error<F>> {
373 let Operation::Commit(last_commit_metadata, inactivity_floor_loc) = last_commit_op else {
374 return Err(Error::UnexpectedData(last_commit_loc));
375 };
376 witness::validate_inactivity_floor(inactivity_floor_loc, last_commit_loc)?;
377
378 let op_bytes = Self::encode_commit_op(last_commit_metadata.clone(), inactivity_floor_loc);
379 let merkle =
380 compact_merkle::Merkle::from_compact_state(strategy, last_commit_loc, pinned_nodes)?;
381 let hasher = qmdb::hasher::<H>();
382 merkle.append_leaf(&hasher, &op_bytes)?;
383 let imported = witness::build_witness::<F, H, S>(&merkle, inactivity_floor_loc, op_bytes)?;
384 merkle.prune_to_frontier();
385
386 let witness = witness::Store::from_import(journal, imported);
387 let root = witness.with(|w| w.root);
388 Ok(Self {
389 merkle,
390 root,
391 last_commit_loc,
392 last_commit_metadata,
393 inactivity_floor_loc,
394 commit_codec_config,
395 witness,
396 })
397 }
398
399 #[boxed]
404 pub(crate) async fn init_from_merkle(
405 mut merkle: compact_merkle::Merkle<F, H::Digest, S>,
406 witness_context: E,
407 witness_config: JournalConfig<()>,
408 commit_codec_config: C,
409 ) -> Result<Self, Error<F>>
410 where
411 F: Family,
412 Operation<F, V>: Read<Cfg = C>,
413 {
414 let journal: witness::Journal<E, F, H::Digest> =
416 variable::Journal::init(witness_context, witness_config).await?;
417 let (witness, last_commit_op) = witness::init::<E, F, H, S, Operation<F, V>>(
418 journal,
419 &mut merkle,
420 &commit_codec_config,
421 Operation::<F, V>::Commit(None, Location::new(0))
422 .encode()
423 .to_vec(),
424 )
425 .await?;
426 let Operation::Commit(last_commit_metadata, inactivity_floor_loc) = last_commit_op else {
427 return Err(Error::DataCorrupted("last operation was not a commit"));
428 };
429 let last_commit_loc = witness.with(|w| w.size()) - 1;
430 let root = witness.with(|w| w.root);
431
432 Ok(Self {
433 merkle,
434 root,
435 last_commit_loc,
436 last_commit_metadata,
437 inactivity_floor_loc,
438 commit_codec_config,
439 witness,
440 })
441 }
442
443 pub const fn root(&self) -> H::Digest {
445 self.root
446 }
447
448 pub const fn strategy(&self) -> &S {
450 self.merkle.strategy()
451 }
452
453 pub const fn last_commit_loc(&self) -> Location<F> {
455 self.last_commit_loc
456 }
457
458 pub const fn inactivity_floor_loc(&self) -> Location<F> {
460 self.inactivity_floor_loc
461 }
462
463 pub fn size(&self) -> Location<F> {
465 self.last_commit_loc + 1
466 }
467
468 pub fn get_metadata(&self) -> Option<V::Value> {
470 self.last_commit_metadata.clone()
471 }
472
473 pub fn target(&self) -> CompactTarget<F, H::Digest> {
478 self.witness.with(VerifiedWitness::target)
479 }
480
481 pub(crate) fn commitment(&self) -> batch_chain::Commitment<F, H::Digest> {
483 batch_chain::Commitment::new(self.last_commit_loc + 1, self.root())
484 }
485
486 pub fn new_batch(&self) -> UnmerkleizedBatch<F, H, V, S> {
488 UnmerkleizedBatch::new(self, self.commitment())
489 }
490
491 pub fn to_batch(&self) -> Arc<MerkleizedBatch<F, H::Digest, V, S>>
493 where
494 F: Family,
495 {
496 Arc::new(MerkleizedBatch {
497 merkle_batch: self.merkle.to_batch(),
498 operations: Arc::new(Vec::new()),
499 commit_metadata: self.last_commit_metadata.clone(),
500 parent: None,
501 bounds: batch_chain::Bounds::from_db(self.commitment(), self.inactivity_floor_loc),
502 })
503 }
504
505 pub fn validate_batch(
511 &self,
512 batch: &MerkleizedBatch<F, H::Digest, V, S>,
513 ) -> Result<(), Error<F>> {
514 batch
515 .bounds
516 .validate_apply_to(self.commitment(), self.inactivity_floor_loc)
517 }
518
519 #[tracing::instrument(name = "qmdb.keyless.compact.db.apply_batch", level = "info", skip_all)]
534 pub async fn apply_batch(
535 mut self,
536 batch: Arc<MerkleizedBatch<F, H::Digest, V, S>>,
537 ) -> Result<(Self, core::ops::Range<Location<F>>), Error<F>> {
538 self.validate_batch(&batch)?;
539
540 let start_loc = self.last_commit_loc + 1;
541 self.merkle.apply_batch(&batch.merkle_batch)?;
542 self.root = batch.root();
543 self.last_commit_loc = batch.bounds.tip.size - 1;
544 self.last_commit_metadata = batch.commit_metadata.clone();
545 self.inactivity_floor_loc = batch.bounds.inactivity_floor;
546 let last_commit_metadata = self.last_commit_metadata.clone();
547 let inactivity_floor_loc = self.inactivity_floor_loc;
548 self.witness = self
549 .witness
550 .apply::<H, S>(&self.merkle, inactivity_floor_loc, || {
551 Self::encode_commit_op(last_commit_metadata, inactivity_floor_loc)
552 })
553 .await?;
554 Ok((self, start_loc..batch.bounds.tip.size))
555 }
556
557 #[tracing::instrument(name = "qmdb.keyless.compact.db.start_sync", level = "info", skip_all)]
565 pub async fn start_sync(mut self) -> Result<(Self, Handle<()>), Error<F>> {
566 let last_commit_metadata = self.last_commit_metadata.clone();
567 let inactivity_floor_loc = self.inactivity_floor_loc;
568 let handle;
569 (self.witness, handle) = self
570 .witness
571 .start_sync::<H, S>(&self.merkle, inactivity_floor_loc, || {
572 Self::encode_commit_op(last_commit_metadata, inactivity_floor_loc)
573 })
574 .await?;
575 Ok((self, handle))
576 }
577
578 #[tracing::instrument(name = "qmdb.keyless.compact.db.commit", level = "info", skip_all)]
581 pub async fn commit(mut self) -> Result<Self, Error<F>> {
582 let last_commit_metadata = self.last_commit_metadata.clone();
583 let inactivity_floor_loc = self.inactivity_floor_loc;
584 self.witness = self
585 .witness
586 .commit::<H, S>(&self.merkle, inactivity_floor_loc, || {
587 Self::encode_commit_op(last_commit_metadata, inactivity_floor_loc)
588 })
589 .await?;
590 Ok(self)
591 }
592
593 #[tracing::instrument(name = "qmdb.keyless.compact.db.sync", level = "info", skip_all)]
596 pub async fn sync(mut self) -> Result<Self, Error<F>> {
597 let last_commit_metadata = self.last_commit_metadata.clone();
598 let inactivity_floor_loc = self.inactivity_floor_loc;
599 self.witness = self
600 .witness
601 .sync::<H, S>(&self.merkle, inactivity_floor_loc, || {
602 Self::encode_commit_op(last_commit_metadata, inactivity_floor_loc)
603 })
604 .await?;
605 Ok(self)
606 }
607
608 #[tracing::instrument(name = "qmdb.keyless.compact.db.rewind", level = "info", skip_all)]
617 pub async fn rewind(mut self, target: Location<F>) -> Result<Self, Error<F>>
618 where
619 F: Family,
620 {
621 if self.size() == target
624 && self.witness.with(|w| w.size()) == target
625 && !self.witness.has_uncommitted_state()
626 {
627 self.witness.wait_for_sync().await?;
628 return Ok(self);
629 }
630
631 let last_commit_op;
632 (self.witness, last_commit_op) = self
633 .witness
634 .rewind::<H, S, Operation<F, V>>(&self.merkle, target, &self.commit_codec_config)
635 .await?;
636 let Operation::Commit(last_commit_metadata, inactivity_floor_loc) = last_commit_op else {
637 return Err(Error::DataCorrupted("last operation was not a commit"));
638 };
639 self.last_commit_metadata = last_commit_metadata;
640 self.inactivity_floor_loc = inactivity_floor_loc;
641 self.last_commit_loc = target - 1;
642 self.root = self.witness.with(|w| w.root);
643 Ok(self)
644 }
645
646 #[tracing::instrument(name = "qmdb.keyless.compact.db.prune", level = "info", skip_all)]
656 pub async fn prune(mut self, pruning_boundary: Location<F>) -> Result<Self, Error<F>> {
657 self.witness = self.witness.prune(pruning_boundary).await?;
658 Ok(self)
659 }
660
661 #[boxed]
663 pub async fn destroy(self) -> Result<(), Error<F>> {
664 self.witness.destroy().await?;
665 Ok(())
666 }
667}
668
669impl<F, E, V, H, C, S> Source for Db<F, E, V, H, C, S>
670where
671 F: Family,
672 E: Context,
673 V: ValueEncoding,
674 H: Hasher,
675 Operation<F, V>: EncodeShared + Read<Cfg = C>,
676 C: Clone + Send + Sync + 'static,
677 S: Strategy,
678{
679 type Family = F;
680 type Digest = H::Digest;
681 type Op = Operation<F, V>;
682 type Error = qmdb::Error<F>;
683
684 async fn serve(
685 &self,
686 request: Request<F>,
687 ) -> Result<(Response<F, Self::Op, H::Digest>, FeedbackTx), Self::Error> {
688 Ok((
689 self.witness
690 .compact_state(&self.commit_codec_config, request)?,
691 None,
692 ))
693 }
694}
695
696#[cfg(test)]
697mod tests {
698 use super::*;
699 use crate::{
700 merkle::{mmb, mmr},
701 qmdb::{
702 any::value::FixedEncoding, compact::witness, verify_proof,
703 verify_proof_and_pinned_nodes,
704 },
705 };
706 use commonware_cryptography::{Sha256, sha256::Digest};
707 use commonware_macros::test_traced;
708 use commonware_parallel::Sequential;
709 use commonware_runtime::{
710 BufferPooler, Runner as _, Spawner as _, Supervisor as _,
711 buffer::paged::CacheRef,
712 deterministic,
713 mocks::{DelayedSyncContext, PendingSyncs, drive_pending_syncs, fail_pending_syncs},
714 reschedule,
715 };
716 use commonware_utils::{NZU16, NZU64, NZUsize, sequence::U64};
717 use core::future::Future;
718 use futures::FutureExt as _;
719 use std::num::{NonZeroU16, NonZeroUsize};
720
721 type TestDb<F> = Db<F, deterministic::Context, FixedEncoding<U64>, Sha256, (), Sequential>;
722
723 const WITNESS_PAGE_SIZE: NonZeroU16 = NZU16!(77);
724 const WITNESS_PAGE_CACHE_SIZE: NonZeroUsize = NZUsize!(9);
725
726 fn witness_config(partition: &str, pooler: &impl BufferPooler) -> JournalConfig<()> {
727 JournalConfig {
728 partition: format!("{partition}-witness"),
729 items_per_section: NZU64!(64),
730 compression: None,
731 codec_config: (),
732 page_cache: CacheRef::from_pooler(pooler, WITNESS_PAGE_SIZE, WITNESS_PAGE_CACHE_SIZE),
733 write_buffer: NZUsize!(1024),
734 replay_buffer: NZUsize!(1024),
735 }
736 }
737
738 async fn open_db<F: Family>(context: deterministic::Context, partition: &str) -> TestDb<F> {
739 let witness_cfg = witness_config(partition, &context);
740 let merkle = crate::merkle::compact::Merkle::new(Sequential);
741 Db::init_from_merkle(merkle, context.child("witness"), witness_cfg, ())
742 .await
743 .unwrap()
744 }
745
746 async fn compact_operations_and_proof_inner<F: Family>(context: deterministic::Context) {
750 let db = open_db::<F>(context.child("db"), "keyless-operations-and-proof").await;
751
752 let mut seed = db.new_batch();
754 for value in 1u64..=6 {
755 seed = seed.append(U64::new(value));
756 }
757 let seed = seed
758 .merkleize(&db, Some(U64::new(7)), Location::new(0))
759 .await;
760 let (db, _) = db.apply_batch(seed).await.unwrap();
761 let db = db.sync().await.unwrap();
762 let floor = db.size();
763 assert_eq!(floor, Location::new(8));
764
765 assert!(matches!(
767 db.to_batch().proof(&db),
768 Err(Error::Merkle(crate::merkle::Error::Empty))
769 ));
770
771 let mut parent = db.new_batch();
774 for value in 8u64..=12 {
775 parent = parent.append(U64::new(value));
776 }
777 let parent = parent.merkleize(&db, Some(U64::new(13)), floor).await;
778 let child = parent
779 .new_batch::<Sha256>()
780 .append(U64::new(14))
781 .merkleize(&db, Some(U64::new(15)), floor)
782 .await;
783
784 let (child_start, child_ops) = child.operations();
786 let (_, child_ops_again) = child.operations();
787 let child_end = child.bounds().tip.size;
788 assert!(Arc::ptr_eq(&child_ops, &child_ops_again));
789 assert_eq!(child_start, parent.bounds().tip.size);
790 assert_eq!(*child_start + child_ops.len() as u64, *child_end);
791 assert_eq!(child_end, Location::new(16));
792 assert!(matches!(
793 child_ops.as_slice(),
794 [Operation::Append(value), Operation::Commit(Some(metadata), operation_floor)]
795 if value == &U64::new(14)
796 && metadata == &U64::new(15)
797 && operation_floor == &floor
798 ));
799
800 let child_root = child.root();
802 let child_proof = child.proof(&db).unwrap();
803 let child_pins = child.pinned_nodes(&db).unwrap();
804 assert_eq!(child_proof.leaves, child_end);
805 assert_eq!(
806 child_proof.inactive_peaks,
807 F::inactive_peaks(child_end, floor),
808 );
809 assert!(verify_proof::<Sha256, _, _>(
810 &child_proof,
811 child_start,
812 &child_ops,
813 &child_root
814 ));
815 assert!(verify_proof_and_pinned_nodes::<Sha256, _, _>(
816 &child_proof,
817 child_start,
818 &child_ops,
819 &child_pins,
820 &child_root
821 ));
822
823 assert!(child_pins.len() > 1);
825 let mut reordered_child_pins = child_pins.clone();
826 reordered_child_pins.swap(0, 1);
827 assert!(!verify_proof_and_pinned_nodes::<Sha256, _, _>(
828 &child_proof,
829 child_start,
830 &child_ops,
831 &reordered_child_pins,
832 &child_root
833 ));
834
835 let (db, _) = db.apply_batch(parent).await.unwrap();
839 let child_proof_after = child.proof(&db).unwrap();
840 assert_eq!(child.pinned_nodes(&db).unwrap(), child_pins);
841 assert!(verify_proof_and_pinned_nodes::<Sha256, _, _>(
842 &child_proof_after,
843 child_start,
844 &child_ops,
845 &child_pins,
846 &child_root
847 ));
848 let (db, child_range) = db.apply_batch(child).await.unwrap();
849 assert_eq!(child_range, child_start..child_end);
850
851 let commit_floor = db.size();
853 let commit_only = db
854 .new_batch()
855 .merkleize(&db, Some(U64::new(16)), commit_floor)
856 .await;
857 let (commit_start, commit_ops) = commit_only.operations();
858 let commit_end = commit_only.bounds().tip.size;
859 let commit_root = commit_only.root();
860 let commit_proof = commit_only.proof(&db).unwrap();
861 let commit_pins = commit_only.pinned_nodes(&db).unwrap();
862 assert_eq!(commit_start, commit_floor);
863 assert!(matches!(
864 commit_ops.as_slice(),
865 [Operation::Commit(Some(metadata), operation_floor)]
866 if metadata == &U64::new(16) && operation_floor == &commit_floor
867 ));
868 assert_eq!(*commit_start + commit_ops.len() as u64, *commit_end);
869 assert_eq!(commit_proof.leaves, commit_end);
870 assert_eq!(
871 commit_proof.inactive_peaks,
872 F::inactive_peaks(commit_end, commit_floor)
873 );
874 assert!(verify_proof_and_pinned_nodes::<Sha256, _, _>(
875 &commit_proof,
876 commit_start,
877 &commit_ops,
878 &commit_pins,
879 &commit_root
880 ));
881 let (db, commit_range) = db.apply_batch(commit_only).await.unwrap();
882 assert_eq!(commit_range, commit_start..commit_end);
883 let db = db.sync().await.unwrap();
884
885 let late_parent = db
889 .new_batch()
890 .append(U64::new(17))
891 .merkleize(&db, Some(U64::new(18)), db.size())
892 .await;
893 let late = late_parent
894 .new_batch::<Sha256>()
895 .append(U64::new(19))
896 .merkleize(&db, Some(U64::new(20)), db.size())
897 .await;
898 let (db, _) = db.apply_batch(Arc::clone(&late_parent)).await.unwrap();
899 assert!(matches!(
900 late_parent.proof(&db),
901 Err(Error::Merkle(crate::merkle::Error::ElementPruned(_)))
902 ));
903 assert!(matches!(
904 late_parent.pinned_nodes(&db),
905 Err(Error::Merkle(crate::merkle::Error::ElementPruned(_)))
906 ));
907 drop(late_parent);
908
909 let (late_start, late_ops) = late.operations();
910 let late_root = late.root();
911 let late_proof = late.proof(&db).unwrap();
912 let late_pins = late.pinned_nodes(&db).unwrap();
913 assert!(verify_proof_and_pinned_nodes::<Sha256, _, _>(
914 &late_proof,
915 late_start,
916 &late_ops,
917 &late_pins,
918 &late_root
919 ));
920 let (db, _) = db.apply_batch(late).await.unwrap();
921
922 db.destroy().await.unwrap();
923 }
924
925 #[test_traced]
926 fn test_compact_operations_and_proof_mmr() {
927 deterministic::Runner::default().start(compact_operations_and_proof_inner::<mmr::Family>);
928 }
929
930 #[test_traced]
931 fn test_compact_operations_and_proof_mmb() {
932 deterministic::Runner::default().start(compact_operations_and_proof_inner::<mmb::Family>);
933 }
934
935 async fn open_witness_journal(
937 context: deterministic::Context,
938 partition: &str,
939 ) -> witness::Journal<deterministic::Context, mmr::Family, Digest> {
940 let cfg = witness_config(partition, &context);
941 witness::Journal::init(context, cfg).await.unwrap()
942 }
943
944 #[test_traced("INFO")]
947 fn test_serve_refuses_requests_outside_witness() {
948 deterministic::Runner::default().start(|context| async move {
949 let db = open_db::<mmr::Family>(context.child("db"), "keyless-serve-refusal").await;
950 let floor = db.inactivity_floor_loc();
951 let batch = db
952 .new_batch()
953 .append(U64::new(1))
954 .merkleize(&db, Some(U64::new(11)), floor)
955 .await;
956 let (db, _) = db.apply_batch(batch).await.unwrap();
957 let db = db.sync().await.unwrap();
958 let n = db.target().size;
959 let boundary = |size: Location<mmr::Family>, start: Location<mmr::Family>| {
960 Request::Boundary { size, start }
961 };
962 let operations =
963 |size: Location<mmr::Family>, start: Location<mmr::Family>| Request::Operations {
964 size,
965 start,
966 max_ops: NZU64!(1),
967 };
968
969 let beyond = n + 1;
970 assert!(matches!(
971 db.serve(boundary(beyond, n)).await,
972 Err(Error::Merkle(crate::merkle::Error::RangeOutOfBounds(_)))
973 ));
974 assert!(matches!(
975 db.serve(operations(Location::new(0), Location::new(0)))
976 .await,
977 Err(Error::Merkle(crate::merkle::Error::RangeOutOfBounds(_)))
978 ));
979 assert!(matches!(
980 db.serve(operations(n - 1, n - 2)).await,
981 Err(Error::Journal(crate::journal::Error::ItemPruned(_)))
982 ));
983 assert!(matches!(
984 db.serve(boundary(n, n)).await,
985 Err(Error::Merkle(crate::merkle::Error::RangeOutOfBounds(_)))
986 ));
987 assert!(matches!(
988 db.serve(boundary(n, n - 2)).await,
989 Err(Error::Journal(crate::journal::Error::ItemPruned(_)))
990 ));
991
992 let (response, feedback_tx) = db
995 .serve(Request::Operations {
996 size: n,
997 start: n - 1,
998 max_ops: NZU64!(5),
999 })
1000 .await
1001 .unwrap();
1002 assert!(feedback_tx.is_none());
1003 let Response::Operations { operations, .. } = response else {
1004 panic!("operations request should get an operations response");
1005 };
1006 assert_eq!(operations.len(), 1);
1007 let (response, _) = db.serve(boundary(n, n - 1)).await.unwrap();
1008 assert!(matches!(response, Response::Boundary { .. }));
1009 });
1010 }
1011
1012 type DelayedDb = Db<
1014 mmr::Family,
1015 DelayedSyncContext<deterministic::Context>,
1016 FixedEncoding<U64>,
1017 Sha256,
1018 (),
1019 Sequential,
1020 >;
1021
1022 fn open_delayed_db(
1027 context: &deterministic::Context,
1028 label: &'static str,
1029 partition: &str,
1030 pending: &PendingSyncs,
1031 ) -> impl Future<Output = Result<DelayedDb, Error<mmr::Family>>> {
1032 let witness_cfg = witness_config(partition, context);
1033 let merkle = crate::merkle::compact::Merkle::new(Sequential);
1034 let context = DelayedSyncContext {
1035 inner: context.child(label),
1036 pending: pending.clone(),
1037 };
1038 DelayedDb::init_from_merkle(merkle, context.child("witness"), witness_cfg, ())
1039 }
1040
1041 async fn apply_append(db: DelayedDb, seed: u64) -> DelayedDb {
1043 let floor = db.inactivity_floor_loc();
1044 let batch = db
1045 .new_batch()
1046 .append(U64::new(seed))
1047 .merkleize(&db, Some(U64::new(seed)), floor)
1048 .await;
1049 let (db, _) = db.apply_batch(batch).await.unwrap();
1050 db
1051 }
1052
1053 async fn fail_dropped_watermark_sync(mut db: DelayedDb, pending: &PendingSyncs) -> DelayedDb {
1055 let first;
1057 (db, first) = db.start_sync().await.unwrap();
1058 drive_pending_syncs(pending, first).await.unwrap();
1059
1060 let dropped;
1061 (db, dropped) = db.start_sync().await.unwrap();
1062 assert_eq!(
1063 pending.lock().len(),
1064 1,
1065 "expected only the recovery-watermark sync"
1066 );
1067 fail_pending_syncs(pending);
1068 drop(dropped);
1069 db
1070 }
1071
1072 #[test_traced]
1074 fn test_compact_apply_overlaps_start_sync() {
1075 deterministic::Runner::default().start(|ctx| async move {
1076 let partition = "keyless-start-sync-overlap";
1077 let pending = PendingSyncs::default();
1078 let open = open_delayed_db(&ctx, "delayed", partition, &pending);
1079 let mut db = drive_pending_syncs(&pending, open).await.unwrap();
1080 db = apply_append(db, 1).await;
1081
1082 let starts_before = pending.starts();
1083 let entered_before = pending.entered();
1084 let completions_before = pending.completions();
1085 let handle;
1086 (db, handle) = db.start_sync().await.unwrap();
1087 assert!(pending.starts() > starts_before);
1088 assert_eq!(pending.completions(), completions_before);
1089 let first_target = db.target();
1090
1091 let waiter = ctx
1092 .child("await_sync")
1093 .spawn(|_| async move { handle.await.unwrap() });
1094 while pending.entered() == entered_before {
1095 reschedule().await;
1096 }
1097
1098 db = apply_append(db, 2).await;
1099 assert_ne!(db.root(), first_target.root);
1100 let second_target = db.target();
1101 assert_ne!(second_target, first_target);
1102 assert_eq!(
1103 pending.completions(),
1104 completions_before,
1105 "the database made progress while the sync was still in flight"
1106 );
1107
1108 pending.unblock();
1109 waiter.await.unwrap();
1110
1111 let handle;
1113 (db, handle) = db.start_sync().await.unwrap();
1114 handle.await.unwrap();
1115 drop(db);
1116
1117 let db = open_delayed_db(&ctx, "reopen", partition, &pending)
1118 .await
1119 .unwrap();
1120 assert_eq!(db.target(), second_target);
1121 assert_eq!(db.get_metadata(), Some(U64::new(2)));
1122 let db = db.rewind(first_target.size).await.unwrap();
1123 assert_eq!(db.target(), first_target);
1124 db.destroy().await.unwrap();
1125 });
1126 }
1127
1128 #[test_traced]
1130 fn test_compact_start_sync_recovery() {
1131 deterministic::Runner::default().start(|ctx| async move {
1132 let partition = "keyless-start-sync-recovery";
1133 let pending = PendingSyncs::default();
1134 pending.unblock();
1135 let mut db = open_delayed_db(&ctx, "delayed", partition, &pending)
1136 .await
1137 .unwrap();
1138 db = apply_append(db, 1).await;
1139
1140 let handle;
1141 (db, handle) = db.start_sync().await.unwrap();
1142 handle.await.unwrap();
1143 let root = db.root();
1144 drop(db);
1145
1146 let db = open_delayed_db(&ctx, "reopen", partition, &pending)
1147 .await
1148 .unwrap();
1149 assert_eq!(db.root(), root);
1150 assert_eq!(db.get_metadata(), Some(U64::new(1)));
1151 db.destroy().await.unwrap();
1152 });
1153 }
1154
1155 #[test_traced]
1159 fn test_compact_start_sync_failure_propagates() {
1160 deterministic::Runner::default().start(|ctx| async move {
1161 let pending = PendingSyncs::default();
1162 pending.unblock();
1163 let mut db = open_delayed_db(&ctx, "delayed", "keyless-start-sync-fail", &pending)
1164 .await
1165 .unwrap();
1166 db = apply_append(db, 1).await;
1167
1168 pending.arm_fail();
1170
1171 let handle;
1172 (db, handle) = db.start_sync().await.unwrap();
1173 assert!(
1174 handle.await.is_err(),
1175 "the sync handle surfaces the failure"
1176 );
1177 let starts_before = pending.starts();
1178
1179 assert!(
1182 db.commit().await.is_err(),
1183 "the next durability op surfaces the failed in-flight sync"
1184 );
1185 assert_eq!(
1186 pending.starts(),
1187 starts_before,
1188 "the surfaced error is the retained failure, not a fresh sync's"
1189 );
1190 });
1191 }
1192
1193 #[test_traced]
1196 fn test_compact_start_sync_then_noop_sync_drains() {
1197 deterministic::Runner::default().start(|ctx| async move {
1198 let partition = "keyless-start-sync-noop-drain";
1199 let pending = PendingSyncs::default();
1200 let open = open_delayed_db(&ctx, "delayed", partition, &pending);
1201 let mut db = drive_pending_syncs(&pending, open).await.unwrap();
1202 db = apply_append(db, 1).await;
1203
1204 let starts_before = pending.starts();
1205 let completions_before = pending.completions();
1206 let handle;
1207 (db, handle) = db.start_sync().await.unwrap();
1208 assert!(pending.starts() > starts_before);
1209 assert_eq!(pending.completions(), completions_before);
1210 let root = db.root();
1211
1212 let db = {
1213 let mut sync = std::pin::pin!(db.sync());
1214 assert!(
1215 sync.as_mut().now_or_never().is_none(),
1216 "sync proceeded while the started sync was pending"
1217 );
1218 pending.unblock();
1219 sync.await.unwrap()
1220 };
1221 handle.await.unwrap();
1222 assert!(pending.completions() > completions_before);
1223 drop(db);
1224
1225 let db = open_delayed_db(&ctx, "reopen", partition, &pending)
1226 .await
1227 .unwrap();
1228 assert_eq!(db.root(), root);
1229 db.destroy().await.unwrap();
1230 });
1231 }
1232
1233 #[test_traced]
1235 fn test_compact_start_sync_then_noop_sync_fails_without_new_work() {
1236 deterministic::Runner::default().start(|ctx| async move {
1237 let pending = PendingSyncs::default();
1238 let open = open_delayed_db(
1239 &ctx,
1240 "delayed",
1241 "keyless-start-sync-noop-sync-fail",
1242 &pending,
1243 );
1244 let mut db = drive_pending_syncs(&pending, open).await.unwrap();
1245 db = apply_append(db, 1).await;
1246 db = fail_dropped_watermark_sync(db, &pending).await;
1247
1248 let starts_before = pending.starts();
1249 assert!(
1250 drive_pending_syncs(&pending, db.sync()).await.is_err(),
1251 "sync absorbed the retained metadata failure"
1252 );
1253 assert_eq!(
1254 pending.starts(),
1255 starts_before,
1256 "sync started new journal work before returning the retained failure"
1257 );
1258 });
1259 }
1260
1261 #[test_traced]
1265 fn test_compact_start_sync_then_noop_commit_waits() {
1266 deterministic::Runner::default().start(|ctx| async move {
1267 let pending = PendingSyncs::default();
1268 let open = open_delayed_db(&ctx, "delayed", "keyless-start-sync-noop-commit", &pending);
1269 let mut db = drive_pending_syncs(&pending, open).await.unwrap();
1270 db = apply_append(db, 1).await;
1271
1272 let handle;
1273 (db, handle) = db.start_sync().await.unwrap();
1274 let starts_before = pending.starts();
1275
1276 let db = {
1277 let mut commit = std::pin::pin!(db.commit());
1278 assert!(
1279 commit.as_mut().now_or_never().is_none(),
1280 "commit proceeded while the started sync was pending"
1281 );
1282 pending.unblock();
1283 commit.await.unwrap()
1284 };
1285 handle.await.unwrap();
1286 assert_eq!(
1287 pending.starts(),
1288 starts_before,
1289 "a successful pipelined sync still triggered journal work"
1290 );
1291 db.destroy().await.unwrap();
1292 });
1293 }
1294
1295 #[test_traced]
1298 fn test_compact_start_sync_noop_second_call() {
1299 deterministic::Runner::default().start(|ctx| async move {
1300 let partition = "keyless-start-sync-noop-second";
1301 let pending = PendingSyncs::default();
1302 let open = open_delayed_db(&ctx, "delayed", partition, &pending);
1303 let mut db = drive_pending_syncs(&pending, open).await.unwrap();
1304 db = apply_append(db, 1).await;
1305
1306 let h1;
1307 (db, h1) = db.start_sync().await.unwrap();
1308 pending.unblock();
1311 h1.await.unwrap();
1312
1313 let h2;
1314 (db, h2) = db.start_sync().await.unwrap();
1315 h2.await.unwrap();
1316 let root = db.root();
1317 drop(db);
1318
1319 let journal = open_witness_journal(ctx.child("probe"), partition).await;
1321 assert_eq!(journal.size(), 2);
1322 drop(journal);
1323
1324 let db = open_delayed_db(&ctx, "reopen", partition, &pending)
1325 .await
1326 .unwrap();
1327 assert_eq!(db.root(), root);
1328 db.destroy().await.unwrap();
1329 });
1330 }
1331
1332 #[test_traced]
1335 fn test_compact_start_sync_rewind_fast_path_drains() {
1336 deterministic::Runner::default().start(|ctx| async move {
1337 let partition = "keyless-start-sync-rewind-drain";
1338 let pending = PendingSyncs::default();
1339 let open = open_delayed_db(&ctx, "delayed", partition, &pending);
1340 let mut db = drive_pending_syncs(&pending, open).await.unwrap();
1341 db = apply_append(db, 1).await;
1342
1343 let handle;
1344 (db, handle) = db.start_sync().await.unwrap();
1345 let root = db.root();
1346 let size = db.size();
1347
1348 let starts_before = pending.starts();
1349 let db = {
1350 let mut rewind = std::pin::pin!(db.rewind(size));
1351 assert!(
1352 rewind.as_mut().now_or_never().is_none(),
1353 "rewind proceeded while the started sync was pending"
1354 );
1355 pending.unblock();
1356 rewind.await.unwrap()
1357 };
1358 handle.await.unwrap();
1359 assert_eq!(
1360 pending.starts(),
1361 starts_before,
1362 "the fast path started journal work instead of adopting the proven sync"
1363 );
1364 assert_eq!(db.root(), root);
1365 drop(db);
1366
1367 let db = open_delayed_db(&ctx, "reopen", partition, &pending)
1369 .await
1370 .unwrap();
1371 assert_eq!(db.root(), root);
1372 db.destroy().await.unwrap();
1373 });
1374 }
1375
1376 #[test_traced]
1379 fn test_compact_start_sync_rewind_fast_path_fails() {
1380 deterministic::Runner::default().start(|ctx| async move {
1381 let pending = PendingSyncs::default();
1382 pending.unblock();
1383 let mut db =
1384 open_delayed_db(&ctx, "delayed", "keyless-start-sync-rewind-fail", &pending)
1385 .await
1386 .unwrap();
1387 db = apply_append(db, 1).await;
1388
1389 pending.arm_fail();
1390 let handle;
1391 (db, handle) = db.start_sync().await.unwrap();
1392 assert!(handle.await.is_err());
1393 let size = db.size();
1394 assert!(
1395 db.rewind(size).await.is_err(),
1396 "rewind reported an unproven tip as durable"
1397 );
1398 });
1399 }
1400
1401 #[test_traced]
1404 fn test_compact_start_sync_metadata_failure_resurfaces_on_commit() {
1405 deterministic::Runner::default().start(|ctx| async move {
1406 let pending = PendingSyncs::default();
1407 let open = open_delayed_db(&ctx, "delayed", "keyless-start-sync-meta-fail", &pending);
1408 let mut db = drive_pending_syncs(&pending, open).await.unwrap();
1409 db = apply_append(db, 1).await;
1410
1411 let handle;
1412 (db, handle) = db.start_sync().await.unwrap();
1413
1414 {
1417 let mut parked = pending.lock();
1418 assert_eq!(
1419 parked.len(),
1420 2,
1421 "expected the data and offsets syncs parked"
1422 );
1423 let offsets = parked.pop().unwrap();
1424 let data = parked.pop().unwrap();
1425 data.release.send(Ok(())).unwrap();
1426 offsets
1427 .release
1428 .send(Err(commonware_runtime::Error::Io(
1429 std::io::Error::other("injected sync failure").into(),
1430 )))
1431 .unwrap();
1432 }
1433 assert!(
1434 handle.await.is_err(),
1435 "the sync handle surfaces the failure"
1436 );
1437
1438 pending.unblock();
1440
1441 let db = apply_append(db, 2).await;
1444 assert!(
1445 db.commit().await.is_err(),
1446 "commit absorbed the retained metadata failure"
1447 );
1448 });
1449 }
1450
1451 #[test_traced]
1453 fn test_compact_start_sync_retains_dropped_metadata_failure() {
1454 deterministic::Runner::default().start(|ctx| async move {
1455 let pending = PendingSyncs::default();
1456 let open = open_delayed_db(
1457 &ctx,
1458 "delayed",
1459 "keyless-start-sync-dropped-meta-fail",
1460 &pending,
1461 );
1462 let mut db = drive_pending_syncs(&pending, open).await.unwrap();
1463 db = apply_append(db, 1).await;
1464 db = fail_dropped_watermark_sync(db, &pending).await;
1466
1467 let next;
1470 (db, next) = db.start_sync().await.unwrap();
1471 assert!(
1472 drive_pending_syncs(&pending, next).await.is_err(),
1473 "a later start_sync masked the retained metadata failure"
1474 );
1475 drop(db);
1476 });
1477 }
1478
1479 #[test_traced]
1482 fn test_compact_start_sync_proven_skips_journal() {
1483 deterministic::Runner::default().start(|ctx| async move {
1484 let pending = PendingSyncs::default();
1485 pending.unblock();
1486 let mut db = open_delayed_db(&ctx, "delayed", "keyless-start-sync-proven", &pending)
1487 .await
1488 .unwrap();
1489 db = apply_append(db, 1).await;
1490
1491 let handle;
1492 (db, handle) = db.start_sync().await.unwrap();
1493 handle.await.unwrap();
1494
1495 let starts_before = pending.starts();
1496 let db = db.commit().await.unwrap();
1497 let size = db.size();
1498 let db = db.rewind(size).await.unwrap();
1499 assert_eq!(
1500 pending.starts(),
1501 starts_before,
1502 "a proven pipelined sync still triggered journal work"
1503 );
1504 db.destroy().await.unwrap();
1505 });
1506 }
1507
1508 #[test_traced("INFO")]
1511 fn test_compact_start_sync_persists_import() {
1512 deterministic::Runner::default().start(|context| async move {
1513 let dst = "keyless-import-start-sync-dst";
1514 let src = "keyless-import-start-sync-src";
1515 let meta_a = U64::new(11);
1516 let meta_b = U64::new(22);
1517
1518 let target_b = {
1520 let source = open_db::<mmr::Family>(context.child("src"), src).await;
1521 let batch = source
1522 .new_batch()
1523 .append(U64::new(2))
1524 .merkleize(&source, Some(meta_b.clone()), Location::new(0))
1525 .await;
1526 let (source, _) = source.apply_batch(batch).await.unwrap();
1527 let source = source.sync().await.unwrap();
1528 source.target()
1529 };
1530 let (_, size_b, pinned_b) = {
1531 let journal = open_witness_journal(context.child("src_tip"), src).await;
1532 witness::tests::tip(&journal).await
1533 };
1534 assert_eq!(size_b, target_b.size);
1535
1536 {
1538 let seeded = open_db::<mmr::Family>(context.child("seed"), dst).await;
1539 let batch = seeded
1540 .new_batch()
1541 .append(U64::new(1))
1542 .merkleize(&seeded, Some(meta_a), Location::new(0))
1543 .await;
1544 let (seeded, _) = seeded.apply_batch(batch).await.unwrap();
1545 let seeded = seeded.sync().await.unwrap();
1546 assert_ne!(seeded.target(), target_b);
1547 }
1548
1549 {
1551 let journal = open_witness_journal(context.child("import"), dst).await;
1552 let imported = TestDb::<mmr::Family>::init_from_sync(
1553 Sequential,
1554 journal,
1555 (),
1556 size_b - 1,
1557 pinned_b,
1558 Operation::Commit(Some(meta_b.clone()), Location::new(0)),
1559 )
1560 .unwrap();
1561 assert_eq!(imported.target(), target_b);
1562 let (_imported, handle) = imported.start_sync().await.unwrap();
1563 handle.await.unwrap();
1564 }
1565
1566 let db = open_db::<mmr::Family>(context.child("reopen"), dst).await;
1568 assert_eq!(db.target(), target_b);
1569 assert_eq!(db.root(), target_b.root);
1570 assert_eq!(db.get_metadata(), Some(meta_b));
1571 db.destroy().await.unwrap();
1572 });
1573 }
1574
1575 #[test_traced("INFO")]
1576 fn test_compact_stale_batch_rejected() {
1577 deterministic::Runner::default().start(|context| async move {
1578 let db = open_db::<mmr::Family>(context.child("db"), "keyless-stale").await;
1579 let floor = db.inactivity_floor_loc();
1580
1581 let batch_a = db
1582 .new_batch()
1583 .append(U64::new(1))
1584 .merkleize(&db, Some(U64::new(11)), floor)
1585 .await;
1586 let batch_b = db
1587 .new_batch()
1588 .append(U64::new(2))
1589 .merkleize(&db, Some(U64::new(22)), floor)
1590 .await;
1591
1592 let expected_root = batch_a.root();
1593 let (db, _) = db.apply_batch(batch_a).await.unwrap();
1594 assert_eq!(db.root(), expected_root);
1595 assert!(matches!(
1596 db.apply_batch(batch_b).await,
1597 Err(Error::StaleBatch)
1598 ));
1599 });
1600 }
1601
1602 #[test_traced("INFO")]
1603 fn test_compact_delayed_merkleize_after_ancestor_apply() {
1604 deterministic::Runner::default().start(|context| async move {
1605 let db = open_db::<mmr::Family>(context.child("db"), "keyless-delayed-child").await;
1606 let floor = db.inactivity_floor_loc();
1607
1608 let a = db
1609 .new_batch()
1610 .append(U64::new(1))
1611 .merkleize(&db, None, floor)
1612 .await;
1613 let b = a
1614 .new_batch::<Sha256>()
1615 .append(U64::new(2))
1616 .merkleize(&db, None, floor)
1617 .await;
1618 let c = b.new_batch::<Sha256>().append(U64::new(3));
1619
1620 let (db, _) = db.apply_batch(a).await.unwrap();
1621 let c = c.merkleize(&db, None, floor).await;
1622 let expected_root = c.root();
1623 let (db, _) = db.apply_batch(c).await.unwrap();
1624
1625 assert_eq!(db.root(), expected_root);
1626 });
1627 }
1628
1629 #[test_traced("INFO")]
1631 fn test_compact_to_batch_reflects_live_state() {
1632 deterministic::Runner::default().start(|context| async move {
1633 let db = open_db::<mmr::Family>(context.child("db"), "keyless-to-batch-live").await;
1634 let floor = db.inactivity_floor_loc();
1635
1636 let pre_apply_root = db.root();
1637 let pre_snapshot = db.to_batch();
1638 assert_eq!(
1639 pre_snapshot.root(),
1640 pre_apply_root,
1641 "snapshot before any mutation should match the live root"
1642 );
1643
1644 let batch = db
1645 .new_batch()
1646 .append(U64::new(1))
1647 .merkleize(&db, Some(U64::new(11)), floor)
1648 .await;
1649 let (db, _) = db.apply_batch(batch).await.unwrap();
1650
1651 let live_root = db.root();
1653 assert_ne!(
1654 live_root, pre_apply_root,
1655 "applying a non-empty batch must change the live root"
1656 );
1657
1658 let snapshot = db.to_batch();
1659 assert_eq!(
1660 snapshot.root(),
1661 live_root,
1662 "to_batch().root() must match the live db.root() even before sync"
1663 );
1664
1665 db.destroy().await.unwrap();
1666 });
1667 }
1668
1669 #[test_traced("INFO")]
1670 fn test_compact_stale_batch_chained() {
1671 deterministic::Runner::default().start(|context| async move {
1672 let db = open_db::<mmr::Family>(context.child("db"), "keyless-chained-stale").await;
1673 let floor = db.inactivity_floor_loc();
1674
1675 let common_parent = db
1676 .new_batch()
1677 .append(U64::new(10))
1678 .merkleize(&db, Some(U64::new(110)), floor)
1679 .await;
1680 let sibling_a = common_parent
1681 .new_batch::<Sha256>()
1682 .append(U64::new(11))
1683 .merkleize(&db, Some(U64::new(111)), floor)
1684 .await;
1685 let sibling_b = common_parent
1686 .new_batch::<Sha256>()
1687 .append(U64::new(12))
1688 .merkleize(&db, Some(U64::new(112)), floor)
1689 .await;
1690 let (db, _) = db.apply_batch(sibling_a).await.unwrap();
1691 assert!(matches!(
1692 db.validate_batch(&sibling_b),
1693 Err(Error::StaleBatch)
1694 ));
1695
1696 let parent_a = db
1697 .new_batch()
1698 .append(U64::new(1))
1699 .merkleize(&db, Some(U64::new(11)), floor)
1700 .await;
1701 let parent_b = db
1702 .new_batch()
1703 .append(U64::new(2))
1704 .merkleize(&db, Some(U64::new(22)), floor)
1705 .await;
1706 let child_b = parent_b
1707 .new_batch::<Sha256>()
1708 .append(U64::new(3))
1709 .merkleize(&db, Some(U64::new(33)), floor)
1710 .await;
1711
1712 let (db, _) = db.apply_batch(parent_a).await.unwrap();
1713 assert!(matches!(
1714 db.validate_batch(&child_b),
1715 Err(Error::StaleBatch)
1716 ));
1717 db.destroy().await.unwrap();
1718 });
1719 }
1720
1721 #[test_traced("INFO")]
1722 fn test_compact_stale_parent_after_child_applied() {
1723 deterministic::Runner::default().start(|context| async move {
1724 let db =
1725 open_db::<mmr::Family>(context.child("db"), "keyless-child-before-parent").await;
1726 let floor = db.inactivity_floor_loc();
1727
1728 let parent = db
1729 .new_batch()
1730 .append(U64::new(1))
1731 .merkleize(&db, Some(U64::new(11)), floor)
1732 .await;
1733 let child = parent
1734 .new_batch::<Sha256>()
1735 .append(U64::new(2))
1736 .merkleize(&db, Some(U64::new(22)), floor)
1737 .await;
1738
1739 let (db, _) = db.apply_batch(child).await.unwrap();
1740 assert!(matches!(
1741 db.apply_batch(parent).await,
1742 Err(Error::StaleBatch)
1743 ));
1744 });
1745 }
1746
1747 #[test_traced("INFO")]
1748 fn test_compact_sequential_commit_parent_then_child() {
1749 deterministic::Runner::default().start(|context| async move {
1750 let db = open_db::<mmr::Family>(context.child("db"), "keyless-parent-child").await;
1751 let floor = db.inactivity_floor_loc();
1752
1753 let parent = db
1754 .new_batch()
1755 .append(U64::new(1))
1756 .merkleize(&db, Some(U64::new(11)), floor)
1757 .await;
1758 let child = parent
1759 .new_batch::<Sha256>()
1760 .append(U64::new(2))
1761 .merkleize(&db, Some(U64::new(22)), floor)
1762 .await;
1763 let expected_root = child.root();
1764
1765 let (db, _) = db.apply_batch(parent).await.unwrap();
1766 let (db, _) = db.apply_batch(child).await.unwrap();
1767 let db = db.sync().await.unwrap();
1768
1769 assert_eq!(db.root(), expected_root);
1770
1771 db.destroy().await.unwrap();
1772 });
1773 }
1774
1775 #[test_traced("INFO")]
1776 fn test_compact_floor_regressed() {
1777 deterministic::Runner::default().start(|context| async move {
1778 let db = open_db::<mmr::Family>(context.child("db"), "keyless-floor-regressed").await;
1779
1780 let advance_floor = db.new_batch().append(U64::new(1));
1781 let advance_floor = advance_floor.merkleize(&db, None, Location::new(1)).await;
1782 let (db, _) = db.apply_batch(advance_floor).await.unwrap();
1783 let db = db.sync().await.unwrap();
1784 let target = db.target();
1785
1786 let regressed = db
1787 .new_batch()
1788 .append(U64::new(2))
1789 .merkleize(&db, None, Location::new(0))
1790 .await;
1791
1792 assert!(matches!(
1793 db.apply_batch(regressed).await,
1794 Err(Error::FloorRegressed(new, current))
1795 if new == Location::new(0) && current == Location::new(1)
1796 ));
1797
1798 let db =
1800 open_db::<mmr::Family>(context.child("reopen"), "keyless-floor-regressed").await;
1801 assert_eq!(db.target(), target);
1802 });
1803 }
1804
1805 #[test_traced("INFO")]
1809 fn test_compact_ancestor_floor_regressed() {
1810 deterministic::Runner::default().start(|context| async move {
1811 let db =
1812 open_db::<mmr::Family>(context.child("db"), "keyless-ancestor-floor-regressed")
1813 .await;
1814
1815 let parent = db
1817 .new_batch()
1818 .append(U64::new(1))
1819 .merkleize(&db, None, Location::new(2))
1820 .await;
1821 let child = parent
1823 .new_batch::<Sha256>()
1824 .append(U64::new(2))
1825 .merkleize(&db, None, Location::new(1))
1826 .await;
1827
1828 let target = db.target();
1829 assert!(matches!(
1830 db.apply_batch(child).await,
1831 Err(Error::FloorRegressed(new, prev))
1832 if new == Location::new(1) && prev == Location::new(2)
1833 ));
1834
1835 let db =
1837 open_db::<mmr::Family>(context.child("reopen"), "keyless-ancestor-floor-regressed")
1838 .await;
1839 assert_eq!(db.target(), target);
1840 });
1841 }
1842
1843 #[test_traced("INFO")]
1844 fn test_compact_rewind_restores_commit_metadata_and_floor() {
1845 deterministic::Runner::default().start(|context| async move {
1846 let db = open_db::<mmr::Family>(context.child("db"), "keyless-rewind-meta").await;
1847
1848 let v1 = U64::new(1);
1849 let meta1 = U64::new(11);
1850 let floor1 = Location::new(0);
1851 let batch = db
1852 .new_batch()
1853 .append(v1)
1854 .merkleize(&db, Some(meta1.clone()), floor1)
1855 .await;
1856 let (db, _) = db.apply_batch(batch).await.unwrap();
1857 let db = db.sync().await.unwrap();
1858 let root_after_first = db.root();
1859 let size_after_first = db.size();
1860
1861 let v2 = U64::new(2);
1862 let meta2 = U64::new(22);
1863 let floor2 = Location::new(1);
1864 let batch = db
1865 .new_batch()
1866 .append(v2)
1867 .merkleize(&db, Some(meta2.clone()), floor2)
1868 .await;
1869 let (db, _) = db.apply_batch(batch).await.unwrap();
1870 let db = db.sync().await.unwrap();
1871 assert_eq!(db.get_metadata(), Some(meta2));
1872 assert_eq!(db.inactivity_floor_loc(), floor2);
1873
1874 let db = db.rewind(size_after_first).await.unwrap();
1875 assert_eq!(db.root(), root_after_first);
1876 assert_eq!(db.get_metadata(), Some(meta1));
1877 assert_eq!(db.inactivity_floor_loc(), floor1);
1878
1879 db.destroy().await.unwrap();
1880 });
1881 }
1882
1883 #[test_traced("INFO")]
1884 fn test_compact_rewind_persists_across_reopen() {
1885 deterministic::Runner::default().start(|context| async move {
1886 let partition = "keyless-rewind-reopen";
1887 let meta1 = U64::new(11);
1888 let floor1 = Location::new(0);
1889 let meta2 = U64::new(22);
1890 let floor2 = Location::new(1);
1891
1892 let root_after_first = {
1893 let db = open_db::<mmr::Family>(context.child("first"), partition).await;
1894 let batch = db
1895 .new_batch()
1896 .append(U64::new(1))
1897 .merkleize(&db, Some(meta1.clone()), floor1)
1898 .await;
1899 let (db, _) = db.apply_batch(batch).await.unwrap();
1900 let db = db.sync().await.unwrap();
1901 let root = db.root();
1902 let size_after_first = db.size();
1903
1904 let batch = db
1905 .new_batch()
1906 .append(U64::new(2))
1907 .merkleize(&db, Some(meta2), floor2)
1908 .await;
1909 let (db, _) = db.apply_batch(batch).await.unwrap();
1910 let db = db.sync().await.unwrap();
1911
1912 let _db = db.rewind(size_after_first).await.unwrap();
1913 root
1914 };
1915
1916 let db = open_db::<mmr::Family>(context.child("second"), partition).await;
1917 assert_eq!(db.root(), root_after_first);
1918 assert_eq!(db.get_metadata(), Some(meta1));
1919 assert_eq!(db.inactivity_floor_loc(), floor1);
1920
1921 db.destroy().await.unwrap();
1922 });
1923 }
1924
1925 #[test_traced("INFO")]
1926 fn test_compact_commit_persists_across_reopen() {
1927 deterministic::Runner::default().start(|context| async move {
1928 let partition = "keyless-commit-reopen";
1929 let meta1 = U64::new(11);
1930 let meta2 = U64::new(22);
1931
1932 let root_after_second = {
1933 let db = open_db::<mmr::Family>(context.child("first"), partition).await;
1934 let batch = db
1935 .new_batch()
1936 .append(U64::new(1))
1937 .merkleize(&db, Some(meta1), Location::new(0))
1938 .await;
1939 let (db, _) = db.apply_batch(batch).await.unwrap();
1940 let db = db.commit().await.unwrap();
1941
1942 let batch = db
1943 .new_batch()
1944 .append(U64::new(2))
1945 .merkleize(&db, Some(meta2.clone()), Location::new(1))
1946 .await;
1947 let (db, _) = db.apply_batch(batch).await.unwrap();
1948 let db = db.commit().await.unwrap();
1949 db.root()
1950 };
1951
1952 let db = open_db::<mmr::Family>(context.child("second"), partition).await;
1954 assert_eq!(db.root(), root_after_second);
1955 assert_eq!(db.get_metadata(), Some(meta2));
1956 assert_eq!(db.inactivity_floor_loc(), Location::new(1));
1957 db.destroy().await.unwrap();
1958 });
1959 }
1960
1961 #[test_traced("INFO")]
1962 fn test_compact_rewind_to_committed_entry_after_reopen() {
1963 deterministic::Runner::default().start(|context| async move {
1964 let partition = "keyless-commit-rewind-reopen";
1965 let meta1 = U64::new(11);
1966 let meta2 = U64::new(22);
1967
1968 let (root_a, size_a) = {
1969 let db = open_db::<mmr::Family>(context.child("first"), partition).await;
1970 let batch = db
1971 .new_batch()
1972 .append(U64::new(1))
1973 .merkleize(&db, Some(meta1.clone()), Location::new(0))
1974 .await;
1975 let (db, _) = db.apply_batch(batch).await.unwrap();
1976 let db = db.commit().await.unwrap();
1977 let root_a = db.root();
1978 let size_a = db.size();
1979
1980 let batch = db
1981 .new_batch()
1982 .append(U64::new(2))
1983 .merkleize(&db, Some(meta2), Location::new(1))
1984 .await;
1985 let (db, _) = db.apply_batch(batch).await.unwrap();
1986 let _db = db.commit().await.unwrap();
1987 (root_a, size_a)
1988 };
1989
1990 let db = open_db::<mmr::Family>(context.child("second"), partition).await;
1993 let db = db.rewind(size_a).await.unwrap();
1994 assert_eq!(db.root(), root_a);
1995 assert_eq!(db.get_metadata(), Some(meta1));
1996 db.destroy().await.unwrap();
1997 });
1998 }
1999
2000 #[test_traced("INFO")]
2001 fn test_compact_sync_after_commit() {
2002 deterministic::Runner::default().start(|context| async move {
2003 let partition = "keyless-sync-after-commit";
2004 let meta = U64::new(11);
2005
2006 let root = {
2007 let db = open_db::<mmr::Family>(context.child("first"), partition).await;
2008 let batch = db
2009 .new_batch()
2010 .append(U64::new(1))
2011 .merkleize(&db, Some(meta.clone()), Location::new(0))
2012 .await;
2013 let (db, _) = db.apply_batch(batch).await.unwrap();
2014 let db = db.commit().await.unwrap();
2015 let db = db.sync().await.unwrap();
2017 db.root()
2018 };
2019
2020 let db = open_db::<mmr::Family>(context.child("second"), partition).await;
2021 assert_eq!(db.root(), root);
2022 assert_eq!(db.get_metadata(), Some(meta));
2023 db.destroy().await.unwrap();
2024 });
2025 }
2026
2027 #[test_traced("INFO")]
2028 fn test_compact_import_persists_with_commit() {
2029 deterministic::Runner::default().start(|context| async move {
2030 let dst = "keyless-import-commit-dst";
2031 let src = "keyless-import-commit-src";
2032 let meta_a = U64::new(11);
2033 let meta_b = U64::new(22);
2034
2035 let (target_b, pinned_b) = {
2038 let source = open_db::<mmr::Family>(context.child("src"), src).await;
2039 let batch = source
2040 .new_batch()
2041 .append(U64::new(2))
2042 .merkleize(&source, Some(meta_b.clone()), Location::new(0))
2043 .await;
2044 let (source, _) = source.apply_batch(batch).await.unwrap();
2045 let source = source.sync().await.unwrap();
2046 let target = source.target();
2047 let (response, _) = source
2048 .serve(Request::Boundary {
2049 size: target.size,
2050 start: target.size - 1,
2051 })
2052 .await
2053 .unwrap();
2054 let Response::Boundary { pinned_nodes, .. } = response else {
2055 panic!("boundary request should get a boundary response");
2056 };
2057 (target, pinned_nodes)
2058 };
2059
2060 {
2062 let seeded = open_db::<mmr::Family>(context.child("seed"), dst).await;
2063 let batch = seeded
2064 .new_batch()
2065 .append(U64::new(1))
2066 .merkleize(&seeded, Some(meta_a), Location::new(0))
2067 .await;
2068 let (seeded, _) = seeded.apply_batch(batch).await.unwrap();
2069 let seeded = seeded.sync().await.unwrap();
2070 assert_ne!(seeded.target(), target_b);
2071 }
2072
2073 {
2075 let journal = open_witness_journal(context.child("import"), dst).await;
2076 let imported = TestDb::<mmr::Family>::init_from_sync(
2077 Sequential,
2078 journal,
2079 (),
2080 target_b.size - 1,
2081 pinned_b,
2082 Operation::Commit(Some(meta_b.clone()), Location::new(0)),
2083 )
2084 .unwrap();
2085 assert_eq!(imported.target(), target_b);
2086 let _imported = imported.commit().await.unwrap();
2087 }
2088
2089 let db = open_db::<mmr::Family>(context.child("reopen"), dst).await;
2092 assert_eq!(db.target(), target_b);
2093 assert_eq!(db.root(), target_b.root);
2094 assert_eq!(db.get_metadata(), Some(meta_b));
2095 db.destroy().await.unwrap();
2096 });
2097 }
2098
2099 #[test_traced("INFO")]
2100 fn test_compact_reopen_rejects_tampered_witness() {
2101 deterministic::Runner::default().start(|context| async move {
2102 let partition = "keyless-witness-tamper";
2103 let db = open_db::<mmr::Family>(context.child("db"), partition).await;
2104 let batch = db
2105 .new_batch()
2106 .append(U64::new(7))
2107 .merkleize(&db, Some(U64::new(11)), Location::new(1))
2108 .await;
2109 let (db, _) = db.apply_batch(batch).await.unwrap();
2110 let db = db.sync().await.unwrap();
2111 drop(db);
2112
2113 let journal = open_witness_journal(context.child("tamper"), partition).await;
2115 let (op_bytes, size, mut pinned_nodes) = witness::tests::tip(&journal).await;
2116 pinned_nodes.push(Sha256::fill(0xff));
2117 witness::tests::overwrite_tip(journal, op_bytes, size, pinned_nodes).await;
2118
2119 let merkle = crate::merkle::compact::Merkle::new(Sequential);
2120 let reopened = TestDb::<mmr::Family>::init_from_merkle(
2121 merkle,
2122 context.child("reopen_witness"),
2123 witness_config(partition, &context),
2124 (),
2125 )
2126 .await;
2127 assert!(matches!(reopened, Err(Error::DataCorrupted(_))));
2128 });
2129 }
2130
2131 #[test_traced("INFO")]
2132 fn test_compact_rewind_rejects_corrupt_target_entry() {
2133 deterministic::Runner::default().start(|context| async move {
2134 let partition = "keyless-corrupt-rewind-target";
2135 let db = open_db::<mmr::Family>(context.child("db"), partition).await;
2136 let batch = db
2137 .new_batch()
2138 .append(U64::new(1))
2139 .merkleize(&db, None, Location::new(1))
2140 .await;
2141 let (db, _) = db.apply_batch(batch).await.unwrap();
2142 let db = db.sync().await.unwrap();
2143 let rewind_target = db.target().size;
2144 let batch = db
2145 .new_batch()
2146 .append(U64::new(2))
2147 .merkleize(&db, None, Location::new(1))
2148 .await;
2149 let (db, _) = db.apply_batch(batch).await.unwrap();
2150 let db = db.sync().await.unwrap();
2151 let tip_target = db.target();
2152 drop(db);
2153
2154 let mut journal = open_witness_journal(context.child("corrupt"), partition).await;
2156 journal = witness::tests::corrupt_entry(journal, 1, |entry| {
2157 entry.pinned_nodes.push(Sha256::fill(0xff));
2158 })
2159 .await;
2160 drop(journal);
2161
2162 let merkle = crate::merkle::compact::Merkle::new(Sequential);
2164 let reopened = TestDb::<mmr::Family>::init_from_merkle(
2165 merkle,
2166 context.child("reopen"),
2167 witness_config(partition, &context),
2168 (),
2169 )
2170 .await
2171 .unwrap();
2172 assert_eq!(reopened.target(), tip_target);
2173
2174 assert!(matches!(
2176 reopened.rewind(rewind_target).await,
2177 Err(Error::DataCorrupted(_))
2178 ));
2179
2180 let merkle = crate::merkle::compact::Merkle::new(Sequential);
2182 let reopened = TestDb::<mmr::Family>::init_from_merkle(
2183 merkle,
2184 context.child("reopen2"),
2185 witness_config(partition, &context),
2186 (),
2187 )
2188 .await
2189 .unwrap();
2190 assert_eq!(reopened.target(), tip_target);
2191 reopened.destroy().await.unwrap();
2192 });
2193 }
2194
2195 #[test_traced("INFO")]
2196 fn test_compact_reopen_rejects_interrupted_import() {
2197 deterministic::Runner::default().start(|context| async move {
2198 let partition = "keyless-interrupted-import";
2199 let db = open_db::<mmr::Family>(context.child("db"), partition).await;
2200 let batch = db
2201 .new_batch()
2202 .append(U64::new(7))
2203 .merkleize(&db, Some(U64::new(11)), Location::new(1))
2204 .await;
2205 let (db, _) = db.apply_batch(batch).await.unwrap();
2206 let db = db.sync().await.unwrap();
2207 drop(db);
2208
2209 let journal = open_witness_journal(context.child("clear"), partition).await;
2212 let size = journal.size();
2213 let journal = journal.clear_to_size(size.max(1)).await.unwrap();
2214 drop(journal);
2215
2216 let merkle = crate::merkle::compact::Merkle::new(Sequential);
2218 let reopened = TestDb::<mmr::Family>::init_from_merkle(
2219 merkle,
2220 context.child("reopen_witness"),
2221 witness_config(partition, &context),
2222 (),
2223 )
2224 .await;
2225 assert!(matches!(reopened, Err(Error::Journal(_))));
2226 });
2227 }
2228
2229 #[test_traced("INFO")]
2230 fn test_compact_reopen_rejects_commit_floor_beyond_tip() {
2231 deterministic::Runner::default().start(|context| async move {
2232 let partition = "keyless-invalid-persisted-floor";
2233 let db = open_db::<mmr::Family>(context.child("db"), partition).await;
2234 let batch = db
2235 .new_batch()
2236 .append(U64::new(7))
2237 .merkleize(&db, Some(U64::new(11)), Location::new(1))
2238 .await;
2239 let (db, _) = db.apply_batch(batch).await.unwrap();
2240 let db = db.sync().await.unwrap();
2241 drop(db);
2242 let oversized_floor = Location::new(10);
2243
2244 let journal = open_witness_journal(context.child("tamper"), partition).await;
2246 let (_, size, pinned_nodes) = witness::tests::tip(&journal).await;
2247 let bad_op = Operation::<mmr::Family, FixedEncoding<U64>>::Commit(
2248 Some(U64::new(11)),
2249 oversized_floor,
2250 )
2251 .encode()
2252 .to_vec();
2253 witness::tests::overwrite_tip(journal, bad_op, size, pinned_nodes).await;
2254
2255 let merkle = crate::merkle::compact::Merkle::new(Sequential);
2256 let reopened = TestDb::<mmr::Family>::init_from_merkle(
2257 merkle,
2258 context.child("reopen_witness"),
2259 witness_config(partition, &context),
2260 (),
2261 )
2262 .await;
2263 assert!(matches!(
2264 reopened,
2265 Err(Error::DataCorrupted("invalid compact witness"))
2266 ));
2267 });
2268 }
2269
2270 #[test_traced("INFO")]
2271 fn test_compact_reopen_rejects_tampered_pinned_nodes() {
2272 deterministic::Runner::default().start(|context| async move {
2273 let partition = "keyless-pinned-nodes-tamper";
2274 let db = open_db::<mmr::Family>(context.child("db"), partition).await;
2275 let batch = db
2276 .new_batch()
2277 .append(U64::new(7))
2278 .merkleize(&db, Some(U64::new(11)), Location::new(1))
2279 .await;
2280 let (db, _) = db.apply_batch(batch).await.unwrap();
2281 let db = db.sync().await.unwrap();
2282 let tampered_target = db.target();
2283 drop(db);
2284
2285 let journal = open_witness_journal(context.child("tamper"), partition).await;
2289 let (op_bytes, size, mut pinned_nodes) = witness::tests::tip(&journal).await;
2290 pinned_nodes[0] = Sha256::fill(0xff);
2291 witness::tests::overwrite_tip(journal, op_bytes, size, pinned_nodes).await;
2292
2293 let merkle = crate::merkle::compact::Merkle::new(Sequential);
2294 let reopened = TestDb::<mmr::Family>::init_from_merkle(
2295 merkle,
2296 context.child("reopen_witness"),
2297 witness_config(partition, &context),
2298 (),
2299 )
2300 .await
2301 .unwrap();
2302 assert_ne!(reopened.target(), tampered_target);
2303 reopened.destroy().await.unwrap();
2304 });
2305 }
2306
2307 #[test_traced("INFO")]
2308 fn test_compact_rewind_to_current_is_noop() {
2309 deterministic::Runner::default().start(|context| async move {
2310 let db = open_db::<mmr::Family>(context.child("db"), "keyless-rewind-noop").await;
2311 let batch = db
2312 .new_batch()
2313 .append(U64::new(1))
2314 .merkleize(&db, Some(U64::new(11)), Location::new(0))
2315 .await;
2316 let (db, _) = db.apply_batch(batch).await.unwrap();
2317 let db = db.sync().await.unwrap();
2318 let root = db.root();
2319 let size = db.size();
2320
2321 let db = db.rewind(size).await.unwrap();
2322 assert_eq!(db.root(), root);
2323 assert_eq!(db.size(), size);
2324 db.destroy().await.unwrap();
2325 });
2326 }
2327
2328 #[test_traced("INFO")]
2329 fn test_compact_prune_past_tip_keeps_tip() {
2330 deterministic::Runner::default().start(|context| async move {
2331 let partition = "keyless-prune-past-tip";
2332 let db = open_db::<mmr::Family>(context.child("db"), partition).await;
2333 let batch = db
2334 .new_batch()
2335 .append(U64::new(1))
2336 .merkleize(&db, Some(U64::new(11)), Location::new(0))
2337 .await;
2338 let (db, _) = db.apply_batch(batch).await.unwrap();
2339 let db = db.sync().await.unwrap();
2340 let target = db.target();
2341
2342 let boundary = db.size() + 100;
2344 let db = db.prune(boundary).await.unwrap();
2345 assert_eq!(db.target(), target);
2346 drop(db);
2347
2348 let reopened = open_db::<mmr::Family>(context.child("reopen"), partition).await;
2349 assert_eq!(reopened.target(), target);
2350 reopened.destroy().await.unwrap();
2351 });
2352 }
2353
2354 #[test_traced("INFO")]
2355 fn test_compact_rewind_beyond_history() {
2356 deterministic::Runner::default().start(|context| async move {
2357 let db = open_db::<mmr::Family>(context.child("db"), "keyless-rewind-beyond").await;
2358 assert!(matches!(
2361 db.rewind(Location::new(0)).await,
2362 Err(Error::Merkle(crate::merkle::Error::RewindBeyondHistory))
2363 ));
2364
2365 let db = open_db::<mmr::Family>(context.child("reopen"), "keyless-rewind-beyond").await;
2366 let beyond_tip = db.size() + 100;
2368 assert!(matches!(
2369 db.rewind(beyond_tip).await,
2370 Err(Error::Merkle(crate::merkle::Error::RewindBeyondHistory))
2371 ));
2372 });
2373 }
2374
2375 #[test_traced("INFO")]
2376 fn test_compact_rewind_between_commits() {
2377 deterministic::Runner::default().start(|context| async move {
2378 let db = open_db::<mmr::Family>(context.child("db"), "keyless-rewind-between").await;
2379 let floor = db.inactivity_floor_loc();
2380
2381 let batch = db
2383 .new_batch()
2384 .append(U64::new(1))
2385 .append(U64::new(2))
2386 .merkleize(&db, Some(U64::new(11)), floor)
2387 .await;
2388 let (db, _) = db.apply_batch(batch).await.unwrap();
2389 let db = db.sync().await.unwrap();
2390 let root_a = db.root();
2391 let size_a = db.size();
2392 assert_eq!(size_a, Location::new(4));
2393
2394 let batch = db
2396 .new_batch()
2397 .append(U64::new(3))
2398 .merkleize(&db, Some(U64::new(22)), floor)
2399 .await;
2400 let (db, _) = db.apply_batch(batch).await.unwrap();
2401 let db = db.sync().await.unwrap();
2402 let root_b = db.root();
2403
2404 let mut db = db;
2407 for target in [2u64, 3, 5] {
2408 assert!(matches!(
2409 db.rewind(Location::new(target)).await,
2410 Err(Error::Merkle(crate::merkle::Error::RewindBeyondHistory))
2411 ));
2412 db = open_db::<mmr::Family>(
2413 context.child("reopen").with_attribute("target", target),
2414 "keyless-rewind-between",
2415 )
2416 .await;
2417 }
2418 assert_eq!(db.root(), root_b);
2419
2420 let db = db.rewind(size_a).await.unwrap();
2422 assert_eq!(db.root(), root_a);
2423 assert_eq!(db.get_metadata(), Some(U64::new(11)));
2424 db.destroy().await.unwrap();
2425 });
2426 }
2427
2428 #[test_traced("INFO")]
2431 fn test_compact_reopen_drops_unsynced_witness() {
2432 deterministic::Runner::default().start(|context| async move {
2433 let partition = "keyless-witness-unsynced";
2434 let db = open_db::<mmr::Family>(context.child("db"), partition).await;
2435
2436 let batch = db
2438 .new_batch()
2439 .append(U64::new(1))
2440 .merkleize(&db, Some(U64::new(11)), Location::new(1))
2441 .await;
2442 let (db, _) = db.apply_batch(batch).await.unwrap();
2443 let db = db.sync().await.unwrap();
2444 let target_a = db.target();
2445 drop(db);
2446
2447 let journal = open_witness_journal(context.child("crash"), partition).await;
2450 let (op_bytes, mut size, pinned_nodes) = witness::tests::tip(&journal).await;
2451 size += 2;
2452 witness::tests::append_unsynced(journal, op_bytes, size, pinned_nodes).await;
2453
2454 let reopened = open_db::<mmr::Family>(context.child("reopen"), partition).await;
2456 assert_eq!(reopened.target(), target_a);
2457 reopened.destroy().await.unwrap();
2458 });
2459 }
2460
2461 #[test_traced("INFO")]
2462 fn test_compact_rewind_multiple_commits() {
2463 deterministic::Runner::default().start(|context| async move {
2464 let partition = "keyless-rewind-multi";
2465 let db = open_db::<mmr::Family>(context.child("db"), partition).await;
2466
2467 let batch = db
2469 .new_batch()
2470 .append(U64::new(1))
2471 .merkleize(&db, Some(U64::new(11)), Location::new(0))
2472 .await;
2473 let (db, _) = db.apply_batch(batch).await.unwrap();
2474 let db = db.sync().await.unwrap();
2475 let root_a = db.root();
2476 let size_a = db.size();
2477 let target_a = db.target();
2478
2479 let mut db = db;
2480 for i in [2u64, 3] {
2481 let batch = db
2482 .new_batch()
2483 .append(U64::new(i))
2484 .merkleize(&db, Some(U64::new(i * 11)), Location::new(0))
2485 .await;
2486 (db, _) = db.apply_batch(batch).await.unwrap();
2487 db = db.sync().await.unwrap();
2488 }
2489 assert_ne!(db.root(), root_a);
2490
2491 let db = db.rewind(size_a).await.unwrap();
2493 assert_eq!(db.root(), root_a);
2494 assert_eq!(db.size(), size_a);
2495 assert_eq!(db.get_metadata(), Some(U64::new(11)));
2496 assert_eq!(db.target(), target_a);
2497 drop(db);
2498
2499 let db = open_db::<mmr::Family>(context.child("reopen"), partition).await;
2501 assert_eq!(db.root(), root_a);
2502 assert_eq!(db.target(), target_a);
2503 db.destroy().await.unwrap();
2504 });
2505 }
2506
2507 #[test_traced("INFO")]
2508 fn test_compact_prune_then_rewind() {
2509 deterministic::Runner::default().start(|context| async move {
2510 let mut witness_cfg = witness_config("keyless-prune-rewind", &context);
2513 witness_cfg.items_per_section = NZU64!(1);
2514 let merkle = crate::merkle::compact::Merkle::new(Sequential);
2515 let mut db: TestDb<mmr::Family> =
2516 Db::init_from_merkle(merkle, context.child("witness"), witness_cfg.clone(), ())
2517 .await
2518 .unwrap();
2519
2520 let mut sizes = Vec::new();
2522 for i in [1u64, 2, 3] {
2523 let batch = db
2524 .new_batch()
2525 .append(U64::new(i))
2526 .merkleize(&db, Some(U64::new(i * 11)), Location::new(0))
2527 .await;
2528 (db, _) = db.apply_batch(batch).await.unwrap();
2529 db = db.sync().await.unwrap();
2530 sizes.push(db.size());
2531 }
2532
2533 let db = db.prune(sizes[1]).await.unwrap();
2535 assert!(matches!(
2536 db.rewind(sizes[0]).await,
2537 Err(Error::Merkle(crate::merkle::Error::RewindBeyondHistory))
2538 ));
2539
2540 let merkle = crate::merkle::compact::Merkle::new(Sequential);
2542 let db: TestDb<mmr::Family> = Db::init_from_merkle(
2543 merkle,
2544 context.child("witness").with_attribute("index", 2),
2545 witness_cfg,
2546 (),
2547 )
2548 .await
2549 .unwrap();
2550 let db = db.rewind(sizes[1]).await.unwrap();
2551 assert_eq!(db.size(), sizes[1]);
2552 assert_eq!(db.get_metadata(), Some(U64::new(22)));
2553
2554 db.destroy().await.unwrap();
2555 });
2556 }
2557
2558 #[test_traced("INFO")]
2559 fn test_compact_rewind_preserves_pre_advance_batch() {
2560 deterministic::Runner::default().start(|context| async move {
2561 let db =
2562 open_db::<mmr::Family>(context.child("db"), "keyless-rewind-preserves-pre-advance")
2563 .await;
2564
2565 let batch = db
2566 .new_batch()
2567 .append(U64::new(1))
2568 .merkleize(&db, None, Location::new(0))
2569 .await;
2570 let (db, _) = db.apply_batch(batch).await.unwrap();
2571 let db = db.sync().await.unwrap();
2572 let size_after_first = db.size();
2573
2574 let held = db
2576 .new_batch()
2577 .append(U64::new(2))
2578 .merkleize(&db, None, Location::new(0))
2579 .await;
2580
2581 let batch = db
2583 .new_batch()
2584 .append(U64::new(3))
2585 .merkleize(&db, None, Location::new(0))
2586 .await;
2587 let (db, _) = db.apply_batch(batch).await.unwrap();
2588 let db = db.sync().await.unwrap();
2589 let db = db.rewind(size_after_first).await.unwrap();
2590
2591 let (db, _) = db.apply_batch(held).await.unwrap();
2594
2595 db.destroy().await.unwrap();
2596 });
2597 }
2598
2599 #[test_traced("INFO")]
2600 fn test_compact_noop_commit_after_commit() {
2601 deterministic::Runner::default().start(|context| async move {
2602 let db = open_db::<mmr::Family>(context.child("db"), "keyless-noop-after-commit").await;
2603
2604 let batch = db
2605 .new_batch()
2606 .append(U64::new(1))
2607 .append(U64::new(2))
2608 .merkleize(&db, Some(U64::new(11)), Location::new(0))
2609 .await;
2610 let (db, _) = db.apply_batch(batch).await.unwrap();
2611 let db = db.sync().await.unwrap();
2612 let root_after_first = db.root();
2613 assert_eq!(db.size(), Location::new(4));
2614
2615 let db = db.sync().await.unwrap();
2616 assert_eq!(db.size(), Location::new(4));
2617 assert_eq!(db.root(), root_after_first);
2618 assert_eq!(db.target().root, db.root());
2619
2620 db.destroy().await.unwrap();
2621 });
2622 }
2623
2624 #[test_traced("INFO")]
2625 fn test_compact_noop_commit_after_reopen() {
2626 deterministic::Runner::default().start(|context| async move {
2627 let partition = "keyless-noop-after-reopen";
2628
2629 let root_before_drop = {
2630 let db = open_db::<mmr::Family>(context.child("first"), partition).await;
2631 let batch = db
2632 .new_batch()
2633 .append(U64::new(1))
2634 .append(U64::new(2))
2635 .merkleize(&db, Some(U64::new(11)), Location::new(0))
2636 .await;
2637 let (db, _) = db.apply_batch(batch).await.unwrap();
2638 let db = db.sync().await.unwrap();
2639 let root = db.root();
2640 assert_eq!(db.size(), Location::new(4));
2641 root
2642 };
2643
2644 let db = open_db::<mmr::Family>(context.child("second"), partition).await;
2645 assert_eq!(db.root(), root_before_drop);
2646 assert_eq!(db.size(), Location::new(4));
2647
2648 let db = db.sync().await.unwrap();
2649 assert_eq!(db.size(), Location::new(4));
2650 assert_eq!(db.root(), root_before_drop);
2651 assert_eq!(db.target().root, db.root());
2652
2653 db.destroy().await.unwrap();
2654 });
2655 }
2656
2657 #[test_traced("INFO")]
2658 fn test_compact_noop_commit_after_rewind() {
2659 deterministic::Runner::default().start(|context| async move {
2660 let db = open_db::<mmr::Family>(context.child("db"), "keyless-noop-after-rewind").await;
2661
2662 let batch = db
2663 .new_batch()
2664 .append(U64::new(1))
2665 .append(U64::new(2))
2666 .merkleize(&db, Some(U64::new(11)), Location::new(0))
2667 .await;
2668 let (db, _) = db.apply_batch(batch).await.unwrap();
2669 let db = db.sync().await.unwrap();
2670 let root_after_first = db.root();
2671
2672 let batch = db
2673 .new_batch()
2674 .append(U64::new(3))
2675 .merkleize(&db, Some(U64::new(22)), Location::new(1))
2676 .await;
2677 let (db, _) = db.apply_batch(batch).await.unwrap();
2678 let db = db.sync().await.unwrap();
2679
2680 let db = db.rewind(Location::new(4)).await.unwrap();
2681 assert_eq!(db.size(), Location::new(4));
2682 assert_eq!(db.root(), root_after_first);
2683
2684 let db = db.sync().await.unwrap();
2685 assert_eq!(db.size(), Location::new(4));
2686 assert_eq!(db.root(), root_after_first);
2687 assert_eq!(db.target().root, db.root());
2688
2689 db.destroy().await.unwrap();
2690 });
2691 }
2692
2693 #[test_traced("INFO")]
2694 fn test_compact_rewind_makes_post_advance_batch_stale() {
2695 deterministic::Runner::default().start(|context| async move {
2696 let db =
2697 open_db::<mmr::Family>(context.child("db"), "keyless-rewind-makes-stale").await;
2698
2699 let batch = db
2700 .new_batch()
2701 .append(U64::new(1))
2702 .merkleize(&db, None, Location::new(0))
2703 .await;
2704 let (db, _) = db.apply_batch(batch).await.unwrap();
2705 let db = db.sync().await.unwrap();
2706 let size_after_first = db.size();
2707
2708 let batch = db
2709 .new_batch()
2710 .append(U64::new(2))
2711 .merkleize(&db, None, Location::new(0))
2712 .await;
2713 let (db, _) = db.apply_batch(batch).await.unwrap();
2714 let db = db.sync().await.unwrap();
2715
2716 let held = db
2718 .new_batch()
2719 .append(U64::new(3))
2720 .merkleize(&db, None, Location::new(0))
2721 .await;
2722
2723 let db = db.rewind(size_after_first).await.unwrap();
2724
2725 assert!(matches!(db.apply_batch(held).await, Err(Error::StaleBatch)));
2728 });
2729 }
2730
2731 #[test_traced("INFO")]
2732 fn test_compact_floor_beyond_size() {
2733 deterministic::Runner::default().start(|context| async move {
2734 let db = open_db::<mmr::Family>(context.child("db"), "keyless-floor-beyond").await;
2735
2736 let batch = db.new_batch().merkleize(&db, None, Location::new(2)).await;
2737
2738 assert!(matches!(
2739 db.apply_batch(batch).await,
2740 Err(Error::FloorBeyondSize(floor, tip))
2741 if floor == Location::new(2) && tip == Location::new(1)
2742 ));
2743 });
2744 }
2745
2746 #[test_traced("INFO")]
2749 fn test_compact_ancestor_floor_beyond_size() {
2750 deterministic::Runner::default().start(|context| async move {
2751 let db =
2752 open_db::<mmr::Family>(context.child("db"), "keyless-ancestor-floor-beyond").await;
2753
2754 let parent = db
2756 .new_batch()
2757 .append(U64::new(1))
2758 .merkleize(&db, None, Location::new(3))
2759 .await;
2760 let child = parent
2762 .new_batch::<Sha256>()
2763 .append(U64::new(2))
2764 .merkleize(&db, None, Location::new(0))
2765 .await;
2766
2767 assert!(matches!(
2768 db.apply_batch(child).await,
2769 Err(Error::FloorBeyondSize(floor, commit))
2770 if floor == Location::new(3) && commit == Location::new(2)
2771 ));
2772 });
2773 }
2774}