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