1use crate::challenge::{Challenge, ChallengeGenerator};
4use crate::mml_path::MMLPathValidator;
5use crate::neural_path::NeuralPathValidator;
6use ai3_lib::{AI3Engine, EngineStats, TensorEngine};
7use pot_o_core::TribeResult;
8use serde::{Deserialize, Serialize};
9use sha2::{Digest, Sha256};
10use std::time::Instant;
11
12#[derive(Debug, Clone, Serialize, Deserialize)]
14pub struct PotOProof {
15 pub challenge_id: String,
16 pub challenge_hash: String,
17 pub tensor_result_hash: String,
18 pub mml_score: f64,
19 pub path_signature: String,
20 pub path_distance: u32,
21 pub computation_nonce: u64,
22 pub computation_hash: String,
23 pub miner_pubkey: String,
24 pub timestamp: chrono::DateTime<chrono::Utc>,
25}
26
27#[derive(Debug, Clone, Serialize, Deserialize)]
29pub struct ProofPayload {
30 pub proof: PotOProof,
31 pub signature: Vec<u8>,
32}
33
34pub struct PotOConsensus {
37 pub engine: Box<dyn TensorEngine>,
38 pub challenge_gen: ChallengeGenerator,
39 pub mml_validator: MMLPathValidator,
40 pub neural_validator: NeuralPathValidator,
41}
42
43impl PotOConsensus {
44 pub fn new(difficulty: u64, max_tensor_dim: usize) -> Self {
45 Self {
46 engine: Box::new(AI3Engine::new()),
47 challenge_gen: ChallengeGenerator::new(difficulty, max_tensor_dim),
48 mml_validator: MMLPathValidator::default(),
49 neural_validator: NeuralPathValidator::default(),
50 }
51 }
52
53 pub fn generate_challenge(&self, slot: u64, slot_hash: &str) -> TribeResult<Challenge> {
55 self.challenge_gen.generate(slot, slot_hash)
56 }
57
58 pub fn mine(
61 &self,
62 challenge: &Challenge,
63 miner_pubkey: &str,
64 max_iterations: u64,
65 ) -> TribeResult<Option<PotOProof>> {
66 let start = Instant::now();
67 let task = challenge.to_mining_task(miner_pubkey);
68
69 let output_tensor = self.engine.execute_task(&task)?;
70 let mml_score = self
71 .mml_validator
72 .compute_mml_score(&challenge.input_tensor, &output_tensor)?;
73
74 for nonce in 0..max_iterations {
75 let actual_path = self
76 .neural_validator
77 .compute_actual_path(&output_tensor, nonce)?;
78 let expected = self.neural_validator.expected_path_signature(&challenge.id);
79 let min_len = actual_path.len().min(expected.len());
80 let distance = NeuralPathValidator::hamming_distance(
81 &actual_path[..min_len],
82 &expected[..min_len],
83 );
84
85 if distance <= challenge.path_distance_max
86 && self
87 .mml_validator
88 .validate(mml_score, challenge.mml_threshold)
89 {
90 let tensor_result_hash = output_tensor.calculate_hash();
91 let path_sig = NeuralPathValidator::path_to_hex(&actual_path);
92 let computation_hash = Self::compute_proof_hash(
93 &challenge.id,
94 &tensor_result_hash,
95 mml_score,
96 &path_sig,
97 nonce,
98 );
99
100 let elapsed = start.elapsed();
101 self.engine.record_result(true, elapsed);
102
103 return Ok(Some(PotOProof {
104 challenge_id: challenge.id.clone(),
105 challenge_hash: challenge.slot_hash.clone(),
106 tensor_result_hash,
107 mml_score,
108 path_signature: path_sig,
109 path_distance: distance,
110 computation_nonce: nonce,
111 computation_hash,
112 miner_pubkey: miner_pubkey.to_string(),
113 timestamp: chrono::Utc::now(),
114 }));
115 }
116 }
117
118 self.engine.record_result(false, start.elapsed());
119 Ok(None)
120 }
121
122 pub fn verify_proof(&self, proof: &PotOProof, challenge: &Challenge) -> TribeResult<bool> {
124 let expected_hash = Self::compute_proof_hash(
126 &proof.challenge_id,
127 &proof.tensor_result_hash,
128 proof.mml_score,
129 &proof.path_signature,
130 proof.computation_nonce,
131 );
132 if expected_hash != proof.computation_hash {
133 return Ok(false);
134 }
135
136 if !self
138 .mml_validator
139 .validate(proof.mml_score, challenge.mml_threshold)
140 {
141 return Ok(false);
142 }
143
144 if proof.path_distance > challenge.path_distance_max {
146 return Ok(false);
147 }
148
149 Ok(true)
150 }
151
152 pub fn expected_paths_and_calcs(&self, challenge: &Challenge) -> (u64, u64) {
156 let expected_paths = self
157 .neural_validator
158 .expected_path_signature(&challenge.id)
159 .len() as u64;
160 let expected_calcs = 1 + challenge.difficulty;
161 (expected_paths, expected_calcs)
162 }
163
164 pub fn engine_stats(&self) -> EngineStats {
166 self.engine.get_stats()
167 }
168
169 pub fn compute_proof_hash(
171 challenge_id: &str,
172 tensor_result_hash: &str,
173 mml_score: f64,
174 path_signature: &str,
175 nonce: u64,
176 ) -> String {
177 let mut hasher = Sha256::new();
178 hasher.update(challenge_id.as_bytes());
179 hasher.update(tensor_result_hash.as_bytes());
180 hasher.update(mml_score.to_le_bytes());
181 hasher.update(path_signature.as_bytes());
182 hasher.update(nonce.to_le_bytes());
183 hex::encode(hasher.finalize())
184 }
185}
186
187#[cfg(test)]
188mod tests {
189 use super::*;
190
191 #[test]
192 fn test_consensus_lifecycle() {
193 let consensus = PotOConsensus::new(1, 8);
194 let hash = "abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789";
195 let challenge = consensus.generate_challenge(100, hash).unwrap();
196 assert!(!challenge.id.is_empty());
197
198 let result = consensus
200 .mine(&challenge, "test_miner_pubkey", 1000)
201 .unwrap();
202 assert!(result.is_some(), "Should find a proof with low difficulty");
203
204 let proof = result.unwrap();
205 let valid = consensus.verify_proof(&proof, &challenge).unwrap();
206 assert!(valid, "Mined proof should verify");
207 }
208
209 #[test]
210 fn test_proof_hash_deterministic() {
211 let h1 = PotOConsensus::compute_proof_hash("chal", "tensor", 0.5, "path", 42);
212 let h2 = PotOConsensus::compute_proof_hash("chal", "tensor", 0.5, "path", 42);
213 assert_eq!(h1, h2);
214 }
215
216 #[test]
217 fn test_expected_paths_and_calcs() {
218 let consensus = PotOConsensus::new(2, 8);
219 let hash = "abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789";
220 let challenge = consensus.generate_challenge(100, hash).unwrap();
221 let (expected_paths, expected_calcs) = consensus.expected_paths_and_calcs(&challenge);
222 assert!(expected_paths > 0, "expected_paths should be positive");
223 assert!(expected_calcs > 0, "expected_calcs should be positive");
224 let path_len = consensus
225 .neural_validator
226 .expected_path_signature(&challenge.id)
227 .len() as u64;
228 assert_eq!(
229 expected_paths, path_len,
230 "expected_paths should match path signature length"
231 );
232 assert_eq!(expected_calcs, 1 + challenge.difficulty);
233 }
234}