Skip to main content

commonware_storage/qmdb/keyless/
compact.rs

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