canic_core/memory/runtime/
registry.rs1use 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
33pub struct MemoryRegistryRuntime;
47
48impl MemoryRegistryRuntime {
49 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 #[must_use]
80 pub fn is_initialized() -> bool {
81 initialized()
82 }
83
84 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
125struct 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#[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}