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, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
175pub struct FleetSubnetRootStoreBindingFinalizationRequest {
176 pub operation_id: [u8; 32],
177 pub expected_reclamation_hash: [u8; 32],
178}
179
180#[derive(CandidType, Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
182pub struct FleetSubnetRootStoreBindingFinalizationStatusRequest {
183 pub operation_id: [u8; 32],
184}
185
186#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
188pub struct FleetSubnetRootStoreBindingFinalizationResponse {
189 pub operation_id: [u8; 32],
190 pub fleet_subnet_root: Principal,
191 pub wasm_store: Principal,
192 pub final_inventory_hash: [u8; 32],
193 pub reclamation_hash: [u8; 32],
194 pub source_generation: u64,
195 pub finalized_generation: u64,
196 pub finalized_at_secs: u64,
197 pub completed_at_ns: u64,
198 pub finalization_hash: [u8; 32],
199}
200
201#[derive(CandidType, Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
203pub struct FleetSubnetRootStoreDeletionRequest {
204 pub operation_id: [u8; 32],
205 pub expected_binding_finalization_hash: [u8; 32],
206}
207
208#[derive(CandidType, Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
210pub struct FleetSubnetRootStoreDeletionStatusRequest {
211 pub operation_id: [u8; 32],
212}
213
214#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
216pub struct FleetSubnetRootStoreDeletionResponse {
217 pub operation_id: [u8; 32],
218 pub fleet_subnet_root: Principal,
219 pub wasm_store: Principal,
220 pub binding_finalization_hash: [u8; 32],
221 pub observed_module_hash: [u8; 32],
222 pub observed_controllers: Vec<Principal>,
223 pub observed_cycles_before_reclamation: u128,
224 pub maximum_cycles_to_retain: u128,
225 pub observed_cycles_after_reclamation: u128,
226 pub cycles_reclaimed_at_ns: u64,
227 pub prepared_at_ns: u64,
228 pub observed_absent_at_ns: u64,
229 pub completed_at_ns: u64,
230 pub deletion_hash: [u8; 32],
231}
232
233#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
240pub struct FleetSubnetRootFinalInventoryResponse {
241 pub operation_id: [u8; 32],
242 pub fleet_subnet_root: Principal,
243 pub placement_subnet: SubnetId,
244 pub registry: FleetRegistryVersion,
245 pub component_topology_digest: ComponentTopologyDigest,
246 pub active_release_set: FleetSubnetRootReleaseSet,
247 pub next_allocation_sequence: u64,
248 pub removed_component_instances: u32,
249 pub terminal_component_history_hash: [u8; 32],
250 pub root_registry_encoded_bytes: u64,
251 pub wasm_store: Principal,
252 pub wasm_store_catalog_hash: [u8; 32],
253 pub wasm_store_catalog_entries: u32,
254 pub wasm_store_occupied_bytes: u64,
255 pub wasm_store_template_count: u32,
256 pub wasm_store_release_count: u32,
257 pub wasm_store_gc_prepared_at_secs: u64,
258 pub finalized_at_ns: u64,
259 pub inventory_hash: [u8; 32],
260}
261
262#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq)]
269pub struct FleetSubnetRootInitArgs {
270 pub authority: FleetSubnetRootAuthority,
271 pub install_id: [u8; 32],
272}
273
274#[cfg(test)]
275mod tests {
276 use super::*;
277 use crate::ids::{
278 AppId, CanonicalNetworkId, FleetBinding, FleetCoordinatorBinding, FleetId, FleetKey,
279 FleetRegistryAuthority,
280 };
281
282 #[test]
283 fn canister_summary_and_root_lifecycle_contracts_round_trip_through_candid() {
284 let summary = canister_summary();
285 let candid = candid::encode_one(&summary).expect("encode Canister summary");
286 let decoded: FleetSubnetRootCanisterSummary =
287 candid::decode_one(&candid).expect("decode Canister summary");
288
289 assert_eq!(decoded, summary);
290
291 let draining = draining_response(&summary);
292 let request = FleetSubnetRootDrainingRequest {
293 operation_id: draining.operation_id,
294 expected_registry: draining.active_registry.clone(),
295 };
296 let status = FleetSubnetRootDrainingStatusRequest {
297 operation_id: draining.operation_id,
298 };
299 let request_bytes = candid::encode_one(&request).expect("encode root draining request");
300 let status_bytes = candid::encode_one(status).expect("encode root draining status");
301 let response_bytes = candid::encode_one(&draining).expect("encode root draining response");
302 assert_eq!(
303 candid::decode_one::<FleetSubnetRootDrainingRequest>(&request_bytes)
304 .expect("decode root draining request"),
305 request
306 );
307 assert_eq!(
308 candid::decode_one::<FleetSubnetRootDrainingStatusRequest>(&status_bytes)
309 .expect("decode root draining status"),
310 status
311 );
312 assert_eq!(
313 candid::decode_one::<FleetSubnetRootDrainingResponse>(&response_bytes)
314 .expect("decode root draining response"),
315 draining
316 );
317
318 let inventory = final_inventory_response(&draining);
319 let inventory_request = FleetSubnetRootFinalInventoryRequest {
320 operation_id: inventory.operation_id,
321 expected_registry: inventory.registry.clone(),
322 };
323 let inventory_status = FleetSubnetRootFinalInventoryStatusRequest {
324 operation_id: inventory.operation_id,
325 };
326 let request_bytes =
327 candid::encode_one(&inventory_request).expect("encode root inventory request");
328 let status_bytes =
329 candid::encode_one(inventory_status).expect("encode root inventory status");
330 let response_bytes =
331 candid::encode_one(&inventory).expect("encode root inventory response");
332 assert_eq!(
333 candid::decode_one::<FleetSubnetRootFinalInventoryRequest>(&request_bytes)
334 .expect("decode root inventory request"),
335 inventory_request
336 );
337 assert_eq!(
338 candid::decode_one::<FleetSubnetRootFinalInventoryStatusRequest>(&status_bytes)
339 .expect("decode root inventory status"),
340 inventory_status
341 );
342 assert_eq!(
343 candid::decode_one::<FleetSubnetRootFinalInventoryResponse>(&response_bytes)
344 .expect("decode root inventory response"),
345 inventory
346 );
347 }
348
349 #[test]
350 fn draining_publication_contracts_round_trip_through_candid() {
351 let draining = draining_response(&canister_summary());
352 let publication = crate::dto::fleet_registry::FleetSubnetRootDrainingPublicationRequest {
353 expected_registry: draining.active_registry.clone(),
354 root_draining: draining.clone(),
355 };
356 let publication_response =
357 crate::dto::fleet_registry::FleetSubnetRootDrainingPublicationResponse {
358 root_draining: draining,
359 previous_version: publication.expected_registry.clone(),
360 version: FleetRegistryVersion {
361 authority: publication.expected_registry.authority.clone(),
362 revision: publication.expected_registry.revision + 1,
363 content_hash: [19; 32],
364 },
365 };
366 let publication_bytes =
367 candid::encode_one(&publication).expect("encode root draining publication");
368 let publication_response_bytes = candid::encode_one(&publication_response)
369 .expect("encode root draining publication response");
370 assert_eq!(
371 candid::decode_one::<
372 crate::dto::fleet_registry::FleetSubnetRootDrainingPublicationRequest,
373 >(&publication_bytes)
374 .expect("decode root draining publication"),
375 publication
376 );
377 assert_eq!(
378 candid::decode_one::<
379 crate::dto::fleet_registry::FleetSubnetRootDrainingPublicationResponse,
380 >(&publication_response_bytes)
381 .expect("decode root draining publication response"),
382 publication_response
383 );
384
385 let final_inventory = final_inventory_response(&publication_response.root_draining);
386 let removal_request = FleetSubnetRootRemovalRequest {
387 operation_id: final_inventory.operation_id,
388 expected_registry: publication_response.version.clone(),
389 };
390 let removal_status = FleetSubnetRootRemovalStatusRequest {
391 operation_id: final_inventory.operation_id,
392 };
393 let coordinator_request =
394 crate::dto::fleet_registry::FleetSubnetRootRemovalPublicationRequest {
395 expected_registry: publication_response.version.clone(),
396 final_inventory: final_inventory.clone(),
397 };
398 let coordinator_response =
399 crate::dto::fleet_registry::FleetSubnetRootRemovalPublicationResponse {
400 final_inventory,
401 previous_version: publication_response.version.clone(),
402 version: FleetRegistryVersion {
403 authority: publication_response.version.authority.clone(),
404 revision: publication_response.version.revision + 1,
405 content_hash: [29; 32],
406 },
407 };
408 assert_candid_round_trip(&removal_request);
409 assert_candid_round_trip(&removal_status);
410 assert_candid_round_trip(&coordinator_request);
411 assert_candid_round_trip(&coordinator_response);
412
413 let reclamation_request = FleetSubnetRootStoreReclamationRequest {
414 operation_id: coordinator_response.final_inventory.operation_id,
415 expected_final_inventory_hash: coordinator_response.final_inventory.inventory_hash,
416 };
417 let reclamation_status = FleetSubnetRootStoreReclamationStatusRequest {
418 operation_id: reclamation_request.operation_id,
419 };
420 let reclamation_response = FleetSubnetRootStoreReclamationResponse {
421 operation_id: reclamation_request.operation_id,
422 fleet_subnet_root: coordinator_response.final_inventory.fleet_subnet_root,
423 wasm_store: coordinator_response.final_inventory.wasm_store,
424 final_inventory_hash: reclamation_request.expected_final_inventory_hash,
425 reclaimed_store_bytes: coordinator_response
426 .final_inventory
427 .wasm_store_occupied_bytes,
428 reclaimed_catalog_entries: coordinator_response
429 .final_inventory
430 .wasm_store_catalog_entries,
431 reclaimed_template_count: coordinator_response
432 .final_inventory
433 .wasm_store_template_count,
434 reclaimed_release_count: coordinator_response
435 .final_inventory
436 .wasm_store_release_count,
437 gc_prepared_at_secs: coordinator_response
438 .final_inventory
439 .wasm_store_gc_prepared_at_secs,
440 gc_started_at_secs: 30,
441 gc_completed_at_secs: 31,
442 gc_runs_completed: 1,
443 completed_at_ns: 32,
444 reclamation_hash: [33; 32],
445 };
446 assert_candid_round_trip(&reclamation_request);
447 assert_candid_round_trip(&reclamation_status);
448 assert_candid_round_trip(&reclamation_response);
449
450 let finalization =
451 assert_store_binding_finalization_contract_round_trip(&reclamation_response);
452 assert_store_deletion_contract_round_trip(&finalization);
453 }
454
455 fn assert_store_binding_finalization_contract_round_trip(
456 reclamation: &FleetSubnetRootStoreReclamationResponse,
457 ) -> FleetSubnetRootStoreBindingFinalizationResponse {
458 let request = FleetSubnetRootStoreBindingFinalizationRequest {
459 operation_id: reclamation.operation_id,
460 expected_reclamation_hash: reclamation.reclamation_hash,
461 };
462 let status = FleetSubnetRootStoreBindingFinalizationStatusRequest {
463 operation_id: request.operation_id,
464 };
465 let response = FleetSubnetRootStoreBindingFinalizationResponse {
466 operation_id: request.operation_id,
467 fleet_subnet_root: reclamation.fleet_subnet_root,
468 wasm_store: reclamation.wasm_store,
469 final_inventory_hash: reclamation.final_inventory_hash,
470 reclamation_hash: request.expected_reclamation_hash,
471 source_generation: 4,
472 finalized_generation: 7,
473 finalized_at_secs: 34,
474 completed_at_ns: 35,
475 finalization_hash: [36; 32],
476 };
477 assert_candid_round_trip(&request);
478 assert_candid_round_trip(&status);
479 assert_candid_round_trip(&response);
480 response
481 }
482
483 fn assert_store_deletion_contract_round_trip(
484 finalization: &FleetSubnetRootStoreBindingFinalizationResponse,
485 ) {
486 let request = FleetSubnetRootStoreDeletionRequest {
487 operation_id: finalization.operation_id,
488 expected_binding_finalization_hash: finalization.finalization_hash,
489 };
490 let status = FleetSubnetRootStoreDeletionStatusRequest {
491 operation_id: request.operation_id,
492 };
493 let response = FleetSubnetRootStoreDeletionResponse {
494 operation_id: request.operation_id,
495 fleet_subnet_root: finalization.fleet_subnet_root,
496 wasm_store: finalization.wasm_store,
497 binding_finalization_hash: finalization.finalization_hash,
498 observed_module_hash: [37; 32],
499 observed_controllers: vec![finalization.fleet_subnet_root],
500 observed_cycles_before_reclamation: 500,
501 maximum_cycles_to_retain: 100,
502 observed_cycles_after_reclamation: 90,
503 cycles_reclaimed_at_ns: 38,
504 prepared_at_ns: 38,
505 observed_absent_at_ns: 39,
506 completed_at_ns: 40,
507 deletion_hash: [41; 32],
508 };
509 assert_candid_round_trip(&request);
510 assert_candid_round_trip(&status);
511 assert_candid_round_trip(&response);
512 }
513
514 fn assert_candid_round_trip<T>(value: &T)
515 where
516 T: CandidType + for<'de> candid::Deserialize<'de> + Eq + std::fmt::Debug,
517 {
518 let bytes = candid::encode_one(value).expect("encode Candid contract");
519 assert_eq!(
520 &candid::decode_one::<T>(&bytes).expect("decode Candid contract"),
521 value,
522 );
523 }
524
525 fn canister_summary() -> FleetSubnetRootCanisterSummary {
526 FleetSubnetRootCanisterSummary {
527 fleet_registry: FleetRegistryVersion {
528 authority: FleetRegistryAuthority {
529 binding: FleetCoordinatorBinding {
530 fleet: FleetBinding {
531 fleet: FleetKey {
532 canonical_network_id: CanonicalNetworkId::ic_mainnet(),
533 fleet_id: FleetId::from_generated_bytes([1; 32]),
534 },
535 app: AppId::from("toko"),
536 },
537 coordinator_subnet: SubnetId::from_principal(Principal::from_slice(
538 &[2; 29],
539 )),
540 coordinator: Principal::from_slice(&[3; 29]),
541 },
542 epoch: 1,
543 },
544 revision: 4,
545 content_hash: [5; 32],
546 },
547 placement_subnet: SubnetId::from_principal(Principal::from_slice(&[6; 29])),
548 fleet_subnet_root: Principal::from_slice(&[7; 29]),
549 status: FleetSubnetRootStatus::Active,
550 infrastructure_canisters: 2,
551 component_canisters: 3,
552 total_canisters: 5,
553 }
554 }
555
556 fn draining_response(
557 summary: &FleetSubnetRootCanisterSummary,
558 ) -> FleetSubnetRootDrainingResponse {
559 FleetSubnetRootDrainingResponse {
560 operation_id: [8; 32],
561 fleet_subnet_root: summary.fleet_subnet_root,
562 placement_subnet: summary.placement_subnet,
563 active_registry: summary.fleet_registry.clone(),
564 component_topology_digest: ComponentTopologyDigest::from_bytes([9; 32]),
565 active_release_set: FleetSubnetRootReleaseSet {
566 release_build_id: crate::ids::ReleaseBuildId::from_nonce(
567 crate::ids::ReleaseBuildNonce::from_random_bytes([10; 32]),
568 ),
569 manifest_digest: crate::ids::ReleaseSetDigest::from_bytes([11; 32]),
570 },
571 next_allocation_sequence: 12,
572 reserved_component_instances: 13,
573 committed_component_instances: 14,
574 managed_descendants: 15,
575 known_created_component_canisters: 16,
576 root_registry_encoded_bytes: 17_000,
577 started_at_ns: 18,
578 }
579 }
580
581 fn final_inventory_response(
582 draining: &FleetSubnetRootDrainingResponse,
583 ) -> FleetSubnetRootFinalInventoryResponse {
584 FleetSubnetRootFinalInventoryResponse {
585 operation_id: draining.operation_id,
586 fleet_subnet_root: draining.fleet_subnet_root,
587 placement_subnet: draining.placement_subnet,
588 registry: draining.active_registry.clone(),
589 component_topology_digest: draining.component_topology_digest,
590 active_release_set: draining.active_release_set,
591 next_allocation_sequence: draining.next_allocation_sequence,
592 removed_component_instances: 12,
593 terminal_component_history_hash: [19; 32],
594 root_registry_encoded_bytes: 20_000,
595 wasm_store: Principal::from_slice(&[21; 29]),
596 wasm_store_catalog_hash: [22; 32],
597 wasm_store_catalog_entries: 23,
598 wasm_store_occupied_bytes: 24_000,
599 wasm_store_template_count: 25,
600 wasm_store_release_count: 26,
601 wasm_store_gc_prepared_at_secs: 27,
602 finalized_at_ns: 28,
603 inventory_hash: [29; 32],
604 }
605 }
606}