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