Skip to main content

canic_core/ids/fleet_topology/
mod.rs

1//! Module: ids::fleet_topology
2//!
3//! Responsibility: define protected Fleet topology, admission, limit, and binding facts.
4//! Does not own: configuration compilation, placement decisions, Registry mutation, or storage.
5//! Boundary: these passive cross-layer contracts are validated before authoritative use.
6
7use crate::{
8    cdk::types::Cycles,
9    ids::{
10        CanisterRole, ComponentInstanceId, ComponentSpecId, FleetBinding, ReleaseBuildId, SubnetId,
11    },
12};
13use candid::{CandidType, Principal};
14use serde::{Deserialize, Serialize};
15use std::fmt;
16
17///
18/// ComponentTopologyDigest
19///
20/// SHA-256 identity of one canonical root-local Component Topology projection.
21///
22
23#[derive(
24    CandidType, Clone, Copy, Debug, Deserialize, Eq, Hash, Ord, PartialEq, PartialOrd, Serialize,
25)]
26#[serde(transparent)]
27pub struct ComponentTopologyDigest([u8; 32]);
28
29impl ComponentTopologyDigest {
30    #[must_use]
31    pub const fn from_bytes(bytes: [u8; 32]) -> Self {
32        Self(bytes)
33    }
34
35    #[must_use]
36    pub const fn as_bytes(&self) -> &[u8; 32] {
37        &self.0
38    }
39
40    #[must_use]
41    pub const fn into_bytes(self) -> [u8; 32] {
42        self.0
43    }
44}
45
46impl fmt::Display for ComponentTopologyDigest {
47    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
48        for byte in self.0 {
49            write!(formatter, "{byte:02x}")?;
50        }
51        Ok(())
52    }
53}
54
55///
56/// CyclesFundingBudget
57///
58/// Positive aggregate cycles-funding ceiling applied over one bounded window.
59///
60
61#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
62#[serde(deny_unknown_fields)]
63pub struct CyclesFundingBudget {
64    pub window_secs: u64,
65    pub maximum_cycles: Cycles,
66}
67
68/// Protected physical-topology class that selects the minimum Root funding baseline.
69#[derive(CandidType, Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
70pub enum FleetFundingProfile {
71    #[serde(rename = "single_subnet")]
72    SingleSubnet,
73    #[serde(rename = "multi_subnet")]
74    MultiSubnet,
75    #[serde(rename = "preview_multi_subnet")]
76    PreviewMultiSubnet,
77}
78
79/// Minimum post-grant Coordinator execution reserve established by the 0.108 M0 proof.
80pub const COORDINATOR_ROOT_FUNDING_EXECUTION_RESERVE_FLOOR_CYCLES: u128 = 100_000_000;
81
82/// Conservative current-cost reservation for one bounded 16 KiB funding command.
83pub const FLEET_ROOT_FUNDING_CALL_RESERVATION_CYCLES: u128 = 42_118_809_000;
84
85/// Minimum Root balance admitted for the Coordinator request and exact-retry path.
86pub const FLEET_SUBNET_ROOT_FUNDING_REQUEST_FLOOR_CYCLES: u128 = 42_200_000_000;
87
88/// Minimum Root balance admitted for automatic ICP-refill execution and recovery.
89pub const FLEET_SUBNET_ROOT_ICP_REFILL_FLOOR_CYCLES: u128 = 42_200_000_000;
90
91/// Maximum registered roots represented by the bounded Coordinator funding ledger.
92pub const MAX_FLEET_ROOT_FUNDING_SLOTS: usize = 4_096;
93
94///
95/// FleetCoordinatorRootFundingPolicy
96///
97/// Immutable Fleet-wide reserve and grant-budget authority installed into one Coordinator.
98///
99
100#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
101#[serde(deny_unknown_fields)]
102pub struct FleetCoordinatorRootFundingPolicy {
103    pub funding_profile: FleetFundingProfile,
104    pub minimum_reserve_cycles: Cycles,
105    pub budget: CyclesFundingBudget,
106    pub maximum_automatic_grants: u32,
107    pub maximum_automatic_cycles: Cycles,
108}
109
110///
111/// FleetSubnetRootFundingPolicy
112///
113/// Immutable Coordinator-grant thresholds and budget for one registered root.
114///
115
116#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
117#[serde(deny_unknown_fields)]
118pub struct FleetSubnetRootFundingPolicy {
119    pub funding_profile: FleetFundingProfile,
120    pub request_threshold: Cycles,
121    pub target_balance: Cycles,
122    pub cooldown_secs: u64,
123    pub budget: CyclesFundingBudget,
124    pub maximum_automatic_grants: u32,
125    pub maximum_automatic_cycles: Cycles,
126}
127
128///
129/// FleetSubnetRootAutomaticIcpRefillPolicy
130///
131/// Optional emergency trigger and target subordinate to one root's ICP-refill policy.
132///
133
134#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
135#[serde(deny_unknown_fields)]
136pub struct FleetSubnetRootAutomaticIcpRefillPolicy {
137    pub emergency_threshold: Cycles,
138    pub target_balance: Cycles,
139    pub maximum_automatic_refills: u32,
140    pub maximum_automatic_refill_e8s: u64,
141}
142
143///
144/// FleetSubnetRootIcpRefillPolicy
145///
146/// Immutable root-owned ICP conversion budget, balance floor, and system-Canister authority.
147///
148
149#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
150#[serde(deny_unknown_fields)]
151pub struct FleetSubnetRootIcpRefillPolicy {
152    pub max_refill_e8s_per_call: u64,
153    pub window_secs: u64,
154    pub maximum_refill_e8s: u64,
155    pub minimum_icp_balance_e8s: u64,
156    pub min_xdr_permyriad_per_icp: Option<u64>,
157    pub ledger_canister_id: Option<Principal>,
158    pub cmc_canister_id: Option<Principal>,
159    pub allow_ic_system_canister_overrides: bool,
160    pub automatic: Option<FleetSubnetRootAutomaticIcpRefillPolicy>,
161}
162
163///
164/// FleetSubnetRootFundingAuthority
165///
166/// Complete immutable Coordinator-grant and optional ICP-refill policy for one root.
167///
168
169#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
170#[serde(deny_unknown_fields)]
171pub struct FleetSubnetRootFundingAuthority {
172    pub root_funding: FleetSubnetRootFundingPolicy,
173    pub icp_refill: Option<FleetSubnetRootIcpRefillPolicy>,
174}
175
176///
177/// FleetSubnetCanisterPoolConfig
178///
179/// Immutable prepaid empty-Canister inventory policy for one Fleet Subnet Root.
180///
181
182#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
183#[serde(deny_unknown_fields)]
184pub struct FleetSubnetCanisterPoolConfig {
185    /// Ready empty Canisters automatically maintained for the root.
186    pub minimum_size: u32,
187    /// Ceiling for standby and operator-imported pool assets.
188    ///
189    /// Recycled assets remain tracked even when their return temporarily exceeds this target.
190    pub maximum_size: u32,
191    /// Minimum retained balance required before a pool asset becomes Ready.
192    pub canister_cycles: Cycles,
193}
194
195///
196/// ComponentSpecAdmission
197///
198/// Immutable permission and concrete-instance ceiling for one Spec on one Fleet Subnet Root.
199///
200
201#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
202#[serde(deny_unknown_fields)]
203pub struct ComponentSpecAdmission {
204    pub component_spec: ComponentSpecId,
205    pub spec_hash: [u8; 32],
206    pub maximum_root_instances: u32,
207}
208
209///
210/// FleetSubnetRootLimits
211///
212/// Immutable aggregate policy ceilings for one Fleet Subnet Root.
213///
214
215#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
216#[serde(deny_unknown_fields)]
217pub struct FleetSubnetRootLimits {
218    pub maximum_component_instances: u32,
219    pub maximum_registry_bytes: u64,
220    pub maximum_wasm_store_bytes: u64,
221    pub canister_pool: FleetSubnetCanisterPoolConfig,
222    pub cycles_funding: CyclesFundingBudget,
223    /// Maximum accepted or committed Component Group placements on this root.
224    pub maximum_group_placements: u32,
225}
226
227///
228/// FleetCoordinatorBinding
229///
230/// Immutable identity and exact physical placement of one Fleet Coordinator.
231///
232
233#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
234#[serde(deny_unknown_fields)]
235pub struct FleetCoordinatorBinding {
236    pub fleet: FleetBinding,
237    pub coordinator_subnet: SubnetId,
238    pub coordinator: Principal,
239}
240
241///
242/// FleetRegistryAuthority
243///
244/// Exact Coordinator binding and reinstall-local authority epoch for one Fleet Registry.
245///
246
247#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
248#[serde(deny_unknown_fields)]
249pub struct FleetRegistryAuthority {
250    pub binding: FleetCoordinatorBinding,
251    pub epoch: u64,
252}
253
254///
255/// FleetSubnetRootBinding
256///
257/// Complete immutable identity, placement, admissions, and limits of one Fleet Subnet Root.
258///
259
260#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
261#[serde(deny_unknown_fields)]
262pub struct FleetSubnetRootBinding {
263    pub authority: FleetRegistryAuthority,
264    pub placement_subnet: SubnetId,
265    pub fleet_subnet_root: Principal,
266    pub component_admissions: Vec<ComponentSpecAdmission>,
267    pub component_topology_digest: ComponentTopologyDigest,
268    pub limits: FleetSubnetRootLimits,
269    pub funding: FleetSubnetRootFundingAuthority,
270}
271
272///
273/// FleetSubnetWasmStoreAuthority
274///
275/// Exact reciprocal authority retained by one root and its host-installed sibling Store.
276///
277
278#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
279#[serde(deny_unknown_fields)]
280pub struct FleetSubnetWasmStoreAuthority {
281    pub authority: FleetRegistryAuthority,
282    pub placement_subnet: SubnetId,
283    pub fleet_subnet_root: Principal,
284    pub wasm_store: Principal,
285    pub installation_controller: Principal,
286    pub release_build_id: ReleaseBuildId,
287    pub wasm_module_hash: [u8; 32],
288}
289
290///
291/// ComponentBinding
292///
293/// Complete immutable identity and placement of one concrete Component.
294///
295
296#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
297#[serde(deny_unknown_fields)]
298pub struct ComponentBinding {
299    pub authority: FleetRegistryAuthority,
300    pub component: ComponentInstanceId,
301    pub component_spec: ComponentSpecId,
302    pub spec_hash: [u8; 32],
303    pub role: CanisterRole,
304    pub placement_subnet: SubnetId,
305    pub fleet_subnet_root: Principal,
306    pub canister_id: Principal,
307}
308
309///
310/// ComponentChildBinding
311///
312/// Complete immutable identity of one child at any depth in one exact Component tree.
313///
314
315#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
316#[serde(deny_unknown_fields)]
317pub struct ComponentChildBinding {
318    pub component: ComponentBinding,
319    pub parent_canister_id: Principal,
320    pub role: CanisterRole,
321    pub canister_id: Principal,
322}
323
324///
325/// ManagedCanisterBinding
326///
327/// Immutable Registry-issued identity retained by one managed application Canister.
328///
329
330#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
331#[serde(deny_unknown_fields)]
332pub enum ManagedCanisterBinding {
333    Component(ComponentBinding),
334    ComponentChild(ComponentChildBinding),
335}
336
337#[cfg(test)]
338mod tests {
339    use super::*;
340
341    #[test]
342    fn funding_profile_candid_spelling_roundtrips() {
343        for profile in [
344            FleetFundingProfile::SingleSubnet,
345            FleetFundingProfile::PreviewMultiSubnet,
346            FleetFundingProfile::MultiSubnet,
347        ] {
348            let bytes = candid::encode_one(profile).expect("encode funding profile Candid");
349            assert_eq!(
350                candid::decode_one::<FleetFundingProfile>(&bytes)
351                    .expect("decode funding profile Candid"),
352                profile
353            );
354        }
355    }
356}