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(
550 DiagnosticStableCellStatus::Empty,
551 memory_size,
552 None,
553 ),
554 record: Some(StableCellLedgerRecord::default()),
555 };
556 }
557
558 let record = decode_stable_cell_ledger_record_from_memory(&memory);
559 match record {
560 Ok(record) => DefaultStableCellDiagnostic {
561 diagnostic: DiagnosticStableCell::new(
562 DiagnosticStableCellStatus::Readable,
563 memory_size,
564 None,
565 ),
566 record: Some(record),
567 },
568 Err(err) => DefaultStableCellDiagnostic {
569 diagnostic: DiagnosticStableCell::new(
570 DiagnosticStableCellStatus::Corrupt,
571 memory_size,
572 Some(err.to_string()),
573 ),
574 record: None,
575 },
576 }
577}
578
579fn diagnostic_range_authority(
580 registered_ranges: &Result<Vec<StaticMemoryRangeDeclaration>, StaticMemoryDeclarationError>,
581) -> DiagnosticRangeAuthority {
582 match registered_ranges {
583 Ok(ranges) => {
584 let registered_records = ranges
585 .iter()
586 .map(|registration| registration.record().clone())
587 .collect();
588 match range_authority(ranges.clone()) {
589 Ok(authority) => {
590 DiagnosticRangeAuthority::new(registered_records, Some(authority), None)
591 }
592 Err(err) => {
593 DiagnosticRangeAuthority::new(registered_records, None, Some(err.to_string()))
594 }
595 }
596 }
597 Err(err) => DiagnosticRangeAuthority::new(Vec::new(), None, Some(err.to_string())),
598 }
599}
600
601fn diagnostic_validation(
602 registered_declarations: &Result<Vec<StaticMemoryDeclaration>, StaticMemoryDeclarationError>,
603 registered_ranges: &Result<Vec<StaticMemoryRangeDeclaration>, StaticMemoryDeclarationError>,
604 stable_cell_record: Option<&StableCellLedgerRecord>,
605 recovered: Option<&Result<crate::RecoveredLedger, LedgerCommitError>>,
606) -> DiagnosticCheck {
607 let registered_declarations = match registered_declarations {
608 Ok(declarations) => declarations.clone(),
609 Err(err) => return DiagnosticCheck::failed(format!("declaration registry: {err}")),
610 };
611 let registered_ranges = match registered_ranges {
612 Ok(ranges) => ranges.clone(),
613 Err(err) => return DiagnosticCheck::failed(format!("range registry: {err}")),
614 };
615 let range_authority = match range_authority(registered_ranges.clone()) {
616 Ok(authority) => authority,
617 Err(err) => return DiagnosticCheck::failed(format!("range authority: {err}")),
618 };
619 let snapshot = match declaration_snapshot(registered_declarations.clone()) {
620 Ok(snapshot) => snapshot,
621 Err(err) => return DiagnosticCheck::failed(format!("declaration snapshot: {err}")),
622 };
623 let recovered = match diagnostic_validation_ledger(stable_cell_record, recovered) {
624 Ok(recovered) => recovered,
625 Err(reason) => return DiagnosticCheck::not_run(reason),
626 };
627 let policy = RuntimeMemoryManagerPolicy {
628 range_authority,
629 user_ranges_registered: !registered_ranges.is_empty(),
630 declaration_metadata: declaration_metadata(®istered_declarations),
631 custom_policy: &NoopPolicy,
632 };
633
634 match crate::validate_allocations(&recovered, snapshot, &policy) {
635 Ok(_) => DiagnosticCheck::passed(),
636 Err(err) => DiagnosticCheck::failed(err.to_string()),
637 }
638}
639
640fn diagnostic_validation_ledger(
641 stable_cell_record: Option<&StableCellLedgerRecord>,
642 recovered: Option<&Result<crate::RecoveredLedger, LedgerCommitError>>,
643) -> Result<crate::RecoveredLedger, String> {
644 if let Some(Ok(recovered)) = recovered {
645 return Ok(recovered.clone());
646 }
647 if let Some(Err(err)) = recovered {
648 if stable_cell_record.is_some_and(|record| record.store().physical().is_uninitialized()) {
649 return diagnostic_genesis_recovered_ledger();
650 }
651 return Err(format!("protected ledger recovery: {err}"));
652 }
653 if stable_cell_record.is_some() {
654 return diagnostic_genesis_recovered_ledger();
655 }
656 Err("stable-cell ledger record is not readable".to_string())
657}
658
659fn diagnostic_genesis_recovered_ledger() -> Result<crate::RecoveredLedger, String> {
660 AllocationLedger::new(0, AllocationHistory::default())
661 .map(|ledger| crate::RecoveredLedger::from_trusted_parts(ledger, 0))
662 .map_err(|err| format!("genesis ledger: {err}"))
663}
664
665fn publish_committed_allocations<P>(
666 committed: CommittedAllocations,
667) -> Result<(), RuntimeBootstrapError<P>> {
668 *COMMITTED_ALLOCATIONS
669 .lock()
670 .map_err(|_| RuntimeBootstrapError::RuntimeLockPoisoned)? = Some(committed);
671 Ok(())
672}
673
674fn declaration_snapshot(
675 registrations: Vec<StaticMemoryDeclaration>,
676) -> Result<DeclarationSnapshot, StaticMemoryDeclarationError> {
677 let mut declarations = Vec::with_capacity(registrations.len() + 1);
678 declarations.push(internal_ledger_declaration()?);
679 declarations.extend(
680 registrations
681 .into_iter()
682 .map(StaticMemoryDeclaration::into_declaration),
683 );
684 DeclarationSnapshot::new(declarations).map_err(StaticMemoryDeclarationError::Declaration)
685}
686
687fn declaration_metadata(
688 registrations: &[StaticMemoryDeclaration],
689) -> BTreeMap<String, RuntimeDeclarationAuthority> {
690 let mut metadata = BTreeMap::new();
691 metadata.insert(
692 IC_MEMORY_LEDGER_STABLE_KEY.to_string(),
693 RuntimeDeclarationAuthority::Internal,
694 );
695 for registration in registrations {
696 metadata.insert(
697 registration.declaration().stable_key().as_str().to_string(),
698 RuntimeDeclarationAuthority::External(registration.authority().to_string()),
699 );
700 }
701 metadata
702}
703
704fn range_authority(
705 registrations: Vec<StaticMemoryRangeDeclaration>,
706) -> Result<MemoryManagerRangeAuthority, MemoryManagerRangeAuthorityError> {
707 let mut records = Vec::with_capacity(registrations.len() + 1);
708 records.push(internal_ledger_range()?);
709 records.extend(
710 registrations
711 .into_iter()
712 .map(StaticMemoryRangeDeclaration::into_record),
713 );
714 MemoryManagerRangeAuthority::from_records(records)
715}
716
717fn internal_ledger_declaration() -> Result<AllocationDeclaration, crate::DeclarationSnapshotError> {
718 AllocationDeclaration::memory_manager(
719 IC_MEMORY_LEDGER_STABLE_KEY,
720 MEMORY_MANAGER_LEDGER_ID,
721 IC_MEMORY_LEDGER_LABEL,
722 )
723}
724
725fn internal_ledger_range() -> Result<MemoryManagerAuthorityRecord, MemoryManagerRangeAuthorityError>
726{
727 MemoryManagerAuthorityRecord::new(
728 MemoryManagerIdRange::new(
729 MEMORY_MANAGER_LEDGER_ID,
730 crate::MEMORY_MANAGER_GOVERNANCE_MAX_ID,
731 )?,
732 IC_MEMORY_AUTHORITY_OWNER,
733 MemoryManagerRangeMode::Reserved,
734 Some(IC_MEMORY_AUTHORITY_PURPOSE.to_string()),
735 )
736}
737
738fn runtime_bootstrap_error_from_bootstrap<P>(
739 err: crate::BootstrapError<RuntimePolicyError<P>>,
740) -> RuntimeBootstrapError<P> {
741 match err {
742 crate::BootstrapError::Ledger(err) => RuntimeBootstrapError::LedgerCommit(err),
743 crate::BootstrapError::Validation(err) => RuntimeBootstrapError::Validation(err),
744 crate::BootstrapError::Staging(err) => RuntimeBootstrapError::Staging(err),
745 }
746}
747
748struct RuntimeMemoryManagerPolicy<'a, P> {
749 range_authority: MemoryManagerRangeAuthority,
750 user_ranges_registered: bool,
751 declaration_metadata: BTreeMap<String, RuntimeDeclarationAuthority>,
752 custom_policy: &'a P,
753}
754
755enum RuntimeDeclarationAuthority {
756 Internal,
757 External(String),
758}
759
760impl<P: AllocationPolicy> AllocationPolicy for RuntimeMemoryManagerPolicy<'_, P> {
761 type Error = RuntimePolicyError<P::Error>;
762
763 fn validate_key(&self, key: &StableKey) -> Result<(), Self::Error> {
764 let authority = self.declaration_authority(key)?;
765 if crate::is_ic_memory_stable_key(key.as_str())
766 && !matches!(authority, RuntimeDeclarationAuthority::Internal)
767 {
768 return Err(RuntimePolicyError::ReservedStableKeyAuthority {
769 stable_key: key.as_str().to_string(),
770 expected_authority: IC_MEMORY_AUTHORITY_OWNER,
771 });
772 }
773 self.custom_policy
774 .validate_key(key)
775 .map_err(RuntimePolicyError::Custom)
776 }
777
778 fn validate_slot(
779 &self,
780 key: &StableKey,
781 slot: &AllocationSlotDescriptor,
782 ) -> Result<(), Self::Error> {
783 self.validate_runtime_range(key, slot)?;
784 self.custom_policy
785 .validate_slot(key, slot)
786 .map_err(RuntimePolicyError::Custom)
787 }
788
789 fn validate_reserved_slot(
790 &self,
791 key: &StableKey,
792 slot: &AllocationSlotDescriptor,
793 ) -> Result<(), Self::Error> {
794 self.validate_runtime_range(key, slot)?;
795 self.custom_policy
796 .validate_reserved_slot(key, slot)
797 .map_err(RuntimePolicyError::Custom)
798 }
799}
800
801impl<P: AllocationPolicy> RuntimeMemoryManagerPolicy<'_, P> {
802 fn declaration_authority(
803 &self,
804 key: &StableKey,
805 ) -> Result<&RuntimeDeclarationAuthority, RuntimePolicyError<P::Error>> {
806 self.declaration_metadata
807 .get(key.as_str())
808 .ok_or_else(|| RuntimePolicyError::MissingDeclarationMetadata(key.as_str().to_string()))
809 }
810
811 fn validate_runtime_range(
812 &self,
813 key: &StableKey,
814 slot: &AllocationSlotDescriptor,
815 ) -> Result<(), RuntimePolicyError<P::Error>> {
816 let authority = self.declaration_authority(key)?;
817 if matches!(authority, RuntimeDeclarationAuthority::Internal) {
823 self.range_authority
824 .validate_slot_authority(slot, IC_MEMORY_AUTHORITY_OWNER)?;
825 return Ok(());
826 }
827
828 let RuntimeDeclarationAuthority::External(authority) = authority else {
829 return Err(RuntimePolicyError::MissingDeclarationMetadata(
830 key.as_str().to_string(),
831 ));
832 };
833 if self.user_ranges_registered {
834 self.range_authority
835 .validate_slot_authority(slot, authority)?;
836 return Ok(());
837 }
838
839 let id = slot
840 .memory_manager_id()
841 .map_err(MemoryManagerRangeAuthorityError::Slot)?;
842 if self
843 .range_authority
844 .authority_for_id(id)
845 .map_err(RuntimePolicyError::Range)?
846 .is_some()
847 {
848 self.range_authority
849 .validate_slot_authority(slot, authority)?;
850 }
851 Ok(())
852 }
853}
854
855struct NoopPolicy;
856
857impl AllocationPolicy for NoopPolicy {
858 type Error = Infallible;
859
860 fn validate_key(&self, _key: &StableKey) -> Result<(), Self::Error> {
861 Ok(())
862 }
863
864 fn validate_slot(
865 &self,
866 _key: &StableKey,
867 _slot: &AllocationSlotDescriptor,
868 ) -> Result<(), Self::Error> {
869 Ok(())
870 }
871
872 fn validate_reserved_slot(
873 &self,
874 _key: &StableKey,
875 _slot: &AllocationSlotDescriptor,
876 ) -> Result<(), Self::Error> {
877 Ok(())
878 }
879}
880
881#[cfg(test)]
882pub fn reset_for_tests() {
883 crate::registry::reset_static_memory_declarations_for_tests();
884 EAGER_INIT_HOOKS
885 .lock()
886 .expect("ic-memory eager-init queue poisoned")
887 .clear();
888 *COMMITTED_ALLOCATIONS
889 .lock()
890 .expect("ic-memory runtime validation state poisoned") = None;
891 BOOTSTRAPPED.store(false, Ordering::SeqCst);
892 DEFAULT_LEDGER_CELL.with_borrow_mut(|cell| {
893 *cell = None;
894 });
895}
896
897#[cfg(test)]
898mod tests {
899 use super::*;
900 use crate::registry::{
901 TEST_REGISTRY_LOCK, register_static_memory_manager_declaration,
902 register_static_memory_manager_range,
903 };
904 use std::sync::atomic::{AtomicBool, Ordering};
905
906 static EAGER_INIT_RAN: AtomicBool = AtomicBool::new(false);
907
908 fn register_crate_a() {
909 register_static_memory_manager_range(
910 100,
911 109,
912 "crate_a",
913 MemoryManagerRangeMode::Reserved,
914 None,
915 )
916 .expect("crate A range");
917 register_static_memory_manager_declaration(100, "crate_a", "users", "crate_a.users.v1")
918 .expect("crate A memory");
919 }
920
921 fn register_crate_b() {
922 register_static_memory_manager_range(
923 110,
924 119,
925 "crate_b",
926 MemoryManagerRangeMode::Reserved,
927 None,
928 )
929 .expect("crate B range");
930 register_static_memory_manager_declaration(110, "crate_b", "orders", "crate_b.orders.v1")
931 .expect("crate B memory");
932 }
933
934 fn mark_eager_init() {
935 EAGER_INIT_RAN.store(true, Ordering::SeqCst);
936 register_static_memory_manager_declaration(101, "crate_a", "audit", "crate_a.audit.v1")
937 .expect("eager-init declaration");
938 }
939
940 #[test]
941 fn multi_crate_declarations_compose_into_one_bootstrap() {
942 let _guard = TEST_REGISTRY_LOCK.lock().expect("test lock poisoned");
943 reset_for_tests();
944 register_crate_a();
945 register_crate_b();
946
947 let validated = bootstrap_default_memory_manager().expect("bootstrap");
948
949 assert_eq!(validated.declarations().len(), 2);
950 assert!(
951 validated
952 .declarations()
953 .iter()
954 .any(|declaration| declaration.stable_key().as_str() == "crate_a.users.v1")
955 );
956 assert!(
957 validated
958 .declarations()
959 .iter()
960 .any(|declaration| declaration.stable_key().as_str() == "crate_b.orders.v1")
961 );
962 }
963
964 #[test]
965 fn default_runtime_keeps_internal_ledger_slot_private() {
966 let _guard = TEST_REGISTRY_LOCK.lock().expect("test lock poisoned");
967 reset_for_tests();
968
969 let validated = bootstrap_default_memory_manager().expect("bootstrap");
970
971 assert!(validated.declarations().is_empty());
972 assert!(
973 committed_allocations()
974 .expect("published allocations")
975 .declarations()
976 .is_empty()
977 );
978 let Err(err) = open_default_memory_manager_memory(
979 IC_MEMORY_LEDGER_STABLE_KEY,
980 MEMORY_MANAGER_LEDGER_ID,
981 ) else {
982 panic!("internal ledger slot must stay private");
983 };
984 assert!(matches!(err, RuntimeOpenError::ReservedStableKey { .. }));
985 }
986
987 #[test]
988 fn conflicting_ranges_fail() {
989 let _guard = TEST_REGISTRY_LOCK.lock().expect("test lock poisoned");
990 reset_for_tests();
991 register_static_memory_manager_range(
992 100,
993 110,
994 "crate_a",
995 MemoryManagerRangeMode::Reserved,
996 None,
997 )
998 .expect("crate A range");
999 register_static_memory_manager_range(
1000 105,
1001 119,
1002 "crate_b",
1003 MemoryManagerRangeMode::Reserved,
1004 None,
1005 )
1006 .expect("crate B range");
1007
1008 let err = bootstrap_default_memory_manager().expect_err("overlap must fail");
1009 assert!(matches!(
1010 err,
1011 RuntimeBootstrapError::Range(
1012 MemoryManagerRangeAuthorityError::OverlappingRanges { .. }
1013 )
1014 ));
1015 }
1016
1017 #[test]
1018 fn duplicate_stable_keys_fail() {
1019 let _guard = TEST_REGISTRY_LOCK.lock().expect("test lock poisoned");
1020 reset_for_tests();
1021 register_static_memory_manager_declaration(100, "crate_a", "users", "app.users.v1")
1022 .expect("first declaration");
1023 register_static_memory_manager_declaration(101, "crate_b", "users", "app.users.v1")
1024 .expect("second declaration");
1025
1026 let err = bootstrap_default_memory_manager().expect_err("duplicate key must fail");
1027 assert!(matches!(
1028 err,
1029 RuntimeBootstrapError::Registry(StaticMemoryDeclarationError::Declaration(
1030 crate::DeclarationSnapshotError::DuplicateStableKey(_)
1031 ))
1032 ));
1033 }
1034
1035 #[test]
1036 fn duplicate_memory_manager_ids_fail() {
1037 let _guard = TEST_REGISTRY_LOCK.lock().expect("test lock poisoned");
1038 reset_for_tests();
1039 register_static_memory_manager_declaration(100, "crate_a", "users", "crate_a.users.v1")
1040 .expect("first declaration");
1041 register_static_memory_manager_declaration(100, "crate_b", "orders", "crate_b.orders.v1")
1042 .expect("second declaration");
1043
1044 let err = bootstrap_default_memory_manager().expect_err("duplicate slot must fail");
1045 assert!(matches!(
1046 err,
1047 RuntimeBootstrapError::Registry(StaticMemoryDeclarationError::Declaration(
1048 crate::DeclarationSnapshotError::DuplicateSlot(_)
1049 ))
1050 ));
1051 }
1052
1053 #[test]
1054 fn out_of_range_memory_declaration_fails_when_ranges_are_declared() {
1055 let _guard = TEST_REGISTRY_LOCK.lock().expect("test lock poisoned");
1056 reset_for_tests();
1057 register_static_memory_manager_range(
1058 100,
1059 109,
1060 "crate_a",
1061 MemoryManagerRangeMode::Reserved,
1062 None,
1063 )
1064 .expect("crate A range");
1065 register_static_memory_manager_declaration(120, "crate_a", "users", "crate_a.users.v1")
1066 .expect("out-of-range declaration");
1067
1068 let err = bootstrap_default_memory_manager().expect_err("out of range must fail");
1069 assert!(matches!(
1070 err,
1071 RuntimeBootstrapError::Validation(crate::AllocationValidationError::Policy(
1072 RuntimePolicyError::Range(MemoryManagerRangeAuthorityError::UnclaimedId {
1073 id: 120
1074 })
1075 ))
1076 ));
1077 }
1078
1079 #[test]
1080 fn late_registration_after_bootstrap_fails() {
1081 let _guard = TEST_REGISTRY_LOCK.lock().expect("test lock poisoned");
1082 reset_for_tests();
1083 register_static_memory_manager_declaration(100, "crate_a", "users", "crate_a.users.v1")
1084 .expect("declaration");
1085 bootstrap_default_memory_manager().expect("bootstrap");
1086
1087 let err = register_static_memory_manager_declaration(
1088 101,
1089 "crate_a",
1090 "orders",
1091 "crate_a.orders.v1",
1092 )
1093 .expect_err("late registration must fail");
1094 assert_eq!(err, StaticMemoryDeclarationError::RegistrySealed);
1095 }
1096
1097 #[test]
1098 fn late_eager_init_registration_after_bootstrap_fails() {
1099 let _guard = TEST_REGISTRY_LOCK.lock().expect("test lock poisoned");
1100 reset_for_tests();
1101 register_static_memory_manager_declaration(100, "crate_a", "users", "crate_a.users.v1")
1102 .expect("declaration");
1103 bootstrap_default_memory_manager().expect("bootstrap");
1104
1105 let err = std::panic::catch_unwind(|| defer_eager_init(mark_eager_init))
1106 .expect_err("late eager-init registration must fail");
1107
1108 let message = err
1109 .downcast_ref::<String>()
1110 .map(String::as_str)
1111 .or_else(|| err.downcast_ref::<&str>().copied())
1112 .expect("panic message");
1113 assert!(message.contains("after runtime bootstrap"));
1114 }
1115
1116 #[test]
1117 fn eager_init_runs_before_snapshot_seal() {
1118 let _guard = TEST_REGISTRY_LOCK.lock().expect("test lock poisoned");
1119 reset_for_tests();
1120 EAGER_INIT_RAN.store(false, Ordering::SeqCst);
1121 register_static_memory_manager_range(
1122 100,
1123 109,
1124 "crate_a",
1125 MemoryManagerRangeMode::Reserved,
1126 None,
1127 )
1128 .expect("crate A range");
1129 defer_eager_init(mark_eager_init);
1130
1131 let validated = bootstrap_default_memory_manager().expect("bootstrap");
1132
1133 assert!(EAGER_INIT_RAN.load(Ordering::SeqCst));
1134 assert!(
1135 validated
1136 .declarations()
1137 .iter()
1138 .any(|declaration| declaration.stable_key().as_str() == "crate_a.audit.v1")
1139 );
1140 }
1141
1142 #[test]
1143 fn direct_user_can_bootstrap_and_open_without_canic() {
1144 let _guard = TEST_REGISTRY_LOCK.lock().expect("test lock poisoned");
1145 reset_for_tests();
1146 register_static_memory_manager_range(
1147 120,
1148 129,
1149 "icydb",
1150 MemoryManagerRangeMode::Reserved,
1151 None,
1152 )
1153 .expect("icydb range");
1154 register_static_memory_manager_declaration(120, "icydb", "users", "icydb.users.data.v1")
1155 .expect("icydb declaration");
1156
1157 bootstrap_default_memory_manager().expect("bootstrap");
1158 open_default_memory_manager_memory("icydb.users.data.v1", 120).expect("open memory");
1159 }
1160
1161 #[test]
1162 fn diagnostic_export_reports_default_memory_manager_sizes() {
1163 let _guard = TEST_REGISTRY_LOCK.lock().expect("test lock poisoned");
1164 reset_for_tests();
1165 register_static_memory_manager_range(
1166 130,
1167 139,
1168 "diagnostics",
1169 MemoryManagerRangeMode::Reserved,
1170 None,
1171 )
1172 .expect("diagnostics range");
1173 register_static_memory_manager_declaration(
1174 130,
1175 "diagnostics",
1176 "users",
1177 "diagnostics.users.v1",
1178 )
1179 .expect("diagnostics declaration");
1180
1181 bootstrap_default_memory_manager().expect("bootstrap");
1182 let memory =
1183 open_default_memory_manager_memory("diagnostics.users.v1", 130).expect("open memory");
1184 let old_size = memory.size();
1185 memory.grow(2);
1186
1187 let export = default_memory_manager_diagnostic_export().expect("diagnostic export");
1188 let recovery =
1189 default_memory_manager_commit_recovery_diagnostic().expect("recovery diagnostic");
1190 let record = export
1191 .records
1192 .iter()
1193 .find(|record| record.allocation.stable_key().as_str() == "diagnostics.users.v1")
1194 .expect("diagnostic allocation");
1195
1196 assert_eq!(
1197 recovery.authoritative_generation,
1198 Some(export.current_generation)
1199 );
1200 assert_eq!(
1201 record.memory_size,
1202 Some(DiagnosticMemorySize::from_wasm_pages(old_size + 2))
1203 );
1204 }
1205
1206 #[test]
1207 fn doctor_report_preflights_before_bootstrap() {
1208 let _guard = TEST_REGISTRY_LOCK.lock().expect("test lock poisoned");
1209 reset_for_tests();
1210 register_static_memory_manager_range(
1211 240,
1212 240,
1213 "doctor_preflight",
1214 MemoryManagerRangeMode::Reserved,
1215 None,
1216 )
1217 .expect("doctor range");
1218 register_static_memory_manager_declaration(
1219 240,
1220 "doctor_preflight",
1221 "users",
1222 "doctor_preflight.users.v1",
1223 )
1224 .expect("doctor declaration");
1225
1226 let report = default_memory_manager_doctor_report();
1227
1228 assert!(!report.bootstrapped);
1229 assert_eq!(report.registered_declarations.len(), 1);
1230 assert!(report.range_authority.effective_authority.is_some());
1231 assert_eq!(
1232 report.validation.status,
1233 crate::DiagnosticCheckStatus::Passed
1234 );
1235 assert!(report.commit_recovery.is_some());
1236 assert!(matches!(
1237 report.stable_cell.status,
1238 crate::DiagnosticStableCellStatus::Empty | crate::DiagnosticStableCellStatus::Readable
1239 ));
1240 }
1241
1242 #[test]
1243 fn doctor_report_includes_recovered_ledger_and_memory_sizes_after_bootstrap() {
1244 let _guard = TEST_REGISTRY_LOCK.lock().expect("test lock poisoned");
1245 reset_for_tests();
1246 register_static_memory_manager_range(
1247 241,
1248 241,
1249 "doctor_runtime",
1250 MemoryManagerRangeMode::Reserved,
1251 None,
1252 )
1253 .expect("doctor range");
1254 register_static_memory_manager_declaration(
1255 241,
1256 "doctor_runtime",
1257 "orders",
1258 "doctor_runtime.orders.v1",
1259 )
1260 .expect("doctor declaration");
1261
1262 bootstrap_default_memory_manager().expect("bootstrap");
1263 let memory = open_default_memory_manager_memory("doctor_runtime.orders.v1", 241)
1264 .expect("open memory");
1265 let old_size = memory.size();
1266 memory.grow(1);
1267
1268 let report = default_memory_manager_doctor_report();
1269 let ledger = report.ledger.expect("recovered ledger export");
1270 let record = ledger
1271 .records
1272 .iter()
1273 .find(|record| record.allocation.stable_key().as_str() == "doctor_runtime.orders.v1")
1274 .expect("doctor allocation");
1275
1276 assert!(report.bootstrapped);
1277 assert_eq!(
1278 report.stable_cell.status,
1279 crate::DiagnosticStableCellStatus::Readable
1280 );
1281 assert_eq!(
1282 report.validation.status,
1283 crate::DiagnosticCheckStatus::Passed
1284 );
1285 assert_eq!(
1286 record.memory_size,
1287 Some(DiagnosticMemorySize::from_wasm_pages(old_size + 1))
1288 );
1289 }
1290
1291 #[test]
1292 fn doctor_report_captures_validation_failure() {
1293 let _guard = TEST_REGISTRY_LOCK.lock().expect("test lock poisoned");
1294 reset_for_tests();
1295 register_static_memory_manager_declaration(
1296 242,
1297 "doctor_failure_a",
1298 "users",
1299 "doctor_failure.users.v1",
1300 )
1301 .expect("first declaration");
1302 register_static_memory_manager_declaration(
1303 243,
1304 "doctor_failure_b",
1305 "orders",
1306 "doctor_failure.users.v1",
1307 )
1308 .expect("second declaration");
1309
1310 let report = default_memory_manager_doctor_report();
1311
1312 assert_eq!(
1313 report.validation.status,
1314 crate::DiagnosticCheckStatus::Failed
1315 );
1316 assert!(
1317 report
1318 .validation
1319 .message
1320 .expect("validation failure message")
1321 .contains("declared more than once")
1322 );
1323 }
1324}