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, Debug, Deserialize, Eq, PartialEq, Serialize)]
40pub struct RootComponentRegistryStatusResponse {
41 pub fleet_subnet_root: Principal,
42 pub prepared_against_registry: FleetRegistryVersion,
43 pub release_set: FleetSubnetRootReleaseSet,
44 pub component_topology_digest: ComponentTopologyDigest,
45 pub next_allocation_sequence: u64,
46 pub reserved_component_instances: u32,
47 pub committed_component_instances: u32,
48 pub managed_descendants: u32,
49 pub encoded_bytes: u64,
50}
51
52#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
59pub struct RootComponentAllocationRequest {
60 pub operation_id: [u8; 32],
61 pub component_spec: ComponentSpecId,
62}
63
64#[derive(CandidType, Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
71pub struct RootComponentAllocationStatusRequest {
72 pub operation_id: [u8; 32],
73}
74
75#[derive(CandidType, Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
82pub struct RootComponentCreationRequest {
83 pub operation_id: [u8; 32],
84}
85
86#[derive(CandidType, Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
93pub struct RootComponentInstallRequest {
94 pub operation_id: [u8; 32],
95}
96
97#[derive(CandidType, Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
104pub struct RootComponentCommitRequest {
105 pub operation_id: [u8; 32],
106}
107
108#[derive(CandidType, Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
115pub struct RootComponentDirectoryPreparationRequest {
116 pub operation_id: [u8; 32],
117}
118
119#[derive(CandidType, Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
126pub struct RootComponentRuntimeActivationRequest {
127 pub operation_id: [u8; 32],
128}
129
130#[derive(CandidType, Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
137pub struct RootComponentMembershipActivationRequest {
138 pub operation_id: [u8; 32],
139}
140
141#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
148pub enum ComponentProvisioningOrigin {
149 FleetAdministrator { caller: Principal },
150}
151
152#[derive(CandidType, Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
159pub enum RootComponentAllocationPhase {
160 Reserved,
161 CreationIntent,
162 Created,
163 InstallIntent,
164 Installed,
165 Verified,
166 Committed,
167}
168
169#[derive(CandidType, Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
176pub enum ComponentLifecycleStatus {
177 Prepared,
178 Active,
179 Draining,
180 Removed,
181}
182
183#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
190pub struct ComponentRegistryHead {
191 pub component: ComponentInstanceId,
192 pub revision: u64,
193 pub content_hash: [u8; 32],
194}
195
196#[derive(CandidType, Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
203pub struct ComponentRegistryPartitionRequest {
204 pub component: ComponentInstanceId,
205}
206
207#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
214pub struct ComponentRegistryPartitionResponse {
215 pub head: ComponentRegistryHead,
216 pub binding: ComponentBinding,
217 pub provisioning_origin: ComponentProvisioningOrigin,
218 pub release_set: FleetSubnetRootReleaseSet,
219 pub status: ComponentLifecycleStatus,
220 pub encoded_bytes: u64,
221}
222
223#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
230pub struct ComponentDirectoryProvenance {
231 pub component: ComponentBinding,
232 pub source_fleet_subnet_root: Principal,
233 pub component_registry_revision: u64,
234 pub component_registry_content_hash: [u8; 32],
235 pub synchronized_at_ns: u64,
236}
237
238#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
245pub struct ComponentDirectoryHead {
246 pub provenance: ComponentDirectoryProvenance,
247 pub descendant_count: u32,
248}
249
250#[derive(CandidType, Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
257pub struct ComponentDirectoryHeadRequest {
258 pub component: ComponentInstanceId,
259}
260
261#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
268pub struct ComponentRuntimeDirectoryAuthority {
269 pub fleet: FleetDirectorySnapshot,
270 pub component: ComponentDirectoryHead,
271}
272
273#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
280pub struct ComponentRuntimeDirectoryPreparationRequest {
281 pub operation_id: [u8; 32],
282 pub authority: ComponentRuntimeDirectoryAuthority,
283}
284
285#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
292pub struct ComponentRuntimeDirectorySynchronizationRequest {
293 pub operation_id: [u8; 32],
294 pub authority: ComponentRuntimeDirectoryAuthority,
295}
296
297#[derive(CandidType, Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
304pub enum ComponentRuntimePhase {
305 AwaitingDirectory,
306 DirectoryPrepared,
307 Active,
308}
309
310#[derive(CandidType, Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
317pub struct ComponentRuntimeActivationEvidence {
318 pub directory_authority_hash: [u8; 32],
319 pub activated_at_ns: u64,
320}
321
322#[derive(CandidType, Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
329pub struct ComponentRuntimeActivationRequest {
330 pub operation_id: [u8; 32],
331 pub directory_authority_hash: [u8; 32],
332}
333
334#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
341pub struct ComponentRuntimeStatusResponse {
342 pub operation_id: [u8; 32],
343 pub binding: ManagedCanisterBinding,
344 pub phase: ComponentRuntimePhase,
345 pub authority: Option<ComponentRuntimeDirectoryAuthority>,
346 pub authority_hash: Option<[u8; 32]>,
347 pub activation: Option<ComponentRuntimeActivationEvidence>,
348}
349
350#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
357pub struct RootComponentCreationEvidence {
358 pub wasm_store: Principal,
359 pub payload_hash: [u8; 32],
360 pub payload_size_bytes: u64,
361 pub initial_cycles: Cycles,
362 pub controller: Principal,
363 pub canister: Option<Principal>,
364}
365
366#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
373pub struct RootComponentInstallEvidence {
374 pub raw_module_hash: [u8; 32],
375 pub chunk_hashes: Vec<Vec<u8>>,
376 pub binding: ComponentBinding,
377}
378
379#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
386pub struct RootComponentAllocationResponse {
387 pub operation_id: [u8; 32],
388 pub allocation_sequence: u64,
389 pub component: ComponentInstanceId,
390 pub component_spec: ComponentSpecId,
391 pub spec_hash: [u8; 32],
392 pub role: CanisterRole,
393 pub provisioning_origin: ComponentProvisioningOrigin,
394 pub release_set: FleetSubnetRootReleaseSet,
395 pub phase: RootComponentAllocationPhase,
396 pub creation: Option<RootComponentCreationEvidence>,
397 pub installation: Option<RootComponentInstallEvidence>,
398}
399
400#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
407pub struct RootComponentCommitResponse {
408 pub allocation: RootComponentAllocationResponse,
409 pub registry: ComponentRegistryPartitionResponse,
410 pub directory: ComponentDirectoryHead,
411}
412
413#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
420pub struct RootComponentDirectoryPreparationResponse {
421 pub committed: RootComponentCommitResponse,
422 pub target: ComponentRuntimeStatusResponse,
423}
424
425#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
432pub struct RootComponentRuntimeActivationResponse {
433 pub committed: RootComponentCommitResponse,
434 pub target: ComponentRuntimeStatusResponse,
435}
436
437#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
444pub struct RootComponentMembershipActivationResponse {
445 pub allocation: RootComponentAllocationResponse,
446 pub registry: ComponentRegistryPartitionResponse,
447 pub directory: ComponentDirectoryHead,
448 pub target: ComponentRuntimeStatusResponse,
449}
450
451#[cfg(test)]
452mod tests {
453 use super::*;
454 use crate::{
455 dto::root_store::RootStoreBootstrapRequest,
456 ids::{
457 AppId, CanonicalNetworkId, FleetCoordinatorBinding, FleetId, FleetKey,
458 FleetRegistryAuthority, ReleaseBuildId, ReleaseBuildNonce, ReleaseSetDigest, SubnetId,
459 },
460 };
461
462 #[test]
463 fn component_registry_contracts_round_trip_through_candid() {
464 let request = RootComponentRegistryPreparationRequest {
465 store_bootstrap: RootStoreBootstrapRequest {
466 manifest_payload_size_bytes: 128,
467 },
468 expected_fleet_registry: FleetRegistryVersion {
469 authority: fleet_registry_authority(),
470 revision: 4,
471 content_hash: [5; 32],
472 },
473 };
474 let response = RootComponentRegistryStatusResponse {
475 fleet_subnet_root: Principal::from_slice(&[6; 29]),
476 prepared_against_registry: request.expected_fleet_registry.clone(),
477 release_set: FleetSubnetRootReleaseSet {
478 release_build_id: ReleaseBuildId::from_nonce(ReleaseBuildNonce::from_random_bytes(
479 [7; 32],
480 )),
481 manifest_digest: ReleaseSetDigest::from_bytes([8; 32]),
482 },
483 component_topology_digest: ComponentTopologyDigest::from_bytes([9; 32]),
484 next_allocation_sequence: 1,
485 reserved_component_instances: 0,
486 committed_component_instances: 0,
487 managed_descendants: 0,
488 encoded_bytes: 0,
489 };
490 let allocation = RootComponentAllocationResponse {
491 operation_id: [10; 32],
492 allocation_sequence: 1,
493 component: ComponentInstanceId::from_generated_bytes([11; 32]),
494 component_spec: "projects".parse().expect("Component Spec ID"),
495 spec_hash: [12; 32],
496 role: CanisterRole::new("project_hub"),
497 provisioning_origin: ComponentProvisioningOrigin::FleetAdministrator {
498 caller: Principal::from_slice(&[13; 29]),
499 },
500 release_set: response.release_set,
501 phase: RootComponentAllocationPhase::Reserved,
502 creation: None,
503 installation: None,
504 };
505 let created = RootComponentAllocationResponse {
506 phase: RootComponentAllocationPhase::Created,
507 creation: Some(RootComponentCreationEvidence {
508 wasm_store: Principal::from_slice(&[14; 29]),
509 payload_hash: [15; 32],
510 payload_size_bytes: 4_096,
511 initial_cycles: Cycles::new(5_000_000_000_000),
512 controller: Principal::from_slice(&[6; 29]),
513 canister: Some(Principal::from_slice(&[16; 29])),
514 }),
515 installation: None,
516 ..allocation.clone()
517 };
518 let request_bytes = candid::encode_one(&request).expect("encode request");
519 let response_bytes = candid::encode_one(&response).expect("encode response");
520 let allocation_bytes = candid::encode_one(&allocation).expect("encode allocation");
521 let created_bytes = candid::encode_one(&created).expect("encode created allocation");
522
523 assert_eq!(
524 candid::decode_one::<RootComponentRegistryPreparationRequest>(&request_bytes)
525 .expect("decode request"),
526 request
527 );
528 assert_eq!(
529 candid::decode_one::<RootComponentRegistryStatusResponse>(&response_bytes)
530 .expect("decode response"),
531 response
532 );
533 assert_eq!(
534 candid::decode_one::<RootComponentAllocationResponse>(&allocation_bytes)
535 .expect("decode allocation"),
536 allocation
537 );
538 assert_eq!(
539 candid::decode_one::<RootComponentAllocationResponse>(&created_bytes)
540 .expect("decode created allocation"),
541 created
542 );
543 }
544
545 #[test]
546 fn component_commit_response_round_trips_through_candid() {
547 let root = Principal::from_slice(&[6; 29]);
548 let component = ComponentInstanceId::from_generated_bytes([11; 32]);
549 let component_spec: ComponentSpecId = "projects".parse().expect("Component Spec ID");
550 let release_set = FleetSubnetRootReleaseSet {
551 release_build_id: ReleaseBuildId::from_nonce(ReleaseBuildNonce::from_random_bytes(
552 [7; 32],
553 )),
554 manifest_digest: ReleaseSetDigest::from_bytes([8; 32]),
555 };
556 let provisioning_origin = ComponentProvisioningOrigin::FleetAdministrator {
557 caller: Principal::from_slice(&[13; 29]),
558 };
559 let binding = ComponentBinding {
560 authority: fleet_registry_authority(),
561 component,
562 component_spec: component_spec.clone(),
563 spec_hash: [12; 32],
564 role: CanisterRole::new("project_hub"),
565 placement_subnet: SubnetId::from_principal(Principal::from_slice(&[17; 29])),
566 fleet_subnet_root: root,
567 canister_id: Principal::from_slice(&[16; 29]),
568 };
569 let head = ComponentRegistryHead {
570 component,
571 revision: 1,
572 content_hash: [18; 32],
573 };
574 let committed = RootComponentCommitResponse {
575 allocation: RootComponentAllocationResponse {
576 operation_id: [10; 32],
577 allocation_sequence: 1,
578 component,
579 component_spec,
580 spec_hash: binding.spec_hash,
581 role: binding.role.clone(),
582 provisioning_origin: provisioning_origin.clone(),
583 release_set,
584 phase: RootComponentAllocationPhase::Committed,
585 creation: Some(RootComponentCreationEvidence {
586 wasm_store: Principal::from_slice(&[14; 29]),
587 payload_hash: [15; 32],
588 payload_size_bytes: 4_096,
589 initial_cycles: Cycles::new(5_000_000_000_000),
590 controller: root,
591 canister: Some(binding.canister_id),
592 }),
593 installation: Some(RootComponentInstallEvidence {
594 raw_module_hash: [20; 32],
595 chunk_hashes: vec![vec![21; 32]],
596 binding: binding.clone(),
597 }),
598 },
599 registry: ComponentRegistryPartitionResponse {
600 head: head.clone(),
601 binding: binding.clone(),
602 provisioning_origin,
603 release_set,
604 status: ComponentLifecycleStatus::Prepared,
605 encoded_bytes: 2_048,
606 },
607 directory: ComponentDirectoryHead {
608 provenance: ComponentDirectoryProvenance {
609 component: binding,
610 source_fleet_subnet_root: root,
611 component_registry_revision: head.revision,
612 component_registry_content_hash: head.content_hash,
613 synchronized_at_ns: 19,
614 },
615 descendant_count: 0,
616 },
617 };
618 let committed_bytes = candid::encode_one(&committed).expect("encode committed allocation");
619
620 assert_eq!(
621 candid::decode_one::<RootComponentCommitResponse>(&committed_bytes)
622 .expect("decode committed allocation"),
623 committed
624 );
625 }
626
627 fn fleet_registry_authority() -> FleetRegistryAuthority {
628 FleetRegistryAuthority {
629 binding: FleetCoordinatorBinding {
630 fleet: crate::ids::FleetBinding {
631 fleet: FleetKey {
632 canonical_network_id: CanonicalNetworkId::public_ic(),
633 fleet_id: FleetId::from_generated_bytes([1; 32]),
634 },
635 app: AppId::from("toko"),
636 },
637 coordinator_subnet: SubnetId::from_principal(Principal::from_slice(&[2; 29])),
638 coordinator: Principal::from_slice(&[3; 29]),
639 },
640 epoch: 1,
641 }
642 }
643
644 #[test]
645 fn component_creation_request_round_trips_through_candid() {
646 let request = RootComponentCreationRequest {
647 operation_id: [10; 32],
648 };
649 let bytes = candid::encode_one(request).expect("encode creation request");
650
651 assert_eq!(
652 candid::decode_one::<RootComponentCreationRequest>(&bytes)
653 .expect("decode creation request"),
654 request
655 );
656 }
657
658 #[test]
659 fn component_install_request_round_trips_through_candid() {
660 let request = RootComponentInstallRequest {
661 operation_id: [10; 32],
662 };
663 let bytes = candid::encode_one(request).expect("encode install request");
664
665 assert_eq!(
666 candid::decode_one::<RootComponentInstallRequest>(&bytes)
667 .expect("decode install request"),
668 request
669 );
670 }
671
672 #[test]
673 fn component_commit_request_round_trips_through_candid() {
674 let request = RootComponentCommitRequest {
675 operation_id: [10; 32],
676 };
677 let bytes = candid::encode_one(request).expect("encode commit request");
678
679 assert_eq!(
680 candid::decode_one::<RootComponentCommitRequest>(&bytes)
681 .expect("decode commit request"),
682 request
683 );
684 }
685
686 #[test]
687 fn component_runtime_activation_requests_round_trip_through_candid() {
688 let root_request = RootComponentRuntimeActivationRequest {
689 operation_id: [22; 32],
690 };
691 let target_request = ComponentRuntimeActivationRequest {
692 operation_id: root_request.operation_id,
693 directory_authority_hash: [23; 32],
694 };
695 let membership_request = RootComponentMembershipActivationRequest {
696 operation_id: root_request.operation_id,
697 };
698 let root_bytes = candid::encode_one(root_request).expect("encode root activation request");
699 let target_bytes =
700 candid::encode_one(target_request).expect("encode target activation request");
701 let membership_bytes =
702 candid::encode_one(membership_request).expect("encode membership activation request");
703
704 assert_eq!(
705 candid::decode_one::<RootComponentRuntimeActivationRequest>(&root_bytes)
706 .expect("decode root activation request"),
707 root_request
708 );
709 assert_eq!(
710 candid::decode_one::<ComponentRuntimeActivationRequest>(&target_bytes)
711 .expect("decode target activation request"),
712 target_request
713 );
714 assert_eq!(
715 candid::decode_one::<RootComponentMembershipActivationRequest>(&membership_bytes)
716 .expect("decode membership activation request"),
717 membership_request
718 );
719 }
720}