1use super::operation::Operation;
28pub use crate::qmdb::compact::Config;
29use crate::{
30 Context,
31 merkle::{Family, Location, Proof, batch, compact as compact_merkle},
32 qmdb::{
33 self, Error,
34 any::value::ValueEncoding,
35 chain::{self, Bounds, Commitment},
36 compact::{batch as compact_batch, witness},
37 operation::Key,
38 sync::{CompactTarget, Request, Source, source},
39 },
40};
41use commonware_codec::{Encode, EncodeShared, Read};
42use commonware_cryptography::{Digest, Hasher};
43use commonware_macros::boxed;
44use commonware_parallel::Strategy;
45use commonware_runtime::Handle;
46use core::marker::PhantomData;
47use std::{
48 collections::BTreeMap,
49 sync::{Arc, Weak},
50};
51
52pub struct Db<F, E, K, V, H, S: Strategy>
55where
56 F: Family,
57 E: Context,
58 K: Key,
59 V: ValueEncoding,
60 H: Hasher,
61 Operation<F, K, V>: EncodeShared,
62 Operation<F, K, V>: Read,
63{
64 merkle: compact_merkle::Merkle<F, H::Digest, S>,
65 last_commit_metadata: Option<V::Value>,
66 inactivity_floor_loc: Location<F>,
67 commit_codec_config: <Operation<F, K, V> as Read>::Cfg,
68 witness: witness::Store<E, F, H::Digest>,
69 _key: PhantomData<K>,
70}
71
72impl<F, E, K, V, H, S: Strategy> std::fmt::Debug for Db<F, E, K, V, H, S>
73where
74 F: Family,
75 E: Context,
76 K: Key,
77 V: ValueEncoding,
78 H: Hasher,
79 Operation<F, K, V>: EncodeShared,
80 Operation<F, K, V>: Read,
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
90type MerkleizeResult<F, D, K, V, S> = Result<Arc<MerkleizedBatch<F, D, K, V, S>>, Error<F>>;
92
93#[allow(clippy::type_complexity)]
95pub struct UnmerkleizedBatch<F, H, K, V, S: Strategy>
96where
97 F: Family,
98 K: Key,
99 V: ValueEncoding,
100 H: Hasher,
101 Operation<F, K, V>: EncodeShared,
102{
103 merkle_batch: compact_merkle::UnmerkleizedBatch<F, H::Digest, S>,
104 mutations: BTreeMap<K, V::Value>,
105 parent: Option<Arc<MerkleizedBatch<F, H::Digest, K, V, S>>>,
106 base: chain::Commitment<F, H::Digest>,
107}
108
109#[derive(Clone)]
111pub struct MerkleizedBatch<F: Family, D: Digest, K: Key, V: ValueEncoding, S: Strategy>
112where
113 Operation<F, K, V>: EncodeShared,
114{
115 pub(super) merkle_batch: Arc<batch::MerkleizedBatch<F, D, S>>,
116 operations: Arc<Vec<Operation<F, K, V>>>,
117 pub(super) commit_metadata: Option<V::Value>,
118 pub(super) parent: Option<Weak<Self>>,
119 pub(super) bounds: chain::Bounds<F, D>,
120}
121
122impl<F: Family, D: Digest, K: Key, V: ValueEncoding, S: Strategy> MerkleizedBatch<F, D, K, V, S>
123where
124 Operation<F, K, V>: EncodeShared,
125{
126 pub(super) fn ancestors(&self) -> impl Iterator<Item = Arc<Self>> + use<F, D, K, V, S> {
127 chain::ancestors(self.parent.clone(), |batch| batch.parent.as_ref())
128 }
129
130 pub(super) const fn commitment(&self) -> Commitment<F, D> {
132 self.bounds.tip
133 }
134
135 pub const fn root(&self) -> D {
137 self.bounds.tip.root
138 }
139
140 pub const fn bounds(&self) -> &Bounds<F, D> {
142 &self.bounds
143 }
144
145 #[allow(clippy::type_complexity)]
147 pub fn operations(&self) -> (Location<F>, Arc<Vec<Operation<F, K, V>>>) {
148 (self.bounds.base.size, Arc::clone(&self.operations))
149 }
150
151 pub fn proof<E, H>(&self, db: &Db<F, E, K, V, H, S>) -> Result<Proof<F, D>, Error<F>>
168 where
169 E: Context,
170 H: Hasher<Digest = D>,
171 Operation<F, K, V>: Read,
172 {
173 let db = self.bounds.compatible(db, db.commitment())?;
174 let inactive_peaks = F::inactive_peaks(self.bounds.tip.size, self.bounds.inactivity_floor);
175 let hasher = qmdb::hasher::<H>();
176 self.merkle_batch
177 .range_proof(
178 db.merkle.mem(),
179 &hasher,
180 self.bounds.base.size..self.bounds.tip.size,
181 inactive_peaks,
182 )
183 .map_err(Into::into)
184 }
185
186 pub fn pinned_nodes<E, H>(&self, db: &Db<F, E, K, V, H, S>) -> Result<Vec<D>, Error<F>>
202 where
203 E: Context,
204 H: Hasher<Digest = D>,
205 Operation<F, K, V>: Read,
206 {
207 let db = self.bounds.compatible(db, db.commitment())?;
208 let base = db.merkle.mem();
209 F::nodes_to_pin(self.bounds.base.size)
210 .map(|pos| {
211 self.merkle_batch
212 .get_node(pos)
213 .or_else(|| base.get_node(pos))
214 .ok_or(crate::merkle::Error::ElementPruned(pos))
215 })
216 .collect::<Result<Vec<_>, _>>()
217 .map_err(Into::into)
218 }
219
220 pub fn new_batch<H>(self: &Arc<Self>) -> UnmerkleizedBatch<F, H, K, V, S>
225 where
226 H: Hasher<Digest = D>,
227 {
228 UnmerkleizedBatch {
229 merkle_batch: compact_merkle::UnmerkleizedBatch::wrap(self.merkle_batch.new_batch()),
230 mutations: BTreeMap::new(),
231 parent: Some(Arc::clone(self)),
232 base: self.commitment(),
233 }
234 }
235}
236
237impl<F, H, K, V, S> UnmerkleizedBatch<F, H, K, V, S>
238where
239 F: Family,
240 K: Key,
241 V: ValueEncoding,
242 H: Hasher,
243 S: Strategy,
244 Operation<F, K, V>: EncodeShared,
245{
246 pub(super) fn new<E>(db: &Db<F, E, K, V, H, S>, base: chain::Commitment<F, H::Digest>) -> Self
247 where
248 E: Context,
249 Operation<F, K, V>: Read,
250 {
251 Self {
252 merkle_batch: db.merkle.new_batch(),
253 mutations: BTreeMap::new(),
254 parent: None,
255 base,
256 }
257 }
258
259 fn db(&self) -> Commitment<F, H::Digest> {
263 self.parent
264 .as_ref()
265 .map_or(self.base, |parent| parent.bounds.db)
266 }
267
268 pub fn set(mut self, key: K, value: V::Value) -> Self {
269 self.mutations.insert(key, value);
270 self
271 }
272
273 #[tracing::instrument(
288 name = "qmdb.immutable.compact.batch.merkleize",
289 level = "info",
290 skip_all
291 )]
292 pub async fn merkleize<E>(
293 self,
294 db: &Db<F, E, K, V, H, S>,
295 metadata: Option<V::Value>,
296 inactivity_floor: Location<F>,
297 ) -> MerkleizeResult<F, H::Digest, K, V, S>
298 where
299 E: Context,
300 Operation<F, K, V>: Read,
301 {
302 let live_ancestors: Vec<_> =
303 chain::parent_and_ancestors(self.parent.as_ref(), |parent| parent.ancestors())
304 .collect();
305 let boundary = chain::effective_boundary(
306 self.db(),
307 live_ancestors.last().map(|oldest| oldest.bounds.base),
308 );
309
310 let ancestors: Vec<_> = live_ancestors
311 .iter()
312 .map(|batch| batch.commitment())
313 .collect();
314 let db = chain::merkleizable(db, db.commitment(), boundary, ancestors.iter().copied())?;
315
316 let mut ops: Vec<Operation<F, K, V>> = Vec::with_capacity(self.mutations.len() + 1);
317 for (key, value) in self.mutations {
318 ops.push(Operation::Set(key, value));
319 }
320 ops.push(Operation::Commit(metadata.clone(), inactivity_floor));
321
322 let operations = Arc::new(ops);
323 let total_size = self.base.size + operations.len() as u64;
324 chain::validate_merkleize_floor::<F>(
325 self.parent.as_ref().map_or_else(
326 || db.inactivity_floor_loc(),
327 |parent| parent.bounds.inactivity_floor,
328 ),
329 inactivity_floor,
330 total_size - 1,
331 )?;
332 let inactive_peaks = F::inactive_peaks(total_size, inactivity_floor);
333 let (merkle, root) = compact_batch::merkleize_ops::<F, H, S, _>(
334 &db.merkle,
335 self.merkle_batch,
336 Arc::clone(&operations),
337 inactive_peaks,
338 )
339 .await?;
340
341 drop(live_ancestors);
343
344 Ok(Arc::new(MerkleizedBatch {
345 merkle_batch: merkle,
346 operations,
347 commit_metadata: metadata,
348 parent: self.parent.as_ref().map(Arc::downgrade),
349 bounds: chain::Bounds {
350 base: self.base,
351 db: boundary,
352 tip: Commitment::new(total_size, root),
353 ancestors,
354 inactivity_floor,
355 },
356 }))
357 }
358}
359
360impl<F, E, K, V, H, S> Db<F, E, K, V, H, S>
361where
362 F: Family,
363 E: Context,
364 K: Key,
365 V: ValueEncoding,
366 H: Hasher,
367 S: Strategy,
368 Operation<F, K, V>: EncodeShared,
369 Operation<F, K, V>: Read,
370{
371 #[boxed]
377 pub async fn init(
378 context: E,
379 cfg: Config<<Operation<F, K, V> as Read>::Cfg, S>,
380 max_size: Option<Location<F>>,
381 ) -> Result<Self, Error<F>> {
382 let Config {
383 strategy,
384 witness: witness_config,
385 commit_codec_config,
386 } = cfg;
387
388 let mut merkle = compact_merkle::Merkle::new(strategy);
390 let (witness, last_commit_op) = witness::init::<E, F, H, S, Operation<F, K, V>>(
391 context.child("witness"),
392 witness_config,
393 max_size,
394 &mut merkle,
395 &commit_codec_config,
396 Operation::<F, K, V>::Commit(None, Location::new(0))
397 .encode()
398 .to_vec(),
399 )
400 .await?;
401
402 let Operation::Commit(last_commit_metadata, inactivity_floor_loc) = last_commit_op else {
403 return Err(Error::DataCorrupted("last operation was not a commit"));
404 };
405
406 Ok(Self {
407 merkle,
408 last_commit_metadata,
409 inactivity_floor_loc,
410 commit_codec_config,
411 witness,
412 _key: PhantomData,
413 })
414 }
415
416 fn encode_commit_op(metadata: Option<V::Value>, inactivity_floor_loc: Location<F>) -> Vec<u8> {
417 Operation::<F, K, V>::Commit(metadata, inactivity_floor_loc)
418 .encode()
419 .to_vec()
420 }
421
422 pub(crate) fn init_from_sync(
429 strategy: S,
430 journal: witness::Journal<E, F, H::Digest>,
431 commit_codec_config: <Operation<F, K, V> as Read>::Cfg,
432 last_commit_loc: Location<F>,
433 pinned_nodes: Vec<H::Digest>,
434 last_commit_op: Operation<F, K, V>,
435 ) -> Result<Self, Error<F>> {
436 let Operation::Commit(last_commit_metadata, inactivity_floor_loc) = last_commit_op else {
437 return Err(Error::UnexpectedData(last_commit_loc));
438 };
439 witness::validate_inactivity_floor(inactivity_floor_loc, last_commit_loc)?;
440
441 let op_bytes = Self::encode_commit_op(last_commit_metadata.clone(), inactivity_floor_loc);
442 let mut merkle =
443 compact_merkle::Merkle::from_compact_state(strategy, last_commit_loc, pinned_nodes)?;
444 let hasher = qmdb::hasher::<H>();
445 merkle.append_leaf(&hasher, &op_bytes)?;
446 let imported = witness::build_witness::<F, H, S>(&merkle, inactivity_floor_loc, op_bytes)?;
447 merkle.prune_to_frontier();
448
449 let witness = witness::Store::from_import(journal, imported);
450 Ok(Self {
451 merkle,
452 last_commit_metadata,
453 inactivity_floor_loc,
454 commit_codec_config,
455 witness,
456 _key: PhantomData,
457 })
458 }
459
460 pub const fn root(&self) -> H::Digest {
462 self.witness.tip().root
463 }
464
465 pub const fn inactivity_floor_loc(&self) -> Location<F> {
467 self.inactivity_floor_loc
468 }
469
470 pub const fn size(&self) -> Location<F> {
472 self.witness.tip().size()
473 }
474
475 pub fn get_metadata(&self) -> Option<V::Value> {
477 self.last_commit_metadata.clone()
478 }
479
480 pub const fn target(&self) -> CompactTarget<F, H::Digest> {
485 self.witness.tip().target()
486 }
487
488 pub(crate) const fn commitment(&self) -> chain::Commitment<F, H::Digest> {
490 chain::Commitment::new(self.size(), self.root())
491 }
492
493 pub fn new_batch(&self) -> UnmerkleizedBatch<F, H, K, V, S> {
495 UnmerkleizedBatch::new(self, self.commitment())
496 }
497
498 pub fn to_batch(&self) -> Arc<MerkleizedBatch<F, H::Digest, K, V, S>> {
500 Arc::new(MerkleizedBatch {
501 merkle_batch: self.merkle.to_batch(),
502 operations: Arc::new(Vec::new()),
503 commit_metadata: self.last_commit_metadata.clone(),
504 parent: None,
505 bounds: chain::Bounds::from_db(self.commitment(), self.inactivity_floor_loc),
506 })
507 }
508
509 pub fn validate_batch(
515 &self,
516 batch: &MerkleizedBatch<F, H::Digest, K, V, S>,
517 ) -> Result<(), Error<F>> {
518 batch.bounds.validate_apply_to(self.commitment())
519 }
520
521 #[tracing::instrument(
532 name = "qmdb.immutable.compact.db.apply_batch",
533 level = "info",
534 skip_all
535 )]
536 pub async fn apply_batch(
537 mut self,
538 batch: Arc<MerkleizedBatch<F, H::Digest, K, V, S>>,
539 ) -> Result<Self, Error<F>> {
540 self.validate_batch(&batch)?;
541 self.merkle.apply_batch(&batch.merkle_batch)?;
542 self.last_commit_metadata = batch.commit_metadata.clone();
543 self.inactivity_floor_loc = batch.bounds.inactivity_floor;
544 let last_commit_metadata = self.last_commit_metadata.clone();
545 let inactivity_floor_loc = self.inactivity_floor_loc;
546 self.witness = self
547 .witness
548 .apply::<H, S>(&mut self.merkle, inactivity_floor_loc, || {
549 Self::encode_commit_op(last_commit_metadata, inactivity_floor_loc)
550 })
551 .await?;
552 assert_eq!(self.commitment(), batch.bounds.tip);
553 Ok(self)
554 }
555
556 #[tracing::instrument(
564 name = "qmdb.immutable.compact.db.start_sync",
565 level = "info",
566 skip_all
567 )]
568 pub async fn start_sync(mut self) -> Result<(Self, Handle<()>), Error<F>> {
569 let last_commit_metadata = self.last_commit_metadata.clone();
570 let inactivity_floor_loc = self.inactivity_floor_loc;
571 let handle;
572 (self.witness, handle) = self
573 .witness
574 .start_sync::<H, S>(&mut self.merkle, inactivity_floor_loc, || {
575 Self::encode_commit_op(last_commit_metadata, inactivity_floor_loc)
576 })
577 .await?;
578 Ok((self, handle))
579 }
580
581 #[tracing::instrument(name = "qmdb.immutable.compact.db.commit", level = "info", skip_all)]
584 pub async fn commit(mut self) -> Result<Self, Error<F>> {
585 let last_commit_metadata = self.last_commit_metadata.clone();
586 let inactivity_floor_loc = self.inactivity_floor_loc;
587 self.witness = self
588 .witness
589 .commit::<H, S>(&mut self.merkle, inactivity_floor_loc, || {
590 Self::encode_commit_op(last_commit_metadata, inactivity_floor_loc)
591 })
592 .await?;
593 Ok(self)
594 }
595
596 #[tracing::instrument(name = "qmdb.immutable.compact.db.sync", level = "info", skip_all)]
599 pub async fn sync(mut self) -> Result<Self, Error<F>> {
600 let last_commit_metadata = self.last_commit_metadata.clone();
601 let inactivity_floor_loc = self.inactivity_floor_loc;
602 self.witness = self
603 .witness
604 .sync::<H, S>(&mut self.merkle, inactivity_floor_loc, || {
605 Self::encode_commit_op(last_commit_metadata, inactivity_floor_loc)
606 })
607 .await?;
608 Ok(self)
609 }
610
611 #[tracing::instrument(name = "qmdb.immutable.compact.db.prune", level = "info", skip_all)]
621 pub async fn prune(mut self, pruning_boundary: Location<F>) -> Result<Self, Error<F>> {
622 self.witness = self.witness.prune(pruning_boundary).await?;
623 Ok(self)
624 }
625
626 #[boxed]
628 pub async fn destroy(self) -> Result<(), Error<F>> {
629 self.witness.destroy().await?;
630 Ok(())
631 }
632}
633
634impl<F, E, K, V, H, S> Source for Db<F, E, K, V, H, S>
635where
636 F: Family,
637 E: Context,
638 K: Key,
639 V: ValueEncoding,
640 H: Hasher,
641 Operation<F, K, V>: EncodeShared + Read,
642 S: Strategy,
643{
644 type Family = F;
645 type Digest = H::Digest;
646 type Op = Operation<F, K, V>;
647 type Error = qmdb::Error<F>;
648
649 async fn serve(&self, request: Request<F>) -> source::Result<Self> {
650 let response = self
651 .witness
652 .compact_state::<H, S, Self::Op>(
653 self.merkle.strategy(),
654 &self.commit_codec_config,
655 request,
656 )
657 .await?;
658 Ok((response, None))
659 }
660}
661
662#[cfg(test)]
663mod tests {
664 use super::*;
665 use crate::{
666 journal::contiguous::{Contiguous as _, fixed, variable::Config as JournalConfig},
667 merkle::{mmb, mmr},
668 metadata::{Config as MetadataConfig, Metadata},
669 qmdb::{
670 any::value::FixedEncoding, compact::witness, verify_proof,
671 verify_proof_and_pinned_nodes,
672 },
673 };
674 use commonware_cryptography::{Sha256, sha256::Digest};
675 use commonware_macros::test_traced;
676 use commonware_parallel::Sequential;
677 use commonware_runtime::{
678 Blob as _, Runner as _, Storage as _, Supervisor as _,
679 buffer::paged::CacheRef,
680 deterministic,
681 mocks::{DelayedSyncContext, PendingSyncs, drive_pending_syncs},
682 };
683 use commonware_utils::{NZU16, NZU64, NZUsize, sequence::VecU64};
684 use core::future::Future;
685 use std::num::{NonZeroU16, NonZeroUsize};
686
687 type TestDb<F> =
688 Db<F, deterministic::Context, Digest, FixedEncoding<Digest>, Sha256, Sequential>;
689
690 const WITNESS_PAGE_SIZE: NonZeroU16 = NZU16!(77);
691 const WITNESS_PAGE_CACHE_SIZE: NonZeroUsize = NZUsize!(9);
692
693 fn witness_config(partition: &str) -> JournalConfig<()> {
694 JournalConfig {
695 partition: format!("{partition}-witness"),
696 items_per_section: NZU64!(64),
697 compression: None,
698 codec_config: (),
699 page_cache: CacheRef::new(WITNESS_PAGE_SIZE, WITNESS_PAGE_CACHE_SIZE),
700 write_buffer: NZUsize!(1024),
701 replay_buffer: NZUsize!(1024),
702 }
703 }
704
705 async fn open_db<F: Family>(context: deterministic::Context, partition: &str) -> TestDb<F> {
706 let witness_cfg = witness_config(partition);
707 let cfg = Config {
708 strategy: Sequential,
709 witness: witness_cfg,
710 commit_codec_config: (),
711 };
712 Db::init(context, cfg, None).await.unwrap()
713 }
714
715 async fn compact_merkleize_foreign_db_inner<F: Family>(context: deterministic::Context) {
716 let db = open_db::<F>(context.child("db"), "immutable-merkleize-foreign-db").await;
717 let foreign = open_db::<F>(
718 context.child("foreign"),
719 "immutable-merkleize-foreign-db-foreign",
720 )
721 .await;
722
723 let batch = db
724 .new_batch()
725 .set(Sha256::fill(1), Sha256::fill(11))
726 .merkleize(&db, None, Location::new(0))
727 .await
728 .unwrap();
729 let db = db.apply_batch(batch).await.unwrap();
730 let batch = foreign
731 .new_batch()
732 .set(Sha256::fill(2), Sha256::fill(99))
733 .merkleize(&foreign, None, Location::new(0))
734 .await
735 .unwrap();
736 let foreign = foreign.apply_batch(batch).await.unwrap();
737 assert_eq!(db.size(), foreign.size());
738 assert_ne!(db.root(), foreign.root());
739
740 let batch = db.new_batch().set(Sha256::fill(3), Sha256::fill(22));
741 assert!(matches!(
742 batch.merkleize(&foreign, None, Location::new(0)).await,
743 Err(Error::StaleBatch)
744 ));
745
746 let parent = db
747 .new_batch()
748 .set(Sha256::fill(4), Sha256::fill(33))
749 .merkleize(&db, None, Location::new(0))
750 .await
751 .unwrap();
752 let child = parent
753 .new_batch::<Sha256>()
754 .set(Sha256::fill(5), Sha256::fill(44));
755 assert!(matches!(
756 child.merkleize(&foreign, None, Location::new(0)).await,
757 Err(Error::StaleBatch)
758 ));
759 db.destroy().await.unwrap();
760 foreign.destroy().await.unwrap();
761 }
762
763 async fn compact_merkleize_stale_sibling_inner<F: Family>(context: deterministic::Context) {
764 let db = open_db::<F>(context.child("db"), "immutable-merkleize-stale-sibling").await;
765
766 let direct = db.new_batch().set(Sha256::fill(1), Sha256::fill(11));
767 let sibling = db
768 .new_batch()
769 .set(Sha256::fill(2), Sha256::fill(22))
770 .merkleize(&db, None, Location::new(0))
771 .await
772 .unwrap();
773 let db = db.apply_batch(sibling).await.unwrap();
774 assert!(matches!(
775 direct.merkleize(&db, None, Location::new(0)).await,
776 Err(Error::StaleBatch)
777 ));
778
779 let parent = db
780 .new_batch()
781 .set(Sha256::fill(3), Sha256::fill(33))
782 .merkleize(&db, None, Location::new(0))
783 .await
784 .unwrap();
785 let child = parent
786 .new_batch::<Sha256>()
787 .set(Sha256::fill(4), Sha256::fill(44));
788 let sibling = db
789 .new_batch()
790 .set(Sha256::fill(5), Sha256::fill(55))
791 .merkleize(&db, None, Location::new(0))
792 .await
793 .unwrap();
794 let db = db.apply_batch(sibling).await.unwrap();
795 assert!(matches!(
796 child.merkleize(&db, None, Location::new(0)).await,
797 Err(Error::StaleBatch)
798 ));
799
800 db.destroy().await.unwrap();
801 }
802
803 async fn compact_merkleize_ancestor_states_inner<F: Family>(context: deterministic::Context) {
804 let db = open_db::<F>(context.child("db"), "immutable-merkleize-ancestor-states").await;
805
806 let grandparent = db
807 .new_batch()
808 .set(Sha256::fill(1), Sha256::fill(11))
809 .merkleize(&db, None, Location::new(0))
810 .await
811 .unwrap();
812 let parent = grandparent
813 .new_batch::<Sha256>()
814 .set(Sha256::fill(2), Sha256::fill(22))
815 .merkleize(&db, None, Location::new(0))
816 .await
817 .unwrap();
818 let pending = parent
819 .new_batch::<Sha256>()
820 .set(Sha256::fill(3), Sha256::fill(33))
821 .merkleize(&db, None, Location::new(0))
822 .await
823 .unwrap();
824
825 let db = db.apply_batch(Arc::clone(&grandparent)).await.unwrap();
826 let applied = parent
827 .new_batch::<Sha256>()
828 .set(Sha256::fill(3), Sha256::fill(33))
829 .merkleize(&db, None, Location::new(0))
830 .await
831 .unwrap();
832 assert_eq!(pending.root(), applied.root());
833
834 drop(grandparent);
835 let retired = parent
836 .new_batch::<Sha256>()
837 .set(Sha256::fill(3), Sha256::fill(33))
838 .merkleize(&db, None, Location::new(0))
839 .await
840 .unwrap();
841 assert_eq!(retired.bounds().db, db.commitment());
842 assert_eq!(pending.root(), retired.root());
843
844 let child = parent
845 .new_batch::<Sha256>()
846 .set(Sha256::fill(3), Sha256::fill(33));
847 let db = db.apply_batch(parent).await.unwrap();
848 let child = child.merkleize(&db, None, Location::new(0)).await.unwrap();
849 assert_eq!(pending.root(), child.root());
850 let expected_root = child.root();
851 let db = db.apply_batch(child).await.unwrap();
852 assert_eq!(db.root(), expected_root);
853 db.destroy().await.unwrap();
854 }
855
856 #[test_traced]
857 fn test_compact_merkleize_foreign_db_mmr() {
858 deterministic::Runner::default().start(compact_merkleize_foreign_db_inner::<mmr::Family>);
859 }
860
861 #[test_traced]
862 fn test_compact_merkleize_foreign_db_mmb() {
863 deterministic::Runner::default().start(compact_merkleize_foreign_db_inner::<mmb::Family>);
864 }
865
866 #[test_traced]
867 fn test_compact_merkleize_stale_sibling_mmr() {
868 deterministic::Runner::default()
869 .start(compact_merkleize_stale_sibling_inner::<mmr::Family>);
870 }
871
872 #[test_traced]
873 fn test_compact_merkleize_stale_sibling_mmb() {
874 deterministic::Runner::default()
875 .start(compact_merkleize_stale_sibling_inner::<mmb::Family>);
876 }
877
878 #[test_traced]
879 fn test_compact_merkleize_ancestor_states_mmr() {
880 deterministic::Runner::default()
881 .start(compact_merkleize_ancestor_states_inner::<mmr::Family>);
882 }
883
884 #[test_traced]
885 fn test_compact_merkleize_ancestor_states_mmb() {
886 deterministic::Runner::default()
887 .start(compact_merkleize_ancestor_states_inner::<mmb::Family>);
888 }
889
890 async fn open_bounded<F: Family>(
891 context: deterministic::Context,
892 witness_cfg: JournalConfig<()>,
893 cap: Location<F>,
894 ) -> Result<TestDb<F>, Error<F>> {
895 let cfg = Config {
896 strategy: Sequential,
897 witness: witness_cfg,
898 commit_codec_config: (),
899 };
900 Db::init(context, cfg, Some(cap)).await
901 }
902
903 async fn proof_refused_after_off_chain_reopen<F: Family>(context: deterministic::Context) {
906 let db = open_db::<F>(context.child("db"), "off-chain-proof").await;
907 let mut seed = db.new_batch();
908 for value in 1..=6 {
909 seed = seed.set(Sha256::fill(value), Sha256::fill(value));
910 }
911 let seed = seed.merkleize(&db, None, Location::new(0)).await.unwrap();
912 let db = db.apply_batch(seed).await.unwrap();
913 let db = db.sync().await.unwrap();
914 let batch = db
915 .new_batch()
916 .set(Sha256::fill(100), Sha256::fill(100))
917 .merkleize(&db, None, Location::new(0))
918 .await
919 .unwrap();
920 let (start, ops) = batch.operations();
921 let root = batch.root();
922 let original_proof = batch.proof(&db).unwrap();
923 let original_pins = batch.pinned_nodes(&db).unwrap();
924 assert!(verify_proof_and_pinned_nodes::<Sha256, _, _>(
925 &original_proof,
926 start,
927 &ops,
928 &original_pins,
929 &root
930 ));
931 drop(db);
932
933 let db = open_bounded::<F>(
934 context.child("reopen"),
935 witness_config("off-chain-proof"),
936 Location::new(1),
937 )
938 .await
939 .unwrap();
940 let mut other = db.new_batch();
941 for value in 11..=16 {
942 other = other.set(Sha256::fill(value), Sha256::fill(value));
943 }
944 let other = other.merkleize(&db, None, Location::new(0)).await.unwrap();
945 let db = db.apply_batch(other).await.unwrap();
946 assert_eq!(db.size(), start);
947 assert!(matches!(
948 batch
949 .new_batch::<Sha256>()
950 .merkleize(&db, None, Location::new(0))
951 .await,
952 Err(Error::StaleBatch)
953 ));
954
955 let proof = batch.proof(&db);
956 let pins = batch.pinned_nodes(&db);
957 assert!(
958 matches!(proof, Err(Error::StaleRead)),
959 "proof must return StaleRead at a state outside the batch's chain"
960 );
961 assert!(
962 matches!(pins, Err(Error::StaleRead)),
963 "pinned nodes must return StaleRead at a state outside the batch's chain"
964 );
965 }
966
967 #[test]
968 fn compact_mmr_proof_refused_after_off_chain_reopen() {
969 deterministic::Runner::default().start(proof_refused_after_off_chain_reopen::<mmr::Family>);
970 }
971
972 #[test]
973 fn compact_mmb_proof_refused_after_off_chain_reopen() {
974 deterministic::Runner::default().start(proof_refused_after_off_chain_reopen::<mmb::Family>);
975 }
976
977 async fn compact_operations_and_proof_inner<F: Family>(context: deterministic::Context) {
981 let db = open_db::<F>(context.child("db"), "immutable-operations-and-proof").await;
982 let value = |key: u8| Sha256::fill(key.wrapping_add(100));
983
984 let mut seed = db.new_batch();
986 for key in 1u8..=6 {
987 seed = seed.set(Sha256::fill(key), value(key));
988 }
989 let seed = seed
990 .merkleize(&db, Some(Sha256::fill(7)), Location::new(0))
991 .await
992 .unwrap();
993 let db = db.apply_batch(seed).await.unwrap().sync().await.unwrap();
994 let floor = db.size();
995 assert_eq!(floor, Location::new(8));
996
997 assert!(matches!(
999 db.to_batch().proof(&db),
1000 Err(Error::Merkle(crate::merkle::Error::Empty))
1001 ));
1002
1003 let mut parent = db.new_batch();
1006 for key in 8u8..=12 {
1007 parent = parent.set(Sha256::fill(key), value(key));
1008 }
1009 let parent = parent
1010 .merkleize(&db, Some(Sha256::fill(13)), floor)
1011 .await
1012 .unwrap();
1013 let child = parent
1014 .new_batch::<Sha256>()
1015 .set(Sha256::fill(14), value(14))
1016 .merkleize(&db, Some(Sha256::fill(15)), floor)
1017 .await
1018 .unwrap();
1019
1020 let (child_start, child_ops) = child.operations();
1022 let (_, child_ops_again) = child.operations();
1023 let child_end = child.bounds().tip.size;
1024 assert!(Arc::ptr_eq(&child_ops, &child_ops_again));
1025 assert_eq!(child_start, parent.bounds().tip.size);
1026 assert_eq!(*child_start + child_ops.len() as u64, *child_end);
1027 assert_eq!(child_end, Location::new(16));
1028 assert!(matches!(
1029 child_ops.as_slice(),
1030 [Operation::Set(key, set_value), Operation::Commit(Some(metadata), operation_floor)]
1031 if key == &Sha256::fill(14)
1032 && set_value == &value(14)
1033 && metadata == &Sha256::fill(15)
1034 && operation_floor == &floor
1035 ));
1036
1037 let child_root = child.root();
1039 let child_proof = child.proof(&db).unwrap();
1040 let child_pins = child.pinned_nodes(&db).unwrap();
1041 assert_eq!(child_proof.leaves, child_end);
1042 assert_eq!(
1043 child_proof.inactive_peaks,
1044 F::inactive_peaks(child_end, floor),
1045 );
1046 assert!(verify_proof::<Sha256, _, _>(
1047 &child_proof,
1048 child_start,
1049 &child_ops,
1050 &child_root
1051 ));
1052 assert!(verify_proof_and_pinned_nodes::<Sha256, _, _>(
1053 &child_proof,
1054 child_start,
1055 &child_ops,
1056 &child_pins,
1057 &child_root
1058 ));
1059
1060 assert!(child_pins.len() > 1);
1062 let mut reordered_child_pins = child_pins.clone();
1063 reordered_child_pins.swap(0, 1);
1064 assert!(!verify_proof_and_pinned_nodes::<Sha256, _, _>(
1065 &child_proof,
1066 child_start,
1067 &child_ops,
1068 &reordered_child_pins,
1069 &child_root
1070 ));
1071
1072 let db = db.apply_batch(parent).await.unwrap();
1076 let child_proof_after = child.proof(&db).unwrap();
1077 assert_eq!(child.pinned_nodes(&db).unwrap(), child_pins);
1078 assert!(verify_proof_and_pinned_nodes::<Sha256, _, _>(
1079 &child_proof_after,
1080 child_start,
1081 &child_ops,
1082 &child_pins,
1083 &child_root
1084 ));
1085 let applied_start = db.size();
1086 let db = db.apply_batch(child).await.unwrap();
1087 let child_range = applied_start..db.size();
1088 assert_eq!(child_range, child_start..child_end);
1089
1090 let commit_floor = db.size();
1092 let commit_only = db
1093 .new_batch()
1094 .merkleize(&db, Some(Sha256::fill(16)), commit_floor)
1095 .await
1096 .unwrap();
1097 let (commit_start, commit_ops) = commit_only.operations();
1098 let commit_end = commit_only.bounds().tip.size;
1099 let commit_root = commit_only.root();
1100 let commit_proof = commit_only.proof(&db).unwrap();
1101 let commit_pins = commit_only.pinned_nodes(&db).unwrap();
1102 assert_eq!(commit_start, commit_floor);
1103 assert!(matches!(
1104 commit_ops.as_slice(),
1105 [Operation::Commit(Some(metadata), operation_floor)]
1106 if metadata == &Sha256::fill(16) && operation_floor == &commit_floor
1107 ));
1108 assert_eq!(*commit_start + commit_ops.len() as u64, *commit_end);
1109 assert_eq!(commit_proof.leaves, commit_end);
1110 assert_eq!(
1111 commit_proof.inactive_peaks,
1112 F::inactive_peaks(commit_end, commit_floor)
1113 );
1114 assert!(verify_proof_and_pinned_nodes::<Sha256, _, _>(
1115 &commit_proof,
1116 commit_start,
1117 &commit_ops,
1118 &commit_pins,
1119 &commit_root
1120 ));
1121 let applied_start = db.size();
1122 let db = db.apply_batch(commit_only).await.unwrap();
1123 let commit_range = applied_start..db.size();
1124 assert_eq!(commit_range, commit_start..commit_end);
1125 let db = db.sync().await.unwrap();
1126
1127 let late_parent = db
1131 .new_batch()
1132 .set(Sha256::fill(17), value(17))
1133 .merkleize(&db, Some(Sha256::fill(18)), db.size())
1134 .await
1135 .unwrap();
1136 let late = late_parent
1137 .new_batch::<Sha256>()
1138 .set(Sha256::fill(19), value(19))
1139 .merkleize(&db, Some(Sha256::fill(20)), db.size())
1140 .await
1141 .unwrap();
1142 let db = db.apply_batch(Arc::clone(&late_parent)).await.unwrap();
1143 assert!(matches!(
1144 late_parent.proof(&db),
1145 Err(Error::Merkle(crate::merkle::Error::ElementPruned(_)))
1146 ));
1147 assert!(matches!(
1148 late_parent.pinned_nodes(&db),
1149 Err(Error::Merkle(crate::merkle::Error::ElementPruned(_)))
1150 ));
1151 drop(late_parent);
1152
1153 let (late_start, late_ops) = late.operations();
1154 let late_root = late.root();
1155 let late_proof = late.proof(&db).unwrap();
1156 let late_pins = late.pinned_nodes(&db).unwrap();
1157 assert!(verify_proof_and_pinned_nodes::<Sha256, _, _>(
1158 &late_proof,
1159 late_start,
1160 &late_ops,
1161 &late_pins,
1162 &late_root
1163 ));
1164 let db = db.apply_batch(late).await.unwrap();
1165
1166 db.destroy().await.unwrap();
1167 }
1168
1169 #[test_traced]
1170 fn test_compact_operations_and_proof_mmr() {
1171 deterministic::Runner::default().start(compact_operations_and_proof_inner::<mmr::Family>);
1172 }
1173
1174 #[test_traced]
1175 fn test_compact_operations_and_proof_mmb() {
1176 deterministic::Runner::default().start(compact_operations_and_proof_inner::<mmb::Family>);
1177 }
1178
1179 async fn open_witness_journal(
1181 context: deterministic::Context,
1182 partition: &str,
1183 ) -> witness::Journal<deterministic::Context, mmr::Family, Digest> {
1184 let cfg = witness_config(partition);
1185 witness::Journal::init(context, cfg).await.unwrap()
1186 }
1187
1188 type DelayedDb = Db<
1190 mmr::Family,
1191 DelayedSyncContext<deterministic::Context>,
1192 Digest,
1193 FixedEncoding<Digest>,
1194 Sha256,
1195 Sequential,
1196 >;
1197
1198 fn open_delayed_db(
1203 context: deterministic::Context,
1204 partition: &str,
1205 pending: &PendingSyncs,
1206 ) -> impl Future<Output = Result<DelayedDb, Error<mmr::Family>>> {
1207 let witness_cfg = witness_config(partition);
1208 let cfg = Config {
1209 strategy: Sequential,
1210 witness: witness_cfg,
1211 commit_codec_config: (),
1212 };
1213 let context = DelayedSyncContext {
1214 inner: context,
1215 pending: pending.clone(),
1216 };
1217 DelayedDb::init(context, cfg, None)
1218 }
1219
1220 async fn apply_set(db: DelayedDb, key: Digest, value: Digest, metadata: Digest) -> DelayedDb {
1222 let floor = db.inactivity_floor_loc();
1223 let batch = db
1224 .new_batch()
1225 .set(key, value)
1226 .merkleize(&db, Some(metadata), floor)
1227 .await
1228 .unwrap();
1229 db.apply_batch(batch).await.unwrap()
1230 }
1231
1232 #[test_traced]
1234 fn test_compact_start_sync_recovery() {
1235 deterministic::Runner::default().start(|ctx| async move {
1236 let partition = "immutable-start-sync-recovery";
1237 let pending = PendingSyncs::default();
1238 pending.unblock();
1239 let mut db = open_delayed_db(ctx.child("delayed"), partition, &pending)
1240 .await
1241 .unwrap();
1242 let metadata = Sha256::fill(9u8);
1243 db = apply_set(db, Sha256::fill(1u8), Sha256::fill(2u8), metadata).await;
1244
1245 let handle;
1246 (db, handle) = db.start_sync().await.unwrap();
1247 handle.await.unwrap();
1248 let root = db.root();
1249 drop(db);
1250
1251 let db = open_delayed_db(ctx.child("reopen"), partition, &pending)
1252 .await
1253 .unwrap();
1254 assert_eq!(db.root(), root);
1255 assert_eq!(db.get_metadata(), Some(metadata));
1256 db.destroy().await.unwrap();
1257 });
1258 }
1259
1260 #[test_traced]
1264 fn test_compact_start_sync_failure_propagates() {
1265 deterministic::Runner::default().start(|ctx| async move {
1266 let pending = PendingSyncs::default();
1267 pending.unblock();
1268 let mut db =
1269 open_delayed_db(ctx.child("delayed"), "immutable-start-sync-fail", &pending)
1270 .await
1271 .unwrap();
1272 db = apply_set(db, Sha256::fill(1u8), Sha256::fill(2u8), Sha256::fill(9u8)).await;
1273
1274 pending.arm_fail();
1276
1277 let handle;
1278 (db, handle) = db.start_sync().await.unwrap();
1279 assert!(
1280 handle.await.is_err(),
1281 "the sync handle surfaces the failure"
1282 );
1283 let starts_before = pending.starts();
1284
1285 assert!(
1288 db.commit().await.is_err(),
1289 "the next durability op surfaces the failed in-flight sync"
1290 );
1291 assert_eq!(
1292 pending.starts(),
1293 starts_before,
1294 "the surfaced error is the retained failure, not a fresh sync's"
1295 );
1296 });
1297 }
1298
1299 #[test_traced("INFO")]
1300 fn test_compact_stale_batch_rejected() {
1301 deterministic::Runner::default().start(|context| async move {
1302 let db = open_db::<mmr::Family>(context.child("db"), "immutable-stale").await;
1303
1304 let key1 = Sha256::hash(&[&[1]]);
1305 let key2 = Sha256::hash(&[&[2]]);
1306 let value1 = Sha256::fill(10u8);
1307 let value2 = Sha256::fill(20u8);
1308
1309 let batch_a = db
1310 .new_batch()
1311 .set(key1, value1)
1312 .merkleize(&db, None, Location::new(0))
1313 .await
1314 .unwrap();
1315 let batch_b = db
1316 .new_batch()
1317 .set(key2, value2)
1318 .merkleize(&db, None, Location::new(0))
1319 .await
1320 .unwrap();
1321
1322 let stale = db.new_batch().set(key2, value2);
1323 let expected_root = batch_a.root();
1324 let db = db.apply_batch(batch_a).await.unwrap();
1325 assert_eq!(db.root(), expected_root);
1326
1327 assert!(matches!(
1330 stale.merkleize(&db, None, Location::new(0)).await,
1331 Err(Error::StaleBatch)
1332 ));
1333 assert!(matches!(
1334 db.apply_batch(batch_b).await,
1335 Err(Error::StaleBatch)
1336 ));
1337 });
1338 }
1339
1340 #[test_traced("INFO")]
1341 fn test_compact_delayed_merkleize_after_ancestor_apply() {
1342 deterministic::Runner::default().start(|context| async move {
1343 let db = open_db::<mmr::Family>(context.child("db"), "immutable-delayed-child").await;
1344 let key1 = Sha256::hash(&[&[1]]);
1345 let key2 = Sha256::hash(&[&[2]]);
1346 let key3 = Sha256::hash(&[&[3]]);
1347 let value1 = Sha256::fill(10u8);
1348 let value2 = Sha256::fill(20u8);
1349 let value3 = Sha256::fill(30u8);
1350
1351 let a = db
1352 .new_batch()
1353 .set(key1, value1)
1354 .merkleize(&db, None, Location::new(0))
1355 .await
1356 .unwrap();
1357 let b = a
1358 .new_batch::<Sha256>()
1359 .set(key2, value2)
1360 .merkleize(&db, None, Location::new(0))
1361 .await
1362 .unwrap();
1363 let c = b.new_batch::<Sha256>().set(key3, value3);
1364
1365 let db = db.apply_batch(a).await.unwrap();
1366 let c = c.merkleize(&db, None, Location::new(0)).await.unwrap();
1367 let expected_root = c.root();
1368 let db = db.apply_batch(c).await.unwrap();
1369
1370 assert_eq!(db.root(), expected_root);
1371 });
1372 }
1373
1374 #[test_traced("INFO")]
1376 fn test_compact_to_batch_reflects_live_state() {
1377 deterministic::Runner::default().start(|context| async move {
1378 let db = open_db::<mmr::Family>(context.child("db"), "immutable-to-batch-live").await;
1379
1380 let pre_apply_root = db.root();
1381 let pre_snapshot = db.to_batch();
1382 assert_eq!(
1383 pre_snapshot.root(),
1384 pre_apply_root,
1385 "snapshot before any mutation should match the live root"
1386 );
1387
1388 let key = Sha256::hash(&[&[1]]);
1389 let value = Sha256::fill(10u8);
1390 let batch = db
1391 .new_batch()
1392 .set(key, value)
1393 .merkleize(&db, None, Location::new(0))
1394 .await
1395 .unwrap();
1396 let db = db.apply_batch(batch).await.unwrap();
1397
1398 let live_root = db.root();
1400 assert_ne!(
1401 live_root, pre_apply_root,
1402 "applying a non-empty batch must change the live root"
1403 );
1404
1405 let snapshot = db.to_batch();
1406 assert_eq!(
1407 snapshot.root(),
1408 live_root,
1409 "to_batch().root() must match the live db.root() even before sync"
1410 );
1411
1412 db.destroy().await.unwrap();
1413 });
1414 }
1415
1416 #[test_traced("INFO")]
1417 fn test_compact_stale_batch_chained() {
1418 deterministic::Runner::default().start(|context| async move {
1419 let db = open_db::<mmr::Family>(context.child("db"), "immutable-chained-stale").await;
1420
1421 let common_parent = db
1422 .new_batch()
1423 .set(Sha256::hash(&[&[10]]), Sha256::fill(10u8))
1424 .merkleize(&db, None, Location::new(0))
1425 .await
1426 .unwrap();
1427 let sibling_a = common_parent
1428 .new_batch::<Sha256>()
1429 .set(Sha256::hash(&[&[11]]), Sha256::fill(11u8))
1430 .merkleize(&db, None, Location::new(0))
1431 .await
1432 .unwrap();
1433 let sibling_b = common_parent
1434 .new_batch::<Sha256>()
1435 .set(Sha256::hash(&[&[12]]), Sha256::fill(12u8))
1436 .merkleize(&db, None, Location::new(0))
1437 .await
1438 .unwrap();
1439 let db = db.apply_batch(sibling_a).await.unwrap();
1440 assert!(matches!(
1441 db.validate_batch(&sibling_b),
1442 Err(Error::StaleBatch)
1443 ));
1444
1445 let parent_a = db
1446 .new_batch()
1447 .set(Sha256::hash(&[&[1]]), Sha256::fill(1u8))
1448 .merkleize(&db, None, Location::new(0))
1449 .await
1450 .unwrap();
1451 let parent_b = db
1452 .new_batch()
1453 .set(Sha256::hash(&[&[2]]), Sha256::fill(2u8))
1454 .merkleize(&db, None, Location::new(0))
1455 .await
1456 .unwrap();
1457 let child_b = parent_b
1458 .new_batch::<Sha256>()
1459 .set(Sha256::hash(&[&[3]]), Sha256::fill(3u8))
1460 .merkleize(&db, None, Location::new(0))
1461 .await
1462 .unwrap();
1463
1464 let db = db.apply_batch(parent_a).await.unwrap();
1465 assert!(matches!(
1466 db.validate_batch(&child_b),
1467 Err(Error::StaleBatch)
1468 ));
1469 db.destroy().await.unwrap();
1470 });
1471 }
1472
1473 #[test_traced("INFO")]
1474 fn test_compact_stale_parent_after_child_applied() {
1475 deterministic::Runner::default().start(|context| async move {
1476 let db =
1477 open_db::<mmr::Family>(context.child("db"), "immutable-child-before-parent").await;
1478
1479 let parent = db
1480 .new_batch()
1481 .set(Sha256::hash(&[&[1]]), Sha256::fill(1u8))
1482 .merkleize(&db, None, Location::new(0))
1483 .await
1484 .unwrap();
1485 let child = parent
1486 .new_batch::<Sha256>()
1487 .set(Sha256::hash(&[&[2]]), Sha256::fill(2u8))
1488 .merkleize(&db, None, Location::new(0))
1489 .await
1490 .unwrap();
1491
1492 let db = db.apply_batch(child).await.unwrap();
1493 assert!(matches!(
1494 db.apply_batch(parent).await,
1495 Err(Error::StaleBatch)
1496 ));
1497 });
1498 }
1499
1500 #[test_traced("INFO")]
1501 fn test_compact_sequential_commit_parent_then_child() {
1502 deterministic::Runner::default().start(|context| async move {
1503 let db = open_db::<mmr::Family>(context.child("db"), "immutable-parent-child").await;
1504
1505 let parent = db
1506 .new_batch()
1507 .set(Sha256::hash(&[&[1]]), Sha256::fill(1u8))
1508 .merkleize(&db, None, Location::new(0))
1509 .await
1510 .unwrap();
1511 let child = parent
1512 .new_batch::<Sha256>()
1513 .set(Sha256::hash(&[&[2]]), Sha256::fill(2u8))
1514 .merkleize(&db, None, Location::new(0))
1515 .await
1516 .unwrap();
1517 let expected_root = child.root();
1518
1519 let db = db.apply_batch(parent).await.unwrap();
1520 let db = db.apply_batch(child).await.unwrap().sync().await.unwrap();
1521
1522 assert_eq!(db.root(), expected_root);
1523
1524 db.destroy().await.unwrap();
1525 });
1526 }
1527
1528 #[test_traced("INFO")]
1529 fn test_compact_floor_regressed() {
1530 deterministic::Runner::default().start(|context| async move {
1531 let db = open_db::<mmr::Family>(context.child("db"), "immutable-floor-regressed").await;
1532
1533 let advance_floor = db.new_batch().set(Sha256::hash(&[&[1]]), Sha256::fill(1u8));
1534 let advance_floor = advance_floor
1535 .merkleize(&db, None, Location::new(1))
1536 .await
1537 .unwrap();
1538 let db = db.apply_batch(advance_floor).await.unwrap();
1539 let db = db.sync().await.unwrap();
1540
1541 let regressed = db
1542 .new_batch()
1543 .set(Sha256::hash(&[&[2]]), Sha256::fill(2u8))
1544 .merkleize(&db, None, Location::new(0))
1545 .await;
1546
1547 assert!(matches!(
1548 regressed,
1549 Err(Error::FloorRegressed(new, current))
1550 if new == Location::new(0) && current == Location::new(1)
1551 ));
1552 });
1553 }
1554
1555 #[test_traced("INFO")]
1559 fn test_compact_chained_floor_regression() {
1560 deterministic::Runner::default().start(|context| async move {
1561 let db =
1562 open_db::<mmr::Family>(context.child("db"), "immutable-regressed-ancestor-floor")
1563 .await;
1564
1565 let parent = db
1566 .new_batch()
1567 .set(Sha256::hash(&[&[1]]), Sha256::fill(1u8))
1568 .merkleize(&db, None, Location::new(1))
1569 .await
1570 .unwrap();
1571 let child = parent
1572 .new_batch::<Sha256>()
1573 .set(Sha256::hash(&[&[2]]), Sha256::fill(2u8))
1574 .merkleize(&db, None, Location::new(0))
1575 .await;
1576
1577 assert!(matches!(
1578 child,
1579 Err(Error::FloorRegressed(new, prev))
1580 if new == Location::new(0) && prev == Location::new(1)
1581 ));
1582 });
1583 }
1584
1585 #[test_traced("INFO")]
1586 fn test_compact_bounded_initialization_restores_commit_metadata_and_floor() {
1587 deterministic::Runner::default().start(|context| async move {
1588 let db = open_db::<mmr::Family>(context.child("db"), "immutable-rewind-meta").await;
1589
1590 let k1 = Sha256::hash(&[&[1]]);
1591 let v1 = Sha256::fill(11u8);
1592 let meta1 = Sha256::fill(0xaa);
1593 let floor1 = Location::new(0);
1594 let batch = db
1595 .new_batch()
1596 .set(k1, v1)
1597 .merkleize(&db, Some(meta1), floor1)
1598 .await
1599 .unwrap();
1600 let db = db.apply_batch(batch).await.unwrap().sync().await.unwrap();
1601 let root_after_first = db.root();
1602 let size_after_first = db.size();
1603
1604 let k2 = Sha256::hash(&[&[2]]);
1605 let v2 = Sha256::fill(22u8);
1606 let meta2 = Sha256::fill(0xbb);
1607 let floor2 = Location::new(1);
1609 let batch = db
1610 .new_batch()
1611 .set(k2, v2)
1612 .merkleize(&db, Some(meta2), floor2)
1613 .await
1614 .unwrap();
1615 let db = db.apply_batch(batch).await.unwrap().sync().await.unwrap();
1616 assert_eq!(db.get_metadata(), Some(meta2));
1617 assert_eq!(db.inactivity_floor_loc(), floor2);
1618
1619 let db = {
1620 _ = db.sync().await.unwrap();
1621 open_bounded::<mmr::Family>(
1622 context.child("cap"),
1623 witness_config("immutable-rewind-meta"),
1624 size_after_first,
1625 )
1626 .await
1627 }
1628 .unwrap();
1629 assert_eq!(db.root(), root_after_first);
1630 assert_eq!(db.get_metadata(), Some(meta1));
1631 assert_eq!(db.inactivity_floor_loc(), floor1);
1632
1633 db.destroy().await.unwrap();
1634 });
1635 }
1636
1637 #[test_traced("INFO")]
1638 fn test_compact_bounded_initialization_persists_across_reopen() {
1639 deterministic::Runner::default().start(|context| async move {
1640 let partition = "immutable-rewind-reopen";
1641 let meta1 = Sha256::fill(0xaa);
1642 let floor1 = Location::new(0);
1643 let meta2 = Sha256::fill(0xbb);
1644 let floor2 = Location::new(1);
1645
1646 let root_after_first = {
1647 let db = open_db::<mmr::Family>(context.child("first"), partition).await;
1648 let batch = db
1649 .new_batch()
1650 .set(Sha256::hash(&[&[1]]), Sha256::fill(11u8))
1651 .merkleize(&db, Some(meta1), floor1)
1652 .await
1653 .unwrap();
1654 let db = db.apply_batch(batch).await.unwrap().sync().await.unwrap();
1655 let root = db.root();
1656 let size_after_first = db.size();
1657
1658 let batch = db
1659 .new_batch()
1660 .set(Sha256::hash(&[&[2]]), Sha256::fill(22u8))
1661 .merkleize(&db, Some(meta2), floor2)
1662 .await
1663 .unwrap();
1664 let db = db.apply_batch(batch).await.unwrap().sync().await.unwrap();
1665
1666 let _db = {
1667 _ = db.sync().await.unwrap();
1668 open_bounded::<mmr::Family>(
1669 context.child("cap"),
1670 witness_config(partition),
1671 size_after_first,
1672 )
1673 .await
1674 }
1675 .unwrap();
1676 root
1677 };
1678
1679 let db = open_db::<mmr::Family>(context.child("second"), partition).await;
1680 assert_eq!(db.root(), root_after_first);
1681 assert_eq!(db.get_metadata(), Some(meta1));
1682 assert_eq!(db.inactivity_floor_loc(), floor1);
1683
1684 db.destroy().await.unwrap();
1685 });
1686 }
1687
1688 #[test_traced("INFO")]
1689 fn test_compact_commit_persists_across_reopen() {
1690 deterministic::Runner::default().start(|context| async move {
1691 let partition = "immutable-commit-reopen";
1692 let meta1 = Sha256::fill(0xaa);
1693 let meta2 = Sha256::fill(0xbb);
1694
1695 let root_after_second = {
1696 let db = open_db::<mmr::Family>(context.child("first"), partition).await;
1697 let batch = db
1698 .new_batch()
1699 .set(Sha256::hash(&[&[1]]), Sha256::fill(11u8))
1700 .merkleize(&db, Some(meta1), Location::new(0))
1701 .await
1702 .unwrap();
1703 let db = db.apply_batch(batch).await.unwrap().commit().await.unwrap();
1704
1705 let batch = db
1706 .new_batch()
1707 .set(Sha256::hash(&[&[2]]), Sha256::fill(22u8))
1708 .merkleize(&db, Some(meta2), Location::new(1))
1709 .await
1710 .unwrap();
1711 let db = db.apply_batch(batch).await.unwrap().commit().await.unwrap();
1712 db.root()
1713 };
1714
1715 let db = open_db::<mmr::Family>(context.child("second"), partition).await;
1717 assert_eq!(db.root(), root_after_second);
1718 assert_eq!(db.get_metadata(), Some(meta2));
1719 assert_eq!(db.inactivity_floor_loc(), Location::new(1));
1720 db.destroy().await.unwrap();
1721 });
1722 }
1723
1724 #[test_traced("INFO")]
1725 fn test_compact_bounded_initialization_to_committed_entry_after_reopen() {
1726 deterministic::Runner::default().start(|context| async move {
1727 let partition = "immutable-commit-rewind-reopen";
1728 let meta1 = Sha256::fill(0xaa);
1729 let meta2 = Sha256::fill(0xbb);
1730
1731 let (root_a, size_a) = {
1732 let db = open_db::<mmr::Family>(context.child("first"), partition).await;
1733 let batch = db
1734 .new_batch()
1735 .set(Sha256::hash(&[&[1]]), Sha256::fill(11u8))
1736 .merkleize(&db, Some(meta1), Location::new(0))
1737 .await
1738 .unwrap();
1739 let db = db.apply_batch(batch).await.unwrap().commit().await.unwrap();
1740 let root_a = db.root();
1741 let size_a = db.size();
1742
1743 let batch = db
1744 .new_batch()
1745 .set(Sha256::hash(&[&[2]]), Sha256::fill(22u8))
1746 .merkleize(&db, Some(meta2), Location::new(1))
1747 .await
1748 .unwrap();
1749 let _db = db.apply_batch(batch).await.unwrap().commit().await.unwrap();
1750 (root_a, size_a)
1751 };
1752
1753 let db = open_db::<mmr::Family>(context.child("second"), partition).await;
1756 let db = {
1757 _ = db.sync().await.unwrap();
1758 open_bounded::<mmr::Family>(context.child("cap"), witness_config(partition), size_a)
1759 .await
1760 }
1761 .unwrap();
1762 assert_eq!(db.root(), root_a);
1763 assert_eq!(db.get_metadata(), Some(meta1));
1764 db.destroy().await.unwrap();
1765 });
1766 }
1767
1768 #[test_traced("INFO")]
1769 fn test_compact_sync_after_commit() {
1770 deterministic::Runner::default().start(|context| async move {
1771 let partition = "immutable-sync-after-commit";
1772 let meta = Sha256::fill(0xaa);
1773
1774 let root = {
1775 let db = open_db::<mmr::Family>(context.child("first"), partition).await;
1776 let batch = db
1777 .new_batch()
1778 .set(Sha256::hash(&[&[1]]), Sha256::fill(11u8))
1779 .merkleize(&db, Some(meta), Location::new(0))
1780 .await
1781 .unwrap();
1782 let db = db.apply_batch(batch).await.unwrap();
1783
1784 let db = db.commit().await.unwrap();
1786 let db = db.sync().await.unwrap();
1787 db.root()
1788 };
1789
1790 let metadata = Metadata::<_, u64, VecU64>::init(
1792 context.child("checkpoint"),
1793 MetadataConfig {
1794 partition: format!("{partition}-witness_offsets-metadata"),
1795 codec_config: (),
1796 },
1797 )
1798 .await
1799 .unwrap();
1800
1801 assert_eq!(metadata.get(&3).copied().map(u64::from), Some(2));
1803 drop(metadata);
1804
1805 let db = open_db::<mmr::Family>(context.child("second"), partition).await;
1806 assert_eq!(db.root(), root);
1807 assert_eq!(db.get_metadata(), Some(meta));
1808 db.destroy().await.unwrap();
1809 });
1810 }
1811
1812 #[test_traced("INFO")]
1813 fn test_compact_reopen_rejects_tampered_witness() {
1814 deterministic::Runner::default().start(|context| async move {
1815 let partition = "immutable-witness-tamper";
1816 let db = open_db::<mmr::Family>(context.child("db"), partition).await;
1817 let batch = db
1818 .new_batch()
1819 .set(Sha256::hash(&[&[7]]), Sha256::fill(7u8))
1820 .merkleize(&db, Some(Sha256::fill(0xaa)), Location::new(1))
1821 .await
1822 .unwrap();
1823 let db = db.apply_batch(batch).await.unwrap().sync().await.unwrap();
1824 drop(db);
1825
1826 let journal = open_witness_journal(context.child("tamper"), partition).await;
1828 let (op_bytes, size, mut pinned_nodes) = witness::tests::tip(&journal).await;
1829 pinned_nodes.push(Sha256::fill(0xff));
1830 witness::tests::overwrite_tip(journal, op_bytes, size, pinned_nodes).await;
1831
1832 let cfg = Config {
1833 strategy: Sequential,
1834 witness: witness_config(partition),
1835 commit_codec_config: (),
1836 };
1837 let reopened =
1838 TestDb::<mmr::Family>::init(context.child("reopen_witness"), cfg, None).await;
1839 assert!(matches!(reopened, Err(Error::DataCorrupted(_))));
1840 });
1841 }
1842
1843 #[test_traced("INFO")]
1844 fn test_compact_bounded_initialization_rejects_corrupt_target_entry() {
1845 deterministic::Runner::default().start(|context| async move {
1846 let partition = "immutable-corrupt-rewind-target";
1847 let db = open_db::<mmr::Family>(context.child("db"), partition).await;
1848 let batch = db
1849 .new_batch()
1850 .set(Sha256::hash(&[&[1]]), Sha256::fill(1u8))
1851 .merkleize(&db, None, Location::new(1))
1852 .await
1853 .unwrap();
1854 let db = db.apply_batch(batch).await.unwrap().sync().await.unwrap();
1855 let initialization_bound = db.target().size;
1856 let batch = db
1857 .new_batch()
1858 .set(Sha256::hash(&[&[2]]), Sha256::fill(2u8))
1859 .merkleize(&db, None, Location::new(1))
1860 .await
1861 .unwrap();
1862 let db = db.apply_batch(batch).await.unwrap().sync().await.unwrap();
1863 let tip_target = db.target();
1864 drop(db);
1865
1866 let mut journal = open_witness_journal(context.child("corrupt"), partition).await;
1868 journal = witness::tests::corrupt_entry(journal, 1, |entry| {
1869 entry.pinned_nodes.push(Sha256::fill(0xff));
1870 })
1871 .await;
1872 drop(journal);
1873
1874 let cfg = Config {
1876 strategy: Sequential,
1877 witness: witness_config(partition),
1878 commit_codec_config: (),
1879 };
1880 let reopened = TestDb::<mmr::Family>::init(context.child("reopen"), cfg, None)
1881 .await
1882 .unwrap();
1883 assert_eq!(reopened.target(), tip_target);
1884
1885 assert!(matches!(
1887 {
1888 drop(reopened);
1889 open_bounded::<mmr::Family>(
1890 context.child("cap"),
1891 witness_config(partition),
1892 initialization_bound,
1893 )
1894 .await
1895 },
1896 Err(Error::DataCorrupted(_))
1897 ));
1898
1899 let cfg = Config {
1901 strategy: Sequential,
1902 witness: witness_config(partition),
1903 commit_codec_config: (),
1904 };
1905 let reopened = TestDb::<mmr::Family>::init(context.child("reopen2"), cfg, None)
1906 .await
1907 .unwrap();
1908 assert_eq!(reopened.target(), tip_target);
1909 reopened.destroy().await.unwrap();
1910 });
1911 }
1912
1913 #[test_traced("INFO")]
1914 fn test_compact_reopen_rejects_interrupted_import() {
1915 deterministic::Runner::default().start(|context| async move {
1916 let partition = "immutable-interrupted-import";
1917 let db = open_db::<mmr::Family>(context.child("db"), partition).await;
1918 let batch = db
1919 .new_batch()
1920 .set(Sha256::hash(&[&[7]]), Sha256::fill(7u8))
1921 .merkleize(&db, Some(Sha256::fill(0xaa)), Location::new(1))
1922 .await
1923 .unwrap();
1924 let db = db.apply_batch(batch).await.unwrap().sync().await.unwrap();
1925 drop(db);
1926
1927 let journal = open_witness_journal(context.child("clear"), partition).await;
1930 let size = journal.size();
1931 let journal = journal.clear_to_size(size.max(1)).await.unwrap();
1932 drop(journal);
1933
1934 let cfg = Config {
1936 strategy: Sequential,
1937 witness: witness_config(partition),
1938 commit_codec_config: (),
1939 };
1940 let reopened =
1941 TestDb::<mmr::Family>::init(context.child("reopen_witness"), cfg, None).await;
1942 assert!(matches!(
1943 reopened,
1944 Err(Error::DataCorrupted("witness journal has no tip"))
1945 ));
1946 });
1947 }
1948
1949 #[test_traced("INFO")]
1950 fn test_compact_reopen_rejects_commit_floor_beyond_tip() {
1951 deterministic::Runner::default().start(|context| async move {
1952 let partition = "immutable-invalid-persisted-floor";
1953 let db = open_db::<mmr::Family>(context.child("db"), partition).await;
1954 let batch = db
1955 .new_batch()
1956 .set(Sha256::hash(&[&[7]]), Sha256::fill(7u8))
1957 .merkleize(&db, Some(Sha256::fill(0xaa)), Location::new(1))
1958 .await
1959 .unwrap();
1960 let db = db.apply_batch(batch).await.unwrap().sync().await.unwrap();
1961 drop(db);
1962 let oversized_floor = Location::new(10);
1963
1964 let journal = open_witness_journal(context.child("tamper"), partition).await;
1966 let (_, size, pinned_nodes) = witness::tests::tip(&journal).await;
1967 let bad_op = Operation::<mmr::Family, Digest, FixedEncoding<Digest>>::Commit(
1968 Some(Sha256::fill(0xaa)),
1969 oversized_floor,
1970 )
1971 .encode()
1972 .to_vec();
1973 witness::tests::overwrite_tip(journal, bad_op, size, pinned_nodes).await;
1974
1975 let cfg = Config {
1976 strategy: Sequential,
1977 witness: witness_config(partition),
1978 commit_codec_config: (),
1979 };
1980 let reopened =
1981 TestDb::<mmr::Family>::init(context.child("reopen_witness"), cfg, None).await;
1982 assert!(matches!(
1983 reopened,
1984 Err(Error::DataCorrupted("invalid compact witness"))
1985 ));
1986 });
1987 }
1988
1989 #[test_traced("INFO")]
1990 fn test_compact_reopen_rejects_tampered_pinned_nodes() {
1991 deterministic::Runner::default().start(|context| async move {
1992 let partition = "immutable-pinned-nodes-tamper";
1993 let db = open_db::<mmr::Family>(context.child("db"), partition).await;
1994 let batch = db
1995 .new_batch()
1996 .set(Sha256::hash(&[&[7]]), Sha256::fill(7u8))
1997 .merkleize(&db, Some(Sha256::fill(0xaa)), Location::new(1))
1998 .await
1999 .unwrap();
2000 let db = db.apply_batch(batch).await.unwrap().sync().await.unwrap();
2001 let tampered_target = db.target();
2002 drop(db);
2003
2004 let journal = open_witness_journal(context.child("tamper"), partition).await;
2008 let (op_bytes, size, mut pinned_nodes) = witness::tests::tip(&journal).await;
2009 pinned_nodes[0] = Sha256::fill(0xff);
2010 witness::tests::overwrite_tip(journal, op_bytes, size, pinned_nodes).await;
2011
2012 let cfg = Config {
2013 strategy: Sequential,
2014 witness: witness_config(partition),
2015 commit_codec_config: (),
2016 };
2017 let reopened = TestDb::<mmr::Family>::init(context.child("reopen_witness"), cfg, None)
2018 .await
2019 .unwrap();
2020 assert_ne!(reopened.target(), tampered_target);
2021 reopened.destroy().await.unwrap();
2022 });
2023 }
2024
2025 #[test_traced("INFO")]
2028 fn test_compact_reopen_drops_unsynced_witness() {
2029 deterministic::Runner::default().start(|context| async move {
2030 let partition = "immutable-witness-unsynced";
2031 let db = open_db::<mmr::Family>(context.child("db"), partition).await;
2032
2033 let batch = db
2035 .new_batch()
2036 .set(Sha256::hash(&[&[1]]), Sha256::fill(1u8))
2037 .merkleize(&db, Some(Sha256::fill(0xa1)), Location::new(1))
2038 .await
2039 .unwrap();
2040 let db = db.apply_batch(batch).await.unwrap().sync().await.unwrap();
2041 let target_a = db.target();
2042 drop(db);
2043
2044 let journal = open_witness_journal(context.child("crash"), partition).await;
2047 let (op_bytes, mut size, pinned_nodes) = witness::tests::tip(&journal).await;
2048 size += 2;
2049 witness::tests::append_unsynced(journal, op_bytes, size, pinned_nodes).await;
2050
2051 let reopened = open_db::<mmr::Family>(context.child("reopen"), partition).await;
2053 assert_eq!(reopened.target(), target_a);
2054 reopened.destroy().await.unwrap();
2055 });
2056 }
2057
2058 #[test_traced("INFO")]
2059 fn test_compact_initialization_zero_and_above_end() {
2060 deterministic::Runner::default().start(|context| async move {
2061 let cfg = witness_config("immutable-caps");
2062 assert!(matches!(
2063 open_bounded::<mmr::Family>(context.child("zero"), cfg.clone(), Location::new(0))
2064 .await,
2065 Err(Error::InvalidInitializationBound)
2066 ));
2067 let db = open_bounded::<mmr::Family>(
2068 context.child("fresh"),
2069 cfg.clone(),
2070 Location::new(100),
2071 )
2072 .await
2073 .unwrap();
2074 let root = db.root();
2075 assert_eq!(db.size(), Location::new(1));
2076 drop(db);
2077 let db = open_bounded::<mmr::Family>(context.child("above"), cfg, Location::new(100))
2078 .await
2079 .unwrap();
2080 assert_eq!(db.root(), root);
2081 assert_eq!(db.size(), Location::new(1));
2082 });
2083 }
2084
2085 #[test_traced("INFO")]
2086 fn test_compact_bounded_initialization_between_commits() {
2087 deterministic::Runner::default().start(|context| async move {
2088 let db = open_db::<mmr::Family>(context.child("db"), "immutable-rewind-between").await;
2089 let initial_root = db.root();
2090
2091 let batch = db
2093 .new_batch()
2094 .set(Sha256::hash(&[&[1]]), Sha256::fill(1u8))
2095 .set(Sha256::hash(&[&[2]]), Sha256::fill(2u8))
2096 .merkleize(&db, Some(Sha256::fill(0xa1)), Location::new(0))
2097 .await
2098 .unwrap();
2099 let db = db.apply_batch(batch).await.unwrap().sync().await.unwrap();
2100 let root_a = db.root();
2101 let size_a = db.size();
2102 assert_eq!(size_a, Location::new(4));
2103
2104 let batch = db
2106 .new_batch()
2107 .set(Sha256::hash(&[&[3]]), Sha256::fill(3u8))
2108 .merkleize(&db, Some(Sha256::fill(0xb1)), Location::new(0))
2109 .await
2110 .unwrap();
2111 let db = db.apply_batch(batch).await.unwrap().sync().await.unwrap();
2112 let cfg = witness_config("immutable-rewind-between");
2113 drop(db);
2114 for (target, size, root) in [(5, 4, root_a), (3, 1, initial_root), (2, 1, initial_root)]
2115 {
2116 let db = open_bounded::<mmr::Family>(
2117 context.child("cap"),
2118 cfg.clone(),
2119 Location::new(target),
2120 )
2121 .await
2122 .unwrap();
2123 assert_eq!(db.size(), Location::new(size));
2124 assert_eq!(db.root(), root);
2125 drop(db);
2126 let db =
2127 open_db::<mmr::Family>(context.child("restart"), "immutable-rewind-between")
2128 .await;
2129 assert_eq!(db.root(), root);
2130 }
2131 });
2132 }
2133
2134 #[test_traced("INFO")]
2135 fn test_compact_bounded_initialization_multiple_commits() {
2136 deterministic::Runner::default().start(|context| async move {
2137 let partition = "immutable-rewind-multi";
2138 let db = open_db::<mmr::Family>(context.child("db"), partition).await;
2139
2140 let batch = db
2142 .new_batch()
2143 .set(Sha256::hash(&[&[1]]), Sha256::fill(1u8))
2144 .merkleize(&db, Some(Sha256::fill(0xa1)), Location::new(0))
2145 .await
2146 .unwrap();
2147 let db = db.apply_batch(batch).await.unwrap().sync().await.unwrap();
2148 let root_a = db.root();
2149 let size_a = db.size();
2150 let target_a = db.target();
2151
2152 let mut db = db;
2153 for i in [2u8, 3] {
2154 let batch = db
2155 .new_batch()
2156 .set(Sha256::hash(&[&[i]]), Sha256::fill(i))
2157 .merkleize(&db, Some(Sha256::fill(i)), Location::new(0))
2158 .await
2159 .unwrap();
2160 db = db.apply_batch(batch).await.unwrap().sync().await.unwrap();
2161 }
2162 assert_ne!(db.root(), root_a);
2163
2164 let db = {
2166 _ = db.sync().await.unwrap();
2167 open_bounded::<mmr::Family>(context.child("cap"), witness_config(partition), size_a)
2168 .await
2169 }
2170 .unwrap();
2171 assert_eq!(db.root(), root_a);
2172 assert_eq!(db.size(), size_a);
2173 assert_eq!(db.get_metadata(), Some(Sha256::fill(0xa1)));
2174 assert_eq!(db.target(), target_a);
2175 drop(db);
2176
2177 let db = open_db::<mmr::Family>(context.child("reopen"), partition).await;
2179 assert_eq!(db.root(), root_a);
2180 assert_eq!(db.target(), target_a);
2181 db.destroy().await.unwrap();
2182 });
2183 }
2184
2185 #[test_traced("INFO")]
2186 fn test_compact_bounded_initialization_preserves_current_state() {
2187 deterministic::Runner::default().start(|context| async move {
2188 let db = open_db::<mmr::Family>(context.child("db"), "immutable-rewind-noop").await;
2189 let batch = db
2190 .new_batch()
2191 .set(Sha256::hash(&[&[1]]), Sha256::fill(1u8))
2192 .merkleize(&db, Some(Sha256::fill(0xa1)), Location::new(0))
2193 .await
2194 .unwrap();
2195 let db = db.apply_batch(batch).await.unwrap().sync().await.unwrap();
2196 let root = db.root();
2197 let size = db.size();
2198
2199 let db = {
2200 _ = db.sync().await.unwrap();
2201 open_bounded::<mmr::Family>(
2202 context.child("cap"),
2203 witness_config("immutable-rewind-noop"),
2204 size,
2205 )
2206 .await
2207 }
2208 .unwrap();
2209 assert_eq!(db.root(), root);
2210 assert_eq!(db.size(), size);
2211 db.destroy().await.unwrap();
2212 });
2213 }
2214
2215 #[test_traced("INFO")]
2216 fn test_compact_prune_then_bounded_initialization() {
2217 deterministic::Runner::default().start(|context| async move {
2218 let mut witness_cfg = witness_config("immutable-prune-rewind");
2221 witness_cfg.items_per_section = NZU64!(1);
2222 let cfg = Config {
2223 strategy: Sequential,
2224 witness: witness_cfg.clone(),
2225 commit_codec_config: (),
2226 };
2227 let mut db: TestDb<mmr::Family> =
2228 Db::init(context.child("db"), cfg, None).await.unwrap();
2229
2230 let mut sizes = Vec::new();
2232 for i in [1u8, 2, 3] {
2233 let batch = db
2234 .new_batch()
2235 .set(Sha256::hash(&[&[i]]), Sha256::fill(i))
2236 .merkleize(&db, Some(Sha256::fill(i)), Location::new(0))
2237 .await
2238 .unwrap();
2239 db = db.apply_batch(batch).await.unwrap().sync().await.unwrap();
2240 sizes.push(db.size());
2241 }
2242
2243 let db = db.prune(sizes[1]).await.unwrap().sync().await.unwrap();
2245 drop(db);
2246 let db = open_bounded::<mmr::Family>(
2247 context.child("retained"),
2248 witness_cfg.clone(),
2249 sizes[1],
2250 )
2251 .await
2252 .unwrap();
2253 assert_eq!(db.size(), sizes[1]);
2254 assert_eq!(db.get_metadata(), Some(Sha256::fill(2)));
2255 drop(db);
2256
2257 assert!(matches!(
2258 open_bounded::<mmr::Family>(context.child("pruned"), witness_cfg, sizes[0]).await,
2259 Err(Error::HistoricalFloorPruned(_))
2260 ));
2261 });
2262 }
2263
2264 fn sectioned_witness_config(partition: &str) -> JournalConfig<()> {
2266 let mut cfg = witness_config(partition);
2267 cfg.items_per_section = NZU64!(1);
2268 cfg
2269 }
2270
2271 async fn seed_witness_sections(
2275 context: deterministic::Context,
2276 witness_cfg: JournalConfig<()>,
2277 commits: u8,
2278 ) -> Vec<(Location<mmr::Family>, Digest)> {
2279 let cfg = Config {
2280 strategy: Sequential,
2281 witness: witness_cfg,
2282 commit_codec_config: (),
2283 };
2284 let mut db: TestDb<mmr::Family> = Db::init(context, cfg, None).await.unwrap();
2285 let mut states = Vec::new();
2286 for i in 1..=commits {
2287 let batch = db
2288 .new_batch()
2289 .merkleize(&db, Some(Sha256::fill(i)), db.inactivity_floor_loc())
2290 .await
2291 .unwrap();
2292 db = db.apply_batch(batch).await.unwrap().sync().await.unwrap();
2293 states.push((db.size(), db.root()));
2294 }
2295 states
2296 }
2297
2298 async fn tear_witness_data(
2301 context: &deterministic::Context,
2302 witness_cfg: &JournalConfig<()>,
2303 blob: u64,
2304 ) {
2305 let partition = format!("{}_data", witness_cfg.partition);
2308 let (handle, len) = context.open(&partition, &blob.to_be_bytes()).await.unwrap();
2309 handle.resize(len - 1).await.unwrap();
2310 handle.sync().await.unwrap();
2311 }
2312
2313 async fn open_bounded_counting(
2316 context: deterministic::Context,
2317 witness_cfg: JournalConfig<()>,
2318 cap: Location<mmr::Family>,
2319 ) -> (DelayedDb, usize) {
2320 let pending = PendingSyncs::default();
2324 pending.arm();
2325 let delayed = DelayedSyncContext {
2326 inner: context,
2327 pending: pending.clone(),
2328 };
2329 let cfg = Config {
2330 strategy: Sequential,
2331 witness: witness_cfg,
2332 commit_codec_config: (),
2333 };
2334 let db = drive_pending_syncs(&pending, DelayedDb::init(delayed, cfg, Some(cap)))
2335 .await
2336 .unwrap();
2337 (db, pending.calls())
2338 }
2339
2340 #[test_traced]
2343 fn test_compact_bounded_initialization_ignores_discarded_witness_sections() {
2344 deterministic::Runner::default().start(|context| async move {
2345 let control_cfg = sectioned_witness_config("immutable-skip-discarded-control");
2350 let torn_cfg = sectioned_witness_config("immutable-skip-discarded-torn");
2351 let mut states = Vec::new();
2352 for (label, witness_cfg) in [("control", &control_cfg), ("torn", &torn_cfg)] {
2353 let context = context.child(label);
2354 states.push(
2355 seed_witness_sections(context.child("seed"), witness_cfg.clone(), 1).await[0],
2356 );
2357
2358 let cfg = Config {
2361 strategy: Sequential,
2362 witness: witness_cfg.clone(),
2363 commit_codec_config: (),
2364 };
2365 let db: TestDb<mmr::Family> =
2366 Db::init(context.child("extend"), cfg, None).await.unwrap();
2367 let batch = db
2368 .new_batch()
2369 .merkleize(&db, Some(Sha256::fill(2)), db.inactivity_floor_loc())
2370 .await
2371 .unwrap();
2372 let db = db.apply_batch(batch).await.unwrap();
2373 drop(db.commit().await.unwrap());
2374 }
2375
2376 assert_eq!(states[0], states[1]);
2379 let (size, root) = states[0];
2380
2381 tear_witness_data(&context, &torn_cfg, 2).await;
2384
2385 let (control, control_calls) =
2388 open_bounded_counting(context.child("control"), control_cfg, size).await;
2389 let (torn, torn_calls) =
2390 open_bounded_counting(context.child("torn"), torn_cfg, size).await;
2391 assert_eq!(control.size(), size);
2392 assert_eq!(control.root(), root);
2393 assert_eq!(torn.size(), size);
2394 assert_eq!(torn.root(), root);
2395 assert_eq!(
2396 torn_calls, control_calls,
2397 "the discarded torn section must not be repaired"
2398 );
2399 control.destroy().await.unwrap();
2400 torn.destroy().await.unwrap();
2401 });
2402 }
2403
2404 #[test_traced]
2407 fn test_compact_bounded_initialization_repairs_retained_witness_section() {
2408 deterministic::Runner::default().start(|context| async move {
2409 let clean_cfg = witness_config("immutable-repair-retained-clean");
2412 let torn_cfg = witness_config("immutable-repair-retained-torn");
2413 let mut states = Vec::new();
2414 for (label, witness_cfg) in [("clean", &clean_cfg), ("torn", &torn_cfg)] {
2415 let cfg = Config {
2418 strategy: Sequential,
2419 witness: witness_cfg.clone(),
2420 commit_codec_config: (),
2421 };
2422 let db: TestDb<mmr::Family> =
2423 Db::init(context.child(label), cfg, None).await.unwrap();
2424 let genesis = db.root();
2425
2426 let batch = db
2429 .new_batch()
2430 .merkleize(&db, Some(Sha256::fill(1)), db.inactivity_floor_loc())
2431 .await
2432 .unwrap();
2433 let db = db.apply_batch(batch).await.unwrap().commit().await.unwrap();
2434 states.push((genesis, db.size(), db.root()));
2435 }
2436 assert_eq!(states[0], states[1]);
2437 let (genesis, size, root) = states[0];
2438
2439 tear_witness_data(&context, &torn_cfg, 0).await;
2442
2443 let (clean, clean_calls) =
2446 open_bounded_counting(context.child("clean"), clean_cfg, size).await;
2447 let (torn, torn_calls) =
2448 open_bounded_counting(context.child("torn"), torn_cfg, size).await;
2449
2450 assert_eq!(clean.size(), size);
2453 assert_eq!(clean.root(), root);
2454 assert_eq!(torn.size(), Location::new(1));
2455 assert_eq!(torn.root(), genesis);
2456 assert!(
2457 torn_calls > clean_calls,
2458 "the retained torn section is repaired"
2459 );
2460 clean.destroy().await.unwrap();
2461 torn.destroy().await.unwrap();
2462 });
2463 }
2464
2465 #[test_traced("INFO")]
2469 fn test_compact_bounded_initialization_after_import_below_positions() {
2470 deterministic::Runner::default().start(|context| async move {
2471 let src_cfg = sectioned_witness_config("immutable-import-below-src");
2475 let (src_size, src_root) =
2476 seed_witness_sections(context.child("src"), src_cfg.clone(), 1).await[0];
2477 let journal =
2478 witness::Journal::<_, mmr::Family, Digest>::init(context.child("tip"), src_cfg)
2479 .await
2480 .unwrap();
2481 let (_, _, src_pinned) = witness::tests::tip(&journal).await;
2482 drop(journal);
2483
2484 let dst_cfg = sectioned_witness_config("immutable-import-below-dst");
2486 seed_witness_sections(context.child("dst"), dst_cfg.clone(), 3).await;
2487
2488 let journal = witness::Journal::init(context.child("import"), dst_cfg.clone())
2492 .await
2493 .unwrap();
2494 let imported = TestDb::<mmr::Family>::init_from_sync(
2495 Sequential,
2496 journal,
2497 (),
2498 src_size - 1,
2499 src_pinned,
2500 Operation::Commit(Some(Sha256::fill(1)), Location::new(0)),
2501 )
2502 .unwrap();
2503 assert_eq!(imported.root(), src_root);
2504
2505 drop(imported.commit().await.unwrap());
2508 let journal = witness::Journal::<_, mmr::Family, Digest>::init(
2509 context.child("placed"),
2510 dst_cfg.clone(),
2511 )
2512 .await
2513 .unwrap();
2514 assert_eq!(journal.bounds(), 4..5);
2515 drop(journal);
2516
2517 let cfg = Config {
2520 strategy: Sequential,
2521 witness: dst_cfg.clone(),
2522 commit_codec_config: (),
2523 };
2524 let mut db: TestDb<mmr::Family> =
2525 Db::init(context.child("reopen"), cfg, None).await.unwrap();
2526 assert_eq!(db.size(), src_size);
2527 let mut states = vec![(src_size, src_root)];
2528 for i in 2..=4u8 {
2529 let batch = db
2530 .new_batch()
2531 .merkleize(&db, Some(Sha256::fill(i)), db.inactivity_floor_loc())
2532 .await
2533 .unwrap();
2534 db = db.apply_batch(batch).await.unwrap().sync().await.unwrap();
2535 states.push((db.size(), db.root()));
2536 }
2537 drop(db);
2538
2539 for (size, root) in states.into_iter().rev() {
2546 let db = open_bounded::<mmr::Family>(
2547 context.child("bounded").with_attribute("cap", *size),
2548 dst_cfg.clone(),
2549 size,
2550 )
2551 .await
2552 .unwrap();
2553 assert_eq!(db.size(), size);
2554 assert_eq!(db.root(), root);
2555 }
2556
2557 assert!(matches!(
2559 open_bounded::<mmr::Family>(context.child("pruned"), dst_cfg, Location::new(1))
2560 .await,
2561 Err(Error::HistoricalFloorPruned(pruned)) if pruned == Location::new(1)
2562 ));
2563 });
2564 }
2565
2566 #[test_traced]
2569 fn test_compact_failed_bounded_selection_preserves_acknowledged_offsets() {
2570 deterministic::Runner::default().start(|context| async move {
2571 let partition = "immutable-failed-bounded-offsets";
2572 let cap = Location::new(5);
2573 let witness_cfg = sectioned_witness_config(partition);
2574
2575 let offsets_partition = format!("{partition}-witness_offsets");
2578 let cfg = Config {
2579 strategy: Sequential,
2580 witness: witness_cfg.clone(),
2581 commit_codec_config: (),
2582 };
2583 let mut db: TestDb<mmr::Family> =
2584 Db::init(context.child("seed"), cfg, None).await.unwrap();
2585
2586 let mut batch = db.new_batch();
2589 for key in 1..=6u8 {
2590 batch = batch.set(Sha256::fill(key), Sha256::fill(key));
2591 }
2592 let batch = batch.merkleize(&db, None, Location::new(0)).await.unwrap();
2593 db = db.apply_batch(batch).await.unwrap().sync().await.unwrap();
2594 let first_retained_size = db.size();
2595 assert!(first_retained_size > cap);
2596
2597 for _ in 0..8 {
2601 let batch = db
2602 .new_batch()
2603 .merkleize(&db, None, db.inactivity_floor_loc())
2604 .await
2605 .unwrap();
2606 db = db.apply_batch(batch).await.unwrap().sync().await.unwrap();
2607 }
2608 let tip = db.size();
2609
2610 let db = db
2614 .prune(first_retained_size)
2615 .await
2616 .unwrap()
2617 .sync()
2618 .await
2619 .unwrap();
2620 drop(db);
2621
2622 let watermark = fixed::Journal::<_, u64>::persisted_watermark(
2625 context.child("before"),
2626 &offsets_partition,
2627 )
2628 .await
2629 .unwrap();
2630 assert_eq!(watermark, Some(10));
2631
2632 assert!(matches!(
2638 open_bounded::<mmr::Family>(context.child("failed"), witness_cfg.clone(), cap)
2639 .await,
2640 Err(Error::HistoricalFloorPruned(found)) if found == cap
2641 ));
2642
2643 let watermark = fixed::Journal::<_, u64>::persisted_watermark(
2646 context.child("after"),
2647 &offsets_partition,
2648 )
2649 .await
2650 .unwrap();
2651 assert_eq!(watermark, Some(10));
2652
2653 let reopened = open_bounded::<mmr::Family>(context.child("retry"), witness_cfg, tip)
2655 .await
2656 .unwrap();
2657 assert_eq!(reopened.size(), tip);
2658 reopened.destroy().await.unwrap();
2659 });
2660 }
2661
2662 #[test_traced("INFO")]
2663 fn test_compact_prune_past_tip_keeps_tip() {
2664 deterministic::Runner::default().start(|context| async move {
2665 let partition = "immutable-prune-past-tip";
2666 let db = open_db::<mmr::Family>(context.child("db"), partition).await;
2667 let batch = db
2668 .new_batch()
2669 .set(Sha256::hash(&[&[1]]), Sha256::fill(1u8))
2670 .merkleize(&db, Some(Sha256::fill(0xa1)), Location::new(0))
2671 .await
2672 .unwrap();
2673 let db = db.apply_batch(batch).await.unwrap().sync().await.unwrap();
2674 let target = db.target();
2675
2676 let boundary = db.size() + 100;
2678 let db = db.prune(boundary).await.unwrap();
2679 assert_eq!(db.target(), target);
2680 drop(db);
2681
2682 let reopened = open_db::<mmr::Family>(context.child("reopen"), partition).await;
2683 assert_eq!(reopened.target(), target);
2684 reopened.destroy().await.unwrap();
2685 });
2686 }
2687
2688 #[test_traced("INFO")]
2689 fn test_compact_bounded_initialization_preserves_pre_advance_batch() {
2690 deterministic::Runner::default().start(|context| async move {
2691 let db = open_db::<mmr::Family>(
2692 context.child("db"),
2693 "immutable-rewind-preserves-pre-advance",
2694 )
2695 .await;
2696
2697 let batch = db
2698 .new_batch()
2699 .set(Sha256::hash(&[&[1]]), Sha256::fill(1u8))
2700 .merkleize(&db, None, Location::new(0))
2701 .await
2702 .unwrap();
2703 let db = db.apply_batch(batch).await.unwrap().sync().await.unwrap();
2704 let size_after_first = db.size();
2705
2706 let held = db
2708 .new_batch()
2709 .set(Sha256::hash(&[&[2]]), Sha256::fill(2u8))
2710 .merkleize(&db, None, Location::new(0))
2711 .await
2712 .unwrap();
2713
2714 let batch = db
2716 .new_batch()
2717 .set(Sha256::hash(&[&[3]]), Sha256::fill(3u8))
2718 .merkleize(&db, None, Location::new(0))
2719 .await
2720 .unwrap();
2721 let db = db.apply_batch(batch).await.unwrap().sync().await.unwrap();
2722 let db = {
2723 _ = db.sync().await.unwrap();
2724 open_bounded::<mmr::Family>(
2725 context.child("cap"),
2726 witness_config("immutable-rewind-preserves-pre-advance"),
2727 size_after_first,
2728 )
2729 .await
2730 }
2731 .unwrap();
2732
2733 let db = db.apply_batch(held).await.unwrap();
2736
2737 db.destroy().await.unwrap();
2738 });
2739 }
2740
2741 #[test_traced("INFO")]
2742 fn test_compact_noop_commit_after_commit() {
2743 deterministic::Runner::default().start(|context| async move {
2744 let db =
2745 open_db::<mmr::Family>(context.child("db"), "immutable-noop-after-commit").await;
2746
2747 let k1 = Sha256::hash(&[&[1]]);
2748 let v1 = Sha256::fill(11u8);
2749 let k2 = Sha256::hash(&[&[2]]);
2750 let v2 = Sha256::fill(22u8);
2751 let batch = db
2752 .new_batch()
2753 .set(k1, v1)
2754 .set(k2, v2)
2755 .merkleize(&db, Some(Sha256::fill(0xaa)), Location::new(0))
2756 .await
2757 .unwrap();
2758 let db = db.apply_batch(batch).await.unwrap().sync().await.unwrap();
2759 let root_after_first = db.root();
2760 let size_after_first = db.size();
2761
2762 let db = db.sync().await.unwrap();
2763 assert_eq!(db.size(), size_after_first);
2764 assert_eq!(db.root(), root_after_first);
2765
2766 db.destroy().await.unwrap();
2767 });
2768 }
2769
2770 #[test_traced("INFO")]
2771 fn test_compact_noop_commit_after_reopen() {
2772 deterministic::Runner::default().start(|context| async move {
2773 let partition = "immutable-noop-after-reopen";
2774
2775 let (root_before_drop, size_before_drop) = {
2776 let db = open_db::<mmr::Family>(context.child("first"), partition).await;
2777 let k1 = Sha256::hash(&[&[1]]);
2778 let v1 = Sha256::fill(11u8);
2779 let k2 = Sha256::hash(&[&[2]]);
2780 let v2 = Sha256::fill(22u8);
2781 let batch = db
2782 .new_batch()
2783 .set(k1, v1)
2784 .set(k2, v2)
2785 .merkleize(&db, Some(Sha256::fill(0xaa)), Location::new(0))
2786 .await
2787 .unwrap();
2788 let db = db.apply_batch(batch).await.unwrap().sync().await.unwrap();
2789 (db.root(), db.size())
2790 };
2791
2792 let db = open_db::<mmr::Family>(context.child("second"), partition).await;
2793 assert_eq!(db.root(), root_before_drop);
2794 assert_eq!(db.size(), size_before_drop);
2795
2796 let db = db.sync().await.unwrap();
2797 assert_eq!(db.size(), size_before_drop);
2798 assert_eq!(db.root(), root_before_drop);
2799
2800 db.destroy().await.unwrap();
2801 });
2802 }
2803
2804 #[test_traced("INFO")]
2805 fn test_compact_noop_commit_after_bounded_initialization() {
2806 deterministic::Runner::default().start(|context| async move {
2807 let db =
2808 open_db::<mmr::Family>(context.child("db"), "immutable-noop-after-rewind").await;
2809
2810 let k1 = Sha256::hash(&[&[1]]);
2811 let v1 = Sha256::fill(11u8);
2812 let k2 = Sha256::hash(&[&[2]]);
2813 let v2 = Sha256::fill(22u8);
2814 let batch = db
2815 .new_batch()
2816 .set(k1, v1)
2817 .set(k2, v2)
2818 .merkleize(&db, Some(Sha256::fill(0xaa)), Location::new(0))
2819 .await
2820 .unwrap();
2821 let db = db.apply_batch(batch).await.unwrap().sync().await.unwrap();
2822 let root_after_first = db.root();
2823 let size_after_first = db.size();
2824
2825 let k3 = Sha256::hash(&[&[3]]);
2826 let v3 = Sha256::fill(33u8);
2827 let batch = db
2828 .new_batch()
2829 .set(k3, v3)
2830 .merkleize(&db, Some(Sha256::fill(0xbb)), Location::new(1))
2831 .await
2832 .unwrap();
2833 let db = db.apply_batch(batch).await.unwrap().sync().await.unwrap();
2834
2835 let db = {
2836 _ = db.sync().await.unwrap();
2837 open_bounded::<mmr::Family>(
2838 context.child("cap"),
2839 witness_config("immutable-noop-after-rewind"),
2840 size_after_first,
2841 )
2842 .await
2843 }
2844 .unwrap();
2845 assert_eq!(db.size(), size_after_first);
2846 assert_eq!(db.root(), root_after_first);
2847
2848 let db = db.sync().await.unwrap();
2849 assert_eq!(db.size(), size_after_first);
2850 assert_eq!(db.root(), root_after_first);
2851
2852 db.destroy().await.unwrap();
2853 });
2854 }
2855
2856 #[test_traced("INFO")]
2857 fn test_compact_bounded_initialization_makes_post_advance_batch_stale() {
2858 deterministic::Runner::default().start(|context| async move {
2859 let db =
2860 open_db::<mmr::Family>(context.child("db"), "immutable-rewind-makes-stale").await;
2861
2862 let batch = db
2863 .new_batch()
2864 .set(Sha256::hash(&[&[1]]), Sha256::fill(1u8))
2865 .merkleize(&db, None, Location::new(0))
2866 .await
2867 .unwrap();
2868 let db = db.apply_batch(batch).await.unwrap().sync().await.unwrap();
2869 let size_after_first = db.size();
2870
2871 let batch = db
2872 .new_batch()
2873 .set(Sha256::hash(&[&[2]]), Sha256::fill(2u8))
2874 .merkleize(&db, None, Location::new(0))
2875 .await
2876 .unwrap();
2877 let db = db.apply_batch(batch).await.unwrap().sync().await.unwrap();
2878
2879 let held = db
2881 .new_batch()
2882 .set(Sha256::hash(&[&[3]]), Sha256::fill(3u8))
2883 .merkleize(&db, None, Location::new(0))
2884 .await
2885 .unwrap();
2886
2887 let db = {
2888 _ = db.sync().await.unwrap();
2889 open_bounded::<mmr::Family>(
2890 context.child("cap"),
2891 witness_config("immutable-rewind-makes-stale"),
2892 size_after_first,
2893 )
2894 .await
2895 }
2896 .unwrap();
2897
2898 assert!(matches!(db.apply_batch(held).await, Err(Error::StaleBatch)));
2901 });
2902 }
2903
2904 #[test_traced("INFO")]
2905 fn test_compact_floor_beyond_size() {
2906 deterministic::Runner::default().start(|context| async move {
2907 let db = open_db::<mmr::Family>(context.child("db"), "immutable-floor-beyond").await;
2908
2909 let batch = db.new_batch().merkleize(&db, None, Location::new(2)).await;
2910
2911 assert!(matches!(
2912 batch,
2913 Err(Error::FloorBeyondSize(floor, tip))
2914 if floor == Location::new(2) && tip == Location::new(1)
2915 ));
2916 });
2917 }
2918
2919 #[test_traced("INFO")]
2922 fn test_compact_chained_floor_beyond_commit() {
2923 deterministic::Runner::default().start(|context| async move {
2924 let db = open_db::<mmr::Family>(context.child("db"), "immutable-ancestor-floor-beyond")
2925 .await;
2926
2927 let parent = db
2929 .new_batch()
2930 .set(Sha256::hash(&[&[1]]), Sha256::fill(1u8))
2931 .merkleize(&db, None, Location::new(2))
2932 .await
2933 .unwrap();
2934
2935 let child = parent
2937 .new_batch::<Sha256>()
2938 .set(Sha256::hash(&[&[2]]), Sha256::fill(2u8))
2939 .merkleize(&db, None, Location::new(5))
2940 .await;
2941
2942 assert!(matches!(
2943 child,
2944 Err(Error::FloorBeyondSize(floor, commit))
2945 if floor == Location::new(5) && commit == Location::new(4)
2946 ));
2947 });
2948 }
2949}