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