Skip to main content

commonware_storage/qmdb/current/ordered/
db.rs

1//! Shared implementation for ordered Current QMDB variants.
2//!
3//! This module contains impl blocks that are generic over `ValueEncoding`, allowing them to be
4//! used by both fixed and variable ordered QMDB implementations.
5
6use crate::{
7    index::Ordered as OrderedIndex,
8    journal::contiguous::{Contiguous, Mutable, Reader},
9    kv,
10    mmr::Location,
11    qmdb::{
12        any::{
13            ordered::{Operation, Update},
14            ValueEncoding,
15        },
16        current::proof::OperationProof,
17        operation::Key,
18        Error,
19    },
20};
21use commonware_codec::Codec;
22use commonware_cryptography::{Digest, Hasher};
23use commonware_runtime::{Clock, Metrics, Storage};
24use futures::stream::Stream;
25
26/// Proof information for verifying a key has a particular value in the database.
27#[derive(Clone, Eq, PartialEq, Debug)]
28pub struct KeyValueProof<K: Key, D: Digest, const N: usize> {
29    pub proof: OperationProof<D, N>,
30    pub next_key: K,
31}
32
33/// The generic Db type for ordered Current QMDB variants.
34///
35/// This type is generic over the index type `I`, allowing it to be used with both regular
36/// and partitioned indices.
37pub type Db<E, C, K, V, I, H, const N: usize> =
38    crate::qmdb::current::db::Db<E, C, I, H, Update<K, V>, N>;
39
40// Shared read-only functionality.
41impl<
42        E: Storage + Clock + Metrics,
43        C: Contiguous<Item = Operation<K, V>>,
44        K: Key,
45        V: ValueEncoding,
46        I: OrderedIndex<Value = Location>,
47        H: Hasher,
48        const N: usize,
49    > Db<E, C, K, V, I, H, N>
50where
51    Operation<K, V>: Codec,
52    V::Value: Send + Sync,
53{
54    /// Get the value of `key` in the db, or None if it has no value.
55    pub async fn get(&self, key: &K) -> Result<Option<V::Value>, Error> {
56        self.any.get(key).await
57    }
58
59    /// Return true if the proof authenticates that `key` currently has value `value` in the db with
60    /// the provided `root`.
61    pub fn verify_key_value_proof(
62        hasher: &mut H,
63        key: K,
64        value: V::Value,
65        proof: &KeyValueProof<K, H::Digest, N>,
66        root: &H::Digest,
67    ) -> bool {
68        let op = Operation::Update(Update {
69            key,
70            value,
71            next_key: proof.next_key.clone(),
72        });
73
74        proof.proof.verify(hasher, op, root)
75    }
76
77    /// Get the operation that currently defines the span whose range contains `key`, or None if the
78    /// DB is empty.
79    pub async fn get_span(&self, key: &K) -> Result<Option<(Location, Update<K, V>)>, Error> {
80        self.any.get_span(key).await
81    }
82
83    /// Streams all active (key, value) pairs in the database in key order, starting from the first
84    /// active key greater than or equal to `start`.
85    pub async fn stream_range<'a>(
86        &'a self,
87        start: K,
88    ) -> Result<impl Stream<Item = Result<(K, V::Value), Error>> + 'a, Error>
89    where
90        V: 'a,
91    {
92        self.any.stream_range(start).await
93    }
94
95    /// Return true if the proof authenticates that `key` does _not_ exist in the db with the
96    /// provided `root`.
97    pub fn verify_exclusion_proof(
98        hasher: &mut H,
99        key: &K,
100        proof: &super::ExclusionProof<K, V, H::Digest, N>,
101        root: &H::Digest,
102    ) -> bool {
103        let (op_proof, op) = match proof {
104            super::ExclusionProof::KeyValue(op_proof, data) => {
105                if data.key == *key {
106                    // The provided `key` is in the DB if it matches the start of the span.
107                    return false;
108                }
109                if !crate::qmdb::any::db::Db::<E, C, I, H, Update<K, V>>::span_contains(
110                    &data.key,
111                    &data.next_key,
112                    key,
113                ) {
114                    // If the key is not within the span, then this proof cannot prove its
115                    // exclusion.
116                    return false;
117                }
118
119                (op_proof, Operation::Update(data.clone()))
120            }
121            super::ExclusionProof::Commit(op_proof, metadata) => {
122                // Handle the case where the proof shows the db is empty, hence any key is proven
123                // excluded. For the db to be empty, the floor must equal the commit operation's
124                // location.
125                let floor_loc = op_proof.loc;
126                (
127                    op_proof,
128                    Operation::CommitFloor(metadata.clone(), floor_loc),
129                )
130            }
131        };
132
133        op_proof.verify(hasher, op, root)
134    }
135}
136
137impl<
138        E: Storage + Clock + Metrics,
139        C: Mutable<Item = Operation<K, V>>,
140        K: Key,
141        V: ValueEncoding,
142        I: OrderedIndex<Value = Location>,
143        H: Hasher,
144        const N: usize,
145    > Db<E, C, K, V, I, H, N>
146where
147    Operation<K, V>: Codec,
148    V::Value: Send + Sync,
149{
150    /// Generate and return a proof of the current value of `key`, along with the other
151    /// [KeyValueProof] required to verify the proof. Returns KeyNotFound error if the key is not
152    /// currently assigned any value.
153    ///
154    /// # Errors
155    ///
156    /// Returns [Error::KeyNotFound] if the key is not currently assigned any value.
157    pub async fn key_value_proof(
158        &self,
159        hasher: &mut H,
160        key: K,
161    ) -> Result<KeyValueProof<K, H::Digest, N>, Error> {
162        let op_loc = self.any.get_with_loc(&key).await?;
163        let Some((data, loc)) = op_loc else {
164            return Err(Error::KeyNotFound);
165        };
166        let proof = self.operation_proof(hasher, loc).await?;
167
168        Ok(KeyValueProof {
169            proof,
170            next_key: data.next_key,
171        })
172    }
173
174    /// Generate and return a proof that the specified `key` does not exist in the db.
175    ///
176    /// # Errors
177    ///
178    /// Returns [Error::KeyExists] if the key exists in the db.
179    pub async fn exclusion_proof(
180        &self,
181        hasher: &mut H,
182        key: &K,
183    ) -> Result<super::ExclusionProof<K, V, H::Digest, N>, Error> {
184        match self.any.get_span(key).await? {
185            Some((loc, key_data)) => {
186                if key_data.key == *key {
187                    // Cannot prove exclusion of a key that exists in the db.
188                    return Err(Error::KeyExists);
189                }
190                let op_proof = self.operation_proof(hasher, loc).await?;
191                Ok(super::ExclusionProof::KeyValue(op_proof, key_data))
192            }
193            None => {
194                // The DB is empty. Use the last CommitFloor to prove emptiness. The Commit proof
195                // variant requires the CommitFloor's floor to equal its own location (genuinely
196                // empty at commit time). If this doesn't hold, the persisted state is inconsistent.
197                let op = self
198                    .any
199                    .log
200                    .reader()
201                    .await
202                    .read(*self.any.last_commit_loc)
203                    .await?;
204                let Operation::CommitFloor(value, floor) = op else {
205                    unreachable!("last_commit_loc should always point to a CommitFloor");
206                };
207                assert_eq!(
208                    floor, self.any.last_commit_loc,
209                    "inconsistent commit floor: expected last_commit_loc={}, got floor={}",
210                    self.any.last_commit_loc, floor
211                );
212                let op_proof = self
213                    .operation_proof(hasher, self.any.last_commit_loc)
214                    .await?;
215                Ok(super::ExclusionProof::Commit(op_proof, value))
216            }
217        }
218    }
219}
220
221// Store implementation
222impl<
223        E: Storage + Clock + Metrics,
224        C: Contiguous<Item = Operation<K, V>>,
225        K: Key,
226        V: ValueEncoding,
227        I: OrderedIndex<Value = Location>,
228        H: Hasher,
229        const N: usize,
230    > kv::Gettable for Db<E, C, K, V, I, H, N>
231where
232    Operation<K, V>: Codec,
233    V::Value: Send + Sync,
234{
235    type Key = K;
236    type Value = V::Value;
237    type Error = Error;
238
239    async fn get(&self, key: &Self::Key) -> Result<Option<Self::Value>, Self::Error> {
240        self.get(key).await
241    }
242}