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, Debug, Deserialize, Eq, PartialEq, Serialize)]
126pub enum ComponentProvisioningOrigin {
127 FleetAdministrator { caller: Principal },
128}
129
130#[derive(CandidType, Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
137pub enum RootComponentAllocationPhase {
138 Reserved,
139 CreationIntent,
140 Created,
141 InstallIntent,
142 Installed,
143 Verified,
144 Committed,
145}
146
147#[derive(CandidType, Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
154pub enum ComponentLifecycleStatus {
155 Prepared,
156 Active,
157 Draining,
158 Removed,
159}
160
161#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
168pub struct ComponentRegistryHead {
169 pub component: ComponentInstanceId,
170 pub revision: u64,
171 pub content_hash: [u8; 32],
172}
173
174#[derive(CandidType, Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
181pub struct ComponentRegistryPartitionRequest {
182 pub component: ComponentInstanceId,
183}
184
185#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
192pub struct ComponentRegistryPartitionResponse {
193 pub head: ComponentRegistryHead,
194 pub binding: ComponentBinding,
195 pub provisioning_origin: ComponentProvisioningOrigin,
196 pub release_set: FleetSubnetRootReleaseSet,
197 pub status: ComponentLifecycleStatus,
198 pub encoded_bytes: u64,
199}
200
201#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
208pub struct ComponentDirectoryProvenance {
209 pub component: ComponentBinding,
210 pub source_fleet_subnet_root: Principal,
211 pub component_registry_revision: u64,
212 pub component_registry_content_hash: [u8; 32],
213 pub synchronized_at_ns: u64,
214}
215
216#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
223pub struct ComponentDirectoryHead {
224 pub provenance: ComponentDirectoryProvenance,
225 pub descendant_count: u32,
226}
227
228#[derive(CandidType, Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
235pub struct ComponentDirectoryHeadRequest {
236 pub component: ComponentInstanceId,
237}
238
239#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
246pub struct ComponentRuntimeDirectoryAuthority {
247 pub fleet: FleetDirectorySnapshot,
248 pub component: ComponentDirectoryHead,
249}
250
251#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
258pub struct ComponentRuntimeDirectoryPreparationRequest {
259 pub operation_id: [u8; 32],
260 pub authority: ComponentRuntimeDirectoryAuthority,
261}
262
263#[derive(CandidType, Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
270pub enum ComponentRuntimeDirectoryPhase {
271 AwaitingDirectory,
272 DirectoryPrepared,
273}
274
275#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
282pub struct ComponentRuntimeDirectoryStatusResponse {
283 pub operation_id: [u8; 32],
284 pub binding: ManagedCanisterBinding,
285 pub phase: ComponentRuntimeDirectoryPhase,
286 pub authority: Option<ComponentRuntimeDirectoryAuthority>,
287 pub authority_hash: Option<[u8; 32]>,
288}
289
290#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
297pub struct RootComponentCreationEvidence {
298 pub wasm_store: Principal,
299 pub payload_hash: [u8; 32],
300 pub payload_size_bytes: u64,
301 pub initial_cycles: Cycles,
302 pub controller: Principal,
303 pub canister: Option<Principal>,
304}
305
306#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
313pub struct RootComponentInstallEvidence {
314 pub raw_module_hash: [u8; 32],
315 pub chunk_hashes: Vec<Vec<u8>>,
316 pub binding: ComponentBinding,
317}
318
319#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
326pub struct RootComponentAllocationResponse {
327 pub operation_id: [u8; 32],
328 pub allocation_sequence: u64,
329 pub component: ComponentInstanceId,
330 pub component_spec: ComponentSpecId,
331 pub spec_hash: [u8; 32],
332 pub role: CanisterRole,
333 pub provisioning_origin: ComponentProvisioningOrigin,
334 pub release_set: FleetSubnetRootReleaseSet,
335 pub phase: RootComponentAllocationPhase,
336 pub creation: Option<RootComponentCreationEvidence>,
337 pub installation: Option<RootComponentInstallEvidence>,
338}
339
340#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
347pub struct RootComponentCommitResponse {
348 pub allocation: RootComponentAllocationResponse,
349 pub registry: ComponentRegistryPartitionResponse,
350 pub directory: ComponentDirectoryHead,
351}
352
353#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
360pub struct RootComponentDirectoryPreparationResponse {
361 pub committed: RootComponentCommitResponse,
362 pub target: ComponentRuntimeDirectoryStatusResponse,
363}
364
365#[cfg(test)]
366mod tests {
367 use super::*;
368 use crate::{
369 dto::root_store::RootStoreBootstrapRequest,
370 ids::{
371 AppId, CanonicalNetworkId, FleetCoordinatorBinding, FleetId, FleetKey,
372 FleetRegistryAuthority, ReleaseBuildId, ReleaseBuildNonce, ReleaseSetDigest, SubnetId,
373 },
374 };
375
376 #[test]
377 fn component_registry_contracts_round_trip_through_candid() {
378 let request = RootComponentRegistryPreparationRequest {
379 store_bootstrap: RootStoreBootstrapRequest {
380 manifest_payload_size_bytes: 128,
381 },
382 expected_fleet_registry: FleetRegistryVersion {
383 authority: fleet_registry_authority(),
384 revision: 4,
385 content_hash: [5; 32],
386 },
387 };
388 let response = RootComponentRegistryStatusResponse {
389 fleet_subnet_root: Principal::from_slice(&[6; 29]),
390 prepared_against_registry: request.expected_fleet_registry.clone(),
391 release_set: FleetSubnetRootReleaseSet {
392 release_build_id: ReleaseBuildId::from_nonce(ReleaseBuildNonce::from_random_bytes(
393 [7; 32],
394 )),
395 manifest_digest: ReleaseSetDigest::from_bytes([8; 32]),
396 },
397 component_topology_digest: ComponentTopologyDigest::from_bytes([9; 32]),
398 next_allocation_sequence: 1,
399 reserved_component_instances: 0,
400 committed_component_instances: 0,
401 managed_descendants: 0,
402 encoded_bytes: 0,
403 };
404 let allocation = RootComponentAllocationResponse {
405 operation_id: [10; 32],
406 allocation_sequence: 1,
407 component: ComponentInstanceId::from_generated_bytes([11; 32]),
408 component_spec: "projects".parse().expect("Component Spec ID"),
409 spec_hash: [12; 32],
410 role: CanisterRole::new("project_hub"),
411 provisioning_origin: ComponentProvisioningOrigin::FleetAdministrator {
412 caller: Principal::from_slice(&[13; 29]),
413 },
414 release_set: response.release_set,
415 phase: RootComponentAllocationPhase::Reserved,
416 creation: None,
417 installation: None,
418 };
419 let created = RootComponentAllocationResponse {
420 phase: RootComponentAllocationPhase::Created,
421 creation: Some(RootComponentCreationEvidence {
422 wasm_store: Principal::from_slice(&[14; 29]),
423 payload_hash: [15; 32],
424 payload_size_bytes: 4_096,
425 initial_cycles: Cycles::new(5_000_000_000_000),
426 controller: Principal::from_slice(&[6; 29]),
427 canister: Some(Principal::from_slice(&[16; 29])),
428 }),
429 installation: None,
430 ..allocation.clone()
431 };
432 let request_bytes = candid::encode_one(&request).expect("encode request");
433 let response_bytes = candid::encode_one(&response).expect("encode response");
434 let allocation_bytes = candid::encode_one(&allocation).expect("encode allocation");
435 let created_bytes = candid::encode_one(&created).expect("encode created allocation");
436
437 assert_eq!(
438 candid::decode_one::<RootComponentRegistryPreparationRequest>(&request_bytes)
439 .expect("decode request"),
440 request
441 );
442 assert_eq!(
443 candid::decode_one::<RootComponentRegistryStatusResponse>(&response_bytes)
444 .expect("decode response"),
445 response
446 );
447 assert_eq!(
448 candid::decode_one::<RootComponentAllocationResponse>(&allocation_bytes)
449 .expect("decode allocation"),
450 allocation
451 );
452 assert_eq!(
453 candid::decode_one::<RootComponentAllocationResponse>(&created_bytes)
454 .expect("decode created allocation"),
455 created
456 );
457 }
458
459 #[test]
460 fn component_commit_response_round_trips_through_candid() {
461 let root = Principal::from_slice(&[6; 29]);
462 let component = ComponentInstanceId::from_generated_bytes([11; 32]);
463 let component_spec: ComponentSpecId = "projects".parse().expect("Component Spec ID");
464 let release_set = FleetSubnetRootReleaseSet {
465 release_build_id: ReleaseBuildId::from_nonce(ReleaseBuildNonce::from_random_bytes(
466 [7; 32],
467 )),
468 manifest_digest: ReleaseSetDigest::from_bytes([8; 32]),
469 };
470 let provisioning_origin = ComponentProvisioningOrigin::FleetAdministrator {
471 caller: Principal::from_slice(&[13; 29]),
472 };
473 let binding = ComponentBinding {
474 authority: fleet_registry_authority(),
475 component,
476 component_spec: component_spec.clone(),
477 spec_hash: [12; 32],
478 role: CanisterRole::new("project_hub"),
479 placement_subnet: SubnetId::from_principal(Principal::from_slice(&[17; 29])),
480 fleet_subnet_root: root,
481 canister_id: Principal::from_slice(&[16; 29]),
482 };
483 let head = ComponentRegistryHead {
484 component,
485 revision: 1,
486 content_hash: [18; 32],
487 };
488 let committed = RootComponentCommitResponse {
489 allocation: RootComponentAllocationResponse {
490 operation_id: [10; 32],
491 allocation_sequence: 1,
492 component,
493 component_spec,
494 spec_hash: binding.spec_hash,
495 role: binding.role.clone(),
496 provisioning_origin: provisioning_origin.clone(),
497 release_set,
498 phase: RootComponentAllocationPhase::Committed,
499 creation: Some(RootComponentCreationEvidence {
500 wasm_store: Principal::from_slice(&[14; 29]),
501 payload_hash: [15; 32],
502 payload_size_bytes: 4_096,
503 initial_cycles: Cycles::new(5_000_000_000_000),
504 controller: root,
505 canister: Some(binding.canister_id),
506 }),
507 installation: Some(RootComponentInstallEvidence {
508 raw_module_hash: [20; 32],
509 chunk_hashes: vec![vec![21; 32]],
510 binding: binding.clone(),
511 }),
512 },
513 registry: ComponentRegistryPartitionResponse {
514 head: head.clone(),
515 binding: binding.clone(),
516 provisioning_origin,
517 release_set,
518 status: ComponentLifecycleStatus::Prepared,
519 encoded_bytes: 2_048,
520 },
521 directory: ComponentDirectoryHead {
522 provenance: ComponentDirectoryProvenance {
523 component: binding,
524 source_fleet_subnet_root: root,
525 component_registry_revision: head.revision,
526 component_registry_content_hash: head.content_hash,
527 synchronized_at_ns: 19,
528 },
529 descendant_count: 0,
530 },
531 };
532 let committed_bytes = candid::encode_one(&committed).expect("encode committed allocation");
533
534 assert_eq!(
535 candid::decode_one::<RootComponentCommitResponse>(&committed_bytes)
536 .expect("decode committed allocation"),
537 committed
538 );
539 }
540
541 fn fleet_registry_authority() -> FleetRegistryAuthority {
542 FleetRegistryAuthority {
543 binding: FleetCoordinatorBinding {
544 fleet: crate::ids::FleetBinding {
545 fleet: FleetKey {
546 canonical_network_id: CanonicalNetworkId::public_ic(),
547 fleet_id: FleetId::from_generated_bytes([1; 32]),
548 },
549 app: AppId::from("toko"),
550 },
551 coordinator_subnet: SubnetId::from_principal(Principal::from_slice(&[2; 29])),
552 coordinator: Principal::from_slice(&[3; 29]),
553 },
554 epoch: 1,
555 }
556 }
557
558 #[test]
559 fn component_creation_request_round_trips_through_candid() {
560 let request = RootComponentCreationRequest {
561 operation_id: [10; 32],
562 };
563 let bytes = candid::encode_one(request).expect("encode creation request");
564
565 assert_eq!(
566 candid::decode_one::<RootComponentCreationRequest>(&bytes)
567 .expect("decode creation request"),
568 request
569 );
570 }
571
572 #[test]
573 fn component_install_request_round_trips_through_candid() {
574 let request = RootComponentInstallRequest {
575 operation_id: [10; 32],
576 };
577 let bytes = candid::encode_one(request).expect("encode install request");
578
579 assert_eq!(
580 candid::decode_one::<RootComponentInstallRequest>(&bytes)
581 .expect("decode install request"),
582 request
583 );
584 }
585
586 #[test]
587 fn component_commit_request_round_trips_through_candid() {
588 let request = RootComponentCommitRequest {
589 operation_id: [10; 32],
590 };
591 let bytes = candid::encode_one(request).expect("encode commit request");
592
593 assert_eq!(
594 candid::decode_one::<RootComponentCommitRequest>(&bytes)
595 .expect("decode commit request"),
596 request
597 );
598 }
599}