1use crate::{
8 cdk::types::Cycles,
9 config::schema::ComponentChildKind,
10 dto::{
11 fleet_registry::{FleetDirectorySnapshot, FleetRegistryVersion},
12 root_store::RootStoreBootstrapRequest,
13 },
14 ids::{
15 CanisterRole, ComponentBinding, ComponentChildBinding, ComponentInstanceId,
16 ComponentSpecId, ComponentTopologyDigest, FleetSubnetRootReleaseSet,
17 ManagedCanisterBinding,
18 },
19};
20use candid::{CandidType, Principal};
21use serde::{Deserialize, Serialize};
22
23#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
30pub struct RootComponentRegistryPreparationRequest {
31 pub store_bootstrap: RootStoreBootstrapRequest,
32 pub expected_fleet_registry: FleetRegistryVersion,
33}
34
35#[derive(CandidType, Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
42pub struct RootComponentInitialInventoryStatus {
43 pub fleet_activation_operation_id: [u8; 32],
44 pub component_count: u32,
45 pub inventory_hash: [u8; 32],
46 pub sealed_at_ns: u64,
47 pub directories_converged: bool,
48 pub root_runtime_activated: bool,
49}
50
51#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
58pub struct RootComponentRegistryStatusResponse {
59 pub fleet_subnet_root: Principal,
60 pub prepared_against_registry: FleetRegistryVersion,
61 pub release_set: FleetSubnetRootReleaseSet,
62 pub component_topology_digest: ComponentTopologyDigest,
63 pub next_allocation_sequence: u64,
64 pub reserved_component_instances: u32,
65 pub committed_component_instances: u32,
66 pub managed_descendants: u32,
67 pub known_created_component_canisters: u32,
68 pub encoded_bytes: u64,
69 pub initial_inventory: Option<RootComponentInitialInventoryStatus>,
70}
71
72#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
79pub struct RootComponentAllocationRequest {
80 pub operation_id: [u8; 32],
81 pub component_spec: ComponentSpecId,
82}
83
84#[derive(CandidType, Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
91pub struct RootComponentAllocationStatusRequest {
92 pub operation_id: [u8; 32],
93}
94
95#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
102pub struct RootComponentChildAllocationRequest {
103 pub operation_id: [u8; 32],
104 pub component: ComponentInstanceId,
105 pub expected_registry: ComponentRegistryHead,
106 pub child_role: CanisterRole,
107}
108
109#[derive(CandidType, Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
116pub struct RootComponentChildAllocationStatusRequest {
117 pub operation_id: [u8; 32],
118 pub component: ComponentInstanceId,
119}
120
121#[derive(CandidType, Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
128pub struct RootComponentChildCreationRequest {
129 pub operation_id: [u8; 32],
130 pub component: ComponentInstanceId,
131}
132
133#[derive(CandidType, Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
140pub struct RootComponentChildInstallRequest {
141 pub operation_id: [u8; 32],
142 pub component: ComponentInstanceId,
143}
144
145#[derive(CandidType, Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
152pub struct RootComponentChildCommitRequest {
153 pub operation_id: [u8; 32],
154 pub component: ComponentInstanceId,
155}
156
157#[derive(CandidType, Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
164pub struct RootComponentChildDirectoryPreparationRequest {
165 pub operation_id: [u8; 32],
166 pub component: ComponentInstanceId,
167}
168
169#[derive(CandidType, Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
176pub struct RootComponentChildRuntimeActivationRequest {
177 pub operation_id: [u8; 32],
178 pub component: ComponentInstanceId,
179}
180
181#[derive(CandidType, Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
188pub struct RootComponentCreationRequest {
189 pub operation_id: [u8; 32],
190}
191
192#[derive(CandidType, Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
199pub struct RootComponentInstallRequest {
200 pub operation_id: [u8; 32],
201}
202
203#[derive(CandidType, Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
210pub struct RootComponentCommitRequest {
211 pub operation_id: [u8; 32],
212}
213
214#[derive(CandidType, Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
221pub struct RootComponentDirectoryPreparationRequest {
222 pub operation_id: [u8; 32],
223}
224
225#[derive(CandidType, Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
232pub struct RootComponentRuntimeActivationRequest {
233 pub operation_id: [u8; 32],
234}
235
236#[derive(CandidType, Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
243pub struct RootComponentMembershipActivationRequest {
244 pub operation_id: [u8; 32],
245}
246
247#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
254pub enum ComponentProvisioningOrigin {
255 FleetAdministrator { caller: Principal },
256}
257
258#[derive(CandidType, Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
265pub enum RootComponentAllocationPhase {
266 Reserved,
267 CreationIntent,
268 Created,
269 InstallIntent,
270 Installed,
271 Verified,
272 Committed,
273}
274
275#[derive(CandidType, Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
282pub enum ComponentLifecycleStatus {
283 Prepared,
284 Active,
285 Draining,
286 Removed,
287}
288
289#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
296pub struct ComponentRegistryHead {
297 pub component: ComponentInstanceId,
298 pub revision: u64,
299 pub content_hash: [u8; 32],
300}
301
302#[derive(CandidType, Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
309pub struct ComponentRegistryPartitionRequest {
310 pub component: ComponentInstanceId,
311}
312
313#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
320pub struct ComponentRegistryPartitionResponse {
321 pub head: ComponentRegistryHead,
322 pub binding: ComponentBinding,
323 pub provisioning_origin: ComponentProvisioningOrigin,
324 pub release_set: FleetSubnetRootReleaseSet,
325 pub status: ComponentLifecycleStatus,
326 pub reserved_descendants: u32,
327 pub committed_descendants: u32,
328 pub encoded_bytes: u64,
329}
330
331#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
338pub struct ComponentDirectoryProvenance {
339 pub component: ComponentBinding,
340 pub source_fleet_subnet_root: Principal,
341 pub component_registry_revision: u64,
342 pub component_registry_content_hash: [u8; 32],
343 pub synchronized_at_ns: u64,
344}
345
346#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
353pub struct ComponentDirectoryHead {
354 pub provenance: ComponentDirectoryProvenance,
355 pub descendant_count: u32,
356}
357
358#[derive(CandidType, Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
365pub struct ComponentDirectoryHeadRequest {
366 pub component: ComponentInstanceId,
367}
368
369#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
376pub struct ComponentRuntimeDirectoryAuthority {
377 pub fleet: FleetDirectorySnapshot,
378 pub component: ComponentDirectoryHead,
379}
380
381#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
388pub struct ComponentRuntimeDirectoryPreparationRequest {
389 pub operation_id: [u8; 32],
390 pub authority: ComponentRuntimeDirectoryAuthority,
391}
392
393#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
400pub struct ComponentRuntimeDirectorySynchronizationRequest {
401 pub operation_id: [u8; 32],
402 pub authority: ComponentRuntimeDirectoryAuthority,
403}
404
405#[derive(CandidType, Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
412pub enum ComponentRuntimePhase {
413 AwaitingDirectory,
414 DirectoryPrepared,
415 Active,
416}
417
418#[derive(CandidType, Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
425pub struct ComponentRuntimeActivationEvidence {
426 pub directory_authority_hash: [u8; 32],
427 pub activated_at_ns: u64,
428}
429
430#[derive(CandidType, Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
437pub struct ComponentRuntimeActivationRequest {
438 pub operation_id: [u8; 32],
439 pub directory_authority_hash: [u8; 32],
440}
441
442#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
449pub struct ComponentRuntimeStatusResponse {
450 pub operation_id: [u8; 32],
451 pub binding: ManagedCanisterBinding,
452 pub phase: ComponentRuntimePhase,
453 pub authority: Option<ComponentRuntimeDirectoryAuthority>,
454 pub authority_hash: Option<[u8; 32]>,
455 pub activation: Option<ComponentRuntimeActivationEvidence>,
456}
457
458#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
465pub struct ComponentRuntimeDirectoryConvergenceEvidence {
466 pub operation_id: [u8; 32],
467 pub binding: ManagedCanisterBinding,
468 pub covered_authority: ComponentRuntimeDirectoryAuthority,
469 pub covered_authority_hash: [u8; 32],
470 pub activation: ComponentRuntimeActivationEvidence,
471}
472
473#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
480pub struct RootComponentCreationEvidence {
481 pub wasm_store: Principal,
482 pub payload_hash: [u8; 32],
483 pub payload_size_bytes: u64,
484 pub initial_cycles: Cycles,
485 pub controller: Principal,
486 pub canister: Option<Principal>,
487}
488
489#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
496pub struct RootComponentInstallEvidence {
497 pub raw_module_hash: [u8; 32],
498 pub chunk_hashes: Vec<Vec<u8>>,
499 pub binding: ComponentBinding,
500}
501
502#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
509pub struct RootComponentChildInstallEvidence {
510 pub raw_module_hash: [u8; 32],
511 pub chunk_hashes: Vec<Vec<u8>>,
512 pub binding: ComponentChildBinding,
513}
514
515#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
522pub struct RootComponentAllocationResponse {
523 pub operation_id: [u8; 32],
524 pub allocation_sequence: u64,
525 pub component: ComponentInstanceId,
526 pub component_spec: ComponentSpecId,
527 pub spec_hash: [u8; 32],
528 pub role: CanisterRole,
529 pub provisioning_origin: ComponentProvisioningOrigin,
530 pub release_set: FleetSubnetRootReleaseSet,
531 pub phase: RootComponentAllocationPhase,
532 pub creation: Option<RootComponentCreationEvidence>,
533 pub installation: Option<RootComponentInstallEvidence>,
534}
535
536#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
543pub struct RootComponentChildAllocationResponse {
544 pub operation_id: [u8; 32],
545 pub component: ComponentInstanceId,
546 pub parent_canister_id: Principal,
547 pub parent_role: CanisterRole,
548 pub child_role: CanisterRole,
549 pub child_kind: ComponentChildKind,
550 pub maximum_instances_per_parent: u32,
551 pub maximum_descendants: u32,
552 pub maximum_registry_bytes: u64,
553 pub reserved_against_registry: ComponentRegistryHead,
554 pub release_set: FleetSubnetRootReleaseSet,
555 pub phase: RootComponentAllocationPhase,
556 pub creation: Option<RootComponentCreationEvidence>,
557 pub installation: Option<RootComponentChildInstallEvidence>,
558}
559
560#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
567pub struct RootComponentChildCommitResponse {
568 pub allocation: RootComponentChildAllocationResponse,
569 pub registry: ComponentRegistryPartitionResponse,
570 pub directory: ComponentDirectoryHead,
571}
572
573#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
580pub struct RootComponentChildDirectoryPreparationResponse {
581 pub committed: RootComponentChildCommitResponse,
582 pub child: ComponentRuntimeStatusResponse,
583 pub owning_component: ComponentRuntimeDirectoryConvergenceEvidence,
584 pub parent: Option<ComponentRuntimeDirectoryConvergenceEvidence>,
585}
586
587#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
594pub struct RootComponentChildRuntimeActivationResponse {
595 pub committed: RootComponentChildCommitResponse,
596 pub child: ComponentRuntimeStatusResponse,
597}
598
599#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
606pub struct RootComponentCommitResponse {
607 pub allocation: RootComponentAllocationResponse,
608 pub registry: ComponentRegistryPartitionResponse,
609 pub directory: ComponentDirectoryHead,
610}
611
612#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
619pub struct RootComponentDirectoryPreparationResponse {
620 pub committed: RootComponentCommitResponse,
621 pub target: ComponentRuntimeStatusResponse,
622}
623
624#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
631pub struct RootComponentRuntimeActivationResponse {
632 pub committed: RootComponentCommitResponse,
633 pub target: ComponentRuntimeStatusResponse,
634}
635
636#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
643pub struct RootComponentMembershipActivationResponse {
644 pub allocation: RootComponentAllocationResponse,
645 pub registry: ComponentRegistryPartitionResponse,
646 pub directory: ComponentDirectoryHead,
647 pub target: ComponentRuntimeStatusResponse,
648}
649
650#[cfg(test)]
651mod tests {
652 use super::*;
653 use crate::{
654 dto::root_store::RootStoreBootstrapRequest,
655 ids::{
656 AppId, CanonicalNetworkId, FleetCoordinatorBinding, FleetId, FleetKey,
657 FleetRegistryAuthority, ReleaseBuildId, ReleaseBuildNonce, ReleaseSetDigest, SubnetId,
658 },
659 };
660
661 #[test]
662 fn component_registry_contracts_round_trip_through_candid() {
663 let request = RootComponentRegistryPreparationRequest {
664 store_bootstrap: RootStoreBootstrapRequest {
665 manifest_payload_size_bytes: 128,
666 },
667 expected_fleet_registry: FleetRegistryVersion {
668 authority: fleet_registry_authority(),
669 revision: 4,
670 content_hash: [5; 32],
671 },
672 };
673 let response = RootComponentRegistryStatusResponse {
674 fleet_subnet_root: Principal::from_slice(&[6; 29]),
675 prepared_against_registry: request.expected_fleet_registry.clone(),
676 release_set: FleetSubnetRootReleaseSet {
677 release_build_id: ReleaseBuildId::from_nonce(ReleaseBuildNonce::from_random_bytes(
678 [7; 32],
679 )),
680 manifest_digest: ReleaseSetDigest::from_bytes([8; 32]),
681 },
682 component_topology_digest: ComponentTopologyDigest::from_bytes([9; 32]),
683 next_allocation_sequence: 1,
684 reserved_component_instances: 0,
685 committed_component_instances: 0,
686 managed_descendants: 0,
687 known_created_component_canisters: 0,
688 encoded_bytes: 0,
689 initial_inventory: Some(RootComponentInitialInventoryStatus {
690 fleet_activation_operation_id: [10; 32],
691 component_count: 0,
692 inventory_hash: [11; 32],
693 sealed_at_ns: 12,
694 directories_converged: true,
695 root_runtime_activated: true,
696 }),
697 };
698 let allocation = RootComponentAllocationResponse {
699 operation_id: [10; 32],
700 allocation_sequence: 1,
701 component: ComponentInstanceId::from_generated_bytes([11; 32]),
702 component_spec: "projects".parse().expect("Component Spec ID"),
703 spec_hash: [12; 32],
704 role: CanisterRole::new("project_hub"),
705 provisioning_origin: ComponentProvisioningOrigin::FleetAdministrator {
706 caller: Principal::from_slice(&[13; 29]),
707 },
708 release_set: response.release_set,
709 phase: RootComponentAllocationPhase::Reserved,
710 creation: None,
711 installation: None,
712 };
713 let created = RootComponentAllocationResponse {
714 phase: RootComponentAllocationPhase::Created,
715 creation: Some(RootComponentCreationEvidence {
716 wasm_store: Principal::from_slice(&[14; 29]),
717 payload_hash: [15; 32],
718 payload_size_bytes: 4_096,
719 initial_cycles: Cycles::new(5_000_000_000_000),
720 controller: Principal::from_slice(&[6; 29]),
721 canister: Some(Principal::from_slice(&[16; 29])),
722 }),
723 installation: None,
724 ..allocation.clone()
725 };
726 let request_bytes = candid::encode_one(&request).expect("encode request");
727 let response_bytes = candid::encode_one(&response).expect("encode response");
728 let allocation_bytes = candid::encode_one(&allocation).expect("encode allocation");
729 let created_bytes = candid::encode_one(&created).expect("encode created allocation");
730
731 assert_eq!(
732 candid::decode_one::<RootComponentRegistryPreparationRequest>(&request_bytes)
733 .expect("decode request"),
734 request
735 );
736 assert_eq!(
737 candid::decode_one::<RootComponentRegistryStatusResponse>(&response_bytes)
738 .expect("decode response"),
739 response
740 );
741 assert_eq!(
742 candid::decode_one::<RootComponentAllocationResponse>(&allocation_bytes)
743 .expect("decode allocation"),
744 allocation
745 );
746 assert_eq!(
747 candid::decode_one::<RootComponentAllocationResponse>(&created_bytes)
748 .expect("decode created allocation"),
749 created
750 );
751 }
752
753 #[test]
754 fn component_commit_response_round_trips_through_candid() {
755 let root = Principal::from_slice(&[6; 29]);
756 let component = ComponentInstanceId::from_generated_bytes([11; 32]);
757 let component_spec: ComponentSpecId = "projects".parse().expect("Component Spec ID");
758 let release_set = FleetSubnetRootReleaseSet {
759 release_build_id: ReleaseBuildId::from_nonce(ReleaseBuildNonce::from_random_bytes(
760 [7; 32],
761 )),
762 manifest_digest: ReleaseSetDigest::from_bytes([8; 32]),
763 };
764 let provisioning_origin = ComponentProvisioningOrigin::FleetAdministrator {
765 caller: Principal::from_slice(&[13; 29]),
766 };
767 let binding = ComponentBinding {
768 authority: fleet_registry_authority(),
769 component,
770 component_spec: component_spec.clone(),
771 spec_hash: [12; 32],
772 role: CanisterRole::new("project_hub"),
773 placement_subnet: SubnetId::from_principal(Principal::from_slice(&[17; 29])),
774 fleet_subnet_root: root,
775 canister_id: Principal::from_slice(&[16; 29]),
776 };
777 let head = ComponentRegistryHead {
778 component,
779 revision: 1,
780 content_hash: [18; 32],
781 };
782 let committed = RootComponentCommitResponse {
783 allocation: RootComponentAllocationResponse {
784 operation_id: [10; 32],
785 allocation_sequence: 1,
786 component,
787 component_spec,
788 spec_hash: binding.spec_hash,
789 role: binding.role.clone(),
790 provisioning_origin: provisioning_origin.clone(),
791 release_set,
792 phase: RootComponentAllocationPhase::Committed,
793 creation: Some(RootComponentCreationEvidence {
794 wasm_store: Principal::from_slice(&[14; 29]),
795 payload_hash: [15; 32],
796 payload_size_bytes: 4_096,
797 initial_cycles: Cycles::new(5_000_000_000_000),
798 controller: root,
799 canister: Some(binding.canister_id),
800 }),
801 installation: Some(RootComponentInstallEvidence {
802 raw_module_hash: [20; 32],
803 chunk_hashes: vec![vec![21; 32]],
804 binding: binding.clone(),
805 }),
806 },
807 registry: ComponentRegistryPartitionResponse {
808 head: head.clone(),
809 binding: binding.clone(),
810 provisioning_origin,
811 release_set,
812 status: ComponentLifecycleStatus::Prepared,
813 reserved_descendants: 0,
814 committed_descendants: 0,
815 encoded_bytes: 2_048,
816 },
817 directory: ComponentDirectoryHead {
818 provenance: ComponentDirectoryProvenance {
819 component: binding,
820 source_fleet_subnet_root: root,
821 component_registry_revision: head.revision,
822 component_registry_content_hash: head.content_hash,
823 synchronized_at_ns: 19,
824 },
825 descendant_count: 0,
826 },
827 };
828 let committed_bytes = candid::encode_one(&committed).expect("encode committed allocation");
829
830 assert_eq!(
831 candid::decode_one::<RootComponentCommitResponse>(&committed_bytes)
832 .expect("decode committed allocation"),
833 committed
834 );
835 }
836
837 fn fleet_registry_authority() -> FleetRegistryAuthority {
838 FleetRegistryAuthority {
839 binding: FleetCoordinatorBinding {
840 fleet: crate::ids::FleetBinding {
841 fleet: FleetKey {
842 canonical_network_id: CanonicalNetworkId::public_ic(),
843 fleet_id: FleetId::from_generated_bytes([1; 32]),
844 },
845 app: AppId::from("toko"),
846 },
847 coordinator_subnet: SubnetId::from_principal(Principal::from_slice(&[2; 29])),
848 coordinator: Principal::from_slice(&[3; 29]),
849 },
850 epoch: 1,
851 }
852 }
853
854 #[test]
855 fn component_creation_request_round_trips_through_candid() {
856 let request = RootComponentCreationRequest {
857 operation_id: [10; 32],
858 };
859 let bytes = candid::encode_one(request).expect("encode creation request");
860
861 assert_eq!(
862 candid::decode_one::<RootComponentCreationRequest>(&bytes)
863 .expect("decode creation request"),
864 request
865 );
866 }
867
868 #[test]
869 #[expect(
870 clippy::too_many_lines,
871 reason = "one round-trip test keeps the complete child lifecycle boundary coherent"
872 )]
873 fn component_child_lifecycle_contracts_round_trip_through_candid() {
874 let component = ComponentInstanceId::from_generated_bytes([11; 32]);
875 let registry = ComponentRegistryHead {
876 component,
877 revision: 2,
878 content_hash: [12; 32],
879 };
880 let request = RootComponentChildAllocationRequest {
881 operation_id: [13; 32],
882 component,
883 expected_registry: registry.clone(),
884 child_role: CanisterRole::new("project_instance"),
885 };
886 let status_request = RootComponentChildAllocationStatusRequest {
887 operation_id: request.operation_id,
888 component,
889 };
890 let creation_request = RootComponentChildCreationRequest {
891 operation_id: request.operation_id,
892 component,
893 };
894 let install_request = RootComponentChildInstallRequest {
895 operation_id: request.operation_id,
896 component,
897 };
898 let commit_request = RootComponentChildCommitRequest {
899 operation_id: request.operation_id,
900 component,
901 };
902 let directory_request = RootComponentChildDirectoryPreparationRequest {
903 operation_id: request.operation_id,
904 component,
905 };
906 let activation_request = RootComponentChildRuntimeActivationRequest {
907 operation_id: request.operation_id,
908 component,
909 };
910 let root = Principal::from_slice(&[17; 29]);
911 let parent = Principal::from_slice(&[14; 29]);
912 let child = Principal::from_slice(&[18; 29]);
913 let child_binding = ComponentChildBinding {
914 component: ComponentBinding {
915 authority: fleet_registry_authority(),
916 component,
917 component_spec: "projects".parse().expect("Component Spec"),
918 spec_hash: [19; 32],
919 role: CanisterRole::new("project_hub"),
920 placement_subnet: SubnetId::from_principal(Principal::from_slice(&[20; 29])),
921 fleet_subnet_root: root,
922 canister_id: parent,
923 },
924 parent_canister_id: parent,
925 role: request.child_role.clone(),
926 canister_id: child,
927 };
928 let response = RootComponentChildAllocationResponse {
929 operation_id: request.operation_id,
930 component,
931 parent_canister_id: parent,
932 parent_role: CanisterRole::new("project_hub"),
933 child_role: request.child_role.clone(),
934 child_kind: ComponentChildKind::Instance,
935 maximum_instances_per_parent: 10_000,
936 maximum_descendants: 20_000,
937 maximum_registry_bytes: 16_777_216,
938 reserved_against_registry: registry,
939 release_set: FleetSubnetRootReleaseSet {
940 release_build_id: ReleaseBuildId::from_nonce(ReleaseBuildNonce::from_random_bytes(
941 [15; 32],
942 )),
943 manifest_digest: ReleaseSetDigest::from_bytes([16; 32]),
944 },
945 phase: RootComponentAllocationPhase::Verified,
946 creation: Some(RootComponentCreationEvidence {
947 wasm_store: Principal::from_slice(&[21; 29]),
948 payload_hash: [22; 32],
949 payload_size_bytes: 4_096,
950 initial_cycles: Cycles::new(5_000_000_000_000),
951 controller: root,
952 canister: Some(child),
953 }),
954 installation: Some(RootComponentChildInstallEvidence {
955 raw_module_hash: [23; 32],
956 chunk_hashes: vec![vec![24; 32]],
957 binding: child_binding.clone(),
958 }),
959 };
960 let commit_response = RootComponentChildCommitResponse {
961 allocation: response.clone(),
962 registry: ComponentRegistryPartitionResponse {
963 head: ComponentRegistryHead {
964 component,
965 revision: 3,
966 content_hash: [25; 32],
967 },
968 binding: child_binding.component.clone(),
969 provisioning_origin: ComponentProvisioningOrigin::FleetAdministrator {
970 caller: Principal::from_slice(&[26; 29]),
971 },
972 release_set: response.release_set,
973 status: ComponentLifecycleStatus::Active,
974 reserved_descendants: 0,
975 committed_descendants: 1,
976 encoded_bytes: 8_192,
977 },
978 directory: ComponentDirectoryHead {
979 provenance: ComponentDirectoryProvenance {
980 component: child_binding.component.clone(),
981 source_fleet_subnet_root: root,
982 component_registry_revision: 3,
983 component_registry_content_hash: [25; 32],
984 synchronized_at_ns: 27,
985 },
986 descendant_count: 1,
987 },
988 };
989 let runtime_authority = ComponentRuntimeDirectoryAuthority {
990 fleet: FleetDirectorySnapshot {
991 provenance: crate::dto::fleet_registry::FleetDirectoryProvenance {
992 registry: FleetRegistryVersion {
993 authority: fleet_registry_authority(),
994 revision: 4,
995 content_hash: [28; 32],
996 },
997 source_fleet_subnet_root: root,
998 },
999 fleet_subnet_roots: vec![
1000 crate::dto::fleet_registry::FleetSubnetRootDirectoryEntry {
1001 placement_subnet: commit_response.registry.binding.placement_subnet,
1002 fleet_subnet_root: root,
1003 status: crate::dto::fleet_registry::FleetSubnetRootStatus::Active,
1004 },
1005 ],
1006 },
1007 component: commit_response.directory.clone(),
1008 };
1009 let activation = ComponentRuntimeActivationEvidence {
1010 directory_authority_hash: [29; 32],
1011 activated_at_ns: 30,
1012 };
1013 let directory_response = RootComponentChildDirectoryPreparationResponse {
1014 committed: commit_response.clone(),
1015 child: ComponentRuntimeStatusResponse {
1016 operation_id: request.operation_id,
1017 binding: ManagedCanisterBinding::ComponentChild(child_binding.clone()),
1018 phase: ComponentRuntimePhase::DirectoryPrepared,
1019 authority: Some(runtime_authority.clone()),
1020 authority_hash: Some([31; 32]),
1021 activation: None,
1022 },
1023 owning_component: ComponentRuntimeDirectoryConvergenceEvidence {
1024 operation_id: [32; 32],
1025 binding: ManagedCanisterBinding::Component(child_binding.component.clone()),
1026 covered_authority: runtime_authority.clone(),
1027 covered_authority_hash: [31; 32],
1028 activation,
1029 },
1030 parent: None,
1031 };
1032 let activation_response = RootComponentChildRuntimeActivationResponse {
1033 committed: commit_response.clone(),
1034 child: ComponentRuntimeStatusResponse {
1035 operation_id: request.operation_id,
1036 binding: ManagedCanisterBinding::ComponentChild(child_binding),
1037 phase: ComponentRuntimePhase::Active,
1038 authority: Some(runtime_authority),
1039 authority_hash: Some([31; 32]),
1040 activation: Some(ComponentRuntimeActivationEvidence {
1041 directory_authority_hash: [31; 32],
1042 activated_at_ns: 33,
1043 }),
1044 },
1045 };
1046
1047 let request_bytes = candid::encode_one(&request).expect("encode child reservation");
1048 let status_bytes =
1049 candid::encode_one(status_request).expect("encode child reservation status");
1050 let creation_bytes =
1051 candid::encode_one(creation_request).expect("encode child creation request");
1052 let install_bytes =
1053 candid::encode_one(install_request).expect("encode child install request");
1054 let response_bytes = candid::encode_one(&response).expect("encode child response");
1055 let commit_request_bytes =
1056 candid::encode_one(commit_request).expect("encode child commit request");
1057 let directory_request_bytes =
1058 candid::encode_one(directory_request).expect("encode child Directory request");
1059 let activation_request_bytes =
1060 candid::encode_one(activation_request).expect("encode child activation request");
1061 let commit_response_bytes =
1062 candid::encode_one(&commit_response).expect("encode child commit response");
1063 let directory_response_bytes =
1064 candid::encode_one(&directory_response).expect("encode child Directory response");
1065 let activation_response_bytes =
1066 candid::encode_one(&activation_response).expect("encode child activation response");
1067
1068 assert_eq!(
1069 candid::decode_one::<RootComponentChildAllocationRequest>(&request_bytes)
1070 .expect("decode child reservation"),
1071 request
1072 );
1073 assert_eq!(
1074 candid::decode_one::<RootComponentChildAllocationStatusRequest>(&status_bytes)
1075 .expect("decode child reservation status"),
1076 status_request
1077 );
1078 assert_eq!(
1079 candid::decode_one::<RootComponentChildCreationRequest>(&creation_bytes)
1080 .expect("decode child creation request"),
1081 creation_request
1082 );
1083 assert_eq!(
1084 candid::decode_one::<RootComponentChildInstallRequest>(&install_bytes)
1085 .expect("decode child install request"),
1086 install_request
1087 );
1088 assert_eq!(
1089 candid::decode_one::<RootComponentChildAllocationResponse>(&response_bytes)
1090 .expect("decode child response"),
1091 response
1092 );
1093 assert_eq!(
1094 candid::decode_one::<RootComponentChildCommitRequest>(&commit_request_bytes)
1095 .expect("decode child commit request"),
1096 commit_request
1097 );
1098 assert_eq!(
1099 candid::decode_one::<RootComponentChildDirectoryPreparationRequest>(
1100 &directory_request_bytes
1101 )
1102 .expect("decode child Directory request"),
1103 directory_request
1104 );
1105 assert_eq!(
1106 candid::decode_one::<RootComponentChildCommitResponse>(&commit_response_bytes)
1107 .expect("decode child commit response"),
1108 commit_response
1109 );
1110 assert_eq!(
1111 candid::decode_one::<RootComponentChildDirectoryPreparationResponse>(
1112 &directory_response_bytes
1113 )
1114 .expect("decode child Directory response"),
1115 directory_response
1116 );
1117 assert_eq!(
1118 candid::decode_one::<RootComponentChildRuntimeActivationRequest>(
1119 &activation_request_bytes
1120 )
1121 .expect("decode child activation request"),
1122 activation_request
1123 );
1124 assert_eq!(
1125 candid::decode_one::<RootComponentChildRuntimeActivationResponse>(
1126 &activation_response_bytes
1127 )
1128 .expect("decode child activation response"),
1129 activation_response
1130 );
1131 }
1132
1133 #[test]
1134 fn component_install_request_round_trips_through_candid() {
1135 let request = RootComponentInstallRequest {
1136 operation_id: [10; 32],
1137 };
1138 let bytes = candid::encode_one(request).expect("encode install request");
1139
1140 assert_eq!(
1141 candid::decode_one::<RootComponentInstallRequest>(&bytes)
1142 .expect("decode install request"),
1143 request
1144 );
1145 }
1146
1147 #[test]
1148 fn component_commit_request_round_trips_through_candid() {
1149 let request = RootComponentCommitRequest {
1150 operation_id: [10; 32],
1151 };
1152 let bytes = candid::encode_one(request).expect("encode commit request");
1153
1154 assert_eq!(
1155 candid::decode_one::<RootComponentCommitRequest>(&bytes)
1156 .expect("decode commit request"),
1157 request
1158 );
1159 }
1160
1161 #[test]
1162 fn component_runtime_activation_requests_round_trip_through_candid() {
1163 let root_request = RootComponentRuntimeActivationRequest {
1164 operation_id: [22; 32],
1165 };
1166 let target_request = ComponentRuntimeActivationRequest {
1167 operation_id: root_request.operation_id,
1168 directory_authority_hash: [23; 32],
1169 };
1170 let membership_request = RootComponentMembershipActivationRequest {
1171 operation_id: root_request.operation_id,
1172 };
1173 let root_bytes = candid::encode_one(root_request).expect("encode root activation request");
1174 let target_bytes =
1175 candid::encode_one(target_request).expect("encode target activation request");
1176 let membership_bytes =
1177 candid::encode_one(membership_request).expect("encode membership activation request");
1178
1179 assert_eq!(
1180 candid::decode_one::<RootComponentRuntimeActivationRequest>(&root_bytes)
1181 .expect("decode root activation request"),
1182 root_request
1183 );
1184 assert_eq!(
1185 candid::decode_one::<ComponentRuntimeActivationRequest>(&target_bytes)
1186 .expect("decode target activation request"),
1187 target_request
1188 );
1189 assert_eq!(
1190 candid::decode_one::<RootComponentMembershipActivationRequest>(&membership_bytes)
1191 .expect("decode membership activation request"),
1192 membership_request
1193 );
1194 }
1195}