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