Skip to main content

commonware_storage/qmdb/keyless/
mod.rs

1//! The [Keyless] qmdb allows for append-only storage of data that can later be retrieved by its
2//! location. Both fixed-size and variable-size values are supported via the [fixed] and [variable]
3//! submodules.
4//!
5//! # Examples
6//!
7//! ```ignore
8//! // Simple mode: apply a batch, then durably commit it.
9//! let batch = db.new_batch().append(value);
10//! let merkleized = batch.merkleize(&db, None, db.inactivity_floor_loc()).await?;
11//! let db = db.apply_batch(merkleized).await?.commit().await?;
12//! ```
13//!
14//! ```ignore
15//! // Several batches can be built on one parent before any is applied.
16//! let floor = db.inactivity_floor_loc();
17//! let parent = db.new_batch().append(value_a);
18//! let parent = parent.merkleize(&db, None, floor).await?;
19//!
20//! let child_a = parent.new_batch();
21//! let child_a = child_a.append(value_b);
22//! let child_a = child_a.merkleize(&db, None, floor).await?;
23//!
24//! let child_b = parent.new_batch();
25//! let child_b = child_b.append(value_c);
26//! let child_b = child_b.merkleize(&db, None, floor).await?;
27//!
28//! let db = db.apply_batch(child_a).await?.commit().await?;
29//! ```
30//!
31//! ```ignore
32//! // Sequential commit: apply parent then child.
33//! let floor = db.inactivity_floor_loc();
34//! let parent = db.new_batch().append(value_a);
35//! let parent_m = parent.merkleize(&db, None, floor).await?;
36//! let child = parent_m.new_batch().append(value_b);
37//! let child_m = child.merkleize(&db, None, floor).await?;
38//!
39//! let db = db.apply_batch(parent_m).await?;
40//! let db = db.apply_batch(child_m).await?.commit().await?;
41//! ```
42
43use crate::{
44    Context,
45    journal::{
46        authenticated,
47        contiguous::{Contiguous, Mutable},
48    },
49    merkle::{Family, Location, Proof, full::Config as MerkleConfig},
50    qmdb::{
51        Error, any::value::ValueEncoding, chain, metrics::Metrics, single_operation_root,
52        sync::source,
53    },
54};
55use commonware_codec::EncodeShared;
56use commonware_cryptography::Hasher;
57use commonware_macros::boxed;
58use commonware_parallel::Strategy;
59use commonware_runtime::Handle;
60use std::{num::NonZeroU64, sync::Arc};
61use tracing::{debug, warn};
62
63pub mod batch;
64mod compact;
65pub mod fixed;
66mod operation;
67pub(crate) mod sync;
68pub mod variable;
69pub use compact::{
70    Config as CompactConfig, Db as CompactDb, MerkleizedBatch as CompactMerkleizedBatch,
71    UnmerkleizedBatch as CompactUnmerkleizedBatch,
72};
73pub use operation::{APPEND_CONTEXT, COMMIT_CONTEXT, Operation};
74
75/// Compute the authenticated root of a newly initialized database without opening storage.
76///
77/// The initial commit never carries metadata, so this root always represents `Commit(None, 0)`.
78pub fn initial_root<F, V, H>() -> H::Digest
79where
80    F: Family,
81    V: ValueEncoding,
82    H: Hasher,
83    Operation<F, V>: EncodeShared,
84{
85    single_operation_root::<F, H>(&Operation::<F, V>::Commit(None, Location::new(0)))
86}
87
88/// Check that `locs` is strictly increasing and every location is below `end`.
89fn check_locations<F: Family>(locs: &[Location<F>], end: Location<F>) -> Result<(), Error<F>> {
90    if let Some(pair) = locs.windows(2).find(|pair| pair[0] >= pair[1]) {
91        return Err(Error::LocationOutOfOrder(pair[0], pair[1]));
92    }
93    // Strictly increasing, so only the last location can reach `end`.
94    if let Some(&last) = locs.last()
95        && last >= end
96    {
97        return Err(Error::LocationOutOfBounds(last, end));
98    }
99    Ok(())
100}
101
102/// Configuration for a [Keyless] authenticated db.
103#[derive(Clone)]
104pub struct Config<J, S: Strategy> {
105    /// Configuration for the Merkle structure backing the authenticated journal.
106    pub merkle: MerkleConfig<S>,
107
108    /// Configuration for the operations log journal.
109    pub log: J,
110}
111
112/// A keyless authenticated database.
113pub struct Keyless<F, E, V, C, H, S>
114where
115    F: Family,
116    E: Context,
117    V: ValueEncoding,
118    C: Contiguous<Item = Operation<F, V>>,
119    H: Hasher,
120    S: Strategy,
121    Operation<F, V>: EncodeShared,
122{
123    /// Authenticated journal of operations.
124    journal: authenticated::Journal<F, E, C, H, S>,
125
126    /// Cached canonical operations root.
127    root: H::Digest,
128
129    /// The inactivity floor declared by the last committed batch. Operations at locations below
130    /// this value are considered inactive by the application and may be pruned.
131    inactivity_floor_loc: Location<F>,
132
133    /// Metrics for this database.
134    metrics: Metrics<E>,
135}
136
137impl<F, E, V, C, H, S> std::fmt::Debug for Keyless<F, E, V, C, H, S>
138where
139    F: Family,
140    E: Context,
141    V: ValueEncoding,
142    C: Mutable<Item = Operation<F, V>>,
143    H: Hasher,
144    S: Strategy,
145    Operation<F, V>: EncodeShared,
146{
147    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
148        f.debug_struct("Keyless")
149            .field("bounds", &self.bounds())
150            .field("inactivity_floor_loc", &self.inactivity_floor_loc())
151            .finish_non_exhaustive()
152    }
153}
154
155impl<F, E, V, C, H, S> Keyless<F, E, V, C, H, S>
156where
157    F: Family,
158    E: Context,
159    V: ValueEncoding,
160    C: Mutable<Item = Operation<F, V>>,
161    H: Hasher,
162    S: Strategy,
163    Operation<F, V>: EncodeShared,
164{
165    /// Initialize from the latest retained commit, discarding every operation after it.
166    /// `Some(max_size)` selects the latest retained commit with at most `max_size` operations.
167    /// `None` selects the latest retained state.
168    ///
169    /// After a bounded initialization past a prune, or a sync of a range that starts above the
170    /// floor, the selected commit's inactivity floor can be below the retained start. A batch
171    /// read of a location between the two that no batch chain holds then returns
172    /// [`crate::journal::Error::ItemPruned`], while a database that retains the location answers
173    /// it. Databases at the same commitment therefore answer batch reads alike only when each
174    /// retains every operation from its floor onward.
175    #[boxed]
176    pub async fn init(
177        context: E,
178        cfg: Config<C::Config, S>,
179        max_size: Option<Location<F>>,
180    ) -> Result<Self, Error<F>>
181    where
182        C: authenticated::Backing<E>,
183    {
184        // Keyless restores commit fields without replaying a keyed snapshot, so its logical floor
185        // may precede the retained operation prefix.
186        let mut journal = crate::qmdb::init_journal::<F, E, C, H, S>(
187            context.child("journal"),
188            cfg.merkle,
189            cfg.log,
190            max_size,
191            false,
192        )
193        .await?;
194        let metrics = Metrics::new(context);
195        if journal.size() == 0 {
196            warn!("no operations found in log, creating initial commit");
197            (journal, _) = journal
198                .append(&Operation::Commit(None, Location::new(0)))
199                .await?;
200            journal = journal.sync().await?;
201        }
202
203        let (last_commit_loc, inactivity_floor_loc) = {
204            let bounds = journal.bounds();
205            let last_commit_loc = Location::new(
206                bounds
207                    .end
208                    .checked_sub(1)
209                    .expect("at least one commit should exist"),
210            );
211            let op = journal.read(*last_commit_loc).await?;
212            let inactivity_floor_loc = op
213                .has_floor()
214                .expect("last operation should be a commit with floor");
215            (last_commit_loc, inactivity_floor_loc)
216        };
217        let inactive_peaks = F::inactive_peaks(last_commit_loc + 1, inactivity_floor_loc);
218        let root = journal.root(inactive_peaks)?;
219
220        let db = Self {
221            journal,
222            root,
223            inactivity_floor_loc,
224            metrics,
225        };
226        db.update_metrics();
227        Ok(db)
228    }
229
230    /// Get the value at location `loc` in the database.
231    ///
232    /// # Errors
233    ///
234    /// - Returns [`Error::LocationOutOfBounds`] if `loc` >= `self.bounds().end`.
235    /// - Returns [`Error::Journal`] with [`crate::journal::Error::ItemPruned`] if `loc` <
236    ///   `self.bounds().start`.
237    pub async fn get(&self, loc: Location<F>) -> Result<Option<V::Value>, Error<F>> {
238        let _timer = self.metrics.get_timer();
239        self.metrics.get_calls.inc();
240        self.metrics.lookups_requested.inc();
241        let op_count = self.journal.bounds().end;
242        if loc >= op_count {
243            return Err(Error::LocationOutOfBounds(loc, Location::new(op_count)));
244        }
245        let op = self.journal.read(*loc).await?;
246
247        let result = op.into_value();
248        Ok(result)
249    }
250
251    /// Batch read values at multiple locations.
252    ///
253    /// Locations must be strictly increasing.
254    /// Returns results in the same order as the input locations.
255    ///
256    /// # Errors
257    ///
258    /// - Returns [`Error::LocationOutOfOrder`] if the locations are not strictly increasing.
259    /// - Returns [`Error::LocationOutOfBounds`] if any location >= `self.bounds().end`.
260    /// - Returns [`Error::Journal`] with [`crate::journal::Error::ItemPruned`] if any location <
261    ///   `self.bounds().start`.
262    pub async fn get_many(&self, locs: &[Location<F>]) -> Result<Vec<Option<V::Value>>, Error<F>> {
263        if locs.is_empty() {
264            return Ok(Vec::new());
265        }
266
267        let _timer = self.metrics.get_many_timer();
268        self.metrics.get_many_calls.inc();
269        self.metrics.lookups_requested.inc_by(locs.len() as u64);
270        check_locations(locs, Location::new(self.journal.bounds().end))?;
271        let positions: Vec<u64> = locs.iter().map(|loc| **loc).collect();
272        let ops = self.journal.read_many(&positions).await?;
273        let result = ops.into_iter().map(|op| op.into_value()).collect();
274        Ok(result)
275    }
276
277    /// Returns the inactivity floor declared by the last committed batch.
278    pub const fn inactivity_floor_loc(&self) -> Location<F> {
279        self.inactivity_floor_loc
280    }
281
282    /// Return the retained operation range `[start, end)`.
283    ///
284    /// Proof generation also requires the necessary Merkle nodes to be retained.
285    pub fn bounds(&self) -> std::ops::Range<Location<F>> {
286        let bounds = self.journal.bounds();
287        Location::new(bounds.start)..Location::new(bounds.end)
288    }
289
290    /// Update state gauges from the current database state.
291    fn update_metrics(&self) {
292        let bounds = self.journal.bounds();
293        self.metrics.update(
294            bounds.end,
295            bounds.start,
296            *self.inactivity_floor_loc,
297            bounds.end - 1,
298        );
299    }
300
301    /// Return the inactivity floor declared by the last committed batch, which is the upper bound
302    /// on [`Self::prune`]'s `loc`.
303    ///
304    /// This logical boundary may precede the retained start in [`Self::bounds`].
305    pub const fn sync_boundary(&self) -> Location<F> {
306        self.inactivity_floor_loc
307    }
308
309    /// Get the metadata associated with the last commit.
310    pub async fn get_metadata(&self) -> Result<Option<V::Value>, Error<F>> {
311        // The journal always ends with a commit operation.
312        let op = self.journal.read(*self.journal.size() - 1).await?;
313        let Operation::Commit(metadata, _floor) = op else {
314            return Ok(None);
315        };
316
317        Ok(metadata)
318    }
319
320    /// Return the root of the db.
321    pub const fn root(&self) -> H::Digest {
322        self.root
323    }
324
325    /// Return a reference to the merkleization strategy.
326    pub const fn strategy(&self) -> &S {
327        self.journal.strategy()
328    }
329
330    /// Generate and return:
331    ///  1. a proof of all operations applied to the db in the range starting at (and including)
332    ///     location `start_loc`, and ending at the first of either:
333    ///     - the last operation performed, or
334    ///     - the operation `max_ops` from the start.
335    ///  2. the operations corresponding to the leaves in this range.
336    ///
337    /// # Errors
338    ///
339    /// - Returns [`Error::Merkle`] with [`crate::merkle::Error::RangeOutOfBounds`] if `start_loc`
340    ///   >= the number of operations.
341    /// - Returns [`Error::Journal`] with [`crate::journal::Error::ItemPruned`] or [`Error::Merkle`]
342    ///   with [`crate::merkle::Error::ElementPruned`] if a required operation or Merkle node has
343    ///   been pruned.
344    pub async fn proof(
345        &self,
346        start_loc: Location<F>,
347        max_ops: NonZeroU64,
348    ) -> Result<(Proof<F, H::Digest>, Vec<Operation<F, V>>), Error<F>> {
349        self.historical_proof(self.bounds().end, start_loc, max_ops)
350            .await
351    }
352
353    /// Analogous to proof, but with respect to the state of the Merkle structure when it had
354    /// `op_count` operations.
355    ///
356    /// `op_count` must be the size of a commit boundary.
357    ///
358    /// # Errors
359    ///
360    /// - Returns [`Error::Merkle`] with [`crate::merkle::Error::RangeOutOfBounds`] if `start_loc`
361    ///   >= `op_count` or `op_count` > number of operations.
362    /// - Returns [`Error::Journal`] with [`crate::journal::Error::ItemPruned`] or [`Error::Merkle`]
363    ///   with [`crate::merkle::Error::ElementPruned`] if a required operation or Merkle node has
364    ///   been pruned.
365    /// - Returns [`Error::HistoricalFloorPruned`] if `op_count - 1` is retained but is not a commit
366    ///   op.
367    #[allow(clippy::type_complexity)]
368    #[tracing::instrument(
369        name = "qmdb.keyless.db.historical_proof",
370        level = "info",
371        skip_all,
372        fields(
373            op_count = *op_count,
374            start_loc = *start_loc,
375            max_ops = max_ops.get(),
376        ),
377    )]
378    pub async fn historical_proof(
379        &self,
380        op_count: Location<F>,
381        start_loc: Location<F>,
382        max_ops: NonZeroU64,
383    ) -> Result<(Proof<F, H::Digest>, Vec<Operation<F, V>>), Error<F>> {
384        if op_count > self.journal.size() {
385            return Err(crate::merkle::Error::RangeOutOfBounds(op_count).into());
386        }
387
388        let inactive_peaks =
389            crate::qmdb::inactive_peaks_at::<F, _>(&self.journal, op_count).await?;
390
391        Ok(self
392            .journal
393            .historical_proof(op_count, start_loc, max_ops, inactive_peaks)
394            .await?)
395    }
396
397    /// Return the pinned Merkle nodes for a lower operation boundary of `loc`.
398    pub async fn pinned_nodes_at(&self, loc: Location<F>) -> Result<Vec<H::Digest>, Error<F>> {
399        self.journal
400            .merkle
401            .pinned_nodes_at(loc)
402            .await
403            .map_err(Into::into)
404    }
405
406    /// Prune historical operations prior to `loc`. This does not affect the db's root.
407    ///
408    /// The retained start in [`Self::bounds`] can remain below `loc`.
409    ///
410    /// `prune` requires no prior commit. After a crash, the database remains recoverable;
411    /// uncommitted operations are not guaranteed to survive.
412    ///
413    /// # Errors
414    ///
415    /// - Returns [`Error::PruneBeyondMinRequired`] if `loc` > the inactivity floor.
416    #[tracing::instrument(name = "qmdb.keyless.db.prune", level = "info", skip_all)]
417    #[boxed]
418    pub async fn prune(mut self, loc: Location<F>) -> Result<Self, Error<F>> {
419        let _timer = self.metrics.prune_timer();
420        self.metrics.prune_calls.inc();
421        if loc > self.inactivity_floor_loc {
422            return Err(Error::PruneBeyondMinRequired(
423                loc,
424                self.inactivity_floor_loc,
425            ));
426        }
427        (self.journal, _) = self.journal.prune(loc).await?;
428        self.update_metrics();
429        Ok(self)
430    }
431
432    /// Sync all database state to disk. While this isn't necessary to ensure durability of
433    /// committed operations, periodic invocation may reduce memory usage and the time required to
434    /// recover the database on restart.
435    #[tracing::instrument(name = "qmdb.keyless.db.sync", level = "info", skip_all)]
436    pub async fn sync(mut self) -> Result<Self, Error<F>> {
437        let _timer = self.metrics.sync_timer();
438        self.metrics.sync_calls.inc();
439        self.journal = self.journal.sync().await?;
440        Ok(self)
441    }
442
443    /// Begin durably persisting the journal state published by prior [`Keyless::apply_batch`]
444    /// calls.
445    ///
446    /// Awaiting the returned [Handle] provides the same durability guarantee as [Self::commit],
447    /// plus a best-effort attempt to bound the recovery needed on startup. Use [Self::sync] to
448    /// guarantee none is needed. A new sync waits for the prior sync before starting. Failures
449    /// of the deferred durability work surface on the returned handle. A failed data sync also
450    /// fails the next durability operation. A failed recovery-watermark sync is not observed by
451    /// [Self::commit], and a failed merkle-node sync may not be. Both resurface on the next
452    /// [Self::sync].
453    #[tracing::instrument(name = "qmdb.keyless.db.start_sync", level = "info", skip_all)]
454    pub async fn start_sync(mut self) -> Result<(Self, Handle<()>), Error<F>> {
455        self.metrics.start_sync_calls.inc();
456        let handle;
457        (self.journal, handle) = self.journal.start_sync().await?;
458        Ok((self, handle))
459    }
460
461    /// Durably commit the journal state published by prior [`Keyless::apply_batch`] calls.
462    #[tracing::instrument(name = "qmdb.keyless.db.commit", level = "info", skip_all)]
463    pub async fn commit(mut self) -> Result<Self, Error<F>> {
464        let _timer = self.metrics.commit_timer();
465        self.metrics.commit_calls.inc();
466        self.journal = self.journal.commit().await?;
467        Ok(self)
468    }
469
470    /// Destroy the db, removing all data from disk.
471    #[boxed]
472    pub async fn destroy(self) -> Result<(), Error<F>> {
473        Ok(self.journal.destroy().await?)
474    }
475
476    /// The [`Commitment`](chain::Commitment) for the database's current state.
477    pub(crate) fn commitment(&self) -> chain::Commitment<F, H::Digest> {
478        chain::Commitment::new(self.journal.size(), self.root)
479    }
480
481    /// Create a new speculative batch of operations with this database as its parent.
482    pub fn new_batch(&self) -> batch::UnmerkleizedBatch<F, H, V, S> {
483        batch::UnmerkleizedBatch::new(self, self.commitment())
484    }
485
486    /// Create an initial [`batch::MerkleizedBatch`] from the DB's current state.
487    pub fn to_batch(&self) -> Arc<batch::MerkleizedBatch<F, H::Digest, V, S>> {
488        Arc::new(batch::MerkleizedBatch {
489            journal_batch: self.journal.to_merkleized_batch(),
490            parent: None,
491            bounds: chain::Bounds::from_db(self.commitment(), self.inactivity_floor_loc),
492        })
493    }
494
495    /// Check that `batch` can be applied to the database in its current state, without
496    /// applying it.
497    ///
498    /// [`Self::apply_batch`] runs the same validation but consumes the database when it
499    /// fails; callers that want to reject a bad batch and keep the handle can check first.
500    pub fn validate_batch(
501        &self,
502        batch: &batch::MerkleizedBatch<F, H::Digest, V, S>,
503    ) -> Result<(), Error<F>> {
504        batch.bounds.validate_apply_to(self.commitment())
505    }
506
507    /// Apply a [`batch::MerkleizedBatch`] to the database.
508    ///
509    /// This publishes the batch to the in-memory database state and appends it to the journal.
510    /// Call [`Keyless::commit`] or [`Keyless::sync`], or await the handle returned by
511    /// [`Keyless::start_sync`], to make the applied state durable.
512    ///
513    /// # Errors
514    ///
515    /// Returns [`Error::StaleBatch`] if the database is at neither the batch's recorded database
516    /// boundary nor an ancestor's tip.
517    #[tracing::instrument(name = "qmdb.keyless.db.apply_batch", level = "info", skip_all)]
518    pub async fn apply_batch(
519        mut self,
520        batch: Arc<batch::MerkleizedBatch<F, H::Digest, V, S>>,
521    ) -> Result<Self, Error<F>> {
522        let _timer = self.metrics.apply_batch_timer();
523        self.metrics.apply_batch_calls.inc();
524        self.validate_batch(&batch)?;
525        let start_loc = self.journal.size();
526
527        self.journal = self.journal.apply_batch(&batch.journal_batch).await?;
528
529        self.inactivity_floor_loc = batch.bounds.inactivity_floor;
530        self.root = batch.root();
531        let end_loc = batch.bounds.tip.size;
532        debug!(size = ?end_loc, "applied batch");
533        self.update_metrics();
534        self.metrics
535            .operations_applied
536            .inc_by(*end_loc - *start_loc);
537        Ok(self)
538    }
539}
540
541impl<F, E, V, C, H, S> crate::qmdb::sync::Source for Keyless<F, E, V, C, H, S>
542where
543    F: Family,
544    E: Context,
545    V: ValueEncoding,
546    C: Mutable<Item = Operation<F, V>>,
547    H: Hasher,
548    S: Strategy,
549    Operation<F, V>: EncodeShared,
550{
551    type Family = F;
552    type Digest = H::Digest;
553    type Op = Operation<F, V>;
554    type Error = Error<F>;
555
556    async fn serve(&self, request: crate::qmdb::sync::Request<F>) -> source::Result<Self> {
557        self.journal.serve(request).await
558    }
559}
560
561#[cfg(test)]
562pub(crate) mod tests {
563    use super::*;
564    use crate::qmdb::{verify_proof, verify_proof_and_pinned_nodes};
565    use commonware_cryptography::Sha256;
566    use commonware_parallel::Strategy;
567    use commonware_runtime::{Supervisor as _, deterministic};
568    use commonware_utils::NZU64;
569    use std::{future::Future, pin::Pin};
570
571    pub(crate) type Reopen<D> =
572        Box<dyn Fn(deterministic::Context) -> Pin<Box<dyn Future<Output = D> + Send>>>;
573
574    pub(crate) type BoundedOpen<D, F> = Box<
575        dyn Fn(
576            deterministic::Context,
577            Location<F>,
578        ) -> Pin<Box<dyn Future<Output = Result<D, Error<F>>> + Send>>,
579    >;
580
581    type TestKeyless<F, V, C, H, S> = Keyless<F, deterministic::Context, V, C, H, S>;
582
583    /// Test value factory: creates distinct values from an index.
584    pub(crate) trait TestValue: Clone + PartialEq + std::fmt::Debug + Send + Sync {
585        fn make(i: u64) -> Self;
586    }
587
588    impl TestValue for Vec<u8> {
589        fn make(i: u64) -> Self {
590            vec![(i % 255) as u8; ((i % 13) + 7) as usize]
591        }
592    }
593
594    impl TestValue for commonware_utils::sequence::U64 {
595        fn make(i: u64) -> Self {
596            Self::new(i * 10 + 1)
597        }
598    }
599
600    /// Emits the named test against `mmr::Family` and `mmb::Family`.
601    macro_rules! keyless_tests {
602        ($($name:ident => $scenario:ident, $fixture:ident;)*) => {
603            $(
604                #[test_traced]
605                fn $name() {
606                    deterministic::Runner::default().start(|ctx| async move {
607                        keyless_tests!(@fixture $fixture, $scenario, mmr, ctx);
608                    });
609                }
610            )*
611            paste::paste! {
612                $(
613                    #[test_traced]
614                    fn [<$name _mmb>]() {
615                        deterministic::Runner::default().start(|ctx| async move {
616                            keyless_tests!(@fixture $fixture, $scenario, mmb, ctx);
617                        });
618                    }
619                )*
620            }
621        };
622        (@fixture bounded, $scenario:ident, $family:ident, $ctx:ident) => {
623            let db = open_db::<$family::Family>($ctx.child("db")).await;
624            tests::$scenario($ctx, db, reopen::<$family::Family>(), bounded_open::<$family::Family>()).await;
625        };
626        (@fixture bounded_floor, $scenario:ident, $family:ident, $ctx:ident) => {
627            let db = open_db::<$family::Family>($ctx.child("db")).await;
628            tests::$scenario($ctx, db, bounded_open::<$family::Family>()).await;
629        };
630        (@fixture bounded_indexed, $scenario:ident, $family:ident, $ctx:ident) => {
631            let db = open_db::<$family::Family>($ctx.child("db").with_attribute("index", 1)).await;
632            tests::$scenario($ctx, db, reopen::<$family::Family>(), bounded_open::<$family::Family>()).await;
633        };
634        (@fixture db, $scenario:ident, $family:ident, $ctx:ident) => {
635            let db = open_db::<$family::Family>($ctx.child("db")).await;
636            tests::$scenario(db).await;
637        };
638        (@fixture pair, $scenario:ident, $family:ident, $ctx:ident) => {
639            let db = open_db::<$family::Family>($ctx.child("db")).await;
640            let foreign = open_db_with_suffix::<$family::Family>("foreign", $ctx.child("foreign")).await;
641            tests::$scenario(db, foreign).await;
642        };
643        (@fixture reopen, $scenario:ident, $family:ident, $ctx:ident) => {
644            let db = open_db::<$family::Family>($ctx.child("db")).await;
645            tests::$scenario($ctx, db, reopen::<$family::Family>()).await;
646        };
647        (@fixture reopen_indexed, $scenario:ident, $family:ident, $ctx:ident) => {
648            let db =
649                open_db::<$family::Family>($ctx.child("db").with_attribute("index", 1)).await;
650            tests::$scenario($ctx, db, reopen::<$family::Family>()).await;
651        };
652    }
653
654    pub(super) use keyless_tests;
655
656    /// Reads stay exact after the ancestor batches are dropped, because the
657    /// journal batch retains every ancestor's items.
658    #[boxed]
659    pub(crate) async fn run_dropped_ancestor_reads<F: Family, V, C, S: Strategy>(
660        mut db: TestKeyless<F, V, C, Sha256, S>,
661    ) where
662        V: ValueEncoding<Value: TestValue>,
663        C: Mutable<Item = Operation<F, V>>,
664        Operation<F, V>: EncodeShared + std::fmt::Debug,
665    {
666        // Commit one value so the chain has committed state below it.
667        let seed = db
668            .new_batch()
669            .append(V::Value::make(0))
670            .merkleize(&db, None, db.inactivity_floor_loc())
671            .await
672            .unwrap();
673        db = db.apply_batch(seed).await.unwrap();
674        db = db.commit().await.unwrap();
675        let floor = db.inactivity_floor_loc();
676
677        // A three-level unapplied chain, two appends per level. Each level's
678        // appends start at its parent's tip.
679        let base_a = db.bounds().end;
680        let a = db
681            .new_batch()
682            .append(V::Value::make(1))
683            .append(V::Value::make(2))
684            .merkleize(&db, None, floor)
685            .await
686            .unwrap();
687        let base_b = a.bounds().tip.size;
688        let b = a
689            .new_batch::<Sha256>()
690            .append(V::Value::make(3))
691            .append(V::Value::make(4))
692            .merkleize(&db, None, floor)
693            .await
694            .unwrap();
695        let base_c = b.bounds().tip.size;
696        let c = b
697            .new_batch::<Sha256>()
698            .append(V::Value::make(5))
699            .merkleize(&db, None, floor)
700            .await
701            .unwrap();
702        let child = c.new_batch::<Sha256>();
703        drop(a);
704        drop(b);
705
706        // Appended values across all three levels resolve from the retained items.
707        let locs = [base_a, base_a + 1, base_b, base_b + 1, base_c];
708        let expected: Vec<_> = (1..=5u64).map(|i| Some(V::Value::make(i))).collect();
709        for (loc, expected) in locs.iter().zip(&expected) {
710            assert_eq!(&c.get(*loc, &db).await.unwrap(), expected);
711            assert_eq!(&child.get(*loc, &db).await.unwrap(), expected);
712        }
713        assert_eq!(c.get_many(&locs, &db).await.unwrap(), expected);
714        assert_eq!(child.get_many(&locs, &db).await.unwrap(), expected);
715
716        // Merkleizing the child returns `StaleBatch`, because its dropped ancestors are unapplied.
717        assert!(matches!(
718            child.merkleize(&db, None, floor).await,
719            Err(Error::StaleBatch)
720        ));
721
722        // Applying the chain lands the same values the reads reported.
723        let db = db.apply_batch(c).await.unwrap();
724        for (loc, expected) in locs.iter().zip(&expected) {
725            assert_eq!(&db.get(*loc).await.unwrap(), expected);
726        }
727        db.destroy().await.unwrap();
728    }
729
730    /// Batch reads below the batch's inactivity floor return `BelowInactivityFloor` however far
731    /// the database has pruned, and a merkleized batch returns `LocationOutOfBounds` at or past
732    /// its tip, like an unmerkleized one.
733    #[boxed]
734    pub(crate) async fn run_reads_below_floor_refused<F: Family, V, C, S: Strategy>(
735        mut db: TestKeyless<F, V, C, Sha256, S>,
736    ) where
737        V: ValueEncoding<Value: TestValue>,
738        C: Mutable<Item = Operation<F, V>>,
739        Operation<F, V>: EncodeShared + std::fmt::Debug,
740    {
741        // Locations 1 and 2 hold appends, and the commit at 3 raises the floor to 2.
742        let seed = db
743            .new_batch()
744            .append(V::Value::make(1))
745            .append(V::Value::make(2))
746            .merkleize(&db, None, Location::new(2))
747            .await
748            .unwrap();
749        db = db.apply_batch(seed).await.unwrap();
750        db = db.commit().await.unwrap();
751
752        // A batch built on the database uses its floor.
753        let batch = db.new_batch();
754        assert!(matches!(
755            batch.get(Location::new(1), &db).await,
756            Err(Error::BelowInactivityFloor(loc)) if loc == Location::new(1)
757        ));
758        assert_eq!(
759            batch.get(Location::new(2), &db).await.unwrap(),
760            Some(V::Value::make(2))
761        );
762        assert!(matches!(
763            batch
764                .get_many(&[Location::new(1), Location::new(2)], &db)
765                .await,
766            Err(Error::BelowInactivityFloor(loc)) if loc == Location::new(1)
767        ));
768
769        // Unordered locations are reported before a location below the floor.
770        assert!(matches!(
771            batch
772                .get_many(&[Location::new(1), Location::new(0)], &db)
773                .await,
774            Err(Error::LocationOutOfOrder(first, second))
775                if first == Location::new(1) && second == Location::new(0)
776        ));
777
778        // A merkleized batch appending at 4 and raising the floor to 4 returns
779        // `BelowInactivityFloor` for location 2.
780        let parent = db
781            .new_batch()
782            .append(V::Value::make(3))
783            .merkleize(&db, None, Location::new(4))
784            .await
785            .unwrap();
786        assert!(matches!(
787            parent.get(Location::new(2), &db).await,
788            Err(Error::BelowInactivityFloor(loc)) if loc == Location::new(2)
789        ));
790        assert_eq!(
791            parent.get(Location::new(4), &db).await.unwrap(),
792            Some(V::Value::make(3))
793        );
794        assert!(matches!(
795            parent
796                .get_many(&[Location::new(2), Location::new(4)], &db)
797                .await,
798            Err(Error::BelowInactivityFloor(loc)) if loc == Location::new(2)
799        ));
800        assert!(matches!(
801            parent
802                .get_many(&[Location::new(3), Location::new(2)], &db)
803                .await,
804            Err(Error::LocationOutOfOrder(first, second))
805                if first == Location::new(3) && second == Location::new(2)
806        ));
807
808        // A merkleized batch bounds reads by its own tip, like an unmerkleized one.
809        let tip = parent.bounds().tip.size;
810        assert!(matches!(
811            parent.get(tip, &db).await,
812            Err(Error::LocationOutOfBounds(loc, end)) if loc == tip && end == tip
813        ));
814        assert!(matches!(
815            parent.get_many(&[Location::new(4), tip], &db).await,
816            Err(Error::LocationOutOfBounds(loc, end)) if loc == tip && end == tip
817        ));
818
819        // Its unmerkleized child builds on the parent's floor.
820        let child = parent.new_batch::<Sha256>();
821        assert!(matches!(
822            child.get(Location::new(3), &db).await,
823            Err(Error::BelowInactivityFloor(loc)) if loc == Location::new(3)
824        ));
825        assert_eq!(
826            child.get(Location::new(4), &db).await.unwrap(),
827            Some(V::Value::make(3))
828        );
829        drop(child);
830
831        // A pruned instance returns `BelowInactivityFloor` for the location, not `ItemPruned`.
832        db = db.apply_batch(parent).await.unwrap();
833        let mut batch = db.new_batch();
834        for i in 10..22 {
835            batch = batch.append(V::Value::make(i));
836        }
837        let floor = db.bounds().end + 12;
838        let batch = batch.merkleize(&db, None, floor).await.unwrap();
839        db = db.apply_batch(batch).await.unwrap();
840        db = db.commit().await.unwrap();
841        db = db.prune(floor).await.unwrap();
842        assert!(db.bounds().start > Location::new(2));
843        assert!(matches!(
844            db.new_batch().get(Location::new(2), &db).await,
845            Err(Error::BelowInactivityFloor(loc)) if loc == Location::new(2)
846        ));
847        db.destroy().await.unwrap();
848    }
849
850    /// A child built before its parent was applied merkleizes to the same root after the
851    /// database prunes past the chain's recorded database boundary.
852    #[boxed]
853    pub(crate) async fn run_merkleize_across_prune<F: Family, V, C, S: Strategy>(
854        mut db: TestKeyless<F, V, C, Sha256, S>,
855    ) where
856        V: ValueEncoding<Value: TestValue>,
857        C: Mutable<Item = Operation<F, V>>,
858        Operation<F, V>: EncodeShared + std::fmt::Debug,
859    {
860        // Seed committed state so the chain's recorded boundary sits above zero.
861        let mut seed = db.new_batch();
862        for i in 0..80 {
863            seed = seed.append(V::Value::make(i));
864        }
865        let seed = seed
866            .merkleize(&db, None, db.inactivity_floor_loc())
867            .await
868            .unwrap();
869        db = db.apply_batch(seed).await.unwrap();
870        db = db.commit().await.unwrap();
871
872        // The parent declares a floor at its own commit, past the chain's recorded boundary.
873        let parent_floor = db.bounds().end + 1;
874        let parent = db
875            .new_batch()
876            .append(V::Value::make(100))
877            .merkleize(&db, None, parent_floor)
878            .await
879            .unwrap();
880
881        // Two identical children built before the apply, appending enough that their merkleize
882        // merges deep over the database's Merkle nodes.
883        let build = |parent: &Arc<batch::MerkleizedBatch<F, _, V, S>>| {
884            let mut child = parent.new_batch::<Sha256>();
885            for i in 200..280 {
886                child = child.append(V::Value::make(i));
887            }
888            child
889        };
890        let child = build(&parent);
891        let expected = build(&parent)
892            .merkleize(&db, None, parent_floor)
893            .await
894            .unwrap()
895            .root();
896
897        // Apply the parent and prune to its floor.
898        let db = db.apply_batch(parent).await.unwrap();
899        let db = db.commit().await.unwrap();
900        let floor = db.inactivity_floor_loc();
901        let db = db.prune(floor).await.unwrap();
902        assert!(*db.bounds().start > 0, "the prune must remove history");
903
904        // The child built before the apply still merkleizes to the same root.
905        let merkleized = child.merkleize(&db, None, parent_floor).await.unwrap();
906        assert_eq!(merkleized.root(), expected);
907        db.destroy().await.unwrap();
908    }
909
910    /// Once a sibling batch is applied, reads through the other sibling and its child return
911    /// `StaleRead`, while merkleizing the child and applying the other sibling return `StaleBatch`.
912    #[boxed]
913    pub(crate) async fn run_stale_fork_refuses<F: Family, V, C, S: Strategy>(
914        mut db: TestKeyless<F, V, C, Sha256, S>,
915    ) where
916        V: ValueEncoding<Value: TestValue>,
917        C: Mutable<Item = Operation<F, V>>,
918        Operation<F, V>: EncodeShared + std::fmt::Debug,
919    {
920        // Commit one value so the siblings have committed state to read.
921        let merkleized = db
922            .new_batch()
923            .append(V::Value::make(0))
924            .merkleize(&db, None, db.inactivity_floor_loc())
925            .await
926            .unwrap();
927        db = db.apply_batch(merkleized).await.unwrap();
928        db = db.commit().await.unwrap();
929
930        let floor = db.inactivity_floor_loc();
931        let winner = db
932            .new_batch()
933            .append(V::Value::make(1))
934            .merkleize(&db, None, floor)
935            .await
936            .unwrap();
937        let loser = db
938            .new_batch()
939            .append(V::Value::make(2))
940            .merkleize(&db, None, floor)
941            .await
942            .unwrap();
943        assert_eq!(winner.bounds().tip.size, loser.bounds().tip.size);
944        assert_ne!(winner.root(), loser.root());
945
946        let child = loser.new_batch::<Sha256>().append(V::Value::make(3));
947        let direct = db.new_batch().append(V::Value::make(4));
948        let committed = Location::new(0);
949        let db = db.apply_batch(winner).await.unwrap();
950
951        // A batch built directly on the database before the apply is stale too.
952        assert!(matches!(
953            direct.get(committed, &db).await,
954            Err(Error::StaleRead)
955        ));
956        assert!(matches!(
957            direct.get_many(&[committed], &db).await,
958            Err(Error::StaleRead)
959        ));
960
961        assert!(matches!(
962            child.get(committed, &db).await,
963            Err(Error::StaleRead)
964        ));
965        assert!(matches!(
966            child.get_many(&[committed], &db).await,
967            Err(Error::StaleRead)
968        ));
969        assert!(matches!(
970            loser.get(committed, &db).await,
971            Err(Error::StaleRead)
972        ));
973
974        // The compatibility check precedes every other read argument check, so reads of no
975        // locations, of a location at the reader's end, and of unordered locations all return
976        // `StaleRead`.
977        assert!(matches!(
978            child.get_many(&[], &db).await,
979            Err(Error::StaleRead)
980        ));
981        assert!(matches!(
982            loser.get_many(&[], &db).await,
983            Err(Error::StaleRead)
984        ));
985        assert!(matches!(
986            child.get(child.size(), &db).await,
987            Err(Error::StaleRead)
988        ));
989        assert!(matches!(
990            loser.get(loser.size(), &db).await,
991            Err(Error::StaleRead)
992        ));
993        assert!(matches!(
994            child
995                .get_many(&[Location::new(1), Location::new(0)], &db)
996                .await,
997            Err(Error::StaleRead)
998        ));
999        assert!(matches!(
1000            child.merkleize(&db, None, floor).await,
1001            Err(Error::StaleBatch)
1002        ));
1003        assert!(matches!(db.validate_batch(&loser), Err(Error::StaleBatch)));
1004        db.destroy().await.unwrap();
1005    }
1006
1007    /// Applying a batch's child moves the database to a state outside the parent's chain, so
1008    /// reads through the parent return `StaleRead` while the child keeps reading.
1009    #[boxed]
1010    pub(crate) async fn run_descendant_apply_makes_parent_reads_stale<
1011        F: Family,
1012        V,
1013        C,
1014        S: Strategy,
1015    >(
1016        db: TestKeyless<F, V, C, Sha256, S>,
1017    ) where
1018        V: ValueEncoding<Value: TestValue>,
1019        C: Mutable<Item = Operation<F, V>>,
1020        Operation<F, V>: EncodeShared + std::fmt::Debug,
1021    {
1022        let floor = db.inactivity_floor_loc();
1023        let parent = db
1024            .new_batch()
1025            .append(V::Value::make(1))
1026            .merkleize(&db, None, floor)
1027            .await
1028            .unwrap();
1029        let loc = Location::new(1);
1030        assert_eq!(parent.get(loc, &db).await.unwrap(), Some(V::Value::make(1)));
1031        let child = parent
1032            .new_batch::<Sha256>()
1033            .append(V::Value::make(2))
1034            .merkleize(&db, None, floor)
1035            .await
1036            .unwrap();
1037
1038        let db = db.apply_batch(child.clone()).await.unwrap();
1039        assert_eq!(child.get(loc, &db).await.unwrap(), Some(V::Value::make(1)));
1040        assert!(matches!(parent.get(loc, &db).await, Err(Error::StaleRead)));
1041        assert!(matches!(
1042            parent.get_many(&[loc], &db).await,
1043            Err(Error::StaleRead)
1044        ));
1045        db.destroy().await.unwrap();
1046    }
1047
1048    #[boxed]
1049    pub(crate) async fn run_merkleize_foreign_db<F: Family, V, C, S: Strategy>(
1050        db: TestKeyless<F, V, C, Sha256, S>,
1051        foreign: TestKeyless<F, V, C, Sha256, S>,
1052    ) where
1053        V: ValueEncoding<Value: TestValue>,
1054        C: Mutable<Item = Operation<F, V>>,
1055        Operation<F, V>: EncodeShared,
1056    {
1057        let batch = db
1058            .new_batch()
1059            .append(V::Value::make(11))
1060            .merkleize(&db, None, Location::new(0))
1061            .await
1062            .unwrap();
1063        let db = db.apply_batch(batch).await.unwrap();
1064        let batch = foreign
1065            .new_batch()
1066            .append(V::Value::make(99))
1067            .merkleize(&foreign, None, Location::new(0))
1068            .await
1069            .unwrap();
1070        let foreign = foreign.apply_batch(batch).await.unwrap();
1071        assert_eq!(db.bounds().end, foreign.bounds().end);
1072        assert_ne!(db.root(), foreign.root());
1073
1074        let batch = db.new_batch().append(V::Value::make(22));
1075        assert!(matches!(
1076            batch.merkleize(&foreign, None, Location::new(0)).await,
1077            Err(Error::StaleBatch)
1078        ));
1079
1080        // A child must also reject a database outside its ancestor chain.
1081        let parent = db
1082            .new_batch()
1083            .append(V::Value::make(33))
1084            .merkleize(&db, None, Location::new(0))
1085            .await
1086            .unwrap();
1087        let child = parent.new_batch::<Sha256>().append(V::Value::make(44));
1088        assert!(matches!(
1089            child.merkleize(&foreign, None, Location::new(0)).await,
1090            Err(Error::StaleBatch)
1091        ));
1092        db.destroy().await.unwrap();
1093        foreign.destroy().await.unwrap();
1094    }
1095
1096    #[boxed]
1097    pub(crate) async fn run_merkleize_stale_sibling<F: Family, V, C, S: Strategy>(
1098        db: TestKeyless<F, V, C, Sha256, S>,
1099    ) where
1100        V: ValueEncoding<Value: TestValue>,
1101        C: Mutable<Item = Operation<F, V>>,
1102        Operation<F, V>: EncodeShared,
1103    {
1104        let direct = db.new_batch().append(V::Value::make(11));
1105        let sibling = db
1106            .new_batch()
1107            .append(V::Value::make(22))
1108            .merkleize(&db, None, Location::new(0))
1109            .await
1110            .unwrap();
1111        let db = db.apply_batch(sibling).await.unwrap();
1112        let direct_result = direct.merkleize(&db, None, Location::new(0)).await;
1113
1114        let parent = db
1115            .new_batch()
1116            .append(V::Value::make(33))
1117            .merkleize(&db, None, Location::new(0))
1118            .await
1119            .unwrap();
1120        let child = parent.new_batch::<Sha256>().append(V::Value::make(44));
1121        let sibling = db
1122            .new_batch()
1123            .append(V::Value::make(55))
1124            .merkleize(&db, None, Location::new(0))
1125            .await
1126            .unwrap();
1127        let db = db.apply_batch(sibling).await.unwrap();
1128        let child_result = child.merkleize(&db, None, Location::new(0)).await;
1129
1130        db.destroy().await.unwrap();
1131        assert!(matches!(direct_result, Err(Error::StaleBatch)));
1132        assert!(matches!(child_result, Err(Error::StaleBatch)));
1133    }
1134
1135    #[boxed]
1136    pub(crate) async fn run_merkleize_ancestor_states<F: Family, V, C, S: Strategy>(
1137        db: TestKeyless<F, V, C, Sha256, S>,
1138    ) where
1139        V: ValueEncoding<Value: TestValue>,
1140        C: Mutable<Item = Operation<F, V>>,
1141        Operation<F, V>: EncodeShared,
1142    {
1143        let grandparent = db
1144            .new_batch()
1145            .append(V::Value::make(1))
1146            .merkleize(&db, None, Location::new(0))
1147            .await
1148            .unwrap();
1149        let parent = grandparent
1150            .new_batch::<Sha256>()
1151            .append(V::Value::make(2))
1152            .merkleize(&db, None, Location::new(0))
1153            .await
1154            .unwrap();
1155        let pending = parent
1156            .new_batch::<Sha256>()
1157            .append(V::Value::make(3))
1158            .merkleize(&db, None, Location::new(0))
1159            .await
1160            .unwrap();
1161
1162        // The database may advance to a held intermediate ancestor.
1163        let db = db.apply_batch(Arc::clone(&grandparent)).await.unwrap();
1164        let applied = parent
1165            .new_batch::<Sha256>()
1166            .append(V::Value::make(3))
1167            .merkleize(&db, None, Location::new(0))
1168            .await
1169            .unwrap();
1170        assert_eq!(pending.root(), applied.root());
1171
1172        // Once that ancestor is dropped, its commitment becomes the effective boundary.
1173        drop(grandparent);
1174        let retired = parent
1175            .new_batch::<Sha256>()
1176            .append(V::Value::make(3))
1177            .merkleize(&db, None, Location::new(0))
1178            .await
1179            .unwrap();
1180        assert_eq!(retired.bounds().db, db.commitment());
1181        assert_eq!(pending.root(), retired.root());
1182
1183        // A child created before its immediate parent is applied still merkleizes and applies
1184        // afterward.
1185        let child = parent.new_batch::<Sha256>().append(V::Value::make(3));
1186        let db = db.apply_batch(parent).await.unwrap();
1187        let child = child.merkleize(&db, None, Location::new(0)).await.unwrap();
1188        assert_eq!(pending.root(), child.root());
1189        let db = db.apply_batch(child).await.unwrap();
1190        let (proof, ops) = db.proof(Location::new(0), NZU64!(100)).await.unwrap();
1191        assert!(verify_proof::<Sha256, _, _>(
1192            &proof,
1193            Location::new(0),
1194            &ops,
1195            &db.root()
1196        ));
1197        db.destroy().await.unwrap();
1198    }
1199
1200    #[boxed]
1201    pub(crate) async fn run_empty<F: Family, V, C, H, S: Strategy>(
1202        context: deterministic::Context,
1203        db: TestKeyless<F, V, C, H, S>,
1204        reopen: Reopen<TestKeyless<F, V, C, H, S>>,
1205    ) where
1206        V: ValueEncoding<Value: TestValue>,
1207        C: Mutable<Item = Operation<F, V>>,
1208        H: Hasher,
1209        Operation<F, V>: EncodeShared,
1210    {
1211        let bounds = db.bounds();
1212        assert_eq!(bounds.end, 1); // initial commit should exist
1213        assert_eq!(bounds.start, Location::new(0));
1214        assert_eq!(db.get_metadata().await.unwrap(), None);
1215        assert_eq!(db.bounds().end - 1, Location::new(0));
1216
1217        // Make sure closing/reopening gets us back to the same state, even after adding an uncommitted op.
1218        let root = db.root();
1219        {
1220            db.new_batch().append(V::Value::make(1));
1221
1222            // Don't merkleize/apply, to simulate a failed commit
1223        }
1224        drop(db);
1225
1226        let db = reopen(context.child("db").with_attribute("index", 2)).await;
1227        assert_eq!(db.root(), root);
1228        assert_eq!(db.bounds().end, 1);
1229        assert_eq!(db.get_metadata().await.unwrap(), None);
1230
1231        // Test calling commit on an empty db which should make it (durably) non-empty.
1232        let metadata = V::Value::make(99);
1233        let merkleized = db
1234            .new_batch()
1235            .merkleize(&db, Some(metadata.clone()), db.inactivity_floor_loc())
1236            .await
1237            .unwrap();
1238        let db = db.apply_batch(merkleized).await.unwrap();
1239        let db = db.commit().await.unwrap();
1240        assert_eq!(db.bounds().end, 2); // 2 commit ops
1241        assert_eq!(db.get_metadata().await.unwrap(), Some(metadata.clone()));
1242        assert_eq!(
1243            db.get(Location::new(1)).await.unwrap(),
1244            Some(metadata.clone())
1245        ); // the commit op
1246        let root = db.root();
1247
1248        // Commit op should remain after reopen even without clean shutdown.
1249        drop(db);
1250        let db = reopen(context.child("db").with_attribute("index", 3)).await;
1251        assert_eq!(db.bounds().end, 2); // commit op should remain after re-open.
1252        assert_eq!(db.get_metadata().await.unwrap(), Some(metadata));
1253        assert_eq!(db.root(), root);
1254        assert_eq!(db.bounds().end - 1, Location::new(1));
1255
1256        db.destroy().await.unwrap();
1257    }
1258
1259    /// `operations()` must cover exactly the batch's own applied range and match the
1260    /// operations a post-apply `historical_proof` recovers from the log, for a db-based
1261    /// batch and for a chained batch applied after its ancestor.
1262    #[boxed]
1263    pub(crate) async fn run_operations_match_applied_log<F: Family, V, C, H, S: Strategy>(
1264        db: TestKeyless<F, V, C, H, S>,
1265    ) where
1266        V: ValueEncoding<Value: TestValue>,
1267        C: Mutable<Item = Operation<F, V>>,
1268        H: Hasher,
1269        Operation<F, V>: EncodeShared + PartialEq + core::fmt::Debug,
1270    {
1271        let seed = db
1272            .new_batch()
1273            .append(V::Value::make(1))
1274            .append(V::Value::make(2))
1275            .merkleize(&db, None, db.inactivity_floor_loc())
1276            .await
1277            .unwrap();
1278        let (seed_start, seed_ops) = seed.operations();
1279        let seed_root = seed.root();
1280        let seed_proof = seed.proof(&db).unwrap();
1281        let seed_pins = seed.pinned_nodes(&db).unwrap();
1282        let applied_start = db.bounds().end;
1283        let db = db.apply_batch(seed).await.unwrap();
1284        let seed_range = applied_start..db.bounds().end;
1285        assert_eq!(seed_start, seed_range.start);
1286        assert_eq!(*seed_start + seed_ops.len() as u64, *seed_range.end);
1287
1288        // A chained batch's operations are its own suffix only.
1289        let parent = db
1290            .new_batch()
1291            .append(V::Value::make(3))
1292            .merkleize(&db, None, db.inactivity_floor_loc())
1293            .await
1294            .unwrap();
1295        let child = parent
1296            .new_batch::<H>()
1297            .append(V::Value::make(4))
1298            .merkleize(&db, None, db.inactivity_floor_loc())
1299            .await
1300            .unwrap();
1301        let (parent_start, parent_ops) = parent.operations();
1302        let (child_start, child_ops) = child.operations();
1303        let (parent_root, child_root) = (parent.root(), child.root());
1304        let (parent_pins, child_pins) = (
1305            parent.pinned_nodes(&db).unwrap(),
1306            child.pinned_nodes(&db).unwrap(),
1307        );
1308        let (parent_proof, child_proof) = (parent.proof(&db).unwrap(), child.proof(&db).unwrap());
1309        let applied_start = db.bounds().end;
1310        let db = db.apply_batch(parent).await.unwrap();
1311        let parent_range = applied_start..db.bounds().end;
1312
1313        // With the database at the parent's tip, the child's proof and pinned nodes are
1314        // unchanged.
1315        assert_eq!(child.proof(&db).unwrap(), child_proof);
1316        assert_eq!(child.pinned_nodes(&db).unwrap(), child_pins);
1317        let applied_start = db.bounds().end;
1318        let db = db.apply_batch(child).await.unwrap();
1319        let child_range = applied_start..db.bounds().end;
1320        assert_eq!(parent_start, parent_range.start);
1321        assert_eq!(*parent_start + parent_ops.len() as u64, *parent_range.end);
1322        assert_eq!(child_start, child_range.start);
1323        assert_eq!(*child_start + child_ops.len() as u64, *child_range.end);
1324
1325        // A write-free batch still captures its commit-only suffix.
1326        let empty = db
1327            .new_batch()
1328            .merkleize(&db, None, db.inactivity_floor_loc())
1329            .await
1330            .unwrap();
1331        let (empty_start, empty_ops) = empty.operations();
1332        let (empty_root, empty_proof) = (empty.root(), empty.proof(&db).unwrap());
1333        let empty_pins = empty.pinned_nodes(&db).unwrap();
1334        let applied_start = db.bounds().end;
1335        let db = db.apply_batch(empty).await.unwrap();
1336        let empty_range = applied_start..db.bounds().end;
1337        assert_eq!(empty_start, empty_range.start);
1338        assert_eq!(*empty_start + empty_ops.len() as u64, *empty_range.end);
1339
1340        // Every captured delta and proof must match what the log recovers for its
1341        // range, and verify against the batch's own root with and without the pins.
1342        for (start, ops, proof, pins, root) in [
1343            (seed_start, seed_ops, seed_proof, seed_pins, seed_root),
1344            (
1345                parent_start,
1346                parent_ops,
1347                parent_proof,
1348                parent_pins,
1349                parent_root,
1350            ),
1351            (child_start, child_ops, child_proof, child_pins, child_root),
1352            (empty_start, empty_ops, empty_proof, empty_pins, empty_root),
1353        ] {
1354            let len = core::num::NonZeroU64::new(ops.len() as u64).unwrap();
1355            let end = Location::new(*start + ops.len() as u64);
1356            let (log_proof, log_ops) = db.historical_proof(end, start, len).await.unwrap();
1357            assert_eq!(log_ops, *ops);
1358            assert_eq!(log_proof, proof);
1359            assert!(verify_proof::<H, _, _>(&proof, start, &ops, &root));
1360            assert!(verify_proof_and_pinned_nodes::<H, _, _>(
1361                &proof, start, &ops, &pins, &root
1362            ));
1363        }
1364
1365        // Flushing the applied batch prunes the store to its peaks. The late batch's base is
1366        // mid-mountain, so its artifacts return `ElementPruned` rather than unverifiable data.
1367        let late = db
1368            .new_batch()
1369            .append(V::Value::make(5))
1370            .merkleize(&db, None, db.inactivity_floor_loc())
1371            .await
1372            .unwrap();
1373        let db = db.apply_batch(Arc::clone(&late)).await.unwrap();
1374        let db = db.commit().await.unwrap();
1375        assert!(matches!(
1376            late.proof(&db),
1377            Err(crate::qmdb::Error::Merkle(
1378                crate::merkle::Error::ElementPruned(_)
1379            ))
1380        ));
1381        assert!(matches!(
1382            late.pinned_nodes(&db),
1383            Err(crate::qmdb::Error::Merkle(
1384                crate::merkle::Error::ElementPruned(_)
1385            ))
1386        ));
1387
1388        // A batch built on the flushed store reads every node below it from the pinned peaks.
1389        let flushed = db
1390            .new_batch()
1391            .append(V::Value::make(6))
1392            .merkleize(&db, None, db.inactivity_floor_loc())
1393            .await
1394            .unwrap();
1395        let (flushed_start, flushed_ops) = flushed.operations();
1396        let flushed_root = flushed.root();
1397        let flushed_proof = flushed.proof(&db).unwrap();
1398        let flushed_pins = flushed.pinned_nodes(&db).unwrap();
1399        assert!(verify_proof_and_pinned_nodes::<H, _, _>(
1400            &flushed_proof,
1401            flushed_start,
1402            &flushed_ops,
1403            &flushed_pins,
1404            &flushed_root
1405        ));
1406        let applied_start = db.bounds().end;
1407        let db = db.apply_batch(flushed).await.unwrap();
1408        let flushed_range = applied_start..db.bounds().end;
1409        assert_eq!(flushed_start, flushed_range.start);
1410        assert_eq!(
1411            *flushed_start + flushed_ops.len() as u64,
1412            *flushed_range.end
1413        );
1414
1415        db.destroy().await.unwrap();
1416    }
1417
1418    #[boxed]
1419    pub(crate) async fn run_commit_after_sync_recovery<F: Family, V, C, H, S: Strategy>(
1420        context: deterministic::Context,
1421        db: TestKeyless<F, V, C, H, S>,
1422        reopen: Reopen<TestKeyless<F, V, C, H, S>>,
1423    ) where
1424        V: ValueEncoding<Value: TestValue>,
1425        C: Mutable<Item = Operation<F, V>>,
1426        H: Hasher,
1427        Operation<F, V>: EncodeShared,
1428    {
1429        let value0 = V::Value::make(10);
1430        let value1 = V::Value::make(20);
1431
1432        // Commit and sync the first append so recovery has an older durable boundary.
1433        let first_loc = Location::new(1);
1434        let merkleized = db
1435            .new_batch()
1436            .append(value0.clone())
1437            .merkleize(&db, None, db.inactivity_floor_loc())
1438            .await
1439            .unwrap();
1440        let db = db.apply_batch(merkleized).await.unwrap();
1441        let db = db.commit().await.unwrap();
1442        let db = db.sync().await.unwrap();
1443
1444        // Commit the next append without syncing; reopen must replay it from the journal.
1445        let second_loc = db.bounds().end;
1446        let merkleized = db
1447            .new_batch()
1448            .append(value1.clone())
1449            .merkleize(&db, None, db.inactivity_floor_loc())
1450            .await
1451            .unwrap();
1452        let db = db.apply_batch(merkleized).await.unwrap();
1453        let db = db.commit().await.unwrap();
1454        let committed_bounds = db.bounds();
1455        let committed_root = db.root();
1456        drop(db);
1457
1458        let db = reopen(context.child("db").with_attribute("index", 2)).await;
1459        assert_eq!(db.bounds(), committed_bounds);
1460        assert_eq!(db.root(), committed_root);
1461        assert_eq!(db.get(first_loc).await.unwrap(), Some(value0));
1462        assert_eq!(db.get(second_loc).await.unwrap(), Some(value1));
1463
1464        db.destroy().await.unwrap();
1465    }
1466
1467    #[boxed]
1468    pub(crate) async fn run_build_basic<F: Family, V, C, H, S: Strategy>(
1469        context: deterministic::Context,
1470        mut db: TestKeyless<F, V, C, H, S>,
1471        reopen: Reopen<TestKeyless<F, V, C, H, S>>,
1472    ) where
1473        V: ValueEncoding<Value: TestValue>,
1474        C: Mutable<Item = Operation<F, V>>,
1475        H: Hasher,
1476        Operation<F, V>: EncodeShared,
1477    {
1478        // Build a db with 2 values and make sure we can get them back.
1479        let v1 = V::Value::make(1);
1480        let v2 = V::Value::make(2);
1481
1482        {
1483            let batch = db.new_batch();
1484            let loc1 = batch.size();
1485            let batch = batch.append(v1.clone());
1486            let loc2 = batch.size();
1487            let batch = batch.append(v2.clone());
1488            assert_eq!(loc1, Location::new(1));
1489            assert_eq!(loc2, Location::new(2));
1490            let merkleized = batch
1491                .merkleize(&db, None, db.inactivity_floor_loc())
1492                .await
1493                .unwrap();
1494            db = db.apply_batch(merkleized).await.unwrap();
1495        }
1496
1497        // Make sure closing/reopening gets us back to the same state.
1498        assert_eq!(db.bounds().end, 4); // 2 appends, 1 commit + 1 initial commit
1499        assert_eq!(db.get_metadata().await.unwrap(), None);
1500        assert_eq!(db.get(Location::new(3)).await.unwrap(), None); // the commit op
1501        let root = db.root();
1502        db.sync().await.unwrap();
1503
1504        let db = reopen(context.child("db").with_attribute("index", 2)).await;
1505        assert_eq!(db.bounds().end, 4);
1506        assert_eq!(db.root(), root);
1507        assert_eq!(db.get(Location::new(1)).await.unwrap().unwrap(), v1);
1508        assert_eq!(db.get(Location::new(2)).await.unwrap().unwrap(), v2);
1509
1510        // Make sure commit operation remains after drop/reopen.
1511        drop(db);
1512        let db = reopen(context.child("db").with_attribute("index", 3)).await;
1513        assert_eq!(db.bounds().end, 4);
1514        assert_eq!(db.root(), root);
1515
1516        db.destroy().await.unwrap();
1517    }
1518
1519    #[boxed]
1520    pub(crate) async fn run_recovery<F: Family, V, C, H, S: Strategy>(
1521        context: deterministic::Context,
1522        db: TestKeyless<F, V, C, H, S>,
1523        reopen: Reopen<TestKeyless<F, V, C, H, S>>,
1524    ) where
1525        V: ValueEncoding<Value: TestValue>,
1526        C: Mutable<Item = Operation<F, V>>,
1527        H: Hasher,
1528        Operation<F, V>: EncodeShared,
1529    {
1530        let root = db.root();
1531        const ELEMENTS: u64 = 100;
1532
1533        // Create uncommitted appends then simulate failure.
1534        {
1535            let mut batch = db.new_batch();
1536            for i in 0..ELEMENTS {
1537                batch = batch.append(V::Value::make(i));
1538            }
1539
1540            // Don't merkleize/apply, to simulate a failed commit
1541        }
1542        drop(db);
1543        // Should rollback to the previous root.
1544        let mut db = reopen(context.child("db").with_attribute("index", 2)).await;
1545        assert_eq!(root, db.root());
1546
1547        // Apply the updates and commit them this time.
1548        {
1549            let mut batch = db.new_batch();
1550            for i in 0..ELEMENTS {
1551                batch = batch.append(V::Value::make(i + 100));
1552            }
1553            let merkleized = batch
1554                .merkleize(&db, None, db.inactivity_floor_loc())
1555                .await
1556                .unwrap();
1557            db = db.apply_batch(merkleized).await.unwrap();
1558        }
1559        let db = db.commit().await.unwrap();
1560        let root = db.root();
1561
1562        // Create uncommitted appends then simulate failure.
1563        {
1564            let mut batch = db.new_batch();
1565            for i in 0..ELEMENTS {
1566                batch = batch.append(V::Value::make(i + 200));
1567            }
1568
1569            // Don't merkleize/apply, to simulate a failed commit
1570        }
1571        drop(db);
1572        // Should rollback to the previous root.
1573        let mut db = reopen(context.child("db").with_attribute("index", 3)).await;
1574        assert_eq!(root, db.root());
1575
1576        // Apply the updates and commit them this time.
1577        {
1578            let mut batch = db.new_batch();
1579            for i in 0..ELEMENTS {
1580                batch = batch.append(V::Value::make(i + 300));
1581            }
1582            let merkleized = batch
1583                .merkleize(&db, None, db.inactivity_floor_loc())
1584                .await
1585                .unwrap();
1586            db = db.apply_batch(merkleized).await.unwrap();
1587        }
1588        let db = db.commit().await.unwrap();
1589        let root = db.root();
1590
1591        // Make sure we can reopen and get back to the same state.
1592        drop(db);
1593        let db = reopen(context.child("db").with_attribute("index", 4)).await;
1594        assert_eq!(db.bounds().end, 2 * ELEMENTS + 3);
1595        assert_eq!(db.root(), root);
1596
1597        db.destroy().await.unwrap();
1598    }
1599
1600    #[boxed]
1601    pub(crate) async fn run_proof<F: Family, V, C, S: Strategy>(
1602        mut db: TestKeyless<F, V, C, Sha256, S>,
1603    ) where
1604        V: ValueEncoding<Value: TestValue>,
1605        C: Mutable<Item = Operation<F, V>>,
1606        Operation<F, V>: EncodeShared + std::fmt::Debug,
1607    {
1608        const ELEMENTS: u64 = 50;
1609
1610        {
1611            let mut batch = db.new_batch();
1612            for i in 0..ELEMENTS {
1613                batch = batch.append(V::Value::make(i));
1614            }
1615            let merkleized = batch
1616                .merkleize(&db, None, db.inactivity_floor_loc())
1617                .await
1618                .unwrap();
1619            db = db.apply_batch(merkleized).await.unwrap();
1620        }
1621        let root = db.root();
1622
1623        let (proof, ops) = db.proof(Location::new(0), NZU64!(100)).await.unwrap();
1624        assert!(verify_proof::<Sha256, _, _>(
1625            &proof,
1626            Location::new(0),
1627            &ops,
1628            &root,
1629        ));
1630        assert_eq!(ops.len() as u64, 1 + ELEMENTS + 1);
1631
1632        let (proof, ops) = db.proof(Location::new(10), NZU64!(5)).await.unwrap();
1633        assert!(verify_proof::<Sha256, _, _>(
1634            &proof,
1635            Location::new(10),
1636            &ops,
1637            &root,
1638        ));
1639        assert_eq!(ops.len(), 5);
1640
1641        db.destroy().await.unwrap();
1642    }
1643
1644    #[boxed]
1645    pub(crate) async fn run_metadata<F: Family, V, C, H, S: Strategy>(
1646        db: TestKeyless<F, V, C, H, S>,
1647    ) where
1648        V: ValueEncoding<Value: TestValue>,
1649        C: Mutable<Item = Operation<F, V>>,
1650        H: Hasher,
1651        Operation<F, V>: EncodeShared,
1652    {
1653        let metadata = V::Value::make(99);
1654        let merkleized = db
1655            .new_batch()
1656            .append(V::Value::make(1))
1657            .merkleize(&db, Some(metadata.clone()), db.inactivity_floor_loc())
1658            .await
1659            .unwrap();
1660        let db = db.apply_batch(merkleized).await.unwrap();
1661        assert_eq!(db.get_metadata().await.unwrap(), Some(metadata));
1662
1663        let merkleized = db
1664            .new_batch()
1665            .merkleize(&db, None, db.inactivity_floor_loc())
1666            .await
1667            .unwrap();
1668        let db = db.apply_batch(merkleized).await.unwrap();
1669        assert_eq!(db.get_metadata().await.unwrap(), None);
1670
1671        db.destroy().await.unwrap();
1672    }
1673
1674    #[boxed]
1675    pub(crate) async fn run_pruning<F: Family, V, C, H, S: Strategy>(
1676        context: deterministic::Context,
1677        db: TestKeyless<F, V, C, H, S>,
1678        reopen: Reopen<TestKeyless<F, V, C, H, S>>,
1679    ) where
1680        V: ValueEncoding<Value: TestValue>,
1681        C: Mutable<Item = Operation<F, V>>,
1682        H: Hasher,
1683        Operation<F, V>: EncodeShared,
1684    {
1685        // Initial floor is 0, so pruning past 0 should fail.
1686        assert_eq!(db.inactivity_floor_loc(), Location::new(0));
1687        let result = db.prune(Location::new(1)).await;
1688        assert!(
1689            matches!(result, Err(Error::PruneBeyondMinRequired(prune_loc, floor))
1690                if prune_loc == Location::new(1) && floor == Location::new(0))
1691        );
1692
1693        let db = reopen(context.child("reopen_empty")).await;
1694
1695        // Add values and commit, advancing the floor to the new commit location.
1696        let first_commit_loc = Location::<F>::new(3);
1697        let merkleized = db
1698            .new_batch()
1699            .append(V::Value::make(1))
1700            .append(V::Value::make(2))
1701            .merkleize(&db, None, first_commit_loc)
1702            .await
1703            .unwrap();
1704        let db = db.apply_batch(merkleized).await.unwrap();
1705        assert_eq!(db.bounds().end - 1, first_commit_loc);
1706        assert_eq!(db.inactivity_floor_loc(), first_commit_loc);
1707
1708        // Append one more, advancing the floor with it.
1709        let second_commit_loc = Location::<F>::new(5);
1710        let merkleized = db
1711            .new_batch()
1712            .append(V::Value::make(3))
1713            .merkleize(&db, None, second_commit_loc)
1714            .await
1715            .unwrap();
1716        let db = db.apply_batch(merkleized).await.unwrap();
1717
1718        // Valid prune: up to the floor (previous commit location).
1719        let root = db.root();
1720        let db = db.prune(first_commit_loc).await.unwrap();
1721        assert_eq!(db.root(), root);
1722
1723        // Pruning beyond the current floor fails.
1724        let new_floor = db.inactivity_floor_loc();
1725        let beyond = new_floor + 1;
1726        let result = db.prune(beyond).await;
1727        assert!(
1728            matches!(result, Err(Error::PruneBeyondMinRequired(prune_loc, floor))
1729                if prune_loc == beyond && floor == new_floor)
1730        );
1731    }
1732
1733    #[boxed]
1734    pub(crate) async fn run_empty_db_recovery<F: Family, V, C, H, S: Strategy>(
1735        context: deterministic::Context,
1736        db: TestKeyless<F, V, C, H, S>,
1737        reopen: Reopen<TestKeyless<F, V, C, H, S>>,
1738    ) where
1739        V: ValueEncoding<Value: TestValue>,
1740        C: Mutable<Item = Operation<F, V>>,
1741        H: Hasher,
1742        Operation<F, V>: EncodeShared,
1743    {
1744        let root = db.root();
1745        const ELEMENTS: u64 = 200;
1746
1747        // Reopen DB without clean shutdown and make sure the state is the same.
1748        drop(db);
1749        let db = reopen(context.child("db").with_attribute("index", 2)).await;
1750        assert_eq!(db.bounds().end, 1); // initial commit should exist
1751        assert_eq!(db.root(), root);
1752
1753        // Simulate failure after inserting operations without a commit.
1754        {
1755            let mut batch = db.new_batch();
1756            for i in 0..ELEMENTS {
1757                batch = batch.append(V::Value::make(i));
1758            }
1759
1760            // Don't merkleize/apply, to simulate a failed commit
1761        }
1762        drop(db);
1763        let db = reopen(context.child("db").with_attribute("index", 3)).await;
1764        assert_eq!(db.bounds().end, 1); // initial commit should exist
1765        assert_eq!(db.root(), root);
1766
1767        // Repeat: simulate failure after inserting operations without a commit.
1768        {
1769            let mut batch = db.new_batch();
1770            for i in 0..ELEMENTS {
1771                batch = batch.append(V::Value::make(i + 500));
1772            }
1773
1774            // Don't merkleize/apply, to simulate a failed commit
1775        }
1776        drop(db);
1777        let db = reopen(context.child("db").with_attribute("index", 4)).await;
1778        assert_eq!(db.bounds().end, 1); // initial commit should exist
1779        assert_eq!(db.root(), root);
1780
1781        // One last check: multiple batches of uncommitted appends.
1782        {
1783            let mut batch = db.new_batch();
1784            for i in 0..ELEMENTS * 3 {
1785                batch = batch.append(V::Value::make(i + 1000));
1786            }
1787
1788            // Don't merkleize/apply, to simulate a failed commit
1789        }
1790        drop(db);
1791        let mut db = reopen(context.child("db").with_attribute("index", 5)).await;
1792        assert_eq!(db.bounds().end, 1); // initial commit should exist
1793        assert_eq!(db.root(), root);
1794        assert_eq!(db.bounds().end - 1, Location::new(0));
1795
1796        // Apply the ops one last time but fully commit them this time, then clean up.
1797        {
1798            let mut batch = db.new_batch();
1799            for i in 0..ELEMENTS {
1800                batch = batch.append(V::Value::make(i + 2000));
1801            }
1802            let merkleized = batch
1803                .merkleize(&db, None, db.inactivity_floor_loc())
1804                .await
1805                .unwrap();
1806            db = db.apply_batch(merkleized).await.unwrap();
1807        }
1808        db.commit().await.unwrap();
1809        let db = reopen(context.child("db").with_attribute("index", 6)).await;
1810        assert!(db.bounds().end > 1);
1811        assert_ne!(db.root(), root);
1812
1813        db.destroy().await.unwrap();
1814    }
1815
1816    #[boxed]
1817    pub(crate) async fn run_replay_with_trailing_appends<F: Family, V, C, H, S: Strategy>(
1818        context: deterministic::Context,
1819        mut db: TestKeyless<F, V, C, H, S>,
1820        reopen: Reopen<TestKeyless<F, V, C, H, S>>,
1821    ) where
1822        V: ValueEncoding<Value: TestValue>,
1823        C: Mutable<Item = Operation<F, V>>,
1824        H: Hasher,
1825        Operation<F, V>: EncodeShared,
1826    {
1827        // Add some initial operations and commit.
1828        {
1829            let mut batch = db.new_batch();
1830            for i in 0..10u64 {
1831                batch = batch.append(V::Value::make(i));
1832            }
1833            let merkleized = batch
1834                .merkleize(&db, None, db.inactivity_floor_loc())
1835                .await
1836                .unwrap();
1837            db = db.apply_batch(merkleized).await.unwrap();
1838        }
1839        let db = db.commit().await.unwrap();
1840        let committed_root = db.root();
1841        let committed_size = db.bounds().end;
1842
1843        // Add exactly one more append (uncommitted).
1844        {
1845            db.new_batch().append(V::Value::make(99));
1846
1847            // Don't merkleize/apply, to simulate a failed commit
1848        }
1849        drop(db);
1850
1851        // Reopen and verify correct recovery.
1852        let mut db = reopen(context.child("db").with_attribute("index", 2)).await;
1853        assert_eq!(
1854            db.bounds().end,
1855            committed_size,
1856            "Should recover to last commit"
1857        );
1858        assert_eq!(db.root(), committed_root, "Root should match last commit");
1859        assert_eq!(
1860            db.bounds().end - 1,
1861            committed_size - 1,
1862            "Last commit location should be correct"
1863        );
1864
1865        // Verify we can append and commit new data after recovery.
1866        let new_value = V::Value::make(77);
1867        {
1868            let batch = db.new_batch();
1869            let loc = batch.size();
1870            let batch = batch.append(new_value.clone());
1871            assert_eq!(
1872                loc, committed_size,
1873                "New append should get the expected location"
1874            );
1875            let merkleized = batch
1876                .merkleize(&db, None, db.inactivity_floor_loc())
1877                .await
1878                .unwrap();
1879            db = db.apply_batch(merkleized).await.unwrap();
1880        }
1881        let db = db.commit().await.unwrap();
1882
1883        assert_eq!(db.get(committed_size).await.unwrap(), Some(new_value));
1884
1885        let new_committed_root = db.root();
1886        let new_committed_size = db.bounds().end;
1887
1888        // Add multiple uncommitted appends.
1889        {
1890            let mut batch = db.new_batch();
1891            for i in 0..5u64 {
1892                batch = batch.append(V::Value::make(200 + i));
1893            }
1894
1895            // Don't merkleize/apply, to simulate a failed commit
1896        }
1897        drop(db);
1898
1899        // Reopen and verify correct recovery.
1900        let db = reopen(context.child("db").with_attribute("index", 3)).await;
1901        assert_eq!(
1902            db.bounds().end,
1903            new_committed_size,
1904            "Should recover to last commit with multiple trailing appends"
1905        );
1906        assert_eq!(
1907            db.root(),
1908            new_committed_root,
1909            "Root should match last commit after multiple appends"
1910        );
1911        assert_eq!(
1912            db.bounds().end - 1,
1913            new_committed_size - 1,
1914            "Last commit location should be correct after multiple appends"
1915        );
1916
1917        db.destroy().await.unwrap();
1918    }
1919
1920    /// `get_many` on the DB and on unmerkleized/merkleized batches returns
1921    /// results consistent with individual `get` calls, and rejects locations that
1922    /// are not strictly increasing or out of bounds.
1923    #[boxed]
1924    pub(crate) async fn run_get_many<F: Family, V, C, S: Strategy>(
1925        db: TestKeyless<F, V, C, Sha256, S>,
1926    ) where
1927        V: ValueEncoding<Value: TestValue>,
1928        C: Mutable<Item = Operation<F, V>>,
1929        Operation<F, V>: EncodeShared,
1930    {
1931        let v1 = V::Value::make(1);
1932        let v2 = V::Value::make(2);
1933        let v3 = V::Value::make(3);
1934
1935        // Commit v1 and v2 to disk.
1936        let batch = db.new_batch();
1937        let loc1 = batch.size();
1938        let batch = batch.append(v1.clone());
1939        let loc2 = batch.size();
1940        let batch = batch.append(v2.clone());
1941        let merkleized = batch
1942            .merkleize(&db, None, db.inactivity_floor_loc())
1943            .await
1944            .unwrap();
1945        let db = db.apply_batch(merkleized).await.unwrap();
1946        let db = db.commit().await.unwrap();
1947
1948        // DB-level get_many.
1949        let results = db.get_many(&[loc1, loc2]).await.unwrap();
1950        assert_eq!(results, vec![Some(v1.clone()), Some(v2.clone())]);
1951
1952        // Empty input.
1953        let results = db.get_many(&[]).await.unwrap();
1954        assert!(results.is_empty());
1955
1956        // Decreasing and repeated locations are rejected.
1957        assert!(matches!(
1958            db.get_many(&[loc2, loc1]).await,
1959            Err(Error::LocationOutOfOrder(first, second)) if first == loc2 && second == loc1
1960        ));
1961        assert!(matches!(
1962            db.get_many(&[loc1, loc1]).await,
1963            Err(Error::LocationOutOfOrder(..))
1964        ));
1965
1966        // An out-of-bounds location is rejected even when it follows valid ones.
1967        let end = db.bounds().end;
1968        assert!(matches!(
1969            db.get_many(&[loc1, end]).await,
1970            Err(Error::LocationOutOfBounds(loc, bound)) if loc == end && bound == end
1971        ));
1972
1973        // Unmerkleized batch: pending appends + DB fallthrough.
1974        let batch = db.new_batch();
1975        let loc3 = batch.size();
1976        let batch = batch.append(v3.clone());
1977        let results = batch.get_many(&[loc1, loc3], &db).await.unwrap();
1978        assert_eq!(results, vec![Some(v1.clone()), Some(v3.clone())]);
1979        assert!(matches!(
1980            batch.get_many(&[loc3, loc1], &db).await,
1981            Err(Error::LocationOutOfOrder(..))
1982        ));
1983        let end = batch.size();
1984        assert!(matches!(
1985            batch.get_many(&[loc1, end], &db).await,
1986            Err(Error::LocationOutOfBounds(loc, bound)) if loc == end && bound == end
1987        ));
1988
1989        // Merkleized batch: parent chain + DB fallthrough.
1990        let parent = db.new_batch();
1991        let parent_loc = parent.size();
1992        let parent = parent
1993            .append(v3.clone())
1994            .merkleize(&db, None, db.inactivity_floor_loc())
1995            .await
1996            .unwrap();
1997        let results = parent.get_many(&[loc1, parent_loc], &db).await.unwrap();
1998        assert_eq!(results, vec![Some(v1.clone()), Some(v3.clone())]);
1999        assert!(matches!(
2000            parent.get_many(&[parent_loc, loc1], &db).await,
2001            Err(Error::LocationOutOfOrder(..))
2002        ));
2003        let end = parent.size();
2004        assert!(matches!(
2005            parent.get_many(&[loc1, end], &db).await,
2006            Err(Error::LocationOutOfBounds(loc, bound)) if loc == end && bound == end
2007        ));
2008        let child = parent.new_batch::<Sha256>().append(V::Value::make(4));
2009        let results = child.get_many(&[loc1, loc2], &db).await.unwrap();
2010        assert_eq!(results, vec![Some(v1.clone()), Some(v2.clone())]);
2011        assert!(matches!(
2012            child.get_many(&[loc2, loc1], &db).await,
2013            Err(Error::LocationOutOfOrder(..))
2014        ));
2015
2016        db.destroy().await.unwrap();
2017    }
2018
2019    #[boxed]
2020    pub(crate) async fn run_batch_chained<F: Family, V, C, S: Strategy>(
2021        db: TestKeyless<F, V, C, Sha256, S>,
2022    ) where
2023        V: ValueEncoding<Value: TestValue>,
2024        C: Mutable<Item = Operation<F, V>>,
2025        Operation<F, V>: EncodeShared,
2026    {
2027        let v1 = V::Value::make(10);
2028        let v2 = V::Value::make(20);
2029        let v3 = V::Value::make(30);
2030
2031        let parent = db.new_batch();
2032        let loc1 = parent.size();
2033        let parent = parent.append(v1.clone());
2034        let parent_m = parent
2035            .merkleize(&db, None, db.inactivity_floor_loc())
2036            .await
2037            .unwrap();
2038
2039        let child = parent_m.new_batch::<Sha256>();
2040        let loc2 = child.size();
2041        let child = child.append(v2.clone());
2042        let loc3 = child.size();
2043        let child = child.append(v3.clone());
2044        let child_m = child
2045            .merkleize(&db, None, db.inactivity_floor_loc())
2046            .await
2047            .unwrap();
2048        let child_root = child_m.root();
2049
2050        let db = db.apply_batch(child_m).await.unwrap();
2051        let db = db.commit().await.unwrap();
2052
2053        assert_eq!(db.root(), child_root);
2054        assert_eq!(db.get(loc1).await.unwrap(), Some(v1));
2055        assert_eq!(db.get(loc2).await.unwrap(), Some(v2));
2056        assert_eq!(db.get(loc3).await.unwrap(), Some(v3));
2057
2058        db.destroy().await.unwrap();
2059    }
2060
2061    #[boxed]
2062    pub(crate) async fn run_stale_batch<F: Family, V, C, H, S: Strategy>(
2063        context: deterministic::Context,
2064        db: TestKeyless<F, V, C, H, S>,
2065        reopen: Reopen<TestKeyless<F, V, C, H, S>>,
2066    ) where
2067        V: ValueEncoding<Value: TestValue>,
2068        C: Mutable<Item = Operation<F, V>>,
2069        H: Hasher,
2070        Operation<F, V>: EncodeShared,
2071    {
2072        let batch_a = db
2073            .new_batch()
2074            .append(V::Value::make(10))
2075            .merkleize(&db, None, db.inactivity_floor_loc())
2076            .await
2077            .unwrap();
2078        let batch_b = db
2079            .new_batch()
2080            .append(V::Value::make(20))
2081            .merkleize(&db, None, db.inactivity_floor_loc())
2082            .await
2083            .unwrap();
2084
2085        let db = db.apply_batch(batch_a).await.unwrap();
2086        let db = db.commit().await.unwrap();
2087        let root = db.root();
2088        let last_commit_loc = db.bounds().end - 1;
2089
2090        let result = db.apply_batch(batch_b).await;
2091        assert!(matches!(result, Err(Error::StaleBatch)));
2092
2093        // The rejection mutated nothing: reopening recovers the committed state.
2094        let db = reopen(context.child("reopen")).await;
2095        assert_eq!(db.root(), root);
2096        assert_eq!(db.bounds().end - 1, last_commit_loc);
2097        db.destroy().await.unwrap();
2098    }
2099
2100    #[boxed]
2101    pub(crate) async fn run_partial_ancestor_commit<F: Family, V, C, H, S: Strategy>(
2102        db: TestKeyless<F, V, C, H, S>,
2103    ) where
2104        V: ValueEncoding<Value: TestValue>,
2105        C: Mutable<Item = Operation<F, V>>,
2106        H: Hasher,
2107        Operation<F, V>: EncodeShared,
2108    {
2109        // Chain: DB <- A <- B <- C
2110        let a = db
2111            .new_batch()
2112            .append(V::Value::make(10))
2113            .merkleize(&db, None, db.inactivity_floor_loc())
2114            .await
2115            .unwrap();
2116        let b = a
2117            .new_batch::<H>()
2118            .append(V::Value::make(20))
2119            .merkleize(&db, None, db.inactivity_floor_loc())
2120            .await
2121            .unwrap();
2122        let c = b
2123            .new_batch::<H>()
2124            .append(V::Value::make(30))
2125            .merkleize(&db, None, db.inactivity_floor_loc())
2126            .await
2127            .unwrap();
2128
2129        let expected_root = c.root();
2130
2131        // Apply only A, then apply C directly (B's items applied via ancestor batches).
2132        let db = db.apply_batch(a).await.unwrap();
2133        let db = db.apply_batch(c).await.unwrap();
2134
2135        // Root must match what the full chain produces.
2136        assert_eq!(db.root(), expected_root);
2137
2138        db.destroy().await.unwrap();
2139    }
2140
2141    #[boxed]
2142    pub(crate) async fn run_delayed_merkleize_after_ancestor_apply<
2143        F: Family,
2144        V,
2145        C,
2146        H,
2147        S: Strategy,
2148    >(
2149        db: TestKeyless<F, V, C, H, S>,
2150    ) where
2151        V: ValueEncoding<Value: TestValue>,
2152        C: Mutable<Item = Operation<F, V>>,
2153        H: Hasher,
2154        Operation<F, V>: EncodeShared,
2155    {
2156        let floor = db.inactivity_floor_loc();
2157        let a = db
2158            .new_batch()
2159            .append(V::Value::make(10))
2160            .merkleize(&db, None, floor)
2161            .await
2162            .unwrap();
2163        let b = a
2164            .new_batch::<H>()
2165            .append(V::Value::make(20))
2166            .merkleize(&db, None, floor)
2167            .await
2168            .unwrap();
2169        let c = b.new_batch::<H>().append(V::Value::make(30));
2170
2171        let db = db.apply_batch(a).await.unwrap();
2172        let c = c.merkleize(&db, None, floor).await.unwrap();
2173        let expected_root = c.root();
2174        let db = db.apply_batch(c).await.unwrap();
2175
2176        assert_eq!(db.root(), expected_root);
2177        db.destroy().await.unwrap();
2178    }
2179
2180    #[boxed]
2181    pub(crate) async fn run_to_batch<F: Family, V, C, S: Strategy>(
2182        db: TestKeyless<F, V, C, Sha256, S>,
2183    ) where
2184        V: ValueEncoding<Value: TestValue>,
2185        C: Mutable<Item = Operation<F, V>>,
2186        Operation<F, V>: EncodeShared,
2187    {
2188        let batch = db.new_batch();
2189        let loc1 = batch.size();
2190        let batch = batch.append(V::Value::make(10));
2191        let merkleized = batch
2192            .merkleize(&db, None, db.inactivity_floor_loc())
2193            .await
2194            .unwrap();
2195        let db = db.apply_batch(merkleized).await.unwrap();
2196
2197        let snapshot = db.to_batch();
2198        assert_eq!(snapshot.root(), db.root());
2199
2200        let child_batch = snapshot.new_batch::<Sha256>();
2201        let loc2 = child_batch.size();
2202        let child_batch = child_batch.append(V::Value::make(20));
2203        let merkleized = child_batch
2204            .merkleize(&db, None, db.inactivity_floor_loc())
2205            .await
2206            .unwrap();
2207        let db = db.apply_batch(merkleized).await.unwrap();
2208
2209        assert_eq!(db.get(loc1).await.unwrap(), Some(V::Value::make(10)));
2210        assert_eq!(db.get(loc2).await.unwrap(), Some(V::Value::make(20)));
2211
2212        db.destroy().await.unwrap();
2213    }
2214
2215    #[boxed]
2216    pub(crate) async fn run_non_empty_recovery<F: Family, V, C, H, S: Strategy>(
2217        context: deterministic::Context,
2218        mut db: TestKeyless<F, V, C, H, S>,
2219        reopen: Reopen<TestKeyless<F, V, C, H, S>>,
2220    ) where
2221        V: ValueEncoding<Value: TestValue>,
2222        C: Mutable<Item = Operation<F, V>>,
2223        H: Hasher,
2224        Operation<F, V>: EncodeShared,
2225    {
2226        // Append many values then commit, advancing the floor to the new commit so we can
2227        // later prune up to it.
2228        const ELEMENTS: u64 = 200;
2229        {
2230            let mut batch = db.new_batch();
2231            for i in 0..ELEMENTS {
2232                batch = batch.append(V::Value::make(i));
2233            }
2234            let new_commit = db.bounds().end + ELEMENTS;
2235            let merkleized = batch.merkleize(&db, None, new_commit).await.unwrap();
2236            db = db.apply_batch(merkleized).await.unwrap();
2237        }
2238        let db = db.commit().await.unwrap();
2239        let root = db.root();
2240        let op_count = db.bounds().end;
2241
2242        // Reopen DB without clean shutdown and make sure the state is the same.
2243        drop(db);
2244        let db = reopen(context.child("db").with_attribute("index", 2)).await;
2245        assert_eq!(db.bounds().end, op_count);
2246        assert_eq!(db.root(), root);
2247        assert_eq!(db.bounds().end - 1, op_count - 1);
2248        drop(db);
2249
2250        // Insert many operations without commit, then simulate failure.
2251        let db = reopen(context.child("recovery_a")).await;
2252        {
2253            let mut batch = db.new_batch();
2254            for i in 0..ELEMENTS {
2255                batch = batch.append(V::Value::make(i + 1000));
2256            }
2257
2258            // Don't merkleize/apply, to simulate a failed commit
2259        }
2260        drop(db);
2261        let db = reopen(context.child("recovery_b")).await;
2262        assert_eq!(db.bounds().end, op_count);
2263        assert_eq!(db.root(), root);
2264        drop(db);
2265
2266        // Repeat after pruning to the last commit.
2267        let db = reopen(context.child("db").with_attribute("index", 3)).await;
2268        let last_commit = db.bounds().end - 1;
2269        let db = db.prune(last_commit).await.unwrap();
2270        assert_eq!(db.bounds().end, op_count);
2271        assert_eq!(db.root(), root);
2272        db.sync().await.unwrap();
2273
2274        let db = reopen(context.child("recovery_c")).await;
2275        {
2276            let mut batch = db.new_batch();
2277            for i in 0..ELEMENTS {
2278                batch = batch.append(V::Value::make(i + 2000));
2279            }
2280        }
2281        drop(db);
2282        let db = reopen(context.child("recovery_d")).await;
2283        assert_eq!(db.bounds().end, op_count);
2284        assert_eq!(db.root(), root);
2285        drop(db);
2286
2287        // Apply the ops one last time but fully commit them this time, then clean up.
2288        let mut db = reopen(context.child("db").with_attribute("index", 4)).await;
2289        {
2290            let mut batch = db.new_batch();
2291            for i in 0..ELEMENTS {
2292                batch = batch.append(V::Value::make(i + 3000));
2293            }
2294            let merkleized = batch
2295                .merkleize(&db, None, db.inactivity_floor_loc())
2296                .await
2297                .unwrap();
2298            db = db.apply_batch(merkleized).await.unwrap();
2299        }
2300        db.commit().await.unwrap();
2301        let db = reopen(context.child("db").with_attribute("index", 5)).await;
2302        let bounds = db.bounds();
2303        assert!(bounds.end > op_count);
2304        assert_ne!(db.root(), root);
2305
2306        db.destroy().await.unwrap();
2307    }
2308
2309    #[boxed]
2310    pub(crate) async fn run_proof_comprehensive<F: Family, V, C, S: Strategy>(
2311        mut db: TestKeyless<F, V, C, Sha256, S>,
2312    ) where
2313        V: ValueEncoding<Value: TestValue>,
2314        C: Mutable<Item = Operation<F, V>>,
2315        Operation<F, V>: EncodeShared + std::fmt::Debug,
2316    {
2317        // Build a db with some values.
2318        const ELEMENTS: u64 = 100;
2319        {
2320            let mut batch = db.new_batch();
2321            for i in 0u64..ELEMENTS {
2322                batch = batch.append(V::Value::make(i));
2323            }
2324            let merkleized = batch
2325                .merkleize(&db, None, db.inactivity_floor_loc())
2326                .await
2327                .unwrap();
2328            db = db.apply_batch(merkleized).await.unwrap();
2329        }
2330
2331        // Test that historical proof fails with op_count > number of operations.
2332        assert!(matches!(
2333            db.historical_proof(db.bounds().end + 1, Location::new(5), NZU64!(10))
2334                .await,
2335            Err(Error::<F>::Merkle(crate::merkle::Error::RangeOutOfBounds(
2336                _
2337            )))
2338        ));
2339
2340        let root = db.root();
2341
2342        for (start_loc, max_ops) in [
2343            (0, 10),
2344            (10, 5),
2345            (50, 20),
2346            (90, 15),
2347            (0, 1),
2348            (ELEMENTS - 1, 1),
2349            (ELEMENTS, 1),
2350        ] {
2351            let (proof, ops) = db
2352                .proof(Location::new(start_loc), NZU64!(max_ops))
2353                .await
2354                .unwrap();
2355            assert!(
2356                verify_proof::<Sha256, _, _>(&proof, Location::new(start_loc), &ops, &root,),
2357                "Failed to verify proof for range starting at {start_loc} with max {max_ops} ops",
2358            );
2359            let expected_ops = std::cmp::min(max_ops, *db.bounds().end - start_loc);
2360            assert_eq!(ops.len() as u64, expected_ops);
2361
2362            let wrong_root = Sha256::hash(&[&[0xFF; 32]]);
2363            assert!(!verify_proof::<Sha256, _, _>(
2364                &proof,
2365                Location::new(start_loc),
2366                &ops,
2367                &wrong_root,
2368            ));
2369            if start_loc > 0 {
2370                assert!(!verify_proof::<Sha256, _, _>(
2371                    &proof,
2372                    Location::new(start_loc - 1),
2373                    &ops,
2374                    &root,
2375                ));
2376            }
2377        }
2378
2379        db.destroy().await.unwrap();
2380    }
2381
2382    #[boxed]
2383    pub(crate) async fn run_proof_with_pruning<F: Family, V, C, S: Strategy>(
2384        context: deterministic::Context,
2385        mut db: TestKeyless<F, V, C, Sha256, S>,
2386        reopen: Reopen<TestKeyless<F, V, C, Sha256, S>>,
2387    ) where
2388        V: ValueEncoding<Value: TestValue>,
2389        C: Mutable<Item = Operation<F, V>>,
2390        Operation<F, V>: EncodeShared + std::fmt::Debug,
2391    {
2392        const ELEMENTS: u64 = 100;
2393        {
2394            let mut batch = db.new_batch();
2395            for i in 0u64..ELEMENTS {
2396                batch = batch.append(V::Value::make(i));
2397            }
2398            let new_commit = db.bounds().end + ELEMENTS;
2399            let merkleized = batch.merkleize(&db, None, new_commit).await.unwrap();
2400            db = db.apply_batch(merkleized).await.unwrap();
2401        }
2402
2403        {
2404            let mut batch = db.new_batch();
2405            for i in ELEMENTS..ELEMENTS * 2 {
2406                batch = batch.append(V::Value::make(i));
2407            }
2408            let new_commit = db.bounds().end + ELEMENTS;
2409            let merkleized = batch.merkleize(&db, None, new_commit).await.unwrap();
2410            db = db.apply_batch(merkleized).await.unwrap();
2411        }
2412        let root = db.root();
2413
2414        const PRUNE_LOC: u64 = 30;
2415        let db = db.prune(Location::new(PRUNE_LOC)).await.unwrap();
2416        let oldest_retained = db.bounds().start;
2417        assert_eq!(db.root(), root);
2418
2419        db.sync().await.unwrap();
2420        let db = reopen(context).await;
2421        assert_eq!(db.root(), root);
2422
2423        for (start_loc, max_ops) in [
2424            (oldest_retained, 10),
2425            (Location::new(50), 20),
2426            (Location::new(150), 10),
2427            (Location::new(190), 15),
2428        ] {
2429            if start_loc < oldest_retained {
2430                continue;
2431            }
2432            let (proof, ops) = db.proof(start_loc, NZU64!(max_ops)).await.unwrap();
2433            assert!(verify_proof::<Sha256, _, _>(&proof, start_loc, &ops, &root,));
2434        }
2435
2436        let aggressive_prune: Location<F> = Location::new(150);
2437        let db = db.prune(aggressive_prune).await.unwrap();
2438
2439        let new_oldest = db.bounds().start;
2440        let (proof, ops) = db.proof(new_oldest, NZU64!(20)).await.unwrap();
2441        assert!(verify_proof::<Sha256, _, _>(
2442            &proof, new_oldest, &ops, &root,
2443        ));
2444
2445        let almost_all = db.bounds().end - 5;
2446        let db = db.prune(almost_all).await.unwrap();
2447        let final_oldest = db.bounds().start;
2448        if final_oldest < db.bounds().end {
2449            let (final_proof, final_ops) = db.proof(final_oldest, NZU64!(10)).await.unwrap();
2450            assert!(verify_proof::<Sha256, _, _>(
2451                &final_proof,
2452                final_oldest,
2453                &final_ops,
2454                &root,
2455            ));
2456        }
2457
2458        db.destroy().await.unwrap();
2459    }
2460
2461    #[boxed]
2462    pub(crate) async fn run_get_out_of_bounds<F: Family, V, C, H, S: Strategy>(
2463        db: TestKeyless<F, V, C, H, S>,
2464    ) where
2465        V: ValueEncoding<Value: TestValue>,
2466        C: Mutable<Item = Operation<F, V>>,
2467        H: Hasher,
2468        Operation<F, V>: EncodeShared,
2469    {
2470        assert!(db.get(Location::new(0)).await.unwrap().is_none());
2471
2472        let merkleized = db
2473            .new_batch()
2474            .append(V::Value::make(1))
2475            .append(V::Value::make(2))
2476            .merkleize(&db, None, db.inactivity_floor_loc())
2477            .await
2478            .unwrap();
2479        let db = db.apply_batch(merkleized).await.unwrap();
2480
2481        assert_eq!(
2482            db.get(Location::new(1)).await.unwrap(),
2483            Some(V::Value::make(1))
2484        );
2485        assert!(db.get(Location::new(3)).await.unwrap().is_none());
2486        assert!(matches!(
2487            db.get(Location::new(4)).await,
2488            Err(Error::LocationOutOfBounds(loc, size))
2489                if loc == Location::new(4) && size == Location::new(4)
2490        ));
2491
2492        db.destroy().await.unwrap();
2493    }
2494
2495    #[boxed]
2496    pub(crate) async fn run_get_pruned<F: Family, V, C, H, S: Strategy>(
2497        mut db: TestKeyless<F, V, C, H, S>,
2498    ) where
2499        V: ValueEncoding<Value: TestValue>,
2500        C: Mutable<Item = Operation<F, V>>,
2501        H: Hasher,
2502        Operation<F, V>: EncodeShared,
2503    {
2504        let mut round = 0u64;
2505        while db.bounds().start == Location::new(0) {
2506            round += 1;
2507            assert!(round <= 64, "failed to prune any history");
2508            db = commit_appends(db, (0..16).map(|i| V::Value::make(round * 100 + i)), None).await;
2509            let last_commit = db.bounds().end - 1;
2510            db = db.prune(last_commit).await.unwrap();
2511        }
2512
2513        let start = db.bounds().start;
2514        let pruned = start - 1;
2515        assert!(matches!(
2516            db.get(pruned).await,
2517            Err(Error::Journal(crate::journal::Error::ItemPruned(loc))) if loc == *pruned
2518        ));
2519        assert!(matches!(
2520            db.get_many(&[pruned, start]).await,
2521            Err(Error::Journal(crate::journal::Error::ItemPruned(loc))) if loc == *pruned
2522        ));
2523        db.get(start).await.unwrap();
2524        db.get_many(&[start]).await.unwrap();
2525
2526        db.destroy().await.unwrap();
2527    }
2528
2529    #[boxed]
2530    pub(crate) async fn run_batch_get<F: Family, V, C, H, S: Strategy>(
2531        mut db: TestKeyless<F, V, C, H, S>,
2532    ) where
2533        V: ValueEncoding<Value: TestValue>,
2534        C: Mutable<Item = Operation<F, V>>,
2535        H: Hasher,
2536        Operation<F, V>: EncodeShared,
2537    {
2538        let base_vals: Vec<V::Value> = (0..3).map(|i| V::Value::make(10 + i)).collect();
2539        let mut base_locs = Vec::new();
2540        {
2541            let mut batch = db.new_batch();
2542            for v in &base_vals {
2543                let loc = batch.size();
2544                batch = batch.append(v.clone());
2545                base_locs.push(loc);
2546            }
2547            let merkleized = batch
2548                .merkleize(&db, None, db.inactivity_floor_loc())
2549                .await
2550                .unwrap();
2551            db = db.apply_batch(merkleized).await.unwrap();
2552        }
2553
2554        let batch = db.new_batch();
2555        for (i, loc) in base_locs.iter().enumerate() {
2556            assert_eq!(
2557                batch.get(*loc, &db).await.unwrap(),
2558                Some(base_vals[i].clone()),
2559            );
2560        }
2561
2562        let new_val = V::Value::make(99);
2563        let new_loc = batch.size();
2564        let batch = batch.append(new_val.clone());
2565        assert_eq!(batch.get(new_loc, &db).await.unwrap(), Some(new_val));
2566        let end = new_loc + 1;
2567        assert!(matches!(
2568            batch.get(end, &db).await,
2569            Err(Error::LocationOutOfBounds(loc, bound)) if loc == end && bound == end
2570        ));
2571        assert!(matches!(
2572            batch.get_many(&[base_locs[0], end], &db).await,
2573            Err(Error::LocationOutOfBounds(loc, bound)) if loc == end && bound == end
2574        ));
2575
2576        db.destroy().await.unwrap();
2577    }
2578
2579    #[boxed]
2580    pub(crate) async fn run_batch_stacked_get<F: Family, V, C, S: Strategy>(
2581        db: TestKeyless<F, V, C, Sha256, S>,
2582    ) where
2583        V: ValueEncoding<Value: TestValue>,
2584        C: Mutable<Item = Operation<F, V>>,
2585        Operation<F, V>: EncodeShared,
2586    {
2587        let v1 = V::Value::make(1);
2588        let v2 = V::Value::make(2);
2589
2590        let parent = db.new_batch();
2591        let loc1 = parent.size();
2592        let parent = parent.append(v1.clone());
2593        let parent_m = parent
2594            .merkleize(&db, None, db.inactivity_floor_loc())
2595            .await
2596            .unwrap();
2597
2598        let child = parent_m.new_batch::<Sha256>();
2599        assert_eq!(child.get(loc1, &db).await.unwrap(), Some(v1));
2600
2601        let loc2 = child.size();
2602        let child = child.append(v2.clone());
2603        assert_eq!(child.get(loc2, &db).await.unwrap(), Some(v2));
2604        assert!(matches!(
2605            child.get(Location::new(9999), &db).await,
2606            Err(Error::LocationOutOfBounds(loc, bound))
2607                if loc == Location::new(9999) && bound == loc2 + 1
2608        ));
2609
2610        db.destroy().await.unwrap();
2611    }
2612
2613    #[boxed]
2614    pub(crate) async fn run_batch_speculative_root<F: Family, V, C, H, S: Strategy>(
2615        db: TestKeyless<F, V, C, H, S>,
2616    ) where
2617        V: ValueEncoding<Value: TestValue>,
2618        C: Mutable<Item = Operation<F, V>>,
2619        H: Hasher,
2620        Operation<F, V>: EncodeShared,
2621    {
2622        let mut batch = db.new_batch();
2623        for i in 0u64..10 {
2624            batch = batch.append(V::Value::make(i));
2625        }
2626        let merkleized = batch
2627            .merkleize(&db, None, db.inactivity_floor_loc())
2628            .await
2629            .unwrap();
2630        let speculative = merkleized.root();
2631        let db = db.apply_batch(merkleized).await.unwrap();
2632        assert_eq!(db.root(), speculative);
2633
2634        let merkleized = db
2635            .new_batch()
2636            .append(V::Value::make(100))
2637            .merkleize(&db, Some(V::Value::make(55)), db.inactivity_floor_loc())
2638            .await
2639            .unwrap();
2640        let speculative = merkleized.root();
2641        let db = db.apply_batch(merkleized).await.unwrap();
2642        assert_eq!(db.root(), speculative);
2643
2644        db.destroy().await.unwrap();
2645    }
2646
2647    #[boxed]
2648    pub(crate) async fn run_merkleized_batch_get<F: Family, V, C, S: Strategy>(
2649        db: TestKeyless<F, V, C, Sha256, S>,
2650    ) where
2651        V: ValueEncoding<Value: TestValue>,
2652        C: Mutable<Item = Operation<F, V>>,
2653        Operation<F, V>: EncodeShared,
2654    {
2655        let base_val = V::Value::make(10);
2656        let merkleized = db
2657            .new_batch()
2658            .append(base_val.clone())
2659            .merkleize(&db, None, db.inactivity_floor_loc())
2660            .await
2661            .unwrap();
2662        let db = db.apply_batch(merkleized).await.unwrap();
2663
2664        let new_val = V::Value::make(20);
2665        let merkleized = db
2666            .new_batch()
2667            .append(new_val.clone())
2668            .merkleize(&db, None, db.inactivity_floor_loc())
2669            .await
2670            .unwrap();
2671
2672        assert_eq!(
2673            merkleized.get(Location::new(1), &db).await.unwrap(),
2674            Some(base_val),
2675        );
2676        assert_eq!(
2677            merkleized.get(Location::new(3), &db).await.unwrap(),
2678            Some(new_val),
2679        );
2680        assert_eq!(merkleized.get(Location::new(4), &db).await.unwrap(), None);
2681        assert!(matches!(
2682            merkleized.get(Location::new(5), &db).await,
2683            Err(Error::LocationOutOfBounds(loc, bound))
2684                if loc == Location::new(5) && bound == Location::new(5)
2685        ));
2686
2687        db.destroy().await.unwrap();
2688    }
2689
2690    #[boxed]
2691    pub(crate) async fn run_batch_chained_apply_sequential<F: Family, V, C, H, S: Strategy>(
2692        db: TestKeyless<F, V, C, H, S>,
2693    ) where
2694        V: ValueEncoding<Value: TestValue>,
2695        C: Mutable<Item = Operation<F, V>>,
2696        H: Hasher,
2697        Operation<F, V>: EncodeShared,
2698    {
2699        let v1 = V::Value::make(1);
2700        let v2 = V::Value::make(2);
2701
2702        let parent = db.new_batch();
2703        let loc1 = parent.size();
2704        let parent = parent.append(v1.clone());
2705        let parent_m = parent
2706            .merkleize(&db, None, db.inactivity_floor_loc())
2707            .await
2708            .unwrap();
2709        let parent_root = parent_m.root();
2710
2711        let db = db.apply_batch(parent_m).await.unwrap();
2712        assert_eq!(db.root(), parent_root);
2713        assert_eq!(db.get(loc1).await.unwrap(), Some(v1));
2714
2715        let batch2 = db.new_batch();
2716        let loc2 = batch2.size();
2717        let batch2 = batch2.append(v2.clone());
2718        let batch2_m = batch2
2719            .merkleize(&db, None, db.inactivity_floor_loc())
2720            .await
2721            .unwrap();
2722        let batch2_root = batch2_m.root();
2723        let db = db.apply_batch(batch2_m).await.unwrap();
2724        assert_eq!(db.root(), batch2_root);
2725        assert_eq!(db.get(loc2).await.unwrap(), Some(v2));
2726
2727        db.destroy().await.unwrap();
2728    }
2729
2730    #[boxed]
2731    pub(crate) async fn run_batch_many_sequential<F: Family, V, C, S: Strategy>(
2732        mut db: TestKeyless<F, V, C, Sha256, S>,
2733    ) where
2734        V: ValueEncoding<Value: TestValue>,
2735        C: Mutable<Item = Operation<F, V>>,
2736        Operation<F, V>: EncodeShared + std::fmt::Debug,
2737    {
2738        const BATCHES: u64 = 20;
2739        const APPENDS_PER_BATCH: u64 = 5;
2740        let mut all_values: Vec<V::Value> = Vec::new();
2741        let mut all_locs: Vec<Location<F>> = Vec::new();
2742
2743        for batch_idx in 0..BATCHES {
2744            let mut batch = db.new_batch();
2745            for j in 0..APPENDS_PER_BATCH {
2746                let v = V::Value::make(batch_idx * 10 + j);
2747                let loc = batch.size();
2748                batch = batch.append(v.clone());
2749                all_values.push(v);
2750                all_locs.push(loc);
2751            }
2752            let merkleized = batch
2753                .merkleize(&db, None, db.inactivity_floor_loc())
2754                .await
2755                .unwrap();
2756            db = db.apply_batch(merkleized).await.unwrap();
2757        }
2758
2759        for (i, loc) in all_locs.iter().enumerate() {
2760            assert_eq!(db.get(*loc).await.unwrap(), Some(all_values[i].clone()));
2761        }
2762
2763        let root = db.root();
2764        let (proof, ops) = db.proof(Location::new(0), NZU64!(1000)).await.unwrap();
2765        assert!(verify_proof::<Sha256, _, _>(
2766            &proof,
2767            Location::new(0),
2768            &ops,
2769            &root,
2770        ));
2771        assert_eq!(db.bounds().end, 1 + BATCHES * (APPENDS_PER_BATCH + 1));
2772
2773        db.destroy().await.unwrap();
2774    }
2775
2776    #[boxed]
2777    pub(crate) async fn run_batch_empty<F: Family, V, C, H, S: Strategy>(
2778        db: TestKeyless<F, V, C, H, S>,
2779    ) where
2780        V: ValueEncoding<Value: TestValue>,
2781        C: Mutable<Item = Operation<F, V>>,
2782        H: Hasher,
2783        Operation<F, V>: EncodeShared,
2784    {
2785        let merkleized = db
2786            .new_batch()
2787            .append(V::Value::make(1))
2788            .merkleize(&db, None, db.inactivity_floor_loc())
2789            .await
2790            .unwrap();
2791        let db = db.apply_batch(merkleized).await.unwrap();
2792        let root_before = db.root();
2793        let size_before = db.bounds().end;
2794
2795        let merkleized = db
2796            .new_batch()
2797            .merkleize(&db, None, db.inactivity_floor_loc())
2798            .await
2799            .unwrap();
2800        let speculative = merkleized.root();
2801        let db = db.apply_batch(merkleized).await.unwrap();
2802
2803        assert_ne!(db.root(), root_before);
2804        assert_eq!(db.root(), speculative);
2805        assert_eq!(db.bounds().end, size_before + 1);
2806
2807        db.destroy().await.unwrap();
2808    }
2809
2810    #[boxed]
2811    pub(crate) async fn run_batch_chained_merkleized_get<F: Family, V, C, S: Strategy>(
2812        db: TestKeyless<F, V, C, Sha256, S>,
2813    ) where
2814        V: ValueEncoding<Value: TestValue>,
2815        C: Mutable<Item = Operation<F, V>>,
2816        Operation<F, V>: EncodeShared,
2817    {
2818        let base_val = V::Value::make(10);
2819        let floor = db.inactivity_floor_loc();
2820        let merkleized = db
2821            .new_batch()
2822            .append(base_val.clone())
2823            .merkleize(&db, None, floor)
2824            .await
2825            .unwrap();
2826        let db = db.apply_batch(merkleized).await.unwrap();
2827
2828        let v1 = V::Value::make(1);
2829        let parent = db.new_batch();
2830        let loc1 = parent.size();
2831        let parent_m = parent
2832            .append(v1.clone())
2833            .merkleize(&db, None, db.inactivity_floor_loc())
2834            .await
2835            .unwrap();
2836
2837        let v2 = V::Value::make(2);
2838        let child = parent_m.new_batch::<Sha256>();
2839        let loc2 = child.size();
2840        let child_m = child
2841            .append(v2.clone())
2842            .merkleize(&db, None, db.inactivity_floor_loc())
2843            .await
2844            .unwrap();
2845
2846        assert_eq!(
2847            child_m.get(Location::new(1), &db).await.unwrap(),
2848            Some(base_val),
2849        );
2850        assert_eq!(child_m.get(loc1, &db).await.unwrap(), Some(v1.clone()));
2851        assert_eq!(child_m.get(loc2, &db).await.unwrap(), Some(v2.clone()));
2852        let child_end = child_m.size();
2853        assert!(matches!(
2854            child_m.get(child_end, &db).await,
2855            Err(Error::LocationOutOfBounds(loc, bound)) if loc == child_end && bound == child_end
2856        ));
2857
2858        // An ancestor's view ends at its own tip.
2859        let parent_end = parent_m.size();
2860        assert!(matches!(
2861            parent_m.get(loc2, &db).await,
2862            Err(Error::LocationOutOfBounds(loc, bound)) if loc == loc2 && bound == parent_end
2863        ));
2864
2865        // Applying the descendant moves the database off the ancestor's chain states.
2866        let db = db.apply_batch(Arc::clone(&parent_m)).await.unwrap();
2867        let db = db.apply_batch(child_m).await.unwrap();
2868        assert_eq!(db.get(loc2).await.unwrap(), Some(v2));
2869        assert!(matches!(
2870            parent_m.get(loc1, &db).await,
2871            Err(Error::StaleRead)
2872        ));
2873
2874        db.destroy().await.unwrap();
2875    }
2876
2877    #[boxed]
2878    pub(crate) async fn run_batch_large<F: Family, V, C, S: Strategy>(
2879        db: TestKeyless<F, V, C, Sha256, S>,
2880    ) where
2881        V: ValueEncoding<Value: TestValue>,
2882        C: Mutable<Item = Operation<F, V>>,
2883        Operation<F, V>: EncodeShared + std::fmt::Debug,
2884    {
2885        const N: u64 = 500;
2886        let mut values = Vec::new();
2887        let mut locs = Vec::new();
2888
2889        let mut batch = db.new_batch();
2890        for i in 0..N {
2891            let v = V::Value::make(i);
2892            locs.push(batch.size());
2893            batch = batch.append(v.clone());
2894            values.push(v);
2895        }
2896        let merkleized = batch
2897            .merkleize(&db, None, db.inactivity_floor_loc())
2898            .await
2899            .unwrap();
2900        let db = db.apply_batch(merkleized).await.unwrap();
2901
2902        for (i, loc) in locs.iter().enumerate() {
2903            assert_eq!(db.get(*loc).await.unwrap(), Some(values[i].clone()));
2904        }
2905
2906        let root = db.root();
2907        let (proof, ops) = db.proof(Location::new(0), NZU64!(1000)).await.unwrap();
2908        assert!(verify_proof::<Sha256, _, _>(
2909            &proof,
2910            Location::new(0),
2911            &ops,
2912            &root,
2913        ));
2914        assert_eq!(db.bounds().end, 1 + N + 1);
2915
2916        db.destroy().await.unwrap();
2917    }
2918
2919    #[boxed]
2920    pub(crate) async fn run_stale_batch_chained<F: Family, V, C, S: Strategy>(
2921        db: TestKeyless<F, V, C, Sha256, S>,
2922    ) where
2923        V: ValueEncoding<Value: TestValue>,
2924        C: Mutable<Item = Operation<F, V>>,
2925        Operation<F, V>: EncodeShared,
2926    {
2927        let common_parent = db
2928            .new_batch()
2929            .append(V::Value::make(10))
2930            .merkleize(&db, None, db.inactivity_floor_loc())
2931            .await
2932            .unwrap();
2933        let sibling_a = common_parent
2934            .new_batch::<Sha256>()
2935            .append(V::Value::make(11))
2936            .merkleize(&db, None, db.inactivity_floor_loc())
2937            .await
2938            .unwrap();
2939        let sibling_b = common_parent
2940            .new_batch::<Sha256>()
2941            .append(V::Value::make(12))
2942            .merkleize(&db, None, db.inactivity_floor_loc())
2943            .await
2944            .unwrap();
2945        let db = db.apply_batch(sibling_a).await.unwrap();
2946        assert!(matches!(
2947            db.validate_batch(&sibling_b),
2948            Err(Error::StaleBatch)
2949        ));
2950
2951        let parent_a = db
2952            .new_batch()
2953            .append(V::Value::make(1))
2954            .merkleize(&db, None, db.inactivity_floor_loc())
2955            .await
2956            .unwrap();
2957        let parent_b = db
2958            .new_batch()
2959            .append(V::Value::make(2))
2960            .merkleize(&db, None, db.inactivity_floor_loc())
2961            .await
2962            .unwrap();
2963        let child_b = parent_b
2964            .new_batch::<Sha256>()
2965            .append(V::Value::make(3))
2966            .merkleize(&db, None, db.inactivity_floor_loc())
2967            .await
2968            .unwrap();
2969
2970        let db = db.apply_batch(parent_a).await.unwrap();
2971        assert!(matches!(
2972            db.validate_batch(&child_b),
2973            Err(Error::StaleBatch)
2974        ));
2975        db.destroy().await.unwrap();
2976    }
2977
2978    #[boxed]
2979    pub(crate) async fn run_sequential_commit_parent_then_child<F: Family, V, C, S: Strategy>(
2980        db: TestKeyless<F, V, C, Sha256, S>,
2981    ) where
2982        V: ValueEncoding<Value: TestValue>,
2983        C: Mutable<Item = Operation<F, V>>,
2984        Operation<F, V>: EncodeShared,
2985    {
2986        let parent = db
2987            .new_batch()
2988            .append(V::Value::make(1))
2989            .merkleize(&db, None, db.inactivity_floor_loc())
2990            .await
2991            .unwrap();
2992        let child = parent
2993            .new_batch::<Sha256>()
2994            .append(V::Value::make(2))
2995            .merkleize(&db, None, db.inactivity_floor_loc())
2996            .await
2997            .unwrap();
2998
2999        let db = db.apply_batch(parent).await.unwrap();
3000        let db = db.apply_batch(child).await.unwrap();
3001
3002        db.destroy().await.unwrap();
3003    }
3004
3005    #[boxed]
3006    pub(crate) async fn run_stale_batch_child_before_parent<F: Family, V, C, S: Strategy>(
3007        db: TestKeyless<F, V, C, Sha256, S>,
3008    ) where
3009        V: ValueEncoding<Value: TestValue>,
3010        C: Mutable<Item = Operation<F, V>>,
3011        Operation<F, V>: EncodeShared,
3012    {
3013        let parent = db
3014            .new_batch()
3015            .append(V::Value::make(1))
3016            .merkleize(&db, None, db.inactivity_floor_loc())
3017            .await
3018            .unwrap();
3019        let child = parent
3020            .new_batch::<Sha256>()
3021            .append(V::Value::make(2))
3022            .merkleize(&db, None, db.inactivity_floor_loc())
3023            .await
3024            .unwrap();
3025
3026        let db = db.apply_batch(child).await.unwrap();
3027        assert!(matches!(
3028            db.apply_batch(parent).await,
3029            Err(Error::StaleBatch)
3030        ));
3031    }
3032
3033    #[boxed]
3034    pub(crate) async fn run_child_root_matches_pending_and_committed<F: Family, V, C, S: Strategy>(
3035        db: TestKeyless<F, V, C, Sha256, S>,
3036    ) where
3037        V: ValueEncoding<Value: TestValue>,
3038        C: Mutable<Item = Operation<F, V>>,
3039        Operation<F, V>: EncodeShared,
3040    {
3041        // Build the child while the parent is unapplied.
3042        let parent = db
3043            .new_batch()
3044            .append(V::Value::make(1))
3045            .merkleize(&db, None, db.inactivity_floor_loc())
3046            .await
3047            .unwrap();
3048        let pending_child = parent
3049            .new_batch::<Sha256>()
3050            .append(V::Value::make(2))
3051            .merkleize(&db, None, db.inactivity_floor_loc())
3052            .await
3053            .unwrap();
3054
3055        // Commit the parent, then rebuild the same logical child from the
3056        // committed DB state and compare roots.
3057        let db = db.apply_batch(parent).await.unwrap();
3058        let db = db.commit().await.unwrap();
3059
3060        let committed_child = db
3061            .new_batch()
3062            .append(V::Value::make(2))
3063            .merkleize(&db, None, db.inactivity_floor_loc())
3064            .await
3065            .unwrap();
3066
3067        assert_eq!(pending_child.root(), committed_child.root());
3068
3069        db.destroy().await.unwrap();
3070    }
3071
3072    async fn commit_appends<F: Family, V, C, H, S: Strategy>(
3073        db: TestKeyless<F, V, C, H, S>,
3074        values: impl IntoIterator<Item = V::Value>,
3075        metadata: Option<V::Value>,
3076    ) -> TestKeyless<F, V, C, H, S>
3077    where
3078        V: ValueEncoding<Value: TestValue>,
3079        C: Mutable<Item = Operation<F, V>>,
3080        H: Hasher,
3081        Operation<F, V>: EncodeShared,
3082    {
3083        // Tests that don't specifically exercise floor behavior advance the floor to the new
3084        // commit location, so pruning up to the last commit works analogously to the pre-floor
3085        // semantics.
3086        let base_size = *db.bounds().end;
3087        let appends_iter: Vec<_> = values.into_iter().collect();
3088        let new_commit_loc = Location::new(base_size + appends_iter.len() as u64);
3089        let mut batch = db.new_batch();
3090        for value in appends_iter {
3091            batch = batch.append(value);
3092        }
3093        let merkleized = batch
3094            .merkleize(&db, metadata, new_commit_loc)
3095            .await
3096            .unwrap();
3097        let expected_end = merkleized.bounds().tip.size;
3098        let db = db.apply_batch(merkleized).await.unwrap();
3099        assert_eq!(db.bounds().end, expected_end);
3100        db.commit().await.unwrap()
3101    }
3102
3103    #[boxed]
3104    pub(crate) async fn run_bounded_initialization_recovery<F: Family, V, C, H, S: Strategy>(
3105        context: deterministic::Context,
3106        db: TestKeyless<F, V, C, H, S>,
3107        reopen: Reopen<TestKeyless<F, V, C, H, S>>,
3108        bounded: BoundedOpen<TestKeyless<F, V, C, H, S>, F>,
3109    ) where
3110        V: ValueEncoding<Value: TestValue>,
3111        C: Mutable<Item = Operation<F, V>>,
3112        H: Hasher,
3113        Operation<F, V>: EncodeShared,
3114    {
3115        let initial_root = db.root();
3116        let initial_size = db.bounds().end;
3117
3118        let value_a = V::Value::make(1);
3119        let value_b = V::Value::make(2);
3120        let metadata_a = V::Value::make(3);
3121        let db = commit_appends(
3122            db,
3123            [value_a.clone(), value_b.clone()],
3124            Some(metadata_a.clone()),
3125        )
3126        .await;
3127
3128        let root_before = db.root();
3129        let size_before = db.bounds().end;
3130        let commit_before = db.bounds().end - 1;
3131
3132        let value_c = V::Value::make(4);
3133        let metadata_b = V::Value::make(5);
3134        let db = commit_appends(db, [value_c.clone()], Some(metadata_b.clone())).await;
3135        assert_ne!(db.root(), root_before);
3136        assert_eq!(db.get_metadata().await.unwrap(), Some(metadata_b));
3137
3138        drop(db);
3139        let db = bounded(context.child("cap"), size_before).await.unwrap();
3140        assert_eq!(db.root(), root_before);
3141        assert_eq!(db.bounds().end, size_before);
3142        assert_eq!(db.bounds().end - 1, commit_before);
3143        assert_eq!(db.get_metadata().await.unwrap(), Some(metadata_a.clone()));
3144        assert_eq!(
3145            db.get(Location::new(1)).await.unwrap(),
3146            Some(value_a.clone())
3147        );
3148        assert_eq!(
3149            db.get(Location::new(2)).await.unwrap(),
3150            Some(value_b.clone())
3151        );
3152        assert!(
3153            matches!(
3154                db.get(Location::new(4)).await,
3155                Err(Error::LocationOutOfBounds(_, size)) if size == size_before
3156            ),
3157            "discarded append should be out of bounds",
3158        );
3159
3160        drop(db);
3161        let db = reopen(context.child("reopen")).await;
3162        assert_eq!(db.root(), root_before);
3163        assert_eq!(db.bounds().end, size_before);
3164        assert_eq!(db.bounds().end - 1, commit_before);
3165        assert_eq!(db.get_metadata().await.unwrap(), Some(metadata_a));
3166        assert_eq!(
3167            db.get(Location::new(1)).await.unwrap(),
3168            Some(value_a.clone())
3169        );
3170        assert_eq!(
3171            db.get(Location::new(2)).await.unwrap(),
3172            Some(value_b.clone())
3173        );
3174        assert!(matches!(
3175            db.get(Location::new(4)).await,
3176            Err(Error::LocationOutOfBounds(_, size)) if size == size_before
3177        ));
3178
3179        drop(db);
3180        let db = bounded(context.child("cap"), initial_size).await.unwrap();
3181        assert_eq!(db.root(), initial_root);
3182        assert_eq!(db.bounds().end, initial_size);
3183        assert_eq!(db.get_metadata().await.unwrap(), None);
3184        assert!(matches!(
3185            db.get(Location::new(1)).await,
3186            Err(Error::LocationOutOfBounds(_, size)) if size == initial_size
3187        ));
3188
3189        drop(db);
3190        let db = reopen(context.child("reopen_initial_boundary")).await;
3191        assert_eq!(db.root(), initial_root);
3192        assert_eq!(db.bounds().end, initial_size);
3193        assert_eq!(db.get_metadata().await.unwrap(), None);
3194        assert!(matches!(
3195            db.get(Location::new(1)).await,
3196            Err(Error::LocationOutOfBounds(_, size)) if size == initial_size
3197        ));
3198
3199        db.destroy().await.unwrap();
3200    }
3201
3202    #[boxed]
3203    pub(crate) async fn run_bounded_initialization_pruned_target_errors<
3204        F: Family,
3205        V,
3206        C,
3207        H,
3208        S: Strategy,
3209    >(
3210        context: deterministic::Context,
3211        db: TestKeyless<F, V, C, H, S>,
3212        reopen: Reopen<TestKeyless<F, V, C, H, S>>,
3213        bounded: BoundedOpen<TestKeyless<F, V, C, H, S>, F>,
3214    ) where
3215        V: ValueEncoding<Value: TestValue>,
3216        C: Mutable<Item = Operation<F, V>>,
3217        H: Hasher,
3218        Operation<F, V>: EncodeShared,
3219    {
3220        let first_start = db.bounds().end;
3221        let mut db = commit_appends(db, (0..16).map(V::Value::make), None).await;
3222
3223        let mut round = 0u64;
3224        loop {
3225            round += 1;
3226            assert!(
3227                round <= 64,
3228                "failed to prune enough history for the initialization test"
3229            );
3230
3231            db = commit_appends(db, (0..16).map(|i| V::Value::make(round * 100 + i)), None).await;
3232            let last_commit = db.bounds().end - 1;
3233            db = db.prune(last_commit).await.unwrap();
3234
3235            if db.bounds().start > first_start {
3236                break;
3237            }
3238        }
3239
3240        let oldest_retained = db.bounds().start;
3241        drop(db);
3242        let Err(boundary_err) = bounded(context.child("cap_error"), oldest_retained).await else {
3243            panic!("expected bounded initialization to fail");
3244        };
3245        assert!(
3246            matches!(
3247                boundary_err,
3248                Error::Journal(crate::journal::Error::ItemPruned(_))
3249            ),
3250            "unexpected bounded initialization error at retained boundary: {boundary_err:?}"
3251        );
3252
3253        let db = reopen(context.child("reopen_boundary")).await;
3254        drop(db);
3255        let Err(err) = bounded(context.child("cap_error"), first_start).await else {
3256            panic!("expected bounded initialization to fail");
3257        };
3258        assert!(
3259            matches!(err, Error::Journal(crate::journal::Error::ItemPruned(_))),
3260            "unexpected bounded initialization error: {err:?}"
3261        );
3262    }
3263
3264    #[boxed]
3265    pub(crate) async fn run_floor_tracking<F: Family, V, C, H, S: Strategy>(
3266        context: deterministic::Context,
3267        db: TestKeyless<F, V, C, H, S>,
3268        reopen: Reopen<TestKeyless<F, V, C, H, S>>,
3269    ) where
3270        V: ValueEncoding<Value: TestValue>,
3271        C: Mutable<Item = Operation<F, V>>,
3272        H: Hasher,
3273        Operation<F, V>: EncodeShared,
3274    {
3275        // Freshly created db has floor = 0.
3276        assert_eq!(db.inactivity_floor_loc(), Location::new(0));
3277
3278        // Apply a batch with a declared floor; the db's floor should update.
3279        let floor_a = Location::<F>::new(2);
3280        let merkleized = db
3281            .new_batch()
3282            .append(V::Value::make(1))
3283            .append(V::Value::make(2))
3284            .merkleize(&db, None, floor_a)
3285            .await
3286            .unwrap();
3287        let db = db.apply_batch(merkleized).await.unwrap();
3288        let db = db.commit().await.unwrap();
3289        assert_eq!(db.inactivity_floor_loc(), floor_a);
3290
3291        // Reopen: floor should survive restart (it's part of the last commit operation).
3292        drop(db);
3293        let db = reopen(context.child("reopen")).await;
3294        assert_eq!(db.inactivity_floor_loc(), floor_a);
3295
3296        // Floor may stay the same across a commit (monotonic non-decreasing).
3297        let merkleized = db
3298            .new_batch()
3299            .append(V::Value::make(3))
3300            .merkleize(&db, None, floor_a)
3301            .await
3302            .unwrap();
3303        let db = db.apply_batch(merkleized).await.unwrap();
3304        assert_eq!(db.inactivity_floor_loc(), floor_a);
3305
3306        // Floor may advance further.
3307        let floor_b = Location::<F>::new(5);
3308        let merkleized = db
3309            .new_batch()
3310            .append(V::Value::make(4))
3311            .merkleize(&db, None, floor_b)
3312            .await
3313            .unwrap();
3314        let db = db.apply_batch(merkleized).await.unwrap();
3315        assert_eq!(db.inactivity_floor_loc(), floor_b);
3316
3317        db.destroy().await.unwrap();
3318    }
3319
3320    #[boxed]
3321    pub(crate) async fn run_floor_regression_rejected<F: Family, V, C, H, S: Strategy>(
3322        db: TestKeyless<F, V, C, H, S>,
3323    ) where
3324        V: ValueEncoding<Value: TestValue>,
3325        C: Mutable<Item = Operation<F, V>>,
3326        H: Hasher,
3327        Operation<F, V>: EncodeShared,
3328    {
3329        // Advance floor to 3 and commit so the state is durable.
3330        let merkleized = db
3331            .new_batch()
3332            .append(V::Value::make(1))
3333            .append(V::Value::make(2))
3334            .merkleize(&db, None, Location::new(3))
3335            .await
3336            .unwrap();
3337        let db = db.apply_batch(merkleized).await.unwrap();
3338        let db = db.commit().await.unwrap();
3339        assert_eq!(db.inactivity_floor_loc(), Location::new(3));
3340
3341        // Merkleize rejects a commit with a lower floor.
3342        let Err(err) = db
3343            .new_batch()
3344            .append(V::Value::make(3))
3345            .merkleize(&db, None, Location::new(1))
3346            .await
3347        else {
3348            panic!("merkleize must reject the invalid floor");
3349        };
3350        assert!(
3351            matches!(err, Error::FloorRegressed(new, current) if *new == 1 && *current == 3),
3352            "unexpected error: {err:?}"
3353        );
3354
3355        db.destroy().await.unwrap();
3356    }
3357
3358    #[boxed]
3359    pub(crate) async fn run_floor_beyond_commit_loc_rejected<F: Family, V, C, H, S: Strategy>(
3360        db: TestKeyless<F, V, C, H, S>,
3361    ) where
3362        V: ValueEncoding<Value: TestValue>,
3363        C: Mutable<Item = Operation<F, V>>,
3364        H: Hasher,
3365        Operation<F, V>: EncodeShared,
3366    {
3367        // Batch of 2 appends + 1 commit lands at locations [1..4); commit at 3, total_size = 4.
3368        // A floor > 3 (the commit location) is invalid. Even floor == 4 (one past the commit)
3369        // is rejected so a subsequent prune cannot remove the last readable commit.
3370        let Err(err) = db
3371            .new_batch()
3372            .append(V::Value::make(1))
3373            .append(V::Value::make(2))
3374            .merkleize(&db, None, Location::new(999))
3375            .await
3376        else {
3377            panic!("merkleize must reject the invalid floor");
3378        };
3379        assert!(
3380            matches!(err, Error::FloorBeyondSize(floor, commit) if *floor == 999 && *commit == 3),
3381            "unexpected error: {err:?}"
3382        );
3383
3384        // Boundary: floor == total_size (= commit_loc + 1) is also rejected.
3385        let Err(err) = db
3386            .new_batch()
3387            .append(V::Value::make(3))
3388            .append(V::Value::make(4))
3389            .merkleize(&db, None, Location::new(4))
3390            .await
3391        else {
3392            panic!("merkleize must reject the invalid floor");
3393        };
3394        assert!(
3395            matches!(err, Error::FloorBeyondSize(floor, commit) if *floor == 4 && *commit == 3),
3396            "unexpected error: {err:?}"
3397        );
3398    }
3399
3400    #[boxed]
3401    pub(crate) async fn run_bounded_initialization_restores_floor<F: Family, V, C, H, S: Strategy>(
3402        context: deterministic::Context,
3403        db: TestKeyless<F, V, C, H, S>,
3404        bounded: BoundedOpen<TestKeyless<F, V, C, H, S>, F>,
3405    ) where
3406        V: ValueEncoding<Value: TestValue>,
3407        C: Mutable<Item = Operation<F, V>>,
3408        H: Hasher,
3409        Operation<F, V>: EncodeShared,
3410    {
3411        // First commit: floor advances to 3 (= commit location).
3412        let floor_a = Location::<F>::new(3);
3413        let merkleized = db
3414            .new_batch()
3415            .append(V::Value::make(1))
3416            .append(V::Value::make(2))
3417            .merkleize(&db, None, floor_a)
3418            .await
3419            .unwrap();
3420        let db = db.apply_batch(merkleized).await.unwrap();
3421        let db = db.commit().await.unwrap();
3422        let initialization_bound = db.bounds().end;
3423
3424        // Second commit: floor advances to 6.
3425        let floor_b = Location::<F>::new(6);
3426        let merkleized = db
3427            .new_batch()
3428            .append(V::Value::make(3))
3429            .append(V::Value::make(4))
3430            .merkleize(&db, None, floor_b)
3431            .await
3432            .unwrap();
3433        let db = db.apply_batch(merkleized).await.unwrap();
3434        let db = db.commit().await.unwrap();
3435        assert_eq!(db.inactivity_floor_loc(), floor_b);
3436
3437        // Reopen at the first commit. The floor should restore to floor_a.
3438        drop(db);
3439        let db = bounded(context.child("cap"), initialization_bound)
3440            .await
3441            .unwrap();
3442        assert_eq!(db.inactivity_floor_loc(), floor_a);
3443
3444        // Prune is now gated at floor_a: pruning up to the floor works.
3445        let db = db.prune(floor_a).await.unwrap();
3446
3447        // Pruning past the floor fails.
3448        let beyond = floor_a + 1;
3449        let Err(err) = db.prune(beyond).await else {
3450            panic!("expected prune to fail");
3451        };
3452        assert!(matches!(err, Error::PruneBeyondMinRequired(_, _)));
3453    }
3454
3455    /// Floor is embedded in the Commit operation and therefore in the Merkle root: two databases
3456    /// with identical appends but different floors must produce different roots.
3457    #[boxed]
3458    pub(crate) async fn run_floor_changes_root<F: Family, V, C, H, S: Strategy>(
3459        db_a: TestKeyless<F, V, C, H, S>,
3460        db_b: TestKeyless<F, V, C, H, S>,
3461    ) where
3462        V: ValueEncoding<Value: TestValue>,
3463        C: Mutable<Item = Operation<F, V>>,
3464        H: Hasher,
3465        Operation<F, V>: EncodeShared,
3466    {
3467        let appends = [V::Value::make(1), V::Value::make(2)];
3468
3469        // db_a commits with floor=0.
3470        let mut batch_a = db_a.new_batch();
3471        for v in appends.iter() {
3472            batch_a = batch_a.append(v.clone());
3473        }
3474        let merkleized = batch_a
3475            .merkleize(&db_a, None, Location::new(0))
3476            .await
3477            .unwrap();
3478        let db_a = db_a.apply_batch(merkleized).await.unwrap();
3479
3480        // db_b commits the same appends but with floor=3 (= commit location).
3481        let mut batch_b = db_b.new_batch();
3482        for v in appends.iter() {
3483            batch_b = batch_b.append(v.clone());
3484        }
3485        let merkleized = batch_b
3486            .merkleize(&db_b, None, Location::new(3))
3487            .await
3488            .unwrap();
3489        let db_b = db_b.apply_batch(merkleized).await.unwrap();
3490
3491        assert_ne!(db_a.root(), db_b.root());
3492
3493        db_a.destroy().await.unwrap();
3494        db_b.destroy().await.unwrap();
3495    }
3496
3497    /// A floor equal to the commit operation's location is on the tight boundary of acceptance.
3498    #[boxed]
3499    pub(crate) async fn run_floor_at_commit_loc_accepted<F: Family, V, C, H, S: Strategy>(
3500        db: TestKeyless<F, V, C, H, S>,
3501    ) where
3502        V: ValueEncoding<Value: TestValue>,
3503        C: Mutable<Item = Operation<F, V>>,
3504        H: Hasher,
3505        Operation<F, V>: EncodeShared,
3506    {
3507        // 2 appends + 1 commit on top of the initial commit: commit lands at location 3.
3508        // floor == 3 (= commit_loc) is the maximum accepted value under the per-commit bound.
3509        let commit_loc = Location::<F>::new(3);
3510        let merkleized = db
3511            .new_batch()
3512            .append(V::Value::make(1))
3513            .append(V::Value::make(2))
3514            .merkleize(&db, None, commit_loc)
3515            .await
3516            .unwrap();
3517        let db = db.apply_batch(merkleized).await.unwrap();
3518        assert_eq!(db.inactivity_floor_loc(), commit_loc);
3519
3520        db.destroy().await.unwrap();
3521    }
3522
3523    /// Commit, reinitialize at an earlier bound, then verify the restored floor survives another
3524    /// reopen.
3525    #[boxed]
3526    pub(crate) async fn run_bounded_initialization_after_reopen_with_floor<
3527        F: Family,
3528        V,
3529        C,
3530        H,
3531        S: Strategy,
3532    >(
3533        context: deterministic::Context,
3534        db: TestKeyless<F, V, C, H, S>,
3535        reopen: Reopen<TestKeyless<F, V, C, H, S>>,
3536        bounded: BoundedOpen<TestKeyless<F, V, C, H, S>, F>,
3537    ) where
3538        V: ValueEncoding<Value: TestValue>,
3539        C: Mutable<Item = Operation<F, V>>,
3540        H: Hasher,
3541        Operation<F, V>: EncodeShared,
3542    {
3543        // First commit: 2 appends + commit, floor advances to 3.
3544        let floor_a = Location::<F>::new(3);
3545        let merkleized = db
3546            .new_batch()
3547            .append(V::Value::make(1))
3548            .append(V::Value::make(2))
3549            .merkleize(&db, None, floor_a)
3550            .await
3551            .unwrap();
3552        let db = db.apply_batch(merkleized).await.unwrap();
3553        let db = db.commit().await.unwrap();
3554        let initialization_bound = db.bounds().end;
3555
3556        // Second commit: 2 appends + commit, floor advances to 6.
3557        let floor_b = Location::<F>::new(6);
3558        let merkleized = db
3559            .new_batch()
3560            .append(V::Value::make(3))
3561            .append(V::Value::make(4))
3562            .merkleize(&db, None, floor_b)
3563            .await
3564            .unwrap();
3565        let db = db.apply_batch(merkleized).await.unwrap();
3566        db.commit().await.unwrap();
3567
3568        // Reopen to simulate a crash after both commits were durable.
3569        let db = reopen(context.child("reopen")).await;
3570        assert_eq!(db.inactivity_floor_loc(), floor_b);
3571
3572        // Reopen at the first commit. The floor should restore to floor_a.
3573        drop(db);
3574        let db = bounded(context.child("cap"), initialization_bound)
3575            .await
3576            .unwrap();
3577        assert_eq!(db.inactivity_floor_loc(), floor_a);
3578        assert_eq!(db.bounds().end - 1, Location::new(3));
3579
3580        // Bounded initialization is durable. Reopen and confirm the floor again.
3581        drop(db);
3582        let db = reopen(context.child("reopen").with_attribute("index", 2)).await;
3583        assert_eq!(db.inactivity_floor_loc(), floor_a);
3584
3585        db.destroy().await.unwrap();
3586    }
3587
3588    /// Merkleize returns `FloorRegressed` for a chained batch whose floor is below its parent's
3589    /// floor, even though that floor is still at or above the database's floor.
3590    #[boxed]
3591    pub(crate) async fn run_chained_floor_regression_rejected<F, V, C, H, S: Strategy>(
3592        db: TestKeyless<F, V, C, H, S>,
3593    ) where
3594        F: Family,
3595        V: ValueEncoding<Value: TestValue>,
3596        C: Mutable<Item = Operation<F, V>>,
3597        H: Hasher,
3598        Operation<F, V>: EncodeShared,
3599    {
3600        // parent: 1 append + commit at loc 2 with floor=2 (the parent's commit_loc).
3601        let parent = db
3602            .new_batch()
3603            .append(V::Value::make(1))
3604            .merkleize(&db, None, Location::new(2))
3605            .await
3606            .unwrap();
3607
3608        // child: 1 append + commit at loc 4 with floor=1 (regressed from parent's floor=2).
3609        let Err(err) = parent
3610            .new_batch::<H>()
3611            .append(V::Value::make(2))
3612            .merkleize(&db, None, Location::new(1))
3613            .await
3614        else {
3615            panic!("merkleize must reject the invalid floor");
3616        };
3617        assert!(
3618            matches!(err, Error::FloorRegressed(new, prev) if *new == 1 && *prev == 2),
3619            "unexpected error: {err:?}"
3620        );
3621
3622        db.destroy().await.unwrap();
3623    }
3624
3625    /// Merkleize returns `FloorBeyondSize` for a chained batch whose floor exceeds its own commit
3626    /// location. The commit location includes the parent's operations.
3627    #[boxed]
3628    pub(crate) async fn run_chained_floor_beyond_commit_rejected<F, V, C, H, S: Strategy>(
3629        db: TestKeyless<F, V, C, H, S>,
3630    ) where
3631        F: Family,
3632        V: ValueEncoding<Value: TestValue>,
3633        C: Mutable<Item = Operation<F, V>>,
3634        H: Hasher,
3635        Operation<F, V>: EncodeShared,
3636    {
3637        // parent: 1 append + commit at loc 2 with floor=2.
3638        let parent = db
3639            .new_batch()
3640            .append(V::Value::make(1))
3641            .merkleize(&db, None, Location::new(2))
3642            .await
3643            .unwrap();
3644
3645        // child: 1 append + commit at loc 4, declaring floor=5 (one past its commit). The error
3646        // names commit location 4, so the bound counts the parent's operations.
3647        let Err(err) = parent
3648            .new_batch::<H>()
3649            .append(V::Value::make(2))
3650            .merkleize(&db, None, Location::new(5))
3651            .await
3652        else {
3653            panic!("merkleize must reject the invalid floor");
3654        };
3655        assert!(
3656            matches!(err, Error::FloorBeyondSize(floor, commit) if *floor == 5 && *commit == 4),
3657            "unexpected error: {err:?}"
3658        );
3659    }
3660
3661    /// After committing with `floor = commit_loc` and pruning down to it, exactly one operation,
3662    /// the commit itself, is at or above the floor. This is the smallest nonempty such set the
3663    /// per-commit bound allows. The DB must remain fully usable: the commit is readable, the root
3664    /// is preserved, reopen recovers `inactivity_floor_loc` from the sole remaining op, and a
3665    /// follow-on batch applies cleanly on top.
3666    #[boxed]
3667    pub(crate) async fn run_single_commit_live_set<F, V, C, H, S: Strategy>(
3668        context: deterministic::Context,
3669        db: TestKeyless<F, V, C, H, S>,
3670        reopen: Reopen<TestKeyless<F, V, C, H, S>>,
3671    ) where
3672        F: Family,
3673        V: ValueEncoding<Value: TestValue>,
3674        C: Mutable<Item = Operation<F, V>>,
3675        H: Hasher,
3676        Operation<F, V>: EncodeShared,
3677    {
3678        // Initial commit is at loc 0. 3 appends + commit -> commit lands at loc 4.
3679        // Declare floor = 4 (= commit_loc), the tight maximum.
3680        let metadata = V::Value::make(42);
3681        let commit_loc = Location::<F>::new(4);
3682        let merkleized = db
3683            .new_batch()
3684            .append(V::Value::make(1))
3685            .append(V::Value::make(2))
3686            .append(V::Value::make(3))
3687            .merkleize(&db, Some(metadata.clone()), commit_loc)
3688            .await
3689            .unwrap();
3690        let db = db.apply_batch(merkleized).await.unwrap();
3691        let db = db.commit().await.unwrap();
3692        assert_eq!(db.bounds().end - 1, commit_loc);
3693        assert_eq!(db.inactivity_floor_loc(), commit_loc);
3694        let root_after_commit = db.root();
3695
3696        // Prune at the floor, the maximum prune allowed under the invariant. Pruning is
3697        // blob-aligned, so `bounds.start` may not physically advance all the way to `commit_loc`.
3698        // What matters semantically is that the floor has authorized pruning of everything below
3699        // the commit and that any further prune is rejected.
3700        let db = db.prune(commit_loc).await.unwrap();
3701        let bounds = db.bounds();
3702        assert!(
3703            bounds.start <= commit_loc,
3704            "prune must not advance bounds.start past the floor"
3705        );
3706        assert_eq!(bounds.end, commit_loc + 1);
3707
3708        // The commit op remains readable; its metadata is intact.
3709        assert_eq!(db.get(commit_loc).await.unwrap(), Some(metadata.clone()));
3710        assert_eq!(db.get_metadata().await.unwrap(), Some(metadata.clone()));
3711        assert_eq!(db.bounds().end - 1, commit_loc);
3712        assert_eq!(db.inactivity_floor_loc(), commit_loc);
3713        // Prune does not affect the root (documented invariant on `prune`).
3714        assert_eq!(db.root(), root_after_commit);
3715
3716        // Persist the prune, then verify pruning one past the floor is rejected. The
3717        // floor is the hard ceiling.
3718        let db = db.sync().await.unwrap();
3719        let Err(err) = db.prune(commit_loc + 1).await else {
3720            panic!("expected prune to fail");
3721        };
3722        assert!(matches!(err, Error::PruneBeyondMinRequired(p, f)
3723                if *p == *commit_loc + 1 && *f == *commit_loc));
3724
3725        // Reopening restores the inactivity floor from the last commit.
3726        let db = reopen(context.child("reopened")).await;
3727        let reopened_bounds = db.bounds();
3728        assert_eq!(reopened_bounds.end, commit_loc + 1);
3729        assert_eq!(db.bounds().end - 1, commit_loc);
3730        assert_eq!(db.inactivity_floor_loc(), commit_loc);
3731        assert_eq!(db.root(), root_after_commit);
3732        assert_eq!(db.get_metadata().await.unwrap(), Some(metadata.clone()));
3733
3734        // A follow-on batch applies on top. Monotonicity requires the new floor to be at
3735        // least `commit_loc` (= 4); advancing to the new tight max (= 7) exercises the
3736        // ancestor-to-tip floor transition from a single-commit starting point.
3737        let next_commit_loc = Location::<F>::new(7);
3738        let v5 = V::Value::make(5);
3739        let v6 = V::Value::make(6);
3740        let merkleized = db
3741            .new_batch()
3742            .append(v5.clone())
3743            .append(v6.clone())
3744            .merkleize(&db, None, next_commit_loc)
3745            .await
3746            .unwrap();
3747        let db = db.apply_batch(merkleized).await.unwrap();
3748        let db = db.commit().await.unwrap();
3749        assert_eq!(db.bounds().end - 1, next_commit_loc);
3750        assert_eq!(db.inactivity_floor_loc(), next_commit_loc);
3751
3752        // New appends are readable, and the journal still holds the original commit op (it was
3753        // not pruned again), so reading it still returns its metadata.
3754        assert_eq!(db.get(Location::new(5)).await.unwrap(), Some(v5));
3755        assert_eq!(db.get(Location::new(6)).await.unwrap(), Some(v6));
3756        assert_eq!(db.get(commit_loc).await.unwrap(), Some(metadata));
3757
3758        db.destroy().await.unwrap();
3759    }
3760
3761    /// A multi-level chain with strictly-monotonic, within-bounds floors applies cleanly.
3762    #[boxed]
3763    pub(crate) async fn run_chained_apply_with_valid_floors_succeeds<F, V, C, H, S: Strategy>(
3764        db: TestKeyless<F, V, C, H, S>,
3765    ) where
3766        F: Family,
3767        V: ValueEncoding<Value: TestValue>,
3768        C: Mutable<Item = Operation<F, V>>,
3769        H: Hasher,
3770        Operation<F, V>: EncodeShared,
3771    {
3772        // parent:     1 append, commit at loc 2, floor = 2.
3773        // child:      1 append, commit at loc 4, floor = 3.
3774        // grandchild: 1 append, commit at loc 6, floor = 5.
3775        let parent = db
3776            .new_batch()
3777            .append(V::Value::make(1))
3778            .merkleize(&db, None, Location::new(2))
3779            .await
3780            .unwrap();
3781        let child = parent
3782            .new_batch::<H>()
3783            .append(V::Value::make(2))
3784            .merkleize(&db, None, Location::new(3))
3785            .await
3786            .unwrap();
3787        let grandchild = child
3788            .new_batch::<H>()
3789            .append(V::Value::make(3))
3790            .merkleize(&db, None, Location::new(5))
3791            .await
3792            .unwrap();
3793
3794        let db = db.apply_batch(grandchild).await.unwrap();
3795
3796        // Grandchild's commit is the last op, and the tip's floor is the database's floor.
3797        assert_eq!(db.bounds().end - 1, Location::new(6));
3798        assert_eq!(db.inactivity_floor_loc(), Location::new(5));
3799
3800        db.destroy().await.unwrap();
3801    }
3802}