use crate::utils::error::Result;
use chrono::{DateTime, Utc};
use serde::{Deserialize, Serialize};
use std::collections::HashMap;
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
pub struct VectorClock {
pub clock: HashMap<String, u64>,
}
impl VectorClock {
pub fn new(node_id: String) -> Self {
let mut clock = HashMap::new();
clock.insert(node_id, 0);
VectorClock { clock }
}
pub fn tick(&mut self, node_id: &str) {
let entry = self.clock.entry(node_id.to_string()).or_insert(0);
*entry += 1;
}
pub fn merge(&mut self, other: &VectorClock) {
for (node_id, time) in &other.clock {
let entry = self.clock.entry(node_id.clone()).or_insert(0);
*entry = (*entry).max(*time);
}
}
pub fn happens_before(&self, other: &VectorClock) -> bool {
let mut less_somewhere = false;
for (node_id, time) in &self.clock {
let other_time = other.clock.get(node_id).copied().unwrap_or(0);
if *time > other_time {
return false; }
if *time < other_time {
less_somewhere = true;
}
}
less_somewhere
}
pub fn to_json_string(&self) -> String {
serde_json::to_string(&self.clock).unwrap_or_else(|_| "{}".to_string())
}
}
#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)]
pub enum ExecutionStatus {
Success,
Refused,
Error,
}
impl std::fmt::Display for ExecutionStatus {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
ExecutionStatus::Success => write!(f, "Success"),
ExecutionStatus::Refused => write!(f, "Refused"),
ExecutionStatus::Error => write!(f, "Error"),
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ExecutionPacket {
pub operation_id: String,
pub part_id: String,
pub input_hash: [u8; 32],
pub output_hash: [u8; 32],
pub vector_clock: VectorClock,
pub signature: Option<String>, pub status: ExecutionStatus,
pub timestamp: DateTime<Utc>,
pub duration_ms: u64,
}
impl ExecutionPacket {
pub fn new(
operation_id: String, part_id: String, input_hash: [u8; 32], output_hash: [u8; 32],
vector_clock: VectorClock, status: ExecutionStatus, duration_ms: u64,
) -> Self {
ExecutionPacket {
operation_id,
part_id,
input_hash,
output_hash,
vector_clock,
signature: None,
status,
timestamp: Utc::now(),
duration_ms,
}
}
pub fn output_size_bytes(&self) -> u64 {
1024 }
}
pub struct LocalExecutionContext {
pub part_id: String,
pub vector_clock: VectorClock,
pub event_log: Vec<serde_json::Value>, }
impl LocalExecutionContext {
pub fn new(part_id: String) -> Self {
LocalExecutionContext {
part_id: part_id.clone(),
vector_clock: VectorClock::new(part_id),
event_log: Vec::new(),
}
}
pub fn tick(&mut self) -> VectorClock {
self.vector_clock.tick(&self.part_id);
self.vector_clock.clone()
}
pub fn merge_clock(&mut self, external_clock: &VectorClock) {
self.vector_clock.merge(external_clock);
}
pub fn emit_event(&mut self, event: serde_json::Value) {
self.event_log.push(event);
}
pub fn log_execution(&mut self, packet: ExecutionPacket) {
self.event_log.push(serde_json::json!({
"operation_id": packet.operation_id,
"status": packet.status.to_string(),
}));
}
pub fn packets(&self) -> &[serde_json::Value] {
&self.event_log
}
}
#[async_trait::async_trait]
pub trait PartExecutor: Send + Sync {
async fn execute(&self, input: Vec<u8>) -> Result<(Vec<u8>, ExecutionPacket)>;
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct RefusalEvidence {
pub operation_id: String,
pub part_id: String,
pub refusal_code: String,
pub reason: String,
pub timestamp: DateTime<Utc>,
pub vector_clock: VectorClock,
}
impl RefusalEvidence {
pub fn new(
operation_id: String, part_id: String, refusal_code: String, reason: String,
vector_clock: VectorClock,
) -> Self {
RefusalEvidence {
operation_id,
part_id,
refusal_code,
reason,
timestamp: Utc::now(),
vector_clock,
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_vector_clock_new() {
let clock = VectorClock::new("part-1".to_string());
assert_eq!(clock.clock.get("part-1"), Some(&0));
}
#[test]
fn test_vector_clock_tick() {
let mut clock = VectorClock::new("part-1".to_string());
clock.tick("part-1");
assert_eq!(clock.clock.get("part-1"), Some(&1));
}
#[test]
fn test_execution_packet_creation() {
let clock = VectorClock::new("part-1".to_string());
let packet = ExecutionPacket::new(
"op-1".to_string(),
"part-1".to_string(),
[0u8; 32],
[1u8; 32],
clock,
ExecutionStatus::Success,
100,
);
assert_eq!(packet.operation_id, "op-1");
assert_eq!(packet.status, ExecutionStatus::Success);
}
}