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