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