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 RootComponentCreationRequest {
177 pub operation_id: [u8; 32],
178}
179
180#[derive(CandidType, Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
187pub struct RootComponentInstallRequest {
188 pub operation_id: [u8; 32],
189}
190
191#[derive(CandidType, Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
198pub struct RootComponentCommitRequest {
199 pub operation_id: [u8; 32],
200}
201
202#[derive(CandidType, Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
209pub struct RootComponentDirectoryPreparationRequest {
210 pub operation_id: [u8; 32],
211}
212
213#[derive(CandidType, Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
220pub struct RootComponentRuntimeActivationRequest {
221 pub operation_id: [u8; 32],
222}
223
224#[derive(CandidType, Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
231pub struct RootComponentMembershipActivationRequest {
232 pub operation_id: [u8; 32],
233}
234
235#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
242pub enum ComponentProvisioningOrigin {
243 FleetAdministrator { caller: Principal },
244}
245
246#[derive(CandidType, Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
253pub enum RootComponentAllocationPhase {
254 Reserved,
255 CreationIntent,
256 Created,
257 InstallIntent,
258 Installed,
259 Verified,
260 Committed,
261}
262
263#[derive(CandidType, Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
270pub enum ComponentLifecycleStatus {
271 Prepared,
272 Active,
273 Draining,
274 Removed,
275}
276
277#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
284pub struct ComponentRegistryHead {
285 pub component: ComponentInstanceId,
286 pub revision: u64,
287 pub content_hash: [u8; 32],
288}
289
290#[derive(CandidType, Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
297pub struct ComponentRegistryPartitionRequest {
298 pub component: ComponentInstanceId,
299}
300
301#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
308pub struct ComponentRegistryPartitionResponse {
309 pub head: ComponentRegistryHead,
310 pub binding: ComponentBinding,
311 pub provisioning_origin: ComponentProvisioningOrigin,
312 pub release_set: FleetSubnetRootReleaseSet,
313 pub status: ComponentLifecycleStatus,
314 pub reserved_descendants: u32,
315 pub committed_descendants: u32,
316 pub encoded_bytes: u64,
317}
318
319#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
326pub struct ComponentDirectoryProvenance {
327 pub component: ComponentBinding,
328 pub source_fleet_subnet_root: Principal,
329 pub component_registry_revision: u64,
330 pub component_registry_content_hash: [u8; 32],
331 pub synchronized_at_ns: u64,
332}
333
334#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
341pub struct ComponentDirectoryHead {
342 pub provenance: ComponentDirectoryProvenance,
343 pub descendant_count: u32,
344}
345
346#[derive(CandidType, Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
353pub struct ComponentDirectoryHeadRequest {
354 pub component: ComponentInstanceId,
355}
356
357#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
364pub struct ComponentRuntimeDirectoryAuthority {
365 pub fleet: FleetDirectorySnapshot,
366 pub component: ComponentDirectoryHead,
367}
368
369#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
376pub struct ComponentRuntimeDirectoryPreparationRequest {
377 pub operation_id: [u8; 32],
378 pub authority: ComponentRuntimeDirectoryAuthority,
379}
380
381#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
388pub struct ComponentRuntimeDirectorySynchronizationRequest {
389 pub operation_id: [u8; 32],
390 pub authority: ComponentRuntimeDirectoryAuthority,
391}
392
393#[derive(CandidType, Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
400pub enum ComponentRuntimePhase {
401 AwaitingDirectory,
402 DirectoryPrepared,
403 Active,
404}
405
406#[derive(CandidType, Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
413pub struct ComponentRuntimeActivationEvidence {
414 pub directory_authority_hash: [u8; 32],
415 pub activated_at_ns: u64,
416}
417
418#[derive(CandidType, Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
425pub struct ComponentRuntimeActivationRequest {
426 pub operation_id: [u8; 32],
427 pub directory_authority_hash: [u8; 32],
428}
429
430#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
437pub struct ComponentRuntimeStatusResponse {
438 pub operation_id: [u8; 32],
439 pub binding: ManagedCanisterBinding,
440 pub phase: ComponentRuntimePhase,
441 pub authority: Option<ComponentRuntimeDirectoryAuthority>,
442 pub authority_hash: Option<[u8; 32]>,
443 pub activation: Option<ComponentRuntimeActivationEvidence>,
444}
445
446#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
453pub struct ComponentRuntimeDirectoryConvergenceEvidence {
454 pub operation_id: [u8; 32],
455 pub binding: ManagedCanisterBinding,
456 pub covered_authority: ComponentRuntimeDirectoryAuthority,
457 pub covered_authority_hash: [u8; 32],
458 pub activation: ComponentRuntimeActivationEvidence,
459}
460
461#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
468pub struct RootComponentCreationEvidence {
469 pub wasm_store: Principal,
470 pub payload_hash: [u8; 32],
471 pub payload_size_bytes: u64,
472 pub initial_cycles: Cycles,
473 pub controller: Principal,
474 pub canister: Option<Principal>,
475}
476
477#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
484pub struct RootComponentInstallEvidence {
485 pub raw_module_hash: [u8; 32],
486 pub chunk_hashes: Vec<Vec<u8>>,
487 pub binding: ComponentBinding,
488}
489
490#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
497pub struct RootComponentChildInstallEvidence {
498 pub raw_module_hash: [u8; 32],
499 pub chunk_hashes: Vec<Vec<u8>>,
500 pub binding: ComponentChildBinding,
501}
502
503#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
510pub struct RootComponentAllocationResponse {
511 pub operation_id: [u8; 32],
512 pub allocation_sequence: u64,
513 pub component: ComponentInstanceId,
514 pub component_spec: ComponentSpecId,
515 pub spec_hash: [u8; 32],
516 pub role: CanisterRole,
517 pub provisioning_origin: ComponentProvisioningOrigin,
518 pub release_set: FleetSubnetRootReleaseSet,
519 pub phase: RootComponentAllocationPhase,
520 pub creation: Option<RootComponentCreationEvidence>,
521 pub installation: Option<RootComponentInstallEvidence>,
522}
523
524#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
531pub struct RootComponentChildAllocationResponse {
532 pub operation_id: [u8; 32],
533 pub component: ComponentInstanceId,
534 pub parent_canister_id: Principal,
535 pub parent_role: CanisterRole,
536 pub child_role: CanisterRole,
537 pub child_kind: ComponentChildKind,
538 pub maximum_instances_per_parent: u32,
539 pub maximum_descendants: u32,
540 pub maximum_registry_bytes: u64,
541 pub reserved_against_registry: ComponentRegistryHead,
542 pub release_set: FleetSubnetRootReleaseSet,
543 pub phase: RootComponentAllocationPhase,
544 pub creation: Option<RootComponentCreationEvidence>,
545 pub installation: Option<RootComponentChildInstallEvidence>,
546}
547
548#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
555pub struct RootComponentChildCommitResponse {
556 pub allocation: RootComponentChildAllocationResponse,
557 pub registry: ComponentRegistryPartitionResponse,
558 pub directory: ComponentDirectoryHead,
559}
560
561#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
568pub struct RootComponentChildDirectoryPreparationResponse {
569 pub committed: RootComponentChildCommitResponse,
570 pub child: ComponentRuntimeStatusResponse,
571 pub owning_component: ComponentRuntimeDirectoryConvergenceEvidence,
572 pub parent: Option<ComponentRuntimeDirectoryConvergenceEvidence>,
573}
574
575#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
582pub struct RootComponentCommitResponse {
583 pub allocation: RootComponentAllocationResponse,
584 pub registry: ComponentRegistryPartitionResponse,
585 pub directory: ComponentDirectoryHead,
586}
587
588#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
595pub struct RootComponentDirectoryPreparationResponse {
596 pub committed: RootComponentCommitResponse,
597 pub target: ComponentRuntimeStatusResponse,
598}
599
600#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
607pub struct RootComponentRuntimeActivationResponse {
608 pub committed: RootComponentCommitResponse,
609 pub target: ComponentRuntimeStatusResponse,
610}
611
612#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
619pub struct RootComponentMembershipActivationResponse {
620 pub allocation: RootComponentAllocationResponse,
621 pub registry: ComponentRegistryPartitionResponse,
622 pub directory: ComponentDirectoryHead,
623 pub target: ComponentRuntimeStatusResponse,
624}
625
626#[cfg(test)]
627mod tests {
628 use super::*;
629 use crate::{
630 dto::root_store::RootStoreBootstrapRequest,
631 ids::{
632 AppId, CanonicalNetworkId, FleetCoordinatorBinding, FleetId, FleetKey,
633 FleetRegistryAuthority, ReleaseBuildId, ReleaseBuildNonce, ReleaseSetDigest, SubnetId,
634 },
635 };
636
637 #[test]
638 fn component_registry_contracts_round_trip_through_candid() {
639 let request = RootComponentRegistryPreparationRequest {
640 store_bootstrap: RootStoreBootstrapRequest {
641 manifest_payload_size_bytes: 128,
642 },
643 expected_fleet_registry: FleetRegistryVersion {
644 authority: fleet_registry_authority(),
645 revision: 4,
646 content_hash: [5; 32],
647 },
648 };
649 let response = RootComponentRegistryStatusResponse {
650 fleet_subnet_root: Principal::from_slice(&[6; 29]),
651 prepared_against_registry: request.expected_fleet_registry.clone(),
652 release_set: FleetSubnetRootReleaseSet {
653 release_build_id: ReleaseBuildId::from_nonce(ReleaseBuildNonce::from_random_bytes(
654 [7; 32],
655 )),
656 manifest_digest: ReleaseSetDigest::from_bytes([8; 32]),
657 },
658 component_topology_digest: ComponentTopologyDigest::from_bytes([9; 32]),
659 next_allocation_sequence: 1,
660 reserved_component_instances: 0,
661 committed_component_instances: 0,
662 managed_descendants: 0,
663 known_created_component_canisters: 0,
664 encoded_bytes: 0,
665 initial_inventory: Some(RootComponentInitialInventoryStatus {
666 fleet_activation_operation_id: [10; 32],
667 component_count: 0,
668 inventory_hash: [11; 32],
669 sealed_at_ns: 12,
670 directories_converged: true,
671 root_runtime_activated: true,
672 }),
673 };
674 let allocation = RootComponentAllocationResponse {
675 operation_id: [10; 32],
676 allocation_sequence: 1,
677 component: ComponentInstanceId::from_generated_bytes([11; 32]),
678 component_spec: "projects".parse().expect("Component Spec ID"),
679 spec_hash: [12; 32],
680 role: CanisterRole::new("project_hub"),
681 provisioning_origin: ComponentProvisioningOrigin::FleetAdministrator {
682 caller: Principal::from_slice(&[13; 29]),
683 },
684 release_set: response.release_set,
685 phase: RootComponentAllocationPhase::Reserved,
686 creation: None,
687 installation: None,
688 };
689 let created = RootComponentAllocationResponse {
690 phase: RootComponentAllocationPhase::Created,
691 creation: Some(RootComponentCreationEvidence {
692 wasm_store: Principal::from_slice(&[14; 29]),
693 payload_hash: [15; 32],
694 payload_size_bytes: 4_096,
695 initial_cycles: Cycles::new(5_000_000_000_000),
696 controller: Principal::from_slice(&[6; 29]),
697 canister: Some(Principal::from_slice(&[16; 29])),
698 }),
699 installation: None,
700 ..allocation.clone()
701 };
702 let request_bytes = candid::encode_one(&request).expect("encode request");
703 let response_bytes = candid::encode_one(&response).expect("encode response");
704 let allocation_bytes = candid::encode_one(&allocation).expect("encode allocation");
705 let created_bytes = candid::encode_one(&created).expect("encode created allocation");
706
707 assert_eq!(
708 candid::decode_one::<RootComponentRegistryPreparationRequest>(&request_bytes)
709 .expect("decode request"),
710 request
711 );
712 assert_eq!(
713 candid::decode_one::<RootComponentRegistryStatusResponse>(&response_bytes)
714 .expect("decode response"),
715 response
716 );
717 assert_eq!(
718 candid::decode_one::<RootComponentAllocationResponse>(&allocation_bytes)
719 .expect("decode allocation"),
720 allocation
721 );
722 assert_eq!(
723 candid::decode_one::<RootComponentAllocationResponse>(&created_bytes)
724 .expect("decode created allocation"),
725 created
726 );
727 }
728
729 #[test]
730 fn component_commit_response_round_trips_through_candid() {
731 let root = Principal::from_slice(&[6; 29]);
732 let component = ComponentInstanceId::from_generated_bytes([11; 32]);
733 let component_spec: ComponentSpecId = "projects".parse().expect("Component Spec ID");
734 let release_set = FleetSubnetRootReleaseSet {
735 release_build_id: ReleaseBuildId::from_nonce(ReleaseBuildNonce::from_random_bytes(
736 [7; 32],
737 )),
738 manifest_digest: ReleaseSetDigest::from_bytes([8; 32]),
739 };
740 let provisioning_origin = ComponentProvisioningOrigin::FleetAdministrator {
741 caller: Principal::from_slice(&[13; 29]),
742 };
743 let binding = ComponentBinding {
744 authority: fleet_registry_authority(),
745 component,
746 component_spec: component_spec.clone(),
747 spec_hash: [12; 32],
748 role: CanisterRole::new("project_hub"),
749 placement_subnet: SubnetId::from_principal(Principal::from_slice(&[17; 29])),
750 fleet_subnet_root: root,
751 canister_id: Principal::from_slice(&[16; 29]),
752 };
753 let head = ComponentRegistryHead {
754 component,
755 revision: 1,
756 content_hash: [18; 32],
757 };
758 let committed = RootComponentCommitResponse {
759 allocation: RootComponentAllocationResponse {
760 operation_id: [10; 32],
761 allocation_sequence: 1,
762 component,
763 component_spec,
764 spec_hash: binding.spec_hash,
765 role: binding.role.clone(),
766 provisioning_origin: provisioning_origin.clone(),
767 release_set,
768 phase: RootComponentAllocationPhase::Committed,
769 creation: Some(RootComponentCreationEvidence {
770 wasm_store: Principal::from_slice(&[14; 29]),
771 payload_hash: [15; 32],
772 payload_size_bytes: 4_096,
773 initial_cycles: Cycles::new(5_000_000_000_000),
774 controller: root,
775 canister: Some(binding.canister_id),
776 }),
777 installation: Some(RootComponentInstallEvidence {
778 raw_module_hash: [20; 32],
779 chunk_hashes: vec![vec![21; 32]],
780 binding: binding.clone(),
781 }),
782 },
783 registry: ComponentRegistryPartitionResponse {
784 head: head.clone(),
785 binding: binding.clone(),
786 provisioning_origin,
787 release_set,
788 status: ComponentLifecycleStatus::Prepared,
789 reserved_descendants: 0,
790 committed_descendants: 0,
791 encoded_bytes: 2_048,
792 },
793 directory: ComponentDirectoryHead {
794 provenance: ComponentDirectoryProvenance {
795 component: binding,
796 source_fleet_subnet_root: root,
797 component_registry_revision: head.revision,
798 component_registry_content_hash: head.content_hash,
799 synchronized_at_ns: 19,
800 },
801 descendant_count: 0,
802 },
803 };
804 let committed_bytes = candid::encode_one(&committed).expect("encode committed allocation");
805
806 assert_eq!(
807 candid::decode_one::<RootComponentCommitResponse>(&committed_bytes)
808 .expect("decode committed allocation"),
809 committed
810 );
811 }
812
813 fn fleet_registry_authority() -> FleetRegistryAuthority {
814 FleetRegistryAuthority {
815 binding: FleetCoordinatorBinding {
816 fleet: crate::ids::FleetBinding {
817 fleet: FleetKey {
818 canonical_network_id: CanonicalNetworkId::public_ic(),
819 fleet_id: FleetId::from_generated_bytes([1; 32]),
820 },
821 app: AppId::from("toko"),
822 },
823 coordinator_subnet: SubnetId::from_principal(Principal::from_slice(&[2; 29])),
824 coordinator: Principal::from_slice(&[3; 29]),
825 },
826 epoch: 1,
827 }
828 }
829
830 #[test]
831 fn component_creation_request_round_trips_through_candid() {
832 let request = RootComponentCreationRequest {
833 operation_id: [10; 32],
834 };
835 let bytes = candid::encode_one(request).expect("encode creation request");
836
837 assert_eq!(
838 candid::decode_one::<RootComponentCreationRequest>(&bytes)
839 .expect("decode creation request"),
840 request
841 );
842 }
843
844 #[test]
845 #[expect(
846 clippy::too_many_lines,
847 reason = "one round-trip test keeps the complete child lifecycle boundary coherent"
848 )]
849 fn component_child_lifecycle_contracts_round_trip_through_candid() {
850 let component = ComponentInstanceId::from_generated_bytes([11; 32]);
851 let registry = ComponentRegistryHead {
852 component,
853 revision: 2,
854 content_hash: [12; 32],
855 };
856 let request = RootComponentChildAllocationRequest {
857 operation_id: [13; 32],
858 component,
859 expected_registry: registry.clone(),
860 child_role: CanisterRole::new("project_instance"),
861 };
862 let status_request = RootComponentChildAllocationStatusRequest {
863 operation_id: request.operation_id,
864 component,
865 };
866 let creation_request = RootComponentChildCreationRequest {
867 operation_id: request.operation_id,
868 component,
869 };
870 let install_request = RootComponentChildInstallRequest {
871 operation_id: request.operation_id,
872 component,
873 };
874 let commit_request = RootComponentChildCommitRequest {
875 operation_id: request.operation_id,
876 component,
877 };
878 let directory_request = RootComponentChildDirectoryPreparationRequest {
879 operation_id: request.operation_id,
880 component,
881 };
882 let root = Principal::from_slice(&[17; 29]);
883 let parent = Principal::from_slice(&[14; 29]);
884 let child = Principal::from_slice(&[18; 29]);
885 let child_binding = ComponentChildBinding {
886 component: ComponentBinding {
887 authority: fleet_registry_authority(),
888 component,
889 component_spec: "projects".parse().expect("Component Spec"),
890 spec_hash: [19; 32],
891 role: CanisterRole::new("project_hub"),
892 placement_subnet: SubnetId::from_principal(Principal::from_slice(&[20; 29])),
893 fleet_subnet_root: root,
894 canister_id: parent,
895 },
896 parent_canister_id: parent,
897 role: request.child_role.clone(),
898 canister_id: child,
899 };
900 let response = RootComponentChildAllocationResponse {
901 operation_id: request.operation_id,
902 component,
903 parent_canister_id: parent,
904 parent_role: CanisterRole::new("project_hub"),
905 child_role: request.child_role.clone(),
906 child_kind: ComponentChildKind::Instance,
907 maximum_instances_per_parent: 10_000,
908 maximum_descendants: 20_000,
909 maximum_registry_bytes: 16_777_216,
910 reserved_against_registry: registry,
911 release_set: FleetSubnetRootReleaseSet {
912 release_build_id: ReleaseBuildId::from_nonce(ReleaseBuildNonce::from_random_bytes(
913 [15; 32],
914 )),
915 manifest_digest: ReleaseSetDigest::from_bytes([16; 32]),
916 },
917 phase: RootComponentAllocationPhase::Verified,
918 creation: Some(RootComponentCreationEvidence {
919 wasm_store: Principal::from_slice(&[21; 29]),
920 payload_hash: [22; 32],
921 payload_size_bytes: 4_096,
922 initial_cycles: Cycles::new(5_000_000_000_000),
923 controller: root,
924 canister: Some(child),
925 }),
926 installation: Some(RootComponentChildInstallEvidence {
927 raw_module_hash: [23; 32],
928 chunk_hashes: vec![vec![24; 32]],
929 binding: child_binding.clone(),
930 }),
931 };
932 let commit_response = RootComponentChildCommitResponse {
933 allocation: response.clone(),
934 registry: ComponentRegistryPartitionResponse {
935 head: ComponentRegistryHead {
936 component,
937 revision: 3,
938 content_hash: [25; 32],
939 },
940 binding: child_binding.component.clone(),
941 provisioning_origin: ComponentProvisioningOrigin::FleetAdministrator {
942 caller: Principal::from_slice(&[26; 29]),
943 },
944 release_set: response.release_set,
945 status: ComponentLifecycleStatus::Active,
946 reserved_descendants: 0,
947 committed_descendants: 1,
948 encoded_bytes: 8_192,
949 },
950 directory: ComponentDirectoryHead {
951 provenance: ComponentDirectoryProvenance {
952 component: child_binding.component.clone(),
953 source_fleet_subnet_root: root,
954 component_registry_revision: 3,
955 component_registry_content_hash: [25; 32],
956 synchronized_at_ns: 27,
957 },
958 descendant_count: 1,
959 },
960 };
961 let runtime_authority = ComponentRuntimeDirectoryAuthority {
962 fleet: FleetDirectorySnapshot {
963 provenance: crate::dto::fleet_registry::FleetDirectoryProvenance {
964 registry: FleetRegistryVersion {
965 authority: fleet_registry_authority(),
966 revision: 4,
967 content_hash: [28; 32],
968 },
969 source_fleet_subnet_root: root,
970 },
971 fleet_subnet_roots: vec![
972 crate::dto::fleet_registry::FleetSubnetRootDirectoryEntry {
973 placement_subnet: commit_response.registry.binding.placement_subnet,
974 fleet_subnet_root: root,
975 status: crate::dto::fleet_registry::FleetSubnetRootStatus::Active,
976 },
977 ],
978 },
979 component: commit_response.directory.clone(),
980 };
981 let activation = ComponentRuntimeActivationEvidence {
982 directory_authority_hash: [29; 32],
983 activated_at_ns: 30,
984 };
985 let directory_response = RootComponentChildDirectoryPreparationResponse {
986 committed: commit_response.clone(),
987 child: ComponentRuntimeStatusResponse {
988 operation_id: request.operation_id,
989 binding: ManagedCanisterBinding::ComponentChild(child_binding.clone()),
990 phase: ComponentRuntimePhase::DirectoryPrepared,
991 authority: Some(runtime_authority.clone()),
992 authority_hash: Some([31; 32]),
993 activation: None,
994 },
995 owning_component: ComponentRuntimeDirectoryConvergenceEvidence {
996 operation_id: [32; 32],
997 binding: ManagedCanisterBinding::Component(child_binding.component),
998 covered_authority: runtime_authority,
999 covered_authority_hash: [31; 32],
1000 activation,
1001 },
1002 parent: None,
1003 };
1004
1005 let request_bytes = candid::encode_one(&request).expect("encode child reservation");
1006 let status_bytes =
1007 candid::encode_one(status_request).expect("encode child reservation status");
1008 let creation_bytes =
1009 candid::encode_one(creation_request).expect("encode child creation request");
1010 let install_bytes =
1011 candid::encode_one(install_request).expect("encode child install request");
1012 let response_bytes = candid::encode_one(&response).expect("encode child response");
1013 let commit_request_bytes =
1014 candid::encode_one(commit_request).expect("encode child commit request");
1015 let directory_request_bytes =
1016 candid::encode_one(directory_request).expect("encode child Directory request");
1017 let commit_response_bytes =
1018 candid::encode_one(&commit_response).expect("encode child commit response");
1019 let directory_response_bytes =
1020 candid::encode_one(&directory_response).expect("encode child Directory response");
1021
1022 assert_eq!(
1023 candid::decode_one::<RootComponentChildAllocationRequest>(&request_bytes)
1024 .expect("decode child reservation"),
1025 request
1026 );
1027 assert_eq!(
1028 candid::decode_one::<RootComponentChildAllocationStatusRequest>(&status_bytes)
1029 .expect("decode child reservation status"),
1030 status_request
1031 );
1032 assert_eq!(
1033 candid::decode_one::<RootComponentChildCreationRequest>(&creation_bytes)
1034 .expect("decode child creation request"),
1035 creation_request
1036 );
1037 assert_eq!(
1038 candid::decode_one::<RootComponentChildInstallRequest>(&install_bytes)
1039 .expect("decode child install request"),
1040 install_request
1041 );
1042 assert_eq!(
1043 candid::decode_one::<RootComponentChildAllocationResponse>(&response_bytes)
1044 .expect("decode child response"),
1045 response
1046 );
1047 assert_eq!(
1048 candid::decode_one::<RootComponentChildCommitRequest>(&commit_request_bytes)
1049 .expect("decode child commit request"),
1050 commit_request
1051 );
1052 assert_eq!(
1053 candid::decode_one::<RootComponentChildDirectoryPreparationRequest>(
1054 &directory_request_bytes
1055 )
1056 .expect("decode child Directory request"),
1057 directory_request
1058 );
1059 assert_eq!(
1060 candid::decode_one::<RootComponentChildCommitResponse>(&commit_response_bytes)
1061 .expect("decode child commit response"),
1062 commit_response
1063 );
1064 assert_eq!(
1065 candid::decode_one::<RootComponentChildDirectoryPreparationResponse>(
1066 &directory_response_bytes
1067 )
1068 .expect("decode child Directory response"),
1069 directory_response
1070 );
1071 }
1072
1073 #[test]
1074 fn component_install_request_round_trips_through_candid() {
1075 let request = RootComponentInstallRequest {
1076 operation_id: [10; 32],
1077 };
1078 let bytes = candid::encode_one(request).expect("encode install request");
1079
1080 assert_eq!(
1081 candid::decode_one::<RootComponentInstallRequest>(&bytes)
1082 .expect("decode install request"),
1083 request
1084 );
1085 }
1086
1087 #[test]
1088 fn component_commit_request_round_trips_through_candid() {
1089 let request = RootComponentCommitRequest {
1090 operation_id: [10; 32],
1091 };
1092 let bytes = candid::encode_one(request).expect("encode commit request");
1093
1094 assert_eq!(
1095 candid::decode_one::<RootComponentCommitRequest>(&bytes)
1096 .expect("decode commit request"),
1097 request
1098 );
1099 }
1100
1101 #[test]
1102 fn component_runtime_activation_requests_round_trip_through_candid() {
1103 let root_request = RootComponentRuntimeActivationRequest {
1104 operation_id: [22; 32],
1105 };
1106 let target_request = ComponentRuntimeActivationRequest {
1107 operation_id: root_request.operation_id,
1108 directory_authority_hash: [23; 32],
1109 };
1110 let membership_request = RootComponentMembershipActivationRequest {
1111 operation_id: root_request.operation_id,
1112 };
1113 let root_bytes = candid::encode_one(root_request).expect("encode root activation request");
1114 let target_bytes =
1115 candid::encode_one(target_request).expect("encode target activation request");
1116 let membership_bytes =
1117 candid::encode_one(membership_request).expect("encode membership activation request");
1118
1119 assert_eq!(
1120 candid::decode_one::<RootComponentRuntimeActivationRequest>(&root_bytes)
1121 .expect("decode root activation request"),
1122 root_request
1123 );
1124 assert_eq!(
1125 candid::decode_one::<ComponentRuntimeActivationRequest>(&target_bytes)
1126 .expect("decode target activation request"),
1127 target_request
1128 );
1129 assert_eq!(
1130 candid::decode_one::<RootComponentMembershipActivationRequest>(&membership_bytes)
1131 .expect("decode membership activation request"),
1132 membership_request
1133 );
1134 }
1135}