use crate::types::{Receipt, OperationEvent};
use std::io::{self, Write};
use std::fmt::Debug;
pub struct TimeTravelDebugger<'a, S> {
receipt: &'a Receipt,
states: Vec<S>,
cursor: usize,
}
impl<'a, S: Debug + Clone> TimeTravelDebugger<'a, S> {
pub fn new<F>(receipt: &'a Receipt, initial_state: S, transition: F) -> Self
where
F: Fn(&S, &OperationEvent) -> S,
{
let mut states = Vec::with_capacity(receipt.events.len() + 1);
states.push(initial_state);
let mut current = states[0].clone();
for event in &receipt.events {
current = transition(¤t, event);
states.push(current.clone());
}
Self {
receipt,
states,
cursor: 0,
}
}
pub fn run_repl(&mut self) -> io::Result<()> {
println!("\n\x1b[1;36m1000X TIME-TRAVEL DEBUGGER\x1b[0m");
println!("============================");
println!("Receipt: {}", self.receipt.chain_hash.as_hex());
println!("Events: {}", self.receipt.events.len());
println!("Ticks: 0 to {} (0 is GENESIS)", self.states.len() - 1);
println!("Type \x1b[1;33m'help'\x1b[0m for commands.\n");
loop {
self.print_current_tick();
print!("\x1b[1;32m(tt-dbg @ {})\x1b[0m > ", self.cursor);
io::stdout().flush()?;
let mut input = String::new();
io::stdin().read_line(&mut input)?;
let parts: Vec<&str> = input.trim().split_whitespace().collect();
if parts.is_empty() {
continue;
}
match parts[0] {
"f" | "forward" | "n" | "next" => {
if self.cursor < self.states.len() - 1 {
self.cursor += 1;
} else {
println!("\x1b[1;31m[!] Already at the end of history.\x1b[0m");
}
}
"b" | "backward" | "p" | "prev" => {
if self.cursor > 0 {
self.cursor -= 1;
} else {
println!("\x1b[1;31m[!] Already at GENESIS.\x1b[0m");
}
}
"g" | "goto" => {
if let Some(tick_str) = parts.get(1) {
if let Ok(tick) = tick_str.parse::<usize>() {
if tick < self.states.len() {
self.cursor = tick;
} else {
println!("\x1b[1;31m[!] Tick {} is out of range (0-{}).\x1b[0m", tick, self.states.len() - 1);
}
} else {
println!("\x1b[1;31m[!] Invalid tick number: {}\x1b[0m", tick_str);
}
} else {
println!("\x1b[1;31m[!] Usage: goto <tick_number>\x1b[0m");
}
}
"m" | "memory" | "state" | "inspect" => {
self.inspect_memory();
}
"h" | "help" | "?" => {
self.print_help();
}
"q" | "quit" | "exit" => {
println!("Exiting Time-Travel Debugger.");
break;
}
_ => {
println!("\x1b[1;31m[!] Unknown command: '{}'. Type 'help'.\x1b[0m", parts[0]);
}
}
}
Ok(())
}
fn print_current_tick(&self) {
let is_genesis = self.cursor == 0;
println!("\x1b[1;34m--- TICK {} ---\x1b[0m", self.cursor);
if is_genesis {
println!("Event: \x1b[33m[GENESIS]\x1b[0m");
println!("Hash: {}", crate::chain::GENESIS_SEED.escape_ascii());
} else {
let event = &self.receipt.events[self.cursor - 1];
println!("Event: \x1b[1;33m{}\x1b[0m", event.event_type);
println!("Seq: {}", event.seq);
println!("ID: {}", event.id);
println!("Comm: {}", event.payload_commitment.as_hex());
}
println!();
}
fn inspect_memory(&self) {
println!("\x1b[1;35m--- MEMORY INSPECTION ---\x1b[0m");
println!("{:#?}", self.states[self.cursor]);
println!();
}
fn print_help(&self) {
println!("\x1b[1;33mAVAILABLE COMMANDS:\x1b[0m");
println!(" \x1b[1;32mf, forward, n, next\x1b[0m : Step forward to next event");
println!(" \x1b[1;32mb, backward, p, prev\x1b[0m : Step backward to previous state");
println!(" \x1b[1;32mg, goto <tick>\x1b[0m : Jump to a specific point in time");
println!(" \x1b[1;32mm, memory, inspect\x1b[0m : Dump current state memory");
println!(" \x1b[1;32mh, help\x1b[0m : Show this help");
println!(" \x1b[1;32mq, quit, exit\x1b[0m : Exit REPL");
println!();
}
}
#[macro_export]
macro_rules! time_travel_harness {
($receipt:expr, $initial_state:expr, $transition:expr) => {
{
let mut dbg = $crate::TimeTravelDebugger::new($receipt, $initial_state, $transition);
dbg.run_repl().expect("Time-Travel Debugger REPL failed");
}
};
}
#[cfg(test)]
mod tests {
use super::*;
use crate::ocel::{build_event, object_ref, SeqCounter};
use crate::chain::ChainAssembler;
#[derive(Debug, Clone, Default)]
struct MockSystemState {
counter: i32,
last_op: String,
objects_seen: Vec<String>,
}
#[test]
#[ignore] fn test_time_travel_debugger_manual() {
let mut asm = ChainAssembler::new();
let mut counter = SeqCounter::new();
let e1 = build_event("increment", vec![object_ref("c1", "counter")], b"1", &mut counter).unwrap();
let e2 = build_event("increment", vec![object_ref("c1", "counter")], b"5", &mut counter).unwrap();
let e3 = build_event("decrement", vec![object_ref("c1", "counter")], b"2", &mut counter).unwrap();
asm.append(e1).unwrap();
asm.append(e2).unwrap();
asm.append(e3).unwrap();
let receipt = asm.finalize();
let initial_state = MockSystemState::default();
let transition = |state: &MockSystemState, event: &OperationEvent| {
let mut next = state.clone();
next.last_op = event.event_type.clone();
for obj in &event.objects {
if !next.objects_seen.contains(&obj.id) {
next.objects_seen.push(obj.id.clone());
}
}
match event.event_type.as_str() {
"increment" => next.counter += 1, "decrement" => next.counter -= 1,
_ => {}
}
next
};
let mut dbg = TimeTravelDebugger::new(&receipt, initial_state, transition);
assert_eq!(dbg.states.len(), 4);
assert_eq!(dbg.states[0].counter, 0); assert_eq!(dbg.states[1].counter, 1); assert_eq!(dbg.states[2].counter, 2); assert_eq!(dbg.states[3].counter, 1); }
}
}
}