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