use ruvix_region::backing::MemoryBacking;
use ruvix_region::AppendOnlyRegion;
use ruvix_types::{GraphMutation, KernelError, ProofAttestation, RegionHandle, VectorKey};
use crate::Result;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[repr(u8)]
pub enum WitnessEntryType {
VectorMutation = 1,
GraphMutation = 2,
StoreCreation = 3,
StoreDestruction = 4,
}
impl WitnessEntryType {
#[inline]
#[must_use]
pub const fn from_u8(value: u8) -> Option<Self> {
match value {
1 => Some(Self::VectorMutation),
2 => Some(Self::GraphMutation),
3 => Some(Self::StoreCreation),
4 => Some(Self::StoreDestruction),
_ => None,
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[repr(C)]
pub struct WitnessEntry {
pub entry_type: WitnessEntryType,
_padding: [u8; 3],
pub store_id: u32,
pub sequence: u64,
pub attestation: ProofAttestation,
pub prev_hash: [u8; 32],
pub timestamp_ns: u64,
}
impl WitnessEntry {
pub const SIZE: usize = core::mem::size_of::<Self>();
#[inline]
#[must_use]
pub const fn new(
entry_type: WitnessEntryType,
store_id: u32,
sequence: u64,
attestation: ProofAttestation,
prev_hash: [u8; 32],
timestamp_ns: u64,
) -> Self {
Self {
entry_type,
_padding: [0; 3],
store_id,
sequence,
attestation,
prev_hash,
timestamp_ns,
}
}
#[must_use]
pub fn compute_hash(&self) -> [u8; 32] {
let mut hash = [0u8; 32];
hash[0] = self.entry_type as u8;
hash[1..5].copy_from_slice(&self.store_id.to_le_bytes());
hash[5..13].copy_from_slice(&self.sequence.to_le_bytes());
hash[13..21].copy_from_slice(&self.timestamp_ns.to_le_bytes());
for i in 0..32 {
hash[i] ^= self.attestation.proof_term_hash[i];
}
for i in 0..32 {
hash[i] ^= self.prev_hash[i];
}
hash
}
#[must_use]
pub fn to_bytes(&self) -> [u8; Self::SIZE] {
unsafe { core::mem::transmute_copy(self) }
}
#[must_use]
pub fn from_bytes(bytes: &[u8; Self::SIZE]) -> Option<Self> {
let entry_type = WitnessEntryType::from_u8(bytes[0])?;
let mut entry: Self = unsafe { core::mem::transmute_copy(bytes) };
entry.entry_type = entry_type;
Some(entry)
}
}
pub struct WitnessLog<B: MemoryBacking> {
region: AppendOnlyRegion<B>,
last_hash: [u8; 32],
sequence: u64,
store_id: u32,
}
impl<B: MemoryBacking> WitnessLog<B> {
pub fn new(
backing: B,
max_entries: usize,
handle: RegionHandle,
store_id: u32,
) -> Result<Self> {
let max_size = max_entries
.checked_mul(WitnessEntry::SIZE)
.ok_or(KernelError::InvalidArgument)?;
let region = AppendOnlyRegion::new(backing, max_size, handle)?;
Ok(Self {
region,
last_hash: [0u8; 32],
sequence: 0,
store_id,
})
}
pub fn record_vector_mutation(
&mut self,
_key: VectorKey,
attestation: ProofAttestation,
timestamp_ns: u64,
) -> Result<WitnessEntry> {
let entry = WitnessEntry::new(
WitnessEntryType::VectorMutation,
self.store_id,
self.sequence,
attestation,
self.last_hash,
timestamp_ns,
);
self.append_entry(entry)
}
pub fn record_graph_mutation(
&mut self,
_mutation: &GraphMutation,
attestation: ProofAttestation,
timestamp_ns: u64,
) -> Result<WitnessEntry> {
let entry = WitnessEntry::new(
WitnessEntryType::GraphMutation,
self.store_id,
self.sequence,
attestation,
self.last_hash,
timestamp_ns,
);
self.append_entry(entry)
}
pub fn record_store_creation(
&mut self,
attestation: ProofAttestation,
timestamp_ns: u64,
) -> Result<WitnessEntry> {
let entry = WitnessEntry::new(
WitnessEntryType::StoreCreation,
self.store_id,
self.sequence,
attestation,
self.last_hash,
timestamp_ns,
);
self.append_entry(entry)
}
fn append_entry(&mut self, entry: WitnessEntry) -> Result<WitnessEntry> {
let bytes = entry.to_bytes();
self.region.append(&bytes)?;
self.last_hash = entry.compute_hash();
self.sequence = self.sequence.wrapping_add(1);
Ok(entry)
}
#[inline]
#[must_use]
pub fn entry_count(&self) -> usize {
self.region.len() / WitnessEntry::SIZE
}
#[inline]
#[must_use]
pub const fn sequence(&self) -> u64 {
self.sequence
}
#[inline]
#[must_use]
pub const fn last_hash(&self) -> [u8; 32] {
self.last_hash
}
#[inline]
#[must_use]
pub fn handle(&self) -> RegionHandle {
self.region.handle()
}
#[inline]
#[must_use]
pub fn remaining_entries(&self) -> usize {
self.region.remaining() / WitnessEntry::SIZE
}
#[inline]
#[must_use]
pub fn is_full(&self) -> bool {
self.region.remaining() < WitnessEntry::SIZE
}
#[inline]
#[must_use]
pub fn fill_ratio(&self) -> f32 {
self.region.fill_ratio()
}
pub fn read_entry(&self, index: usize) -> Result<WitnessEntry> {
let offset = index
.checked_mul(WitnessEntry::SIZE)
.ok_or(KernelError::InvalidArgument)?;
let mut bytes = [0u8; WitnessEntry::SIZE];
let read = self.region.read(offset, &mut bytes)?;
if read < WitnessEntry::SIZE {
return Err(KernelError::BufferTooSmall);
}
WitnessEntry::from_bytes(&bytes).ok_or(KernelError::InternalError)
}
pub fn verify_chain(&self, start_index: usize) -> Result<bool> {
let count = self.entry_count();
if start_index >= count {
return Ok(true); }
let mut prev_hash = if start_index == 0 {
[0u8; 32]
} else {
let prev_entry = self.read_entry(start_index - 1)?;
prev_entry.compute_hash()
};
for i in start_index..count {
let entry = self.read_entry(i)?;
if entry.prev_hash != prev_hash {
return Ok(false);
}
prev_hash = entry.compute_hash();
}
Ok(true)
}
}
#[cfg(test)]
mod tests {
use super::*;
use ruvix_region::backing::StaticBacking;
fn create_test_attestation() -> ProofAttestation {
ProofAttestation::new([1u8; 32], [2u8; 32], 1000, 0x00_01_00_00, 1, 0)
}
#[test]
fn test_witness_entry_type() {
assert_eq!(
WitnessEntryType::from_u8(1),
Some(WitnessEntryType::VectorMutation)
);
assert_eq!(
WitnessEntryType::from_u8(2),
Some(WitnessEntryType::GraphMutation)
);
assert_eq!(WitnessEntryType::from_u8(100), None);
}
#[test]
fn test_witness_entry_serialization() {
let entry = WitnessEntry::new(
WitnessEntryType::VectorMutation,
42,
1,
create_test_attestation(),
[3u8; 32],
1000,
);
let bytes = entry.to_bytes();
let restored = WitnessEntry::from_bytes(&bytes).unwrap();
assert_eq!(restored.entry_type, entry.entry_type);
assert_eq!(restored.store_id, entry.store_id);
assert_eq!(restored.sequence, entry.sequence);
assert_eq!(restored.prev_hash, entry.prev_hash);
assert_eq!(restored.timestamp_ns, entry.timestamp_ns);
}
#[test]
fn test_witness_log_append() {
let backing = StaticBacking::<8192>::new();
let handle = RegionHandle::new(1, 0);
let mut log = WitnessLog::new(backing, 10, handle, 1).unwrap();
assert_eq!(log.entry_count(), 0);
let attestation = create_test_attestation();
let entry = log
.record_vector_mutation(VectorKey::new(1), attestation, 1000)
.unwrap();
assert_eq!(entry.entry_type, WitnessEntryType::VectorMutation);
assert_eq!(entry.sequence, 0);
assert_eq!(log.entry_count(), 1);
assert_eq!(log.sequence(), 1);
}
#[test]
fn test_witness_log_chain() {
let backing = StaticBacking::<8192>::new();
let handle = RegionHandle::new(1, 0);
let mut log = WitnessLog::new(backing, 10, handle, 1).unwrap();
let attestation = create_test_attestation();
let entry1 = log
.record_vector_mutation(VectorKey::new(1), attestation, 1000)
.unwrap();
assert_eq!(entry1.prev_hash, [0u8; 32]);
let entry2 = log
.record_vector_mutation(VectorKey::new(2), attestation, 2000)
.unwrap();
assert_eq!(entry2.prev_hash, entry1.compute_hash());
let entry3 = log
.record_vector_mutation(VectorKey::new(3), attestation, 3000)
.unwrap();
assert_eq!(entry3.prev_hash, entry2.compute_hash());
}
#[test]
fn test_witness_log_read_entry() {
let backing = StaticBacking::<8192>::new();
let handle = RegionHandle::new(1, 0);
let mut log = WitnessLog::new(backing, 10, handle, 1).unwrap();
let attestation = create_test_attestation();
log.record_vector_mutation(VectorKey::new(1), attestation, 1000)
.unwrap();
log.record_vector_mutation(VectorKey::new(2), attestation, 2000)
.unwrap();
let entry0 = log.read_entry(0).unwrap();
assert_eq!(entry0.sequence, 0);
let entry1 = log.read_entry(1).unwrap();
assert_eq!(entry1.sequence, 1);
}
#[test]
fn test_witness_log_verify_chain() {
let backing = StaticBacking::<8192>::new();
let handle = RegionHandle::new(1, 0);
let mut log = WitnessLog::new(backing, 10, handle, 1).unwrap();
let attestation = create_test_attestation();
for i in 0..5 {
log.record_vector_mutation(VectorKey::new(i), attestation, i as u64 * 1000)
.unwrap();
}
assert!(log.verify_chain(0).unwrap());
assert!(log.verify_chain(2).unwrap());
}
#[test]
fn test_witness_log_full() {
let backing = StaticBacking::<512>::new();
let handle = RegionHandle::new(1, 0);
let mut log = WitnessLog::new(backing, 2, handle, 1).unwrap();
let attestation = create_test_attestation();
log.record_vector_mutation(VectorKey::new(1), attestation, 1000)
.unwrap();
log.record_vector_mutation(VectorKey::new(2), attestation, 2000)
.unwrap();
assert!(log.is_full());
let result = log.record_vector_mutation(VectorKey::new(3), attestation, 3000);
assert_eq!(result, Err(KernelError::RegionFull));
}
}