Skip to main content

commonware_storage/qmdb/immutable/
mod.rs

1//! An authenticated database that only supports adding new keyed values (no updates or
2//! deletions).
3//!
4//! Two variants are available based on value encoding:
5//! - [fixed]: For fixed-size values.
6//! - [variable]: For variable-size values.
7//!
8//! # Inactivity floor
9//!
10//! Each commit carries an inactivity floor: a location before which the application
11//! declares operations are no longer needed. The floor is embedded in the operation
12//! log and included in the Merkle root, so all replicas processing the same operations
13//! arrive at the same floor.
14//!
15//! The floor controls two things:
16//! - **Pruning**: [`Immutable::prune`] only allows pruning up to the floor.
17//! - **Reconstruction**: on restart or sync, the snapshot is rebuilt from the floor
18//!   onward. Keys set before the floor are not loaded into memory.
19//!
20//! A running database keeps serving keys set before the floor until their operations are
21//! pruned, while a restarted or synced one does not know them, so only reads at or above the
22//! floor are consistent across replicas. Treat declaring a floor as retiring every key set
23//! before it.
24//!
25//! The floor must be monotonically non-decreasing across commits and must not exceed the
26//! location of the batch's own commit operation. Pass `db.inactivity_floor_loc()` to keep the
27//! floor unchanged, or a higher value to advance it.
28//!
29//! # Examples
30//!
31//! ```ignore
32//! // Simple mode: apply a batch, then durably commit it.
33//! // The third argument to merkleize is the inactivity floor. Operations
34//! // before this location are declared inactive by the application.
35//! let floor = db.inactivity_floor_loc();
36//! let merkleized = db.new_batch()
37//!     .set(key, value)
38//!     .merkleize(&db, None, floor).await.unwrap();
39//! let db = db.apply_batch(merkleized).await?.commit().await?;
40//! ```
41//!
42//! ```ignore
43//! // Several batches can be built on one parent before any is applied.
44//! let floor = db.inactivity_floor_loc();
45//! let parent = db.new_batch()
46//!     .set(key_a, value_a)
47//!     .merkleize(&db, None, floor).await.unwrap();
48//!
49//! let child_a = parent.new_batch::<Sha256>()
50//!     .set(key_b, value_b)
51//!     .merkleize(&db, None, floor).await.unwrap();
52//!
53//! let child_b = parent.new_batch::<Sha256>()
54//!     .set(key_c, value_c)
55//!     .merkleize(&db, None, floor).await.unwrap();
56//!
57//! let db = db.apply_batch(child_a).await?.commit().await?;
58//! ```
59//!
60//! ```ignore
61//! // Apply a parent batch, commit, then build a child batch from the newly
62//! // published state and apply it.
63//! let floor = db.inactivity_floor_loc();
64//! let parent = db.new_batch()
65//!     .set(key_a, value_a)
66//!     .merkleize(&db, None, floor).await.unwrap();
67//! let db = db.apply_batch(parent).await?.commit().await?;
68//!
69//! let child = db.new_batch()
70//!     .set(key_b, value_b)
71//!     .merkleize(&db, None, floor).await.unwrap();
72//! let db = db.apply_batch(child).await?.commit().await?;
73//! ```
74
75use crate::{
76    Context,
77    index::immutable::Index,
78    journal::{
79        authenticated,
80        contiguous::{Contiguous, Mutable},
81    },
82    merkle::{Family, Location, Proof, full::Config as MerkleConfig},
83    qmdb::{
84        Error, any::ValueEncoding, chain, metrics::Metrics, operation::Key, single_operation_root,
85        sync::source,
86    },
87    translator::Translator,
88};
89use commonware_codec::EncodeShared;
90use commonware_cryptography::Hasher;
91use commonware_macros::boxed;
92use commonware_parallel::Strategy;
93use commonware_runtime::{Handle, ReadOptions};
94use core::num::{NonZeroU64, NonZeroUsize};
95use futures::{StreamExt, pin_mut};
96use std::{ops::Range, sync::Arc};
97use tracing::warn;
98
99pub mod batch;
100mod compact;
101pub mod fixed;
102mod operation;
103pub mod sync;
104pub mod variable;
105
106pub use compact::{
107    Config as CompactConfig, Db as CompactDb, MerkleizedBatch as CompactMerkleizedBatch,
108    UnmerkleizedBatch as CompactUnmerkleizedBatch,
109};
110pub use operation::Operation;
111
112/// Build the snapshot by replaying the log from `inactivity_floor_loc`, inserting the location of
113/// every retained [Operation::Set]. Assumes the log is not pruned beyond the inactivity floor.
114///
115/// `init_buffer` sizes the replay read buffer (in bytes).
116async fn build_snapshot<F, E, K, V, C, T>(
117    context: E,
118    translator: T,
119    inactivity_floor_loc: Location<F>,
120    log: &C,
121    init_buffer: NonZeroUsize,
122) -> Result<Index<T>, Error<F>>
123where
124    F: Family,
125    E: Context,
126    K: Key,
127    V: ValueEncoding,
128    C: Contiguous<Item = Operation<F, K, V>>,
129    T: Translator,
130{
131    // Size the table for every replayed operation, an upper bound on the sets among them, so the
132    // replay neither counts them first nor grows the table as it goes. Commits are not indexed,
133    // so a log of tiny batches can leave the table one doubling larger than its entries need.
134    let end = log.bounds().end;
135    let mut snapshot = Index::new(
136        context,
137        translator,
138        *inactivity_floor_loc,
139        usize::try_from(end - *inactivity_floor_loc)
140            .expect("operations since the floor must fit in usize"),
141    );
142
143    // Init reads every operation once, so the replayed pages are not kept in the OS page cache:
144    // the init cache, not the OS cache, decides which probes hit.
145    let stream = log
146        .replay(
147            *inactivity_floor_loc..end,
148            init_buffer,
149            ReadOptions::DONT_CACHE,
150        )
151        .await?;
152    pin_mut!(stream);
153    while let Some(batch) = stream.next().await {
154        for (loc, op) in batch? {
155            if let Operation::Set(key, _) = op {
156                snapshot.insert(&key, loc);
157            }
158        }
159    }
160    Ok(snapshot)
161}
162
163/// Compute the authenticated root of a newly initialized database without opening storage.
164///
165/// The initial commit never carries metadata, so this root always represents `Commit(None, 0)`.
166pub fn initial_root<F, K, V, H>() -> H::Digest
167where
168    F: Family,
169    K: Key,
170    V: ValueEncoding,
171    H: Hasher,
172    Operation<F, K, V>: EncodeShared,
173{
174    single_operation_root::<F, H>(&Operation::<F, K, V>::Commit(None, Location::new(0)))
175}
176
177/// Configuration for an [Immutable] authenticated db.
178#[derive(Clone)]
179pub struct Config<T: Translator, J, S: Strategy> {
180    /// Configuration for the Merkle structure backing the authenticated journal.
181    pub merkle_config: MerkleConfig<S>,
182
183    /// Configuration for the operations log journal.
184    pub log: J,
185
186    /// The translator used by the compressed index.
187    pub translator: T,
188
189    /// Size (in bytes) of the read buffer used to replay the log during init.
190    pub init_buffer: NonZeroUsize,
191}
192
193/// An authenticated database that only supports adding new keyed values (no updates or
194/// deletions).
195///
196/// # Invariant
197///
198/// A key must be set at most once across the database history.
199///
200/// Use [fixed::Db] or [variable::Db] for concrete instantiations.
201pub struct Immutable<
202    F: Family,
203    E: Context,
204    K: Key,
205    V: ValueEncoding,
206    C: Mutable<Item = Operation<F, K, V>>,
207    H: Hasher,
208    T: Translator,
209    S: Strategy,
210> where
211    C::Item: EncodeShared,
212{
213    /// Authenticated journal of operations.
214    journal: authenticated::Journal<F, E, C, H, S>,
215
216    /// Cached canonical operations root.
217    pub(crate) root: H::Digest,
218
219    /// The location of the [Operation::Set] of every key set at or after the inactivity floor
220    /// the snapshot was built from, excluding pruned locations.
221    pub(crate) snapshot: Index<T>,
222
223    /// The inactivity floor declared by the last committed batch.
224    /// Operations before this location are considered inactive by the application.
225    pub(crate) inactivity_floor_loc: Location<F>,
226
227    /// Metrics for this database.
228    metrics: Metrics<E>,
229}
230
231impl<F, E, K, V, C, H, T, S> std::fmt::Debug for Immutable<F, E, K, V, C, H, T, S>
232where
233    F: Family,
234    E: Context,
235    K: Key,
236    V: ValueEncoding,
237    C: Mutable<Item = Operation<F, K, V>>,
238    C::Item: EncodeShared,
239    H: Hasher,
240    T: Translator,
241    S: Strategy,
242{
243    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
244        f.debug_struct("Immutable")
245            .field("bounds", &self.bounds())
246            .field("inactivity_floor_loc", &self.inactivity_floor_loc())
247            .finish_non_exhaustive()
248    }
249}
250
251// Shared read-only functionality.
252impl<F, E, K, V, C, H, T, S> Immutable<F, E, K, V, C, H, T, S>
253where
254    F: Family,
255    E: Context,
256    K: Key,
257    V: ValueEncoding,
258    C: Mutable<Item = Operation<F, K, V>>,
259    C::Item: EncodeShared,
260    H: Hasher,
261    T: Translator,
262    S: Strategy,
263{
264    /// Initialize from the latest retained commit, discarding every operation after it.
265    /// `Some(max_size)` selects the latest retained commit with at most `max_size` operations.
266    /// `None` selects the latest retained state.
267    #[boxed]
268    pub async fn init(
269        context: E,
270        cfg: Config<T, C::Config, S>,
271        max_size: Option<Location<F>>,
272    ) -> Result<Self, Error<F>>
273    where
274        C: authenticated::Backing<E>,
275    {
276        // Snapshot reconstruction replays from the selected commit's inactivity floor, so that
277        // floor must remain in the retained operation prefix.
278        let mut journal = crate::qmdb::init_journal::<F, E, C, H, S>(
279            context.child("journal"),
280            cfg.merkle_config,
281            cfg.log,
282            max_size,
283            true,
284        )
285        .await?;
286        if journal.size() == 0 {
287            warn!("Authenticated log is empty, initialized new db.");
288            (journal, _) = journal
289                .append(&Operation::Commit(None, Location::new(0)))
290                .await?;
291            journal = journal.sync().await?;
292        }
293
294        // Rebuild the in-memory state the last commit defines. Its inactivity floor is where
295        // the snapshot replay starts, so keys set before the floor are not loaded.
296        let (last_commit_loc, inactivity_floor_loc, snapshot) = {
297            let bounds = journal.journal.bounds();
298            let last_commit_loc =
299                Location::new(bounds.end.checked_sub(1).expect("commit should exist"));
300
301            // Read the floor from the last commit operation.
302            let last_op = journal.journal.read(*last_commit_loc).await?;
303            let inactivity_floor_loc = last_op
304                .has_floor()
305                .expect("last operation should be a commit with floor");
306
307            // Replay the log from the inactivity floor to build the snapshot.
308            let snapshot = build_snapshot(
309                context.child("snapshot"),
310                cfg.translator,
311                inactivity_floor_loc,
312                &journal.journal,
313                cfg.init_buffer,
314            )
315            .await?;
316
317            (last_commit_loc, inactivity_floor_loc, snapshot)
318        };
319        let inactive_peaks = F::inactive_peaks(last_commit_loc + 1, inactivity_floor_loc);
320        let root = journal.root(inactive_peaks)?;
321
322        let metrics = Metrics::new(context);
323        let db = Self {
324            journal,
325            root,
326            snapshot,
327            inactivity_floor_loc,
328            metrics,
329        };
330        db.update_metrics();
331        Ok(db)
332    }
333
334    /// Return the inactivity floor location declared by the last committed batch.
335    pub const fn inactivity_floor_loc(&self) -> Location<F> {
336        self.inactivity_floor_loc
337    }
338
339    /// Return the Location of the next operation appended to this db.
340    pub fn size(&self) -> Location<F> {
341        self.bounds().end
342    }
343
344    /// Return the retained operation range `[start, end)`.
345    ///
346    /// Proof generation also requires the necessary Merkle nodes to be retained.
347    pub fn bounds(&self) -> Range<Location<F>> {
348        Location::new(self.journal.bounds().start)..Location::new(self.journal.bounds().end)
349    }
350
351    /// Update state gauges from the current database state.
352    fn update_metrics(&self) {
353        let bounds = self.journal.bounds();
354        self.metrics.update(
355            bounds.end,
356            bounds.start,
357            *self.inactivity_floor_loc,
358            bounds.end - 1,
359        );
360    }
361
362    /// Return the most recent location from which this database can safely be synced, and the
363    /// upper bound on [`Self::prune`]'s `loc`. For immutable databases, this equals the
364    /// inactivity floor declared by the last committed batch.
365    pub const fn sync_boundary(&self) -> Location<F> {
366        self.inactivity_floor_loc
367    }
368
369    /// Get the value of `key` in the db, or None if it has no value or its corresponding operation
370    /// has been pruned.
371    ///
372    /// Unlike batch reads, this may return a value written below the inactivity floor, until the
373    /// database prunes that operation or rebuilds its index from the floor on restart or sync.
374    pub async fn get(&self, key: &K) -> Result<Option<V::Value>, Error<F>> {
375        self.get_from(key, Location::new(0)).await
376    }
377
378    /// Like [`Self::get`], but also ignores operations below `floor`.
379    pub(crate) async fn get_from(
380        &self,
381        key: &K,
382        floor: Location<F>,
383    ) -> Result<Option<V::Value>, Error<F>> {
384        let _timer = self.metrics.get_timer();
385        self.metrics.get_calls.inc();
386        self.metrics.lookups_requested.inc();
387        for loc in self.snapshot.get(key) {
388            let loc = Location::new(loc);
389            if loc < floor {
390                continue;
391            }
392            if let Some(v) = Self::get_from_loc(&self.journal, key, loc).await? {
393                return Ok(Some(v));
394            }
395        }
396        Ok(None)
397    }
398
399    /// Batch read multiple keys.
400    ///
401    /// Returns results in the same order as the input keys. Like [`Self::get`], this may return
402    /// values written below the inactivity floor.
403    pub async fn get_many(&self, keys: &[&K]) -> Result<Vec<Option<V::Value>>, Error<F>> {
404        self.get_many_from(keys, Location::new(0)).await
405    }
406
407    /// Like [`Self::get_many`], but also ignores operations below `floor`.
408    pub(crate) async fn get_many_from(
409        &self,
410        keys: &[&K],
411        floor: Location<F>,
412    ) -> Result<Vec<Option<V::Value>>, Error<F>> {
413        if keys.is_empty() {
414            return Ok(Vec::new());
415        }
416
417        let _timer = self.metrics.get_many_timer();
418        self.metrics.get_many_calls.inc();
419        self.metrics.lookups_requested.inc_by(keys.len() as u64);
420        let mut candidates: Vec<(usize, u64)> = Vec::with_capacity(keys.len());
421        let mut results: Vec<Option<V::Value>> = vec![None; keys.len()];
422
423        // Every location at or above `floor` whose translated key matches a requested key is a
424        // candidate for it, including collisions. They are read in one batch below, each location
425        // once, and resolved by comparing full keys.
426        for (key_idx, key) in keys.iter().enumerate() {
427            for loc in self.snapshot.get(key) {
428                if loc < *floor {
429                    continue;
430                }
431                candidates.push((key_idx, loc));
432            }
433        }
434
435        if candidates.is_empty() {
436            return Ok(results);
437        }
438
439        candidates.sort_unstable_by_key(|&(_, pos)| pos);
440
441        let same_pos = |a: &(usize, u64), b: &(usize, u64)| a.1 == b.1;
442        let positions: Vec<u64> = candidates
443            .chunk_by(same_pos)
444            .map(|group| group[0].1)
445            .collect();
446
447        let ops = self.journal.read_many(&positions).await?;
448        assert_eq!(
449            ops.len(),
450            positions.len(),
451            "read_many returns one operation per position"
452        );
453
454        for (op, group) in ops.into_iter().zip(candidates.chunk_by(same_pos)) {
455            let Operation::Set(k, v) = op else {
456                return Err(Error::UnexpectedData(Location::new(group[0].1)));
457            };
458            let mut pending = None;
459            for &(key_idx, _) in group {
460                if results[key_idx].is_some() || k != *keys[key_idx] {
461                    continue;
462                }
463                if let Some(prev) = pending.replace(key_idx) {
464                    results[prev] = Some(v.clone());
465                }
466            }
467            if let Some(last) = pending {
468                results[last] = Some(v);
469            }
470        }
471
472        Ok(results)
473    }
474
475    /// Get the value of the operation with location `loc` in the db if it matches `key`. Returns
476    /// [`crate::qmdb::Error::OperationPruned`] if loc precedes the oldest retained location. The
477    /// location is otherwise assumed valid.
478    async fn get_from_loc(
479        reader: &impl Contiguous<Item = Operation<F, K, V>>,
480        key: &K,
481        loc: Location<F>,
482    ) -> Result<Option<V::Value>, Error<F>> {
483        if loc < reader.bounds().start {
484            return Err(Error::OperationPruned(loc));
485        }
486
487        let Operation::Set(k, v) = reader.read(*loc).await? else {
488            return Err(Error::UnexpectedData(loc));
489        };
490
491        if k != *key { Ok(None) } else { Ok(Some(v)) }
492    }
493
494    /// Get the metadata associated with the last commit.
495    pub async fn get_metadata(&self) -> Result<Option<V::Value>, Error<F>> {
496        // The journal always ends with a commit operation.
497        let last_commit_loc = self.size() - 1;
498        let Operation::Commit(metadata, _floor) =
499            self.journal.journal.read(*last_commit_loc).await?
500        else {
501            unreachable!("no commit operation at location of last commit {last_commit_loc}");
502        };
503
504        Ok(metadata)
505    }
506
507    /// Analogous to proof but with respect to the state of the database when it had `op_count`
508    /// operations.
509    ///
510    /// # Contract
511    ///
512    /// `op_count` must be a commit-boundary size: the operation at `op_count - 1` must
513    /// itself be a commit op. Non-commit-boundary sizes are not supported because the
514    /// inactivity floor governing them is not directly retrievable.
515    ///
516    /// # Errors
517    ///
518    /// Returns [crate::merkle::Error::RangeOutOfBounds] if `op_count` > number of operations, or
519    /// if `start_loc` >= `op_count`.
520    /// Returns [`Error::Journal`] with [`crate::journal::Error::ItemPruned`] or [`Error::Merkle`]
521    /// with [`crate::merkle::Error::ElementPruned`] if a required operation or Merkle node has
522    /// been pruned.
523    /// Returns [`Error::HistoricalFloorPruned`] if `op_count - 1` is retained but is not a
524    /// commit op, either because the caller passed a non-commit-boundary `op_count` or
525    /// because pruning removed the commit that would have governed `op_count`.
526    #[allow(clippy::type_complexity)]
527    #[tracing::instrument(
528        name = "qmdb.immutable.db.historical_proof",
529        level = "info",
530        skip_all,
531        fields(
532            op_count = *op_count,
533            start_loc = *start_loc,
534            max_ops = max_ops.get(),
535        ),
536    )]
537    pub async fn historical_proof(
538        &self,
539        op_count: Location<F>,
540        start_loc: Location<F>,
541        max_ops: NonZeroU64,
542    ) -> Result<(Proof<F, H::Digest>, Vec<Operation<F, K, V>>), Error<F>> {
543        if op_count > self.journal.size() {
544            return Err(crate::merkle::Error::RangeOutOfBounds(op_count).into());
545        }
546
547        let inactive_peaks =
548            crate::qmdb::inactive_peaks_at::<F, _>(&self.journal, op_count).await?;
549
550        Ok(self
551            .journal
552            .historical_proof(op_count, start_loc, max_ops, inactive_peaks)
553            .await?)
554    }
555
556    /// Generate and return:
557    ///  1. a proof of all operations applied to the db in the range starting at (and including)
558    ///     location `start_loc`, and ending at the first of either:
559    ///     - the last operation performed, or
560    ///     - the operation `max_ops` from the start.
561    ///  2. the operations corresponding to the leaves in this range.
562    pub async fn proof(
563        &self,
564        start_index: Location<F>,
565        max_ops: NonZeroU64,
566    ) -> Result<(Proof<F, H::Digest>, Vec<Operation<F, K, V>>), Error<F>> {
567        let op_count = self.bounds().end;
568        self.historical_proof(op_count, start_index, max_ops).await
569    }
570
571    /// Prune operations prior to `loc` without changing the db's root. Keys whose operations
572    /// are no longer retained cannot be retrieved.
573    ///
574    /// The retained start in [`Self::bounds`] can remain below `loc`.
575    ///
576    /// Pruning is irreversible and requires no prior commit. After a crash, the database remains
577    /// recoverable; uncommitted operations are not guaranteed to survive.
578    ///
579    /// # Errors
580    ///
581    /// - Returns [Error::PruneBeyondMinRequired] if `loc` > inactivity floor.
582    #[tracing::instrument(name = "qmdb.immutable.db.prune", level = "info", skip_all)]
583    #[boxed]
584    pub async fn prune(mut self, loc: Location<F>) -> Result<Self, Error<F>> {
585        let _timer = self.metrics.prune_timer();
586        self.metrics.prune_calls.inc();
587        if loc > self.inactivity_floor_loc {
588            return Err(Error::PruneBeyondMinRequired(
589                loc,
590                self.inactivity_floor_loc,
591            ));
592        }
593        (self.journal, _) = self.journal.prune(loc).await?;
594        self.snapshot.prune(*self.bounds().start);
595        self.update_metrics();
596        Ok(self)
597    }
598
599    /// Return the canonical QMDB root of the db.
600    pub const fn root(&self) -> H::Digest {
601        self.root
602    }
603
604    /// Return a reference to the merkleization strategy.
605    pub const fn strategy(&self) -> &S {
606        self.journal.strategy()
607    }
608
609    /// Return the pinned Merkle nodes at the given location.
610    pub async fn pinned_nodes_at(&self, loc: Location<F>) -> Result<Vec<H::Digest>, Error<F>> {
611        self.journal
612            .merkle
613            .pinned_nodes_at(loc)
614            .await
615            .map_err(Into::into)
616    }
617
618    /// Sync all database state to disk. While this isn't necessary to ensure durability of
619    /// committed operations, periodic invocation may reduce memory usage and the time required to
620    /// recover the database on restart.
621    #[tracing::instrument(name = "qmdb.immutable.db.sync", level = "info", skip_all)]
622    pub async fn sync(mut self) -> Result<Self, Error<F>> {
623        let _timer = self.metrics.sync_timer();
624        self.metrics.sync_calls.inc();
625        self.journal = self.journal.sync().await?;
626        Ok(self)
627    }
628
629    /// Begin durably persisting the journal state published by prior [`Immutable::apply_batch`]
630    /// calls.
631    ///
632    /// Awaiting the returned [Handle] provides the same durability guarantee as [Self::commit],
633    /// plus a best-effort attempt to bound the recovery needed on startup. Use [Self::sync] to
634    /// guarantee none is needed. A new sync waits for the prior sync before starting. Failures
635    /// of the deferred durability work surface on the returned handle. A failed data sync also
636    /// fails the next durability operation. A failed recovery-watermark sync is not observed by
637    /// [Self::commit], and a failed merkle-node sync may not be. Both resurface on the next
638    /// [Self::sync].
639    #[tracing::instrument(name = "qmdb.immutable.db.start_sync", level = "info", skip_all)]
640    pub async fn start_sync(mut self) -> Result<(Self, Handle<()>), Error<F>> {
641        self.metrics.start_sync_calls.inc();
642        let handle;
643        (self.journal, handle) = self.journal.start_sync().await?;
644        Ok((self, handle))
645    }
646
647    /// Durably commit the journal state published by prior [`Immutable::apply_batch`] calls.
648    #[tracing::instrument(name = "qmdb.immutable.db.commit", level = "info", skip_all)]
649    pub async fn commit(mut self) -> Result<Self, Error<F>> {
650        let _timer = self.metrics.commit_timer();
651        self.metrics.commit_calls.inc();
652        self.journal = self.journal.commit().await?;
653        Ok(self)
654    }
655
656    /// Destroy the db, removing all data from disk.
657    #[boxed]
658    pub async fn destroy(self) -> Result<(), Error<F>> {
659        Ok(self.journal.destroy().await?)
660    }
661
662    /// The [`Commitment`](chain::Commitment) for the database's current state.
663    pub(crate) fn commitment(&self) -> chain::Commitment<F, H::Digest> {
664        chain::Commitment::new(self.size(), self.root)
665    }
666
667    /// Create a new speculative batch of operations with this database as its parent.
668    pub fn new_batch(&self) -> batch::UnmerkleizedBatch<F, H, K, V, S> {
669        batch::UnmerkleizedBatch::new(self, self.commitment())
670    }
671
672    /// Check that `batch` can be applied to the database in its current state, without
673    /// applying it.
674    ///
675    /// [`Self::apply_batch`] runs the same validation but consumes the database when it
676    /// fails; callers that want to reject a bad batch and keep the handle can check first.
677    pub fn validate_batch(
678        &self,
679        batch: &batch::MerkleizedBatch<F, H::Digest, K, V, S>,
680    ) -> Result<(), Error<F>> {
681        batch.bounds.validate_apply_to(self.commitment())
682    }
683
684    /// Apply a [`batch::MerkleizedBatch`] to the database.
685    ///
686    /// This publishes the batch to the in-memory database state and appends it to the journal.
687    /// Call [`Immutable::commit`] or [`Immutable::sync`], or await the handle returned by
688    /// [`Immutable::start_sync`], to make the applied state durable.
689    ///
690    /// # Errors
691    ///
692    /// Returns [`Error::StaleBatch`] if the database is at neither the batch's recorded database
693    /// boundary nor an ancestor's tip.
694    #[tracing::instrument(name = "qmdb.immutable.db.apply_batch", level = "info", skip_all)]
695    pub async fn apply_batch(
696        mut self,
697        batch: Arc<batch::MerkleizedBatch<F, H::Digest, K, V, S>>,
698    ) -> Result<Self, Error<F>> {
699        let _timer = self.metrics.apply_batch_timer();
700        self.metrics.apply_batch_calls.inc();
701        self.validate_batch(&batch)?;
702        let db_size = self.size();
703
704        // Journal writes and snapshot inserts are independent until batch publication.
705        let apply_snapshot = async {
706            for (key, entry) in batch.diff.iter() {
707                self.snapshot.insert(key, *entry.loc);
708            }
709            for (i, ancestor_diff) in batch.ancestor_diffs.iter().enumerate() {
710                if batch.bounds.ancestors[i].size <= db_size {
711                    continue;
712                }
713                for (key, entry) in ancestor_diff.iter() {
714                    self.snapshot.insert(key, *entry.loc);
715                }
716            }
717        };
718        let (journal, ()) = futures::join!(
719            self.journal.apply_batch(&batch.journal_batch),
720            apply_snapshot
721        );
722        self.journal = journal?;
723
724        // Update state.
725        self.inactivity_floor_loc = batch.bounds.inactivity_floor;
726        self.root = batch.root();
727        self.update_metrics();
728        self.metrics
729            .operations_applied
730            .inc_by(*batch.bounds.tip.size - *db_size);
731        Ok(self)
732    }
733}
734
735impl<F, E, K, V, C, H, T, S> crate::qmdb::sync::Source for Immutable<F, E, K, V, C, H, T, S>
736where
737    F: Family,
738    E: Context,
739    K: Key,
740    V: ValueEncoding,
741    C: Mutable<Item = Operation<F, K, V>>,
742    Operation<F, K, V>: EncodeShared,
743    H: Hasher,
744    T: Translator,
745    S: Strategy,
746{
747    type Family = F;
748    type Digest = H::Digest;
749    type Op = Operation<F, K, V>;
750    type Error = Error<F>;
751
752    async fn serve(&self, request: crate::qmdb::sync::Request<F>) -> source::Result<Self> {
753        self.journal.serve(request).await
754    }
755}
756
757#[cfg(test)]
758pub(super) mod tests {
759    use super::*;
760    use crate::{
761        merkle::{Family, Location},
762        qmdb::{verify_proof, verify_proof_and_pinned_nodes},
763        translator::TwoCap,
764    };
765    use commonware_codec::EncodeShared;
766    use commonware_cryptography::{Sha256, sha256, sha256::Digest};
767    use commonware_runtime::{Supervisor as _, deterministic};
768    use commonware_utils::NZU64;
769    use core::{future::Future, pin::Pin};
770
771    const ITEMS_PER_SECTION: u64 = 5;
772
773    /// Emits the named test against `mmr::Family` and `mmb::Family`.
774    macro_rules! immutable_tests {
775        ($($name:ident => $scenario:ident, $open:ident;)*) => {
776            $(
777                #[test_traced]
778                fn $name() {
779                    deterministic::Runner::default().start(|ctx| async move {
780                        immutable_tests!(@fixture $open, $scenario, mmr, ctx);
781                    });
782                }
783            )*
784            paste::paste! {
785                $(
786                    #[test_traced]
787                    fn [<$name _mmb>]() {
788                        deterministic::Runner::default().start(|ctx| async move {
789                            immutable_tests!(@fixture $open, $scenario, mmb, ctx);
790                        });
791                    }
792                )*
793            }
794        };
795        (@fixture pair, $scenario:ident, $family:ident, $ctx:ident) => {
796            let db = Db::<$family::Family, _, Digest, Digest, Sha256, TwoCap, Sequential>::init(
797                $ctx.child("db"), config("db"), None,
798            ).await.unwrap();
799            let foreign = Db::<$family::Family, _, Digest, Digest, Sha256, TwoCap, Sequential>::init(
800                $ctx.child("foreign"), config("foreign"), None,
801            ).await.unwrap();
802            tests::$scenario(db, foreign).await;
803        };
804        (@fixture $open:ident, $scenario:ident, $family:ident, $ctx:ident) => {
805            tests::$scenario($ctx, $open::<$family::Family>).await;
806        };
807    }
808
809    pub(super) use immutable_tests;
810
811    type TestDb<F, V, C> = Immutable<
812        F,
813        deterministic::Context,
814        Digest,
815        V,
816        C,
817        Sha256,
818        TwoCap,
819        commonware_parallel::Sequential,
820    >;
821
822    /// Reads stay exact after the ancestor batches are dropped, because merkleization
823    /// retains their diffs.
824    #[boxed]
825    pub(crate) async fn run_dropped_ancestor_reads<F: Family, V, C>(
826        context: deterministic::Context,
827        open_db: impl Fn(
828            deterministic::Context,
829        ) -> Pin<Box<dyn Future<Output = TestDb<F, V, C>> + Send>>,
830    ) where
831        V: ValueEncoding<Value = Digest>,
832        C: Mutable<Item = Operation<F, Digest, V>>,
833        C::Item: EncodeShared,
834    {
835        let db = open_db(context.child("db")).await;
836
837        // Seed the database with a key the chain never touches.
838        let untouched = Sha256::fill(0u8);
839        let untouched_value = Sha256::fill(100u8);
840        let seed = db
841            .new_batch()
842            .set(untouched, untouched_value)
843            .merkleize(&db, None, Location::new(0))
844            .await
845            .unwrap();
846        let db = db.apply_batch(seed).await.unwrap();
847
848        // A three-level unapplied chain, one key per level.
849        let a = db
850            .new_batch()
851            .set(Sha256::fill(1u8), Sha256::fill(101u8))
852            .merkleize(&db, None, Location::new(0))
853            .await
854            .unwrap();
855        let b = a
856            .new_batch::<Sha256>()
857            .set(Sha256::fill(2u8), Sha256::fill(102u8))
858            .merkleize(&db, None, Location::new(0))
859            .await
860            .unwrap();
861        let c = b
862            .new_batch::<Sha256>()
863            .set(Sha256::fill(3u8), Sha256::fill(103u8))
864            .merkleize(&db, None, Location::new(0))
865            .await
866            .unwrap();
867        let child = c.new_batch::<Sha256>();
868        drop(a);
869        drop(b);
870
871        // The merkleized batch and its unmerkleized child both resolve every
872        // ancestor write from the retained diffs.
873        for i in 0..4u8 {
874            let key = Sha256::fill(i);
875            let expected = Some(Sha256::fill(i.wrapping_add(100)));
876            assert_eq!(c.get(&key, &db).await.unwrap(), expected);
877            assert_eq!(child.get(&key, &db).await.unwrap(), expected);
878        }
879        let keys: Vec<_> = (0..4u8).map(Sha256::fill).collect();
880        let key_refs: Vec<_> = keys.iter().collect();
881        let expected: Vec<_> = (0..4u8)
882            .map(|i| Some(Sha256::fill(i.wrapping_add(100))))
883            .collect();
884        assert_eq!(c.get_many(&key_refs, &db).await.unwrap(), expected);
885        assert_eq!(child.get_many(&key_refs, &db).await.unwrap(), expected);
886
887        // Merkleizing the child returns `StaleBatch`, because its dropped ancestors are unapplied.
888        assert!(matches!(
889            child.merkleize(&db, None, Location::new(0)).await,
890            Err(Error::StaleBatch)
891        ));
892
893        // Applying the chain lands the same values the reads reported.
894        let db = db.apply_batch(c).await.unwrap();
895        for i in 0..4u8 {
896            assert_eq!(
897                db.get(&Sha256::fill(i)).await.unwrap(),
898                Some(Sha256::fill(i.wrapping_add(100)))
899            );
900        }
901        assert_eq!(db.get(&untouched).await.unwrap(), Some(untouched_value));
902        db.destroy().await.unwrap();
903    }
904
905    /// Speculative reads binary-search a merkleized batch's diff, so every key of a multi-key
906    /// batch must resolve before apply regardless of the order the keys were set in.
907    #[boxed]
908    pub(crate) async fn run_multi_key_batch_reads<F: Family, V, C>(
909        context: deterministic::Context,
910        open_db: impl Fn(
911            deterministic::Context,
912        ) -> Pin<Box<dyn Future<Output = TestDb<F, V, C>> + Send>>,
913    ) where
914        V: ValueEncoding<Value = Digest>,
915        C: Mutable<Item = Operation<F, Digest, V>>,
916        C::Item: EncodeShared,
917    {
918        let db = open_db(context.child("db")).await;
919
920        // Set the keys in descending order so the batch itself must order the diff.
921        let value = |i: u8| Sha256::fill(i.wrapping_add(100));
922        let mut batch = db.new_batch();
923        for i in (0..8u8).rev() {
924            batch = batch.set(Sha256::fill(i), value(i));
925        }
926        let parent = batch.merkleize(&db, None, Location::new(0)).await.unwrap();
927
928        let keys: Vec<_> = (0..8u8).map(Sha256::fill).collect();
929        let key_refs: Vec<_> = keys.iter().collect();
930        let expected: Vec<_> = (0..8u8).map(|i| Some(value(i))).collect();
931
932        // The batch resolves its own diff, an unmerkleized child resolves it through the parent,
933        // and a merkleized child resolves it as a retained ancestor diff.
934        assert_eq!(parent.get_many(&key_refs, &db).await.unwrap(), expected);
935        let draft = parent.new_batch::<Sha256>();
936        assert_eq!(draft.get_many(&key_refs, &db).await.unwrap(), expected);
937        let child = draft.merkleize(&db, None, Location::new(0)).await.unwrap();
938        assert_eq!(child.get_many(&key_refs, &db).await.unwrap(), expected);
939        for i in 0..8u8 {
940            assert_eq!(
941                child.get(&Sha256::fill(i), &db).await.unwrap(),
942                Some(value(i))
943            );
944        }
945
946        db.destroy().await.unwrap();
947    }
948
949    /// A batch's proof and pinned nodes return `StaleRead` once a sibling is applied.
950    #[boxed]
951    pub(crate) async fn run_proof_refused_after_sibling_apply<F: Family, V, C>(
952        context: deterministic::Context,
953        open_db: impl Fn(
954            deterministic::Context,
955        ) -> Pin<Box<dyn Future<Output = TestDb<F, V, C>> + Send>>,
956    ) where
957        V: ValueEncoding<Value = Digest>,
958        C: Mutable<Item = Operation<F, Digest, V>>,
959        C::Item: EncodeShared,
960    {
961        let db = open_db(context.child("db")).await;
962        let batch = db
963            .new_batch()
964            .set(Sha256::fill(1u8), Sha256::fill(101u8))
965            .merkleize(&db, None, Location::new(0))
966            .await
967            .unwrap();
968        let sibling = db
969            .new_batch()
970            .set(Sha256::fill(1u8), Sha256::fill(102u8))
971            .merkleize(&db, None, Location::new(0))
972            .await
973            .unwrap();
974        assert!(batch.proof(&db).is_ok());
975        assert!(batch.pinned_nodes(&db).is_ok());
976
977        // Applying the sibling moves the database to a state outside the batch's chain.
978        let db = db.apply_batch(sibling).await.unwrap();
979        assert!(matches!(batch.proof(&db), Err(Error::StaleRead)));
980        assert!(matches!(batch.pinned_nodes(&db), Err(Error::StaleRead)));
981        db.destroy().await.unwrap();
982    }
983
984    /// Batch reads cover only operations at or above the batch's inactivity floor, so a long-lived,
985    /// a pruned, and a reopened instance at the same commitment all answer alike.
986    #[boxed]
987    pub(crate) async fn run_reads_ignore_inactive_operations<F: Family, V, C>(
988        context: deterministic::Context,
989        open_db: impl Fn(
990            deterministic::Context,
991        ) -> Pin<Box<dyn Future<Output = TestDb<F, V, C>> + Send>>,
992    ) where
993        V: ValueEncoding<Value = Digest>,
994        C: Mutable<Item = Operation<F, Digest, V>>,
995        C::Item: EncodeShared,
996    {
997        let db = open_db(context.child("db")).await;
998        let (old, fresh, own) = (Sha256::fill(1u8), Sha256::fill(2u8), Sha256::fill(3u8));
999
1000        // `old` lands at location 1, and the commit at 2 keeps the floor at 0.
1001        let seed = db
1002            .new_batch()
1003            .set(old, Sha256::fill(101u8))
1004            .merkleize(&db, None, Location::new(0))
1005            .await
1006            .unwrap();
1007        let db = db.apply_batch(seed).await.unwrap();
1008
1009        // Filler keys push the next write past a blob boundary, so pruning can remove `old`.
1010        let mut filler = db.new_batch();
1011        for i in 10..18u8 {
1012            filler = filler.set(Sha256::fill(i), Sha256::fill(i));
1013        }
1014        let filler = filler.merkleize(&db, None, Location::new(0)).await.unwrap();
1015        let db = db.apply_batch(filler).await.unwrap();
1016
1017        // A parent writes `fresh` at `base` and raises the floor there, leaving `old` below it.
1018        let base = db.bounds().end;
1019        let parent = db
1020            .new_batch()
1021            .set(fresh, Sha256::fill(102u8))
1022            .merkleize(&db, None, base)
1023            .await
1024            .unwrap();
1025        assert_eq!(parent.get(&old, &db).await.unwrap(), None);
1026        assert_eq!(
1027            parent.get(&fresh, &db).await.unwrap(),
1028            Some(Sha256::fill(102u8))
1029        );
1030        let child = parent.new_batch::<Sha256>();
1031        assert_eq!(child.get(&old, &db).await.unwrap(), None);
1032        assert_eq!(
1033            child.get_many(&[&old, &fresh], &db).await.unwrap(),
1034            vec![None, Some(Sha256::fill(102u8))]
1035        );
1036
1037        // A child raising the floor to its commit, past every write including its own, sees none.
1038        let grandchild = child
1039            .set(own, Sha256::fill(103u8))
1040            .merkleize(&db, None, base + 3)
1041            .await
1042            .unwrap();
1043        assert_eq!(
1044            grandchild
1045                .get_many(&[&old, &fresh, &own], &db)
1046                .await
1047                .unwrap(),
1048            vec![None, None, None]
1049        );
1050        drop(grandchild);
1051
1052        // Apply the parent. The long-lived instance still indexes `old`, a pruned one has removed
1053        // it, and a reopened one rebuilds its index from the floor. Batch reads agree on all three.
1054        let db = db.apply_batch(parent).await.unwrap();
1055        let db = db.commit().await.unwrap();
1056        let expected = vec![None, Some(Sha256::fill(102u8))];
1057        assert_eq!(
1058            db.new_batch().get_many(&[&old, &fresh], &db).await.unwrap(),
1059            expected
1060        );
1061        assert_eq!(
1062            db.new_batch().get(&fresh, &db).await.unwrap(),
1063            Some(Sha256::fill(102u8))
1064        );
1065
1066        // `twin` shares `old`'s translated key, so the index yields both locations for either
1067        // key, and only the one at or above the floor is read.
1068        let mut twin = [1u8; 32];
1069        twin[31] = 9;
1070        let twin = Digest(twin);
1071        let collision = db
1072            .new_batch()
1073            .set(twin, Sha256::fill(104u8))
1074            .merkleize(&db, None, base)
1075            .await
1076            .unwrap();
1077        let db = db.apply_batch(collision).await.unwrap();
1078        let db = db.commit().await.unwrap();
1079        let collided = vec![None, Some(Sha256::fill(104u8))];
1080        assert_eq!(db.new_batch().get(&old, &db).await.unwrap(), None);
1081        assert_eq!(
1082            db.new_batch().get(&twin, &db).await.unwrap(),
1083            Some(Sha256::fill(104u8))
1084        );
1085        assert_eq!(
1086            db.new_batch().get_many(&[&old, &twin], &db).await.unwrap(),
1087            collided
1088        );
1089        let db = db.prune(base).await.unwrap();
1090        assert!(db.bounds().start > Location::new(1), "`old` must be pruned");
1091        assert_eq!(
1092            db.new_batch().get_many(&[&old, &fresh], &db).await.unwrap(),
1093            expected
1094        );
1095        assert_eq!(
1096            db.new_batch().get_many(&[&old, &twin], &db).await.unwrap(),
1097            collided
1098        );
1099        drop(db);
1100        let db = open_db(context.child("db")).await;
1101        assert_eq!(
1102            db.new_batch().get_many(&[&old, &fresh], &db).await.unwrap(),
1103            expected
1104        );
1105        assert_eq!(db.new_batch().get(&old, &db).await.unwrap(), None);
1106        assert_eq!(
1107            db.new_batch().get_many(&[&old, &twin], &db).await.unwrap(),
1108            collided
1109        );
1110        assert_eq!(
1111            db.new_batch().get(&twin, &db).await.unwrap(),
1112            Some(Sha256::fill(104u8))
1113        );
1114        db.destroy().await.unwrap();
1115    }
1116
1117    /// A child built before its parent was applied merkleizes to the same root after the
1118    /// database prunes past the chain's recorded database boundary.
1119    #[boxed]
1120    pub(crate) async fn run_merkleize_across_prune<F: Family, V, C>(
1121        context: deterministic::Context,
1122        open_db: impl Fn(
1123            deterministic::Context,
1124        ) -> Pin<Box<dyn Future<Output = TestDb<F, V, C>> + Send>>,
1125    ) where
1126        V: ValueEncoding<Value = Digest>,
1127        C: Mutable<Item = Operation<F, Digest, V>>,
1128        C::Item: EncodeShared,
1129    {
1130        let db = open_db(context.child("db")).await;
1131
1132        // Seed committed state spanning several journal blobs, so the prune below removes
1133        // history and the chain's recorded boundary sits above zero.
1134        let mut seed = db.new_batch();
1135        for i in 0..40u8 {
1136            seed = seed.set(Sha256::fill(i), Sha256::fill(i.wrapping_add(100)));
1137        }
1138        let seed = seed.merkleize(&db, None, Location::new(0)).await.unwrap();
1139        let db = db.apply_batch(seed).await.unwrap();
1140        let db = db.commit().await.unwrap();
1141
1142        // The parent declares a floor at its own commit, past the chain's recorded boundary.
1143        let parent_floor = db.bounds().end + 1;
1144        let parent = db
1145            .new_batch()
1146            .set(Sha256::fill(200u8), Sha256::fill(212u8))
1147            .merkleize(&db, None, parent_floor)
1148            .await
1149            .unwrap();
1150
1151        // Two identical children built before the apply. One merkleizes now to record the expected
1152        // root.
1153        let child = parent
1154            .new_batch::<Sha256>()
1155            .set(Sha256::fill(201u8), Sha256::fill(213u8));
1156        let twin = parent
1157            .new_batch::<Sha256>()
1158            .set(Sha256::fill(201u8), Sha256::fill(213u8));
1159        let expected = twin
1160            .merkleize(&db, None, parent_floor)
1161            .await
1162            .unwrap()
1163            .root();
1164
1165        // Apply the parent and prune to its floor.
1166        let db = db.apply_batch(Arc::clone(&parent)).await.unwrap();
1167        let db = db.commit().await.unwrap();
1168        let floor = db.inactivity_floor_loc();
1169        let db = db.prune(floor).await.unwrap();
1170        assert!(*db.bounds().start > 0, "the prune must remove history");
1171
1172        // The child built before the apply still merkleizes to the same root.
1173        let merkleized = child.merkleize(&db, None, parent_floor).await.unwrap();
1174        assert_eq!(merkleized.root(), expected);
1175        db.destroy().await.unwrap();
1176    }
1177
1178    /// Once a sibling batch is applied, reads through the other sibling and its child return
1179    /// `StaleRead`, while merkleizing the child and applying the other sibling return `StaleBatch`.
1180    #[boxed]
1181    pub(crate) async fn run_stale_fork_refuses<F: Family, V, C>(
1182        context: deterministic::Context,
1183        open_db: impl Fn(
1184            deterministic::Context,
1185        ) -> Pin<Box<dyn Future<Output = TestDb<F, V, C>> + Send>>,
1186    ) where
1187        V: ValueEncoding<Value = Digest>,
1188        C: Mutable<Item = Operation<F, Digest, V>>,
1189        C::Item: EncodeShared,
1190    {
1191        let db = open_db(context.child("db")).await;
1192        let k1 = Sha256::fill(1u8);
1193        let k2 = Sha256::fill(2u8);
1194        let floor = db.inactivity_floor_loc();
1195        let winner = db
1196            .new_batch()
1197            .set(k1, Sha256::fill(3u8))
1198            .merkleize(&db, None, floor)
1199            .await
1200            .unwrap();
1201        let loser = db
1202            .new_batch()
1203            .set(k1, Sha256::fill(4u8))
1204            .merkleize(&db, None, floor)
1205            .await
1206            .unwrap();
1207        assert_eq!(winner.bounds().tip.size, loser.bounds().tip.size);
1208        assert_ne!(winner.root(), loser.root());
1209
1210        let child = loser.new_batch::<Sha256>().set(k2, Sha256::fill(5u8));
1211        let direct = db.new_batch().set(k2, Sha256::fill(6u8));
1212        let db = db.apply_batch(winner).await.unwrap();
1213
1214        // A batch built directly on the database before the apply is stale too.
1215        assert!(matches!(direct.get(&k1, &db).await, Err(Error::StaleRead)));
1216        assert!(matches!(
1217            direct.get_many(&[&k1], &db).await,
1218            Err(Error::StaleRead)
1219        ));
1220
1221        assert!(matches!(child.get(&k1, &db).await, Err(Error::StaleRead)));
1222        assert!(matches!(
1223            child.get_many(&[&k1], &db).await,
1224            Err(Error::StaleRead)
1225        ));
1226        assert!(matches!(loser.get(&k1, &db).await, Err(Error::StaleRead)));
1227
1228        // The compatibility check precedes every other read argument check, so a read of no keys
1229        // returns `StaleRead` too.
1230        let no_keys: [&Digest; 0] = [];
1231        assert!(matches!(
1232            child.get_many(&no_keys, &db).await,
1233            Err(Error::StaleRead)
1234        ));
1235        assert!(matches!(
1236            loser.get_many(&no_keys, &db).await,
1237            Err(Error::StaleRead)
1238        ));
1239        assert!(matches!(
1240            child.merkleize(&db, None, floor).await,
1241            Err(Error::StaleBatch)
1242        ));
1243        assert!(matches!(db.validate_batch(&loser), Err(Error::StaleBatch)));
1244        db.destroy().await.unwrap();
1245    }
1246
1247    /// Applying a batch's child moves the database to a state outside the parent's chain, so
1248    /// reads through the parent return `StaleRead` while the child keeps reading.
1249    #[boxed]
1250    pub(crate) async fn run_descendant_apply_makes_parent_reads_stale<F: Family, V, C>(
1251        context: deterministic::Context,
1252        open_db: impl Fn(
1253            deterministic::Context,
1254        ) -> Pin<Box<dyn Future<Output = TestDb<F, V, C>> + Send>>,
1255    ) where
1256        V: ValueEncoding<Value = Digest>,
1257        C: Mutable<Item = Operation<F, Digest, V>>,
1258        C::Item: EncodeShared,
1259    {
1260        let db = open_db(context.child("db")).await;
1261        let (k1, k2) = (Sha256::fill(1u8), Sha256::fill(2u8));
1262        let floor = db.inactivity_floor_loc();
1263        let parent = db
1264            .new_batch()
1265            .set(k1, Sha256::fill(3u8))
1266            .merkleize(&db, None, floor)
1267            .await
1268            .unwrap();
1269        assert_eq!(parent.get(&k1, &db).await.unwrap(), Some(Sha256::fill(3u8)));
1270        let child = parent
1271            .new_batch::<Sha256>()
1272            .set(k2, Sha256::fill(4u8))
1273            .merkleize(&db, None, floor)
1274            .await
1275            .unwrap();
1276
1277        let db = db.apply_batch(child.clone()).await.unwrap();
1278        assert_eq!(child.get(&k1, &db).await.unwrap(), Some(Sha256::fill(3u8)));
1279        assert!(matches!(parent.get(&k1, &db).await, Err(Error::StaleRead)));
1280        assert!(matches!(
1281            parent.get_many(&[&k1], &db).await,
1282            Err(Error::StaleRead)
1283        ));
1284        db.destroy().await.unwrap();
1285    }
1286
1287    #[boxed]
1288    pub(crate) async fn run_empty<F: Family, V, C>(
1289        context: deterministic::Context,
1290        open_db: impl Fn(
1291            deterministic::Context,
1292        ) -> Pin<Box<dyn Future<Output = TestDb<F, V, C>> + Send>>,
1293    ) where
1294        V: ValueEncoding<Value = Digest>,
1295        C: Mutable<Item = Operation<F, Digest, V>>,
1296        C::Item: EncodeShared,
1297    {
1298        let db = open_db(context.child("first")).await;
1299        let bounds = db.bounds();
1300        assert_eq!(bounds.end, 1);
1301        assert_eq!(bounds.start, Location::new(0));
1302        assert_eq!(db.inactivity_floor_loc(), Location::new(0));
1303        assert!(db.get_metadata().await.unwrap().is_none());
1304
1305        // Make sure closing/reopening gets us back to the same state, even after adding an uncommitted op.
1306        let k1 = Sha256::fill(1u8);
1307        let v1 = Sha256::fill(2u8);
1308        let root = db.root();
1309        {
1310            let _batch = db.new_batch().set(k1, v1);
1311
1312            // Don't merkleize/apply. This simulates a failed commit.
1313        }
1314        drop(db);
1315        let db = open_db(context.child("second")).await;
1316        assert_eq!(db.root(), root);
1317        assert_eq!(db.bounds().end, 1);
1318
1319        // Test calling commit on an empty db which should make it (durably) non-empty.
1320        let merkleized = db
1321            .new_batch()
1322            .merkleize(&db, None, Location::new(0))
1323            .await
1324            .unwrap();
1325        let db = db.apply_batch(merkleized).await.unwrap();
1326        let db = db.commit().await.unwrap();
1327        assert_eq!(db.bounds().end, 2); // commit op added
1328        let root = db.root();
1329        drop(db);
1330
1331        let db = open_db(context.child("third")).await;
1332        assert_eq!(db.root(), root);
1333
1334        db.destroy().await.unwrap();
1335    }
1336
1337    #[boxed]
1338    pub(crate) async fn run_commit_after_sync_recovery<F: Family, V, C>(
1339        context: deterministic::Context,
1340        open_db: impl Fn(
1341            deterministic::Context,
1342        ) -> Pin<Box<dyn Future<Output = TestDb<F, V, C>> + Send>>,
1343    ) where
1344        V: ValueEncoding<Value = Digest>,
1345        C: Mutable<Item = Operation<F, Digest, V>>,
1346        C::Item: EncodeShared,
1347    {
1348        let db = open_db(context.child("first")).await;
1349        let k1 = Sha256::fill(1u8);
1350        let k2 = Sha256::fill(2u8);
1351        let v1 = Sha256::fill(3u8);
1352        let v2 = Sha256::fill(4u8);
1353
1354        // Commit and sync the first key so recovery has an older persisted boundary.
1355        let db = commit_sets(db, [(k1, v1)], None).await;
1356        let db = db.sync().await.unwrap();
1357
1358        // Commit a second key without syncing; reopen must replay it from journal data.
1359        let db = commit_sets(db, [(k2, v2)], None).await;
1360        let committed_bounds = db.bounds();
1361        let committed_root = db.root();
1362        drop(db);
1363
1364        let db = open_db(context.child("second")).await;
1365        assert_eq!(db.bounds(), committed_bounds);
1366        assert_eq!(db.root(), committed_root);
1367        assert_eq!(db.get(&k1).await.unwrap(), Some(v1));
1368        assert_eq!(db.get(&k2).await.unwrap(), Some(v2));
1369
1370        db.destroy().await.unwrap();
1371    }
1372
1373    #[boxed]
1374    pub(crate) async fn run_build_basic<F: Family, V, C>(
1375        context: deterministic::Context,
1376        open_db: impl Fn(
1377            deterministic::Context,
1378        ) -> Pin<Box<dyn Future<Output = TestDb<F, V, C>> + Send>>,
1379    ) where
1380        V: ValueEncoding<Value = Digest>,
1381        C: Mutable<Item = Operation<F, Digest, V>>,
1382        C::Item: EncodeShared,
1383    {
1384        // Build a db with 2 keys.
1385        let db = open_db(context.child("first")).await;
1386
1387        let k1 = Sha256::fill(1u8);
1388        let k2 = Sha256::fill(2u8);
1389        let v1 = Sha256::fill(3u8);
1390        let v2 = Sha256::fill(4u8);
1391
1392        assert!(db.get(&k1).await.unwrap().is_none());
1393        assert!(db.get(&k2).await.unwrap().is_none());
1394
1395        // Set and commit the first key.
1396        let metadata = Some(Sha256::fill(99u8));
1397        let merkleized = db
1398            .new_batch()
1399            .set(k1, v1)
1400            .merkleize(&db, metadata, Location::new(0))
1401            .await
1402            .unwrap();
1403        let db = db.apply_batch(merkleized).await.unwrap();
1404        let db = db.commit().await.unwrap();
1405        assert_eq!(db.get(&k1).await.unwrap().unwrap(), v1);
1406        assert!(db.get(&k2).await.unwrap().is_none());
1407        assert_eq!(db.bounds().end, 3);
1408        assert_eq!(db.get_metadata().await.unwrap(), Some(Sha256::fill(99u8)));
1409
1410        // Set and commit the second key.
1411        let merkleized = db
1412            .new_batch()
1413            .set(k2, v2)
1414            .merkleize(&db, None, Location::new(0))
1415            .await
1416            .unwrap();
1417        let db = db.apply_batch(merkleized).await.unwrap();
1418        let db = db.commit().await.unwrap();
1419        assert_eq!(db.get(&k1).await.unwrap().unwrap(), v1);
1420        assert_eq!(db.get(&k2).await.unwrap().unwrap(), v2);
1421        assert_eq!(db.bounds().end, 5);
1422        assert_eq!(db.get_metadata().await.unwrap(), None);
1423
1424        // Capture state.
1425        let root = db.root();
1426
1427        // Add an uncommitted op then simulate failure.
1428        let k3 = Sha256::fill(5u8);
1429        let v3 = Sha256::fill(6u8);
1430        {
1431            let _batch = db.new_batch().set(k3, v3);
1432
1433            // Don't merkleize/apply. This simulates a failed commit.
1434        }
1435
1436        // Reopen, make sure state is restored to last commit point.
1437        drop(db); // Simulate failed commit
1438        let db = open_db(context.child("second")).await;
1439        assert!(db.get(&k3).await.unwrap().is_none());
1440        assert_eq!(db.root(), root);
1441        assert_eq!(db.get(&k1).await.unwrap().unwrap(), v1);
1442        assert_eq!(db.get(&k2).await.unwrap().unwrap(), v2);
1443        assert_eq!(db.bounds().end, 5);
1444        assert_eq!(db.get_metadata().await.unwrap(), None);
1445
1446        // Cleanup.
1447        db.destroy().await.unwrap();
1448    }
1449
1450    #[boxed]
1451    pub(crate) async fn run_proof_verify<F: Family, V, C>(
1452        context: deterministic::Context,
1453        open_db: impl Fn(
1454            deterministic::Context,
1455        ) -> Pin<Box<dyn Future<Output = TestDb<F, V, C>> + Send>>,
1456    ) where
1457        V: ValueEncoding<Value = Digest>,
1458        C: Mutable<Item = Operation<F, Digest, V>>,
1459        C::Item: EncodeShared,
1460    {
1461        let db = open_db(context.child("first")).await;
1462
1463        let k1 = Sha256::fill(1u8);
1464        let v1 = Sha256::fill(10u8);
1465        let merkleized = db
1466            .new_batch()
1467            .set(k1, v1)
1468            .merkleize(&db, None, Location::new(0))
1469            .await
1470            .unwrap();
1471        let db = db.apply_batch(merkleized).await.unwrap();
1472        let db = db.commit().await.unwrap();
1473
1474        let (proof, ops) = db.proof(Location::new(0), NZU64!(100)).await.unwrap();
1475        let root = db.root();
1476        assert!(verify_proof::<Sha256, _, _>(
1477            &proof,
1478            Location::new(0),
1479            &ops,
1480            &root
1481        ));
1482
1483        db.destroy().await.unwrap();
1484    }
1485
1486    #[boxed]
1487    pub(crate) async fn run_prune<F: Family, V, C>(
1488        context: deterministic::Context,
1489        open_db: impl Fn(
1490            deterministic::Context,
1491        ) -> Pin<Box<dyn Future<Output = TestDb<F, V, C>> + Send>>,
1492    ) where
1493        V: ValueEncoding<Value = Digest>,
1494        C: Mutable<Item = Operation<F, Digest, V>>,
1495        C::Item: EncodeShared,
1496    {
1497        let mut db = open_db(context.child("first")).await;
1498
1499        for i in 0..20u8 {
1500            let key = Sha256::fill(i);
1501            let value = Sha256::fill(i.wrapping_add(100));
1502            let floor = db.bounds().end;
1503            let merkleized = db
1504                .new_batch()
1505                .set(key, value)
1506                .merkleize(&db, None, floor)
1507                .await
1508                .unwrap();
1509            db = db.apply_batch(merkleized).await.unwrap();
1510            db = db.commit().await.unwrap();
1511        }
1512
1513        let root_before = db.root();
1514        let bounds_before = db.bounds();
1515
1516        let prune_loc = bounds_before.end - 5;
1517        let db = db.prune(prune_loc).await.unwrap();
1518
1519        assert_eq!(db.root(), root_before);
1520
1521        let key_0 = Sha256::fill(0u8);
1522        assert!(db.get(&key_0).await.unwrap().is_none());
1523
1524        let key_19 = Sha256::fill(19u8);
1525        assert_eq!(
1526            db.get(&key_19).await.unwrap(),
1527            Some(Sha256::fill(19u8.wrapping_add(100)))
1528        );
1529
1530        db.destroy().await.unwrap();
1531    }
1532
1533    /// Pruning immediately after an uncommitted batch must leave the database recoverable. Since
1534    /// prune is not a durability boundary, recovery may return either the durable baseline or the
1535    /// buffered state, but never a mixture whose floor references pruned operations.
1536    #[boxed]
1537    pub(crate) async fn run_prune_after_uncommitted_apply_batch_recovery<F: Family, V, C>(
1538        context: deterministic::Context,
1539        open_db: impl Fn(
1540            deterministic::Context,
1541        ) -> Pin<Box<dyn Future<Output = TestDb<F, V, C>> + Send>>,
1542    ) where
1543        V: ValueEncoding<Value = Digest>,
1544        C: Mutable<Item = Operation<F, Digest, V>>,
1545        C::Item: EncodeShared,
1546    {
1547        let db = open_db(context.child("first")).await;
1548
1549        // Fill more than one journal blob and establish a durable baseline whose floor still
1550        // requires the oldest blob.
1551        let mut batch = db.new_batch();
1552        for i in 0..6u8 {
1553            batch = batch.set(Sha256::fill(i), Sha256::fill(i.wrapping_add(10)));
1554        }
1555        let merkleized = batch.merkleize(&db, None, Location::new(0)).await.unwrap();
1556        let db = db.apply_batch(merkleized).await.unwrap();
1557        let db = db.sync().await.unwrap();
1558        let durable_state = (db.root(), db.inactivity_floor_loc(), db.bounds().end);
1559
1560        // Apply, but do not commit, a batch that advances the floor far enough for prune to
1561        // remove the oldest blob.
1562        let buffered_floor = db.bounds().end;
1563        let key = Sha256::fill(100);
1564        let value = Sha256::fill(101);
1565        let merkleized = db
1566            .new_batch()
1567            .set(key, value)
1568            .merkleize(&db, None, buffered_floor)
1569            .await
1570            .unwrap();
1571        let db = db.apply_batch(merkleized).await.unwrap();
1572        let buffered_state = (db.root(), db.inactivity_floor_loc(), db.bounds().end);
1573        assert_ne!(buffered_state, durable_state);
1574
1575        let db = db.prune(buffered_floor).await.unwrap();
1576        assert!(db.bounds().start > Location::new(0));
1577        drop(db);
1578
1579        // Reopen must produce one coherent state. In particular, it must not recover the old
1580        // floor after the prune has removed operations that floor still needs.
1581        let db = open_db(context.child("second")).await;
1582        assert!(db.bounds().start <= db.inactivity_floor_loc());
1583        let recovered_state = (db.root(), db.inactivity_floor_loc(), db.bounds().end);
1584        assert!(
1585            recovered_state == durable_state || recovered_state == buffered_state,
1586            "recovered state is neither the durable baseline nor the buffered state"
1587        );
1588
1589        db.destroy().await.unwrap();
1590    }
1591
1592    /// `operations()` must cover exactly the batch's own applied range and match the
1593    /// operations a post-apply `historical_proof` recovers from the log, for a db-based
1594    /// batch and for a chained batch applied after its ancestor.
1595    #[boxed]
1596    pub(crate) async fn run_operations_match_applied_log<F: Family, V, C>(
1597        context: deterministic::Context,
1598        open_db: impl Fn(
1599            deterministic::Context,
1600        ) -> Pin<Box<dyn Future<Output = TestDb<F, V, C>> + Send>>,
1601    ) where
1602        V: ValueEncoding<Value = Digest>,
1603        C: Mutable<Item = Operation<F, Digest, V>>,
1604        C::Item: EncodeShared + PartialEq + core::fmt::Debug,
1605    {
1606        let db = open_db(context.child("db")).await;
1607
1608        let seed = db
1609            .new_batch()
1610            .set(Sha256::fill(1u8), Sha256::fill(11u8))
1611            .set(Sha256::fill(2u8), Sha256::fill(12u8))
1612            .merkleize(&db, None, db.inactivity_floor_loc())
1613            .await
1614            .unwrap();
1615        let (seed_start, seed_ops) = seed.operations();
1616        let seed_root = seed.root();
1617        let seed_proof = seed.proof(&db).unwrap();
1618        let seed_pins = seed.pinned_nodes(&db).unwrap();
1619        let applied_start = db.bounds().end;
1620        let db = db.apply_batch(seed).await.unwrap();
1621        let seed_range = applied_start..db.bounds().end;
1622        assert_eq!(seed_start, seed_range.start);
1623        assert_eq!(*seed_start + seed_ops.len() as u64, *seed_range.end);
1624
1625        // A chained batch's operations are its own suffix only.
1626        let parent = db
1627            .new_batch()
1628            .set(Sha256::fill(3u8), Sha256::fill(13u8))
1629            .merkleize(&db, None, db.inactivity_floor_loc())
1630            .await
1631            .unwrap();
1632        let child = parent
1633            .new_batch::<Sha256>()
1634            .set(Sha256::fill(4u8), Sha256::fill(14u8))
1635            .merkleize(&db, None, db.inactivity_floor_loc())
1636            .await
1637            .unwrap();
1638        let (parent_start, parent_ops) = parent.operations();
1639        let (child_start, child_ops) = child.operations();
1640        let (parent_root, child_root) = (parent.root(), child.root());
1641        let (parent_pins, child_pins) = (
1642            parent.pinned_nodes(&db).unwrap(),
1643            child.pinned_nodes(&db).unwrap(),
1644        );
1645        let (parent_proof, child_proof) = (parent.proof(&db).unwrap(), child.proof(&db).unwrap());
1646        let applied_start = db.bounds().end;
1647        let db = db.apply_batch(parent).await.unwrap();
1648        let parent_range = applied_start..db.bounds().end;
1649
1650        // With the database at the parent's tip, the child's proof and pinned nodes are
1651        // unchanged.
1652        assert_eq!(child.proof(&db).unwrap(), child_proof);
1653        assert_eq!(child.pinned_nodes(&db).unwrap(), child_pins);
1654        let applied_start = db.bounds().end;
1655        let db = db.apply_batch(child).await.unwrap();
1656        let child_range = applied_start..db.bounds().end;
1657        assert_eq!(parent_start, parent_range.start);
1658        assert_eq!(*parent_start + parent_ops.len() as u64, *parent_range.end);
1659        assert_eq!(child_start, child_range.start);
1660        assert_eq!(*child_start + child_ops.len() as u64, *child_range.end);
1661
1662        // A write-free batch still captures its commit-only suffix.
1663        let empty = db
1664            .new_batch()
1665            .merkleize(&db, None, db.inactivity_floor_loc())
1666            .await
1667            .unwrap();
1668        let (empty_start, empty_ops) = empty.operations();
1669        let (empty_root, empty_proof) = (empty.root(), empty.proof(&db).unwrap());
1670        let empty_pins = empty.pinned_nodes(&db).unwrap();
1671        let applied_start = db.bounds().end;
1672        let db = db.apply_batch(empty).await.unwrap();
1673        let empty_range = applied_start..db.bounds().end;
1674        assert_eq!(empty_start, empty_range.start);
1675        assert_eq!(*empty_start + empty_ops.len() as u64, *empty_range.end);
1676
1677        // Every captured delta and proof must match what the log recovers for its
1678        // range, and verify against the batch's own root with and without the pins.
1679        for (start, ops, proof, pins, root) in [
1680            (seed_start, seed_ops, seed_proof, seed_pins, seed_root),
1681            (
1682                parent_start,
1683                parent_ops,
1684                parent_proof,
1685                parent_pins,
1686                parent_root,
1687            ),
1688            (child_start, child_ops, child_proof, child_pins, child_root),
1689            (empty_start, empty_ops, empty_proof, empty_pins, empty_root),
1690        ] {
1691            let len = core::num::NonZeroU64::new(ops.len() as u64).unwrap();
1692            let end = Location::new(*start + ops.len() as u64);
1693            let (log_proof, log_ops) = db.historical_proof(end, start, len).await.unwrap();
1694            assert_eq!(log_ops, *ops);
1695            assert_eq!(log_proof, proof);
1696            assert!(verify_proof::<Sha256, _, _>(&proof, start, &ops, &root));
1697            assert!(verify_proof_and_pinned_nodes::<Sha256, _, _>(
1698                &proof, start, &ops, &pins, &root
1699            ));
1700        }
1701
1702        // Flushing the applied batch prunes the store to its peaks. The late batch's base is
1703        // mid-mountain, so its artifacts return `ElementPruned` rather than unverifiable data.
1704        let late = db
1705            .new_batch()
1706            .set(Sha256::fill(5u8), Sha256::fill(15u8))
1707            .merkleize(&db, None, db.inactivity_floor_loc())
1708            .await
1709            .unwrap();
1710        let db = db.apply_batch(Arc::clone(&late)).await.unwrap();
1711        let db = db.commit().await.unwrap();
1712        assert!(matches!(
1713            late.proof(&db),
1714            Err(crate::qmdb::Error::Merkle(
1715                crate::merkle::Error::ElementPruned(_)
1716            ))
1717        ));
1718        assert!(matches!(
1719            late.pinned_nodes(&db),
1720            Err(crate::qmdb::Error::Merkle(
1721                crate::merkle::Error::ElementPruned(_)
1722            ))
1723        ));
1724
1725        // A batch built on the flushed store reads every node below it from the pinned peaks.
1726        let flushed = db
1727            .new_batch()
1728            .set(Sha256::fill(6u8), Sha256::fill(16u8))
1729            .merkleize(&db, None, db.inactivity_floor_loc())
1730            .await
1731            .unwrap();
1732        let (flushed_start, flushed_ops) = flushed.operations();
1733        let flushed_root = flushed.root();
1734        let flushed_proof = flushed.proof(&db).unwrap();
1735        let flushed_pins = flushed.pinned_nodes(&db).unwrap();
1736        assert!(verify_proof_and_pinned_nodes::<Sha256, _, _>(
1737            &flushed_proof,
1738            flushed_start,
1739            &flushed_ops,
1740            &flushed_pins,
1741            &flushed_root
1742        ));
1743        let applied_start = db.bounds().end;
1744        let db = db.apply_batch(flushed).await.unwrap();
1745        let flushed_range = applied_start..db.bounds().end;
1746        assert_eq!(flushed_start, flushed_range.start);
1747        assert_eq!(
1748            *flushed_start + flushed_ops.len() as u64,
1749            *flushed_range.end
1750        );
1751
1752        db.destroy().await.unwrap();
1753    }
1754
1755    #[boxed]
1756    pub(crate) async fn run_batch_chain<F: Family, V, C>(
1757        context: deterministic::Context,
1758        open_db: impl Fn(
1759            deterministic::Context,
1760        ) -> Pin<Box<dyn Future<Output = TestDb<F, V, C>> + Send>>,
1761    ) where
1762        V: ValueEncoding<Value = Digest>,
1763        C: Mutable<Item = Operation<F, Digest, V>>,
1764        C::Item: EncodeShared,
1765    {
1766        let db = open_db(context.child("first")).await;
1767
1768        let k1 = Sha256::fill(1u8);
1769        let k2 = Sha256::fill(2u8);
1770        let k3 = Sha256::fill(3u8);
1771        let v1 = Sha256::fill(11u8);
1772        let v2 = Sha256::fill(12u8);
1773        let v3 = Sha256::fill(13u8);
1774
1775        let parent = db
1776            .new_batch()
1777            .set(k1, v1)
1778            .merkleize(&db, None, Location::new(0))
1779            .await
1780            .unwrap();
1781        let child = parent
1782            .new_batch::<Sha256>()
1783            .set(k2, v2)
1784            .merkleize(&db, None, Location::new(0))
1785            .await
1786            .unwrap();
1787
1788        assert_eq!(child.get(&k1, &db).await.unwrap(), Some(v1));
1789        assert_eq!(child.get(&k2, &db).await.unwrap(), Some(v2));
1790        assert!(child.get(&k3, &db).await.unwrap().is_none());
1791
1792        let db = db.apply_batch(child).await.unwrap().commit().await.unwrap();
1793
1794        assert_eq!(db.get(&k1).await.unwrap(), Some(v1));
1795        assert_eq!(db.get(&k2).await.unwrap(), Some(v2));
1796
1797        let merkleized = db
1798            .new_batch()
1799            .set(k3, v3)
1800            .merkleize(&db, None, Location::new(0))
1801            .await
1802            .unwrap();
1803        let db = db.apply_batch(merkleized).await.unwrap();
1804        let db = db.commit().await.unwrap();
1805        assert_eq!(db.get(&k3).await.unwrap(), Some(v3));
1806
1807        db.destroy().await.unwrap();
1808    }
1809
1810    #[boxed]
1811    pub(crate) async fn run_build_and_authenticate<F: Family, V, C>(
1812        context: deterministic::Context,
1813        open_db: impl Fn(
1814            deterministic::Context,
1815        ) -> Pin<Box<dyn Future<Output = TestDb<F, V, C>> + Send>>,
1816    ) where
1817        V: ValueEncoding<Value = Digest>,
1818        C: Mutable<Item = Operation<F, Digest, V>>,
1819        C::Item: EncodeShared,
1820    {
1821        // Build a db with `ELEMENTS` key/value pairs and prove ranges over them.
1822        let db = open_db(context.child("first")).await;
1823
1824        let mut batch = db.new_batch();
1825        for i in 0u64..2_000 {
1826            let k = Sha256::hash(&[&i.to_be_bytes()]);
1827            let v = Sha256::fill(i as u8);
1828            batch = batch.set(k, v);
1829        }
1830        let merkleized = batch.merkleize(&db, None, Location::new(0)).await.unwrap();
1831        let db = db.apply_batch(merkleized).await.unwrap();
1832        let db = db.commit().await.unwrap();
1833        assert_eq!(db.bounds().end, 2_000 + 2);
1834
1835        // Drop & reopen the db, making sure it has exactly the same state.
1836        let root = db.root();
1837        drop(db);
1838
1839        let db = open_db(context.child("second")).await;
1840        assert_eq!(root, db.root());
1841        assert_eq!(db.bounds().end, 2_000 + 2);
1842        for i in 0u64..2_000 {
1843            let k = Sha256::hash(&[&i.to_be_bytes()]);
1844            let v = Sha256::fill(i as u8);
1845            assert_eq!(db.get(&k).await.unwrap().unwrap(), v);
1846        }
1847
1848        // Make sure all ranges of 5 operations are provable, including truncated ranges at the
1849        // end.
1850        let max_ops = NZU64!(5);
1851        for i in 0..*db.bounds().end {
1852            let (proof, log) = db.proof(Location::new(i), max_ops).await.unwrap();
1853            assert!(verify_proof::<Sha256, _, _>(
1854                &proof,
1855                Location::new(i),
1856                &log,
1857                &root
1858            ));
1859        }
1860
1861        db.destroy().await.unwrap();
1862    }
1863
1864    #[boxed]
1865    pub(crate) async fn run_recovery_from_failed_merkle_sync<F: Family, V, C>(
1866        context: deterministic::Context,
1867        open_db: impl Fn(
1868            deterministic::Context,
1869        ) -> Pin<Box<dyn Future<Output = TestDb<F, V, C>> + Send>>,
1870    ) where
1871        V: ValueEncoding<Value = Digest>,
1872        C: Mutable<Item = Operation<F, Digest, V>>,
1873        C::Item: EncodeShared,
1874    {
1875        // Insert 1000 keys then sync.
1876        const ELEMENTS: u64 = 1000;
1877        let db = open_db(context.child("first")).await;
1878
1879        let mut batch = db.new_batch();
1880        for i in 0u64..ELEMENTS {
1881            let k = Sha256::hash(&[&i.to_be_bytes()]);
1882            let v = Sha256::fill(i as u8);
1883            batch = batch.set(k, v);
1884        }
1885        let merkleized = batch.merkleize(&db, None, Location::new(0)).await.unwrap();
1886        let db = db.apply_batch(merkleized).await.unwrap();
1887        let db = db.commit().await.unwrap();
1888        assert_eq!(db.bounds().end, ELEMENTS + 2);
1889        let db = db.sync().await.unwrap();
1890        let halfway_root = db.root();
1891
1892        // Insert another 1000 keys (different from the first batch) then commit.
1893        let mut batch = db.new_batch();
1894        for i in ELEMENTS..ELEMENTS * 2 {
1895            let k = Sha256::hash(&[&i.to_be_bytes()]);
1896            let v = Sha256::fill(i as u8);
1897            batch = batch.set(k, v);
1898        }
1899        let merkleized = batch.merkleize(&db, None, Location::new(0)).await.unwrap();
1900        let db = db.apply_batch(merkleized).await.unwrap();
1901        db.commit().await.unwrap(); // Drop before syncing
1902
1903        // Recovery should replay the log to regenerate the merkle structure.
1904        // op_count = 1002 (first batch + commit) + 1000 (second batch) + 1 (second commit) = 2003
1905        let db = open_db(context.child("second")).await;
1906        assert_eq!(db.bounds().end, 2003);
1907        let root = db.root();
1908        assert_ne!(root, halfway_root);
1909
1910        // Drop & reopen could preserve the final commit.
1911        drop(db);
1912        let db = open_db(context.child("third")).await;
1913        assert_eq!(db.bounds().end, 2003);
1914        assert_eq!(db.root(), root);
1915
1916        db.destroy().await.unwrap();
1917    }
1918
1919    #[boxed]
1920    pub(crate) async fn run_recovery_from_failed_log_sync<F: Family, V, C>(
1921        context: deterministic::Context,
1922        open_db: impl Fn(
1923            deterministic::Context,
1924        ) -> Pin<Box<dyn Future<Output = TestDb<F, V, C>> + Send>>,
1925    ) where
1926        V: ValueEncoding<Value = Digest>,
1927        C: Mutable<Item = Operation<F, Digest, V>>,
1928        C::Item: EncodeShared,
1929    {
1930        let db = open_db(context.child("first")).await;
1931
1932        // Insert a single key and then commit to create a first commit point.
1933        let k1 = Sha256::fill(1u8);
1934        let v1 = Sha256::fill(3u8);
1935        let merkleized = db
1936            .new_batch()
1937            .set(k1, v1)
1938            .merkleize(&db, None, Location::new(0))
1939            .await
1940            .unwrap();
1941        let db = db.apply_batch(merkleized).await.unwrap();
1942        let db = db.commit().await.unwrap();
1943        let first_commit_root = db.root();
1944
1945        // Simulate failure. Sets that are never merkleized/applied are lost.
1946        // Recovery should restore the last commit point.
1947        drop(db);
1948
1949        // Recovery should back up to previous commit point.
1950        let db = open_db(context.child("second")).await;
1951        assert_eq!(db.bounds().end, 3);
1952        let root = db.root();
1953        assert_eq!(root, first_commit_root);
1954
1955        db.destroy().await.unwrap();
1956    }
1957
1958    #[boxed]
1959    pub(crate) async fn run_pruning<F: Family, V, C>(
1960        context: deterministic::Context,
1961        open_db: impl Fn(
1962            deterministic::Context,
1963        ) -> Pin<Box<dyn Future<Output = TestDb<F, V, C>> + Send>>,
1964    ) where
1965        V: ValueEncoding<Value = Digest>,
1966        C: Mutable<Item = Operation<F, Digest, V>>,
1967        C::Item: EncodeShared,
1968    {
1969        // Build a db with `ELEMENTS` key/value pairs then prune some of them.
1970        const ELEMENTS: u64 = 2_000;
1971        let db = open_db(context.child("first")).await;
1972
1973        // Batch writes keys in BTreeMap-sorted order, so build the sorted key
1974        // list to map between journal locations and keys.
1975        let mut sorted_keys: Vec<sha256::Digest> = (1u64..ELEMENTS + 1)
1976            .map(|i| Sha256::hash(&[&i.to_be_bytes()]))
1977            .collect();
1978        sorted_keys.sort();
1979        // Location 0: initial commit; locations 1..=ELEMENTS: Set ops in sorted
1980        // key order; location ELEMENTS+1: batch commit.
1981        // key_at_loc(L) = sorted_keys[L - 1] for 1 <= L <= ELEMENTS.
1982
1983        let mut batch = db.new_batch();
1984        for i in 1u64..ELEMENTS + 1 {
1985            let k = Sha256::hash(&[&i.to_be_bytes()]);
1986            let v = Sha256::fill(i as u8);
1987            batch = batch.set(k, v);
1988        }
1989        // The inactivity floor must cover both prune targets in this test.
1990        // Second prune request is at ELEMENTS / 2 + ITEMS_PER_SECTION * 2 - 1.
1991        let inactivity_floor = Location::new(ELEMENTS / 2 + ITEMS_PER_SECTION * 2 - 1);
1992        let merkleized = batch.merkleize(&db, None, inactivity_floor).await.unwrap();
1993        let db = db.apply_batch(merkleized).await.unwrap();
1994        assert_eq!(db.bounds().end, ELEMENTS + 2);
1995
1996        // Prune the db to the first half of the operations.
1997        let db = db.prune(Location::new((ELEMENTS + 2) / 2)).await.unwrap();
1998        let bounds = db.bounds();
1999        assert_eq!(bounds.end, ELEMENTS + 2);
2000
2001        // items_per_section is 5, so half should be exactly at a blob boundary, in which case
2002        // the actual pruning location should match the requested.
2003        let oldest_retained_loc = bounds.start;
2004        assert_eq!(oldest_retained_loc, Location::new(ELEMENTS / 2));
2005
2006        // Try to fetch a pruned key (at location oldest_retained - 1).
2007        let pruned_key = sorted_keys[*oldest_retained_loc as usize - 2];
2008        assert!(db.get(&pruned_key).await.unwrap().is_none());
2009
2010        // Try to fetch unpruned key (at location oldest_retained).
2011        let unpruned_key = sorted_keys[*oldest_retained_loc as usize - 1];
2012        assert!(db.get(&unpruned_key).await.unwrap().is_some());
2013
2014        // Drop & reopen the db, making sure it has exactly the same state.
2015        let root = db.root();
2016        db.sync().await.unwrap();
2017
2018        let db = open_db(context.child("second")).await;
2019        assert_eq!(root, db.root());
2020        let bounds = db.bounds();
2021        assert_eq!(bounds.end, ELEMENTS + 2);
2022        let oldest_retained_loc = bounds.start;
2023        assert_eq!(oldest_retained_loc, Location::new(ELEMENTS / 2));
2024
2025        // Prune to a non-blob boundary.
2026        let loc = Location::new(ELEMENTS / 2 + (ITEMS_PER_SECTION * 2 - 1));
2027        let db = db.prune(loc).await.unwrap();
2028        // Actual boundary should be a multiple of 5.
2029        let oldest_retained_loc = db.bounds().start;
2030        assert_eq!(
2031            oldest_retained_loc,
2032            Location::new(ELEMENTS / 2 + ITEMS_PER_SECTION)
2033        );
2034
2035        // Confirm boundary persists across restart.
2036        db.sync().await.unwrap();
2037        let db = open_db(context.child("third")).await;
2038        let oldest_retained_loc = db.bounds().start;
2039        assert_eq!(
2040            oldest_retained_loc,
2041            Location::new(ELEMENTS / 2 + ITEMS_PER_SECTION)
2042        );
2043
2044        // Try to fetch a key before the inactivity floor (not in snapshot after reopen).
2045        let floor_val = ELEMENTS / 2 + ITEMS_PER_SECTION * 2 - 1;
2046        let inactive_key = sorted_keys[floor_val as usize - 2];
2047        assert!(db.get(&inactive_key).await.unwrap().is_none());
2048
2049        // Try to fetch a key at the inactivity floor (in snapshot after reopen).
2050        let active_key = sorted_keys[floor_val as usize - 1];
2051        assert!(db.get(&active_key).await.unwrap().is_some());
2052
2053        // Confirm behavior of trying to create a proof of pruned items is as expected.
2054        let pruned_pos = ELEMENTS / 2;
2055        let proof_result = db
2056            .proof(Location::new(pruned_pos), NZU64!(pruned_pos + 100))
2057            .await;
2058        assert!(
2059            matches!(proof_result, Err(Error::Journal(crate::journal::Error::ItemPruned(pos))) if pos == pruned_pos)
2060        );
2061
2062        db.destroy().await.unwrap();
2063    }
2064
2065    #[boxed]
2066    pub(crate) async fn run_prune_beyond_floor<F: Family, V, C>(
2067        context: deterministic::Context,
2068        open_db: impl Fn(
2069            deterministic::Context,
2070        ) -> Pin<Box<dyn Future<Output = TestDb<F, V, C>> + Send>>,
2071    ) where
2072        V: ValueEncoding<Value = Digest>,
2073        C: Mutable<Item = Operation<F, Digest, V>>,
2074        C::Item: EncodeShared,
2075    {
2076        let db = open_db(context.child("test")).await;
2077
2078        // Test pruning empty database (floor=0, so prune(1) fails)
2079        let result = db.prune(Location::new(1)).await;
2080        assert!(
2081            matches!(result, Err(Error::PruneBeyondMinRequired(prune_loc, floor))
2082                if prune_loc == Location::new(1) && floor == Location::new(0))
2083        );
2084
2085        let db = open_db(context.child("test")).await;
2086
2087        // Add key-value pairs and commit
2088        let k1 = Digest::from(*b"12345678901234567890123456789012");
2089        let k2 = Digest::from(*b"abcdefghijklmnopqrstuvwxyz123456");
2090        let k3 = Digest::from(*b"99999999999999999999999999999999");
2091        let v1 = Sha256::fill(1u8);
2092        let v2 = Sha256::fill(2u8);
2093        let v3 = Sha256::fill(3u8);
2094
2095        // First batch with floor=3 (the commit location).
2096        let merkleized = db
2097            .new_batch()
2098            .set(k1, v1)
2099            .set(k2, v2)
2100            .merkleize(&db, None, Location::new(3))
2101            .await
2102            .unwrap();
2103        let db = db.apply_batch(merkleized).await.unwrap();
2104
2105        // op_count is 4 (initial_commit, k1, k2, commit), last_commit is at location 3
2106        assert_eq!(*db.size() - 1, 3);
2107
2108        // Second batch with floor=5 (the new commit location).
2109        let merkleized = db
2110            .new_batch()
2111            .set(k3, v3)
2112            .merkleize(&db, None, Location::new(5))
2113            .await
2114            .unwrap();
2115        let db = db.apply_batch(merkleized).await.unwrap();
2116
2117        // Test valid prune (3 <= floor of 5)
2118        let db = db.prune(Location::new(3)).await.unwrap();
2119
2120        // Test pruning beyond inactivity floor
2121        let floor = db.inactivity_floor_loc();
2122        let beyond = floor + 1;
2123        let result = db.prune(beyond).await;
2124        assert!(
2125            matches!(result, Err(Error::PruneBeyondMinRequired(prune_loc, f))
2126                if prune_loc == beyond && f == floor)
2127        );
2128    }
2129
2130    async fn commit_sets<F: Family, V, C>(
2131        db: TestDb<F, V, C>,
2132        sets: impl IntoIterator<Item = (Digest, V::Value)>,
2133        metadata: Option<V::Value>,
2134    ) -> TestDb<F, V, C>
2135    where
2136        V: ValueEncoding<Value = Digest>,
2137        C: Mutable<Item = Operation<F, Digest, V>>,
2138        C::Item: EncodeShared,
2139    {
2140        commit_sets_with_floor(db, sets, metadata, Location::new(0)).await
2141    }
2142
2143    async fn commit_sets_with_floor<F: Family, V, C>(
2144        db: TestDb<F, V, C>,
2145        sets: impl IntoIterator<Item = (Digest, V::Value)>,
2146        metadata: Option<V::Value>,
2147        floor: Location<F>,
2148    ) -> TestDb<F, V, C>
2149    where
2150        V: ValueEncoding<Value = Digest>,
2151        C: Mutable<Item = Operation<F, Digest, V>>,
2152        C::Item: EncodeShared,
2153    {
2154        let mut batch = db.new_batch();
2155        for (key, value) in sets {
2156            batch = batch.set(key, value);
2157        }
2158        let merkleized = batch.merkleize(&db, metadata, floor).await.unwrap();
2159        let expected_end = merkleized.bounds().tip.size;
2160        let db = db.apply_batch(merkleized).await.unwrap();
2161        assert_eq!(db.bounds().end, expected_end);
2162        db.commit().await.unwrap()
2163    }
2164
2165    #[boxed]
2166    pub(crate) async fn run_bounded_initialization_recovery<F: Family, V, C>(
2167        context: deterministic::Context,
2168        open_db: impl Fn(
2169            deterministic::Context,
2170            Option<Location<F>>,
2171        )
2172            -> Pin<Box<dyn Future<Output = Result<TestDb<F, V, C>, Error<F>>> + Send>>,
2173    ) where
2174        V: ValueEncoding<Value = Digest>,
2175        C: Mutable<Item = Operation<F, Digest, V>>,
2176        C::Item: EncodeShared,
2177    {
2178        let db = open_db(context.child("db"), None).await.unwrap();
2179
2180        let key1 = Sha256::hash(&[&1u64.to_be_bytes()]);
2181        let key2 = Sha256::hash(&[&2u64.to_be_bytes()]);
2182        let key3 = Sha256::hash(&[&3u64.to_be_bytes()]);
2183        let key4 = Sha256::hash(&[&4u64.to_be_bytes()]);
2184
2185        let value1 = Sha256::fill(11u8);
2186        let value2 = Sha256::fill(22u8);
2187        let value3 = Sha256::fill(33u8);
2188        let value4 = Sha256::fill(66u8);
2189
2190        let metadata_a = Sha256::fill(44u8);
2191        let db = commit_sets(db, [(key1, value1), (key2, value2)], Some(metadata_a)).await;
2192        let size_before = db.bounds().end;
2193        let root_before = db.root();
2194        let last_commit_before = db.size() - 1;
2195
2196        let metadata_b = Sha256::fill(55u8);
2197        let db = commit_sets(db, [(key3, value3), (key4, value4)], Some(metadata_b)).await;
2198        assert_ne!(db.root(), root_before);
2199        assert_eq!(db.get_metadata().await.unwrap(), Some(metadata_b));
2200        assert_eq!(db.get(&key3).await.unwrap(), Some(value3));
2201        assert_eq!(db.get(&key4).await.unwrap(), Some(value4));
2202
2203        _ = db.sync().await.unwrap();
2204        let db = open_db(context.child("cap"), Some(size_before))
2205            .await
2206            .unwrap();
2207        assert_eq!(db.root(), root_before);
2208        assert_eq!(db.bounds().end, size_before);
2209        assert_eq!(db.size() - 1, last_commit_before);
2210        assert_eq!(db.get_metadata().await.unwrap(), Some(metadata_a));
2211        assert_eq!(db.get(&key1).await.unwrap(), Some(value1));
2212        assert_eq!(db.get(&key2).await.unwrap(), Some(value2));
2213        assert_eq!(db.get(&key3).await.unwrap(), None);
2214        assert_eq!(db.get(&key4).await.unwrap(), None);
2215
2216        db.commit().await.unwrap();
2217        let db = open_db(context.child("reopen"), None).await.unwrap();
2218        assert_eq!(db.root(), root_before);
2219        assert_eq!(db.bounds().end, size_before);
2220        assert_eq!(db.size() - 1, last_commit_before);
2221        assert_eq!(db.get_metadata().await.unwrap(), Some(metadata_a));
2222        assert_eq!(db.get(&key1).await.unwrap(), Some(value1));
2223        assert_eq!(db.get(&key2).await.unwrap(), Some(value2));
2224        assert_eq!(db.get(&key3).await.unwrap(), None);
2225        assert_eq!(db.get(&key4).await.unwrap(), None);
2226
2227        db.destroy().await.unwrap();
2228    }
2229
2230    /// A key retained by the initialization bound must survive even when it translator-collides
2231    /// with a key in the discarded suffix.
2232    #[boxed]
2233    pub(crate) async fn run_bounded_initialization_preserves_collision_bucket<F: Family, V, C>(
2234        context: deterministic::Context,
2235        open_db: impl Fn(
2236            deterministic::Context,
2237            Option<Location<F>>,
2238        )
2239            -> Pin<Box<dyn Future<Output = Result<TestDb<F, V, C>, Error<F>>> + Send>>,
2240    ) where
2241        V: ValueEncoding<Value = Digest>,
2242        C: Mutable<Item = Operation<F, Digest, V>>,
2243        C::Item: EncodeShared,
2244    {
2245        let db = open_db(context.child("db"), None).await.unwrap();
2246
2247        // Two keys sharing the first two bytes collide under TwoCap.
2248        let mut k1_bytes = [0u8; 32];
2249        let mut k2_bytes = [0u8; 32];
2250        k1_bytes[0] = 0xAA;
2251        k1_bytes[1] = 0xBB;
2252        k2_bytes[0] = 0xAA;
2253        k2_bytes[1] = 0xBB;
2254        k1_bytes[31] = 0x01;
2255        k2_bytes[31] = 0x02;
2256        let key1 = Digest::from(k1_bytes);
2257        let key2 = Digest::from(k2_bytes);
2258        let value1 = Sha256::fill(11u8);
2259        let value2 = Sha256::fill(22u8);
2260
2261        let db = commit_sets(db, [(key1, value1)], None).await;
2262        let size_after_first = db.bounds().end;
2263        let db = commit_sets(db, [(key2, value2)], None).await;
2264        assert_eq!(db.get(&key1).await.unwrap(), Some(value1));
2265        assert_eq!(db.get(&key2).await.unwrap(), Some(value2));
2266
2267        _ = db.sync().await.unwrap();
2268        let db = open_db(context.child("cap"), Some(size_after_first))
2269            .await
2270            .unwrap();
2271
2272        // The retained key must still be readable; pre-fix this returned None because the
2273        // translator bucket was wiped by the suffix-key remove.
2274        assert_eq!(db.get(&key1).await.unwrap(), Some(value1));
2275        assert_eq!(db.get(&key2).await.unwrap(), None);
2276
2277        db.destroy().await.unwrap();
2278    }
2279
2280    #[boxed]
2281    pub(crate) async fn run_bounded_initialization_pruned_target_errors<F: Family, V, C>(
2282        context: deterministic::Context,
2283        open_small_sections_db: impl Fn(
2284            deterministic::Context,
2285            Option<Location<F>>,
2286        ) -> Pin<
2287            Box<dyn Future<Output = Result<TestDb<F, V, C>, Error<F>>> + Send>,
2288        >,
2289    ) where
2290        V: ValueEncoding<Value = Digest>,
2291        C: Mutable<Item = Operation<F, Digest, V>>,
2292        C::Item: EncodeShared,
2293    {
2294        let db = open_small_sections_db(context.child("db"), None)
2295            .await
2296            .unwrap();
2297
2298        let first_start = db.bounds().end;
2299        let mut db = commit_sets(
2300            db,
2301            (0u64..16).map(|i| (Sha256::hash(&[&i.to_be_bytes()]), Sha256::fill(i as u8))),
2302            None,
2303        )
2304        .await;
2305
2306        let mut round = 0u64;
2307        loop {
2308            round += 1;
2309            assert!(
2310                round <= 64,
2311                "failed to prune enough history for the bounded initialization test"
2312            );
2313
2314            // Floor must be >= last_commit_loc for prune to succeed.
2315            // With 16 sets, commit is at current end + 16.
2316            let floor = db.bounds().end + 16;
2317            db = commit_sets_with_floor(
2318                db,
2319                (0u64..16).map(|i| {
2320                    let seed = round * 100 + i;
2321                    (
2322                        Sha256::hash(&[&seed.to_be_bytes()]),
2323                        Sha256::fill(seed as u8),
2324                    )
2325                }),
2326                None,
2327                floor,
2328            )
2329            .await;
2330            let last_commit = db.size() - 1;
2331            db = db.prune(last_commit).await.unwrap();
2332
2333            if db.bounds().start > first_start {
2334                break;
2335            }
2336        }
2337
2338        let oldest_retained = db.bounds().start;
2339        _ = db.sync().await.unwrap();
2340        let Err(boundary_err) =
2341            open_small_sections_db(context.child("cap_error"), Some(oldest_retained)).await
2342        else {
2343            panic!("expected bounded initialization to fail");
2344        };
2345        assert!(
2346            matches!(
2347                boundary_err,
2348                Error::Journal(crate::journal::Error::ItemPruned(_))
2349            ),
2350            "unexpected bounded initialization error at retained boundary: {boundary_err:?}"
2351        );
2352
2353        let db = open_small_sections_db(context.child("db"), None)
2354            .await
2355            .unwrap();
2356        _ = db.sync().await.unwrap();
2357        let Err(err) = open_small_sections_db(context.child("cap_error"), Some(first_start)).await
2358        else {
2359            panic!("expected bounded initialization to fail");
2360        };
2361        assert!(
2362            matches!(err, Error::Journal(crate::journal::Error::ItemPruned(_))),
2363            "unexpected bounded initialization error: {err:?}"
2364        );
2365    }
2366
2367    /// batch.get() reads pending mutations, then the database.
2368    #[boxed]
2369    pub(crate) async fn run_batch_get_read_through<F: Family, V, C>(
2370        context: deterministic::Context,
2371        open_db: impl Fn(
2372            deterministic::Context,
2373        ) -> Pin<Box<dyn Future<Output = TestDb<F, V, C>> + Send>>,
2374    ) where
2375        V: ValueEncoding<Value = Digest>,
2376        C: Mutable<Item = Operation<F, Digest, V>>,
2377        C::Item: EncodeShared,
2378    {
2379        let db = open_db(context.child("db")).await;
2380
2381        // Pre-populate with key A.
2382        let key_a = Sha256::hash(&[&0u64.to_be_bytes()]);
2383        let val_a = Sha256::fill(1u8);
2384        let merkleized = db
2385            .new_batch()
2386            .set(key_a, val_a)
2387            .merkleize(&db, None, Location::new(0))
2388            .await
2389            .unwrap();
2390        let db = db.apply_batch(merkleized).await.unwrap();
2391
2392        // batch.get(&A) should return DB value.
2393        let mut batch = db.new_batch();
2394        assert_eq!(batch.get(&key_a, &db).await.unwrap(), Some(val_a));
2395
2396        // Set B in batch, batch.get(&B) returns the value.
2397        let key_b = Sha256::hash(&[&1u64.to_be_bytes()]);
2398        let val_b = Sha256::fill(2u8);
2399        batch = batch.set(key_b, val_b);
2400        assert_eq!(batch.get(&key_b, &db).await.unwrap(), Some(val_b));
2401
2402        // Nonexistent key.
2403        let key_c = Sha256::hash(&[&2u64.to_be_bytes()]);
2404        assert_eq!(batch.get(&key_c, &db).await.unwrap(), None);
2405
2406        db.destroy().await.unwrap();
2407    }
2408
2409    /// Child batch reads parent diff and adds its own mutations.
2410    #[boxed]
2411    pub(crate) async fn run_batch_stacked_get<F: Family, V, C>(
2412        context: deterministic::Context,
2413        open_db: impl Fn(
2414            deterministic::Context,
2415        ) -> Pin<Box<dyn Future<Output = TestDb<F, V, C>> + Send>>,
2416    ) where
2417        V: ValueEncoding<Value = Digest>,
2418        C: Mutable<Item = Operation<F, Digest, V>>,
2419        C::Item: EncodeShared,
2420    {
2421        let db = open_db(context.child("db")).await;
2422
2423        // Parent batch: set A.
2424        let key_a = Sha256::hash(&[&0u64.to_be_bytes()]);
2425        let val_a = Sha256::fill(10u8);
2426        let parent = db.new_batch().set(key_a, val_a);
2427        let parent_m = parent.merkleize(&db, None, Location::new(0)).await.unwrap();
2428
2429        // Child reads parent's A.
2430        let mut child = parent_m.new_batch::<Sha256>();
2431        assert_eq!(child.get(&key_a, &db).await.unwrap(), Some(val_a));
2432
2433        // Child sets B.
2434        let key_b = Sha256::hash(&[&1u64.to_be_bytes()]);
2435        let val_b = Sha256::fill(20u8);
2436        child = child.set(key_b, val_b);
2437        assert_eq!(child.get(&key_b, &db).await.unwrap(), Some(val_b));
2438
2439        // Nonexistent key.
2440        let key_c = Sha256::hash(&[&2u64.to_be_bytes()]);
2441        assert_eq!(child.get(&key_c, &db).await.unwrap(), None);
2442
2443        db.destroy().await.unwrap();
2444    }
2445
2446    /// Two-level stacked batch apply works end-to-end.
2447    #[boxed]
2448    pub(crate) async fn run_batch_stacked_apply<F: Family, V, C>(
2449        context: deterministic::Context,
2450        open_db: impl Fn(
2451            deterministic::Context,
2452        ) -> Pin<Box<dyn Future<Output = TestDb<F, V, C>> + Send>>,
2453    ) where
2454        V: ValueEncoding<Value = Digest>,
2455        C: Mutable<Item = Operation<F, Digest, V>>,
2456        C::Item: EncodeShared,
2457    {
2458        let db = open_db(context.child("db")).await;
2459
2460        // Sort keys so operations are in BTreeMap order (same as merkleize writes).
2461        let mut kvs_first: Vec<(Digest, Digest)> = (0u64..5)
2462            .map(|i| (Sha256::hash(&[&i.to_be_bytes()]), Sha256::fill(i as u8)))
2463            .collect();
2464        kvs_first.sort_by_key(|a| a.0);
2465
2466        let mut kvs_second: Vec<(Digest, Digest)> = (5u64..10)
2467            .map(|i| (Sha256::hash(&[&i.to_be_bytes()]), Sha256::fill(i as u8)))
2468            .collect();
2469        kvs_second.sort_by_key(|a| a.0);
2470
2471        // Parent batch: set keys 0..5.
2472        let mut parent = db.new_batch();
2473        for (k, v) in &kvs_first {
2474            parent = parent.set(*k, *v);
2475        }
2476        let parent_m = parent.merkleize(&db, None, Location::new(0)).await.unwrap();
2477
2478        // Child batch: set keys 5..10.
2479        let mut child = parent_m.new_batch::<Sha256>();
2480        for (k, v) in &kvs_second {
2481            child = child.set(*k, *v);
2482        }
2483        let child_m = child.merkleize(&db, None, Location::new(0)).await.unwrap();
2484        let expected_root = child_m.root();
2485        let db = db.apply_batch(child_m).await.unwrap();
2486
2487        assert_eq!(db.root(), expected_root);
2488
2489        // All 10 keys should be accessible.
2490        for (k, v) in kvs_first.iter().chain(kvs_second.iter()) {
2491            assert_eq!(db.get(k).await.unwrap(), Some(*v));
2492        }
2493
2494        db.destroy().await.unwrap();
2495    }
2496
2497    /// MerkleizedBatch::root() matches db.root() after apply_batch().
2498    #[boxed]
2499    pub(crate) async fn run_batch_speculative_root<F: Family, V, C>(
2500        context: deterministic::Context,
2501        open_db: impl Fn(
2502            deterministic::Context,
2503        ) -> Pin<Box<dyn Future<Output = TestDb<F, V, C>> + Send>>,
2504    ) where
2505        V: ValueEncoding<Value = Digest>,
2506        C: Mutable<Item = Operation<F, Digest, V>>,
2507        C::Item: EncodeShared,
2508    {
2509        let db = open_db(context.child("db")).await;
2510
2511        let mut batch = db.new_batch();
2512        for i in 0u8..10 {
2513            let k = Sha256::hash(&[&[i]]);
2514            batch = batch.set(k, Sha256::fill(i));
2515        }
2516        let merkleized = batch.merkleize(&db, None, Location::new(0)).await.unwrap();
2517
2518        let speculative = merkleized.root();
2519        let db = db.apply_batch(merkleized).await.unwrap();
2520        assert_eq!(db.root(), speculative);
2521
2522        // Second batch with metadata.
2523        let metadata = Some(Sha256::fill(55u8));
2524        let mut batch = db.new_batch();
2525        let k = Sha256::hash(&[&[0xAA]]);
2526        batch = batch.set(k, Sha256::fill(0xAA));
2527        let merkleized = batch
2528            .merkleize(&db, metadata, Location::new(0))
2529            .await
2530            .unwrap();
2531        let speculative = merkleized.root();
2532        let db = db.apply_batch(merkleized).await.unwrap();
2533        assert_eq!(db.root(), speculative);
2534
2535        db.destroy().await.unwrap();
2536    }
2537
2538    /// MerkleizedBatch::get() reads from diff and base DB.
2539    #[boxed]
2540    pub(crate) async fn run_merkleized_batch_get<F: Family, V, C>(
2541        context: deterministic::Context,
2542        open_db: impl Fn(
2543            deterministic::Context,
2544        ) -> Pin<Box<dyn Future<Output = TestDb<F, V, C>> + Send>>,
2545    ) where
2546        V: ValueEncoding<Value = Digest>,
2547        C: Mutable<Item = Operation<F, Digest, V>>,
2548        C::Item: EncodeShared,
2549    {
2550        let db = open_db(context.child("db")).await;
2551
2552        // Pre-populate base DB.
2553        let key_a = Sha256::hash(&[&0u64.to_be_bytes()]);
2554        let val_a = Sha256::fill(10u8);
2555        let merkleized = db
2556            .new_batch()
2557            .set(key_a, val_a)
2558            .merkleize(&db, None, Location::new(0))
2559            .await
2560            .unwrap();
2561        let db = db.apply_batch(merkleized).await.unwrap();
2562
2563        // Create a merkleized batch with a new key.
2564        let key_b = Sha256::hash(&[&1u64.to_be_bytes()]);
2565        let val_b = Sha256::fill(20u8);
2566        let merkleized = db
2567            .new_batch()
2568            .set(key_b, val_b)
2569            .merkleize(&db, None, Location::new(0))
2570            .await
2571            .unwrap();
2572
2573        // Read base DB value through merkleized batch.
2574        assert_eq!(merkleized.get(&key_a, &db).await.unwrap(), Some(val_a));
2575
2576        // Read this batch's key from the diff.
2577        assert_eq!(merkleized.get(&key_b, &db).await.unwrap(), Some(val_b));
2578
2579        // Nonexistent key.
2580        let key_c = Sha256::hash(&[&2u64.to_be_bytes()]);
2581        assert_eq!(merkleized.get(&key_c, &db).await.unwrap(), None);
2582
2583        db.destroy().await.unwrap();
2584    }
2585
2586    /// Independent sequential batches applied one at a time.
2587    #[boxed]
2588    pub(crate) async fn run_batch_sequential_apply<F: Family, V, C>(
2589        context: deterministic::Context,
2590        open_db: impl Fn(
2591            deterministic::Context,
2592        ) -> Pin<Box<dyn Future<Output = TestDb<F, V, C>> + Send>>,
2593    ) where
2594        V: ValueEncoding<Value = Digest>,
2595        C: Mutable<Item = Operation<F, Digest, V>>,
2596        C::Item: EncodeShared,
2597    {
2598        let db = open_db(context.child("db")).await;
2599
2600        let key_a = Sha256::hash(&[&0u64.to_be_bytes()]);
2601        let val_a = Sha256::fill(1u8);
2602
2603        // First batch.
2604        let m = db
2605            .new_batch()
2606            .set(key_a, val_a)
2607            .merkleize(&db, None, Location::new(0))
2608            .await
2609            .unwrap();
2610        let root1 = m.root();
2611        let db = db.apply_batch(m).await.unwrap();
2612        assert_eq!(db.root(), root1);
2613        assert_eq!(db.get(&key_a).await.unwrap(), Some(val_a));
2614
2615        // Second independent batch.
2616        let key_b = Sha256::hash(&[&1u64.to_be_bytes()]);
2617        let val_b = Sha256::fill(2u8);
2618        let m = db
2619            .new_batch()
2620            .set(key_b, val_b)
2621            .merkleize(&db, None, Location::new(0))
2622            .await
2623            .unwrap();
2624        let root2 = m.root();
2625        let db = db.apply_batch(m).await.unwrap();
2626        assert_eq!(db.root(), root2);
2627        assert_eq!(db.get(&key_b).await.unwrap(), Some(val_b));
2628
2629        db.destroy().await.unwrap();
2630    }
2631
2632    /// Many sequential batches accumulate correctly.
2633    #[boxed]
2634    pub(crate) async fn run_batch_many_sequential<F: Family, V, C>(
2635        context: deterministic::Context,
2636        open_db: impl Fn(
2637            deterministic::Context,
2638        ) -> Pin<Box<dyn Future<Output = TestDb<F, V, C>> + Send>>,
2639    ) where
2640        V: ValueEncoding<Value = Digest>,
2641        C: Mutable<Item = Operation<F, Digest, V>>,
2642        C::Item: EncodeShared,
2643    {
2644        let mut db = open_db(context.child("db")).await;
2645
2646        const BATCHES: u64 = 20;
2647        const KEYS_PER_BATCH: u64 = 5;
2648
2649        let mut all_kvs: Vec<(Digest, Digest)> = Vec::new();
2650
2651        for batch_idx in 0..BATCHES {
2652            let mut batch = db.new_batch();
2653            for j in 0..KEYS_PER_BATCH {
2654                let seed = batch_idx * 100 + j;
2655                let k = Sha256::hash(&[&seed.to_be_bytes()]);
2656                let v = Sha256::fill(seed as u8);
2657                batch = batch.set(k, v);
2658                all_kvs.push((k, v));
2659            }
2660            let merkleized = batch.merkleize(&db, None, Location::new(0)).await.unwrap();
2661            db = db.apply_batch(merkleized).await.unwrap();
2662        }
2663
2664        // Verify all key-values are readable.
2665        for (k, v) in &all_kvs {
2666            assert_eq!(db.get(k).await.unwrap(), Some(*v));
2667        }
2668
2669        // Verify proof over the full range.
2670        let root = db.root();
2671        let (proof, ops) = db.proof(Location::new(0), NZU64!(10000)).await.unwrap();
2672        assert!(verify_proof::<Sha256, _, _>(
2673            &proof,
2674            Location::new(0),
2675            &ops,
2676            &root
2677        ));
2678
2679        // Expected: 1 initial commit + BATCHES * (KEYS_PER_BATCH + 1 commit).
2680        let expected = 1 + BATCHES * (KEYS_PER_BATCH + 1);
2681        assert_eq!(db.bounds().end, expected);
2682
2683        db.destroy().await.unwrap();
2684    }
2685
2686    /// Empty batch (zero mutations) produces correct speculative root.
2687    #[boxed]
2688    pub(crate) async fn run_batch_empty_batch<F: Family, V, C>(
2689        context: deterministic::Context,
2690        open_db: impl Fn(
2691            deterministic::Context,
2692        ) -> Pin<Box<dyn Future<Output = TestDb<F, V, C>> + Send>>,
2693    ) where
2694        V: ValueEncoding<Value = Digest>,
2695        C: Mutable<Item = Operation<F, Digest, V>>,
2696        C::Item: EncodeShared,
2697    {
2698        let db = open_db(context.child("db")).await;
2699
2700        // Apply a non-empty batch first.
2701        let k = Sha256::hash(&[&[1u8]]);
2702        let merkleized = db
2703            .new_batch()
2704            .set(k, Sha256::fill(1u8))
2705            .merkleize(&db, None, Location::new(0))
2706            .await
2707            .unwrap();
2708        let db = db.apply_batch(merkleized).await.unwrap();
2709        let root_before = db.root();
2710        let size_before = db.bounds().end;
2711
2712        // Empty batch with no mutations.
2713        let merkleized = db
2714            .new_batch()
2715            .merkleize(&db, None, Location::new(0))
2716            .await
2717            .unwrap();
2718        let speculative = merkleized.root();
2719        let db = db.apply_batch(merkleized).await.unwrap();
2720
2721        // Root changed (a new Commit op was appended).
2722        assert_ne!(db.root(), root_before);
2723        assert_eq!(db.root(), speculative);
2724        // Size grew by exactly 1 (the Commit op).
2725        assert_eq!(db.bounds().end, size_before + 1);
2726
2727        db.destroy().await.unwrap();
2728    }
2729
2730    /// MerkleizedBatch::get() works on a chained child's merkleized batch.
2731    #[boxed]
2732    pub(crate) async fn run_batch_chained_merkleized_get<F: Family, V, C>(
2733        context: deterministic::Context,
2734        open_db: impl Fn(
2735            deterministic::Context,
2736        ) -> Pin<Box<dyn Future<Output = TestDb<F, V, C>> + Send>>,
2737    ) where
2738        V: ValueEncoding<Value = Digest>,
2739        C: Mutable<Item = Operation<F, Digest, V>>,
2740        C::Item: EncodeShared,
2741    {
2742        let db = open_db(context.child("db")).await;
2743
2744        // Pre-populate base DB.
2745        let key_a = Sha256::hash(&[&0u64.to_be_bytes()]);
2746        let val_a = Sha256::fill(10u8);
2747        let merkleized = db
2748            .new_batch()
2749            .set(key_a, val_a)
2750            .merkleize(&db, None, Location::new(0))
2751            .await
2752            .unwrap();
2753        let db = db.apply_batch(merkleized).await.unwrap();
2754
2755        // Parent batch sets key B.
2756        let key_b = Sha256::hash(&[&1u64.to_be_bytes()]);
2757        let val_b = Sha256::fill(1u8);
2758        let parent_m = db
2759            .new_batch()
2760            .set(key_b, val_b)
2761            .merkleize(&db, None, Location::new(0))
2762            .await
2763            .unwrap();
2764
2765        // Child batch sets key C.
2766        let key_c = Sha256::hash(&[&2u64.to_be_bytes()]);
2767        let val_c = Sha256::fill(2u8);
2768        let child_m = parent_m
2769            .new_batch::<Sha256>()
2770            .set(key_c, val_c)
2771            .merkleize(&db, None, Location::new(0))
2772            .await
2773            .unwrap();
2774
2775        // Child's MerkleizedBatch can read all three layers:
2776        // base DB value
2777        assert_eq!(child_m.get(&key_a, &db).await.unwrap(), Some(val_a));
2778        // parent diff value
2779        assert_eq!(child_m.get(&key_b, &db).await.unwrap(), Some(val_b));
2780        // child's own value
2781        assert_eq!(child_m.get(&key_c, &db).await.unwrap(), Some(val_c));
2782        // nonexistent key
2783        let key_d = Sha256::hash(&[&3u64.to_be_bytes()]);
2784        assert_eq!(child_m.get(&key_d, &db).await.unwrap(), None);
2785
2786        db.destroy().await.unwrap();
2787    }
2788
2789    /// Large single batch, verifying all values and proof.
2790    #[boxed]
2791    pub(crate) async fn run_batch_large<F: Family, V, C>(
2792        context: deterministic::Context,
2793        open_db: impl Fn(
2794            deterministic::Context,
2795        ) -> Pin<Box<dyn Future<Output = TestDb<F, V, C>> + Send>>,
2796    ) where
2797        V: ValueEncoding<Value = Digest>,
2798        C: Mutable<Item = Operation<F, Digest, V>>,
2799        C::Item: EncodeShared,
2800    {
2801        let db = open_db(context.child("db")).await;
2802
2803        const N: u64 = 500;
2804        let mut kvs: Vec<(Digest, Digest)> = Vec::new();
2805
2806        let mut batch = db.new_batch();
2807        for i in 0..N {
2808            let k = Sha256::hash(&[&i.to_be_bytes()]);
2809            let v = Sha256::fill((i % 256) as u8);
2810            batch = batch.set(k, v);
2811            kvs.push((k, v));
2812        }
2813        let merkleized = batch.merkleize(&db, None, Location::new(0)).await.unwrap();
2814        let db = db.apply_batch(merkleized).await.unwrap();
2815
2816        // Verify every value.
2817        for (k, v) in &kvs {
2818            assert_eq!(db.get(k).await.unwrap(), Some(*v));
2819        }
2820
2821        // Verify proof over the full range.
2822        let root = db.root();
2823        let (proof, ops) = db.proof(Location::new(0), NZU64!(1000)).await.unwrap();
2824        assert!(verify_proof::<Sha256, _, _>(
2825            &proof,
2826            Location::new(0),
2827            &ops,
2828            &root
2829        ));
2830
2831        // Expected: 1 initial commit + N sets + 1 commit.
2832        assert_eq!(db.bounds().end, 1 + N + 1);
2833
2834        db.destroy().await.unwrap();
2835    }
2836
2837    /// Two keys that collide under the translator are set in sequential applied batches. Pruning
2838    /// the first write retires only its location, and `get()` still serves the second key.
2839    ///
2840    /// `open_db_small_sections` must return a DB whose log has `items_per_section=1`
2841    /// so pruning is per-item.
2842    #[boxed]
2843    pub(crate) async fn run_prune_collision_bucket<F: Family, V, C>(
2844        context: deterministic::Context,
2845        open_db_small_sections: impl Fn(
2846            deterministic::Context,
2847        )
2848            -> Pin<Box<dyn Future<Output = TestDb<F, V, C>> + Send>>,
2849    ) where
2850        V: ValueEncoding<Value = Digest>,
2851        C: Mutable<Item = Operation<F, Digest, V>>,
2852        C::Item: EncodeShared,
2853    {
2854        let db = open_db_small_sections(context.child("db")).await;
2855
2856        // Two keys sharing the first two bytes collide under TwoCap.
2857        let mut k1_bytes = [0u8; 32];
2858        let mut k2_bytes = [0u8; 32];
2859        k1_bytes[0] = 0xAA;
2860        k1_bytes[1] = 0xBB;
2861        k2_bytes[0] = 0xAA;
2862        k2_bytes[1] = 0xBB;
2863        k1_bytes[31] = 0x01;
2864        k2_bytes[31] = 0x02;
2865        let key1 = Digest::from(k1_bytes);
2866        let key2 = Digest::from(k2_bytes);
2867        let key3 = Sha256::fill(3u8);
2868        let v1 = Sha256::fill(1u8);
2869        let v2 = Sha256::fill(2u8);
2870        let v3 = Sha256::fill(3u8);
2871
2872        // The log holds 0=initial commit, 1=Set(key1,v1), 2=Commit, 3-4=Set(key2,v2) and
2873        // Set(key3,v3), 5=Commit(floor=4). Floor=4 permits prune(2).
2874        let merkleized = db
2875            .new_batch()
2876            .set(key1, v1)
2877            .merkleize(&db, None, Location::new(0))
2878            .await
2879            .unwrap();
2880        let db = db.apply_batch(merkleized).await.unwrap();
2881        let merkleized = db
2882            .new_batch()
2883            .set(key2, v2)
2884            .set(key3, v3)
2885            .merkleize(&db, None, Location::new(4))
2886            .await
2887            .unwrap();
2888        let db = db.apply_batch(merkleized).await.unwrap();
2889        assert_eq!(db.get(&key1).await.unwrap(), Some(v1));
2890        assert_eq!(db.get(&key2).await.unwrap(), Some(v2));
2891        assert_eq!(db.snapshot.len(), 3);
2892
2893        // Prune past the first Set (loc 1). With items_per_section=1, pruning to loc 2 removes
2894        // the blob containing loc 1 and retires only that location from the snapshot.
2895        let db = db.prune(Location::new(2)).await.unwrap();
2896        assert_eq!(db.bounds().start, Location::new(2));
2897        assert_eq!(db.snapshot.len(), 2);
2898        assert_eq!(db.get(&key1).await.unwrap(), None);
2899        assert_eq!(db.get(&key2).await.unwrap(), Some(v2));
2900        assert_eq!(db.get(&key3).await.unwrap(), Some(v3));
2901
2902        db.destroy().await.unwrap();
2903    }
2904
2905    /// Every prune retires exactly the sets below the retained start, and a set at the retained
2906    /// start survives and stays readable.
2907    ///
2908    /// `open_db_small_sections` must return a DB whose log has `items_per_section=1`
2909    /// so pruning is per-item.
2910    #[boxed]
2911    pub(crate) async fn run_prune_retires_pruned_sets<F: Family, V, C>(
2912        context: deterministic::Context,
2913        open_db_small_sections: impl Fn(
2914            deterministic::Context,
2915        )
2916            -> Pin<Box<dyn Future<Output = TestDb<F, V, C>> + Send>>,
2917    ) where
2918        V: ValueEncoding<Value = Digest>,
2919        C: Mutable<Item = Operation<F, Digest, V>>,
2920        C::Item: EncodeShared,
2921    {
2922        let mut db = open_db_small_sections(context.child("db")).await;
2923
2924        // The log holds 0=initial commit, then Set(key i) at 2i+1 and Commit at 2i+2.
2925        for i in 0..10u8 {
2926            let floor = db.bounds().end;
2927            let merkleized = db
2928                .new_batch()
2929                .set(Sha256::fill(i), Sha256::fill(i))
2930                .merkleize(&db, None, floor)
2931                .await
2932                .unwrap();
2933            db = db.apply_batch(merkleized).await.unwrap();
2934        }
2935        assert_eq!(db.bounds(), Location::new(0)..Location::new(21));
2936        assert_eq!(db.snapshot.len(), 10);
2937
2938        // Pruning to a commit retires the sets before it: one per two pruned operations.
2939        for loc in [2u64, 4, 6] {
2940            db = db.prune(Location::new(loc)).await.unwrap();
2941            assert_eq!(db.bounds().start, Location::new(loc));
2942            assert_eq!(db.snapshot.len(), 10 - loc as usize / 2);
2943        }
2944        assert!(db.get(&Sha256::fill(0)).await.unwrap().is_none());
2945
2946        // Pruning to the set at 9 retires the set at 7 and keeps the one at the retained start.
2947        db = db.prune(Location::new(9)).await.unwrap();
2948        assert_eq!(db.snapshot.len(), 6);
2949        assert_eq!(
2950            db.get(&Sha256::fill(4)).await.unwrap(),
2951            Some(Sha256::fill(4))
2952        );
2953
2954        // Pruning past it retires it.
2955        db = db.prune(Location::new(10)).await.unwrap();
2956        assert_eq!(db.snapshot.len(), 5);
2957        assert!(db.get(&Sha256::fill(4)).await.unwrap().is_none());
2958        assert_eq!(
2959            db.get(&Sha256::fill(5)).await.unwrap(),
2960            Some(Sha256::fill(5))
2961        );
2962
2963        db.destroy().await.unwrap();
2964    }
2965
2966    /// Metadata propagates through merkleize and clears with None.
2967    #[boxed]
2968    pub(crate) async fn run_batch_metadata<F: Family, V, C>(
2969        context: deterministic::Context,
2970        open_db: impl Fn(
2971            deterministic::Context,
2972        ) -> Pin<Box<dyn Future<Output = TestDb<F, V, C>> + Send>>,
2973    ) where
2974        V: ValueEncoding<Value = Digest>,
2975        C: Mutable<Item = Operation<F, Digest, V>>,
2976        C::Item: EncodeShared,
2977    {
2978        let db = open_db(context.child("db")).await;
2979
2980        // Batch with metadata.
2981        let metadata = Sha256::fill(42u8);
2982        let k = Sha256::hash(&[&[1u8]]);
2983        let merkleized = db
2984            .new_batch()
2985            .set(k, Sha256::fill(1u8))
2986            .merkleize(&db, Some(metadata), Location::new(0))
2987            .await
2988            .unwrap();
2989        let db = db.apply_batch(merkleized).await.unwrap();
2990        assert_eq!(db.get_metadata().await.unwrap(), Some(metadata));
2991
2992        // Second batch clears metadata.
2993        let merkleized = db
2994            .new_batch()
2995            .merkleize(&db, None, Location::new(0))
2996            .await
2997            .unwrap();
2998        let db = db.apply_batch(merkleized).await.unwrap();
2999        assert_eq!(db.get_metadata().await.unwrap(), None);
3000
3001        db.destroy().await.unwrap();
3002    }
3003
3004    #[boxed]
3005    pub(crate) async fn run_merkleize_foreign_db<F: Family, V, C>(
3006        db: TestDb<F, V, C>,
3007        foreign: TestDb<F, V, C>,
3008    ) where
3009        V: ValueEncoding<Value = Digest>,
3010        C: Mutable<Item = Operation<F, Digest, V>>,
3011        C::Item: EncodeShared,
3012    {
3013        let key = Sha256::fill(1);
3014        let batch = db
3015            .new_batch()
3016            .set(key, Sha256::fill(11))
3017            .merkleize(&db, None, Location::new(0))
3018            .await
3019            .unwrap();
3020        let db = db.apply_batch(batch).await.unwrap();
3021        let batch = foreign
3022            .new_batch()
3023            .set(key, Sha256::fill(99))
3024            .merkleize(&foreign, None, Location::new(0))
3025            .await
3026            .unwrap();
3027        let foreign = foreign.apply_batch(batch).await.unwrap();
3028        assert_eq!(db.size(), foreign.size());
3029        assert_ne!(db.root(), foreign.root());
3030
3031        let direct = db.new_batch().set(Sha256::fill(2), Sha256::fill(22));
3032        let direct = direct.merkleize(&foreign, None, Location::new(0)).await;
3033        let parent = db
3034            .new_batch()
3035            .set(Sha256::fill(3), Sha256::fill(33))
3036            .merkleize(&db, None, Location::new(0))
3037            .await
3038            .unwrap();
3039        let child = parent
3040            .new_batch::<Sha256>()
3041            .set(Sha256::fill(4), Sha256::fill(44));
3042        let child = child.merkleize(&foreign, None, Location::new(0)).await;
3043        assert!(matches!(direct, Err(Error::StaleBatch)));
3044        assert!(matches!(child, Err(Error::StaleBatch)));
3045        db.destroy().await.unwrap();
3046        foreign.destroy().await.unwrap();
3047    }
3048
3049    #[boxed]
3050    pub(crate) async fn run_merkleize_stale_sibling<F: Family, V, C>(
3051        context: deterministic::Context,
3052        open_db: impl Fn(
3053            deterministic::Context,
3054        ) -> Pin<Box<dyn Future<Output = TestDb<F, V, C>> + Send>>,
3055    ) where
3056        V: ValueEncoding<Value = Digest>,
3057        C: Mutable<Item = Operation<F, Digest, V>>,
3058        C::Item: EncodeShared,
3059    {
3060        let db = open_db(context.child("db")).await;
3061
3062        let direct = db.new_batch().set(Sha256::fill(1), Sha256::fill(11));
3063        let sibling = db
3064            .new_batch()
3065            .set(Sha256::fill(2), Sha256::fill(22))
3066            .merkleize(&db, None, Location::new(0))
3067            .await
3068            .unwrap();
3069        let db = db.apply_batch(sibling).await.unwrap();
3070        let direct = direct.merkleize(&db, None, Location::new(0)).await;
3071        assert!(matches!(direct, Err(Error::StaleBatch)));
3072
3073        let parent = db
3074            .new_batch()
3075            .set(Sha256::fill(3), Sha256::fill(33))
3076            .merkleize(&db, None, Location::new(0))
3077            .await
3078            .unwrap();
3079        let child = parent
3080            .new_batch::<Sha256>()
3081            .set(Sha256::fill(4), Sha256::fill(44));
3082        let sibling = db
3083            .new_batch()
3084            .set(Sha256::fill(5), Sha256::fill(55))
3085            .merkleize(&db, None, Location::new(0))
3086            .await
3087            .unwrap();
3088        let db = db.apply_batch(sibling).await.unwrap();
3089        let child = child.merkleize(&db, None, Location::new(0)).await;
3090        assert!(matches!(child, Err(Error::StaleBatch)));
3091
3092        db.destroy().await.unwrap();
3093    }
3094
3095    #[boxed]
3096    pub(crate) async fn run_stale_batch_rejected<F: Family, V, C>(
3097        context: deterministic::Context,
3098        open_db: impl Fn(
3099            deterministic::Context,
3100        ) -> Pin<Box<dyn Future<Output = TestDb<F, V, C>> + Send>>,
3101    ) where
3102        V: ValueEncoding<Value = Digest>,
3103        C: Mutable<Item = Operation<F, Digest, V>>,
3104        C::Item: EncodeShared,
3105    {
3106        let db = open_db(context.child("db")).await;
3107
3108        let key1 = Sha256::hash(&[&[1]]);
3109        let key2 = Sha256::hash(&[&[2]]);
3110        let v1 = Sha256::fill(10u8);
3111        let v2 = Sha256::fill(20u8);
3112
3113        // Create two batches from the same DB state.
3114        let batch_a = db
3115            .new_batch()
3116            .set(key1, v1)
3117            .merkleize(&db, None, Location::new(0))
3118            .await
3119            .unwrap();
3120        let batch_b = db
3121            .new_batch()
3122            .set(key2, v2)
3123            .merkleize(&db, None, Location::new(0))
3124            .await
3125            .unwrap();
3126
3127        // Applying and committing the first must succeed.
3128        let db = db.apply_batch(batch_a).await.unwrap();
3129        assert_eq!(db.get(&key1).await.unwrap(), Some(v1));
3130        assert_eq!(db.get(&key2).await.unwrap(), None);
3131        assert_eq!(db.get_metadata().await.unwrap(), None);
3132        let db = db.commit().await.unwrap();
3133        let root = db.root();
3134        let size = db.size();
3135
3136        // Applying the second must fail because the DB was modified.
3137        let result = db.apply_batch(batch_b).await;
3138        assert!(matches!(result, Err(Error::StaleBatch)));
3139
3140        // The rejection mutated nothing: reopening recovers the committed state.
3141        let db = open_db(context.child("reopen")).await;
3142        assert_eq!(db.root(), root);
3143        assert_eq!(db.size(), size);
3144        assert_eq!(db.get(&key1).await.unwrap(), Some(v1));
3145        assert_eq!(db.get(&key2).await.unwrap(), None);
3146        db.destroy().await.unwrap();
3147    }
3148
3149    #[boxed]
3150    pub(crate) async fn run_stale_batch_chained<F: Family, V, C>(
3151        context: deterministic::Context,
3152        open_db: impl Fn(
3153            deterministic::Context,
3154        ) -> Pin<Box<dyn Future<Output = TestDb<F, V, C>> + Send>>,
3155    ) where
3156        V: ValueEncoding<Value = Digest>,
3157        C: Mutable<Item = Operation<F, Digest, V>>,
3158        C::Item: EncodeShared,
3159    {
3160        let db = open_db(context.child("db")).await;
3161
3162        let key1 = Sha256::hash(&[&[1]]);
3163        let key2 = Sha256::hash(&[&[2]]);
3164        let key3 = Sha256::hash(&[&[3]]);
3165
3166        let common_parent = db
3167            .new_batch()
3168            .set(Sha256::hash(&[&[10]]), Sha256::fill(10u8))
3169            .merkleize(&db, None, Location::new(0))
3170            .await
3171            .unwrap();
3172        let sibling_a = common_parent
3173            .new_batch::<Sha256>()
3174            .set(Sha256::hash(&[&[11]]), Sha256::fill(11u8))
3175            .merkleize(&db, None, Location::new(0))
3176            .await
3177            .unwrap();
3178        let sibling_b = common_parent
3179            .new_batch::<Sha256>()
3180            .set(Sha256::hash(&[&[12]]), Sha256::fill(12u8))
3181            .merkleize(&db, None, Location::new(0))
3182            .await
3183            .unwrap();
3184        let db = db.apply_batch(sibling_a).await.unwrap();
3185        assert!(matches!(
3186            db.validate_batch(&sibling_b),
3187            Err(Error::StaleBatch)
3188        ));
3189
3190        // Build equal-size sibling parents, then extend only one sibling.
3191        let parent_a = db
3192            .new_batch()
3193            .set(key1, Sha256::fill(1u8))
3194            .merkleize(&db, None, Location::new(0))
3195            .await
3196            .unwrap();
3197        let parent_b = db
3198            .new_batch()
3199            .set(key2, Sha256::fill(2u8))
3200            .merkleize(&db, None, Location::new(0))
3201            .await
3202            .unwrap();
3203        let child_b = parent_b
3204            .new_batch::<Sha256>()
3205            .set(key3, Sha256::fill(3u8))
3206            .merkleize(&db, None, Location::new(0))
3207            .await
3208            .unwrap();
3209
3210        let db = db.apply_batch(parent_a).await.unwrap();
3211        assert!(matches!(
3212            db.validate_batch(&child_b),
3213            Err(Error::StaleBatch)
3214        ));
3215        db.destroy().await.unwrap();
3216    }
3217
3218    /// Applying a batch over an applied ancestor and unapplied ones, including an empty one,
3219    /// inserts one snapshot location per key, matching the snapshot rebuilt on reopen.
3220    #[boxed]
3221    pub(crate) async fn run_partial_ancestor_commit<F: Family, V, C>(
3222        context: deterministic::Context,
3223        open_db: impl Fn(
3224            deterministic::Context,
3225        ) -> Pin<Box<dyn Future<Output = TestDb<F, V, C>> + Send>>,
3226    ) where
3227        V: ValueEncoding<Value = Digest>,
3228        C: Mutable<Item = Operation<F, Digest, V>>,
3229        C::Item: EncodeShared,
3230    {
3231        let db = open_db(context.child("db")).await;
3232
3233        let key1 = Sha256::hash(&[&[1]]);
3234        let key2 = Sha256::hash(&[&[2]]);
3235        let key3 = Sha256::hash(&[&[3]]);
3236        let v1 = Sha256::fill(1u8);
3237        let v2 = Sha256::fill(2u8);
3238        let v3 = Sha256::fill(3u8);
3239
3240        // Chain: DB <- A <- E <- B <- C, where E is empty.
3241        let a = db
3242            .new_batch()
3243            .set(key1, v1)
3244            .merkleize(&db, None, Location::new(0))
3245            .await
3246            .unwrap();
3247        let e = a
3248            .new_batch::<Sha256>()
3249            .merkleize(&db, None, Location::new(0))
3250            .await
3251            .unwrap();
3252        let b = e
3253            .new_batch::<Sha256>()
3254            .set(key2, v2)
3255            .merkleize(&db, None, Location::new(0))
3256            .await
3257            .unwrap();
3258        let c = b
3259            .new_batch::<Sha256>()
3260            .set(key3, v3)
3261            .merkleize(&db, None, Location::new(0))
3262            .await
3263            .unwrap();
3264
3265        let expected_root = c.root();
3266
3267        // Apply only A, then apply C directly (E and B unapplied).
3268        let db = db.apply_batch(a).await.unwrap();
3269        let db = db.apply_batch(c).await.unwrap();
3270
3271        assert_eq!(db.root(), expected_root);
3272        assert_eq!(db.get(&key1).await.unwrap(), Some(v1));
3273        assert_eq!(db.get(&key2).await.unwrap(), Some(v2));
3274        assert_eq!(db.get(&key3).await.unwrap(), Some(v3));
3275
3276        // The snapshot holds one location per key.
3277        let keys = [key1, key2, key3];
3278        let locations = |db: &TestDb<F, V, C>| {
3279            keys.iter()
3280                .map(|key| {
3281                    let mut locations: Vec<_> = db.snapshot.get(key).collect();
3282                    locations.sort();
3283                    locations
3284                })
3285                .collect::<Vec<_>>()
3286        };
3287        assert_eq!(db.snapshot.len(), keys.len());
3288        let live = locations(&db);
3289
3290        // Reopen: the rebuilt snapshot holds the same locations.
3291        db.sync().await.unwrap();
3292        let db = open_db(context.child("reopen")).await;
3293        assert_eq!(db.snapshot.len(), keys.len());
3294        assert_eq!(locations(&db), live);
3295
3296        db.destroy().await.unwrap();
3297    }
3298
3299    #[boxed]
3300    pub(crate) async fn run_delayed_merkleize_after_ancestor_apply<F: Family, V, C>(
3301        context: deterministic::Context,
3302        open_db: impl Fn(
3303            deterministic::Context,
3304        ) -> Pin<Box<dyn Future<Output = TestDb<F, V, C>> + Send>>,
3305    ) where
3306        V: ValueEncoding<Value = Digest>,
3307        C: Mutable<Item = Operation<F, Digest, V>>,
3308        C::Item: EncodeShared,
3309    {
3310        let db = open_db(context.child("db")).await;
3311
3312        let key1 = Sha256::hash(&[&[1]]);
3313        let key2 = Sha256::hash(&[&[2]]);
3314        let key3 = Sha256::hash(&[&[3]]);
3315        let v1 = Sha256::fill(1u8);
3316        let v2 = Sha256::fill(2u8);
3317        let v3 = Sha256::fill(3u8);
3318
3319        let a = db
3320            .new_batch()
3321            .set(key1, v1)
3322            .merkleize(&db, None, Location::new(0))
3323            .await
3324            .unwrap();
3325        let b = a
3326            .new_batch::<Sha256>()
3327            .set(key2, v2)
3328            .merkleize(&db, None, Location::new(0))
3329            .await
3330            .unwrap();
3331        let pending = b
3332            .new_batch::<Sha256>()
3333            .set(key3, v3)
3334            .merkleize(&db, None, Location::new(0))
3335            .await
3336            .unwrap();
3337        let expected_root = pending.root();
3338        let c = b.new_batch::<Sha256>().set(key3, v3);
3339
3340        // The database may advance to a held intermediate ancestor.
3341        let db = db.apply_batch(Arc::clone(&a)).await.unwrap();
3342        let live = b
3343            .new_batch::<Sha256>()
3344            .set(key3, v3)
3345            .merkleize(&db, None, Location::new(0))
3346            .await
3347            .unwrap();
3348        assert_eq!(live.root(), expected_root);
3349
3350        // Retiring that ancestor advances the effective database boundary.
3351        drop(a);
3352        let retired = c.merkleize(&db, None, Location::new(0)).await.unwrap();
3353        assert_eq!(retired.bounds().db, db.commitment());
3354        assert_eq!(retired.root(), expected_root);
3355
3356        let c = b.new_batch::<Sha256>().set(key3, v3);
3357        let db = db.apply_batch(b).await.unwrap();
3358        let c = c.merkleize(&db, None, Location::new(0)).await.unwrap();
3359        assert_eq!(c.root(), expected_root);
3360        let db = db.apply_batch(c).await.unwrap();
3361
3362        assert_eq!(db.root(), expected_root);
3363        assert_eq!(db.get(&key1).await.unwrap(), Some(v1));
3364        assert_eq!(db.get(&key2).await.unwrap(), Some(v2));
3365        assert_eq!(db.get(&key3).await.unwrap(), Some(v3));
3366
3367        db.destroy().await.unwrap();
3368    }
3369
3370    #[boxed]
3371    pub(crate) async fn run_sequential_commit_parent_then_child<F: Family, V, C>(
3372        context: deterministic::Context,
3373        open_db: impl Fn(
3374            deterministic::Context,
3375        ) -> Pin<Box<dyn Future<Output = TestDb<F, V, C>> + Send>>,
3376    ) where
3377        V: ValueEncoding<Value = Digest>,
3378        C: Mutable<Item = Operation<F, Digest, V>>,
3379        C::Item: EncodeShared,
3380    {
3381        let db = open_db(context.child("db")).await;
3382
3383        let key1 = Sha256::hash(&[&[1]]);
3384        let key2 = Sha256::hash(&[&[2]]);
3385        let v1 = Sha256::fill(1u8);
3386        let v2 = Sha256::fill(2u8);
3387
3388        // Parent batch.
3389        let parent_m = db
3390            .new_batch()
3391            .set(key1, v1)
3392            .merkleize(&db, None, Location::new(0))
3393            .await
3394            .unwrap();
3395
3396        // Child batch built on parent.
3397        let child_m = parent_m
3398            .new_batch::<Sha256>()
3399            .set(key2, v2)
3400            .merkleize(&db, None, Location::new(0))
3401            .await
3402            .unwrap();
3403
3404        // Apply parent first, then child, as a sequential chain.
3405        let db = db.apply_batch(parent_m).await.unwrap();
3406        let db = db.apply_batch(child_m).await.unwrap();
3407
3408        // Both keys present.
3409        assert_eq!(db.get(&key1).await.unwrap(), Some(v1));
3410        assert_eq!(db.get(&key2).await.unwrap(), Some(v2));
3411
3412        db.destroy().await.unwrap();
3413    }
3414
3415    #[boxed]
3416    pub(crate) async fn run_child_root_matches_pending_and_committed<F: Family, V, C>(
3417        context: deterministic::Context,
3418        open_db: impl Fn(
3419            deterministic::Context,
3420        ) -> Pin<Box<dyn Future<Output = TestDb<F, V, C>> + Send>>,
3421    ) where
3422        V: ValueEncoding<Value = Digest>,
3423        C: Mutable<Item = Operation<F, Digest, V>>,
3424        C::Item: EncodeShared,
3425    {
3426        let db = open_db(context.child("db")).await;
3427
3428        let key1 = Sha256::hash(&[&[1]]);
3429        let key2 = Sha256::hash(&[&[2]]);
3430
3431        // Build the child while the parent is unapplied.
3432        let parent = db
3433            .new_batch()
3434            .set(key1, Sha256::fill(1u8))
3435            .merkleize(&db, None, Location::new(0))
3436            .await
3437            .unwrap();
3438        let pending_child = parent
3439            .new_batch::<Sha256>()
3440            .set(key2, Sha256::fill(2u8))
3441            .merkleize(&db, None, Location::new(0))
3442            .await
3443            .unwrap();
3444
3445        // Commit the parent, then rebuild the same logical child from the
3446        // committed DB state and compare roots.
3447        let db = db.apply_batch(parent).await.unwrap();
3448        let db = db.commit().await.unwrap();
3449
3450        let committed_child = db
3451            .new_batch()
3452            .set(key2, Sha256::fill(2u8))
3453            .merkleize(&db, None, Location::new(0))
3454            .await
3455            .unwrap();
3456
3457        assert_eq!(pending_child.root(), committed_child.root());
3458
3459        db.destroy().await.unwrap();
3460    }
3461
3462    #[boxed]
3463    pub(crate) async fn run_stale_batch_child_applied_before_parent<F: Family, V, C>(
3464        context: deterministic::Context,
3465        open_db: impl Fn(
3466            deterministic::Context,
3467        ) -> Pin<Box<dyn Future<Output = TestDb<F, V, C>> + Send>>,
3468    ) where
3469        V: ValueEncoding<Value = Digest>,
3470        C: Mutable<Item = Operation<F, Digest, V>>,
3471        C::Item: EncodeShared,
3472    {
3473        let db = open_db(context.child("db")).await;
3474
3475        let key1 = Sha256::hash(&[&[1]]);
3476        let key2 = Sha256::hash(&[&[2]]);
3477
3478        // Parent batch.
3479        let parent_m = db
3480            .new_batch()
3481            .set(key1, Sha256::fill(1u8))
3482            .merkleize(&db, None, Location::new(0))
3483            .await
3484            .unwrap();
3485
3486        // Child batch.
3487        let child_m = parent_m
3488            .new_batch::<Sha256>()
3489            .set(key2, Sha256::fill(2u8))
3490            .merkleize(&db, None, Location::new(0))
3491            .await
3492            .unwrap();
3493
3494        // Apply child first (it carries all parent ops too).
3495        let db = db.apply_batch(child_m).await.unwrap();
3496
3497        // Parent is stale.
3498        let result = db.apply_batch(parent_m).await;
3499        assert!(matches!(result, Err(Error::StaleBatch)));
3500    }
3501
3502    /// to_batch() creates an owned batch whose root matches the DB's.
3503    /// A child batch chained from it can be applied.
3504    #[boxed]
3505    pub(crate) async fn run_to_batch<F: Family, V, C>(
3506        context: deterministic::Context,
3507        open_db: impl Fn(
3508            deterministic::Context,
3509        ) -> Pin<Box<dyn Future<Output = TestDb<F, V, C>> + Send>>,
3510    ) where
3511        V: ValueEncoding<Value = Digest>,
3512        C: Mutable<Item = Operation<F, Digest, V>>,
3513        C::Item: EncodeShared,
3514    {
3515        let db = open_db(context.child("db")).await;
3516
3517        // Populate.
3518        let key1 = Sha256::hash(&[&[1]]);
3519        let v1 = Sha256::fill(10u8);
3520        let merkleized = db
3521            .new_batch()
3522            .set(key1, v1)
3523            .merkleize(&db, None, Location::new(0))
3524            .await
3525            .unwrap();
3526        let db = db.apply_batch(merkleized).await.unwrap();
3527
3528        // to_batch root matches the DB's root.
3529        let snapshot = db.to_batch();
3530        assert_eq!(snapshot.root(), db.root());
3531
3532        // Chain a child from `snapshot` and apply it.
3533        let key2 = Sha256::hash(&[&[2]]);
3534        let v2 = Sha256::fill(20u8);
3535        let child = snapshot
3536            .new_batch::<Sha256>()
3537            .set(key2, v2)
3538            .merkleize(&db, None, Location::new(0))
3539            .await
3540            .unwrap();
3541        let db = db.apply_batch(child).await.unwrap();
3542
3543        assert_eq!(db.get(&key1).await.unwrap(), Some(v1));
3544        assert_eq!(db.get(&key2).await.unwrap(), Some(v2));
3545
3546        db.destroy().await.unwrap();
3547    }
3548
3549    /// Regression: applying a batch after its unapplied ancestor's Arc is
3550    /// dropped must still apply the ancestor's snapshot diffs.
3551    #[boxed]
3552    pub(crate) async fn run_apply_after_ancestor_dropped<F: Family, V, C>(
3553        context: deterministic::Context,
3554        open_db: impl Fn(
3555            deterministic::Context,
3556        ) -> Pin<Box<dyn Future<Output = TestDb<F, V, C>> + Send>>,
3557    ) where
3558        V: ValueEncoding<Value = Digest>,
3559        C: Mutable<Item = Operation<F, Digest, V>>,
3560        C::Item: EncodeShared,
3561    {
3562        let db = open_db(context.child("db")).await;
3563
3564        let key1 = Sha256::hash(&[&[1]]);
3565        let key2 = Sha256::hash(&[&[2]]);
3566        let key3 = Sha256::hash(&[&[3]]);
3567        let v1 = Sha256::fill(1u8);
3568        let v2 = Sha256::fill(2u8);
3569        let v3 = Sha256::fill(3u8);
3570
3571        // Chain: DB <- A <- B <- C
3572        let a = db
3573            .new_batch()
3574            .set(key1, v1)
3575            .merkleize(&db, None, Location::new(0))
3576            .await
3577            .unwrap();
3578        let b = a
3579            .new_batch::<Sha256>()
3580            .set(key2, v2)
3581            .merkleize(&db, None, Location::new(0))
3582            .await
3583            .unwrap();
3584        let c = b
3585            .new_batch::<Sha256>()
3586            .set(key3, v3)
3587            .merkleize(&db, None, Location::new(0))
3588            .await
3589            .unwrap();
3590
3591        // Drop A and B without committing. Their Weak refs in C are now dead.
3592        drop(a);
3593        drop(b);
3594
3595        // Apply only the tip. This is !skip_ancestors (DB hasn't changed).
3596        let db = db.apply_batch(c).await.unwrap();
3597
3598        // All three keys must be in the snapshot.
3599        assert_eq!(db.get(&key1).await.unwrap(), Some(v1));
3600        assert_eq!(db.get(&key2).await.unwrap(), Some(v2));
3601        assert_eq!(db.get(&key3).await.unwrap(), Some(v3));
3602
3603        db.destroy().await.unwrap();
3604    }
3605
3606    /// Verify the inactivity floor is zero for a fresh empty database and is
3607    /// correctly set after applying batches with specific floor values.
3608    #[boxed]
3609    pub(crate) async fn run_inactivity_floor_tracking<F: Family, V, C>(
3610        context: deterministic::Context,
3611        open_db: impl Fn(
3612            deterministic::Context,
3613        ) -> Pin<Box<dyn Future<Output = TestDb<F, V, C>> + Send>>,
3614    ) where
3615        V: ValueEncoding<Value = Digest>,
3616        C: Mutable<Item = Operation<F, Digest, V>>,
3617        C::Item: EncodeShared,
3618    {
3619        let db = open_db(context.child("test")).await;
3620
3621        // Empty DB has floor=0.
3622        assert_eq!(db.inactivity_floor_loc(), Location::new(0));
3623
3624        // Apply batch with floor=0, floor stays 0.
3625        let k1 = Sha256::fill(1u8);
3626        let v1 = Sha256::fill(2u8);
3627        let merkleized = db
3628            .new_batch()
3629            .set(k1, v1)
3630            .merkleize(&db, None, Location::new(0))
3631            .await
3632            .unwrap();
3633        let db = db.apply_batch(merkleized).await.unwrap();
3634        assert_eq!(db.inactivity_floor_loc(), Location::new(0));
3635
3636        // Apply batch with floor=3, floor advances.
3637        let k2 = Sha256::fill(3u8);
3638        let v2 = Sha256::fill(4u8);
3639        let merkleized = db
3640            .new_batch()
3641            .set(k2, v2)
3642            .merkleize(&db, None, Location::new(3))
3643            .await
3644            .unwrap();
3645        let db = db.apply_batch(merkleized).await.unwrap();
3646        assert_eq!(db.inactivity_floor_loc(), Location::new(3));
3647
3648        // Floor persists across restart.
3649        let db = db.commit().await.unwrap();
3650        db.sync().await.unwrap();
3651        let db = open_db(context.child("reopen")).await;
3652        assert_eq!(db.inactivity_floor_loc(), Location::new(3));
3653
3654        db.destroy().await.unwrap();
3655    }
3656
3657    /// Verify that applying a batch with a floor equal to the current floor succeeds,
3658    /// and that a higher floor also succeeds.
3659    #[boxed]
3660    pub(crate) async fn run_floor_monotonicity<F: Family, V, C>(
3661        context: deterministic::Context,
3662        open_db: impl Fn(
3663            deterministic::Context,
3664        ) -> Pin<Box<dyn Future<Output = TestDb<F, V, C>> + Send>>,
3665    ) where
3666        V: ValueEncoding<Value = Digest>,
3667        C: Mutable<Item = Operation<F, Digest, V>>,
3668        C::Item: EncodeShared,
3669    {
3670        let db = open_db(context.child("test")).await;
3671
3672        // DB starts with 1 op (initial commit).
3673        // First batch: 1 set + 1 commit = total_size 3. Use floor=2 (the commit loc).
3674        let k1 = Sha256::fill(1u8);
3675        let v1 = Sha256::fill(2u8);
3676        let merkleized = db
3677            .new_batch()
3678            .set(k1, v1)
3679            .merkleize(&db, None, Location::new(2))
3680            .await
3681            .unwrap();
3682        let db = db.apply_batch(merkleized).await.unwrap();
3683        assert_eq!(db.inactivity_floor_loc(), Location::new(2));
3684
3685        // Same floor is OK. Second batch: 1 set + 1 commit = total_size 5. floor=2 < 5.
3686        let k2 = Sha256::fill(3u8);
3687        let v2 = Sha256::fill(4u8);
3688        let merkleized = db
3689            .new_batch()
3690            .set(k2, v2)
3691            .merkleize(&db, None, Location::new(2))
3692            .await
3693            .unwrap();
3694        let db = db.apply_batch(merkleized).await.unwrap();
3695        assert_eq!(db.inactivity_floor_loc(), Location::new(2));
3696
3697        // Higher floor also succeeds. Third batch: 1 set + 1 commit = total_size 7. floor=5 < 7.
3698        let k3 = Sha256::fill(5u8);
3699        let v3 = Sha256::fill(6u8);
3700        let merkleized = db
3701            .new_batch()
3702            .set(k3, v3)
3703            .merkleize(&db, None, Location::new(5))
3704            .await
3705            .unwrap();
3706        let db = db.apply_batch(merkleized).await.unwrap();
3707        assert_eq!(db.inactivity_floor_loc(), Location::new(5));
3708
3709        db.destroy().await.unwrap();
3710    }
3711
3712    /// Verify that the inactivity floor is correctly restored after a recovery.
3713    #[boxed]
3714    pub(crate) async fn run_bounded_initialization_restores_floor<F: Family, V, C>(
3715        context: deterministic::Context,
3716        open_db: impl Fn(
3717            deterministic::Context,
3718            Option<Location<F>>,
3719        )
3720            -> Pin<Box<dyn Future<Output = Result<TestDb<F, V, C>, Error<F>>> + Send>>,
3721    ) where
3722        V: ValueEncoding<Value = Digest>,
3723        C: Mutable<Item = Operation<F, Digest, V>>,
3724        C::Item: EncodeShared,
3725    {
3726        let db = open_db(context.child("test"), None).await.unwrap();
3727
3728        // Apply first batch with floor=2.
3729        let k1 = Sha256::fill(1u8);
3730        let v1 = Sha256::fill(2u8);
3731        let merkleized = db
3732            .new_batch()
3733            .set(k1, v1)
3734            .merkleize(&db, None, Location::new(2))
3735            .await
3736            .unwrap();
3737        let db = db.apply_batch(merkleized).await.unwrap();
3738        let db = db.commit().await.unwrap();
3739        let first_size = db.bounds().end;
3740        assert_eq!(db.inactivity_floor_loc(), Location::new(2));
3741
3742        // Apply second batch with floor=4 (the new commit's location).
3743        let k2 = Sha256::fill(3u8);
3744        let v2 = Sha256::fill(4u8);
3745        let merkleized = db
3746            .new_batch()
3747            .set(k2, v2)
3748            .merkleize(&db, None, Location::new(4))
3749            .await
3750            .unwrap();
3751        let db = db.apply_batch(merkleized).await.unwrap();
3752        let db = db.commit().await.unwrap();
3753        assert_eq!(db.inactivity_floor_loc(), Location::new(4));
3754
3755        // Reopen at the first batch.
3756        _ = db.sync().await.unwrap();
3757        let db = open_db(context.child("cap"), Some(first_size))
3758            .await
3759            .unwrap();
3760        assert_eq!(db.inactivity_floor_loc(), Location::new(2));
3761
3762        db.destroy().await.unwrap();
3763    }
3764
3765    /// Verify that applying a batch with a floor lower than the current floor
3766    /// returns an error.
3767    #[boxed]
3768    pub(crate) async fn run_floor_monotonicity_violation<F: Family, V, C>(
3769        context: deterministic::Context,
3770        open_db: impl Fn(
3771            deterministic::Context,
3772        ) -> Pin<Box<dyn Future<Output = TestDb<F, V, C>> + Send>>,
3773    ) where
3774        V: ValueEncoding<Value = Digest>,
3775        C: Mutable<Item = Operation<F, Digest, V>>,
3776        C::Item: EncodeShared,
3777    {
3778        let db = open_db(context.child("test")).await;
3779
3780        // DB starts with 1 op. First batch: 1 set + 1 commit = total_size 3. floor=2.
3781        let k1 = Sha256::fill(1u8);
3782        let v1 = Sha256::fill(2u8);
3783        let merkleized = db
3784            .new_batch()
3785            .set(k1, v1)
3786            .merkleize(&db, None, Location::new(2))
3787            .await
3788            .unwrap();
3789        let db = db.apply_batch(merkleized).await.unwrap();
3790
3791        // Merkleize a batch with floor=1 (regression). Should return an error.
3792        let k2 = Sha256::fill(3u8);
3793        let v2 = Sha256::fill(4u8);
3794        let result = db
3795            .new_batch()
3796            .set(k2, v2)
3797            .merkleize(&db, None, Location::new(1))
3798            .await;
3799        assert!(matches!(result, Err(Error::FloorRegressed(new, current))
3800                if new == Location::new(1) && current == Location::new(2)));
3801    }
3802
3803    /// Verify that merkleizing a batch with a floor beyond its commit location returns an error.
3804    #[boxed]
3805    pub(crate) async fn run_floor_beyond_size<F: Family, V, C>(
3806        context: deterministic::Context,
3807        open_db: impl Fn(
3808            deterministic::Context,
3809        ) -> Pin<Box<dyn Future<Output = TestDb<F, V, C>> + Send>>,
3810    ) where
3811        V: ValueEncoding<Value = Digest>,
3812        C: Mutable<Item = Operation<F, Digest, V>>,
3813        C::Item: EncodeShared,
3814    {
3815        let db = open_db(context.child("test")).await;
3816
3817        // DB has 1 op (initial commit). A batch with 1 set + 1 commit = total_size 3.
3818        // Setting floor=100 exceeds total_size.
3819        let k1 = Sha256::fill(1u8);
3820        let v1 = Sha256::fill(2u8);
3821        let result = db
3822            .new_batch()
3823            .set(k1, v1)
3824            .merkleize(&db, None, Location::new(100))
3825            .await;
3826        assert!(matches!(result, Err(Error::FloorBeyondSize(floor, commit))
3827                if floor == Location::new(100) && commit == Location::new(2)));
3828
3829        // Boundary: floor == total_size must also be rejected. The commit op is
3830        // at total_size - 1, so a floor equal to total_size would allow a later
3831        // prune to remove the commit and leave the db unrecoverable.
3832        let k2 = Sha256::fill(3u8);
3833        let v2 = Sha256::fill(4u8);
3834        let result = db
3835            .new_batch()
3836            .set(k2, v2)
3837            .merkleize(&db, None, Location::new(3))
3838            .await;
3839        assert!(matches!(result, Err(Error::FloorBeyondSize(floor, commit))
3840                if floor == Location::new(3) && commit == Location::new(2)));
3841
3842        // Floor == total_size - 1 (the commit location) is the maximum valid.
3843        let merkleized = db
3844            .new_batch()
3845            .set(k2, v2)
3846            .merkleize(&db, None, Location::new(2))
3847            .await
3848            .unwrap();
3849        let db = db.apply_batch(merkleized).await.unwrap();
3850
3851        db.destroy().await.unwrap();
3852    }
3853
3854    /// Merkleize returns `FloorRegressed` for a chained batch whose floor is below its parent's
3855    /// floor, even though that floor is still at or above the database's floor.
3856    #[boxed]
3857    pub(crate) async fn run_chained_floor_regression<F: Family, V, C>(
3858        context: deterministic::Context,
3859        open_db: impl Fn(
3860            deterministic::Context,
3861        ) -> Pin<Box<dyn Future<Output = TestDb<F, V, C>> + Send>>,
3862    ) where
3863        V: ValueEncoding<Value = Digest>,
3864        C: Mutable<Item = Operation<F, Digest, V>>,
3865        C::Item: EncodeShared,
3866    {
3867        let db = open_db(context.child("test")).await;
3868
3869        // The database's floor is 0 (from the seeded initial commit).
3870        // a: 1 set + commit at loc 2, floor=2 (valid: >= 0, == commit_loc).
3871        // b: 1 set + commit at loc 4, floor=1 (regresses below a's floor=2, but still >= 0).
3872        // Merkleizing b must fail with FloorRegressed(1, 2). The bound is a's floor, not the
3873        // database's floor.
3874        let a = db
3875            .new_batch()
3876            .set(Sha256::fill(1u8), Sha256::fill(2u8))
3877            .merkleize(&db, None, Location::new(2))
3878            .await
3879            .unwrap();
3880
3881        let Err(err) = a
3882            .new_batch::<Sha256>()
3883            .set(Sha256::fill(3u8), Sha256::fill(4u8))
3884            .merkleize(&db, None, Location::new(1))
3885            .await
3886        else {
3887            panic!("merkleize must reject the invalid floor");
3888        };
3889        assert!(
3890            matches!(err, Error::FloorRegressed(new, prev)
3891                if new == Location::new(1) && prev == Location::new(2)),
3892            "unexpected error: {err:?}"
3893        );
3894
3895        db.destroy().await.unwrap();
3896    }
3897
3898    /// Merkleize returns `FloorBeyondSize` for a chained batch whose floor exceeds its own commit
3899    /// location. The commit location includes the parent's operations. The floor still satisfies
3900    /// monotonicity, so only the commit-location bound rejects it.
3901    #[boxed]
3902    pub(crate) async fn run_chained_floor_beyond_commit<F: Family, V, C>(
3903        context: deterministic::Context,
3904        open_db: impl Fn(
3905            deterministic::Context,
3906        ) -> Pin<Box<dyn Future<Output = TestDb<F, V, C>> + Send>>,
3907    ) where
3908        V: ValueEncoding<Value = Digest>,
3909        C: Mutable<Item = Operation<F, Digest, V>>,
3910        C::Item: EncodeShared,
3911    {
3912        let db = open_db(context.child("test")).await;
3913
3914        // a: 1 set + commit at loc 2, floor=2.
3915        let a = db
3916            .new_batch()
3917            .set(Sha256::fill(1u8), Sha256::fill(2u8))
3918            .merkleize(&db, None, Location::new(2))
3919            .await
3920            .unwrap();
3921
3922        // b: 1 set + commit at loc 4, declaring floor=5 (one past its commit). The error names
3923        // commit location 4, so the bound counts a's operations.
3924        let Err(err) = a
3925            .new_batch::<Sha256>()
3926            .set(Sha256::fill(3u8), Sha256::fill(4u8))
3927            .merkleize(&db, None, Location::new(5))
3928            .await
3929        else {
3930            panic!("merkleize must reject the invalid floor");
3931        };
3932        assert!(
3933            matches!(err, Error::FloorBeyondSize(floor, commit)
3934                if floor == Location::new(5) && commit == Location::new(4)),
3935            "unexpected error: {err:?}"
3936        );
3937
3938        db.destroy().await.unwrap();
3939    }
3940
3941    /// Reinitializing at an earlier commit restores every key active at that commit, even after an
3942    /// ordinary reopen rebuilds the snapshot from the latest floor.
3943    #[boxed]
3944    pub(crate) async fn run_bounded_initialization_after_reopen_with_floor_change<F: Family, V, C>(
3945        context: deterministic::Context,
3946        open_db: impl Fn(
3947            deterministic::Context,
3948            Option<Location<F>>,
3949        )
3950            -> Pin<Box<dyn Future<Output = Result<TestDb<F, V, C>, Error<F>>> + Send>>,
3951    ) where
3952        V: ValueEncoding<Value = Digest>,
3953        C: Mutable<Item = Operation<F, Digest, V>>,
3954        C::Item: EncodeShared,
3955    {
3956        let db = open_db(context.child("first"), None).await.unwrap();
3957
3958        let k1 = Sha256::fill(1u8);
3959        let k2 = Sha256::fill(2u8);
3960        let k3 = Sha256::fill(3u8);
3961        let v1 = Sha256::fill(11u8);
3962        let v2 = Sha256::fill(12u8);
3963        let v3 = Sha256::fill(13u8);
3964
3965        // Commit A: 3 keys with floor=0.
3966        let db = commit_sets(db, [(k1, v1), (k2, v2), (k3, v3)], None).await;
3967        let first_size = db.bounds().end;
3968        let first_root = db.root();
3969
3970        // Commit B: 3 more keys with floor=first_size (declares batch A inactive).
3971        let k4 = Sha256::fill(4u8);
3972        let k5 = Sha256::fill(5u8);
3973        let k6 = Sha256::fill(6u8);
3974        let v4 = Sha256::fill(14u8);
3975        let v5 = Sha256::fill(15u8);
3976        let v6 = Sha256::fill(16u8);
3977        let db = commit_sets_with_floor(db, [(k4, v4), (k5, v5), (k6, v6)], None, first_size).await;
3978        db.sync().await.unwrap();
3979
3980        // Reopen: snapshot rebuilt from floor=first_size, batch A keys excluded.
3981        let db = open_db(context.child("second"), None).await.unwrap();
3982
3983        // Verify batch A keys are NOT in the reopened snapshot (expected).
3984        assert!(db.get(&k1).await.unwrap().is_none());
3985
3986        // Reopen at commit A.
3987        _ = db.sync().await.unwrap();
3988        let db = open_db(context.child("cap"), Some(first_size))
3989            .await
3990            .unwrap();
3991
3992        // All batch A keys must be accessible after recovery.
3993        assert_eq!(db.get(&k1).await.unwrap(), Some(v1));
3994        assert_eq!(db.get(&k2).await.unwrap(), Some(v2));
3995        assert_eq!(db.get(&k3).await.unwrap(), Some(v3));
3996        assert_eq!(db.root(), first_root);
3997        assert_eq!(db.inactivity_floor_loc(), Location::new(0));
3998
3999        // Batch B keys must NOT be accessible.
4000        assert!(db.get(&k4).await.unwrap().is_none());
4001
4002        db.destroy().await.unwrap();
4003    }
4004
4005    /// Opening an earlier commit rebuilds the snapshot from that commit's floor, even when
4006    /// the database was previously opened at a later floor.
4007    #[boxed]
4008    pub(crate) async fn run_bounded_initialization_after_reopen_partial_floor_gap<F: Family, V, C>(
4009        context: deterministic::Context,
4010        open_db: impl Fn(
4011            deterministic::Context,
4012            Option<Location<F>>,
4013        )
4014            -> Pin<Box<dyn Future<Output = Result<TestDb<F, V, C>, Error<F>>> + Send>>,
4015    ) where
4016        V: ValueEncoding<Value = Digest>,
4017        C: Mutable<Item = Operation<F, Digest, V>>,
4018        C::Item: EncodeShared,
4019    {
4020        let db = open_db(context.child("first"), None).await.unwrap();
4021
4022        let k1 = Sha256::fill(1u8);
4023        let v1 = Sha256::fill(11u8);
4024
4025        // Commit A: 1 key, floor=0.
4026        let db = commit_sets(db, [(k1, v1)], None).await;
4027        let first_size = db.bounds().end;
4028        let first_root = db.root();
4029
4030        // Commit B: 1 key, floor=first_size.
4031        let k2 = Sha256::fill(2u8);
4032        let v2 = Sha256::fill(12u8);
4033        let db = commit_sets_with_floor(db, [(k2, v2)], None, first_size).await;
4034        let second_size = db.bounds().end;
4035
4036        // Commit C: 1 key, floor=second_size. This raises the floor
4037        // above commit B's keys, so reopen excludes both A and B keys.
4038        let k3 = Sha256::fill(3u8);
4039        let v3 = Sha256::fill(13u8);
4040        let db = commit_sets_with_floor(db, [(k3, v3)], None, second_size).await;
4041        db.sync().await.unwrap();
4042
4043        // Reopen: snapshot rebuilt from floor=second_size. Only k3 is in snapshot.
4044        let db = open_db(context.child("second"), None).await.unwrap();
4045        assert!(db.get(&k1).await.unwrap().is_none());
4046        assert!(db.get(&k2).await.unwrap().is_none());
4047        assert_eq!(db.get(&k3).await.unwrap(), Some(v3));
4048
4049        // Commit B's snapshot contains k2. Its floor excludes k1, and its end excludes k3.
4050        _ = db.sync().await.unwrap();
4051        let db = open_db(context.child("cap"), Some(second_size))
4052            .await
4053            .unwrap();
4054        assert!(db.get(&k1).await.unwrap().is_none()); // below B's floor
4055        assert_eq!(db.get(&k2).await.unwrap(), Some(v2));
4056        assert!(db.get(&k3).await.unwrap().is_none()); // in suffix, removed
4057
4058        // Open commit A and rebuild its snapshot from floor zero.
4059        _ = db.sync().await.unwrap();
4060        let db = open_db(context.child("cap"), Some(first_size))
4061            .await
4062            .unwrap();
4063        assert_eq!(db.get(&k1).await.unwrap(), Some(v1));
4064        assert!(db.get(&k2).await.unwrap().is_none()); // above first_size, truncated
4065        assert_eq!(db.root(), first_root);
4066        assert_eq!(db.inactivity_floor_loc(), Location::new(0));
4067
4068        db.destroy().await.unwrap();
4069    }
4070
4071    /// After committing with `floor = commit_loc` and pruning down to it, exactly one operation,
4072    /// the commit itself, is at or above the floor. This is the smallest nonempty such set the
4073    /// per-commit bound allows. The DB must remain fully usable:
4074    ///
4075    /// - `prune(commit_loc + 1)` is rejected (the floor is a hard ceiling).
4076    /// - `prune` does not affect the root (documented invariant).
4077    /// - Reopen reconstructs `inactivity_floor_loc` from the sole remaining commit op, and the
4078    ///   in-memory snapshot is empty (all Sets were below the floor).
4079    /// - A follow-on batch applies cleanly on top from the floor-at-max state.
4080    #[boxed]
4081    pub(crate) async fn run_single_commit_live_set<F: Family, V, C>(
4082        context: deterministic::Context,
4083        open_db: impl Fn(
4084            deterministic::Context,
4085        ) -> Pin<Box<dyn Future<Output = TestDb<F, V, C>> + Send>>,
4086    ) where
4087        V: ValueEncoding<Value = Digest>,
4088        C: Mutable<Item = Operation<F, Digest, V>>,
4089        C::Item: EncodeShared,
4090    {
4091        let db = open_db(context.child("test")).await;
4092
4093        // Initial commit is at loc 0. 3 sets + 1 commit -> commit lands at loc 4.
4094        // Declare floor = 4 (= commit_loc), the tight maximum.
4095        let metadata = Sha256::fill(42u8);
4096        let commit_loc = Location::<F>::new(4);
4097        let k1 = Sha256::fill(1u8);
4098        let k2 = Sha256::fill(2u8);
4099        let k3 = Sha256::fill(3u8);
4100        let v1 = Sha256::fill(11u8);
4101        let v2 = Sha256::fill(12u8);
4102        let v3 = Sha256::fill(13u8);
4103        let merkleized = db
4104            .new_batch()
4105            .set(k1, v1)
4106            .set(k2, v2)
4107            .set(k3, v3)
4108            .merkleize(&db, Some(metadata), commit_loc)
4109            .await
4110            .unwrap();
4111        let db = db.apply_batch(merkleized).await.unwrap();
4112        let db = db.commit().await.unwrap();
4113        assert_eq!(db.size() - 1, commit_loc);
4114        assert_eq!(db.inactivity_floor_loc(), commit_loc);
4115        let root_after_commit = db.root();
4116
4117        // All three keys are in the in-memory snapshot pre-prune.
4118        assert_eq!(db.get(&k1).await.unwrap(), Some(v1));
4119        assert_eq!(db.get(&k2).await.unwrap(), Some(v2));
4120        assert_eq!(db.get(&k3).await.unwrap(), Some(v3));
4121
4122        // Prune at the floor, the maximum prune allowed.
4123        // Pruning is blob-aligned, so `bounds.start` may not physically advance all the way
4124        // to `commit_loc`. What matters semantically is that the floor authorizes pruning
4125        // of everything below the commit and that any further prune is rejected.
4126        let db = db.prune(commit_loc).await.unwrap();
4127        let bounds = db.bounds();
4128        assert!(
4129            bounds.start <= commit_loc,
4130            "prune must not advance bounds.start past the floor"
4131        );
4132        assert_eq!(bounds.end, commit_loc + 1);
4133
4134        // State preserved across the prune; root unchanged; commit metadata still readable.
4135        assert_eq!(db.size() - 1, commit_loc);
4136        assert_eq!(db.inactivity_floor_loc(), commit_loc);
4137        assert_eq!(db.root(), root_after_commit);
4138        assert_eq!(db.get_metadata().await.unwrap(), Some(metadata));
4139
4140        // Persist, then verify pruning one past the floor is rejected, because the floor is
4141        // the hard ceiling.
4142        let db = db.sync().await.unwrap();
4143        let Err(err) = db.prune(commit_loc + 1).await else {
4144            panic!("expected prune to fail");
4145        };
4146        assert!(matches!(err, Error::PruneBeyondMinRequired(p, f)
4147                if *p == *commit_loc + 1 && *f == *commit_loc));
4148
4149        // Reopening rebuilds the snapshot from the inactivity floor. Only the commit
4150        // remains at or above that floor, so the rebuilt snapshot contains no keys.
4151        let db = open_db(context.child("reopened")).await;
4152        assert_eq!(db.size() - 1, commit_loc);
4153        assert_eq!(db.inactivity_floor_loc(), commit_loc);
4154        assert_eq!(db.root(), root_after_commit);
4155
4156        // The commit op at `commit_loc` is the anchor that survived pruning. Its metadata
4157        // must come back through `get_metadata` after the snapshot rebuild.
4158        assert_eq!(db.get_metadata().await.unwrap(), Some(metadata));
4159
4160        // Keys set below the floor are excluded from the rebuilt snapshot.
4161        assert!(db.get(&k1).await.unwrap().is_none());
4162        assert!(db.get(&k2).await.unwrap().is_none());
4163        assert!(db.get(&k3).await.unwrap().is_none());
4164
4165        // A follow-on batch applies on top. Monotonicity requires the new floor to be at
4166        // least `commit_loc` (= 4). Advancing to the new tight max (= 6) exercises the
4167        // floor-at-max -> new-batch transition.
4168        let k4 = Sha256::fill(4u8);
4169        let v4 = Sha256::fill(14u8);
4170        let next_commit_loc = Location::<F>::new(6);
4171        let merkleized = db
4172            .new_batch()
4173            .set(k4, v4)
4174            .merkleize(&db, None, next_commit_loc)
4175            .await
4176            .unwrap();
4177        let db = db.apply_batch(merkleized).await.unwrap();
4178        let db = db.commit().await.unwrap();
4179        assert_eq!(db.size() - 1, next_commit_loc);
4180        assert_eq!(db.inactivity_floor_loc(), next_commit_loc);
4181
4182        // New key readable; keys from the pre-prune batch remain excluded.
4183        assert_eq!(db.get(&k4).await.unwrap(), Some(v4));
4184        assert!(db.get(&k1).await.unwrap().is_none());
4185        // Follow-on commit replaced the anchor: its metadata was `None`, so `get_metadata`
4186        // should no longer return the original metadata.
4187        assert_eq!(db.get_metadata().await.unwrap(), None);
4188
4189        db.destroy().await.unwrap();
4190    }
4191
4192    /// `get_many` on the DB and on unmerkleized/merkleized batches returns results
4193    /// that match individual `get` calls.
4194    #[boxed]
4195    pub(crate) async fn run_get_many<F: Family, V, C>(
4196        context: deterministic::Context,
4197        open_db: impl Fn(
4198            deterministic::Context,
4199        ) -> Pin<Box<dyn Future<Output = TestDb<F, V, C>> + Send>>,
4200    ) where
4201        V: ValueEncoding<Value = Digest>,
4202        C: Mutable<Item = Operation<F, Digest, V>>,
4203        C::Item: EncodeShared,
4204    {
4205        let db = open_db(context.child("db")).await;
4206
4207        let k1 = Sha256::fill(1u8);
4208        let k2 = Sha256::fill(2u8);
4209        let k3 = Sha256::fill(3u8);
4210        let k_missing = Sha256::fill(99u8);
4211
4212        let v1 = Sha256::fill(11u8);
4213        let v2 = Sha256::fill(12u8);
4214        let v3 = Sha256::fill(13u8);
4215
4216        // Commit k1 and k2 to disk.
4217        let merkleized = db
4218            .new_batch()
4219            .set(k1, v1)
4220            .set(k2, v2)
4221            .merkleize(&db, None, db.inactivity_floor_loc())
4222            .await
4223            .unwrap();
4224        let db = db.apply_batch(merkleized).await.unwrap();
4225        let db = db.commit().await.unwrap();
4226
4227        // DB-level get_many.
4228        let results = db.get_many(&[&k1, &k2, &k_missing]).await.unwrap();
4229        assert_eq!(results, vec![Some(v1), Some(v2), None]);
4230
4231        // Empty input.
4232        let results = db.get_many(&([] as [&Digest; 0])).await.unwrap();
4233        assert!(results.is_empty());
4234
4235        // Unmerkleized batch: mutations + DB fallthrough.
4236        let batch = db.new_batch().set(k3, v3);
4237        let results = batch.get_many(&[&k3, &k1, &k_missing], &db).await.unwrap();
4238        assert_eq!(results, vec![Some(v3), Some(v1), None]);
4239
4240        // Merkleized batch: diff + parent chain + DB fallthrough.
4241        let parent = db
4242            .new_batch()
4243            .set(k3, v3)
4244            .merkleize(&db, None, db.inactivity_floor_loc())
4245            .await
4246            .unwrap();
4247        let results = parent.get_many(&[&k1, &k3, &k_missing], &db).await.unwrap();
4248        assert_eq!(results, vec![Some(v1), Some(v3), None]);
4249
4250        // Child of merkleized parent reads parent diff.
4251        let k4 = Sha256::fill(4u8);
4252        let v4 = Sha256::fill(14u8);
4253        let child = parent.new_batch::<Sha256>().set(k4, v4);
4254        let results = child
4255            .get_many(&[&k1, &k3, &k4, &k_missing], &db)
4256            .await
4257            .unwrap();
4258        assert_eq!(results, vec![Some(v1), Some(v3), Some(v4), None]);
4259
4260        db.destroy().await.unwrap();
4261    }
4262
4263    /// `get_many` fills every slot of a key listed more than once in the input.
4264    #[boxed]
4265    pub(crate) async fn run_get_many_duplicate_keys<F: Family, V, C>(
4266        context: deterministic::Context,
4267        open_db: impl Fn(
4268            deterministic::Context,
4269        ) -> Pin<Box<dyn Future<Output = TestDb<F, V, C>> + Send>>,
4270    ) where
4271        V: ValueEncoding<Value = Digest>,
4272        C: Mutable<Item = Operation<F, Digest, V>>,
4273        C::Item: EncodeShared,
4274    {
4275        let db = open_db(context.child("db")).await;
4276
4277        let key = Sha256::fill(1u8);
4278        let value = Sha256::fill(11u8);
4279        let k_missing = Sha256::fill(99u8);
4280        let merkleized = db
4281            .new_batch()
4282            .set(key, value)
4283            .merkleize(&db, None, db.inactivity_floor_loc())
4284            .await
4285            .unwrap();
4286        let db = db.apply_batch(merkleized).await.unwrap();
4287        let db = db.commit().await.unwrap();
4288
4289        let results = db.get_many(&[&key, &k_missing, &key, &key]).await.unwrap();
4290        assert_eq!(results, vec![Some(value), None, Some(value), Some(value)]);
4291
4292        db.destroy().await.unwrap();
4293    }
4294
4295    /// `get_many` reports unexpected data when the snapshot points at a non-`Set` operation.
4296    #[boxed]
4297    pub(crate) async fn run_get_many_unexpected_data<F: Family, V, C>(
4298        context: deterministic::Context,
4299        open_db: impl Fn(
4300            deterministic::Context,
4301        ) -> Pin<Box<dyn Future<Output = TestDb<F, V, C>> + Send>>,
4302    ) where
4303        V: ValueEncoding<Value = Digest>,
4304        C: Mutable<Item = Operation<F, Digest, V>>,
4305        C::Item: EncodeShared,
4306    {
4307        let db = open_db(context.child("db")).await;
4308
4309        let key = Sha256::fill(1u8);
4310        let value = Sha256::fill(11u8);
4311        let merkleized = db
4312            .new_batch()
4313            .set(key, value)
4314            .merkleize(&db, None, db.inactivity_floor_loc())
4315            .await
4316            .unwrap();
4317        let db = db.apply_batch(merkleized).await.unwrap();
4318        let mut db = db.commit().await.unwrap();
4319
4320        let bad_key = Sha256::fill(99u8);
4321        let bad_loc = db.size() - 1;
4322        db.snapshot.insert(&bad_key, *bad_loc);
4323
4324        let err = db.get(&bad_key).await.unwrap_err();
4325        assert!(matches!(err, Error::UnexpectedData(loc) if loc == bad_loc));
4326
4327        let err = db.get_many(&[&bad_key]).await.unwrap_err();
4328        assert!(matches!(err, Error::UnexpectedData(loc) if loc == bad_loc));
4329
4330        db.destroy().await.unwrap();
4331    }
4332}