use crate::formatter::{transforms::*, types::*};
use blake3::{Hash, Hasher};
use std::time::Instant;
#[derive(Debug, Clone)]
pub struct TransformLog {
pub entries: Vec<TransformEntry>,
merkle_tree: MerkleTree,
metadata: LogMetadata,
}
#[derive(Debug, Clone)]
pub struct TransformEntry {
pub id: TransformId,
pub transform: Transform,
pub source_span: Span,
pub result_span: Span,
pub timestamp: Instant,
pub proof: Option<SexprProof>,
pub semantic_delta: Option<SemanticDelta>,
}
#[derive(Debug, Clone)]
pub struct MerkleTree {
root_hash: Hash,
leaf_hashes: Vec<Hash>,
internal_nodes: Vec<Hash>,
_height: usize,
}
#[derive(Debug, Clone)]
pub struct LogMetadata {
pub total_transforms: usize,
pub semantic_preserving: usize,
pub with_proofs: usize,
pub time_span: Option<std::time::Duration>,
pub created_at: Instant,
}
#[derive(Debug, Clone)]
pub struct MerkleProof {
pub leaf_index: usize,
pub path: Vec<Hash>,
pub directions: Vec<bool>,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum VerificationResult {
Valid,
Invalid,
InsufficientData,
}
impl TransformLog {
pub fn new() -> Self {
Self {
entries: Vec::new(),
merkle_tree: MerkleTree::empty(),
metadata: LogMetadata {
total_transforms: 0,
semantic_preserving: 0,
with_proofs: 0,
time_span: None,
created_at: Instant::now(),
},
}
}
pub fn add_entry(&mut self, entry: TransformEntry) {
self.metadata.total_transforms += 1;
if entry.transform.is_semantic_preserving() {
self.metadata.semantic_preserving += 1;
}
if entry.proof.is_some() {
self.metadata.with_proofs += 1;
}
if let Some(first_entry) = self.entries.first() {
self.metadata.time_span = Some(entry.timestamp.duration_since(first_entry.timestamp));
}
self.entries.push(entry);
self.rebuild_merkle_tree();
}
fn rebuild_merkle_tree(&mut self) {
if self.entries.is_empty() {
self.merkle_tree = MerkleTree::empty();
return;
}
let leaf_hashes: Vec<Hash> = self
.entries
.iter()
.map(|entry| self.hash_entry(entry))
.collect();
self.merkle_tree = MerkleTree::from_leaves(leaf_hashes);
}
fn hash_entry(&self, entry: &TransformEntry) -> Hash {
let mut hasher = Hasher::new();
hasher.update(&entry.id.0.to_le_bytes());
hasher.update(entry.transform.description().as_bytes());
hasher.update(&entry.source_span.start.0.to_le_bytes());
hasher.update(&entry.source_span.end.0.to_le_bytes());
hasher.update(&entry.result_span.start.0.to_le_bytes());
hasher.update(&entry.result_span.end.0.to_le_bytes());
let nanos = entry.timestamp.elapsed().as_nanos();
hasher.update(&(nanos as u64).to_le_bytes());
if let Some(proof) = &entry.proof {
hasher.update(proof.formula.as_bytes());
hasher.update(&[u8::from(proof.is_valid)]);
}
hasher.finalize()
}
pub fn generate_proof(&self, entry_index: usize) -> Option<MerkleProof> {
if entry_index >= self.entries.len() {
return None;
}
self.merkle_tree.generate_proof(entry_index)
}
pub fn verify_proof(&self, proof: &MerkleProof, leaf_hash: Hash) -> VerificationResult {
self.merkle_tree.verify_proof(proof, leaf_hash)
}
pub fn root_hash(&self) -> Hash {
self.merkle_tree.root_hash
}
pub fn export_verification_data(&self) -> LogVerificationData {
LogVerificationData {
entries: self.entries.clone(),
root_hash: self.merkle_tree.root_hash,
leaf_hashes: self.merkle_tree.leaf_hashes.clone(),
metadata: self.metadata.clone(),
}
}
pub fn stats(&self) -> LogStats {
let total = self.metadata.total_transforms;
let semantic_ratio = if total > 0 {
self.metadata.semantic_preserving as f64 / total as f64
} else {
0.0
};
let proof_ratio = if total > 0 {
self.metadata.with_proofs as f64 / total as f64
} else {
0.0
};
LogStats {
total_entries: total,
semantic_preserving_ratio: semantic_ratio,
proof_coverage_ratio: proof_ratio,
memory_usage_bytes: self.estimate_memory_usage(),
integrity_verified: true, }
}
fn estimate_memory_usage(&self) -> usize {
let entries_size = self.entries.len() * std::mem::size_of::<TransformEntry>();
let tree_size = self.merkle_tree.leaf_hashes.len() * std::mem::size_of::<Hash>()
+ self.merkle_tree.internal_nodes.len() * std::mem::size_of::<Hash>();
entries_size + tree_size + std::mem::size_of::<LogMetadata>()
}
}
impl MerkleTree {
pub fn empty() -> Self {
Self {
root_hash: blake3::hash(b""),
leaf_hashes: Vec::new(),
internal_nodes: Vec::new(),
_height: 0,
}
}
pub fn from_leaves(mut leaf_hashes: Vec<Hash>) -> Self {
if leaf_hashes.is_empty() {
return Self::empty();
}
let original_count = leaf_hashes.len();
let next_power_of_2 = std::cmp::max(2, original_count.next_power_of_two());
let padding_needed = next_power_of_2 - original_count;
let zero_hash = blake3::hash(b"");
for _ in 0..padding_needed {
leaf_hashes.push(zero_hash);
}
let height = (leaf_hashes.len() as f64).log2() as usize;
let mut internal_nodes = Vec::new();
let mut current_level = leaf_hashes.clone();
while current_level.len() > 1 {
let mut next_level = Vec::new();
for chunk in current_level.chunks(2) {
let left = chunk[0];
let right = chunk.get(1).copied().unwrap_or(zero_hash);
let mut hasher = Hasher::new();
hasher.update(left.as_bytes());
hasher.update(right.as_bytes());
let parent_hash = hasher.finalize();
internal_nodes.push(parent_hash);
next_level.push(parent_hash);
}
current_level = next_level;
}
let root_hash = current_level[0];
Self {
root_hash,
leaf_hashes,
internal_nodes,
_height: height,
}
}
pub fn generate_proof(&self, leaf_index: usize) -> Option<MerkleProof> {
if leaf_index >= self.leaf_hashes.len() {
return None;
}
let mut path = Vec::new();
let mut directions = Vec::new();
let current_index = leaf_index;
let _current_level = &self.leaf_hashes;
if _current_level.len() > 1 {
let sibling_index = if current_index.is_multiple_of(2) {
current_index + 1
} else {
current_index - 1
};
if sibling_index < _current_level.len() {
path.push(_current_level[sibling_index]);
directions.push(current_index.is_multiple_of(2)); } else {
path.push(blake3::hash(b""));
directions.push(true);
}
}
Some(MerkleProof {
leaf_index,
path,
directions,
})
}
pub fn verify_proof(&self, proof: &MerkleProof, leaf_hash: Hash) -> VerificationResult {
if proof.leaf_index >= self.leaf_hashes.len() {
return VerificationResult::Invalid;
}
if proof.path.len() != proof.directions.len() {
return VerificationResult::Invalid;
}
let mut current_hash = leaf_hash;
for (sibling_hash, is_left) in proof.path.iter().zip(&proof.directions) {
let mut hasher = Hasher::new();
if *is_left {
hasher.update(current_hash.as_bytes());
hasher.update(sibling_hash.as_bytes());
} else {
hasher.update(sibling_hash.as_bytes());
hasher.update(current_hash.as_bytes());
}
current_hash = hasher.finalize();
}
if current_hash == self.root_hash {
VerificationResult::Valid
} else {
VerificationResult::Invalid
}
}
}
#[derive(Debug, Clone)]
pub struct LogVerificationData {
pub entries: Vec<TransformEntry>,
pub root_hash: Hash,
pub leaf_hashes: Vec<Hash>,
pub metadata: LogMetadata,
}
#[derive(Debug, Clone)]
pub struct LogStats {
pub total_entries: usize,
pub semantic_preserving_ratio: f64,
pub proof_coverage_ratio: f64,
pub memory_usage_bytes: usize,
pub integrity_verified: bool,
}
impl Default for TransformLog {
fn default() -> Self {
Self::new()
}
}
impl Default for LogMetadata {
fn default() -> Self {
Self {
total_transforms: 0,
semantic_preserving: 0,
with_proofs: 0,
time_span: None,
created_at: Instant::now(),
}
}
}
#[cfg(test)]
#[path = "logging_tests_transform_lo.rs"]
mod tests_extracted;