extern crate alloc;
use alloc::collections::{BTreeMap, BTreeSet};
use alloc::vec::Vec;
use proptest::prelude::*;
use crate::metis::{
Admission, AdoptionReportRecord, ConfirmationRecord, Cut, Declaration, Endorsement,
EndorsementDecodeBudget, LineageProofRecord, SealRecord, VersionVector,
};
use super::d;
use super::{cut, declaration};
fn arb_cut_entries() -> impl Strategy<Value = Vec<(u32, u64)>> {
prop::collection::btree_map(0u32..=4, 1u64..=40, 0..=5)
.prop_map(|counts| counts.into_iter().collect())
}
fn arb_address_parts() -> impl Strategy<Value = (u64, (u32, u64))> {
(1u64..=6, 0u32..=4, 1u64..=50)
.prop_map(|(generation, station, index)| (generation, (station, index)))
}
fn arb_seal_frame() -> impl Strategy<Value = Vec<u8>> {
(
arb_address_parts(),
prop::collection::btree_set(arb_address_parts(), 0..=4),
prop::collection::btree_set((0u32..=4, 1u64..=50), 0..=5),
arb_cut_entries(),
)
.prop_map(|(winner, candidates, ledger, join)| {
let mut out = Vec::new();
out.push(0x01);
out.extend_from_slice(&winner.0.to_be_bytes());
out.extend_from_slice(&winner.1.0.to_be_bytes());
out.extend_from_slice(&winner.1.1.to_be_bytes());
let sorted: BTreeSet<(u64, u32, u64)> = candidates
.into_iter()
.map(|(generation, (station, index))| (generation, station, index))
.collect();
out.extend_from_slice(&u32::try_from(sorted.len()).unwrap().to_be_bytes());
for (generation, station, index) in sorted {
out.extend_from_slice(&generation.to_be_bytes());
out.extend_from_slice(&station.to_be_bytes());
out.extend_from_slice(&index.to_be_bytes());
}
out.extend_from_slice(&u32::try_from(ledger.len()).unwrap().to_be_bytes());
for (station, index) in ledger {
out.extend_from_slice(&station.to_be_bytes());
out.extend_from_slice(&index.to_be_bytes());
}
let join: BTreeMap<u32, u64> = join.into_iter().collect();
out.push(0x01);
out.extend_from_slice(&u32::try_from(join.len()).unwrap().to_be_bytes());
for (station, floor) in join {
out.extend_from_slice(&station.to_be_bytes());
out.extend_from_slice(&floor.to_be_bytes());
out.extend_from_slice(&0u32.to_be_bytes());
}
out
})
}
proptest! {
#![proptest_config(ProptestConfig::with_cases(256))]
#[test]
fn prop_declaration_round_trips(
entries in arb_cut_entries(),
station in 0u32..=4,
above in 1u64..=9,
tick in 0u64..=1_000,
) {
let held = entries.iter().copied().collect::<BTreeMap<u32, u64>>();
let fresh = held.get(&station).copied().unwrap_or(0) + above;
let record = declaration(&[0, 1, 2, 3, 4], d(station, fresh), tick, &entries);
let frame = record.to_bytes();
prop_assert_eq!(record.encoded_len(), frame.len());
prop_assert_eq!(Declaration::from_bytes(&frame), Ok(record));
}
#[test]
fn prop_confirmation_round_trips(
parts in arb_address_parts(),
entries in arb_cut_entries(),
) {
let epoch = crate::metis::EpochAddress::try_from_parts(parts.0, parts.1).unwrap();
let record = ConfirmationRecord::new(epoch, cut(&entries));
let frame = record.to_bytes();
prop_assert_eq!(record.encoded_len(), frame.len());
prop_assert_eq!(ConfirmationRecord::from_bytes(&frame), Ok(record));
}
#[test]
fn prop_adoption_report_round_trips(
parts in arb_address_parts(),
own_counter in 0u64..=u64::MAX,
) {
let epoch = crate::metis::EpochAddress::try_from_parts(parts.0, parts.1).unwrap();
let record = AdoptionReportRecord::new(epoch, own_counter);
let frame = record.to_bytes();
prop_assert_eq!(record.encoded_len(), frame.len());
prop_assert_eq!(AdoptionReportRecord::from_bytes(&frame), Ok(record));
}
#[test]
fn prop_seal_frames_round_trip(frame in arb_seal_frame()) {
let record = SealRecord::from_bytes(&frame).expect("a synthesized canonical frame decodes");
let bytes = record.to_bytes();
prop_assert_eq!(record.encoded_len(), bytes.len());
prop_assert_eq!(bytes, frame);
}
#[test]
fn prop_lineage_proof_frames_round_trip(
frames in prop::collection::vec(arb_seal_frame(), 0..=3),
nodes in prop::collection::vec(prop::array::uniform32(any::<u8>()), 3),
) {
let mut out = Vec::new();
out.push(0x01);
out.extend_from_slice(&u32::try_from(frames.len()).unwrap().to_be_bytes());
for (offset, frame) in frames.iter().enumerate() {
let mut frame = frame.clone();
frame[1..9].copy_from_slice(&(offset as u64 + 1).to_be_bytes());
out.extend_from_slice(&nodes[offset]);
out.extend_from_slice(&frame);
}
let proof = LineageProofRecord::from_bytes(&out)
.expect("a synthesized consecutive proof decodes");
prop_assert_eq!(proof.encoded_len(), out.len());
prop_assert_eq!(proof.to_bytes(), out);
}
#[test]
fn prop_endorsement_round_trips(
base in arb_address_parts(),
joiners in prop::collection::btree_set(0u32..=100, 1..=6),
commitment in prop::array::uniform32(any::<u8>()),
) {
let base = crate::metis::EpochAddress::try_from_parts(base.0, base.1).unwrap();
let count = joiners.len();
let admission = Admission::new(base, joiners).expect("nonempty by construction");
let record = Endorsement::from_parts(admission, commitment);
let frame = record.to_bytes();
prop_assert_eq!(record.encoded_len(), frame.len());
prop_assert_eq!(Endorsement::from_bytes(&frame), Ok(record.clone()));
prop_assert_eq!(
Endorsement::from_bytes_with_budget(&frame, EndorsementDecodeBudget::new(count)),
Ok(record)
);
}
#[test]
fn prop_lifecycle_decoders_never_panic(bytes in prop::collection::vec(any::<u8>(), 0..192)) {
let _ = Declaration::from_bytes(&bytes);
let _ = ConfirmationRecord::from_bytes(&bytes);
let _ = AdoptionReportRecord::from_bytes(&bytes);
let _ = SealRecord::from_bytes(&bytes);
let _ = LineageProofRecord::from_bytes(&bytes);
let _ = Endorsement::from_bytes(&bytes);
}
#[test]
fn prop_lifecycle_truncation_never_panics_and_always_refuses(
entries in arb_cut_entries(),
keep in 0usize..=64,
) {
let record = declaration(&[1, 2, 3, 4], d(1, 100), 7, &entries);
let frame = record.to_bytes();
let keep = keep.min(frame.len().saturating_sub(1));
prop_assert!(Declaration::from_bytes(&frame[..keep]).is_err());
let confirmation = ConfirmationRecord::new(record.address(), cut(&entries));
let frame = confirmation.to_bytes();
let keep = keep.min(frame.len().saturating_sub(1));
prop_assert!(ConfirmationRecord::from_bytes(&frame[..keep]).is_err());
let admission = Admission::new(record.address(), [0, 4]).unwrap();
let endorsement = Endorsement::from_parts(admission, [0xA5; 32]);
let frame = endorsement.to_bytes();
let keep = keep.min(frame.len().saturating_sub(1));
prop_assert!(Endorsement::from_bytes(&frame[..keep]).is_err());
}
#[test]
fn prop_gap_free_embedding_length_agrees(entries in arb_cut_entries()) {
let witnessed = cut(&entries);
let embedded = crate::metis::DotSet::from_witnessed(&witnessed);
let vector: VersionVector = witnessed.as_vector().clone();
prop_assert_eq!(embedded.encoded_len(), 5 + vector.iter().count() * 16);
prop_assert_eq!(embedded.to_bytes().len(), embedded.encoded_len());
}
}
#[test]
fn test_the_two_seal_directions_agree_on_the_fixture() {
let (_, sealed) = super::displaced_window();
let record = SealRecord::from_sealed(&sealed);
let frame = record.to_bytes();
assert_eq!(SealRecord::from_bytes(&frame), Ok(record));
}
#[test]
fn test_a_decoded_declaration_feeds_the_deliver_door() {
use core::num::NonZeroUsize;
let record = declaration(&[1, 2], d(2, 3), 7, &[(1, 2), (2, 2)]);
let decoded = Declaration::from_bytes(&record.to_bytes()).unwrap();
let witnessed = cut(&[(1, 2), (2, 2)]);
let mut stability = crate::metis::Stability::new([1, 2]);
for station in [1, 2] {
stability.report_cut(station, &witnessed).unwrap();
}
let mut epochs = crate::metis::Epochs::new([1, 2], NonZeroUsize::new(1).unwrap());
epochs
.deliver(decoded, &stability, &Cut::bottom())
.expect("the decoded record is the record the door consumes");
assert_eq!(epochs.candidates().count(), 1);
}