minerva 0.2.0

Causal ordering for distributed systems
//! Positive layouts and the retained-set round-trip fixture.

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

fn zero_station_declaration_frame() -> Vec<u8> {
    let mut frame = vec![0x02];
    frame.extend_from_slice(&2u64.to_be_bytes());
    frame.extend_from_slice(&0u32.to_be_bytes());
    frame.extend_from_slice(&1u64.to_be_bytes());
    frame.extend_from_slice(&crate::kairos::Kairos::new(7, 0u16, 0, 0u16).to_bytes());
    frame.extend_from_slice(&cut(&[(0, 1)]).to_have_set().to_bytes());
    frame.extend_from_slice(&1u64.to_be_bytes());
    frame.extend_from_slice(&0u32.to_be_bytes());
    frame.extend_from_slice(&1u64.to_be_bytes());
    frame.extend_from_slice(&1u32.to_be_bytes());
    frame.extend_from_slice(&0u32.to_be_bytes());
    frame.extend_from_slice(&[0x5a; 32]);
    frame
}

fn zero_station_seal_frame() -> Vec<u8> {
    let mut frame = vec![0x02];
    frame.extend_from_slice(&2u64.to_be_bytes());
    frame.extend_from_slice(&0u32.to_be_bytes());
    frame.extend_from_slice(&1u64.to_be_bytes());
    frame.extend_from_slice(&1u32.to_be_bytes());
    frame.extend_from_slice(&2u64.to_be_bytes());
    frame.extend_from_slice(&0u32.to_be_bytes());
    frame.extend_from_slice(&1u64.to_be_bytes());
    frame.extend_from_slice(&1u32.to_be_bytes());
    frame.extend_from_slice(&0u32.to_be_bytes());
    frame.extend_from_slice(&1u64.to_be_bytes());
    frame.extend_from_slice(&cut(&[(0, 1)]).to_have_set().to_bytes());
    frame.extend_from_slice(&1u64.to_be_bytes());
    frame.extend_from_slice(&0u32.to_be_bytes());
    frame.extend_from_slice(&1u64.to_be_bytes());
    frame.extend_from_slice(&1u32.to_be_bytes());
    frame.extend_from_slice(&0u32.to_be_bytes());
    frame
}

/// Station zero is accepted and re-encodes identically at every lifecycle seat.
#[test]
fn test_station_zero_round_trips_through_every_lifecycle_codec() {
    let declaration_frame = zero_station_declaration_frame();
    let declaration = Declaration::from_bytes(&declaration_frame).expect("declaration decodes");
    assert_eq!(declaration.dot(), d(0, 1));
    assert_eq!(declaration.rank().station_id(), 0);
    assert_eq!(declaration.cut(), &cut(&[(0, 1)]));
    let admission = declaration.admission().expect("the v2 admission rides");
    assert_eq!(admission.base(), address(1, (0, 1)));
    assert_eq!(admission.joiners().collect::<Vec<_>>(), [0]);
    assert_eq!(declaration.to_bytes(), declaration_frame);

    let confirmation = ConfirmationRecord::new(address(2, (0, 1)), cut(&[(0, 1)]));
    let confirmation_frame = confirmation.to_bytes();
    let decoded =
        ConfirmationRecord::from_bytes(&confirmation_frame).expect("confirmation decodes");
    assert_eq!(decoded, confirmation);
    assert_eq!(decoded.to_bytes(), confirmation_frame);

    let adoption = AdoptionReportRecord::new(address(2, (0, 1)), 0);
    let adoption_frame = adoption.to_bytes();
    let decoded = AdoptionReportRecord::from_bytes(&adoption_frame).expect("adoption decodes");
    assert_eq!(decoded, adoption);
    assert_eq!(decoded.to_bytes(), adoption_frame);

    let seal_frame = zero_station_seal_frame();
    let seal = SealRecord::from_bytes(&seal_frame).expect("seal decodes");
    assert_eq!(seal.declaration(), address(2, (0, 1)));
    assert_eq!(seal.candidates().collect::<Vec<_>>(), [address(2, (0, 1))]);
    assert_eq!(seal.declaration_dots().collect::<Vec<_>>(), [d(0, 1)]);
    assert_eq!(seal.sealed_join(), cut(&[(0, 1)]).as_vector());
    let admission = seal.admission().expect("the v2 admission rides");
    assert_eq!(admission.base(), address(1, (0, 1)));
    assert_eq!(admission.joiners().collect::<Vec<_>>(), [0]);
    assert_eq!(seal.to_bytes(), seal_frame);

    let proof = LineageProofRecord::try_new(vec![LineageProofEntry::new(seal, [0x33; 32])])
        .expect("one retained generation is consecutive");
    let proof_frame = proof.to_bytes();
    assert_eq!(proof_frame[0], 0x02);
    let decoded = LineageProofRecord::from_bytes(&proof_frame).expect("lineage proof decodes");
    assert_eq!(decoded, proof);
    assert_eq!(decoded.to_bytes(), proof_frame);
}

/// The golden declaration layout (the cross-repository fixture
/// obligation): every frame-owned byte pinned, the rank bytes spliced
/// from the stamp codec's own golden territory, and the frame
/// round-trips to the record that produced it.
#[test]
fn test_declaration_to_bytes_layout() {
    let record = declaration(&[1, 2], d(2, 3), 7, &[(1, 2), (2, 2)]);
    let frame = record.to_bytes();

    let mut expected = vec![
        0x01, // version (v1)
        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, // generation = 1
        0x00, 0x00, 0x00, 0x02, // dot station = 2
        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, // dot index = 3
    ];
    expected.extend_from_slice(&rank(7).to_bytes());
    expected.extend_from_slice(&[
        0x01, // have-set version (v1)
        0x00, 0x00, 0x00, 0x02, // station count = 2
        0x00, 0x00, 0x00, 0x01, // station = 1
        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, // floor = 2
        0x00, 0x00, 0x00, 0x00, // run count = 0 (gap-free)
        0x00, 0x00, 0x00, 0x02, // station = 2
        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, // floor = 2
        0x00, 0x00, 0x00, 0x00, // run count = 0 (gap-free)
    ]);
    assert_eq!(frame, expected);
    assert_eq!(record.encoded_len(), frame.len());
    assert_eq!(Declaration::from_bytes(&frame), Ok(record));
}

/// The golden confirmation layout, and the record's `into_parts` feeds
/// the confirm door's argument pair.
#[test]
fn test_confirmation_to_bytes_layout() {
    let record = ConfirmationRecord::new(address(1, (2, 3)), cut(&[(1, 3), (2, 3)]));
    let frame = record.to_bytes();
    assert_eq!(
        frame,
        [
            0x01, // version (v1)
            0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, // generation = 1
            0x00, 0x00, 0x00, 0x02, // candidate station = 2
            0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, // candidate index = 3
            0x01, // have-set version (v1)
            0x00, 0x00, 0x00, 0x02, // station count = 2
            0x00, 0x00, 0x00, 0x01, // station = 1
            0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, // floor = 3
            0x00, 0x00, 0x00, 0x00, // run count = 0
            0x00, 0x00, 0x00, 0x02, // station = 2
            0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, // floor = 3
            0x00, 0x00, 0x00, 0x00, // run count = 0
        ]
    );
    assert_eq!(record.encoded_len(), frame.len());
    let decoded = ConfirmationRecord::from_bytes(&frame).unwrap();
    assert_eq!(decoded, record);
    let (epoch, delivered) = decoded.into_parts();
    assert_eq!(epoch, address(1, (2, 3)));
    assert_eq!(delivered, cut(&[(1, 3), (2, 3)]));
}

/// The golden adoption-report layout: the one fixed-width frame.
#[test]
fn test_adoption_report_to_bytes_layout() {
    let record = AdoptionReportRecord::new(address(1, (2, 3)), 5);
    let frame = record.to_bytes();
    assert_eq!(
        frame,
        [
            0x01, // version (v1)
            0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, // generation = 1
            0x00, 0x00, 0x00, 0x02, // winner station = 2
            0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, // winner index = 3
            0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x05, // own counter = 5
        ]
    );
    assert_eq!(record.encoded_len(), frame.len());
    assert_eq!(AdoptionReportRecord::from_bytes(&frame), Ok(record));
}

/// The golden seal layout, driven off the R4 displaced-declaration
/// window: the ledger keeps the dot the candidates lost, and both
/// retained sets survive the round trip distinctly.
#[test]
fn test_seal_to_bytes_layout_keeps_the_displaced_dot() {
    let (_, sealed) = displaced_window();
    let record = SealRecord::from_sealed(&sealed);
    let frame = record.to_bytes();
    assert_eq!(
        frame,
        [
            0x01, // version (v1)
            0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, // winner generation = 1
            0x00, 0x00, 0x00, 0x02, // winner station = 2
            0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, // winner index = 3
            0x00, 0x00, 0x00, 0x01, // candidate count = 1
            0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, // candidate generation = 1
            0x00, 0x00, 0x00, 0x02, // candidate station = 2
            0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, // candidate index = 3
            0x00, 0x00, 0x00, 0x02, // ledger count = 2
            0x00, 0x00, 0x00, 0x01, // ledger dot station = 1 (displaced)
            0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, // ledger dot index = 3
            0x00, 0x00, 0x00, 0x02, // ledger dot station = 2 (winner)
            0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, // ledger dot index = 3
            0x01, // have-set version (v1)
            0x00, 0x00, 0x00, 0x02, // station count = 2
            0x00, 0x00, 0x00, 0x01, // station = 1
            0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, // sealed join = 3
            0x00, 0x00, 0x00, 0x00, // run count = 0
            0x00, 0x00, 0x00, 0x02, // station = 2
            0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, // sealed join = 3
            0x00, 0x00, 0x00, 0x00, // run count = 0
        ]
    );
    assert_eq!(record.encoded_len(), frame.len());

    let decoded = SealRecord::from_bytes(&frame).unwrap();
    assert_eq!(decoded, record);
    // The R4 distinction survives: the displaced dot rides the ledger
    // and not the candidates.
    let candidates: Vec<EpochAddress> = decoded.candidates().collect();
    assert_eq!(candidates, [address(1, (2, 3))]);
    let dots: Vec<Dot> = decoded.declaration_dots().collect();
    assert_eq!(dots, [d(1, 3), d(2, 3)]);
}

/// The lineage proof carries the sealed records of consecutive
/// generations beside their boundary-node values, and the R4
/// distinction survives the embedding whole.
#[test]
fn test_lineage_proof_round_trips_the_displaced_window() {
    let (mut epochs, first) = displaced_window();

    // A second, uncontested generation sealed through the same machine
    // (already at generation two), so the proof spans consecutive
    // generations. The declaration is minted at the (1, 4)-watermark;
    // the confirmation cuts then cover its dot.
    let mut stability = crate::metis::Stability::new([1, 2]);
    for station in [1, 2] {
        stability
            .report_cut(station, &cut(&[(1, 4), (2, 4)]))
            .unwrap();
    }
    let second = epochs
        .declare(d(1, 5), rank(11), &stability, &Cut::bottom())
        .expect("the second window opens");
    assert_eq!(second.address().generation(), 2);
    let delivered = cut(&[(1, 5), (2, 5)]);
    for station in [1, 2] {
        stability.report_cut(station, &delivered).unwrap();
    }
    epochs
        .confirm(second.address(), &Vouched::trust(1, delivered.clone()))
        .unwrap();
    epochs
        .confirm(second.address(), &Vouched::trust(2, delivered.clone()))
        .unwrap();
    let _ = epochs.adopt(1, 5, &stability).unwrap();
    epochs
        .adopt_report(second.address(), &Vouched::trust(2, 5))
        .unwrap();
    let sealed_two = epochs
        .try_seal(&stability)
        .expect("the second seal fires")
        .clone();

    let proof = LineageProofRecord::try_new(vec![
        LineageProofEntry::new(SealRecord::from_sealed(&first), [0xAA; 32]),
        LineageProofEntry::new(SealRecord::from_sealed(&sealed_two), [0xBB; 32]),
    ])
    .expect("consecutive generations construct");
    let frame = proof.to_bytes();
    assert_eq!(proof.encoded_len(), frame.len());

    let decoded = LineageProofRecord::from_bytes(&frame).unwrap();
    assert_eq!(decoded, proof);
    assert_eq!(decoded.len(), 2);
    assert_eq!(decoded.entries()[0].node(), [0xAA; 32]);
    let dots: Vec<Dot> = decoded.entries()[0].seal().declaration_dots().collect();
    assert_eq!(dots, [d(1, 3), d(2, 3)], "the displaced dot crossed whole");
    assert_eq!(decoded.entries()[0].seal().candidates().count(), 1);

    // The budgeted door accepts under honest ceilings and refuses past
    // them, in both of its units.
    let generous = LineageProofDecodeBudget::new(2, 4);
    assert_eq!(
        LineageProofRecord::from_bytes_with_budget(&frame, generous),
        Ok(proof)
    );
    assert_eq!(
        LineageProofRecord::from_bytes_with_budget(&frame, LineageProofDecodeBudget::new(1, 4)),
        Err(LineageProofDecodeError::TooManyGenerations {
            count: 2,
            budget: 1
        })
    );
    assert_eq!(
        LineageProofRecord::from_bytes_with_budget(&frame, LineageProofDecodeBudget::new(2, 0)),
        Err(LineageProofDecodeError::Seal(
            SealDecodeError::TooManyCandidates {
                count: 1,
                budget: 0
            }
        ))
    );
}