pot_o_mining/
challenge.rs1use crate::neural_path::NeuralPathValidator;
4use ai3_lib::tensor::{Tensor, TensorData, TensorShape};
5use ai3_lib::MiningTask;
6use pot_o_core::TribeResult;
7use serde::{Deserialize, Serialize};
8use sha2::{Digest, Sha256};
9
10#[derive(Debug, Clone, Serialize, Deserialize)]
12pub struct Challenge {
13 pub id: String,
15 pub slot: u64,
17 pub slot_hash: String,
19 pub operation_type: String,
21 pub input_tensor: Tensor,
23 pub difficulty: u64,
25 pub mml_threshold: f64,
27 pub path_distance_max: u32,
29 pub max_tensor_dim: usize,
31 pub created_at: chrono::DateTime<chrono::Utc>,
33 pub expires_at: chrono::DateTime<chrono::Utc>,
35}
36
37impl Challenge {
38 pub fn is_expired(&self) -> bool {
40 chrono::Utc::now() > self.expires_at
41 }
42
43 pub fn to_mining_task(&self, requester: &str) -> MiningTask {
45 MiningTask::new(
46 self.operation_type.clone(),
47 vec![self.input_tensor.clone()],
48 self.difficulty,
49 50_000_000, 300,
51 requester.to_string(),
52 )
53 }
54}
55
56pub struct ChallengeGenerator {
58 pub base_difficulty: u64,
60 pub base_mml_threshold: f64,
62 pub base_path_distance: u32,
64 pub max_tensor_dim: usize,
66 pub challenge_ttl_secs: i64,
68}
69
70impl Default for ChallengeGenerator {
71 fn default() -> Self {
72 let base_path_distance = NeuralPathValidator::default()
73 .layer_widths
74 .iter()
75 .sum::<usize>() as u32;
76
77 Self {
78 base_difficulty: 2,
79 base_mml_threshold: 2.0,
80 base_path_distance,
81 max_tensor_dim: pot_o_core::ESP_MAX_TENSOR_DIM,
82 challenge_ttl_secs: 120,
83 }
84 }
85}
86
87const OPERATIONS: &[&str] = &[
89 "matrix_multiply",
90 "convolution",
91 "relu",
92 "sigmoid",
93 "tanh",
94 "dot_product",
95 "normalize",
96];
97
98impl ChallengeGenerator {
99 pub fn new(difficulty: u64, max_tensor_dim: usize) -> Self {
101 Self {
102 base_difficulty: difficulty,
103 max_tensor_dim,
104 ..Default::default()
105 }
106 }
107
108 pub fn generate(&self, slot: u64, slot_hash_hex: &str) -> TribeResult<Challenge> {
110 let hash_bytes = hex::decode(slot_hash_hex).map_err(|e| {
111 pot_o_core::TribeError::InvalidOperation(format!("Invalid slot hash hex: {e}"))
112 })?;
113
114 let op_index = hash_bytes.first().copied().unwrap_or(0) as usize % OPERATIONS.len();
115 let operation_type = OPERATIONS[op_index].to_string();
116
117 let input_tensor = self.derive_input_tensor(&hash_bytes)?;
118
119 let difficulty = self.compute_difficulty(slot);
120 let mml_threshold = self.base_mml_threshold / (1.0 + (difficulty as f64).log2().max(0.0));
121 let path_distance_max = self
122 .base_path_distance
123 .saturating_sub((difficulty as u32).min(self.base_path_distance - 1));
124
125 let now = chrono::Utc::now();
126 let challenge_id = {
127 let mut h = Sha256::new();
128 h.update(slot.to_le_bytes());
129 h.update(&hash_bytes);
130 hex::encode(h.finalize())
131 };
132
133 Ok(Challenge {
134 id: challenge_id,
135 slot,
136 slot_hash: slot_hash_hex.to_string(),
137 operation_type,
138 input_tensor,
139 difficulty,
140 mml_threshold,
141 path_distance_max,
142 max_tensor_dim: self.max_tensor_dim,
143 created_at: now,
144 expires_at: now + chrono::Duration::seconds(self.challenge_ttl_secs),
145 })
146 }
147
148 fn derive_input_tensor(&self, hash_bytes: &[u8]) -> TribeResult<Tensor> {
150 let dim_byte = hash_bytes.get(1).copied().unwrap_or(4);
151 let dim = ((dim_byte as usize % self.max_tensor_dim) + 2).min(self.max_tensor_dim);
152 let total = dim * dim;
153
154 let mut floats: Vec<f32> = hash_bytes.iter().map(|&b| b as f32 / 255.0).collect();
155 while floats.len() < total {
157 let seed = floats.len() as f32 * 0.618_034;
158 floats.push(seed.fract());
159 }
160 floats.truncate(total);
161
162 Tensor::new(TensorShape::new(vec![dim, dim]), TensorData::F32(floats))
163 }
164
165 fn compute_difficulty(&self, slot: u64) -> u64 {
167 let epoch = slot / 10_000;
168 self.base_difficulty + epoch.min(10)
169 }
170}
171
172#[cfg(test)]
173mod tests {
174 use super::*;
175
176 #[test]
177 fn test_challenge_generation() {
178 let gen = ChallengeGenerator::default();
179 let hash = "abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789";
180 let challenge = gen.generate(100, hash).unwrap();
181 assert!(!challenge.id.is_empty());
182 assert!(challenge.mml_threshold > 0.0);
183 assert!(challenge.mml_threshold <= gen.base_mml_threshold);
184 }
185
186 #[test]
187 fn test_deterministic_operation() {
188 let gen = ChallengeGenerator::default();
189 let hash = "ff00112233445566778899aabbccddeeff00112233445566778899aabbccddeeff";
190 let c1 = gen.generate(42, hash).unwrap();
191 let c2 = gen.generate(42, hash).unwrap();
192 assert_eq!(c1.operation_type, c2.operation_type);
193 assert_eq!(c1.id, c2.id);
194 }
195}