#[cfg(test)]
mod tests;
use crate::{
InternalError,
config::ComponentTopology,
dto::fleet_registry::{
FleetComponentSpecEntry, FleetRegistry, FleetRegistryManifest, FleetRegistryVersion,
FleetSubnetRootEntry, FleetSubnetRootStatus,
},
ids::{AppId, ComponentSpecAdmission, FleetRegistryAuthority, FleetSubnetRootLimits},
model::fleet_registry::{self, FleetRegistryModelError},
ops::OpsError,
};
use sha2::{Digest, Sha256};
use thiserror::Error as ThisError;
const FLEET_REGISTRY_DOMAIN: &[u8] = b"canic/fleet-registry/v1";
const FLEET_REGISTRY_SCHEMA_VERSION: u32 = 1;
pub const MAX_FLEET_REGISTRY_CANONICAL_BYTES: usize = 2_097_152;
#[derive(Debug, ThisError)]
pub enum FleetRegistryOpsError {
#[error("Fleet Registry canonical bytes exceed bound {maximum_bytes}: {actual_bytes}")]
CanonicalBytesExceeded {
actual_bytes: usize,
maximum_bytes: usize,
},
#[error(transparent)]
Model(#[from] FleetRegistryModelError),
}
pub struct FleetRegistryOps;
impl FleetRegistryOps {
pub fn compile_genesis(
configured_app: &AppId,
authority: FleetRegistryAuthority,
topology: &ComponentTopology,
) -> Result<FleetRegistry, InternalError> {
fleet_registry::compile_genesis(configured_app, authority, topology)
.map_err(FleetRegistryOpsError::from)
.map_err(OpsError::from)
.map_err(InternalError::from)
}
pub fn validate(
expected_authority: &FleetRegistryAuthority,
topology: &ComponentTopology,
registry: &FleetRegistry,
) -> Result<(), InternalError> {
fleet_registry::validate(expected_authority, topology, registry)
.map_err(FleetRegistryOpsError::from)
.map_err(OpsError::from)
.map_err(InternalError::from)
}
pub fn canonical_bytes(
expected_authority: &FleetRegistryAuthority,
topology: &ComponentTopology,
registry: &FleetRegistry,
) -> Result<Vec<u8>, InternalError> {
canonical_bytes(expected_authority, topology, registry)
.map_err(OpsError::from)
.map_err(InternalError::from)
}
pub fn manifest(
expected_authority: &FleetRegistryAuthority,
topology: &ComponentTopology,
registry: &FleetRegistry,
) -> Result<FleetRegistryManifest, InternalError> {
let bytes = canonical_bytes(expected_authority, topology, registry)
.map_err(OpsError::from)
.map_err(InternalError::from)?;
Ok(FleetRegistryManifest {
authority: registry.authority.clone(),
revision: registry.revision,
byte_length: bytes.len() as u64,
content_hash: Sha256::digest(bytes).into(),
})
}
pub fn version(
expected_authority: &FleetRegistryAuthority,
topology: &ComponentTopology,
registry: &FleetRegistry,
) -> Result<FleetRegistryVersion, InternalError> {
let manifest = Self::manifest(expected_authority, topology, registry)?;
Ok(FleetRegistryVersion {
authority: manifest.authority,
revision: manifest.revision,
content_hash: manifest.content_hash,
})
}
}
fn canonical_bytes(
expected_authority: &FleetRegistryAuthority,
topology: &ComponentTopology,
registry: &FleetRegistry,
) -> Result<Vec<u8>, FleetRegistryOpsError> {
fleet_registry::validate(expected_authority, topology, registry)?;
let mut encoder = CanonicalEncoder::new();
encode_authority(&mut encoder, ®istry.authority);
encoder.u64(registry.revision);
encoder.u64(registry.component_specs.len() as u64);
for component_spec in ®istry.component_specs {
encode_component_spec(&mut encoder, component_spec);
}
encoder.u64(registry.fleet_subnet_roots.len() as u64);
for root in ®istry.fleet_subnet_roots {
encode_root(&mut encoder, root);
}
encoder.finish()
}
fn encode_authority(encoder: &mut CanonicalEncoder, authority: &FleetRegistryAuthority) {
let binding = &authority.binding;
encoder.bytes(binding.fleet.fleet.canonical_network_id.as_bytes());
encoder.bytes(binding.fleet.fleet.fleet_id.as_bytes());
encoder.string(binding.fleet.app.as_str());
encoder.bytes(binding.coordinator_subnet.as_principal().as_slice());
encoder.bytes(binding.coordinator.as_slice());
encoder.u64(authority.epoch);
}
fn encode_component_spec(encoder: &mut CanonicalEncoder, entry: &FleetComponentSpecEntry) {
encoder.string(entry.component_spec.as_str());
encoder.bytes(&entry.spec_hash);
encoder.string(entry.component_role.as_str());
encoder.u32(entry.maximum_fleet_instances);
}
fn encode_root(encoder: &mut CanonicalEncoder, root: &FleetSubnetRootEntry) {
encoder.bytes(root.placement_subnet.as_principal().as_slice());
encoder.bytes(root.fleet_subnet_root.as_slice());
encoder.u64(root.component_admissions.len() as u64);
for admission in &root.component_admissions {
encode_admission(encoder, admission);
}
encoder.bytes(root.component_topology_digest.as_bytes());
encoder.bytes(root.active_release_set.release_build_id.as_bytes());
encoder.bytes(root.active_release_set.manifest_digest.as_bytes());
encode_limits(encoder, &root.limits);
encoder.u8(status_tag(root.status));
}
fn encode_admission(encoder: &mut CanonicalEncoder, admission: &ComponentSpecAdmission) {
encoder.string(admission.component_spec.as_str());
encoder.bytes(&admission.spec_hash);
encoder.u32(admission.maximum_root_instances);
}
fn encode_limits(encoder: &mut CanonicalEncoder, limits: &FleetSubnetRootLimits) {
encoder.u32(limits.maximum_component_instances);
encoder.u32(limits.maximum_managed_canisters);
encoder.u64(limits.maximum_registry_bytes);
encoder.u64(limits.maximum_wasm_store_bytes);
encoder.u64(limits.cycles_funding.window_secs);
encoder.u128(limits.cycles_funding.maximum_cycles.to_u128());
}
const fn status_tag(status: FleetSubnetRootStatus) -> u8 {
match status {
FleetSubnetRootStatus::Joining => 0,
FleetSubnetRootStatus::Active => 1,
FleetSubnetRootStatus::Draining => 2,
FleetSubnetRootStatus::Removed => 3,
}
}
struct CanonicalEncoder {
bytes: Vec<u8>,
}
impl CanonicalEncoder {
fn new() -> Self {
let mut encoder = Self { bytes: Vec::new() };
encoder.bytes(FLEET_REGISTRY_DOMAIN);
encoder.u32(FLEET_REGISTRY_SCHEMA_VERSION);
encoder
}
fn bytes(&mut self, value: &[u8]) {
self.u64(value.len() as u64);
self.bytes.extend_from_slice(value);
}
fn string(&mut self, value: &str) {
self.bytes(value.as_bytes());
}
fn u8(&mut self, value: u8) {
self.bytes.push(value);
}
fn u32(&mut self, value: u32) {
self.bytes.extend_from_slice(&value.to_be_bytes());
}
fn u64(&mut self, value: u64) {
self.bytes.extend_from_slice(&value.to_be_bytes());
}
fn u128(&mut self, value: u128) {
self.bytes.extend_from_slice(&value.to_be_bytes());
}
fn finish(self) -> Result<Vec<u8>, FleetRegistryOpsError> {
if self.bytes.len() > MAX_FLEET_REGISTRY_CANONICAL_BYTES {
return Err(FleetRegistryOpsError::CanonicalBytesExceeded {
actual_bytes: self.bytes.len(),
maximum_bytes: MAX_FLEET_REGISTRY_CANONICAL_BYTES,
});
}
Ok(self.bytes)
}
}