minerva 0.2.0

Causal ordering for distributed systems
extern crate alloc;

use alloc::vec;
use alloc::vec::Vec;
use core::num::NonZeroUsize;

use crate::metis::{
    AdoptionReportDecodeError, AdoptionReportRecord, ConfirmationDecodeError, ConfirmationRecord,
    Cut, Declaration, DeclarationDecodeError, Dot, DotSet, EpochAddress, EpochLedgerDecodeBudget,
    EpochLedgerSnapshot, EpochRefusal, Epochs, InvalidEpochAddress, LineageProofDecodeBudget,
    LineageProofDecodeError, LineageProofEntry, LineageProofRecord, SealDecodeBudget,
    SealDecodeError, SealRecord, Stability, Vouched, WireCutError,
};

use super::{cut, d, declaration, displaced_window, rank};

/// One epoch address from raw parts, unwrapped: the fixture spelling.
fn address(generation: u64, dot: (u32, u64)) -> EpochAddress {
    EpochAddress::try_from_parts(generation, dot).unwrap()
}

mod endorsement;
mod joint;
mod layout;
mod lineage;
mod refusals;

// A chain digest does not authenticate retained sets carried beside it.
#[test]
fn test_identical_node_chains_do_not_determine_the_retained_sets() {
    let (epochs, sealed, loser) = contested_window();
    let full = SealRecord::from_sealed(&sealed);
    let frame = full.to_bytes();

    // Strip only the losing candidate. The winner remains, so both the
    // lifecycle frame and checkpoint machine invariants still hold.
    let mut stripped = frame[..25].to_vec();
    stripped[21..25].copy_from_slice(&1u32.to_be_bytes());
    stripped.extend_from_slice(&frame[45..]);
    let hollow = SealRecord::from_bytes(&stripped).expect("the stripped twin is canonical");
    assert_eq!(hollow.declaration(), full.declaration());
    assert_eq!(hollow.sealed_join(), full.sealed_join());
    assert_eq!(full.candidates().count(), 2);
    assert_eq!(
        hollow.candidates().collect::<Vec<_>>(),
        [full.declaration()]
    );

    // The same node value admits both retained-set spellings.
    let node = [0xCC; 32];
    let genuine = LineageProofRecord::try_new(vec![LineageProofEntry::new(full, node)]).unwrap();
    let forged = LineageProofRecord::try_new(vec![LineageProofEntry::new(hollow, node)]).unwrap();
    assert_eq!(genuine.entries()[0].node(), forged.entries()[0].node());
    assert_ne!(genuine, forged);
    assert_eq!(
        LineageProofRecord::from_bytes(&genuine.to_bytes()),
        Ok(genuine)
    );
    assert_eq!(
        LineageProofRecord::from_bytes(&forged.to_bytes()),
        Ok(forged)
    );

    // The corresponding checkpoint mutation passes the real decode and
    // rehydrate doors while changing old-address replay recognition.
    let snapshot = epochs.snapshot().to_bytes();
    assert_eq!(&snapshot[61..69], &2u64.to_be_bytes());
    let mut stripped = snapshot[..61].to_vec();
    stripped.extend_from_slice(&1u64.to_be_bytes());
    stripped.extend_from_slice(&snapshot[81..]);
    let budget = EpochLedgerDecodeBudget::new(2, 2, 2, 2, 2);
    let decoded = EpochLedgerSnapshot::from_bytes(&stripped, budget)
        .expect("the reduced checkpoint remains structurally valid");
    let reduced = Epochs::rehydrate(&decoded, [1, 2], NonZeroUsize::new(2).unwrap())
        .expect("the checkpoint matches the deployment");
    assert_eq!(epochs.recognize(loser, loser.declaration()), Ok(()));
    assert_eq!(
        reduced.recognize(loser, loser.declaration()),
        Err(EpochRefusal::BeyondHorizon { epoch: loser })
    );
}

fn contested_window() -> (Epochs, crate::metis::SealedEpoch, EpochAddress) {
    let loser = declaration(&[1, 2], d(1, 3), 7, &[(1, 2), (2, 2)]);
    let winner = declaration(&[1, 2], d(2, 3), 9, &[(1, 2), (2, 2)]);
    let mut epochs = Epochs::new([1, 2], NonZeroUsize::new(2).unwrap());
    let mut stability = Stability::new([1, 2]);
    let base = cut(&[(1, 2), (2, 2)]);
    for station in [1, 2] {
        stability.report_cut(station, &base).unwrap();
    }
    epochs
        .deliver(loser.clone(), &stability, &Cut::bottom())
        .unwrap();
    epochs
        .deliver(winner.clone(), &stability, &Cut::bottom())
        .unwrap();

    let delivered = cut(&[(1, 3), (2, 3)]);
    for station in [1, 2] {
        stability.report_cut(station, &delivered).unwrap();
    }
    epochs
        .confirm(loser.address(), &Vouched::trust(1, delivered.clone()))
        .unwrap();
    epochs
        .confirm(winner.address(), &Vouched::trust(2, delivered.clone()))
        .unwrap();
    let adopted = epochs.adopt(1, 3, &stability).unwrap();
    assert_eq!(adopted.address(), winner.address());
    epochs
        .adopt_report(winner.address(), &Vouched::trust(2, 3))
        .unwrap();
    let sealed = epochs.try_seal(&stability).unwrap().clone();
    assert!(sealed.contains(loser.address()));
    assert!(sealed.contains(winner.address()));
    (epochs, sealed, loser.address())
}