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, version } => {
204 trace!(事实ID = ?id, 版本 = version, "处理稳定状态类型事实");
205 obj.insert("type".into(), serde_json::Value::String("Stable".into()));
206 obj.insert("id".into(), serde_json::Value::Number(id.0.into()));
207 obj.insert("version".into(), serde_json::Value::Number((*version).into()));
208 }
209 Fact::Error { id, message } => {
210 trace!(
211 事实ID = ?id,
212 消息 = %message,
213 "处理错误类型事实"
214 );
215 obj.insert("type".into(), serde_json::Value::String("Error".into()));
216 obj.insert("id".into(), serde_json::Value::Number(id.0.into()));
217 obj.insert("message".into(), serde_json::Value::String(message.clone()));
218 }
219 }
220
221 let value = serde_json::Value::Object(obj);
222
223 trace!(
224 事实ID = ?fact_id,
225 事实类型 = %fact_type,
226 "序列化完成"
227 );
228
229 Ok(value)
230}
231
232#[allow(dead_code)] pub fn content_hash(value: &JsonValue) -> Result<String, HashError> {
245 #[cfg(kani)]
246 {
247 Ok(String::from("c"))
251 }
252 #[cfg(not(kani))]
253 {
254 let serde_value = tcb_to_serde(value);
255 let serialized = serde_json::to_string(&serde_value)
256 .map_err(|e| HashError::new(format!("内容哈希序列化失败: {}", e)))?;
257 let hash = blake3::hash(serialized.as_bytes()).to_hex().to_string();
258
259 trace!(
260 序列化长度 = serialized.len(),
261 哈希值 = %hash,
262 "内容哈希计算完成"
263 );
264
265 Ok(hash)
266 }
267}
268
269pub fn fact_hash(fact: &Fact) -> Result<String, HashError> {
293 #[cfg(kani)]
294 {
295 let id = fact.id().0;
300 let h = match id % 7 {
301 0 => "a",
302 1 => "b",
303 2 => "c",
304 3 => "d",
305 4 => "e",
306 5 => "f",
307 _ => "g",
308 };
309 Ok(String::from(h))
310 }
311 #[cfg(not(kani))]
312 {
313 let fact_type = fact.type_name();
314 let fact_id = fact.id();
315
316 debug!(
317 事实ID = ?fact_id,
318 事实类型 = %fact_type,
319 "开始计算事实哈希"
320 );
321
322 let value = fact_to_stable_json(fact)?;
323 let serialized = serde_json::to_string(&value)
324 .map_err(|e| HashError::new(format!("序列化失败: {}", e)))?;
325
326 let hash = blake3::hash(serialized.as_bytes()).to_hex().to_string();
327
328 debug!(
329 事实ID = ?fact_id,
330 事实类型 = %fact_type,
331 哈希值 = %hash,
332 序列化长度 = serialized.len(),
333 "事实哈希计算完成"
334 );
335
336 Ok(hash)
337 }
338}
339
340pub fn compute_chain_hash(facts: &[Fact]) -> Result<String, HashError> {
363 let fact_count = facts.len();
364
365 debug!(事实数量 = fact_count, "开始计算链哈希");
366
367 if fact_count == 0 {
368 debug!("事实列表为空,返回 genesis 哈希");
369 return Ok(String::from("genesis"));
370 }
371
372 let mut prev_hash = String::from("genesis");
373
374 trace!(
375 初始前序哈希 = %prev_hash,
376 "初始化前序哈希为创世值"
377 );
378
379 for (index, fact) in facts.iter().enumerate() {
380 let fact_type = fact.type_name();
381 let fact_id = fact.id();
382
383 trace!(
384 索引 = index,
385 事实ID = ?fact_id,
386 事实类型 = %fact_type,
387 前序哈希 = %prev_hash,
388 "处理事实"
389 );
390
391 let fh = fact_hash(fact)?;
392
393 trace!(
394 索引 = index,
395 事实ID = ?fact_id,
396 事实哈希 = %fh,
397 "事实哈希计算完成"
398 );
399
400 let current = chain_step(&prev_hash, &fh);
401
402 trace!(
403 索引 = index,
404 事实ID = ?fact_id,
405 当前哈希 = %current,
406 "计算当前哈希(前序哈希+事实哈希)"
407 );
408
409 prev_hash = current;
410 }
411
412 debug!(
413 事实数量 = fact_count,
414 最终哈希 = %prev_hash,
415 "链哈希计算完成"
416 );
417
418 Ok(prev_hash)
419}
420
421pub fn chain_step(prev_hash: &str, content_hash: &str) -> String {
434 #[cfg(kani)]
435 {
436 let mut s = String::from(prev_hash);
440 s.push_str(content_hash);
441 s
442 }
443 #[cfg(not(kani))]
444 {
445 let combined = format!("{}{}", prev_hash, content_hash);
446 blake3::hash(combined.as_bytes()).to_hex().to_string()
447 }
448}
449
450#[cfg(test)]
451mod tests {
452 #![allow(clippy::unwrap_used, clippy::panic, clippy::expect_used)]
453 use super::*;
454 use crate::fact::{Fact, FactId, IoType};
455 use evorule_tcb::JsonValue;
456
457 #[test]
458 fn test_fact_to_stable_json_format() {
459 let command = Fact::Command {
460 id: FactId(1),
461 instruction: JsonValue::object_from_pairs(&[
462 ("type", JsonValue::string("increment")),
463 (
464 "params",
465 JsonValue::object_from_pairs(&[
466 ("attr", JsonValue::string("x")),
467 ("delta", JsonValue::Integer(5)),
468 ]),
469 ),
470 ]),
471 };
472
473 let json_value = fact_to_stable_json(&command).unwrap();
474
475 assert_eq!(json_value.get("type").unwrap().as_str().unwrap(), "Command");
476 assert_eq!(json_value.get("id").unwrap().as_u64().unwrap(), 1);
477 assert!(json_value.get("instruction").is_some());
478 }
479
480 #[test]
481 fn test_fact_hash_all_variants() {
482 let test_facts = vec![
483 Fact::Command {
484 id: FactId(1),
485 instruction: JsonValue::empty_object(),
486 },
487 Fact::PayloadUpdate {
488 id: FactId(2),
489 path: "test.path".into(),
490 value: JsonValue::string("test_value"),
491 },
492 Fact::StateTransition {
493 id: FactId(3),
494 cause: FactId(1),
495 new_payload: JsonValue::empty_object(),
496 new_queue: vec![],
497 },
498 Fact::IoRequest {
499 id: FactId(4),
500 cause: FactId(3),
501 io_type: IoType::http_get(),
502 params: JsonValue::empty_object(),
503 },
504 Fact::IoResponse {
505 id: FactId(5),
506 request_id: FactId(4),
507 result: JsonValue::string("response"),
508 error: None,
509 },
510 Fact::IoResponse {
511 id: FactId(6),
512 request_id: FactId(4),
513 result: JsonValue::Null,
514 error: Some("timeout".to_string()),
515 },
516 Fact::Stable {
517 id: FactId(7),
518 version: 0,
519 },
520 Fact::Error {
521 id: FactId(8),
522 message: "test error".into(),
523 },
524 ];
525
526 for fact in test_facts {
527 let hash = fact_hash(&fact).unwrap();
528 assert_eq!(hash.len(), 64);
529
530 let hash2 = fact_hash(&fact).unwrap();
531 assert_eq!(
532 hash,
533 hash2,
534 "fact_hash should be deterministic for {}",
535 fact.type_name()
536 );
537 }
538 }
539
540 #[test]
541 fn test_fact_hash_identity() {
542 let fact1 = Fact::Command {
543 id: FactId(1),
544 instruction: JsonValue::string("same"),
545 };
546 let fact2 = Fact::Command {
547 id: FactId(1),
548 instruction: JsonValue::string("same"),
549 };
550
551 assert_eq!(fact_hash(&fact1).unwrap(), fact_hash(&fact2).unwrap());
552 }
553
554 #[test]
555 fn test_fact_hash_different_ids() {
556 let fact1 = Fact::Command {
557 id: FactId(1),
558 instruction: JsonValue::string("same"),
559 };
560 let fact2 = Fact::Command {
561 id: FactId(2),
562 instruction: JsonValue::string("same"),
563 };
564
565 assert_ne!(fact_hash(&fact1).unwrap(), fact_hash(&fact2).unwrap());
566 }
567
568 #[test]
569 fn test_fact_hash_snapshot() {
570 let test_facts = [
571 Fact::Command {
572 id: FactId(1),
573 instruction: JsonValue::empty_object(),
574 },
575 Fact::PayloadUpdate {
576 id: FactId(2),
577 path: "test.path".into(),
578 value: JsonValue::string("test_value"),
579 },
580 Fact::StateTransition {
581 id: FactId(3),
582 cause: FactId(1),
583 new_payload: JsonValue::empty_object(),
584 new_queue: vec![],
585 },
586 Fact::IoRequest {
587 id: FactId(4),
588 cause: FactId(3),
589 io_type: IoType::http_get(),
590 params: JsonValue::empty_object(),
591 },
592 Fact::IoResponse {
593 id: FactId(5),
594 request_id: FactId(4),
595 result: JsonValue::string("response"),
596 error: None,
597 },
598 Fact::Stable {
599 id: FactId(6),
600 version: 0,
601 },
602 Fact::Error {
603 id: FactId(7),
604 message: "test error".into(),
605 },
606 ];
607
608 let current_hashes: Vec<String> =
609 test_facts.iter().map(|f| fact_hash(f).unwrap()).collect();
610 let snapshot_file = env!("CARGO_MANIFEST_DIR").to_string() + "/hash_snapshot.txt";
611
612 if std::path::Path::new(&snapshot_file).exists() {
613 let snapshot = std::fs::read_to_string(&snapshot_file).unwrap();
614 let expected_hashes: Vec<String> = snapshot.lines().map(|s| s.to_string()).collect();
615 assert_eq!(
616 current_hashes, expected_hashes,
617 "Hash snapshot mismatch! If this is an expected change (e.g., Fact struct modification), delete {} to regenerate.",
618 snapshot_file
619 );
620 } else {
621 let snapshot_content = current_hashes.join("\n");
622 std::fs::write(&snapshot_file, snapshot_content).unwrap();
623 println!("Created hash snapshot: {}", snapshot_file);
624 }
625 }
626
627 #[test]
628 fn test_compute_chain_hash_empty() {
629 let facts: Vec<Fact> = vec![];
630 let chain_hash = compute_chain_hash(&facts).unwrap();
631 assert_eq!(chain_hash, "genesis");
632 }
633
634 #[test]
635 fn test_compute_chain_hash_deterministic() {
636 let facts = vec![
637 Fact::Command {
638 id: FactId(1),
639 instruction: JsonValue::empty_object(),
640 },
641 Fact::StateTransition {
642 id: FactId(2),
643 cause: FactId(1),
644 new_payload: JsonValue::empty_object(),
645 new_queue: vec![],
646 },
647 ];
648
649 let result1 = compute_chain_hash(&facts).unwrap();
650 let result2 = compute_chain_hash(&facts).unwrap();
651 assert_eq!(result1, result2);
652 }
653
654 #[test]
655 fn test_compute_chain_hash_order_sensitive() {
656 let fact1 = Fact::Command {
657 id: FactId(1),
658 instruction: JsonValue::empty_object(),
659 };
660 let fact2 = Fact::StateTransition {
661 id: FactId(2),
662 cause: FactId(1),
663 new_payload: JsonValue::empty_object(),
664 new_queue: vec![],
665 };
666
667 let chain1 = compute_chain_hash(&[fact1.clone(), fact2.clone()]).unwrap();
668 let chain2 = compute_chain_hash(&[fact2, fact1]).unwrap();
669 assert_ne!(chain1, chain2, "链哈希应对 Fact 顺序敏感");
670 }
671
672 #[test]
677 fn test_cross_validate_with_tier2() {
678 let test_facts = [
680 Fact::Command {
681 id: FactId(1),
682 instruction: JsonValue::empty_object(),
683 },
684 Fact::PayloadUpdate {
685 id: FactId(2),
686 path: "test.path".into(),
687 value: JsonValue::string("test_value"),
688 },
689 Fact::StateTransition {
690 id: FactId(3),
691 cause: FactId(1),
692 new_payload: JsonValue::empty_object(),
693 new_queue: vec![],
694 },
695 Fact::IoRequest {
696 id: FactId(4),
697 cause: FactId(3),
698 io_type: IoType::http_get(),
699 params: JsonValue::empty_object(),
700 },
701 Fact::IoResponse {
702 id: FactId(5),
703 request_id: FactId(4),
704 result: JsonValue::string("response"),
705 error: None,
706 },
707 Fact::Stable {
708 id: FactId(6),
709 version: 0,
710 },
711 Fact::Error {
712 id: FactId(7),
713 message: "test error".into(),
714 },
715 ];
716
717 let tier1_hashes: Vec<String> = test_facts.iter().map(|f| fact_hash(f).unwrap()).collect();
719
720 let tier2_snapshot_path = "../../evorule-governance/src/hash_snapshot.txt".to_string();
722 if std::path::Path::new(&tier2_snapshot_path).exists() {
723 let tier2_snapshot = std::fs::read_to_string(&tier2_snapshot_path).unwrap();
724 let tier2_hashes: Vec<String> = tier2_snapshot.lines().map(|s| s.to_string()).collect();
725
726 assert_eq!(
727 tier1_hashes, tier2_hashes,
728 "tier1 和 tier2 的 fact_hash 不一致!\
729 如果这是预期变更(如 Fact 结构修改),\
730 请删除 evorule-governance/src/hash_snapshot.txt 重新生成。"
731 );
732 }
733 }
734}