use crate::{
index::Ordered as OrderedIndex,
journal::contiguous::{Contiguous, Mutable, Reader},
merkle::{self, Location},
qmdb::{
any::{
ordered::{Operation, Update},
ValueEncoding,
},
current::proof::OperationProof,
operation::Key,
Error,
},
Context,
};
use commonware_codec::Codec;
use commonware_cryptography::{Digest, Hasher};
use futures::stream::Stream;
#[derive(Clone, Eq, PartialEq, Debug)]
pub struct KeyValueProof<F: merkle::Family, K: Key, D: Digest, const N: usize> {
pub proof: OperationProof<F, D, N>,
pub next_key: K,
}
pub type Db<F, E, C, K, V, I, H, const N: usize> =
crate::qmdb::current::db::Db<F, E, C, I, H, Update<K, V>, N>;
impl<
F: merkle::Graftable,
E: Context,
C: Contiguous<Item = Operation<F, K, V>>,
K: Key,
V: ValueEncoding,
I: OrderedIndex<Value = Location<F>>,
H: Hasher,
const N: usize,
> Db<F, E, C, K, V, I, H, N>
where
Operation<F, K, V>: Codec,
{
pub async fn get(&self, key: &K) -> Result<Option<V::Value>, Error<F>> {
self.any.get(key).await
}
pub fn verify_key_value_proof(
hasher: &mut H,
key: K,
value: V::Value,
proof: &KeyValueProof<F, K, H::Digest, N>,
root: &H::Digest,
) -> bool {
let op = Operation::Update(Update {
key,
value,
next_key: proof.next_key.clone(),
});
proof.proof.verify(hasher, op, root)
}
pub async fn get_span(&self, key: &K) -> Result<Option<(Location<F>, Update<K, V>)>, Error<F>> {
self.any.get_span(key).await
}
pub async fn stream_range<'a>(
&'a self,
start: K,
) -> Result<impl Stream<Item = Result<(K, V::Value), Error<F>>> + 'a, Error<F>>
where
V: 'a,
{
self.any.stream_range(start).await
}
pub fn verify_exclusion_proof(
hasher: &mut H,
key: &K,
proof: &super::ExclusionProof<F, K, V, H::Digest, N>,
root: &H::Digest,
) -> bool {
let (op_proof, op) = match proof {
super::ExclusionProof::KeyValue(op_proof, data) => {
if data.key == *key {
return false;
}
if !crate::qmdb::any::db::Db::<F, E, C, I, H, Update<K, V>>::span_contains(
&data.key,
&data.next_key,
key,
) {
return false;
}
(op_proof, Operation::Update(data.clone()))
}
super::ExclusionProof::Commit(op_proof, metadata) => {
let floor_loc = op_proof.loc;
(
op_proof,
Operation::CommitFloor(metadata.clone(), floor_loc),
)
}
};
op_proof.verify(hasher, op, root)
}
}
impl<
F: merkle::Graftable,
E: Context,
C: Mutable<Item = Operation<F, K, V>>,
K: Key,
V: ValueEncoding,
I: OrderedIndex<Value = Location<F>>,
H: Hasher,
const N: usize,
> Db<F, E, C, K, V, I, H, N>
where
Operation<F, K, V>: Codec,
{
pub async fn key_value_proof(
&self,
hasher: &mut H,
key: K,
) -> Result<KeyValueProof<F, K, H::Digest, N>, Error<F>> {
let op_loc = self.any.get_with_loc(&key).await?;
let Some((data, loc)) = op_loc else {
return Err(Error::<F>::KeyNotFound);
};
let proof = self.operation_proof(hasher, loc).await?;
Ok(KeyValueProof {
proof,
next_key: data.next_key,
})
}
pub async fn exclusion_proof(
&self,
hasher: &mut H,
key: &K,
) -> Result<super::ExclusionProof<F, K, V, H::Digest, N>, Error<F>> {
match self.any.get_span(key).await? {
Some((loc, key_data)) => {
if key_data.key == *key {
return Err(Error::<F>::KeyExists);
}
let op_proof = self.operation_proof(hasher, loc).await?;
Ok(super::ExclusionProof::KeyValue(op_proof, key_data))
}
None => {
let op = self
.any
.log
.reader()
.await
.read(*self.any.last_commit_loc)
.await?;
let Operation::CommitFloor(value, floor) = op else {
unreachable!("last_commit_loc should always point to a CommitFloor");
};
assert_eq!(
floor, self.any.last_commit_loc,
"inconsistent commit floor: expected last_commit_loc={}, got floor={}",
self.any.last_commit_loc, floor
);
let op_proof = self
.operation_proof(hasher, self.any.last_commit_loc)
.await?;
Ok(super::ExclusionProof::Commit(op_proof, value))
}
}
}
}