Skip to main content

commonware_storage/qmdb/current/ordered/
fixed.rs

1//! An _ordered_ variant of a [crate::qmdb::current] authenticated database optimized for fixed-size
2//! values.
3//!
4//! This variant maintains the lexicographic-next active key for each active key, enabling exclusion
5//! proofs (proving a key is currently inactive). Use [crate::qmdb::current::unordered::fixed] if
6//! exclusion proofs are not needed.
7//!
8//! See [Db] for the main database type and [super::ExclusionProof] for proving key inactivity.
9
10pub use super::db::KeyValueProof;
11use crate::{
12    index::ordered::Index,
13    journal::contiguous::fixed::Journal,
14    merkle::{Graftable, Location},
15    qmdb::{
16        any::{ordered::fixed::Operation, value::FixedEncoding, FixedValue},
17        current::FixedConfig as Config,
18        Error,
19    },
20    translator::Translator,
21    Context,
22};
23use commonware_cryptography::Hasher;
24use commonware_parallel::Strategy;
25use commonware_utils::Array;
26
27pub type Db<F, E, K, V, H, T, const N: usize, S> = super::db::Db<
28    F,
29    E,
30    Journal<E, Operation<F, K, V>>,
31    K,
32    FixedEncoding<V>,
33    Index<T, Location<F>>,
34    H,
35    N,
36    S,
37>;
38
39impl<
40        F: Graftable,
41        E: Context,
42        K: Array,
43        V: FixedValue,
44        H: Hasher,
45        T: Translator,
46        const N: usize,
47        S: Strategy,
48    > Db<F, E, K, V, H, T, N, S>
49{
50    /// Initializes a [Db] from the given `config`.
51    /// The configured [`Strategy`] is used to parallelize merkleization.
52    pub async fn init(context: E, config: Config<T, S>) -> Result<Self, Error<F>> {
53        crate::qmdb::current::init(context, config).await
54    }
55}
56
57pub mod partitioned {
58    //! A variant of [super] that uses a partitioned index for the snapshot.
59
60    use super::*;
61    use crate::index::partitioned::ordered::Index;
62
63    /// A partitioned variant of [super::Db].
64    ///
65    /// The const generic `P` specifies the number of prefix bytes used for partitioning:
66    /// - `P = 1`: 256 partitions
67    /// - `P = 2`: 65,536 partitions
68    /// - `P = 3`: ~16 million partitions
69    pub type Db<F, E, K, V, H, T, const P: usize, const N: usize, S> =
70        crate::qmdb::current::ordered::db::Db<
71            F,
72            E,
73            Journal<E, Operation<F, K, V>>,
74            K,
75            FixedEncoding<V>,
76            Index<T, Location<F>, P>,
77            H,
78            N,
79            S,
80        >;
81
82    impl<
83            F: Graftable,
84            E: Context,
85            K: Array,
86            V: FixedValue,
87            H: Hasher,
88            T: Translator,
89            const P: usize,
90            const N: usize,
91            S: Strategy,
92        > Db<F, E, K, V, H, T, P, N, S>
93    {
94        /// Initializes a [Db] authenticated database from the given `config`.
95        /// The configured [`Strategy`] is used to parallelize merkleization.
96        pub async fn init(context: E, config: Config<T, S>) -> Result<Self, Error<F>> {
97            crate::qmdb::current::init(context, config).await
98        }
99    }
100}
101
102#[cfg(test)]
103pub mod test {
104    use super::*;
105    use crate::{
106        mmr,
107        qmdb::{
108            current::{ordered::tests as shared, tests::fixed_config},
109            Error,
110        },
111        translator::OneCap,
112    };
113    use commonware_cryptography::{sha256::Digest, Sha256};
114    use commonware_macros::test_traced;
115    use commonware_runtime::{deterministic, Runner as _, Supervisor as _};
116    use commonware_utils::{
117        bitmap::{Prunable as BitMap, Readable as _},
118        NZU64,
119    };
120
121    /// A type alias for the concrete [Db] type used in these unit tests.
122    type CurrentTest = Db<
123        mmr::Family,
124        deterministic::Context,
125        Digest,
126        Digest,
127        Sha256,
128        OneCap,
129        32,
130        commonware_parallel::Sequential,
131    >;
132
133    /// Return an [Db] database initialized with a fixed config.
134    async fn open_db(context: deterministic::Context, partition_prefix: String) -> CurrentTest {
135        let cfg = fixed_config::<OneCap>(&partition_prefix, &context);
136        CurrentTest::init(context, cfg).await.unwrap()
137    }
138
139    #[test_traced("DEBUG")]
140    pub fn test_current_db_verify_proof_over_bits_in_uncommitted_chunk() {
141        shared::test_verify_proof_over_bits_in_uncommitted_chunk(open_db);
142    }
143
144    #[test_traced("DEBUG")]
145    pub fn test_current_db_range_proofs() {
146        shared::test_range_proofs(open_db);
147    }
148
149    /// Regression test: requesting a range proof for a location in a pruned bitmap chunk
150    /// must return `Error::OperationPruned`, not panic in the bitmap accessor.
151    #[test_traced("DEBUG")]
152    pub fn test_range_proof_returns_error_on_pruned_chunks() {
153        let executor = deterministic::Runner::default();
154        executor.start(|context| async move {
155            let partition = "range-proofs-pruned".to_string();
156            let hasher = crate::qmdb::hasher::<Sha256>();
157            let mut db = open_db(context.child("db"), partition).await;
158
159            let chunk_bits = BitMap::<32>::CHUNK_SIZE_BITS;
160
161            // Repeatedly update the same key to generate many inactive operations,
162            // pushing the inactivity floor past at least one full bitmap chunk.
163            let key = Sha256::fill(0x11);
164            for i in 0..chunk_bits + 10 {
165                let value = Sha256::hash(&i.to_be_bytes());
166                let merkleized = db
167                    .new_batch()
168                    .write(key, Some(value))
169                    .merkleize(&db, None)
170                    .await
171                    .unwrap();
172                db.apply_batch(merkleized).await.unwrap();
173            }
174
175            // Prune the database
176            db.prune(db.sync_boundary()).await.unwrap();
177
178            assert!(
179                db.any.bitmap.pruned_chunks() > 0,
180                "expected at least one pruned chunk"
181            );
182
183            // Requesting a range proof at location 0 (in the pruned range) should return
184            // OperationPruned, not panic.
185            let result = db.range_proof(&hasher, Location::new(0), NZU64!(1)).await;
186            assert!(
187                matches!(result, Err(Error::OperationPruned(_))),
188                "expected OperationPruned, got {result:?}"
189            );
190
191            db.destroy().await.unwrap();
192        });
193    }
194
195    #[test_traced("DEBUG")]
196    pub fn test_current_db_key_value_proof() {
197        shared::test_key_value_proof(open_db);
198    }
199
200    #[test_traced("WARN")]
201    pub fn test_current_db_proving_repeated_updates() {
202        shared::test_proving_repeated_updates(open_db);
203    }
204
205    #[test_traced("DEBUG")]
206    pub fn test_current_db_exclusion_proofs() {
207        shared::test_exclusion_proofs(open_db);
208    }
209}