1use crate::{
2 AllocationBootstrap, AllocationDeclaration, AllocationHistory, AllocationLedger,
3 AllocationPolicy, AllocationSlotDescriptor, DeclarationSnapshot,
4 DefaultMemoryManagerDoctorReport, DiagnosticCheck, DiagnosticDeclaration, DiagnosticExport,
5 DiagnosticMemorySize, DiagnosticRangeAuthority, DiagnosticStableCell,
6 DiagnosticStableCellStatus, LedgerCommitError, StableCellLedgerError, StableCellLedgerRecord,
7 StableKey, ValidatedAllocations, decode_stable_cell_ledger_record, decode_stable_cell_payload,
8 physical::CommitStoreDiagnostic,
9 registry::{
10 StaticMemoryDeclaration, StaticMemoryDeclarationError, StaticMemoryRangeDeclaration,
11 seal_static_memory_registry, static_memory_declarations, static_memory_range_declarations,
12 },
13 slot::{
14 IC_MEMORY_AUTHORITY_OWNER, IC_MEMORY_AUTHORITY_PURPOSE, IC_MEMORY_LEDGER_LABEL,
15 IC_MEMORY_LEDGER_STABLE_KEY, MEMORY_MANAGER_LEDGER_ID, MemoryManagerAuthorityRecord,
16 MemoryManagerIdRange, MemoryManagerRangeAuthority, MemoryManagerRangeAuthorityError,
17 MemoryManagerRangeMode, MemoryManagerSlotError,
18 },
19};
20use ic_stable_structures::{
21 Cell, DefaultMemoryImpl, Memory,
22 memory_manager::{MemoryId, MemoryManager, VirtualMemory},
23};
24use std::{
25 cell::RefCell,
26 collections::BTreeMap,
27 convert::Infallible,
28 sync::{
29 Mutex,
30 atomic::{AtomicBool, Ordering},
31 },
32};
33
34type DefaultLedgerCell = Cell<StableCellLedgerRecord, VirtualMemory<DefaultMemoryImpl>>;
35
36thread_local! {
37 static DEFAULT_MEMORY_MANAGER: MemoryManager<DefaultMemoryImpl> =
38 MemoryManager::init(DefaultMemoryImpl::default());
39 static DEFAULT_LEDGER_CELL: RefCell<Option<DefaultLedgerCell>> = const {
40 RefCell::new(None)
41 };
42}
43
44static EAGER_INIT_HOOKS: Mutex<Vec<fn()>> = Mutex::new(Vec::new());
45static VALIDATED_ALLOCATIONS: Mutex<Option<ValidatedAllocations>> = Mutex::new(None);
46static BOOTSTRAPPED: AtomicBool = AtomicBool::new(false);
47
48#[derive(Debug, thiserror::Error)]
53pub enum RuntimeBootstrapError<P> {
54 #[error(transparent)]
56 Registry(#[from] StaticMemoryDeclarationError),
57 #[error(transparent)]
59 Range(#[from] MemoryManagerRangeAuthorityError),
60 #[error(transparent)]
62 LedgerIntegrity(#[from] crate::LedgerIntegrityError),
63 #[error(transparent)]
65 LedgerCommit(#[from] crate::LedgerCommitError),
66 #[error(transparent)]
68 StableCellLedger(#[from] StableCellLedgerError),
69 #[error(transparent)]
71 Validation(#[from] crate::AllocationValidationError<RuntimePolicyError<P>>),
72 #[error(transparent)]
74 Staging(#[from] crate::AllocationStageError),
75 #[error("ic-memory runtime lock poisoned")]
77 RuntimeLockPoisoned,
78}
79
80#[derive(Clone, Debug, Eq, thiserror::Error, PartialEq)]
85pub enum RuntimeOpenError {
86 #[error("ic-memory runtime has not completed bootstrap validation")]
88 NotBootstrapped,
89 #[error("ic-memory runtime lock poisoned")]
91 RuntimeLockPoisoned,
92 #[error(transparent)]
94 StableKey(#[from] crate::StableKeyError),
95 #[error("stable key '{0}' was not validated by ic-memory runtime bootstrap")]
97 StableKeyNotValidated(String),
98 #[error(transparent)]
100 MemoryManagerSlot(#[from] MemoryManagerSlotError),
101 #[error(
103 "stable key '{stable_key}' is validated for MemoryManager ID {validated_id}, not requested ID {requested_id}"
104 )]
105 MemoryIdMismatch {
106 stable_key: String,
108 validated_id: u8,
110 requested_id: u8,
112 },
113}
114
115#[derive(Debug, thiserror::Error)]
122pub enum RuntimeDiagnosticError {
123 #[error("ic-memory runtime has not completed bootstrap validation")]
125 NotBootstrapped,
126 #[error(transparent)]
128 LedgerCommit(#[from] LedgerCommitError),
129 #[error(transparent)]
131 StableCellLedger(#[from] StableCellLedgerError),
132 #[error(transparent)]
134 MemoryManagerSlot(#[from] MemoryManagerSlotError),
135}
136
137#[derive(Clone, Debug, Eq, thiserror::Error, PartialEq)]
142pub enum RuntimePolicyError<P> {
143 #[error(transparent)]
145 Range(#[from] MemoryManagerRangeAuthorityError),
146 #[error("runtime declaration metadata is missing for stable key '{0}'")]
148 MissingDeclarationMetadata(String),
149 #[error("stable key '{stable_key}' is reserved to authority '{expected_authority}'")]
151 ReservedStableKeyAuthority {
152 stable_key: String,
154 expected_authority: &'static str,
156 },
157 #[error(transparent)]
159 Custom(P),
160}
161
162pub fn defer_eager_init(f: fn()) {
164 assert!(
165 !is_default_memory_manager_bootstrapped(),
166 "ic-memory eager-init registration attempted after runtime bootstrap"
167 );
168 EAGER_INIT_HOOKS
169 .lock()
170 .expect("ic-memory eager-init queue poisoned")
171 .push(f);
172}
173
174#[must_use]
176pub fn is_default_memory_manager_bootstrapped() -> bool {
177 BOOTSTRAPPED.load(Ordering::SeqCst)
178}
179
180pub fn validated_allocations() -> Result<ValidatedAllocations, RuntimeOpenError> {
182 if !is_default_memory_manager_bootstrapped() {
183 return Err(RuntimeOpenError::NotBootstrapped);
184 }
185 VALIDATED_ALLOCATIONS
186 .lock()
187 .map_err(|_| RuntimeOpenError::RuntimeLockPoisoned)?
188 .clone()
189 .ok_or(RuntimeOpenError::NotBootstrapped)
190}
191
192pub fn bootstrap_default_memory_manager()
194-> Result<ValidatedAllocations, RuntimeBootstrapError<Infallible>> {
195 bootstrap_default_memory_manager_with_policy(&NoopPolicy)
196}
197
198pub fn bootstrap_default_memory_manager_with_policy<P: AllocationPolicy>(
213 policy: &P,
214) -> Result<ValidatedAllocations, RuntimeBootstrapError<P::Error>> {
215 if let Ok(validated) = validated_allocations() {
216 return Ok(validated);
217 }
218
219 run_eager_init_hooks();
220
221 let registered_declarations = static_memory_declarations()?;
222 let registered_ranges = static_memory_range_declarations()?;
223 let user_ranges_registered = !registered_ranges.is_empty();
224 let declaration_metadata = declaration_metadata(®istered_declarations);
225 let range_authority = range_authority(registered_ranges)?;
226 let snapshot = declaration_snapshot(registered_declarations)?;
227 seal_static_memory_registry()?;
228 let policy = RuntimeMemoryManagerPolicy {
229 range_authority,
230 user_ranges_registered,
231 declaration_metadata,
232 custom_policy: policy,
233 };
234 let genesis = AllocationLedger::new(0, AllocationHistory::default())?;
235
236 let validated = with_default_ledger_cell(
237 |cell| -> Result<ValidatedAllocations, RuntimeBootstrapError<P::Error>> {
238 let mut record = cell.get().clone();
239 let mut bootstrap = AllocationBootstrap::new(record.store_mut());
240 let commit = bootstrap
241 .initialize_validate_and_commit(&genesis, snapshot, &policy, None)
242 .map_err(runtime_bootstrap_error_from_bootstrap)?;
243 cell.set(record);
244 Ok(commit.validated)
245 },
246 )?;
247
248 publish_validated_allocations(validated.clone())?;
249 BOOTSTRAPPED.store(true, Ordering::SeqCst);
250 Ok(validated)
251}
252
253pub fn open_default_memory_manager_memory(
255 stable_key: &str,
256 id: u8,
257) -> Result<VirtualMemory<DefaultMemoryImpl>, RuntimeOpenError> {
258 let key = StableKey::parse(stable_key)?;
259 let validated = validated_allocations()?;
260 let slot = validated
261 .slot_for(&key)
262 .ok_or_else(|| RuntimeOpenError::StableKeyNotValidated(stable_key.to_string()))?;
263 let validated_id = slot.memory_manager_id()?;
264 if validated_id != id {
265 return Err(RuntimeOpenError::MemoryIdMismatch {
266 stable_key: stable_key.to_string(),
267 validated_id,
268 requested_id: id,
269 });
270 }
271 Ok(default_memory_manager_memory(id))
272}
273
274pub fn default_memory_manager_diagnostic_export() -> Result<DiagnosticExport, RuntimeDiagnosticError>
281{
282 let record = default_ledger_record_for_diagnostics()?;
283 let recovered = record.store().recover()?;
284 let ledger = recovered.ledger();
285 let memory_sizes = default_memory_manager_memory_sizes(ledger)?;
286
287 Ok(
288 DiagnosticExport::from_ledger_with_commit_recovery_and_memory_sizes(
289 ledger,
290 AllocationSlotDescriptor::memory_manager(MEMORY_MANAGER_LEDGER_ID)?,
291 Some(record.store().physical().diagnostic()),
292 memory_sizes,
293 ),
294 )
295}
296
297pub fn default_memory_manager_commit_recovery_diagnostic()
304-> Result<CommitStoreDiagnostic, RuntimeDiagnosticError> {
305 let record = default_ledger_record_from_memory()?;
306 Ok(record.store().physical().diagnostic())
307}
308
309#[must_use]
317pub fn default_memory_manager_doctor_report() -> DefaultMemoryManagerDoctorReport {
318 if !is_default_memory_manager_bootstrapped() {
319 run_eager_init_hooks();
320 }
321
322 let stable_cell = default_memory_manager_stable_cell_diagnostic();
323 let commit_recovery = stable_cell
324 .record
325 .as_ref()
326 .map(|record| record.store().physical().diagnostic());
327 let recovered = stable_cell
328 .record
329 .as_ref()
330 .map(|record| record.store().recover());
331 let recovered_for_export = recovered.as_ref().and_then(|result| result.as_ref().ok());
332 let ledger_anchor = default_ledger_anchor_descriptor();
333 let ledger = recovered_for_export.map(|recovered| {
334 DiagnosticExport::from_ledger_with_commit_recovery_and_memory_sizes(
335 recovered.ledger(),
336 ledger_anchor.clone(),
337 commit_recovery,
338 default_memory_manager_memory_sizes_lossy(recovered.ledger()),
339 )
340 });
341
342 let registered_declarations = static_memory_declarations();
343 let registered_ranges = static_memory_range_declarations();
344 let diagnostic_declarations = registered_declarations
345 .as_ref()
346 .map(|declarations| {
347 declarations
348 .iter()
349 .map(|registration| {
350 DiagnosticDeclaration::new(
351 registration.declaring_crate(),
352 registration.declaration().clone(),
353 )
354 })
355 .collect()
356 })
357 .unwrap_or_default();
358 let range_authority = diagnostic_range_authority(®istered_ranges);
359 let validation = diagnostic_validation(
360 ®istered_declarations,
361 ®istered_ranges,
362 stable_cell.record.as_ref(),
363 recovered.as_ref(),
364 );
365
366 DefaultMemoryManagerDoctorReport {
367 bootstrapped: is_default_memory_manager_bootstrapped(),
368 ledger_anchor,
369 stable_cell: stable_cell.diagnostic,
370 commit_recovery,
371 ledger,
372 registered_declarations: diagnostic_declarations,
373 range_authority,
374 validation,
375 }
376}
377
378fn run_eager_init_hooks() {
379 let hooks = {
380 let mut hooks = EAGER_INIT_HOOKS
381 .lock()
382 .expect("ic-memory eager-init queue poisoned");
383 std::mem::take(&mut *hooks)
384 };
385
386 for hook in hooks {
387 hook();
388 }
389}
390
391fn with_default_ledger_cell<P, T>(
392 op: impl FnOnce(&mut DefaultLedgerCell) -> Result<T, RuntimeBootstrapError<P>>,
393) -> Result<T, RuntimeBootstrapError<P>> {
394 DEFAULT_LEDGER_CELL.with(|cell| {
395 let mut cell = cell.borrow_mut();
396 if cell.is_none() {
397 let memory = default_memory_manager_memory(MEMORY_MANAGER_LEDGER_ID);
398 crate::validate_stable_cell_ledger_memory(&memory)?;
399 *cell = Some(Cell::init(memory, StableCellLedgerRecord::default()));
400 }
401 op(cell.as_mut().expect("default ledger cell initialized"))
402 })
403}
404
405fn default_memory_manager_memory(id: u8) -> VirtualMemory<DefaultMemoryImpl> {
406 DEFAULT_MEMORY_MANAGER.with(|manager| manager.get(MemoryId::new(id)))
407}
408
409const fn default_ledger_anchor_descriptor() -> AllocationSlotDescriptor {
410 AllocationSlotDescriptor::memory_manager_unchecked(MEMORY_MANAGER_LEDGER_ID)
411}
412
413fn default_ledger_record_for_diagnostics() -> Result<StableCellLedgerRecord, RuntimeDiagnosticError>
414{
415 if !is_default_memory_manager_bootstrapped() {
416 return Err(RuntimeDiagnosticError::NotBootstrapped);
417 }
418
419 default_ledger_record_from_memory().map_err(RuntimeDiagnosticError::StableCellLedger)
420}
421
422fn default_ledger_record_from_memory() -> Result<StableCellLedgerRecord, StableCellLedgerError> {
423 let memory = default_memory_manager_memory(MEMORY_MANAGER_LEDGER_ID);
424 if memory.size() == 0 {
425 return Ok(StableCellLedgerRecord::default());
426 }
427
428 let payload = decode_stable_cell_payload(&memory)?;
429 decode_stable_cell_ledger_record(&payload).map_err(StableCellLedgerError::Record)
430}
431
432fn default_memory_manager_memory_sizes(
433 ledger: &AllocationLedger,
434) -> Result<Vec<(AllocationSlotDescriptor, DiagnosticMemorySize)>, RuntimeDiagnosticError> {
435 ledger
436 .allocation_history()
437 .records()
438 .iter()
439 .map(|record| {
440 let id = record.slot().memory_manager_id()?;
441 let memory = default_memory_manager_memory(id);
442 Ok((
443 record.slot().clone(),
444 DiagnosticMemorySize::from_wasm_pages(memory.size()),
445 ))
446 })
447 .collect()
448}
449
450fn default_memory_manager_memory_sizes_lossy(
451 ledger: &AllocationLedger,
452) -> Vec<(AllocationSlotDescriptor, DiagnosticMemorySize)> {
453 default_memory_manager_memory_sizes(ledger).unwrap_or_default()
454}
455
456struct DefaultStableCellDiagnostic {
457 diagnostic: DiagnosticStableCell,
458 record: Option<StableCellLedgerRecord>,
459}
460
461fn default_memory_manager_stable_cell_diagnostic() -> DefaultStableCellDiagnostic {
462 let memory = default_memory_manager_memory(MEMORY_MANAGER_LEDGER_ID);
463 let memory_size = DiagnosticMemorySize::from_wasm_pages(memory.size());
464 if memory.size() == 0 {
465 return DefaultStableCellDiagnostic {
466 diagnostic: DiagnosticStableCell::new(
467 DiagnosticStableCellStatus::Empty,
468 memory_size,
469 None,
470 ),
471 record: Some(StableCellLedgerRecord::default()),
472 };
473 }
474
475 let record = decode_stable_cell_payload(&memory)
476 .map_err(StableCellLedgerError::Payload)
477 .and_then(|payload| {
478 decode_stable_cell_ledger_record(&payload).map_err(StableCellLedgerError::Record)
479 });
480 match record {
481 Ok(record) => DefaultStableCellDiagnostic {
482 diagnostic: DiagnosticStableCell::new(
483 DiagnosticStableCellStatus::Readable,
484 memory_size,
485 None,
486 ),
487 record: Some(record),
488 },
489 Err(err) => DefaultStableCellDiagnostic {
490 diagnostic: DiagnosticStableCell::new(
491 DiagnosticStableCellStatus::Corrupt,
492 memory_size,
493 Some(err.to_string()),
494 ),
495 record: None,
496 },
497 }
498}
499
500fn diagnostic_range_authority(
501 registered_ranges: &Result<Vec<StaticMemoryRangeDeclaration>, StaticMemoryDeclarationError>,
502) -> DiagnosticRangeAuthority {
503 match registered_ranges {
504 Ok(ranges) => {
505 let registered_records = ranges
506 .iter()
507 .map(|registration| registration.record().clone())
508 .collect();
509 match range_authority(ranges.clone()) {
510 Ok(authority) => {
511 DiagnosticRangeAuthority::new(registered_records, Some(authority), None)
512 }
513 Err(err) => {
514 DiagnosticRangeAuthority::new(registered_records, None, Some(err.to_string()))
515 }
516 }
517 }
518 Err(err) => DiagnosticRangeAuthority::new(Vec::new(), None, Some(err.to_string())),
519 }
520}
521
522fn diagnostic_validation(
523 registered_declarations: &Result<Vec<StaticMemoryDeclaration>, StaticMemoryDeclarationError>,
524 registered_ranges: &Result<Vec<StaticMemoryRangeDeclaration>, StaticMemoryDeclarationError>,
525 stable_cell_record: Option<&StableCellLedgerRecord>,
526 recovered: Option<&Result<crate::RecoveredLedger, LedgerCommitError>>,
527) -> DiagnosticCheck {
528 let registered_declarations = match registered_declarations {
529 Ok(declarations) => declarations.clone(),
530 Err(err) => return DiagnosticCheck::failed(format!("declaration registry: {err}")),
531 };
532 let registered_ranges = match registered_ranges {
533 Ok(ranges) => ranges.clone(),
534 Err(err) => return DiagnosticCheck::failed(format!("range registry: {err}")),
535 };
536 let range_authority = match range_authority(registered_ranges.clone()) {
537 Ok(authority) => authority,
538 Err(err) => return DiagnosticCheck::failed(format!("range authority: {err}")),
539 };
540 let snapshot = match declaration_snapshot(registered_declarations.clone()) {
541 Ok(snapshot) => snapshot,
542 Err(err) => return DiagnosticCheck::failed(format!("declaration snapshot: {err}")),
543 };
544 let recovered = match diagnostic_validation_ledger(stable_cell_record, recovered) {
545 Ok(recovered) => recovered,
546 Err(reason) => return DiagnosticCheck::not_run(reason),
547 };
548 let policy = RuntimeMemoryManagerPolicy {
549 range_authority,
550 user_ranges_registered: !registered_ranges.is_empty(),
551 declaration_metadata: declaration_metadata(®istered_declarations),
552 custom_policy: &NoopPolicy,
553 };
554
555 match crate::validate_allocations(&recovered, snapshot, &policy) {
556 Ok(_) => DiagnosticCheck::passed(),
557 Err(err) => DiagnosticCheck::failed(err.to_string()),
558 }
559}
560
561fn diagnostic_validation_ledger(
562 stable_cell_record: Option<&StableCellLedgerRecord>,
563 recovered: Option<&Result<crate::RecoveredLedger, LedgerCommitError>>,
564) -> Result<crate::RecoveredLedger, String> {
565 if let Some(Ok(recovered)) = recovered {
566 return Ok(recovered.clone());
567 }
568 if let Some(Err(err)) = recovered {
569 if stable_cell_record.is_some_and(|record| record.store().physical().is_uninitialized()) {
570 return diagnostic_genesis_recovered_ledger();
571 }
572 return Err(format!("protected ledger recovery: {err}"));
573 }
574 if stable_cell_record.is_some() {
575 return diagnostic_genesis_recovered_ledger();
576 }
577 Err("stable-cell ledger record is not readable".to_string())
578}
579
580fn diagnostic_genesis_recovered_ledger() -> Result<crate::RecoveredLedger, String> {
581 AllocationLedger::new(0, AllocationHistory::default())
582 .map(|ledger| crate::RecoveredLedger::from_trusted_parts(ledger, 0))
583 .map_err(|err| format!("genesis ledger: {err}"))
584}
585
586fn publish_validated_allocations<P>(
587 validated: ValidatedAllocations,
588) -> Result<(), RuntimeBootstrapError<P>> {
589 *VALIDATED_ALLOCATIONS
590 .lock()
591 .map_err(|_| RuntimeBootstrapError::RuntimeLockPoisoned)? = Some(validated);
592 Ok(())
593}
594
595fn declaration_snapshot(
596 registrations: Vec<StaticMemoryDeclaration>,
597) -> Result<DeclarationSnapshot, StaticMemoryDeclarationError> {
598 let mut declarations = Vec::with_capacity(registrations.len() + 1);
599 declarations.push(internal_ledger_declaration()?);
600 declarations.extend(
601 registrations
602 .into_iter()
603 .map(StaticMemoryDeclaration::into_declaration),
604 );
605 DeclarationSnapshot::new(declarations).map_err(StaticMemoryDeclarationError::Declaration)
606}
607
608fn declaration_metadata(registrations: &[StaticMemoryDeclaration]) -> BTreeMap<String, String> {
609 let mut metadata = BTreeMap::new();
610 metadata.insert(
611 IC_MEMORY_LEDGER_STABLE_KEY.to_string(),
612 IC_MEMORY_AUTHORITY_OWNER.to_string(),
613 );
614 for registration in registrations {
615 metadata.insert(
616 registration.declaration().stable_key().as_str().to_string(),
617 registration.declaring_crate().to_string(),
618 );
619 }
620 metadata
621}
622
623fn range_authority(
624 registrations: Vec<StaticMemoryRangeDeclaration>,
625) -> Result<MemoryManagerRangeAuthority, MemoryManagerRangeAuthorityError> {
626 let mut records = Vec::with_capacity(registrations.len() + 1);
627 records.push(internal_ledger_range()?);
628 records.extend(
629 registrations
630 .into_iter()
631 .map(StaticMemoryRangeDeclaration::into_record),
632 );
633 MemoryManagerRangeAuthority::from_records(records)
634}
635
636fn internal_ledger_declaration() -> Result<AllocationDeclaration, crate::DeclarationSnapshotError> {
637 AllocationDeclaration::memory_manager(
638 IC_MEMORY_LEDGER_STABLE_KEY,
639 MEMORY_MANAGER_LEDGER_ID,
640 IC_MEMORY_LEDGER_LABEL,
641 )
642}
643
644fn internal_ledger_range() -> Result<MemoryManagerAuthorityRecord, MemoryManagerRangeAuthorityError>
645{
646 MemoryManagerAuthorityRecord::new(
647 MemoryManagerIdRange::new(
648 MEMORY_MANAGER_LEDGER_ID,
649 crate::MEMORY_MANAGER_GOVERNANCE_MAX_ID,
650 )?,
651 IC_MEMORY_AUTHORITY_OWNER,
652 MemoryManagerRangeMode::Reserved,
653 Some(IC_MEMORY_AUTHORITY_PURPOSE.to_string()),
654 )
655}
656
657fn runtime_bootstrap_error_from_bootstrap<P>(
658 err: crate::BootstrapError<RuntimePolicyError<P>>,
659) -> RuntimeBootstrapError<P> {
660 match err {
661 crate::BootstrapError::Ledger(err) => RuntimeBootstrapError::LedgerCommit(err),
662 crate::BootstrapError::Validation(err) => RuntimeBootstrapError::Validation(err),
663 crate::BootstrapError::Staging(err) => RuntimeBootstrapError::Staging(err),
664 }
665}
666
667struct RuntimeMemoryManagerPolicy<'a, P> {
668 range_authority: MemoryManagerRangeAuthority,
669 user_ranges_registered: bool,
670 declaration_metadata: BTreeMap<String, String>,
671 custom_policy: &'a P,
672}
673
674impl<P: AllocationPolicy> AllocationPolicy for RuntimeMemoryManagerPolicy<'_, P> {
675 type Error = RuntimePolicyError<P::Error>;
676
677 fn validate_key(&self, key: &StableKey) -> Result<(), Self::Error> {
678 let declaring_crate = self.declaring_crate(key)?;
679 if crate::is_ic_memory_stable_key(key.as_str())
680 && declaring_crate != IC_MEMORY_AUTHORITY_OWNER
681 {
682 return Err(RuntimePolicyError::ReservedStableKeyAuthority {
683 stable_key: key.as_str().to_string(),
684 expected_authority: IC_MEMORY_AUTHORITY_OWNER,
685 });
686 }
687 self.custom_policy
688 .validate_key(key)
689 .map_err(RuntimePolicyError::Custom)
690 }
691
692 fn validate_slot(
693 &self,
694 key: &StableKey,
695 slot: &AllocationSlotDescriptor,
696 ) -> Result<(), Self::Error> {
697 self.validate_runtime_range(key, slot)?;
698 self.custom_policy
699 .validate_slot(key, slot)
700 .map_err(RuntimePolicyError::Custom)
701 }
702
703 fn validate_reserved_slot(
704 &self,
705 key: &StableKey,
706 slot: &AllocationSlotDescriptor,
707 ) -> Result<(), Self::Error> {
708 self.validate_runtime_range(key, slot)?;
709 self.custom_policy
710 .validate_reserved_slot(key, slot)
711 .map_err(RuntimePolicyError::Custom)
712 }
713}
714
715impl<P: AllocationPolicy> RuntimeMemoryManagerPolicy<'_, P> {
716 fn declaring_crate(&self, key: &StableKey) -> Result<&str, RuntimePolicyError<P::Error>> {
717 self.declaration_metadata
718 .get(key.as_str())
719 .map(String::as_str)
720 .ok_or_else(|| RuntimePolicyError::MissingDeclarationMetadata(key.as_str().to_string()))
721 }
722
723 fn validate_runtime_range(
724 &self,
725 key: &StableKey,
726 slot: &AllocationSlotDescriptor,
727 ) -> Result<(), RuntimePolicyError<P::Error>> {
728 let declaring_crate = self.declaring_crate(key)?;
729 if declaring_crate == IC_MEMORY_AUTHORITY_OWNER || self.user_ranges_registered {
735 self.range_authority
736 .validate_slot_authority(slot, declaring_crate)?;
737 return Ok(());
738 }
739
740 let id = slot
741 .memory_manager_id()
742 .map_err(MemoryManagerRangeAuthorityError::Slot)?;
743 if self
744 .range_authority
745 .authority_for_id(id)
746 .map_err(RuntimePolicyError::Range)?
747 .is_some()
748 {
749 self.range_authority
750 .validate_slot_authority(slot, declaring_crate)?;
751 }
752 Ok(())
753 }
754}
755
756struct NoopPolicy;
757
758impl AllocationPolicy for NoopPolicy {
759 type Error = Infallible;
760
761 fn validate_key(&self, _key: &StableKey) -> Result<(), Self::Error> {
762 Ok(())
763 }
764
765 fn validate_slot(
766 &self,
767 _key: &StableKey,
768 _slot: &AllocationSlotDescriptor,
769 ) -> Result<(), Self::Error> {
770 Ok(())
771 }
772
773 fn validate_reserved_slot(
774 &self,
775 _key: &StableKey,
776 _slot: &AllocationSlotDescriptor,
777 ) -> Result<(), Self::Error> {
778 Ok(())
779 }
780}
781
782#[cfg(test)]
783pub(crate) fn reset_for_tests() {
784 crate::registry::reset_static_memory_declarations_for_tests();
785 EAGER_INIT_HOOKS
786 .lock()
787 .expect("ic-memory eager-init queue poisoned")
788 .clear();
789 *VALIDATED_ALLOCATIONS
790 .lock()
791 .expect("ic-memory runtime validation state poisoned") = None;
792 BOOTSTRAPPED.store(false, Ordering::SeqCst);
793 DEFAULT_LEDGER_CELL.with_borrow_mut(|cell| {
794 *cell = None;
795 });
796}
797
798#[cfg(test)]
799mod tests {
800 use super::*;
801 use crate::registry::{
802 TEST_REGISTRY_LOCK, register_static_memory_manager_declaration,
803 register_static_memory_manager_range,
804 };
805 use std::sync::atomic::{AtomicBool, Ordering};
806
807 static EAGER_INIT_RAN: AtomicBool = AtomicBool::new(false);
808
809 fn register_crate_a() {
810 register_static_memory_manager_range(
811 100,
812 109,
813 "crate_a",
814 MemoryManagerRangeMode::Reserved,
815 None,
816 )
817 .expect("crate A range");
818 register_static_memory_manager_declaration(100, "crate_a", "users", "crate_a.users.v1")
819 .expect("crate A memory");
820 }
821
822 fn register_crate_b() {
823 register_static_memory_manager_range(
824 110,
825 119,
826 "crate_b",
827 MemoryManagerRangeMode::Reserved,
828 None,
829 )
830 .expect("crate B range");
831 register_static_memory_manager_declaration(110, "crate_b", "orders", "crate_b.orders.v1")
832 .expect("crate B memory");
833 }
834
835 fn mark_eager_init() {
836 EAGER_INIT_RAN.store(true, Ordering::SeqCst);
837 register_static_memory_manager_declaration(101, "crate_a", "audit", "crate_a.audit.v1")
838 .expect("eager-init declaration");
839 }
840
841 #[test]
842 fn multi_crate_declarations_compose_into_one_bootstrap() {
843 let _guard = TEST_REGISTRY_LOCK.lock().expect("test lock poisoned");
844 reset_for_tests();
845 register_crate_a();
846 register_crate_b();
847
848 let validated = bootstrap_default_memory_manager().expect("bootstrap");
849
850 assert_eq!(validated.declarations().len(), 3);
851 assert!(
852 validated
853 .declarations()
854 .iter()
855 .any(|declaration| declaration.stable_key().as_str() == "crate_a.users.v1")
856 );
857 assert!(
858 validated
859 .declarations()
860 .iter()
861 .any(|declaration| declaration.stable_key().as_str() == "crate_b.orders.v1")
862 );
863 }
864
865 #[test]
866 fn conflicting_ranges_fail() {
867 let _guard = TEST_REGISTRY_LOCK.lock().expect("test lock poisoned");
868 reset_for_tests();
869 register_static_memory_manager_range(
870 100,
871 110,
872 "crate_a",
873 MemoryManagerRangeMode::Reserved,
874 None,
875 )
876 .expect("crate A range");
877 register_static_memory_manager_range(
878 105,
879 119,
880 "crate_b",
881 MemoryManagerRangeMode::Reserved,
882 None,
883 )
884 .expect("crate B range");
885
886 let err = bootstrap_default_memory_manager().expect_err("overlap must fail");
887 assert!(matches!(
888 err,
889 RuntimeBootstrapError::Range(
890 MemoryManagerRangeAuthorityError::OverlappingRanges { .. }
891 )
892 ));
893 }
894
895 #[test]
896 fn duplicate_stable_keys_fail() {
897 let _guard = TEST_REGISTRY_LOCK.lock().expect("test lock poisoned");
898 reset_for_tests();
899 register_static_memory_manager_declaration(100, "crate_a", "users", "app.users.v1")
900 .expect("first declaration");
901 register_static_memory_manager_declaration(101, "crate_b", "users", "app.users.v1")
902 .expect("second declaration");
903
904 let err = bootstrap_default_memory_manager().expect_err("duplicate key must fail");
905 assert!(matches!(
906 err,
907 RuntimeBootstrapError::Registry(StaticMemoryDeclarationError::Declaration(
908 crate::DeclarationSnapshotError::DuplicateStableKey(_)
909 ))
910 ));
911 }
912
913 #[test]
914 fn duplicate_memory_manager_ids_fail() {
915 let _guard = TEST_REGISTRY_LOCK.lock().expect("test lock poisoned");
916 reset_for_tests();
917 register_static_memory_manager_declaration(100, "crate_a", "users", "crate_a.users.v1")
918 .expect("first declaration");
919 register_static_memory_manager_declaration(100, "crate_b", "orders", "crate_b.orders.v1")
920 .expect("second declaration");
921
922 let err = bootstrap_default_memory_manager().expect_err("duplicate slot must fail");
923 assert!(matches!(
924 err,
925 RuntimeBootstrapError::Registry(StaticMemoryDeclarationError::Declaration(
926 crate::DeclarationSnapshotError::DuplicateSlot(_)
927 ))
928 ));
929 }
930
931 #[test]
932 fn out_of_range_memory_declaration_fails_when_ranges_are_declared() {
933 let _guard = TEST_REGISTRY_LOCK.lock().expect("test lock poisoned");
934 reset_for_tests();
935 register_static_memory_manager_range(
936 100,
937 109,
938 "crate_a",
939 MemoryManagerRangeMode::Reserved,
940 None,
941 )
942 .expect("crate A range");
943 register_static_memory_manager_declaration(120, "crate_a", "users", "crate_a.users.v1")
944 .expect("out-of-range declaration");
945
946 let err = bootstrap_default_memory_manager().expect_err("out of range must fail");
947 assert!(matches!(
948 err,
949 RuntimeBootstrapError::Validation(crate::AllocationValidationError::Policy(
950 RuntimePolicyError::Range(MemoryManagerRangeAuthorityError::UnclaimedId {
951 id: 120
952 })
953 ))
954 ));
955 }
956
957 #[test]
958 fn late_registration_after_bootstrap_fails() {
959 let _guard = TEST_REGISTRY_LOCK.lock().expect("test lock poisoned");
960 reset_for_tests();
961 register_static_memory_manager_declaration(100, "crate_a", "users", "crate_a.users.v1")
962 .expect("declaration");
963 bootstrap_default_memory_manager().expect("bootstrap");
964
965 let err = register_static_memory_manager_declaration(
966 101,
967 "crate_a",
968 "orders",
969 "crate_a.orders.v1",
970 )
971 .expect_err("late registration must fail");
972 assert_eq!(err, StaticMemoryDeclarationError::RegistrySealed);
973 }
974
975 #[test]
976 fn late_eager_init_registration_after_bootstrap_fails() {
977 let _guard = TEST_REGISTRY_LOCK.lock().expect("test lock poisoned");
978 reset_for_tests();
979 register_static_memory_manager_declaration(100, "crate_a", "users", "crate_a.users.v1")
980 .expect("declaration");
981 bootstrap_default_memory_manager().expect("bootstrap");
982
983 let err = std::panic::catch_unwind(|| defer_eager_init(mark_eager_init))
984 .expect_err("late eager-init registration must fail");
985
986 let message = err
987 .downcast_ref::<String>()
988 .map(String::as_str)
989 .or_else(|| err.downcast_ref::<&str>().copied())
990 .expect("panic message");
991 assert!(message.contains("after runtime bootstrap"));
992 }
993
994 #[test]
995 fn eager_init_runs_before_snapshot_seal() {
996 let _guard = TEST_REGISTRY_LOCK.lock().expect("test lock poisoned");
997 reset_for_tests();
998 EAGER_INIT_RAN.store(false, Ordering::SeqCst);
999 register_static_memory_manager_range(
1000 100,
1001 109,
1002 "crate_a",
1003 MemoryManagerRangeMode::Reserved,
1004 None,
1005 )
1006 .expect("crate A range");
1007 defer_eager_init(mark_eager_init);
1008
1009 let validated = bootstrap_default_memory_manager().expect("bootstrap");
1010
1011 assert!(EAGER_INIT_RAN.load(Ordering::SeqCst));
1012 assert!(
1013 validated
1014 .declarations()
1015 .iter()
1016 .any(|declaration| declaration.stable_key().as_str() == "crate_a.audit.v1")
1017 );
1018 }
1019
1020 #[test]
1021 fn direct_user_can_bootstrap_and_open_without_canic() {
1022 let _guard = TEST_REGISTRY_LOCK.lock().expect("test lock poisoned");
1023 reset_for_tests();
1024 register_static_memory_manager_range(
1025 120,
1026 129,
1027 "icydb",
1028 MemoryManagerRangeMode::Reserved,
1029 None,
1030 )
1031 .expect("icydb range");
1032 register_static_memory_manager_declaration(120, "icydb", "users", "icydb.users.data.v1")
1033 .expect("icydb declaration");
1034
1035 bootstrap_default_memory_manager().expect("bootstrap");
1036 open_default_memory_manager_memory("icydb.users.data.v1", 120).expect("open memory");
1037 }
1038
1039 #[test]
1040 fn diagnostic_export_reports_default_memory_manager_sizes() {
1041 let _guard = TEST_REGISTRY_LOCK.lock().expect("test lock poisoned");
1042 reset_for_tests();
1043 register_static_memory_manager_range(
1044 130,
1045 139,
1046 "diagnostics",
1047 MemoryManagerRangeMode::Reserved,
1048 None,
1049 )
1050 .expect("diagnostics range");
1051 register_static_memory_manager_declaration(
1052 130,
1053 "diagnostics",
1054 "users",
1055 "diagnostics.users.v1",
1056 )
1057 .expect("diagnostics declaration");
1058
1059 bootstrap_default_memory_manager().expect("bootstrap");
1060 let memory =
1061 open_default_memory_manager_memory("diagnostics.users.v1", 130).expect("open memory");
1062 let old_size = memory.size();
1063 memory.grow(2);
1064
1065 let export = default_memory_manager_diagnostic_export().expect("diagnostic export");
1066 let recovery =
1067 default_memory_manager_commit_recovery_diagnostic().expect("recovery diagnostic");
1068 let record = export
1069 .records
1070 .iter()
1071 .find(|record| record.allocation.stable_key().as_str() == "diagnostics.users.v1")
1072 .expect("diagnostic allocation");
1073
1074 assert_eq!(
1075 recovery.authoritative_generation,
1076 Some(export.current_generation)
1077 );
1078 assert_eq!(
1079 record.memory_size,
1080 Some(DiagnosticMemorySize::from_wasm_pages(old_size + 2))
1081 );
1082 }
1083
1084 #[test]
1085 fn doctor_report_preflights_before_bootstrap() {
1086 let _guard = TEST_REGISTRY_LOCK.lock().expect("test lock poisoned");
1087 reset_for_tests();
1088 register_static_memory_manager_range(
1089 240,
1090 240,
1091 "doctor_preflight",
1092 MemoryManagerRangeMode::Reserved,
1093 None,
1094 )
1095 .expect("doctor range");
1096 register_static_memory_manager_declaration(
1097 240,
1098 "doctor_preflight",
1099 "users",
1100 "doctor_preflight.users.v1",
1101 )
1102 .expect("doctor declaration");
1103
1104 let report = default_memory_manager_doctor_report();
1105
1106 assert!(!report.bootstrapped);
1107 assert_eq!(report.registered_declarations.len(), 1);
1108 assert!(report.range_authority.effective_authority.is_some());
1109 assert_eq!(
1110 report.validation.status,
1111 crate::DiagnosticCheckStatus::Passed
1112 );
1113 assert!(report.commit_recovery.is_some());
1114 assert!(matches!(
1115 report.stable_cell.status,
1116 crate::DiagnosticStableCellStatus::Empty | crate::DiagnosticStableCellStatus::Readable
1117 ));
1118 }
1119
1120 #[test]
1121 fn doctor_report_includes_recovered_ledger_and_memory_sizes_after_bootstrap() {
1122 let _guard = TEST_REGISTRY_LOCK.lock().expect("test lock poisoned");
1123 reset_for_tests();
1124 register_static_memory_manager_range(
1125 241,
1126 241,
1127 "doctor_runtime",
1128 MemoryManagerRangeMode::Reserved,
1129 None,
1130 )
1131 .expect("doctor range");
1132 register_static_memory_manager_declaration(
1133 241,
1134 "doctor_runtime",
1135 "orders",
1136 "doctor_runtime.orders.v1",
1137 )
1138 .expect("doctor declaration");
1139
1140 bootstrap_default_memory_manager().expect("bootstrap");
1141 let memory = open_default_memory_manager_memory("doctor_runtime.orders.v1", 241)
1142 .expect("open memory");
1143 let old_size = memory.size();
1144 memory.grow(1);
1145
1146 let report = default_memory_manager_doctor_report();
1147 let ledger = report.ledger.expect("recovered ledger export");
1148 let record = ledger
1149 .records
1150 .iter()
1151 .find(|record| record.allocation.stable_key().as_str() == "doctor_runtime.orders.v1")
1152 .expect("doctor allocation");
1153
1154 assert!(report.bootstrapped);
1155 assert_eq!(
1156 report.stable_cell.status,
1157 crate::DiagnosticStableCellStatus::Readable
1158 );
1159 assert_eq!(
1160 report.validation.status,
1161 crate::DiagnosticCheckStatus::Passed
1162 );
1163 assert_eq!(
1164 record.memory_size,
1165 Some(DiagnosticMemorySize::from_wasm_pages(old_size + 1))
1166 );
1167 }
1168
1169 #[test]
1170 fn doctor_report_captures_validation_failure() {
1171 let _guard = TEST_REGISTRY_LOCK.lock().expect("test lock poisoned");
1172 reset_for_tests();
1173 register_static_memory_manager_declaration(
1174 242,
1175 "doctor_failure_a",
1176 "users",
1177 "doctor_failure.users.v1",
1178 )
1179 .expect("first declaration");
1180 register_static_memory_manager_declaration(
1181 243,
1182 "doctor_failure_b",
1183 "orders",
1184 "doctor_failure.users.v1",
1185 )
1186 .expect("second declaration");
1187
1188 let report = default_memory_manager_doctor_report();
1189
1190 assert_eq!(
1191 report.validation.status,
1192 crate::DiagnosticCheckStatus::Failed
1193 );
1194 assert!(
1195 report
1196 .validation
1197 .message
1198 .expect("validation failure message")
1199 .contains("declared more than once")
1200 );
1201 }
1202}