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