pub mod clock;
pub mod fault;
pub mod scheduler;
pub mod storage;
pub use clock::{ClockInjectionGuard, SimulatedClock};
pub use fault::{FaultConfig, FaultInjector, FaultType};
pub use scheduler::SimulatedScheduler;
pub use storage::{SimStorageError, SimulatedStorage};
use crate::core::temporal::{TIMESTAMP_MAX, TimeRange};
use crate::db::AletheiaDB;
#[derive(Debug, Default)]
pub struct InvariantReport {
pub passed: bool,
pub violations: Vec<String>,
pub nodes_checked: usize,
}
fn verify_temporal_invariants_impl(db: &AletheiaDB) -> InvariantReport {
use crate::core::temporal::time;
let mut report = InvariantReport {
passed: true,
violations: Vec::new(),
nodes_checked: 0,
};
{
let r = TimeRange::from(time::from_secs(0));
if !r.contains_range(&r) {
push_violation(
&mut report,
"TimeRange::contains_range reflexivity violated".to_owned(),
);
}
}
let node_ids = db.get_all_node_ids();
report.nodes_checked = node_ids.len();
for node_id in node_ids {
let history = match db.get_node_history(node_id) {
Ok(h) => h,
Err(e) => {
push_violation(
&mut report,
format!("node {node_id:?}: failed to retrieve history: {e}"),
);
continue;
}
};
let mut prev_tx_start = None;
let mut prev_version_number = None;
for version in &history.versions {
let vt = version.temporal.valid_time();
let tt = version.temporal.transaction_time();
if vt.start() > vt.end() {
push_violation(
&mut report,
format!(
"node {node_id:?} v{}: valid_time start {:?} > end {:?} (Inv 3)",
version.version_number,
vt.start(),
vt.end()
),
);
}
if tt.start() > tt.end() {
push_violation(
&mut report,
format!(
"node {node_id:?} v{}: tx_time start {:?} > end {:?} (Inv 3)",
version.version_number,
tt.start(),
tt.end()
),
);
}
if vt.is_closed() && vt.contains(vt.end()) {
push_violation(
&mut report,
format!(
"node {node_id:?} v{}: valid_time.end {:?} is inclusive (Inv 7)",
version.version_number,
vt.end()
),
);
}
if tt.is_closed() && tt.contains(tt.end()) {
push_violation(
&mut report,
format!(
"node {node_id:?} v{}: tx_time.end {:?} is inclusive (Inv 7)",
version.version_number,
tt.end()
),
);
}
if let Some(prev) = prev_tx_start
&& tt.start() < prev
{
push_violation(
&mut report,
format!(
"node {node_id:?} v{}: tx_time.start {:?} < previous {:?} (Inv 1)",
version.version_number,
tt.start(),
prev
),
);
}
prev_tx_start = Some(tt.start());
if let Some(prev_vn) = prev_version_number
&& version.version_number <= prev_vn
{
push_violation(
&mut report,
format!(
"node {node_id:?}: version_number {} not > previous {} (Inv 2)",
version.version_number, prev_vn
),
);
}
prev_version_number = Some(version.version_number);
let vt_mid = vt.start();
let tt_mid = tt.start();
let expected = vt.contains(vt_mid) && tt.contains(tt_mid);
let actual = version.temporal.is_visible_at(vt_mid, tt_mid);
if actual != expected {
push_violation(
&mut report,
format!(
"node {node_id:?} v{}: is_visible_at({vt_mid:?},{tt_mid:?}) = {actual} \
but expected {expected} (Inv 4)",
version.version_number
),
);
}
}
}
{
let now = time::now();
if now > TIMESTAMP_MAX {
push_violation(
&mut report,
format!(
"Current time {now:?} exceeds TIMESTAMP_MAX — clock injection out of bounds"
),
);
}
}
report
}
fn push_violation(report: &mut InvariantReport, msg: String) {
report.violations.push(msg);
report.passed = false;
}
#[derive(Debug)]
pub struct Simulator {
seed: u64,
clock: SimulatedClock,
faults: FaultInjector,
_clock_guard: ClockInjectionGuard,
}
impl Simulator {
pub fn new(seed: u64) -> Self {
let clock = SimulatedClock::new(0);
let guard = clock.inject();
Self {
seed,
clock,
faults: FaultInjector::new(FaultConfig::default(), seed),
_clock_guard: guard,
}
}
pub fn with_seed_and_faults(seed: u64, config: FaultConfig) -> Self {
let clock = SimulatedClock::new(0);
let guard = clock.inject();
Self {
seed,
clock,
faults: FaultInjector::new(config, seed),
_clock_guard: guard,
}
}
pub fn seed(&self) -> u64 {
self.seed
}
pub fn clock(&self) -> &SimulatedClock {
&self.clock
}
pub fn clock_mut(&mut self) -> &mut SimulatedClock {
&mut self.clock
}
pub fn faults(&self) -> &FaultInjector {
&self.faults
}
pub fn advance_time_by(&mut self, delta_micros: i64) {
self.clock.advance_by(delta_micros);
}
pub fn inject_clock_jump(&mut self, delta_micros: i64) {
self.clock.jump_by(delta_micros);
}
pub fn verify_temporal_invariants(&self, db: &AletheiaDB) -> InvariantReport {
verify_temporal_invariants_impl(db)
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::core::temporal::time;
use crate::test_utils::create_test_db;
#[test]
fn new_simulator_seed_accessible() {
let s = Simulator::new(99);
assert_eq!(s.seed(), 99);
}
#[test]
fn simulator_auto_injects_clock() {
let sim = Simulator::new(0);
assert_eq!(time::now().wallclock(), 0);
drop(sim);
}
#[test]
fn advance_time_by_updates_time_now() {
let mut sim = Simulator::new(0);
sim.advance_time_by(500_000);
assert_eq!(time::now().wallclock(), 500_000);
}
#[test]
fn fresh_db_passes_invariants() {
let s = Simulator::new(0);
let (_tmp, db) = create_test_db().unwrap();
let r = s.verify_temporal_invariants(&db);
assert!(r.passed, "{:?}", r.violations);
}
#[test]
fn invariants_check_real_node_versions() {
use crate::PropertyMapBuilder;
use crate::api::WriteOps;
let s = Simulator::new(1_000_000); let (_tmp, db) = create_test_db().unwrap();
let props = PropertyMapBuilder::new().build();
let node = db.create_node("Item", props).unwrap();
db.write(|tx: &mut crate::WriteTransaction| {
tx.update_node(node, PropertyMapBuilder::new().insert("v", 2_i64).build())
})
.unwrap();
let r = s.verify_temporal_invariants(&db);
assert_eq!(r.nodes_checked, 1);
assert!(r.passed, "{:?}", r.violations);
}
}