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, Debug, Deserialize, Eq, PartialEq, Serialize)]
128pub struct RootComponentSubtreeRemovalRequest {
129 pub operation_id: [u8; 32],
130 pub component: ComponentInstanceId,
131 pub target_canister_id: Principal,
132 pub expected_registry: ComponentRegistryHead,
133}
134
135#[derive(CandidType, Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
142pub struct RootComponentSubtreeRemovalAdvanceRequest {
143 pub operation_id: [u8; 32],
144 pub component: ComponentInstanceId,
145 pub expected_traversal_steps: u32,
146}
147
148#[derive(CandidType, Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
155pub struct RootComponentSubtreeRemovalStopPreparationRequest {
156 pub operation_id: [u8; 32],
157 pub component: ComponentInstanceId,
158 pub expected_traversal_steps: u32,
159 pub expected_leaf_canister_id: Principal,
160 pub expected_leaf_parent_canister_id: Principal,
161}
162
163#[derive(CandidType, Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
170pub struct RootComponentSubtreeRemovalStatusRequest {
171 pub operation_id: [u8; 32],
172 pub component: ComponentInstanceId,
173}
174
175#[derive(CandidType, Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
182pub struct RootComponentChildCreationRequest {
183 pub operation_id: [u8; 32],
184 pub component: ComponentInstanceId,
185}
186
187#[derive(CandidType, Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
194pub struct RootComponentChildInstallRequest {
195 pub operation_id: [u8; 32],
196 pub component: ComponentInstanceId,
197}
198
199#[derive(CandidType, Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
206pub struct RootComponentChildCommitRequest {
207 pub operation_id: [u8; 32],
208 pub component: ComponentInstanceId,
209}
210
211#[derive(CandidType, Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
218pub struct RootComponentChildDirectoryPreparationRequest {
219 pub operation_id: [u8; 32],
220 pub component: ComponentInstanceId,
221}
222
223#[derive(CandidType, Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
230pub struct RootComponentChildRuntimeActivationRequest {
231 pub operation_id: [u8; 32],
232 pub component: ComponentInstanceId,
233}
234
235#[derive(CandidType, Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
242pub struct RootComponentChildMembershipActivationRequest {
243 pub operation_id: [u8; 32],
244 pub component: ComponentInstanceId,
245}
246
247#[derive(CandidType, Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
254pub struct RootComponentCreationRequest {
255 pub operation_id: [u8; 32],
256}
257
258#[derive(CandidType, Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
265pub struct RootComponentInstallRequest {
266 pub operation_id: [u8; 32],
267}
268
269#[derive(CandidType, Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
276pub struct RootComponentCommitRequest {
277 pub operation_id: [u8; 32],
278}
279
280#[derive(CandidType, Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
287pub struct RootComponentDirectoryPreparationRequest {
288 pub operation_id: [u8; 32],
289}
290
291#[derive(CandidType, Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
298pub struct RootComponentRuntimeActivationRequest {
299 pub operation_id: [u8; 32],
300}
301
302#[derive(CandidType, Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
309pub struct RootComponentMembershipActivationRequest {
310 pub operation_id: [u8; 32],
311}
312
313#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
320pub enum ComponentProvisioningOrigin {
321 FleetAdministrator { caller: Principal },
322}
323
324#[derive(CandidType, Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
331pub enum RootComponentAllocationPhase {
332 Reserved,
333 CreationIntent,
334 Created,
335 InstallIntent,
336 Installed,
337 Verified,
338 Committed,
339}
340
341#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
348pub enum RootComponentSubtreeRemovalPhase {
349 Fenced,
350 Traversing(RootComponentSubtreeRemovalNode),
351 LeafSelected(RootComponentSubtreeRemovalNode),
352 StopIntent(RootComponentSubtreeRemovalStopIntent),
353}
354
355#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
362pub struct RootComponentSubtreeRemovalNode {
363 pub canister_id: Principal,
364 pub parent_canister_id: Principal,
365 pub role: CanisterRole,
366 pub kind: ComponentChildKind,
367 pub installed_artifact_hash: [u8; 32],
368 pub status: ComponentLifecycleStatus,
369}
370
371#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
378pub struct RootComponentSubtreeRemovalStopIntent {
379 pub leaf: RootComponentSubtreeRemovalNode,
380 pub controller: Principal,
381}
382
383#[derive(CandidType, Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
390pub enum ComponentLifecycleStatus {
391 Prepared,
392 Active,
393 Draining,
394 Removed,
395}
396
397#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
404pub struct ComponentRegistryHead {
405 pub component: ComponentInstanceId,
406 pub revision: u64,
407 pub content_hash: [u8; 32],
408}
409
410#[derive(CandidType, Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
417pub struct ComponentRegistryPartitionRequest {
418 pub component: ComponentInstanceId,
419}
420
421#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
428pub struct ComponentRegistryPartitionResponse {
429 pub head: ComponentRegistryHead,
430 pub binding: ComponentBinding,
431 pub provisioning_origin: ComponentProvisioningOrigin,
432 pub release_set: FleetSubnetRootReleaseSet,
433 pub status: ComponentLifecycleStatus,
434 pub reserved_descendants: u32,
435 pub committed_descendants: u32,
436 pub encoded_bytes: u64,
437}
438
439#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
446pub struct ComponentDirectoryProvenance {
447 pub component: ComponentBinding,
448 pub source_fleet_subnet_root: Principal,
449 pub component_registry_revision: u64,
450 pub component_registry_content_hash: [u8; 32],
451 pub synchronized_at_ns: u64,
452}
453
454#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
461pub struct ComponentDirectoryHead {
462 pub provenance: ComponentDirectoryProvenance,
463 pub descendant_count: u32,
464}
465
466#[derive(CandidType, Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
473pub struct ComponentDirectoryHeadRequest {
474 pub component: ComponentInstanceId,
475}
476
477#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
484pub struct ComponentDirectoryPageCursor(pub Vec<u8>);
485
486#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
493pub struct ComponentDirectoryPageRequest {
494 pub directory: ComponentDirectoryHead,
495 pub parent_canister_id: Option<Principal>,
496 pub role: Option<CanisterRole>,
497 pub status: Option<ComponentLifecycleStatus>,
498 pub cursor: Option<ComponentDirectoryPageCursor>,
499 pub limit: u16,
500}
501
502#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
509pub struct ComponentDirectoryChildEntry {
510 pub binding: ComponentChildBinding,
511 pub kind: ComponentChildKind,
512 pub installed_artifact_hash: [u8; 32],
513 pub status: ComponentLifecycleStatus,
514}
515
516#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
523pub struct ComponentDirectoryPageResponse {
524 pub directory: ComponentDirectoryHead,
525 pub entries: Vec<ComponentDirectoryChildEntry>,
526 pub next_cursor: Option<ComponentDirectoryPageCursor>,
527}
528
529#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
536pub struct ComponentRuntimeDirectoryAuthority {
537 pub fleet: FleetDirectorySnapshot,
538 pub component: ComponentDirectoryHead,
539}
540
541#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
548pub struct ComponentRuntimeDirectoryPreparationRequest {
549 pub operation_id: [u8; 32],
550 pub authority: ComponentRuntimeDirectoryAuthority,
551}
552
553#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
560pub struct ComponentRuntimeDirectorySynchronizationRequest {
561 pub operation_id: [u8; 32],
562 pub authority: ComponentRuntimeDirectoryAuthority,
563}
564
565#[derive(CandidType, Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
572pub enum ComponentRuntimePhase {
573 AwaitingDirectory,
574 DirectoryPrepared,
575 Active,
576}
577
578#[derive(CandidType, Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
585pub struct ComponentRuntimeActivationEvidence {
586 pub directory_authority_hash: [u8; 32],
587 pub activated_at_ns: u64,
588}
589
590#[derive(CandidType, Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
597pub struct ComponentRuntimeActivationRequest {
598 pub operation_id: [u8; 32],
599 pub directory_authority_hash: [u8; 32],
600}
601
602#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
609pub struct ComponentRuntimeStatusResponse {
610 pub operation_id: [u8; 32],
611 pub binding: ManagedCanisterBinding,
612 pub phase: ComponentRuntimePhase,
613 pub authority: Option<ComponentRuntimeDirectoryAuthority>,
614 pub authority_hash: Option<[u8; 32]>,
615 pub activation: Option<ComponentRuntimeActivationEvidence>,
616}
617
618#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
625pub struct ComponentRuntimeDirectoryConvergenceEvidence {
626 pub operation_id: [u8; 32],
627 pub binding: ManagedCanisterBinding,
628 pub covered_authority: ComponentRuntimeDirectoryAuthority,
629 pub covered_authority_hash: [u8; 32],
630 pub activation: ComponentRuntimeActivationEvidence,
631}
632
633#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
640pub struct RootComponentCreationEvidence {
641 pub wasm_store: Principal,
642 pub payload_hash: [u8; 32],
643 pub payload_size_bytes: u64,
644 pub initial_cycles: Cycles,
645 pub controller: Principal,
646 pub canister: Option<Principal>,
647}
648
649#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
656pub struct RootComponentInstallEvidence {
657 pub raw_module_hash: [u8; 32],
658 pub chunk_hashes: Vec<Vec<u8>>,
659 pub binding: ComponentBinding,
660}
661
662#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
669pub struct RootComponentChildInstallEvidence {
670 pub raw_module_hash: [u8; 32],
671 pub chunk_hashes: Vec<Vec<u8>>,
672 pub binding: ComponentChildBinding,
673}
674
675#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
682pub struct RootComponentAllocationResponse {
683 pub operation_id: [u8; 32],
684 pub allocation_sequence: u64,
685 pub component: ComponentInstanceId,
686 pub component_spec: ComponentSpecId,
687 pub spec_hash: [u8; 32],
688 pub role: CanisterRole,
689 pub provisioning_origin: ComponentProvisioningOrigin,
690 pub release_set: FleetSubnetRootReleaseSet,
691 pub phase: RootComponentAllocationPhase,
692 pub creation: Option<RootComponentCreationEvidence>,
693 pub installation: Option<RootComponentInstallEvidence>,
694}
695
696#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
703pub struct RootComponentChildAllocationResponse {
704 pub operation_id: [u8; 32],
705 pub component: ComponentInstanceId,
706 pub parent_canister_id: Principal,
707 pub parent_role: CanisterRole,
708 pub child_role: CanisterRole,
709 pub child_kind: ComponentChildKind,
710 pub maximum_instances_per_parent: u32,
711 pub maximum_descendants: u32,
712 pub maximum_registry_bytes: u64,
713 pub reserved_against_registry: ComponentRegistryHead,
714 pub release_set: FleetSubnetRootReleaseSet,
715 pub phase: RootComponentAllocationPhase,
716 pub creation: Option<RootComponentCreationEvidence>,
717 pub installation: Option<RootComponentChildInstallEvidence>,
718}
719
720#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
727pub struct RootComponentSubtreeRemovalResponse {
728 pub operation_id: [u8; 32],
729 pub component: ComponentInstanceId,
730 pub target_canister_id: Principal,
731 pub target_parent_canister_id: Principal,
732 pub target_role: CanisterRole,
733 pub target_status: ComponentLifecycleStatus,
734 pub reserved_against_registry: ComponentRegistryHead,
735 pub traversal_steps: u32,
736 pub phase: RootComponentSubtreeRemovalPhase,
737}
738
739#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
746pub struct RootComponentChildCommitResponse {
747 pub allocation: RootComponentChildAllocationResponse,
748 pub registry: ComponentRegistryPartitionResponse,
749 pub directory: ComponentDirectoryHead,
750}
751
752#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
759pub struct RootComponentChildDirectoryPreparationResponse {
760 pub committed: RootComponentChildCommitResponse,
761 pub child: ComponentRuntimeStatusResponse,
762 pub owning_component: ComponentRuntimeDirectoryConvergenceEvidence,
763 pub parent: Option<ComponentRuntimeDirectoryConvergenceEvidence>,
764}
765
766#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
773pub struct RootComponentChildRuntimeActivationResponse {
774 pub committed: RootComponentChildCommitResponse,
775 pub child: ComponentRuntimeStatusResponse,
776}
777
778#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
785pub struct RootComponentChildMembershipActivationResponse {
786 pub committed: RootComponentChildCommitResponse,
787 pub registry: ComponentRegistryPartitionResponse,
788 pub directory: ComponentDirectoryHead,
789 pub child: ComponentRuntimeStatusResponse,
790}
791
792#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
799pub struct RootComponentCommitResponse {
800 pub allocation: RootComponentAllocationResponse,
801 pub registry: ComponentRegistryPartitionResponse,
802 pub directory: ComponentDirectoryHead,
803}
804
805#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
812pub struct RootComponentDirectoryPreparationResponse {
813 pub committed: RootComponentCommitResponse,
814 pub target: ComponentRuntimeStatusResponse,
815}
816
817#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
824pub struct RootComponentRuntimeActivationResponse {
825 pub committed: RootComponentCommitResponse,
826 pub target: ComponentRuntimeStatusResponse,
827}
828
829#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
836pub struct RootComponentMembershipActivationResponse {
837 pub allocation: RootComponentAllocationResponse,
838 pub registry: ComponentRegistryPartitionResponse,
839 pub directory: ComponentDirectoryHead,
840 pub target: ComponentRuntimeStatusResponse,
841}
842
843#[cfg(test)]
844mod tests {
845 use super::*;
846 use crate::{
847 dto::root_store::RootStoreBootstrapRequest,
848 ids::{
849 AppId, CanonicalNetworkId, FleetCoordinatorBinding, FleetId, FleetKey,
850 FleetRegistryAuthority, ReleaseBuildId, ReleaseBuildNonce, ReleaseSetDigest, SubnetId,
851 },
852 };
853
854 #[test]
855 fn component_registry_contracts_round_trip_through_candid() {
856 let request = RootComponentRegistryPreparationRequest {
857 store_bootstrap: RootStoreBootstrapRequest {
858 manifest_payload_size_bytes: 128,
859 },
860 expected_fleet_registry: FleetRegistryVersion {
861 authority: fleet_registry_authority(),
862 revision: 4,
863 content_hash: [5; 32],
864 },
865 };
866 let response = RootComponentRegistryStatusResponse {
867 fleet_subnet_root: Principal::from_slice(&[6; 29]),
868 prepared_against_registry: request.expected_fleet_registry.clone(),
869 release_set: FleetSubnetRootReleaseSet {
870 release_build_id: ReleaseBuildId::from_nonce(ReleaseBuildNonce::from_random_bytes(
871 [7; 32],
872 )),
873 manifest_digest: ReleaseSetDigest::from_bytes([8; 32]),
874 },
875 component_topology_digest: ComponentTopologyDigest::from_bytes([9; 32]),
876 next_allocation_sequence: 1,
877 reserved_component_instances: 0,
878 committed_component_instances: 0,
879 managed_descendants: 0,
880 known_created_component_canisters: 0,
881 encoded_bytes: 0,
882 initial_inventory: Some(RootComponentInitialInventoryStatus {
883 fleet_activation_operation_id: [10; 32],
884 component_count: 0,
885 inventory_hash: [11; 32],
886 sealed_at_ns: 12,
887 directories_converged: true,
888 root_runtime_activated: true,
889 }),
890 };
891 let allocation = RootComponentAllocationResponse {
892 operation_id: [10; 32],
893 allocation_sequence: 1,
894 component: ComponentInstanceId::from_generated_bytes([11; 32]),
895 component_spec: "projects".parse().expect("Component Spec ID"),
896 spec_hash: [12; 32],
897 role: CanisterRole::new("project_hub"),
898 provisioning_origin: ComponentProvisioningOrigin::FleetAdministrator {
899 caller: Principal::from_slice(&[13; 29]),
900 },
901 release_set: response.release_set,
902 phase: RootComponentAllocationPhase::Reserved,
903 creation: None,
904 installation: None,
905 };
906 let created = RootComponentAllocationResponse {
907 phase: RootComponentAllocationPhase::Created,
908 creation: Some(RootComponentCreationEvidence {
909 wasm_store: Principal::from_slice(&[14; 29]),
910 payload_hash: [15; 32],
911 payload_size_bytes: 4_096,
912 initial_cycles: Cycles::new(5_000_000_000_000),
913 controller: Principal::from_slice(&[6; 29]),
914 canister: Some(Principal::from_slice(&[16; 29])),
915 }),
916 installation: None,
917 ..allocation.clone()
918 };
919 let request_bytes = candid::encode_one(&request).expect("encode request");
920 let response_bytes = candid::encode_one(&response).expect("encode response");
921 let allocation_bytes = candid::encode_one(&allocation).expect("encode allocation");
922 let created_bytes = candid::encode_one(&created).expect("encode created allocation");
923
924 assert_eq!(
925 candid::decode_one::<RootComponentRegistryPreparationRequest>(&request_bytes)
926 .expect("decode request"),
927 request
928 );
929 assert_eq!(
930 candid::decode_one::<RootComponentRegistryStatusResponse>(&response_bytes)
931 .expect("decode response"),
932 response
933 );
934 assert_eq!(
935 candid::decode_one::<RootComponentAllocationResponse>(&allocation_bytes)
936 .expect("decode allocation"),
937 allocation
938 );
939 assert_eq!(
940 candid::decode_one::<RootComponentAllocationResponse>(&created_bytes)
941 .expect("decode created allocation"),
942 created
943 );
944 }
945
946 #[test]
947 fn component_commit_response_round_trips_through_candid() {
948 let root = Principal::from_slice(&[6; 29]);
949 let component = ComponentInstanceId::from_generated_bytes([11; 32]);
950 let component_spec: ComponentSpecId = "projects".parse().expect("Component Spec ID");
951 let release_set = FleetSubnetRootReleaseSet {
952 release_build_id: ReleaseBuildId::from_nonce(ReleaseBuildNonce::from_random_bytes(
953 [7; 32],
954 )),
955 manifest_digest: ReleaseSetDigest::from_bytes([8; 32]),
956 };
957 let provisioning_origin = ComponentProvisioningOrigin::FleetAdministrator {
958 caller: Principal::from_slice(&[13; 29]),
959 };
960 let binding = ComponentBinding {
961 authority: fleet_registry_authority(),
962 component,
963 component_spec: component_spec.clone(),
964 spec_hash: [12; 32],
965 role: CanisterRole::new("project_hub"),
966 placement_subnet: SubnetId::from_principal(Principal::from_slice(&[17; 29])),
967 fleet_subnet_root: root,
968 canister_id: Principal::from_slice(&[16; 29]),
969 };
970 let head = ComponentRegistryHead {
971 component,
972 revision: 1,
973 content_hash: [18; 32],
974 };
975 let committed = RootComponentCommitResponse {
976 allocation: RootComponentAllocationResponse {
977 operation_id: [10; 32],
978 allocation_sequence: 1,
979 component,
980 component_spec,
981 spec_hash: binding.spec_hash,
982 role: binding.role.clone(),
983 provisioning_origin: provisioning_origin.clone(),
984 release_set,
985 phase: RootComponentAllocationPhase::Committed,
986 creation: Some(RootComponentCreationEvidence {
987 wasm_store: Principal::from_slice(&[14; 29]),
988 payload_hash: [15; 32],
989 payload_size_bytes: 4_096,
990 initial_cycles: Cycles::new(5_000_000_000_000),
991 controller: root,
992 canister: Some(binding.canister_id),
993 }),
994 installation: Some(RootComponentInstallEvidence {
995 raw_module_hash: [20; 32],
996 chunk_hashes: vec![vec![21; 32]],
997 binding: binding.clone(),
998 }),
999 },
1000 registry: ComponentRegistryPartitionResponse {
1001 head: head.clone(),
1002 binding: binding.clone(),
1003 provisioning_origin,
1004 release_set,
1005 status: ComponentLifecycleStatus::Prepared,
1006 reserved_descendants: 0,
1007 committed_descendants: 0,
1008 encoded_bytes: 2_048,
1009 },
1010 directory: ComponentDirectoryHead {
1011 provenance: ComponentDirectoryProvenance {
1012 component: binding,
1013 source_fleet_subnet_root: root,
1014 component_registry_revision: head.revision,
1015 component_registry_content_hash: head.content_hash,
1016 synchronized_at_ns: 19,
1017 },
1018 descendant_count: 0,
1019 },
1020 };
1021 let committed_bytes = candid::encode_one(&committed).expect("encode committed allocation");
1022
1023 assert_eq!(
1024 candid::decode_one::<RootComponentCommitResponse>(&committed_bytes)
1025 .expect("decode committed allocation"),
1026 committed
1027 );
1028 }
1029
1030 #[test]
1031 fn component_directory_page_contracts_round_trip_through_candid() {
1032 let root = Principal::from_slice(&[6; 29]);
1033 let component = ComponentInstanceId::from_generated_bytes([11; 32]);
1034 let binding = ComponentBinding {
1035 authority: fleet_registry_authority(),
1036 component,
1037 component_spec: "projects".parse().expect("Component Spec ID"),
1038 spec_hash: [12; 32],
1039 role: CanisterRole::new("project_hub"),
1040 placement_subnet: SubnetId::from_principal(Principal::from_slice(&[17; 29])),
1041 fleet_subnet_root: root,
1042 canister_id: Principal::from_slice(&[16; 29]),
1043 };
1044 let directory = ComponentDirectoryHead {
1045 provenance: ComponentDirectoryProvenance {
1046 component: binding.clone(),
1047 source_fleet_subnet_root: root,
1048 component_registry_revision: 3,
1049 component_registry_content_hash: [18; 32],
1050 synchronized_at_ns: 19,
1051 },
1052 descendant_count: 1,
1053 };
1054 let request = ComponentDirectoryPageRequest {
1055 directory: directory.clone(),
1056 parent_canister_id: Some(binding.canister_id),
1057 role: Some(CanisterRole::new("project_instance")),
1058 status: Some(ComponentLifecycleStatus::Active),
1059 cursor: Some(ComponentDirectoryPageCursor(vec![20; 64])),
1060 limit: 50,
1061 };
1062 let response = ComponentDirectoryPageResponse {
1063 directory,
1064 entries: vec![ComponentDirectoryChildEntry {
1065 binding: ComponentChildBinding {
1066 component: binding.clone(),
1067 parent_canister_id: binding.canister_id,
1068 role: CanisterRole::new("project_instance"),
1069 canister_id: Principal::from_slice(&[21; 29]),
1070 },
1071 kind: ComponentChildKind::Instance,
1072 installed_artifact_hash: [22; 32],
1073 status: ComponentLifecycleStatus::Active,
1074 }],
1075 next_cursor: Some(ComponentDirectoryPageCursor(vec![23; 64])),
1076 };
1077 let request_bytes = candid::encode_one(&request).expect("encode Directory page request");
1078 let response_bytes = candid::encode_one(&response).expect("encode Directory page response");
1079
1080 assert_eq!(
1081 candid::decode_one::<ComponentDirectoryPageRequest>(&request_bytes)
1082 .expect("decode Directory page request"),
1083 request
1084 );
1085 assert_eq!(
1086 candid::decode_one::<ComponentDirectoryPageResponse>(&response_bytes)
1087 .expect("decode Directory page response"),
1088 response
1089 );
1090 }
1091
1092 fn fleet_registry_authority() -> FleetRegistryAuthority {
1093 FleetRegistryAuthority {
1094 binding: FleetCoordinatorBinding {
1095 fleet: crate::ids::FleetBinding {
1096 fleet: FleetKey {
1097 canonical_network_id: CanonicalNetworkId::ic_mainnet(),
1098 fleet_id: FleetId::from_generated_bytes([1; 32]),
1099 },
1100 app: AppId::from("toko"),
1101 },
1102 coordinator_subnet: SubnetId::from_principal(Principal::from_slice(&[2; 29])),
1103 coordinator: Principal::from_slice(&[3; 29]),
1104 },
1105 epoch: 1,
1106 }
1107 }
1108
1109 #[test]
1110 fn component_creation_request_round_trips_through_candid() {
1111 let request = RootComponentCreationRequest {
1112 operation_id: [10; 32],
1113 };
1114 let bytes = candid::encode_one(request).expect("encode creation request");
1115
1116 assert_eq!(
1117 candid::decode_one::<RootComponentCreationRequest>(&bytes)
1118 .expect("decode creation request"),
1119 request
1120 );
1121 }
1122
1123 #[test]
1124 fn component_subtree_removal_contracts_round_trip_through_candid() {
1125 let component = ComponentInstanceId::from_generated_bytes([41; 32]);
1126 let registry = ComponentRegistryHead {
1127 component,
1128 revision: 7,
1129 content_hash: [42; 32],
1130 };
1131 let request = RootComponentSubtreeRemovalRequest {
1132 operation_id: [43; 32],
1133 component,
1134 target_canister_id: Principal::from_slice(&[44; 29]),
1135 expected_registry: registry.clone(),
1136 };
1137 let status_request = RootComponentSubtreeRemovalStatusRequest {
1138 operation_id: request.operation_id,
1139 component,
1140 };
1141 let advance_request = RootComponentSubtreeRemovalAdvanceRequest {
1142 operation_id: request.operation_id,
1143 component,
1144 expected_traversal_steps: 1,
1145 };
1146 let stop_request = RootComponentSubtreeRemovalStopPreparationRequest {
1147 operation_id: request.operation_id,
1148 component,
1149 expected_traversal_steps: 2,
1150 expected_leaf_canister_id: Principal::from_slice(&[46; 29]),
1151 expected_leaf_parent_canister_id: request.target_canister_id,
1152 };
1153 let response = RootComponentSubtreeRemovalResponse {
1154 operation_id: request.operation_id,
1155 component,
1156 target_canister_id: request.target_canister_id,
1157 target_parent_canister_id: Principal::from_slice(&[45; 29]),
1158 target_role: CanisterRole::new("project_instance"),
1159 target_status: ComponentLifecycleStatus::Active,
1160 reserved_against_registry: registry,
1161 traversal_steps: 2,
1162 phase: RootComponentSubtreeRemovalPhase::StopIntent(
1163 RootComponentSubtreeRemovalStopIntent {
1164 controller: Principal::from_slice(&[48; 29]),
1165 leaf: RootComponentSubtreeRemovalNode {
1166 canister_id: Principal::from_slice(&[46; 29]),
1167 parent_canister_id: request.target_canister_id,
1168 role: CanisterRole::new("project_ledger"),
1169 kind: ComponentChildKind::Singleton,
1170 installed_artifact_hash: [47; 32],
1171 status: ComponentLifecycleStatus::Active,
1172 },
1173 },
1174 ),
1175 };
1176
1177 let request_bytes = candid::encode_one(&request).expect("encode subtree removal request");
1178 let advance_bytes =
1179 candid::encode_one(advance_request).expect("encode subtree removal advance request");
1180 let stop_bytes =
1181 candid::encode_one(stop_request).expect("encode subtree removal stop request");
1182 let status_bytes =
1183 candid::encode_one(status_request).expect("encode subtree removal status request");
1184 let response_bytes =
1185 candid::encode_one(&response).expect("encode subtree removal response");
1186
1187 assert_eq!(
1188 candid::decode_one::<RootComponentSubtreeRemovalRequest>(&request_bytes)
1189 .expect("decode subtree removal request"),
1190 request
1191 );
1192 assert_eq!(
1193 candid::decode_one::<RootComponentSubtreeRemovalAdvanceRequest>(&advance_bytes)
1194 .expect("decode subtree removal advance request"),
1195 advance_request
1196 );
1197 assert_eq!(
1198 candid::decode_one::<RootComponentSubtreeRemovalStopPreparationRequest>(&stop_bytes)
1199 .expect("decode subtree removal stop request"),
1200 stop_request
1201 );
1202 assert_eq!(
1203 candid::decode_one::<RootComponentSubtreeRemovalStatusRequest>(&status_bytes)
1204 .expect("decode subtree removal status request"),
1205 status_request
1206 );
1207 assert_eq!(
1208 candid::decode_one::<RootComponentSubtreeRemovalResponse>(&response_bytes)
1209 .expect("decode subtree removal response"),
1210 response
1211 );
1212 }
1213
1214 #[test]
1215 #[expect(
1216 clippy::too_many_lines,
1217 reason = "one round-trip test keeps the complete child lifecycle boundary coherent"
1218 )]
1219 fn component_child_lifecycle_contracts_round_trip_through_candid() {
1220 let component = ComponentInstanceId::from_generated_bytes([11; 32]);
1221 let registry = ComponentRegistryHead {
1222 component,
1223 revision: 2,
1224 content_hash: [12; 32],
1225 };
1226 let request = RootComponentChildAllocationRequest {
1227 operation_id: [13; 32],
1228 component,
1229 expected_registry: registry.clone(),
1230 child_role: CanisterRole::new("project_instance"),
1231 };
1232 let status_request = RootComponentChildAllocationStatusRequest {
1233 operation_id: request.operation_id,
1234 component,
1235 };
1236 let creation_request = RootComponentChildCreationRequest {
1237 operation_id: request.operation_id,
1238 component,
1239 };
1240 let install_request = RootComponentChildInstallRequest {
1241 operation_id: request.operation_id,
1242 component,
1243 };
1244 let commit_request = RootComponentChildCommitRequest {
1245 operation_id: request.operation_id,
1246 component,
1247 };
1248 let directory_request = RootComponentChildDirectoryPreparationRequest {
1249 operation_id: request.operation_id,
1250 component,
1251 };
1252 let activation_request = RootComponentChildRuntimeActivationRequest {
1253 operation_id: request.operation_id,
1254 component,
1255 };
1256 let membership_request = RootComponentChildMembershipActivationRequest {
1257 operation_id: request.operation_id,
1258 component,
1259 };
1260 let root = Principal::from_slice(&[17; 29]);
1261 let parent = Principal::from_slice(&[14; 29]);
1262 let child = Principal::from_slice(&[18; 29]);
1263 let child_binding = ComponentChildBinding {
1264 component: ComponentBinding {
1265 authority: fleet_registry_authority(),
1266 component,
1267 component_spec: "projects".parse().expect("Component Spec"),
1268 spec_hash: [19; 32],
1269 role: CanisterRole::new("project_hub"),
1270 placement_subnet: SubnetId::from_principal(Principal::from_slice(&[20; 29])),
1271 fleet_subnet_root: root,
1272 canister_id: parent,
1273 },
1274 parent_canister_id: parent,
1275 role: request.child_role.clone(),
1276 canister_id: child,
1277 };
1278 let response = RootComponentChildAllocationResponse {
1279 operation_id: request.operation_id,
1280 component,
1281 parent_canister_id: parent,
1282 parent_role: CanisterRole::new("project_hub"),
1283 child_role: request.child_role.clone(),
1284 child_kind: ComponentChildKind::Instance,
1285 maximum_instances_per_parent: 10_000,
1286 maximum_descendants: 20_000,
1287 maximum_registry_bytes: 16_777_216,
1288 reserved_against_registry: registry,
1289 release_set: FleetSubnetRootReleaseSet {
1290 release_build_id: ReleaseBuildId::from_nonce(ReleaseBuildNonce::from_random_bytes(
1291 [15; 32],
1292 )),
1293 manifest_digest: ReleaseSetDigest::from_bytes([16; 32]),
1294 },
1295 phase: RootComponentAllocationPhase::Verified,
1296 creation: Some(RootComponentCreationEvidence {
1297 wasm_store: Principal::from_slice(&[21; 29]),
1298 payload_hash: [22; 32],
1299 payload_size_bytes: 4_096,
1300 initial_cycles: Cycles::new(5_000_000_000_000),
1301 controller: root,
1302 canister: Some(child),
1303 }),
1304 installation: Some(RootComponentChildInstallEvidence {
1305 raw_module_hash: [23; 32],
1306 chunk_hashes: vec![vec![24; 32]],
1307 binding: child_binding.clone(),
1308 }),
1309 };
1310 let commit_response = RootComponentChildCommitResponse {
1311 allocation: response.clone(),
1312 registry: ComponentRegistryPartitionResponse {
1313 head: ComponentRegistryHead {
1314 component,
1315 revision: 3,
1316 content_hash: [25; 32],
1317 },
1318 binding: child_binding.component.clone(),
1319 provisioning_origin: ComponentProvisioningOrigin::FleetAdministrator {
1320 caller: Principal::from_slice(&[26; 29]),
1321 },
1322 release_set: response.release_set,
1323 status: ComponentLifecycleStatus::Active,
1324 reserved_descendants: 0,
1325 committed_descendants: 1,
1326 encoded_bytes: 8_192,
1327 },
1328 directory: ComponentDirectoryHead {
1329 provenance: ComponentDirectoryProvenance {
1330 component: child_binding.component.clone(),
1331 source_fleet_subnet_root: root,
1332 component_registry_revision: 3,
1333 component_registry_content_hash: [25; 32],
1334 synchronized_at_ns: 27,
1335 },
1336 descendant_count: 1,
1337 },
1338 };
1339 let runtime_authority = ComponentRuntimeDirectoryAuthority {
1340 fleet: FleetDirectorySnapshot {
1341 provenance: crate::dto::fleet_registry::FleetDirectoryProvenance {
1342 registry: FleetRegistryVersion {
1343 authority: fleet_registry_authority(),
1344 revision: 4,
1345 content_hash: [28; 32],
1346 },
1347 source_fleet_subnet_root: root,
1348 },
1349 fleet_subnet_roots: vec![
1350 crate::dto::fleet_registry::FleetSubnetRootDirectoryEntry {
1351 placement_subnet: commit_response.registry.binding.placement_subnet,
1352 fleet_subnet_root: root,
1353 status: crate::dto::fleet_registry::FleetSubnetRootStatus::Active,
1354 },
1355 ],
1356 },
1357 component: commit_response.directory.clone(),
1358 };
1359 let activation = ComponentRuntimeActivationEvidence {
1360 directory_authority_hash: [29; 32],
1361 activated_at_ns: 30,
1362 };
1363 let directory_response = RootComponentChildDirectoryPreparationResponse {
1364 committed: commit_response.clone(),
1365 child: ComponentRuntimeStatusResponse {
1366 operation_id: request.operation_id,
1367 binding: ManagedCanisterBinding::ComponentChild(child_binding.clone()),
1368 phase: ComponentRuntimePhase::DirectoryPrepared,
1369 authority: Some(runtime_authority.clone()),
1370 authority_hash: Some([31; 32]),
1371 activation: None,
1372 },
1373 owning_component: ComponentRuntimeDirectoryConvergenceEvidence {
1374 operation_id: [32; 32],
1375 binding: ManagedCanisterBinding::Component(child_binding.component.clone()),
1376 covered_authority: runtime_authority.clone(),
1377 covered_authority_hash: [31; 32],
1378 activation,
1379 },
1380 parent: None,
1381 };
1382 let activation_response = RootComponentChildRuntimeActivationResponse {
1383 committed: commit_response.clone(),
1384 child: ComponentRuntimeStatusResponse {
1385 operation_id: request.operation_id,
1386 binding: ManagedCanisterBinding::ComponentChild(child_binding.clone()),
1387 phase: ComponentRuntimePhase::Active,
1388 authority: Some(runtime_authority.clone()),
1389 authority_hash: Some([31; 32]),
1390 activation: Some(ComponentRuntimeActivationEvidence {
1391 directory_authority_hash: [31; 32],
1392 activated_at_ns: 33,
1393 }),
1394 },
1395 };
1396 let active_directory = ComponentDirectoryHead {
1397 provenance: ComponentDirectoryProvenance {
1398 component: child_binding.component.clone(),
1399 source_fleet_subnet_root: root,
1400 component_registry_revision: 4,
1401 component_registry_content_hash: [34; 32],
1402 synchronized_at_ns: 35,
1403 },
1404 descendant_count: 1,
1405 };
1406 let active_authority = ComponentRuntimeDirectoryAuthority {
1407 fleet: runtime_authority.fleet,
1408 component: active_directory.clone(),
1409 };
1410 let membership_response = RootComponentChildMembershipActivationResponse {
1411 committed: commit_response.clone(),
1412 registry: ComponentRegistryPartitionResponse {
1413 head: ComponentRegistryHead {
1414 component,
1415 revision: 4,
1416 content_hash: [34; 32],
1417 },
1418 binding: child_binding.component.clone(),
1419 provisioning_origin: commit_response.registry.provisioning_origin.clone(),
1420 release_set: commit_response.registry.release_set,
1421 status: ComponentLifecycleStatus::Active,
1422 reserved_descendants: 0,
1423 committed_descendants: 1,
1424 encoded_bytes: 8_256,
1425 },
1426 directory: active_directory,
1427 child: ComponentRuntimeStatusResponse {
1428 operation_id: request.operation_id,
1429 binding: ManagedCanisterBinding::ComponentChild(child_binding),
1430 phase: ComponentRuntimePhase::Active,
1431 authority: Some(active_authority),
1432 authority_hash: Some([36; 32]),
1433 activation: Some(ComponentRuntimeActivationEvidence {
1434 directory_authority_hash: [31; 32],
1435 activated_at_ns: 33,
1436 }),
1437 },
1438 };
1439
1440 let request_bytes = candid::encode_one(&request).expect("encode child reservation");
1441 let status_bytes =
1442 candid::encode_one(status_request).expect("encode child reservation status");
1443 let creation_bytes =
1444 candid::encode_one(creation_request).expect("encode child creation request");
1445 let install_bytes =
1446 candid::encode_one(install_request).expect("encode child install request");
1447 let response_bytes = candid::encode_one(&response).expect("encode child response");
1448 let commit_request_bytes =
1449 candid::encode_one(commit_request).expect("encode child commit request");
1450 let directory_request_bytes =
1451 candid::encode_one(directory_request).expect("encode child Directory request");
1452 let activation_request_bytes =
1453 candid::encode_one(activation_request).expect("encode child activation request");
1454 let membership_request_bytes =
1455 candid::encode_one(membership_request).expect("encode child membership request");
1456 let commit_response_bytes =
1457 candid::encode_one(&commit_response).expect("encode child commit response");
1458 let directory_response_bytes =
1459 candid::encode_one(&directory_response).expect("encode child Directory response");
1460 let activation_response_bytes =
1461 candid::encode_one(&activation_response).expect("encode child activation response");
1462 let membership_response_bytes =
1463 candid::encode_one(&membership_response).expect("encode child membership response");
1464
1465 assert_eq!(
1466 candid::decode_one::<RootComponentChildAllocationRequest>(&request_bytes)
1467 .expect("decode child reservation"),
1468 request
1469 );
1470 assert_eq!(
1471 candid::decode_one::<RootComponentChildAllocationStatusRequest>(&status_bytes)
1472 .expect("decode child reservation status"),
1473 status_request
1474 );
1475 assert_eq!(
1476 candid::decode_one::<RootComponentChildCreationRequest>(&creation_bytes)
1477 .expect("decode child creation request"),
1478 creation_request
1479 );
1480 assert_eq!(
1481 candid::decode_one::<RootComponentChildInstallRequest>(&install_bytes)
1482 .expect("decode child install request"),
1483 install_request
1484 );
1485 assert_eq!(
1486 candid::decode_one::<RootComponentChildAllocationResponse>(&response_bytes)
1487 .expect("decode child response"),
1488 response
1489 );
1490 assert_eq!(
1491 candid::decode_one::<RootComponentChildCommitRequest>(&commit_request_bytes)
1492 .expect("decode child commit request"),
1493 commit_request
1494 );
1495 assert_eq!(
1496 candid::decode_one::<RootComponentChildDirectoryPreparationRequest>(
1497 &directory_request_bytes
1498 )
1499 .expect("decode child Directory request"),
1500 directory_request
1501 );
1502 assert_eq!(
1503 candid::decode_one::<RootComponentChildCommitResponse>(&commit_response_bytes)
1504 .expect("decode child commit response"),
1505 commit_response
1506 );
1507 assert_eq!(
1508 candid::decode_one::<RootComponentChildDirectoryPreparationResponse>(
1509 &directory_response_bytes
1510 )
1511 .expect("decode child Directory response"),
1512 directory_response
1513 );
1514 assert_eq!(
1515 candid::decode_one::<RootComponentChildRuntimeActivationRequest>(
1516 &activation_request_bytes
1517 )
1518 .expect("decode child activation request"),
1519 activation_request
1520 );
1521 assert_eq!(
1522 candid::decode_one::<RootComponentChildRuntimeActivationResponse>(
1523 &activation_response_bytes
1524 )
1525 .expect("decode child activation response"),
1526 activation_response
1527 );
1528 assert_eq!(
1529 candid::decode_one::<RootComponentChildMembershipActivationRequest>(
1530 &membership_request_bytes
1531 )
1532 .expect("decode child membership request"),
1533 membership_request
1534 );
1535 assert_eq!(
1536 candid::decode_one::<RootComponentChildMembershipActivationResponse>(
1537 &membership_response_bytes
1538 )
1539 .expect("decode child membership response"),
1540 membership_response
1541 );
1542 }
1543
1544 #[test]
1545 fn component_install_request_round_trips_through_candid() {
1546 let request = RootComponentInstallRequest {
1547 operation_id: [10; 32],
1548 };
1549 let bytes = candid::encode_one(request).expect("encode install request");
1550
1551 assert_eq!(
1552 candid::decode_one::<RootComponentInstallRequest>(&bytes)
1553 .expect("decode install request"),
1554 request
1555 );
1556 }
1557
1558 #[test]
1559 fn component_commit_request_round_trips_through_candid() {
1560 let request = RootComponentCommitRequest {
1561 operation_id: [10; 32],
1562 };
1563 let bytes = candid::encode_one(request).expect("encode commit request");
1564
1565 assert_eq!(
1566 candid::decode_one::<RootComponentCommitRequest>(&bytes)
1567 .expect("decode commit request"),
1568 request
1569 );
1570 }
1571
1572 #[test]
1573 fn component_runtime_activation_requests_round_trip_through_candid() {
1574 let root_request = RootComponentRuntimeActivationRequest {
1575 operation_id: [22; 32],
1576 };
1577 let target_request = ComponentRuntimeActivationRequest {
1578 operation_id: root_request.operation_id,
1579 directory_authority_hash: [23; 32],
1580 };
1581 let membership_request = RootComponentMembershipActivationRequest {
1582 operation_id: root_request.operation_id,
1583 };
1584 let root_bytes = candid::encode_one(root_request).expect("encode root activation request");
1585 let target_bytes =
1586 candid::encode_one(target_request).expect("encode target activation request");
1587 let membership_bytes =
1588 candid::encode_one(membership_request).expect("encode membership activation request");
1589
1590 assert_eq!(
1591 candid::decode_one::<RootComponentRuntimeActivationRequest>(&root_bytes)
1592 .expect("decode root activation request"),
1593 root_request
1594 );
1595 assert_eq!(
1596 candid::decode_one::<ComponentRuntimeActivationRequest>(&target_bytes)
1597 .expect("decode target activation request"),
1598 target_request
1599 );
1600 assert_eq!(
1601 candid::decode_one::<RootComponentMembershipActivationRequest>(&membership_bytes)
1602 .expect("decode membership activation request"),
1603 membership_request
1604 );
1605 }
1606}