1use crate::{
2 cdk::types::Principal,
3 dto::{
4 error::Error,
5 template::{
6 TemplateChunkInput, TemplateChunkResponse, TemplateChunkSetInfoResponse,
7 TemplateChunkSetPrepareInput, TemplateManifestInput, WasmStoreAdminCommand,
8 WasmStoreAdminResponse, WasmStoreBootstrapDebugResponse, WasmStoreCatalogEntryResponse,
9 WasmStoreOverviewResponse, WasmStorePublicationSlotResponse, WasmStoreStatusResponse,
10 },
11 },
12 ids::{CanisterRole, TemplateId, TemplateVersion, WasmStoreBinding, WasmStoreGcStatus},
13 ops::{
14 config::ConfigOps,
15 ic::IcOps,
16 storage::{
17 state::subnet::SubnetStateOps,
18 template::{TemplateManifestOps, WasmStoreGcExecutionStats, WasmStoreLimits},
19 },
20 },
21 workflow::runtime::template::WasmStorePublicationWorkflow,
22};
23
24const ROOT_WASM_STORE_BOOTSTRAP_TEMPLATE_ID: TemplateId = TemplateId::new("embedded:wasm_store");
25const ROOT_WASM_STORE_BOOTSTRAP_BINDING: WasmStoreBinding = WasmStoreBinding::new("bootstrap");
26
27pub struct EmbeddedTemplateApi;
32
33impl EmbeddedTemplateApi {
34 pub fn import_embedded_release_set(wasms: &'static [(CanisterRole, &[u8])]) {
36 WasmStorePublicationWorkflow::import_embedded_release_set(wasms);
37 }
38}
39
40pub struct WasmStoreBootstrapApi;
45
46impl WasmStoreBootstrapApi {
47 fn ensure_root_wasm_store_bootstrap_template(template_id: &TemplateId) -> Result<(), Error> {
49 if template_id == &ROOT_WASM_STORE_BOOTSTRAP_TEMPLATE_ID {
50 Ok(())
51 } else {
52 Err(Error::invalid(format!(
53 "bootstrap only accepts template '{ROOT_WASM_STORE_BOOTSTRAP_TEMPLATE_ID}'"
54 )))
55 }
56 }
57
58 fn normalize_root_wasm_store_bootstrap_manifest(
60 request: TemplateManifestInput,
61 ) -> Result<TemplateManifestInput, Error> {
62 if request.role != CanisterRole::WASM_STORE {
63 return Err(Error::invalid(format!(
64 "bootstrap only accepts role '{}'",
65 CanisterRole::WASM_STORE
66 )));
67 }
68
69 Self::ensure_root_wasm_store_bootstrap_template(&request.template_id)?;
70
71 let now_secs = IcOps::now_secs();
72
73 Ok(TemplateManifestInput {
74 template_id: ROOT_WASM_STORE_BOOTSTRAP_TEMPLATE_ID,
75 role: CanisterRole::WASM_STORE,
76 version: request.version,
77 payload_hash: request.payload_hash,
78 payload_size_bytes: request.payload_size_bytes,
79 store_binding: ROOT_WASM_STORE_BOOTSTRAP_BINDING,
80 chunking_mode: crate::ids::TemplateChunkingMode::Chunked,
81 manifest_state: crate::ids::TemplateManifestState::Approved,
82 approved_at: Some(now_secs),
83 created_at: now_secs,
84 })
85 }
86
87 pub fn stage_root_wasm_store_manifest(request: TemplateManifestInput) -> Result<(), Error> {
89 Self::stage_manifest(Self::normalize_root_wasm_store_bootstrap_manifest(request)?);
90 Ok(())
91 }
92
93 pub fn prepare_root_wasm_store_chunk_set(
95 request: TemplateChunkSetPrepareInput,
96 ) -> Result<TemplateChunkSetInfoResponse, Error> {
97 Self::ensure_root_wasm_store_bootstrap_template(&request.template_id)?;
98 Self::prepare_chunk_set(request)
99 }
100
101 pub fn publish_root_wasm_store_chunk(request: TemplateChunkInput) -> Result<(), Error> {
103 Self::ensure_root_wasm_store_bootstrap_template(&request.template_id)?;
104 Self::publish_chunk(request)
105 }
106
107 pub fn stage_manifest(input: TemplateManifestInput) {
109 TemplateManifestOps::replace_approved_from_input(input);
110 }
111
112 pub fn prepare_chunk_set(
114 request: TemplateChunkSetPrepareInput,
115 ) -> Result<TemplateChunkSetInfoResponse, Error> {
116 let now_secs = IcOps::now_secs();
117 TemplateManifestOps::prepare_chunk_set_from_input(request, now_secs).map_err(Error::from)
118 }
119
120 pub fn publish_chunk(request: TemplateChunkInput) -> Result<(), Error> {
122 TemplateManifestOps::publish_chunk_from_input(request).map_err(Error::from)
123 }
124
125 pub async fn publish_staged_release_set_to_current_store() -> Result<(), Error> {
127 WasmStorePublicationWorkflow::publish_staged_release_set_to_current_store()
128 .await
129 .map_err(Error::from)
130 }
131
132 pub fn debug_bootstrap() -> Result<WasmStoreBootstrapDebugResponse, Error> {
134 TemplateManifestOps::bootstrap_debug_response(&CanisterRole::WASM_STORE)
135 .map_err(Error::from)
136 }
137}
138
139pub struct WasmStorePublicationApi;
144
145impl WasmStorePublicationApi {
146 fn current_overview_store_limits() -> WasmStoreLimits {
147 let store = ConfigOps::current_subnet_default_wasm_store();
148
149 WasmStoreLimits {
150 max_store_bytes: store.max_store_bytes(),
151 max_templates: store.max_templates(),
152 max_template_versions_per_template: store.max_template_versions_per_template(),
153 }
154 }
155
156 fn current_overview_store_headroom_bytes() -> Option<u64> {
157 ConfigOps::current_subnet_default_wasm_store().headroom_bytes()
158 }
159
160 pub async fn admin(cmd: WasmStoreAdminCommand) -> Result<WasmStoreAdminResponse, Error> {
162 WasmStorePublicationWorkflow::handle_admin(cmd)
163 .await
164 .map_err(Error::from)
165 }
166
167 pub async fn publish_current_release_set_to_store(store_pid: Principal) -> Result<(), Error> {
169 WasmStorePublicationWorkflow::publish_current_release_set_to_store(store_pid)
170 .await
171 .map_err(Error::from)
172 }
173
174 pub async fn publish_current_release_set_to_current_store() -> Result<(), Error> {
176 WasmStorePublicationWorkflow::publish_current_release_set_to_current_store()
177 .await
178 .map_err(Error::from)
179 }
180
181 pub fn set_current_publication_store_binding(binding: WasmStoreBinding) -> Result<(), Error> {
183 WasmStorePublicationWorkflow::set_current_publication_store_binding(binding)
184 .map_err(Error::from)
185 }
186
187 pub fn clear_current_publication_store_binding() {
189 WasmStorePublicationWorkflow::clear_current_publication_store_binding();
190 }
191
192 #[must_use]
194 pub fn retire_detached_publication_store_binding() -> Option<WasmStoreBinding> {
195 WasmStorePublicationWorkflow::retire_detached_publication_store_binding()
196 }
197
198 pub fn overview() -> Result<WasmStoreOverviewResponse, Error> {
200 let publication = SubnetStateOps::publication_store_state_response();
201 let limits = Self::current_overview_store_limits();
202 let headroom_bytes = Self::current_overview_store_headroom_bytes();
203 let stores = SubnetStateOps::wasm_stores()
204 .into_iter()
205 .map(|store| {
206 let publication_slot =
207 if publication.active_binding.as_ref() == Some(&store.binding) {
208 Some(WasmStorePublicationSlotResponse::Active)
209 } else if publication.detached_binding.as_ref() == Some(&store.binding) {
210 Some(WasmStorePublicationSlotResponse::Detached)
211 } else if publication.retired_binding.as_ref() == Some(&store.binding) {
212 Some(WasmStorePublicationSlotResponse::Retired)
213 } else {
214 None
215 };
216
217 TemplateManifestOps::root_store_overview_response(
218 &store.binding,
219 store.pid,
220 store.created_at,
221 limits,
222 headroom_bytes,
223 crate::ids::WasmStoreGcStatus {
224 mode: store.gc.mode,
225 changed_at: store.gc.changed_at,
226 prepared_at: store.gc.prepared_at,
227 started_at: store.gc.started_at,
228 completed_at: store.gc.completed_at,
229 runs_completed: store.gc.runs_completed,
230 },
231 publication_slot,
232 )
233 })
234 .collect();
235
236 Ok(WasmStoreOverviewResponse {
237 publication,
238 stores,
239 })
240 }
241
242 pub async fn prepare_retired_publication_store_for_gc()
244 -> Result<Option<WasmStoreBinding>, Error> {
245 WasmStorePublicationWorkflow::prepare_retired_publication_store_for_gc()
246 .await
247 .map_err(Error::from)
248 }
249
250 pub async fn begin_retired_publication_store_gc() -> Result<Option<WasmStoreBinding>, Error> {
252 WasmStorePublicationWorkflow::begin_retired_publication_store_gc()
253 .await
254 .map_err(Error::from)
255 }
256
257 pub async fn complete_retired_publication_store_gc() -> Result<Option<WasmStoreBinding>, Error>
259 {
260 WasmStorePublicationWorkflow::complete_retired_publication_store_gc()
261 .await
262 .map_err(Error::from)
263 }
264
265 pub async fn finalize_retired_publication_store_binding()
267 -> Result<Option<(WasmStoreBinding, Principal)>, Error> {
268 WasmStorePublicationWorkflow::finalize_retired_publication_store_binding()
269 .await
270 .map_err(Error::from)
271 }
272
273 pub async fn delete_finalized_publication_store(
275 binding: WasmStoreBinding,
276 store_pid: Principal,
277 ) -> Result<(), Error> {
278 WasmStorePublicationWorkflow::delete_finalized_publication_store(binding, store_pid)
279 .await
280 .map_err(Error::from)
281 }
282}
283
284pub struct WasmStoreApi;
289
290impl WasmStoreApi {
291 fn current_store_limits() -> Result<WasmStoreLimits, Error> {
292 let store = ConfigOps::current_wasm_store()?;
293
294 Ok(WasmStoreLimits {
295 max_store_bytes: store.max_store_bytes(),
296 max_templates: store.max_templates(),
297 max_template_versions_per_template: store.max_template_versions_per_template(),
298 })
299 }
300
301 fn current_store_headroom_bytes() -> Result<Option<u64>, Error> {
302 Ok(ConfigOps::current_wasm_store()?.headroom_bytes())
303 }
304
305 pub fn template_catalog() -> Result<Vec<WasmStoreCatalogEntryResponse>, Error> {
307 Ok(TemplateManifestOps::approved_catalog_response())
308 }
309
310 pub fn template_status(gc: WasmStoreGcStatus) -> Result<WasmStoreStatusResponse, Error> {
312 Ok(TemplateManifestOps::store_status_response(
313 Self::current_store_limits()?,
314 Self::current_store_headroom_bytes()?,
315 gc,
316 ))
317 }
318
319 pub fn prepare_chunk_set(
321 request: TemplateChunkSetPrepareInput,
322 ) -> Result<TemplateChunkSetInfoResponse, Error> {
323 let now_secs = IcOps::now_secs();
324 TemplateManifestOps::prepare_chunk_set_in_store_from_input(
325 request,
326 now_secs,
327 Self::current_store_limits()?,
328 )
329 .map_err(Error::from)
330 }
331
332 pub fn publish_chunk(request: TemplateChunkInput) -> Result<(), Error> {
334 TemplateManifestOps::publish_chunk_in_store_from_input(
335 request,
336 Self::current_store_limits()?,
337 )
338 .map_err(Error::from)
339 }
340
341 pub async fn execute_local_store_gc() -> Result<WasmStoreGcExecutionStats, Error> {
343 TemplateManifestOps::execute_local_store_gc()
344 .await
345 .map_err(Error::from)
346 }
347
348 pub fn template_info(
350 template_id: TemplateId,
351 version: TemplateVersion,
352 ) -> Result<TemplateChunkSetInfoResponse, Error> {
353 TemplateManifestOps::chunk_set_info_response(&template_id, &version).map_err(Error::from)
354 }
355
356 pub fn template_chunk(
358 template_id: TemplateId,
359 version: TemplateVersion,
360 chunk_index: u32,
361 ) -> Result<TemplateChunkResponse, Error> {
362 TemplateManifestOps::chunk_response(&template_id, &version, chunk_index)
363 .map_err(Error::from)
364 }
365}