use crate::{
cdk::types::Cycles,
ids::{ComponentInstanceId, FleetSubnetCanisterPoolConfig, ReleaseBuildId},
};
use candid::{CandidType, Principal};
use serde::{Deserialize, Serialize};
#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
pub struct CanisterPoolClaim {
pub component: ComponentInstanceId,
pub operation_id: [u8; 32],
}
#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
pub enum CanisterPoolRecycleReset {
Pending,
Ready,
Failed { reason: String },
}
#[derive(CandidType, Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
pub enum CanisterPoolAssetOrigin {
InfrastructureStore,
Created,
Imported,
Recycled,
}
#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
pub enum CanisterPoolAssetStatus {
Store,
StoreDeletionPending {
operation_id: [u8; 32],
},
PendingReset,
Ready,
Claimed {
claim: CanisterPoolClaim,
},
Workload {
claim: CanisterPoolClaim,
},
Recycling {
claim: CanisterPoolClaim,
reset: CanisterPoolRecycleReset,
},
RecoveringLedger {
operation_id: [u8; 32],
},
HandingOff {
recipient: Principal,
},
Failed {
reason: String,
},
}
#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
pub struct CanisterPoolAsset {
pub canister_id: Principal,
pub cycles: Cycles,
pub origin: CanisterPoolAssetOrigin,
pub status: CanisterPoolAssetStatus,
pub added_at_ns: u64,
pub updated_at_ns: u64,
}
#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
pub struct CanisterPoolHandoff {
pub canister_id: Principal,
pub recipient: Principal,
pub prepared_at_ns: u64,
}
#[derive(CandidType, Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
pub enum CanisterPoolCreationFailure {
UnresolvedAfterLedgerWindow,
LedgerCreationFailed,
LedgerRejected,
}
#[derive(CandidType, Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
pub enum CanisterPoolCreationProgress {
Intent {
uncertain_result: bool,
},
Created {
block_index: u64,
canister_id: Principal,
},
Blocked {
failure: CanisterPoolCreationFailure,
},
}
#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
pub struct CanisterPoolCreation {
pub operation_id: [u8; 32],
pub cycles_ledger: Principal,
pub placement_subnet: Principal,
pub root: Principal,
pub ledger_amount: Cycles,
pub created_at_time_ns: u64,
pub progress: CanisterPoolCreationProgress,
}
#[derive(CandidType, Clone, Copy, Debug, Deserialize, Eq, PartialEq)]
pub struct CanisterPoolStatusRequest {
pub start_after: Option<Principal>,
pub limit: u16,
}
#[derive(CandidType, Clone, Copy, Debug, Deserialize, Eq, PartialEq)]
pub struct PoolCanisterRequest {
pub canister_id: Principal,
}
#[derive(CandidType, Clone, Copy, Debug, Deserialize, Eq, PartialEq)]
pub struct PoolHandoffRequest {
pub canister_id: Principal,
pub recipient: Principal,
}
#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
pub struct PoolLedgerRecoveryArtifact {
pub candid_sha256: [u8; 32],
pub payload_hash: [u8; 32],
pub payload_size_bytes: u64,
pub raw_module_hash: [u8; 32],
pub release_build_id: ReleaseBuildId,
}
#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
pub struct PoolLedgerRecoveryRequest {
pub artifact: PoolLedgerRecoveryArtifact,
pub canister_id: Principal,
pub created_at_time_ns: u64,
pub cycles_ledger: Principal,
pub ledger_balance: Cycles,
pub ledger_fee: Cycles,
pub maximum_execution_burn_cycles: Cycles,
pub operation_id: [u8; 32],
pub withdrawal_amount: Cycles,
}
#[derive(CandidType, Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
pub enum PoolLedgerRecoveryPhase {
Prepared,
HelperInstallIssued,
HelperInstalled,
WithdrawalIssued,
WithdrawalVerified,
HelperUninstallIssued,
Complete,
}
#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
pub struct PoolLedgerRecoveryReceipt {
pub block_index: u64,
pub completed_at_ns: u64,
pub final_native_cycles: Cycles,
pub operation_id: [u8; 32],
pub request: PoolLedgerRecoveryRequest,
}
#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
pub struct PoolLedgerRecoveryStatusResponse {
pub block_index: Option<u64>,
pub initial_native_cycles: Cycles,
pub phase: PoolLedgerRecoveryPhase,
pub receipt: Option<PoolLedgerRecoveryReceipt>,
pub request: PoolLedgerRecoveryRequest,
}
#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
pub struct CanisterPoolResponse {
pub config: FleetSubnetCanisterPoolConfig,
pub tracked: u32,
pub store: u32,
pub store_deletion_pending: u32,
pub pooled: u32,
pub workload: u32,
pub surplus: u32,
pub ready: u32,
pub pending_reset: u32,
pub claimed: u32,
pub recycling: u32,
pub recovering_ledger: u32,
pub handing_off: u32,
pub failed: u32,
pub completed_handoffs: u64,
pub pending_creation: Option<CanisterPoolCreation>,
pub pending_handoff: Option<CanisterPoolHandoff>,
pub entries: Vec<CanisterPoolAsset>,
pub next_start_after: Option<Principal>,
}
#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq)]
pub enum PoolAdminCommand {
Maintain,
RetryRefill,
Import {
canister_id: Principal,
},
RetryReset {
canister_id: Principal,
},
Handoff {
canister_id: Principal,
recipient: Principal,
},
RecoverLedger(Box<PoolLedgerRecoveryRequest>),
}
#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq)]
pub enum PoolAdminResponse {
Maintained,
MaintenancePaused {
reason: String,
},
Created {
canister_id: Principal,
},
RefillWaitingForCycles {
available: Cycles,
creation_amount: Cycles,
},
RefillPending {
operation_id: [u8; 32],
uncertain_result: bool,
},
RefillBlocked {
operation_id: [u8; 32],
failure: CanisterPoolCreationFailure,
},
RefillRetryScheduled {
previous_operation_id: [u8; 32],
},
Imported {
canister_id: Principal,
},
ResetQueued {
canister_id: Principal,
},
ResetReady {
canister_id: Principal,
},
HandedOff {
canister_id: Principal,
recipient: Principal,
},
LedgerRecovered(Box<PoolLedgerRecoveryReceipt>),
ResetFailed {
canister_id: Principal,
reason: String,
},
}
#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq)]
pub enum PoolMaintenanceResponse {
Maintained,
MaintenancePaused {
reason: String,
},
Created {
canister_id: Principal,
},
RefillWaitingForCycles {
available: Cycles,
creation_amount: Cycles,
},
RefillPending {
operation_id: [u8; 32],
uncertain_result: bool,
},
RefillBlocked {
operation_id: [u8; 32],
failure: CanisterPoolCreationFailure,
},
ResetReady {
canister_id: Principal,
},
ResetFailed {
canister_id: Principal,
reason: String,
},
}
#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq)]
pub enum PoolImportResponse {
Imported {
canister_id: Principal,
},
ResetFailed {
canister_id: Principal,
reason: String,
},
}
#[derive(CandidType, Clone, Copy, Debug, Deserialize, Eq, PartialEq)]
pub struct PoolRefillRetryResponse {
pub previous_operation_id: [u8; 32],
}
#[derive(CandidType, Clone, Copy, Debug, Deserialize, Eq, PartialEq)]
pub struct PoolResetRetryResponse {
pub canister_id: Principal,
}
#[derive(CandidType, Clone, Copy, Debug, Deserialize, Eq, PartialEq)]
pub struct PoolHandoffResponse {
pub canister_id: Principal,
pub recipient: Principal,
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn pool_status_and_admin_contracts_round_trip_through_candid() {
let canister_id = Principal::from_slice(&[7; 29]);
let response = CanisterPoolResponse {
config: FleetSubnetCanisterPoolConfig {
minimum_size: 3,
maximum_size: 10,
canister_cycles: Cycles::new(5_000_000_000_000),
},
tracked: 1,
store: 0,
store_deletion_pending: 0,
pooled: 1,
workload: 0,
surplus: 0,
ready: 0,
pending_reset: 0,
claimed: 0,
recycling: 0,
recovering_ledger: 0,
handing_off: 0,
failed: 1,
completed_handoffs: 0,
pending_creation: Some(CanisterPoolCreation {
operation_id: [8; 32],
cycles_ledger: Principal::from_slice(&[6; 29]),
placement_subnet: Principal::from_slice(&[5; 29]),
root: Principal::from_slice(&[4; 29]),
ledger_amount: Cycles::new(5_500_000_000_000),
created_at_time_ns: 12,
progress: CanisterPoolCreationProgress::Blocked {
failure: CanisterPoolCreationFailure::LedgerCreationFailed,
},
}),
pending_handoff: None,
entries: vec![CanisterPoolAsset {
canister_id,
cycles: Cycles::new(4_000_000_000_000),
origin: CanisterPoolAssetOrigin::Recycled,
status: CanisterPoolAssetStatus::Failed {
reason: "below configured cycles".to_string(),
},
added_at_ns: 10,
updated_at_ns: 11,
}],
next_start_after: None,
};
let bytes = candid::encode_one(&response).expect("encode pool status");
assert_eq!(
candid::decode_one::<CanisterPoolResponse>(&bytes).expect("decode pool status"),
response,
);
let command = PoolAdminCommand::Import { canister_id };
let bytes = candid::encode_one(&command).expect("encode pool command");
assert_eq!(
candid::decode_one::<PoolAdminCommand>(&bytes).expect("decode pool command"),
command,
);
let command = PoolAdminCommand::RetryRefill;
let bytes = candid::encode_one(&command).expect("encode refill retry command");
assert_eq!(
candid::decode_one::<PoolAdminCommand>(&bytes).expect("decode refill retry command"),
command,
);
}
}