minerva 0.2.0

Causal ordering for distributed systems
//! Protocol-ledger canonicalization across delivery orders.

use super::*;
use crate::metis::Vouched;

/// A displaced declaration stays receipt-accounted without becoming a candidate.
#[test]
fn a_displaced_provisional_declaration_stays_in_the_seal_record() {
    let mut text = Text::new();
    weave(&mut text, d(1, 1), locus(Anchor::Origin, rank(1)));
    let boundary = Cut::floor_of(text.context());

    // The predecessor: station 1 declares at the settled boundary.
    let mut early_stability = Stability::new([1, 2]);
    early_stability.report_cut(1, &boundary).unwrap();
    early_stability.report_cut(2, &boundary).unwrap();
    let mut declarer = Epochs::new([1, 2], NonZeroUsize::new(1).unwrap());
    let predecessor = declarer
        .declare(d(1, 2), rank(2), &early_stability, &Cut::bottom())
        .unwrap();

    // The causally later declaration: station 2's cut covers the
    // predecessor's dot (an unlawful mint an honest door would refuse
    // locally; the delivery machine is charitable to its reordered
    // arrival and displaces it when the predecessor lands).
    let mut later_vector = boundary.as_vector().clone();
    later_vector.observe(1, 2);
    let later_cut = Cut::from_witnessed(later_vector);
    let mut later_stability = Stability::new([1, 2]);
    later_stability.report_cut(1, &later_cut).unwrap();
    later_stability.report_cut(2, &later_cut).unwrap();
    let mut late_declarer = Epochs::new([1, 2], NonZeroUsize::new(1).unwrap());
    let later = late_declarer
        .declare(d(2, 1), rank(3), &later_stability, &Cut::bottom())
        .unwrap();

    // Two subject replicas hear them in opposite orders: reverse causal
    // order accepts then displaces; forward order refuses the later one
    // at arrival. Receipt recording is verdict-independent, so both seal
    // the identical record and consign byte-identically.
    let mut runs = Vec::new();
    for reversed in [true, false] {
        let mut stability = Stability::new([1, 2]);
        stability.report_cut(1, &boundary).unwrap();
        stability.report_cut(2, &boundary).unwrap();
        let mut epochs = Epochs::new([1, 2], NonZeroUsize::new(1).unwrap());
        if reversed {
            epochs
                .deliver(later.clone(), &stability, &Cut::bottom())
                .unwrap();
            epochs
                .deliver(predecessor.clone(), &stability, &Cut::bottom())
                .unwrap();
        } else {
            epochs
                .deliver(predecessor.clone(), &stability, &Cut::bottom())
                .unwrap();
            let refusal = epochs
                .deliver(later.clone(), &stability, &Cut::bottom())
                .unwrap_err();
            assert!(
                matches!(refusal, crate::metis::EpochRefusal::WindowOpen { .. }),
                "the forward order refuses the causally later declaration"
            );
        }

        // The full round: both dots delivered everywhere, the predecessor
        // the sole candidate, adoption, seal.
        let mut delivered = boundary.as_vector().clone();
        delivered.observe(1, 2);
        delivered.observe(2, 1);
        let delivered = Cut::from_witnessed(delivered);
        stability.report_cut(1, &delivered).unwrap();
        stability.report_cut(2, &delivered).unwrap();
        epochs
            .confirm(predecessor.address(), &Vouched::trust(1, delivered.clone()))
            .unwrap();
        epochs
            .confirm(predecessor.address(), &Vouched::trust(2, delivered.clone()))
            .unwrap();
        let adopted = epochs
            .adopt(1, delivered.as_vector().get(1), &stability)
            .unwrap();
        epochs
            .adopt_report(
                predecessor.address(),
                &Vouched::trust(2, delivered.as_vector().get(2)),
            )
            .unwrap();
        let sealed = epochs
            .try_seal(&stability)
            .expect("the displaced record does not block the seal")
            .clone();

        assert!(
            !sealed.contains(later.address()),
            "the displaced declaration is not a retired candidate"
        );
        let dots: Vec<Dot> = sealed.declaration_dots().collect();
        assert!(dots.contains(&d(2, 1)), "its dot stays in the ledger");
        assert!(dots.contains(&d(1, 2)));

        let shadow = EpochStratum::new(&adopted, text.clone(), Moves::new())
            .unwrap()
            .into_transition()
            .shadow;
        let consignment = shadow
            .consign(&sealed)
            .map(|(consignment, _)| consignment)
            .expect("the accounted ledger covers the sealed join");
        assert!(consignment.text().context().contains(d(2, 1)));
        runs.push((sealed, consignment));
    }
    assert_eq!(runs[0].0, runs[1].0, "one sealed record, either order");
    assert_eq!(
        runs[0].1.text().store().to_bytes(),
        runs[1].1.text().store().to_bytes()
    );
    assert_eq!(runs[0].1.text().context(), runs[1].1.text().context());
}

/// Receipt dots above the adoption join are excluded from the sealed ledger.
#[test]
fn an_above_join_refused_declaration_never_contaminates_the_consignment() {
    let mut text = Text::new();
    weave(&mut text, d(1, 1), locus(Anchor::Origin, rank(1)));
    let boundary = Cut::floor_of(text.context());

    let mut early_stability = Stability::new([1, 2]);
    early_stability.report_cut(1, &boundary).unwrap();
    early_stability.report_cut(2, &boundary).unwrap();
    let mut declarer = Epochs::new([1, 2], NonZeroUsize::new(1).unwrap());
    let predecessor = declarer
        .declare(d(1, 2), rank(2), &early_stability, &Cut::bottom())
        .unwrap();

    // The violating late declaration: causally after the predecessor,
    // its dot far above anything the confirmation round will cover.
    let mut later_vector = boundary.as_vector().clone();
    later_vector.observe(1, 2);
    let later_cut = Cut::from_witnessed(later_vector);
    let mut later_stability = Stability::new([1, 2]);
    later_stability.report_cut(1, &later_cut).unwrap();
    later_stability.report_cut(2, &later_cut).unwrap();
    let mut late_declarer = Epochs::new([1, 2], NonZeroUsize::new(1).unwrap());
    let stray = late_declarer
        .declare(d(2, 9), rank(3), &later_stability, &Cut::bottom())
        .unwrap();

    // Replica A receives it (and refuses it); replica B never does.
    let mut runs = Vec::new();
    for receives_stray in [true, false] {
        let mut stability = Stability::new([1, 2]);
        stability.report_cut(1, &boundary).unwrap();
        stability.report_cut(2, &boundary).unwrap();
        let mut epochs = Epochs::new([1, 2], NonZeroUsize::new(1).unwrap());
        epochs
            .deliver(predecessor.clone(), &stability, &Cut::bottom())
            .unwrap();
        if receives_stray {
            let refusal = epochs
                .deliver(stray.clone(), &stability, &Cut::bottom())
                .unwrap_err();
            assert!(matches!(
                refusal,
                crate::metis::EpochRefusal::WindowOpen { .. }
            ));
        }

        let mut delivered = boundary.as_vector().clone();
        delivered.observe(1, 2);
        let delivered = Cut::from_witnessed(delivered);
        stability.report_cut(1, &delivered).unwrap();
        stability.report_cut(2, &delivered).unwrap();
        epochs
            .confirm(predecessor.address(), &Vouched::trust(1, delivered.clone()))
            .unwrap();
        epochs
            .confirm(predecessor.address(), &Vouched::trust(2, delivered.clone()))
            .unwrap();
        let adopted = epochs
            .adopt(1, delivered.as_vector().get(1), &stability)
            .unwrap();
        epochs
            .adopt_report(
                predecessor.address(),
                &Vouched::trust(2, delivered.as_vector().get(2)),
            )
            .unwrap();
        let sealed = epochs.try_seal(&stability).expect("seals").clone();
        assert!(
            sealed.declaration_dots().all(|dot| dot != d(2, 9)),
            "the above-join receipt is canonicalized out of the ledger"
        );

        let shadow = EpochStratum::new(&adopted, text.clone(), Moves::new())
            .unwrap()
            .into_transition()
            .shadow;
        runs.push((sealed, shadow));
    }
    let (sealed_a, shadow_a) = runs.remove(0);
    let (sealed_b, shadow_b) = runs.remove(0);
    assert_eq!(sealed_a, sealed_b, "one sealed record, stray or not");
    let consignment_a = shadow_a.consign(&sealed_a).unwrap().0;
    let consignment_b = shadow_b.consign(&sealed_b).unwrap().0;
    assert_eq!(
        consignment_a.text().store().to_bytes(),
        consignment_b.text().store().to_bytes()
    );
    assert_eq!(
        consignment_a.text().context(),
        consignment_b.text().context()
    );
    assert!(!consignment_a.text().context().contains(d(2, 9)));
}