1use crate::{
4 cdk::types::Cycles,
5 ids::{ComponentInstanceId, FleetSubnetCanisterPoolConfig, ReleaseBuildId},
6};
7use candid::{CandidType, Principal};
8use serde::{Deserialize, Serialize};
9
10#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
12pub struct CanisterPoolClaim {
13 pub component: ComponentInstanceId,
14 pub operation_id: [u8; 32],
15}
16
17#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
19pub enum CanisterPoolRecycleReset {
20 Pending,
21 Ready,
22 Failed { reason: String },
23}
24
25#[derive(CandidType, Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
27pub enum CanisterPoolAssetOrigin {
28 InfrastructureStore,
29 Created,
30 Imported,
31 Recycled,
32}
33
34#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
36pub enum CanisterPoolAssetStatus {
37 Store,
38 StoreDeletionPending {
39 operation_id: [u8; 32],
40 },
41 PendingReset,
42 Ready,
43 Claimed {
44 claim: CanisterPoolClaim,
45 },
46 Workload {
47 claim: CanisterPoolClaim,
48 },
49 Recycling {
50 claim: CanisterPoolClaim,
51 reset: CanisterPoolRecycleReset,
52 },
53 RecoveringLedger {
54 operation_id: [u8; 32],
55 },
56 HandingOff {
57 recipient: Principal,
58 },
59 Failed {
60 reason: String,
61 },
62}
63
64#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
66pub struct CanisterPoolAsset {
67 pub canister_id: Principal,
68 pub cycles: Cycles,
69 pub origin: CanisterPoolAssetOrigin,
70 pub status: CanisterPoolAssetStatus,
71 pub added_at_ns: u64,
72 pub updated_at_ns: u64,
73}
74
75#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
77pub struct CanisterPoolHandoff {
78 pub canister_id: Principal,
79 pub recipient: Principal,
80 pub prepared_at_ns: u64,
81}
82
83#[derive(CandidType, Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
85pub enum CanisterPoolCreationFailure {
86 UnresolvedAfterLedgerWindow,
87 LedgerCreationFailed,
88 LedgerRejected,
89}
90
91#[derive(CandidType, Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
93pub enum CanisterPoolCreationProgress {
94 Intent {
95 uncertain_result: bool,
96 },
97 Created {
98 block_index: u64,
99 canister_id: Principal,
100 },
101 Blocked {
102 failure: CanisterPoolCreationFailure,
103 },
104}
105
106#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
108pub struct CanisterPoolCreation {
109 pub operation_id: [u8; 32],
110 pub cycles_ledger: Principal,
111 pub placement_subnet: Principal,
112 pub root: Principal,
113 pub ledger_amount: Cycles,
114 pub created_at_time_ns: u64,
115 pub progress: CanisterPoolCreationProgress,
116}
117
118#[derive(CandidType, Clone, Copy, Debug, Deserialize, Eq, PartialEq)]
120pub struct CanisterPoolStatusRequest {
121 pub start_after: Option<Principal>,
122 pub limit: u16,
123}
124
125#[derive(CandidType, Clone, Copy, Debug, Deserialize, Eq, PartialEq)]
127pub struct PoolCanisterRequest {
128 pub canister_id: Principal,
129}
130
131#[derive(CandidType, Clone, Copy, Debug, Deserialize, Eq, PartialEq)]
133pub struct PoolHandoffRequest {
134 pub canister_id: Principal,
135 pub recipient: Principal,
136}
137
138#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
140pub struct PoolLedgerRecoveryArtifact {
141 pub candid_sha256: [u8; 32],
142 pub payload_hash: [u8; 32],
143 pub payload_size_bytes: u64,
144 pub raw_module_hash: [u8; 32],
145 pub release_build_id: ReleaseBuildId,
146}
147
148#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
150pub struct PoolLedgerRecoveryRequest {
151 pub artifact: PoolLedgerRecoveryArtifact,
152 pub canister_id: Principal,
153 pub created_at_time_ns: u64,
154 pub cycles_ledger: Principal,
155 pub ledger_balance: Cycles,
156 pub ledger_fee: Cycles,
157 pub maximum_execution_burn_cycles: Cycles,
158 pub operation_id: [u8; 32],
159 pub withdrawal_amount: Cycles,
160}
161
162#[derive(CandidType, Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
164pub enum PoolLedgerRecoveryPhase {
165 Prepared,
166 HelperInstallIssued,
167 HelperInstalled,
168 WithdrawalIssued,
169 WithdrawalVerified,
170 HelperUninstallIssued,
171 Complete,
172}
173
174#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
176pub struct PoolLedgerRecoveryReceipt {
177 pub block_index: u64,
178 pub completed_at_ns: u64,
179 pub final_native_cycles: Cycles,
180 pub operation_id: [u8; 32],
181 pub request: PoolLedgerRecoveryRequest,
182}
183
184#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
186pub struct PoolLedgerRecoveryStatusResponse {
187 pub block_index: Option<u64>,
188 pub initial_native_cycles: Cycles,
189 pub phase: PoolLedgerRecoveryPhase,
190 pub receipt: Option<PoolLedgerRecoveryReceipt>,
191 pub request: PoolLedgerRecoveryRequest,
192}
193
194#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
196pub struct CanisterPoolResponse {
197 pub config: FleetSubnetCanisterPoolConfig,
198 pub tracked: u32,
199 pub store: u32,
200 pub store_deletion_pending: u32,
201 pub pooled: u32,
202 pub workload: u32,
203 pub surplus: u32,
204 pub ready: u32,
205 pub pending_reset: u32,
206 pub claimed: u32,
207 pub recycling: u32,
208 pub recovering_ledger: u32,
209 pub handing_off: u32,
210 pub failed: u32,
211 pub completed_handoffs: u64,
212 pub pending_creation: Option<CanisterPoolCreation>,
213 pub pending_handoff: Option<CanisterPoolHandoff>,
214 pub entries: Vec<CanisterPoolAsset>,
215 pub next_start_after: Option<Principal>,
216}
217
218#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq)]
220pub enum PoolAdminCommand {
221 Maintain,
222 RetryRefill,
223 Import {
224 canister_id: Principal,
225 },
226 RetryReset {
227 canister_id: Principal,
228 },
229 Handoff {
230 canister_id: Principal,
231 recipient: Principal,
232 },
233 RecoverLedger(Box<PoolLedgerRecoveryRequest>),
234}
235
236#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq)]
238pub enum PoolAdminResponse {
239 Maintained,
240 MaintenancePaused {
241 reason: String,
242 },
243 Created {
244 canister_id: Principal,
245 },
246 RefillWaitingForCycles {
247 available: Cycles,
248 creation_amount: Cycles,
249 },
250 RefillPending {
251 operation_id: [u8; 32],
252 uncertain_result: bool,
253 },
254 RefillBlocked {
255 operation_id: [u8; 32],
256 failure: CanisterPoolCreationFailure,
257 },
258 RefillRetryScheduled {
259 previous_operation_id: [u8; 32],
260 },
261 Imported {
262 canister_id: Principal,
263 },
264 ResetQueued {
265 canister_id: Principal,
266 },
267 ResetReady {
268 canister_id: Principal,
269 },
270 HandedOff {
271 canister_id: Principal,
272 recipient: Principal,
273 },
274 LedgerRecovered(Box<PoolLedgerRecoveryReceipt>),
275 ResetFailed {
276 canister_id: Principal,
277 reason: String,
278 },
279}
280
281#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq)]
283pub enum PoolMaintenanceResponse {
284 Maintained,
285 MaintenancePaused {
286 reason: String,
287 },
288 Created {
289 canister_id: Principal,
290 },
291 RefillWaitingForCycles {
292 available: Cycles,
293 creation_amount: Cycles,
294 },
295 RefillPending {
296 operation_id: [u8; 32],
297 uncertain_result: bool,
298 },
299 RefillBlocked {
300 operation_id: [u8; 32],
301 failure: CanisterPoolCreationFailure,
302 },
303 ResetReady {
304 canister_id: Principal,
305 },
306 ResetFailed {
307 canister_id: Principal,
308 reason: String,
309 },
310}
311
312#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq)]
314pub enum PoolImportResponse {
315 Imported {
316 canister_id: Principal,
317 },
318 ResetFailed {
319 canister_id: Principal,
320 reason: String,
321 },
322}
323
324#[derive(CandidType, Clone, Copy, Debug, Deserialize, Eq, PartialEq)]
326pub struct PoolRefillRetryResponse {
327 pub previous_operation_id: [u8; 32],
328}
329
330#[derive(CandidType, Clone, Copy, Debug, Deserialize, Eq, PartialEq)]
332pub struct PoolResetRetryResponse {
333 pub canister_id: Principal,
334}
335
336#[derive(CandidType, Clone, Copy, Debug, Deserialize, Eq, PartialEq)]
338pub struct PoolHandoffResponse {
339 pub canister_id: Principal,
340 pub recipient: Principal,
341}
342
343#[cfg(test)]
348mod tests {
349 use super::*;
350
351 #[test]
352 fn pool_status_and_admin_contracts_round_trip_through_candid() {
353 let canister_id = Principal::from_slice(&[7; 29]);
354 let response = CanisterPoolResponse {
355 config: FleetSubnetCanisterPoolConfig {
356 minimum_size: 3,
357 maximum_size: 10,
358 canister_cycles: Cycles::new(5_000_000_000_000),
359 },
360 tracked: 1,
361 store: 0,
362 store_deletion_pending: 0,
363 pooled: 1,
364 workload: 0,
365 surplus: 0,
366 ready: 0,
367 pending_reset: 0,
368 claimed: 0,
369 recycling: 0,
370 recovering_ledger: 0,
371 handing_off: 0,
372 failed: 1,
373 completed_handoffs: 0,
374 pending_creation: Some(CanisterPoolCreation {
375 operation_id: [8; 32],
376 cycles_ledger: Principal::from_slice(&[6; 29]),
377 placement_subnet: Principal::from_slice(&[5; 29]),
378 root: Principal::from_slice(&[4; 29]),
379 ledger_amount: Cycles::new(5_500_000_000_000),
380 created_at_time_ns: 12,
381 progress: CanisterPoolCreationProgress::Blocked {
382 failure: CanisterPoolCreationFailure::LedgerCreationFailed,
383 },
384 }),
385 pending_handoff: None,
386 entries: vec![CanisterPoolAsset {
387 canister_id,
388 cycles: Cycles::new(4_000_000_000_000),
389 origin: CanisterPoolAssetOrigin::Recycled,
390 status: CanisterPoolAssetStatus::Failed {
391 reason: "below configured cycles".to_string(),
392 },
393 added_at_ns: 10,
394 updated_at_ns: 11,
395 }],
396 next_start_after: None,
397 };
398 let bytes = candid::encode_one(&response).expect("encode pool status");
399 assert_eq!(
400 candid::decode_one::<CanisterPoolResponse>(&bytes).expect("decode pool status"),
401 response,
402 );
403
404 let command = PoolAdminCommand::Import { canister_id };
405 let bytes = candid::encode_one(&command).expect("encode pool command");
406 assert_eq!(
407 candid::decode_one::<PoolAdminCommand>(&bytes).expect("decode pool command"),
408 command,
409 );
410
411 let command = PoolAdminCommand::RetryRefill;
412 let bytes = candid::encode_one(&command).expect("encode refill retry command");
413 assert_eq!(
414 candid::decode_one::<PoolAdminCommand>(&bytes).expect("decode refill retry command"),
415 command,
416 );
417 }
418}