Skip to main content

canic_core/memory/runtime/
registry.rs

1use super::super::manager;
2#[cfg(test)]
3use super::super::registry::drain_pending_registrations;
4use super::super::registry::{
5    MemoryRegistryError, PendingRegistration, memory_registry_error_from_declaration_error,
6    static_declarations,
7};
8use super::super::{ledger, policy::CanicMemoryManagerPolicy};
9use ic_memory::{
10    AllocationPolicy, AllocationSlotDescriptor, AllocationValidationError, BootstrapError,
11    DeclarationSnapshot, StableKey, ValidatedAllocations,
12};
13#[cfg(test)]
14use std::cell::Cell;
15#[cfg(not(test))]
16use std::sync::atomic::{AtomicBool, Ordering};
17use std::{cell::RefCell, collections::BTreeMap};
18
19#[cfg(not(test))]
20static MEMORY_REGISTRY_INITIALIZED: AtomicBool = AtomicBool::new(false);
21
22#[cfg(test)]
23thread_local! {
24    static MEMORY_REGISTRY_INITIALIZED: Cell<bool> = const { Cell::new(false) };
25}
26
27thread_local! {
28    static VALIDATED_ALLOCATIONS: RefCell<Option<ValidatedAllocations>> = const {
29        RefCell::new(None)
30    };
31}
32
33///
34/// MemoryRegistryRuntime
35///
36/// Substrate runtime controller responsible for initializing the
37/// global memory registry.
38///
39/// This type performs mechanical coordination only:
40/// - ordering
41/// - conflict detection
42/// - idempotent initialization
43///
44/// It encodes no application semantics.
45///
46pub struct MemoryRegistryRuntime;
47
48impl MemoryRegistryRuntime {
49    /// Initialize the memory registry.
50    ///
51    /// - Applies all deferred ID registrations.
52    ///
53    /// This function is idempotent for the same declaration snapshot.
54    pub fn init() -> Result<(), MemoryRegistryError> {
55        let raw_state = manager::classify_raw_stable_memory();
56        ledger::validate_bootstrap_state_before_cell_init(raw_state)?;
57
58        let mut declarations = static_declarations();
59        #[cfg(test)]
60        declarations.extend(drain_pending_registrations());
61        declarations.insert(0, PendingRegistration::internal_layout_ledger()?);
62        let declaration_snapshot = DeclarationSnapshot::new(
63            declarations
64                .iter()
65                .map(|registration| registration.declaration().clone())
66                .collect(),
67        )
68        .map_err(|err| memory_registry_error_from_declaration_error(err, "<snapshot>"))?;
69        let validated_allocations =
70            validate_and_commit_ledger_claims(&declarations, declaration_snapshot)?;
71
72        set_validated_allocations(Some(validated_allocations));
73        set_initialized(true);
74
75        Ok(())
76    }
77
78    /// Return whether the memory registry has completed initialization.
79    #[must_use]
80    pub fn is_initialized() -> bool {
81        initialized()
82    }
83
84    /// Return the sealed validated allocation set published by bootstrap.
85    pub fn validated_allocations() -> Result<ValidatedAllocations, MemoryRegistryError> {
86        if !Self::is_initialized() {
87            return Err(MemoryRegistryError::RegistryNotBootstrapped);
88        }
89
90        VALIDATED_ALLOCATIONS.with_borrow(|validated| {
91            validated
92                .clone()
93                .ok_or(MemoryRegistryError::RegistryNotBootstrapped)
94        })
95    }
96
97    #[cfg(test)]
98    pub fn commit_pending_if_initialized() -> Result<(), MemoryRegistryError> {
99        if !Self::is_initialized() || super::is_eager_tls_initializing() {
100            return Ok(());
101        }
102
103        let regs = drain_pending_registrations();
104
105        if regs.is_empty() {
106            return Ok(());
107        }
108
109        Err(MemoryRegistryError::RegistrationAfterBootstrap {
110            registrations: regs.len(),
111        })
112    }
113}
114
115fn validate_and_commit_ledger_claims(
116    regs: &[PendingRegistration],
117    declaration_snapshot: DeclarationSnapshot,
118) -> Result<ValidatedAllocations, MemoryRegistryError> {
119    let policy = RuntimeDeclarationPolicy::from_registrations(regs);
120    ledger::bootstrap_declarations(declaration_snapshot, &policy)
121        .map(|commit| commit.validated)
122        .map_err(memory_registry_error_from_bootstrap)
123}
124
125///
126/// RuntimeDeclarationPolicy
127///
128/// Adapter that lets generic `ic-memory` validation apply Canic's per-crate
129/// namespace/range policy to a sealed multi-crate declaration snapshot.
130struct RuntimeDeclarationPolicy {
131    declaring_crates: BTreeMap<String, String>,
132}
133
134impl RuntimeDeclarationPolicy {
135    fn from_registrations(regs: &[PendingRegistration]) -> Self {
136        let declaring_crates = regs
137            .iter()
138            .map(|registration| {
139                (
140                    registration.declaration().stable_key().as_str().to_string(),
141                    registration.crate_name().to_string(),
142                )
143            })
144            .collect();
145        Self { declaring_crates }
146    }
147}
148
149impl AllocationPolicy for RuntimeDeclarationPolicy {
150    type Error = MemoryRegistryError;
151
152    fn validate_key(&self, _key: &StableKey) -> Result<(), Self::Error> {
153        Ok(())
154    }
155
156    fn validate_slot(
157        &self,
158        key: &StableKey,
159        slot: &AllocationSlotDescriptor,
160    ) -> Result<(), Self::Error> {
161        let declaring_crate =
162            self.declaring_crates
163                .get(key.as_str())
164                .ok_or(MemoryRegistryError::LedgerCorrupt {
165                    reason: "validated declaration is missing runtime crate ownership metadata",
166                })?;
167        let policy = CanicMemoryManagerPolicy::for_declaring_crate(declaring_crate);
168        AllocationPolicy::validate_slot(&policy, key, slot)
169    }
170
171    fn validate_reserved_slot(
172        &self,
173        key: &StableKey,
174        slot: &AllocationSlotDescriptor,
175    ) -> Result<(), Self::Error> {
176        let declaring_crate =
177            self.declaring_crates
178                .get(key.as_str())
179                .ok_or(MemoryRegistryError::LedgerCorrupt {
180                    reason: "validated declaration is missing runtime crate ownership metadata",
181                })?;
182        let policy = CanicMemoryManagerPolicy::for_declaring_crate(declaring_crate);
183        AllocationPolicy::validate_reserved_slot(&policy, key, slot)
184    }
185}
186
187fn memory_registry_error_from_allocation_validation(
188    err: AllocationValidationError<MemoryRegistryError>,
189) -> MemoryRegistryError {
190    match err {
191        AllocationValidationError::Policy(err) => err,
192        AllocationValidationError::StableKeySlotConflict { .. }
193        | AllocationValidationError::SlotStableKeyConflict { .. } => {
194            MemoryRegistryError::LedgerCorrupt {
195                reason: "ic-memory allocation history rejected a conflicting declaration",
196            }
197        }
198        AllocationValidationError::RetiredAllocation { .. } => MemoryRegistryError::LedgerCorrupt {
199            reason: "allocation was explicitly retired and cannot be redeclared",
200        },
201    }
202}
203
204fn memory_registry_error_from_bootstrap<L>(
205    err: BootstrapError<L, MemoryRegistryError>,
206) -> MemoryRegistryError {
207    match err {
208        BootstrapError::Ledger(_) => MemoryRegistryError::LedgerCorrupt {
209            reason: "native ic-memory ledger recovery or commit failed",
210        },
211        BootstrapError::Validation(err) => memory_registry_error_from_allocation_validation(err),
212        BootstrapError::Staging(_) => MemoryRegistryError::LedgerCorrupt {
213            reason: "native ic-memory ledger generation staging failed",
214        },
215    }
216}
217
218#[cfg(test)]
219pub(crate) fn reset_initialized_for_tests() {
220    set_initialized(false);
221    set_validated_allocations(None);
222}
223
224#[cfg(not(test))]
225fn initialized() -> bool {
226    MEMORY_REGISTRY_INITIALIZED.load(Ordering::SeqCst)
227}
228
229#[cfg(test)]
230fn initialized() -> bool {
231    MEMORY_REGISTRY_INITIALIZED.with(Cell::get)
232}
233
234#[cfg(not(test))]
235fn set_initialized(value: bool) {
236    MEMORY_REGISTRY_INITIALIZED.store(value, Ordering::SeqCst);
237}
238
239#[cfg(test)]
240fn set_initialized(value: bool) {
241    MEMORY_REGISTRY_INITIALIZED.with(|initialized| initialized.set(value));
242}
243
244fn set_validated_allocations(value: Option<ValidatedAllocations>) {
245    VALIDATED_ALLOCATIONS.with_borrow_mut(|validated| {
246        *validated = value;
247    });
248}
249
250///
251/// TESTS
252///
253
254#[cfg(test)]
255mod tests {
256    use super::*;
257    use crate::memory::registry::{defer_register_with_key, reset_for_tests};
258
259    #[test]
260    fn init_reports_declared_slot_count() {
261        reset_for_tests();
262        defer_register_with_key(110, "crate_b", "B110", "app.crate_b.b110.v1")
263            .expect("defer register");
264
265        MemoryRegistryRuntime::init().expect("init should succeed");
266    }
267
268    #[test]
269    fn init_is_idempotent_for_same_snapshot() {
270        reset_for_tests();
271
272        MemoryRegistryRuntime::init().expect("first init should succeed");
273        MemoryRegistryRuntime::init().expect("second init should succeed");
274    }
275
276    #[test]
277    fn init_rejects_historical_conflict_before_user_ledger_mutation() {
278        reset_for_tests();
279        seed_historical_entry(100, "crate_a", "slot", "app.crate_a.slot.v1");
280        defer_register_with_key(101, "crate_a", "new_slot", "app.crate_a.new_slot.v1")
281            .expect("defer non-conflicting register");
282        defer_register_with_key(102, "crate_a", "moved_slot", "app.crate_a.slot.v1")
283            .expect("defer conflicting register");
284
285        let err = MemoryRegistryRuntime::init()
286            .expect_err("historical stable key movement should fail before commit");
287        assert!(matches!(err, MemoryRegistryError::LedgerCorrupt { .. }));
288        assert!(
289            !ledger::try_export_records()
290                .expect("ledger records")
291                .iter()
292                .any(|record| record.slot().memory_manager_id() == Ok(101))
293        );
294        assert!(!MemoryRegistryRuntime::is_initialized());
295        assert!(matches!(
296            crate::memory::try_open_validated_memory("app.crate_a.new_slot.v1", 101),
297            Err(MemoryRegistryError::RegistryNotBootstrapped)
298        ));
299    }
300
301    #[test]
302    fn commit_pending_after_init_rejects_late_deferred_items() {
303        reset_for_tests();
304
305        MemoryRegistryRuntime::init().expect("init should succeed");
306        defer_register_with_key(112, "late", "late_slot", "app.late.late_slot.v1")
307            .expect("defer late register");
308
309        let err = MemoryRegistryRuntime::commit_pending_if_initialized()
310            .expect_err("late pending commit should fail after bootstrap seal");
311        assert!(matches!(
312            err,
313            MemoryRegistryError::RegistrationAfterBootstrap { registrations: 1 }
314        ));
315    }
316
317    fn seed_historical_entry(id: u8, owner: &str, label: &str, stable_key: &str) {
318        defer_register_with_key(id, owner, label, stable_key).expect("seed declaration");
319        MemoryRegistryRuntime::init().expect("seed bootstrap");
320        reset_initialized_for_tests();
321    }
322}