1mod default;
2mod diagnostics;
3mod error;
4mod policy;
5
6#[cfg(test)]
7mod tests;
8
9pub use default::{
10 bootstrap_default_memory_manager, bootstrap_default_memory_manager_with_policy,
11 committed_allocations, default_memory_manager_commit_recovery_diagnostic,
12 default_memory_manager_diagnostic_export, default_memory_manager_doctor_report,
13 is_default_memory_manager_bootstrapped, open_default_memory_manager_memory,
14};
15pub use error::{
16 RuntimeBootstrapError, RuntimeConstructionError, RuntimeDiagnosticError, RuntimeOpenError,
17 RuntimePolicyError, RuntimeStateError,
18};
19
20use self::policy::{RuntimeMemoryManagerPolicy, runtime_bootstrap_error_from_bootstrap};
21use crate::{
22 AllocationBootstrap, AllocationHistory, AllocationLedger, AllocationPolicy,
23 CommittedAllocations, RuntimeBootstrapPolicy, STABLE_CELL_VALUE_OFFSET, StableCellLedgerError,
24 StableCellLedgerRecord, StableKey, registry::SealedDeclarationSnapshot,
25 slot::MEMORY_MANAGER_LEDGER_ID, stable_cell::decode_stable_cell_ledger_record_from_memory,
26};
27use ic_stable_structures::{
28 Cell, Memory, Storable,
29 memory_manager::{MemoryId, MemoryManager, VirtualMemory},
30};
31
32type LedgerCell<M> = Cell<StableCellLedgerRecord, VirtualMemory<M>>;
33
34const MEMORY_MANAGER_MAGIC: [u8; 3] = *b"MGR";
38const MEMORY_MANAGER_LAYOUT_VERSION: u8 = 1;
39
40enum RuntimeLifecycle {
41 Unbootstrapped,
42 Bootstrapped {
43 committed_allocations: CommittedAllocations,
44 binding: RuntimeBootstrapBinding,
45 },
46}
47
48struct RuntimeBootstrapBinding {
49 declarations: SealedDeclarationSnapshot,
50 policy_identity: &'static str,
51}
52
53pub struct MemoryRuntime<M: Memory> {
68 memory_manager: MemoryManager<M>,
69 ledger_cell: Option<LedgerCell<M>>,
70 lifecycle: RuntimeLifecycle,
71}
72
73impl<M: Memory> MemoryRuntime<M> {
74 pub fn new(memory: M) -> Result<Self, RuntimeConstructionError> {
90 preflight_memory_manager_backing(&memory)?;
91 Ok(Self {
92 memory_manager: MemoryManager::init(memory),
93 ledger_cell: None,
94 lifecycle: RuntimeLifecycle::Unbootstrapped,
95 })
96 }
97
98 #[must_use]
100 pub const fn is_bootstrapped(&self) -> bool {
101 matches!(self.lifecycle, RuntimeLifecycle::Bootstrapped { .. })
102 }
103
104 pub fn bootstrap<P: RuntimeBootstrapPolicy>(
113 &mut self,
114 declarations: &SealedDeclarationSnapshot,
115 policy: &P,
116 ) -> Result<&CommittedAllocations, RuntimeBootstrapError<P::Error>> {
117 let policy_identity = policy.runtime_bootstrap_identity();
118 if policy_identity.is_empty() {
119 return Err(RuntimeBootstrapError::EmptyPolicyIdentity);
120 }
121 let already_bootstrapped = match &self.lifecycle {
122 RuntimeLifecycle::Unbootstrapped => false,
123 RuntimeLifecycle::Bootstrapped { binding, .. } => {
124 binding.validate(declarations, policy_identity)?;
125 true
126 }
127 };
128 if !already_bootstrapped {
129 self.bootstrap_unbootstrapped(declarations, policy, policy_identity)?;
130 }
131 match &self.lifecycle {
132 RuntimeLifecycle::Bootstrapped {
133 committed_allocations,
134 ..
135 } => Ok(committed_allocations),
136 RuntimeLifecycle::Unbootstrapped => Err(RuntimeBootstrapError::State(
137 RuntimeStateError::InconsistentLifecycle,
138 )),
139 }
140 }
141
142 fn bootstrap_unbootstrapped<P: AllocationPolicy>(
143 &mut self,
144 declarations: &SealedDeclarationSnapshot,
145 policy: &P,
146 policy_identity: &'static str,
147 ) -> Result<(), RuntimeBootstrapError<P::Error>> {
148 self.initialize_ledger_cell()?;
149 let mut record = self
150 .ledger_cell
151 .as_ref()
152 .map(|cell| cell.get().clone())
153 .ok_or(RuntimeStateError::InconsistentLifecycle)?;
154 let runtime_policy = RuntimeMemoryManagerPolicy {
155 declarations,
156 custom_policy: policy,
157 };
158 let genesis = AllocationLedger::new(0, AllocationHistory::default())?;
159 let commit = AllocationBootstrap::new(record.store_mut())
160 .initialize_validate_and_commit(
161 &genesis,
162 declarations.allocation_snapshot().clone(),
163 &runtime_policy,
164 None,
165 )
166 .map_err(runtime_bootstrap_error_from_bootstrap)?;
167 let (ledger, validated) = commit.into_parts();
168
169 self.persist_ledger_record(record)?;
170 let committed =
171 external_runtime_allocations(validated.confirm_persisted(ledger.current_generation()));
172 self.lifecycle = RuntimeLifecycle::Bootstrapped {
173 committed_allocations: committed,
174 binding: RuntimeBootstrapBinding {
175 declarations: declarations.clone(),
176 policy_identity,
177 },
178 };
179 Ok(())
180 }
181
182 pub const fn committed_allocations(&self) -> Result<&CommittedAllocations, RuntimeOpenError> {
184 match &self.lifecycle {
185 RuntimeLifecycle::Unbootstrapped => Err(RuntimeOpenError::NotBootstrapped),
186 RuntimeLifecycle::Bootstrapped {
187 committed_allocations,
188 ..
189 } => Ok(committed_allocations),
190 }
191 }
192
193 pub fn open_memory(
195 &self,
196 stable_key: &str,
197 expected_id: u8,
198 ) -> Result<VirtualMemory<M>, RuntimeOpenError> {
199 let key = StableKey::parse(stable_key)?;
200 if crate::is_ic_memory_stable_key(key.as_str()) {
201 return Err(RuntimeOpenError::ReservedStableKey {
202 stable_key: stable_key.to_string(),
203 });
204 }
205 let committed = self.committed_allocations()?;
206 let slot = committed
207 .slot_for(&key)
208 .ok_or_else(|| RuntimeOpenError::StableKeyNotCommitted(stable_key.to_string()))?;
209 let committed_id = slot.memory_manager_id()?;
210 if committed_id != expected_id {
211 return Err(RuntimeOpenError::MemoryIdMismatch {
212 stable_key: stable_key.to_string(),
213 committed_id,
214 requested_id: expected_id,
215 });
216 }
217 Ok(self.memory(expected_id))
218 }
219
220 fn initialize_ledger_cell<P>(&mut self) -> Result<(), RuntimeBootstrapError<P>> {
221 if self.ledger_cell.is_some() {
222 return Ok(());
223 }
224 let memory = self.memory(MEMORY_MANAGER_LEDGER_ID);
225 crate::validate_stable_cell_ledger_memory(&memory)?;
226 ensure_ledger_cell_capacity(&memory, &StableCellLedgerRecord::default())?;
227 self.ledger_cell = Some(Cell::init(memory, StableCellLedgerRecord::default()));
228 Ok(())
229 }
230
231 fn persist_ledger_record<P>(
232 &mut self,
233 record: StableCellLedgerRecord,
234 ) -> Result<(), RuntimeBootstrapError<P>> {
235 let memory = self.memory(MEMORY_MANAGER_LEDGER_ID);
236 ensure_ledger_cell_capacity(&memory, &record)?;
237 let cell = self
238 .ledger_cell
239 .as_mut()
240 .ok_or(RuntimeStateError::InconsistentLifecycle)?;
241 let _previous = cell.set(record);
242 Ok(())
243 }
244
245 fn memory(&self, id: u8) -> VirtualMemory<M> {
246 self.memory_manager.get(MemoryId::new(id))
247 }
248
249 fn ledger_record_from_memory(&self) -> Result<StableCellLedgerRecord, StableCellLedgerError> {
250 decode_stable_cell_ledger_record_from_memory(&self.memory(MEMORY_MANAGER_LEDGER_ID))
251 }
252}
253
254fn preflight_memory_manager_backing<M: Memory>(memory: &M) -> Result<(), RuntimeConstructionError> {
255 if memory.size() == 0 {
256 return Ok(());
257 }
258
259 let mut prefix = [0_u8; 4];
260 memory.read(0, &mut prefix);
261 let observed_magic = [prefix[0], prefix[1], prefix[2]];
262 if observed_magic != MEMORY_MANAGER_MAGIC {
263 return Err(RuntimeConstructionError::ForeignMemory { observed_magic });
264 }
265 let observed = prefix[3];
266 if observed != MEMORY_MANAGER_LAYOUT_VERSION {
267 return Err(RuntimeConstructionError::UnsupportedMemoryManagerVersion {
268 observed,
269 supported: MEMORY_MANAGER_LAYOUT_VERSION,
270 });
271 }
272 Ok(())
273}
274
275impl RuntimeBootstrapBinding {
276 fn validate<P>(
277 &self,
278 declarations: &SealedDeclarationSnapshot,
279 policy_identity: &'static str,
280 ) -> Result<(), RuntimeBootstrapError<P>> {
281 if !self.declarations.shares_storage_with(declarations) {
282 return Err(RuntimeBootstrapError::DeclarationSnapshotMismatch);
283 }
284 if self.policy_identity != policy_identity {
285 return Err(RuntimeBootstrapError::PolicyIdentityMismatch {
286 established: self.policy_identity,
287 requested: policy_identity,
288 });
289 }
290 Ok(())
291 }
292}
293
294fn ensure_ledger_cell_capacity<M: Memory, P>(
295 memory: &VirtualMemory<M>,
296 record: &StableCellLedgerRecord,
297) -> Result<(), RuntimeBootstrapError<P>> {
298 let value_size = record.to_bytes().len();
299 let value_size_u32 = u32::try_from(value_size)
300 .map_err(|_| RuntimeBootstrapError::StableCellLedgerWriteTooLarge { value_size })?;
301 let required_bytes = STABLE_CELL_VALUE_OFFSET
302 .checked_add(u64::from(value_size_u32))
303 .ok_or(RuntimeBootstrapError::StableCellLedgerWriteTooLarge { value_size })?;
304 let available_bytes = memory.size().saturating_mul(crate::WASM_PAGE_SIZE_BYTES);
305 if required_bytes <= available_bytes {
306 return Ok(());
307 }
308 let grow_by = required_bytes
309 .saturating_sub(available_bytes)
310 .div_ceil(crate::WASM_PAGE_SIZE_BYTES);
311 if memory.grow(grow_by) < 0 {
312 return Err(RuntimeBootstrapError::StableCellLedgerWriteTooLarge { value_size });
313 }
314 Ok(())
315}
316
317fn external_runtime_allocations(committed: CommittedAllocations) -> CommittedAllocations {
318 committed.without_stable_key_prefix(crate::IC_MEMORY_STABLE_KEY_PREFIX)
319}