use crate::{
InternalError, InternalErrorOrigin,
cdk::types::Principal,
domain::metrics::{
WasmStoreMetricOperation, WasmStoreMetricOutcome, WasmStoreMetricReason,
WasmStoreMetricSource,
},
format::byte_size,
ids::CanisterRole,
ops::runtime::metrics::wasm_store::WasmStoreMetrics,
};
use async_trait::async_trait;
use std::sync::OnceLock;
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct ApprovedModuleSource {
source_canister: Principal,
source_label: String,
module_hash: Vec<u8>,
chunk_hashes: Vec<Vec<u8>>,
payload_size_bytes: u64,
}
impl ApprovedModuleSource {
#[must_use]
pub const fn chunked(
source_canister: Principal,
source_label: String,
module_hash: Vec<u8>,
chunk_hashes: Vec<Vec<u8>>,
payload_size_bytes: u64,
) -> Self {
Self {
source_canister,
source_label,
module_hash,
chunk_hashes,
payload_size_bytes,
}
}
#[must_use]
pub const fn source_canister(&self) -> &Principal {
&self.source_canister
}
#[must_use]
pub fn source_label(&self) -> &str {
&self.source_label
}
#[must_use]
pub fn module_hash(&self) -> &[u8] {
&self.module_hash
}
#[must_use]
pub fn payload_size(&self) -> String {
byte_size(self.payload_size_bytes)
}
#[must_use]
pub const fn payload_size_bytes(&self) -> u64 {
self.payload_size_bytes
}
#[must_use]
pub fn chunk_hashes(&self) -> &[Vec<u8>] {
&self.chunk_hashes
}
#[must_use]
pub const fn chunk_count(&self) -> usize {
self.chunk_hashes.len()
}
}
#[async_trait]
pub trait ModuleSourceResolver: Send + Sync {
async fn approved_module_source(
&self,
role: &CanisterRole,
) -> Result<ApprovedModuleSource, InternalError>;
}
static MODULE_SOURCE_RESOLVER: OnceLock<&'static dyn ModuleSourceResolver> = OnceLock::new();
pub struct ModuleSourceRuntimeApi;
impl ModuleSourceRuntimeApi {
pub fn register_module_source_resolver(resolver: &'static dyn ModuleSourceResolver) {
let _ = MODULE_SOURCE_RESOLVER.set(resolver);
}
pub(crate) async fn approved_module_source(
role: &CanisterRole,
) -> Result<ApprovedModuleSource, InternalError> {
let resolver = MODULE_SOURCE_RESOLVER.get().ok_or_else(|| {
WasmStoreMetrics::record(
WasmStoreMetricOperation::SourceResolve,
WasmStoreMetricSource::Resolver,
WasmStoreMetricOutcome::Failed,
WasmStoreMetricReason::InvalidState,
);
InternalError::workflow(
InternalErrorOrigin::Workflow,
"module source resolver is not registered; root/control-plane install flows are unavailable".to_string(),
)
})?;
match resolver.approved_module_source(role).await {
Ok(source) => {
WasmStoreMetrics::record(
WasmStoreMetricOperation::SourceResolve,
WasmStoreMetricSource::Resolver,
WasmStoreMetricOutcome::Completed,
WasmStoreMetricReason::Ok,
);
Ok(source)
}
Err(err) => {
WasmStoreMetrics::record(
WasmStoreMetricOperation::SourceResolve,
WasmStoreMetricSource::Resolver,
WasmStoreMetricOutcome::Failed,
WasmStoreMetricReason::StoreCall,
);
Err(err)
}
}
}
}