Skip to main content

commonware_storage/qmdb/any/
mod.rs

1//! An _Any_ authenticated database provides succinct proofs of any value ever associated with a
2//! key.
3//!
4//! The specific variants provided within this module include:
5//! - Unordered: The database does not maintain or require any ordering over the key space.
6//!   - Fixed-size values
7//!   - Variable-size values
8//! - Ordered: The database maintains a total order over active keys.
9//!   - Fixed-size values
10//!   - Variable-size values
11//!
12//! # Examples
13//!
14//! ```ignore
15//! // 1. Create a batch and apply it.
16//! let batch = db.new_batch()
17//!     .write(key, Some(value))    // upsert
18//!     .write(other_key, None)     // delete
19//!     .merkleize(&db, None).await?;
20//! let root = batch.root();        // speculative root
21//! db.apply_batch(batch).await?;
22//! db.commit().await?;             // flush to disk
23//! ```
24//!
25//! ```ignore
26//! // 2. Fork two batches from the same parent. Apply one; the other is stale.
27//! let parent = db.new_batch().write(k1, Some(v1)).merkleize(&db, None).await?;
28//! let fork_a = parent.new_batch::<Sha256>().write(k2, Some(v2)).merkleize(&db, None).await?;
29//! let fork_b = parent.new_batch::<Sha256>().write(k3, Some(v3)).merkleize(&db, None).await?;
30//!
31//! db.apply_batch(fork_a).await?;                 // OK -- includes parent
32//! assert!(db.apply_batch(fork_b).await.is_err()); // StaleBatch
33//! ```
34//!
35//! ```ignore
36//! // 3. Chain two batches. Apply parent first, then child.
37//! let parent = db.new_batch().write(k1, Some(v1)).merkleize(&db, None).await?;
38//! let child = parent.new_batch::<Sha256>().write(k2, Some(v2)).merkleize(&db, None).await?;
39//!
40//! db.apply_batch(parent).await?;           // apply parent
41//! db.apply_batch(child).await?;            // ancestors skipped automatically
42//! db.commit().await?;
43//! ```
44//!
45//! ```ignore
46//! // 4. Chain two batches. Apply child directly (includes parent's changes).
47//! let parent = db.new_batch().write(k1, Some(v1)).merkleize(&db, None).await?;
48//! let child = parent.new_batch::<Sha256>().write(k2, Some(v2)).merkleize(&db, None).await?;
49//!
50//! db.apply_batch(child).await?;                  // OK -- includes parent
51//! assert!(db.apply_batch(parent).await.is_err()); // StaleBatch
52//! ```
53//!
54//! ```ignore
55//! // 5. Two independent chains. Commit the tail of one; the other chain is stale.
56//! let a1 = db.new_batch().write(k1, Some(v1)).merkleize(&db, None).await?;
57//! let a2 = a1.new_batch::<Sha256>().write(k2, Some(v2)).merkleize(&db, None).await?;
58//!
59//! let b1 = db.new_batch().write(k3, Some(v3)).merkleize(&db, None).await?;
60//! let b2 = b1.new_batch::<Sha256>().write(k4, Some(v4)).merkleize(&db, None).await?;
61//!
62//! db.apply_batch(a2).await?;                 // OK -- includes a1
63//! assert!(db.apply_batch(b2).await.is_err()); // StaleBatch
64//! ```
65
66use crate::{
67    index::Factory as IndexFactory,
68    journal::{
69        authenticated::Inner,
70        contiguous::{fixed::Config as FConfig, variable::Config as VConfig},
71    },
72    merkle::{full::Config as MerkleConfig, Family, Location},
73    qmdb::{
74        any::operation::{Operation, Update},
75        bitmap::Shared,
76        metrics::Metrics,
77        operation::Committable,
78        single_operation_root, ROOT_BAGGING,
79    },
80    translator::Translator,
81    Context,
82};
83use commonware_codec::{CodecShared, Encode};
84use commonware_cryptography::Hasher;
85use commonware_macros::boxed;
86use commonware_parallel::Strategy;
87use core::num::NonZeroUsize;
88use std::sync::Arc;
89use tracing::warn;
90
91pub mod batch;
92pub mod db;
93pub mod operation;
94#[cfg(any(test, feature = "test-traits"))]
95pub mod traits;
96pub mod value;
97pub use value::{FixedValue, ValueEncoding, VariableValue};
98pub mod ordered;
99pub(crate) mod sync;
100pub mod unordered;
101
102/// Compute the authenticated root of a newly initialized database without opening storage.
103///
104/// The initial commit never carries metadata, so this root always represents
105/// `CommitFloor(None, 0)`.
106pub fn initial_root<F, U, H>() -> H::Digest
107where
108    F: Family,
109    H: Hasher,
110    U: Update,
111    Operation<F, U>: Encode,
112{
113    single_operation_root::<F, H>(&Operation::<F, U>::CommitFloor(None, Location::new(0)))
114}
115
116pub(crate) const BITMAP_CHUNK_BYTES: usize = 64;
117
118/// Configuration for an `Any` authenticated db.
119#[derive(Clone)]
120pub struct Config<T: Translator, J, S: Strategy> {
121    /// Configuration for the Merkle structure backing the authenticated journal.
122    pub merkle_config: MerkleConfig<S>,
123
124    /// Configuration for the operations log journal.
125    pub journal_config: J,
126
127    /// The translator used by the compressed index.
128    pub translator: T,
129
130    /// Capacity (in entries) of the `(location -> key)` cache used during init to resolve snapshot
131    /// collisions without re-reading the log; `None` disables it.
132    pub init_cache_size: Option<NonZeroUsize>,
133}
134
135/// Configuration for an `Any` authenticated db with fixed-size values.
136pub type FixedConfig<T, S> = Config<T, FConfig, S>;
137
138/// Configuration for an `Any` authenticated db with variable-sized values.
139pub type VariableConfig<T, C, S> = Config<T, VConfig<C>, S>;
140
141/// Initialize an `Any` authenticated db from the given config.
142pub async fn init<F, E, U, H, T, I, J, S>(
143    context: E,
144    cfg: Config<T, J::Config, S>,
145) -> Result<db::Db<F, E, J, I, H, U, BITMAP_CHUNK_BYTES, S>, crate::qmdb::Error<F>>
146where
147    F: Family,
148    E: Context,
149    U: Update + Send + Sync,
150    H: Hasher,
151    T: Translator,
152    I: IndexFactory<T, Value = Location<F>>,
153    J: Inner<E, Item = Operation<F, U>>,
154    S: Strategy,
155    Operation<F, U>: Committable + CodecShared,
156{
157    init_with_bitmap::<F, E, U, H, T, I, J, S, BITMAP_CHUNK_BYTES>(context, cfg, None).await
158}
159
160/// Like [`init`] but accepts a pre-allocated bitmap (used by `current::Db`, which sizes pruned
161/// chunks from grafted metadata). `bitmap = None` allocates internally.
162#[boxed]
163pub(crate) async fn init_with_bitmap<F, E, U, H, T, I, J, S, const N: usize>(
164    context: E,
165    cfg: Config<T, J::Config, S>,
166    bitmap: Option<Arc<Shared<N>>>,
167) -> Result<db::Db<F, E, J, I, H, U, N, S>, crate::qmdb::Error<F>>
168where
169    F: Family,
170    E: Context,
171    U: Update + Send + Sync,
172    H: Hasher,
173    T: Translator,
174    I: IndexFactory<T, Value = Location<F>>,
175    J: Inner<E, Item = Operation<F, U>>,
176    S: Strategy,
177    Operation<F, U>: Committable + CodecShared,
178{
179    let mut log = J::init::<F, H, S>(
180        context.child("log"),
181        cfg.merkle_config,
182        cfg.journal_config,
183        Operation::is_commit,
184        ROOT_BAGGING,
185    )
186    .await?;
187
188    if log.size() == 0 {
189        warn!("Authenticated log is empty, initializing new db");
190        let commit_floor = Operation::CommitFloor(None, Location::new(0));
191        log.append(&commit_floor).await?;
192        log.sync().await?;
193    }
194
195    let index = I::new(context.child("index"), cfg.translator);
196    let metrics = Metrics::new(context);
197    db::Db::init_from_log(index, log, bitmap, cfg.init_cache_size, metrics).await
198}
199
200#[cfg(test)]
201// pub(crate) so qmdb/current can use the generic tests.
202pub(crate) mod test {
203    use super::*;
204    use crate::{
205        journal::contiguous::{fixed::Config as FConfig, variable::Config as VConfig},
206        qmdb::any::{FixedConfig, MerkleConfig, VariableConfig},
207        translator::OneCap,
208    };
209    use commonware_codec::{Codec, CodecShared};
210    use commonware_cryptography::{sha256::Digest, Hasher, Sha256};
211    use commonware_runtime::{
212        buffer::paged::CacheRef, deterministic::Context, BufferPooler, Supervisor as _,
213    };
214    use commonware_utils::{NZUsize, NZU16, NZU64};
215    use core::{future::Future, pin::Pin};
216    use std::{
217        collections::HashMap,
218        num::{NonZeroU16, NonZeroUsize},
219    };
220
221    pub(crate) fn colliding_digest(prefix: u8, suffix: u64) -> Digest {
222        let mut bytes = [0u8; 32];
223        bytes[0] = prefix;
224        bytes[24..].copy_from_slice(&suffix.to_be_bytes());
225        Digest::from(bytes)
226    }
227
228    // Janky page & cache sizes to exercise boundary conditions.
229    const PAGE_SIZE: NonZeroU16 = NZU16!(101);
230    const PAGE_CACHE_SIZE: NonZeroUsize = NZUsize!(11);
231
232    pub(crate) fn fixed_db_config<T: Translator + Default>(
233        suffix: &str,
234        pooler: &impl BufferPooler,
235    ) -> FixedConfig<T, Sequential> {
236        fixed_db_config_with_strategy(suffix, pooler, Sequential)
237    }
238
239    pub(crate) fn fixed_db_config_with_strategy<
240        T: Translator + Default,
241        S: commonware_parallel::Strategy,
242    >(
243        suffix: &str,
244        pooler: &impl BufferPooler,
245        strategy: S,
246    ) -> FixedConfig<T, S> {
247        let page_cache = CacheRef::from_pooler(pooler, PAGE_SIZE, PAGE_CACHE_SIZE);
248        FixedConfig {
249            merkle_config: MerkleConfig {
250                journal_partition: format!("journal-{suffix}"),
251                metadata_partition: format!("metadata-{suffix}"),
252                items_per_blob: NZU64!(11),
253                write_buffer: NZUsize!(1024),
254                strategy,
255                page_cache: page_cache.clone(),
256            },
257            journal_config: FConfig {
258                partition: format!("log-journal-{suffix}"),
259                items_per_blob: NZU64!(7),
260                page_cache,
261                write_buffer: NZUsize!(1024),
262            },
263            translator: T::default(),
264            init_cache_size: Some(NZUsize!(1024)),
265        }
266    }
267
268    pub(crate) fn variable_db_config<T: Translator + Default>(
269        suffix: &str,
270        pooler: &impl BufferPooler,
271    ) -> VariableConfig<T, ((), ()), Sequential> {
272        let page_cache = CacheRef::from_pooler(pooler, PAGE_SIZE, PAGE_CACHE_SIZE);
273        VariableConfig {
274            merkle_config: MerkleConfig {
275                journal_partition: format!("journal-{suffix}"),
276                metadata_partition: format!("metadata-{suffix}"),
277                items_per_blob: NZU64!(11),
278                write_buffer: NZUsize!(1024),
279                strategy: Sequential,
280                page_cache: page_cache.clone(),
281            },
282            journal_config: VConfig {
283                partition: format!("log-journal-{suffix}"),
284                items_per_section: NZU64!(7),
285                compression: None,
286                codec_config: ((), ()),
287                page_cache,
288                write_buffer: NZUsize!(1024),
289            },
290            translator: T::default(),
291            init_cache_size: Some(NZUsize!(1024)),
292        }
293    }
294
295    use crate::{
296        index::Unordered as UnorderedIndex,
297        journal::contiguous::Mutable,
298        merkle::mmr,
299        qmdb::any::{
300            db::Db as AnyDb,
301            operation::{update::Update as UpdateTrait, Operation as AnyOperation},
302            traits::{DbAny, Provable, UnmerkleizedBatch as _},
303        },
304    };
305
306    type Error = crate::qmdb::Error<mmr::Family>;
307    type Location = mmr::Location;
308
309    pub(crate) trait RewindableDb {
310        fn rewind_to_size(
311            &mut self,
312            size: Location,
313        ) -> impl Future<Output = Result<(), Error>> + Send;
314    }
315
316    impl<E, U, C, I, H, const N: usize, S> RewindableDb for AnyDb<mmr::Family, E, C, I, H, U, N, S>
317    where
318        E: crate::Context,
319        U: UpdateTrait,
320        C: Mutable<Item = AnyOperation<mmr::Family, U>>,
321        I: UnorderedIndex<Value = Location>,
322        H: Hasher,
323        AnyOperation<mmr::Family, U>: Codec,
324        S: Strategy,
325    {
326        async fn rewind_to_size(&mut self, size: Location) -> Result<(), Error> {
327            self.rewind(size).await?;
328            Ok(())
329        }
330    }
331
332    /// Test recovery on non-empty db.
333    pub(crate) async fn test_any_db_non_empty_recovery<F: Family, D, V: Clone + CodecShared>(
334        context: Context,
335        mut db: D,
336        reopen_db: impl Fn(Context) -> Pin<Box<dyn Future<Output = D> + Send>>,
337        make_value: impl Fn(u64) -> V,
338    ) where
339        D: DbAny<F, Key = Digest, Value = V, Digest = Digest>,
340    {
341        const ELEMENTS: u64 = 1000;
342
343        // Commit initial batch.
344        {
345            let mut batch = db.new_batch();
346            for i in 0u64..ELEMENTS {
347                let k = Sha256::hash(&i.to_be_bytes());
348                let v = make_value(i * 1000);
349                batch = batch.write(k, Some(v));
350            }
351            let merkleized = batch.merkleize(&db, None).await.unwrap();
352            db.apply_batch(merkleized).await.unwrap();
353        }
354        db.commit().await.unwrap();
355        db.prune(db.sync_boundary()).await.unwrap();
356        let root = db.root();
357        let op_count = db.size();
358        let inactivity_floor_loc = db.inactivity_floor_loc();
359
360        let db = reopen_db(context.child("reopen").with_attribute("index", 1)).await;
361        assert_eq!(db.size(), op_count);
362        assert_eq!(db.inactivity_floor_loc(), inactivity_floor_loc);
363        assert_eq!(db.root(), root);
364
365        // Write without applying (unapplied batch should be lost on reopen).
366        {
367            let mut batch = db.new_batch();
368            for i in 0u64..ELEMENTS {
369                let k = Sha256::hash(&i.to_be_bytes());
370                let v = make_value((i + 1) * 10000);
371                batch = batch.write(k, Some(v));
372            }
373            let _merkleized = batch.merkleize(&db, None).await.unwrap();
374        }
375        let db = reopen_db(context.child("reopen").with_attribute("index", 2)).await;
376        assert_eq!(db.size(), op_count);
377        assert_eq!(db.inactivity_floor_loc(), inactivity_floor_loc);
378        assert_eq!(db.root(), root);
379
380        // Write without applying again.
381        {
382            let mut batch = db.new_batch();
383            for i in 0u64..ELEMENTS {
384                let k = Sha256::hash(&i.to_be_bytes());
385                let v = make_value((i + 1) * 10000);
386                batch = batch.write(k, Some(v));
387            }
388            let _merkleized = batch.merkleize(&db, None).await.unwrap();
389        }
390        let db = reopen_db(context.child("reopen").with_attribute("index", 3)).await;
391        assert_eq!(db.size(), op_count);
392        assert_eq!(db.root(), root);
393
394        // Three rounds of unapplied batches.
395        for _ in 0..3 {
396            let mut batch = db.new_batch();
397            for i in 0u64..ELEMENTS {
398                let k = Sha256::hash(&i.to_be_bytes());
399                let v = make_value((i + 1) * 10000);
400                batch = batch.write(k, Some(v));
401            }
402            let _merkleized = batch.merkleize(&db, None).await.unwrap();
403        }
404        let mut db = reopen_db(context.child("reopen").with_attribute("index", 4)).await;
405        assert_eq!(db.size(), op_count);
406        assert_eq!(db.root(), root);
407
408        // Now actually commit a batch.
409        {
410            let mut batch = db.new_batch();
411            for i in 0u64..ELEMENTS {
412                let k = Sha256::hash(&i.to_be_bytes());
413                let v = make_value((i + 1) * 10000);
414                batch = batch.write(k, Some(v));
415            }
416            let merkleized = batch.merkleize(&db, None).await.unwrap();
417            db.apply_batch(merkleized).await.unwrap();
418        }
419        db.commit().await.unwrap();
420        let db = reopen_db(context.child("reopen").with_attribute("index", 5)).await;
421        assert!(db.size() > op_count);
422        assert_ne!(db.inactivity_floor_loc(), inactivity_floor_loc);
423        assert_ne!(db.root(), root);
424
425        db.destroy().await.unwrap();
426    }
427
428    /// Test recovery on empty db.
429    pub(crate) async fn test_any_db_empty_recovery<F: Family, D, V: Clone + CodecShared>(
430        context: Context,
431        db: D,
432        reopen_db: impl Fn(Context) -> Pin<Box<dyn Future<Output = D> + Send>>,
433        make_value: impl Fn(u64) -> V,
434    ) where
435        D: DbAny<F, Key = Digest, Value = V, Digest = Digest>,
436    {
437        let root = db.root();
438
439        let db = reopen_db(context.child("reopen").with_attribute("index", 1)).await;
440        assert_eq!(db.size(), 1);
441        assert_eq!(db.root(), root);
442
443        // Write without applying (unapplied batch should be lost on reopen).
444        {
445            let mut batch = db.new_batch();
446            for i in 0u64..1000 {
447                let k = Sha256::hash(&i.to_be_bytes());
448                let v = make_value((i + 1) * 10000);
449                batch = batch.write(k, Some(v));
450            }
451            let _merkleized = batch.merkleize(&db, None).await.unwrap();
452        }
453        let db = reopen_db(context.child("reopen").with_attribute("index", 2)).await;
454        assert_eq!(db.size(), 1);
455        assert_eq!(db.root(), root);
456
457        // Write without applying again.
458        {
459            let mut batch = db.new_batch();
460            for i in 0u64..1000 {
461                let k = Sha256::hash(&i.to_be_bytes());
462                let v = make_value((i + 1) * 10000);
463                batch = batch.write(k, Some(v));
464            }
465            let _merkleized = batch.merkleize(&db, None).await.unwrap();
466        }
467        drop(db);
468        let db = reopen_db(context.child("reopen").with_attribute("index", 3)).await;
469        assert_eq!(db.size(), 1);
470        assert_eq!(db.root(), root);
471
472        // Three rounds of unapplied batches.
473        for _ in 0..3 {
474            let mut batch = db.new_batch();
475            for i in 0u64..1000 {
476                let k = Sha256::hash(&i.to_be_bytes());
477                let v = make_value((i + 1) * 10000);
478                batch = batch.write(k, Some(v));
479            }
480            let _merkleized = batch.merkleize(&db, None).await.unwrap();
481        }
482        drop(db);
483        let mut db = reopen_db(context.child("reopen").with_attribute("index", 4)).await;
484        assert_eq!(db.size(), 1);
485        assert_eq!(db.root(), root);
486
487        // Now actually commit a batch.
488        {
489            let mut batch = db.new_batch();
490            for i in 0u64..1000 {
491                let k = Sha256::hash(&i.to_be_bytes());
492                let v = make_value((i + 1) * 10000);
493                batch = batch.write(k, Some(v));
494            }
495            let merkleized = batch.merkleize(&db, None).await.unwrap();
496            db.apply_batch(merkleized).await.unwrap();
497        }
498        db.commit().await.unwrap();
499        drop(db);
500        let db = reopen_db(context.child("reopen").with_attribute("index", 5)).await;
501        assert!(db.size() > 1);
502        assert_ne!(db.root(), root);
503
504        db.destroy().await.unwrap();
505    }
506
507    /// Test that a commit after an older sync boundary is recovered without another sync.
508    pub(crate) async fn test_any_db_commit_after_sync_recovery<F: Family, D, V>(
509        context: Context,
510        mut db: D,
511        reopen_db: impl Fn(Context) -> Pin<Box<dyn Future<Output = D> + Send>>,
512        make_value: impl Fn(u64) -> V,
513    ) where
514        D: DbAny<F, Key = Digest, Value = V, Digest = Digest>,
515        V: Clone + CodecShared + Eq + std::fmt::Debug,
516    {
517        let key0 = Sha256::hash(&0u64.to_be_bytes());
518        let key1 = Sha256::hash(&1u64.to_be_bytes());
519        let value0 = make_value(100);
520        let value1 = make_value(200);
521
522        // Establish a synced baseline so recovery starts before the later commit.
523        let merkleized = db
524            .new_batch()
525            .write(key0, Some(value0.clone()))
526            .merkleize(&db, None)
527            .await
528            .unwrap();
529        db.apply_batch(merkleized).await.unwrap();
530        db.commit().await.unwrap();
531        db.sync().await.unwrap();
532
533        // Commit a second batch without syncing; reopen must replay it from the journal.
534        let merkleized = db
535            .new_batch()
536            .write(key1, Some(value1.clone()))
537            .merkleize(&db, None)
538            .await
539            .unwrap();
540        db.apply_batch(merkleized).await.unwrap();
541        db.commit().await.unwrap();
542        let committed_root = db.root();
543        let committed_size = db.size();
544        drop(db);
545
546        let db = reopen_db(context.child("reopen").with_attribute("index", 1)).await;
547        assert_eq!(db.root(), committed_root);
548        assert_eq!(db.size(), committed_size);
549        assert_eq!(db.get(&key0).await.unwrap(), Some(value0));
550        assert_eq!(db.get(&key1).await.unwrap(), Some(value1));
551
552        db.destroy().await.unwrap();
553    }
554
555    /// Pruning to a floor advanced by an applied-but-uncommitted batch must not durably outrun
556    /// the last durable commit: after a crash, the recovered commit's floor would lie below the
557    /// pruned boundary and the database could never reopen.
558    pub(crate) async fn test_any_db_prune_after_unsynced_floor_recovery<
559        F: Family,
560        D,
561        V: Clone + CodecShared,
562    >(
563        context: Context,
564        mut db: D,
565        reopen_db: impl Fn(Context) -> Pin<Box<dyn Future<Output = D> + Send>>,
566        make_value: impl Fn(u64) -> V,
567    ) where
568        D: DbAny<F, Key = Digest, Value = V, Digest = Digest>,
569    {
570        const ELEMENTS: u64 = 1000;
571
572        // Establish a durable state whose last commit declares an early inactivity floor.
573        {
574            let mut batch = db.new_batch();
575            for i in 0u64..ELEMENTS {
576                let k = Sha256::hash(&i.to_be_bytes());
577                batch = batch.write(k, Some(make_value(i)));
578            }
579            let merkleized = batch.merkleize(&db, None).await.unwrap();
580            db.apply_batch(merkleized).await.unwrap();
581        }
582        db.commit().await.unwrap();
583        let durable_floor = db.inactivity_floor_loc();
584
585        // Apply (but do not commit) a batch that advances the in-memory floor well past the
586        // durable commit's floor.
587        {
588            let mut batch = db.new_batch();
589            for i in 0u64..ELEMENTS {
590                let k = Sha256::hash(&i.to_be_bytes());
591                batch = batch.write(k, Some(make_value(i + 1)));
592            }
593            let merkleized = batch.merkleize(&db, None).await.unwrap();
594            db.apply_batch(merkleized).await.unwrap();
595        }
596        let unsynced_floor = db.inactivity_floor_loc();
597        assert!(unsynced_floor > durable_floor);
598
599        // Prune to the in-memory floor, then crash before any further commit.
600        db.prune(db.sync_boundary()).await.unwrap();
601        let root = db.root();
602        let op_count = db.size();
603        drop(db);
604
605        // Reopening must succeed: pruning made the floor-declaring commit durable before the
606        // journal durably advanced its boundary past positions that commit still needs.
607        let db = reopen_db(context.child("reopen").with_attribute("index", 1)).await;
608        assert_eq!(db.size(), op_count);
609        assert_eq!(db.inactivity_floor_loc(), unsynced_floor);
610        assert_eq!(db.root(), root);
611
612        db.destroy().await.unwrap();
613    }
614
615    /// Test rewinding to a prior committed state and recovering that state after reopen.
616    pub(crate) async fn test_any_db_rewind_recovery<D, V>(
617        context: Context,
618        mut db: D,
619        reopen_db: impl Fn(Context) -> Pin<Box<dyn Future<Output = D> + Send>>,
620        make_value: impl Fn(u64) -> V,
621    ) where
622        D: DbAny<mmr::Family, Key = Digest, Value = V, Digest = Digest> + RewindableDb,
623        V: Clone + CodecShared + Eq + std::fmt::Debug,
624    {
625        let key0 = Sha256::hash(&0u64.to_be_bytes());
626        let key1 = Sha256::hash(&1u64.to_be_bytes());
627        let key2 = Sha256::hash(&2u64.to_be_bytes());
628        let initial_root = db.root();
629        let initial_size = db.size();
630        let initial_floor = db.inactivity_floor_loc();
631
632        // Empty-batch rewind on an otherwise empty DB should apply no snapshot undos.
633        let merkleized = db.new_batch().merkleize(&db, None).await.unwrap();
634        let empty_range = db.apply_batch(merkleized).await.unwrap();
635        db.commit().await.unwrap();
636        assert_eq!(empty_range.start, initial_size);
637        assert_eq!(db.size(), empty_range.end);
638        db.rewind_to_size(initial_size).await.unwrap();
639        assert_eq!(db.root(), initial_root);
640        assert_eq!(db.size(), initial_size);
641        assert_eq!(db.inactivity_floor_loc(), initial_floor);
642        assert_eq!(db.get_metadata().await.unwrap(), None);
643
644        let value0_a = make_value(10);
645        let value1_a = make_value(11);
646        let metadata_a = make_value(12);
647
648        let merkleized = db
649            .new_batch()
650            .write(key0, Some(value0_a.clone()))
651            .write(key1, Some(value1_a.clone()))
652            .merkleize(&db, Some(metadata_a.clone()))
653            .await
654            .unwrap();
655        let range_a = db.apply_batch(merkleized).await.unwrap();
656        db.commit().await.unwrap();
657
658        let root_a = db.root();
659        let size_a = db.size();
660        let floor_a = db.inactivity_floor_loc();
661        assert_eq!(size_a, range_a.end);
662
663        let value0_b = make_value(20);
664        let value2_b = make_value(21);
665        let metadata_b = make_value(22);
666
667        let merkleized = db
668            .new_batch()
669            .write(key0, Some(value0_b))
670            .write(key1, None)
671            .write(key2, Some(value2_b))
672            .merkleize(&db, Some(metadata_b))
673            .await
674            .unwrap();
675        let range_b = db.apply_batch(merkleized).await.unwrap();
676        db.commit().await.unwrap();
677        assert_eq!(range_b.start, size_a);
678        assert_ne!(db.root(), root_a);
679
680        let value0_c = make_value(30);
681        let value1_c = make_value(31);
682        let metadata_c = make_value(32);
683        let merkleized = db
684            .new_batch()
685            .write(key0, Some(value0_c))
686            .write(key1, Some(value1_c))
687            .write(key2, None)
688            .merkleize(&db, Some(metadata_c))
689            .await
690            .unwrap();
691        db.apply_batch(merkleized).await.unwrap();
692        db.commit().await.unwrap();
693
694        // Rewind across a tail where:
695        // - the same key (`key0`) was updated multiple times
696        // - `key1` was deleted then recreated (exercises net-zero active_keys_delta path)
697        db.rewind_to_size(size_a).await.unwrap();
698        assert_eq!(db.root(), root_a);
699        assert_eq!(db.size(), size_a);
700        assert_eq!(db.inactivity_floor_loc(), floor_a);
701        assert_eq!(db.get_metadata().await.unwrap(), Some(metadata_a.clone()));
702        assert_eq!(db.get(&key0).await.unwrap(), Some(value0_a));
703        assert_eq!(db.get(&key1).await.unwrap(), Some(value1_a));
704        assert_eq!(db.get(&key2).await.unwrap(), None);
705
706        db.commit().await.unwrap();
707        drop(db);
708        let mut db = reopen_db(context.child("reopen_after_rewind")).await;
709        assert_eq!(db.root(), root_a);
710        assert_eq!(db.size(), size_a);
711        assert_eq!(db.inactivity_floor_loc(), floor_a);
712        assert_eq!(db.get_metadata().await.unwrap(), Some(metadata_a));
713        assert_eq!(db.get(&key0).await.unwrap(), Some(make_value(10)));
714        assert_eq!(db.get(&key1).await.unwrap(), Some(make_value(11)));
715        assert_eq!(db.get(&key2).await.unwrap(), None);
716
717        // Fresh writes from the rewound tip should produce a correct new chain and persist
718        // across reopen.
719        let value2_d = make_value(40);
720        let metadata_d = make_value(41);
721        let merkleized = db
722            .new_batch()
723            .write(key2, Some(value2_d.clone()))
724            .merkleize(&db, Some(metadata_d.clone()))
725            .await
726            .unwrap();
727        db.apply_batch(merkleized).await.unwrap();
728        db.commit().await.unwrap();
729        assert_eq!(db.get_metadata().await.unwrap(), Some(metadata_d.clone()));
730        assert_eq!(db.get(&key0).await.unwrap(), Some(make_value(10)));
731        assert_eq!(db.get(&key1).await.unwrap(), Some(make_value(11)));
732        assert_eq!(db.get(&key2).await.unwrap(), Some(value2_d.clone()));
733
734        drop(db);
735        let mut db = reopen_db(context.child("reopen_after_rewind_new_writes")).await;
736        assert_eq!(db.get_metadata().await.unwrap(), Some(metadata_d));
737        assert_eq!(db.get(&key0).await.unwrap(), Some(make_value(10)));
738        assert_eq!(db.get(&key1).await.unwrap(), Some(make_value(11)));
739        assert_eq!(db.get(&key2).await.unwrap(), Some(value2_d));
740
741        // Rewind all the way to the initial commit boundary (`first_commit_loc + 1`).
742        db.rewind_to_size(initial_size).await.unwrap();
743        assert_eq!(db.root(), initial_root);
744        assert_eq!(db.size(), initial_size);
745        assert_eq!(db.inactivity_floor_loc(), initial_floor);
746        assert_eq!(db.get_metadata().await.unwrap(), None);
747        assert_eq!(db.get(&key0).await.unwrap(), None);
748        assert_eq!(db.get(&key1).await.unwrap(), None);
749        assert_eq!(db.get(&key2).await.unwrap(), None);
750
751        db.commit().await.unwrap();
752        drop(db);
753        let db = reopen_db(context.child("reopen_initial_boundary")).await;
754        assert_eq!(db.root(), initial_root);
755        assert_eq!(db.size(), initial_size);
756        assert_eq!(db.inactivity_floor_loc(), initial_floor);
757        assert_eq!(db.get_metadata().await.unwrap(), None);
758        assert_eq!(db.get(&key0).await.unwrap(), None);
759        assert_eq!(db.get(&key1).await.unwrap(), None);
760        assert_eq!(db.get(&key2).await.unwrap(), None);
761
762        db.destroy().await.unwrap();
763    }
764
765    /// Test that a large mixed workload can be authenticated and replayed correctly.
766    #[boxed]
767    pub(crate) async fn test_any_db_build_and_authenticate<D, V>(
768        context: Context,
769        mut db: D,
770        reopen_db: impl Fn(Context) -> Pin<Box<dyn Future<Output = D> + Send>>,
771        make_value: impl Fn(u64) -> V,
772    ) where
773        D: DbAny<mmr::Family, Key = Digest, Value = V, Digest = Digest> + Provable<mmr::Family>,
774        V: CodecShared + Clone + Eq + std::hash::Hash + std::fmt::Debug,
775        <D as Provable<mmr::Family>>::Operation: Codec,
776    {
777        use crate::qmdb::verify_proof;
778
779        const ELEMENTS: u64 = 1000;
780
781        let mut map = HashMap::<Digest, V>::default();
782        {
783            let mut batch = db.new_batch();
784            for i in 0u64..ELEMENTS {
785                let k = Sha256::hash(&i.to_be_bytes());
786                let v = make_value(i * 1000);
787                batch = batch.write(k, Some(v.clone()));
788                map.insert(k, v);
789            }
790
791            // Update every 3rd key.
792            for i in 0u64..ELEMENTS {
793                if i % 3 != 0 {
794                    continue;
795                }
796                let k = Sha256::hash(&i.to_be_bytes());
797                let v = make_value((i + 1) * 10000);
798                batch = batch.write(k, Some(v.clone()));
799                map.insert(k, v);
800            }
801
802            // Delete every 7th key.
803            for i in 0u64..ELEMENTS {
804                if i % 7 != 1 {
805                    continue;
806                }
807                let k = Sha256::hash(&i.to_be_bytes());
808                batch = batch.write(k, None);
809                map.remove(&k);
810            }
811
812            let merkleized = batch.merkleize(&db, None).await.unwrap();
813            db.apply_batch(merkleized).await.unwrap();
814        }
815        // Commit + sync with pruning raises inactivity floor.
816        db.sync().await.unwrap();
817        db.prune(db.sync_boundary()).await.unwrap();
818
819        // Drop & reopen and ensure state matches.
820        let root = db.root();
821        db.sync().await.unwrap();
822        drop(db);
823        let db = reopen_db(context.child("reopened")).await;
824        assert_eq!(root, db.root());
825
826        // State matches reference map.
827        for i in 0u64..ELEMENTS {
828            let k = Sha256::hash(&i.to_be_bytes());
829            if let Some(map_value) = map.get(&k) {
830                let Some(db_value) = db.get(&k).await.unwrap() else {
831                    panic!("key not found in db: {k}");
832                };
833                assert_eq!(*map_value, db_value);
834            } else {
835                assert!(db.get(&k).await.unwrap().is_none());
836            }
837        }
838        let bounds = db.bounds();
839        let inactivity_floor = db.inactivity_floor_loc();
840        for loc in *inactivity_floor..*bounds.end {
841            let loc = Location::new(loc);
842            let (proof, ops) = db.proof(loc, NZU64!(10)).await.unwrap();
843            assert!(verify_proof::<Sha256, _, _>(&proof, loc, &ops, &root));
844        }
845
846        db.destroy().await.unwrap();
847    }
848
849    /// Test that replaying multiple updates of the same key on startup preserves correct state.
850    pub(crate) async fn test_any_db_log_replay<
851        F: Family,
852        D,
853        V: Clone + CodecShared + PartialEq + std::fmt::Debug,
854    >(
855        context: Context,
856        mut db: D,
857        reopen_db: impl Fn(Context) -> Pin<Box<dyn Future<Output = D> + Send>>,
858        make_value: impl Fn(u64) -> V,
859    ) where
860        D: DbAny<F, Key = Digest, Value = V, Digest = Digest>,
861    {
862        // Update the same key many times within a single batch.
863        const UPDATES: u64 = 100;
864        let k = Sha256::hash(&UPDATES.to_be_bytes());
865        let mut last_value = None;
866        {
867            let mut batch = db.new_batch();
868            for i in 0u64..UPDATES {
869                let v = make_value(i * 1000);
870                last_value = Some(v.clone());
871                batch = batch.write(k, Some(v));
872            }
873            let merkleized = batch.merkleize(&db, None).await.unwrap();
874            db.apply_batch(merkleized).await.unwrap();
875        }
876        db.commit().await.unwrap();
877        let root = db.root();
878
879        // Reopen and verify the state is preserved correctly.
880        drop(db);
881        let db = reopen_db(context.child("reopened")).await;
882        assert_eq!(db.root(), root);
883        assert_eq!(db.get(&k).await.unwrap(), last_value);
884
885        db.destroy().await.unwrap();
886    }
887
888    /// Test that historical_proof returns correct proofs for past database states.
889    pub(crate) async fn test_any_db_historical_proof_basic<D, V: Clone + CodecShared>(
890        _context: Context,
891        mut db: D,
892        make_value: impl Fn(u64) -> V,
893    ) where
894        D: DbAny<mmr::Family, Key = Digest, Value = V, Digest = Digest> + Provable<mmr::Family>,
895        <D as Provable<mmr::Family>>::Operation: Codec + PartialEq + std::fmt::Debug,
896    {
897        use crate::qmdb::verify_proof;
898        use commonware_utils::NZU64;
899
900        // Add some operations
901        const OPS: u64 = 20;
902        {
903            let mut batch = db.new_batch();
904            for i in 0u64..OPS {
905                let k = Sha256::hash(&i.to_be_bytes());
906                let v = make_value(i * 1000);
907                batch = batch.write(k, Some(v));
908            }
909            let merkleized = batch.merkleize(&db, None).await.unwrap();
910            db.apply_batch(merkleized).await.unwrap();
911        }
912        let root_hash = db.root();
913        let original_op_count = db.size();
914
915        // Historical proof should match "regular" proof when historical size == current database size
916        let max_ops = NZU64!(10);
917        let start_loc = Location::new(5);
918        let (historical_proof, historical_ops) = db
919            .historical_proof(original_op_count, start_loc, max_ops)
920            .await
921            .unwrap();
922        let (regular_proof, regular_ops) = db.proof(start_loc, max_ops).await.unwrap();
923
924        assert_eq!(historical_proof.leaves, regular_proof.leaves);
925        assert_eq!(historical_proof.digests, regular_proof.digests);
926        assert_eq!(historical_ops, regular_ops);
927        assert!(verify_proof::<Sha256, _, _>(
928            &historical_proof,
929            start_loc,
930            &historical_ops,
931            &root_hash,
932        ));
933
934        // Add more operations to the database
935        {
936            let mut batch = db.new_batch();
937            for i in OPS..(OPS + 5) {
938                let k = Sha256::hash(&(i + 1000).to_be_bytes()); // different keys
939                let v = make_value(i * 1000);
940                batch = batch.write(k, Some(v));
941            }
942            let merkleized = batch.merkleize(&db, None).await.unwrap();
943            db.apply_batch(merkleized).await.unwrap();
944        }
945
946        // Historical proof should remain the same even though database has grown
947        let (historical_proof2, historical_ops2) = db
948            .historical_proof(original_op_count, start_loc, max_ops)
949            .await
950            .unwrap();
951        assert_eq!(historical_proof2.leaves, original_op_count);
952        assert_eq!(historical_proof2.digests, regular_proof.digests);
953        assert_eq!(historical_ops2, regular_ops);
954        assert!(verify_proof::<Sha256, _, _>(
955            &historical_proof2,
956            start_loc,
957            &historical_ops2,
958            &root_hash,
959        ));
960
961        db.destroy().await.unwrap();
962    }
963
964    /// Test that tampering with historical proofs causes verification to fail.
965    pub(crate) async fn test_any_db_historical_proof_invalid<D, V: Clone + CodecShared>(
966        _context: Context,
967        mut db: D,
968        make_value: impl Fn(u64) -> V,
969    ) where
970        D: DbAny<mmr::Family, Key = Digest, Value = V, Digest = Digest> + Provable<mmr::Family>,
971        <D as Provable<mmr::Family>>::Operation: Codec + PartialEq + std::fmt::Debug + Clone,
972    {
973        use crate::qmdb::verify_proof;
974        use commonware_utils::NZU64;
975
976        // Apply two single-write batches and capture the commit-boundary size after the
977        // first batch. `historical_proof` requires the historical size to land on a commit
978        // boundary when the db commits to an inactive peak boundary.
979        let mut historical_op_count = Location::new(0);
980        for i in 0u64..2 {
981            let k = Sha256::hash(&i.to_be_bytes());
982            let v = make_value(i * 1000);
983            let merkleized = db
984                .new_batch()
985                .write(k, Some(v))
986                .merkleize(&db, None)
987                .await
988                .unwrap();
989            db.apply_batch(merkleized).await.unwrap();
990            if i == 0 {
991                historical_op_count = db.bounds().end;
992            }
993        }
994
995        let expected_ops_len = (*historical_op_count - 1) as usize;
996        let (proof, ops) = db
997            .historical_proof(historical_op_count, Location::new(1), NZU64!(10))
998            .await
999            .unwrap();
1000        assert_eq!(proof.leaves, historical_op_count);
1001        assert_eq!(ops.len(), expected_ops_len);
1002
1003        // Changing the proof digests should cause verification to fail
1004        {
1005            let mut tampered_proof = proof.clone();
1006            tampered_proof.digests[0] = Sha256::hash(b"invalid");
1007            let root_hash = db.root();
1008            assert!(!verify_proof::<Sha256, _, _>(
1009                &tampered_proof,
1010                Location::new(1),
1011                &ops,
1012                &root_hash,
1013            ));
1014        }
1015
1016        // Appending an extra digest should cause verification to fail
1017        {
1018            let mut tampered_proof = proof.clone();
1019            tampered_proof.digests.push(Sha256::hash(b"invalid"));
1020            let root_hash = db.root();
1021            assert!(!verify_proof::<Sha256, _, _>(
1022                &tampered_proof,
1023                Location::new(1),
1024                &ops,
1025                &root_hash,
1026            ));
1027        }
1028
1029        // Changing the ops should cause verification to fail
1030        {
1031            let root_hash = db.root();
1032            let mut tampered_ops = ops.clone();
1033            // Swap first two ops if we have at least 2
1034            if tampered_ops.len() >= 2 {
1035                tampered_ops.swap(0, 1);
1036                assert!(!verify_proof::<Sha256, _, _>(
1037                    &proof,
1038                    Location::new(1),
1039                    &tampered_ops,
1040                    &root_hash,
1041                ));
1042            }
1043        }
1044
1045        // Appending an extra (duplicate) op should cause verification to fail
1046        {
1047            let root_hash = db.root();
1048            let mut tampered_ops = ops.clone();
1049            tampered_ops.push(tampered_ops[0].clone());
1050            assert!(!verify_proof::<Sha256, _, _>(
1051                &proof,
1052                Location::new(1),
1053                &tampered_ops,
1054                &root_hash,
1055            ));
1056        }
1057
1058        // Changing the start location should cause verification to fail
1059        {
1060            let root_hash = db.root();
1061            assert!(!verify_proof::<Sha256, _, _>(
1062                &proof,
1063                Location::new(2),
1064                &ops,
1065                &root_hash,
1066            ));
1067        }
1068
1069        // Changing the root digest should cause verification to fail
1070        {
1071            let invalid_root = Sha256::hash(b"invalid");
1072            assert!(!verify_proof::<Sha256, _, _>(
1073                &proof,
1074                Location::new(1),
1075                &ops,
1076                &invalid_root,
1077            ));
1078        }
1079
1080        // Changing the proof leaves count should cause verification to fail
1081        {
1082            let mut tampered_proof = proof.clone();
1083            tampered_proof.leaves = Location::new(100);
1084            let root_hash = db.root();
1085            assert!(!verify_proof::<Sha256, _, _>(
1086                &tampered_proof,
1087                Location::new(1),
1088                &ops,
1089                &root_hash,
1090            ));
1091        }
1092
1093        db.destroy().await.unwrap();
1094    }
1095
1096    /// Test historical_proof edge cases: singleton db, limited ops, min position.
1097    pub(crate) async fn test_any_db_historical_proof_edge_cases<D, V: Clone + CodecShared>(
1098        _context: Context,
1099        mut db: D,
1100        make_value: impl Fn(u64) -> V,
1101    ) where
1102        D: DbAny<mmr::Family, Key = Digest, Value = V, Digest = Digest> + Provable<mmr::Family>,
1103        <D as Provable<mmr::Family>>::Operation: Codec + PartialEq + std::fmt::Debug,
1104    {
1105        use commonware_utils::NZU64;
1106
1107        // Apply a sequence of single-write batches and record the commit-boundary size
1108        // reached after each. `historical_proof` requires the historical size to be a
1109        // commit boundary when the db commits to an inactive peak boundary, so we anchor each test on
1110        // one of the boundaries we recorded here rather than hardcoding sizes that depend
1111        // on internal floor-raising behavior.
1112        let initial_size = db.bounds().end;
1113        let mut boundaries = vec![initial_size];
1114        for i in 0u64..5 {
1115            let k = Sha256::hash(&i.to_be_bytes());
1116            let v = make_value(i * 1000);
1117            let merkleized = db
1118                .new_batch()
1119                .write(k, Some(v))
1120                .merkleize(&db, None)
1121                .await
1122                .unwrap();
1123            db.apply_batch(merkleized).await.unwrap();
1124            boundaries.push(db.bounds().end);
1125        }
1126
1127        // Singleton historical state: only the initial CommitFloor is visible.
1128        let singleton_size = boundaries[0];
1129        let (single_proof, single_ops) = db
1130            .historical_proof(singleton_size, Location::new(0), NZU64!(1))
1131            .await
1132            .unwrap();
1133        assert_eq!(single_proof.leaves, singleton_size);
1134        assert_eq!(single_ops.len(), 1);
1135
1136        // max_ops exceeds the ops remaining at this historical size, so the returned count
1137        // is capped at `historical_size - start_loc`. Anchor at the earliest post-batch
1138        // boundary that has at least 3 ops past `boundaries[1]`.
1139        let limited_size = boundaries[2];
1140        let limited_start = boundaries[1];
1141        let expected_limited = (*limited_size - *limited_start) as usize;
1142        assert!(expected_limited > 0);
1143        let (_limited_proof, limited_ops) = db
1144            .historical_proof(limited_size, limited_start, NZU64!(20))
1145            .await
1146            .unwrap();
1147        assert_eq!(limited_ops.len(), expected_limited);
1148
1149        // Standard historical proof anchored at an early commit boundary, requesting a
1150        // bounded number of ops within the historical range.
1151        let min_size = boundaries[2];
1152        let max_ops = NZU64!(3);
1153        let expected_min = core::cmp::min(max_ops.get(), *min_size - 1) as usize;
1154        let (min_proof, min_ops) = db
1155            .historical_proof(min_size, Location::new(1), max_ops)
1156            .await
1157            .unwrap();
1158        assert_eq!(min_proof.leaves, min_size);
1159        assert_eq!(min_ops.len(), expected_min);
1160
1161        db.destroy().await.unwrap();
1162    }
1163
1164    /// Test making multiple commits, one of which deletes a key from a previous commit.
1165    pub(crate) async fn test_any_db_multiple_commits_delete_replayed<F: Family, D, V>(
1166        context: Context,
1167        mut db: D,
1168        reopen_db: impl Fn(Context) -> Pin<Box<dyn Future<Output = D> + Send>>,
1169        make_value: impl Fn(u64) -> V,
1170    ) where
1171        D: DbAny<F, Key = Digest, Value = V, Digest = Digest>,
1172        V: Clone + CodecShared + Eq + std::fmt::Debug,
1173    {
1174        let mut map = HashMap::<Digest, V>::default();
1175        const ELEMENTS: u64 = 10;
1176        let metadata_value = make_value(42);
1177        let key_at = |j: u64, i: u64| Sha256::hash(&(j * 1000 + i).to_be_bytes());
1178        for j in 0u64..ELEMENTS {
1179            let mut batch = db.new_batch();
1180            for i in 0u64..ELEMENTS {
1181                let k = key_at(j, i);
1182                let v = make_value(i * 1000);
1183                batch = batch.write(k, Some(v.clone()));
1184                map.insert(k, v);
1185            }
1186            let merkleized = batch
1187                .merkleize(&db, Some(metadata_value.clone()))
1188                .await
1189                .unwrap();
1190            db.apply_batch(merkleized).await.unwrap();
1191            db.commit().await.unwrap();
1192        }
1193        assert_eq!(db.get_metadata().await.unwrap(), Some(metadata_value));
1194        let k = key_at(ELEMENTS - 1, ELEMENTS - 1);
1195
1196        let merkleized = db
1197            .new_batch()
1198            .write(k, None)
1199            .merkleize(&db, None)
1200            .await
1201            .unwrap();
1202        db.apply_batch(merkleized).await.unwrap();
1203        db.commit().await.unwrap();
1204        assert_eq!(db.get_metadata().await.unwrap(), None);
1205        assert!(db.get(&k).await.unwrap().is_none());
1206
1207        let root = db.root();
1208        drop(db);
1209        let db = reopen_db(context.child("reopened")).await;
1210        assert_eq!(root, db.root());
1211        assert_eq!(db.get_metadata().await.unwrap(), None);
1212        assert!(db.get(&k).await.unwrap().is_none());
1213
1214        db.destroy().await.unwrap();
1215    }
1216
1217    use crate::qmdb::any::{
1218        ordered::{fixed::Db as OrderedFixedDb, variable::Db as OrderedVariableDb},
1219        unordered::{fixed::Db as UnorderedFixedDb, variable::Db as UnorderedVariableDb},
1220    };
1221    use commonware_macros::{test_group, test_traced};
1222    use commonware_parallel::{Sequential, Strategy};
1223    use commonware_runtime::{deterministic, Runner as _};
1224
1225    // Type aliases for all 12 MMR variants (all use OneCap for collision coverage).
1226    type UnorderedFixed =
1227        UnorderedFixedDb<mmr::Family, Context, Digest, Digest, Sha256, OneCap, Sequential>;
1228    type UnorderedVariable =
1229        UnorderedVariableDb<mmr::Family, Context, Digest, Digest, Sha256, OneCap, Sequential>;
1230    type OrderedFixed =
1231        OrderedFixedDb<mmr::Family, Context, Digest, Digest, Sha256, OneCap, Sequential>;
1232    type OrderedVariable =
1233        OrderedVariableDb<mmr::Family, Context, Digest, Digest, Sha256, OneCap, Sequential>;
1234    type UnorderedFixedP1 = unordered::fixed::partitioned::Db<
1235        mmr::Family,
1236        Context,
1237        Digest,
1238        Digest,
1239        Sha256,
1240        OneCap,
1241        1,
1242        Sequential,
1243    >;
1244    type UnorderedVariableP1 = unordered::variable::partitioned::Db<
1245        mmr::Family,
1246        Context,
1247        Digest,
1248        Digest,
1249        Sha256,
1250        OneCap,
1251        1,
1252        Sequential,
1253    >;
1254    type OrderedFixedP1 = ordered::fixed::partitioned::Db<
1255        mmr::Family,
1256        Context,
1257        Digest,
1258        Digest,
1259        Sha256,
1260        OneCap,
1261        1,
1262        Sequential,
1263    >;
1264    type OrderedVariableP1 = ordered::variable::partitioned::Db<
1265        mmr::Family,
1266        Context,
1267        Digest,
1268        Digest,
1269        Sha256,
1270        OneCap,
1271        1,
1272        Sequential,
1273    >;
1274    type UnorderedFixedP2 = unordered::fixed::partitioned::Db<
1275        mmr::Family,
1276        Context,
1277        Digest,
1278        Digest,
1279        Sha256,
1280        OneCap,
1281        2,
1282        Sequential,
1283    >;
1284    type UnorderedVariableP2 = unordered::variable::partitioned::Db<
1285        mmr::Family,
1286        Context,
1287        Digest,
1288        Digest,
1289        Sha256,
1290        OneCap,
1291        2,
1292        Sequential,
1293    >;
1294    type OrderedFixedP2 = ordered::fixed::partitioned::Db<
1295        mmr::Family,
1296        Context,
1297        Digest,
1298        Digest,
1299        Sha256,
1300        OneCap,
1301        2,
1302        Sequential,
1303    >;
1304    type OrderedVariableP2 = ordered::variable::partitioned::Db<
1305        mmr::Family,
1306        Context,
1307        Digest,
1308        Digest,
1309        Sha256,
1310        OneCap,
1311        2,
1312        Sequential,
1313    >;
1314
1315    // MMB type aliases for with_all_variants.
1316    mod mmb_types {
1317        use super::*;
1318        use crate::{
1319            index::{ordered::Index as OrderedIndex, unordered::Index as UnorderedIndex},
1320            journal::contiguous::{fixed::Journal as FJournal, variable::Journal as VJournal},
1321            merkle::{mmb, Location},
1322            qmdb::any::{
1323                operation::{update, Operation},
1324                value::{FixedEncoding, VariableEncoding},
1325            },
1326        };
1327
1328        type MmbLocation = Location<mmb::Family>;
1329
1330        pub type MmbUnorderedFixed = super::super::db::Db<
1331            mmb::Family,
1332            Context,
1333            FJournal<
1334                Context,
1335                Operation<mmb::Family, update::Unordered<Digest, FixedEncoding<Digest>>>,
1336            >,
1337            UnorderedIndex<OneCap, MmbLocation>,
1338            Sha256,
1339            update::Unordered<Digest, FixedEncoding<Digest>>,
1340            { crate::qmdb::any::BITMAP_CHUNK_BYTES },
1341            Sequential,
1342        >;
1343
1344        pub type MmbUnorderedVariable = super::super::db::Db<
1345            mmb::Family,
1346            Context,
1347            VJournal<
1348                Context,
1349                Operation<mmb::Family, update::Unordered<Digest, VariableEncoding<Digest>>>,
1350            >,
1351            UnorderedIndex<OneCap, MmbLocation>,
1352            Sha256,
1353            update::Unordered<Digest, VariableEncoding<Digest>>,
1354            { crate::qmdb::any::BITMAP_CHUNK_BYTES },
1355            Sequential,
1356        >;
1357
1358        pub type MmbOrderedFixed = super::super::db::Db<
1359            mmb::Family,
1360            Context,
1361            FJournal<
1362                Context,
1363                Operation<mmb::Family, update::Ordered<Digest, FixedEncoding<Digest>>>,
1364            >,
1365            OrderedIndex<OneCap, MmbLocation>,
1366            Sha256,
1367            update::Ordered<Digest, FixedEncoding<Digest>>,
1368            { crate::qmdb::any::BITMAP_CHUNK_BYTES },
1369            Sequential,
1370        >;
1371
1372        pub type MmbOrderedVariable = super::super::db::Db<
1373            mmb::Family,
1374            Context,
1375            VJournal<
1376                Context,
1377                Operation<mmb::Family, update::Ordered<Digest, VariableEncoding<Digest>>>,
1378            >,
1379            OrderedIndex<OneCap, MmbLocation>,
1380            Sha256,
1381            update::Ordered<Digest, VariableEncoding<Digest>>,
1382            { crate::qmdb::any::BITMAP_CHUNK_BYTES },
1383            Sequential,
1384        >;
1385    }
1386    use mmb_types::*;
1387
1388    #[inline]
1389    fn to_digest(i: u64) -> Digest {
1390        Sha256::hash(&i.to_be_bytes())
1391    }
1392
1393    // Defines MMR-only variants (for tests that require mmr::Family, e.g. proof verification).
1394    macro_rules! with_mmr_variants {
1395        ($cb:ident!($($args:tt)*)) => {
1396            $cb!($($args)*, uf, UnorderedFixed, mmr::Family, fixed_db_config);
1397            $cb!($($args)*, uv, UnorderedVariable, mmr::Family, variable_db_config);
1398            $cb!($($args)*, of, OrderedFixed, mmr::Family, fixed_db_config);
1399            $cb!($($args)*, ov, OrderedVariable, mmr::Family, variable_db_config);
1400            $cb!($($args)*, ufp1, UnorderedFixedP1, mmr::Family, fixed_db_config);
1401            $cb!($($args)*, uvp1, UnorderedVariableP1, mmr::Family, variable_db_config);
1402            $cb!($($args)*, ofp1, OrderedFixedP1, mmr::Family, fixed_db_config);
1403            $cb!($($args)*, ovp1, OrderedVariableP1, mmr::Family, variable_db_config);
1404            $cb!($($args)*, ufp2, UnorderedFixedP2, mmr::Family, fixed_db_config);
1405            $cb!($($args)*, uvp2, UnorderedVariableP2, mmr::Family, variable_db_config);
1406            $cb!($($args)*, ofp2, OrderedFixedP2, mmr::Family, fixed_db_config);
1407            $cb!($($args)*, ovp2, OrderedVariableP2, mmr::Family, variable_db_config);
1408        };
1409    }
1410
1411    // Defines all variants (MMR + MMB). Calls $cb!($($args)*, $label, $type, $family, $config) for each.
1412    macro_rules! with_all_variants {
1413        ($cb:ident!($($args:tt)*)) => {
1414            $cb!($($args)*, uf, UnorderedFixed, mmr::Family, fixed_db_config);
1415            $cb!($($args)*, uv, UnorderedVariable, mmr::Family, variable_db_config);
1416            $cb!($($args)*, of, OrderedFixed, mmr::Family, fixed_db_config);
1417            $cb!($($args)*, ov, OrderedVariable, mmr::Family, variable_db_config);
1418            $cb!($($args)*, ufp1, UnorderedFixedP1, mmr::Family, fixed_db_config);
1419            $cb!($($args)*, uvp1, UnorderedVariableP1, mmr::Family, variable_db_config);
1420            $cb!($($args)*, ofp1, OrderedFixedP1, mmr::Family, fixed_db_config);
1421            $cb!($($args)*, ovp1, OrderedVariableP1, mmr::Family, variable_db_config);
1422            $cb!($($args)*, ufp2, UnorderedFixedP2, mmr::Family, fixed_db_config);
1423            $cb!($($args)*, uvp2, UnorderedVariableP2, mmr::Family, variable_db_config);
1424            $cb!($($args)*, ofp2, OrderedFixedP2, mmr::Family, fixed_db_config);
1425            $cb!($($args)*, ovp2, OrderedVariableP2, mmr::Family, variable_db_config);
1426            $cb!($($args)*, uf_mmb, MmbUnorderedFixed, mmb::Family, fixed_db_config);
1427            $cb!($($args)*, uv_mmb, MmbUnorderedVariable, mmb::Family, variable_db_config);
1428            $cb!($($args)*, of_mmb, MmbOrderedFixed, mmb::Family, fixed_db_config);
1429            $cb!($($args)*, ov_mmb, MmbOrderedVariable, mmb::Family, variable_db_config);
1430        };
1431    }
1432
1433    // Emit one `#[test_group("slow")] #[test_traced]` test per variant, named
1434    // `<f>_<variant_label>`. `with_reopen` hands the test a db plus a reopen
1435    // closure, `with_make_value` hands it just the db.
1436    macro_rules! test_for_variant {
1437        (with_reopen: $f:ident, $traced:literal, $l:ident, $db:ty, $family:ty, $cfg:ident) => {
1438            paste::paste! {
1439                #[test_group("slow")]
1440                #[test_traced($traced)]
1441                fn [<$f _ $l>]() {
1442                    let executor = deterministic::Runner::default();
1443                    executor.start(|context| async move {
1444                        let ctx = context.child(stringify!($l));
1445                        let db = <$db>::init(ctx.child("storage"), $cfg::<OneCap>("db", &ctx))
1446                            .await
1447                            .unwrap();
1448                        $f(
1449                            ctx,
1450                            db,
1451                            |ctx| {
1452                                Box::pin(async move {
1453                                    <$db>::init(ctx.child("storage"), $cfg::<OneCap>("db", &ctx))
1454                                        .await
1455                                        .unwrap()
1456                                })
1457                            },
1458                            to_digest,
1459                        )
1460                        .await;
1461                    });
1462                }
1463            }
1464        };
1465        (with_make_value: $f:ident, $traced:literal, $l:ident, $db:ty, $family:ty, $cfg:ident) => {
1466            paste::paste! {
1467                #[test_group("slow")]
1468                #[test_traced($traced)]
1469                fn [<$f _ $l>]() {
1470                    let executor = deterministic::Runner::default();
1471                    executor.start(|context| async move {
1472                        let ctx = context.child(stringify!($l));
1473                        let db = <$db>::init(ctx.child("storage"), $cfg::<OneCap>("db", &ctx))
1474                            .await
1475                            .unwrap();
1476                        $f(ctx, db, to_digest).await;
1477                    });
1478                }
1479            }
1480        };
1481    }
1482
1483    // Generate one slow test per variant across all variants (MMR + MMB).
1484    macro_rules! test_for_all_variants {
1485        (with_reopen: $f:ident, $traced:literal) => {
1486            with_all_variants!(test_for_variant!(with_reopen: $f, $traced));
1487        };
1488        (with_make_value: $f:ident, $traced:literal) => {
1489            with_all_variants!(test_for_variant!(with_make_value: $f, $traced));
1490        };
1491    }
1492
1493    // Generate one slow test per variant across the MMR-only variants (for
1494    // tests that use mmr::Family-specific features like Location::new or
1495    // verify_proof).
1496    macro_rules! test_for_mmr_variants {
1497        (with_reopen: $f:ident, $traced:literal) => {
1498            with_mmr_variants!(test_for_variant!(with_reopen: $f, $traced));
1499        };
1500        (with_make_value: $f:ident, $traced:literal) => {
1501            with_mmr_variants!(test_for_variant!(with_make_value: $f, $traced));
1502        };
1503    }
1504
1505    test_for_all_variants!(with_reopen: test_any_db_log_replay, "WARN");
1506    test_for_mmr_variants!(with_reopen: test_any_db_build_and_authenticate, "WARN");
1507    test_for_mmr_variants!(with_make_value: test_any_db_historical_proof_basic, "WARN");
1508    test_for_mmr_variants!(with_make_value: test_any_db_historical_proof_invalid, "WARN");
1509    test_for_mmr_variants!(with_make_value: test_any_db_historical_proof_edge_cases, "WARN");
1510    test_for_all_variants!(with_reopen: test_any_db_multiple_commits_delete_replayed, "WARN");
1511    test_for_all_variants!(with_reopen: test_any_db_non_empty_recovery, "WARN");
1512    test_for_all_variants!(with_reopen: test_any_db_empty_recovery, "WARN");
1513    test_for_all_variants!(with_reopen: test_any_db_commit_after_sync_recovery, "WARN");
1514    test_for_all_variants!(with_reopen: test_any_db_prune_after_unsynced_floor_recovery, "WARN");
1515    test_for_mmr_variants!(with_reopen: test_any_db_rewind_recovery, "WARN");
1516
1517    fn key(i: u64) -> Digest {
1518        Sha256::hash(&i.to_be_bytes())
1519    }
1520
1521    fn val(i: u64) -> Digest {
1522        Sha256::hash(&(i + 10000).to_be_bytes())
1523    }
1524
1525    /// Helper: commit a batch of key-value writes and return the applied range.
1526    async fn commit_writes(
1527        db: &mut UnorderedVariable,
1528        writes: impl IntoIterator<Item = (Digest, Option<Digest>)>,
1529        metadata: Option<Digest>,
1530    ) -> std::ops::Range<crate::mmr::Location> {
1531        let mut batch = db.new_batch();
1532        for (k, v) in writes {
1533            batch = batch.write(k, v);
1534        }
1535        let merkleized = batch.merkleize(&*db, metadata).await.unwrap();
1536        let range = db.apply_batch(merkleized).await.unwrap();
1537        db.commit().await.unwrap();
1538        range
1539    }
1540
1541    /// An empty batch (no mutations) still produces a valid commit.
1542    #[test_traced("INFO")]
1543    fn test_any_batch_empty() {
1544        let executor = deterministic::Runner::default();
1545        executor.start(|context| async move {
1546            let ctx = context.child("db");
1547            let mut db: UnorderedVariable = UnorderedVariableDb::init(
1548                ctx.child("storage"),
1549                variable_db_config::<OneCap>("e", &ctx),
1550            )
1551            .await
1552            .unwrap();
1553
1554            let root_before = db.root();
1555            let batch = db.new_batch();
1556            let merkleized = batch.merkleize(&db, None).await.unwrap();
1557            db.apply_batch(merkleized).await.unwrap();
1558
1559            // A CommitFloor op was appended, so root must change.
1560            assert_ne!(db.root(), root_before);
1561
1562            // DB should still be functional.
1563            commit_writes(&mut db, [(key(0), Some(val(0)))], None).await;
1564            assert_eq!(db.get(&key(0)).await.unwrap(), Some(val(0)));
1565
1566            db.destroy().await.unwrap();
1567        });
1568    }
1569
1570    /// Metadata propagates through merkleize and clears with None.
1571    #[test_traced("INFO")]
1572    fn test_any_batch_metadata() {
1573        let executor = deterministic::Runner::default();
1574        executor.start(|context| async move {
1575            let ctx = context.child("db");
1576            let mut db: UnorderedVariable = UnorderedVariableDb::init(
1577                ctx.child("storage"),
1578                variable_db_config::<OneCap>("m", &ctx),
1579            )
1580            .await
1581            .unwrap();
1582
1583            let metadata = val(42);
1584
1585            // Batch with metadata.
1586            commit_writes(&mut db, [(key(0), Some(val(0)))], Some(metadata)).await;
1587            assert_eq!(db.get_metadata().await.unwrap(), Some(metadata));
1588
1589            // Batch without metadata clears it.
1590            let batch = db.new_batch();
1591            let merkleized = batch.merkleize(&db, None).await.unwrap();
1592            db.apply_batch(merkleized).await.unwrap();
1593            assert_eq!(db.get_metadata().await.unwrap(), None);
1594
1595            db.destroy().await.unwrap();
1596        });
1597    }
1598
1599    /// batch.get() reads through: pending mutations -> base DB.
1600    /// Updates shadow the base value; deletes hide the key.
1601    #[test_traced("INFO")]
1602    fn test_any_batch_get_read_through() {
1603        let executor = deterministic::Runner::default();
1604        executor.start(|context| async move {
1605            let ctx = context.child("db");
1606            let mut db: UnorderedVariable = UnorderedVariableDb::init(
1607                ctx.child("storage"),
1608                variable_db_config::<OneCap>("g", &ctx),
1609            )
1610            .await
1611            .unwrap();
1612
1613            // Pre-populate with key A.
1614            let ka = key(0);
1615            let va = val(0);
1616            commit_writes(&mut db, [(ka, Some(va))], None).await;
1617
1618            let kb = key(1);
1619            let vb = val(1);
1620            let kc = key(2);
1621
1622            let mut batch = db.new_batch();
1623
1624            // Read-through to base DB.
1625            assert_eq!(batch.get(&ka, &db).await.unwrap(), Some(va));
1626
1627            // Pending mutation visible.
1628            batch = batch.write(kb, Some(vb));
1629            assert_eq!(batch.get(&kb, &db).await.unwrap(), Some(vb));
1630
1631            // Nonexistent key.
1632            assert_eq!(batch.get(&kc, &db).await.unwrap(), None);
1633
1634            // Update shadows base DB value.
1635            let va2 = val(100);
1636            batch = batch.write(ka, Some(va2));
1637            assert_eq!(batch.get(&ka, &db).await.unwrap(), Some(va2));
1638
1639            // Delete hides the key.
1640            batch = batch.write(ka, None);
1641            assert_eq!(batch.get(&ka, &db).await.unwrap(), None);
1642
1643            db.destroy().await.unwrap();
1644        });
1645    }
1646
1647    /// merkleized.get() reflects the resolved diff after merkleize.
1648    #[test_traced("INFO")]
1649    fn test_any_batch_get_on_merkleized() {
1650        let executor = deterministic::Runner::default();
1651        executor.start(|context| async move {
1652            let ctx = context.child("db");
1653            let mut db: UnorderedVariable = UnorderedVariableDb::init(
1654                ctx.child("storage"),
1655                variable_db_config::<OneCap>("mg", &ctx),
1656            )
1657            .await
1658            .unwrap();
1659
1660            let ka = key(0);
1661            let kb = key(1);
1662            let kc = key(2);
1663            let kd = key(3);
1664
1665            // Pre-populate A and B.
1666            commit_writes(&mut db, [(ka, Some(val(0))), (kb, Some(val(1)))], None).await;
1667
1668            // Batch: update A, delete B, create C.
1669            let va2 = val(100);
1670            let vc = val(2);
1671            let mut batch = db.new_batch();
1672            batch = batch.write(ka, Some(va2));
1673            batch = batch.write(kb, None);
1674            batch = batch.write(kc, Some(vc));
1675            let merkleized = batch.merkleize(&db, None).await.unwrap();
1676
1677            assert_eq!(merkleized.get(&ka, &db).await.unwrap(), Some(va2));
1678            assert_eq!(merkleized.get(&kb, &db).await.unwrap(), None);
1679            assert_eq!(merkleized.get(&kc, &db).await.unwrap(), Some(vc));
1680            assert_eq!(merkleized.get(&kd, &db).await.unwrap(), None);
1681
1682            db.destroy().await.unwrap();
1683        });
1684    }
1685
1686    /// Child batch reads through: child mutations -> parent diff -> base DB.
1687    #[test_traced("INFO")]
1688    fn test_any_batch_stacked_get() {
1689        let executor = deterministic::Runner::default();
1690        executor.start(|context| async move {
1691            let ctx = context.child("db");
1692            let db: UnorderedVariable = UnorderedVariableDb::init(
1693                ctx.child("storage"),
1694                variable_db_config::<OneCap>("sg", &ctx),
1695            )
1696            .await
1697            .unwrap();
1698
1699            let ka = key(0);
1700            let kb = key(1);
1701
1702            // Parent batch writes A.
1703            let mut batch = db.new_batch();
1704            batch = batch.write(ka, Some(val(0)));
1705            let merkleized = batch.merkleize(&db, None).await.unwrap();
1706
1707            // Child reads parent's A.
1708            let mut child = merkleized.new_batch::<Sha256>();
1709            assert_eq!(child.get(&ka, &db).await.unwrap(), Some(val(0)));
1710
1711            // Child overwrites A.
1712            child = child.write(ka, Some(val(100)));
1713            assert_eq!(child.get(&ka, &db).await.unwrap(), Some(val(100)));
1714
1715            // Child writes new key B.
1716            child = child.write(kb, Some(val(1)));
1717            assert_eq!(child.get(&kb, &db).await.unwrap(), Some(val(1)));
1718
1719            // Child deletes A.
1720            child = child.write(ka, None);
1721            assert_eq!(child.get(&ka, &db).await.unwrap(), None);
1722
1723            db.destroy().await.unwrap();
1724        });
1725    }
1726
1727    /// Parent deletes a base-DB key, child re-creates it.
1728    #[test_traced("INFO")]
1729    fn test_any_batch_stacked_delete_recreate() {
1730        let executor = deterministic::Runner::default();
1731        executor.start(|context| async move {
1732            let ctx = context.child("db");
1733            let mut db: UnorderedVariable = UnorderedVariableDb::init(
1734                ctx.child("storage"),
1735                variable_db_config::<OneCap>("dr", &ctx),
1736            )
1737            .await
1738            .unwrap();
1739
1740            let ka = key(0);
1741
1742            // Pre-populate with key A.
1743            commit_writes(&mut db, [(ka, Some(val(0)))], None).await;
1744
1745            // Parent batch deletes A.
1746            let mut parent = db.new_batch();
1747            parent = parent.write(ka, None);
1748            let parent_m = parent.merkleize(&db, None).await.unwrap();
1749            assert_eq!(parent_m.get(&ka, &db).await.unwrap(), None);
1750
1751            // Child re-creates A with a new value.
1752            let mut child = parent_m.new_batch::<Sha256>();
1753            child = child.write(ka, Some(val(200)));
1754            let child_m = child.merkleize(&db, None).await.unwrap();
1755            assert_eq!(child_m.get(&ka, &db).await.unwrap(), Some(val(200)));
1756
1757            // Apply and verify DB state.
1758            db.apply_batch(child_m).await.unwrap();
1759            assert_eq!(db.get(&ka).await.unwrap(), Some(val(200)));
1760
1761            db.destroy().await.unwrap();
1762        });
1763    }
1764
1765    /// Floor raise during merkleize moves active operations to the tip.
1766    /// All keys remain accessible with correct values.
1767    #[test_traced("INFO")]
1768    fn test_any_batch_floor_raise() {
1769        let executor = deterministic::Runner::default();
1770        executor.start(|context| async move {
1771            let ctx = context.child("db");
1772            let mut db: UnorderedVariable = UnorderedVariableDb::init(
1773                ctx.child("storage"),
1774                variable_db_config::<OneCap>("fr", &ctx),
1775            )
1776            .await
1777            .unwrap();
1778
1779            // Pre-populate with 100 keys.
1780            let init: Vec<_> = (0..100).map(|i| (key(i), Some(val(i)))).collect();
1781            commit_writes(&mut db, init, None).await;
1782
1783            let floor_before = db.inactivity_floor_loc();
1784
1785            // Update 30 keys.
1786            let updates: Vec<_> = (0..30).map(|i| (key(i), Some(val(i + 500)))).collect();
1787            commit_writes(&mut db, updates, None).await;
1788
1789            // Floor should have advanced.
1790            assert!(db.inactivity_floor_loc() > floor_before);
1791
1792            // All keys should still be accessible with correct values.
1793            for i in 0..30 {
1794                assert_eq!(
1795                    db.get(&key(i)).await.unwrap(),
1796                    Some(val(i + 500)),
1797                    "updated key {i} mismatch"
1798                );
1799            }
1800            for i in 30..100 {
1801                assert_eq!(
1802                    db.get(&key(i)).await.unwrap(),
1803                    Some(val(i)),
1804                    "untouched key {i} mismatch"
1805                );
1806            }
1807
1808            db.destroy().await.unwrap();
1809        });
1810    }
1811
1812    /// apply_batch() returns the correct range of committed locations.
1813    #[test_traced("INFO")]
1814    fn test_any_batch_apply_returns_range() {
1815        let executor = deterministic::Runner::default();
1816        executor.start(|context| async move {
1817            let ctx = context.child("db");
1818            let mut db: UnorderedVariable = UnorderedVariableDb::init(
1819                ctx.child("storage"),
1820                variable_db_config::<OneCap>("ar", &ctx),
1821            )
1822            .await
1823            .unwrap();
1824
1825            // First batch: 5 keys.
1826            let writes: Vec<_> = (0..5).map(|i| (key(i), Some(val(i)))).collect();
1827            let range1 = commit_writes(&mut db, writes, None).await;
1828
1829            // Range should start after the initial CommitFloor (location 0).
1830            assert_eq!(range1.start, crate::mmr::Location::new(1));
1831            // Range length >= 6 (5 writes + 1 CommitFloor + possible floor raise ops).
1832            assert!(range1.end.saturating_sub(*range1.start) >= 6);
1833
1834            // Second batch: ranges must be contiguous.
1835            let writes: Vec<_> = (5..10).map(|i| (key(i), Some(val(i)))).collect();
1836            let range2 = commit_writes(&mut db, writes, None).await;
1837            assert_eq!(range2.start, range1.end);
1838
1839            db.destroy().await.unwrap();
1840        });
1841    }
1842
1843    /// 3-level chain: parent -> child -> grandchild, merkleize grandchild and apply.
1844    #[test_traced("INFO")]
1845    fn test_any_batch_deep_chain() {
1846        let executor = deterministic::Runner::default();
1847        executor.start(|context| async move {
1848            let ctx = context.child("db");
1849            let mut db: UnorderedVariable = UnorderedVariableDb::init(
1850                ctx.child("storage"),
1851                variable_db_config::<OneCap>("dc", &ctx),
1852            )
1853            .await
1854            .unwrap();
1855
1856            // Pre-populate with keys 0..5.
1857            let init: Vec<_> = (0..5).map(|i| (key(i), Some(val(i)))).collect();
1858            commit_writes(&mut db, init, None).await;
1859
1860            // Parent: overwrite key 0, add key 5.
1861            let mut parent = db.new_batch();
1862            parent = parent.write(key(0), Some(val(100)));
1863            parent = parent.write(key(5), Some(val(5)));
1864            let parent_m = parent.merkleize(&db, None).await.unwrap();
1865
1866            // Child: overwrite key 1, add key 6.
1867            let mut child = parent_m.new_batch::<Sha256>();
1868            child = child.write(key(1), Some(val(101)));
1869            child = child.write(key(6), Some(val(6)));
1870            let child_m = child.merkleize(&db, None).await.unwrap();
1871
1872            // Grandchild: delete key 2, add key 7.
1873            let mut grandchild = child_m.new_batch::<Sha256>();
1874            grandchild = grandchild.write(key(2), None);
1875            grandchild = grandchild.write(key(7), Some(val(7)));
1876            let grandchild_m = grandchild.merkleize(&db, None).await.unwrap();
1877
1878            // Verify reads through the chain.
1879            assert_eq!(
1880                grandchild_m.get(&key(0), &db).await.unwrap(),
1881                Some(val(100))
1882            );
1883            assert_eq!(
1884                grandchild_m.get(&key(1), &db).await.unwrap(),
1885                Some(val(101))
1886            );
1887            assert_eq!(grandchild_m.get(&key(2), &db).await.unwrap(), None);
1888            assert_eq!(grandchild_m.get(&key(7), &db).await.unwrap(), Some(val(7)));
1889
1890            // Apply.
1891            db.apply_batch(grandchild_m).await.unwrap();
1892
1893            assert_eq!(db.get(&key(0)).await.unwrap(), Some(val(100)));
1894            assert_eq!(db.get(&key(1)).await.unwrap(), Some(val(101)));
1895            assert_eq!(db.get(&key(2)).await.unwrap(), None);
1896            assert_eq!(db.get(&key(3)).await.unwrap(), Some(val(3)));
1897            assert_eq!(db.get(&key(4)).await.unwrap(), Some(val(4)));
1898            assert_eq!(db.get(&key(5)).await.unwrap(), Some(val(5)));
1899            assert_eq!(db.get(&key(6)).await.unwrap(), Some(val(6)));
1900            assert_eq!(db.get(&key(7)).await.unwrap(), Some(val(7)));
1901
1902            db.destroy().await.unwrap();
1903        });
1904    }
1905
1906    /// Chained batch produces the same DB state as sequential apply_batch calls.
1907    #[test_traced("INFO")]
1908    fn test_any_batch_chain_matches_sequential() {
1909        let executor = deterministic::Runner::default();
1910        executor.start(|context| async move {
1911            let ctx = context.child("db");
1912
1913            // DB A: sequential apply.
1914            let ctx_a = ctx.child("a");
1915            let mut db_a: UnorderedVariable = UnorderedVariableDb::init(
1916                ctx_a.child("db"),
1917                variable_db_config::<OneCap>("cms-a", &ctx_a),
1918            )
1919            .await
1920            .unwrap();
1921
1922            // DB B: chained batch.
1923            let ctx_b = ctx.child("b");
1924            let mut db_b: UnorderedVariable = UnorderedVariableDb::init(
1925                ctx_b.child("db"),
1926                variable_db_config::<OneCap>("cms-b", &ctx_b),
1927            )
1928            .await
1929            .unwrap();
1930
1931            // Batch 1 operations: create keys 0..5.
1932            let writes1: Vec<_> = (0..5).map(|i| (key(i), Some(val(i)))).collect();
1933
1934            // Batch 2 operations: update key 0, delete key 1, create key 5.
1935            let writes2 = vec![
1936                (key(0), Some(val(100))),
1937                (key(1), None),
1938                (key(5), Some(val(5))),
1939            ];
1940
1941            // DB A: apply sequentially.
1942            commit_writes(&mut db_a, writes1.clone(), None).await;
1943            commit_writes(&mut db_a, writes2.clone(), None).await;
1944
1945            // DB B: apply as chain.
1946            let mut parent = db_b.new_batch();
1947            for (k, v) in &writes1 {
1948                parent = parent.write(*k, *v);
1949            }
1950            let parent_m = parent.merkleize(&db_b, None).await.unwrap();
1951
1952            let mut child = parent_m.new_batch::<Sha256>();
1953            for (k, v) in &writes2 {
1954                child = child.write(*k, *v);
1955            }
1956            let child_m = child.merkleize(&db_b, None).await.unwrap();
1957            db_b.apply_batch(child_m).await.unwrap();
1958
1959            // Both DBs must have the same state.
1960            assert_eq!(db_a.root(), db_b.root());
1961            for i in 0..6 {
1962                assert_eq!(
1963                    db_a.get(&key(i)).await.unwrap(),
1964                    db_b.get(&key(i)).await.unwrap(),
1965                    "key {i} mismatch"
1966                );
1967            }
1968
1969            db_a.destroy().await.unwrap();
1970            db_b.destroy().await.unwrap();
1971        });
1972    }
1973
1974    /// Create and delete the same key in a single batch produces no net change for that key.
1975    #[test_traced("INFO")]
1976    fn test_any_batch_create_then_delete_same_batch() {
1977        let executor = deterministic::Runner::default();
1978        executor.start(|context| async move {
1979            let ctx = context.child("db");
1980            let mut db: UnorderedVariable = UnorderedVariableDb::init(
1981                ctx.child("storage"),
1982                variable_db_config::<OneCap>("cd", &ctx),
1983            )
1984            .await
1985            .unwrap();
1986
1987            // Pre-populate key A.
1988            commit_writes(&mut db, [(key(0), Some(val(0)))], None).await;
1989
1990            // In one batch: create B then delete B, also create C and delete A.
1991            let mut batch = db.new_batch();
1992            batch = batch.write(key(1), Some(val(1))); // create B
1993            batch = batch.write(key(1), None); // delete B (net: no B)
1994            batch = batch.write(key(2), Some(val(2))); // create C
1995            batch = batch.write(key(0), None); // delete A
1996            let merkleized = batch.merkleize(&db, None).await.unwrap();
1997            db.apply_batch(merkleized).await.unwrap();
1998
1999            assert_eq!(db.get(&key(0)).await.unwrap(), None);
2000            assert_eq!(db.get(&key(1)).await.unwrap(), None);
2001            assert_eq!(db.get(&key(2)).await.unwrap(), Some(val(2)));
2002
2003            db.destroy().await.unwrap();
2004        });
2005    }
2006
2007    /// Deleting all keys exercises the total_active_keys == 0 floor-raise fast path.
2008    #[test_traced("INFO")]
2009    fn test_any_batch_delete_all_keys() {
2010        let executor = deterministic::Runner::default();
2011        executor.start(|context| async move {
2012            let ctx = context.child("db");
2013            let mut db: UnorderedVariable = UnorderedVariableDb::init(
2014                ctx.child("storage"),
2015                variable_db_config::<OneCap>("da", &ctx),
2016            )
2017            .await
2018            .unwrap();
2019
2020            // Pre-populate 5 keys.
2021            let init: Vec<_> = (0..5).map(|i| (key(i), Some(val(i)))).collect();
2022            commit_writes(&mut db, init, None).await;
2023
2024            // Delete all 5.
2025            let deletes: Vec<_> = (0..5).map(|i| (key(i), None)).collect();
2026            commit_writes(&mut db, deletes, None).await;
2027
2028            for i in 0..5 {
2029                assert_eq!(db.get(&key(i)).await.unwrap(), None, "key {i} not deleted");
2030            }
2031
2032            // DB should still be functional after deleting everything.
2033            commit_writes(&mut db, [(key(10), Some(val(10)))], None).await;
2034            assert_eq!(db.get(&key(10)).await.unwrap(), Some(val(10)));
2035
2036            db.destroy().await.unwrap();
2037        });
2038    }
2039
2040    /// Two independent batches from the same DB do not interfere with each other.
2041    #[test_traced("INFO")]
2042    fn test_any_batch_parallel_forks() {
2043        let executor = deterministic::Runner::default();
2044        executor.start(|context| async move {
2045            let ctx = context.child("db");
2046            let mut db: UnorderedVariable = UnorderedVariableDb::init(
2047                ctx.child("storage"),
2048                variable_db_config::<OneCap>("pf", &ctx),
2049            )
2050            .await
2051            .unwrap();
2052
2053            // Pre-populate.
2054            commit_writes(&mut db, [(key(0), Some(val(0)))], None).await;
2055            let root_before = db.root();
2056
2057            // Fork A: update key 0 and create key 1.
2058            let fork_a_m = db
2059                .new_batch()
2060                .write(key(0), Some(val(100)))
2061                .write(key(1), Some(val(1)))
2062                .merkleize(&db, None)
2063                .await
2064                .unwrap();
2065
2066            // Fork B: delete key 0 and create key 2.
2067            let fork_b_m = db
2068                .new_batch()
2069                .write(key(0), None)
2070                .write(key(2), Some(val(2)))
2071                .merkleize(&db, None)
2072                .await
2073                .unwrap();
2074
2075            // Different mutations must produce different roots.
2076            assert_ne!(fork_a_m.root(), fork_b_m.root());
2077
2078            // DB is unchanged (neither batch applied).
2079            assert_eq!(db.root(), root_before);
2080            assert_eq!(db.get(&key(0)).await.unwrap(), Some(val(0)));
2081            assert_eq!(db.get(&key(1)).await.unwrap(), None);
2082
2083            // Apply fork A only.
2084            db.apply_batch(fork_a_m).await.unwrap();
2085            assert_eq!(db.get(&key(0)).await.unwrap(), Some(val(100)));
2086            assert_eq!(db.get(&key(1)).await.unwrap(), Some(val(1)));
2087            assert_eq!(db.get(&key(2)).await.unwrap(), None);
2088
2089            db.destroy().await.unwrap();
2090        });
2091    }
2092
2093    /// Floor raise advances correctly across a chained batch.
2094    #[test_traced("INFO")]
2095    fn test_any_batch_floor_raise_chained() {
2096        let executor = deterministic::Runner::default();
2097        executor.start(|context| async move {
2098            let ctx = context.child("db");
2099            let mut db: UnorderedVariable = UnorderedVariableDb::init(
2100                ctx.child("storage"),
2101                variable_db_config::<OneCap>("frc", &ctx),
2102            )
2103            .await
2104            .unwrap();
2105
2106            // Pre-populate with 50 keys.
2107            let init: Vec<_> = (0..50).map(|i| (key(i), Some(val(i)))).collect();
2108            commit_writes(&mut db, init, None).await;
2109            let floor_before = db.inactivity_floor_loc();
2110
2111            // Parent: update keys 0..20.
2112            let mut parent = db.new_batch();
2113            for i in 0..20 {
2114                parent = parent.write(key(i), Some(val(i + 500)));
2115            }
2116            let parent_m = parent.merkleize(&db, None).await.unwrap();
2117
2118            // Child: update keys 20..30.
2119            let mut child = parent_m.new_batch::<Sha256>();
2120            for i in 20..30 {
2121                child = child.write(key(i), Some(val(i + 500)));
2122            }
2123            let child_m = child.merkleize(&db, None).await.unwrap();
2124            db.apply_batch(child_m).await.unwrap();
2125
2126            // Floor must have advanced.
2127            assert!(db.inactivity_floor_loc() > floor_before);
2128
2129            // All keys should be accessible.
2130            for i in 0..30 {
2131                assert_eq!(
2132                    db.get(&key(i)).await.unwrap(),
2133                    Some(val(i + 500)),
2134                    "updated key {i} mismatch"
2135                );
2136            }
2137            for i in 30..50 {
2138                assert_eq!(
2139                    db.get(&key(i)).await.unwrap(),
2140                    Some(val(i)),
2141                    "untouched key {i} mismatch"
2142                );
2143            }
2144
2145            db.destroy().await.unwrap();
2146        });
2147    }
2148
2149    /// Dropping a batch without applying it leaves the DB unchanged.
2150    #[test_traced("INFO")]
2151    fn test_any_batch_abandoned() {
2152        let executor = deterministic::Runner::default();
2153        executor.start(|context| async move {
2154            let ctx = context.child("db");
2155            let mut db: UnorderedVariable = UnorderedVariableDb::init(
2156                ctx.child("storage"),
2157                variable_db_config::<OneCap>("ab", &ctx),
2158            )
2159            .await
2160            .unwrap();
2161
2162            commit_writes(&mut db, [(key(0), Some(val(0)))], None).await;
2163            let root_before = db.root();
2164
2165            // Create, populate, merkleize, then drop without apply.
2166            {
2167                let mut batch = db.new_batch();
2168                batch = batch.write(key(0), Some(val(999)));
2169                batch = batch.write(key(1), Some(val(1)));
2170                let _merkleized = batch.merkleize(&db, None).await.unwrap();
2171                // dropped here
2172            }
2173
2174            // DB state is identical.
2175            assert_eq!(db.root(), root_before);
2176            assert_eq!(db.get(&key(0)).await.unwrap(), Some(val(0)));
2177            assert_eq!(db.get(&key(1)).await.unwrap(), None);
2178
2179            db.destroy().await.unwrap();
2180        });
2181    }
2182
2183    /// Applying without `commit()` publishes in memory but is not recovered after reopen.
2184    #[test_traced("INFO")]
2185    fn test_any_batch_apply_requires_commit_for_recovery() {
2186        let executor = deterministic::Runner::default();
2187        executor.start(|context| async move {
2188            let partition = "apply_requires_commit";
2189            let ctx = context.child("db");
2190            let mut db: UnorderedVariable = UnorderedVariableDb::init(
2191                ctx.child("storage"),
2192                variable_db_config::<OneCap>(partition, &ctx),
2193            )
2194            .await
2195            .unwrap();
2196
2197            let committed_root = db.root();
2198
2199            let merkleized = db
2200                .new_batch()
2201                .write(key(0), Some(val(0)))
2202                .merkleize(&db, None)
2203                .await
2204                .unwrap();
2205            db.apply_batch(merkleized).await.unwrap();
2206
2207            assert_eq!(db.get(&key(0)).await.unwrap(), Some(val(0)));
2208
2209            drop(db);
2210
2211            let reopened: UnorderedVariable = UnorderedVariableDb::init(
2212                context.child("reopen"),
2213                variable_db_config::<OneCap>(partition, &context),
2214            )
2215            .await
2216            .unwrap();
2217            assert_eq!(reopened.root(), committed_root);
2218            assert_eq!(reopened.get(&key(0)).await.unwrap(), None);
2219
2220            reopened.destroy().await.unwrap();
2221        });
2222    }
2223
2224    /// Rewinding to a pruned target returns an error.
2225    #[test_traced("INFO")]
2226    fn test_any_rewind_pruned_target_errors() {
2227        let executor = deterministic::Runner::default();
2228        executor.start(|context| async move {
2229            const KEYS: u64 = 64;
2230
2231            let ctx = context.child("db");
2232            let mut db: UnorderedVariable = UnorderedVariableDb::init(
2233                ctx.child("storage"),
2234                variable_db_config::<OneCap>("rp", &ctx),
2235            )
2236            .await
2237            .unwrap();
2238
2239            let initial: Vec<_> = (0..KEYS).map(|i| (key(i), Some(val(i)))).collect();
2240            let first_range = commit_writes(&mut db, initial, None).await;
2241
2242            let mut round = 0u64;
2243            loop {
2244                round += 1;
2245                assert!(
2246                    round <= 64,
2247                    "failed to prune enough history for rewind test"
2248                );
2249
2250                let updates: Vec<_> = (0..KEYS)
2251                    .map(|i| (key(i), Some(val(1000 + round * KEYS + i))))
2252                    .collect();
2253                commit_writes(&mut db, updates, None).await;
2254
2255                db.prune(db.sync_boundary()).await.unwrap();
2256                let bounds = db.bounds();
2257                if bounds.start > first_range.start {
2258                    break;
2259                }
2260            }
2261
2262            let oldest_retained = db.bounds().start;
2263            let boundary_err = db.rewind(oldest_retained).await.unwrap_err();
2264            assert!(
2265                matches!(
2266                    boundary_err,
2267                    crate::qmdb::Error::Journal(crate::journal::Error::ItemPruned(_))
2268                ),
2269                "unexpected rewind error at retained boundary: {boundary_err:?}"
2270            );
2271
2272            let err = db.rewind(first_range.start).await.unwrap_err();
2273            assert!(
2274                matches!(
2275                    err,
2276                    crate::qmdb::Error::Journal(crate::journal::Error::ItemPruned(_))
2277                ),
2278                "unexpected rewind error: {err:?}"
2279            );
2280
2281            db.destroy().await.unwrap();
2282        });
2283    }
2284
2285    /// Rewinding rejects out-of-range targets and keeps state unchanged.
2286    #[test_traced("INFO")]
2287    fn test_any_rewind_invalid_target_errors() {
2288        let executor = deterministic::Runner::default();
2289        executor.start(|context| async move {
2290            let ctx = context.child("db");
2291            let mut db: UnorderedVariable = UnorderedVariableDb::init(
2292                ctx.child("storage"),
2293                variable_db_config::<OneCap>("ri", &ctx),
2294            )
2295            .await
2296            .unwrap();
2297
2298            let root_before = db.root();
2299            let size_before = db.size();
2300            db.rewind(size_before).await.unwrap();
2301            assert_eq!(db.root(), root_before);
2302            assert_eq!(db.size(), size_before);
2303
2304            let zero_err = db.rewind(Location::new(0)).await.unwrap_err();
2305            assert!(
2306                matches!(
2307                    zero_err,
2308                    crate::qmdb::Error::Journal(crate::journal::Error::InvalidRewind(0))
2309                ),
2310                "unexpected rewind error: {zero_err:?}"
2311            );
2312            assert_eq!(db.root(), root_before);
2313            assert_eq!(db.size(), size_before);
2314
2315            let too_large_target = Location::new(*size_before + 1);
2316            let too_large_err = db.rewind(too_large_target).await.unwrap_err();
2317            assert!(
2318                matches!(
2319                    too_large_err,
2320                    crate::qmdb::Error::Journal(crate::journal::Error::InvalidRewind(size))
2321                    if size == *too_large_target
2322                ),
2323                "unexpected rewind error: {too_large_err:?}"
2324            );
2325            assert_eq!(db.root(), root_before);
2326            assert_eq!(db.size(), size_before);
2327
2328            db.destroy().await.unwrap();
2329        });
2330    }
2331
2332    /// Rewinding fails when the target commit's inactivity floor has been pruned, even if the
2333    /// target commit location is still retained.
2334    #[test_traced("INFO")]
2335    fn test_any_rewind_rejects_target_with_pruned_floor() {
2336        let executor = deterministic::Runner::default();
2337        executor.start(|context| async move {
2338            const KEYS: u64 = 64;
2339
2340            let ctx = context.child("db");
2341            let mut db: UnorderedVariable =
2342                UnorderedVariableDb::init(ctx.child("storage"), variable_db_config::<OneCap>("rf", &ctx))
2343                    .await
2344                    .unwrap();
2345
2346            commit_writes(&mut db, (0..KEYS).map(|i| (key(i), Some(val(i)))), None).await;
2347            commit_writes(
2348                &mut db,
2349                (0..KEYS).map(|i| (key(i), Some(val(1_000 + i)))),
2350                None,
2351            )
2352            .await;
2353
2354            let rewind_target = db.size();
2355            let target_floor = db.inactivity_floor_loc();
2356            let prune_loc = Location::new(*target_floor + (KEYS / 2));
2357            assert!(
2358                rewind_target > *prune_loc,
2359                "test setup expected target size > prune_loc; target={rewind_target:?}, floor={target_floor:?}"
2360            );
2361
2362            let mut round = 0u64;
2363            while db.inactivity_floor_loc() < prune_loc {
2364                round += 1;
2365                assert!(
2366                    round <= 8,
2367                    "failed to advance inactivity floor enough for floor-pruned rewind test"
2368                );
2369                commit_writes(
2370                    &mut db,
2371                    (0..KEYS).map(|i| (key(i), Some(val(10_000 + round * KEYS + i)))),
2372                    None,
2373                )
2374                .await;
2375            }
2376
2377            db.prune(prune_loc).await.unwrap();
2378            let bounds = db.bounds();
2379            assert!(
2380                bounds.start > *target_floor,
2381                "test setup expected pruned start beyond target floor; bounds={bounds:?}, target_floor={target_floor:?}"
2382            );
2383            assert!(
2384                rewind_target > bounds.start,
2385                "test setup expected target commit retained; target={rewind_target:?}, bounds={bounds:?}"
2386            );
2387
2388            let err = db.rewind(rewind_target).await.unwrap_err();
2389            assert!(
2390                matches!(
2391                    err,
2392                    crate::qmdb::Error::Journal(crate::journal::Error::ItemPruned(_))
2393                ),
2394                "unexpected rewind error: {err:?}"
2395            );
2396
2397            db.destroy().await.unwrap();
2398        });
2399    }
2400
2401    /// `prune()` must advance the bitmap only as far as the authenticated journal actually
2402    /// pruned. Journal pruning is section-granular while bitmap pruning rounds to chunk
2403    /// boundaries, so a coarse `items_per_section` can leave the journal retaining from the
2404    /// start while the bitmap has already crossed the next chunk boundary. A subsequent
2405    /// `rewind()` to a still-retained early commit must still succeed.
2406    #[test_traced("INFO")]
2407    fn test_any_prune_keeps_bitmap_aligned_with_journal() {
2408        let executor = deterministic::Runner::default();
2409        executor.start(|context| async move {
2410            // Bitmap chunk size in bits. The bug requires the bitmap to round across at least
2411            // one chunk boundary while the journal cannot prune any section.
2412            const BITMAP_CHUNK_BITS: u64 =
2413                commonware_utils::bitmap::Prunable::<BITMAP_CHUNK_BYTES>::CHUNK_SIZE_BITS;
2414            // Items-per-section is chosen so that no full section fits in the test's op count,
2415            // forcing the journal to retain from 0 even when prune is requested past the first
2416            // bitmap chunk boundary.
2417            const ITEMS_PER_SECTION: u64 = 2048;
2418            const { assert!(ITEMS_PER_SECTION > BITMAP_CHUNK_BITS) };
2419
2420            let ctx = context.child("db");
2421            let mut cfg = variable_db_config::<OneCap>("rg", &ctx);
2422            cfg.journal_config.items_per_section = NZU64!(ITEMS_PER_SECTION);
2423
2424            let mut db: UnorderedVariable =
2425                UnorderedVariableDb::init(ctx.child("storage"), cfg).await.unwrap();
2426
2427            commit_writes(&mut db, (0..100).map(|i| (key(i), Some(val(i)))), None).await;
2428            let rewind_target = db.size();
2429            // Rewind target must lie below the chunk boundary the buggy prune would advance
2430            // to; otherwise the unfixed code would not panic on truncate.
2431            assert!(
2432                *rewind_target < BITMAP_CHUNK_BITS,
2433                "rewind_target {rewind_target:?} must be < {BITMAP_CHUNK_BITS} for the bug to manifest"
2434            );
2435            let root_at_target = db.root();
2436
2437            commit_writes(
2438                &mut db,
2439                (0..700).map(|i| (key(i), Some(val(1_000 + i)))),
2440                None,
2441            )
2442            .await;
2443            commit_writes(
2444                &mut db,
2445                (0..700).map(|i| (key(i), Some(val(10_000 + i)))),
2446                None,
2447            )
2448            .await;
2449
2450            // Pre-rewind size must actually exceed the rewind target so the rewind is not a
2451            // no-op.
2452            let pre_prune_size = db.size();
2453            assert!(pre_prune_size > rewind_target);
2454
2455            let prune_loc = Location::new(600);
2456            // prune_loc must cross at least one bitmap chunk boundary; otherwise the buggy
2457            // bitmap prune would correctly stay at 0 and the test would pass even unfixed.
2458            assert!(
2459                *prune_loc > BITMAP_CHUNK_BITS,
2460                "prune_loc {prune_loc:?} must exceed one bitmap chunk ({BITMAP_CHUNK_BITS} bits)"
2461            );
2462            // prune_loc must lie within the first journal section so the journal cannot
2463            // prune any section, leaving bounds.start at 0 to expose the bitmap drift.
2464            assert!(
2465                *prune_loc < ITEMS_PER_SECTION,
2466                "prune_loc {prune_loc:?} must be < {ITEMS_PER_SECTION} so the journal retains section 0"
2467            );
2468            assert!(db.inactivity_floor_loc() >= prune_loc);
2469
2470            db.prune(prune_loc).await.unwrap();
2471
2472            // Journal could not prune any section, so it still retains from 0. The bitmap
2473            // must therefore also remain at 0.
2474            let bounds = db.bounds();
2475            assert_eq!(bounds.start, Location::new(0));
2476            assert_eq!(
2477                db.bitmap.pruned_bits(),
2478                0,
2479                "bitmap pruned past journal retained start"
2480            );
2481
2482            // Rewind to the still-retained early commit must succeed and restore visible
2483            // state (root match implies the snapshot was rebuilt correctly).
2484            db.rewind(rewind_target).await.unwrap();
2485            assert_eq!(db.size(), rewind_target);
2486            assert_eq!(db.root(), root_at_target);
2487
2488            db.destroy().await.unwrap();
2489        });
2490    }
2491
2492    // --- MMB family tests ---
2493    //
2494    // The tests above use MMR-backed databases (via the concrete Db type aliases). The tests
2495    // below verify the same core operations work with the MMB family, exercising the generic
2496    // `init_fixed`/`init_variable` path with `mmb::Family`.
2497
2498    type MmbVariable = super::db::Db<
2499        crate::merkle::mmb::Family,
2500        Context,
2501        crate::journal::contiguous::variable::Journal<
2502            Context,
2503            super::operation::Operation<
2504                crate::merkle::mmb::Family,
2505                super::operation::update::Unordered<Digest, super::value::VariableEncoding<Digest>>,
2506            >,
2507        >,
2508        crate::index::unordered::Index<OneCap, crate::merkle::Location<crate::merkle::mmb::Family>>,
2509        Sha256,
2510        super::operation::update::Unordered<Digest, super::value::VariableEncoding<Digest>>,
2511        { crate::qmdb::any::BITMAP_CHUNK_BYTES },
2512        Sequential,
2513    >;
2514
2515    async fn open_mmb_db(context: Context, suffix: &str) -> MmbVariable {
2516        let cfg = variable_db_config::<OneCap>(suffix, &context);
2517        super::init(context, cfg).await.unwrap()
2518    }
2519
2520    async fn commit_writes_mmb(
2521        db: &mut MmbVariable,
2522        writes: impl IntoIterator<Item = (Digest, Option<Digest>)>,
2523        metadata: Option<Digest>,
2524    ) {
2525        let mut batch = db.new_batch();
2526        for (k, v) in writes {
2527            batch = batch.write(k, v);
2528        }
2529        let merkleized = batch.merkleize(db, metadata).await.unwrap();
2530        db.apply_batch(merkleized).await.unwrap();
2531        db.commit().await.unwrap();
2532    }
2533
2534    #[test_traced("INFO")]
2535    fn test_mmb_batch_crud() {
2536        let executor = deterministic::Runner::default();
2537        executor.start(|context| async move {
2538            let mut db = open_mmb_db(context.child("db"), "crud").await;
2539
2540            // Insert and read back.
2541            commit_writes_mmb(&mut db, [(key(0), Some(val(0)))], None).await;
2542            assert_eq!(db.get(&key(0)).await.unwrap(), Some(val(0)));
2543
2544            // Update existing key.
2545            commit_writes_mmb(&mut db, [(key(0), Some(val(1)))], None).await;
2546            assert_eq!(db.get(&key(0)).await.unwrap(), Some(val(1)));
2547
2548            // Delete key.
2549            commit_writes_mmb(&mut db, [(key(0), None)], None).await;
2550            assert!(db.get(&key(0)).await.unwrap().is_none());
2551
2552            // Multiple keys.
2553            commit_writes_mmb(
2554                &mut db,
2555                [(key(1), Some(val(1))), (key(2), Some(val(2)))],
2556                None,
2557            )
2558            .await;
2559            assert_eq!(db.get(&key(1)).await.unwrap(), Some(val(1)));
2560            assert_eq!(db.get(&key(2)).await.unwrap(), Some(val(2)));
2561
2562            db.destroy().await.unwrap();
2563        });
2564    }
2565
2566    #[test_traced("INFO")]
2567    fn test_mmb_batch_empty() {
2568        let executor = deterministic::Runner::default();
2569        executor.start(|context| async move {
2570            let mut db = open_mmb_db(context.child("db"), "empty").await;
2571            let root_before = db.root();
2572
2573            let merkleized = db.new_batch().merkleize(&db, None).await.unwrap();
2574            db.apply_batch(merkleized).await.unwrap();
2575            assert_ne!(db.root(), root_before);
2576
2577            commit_writes_mmb(&mut db, [(key(0), Some(val(0)))], None).await;
2578            assert_eq!(db.get(&key(0)).await.unwrap(), Some(val(0)));
2579
2580            db.destroy().await.unwrap();
2581        });
2582    }
2583
2584    #[test_traced("INFO")]
2585    fn test_mmb_batch_metadata() {
2586        let executor = deterministic::Runner::default();
2587        executor.start(|context| async move {
2588            let mut db = open_mmb_db(context.child("db"), "meta").await;
2589
2590            let metadata = val(42);
2591            commit_writes_mmb(&mut db, [(key(0), Some(val(0)))], Some(metadata)).await;
2592            assert_eq!(db.get_metadata().await.unwrap(), Some(metadata));
2593
2594            let merkleized = db.new_batch().merkleize(&db, None).await.unwrap();
2595            db.apply_batch(merkleized).await.unwrap();
2596            assert_eq!(db.get_metadata().await.unwrap(), None);
2597
2598            db.destroy().await.unwrap();
2599        });
2600    }
2601
2602    #[test_traced("WARN")]
2603    fn test_mmb_recovery() {
2604        let executor = deterministic::Runner::default();
2605        executor.start(|context| async move {
2606            let mut db =
2607                open_mmb_db(context.child("db").with_attribute("index", 0), "recovery").await;
2608
2609            commit_writes_mmb(&mut db, [(key(0), Some(val(0)))], Some(val(99))).await;
2610            commit_writes_mmb(&mut db, [(key(1), Some(val(1)))], None).await;
2611
2612            let root = db.root();
2613            let bounds = db.bounds();
2614            db.sync().await.unwrap();
2615            drop(db);
2616
2617            // Reopen and verify state.
2618            let db = open_mmb_db(context.child("db").with_attribute("index", 1), "recovery").await;
2619            assert_eq!(db.root(), root);
2620            assert_eq!(db.bounds(), bounds);
2621            assert_eq!(db.get(&key(0)).await.unwrap(), Some(val(0)));
2622            assert_eq!(db.get(&key(1)).await.unwrap(), Some(val(1)));
2623            assert_eq!(db.get_metadata().await.unwrap(), None);
2624
2625            db.destroy().await.unwrap();
2626        });
2627    }
2628
2629    #[test_traced("INFO")]
2630    fn test_mmb_prune() {
2631        let executor = deterministic::Runner::default();
2632        executor.start(|context| async move {
2633            let mut db = open_mmb_db(context.child("db"), "prune").await;
2634
2635            for i in 0u64..20 {
2636                commit_writes_mmb(&mut db, [(key(i), Some(val(i)))], None).await;
2637            }
2638
2639            let floor = db.inactivity_floor_loc();
2640            db.prune(floor).await.unwrap();
2641
2642            // All keys still accessible.
2643            for i in 0u64..20 {
2644                assert_eq!(db.get(&key(i)).await.unwrap(), Some(val(i)));
2645            }
2646
2647            db.destroy().await.unwrap();
2648        });
2649    }
2650
2651    /// One-stage pipelining lets the next batch be built while the prior batch commits.
2652    #[test_traced("INFO")]
2653    fn test_any_batch_single_stage_pipeline() {
2654        let executor = deterministic::Runner::default();
2655        executor.start(|context| async move {
2656            let ctx = context.child("db");
2657            let mut db: UnorderedVariable = UnorderedVariableDb::init(
2658                ctx.child("storage"),
2659                variable_db_config::<OneCap>("pipe", &ctx),
2660            )
2661            .await
2662            .unwrap();
2663
2664            {
2665                let mut batch = db.new_batch();
2666                batch = batch.write(key(0), Some(val(0)));
2667                let merkleized = batch.merkleize(&db, None).await.unwrap();
2668                db.apply_batch(merkleized).await.unwrap();
2669            }
2670
2671            let child_merkleized = {
2672                assert_eq!(db.get(&key(0)).await.unwrap(), Some(val(0)));
2673                let mut child = db.new_batch();
2674                child = child.write(key(1), Some(val(1)));
2675                child.merkleize(&db, None).await.unwrap()
2676            };
2677            db.commit().await.unwrap();
2678
2679            db.apply_batch(child_merkleized).await.unwrap();
2680            db.commit().await.unwrap();
2681
2682            assert_eq!(db.get(&key(0)).await.unwrap(), Some(val(0)));
2683            assert_eq!(db.get(&key(1)).await.unwrap(), Some(val(1)));
2684
2685            db.destroy().await.unwrap();
2686        });
2687    }
2688}
2689
2690#[cfg(test)]
2691mod bitmap_tests {
2692    //! Regression tests for activity-bitmap maintenance in `any::Db`. The mutation code in
2693    //! `apply_batch`, `prune_bitmap`, and `rewind` is independent of the snapshot index variant,
2694    //! so one variant (`unordered::variable`) suffices as the test bed.
2695    use crate::{
2696        merkle::Location,
2697        qmdb::any::unordered::variable::test::{create_test_config, AnyTest},
2698    };
2699    use commonware_cryptography::{Hasher as _, Sha256};
2700    use commonware_macros::{boxed, test_traced};
2701    use commonware_runtime::{
2702        deterministic::{self, Context},
2703        Runner as _, Supervisor as _,
2704    };
2705    use commonware_utils::bitmap::Readable as _;
2706
2707    /// Open a fresh test DB.
2708    async fn open_db(context: Context) -> AnyTest {
2709        let cfg = create_test_config(0, &context);
2710        AnyTest::init(context, cfg).await.unwrap()
2711    }
2712
2713    /// Active locations (bit=1) in `[pruned_bits, len)` of `db.bitmap`.
2714    fn bitmap_active_locs(db: &AnyTest) -> Vec<u64> {
2715        let b = &db.bitmap;
2716        (b.pruned_bits()..b.len())
2717            .filter(|loc| b.get_bit(*loc))
2718            .collect()
2719    }
2720
2721    /// Commit, drop, reopen, and assert the rebuilt bitmap matches the in-memory bitmap.
2722    #[boxed]
2723    async fn assert_oracle_round_trip(mut db: AnyTest, context: Context, label: &str) -> AnyTest {
2724        let pre_active = bitmap_active_locs(&db);
2725        let pre_len = db.bitmap.len();
2726        let pre_pruned = db.bitmap.pruned_bits();
2727
2728        db.commit().await.unwrap();
2729        drop(db);
2730
2731        let db = open_db(context.child("reopen").with_attribute("case", label)).await;
2732
2733        assert_eq!(
2734            db.bitmap.pruned_bits(),
2735            pre_pruned,
2736            "pruned_bits diverged on reopen",
2737        );
2738        assert_eq!(db.bitmap.len(), pre_len, "bitmap len diverged on reopen");
2739        assert_eq!(
2740            bitmap_active_locs(&db),
2741            pre_active,
2742            "active locations diverged on reopen",
2743        );
2744        db
2745    }
2746
2747    /// CommitFloor convention: only the *current* `last_commit_loc` carries bit=1; every earlier
2748    /// (now intermediate) commit boundary carries bit=0.
2749    ///
2750    /// Maintained by `apply_batch`'s explicit demote-then-promote pair on CommitFloor bits. If
2751    /// the demote step were missed, intermediate commits would persist at bit=1.
2752    #[test_traced]
2753    fn current_commit_floor_bit_is_one_others_zero() {
2754        deterministic::Runner::default().start(|context| async move {
2755            let mut db = open_db(context.child("db")).await;
2756
2757            // Apply three single-write batches; each produces one CommitFloor op.
2758            let mut commit_locs = Vec::new();
2759            for i in 0..3u64 {
2760                let key = Sha256::hash(&i.to_be_bytes());
2761                let batch = db
2762                    .new_batch()
2763                    .write(key, Some(vec![i as u8]))
2764                    .merkleize(&db, None)
2765                    .await
2766                    .unwrap();
2767                commit_locs.push(Location::<crate::merkle::mmr::Family>::new(
2768                    batch.bounds.total_size - 1,
2769                ));
2770                db.apply_batch(batch).await.unwrap();
2771            }
2772            db.commit().await.unwrap();
2773
2774            // Setup sanity: three strictly-increasing commit locations, all within the bitmap.
2775            assert_eq!(commit_locs.len(), 3);
2776            assert!(*commit_locs[0] < *commit_locs[1]);
2777            assert!(*commit_locs[1] < *commit_locs[2]);
2778            assert!(*commit_locs[2] < db.bitmap.len());
2779
2780            // Earlier two commits are intermediate -> bit=0.
2781            assert!(!db.bitmap.get_bit(*commit_locs[0]));
2782            assert!(!db.bitmap.get_bit(*commit_locs[1]));
2783            // Most recent commit is current -> bit=1.
2784            assert!(db.bitmap.get_bit(*commit_locs[2]));
2785
2786            let db = assert_oracle_round_trip(db, context, "commit_floor").await;
2787            db.destroy().await.unwrap();
2788        });
2789    }
2790
2791    /// `any::Db::rewind` restores bitmap state correctly.
2792    ///
2793    /// `any::rewind` is the sole writer of the bitmap during rewind; it must:
2794    ///   1. truncate the bitmap to the rewind size,
2795    ///   2. flip restored locs (committed snapshot entries the rewound tail had superseded) back
2796    ///      to active,
2797    ///   3. set the rewound tail's CommitFloor bit to 1 (the new current commit).
2798    ///
2799    /// The oracle round-trip catches all three: any divergence from `init_from_log`'s rebuild
2800    /// fails the comparison.
2801    #[test_traced]
2802    fn rewind_restores_bitmap_to_target_commit() {
2803        deterministic::Runner::default().start(|context| async move {
2804            let mut db = open_db(context.child("db")).await;
2805            let k1 = Sha256::hash(&[1]);
2806            let k2 = Sha256::hash(&[2]);
2807
2808            // Two committed batches; remember the size after the first.
2809            let b1 = db
2810                .new_batch()
2811                .write(k1, Some(vec![10]))
2812                .merkleize(&db, None)
2813                .await
2814                .unwrap();
2815            db.apply_batch(b1).await.unwrap();
2816            db.commit().await.unwrap();
2817            let size_after_first = Location::new(*db.last_commit_loc + 1);
2818
2819            let b2 = db
2820                .new_batch()
2821                .write(k2, Some(vec![20]))
2822                .merkleize(&db, None)
2823                .await
2824                .unwrap();
2825            db.apply_batch(b2).await.unwrap();
2826
2827            // Setup sanity: both keys present, db has advanced past size_after_first.
2828            assert_eq!(db.get(&k1).await.unwrap(), Some(vec![10]));
2829            assert_eq!(db.get(&k2).await.unwrap(), Some(vec![20]));
2830            assert!(*db.last_commit_loc + 1 > *size_after_first);
2831
2832            // Rewind to the state after the first commit.
2833            db.rewind(size_after_first).await.unwrap();
2834
2835            // Post-rewind: k2 gone, k1 remains.
2836            assert_eq!(db.get(&k1).await.unwrap(), Some(vec![10]));
2837            assert!(db.get(&k2).await.unwrap().is_none());
2838
2839            let db = assert_oracle_round_trip(db, context, "rewind").await;
2840            db.destroy().await.unwrap();
2841        });
2842    }
2843
2844    /// Floor-scan falls through to the uncommitted tail when the committed bitmap region runs
2845    /// out of active bits.
2846    ///
2847    /// `next_candidate` returns set-bit locations within `[floor, bitmap.len)` (skipping inactive
2848    /// ones), then sequential candidates beyond `bitmap.len` (uncommitted ancestor ops not
2849    /// tracked in the bitmap). The floor-raise loop's per-candidate revalidation is the only
2850    /// thing that prevents stale ancestor locations from being moved when a child batch
2851    /// supersedes the same key.
2852    ///
2853    /// Setup: 1 committed key + uncommitted parent re-touching that key + uncommitted child that
2854    /// supersedes the key AND writes many other keys. The added user mutations push
2855    /// `total_steps` past the active bits available in the committed region, forcing the scan
2856    /// to walk into the tail.
2857    ///
2858    /// Failure modes caught:
2859    /// - tail-fallthrough boundary off-by-one → wrong root,
2860    /// - missing floor-raise revalidation → parent's superseded loc gets moved → divergent
2861    ///   root,
2862    /// - bitmap state inconsistent with `init_from_log` → oracle reopen mismatch.
2863    #[test_traced]
2864    fn floor_scan_falls_through_to_uncommitted_tail() {
2865        deterministic::Runner::default().start(|context| async move {
2866            let mut db = open_db(context.child("db")).await;
2867            let anchor = Sha256::hash(&[0xAA]);
2868
2869            // Commit one key.
2870            let b = db
2871                .new_batch()
2872                .write(anchor, Some(vec![1]))
2873                .merkleize(&db, None)
2874                .await
2875                .unwrap();
2876            db.apply_batch(b).await.unwrap();
2877
2878            // Setup sanity: anchor in committed snapshot.
2879            assert_eq!(db.get(&anchor).await.unwrap(), Some(vec![1]));
2880            let committed_bitmap_len = db.bitmap.len();
2881
2882            // Uncommitted parent: re-touch anchor at a location above the committed bitmap.
2883            let parent = db
2884                .new_batch()
2885                .write(anchor, Some(vec![2]))
2886                .merkleize(&db, None)
2887                .await
2888                .unwrap();
2889            assert!(
2890                parent.bounds.total_size > committed_bitmap_len,
2891                "parent must extend past committed bitmap to exercise the tail path",
2892            );
2893
2894            // Uncommitted child: supersede anchor + add 16 more writes. The extra user_steps
2895            // ensure `total_steps` exceeds active bits in the committed region, forcing the
2896            // floor-raise scan into the uncommitted tail.
2897            let mut child_batch = parent.new_batch::<Sha256>();
2898            child_batch = child_batch.write(anchor, Some(vec![3]));
2899            for i in 0..16u64 {
2900                let k = Sha256::hash(&(1000 + i).to_be_bytes());
2901                child_batch = child_batch.write(k, Some(vec![i as u8]));
2902            }
2903            let child = child_batch.merkleize(&db, None).await.unwrap();
2904            assert!(
2905                child.bounds.total_size > committed_bitmap_len,
2906                "child must include an uncommitted tail beyond committed bitmap",
2907            );
2908            let expected_root = child.root();
2909
2910            // Apply. If tail-fallthrough or revalidation were wrong, the produced root would
2911            // diverge from the merkleize-time root.
2912            db.apply_batch(child).await.unwrap();
2913            assert_eq!(db.root(), expected_root);
2914            assert_eq!(db.get(&anchor).await.unwrap(), Some(vec![3]));
2915
2916            let db = assert_oracle_round_trip(db, context, "tail").await;
2917            db.destroy().await.unwrap();
2918        });
2919    }
2920}