Skip to main content

commonware_storage/qmdb/current/
db.rs

1//! A shared, generic implementation of the _Current_ QMDB.
2//!
3//! The impl blocks in this file defines shared functionality across all Current QMDB variants.
4
5use crate::{
6    bitmap,
7    index::Unordered as UnorderedIndex,
8    journal::{
9        contiguous::{Contiguous, MutableContiguous},
10        Error as JournalError,
11    },
12    mmr::{
13        self,
14        grafting::{Hasher as GraftingHasher, Storage as GraftingStorage},
15        hasher::Hasher as MmrHasher,
16        journaled::Mmr,
17        Location, Proof, StandardHasher,
18    },
19    qmdb::{
20        any::{
21            self,
22            operation::{update::Update, Operation},
23            ValueEncoding,
24        },
25        current::proof::RangeProof,
26        store::{self, LogStore, MerkleizedStore, PrunableStore},
27        DurabilityState, Durable, Error, NonDurable,
28    },
29    Persistable,
30};
31use commonware_codec::{Codec, CodecShared};
32use commonware_cryptography::{Digest, DigestOf, Hasher};
33use commonware_runtime::{Clock, Metrics, Storage};
34use commonware_utils::{bitmap::Prunable as PrunableBitMap, Array};
35use core::{num::NonZeroU64, ops::Range};
36
37/// Get the grafting height for a bitmap with chunk size determined by N.
38const fn grafting_height<const N: usize>() -> u32 {
39    PrunableBitMap::<N>::CHUNK_SIZE_BITS.trailing_zeros()
40}
41
42mod private {
43    pub trait Sealed {}
44}
45
46/// Trait for valid [Db] type states.
47pub trait State<D: Digest>: private::Sealed + Sized + Send + Sync {
48    /// The merkleization type state for the inner `any::db::Db`.
49    type AnyState: mmr::mem::State<D>;
50    /// The merkleization type state for the inner [bitmap::BitMap].
51    type BitMapState: bitmap::State<D>;
52}
53
54/// Merkleized state: the database has been merkleized and the root is cached.
55pub struct Merkleized<D: Digest> {
56    /// The cached root of the database (combining bitmap and operations MMR).
57    pub(super) root: D,
58}
59
60impl<D: Digest> private::Sealed for Merkleized<D> {}
61impl<D: Digest> State<D> for Merkleized<D> {
62    type AnyState = mmr::mem::Clean<D>;
63    type BitMapState = bitmap::Merkleized<D>;
64}
65
66/// Unmerkleized state: the database has pending changes not yet merkleized.
67pub struct Unmerkleized;
68
69impl private::Sealed for Unmerkleized {}
70impl<D: Digest> State<D> for Unmerkleized {
71    type AnyState = mmr::mem::Dirty;
72    type BitMapState = bitmap::Unmerkleized;
73}
74
75/// A Current QMDB implementation generic over ordered/unordered keys and variable/fixed values.
76pub struct Db<
77    E: Storage + Clock + Metrics,
78    C: Contiguous<Item: CodecShared>,
79    I: UnorderedIndex<Value = Location>,
80    H: Hasher,
81    U: Send + Sync,
82    const N: usize,
83    S: State<DigestOf<H>> = Merkleized<DigestOf<H>>,
84    D: DurabilityState = Durable,
85> {
86    /// An authenticated database that provides the ability to prove whether a key ever had a
87    /// specific value.
88    pub(super) any: any::db::Db<E, C, I, H, U, S::AnyState, D>,
89
90    /// The bitmap over the activity status of each operation. Supports augmenting [Db] proofs in
91    /// order to further prove whether a key _currently_ has a specific value.
92    pub(super) status: bitmap::BitMap<E, H::Digest, N, S::BitMapState>,
93
94    /// Type state based on whether the database is [Merkleized] or [Unmerkleized].
95    pub(super) state: S,
96}
97
98// Functionality shared across all DB states, such as most non-mutating operations.
99impl<E, K, V, C, I, H, U, const N: usize, S, D> Db<E, C, I, H, U, N, S, D>
100where
101    E: Storage + Clock + Metrics,
102    K: Array,
103    V: ValueEncoding,
104    U: Update<K, V>,
105    C: Contiguous<Item = Operation<K, V, U>>,
106    I: UnorderedIndex<Value = Location>,
107    H: Hasher,
108    S: State<DigestOf<H>>,
109    D: DurabilityState,
110    Operation<K, V, U>: Codec,
111{
112    /// Return the inactivity floor location. This is the location before which all operations are
113    /// known to be inactive. Operations before this point can be safely pruned.
114    pub const fn inactivity_floor_loc(&self) -> Location {
115        self.any.inactivity_floor_loc()
116    }
117
118    /// Whether the snapshot currently has no active keys.
119    pub const fn is_empty(&self) -> bool {
120        self.any.is_empty()
121    }
122
123    /// Get the metadata associated with the last commit.
124    pub async fn get_metadata(&self) -> Result<Option<V::Value>, Error> {
125        self.any.get_metadata().await
126    }
127
128    /// Get the level of the base MMR into which we are grafting.
129    ///
130    /// This value is log2 of the chunk size in bits. Since we assume the chunk size is a power of
131    /// 2, we compute this from trailing_zeros.
132    pub const fn grafting_height() -> u32 {
133        grafting_height::<N>()
134    }
135
136    /// Returns a proof that the specified range of operations are part of the database, along with
137    /// the operations from the range. A truncated range (from hitting the max) can be detected by
138    /// looking at the length of the returned operations vector. Also returns the bitmap chunks
139    /// required to verify the proof.
140    ///
141    /// # Errors
142    ///
143    /// Returns [mmr::Error::LocationOverflow] if `start_loc` > [mmr::MAX_LOCATION].
144    /// Returns [mmr::Error::RangeOutOfBounds] if `start_loc` >= number of leaves in the MMR.
145    /// Return true if the given sequence of `ops` were applied starting at location `start_loc` in
146    /// the log with the provided root.
147    pub fn verify_range_proof(
148        hasher: &mut H,
149        proof: &RangeProof<H::Digest>,
150        start_loc: Location,
151        ops: &[Operation<K, V, U>],
152        chunks: &[[u8; N]],
153        root: &H::Digest,
154    ) -> bool {
155        let height = Self::grafting_height();
156
157        proof.verify(hasher, height, start_loc, ops, chunks, root)
158    }
159}
160
161// Functionality shared across Merkleized states, such as the ability to prune the log and retrieve
162// the state root
163impl<E, K, V, U, C, I, H, D, const N: usize> Db<E, C, I, H, U, N, Merkleized<DigestOf<H>>, D>
164where
165    E: Storage + Clock + Metrics,
166    K: Array,
167    V: ValueEncoding,
168    U: Update<K, V>,
169    C: MutableContiguous<Item = Operation<K, V, U>> + Persistable<Error = JournalError>,
170    I: UnorderedIndex<Value = Location>,
171    H: Hasher,
172    D: DurabilityState,
173    Operation<K, V, U>: Codec,
174{
175    pub const fn root(&self) -> H::Digest {
176        self.state.root
177    }
178
179    /// Returns a proof that the specified range of operations are part of the database, along with
180    /// the operations from the range. A truncated range (from hitting the max) can be detected by
181    /// looking at the length of the returned operations vector. Also returns the bitmap chunks
182    /// required to verify the proof.
183    ///
184    /// # Errors
185    ///
186    /// Returns [mmr::Error::LocationOverflow] if `start_loc` > [mmr::MAX_LOCATION].
187    /// Returns [mmr::Error::RangeOutOfBounds] if `start_loc` >= number of leaves in the MMR.
188    pub async fn range_proof(
189        &self,
190        hasher: &mut H,
191        start_loc: Location,
192        max_ops: NonZeroU64,
193    ) -> Result<(RangeProof<H::Digest>, Vec<Operation<K, V, U>>, Vec<[u8; N]>), Error> {
194        RangeProof::<H::Digest>::new_with_ops(
195            hasher,
196            &self.status,
197            Self::grafting_height(),
198            &self.any.log.mmr,
199            &self.any.log,
200            start_loc,
201            max_ops,
202        )
203        .await
204    }
205
206    /// Prunes historical operations prior to `prune_loc`. This does not affect the db's root or
207    /// snapshot.
208    ///
209    /// # Errors
210    ///
211    /// - Returns [Error::PruneBeyondMinRequired] if `prune_loc` > inactivity floor.
212    /// - Returns [mmr::Error::LocationOverflow] if `prune_loc` > [mmr::MAX_LOCATION].
213    pub async fn prune(&mut self, prune_loc: Location) -> Result<(), Error> {
214        // Write the pruned portion of the bitmap to disk *first* to ensure recovery in case of
215        // failure during pruning. If we don't do this, we may not be able to recover the bitmap
216        // because it may require replaying of pruned operations.
217        self.status.write_pruned().await?;
218
219        self.any.prune(prune_loc).await
220    }
221}
222
223// Functionality specific to (Merkleized, Durable) state, such as ability to persist the database.
224impl<E, K, V, U, C, I, H, const N: usize> Db<E, C, I, H, U, N, Merkleized<DigestOf<H>>, Durable>
225where
226    E: Storage + Clock + Metrics,
227    K: Array,
228    V: ValueEncoding,
229    U: Update<K, V>,
230    C: MutableContiguous<Item = Operation<K, V, U>> + Persistable<Error = JournalError>,
231    I: UnorderedIndex<Value = Location>,
232    H: Hasher,
233    Operation<K, V, U>: Codec,
234{
235    /// Sync all database state to disk.
236    pub async fn sync(&mut self) -> Result<(), Error> {
237        self.any.sync().await?;
238
239        // Write the bitmap pruning boundary to disk so that next startup doesn't have to
240        // re-Merkleize the inactive portion up to the inactivity floor.
241        self.status.write_pruned().await.map_err(Into::into)
242    }
243
244    /// Destroy the db, removing all data from disk.
245    pub async fn destroy(self) -> Result<(), Error> {
246        // Clean up bitmap metadata partition.
247        self.status.destroy().await?;
248
249        // Clean up Any components (MMR and log).
250        self.any.destroy().await
251    }
252
253    /// Convert this database into a mutable state.
254    pub fn into_mutable(self) -> Db<E, C, I, H, U, N, Unmerkleized, NonDurable> {
255        Db {
256            any: self.any.into_mutable(),
257            status: self.status.into_dirty(),
258            state: Unmerkleized,
259        }
260    }
261}
262
263// Functionality shared across Unmerkleized states.
264impl<E, K, V, U, C, I, H, const N: usize, D> Db<E, C, I, H, U, N, Unmerkleized, D>
265where
266    E: Storage + Clock + Metrics,
267    K: Array,
268    V: ValueEncoding,
269    U: Update<K, V>,
270    C: Contiguous<Item = Operation<K, V, U>>,
271    I: UnorderedIndex<Value = Location>,
272    H: Hasher,
273    D: DurabilityState,
274    Operation<K, V, U>: Codec,
275{
276    /// Merkleize the database and transition to the provable state.
277    pub async fn into_merkleized(
278        self,
279    ) -> Result<Db<E, C, I, H, U, N, Merkleized<DigestOf<H>>, D>, Error> {
280        // Merkleize the any db
281        let mut any = self.any.into_merkleized();
282
283        // Merkleize the bitmap using the clean MMR
284        let hasher = &mut any.log.hasher;
285        let mut status = merkleize_grafted_bitmap(hasher, self.status, &any.log.mmr).await?;
286
287        // Prune the bitmap of no-longer-necessary bits.
288        status.prune_to_bit(*any.inactivity_floor_loc)?;
289
290        // Compute and cache the root
291        let root = root(hasher, &status, &any.log.mmr).await?;
292
293        Ok(Db {
294            any,
295            status,
296            state: Merkleized { root },
297        })
298    }
299}
300
301// Functionality specific to (Unmerkleized,Durable) state.
302impl<E, K, V, U, C, I, H, const N: usize> Db<E, C, I, H, U, N, Unmerkleized, Durable>
303where
304    E: Storage + Clock + Metrics,
305    K: Array,
306    V: ValueEncoding,
307    U: Update<K, V>,
308    C: Contiguous<Item = Operation<K, V, U>>,
309    I: UnorderedIndex<Value = Location>,
310    H: Hasher,
311    Operation<K, V, U>: Codec,
312{
313    /// Convert this database into a mutable state.
314    pub fn into_mutable(self) -> Db<E, C, I, H, U, N, Unmerkleized, NonDurable> {
315        Db {
316            any: self.any.into_mutable(),
317            status: self.status,
318            state: Unmerkleized,
319        }
320    }
321}
322
323// Functionality specific to (Unmerkleized, NonDurable) state.
324impl<E, K, V, U, C, I, H, const N: usize> Db<E, C, I, H, U, N, Unmerkleized, NonDurable>
325where
326    E: Storage + Clock + Metrics,
327    K: Array,
328    V: ValueEncoding,
329    U: Update<K, V>,
330    C: MutableContiguous<Item = Operation<K, V, U>> + Persistable<Error = JournalError>,
331    I: UnorderedIndex<Value = Location>,
332    H: Hasher,
333    Operation<K, V, U>: Codec,
334{
335    /// Raises the activity floor according to policy followed by appending a commit operation with
336    /// the provided `metadata` and the new inactivity floor value. Returns the `[start_loc,
337    /// end_loc)` location range of committed operations.
338    async fn apply_commit_op(
339        &mut self,
340        metadata: Option<V::Value>,
341    ) -> Result<Range<Location>, Error> {
342        let start_loc = self.any.last_commit_loc + 1;
343
344        // Inactivate the current commit operation.
345        self.status.set_bit(*self.any.last_commit_loc, false);
346
347        // Raise the inactivity floor by taking `self.steps` steps, plus 1 to account for the
348        // previous commit becoming inactive.
349        let inactivity_floor_loc = self.any.raise_floor_with_bitmap(&mut self.status).await?;
350
351        // Append the commit operation with the new floor and tag it as active in the bitmap.
352        self.status.push(true);
353        let commit_op = Operation::CommitFloor(metadata, inactivity_floor_loc);
354
355        self.any.apply_commit_op(commit_op).await?;
356
357        Ok(start_loc..self.any.log.bounds().end)
358    }
359
360    /// Commit any pending operations to the database, ensuring their durability upon return.
361    /// This transitions to the Durable state without merkleizing. Returns the committed database
362    /// and the `[start_loc, end_loc)` range of committed operations.
363    pub async fn commit(
364        mut self,
365        metadata: Option<V::Value>,
366    ) -> Result<(Db<E, C, I, H, U, N, Unmerkleized, Durable>, Range<Location>), Error> {
367        let range = self.apply_commit_op(metadata).await?;
368
369        // Transition to Durable state without merkleizing
370        let any = any::db::Db {
371            log: self.any.log,
372            inactivity_floor_loc: self.any.inactivity_floor_loc,
373            last_commit_loc: self.any.last_commit_loc,
374            snapshot: self.any.snapshot,
375            durable_state: store::Durable,
376            active_keys: self.any.active_keys,
377            _update: core::marker::PhantomData,
378        };
379
380        Ok((
381            Db {
382                any,
383                status: self.status,
384                state: Unmerkleized,
385            },
386            range,
387        ))
388    }
389}
390
391// Functionality specific to (Merkleized, NonDurable) state.
392impl<E, K, V, U, C, I, H, const N: usize> Db<E, C, I, H, U, N, Merkleized<DigestOf<H>>, NonDurable>
393where
394    E: Storage + Clock + Metrics,
395    K: Array,
396    V: ValueEncoding,
397    U: Update<K, V>,
398    C: MutableContiguous<Item = Operation<K, V, U>> + Persistable<Error = JournalError>,
399    I: UnorderedIndex<Value = Location>,
400    H: Hasher,
401    Operation<K, V, U>: Codec,
402{
403    /// Convert this database into a mutable state.
404    pub fn into_mutable(self) -> Db<E, C, I, H, U, N, Unmerkleized, NonDurable> {
405        Db {
406            any: self.any.into_mutable(),
407            status: self.status.into_dirty(),
408            state: Unmerkleized,
409        }
410    }
411}
412
413impl<E, K, V, U, C, I, H, const N: usize> Persistable
414    for Db<E, C, I, H, U, N, Merkleized<DigestOf<H>>, Durable>
415where
416    E: Storage + Clock + Metrics,
417    K: Array,
418    V: ValueEncoding,
419    U: Update<K, V>,
420    C: MutableContiguous<Item = Operation<K, V, U>> + Persistable<Error = JournalError>,
421    I: UnorderedIndex<Value = Location>,
422    H: Hasher,
423    Operation<K, V, U>: Codec,
424{
425    type Error = Error;
426
427    async fn commit(&mut self) -> Result<(), Error> {
428        // No-op, DB already in recoverable state.
429        Ok(())
430    }
431
432    async fn sync(&mut self) -> Result<(), Error> {
433        self.sync().await
434    }
435
436    async fn destroy(self) -> Result<(), Error> {
437        self.destroy().await
438    }
439}
440
441// MerkleizedStore for Merkleized states (both Durable and NonDurable)
442// TODO(https://github.com/commonwarexyz/monorepo/issues/2560): This is broken -- it's computing
443// proofs only over the any db mmr not the grafted mmr, so they won't validate against the grafted
444// root.
445impl<E, K, V, U, C, I, H, D, const N: usize> MerkleizedStore
446    for Db<E, C, I, H, U, N, Merkleized<DigestOf<H>>, D>
447where
448    E: Storage + Clock + Metrics,
449    K: Array,
450    V: ValueEncoding,
451    U: Update<K, V>,
452    C: MutableContiguous<Item = Operation<K, V, U>> + Persistable<Error = JournalError>,
453    I: UnorderedIndex<Value = Location>,
454    H: Hasher,
455    D: DurabilityState,
456    Operation<K, V, U>: Codec,
457{
458    type Digest = H::Digest;
459    type Operation = Operation<K, V, U>;
460
461    fn root(&self) -> H::Digest {
462        self.root()
463    }
464
465    async fn historical_proof(
466        &self,
467        historical_size: Location,
468        start_loc: Location,
469        max_ops: NonZeroU64,
470    ) -> Result<(Proof<Self::Digest>, Vec<Self::Operation>), Error> {
471        self.any
472            .historical_proof(historical_size, start_loc, max_ops)
473            .await
474    }
475}
476
477impl<E, K, V, U, C, I, H, const N: usize, S, D> LogStore for Db<E, C, I, H, U, N, S, D>
478where
479    E: Storage + Clock + Metrics,
480    K: Array,
481    V: ValueEncoding,
482    U: Update<K, V>,
483    C: Contiguous<Item = Operation<K, V, U>>,
484    I: UnorderedIndex<Value = Location>,
485    H: Hasher,
486    S: State<DigestOf<H>>,
487    D: DurabilityState,
488    Operation<K, V, U>: Codec,
489{
490    type Value = V::Value;
491
492    fn bounds(&self) -> std::ops::Range<Location> {
493        self.any.bounds()
494    }
495
496    fn inactivity_floor_loc(&self) -> Location {
497        self.inactivity_floor_loc()
498    }
499
500    async fn get_metadata(&self) -> Result<Option<V::Value>, Error> {
501        self.get_metadata().await
502    }
503
504    fn is_empty(&self) -> bool {
505        self.is_empty()
506    }
507}
508
509impl<E, K, V, U, C, I, H, const N: usize, D> PrunableStore
510    for Db<E, C, I, H, U, N, Merkleized<DigestOf<H>>, D>
511where
512    E: Storage + Clock + Metrics,
513    K: Array,
514    V: ValueEncoding,
515    U: Update<K, V>,
516    C: MutableContiguous<Item = Operation<K, V, U>> + Persistable<Error = JournalError>,
517    I: UnorderedIndex<Value = Location>,
518    H: Hasher,
519    D: DurabilityState,
520    Operation<K, V, U>: Codec,
521{
522    async fn prune(&mut self, prune_loc: Location) -> Result<(), Error> {
523        self.prune(prune_loc).await
524    }
525}
526
527/// Return the root of the current QMDB represented by the provided mmr and bitmap.
528pub(super) async fn root<E: Storage + Clock + Metrics, H: Hasher, const N: usize>(
529    hasher: &mut StandardHasher<H>,
530    status: &bitmap::MerkleizedBitMap<E, H::Digest, N>,
531    mmr: &Mmr<E, H::Digest, mmr::mem::Clean<DigestOf<H>>>,
532) -> Result<H::Digest, Error> {
533    let grafted_mmr = GraftingStorage::<'_, H, _, _>::new(status, mmr, grafting_height::<N>());
534    let mmr_root = grafted_mmr.root(hasher).await?;
535
536    // If we are on a chunk boundary, then the mmr_root fully captures the state of the DB.
537    let (last_chunk, next_bit) = status.last_chunk();
538    if next_bit == PrunableBitMap::<N>::CHUNK_SIZE_BITS {
539        // Last chunk is complete, no partial chunk to add
540        return Ok(mmr_root);
541    }
542
543    // There are bits in an uncommitted (partial) chunk, so we need to incorporate that information
544    // into the root digest to fully capture the database state. We do so by hashing the mmr root
545    // along with the number of bits within the last chunk and the digest of the last chunk.
546    hasher.inner().update(last_chunk);
547    let last_chunk_digest = hasher.inner().finalize();
548
549    Ok(bitmap::partial_chunk_root::<H, N>(
550        hasher.inner(),
551        &mmr_root,
552        next_bit,
553        &last_chunk_digest,
554    ))
555}
556
557/// Consumes an `UnmerkleizedBitMap`, performs merkleization using the provided hasher and MMR storage,
558/// and returns a `MerkleizedBitMap` containing the merkleized result.
559///
560/// # Arguments
561/// * `hasher` - The hasher used for merkleization.
562/// * `status` - The `UnmerkleizedBitMap` to be merkleized. Ownership is taken.
563/// * `mmr` - The MMR storage used for grafting.
564pub(super) async fn merkleize_grafted_bitmap<E, H, const N: usize>(
565    hasher: &mut StandardHasher<H>,
566    status: bitmap::UnmerkleizedBitMap<E, H::Digest, N>,
567    mmr: &impl mmr::storage::Storage<H::Digest>,
568) -> Result<bitmap::MerkleizedBitMap<E, H::Digest, N>, Error>
569where
570    E: Storage + Clock + Metrics,
571    H: Hasher,
572{
573    let mut grafter = GraftingHasher::new(hasher, grafting_height::<N>());
574    grafter
575        .load_grafted_digests(&status.dirty_chunks(), mmr)
576        .await?;
577    status.merkleize(&mut grafter).await.map_err(Into::into)
578}