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