use crate::{
dto::template::{
TemplateChunkInput, TemplateChunkResponse, TemplateChunkSetInfoResponse,
TemplateChunkSetPrepareInput, TemplateManifestInput, WasmStoreAdminCommand,
WasmStoreAdminResponse, WasmStoreBootstrapDebugResponse, WasmStoreCatalogEntryResponse,
WasmStoreOverviewResponse, WasmStorePublicationStatusResponse,
WasmStoreRetiredStoreStatusResponse, WasmStoreStatusResponse,
},
ids::{
CanisterRole, TemplateId, TemplateVersion, WasmStoreBinding, WasmStoreGcMode,
WasmStoreGcStatus,
},
ops::storage::template::WasmStoreGcOps,
support::{self, WasmStoreGcExecutionStats},
};
use canic_core::{
api::runtime::install::ModuleSourceRuntimeApi, bootstrap::EmbeddedRootBootstrapEntry,
cdk::types::Principal, dto::error::Error, log, log::Topic,
};
const ROOT_WASM_STORE_BOOTSTRAP_TEMPLATE_ID: TemplateId = TemplateId::new("embedded:wasm_store");
const ROOT_WASM_STORE_BOOTSTRAP_BINDING: WasmStoreBinding = WasmStoreBinding::new("bootstrap");
pub struct WasmStoreBootstrapApi;
impl WasmStoreBootstrapApi {
pub fn register_embedded_root_wasm_store_release_set(
entries: &'static [EmbeddedRootBootstrapEntry],
) {
let Some(entry) = entries
.iter()
.find(|entry| entry.role == CanisterRole::WASM_STORE.as_str())
else {
return;
};
ModuleSourceRuntimeApi::register_embedded_module_wasm(
CanisterRole::WASM_STORE,
ROOT_WASM_STORE_BOOTSTRAP_TEMPLATE_ID.as_str().to_string(),
entry.wasm_module,
);
}
pub fn log_embedded_root_wasm_store_release_set(
entries: &'static [EmbeddedRootBootstrapEntry],
) {
let Some(entry) = entries
.iter()
.find(|entry| entry.role == CanisterRole::WASM_STORE.as_str())
else {
return;
};
log!(
Topic::Init,
Info,
"ws bootstrap artifact: source_path={} embedded_path={} kind={} bytes={} sha256={} decompressed_bytes={:?} decompressed_sha256={:?}",
entry.artifact_path,
entry.embedded_artifact_path,
entry.artifact_kind,
entry.artifact_size_bytes,
entry.artifact_sha256_hex,
entry.decompressed_size_bytes,
entry.decompressed_sha256_hex,
);
}
fn ensure_root_wasm_store_bootstrap_template(template_id: &TemplateId) -> Result<(), Error> {
if template_id == &ROOT_WASM_STORE_BOOTSTRAP_TEMPLATE_ID {
Ok(())
} else {
Err(Error::invalid(format!(
"bootstrap only accepts template '{ROOT_WASM_STORE_BOOTSTRAP_TEMPLATE_ID}'"
)))
}
}
fn normalize_root_wasm_store_bootstrap_manifest(
request: TemplateManifestInput,
) -> Result<TemplateManifestInput, Error> {
if request.role != CanisterRole::WASM_STORE {
return Err(Error::invalid(format!(
"bootstrap only accepts role '{}'",
CanisterRole::WASM_STORE
)));
}
Self::ensure_root_wasm_store_bootstrap_template(&request.template_id)?;
let now_secs = support::now_secs();
Ok(TemplateManifestInput {
template_id: ROOT_WASM_STORE_BOOTSTRAP_TEMPLATE_ID,
role: CanisterRole::WASM_STORE,
version: request.version,
payload_hash: request.payload_hash,
payload_size_bytes: request.payload_size_bytes,
store_binding: ROOT_WASM_STORE_BOOTSTRAP_BINDING,
chunking_mode: crate::ids::TemplateChunkingMode::Chunked,
manifest_state: crate::ids::TemplateManifestState::Approved,
approved_at: Some(now_secs),
created_at: now_secs,
})
}
pub fn stage_root_wasm_store_manifest(request: TemplateManifestInput) -> Result<(), Error> {
Self::stage_manifest(Self::normalize_root_wasm_store_bootstrap_manifest(request)?);
Ok(())
}
pub fn prepare_root_wasm_store_chunk_set(
request: TemplateChunkSetPrepareInput,
) -> Result<TemplateChunkSetInfoResponse, Error> {
Self::ensure_root_wasm_store_bootstrap_template(&request.template_id)?;
Self::prepare_chunk_set(request)
}
pub fn publish_root_wasm_store_chunk(request: TemplateChunkInput) -> Result<(), Error> {
Self::ensure_root_wasm_store_bootstrap_template(&request.template_id)?;
Self::publish_chunk(request)
}
pub fn stage_manifest(input: TemplateManifestInput) {
support::stage_manifest(input);
}
pub fn prepare_chunk_set(
request: TemplateChunkSetPrepareInput,
) -> Result<TemplateChunkSetInfoResponse, Error> {
support::prepare_chunk_set(request)
}
pub fn publish_chunk(request: TemplateChunkInput) -> Result<(), Error> {
support::publish_chunk(request)
}
pub async fn publish_staged_release_set_to_current_store() -> Result<(), Error> {
support::publish_staged_release_set_to_current_store().await
}
pub fn debug_bootstrap() -> Result<WasmStoreBootstrapDebugResponse, Error> {
support::bootstrap_debug(&CanisterRole::WASM_STORE)
}
}
pub struct WasmStorePublicationApi;
impl WasmStorePublicationApi {
pub async fn admin(cmd: WasmStoreAdminCommand) -> Result<WasmStoreAdminResponse, Error> {
support::publication_admin(cmd).await
}
pub async fn publish_current_release_set_to_store(store_pid: Principal) -> Result<(), Error> {
support::publish_current_release_set_to_store(store_pid).await
}
pub async fn publish_current_release_set_to_current_store() -> Result<(), Error> {
support::publish_current_release_set_to_current_store().await
}
pub fn set_current_publication_store_binding(binding: WasmStoreBinding) -> Result<(), Error> {
support::set_current_publication_store_binding(binding)
}
pub fn clear_current_publication_store_binding() {
support::clear_current_publication_store_binding();
}
#[must_use]
pub fn retire_detached_publication_store_binding() -> Option<WasmStoreBinding> {
support::retire_detached_publication_store_binding()
}
pub fn overview() -> Result<WasmStoreOverviewResponse, Error> {
Ok(support::publication_overview())
}
pub async fn status() -> Result<WasmStorePublicationStatusResponse, Error> {
support::publication_status().await
}
pub async fn retired_store_status() -> Result<Option<WasmStoreRetiredStoreStatusResponse>, Error>
{
support::retired_publication_store_status().await
}
pub async fn prepare_retired_publication_store_for_gc()
-> Result<Option<WasmStoreBinding>, Error> {
support::prepare_retired_publication_store_for_gc().await
}
pub async fn begin_retired_publication_store_gc() -> Result<Option<WasmStoreBinding>, Error> {
support::begin_retired_publication_store_gc().await
}
pub async fn complete_retired_publication_store_gc() -> Result<Option<WasmStoreBinding>, Error>
{
support::complete_retired_publication_store_gc().await
}
pub async fn finalize_retired_publication_store_binding()
-> Result<Option<WasmStoreBinding>, Error> {
support::finalize_retired_publication_store_binding().await
}
pub async fn delete_finalized_publication_store(
binding: WasmStoreBinding,
store_pid: Principal,
) -> Result<(), Error> {
support::delete_finalized_publication_store(binding, store_pid).await
}
}
pub struct WasmStoreApi;
impl WasmStoreApi {
pub fn template_catalog() -> Result<Vec<WasmStoreCatalogEntryResponse>, Error> {
Ok(support::local_template_catalog())
}
pub fn template_status(gc: WasmStoreGcStatus) -> Result<WasmStoreStatusResponse, Error> {
support::local_template_status(gc)
}
pub fn prepare_chunk_set(
request: TemplateChunkSetPrepareInput,
) -> Result<TemplateChunkSetInfoResponse, Error> {
support::local_prepare_chunk_set(request)
}
pub fn stage_manifest(request: TemplateManifestInput) -> Result<(), Error> {
support::local_stage_manifest(request)
}
pub fn publish_chunk(request: TemplateChunkInput) -> Result<(), Error> {
support::local_publish_chunk(request)
}
pub async fn execute_local_store_gc() -> Result<WasmStoreGcExecutionStats, Error> {
support::execute_local_store_gc().await
}
pub fn template_info(
template_id: TemplateId,
version: TemplateVersion,
) -> Result<TemplateChunkSetInfoResponse, Error> {
support::local_template_info(template_id, version)
}
pub fn template_chunk(
template_id: TemplateId,
version: TemplateVersion,
chunk_index: u32,
) -> Result<TemplateChunkResponse, Error> {
support::local_template_chunk(template_id, version, chunk_index)
}
}
pub struct WasmStoreCanisterApi;
impl WasmStoreCanisterApi {
pub fn catalog() -> Result<Vec<WasmStoreCatalogEntryResponse>, Error> {
WasmStoreApi::template_catalog()
}
pub fn prepare(
request: TemplateChunkSetPrepareInput,
) -> Result<TemplateChunkSetInfoResponse, Error> {
WasmStoreApi::prepare_chunk_set(request)
}
pub fn stage_manifest(request: TemplateManifestInput) -> Result<(), Error> {
WasmStoreApi::stage_manifest(request)
}
pub fn publish_chunk(request: TemplateChunkInput) -> Result<(), Error> {
WasmStoreApi::publish_chunk(request)
}
pub fn info(
template_id: TemplateId,
version: TemplateVersion,
) -> Result<TemplateChunkSetInfoResponse, Error> {
WasmStoreApi::template_info(template_id, version)
}
pub fn status() -> Result<WasmStoreStatusResponse, Error> {
WasmStoreApi::template_status(WasmStoreGcOps::snapshot())
}
pub fn prepare_gc() -> Result<(), Error> {
WasmStoreGcOps::prepare(support::now_secs())
}
pub fn begin_gc() -> Result<(), Error> {
WasmStoreGcOps::begin(support::now_secs())
}
pub async fn complete_gc() -> Result<(), Error> {
let now_secs = support::now_secs();
let current = WasmStoreGcOps::status();
if current.mode == WasmStoreGcMode::Complete {
return Ok(());
}
if current.mode != WasmStoreGcMode::InProgress {
return Err(Error::conflict(format!(
"wasm store gc transition {:?} -> Complete is not allowed",
current.mode
)));
}
let stats = WasmStoreApi::execute_local_store_gc().await?;
WasmStoreGcOps::complete(now_secs)?;
log!(
Topic::Wasm,
Ok,
"wasm_store: gc complete reclaimed_bytes={} cleared_templates={} cleared_releases={} cleared_chunks={} cleared_chunk_hashes={}",
stats.reclaimed_store_bytes,
stats.cleared_template_count,
stats.cleared_release_count,
stats.cleared_chunk_count,
stats.cleared_chunk_store_hash_count
);
Ok(())
}
pub fn chunk(
template_id: TemplateId,
version: TemplateVersion,
chunk_index: u32,
) -> Result<TemplateChunkResponse, Error> {
WasmStoreApi::template_chunk(template_id, version, chunk_index)
}
}