canic-core 0.110.42

Canic — a canister orchestration and management toolkit for the Internet Computer
Documentation
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
//! Module: ids::fleet_topology
//!
//! Responsibility: define protected Fleet topology, admission, limit, and binding facts.
//! Does not own: configuration compilation, placement decisions, Registry mutation, or storage.
//! Boundary: these passive cross-layer contracts are validated before authoritative use.

use crate::{
    cdk::types::Cycles,
    ids::{
        CanisterRole, ComponentInstanceId, ComponentSpecId, FleetBinding, ReleaseBuildId,
        ReleaseSetDigest, SubnetId,
    },
};
use candid::{CandidType, Principal};
use serde::{Deserialize, Serialize};
use std::fmt;

///
/// ComponentTopologyDigest
///
/// SHA-256 identity of one canonical root-local Component Topology projection.
///

#[derive(
    CandidType, Clone, Copy, Debug, Deserialize, Eq, Hash, Ord, PartialEq, PartialOrd, Serialize,
)]
#[serde(transparent)]
pub struct ComponentTopologyDigest([u8; 32]);

impl ComponentTopologyDigest {
    #[must_use]
    pub const fn from_bytes(bytes: [u8; 32]) -> Self {
        Self(bytes)
    }

    #[must_use]
    pub const fn as_bytes(&self) -> &[u8; 32] {
        &self.0
    }

    #[must_use]
    pub const fn into_bytes(self) -> [u8; 32] {
        self.0
    }
}

impl fmt::Display for ComponentTopologyDigest {
    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
        for byte in self.0 {
            write!(formatter, "{byte:02x}")?;
        }
        Ok(())
    }
}

///
/// CyclesFundingBudget
///
/// Positive aggregate cycles-funding ceiling applied over one bounded window.
///

#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
#[serde(deny_unknown_fields)]
pub struct CyclesFundingBudget {
    pub window_secs: u64,
    pub maximum_cycles: Cycles,
}

/// Protected physical-topology class that selects the minimum Root funding baseline.
#[derive(CandidType, Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
pub enum FleetFundingProfile {
    #[serde(rename = "single_subnet")]
    SingleSubnet,
    #[serde(rename = "multi_subnet")]
    MultiSubnet,
    #[serde(rename = "preview_multi_subnet")]
    PreviewMultiSubnet,
}

/// Minimum post-grant Coordinator execution reserve established by the 0.108 M0 proof.
pub const COORDINATOR_ROOT_FUNDING_EXECUTION_RESERVE_FLOOR_CYCLES: u128 = 100_000_000;

/// Conservative current-cost reservation for one bounded 16 KiB funding command.
pub const FLEET_ROOT_FUNDING_CALL_RESERVATION_CYCLES: u128 = 42_118_809_000;

/// Minimum Root balance admitted for the Coordinator request and exact-retry path.
pub const FLEET_SUBNET_ROOT_FUNDING_REQUEST_FLOOR_CYCLES: u128 = 42_200_000_000;

/// Minimum Root balance admitted for automatic ICP-refill execution and recovery.
pub const FLEET_SUBNET_ROOT_ICP_REFILL_FLOOR_CYCLES: u128 = 42_200_000_000;

/// Maximum registered roots represented by the bounded Coordinator funding ledger.
pub const MAX_FLEET_ROOT_FUNDING_SLOTS: usize = 4_096;

///
/// FleetCoordinatorRootFundingPolicy
///
/// Immutable Fleet-wide reserve and grant-budget authority installed into one Coordinator.
///

#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
#[serde(deny_unknown_fields)]
pub struct FleetCoordinatorRootFundingPolicy {
    pub funding_profile: FleetFundingProfile,
    pub minimum_reserve_cycles: Cycles,
    pub budget: CyclesFundingBudget,
    pub maximum_automatic_grants: u32,
    pub maximum_automatic_cycles: Cycles,
}

///
/// FleetSubnetRootFundingPolicy
///
/// Immutable Coordinator-grant thresholds and budget for one registered root.
///

#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
#[serde(deny_unknown_fields)]
pub struct FleetSubnetRootFundingPolicy {
    pub funding_profile: FleetFundingProfile,
    pub request_threshold: Cycles,
    pub target_balance: Cycles,
    pub cooldown_secs: u64,
    pub budget: CyclesFundingBudget,
    pub maximum_automatic_grants: u32,
    pub maximum_automatic_cycles: Cycles,
}

///
/// FleetSubnetRootAutomaticIcpRefillPolicy
///
/// Optional emergency trigger and target subordinate to one root's ICP-refill policy.
///

#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
#[serde(deny_unknown_fields)]
pub struct FleetSubnetRootAutomaticIcpRefillPolicy {
    pub emergency_threshold: Cycles,
    pub target_balance: Cycles,
    pub maximum_automatic_refills: u32,
    pub maximum_automatic_refill_e8s: u64,
}

///
/// FleetSubnetRootIcpRefillPolicy
///
/// Immutable root-owned ICP conversion budget, balance floor, and system-Canister authority.
///

#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
#[serde(deny_unknown_fields)]
pub struct FleetSubnetRootIcpRefillPolicy {
    pub max_refill_e8s_per_call: u64,
    pub window_secs: u64,
    pub maximum_refill_e8s: u64,
    pub minimum_icp_balance_e8s: u64,
    pub min_xdr_permyriad_per_icp: Option<u64>,
    pub ledger_canister_id: Option<Principal>,
    pub cmc_canister_id: Option<Principal>,
    pub allow_ic_system_canister_overrides: bool,
    pub automatic: Option<FleetSubnetRootAutomaticIcpRefillPolicy>,
}

///
/// FleetSubnetRootFundingAuthority
///
/// Complete immutable Coordinator-grant and optional ICP-refill policy for one root.
///

#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
#[serde(deny_unknown_fields)]
pub struct FleetSubnetRootFundingAuthority {
    pub root_funding: FleetSubnetRootFundingPolicy,
    pub icp_refill: Option<FleetSubnetRootIcpRefillPolicy>,
}

///
/// FleetSubnetCanisterPoolConfig
///
/// Immutable prepaid empty-Canister inventory policy for one Fleet Subnet Root.
///

#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
#[serde(deny_unknown_fields)]
pub struct FleetSubnetCanisterPoolConfig {
    /// Ready empty Canisters automatically maintained for the root.
    pub minimum_size: u32,
    /// Ceiling for standby and operator-imported pool assets.
    ///
    /// Recycled assets remain tracked even when their return temporarily exceeds this target.
    pub maximum_size: u32,
    /// Minimum retained balance required before a pool asset becomes Ready.
    pub canister_cycles: Cycles,
    /// Native cycles retained above the Ready floor while a newly created asset is
    /// created, inspected, controller-checked, and admitted to the pool.
    pub creation_execution_margin: Cycles,
}

///
/// ComponentSpecAdmission
///
/// Immutable permission and concrete-instance ceiling for one Spec on one Fleet Subnet Root.
///

#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
#[serde(deny_unknown_fields)]
pub struct ComponentSpecAdmission {
    pub component_spec: ComponentSpecId,
    pub spec_hash: [u8; 32],
    pub maximum_root_instances: u32,
}

///
/// FleetSubnetRootLimits
///
/// Immutable aggregate policy ceilings for one Fleet Subnet Root.
///

#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
#[serde(deny_unknown_fields)]
pub struct FleetSubnetRootLimits {
    pub maximum_component_instances: u32,
    pub maximum_registry_bytes: u64,
    pub maximum_wasm_store_bytes: u64,
    pub canister_pool: FleetSubnetCanisterPoolConfig,
    pub cycles_funding: CyclesFundingBudget,
    /// Maximum accepted or committed Component Group placements on this root.
    pub maximum_group_placements: u32,
}

///
/// FleetCoordinatorBinding
///
/// Immutable Coordinator identity, placement and direct Fleet recovery authority.
///

#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
#[serde(deny_unknown_fields)]
pub struct FleetCoordinatorBinding {
    pub fleet: FleetBinding,
    pub coordinator_subnet: SubnetId,
    pub coordinator: Principal,
    /// Canonical direct controllers that may recover any canister in this Fleet.
    pub recovery_controllers: Vec<Principal>,
}

impl FleetCoordinatorBinding {
    /// Exact controller set for a Root-owned canister, including recovery authority.
    #[must_use]
    pub fn root_controllers(&self, root: Principal) -> Vec<Principal> {
        let mut controllers = self.recovery_controllers.clone();
        controllers.push(root);
        controllers.sort_unstable();
        controllers.dedup();
        controllers
    }

    /// Compare IC controller sets without relying on management response order.
    #[must_use]
    pub fn has_exact_root_controllers(&self, root: Principal, observed: &[Principal]) -> bool {
        let mut observed = observed.to_vec();
        observed.sort_unstable();
        observed == self.root_controllers(root)
    }
}

///
/// FleetRegistryAuthority
///
/// Exact Coordinator binding and reinstall-local authority epoch for one Fleet Registry.
///

#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
#[serde(deny_unknown_fields)]
pub struct FleetRegistryAuthority {
    pub binding: FleetCoordinatorBinding,
    pub epoch: u64,
}

///
/// FleetSubnetRootBinding
///
/// Complete immutable identity, placement, admissions, and limits of one Fleet Subnet Root.
///

#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
#[serde(deny_unknown_fields)]
pub struct FleetSubnetRootBinding {
    pub authority: FleetRegistryAuthority,
    pub placement_subnet: SubnetId,
    pub fleet_subnet_root: Principal,
    pub component_admissions: Vec<ComponentSpecAdmission>,
    pub component_topology_digest: ComponentTopologyDigest,
    pub limits: FleetSubnetRootLimits,
    pub funding: FleetSubnetRootFundingAuthority,
}

///
/// FleetSubnetWasmStoreAuthority
///
/// Exact reciprocal authority retained by one root and its host-installed sibling Store.
///

#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
#[serde(deny_unknown_fields)]
pub struct FleetSubnetWasmStoreAuthority {
    pub authority: FleetRegistryAuthority,
    pub placement_subnet: SubnetId,
    pub fleet_subnet_root: Principal,
    pub wasm_store: Principal,
    pub installation_controller: Principal,
    pub release_build_id: ReleaseBuildId,
    pub wasm_module_hash: [u8; 32],
}

/// Exact child identity Root must use while activating its independently installed Store.
#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
pub struct FleetSubnetWasmStoreActivationAuthority {
    pub fleet: FleetBinding,
    pub operation_id: [u8; 32],
    pub fleet_subnet_root: Principal,
    pub wasm_store: Principal,
    pub release_build_id: ReleaseBuildId,
    pub component_topology_digest: ComponentTopologyDigest,
    pub controllers: Vec<Principal>,
    pub manifest_digest: ReleaseSetDigest,
}

///
/// ComponentBinding
///
/// Complete immutable identity and placement of one concrete Component.
///

#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
#[serde(deny_unknown_fields)]
pub struct ComponentBinding {
    pub authority: FleetRegistryAuthority,
    pub component: ComponentInstanceId,
    pub component_spec: ComponentSpecId,
    pub spec_hash: [u8; 32],
    pub role: CanisterRole,
    pub placement_subnet: SubnetId,
    pub fleet_subnet_root: Principal,
    pub canister_id: Principal,
}

///
/// ComponentChildBinding
///
/// Complete immutable identity of one child at any depth in one exact Component tree.
///

#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
#[serde(deny_unknown_fields)]
pub struct ComponentChildBinding {
    pub component: ComponentBinding,
    pub parent_canister_id: Principal,
    pub role: CanisterRole,
    pub canister_id: Principal,
}

///
/// ManagedCanisterBinding
///
/// Immutable Registry-issued identity retained by one managed application Canister.
///

#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
#[serde(deny_unknown_fields)]
pub enum ManagedCanisterBinding {
    Component(ComponentBinding),
    ComponentChild(ComponentChildBinding),
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn root_controller_set_includes_exact_recovery_principals() {
        use crate::ids::{AppId, CanonicalNetworkId, FleetId, FleetKey};

        let root = Principal::from_slice(&[21; 29]);
        let recovery = Principal::from_slice(&[22; 29]);
        let binding = FleetCoordinatorBinding {
            fleet: FleetBinding {
                fleet: FleetKey {
                    canonical_network_id: CanonicalNetworkId::ic_mainnet(),
                    fleet_id: FleetId::from_generated_bytes([23; 32]),
                },
                app: AppId::from("recovery-test"),
            },
            coordinator_subnet: SubnetId::from_principal(Principal::from_slice(&[24; 29])),
            coordinator: Principal::from_slice(&[25; 29]),
            recovery_controllers: vec![recovery],
        };
        let mut expected = vec![root, recovery];
        expected.sort_unstable();
        assert_eq!(binding.root_controllers(root), expected);
        assert!(binding.has_exact_root_controllers(root, &[recovery, root]));
        assert!(!binding.has_exact_root_controllers(root, &[root]));
    }

    #[test]
    fn funding_profile_candid_spelling_roundtrips() {
        for profile in [
            FleetFundingProfile::SingleSubnet,
            FleetFundingProfile::PreviewMultiSubnet,
            FleetFundingProfile::MultiSubnet,
        ] {
            let bytes = candid::encode_one(profile).expect("encode funding profile Candid");
            assert_eq!(
                candid::decode_one::<FleetFundingProfile>(&bytes)
                    .expect("decode funding profile Candid"),
                profile
            );
        }
    }
}