use crate::{
merkle::{Family, Location},
qmdb::sync::error::EngineError,
};
use commonware_codec::{EncodeSize, Error as CodecError, Read, ReadExt as _, Write};
use commonware_cryptography::Digest;
use commonware_runtime::{Buf, BufMut};
use commonware_utils::{non_empty_range, range::NonEmptyRange};
#[derive(Debug)]
pub struct Target<F: Family, D: Digest> {
pub root: D,
pub range: NonEmptyRange<Location<F>>,
}
impl<F: Family, D: Digest> Target<F, D> {
pub const fn new(root: D, range: NonEmptyRange<Location<F>>) -> Self {
Self { root, range }
}
pub fn advances(&self, from: &Self) -> bool {
self.range.end().is_valid()
&& self.range.end() > from.range.end()
&& self.range.start() >= from.range.start()
}
}
impl<F: Family, D: Digest> Clone for Target<F, D> {
fn clone(&self) -> Self {
Self {
root: self.root,
range: self.range.clone(),
}
}
}
impl<F: Family, D: Digest> PartialEq for Target<F, D> {
fn eq(&self, other: &Self) -> bool {
self.root == other.root && self.range == other.range
}
}
impl<F: Family, D: Digest> Eq for Target<F, D> {}
impl<F: Family, D: Digest> Write for Target<F, D> {
fn write(&self, buf: &mut impl BufMut) {
self.root.write(buf);
self.range.write(buf);
}
}
impl<F: Family, D: Digest> EncodeSize for Target<F, D> {
fn encode_size(&self) -> usize {
self.root.encode_size() + self.range.encode_size()
}
}
impl<F: Family, D: Digest> Read for Target<F, D> {
type Cfg = ();
fn read_cfg(buf: &mut impl Buf, _: &()) -> Result<Self, CodecError> {
let root = D::read(buf)?;
let range = NonEmptyRange::<Location<F>>::read(buf)?;
if !range.start().is_valid() || !range.end().is_valid() {
return Err(CodecError::Invalid(
"storage::qmdb::sync::Target",
"range bounds out of valid range",
));
}
Ok(Self { root, range })
}
}
#[cfg(feature = "arbitrary")]
impl<F: Family, D: Digest> arbitrary::Arbitrary<'_> for Target<F, D>
where
D: for<'a> arbitrary::Arbitrary<'a>,
{
fn arbitrary(u: &mut arbitrary::Unstructured<'_>) -> arbitrary::Result<Self> {
let root = u.arbitrary()?;
let max_loc = F::MAX_LEAVES;
let lower = u.int_in_range(0..=*max_loc - 1)?;
let upper = u.int_in_range(lower + 1..=*max_loc)?;
Ok(Self {
root,
range: commonware_utils::non_empty_range!(Location::new(lower), Location::new(upper)),
})
}
}
#[derive(Debug)]
pub struct CompactTarget<F: Family, D: Digest> {
pub root: D,
pub size: Location<F>,
}
impl<F: Family, D: Digest> TryFrom<&CompactTarget<F, D>> for Target<F, D> {
type Error = EngineError<F, D>;
fn try_from(target: &CompactTarget<F, D>) -> Result<Self, Self::Error> {
let end = target.size;
let start =
end.checked_sub(1)
.filter(|_| end.is_valid())
.ok_or(EngineError::InvalidTarget {
lower_bound_pos: Location::new(0),
upper_bound_pos: end,
})?;
Ok(Self {
root: target.root,
range: non_empty_range!(start, end),
})
}
}
impl<F: Family, D: Digest> Clone for CompactTarget<F, D> {
fn clone(&self) -> Self {
Self {
root: self.root,
size: self.size,
}
}
}
impl<F: Family, D: Digest> PartialEq for CompactTarget<F, D> {
fn eq(&self, other: &Self) -> bool {
self.root == other.root && self.size == other.size
}
}
impl<F: Family, D: Digest> Eq for CompactTarget<F, D> {}
impl<F: Family, D: Digest> Write for CompactTarget<F, D> {
fn write(&self, buf: &mut impl BufMut) {
self.root.write(buf);
self.size.write(buf);
}
}
impl<F: Family, D: Digest> EncodeSize for CompactTarget<F, D> {
fn encode_size(&self) -> usize {
self.root.encode_size() + self.size.encode_size()
}
}
impl<F: Family, D: Digest> Read for CompactTarget<F, D> {
type Cfg = ();
fn read_cfg(buf: &mut impl Buf, _: &()) -> Result<Self, CodecError> {
let root = D::read(buf)?;
let size = Location::<F>::read(buf)?;
if !size.is_valid() || size == 0 {
return Err(CodecError::Invalid(
"storage::qmdb::sync::CompactTarget",
"size must be in 1..=MAX_LEAVES",
));
}
Ok(Self { root, size })
}
}
#[cfg(feature = "arbitrary")]
impl<F: Family, D: Digest> arbitrary::Arbitrary<'_> for CompactTarget<F, D>
where
D: for<'a> arbitrary::Arbitrary<'a>,
{
fn arbitrary(u: &mut arbitrary::Unstructured<'_>) -> arbitrary::Result<Self> {
let root = u.arbitrary()?;
let size = Location::new(u.int_in_range(1..=*F::MAX_LEAVES)?);
Ok(Self { root, size })
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::merkle::mmr::Family as MmrFamily;
use commonware_codec::{DecodeExt as _, Encode as _};
use commonware_cryptography::sha256;
use commonware_utils::non_empty_range;
use std::io::Cursor;
fn target(root: sha256::Digest, start: u64, end: u64) -> Target<MmrFamily, sha256::Digest> {
Target::new(
root,
non_empty_range!(Location::new(start), Location::new(end)),
)
}
#[test]
fn test_sync_target_serialization() {
let target = target(sha256::Digest::from([42; 32]), 100, 500);
let mut buffer = Vec::new();
target.write(&mut buffer);
assert_eq!(buffer.len(), target.encode_size());
let mut cursor = Cursor::new(buffer);
let deserialized = Target::read(&mut cursor).unwrap();
assert_eq!(target, deserialized);
assert_eq!(target.root, deserialized.root);
assert_eq!(target.range, deserialized.range);
}
#[test]
fn test_sync_target_read_invalid_bounds() {
let mut buffer = Vec::new();
sha256::Digest::from([42; 32]).write(&mut buffer);
Location::<MmrFamily>::new(100).write(&mut buffer); Location::<MmrFamily>::new(50).write(&mut buffer);
let mut cursor = Cursor::new(buffer);
assert!(matches!(
Target::<MmrFamily, sha256::Digest>::read(&mut cursor),
Err(CodecError::Invalid("NonEmptyRange", "start must be < end"))
));
let root = sha256::Digest::from([42; 32]);
let mut buffer = Vec::new();
root.write(&mut buffer);
Location::<MmrFamily>::new(100).write(&mut buffer);
Location::<MmrFamily>::new(100).write(&mut buffer);
let mut cursor = Cursor::new(buffer);
assert!(matches!(
Target::<MmrFamily, sha256::Digest>::read(&mut cursor),
Err(CodecError::Invalid("NonEmptyRange", "start must be < end"))
));
}
#[test]
fn test_compact_target_decode_rejects_zero_size() {
let unused_root = sha256::Digest::from([42; 32]);
let encoded = CompactTarget::<MmrFamily, sha256::Digest> {
root: unused_root,
size: Location::new(0),
}
.encode();
assert!(CompactTarget::<MmrFamily, sha256::Digest>::decode(encoded).is_err());
}
#[test]
fn test_advances() {
let current_root = sha256::Digest::from([0; 32]);
let advanced_root = sha256::Digest::from([1; 32]);
let current = target(current_root, 10, 100);
assert!(target(advanced_root, 10, 101).advances(¤t));
assert!(target(advanced_root, 50, 200).advances(¤t));
assert!(!target(current_root, 10, 100).advances(¤t));
assert!(!target(current_root, 10, 50).advances(¤t));
assert!(!target(advanced_root, 5, 200).advances(¤t));
let beyond = target(advanced_root, 10, *MmrFamily::MAX_LEAVES + 1);
assert!(!beyond.advances(¤t));
}
#[test]
fn test_compact_target_serialization() {
let target = CompactTarget::<MmrFamily, sha256::Digest> {
root: sha256::Digest::from([42; 32]),
size: Location::new(100),
};
let mut buffer = Vec::new();
target.write(&mut buffer);
assert_eq!(buffer.len(), target.encode_size());
let mut cursor = Cursor::new(buffer);
let deserialized = CompactTarget::read(&mut cursor).unwrap();
assert_eq!(target, deserialized);
assert_eq!(target.root, deserialized.root);
assert_eq!(target.size, deserialized.size);
}
#[test]
fn test_compact_target_decode_rejects_size_beyond_domain() {
let mut buffer = Vec::new();
sha256::Digest::from([42; 32]).write(&mut buffer);
Location::<MmrFamily>::new(*MmrFamily::MAX_LEAVES + 1).write(&mut buffer);
let mut cursor = Cursor::new(buffer);
assert!(matches!(
CompactTarget::<MmrFamily, sha256::Digest>::read(&mut cursor),
Err(CodecError::Invalid(_, _))
));
}
#[test]
fn test_compact_target_to_ranged() {
let root = sha256::Digest::from([42; 32]);
let compact = CompactTarget::<MmrFamily, _> {
root,
size: Location::new(100),
};
let ranged = Target::try_from(&compact).unwrap();
assert_eq!(ranged.root, root);
assert_eq!(ranged.range.start(), Location::new(99));
assert_eq!(ranged.range.end(), Location::new(100));
let genesis = CompactTarget::<MmrFamily, _> {
root,
size: Location::new(1),
};
let ranged = Target::try_from(&genesis).unwrap();
assert_eq!(ranged.range.start(), Location::new(0));
assert_eq!(ranged.range.end(), Location::new(1));
let empty = CompactTarget::<MmrFamily, _> {
root,
size: Location::new(0),
};
assert!(matches!(
Target::try_from(&empty),
Err(EngineError::InvalidTarget { .. })
));
let beyond = CompactTarget::<MmrFamily, _> {
root,
size: MmrFamily::MAX_LEAVES + 1,
};
assert!(matches!(
Target::try_from(&beyond),
Err(EngineError::InvalidTarget { .. })
));
}
#[cfg(feature = "arbitrary")]
mod conformance {
use super::*;
use crate::merkle::mmb;
use commonware_codec::conformance::CodecConformance;
commonware_conformance::conformance_tests! {
CodecConformance<Target<MmrFamily, sha256::Digest>>,
CodecConformance<Target<mmb::Family, sha256::Digest>>,
CodecConformance<CompactTarget<MmrFamily, sha256::Digest>>,
CodecConformance<CompactTarget<mmb::Family, sha256::Digest>>,
}
}
}