1use crate::{
2 AllocationBootstrap, AllocationDeclaration, AllocationHistory, AllocationLedger,
3 AllocationPolicy, AllocationSlotDescriptor, CborLedgerCodec, DeclarationSnapshot,
4 StableCellLedgerRecord, StableKey, ValidatedAllocations,
5 registry::{
6 StaticMemoryDeclaration, StaticMemoryDeclarationError, StaticMemoryRangeDeclaration,
7 seal_static_memory_registry, static_memory_declarations, static_memory_range_declarations,
8 },
9 slot::{
10 IC_MEMORY_AUTHORITY_OWNER, IC_MEMORY_AUTHORITY_PURPOSE, IC_MEMORY_LEDGER_LABEL,
11 IC_MEMORY_LEDGER_STABLE_KEY, MEMORY_MANAGER_LEDGER_ID, MemoryManagerAuthorityRecord,
12 MemoryManagerIdRange, MemoryManagerRangeAuthority, MemoryManagerRangeAuthorityError,
13 MemoryManagerRangeMode, MemoryManagerSlotError,
14 },
15};
16use ic_stable_structures::{
17 Cell, DefaultMemoryImpl,
18 memory_manager::{MemoryId, MemoryManager, VirtualMemory},
19};
20use std::{
21 cell::RefCell,
22 collections::BTreeMap,
23 convert::Infallible,
24 sync::{
25 Mutex,
26 atomic::{AtomicBool, Ordering},
27 },
28};
29
30type DefaultLedgerCell = Cell<StableCellLedgerRecord, VirtualMemory<DefaultMemoryImpl>>;
31
32thread_local! {
33 static DEFAULT_MEMORY_MANAGER: MemoryManager<DefaultMemoryImpl> =
34 MemoryManager::init(DefaultMemoryImpl::default());
35 static DEFAULT_LEDGER_CELL: RefCell<Option<DefaultLedgerCell>> = const {
36 RefCell::new(None)
37 };
38}
39
40static EAGER_INIT_HOOKS: Mutex<Vec<fn()>> = Mutex::new(Vec::new());
41static VALIDATED_ALLOCATIONS: Mutex<Option<ValidatedAllocations>> = Mutex::new(None);
42static BOOTSTRAPPED: AtomicBool = AtomicBool::new(false);
43
44#[derive(Debug, thiserror::Error)]
49pub enum RuntimeBootstrapError<P> {
50 #[error(transparent)]
52 Registry(#[from] StaticMemoryDeclarationError),
53 #[error(transparent)]
55 Range(#[from] MemoryManagerRangeAuthorityError),
56 #[error(transparent)]
58 LedgerIntegrity(#[from] crate::LedgerIntegrityError),
59 #[error(transparent)]
61 LedgerCommit(#[from] crate::LedgerCommitError<serde_cbor::Error>),
62 #[error(transparent)]
64 Validation(#[from] crate::AllocationValidationError<RuntimePolicyError<P>>),
65 #[error(transparent)]
67 Staging(#[from] crate::AllocationStageError),
68 #[error("ic-memory runtime lock poisoned")]
70 RuntimeLockPoisoned,
71}
72
73#[derive(Clone, Debug, Eq, thiserror::Error, PartialEq)]
78pub enum RuntimeOpenError {
79 #[error("ic-memory runtime has not completed bootstrap validation")]
81 NotBootstrapped,
82 #[error("ic-memory runtime lock poisoned")]
84 RuntimeLockPoisoned,
85 #[error(transparent)]
87 StableKey(#[from] crate::StableKeyError),
88 #[error("stable key '{0}' was not validated by ic-memory runtime bootstrap")]
90 StableKeyNotValidated(String),
91 #[error(transparent)]
93 MemoryManagerSlot(#[from] MemoryManagerSlotError),
94 #[error(
96 "stable key '{stable_key}' is validated for MemoryManager ID {validated_id}, not requested ID {requested_id}"
97 )]
98 MemoryIdMismatch {
99 stable_key: String,
101 validated_id: u8,
103 requested_id: u8,
105 },
106}
107
108#[derive(Clone, Debug, Eq, thiserror::Error, PartialEq)]
113pub enum RuntimePolicyError<P> {
114 #[error(transparent)]
116 Range(#[from] MemoryManagerRangeAuthorityError),
117 #[error("runtime declaration metadata is missing for stable key '{0}'")]
119 MissingDeclarationMetadata(String),
120 #[error("stable key '{stable_key}' is reserved to authority '{expected_authority}'")]
122 ReservedStableKeyAuthority {
123 stable_key: String,
125 expected_authority: &'static str,
127 },
128 #[error(transparent)]
130 Custom(P),
131}
132
133pub fn defer_eager_init(f: fn()) {
135 EAGER_INIT_HOOKS
136 .lock()
137 .expect("ic-memory eager-init queue poisoned")
138 .push(f);
139}
140
141#[must_use]
143pub fn is_default_memory_manager_bootstrapped() -> bool {
144 BOOTSTRAPPED.load(Ordering::SeqCst)
145}
146
147pub fn validated_allocations() -> Result<ValidatedAllocations, RuntimeOpenError> {
149 if !is_default_memory_manager_bootstrapped() {
150 return Err(RuntimeOpenError::NotBootstrapped);
151 }
152 VALIDATED_ALLOCATIONS
153 .lock()
154 .map_err(|_| RuntimeOpenError::RuntimeLockPoisoned)?
155 .clone()
156 .ok_or(RuntimeOpenError::NotBootstrapped)
157}
158
159pub fn bootstrap_default_memory_manager()
161-> Result<ValidatedAllocations, RuntimeBootstrapError<Infallible>> {
162 bootstrap_default_memory_manager_with_policy(&NoopPolicy)
163}
164
165pub fn bootstrap_default_memory_manager_with_policy<P: AllocationPolicy>(
167 policy: &P,
168) -> Result<ValidatedAllocations, RuntimeBootstrapError<P::Error>> {
169 if let Ok(validated) = validated_allocations() {
170 return Ok(validated);
171 }
172
173 run_eager_init_hooks();
174
175 let registered_declarations = static_memory_declarations()?;
176 let registered_ranges = static_memory_range_declarations()?;
177 let user_ranges_registered = !registered_ranges.is_empty();
178 let declaration_metadata = declaration_metadata(®istered_declarations);
179 let range_authority = range_authority(registered_ranges)?;
180 let snapshot = declaration_snapshot(registered_declarations)?;
181 seal_static_memory_registry()?;
182 let policy = RuntimeMemoryManagerPolicy {
183 range_authority,
184 user_ranges_registered,
185 declaration_metadata,
186 custom_policy: policy,
187 };
188 let genesis = AllocationLedger::new(
189 crate::CURRENT_LEDGER_SCHEMA_VERSION,
190 crate::CURRENT_PHYSICAL_FORMAT_ID,
191 0,
192 AllocationHistory::default(),
193 )?;
194
195 let validated = with_default_ledger_cell(
196 |cell| -> Result<ValidatedAllocations, RuntimeBootstrapError<P::Error>> {
197 let mut record = cell.get().clone();
198 let mut bootstrap = AllocationBootstrap::new(record.store_mut());
199 let commit = bootstrap
200 .initialize_validate_and_commit(&CborLedgerCodec, &genesis, snapshot, &policy, None)
201 .map_err(runtime_bootstrap_error_from_bootstrap)?;
202 cell.set(record);
203 Ok(commit.validated)
204 },
205 )?;
206
207 publish_validated_allocations(validated.clone())?;
208 BOOTSTRAPPED.store(true, Ordering::SeqCst);
209 Ok(validated)
210}
211
212pub fn open_default_memory_manager_memory(
214 stable_key: &str,
215 id: u8,
216) -> Result<VirtualMemory<DefaultMemoryImpl>, RuntimeOpenError> {
217 let key = StableKey::parse(stable_key)?;
218 let validated = validated_allocations()?;
219 let slot = validated
220 .slot_for(&key)
221 .ok_or_else(|| RuntimeOpenError::StableKeyNotValidated(stable_key.to_string()))?;
222 let validated_id = slot.memory_manager_id()?;
223 if validated_id != id {
224 return Err(RuntimeOpenError::MemoryIdMismatch {
225 stable_key: stable_key.to_string(),
226 validated_id,
227 requested_id: id,
228 });
229 }
230 Ok(default_memory_manager_memory(id))
231}
232
233fn run_eager_init_hooks() {
234 let hooks = {
235 let mut hooks = EAGER_INIT_HOOKS
236 .lock()
237 .expect("ic-memory eager-init queue poisoned");
238 std::mem::take(&mut *hooks)
239 };
240
241 for hook in hooks {
242 hook();
243 }
244}
245
246fn with_default_ledger_cell<T>(op: impl FnOnce(&mut DefaultLedgerCell) -> T) -> T {
247 DEFAULT_LEDGER_CELL.with(|cell| {
248 let mut cell = cell.borrow_mut();
249 if cell.is_none() {
250 *cell = Some(Cell::init(
251 default_memory_manager_memory(MEMORY_MANAGER_LEDGER_ID),
252 StableCellLedgerRecord::default(),
253 ));
254 }
255 op(cell.as_mut().expect("default ledger cell initialized"))
256 })
257}
258
259fn default_memory_manager_memory(id: u8) -> VirtualMemory<DefaultMemoryImpl> {
260 DEFAULT_MEMORY_MANAGER.with(|manager| manager.get(MemoryId::new(id)))
261}
262
263fn publish_validated_allocations<P>(
264 validated: ValidatedAllocations,
265) -> Result<(), RuntimeBootstrapError<P>> {
266 *VALIDATED_ALLOCATIONS
267 .lock()
268 .map_err(|_| RuntimeBootstrapError::RuntimeLockPoisoned)? = Some(validated);
269 Ok(())
270}
271
272fn declaration_snapshot(
273 registrations: Vec<StaticMemoryDeclaration>,
274) -> Result<DeclarationSnapshot, StaticMemoryDeclarationError> {
275 let mut declarations = Vec::with_capacity(registrations.len() + 1);
276 declarations.push(internal_ledger_declaration()?);
277 declarations.extend(
278 registrations
279 .into_iter()
280 .map(StaticMemoryDeclaration::into_declaration),
281 );
282 DeclarationSnapshot::new(declarations).map_err(StaticMemoryDeclarationError::Declaration)
283}
284
285fn declaration_metadata(registrations: &[StaticMemoryDeclaration]) -> BTreeMap<String, String> {
286 let mut metadata = BTreeMap::new();
287 metadata.insert(
288 IC_MEMORY_LEDGER_STABLE_KEY.to_string(),
289 IC_MEMORY_AUTHORITY_OWNER.to_string(),
290 );
291 for registration in registrations {
292 metadata.insert(
293 registration.declaration().stable_key().as_str().to_string(),
294 registration.declaring_crate().to_string(),
295 );
296 }
297 metadata
298}
299
300fn range_authority(
301 registrations: Vec<StaticMemoryRangeDeclaration>,
302) -> Result<MemoryManagerRangeAuthority, MemoryManagerRangeAuthorityError> {
303 let mut records = Vec::with_capacity(registrations.len() + 1);
304 records.push(internal_ledger_range()?);
305 records.extend(
306 registrations
307 .into_iter()
308 .map(StaticMemoryRangeDeclaration::into_record),
309 );
310 MemoryManagerRangeAuthority::from_records(records)
311}
312
313fn internal_ledger_declaration() -> Result<AllocationDeclaration, crate::DeclarationSnapshotError> {
314 AllocationDeclaration::memory_manager(
315 IC_MEMORY_LEDGER_STABLE_KEY,
316 MEMORY_MANAGER_LEDGER_ID,
317 IC_MEMORY_LEDGER_LABEL,
318 )
319}
320
321fn internal_ledger_range() -> Result<MemoryManagerAuthorityRecord, MemoryManagerRangeAuthorityError>
322{
323 MemoryManagerAuthorityRecord::new(
324 MemoryManagerIdRange::new(
325 MEMORY_MANAGER_LEDGER_ID,
326 crate::MEMORY_MANAGER_GOVERNANCE_MAX_ID,
327 )?,
328 IC_MEMORY_AUTHORITY_OWNER,
329 MemoryManagerRangeMode::Reserved,
330 Some(IC_MEMORY_AUTHORITY_PURPOSE.to_string()),
331 )
332}
333
334fn runtime_bootstrap_error_from_bootstrap<P>(
335 err: crate::BootstrapError<serde_cbor::Error, RuntimePolicyError<P>>,
336) -> RuntimeBootstrapError<P> {
337 match err {
338 crate::BootstrapError::Ledger(err) => RuntimeBootstrapError::LedgerCommit(err),
339 crate::BootstrapError::Validation(err) => RuntimeBootstrapError::Validation(err),
340 crate::BootstrapError::Staging(err) => RuntimeBootstrapError::Staging(err),
341 }
342}
343
344struct RuntimeMemoryManagerPolicy<'a, P> {
345 range_authority: MemoryManagerRangeAuthority,
346 user_ranges_registered: bool,
347 declaration_metadata: BTreeMap<String, String>,
348 custom_policy: &'a P,
349}
350
351impl<P: AllocationPolicy> AllocationPolicy for RuntimeMemoryManagerPolicy<'_, P> {
352 type Error = RuntimePolicyError<P::Error>;
353
354 fn validate_key(&self, key: &StableKey) -> Result<(), Self::Error> {
355 let declaring_crate = self.declaring_crate(key)?;
356 if crate::is_ic_memory_stable_key(key.as_str())
357 && declaring_crate != IC_MEMORY_AUTHORITY_OWNER
358 {
359 return Err(RuntimePolicyError::ReservedStableKeyAuthority {
360 stable_key: key.as_str().to_string(),
361 expected_authority: IC_MEMORY_AUTHORITY_OWNER,
362 });
363 }
364 self.custom_policy
365 .validate_key(key)
366 .map_err(RuntimePolicyError::Custom)
367 }
368
369 fn validate_slot(
370 &self,
371 key: &StableKey,
372 slot: &AllocationSlotDescriptor,
373 ) -> Result<(), Self::Error> {
374 self.validate_runtime_range(key, slot)?;
375 self.custom_policy
376 .validate_slot(key, slot)
377 .map_err(RuntimePolicyError::Custom)
378 }
379
380 fn validate_reserved_slot(
381 &self,
382 key: &StableKey,
383 slot: &AllocationSlotDescriptor,
384 ) -> Result<(), Self::Error> {
385 self.validate_runtime_range(key, slot)?;
386 self.custom_policy
387 .validate_reserved_slot(key, slot)
388 .map_err(RuntimePolicyError::Custom)
389 }
390}
391
392impl<P: AllocationPolicy> RuntimeMemoryManagerPolicy<'_, P> {
393 fn declaring_crate(&self, key: &StableKey) -> Result<&str, RuntimePolicyError<P::Error>> {
394 self.declaration_metadata
395 .get(key.as_str())
396 .map(String::as_str)
397 .ok_or_else(|| RuntimePolicyError::MissingDeclarationMetadata(key.as_str().to_string()))
398 }
399
400 fn validate_runtime_range(
401 &self,
402 key: &StableKey,
403 slot: &AllocationSlotDescriptor,
404 ) -> Result<(), RuntimePolicyError<P::Error>> {
405 let declaring_crate = self.declaring_crate(key)?;
406 if declaring_crate == IC_MEMORY_AUTHORITY_OWNER || self.user_ranges_registered {
407 self.range_authority
408 .validate_slot_authority(slot, declaring_crate)?;
409 return Ok(());
410 }
411
412 let id = slot
413 .memory_manager_id()
414 .map_err(MemoryManagerRangeAuthorityError::Slot)?;
415 if self
416 .range_authority
417 .authority_for_id(id)
418 .map_err(RuntimePolicyError::Range)?
419 .is_some()
420 {
421 self.range_authority
422 .validate_slot_authority(slot, declaring_crate)?;
423 }
424 Ok(())
425 }
426}
427
428struct NoopPolicy;
429
430impl AllocationPolicy for NoopPolicy {
431 type Error = Infallible;
432
433 fn validate_key(&self, _key: &StableKey) -> Result<(), Self::Error> {
434 Ok(())
435 }
436
437 fn validate_slot(
438 &self,
439 _key: &StableKey,
440 _slot: &AllocationSlotDescriptor,
441 ) -> Result<(), Self::Error> {
442 Ok(())
443 }
444
445 fn validate_reserved_slot(
446 &self,
447 _key: &StableKey,
448 _slot: &AllocationSlotDescriptor,
449 ) -> Result<(), Self::Error> {
450 Ok(())
451 }
452}
453
454#[cfg(test)]
455pub(crate) fn reset_for_tests() {
456 crate::registry::reset_static_memory_declarations_for_tests();
457 EAGER_INIT_HOOKS
458 .lock()
459 .expect("ic-memory eager-init queue poisoned")
460 .clear();
461 *VALIDATED_ALLOCATIONS
462 .lock()
463 .expect("ic-memory runtime validation state poisoned") = None;
464 BOOTSTRAPPED.store(false, Ordering::SeqCst);
465 DEFAULT_LEDGER_CELL.with_borrow_mut(|cell| {
466 *cell = None;
467 });
468}
469
470#[cfg(test)]
471mod tests {
472 use super::*;
473 use crate::registry::{
474 TEST_REGISTRY_LOCK, register_static_memory_manager_declaration,
475 register_static_memory_manager_range,
476 };
477 use std::sync::atomic::{AtomicBool, Ordering};
478
479 static EAGER_INIT_RAN: AtomicBool = AtomicBool::new(false);
480
481 fn register_crate_a() {
482 register_static_memory_manager_range(
483 100,
484 109,
485 "crate_a",
486 MemoryManagerRangeMode::Reserved,
487 None,
488 )
489 .expect("crate A range");
490 register_static_memory_manager_declaration(100, "crate_a", "users", "crate_a.users.v1")
491 .expect("crate A memory");
492 }
493
494 fn register_crate_b() {
495 register_static_memory_manager_range(
496 110,
497 119,
498 "crate_b",
499 MemoryManagerRangeMode::Reserved,
500 None,
501 )
502 .expect("crate B range");
503 register_static_memory_manager_declaration(110, "crate_b", "orders", "crate_b.orders.v1")
504 .expect("crate B memory");
505 }
506
507 fn mark_eager_init() {
508 EAGER_INIT_RAN.store(true, Ordering::SeqCst);
509 register_static_memory_manager_declaration(101, "crate_a", "audit", "crate_a.audit.v1")
510 .expect("eager-init declaration");
511 }
512
513 #[test]
514 fn multi_crate_declarations_compose_into_one_bootstrap() {
515 let _guard = TEST_REGISTRY_LOCK.lock().expect("test lock poisoned");
516 reset_for_tests();
517 register_crate_a();
518 register_crate_b();
519
520 let validated = bootstrap_default_memory_manager().expect("bootstrap");
521
522 assert_eq!(validated.declarations().len(), 3);
523 assert!(
524 validated
525 .declarations()
526 .iter()
527 .any(|declaration| declaration.stable_key().as_str() == "crate_a.users.v1")
528 );
529 assert!(
530 validated
531 .declarations()
532 .iter()
533 .any(|declaration| declaration.stable_key().as_str() == "crate_b.orders.v1")
534 );
535 }
536
537 #[test]
538 fn conflicting_ranges_fail() {
539 let _guard = TEST_REGISTRY_LOCK.lock().expect("test lock poisoned");
540 reset_for_tests();
541 register_static_memory_manager_range(
542 100,
543 110,
544 "crate_a",
545 MemoryManagerRangeMode::Reserved,
546 None,
547 )
548 .expect("crate A range");
549 register_static_memory_manager_range(
550 105,
551 119,
552 "crate_b",
553 MemoryManagerRangeMode::Reserved,
554 None,
555 )
556 .expect("crate B range");
557
558 let err = bootstrap_default_memory_manager().expect_err("overlap must fail");
559 assert!(matches!(
560 err,
561 RuntimeBootstrapError::Range(
562 MemoryManagerRangeAuthorityError::OverlappingRanges { .. }
563 )
564 ));
565 }
566
567 #[test]
568 fn duplicate_stable_keys_fail() {
569 let _guard = TEST_REGISTRY_LOCK.lock().expect("test lock poisoned");
570 reset_for_tests();
571 register_static_memory_manager_declaration(100, "crate_a", "users", "app.users.v1")
572 .expect("first declaration");
573 register_static_memory_manager_declaration(101, "crate_b", "users", "app.users.v1")
574 .expect("second declaration");
575
576 let err = bootstrap_default_memory_manager().expect_err("duplicate key must fail");
577 assert!(matches!(
578 err,
579 RuntimeBootstrapError::Registry(StaticMemoryDeclarationError::Declaration(
580 crate::DeclarationSnapshotError::DuplicateStableKey(_)
581 ))
582 ));
583 }
584
585 #[test]
586 fn duplicate_memory_manager_ids_fail() {
587 let _guard = TEST_REGISTRY_LOCK.lock().expect("test lock poisoned");
588 reset_for_tests();
589 register_static_memory_manager_declaration(100, "crate_a", "users", "crate_a.users.v1")
590 .expect("first declaration");
591 register_static_memory_manager_declaration(100, "crate_b", "orders", "crate_b.orders.v1")
592 .expect("second declaration");
593
594 let err = bootstrap_default_memory_manager().expect_err("duplicate slot must fail");
595 assert!(matches!(
596 err,
597 RuntimeBootstrapError::Registry(StaticMemoryDeclarationError::Declaration(
598 crate::DeclarationSnapshotError::DuplicateSlot(_)
599 ))
600 ));
601 }
602
603 #[test]
604 fn out_of_range_memory_declaration_fails_when_ranges_are_declared() {
605 let _guard = TEST_REGISTRY_LOCK.lock().expect("test lock poisoned");
606 reset_for_tests();
607 register_static_memory_manager_range(
608 100,
609 109,
610 "crate_a",
611 MemoryManagerRangeMode::Reserved,
612 None,
613 )
614 .expect("crate A range");
615 register_static_memory_manager_declaration(120, "crate_a", "users", "crate_a.users.v1")
616 .expect("out-of-range declaration");
617
618 let err = bootstrap_default_memory_manager().expect_err("out of range must fail");
619 assert!(matches!(
620 err,
621 RuntimeBootstrapError::Validation(crate::AllocationValidationError::Policy(
622 RuntimePolicyError::Range(MemoryManagerRangeAuthorityError::UnclaimedId {
623 id: 120
624 })
625 ))
626 ));
627 }
628
629 #[test]
630 fn late_registration_after_bootstrap_fails() {
631 let _guard = TEST_REGISTRY_LOCK.lock().expect("test lock poisoned");
632 reset_for_tests();
633 register_static_memory_manager_declaration(100, "crate_a", "users", "crate_a.users.v1")
634 .expect("declaration");
635 bootstrap_default_memory_manager().expect("bootstrap");
636
637 let err = register_static_memory_manager_declaration(
638 101,
639 "crate_a",
640 "orders",
641 "crate_a.orders.v1",
642 )
643 .expect_err("late registration must fail");
644 assert_eq!(err, StaticMemoryDeclarationError::RegistrySealed);
645 }
646
647 #[test]
648 fn eager_init_runs_before_snapshot_seal() {
649 let _guard = TEST_REGISTRY_LOCK.lock().expect("test lock poisoned");
650 reset_for_tests();
651 EAGER_INIT_RAN.store(false, Ordering::SeqCst);
652 register_static_memory_manager_range(
653 100,
654 109,
655 "crate_a",
656 MemoryManagerRangeMode::Reserved,
657 None,
658 )
659 .expect("crate A range");
660 defer_eager_init(mark_eager_init);
661
662 let validated = bootstrap_default_memory_manager().expect("bootstrap");
663
664 assert!(EAGER_INIT_RAN.load(Ordering::SeqCst));
665 assert!(
666 validated
667 .declarations()
668 .iter()
669 .any(|declaration| declaration.stable_key().as_str() == "crate_a.audit.v1")
670 );
671 }
672
673 #[test]
674 fn direct_user_can_bootstrap_and_open_without_canic() {
675 let _guard = TEST_REGISTRY_LOCK.lock().expect("test lock poisoned");
676 reset_for_tests();
677 register_static_memory_manager_range(
678 120,
679 129,
680 "icydb",
681 MemoryManagerRangeMode::Reserved,
682 None,
683 )
684 .expect("icydb range");
685 register_static_memory_manager_declaration(120, "icydb", "users", "icydb.users.data.v1")
686 .expect("icydb declaration");
687
688 bootstrap_default_memory_manager().expect("bootstrap");
689 open_default_memory_manager_memory("icydb.users.data.v1", 120).expect("open memory");
690 }
691}