pub mod funding_observation;
pub mod operator_mint;
mod retirement;
mod serialization;
use serde::{Deserialize, Serialize};
use std::collections::BTreeMap;
pub use retirement::FleetRetirementConservationRecord;
pub const FLEET_ENSURE_SCHEMA_VERSION: u16 = 1;
pub const MAX_FLEET_ENSURE_CANISTERS: usize = 4_096;
pub const MAX_FLEET_ENSURE_PROTOCOL_STEPS: usize = 4_096;
pub(in crate::fleet_ensure) const MAX_INDEPENDENT_EFFECTS_IN_FLIGHT: usize = 4;
#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
pub struct LiveCanister {
pub canister_version: Option<u64>,
pub controllers: Vec<String>,
#[serde(with = "u128_text")]
pub cycles: u128,
pub module_sha256: Option<String>,
pub principal: String,
pub reinstall_required: bool,
pub root_owned_lifecycle: Option<RootOwnedCanisterLifecycle>,
pub status: CanisterRuntimeStatus,
}
#[derive(Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
#[serde(rename_all = "snake_case")]
pub enum RootOwnedCanisterLifecycle {
Claimed,
Idle,
Reconciling,
Retained,
Store,
Workload,
}
impl RootOwnedCanisterLifecycle {
#[must_use]
pub const fn label(self) -> &'static str {
match self {
Self::Claimed => "claimed",
Self::Idle => "idle",
Self::Reconciling => "reconciling",
Self::Retained => "retained",
Self::Store => "store",
Self::Workload => "workload",
}
}
}
#[derive(Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
#[serde(rename_all = "snake_case")]
pub enum CanisterRuntimeStatus {
Running,
Stopped,
Stopping,
}
#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
pub struct FleetObservation {
pub additional_controlled_cycles: BTreeMap<String, u128>,
pub canisters: BTreeMap<String, Option<LiveCanister>>,
pub estate_funding_domains: BTreeMap<String, EstateFundingDomainObservation>,
#[serde(with = "u128_text")]
pub ledger_fee_cycles: u128,
#[serde(with = "u128_text")]
pub operator_cycles: u128,
pub protocol_ready: BTreeMap<String, bool>,
}
#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
#[serde(deny_unknown_fields)]
pub struct EstateFundingDomainObservation {
#[serde(with = "option_u128_text")]
pub balance_cycles: Option<u128>,
pub cycles_ledger: String,
pub pool: Option<EstatePoolInventoryObservation>,
pub root_principal: Option<String>,
}
#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
#[serde(deny_unknown_fields)]
pub struct EstatePoolInventoryObservation {
pub assets: Vec<EstatePoolAssetObservation>,
pub maximum_size: u32,
pub minimum_size: u32,
pub pending_creation: Option<EstatePoolPendingCreationObservation>,
#[serde(with = "u128_text")]
pub readiness_floor_cycles: u128,
#[serde(with = "u128_text")]
pub creation_execution_margin_cycles: u128,
}
#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
#[serde(deny_unknown_fields)]
pub struct EstatePoolAssetObservation {
pub creation_receipt: Option<EstatePoolCreationReceiptObservation>,
#[serde(with = "u128_text")]
pub cycles: u128,
pub lifecycle: EstatePoolAssetLifecycle,
pub origin: EstatePoolAssetOrigin,
pub principal: String,
}
#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
#[serde(deny_unknown_fields)]
pub struct EstatePoolCreationReceiptObservation {
pub block_index: u64,
pub operation_id: String,
pub cycles_ledger: String,
#[serde(with = "u128_text")]
pub ledger_amount_cycles: u128,
#[serde(with = "u128_text")]
pub ledger_fee_cycles: u128,
#[serde(with = "u128_text")]
pub readiness_floor_cycles: u128,
#[serde(with = "u128_text")]
pub creation_execution_margin_cycles: u128,
#[serde(with = "u128_text")]
pub management_creation_fee_cycles: u128,
#[serde(with = "option_u128_text")]
pub first_observed_cycles: Option<u128>,
}
#[derive(Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
#[serde(rename_all = "snake_case")]
pub enum EstatePoolAssetOrigin {
InfrastructureStore,
Created,
Imported,
Recycled,
}
#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
#[serde(deny_unknown_fields)]
pub struct PoolFundingAuthority {
pub root: String,
pub lifecycle: EstatePoolAssetLifecycle,
}
#[derive(Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
#[serde(rename_all = "snake_case")]
pub enum EstatePoolAssetLifecycle {
Claimed,
Failed,
HandingOff,
PendingReset,
Ready,
Recycling,
Workload,
}
#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
#[serde(deny_unknown_fields)]
pub struct EstatePoolPendingCreationObservation {
pub attempt_count: u32,
#[serde(with = "option_u128_text")]
pub available_cycles: Option<u128>,
#[serde(with = "u128_text")]
pub creation_amount_cycles: u128,
pub created_principal: Option<String>,
pub diagnostic: Option<EstatePoolCreationDiagnostic>,
pub last_attempt_at_ns: Option<u64>,
pub operation_id: String,
#[serde(with = "option_u128_text")]
pub required_cycles: Option<u128>,
pub retry_at_ns: Option<u64>,
#[serde(with = "option_u128_text")]
pub shortfall_cycles: Option<u128>,
pub uncertain_result: bool,
}
#[derive(Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
#[serde(rename_all = "snake_case")]
pub enum EstatePoolCreationDiagnostic {
FundingRequired,
LedgerCreationFailed,
LedgerRejected,
UnresolvedAfterLedgerWindow,
}
#[derive(Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
#[serde(rename_all = "snake_case")]
pub enum DesiredPresence {
Absent,
Present,
}
#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
#[serde(deny_unknown_fields)]
pub struct DrainAuthority {
pub candid: String,
pub method: String,
pub destination: String,
pub maximum_execution_burn_cycles: String,
}
#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
#[serde(deny_unknown_fields)]
pub struct DesiredCanister {
#[serde(default)]
pub canic_init: Option<DesiredCanisterInit>,
#[serde(default)]
pub controller_canisters: Vec<String>,
pub controllers: Vec<String>,
pub drain: Option<DrainAuthority>,
pub initial_cycles: String,
pub init_arg: Option<String>,
pub init_candid: Option<String>,
pub kind: DesiredCanisterKind,
pub minimum_cycles: String,
pub name: String,
pub parent: Option<String>,
pub presence: DesiredPresence,
pub principal: Option<String>,
#[serde(skip)]
pub(crate) protocol_binding: Option<crate::protocol_binding::RegistryProtocolBinding>,
pub replace: bool,
pub subnet: String,
pub wasm: Option<String>,
}
#[derive(Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
#[serde(rename_all = "snake_case")]
pub enum DesiredCanisterKind {
Auxiliary,
Component,
Coordinator,
Pool,
Root,
Store,
}
#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
#[serde(rename_all = "snake_case", tag = "role")]
pub enum DesiredCanisterInit {
Coordinator,
Root { root: String },
Store { root: String },
}
#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
#[serde(deny_unknown_fields)]
pub(crate) struct DesiredProtocolStep {
pub canister: String,
pub candid: String,
pub command_args: String,
pub command_method: String,
pub expected_status: String,
pub maximum_execution_burn_cycles: String,
pub name: String,
pub status_args: String,
pub status_method: String,
}
#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
#[serde(deny_unknown_fields)]
pub struct DesiredFleet {
#[serde(default)]
pub bootstrap: Option<DesiredFleetBootstrap>,
pub canisters: Vec<DesiredCanister>,
pub cycles_ledger: String,
pub environment: String,
pub fleet: String,
pub ledger_fee_cycles: String,
pub management_creation_fee_cycles: String,
pub material_cycle_threshold: String,
pub maximum_observation_burn_cycles: String,
pub maximum_stalled_observations: u32,
pub maximum_update_burn_cycles: String,
pub operator: String,
#[serde(default)]
pub protocol: Option<DesiredFleetProtocol>,
#[serde(skip)]
pub(crate) protocol_steps: Vec<DesiredProtocolStep>,
pub schema_version: u16,
pub treasury: String,
}
#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
#[serde(deny_unknown_fields)]
pub struct DesiredFleetBootstrap {
#[serde(default)]
pub admission_identity_origin: Option<String>,
pub admission: canic_core::ids::FleetAdmissionPolicyTemplate,
pub app: canic_core::ids::AppId,
pub canonical_network_id: canic_core::ids::CanonicalNetworkId,
pub component_deployment_configuration:
canic_core::control_plane_support::config::ComponentDeploymentConfiguration,
pub coordinator: String,
pub coordinator_subnet: canic_core::ids::SubnetId,
pub fleet_id: canic_core::ids::FleetId,
pub fresh_estate: bool,
pub release_build_id: canic_core::ids::ReleaseBuildId,
pub root_funding: Option<canic_core::ids::FleetCoordinatorRootFundingPolicy>,
pub roots: Vec<DesiredFleetBootstrapRoot>,
}
#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
#[serde(deny_unknown_fields)]
pub struct DesiredFleetBootstrapRoot {
pub canister_pool_imports: Vec<String>,
pub component_admissions: Vec<canic_core::ids::ComponentSpecAdmission>,
pub component_topology_digest: canic_core::ids::ComponentTopologyDigest,
pub funding: canic_core::ids::FleetSubnetRootFundingAuthority,
pub limits: canic_core::ids::FleetSubnetRootLimits,
pub placement_subnet: canic_core::ids::SubnetId,
pub root: String,
pub store: String,
}
#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
#[serde(deny_unknown_fields)]
pub struct DesiredFleetProtocol {
pub app_config: String,
pub component_group_placements: Vec<DesiredComponentGroupPlacement>,
pub coordinator_candid: String,
pub root_candid: String,
pub store_candid: String,
}
#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
#[serde(deny_unknown_fields)]
pub struct DesiredComponentGroupPlacement {
pub deployment: String,
pub ordinal: u32,
pub root: String,
}
#[derive(Clone, Debug, Default, Eq, PartialEq)]
pub struct DesiredFleetArtifacts {
pub continuation: Option<FleetEnsureContinuationAuthority>,
pub drain_candid_sha256_by_canister: BTreeMap<String, String>,
pub init_arg_sha256_by_canister: BTreeMap<String, String>,
pub init_candid_sha256_by_canister: BTreeMap<String, String>,
pub wasm_sha256_by_canister: BTreeMap<String, String>,
pub startup_funding_by_root: BTreeMap<String, StartupFundingRequirement>,
pub(crate) protocol_by_step: BTreeMap<String, ProtocolArtifactDigests>,
}
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct StartupFundingRequirement {
pub maximum_continuation_steps: u32,
pub minimum_native_cycles: u128,
pub unfunded_role: Option<StartupRoleShortfall>,
}
#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
#[serde(deny_unknown_fields)]
pub struct StartupRoleShortfall {
pub role: canic_core::ids::CanisterRole,
#[serde(with = "u128_text")]
pub cycles: u128,
}
#[derive(Clone, Debug, Default, Eq, PartialEq)]
pub(crate) struct ProtocolArtifactDigests {
pub candid_sha256: String,
pub command_args_sha256: String,
pub expected_status_sha256: String,
pub status_args_sha256: String,
}
#[derive(Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
pub struct CanisterCyclePolicy {
#[serde(with = "u128_text")]
pub initial_cycles: u128,
#[serde(with = "u128_text")]
pub minimum_cycles: u128,
}
#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
#[serde(deny_unknown_fields)]
pub struct ReinstallHistoryWitness {
pub prior_module_sha256: String,
pub authority: RootManagementBinding,
pub candid: String,
pub candid_sha256: String,
#[serde(deserialize_with = "serialization::required_option")]
pub replacement: Option<Box<ReinstallRootWitnessRecord>>,
}
#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
#[serde(deny_unknown_fields)]
pub struct ReinstallRootWitnessRecord {
pub authority: RootManagementBinding,
pub candid: String,
pub candid_sha256: String,
}
#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
#[serde(rename_all = "snake_case", tag = "kind")]
pub enum EnsureAction {
SealAuthority {
candid: String,
candid_sha256: String,
authority_kind: DesiredCanisterKind,
name: String,
principal: String,
},
Create {
controller_canisters: Vec<String>,
controllers: Vec<String>,
created_at_time: u64,
ledger: String,
name: String,
#[serde(with = "u128_text")]
requested_initial_cycles: u128,
subnet: String,
},
Delete {
#[serde(with = "u128_text")]
maximum_remaining_cycles: u128,
name: String,
principal: String,
},
Fund {
#[serde(deserialize_with = "serialization::required_option")]
pool_funding: Option<PoolFundingAuthority>,
#[serde(with = "u128_text")]
amount: u128,
created_at_time: u64,
#[serde(with = "u128_text")]
expected_post_cycles: u128,
#[serde(with = "u128_text")]
funding_deficit_cycles: u128,
#[serde(with = "u128_text")]
funding_margin_cycles: u128,
ledger: String,
name: String,
principal: String,
},
FundEstate {
#[serde(with = "u128_text")]
amount: u128,
created_at_time: u64,
#[serde(with = "u128_text")]
expected_post_cycles: u128,
ledger: String,
#[serde(with = "u128_text")]
ledger_fee_cycles: u128,
name: String,
principal: String,
},
Install {
canic_init: Option<DesiredCanisterInit>,
#[serde(deserialize_with = "serialization::required_option")]
reinstall_witness: Option<Box<ReinstallHistoryWitness>>,
init_arg: Option<String>,
init_arg_sha256: Option<String>,
init_candid: Option<String>,
init_candid_sha256: Option<String>,
mode: InstallMode,
name: String,
principal: String,
wasm: String,
wasm_sha256: String,
},
FleetProtocol {
action: Box<CurrentFleetProtocolAction>,
candid: String,
candid_sha256: String,
#[serde(with = "u128_text")]
maximum_execution_burn_cycles: u128,
name: String,
principal: String,
},
Protocol {
candid: String,
candid_sha256: String,
command_args: String,
command_args_sha256: String,
command_method: String,
expected_status: String,
expected_status_sha256: String,
#[serde(with = "u128_text")]
maximum_execution_burn_cycles: u128,
name: String,
principal: String,
status_args: String,
status_args_sha256: String,
status_method: String,
},
SetControllers {
controller_canisters: Vec<String>,
controllers: Vec<String>,
name: String,
principal: String,
},
Start {
name: String,
principal: String,
},
Stop {
name: String,
principal: String,
},
Transfer {
#[serde(with = "u128_text")]
amount: u128,
candid: String,
candid_sha256: String,
destination: String,
#[serde(with = "u128_text")]
maximum_execution_burn_cycles: u128,
method: String,
name: String,
principal: String,
},
}
impl EnsureAction {
pub(crate) fn fixture_publication_attempt_limit(&self) -> Option<u32> {
if let Self::FleetProtocol { action, .. } = self {
match action.as_ref() {
CurrentFleetProtocolAction::PrepareStoreFixture {
maximum_attempts, ..
}
| CurrentFleetProtocolAction::PublishStoreFixtureChunk {
maximum_attempts, ..
} => {
return Some(*maximum_attempts);
}
_ => {}
}
}
None
}
#[must_use]
pub fn name(&self) -> &str {
match self {
Self::SealAuthority { name, .. }
| Self::Create { name, .. }
| Self::Delete { name, .. }
| Self::FleetProtocol { name, .. }
| Self::Fund { name, .. }
| Self::FundEstate { name, .. }
| Self::Install { name, .. }
| Self::Protocol { name, .. }
| Self::SetControllers { name, .. }
| Self::Start { name, .. }
| Self::Stop { name, .. }
| Self::Transfer { name, .. } => name,
}
}
}
#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
#[serde(rename_all = "snake_case", tag = "kind")]
#[expect(
clippy::large_enum_variant,
reason = "each exact typed action is boxed by EnsureAction before durable retention"
)]
pub enum CurrentFleetProtocolAction {
ObservePoolReadiness {
minimum_ready: u32,
readiness_floor: canic_core::cdk::types::Cycles,
},
MaintainPoolReadiness {
maximum_updates: u32,
minimum_ready: u32,
readiness_floor: canic_core::cdk::types::Cycles,
},
ReconcilePoolAsset {
request: canic_core::dto::pool::PoolCanisterRequest,
minimum_cycles: canic_core::cdk::types::Cycles,
},
ActivateRegistry {
expected_registry: canic_core::dto::fleet_registry::FleetRegistry,
expected_version: canic_core::dto::fleet_registry::FleetRegistryVersion,
request: canic_core::dto::fleet_registry::FleetRegistryActivationRequest,
},
ActivateRegistryMirror {
expected: canic_core::dto::fleet_registry::FleetSubnetRootRegistryMirrorActivationResponse,
request: canic_core::dto::fleet_registry::FleetSubnetRootRegistrySyncRequest,
},
AdoptStore {
request: canic_core::dto::fleet_subnet_root::FleetSubnetWasmStoreAdoptionRequest,
},
BootstrapStore {
expected: canic_core::dto::root_store::RootStoreBootstrapResponse,
request: canic_core::dto::root_store::RootStoreBootstrapRequest,
},
JoinRoot {
expected_registry: canic_core::dto::fleet_registry::FleetRegistry,
expected_version: canic_core::dto::fleet_registry::FleetRegistryVersion,
request: canic_core::dto::fleet_registry::FleetSubnetRootJoinRequest,
},
PrepareStoreFixture {
maximum_attempts: u32,
request: canic_core::dto::root_store::RootStoreFixturePrepareRequest,
source: canic_core::dto::root_store::RootStoreFixture,
store: candid::Principal,
},
PublishStoreFixtureChunk {
maximum_attempts: u32,
request: canic_core::dto::fixture_provisioning::FixtureChunkUpload,
expected: canic_core::dto::fixture_provisioning::FixtureSourceStatus,
source_bytes: u64,
},
PrepareComponentRegistry {
expected: canic_core::dto::component_registry::RootComponentRegistryStatusResponse,
request: canic_core::dto::component_registry::RootComponentRegistryPreparationRequest,
},
ProvisionComponents {
plan_hash: [u8; 32],
request: canic_core::dto::component_provisioning::FleetComponentProvisioningPrepareRequest,
},
PublishStoreChunk {
request: canic_control_plane::dto::template::TemplateChunkInput,
},
SynchronizeRegistry {
expected: canic_core::dto::fleet_registry::FleetSubnetRootRegistrySyncResponse,
request: canic_core::dto::fleet_registry::FleetSubnetRootRegistrySyncRequest,
},
}
impl CurrentFleetProtocolAction {
pub(crate) fn publication_bytes(&self) -> Option<&[u8]> {
match self {
Self::PublishStoreChunk { request } => Some(&request.bytes),
Self::PublishStoreFixtureChunk { request, .. } => Some(&request.bytes),
_ => None,
}
}
pub(crate) const fn publication_bytes_mut(&mut self) -> Option<&mut Vec<u8>> {
match self {
Self::PublishStoreChunk { request } => Some(&mut request.bytes),
Self::PublishStoreFixtureChunk { request, .. } => Some(&mut request.bytes),
_ => None,
}
}
#[must_use]
pub const fn target_kind(&self) -> DesiredCanisterKind {
match self {
Self::ActivateRegistry { .. }
| Self::JoinRoot { .. }
| Self::ProvisionComponents { .. } => DesiredCanisterKind::Coordinator,
Self::ActivateRegistryMirror { .. }
| Self::ReconcilePoolAsset { .. }
| Self::MaintainPoolReadiness { .. }
| Self::ObservePoolReadiness { .. }
| Self::AdoptStore { .. }
| Self::BootstrapStore { .. }
| Self::PrepareStoreFixture { .. }
| Self::PrepareComponentRegistry { .. }
| Self::SynchronizeRegistry { .. } => DesiredCanisterKind::Root,
Self::PublishStoreChunk { .. } | Self::PublishStoreFixtureChunk { .. } => {
DesiredCanisterKind::Store
}
}
}
#[must_use]
pub const fn operation_id(&self) -> Option<[u8; 32]> {
match self {
Self::ActivateRegistry { .. }
| Self::ReconcilePoolAsset { .. }
| Self::MaintainPoolReadiness { .. }
| Self::ObservePoolReadiness { .. }
| Self::JoinRoot { .. }
| Self::PrepareComponentRegistry { .. }
| Self::PrepareStoreFixture { .. }
| Self::PublishStoreChunk { .. }
| Self::PublishStoreFixtureChunk { .. } => None,
Self::ActivateRegistryMirror { request, .. }
| Self::SynchronizeRegistry { request, .. } => Some(request.operation_id),
Self::AdoptStore { request } => Some(request.operation_id),
Self::BootstrapStore { request, .. } => Some(request.operation_id),
Self::ProvisionComponents { request, .. } => Some(request.operation_id),
}
}
}
#[derive(Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
#[serde(rename_all = "snake_case")]
pub enum InstallMode {
Install,
Reinstall,
}
#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
#[serde(deny_unknown_fields)]
pub struct CanisterPlan {
pub actions: Vec<EnsureAction>,
pub disposition: CanisterDisposition,
pub name: String,
#[serde(with = "u128_text")]
pub observed_cycles: u128,
#[serde(deserialize_with = "serialization::required_option")]
pub principal: Option<String>,
}
#[derive(Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
#[serde(rename_all = "snake_case")]
pub enum CanisterDisposition {
Create,
Delete,
Replace,
Reinstall,
Reuse,
}
#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
pub struct CycleConservation {
pub estate_funding_domains: Vec<EstateFundingDomainPlan>,
#[serde(with = "u128_text")]
pub expected_post_operation_cycles: u128,
#[serde(with = "u128_text")]
pub maximum_execution_burn_cycles: u128,
#[serde(with = "u128_text")]
pub maximum_new_funding_cycles: u128,
#[serde(with = "u128_text")]
pub maximum_operator_debit_cycles: u128,
#[serde(with = "u128_text")]
pub maximum_unavoidable_fee_cycles: u128,
#[serde(with = "u128_text")]
pub observed_controlled_cycles: u128,
#[serde(with = "u128_text")]
pub retained_in_reused_canisters_cycles: u128,
#[serde(with = "u128_text")]
pub scheduled_transfer_cycles: u128,
}
#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
#[serde(deny_unknown_fields)]
pub struct EstateFundingDomainPlan {
pub allocated_workloads: u32,
#[serde(with = "option_u128_text")]
pub available_cycles: Option<u128>,
pub available_pool_slots: u32,
#[serde(with = "u128_text")]
pub creation_amount_cycles: u128,
pub cycles_ledger: String,
#[serde(with = "u128_text")]
pub creation_execution_margin_cycles: u128,
#[serde(with = "u128_text")]
pub readiness_floor_cycles: u128,
pub eligible_ready_pool_assets: u32,
pub initial_pool_assets: Vec<String>,
#[serde(with = "u128_text")]
pub ledger_fee_cycles: u128,
#[serde(with = "u128_text")]
pub management_creation_fee_cycles: u128,
#[serde(with = "u128_text")]
pub maximum_creation_debit_cycles: u128,
#[serde(with = "u128_text")]
pub maximum_creation_fee_cycles: u128,
#[serde(with = "u128_text")]
pub maximum_funding_cycles: u128,
pub occupied_pool_assets: u32,
pub pending_creation_count: u32,
pub pending_creation: Option<EstatePoolPendingCreationObservation>,
pub pool_maximum_size: u32,
pub planned_initial_workloads: u32,
pub required_creation_count: u32,
pub root: String,
pub root_principal: Option<String>,
#[serde(with = "u128_text")]
pub shortfall_cycles: u128,
}
#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
#[serde(deny_unknown_fields)]
pub struct EstateFundingRequiredRecord {
pub attempt_count: Option<u32>,
#[serde(with = "u128_text")]
pub available_cycles: u128,
#[serde(with = "u128_text")]
pub creation_amount_cycles: u128,
pub cycles_ledger: String,
#[serde(with = "u128_text")]
pub creation_execution_margin_cycles: u128,
#[serde(with = "u128_text")]
pub readiness_floor_cycles: u128,
#[serde(with = "u128_text")]
pub ledger_fee_cycles: u128,
#[serde(with = "u128_text")]
pub management_creation_fee_cycles: u128,
#[serde(with = "u128_text")]
pub maximum_creation_debit_cycles: u128,
#[serde(with = "u128_text")]
pub maximum_creation_fee_cycles: u128,
pub operation_id: String,
pub pending_creation_operation_id: Option<String>,
pub plan_sha256: String,
pub required_creation_count: u32,
pub root: String,
pub root_principal: String,
pub last_attempt_at_ns: Option<u64>,
pub retry_at_ns: Option<u64>,
#[serde(with = "u128_text")]
pub shortfall_cycles: u128,
}
#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
#[serde(deny_unknown_fields)]
pub struct ActualCycleConservation {
#[serde(with = "u128_text")]
pub estate_funding_cycles: u128,
#[serde(with = "u128_text")]
pub exact_estate_creation_fee_cycles: u128,
#[serde(with = "u128_text")]
pub exact_unavoidable_fee_cycles: u128,
#[serde(with = "u128_text")]
pub final_controlled_cycles: u128,
#[serde(with = "u128_text")]
pub observed_net_cycle_debit_cycles: u128,
#[serde(with = "u128_text")]
pub observed_starting_cycles: u128,
#[serde(with = "u128_text")]
pub observed_net_cycle_credit_cycles: u128,
#[serde(with = "u128_text")]
pub operator_debit_cycles: u128,
#[serde(with = "u128_text")]
pub received_new_funding_cycles: u128,
}
pub(crate) const fn creation_observation_burn(observation: u128, update: u128) -> Option<u128> {
observation.checked_add(update)
}
pub(crate) const fn create_balance_is_terminal(
actual_cycles: Option<u128>,
requested_initial_cycles: u128,
maximum_observation_burn_cycles: u128,
) -> bool {
let Some(actual_cycles) = actual_cycles else {
return false;
};
requested_initial_cycles.saturating_sub(actual_cycles) <= maximum_observation_burn_cycles
}
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct ReviewedDesiredFleetRecord {
desired: DesiredFleet,
}
impl ReviewedDesiredFleetRecord {
pub(crate) fn capture(desired: &DesiredFleet) -> Self {
Self {
desired: desired.clone(),
}
}
#[must_use]
pub const fn desired(&self) -> &DesiredFleet {
&self.desired
}
#[must_use]
pub fn into_desired(self) -> DesiredFleet {
self.desired
}
}
#[derive(Deserialize, Serialize)]
struct ReviewedDesiredFleetRecordProjection {
desired: DesiredFleet,
protocol_steps: Vec<DesiredProtocolStep>,
}
impl Serialize for ReviewedDesiredFleetRecord {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: serde::Serializer,
{
ReviewedDesiredFleetRecordProjection {
desired: self.desired.clone(),
protocol_steps: self.desired.protocol_steps.clone(),
}
.serialize(serializer)
}
}
impl<'de> Deserialize<'de> for ReviewedDesiredFleetRecord {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: serde::Deserializer<'de>,
{
let mut projection = ReviewedDesiredFleetRecordProjection::deserialize(deserializer)?;
projection.desired.protocol_steps = projection.protocol_steps;
Ok(Self {
desired: projection.desired,
})
}
}
#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
#[serde(deny_unknown_fields)]
pub struct FleetEnsurePlan {
#[serde(deserialize_with = "serialization::required_option")]
pub recovery_review: Option<Box<FleetRecoveryReview>>,
#[serde(deserialize_with = "serialization::required_option")]
pub reinstall: Option<Box<FleetReinstallRecord>>,
#[serde(deserialize_with = "serialization::required_option")]
pub continuation: Option<FleetEnsureContinuationAuthority>,
pub canisters: Vec<CanisterPlan>,
pub conservation: CycleConservation,
pub desired_sha256: String,
pub environment: String,
pub fleet: String,
pub operation_id: String,
pub plan_sha256: String,
pub planned_at_time: u64,
pub protocol_actions: Vec<EnsureAction>,
#[serde(deserialize_with = "serialization::required_option")]
pub root_start_authority: Option<Box<RetainedRootStartAuthorityRecord>>,
pub root_reinstall_bindings: Vec<RootManagementBinding>,
#[serde(deserialize_with = "serialization::required_option")]
pub reviewed_desired: Option<Box<ReviewedDesiredFleetRecord>>,
pub schema_version: u16,
pub scope: FleetEnsurePlanScope,
#[serde(deserialize_with = "serialization::required_option")]
pub terminal_inventory_operation_id: Option<String>,
}
#[derive(Clone, Copy, Debug, Default, Deserialize, Eq, PartialEq, Serialize)]
#[serde(rename_all = "snake_case")]
pub enum FleetEnsurePlanScope {
#[default]
Full,
ReinstallPreparation,
RootReinstallPrerequisite,
RootStartPrerequisite,
}
impl FleetEnsurePlanScope {
#[must_use]
pub const fn as_str(self) -> &'static str {
match self {
Self::Full => "full",
Self::ReinstallPreparation => "reinstall_preparation",
Self::RootReinstallPrerequisite => "root_reinstall_prerequisite",
Self::RootStartPrerequisite => "root_start_prerequisite",
}
}
}
#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
#[serde(deny_unknown_fields)]
pub struct FleetReinstallRecord {
#[serde(deserialize_with = "serialization::required_option")]
pub target_artifacts_sha256: Option<String>,
#[serde(deserialize_with = "serialization::required_option")]
pub source: Option<Box<FleetReinstallSourceRecord>>,
#[serde(deserialize_with = "serialization::required_option")]
pub activation_reset: Option<Box<FleetActivationResetRecord>>,
pub operation_id: String,
pub source_operation_id: String,
pub authorities: Vec<RootManagementBinding>,
pub assets: Vec<FleetReinstallAssetRecord>,
}
#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
#[serde(deny_unknown_fields)]
pub struct FleetReinstallSourceRecord {
#[serde(deserialize_with = "serialization::required_option")]
pub terminal_retirement: Option<Box<FleetTerminalRetirementRecord>>,
pub reviewed_desired: ReviewedDesiredFleetRecord,
pub wasm_sha256_by_canister: BTreeMap<String, String>,
pub candid_sha256_by_path: BTreeMap<String, String>,
}
#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
#[serde(deny_unknown_fields)]
pub struct FleetTerminalSourceRecord {
pub operation_id: String,
pub plan_sha256: String,
pub plan_document_sha256: String,
pub journal_document_sha256: String,
pub state_document_sha256: String,
pub phase_document_sha256: BTreeMap<String, String>,
}
#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
#[serde(deny_unknown_fields)]
pub struct FleetTerminalRetirementRecord {
pub source: FleetTerminalSourceRecord,
pub conservation: FleetRetirementConservationRecord,
}
#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
#[serde(deny_unknown_fields)]
pub struct FleetActivationSourceRecord {
pub infrastructure: Vec<RootManagementBinding>,
pub operator: String,
pub cycles_ledger: String,
#[serde(with = "u128_text")]
pub initial_controlled_cycles: u128,
#[serde(with = "u128_text")]
pub maximum_execution_burn_cycles: u128,
#[serde(with = "u128_text_map")]
pub initial_estate_funding_cycles_by_root: BTreeMap<String, u128>,
pub operation_id: String,
pub plan_sha256: String,
pub plan_document_sha256: String,
pub journal_document_sha256: String,
pub state_document_sha256: String,
pub provisioning: EnsureAction,
pub registry_preparations: Vec<EnsureAction>,
pub stores: Vec<FleetProtocolReadRecord>,
}
#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
#[serde(deny_unknown_fields)]
pub struct FleetActivationResetRecord {
#[serde(deserialize_with = "serialization::required_option")]
pub preparation: Option<ActivationPreparationEvidenceRecord>,
pub source: FleetActivationSourceRecord,
pub roots: Vec<RootActivationResetRecord>,
}
#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
#[serde(deny_unknown_fields)]
pub struct ActivationPreparationEvidenceRecord {
pub plan_document_sha256: String,
pub journal_document_sha256: String,
}
#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
#[serde(deny_unknown_fields)]
pub struct ActivationResetAdoptionRecord {
pub source_plan_sha256: String,
pub source_journal_sha256: String,
pub source_state_sha256: String,
pub replacement_plan_sha256: String,
pub replacement_journal_sha256: String,
pub complete: bool,
}
#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
#[serde(deny_unknown_fields)]
pub struct FleetProtocolReadRecord {
pub candid: String,
pub candid_sha256: String,
pub principal: String,
}
#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
#[serde(deny_unknown_fields)]
pub struct RootActivationResetRecord {
pub root: String,
pub activation_operation_id: [u8; 32],
pub inventory_hash: [u8; 32],
pub provisioning_receipt_hash: [u8; 32],
pub component_count: u32,
pub managed_descendants: u32,
}
#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
#[serde(deny_unknown_fields)]
pub struct FleetReinstallAssetRecord {
pub controllers: Vec<String>,
#[serde(deserialize_with = "serialization::required_option")]
pub module_sha256: Option<String>,
pub principal: String,
pub root: String,
pub subnet: String,
}
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct RootManagementCanisterObservation {
pub live: LiveCanister,
pub name: String,
pub subnet: String,
}
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct RootManagementObservation {
pub operator_cycles: u128,
pub roots: BTreeMap<String, RootManagementCanisterObservation>,
}
#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
#[serde(deny_unknown_fields)]
pub struct RootManagementBinding {
pub controllers: Vec<String>,
pub name: String,
pub module_sha256: String,
pub principal: String,
pub subnet: String,
}
#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
#[serde(deny_unknown_fields)]
pub struct RetainedRootStartAuthorityRecord {
pub authority_sha256: String,
pub environment: String,
pub fleet: String,
pub fleet_id: canic_core::ids::FleetId,
pub roots: Vec<RootManagementBinding>,
pub schema_version: u16,
}
impl RetainedRootStartAuthorityRecord {
pub(crate) fn seal(&mut self) {
self.authority_sha256 = self.expected_sha256();
}
pub(crate) fn has_valid_digest(&self) -> bool {
self.authority_sha256 == self.expected_sha256()
}
fn expected_sha256(&self) -> String {
let mut canonical = self.clone();
canonical.authority_sha256.clear();
let bytes = serde_json::to_vec(&canonical)
.expect("retained Root-start authority is JSON serializable");
let mut framed = b"canic:fleet-ensure:root-start-authority:v1".to_vec();
framed.extend_from_slice(&u64::try_from(bytes.len()).unwrap_or(u64::MAX).to_be_bytes());
framed.extend_from_slice(&bytes);
canic_core::cdk::utils::hash::sha256_hex(&framed)
}
}
#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
#[serde(rename_all = "snake_case")]
pub enum EffectState {
Applied,
Intent,
Issued,
}
#[derive(Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
#[serde(rename_all = "snake_case")]
pub enum FleetEnsureCompletion {
Converged,
Prepared,
InProgress,
ReplanRequired,
}
#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
#[serde(deny_unknown_fields)]
pub struct EffectRecord {
pub maintenance_attempts: u32,
pub publication_attempts: u32,
pub action_sha256: String,
#[serde(deserialize_with = "serialization::required_option")]
pub created_principal: Option<String>,
#[serde(with = "option_u128_text")]
pub destination_post_cycles: Option<u128>,
#[serde(with = "option_u128_text")]
pub destination_pre_cycles: Option<u128>,
#[serde(with = "option_u128_text")]
pub post_cycles: Option<u128>,
#[serde(with = "option_u128_text")]
pub pre_cycles: Option<u128>,
#[serde(deserialize_with = "serialization::required_option")]
pub pre_canister_version: Option<u64>,
#[serde(deserialize_with = "serialization::required_option")]
pub progress_identity: Option<String>,
#[serde(deserialize_with = "serialization::required_option")]
pub receipt: Option<String>,
pub state: EffectState,
}
impl EffectRecord {
pub(in crate::fleet_ensure) fn attempts_match(&self, action: &EnsureAction) -> bool {
let publication = match action.fixture_publication_attempt_limit() {
Some(limit) => limit > 0 && self.publication_attempts <= limit,
None => self.publication_attempts == 0,
};
let maintenance = match action {
EnsureAction::FleetProtocol { action, .. } => match action.as_ref() {
CurrentFleetProtocolAction::MaintainPoolReadiness {
maximum_updates, ..
} => self.maintenance_attempts <= *maximum_updates,
_ => self.maintenance_attempts == 0,
},
_ => self.maintenance_attempts == 0,
};
publication && maintenance
}
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub struct RetirementTransferBalances {
pub destination_after: u128,
pub destination_before: u128,
pub maximum_execution_burn: u128,
pub source_after: u128,
pub source_before: u128,
pub transfer_amount: u128,
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum RetirementTransferReconciliation {
Conserved {
destination_credit: u128,
source_debit: u128,
},
Pending,
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum RetirementTransferInvariantError {
ArithmeticOverflow,
BalanceMovedInInvalidDirection,
Unbalanced {
destination_credit: u128,
maximum_source_debit: u128,
source_debit: u128,
},
}
pub fn reconcile_retirement_transfer(
balances: RetirementTransferBalances,
) -> Result<RetirementTransferReconciliation, RetirementTransferInvariantError> {
let source_debit = balances
.source_before
.checked_sub(balances.source_after)
.ok_or(RetirementTransferInvariantError::BalanceMovedInInvalidDirection)?;
let destination_credit = balances
.destination_after
.checked_sub(balances.destination_before)
.ok_or(RetirementTransferInvariantError::BalanceMovedInInvalidDirection)?;
let maximum_source_debit = balances
.transfer_amount
.checked_add(balances.maximum_execution_burn)
.ok_or(RetirementTransferInvariantError::ArithmeticOverflow)?;
if source_debit == 0 && destination_credit == 0 {
return Ok(RetirementTransferReconciliation::Pending);
}
if source_debit >= balances.transfer_amount
&& source_debit <= maximum_source_debit
&& destination_credit == balances.transfer_amount
{
return Ok(RetirementTransferReconciliation::Conserved {
destination_credit,
source_debit,
});
}
Err(RetirementTransferInvariantError::Unbalanced {
destination_credit,
maximum_source_debit,
source_debit,
})
}
#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
#[serde(deny_unknown_fields)]
pub struct FleetEnsureStateRecord {
#[serde(deserialize_with = "serialization::required_option")]
pub active_registry: Option<canic_core::dto::fleet_registry::FleetRegistry>,
pub completed_reinstall_action_sha256: BTreeMap<String, String>,
#[serde(deserialize_with = "serialization::required_option")]
pub completed_reinstall_operation_id: Option<String>,
pub completed_reinstalls: BTreeMap<String, u64>,
pub fleet: String,
pub pending_principals: BTreeMap<String, String>,
pub principals: BTreeMap<String, String>,
pub retained_cycles_by_principal: BTreeMap<String, u128>,
pub schema_version: u16,
pub topology: BTreeMap<String, FleetEnsureTopologyRecord>,
}
#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
#[serde(deny_unknown_fields)]
pub struct FleetEnsureTopologyRecord {
pub kind: DesiredCanisterKind,
#[serde(deserialize_with = "serialization::required_option")]
pub module_hash: Option<String>,
#[serde(deserialize_with = "serialization::required_option")]
pub parent: Option<String>,
#[serde(deserialize_with = "serialization::required_option")]
pub protocol_binding: Option<crate::protocol_binding::RegistryProtocolBinding>,
#[serde(deserialize_with = "serialization::required_option")]
pub role: Option<String>,
}
#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
#[serde(deny_unknown_fields)]
pub struct FleetEnsureJournalRecord {
pub funding_observations: BTreeMap<String, funding_observation::FundingObservationReviewRecord>,
pub funding_reviews: Vec<FundingReviewRecord>,
pub successor_phases: Vec<FleetEnsureSuccessorPhaseRecord>,
pub completion: FleetEnsureCompletion,
#[serde(deserialize_with = "serialization::required_option")]
pub estate_funding_required: Option<EstateFundingRequiredRecord>,
pub effects: Vec<EffectRecord>,
pub fleet: String,
#[serde(with = "u128_text")]
pub initial_controlled_cycles: u128,
#[serde(with = "u128_text_map")]
pub initial_estate_funding_cycles_by_root: BTreeMap<String, u128>,
#[serde(with = "u128_text")]
pub initial_operator_cycles: u128,
pub operation_id: String,
pub plan_sha256: String,
pub schema_version: u16,
pub stalled_observations: u32,
}
#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
#[serde(deny_unknown_fields)]
pub struct FleetEnsureContinuationAuthority {
pub fixture_publication_retry_attempts: u32,
pub app_config_sha256: String,
pub application_artifact_union_sha256: String,
pub coordinator_candid_sha256: String,
pub maximum_successor_actions: u32,
pub root_candid_sha256: String,
pub store_candid_sha256: String,
}
#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
#[serde(deny_unknown_fields)]
pub struct FleetEnsureSuccessorPhaseRecord {
#[serde(with = "u128_text")]
pub execution_burn_before_phase: u128,
pub plan_sha256: String,
#[serde(skip)]
pub plan: Option<Box<FleetEnsurePlan>>,
}
#[derive(Clone, Copy, Debug, Eq, PartialEq, Serialize)]
#[serde(rename_all = "snake_case")]
pub enum FleetEnsureSuccessorReviewReason {
AdditionalEffect,
BudgetExceeded,
ChangedInputs,
PhaseBound,
ProtocolAuthority,
}
#[derive(Clone, Debug, Eq, PartialEq, Serialize)]
pub struct FleetEnsureReport {
pub funding_review: Option<FundingReviewRecord>,
pub actual_conservation: Option<ActualCycleConservation>,
pub effects_applied: u32,
pub plan: FleetEnsurePlan,
pub terminal: bool,
}
#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
#[serde(deny_unknown_fields)]
pub struct FundingReviewRecord {
pub action: EnsureAction,
#[serde(deserialize_with = "serialization::required_option")]
pub effect: Option<EffectRecord>,
#[serde(deserialize_with = "serialization::required_option")]
pub operator_mint: Option<operator_mint::OperatorMintReviewRecord>,
pub pause: FundingPauseRecord,
pub review_sha256: String,
}
#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
#[serde(
tag = "kind",
content = "evidence",
rename_all = "snake_case",
deny_unknown_fields
)]
pub enum FundingPauseRecord {
Estate(EstateFundingRequiredRecord),
Native(NativeFundingRequiredRecord),
Operator(operator_mint::InitialOperatorFundingRequiredRecord),
}
impl FundingPauseRecord {
#[must_use]
pub fn root(&self) -> &str {
match self {
Self::Estate(p) => &p.root,
Self::Native(p) => &p.root,
Self::Operator(p) => &p.target,
}
}
#[must_use]
pub fn root_principal(&self) -> &str {
match self {
Self::Estate(p) => &p.root_principal,
Self::Native(p) => &p.root_principal,
Self::Operator(p) => &p.target_principal,
}
}
#[must_use]
pub fn cycles_ledger(&self) -> &str {
match self {
Self::Estate(p) => &p.cycles_ledger,
Self::Native(p) => &p.cycles_ledger,
Self::Operator(p) => &p.cycles_ledger,
}
}
#[must_use]
pub const fn available_cycles(&self) -> u128 {
match self {
Self::Estate(p) => p.available_cycles,
Self::Native(p) => p.available_cycles,
Self::Operator(p) => p.available_cycles,
}
}
#[must_use]
pub const fn shortfall_cycles(&self) -> u128 {
match self {
Self::Estate(p) => p.shortfall_cycles,
Self::Native(p) => p.shortfall_cycles,
Self::Operator(p) => p.shortfall_cycles,
}
}
#[must_use]
pub const fn ledger_fee_cycles(&self) -> u128 {
match self {
Self::Estate(p) => p.ledger_fee_cycles,
Self::Native(p) => p.ledger_fee_cycles,
Self::Operator(p) => p.ledger_fee_cycles,
}
}
}
#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
#[serde(deny_unknown_fields)]
pub struct NativeFundingRequiredRecord {
#[serde(deserialize_with = "serialization::required_option")]
pub observation_quote: Option<funding_observation::FundingQuoteSourceRecord>,
#[serde(with = "u128_text")]
pub available_cycles: u128,
pub cycles_ledger: String,
#[serde(with = "u128_text")]
pub funding_margin_cycles: u128,
#[serde(with = "u128_text")]
pub ledger_fee_cycles: u128,
#[serde(with = "u128_text")]
pub minimum_cycles: u128,
pub operation_id: String,
pub plan_sha256: String,
pub provisioning_action_sha256: String,
pub root: String,
pub root_principal: String,
#[serde(with = "u128_text")]
pub shortfall_cycles: u128,
}
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct NativeFundingObservation {
pub cycles_ledger: String,
pub ledger_fee_cycles: u128,
pub live: LiveCanister,
pub operator_cycles: u128,
}
pub(in crate::fleet_ensure) mod u128_text {
use serde::{Deserialize, Deserializer, Serializer};
pub fn serialize<S>(value: &u128, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
serializer.serialize_str(&value.to_string())
}
pub fn deserialize<'de, D>(deserializer: D) -> Result<u128, D::Error>
where
D: Deserializer<'de>,
{
String::deserialize(deserializer)?
.parse()
.map_err(serde::de::Error::custom)
}
}
pub(crate) mod option_u128_text {
use serde::{Deserialize, Deserializer, Serialize, Serializer};
#[expect(
clippy::ref_option,
reason = "serde(with) requires the serializer to accept the field by reference"
)]
pub fn serialize<S>(value: &Option<u128>, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
value.map(|cycles| cycles.to_string()).serialize(serializer)
}
pub fn deserialize<'de, D>(deserializer: D) -> Result<Option<u128>, D::Error>
where
D: Deserializer<'de>,
{
Option::<String>::deserialize(deserializer)?
.map(|value| value.parse().map_err(serde::de::Error::custom))
.transpose()
}
}
mod u128_text_map {
use std::collections::BTreeMap;
use serde::{Deserialize, Deserializer, Serialize, Serializer};
pub fn serialize<S>(values: &BTreeMap<String, u128>, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
values
.iter()
.map(|(key, value)| (key, value.to_string()))
.collect::<BTreeMap<_, _>>()
.serialize(serializer)
}
pub fn deserialize<'de, D>(deserializer: D) -> Result<BTreeMap<String, u128>, D::Error>
where
D: Deserializer<'de>,
{
BTreeMap::<String, String>::deserialize(deserializer)?
.into_iter()
.map(|(key, value)| {
value
.parse()
.map(|value| (key, value))
.map_err(serde::de::Error::custom)
})
.collect()
}
}
#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
#[serde(deny_unknown_fields)]
pub struct FleetRecoveryReview {
#[serde(with = "u128_text")]
pub base_execution_burn_cycles: u128,
#[serde(with = "u128_text")]
pub continuation_reserve_cycles: u128,
#[serde(with = "u128_text")]
pub whole_continuation_ceiling_cycles: u128,
pub maximum_successor_actions: u32,
pub fixture_publication_retry_attempts: u32,
#[serde(with = "u128_text")]
pub per_step_burn_cycles: u128,
pub startup_funding: Vec<RootStartupFundingForecast>,
pub known_pool_funding: Vec<PoolRecoveryFunding>,
pub discovery: RecoveryDiscovery,
}
#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
#[serde(deny_unknown_fields)]
pub struct RootStartupFundingForecast {
pub root: String,
#[serde(with = "u128_text")]
pub startup_minimum_cycles: u128,
pub maximum_continuation_steps: u32,
#[serde(with = "u128_text")]
pub continuation_allowance_cycles: u128,
#[serde(with = "u128_text")]
pub configured_minimum_cycles: u128,
#[serde(with = "u128_text")]
pub required_native_cycles: u128,
pub reuse_assumption: StartupFundingReuseAssumption,
#[serde(deserialize_with = "serialization::required_option")]
pub unfunded_role: Option<StartupRoleShortfall>,
}
#[derive(Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
#[serde(rename_all = "snake_case")]
pub enum StartupFundingReuseAssumption {
FreshChildrenAndFullPublication,
}
#[derive(Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
#[serde(rename_all = "snake_case")]
pub enum RecoveryDiscovery {
PendingCurrentProtocol,
}
#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
#[serde(deny_unknown_fields)]
pub struct PoolRecoveryFunding {
pub principal: String,
pub root: String,
#[serde(with = "u128_text")]
pub amount_cycles: u128,
#[serde(with = "u128_text")]
pub ledger_fee_cycles: u128,
#[serde(with = "u128_text")]
pub funding_deficit_cycles: u128,
#[serde(with = "u128_text")]
pub funding_margin_cycles: u128,
#[serde(with = "u128_text")]
pub expected_post_cycles: u128,
}
#[derive(Clone, Debug, Eq, PartialEq, Serialize)]
pub struct FleetSuccessorReview {
pub actions: Vec<FleetReviewAction>,
#[serde(with = "u128_text")]
pub maximum_additional_debit_cycles: u128,
pub next_review_command: String,
}
#[derive(Clone, Debug, Eq, PartialEq, Serialize)]
pub struct FleetReviewAction {
pub name: String,
pub principal: Option<String>,
pub kind: String,
}
impl std::fmt::Display for FleetSuccessorReview {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "remaining actions: ")?;
for (index, action) in self.actions.iter().enumerate() {
if index > 0 {
write!(f, ", ")?;
}
write!(
f,
"{} {} ({})",
action.kind,
action.name,
action.principal.as_deref().unwrap_or("not yet created")
)?;
}
write!(
f,
"; maximum additional debit including fees: {} cycles; next review: {}",
self.maximum_additional_debit_cycles, self.next_review_command
)
}
}