1use std::fmt;
26
27use evorule_tcb::JsonValue;
28use tracing::{debug, trace};
29
30use crate::fact::Fact;
31
32#[derive(Debug)]
36pub struct HashError {
37 message: String,
38}
39
40impl HashError {
41 fn new(message: impl Into<String>) -> Self {
42 Self {
43 message: message.into(),
44 }
45 }
46}
47
48impl fmt::Display for HashError {
49 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
50 write!(f, "哈希计算错误: {}", self.message)
51 }
52}
53
54impl std::error::Error for HashError {}
55
56fn tcb_to_serde(value: &JsonValue) -> serde_json::Value {
61 match value {
62 JsonValue::Null => serde_json::Value::Null,
63 JsonValue::Bool(b) => serde_json::Value::Bool(*b),
64 JsonValue::Integer(i) => serde_json::Value::Number(serde_json::Number::from(*i)),
65 JsonValue::String(s) => serde_json::Value::String(s.to_string()),
66 JsonValue::Array(arr) => serde_json::Value::Array(arr.iter().map(tcb_to_serde).collect()),
67 JsonValue::Object(obj) => {
68 let mut map = serde_json::Map::new();
69 for (k, v) in obj.iter() {
70 map.insert(k.clone(), tcb_to_serde(v));
71 }
72 serde_json::Value::Object(map)
73 }
74 }
75}
76
77#[allow(clippy::cognitive_complexity, clippy::too_many_lines)]
92pub fn fact_to_stable_json(fact: &Fact) -> Result<serde_json::Value, HashError> {
93 let fact_type = fact.type_name();
94 let fact_id = fact.id();
95
96 trace!(
97 事实ID = ?fact_id,
98 事实类型 = %fact_type,
99 "序列化开始"
100 );
101
102 let mut obj = serde_json::Map::new();
103 match fact {
104 Fact::Command { id, instruction } => {
105 trace!(
106 事实ID = ?id,
107 指令大小 = instruction.to_string().len(),
108 "处理命令类型事实"
109 );
110 obj.insert("type".into(), serde_json::Value::String("Command".into()));
111 obj.insert("id".into(), serde_json::Value::Number(id.0.into()));
112 obj.insert("instruction".into(), tcb_to_serde(instruction));
113 }
114 Fact::PayloadUpdate { id, path, value } => {
115 trace!(
116 事实ID = ?id,
117 路径 = %path,
118 "处理载荷更新类型事实"
119 );
120 obj.insert(
121 "type".into(),
122 serde_json::Value::String("PayloadUpdate".into()),
123 );
124 obj.insert("id".into(), serde_json::Value::Number(id.0.into()));
125 obj.insert("path".into(), serde_json::Value::String(path.clone()));
126 obj.insert("value".into(), tcb_to_serde(value));
127 }
128 Fact::StateTransition {
129 id,
130 cause,
131 new_payload,
132 new_queue,
133 } => {
134 trace!(
135 事实ID = ?id,
136 原因ID = ?cause,
137 队列长度 = new_queue.len(),
138 "处理状态转换类型事实"
139 );
140 obj.insert(
141 "type".into(),
142 serde_json::Value::String("StateTransition".into()),
143 );
144 obj.insert("id".into(), serde_json::Value::Number(id.0.into()));
145 obj.insert("cause".into(), serde_json::Value::Number(cause.0.into()));
146 obj.insert("new_payload".into(), tcb_to_serde(new_payload));
147 obj.insert(
148 "new_queue".into(),
149 serde_json::Value::Array(new_queue.iter().map(tcb_to_serde).collect()),
150 );
151 }
152 Fact::IoRequest {
153 id,
154 cause,
155 io_type,
156 params,
157 } => {
158 trace!(
159 事实ID = ?id,
160 原因ID = ?cause,
161 IO类型 = %io_type.as_str(),
162 "处理IO请求类型事实"
163 );
164 obj.insert("type".into(), serde_json::Value::String("IoRequest".into()));
165 obj.insert("id".into(), serde_json::Value::Number(id.0.into()));
166 obj.insert("cause".into(), serde_json::Value::Number(cause.0.into()));
167 obj.insert(
168 "io_type".into(),
169 serde_json::Value::String(io_type.as_str().into()),
170 );
171 obj.insert("params".into(), tcb_to_serde(params));
172 }
173 Fact::IoResponse {
174 id,
175 request_id,
176 result,
177 error,
178 } => {
179 trace!(
180 事实ID = ?id,
181 请求ID = ?request_id,
182 是否有错误 = error.is_some(),
183 "处理IO响应类型事实"
184 );
185 obj.insert(
186 "type".into(),
187 serde_json::Value::String("IoResponse".into()),
188 );
189 obj.insert("id".into(), serde_json::Value::Number(id.0.into()));
190 obj.insert(
191 "request_id".into(),
192 serde_json::Value::Number(request_id.0.into()),
193 );
194 obj.insert("result".into(), tcb_to_serde(result));
195 obj.insert(
196 "error".into(),
197 error
198 .as_ref()
199 .map(|e| serde_json::Value::String(e.clone()))
200 .unwrap_or(serde_json::Value::Null),
201 );
202 }
203 Fact::Stable { id, final_snapshot } => {
204 trace!(
205 事实ID = ?id,
206 快照大小 = final_snapshot.to_string().len(),
207 "处理稳定状态类型事实"
208 );
209 obj.insert("type".into(), serde_json::Value::String("Stable".into()));
210 obj.insert("id".into(), serde_json::Value::Number(id.0.into()));
211 obj.insert("final_snapshot".into(), tcb_to_serde(final_snapshot));
212 }
213 Fact::Error { id, message } => {
214 trace!(
215 事实ID = ?id,
216 消息 = %message,
217 "处理错误类型事实"
218 );
219 obj.insert("type".into(), serde_json::Value::String("Error".into()));
220 obj.insert("id".into(), serde_json::Value::Number(id.0.into()));
221 obj.insert("message".into(), serde_json::Value::String(message.clone()));
222 }
223 }
224
225 let value = serde_json::Value::Object(obj);
226
227 trace!(
228 事实ID = ?fact_id,
229 事实类型 = %fact_type,
230 "序列化完成"
231 );
232
233 Ok(value)
234}
235
236#[allow(dead_code)] pub fn content_hash(value: &JsonValue) -> Result<String, HashError> {
249 #[cfg(kani)]
250 {
251 Ok(String::from("c"))
255 }
256 #[cfg(not(kani))]
257 {
258 let serde_value = tcb_to_serde(value);
259 let serialized = serde_json::to_string(&serde_value)
260 .map_err(|e| HashError::new(format!("内容哈希序列化失败: {}", e)))?;
261 let hash = blake3::hash(serialized.as_bytes()).to_hex().to_string();
262
263 trace!(
264 序列化长度 = serialized.len(),
265 哈希值 = %hash,
266 "内容哈希计算完成"
267 );
268
269 Ok(hash)
270 }
271}
272
273pub fn fact_hash(fact: &Fact) -> Result<String, HashError> {
297 #[cfg(kani)]
298 {
299 let id = fact.id().0;
304 let h = match id % 7 {
305 0 => "a",
306 1 => "b",
307 2 => "c",
308 3 => "d",
309 4 => "e",
310 5 => "f",
311 _ => "g",
312 };
313 Ok(String::from(h))
314 }
315 #[cfg(not(kani))]
316 {
317 let fact_type = fact.type_name();
318 let fact_id = fact.id();
319
320 debug!(
321 事实ID = ?fact_id,
322 事实类型 = %fact_type,
323 "开始计算事实哈希"
324 );
325
326 let value = fact_to_stable_json(fact)?;
327 let serialized = serde_json::to_string(&value)
328 .map_err(|e| HashError::new(format!("序列化失败: {}", e)))?;
329
330 let hash = blake3::hash(serialized.as_bytes()).to_hex().to_string();
331
332 debug!(
333 事实ID = ?fact_id,
334 事实类型 = %fact_type,
335 哈希值 = %hash,
336 序列化长度 = serialized.len(),
337 "事实哈希计算完成"
338 );
339
340 Ok(hash)
341 }
342}
343
344pub fn compute_chain_hash(facts: &[Fact]) -> Result<String, HashError> {
367 let fact_count = facts.len();
368
369 debug!(事实数量 = fact_count, "开始计算链哈希");
370
371 if fact_count == 0 {
372 debug!("事实列表为空,返回 genesis 哈希");
373 return Ok(String::from("genesis"));
374 }
375
376 let mut prev_hash = String::from("genesis");
377
378 trace!(
379 初始前序哈希 = %prev_hash,
380 "初始化前序哈希为创世值"
381 );
382
383 for (index, fact) in facts.iter().enumerate() {
384 let fact_type = fact.type_name();
385 let fact_id = fact.id();
386
387 trace!(
388 索引 = index,
389 事实ID = ?fact_id,
390 事实类型 = %fact_type,
391 前序哈希 = %prev_hash,
392 "处理事实"
393 );
394
395 let fh = fact_hash(fact)?;
396
397 trace!(
398 索引 = index,
399 事实ID = ?fact_id,
400 事实哈希 = %fh,
401 "事实哈希计算完成"
402 );
403
404 let current = chain_step(&prev_hash, &fh);
405
406 trace!(
407 索引 = index,
408 事实ID = ?fact_id,
409 当前哈希 = %current,
410 "计算当前哈希(前序哈希+事实哈希)"
411 );
412
413 prev_hash = current;
414 }
415
416 debug!(
417 事实数量 = fact_count,
418 最终哈希 = %prev_hash,
419 "链哈希计算完成"
420 );
421
422 Ok(prev_hash)
423}
424
425pub fn chain_step(prev_hash: &str, content_hash: &str) -> String {
438 #[cfg(kani)]
439 {
440 let mut s = String::from(prev_hash);
444 s.push_str(content_hash);
445 s
446 }
447 #[cfg(not(kani))]
448 {
449 let combined = format!("{}{}", prev_hash, content_hash);
450 blake3::hash(combined.as_bytes()).to_hex().to_string()
451 }
452}
453
454#[cfg(test)]
455mod tests {
456 #![allow(clippy::unwrap_used, clippy::panic, clippy::expect_used)]
457 use super::*;
458 use crate::fact::{Fact, FactId, IoType};
459 use evorule_tcb::JsonValue;
460
461 #[test]
462 fn test_fact_to_stable_json_format() {
463 let command = Fact::Command {
464 id: FactId(1),
465 instruction: JsonValue::object_from_pairs(&[
466 ("type", JsonValue::string("increment")),
467 (
468 "params",
469 JsonValue::object_from_pairs(&[
470 ("attr", JsonValue::string("x")),
471 ("delta", JsonValue::Integer(5)),
472 ]),
473 ),
474 ]),
475 };
476
477 let json_value = fact_to_stable_json(&command).unwrap();
478
479 assert_eq!(json_value.get("type").unwrap().as_str().unwrap(), "Command");
480 assert_eq!(json_value.get("id").unwrap().as_u64().unwrap(), 1);
481 assert!(json_value.get("instruction").is_some());
482 }
483
484 #[test]
485 fn test_fact_hash_all_variants() {
486 let test_facts = vec![
487 Fact::Command {
488 id: FactId(1),
489 instruction: JsonValue::empty_object(),
490 },
491 Fact::PayloadUpdate {
492 id: FactId(2),
493 path: "test.path".into(),
494 value: JsonValue::string("test_value"),
495 },
496 Fact::StateTransition {
497 id: FactId(3),
498 cause: FactId(1),
499 new_payload: JsonValue::empty_object(),
500 new_queue: vec![],
501 },
502 Fact::IoRequest {
503 id: FactId(4),
504 cause: FactId(3),
505 io_type: IoType::http_get(),
506 params: JsonValue::empty_object(),
507 },
508 Fact::IoResponse {
509 id: FactId(5),
510 request_id: FactId(4),
511 result: JsonValue::string("response"),
512 error: None,
513 },
514 Fact::IoResponse {
515 id: FactId(6),
516 request_id: FactId(4),
517 result: JsonValue::Null,
518 error: Some("timeout".to_string()),
519 },
520 Fact::Stable {
521 id: FactId(7),
522 final_snapshot: JsonValue::empty_object(),
523 },
524 Fact::Error {
525 id: FactId(8),
526 message: "test error".into(),
527 },
528 ];
529
530 for fact in test_facts {
531 let hash = fact_hash(&fact).unwrap();
532 assert_eq!(hash.len(), 64);
533
534 let hash2 = fact_hash(&fact).unwrap();
535 assert_eq!(
536 hash,
537 hash2,
538 "fact_hash should be deterministic for {}",
539 fact.type_name()
540 );
541 }
542 }
543
544 #[test]
545 fn test_fact_hash_identity() {
546 let fact1 = Fact::Command {
547 id: FactId(1),
548 instruction: JsonValue::string("same"),
549 };
550 let fact2 = Fact::Command {
551 id: FactId(1),
552 instruction: JsonValue::string("same"),
553 };
554
555 assert_eq!(fact_hash(&fact1).unwrap(), fact_hash(&fact2).unwrap());
556 }
557
558 #[test]
559 fn test_fact_hash_different_ids() {
560 let fact1 = Fact::Command {
561 id: FactId(1),
562 instruction: JsonValue::string("same"),
563 };
564 let fact2 = Fact::Command {
565 id: FactId(2),
566 instruction: JsonValue::string("same"),
567 };
568
569 assert_ne!(fact_hash(&fact1).unwrap(), fact_hash(&fact2).unwrap());
570 }
571
572 #[test]
573 fn test_fact_hash_snapshot() {
574 let test_facts = [
575 Fact::Command {
576 id: FactId(1),
577 instruction: JsonValue::empty_object(),
578 },
579 Fact::PayloadUpdate {
580 id: FactId(2),
581 path: "test.path".into(),
582 value: JsonValue::string("test_value"),
583 },
584 Fact::StateTransition {
585 id: FactId(3),
586 cause: FactId(1),
587 new_payload: JsonValue::empty_object(),
588 new_queue: vec![],
589 },
590 Fact::IoRequest {
591 id: FactId(4),
592 cause: FactId(3),
593 io_type: IoType::http_get(),
594 params: JsonValue::empty_object(),
595 },
596 Fact::IoResponse {
597 id: FactId(5),
598 request_id: FactId(4),
599 result: JsonValue::string("response"),
600 error: None,
601 },
602 Fact::Stable {
603 id: FactId(6),
604 final_snapshot: JsonValue::empty_object(),
605 },
606 Fact::Error {
607 id: FactId(7),
608 message: "test error".into(),
609 },
610 ];
611
612 let current_hashes: Vec<String> =
613 test_facts.iter().map(|f| fact_hash(f).unwrap()).collect();
614 let snapshot_file = env!("CARGO_MANIFEST_DIR").to_string() + "/hash_snapshot.txt";
615
616 if std::path::Path::new(&snapshot_file).exists() {
617 let snapshot = std::fs::read_to_string(&snapshot_file).unwrap();
618 let expected_hashes: Vec<String> = snapshot.lines().map(|s| s.to_string()).collect();
619 assert_eq!(
620 current_hashes, expected_hashes,
621 "Hash snapshot mismatch! If this is an expected change (e.g., Fact struct modification), delete {} to regenerate.",
622 snapshot_file
623 );
624 } else {
625 let snapshot_content = current_hashes.join("\n");
626 std::fs::write(&snapshot_file, snapshot_content).unwrap();
627 println!("Created hash snapshot: {}", snapshot_file);
628 }
629 }
630
631 #[test]
632 fn test_compute_chain_hash_empty() {
633 let facts: Vec<Fact> = vec![];
634 let chain_hash = compute_chain_hash(&facts).unwrap();
635 assert_eq!(chain_hash, "genesis");
636 }
637
638 #[test]
639 fn test_compute_chain_hash_deterministic() {
640 let facts = vec![
641 Fact::Command {
642 id: FactId(1),
643 instruction: JsonValue::empty_object(),
644 },
645 Fact::StateTransition {
646 id: FactId(2),
647 cause: FactId(1),
648 new_payload: JsonValue::empty_object(),
649 new_queue: vec![],
650 },
651 ];
652
653 let result1 = compute_chain_hash(&facts).unwrap();
654 let result2 = compute_chain_hash(&facts).unwrap();
655 assert_eq!(result1, result2);
656 }
657
658 #[test]
659 fn test_compute_chain_hash_order_sensitive() {
660 let fact1 = Fact::Command {
661 id: FactId(1),
662 instruction: JsonValue::empty_object(),
663 };
664 let fact2 = Fact::StateTransition {
665 id: FactId(2),
666 cause: FactId(1),
667 new_payload: JsonValue::empty_object(),
668 new_queue: vec![],
669 };
670
671 let chain1 = compute_chain_hash(&[fact1.clone(), fact2.clone()]).unwrap();
672 let chain2 = compute_chain_hash(&[fact2, fact1]).unwrap();
673 assert_ne!(chain1, chain2, "链哈希应对 Fact 顺序敏感");
674 }
675
676 #[test]
681 fn test_cross_validate_with_tier2() {
682 let test_facts = [
684 Fact::Command {
685 id: FactId(1),
686 instruction: JsonValue::empty_object(),
687 },
688 Fact::PayloadUpdate {
689 id: FactId(2),
690 path: "test.path".into(),
691 value: JsonValue::string("test_value"),
692 },
693 Fact::StateTransition {
694 id: FactId(3),
695 cause: FactId(1),
696 new_payload: JsonValue::empty_object(),
697 new_queue: vec![],
698 },
699 Fact::IoRequest {
700 id: FactId(4),
701 cause: FactId(3),
702 io_type: IoType::http_get(),
703 params: JsonValue::empty_object(),
704 },
705 Fact::IoResponse {
706 id: FactId(5),
707 request_id: FactId(4),
708 result: JsonValue::string("response"),
709 error: None,
710 },
711 Fact::Stable {
712 id: FactId(6),
713 final_snapshot: JsonValue::empty_object(),
714 },
715 Fact::Error {
716 id: FactId(7),
717 message: "test error".into(),
718 },
719 ];
720
721 let tier1_hashes: Vec<String> = test_facts.iter().map(|f| fact_hash(f).unwrap()).collect();
723
724 let tier2_snapshot_path = "../../evorule-governance/src/hash_snapshot.txt".to_string();
726 if std::path::Path::new(&tier2_snapshot_path).exists() {
727 let tier2_snapshot = std::fs::read_to_string(&tier2_snapshot_path).unwrap();
728 let tier2_hashes: Vec<String> = tier2_snapshot.lines().map(|s| s.to_string()).collect();
729
730 assert_eq!(
731 tier1_hashes, tier2_hashes,
732 "tier1 和 tier2 的 fact_hash 不一致!\
733 如果这是预期变更(如 Fact 结构修改),\
734 请删除 evorule-governance/src/hash_snapshot.txt 重新生成。"
735 );
736 }
737 }
738}