1use crate::{
8 dto::fleet_registry::{FleetRegistryVersion, FleetSubnetRootStatus},
9 ids::{ComponentTopologyDigest, FleetSubnetRootBinding, FleetSubnetRootReleaseSet, SubnetId},
10};
11use candid::{CandidType, Principal};
12use serde::{Deserialize, Serialize};
13
14#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq)]
21pub struct FleetSubnetRootAuthority {
22 pub binding: FleetSubnetRootBinding,
23 pub initial_release_set: FleetSubnetRootReleaseSet,
24 pub expected_module_hash: [u8; 32],
25}
26
27#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
34pub struct FleetSubnetRootCanisterSummary {
35 pub fleet_registry: FleetRegistryVersion,
36 pub placement_subnet: SubnetId,
37 pub fleet_subnet_root: Principal,
38 pub status: FleetSubnetRootStatus,
39 pub infrastructure_canisters: u32,
40 pub component_canisters: u32,
41 pub total_canisters: u32,
42}
43
44#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
51pub struct FleetSubnetRootDrainingRequest {
52 pub operation_id: [u8; 32],
53 pub expected_registry: FleetRegistryVersion,
54}
55
56#[derive(CandidType, Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
63pub struct FleetSubnetRootDrainingStatusRequest {
64 pub operation_id: [u8; 32],
65}
66
67#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
74pub struct FleetSubnetRootDrainingResponse {
75 pub operation_id: [u8; 32],
76 pub fleet_subnet_root: Principal,
77 pub placement_subnet: SubnetId,
78 pub active_registry: FleetRegistryVersion,
79 pub component_topology_digest: ComponentTopologyDigest,
80 pub active_release_set: FleetSubnetRootReleaseSet,
81 pub next_allocation_sequence: u64,
82 pub reserved_component_instances: u32,
83 pub committed_component_instances: u32,
84 pub managed_descendants: u32,
85 pub known_created_component_canisters: u32,
86 pub root_registry_encoded_bytes: u64,
87 pub started_at_ns: u64,
88}
89
90#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
97pub struct FleetSubnetRootFinalInventoryRequest {
98 pub operation_id: [u8; 32],
99 pub expected_registry: FleetRegistryVersion,
100}
101
102#[derive(CandidType, Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
109pub struct FleetSubnetRootFinalInventoryStatusRequest {
110 pub operation_id: [u8; 32],
111}
112
113#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
120pub struct FleetSubnetRootRemovalRequest {
121 pub operation_id: [u8; 32],
122 pub expected_registry: FleetRegistryVersion,
123}
124
125#[derive(CandidType, Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
127pub struct FleetSubnetRootRemovalStatusRequest {
128 pub operation_id: [u8; 32],
129}
130
131#[derive(CandidType, Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
138pub struct FleetSubnetRootStoreReclamationRequest {
139 pub operation_id: [u8; 32],
140 pub expected_final_inventory_hash: [u8; 32],
141}
142
143#[derive(CandidType, Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
145pub struct FleetSubnetRootStoreReclamationStatusRequest {
146 pub operation_id: [u8; 32],
147}
148
149#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
156pub struct FleetSubnetRootStoreReclamationResponse {
157 pub operation_id: [u8; 32],
158 pub fleet_subnet_root: Principal,
159 pub wasm_store: Principal,
160 pub final_inventory_hash: [u8; 32],
161 pub reclaimed_store_bytes: u64,
162 pub reclaimed_catalog_entries: u32,
163 pub reclaimed_template_count: u32,
164 pub reclaimed_release_count: u32,
165 pub gc_prepared_at_secs: u64,
166 pub gc_started_at_secs: u64,
167 pub gc_completed_at_secs: u64,
168 pub gc_runs_completed: u32,
169 pub completed_at_ns: u64,
170 pub reclamation_hash: [u8; 32],
171}
172
173#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
180pub struct FleetSubnetRootFinalInventoryResponse {
181 pub operation_id: [u8; 32],
182 pub fleet_subnet_root: Principal,
183 pub placement_subnet: SubnetId,
184 pub registry: FleetRegistryVersion,
185 pub component_topology_digest: ComponentTopologyDigest,
186 pub active_release_set: FleetSubnetRootReleaseSet,
187 pub next_allocation_sequence: u64,
188 pub removed_component_instances: u32,
189 pub terminal_component_history_hash: [u8; 32],
190 pub root_registry_encoded_bytes: u64,
191 pub wasm_store: Principal,
192 pub wasm_store_catalog_hash: [u8; 32],
193 pub wasm_store_catalog_entries: u32,
194 pub wasm_store_occupied_bytes: u64,
195 pub wasm_store_template_count: u32,
196 pub wasm_store_release_count: u32,
197 pub wasm_store_gc_prepared_at_secs: u64,
198 pub finalized_at_ns: u64,
199 pub inventory_hash: [u8; 32],
200}
201
202#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq)]
209pub struct FleetSubnetRootInitArgs {
210 pub authority: FleetSubnetRootAuthority,
211 pub install_id: [u8; 32],
212}
213
214#[cfg(test)]
215mod tests {
216 use super::*;
217 use crate::ids::{
218 AppId, CanonicalNetworkId, FleetBinding, FleetCoordinatorBinding, FleetId, FleetKey,
219 FleetRegistryAuthority,
220 };
221
222 #[test]
223 fn canister_summary_and_root_lifecycle_contracts_round_trip_through_candid() {
224 let summary = canister_summary();
225 let candid = candid::encode_one(&summary).expect("encode Canister summary");
226 let decoded: FleetSubnetRootCanisterSummary =
227 candid::decode_one(&candid).expect("decode Canister summary");
228
229 assert_eq!(decoded, summary);
230
231 let draining = draining_response(&summary);
232 let request = FleetSubnetRootDrainingRequest {
233 operation_id: draining.operation_id,
234 expected_registry: draining.active_registry.clone(),
235 };
236 let status = FleetSubnetRootDrainingStatusRequest {
237 operation_id: draining.operation_id,
238 };
239 let request_bytes = candid::encode_one(&request).expect("encode root draining request");
240 let status_bytes = candid::encode_one(status).expect("encode root draining status");
241 let response_bytes = candid::encode_one(&draining).expect("encode root draining response");
242 assert_eq!(
243 candid::decode_one::<FleetSubnetRootDrainingRequest>(&request_bytes)
244 .expect("decode root draining request"),
245 request
246 );
247 assert_eq!(
248 candid::decode_one::<FleetSubnetRootDrainingStatusRequest>(&status_bytes)
249 .expect("decode root draining status"),
250 status
251 );
252 assert_eq!(
253 candid::decode_one::<FleetSubnetRootDrainingResponse>(&response_bytes)
254 .expect("decode root draining response"),
255 draining
256 );
257
258 let inventory = final_inventory_response(&draining);
259 let inventory_request = FleetSubnetRootFinalInventoryRequest {
260 operation_id: inventory.operation_id,
261 expected_registry: inventory.registry.clone(),
262 };
263 let inventory_status = FleetSubnetRootFinalInventoryStatusRequest {
264 operation_id: inventory.operation_id,
265 };
266 let request_bytes =
267 candid::encode_one(&inventory_request).expect("encode root inventory request");
268 let status_bytes =
269 candid::encode_one(inventory_status).expect("encode root inventory status");
270 let response_bytes =
271 candid::encode_one(&inventory).expect("encode root inventory response");
272 assert_eq!(
273 candid::decode_one::<FleetSubnetRootFinalInventoryRequest>(&request_bytes)
274 .expect("decode root inventory request"),
275 inventory_request
276 );
277 assert_eq!(
278 candid::decode_one::<FleetSubnetRootFinalInventoryStatusRequest>(&status_bytes)
279 .expect("decode root inventory status"),
280 inventory_status
281 );
282 assert_eq!(
283 candid::decode_one::<FleetSubnetRootFinalInventoryResponse>(&response_bytes)
284 .expect("decode root inventory response"),
285 inventory
286 );
287 }
288
289 #[test]
290 fn draining_publication_contracts_round_trip_through_candid() {
291 let draining = draining_response(&canister_summary());
292 let publication = crate::dto::fleet_registry::FleetSubnetRootDrainingPublicationRequest {
293 expected_registry: draining.active_registry.clone(),
294 root_draining: draining.clone(),
295 };
296 let publication_response =
297 crate::dto::fleet_registry::FleetSubnetRootDrainingPublicationResponse {
298 root_draining: draining,
299 previous_version: publication.expected_registry.clone(),
300 version: FleetRegistryVersion {
301 authority: publication.expected_registry.authority.clone(),
302 revision: publication.expected_registry.revision + 1,
303 content_hash: [19; 32],
304 },
305 };
306 let publication_bytes =
307 candid::encode_one(&publication).expect("encode root draining publication");
308 let publication_response_bytes = candid::encode_one(&publication_response)
309 .expect("encode root draining publication response");
310 assert_eq!(
311 candid::decode_one::<
312 crate::dto::fleet_registry::FleetSubnetRootDrainingPublicationRequest,
313 >(&publication_bytes)
314 .expect("decode root draining publication"),
315 publication
316 );
317 assert_eq!(
318 candid::decode_one::<
319 crate::dto::fleet_registry::FleetSubnetRootDrainingPublicationResponse,
320 >(&publication_response_bytes)
321 .expect("decode root draining publication response"),
322 publication_response
323 );
324
325 let final_inventory = final_inventory_response(&publication_response.root_draining);
326 let removal_request = FleetSubnetRootRemovalRequest {
327 operation_id: final_inventory.operation_id,
328 expected_registry: publication_response.version.clone(),
329 };
330 let removal_status = FleetSubnetRootRemovalStatusRequest {
331 operation_id: final_inventory.operation_id,
332 };
333 let coordinator_request =
334 crate::dto::fleet_registry::FleetSubnetRootRemovalPublicationRequest {
335 expected_registry: publication_response.version.clone(),
336 final_inventory: final_inventory.clone(),
337 };
338 let coordinator_response =
339 crate::dto::fleet_registry::FleetSubnetRootRemovalPublicationResponse {
340 final_inventory,
341 previous_version: publication_response.version.clone(),
342 version: FleetRegistryVersion {
343 authority: publication_response.version.authority.clone(),
344 revision: publication_response.version.revision + 1,
345 content_hash: [29; 32],
346 },
347 };
348 assert_candid_round_trip(&removal_request);
349 assert_candid_round_trip(&removal_status);
350 assert_candid_round_trip(&coordinator_request);
351 assert_candid_round_trip(&coordinator_response);
352
353 let reclamation_request = FleetSubnetRootStoreReclamationRequest {
354 operation_id: coordinator_response.final_inventory.operation_id,
355 expected_final_inventory_hash: coordinator_response.final_inventory.inventory_hash,
356 };
357 let reclamation_status = FleetSubnetRootStoreReclamationStatusRequest {
358 operation_id: reclamation_request.operation_id,
359 };
360 let reclamation_response = FleetSubnetRootStoreReclamationResponse {
361 operation_id: reclamation_request.operation_id,
362 fleet_subnet_root: coordinator_response.final_inventory.fleet_subnet_root,
363 wasm_store: coordinator_response.final_inventory.wasm_store,
364 final_inventory_hash: reclamation_request.expected_final_inventory_hash,
365 reclaimed_store_bytes: coordinator_response
366 .final_inventory
367 .wasm_store_occupied_bytes,
368 reclaimed_catalog_entries: coordinator_response
369 .final_inventory
370 .wasm_store_catalog_entries,
371 reclaimed_template_count: coordinator_response
372 .final_inventory
373 .wasm_store_template_count,
374 reclaimed_release_count: coordinator_response
375 .final_inventory
376 .wasm_store_release_count,
377 gc_prepared_at_secs: coordinator_response
378 .final_inventory
379 .wasm_store_gc_prepared_at_secs,
380 gc_started_at_secs: 30,
381 gc_completed_at_secs: 31,
382 gc_runs_completed: 1,
383 completed_at_ns: 32,
384 reclamation_hash: [33; 32],
385 };
386 assert_candid_round_trip(&reclamation_request);
387 assert_candid_round_trip(&reclamation_status);
388 assert_candid_round_trip(&reclamation_response);
389 }
390
391 fn assert_candid_round_trip<T>(value: &T)
392 where
393 T: CandidType + for<'de> candid::Deserialize<'de> + Eq + std::fmt::Debug,
394 {
395 let bytes = candid::encode_one(value).expect("encode Candid contract");
396 assert_eq!(
397 &candid::decode_one::<T>(&bytes).expect("decode Candid contract"),
398 value,
399 );
400 }
401
402 fn canister_summary() -> FleetSubnetRootCanisterSummary {
403 FleetSubnetRootCanisterSummary {
404 fleet_registry: FleetRegistryVersion {
405 authority: FleetRegistryAuthority {
406 binding: FleetCoordinatorBinding {
407 fleet: FleetBinding {
408 fleet: FleetKey {
409 canonical_network_id: CanonicalNetworkId::ic_mainnet(),
410 fleet_id: FleetId::from_generated_bytes([1; 32]),
411 },
412 app: AppId::from("toko"),
413 },
414 coordinator_subnet: SubnetId::from_principal(Principal::from_slice(
415 &[2; 29],
416 )),
417 coordinator: Principal::from_slice(&[3; 29]),
418 },
419 epoch: 1,
420 },
421 revision: 4,
422 content_hash: [5; 32],
423 },
424 placement_subnet: SubnetId::from_principal(Principal::from_slice(&[6; 29])),
425 fleet_subnet_root: Principal::from_slice(&[7; 29]),
426 status: FleetSubnetRootStatus::Active,
427 infrastructure_canisters: 2,
428 component_canisters: 3,
429 total_canisters: 5,
430 }
431 }
432
433 fn draining_response(
434 summary: &FleetSubnetRootCanisterSummary,
435 ) -> FleetSubnetRootDrainingResponse {
436 FleetSubnetRootDrainingResponse {
437 operation_id: [8; 32],
438 fleet_subnet_root: summary.fleet_subnet_root,
439 placement_subnet: summary.placement_subnet,
440 active_registry: summary.fleet_registry.clone(),
441 component_topology_digest: ComponentTopologyDigest::from_bytes([9; 32]),
442 active_release_set: FleetSubnetRootReleaseSet {
443 release_build_id: crate::ids::ReleaseBuildId::from_nonce(
444 crate::ids::ReleaseBuildNonce::from_random_bytes([10; 32]),
445 ),
446 manifest_digest: crate::ids::ReleaseSetDigest::from_bytes([11; 32]),
447 },
448 next_allocation_sequence: 12,
449 reserved_component_instances: 13,
450 committed_component_instances: 14,
451 managed_descendants: 15,
452 known_created_component_canisters: 16,
453 root_registry_encoded_bytes: 17_000,
454 started_at_ns: 18,
455 }
456 }
457
458 fn final_inventory_response(
459 draining: &FleetSubnetRootDrainingResponse,
460 ) -> FleetSubnetRootFinalInventoryResponse {
461 FleetSubnetRootFinalInventoryResponse {
462 operation_id: draining.operation_id,
463 fleet_subnet_root: draining.fleet_subnet_root,
464 placement_subnet: draining.placement_subnet,
465 registry: draining.active_registry.clone(),
466 component_topology_digest: draining.component_topology_digest,
467 active_release_set: draining.active_release_set,
468 next_allocation_sequence: draining.next_allocation_sequence,
469 removed_component_instances: 12,
470 terminal_component_history_hash: [19; 32],
471 root_registry_encoded_bytes: 20_000,
472 wasm_store: Principal::from_slice(&[21; 29]),
473 wasm_store_catalog_hash: [22; 32],
474 wasm_store_catalog_entries: 23,
475 wasm_store_occupied_bytes: 24_000,
476 wasm_store_template_count: 25,
477 wasm_store_release_count: 26,
478 wasm_store_gc_prepared_at_secs: 27,
479 finalized_at_ns: 28,
480 inventory_hash: [29; 32],
481 }
482 }
483}