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