radixdb-storage 1.1.0

Storage contracts and physical persistence engine for RadixDB
Documentation
use radixdb_storage::v6::{
    decode_control_slot, encode_control_slot, select_control_slots, CatalogGeneration, CatalogId,
    CatalogRootRef, ControlRecord, ControlSlotIndex, DatabaseGeneration, DatabaseId,
    DatabaseManifestRootRef, FormatError, ManifestGeneration, ManifestId, WalGeneration,
    WalReplayFloor, WriterInstanceId, CONTROL_RECORD_BYTES,
};

fn identity(marker: u8) -> [u8; 16] {
    [marker; 16]
}

fn record(slot: ControlSlotIndex, generation: u64, marker: u8) -> ControlRecord {
    ControlRecord::new(
        slot,
        DatabaseGeneration::new(generation).unwrap(),
        DatabaseId::from_bytes(identity(marker)).unwrap(),
        DatabaseManifestRootRef::new(
            ManifestId::from_bytes(identity(marker.wrapping_add(1))).unwrap(),
            ManifestGeneration::new(generation).unwrap(),
            [marker.wrapping_add(2); 32],
        ),
        CatalogRootRef::new(
            CatalogId::from_bytes(identity(marker.wrapping_add(3))).unwrap(),
            CatalogGeneration::new(generation + 10).unwrap(),
            [marker.wrapping_add(4); 32],
        ),
        WalReplayFloor::new(
            WalGeneration::new(generation + 20).unwrap(),
            generation * 100,
        ),
        generation * 1_000,
        WriterInstanceId::from_bytes(identity(marker.wrapping_add(5))).unwrap(),
    )
    .unwrap()
}

fn refresh_crc(bytes: &mut [u8]) {
    let mut hasher = crc32fast::Hasher::new();
    hasher.update(&bytes[..248]);
    hasher.update(&bytes[252..]);
    bytes[248..252].copy_from_slice(&hasher.finalize().to_le_bytes());
}

#[test]
fn control_codec_has_exact_layout_and_roundtrips() {
    let expected = record(ControlSlotIndex::One, 7, 0x11);
    let bytes = encode_control_slot(expected);

    assert_eq!(bytes.len(), CONTROL_RECORD_BYTES);
    assert_eq!(&bytes[..8], b"RDX6CTL\0");
    assert_eq!(u16::from_le_bytes(bytes[8..10].try_into().unwrap()), 6);
    assert_eq!(u16::from_le_bytes(bytes[10..12].try_into().unwrap()), 0);
    assert_eq!(u32::from_le_bytes(bytes[12..16].try_into().unwrap()), 4096);
    assert_eq!(bytes[16], 1);
    assert_eq!(bytes[17], 1);
    assert_eq!(u64::from_le_bytes(bytes[24..32].try_into().unwrap()), 7);
    assert_eq!(&bytes[32..48], &[0x11; 16]);
    assert_eq!(&bytes[48..64], &[0x12; 16]);
    assert_eq!(u64::from_le_bytes(bytes[64..72].try_into().unwrap()), 7);
    assert_eq!(&bytes[72..88], &[0x14; 16]);
    assert_eq!(u64::from_le_bytes(bytes[88..96].try_into().unwrap()), 17);
    assert_eq!(u64::from_le_bytes(bytes[96..104].try_into().unwrap()), 27);
    assert_eq!(u64::from_le_bytes(bytes[104..112].try_into().unwrap()), 700);
    assert_eq!(
        u64::from_le_bytes(bytes[112..120].try_into().unwrap()),
        7_000
    );
    assert_eq!(&bytes[128..160], &[0x13; 32]);
    assert_eq!(&bytes[160..192], &[0x15; 32]);
    assert_eq!(&bytes[192..208], &[0x16; 16]);
    assert!(bytes[208..248].iter().all(|byte| *byte == 0));
    assert!(bytes[252..].iter().all(|byte| *byte == 0));
    assert_ne!(&bytes[248..252], &[0; 4]);

    assert_eq!(
        decode_control_slot(&bytes, ControlSlotIndex::One).unwrap(),
        expected
    );
}

#[test]
fn control_decoder_rejects_torn_unknown_and_mismatched_records() {
    let original = encode_control_slot(record(ControlSlotIndex::Zero, 1, 0x21));
    assert!(decode_control_slot(&original[..4095], ControlSlotIndex::Zero).is_err());
    let mut extra = original.to_vec();
    extra.push(0);
    assert!(decode_control_slot(&extra, ControlSlotIndex::Zero).is_err());
    assert!(decode_control_slot(&original, ControlSlotIndex::One).is_err());

    let mut bad_crc = original;
    bad_crc[32] ^= 1;
    assert_eq!(
        decode_control_slot(&bad_crc, ControlSlotIndex::Zero),
        Err(FormatError::ControlChecksumMismatch)
    );

    let mut reserved = original;
    reserved[3000] = 1;
    refresh_crc(&mut reserved);
    assert!(matches!(
        decode_control_slot(&reserved, ControlSlotIndex::Zero),
        Err(FormatError::InvalidControlRecord { .. })
    ));

    let mut flags = original;
    flags[120..128].copy_from_slice(&1_u64.to_le_bytes());
    refresh_crc(&mut flags);
    assert!(matches!(
        decode_control_slot(&flags, ControlSlotIndex::Zero),
        Err(FormatError::InvalidControlRecord { .. })
    ));

    let mut zero_database = original;
    zero_database[32..48].fill(0);
    refresh_crc(&mut zero_database);
    assert!(matches!(
        decode_control_slot(&zero_database, ControlSlotIndex::Zero),
        Err(FormatError::ZeroIdentity { .. })
    ));
}

#[test]
fn selector_falls_back_from_newer_incomplete_or_invalid_slot() {
    let old = encode_control_slot(record(ControlSlotIndex::Zero, 4, 0x31));
    let new = encode_control_slot(record(ControlSlotIndex::One, 5, 0x41));

    let selected = select_control_slots(&old, &new, |candidate| {
        candidate.database_generation().get() == 4
    })
    .unwrap();
    assert_eq!(selected.database_generation().get(), 4);

    let selected = select_control_slots(&old, &new, |_| true).unwrap();
    assert_eq!(selected.database_generation().get(), 5);

    let selected = select_control_slots(&old, &new[..4090], |_| true).unwrap();
    assert_eq!(selected.database_generation().get(), 4);

    assert_eq!(
        select_control_slots(&old, &new, |_| false),
        Err(FormatError::NoCompleteControlGeneration)
    );
    assert_eq!(
        select_control_slots(&old[..10], &new[..10], |_| true),
        Err(FormatError::NoValidControlSlot)
    );
}

#[test]
fn equal_generation_with_different_roots_is_split_brain() {
    let left = encode_control_slot(record(ControlSlotIndex::Zero, 9, 0x51));
    let right = encode_control_slot(record(ControlSlotIndex::One, 9, 0x61));
    assert_eq!(
        select_control_slots(&left, &right, |_| true),
        Err(FormatError::ControlSplitBrain { generation: 9 })
    );
}

#[test]
fn control_constructor_rejects_manifest_generation_mismatch() {
    let result = ControlRecord::new(
        ControlSlotIndex::Zero,
        DatabaseGeneration::new(3).unwrap(),
        DatabaseId::from_bytes(identity(1)).unwrap(),
        DatabaseManifestRootRef::new(
            ManifestId::from_bytes(identity(2)).unwrap(),
            ManifestGeneration::new(2).unwrap(),
            [3; 32],
        ),
        CatalogRootRef::new(
            CatalogId::from_bytes(identity(4)).unwrap(),
            CatalogGeneration::new(5).unwrap(),
            [6; 32],
        ),
        WalReplayFloor::new(WalGeneration::new(7).unwrap(), 8),
        9,
        WriterInstanceId::from_bytes(identity(10)).unwrap(),
    );
    assert!(matches!(
        result,
        Err(FormatError::InvalidControlRecord { .. })
    ));
}