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 = 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 temporary_controllers: Vec<Principal>,
64 pub final_controllers: Vec<Principal>,
65 pub adopted_at_ns: u64,
66}
67
68#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
75pub struct FleetSubnetRootCanisterSummary {
76 pub fleet_registry: FleetRegistryVersion,
77 pub placement_subnet: SubnetId,
78 pub fleet_subnet_root: Principal,
79 pub status: FleetSubnetRootStatus,
80 pub infrastructure_canisters: u32,
81 pub component_canisters: u32,
82 pub pooled_canisters: u32,
83 pub total_canisters: u32,
84}
85
86#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
93pub struct FleetSubnetRootDrainingRequest {
94 pub operation_id: [u8; 32],
95 pub expected_registry: FleetRegistryVersion,
96}
97
98#[derive(CandidType, Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
105pub struct FleetSubnetRootDrainingStatusRequest {
106 pub operation_id: [u8; 32],
107}
108
109#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
116pub struct FleetSubnetRootDrainingResponse {
117 pub operation_id: [u8; 32],
118 pub fleet_subnet_root: Principal,
119 pub placement_subnet: SubnetId,
120 pub active_registry: FleetRegistryVersion,
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 canister_pool_imports: Vec<Principal>,
354}
355
356#[cfg(test)]
357mod tests {
358 use super::*;
359 use crate::ids::{
360 AppId, CanonicalNetworkId, FleetBinding, FleetCoordinatorBinding, FleetId, FleetKey,
361 FleetRegistryAuthority,
362 };
363
364 #[test]
365 fn canister_summary_and_root_lifecycle_contracts_round_trip_through_candid() {
366 let summary = canister_summary();
367 let candid = candid::encode_one(&summary).expect("encode Canister summary");
368 let decoded: FleetSubnetRootCanisterSummary =
369 candid::decode_one(&candid).expect("decode Canister summary");
370
371 assert_eq!(decoded, summary);
372
373 let draining = draining_response(&summary);
374 let request = FleetSubnetRootDrainingRequest {
375 operation_id: draining.operation_id,
376 expected_registry: draining.active_registry.clone(),
377 };
378 let status = FleetSubnetRootDrainingStatusRequest {
379 operation_id: draining.operation_id,
380 };
381 let request_bytes = candid::encode_one(&request).expect("encode root draining request");
382 let status_bytes = candid::encode_one(status).expect("encode root draining status");
383 let response_bytes = candid::encode_one(&draining).expect("encode root draining response");
384 assert_eq!(
385 candid::decode_one::<FleetSubnetRootDrainingRequest>(&request_bytes)
386 .expect("decode root draining request"),
387 request
388 );
389 assert_eq!(
390 candid::decode_one::<FleetSubnetRootDrainingStatusRequest>(&status_bytes)
391 .expect("decode root draining status"),
392 status
393 );
394 assert_eq!(
395 candid::decode_one::<FleetSubnetRootDrainingResponse>(&response_bytes)
396 .expect("decode root draining response"),
397 draining
398 );
399
400 let inventory = final_inventory_response(&draining);
401 let inventory_request = FleetSubnetRootFinalInventoryRequest {
402 operation_id: inventory.operation_id,
403 expected_registry: inventory.registry.clone(),
404 };
405 let inventory_status = FleetSubnetRootFinalInventoryStatusRequest {
406 operation_id: inventory.operation_id,
407 };
408 let request_bytes =
409 candid::encode_one(&inventory_request).expect("encode root inventory request");
410 let status_bytes =
411 candid::encode_one(inventory_status).expect("encode root inventory status");
412 let response_bytes =
413 candid::encode_one(&inventory).expect("encode root inventory response");
414 assert_eq!(
415 candid::decode_one::<FleetSubnetRootFinalInventoryRequest>(&request_bytes)
416 .expect("decode root inventory request"),
417 inventory_request
418 );
419 assert_eq!(
420 candid::decode_one::<FleetSubnetRootFinalInventoryStatusRequest>(&status_bytes)
421 .expect("decode root inventory status"),
422 inventory_status
423 );
424 assert_eq!(
425 candid::decode_one::<FleetSubnetRootFinalInventoryResponse>(&response_bytes)
426 .expect("decode root inventory response"),
427 inventory
428 );
429 }
430
431 #[test]
432 fn draining_publication_contracts_round_trip_through_candid() {
433 let draining = draining_response(&canister_summary());
434 let publication = crate::dto::fleet_registry::FleetSubnetRootDrainingPublicationRequest {
435 expected_registry: draining.active_registry.clone(),
436 root_draining: draining.clone(),
437 };
438 let publication_response =
439 crate::dto::fleet_registry::FleetSubnetRootDrainingPublicationResponse {
440 root_draining: draining,
441 previous_version: publication.expected_registry.clone(),
442 version: FleetRegistryVersion {
443 authority: publication.expected_registry.authority.clone(),
444 revision: publication.expected_registry.revision + 1,
445 content_hash: [19; 32],
446 },
447 };
448 let publication_bytes =
449 candid::encode_one(&publication).expect("encode root draining publication");
450 let publication_response_bytes = candid::encode_one(&publication_response)
451 .expect("encode root draining publication response");
452 assert_eq!(
453 candid::decode_one::<
454 crate::dto::fleet_registry::FleetSubnetRootDrainingPublicationRequest,
455 >(&publication_bytes)
456 .expect("decode root draining publication"),
457 publication
458 );
459 assert_eq!(
460 candid::decode_one::<
461 crate::dto::fleet_registry::FleetSubnetRootDrainingPublicationResponse,
462 >(&publication_response_bytes)
463 .expect("decode root draining publication response"),
464 publication_response
465 );
466
467 let final_inventory = final_inventory_response(&publication_response.root_draining);
468 let removal_request = FleetSubnetRootRemovalRequest {
469 operation_id: final_inventory.operation_id,
470 expected_registry: publication_response.version.clone(),
471 };
472 let removal_status = FleetSubnetRootRemovalStatusRequest {
473 operation_id: final_inventory.operation_id,
474 };
475 let coordinator_request =
476 crate::dto::fleet_registry::FleetSubnetRootRemovalPublicationRequest {
477 expected_registry: publication_response.version.clone(),
478 final_inventory: final_inventory.clone(),
479 };
480 let coordinator_response =
481 crate::dto::fleet_registry::FleetSubnetRootRemovalPublicationResponse {
482 final_inventory,
483 previous_version: publication_response.version.clone(),
484 version: FleetRegistryVersion {
485 authority: publication_response.version.authority.clone(),
486 revision: publication_response.version.revision + 1,
487 content_hash: [29; 32],
488 },
489 };
490 assert_candid_round_trip(&removal_request);
491 assert_candid_round_trip(&removal_status);
492 assert_candid_round_trip(&coordinator_request);
493 assert_candid_round_trip(&coordinator_response);
494
495 let reclamation_request = FleetSubnetRootStoreReclamationRequest {
496 operation_id: coordinator_response.final_inventory.operation_id,
497 expected_final_inventory_hash: coordinator_response.final_inventory.inventory_hash,
498 };
499 let reclamation_status = FleetSubnetRootStoreReclamationStatusRequest {
500 operation_id: reclamation_request.operation_id,
501 };
502 let reclamation_response = FleetSubnetRootStoreReclamationResponse {
503 operation_id: reclamation_request.operation_id,
504 fleet_subnet_root: coordinator_response.final_inventory.fleet_subnet_root,
505 wasm_store: coordinator_response.final_inventory.wasm_store,
506 final_inventory_hash: reclamation_request.expected_final_inventory_hash,
507 reclaimed_store_bytes: coordinator_response
508 .final_inventory
509 .wasm_store_occupied_bytes,
510 reclaimed_catalog_entries: coordinator_response
511 .final_inventory
512 .wasm_store_catalog_entries,
513 reclaimed_template_count: coordinator_response
514 .final_inventory
515 .wasm_store_template_count,
516 reclaimed_release_count: coordinator_response
517 .final_inventory
518 .wasm_store_release_count,
519 gc_prepared_at_secs: coordinator_response
520 .final_inventory
521 .wasm_store_gc_prepared_at_secs,
522 gc_started_at_secs: 30,
523 gc_completed_at_secs: 31,
524 gc_runs_completed: 1,
525 completed_at_ns: 32,
526 reclamation_hash: [33; 32],
527 };
528 assert_candid_round_trip(&reclamation_request);
529 assert_candid_round_trip(&reclamation_status);
530 assert_candid_round_trip(&reclamation_response);
531
532 let finalization =
533 assert_store_binding_finalization_contract_round_trip(&reclamation_response);
534 assert_store_deletion_contract_round_trip(&finalization);
535 }
536
537 fn assert_store_binding_finalization_contract_round_trip(
538 reclamation: &FleetSubnetRootStoreReclamationResponse,
539 ) -> FleetSubnetRootStoreBindingFinalizationResponse {
540 let request = FleetSubnetRootStoreBindingFinalizationRequest {
541 operation_id: reclamation.operation_id,
542 expected_reclamation_hash: reclamation.reclamation_hash,
543 };
544 let status = FleetSubnetRootStoreBindingFinalizationStatusRequest {
545 operation_id: request.operation_id,
546 };
547 let response = FleetSubnetRootStoreBindingFinalizationResponse {
548 operation_id: request.operation_id,
549 fleet_subnet_root: reclamation.fleet_subnet_root,
550 wasm_store: reclamation.wasm_store,
551 final_inventory_hash: reclamation.final_inventory_hash,
552 reclamation_hash: request.expected_reclamation_hash,
553 source_generation: 4,
554 finalized_generation: 7,
555 finalized_at_secs: 34,
556 completed_at_ns: 35,
557 finalization_hash: [36; 32],
558 };
559 assert_candid_round_trip(&request);
560 assert_candid_round_trip(&status);
561 assert_candid_round_trip(&response);
562 response
563 }
564
565 fn assert_store_deletion_contract_round_trip(
566 finalization: &FleetSubnetRootStoreBindingFinalizationResponse,
567 ) {
568 let request = FleetSubnetRootStoreDeletionRequest {
569 operation_id: finalization.operation_id,
570 expected_binding_finalization_hash: finalization.finalization_hash,
571 };
572 let status = FleetSubnetRootStoreDeletionStatusRequest {
573 operation_id: request.operation_id,
574 };
575 let response = FleetSubnetRootStoreDeletionResponse {
576 operation_id: request.operation_id,
577 fleet_subnet_root: finalization.fleet_subnet_root,
578 wasm_store: finalization.wasm_store,
579 binding_finalization_hash: finalization.finalization_hash,
580 observed_module_hash: [37; 32],
581 observed_controllers: vec![finalization.fleet_subnet_root],
582 observed_cycles_before_reclamation: 500,
583 retained_cycles_target: 100,
584 observed_cycles_after_reclamation: 90,
585 cycles_reclaimed_at_ns: 38,
586 prepared_at_ns: 38,
587 observed_absent_at_ns: 39,
588 completed_at_ns: 40,
589 deletion_hash: [41; 32],
590 };
591 assert_candid_round_trip(&request);
592 assert_candid_round_trip(&status);
593 assert_candid_round_trip(&response);
594 assert_root_deletion_handoff_contract_round_trip(
595 &response,
596 finalization.final_inventory_hash,
597 );
598 }
599
600 fn assert_root_deletion_handoff_contract_round_trip(
601 store_deletion: &FleetSubnetRootStoreDeletionResponse,
602 final_inventory_hash: [u8; 32],
603 ) {
604 use crate::dto::fleet_registry::{
605 FleetSubnetRootDeletionReadinessIntentRequest,
606 FleetSubnetRootDeletionReadinessIntentResponse,
607 FleetSubnetRootDeletionReadinessRequest, FleetSubnetRootDeletionReadinessResponse,
608 };
609
610 let preparation_request = FleetSubnetRootDeletionPreparationRequest {
611 operation_id: store_deletion.operation_id,
612 expected_store_deletion_hash: store_deletion.deletion_hash,
613 retained_cycles_target: 100_000_000_001,
614 observed_reserved_cycles: 0,
615 observed_idle_cycles_burned_per_day: 86_400,
616 observed_freezing_threshold_seconds: 1,
617 };
618 let preparation_status = FleetSubnetRootDeletionPreparationStatusRequest {
619 operation_id: store_deletion.operation_id,
620 };
621 let intent_request = FleetSubnetRootDeletionReadinessIntentRequest {
622 operation_id: store_deletion.operation_id,
623 fleet_subnet_root: store_deletion.fleet_subnet_root,
624 final_inventory_hash,
625 store_deletion_hash: store_deletion.deletion_hash,
626 observed_cycles_before_reclamation: 500_000_000_000,
627 retained_cycles_target: 100_000_000_001,
628 observed_reserved_cycles: 0,
629 observed_idle_cycles_burned_per_day: 86_400,
630 observed_freezing_threshold_seconds: 1,
631 prepared_at_ns: 42,
632 };
633 let intent = FleetSubnetRootDeletionReadinessIntentResponse {
634 request: intent_request.clone(),
635 coordinator: Principal::from_slice(&[43; 29]),
636 recorded_at_ns: 44,
637 intent_hash: [45; 32],
638 };
639 let readiness_request = FleetSubnetRootDeletionReadinessRequest {
640 operation_id: store_deletion.operation_id,
641 fleet_subnet_root: store_deletion.fleet_subnet_root,
642 expected_intent_hash: intent.intent_hash,
643 observed_cycles_after_reclamation: 90_000_000_000,
644 cycles_reclaimed_at_ns: 46,
645 };
646 let readiness = FleetSubnetRootDeletionReadinessResponse {
647 request: readiness_request.clone(),
648 coordinator: intent.coordinator,
649 final_inventory_hash,
650 store_deletion_hash: store_deletion.deletion_hash,
651 observed_cycles_before_reclamation: 500_000_000_000,
652 retained_cycles_target: 100_000_000_001,
653 observed_reserved_cycles: 0,
654 observed_idle_cycles_burned_per_day: 86_400,
655 observed_freezing_threshold_seconds: 1,
656 prepared_at_ns: intent.request.prepared_at_ns,
657 recorded_at_ns: 47,
658 readiness_hash: [48; 32],
659 };
660 assert_root_deletion_execution_contract_round_trip(store_deletion, &readiness);
661
662 let preparation = FleetSubnetRootDeletionPreparationResponse {
663 operation_id: store_deletion.operation_id,
664 fleet_subnet_root: store_deletion.fleet_subnet_root,
665 coordinator: intent.coordinator,
666 final_inventory_hash,
667 store_deletion_hash: store_deletion.deletion_hash,
668 observed_cycles_before_reclamation: 500_000_000_000,
669 retained_cycles_target: 100_000_000_001,
670 observed_reserved_cycles: 0,
671 observed_idle_cycles_burned_per_day: 86_400,
672 observed_freezing_threshold_seconds: 1,
673 observed_cycles_after_reclamation: 90_000_000_000,
674 cycles_reclaimed_at_ns: readiness_request.cycles_reclaimed_at_ns,
675 coordinator_intent_hash: intent.intent_hash,
676 coordinator_readiness_hash: readiness.readiness_hash,
677 prepared_at_ns: intent.request.prepared_at_ns,
678 completed_at_ns: 56,
679 };
680
681 assert_candid_round_trip(&preparation_request);
682 assert_candid_round_trip(&preparation_status);
683 assert_candid_round_trip(&preparation);
684 assert_candid_round_trip(&intent_request);
685 assert_candid_round_trip(&intent);
686 assert_candid_round_trip(&readiness_request);
687 assert_candid_round_trip(&readiness);
688 }
689
690 fn assert_root_deletion_execution_contract_round_trip(
691 store_deletion: &FleetSubnetRootStoreDeletionResponse,
692 readiness: &crate::dto::fleet_registry::FleetSubnetRootDeletionReadinessResponse,
693 ) {
694 use crate::dto::fleet_registry::{
695 FleetSubnetRootDeletionCompletionRequest, FleetSubnetRootDeletionExecutionRequest,
696 FleetSubnetRootDeletionExecutionResponse, FleetSubnetRootDeletionResponse,
697 FleetSubnetRootDeletionStatusRequest,
698 };
699
700 let executor = Principal::from_slice(&[49; 29]);
701 let execution_request = FleetSubnetRootDeletionExecutionRequest {
702 operation_id: store_deletion.operation_id,
703 fleet_subnet_root: store_deletion.fleet_subnet_root,
704 expected_readiness_hash: readiness.readiness_hash,
705 observed_module_hash: [50; 32],
706 observed_controllers: vec![executor],
707 observed_cycles_after_reclamation: 90_000_000_000,
708 observed_reserved_cycles: 0,
709 observed_idle_cycles_burned_per_day: 86_400,
710 observed_freezing_threshold_seconds: 1,
711 };
712 let execution = FleetSubnetRootDeletionExecutionResponse {
713 request: execution_request.clone(),
714 executor,
715 prepared_at_ns: 51,
716 execution_hash: [52; 32],
717 };
718 let completion_request = FleetSubnetRootDeletionCompletionRequest {
719 operation_id: store_deletion.operation_id,
720 fleet_subnet_root: store_deletion.fleet_subnet_root,
721 expected_execution_hash: execution.execution_hash,
722 observed_absent_at_ns: 53,
723 };
724 let status = FleetSubnetRootDeletionStatusRequest {
725 operation_id: store_deletion.operation_id,
726 fleet_subnet_root: store_deletion.fleet_subnet_root,
727 };
728 let deletion = FleetSubnetRootDeletionResponse {
729 operation_id: store_deletion.operation_id,
730 fleet_subnet_root: store_deletion.fleet_subnet_root,
731 coordinator: readiness.coordinator,
732 executor,
733 readiness_hash: readiness.readiness_hash,
734 execution_hash: execution.execution_hash,
735 observed_module_hash: execution_request.observed_module_hash,
736 observed_controllers: execution_request.observed_controllers.clone(),
737 observed_cycles_after_reclamation: 90_000_000_000,
738 observed_absent_at_ns: completion_request.observed_absent_at_ns,
739 completed_at_ns: 54,
740 deletion_hash: [55; 32],
741 };
742 assert_candid_round_trip(&execution_request);
743 assert_candid_round_trip(&execution);
744 assert_candid_round_trip(&completion_request);
745 assert_candid_round_trip(&status);
746 assert_candid_round_trip(&deletion);
747 }
748
749 fn assert_candid_round_trip<T>(value: &T)
750 where
751 T: CandidType + for<'de> candid::Deserialize<'de> + Eq + std::fmt::Debug,
752 {
753 let bytes = candid::encode_one(value).expect("encode Candid contract");
754 assert_eq!(
755 &candid::decode_one::<T>(&bytes).expect("decode Candid contract"),
756 value,
757 );
758 }
759
760 fn canister_summary() -> FleetSubnetRootCanisterSummary {
761 FleetSubnetRootCanisterSummary {
762 fleet_registry: FleetRegistryVersion {
763 authority: FleetRegistryAuthority {
764 binding: FleetCoordinatorBinding {
765 fleet: FleetBinding {
766 fleet: FleetKey {
767 canonical_network_id: CanonicalNetworkId::ic_mainnet(),
768 fleet_id: FleetId::from_generated_bytes([1; 32]),
769 },
770 app: AppId::from("toko"),
771 },
772 coordinator_subnet: SubnetId::from_principal(Principal::from_slice(
773 &[2; 29],
774 )),
775 coordinator: Principal::from_slice(&[3; 29]),
776 },
777 epoch: 1,
778 },
779 revision: 4,
780 content_hash: [5; 32],
781 },
782 placement_subnet: SubnetId::from_principal(Principal::from_slice(&[6; 29])),
783 fleet_subnet_root: Principal::from_slice(&[7; 29]),
784 status: FleetSubnetRootStatus::Active,
785 infrastructure_canisters: 2,
786 component_canisters: 3,
787 pooled_canisters: 4,
788 total_canisters: 9,
789 }
790 }
791
792 fn draining_response(
793 summary: &FleetSubnetRootCanisterSummary,
794 ) -> FleetSubnetRootDrainingResponse {
795 FleetSubnetRootDrainingResponse {
796 operation_id: [8; 32],
797 fleet_subnet_root: summary.fleet_subnet_root,
798 placement_subnet: summary.placement_subnet,
799 active_registry: summary.fleet_registry.clone(),
800 component_topology_digest: ComponentTopologyDigest::from_bytes([9; 32]),
801 active_release_set: FleetSubnetRootReleaseSet {
802 release_build_id: crate::ids::ReleaseBuildId::from_nonce(
803 crate::ids::ReleaseBuildNonce::from_random_bytes([10; 32]),
804 ),
805 manifest_digest: crate::ids::ReleaseSetDigest::from_bytes([11; 32]),
806 },
807 next_allocation_sequence: 12,
808 reserved_component_instances: 13,
809 committed_component_instances: 14,
810 managed_descendants: 15,
811 known_created_component_canisters: 16,
812 root_registry_encoded_bytes: 17_000,
813 started_at_ns: 18,
814 }
815 }
816
817 fn final_inventory_response(
818 draining: &FleetSubnetRootDrainingResponse,
819 ) -> FleetSubnetRootFinalInventoryResponse {
820 FleetSubnetRootFinalInventoryResponse {
821 operation_id: draining.operation_id,
822 fleet_subnet_root: draining.fleet_subnet_root,
823 placement_subnet: draining.placement_subnet,
824 registry: draining.active_registry.clone(),
825 component_topology_digest: draining.component_topology_digest,
826 active_release_set: draining.active_release_set,
827 next_allocation_sequence: draining.next_allocation_sequence,
828 removed_component_instances: 12,
829 terminal_component_history_hash: [19; 32],
830 root_registry_encoded_bytes: 20_000,
831 wasm_store: Principal::from_slice(&[21; 29]),
832 wasm_store_catalog_hash: [22; 32],
833 wasm_store_catalog_entries: 23,
834 wasm_store_occupied_bytes: 24_000,
835 wasm_store_template_count: 25,
836 wasm_store_release_count: 26,
837 wasm_store_gc_prepared_at_secs: 27,
838 finalized_at_ns: 28,
839 inventory_hash: [29; 32],
840 }
841 }
842}