commonware_storage/qmdb/store/
mod.rs

1//! Traits for interacting with stores whose state is derived from an append-only log of
2//! state-changing operations.
3//!
4//! # Pruning
5//!
6//! A log based store maintains a location before which all operations are inactive, called the
7//! _inactivity floor_. These operations can be cleaned from storage by calling [PrunableStore::prune].
8
9use crate::{
10    mmr::{Location, Proof},
11    qmdb::Error,
12};
13use commonware_codec::CodecShared;
14use commonware_cryptography::Digest;
15use core::future::Future;
16use std::num::NonZeroU64;
17
18mod batch;
19pub mod db;
20#[cfg(test)]
21pub use batch::tests as batch_tests;
22
23/// Sealed trait for store state types.
24mod private {
25    pub trait Sealed {}
26}
27
28/// Trait for valid store state types.
29pub trait State: private::Sealed + Sized + Send + Sync {}
30
31/// Marker type for a store in a "durable" state (no uncommitted operations).
32#[derive(Clone, Copy, Debug)]
33pub struct Durable;
34
35impl private::Sealed for Durable {}
36impl State for Durable {}
37
38/// Marker type for a store in a "non-durable" state (may contain uncommitted operations).
39#[derive(Clone, Debug, Default)]
40pub struct NonDurable {
41    /// The number of _steps_ to raise the inactivity floor. Each step involves moving exactly one
42    /// active operation to tip.
43    pub(crate) steps: u64,
44}
45
46impl private::Sealed for NonDurable {}
47impl State for NonDurable {}
48
49/// A trait for a store based on an append-only log of operations.
50pub trait LogStore: Send + Sync {
51    type Value: CodecShared + Clone;
52
53    /// Returns true if there are no active keys in the database.
54    fn is_empty(&self) -> bool;
55
56    /// The number of operations that have been applied to this db, including those that have been
57    /// pruned and those that are not yet committed.
58    fn op_count(&self) -> Location;
59
60    /// Return the inactivity floor location. This is the location before which all operations are
61    /// known to be inactive. Operations before this point can be safely pruned.
62    fn inactivity_floor_loc(&self) -> Location;
63
64    /// Get the metadata associated with the last commit.
65    fn get_metadata(&self) -> impl Future<Output = Result<Option<Self::Value>, Error>> + Send;
66}
67
68/// A trait for stores that can be pruned.
69pub trait PrunableStore: LogStore {
70    /// Prune historical operations prior to `loc`.
71    fn prune(&mut self, loc: Location) -> impl Future<Output = Result<(), Error>> + Send;
72}
73
74/// A trait for stores that support authentication through merkleization and inclusion proofs.
75pub trait MerkleizedStore: LogStore {
76    /// The digest type used for authentication.
77    type Digest: Digest;
78
79    /// The operation type stored in the log.
80    type Operation;
81
82    /// Returns the root digest of the authenticated store.
83    fn root(&self) -> Self::Digest;
84
85    /// Generate and return:
86    ///  1. a proof of all operations applied to the store in the range starting at (and including)
87    ///     location `start_loc`, and ending at the first of either:
88    ///     - the last operation performed, or
89    ///     - the operation `max_ops` from the start.
90    ///  2. the operations corresponding to the leaves in this range.
91    ///
92    /// # Errors
93    ///
94    /// Returns [crate::mmr::Error::LocationOverflow] if `start_loc` > [crate::mmr::MAX_LOCATION].
95    /// Returns [crate::mmr::Error::RangeOutOfBounds] if `start_loc` >= `op_count`.
96    #[allow(clippy::type_complexity)]
97    fn proof(
98        &self,
99        start_loc: Location,
100        max_ops: NonZeroU64,
101    ) -> impl Future<Output = Result<(Proof<Self::Digest>, Vec<Self::Operation>), Error>> + Send
102    {
103        self.historical_proof(self.op_count(), start_loc, max_ops)
104    }
105
106    /// Generate and return:
107    ///  1. a proof of all operations applied to the store in the range starting at (and including)
108    ///     location `start_loc`, and ending at the first of either:
109    ///     - the last operation performed, or
110    ///     - the operation `max_ops` from the start.
111    ///  2. the operations corresponding to the leaves in this range.
112    ///
113    /// for the store when it had `historical_size` operations.
114    ///
115    /// # Errors
116    ///
117    /// Returns [crate::mmr::Error::LocationOverflow] if `start_loc` > [crate::mmr::MAX_LOCATION].
118    /// Returns [crate::mmr::Error::RangeOutOfBounds] if `start_loc` >= `op_count`.
119    #[allow(clippy::type_complexity)]
120    fn historical_proof(
121        &self,
122        historical_size: Location,
123        start_loc: Location,
124        max_ops: NonZeroU64,
125    ) -> impl Future<Output = Result<(Proof<Self::Digest>, Vec<Self::Operation>), Error>> + Send;
126}
127
128#[cfg(test)]
129pub(crate) mod tests {
130    use super::{LogStore, MerkleizedStore, PrunableStore};
131    use crate::mmr::Location;
132    use commonware_utils::NZU64;
133
134    pub fn assert_send<T: Send>(_: T) {}
135
136    #[allow(dead_code)]
137    pub fn assert_log_store<T: LogStore>(db: &T) {
138        assert_send(db.get_metadata());
139    }
140
141    #[allow(dead_code)]
142    pub fn assert_prunable_store<T: PrunableStore>(db: &mut T, loc: Location) {
143        assert_send(db.prune(loc));
144    }
145
146    #[allow(dead_code)]
147    pub fn assert_merkleized_store<T: MerkleizedStore>(db: &T, loc: Location) {
148        assert_send(db.proof(loc, NZU64!(1)));
149    }
150}