1use alloy_primitives::{Bytes, U256};
32use blueprint_client_tangle::TangleClient;
33use blueprint_std::format;
34use blueprint_std::string::String;
35use blueprint_std::sync::Arc;
36use thiserror::Error;
37
38#[derive(Debug, Error)]
40pub enum AggregationError {
41 #[error("Client error: {0}")]
43 Client(String),
44 #[error("Threshold not met: got {0} signers, need {1}")]
46 ThresholdNotMet(usize, usize),
47 #[error("Invalid BLS signature")]
49 InvalidSignature,
50 #[error("Contract call failed: {0}")]
52 ContractError(String),
53 #[error("Missing BLS key for operator at index {0}")]
55 MissingOperatorKey(usize),
56}
57
58#[derive(Debug, Clone, Default)]
62pub struct SignerBitmap(pub U256);
63
64impl SignerBitmap {
65 pub fn new() -> Self {
67 Self(U256::ZERO)
68 }
69
70 pub fn from_indices(indices: &[usize]) -> Self {
72 let mut bitmap = U256::ZERO;
73 for &idx in indices {
74 bitmap |= U256::from(1u64) << idx;
75 }
76 Self(bitmap)
77 }
78
79 pub fn is_signer(&self, index: usize) -> bool {
81 (self.0 >> index) & U256::from(1u64) == U256::from(1u64)
82 }
83
84 pub fn add_signer(&mut self, index: usize) {
86 self.0 |= U256::from(1u64) << index;
87 }
88
89 pub fn remove_signer(&mut self, index: usize) {
91 self.0 &= !(U256::from(1u64) << index);
92 }
93
94 pub fn count_signers(&self) -> usize {
96 let mut count = 0;
97 let mut bitmap = self.0;
98 while bitmap > U256::ZERO {
99 if bitmap & U256::from(1u64) == U256::from(1u64) {
100 count += 1;
101 }
102 bitmap >>= 1;
103 }
104 count
105 }
106
107 pub fn as_u256(&self) -> U256 {
109 self.0
110 }
111}
112
113#[derive(Debug, Clone)]
117pub struct G1Point {
118 pub x: U256,
120 pub y: U256,
122}
123
124impl G1Point {
125 pub fn new(x: U256, y: U256) -> Self {
127 Self { x, y }
128 }
129
130 pub fn from_bytes(bytes: &[u8]) -> Option<Self> {
132 if bytes.len() != 64 {
133 return None;
134 }
135 let x = U256::from_be_slice(&bytes[0..32]);
136 let y = U256::from_be_slice(&bytes[32..64]);
137 Some(Self { x, y })
138 }
139
140 pub fn to_array(&self) -> [U256; 2] {
142 [self.x, self.y]
143 }
144}
145
146#[derive(Debug, Clone)]
150pub struct G2Point {
151 pub x0: U256,
153 pub x1: U256,
155 pub y0: U256,
157 pub y1: U256,
159}
160
161impl G2Point {
162 pub fn new(x0: U256, x1: U256, y0: U256, y1: U256) -> Self {
164 Self { x0, x1, y0, y1 }
165 }
166
167 pub fn from_bytes(bytes: &[u8]) -> Option<Self> {
169 if bytes.len() != 128 {
170 return None;
171 }
172 let x0 = U256::from_be_slice(&bytes[0..32]);
173 let x1 = U256::from_be_slice(&bytes[32..64]);
174 let y0 = U256::from_be_slice(&bytes[64..96]);
175 let y1 = U256::from_be_slice(&bytes[96..128]);
176 Some(Self { x0, x1, y0, y1 })
177 }
178
179 pub fn to_array(&self) -> [U256; 4] {
181 [self.x0, self.x1, self.y0, self.y1]
182 }
183}
184
185#[derive(Debug, Clone)]
187pub struct AggregatedResult {
188 pub service_id: u64,
190 pub call_id: u64,
192 pub output: Bytes,
194 pub signer_bitmap: SignerBitmap,
196 pub signature: G1Point,
198 pub pubkey: G2Point,
200}
201
202impl AggregatedResult {
203 pub fn new(
205 service_id: u64,
206 call_id: u64,
207 output: Bytes,
208 signer_bitmap: SignerBitmap,
209 signature: G1Point,
210 pubkey: G2Point,
211 ) -> Self {
212 Self {
213 service_id,
214 call_id,
215 output,
216 signer_bitmap,
217 signature,
218 pubkey,
219 }
220 }
221
222 pub async fn submit(&self, client: &Arc<TangleClient>) -> Result<(), AggregationError> {
226 blueprint_core::debug!(
227 target: "tangle-aggregation",
228 "Submitting aggregated result for service {} call {} with {} signers",
229 self.service_id,
230 self.call_id,
231 self.signer_bitmap.count_signers()
232 );
233
234 let result = client
235 .submit_aggregated_result(
236 self.service_id,
237 self.call_id,
238 self.output.clone(),
239 self.signer_bitmap.as_u256(),
240 self.signature.to_array(),
241 self.pubkey.to_array(),
242 )
243 .await
244 .map_err(|e| {
245 AggregationError::ContractError(format!("Failed to submit aggregated result: {e}"))
246 })?;
247
248 if result.success {
249 blueprint_core::info!(
250 target: "tangle-aggregation",
251 "Successfully submitted aggregated result for service {} call {} with {} signers: tx_hash={:?}",
252 self.service_id,
253 self.call_id,
254 self.signer_bitmap.count_signers(),
255 result.tx_hash
256 );
257 Ok(())
258 } else {
259 Err(AggregationError::ContractError(format!(
260 "Transaction reverted for service {} call {}: tx_hash={:?}",
261 self.service_id, self.call_id, result.tx_hash
262 )))
263 }
264 }
265}
266
267pub const JOB_INDEX_KEY: &str = "tangle.job_index";
269
270#[cfg(test)]
271mod tests {
272 use super::*;
273
274 #[test]
279 fn test_signer_bitmap() {
280 let mut bitmap = SignerBitmap::new();
281 assert_eq!(bitmap.count_signers(), 0);
282
283 bitmap.add_signer(0);
284 bitmap.add_signer(2);
285 bitmap.add_signer(5);
286
287 assert!(bitmap.is_signer(0));
288 assert!(!bitmap.is_signer(1));
289 assert!(bitmap.is_signer(2));
290 assert!(!bitmap.is_signer(3));
291 assert!(!bitmap.is_signer(4));
292 assert!(bitmap.is_signer(5));
293 assert_eq!(bitmap.count_signers(), 3);
294
295 bitmap.remove_signer(2);
296 assert!(!bitmap.is_signer(2));
297 assert_eq!(bitmap.count_signers(), 2);
298 }
299
300 #[test]
301 fn test_signer_bitmap_from_indices() {
302 let bitmap = SignerBitmap::from_indices(&[1, 3, 7]);
303 assert!(!bitmap.is_signer(0));
304 assert!(bitmap.is_signer(1));
305 assert!(!bitmap.is_signer(2));
306 assert!(bitmap.is_signer(3));
307 assert!(bitmap.is_signer(7));
308 assert_eq!(bitmap.count_signers(), 3);
309 }
310
311 #[test]
312 fn test_signer_bitmap_empty_indices() {
313 let bitmap = SignerBitmap::from_indices(&[]);
314 assert_eq!(bitmap.count_signers(), 0);
315 assert_eq!(bitmap.as_u256(), U256::ZERO);
316 }
317
318 #[test]
319 fn test_signer_bitmap_large_indices() {
320 let bitmap = SignerBitmap::from_indices(&[0, 100, 200, 255]);
322 assert!(bitmap.is_signer(0));
323 assert!(bitmap.is_signer(100));
324 assert!(bitmap.is_signer(200));
325 assert!(bitmap.is_signer(255));
326 assert!(!bitmap.is_signer(50));
327 assert_eq!(bitmap.count_signers(), 4);
328 }
329
330 #[test]
331 fn test_signer_bitmap_duplicate_indices() {
332 let bitmap = SignerBitmap::from_indices(&[1, 1, 1, 2]);
334 assert_eq!(bitmap.count_signers(), 2);
335 }
336
337 #[test]
338 fn test_signer_bitmap_as_u256() {
339 let bitmap = SignerBitmap::from_indices(&[0, 1, 2]);
340 assert_eq!(bitmap.as_u256(), U256::from(7u64));
342 }
343
344 #[test]
345 fn test_signer_bitmap_default() {
346 let bitmap = SignerBitmap::default();
347 assert_eq!(bitmap.count_signers(), 0);
348 assert_eq!(bitmap.as_u256(), U256::ZERO);
349 }
350
351 #[test]
356 fn test_g1_point_new() {
357 let x = U256::from(123u64);
358 let y = U256::from(456u64);
359 let point = G1Point::new(x, y);
360 assert_eq!(point.x, x);
361 assert_eq!(point.y, y);
362 }
363
364 #[test]
365 fn test_g1_point_from_bytes() {
366 let mut bytes = [0u8; 64];
367 bytes[31] = 1;
369 bytes[63] = 2;
371
372 let point = G1Point::from_bytes(&bytes).expect("should parse 64 bytes");
373 assert_eq!(point.x, U256::from(1u64));
374 assert_eq!(point.y, U256::from(2u64));
375 }
376
377 #[test]
378 fn test_g1_point_from_bytes_invalid_length() {
379 let bytes = [0u8; 32]; assert!(G1Point::from_bytes(&bytes).is_none());
381
382 let bytes = [0u8; 128]; assert!(G1Point::from_bytes(&bytes).is_none());
384 }
385
386 #[test]
387 fn test_g1_point_to_array() {
388 let x = U256::from(100u64);
389 let y = U256::from(200u64);
390 let point = G1Point::new(x, y);
391 let arr = point.to_array();
392 assert_eq!(arr[0], x);
393 assert_eq!(arr[1], y);
394 }
395
396 #[test]
401 fn test_g2_point_new() {
402 let x0 = U256::from(1u64);
403 let x1 = U256::from(2u64);
404 let y0 = U256::from(3u64);
405 let y1 = U256::from(4u64);
406 let point = G2Point::new(x0, x1, y0, y1);
407 assert_eq!(point.x0, x0);
408 assert_eq!(point.x1, x1);
409 assert_eq!(point.y0, y0);
410 assert_eq!(point.y1, y1);
411 }
412
413 #[test]
414 fn test_g2_point_from_bytes() {
415 let mut bytes = [0u8; 128];
416 bytes[31] = 1;
418 bytes[63] = 2;
419 bytes[95] = 3;
420 bytes[127] = 4;
421
422 let point = G2Point::from_bytes(&bytes).expect("should parse 128 bytes");
423 assert_eq!(point.x0, U256::from(1u64));
424 assert_eq!(point.x1, U256::from(2u64));
425 assert_eq!(point.y0, U256::from(3u64));
426 assert_eq!(point.y1, U256::from(4u64));
427 }
428
429 #[test]
430 fn test_g2_point_from_bytes_invalid_length() {
431 let bytes = [0u8; 64]; assert!(G2Point::from_bytes(&bytes).is_none());
433
434 let bytes = [0u8; 256]; assert!(G2Point::from_bytes(&bytes).is_none());
436 }
437
438 #[test]
439 fn test_g2_point_to_array() {
440 let x0 = U256::from(10u64);
441 let x1 = U256::from(20u64);
442 let y0 = U256::from(30u64);
443 let y1 = U256::from(40u64);
444 let point = G2Point::new(x0, x1, y0, y1);
445 let arr = point.to_array();
446 assert_eq!(arr[0], x0);
447 assert_eq!(arr[1], x1);
448 assert_eq!(arr[2], y0);
449 assert_eq!(arr[3], y1);
450 }
451
452 #[test]
457 fn test_aggregated_result_new() {
458 let service_id = 1u64;
459 let call_id = 42u64;
460 let output = Bytes::from(vec![1, 2, 3, 4]);
461 let signer_bitmap = SignerBitmap::from_indices(&[0, 1, 2]);
462 let signature = G1Point::new(U256::from(100u64), U256::from(200u64));
463 let pubkey = G2Point::new(
464 U256::from(1u64),
465 U256::from(2u64),
466 U256::from(3u64),
467 U256::from(4u64),
468 );
469
470 let result = AggregatedResult::new(
471 service_id,
472 call_id,
473 output.clone(),
474 signer_bitmap.clone(),
475 signature.clone(),
476 pubkey.clone(),
477 );
478
479 assert_eq!(result.service_id, service_id);
480 assert_eq!(result.call_id, call_id);
481 assert_eq!(result.output, output);
482 assert_eq!(result.signer_bitmap.count_signers(), 3);
483 }
484}