//! Shared conformance test utilities and stability tests for Merkle-family structures. use crate::merkle::{hasher::Hasher, mem::Mem, Family}; use commonware_cryptography::sha256; /// Build a test Merkle structure by adding `elements` elements using the provided hasher. /// /// Each element's preimage is `i.to_be_bytes()` for `i` in `0..elements`. The elements are /// first hashed (via [`Hasher::digest`]) before being added, so the leaf digests are /// deterministic regardless of family. pub fn build_test_mem( hasher: &H, mut mem: Mem, elements: u64, ) -> Mem where F: Family, H: Hasher, { let batch = { let mut batch = mem.new_batch(); for i in 0u64..elements { let element = hasher.digest(&i.to_be_bytes()); batch = batch.add(hasher, &element); } batch.merkleize(&mem, hasher) }; mem.apply_batch(&batch).unwrap(); mem } /// Build a test MMR by adding `elements` elements using the provided hasher. /// /// Thin wrapper around [`build_test_mem`] with the MMR family types fixed. pub fn build_test_mmr>( hasher: &H, mmr: crate::mmr::mem::Mmr, elements: u64, ) -> crate::mmr::mem::Mmr { build_test_mem(hasher, mmr, elements) } #[cfg(feature = "arbitrary")] mod tests { use super::*; use crate::merkle::{full, Bagging::ForwardFold}; use commonware_conformance::{conformance_tests, Conformance}; use commonware_cryptography::Sha256; use commonware_parallel::Sequential; use commonware_runtime::{ buffer::paged::CacheRef, conformance::{StorageConformance, StorageWorkload}, BufferPooler, Supervisor as _, }; use commonware_utils::{NZUsize, NZU16, NZU64}; use rand::RngExt as _; type Standard = crate::merkle::hasher::Standard; /// Tests stability of MMR root computation. /// /// Builds an MMR by adding `seed` elements and returns the final root. Any change to the root /// computation algorithm will cause this test to fail. struct MmrRootStability; impl Conformance for MmrRootStability { async fn commit(seed: u64) -> Vec { let hasher = Standard::new(ForwardFold); let mmr = crate::mmr::mem::Mmr::new(); build_test_mem(&hasher, mmr, seed) .root(&hasher, 0) .unwrap() .to_vec() } } /// Tests stability of MMB root computation. /// /// Builds an MMB by adding `seed` elements and returns the final root. Any change to the root /// computation algorithm will cause this test to fail. struct MmbRootStability; impl Conformance for MmbRootStability { async fn commit(seed: u64) -> Vec { let hasher = Standard::new(ForwardFold); let mmb = crate::mmb::mem::Mmb::new(); build_test_mem(&hasher, mmb, seed) .root(&hasher, 0) .unwrap() .to_vec() } } fn storage_config(prefix: &str, pooler: &impl BufferPooler) -> full::Config { full::Config { journal_partition: format!("{prefix}-journal"), metadata_partition: format!("{prefix}-metadata"), items_per_blob: NZU64!(11), write_buffer: NZUsize!(1024), strategy: Sequential, page_cache: CacheRef::from_pooler(pooler, NZU16!(1024), NZUsize!(10)), } } async fn run_full_merkle( mut context: commonware_runtime::deterministic::Context, seed: u64, prefix: &'static str, ) -> Result<(), crate::merkle::Error> where F: Family, { let hasher = Standard::new(ForwardFold); let cfg = storage_config(&format!("{prefix}-{seed}"), &context); let mut merkle = full::Merkle::::init( context.child("merkle"), &hasher, cfg, ) .await?; let items = context.random_range(16..96); let mut batch = merkle.new_batch(); for i in 0..items { let element = hasher.digest(&seed.wrapping_add(i as u64).to_be_bytes()); batch = batch.add(&hasher, &element); } let batch = merkle.with_mem(|mem| batch.merkleize(mem, &hasher)); merkle.apply_batch(&batch)?; merkle.sync().await?; if items > 32 { merkle .prune(crate::merkle::Location::new(items as u64 / 3)) .await?; merkle.sync().await?; } Ok(()) } struct MmrFullWorkload; impl StorageWorkload for MmrFullWorkload { type Error = crate::merkle::Error; async fn run( context: commonware_runtime::deterministic::Context, seed: u64, ) -> Result<(), Self::Error> { run_full_merkle::(context, seed, "mmr-full-conformance").await } } struct MmbFullWorkload; impl StorageWorkload for MmbFullWorkload { type Error = crate::merkle::Error; async fn run( context: commonware_runtime::deterministic::Context, seed: u64, ) -> Result<(), Self::Error> { run_full_merkle::(context, seed, "mmb-full-conformance").await } } conformance_tests! { MmrRootStability => 200, MmbRootStability => 200, StorageConformance => 256, StorageConformance => 256, } }