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