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