Skip to main content

commonware_storage/qmdb/current/
mod.rs

1//! A _Current_ authenticated database provides succinct proofs of _any_ value ever associated with
2//! a key, and also whether that value is the _current_ value associated with it.
3//!
4//! # Examples
5//!
6//! See [`crate::qmdb::any`] for batch API examples (forking, sequential commit, staleness). The
7//! Current layer uses the same batch API.
8//!
9//! # Batch validity
10//!
11//! Current batches are branch-scoped views, not immutable snapshots.
12//!
13//! A batch remains valid only while its ancestor chain is still the committed prefix of the DB.
14//! Once a non-ancestor batch is applied, that batch and all of its descendants are invalid objects:
15//! do not read through them, do not build children from them, and do not attempt to apply them.
16//!
17//! A short rule of thumb:
18//! - A batch is only usable while it stays on the winning branch.
19//!
20//! Valid:
21//! - Build `A`, apply `A`, then build `B` from `A` and read or merkleize `B`.
22//! - Call [`Db::to_batch`](db::Db::to_batch) and use the returned batch only while no divergent
23//!   branch has been applied.
24//!
25//! Invalid:
26//! - Build siblings `B1` and `B2`, apply `B1`, then call `B2.get()`, `B2.new_batch()`, or
27//!   `apply_batch(B2)`.
28//! - Hold `snapshot = db.to_batch()`, mutate the DB through another branch, then use `snapshot`
29//!   again.
30//!
31//! # Motivation
32//!
33//! An [crate::qmdb::any] ("Any") database can prove that a key had a particular value at some
34//! point, but it cannot prove that the value is still current -- some later operation may have
35//! updated or deleted it. A Current database adds exactly this capability by maintaining a bitmap
36//! that tracks which operations are _active_ (i.e. represent the current state of their key).
37//!
38//! To make this useful, a verifier needs both the operation and its activity status authenticated
39//! under a single root. We achieve this by _grafting_ bitmap chunks onto the operations tree.
40//!
41//! # Data structures
42//!
43//! A Current database ([db::Db]) wraps an Any database and adds:
44//!
45//! - **Status bitmap** ([BitMap]): One bit per operation in the log. Bit _i_ is 1 if operation _i_
46//!   is active, 0 otherwise. The bitmap is divided into fixed-size chunks of `N` bytes (i.e. `N *
47//!   8` bits each). `N` must be a power of two.
48//!
49//!   One exception by convention: the *current* `last_commit_loc` carries bit = 1 even though a
50//!   CommitFloor is not an active update — earlier (intermediate) CommitFloors carry bit =
51//!   0. Maintaining this makes the chunk containing the latest commit deterministic across init and
52//!   `apply_batch`.
53//!
54//!   The bitmap lives on the inner `any::Db.bitmap`; `current::Db` reads through it for
55//!   grafted-tree leaves and proofs.
56//!
57//! - **Grafted tree**: An in-memory Merkle structure of digests at and above the _grafting height_
58//!   in the ops tree. This is the core of how bitmap and ops state are combined into a single
59//!   authenticated structure (see below).
60//!
61//! - **Bitmap metadata** (`Metadata`): Persists the pruning boundary and "pinned" digests needed to
62//!   restore the grafted tree after pruning old bitmap chunks.
63//!
64//! # Grafting: combining the activity status bitmap and the ops tree
65//!
66//! ## The problem
67//!
68//! Naively authenticating the bitmap and ops tree as two independent Merkle structures would
69//! require two separate proofs per operation -- one for the operation's value, one for its activity
70//! status. This doubles proof sizes.
71//!
72//! ## The solution
73//!
74//! We combine ("graft") the two structures at a specific height in the ops tree called the
75//! _grafting height_. The grafting height `h = log2(N * 8)` is chosen so that each subtree of
76//! height `h` in the ops tree covers exactly one bitmap chunk's worth of operations.
77//!
78//! At the grafting height, instead of using the ops tree's own subtree root, we replace it with a
79//! _grafted leaf_ digest that incorporates both the bitmap chunk and the ops subtree root:
80//!
81//! ```text
82//! grafted_leaf = hash(bitmap_chunk || ops_subtree_root)   // non-zero chunk
83//! grafted_leaf = ops_subtree_root                         // all-zero chunk (identity)
84//! ```
85//!
86//! The all-zero identity means that for pruned regions (where every operation is inactive), the
87//! grafted tree is structurally identical to the ops tree at and above the grafting height.
88//!
89//! Above the grafting height, internal nodes use standard hashing over the grafted leaves. Below
90//! the grafting height, the ops tree is unchanged.
91//!
92//! Not every complete chunk is graftable. A chunk is _graftable_ when its height-`h` ancestor
93//! exists as a single node in the ops tree. In MMR every complete chunk is immediately graftable.
94//! In MMB, delayed merges can leave a chunk bit-complete before its height-`h` ancestor is born;
95//! that chunk is _pending_ and its digest is hashed directly into the canonical root until the
96//! merge happens, at which point it migrates into the grafted tree. See [Pending and partial
97//! chunks](#pending-and-partial-chunks).
98//!
99//! ## Example
100//!
101//! Consider 8 operations with `N = 1` (8-bit chunks, so `h = log2(8) = 3`). But to illustrate the
102//! structure more clearly, let's use a smaller example: 8 operations with chunk size 4 bits (`h =
103//! 2`), yielding 2 complete bitmap chunks:
104//!
105//! ```text
106//! Ops tree positions (8 leaves):
107//!
108//!   Height
109//!     3              14                    <-- peak: digest commits to ops tree and bitmap chunks
110//!                  /    \
111//!                 /      \
112//!                /        \
113//!     2  [G]    6          13    [G]       <-- grafting height: grafted leaves
114//!             /   \      /    \
115//!     1      2     5    9     12           <-- below grafting height: pure ops tree nodes
116//!           / \   / \  / \   /  \
117//!     0    0   1 3   4 7  8 10  11
118//!          ^           ^
119//!          |           |
120//!      ops 0-3     ops 4-7
121//!      chunk 0     chunk 1
122//! ```
123//!
124//! Positions 6 and 13 are at the grafting height. Their digests are:
125//! - `pos 6: hash(chunk_0 || ops_subtree_root(pos 6))`
126//! - `pos 13: hash(chunk_1 || ops_subtree_root(pos 13))`
127//!
128//! Position 14 (above grafting height) is a standard internal node:
129//! - `pos 14: hash(14 || digest(pos 6) || digest(pos 13))`
130//!
131//! The grafted tree stores positions 6, 13, and 14. The ops tree stores everything below (positions
132//! 0-5 and 7-12). Together they form a single virtual Merkle structure whose root authenticates
133//! both the operations and their activity status.
134//!
135//! ## Proof generation and verification
136//!
137//! To prove that operation _i_ is active, we provide:
138//! 1. An inclusion proof for the operation's leaf, using the virtual (grafted) storage.
139//! 2. The bitmap chunk containing bit _i_.
140//!
141//! The verifier (see `grafting::Verifier`) walks the proof from leaf to root. Below the grafting
142//! height, it uses standard hashing. At the grafting height, it detects the boundary and
143//! reconstructs the grafted leaf from the chunk and the ops subtree root. For non-zero chunks the
144//! grafted leaf is `hash(chunk || ops_subtree_root)`; for all-zero chunks the grafted leaf is the
145//! ops subtree root itself (identity optimization -- see `grafting::Verifier::node`). Above the
146//! grafting height, it resumes standard hashing. If the reconstructed root matches the expected
147//! root and bit _i_ is set in the chunk, the operation is proven active.
148//!
149//! This is a single proof path, not two independent ones -- the bitmap chunk is embedded in the
150//! proof verification at the grafting boundary.
151//!
152//! Range proofs over a window that includes the trailing pending or partial chunk additionally
153//! carry that chunk's digest in the proof and have the verifier re-derive it from the supplied
154//! chunk bytes. Graftable-chunk reconstruction is always single-peak: the height-`gh` ancestor
155//! exists in the ops tree and yields a single grafted leaf to fold against the bitmap chunk.
156//!
157//! ## Pending and partial chunks
158//!
159//! Two kinds of bitmap chunk are not grafted into the overlay at height `gh`; instead, their
160//! digests are hashed into the canonical root directly alongside `ops_root` and `grafted_root`.
161//!
162//! - **Partial chunk.** The trailing bitmap chunk is usually incomplete (fewer than `N * 8` bits).
163//!   It has no grafted leaf because no corresponding subtree exists in the ops tree.
164//!
165//! - **Pending chunk.** A chunk whose bits are complete but whose height-`gh` ancestor has not yet
166//!   been born in the ops tree. Its leaves are split across multiple sub-`gh` peaks, so there is no
167//!   single ops node to graft onto. The chunk is _deferred_ until the merge happens, at which point
168//!   it migrates into the grafted tree.
169//!
170//! Pending chunks only arise in families with delayed merges (MMB). Define `birth_chunk_0` as the
171//! value of `ops_leaves` at which chunk 0's height-`gh` ancestor is first born in the ops tree.
172//! Then chunk `i` is graftable when `ops_leaves >= i * 2^gh + birth_chunk_0`.
173//!
174//! - In MMR, `birth_chunk_0 = 2^gh`, so a chunk is graftable the moment it is bit-complete and
175//!   pending chunks never exist.
176//! - In MMB, `birth_chunk_0 = 3 * 2^(gh-1) - 1`, which exceeds the chunk size `2^gh` by `2^(gh-1) -
177//!   1`. That gap is the **pending window** for chunk `i`: ops_leaves values for which chunk `i` is
178//!   bit-complete but not yet graftable.
179//!
180//! The pending window is strictly narrower than one chunk stride, so **at most one chunk is pending
181//! at any time**. The pending and partial chunks are independent: at `gh >= 3` both can be present;
182//! at `gh == 1` the pending window is empty and only a partial chunk is ever present.
183//!
184//! ### Chunk lifecycle (MMB)
185//!
186//! ```text
187//!                       ops_leaves N grows --->
188//!
189//!   chunk i state:    incomplete    pending          graftable
190//!                    (bits being   (bits set;       (h=gh ancestor born;
191//!                     appended)    ancestor not      chunk grafted onto
192//!                                  yet born)         that ops node)
193//!
194//!   N reaches:                    (i+1)*2^gh     i*2^gh + birth_chunk_0
195//!                                 ^                  ^
196//!                                 |                  |
197//!                                 chunk completes    chunk becomes graftable
198//!
199//!   Where chunk i's bytes live:
200//!     incomplete -> canonical root, as partial_chunk_digest
201//!     pending    -> canonical root, as pending_chunk_digest
202//!     graftable  -> grafted tree
203//!
204//!   At any moment, the canonical root may include zero, one, or both of {pending, partial};
205//!   the grafted tree commits to every chunk in [pruned_chunks, graftable_chunks).
206//! ```
207//!
208//! ### Worked example: pending + partial coexistence (MMB, `gh = 3`)
209//!
210//! For `gh = 3` MMB, `birth_chunk_0 = 3 * 2^(gh-1) - 1 = 11`. Chunk 0 fills at `N = 8` but
211//! does not activate until `N = 11`. The pending window is `N` in `[8, 10]`.
212//!
213//! ```text
214//!   Snapshot at N = 10 (chunk 0 bit-complete; chunk 1 has 2 of 8 bits):
215//!
216//!     chunks (8 bits each)     chunk 0       chunk 1
217//!     ops index range          0..7          8..15
218//!     bits set                 11111111      11
219//!     state                    pending       incomplete
220//!
221//!     graftable_chunks = 0      (chunk 0's h=3 ancestor is not yet born)
222//!     grafted_root           no graftable substitutions; equals ops-tree bagged root
223//!     pending_chunk_digest   = H(chunk_0_bytes)
224//!     partial_chunk_digest   = (next_bit = 2, H(chunk_1_bytes))
225//!     canonical_root         = hash(ops_root || grafted_root || pending
226//!                                                            || next_bit || partial)
227//!
228//!   At N = 11, chunk 0's h=3 ancestor is born:
229//!     graftable_chunks becomes 1, the pending slot is dropped, chunk 0 migrates
230//!     into the grafted tree.
231//! ```
232//!
233//! See [Root structure](#root-structure) below for the canonical root layout.
234//!
235//! ## Incremental updates
236//!
237//! When operations are added or bits change (e.g. an operation becomes inactive during floor
238//! raising), only the affected chunks are marked "dirty". During `merkleize`, only dirty grafted
239//! leaves are recomputed and their ancestors are propagated upward through the cache. This avoids
240//! recomputing the entire grafted tree.
241//!
242//! ## Pruning
243//!
244//! Old bitmap chunks (below the inactivity floor) can be pruned. Before pruning, the grafted tree's
245//! peak digests covering the pruned region are persisted to metadata as "pinned nodes". On
246//! recovery, these pinned nodes are loaded and serve as opaque siblings during upward propagation,
247//! allowing the grafted tree to be rebuilt without the pruned chunks.
248//!
249//! ### Delayed-merge settlement
250//!
251//! For families with delayed merges (e.g. MMB), pruning is slightly more conservative than the
252//! inactivity floor alone would allow.
253//!
254//! The grafted root is computed by iterating the _ops tree's_ peaks and looking up the
255//! corresponding nodes in the grafted tree. After pruning, only the grafted tree's pinned peaks are
256//! available in the pruned region; interior nodes (including individual grafted leaves) are
257//! discarded. If the ops tree has a peak that maps to a discarded grafted node, root computation
258//! fails.
259//!
260//! In an MMR the ops tree's peaks within the pruned region always coincide with the grafted tree's
261//! pinned peaks, so this is never a problem. In an MMB, delayed merges cause the ops tree's peak
262//! structure to lag behind: a chunk pair's parent node at height `gh+1` is not created until some
263//! number of leaves after the pair's last leaf. Until that merge happens, the ops tree still has
264//! individual height-`gh` peaks for each chunk in the pair, and those map to grafted _leaves_
265//! (height 0 in the grafted tree), which are not pinned peaks.
266//!
267//! To avoid this, [`Db::prune`](db::Db::prune) defers bitmap pruning for chunks whose chunk-pair
268//! parent has not yet been born in the ops tree (see `Db::sync_boundary`). Once the parent is born,
269//! every ops peak within the pruned region is at height `gh+1` or above, and maps to a pinned peak
270//! or an ancestor of pinned peaks that can be reconstructed by hashing children (see
271//! `grafting::Storage::reconstruct_grafted_node`).
272//!
273//! The same birth threshold also defines a _rewind floor_: rewinding the database to a size where
274//! the chunk-pair parent has not been born would re-expose the individual ops peaks and break
275//! reconstruction. [`Db::rewind`](db::Db::rewind) rejects targets below this floor. The floor is a
276//! pure function of the pruned chunk count and the family geometry, so it does not need to be
277//! persisted; it is recomputed on startup from the pruned chunk count stored in metadata.
278//!
279//! The pruning lag is small: at most `2^(gh+1) - 1` ops beyond the chunk boundary (just under 2
280//! chunks for the default chunk size).
281//!
282//! # Root structure
283//!
284//! The canonical root of a `current` database is:
285//!
286//! ```text
287//! root = hash(
288//!     ops_root
289//!     || grafted_root
290//!     [|| pending_chunk_digest]
291//!     [|| next_bit || partial_chunk_digest]
292//! )
293//! ```
294//!
295//! Components:
296//!
297//! - **Ops root**: The root of the raw operations tree (the inner [crate::qmdb::any] database's
298//!   root). Used for state sync, where a client downloads operations and verifies each batch
299//!   against this root using ops-tree range proofs.
300//!
301//! - **Grafted root**: The bagged root of the virtual overlay (see `grafting::Storage`) that shares
302//!   the ops-tree topology but substitutes graftable chunks at height `gh`. When no chunks are
303//!   graftable, `grafted_root` still reflects the ops-tree peak structure. Used for proofs about
304//!   operation values and their activity status. See [RangeProof](proof::RangeProof) and
305//!   [OperationProof](proof::OperationProof).
306//!
307//! - **Pending chunk digest** (optional): `H(pending_chunk_bytes)` when a chunk's bits are complete
308//!   but its height-`gh` ancestor has not yet been born in the ops tree. Absent in MMR and in the
309//!   steady state of MMB.
310//!
311//! - **Partial chunk** (optional): When the bitmap length is not chunk-aligned, the trailing
312//!   incomplete chunk's digest and bit count are folded in.
313//!
314//! Pending and partial slots are independent. When both are present, pending hashes in before
315//! partial.
316//!
317//! The canonical root is returned by [Db](db::Db)`::`[root()](db::Db::root). The ops root is
318//! returned by the `sync::Database` trait's `root()` method, since the sync engine verifies batches
319//! against the ops root, not the canonical root.
320//!
321//! For state sync, the sync engine targets the ops root and verifies each batch against it. Callers
322//! verifying ops proofs directly should use [`crate::qmdb::verify_proof`]. After sync, the bitmap
323//! and grafted tree are reconstructed deterministically from the operations, and the canonical root
324//! is computed. [proof::OpsRootWitness] can be used to validate that a particular ops root is
325//! committed by a trusted canonical root; the sync engine does not perform this check itself.
326
327use crate::{
328    index::Factory as IndexFactory,
329    journal::{
330        authenticated::Inner,
331        contiguous::{fixed::Config as FConfig, variable::Config as VConfig},
332    },
333    merkle::{self, full::Config as MerkleConfig, Location},
334    qmdb::{
335        any::{
336            self,
337            operation::{Operation, Update},
338            Config as AnyConfig,
339        },
340        bitmap::Shared,
341        operation::Committable,
342    },
343    translator::Translator,
344    Context,
345};
346use commonware_codec::{CodecShared, FixedSize};
347use commonware_cryptography::Hasher;
348use commonware_macros::boxed;
349use commonware_parallel::Strategy;
350use commonware_utils::bitmap::Prunable as BitMap;
351use core::num::NonZeroUsize;
352use std::sync::Arc;
353
354pub mod batch;
355pub mod db;
356pub mod grafting;
357
358pub mod ordered;
359pub mod proof;
360pub(crate) mod sync;
361pub mod unordered;
362
363use self::db::Metrics;
364
365/// Configuration for a `Current` authenticated db.
366#[derive(Clone)]
367pub struct Config<T: Translator, J, S: Strategy> {
368    /// Configuration for the Merkle structure backing the authenticated journal.
369    pub merkle_config: MerkleConfig<S>,
370
371    /// Configuration for the operations log journal.
372    pub journal_config: J,
373
374    /// The name of the storage partition used for grafted tree metadata.
375    pub grafted_metadata_partition: String,
376
377    /// The translator used by the compressed index.
378    pub translator: T,
379
380    /// Capacity (in entries) of the `(location -> key)` cache used during init to resolve snapshot
381    /// collisions without re-reading the log; `None` disables it.
382    pub init_cache_size: Option<NonZeroUsize>,
383}
384
385impl<T: Translator, J, S: Strategy> From<Config<T, J, S>> for AnyConfig<T, J, S> {
386    fn from(cfg: Config<T, J, S>) -> Self {
387        Self {
388            merkle_config: cfg.merkle_config,
389            journal_config: cfg.journal_config,
390            translator: cfg.translator,
391            init_cache_size: cfg.init_cache_size,
392        }
393    }
394}
395
396/// Configuration for a `Current` authenticated db with fixed-size values.
397pub type FixedConfig<T, S> = Config<T, FConfig, S>;
398
399/// Configuration for a `Current` authenticated db with variable-sized values.
400pub type VariableConfig<T, C, S> = Config<T, VConfig<C>, S>;
401
402/// Initialize a `Current` authenticated db from the given config.
403#[boxed]
404pub(super) async fn init<F, E, U, H, T, I, J, const N: usize, S>(
405    context: E,
406    config: Config<T, J::Config, S>,
407) -> Result<db::Db<F, E, J, I, H, U, N, S>, crate::qmdb::Error<F>>
408where
409    F: merkle::Graftable,
410    E: Context,
411    U: Update + Send + Sync,
412    H: Hasher,
413    T: Translator,
414    I: IndexFactory<T, Value = Location<F>>,
415    J: Inner<E, Item = Operation<F, U>>,
416    S: Strategy,
417    Operation<F, U>: Committable + CodecShared,
418{
419    // TODO: Re-evaluate assertion placement after `generic_const_exprs` is stable.
420    const {
421        // A compile-time assertion that the chunk size is some multiple of digest size. A multiple
422        // of 1 is optimal with respect to proof size, but a higher multiple allows for a smaller
423        // (RAM resident) merkle tree over the structure.
424        assert!(
425            N.is_multiple_of(H::Digest::SIZE),
426            "chunk size must be some multiple of the digest size",
427        );
428        // A compile-time assertion that chunk size is a power of 2, which is necessary to allow
429        // the status bitmap tree to be aligned with the underlying operations MMR.
430        assert!(N.is_power_of_two(), "chunk size must be a power of 2");
431    }
432
433    let strategy = config.merkle_config.strategy.clone();
434    let metadata_partition = config.grafted_metadata_partition.clone();
435
436    // Load bitmap metadata (pruned_chunks + pinned nodes for the grafted tree).
437    let (metadata, pruned_chunks, pinned_nodes) =
438        db::init_metadata(context.child("metadata"), &metadata_partition).await?;
439
440    // Pre-build the activity-status bitmap with the known pruned-chunk count from grafted
441    // metadata, then hand it to `any` which becomes the sole owner. `any::init_with_bitmap`
442    // populates it during snapshot rebuild.
443    let bitmap = BitMap::<N>::new_with_pruned_chunks(pruned_chunks)
444        .map_err(|_| crate::qmdb::Error::<F>::DataCorrupted("pruned chunks overflow"))?;
445    let bitmap = Arc::new(Shared::<N>::new(bitmap));
446
447    let any = any::init_with_bitmap(context.child("any"), config.into(), Some(bitmap)).await?;
448
449    // Build the grafted tree from the bitmap and ops tree.
450    let ops_size = any.log.merkle.size();
451    let ops_leaves = crate::merkle::Location::<F>::try_from(ops_size)?;
452    let grafted_tree = db::build_grafted_tree::<F, H, S, N>(
453        any.bitmap.as_ref(),
454        &pinned_nodes,
455        &any.log.merkle,
456        ops_leaves,
457        &strategy,
458    )
459    .await?;
460
461    // Compute and cache the root.
462    let storage = grafting::Storage::<F, H, _, _>::new(
463        &grafted_tree,
464        grafting::height::<N>(),
465        &any.log.merkle,
466    );
467    let partial_chunk = db::partial_chunk(any.bitmap.as_ref());
468    let ops_root = any.root();
469    let root = db::compute_db_root::<F, H, _, _, N>(
470        any.bitmap.as_ref(),
471        &storage,
472        ops_leaves,
473        partial_chunk,
474        any.inactivity_floor_loc,
475        &ops_root,
476    )
477    .await?;
478
479    let metrics = Metrics::new(context);
480    let db = db::Db {
481        any,
482        grafted_tree: Arc::new(grafted_tree),
483        metadata,
484        strategy,
485        root,
486        metrics,
487        #[cfg(test)]
488        halt_before_prune_log: false,
489    };
490    db.update_metrics();
491    Ok(db)
492}
493
494/// Extension trait for Current QMDB types that exposes bitmap information for testing.
495#[cfg(any(test, feature = "test-traits"))]
496pub trait BitmapPrunedBits {
497    /// Returns the number of bits that have been pruned from the bitmap.
498    fn pruned_bits(&self) -> u64;
499
500    /// Returns the value of the bit at the given index.
501    fn get_bit(&self, index: u64) -> bool;
502
503    /// Returns the position of the oldest retained bit.
504    fn oldest_retained(&self) -> u64;
505}
506
507#[cfg(test)]
508pub mod tests {
509    //! Shared test utilities for Current QMDB variants.
510
511    pub use super::BitmapPrunedBits;
512    use super::{ordered, unordered, FConfig, FixedConfig, MerkleConfig, VConfig, VariableConfig};
513    use crate::{
514        merkle::{self, mmb, mmr},
515        qmdb::{
516            any::{
517                test::colliding_digest,
518                traits::{DbAny, MerkleizedBatch as _, UnmerkleizedBatch as _},
519            },
520            store::tests::{TestKey, TestValue},
521            verify_proof,
522        },
523        translator::Translator,
524    };
525    use commonware_parallel::Sequential;
526    use commonware_runtime::{
527        buffer::paged::CacheRef,
528        deterministic::{self, Context},
529        BufferPooler, Runner as _, Supervisor as _,
530    };
531    use commonware_utils::{bitmap::Readable, NZUsize, TestRng, NZU16, NZU64};
532    use core::future::Future;
533    use ordered::tests::test_build_small_close_reopen as test_ordered_build_small_close_reopen;
534    use rand::Rng;
535    use std::{
536        num::{NonZeroU16, NonZeroUsize},
537        sync::Arc,
538    };
539    use tracing::warn;
540    use unordered::tests::test_build_small_close_reopen as test_unordered_build_small_close_reopen;
541
542    type Error<F> = crate::qmdb::Error<F>;
543    type Location<F> = merkle::Location<F>;
544    type WriteVec<F, C> = Vec<(<C as DbAny<F>>::Key, Option<<C as DbAny<F>>::Value>)>;
545
546    // Janky page & cache sizes to exercise boundary conditions.
547    const PAGE_SIZE: NonZeroU16 = NZU16!(88);
548    const PAGE_CACHE_SIZE: NonZeroUsize = NZUsize!(8);
549
550    /// Instantiate the staged-merkleize parity test for one current DB kind, with `$open_db` as
551    /// the kind's test DB constructor.
552    ///
553    /// The staged path (`stage` + `Staged::merkleize`) must produce a root byte-identical to an
554    /// explicit `get_many` + `write` + `merkleize` over the current layer, across updates,
555    /// deletes (which fall back to normal mutations and, for the ordered kind, rewrite
556    /// predecessors via a snapshot-bucket scan), upserts, duplicate read slots, missing keys,
557    /// and prefix-then-suffix expansion, rooted at the DB (D=0) and through one or two pending
558    /// ancestors (D=1/D=2). This guards the current-layer threading of
559    /// `bitmap_parent`/`grafted_parent`, global read-index assignment across `expand`, and
560    /// `compute_current_layer` for non-empty staged updates. Collision-prone translators in
561    /// `$open_db` (e.g. `OneCap`) stress predecessor rewrites.
562    macro_rules! staged_merkleize_parity_test {
563        ($name:ident, $open_db:path) => {
564            #[test_traced("WARN")]
565            pub fn $name() {
566                fn key(i: u64) -> Digest {
567                    Sha256::hash(&i.to_be_bytes())
568                }
569                fn val(i: u64) -> Digest {
570                    Sha256::hash(&(i + 10000).to_be_bytes())
571                }
572
573                deterministic::Runner::default().start(|ctx| async move {
574                    let mut db = $open_db(ctx.child("current"), "staged-parity".to_string()).await;
575
576                    let mut seed = db.new_batch();
577                    for i in 0..2000u64 {
578                        seed = seed.write(key(i), Some(val(i)));
579                    }
580                    let seed = seed.merkleize(&db, None).await.unwrap();
581                    db.apply_batch(seed).await.unwrap();
582                    db.commit().await.unwrap();
583
584                    for depth in [0u8, 1u8, 2u8] {
585                        // Keep every uncommitted ancestor alive until the child is merkleized.
586                        // Speculative batch Merkle lookups walk weak parent links for in-memory
587                        // ancestor nodes.
588                        let mut stack = Vec::new();
589                        match depth {
590                            0 => {}
591                            1 => {
592                                let mut p = db.new_batch();
593                                for i in 0..50u64 {
594                                    p = p.write(key(i), Some(val(i + 1_000)));
595                                }
596                                for i in 100..110u64 {
597                                    p = p.write(key(i), None);
598                                }
599                                stack.push(p.merkleize(&db, None).await.unwrap());
600                            }
601                            2 => {
602                                let mut grandparent = db.new_batch();
603                                for i in 0..10u64 {
604                                    grandparent = grandparent.write(key(i), Some(val(i + 1_000)));
605                                }
606                                for i in 100..110u64 {
607                                    grandparent = grandparent.write(key(i), None);
608                                }
609                                let grandparent = grandparent.merkleize(&db, None).await.unwrap();
610
611                                let mut p = grandparent.new_batch::<Sha256>();
612                                for i in 20..30u64 {
613                                    p = p.write(key(i), Some(val(i + 2_000)));
614                                }
615                                let p = p.merkleize(&db, None).await.unwrap();
616                                stack.push(grandparent);
617                                stack.push(p);
618                            }
619                            _ => unreachable!("covered depths"),
620                        };
621                        let new_batch = || {
622                            stack
623                                .last()
624                                .map_or_else(|| db.new_batch(), |p| p.new_batch::<Sha256>())
625                        };
626
627                        // key(60) is untouched by the depth-1/2 ancestors, so its staged read
628                        // stays committed-resolved and exercises staged cached-location reuse
629                        // behind stacked batches.
630                        let read_keys = [
631                            key(5),
632                            key(6),
633                            key(9000),
634                            key(5),
635                            key(0),
636                            key(20),
637                            key(60),
638                            key(105),
639                        ];
640                        let keys: Vec<&Digest> = read_keys.iter().collect();
641                        let indexed_updates = vec![
642                            (0, Some(val(5_000))),
643                            (2, Some(val(5_001))),
644                            (3, Some(val(5_002))),
645                            (4, Some(val(5_003))),
646                            (5, None),
647                            (6, Some(val(5_004))),
648                            (7, Some(val(5_005))),
649                        ];
650                        let upserts = vec![
651                            (key(7000), Some(val(6_000))),
652                            (key(30), Some(val(6_001))),
653                            (key(5), Some(val(6_002))),
654                            (key(31), None),
655                        ];
656
657                        let mut explicit = new_batch();
658                        let explicit_values = explicit.get_many(&keys, &db).await.unwrap();
659                        for (slot, value) in &indexed_updates {
660                            explicit = explicit.write(read_keys[*slot], *value);
661                        }
662                        for (k, v) in &upserts {
663                            explicit = explicit.write(*k, *v);
664                        }
665                        let explicit_root = explicit.merkleize(&db, None).await.unwrap().root();
666
667                        let (staged_values, staged) = new_batch().stage(&keys, &db).await.unwrap();
668                        let staged_root = staged
669                            .merkleize(indexed_updates.clone(), upserts.clone(), None, &db)
670                            .await
671                            .unwrap()
672                            .root();
673
674                        assert_eq!(
675                            explicit_values, staged_values,
676                            "value mismatch at depth={depth}"
677                        );
678                        assert_eq!(explicit_root, staged_root, "root mismatch at depth={depth}");
679
680                        let split = 3;
681                        let (mut expanded_values, staged) =
682                            new_batch().stage(&keys[..split], &db).await.unwrap();
683                        let (range, suffix_values, staged) =
684                            staged.expand(&keys[split..], &db).await.unwrap();
685                        assert_eq!(range, split..keys.len());
686                        expanded_values.extend(suffix_values);
687                        let expanded_root = staged
688                            .merkleize(indexed_updates.clone(), upserts.clone(), None, &db)
689                            .await
690                            .unwrap()
691                            .root();
692
693                        assert_eq!(
694                            explicit_values, expanded_values,
695                            "expanded value mismatch at depth={depth}"
696                        );
697                        assert_eq!(
698                            explicit_root, expanded_root,
699                            "expanded root mismatch at depth={depth}"
700                        );
701
702                        let planned = val(7_000);
703                        let duplicate_update = val(7_001);
704                        let (first_values, staged) =
705                            new_batch().stage(&keys[..1], &db).await.unwrap();
706                        let (duplicate_range, duplicate_values, staged) =
707                            staged.expand(&keys[..1], &db).await.unwrap();
708                        assert_eq!(duplicate_range, 1..2);
709                        assert_eq!(
710                            first_values[0], duplicate_values[0],
711                            "duplicate expansion must assign a new slot without changing the base read"
712                        );
713                        assert_ne!(
714                            duplicate_values[0],
715                            Some(planned),
716                            "expand must not observe values computed for earlier staged slots"
717                        );
718
719                        let duplicate_root = staged
720                            .merkleize(
721                                vec![
722                                    (0, Some(planned)),
723                                    (duplicate_range.start, Some(duplicate_update)),
724                                ],
725                                Vec::new(),
726                                None,
727                                &db,
728                            )
729                            .await
730                            .unwrap()
731                            .root();
732                        let expected_duplicate_root = new_batch()
733                            .write(read_keys[0], Some(planned))
734                            .write(read_keys[0], Some(duplicate_update))
735                            .merkleize(&db, None)
736                            .await
737                            .unwrap()
738                            .root();
739                        assert_eq!(
740                            expected_duplicate_root, duplicate_root,
741                            "duplicate expanded slots should use normal update-order semantics"
742                        );
743                    }
744                });
745            }
746        };
747    }
748    pub(crate) use staged_merkleize_parity_test;
749
750    /// Shared config factory for fixed-value Current QMDB tests.
751    pub(crate) fn fixed_config<T: Translator + Default>(
752        partition_prefix: &str,
753        pooler: &impl BufferPooler,
754    ) -> FixedConfig<T, Sequential> {
755        let page_cache = CacheRef::from_pooler(pooler, PAGE_SIZE, PAGE_CACHE_SIZE);
756        FixedConfig {
757            merkle_config: MerkleConfig {
758                journal_partition: format!("{partition_prefix}-journal-partition"),
759                metadata_partition: format!("{partition_prefix}-metadata-partition"),
760                items_per_blob: NZU64!(11),
761                write_buffer: NZUsize!(1024),
762                strategy: Sequential,
763                page_cache: page_cache.clone(),
764            },
765            journal_config: FConfig {
766                partition: format!("{partition_prefix}-partition-prefix"),
767                items_per_blob: NZU64!(7),
768                page_cache,
769                write_buffer: NZUsize!(1024),
770            },
771            grafted_metadata_partition: format!("{partition_prefix}-grafted-metadata-partition"),
772            translator: T::default(),
773            init_cache_size: Some(NZUsize!(1024)),
774        }
775    }
776
777    /// Shared config factory for variable-value Current QMDB tests with unit codec config.
778    pub(crate) fn variable_config<T: Translator + Default>(
779        partition_prefix: &str,
780        pooler: &impl BufferPooler,
781    ) -> VariableConfig<T, ((), ()), Sequential> {
782        let page_cache = CacheRef::from_pooler(pooler, PAGE_SIZE, PAGE_CACHE_SIZE);
783        VariableConfig {
784            merkle_config: MerkleConfig {
785                journal_partition: format!("{partition_prefix}-journal-partition"),
786                metadata_partition: format!("{partition_prefix}-metadata-partition"),
787                items_per_blob: NZU64!(11),
788                write_buffer: NZUsize!(1024),
789                strategy: Sequential,
790                page_cache: page_cache.clone(),
791            },
792            journal_config: VConfig {
793                partition: format!("{partition_prefix}-partition-prefix"),
794                items_per_section: NZU64!(7),
795                compression: None,
796                codec_config: ((), ()),
797                page_cache,
798                write_buffer: NZUsize!(1024),
799            },
800            grafted_metadata_partition: format!("{partition_prefix}-grafted-metadata-partition"),
801            translator: T::default(),
802            init_cache_size: Some(NZUsize!(1024)),
803        }
804    }
805
806    /// Commit a set of writes as a single batch.
807    ///
808    /// Returns a boxed future so the merkleize/apply/commit chain does not inflate the futures
809    /// (and poll frames) of every test composing commits, which overflow the stack on platforms
810    /// with small defaults.
811    fn commit_writes<'a, F: merkle::Graftable, C: DbAny<F>>(
812        db: &'a mut C,
813        writes: impl IntoIterator<Item = (C::Key, Option<<C as DbAny<F>>::Value>)> + 'a,
814    ) -> std::pin::Pin<Box<dyn Future<Output = Result<(), Error<F>>> + 'a>> {
815        Box::pin(async move {
816            let mut batch = db.new_batch();
817            for (k, v) in writes {
818                batch = batch.write(k, v);
819            }
820            let merkleized = batch.merkleize(db, None).await?;
821            db.apply_batch(merkleized).await?;
822            db.commit().await?;
823            Ok(())
824        })
825    }
826
827    /// Apply random operations to the given db, committing them (randomly and at the end) only if
828    /// `commit_changes` is true. Returns the db; callers should commit if needed.
829    #[boxed]
830    pub async fn apply_random_ops<F, C>(
831        num_elements: u64,
832        commit_changes: bool,
833        rng_seed: u64,
834        mut db: C,
835    ) -> Result<C, Error<F>>
836    where
837        F: merkle::Graftable,
838        C: DbAny<F>,
839        C::Key: TestKey,
840        <C as DbAny<F>>::Value: TestValue,
841    {
842        // Log the seed with high visibility to make failures reproducible.
843        warn!("rng_seed={}", rng_seed);
844        let mut rng = TestRng::new(rng_seed);
845
846        // First loop: all initial writes in one batch.
847        let writes: Vec<_> = (0u64..num_elements)
848            .map(|i| {
849                let k = TestKey::from_seed(i);
850                let v = TestValue::from_seed(rng.next_u64());
851                (k, Some(v))
852            })
853            .collect();
854        if commit_changes {
855            commit_writes(&mut db, writes).await?;
856        }
857
858        // Randomly update / delete them. We use a delete frequency that is 1/7th of the update
859        // frequency. Accumulate writes and commit periodically.
860        let mut pending: WriteVec<F, C> = Vec::new();
861        for _ in 0u64..num_elements * 10 {
862            let rand_key = TestKey::from_seed(rng.next_u64() % num_elements);
863            if rng.next_u32().is_multiple_of(7) {
864                pending.push((rand_key, None));
865                continue;
866            }
867            let v = TestValue::from_seed(rng.next_u64());
868            pending.push((rand_key, Some(v)));
869            if commit_changes && rng.next_u32().is_multiple_of(20) {
870                commit_writes(&mut db, pending.drain(..)).await?;
871            }
872        }
873        if commit_changes {
874            commit_writes(&mut db, pending).await?;
875        }
876        Ok(db)
877    }
878
879    /// Build a random database, close and reopen it, and return the auditor state.
880    #[boxed]
881    async fn build_random_close_reopen_round<M, C, F, Fut>(
882        mut context: Context,
883        mut open_db: F,
884    ) -> String
885    where
886        M: merkle::Graftable + 'static,
887        C: DbAny<M> + 'static,
888        C::Key: TestKey,
889        <C as DbAny<M>>::Value: TestValue,
890        F: FnMut(Context, String) -> Fut,
891        Fut: Future<Output = C>,
892    {
893        const ELEMENTS: u64 = 1000;
894
895        let partition = "build-random".to_string();
896        let rng_seed = context.next_u64();
897        let mut db: C = open_db(context.child("first"), partition.clone()).await;
898        db = apply_random_ops::<M, C>(ELEMENTS, true, rng_seed, db)
899            .await
900            .unwrap();
901        let merkleized = db.new_batch().merkleize(&db, None).await.unwrap();
902        db.apply_batch(merkleized).await.unwrap();
903        db.sync().await.unwrap();
904
905        // Drop and reopen the db
906        let root = db.root();
907        drop(db);
908        let db: C = open_db(context.child("second"), partition).await;
909
910        // Ensure the root matches
911        assert_eq!(db.root(), root);
912
913        db.destroy().await.unwrap();
914        context.auditor().state()
915    }
916
917    /// Run `test_build_random_close_reopen` against a database factory.
918    ///
919    /// The factory should return a database when given a context and partition name.
920    /// The factory will be called multiple times to test reopening.
921    pub async fn test_build_random_close_reopen<M, C, F, Fut>(context: Context, open_db: F)
922    where
923        M: merkle::Graftable + 'static,
924        C: DbAny<M> + 'static,
925        C::Key: TestKey,
926        <C as DbAny<M>>::Value: TestValue,
927        F: FnMut(Context, String) -> Fut + Clone,
928        Fut: Future<Output = C>,
929    {
930        // Run on the provided runner.
931        let state1 =
932            build_random_close_reopen_round::<M, C, F, Fut>(context, open_db.clone()).await;
933
934        // Run again on a fresh runner to verify determinism.
935        let executor = deterministic::Runner::default();
936        let state2 = executor
937            .start(|context| build_random_close_reopen_round::<M, C, F, Fut>(context, open_db));
938
939        assert_eq!(state1, state2);
940    }
941
942    /// Run `test_commit_after_sync_recovery` against a database factory.
943    pub async fn test_commit_after_sync_recovery<M, C, F, Fut>(context: Context, mut open_db: F)
944    where
945        M: merkle::Graftable + 'static,
946        C: DbAny<M> + 'static,
947        C::Key: TestKey,
948        <C as DbAny<M>>::Value: TestValue,
949        F: FnMut(Context, String) -> Fut + Clone,
950        Fut: Future<Output = C>,
951    {
952        let mut open_db_clone = open_db.clone();
953        let partition = "commit-after-sync".to_string();
954        let mut db: C = Box::pin(open_db_clone(context.child("first"), partition.clone())).await;
955        let key0 = <<C as DbAny<M>>::Key as TestKey>::from_seed(0);
956        let key1 = <<C as DbAny<M>>::Key as TestKey>::from_seed(1);
957        let value0 = <C as DbAny<M>>::Value::from_seed(100);
958        let value1 = <C as DbAny<M>>::Value::from_seed(200);
959
960        // Establish a synced baseline so metadata and journal recovery start from it.
961        commit_writes::<M, C>(&mut db, [(key0, Some(value0.clone()))])
962            .await
963            .unwrap();
964        db.sync().await.unwrap();
965
966        // Commit a later batch without syncing metadata; reopen must rebuild it from the log.
967        commit_writes::<M, C>(&mut db, [(key1, Some(value1.clone()))])
968            .await
969            .unwrap();
970        let committed_root = db.root();
971        let committed_size = db.size();
972        drop(db);
973
974        let db: C = Box::pin(open_db(context.child("second"), partition)).await;
975        assert_eq!(db.root(), committed_root);
976        assert_eq!(db.size(), committed_size);
977        assert_eq!(db.get(&key0).await.unwrap(), Some(value0));
978        assert_eq!(db.get(&key1).await.unwrap(), Some(value1));
979
980        db.destroy().await.unwrap();
981    }
982
983    /// Run `test_simulate_write_failures` against a database factory.
984    ///
985    /// This test builds a random database and simulates recovery from different types of
986    /// failure scenarios.
987    pub async fn test_simulate_write_failures<M, C, F, Fut>(mut context: Context, mut open_db: F)
988    where
989        M: merkle::Graftable + 'static,
990        C: DbAny<M> + 'static,
991        C::Key: TestKey,
992        <C as DbAny<M>>::Value: TestValue,
993        F: FnMut(Context, String) -> Fut + Clone,
994        Fut: Future<Output = C>,
995    {
996        const ELEMENTS: u64 = 1000;
997
998        let partition = "build-random-fail-commit".to_string();
999        let rng_seed = context.next_u64();
1000        let mut db: C = Box::pin(open_db(context.child("first"), partition.clone())).await;
1001        db = apply_random_ops::<M, C>(ELEMENTS, true, rng_seed, db)
1002            .await
1003            .unwrap();
1004        commit_writes(&mut db, []).await.unwrap();
1005        let committed_root = db.root();
1006        let committed_op_count = db.bounds().end;
1007        db.prune(db.sync_boundary()).await.unwrap();
1008
1009        // Perform more random operations without committing any of them.
1010        let db = apply_random_ops::<M, C>(ELEMENTS, false, rng_seed + 1, db)
1011            .await
1012            .unwrap();
1013
1014        // SCENARIO #1: Simulate a crash that happens before any writes. Upon reopening, the
1015        // state of the DB should be as of the last commit.
1016        drop(db);
1017        let db: C = Box::pin(open_db(
1018            context.child("scenario").with_attribute("index", 1),
1019            partition.clone(),
1020        ))
1021        .await;
1022        assert_eq!(db.root(), committed_root);
1023        assert_eq!(db.bounds().end, committed_op_count);
1024
1025        // Re-apply the exact same operations, this time committed.
1026        let db = apply_random_ops::<M, C>(ELEMENTS, true, rng_seed + 1, db)
1027            .await
1028            .unwrap();
1029
1030        // SCENARIO #2: Simulate a crash that happens after the any db has been committed, but
1031        // before sync/prune is called. We do this by dropping the db without calling
1032        // sync or prune.
1033        let committed_op_count = db.bounds().end;
1034        drop(db);
1035
1036        // We should be able to recover, so the root should differ from the previous commit, and
1037        // the op count should be greater than before.
1038        let db: C = Box::pin(open_db(
1039            context.child("scenario").with_attribute("index", 2),
1040            partition.clone(),
1041        ))
1042        .await;
1043        let scenario_2_root = db.root();
1044
1045        // To confirm the second committed hash is correct we'll re-build the DB in a new
1046        // partition, but without any failures. They should have the exact same state.
1047        let fresh_partition = "build-random-fail-commit-fresh".to_string();
1048        let mut db: C = Box::pin(open_db(context.child("fresh"), fresh_partition.clone())).await;
1049        db = apply_random_ops::<M, C>(ELEMENTS, true, rng_seed, db)
1050            .await
1051            .unwrap();
1052        commit_writes(&mut db, []).await.unwrap();
1053        db = apply_random_ops::<M, C>(ELEMENTS, true, rng_seed + 1, db)
1054            .await
1055            .unwrap();
1056        db.prune(db.sync_boundary()).await.unwrap();
1057        // State from scenario #2 should match that of a successful commit.
1058        assert_eq!(db.bounds().end, committed_op_count);
1059        assert_eq!(db.root(), scenario_2_root);
1060
1061        db.destroy().await.unwrap();
1062    }
1063
1064    /// Run `test_different_pruning_delays_same_root` against a database factory.
1065    ///
1066    /// This test verifies that pruning operations do not affect the root hash - two databases
1067    /// with identical operations but different pruning schedules should have the same root.
1068    pub async fn test_different_pruning_delays_same_root<M, C, F, Fut>(
1069        context: Context,
1070        mut open_db: F,
1071    ) where
1072        M: merkle::Graftable,
1073        C: DbAny<M>,
1074        C::Key: TestKey,
1075        <C as DbAny<M>>::Value: TestValue,
1076        F: FnMut(Context, String) -> Fut + Clone,
1077        Fut: Future<Output = C>,
1078    {
1079        const NUM_OPERATIONS: u64 = 1000;
1080
1081        let mut open_db_clone = open_db.clone();
1082        // Create two databases that are identical other than how they are pruned.
1083        let mut db_no_pruning: C = Box::pin(open_db_clone(
1084            context.child("no_pruning"),
1085            "no-pruning-test".into(),
1086        ))
1087        .await;
1088        let mut db_pruning: C =
1089            Box::pin(open_db(context.child("pruning"), "pruning-test".into())).await;
1090
1091        // Apply identical operations to both databases, but only prune one.
1092        // Accumulate writes between commits.
1093        let mut pending_no_pruning: WriteVec<M, C> = Vec::new();
1094        let mut pending_pruning: WriteVec<M, C> = Vec::new();
1095        for i in 0..NUM_OPERATIONS {
1096            let key: C::Key = TestKey::from_seed(i);
1097            let value: <C as DbAny<M>>::Value = TestValue::from_seed(i * 1000);
1098
1099            pending_no_pruning.push((key, Some(value.clone())));
1100            pending_pruning.push((key, Some(value)));
1101
1102            // Commit periodically
1103            if i % 50 == 49 {
1104                commit_writes(&mut db_no_pruning, pending_no_pruning.drain(..))
1105                    .await
1106                    .unwrap();
1107                commit_writes(&mut db_pruning, pending_pruning.drain(..))
1108                    .await
1109                    .unwrap();
1110                db_pruning
1111                    .prune(db_no_pruning.sync_boundary())
1112                    .await
1113                    .unwrap();
1114            }
1115        }
1116
1117        // Final commit for remaining writes.
1118        commit_writes(&mut db_no_pruning, pending_no_pruning)
1119            .await
1120            .unwrap();
1121        commit_writes(&mut db_pruning, pending_pruning)
1122            .await
1123            .unwrap();
1124
1125        // Get roots from both databases - they should match
1126        let root_no_pruning = db_no_pruning.root();
1127        let root_pruning = db_pruning.root();
1128        assert_eq!(root_no_pruning, root_pruning);
1129
1130        // Also verify inactivity floors match
1131        assert_eq!(
1132            db_no_pruning.inactivity_floor_loc(),
1133            db_pruning.inactivity_floor_loc()
1134        );
1135
1136        db_no_pruning.destroy().await.unwrap();
1137        db_pruning.destroy().await.unwrap();
1138    }
1139
1140    /// Run `test_sync_persists_bitmap_pruning_boundary` against a database factory.
1141    ///
1142    /// This test verifies that calling `sync()` persists the bitmap pruning boundary that was
1143    /// set during `commit()`. If `sync()` didn't call `write_pruned`, the
1144    /// `pruned_bits()` count would be 0 after reopen instead of the expected value.
1145    pub async fn test_sync_persists_bitmap_pruning_boundary<M, C, F, Fut>(
1146        mut context: Context,
1147        mut open_db: F,
1148    ) where
1149        M: merkle::Graftable + 'static,
1150        C: DbAny<M> + BitmapPrunedBits + 'static,
1151        C::Key: TestKey,
1152        <C as DbAny<M>>::Value: TestValue,
1153        F: FnMut(Context, String) -> Fut + Clone,
1154        Fut: Future<Output = C>,
1155    {
1156        const ELEMENTS: u64 = 500;
1157
1158        let mut open_db_clone = open_db.clone();
1159        let partition = "sync-bitmap-pruning".to_string();
1160        let rng_seed = context.next_u64();
1161        let mut db: C = Box::pin(open_db_clone(context.child("first"), partition.clone())).await;
1162
1163        // Apply random operations with commits to advance the inactivity floor.
1164        db = apply_random_ops::<M, C>(ELEMENTS, true, rng_seed, db)
1165            .await
1166            .unwrap();
1167        let merkleized = db.new_batch().merkleize(&db, None).await.unwrap();
1168        db.apply_batch(merkleized).await.unwrap();
1169
1170        // Prune to flatten bitmap layers and advance pruned_chunks.
1171        db.prune(db.sync_boundary()).await.unwrap();
1172
1173        let pruned_bits_before = db.pruned_bits();
1174        warn!(
1175            "pruned_bits_before={}, inactivity_floor={}, op_count={}",
1176            pruned_bits_before,
1177            *db.inactivity_floor_loc(),
1178            *db.bounds().end
1179        );
1180
1181        // Verify we actually have some pruning (otherwise the test is meaningless).
1182        assert!(
1183            pruned_bits_before > 0,
1184            "Expected bitmap to have pruned bits after prune()"
1185        );
1186
1187        // Call sync() to persist the bitmap pruning boundary.
1188        db.sync().await.unwrap();
1189
1190        // Record the root before dropping.
1191        let root_before = db.root();
1192        drop(db);
1193
1194        // Reopen the database.
1195        let db: C = Box::pin(open_db(context.child("second"), partition)).await;
1196
1197        // The pruned bits count should match. If sync() didn't persist the bitmap pruned
1198        // state, this would be 0.
1199        let pruned_bits_after = db.pruned_bits();
1200        warn!("pruned_bits_after={}", pruned_bits_after);
1201
1202        assert_eq!(
1203            pruned_bits_after, pruned_bits_before,
1204            "Bitmap pruned bits mismatch after reopen - sync() may not have called write_pruned()"
1205        );
1206
1207        // Also verify the root matches.
1208        assert_eq!(db.root(), root_before);
1209
1210        db.destroy().await.unwrap();
1211    }
1212
1213    /// Run `test_current_db_build_big` against a database factory.
1214    ///
1215    /// This test builds a database with 1000 keys, updates some, deletes some, and verifies that
1216    /// the final state matches an independently computed HashMap. It also verifies that the state
1217    /// persists correctly after close and reopen.
1218    pub async fn test_current_db_build_big<M, C, F, Fut>(context: Context, mut open_db: F)
1219    where
1220        M: merkle::Graftable,
1221        C: DbAny<M>,
1222        C::Key: TestKey,
1223        <C as DbAny<M>>::Value: TestValue,
1224        F: FnMut(Context, String) -> Fut + Clone,
1225        Fut: Future<Output = C>,
1226    {
1227        const ELEMENTS: u64 = 1000;
1228
1229        let mut open_db_clone = open_db.clone();
1230        let mut db: C = Box::pin(open_db_clone(context.child("first"), "build-big".into())).await;
1231
1232        let mut map = std::collections::HashMap::<C::Key, <C as DbAny<M>>::Value>::default();
1233
1234        // All creates, updates, and deletes in one batch.
1235        let mut batch = db.new_batch();
1236
1237        // Initial creates
1238        for i in 0u64..ELEMENTS {
1239            let k: C::Key = TestKey::from_seed(i);
1240            let v: <C as DbAny<M>>::Value = TestValue::from_seed(i * 1000);
1241            batch = batch.write(k, Some(v.clone()));
1242            map.insert(k, v);
1243        }
1244
1245        // Update every 3rd key
1246        for i in 0u64..ELEMENTS {
1247            if i % 3 != 0 {
1248                continue;
1249            }
1250            let k: C::Key = TestKey::from_seed(i);
1251            let v: <C as DbAny<M>>::Value = TestValue::from_seed((i + 1) * 10000);
1252            batch = batch.write(k, Some(v.clone()));
1253            map.insert(k, v);
1254        }
1255
1256        // Delete every 7th key
1257        for i in 0u64..ELEMENTS {
1258            if i % 7 != 1 {
1259                continue;
1260            }
1261            let k: C::Key = TestKey::from_seed(i);
1262            batch = batch.write(k, None);
1263            map.remove(&k);
1264        }
1265
1266        let merkleized = batch.merkleize(&db, None).await.unwrap();
1267        db.apply_batch(merkleized).await.unwrap();
1268
1269        // Sync and prune.
1270        db.sync().await.unwrap();
1271        db.prune(db.sync_boundary()).await.unwrap();
1272
1273        // Record root before dropping.
1274        let root = db.root();
1275        db.sync().await.unwrap();
1276        drop(db);
1277
1278        // Reopen the db and verify it has exactly the same state.
1279        let db: C = Box::pin(open_db(context.child("second"), "build-big".into())).await;
1280        assert_eq!(root, db.root());
1281
1282        // Confirm the db's state matches that of the separate map we computed independently.
1283        for i in 0u64..ELEMENTS {
1284            let k: C::Key = TestKey::from_seed(i);
1285            if let Some(map_value) = map.get(&k) {
1286                let Some(db_value) = db.get(&k).await.unwrap() else {
1287                    panic!("key not found in db: {k}");
1288                };
1289                assert_eq!(*map_value, db_value);
1290            } else {
1291                assert!(db.get(&k).await.unwrap().is_none());
1292            }
1293        }
1294    }
1295
1296    /// Run `test_stale_batch_side_effect_free` against a database factory.
1297    ///
1298    /// The stale batch must be rejected without mutating the committed state.
1299    pub async fn test_stale_batch_side_effect_free<M, C, F, Fut>(context: Context, mut open_db: F)
1300    where
1301        M: merkle::Graftable,
1302        C: DbAny<M>,
1303        C::Key: TestKey,
1304        <C as DbAny<M>>::Value: TestValue,
1305        F: FnMut(Context, String) -> Fut,
1306        Fut: Future<Output = C>,
1307    {
1308        let mut db: C = Box::pin(open_db(
1309            context.child("db"),
1310            "stale-side-effect-free".into(),
1311        ))
1312        .await;
1313
1314        let key1 = <C::Key as TestKey>::from_seed(1);
1315        let key2 = <C::Key as TestKey>::from_seed(2);
1316        let value1 = <<C as DbAny<M>>::Value as TestValue>::from_seed(10);
1317        let value2 = <<C as DbAny<M>>::Value as TestValue>::from_seed(20);
1318
1319        let mut batch = db.new_batch();
1320        batch = batch.write(key1, Some(value1.clone()));
1321        let batch_a = batch.merkleize(&db, None).await.unwrap();
1322        let mut batch = db.new_batch();
1323        batch = batch.write(key2, Some(value2));
1324        let batch_b = batch.merkleize(&db, None).await.unwrap();
1325
1326        db.apply_batch(batch_a).await.unwrap();
1327        let expected_root = db.root();
1328        let expected_bounds = db.bounds();
1329        let expected_metadata = db.get_metadata().await.unwrap();
1330        assert_eq!(db.get(&key1).await.unwrap(), Some(value1.clone()));
1331        assert_eq!(db.get(&key2).await.unwrap(), None);
1332
1333        let result = db.apply_batch(batch_b).await;
1334        assert!(
1335            matches!(result, Err(Error::StaleBatch { .. })),
1336            "expected StaleBatch error, got {result:?}"
1337        );
1338        assert_eq!(db.root(), expected_root);
1339        assert_eq!(db.bounds(), expected_bounds);
1340        assert_eq!(db.get_metadata().await.unwrap(), expected_metadata);
1341        assert_eq!(db.get(&key1).await.unwrap(), Some(value1));
1342        assert_eq!(db.get(&key2).await.unwrap(), None);
1343
1344        db.destroy().await.unwrap();
1345    }
1346
1347    use crate::translator::OneCap;
1348    use commonware_cryptography::{sha256::Digest, Hasher as _, Sha256};
1349    use commonware_macros::{boxed, test_group, test_traced};
1350
1351    type OrderedFixedDb =
1352        ordered::fixed::Db<mmr::Family, Context, Digest, Digest, Sha256, OneCap, 32, Sequential>;
1353    type OrderedVariableDb =
1354        ordered::variable::Db<mmr::Family, Context, Digest, Digest, Sha256, OneCap, 32, Sequential>;
1355    type UnorderedFixedDb =
1356        unordered::fixed::Db<mmr::Family, Context, Digest, Digest, Sha256, OneCap, 32, Sequential>;
1357    type UnorderedVariableDb = unordered::variable::Db<
1358        mmr::Family,
1359        Context,
1360        Digest,
1361        Digest,
1362        Sha256,
1363        OneCap,
1364        32,
1365        Sequential,
1366    >;
1367    type OrderedFixedP1Db = ordered::fixed::partitioned::Db<
1368        mmr::Family,
1369        Context,
1370        Digest,
1371        Digest,
1372        Sha256,
1373        OneCap,
1374        1,
1375        32,
1376        Sequential,
1377    >;
1378    type OrderedVariableP1Db = ordered::variable::partitioned::Db<
1379        mmr::Family,
1380        Context,
1381        Digest,
1382        Digest,
1383        Sha256,
1384        OneCap,
1385        1,
1386        32,
1387        Sequential,
1388    >;
1389    type UnorderedFixedP1Db = unordered::fixed::partitioned::Db<
1390        mmr::Family,
1391        Context,
1392        Digest,
1393        Digest,
1394        Sha256,
1395        OneCap,
1396        1,
1397        32,
1398        Sequential,
1399    >;
1400    type UnorderedVariableP1Db = unordered::variable::partitioned::Db<
1401        mmr::Family,
1402        Context,
1403        Digest,
1404        Digest,
1405        Sha256,
1406        OneCap,
1407        1,
1408        32,
1409        Sequential,
1410    >;
1411    type OrderedFixedP2Db = ordered::fixed::partitioned::Db<
1412        mmr::Family,
1413        Context,
1414        Digest,
1415        Digest,
1416        Sha256,
1417        OneCap,
1418        2,
1419        32,
1420        Sequential,
1421    >;
1422    type OrderedVariableP2Db = ordered::variable::partitioned::Db<
1423        mmr::Family,
1424        Context,
1425        Digest,
1426        Digest,
1427        Sha256,
1428        OneCap,
1429        2,
1430        32,
1431        Sequential,
1432    >;
1433    type UnorderedFixedP2Db = unordered::fixed::partitioned::Db<
1434        mmr::Family,
1435        Context,
1436        Digest,
1437        Digest,
1438        Sha256,
1439        OneCap,
1440        2,
1441        32,
1442        Sequential,
1443    >;
1444    type UnorderedVariableP2Db = unordered::variable::partitioned::Db<
1445        mmr::Family,
1446        Context,
1447        Digest,
1448        Digest,
1449        Sha256,
1450        OneCap,
1451        2,
1452        32,
1453        Sequential,
1454    >;
1455
1456    type OrderedFixedMmbDb =
1457        ordered::fixed::Db<mmb::Family, Context, Digest, Digest, Sha256, OneCap, 32, Sequential>;
1458    type OrderedVariableMmbDb =
1459        ordered::variable::Db<mmb::Family, Context, Digest, Digest, Sha256, OneCap, 32, Sequential>;
1460    type UnorderedFixedMmbDb =
1461        unordered::fixed::Db<mmb::Family, Context, Digest, Digest, Sha256, OneCap, 32, Sequential>;
1462    type UnorderedVariableMmbDb = unordered::variable::Db<
1463        mmb::Family,
1464        Context,
1465        Digest,
1466        Digest,
1467        Sha256,
1468        OneCap,
1469        32,
1470        Sequential,
1471    >;
1472    type OrderedFixedMmbP1Db = ordered::fixed::partitioned::Db<
1473        mmb::Family,
1474        Context,
1475        Digest,
1476        Digest,
1477        Sha256,
1478        OneCap,
1479        1,
1480        32,
1481        Sequential,
1482    >;
1483    type OrderedVariableMmbP1Db = ordered::variable::partitioned::Db<
1484        mmb::Family,
1485        Context,
1486        Digest,
1487        Digest,
1488        Sha256,
1489        OneCap,
1490        1,
1491        32,
1492        Sequential,
1493    >;
1494    type UnorderedFixedMmbP1Db = unordered::fixed::partitioned::Db<
1495        mmb::Family,
1496        Context,
1497        Digest,
1498        Digest,
1499        Sha256,
1500        OneCap,
1501        1,
1502        32,
1503        Sequential,
1504    >;
1505    type UnorderedVariableMmbP1Db = unordered::variable::partitioned::Db<
1506        mmb::Family,
1507        Context,
1508        Digest,
1509        Digest,
1510        Sha256,
1511        OneCap,
1512        1,
1513        32,
1514        Sequential,
1515    >;
1516    type OrderedFixedMmbP2Db = ordered::fixed::partitioned::Db<
1517        mmb::Family,
1518        Context,
1519        Digest,
1520        Digest,
1521        Sha256,
1522        OneCap,
1523        2,
1524        32,
1525        Sequential,
1526    >;
1527    type OrderedVariableMmbP2Db = ordered::variable::partitioned::Db<
1528        mmb::Family,
1529        Context,
1530        Digest,
1531        Digest,
1532        Sha256,
1533        OneCap,
1534        2,
1535        32,
1536        Sequential,
1537    >;
1538    type UnorderedFixedMmbP2Db = unordered::fixed::partitioned::Db<
1539        mmb::Family,
1540        Context,
1541        Digest,
1542        Digest,
1543        Sha256,
1544        OneCap,
1545        2,
1546        32,
1547        Sequential,
1548    >;
1549    type UnorderedVariableMmbP2Db = unordered::variable::partitioned::Db<
1550        mmb::Family,
1551        Context,
1552        Digest,
1553        Digest,
1554        Sha256,
1555        OneCap,
1556        2,
1557        32,
1558        Sequential,
1559    >;
1560
1561    // Regression test for a forged exclusion proof against the ordered partitioned index with
1562    // variable-length keys shorter than the partition prefix. The buggy router zero-padded a short
1563    // key into the lowest partition, so its `next_key` wrapped around the whole keyspace and the
1564    // resulting authenticated span covered a live key, letting a prover forge that key's exclusion.
1565    // Order-preserving routing keeps the key in lexicographic position, so the span no longer covers
1566    // it.
1567    #[test_traced]
1568    fn test_partitioned_ordered_short_key_exclusion_not_forgeable() {
1569        type ForgedExclusionDb = ordered::variable::partitioned::Db<
1570            mmr::Family,
1571            Context,
1572            Vec<u8>,
1573            Vec<u8>,
1574            Sha256,
1575            OneCap,
1576            2,
1577            32,
1578            Sequential,
1579        >;
1580
1581        let executor = deterministic::Runner::default();
1582        executor.start(|context| async move {
1583            let page_cache = CacheRef::from_pooler(&context, PAGE_SIZE, PAGE_CACHE_SIZE);
1584            let cfg = VariableConfig {
1585                merkle_config: MerkleConfig {
1586                    journal_partition: "forged-exclusion-journal".to_string(),
1587                    metadata_partition: "forged-exclusion-metadata".to_string(),
1588                    items_per_blob: NZU64!(11),
1589                    write_buffer: NZUsize!(1024),
1590                    strategy: Sequential,
1591                    page_cache: page_cache.clone(),
1592                },
1593                journal_config: VConfig {
1594                    partition: "forged-exclusion-log".to_string(),
1595                    items_per_section: NZU64!(7),
1596                    compression: None,
1597                    codec_config: (((0..=8).into(), ()), ((0..=8).into(), ())),
1598                    page_cache,
1599                    write_buffer: NZUsize!(1024),
1600                },
1601                grafted_metadata_partition: "forged-exclusion-grafted".to_string(),
1602                translator: OneCap,
1603                init_cache_size: Some(NZUsize!(1024)),
1604            };
1605            let mut db = ForgedExclusionDb::init(context.child("db"), cfg)
1606                .await
1607                .unwrap();
1608
1609            // `b` is shorter than the 2-byte prefix and lexicographically falls between `a` and `c`
1610            // (`b` is a proper prefix of `c`). The bug routed `b` to partition 0x0001 instead of
1611            // 0x0100, below `a` at 0x0080.
1612            let a = vec![0x00u8, 0x80];
1613            let b = vec![0x01u8];
1614            let c = vec![0x01u8, 0x00];
1615            let (va, vb, vc) = (vec![0x0au8], vec![0x0bu8], vec![0x0cu8]);
1616
1617            // Commit `a` and `b` (ring a -> b -> a), then `c` in a later batch.
1618            let merkleized = db
1619                .new_batch()
1620                .write(a.clone(), Some(va.clone()))
1621                .write(b.clone(), Some(vb.clone()))
1622                .merkleize(&db, None)
1623                .await
1624                .unwrap();
1625            db.apply_batch(merkleized).await.unwrap();
1626            let merkleized = db
1627                .new_batch()
1628                .write(c.clone(), Some(vc.clone()))
1629                .merkleize(&db, None)
1630                .await
1631                .unwrap();
1632            db.apply_batch(merkleized).await.unwrap();
1633            let root = db.root();
1634
1635            // All three keys are live.
1636            assert_eq!(db.get(&a).await.unwrap(), Some(va));
1637            assert_eq!(db.get(&b).await.unwrap(), Some(vb));
1638            assert_eq!(db.get(&c).await.unwrap(), Some(vc));
1639
1640            // Root cause: the ring must link keys in lexicographic order, so `b`'s successor is `c`
1641            // (pre-fix it wrapped around to `a`).
1642            let (_, span_b) = db.get_span(&b).await.unwrap().unwrap();
1643            assert_eq!(
1644                span_b.next_key, c,
1645                "b.next_key must be c, not the wrapped-around a"
1646            );
1647
1648            // Honest exclusion proving refuses a live key.
1649            assert!(matches!(
1650                db.exclusion_proof(&c).await,
1651                Err(Error::KeyExists)
1652            ));
1653
1654            // Forge attempt: package `b`'s authenticated update as an exclusion proof for `c`. The
1655            // span [b, c) does not cover `c`, so the verifier rejects it (pre-fix the span was
1656            // [b, a), which cyclically covered `c` and verified).
1657            let kvp = db.key_value_proof(b.clone()).await.unwrap();
1658            let forged = ordered::ExclusionProof::KeyValue(kvp.proof, span_b);
1659            assert!(!ForgedExclusionDb::verify_exclusion_proof(
1660                &c, &forged, &root
1661            ));
1662
1663            db.destroy().await.unwrap();
1664        });
1665    }
1666
1667    // Helper macro to create an open_db closure for a specific variant.
1668    macro_rules! open_db_fn {
1669        ($db:ty, $cfg:ident) => {
1670            |ctx: Context, partition: String| async move {
1671                <$db>::init(ctx.child("storage"), $cfg::<OneCap>(&partition, &ctx))
1672                    .await
1673                    .unwrap()
1674            }
1675        };
1676    }
1677
1678    // Defines all variants across both supported Merkle families.
1679    macro_rules! with_all_variants {
1680        ($cb:ident!($($args:tt)*)) => {
1681            $cb!($($args)*, of, OrderedFixedDb, fixed_config);
1682            $cb!($($args)*, ov, OrderedVariableDb, variable_config);
1683            $cb!($($args)*, uf, UnorderedFixedDb, fixed_config);
1684            $cb!($($args)*, uv, UnorderedVariableDb, variable_config);
1685            $cb!($($args)*, ofp1, OrderedFixedP1Db, fixed_config);
1686            $cb!($($args)*, ovp1, OrderedVariableP1Db, variable_config);
1687            $cb!($($args)*, ufp1, UnorderedFixedP1Db, fixed_config);
1688            $cb!($($args)*, uvp1, UnorderedVariableP1Db, variable_config);
1689            $cb!($($args)*, ofp2, OrderedFixedP2Db, fixed_config);
1690            $cb!($($args)*, ovp2, OrderedVariableP2Db, variable_config);
1691            $cb!($($args)*, ufp2, UnorderedFixedP2Db, fixed_config);
1692            $cb!($($args)*, uvp2, UnorderedVariableP2Db, variable_config);
1693            $cb!($($args)*, of_mmb, OrderedFixedMmbDb, fixed_config);
1694            $cb!($($args)*, ov_mmb, OrderedVariableMmbDb, variable_config);
1695            $cb!($($args)*, uf_mmb, UnorderedFixedMmbDb, fixed_config);
1696            $cb!($($args)*, uv_mmb, UnorderedVariableMmbDb, variable_config);
1697            $cb!($($args)*, ofp1_mmb, OrderedFixedMmbP1Db, fixed_config);
1698            $cb!($($args)*, ovp1_mmb, OrderedVariableMmbP1Db, variable_config);
1699            $cb!($($args)*, ufp1_mmb, UnorderedFixedMmbP1Db, fixed_config);
1700            $cb!($($args)*, uvp1_mmb, UnorderedVariableMmbP1Db, variable_config);
1701            $cb!($($args)*, ofp2_mmb, OrderedFixedMmbP2Db, fixed_config);
1702            $cb!($($args)*, ovp2_mmb, OrderedVariableMmbP2Db, variable_config);
1703            $cb!($($args)*, ufp2_mmb, UnorderedFixedMmbP2Db, fixed_config);
1704            $cb!($($args)*, uvp2_mmb, UnorderedVariableMmbP2Db, variable_config);
1705        };
1706    }
1707
1708    // Defines 6 ordered variants.
1709    macro_rules! with_ordered_variants {
1710        ($cb:ident!($($args:tt)*)) => {
1711            $cb!($($args)*, of, OrderedFixedDb, fixed_config);
1712            $cb!($($args)*, ov, OrderedVariableDb, variable_config);
1713            $cb!($($args)*, ofp1, OrderedFixedP1Db, fixed_config);
1714            $cb!($($args)*, ovp1, OrderedVariableP1Db, variable_config);
1715            $cb!($($args)*, ofp2, OrderedFixedP2Db, fixed_config);
1716            $cb!($($args)*, ovp2, OrderedVariableP2Db, variable_config);
1717            $cb!($($args)*, of_mmb, OrderedFixedMmbDb, fixed_config);
1718            $cb!($($args)*, ov_mmb, OrderedVariableMmbDb, variable_config);
1719            $cb!($($args)*, ofp1_mmb, OrderedFixedMmbP1Db, fixed_config);
1720            $cb!($($args)*, ovp1_mmb, OrderedVariableMmbP1Db, variable_config);
1721            $cb!($($args)*, ofp2_mmb, OrderedFixedMmbP2Db, fixed_config);
1722            $cb!($($args)*, ovp2_mmb, OrderedVariableMmbP2Db, variable_config);
1723        };
1724    }
1725
1726    // Defines 6 unordered variants.
1727    macro_rules! with_unordered_variants {
1728        ($cb:ident!($($args:tt)*)) => {
1729            $cb!($($args)*, uf, UnorderedFixedDb, fixed_config);
1730            $cb!($($args)*, uv, UnorderedVariableDb, variable_config);
1731            $cb!($($args)*, ufp1, UnorderedFixedP1Db, fixed_config);
1732            $cb!($($args)*, uvp1, UnorderedVariableP1Db, variable_config);
1733            $cb!($($args)*, ufp2, UnorderedFixedP2Db, fixed_config);
1734            $cb!($($args)*, uvp2, UnorderedVariableP2Db, variable_config);
1735            $cb!($($args)*, uf_mmb, UnorderedFixedMmbDb, fixed_config);
1736            $cb!($($args)*, uv_mmb, UnorderedVariableMmbDb, variable_config);
1737            $cb!($($args)*, ufp1_mmb, UnorderedFixedMmbP1Db, fixed_config);
1738            $cb!($($args)*, uvp1_mmb, UnorderedVariableMmbP1Db, variable_config);
1739            $cb!($($args)*, ufp2_mmb, UnorderedFixedMmbP2Db, fixed_config);
1740            $cb!($($args)*, uvp2_mmb, UnorderedVariableMmbP2Db, variable_config);
1741        };
1742    }
1743
1744    // Emit one `#[test_group("slow")] #[test_traced]` test per variant.
1745    // The fn name is `<f>_<variant_label>`, the body runs the async test
1746    // function `f` on a fresh runner, passing it the `Context` and a DB opener
1747    // for the variant.
1748    macro_rules! test_for_variant {
1749        ($f:ident, $traced:literal, $label:ident, $db:ty, $cfg:ident) => {
1750            paste::paste! {
1751                #[test_group("slow")]
1752                #[test_traced($traced)]
1753                fn [<$f _ $label>]() {
1754                    let executor = deterministic::Runner::default();
1755                    executor.start(|context| async move {
1756                        // Box the future to prevent stack overflow when
1757                        // monomorphized across DB variants.
1758                        Box::pin($f(context, open_db_fn!($db, $cfg))).await
1759                    });
1760                }
1761            }
1762        };
1763    }
1764
1765    // Generate one slow test per variant across all 24 variants.
1766    macro_rules! test_for_all_variants {
1767        ($f:ident, $traced:literal) => {
1768            with_all_variants!(test_for_variant!($f, $traced));
1769        };
1770    }
1771
1772    // Generate one slow test per variant across the 12 ordered variants.
1773    macro_rules! test_for_ordered_variants {
1774        ($f:ident, $traced:literal) => {
1775            with_ordered_variants!(test_for_variant!($f, $traced));
1776        };
1777    }
1778
1779    // Generate one slow test per variant across the 12 unordered variants.
1780    macro_rules! test_for_unordered_variants {
1781        ($f:ident, $traced:literal) => {
1782            with_unordered_variants!(test_for_variant!($f, $traced));
1783        };
1784    }
1785
1786    // Wrapper functions for build_big tests with ordered/unordered expected values.
1787    async fn test_ordered_build_big<M, C, F, Fut>(context: Context, open_db: F)
1788    where
1789        M: merkle::Graftable,
1790        C: DbAny<M>,
1791        C::Key: TestKey,
1792        <C as DbAny<M>>::Value: TestValue,
1793        F: FnMut(Context, String) -> Fut + Clone,
1794        Fut: Future<Output = C>,
1795    {
1796        test_current_db_build_big::<M, C, F, Fut>(context, open_db).await;
1797    }
1798
1799    async fn test_unordered_build_big<M, C, F, Fut>(context: Context, open_db: F)
1800    where
1801        M: merkle::Graftable,
1802        C: DbAny<M>,
1803        C::Key: TestKey,
1804        <C as DbAny<M>>::Value: TestValue,
1805        F: FnMut(Context, String) -> Fut + Clone,
1806        Fut: Future<Output = C>,
1807    {
1808        test_current_db_build_big::<M, C, F, Fut>(context, open_db).await;
1809    }
1810
1811    test_for_all_variants!(test_build_random_close_reopen, "WARN");
1812    test_for_all_variants!(test_simulate_write_failures, "WARN");
1813    test_for_all_variants!(test_different_pruning_delays_same_root, "WARN");
1814    test_for_all_variants!(test_sync_persists_bitmap_pruning_boundary, "WARN");
1815    test_for_all_variants!(test_commit_after_sync_recovery, "WARN");
1816    test_for_all_variants!(test_stale_batch_side_effect_free, "WARN");
1817
1818    test_for_ordered_variants!(test_ordered_build_big, "WARN");
1819    test_for_ordered_variants!(test_ordered_build_small_close_reopen, "DEBUG");
1820
1821    test_for_unordered_variants!(test_unordered_build_big, "WARN");
1822    test_for_unordered_variants!(test_unordered_build_small_close_reopen, "DEBUG");
1823
1824    // ---- Current-level batch API tests ----
1825    //
1826    // These exercise the current wrapper's batch methods (root, ops_root,
1827    // MerkleizedBatch::get, batch chaining) which layer bitmap and grafted tree
1828    // computation on top of the `any` batch.
1829
1830    fn key(i: u64) -> Digest {
1831        Sha256::hash(&i.to_be_bytes())
1832    }
1833
1834    fn val(i: u64) -> Digest {
1835        Sha256::hash(&(i + 10000).to_be_bytes())
1836    }
1837
1838    async fn mmb_commit(
1839        db: &mut UnorderedVariableMmbDb,
1840        writes: impl IntoIterator<Item = (Digest, Option<Digest>)>,
1841    ) {
1842        let mut batch = db.new_batch();
1843        for (k, v) in writes {
1844            batch = batch.write(k, v);
1845        }
1846        let merkleized = batch.merkleize(db, None).await.unwrap();
1847        db.apply_batch(merkleized).await.unwrap();
1848        db.commit().await.unwrap();
1849    }
1850
1851    async fn commit_writes_with_metadata(
1852        db: &mut UnorderedVariableDb,
1853        writes: impl IntoIterator<Item = (Digest, Option<Digest>)>,
1854        metadata: Option<Digest>,
1855    ) -> std::ops::Range<Location<mmr::Family>> {
1856        let mut batch = db.new_batch();
1857        for (k, v) in writes {
1858            batch = batch.write(k, v);
1859        }
1860        let merkleized = batch.merkleize(db, metadata).await.unwrap();
1861        let range = db.apply_batch(merkleized).await.unwrap();
1862        db.commit().await.unwrap();
1863        range
1864    }
1865
1866    #[test_traced("INFO")]
1867    fn test_current_rewind_recovery() {
1868        let executor = deterministic::Runner::default();
1869        executor.start(|context| async move {
1870            let partition = "current-rewind-recovery";
1871            let ctx = context.child("db");
1872            let mut db: UnorderedVariableDb = UnorderedVariableDb::init(
1873                ctx.child("storage"),
1874                variable_config::<OneCap>(partition, &ctx),
1875            )
1876            .await
1877            .unwrap();
1878            let initial_size = db.bounds().end;
1879            let initial_root = db.root();
1880            let initial_ops_root = db.ops_root();
1881            let initial_floor = db.inactivity_floor_loc();
1882
1883            let metadata_a = val(900);
1884            let first_range = commit_writes_with_metadata(
1885                &mut db,
1886                [(key(0), Some(val(0))), (key(1), Some(val(1)))],
1887                Some(metadata_a),
1888            )
1889            .await;
1890            assert_eq!(first_range.start, initial_size);
1891            let size_before = db.bounds().end;
1892            let root_before = db.root();
1893            let ops_root_before = db.ops_root();
1894            let floor_before = db.inactivity_floor_loc();
1895            assert_eq!(size_before, first_range.end);
1896
1897            let metadata_b = val(901);
1898            let second_range = commit_writes_with_metadata(
1899                &mut db,
1900                [
1901                    (key(0), Some(val(100))),
1902                    (key(1), None),
1903                    (key(2), Some(val(2))),
1904                ],
1905                Some(metadata_b),
1906            )
1907            .await;
1908            assert_eq!(second_range.start, size_before);
1909            assert_ne!(db.root(), root_before);
1910            assert_eq!(db.get_metadata().await.unwrap(), Some(val(901)));
1911            assert_eq!(db.get(&key(0)).await.unwrap(), Some(val(100)));
1912            assert_eq!(db.get(&key(1)).await.unwrap(), None);
1913            assert_eq!(db.get(&key(2)).await.unwrap(), Some(val(2)));
1914
1915            db.rewind(size_before).await.unwrap();
1916            assert_eq!(db.bounds().end, size_before);
1917            assert_eq!(db.root(), root_before);
1918            assert_eq!(db.ops_root(), ops_root_before);
1919            assert_eq!(db.inactivity_floor_loc(), floor_before);
1920            assert_eq!(db.get_metadata().await.unwrap(), Some(metadata_a));
1921            assert_eq!(db.get(&key(0)).await.unwrap(), Some(val(0)));
1922            assert_eq!(db.get(&key(1)).await.unwrap(), Some(val(1)));
1923            assert_eq!(db.get(&key(2)).await.unwrap(), None);
1924
1925            db.commit().await.unwrap();
1926            drop(db);
1927
1928            let reopened: UnorderedVariableDb = UnorderedVariableDb::init(
1929                context.child("reopen"),
1930                variable_config::<OneCap>(partition, &context),
1931            )
1932            .await
1933            .unwrap();
1934            assert_eq!(reopened.bounds().end, size_before);
1935            assert_eq!(reopened.root(), root_before);
1936            assert_eq!(reopened.ops_root(), ops_root_before);
1937            assert_eq!(reopened.inactivity_floor_loc(), floor_before);
1938            assert_eq!(reopened.get_metadata().await.unwrap(), Some(val(900)));
1939            assert_eq!(reopened.get(&key(0)).await.unwrap(), Some(val(0)));
1940            assert_eq!(reopened.get(&key(1)).await.unwrap(), Some(val(1)));
1941            assert_eq!(reopened.get(&key(2)).await.unwrap(), None);
1942
1943            let mut reopened = reopened;
1944            reopened.rewind(initial_size).await.unwrap();
1945            assert_eq!(reopened.bounds().end, initial_size);
1946            assert_eq!(reopened.root(), initial_root);
1947            assert_eq!(reopened.ops_root(), initial_ops_root);
1948            assert_eq!(reopened.inactivity_floor_loc(), initial_floor);
1949            assert_eq!(reopened.get_metadata().await.unwrap(), None);
1950            assert_eq!(reopened.get(&key(0)).await.unwrap(), None);
1951            assert_eq!(reopened.get(&key(1)).await.unwrap(), None);
1952            assert_eq!(reopened.get(&key(2)).await.unwrap(), None);
1953
1954            reopened.commit().await.unwrap();
1955            drop(reopened);
1956
1957            let reopened_initial: UnorderedVariableDb = UnorderedVariableDb::init(
1958                context.child("reopen_initial"),
1959                variable_config::<OneCap>(partition, &context),
1960            )
1961            .await
1962            .unwrap();
1963            assert_eq!(reopened_initial.bounds().end, initial_size);
1964            assert_eq!(reopened_initial.root(), initial_root);
1965            assert_eq!(reopened_initial.ops_root(), initial_ops_root);
1966            assert_eq!(reopened_initial.inactivity_floor_loc(), initial_floor);
1967            assert_eq!(reopened_initial.get_metadata().await.unwrap(), None);
1968            assert_eq!(reopened_initial.get(&key(0)).await.unwrap(), None);
1969            assert_eq!(reopened_initial.get(&key(1)).await.unwrap(), None);
1970            assert_eq!(reopened_initial.get(&key(2)).await.unwrap(), None);
1971
1972            reopened_initial.destroy().await.unwrap();
1973        });
1974    }
1975
1976    #[test_traced("INFO")]
1977    fn test_current_rewind_recovery_pruned_repeated_updates() {
1978        let executor = deterministic::Runner::default();
1979        executor.start(|context| async move {
1980            const COMMITS: u64 = 96;
1981
1982            let partition = "current-rewind-pruned-recovery";
1983            let ctx = context.child("db");
1984            let mut db: UnorderedVariableDb =
1985                UnorderedVariableDb::init(ctx.child("storage"), variable_config::<OneCap>(partition, &ctx))
1986                    .await
1987                    .unwrap();
1988
1989            let key0 = key(0);
1990            let mut history = Vec::new();
1991            for round in 0..COMMITS {
1992                commit_writes_with_metadata(
1993                    &mut db,
1994                    [(key0, Some(val(20_000 + round)))],
1995                    None,
1996                )
1997                .await;
1998                history.push((
1999                    db.bounds().end,
2000                    db.inactivity_floor_loc(),
2001                    db.root(),
2002                    db.ops_root(),
2003                    val(20_000 + round),
2004                ));
2005            }
2006
2007            // Keep most ops-log history, but force bitmap pruning so rewind uses pinned-node
2008            // reconstruction (`pruned_chunks > 0` path).
2009            db.prune(Location::new(1)).await.unwrap();
2010            let pruned_bits = db.pruned_bits();
2011            assert!(pruned_bits > 0, "expected bitmap pruning for rewind test");
2012            let bounds = db.bounds();
2013
2014            let (target_size, target_root, target_ops_root, target_value) = history
2015                .iter()
2016                .enumerate()
2017                .find_map(|(idx, (size, floor, root, ops_root, value))| {
2018                    let removed_commits = history.len() - idx - 1;
2019                    if removed_commits >= 3 && *size > bounds.start && *floor >= pruned_bits {
2020                        Some((*size, *root, *ops_root, *value))
2021                    } else {
2022                        None
2023                    }
2024                })
2025                .unwrap_or_else(|| {
2026                    panic!(
2027                        "expected legal pruned rewind target with repeated updates; bounds={bounds:?}, pruned_bits={pruned_bits}, latest_floor={:?}, history={history:?}",
2028                        db.inactivity_floor_loc()
2029                    )
2030                });
2031
2032            db.rewind(target_size).await.unwrap();
2033            assert_eq!(db.root(), target_root);
2034            assert_eq!(db.ops_root(), target_ops_root);
2035            assert_eq!(db.bounds().end, target_size);
2036            assert_eq!(db.get(&key0).await.unwrap(), Some(target_value));
2037
2038            db.commit().await.unwrap();
2039            drop(db);
2040
2041            let mut reopened: UnorderedVariableDb = UnorderedVariableDb::init(
2042                context.child("reopen_pruned_recovery"),
2043                variable_config::<OneCap>(partition, &context),
2044            )
2045            .await
2046            .unwrap();
2047            assert_eq!(reopened.root(), target_root);
2048            assert_eq!(reopened.ops_root(), target_ops_root);
2049            assert_eq!(reopened.bounds().end, target_size);
2050            assert_eq!(reopened.get(&key0).await.unwrap(), Some(target_value));
2051
2052            let metadata_after_rewind = val(30_000);
2053            let new_key = key(1);
2054            let new_value = val(30_001);
2055            let expected_end = commit_writes_with_metadata(
2056                &mut reopened,
2057                [(new_key, Some(new_value))],
2058                Some(metadata_after_rewind),
2059            )
2060            .await
2061            .end;
2062            let root_after_new_write = reopened.root();
2063            let ops_root_after_new_write = reopened.ops_root();
2064            assert_eq!(reopened.bounds().end, expected_end);
2065            assert_eq!(reopened.get_metadata().await.unwrap(), Some(metadata_after_rewind));
2066            assert_eq!(reopened.get(&key0).await.unwrap(), Some(target_value));
2067            assert_eq!(reopened.get(&new_key).await.unwrap(), Some(new_value));
2068
2069            drop(reopened);
2070            let reopened_after_new_write: UnorderedVariableDb = UnorderedVariableDb::init(
2071                context.child("reopen_pruned_after_new_write"),
2072                variable_config::<OneCap>(partition, &context),
2073            )
2074            .await
2075            .unwrap();
2076            assert_eq!(reopened_after_new_write.root(), root_after_new_write);
2077            assert_eq!(reopened_after_new_write.ops_root(), ops_root_after_new_write);
2078            assert_eq!(reopened_after_new_write.bounds().end, expected_end);
2079            assert_eq!(
2080                reopened_after_new_write.get_metadata().await.unwrap(),
2081                Some(metadata_after_rewind)
2082            );
2083            assert_eq!(reopened_after_new_write.get(&key0).await.unwrap(), Some(target_value));
2084            assert_eq!(
2085                reopened_after_new_write.get(&new_key).await.unwrap(),
2086                Some(new_value)
2087            );
2088
2089            reopened_after_new_write.destroy().await.unwrap();
2090        });
2091    }
2092
2093    /// Verify that the delayed-merge settlement guard holds `sync_boundary` at 0 during the
2094    /// unsettled window, so `prune` rejects any non-zero `prune_loc`.
2095    #[test_traced("INFO")]
2096    fn test_current_mmb_settlement_guard_defers_pruning() {
2097        let executor = deterministic::Runner::default();
2098        executor.start(|context| async move {
2099            const COMMITS: u64 = 100;
2100
2101            let partition = "current-mmb-reopen-prove-after-prune";
2102            let ctx = context.child("db");
2103            let mut db: UnorderedVariableMmbDb = UnorderedVariableMmbDb::init(
2104                ctx.child("storage"),
2105                variable_config::<OneCap>(partition, &ctx),
2106            )
2107            .await
2108            .unwrap();
2109
2110            let k = key(0);
2111            let mut expected = None;
2112            for round in 0..COMMITS {
2113                expected = Some(val(50_000 + round));
2114                let mut batch = db.new_batch();
2115                batch = batch.write(k, expected);
2116                let merkleized = batch.merkleize(&db, None).await.unwrap();
2117                db.apply_batch(merkleized).await.unwrap();
2118                db.commit().await.unwrap();
2119            }
2120
2121            let root_before = db.root();
2122            assert!(
2123                *db.inactivity_floor_loc() >= 256,
2124                "expected inactivity floor past chunk 0"
2125            );
2126            assert_eq!(
2127                *db.sync_boundary(),
2128                0,
2129                "settlement guard should hold boundary at 0 during unsettled window"
2130            );
2131
2132            // `prune` must reject any non-zero loc because sync_boundary is still 0.
2133            let result = db.prune(Location::<mmb::Family>::new(1)).await;
2134            assert!(
2135                matches!(result, Err(Error::PruneBeyondMinRequired(_, _))),
2136                "expected PruneBeyondMinRequired, got {result:?}"
2137            );
2138            assert_eq!(db.pruned_bits(), 0);
2139            db.sync().await.unwrap();
2140            drop(db);
2141
2142            // Reopen: no pruning occurred, state is unchanged.
2143            let reopened: UnorderedVariableMmbDb = UnorderedVariableMmbDb::init(
2144                context.child("reopen"),
2145                variable_config::<OneCap>(partition, &context),
2146            )
2147            .await
2148            .unwrap();
2149
2150            assert_eq!(reopened.root(), root_before);
2151            assert_eq!(reopened.get(&k).await.unwrap(), expected);
2152
2153            // key_value_proof: RangeProof::new must also handle pruned chunk 0.
2154            let _proof = reopened.key_value_proof(k).await.unwrap();
2155
2156            reopened.destroy().await.unwrap();
2157        });
2158    }
2159
2160    #[test_traced("INFO")]
2161    fn test_current_mmb_rewind_rejects_unsettled_pruned_window() {
2162        let executor = deterministic::Runner::default();
2163        executor.start(|context| async move {
2164            const COMMITS: u64 = 320;
2165            const N: usize = 32;
2166
2167            let partition = "current-mmb-rewind-unsettled-window";
2168            let ctx = context.child("db");
2169            let mut db: UnorderedVariableMmbDb = UnorderedVariableMmbDb::init(
2170                ctx.child("storage"),
2171                variable_config::<OneCap>(partition, &ctx),
2172            )
2173            .await
2174            .unwrap();
2175
2176            let key0 = key(0);
2177            let mut history = Vec::new();
2178            for round in 0..COMMITS {
2179                let mut batch = db.new_batch();
2180                batch = batch.write(key0, Some(val(60_000 + round)));
2181                let merkleized = batch.merkleize(&db, None).await.unwrap();
2182                db.apply_batch(merkleized).await.unwrap();
2183                db.commit().await.unwrap();
2184                history.push((db.bounds().end, db.inactivity_floor_loc()));
2185            }
2186
2187            db.prune(db.sync_boundary()).await.unwrap();
2188            let pruned_bits = db.pruned_bits();
2189            assert!(pruned_bits > 0, "expected MMB bitmap pruning to be active");
2190            db.sync().await.unwrap();
2191
2192            let chunk_bits = commonware_utils::bitmap::BitMap::<N>::CHUNK_SIZE_BITS;
2193            let pruned_chunks = (pruned_bits / chunk_bits) as u64;
2194            let gh = super::grafting::height::<N>();
2195            let youngest = pruned_chunks - 1;
2196            let pair_chunk = youngest & !1;
2197            let pair_start = pair_chunk << gh;
2198            let pair_pos = <mmb::Family as merkle::Graftable>::subtree_root_position(
2199                merkle::Location::<mmb::Family>::new(pair_start),
2200                gh + 1,
2201            );
2202            let absorbed_after =
2203                <mmb::Family as merkle::Graftable>::peak_birth_size(pair_pos, gh + 1);
2204
2205            let unsafe_target = history
2206                .iter()
2207                .filter_map(|(size, floor)| {
2208                    let s = **size;
2209                    if s >= pruned_bits && s < absorbed_after && **floor >= pruned_bits {
2210                        Some(s)
2211                    } else {
2212                        None
2213                    }
2214                })
2215                .max()
2216                .unwrap_or_else(|| {
2217                    panic!(
2218                        "expected rewind target in unsettled window: pruned_bits={pruned_bits}, absorbed_after={absorbed_after}, history={history:?}"
2219                    )
2220                });
2221
2222            let err = db
2223                .rewind(merkle::Location::<mmb::Family>::new(unsafe_target))
2224                .await
2225                .unwrap_err();
2226            assert!(
2227                matches!(err, Error::Journal(crate::journal::Error::ItemPruned(_))),
2228                "unexpected rewind error for unsettled delayed-merge window: {err:?}"
2229            );
2230            drop(db);
2231
2232            let reopened: UnorderedVariableMmbDb = UnorderedVariableMmbDb::init(
2233                context.child("reopen"),
2234                variable_config::<OneCap>(partition, &context),
2235            )
2236            .await
2237            .unwrap();
2238            reopened.destroy().await.unwrap();
2239        });
2240    }
2241
2242    /// Verify that `Db::prune` never advances the ops journal past the settled bitmap
2243    /// pruning boundary on a delayed-merge (MMB) family. The journal's lower bound must be
2244    /// less than or equal to `sync_boundary()`, and the test setup must force the lag to
2245    /// be strictly active so the assertion is not vacuous.
2246    #[test_traced]
2247    fn test_current_mmb_prune_respects_sync_boundary() {
2248        let executor = deterministic::Runner::default();
2249        executor.start(|context| async move {
2250            const COMMITS: u64 = 320;
2251
2252            let ctx = context.child("db");
2253            let mut db: UnorderedVariableMmbDb = UnorderedVariableMmbDb::init(
2254                ctx.child("storage"),
2255                variable_config::<OneCap>("prune-clip-mmb", &ctx),
2256            )
2257            .await
2258            .unwrap();
2259
2260            let k = key(0);
2261            for round in 0..COMMITS {
2262                mmb_commit(&mut db, [(k, Some(val(70_000 + round)))]).await;
2263            }
2264
2265            db.prune(db.sync_boundary()).await.unwrap();
2266
2267            let boundary = db.sync_boundary();
2268            let floor = db.inactivity_floor_loc();
2269            assert!(
2270                boundary < floor,
2271                "delayed-merge lag must be strictly active: boundary={boundary}, floor={floor}"
2272            );
2273            assert!(
2274                db.bounds().start <= boundary,
2275                "ops journal was pruned past the settled bitmap boundary: \
2276                 bounds.start={}, boundary={boundary}",
2277                db.bounds().start
2278            );
2279
2280            db.destroy().await.unwrap();
2281        });
2282    }
2283
2284    /// Verify that on a non-delayed-merge (MMR) family `sync_boundary()` lags the inactivity
2285    /// floor only by chunk alignment (less than one chunk) — never by a delayed-merge absorption
2286    /// window. Guards against an accidental regression that would introduce a larger lag on
2287    /// families that don't need it.
2288    #[test_traced]
2289    fn test_current_mmr_prune_boundary_lag_is_only_chunk_alignment() {
2290        let executor = deterministic::Runner::default();
2291        executor.start(|context| async move {
2292            const COMMITS: u64 = 320;
2293            const N: usize = 32;
2294
2295            let ctx = context.child("db");
2296            let mut db: UnorderedVariableDb = UnorderedVariableDb::init(
2297                ctx.child("storage"),
2298                variable_config::<OneCap>("prune-clip-mmr", &ctx),
2299            )
2300            .await
2301            .unwrap();
2302
2303            for round in 0..COMMITS {
2304                commit_writes_with_metadata(
2305                    &mut db,
2306                    [(key(0), Some(val(80_000 + round)))],
2307                    None,
2308                )
2309                .await;
2310            }
2311
2312            db.prune(db.sync_boundary()).await.unwrap();
2313
2314            let boundary = db.sync_boundary();
2315            let floor = db.inactivity_floor_loc();
2316            let chunk_bits = commonware_utils::bitmap::BitMap::<N>::CHUNK_SIZE_BITS;
2317            assert!(
2318                boundary <= floor && *floor - *boundary < chunk_bits,
2319                "MMR lag should be only chunk alignment: boundary={boundary}, floor={floor}, chunk_bits={chunk_bits}"
2320            );
2321            assert!(
2322                db.bounds().start <= boundary,
2323                "ops journal bounds must be <= sync_boundary: bounds.start={}, boundary={boundary}",
2324                db.bounds().start
2325            );
2326
2327            db.destroy().await.unwrap();
2328        });
2329    }
2330
2331    /// Verify that `prune(loc)` with `loc < sync_boundary()` prunes the ops journal only as far
2332    /// as the caller requested.
2333    #[test_traced]
2334    fn test_current_prune_below_settled_boundary_is_honored() {
2335        let executor = deterministic::Runner::default();
2336        executor.start(|context| async move {
2337            const COMMITS: u64 = 100;
2338
2339            let ctx = context.child("db");
2340            let mut db: UnorderedVariableDb = UnorderedVariableDb::init(
2341                ctx.child("storage"),
2342                variable_config::<OneCap>("prune-below-boundary", &ctx),
2343            )
2344            .await
2345            .unwrap();
2346
2347            for round in 0..COMMITS {
2348                commit_writes_with_metadata(&mut db, [(key(0), Some(val(90_000 + round)))], None)
2349                    .await;
2350            }
2351
2352            assert!(*db.inactivity_floor_loc() > 1);
2353            let small = Location::new(1);
2354            db.prune(small).await.unwrap();
2355
2356            assert!(
2357                db.bounds().start <= small,
2358                "journal pruning exceeded the caller-supplied target: bounds.start={}, requested={small}",
2359                db.bounds().start
2360            );
2361
2362            db.destroy().await.unwrap();
2363        });
2364    }
2365
2366    /// Prune, then grow without pruning again so delayed MMB merges occur inside the
2367    /// already-pruned region. Verify proof + reopen correctness.
2368    #[test_traced]
2369    fn test_current_mmb_reopen_and_prove_after_prune_delayed_merge() {
2370        let executor = deterministic::Runner::default();
2371        executor.start(|context| async move {
2372            let db_ctx = context.child("db_init");
2373            let mut db: UnorderedVariableMmbDb = UnorderedVariableMmbDb::init(
2374                db_ctx.child("db"),
2375                variable_config::<OneCap>("test_prune_delayed_merge", &db_ctx),
2376            )
2377            .await
2378            .unwrap();
2379
2380            let k = key(0);
2381
2382            for round in 0..200u64 {
2383                mmb_commit(&mut db, [(k, Some(val(60_000 + round)))]).await;
2384            }
2385
2386            db.prune(db.sync_boundary()).await.unwrap();
2387            db.sync().await.unwrap();
2388
2389            // Keep growing without pruning: delayed merges now occur in the pruned region.
2390            for round in 200..300u64 {
2391                mmb_commit(&mut db, [(key(1), Some(val(round)))]).await;
2392            }
2393
2394            let proof = db.key_value_proof(k).await.unwrap();
2395            assert!(UnorderedVariableMmbDb::verify_key_value_proof(
2396                k,
2397                val(60_000 + 199),
2398                &proof,
2399                &db.root()
2400            ));
2401
2402            let target_root = db.root();
2403            drop(db);
2404
2405            let reopen_ctx = context.child("db_reopen");
2406            let reopened: UnorderedVariableMmbDb = UnorderedVariableMmbDb::init(
2407                reopen_ctx.child("db"),
2408                variable_config::<OneCap>("test_prune_delayed_merge", &reopen_ctx),
2409            )
2410            .await
2411            .unwrap();
2412
2413            assert_eq!(reopened.root(), target_root);
2414
2415            let proof = reopened.key_value_proof(k).await.unwrap();
2416            assert!(UnorderedVariableMmbDb::verify_key_value_proof(
2417                k,
2418                val(60_000 + 199),
2419                &proof,
2420                &reopened.root()
2421            ));
2422
2423            reopened.destroy().await.unwrap();
2424        });
2425    }
2426
2427    /// Grow past 2 full pruned chunks, prune, reopen, verify root + value.
2428    #[test_traced]
2429    fn test_current_mmb_reopen_after_prune_two_chunks() {
2430        let executor = deterministic::Runner::default();
2431        executor.start(|context| async move {
2432            let db_ctx = context.child("db");
2433            let mut db: UnorderedVariableMmbDb = UnorderedVariableMmbDb::init(
2434                db_ctx.child("db"),
2435                variable_config::<OneCap>("test_prune_two", &db_ctx),
2436            )
2437            .await
2438            .unwrap();
2439
2440            let k = key(0);
2441            // Always assigned before the loop breaks.
2442            let mut expected;
2443
2444            // Keep growing until the settle guard allows 2+ pruned chunks.
2445            // The absorber for chunk pair [0,1] at gh=8 needs ~766 ops leaves.
2446            let mut round = 0u64;
2447            loop {
2448                expected = Some(val(60_000 + round));
2449                mmb_commit(&mut db, [(k, expected)]).await;
2450                round += 1;
2451                db.prune(db.sync_boundary()).await.unwrap();
2452                if db.pruned_bits() >= 512 {
2453                    break;
2454                }
2455                assert!(
2456                    round < 500,
2457                    "failed to reach 2 pruned chunks after {round} commits"
2458                );
2459            }
2460            db.sync().await.unwrap();
2461
2462            let target_root = db.root();
2463            drop(db);
2464
2465            let reopen_ctx = context.child("db_reopen");
2466            let reopened: UnorderedVariableMmbDb = UnorderedVariableMmbDb::init(
2467                reopen_ctx.child("db"),
2468                variable_config::<OneCap>("test_prune_two", &reopen_ctx),
2469            )
2470            .await
2471            .unwrap();
2472
2473            assert_eq!(reopened.root(), target_root);
2474            assert_eq!(reopened.get(&k).await.unwrap(), expected);
2475            reopened.destroy().await.unwrap();
2476        });
2477    }
2478
2479    /// Three rounds of grow + prune + reopen. Verifies repeated prune cycles don't diverge.
2480    #[test_traced]
2481    fn test_current_mmb_repeated_prune() {
2482        let executor = deterministic::Runner::default();
2483        executor.start(|context| async move {
2484            let mut db_ctx = context.child("db_init");
2485            let mut db: UnorderedVariableMmbDb = UnorderedVariableMmbDb::init(
2486                db_ctx.child("db"),
2487                variable_config::<OneCap>("test_repeated_prune", &db_ctx),
2488            )
2489            .await
2490            .unwrap();
2491
2492            for round in 0..3u64 {
2493                let k = key(round * 1000);
2494                let mut expected = None;
2495                for i in 0..90 {
2496                    expected = Some(val(round * 1000 + i));
2497                    mmb_commit(&mut db, [(k, expected)]).await;
2498                }
2499
2500                db.prune(db.sync_boundary()).await.unwrap();
2501                db.sync().await.unwrap();
2502
2503                let root_before = db.root();
2504                db_ctx = context.child("db").with_attribute("round", round);
2505
2506                let prev_db = db;
2507                db = UnorderedVariableMmbDb::init(
2508                    db_ctx.child("db"),
2509                    variable_config::<OneCap>("test_repeated_prune", &db_ctx),
2510                )
2511                .await
2512                .unwrap();
2513
2514                assert_eq!(db.root(), root_before);
2515                assert_eq!(db.get(&k).await.unwrap(), expected);
2516                drop(prev_db);
2517            }
2518
2519            db.destroy().await.unwrap();
2520        });
2521    }
2522
2523    /// Step-by-step growth after prune, comparing roots against an unpruned reference.
2524    #[test_traced]
2525    fn test_current_mmb_stepwise_growth_matches_unpruned_reference() {
2526        let executor = deterministic::Runner::default();
2527        executor.start(|context| async move {
2528            let db_ctx = context.child("db_stepwise");
2529            let mut db: UnorderedVariableMmbDb = UnorderedVariableMmbDb::init(
2530                db_ctx.child("db"),
2531                variable_config::<OneCap>("test_stepwise", &db_ctx),
2532            )
2533            .await
2534            .unwrap();
2535
2536            let ref_ctx = context.child("ref_stepwise");
2537            let mut ref_db: UnorderedVariableMmbDb = UnorderedVariableMmbDb::init(
2538                ref_ctx.child("db"),
2539                variable_config::<OneCap>("test_stepwise_ref", &ref_ctx),
2540            )
2541            .await
2542            .unwrap();
2543
2544            let k = key(0);
2545            let mut commit_idx = 0u64;
2546
2547            // Grow until the inactivity floor reaches 4 chunks.
2548            while *db.inactivity_floor_loc() < 1024 {
2549                let value = Some(val(80_000 + commit_idx));
2550                mmb_commit(&mut db, [(k, value)]).await;
2551                mmb_commit(&mut ref_db, [(k, value)]).await;
2552                commit_idx += 1;
2553            }
2554
2555            db.prune(db.sync_boundary()).await.unwrap();
2556            db.sync().await.unwrap();
2557            assert_eq!(
2558                db.root(),
2559                ref_db.root(),
2560                "root mismatch immediately after prune"
2561            );
2562
2563            // Step-by-step growth through the delayed-merge window.
2564            loop {
2565                let db_leaves =
2566                    *Location::<mmb::Family>::try_from(db.any.log.merkle.size()).unwrap();
2567                if db_leaves >= 1560 {
2568                    break;
2569                }
2570
2571                let value = Some(val(80_000 + commit_idx));
2572                mmb_commit(&mut db, [(k, value)]).await;
2573                mmb_commit(&mut ref_db, [(k, value)]).await;
2574                commit_idx += 1;
2575
2576                let db_leaves =
2577                    *Location::<mmb::Family>::try_from(db.any.log.merkle.size()).unwrap();
2578                assert_eq!(
2579                    db.root(),
2580                    ref_db.root(),
2581                    "stepwise root mismatch: leaves={db_leaves}, commit_idx={commit_idx}"
2582                );
2583            }
2584
2585            db.destroy().await.unwrap();
2586            ref_db.destroy().await.unwrap();
2587        });
2588    }
2589
2590    /// Multi-round prune + reopen + proof against an unpruned reference.
2591    #[test_traced]
2592    fn test_current_mmb_large_repeated_prune_matches_unpruned_reference() {
2593        let executor = deterministic::Runner::default();
2594        executor.start(|context| async move {
2595            const ROUNDS: u64 = 8;
2596            const COMMITS_PER_ROUND: u64 = 120;
2597
2598            let mut db_ctx = context.child("db_init");
2599            let mut db: UnorderedVariableMmbDb = UnorderedVariableMmbDb::init(
2600                db_ctx.child("db"),
2601                variable_config::<OneCap>("test_large_prune", &db_ctx),
2602            )
2603            .await
2604            .unwrap();
2605
2606            let ref_ctx = context.child("ref");
2607            let mut ref_db: UnorderedVariableMmbDb = UnorderedVariableMmbDb::init(
2608                ref_ctx.child("db"),
2609                variable_config::<OneCap>("test_large_prune_ref", &ref_ctx),
2610            )
2611            .await
2612            .unwrap();
2613
2614            let k = key(0);
2615            let mut expected = None;
2616
2617            for round in 0..ROUNDS {
2618                for i in 0..COMMITS_PER_ROUND {
2619                    let value = Some(val(round * 10_000 + i));
2620                    expected = value;
2621                    mmb_commit(&mut db, [(k, value)]).await;
2622                    mmb_commit(&mut ref_db, [(k, value)]).await;
2623                }
2624
2625                assert_eq!(
2626                    db.root(),
2627                    ref_db.root(),
2628                    "root mismatch before prune at round {round}"
2629                );
2630
2631                db.prune(db.sync_boundary()).await.unwrap();
2632                db.sync().await.unwrap();
2633
2634                assert_eq!(
2635                    db.root(),
2636                    ref_db.root(),
2637                    "root mismatch after prune at round {round}"
2638                );
2639
2640                let proof = db.key_value_proof(k).await.unwrap();
2641                assert!(
2642                    UnorderedVariableMmbDb::verify_key_value_proof(
2643                        k,
2644                        expected.expect("value should exist"),
2645                        &proof,
2646                        &db.root()
2647                    ),
2648                    "proof verification failed at round {round}"
2649                );
2650
2651                db_ctx = context.child("db_reopen").with_attribute("round", round);
2652                let prev_db = db;
2653                db = UnorderedVariableMmbDb::init(
2654                    db_ctx.child("db"),
2655                    variable_config::<OneCap>("test_large_prune", &db_ctx),
2656                )
2657                .await
2658                .unwrap();
2659
2660                assert_eq!(
2661                    db.root(),
2662                    ref_db.root(),
2663                    "root mismatch after reopen at round {round}"
2664                );
2665                assert_eq!(
2666                    db.get(&k).await.unwrap(),
2667                    expected,
2668                    "value mismatch after reopen at round {round}"
2669                );
2670
2671                let proof = db.key_value_proof(k).await.unwrap();
2672                assert!(
2673                    UnorderedVariableMmbDb::verify_key_value_proof(
2674                        k,
2675                        expected.expect("value should exist"),
2676                        &proof,
2677                        &db.root()
2678                    ),
2679                    "proof verification failed after reopen at round {round}"
2680                );
2681
2682                drop(prev_db);
2683            }
2684
2685            db.destroy().await.unwrap();
2686            ref_db.destroy().await.unwrap();
2687        });
2688    }
2689
2690    /// Verify that prune beyond the sync boundary is rejected without mutating state.
2691    #[test_traced]
2692    fn test_current_prune_rejects_beyond_sync_boundary_without_mutation() {
2693        let executor = deterministic::Runner::default();
2694        executor.start(|context| async move {
2695            const COMMITS: u64 = 160;
2696
2697            let partition = "current-prune-beyond-boundary";
2698            let ctx = context.child("db");
2699            let mut db: UnorderedVariableDb = UnorderedVariableDb::init(
2700                ctx.child("storage"),
2701                variable_config::<OneCap>(partition, &ctx),
2702            )
2703            .await
2704            .unwrap();
2705
2706            let key0 = key(0);
2707            for round in 0..COMMITS {
2708                commit_writes_with_metadata(&mut db, [(key0, Some(val(40_000 + round)))], None)
2709                    .await;
2710            }
2711
2712            let expected_root = db.root();
2713            let expected_ops_root = db.ops_root();
2714            let expected_boundary = db.sync_boundary();
2715            let expected_pruned_bits = db.pruned_bits();
2716            let expected_value = db.get(&key0).await.unwrap();
2717
2718            // 32 * 8 = 256 bits per chunk for N=32.
2719            let invalid_prune_loc = Location::new(*expected_boundary + 256);
2720            let result = db.prune(invalid_prune_loc).await;
2721            assert!(
2722                matches!(result, Err(Error::PruneBeyondMinRequired(loc, boundary))
2723                    if loc == invalid_prune_loc && boundary == expected_boundary),
2724                "expected prune rejection above sync boundary, got {result:?}"
2725            );
2726
2727            assert_eq!(db.root(), expected_root);
2728            assert_eq!(db.ops_root(), expected_ops_root);
2729            assert_eq!(db.pruned_bits(), expected_pruned_bits);
2730            assert_eq!(db.get(&key0).await.unwrap(), expected_value);
2731
2732            drop(db);
2733
2734            let reopened: UnorderedVariableDb = UnorderedVariableDb::init(
2735                context.child("reopen"),
2736                variable_config::<OneCap>(partition, &context),
2737            )
2738            .await
2739            .unwrap();
2740            assert_eq!(reopened.root(), expected_root);
2741            assert_eq!(reopened.ops_root(), expected_ops_root);
2742            assert_eq!(reopened.pruned_bits(), expected_pruned_bits);
2743            assert_eq!(reopened.get(&key0).await.unwrap(), expected_value);
2744
2745            reopened.destroy().await.unwrap();
2746        });
2747    }
2748
2749    #[test_traced("INFO")]
2750    fn test_current_rewind_small_delta_large_history() {
2751        let executor = deterministic::Runner::default();
2752        executor.start(|context| async move {
2753            const COMMITS: u64 = 200;
2754
2755            let partition = "current-rewind-small-delta";
2756            let ctx = context.child("db");
2757            let mut db: UnorderedVariableDb = UnorderedVariableDb::init(
2758                ctx.child("storage"),
2759                variable_config::<OneCap>(partition, &ctx),
2760            )
2761            .await
2762            .unwrap();
2763
2764            let key0 = key(0);
2765            let key1 = key(1);
2766            let mut history = Vec::new();
2767
2768            for round in 0..COMMITS {
2769                let key0_value = val(40_000 + round);
2770                let key1_value = if round % 3 == 1 {
2771                    None
2772                } else {
2773                    Some(val(50_000 + round))
2774                };
2775
2776                commit_writes_with_metadata(
2777                    &mut db,
2778                    [(key0, Some(key0_value)), (key1, key1_value)],
2779                    None,
2780                )
2781                .await;
2782
2783                history.push((
2784                    db.bounds().end,
2785                    db.root(),
2786                    db.ops_root(),
2787                    key0_value,
2788                    key1_value,
2789                ));
2790            }
2791
2792            let target = *history
2793                .get(history.len() - 3)
2794                .expect("history should contain at least three commits");
2795            let (target_size, target_root, target_ops_root, target_key0, target_key1) = target;
2796
2797            db.rewind(target_size).await.unwrap();
2798            assert_eq!(db.bounds().end, target_size);
2799            assert_eq!(db.root(), target_root);
2800            assert_eq!(db.ops_root(), target_ops_root);
2801            assert_eq!(db.get(&key0).await.unwrap(), Some(target_key0));
2802            assert_eq!(db.get(&key1).await.unwrap(), target_key1);
2803
2804            db.commit().await.unwrap();
2805            drop(db);
2806
2807            let reopened: UnorderedVariableDb = UnorderedVariableDb::init(
2808                context.child("reopen_small_delta"),
2809                variable_config::<OneCap>(partition, &context),
2810            )
2811            .await
2812            .unwrap();
2813            assert_eq!(reopened.bounds().end, target_size);
2814            assert_eq!(reopened.root(), target_root);
2815            assert_eq!(reopened.ops_root(), target_ops_root);
2816            assert_eq!(reopened.get(&key0).await.unwrap(), Some(target_key0));
2817            assert_eq!(reopened.get(&key1).await.unwrap(), target_key1);
2818
2819            reopened.destroy().await.unwrap();
2820        });
2821    }
2822
2823    #[test_traced("INFO")]
2824    fn test_current_rewind_pruned_target_errors() {
2825        let executor = deterministic::Runner::default();
2826        executor.start(|context| async move {
2827            const KEYS: u64 = 384;
2828
2829            let partition = "current-rewind-pruned";
2830            let ctx = context.child("db");
2831            let mut db: UnorderedVariableDb =
2832                UnorderedVariableDb::init(ctx.child("storage"), variable_config::<OneCap>(partition, &ctx))
2833                    .await
2834                    .unwrap();
2835
2836            let first_range = commit_writes_with_metadata(
2837                &mut db,
2838                (0..KEYS).map(|i| (key(i), Some(val(i)))),
2839                None,
2840            )
2841            .await;
2842            commit_writes_with_metadata(
2843                &mut db,
2844                (0..KEYS).map(|i| (key(i), Some(val(1000 + i)))),
2845                None,
2846            )
2847            .await;
2848
2849            db.prune(db.sync_boundary()).await.unwrap();
2850            let pruned_bits = db.pruned_bits();
2851            assert!(
2852                pruned_bits > *first_range.start,
2853                "expected bitmap pruning boundary above rewind target: pruned_bits={pruned_bits}, target={:?}",
2854                first_range.start
2855            );
2856
2857            let oldest_retained = db.bounds().start;
2858            let boundary_err = db.rewind(oldest_retained).await.unwrap_err();
2859            assert!(
2860                matches!(
2861                    boundary_err,
2862                    Error::Journal(crate::journal::Error::ItemPruned(_))
2863                ),
2864                "unexpected rewind error at retained boundary: {boundary_err:?}"
2865            );
2866
2867            let expected_pruned_loc = *first_range.start - 1;
2868            let err = db.rewind(first_range.start).await.unwrap_err();
2869            assert!(
2870                matches!(
2871                    err,
2872                    Error::Journal(crate::journal::Error::ItemPruned(loc))
2873                    if loc == expected_pruned_loc
2874                ),
2875                "unexpected rewind error: {err:?}"
2876            );
2877
2878            db.destroy().await.unwrap();
2879        });
2880    }
2881
2882    #[test_traced("INFO")]
2883    fn test_current_rewind_rejects_target_below_bitmap_floor() {
2884        let executor = deterministic::Runner::default();
2885        executor.start(|context| async move {
2886            const COMMITS: u64 = 96;
2887
2888            let partition = "current-rewind-bitmap-floor";
2889            let ctx = context.child("db");
2890            let mut db: UnorderedVariableDb =
2891                UnorderedVariableDb::init(ctx.child("storage"), variable_config::<OneCap>(partition, &ctx))
2892                    .await
2893                    .unwrap();
2894
2895            let mut history = Vec::new();
2896            for round in 0..COMMITS {
2897                commit_writes_with_metadata(
2898                    &mut db,
2899                    [(key(0), Some(val(10_000 + round)))],
2900                    None,
2901                )
2902                .await;
2903                history.push((db.bounds().end, db.inactivity_floor_loc()));
2904            }
2905            assert!(db.inactivity_floor_loc() > Location::new(64));
2906
2907            // Intentionally prune less than the inactivity floor: log retains older ops, but the
2908            // bitmap still prunes to inactivity floor.
2909            let prune_loc = Location::new(1);
2910            db.prune(prune_loc).await.unwrap();
2911            let pruned_bits = db.pruned_bits();
2912            assert!(pruned_bits > 0);
2913            let retained_start = db.bounds().start;
2914
2915            // Pick a historical commit that is still within retained log bounds but whose floor is
2916            // below the bitmap pruning boundary.
2917            let rewind_target = history
2918                .iter()
2919                .find_map(|(size, floor)| {
2920                    if *size > *retained_start
2921                        && *size >= pruned_bits
2922                        && *floor >= *retained_start
2923                        && *floor < pruned_bits
2924                    {
2925                        Some(*size)
2926                    } else {
2927                        None
2928                    }
2929                })
2930                .unwrap_or_else(|| {
2931                    panic!(
2932                        "expected rewind target below bitmap boundary. retained_start={retained_start:?}, pruned_bits={pruned_bits}, latest_floor={:?}, history={history:?}",
2933                        db.inactivity_floor_loc()
2934                    )
2935                });
2936
2937            let err = db.rewind(rewind_target).await.unwrap_err();
2938            assert!(
2939                matches!(err, Error::Journal(crate::journal::Error::ItemPruned(_))),
2940                "unexpected rewind error: {err:?}"
2941            );
2942
2943            db.destroy().await.unwrap();
2944        });
2945    }
2946
2947    /// Verify that the speculative canonical root from a merkleized batch matches the root
2948    /// recomputed from committed state after sync + reopen.
2949    ///
2950    /// Uses enough operations to cross a chunk boundary (CHUNK_SIZE_BITS = N*8), which exercises
2951    /// the grafted root computation for newly completed chunks.
2952    pub async fn test_speculative_root_matches_committed<M, C, F, Fut>(
2953        context: Context,
2954        mut open_db: F,
2955    ) where
2956        M: merkle::Graftable + 'static,
2957        C: DbAny<M> + 'static,
2958        C::Key: TestKey,
2959        <C as DbAny<M>>::Value: TestValue,
2960        F: FnMut(Context, String) -> Fut + Clone,
2961        Fut: Future<Output = C>,
2962    {
2963        let mut open_db_clone = open_db.clone();
2964        let partition = "speculative-root".to_string();
2965
2966        // Write enough operations to cross a chunk boundary. With N=32 (CHUNK_SIZE_BITS=256),
2967        // 260 writes + 1 CommitFloor = 261 operations, completing one chunk with 5 ops in the
2968        // next partial chunk. This ensures the grafted root computation must handle the
2969        // newly completed chunk.
2970        let mut db: C = Box::pin(open_db_clone(context.child("init"), partition.clone())).await;
2971        let mut batch = db.new_batch();
2972        for i in 0..260 {
2973            batch = batch.write(TestKey::from_seed(i), Some(TestValue::from_seed(i + 1000)));
2974        }
2975        let merkleized = batch.merkleize(&db, None).await.unwrap();
2976        db.apply_batch(merkleized).await.unwrap();
2977        let speculative_root = db.root();
2978
2979        // Sync, close, and reopen to get the root recomputed from committed state.
2980        db.sync().await.unwrap();
2981        drop(db);
2982
2983        let db: C = Box::pin(open_db(context.child("reopen"), partition)).await;
2984        assert_eq!(db.root(), speculative_root);
2985
2986        db.destroy().await.unwrap();
2987    }
2988
2989    test_for_all_variants!(test_speculative_root_matches_committed, "INFO");
2990
2991    /// MerkleizedBatch::get() at the current level reads overlay then base DB.
2992    #[test_traced("INFO")]
2993    fn test_current_batch_merkleized_get() {
2994        let executor = deterministic::Runner::default();
2995        executor.start(|context| async move {
2996            let ctx = context.child("db");
2997            let mut db: UnorderedVariableDb = UnorderedVariableDb::init(
2998                ctx.child("storage"),
2999                variable_config::<OneCap>("mg", &ctx),
3000            )
3001            .await
3002            .unwrap();
3003
3004            let ka = key(0);
3005            let kb = key(1);
3006            let kc = key(2);
3007
3008            // Pre-populate A.
3009            {
3010                let mut batch = db.new_batch();
3011                batch = batch.write(ka, Some(val(0)));
3012                let merkleized = batch.merkleize(&db, None).await.unwrap();
3013                db.apply_batch(merkleized).await.unwrap();
3014            }
3015
3016            // Batch: update A, delete nothing, create B.
3017            let va2 = val(100);
3018            let vb = val(1);
3019            let mut batch = db.new_batch();
3020            batch = batch.write(ka, Some(va2));
3021            batch = batch.write(kb, Some(vb));
3022            let merkleized = batch.merkleize(&db, None).await.unwrap();
3023
3024            assert_eq!(merkleized.get(&ka, &db).await.unwrap(), Some(va2));
3025            assert_eq!(merkleized.get(&kb, &db).await.unwrap(), Some(vb));
3026            assert_eq!(merkleized.get(&kc, &db).await.unwrap(), None);
3027
3028            db.destroy().await.unwrap();
3029        });
3030    }
3031
3032    /// Batch chaining at the current level: parent -> merkleize -> child -> merkleize.
3033    /// Child's canonical root matches db.root() after apply.
3034    #[test_traced("INFO")]
3035    fn test_current_batch_chaining() {
3036        let executor = deterministic::Runner::default();
3037        executor.start(|context| async move {
3038            let ctx = context.child("db");
3039            let mut db: UnorderedVariableDb = UnorderedVariableDb::init(
3040                ctx.child("storage"),
3041                variable_config::<OneCap>("ch", &ctx),
3042            )
3043            .await
3044            .unwrap();
3045
3046            // Parent batch writes keys 0..5.
3047            let mut parent = db.new_batch();
3048            for i in 0..5 {
3049                parent = parent.write(key(i), Some(val(i)));
3050            }
3051            let parent_m = parent.merkleize(&db, None).await.unwrap();
3052
3053            // Child batch writes keys 5..10 and overrides key 0.
3054            let mut child = parent_m.new_batch::<Sha256>();
3055            for i in 5..10 {
3056                child = child.write(key(i), Some(val(i)));
3057            }
3058            child = child.write(key(0), Some(val(999)));
3059            let child_m = child.merkleize(&db, None).await.unwrap();
3060
3061            let child_root = child_m.root();
3062
3063            // Child get reads through all layers.
3064            assert_eq!(child_m.get(&key(0), &db).await.unwrap(), Some(val(999)));
3065            assert_eq!(child_m.get(&key(3), &db).await.unwrap(), Some(val(3)));
3066            assert_eq!(child_m.get(&key(7), &db).await.unwrap(), Some(val(7)));
3067
3068            db.apply_batch(child_m).await.unwrap();
3069            assert_eq!(db.root(), child_root);
3070
3071            // Verify all keys are correct.
3072            assert_eq!(db.get(&key(0)).await.unwrap(), Some(val(999)));
3073            for i in 1..10 {
3074                assert_eq!(db.get(&key(i)).await.unwrap(), Some(val(i)));
3075            }
3076
3077            db.destroy().await.unwrap();
3078        });
3079    }
3080
3081    #[test_traced("INFO")]
3082    fn test_current_unordered_root_matches_between_pending_and_committed_paths() {
3083        let executor = deterministic::Runner::default();
3084        executor.start(|context| async move {
3085            let ctx = context.child("db");
3086            let mut db: UnorderedFixedDb =
3087                UnorderedFixedDb::init(ctx.child("storage"), fixed_config::<OneCap>("ucr", &ctx))
3088                    .await
3089                    .unwrap();
3090            let key_a = colliding_digest(0xAA, 1);
3091            let key_b = colliding_digest(0xAA, 0);
3092
3093            // Seed four colliding committed keys, then update only key_a.
3094            // The specific 4 / 1 / 0 shape is a concrete counterexample:
3095            // key_b remains outside the parent diff and is still resolved
3096            // through the committed snapshot in the child.
3097            let mut initial = db.new_batch();
3098            for i in 0..4 {
3099                initial = initial.write(colliding_digest(0xAA, i), Some(colliding_digest(0xBB, i)));
3100            }
3101            let merkleized = initial.merkleize(&db, None).await.unwrap();
3102            db.apply_batch(merkleized).await.unwrap();
3103            db.commit().await.unwrap();
3104
3105            // Update only key_a so the colliding sibling key_b remains outside
3106            // the parent diff and must still be resolved through the committed
3107            // snapshot in the child.
3108            let parent = db
3109                .new_batch()
3110                .write(key_a, Some(colliding_digest(0xCC, 1)))
3111                .merkleize(&db, None)
3112                .await
3113                .unwrap();
3114
3115            // Build the child while the parent is still pending, then rebuild
3116            // the same logical child after committing the parent and compare
3117            // both canonical and ops roots.
3118            let pending_child = parent
3119                .new_batch::<Sha256>()
3120                .write(key_a, Some(colliding_digest(0xDD, 1)))
3121                .write(key_b, Some(colliding_digest(0xDD, 0)))
3122                .merkleize(&db, None)
3123                .await
3124                .unwrap();
3125
3126            let pending_root = pending_child.root();
3127            let pending_ops_root = pending_child.ops_root();
3128
3129            db.apply_batch(parent).await.unwrap();
3130            db.commit().await.unwrap();
3131
3132            let committed_child = db
3133                .new_batch()
3134                .write(key_a, Some(colliding_digest(0xDD, 1)))
3135                .write(key_b, Some(colliding_digest(0xDD, 0)))
3136                .merkleize(&db, None)
3137                .await
3138                .unwrap();
3139
3140            assert_eq!(pending_root, committed_child.root());
3141            assert_eq!(pending_ops_root, committed_child.ops_root());
3142
3143            // Apply pending child onto the committed parent
3144            // and ensure the applied wrapper roots still match.
3145            db.apply_batch(pending_child).await.unwrap();
3146            assert_eq!(db.root(), committed_child.root());
3147            assert_eq!(db.ops_root(), committed_child.ops_root());
3148
3149            db.destroy().await.unwrap();
3150        });
3151    }
3152
3153    #[test_traced("INFO")]
3154    fn test_current_ordered_root_matches_between_pending_and_committed_paths() {
3155        let executor = deterministic::Runner::default();
3156        executor.start(|context| async move {
3157            let ctx = context.child("db");
3158            let mut db: OrderedFixedDb =
3159                OrderedFixedDb::init(ctx.child("storage"), fixed_config::<OneCap>("ocr", &ctx))
3160                    .await
3161                    .unwrap();
3162            let key_a = colliding_digest(0xAA, 1);
3163            let key_b = colliding_digest(0xAA, 0);
3164
3165            // Match the unordered counterexample shape on the ordered path so
3166            // both wrappers exercise the same collision pattern.
3167            let mut initial = db.new_batch();
3168            for i in 0..4 {
3169                initial = initial.write(colliding_digest(0xAA, i), Some(colliding_digest(0xBB, i)));
3170            }
3171            let merkleized = initial.merkleize(&db, None).await.unwrap();
3172            db.apply_batch(merkleized).await.unwrap();
3173            db.commit().await.unwrap();
3174
3175            // Update only key_a so the colliding sibling key_b remains outside
3176            // the parent diff and must still be resolved through the committed
3177            // snapshot in the child.
3178            let parent = db
3179                .new_batch()
3180                .write(key_a, Some(colliding_digest(0xCC, 1)))
3181                .merkleize(&db, None)
3182                .await
3183                .unwrap();
3184
3185            // Build the child while the parent is still pending, then rebuild
3186            // the same logical child after committing the parent.
3187            let pending_child = parent
3188                .new_batch::<Sha256>()
3189                .write(key_a, Some(colliding_digest(0xDD, 1)))
3190                .write(key_b, Some(colliding_digest(0xDD, 0)))
3191                .merkleize(&db, None)
3192                .await
3193                .unwrap();
3194
3195            let pending_root = pending_child.root();
3196            let pending_ops_root = pending_child.ops_root();
3197
3198            db.apply_batch(parent).await.unwrap();
3199            db.commit().await.unwrap();
3200
3201            let committed_child = db
3202                .new_batch()
3203                .write(key_a, Some(colliding_digest(0xDD, 1)))
3204                .write(key_b, Some(colliding_digest(0xDD, 0)))
3205                .merkleize(&db, None)
3206                .await
3207                .unwrap();
3208
3209            assert_eq!(pending_root, committed_child.root());
3210            assert_eq!(pending_ops_root, committed_child.ops_root());
3211
3212            // Apply pending child onto the committed parent
3213            // and compare the applied wrapper roots with the committed-path child roots.
3214            db.apply_batch(pending_child).await.unwrap();
3215            assert_eq!(db.root(), committed_child.root());
3216            assert_eq!(db.ops_root(), committed_child.ops_root());
3217
3218            db.destroy().await.unwrap();
3219        });
3220    }
3221
3222    /// Applying without `commit()` publishes in memory but is not recovered after reopen.
3223    #[test_traced("INFO")]
3224    fn test_current_batch_apply_requires_commit_for_recovery() {
3225        let executor = deterministic::Runner::default();
3226        executor.start(|context| async move {
3227            let partition = "apply_requires_commit";
3228            let ctx = context.child("db");
3229            let mut db: UnorderedVariableDb = UnorderedVariableDb::init(
3230                ctx.child("storage"),
3231                variable_config::<OneCap>(partition, &ctx),
3232            )
3233            .await
3234            .unwrap();
3235
3236            let committed_root = db.root();
3237
3238            let merkleized = db
3239                .new_batch()
3240                .write(key(0), Some(val(0)))
3241                .merkleize(&db, None)
3242                .await
3243                .unwrap();
3244            db.apply_batch(merkleized).await.unwrap();
3245
3246            assert_eq!(db.get(&key(0)).await.unwrap(), Some(val(0)));
3247
3248            drop(db);
3249
3250            let reopened: UnorderedVariableDb = UnorderedVariableDb::init(
3251                context.child("reopen"),
3252                variable_config::<OneCap>(partition, &context),
3253            )
3254            .await
3255            .unwrap();
3256            assert_eq!(reopened.root(), committed_root);
3257            assert_eq!(reopened.get(&key(0)).await.unwrap(), None);
3258
3259            reopened.destroy().await.unwrap();
3260        });
3261    }
3262
3263    /// One-stage pipelining lets the next batch be built while the prior batch commits.
3264    #[test_traced("INFO")]
3265    fn test_current_batch_single_stage_pipeline() {
3266        let executor = deterministic::Runner::default();
3267        executor.start(|context| async move {
3268            let ctx = context.child("db");
3269            let mut db: UnorderedVariableDb = UnorderedVariableDb::init(
3270                ctx.child("storage"),
3271                variable_config::<OneCap>("pipe", &ctx),
3272            )
3273            .await
3274            .unwrap();
3275
3276            let mut batch = db.new_batch();
3277            batch = batch.write(key(0), Some(val(0)));
3278            let parent_merkleized = batch.merkleize(&db, None).await.unwrap();
3279            db.apply_batch(parent_merkleized).await.unwrap();
3280
3281            let child_merkleized = {
3282                assert_eq!(db.get(&key(0)).await.unwrap(), Some(val(0)));
3283                let mut child = db.new_batch();
3284                child = child.write(key(1), Some(val(1)));
3285                child.merkleize(&db, None).await.unwrap()
3286            };
3287            db.commit().await.unwrap();
3288
3289            db.apply_batch(child_merkleized).await.unwrap();
3290            db.commit().await.unwrap();
3291
3292            assert_eq!(db.get(&key(0)).await.unwrap(), Some(val(0)));
3293            assert_eq!(db.get(&key(1)).await.unwrap(), Some(val(1)));
3294
3295            db.destroy().await.unwrap();
3296        });
3297    }
3298
3299    /// Apply parent then child sequentially. Both keys
3300    /// present and canonical root matches a fresh single-batch build.
3301    #[test_traced("INFO")]
3302    fn test_current_sequential_commit() {
3303        let executor = deterministic::Runner::default();
3304        executor.start(|context| async move {
3305            let ctx = context.child("db");
3306            let mut db: UnorderedVariableDb = UnorderedVariableDb::init(
3307                ctx.child("storage"),
3308                variable_config::<OneCap>("ff", &ctx),
3309            )
3310            .await
3311            .unwrap();
3312
3313            // Parent batch: insert key(0).
3314            let parent_m = db
3315                .new_batch()
3316                .write(key(0), Some(val(0)))
3317                .merkleize(&db, None)
3318                .await
3319                .unwrap();
3320
3321            // Child batch on parent: insert key(1).
3322            let child_m = parent_m
3323                .new_batch::<Sha256>()
3324                .write(key(1), Some(val(1)))
3325                .merkleize(&db, None)
3326                .await
3327                .unwrap();
3328
3329            db.apply_batch(parent_m).await.unwrap();
3330            db.apply_batch(child_m).await.unwrap();
3331
3332            // Both keys present.
3333            assert_eq!(db.get(&key(0)).await.unwrap(), Some(val(0)));
3334            assert_eq!(db.get(&key(1)).await.unwrap(), Some(val(1)));
3335
3336            // Build the same result via two sequential plain batches in a fresh DB
3337            // and verify the roots match.
3338            let ctx2 = context.child("db").with_attribute("index", 2);
3339            let mut db2: UnorderedVariableDb = UnorderedVariableDb::init(
3340                ctx2.child("db"),
3341                variable_config::<OneCap>("ff2", &ctx2),
3342            )
3343            .await
3344            .unwrap();
3345            let m1 = db2
3346                .new_batch()
3347                .write(key(0), Some(val(0)))
3348                .merkleize(&db2, None)
3349                .await
3350                .unwrap();
3351            db2.apply_batch(m1).await.unwrap();
3352            let m2 = db2
3353                .new_batch()
3354                .write(key(1), Some(val(1)))
3355                .merkleize(&db2, None)
3356                .await
3357                .unwrap();
3358            db2.apply_batch(m2).await.unwrap();
3359
3360            assert_eq!(db.root(), db2.root());
3361
3362            db.destroy().await.unwrap();
3363            db2.destroy().await.unwrap();
3364        });
3365    }
3366
3367    /// to_batch() produces a MerkleizedBatch that can be used to chain further
3368    /// batches via new_batch().
3369    #[test_traced("INFO")]
3370    fn test_current_to_batch_then_chain() {
3371        let executor = deterministic::Runner::default();
3372        executor.start(|context| async move {
3373            let ctx = context.child("db");
3374            let mut db: UnorderedVariableDb = UnorderedVariableDb::init(
3375                ctx.child("storage"),
3376                variable_config::<OneCap>("tb", &ctx),
3377            )
3378            .await
3379            .unwrap();
3380
3381            // Apply an initial batch.
3382            let m = db
3383                .new_batch()
3384                .write(key(0), Some(val(0)))
3385                .merkleize(&db, None)
3386                .await
3387                .unwrap();
3388            db.apply_batch(m).await.unwrap();
3389
3390            // Get an owned batch from the committed state.
3391            let snapshot = db.to_batch();
3392            assert_eq!(snapshot.root(), db.root());
3393
3394            // Chain a child batch from the snapshot.
3395            let child = snapshot
3396                .new_batch::<Sha256>()
3397                .write(key(1), Some(val(1)))
3398                .merkleize(&db, None)
3399                .await
3400                .unwrap();
3401
3402            // The child's root should differ from the snapshot.
3403            assert_ne!(child.root(), snapshot.root());
3404
3405            // Apply child.
3406            db.apply_batch(child).await.unwrap();
3407            assert_eq!(db.get(&key(0)).await.unwrap(), Some(val(0)));
3408            assert_eq!(db.get(&key(1)).await.unwrap(), Some(val(1)));
3409
3410            db.destroy().await.unwrap();
3411        });
3412    }
3413
3414    /// A live batch (built off the committed state) must remain readable and applicable after
3415    /// [`Db::prune`] advances the shared bitmap's pruning boundary. Pruning only discards
3416    /// chunks for inactive bits (below the inactivity floor); the batch's own chain and
3417    /// overlays operate at or above the floor, so no reads should land in the pruned region.
3418    #[test_traced("INFO")]
3419    fn test_current_live_batch_safe_across_prune() {
3420        let executor = deterministic::Runner::default();
3421        executor.start(|context| async move {
3422            let ctx = context.child("db");
3423            let mut db: UnorderedVariableDb = UnorderedVariableDb::init(
3424                ctx.child("storage"),
3425                variable_config::<OneCap>("prune-live", &ctx),
3426            )
3427            .await
3428            .unwrap();
3429
3430            // Seed enough ops to span multiple bitmap chunks.
3431            let mut seed = db.new_batch();
3432            for i in 0u64..300 {
3433                seed = seed.write(key(i), Some(val(i)));
3434            }
3435            let seed_m = seed.merkleize(&db, None).await.unwrap();
3436            db.apply_batch(seed_m).await.unwrap();
3437            db.commit().await.unwrap();
3438
3439            // Overwrite keys 0..250 so the inactivity floor advances past chunk 0.
3440            let mut p = db.new_batch();
3441            for i in 0u64..250 {
3442                p = p.write(key(i), Some(val(i + 10_000)));
3443            }
3444            let p_m = p.merkleize(&db, None).await.unwrap();
3445            db.apply_batch(Arc::clone(&p_m)).await.unwrap();
3446            db.commit().await.unwrap();
3447
3448            // Build c off p_m; c is live and shares the committed bitmap via its chain.
3449            let c = p_m
3450                .new_batch::<Sha256>()
3451                .write(key(250), Some(val(99_999)))
3452                .merkleize(&db, None)
3453                .await
3454                .unwrap();
3455
3456            // Prune with c still alive. This advances pruned_chunks on the shared bitmap.
3457            db.prune(db.sync_boundary()).await.unwrap();
3458
3459            // Sanity: c's pending write is still readable via the any-layer diff chain.
3460            assert_eq!(c.get(&key(250), &db).await.unwrap(), Some(val(99_999)));
3461
3462            // The actual prune-interaction test: apply c after prune. apply_batch skips overlay
3463            // chunks below the current pruned boundary.
3464            db.apply_batch(c).await.unwrap();
3465            assert_eq!(db.get(&key(0)).await.unwrap(), Some(val(10_000)));
3466            assert_eq!(db.get(&key(250)).await.unwrap(), Some(val(99_999)));
3467
3468            db.destroy().await.unwrap();
3469        });
3470    }
3471
3472    /// Regression: extending a batch after it has been applied (building a child off the
3473    /// just-applied parent) must produce correct data.
3474    ///
3475    /// With the shared-bitmap `RwLock` design, applying `A` mutates the committed bitmap in
3476    /// place; reads through `A`'s chain after apply fall through to the committed bitmap (which
3477    /// now reflects `A`'s state), and `A`'s own overlays applied on top are consistent with
3478    /// committed. So `A.new_batch()` followed by merkleize + apply is the right-by-construction
3479    /// case, and this test locks it in.
3480    #[test_traced("INFO")]
3481    fn test_current_extend_applied_batch() {
3482        let executor = deterministic::Runner::default();
3483        executor.start(|context| async move {
3484            let ctx = context.child("db");
3485            let mut db: UnorderedVariableDb = UnorderedVariableDb::init(
3486                ctx.child("storage"),
3487                variable_config::<OneCap>("xtend", &ctx),
3488            )
3489            .await
3490            .unwrap();
3491
3492            // Apply A, retaining our Arc so we can extend it post-apply.
3493            let a = db
3494                .new_batch()
3495                .write(key(0), Some(val(0)))
3496                .merkleize(&db, None)
3497                .await
3498                .unwrap();
3499            db.apply_batch(Arc::clone(&a)).await.unwrap();
3500
3501            // Build B off A after A was applied. B's chain walks through A's layer and falls
3502            // through to the committed bitmap (now post-A). B's merkleize must read consistent
3503            // state from both sources.
3504            let b = a
3505                .new_batch::<Sha256>()
3506                .write(key(1), Some(val(1)))
3507                .merkleize(&db, None)
3508                .await
3509                .unwrap();
3510            db.apply_batch(b).await.unwrap();
3511
3512            assert_eq!(db.get(&key(0)).await.unwrap(), Some(val(0)));
3513            assert_eq!(db.get(&key(1)).await.unwrap(), Some(val(1)));
3514
3515            // Extend once more to lock in multi-generation behavior.
3516            let c = db
3517                .new_batch()
3518                .write(key(2), Some(val(2)))
3519                .merkleize(&db, None)
3520                .await
3521                .unwrap();
3522            db.apply_batch(c).await.unwrap();
3523
3524            assert_eq!(db.get(&key(0)).await.unwrap(), Some(val(0)));
3525            assert_eq!(db.get(&key(1)).await.unwrap(), Some(val(1)));
3526            assert_eq!(db.get(&key(2)).await.unwrap(), Some(val(2)));
3527
3528            db.destroy().await.unwrap();
3529        });
3530    }
3531
3532    /// Build a child batch from a still-live parent whose apply was followed by a prune, then
3533    /// merkleize and apply the child. The parent's `BitmapBatch` chain terminates in the shared
3534    /// committed bitmap, and `prune` mutates that bitmap's pruning boundary in place. When the
3535    /// child is constructed via `parent.new_batch()`, the internal `trim_committed` call must
3536    /// observe the advanced boundary and produce a correct child chain; merkleize and apply must
3537    /// then produce correct state for keys at and beyond the advanced floor.
3538    #[test_traced("INFO")]
3539    fn test_current_live_batch_child_after_prune() {
3540        let executor = deterministic::Runner::default();
3541        executor.start(|context| async move {
3542            let ctx = context.child("db");
3543            let mut db: UnorderedVariableDb = UnorderedVariableDb::init(
3544                ctx.child("storage"),
3545                variable_config::<OneCap>("child-after-prune", &ctx),
3546            )
3547            .await
3548            .unwrap();
3549
3550            // Seed enough ops to span multiple bitmap chunks.
3551            let mut seed = db.new_batch();
3552            for i in 0u64..300 {
3553                seed = seed.write(key(i), Some(val(i)));
3554            }
3555            let seed_m = seed.merkleize(&db, None).await.unwrap();
3556            db.apply_batch(seed_m).await.unwrap();
3557            db.commit().await.unwrap();
3558
3559            // Overwrite keys 0..250 so the inactivity floor advances past chunk 0.
3560            let mut a_batch = db.new_batch();
3561            for i in 0u64..250 {
3562                a_batch = a_batch.write(key(i), Some(val(i + 10_000)));
3563            }
3564            let a = a_batch.merkleize(&db, None).await.unwrap();
3565            db.apply_batch(Arc::clone(&a)).await.unwrap();
3566            db.commit().await.unwrap();
3567
3568            // Prune while `a` is still live. Mutates the shared bitmap's pruning boundary in place.
3569            db.prune(db.sync_boundary()).await.unwrap();
3570
3571            // Extend `a` into `b` AFTER the prune. Building `b` off `a` triggers
3572            // `trim_committed` on `a`'s chain, which must correctly see the advanced pruning
3573            // boundary on the shared bitmap.
3574            let b = a
3575                .new_batch::<Sha256>()
3576                .write(key(300), Some(val(300)))
3577                .merkleize(&db, None)
3578                .await
3579                .unwrap();
3580
3581            db.apply_batch(b).await.unwrap();
3582            assert_eq!(db.get(&key(0)).await.unwrap(), Some(val(10_000)));
3583            assert_eq!(db.get(&key(249)).await.unwrap(), Some(val(10_249)));
3584            assert_eq!(db.get(&key(300)).await.unwrap(), Some(val(300)));
3585
3586            db.destroy().await.unwrap();
3587        });
3588    }
3589
3590    /// Regression: applying a batch after its ancestor Arc is dropped (without
3591    /// committing) must still apply the ancestor's bitmap pushes/clears and
3592    /// snapshot diffs.
3593    #[test_traced("WARN")]
3594    fn test_current_apply_after_ancestor_dropped() {
3595        let executor = deterministic::Runner::default();
3596        executor.start(|context| async move {
3597            let ctx = context.child("db");
3598            let mut db: UnorderedVariableDb = UnorderedVariableDb::init(
3599                ctx.child("storage"),
3600                variable_config::<OneCap>("adrop", &ctx),
3601            )
3602            .await
3603            .unwrap();
3604
3605            // Chain: DB <- A <- B <- C
3606            let mut a = db.new_batch();
3607            for i in 0..3 {
3608                a = a.write(key(i), Some(val(i)));
3609            }
3610            let a_m = a.merkleize(&db, None).await.unwrap();
3611
3612            let mut b = a_m.new_batch::<Sha256>();
3613            for i in 3..6 {
3614                b = b.write(key(i), Some(val(i)));
3615            }
3616            let b_m = b.merkleize(&db, None).await.unwrap();
3617
3618            let mut c = b_m.new_batch::<Sha256>();
3619            for i in 6..9 {
3620                c = c.write(key(i), Some(val(i)));
3621            }
3622            let c_m = c.merkleize(&db, None).await.unwrap();
3623
3624            // Drop A and B without committing. Their Weak refs in C are now dead.
3625            drop(a_m);
3626            drop(b_m);
3627
3628            // Apply only the tip. This is !skip_ancestors (DB hasn't changed).
3629            db.apply_batch(c_m).await.unwrap();
3630            db.commit().await.unwrap();
3631
3632            // All nine keys must be accessible.
3633            for i in 0..9 {
3634                assert_eq!(
3635                    db.get(&key(i)).await.unwrap(),
3636                    Some(val(i)),
3637                    "key({i}) missing after apply_batch with dropped ancestors"
3638                );
3639            }
3640
3641            db.destroy().await.unwrap();
3642        });
3643    }
3644
3645    /// Regression: applying a 3-deep chain as a single batch must leave the
3646    /// bitmap in the same state as applying the same operations sequentially.
3647    /// This fails if ancestor bitmap pushes are concatenated in the wrong order
3648    /// (tip-to-root instead of root-to-tip), because Delete operations produce
3649    /// false bitmap bits, and wrong ordering puts the false at the wrong
3650    /// position. We detect this by building a NEW batch on top of the
3651    /// (possibly corrupted) bitmap and comparing its root against the
3652    /// sequential path.
3653    #[test_traced("WARN")]
3654    fn test_current_chain_bitmap_order_matches_sequential() {
3655        let executor = deterministic::Runner::default();
3656        executor.start(|context| async move {
3657            // -- Path 1: build a 3-deep chain and apply the tip directly. --
3658            let ctx1 = context.child("db").with_attribute("index", 1);
3659            let mut db1: UnorderedVariableDb = UnorderedVariableDb::init(
3660                ctx1.child("db"),
3661                variable_config::<OneCap>("ord1", &ctx1),
3662            )
3663            .await
3664            .unwrap();
3665
3666            // Seed some committed data so there's a base bitmap to clear.
3667            commit_writes_with_metadata(
3668                &mut db1,
3669                [(key(10), Some(val(10))), (key(11), Some(val(11)))],
3670                None,
3671            )
3672            .await;
3673
3674            // Chain: DB <- A <- B <- C
3675            // A: updates key(10) and DELETES key(11). The delete produces a
3676            //    false bitmap bit. If A's bits end up at B's positions (wrong
3677            //    order), the false bit lands at the wrong journal location.
3678            // B: updates key(12) and key(13). All true bits.
3679            // C: updates key(14). All true bits.
3680            let a = db1
3681                .new_batch()
3682                .write(key(10), Some(val(100)))
3683                .write(key(11), None) // DELETE
3684                .merkleize(&db1, None)
3685                .await
3686                .unwrap();
3687
3688            let b = a
3689                .new_batch::<Sha256>()
3690                .write(key(12), Some(val(120)))
3691                .write(key(13), Some(val(130)))
3692                .merkleize(&db1, None)
3693                .await
3694                .unwrap();
3695
3696            let c = b
3697                .new_batch::<Sha256>()
3698                .write(key(14), Some(val(140)))
3699                .merkleize(&db1, None)
3700                .await
3701                .unwrap();
3702
3703            db1.apply_batch(c).await.unwrap();
3704            db1.commit().await.unwrap();
3705
3706            // Build one more batch on top to exercise the bitmap state.
3707            let d1 = db1
3708                .new_batch()
3709                .write(key(20), Some(val(200)))
3710                .merkleize(&db1, None)
3711                .await
3712                .unwrap();
3713            let chain_then_d_root = d1.root();
3714
3715            // -- Path 2: apply the same operations sequentially. --
3716            let ctx2 = context.child("db").with_attribute("index", 2);
3717            let mut db2: UnorderedVariableDb = UnorderedVariableDb::init(
3718                ctx2.child("db"),
3719                variable_config::<OneCap>("ord2", &ctx2),
3720            )
3721            .await
3722            .unwrap();
3723
3724            commit_writes_with_metadata(
3725                &mut db2,
3726                [(key(10), Some(val(10))), (key(11), Some(val(11)))],
3727                None,
3728            )
3729            .await;
3730
3731            let a2 = db2
3732                .new_batch()
3733                .write(key(10), Some(val(100)))
3734                .write(key(11), None)
3735                .merkleize(&db2, None)
3736                .await
3737                .unwrap();
3738            db2.apply_batch(a2).await.unwrap();
3739            db2.commit().await.unwrap();
3740
3741            let b2 = db2
3742                .new_batch()
3743                .write(key(12), Some(val(120)))
3744                .write(key(13), Some(val(130)))
3745                .merkleize(&db2, None)
3746                .await
3747                .unwrap();
3748            db2.apply_batch(b2).await.unwrap();
3749            db2.commit().await.unwrap();
3750
3751            let c2 = db2
3752                .new_batch()
3753                .write(key(14), Some(val(140)))
3754                .merkleize(&db2, None)
3755                .await
3756                .unwrap();
3757            db2.apply_batch(c2).await.unwrap();
3758            db2.commit().await.unwrap();
3759
3760            let d2 = db2
3761                .new_batch()
3762                .write(key(20), Some(val(200)))
3763                .merkleize(&db2, None)
3764                .await
3765                .unwrap();
3766            let sequential_then_d_root = d2.root();
3767
3768            assert_eq!(
3769                chain_then_d_root, sequential_then_d_root,
3770                "batch D's root on top of chain-applied state must match sequential state"
3771            );
3772
3773            db1.destroy().await.unwrap();
3774            db2.destroy().await.unwrap();
3775        });
3776    }
3777
3778    /// Regression: C's diff entry has a stale `base_old_loc` (255) pointing into a chunk that
3779    /// was pruned after parent P was committed. `committed_locs` precedence in
3780    /// `any::Db::apply_batch` must override the stale value with P's rewrite location, so the
3781    /// `set_bit(false)` call targets P's (post-floor-raise) loc, not the pruned chunk.
3782    ///
3783    /// With N=32, CHUNK_SIZE_BITS=256. Seed places key(0) at loc 255 (end of chunk 0). P
3784    /// overwrites keys 1..254; P's floor-raise moves key(0) from 255 to a fresh loc above 255.
3785    /// C is built from P and writes key(0) again. After committing P and pruning chunk 0, C's
3786    /// pre-merkleize `base_old_loc=255` is no longer the right clear target — `committed_locs`
3787    /// substitutes P's rewrite loc instead. If that precedence path broke, apply would panic
3788    /// (`set_bit` on a pruned bit).
3789    #[test_traced("WARN")]
3790    fn test_current_stale_bitmap_clears_after_prune() {
3791        let executor = deterministic::Runner::default();
3792        executor.start(|context| async move {
3793            let ctx = context.child("db");
3794            let mut db: UnorderedVariableDb = UnorderedVariableDb::init(
3795                ctx.child("storage"),
3796                variable_config::<OneCap>("stale-clears", &ctx),
3797            )
3798            .await
3799            .unwrap();
3800
3801            // Seed: 255 keys in one batch. key(0) lands at loc 255 (chunk 0).
3802            let mut seed = db.new_batch();
3803            for i in 0u64..255 {
3804                seed = seed.write(key(i), Some(val(i)));
3805            }
3806            let seed_m = seed.merkleize(&db, None).await.unwrap();
3807            db.apply_batch(seed_m).await.unwrap();
3808            db.commit().await.unwrap();
3809
3810            // P: overwrite keys 1..254. Does NOT touch key(0), but P's floor
3811            // raise moves key(0) from 255, advancing the floor past chunk 0.
3812            let mut p = db.new_batch();
3813            for i in 1u64..255 {
3814                p = p.write(key(i), Some(val(i + 10000)));
3815            }
3816            let p_m = p.merkleize(&db, None).await.unwrap();
3817
3818            // C: built from P. Writes key(0). base_old_loc = 255 (chunk 0).
3819            let c_m = p_m
3820                .new_batch::<Sha256>()
3821                .write(key(0), Some(val(9999)))
3822                .merkleize(&db, None)
3823                .await
3824                .unwrap();
3825
3826            // Commit P, prune chunk 0, then apply C.
3827            db.apply_batch(p_m).await.unwrap();
3828            db.commit().await.unwrap();
3829
3830            let floor = *db.inactivity_floor_loc();
3831            assert!(floor >= 256, "floor must be past chunk 0: floor={floor}",);
3832
3833            db.prune(db.sync_boundary()).await.unwrap();
3834            db.apply_batch(c_m).await.unwrap();
3835
3836            db.destroy().await.unwrap();
3837        });
3838    }
3839
3840    /// Apply C (grandchild of A) after only A is committed. B's data (any-layer
3841    /// snapshot diff + current-layer bitmap) must still be applied.
3842    #[test_traced("INFO")]
3843    fn test_current_partial_ancestor_commit() {
3844        let executor = deterministic::Runner::default();
3845        executor.start(|context| async move {
3846            let ctx = context.child("db");
3847            let mut db: UnorderedVariableDb = UnorderedVariableDb::init(
3848                ctx.child("storage"),
3849                variable_config::<OneCap>("pac", &ctx),
3850            )
3851            .await
3852            .unwrap();
3853
3854            let a = db
3855                .new_batch()
3856                .write(key(0), Some(val(0)))
3857                .merkleize(&db, None)
3858                .await
3859                .unwrap();
3860            let b = a
3861                .new_batch::<Sha256>()
3862                .write(key(1), Some(val(1)))
3863                .merkleize(&db, None)
3864                .await
3865                .unwrap();
3866            let c = b
3867                .new_batch::<Sha256>()
3868                .write(key(2), Some(val(2)))
3869                .merkleize(&db, None)
3870                .await
3871                .unwrap();
3872
3873            let expected_root = c.root();
3874
3875            db.apply_batch(a).await.unwrap();
3876            db.apply_batch(c).await.unwrap();
3877
3878            assert_eq!(db.root(), expected_root);
3879            assert_eq!(db.get(&key(0)).await.unwrap(), Some(val(0)));
3880            assert_eq!(db.get(&key(1)).await.unwrap(), Some(val(1)));
3881            assert_eq!(db.get(&key(2)).await.unwrap(), Some(val(2)));
3882
3883            db.destroy().await.unwrap();
3884        });
3885    }
3886
3887    /// Regression: bitmap ancestor skip logic must correctly pair each ancestor's
3888    /// bitmap data with its batch_end. Requires a 3-ancestor chain (A->B->C->D)
3889    /// to expose ordering bugs.
3890    #[test_traced("INFO")]
3891    fn test_current_partial_ancestor_bitmap_ordering() {
3892        let executor = deterministic::Runner::default();
3893        executor.start(|context| async move {
3894            let ctx = context.child("db");
3895            let mut db: UnorderedVariableDb = UnorderedVariableDb::init(
3896                ctx.child("storage"),
3897                variable_config::<OneCap>("bmo", &ctx),
3898            )
3899            .await
3900            .unwrap();
3901
3902            // Build A -> B -> C -> D. Each writes a distinct key.
3903            let a = db
3904                .new_batch()
3905                .write(key(0), Some(val(0)))
3906                .merkleize(&db, None)
3907                .await
3908                .unwrap();
3909            let b = a
3910                .new_batch::<Sha256>()
3911                .write(key(1), Some(val(1)))
3912                .merkleize(&db, None)
3913                .await
3914                .unwrap();
3915            let c = b
3916                .new_batch::<Sha256>()
3917                .write(key(2), Some(val(2)))
3918                .merkleize(&db, None)
3919                .await
3920                .unwrap();
3921            let d = c
3922                .new_batch::<Sha256>()
3923                .write(key(3), Some(val(3)))
3924                .merkleize(&db, None)
3925                .await
3926                .unwrap();
3927
3928            // Apply A only, then apply D (B and C uncommitted).
3929            // D has 3 ancestors: [C, B, A] (parent-first) with batch_ends [C.total, B.total, A.total].
3930            // Bitmap ancestors are also parent-first: [C, B, A].
3931            db.apply_batch(a).await.unwrap();
3932            db.apply_batch(d.clone()).await.unwrap();
3933
3934            // Build a new batch E on top of the current state. If the bitmap was
3935            // corrupted by the ordering bug (A's pushes duplicated or B/C's pushes
3936            // missing), merkleize will compute a different root than a reference
3937            // that applied all ancestors sequentially.
3938            let e = db
3939                .new_batch()
3940                .write(key(4), Some(val(4)))
3941                .merkleize(&db, None)
3942                .await
3943                .unwrap();
3944            db.apply_batch(e).await.unwrap();
3945
3946            // Reference: apply all five sequentially.
3947            let ref_ctx = context.child("ref");
3948            let mut ref_db: UnorderedVariableDb = UnorderedVariableDb::init(
3949                ref_ctx.child("db"),
3950                variable_config::<OneCap>("bmo_ref", &ref_ctx),
3951            )
3952            .await
3953            .unwrap();
3954            for i in 0..5 {
3955                let batch = ref_db
3956                    .new_batch()
3957                    .write(key(i), Some(val(i)))
3958                    .merkleize(&ref_db, None)
3959                    .await
3960                    .unwrap();
3961                ref_db.apply_batch(batch).await.unwrap();
3962            }
3963
3964            assert_eq!(
3965                db.root(),
3966                ref_db.root(),
3967                "root mismatch: bitmap ordering bug"
3968            );
3969
3970            db.destroy().await.unwrap();
3971            ref_db.destroy().await.unwrap();
3972        });
3973    }
3974
3975    /// Regression: the bitmap chunks produced by the speculative `BitmapBatch` chain during
3976    /// merkleize must equal the bytes that `any::Db::apply_batch` writes via diff-driven
3977    /// updates. `current::Db::apply_batch` relies on this equivalence to install the precomputed
3978    /// `batch.grafted` against the now-current bitmap.
3979    ///
3980    /// The workload spans multiple bitmap chunks and exercises:
3981    /// - parent/child same-key overwrite (`committed_locs` precedence path),
3982    /// - parent-create then child-delete (uncommitted-ancestor precedence),
3983    /// - mixed deletes and overwrites in different chunks (clear-bit + set-bit paths).
3984    #[test_traced("INFO")]
3985    fn test_current_apply_chunks_match_speculative_chunks() {
3986        const N: usize = 32;
3987        const CHUNK_SIZE_BITS: u64 = commonware_utils::bitmap::Prunable::<N>::CHUNK_SIZE_BITS;
3988        // Seed enough keys to cross at least one chunk boundary. Each batch also produces a
3989        // CommitFloor op, so the bitmap grows past the user-visible key count.
3990        const SEED_KEYS: u64 = CHUNK_SIZE_BITS + 50;
3991
3992        let executor = deterministic::Runner::default();
3993        executor.start(|context| async move {
3994            let ctx = context.child("db");
3995            let mut db: UnorderedVariableDb = UnorderedVariableDb::init(
3996                ctx.child("storage"),
3997                variable_config::<OneCap>("spec_eq", &ctx),
3998            )
3999            .await
4000            .unwrap();
4001
4002            // Seed all keys in one committed batch.
4003            let seed = (0..SEED_KEYS).fold(db.new_batch(), |b, i| b.write(key(i), Some(val(i))));
4004            let seed = seed.merkleize(&db, None).await.unwrap();
4005            db.apply_batch(seed).await.unwrap();
4006            db.commit().await.unwrap();
4007
4008            // Setup sanity: the committed bitmap spans at least two chunks.
4009            assert!(
4010                Readable::<N>::len(db.any.bitmap.as_ref()) > CHUNK_SIZE_BITS,
4011                "setup must cross a chunk boundary",
4012            );
4013
4014            // Parent (uncommitted): overwrites + delete + creates spread across the bitmap.
4015            let parent = db
4016                .new_batch()
4017                .write(key(10), Some(val(110))) // overwrite (low chunk)
4018                .write(key(50), None) // delete (low chunk)
4019                .write(key(CHUNK_SIZE_BITS + 5), Some(val(120))) // overwrite (high chunk)
4020                .write(key(SEED_KEYS), Some(val(130))) // create new key
4021                .write(key(SEED_KEYS + 1), Some(val(131))) // create new key
4022                .merkleize(&db, None)
4023                .await
4024                .unwrap();
4025
4026            // Child (uncommitted, descendant of parent):
4027            //   - same-key overwrite of parent's key(10)        -> committed_locs precedence
4028            //   - delete of parent's just-created key(SEED_KEYS) -> uncommitted-create-child-delete
4029            //   - additional delete + overwrite in mixed chunks -> set-bit + clear-bit coverage
4030            let child = parent
4031                .new_batch::<Sha256>()
4032                .write(key(10), Some(val(210)))
4033                .write(key(SEED_KEYS), None)
4034                .write(key(75), None)
4035                .write(key(CHUNK_SIZE_BITS + 30), Some(val(220)))
4036                .merkleize(&db, None)
4037                .await
4038                .unwrap();
4039
4040            // Snapshot every chunk in the speculative `BitmapBatch` chain (read through child).
4041            let speculative_chunks: Vec<[u8; N]> = {
4042                let len = Readable::<N>::len(&child.bitmap);
4043                let chunk_count = len.div_ceil(CHUNK_SIZE_BITS) as usize;
4044                (0..chunk_count)
4045                    .map(|idx| Readable::<N>::get_chunk(&child.bitmap, idx))
4046                    .collect()
4047            };
4048            // Setup sanity: speculative state spans at least two chunks.
4049            assert!(speculative_chunks.len() >= 2);
4050
4051            // Apply child (commits parent + child) and re-read every chunk from the committed
4052            // bitmap. The two views must be byte-identical; otherwise the precomputed
4053            // `batch.canonical_root` is no longer valid against the post-apply state.
4054            db.apply_batch(child).await.unwrap();
4055            let committed_chunks: Vec<[u8; N]> = {
4056                let len = Readable::<N>::len(db.any.bitmap.as_ref());
4057                let chunk_count = len.div_ceil(CHUNK_SIZE_BITS) as usize;
4058                (0..chunk_count)
4059                    .map(|idx| Readable::<N>::get_chunk(db.any.bitmap.as_ref(), idx))
4060                    .collect()
4061            };
4062
4063            assert_eq!(
4064                speculative_chunks, committed_chunks,
4065                "speculative chunks must equal post-apply committed chunks across all chunks",
4066            );
4067
4068            db.destroy().await.unwrap();
4069        });
4070    }
4071
4072    /// Regression: `ops_historical_proof` must verify with QMDB's ops-tree hasher configuration.
4073    #[test_traced("INFO")]
4074    fn test_current_mmb_ops_historical_proof_verifies_with_backward_bagging() {
4075        let executor = deterministic::Runner::default();
4076        executor.start(|context| async move {
4077            let ctx = context.child("db");
4078            let mut db: UnorderedFixedMmbDb = UnorderedFixedMmbDb::init(
4079                ctx.child("storage"),
4080                fixed_config::<OneCap>("mmb-ops-proof", &ctx),
4081            )
4082            .await
4083            .unwrap();
4084
4085            // Apply a batch and commit so an ops historical proof exists.
4086            let writes: Vec<(Digest, Option<Digest>)> =
4087                (0u64..16).map(|i| (key(i), Some(val(i)))).collect();
4088            commit_writes(&mut db, writes).await.unwrap();
4089
4090            let ops_root = db.ops_root();
4091            let historical_size = db.bounds().end;
4092            let (proof, ops) = db
4093                .ops_historical_proof(historical_size, Location::new(0), NZU64!(32))
4094                .await
4095                .unwrap();
4096
4097            // Verifies under the QMDB ops-tree hasher configuration.
4098            assert!(verify_proof::<Sha256, _, _>(
4099                &proof,
4100                Location::new(0),
4101                &ops,
4102                &ops_root
4103            ));
4104
4105            db.destroy().await.unwrap();
4106        });
4107    }
4108}