Skip to main content

blueprint_tangle_aggregation_svc/
types.rs

1//! Request and response types for the aggregation service API
2
3use alloy_primitives::U256;
4use serde::{Deserialize, Serialize};
5
6/// Unique identifier for a job aggregation task
7#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
8pub struct TaskId {
9    pub service_id: u64,
10    pub call_id: u64,
11}
12
13impl TaskId {
14    pub fn new(service_id: u64, call_id: u64) -> Self {
15        Self {
16            service_id,
17            call_id,
18        }
19    }
20}
21
22/// Description of how many signatures are required for a task
23#[derive(Debug, Clone, Serialize, Deserialize)]
24#[serde(tag = "type", rename_all = "snake_case")]
25pub enum ThresholdConfig {
26    /// Require a fixed number of operators to sign.
27    Count { required_signers: u32 },
28    /// Require stake-weighted participation. Carries the per-index stakes.
29    StakeWeighted {
30        /// Basis points (0-10000) of total stake required.
31        threshold_bps: u32,
32        /// Stake weight for each operator index.
33        operator_stakes: Vec<OperatorStake>,
34    },
35}
36
37/// Stake weight for a specific operator index.
38#[derive(Debug, Clone, Serialize, Deserialize)]
39pub struct OperatorStake {
40    /// Operator index within the service operator set.
41    pub operator_index: u32,
42    /// Relative stake or weight used for aggregation.
43    pub stake: u64,
44}
45
46/// Request to submit a signature for aggregation
47#[derive(Debug, Clone, Serialize, Deserialize)]
48pub struct SubmitSignatureRequest {
49    /// Service ID
50    pub service_id: u64,
51    /// Job call ID
52    pub call_id: u64,
53    /// Operator index in the service's operator list
54    pub operator_index: u32,
55    /// The job output being signed
56    #[serde(with = "hex_bytes")]
57    pub output: Vec<u8>,
58    /// BLS signature (G1 point, 64 bytes compressed)
59    #[serde(with = "hex_bytes")]
60    pub signature: Vec<u8>,
61    /// Operator's BLS public key (G2 point, 128 bytes compressed)
62    #[serde(with = "hex_bytes")]
63    pub public_key: Vec<u8>,
64}
65
66/// Response after submitting a signature
67#[derive(Debug, Clone, Serialize, Deserialize)]
68pub struct SubmitSignatureResponse {
69    /// Whether the submission was accepted
70    pub accepted: bool,
71    /// Current number of signatures collected
72    pub signatures_collected: usize,
73    /// Required threshold
74    pub threshold_required: usize,
75    /// Whether threshold has been met
76    pub threshold_met: bool,
77    /// Optional error message
78    #[serde(skip_serializing_if = "Option::is_none")]
79    pub error: Option<String>,
80}
81
82/// Request to query aggregation status
83#[derive(Debug, Clone, Serialize, Deserialize)]
84pub struct GetStatusRequest {
85    pub service_id: u64,
86    pub call_id: u64,
87}
88
89/// Response with aggregation status
90#[derive(Debug, Clone, Serialize, Deserialize)]
91pub struct GetStatusResponse {
92    /// Whether the task exists
93    pub exists: bool,
94    /// Current signatures collected
95    pub signatures_collected: usize,
96    /// Required threshold
97    pub threshold_required: usize,
98    /// Whether threshold is met
99    pub threshold_met: bool,
100    /// Signer bitmap (which operators have signed)
101    pub signer_bitmap: U256,
102    /// Signed stake in basis points (0-10000), for stake-weighted thresholds
103    #[serde(skip_serializing_if = "Option::is_none")]
104    pub signed_stake_bps: Option<u32>,
105    /// Whether already submitted to chain
106    pub submitted: bool,
107    /// Whether the task has expired
108    #[serde(skip_serializing_if = "Option::is_none")]
109    pub is_expired: Option<bool>,
110    /// Time remaining until expiry in seconds
111    #[serde(skip_serializing_if = "Option::is_none")]
112    pub time_remaining_secs: Option<u64>,
113}
114
115/// Request to initialize an aggregation task
116#[derive(Debug, Clone, Serialize, Deserialize)]
117pub struct InitTaskRequest {
118    pub service_id: u64,
119    pub call_id: u64,
120    /// Number of operators in the service
121    pub operator_count: u32,
122    /// Threshold definition (count or stake-weighted)
123    pub threshold: ThresholdConfig,
124    /// The output to be signed
125    #[serde(with = "hex_bytes")]
126    pub output: Vec<u8>,
127}
128
129/// Response after initializing a task
130#[derive(Debug, Clone, Serialize, Deserialize)]
131pub struct InitTaskResponse {
132    pub success: bool,
133    #[serde(skip_serializing_if = "Option::is_none")]
134    pub error: Option<String>,
135}
136
137/// Aggregated result ready for submission
138#[derive(Debug, Clone, Serialize, Deserialize)]
139pub struct AggregatedResultResponse {
140    pub service_id: u64,
141    pub call_id: u64,
142    #[serde(with = "hex_bytes")]
143    pub output: Vec<u8>,
144    pub signer_bitmap: U256,
145    /// Indices of operators who did not sign (for potential slashing)
146    pub non_signer_indices: Vec<u32>,
147    /// Aggregated signature (G1 point)
148    #[serde(with = "hex_bytes")]
149    pub aggregated_signature: Vec<u8>,
150    /// Aggregated public key (G2 point)
151    #[serde(with = "hex_bytes")]
152    pub aggregated_pubkey: Vec<u8>,
153}
154
155/// Hex encoding/decoding for byte arrays in JSON
156mod hex_bytes {
157    use serde::{Deserialize, Deserializer, Serializer};
158
159    pub fn serialize<S>(bytes: &[u8], serializer: S) -> Result<S::Ok, S::Error>
160    where
161        S: Serializer,
162    {
163        serializer.serialize_str(&format!("0x{}", hex::encode(bytes)))
164    }
165
166    pub fn deserialize<'de, D>(deserializer: D) -> Result<Vec<u8>, D::Error>
167    where
168        D: Deserializer<'de>,
169    {
170        let s = String::deserialize(deserializer)?;
171        let s = s.strip_prefix("0x").unwrap_or(&s);
172        hex::decode(s).map_err(serde::de::Error::custom)
173    }
174}