1use std::collections::{HashMap, HashSet};
44use std::sync::atomic::{AtomicU64, Ordering};
45use std::time::{SystemTime, UNIX_EPOCH};
46
47use serde::{Deserialize, Serialize};
48use thiserror::Error;
49
50#[derive(Debug, Error)]
54pub enum ProofSerError {
55 #[error("missing node: {0}")]
57 MissingNode(String),
58
59 #[error("cycle detected in proof tree")]
61 CycleDetected,
62
63 #[error("JSON error: {0}")]
65 JsonError(String),
66
67 #[error("proof tree is empty")]
69 EmptyTree,
70}
71
72#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
76pub struct ProofNodeInput {
77 pub rule_id: Option<String>,
79
80 pub bindings: HashMap<String, String>,
82
83 pub children: Vec<String>,
85
86 pub peer_id: Option<String>,
88}
89
90#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
92pub struct ProofTreeInput {
93 pub root_goal: String,
95
96 pub nodes: HashMap<String, ProofNodeInput>,
98
99 pub proved: bool,
101}
102
103#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
107pub struct ProofNodeRecord {
108 pub node_id: String,
110
111 pub rule_id: Option<String>,
113
114 pub bindings: HashMap<String, String>,
116
117 pub children_ids: Vec<String>,
119
120 pub is_leaf: bool,
122
123 pub peer_id: Option<String>,
125}
126
127#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
129pub struct SerializedProof {
130 pub proof_id: String,
132
133 pub goal: String,
135
136 pub nodes: Vec<ProofNodeRecord>,
138
139 pub edge_count: usize,
141
142 pub depth: u32,
144
145 pub proved: bool,
147
148 pub total_bindings: usize,
150
151 pub serialized_at_ms: u64,
153}
154
155#[derive(Debug, Clone, PartialEq, Eq)]
159pub struct ProofSerializerStatsSnapshot {
160 pub total_serialized: u64,
161 pub total_deserialized: u64,
162 pub total_json_encoded: u64,
163 pub total_json_decoded: u64,
164}
165
166#[derive(Debug, Default)]
168pub struct ProofSerializerStats {
169 total_serialized: AtomicU64,
170 total_deserialized: AtomicU64,
171 total_json_encoded: AtomicU64,
172 total_json_decoded: AtomicU64,
173}
174
175impl ProofSerializerStats {
176 pub fn snapshot(&self) -> ProofSerializerStatsSnapshot {
178 ProofSerializerStatsSnapshot {
179 total_serialized: self.total_serialized.load(Ordering::Relaxed),
180 total_deserialized: self.total_deserialized.load(Ordering::Relaxed),
181 total_json_encoded: self.total_json_encoded.load(Ordering::Relaxed),
182 total_json_decoded: self.total_json_decoded.load(Ordering::Relaxed),
183 }
184 }
185}
186
187#[derive(Debug, Default)]
194pub struct ProofSerializer {
195 pub stats: ProofSerializerStats,
197}
198
199impl ProofSerializer {
200 pub fn new() -> Self {
202 Self::default()
203 }
204
205 pub fn serialize(&self, input: &ProofTreeInput) -> Result<SerializedProof, ProofSerError> {
215 if input.nodes.is_empty() {
216 return Err(ProofSerError::EmptyTree);
217 }
218
219 let order = self.dfs_order(input)?;
221
222 let depth = order.iter().map(|(_, d)| *d).max().unwrap_or(0);
224 let edge_count: usize = order
225 .iter()
226 .map(|(id, _)| input.nodes.get(id).map(|n| n.children.len()).unwrap_or(0))
227 .sum();
228 let total_bindings: usize = order
229 .iter()
230 .map(|(id, _)| input.nodes.get(id).map(|n| n.bindings.len()).unwrap_or(0))
231 .sum();
232
233 let nodes: Vec<ProofNodeRecord> = order
235 .iter()
236 .map(|(id, _)| {
237 let n = &input.nodes[id];
238 ProofNodeRecord {
239 node_id: id.clone(),
240 rule_id: n.rule_id.clone(),
241 bindings: n.bindings.clone(),
242 children_ids: n.children.clone(),
243 is_leaf: n.children.is_empty(),
244 peer_id: n.peer_id.clone(),
245 }
246 })
247 .collect();
248
249 let mut sorted_ids: Vec<&str> = order.iter().map(|(id, _)| id.as_str()).collect();
251 sorted_ids.sort_unstable();
252 let proof_id = fnv1a_hex(sorted_ids.iter().copied());
253
254 let serialized_at_ms = current_ms();
255
256 self.stats.total_serialized.fetch_add(1, Ordering::Relaxed);
257
258 Ok(SerializedProof {
259 proof_id,
260 goal: input.root_goal.clone(),
261 nodes,
262 edge_count,
263 depth,
264 proved: input.proved,
265 total_bindings,
266 serialized_at_ms,
267 })
268 }
269
270 pub fn deserialize(&self, proof: &SerializedProof) -> Result<ProofTreeInput, ProofSerError> {
277 if proof.nodes.is_empty() {
278 return Err(ProofSerError::EmptyTree);
279 }
280
281 let mut nodes: HashMap<String, ProofNodeInput> = HashMap::with_capacity(proof.nodes.len());
283 for record in &proof.nodes {
284 nodes.insert(
285 record.node_id.clone(),
286 ProofNodeInput {
287 rule_id: record.rule_id.clone(),
288 bindings: record.bindings.clone(),
289 children: record.children_ids.clone(),
290 peer_id: record.peer_id.clone(),
291 },
292 );
293 }
294
295 for record in &proof.nodes {
297 for child_id in &record.children_ids {
298 if !nodes.contains_key(child_id) {
299 return Err(ProofSerError::MissingNode(child_id.clone()));
300 }
301 }
302 }
303
304 self.stats
305 .total_deserialized
306 .fetch_add(1, Ordering::Relaxed);
307
308 Ok(ProofTreeInput {
309 root_goal: proof.goal.clone(),
310 nodes,
311 proved: proof.proved,
312 })
313 }
314
315 pub fn to_json(&self, proof: &SerializedProof) -> Result<String, ProofSerError> {
317 let json =
318 serde_json::to_string(proof).map_err(|e| ProofSerError::JsonError(e.to_string()))?;
319 self.stats
320 .total_json_encoded
321 .fetch_add(1, Ordering::Relaxed);
322 Ok(json)
323 }
324
325 pub fn from_json(&self, json: &str) -> Result<SerializedProof, ProofSerError> {
327 let proof: SerializedProof =
328 serde_json::from_str(json).map_err(|e| ProofSerError::JsonError(e.to_string()))?;
329 self.stats
330 .total_json_decoded
331 .fetch_add(1, Ordering::Relaxed);
332 Ok(proof)
333 }
334
335 fn dfs_order(&self, input: &ProofTreeInput) -> Result<Vec<(String, u32)>, ProofSerError> {
341 let root = &input.root_goal;
342 if !input.nodes.contains_key(root) {
343 return Err(ProofSerError::MissingNode(root.clone()));
344 }
345
346 let mut order: Vec<(String, u32)> = Vec::with_capacity(input.nodes.len());
347 let mut ancestors: HashSet<String> = HashSet::new();
349
350 Self::dfs_visit(input, root, 0, &mut order, &mut ancestors)?;
351 Ok(order)
352 }
353
354 fn dfs_visit(
355 input: &ProofTreeInput,
356 node_id: &str,
357 depth: u32,
358 order: &mut Vec<(String, u32)>,
359 ancestors: &mut HashSet<String>,
360 ) -> Result<(), ProofSerError> {
361 if ancestors.contains(node_id) {
362 return Err(ProofSerError::CycleDetected);
363 }
364
365 let node = input
366 .nodes
367 .get(node_id)
368 .ok_or_else(|| ProofSerError::MissingNode(node_id.to_string()))?;
369
370 order.push((node_id.to_string(), depth));
371 ancestors.insert(node_id.to_string());
372
373 for child_id in &node.children {
374 Self::dfs_visit(input, child_id, depth + 1, order, ancestors)?;
375 }
376
377 ancestors.remove(node_id);
378 Ok(())
379 }
380}
381
382fn fnv1a_hex<'a>(parts: impl Iterator<Item = &'a str>) -> String {
387 const OFFSET_BASIS: u64 = 14_695_981_039_346_656_037;
388 const PRIME: u64 = 1_099_511_628_211;
389
390 let mut hash: u64 = OFFSET_BASIS;
391 for part in parts {
392 for byte in part.bytes() {
393 hash ^= u64::from(byte);
394 hash = hash.wrapping_mul(PRIME);
395 }
396 }
397 format!("{:016x}", hash)
398}
399
400fn current_ms() -> u64 {
402 SystemTime::now()
403 .duration_since(UNIX_EPOCH)
404 .map(|d| d.as_millis() as u64)
405 .unwrap_or(0)
406}
407
408#[cfg(test)]
411mod tests {
412 use super::*;
413
414 fn single_node_input() -> ProofTreeInput {
417 let mut nodes = HashMap::new();
418 nodes.insert(
419 "n0".to_string(),
420 ProofNodeInput {
421 rule_id: None,
422 bindings: HashMap::new(),
423 children: vec![],
424 peer_id: None,
425 },
426 );
427 ProofTreeInput {
428 root_goal: "n0".to_string(),
429 nodes,
430 proved: true,
431 }
432 }
433
434 fn multi_level_input() -> ProofTreeInput {
443 let mut nodes = HashMap::new();
444 nodes.insert(
445 "n0".to_string(),
446 ProofNodeInput {
447 rule_id: Some("rule_A".to_string()),
448 bindings: [("X".to_string(), "alice".to_string())].into(),
449 children: vec!["n1".to_string(), "n2".to_string()],
450 peer_id: None,
451 },
452 );
453 nodes.insert(
454 "n1".to_string(),
455 ProofNodeInput {
456 rule_id: Some("rule_B".to_string()),
457 bindings: [("Y".to_string(), "bob".to_string())].into(),
458 children: vec!["n3".to_string(), "n4".to_string()],
459 peer_id: Some("peer-1".to_string()),
460 },
461 );
462 nodes.insert(
463 "n2".to_string(),
464 ProofNodeInput {
465 rule_id: None,
466 bindings: HashMap::new(),
467 children: vec![],
468 peer_id: None,
469 },
470 );
471 nodes.insert(
472 "n3".to_string(),
473 ProofNodeInput {
474 rule_id: None,
475 bindings: [("Z".to_string(), "carol".to_string())].into(),
476 children: vec![],
477 peer_id: Some("peer-2".to_string()),
478 },
479 );
480 nodes.insert(
481 "n4".to_string(),
482 ProofNodeInput {
483 rule_id: None,
484 bindings: HashMap::new(),
485 children: vec![],
486 peer_id: None,
487 },
488 );
489 ProofTreeInput {
490 root_goal: "n0".to_string(),
491 nodes,
492 proved: true,
493 }
494 }
495
496 #[test]
499 fn test_serialize_single_node() {
500 let ser = ProofSerializer::new();
501 let input = single_node_input();
502 let proof = ser.serialize(&input).expect("serialize should succeed");
503
504 assert_eq!(proof.nodes.len(), 1);
505 assert_eq!(proof.nodes[0].node_id, "n0");
506 assert!(proof.nodes[0].is_leaf);
507 assert_eq!(proof.edge_count, 0);
508 assert_eq!(proof.depth, 0);
509 assert!(proof.proved);
510 assert_eq!(proof.total_bindings, 0);
511 assert!(!proof.proof_id.is_empty());
512 }
513
514 #[test]
515 fn test_serialize_multi_level() {
516 let ser = ProofSerializer::new();
517 let input = multi_level_input();
518 let proof = ser.serialize(&input).expect("serialize multi-level");
519
520 assert_eq!(proof.nodes.len(), 5);
522
523 assert_eq!(proof.depth, 2);
525
526 assert_eq!(proof.edge_count, 4);
528
529 assert_eq!(proof.nodes[0].node_id, "n0");
531 assert_eq!(proof.nodes[1].node_id, "n1");
532 assert_eq!(proof.nodes[2].node_id, "n3");
533 assert_eq!(proof.nodes[3].node_id, "n4");
534 assert_eq!(proof.nodes[4].node_id, "n2");
535 }
536
537 #[test]
538 fn test_proof_id_is_deterministic() {
539 let ser = ProofSerializer::new();
540 let input = multi_level_input();
541
542 let proof1 = ser.serialize(&input).expect("first serialize");
543 let proof2 = ser.serialize(&input).expect("second serialize");
544
545 assert_eq!(proof1.proof_id, proof2.proof_id);
547 }
548
549 #[test]
550 fn test_proof_id_differs_for_different_trees() {
551 let ser = ProofSerializer::new();
552 let input1 = single_node_input();
553 let input2 = multi_level_input();
554
555 let p1 = ser.serialize(&input1).expect("test: should succeed");
556 let p2 = ser.serialize(&input2).expect("test: should succeed");
557
558 assert_ne!(p1.proof_id, p2.proof_id);
559 }
560
561 #[test]
562 fn test_deserialize_round_trip() {
563 let ser = ProofSerializer::new();
564 let input = multi_level_input();
565
566 let proof = ser.serialize(&input).expect("serialize");
567 let reconstructed = ser.deserialize(&proof).expect("deserialize");
568
569 assert_eq!(reconstructed.root_goal, input.root_goal);
571 assert_eq!(reconstructed.proved, input.proved);
572 assert_eq!(reconstructed.nodes.len(), input.nodes.len());
573
574 for (id, orig_node) in &input.nodes {
575 let rec_node = reconstructed.nodes.get(id).expect("node should exist");
576 assert_eq!(rec_node.rule_id, orig_node.rule_id);
577 assert_eq!(rec_node.bindings, orig_node.bindings);
578 assert_eq!(rec_node.children, orig_node.children);
579 assert_eq!(rec_node.peer_id, orig_node.peer_id);
580 }
581 }
582
583 #[test]
584 fn test_json_round_trip() {
585 let ser = ProofSerializer::new();
586 let input = multi_level_input();
587 let proof = ser.serialize(&input).expect("serialize");
588
589 let json = ser.to_json(&proof).expect("to_json");
590 let decoded = ser.from_json(&json).expect("from_json");
591
592 assert_eq!(proof, decoded);
593 }
594
595 #[test]
596 fn test_missing_root_node_returns_error() {
597 let ser = ProofSerializer::new();
598 let mut input = single_node_input();
599 input.root_goal = "nonexistent".to_string();
600
601 let result = ser.serialize(&input);
602 assert!(matches!(result, Err(ProofSerError::MissingNode(_))));
603 }
604
605 #[test]
606 fn test_empty_tree_returns_error() {
607 let ser = ProofSerializer::new();
608 let input = ProofTreeInput {
609 root_goal: "n0".to_string(),
610 nodes: HashMap::new(),
611 proved: false,
612 };
613 let result = ser.serialize(&input);
614 assert!(matches!(result, Err(ProofSerError::EmptyTree)));
615 }
616
617 #[test]
618 fn test_proved_flag_preserved_false() {
619 let ser = ProofSerializer::new();
620 let mut input = single_node_input();
621 input.proved = false;
622
623 let proof = ser.serialize(&input).expect("serialize");
624 assert!(!proof.proved);
625 let reconstructed = ser.deserialize(&proof).expect("deserialize");
626 assert!(!reconstructed.proved);
627 }
628
629 #[test]
630 fn test_depth_computed_correctly() {
631 let ser = ProofSerializer::new();
632 let mut nodes = HashMap::new();
634 for i in 0..4_usize {
635 nodes.insert(
636 format!("n{}", i),
637 ProofNodeInput {
638 rule_id: None,
639 bindings: HashMap::new(),
640 children: if i < 3 {
641 vec![format!("n{}", i + 1)]
642 } else {
643 vec![]
644 },
645 peer_id: None,
646 },
647 );
648 }
649 let input = ProofTreeInput {
650 root_goal: "n0".to_string(),
651 nodes,
652 proved: true,
653 };
654 let proof = ser.serialize(&input).expect("serialize chain");
655 assert_eq!(proof.depth, 3);
656 assert_eq!(proof.edge_count, 3);
657 }
658
659 #[test]
660 fn test_edge_count_correct() {
661 let ser = ProofSerializer::new();
662 let input = multi_level_input();
663 let proof = ser.serialize(&input).expect("serialize");
664 assert_eq!(proof.edge_count, 4);
666 }
667
668 #[test]
669 fn test_total_bindings_correct() {
670 let ser = ProofSerializer::new();
671 let input = multi_level_input();
672 let proof = ser.serialize(&input).expect("serialize");
673 assert_eq!(proof.total_bindings, 3);
675 }
676
677 #[test]
678 fn test_cycle_detection() {
679 let ser = ProofSerializer::new();
680 let mut nodes = HashMap::new();
682 nodes.insert(
683 "n0".to_string(),
684 ProofNodeInput {
685 rule_id: None,
686 bindings: HashMap::new(),
687 children: vec!["n1".to_string()],
688 peer_id: None,
689 },
690 );
691 nodes.insert(
692 "n1".to_string(),
693 ProofNodeInput {
694 rule_id: None,
695 bindings: HashMap::new(),
696 children: vec!["n0".to_string()],
697 peer_id: None,
698 },
699 );
700 let input = ProofTreeInput {
701 root_goal: "n0".to_string(),
702 nodes,
703 proved: false,
704 };
705 let result = ser.serialize(&input);
706 assert!(matches!(result, Err(ProofSerError::CycleDetected)));
707 }
708
709 #[test]
710 fn test_stats_accumulation() {
711 let ser = ProofSerializer::new();
712 let input = single_node_input();
713
714 let snap0 = ser.stats.snapshot();
716 assert_eq!(snap0.total_serialized, 0);
717 assert_eq!(snap0.total_deserialized, 0);
718 assert_eq!(snap0.total_json_encoded, 0);
719 assert_eq!(snap0.total_json_decoded, 0);
720
721 let proof = ser.serialize(&input).expect("test: should succeed");
722 ser.serialize(&input).expect("test: should succeed");
723 let snap1 = ser.stats.snapshot();
724 assert_eq!(snap1.total_serialized, 2);
725
726 ser.deserialize(&proof).expect("test: should succeed");
727 let snap2 = ser.stats.snapshot();
728 assert_eq!(snap2.total_deserialized, 1);
729
730 let json = ser.to_json(&proof).expect("test: should succeed");
731 let snap3 = ser.stats.snapshot();
732 assert_eq!(snap3.total_json_encoded, 1);
733
734 ser.from_json(&json).expect("test: should succeed");
735 let snap4 = ser.stats.snapshot();
736 assert_eq!(snap4.total_json_decoded, 1);
737 }
738
739 #[test]
740 fn test_json_invalid_input_returns_error() {
741 let ser = ProofSerializer::new();
742 let result = ser.from_json("{ not valid json }");
743 assert!(matches!(result, Err(ProofSerError::JsonError(_))));
744 }
745
746 #[test]
747 fn test_is_leaf_flag_correct() {
748 let ser = ProofSerializer::new();
749 let input = multi_level_input();
750 let proof = ser.serialize(&input).expect("serialize");
751
752 for record in &proof.nodes {
753 let expected_leaf = record.children_ids.is_empty();
754 assert_eq!(
755 record.is_leaf, expected_leaf,
756 "is_leaf mismatch for node {}",
757 record.node_id
758 );
759 }
760 }
761
762 #[test]
763 fn test_peer_id_preserved() {
764 let ser = ProofSerializer::new();
765 let input = multi_level_input();
766 let proof = ser.serialize(&input).expect("serialize");
767
768 let n1 = proof
770 .nodes
771 .iter()
772 .find(|r| r.node_id == "n1")
773 .expect("n1 must be present");
774 assert_eq!(n1.peer_id.as_deref(), Some("peer-1"));
775 }
776}