minerva 0.2.0

Causal ordering for distributed systems
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};

/// A generated cut-shaped entry list over a small station alphabet.
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())
}

/// A generated epoch address as raw parts (counter nonzero by construction).
fn arb_address_parts() -> impl Strategy<Value = (u64, (u32, u64))> {
    (1u64..=6, 0u32..=4, 1u64..=50)
        .prop_map(|(generation, station, index)| (generation, (station, index)))
}

/// A generated canonical seal frame beside its section values: built
/// from sorted sets in the exact wire spelling, so decode is exercised
/// over the whole accepted space without a machine drive per case (the
/// encode side is covered by the machine-driven examples; the bijection
/// closes the loop here: decode then re-encode must reproduce the
/// synthesized bytes exactly).
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))]

    /// Declaration round trip: every machine-minted record encodes,
    /// decodes to itself, and the preflight equals the frame (which is
    /// also the drift pin for the gap-free cut-embedding length: the
    /// preflight speaks the embedding codec's own widths, the frame is
    /// the codec's real output).
    #[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;
        // `above` starts at one, so the fresh index is always a dot.
        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));
    }

    /// Confirmation round trip and preflight equality.
    #[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));
    }

    /// Adoption-report round trip and preflight equality.
    #[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));
    }

    /// Seal bijection over synthesized canonical frames: decode accepts,
    /// re-encodes byte for byte, and the preflight equals the frame.
    #[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);
    }

    /// Lineage-proof bijection over synthesized consecutive windows: the
    /// entries are the synthesized seal frames re-addressed onto
    /// consecutive generations through decode and re-encode.
    #[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),
    ) {
        // Rebase each synthesized seal onto consecutive generations by
        // patching the winner-generation field, the one the proof's
        // canonical form constrains.
        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);
    }

    /// Endorsement round trip: every minted claim encodes, decodes to
    /// itself under the exact-count budget, and the preflight equals the
    /// frame. The station alphabet includes zero at both seats --- the
    /// R-91 division held over the generated space.
    #[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)
        );
    }

    /// Totality: no lifecycle decoder panics on arbitrary bytes.
    #[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);
    }

    /// Totality under truncation: every strict prefix of a valid frame
    /// refuses (no frame is a prefix of another value's frame), and no
    /// decoder panics on any prefix.
    #[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());
    }

    /// The bare-vector embedding agreement (the second-oracle drift
    /// pin, stated observably): for every generated cut, the have-set
    /// the frames embed reports the exact length the lifecycle
    /// preflights assume for it, a header plus one run-free station
    /// record per entry.
    #[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());
    }
}

/// The seal and proof decoders are exercised by the synthesized-frame
/// laws above; the machine-driven displaced-window fixture in the
/// example suite covers the encode direction from a genuine
/// [`SealedEpoch`](crate::metis::SealedEpoch). This pin closes the pair:
/// the two directions meet on the same bytes.
#[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));
}

/// A declaration decoded from bytes is consumable by the shipped
/// deliver door exactly as a replicated one is: the record is a report,
/// and the door owns the semantics (the S273 rule, exercised end to
/// end).
#[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);
}