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, Debug, Deserialize, Eq, PartialEq, Serialize)]
137pub enum ComponentProvisioningOrigin {
138 FleetAdministrator { caller: Principal },
139}
140
141#[derive(CandidType, Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
148pub enum RootComponentAllocationPhase {
149 Reserved,
150 CreationIntent,
151 Created,
152 InstallIntent,
153 Installed,
154 Verified,
155 Committed,
156}
157
158#[derive(CandidType, Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
165pub enum ComponentLifecycleStatus {
166 Prepared,
167 Active,
168 Draining,
169 Removed,
170}
171
172#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
179pub struct ComponentRegistryHead {
180 pub component: ComponentInstanceId,
181 pub revision: u64,
182 pub content_hash: [u8; 32],
183}
184
185#[derive(CandidType, Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
192pub struct ComponentRegistryPartitionRequest {
193 pub component: ComponentInstanceId,
194}
195
196#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
203pub struct ComponentRegistryPartitionResponse {
204 pub head: ComponentRegistryHead,
205 pub binding: ComponentBinding,
206 pub provisioning_origin: ComponentProvisioningOrigin,
207 pub release_set: FleetSubnetRootReleaseSet,
208 pub status: ComponentLifecycleStatus,
209 pub encoded_bytes: u64,
210}
211
212#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
219pub struct ComponentDirectoryProvenance {
220 pub component: ComponentBinding,
221 pub source_fleet_subnet_root: Principal,
222 pub component_registry_revision: u64,
223 pub component_registry_content_hash: [u8; 32],
224 pub synchronized_at_ns: u64,
225}
226
227#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
234pub struct ComponentDirectoryHead {
235 pub provenance: ComponentDirectoryProvenance,
236 pub descendant_count: u32,
237}
238
239#[derive(CandidType, Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
246pub struct ComponentDirectoryHeadRequest {
247 pub component: ComponentInstanceId,
248}
249
250#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
257pub struct ComponentRuntimeDirectoryAuthority {
258 pub fleet: FleetDirectorySnapshot,
259 pub component: ComponentDirectoryHead,
260}
261
262#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
269pub struct ComponentRuntimeDirectoryPreparationRequest {
270 pub operation_id: [u8; 32],
271 pub authority: ComponentRuntimeDirectoryAuthority,
272}
273
274#[derive(CandidType, Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
281pub enum ComponentRuntimePhase {
282 AwaitingDirectory,
283 DirectoryPrepared,
284 Active,
285}
286
287#[derive(CandidType, Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
294pub struct ComponentRuntimeActivationEvidence {
295 pub directory_authority_hash: [u8; 32],
296 pub activated_at_ns: u64,
297}
298
299#[derive(CandidType, Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
306pub struct ComponentRuntimeActivationRequest {
307 pub operation_id: [u8; 32],
308 pub directory_authority_hash: [u8; 32],
309}
310
311#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
318pub struct ComponentRuntimeStatusResponse {
319 pub operation_id: [u8; 32],
320 pub binding: ManagedCanisterBinding,
321 pub phase: ComponentRuntimePhase,
322 pub authority: Option<ComponentRuntimeDirectoryAuthority>,
323 pub authority_hash: Option<[u8; 32]>,
324 pub activation: Option<ComponentRuntimeActivationEvidence>,
325}
326
327#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
334pub struct RootComponentCreationEvidence {
335 pub wasm_store: Principal,
336 pub payload_hash: [u8; 32],
337 pub payload_size_bytes: u64,
338 pub initial_cycles: Cycles,
339 pub controller: Principal,
340 pub canister: Option<Principal>,
341}
342
343#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
350pub struct RootComponentInstallEvidence {
351 pub raw_module_hash: [u8; 32],
352 pub chunk_hashes: Vec<Vec<u8>>,
353 pub binding: ComponentBinding,
354}
355
356#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
363pub struct RootComponentAllocationResponse {
364 pub operation_id: [u8; 32],
365 pub allocation_sequence: u64,
366 pub component: ComponentInstanceId,
367 pub component_spec: ComponentSpecId,
368 pub spec_hash: [u8; 32],
369 pub role: CanisterRole,
370 pub provisioning_origin: ComponentProvisioningOrigin,
371 pub release_set: FleetSubnetRootReleaseSet,
372 pub phase: RootComponentAllocationPhase,
373 pub creation: Option<RootComponentCreationEvidence>,
374 pub installation: Option<RootComponentInstallEvidence>,
375}
376
377#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
384pub struct RootComponentCommitResponse {
385 pub allocation: RootComponentAllocationResponse,
386 pub registry: ComponentRegistryPartitionResponse,
387 pub directory: ComponentDirectoryHead,
388}
389
390#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
397pub struct RootComponentDirectoryPreparationResponse {
398 pub committed: RootComponentCommitResponse,
399 pub target: ComponentRuntimeStatusResponse,
400}
401
402#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
409pub struct RootComponentRuntimeActivationResponse {
410 pub committed: RootComponentCommitResponse,
411 pub target: ComponentRuntimeStatusResponse,
412}
413
414#[cfg(test)]
415mod tests {
416 use super::*;
417 use crate::{
418 dto::root_store::RootStoreBootstrapRequest,
419 ids::{
420 AppId, CanonicalNetworkId, FleetCoordinatorBinding, FleetId, FleetKey,
421 FleetRegistryAuthority, ReleaseBuildId, ReleaseBuildNonce, ReleaseSetDigest, SubnetId,
422 },
423 };
424
425 #[test]
426 fn component_registry_contracts_round_trip_through_candid() {
427 let request = RootComponentRegistryPreparationRequest {
428 store_bootstrap: RootStoreBootstrapRequest {
429 manifest_payload_size_bytes: 128,
430 },
431 expected_fleet_registry: FleetRegistryVersion {
432 authority: fleet_registry_authority(),
433 revision: 4,
434 content_hash: [5; 32],
435 },
436 };
437 let response = RootComponentRegistryStatusResponse {
438 fleet_subnet_root: Principal::from_slice(&[6; 29]),
439 prepared_against_registry: request.expected_fleet_registry.clone(),
440 release_set: FleetSubnetRootReleaseSet {
441 release_build_id: ReleaseBuildId::from_nonce(ReleaseBuildNonce::from_random_bytes(
442 [7; 32],
443 )),
444 manifest_digest: ReleaseSetDigest::from_bytes([8; 32]),
445 },
446 component_topology_digest: ComponentTopologyDigest::from_bytes([9; 32]),
447 next_allocation_sequence: 1,
448 reserved_component_instances: 0,
449 committed_component_instances: 0,
450 managed_descendants: 0,
451 encoded_bytes: 0,
452 };
453 let allocation = RootComponentAllocationResponse {
454 operation_id: [10; 32],
455 allocation_sequence: 1,
456 component: ComponentInstanceId::from_generated_bytes([11; 32]),
457 component_spec: "projects".parse().expect("Component Spec ID"),
458 spec_hash: [12; 32],
459 role: CanisterRole::new("project_hub"),
460 provisioning_origin: ComponentProvisioningOrigin::FleetAdministrator {
461 caller: Principal::from_slice(&[13; 29]),
462 },
463 release_set: response.release_set,
464 phase: RootComponentAllocationPhase::Reserved,
465 creation: None,
466 installation: None,
467 };
468 let created = RootComponentAllocationResponse {
469 phase: RootComponentAllocationPhase::Created,
470 creation: Some(RootComponentCreationEvidence {
471 wasm_store: Principal::from_slice(&[14; 29]),
472 payload_hash: [15; 32],
473 payload_size_bytes: 4_096,
474 initial_cycles: Cycles::new(5_000_000_000_000),
475 controller: Principal::from_slice(&[6; 29]),
476 canister: Some(Principal::from_slice(&[16; 29])),
477 }),
478 installation: None,
479 ..allocation.clone()
480 };
481 let request_bytes = candid::encode_one(&request).expect("encode request");
482 let response_bytes = candid::encode_one(&response).expect("encode response");
483 let allocation_bytes = candid::encode_one(&allocation).expect("encode allocation");
484 let created_bytes = candid::encode_one(&created).expect("encode created allocation");
485
486 assert_eq!(
487 candid::decode_one::<RootComponentRegistryPreparationRequest>(&request_bytes)
488 .expect("decode request"),
489 request
490 );
491 assert_eq!(
492 candid::decode_one::<RootComponentRegistryStatusResponse>(&response_bytes)
493 .expect("decode response"),
494 response
495 );
496 assert_eq!(
497 candid::decode_one::<RootComponentAllocationResponse>(&allocation_bytes)
498 .expect("decode allocation"),
499 allocation
500 );
501 assert_eq!(
502 candid::decode_one::<RootComponentAllocationResponse>(&created_bytes)
503 .expect("decode created allocation"),
504 created
505 );
506 }
507
508 #[test]
509 fn component_commit_response_round_trips_through_candid() {
510 let root = Principal::from_slice(&[6; 29]);
511 let component = ComponentInstanceId::from_generated_bytes([11; 32]);
512 let component_spec: ComponentSpecId = "projects".parse().expect("Component Spec ID");
513 let release_set = FleetSubnetRootReleaseSet {
514 release_build_id: ReleaseBuildId::from_nonce(ReleaseBuildNonce::from_random_bytes(
515 [7; 32],
516 )),
517 manifest_digest: ReleaseSetDigest::from_bytes([8; 32]),
518 };
519 let provisioning_origin = ComponentProvisioningOrigin::FleetAdministrator {
520 caller: Principal::from_slice(&[13; 29]),
521 };
522 let binding = ComponentBinding {
523 authority: fleet_registry_authority(),
524 component,
525 component_spec: component_spec.clone(),
526 spec_hash: [12; 32],
527 role: CanisterRole::new("project_hub"),
528 placement_subnet: SubnetId::from_principal(Principal::from_slice(&[17; 29])),
529 fleet_subnet_root: root,
530 canister_id: Principal::from_slice(&[16; 29]),
531 };
532 let head = ComponentRegistryHead {
533 component,
534 revision: 1,
535 content_hash: [18; 32],
536 };
537 let committed = RootComponentCommitResponse {
538 allocation: RootComponentAllocationResponse {
539 operation_id: [10; 32],
540 allocation_sequence: 1,
541 component,
542 component_spec,
543 spec_hash: binding.spec_hash,
544 role: binding.role.clone(),
545 provisioning_origin: provisioning_origin.clone(),
546 release_set,
547 phase: RootComponentAllocationPhase::Committed,
548 creation: Some(RootComponentCreationEvidence {
549 wasm_store: Principal::from_slice(&[14; 29]),
550 payload_hash: [15; 32],
551 payload_size_bytes: 4_096,
552 initial_cycles: Cycles::new(5_000_000_000_000),
553 controller: root,
554 canister: Some(binding.canister_id),
555 }),
556 installation: Some(RootComponentInstallEvidence {
557 raw_module_hash: [20; 32],
558 chunk_hashes: vec![vec![21; 32]],
559 binding: binding.clone(),
560 }),
561 },
562 registry: ComponentRegistryPartitionResponse {
563 head: head.clone(),
564 binding: binding.clone(),
565 provisioning_origin,
566 release_set,
567 status: ComponentLifecycleStatus::Prepared,
568 encoded_bytes: 2_048,
569 },
570 directory: ComponentDirectoryHead {
571 provenance: ComponentDirectoryProvenance {
572 component: binding,
573 source_fleet_subnet_root: root,
574 component_registry_revision: head.revision,
575 component_registry_content_hash: head.content_hash,
576 synchronized_at_ns: 19,
577 },
578 descendant_count: 0,
579 },
580 };
581 let committed_bytes = candid::encode_one(&committed).expect("encode committed allocation");
582
583 assert_eq!(
584 candid::decode_one::<RootComponentCommitResponse>(&committed_bytes)
585 .expect("decode committed allocation"),
586 committed
587 );
588 }
589
590 fn fleet_registry_authority() -> FleetRegistryAuthority {
591 FleetRegistryAuthority {
592 binding: FleetCoordinatorBinding {
593 fleet: crate::ids::FleetBinding {
594 fleet: FleetKey {
595 canonical_network_id: CanonicalNetworkId::public_ic(),
596 fleet_id: FleetId::from_generated_bytes([1; 32]),
597 },
598 app: AppId::from("toko"),
599 },
600 coordinator_subnet: SubnetId::from_principal(Principal::from_slice(&[2; 29])),
601 coordinator: Principal::from_slice(&[3; 29]),
602 },
603 epoch: 1,
604 }
605 }
606
607 #[test]
608 fn component_creation_request_round_trips_through_candid() {
609 let request = RootComponentCreationRequest {
610 operation_id: [10; 32],
611 };
612 let bytes = candid::encode_one(request).expect("encode creation request");
613
614 assert_eq!(
615 candid::decode_one::<RootComponentCreationRequest>(&bytes)
616 .expect("decode creation request"),
617 request
618 );
619 }
620
621 #[test]
622 fn component_install_request_round_trips_through_candid() {
623 let request = RootComponentInstallRequest {
624 operation_id: [10; 32],
625 };
626 let bytes = candid::encode_one(request).expect("encode install request");
627
628 assert_eq!(
629 candid::decode_one::<RootComponentInstallRequest>(&bytes)
630 .expect("decode install request"),
631 request
632 );
633 }
634
635 #[test]
636 fn component_commit_request_round_trips_through_candid() {
637 let request = RootComponentCommitRequest {
638 operation_id: [10; 32],
639 };
640 let bytes = candid::encode_one(request).expect("encode commit request");
641
642 assert_eq!(
643 candid::decode_one::<RootComponentCommitRequest>(&bytes)
644 .expect("decode commit request"),
645 request
646 );
647 }
648
649 #[test]
650 fn component_runtime_activation_requests_round_trip_through_candid() {
651 let root_request = RootComponentRuntimeActivationRequest {
652 operation_id: [22; 32],
653 };
654 let target_request = ComponentRuntimeActivationRequest {
655 operation_id: root_request.operation_id,
656 directory_authority_hash: [23; 32],
657 };
658 let root_bytes = candid::encode_one(root_request).expect("encode root activation request");
659 let target_bytes =
660 candid::encode_one(target_request).expect("encode target activation request");
661
662 assert_eq!(
663 candid::decode_one::<RootComponentRuntimeActivationRequest>(&root_bytes)
664 .expect("decode root activation request"),
665 root_request
666 );
667 assert_eq!(
668 candid::decode_one::<ComponentRuntimeActivationRequest>(&target_bytes)
669 .expect("decode target activation request"),
670 target_request
671 );
672 }
673}