use crate::{
merkle::{Family, Location},
qmdb::Error,
};
use commonware_cryptography::Digest;
use core::iter;
use std::{
ops::Deref,
sync::{Arc, Weak},
};
pub(crate) struct Compatible<'a, T>(&'a T);
impl<T> Clone for Compatible<'_, T> {
fn clone(&self) -> Self {
*self
}
}
impl<T> Copy for Compatible<'_, T> {}
impl<T> Deref for Compatible<'_, T> {
type Target = T;
fn deref(&self) -> &T {
self.0
}
}
#[derive(Clone, Copy, Debug)]
pub struct Commitment<F: Family, D: Digest> {
pub size: Location<F>,
pub root: D,
}
impl<F: Family, D: Digest> Commitment<F, D> {
pub(crate) const fn new(size: Location<F>, root: D) -> Self {
Self { size, root }
}
pub(crate) fn compatible<'a, T>(
&self,
db: &'a T,
current: Self,
) -> Result<Compatible<'a, T>, Error<F>> {
if *self == current {
Ok(Compatible(db))
} else {
Err(Error::StaleRead)
}
}
}
impl<F: Family, D: Digest> PartialEq for Commitment<F, D> {
fn eq(&self, other: &Self) -> bool {
self.size == other.size && self.root == other.root
}
}
impl<F: Family, D: Digest> Eq for Commitment<F, D> {}
#[derive(Clone)]
pub struct Bounds<F: Family, D: Digest> {
pub base: Commitment<F, D>,
pub db: Commitment<F, D>,
pub tip: Commitment<F, D>,
pub ancestors: Vec<Commitment<F, D>>,
pub inactivity_floor: Location<F>,
}
impl<F: Family, D: Digest> Bounds<F, D> {
pub(crate) const fn from_db(state: Commitment<F, D>, inactivity_floor: Location<F>) -> Self {
Self {
base: state,
db: state,
tip: state,
ancestors: Vec::new(),
inactivity_floor,
}
}
pub(crate) fn compatible<'a, T>(
&self,
db: &'a T,
current: Commitment<F, D>,
) -> Result<Compatible<'a, T>, Error<F>> {
if current == self.tip || current == self.db || self.ancestors.contains(¤t) {
Ok(Compatible(db))
} else {
Err(Error::StaleRead)
}
}
pub(crate) fn validate_apply_to(&self, current: Commitment<F, D>) -> Result<(), Error<F>> {
validate_batch_applicable(current, self.db, self.ancestors.iter().copied())
}
}
pub(crate) fn ancestors<T, P>(
parent: Option<Weak<T>>,
mut parent_of: P,
) -> impl Iterator<Item = Arc<T>>
where
P: for<'a> FnMut(&'a T) -> Option<&'a Weak<T>>,
{
let mut next = parent.as_ref().and_then(Weak::upgrade);
iter::from_fn(move || {
let batch = next.take()?;
next = parent_of(&batch).and_then(Weak::upgrade);
Some(batch)
})
}
pub(crate) fn parent_and_ancestors<'a, T, P, I>(
parent: Option<&'a Arc<T>>,
mut ancestors_of: P,
) -> impl Iterator<Item = Arc<T>> + use<'a, T, P, I>
where
P: FnMut(&'a Arc<T>) -> I,
I: IntoIterator<Item = Arc<T>>,
{
parent
.into_iter()
.flat_map(move |parent| iter::once(Arc::clone(parent)).chain(ancestors_of(parent)))
}
pub(crate) fn effective_boundary<F: Family, D: Digest>(
inherited: Commitment<F, D>,
oldest_live_base: Option<Commitment<F, D>>,
) -> Commitment<F, D> {
oldest_live_base
.filter(|base| base.size > inherited.size)
.unwrap_or(inherited)
}
pub(crate) fn validate_batch_applicable<F: Family, D: Digest>(
current: Commitment<F, D>,
batch_db: Commitment<F, D>,
ancestors: impl IntoIterator<Item = Commitment<F, D>>,
) -> Result<(), Error<F>> {
if current == batch_db || ancestors.into_iter().any(|state| state == current) {
return Ok(());
}
Err(Error::StaleBatch)
}
pub(crate) fn merkleizable<'a, T, F: Family, D: Digest>(
db: &'a T,
current: Commitment<F, D>,
boundary: Commitment<F, D>,
ancestors: impl IntoIterator<Item = Commitment<F, D>>,
) -> Result<Compatible<'a, T>, Error<F>> {
validate_batch_applicable(current, boundary, ancestors)?;
Ok(Compatible(db))
}
pub(crate) fn validate_merkleize_floor<F: Family>(
start: Location<F>,
floor: Location<F>,
commit_loc: Location<F>,
) -> Result<(), Error<F>> {
if floor < start {
return Err(Error::FloorRegressed(floor, start));
}
if floor > commit_loc {
return Err(Error::FloorBeyondSize(floor, commit_loc));
}
Ok(())
}
#[cfg(test)]
mod tests {
use super::*;
use crate::merkle::mmr;
use commonware_cryptography::sha256;
use std::sync::{Arc, Weak};
type F = mmr::Family;
type D = sha256::Digest;
struct TestBatch {
id: u8,
parent: Option<Weak<Self>>,
}
const fn loc(n: u64) -> Location<F> {
Location::new(n)
}
fn state(size: u64, marker: u8) -> Commitment<F, D> {
Commitment::new(Location::new(size), D::from([marker; 32]))
}
#[test]
fn validate_batch_applicable_accepts_valid_boundaries() {
let ancestors = [state(12, 12), state(16, 16)];
assert!(validate_batch_applicable::<F, D>(state(10, 1), state(10, 1), ancestors).is_ok());
assert!(validate_batch_applicable::<F, D>(state(16, 16), state(10, 1), ancestors).is_ok());
}
#[test]
fn validate_batch_applicable_rejects_stale_batch() {
let ancestors = [state(12, 12), state(16, 16)];
let result = validate_batch_applicable::<F, D>(state(18, 18), state(10, 1), ancestors);
assert!(matches!(result, Err(Error::StaleBatch)));
}
#[test]
fn validate_batch_applicable_rejects_equal_size_sibling() {
let ancestors = [state(16, 16)];
let result = validate_batch_applicable::<F, D>(state(16, 99), state(10, 1), ancestors);
assert!(matches!(result, Err(Error::StaleBatch)));
}
#[test]
fn compatible_accepts_own_states_only() {
let bounds = Bounds::<F, D> {
base: state(16, 16),
db: state(10, 1),
tip: state(18, 18),
ancestors: vec![state(16, 16), state(12, 12)],
inactivity_floor: loc(14),
};
assert!(bounds.compatible(&(), state(18, 18)).is_ok());
assert!(bounds.compatible(&(), state(10, 1)).is_ok());
assert!(bounds.compatible(&(), state(16, 16)).is_ok());
assert!(matches!(
bounds.compatible(&(), state(19, 19)),
Err(Error::StaleRead)
));
assert!(matches!(
bounds.compatible(&(), state(16, 99)),
Err(Error::StaleRead)
));
assert!(matches!(
bounds.compatible(&(), state(18, 99)),
Err(Error::StaleRead)
));
}
#[test]
fn compatible_from_base_commitment_requires_unchanged_state() {
let base = state(10, 1);
assert!(base.compatible(&(), state(10, 1)).is_ok());
assert!(matches!(
base.compatible(&(), state(10, 2)),
Err(Error::StaleRead)
));
assert!(matches!(
base.compatible(&(), state(11, 1)),
Err(Error::StaleRead)
));
}
#[test]
fn ancestors_iterates_parent_first() {
let grandparent = Arc::new(TestBatch {
id: 1,
parent: None,
});
let parent = Arc::new(TestBatch {
id: 2,
parent: Some(Arc::downgrade(&grandparent)),
});
let ids: Vec<_> = ancestors(Some(Arc::downgrade(&parent)), |batch| batch.parent.as_ref())
.map(|batch| batch.id)
.collect();
assert_eq!(ids, vec![2, 1]);
}
#[test]
fn bounds_validates_apply_to_current_state() {
let bounds = Bounds::<F, D> {
base: state(10, 1),
db: state(10, 1),
tip: state(14, 14),
ancestors: vec![state(12, 12)],
inactivity_floor: loc(11),
};
assert!(bounds.validate_apply_to(state(10, 1)).is_ok());
assert!(bounds.validate_apply_to(state(12, 12)).is_ok());
let result = bounds.validate_apply_to(state(11, 11));
assert!(matches!(result, Err(Error::StaleBatch)));
let result = bounds.validate_apply_to(state(14, 14));
assert!(matches!(result, Err(Error::StaleBatch)));
}
}