use crate::abi::CapabilityMask;
use crate::runtime::Kernel;
use super::wal::{Wal, WalError, WalHeader};
#[derive(Debug, Default, Clone, PartialEq, Eq)]
pub struct ReplayReport {
pub records_replayed: u32,
pub total_effects_applied: u32,
pub total_effects_denied: u32,
pub final_chain_tip: [u8; 32],
}
#[derive(Debug, Clone)]
#[non_exhaustive]
pub enum ReplayError {
HeaderIncompatible(String),
KernelSemverMismatch {
expected: (u16, u16, u16),
got: (u16, u16, u16),
},
AbiVersionMismatch {
expected: (u16, u16),
got: (u16, u16),
},
WalCorrupted(WalError),
SubmitFailed(String),
}
impl From<WalError> for ReplayError {
fn from(e: WalError) -> Self {
Self::WalCorrupted(e)
}
}
impl core::fmt::Display for ReplayError {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
match self {
Self::HeaderIncompatible(m) => write!(f, "wal header incompatible: {}", m),
Self::KernelSemverMismatch { expected, got } => {
write!(
f,
"kernel semver mismatch: expected {:?}, got {:?}",
expected, got
)
}
Self::AbiVersionMismatch { expected, got } => {
write!(
f,
"abi version mismatch: expected {:?}, got {:?}",
expected, got
)
}
Self::WalCorrupted(e) => write!(f, "wal corrupted: {}", e),
Self::SubmitFailed(m) => write!(f, "submit failed: {}", m),
}
}
}
impl std::error::Error for ReplayError {}
pub fn replay_into(kernel: &mut Kernel, wal: &Wal) -> Result<ReplayReport, ReplayError> {
if wal.header.magic != WalHeader::MAGIC {
return Err(ReplayError::HeaderIncompatible(
"magic mismatch (expected ARKHEWAL)".to_string(),
));
}
if wal.header.kernel_semver.0 != WalHeader::CURRENT_KERNEL_SEMVER.0 {
return Err(ReplayError::KernelSemverMismatch {
expected: WalHeader::CURRENT_KERNEL_SEMVER,
got: wal.header.kernel_semver,
});
}
if wal.header.abi_version != WalHeader::ABI_VERSION {
return Err(ReplayError::AbiVersionMismatch {
expected: WalHeader::ABI_VERSION,
got: wal.header.abi_version,
});
}
wal.verify_chain(wal.header.world_id)?;
let mut report = ReplayReport::default();
for rec in &wal.records {
let caps = CapabilityMask::from_bits_truncate(rec.caps_bits);
let principal = match &rec.principal {
crate::abi::Principal::Unauthenticated => crate::abi::Principal::Unauthenticated,
crate::abi::Principal::External(e) => crate::abi::Principal::External(*e),
crate::abi::Principal::System => crate::abi::Principal::System,
};
kernel
.submit(
rec.instance,
principal,
None,
rec.at,
rec.action_type_code,
rec.action_bytes.clone(),
)
.map_err(|e| ReplayError::SubmitFailed(format!("{:?}", e)))?;
let step_report = kernel.step(rec.at, caps);
report.records_replayed = report.records_replayed.saturating_add(1);
report.total_effects_applied = report
.total_effects_applied
.saturating_add(step_report.effects_applied);
report.total_effects_denied = report
.total_effects_denied
.saturating_add(step_report.effects_denied);
}
report.final_chain_tip = wal.chain_tip();
Ok(report)
}
#[cfg(test)]
mod tests {
use super::*;
use crate::abi::Tick;
use crate::persist::wal::{AuthDecisionAnnotation, Wal, WalWriter};
fn world() -> [u8; 32] {
[11u8; 32]
}
#[test]
fn replay_empty_wal_succeeds() {
let w = WalWriter::new(world(), [0u8; 32]);
let wal = Wal::from_writer(w);
let mut kernel = Kernel::new();
let report = replay_into(&mut kernel, &wal).unwrap();
assert_eq!(report.records_replayed, 0);
}
#[test]
fn replay_rejects_wrong_magic() {
let w = WalWriter::new(world(), [0u8; 32]);
let mut wal = Wal::from_writer(w);
wal.header.magic = *b"BADMAGIC";
let mut kernel = Kernel::new();
let result = replay_into(&mut kernel, &wal);
assert!(matches!(result, Err(ReplayError::HeaderIncompatible(_))));
}
#[test]
fn replay_rejects_kernel_semver_major_mismatch() {
let w = WalWriter::new(world(), [0u8; 32]);
let mut wal = Wal::from_writer(w);
wal.header.kernel_semver = (99, 0, 0);
let mut kernel = Kernel::new();
let result = replay_into(&mut kernel, &wal);
assert!(matches!(
result,
Err(ReplayError::KernelSemverMismatch { .. })
));
}
#[test]
fn replay_rejects_corrupted_chain() {
let mut w = WalWriter::new(world(), [0u8; 32]);
w.append(
Tick(0),
crate::abi::InstanceId::new(1).unwrap(),
crate::abi::Principal::System,
crate::abi::TypeCode(100),
vec![],
0,
crate::runtime::stage::StepStage::default(),
AuthDecisionAnnotation::AllAuthorized,
)
.unwrap();
let mut wal = Wal::from_writer(w);
wal.records[0].this_chain_hash = [0xFFu8; 32];
let mut kernel = Kernel::new();
let result = replay_into(&mut kernel, &wal);
assert!(matches!(result, Err(ReplayError::WalCorrupted(_))));
}
}