1use crate::{
8 dto::fleet_registry::{FleetRegistryVersion, FleetSubnetRootStatus},
9 ids::{
10 ComponentTopologyDigest, FleetSubnetRootBinding, FleetSubnetRootReleaseSet,
11 FleetSubnetWasmStoreAuthority, SubnetId,
12 },
13};
14use candid::{CandidType, Principal};
15use serde::{Deserialize, Serialize};
16
17pub const FLEET_SUBNET_ROOT_DELETION_EXECUTION_RESERVE_CYCLES: u128 = 100_000_000_000;
19
20pub const FLEET_SUBNET_ROOT_DELETION_CALL_REFUND_HEADROOM_CYCLES: u128 = 50_000_000_000;
22
23pub const FLEET_SUBNET_ROOT_DELETION_MAXIMUM_RETAINED_CYCLES: u128 = 1_000_000_000_000;
25
26#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq)]
33pub struct FleetSubnetRootAuthority {
34 pub binding: FleetSubnetRootBinding,
35 pub initial_release_set: FleetSubnetRootReleaseSet,
36 pub expected_module_hash: [u8; 32],
37 pub wasm_store_authority: FleetSubnetWasmStoreAuthority,
38}
39
40#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq)]
47pub struct FleetSubnetWasmStoreInitArgs {
48 pub authority: FleetSubnetWasmStoreAuthority,
49 pub install_id: [u8; 32],
50}
51
52#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
54#[serde(deny_unknown_fields)]
55pub struct FleetSubnetWasmStoreAdoptionRequest {
56 pub operation_id: [u8; 32],
57 pub authority: FleetSubnetWasmStoreAuthority,
58}
59
60#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
62#[serde(deny_unknown_fields)]
63pub struct FleetSubnetWasmStoreAdoptionResponse {
64 pub operation_id: [u8; 32],
65 pub authority: FleetSubnetWasmStoreAuthority,
66 pub temporary_controllers: Vec<Principal>,
67 pub final_controllers: Vec<Principal>,
68 pub adopted_at_ns: u64,
69}
70
71#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
78pub struct FleetSubnetRootCanisterSummary {
79 pub fleet_registry: FleetRegistryVersion,
80 pub placement_subnet: SubnetId,
81 pub fleet_subnet_root: Principal,
82 pub status: FleetSubnetRootStatus,
83 pub infrastructure_canisters: u32,
84 pub component_canisters: u32,
85 pub pooled_canisters: u32,
86 pub total_canisters: u32,
87}
88
89#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
96pub struct FleetSubnetRootDrainingRequest {
97 pub operation_id: [u8; 32],
98 pub expected_registry: FleetRegistryVersion,
99}
100
101#[derive(CandidType, Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
108pub struct FleetSubnetRootDrainingStatusRequest {
109 pub operation_id: [u8; 32],
110}
111
112#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
119pub struct FleetSubnetRootDrainingResponse {
120 pub operation_id: [u8; 32],
121 pub fleet_subnet_root: Principal,
122 pub placement_subnet: SubnetId,
123 pub active_registry: FleetRegistryVersion,
124 pub component_topology_digest: ComponentTopologyDigest,
125 pub active_release_set: FleetSubnetRootReleaseSet,
126 pub next_allocation_sequence: u64,
127 pub reserved_component_instances: u32,
128 pub committed_component_instances: u32,
129 pub managed_descendants: u32,
130 pub known_created_component_canisters: u32,
131 pub root_registry_encoded_bytes: u64,
132 pub started_at_ns: u64,
133}
134
135#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
142pub struct FleetSubnetRootFinalInventoryRequest {
143 pub operation_id: [u8; 32],
144 pub expected_registry: FleetRegistryVersion,
145}
146
147#[derive(CandidType, Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
154pub struct FleetSubnetRootFinalInventoryStatusRequest {
155 pub operation_id: [u8; 32],
156}
157
158#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
165pub struct FleetSubnetRootRemovalRequest {
166 pub operation_id: [u8; 32],
167 pub expected_registry: FleetRegistryVersion,
168}
169
170#[derive(CandidType, Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
172pub struct FleetSubnetRootRemovalStatusRequest {
173 pub operation_id: [u8; 32],
174}
175
176#[derive(CandidType, Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
183pub struct FleetSubnetRootStoreReclamationRequest {
184 pub operation_id: [u8; 32],
185 pub expected_final_inventory_hash: [u8; 32],
186}
187
188#[derive(CandidType, Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
190pub struct FleetSubnetRootStoreReclamationStatusRequest {
191 pub operation_id: [u8; 32],
192}
193
194#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
201pub struct FleetSubnetRootStoreReclamationResponse {
202 pub operation_id: [u8; 32],
203 pub fleet_subnet_root: Principal,
204 pub wasm_store: Principal,
205 pub final_inventory_hash: [u8; 32],
206 pub reclaimed_store_bytes: u64,
207 pub reclaimed_catalog_entries: u32,
208 pub reclaimed_template_count: u32,
209 pub reclaimed_release_count: u32,
210 pub gc_prepared_at_secs: u64,
211 pub gc_started_at_secs: u64,
212 pub gc_completed_at_secs: u64,
213 pub gc_runs_completed: u32,
214 pub completed_at_ns: u64,
215 pub reclamation_hash: [u8; 32],
216}
217
218#[derive(CandidType, Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
220pub struct FleetSubnetRootStoreBindingFinalizationRequest {
221 pub operation_id: [u8; 32],
222 pub expected_reclamation_hash: [u8; 32],
223}
224
225#[derive(CandidType, Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
227pub struct FleetSubnetRootStoreBindingFinalizationStatusRequest {
228 pub operation_id: [u8; 32],
229}
230
231#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
233pub struct FleetSubnetRootStoreBindingFinalizationResponse {
234 pub operation_id: [u8; 32],
235 pub fleet_subnet_root: Principal,
236 pub wasm_store: Principal,
237 pub final_inventory_hash: [u8; 32],
238 pub reclamation_hash: [u8; 32],
239 pub source_generation: u64,
240 pub finalized_generation: u64,
241 pub finalized_at_secs: u64,
242 pub completed_at_ns: u64,
243 pub finalization_hash: [u8; 32],
244}
245
246#[derive(CandidType, Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
248pub struct FleetSubnetRootStoreDeletionRequest {
249 pub operation_id: [u8; 32],
250 pub expected_binding_finalization_hash: [u8; 32],
251}
252
253#[derive(CandidType, Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
255pub struct FleetSubnetRootStoreDeletionStatusRequest {
256 pub operation_id: [u8; 32],
257}
258
259#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
261pub struct FleetSubnetRootStoreDeletionResponse {
262 pub operation_id: [u8; 32],
263 pub fleet_subnet_root: Principal,
264 pub wasm_store: Principal,
265 pub binding_finalization_hash: [u8; 32],
266 pub observed_module_hash: [u8; 32],
267 pub observed_controllers: Vec<Principal>,
268 pub observed_cycles_before_reclamation: u128,
269 pub maximum_cycles_to_retain: u128,
270 pub observed_cycles_after_reclamation: u128,
271 pub cycles_reclaimed_at_ns: u64,
272 pub prepared_at_ns: u64,
273 pub observed_absent_at_ns: u64,
274 pub completed_at_ns: u64,
275 pub deletion_hash: [u8; 32],
276}
277
278#[derive(CandidType, Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
280pub struct FleetSubnetRootDeletionPreparationRequest {
281 pub operation_id: [u8; 32],
282 pub expected_store_deletion_hash: [u8; 32],
283 pub maximum_cycles_to_retain: u128,
284 pub observed_reserved_cycles: u128,
285 pub observed_idle_cycles_burned_per_day: u128,
286 pub observed_freezing_threshold_seconds: u128,
287}
288
289#[derive(CandidType, Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
291pub struct FleetSubnetRootDeletionPreparationStatusRequest {
292 pub operation_id: [u8; 32],
293}
294
295#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
297pub struct FleetSubnetRootDeletionPreparationResponse {
298 pub operation_id: [u8; 32],
299 pub fleet_subnet_root: Principal,
300 pub coordinator: Principal,
301 pub final_inventory_hash: [u8; 32],
302 pub store_deletion_hash: [u8; 32],
303 pub observed_cycles_before_reclamation: u128,
304 pub maximum_cycles_to_retain: u128,
305 pub observed_reserved_cycles: u128,
306 pub observed_idle_cycles_burned_per_day: u128,
307 pub observed_freezing_threshold_seconds: u128,
308 pub observed_cycles_after_reclamation: u128,
309 pub cycles_reclaimed_at_ns: u64,
310 pub coordinator_intent_hash: [u8; 32],
311 pub coordinator_readiness_hash: [u8; 32],
312 pub prepared_at_ns: u64,
313 pub completed_at_ns: u64,
314}
315
316#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
323pub struct FleetSubnetRootFinalInventoryResponse {
324 pub operation_id: [u8; 32],
325 pub fleet_subnet_root: Principal,
326 pub placement_subnet: SubnetId,
327 pub registry: FleetRegistryVersion,
328 pub component_topology_digest: ComponentTopologyDigest,
329 pub active_release_set: FleetSubnetRootReleaseSet,
330 pub next_allocation_sequence: u64,
331 pub removed_component_instances: u32,
332 pub terminal_component_history_hash: [u8; 32],
333 pub root_registry_encoded_bytes: u64,
334 pub wasm_store: Principal,
335 pub wasm_store_catalog_hash: [u8; 32],
336 pub wasm_store_catalog_entries: u32,
337 pub wasm_store_occupied_bytes: u64,
338 pub wasm_store_template_count: u32,
339 pub wasm_store_release_count: u32,
340 pub wasm_store_gc_prepared_at_secs: u64,
341 pub finalized_at_ns: u64,
342 pub inventory_hash: [u8; 32],
343}
344
345#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq)]
352pub struct FleetSubnetRootInitArgs {
353 pub authority: FleetSubnetRootAuthority,
354 pub install_id: [u8; 32],
355 pub canister_pool_imports: Vec<Principal>,
357}
358
359#[cfg(test)]
360mod tests {
361 use super::*;
362 use crate::ids::{
363 AppId, CanonicalNetworkId, FleetBinding, FleetCoordinatorBinding, FleetId, FleetKey,
364 FleetRegistryAuthority,
365 };
366
367 #[test]
368 fn canister_summary_and_root_lifecycle_contracts_round_trip_through_candid() {
369 let summary = canister_summary();
370 let candid = candid::encode_one(&summary).expect("encode Canister summary");
371 let decoded: FleetSubnetRootCanisterSummary =
372 candid::decode_one(&candid).expect("decode Canister summary");
373
374 assert_eq!(decoded, summary);
375
376 let draining = draining_response(&summary);
377 let request = FleetSubnetRootDrainingRequest {
378 operation_id: draining.operation_id,
379 expected_registry: draining.active_registry.clone(),
380 };
381 let status = FleetSubnetRootDrainingStatusRequest {
382 operation_id: draining.operation_id,
383 };
384 let request_bytes = candid::encode_one(&request).expect("encode root draining request");
385 let status_bytes = candid::encode_one(status).expect("encode root draining status");
386 let response_bytes = candid::encode_one(&draining).expect("encode root draining response");
387 assert_eq!(
388 candid::decode_one::<FleetSubnetRootDrainingRequest>(&request_bytes)
389 .expect("decode root draining request"),
390 request
391 );
392 assert_eq!(
393 candid::decode_one::<FleetSubnetRootDrainingStatusRequest>(&status_bytes)
394 .expect("decode root draining status"),
395 status
396 );
397 assert_eq!(
398 candid::decode_one::<FleetSubnetRootDrainingResponse>(&response_bytes)
399 .expect("decode root draining response"),
400 draining
401 );
402
403 let inventory = final_inventory_response(&draining);
404 let inventory_request = FleetSubnetRootFinalInventoryRequest {
405 operation_id: inventory.operation_id,
406 expected_registry: inventory.registry.clone(),
407 };
408 let inventory_status = FleetSubnetRootFinalInventoryStatusRequest {
409 operation_id: inventory.operation_id,
410 };
411 let request_bytes =
412 candid::encode_one(&inventory_request).expect("encode root inventory request");
413 let status_bytes =
414 candid::encode_one(inventory_status).expect("encode root inventory status");
415 let response_bytes =
416 candid::encode_one(&inventory).expect("encode root inventory response");
417 assert_eq!(
418 candid::decode_one::<FleetSubnetRootFinalInventoryRequest>(&request_bytes)
419 .expect("decode root inventory request"),
420 inventory_request
421 );
422 assert_eq!(
423 candid::decode_one::<FleetSubnetRootFinalInventoryStatusRequest>(&status_bytes)
424 .expect("decode root inventory status"),
425 inventory_status
426 );
427 assert_eq!(
428 candid::decode_one::<FleetSubnetRootFinalInventoryResponse>(&response_bytes)
429 .expect("decode root inventory response"),
430 inventory
431 );
432 }
433
434 #[test]
435 fn draining_publication_contracts_round_trip_through_candid() {
436 let draining = draining_response(&canister_summary());
437 let publication = crate::dto::fleet_registry::FleetSubnetRootDrainingPublicationRequest {
438 expected_registry: draining.active_registry.clone(),
439 root_draining: draining.clone(),
440 };
441 let publication_response =
442 crate::dto::fleet_registry::FleetSubnetRootDrainingPublicationResponse {
443 root_draining: draining,
444 previous_version: publication.expected_registry.clone(),
445 version: FleetRegistryVersion {
446 authority: publication.expected_registry.authority.clone(),
447 revision: publication.expected_registry.revision + 1,
448 content_hash: [19; 32],
449 },
450 };
451 let publication_bytes =
452 candid::encode_one(&publication).expect("encode root draining publication");
453 let publication_response_bytes = candid::encode_one(&publication_response)
454 .expect("encode root draining publication response");
455 assert_eq!(
456 candid::decode_one::<
457 crate::dto::fleet_registry::FleetSubnetRootDrainingPublicationRequest,
458 >(&publication_bytes)
459 .expect("decode root draining publication"),
460 publication
461 );
462 assert_eq!(
463 candid::decode_one::<
464 crate::dto::fleet_registry::FleetSubnetRootDrainingPublicationResponse,
465 >(&publication_response_bytes)
466 .expect("decode root draining publication response"),
467 publication_response
468 );
469
470 let final_inventory = final_inventory_response(&publication_response.root_draining);
471 let removal_request = FleetSubnetRootRemovalRequest {
472 operation_id: final_inventory.operation_id,
473 expected_registry: publication_response.version.clone(),
474 };
475 let removal_status = FleetSubnetRootRemovalStatusRequest {
476 operation_id: final_inventory.operation_id,
477 };
478 let coordinator_request =
479 crate::dto::fleet_registry::FleetSubnetRootRemovalPublicationRequest {
480 expected_registry: publication_response.version.clone(),
481 final_inventory: final_inventory.clone(),
482 };
483 let coordinator_response =
484 crate::dto::fleet_registry::FleetSubnetRootRemovalPublicationResponse {
485 final_inventory,
486 previous_version: publication_response.version.clone(),
487 version: FleetRegistryVersion {
488 authority: publication_response.version.authority.clone(),
489 revision: publication_response.version.revision + 1,
490 content_hash: [29; 32],
491 },
492 };
493 assert_candid_round_trip(&removal_request);
494 assert_candid_round_trip(&removal_status);
495 assert_candid_round_trip(&coordinator_request);
496 assert_candid_round_trip(&coordinator_response);
497
498 let reclamation_request = FleetSubnetRootStoreReclamationRequest {
499 operation_id: coordinator_response.final_inventory.operation_id,
500 expected_final_inventory_hash: coordinator_response.final_inventory.inventory_hash,
501 };
502 let reclamation_status = FleetSubnetRootStoreReclamationStatusRequest {
503 operation_id: reclamation_request.operation_id,
504 };
505 let reclamation_response = FleetSubnetRootStoreReclamationResponse {
506 operation_id: reclamation_request.operation_id,
507 fleet_subnet_root: coordinator_response.final_inventory.fleet_subnet_root,
508 wasm_store: coordinator_response.final_inventory.wasm_store,
509 final_inventory_hash: reclamation_request.expected_final_inventory_hash,
510 reclaimed_store_bytes: coordinator_response
511 .final_inventory
512 .wasm_store_occupied_bytes,
513 reclaimed_catalog_entries: coordinator_response
514 .final_inventory
515 .wasm_store_catalog_entries,
516 reclaimed_template_count: coordinator_response
517 .final_inventory
518 .wasm_store_template_count,
519 reclaimed_release_count: coordinator_response
520 .final_inventory
521 .wasm_store_release_count,
522 gc_prepared_at_secs: coordinator_response
523 .final_inventory
524 .wasm_store_gc_prepared_at_secs,
525 gc_started_at_secs: 30,
526 gc_completed_at_secs: 31,
527 gc_runs_completed: 1,
528 completed_at_ns: 32,
529 reclamation_hash: [33; 32],
530 };
531 assert_candid_round_trip(&reclamation_request);
532 assert_candid_round_trip(&reclamation_status);
533 assert_candid_round_trip(&reclamation_response);
534
535 let finalization =
536 assert_store_binding_finalization_contract_round_trip(&reclamation_response);
537 assert_store_deletion_contract_round_trip(&finalization);
538 }
539
540 fn assert_store_binding_finalization_contract_round_trip(
541 reclamation: &FleetSubnetRootStoreReclamationResponse,
542 ) -> FleetSubnetRootStoreBindingFinalizationResponse {
543 let request = FleetSubnetRootStoreBindingFinalizationRequest {
544 operation_id: reclamation.operation_id,
545 expected_reclamation_hash: reclamation.reclamation_hash,
546 };
547 let status = FleetSubnetRootStoreBindingFinalizationStatusRequest {
548 operation_id: request.operation_id,
549 };
550 let response = FleetSubnetRootStoreBindingFinalizationResponse {
551 operation_id: request.operation_id,
552 fleet_subnet_root: reclamation.fleet_subnet_root,
553 wasm_store: reclamation.wasm_store,
554 final_inventory_hash: reclamation.final_inventory_hash,
555 reclamation_hash: request.expected_reclamation_hash,
556 source_generation: 4,
557 finalized_generation: 7,
558 finalized_at_secs: 34,
559 completed_at_ns: 35,
560 finalization_hash: [36; 32],
561 };
562 assert_candid_round_trip(&request);
563 assert_candid_round_trip(&status);
564 assert_candid_round_trip(&response);
565 response
566 }
567
568 fn assert_store_deletion_contract_round_trip(
569 finalization: &FleetSubnetRootStoreBindingFinalizationResponse,
570 ) {
571 let request = FleetSubnetRootStoreDeletionRequest {
572 operation_id: finalization.operation_id,
573 expected_binding_finalization_hash: finalization.finalization_hash,
574 };
575 let status = FleetSubnetRootStoreDeletionStatusRequest {
576 operation_id: request.operation_id,
577 };
578 let response = FleetSubnetRootStoreDeletionResponse {
579 operation_id: request.operation_id,
580 fleet_subnet_root: finalization.fleet_subnet_root,
581 wasm_store: finalization.wasm_store,
582 binding_finalization_hash: finalization.finalization_hash,
583 observed_module_hash: [37; 32],
584 observed_controllers: vec![finalization.fleet_subnet_root],
585 observed_cycles_before_reclamation: 500,
586 maximum_cycles_to_retain: 100,
587 observed_cycles_after_reclamation: 90,
588 cycles_reclaimed_at_ns: 38,
589 prepared_at_ns: 38,
590 observed_absent_at_ns: 39,
591 completed_at_ns: 40,
592 deletion_hash: [41; 32],
593 };
594 assert_candid_round_trip(&request);
595 assert_candid_round_trip(&status);
596 assert_candid_round_trip(&response);
597 assert_root_deletion_handoff_contract_round_trip(
598 &response,
599 finalization.final_inventory_hash,
600 );
601 }
602
603 fn assert_root_deletion_handoff_contract_round_trip(
604 store_deletion: &FleetSubnetRootStoreDeletionResponse,
605 final_inventory_hash: [u8; 32],
606 ) {
607 use crate::dto::fleet_registry::{
608 FleetSubnetRootDeletionReadinessIntentRequest,
609 FleetSubnetRootDeletionReadinessIntentResponse,
610 FleetSubnetRootDeletionReadinessRequest, FleetSubnetRootDeletionReadinessResponse,
611 };
612
613 let preparation_request = FleetSubnetRootDeletionPreparationRequest {
614 operation_id: store_deletion.operation_id,
615 expected_store_deletion_hash: store_deletion.deletion_hash,
616 maximum_cycles_to_retain: 100_000_000_001,
617 observed_reserved_cycles: 0,
618 observed_idle_cycles_burned_per_day: 86_400,
619 observed_freezing_threshold_seconds: 1,
620 };
621 let preparation_status = FleetSubnetRootDeletionPreparationStatusRequest {
622 operation_id: store_deletion.operation_id,
623 };
624 let intent_request = FleetSubnetRootDeletionReadinessIntentRequest {
625 operation_id: store_deletion.operation_id,
626 fleet_subnet_root: store_deletion.fleet_subnet_root,
627 final_inventory_hash,
628 store_deletion_hash: store_deletion.deletion_hash,
629 observed_cycles_before_reclamation: 500_000_000_000,
630 maximum_cycles_to_retain: 100_000_000_001,
631 observed_reserved_cycles: 0,
632 observed_idle_cycles_burned_per_day: 86_400,
633 observed_freezing_threshold_seconds: 1,
634 prepared_at_ns: 42,
635 };
636 let intent = FleetSubnetRootDeletionReadinessIntentResponse {
637 request: intent_request.clone(),
638 coordinator: Principal::from_slice(&[43; 29]),
639 recorded_at_ns: 44,
640 intent_hash: [45; 32],
641 };
642 let readiness_request = FleetSubnetRootDeletionReadinessRequest {
643 operation_id: store_deletion.operation_id,
644 fleet_subnet_root: store_deletion.fleet_subnet_root,
645 expected_intent_hash: intent.intent_hash,
646 observed_cycles_after_reclamation: 90_000_000_000,
647 cycles_reclaimed_at_ns: 46,
648 };
649 let readiness = FleetSubnetRootDeletionReadinessResponse {
650 request: readiness_request.clone(),
651 coordinator: intent.coordinator,
652 final_inventory_hash,
653 store_deletion_hash: store_deletion.deletion_hash,
654 observed_cycles_before_reclamation: 500_000_000_000,
655 maximum_cycles_to_retain: 100_000_000_001,
656 observed_reserved_cycles: 0,
657 observed_idle_cycles_burned_per_day: 86_400,
658 observed_freezing_threshold_seconds: 1,
659 prepared_at_ns: intent.request.prepared_at_ns,
660 recorded_at_ns: 47,
661 readiness_hash: [48; 32],
662 };
663 assert_root_deletion_execution_contract_round_trip(store_deletion, &readiness);
664
665 let preparation = FleetSubnetRootDeletionPreparationResponse {
666 operation_id: store_deletion.operation_id,
667 fleet_subnet_root: store_deletion.fleet_subnet_root,
668 coordinator: intent.coordinator,
669 final_inventory_hash,
670 store_deletion_hash: store_deletion.deletion_hash,
671 observed_cycles_before_reclamation: 500_000_000_000,
672 maximum_cycles_to_retain: 100_000_000_001,
673 observed_reserved_cycles: 0,
674 observed_idle_cycles_burned_per_day: 86_400,
675 observed_freezing_threshold_seconds: 1,
676 observed_cycles_after_reclamation: 90_000_000_000,
677 cycles_reclaimed_at_ns: readiness_request.cycles_reclaimed_at_ns,
678 coordinator_intent_hash: intent.intent_hash,
679 coordinator_readiness_hash: readiness.readiness_hash,
680 prepared_at_ns: intent.request.prepared_at_ns,
681 completed_at_ns: 56,
682 };
683
684 assert_candid_round_trip(&preparation_request);
685 assert_candid_round_trip(&preparation_status);
686 assert_candid_round_trip(&preparation);
687 assert_candid_round_trip(&intent_request);
688 assert_candid_round_trip(&intent);
689 assert_candid_round_trip(&readiness_request);
690 assert_candid_round_trip(&readiness);
691 }
692
693 fn assert_root_deletion_execution_contract_round_trip(
694 store_deletion: &FleetSubnetRootStoreDeletionResponse,
695 readiness: &crate::dto::fleet_registry::FleetSubnetRootDeletionReadinessResponse,
696 ) {
697 use crate::dto::fleet_registry::{
698 FleetSubnetRootDeletionCompletionRequest, FleetSubnetRootDeletionExecutionRequest,
699 FleetSubnetRootDeletionExecutionResponse, FleetSubnetRootDeletionResponse,
700 FleetSubnetRootDeletionStatusRequest,
701 };
702
703 let executor = Principal::from_slice(&[49; 29]);
704 let execution_request = FleetSubnetRootDeletionExecutionRequest {
705 operation_id: store_deletion.operation_id,
706 fleet_subnet_root: store_deletion.fleet_subnet_root,
707 expected_readiness_hash: readiness.readiness_hash,
708 observed_module_hash: [50; 32],
709 observed_controllers: vec![executor],
710 observed_cycles_after_reclamation: 90_000_000_000,
711 observed_reserved_cycles: 0,
712 observed_idle_cycles_burned_per_day: 86_400,
713 observed_freezing_threshold_seconds: 1,
714 };
715 let execution = FleetSubnetRootDeletionExecutionResponse {
716 request: execution_request.clone(),
717 executor,
718 prepared_at_ns: 51,
719 execution_hash: [52; 32],
720 };
721 let completion_request = FleetSubnetRootDeletionCompletionRequest {
722 operation_id: store_deletion.operation_id,
723 fleet_subnet_root: store_deletion.fleet_subnet_root,
724 expected_execution_hash: execution.execution_hash,
725 observed_absent_at_ns: 53,
726 };
727 let status = FleetSubnetRootDeletionStatusRequest {
728 operation_id: store_deletion.operation_id,
729 fleet_subnet_root: store_deletion.fleet_subnet_root,
730 };
731 let deletion = FleetSubnetRootDeletionResponse {
732 operation_id: store_deletion.operation_id,
733 fleet_subnet_root: store_deletion.fleet_subnet_root,
734 coordinator: readiness.coordinator,
735 executor,
736 readiness_hash: readiness.readiness_hash,
737 execution_hash: execution.execution_hash,
738 observed_module_hash: execution_request.observed_module_hash,
739 observed_controllers: execution_request.observed_controllers.clone(),
740 observed_cycles_after_reclamation: 90_000_000_000,
741 observed_absent_at_ns: completion_request.observed_absent_at_ns,
742 completed_at_ns: 54,
743 deletion_hash: [55; 32],
744 };
745 assert_candid_round_trip(&execution_request);
746 assert_candid_round_trip(&execution);
747 assert_candid_round_trip(&completion_request);
748 assert_candid_round_trip(&status);
749 assert_candid_round_trip(&deletion);
750 }
751
752 fn assert_candid_round_trip<T>(value: &T)
753 where
754 T: CandidType + for<'de> candid::Deserialize<'de> + Eq + std::fmt::Debug,
755 {
756 let bytes = candid::encode_one(value).expect("encode Candid contract");
757 assert_eq!(
758 &candid::decode_one::<T>(&bytes).expect("decode Candid contract"),
759 value,
760 );
761 }
762
763 fn canister_summary() -> FleetSubnetRootCanisterSummary {
764 FleetSubnetRootCanisterSummary {
765 fleet_registry: FleetRegistryVersion {
766 authority: FleetRegistryAuthority {
767 binding: FleetCoordinatorBinding {
768 fleet: FleetBinding {
769 fleet: FleetKey {
770 canonical_network_id: CanonicalNetworkId::ic_mainnet(),
771 fleet_id: FleetId::from_generated_bytes([1; 32]),
772 },
773 app: AppId::from("toko"),
774 },
775 coordinator_subnet: SubnetId::from_principal(Principal::from_slice(
776 &[2; 29],
777 )),
778 coordinator: Principal::from_slice(&[3; 29]),
779 },
780 epoch: 1,
781 },
782 revision: 4,
783 content_hash: [5; 32],
784 },
785 placement_subnet: SubnetId::from_principal(Principal::from_slice(&[6; 29])),
786 fleet_subnet_root: Principal::from_slice(&[7; 29]),
787 status: FleetSubnetRootStatus::Active,
788 infrastructure_canisters: 2,
789 component_canisters: 3,
790 pooled_canisters: 4,
791 total_canisters: 9,
792 }
793 }
794
795 fn draining_response(
796 summary: &FleetSubnetRootCanisterSummary,
797 ) -> FleetSubnetRootDrainingResponse {
798 FleetSubnetRootDrainingResponse {
799 operation_id: [8; 32],
800 fleet_subnet_root: summary.fleet_subnet_root,
801 placement_subnet: summary.placement_subnet,
802 active_registry: summary.fleet_registry.clone(),
803 component_topology_digest: ComponentTopologyDigest::from_bytes([9; 32]),
804 active_release_set: FleetSubnetRootReleaseSet {
805 release_build_id: crate::ids::ReleaseBuildId::from_nonce(
806 crate::ids::ReleaseBuildNonce::from_random_bytes([10; 32]),
807 ),
808 manifest_digest: crate::ids::ReleaseSetDigest::from_bytes([11; 32]),
809 },
810 next_allocation_sequence: 12,
811 reserved_component_instances: 13,
812 committed_component_instances: 14,
813 managed_descendants: 15,
814 known_created_component_canisters: 16,
815 root_registry_encoded_bytes: 17_000,
816 started_at_ns: 18,
817 }
818 }
819
820 fn final_inventory_response(
821 draining: &FleetSubnetRootDrainingResponse,
822 ) -> FleetSubnetRootFinalInventoryResponse {
823 FleetSubnetRootFinalInventoryResponse {
824 operation_id: draining.operation_id,
825 fleet_subnet_root: draining.fleet_subnet_root,
826 placement_subnet: draining.placement_subnet,
827 registry: draining.active_registry.clone(),
828 component_topology_digest: draining.component_topology_digest,
829 active_release_set: draining.active_release_set,
830 next_allocation_sequence: draining.next_allocation_sequence,
831 removed_component_instances: 12,
832 terminal_component_history_hash: [19; 32],
833 root_registry_encoded_bytes: 20_000,
834 wasm_store: Principal::from_slice(&[21; 29]),
835 wasm_store_catalog_hash: [22; 32],
836 wasm_store_catalog_entries: 23,
837 wasm_store_occupied_bytes: 24_000,
838 wasm_store_template_count: 25,
839 wasm_store_release_count: 26,
840 wasm_store_gc_prepared_at_secs: 27,
841 finalized_at_ns: 28,
842 inventory_hash: [29; 32],
843 }
844 }
845}