1use crate::{
8 cdk::types::Cycles,
9 dto::{
10 fleet_registry::{FleetDirectorySnapshot, FleetRegistryVersion},
11 root_store::RootStoreBootstrapRequest,
12 },
13 ids::{
14 CanisterRole, ComponentBinding, ComponentInstanceId, ComponentSpecId,
15 ComponentTopologyDigest, FleetSubnetRootReleaseSet, ManagedCanisterBinding,
16 },
17};
18use candid::{CandidType, Principal};
19use serde::{Deserialize, Serialize};
20
21#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
28pub struct RootComponentRegistryPreparationRequest {
29 pub store_bootstrap: RootStoreBootstrapRequest,
30 pub expected_fleet_registry: FleetRegistryVersion,
31}
32
33#[derive(CandidType, Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
40pub struct RootComponentInitialInventoryStatus {
41 pub fleet_activation_operation_id: [u8; 32],
42 pub component_count: u32,
43 pub inventory_hash: [u8; 32],
44 pub sealed_at_ns: u64,
45 pub directories_converged: bool,
46 pub root_runtime_activated: bool,
47}
48
49#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
56pub struct RootComponentRegistryStatusResponse {
57 pub fleet_subnet_root: Principal,
58 pub prepared_against_registry: FleetRegistryVersion,
59 pub release_set: FleetSubnetRootReleaseSet,
60 pub component_topology_digest: ComponentTopologyDigest,
61 pub next_allocation_sequence: u64,
62 pub reserved_component_instances: u32,
63 pub committed_component_instances: u32,
64 pub managed_descendants: u32,
65 pub known_created_component_canisters: u32,
66 pub encoded_bytes: u64,
67 pub initial_inventory: Option<RootComponentInitialInventoryStatus>,
68}
69
70#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
77pub struct RootComponentAllocationRequest {
78 pub operation_id: [u8; 32],
79 pub component_spec: ComponentSpecId,
80}
81
82#[derive(CandidType, Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
89pub struct RootComponentAllocationStatusRequest {
90 pub operation_id: [u8; 32],
91}
92
93#[derive(CandidType, Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
100pub struct RootComponentCreationRequest {
101 pub operation_id: [u8; 32],
102}
103
104#[derive(CandidType, Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
111pub struct RootComponentInstallRequest {
112 pub operation_id: [u8; 32],
113}
114
115#[derive(CandidType, Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
122pub struct RootComponentCommitRequest {
123 pub operation_id: [u8; 32],
124}
125
126#[derive(CandidType, Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
133pub struct RootComponentDirectoryPreparationRequest {
134 pub operation_id: [u8; 32],
135}
136
137#[derive(CandidType, Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
144pub struct RootComponentRuntimeActivationRequest {
145 pub operation_id: [u8; 32],
146}
147
148#[derive(CandidType, Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
155pub struct RootComponentMembershipActivationRequest {
156 pub operation_id: [u8; 32],
157}
158
159#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
166pub enum ComponentProvisioningOrigin {
167 FleetAdministrator { caller: Principal },
168}
169
170#[derive(CandidType, Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
177pub enum RootComponentAllocationPhase {
178 Reserved,
179 CreationIntent,
180 Created,
181 InstallIntent,
182 Installed,
183 Verified,
184 Committed,
185}
186
187#[derive(CandidType, Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
194pub enum ComponentLifecycleStatus {
195 Prepared,
196 Active,
197 Draining,
198 Removed,
199}
200
201#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
208pub struct ComponentRegistryHead {
209 pub component: ComponentInstanceId,
210 pub revision: u64,
211 pub content_hash: [u8; 32],
212}
213
214#[derive(CandidType, Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
221pub struct ComponentRegistryPartitionRequest {
222 pub component: ComponentInstanceId,
223}
224
225#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
232pub struct ComponentRegistryPartitionResponse {
233 pub head: ComponentRegistryHead,
234 pub binding: ComponentBinding,
235 pub provisioning_origin: ComponentProvisioningOrigin,
236 pub release_set: FleetSubnetRootReleaseSet,
237 pub status: ComponentLifecycleStatus,
238 pub encoded_bytes: u64,
239}
240
241#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
248pub struct ComponentDirectoryProvenance {
249 pub component: ComponentBinding,
250 pub source_fleet_subnet_root: Principal,
251 pub component_registry_revision: u64,
252 pub component_registry_content_hash: [u8; 32],
253 pub synchronized_at_ns: u64,
254}
255
256#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
263pub struct ComponentDirectoryHead {
264 pub provenance: ComponentDirectoryProvenance,
265 pub descendant_count: u32,
266}
267
268#[derive(CandidType, Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
275pub struct ComponentDirectoryHeadRequest {
276 pub component: ComponentInstanceId,
277}
278
279#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
286pub struct ComponentRuntimeDirectoryAuthority {
287 pub fleet: FleetDirectorySnapshot,
288 pub component: ComponentDirectoryHead,
289}
290
291#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
298pub struct ComponentRuntimeDirectoryPreparationRequest {
299 pub operation_id: [u8; 32],
300 pub authority: ComponentRuntimeDirectoryAuthority,
301}
302
303#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
310pub struct ComponentRuntimeDirectorySynchronizationRequest {
311 pub operation_id: [u8; 32],
312 pub authority: ComponentRuntimeDirectoryAuthority,
313}
314
315#[derive(CandidType, Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
322pub enum ComponentRuntimePhase {
323 AwaitingDirectory,
324 DirectoryPrepared,
325 Active,
326}
327
328#[derive(CandidType, Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
335pub struct ComponentRuntimeActivationEvidence {
336 pub directory_authority_hash: [u8; 32],
337 pub activated_at_ns: u64,
338}
339
340#[derive(CandidType, Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
347pub struct ComponentRuntimeActivationRequest {
348 pub operation_id: [u8; 32],
349 pub directory_authority_hash: [u8; 32],
350}
351
352#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
359pub struct ComponentRuntimeStatusResponse {
360 pub operation_id: [u8; 32],
361 pub binding: ManagedCanisterBinding,
362 pub phase: ComponentRuntimePhase,
363 pub authority: Option<ComponentRuntimeDirectoryAuthority>,
364 pub authority_hash: Option<[u8; 32]>,
365 pub activation: Option<ComponentRuntimeActivationEvidence>,
366}
367
368#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
375pub struct RootComponentCreationEvidence {
376 pub wasm_store: Principal,
377 pub payload_hash: [u8; 32],
378 pub payload_size_bytes: u64,
379 pub initial_cycles: Cycles,
380 pub controller: Principal,
381 pub canister: Option<Principal>,
382}
383
384#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
391pub struct RootComponentInstallEvidence {
392 pub raw_module_hash: [u8; 32],
393 pub chunk_hashes: Vec<Vec<u8>>,
394 pub binding: ComponentBinding,
395}
396
397#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
404pub struct RootComponentAllocationResponse {
405 pub operation_id: [u8; 32],
406 pub allocation_sequence: u64,
407 pub component: ComponentInstanceId,
408 pub component_spec: ComponentSpecId,
409 pub spec_hash: [u8; 32],
410 pub role: CanisterRole,
411 pub provisioning_origin: ComponentProvisioningOrigin,
412 pub release_set: FleetSubnetRootReleaseSet,
413 pub phase: RootComponentAllocationPhase,
414 pub creation: Option<RootComponentCreationEvidence>,
415 pub installation: Option<RootComponentInstallEvidence>,
416}
417
418#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
425pub struct RootComponentCommitResponse {
426 pub allocation: RootComponentAllocationResponse,
427 pub registry: ComponentRegistryPartitionResponse,
428 pub directory: ComponentDirectoryHead,
429}
430
431#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
438pub struct RootComponentDirectoryPreparationResponse {
439 pub committed: RootComponentCommitResponse,
440 pub target: ComponentRuntimeStatusResponse,
441}
442
443#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
450pub struct RootComponentRuntimeActivationResponse {
451 pub committed: RootComponentCommitResponse,
452 pub target: ComponentRuntimeStatusResponse,
453}
454
455#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
462pub struct RootComponentMembershipActivationResponse {
463 pub allocation: RootComponentAllocationResponse,
464 pub registry: ComponentRegistryPartitionResponse,
465 pub directory: ComponentDirectoryHead,
466 pub target: ComponentRuntimeStatusResponse,
467}
468
469#[cfg(test)]
470mod tests {
471 use super::*;
472 use crate::{
473 dto::root_store::RootStoreBootstrapRequest,
474 ids::{
475 AppId, CanonicalNetworkId, FleetCoordinatorBinding, FleetId, FleetKey,
476 FleetRegistryAuthority, ReleaseBuildId, ReleaseBuildNonce, ReleaseSetDigest, SubnetId,
477 },
478 };
479
480 #[test]
481 fn component_registry_contracts_round_trip_through_candid() {
482 let request = RootComponentRegistryPreparationRequest {
483 store_bootstrap: RootStoreBootstrapRequest {
484 manifest_payload_size_bytes: 128,
485 },
486 expected_fleet_registry: FleetRegistryVersion {
487 authority: fleet_registry_authority(),
488 revision: 4,
489 content_hash: [5; 32],
490 },
491 };
492 let response = RootComponentRegistryStatusResponse {
493 fleet_subnet_root: Principal::from_slice(&[6; 29]),
494 prepared_against_registry: request.expected_fleet_registry.clone(),
495 release_set: FleetSubnetRootReleaseSet {
496 release_build_id: ReleaseBuildId::from_nonce(ReleaseBuildNonce::from_random_bytes(
497 [7; 32],
498 )),
499 manifest_digest: ReleaseSetDigest::from_bytes([8; 32]),
500 },
501 component_topology_digest: ComponentTopologyDigest::from_bytes([9; 32]),
502 next_allocation_sequence: 1,
503 reserved_component_instances: 0,
504 committed_component_instances: 0,
505 managed_descendants: 0,
506 known_created_component_canisters: 0,
507 encoded_bytes: 0,
508 initial_inventory: Some(RootComponentInitialInventoryStatus {
509 fleet_activation_operation_id: [10; 32],
510 component_count: 0,
511 inventory_hash: [11; 32],
512 sealed_at_ns: 12,
513 directories_converged: true,
514 root_runtime_activated: true,
515 }),
516 };
517 let allocation = RootComponentAllocationResponse {
518 operation_id: [10; 32],
519 allocation_sequence: 1,
520 component: ComponentInstanceId::from_generated_bytes([11; 32]),
521 component_spec: "projects".parse().expect("Component Spec ID"),
522 spec_hash: [12; 32],
523 role: CanisterRole::new("project_hub"),
524 provisioning_origin: ComponentProvisioningOrigin::FleetAdministrator {
525 caller: Principal::from_slice(&[13; 29]),
526 },
527 release_set: response.release_set,
528 phase: RootComponentAllocationPhase::Reserved,
529 creation: None,
530 installation: None,
531 };
532 let created = RootComponentAllocationResponse {
533 phase: RootComponentAllocationPhase::Created,
534 creation: Some(RootComponentCreationEvidence {
535 wasm_store: Principal::from_slice(&[14; 29]),
536 payload_hash: [15; 32],
537 payload_size_bytes: 4_096,
538 initial_cycles: Cycles::new(5_000_000_000_000),
539 controller: Principal::from_slice(&[6; 29]),
540 canister: Some(Principal::from_slice(&[16; 29])),
541 }),
542 installation: None,
543 ..allocation.clone()
544 };
545 let request_bytes = candid::encode_one(&request).expect("encode request");
546 let response_bytes = candid::encode_one(&response).expect("encode response");
547 let allocation_bytes = candid::encode_one(&allocation).expect("encode allocation");
548 let created_bytes = candid::encode_one(&created).expect("encode created allocation");
549
550 assert_eq!(
551 candid::decode_one::<RootComponentRegistryPreparationRequest>(&request_bytes)
552 .expect("decode request"),
553 request
554 );
555 assert_eq!(
556 candid::decode_one::<RootComponentRegistryStatusResponse>(&response_bytes)
557 .expect("decode response"),
558 response
559 );
560 assert_eq!(
561 candid::decode_one::<RootComponentAllocationResponse>(&allocation_bytes)
562 .expect("decode allocation"),
563 allocation
564 );
565 assert_eq!(
566 candid::decode_one::<RootComponentAllocationResponse>(&created_bytes)
567 .expect("decode created allocation"),
568 created
569 );
570 }
571
572 #[test]
573 fn component_commit_response_round_trips_through_candid() {
574 let root = Principal::from_slice(&[6; 29]);
575 let component = ComponentInstanceId::from_generated_bytes([11; 32]);
576 let component_spec: ComponentSpecId = "projects".parse().expect("Component Spec ID");
577 let release_set = FleetSubnetRootReleaseSet {
578 release_build_id: ReleaseBuildId::from_nonce(ReleaseBuildNonce::from_random_bytes(
579 [7; 32],
580 )),
581 manifest_digest: ReleaseSetDigest::from_bytes([8; 32]),
582 };
583 let provisioning_origin = ComponentProvisioningOrigin::FleetAdministrator {
584 caller: Principal::from_slice(&[13; 29]),
585 };
586 let binding = ComponentBinding {
587 authority: fleet_registry_authority(),
588 component,
589 component_spec: component_spec.clone(),
590 spec_hash: [12; 32],
591 role: CanisterRole::new("project_hub"),
592 placement_subnet: SubnetId::from_principal(Principal::from_slice(&[17; 29])),
593 fleet_subnet_root: root,
594 canister_id: Principal::from_slice(&[16; 29]),
595 };
596 let head = ComponentRegistryHead {
597 component,
598 revision: 1,
599 content_hash: [18; 32],
600 };
601 let committed = RootComponentCommitResponse {
602 allocation: RootComponentAllocationResponse {
603 operation_id: [10; 32],
604 allocation_sequence: 1,
605 component,
606 component_spec,
607 spec_hash: binding.spec_hash,
608 role: binding.role.clone(),
609 provisioning_origin: provisioning_origin.clone(),
610 release_set,
611 phase: RootComponentAllocationPhase::Committed,
612 creation: Some(RootComponentCreationEvidence {
613 wasm_store: Principal::from_slice(&[14; 29]),
614 payload_hash: [15; 32],
615 payload_size_bytes: 4_096,
616 initial_cycles: Cycles::new(5_000_000_000_000),
617 controller: root,
618 canister: Some(binding.canister_id),
619 }),
620 installation: Some(RootComponentInstallEvidence {
621 raw_module_hash: [20; 32],
622 chunk_hashes: vec![vec![21; 32]],
623 binding: binding.clone(),
624 }),
625 },
626 registry: ComponentRegistryPartitionResponse {
627 head: head.clone(),
628 binding: binding.clone(),
629 provisioning_origin,
630 release_set,
631 status: ComponentLifecycleStatus::Prepared,
632 encoded_bytes: 2_048,
633 },
634 directory: ComponentDirectoryHead {
635 provenance: ComponentDirectoryProvenance {
636 component: binding,
637 source_fleet_subnet_root: root,
638 component_registry_revision: head.revision,
639 component_registry_content_hash: head.content_hash,
640 synchronized_at_ns: 19,
641 },
642 descendant_count: 0,
643 },
644 };
645 let committed_bytes = candid::encode_one(&committed).expect("encode committed allocation");
646
647 assert_eq!(
648 candid::decode_one::<RootComponentCommitResponse>(&committed_bytes)
649 .expect("decode committed allocation"),
650 committed
651 );
652 }
653
654 fn fleet_registry_authority() -> FleetRegistryAuthority {
655 FleetRegistryAuthority {
656 binding: FleetCoordinatorBinding {
657 fleet: crate::ids::FleetBinding {
658 fleet: FleetKey {
659 canonical_network_id: CanonicalNetworkId::public_ic(),
660 fleet_id: FleetId::from_generated_bytes([1; 32]),
661 },
662 app: AppId::from("toko"),
663 },
664 coordinator_subnet: SubnetId::from_principal(Principal::from_slice(&[2; 29])),
665 coordinator: Principal::from_slice(&[3; 29]),
666 },
667 epoch: 1,
668 }
669 }
670
671 #[test]
672 fn component_creation_request_round_trips_through_candid() {
673 let request = RootComponentCreationRequest {
674 operation_id: [10; 32],
675 };
676 let bytes = candid::encode_one(request).expect("encode creation request");
677
678 assert_eq!(
679 candid::decode_one::<RootComponentCreationRequest>(&bytes)
680 .expect("decode creation request"),
681 request
682 );
683 }
684
685 #[test]
686 fn component_install_request_round_trips_through_candid() {
687 let request = RootComponentInstallRequest {
688 operation_id: [10; 32],
689 };
690 let bytes = candid::encode_one(request).expect("encode install request");
691
692 assert_eq!(
693 candid::decode_one::<RootComponentInstallRequest>(&bytes)
694 .expect("decode install request"),
695 request
696 );
697 }
698
699 #[test]
700 fn component_commit_request_round_trips_through_candid() {
701 let request = RootComponentCommitRequest {
702 operation_id: [10; 32],
703 };
704 let bytes = candid::encode_one(request).expect("encode commit request");
705
706 assert_eq!(
707 candid::decode_one::<RootComponentCommitRequest>(&bytes)
708 .expect("decode commit request"),
709 request
710 );
711 }
712
713 #[test]
714 fn component_runtime_activation_requests_round_trip_through_candid() {
715 let root_request = RootComponentRuntimeActivationRequest {
716 operation_id: [22; 32],
717 };
718 let target_request = ComponentRuntimeActivationRequest {
719 operation_id: root_request.operation_id,
720 directory_authority_hash: [23; 32],
721 };
722 let membership_request = RootComponentMembershipActivationRequest {
723 operation_id: root_request.operation_id,
724 };
725 let root_bytes = candid::encode_one(root_request).expect("encode root activation request");
726 let target_bytes =
727 candid::encode_one(target_request).expect("encode target activation request");
728 let membership_bytes =
729 candid::encode_one(membership_request).expect("encode membership activation request");
730
731 assert_eq!(
732 candid::decode_one::<RootComponentRuntimeActivationRequest>(&root_bytes)
733 .expect("decode root activation request"),
734 root_request
735 );
736 assert_eq!(
737 candid::decode_one::<ComponentRuntimeActivationRequest>(&target_bytes)
738 .expect("decode target activation request"),
739 target_request
740 );
741 assert_eq!(
742 candid::decode_one::<RootComponentMembershipActivationRequest>(&membership_bytes)
743 .expect("decode membership activation request"),
744 membership_request
745 );
746 }
747}