1use super::operation::Operation;
26use crate::{
27 journal::contiguous::variable::{self, Config as JournalConfig},
28 merkle::{batch, compact as compact_merkle, Family, Location},
29 qmdb::{
30 self,
31 any::value::ValueEncoding,
32 batch_chain::{self, Bounds},
33 compact::{
34 batch as compact_batch,
35 witness::{self, VerifiedWitness, Witness},
36 },
37 sync::compact as compact_sync,
38 Error,
39 },
40 Context,
41};
42use commonware_codec::{Decode as _, Encode, EncodeShared, Read};
43use commonware_cryptography::{Digest, Hasher};
44use commonware_macros::boxed;
45use commonware_parallel::Strategy;
46use std::sync::{Arc, Weak};
47
48#[derive(Clone)]
50pub struct Config<C, S: Strategy> {
51 pub strategy: S,
53
54 pub witness: JournalConfig<()>,
56
57 pub commit_codec_config: C,
59}
60
61pub struct Db<F, E, V, H, C, S: Strategy>
64where
65 F: Family,
66 E: Context,
67 V: ValueEncoding,
68 H: Hasher,
69 Operation<F, V>: EncodeShared,
70 Operation<F, V>: Read<Cfg = C>,
71 C: Clone + Send + Sync + 'static,
72{
73 merkle: compact_merkle::Merkle<F, H::Digest, S>,
74 last_commit_loc: Location<F>,
75 last_commit_metadata: Option<V::Value>,
76 inactivity_floor_loc: Location<F>,
77 commit_codec_config: C,
78 witness: witness::Store<E, F, H::Digest>,
79}
80
81type CompactStateResult<F, V, D> =
82 Result<compact_sync::State<F, Operation<F, V>, D>, compact_sync::ServeError<F, D>>;
83
84#[allow(clippy::type_complexity)]
86pub struct UnmerkleizedBatch<F, H, V, S: Strategy>
87where
88 F: Family,
89 V: ValueEncoding,
90 H: Hasher,
91 Operation<F, V>: EncodeShared,
92{
93 merkle_batch: compact_merkle::UnmerkleizedBatch<F, H::Digest, S>,
94 appends: Vec<V::Value>,
95 parent: Option<Arc<MerkleizedBatch<F, H::Digest, V, S>>>,
96 base_size: u64,
97 db_size: u64,
98}
99
100#[derive(Clone)]
102pub struct MerkleizedBatch<F: Family, D: Digest, V: ValueEncoding, S: Strategy>
103where
104 Operation<F, V>: EncodeShared,
105{
106 pub(super) merkle_batch: Arc<batch::MerkleizedBatch<F, D, S>>,
107 pub(super) root: D,
108 pub(super) commit_metadata: Option<V::Value>,
109 pub(super) parent: Option<Weak<Self>>,
110 pub(super) bounds: batch_chain::Bounds<F>,
111}
112
113impl<F: Family, D: Digest, V: ValueEncoding, S: Strategy> MerkleizedBatch<F, D, V, S>
114where
115 Operation<F, V>: EncodeShared,
116{
117 pub(super) fn ancestors(&self) -> impl Iterator<Item = Arc<Self>> {
118 batch_chain::ancestors(self.parent.clone(), |batch| batch.parent.as_ref())
119 }
120
121 pub const fn root(&self) -> D {
123 self.root
124 }
125
126 pub const fn bounds(&self) -> &Bounds<F> {
128 &self.bounds
129 }
130
131 pub fn new_batch<H>(self: &Arc<Self>) -> UnmerkleizedBatch<F, H, V, S>
133 where
134 H: Hasher<Digest = D>,
135 {
136 UnmerkleizedBatch {
137 merkle_batch: compact_merkle::UnmerkleizedBatch::wrap(self.merkle_batch.new_batch()),
138 appends: Vec::new(),
139 parent: Some(Arc::clone(self)),
140 base_size: self.bounds.total_size,
141 db_size: self.bounds.db_size,
142 }
143 }
144}
145
146impl<F, H, V, S> UnmerkleizedBatch<F, H, V, S>
147where
148 F: Family,
149 V: ValueEncoding,
150 H: Hasher,
151 S: Strategy,
152 Operation<F, V>: EncodeShared,
153{
154 pub(super) fn new<E, C>(db: &Db<F, E, V, H, C, S>, committed_size: u64) -> Self
155 where
156 E: Context,
157 C: Clone + Send + Sync + 'static,
158 Operation<F, V>: Read<Cfg = C>,
159 {
160 Self {
161 merkle_batch: db.merkle.new_batch(),
162 appends: Vec::new(),
163 parent: None,
164 base_size: committed_size,
165 db_size: committed_size,
166 }
167 }
168
169 pub fn append(mut self, value: V::Value) -> Self {
170 self.appends.push(value);
171 self
172 }
173
174 #[tracing::instrument(
182 name = "qmdb.keyless.compact.batch.merkleize",
183 level = "info",
184 skip_all
185 )]
186 pub async fn merkleize<E, C>(
187 self,
188 db: &Db<F, E, V, H, C, S>,
189 metadata: Option<V::Value>,
190 inactivity_floor: Location<F>,
191 ) -> Arc<MerkleizedBatch<F, H::Digest, V, S>>
192 where
193 F: Family,
194 E: Context,
195 C: Clone + Send + Sync + 'static,
196 Operation<F, V>: Read<Cfg = C>,
197 {
198 let mut ops: Vec<Operation<F, V>> = Vec::with_capacity(self.appends.len() + 1);
199 for value in self.appends {
200 ops.push(Operation::Append(value));
201 }
202 ops.push(Operation::Commit(metadata.clone(), inactivity_floor));
203
204 let total_size = self.base_size + ops.len() as u64;
205 let inactive_peaks = F::inactive_peaks(
206 F::location_to_position(Location::new(total_size)),
207 inactivity_floor,
208 );
209 let (merkle, root) = compact_batch::merkleize_ops::<F, H, S, _>(
210 &db.merkle,
211 self.merkle_batch,
212 ops,
213 inactive_peaks,
214 )
215 .await
216 .expect("inactive_peaks computed from batch size");
217
218 let ancestors =
219 batch_chain::parent_and_ancestors(self.parent.as_ref(), |parent| parent.ancestors());
220 let ancestors = batch_chain::collect_ancestor_bounds(
221 ancestors,
222 |batch| batch.bounds.inactivity_floor,
223 |batch| batch.bounds.total_size,
224 );
225
226 Arc::new(MerkleizedBatch {
227 merkle_batch: merkle,
228 root,
229 commit_metadata: metadata,
230 parent: self.parent.as_ref().map(Arc::downgrade),
231 bounds: batch_chain::Bounds {
232 base_size: self.base_size,
233 db_size: self.db_size,
234 total_size,
235 ancestors,
236 inactivity_floor,
237 },
238 })
239 }
240}
241
242impl<F, E, V, H, C, S> Db<F, E, V, H, C, S>
243where
244 F: Family,
245 E: Context,
246 V: ValueEncoding,
247 H: Hasher,
248 S: Strategy,
249 Operation<F, V>: EncodeShared,
250 Operation<F, V>: Read<Cfg = C>,
251 C: Clone + Send + Sync + 'static,
252{
253 fn encode_commit_op(metadata: Option<V::Value>, inactivity_floor_loc: Location<F>) -> Vec<u8> {
254 Operation::<F, V>::Commit(metadata, inactivity_floor_loc)
255 .encode()
256 .to_vec()
257 }
258
259 pub(crate) fn init_from_validated_state(
266 strategy: S,
267 journal: witness::Journal<E, F, H::Digest>,
268 commit_codec_config: C,
269 validated: compact_sync::ValidatedState<F, Operation<F, V>, H::Digest>,
270 ) -> Result<Self, Error<F>> {
271 let compact_sync::ValidatedState {
272 state:
273 compact_sync::State {
274 leaf_count,
275 pinned_nodes,
276 last_commit_op,
277 last_commit_proof,
278 },
279 root,
280 } = validated;
281 let last_commit_loc = Location::new(*leaf_count - 1);
282 let Operation::Commit(last_commit_metadata, inactivity_floor_loc) = last_commit_op else {
283 return Err(Error::UnexpectedData(last_commit_loc));
284 };
285
286 let merkle =
287 compact_merkle::Merkle::from_compact_state(strategy, leaf_count, pinned_nodes.clone())?;
288 let imported = VerifiedWitness {
289 witness: Witness {
290 op_bytes: Self::encode_commit_op(
291 last_commit_metadata.clone(),
292 inactivity_floor_loc,
293 ),
294 proof: last_commit_proof,
295 pinned_nodes,
296 },
297 root,
298 };
299
300 let witness = witness::Store::from_import(journal, imported);
301 Ok(Self {
302 merkle,
303 last_commit_loc,
304 last_commit_metadata,
305 inactivity_floor_loc,
306 commit_codec_config,
307 witness,
308 })
309 }
310
311 #[boxed]
316 pub(crate) async fn init_from_merkle(
317 mut merkle: compact_merkle::Merkle<F, H::Digest, S>,
318 witness_context: E,
319 witness_config: JournalConfig<()>,
320 commit_codec_config: C,
321 ) -> Result<Self, Error<F>>
322 where
323 F: Family,
324 Operation<F, V>: Read<Cfg = C>,
325 {
326 let journal: witness::Journal<E, F, H::Digest> =
328 variable::Journal::init(witness_context, witness_config).await?;
329 let (witness, last_commit_op) = witness::init::<E, F, H, S, Operation<F, V>>(
330 journal,
331 &mut merkle,
332 &commit_codec_config,
333 Operation::<F, V>::Commit(None, Location::new(0))
334 .encode()
335 .to_vec(),
336 Operation::has_floor,
337 )
338 .await?;
339 let Operation::Commit(last_commit_metadata, inactivity_floor_loc) = last_commit_op else {
340 return Err(Error::DataCorrupted("last operation was not a commit"));
341 };
342 let last_commit_loc = Location::new(*witness.with(|w| w.leaf_count()) - 1);
343
344 Ok(Self {
345 merkle,
346 last_commit_loc,
347 last_commit_metadata,
348 inactivity_floor_loc,
349 commit_codec_config,
350 witness,
351 })
352 }
353
354 pub fn root(&self) -> H::Digest
356 where
357 F: Family,
358 {
359 let hasher = qmdb::hasher::<H>();
360 let inactive_peaks = F::inactive_peaks(
361 F::location_to_position(Location::new(*self.last_commit_loc + 1)),
362 self.inactivity_floor_loc,
363 );
364 self.merkle
365 .root(&hasher, inactive_peaks)
366 .expect("compact Merkle root should not fail")
367 }
368
369 pub const fn strategy(&self) -> &S {
371 self.merkle.strategy()
372 }
373
374 pub const fn last_commit_loc(&self) -> Location<F> {
376 self.last_commit_loc
377 }
378
379 pub const fn inactivity_floor_loc(&self) -> Location<F> {
381 self.inactivity_floor_loc
382 }
383
384 pub fn size(&self) -> Location<F> {
386 Location::new(*self.last_commit_loc + 1)
387 }
388
389 pub fn get_metadata(&self) -> Option<V::Value> {
391 self.last_commit_metadata.clone()
392 }
393
394 pub fn target(&self) -> compact_sync::Target<F, H::Digest> {
399 self.witness.with(VerifiedWitness::target)
400 }
401
402 pub(crate) fn compact_state(
405 &self,
406 target: compact_sync::Target<F, H::Digest>,
407 ) -> CompactStateResult<F, V, H::Digest>
408 where
409 Operation<F, V>: Read<Cfg = C>,
410 {
411 let (entry, leaf_count) = self.witness.with(|w| {
414 if target.root != w.root || target.leaf_count != w.leaf_count() {
415 return Err(compact_sync::ServeError::StaleTarget {
416 requested: target.clone(),
417 current: w.target(),
418 });
419 }
420 Ok((w.witness.clone(), w.leaf_count()))
421 })?;
422 let Witness {
423 op_bytes,
424 proof: last_commit_proof,
425 pinned_nodes,
426 } = entry;
427 let op = Operation::<F, V>::decode_cfg(op_bytes.as_ref(), &self.commit_codec_config)
428 .map_err(|_| {
429 compact_sync::ServeError::Database(Error::DataCorrupted("invalid commit operation"))
430 })?;
431 Ok(compact_sync::State {
432 leaf_count,
433 pinned_nodes,
434 last_commit_op: op,
435 last_commit_proof,
436 })
437 }
438
439 pub fn new_batch(&self) -> UnmerkleizedBatch<F, H, V, S> {
441 let committed_size = *self.last_commit_loc + 1;
442 UnmerkleizedBatch::new(self, committed_size)
443 }
444
445 pub fn to_batch(&self) -> Arc<MerkleizedBatch<F, H::Digest, V, S>>
447 where
448 F: Family,
449 {
450 let committed_size = *self.last_commit_loc + 1;
451 Arc::new(MerkleizedBatch {
452 merkle_batch: self.merkle.to_batch(),
453 root: self.root(),
454 commit_metadata: self.last_commit_metadata.clone(),
455 parent: None,
456 bounds: batch_chain::Bounds {
457 base_size: committed_size,
458 db_size: committed_size,
459 total_size: committed_size,
460 ancestors: Vec::new(),
461 inactivity_floor: self.inactivity_floor_loc,
462 },
463 })
464 }
465
466 #[tracing::instrument(name = "qmdb.keyless.compact.db.apply_batch", level = "info", skip_all)]
480 pub fn apply_batch(
481 &mut self,
482 batch: Arc<MerkleizedBatch<F, H::Digest, V, S>>,
483 ) -> Result<core::ops::Range<Location<F>>, Error<F>> {
484 let db_size = *self.last_commit_loc + 1;
485 batch
486 .bounds
487 .validate_apply_to(db_size, self.inactivity_floor_loc)?;
488
489 let start_loc = self.last_commit_loc + 1;
490 self.merkle.apply_batch(&batch.merkle_batch)?;
491 self.last_commit_loc = Location::new(batch.bounds.total_size - 1);
492 self.last_commit_metadata = batch.commit_metadata.clone();
493 self.inactivity_floor_loc = batch.bounds.inactivity_floor;
494 Ok(start_loc..Location::new(batch.bounds.total_size))
495 }
496
497 #[tracing::instrument(name = "qmdb.keyless.compact.db.commit", level = "info", skip_all)]
500 pub async fn commit(&mut self) -> Result<(), Error<F>> {
501 self.witness
502 .commit::<H, S>(&self.merkle, self.inactivity_floor_loc, || {
503 Self::encode_commit_op(self.last_commit_metadata.clone(), self.inactivity_floor_loc)
504 })
505 .await
506 }
507
508 #[tracing::instrument(name = "qmdb.keyless.compact.db.sync", level = "info", skip_all)]
511 pub async fn sync(&mut self) -> Result<(), Error<F>> {
512 self.witness
513 .sync::<H, S>(&self.merkle, self.inactivity_floor_loc, || {
514 Self::encode_commit_op(self.last_commit_metadata.clone(), self.inactivity_floor_loc)
515 })
516 .await
517 }
518
519 #[tracing::instrument(name = "qmdb.keyless.compact.db.rewind", level = "info", skip_all)]
528 pub async fn rewind(&mut self, target: Location<F>) -> Result<(), Error<F>>
529 where
530 F: Family,
531 {
532 if self.size() == target
534 && self.witness.with(|w| w.leaf_count()) == target
535 && !self.witness.import_pending()
536 {
537 return Ok(());
538 }
539
540 let last_commit_op = self
541 .witness
542 .rewind::<H, S, Operation<F, V>>(
543 &self.merkle,
544 target,
545 &self.commit_codec_config,
546 Operation::has_floor,
547 )
548 .await?;
549 let Operation::Commit(last_commit_metadata, inactivity_floor_loc) = last_commit_op else {
550 return Err(Error::DataCorrupted("last operation was not a commit"));
551 };
552 self.last_commit_metadata = last_commit_metadata;
553 self.inactivity_floor_loc = inactivity_floor_loc;
554 self.last_commit_loc = Location::new(*target - 1);
555 Ok(())
556 }
557
558 pub async fn prune(&mut self, pruning_boundary: Location<F>) -> Result<(), Error<F>> {
569 self.witness.prune(pruning_boundary).await
570 }
571
572 #[boxed]
574 pub async fn destroy(self) -> Result<(), Error<F>> {
575 self.witness.destroy().await?;
576 Ok(())
577 }
578}
579
580#[cfg(test)]
581mod tests {
582 use super::*;
583 use crate::{
584 merkle::mmr,
585 qmdb::{any::value::FixedEncoding, compact::witness},
586 };
587 use commonware_cryptography::{sha256::Digest, Sha256};
588 use commonware_macros::test_traced;
589 use commonware_parallel::Sequential;
590 use commonware_runtime::{
591 buffer::paged::CacheRef, deterministic, BufferPooler, Runner as _, Supervisor as _,
592 };
593 use commonware_utils::{sequence::U64, NZUsize, NZU16, NZU64};
594 use std::num::{NonZeroU16, NonZeroUsize};
595
596 type TestDb<F> = Db<F, deterministic::Context, FixedEncoding<U64>, Sha256, (), Sequential>;
597
598 const WITNESS_PAGE_SIZE: NonZeroU16 = NZU16!(77);
599 const WITNESS_PAGE_CACHE_SIZE: NonZeroUsize = NZUsize!(9);
600
601 fn witness_config(partition: &str, pooler: &impl BufferPooler) -> variable::Config<()> {
602 variable::Config {
603 partition: format!("{partition}-witness"),
604 items_per_section: NZU64!(64),
605 compression: None,
606 codec_config: (),
607 page_cache: CacheRef::from_pooler(pooler, WITNESS_PAGE_SIZE, WITNESS_PAGE_CACHE_SIZE),
608 write_buffer: NZUsize!(1024),
609 }
610 }
611
612 async fn open_db<F: Family>(context: deterministic::Context, partition: &str) -> TestDb<F> {
613 let witness_cfg = witness_config(partition, &context);
614 let merkle = crate::merkle::compact::Merkle::new(Sequential);
615 Db::init_from_merkle(merkle, context.child("witness"), witness_cfg, ())
616 .await
617 .unwrap()
618 }
619
620 async fn open_witness_journal(
622 context: deterministic::Context,
623 partition: &str,
624 ) -> witness::Journal<deterministic::Context, mmr::Family, Digest> {
625 let cfg = witness_config(partition, &context);
626 variable::Journal::init(context, cfg).await.unwrap()
627 }
628
629 #[test_traced("INFO")]
630 fn test_compact_stale_batch_rejected() {
631 deterministic::Runner::default().start(|context| async move {
632 let mut db = open_db::<mmr::Family>(context.child("db"), "keyless-stale").await;
633 let floor = db.inactivity_floor_loc();
634
635 let batch_a = db
636 .new_batch()
637 .append(U64::new(1))
638 .merkleize(&db, Some(U64::new(11)), floor)
639 .await;
640 let batch_b = db
641 .new_batch()
642 .append(U64::new(2))
643 .merkleize(&db, Some(U64::new(22)), floor)
644 .await;
645
646 let expected_root = batch_a.root();
647 db.apply_batch(batch_a).unwrap();
648 assert_eq!(db.root(), expected_root);
649 assert!(matches!(
650 db.apply_batch(batch_b),
651 Err(Error::StaleBatch { .. })
652 ));
653
654 db.destroy().await.unwrap();
655 });
656 }
657
658 #[test_traced("INFO")]
661 fn test_compact_to_batch_reflects_live_state() {
662 deterministic::Runner::default().start(|context| async move {
663 let mut db = open_db::<mmr::Family>(context.child("db"), "keyless-to-batch-live").await;
664 let floor = db.inactivity_floor_loc();
665
666 let pre_apply_root = db.root();
667 let pre_snapshot = db.to_batch();
668 assert_eq!(
669 pre_snapshot.root(),
670 pre_apply_root,
671 "snapshot before any mutation should match the live root"
672 );
673
674 db.apply_batch(
675 db.new_batch()
676 .append(U64::new(1))
677 .merkleize(&db, Some(U64::new(11)), floor)
678 .await,
679 )
680 .unwrap();
681
682 let live_root = db.root();
684 assert_ne!(
685 live_root, pre_apply_root,
686 "applying a non-empty batch must change the live root"
687 );
688
689 let snapshot = db.to_batch();
690 assert_eq!(
691 snapshot.root(),
692 live_root,
693 "to_batch().root() must match the live db.root() even before sync"
694 );
695
696 db.destroy().await.unwrap();
697 });
698 }
699
700 #[test_traced("INFO")]
701 fn test_compact_stale_batch_chained() {
702 deterministic::Runner::default().start(|context| async move {
703 let mut db = open_db::<mmr::Family>(context.child("db"), "keyless-chained-stale").await;
704 let floor = db.inactivity_floor_loc();
705
706 let parent = db
707 .new_batch()
708 .append(U64::new(1))
709 .merkleize(&db, Some(U64::new(11)), floor)
710 .await;
711 let child_a = parent
712 .new_batch::<Sha256>()
713 .append(U64::new(2))
714 .merkleize(&db, Some(U64::new(22)), floor)
715 .await;
716 let child_b = parent
717 .new_batch::<Sha256>()
718 .append(U64::new(3))
719 .merkleize(&db, Some(U64::new(33)), floor)
720 .await;
721
722 db.apply_batch(child_a).unwrap();
723 assert!(matches!(
724 db.apply_batch(child_b),
725 Err(Error::StaleBatch { .. })
726 ));
727
728 db.destroy().await.unwrap();
729 });
730 }
731
732 #[test_traced("INFO")]
733 fn test_compact_stale_parent_after_child_applied() {
734 deterministic::Runner::default().start(|context| async move {
735 let mut db =
736 open_db::<mmr::Family>(context.child("db"), "keyless-child-before-parent").await;
737 let floor = db.inactivity_floor_loc();
738
739 let parent = db
740 .new_batch()
741 .append(U64::new(1))
742 .merkleize(&db, Some(U64::new(11)), floor)
743 .await;
744 let child = parent
745 .new_batch::<Sha256>()
746 .append(U64::new(2))
747 .merkleize(&db, Some(U64::new(22)), floor)
748 .await;
749
750 db.apply_batch(child).unwrap();
751 assert!(matches!(
752 db.apply_batch(parent),
753 Err(Error::StaleBatch { .. })
754 ));
755
756 db.destroy().await.unwrap();
757 });
758 }
759
760 #[test_traced("INFO")]
761 fn test_compact_sequential_commit_parent_then_child() {
762 deterministic::Runner::default().start(|context| async move {
763 let mut db = open_db::<mmr::Family>(context.child("db"), "keyless-parent-child").await;
764 let floor = db.inactivity_floor_loc();
765
766 let parent = db
767 .new_batch()
768 .append(U64::new(1))
769 .merkleize(&db, Some(U64::new(11)), floor)
770 .await;
771 let child = parent
772 .new_batch::<Sha256>()
773 .append(U64::new(2))
774 .merkleize(&db, Some(U64::new(22)), floor)
775 .await;
776 let expected_root = child.root();
777
778 db.apply_batch(parent).unwrap();
779 db.apply_batch(child).unwrap();
780 db.sync().await.unwrap();
781
782 assert_eq!(db.root(), expected_root);
783
784 db.destroy().await.unwrap();
785 });
786 }
787
788 #[test_traced("INFO")]
789 fn test_compact_floor_regressed() {
790 deterministic::Runner::default().start(|context| async move {
791 let mut db =
792 open_db::<mmr::Family>(context.child("db"), "keyless-floor-regressed").await;
793
794 let advance_floor = db.new_batch().append(U64::new(1));
795 let advance_floor = advance_floor.merkleize(&db, None, Location::new(1)).await;
796 db.apply_batch(advance_floor).unwrap();
797
798 let regressed = db
799 .new_batch()
800 .append(U64::new(2))
801 .merkleize(&db, None, Location::new(0))
802 .await;
803
804 assert!(matches!(
805 db.apply_batch(regressed),
806 Err(Error::FloorRegressed(new, current))
807 if new == Location::new(0) && current == Location::new(1)
808 ));
809
810 db.destroy().await.unwrap();
811 });
812 }
813
814 #[test_traced("INFO")]
818 fn test_compact_ancestor_floor_regressed() {
819 deterministic::Runner::default().start(|context| async move {
820 let mut db =
821 open_db::<mmr::Family>(context.child("db"), "keyless-ancestor-floor-regressed")
822 .await;
823
824 let parent = db
826 .new_batch()
827 .append(U64::new(1))
828 .merkleize(&db, None, Location::new(2))
829 .await;
830 let child = parent
832 .new_batch::<Sha256>()
833 .append(U64::new(2))
834 .merkleize(&db, None, Location::new(1))
835 .await;
836
837 assert!(matches!(
838 db.apply_batch(child),
839 Err(Error::FloorRegressed(new, prev))
840 if new == Location::new(1) && prev == Location::new(2)
841 ));
842
843 db.destroy().await.unwrap();
844 });
845 }
846
847 #[test_traced("INFO")]
848 fn test_compact_rewind_restores_commit_metadata_and_floor() {
849 deterministic::Runner::default().start(|context| async move {
850 let mut db = open_db::<mmr::Family>(context.child("db"), "keyless-rewind-meta").await;
851
852 let v1 = U64::new(1);
853 let meta1 = U64::new(11);
854 let floor1 = Location::new(0);
855 db.apply_batch(
856 db.new_batch()
857 .append(v1)
858 .merkleize(&db, Some(meta1.clone()), floor1)
859 .await,
860 )
861 .unwrap();
862 db.sync().await.unwrap();
863 let root_after_first = db.root();
864 let size_after_first = db.size();
865
866 let v2 = U64::new(2);
867 let meta2 = U64::new(22);
868 let floor2 = Location::new(1);
869 db.apply_batch(
870 db.new_batch()
871 .append(v2)
872 .merkleize(&db, Some(meta2.clone()), floor2)
873 .await,
874 )
875 .unwrap();
876 db.sync().await.unwrap();
877 assert_eq!(db.get_metadata(), Some(meta2));
878 assert_eq!(db.inactivity_floor_loc(), floor2);
879
880 db.rewind(size_after_first).await.unwrap();
881 assert_eq!(db.root(), root_after_first);
882 assert_eq!(db.get_metadata(), Some(meta1));
883 assert_eq!(db.inactivity_floor_loc(), floor1);
884
885 db.destroy().await.unwrap();
886 });
887 }
888
889 #[test_traced("INFO")]
890 fn test_compact_rewind_persists_across_reopen() {
891 deterministic::Runner::default().start(|context| async move {
892 let partition = "keyless-rewind-reopen";
893 let meta1 = U64::new(11);
894 let floor1 = Location::new(0);
895 let meta2 = U64::new(22);
896 let floor2 = Location::new(1);
897
898 let root_after_first = {
899 let mut db = open_db::<mmr::Family>(context.child("first"), partition).await;
900 db.apply_batch(
901 db.new_batch()
902 .append(U64::new(1))
903 .merkleize(&db, Some(meta1.clone()), floor1)
904 .await,
905 )
906 .unwrap();
907 db.sync().await.unwrap();
908 let root = db.root();
909 let size_after_first = db.size();
910
911 db.apply_batch(
912 db.new_batch()
913 .append(U64::new(2))
914 .merkleize(&db, Some(meta2), floor2)
915 .await,
916 )
917 .unwrap();
918 db.sync().await.unwrap();
919
920 db.rewind(size_after_first).await.unwrap();
921 root
922 };
923
924 let db = open_db::<mmr::Family>(context.child("second"), partition).await;
925 assert_eq!(db.root(), root_after_first);
926 assert_eq!(db.get_metadata(), Some(meta1));
927 assert_eq!(db.inactivity_floor_loc(), floor1);
928
929 db.destroy().await.unwrap();
930 });
931 }
932
933 #[test_traced("INFO")]
934 fn test_compact_commit_persists_across_reopen() {
935 deterministic::Runner::default().start(|context| async move {
936 let partition = "keyless-commit-reopen";
937 let meta1 = U64::new(11);
938 let meta2 = U64::new(22);
939
940 let root_after_second = {
941 let mut db = open_db::<mmr::Family>(context.child("first"), partition).await;
942 db.apply_batch(
943 db.new_batch()
944 .append(U64::new(1))
945 .merkleize(&db, Some(meta1), Location::new(0))
946 .await,
947 )
948 .unwrap();
949 db.commit().await.unwrap();
950
951 db.apply_batch(
952 db.new_batch()
953 .append(U64::new(2))
954 .merkleize(&db, Some(meta2.clone()), Location::new(1))
955 .await,
956 )
957 .unwrap();
958 db.commit().await.unwrap();
959 db.root()
960 };
961
962 let db = open_db::<mmr::Family>(context.child("second"), partition).await;
964 assert_eq!(db.root(), root_after_second);
965 assert_eq!(db.get_metadata(), Some(meta2));
966 assert_eq!(db.inactivity_floor_loc(), Location::new(1));
967 db.destroy().await.unwrap();
968 });
969 }
970
971 #[test_traced("INFO")]
972 fn test_compact_rewind_to_committed_entry_after_reopen() {
973 deterministic::Runner::default().start(|context| async move {
974 let partition = "keyless-commit-rewind-reopen";
975 let meta1 = U64::new(11);
976 let meta2 = U64::new(22);
977
978 let (root_a, size_a) = {
979 let mut db = open_db::<mmr::Family>(context.child("first"), partition).await;
980 db.apply_batch(
981 db.new_batch()
982 .append(U64::new(1))
983 .merkleize(&db, Some(meta1.clone()), Location::new(0))
984 .await,
985 )
986 .unwrap();
987 db.commit().await.unwrap();
988 let root_a = db.root();
989 let size_a = db.size();
990
991 db.apply_batch(
992 db.new_batch()
993 .append(U64::new(2))
994 .merkleize(&db, Some(meta2), Location::new(1))
995 .await,
996 )
997 .unwrap();
998 db.commit().await.unwrap();
999 (root_a, size_a)
1000 };
1001
1002 let mut db = open_db::<mmr::Family>(context.child("second"), partition).await;
1005 db.rewind(size_a).await.unwrap();
1006 assert_eq!(db.root(), root_a);
1007 assert_eq!(db.get_metadata(), Some(meta1));
1008 db.destroy().await.unwrap();
1009 });
1010 }
1011
1012 #[test_traced("INFO")]
1013 fn test_compact_sync_after_commit() {
1014 deterministic::Runner::default().start(|context| async move {
1015 let partition = "keyless-sync-after-commit";
1016 let meta = U64::new(11);
1017
1018 let root = {
1019 let mut db = open_db::<mmr::Family>(context.child("first"), partition).await;
1020 db.apply_batch(
1021 db.new_batch()
1022 .append(U64::new(1))
1023 .merkleize(&db, Some(meta.clone()), Location::new(0))
1024 .await,
1025 )
1026 .unwrap();
1027 db.commit().await.unwrap();
1028 db.sync().await.unwrap();
1030 db.root()
1031 };
1032
1033 let db = open_db::<mmr::Family>(context.child("second"), partition).await;
1034 assert_eq!(db.root(), root);
1035 assert_eq!(db.get_metadata(), Some(meta));
1036 db.destroy().await.unwrap();
1037 });
1038 }
1039
1040 #[test_traced("INFO")]
1041 fn test_compact_import_persists_with_commit() {
1042 deterministic::Runner::default().start(|context| async move {
1043 let dst = "keyless-import-commit-dst";
1044 let src = "keyless-import-commit-src";
1045 let meta_a = U64::new(11);
1046 let meta_b = U64::new(22);
1047
1048 let target_b = {
1050 let mut source = open_db::<mmr::Family>(context.child("src"), src).await;
1051 source
1052 .apply_batch(
1053 source
1054 .new_batch()
1055 .append(U64::new(2))
1056 .merkleize(&source, Some(meta_b.clone()), Location::new(0))
1057 .await,
1058 )
1059 .unwrap();
1060 source.sync().await.unwrap();
1061 source.target()
1062 };
1063 let (_, proof_b, pinned_b) = {
1064 let journal = open_witness_journal(context.child("src_tip"), src).await;
1065 witness::tests::tip(&journal).await
1066 };
1067 let validated = compact_sync::ValidatedState {
1068 state: compact_sync::State {
1069 leaf_count: target_b.leaf_count,
1070 pinned_nodes: pinned_b,
1071 last_commit_op: Operation::Commit(Some(meta_b.clone()), Location::new(0)),
1072 last_commit_proof: proof_b,
1073 },
1074 root: target_b.root,
1075 };
1076
1077 {
1079 let mut seeded = open_db::<mmr::Family>(context.child("seed"), dst).await;
1080 seeded
1081 .apply_batch(
1082 seeded
1083 .new_batch()
1084 .append(U64::new(1))
1085 .merkleize(&seeded, Some(meta_a), Location::new(0))
1086 .await,
1087 )
1088 .unwrap();
1089 seeded.sync().await.unwrap();
1090 assert_ne!(seeded.target(), target_b);
1091 }
1092
1093 {
1095 let journal = open_witness_journal(context.child("import"), dst).await;
1096 let mut imported = TestDb::<mmr::Family>::init_from_validated_state(
1097 Sequential,
1098 journal,
1099 (),
1100 validated,
1101 )
1102 .unwrap();
1103 assert_eq!(imported.target(), target_b);
1104 imported.commit().await.unwrap();
1105 }
1106
1107 let db = open_db::<mmr::Family>(context.child("reopen"), dst).await;
1110 assert_eq!(db.target(), target_b);
1111 assert_eq!(db.root(), target_b.root);
1112 assert_eq!(db.get_metadata(), Some(meta_b));
1113 db.destroy().await.unwrap();
1114 });
1115 }
1116
1117 #[test_traced("INFO")]
1118 fn test_compact_reopen_rejects_tampered_witness() {
1119 deterministic::Runner::default().start(|context| async move {
1120 let partition = "keyless-witness-tamper";
1121 let mut db = open_db::<mmr::Family>(context.child("db"), partition).await;
1122 db.apply_batch(
1123 db.new_batch()
1124 .append(U64::new(7))
1125 .merkleize(&db, Some(U64::new(11)), Location::new(1))
1126 .await,
1127 )
1128 .unwrap();
1129 db.sync().await.unwrap();
1130 drop(db);
1131
1132 let mut journal = open_witness_journal(context.child("tamper"), partition).await;
1134 let (op_bytes, mut proof, pinned_nodes) = witness::tests::tip(&journal).await;
1135 if let Some(digest) = proof.digests.first_mut() {
1136 *digest = Sha256::fill(0xff);
1137 } else {
1138 proof.leaves = Location::new(*proof.leaves + 1);
1139 }
1140 witness::tests::overwrite_tip(&mut journal, op_bytes, proof, pinned_nodes).await;
1141 drop(journal);
1142
1143 let merkle = crate::merkle::compact::Merkle::new(Sequential);
1144 let reopened = TestDb::<mmr::Family>::init_from_merkle(
1145 merkle,
1146 context.child("reopen_witness"),
1147 witness_config(partition, &context),
1148 (),
1149 )
1150 .await;
1151 assert!(matches!(reopened, Err(Error::DataCorrupted(_))));
1152 });
1153 }
1154
1155 #[test_traced("INFO")]
1156 fn test_compact_rewind_rejects_corrupt_target_entry() {
1157 deterministic::Runner::default().start(|context| async move {
1158 let partition = "keyless-corrupt-rewind-target";
1159 let mut db = open_db::<mmr::Family>(context.child("db"), partition).await;
1160 db.apply_batch(
1161 db.new_batch()
1162 .append(U64::new(1))
1163 .merkleize(&db, None, Location::new(1))
1164 .await,
1165 )
1166 .unwrap();
1167 db.sync().await.unwrap();
1168 let rewind_target = db.target().leaf_count;
1169 db.apply_batch(
1170 db.new_batch()
1171 .append(U64::new(2))
1172 .merkleize(&db, None, Location::new(1))
1173 .await,
1174 )
1175 .unwrap();
1176 db.sync().await.unwrap();
1177 let tip_target = db.target();
1178 drop(db);
1179
1180 let mut journal = open_witness_journal(context.child("corrupt"), partition).await;
1182 witness::tests::corrupt_entry(&mut journal, 1, |entry| {
1183 entry.pinned_nodes[0] = Sha256::fill(0xff);
1184 })
1185 .await;
1186 drop(journal);
1187
1188 let merkle = crate::merkle::compact::Merkle::new(Sequential);
1190 let mut reopened = TestDb::<mmr::Family>::init_from_merkle(
1191 merkle,
1192 context.child("reopen"),
1193 witness_config(partition, &context),
1194 (),
1195 )
1196 .await
1197 .unwrap();
1198 assert_eq!(reopened.target(), tip_target);
1199
1200 assert!(matches!(
1202 reopened.rewind(rewind_target).await,
1203 Err(Error::DataCorrupted(_))
1204 ));
1205 drop(reopened);
1206
1207 let merkle = crate::merkle::compact::Merkle::new(Sequential);
1209 let reopened = TestDb::<mmr::Family>::init_from_merkle(
1210 merkle,
1211 context.child("reopen2"),
1212 witness_config(partition, &context),
1213 (),
1214 )
1215 .await
1216 .unwrap();
1217 assert_eq!(reopened.target(), tip_target);
1218 reopened.destroy().await.unwrap();
1219 });
1220 }
1221
1222 #[test_traced("INFO")]
1223 fn test_compact_reopen_rejects_interrupted_import() {
1224 deterministic::Runner::default().start(|context| async move {
1225 let partition = "keyless-interrupted-import";
1226 let mut db = open_db::<mmr::Family>(context.child("db"), partition).await;
1227 db.apply_batch(
1228 db.new_batch()
1229 .append(U64::new(7))
1230 .merkleize(&db, Some(U64::new(11)), Location::new(1))
1231 .await,
1232 )
1233 .unwrap();
1234 db.sync().await.unwrap();
1235 drop(db);
1236
1237 let mut journal = open_witness_journal(context.child("clear"), partition).await;
1240 let size = journal.size();
1241 journal.clear_to_size(size.max(1)).await.unwrap();
1242 drop(journal);
1243
1244 let merkle = crate::merkle::compact::Merkle::new(Sequential);
1246 let reopened = TestDb::<mmr::Family>::init_from_merkle(
1247 merkle,
1248 context.child("reopen_witness"),
1249 witness_config(partition, &context),
1250 (),
1251 )
1252 .await;
1253 assert!(matches!(reopened, Err(Error::Journal(_))));
1254 });
1255 }
1256
1257 #[test_traced("INFO")]
1258 fn test_compact_reopen_rejects_commit_floor_beyond_tip() {
1259 deterministic::Runner::default().start(|context| async move {
1260 let partition = "keyless-invalid-persisted-floor";
1261 let mut db = open_db::<mmr::Family>(context.child("db"), partition).await;
1262 db.apply_batch(
1263 db.new_batch()
1264 .append(U64::new(7))
1265 .merkleize(&db, Some(U64::new(11)), Location::new(1))
1266 .await,
1267 )
1268 .unwrap();
1269 db.sync().await.unwrap();
1270 drop(db);
1271 let oversized_floor = Location::new(10);
1272
1273 let mut journal = open_witness_journal(context.child("tamper"), partition).await;
1275 let (_, proof, pinned_nodes) = witness::tests::tip(&journal).await;
1276 let bad_op = Operation::<mmr::Family, FixedEncoding<U64>>::Commit(
1277 Some(U64::new(11)),
1278 oversized_floor,
1279 )
1280 .encode()
1281 .to_vec();
1282 witness::tests::overwrite_tip(&mut journal, bad_op, proof, pinned_nodes).await;
1283 drop(journal);
1284
1285 let merkle = crate::merkle::compact::Merkle::new(Sequential);
1286 let reopened = TestDb::<mmr::Family>::init_from_merkle(
1287 merkle,
1288 context.child("reopen_witness"),
1289 witness_config(partition, &context),
1290 (),
1291 )
1292 .await;
1293 assert!(matches!(
1294 reopened,
1295 Err(Error::DataCorrupted("invalid compact witness"))
1296 ));
1297 });
1298 }
1299
1300 #[test_traced("INFO")]
1301 fn test_compact_reopen_rejects_tampered_pinned_nodes() {
1302 deterministic::Runner::default().start(|context| async move {
1303 let partition = "keyless-pins-tamper";
1304 let mut db = open_db::<mmr::Family>(context.child("db"), partition).await;
1305 db.apply_batch(
1306 db.new_batch()
1307 .append(U64::new(7))
1308 .merkleize(&db, Some(U64::new(11)), Location::new(1))
1309 .await,
1310 )
1311 .unwrap();
1312 db.sync().await.unwrap();
1313 drop(db);
1314
1315 let mut journal = open_witness_journal(context.child("tamper"), partition).await;
1318 let (op_bytes, proof, mut pinned_nodes) = witness::tests::tip(&journal).await;
1319 pinned_nodes[0] = Sha256::fill(0xff);
1320 witness::tests::overwrite_tip(&mut journal, op_bytes, proof, pinned_nodes).await;
1321 drop(journal);
1322
1323 let merkle = crate::merkle::compact::Merkle::new(Sequential);
1324 let reopened = TestDb::<mmr::Family>::init_from_merkle(
1325 merkle,
1326 context.child("reopen_witness"),
1327 witness_config(partition, &context),
1328 (),
1329 )
1330 .await;
1331 assert!(matches!(reopened, Err(Error::DataCorrupted(_))));
1332 });
1333 }
1334
1335 #[test_traced("INFO")]
1336 fn test_compact_rewind_to_current_is_noop() {
1337 deterministic::Runner::default().start(|context| async move {
1338 let mut db = open_db::<mmr::Family>(context.child("db"), "keyless-rewind-noop").await;
1339 db.apply_batch(
1340 db.new_batch()
1341 .append(U64::new(1))
1342 .merkleize(&db, Some(U64::new(11)), Location::new(0))
1343 .await,
1344 )
1345 .unwrap();
1346 db.sync().await.unwrap();
1347 let root = db.root();
1348 let size = db.size();
1349
1350 db.rewind(size).await.unwrap();
1351 assert_eq!(db.root(), root);
1352 assert_eq!(db.size(), size);
1353 db.destroy().await.unwrap();
1354 });
1355 }
1356
1357 #[test_traced("INFO")]
1358 fn test_compact_prune_past_tip_keeps_tip() {
1359 deterministic::Runner::default().start(|context| async move {
1360 let partition = "keyless-prune-past-tip";
1361 let mut db = open_db::<mmr::Family>(context.child("db"), partition).await;
1362 db.apply_batch(
1363 db.new_batch()
1364 .append(U64::new(1))
1365 .merkleize(&db, Some(U64::new(11)), Location::new(0))
1366 .await,
1367 )
1368 .unwrap();
1369 db.sync().await.unwrap();
1370 let target = db.target();
1371
1372 db.prune(Location::new(*db.size() + 100)).await.unwrap();
1374 assert_eq!(db.target(), target);
1375 drop(db);
1376
1377 let reopened = open_db::<mmr::Family>(context.child("reopen"), partition).await;
1378 assert_eq!(reopened.target(), target);
1379 reopened.destroy().await.unwrap();
1380 });
1381 }
1382
1383 #[test_traced("INFO")]
1384 fn test_compact_rewind_beyond_history() {
1385 deterministic::Runner::default().start(|context| async move {
1386 let mut db = open_db::<mmr::Family>(context.child("db"), "keyless-rewind-beyond").await;
1387 assert!(matches!(
1390 db.rewind(Location::new(0)).await,
1391 Err(Error::Merkle(crate::merkle::Error::RewindBeyondHistory))
1392 ));
1393 assert!(matches!(
1395 db.rewind(Location::new(*db.size() + 100)).await,
1396 Err(Error::Merkle(crate::merkle::Error::RewindBeyondHistory))
1397 ));
1398 db.destroy().await.unwrap();
1399 });
1400 }
1401
1402 #[test_traced("INFO")]
1403 fn test_compact_rewind_between_commits() {
1404 deterministic::Runner::default().start(|context| async move {
1405 let mut db =
1406 open_db::<mmr::Family>(context.child("db"), "keyless-rewind-between").await;
1407 let floor = db.inactivity_floor_loc();
1408
1409 db.apply_batch(
1411 db.new_batch()
1412 .append(U64::new(1))
1413 .append(U64::new(2))
1414 .merkleize(&db, Some(U64::new(11)), floor)
1415 .await,
1416 )
1417 .unwrap();
1418 db.sync().await.unwrap();
1419 let root_a = db.root();
1420 let size_a = db.size();
1421 assert_eq!(size_a, Location::new(4));
1422
1423 db.apply_batch(
1425 db.new_batch()
1426 .append(U64::new(3))
1427 .merkleize(&db, Some(U64::new(22)), floor)
1428 .await,
1429 )
1430 .unwrap();
1431 db.sync().await.unwrap();
1432 let root_b = db.root();
1433
1434 for target in [2u64, 3, 5] {
1437 assert!(matches!(
1438 db.rewind(Location::new(target)).await,
1439 Err(Error::Merkle(crate::merkle::Error::RewindBeyondHistory))
1440 ));
1441 }
1442 assert_eq!(db.root(), root_b);
1443
1444 db.rewind(size_a).await.unwrap();
1446 assert_eq!(db.root(), root_a);
1447 assert_eq!(db.get_metadata(), Some(U64::new(11)));
1448 db.destroy().await.unwrap();
1449 });
1450 }
1451
1452 #[test_traced("INFO")]
1455 fn test_compact_reopen_drops_unsynced_witness() {
1456 deterministic::Runner::default().start(|context| async move {
1457 let partition = "keyless-witness-unsynced";
1458 let mut db = open_db::<mmr::Family>(context.child("db"), partition).await;
1459
1460 db.apply_batch(
1462 db.new_batch()
1463 .append(U64::new(1))
1464 .merkleize(&db, Some(U64::new(11)), Location::new(1))
1465 .await,
1466 )
1467 .unwrap();
1468 db.sync().await.unwrap();
1469 let target_a = db.target();
1470 drop(db);
1471
1472 let mut journal = open_witness_journal(context.child("crash"), partition).await;
1475 let (op_bytes, mut proof, pinned_nodes) = witness::tests::tip(&journal).await;
1476 proof.leaves = Location::new(*proof.leaves + 2);
1477 witness::tests::append_unsynced(&mut journal, op_bytes, proof, pinned_nodes).await;
1478 drop(journal);
1479
1480 let reopened = open_db::<mmr::Family>(context.child("reopen"), partition).await;
1482 assert_eq!(reopened.target(), target_a);
1483 reopened.destroy().await.unwrap();
1484 });
1485 }
1486
1487 #[test_traced("INFO")]
1488 fn test_compact_rewind_multiple_commits() {
1489 deterministic::Runner::default().start(|context| async move {
1490 let partition = "keyless-rewind-multi";
1491 let mut db = open_db::<mmr::Family>(context.child("db"), partition).await;
1492
1493 db.apply_batch(
1495 db.new_batch()
1496 .append(U64::new(1))
1497 .merkleize(&db, Some(U64::new(11)), Location::new(0))
1498 .await,
1499 )
1500 .unwrap();
1501 db.sync().await.unwrap();
1502 let root_a = db.root();
1503 let size_a = db.size();
1504 let target_a = db.target();
1505
1506 for i in [2u64, 3] {
1507 db.apply_batch(
1508 db.new_batch()
1509 .append(U64::new(i))
1510 .merkleize(&db, Some(U64::new(i * 11)), Location::new(0))
1511 .await,
1512 )
1513 .unwrap();
1514 db.sync().await.unwrap();
1515 }
1516 assert_ne!(db.root(), root_a);
1517
1518 db.rewind(size_a).await.unwrap();
1520 assert_eq!(db.root(), root_a);
1521 assert_eq!(db.size(), size_a);
1522 assert_eq!(db.get_metadata(), Some(U64::new(11)));
1523 assert_eq!(db.target(), target_a);
1524 drop(db);
1525
1526 let db = open_db::<mmr::Family>(context.child("reopen"), partition).await;
1528 assert_eq!(db.root(), root_a);
1529 assert_eq!(db.target(), target_a);
1530 db.destroy().await.unwrap();
1531 });
1532 }
1533
1534 #[test_traced("INFO")]
1535 fn test_compact_prune_then_rewind() {
1536 deterministic::Runner::default().start(|context| async move {
1537 let mut witness_cfg = witness_config("keyless-prune-rewind", &context);
1540 witness_cfg.items_per_section = NZU64!(1);
1541 let merkle = crate::merkle::compact::Merkle::new(Sequential);
1542 let mut db: TestDb<mmr::Family> =
1543 Db::init_from_merkle(merkle, context.child("witness"), witness_cfg, ())
1544 .await
1545 .unwrap();
1546
1547 let mut sizes = Vec::new();
1549 for i in [1u64, 2, 3] {
1550 db.apply_batch(
1551 db.new_batch()
1552 .append(U64::new(i))
1553 .merkleize(&db, Some(U64::new(i * 11)), Location::new(0))
1554 .await,
1555 )
1556 .unwrap();
1557 db.sync().await.unwrap();
1558 sizes.push(db.size());
1559 }
1560
1561 db.prune(sizes[1]).await.unwrap();
1563 assert!(matches!(
1564 db.rewind(sizes[0]).await,
1565 Err(Error::Merkle(crate::merkle::Error::RewindBeyondHistory))
1566 ));
1567 db.rewind(sizes[1]).await.unwrap();
1568 assert_eq!(db.size(), sizes[1]);
1569 assert_eq!(db.get_metadata(), Some(U64::new(22)));
1570
1571 db.destroy().await.unwrap();
1572 });
1573 }
1574
1575 #[test_traced("INFO")]
1576 fn test_compact_rewind_preserves_pre_advance_batch() {
1577 deterministic::Runner::default().start(|context| async move {
1578 let mut db =
1579 open_db::<mmr::Family>(context.child("db"), "keyless-rewind-preserves-pre-advance")
1580 .await;
1581
1582 db.apply_batch(
1583 db.new_batch()
1584 .append(U64::new(1))
1585 .merkleize(&db, None, Location::new(0))
1586 .await,
1587 )
1588 .unwrap();
1589 db.sync().await.unwrap();
1590 let size_after_first = db.size();
1591
1592 let held = db
1594 .new_batch()
1595 .append(U64::new(2))
1596 .merkleize(&db, None, Location::new(0))
1597 .await;
1598
1599 db.apply_batch(
1601 db.new_batch()
1602 .append(U64::new(3))
1603 .merkleize(&db, None, Location::new(0))
1604 .await,
1605 )
1606 .unwrap();
1607 db.sync().await.unwrap();
1608 db.rewind(size_after_first).await.unwrap();
1609
1610 db.apply_batch(held).unwrap();
1613
1614 db.destroy().await.unwrap();
1615 });
1616 }
1617
1618 #[test_traced("INFO")]
1619 fn test_compact_noop_commit_after_commit() {
1620 deterministic::Runner::default().start(|context| async move {
1621 let mut db =
1622 open_db::<mmr::Family>(context.child("db"), "keyless-noop-after-commit").await;
1623
1624 db.apply_batch(
1625 db.new_batch()
1626 .append(U64::new(1))
1627 .append(U64::new(2))
1628 .merkleize(&db, Some(U64::new(11)), Location::new(0))
1629 .await,
1630 )
1631 .unwrap();
1632 db.sync().await.unwrap();
1633 let root_after_first = db.root();
1634 assert_eq!(db.size(), Location::new(4));
1635
1636 db.sync().await.unwrap();
1637 assert_eq!(db.size(), Location::new(4));
1638 assert_eq!(db.root(), root_after_first);
1639 assert_eq!(db.target().root, db.root());
1640
1641 db.destroy().await.unwrap();
1642 });
1643 }
1644
1645 #[test_traced("INFO")]
1646 fn test_compact_noop_commit_after_reopen() {
1647 deterministic::Runner::default().start(|context| async move {
1648 let partition = "keyless-noop-after-reopen";
1649
1650 let root_before_drop = {
1651 let mut db = open_db::<mmr::Family>(context.child("first"), partition).await;
1652 db.apply_batch(
1653 db.new_batch()
1654 .append(U64::new(1))
1655 .append(U64::new(2))
1656 .merkleize(&db, Some(U64::new(11)), Location::new(0))
1657 .await,
1658 )
1659 .unwrap();
1660 db.sync().await.unwrap();
1661 let root = db.root();
1662 assert_eq!(db.size(), Location::new(4));
1663 root
1664 };
1665
1666 let mut db = open_db::<mmr::Family>(context.child("second"), partition).await;
1667 assert_eq!(db.root(), root_before_drop);
1668 assert_eq!(db.size(), Location::new(4));
1669
1670 db.sync().await.unwrap();
1671 assert_eq!(db.size(), Location::new(4));
1672 assert_eq!(db.root(), root_before_drop);
1673 assert_eq!(db.target().root, db.root());
1674
1675 db.destroy().await.unwrap();
1676 });
1677 }
1678
1679 #[test_traced("INFO")]
1680 fn test_compact_noop_commit_after_rewind() {
1681 deterministic::Runner::default().start(|context| async move {
1682 let mut db =
1683 open_db::<mmr::Family>(context.child("db"), "keyless-noop-after-rewind").await;
1684
1685 db.apply_batch(
1686 db.new_batch()
1687 .append(U64::new(1))
1688 .append(U64::new(2))
1689 .merkleize(&db, Some(U64::new(11)), Location::new(0))
1690 .await,
1691 )
1692 .unwrap();
1693 db.sync().await.unwrap();
1694 let root_after_first = db.root();
1695
1696 db.apply_batch(
1697 db.new_batch()
1698 .append(U64::new(3))
1699 .merkleize(&db, Some(U64::new(22)), Location::new(1))
1700 .await,
1701 )
1702 .unwrap();
1703 db.sync().await.unwrap();
1704
1705 db.rewind(Location::new(4)).await.unwrap();
1706 assert_eq!(db.size(), Location::new(4));
1707 assert_eq!(db.root(), root_after_first);
1708
1709 db.sync().await.unwrap();
1710 assert_eq!(db.size(), Location::new(4));
1711 assert_eq!(db.root(), root_after_first);
1712 assert_eq!(db.target().root, db.root());
1713
1714 db.destroy().await.unwrap();
1715 });
1716 }
1717
1718 #[test_traced("INFO")]
1719 fn test_compact_rewind_makes_post_advance_batch_stale() {
1720 deterministic::Runner::default().start(|context| async move {
1721 let mut db =
1722 open_db::<mmr::Family>(context.child("db"), "keyless-rewind-makes-stale").await;
1723
1724 db.apply_batch(
1725 db.new_batch()
1726 .append(U64::new(1))
1727 .merkleize(&db, None, Location::new(0))
1728 .await,
1729 )
1730 .unwrap();
1731 db.sync().await.unwrap();
1732 let size_after_first = db.size();
1733
1734 db.apply_batch(
1735 db.new_batch()
1736 .append(U64::new(2))
1737 .merkleize(&db, None, Location::new(0))
1738 .await,
1739 )
1740 .unwrap();
1741 db.sync().await.unwrap();
1742
1743 let held = db
1745 .new_batch()
1746 .append(U64::new(3))
1747 .merkleize(&db, None, Location::new(0))
1748 .await;
1749
1750 db.rewind(size_after_first).await.unwrap();
1751
1752 assert!(matches!(
1755 db.apply_batch(held),
1756 Err(Error::StaleBatch { .. })
1757 ));
1758
1759 db.destroy().await.unwrap();
1760 });
1761 }
1762
1763 #[test_traced("INFO")]
1764 fn test_compact_floor_beyond_size() {
1765 deterministic::Runner::default().start(|context| async move {
1766 let mut db = open_db::<mmr::Family>(context.child("db"), "keyless-floor-beyond").await;
1767
1768 let batch = db.new_batch().merkleize(&db, None, Location::new(2)).await;
1769
1770 assert!(matches!(
1771 db.apply_batch(batch),
1772 Err(Error::FloorBeyondSize(floor, tip))
1773 if floor == Location::new(2) && tip == Location::new(1)
1774 ));
1775
1776 db.destroy().await.unwrap();
1777 });
1778 }
1779
1780 #[test_traced("INFO")]
1783 fn test_compact_ancestor_floor_beyond_size() {
1784 deterministic::Runner::default().start(|context| async move {
1785 let mut db =
1786 open_db::<mmr::Family>(context.child("db"), "keyless-ancestor-floor-beyond").await;
1787
1788 let parent = db
1790 .new_batch()
1791 .append(U64::new(1))
1792 .merkleize(&db, None, Location::new(3))
1793 .await;
1794 let child = parent
1796 .new_batch::<Sha256>()
1797 .append(U64::new(2))
1798 .merkleize(&db, None, Location::new(0))
1799 .await;
1800
1801 assert!(matches!(
1802 db.apply_batch(child),
1803 Err(Error::FloorBeyondSize(floor, commit))
1804 if floor == Location::new(3) && commit == Location::new(2)
1805 ));
1806
1807 db.destroy().await.unwrap();
1808 });
1809 }
1810}