1use crate::{
8 dto::fleet_registry::{FleetRegistryVersion, FleetSubnetRootStatus},
9 ids::{ComponentTopologyDigest, FleetSubnetRootBinding, FleetSubnetRootReleaseSet, SubnetId},
10};
11use candid::{CandidType, Principal};
12use serde::{Deserialize, Serialize};
13
14#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq)]
21pub struct FleetSubnetRootAuthority {
22 pub binding: FleetSubnetRootBinding,
23 pub initial_release_set: FleetSubnetRootReleaseSet,
24 pub expected_module_hash: [u8; 32],
25}
26
27#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
34pub struct FleetSubnetRootCanisterSummary {
35 pub fleet_registry: FleetRegistryVersion,
36 pub placement_subnet: SubnetId,
37 pub fleet_subnet_root: Principal,
38 pub status: FleetSubnetRootStatus,
39 pub infrastructure_canisters: u32,
40 pub component_canisters: u32,
41 pub total_canisters: u32,
42}
43
44#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
51pub struct FleetSubnetRootDrainingRequest {
52 pub operation_id: [u8; 32],
53 pub expected_registry: FleetRegistryVersion,
54}
55
56#[derive(CandidType, Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
63pub struct FleetSubnetRootDrainingStatusRequest {
64 pub operation_id: [u8; 32],
65}
66
67#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
74pub struct FleetSubnetRootDrainingResponse {
75 pub operation_id: [u8; 32],
76 pub fleet_subnet_root: Principal,
77 pub placement_subnet: SubnetId,
78 pub active_registry: FleetRegistryVersion,
79 pub component_topology_digest: ComponentTopologyDigest,
80 pub active_release_set: FleetSubnetRootReleaseSet,
81 pub next_allocation_sequence: u64,
82 pub reserved_component_instances: u32,
83 pub committed_component_instances: u32,
84 pub managed_descendants: u32,
85 pub known_created_component_canisters: u32,
86 pub root_registry_encoded_bytes: u64,
87 pub started_at_ns: u64,
88}
89
90#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
97pub struct FleetSubnetRootFinalInventoryRequest {
98 pub operation_id: [u8; 32],
99 pub expected_registry: FleetRegistryVersion,
100}
101
102#[derive(CandidType, Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
109pub struct FleetSubnetRootFinalInventoryStatusRequest {
110 pub operation_id: [u8; 32],
111}
112
113#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
120pub struct FleetSubnetRootRemovalRequest {
121 pub operation_id: [u8; 32],
122 pub expected_registry: FleetRegistryVersion,
123}
124
125#[derive(CandidType, Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
127pub struct FleetSubnetRootRemovalStatusRequest {
128 pub operation_id: [u8; 32],
129}
130
131#[derive(CandidType, Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
138pub struct FleetSubnetRootStoreReclamationRequest {
139 pub operation_id: [u8; 32],
140 pub expected_final_inventory_hash: [u8; 32],
141}
142
143#[derive(CandidType, Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
145pub struct FleetSubnetRootStoreReclamationStatusRequest {
146 pub operation_id: [u8; 32],
147}
148
149#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
156pub struct FleetSubnetRootStoreReclamationResponse {
157 pub operation_id: [u8; 32],
158 pub fleet_subnet_root: Principal,
159 pub wasm_store: Principal,
160 pub final_inventory_hash: [u8; 32],
161 pub reclaimed_store_bytes: u64,
162 pub reclaimed_catalog_entries: u32,
163 pub reclaimed_template_count: u32,
164 pub reclaimed_release_count: u32,
165 pub gc_prepared_at_secs: u64,
166 pub gc_started_at_secs: u64,
167 pub gc_completed_at_secs: u64,
168 pub gc_runs_completed: u32,
169 pub completed_at_ns: u64,
170 pub reclamation_hash: [u8; 32],
171}
172
173#[derive(CandidType, Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
175pub struct FleetSubnetRootStoreBindingFinalizationRequest {
176 pub operation_id: [u8; 32],
177 pub expected_reclamation_hash: [u8; 32],
178}
179
180#[derive(CandidType, Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
182pub struct FleetSubnetRootStoreBindingFinalizationStatusRequest {
183 pub operation_id: [u8; 32],
184}
185
186#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
188pub struct FleetSubnetRootStoreBindingFinalizationResponse {
189 pub operation_id: [u8; 32],
190 pub fleet_subnet_root: Principal,
191 pub wasm_store: Principal,
192 pub final_inventory_hash: [u8; 32],
193 pub reclamation_hash: [u8; 32],
194 pub source_generation: u64,
195 pub finalized_generation: u64,
196 pub finalized_at_secs: u64,
197 pub completed_at_ns: u64,
198 pub finalization_hash: [u8; 32],
199}
200
201#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
208pub struct FleetSubnetRootFinalInventoryResponse {
209 pub operation_id: [u8; 32],
210 pub fleet_subnet_root: Principal,
211 pub placement_subnet: SubnetId,
212 pub registry: FleetRegistryVersion,
213 pub component_topology_digest: ComponentTopologyDigest,
214 pub active_release_set: FleetSubnetRootReleaseSet,
215 pub next_allocation_sequence: u64,
216 pub removed_component_instances: u32,
217 pub terminal_component_history_hash: [u8; 32],
218 pub root_registry_encoded_bytes: u64,
219 pub wasm_store: Principal,
220 pub wasm_store_catalog_hash: [u8; 32],
221 pub wasm_store_catalog_entries: u32,
222 pub wasm_store_occupied_bytes: u64,
223 pub wasm_store_template_count: u32,
224 pub wasm_store_release_count: u32,
225 pub wasm_store_gc_prepared_at_secs: u64,
226 pub finalized_at_ns: u64,
227 pub inventory_hash: [u8; 32],
228}
229
230#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq)]
237pub struct FleetSubnetRootInitArgs {
238 pub authority: FleetSubnetRootAuthority,
239 pub install_id: [u8; 32],
240}
241
242#[cfg(test)]
243mod tests {
244 use super::*;
245 use crate::ids::{
246 AppId, CanonicalNetworkId, FleetBinding, FleetCoordinatorBinding, FleetId, FleetKey,
247 FleetRegistryAuthority,
248 };
249
250 #[test]
251 fn canister_summary_and_root_lifecycle_contracts_round_trip_through_candid() {
252 let summary = canister_summary();
253 let candid = candid::encode_one(&summary).expect("encode Canister summary");
254 let decoded: FleetSubnetRootCanisterSummary =
255 candid::decode_one(&candid).expect("decode Canister summary");
256
257 assert_eq!(decoded, summary);
258
259 let draining = draining_response(&summary);
260 let request = FleetSubnetRootDrainingRequest {
261 operation_id: draining.operation_id,
262 expected_registry: draining.active_registry.clone(),
263 };
264 let status = FleetSubnetRootDrainingStatusRequest {
265 operation_id: draining.operation_id,
266 };
267 let request_bytes = candid::encode_one(&request).expect("encode root draining request");
268 let status_bytes = candid::encode_one(status).expect("encode root draining status");
269 let response_bytes = candid::encode_one(&draining).expect("encode root draining response");
270 assert_eq!(
271 candid::decode_one::<FleetSubnetRootDrainingRequest>(&request_bytes)
272 .expect("decode root draining request"),
273 request
274 );
275 assert_eq!(
276 candid::decode_one::<FleetSubnetRootDrainingStatusRequest>(&status_bytes)
277 .expect("decode root draining status"),
278 status
279 );
280 assert_eq!(
281 candid::decode_one::<FleetSubnetRootDrainingResponse>(&response_bytes)
282 .expect("decode root draining response"),
283 draining
284 );
285
286 let inventory = final_inventory_response(&draining);
287 let inventory_request = FleetSubnetRootFinalInventoryRequest {
288 operation_id: inventory.operation_id,
289 expected_registry: inventory.registry.clone(),
290 };
291 let inventory_status = FleetSubnetRootFinalInventoryStatusRequest {
292 operation_id: inventory.operation_id,
293 };
294 let request_bytes =
295 candid::encode_one(&inventory_request).expect("encode root inventory request");
296 let status_bytes =
297 candid::encode_one(inventory_status).expect("encode root inventory status");
298 let response_bytes =
299 candid::encode_one(&inventory).expect("encode root inventory response");
300 assert_eq!(
301 candid::decode_one::<FleetSubnetRootFinalInventoryRequest>(&request_bytes)
302 .expect("decode root inventory request"),
303 inventory_request
304 );
305 assert_eq!(
306 candid::decode_one::<FleetSubnetRootFinalInventoryStatusRequest>(&status_bytes)
307 .expect("decode root inventory status"),
308 inventory_status
309 );
310 assert_eq!(
311 candid::decode_one::<FleetSubnetRootFinalInventoryResponse>(&response_bytes)
312 .expect("decode root inventory response"),
313 inventory
314 );
315 }
316
317 #[test]
318 fn draining_publication_contracts_round_trip_through_candid() {
319 let draining = draining_response(&canister_summary());
320 let publication = crate::dto::fleet_registry::FleetSubnetRootDrainingPublicationRequest {
321 expected_registry: draining.active_registry.clone(),
322 root_draining: draining.clone(),
323 };
324 let publication_response =
325 crate::dto::fleet_registry::FleetSubnetRootDrainingPublicationResponse {
326 root_draining: draining,
327 previous_version: publication.expected_registry.clone(),
328 version: FleetRegistryVersion {
329 authority: publication.expected_registry.authority.clone(),
330 revision: publication.expected_registry.revision + 1,
331 content_hash: [19; 32],
332 },
333 };
334 let publication_bytes =
335 candid::encode_one(&publication).expect("encode root draining publication");
336 let publication_response_bytes = candid::encode_one(&publication_response)
337 .expect("encode root draining publication response");
338 assert_eq!(
339 candid::decode_one::<
340 crate::dto::fleet_registry::FleetSubnetRootDrainingPublicationRequest,
341 >(&publication_bytes)
342 .expect("decode root draining publication"),
343 publication
344 );
345 assert_eq!(
346 candid::decode_one::<
347 crate::dto::fleet_registry::FleetSubnetRootDrainingPublicationResponse,
348 >(&publication_response_bytes)
349 .expect("decode root draining publication response"),
350 publication_response
351 );
352
353 let final_inventory = final_inventory_response(&publication_response.root_draining);
354 let removal_request = FleetSubnetRootRemovalRequest {
355 operation_id: final_inventory.operation_id,
356 expected_registry: publication_response.version.clone(),
357 };
358 let removal_status = FleetSubnetRootRemovalStatusRequest {
359 operation_id: final_inventory.operation_id,
360 };
361 let coordinator_request =
362 crate::dto::fleet_registry::FleetSubnetRootRemovalPublicationRequest {
363 expected_registry: publication_response.version.clone(),
364 final_inventory: final_inventory.clone(),
365 };
366 let coordinator_response =
367 crate::dto::fleet_registry::FleetSubnetRootRemovalPublicationResponse {
368 final_inventory,
369 previous_version: publication_response.version.clone(),
370 version: FleetRegistryVersion {
371 authority: publication_response.version.authority.clone(),
372 revision: publication_response.version.revision + 1,
373 content_hash: [29; 32],
374 },
375 };
376 assert_candid_round_trip(&removal_request);
377 assert_candid_round_trip(&removal_status);
378 assert_candid_round_trip(&coordinator_request);
379 assert_candid_round_trip(&coordinator_response);
380
381 let reclamation_request = FleetSubnetRootStoreReclamationRequest {
382 operation_id: coordinator_response.final_inventory.operation_id,
383 expected_final_inventory_hash: coordinator_response.final_inventory.inventory_hash,
384 };
385 let reclamation_status = FleetSubnetRootStoreReclamationStatusRequest {
386 operation_id: reclamation_request.operation_id,
387 };
388 let reclamation_response = FleetSubnetRootStoreReclamationResponse {
389 operation_id: reclamation_request.operation_id,
390 fleet_subnet_root: coordinator_response.final_inventory.fleet_subnet_root,
391 wasm_store: coordinator_response.final_inventory.wasm_store,
392 final_inventory_hash: reclamation_request.expected_final_inventory_hash,
393 reclaimed_store_bytes: coordinator_response
394 .final_inventory
395 .wasm_store_occupied_bytes,
396 reclaimed_catalog_entries: coordinator_response
397 .final_inventory
398 .wasm_store_catalog_entries,
399 reclaimed_template_count: coordinator_response
400 .final_inventory
401 .wasm_store_template_count,
402 reclaimed_release_count: coordinator_response
403 .final_inventory
404 .wasm_store_release_count,
405 gc_prepared_at_secs: coordinator_response
406 .final_inventory
407 .wasm_store_gc_prepared_at_secs,
408 gc_started_at_secs: 30,
409 gc_completed_at_secs: 31,
410 gc_runs_completed: 1,
411 completed_at_ns: 32,
412 reclamation_hash: [33; 32],
413 };
414 assert_candid_round_trip(&reclamation_request);
415 assert_candid_round_trip(&reclamation_status);
416 assert_candid_round_trip(&reclamation_response);
417
418 assert_store_binding_finalization_contract_round_trip(&reclamation_response);
419 }
420
421 fn assert_store_binding_finalization_contract_round_trip(
422 reclamation: &FleetSubnetRootStoreReclamationResponse,
423 ) {
424 let request = FleetSubnetRootStoreBindingFinalizationRequest {
425 operation_id: reclamation.operation_id,
426 expected_reclamation_hash: reclamation.reclamation_hash,
427 };
428 let status = FleetSubnetRootStoreBindingFinalizationStatusRequest {
429 operation_id: request.operation_id,
430 };
431 let response = FleetSubnetRootStoreBindingFinalizationResponse {
432 operation_id: request.operation_id,
433 fleet_subnet_root: reclamation.fleet_subnet_root,
434 wasm_store: reclamation.wasm_store,
435 final_inventory_hash: reclamation.final_inventory_hash,
436 reclamation_hash: request.expected_reclamation_hash,
437 source_generation: 4,
438 finalized_generation: 7,
439 finalized_at_secs: 34,
440 completed_at_ns: 35,
441 finalization_hash: [36; 32],
442 };
443 assert_candid_round_trip(&request);
444 assert_candid_round_trip(&status);
445 assert_candid_round_trip(&response);
446 }
447
448 fn assert_candid_round_trip<T>(value: &T)
449 where
450 T: CandidType + for<'de> candid::Deserialize<'de> + Eq + std::fmt::Debug,
451 {
452 let bytes = candid::encode_one(value).expect("encode Candid contract");
453 assert_eq!(
454 &candid::decode_one::<T>(&bytes).expect("decode Candid contract"),
455 value,
456 );
457 }
458
459 fn canister_summary() -> FleetSubnetRootCanisterSummary {
460 FleetSubnetRootCanisterSummary {
461 fleet_registry: FleetRegistryVersion {
462 authority: FleetRegistryAuthority {
463 binding: FleetCoordinatorBinding {
464 fleet: FleetBinding {
465 fleet: FleetKey {
466 canonical_network_id: CanonicalNetworkId::ic_mainnet(),
467 fleet_id: FleetId::from_generated_bytes([1; 32]),
468 },
469 app: AppId::from("toko"),
470 },
471 coordinator_subnet: SubnetId::from_principal(Principal::from_slice(
472 &[2; 29],
473 )),
474 coordinator: Principal::from_slice(&[3; 29]),
475 },
476 epoch: 1,
477 },
478 revision: 4,
479 content_hash: [5; 32],
480 },
481 placement_subnet: SubnetId::from_principal(Principal::from_slice(&[6; 29])),
482 fleet_subnet_root: Principal::from_slice(&[7; 29]),
483 status: FleetSubnetRootStatus::Active,
484 infrastructure_canisters: 2,
485 component_canisters: 3,
486 total_canisters: 5,
487 }
488 }
489
490 fn draining_response(
491 summary: &FleetSubnetRootCanisterSummary,
492 ) -> FleetSubnetRootDrainingResponse {
493 FleetSubnetRootDrainingResponse {
494 operation_id: [8; 32],
495 fleet_subnet_root: summary.fleet_subnet_root,
496 placement_subnet: summary.placement_subnet,
497 active_registry: summary.fleet_registry.clone(),
498 component_topology_digest: ComponentTopologyDigest::from_bytes([9; 32]),
499 active_release_set: FleetSubnetRootReleaseSet {
500 release_build_id: crate::ids::ReleaseBuildId::from_nonce(
501 crate::ids::ReleaseBuildNonce::from_random_bytes([10; 32]),
502 ),
503 manifest_digest: crate::ids::ReleaseSetDigest::from_bytes([11; 32]),
504 },
505 next_allocation_sequence: 12,
506 reserved_component_instances: 13,
507 committed_component_instances: 14,
508 managed_descendants: 15,
509 known_created_component_canisters: 16,
510 root_registry_encoded_bytes: 17_000,
511 started_at_ns: 18,
512 }
513 }
514
515 fn final_inventory_response(
516 draining: &FleetSubnetRootDrainingResponse,
517 ) -> FleetSubnetRootFinalInventoryResponse {
518 FleetSubnetRootFinalInventoryResponse {
519 operation_id: draining.operation_id,
520 fleet_subnet_root: draining.fleet_subnet_root,
521 placement_subnet: draining.placement_subnet,
522 registry: draining.active_registry.clone(),
523 component_topology_digest: draining.component_topology_digest,
524 active_release_set: draining.active_release_set,
525 next_allocation_sequence: draining.next_allocation_sequence,
526 removed_component_instances: 12,
527 terminal_component_history_hash: [19; 32],
528 root_registry_encoded_bytes: 20_000,
529 wasm_store: Principal::from_slice(&[21; 29]),
530 wasm_store_catalog_hash: [22; 32],
531 wasm_store_catalog_entries: 23,
532 wasm_store_occupied_bytes: 24_000,
533 wasm_store_template_count: 25,
534 wasm_store_release_count: 26,
535 wasm_store_gc_prepared_at_secs: 27,
536 finalized_at_ns: 28,
537 inventory_hash: [29; 32],
538 }
539 }
540}