canic_core/ops/runtime/
install_source.rs1use crate::{
8 InternalError, InternalErrorOrigin,
9 cdk::types::Principal,
10 domain::metrics::{
11 WasmStoreMetricOperation, WasmStoreMetricOutcome, WasmStoreMetricReason,
12 WasmStoreMetricSource,
13 },
14 format::byte_size,
15 ids::CanisterRole,
16 ops::runtime::metrics::wasm_store::WasmStoreMetrics,
17};
18use async_trait::async_trait;
19use std::sync::OnceLock;
20
21#[derive(Clone, Debug, Eq, PartialEq)]
28pub struct ApprovedModuleSource {
29 source_canister: Principal,
30 source_label: String,
31 module_hash: Vec<u8>,
32 chunk_hashes: Vec<Vec<u8>>,
33 payload_size_bytes: u64,
34}
35
36impl ApprovedModuleSource {
37 #[must_use]
39 pub const fn chunked(
40 source_canister: Principal,
41 source_label: String,
42 module_hash: Vec<u8>,
43 chunk_hashes: Vec<Vec<u8>>,
44 payload_size_bytes: u64,
45 ) -> Self {
46 Self {
47 source_canister,
48 source_label,
49 module_hash,
50 chunk_hashes,
51 payload_size_bytes,
52 }
53 }
54
55 #[must_use]
57 pub const fn source_canister(&self) -> &Principal {
58 &self.source_canister
59 }
60
61 #[must_use]
63 pub fn source_label(&self) -> &str {
64 &self.source_label
65 }
66
67 #[must_use]
69 pub fn module_hash(&self) -> &[u8] {
70 &self.module_hash
71 }
72
73 #[must_use]
75 pub fn payload_size(&self) -> String {
76 byte_size(self.payload_size_bytes)
77 }
78
79 #[must_use]
81 pub const fn payload_size_bytes(&self) -> u64 {
82 self.payload_size_bytes
83 }
84
85 #[must_use]
87 pub fn chunk_hashes(&self) -> &[Vec<u8>] {
88 &self.chunk_hashes
89 }
90
91 #[must_use]
93 pub const fn chunk_count(&self) -> usize {
94 self.chunk_hashes.len()
95 }
96}
97
98#[async_trait]
105pub trait ModuleSourceResolver: Send + Sync {
106 async fn approved_module_source(
108 &self,
109 role: &CanisterRole,
110 ) -> Result<ApprovedModuleSource, InternalError>;
111}
112
113static MODULE_SOURCE_RESOLVER: OnceLock<&'static dyn ModuleSourceResolver> = OnceLock::new();
114
115pub struct ModuleSourceRuntimeApi;
122
123impl ModuleSourceRuntimeApi {
124 pub fn register_module_source_resolver(resolver: &'static dyn ModuleSourceResolver) {
126 let _ = MODULE_SOURCE_RESOLVER.set(resolver);
127 }
128
129 pub(crate) async fn approved_module_source(
131 role: &CanisterRole,
132 ) -> Result<ApprovedModuleSource, InternalError> {
133 let resolver = MODULE_SOURCE_RESOLVER.get().ok_or_else(|| {
134 WasmStoreMetrics::record(
135 WasmStoreMetricOperation::SourceResolve,
136 WasmStoreMetricSource::Resolver,
137 WasmStoreMetricOutcome::Failed,
138 WasmStoreMetricReason::InvalidState,
139 );
140 InternalError::workflow(
141 InternalErrorOrigin::Workflow,
142 "module source resolver is not registered; root/control-plane install flows are unavailable".to_string(),
143 )
144 })?;
145
146 match resolver.approved_module_source(role).await {
147 Ok(source) => {
148 WasmStoreMetrics::record(
149 WasmStoreMetricOperation::SourceResolve,
150 WasmStoreMetricSource::Resolver,
151 WasmStoreMetricOutcome::Completed,
152 WasmStoreMetricReason::Ok,
153 );
154 Ok(source)
155 }
156 Err(err) => {
157 WasmStoreMetrics::record(
158 WasmStoreMetricOperation::SourceResolve,
159 WasmStoreMetricSource::Resolver,
160 WasmStoreMetricOutcome::Failed,
161 WasmStoreMetricReason::StoreCall,
162 );
163 Err(err)
164 }
165 }
166 }
167}