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