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}