use alloc::collections::VecDeque;
use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct ReplayWindow {
pub applied_event_root: [u8; 32],
pub retained_events: VecDeque<[u8; 32]>,
pub max_retained_events: usize,
}
impl ReplayWindow {
#[must_use]
pub fn new(max_retained_events: usize) -> Self {
Self {
applied_event_root: [0; 32], retained_events: VecDeque::with_capacity(max_retained_events),
max_retained_events,
}
}
pub fn apply_event(&mut self, event_hash: [u8; 32]) {
if self.retained_events.len() == self.max_retained_events {
if let Some(oldest) = self.retained_events.pop_front() {
self.compact_event(oldest);
}
}
self.retained_events.push_back(event_hash);
}
fn compact_event(&mut self, old_event: [u8; 32]) {
use sha3::{Digest, Sha3_256};
let mut hasher = Sha3_256::new();
hasher.update(self.applied_event_root);
hasher.update(old_event);
self.applied_event_root.copy_from_slice(&hasher.finalize());
}
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct ReplayState {
pub replay_epoch: u64,
pub reconstructed_root: [u8; 32],
pub window: ReplayWindow,
}
impl ReplayState {
pub fn apply_stream(
&mut self,
events: impl Iterator<Item = ([u8; 32], u64)>,
) -> Result<(), &'static str> {
use sha3::{Digest, Sha3_256};
for (event_hash, event_epoch) in events {
if event_epoch != self.replay_epoch {
return Err("Conflicting epoch detected during deterministic replay");
}
self.window.apply_event(event_hash);
let mut hasher = Sha3_256::new();
hasher.update(self.reconstructed_root);
hasher.update(event_hash);
self.reconstructed_root.copy_from_slice(&hasher.finalize());
}
Ok(())
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn replay_window_bounds_and_compacts() {
let mut window = ReplayWindow::new(3);
assert_eq!(window.retained_events.len(), 0);
assert_eq!(window.applied_event_root, [0; 32]);
window.apply_event([1; 32]);
window.apply_event([2; 32]);
window.apply_event([3; 32]);
assert_eq!(window.retained_events.len(), 3);
assert_eq!(window.applied_event_root, [0; 32]);
window.apply_event([4; 32]);
assert_eq!(window.retained_events.len(), 3);
assert_eq!(window.retained_events.front().unwrap(), &[2; 32]);
assert_ne!(window.applied_event_root, [0; 32]);
}
#[test]
fn deterministic_replay_stream() {
let mut state = ReplayState {
replay_epoch: 5,
reconstructed_root: [0; 32],
window: ReplayWindow::new(10),
};
let stream = vec![([10; 32], 5), ([20; 32], 5), ([30; 32], 5)];
assert!(state.apply_stream(stream.into_iter()).is_ok());
assert_eq!(state.window.retained_events.len(), 3);
assert_ne!(state.reconstructed_root, [0; 32]);
let bad_stream = vec![([40; 32], 6)];
assert!(state.apply_stream(bad_stream.into_iter()).is_err());
}
}