Skip to main content

canic_core/memory/runtime/
registry.rs

1use super::super::manager::{self, RawStableMemoryState};
2use super::super::registry::{
3    MemoryRange, MemoryRangeEntry, MemoryRangeSnapshot, MemoryRegistry, MemoryRegistryEntry,
4    MemoryRegistryError, PendingRegistration, drain_pending_ranges, drain_pending_registrations,
5};
6use super::super::{ledger, policy::CanicMemoryManagerPolicy};
7use ic_memory::{
8    AllocationDeclaration, AllocationPolicy, AllocationSlotDescriptor, AllocationValidationError,
9    DeclarationSnapshot, DeclarationSnapshotError, MemoryManagerSlotError, SchemaMetadata,
10    StableKey, ValidatedAllocations, validate_allocations,
11};
12#[cfg(test)]
13use std::cell::Cell;
14#[cfg(not(test))]
15use std::sync::atomic::{AtomicBool, Ordering};
16use std::{cell::RefCell, collections::BTreeMap};
17
18#[cfg(not(test))]
19static MEMORY_REGISTRY_INITIALIZED: AtomicBool = AtomicBool::new(false);
20
21#[cfg(test)]
22thread_local! {
23    static MEMORY_REGISTRY_INITIALIZED: Cell<bool> = const { Cell::new(false) };
24}
25
26thread_local! {
27    static VALIDATED_ALLOCATIONS: RefCell<Option<ValidatedAllocations>> = const {
28        RefCell::new(None)
29    };
30}
31
32///
33/// MemoryRegistryInitSummary
34///
35/// Substrate-level summary of registry state after initialization.
36/// This is intended for diagnostics and testing only.
37/// It is NOT a stable API contract or external view.
38///
39
40#[derive(Debug)]
41pub struct MemoryRegistryInitSummary {
42    /// Reserved owner ranges after initialization completes.
43    pub ranges: Vec<(String, MemoryRange)>,
44    /// Registered memory IDs after initialization completes.
45    pub entries: Vec<(u8, MemoryRegistryEntry)>,
46}
47
48///
49/// MemoryRegistryRuntime
50///
51/// Substrate runtime controller responsible for initializing the
52/// global memory registry.
53///
54/// This type performs mechanical coordination only:
55/// - ordering
56/// - conflict detection
57/// - idempotent initialization
58///
59/// It encodes no application semantics.
60///
61pub struct MemoryRegistryRuntime;
62
63impl MemoryRegistryRuntime {
64    /// Initialize the memory registry.
65    ///
66    /// - Optionally reserves an initial range for the caller.
67    /// - Applies all deferred range reservations.
68    /// - Applies all deferred ID registrations.
69    ///
70    /// This function is idempotent for the same initial range.
71    pub fn init(
72        initial_range: Option<(&str, u8, u8)>,
73    ) -> Result<MemoryRegistryInitSummary, MemoryRegistryError> {
74        let raw_state = manager::classify_raw_stable_memory();
75        validate_raw_stable_memory_state(raw_state)?;
76        ledger::validate_bootstrap_state_before_cell_init(raw_state)?;
77
78        // Apply deferred range reservations deterministically
79        let mut ranges = drain_pending_ranges();
80        ranges.sort_by_key(|(_, start, _)| *start);
81
82        // Apply deferred registrations deterministically
83        let mut regs = drain_pending_registrations();
84        regs.sort_by_key(|registration| registration.id);
85        let has_runtime_declarations = !regs.is_empty();
86        let declaration_snapshot = validate_current_registration_snapshot(&regs)?;
87
88        MemoryRegistry::reserve_internal_layout_ledger()?;
89        let validated_allocations =
90            validate_pending_ledger_claims(initial_range, &ranges, &regs, declaration_snapshot)?;
91
92        // Reserve the caller's initial range first (if provided)
93        if let Some((crate_name, start, end)) = initial_range {
94            MemoryRegistry::reserve_range(crate_name, start, end)?;
95        }
96
97        for (crate_name, start, end) in ranges {
98            MemoryRegistry::reserve_range(&crate_name, start, end)?;
99        }
100
101        for registration in regs {
102            MemoryRegistry::register_with_key_metadata(
103                registration.id,
104                &registration.crate_name,
105                &registration.label,
106                &registration.stable_key,
107                registration.schema_version,
108                registration.schema_fingerprint.as_deref(),
109            )?;
110        }
111
112        let summary = MemoryRegistryInitSummary {
113            ranges: MemoryRegistry::export_ranges(),
114            entries: MemoryRegistry::export(),
115        };
116        if !Self::is_initialized() || has_runtime_declarations {
117            set_validated_allocations(Some(validated_allocations));
118        }
119        set_initialized(true);
120
121        Ok(summary)
122    }
123
124    /// Return whether the memory registry has completed initialization.
125    #[must_use]
126    pub fn is_initialized() -> bool {
127        initialized()
128    }
129
130    /// Snapshot all registry entries.
131    #[must_use]
132    pub fn snapshot_entries() -> Vec<(u8, MemoryRegistryEntry)> {
133        MemoryRegistry::export()
134    }
135
136    /// Snapshot all reserved memory ranges.
137    #[must_use]
138    pub fn snapshot_ranges() -> Vec<(String, MemoryRange)> {
139        MemoryRegistry::export_ranges()
140    }
141
142    /// Snapshot all reserved memory ranges with owners.
143    #[must_use]
144    pub fn snapshot_range_entries() -> Vec<MemoryRangeEntry> {
145        MemoryRegistry::export_range_entries()
146    }
147
148    /// Snapshot registry entries grouped by range.
149    #[must_use]
150    pub fn snapshot_ids_by_range() -> Vec<MemoryRangeSnapshot> {
151        MemoryRegistry::export_ids_by_range()
152    }
153
154    /// Retrieve a single registry entry by ID.
155    #[must_use]
156    pub fn get(id: u8) -> Option<MemoryRegistryEntry> {
157        MemoryRegistry::get(id)
158    }
159
160    /// Return the sealed validated allocation set published by bootstrap.
161    pub fn validated_allocations() -> Result<ValidatedAllocations, MemoryRegistryError> {
162        if !Self::is_initialized() {
163            return Err(MemoryRegistryError::RegistryNotBootstrapped);
164        }
165
166        VALIDATED_ALLOCATIONS.with_borrow(|validated| {
167            validated
168                .clone()
169                .ok_or(MemoryRegistryError::RegistryNotBootstrapped)
170        })
171    }
172
173    /// Apply any newly deferred registrations/ranges after runtime init.
174    ///
175    /// This is a no-op until initialization has completed. Once initialized,
176    /// this drains pending range/ID registrations so lazily touched statics can
177    /// become visible during the same request.
178    pub fn commit_pending_if_initialized() -> Result<(), MemoryRegistryError> {
179        if !Self::is_initialized() || super::is_eager_tls_initializing() {
180            return Ok(());
181        }
182
183        let ranges = drain_pending_ranges();
184        let regs = drain_pending_registrations();
185
186        if ranges.is_empty() && regs.is_empty() {
187            return Ok(());
188        }
189
190        Err(MemoryRegistryError::RegistrationAfterBootstrap {
191            ranges: ranges.len(),
192            registrations: regs.len(),
193        })
194    }
195}
196
197const fn validate_raw_stable_memory_state(
198    raw_state: RawStableMemoryState,
199) -> Result<(), MemoryRegistryError> {
200    match raw_state {
201        RawStableMemoryState::Empty | RawStableMemoryState::MemoryManager => Ok(()),
202        RawStableMemoryState::ForeignOrCorrupt => Err(MemoryRegistryError::LedgerCorrupt {
203            reason: "foreign or corrupt raw stable memory state",
204        }),
205    }
206}
207
208fn validate_current_registration_snapshot(
209    regs: &[PendingRegistration],
210) -> Result<DeclarationSnapshot, MemoryRegistryError> {
211    let declarations = regs
212        .iter()
213        .map(allocation_declaration_from_pending)
214        .collect::<Result<Vec<_>, _>>()?;
215
216    DeclarationSnapshot::new(declarations).map_err(memory_registry_error_from_snapshot_error)
217}
218
219fn allocation_declaration_from_pending(
220    registration: &PendingRegistration,
221) -> Result<AllocationDeclaration, MemoryRegistryError> {
222    let slot = AllocationSlotDescriptor::memory_manager_checked(registration.id)
223        .map_err(memory_registry_error_from_slot_error)?;
224    let schema = SchemaMetadata::new(
225        registration.schema_version,
226        registration.schema_fingerprint.clone(),
227    )
228    .map_err(|err| MemoryRegistryError::InvalidSchemaMetadata {
229        stable_key: registration.stable_key.clone(),
230        reason: super::super::registry::schema_metadata_reason(err),
231    })?;
232
233    AllocationDeclaration::new(
234        &registration.stable_key,
235        slot,
236        Some(registration.label.clone()),
237        schema,
238    )
239    .map_err(memory_registry_error_from_snapshot_error)
240}
241
242fn memory_registry_error_from_snapshot_error(err: DeclarationSnapshotError) -> MemoryRegistryError {
243    match err {
244        DeclarationSnapshotError::Key(err) => MemoryRegistryError::InvalidStableKey {
245            stable_key: err.stable_key,
246            reason: err.reason,
247        },
248        DeclarationSnapshotError::SchemaMetadata(err) => {
249            MemoryRegistryError::InvalidSchemaMetadata {
250                stable_key: "<unknown>".to_string(),
251                reason: super::super::registry::schema_metadata_reason(err),
252            }
253        }
254        DeclarationSnapshotError::DuplicateStableKey(key) => {
255            MemoryRegistryError::DuplicateStableKey(key.into_string())
256        }
257        DeclarationSnapshotError::DuplicateSlot(slot) => match slot.memory_manager_id() {
258            Ok(id) => MemoryRegistryError::DuplicateId(id),
259            Err(err) => memory_registry_error_from_slot_error(err),
260        },
261    }
262}
263
264fn memory_registry_error_from_slot_error(err: MemoryManagerSlotError) -> MemoryRegistryError {
265    match err {
266        MemoryManagerSlotError::InvalidMemoryManagerId { id } => {
267            MemoryRegistryError::ReservedInternalId { id }
268        }
269        MemoryManagerSlotError::UnsupportedSlot
270        | MemoryManagerSlotError::UnsupportedSubstrate { .. }
271        | MemoryManagerSlotError::UnsupportedDescriptorVersion { .. } => {
272            MemoryRegistryError::LedgerCorrupt {
273                reason: "unsupported MemoryManager allocation slot descriptor",
274            }
275        }
276    }
277}
278
279fn validate_pending_ledger_claims(
280    initial_range: Option<(&str, u8, u8)>,
281    ranges: &[(String, u8, u8)],
282    regs: &[PendingRegistration],
283    declaration_snapshot: DeclarationSnapshot,
284) -> Result<ValidatedAllocations, MemoryRegistryError> {
285    if let Some((owner, start, end)) = initial_range {
286        ledger::validate_range(owner, MemoryRange { start, end })?;
287    }
288
289    for (owner, start, end) in ranges {
290        ledger::validate_range(
291            owner,
292            MemoryRange {
293                start: *start,
294                end: *end,
295            },
296        )?;
297    }
298
299    for registration in regs {
300        ledger::validate_entry(
301            registration.id,
302            &registration.crate_name,
303            &registration.label,
304            &registration.stable_key,
305        )?;
306    }
307
308    let historical_ledger = ledger::try_allocation_ledger_snapshot()?;
309    let policy = RuntimeDeclarationPolicy::from_registrations(regs);
310    validate_allocations(&historical_ledger, declaration_snapshot, &policy)
311        .map_err(|err| memory_registry_error_from_allocation_validation(err, regs))
312}
313
314///
315/// RuntimeDeclarationPolicy
316///
317/// Adapter that lets generic `ic-memory` validation apply Canic's per-crate
318/// namespace/range policy to a sealed multi-crate declaration snapshot.
319struct RuntimeDeclarationPolicy {
320    declaring_crates: BTreeMap<String, String>,
321}
322
323impl RuntimeDeclarationPolicy {
324    fn from_registrations(regs: &[PendingRegistration]) -> Self {
325        let declaring_crates = regs
326            .iter()
327            .map(|registration| {
328                (
329                    registration.stable_key.clone(),
330                    registration.crate_name.clone(),
331                )
332            })
333            .collect();
334        Self { declaring_crates }
335    }
336}
337
338impl AllocationPolicy for RuntimeDeclarationPolicy {
339    type Error = MemoryRegistryError;
340
341    fn validate_key(&self, _key: &StableKey) -> Result<(), Self::Error> {
342        Ok(())
343    }
344
345    fn validate_slot(
346        &self,
347        key: &StableKey,
348        slot: &AllocationSlotDescriptor,
349    ) -> Result<(), Self::Error> {
350        let declaring_crate =
351            self.declaring_crates
352                .get(key.as_str())
353                .ok_or(MemoryRegistryError::LedgerCorrupt {
354                    reason: "validated declaration is missing runtime crate ownership metadata",
355                })?;
356        let policy = CanicMemoryManagerPolicy::for_declaring_crate(declaring_crate);
357        AllocationPolicy::validate_slot(&policy, key, slot)
358    }
359
360    fn validate_reserved_slot(
361        &self,
362        key: &StableKey,
363        slot: &AllocationSlotDescriptor,
364    ) -> Result<(), Self::Error> {
365        let declaring_crate =
366            self.declaring_crates
367                .get(key.as_str())
368                .ok_or(MemoryRegistryError::LedgerCorrupt {
369                    reason: "validated declaration is missing runtime crate ownership metadata",
370                })?;
371        let policy = CanicMemoryManagerPolicy::for_declaring_crate(declaring_crate);
372        AllocationPolicy::validate_reserved_slot(&policy, key, slot)
373    }
374}
375
376fn memory_registry_error_from_allocation_validation(
377    err: AllocationValidationError<MemoryRegistryError>,
378    regs: &[PendingRegistration],
379) -> MemoryRegistryError {
380    match err {
381        AllocationValidationError::Policy(err) => err,
382        AllocationValidationError::StableKeySlotConflict {
383            stable_key,
384            historical_slot,
385            declared_slot,
386        } => MemoryRegistryError::HistoricalStableKeyConflict {
387            stable_key: stable_key.into_string(),
388            existing_id: memory_manager_id_from_allocation_slot(&historical_slot),
389            new_id: memory_manager_id_from_allocation_slot(&declared_slot),
390        },
391        AllocationValidationError::SlotStableKeyConflict {
392            slot,
393            historical_key,
394            declared_key,
395        } => {
396            let requested = regs
397                .iter()
398                .find(|registration| registration.stable_key == declared_key.as_str());
399            MemoryRegistryError::HistoricalIdConflict {
400                id: memory_manager_id_from_allocation_slot(&slot),
401                existing_crate: historical_key.as_str().to_string(),
402                existing_label: historical_key.as_str().to_string(),
403                new_crate: requested.map_or_else(
404                    || declared_key.as_str().to_string(),
405                    |reg| reg.crate_name.clone(),
406                ),
407                new_label: requested.map_or_else(
408                    || declared_key.as_str().to_string(),
409                    |reg| reg.label.clone(),
410                ),
411                new_stable_key: declared_key.into_string(),
412            }
413        }
414        AllocationValidationError::RetiredAllocation { .. } => MemoryRegistryError::LedgerCorrupt {
415            reason: "allocation was explicitly retired and cannot be redeclared",
416        },
417    }
418}
419
420fn memory_manager_id_from_allocation_slot(slot: &AllocationSlotDescriptor) -> u8 {
421    slot.memory_manager_id()
422        .unwrap_or(ic_memory::MEMORY_MANAGER_INVALID_ID)
423}
424
425#[cfg(test)]
426pub(crate) fn reset_initialized_for_tests() {
427    set_initialized(false);
428    set_validated_allocations(None);
429}
430
431#[cfg(not(test))]
432fn initialized() -> bool {
433    MEMORY_REGISTRY_INITIALIZED.load(Ordering::SeqCst)
434}
435
436#[cfg(test)]
437fn initialized() -> bool {
438    MEMORY_REGISTRY_INITIALIZED.with(Cell::get)
439}
440
441#[cfg(not(test))]
442fn set_initialized(value: bool) {
443    MEMORY_REGISTRY_INITIALIZED.store(value, Ordering::SeqCst);
444}
445
446#[cfg(test)]
447fn set_initialized(value: bool) {
448    MEMORY_REGISTRY_INITIALIZED.with(|initialized| initialized.set(value));
449}
450
451fn set_validated_allocations(value: Option<ValidatedAllocations>) {
452    VALIDATED_ALLOCATIONS.with_borrow_mut(|validated| {
453        *validated = value;
454    });
455}
456
457///
458/// TESTS
459///
460
461#[cfg(test)]
462mod tests {
463    use super::*;
464    use crate::memory::registry::{
465        defer_register, defer_register_with_key, defer_reserve_range, reset_for_tests,
466    };
467
468    #[test]
469    fn init_applies_initial_and_pending() {
470        reset_for_tests();
471        defer_reserve_range("crate_b", 110, 111).expect("defer range");
472        defer_register(110, "crate_b", "B110").expect("defer register");
473
474        let summary =
475            MemoryRegistryRuntime::init(Some(("crate_a", 100, 102))).expect("init should succeed");
476
477        assert_eq!(summary.ranges.len(), 3);
478        assert_eq!(summary.entries.len(), 2);
479        assert!(summary.entries.iter().any(|(id, entry)| {
480            *id == 110 && entry.crate_name == "crate_b" && entry.label == "B110"
481        }));
482    }
483
484    #[test]
485    fn init_is_idempotent_for_same_initial_range() {
486        reset_for_tests();
487
488        MemoryRegistryRuntime::init(Some(("crate_a", 100, 102)))
489            .expect("first init should succeed");
490        MemoryRegistryRuntime::init(Some(("crate_a", 100, 102)))
491            .expect("second init should succeed");
492    }
493
494    #[test]
495    fn init_returns_error_on_conflict() {
496        reset_for_tests();
497        defer_reserve_range("crate_a", 100, 102).expect("defer range A");
498        defer_reserve_range("crate_b", 102, 104).expect("defer range B");
499
500        let err = MemoryRegistryRuntime::init(None).unwrap_err();
501        assert!(matches!(err, MemoryRegistryError::Overlap { .. }));
502    }
503
504    #[test]
505    fn init_rejects_duplicate_current_snapshot_id_before_user_ledger_mutation() {
506        reset_for_tests();
507        defer_reserve_range("crate_a", 100, 102).expect("defer range");
508        defer_register_with_key(100, "crate_a", "slot_a", "app.crate_a.slot_a.v1")
509            .expect("defer first register");
510        defer_register_with_key(100, "crate_a", "slot_b", "app.crate_a.slot_b.v1")
511            .expect("defer second register");
512
513        let err = MemoryRegistryRuntime::init(None)
514            .expect_err("duplicate id in one snapshot should fail");
515        assert!(matches!(err, MemoryRegistryError::DuplicateId(100)));
516        assert!(
517            !MemoryRegistry::export_historical()
518                .iter()
519                .any(|(id, _)| *id == 100)
520        );
521    }
522
523    #[test]
524    fn init_rejects_duplicate_current_snapshot_stable_key_before_user_ledger_mutation() {
525        reset_for_tests();
526        defer_reserve_range("crate_a", 100, 102).expect("defer range");
527        defer_register_with_key(100, "crate_a", "slot_a", "app.crate_a.slot.v1")
528            .expect("defer first register");
529        defer_register_with_key(101, "crate_a", "slot_b", "app.crate_a.slot.v1")
530            .expect("defer second register");
531
532        let err = MemoryRegistryRuntime::init(None)
533            .expect_err("duplicate stable key in one snapshot should fail");
534        assert!(
535            matches!(err, MemoryRegistryError::DuplicateStableKey(key) if key == "app.crate_a.slot.v1")
536        );
537        assert!(
538            !MemoryRegistry::export_historical()
539                .iter()
540                .any(|(_, entry)| entry.stable_key == "app.crate_a.slot.v1")
541        );
542    }
543
544    #[test]
545    fn init_rejects_exact_duplicate_current_snapshot_declaration() {
546        reset_for_tests();
547        defer_reserve_range("crate_a", 100, 102).expect("defer range");
548        defer_register_with_key(100, "crate_a", "slot", "app.crate_a.slot.v1")
549            .expect("defer first register");
550        defer_register_with_key(100, "crate_a", "slot", "app.crate_a.slot.v1")
551            .expect("defer second register");
552
553        let err = MemoryRegistryRuntime::init(None)
554            .expect_err("exact duplicate declaration in one snapshot should fail");
555        assert!(matches!(err, MemoryRegistryError::DuplicateId(100)));
556    }
557
558    #[test]
559    fn init_rejects_historical_conflict_before_user_ledger_mutation() {
560        reset_for_tests();
561        ledger::record_range(
562            "crate_a",
563            MemoryRange {
564                start: 100,
565                end: 102,
566            },
567        )
568        .expect("record historical range");
569        ledger::record_entry(100, "crate_a", "slot", "app.crate_a.slot.v1", None, None)
570            .expect("record historical entry");
571        defer_reserve_range("crate_a", 100, 102).expect("defer range");
572        defer_register_with_key(101, "crate_a", "new_slot", "app.crate_a.new_slot.v1")
573            .expect("defer non-conflicting register");
574        defer_register_with_key(102, "crate_a", "moved_slot", "app.crate_a.slot.v1")
575            .expect("defer conflicting register");
576
577        let err = MemoryRegistryRuntime::init(None)
578            .expect_err("historical stable key movement should fail before commit");
579        assert!(matches!(
580            err,
581            MemoryRegistryError::HistoricalStableKeyConflict { .. }
582        ));
583        assert!(
584            !MemoryRegistry::export_historical()
585                .iter()
586                .any(|(id, _)| *id == 101)
587        );
588    }
589
590    #[test]
591    fn commit_pending_after_init_rejects_late_deferred_items() {
592        reset_for_tests();
593
594        MemoryRegistryRuntime::init(Some(("core", 100, 109))).expect("init should succeed");
595        defer_reserve_range("late", 110, 120).expect("defer late range");
596        defer_register(112, "late", "late_slot").expect("defer late register");
597
598        let err = MemoryRegistryRuntime::commit_pending_if_initialized()
599            .expect_err("late pending commit should fail after bootstrap seal");
600        assert!(matches!(
601            err,
602            MemoryRegistryError::RegistrationAfterBootstrap {
603                ranges: 1,
604                registrations: 1,
605            }
606        ));
607        assert!(MemoryRegistryRuntime::get(112).is_none());
608    }
609}