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