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