1use 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#[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#[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#[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
80pub const COORDINATOR_ROOT_FUNDING_EXECUTION_RESERVE_FLOOR_CYCLES: u128 = 100_000_000;
82
83pub const FLEET_ROOT_FUNDING_CALL_RESERVATION_CYCLES: u128 = 42_118_809_000;
85
86pub const FLEET_SUBNET_ROOT_FUNDING_REQUEST_FLOOR_CYCLES: u128 = 42_200_000_000;
88
89pub const FLEET_SUBNET_ROOT_ICP_REFILL_FLOOR_CYCLES: u128 = 42_200_000_000;
91
92pub const MAX_FLEET_ROOT_FUNDING_SLOTS: usize = 4_096;
94
95pub const MAX_FLEET_CAPACITY_IMPORT_SOURCES: usize = 256;
97
98#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
105#[serde(deny_unknown_fields)]
106pub struct FleetCoordinatorRootFundingPolicy {
107 pub funding_profile: FleetFundingProfile,
108 pub minimum_reserve_cycles: Cycles,
109 pub budget: CyclesFundingBudget,
110 pub maximum_automatic_grants: u32,
111 pub maximum_automatic_cycles: Cycles,
112}
113
114#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
121#[serde(deny_unknown_fields)]
122pub struct FleetSubnetRootFundingPolicy {
123 pub funding_profile: FleetFundingProfile,
124 pub request_threshold: Cycles,
125 pub target_balance: Cycles,
126 pub cooldown_secs: u64,
127 pub budget: CyclesFundingBudget,
128 pub maximum_automatic_grants: u32,
129 pub maximum_automatic_cycles: Cycles,
130}
131
132#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
139#[serde(deny_unknown_fields)]
140pub struct FleetSubnetRootAutomaticIcpRefillPolicy {
141 pub emergency_threshold: Cycles,
142 pub target_balance: Cycles,
143 pub maximum_automatic_refills: u32,
144 pub maximum_automatic_refill_e8s: u64,
145}
146
147#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
154#[serde(deny_unknown_fields)]
155pub struct FleetSubnetRootIcpRefillPolicy {
156 pub max_refill_e8s_per_call: u64,
157 pub window_secs: u64,
158 pub maximum_refill_e8s: u64,
159 pub minimum_icp_balance_e8s: u64,
160 pub min_xdr_permyriad_per_icp: Option<u64>,
161 pub ledger_canister_id: Option<Principal>,
162 pub cmc_canister_id: Option<Principal>,
163 pub allow_ic_system_canister_overrides: bool,
164 pub automatic: Option<FleetSubnetRootAutomaticIcpRefillPolicy>,
165}
166
167#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
174#[serde(deny_unknown_fields)]
175pub struct FleetSubnetRootFundingAuthority {
176 pub root_funding: FleetSubnetRootFundingPolicy,
177 pub icp_refill: Option<FleetSubnetRootIcpRefillPolicy>,
178}
179
180#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
187#[serde(deny_unknown_fields)]
188pub struct FleetSubnetCanisterPoolConfig {
189 pub minimum_size: u32,
191 pub maximum_size: u32,
195 pub canister_cycles: Cycles,
197 pub creation_execution_margin: Cycles,
200}
201
202#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
209#[serde(deny_unknown_fields)]
210pub struct ComponentSpecAdmission {
211 pub component_spec: ComponentSpecId,
212 pub spec_hash: [u8; 32],
213 pub maximum_root_instances: u32,
214}
215
216#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
223#[serde(deny_unknown_fields)]
224pub struct FleetSubnetRootLimits {
225 pub maximum_component_instances: u32,
226 pub maximum_registry_bytes: u64,
227 pub maximum_wasm_store_bytes: u64,
228 pub canister_pool: FleetSubnetCanisterPoolConfig,
229 pub cycles_funding: CyclesFundingBudget,
230 pub maximum_group_placements: u32,
232}
233
234#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
241#[serde(deny_unknown_fields)]
242pub struct FleetCoordinatorBinding {
243 pub fleet: FleetBinding,
244 pub coordinator_subnet: SubnetId,
245 pub coordinator: Principal,
246 pub recovery_controllers: Vec<Principal>,
248}
249
250impl FleetCoordinatorBinding {
251 #[must_use]
253 pub fn root_controllers(&self, root: Principal) -> Vec<Principal> {
254 let mut controllers = self.recovery_controllers.clone();
255 controllers.push(root);
256 controllers.sort_unstable();
257 controllers.dedup();
258 controllers
259 }
260
261 #[must_use]
263 pub fn has_exact_root_controllers(&self, root: Principal, observed: &[Principal]) -> bool {
264 let mut observed = observed.to_vec();
265 observed.sort_unstable();
266 observed == self.root_controllers(root)
267 }
268}
269
270#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
277#[serde(deny_unknown_fields)]
278pub struct FleetRegistryAuthority {
279 pub binding: FleetCoordinatorBinding,
280 pub epoch: u64,
281}
282
283#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
290#[serde(deny_unknown_fields)]
291pub struct FleetSubnetRootBinding {
292 pub authority: FleetRegistryAuthority,
293 pub placement_subnet: SubnetId,
294 pub fleet_subnet_root: Principal,
295 pub component_admissions: Vec<ComponentSpecAdmission>,
296 pub component_topology_digest: ComponentTopologyDigest,
297 pub limits: FleetSubnetRootLimits,
298 pub funding: FleetSubnetRootFundingAuthority,
299}
300
301#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
308#[serde(deny_unknown_fields)]
309pub struct FleetSubnetWasmStoreAuthority {
310 pub authority: FleetRegistryAuthority,
311 pub placement_subnet: SubnetId,
312 pub fleet_subnet_root: Principal,
313 pub wasm_store: Principal,
314 pub installation_controller: Principal,
315 pub release_build_id: ReleaseBuildId,
316 pub wasm_module_hash: [u8; 32],
317}
318
319#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
321pub struct FleetSubnetWasmStoreActivationAuthority {
322 pub fleet: FleetBinding,
323 pub operation_id: [u8; 32],
324 pub fleet_subnet_root: Principal,
325 pub wasm_store: Principal,
326 pub release_build_id: ReleaseBuildId,
327 pub component_topology_digest: ComponentTopologyDigest,
328 pub controllers: Vec<Principal>,
329 pub manifest_digest: ReleaseSetDigest,
330}
331
332#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
339#[serde(deny_unknown_fields)]
340pub struct ComponentBinding {
341 pub authority: FleetRegistryAuthority,
342 pub component: ComponentInstanceId,
343 pub component_spec: ComponentSpecId,
344 pub spec_hash: [u8; 32],
345 pub role: CanisterRole,
346 pub placement_subnet: SubnetId,
347 pub fleet_subnet_root: Principal,
348 pub canister_id: Principal,
349}
350
351#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
358#[serde(deny_unknown_fields)]
359pub struct ComponentChildBinding {
360 pub component: ComponentBinding,
361 pub parent_canister_id: Principal,
362 pub role: CanisterRole,
363 pub canister_id: Principal,
364}
365
366#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
373#[serde(deny_unknown_fields)]
374pub enum ManagedCanisterBinding {
375 Component(ComponentBinding),
376 ComponentChild(ComponentChildBinding),
377}
378
379#[cfg(test)]
380mod tests {
381 use super::*;
382
383 #[test]
384 fn root_controller_set_includes_exact_recovery_principals() {
385 use crate::ids::{AppId, CanonicalNetworkId, FleetId, FleetKey};
386
387 let root = Principal::from_slice(&[21; 29]);
388 let recovery = Principal::from_slice(&[22; 29]);
389 let binding = FleetCoordinatorBinding {
390 fleet: FleetBinding {
391 fleet: FleetKey {
392 canonical_network_id: CanonicalNetworkId::ic_mainnet(),
393 fleet_id: FleetId::from_generated_bytes([23; 32]),
394 },
395 app: AppId::from("recovery-test"),
396 },
397 coordinator_subnet: SubnetId::from_principal(Principal::from_slice(&[24; 29])),
398 coordinator: Principal::from_slice(&[25; 29]),
399 recovery_controllers: vec![recovery],
400 };
401 let mut expected = vec![root, recovery];
402 expected.sort_unstable();
403 assert_eq!(binding.root_controllers(root), expected);
404 assert!(binding.has_exact_root_controllers(root, &[recovery, root]));
405 assert!(!binding.has_exact_root_controllers(root, &[root]));
406 }
407
408 #[test]
409 fn funding_profile_candid_spelling_roundtrips() {
410 for profile in [
411 FleetFundingProfile::SingleSubnet,
412 FleetFundingProfile::PreviewMultiSubnet,
413 FleetFundingProfile::MultiSubnet,
414 ] {
415 let bytes = candid::encode_one(profile).expect("encode funding profile Candid");
416 assert_eq!(
417 candid::decode_one::<FleetFundingProfile>(&bytes)
418 .expect("decode funding profile Candid"),
419 profile
420 );
421 }
422 }
423}