Skip to main content

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    /// Return [start, end) where `start` and `end - 1` are the Locations of the oldest and newest
57    /// retained operations respectively.
58    fn bounds(&self) -> std::ops::Range<Location>;
59
60    /// Return the Location of the next operation appended to this db.
61    fn size(&self) -> Location {
62        self.bounds().end
63    }
64
65    /// Return the inactivity floor location. This is the location before which all operations are
66    /// known to be inactive. Operations before this point can be safely pruned.
67    fn inactivity_floor_loc(&self) -> Location;
68
69    /// Get the metadata associated with the last commit.
70    fn get_metadata(&self) -> impl Future<Output = Result<Option<Self::Value>, Error>> + Send;
71}
72
73/// A trait for stores that can be pruned.
74pub trait PrunableStore: LogStore {
75    /// Prune historical operations prior to `loc`.
76    fn prune(&mut self, loc: Location) -> impl Future<Output = Result<(), Error>> + Send;
77}
78
79/// A trait for stores that support authentication through merkleization and inclusion proofs.
80pub trait MerkleizedStore: LogStore {
81    /// The digest type used for authentication.
82    type Digest: Digest;
83
84    /// The operation type stored in the log.
85    type Operation;
86
87    /// Returns the root digest of the authenticated store.
88    fn root(&self) -> Self::Digest;
89
90    /// Generate and return:
91    ///  1. a proof of all operations applied to the store in the range starting at (and including)
92    ///     location `start_loc`, and ending at the first of either:
93    ///     - the last operation performed, or
94    ///     - the operation `max_ops` from the start.
95    ///  2. the operations corresponding to the leaves in this range.
96    ///
97    /// # Errors
98    ///
99    /// Returns [crate::mmr::Error::LocationOverflow] if `start_loc` > [crate::mmr::MAX_LOCATION].
100    /// Returns [crate::mmr::Error::RangeOutOfBounds] if `start_loc` >= `op_count`.
101    #[allow(clippy::type_complexity)]
102    fn proof(
103        &self,
104        start_loc: Location,
105        max_ops: NonZeroU64,
106    ) -> impl Future<Output = Result<(Proof<Self::Digest>, Vec<Self::Operation>), Error>> + Send
107    {
108        self.historical_proof(self.bounds().end, start_loc, max_ops)
109    }
110
111    /// Generate and return:
112    ///  1. a proof of all operations applied to the store in the range starting at (and including)
113    ///     location `start_loc`, and ending at the first of either:
114    ///     - the last operation performed, or
115    ///     - the operation `max_ops` from the start.
116    ///  2. the operations corresponding to the leaves in this range.
117    ///
118    /// for the store when it had `historical_size` operations.
119    ///
120    /// # Errors
121    ///
122    /// Returns [crate::mmr::Error::LocationOverflow] if `start_loc` > [crate::mmr::MAX_LOCATION].
123    /// Returns [crate::mmr::Error::RangeOutOfBounds] if `start_loc` >= `op_count`.
124    #[allow(clippy::type_complexity)]
125    fn historical_proof(
126        &self,
127        historical_size: Location,
128        start_loc: Location,
129        max_ops: NonZeroU64,
130    ) -> impl Future<Output = Result<(Proof<Self::Digest>, Vec<Self::Operation>), Error>> + Send;
131}
132
133#[cfg(test)]
134pub(crate) mod tests {
135    use super::{LogStore, MerkleizedStore, PrunableStore};
136    use crate::mmr::Location;
137    use commonware_utils::NZU64;
138
139    pub fn assert_send<T: Send>(_: T) {}
140
141    #[allow(dead_code)]
142    pub fn assert_log_store<T: LogStore>(db: &T) {
143        assert_send(db.get_metadata());
144    }
145
146    #[allow(dead_code)]
147    pub fn assert_prunable_store<T: PrunableStore>(db: &mut T, loc: Location) {
148        assert_send(db.prune(loc));
149    }
150
151    #[allow(dead_code)]
152    pub fn assert_merkleized_store<T: MerkleizedStore>(db: &T, loc: Location) {
153        assert_send(db.proof(loc, NZU64!(1)));
154    }
155}