use crate::{
InternalError, InternalErrorOrigin,
dto::{
component_registry::{
ComponentRuntimeActivationRequest, ComponentRuntimeDirectoryAuthority,
ComponentRuntimeDirectoryPreparationRequest,
ComponentRuntimeDirectorySynchronizationRequest, ComponentRuntimePhase,
ComponentRuntimeStatusResponse,
},
fleet_registry::{FleetDirectorySnapshot, FleetSubnetRootStatus},
},
ids::{ComponentBinding, ManagedCanisterBinding},
ops::{
component_runtime::ComponentRuntimeOps,
ic::IcOps,
storage::{StorageOpsError, fleet_activation::FleetActivationOps},
},
};
pub fn prepare_directory(
request: ComponentRuntimeDirectoryPreparationRequest,
) -> Result<ComponentRuntimeStatusResponse, InternalError> {
let current = FleetActivationOps::component_runtime_status().map_err(StorageOpsError::from)?;
validate_request(¤t, &request)?;
let authority_hash = ComponentRuntimeOps::directory_authority_hash(&request.authority)?;
FleetActivationOps::prepare_component_runtime_directory(request, authority_hash)
.map_err(StorageOpsError::from)
.map_err(InternalError::from)
}
pub fn synchronize_directory(
request: ComponentRuntimeDirectorySynchronizationRequest,
) -> Result<ComponentRuntimeStatusResponse, InternalError> {
let current = status()?;
if request.operation_id != current.operation_id {
return Err(InternalError::conflict(
"Component runtime Directory operation differs from protected installation identity",
));
}
validate_binding(¤t.binding)?;
validate_authority(¤t.binding, &request.authority)?;
if current.phase != ComponentRuntimePhase::Active || current.activation.is_none() {
return Err(InternalError::conflict(
"current Component Directory synchronization requires an Active runtime",
));
}
let current_authority = current.authority.as_ref().ok_or_else(|| {
InternalError::invariant(
InternalErrorOrigin::Storage,
"Active Component runtime has no current Directory authority",
)
})?;
validate_directory_progression(current_authority, &request.authority)?;
let authority_hash = ComponentRuntimeOps::directory_authority_hash(&request.authority)?;
FleetActivationOps::synchronize_component_runtime_directory(request, authority_hash)
.map_err(StorageOpsError::from)
.map_err(InternalError::from)
}
pub fn status() -> Result<ComponentRuntimeStatusResponse, InternalError> {
let status = FleetActivationOps::component_runtime_status().map_err(StorageOpsError::from)?;
validate_binding(&status.binding)?;
match (&status.authority, status.authority_hash) {
(None, None) => {}
(Some(authority), Some(authority_hash)) => {
validate_authority(&status.binding, authority)?;
if ComponentRuntimeOps::directory_authority_hash(authority)? != authority_hash {
return Err(InternalError::invariant(
InternalErrorOrigin::Storage,
"retained Component runtime Directory authority hash does not match its value",
));
}
}
_ => {
return Err(InternalError::invariant(
InternalErrorOrigin::Storage,
"retained Component runtime Directory authority and hash are incomplete",
));
}
}
Ok(status)
}
pub fn activate(
request: ComponentRuntimeActivationRequest,
) -> Result<crate::view::fleet_activation::ComponentRuntimeActivationTransition, InternalError> {
let current = status()?;
let expected_authority_hash = match current.phase {
ComponentRuntimePhase::AwaitingDirectory | ComponentRuntimePhase::DirectoryPrepared => {
current.authority_hash
}
ComponentRuntimePhase::Active => current
.activation
.map(|activation| activation.directory_authority_hash),
};
if request.operation_id != current.operation_id
|| request.directory_authority_hash == [0; 32]
|| expected_authority_hash != Some(request.directory_authority_hash)
{
return Err(InternalError::conflict(
"Component runtime activation differs from its protected Directory authority",
));
}
let transition = FleetActivationOps::activate_component_runtime(request, IcOps::now_nanos())
.map_err(StorageOpsError::from)
.map_err(InternalError::from)?;
if transition.transitioned
&& let Err(error) = crate::workflow::runtime::RuntimeWorkflow::start_all()
{
IcOps::trap(format!(
"Component runtime activation could not establish runtime services: {error}"
));
}
Ok(transition)
}
fn validate_request(
current: &ComponentRuntimeStatusResponse,
request: &ComponentRuntimeDirectoryPreparationRequest,
) -> Result<(), InternalError> {
if request.operation_id != current.operation_id {
return Err(InternalError::conflict(
"Component runtime Directory operation differs from protected installation identity",
));
}
validate_binding(¤t.binding)?;
validate_authority(¤t.binding, &request.authority)
}
fn validate_binding(binding: &ManagedCanisterBinding) -> Result<(), InternalError> {
let canister = match binding {
ManagedCanisterBinding::Component(component) => component.canister_id,
ManagedCanisterBinding::ComponentChild(child) => child.canister_id,
};
if canister != IcOps::canister_self() {
return Err(InternalError::invariant(
InternalErrorOrigin::Storage,
"protected Component runtime binding does not name this Canister",
));
}
Ok(())
}
fn validate_authority(
binding: &ManagedCanisterBinding,
authority: &ComponentRuntimeDirectoryAuthority,
) -> Result<(), InternalError> {
let component = owning_component(binding);
if authority.component.provenance.component != *component
|| authority.component.provenance.source_fleet_subnet_root != component.fleet_subnet_root
|| authority.component.provenance.component_registry_revision == 0
|| authority
.component
.provenance
.component_registry_content_hash
== [0; 32]
|| authority.component.provenance.synchronized_at_ns == 0
{
return Err(InternalError::invalid_input(
"Component Directory does not match the protected Component-tree binding",
));
}
validate_fleet_directory(component, &authority.fleet)
}
fn validate_directory_progression(
current: &ComponentRuntimeDirectoryAuthority,
next: &ComponentRuntimeDirectoryAuthority,
) -> Result<(), InternalError> {
if current == next {
return Ok(());
}
let current_component = ¤t.component.provenance;
let next_component = &next.component.provenance;
let current_fleet_revision = current.fleet.provenance.registry.revision;
let next_fleet_revision = next.fleet.provenance.registry.revision;
if next_component.component != current_component.component
|| next_component.source_fleet_subnet_root != current_component.source_fleet_subnet_root
|| next_component.component_registry_revision
<= current_component.component_registry_revision
|| next_component.component_registry_content_hash
== current_component.component_registry_content_hash
|| next_component.synchronized_at_ns <= current_component.synchronized_at_ns
|| next_fleet_revision < current_fleet_revision
|| (next_fleet_revision == current_fleet_revision && next.fleet != current.fleet)
{
return Err(InternalError::conflict(
"next Component Directory does not advance exact current authority",
));
}
Ok(())
}
fn validate_fleet_directory(
component: &ComponentBinding,
directory: &FleetDirectorySnapshot,
) -> Result<(), InternalError> {
if directory.provenance.registry.authority != component.authority
|| directory.provenance.registry.revision == 0
|| directory.provenance.registry.content_hash == [0; 32]
|| directory.provenance.source_fleet_subnet_root != component.fleet_subnet_root
|| directory.fleet_subnet_roots.is_empty()
{
return Err(InternalError::invalid_input(
"Fleet Directory does not match the protected Component-tree binding",
));
}
let mut previous: Option<(&[u8], &[u8])> = None;
let mut found_source = false;
for entry in &directory.fleet_subnet_roots {
let key = (
entry.placement_subnet.as_principal().as_slice(),
entry.fleet_subnet_root.as_slice(),
);
if entry.status != FleetSubnetRootStatus::Active
|| previous.is_some_and(|previous| previous >= key)
{
return Err(InternalError::invalid_input(
"Fleet Directory root entries are not unique, canonical and Active",
));
}
previous = Some(key);
if entry.fleet_subnet_root == component.fleet_subnet_root {
if found_source || entry.placement_subnet != component.placement_subnet {
return Err(InternalError::invalid_input(
"Fleet Directory source root placement differs from the Component binding",
));
}
found_source = true;
}
}
if !found_source {
return Err(InternalError::invalid_input(
"Fleet Directory omits the Component's source root",
));
}
Ok(())
}
const fn owning_component(binding: &ManagedCanisterBinding) -> &ComponentBinding {
match binding {
ManagedCanisterBinding::Component(component) => component,
ManagedCanisterBinding::ComponentChild(child) => &child.component,
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::{
dto::{
component_registry::{ComponentDirectoryHead, ComponentDirectoryProvenance},
fleet_registry::{
FleetDirectoryProvenance, FleetRegistryVersion, FleetSubnetRootDirectoryEntry,
},
},
ids::{
AppId, CanisterRole, CanonicalNetworkId, ComponentInstanceId, FleetBinding,
FleetCoordinatorBinding, FleetId, FleetKey, FleetRegistryAuthority, SubnetId,
},
};
use candid::Principal;
#[test]
fn directory_progression_accepts_exact_replay_and_skipped_revisions() {
let current = directory_authority();
assert!(validate_directory_progression(¤t, ¤t).is_ok());
let mut skipped = current.clone();
skipped.component.provenance.component_registry_revision = 3;
skipped.component.provenance.component_registry_content_hash = [13; 32];
skipped.component.provenance.synchronized_at_ns = 14;
skipped.component.descendant_count = 2;
assert!(validate_directory_progression(¤t, &skipped).is_ok());
let mut conflicting = current.clone();
conflicting
.component
.provenance
.component_registry_content_hash = [15; 32];
assert!(validate_directory_progression(¤t, &conflicting).is_err());
}
fn directory_authority() -> ComponentRuntimeDirectoryAuthority {
let subnet = SubnetId::from_principal(Principal::from_slice(&[2; 29]));
let root = Principal::from_slice(&[3; 29]);
let authority = FleetRegistryAuthority {
binding: FleetCoordinatorBinding {
fleet: FleetBinding {
fleet: FleetKey {
canonical_network_id: CanonicalNetworkId::public_ic(),
fleet_id: FleetId::from_generated_bytes([4; 32]),
},
app: AppId::from("test"),
},
coordinator_subnet: subnet,
coordinator: Principal::from_slice(&[5; 29]),
},
epoch: 1,
};
let component = ComponentBinding {
authority: authority.clone(),
component: ComponentInstanceId::from_generated_bytes([6; 32]),
component_spec: "projects".parse().expect("Component Spec"),
spec_hash: [7; 32],
role: CanisterRole::new("project_hub"),
placement_subnet: subnet,
fleet_subnet_root: root,
canister_id: Principal::from_slice(&[8; 29]),
};
ComponentRuntimeDirectoryAuthority {
fleet: FleetDirectorySnapshot {
provenance: FleetDirectoryProvenance {
registry: FleetRegistryVersion {
authority,
revision: 9,
content_hash: [10; 32],
},
source_fleet_subnet_root: root,
},
fleet_subnet_roots: vec![FleetSubnetRootDirectoryEntry {
placement_subnet: subnet,
fleet_subnet_root: root,
status: FleetSubnetRootStatus::Active,
}],
},
component: ComponentDirectoryHead {
provenance: ComponentDirectoryProvenance {
component,
source_fleet_subnet_root: root,
component_registry_revision: 1,
component_registry_content_hash: [11; 32],
synchronized_at_ns: 12,
},
descendant_count: 0,
},
}
}
}