use crate::{
merkle::{Family, Location, Proof},
qmdb::{
self,
any::{
ordered::{
fixed::{Db as OrderedFixedDb, Operation as OrderedFixedOperation},
variable::{Db as OrderedVariableDb, Operation as OrderedVariableOperation},
},
unordered::{
fixed::{Db as FixedDb, Operation as FixedOperation},
variable::{Db as VariableDb, Operation as VariableOperation},
},
FixedValue, VariableValue,
},
immutable::{
fixed::{Db as ImmutableFixedDb, Operation as ImmutableFixedOp},
variable::{Db as ImmutableVariableDb, Operation as ImmutableVariableOp},
},
keyless::{
fixed::{Db as KeylessFixedDb, Operation as KeylessFixedOp},
variable::{Db as KeylessVariableDb, Operation as KeylessVariableOp},
},
operation::Key,
},
translator::Translator,
Context,
};
use commonware_cryptography::{Digest, Hasher};
use commonware_parallel::Strategy;
use commonware_utils::{channel::oneshot, sync::AsyncRwLock, Array};
use std::{future::Future, num::NonZeroU64, sync::Arc};
pub struct FetchResult<F: Family, Op, D: Digest> {
pub proof: Proof<F, D>,
pub operations: Vec<Op>,
pub pinned_nodes: Option<Vec<D>>,
pub callback: Option<oneshot::Sender<bool>>,
}
impl<F: Family, Op, D: Digest> FetchResult<F, Op, D> {
pub const fn new(
proof: Proof<F, D>,
operations: Vec<Op>,
pinned_nodes: Option<Vec<D>>,
) -> Self {
Self {
proof,
operations,
pinned_nodes,
callback: None,
}
}
pub const fn with_callback(
proof: Proof<F, D>,
operations: Vec<Op>,
pinned_nodes: Option<Vec<D>>,
callback: oneshot::Sender<bool>,
) -> Self {
Self {
proof,
operations,
pinned_nodes,
callback: Some(callback),
}
}
}
pub struct FetchedOperations<F: Family, Op, D: Digest> {
pub proof: Proof<F, D>,
pub operations: Vec<Op>,
pub pinned_nodes: Option<Vec<D>>,
}
impl<F: Family, Op, D: Digest> FetchedOperations<F, Op, D> {
pub const fn new(
proof: Proof<F, D>,
operations: Vec<Op>,
pinned_nodes: Option<Vec<D>>,
) -> Self {
Self {
proof,
operations,
pinned_nodes,
}
}
}
impl<F: Family, Op: std::fmt::Debug, D: Digest> std::fmt::Debug for FetchResult<F, Op, D> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("FetchResult")
.field("proof", &self.proof)
.field("operations", &self.operations)
.field("pinned_nodes", &self.pinned_nodes)
.field("callback", &self.callback.as_ref().map(|_| "<callback>"))
.finish()
}
}
pub async fn fetch_operation_range<F, Op, D, Error, Fetch, FetchFuture>(
op_count: Location<F>,
start_loc: Location<F>,
max_ops: NonZeroU64,
include_pinned_nodes: bool,
fetch: Fetch,
) -> Result<FetchResult<F, Op, D>, Error>
where
F: Family,
D: Digest,
Fetch: FnOnce(Location<F>, Location<F>, NonZeroU64, bool) -> FetchFuture,
FetchFuture: Future<Output = Result<FetchedOperations<F, Op, D>, Error>>,
{
let FetchedOperations {
proof,
operations,
pinned_nodes,
} = fetch(op_count, start_loc, max_ops, include_pinned_nodes).await?;
Ok(FetchResult::new(proof, operations, pinned_nodes))
}
pub async fn fetch_operations<
F,
Op,
D,
Error,
HistoricalProof,
HistoricalFuture,
Pins,
PinsFuture,
>(
op_count: Location<F>,
start_loc: Location<F>,
max_ops: NonZeroU64,
include_pinned_nodes: bool,
historical_proof: HistoricalProof,
pinned_nodes_at: Pins,
) -> Result<FetchResult<F, Op, D>, Error>
where
F: Family,
D: Digest,
HistoricalProof: FnOnce(Location<F>, Location<F>, NonZeroU64) -> HistoricalFuture,
HistoricalFuture: Future<Output = Result<(Proof<F, D>, Vec<Op>), Error>>,
Pins: FnOnce(Location<F>) -> PinsFuture,
PinsFuture: Future<Output = Result<Vec<D>, Error>>,
{
fetch_operation_range(
op_count,
start_loc,
max_ops,
include_pinned_nodes,
|op_count, start_loc, max_ops, include_pinned_nodes| async move {
let (proof, operations) = historical_proof(op_count, start_loc, max_ops).await?;
let pinned_nodes = if include_pinned_nodes {
Some(pinned_nodes_at(start_loc).await?)
} else {
None
};
Ok(FetchedOperations::new(proof, operations, pinned_nodes))
},
)
.await
}
pub trait Resolver: Send + Sync + Clone + 'static {
type Family: Family;
type Digest: Digest;
type Op;
type Error: std::error::Error + Send + 'static;
#[allow(clippy::type_complexity)]
fn get_operations<'a>(
&'a self,
op_count: Location<Self::Family>,
start_loc: Location<Self::Family>,
max_ops: NonZeroU64,
include_pinned_nodes: bool,
cancel_rx: oneshot::Receiver<()>,
) -> impl Future<Output = Result<FetchResult<Self::Family, Self::Op, Self::Digest>, Self::Error>>
+ Send
+ 'a;
}
macro_rules! impl_resolver {
($db:ident, $op:ident, $val_bound:ident) => {
impl<F, E, K, V, H, T, S> Resolver for Arc<$db<F, E, K, V, H, T, S>>
where
F: Family,
E: Context,
K: Array,
V: $val_bound + Send + Sync + 'static,
H: Hasher,
T: Translator + Send + Sync + 'static,
T::Key: Send + Sync,
S: Strategy,
{
type Family = F;
type Digest = H::Digest;
type Op = $op<F, K, V>;
type Error = qmdb::Error<F>;
async fn get_operations(
&self,
op_count: Location<Self::Family>,
start_loc: Location<Self::Family>,
max_ops: NonZeroU64,
include_pinned_nodes: bool,
_cancel_rx: oneshot::Receiver<()>,
) -> Result<FetchResult<Self::Family, Self::Op, Self::Digest>, Self::Error> {
fetch_operations(
op_count,
start_loc,
max_ops,
include_pinned_nodes,
|op_count, start_loc, max_ops| {
self.historical_proof(op_count, start_loc, max_ops)
},
|start_loc| self.pinned_nodes_at(start_loc),
)
.await
}
}
impl<F, E, K, V, H, T, S> Resolver for Arc<AsyncRwLock<$db<F, E, K, V, H, T, S>>>
where
F: Family,
E: Context,
K: Array,
V: $val_bound + Send + Sync + 'static,
H: Hasher,
T: Translator + Send + Sync + 'static,
T::Key: Send + Sync,
S: Strategy,
{
type Family = F;
type Digest = H::Digest;
type Op = $op<F, K, V>;
type Error = qmdb::Error<F>;
async fn get_operations(
&self,
op_count: Location<Self::Family>,
start_loc: Location<Self::Family>,
max_ops: NonZeroU64,
include_pinned_nodes: bool,
_cancel_rx: oneshot::Receiver<()>,
) -> Result<FetchResult<Self::Family, Self::Op, Self::Digest>, Self::Error> {
let db = self.read().await;
fetch_operations(
op_count,
start_loc,
max_ops,
include_pinned_nodes,
|op_count, start_loc, max_ops| {
db.historical_proof(op_count, start_loc, max_ops)
},
|start_loc| db.pinned_nodes_at(start_loc),
)
.await
}
}
impl<F, E, K, V, H, T, S> Resolver for Arc<AsyncRwLock<Option<$db<F, E, K, V, H, T, S>>>>
where
F: Family,
E: Context,
K: Array,
V: $val_bound + Send + Sync + 'static,
H: Hasher,
T: Translator + Send + Sync + 'static,
T::Key: Send + Sync,
S: Strategy,
{
type Family = F;
type Digest = H::Digest;
type Op = $op<F, K, V>;
type Error = qmdb::Error<F>;
async fn get_operations(
&self,
op_count: Location<Self::Family>,
start_loc: Location<Self::Family>,
max_ops: NonZeroU64,
include_pinned_nodes: bool,
_cancel_rx: oneshot::Receiver<()>,
) -> Result<FetchResult<Self::Family, Self::Op, Self::Digest>, Self::Error> {
let guard = self.read().await;
let db = guard.as_ref().ok_or(qmdb::Error::KeyNotFound)?;
fetch_operations(
op_count,
start_loc,
max_ops,
include_pinned_nodes,
|op_count, start_loc, max_ops| {
db.historical_proof(op_count, start_loc, max_ops)
},
|start_loc| db.pinned_nodes_at(start_loc),
)
.await
}
}
};
}
impl_resolver!(FixedDb, FixedOperation, FixedValue);
impl_resolver!(VariableDb, VariableOperation, VariableValue);
impl_resolver!(OrderedFixedDb, OrderedFixedOperation, FixedValue);
impl_resolver!(OrderedVariableDb, OrderedVariableOperation, VariableValue);
macro_rules! impl_resolver_immutable {
($db:ident, $op:ident, $val_bound:ident, $key_bound:path) => {
impl<F, E, K, V, H, T, S> Resolver for Arc<$db<F, E, K, V, H, T, S>>
where
F: Family,
E: Context,
K: $key_bound,
V: $val_bound + Send + Sync + 'static,
H: Hasher,
T: Translator + Send + Sync + 'static,
T::Key: Send + Sync,
S: Strategy,
{
type Family = F;
type Digest = H::Digest;
type Op = $op<F, K, V>;
type Error = qmdb::Error<F>;
async fn get_operations(
&self,
op_count: Location<Self::Family>,
start_loc: Location<Self::Family>,
max_ops: NonZeroU64,
include_pinned_nodes: bool,
_cancel_rx: oneshot::Receiver<()>,
) -> Result<FetchResult<Self::Family, Self::Op, Self::Digest>, Self::Error> {
fetch_operations(
op_count,
start_loc,
max_ops,
include_pinned_nodes,
|op_count, start_loc, max_ops| {
self.historical_proof(op_count, start_loc, max_ops)
},
|start_loc| self.pinned_nodes_at(start_loc),
)
.await
}
}
impl<F, E, K, V, H, T, S> Resolver for Arc<AsyncRwLock<$db<F, E, K, V, H, T, S>>>
where
F: Family,
E: Context,
K: $key_bound,
V: $val_bound + Send + Sync + 'static,
H: Hasher,
T: Translator + Send + Sync + 'static,
T::Key: Send + Sync,
S: Strategy,
{
type Family = F;
type Digest = H::Digest;
type Op = $op<F, K, V>;
type Error = qmdb::Error<F>;
async fn get_operations(
&self,
op_count: Location<Self::Family>,
start_loc: Location<Self::Family>,
max_ops: NonZeroU64,
include_pinned_nodes: bool,
_cancel_rx: oneshot::Receiver<()>,
) -> Result<FetchResult<Self::Family, Self::Op, Self::Digest>, Self::Error> {
let db = self.read().await;
fetch_operations(
op_count,
start_loc,
max_ops,
include_pinned_nodes,
|op_count, start_loc, max_ops| {
db.historical_proof(op_count, start_loc, max_ops)
},
|start_loc| db.pinned_nodes_at(start_loc),
)
.await
}
}
impl<F, E, K, V, H, T, S> Resolver for Arc<AsyncRwLock<Option<$db<F, E, K, V, H, T, S>>>>
where
F: Family,
E: Context,
K: $key_bound,
V: $val_bound + Send + Sync + 'static,
H: Hasher,
T: Translator + Send + Sync + 'static,
T::Key: Send + Sync,
S: Strategy,
{
type Family = F;
type Digest = H::Digest;
type Op = $op<F, K, V>;
type Error = qmdb::Error<F>;
async fn get_operations(
&self,
op_count: Location<Self::Family>,
start_loc: Location<Self::Family>,
max_ops: NonZeroU64,
include_pinned_nodes: bool,
_cancel_rx: oneshot::Receiver<()>,
) -> Result<FetchResult<Self::Family, Self::Op, Self::Digest>, Self::Error> {
let guard = self.read().await;
let db = guard.as_ref().ok_or(qmdb::Error::KeyNotFound)?;
fetch_operations(
op_count,
start_loc,
max_ops,
include_pinned_nodes,
|op_count, start_loc, max_ops| {
db.historical_proof(op_count, start_loc, max_ops)
},
|start_loc| db.pinned_nodes_at(start_loc),
)
.await
}
}
};
}
impl_resolver_immutable!(ImmutableFixedDb, ImmutableFixedOp, FixedValue, Array);
impl_resolver_immutable!(ImmutableVariableDb, ImmutableVariableOp, VariableValue, Key);
macro_rules! impl_resolver_keyless {
($db:ident, $op:ident, $val_bound:ident) => {
impl<F, E, V, H, S> Resolver for Arc<$db<F, E, V, H, S>>
where
F: Family,
E: Context,
V: $val_bound + Send + Sync + 'static,
H: Hasher,
S: Strategy,
{
type Family = F;
type Digest = H::Digest;
type Op = $op<F, V>;
type Error = qmdb::Error<F>;
async fn get_operations(
&self,
op_count: Location<Self::Family>,
start_loc: Location<Self::Family>,
max_ops: NonZeroU64,
include_pinned_nodes: bool,
_cancel_rx: oneshot::Receiver<()>,
) -> Result<FetchResult<Self::Family, Self::Op, Self::Digest>, Self::Error> {
fetch_operations(
op_count,
start_loc,
max_ops,
include_pinned_nodes,
|op_count, start_loc, max_ops| {
self.historical_proof(op_count, start_loc, max_ops)
},
|start_loc| self.pinned_nodes_at(start_loc),
)
.await
}
}
impl<F, E, V, H, S> Resolver for Arc<AsyncRwLock<$db<F, E, V, H, S>>>
where
F: Family,
E: Context,
V: $val_bound + Send + Sync + 'static,
H: Hasher,
S: Strategy,
{
type Family = F;
type Digest = H::Digest;
type Op = $op<F, V>;
type Error = qmdb::Error<F>;
async fn get_operations(
&self,
op_count: Location<Self::Family>,
start_loc: Location<Self::Family>,
max_ops: NonZeroU64,
include_pinned_nodes: bool,
_cancel_rx: oneshot::Receiver<()>,
) -> Result<FetchResult<Self::Family, Self::Op, Self::Digest>, Self::Error> {
let db = self.read().await;
fetch_operations(
op_count,
start_loc,
max_ops,
include_pinned_nodes,
|op_count, start_loc, max_ops| {
db.historical_proof(op_count, start_loc, max_ops)
},
|start_loc| db.pinned_nodes_at(start_loc),
)
.await
}
}
impl<F, E, V, H, S> Resolver for Arc<AsyncRwLock<Option<$db<F, E, V, H, S>>>>
where
F: Family,
E: Context,
V: $val_bound + Send + Sync + 'static,
H: Hasher,
S: Strategy,
{
type Family = F;
type Digest = H::Digest;
type Op = $op<F, V>;
type Error = qmdb::Error<F>;
async fn get_operations(
&self,
op_count: Location<Self::Family>,
start_loc: Location<Self::Family>,
max_ops: NonZeroU64,
include_pinned_nodes: bool,
_cancel_rx: oneshot::Receiver<()>,
) -> Result<FetchResult<Self::Family, Self::Op, Self::Digest>, Self::Error> {
let guard = self.read().await;
let db = guard.as_ref().ok_or(qmdb::Error::KeyNotFound)?;
fetch_operations(
op_count,
start_loc,
max_ops,
include_pinned_nodes,
|op_count, start_loc, max_ops| {
db.historical_proof(op_count, start_loc, max_ops)
},
|start_loc| db.pinned_nodes_at(start_loc),
)
.await
}
}
};
}
impl_resolver_keyless!(KeylessFixedDb, KeylessFixedOp, FixedValue);
impl_resolver_keyless!(KeylessVariableDb, KeylessVariableOp, VariableValue);
#[cfg(test)]
pub(crate) mod tests {
use super::*;
use crate::{
merkle::mmr,
translator::{OneCap, TwoCap},
};
use commonware_cryptography::{sha256::Digest as ShaDigest, Sha256};
use commonware_parallel::Rayon;
use commonware_runtime::deterministic;
use commonware_utils::sync::AsyncRwLock;
use std::{marker::PhantomData, sync::Arc};
macro_rules! assert_resolver_variants {
($db:ty) => {
assert_resolver::<Arc<$db>>();
assert_resolver::<Arc<AsyncRwLock<$db>>>();
assert_resolver::<Arc<AsyncRwLock<Option<$db>>>>();
};
}
fn assert_resolver<R: Resolver>() {}
fn empty_proof() -> Proof<mmr::Family, ShaDigest> {
Proof {
leaves: Location::new(0),
inactive_peaks: 0,
digests: vec![],
}
}
#[test]
fn test_fetch_result_new_has_no_success_acknowledgement() {
let result = FetchResult::<mmr::Family, (), ShaDigest>::new(empty_proof(), vec![], None);
assert!(result.callback.is_none());
}
#[test]
fn test_fetch_result_with_callback_reports_to_external_receiver() {
let (success_tx, mut success_rx) = oneshot::channel();
let result = FetchResult::<mmr::Family, (), ShaDigest>::with_callback(
empty_proof(),
vec![],
None,
success_tx,
);
assert!(result.callback.expect("success sender").send(true).is_ok());
assert_eq!(success_rx.try_recv(), Ok(true));
}
#[derive(Clone)]
pub struct FailResolver<F: Family, Op, D> {
_phantom: PhantomData<(F, Op, D)>,
}
impl<F, Op, D> Resolver for FailResolver<F, Op, D>
where
F: Family,
D: Digest,
Op: Send + Sync + Clone + 'static,
{
type Family = F;
type Digest = D;
type Op = Op;
type Error = qmdb::Error<F>;
async fn get_operations(
&self,
_op_count: Location<F>,
_start_loc: Location<F>,
_max_ops: NonZeroU64,
_include_pinned_nodes: bool,
_cancel: oneshot::Receiver<()>,
) -> Result<FetchResult<F, Op, D>, qmdb::Error<F>> {
Err(qmdb::Error::KeyNotFound) }
}
impl<F: Family, Op, D> FailResolver<F, Op, D> {
pub fn new() -> Self {
Self {
_phantom: PhantomData,
}
}
}
#[test]
fn test_all_qmdb_variants_implement_strategy_resolvers() {
type AnyOrderedFixed = crate::qmdb::any::ordered::fixed::Db<
mmr::Family,
deterministic::Context,
ShaDigest,
ShaDigest,
Sha256,
OneCap,
Rayon,
>;
type AnyOrderedVariable = crate::qmdb::any::ordered::variable::Db<
mmr::Family,
deterministic::Context,
ShaDigest,
Vec<u8>,
Sha256,
OneCap,
Rayon,
>;
type AnyUnorderedFixed = crate::qmdb::any::unordered::fixed::Db<
mmr::Family,
deterministic::Context,
ShaDigest,
ShaDigest,
Sha256,
TwoCap,
Rayon,
>;
type AnyUnorderedVariable = crate::qmdb::any::unordered::variable::Db<
mmr::Family,
deterministic::Context,
ShaDigest,
Vec<u8>,
Sha256,
TwoCap,
Rayon,
>;
type CurrentOrderedFixed = crate::qmdb::current::ordered::fixed::Db<
mmr::Family,
deterministic::Context,
ShaDigest,
ShaDigest,
Sha256,
OneCap,
32,
Rayon,
>;
type CurrentOrderedVariable = crate::qmdb::current::ordered::variable::Db<
mmr::Family,
deterministic::Context,
ShaDigest,
Vec<u8>,
Sha256,
OneCap,
32,
Rayon,
>;
type CurrentUnorderedFixed = crate::qmdb::current::unordered::fixed::Db<
mmr::Family,
deterministic::Context,
ShaDigest,
ShaDigest,
Sha256,
TwoCap,
32,
Rayon,
>;
type CurrentUnorderedVariable = crate::qmdb::current::unordered::variable::Db<
mmr::Family,
deterministic::Context,
ShaDigest,
Vec<u8>,
Sha256,
TwoCap,
32,
Rayon,
>;
type ImmutableFixed = crate::qmdb::immutable::fixed::Db<
mmr::Family,
deterministic::Context,
ShaDigest,
ShaDigest,
Sha256,
TwoCap,
Rayon,
>;
type ImmutableVariable = crate::qmdb::immutable::variable::Db<
mmr::Family,
deterministic::Context,
ShaDigest,
Vec<u8>,
Sha256,
TwoCap,
Rayon,
>;
type KeylessFixed = crate::qmdb::keyless::fixed::Db<
mmr::Family,
deterministic::Context,
ShaDigest,
Sha256,
Rayon,
>;
type KeylessVariable = crate::qmdb::keyless::variable::Db<
mmr::Family,
deterministic::Context,
Vec<u8>,
Sha256,
Rayon,
>;
assert_resolver_variants!(AnyOrderedFixed);
assert_resolver_variants!(AnyOrderedVariable);
assert_resolver_variants!(AnyUnorderedFixed);
assert_resolver_variants!(AnyUnorderedVariable);
assert_resolver_variants!(CurrentOrderedFixed);
assert_resolver_variants!(CurrentOrderedVariable);
assert_resolver_variants!(CurrentUnorderedFixed);
assert_resolver_variants!(CurrentUnorderedVariable);
assert_resolver_variants!(ImmutableFixed);
assert_resolver_variants!(ImmutableVariable);
assert_resolver_variants!(KeylessFixed);
assert_resolver_variants!(KeylessVariable);
}
}