Skip to main content

ic_memory/
lib.rs

1#![deny(unsafe_code, unsafe_op_in_unsafe_fn)]
2#![deny(rustdoc::broken_intra_doc_links)]
3#![doc = include_str!("../README.md")]
4
5//! Stable-memory allocation-governance primitives for Internet Computer
6//! canister upgrades.
7//!
8//! `ic-memory` prevents stable-memory slot drift.
9//!
10//! Once a stable key is committed to a physical allocation slot, future binaries
11//! must either reopen that same stable key on that same slot or declare a new
12//! stable key.
13//!
14//! The crate records and validates durable ownership in both directions: an
15//! active stable key cannot move to a different physical slot, and an active
16//! physical slot cannot be reused by a different stable key.
17//!
18//! The intended integration flow is:
19//!
20//! 1. Recover the persisted allocation ledger.
21//! 2. Declare the stable stores expected by the current binary.
22//! 3. Validate those declarations against ledger history and any framework
23//!    policy.
24//! 4. Commit the next generation.
25//! 5. Only then open stable-memory handles through committed allocation
26//!    authority.
27//!
28//! This crate owns allocation invariants, not framework policy. Namespace
29//! rules, controller authorization, endpoint lifecycle, schema migrations, and
30//! application validation belong to the framework or application.
31//!
32//! For the default `MemoryManager` runtime, registered `ic-memory` range claims
33//! are generic allocation policy and are enforced before caller-supplied
34//! policy. A framework such as Canic that wants higher-level range semantics
35//! should adapt to this contract deliberately: either register the ranges it
36//! wants `ic-memory` to enforce, or omit user ranges and enforce application
37//! space through its own [`AllocationPolicy`].
38//!
39//! Use these primitives before opening stable-memory handles. Integrations
40//! should recover the historical ledger, declare the stores expected by the
41//! current binary, validate declarations against history and policy, commit a
42//! new generation, and only then publish committed allocation authority before
43//! opening slots through the storage owner.
44//!
45//! Bounded physical attribution is available through
46//! [`MemoryRuntime::memory_allocations`] and
47//! [`default_memory_manager_memory_allocations`]. It reports actual persisted
48//! buckets and explicit residuals without decoding ledger history. Virtual
49//! extent is not payload occupancy. Opens return [`RuntimeMemory`]; explicit
50//! [`MemoryManagerConfig`] selects fresh-state buckets or checks a persisted
51//! setting without migration. The default remains 128 pages.
52//!
53//! [`MemoryRuntime`] is the canonical owner for one backing memory instance. It
54//! contains that memory's manager, ledger cell, bootstrap lifecycle, committed
55//! capability, opens, and diagnostics. Linked code contributes declarations to
56//! one immutable [`SealedDeclarationSnapshot`], which is supplied to each
57//! runtime independently.
58//!
59//! [`AllocationBootstrap`] is the golden path for whichever layer owns a given
60//! ledger store. Canic may own bootstrap for a framework canister and compose
61//! IcyDB/application declarations through its registry; IcyDB may own bootstrap
62//! directly for generated database stores; or a standalone application canister
63//! may own bootstrap itself. Exactly one owner should bootstrap one ledger
64//! store. Multiple layers in the same canister must either compose declarations
65//! into that owner or use distinct ledger stores and allocation domains.
66//!
67//! `ic-stable-structures` `MemoryManager` IDs are the first-class supported
68//! physical slot substrate. That ID domain is `u8`: IDs `0..=254` are usable,
69//! and ID `255` is always the `ic-stable-structures` unallocated sentinel.
70//! The crate still keeps narrow internal abstractions for storage adapters and
71//! diagnostics, but the native IC path is
72//! `MemoryManager` ID 0 -> `ic-stable-structures::Cell<StableCellLedgerRecord,
73//! _>` -> [`LedgerCommitStore`] -> [`CommittedGenerationBytes`] ->
74//! [`LedgerPayloadEnvelope`] -> [`RecoveredLedger`] -> [`ValidatedAllocations`]
75//! -> [`CommittedAllocations`].
76//!
77//! [`ic_stable_structures`] re-exports the exact substrate version used by this
78//! crate. Use its collections and traits with [`RuntimeMemory`] handles;
79//! `ic-memory` owns allocation governance without wrapping typed collections.
80
81mod bootstrap;
82mod capability;
83mod cbor;
84mod constants;
85mod declaration;
86mod diagnostics;
87mod key;
88mod ledger;
89mod physical;
90mod policy;
91mod registry;
92mod runtime;
93mod schema;
94mod slot;
95mod stable_cell;
96mod validation;
97
98#[cfg(test)]
99mod test_cbor {
100    use serde::{Serialize, de::DeserializeOwned};
101
102    pub use ciborium::Value;
103
104    pub fn to_vec<T: Serialize>(
105        value: &T,
106    ) -> Result<Vec<u8>, ciborium::ser::Error<std::io::Error>> {
107        let mut bytes = Vec::new();
108        ciborium::into_writer(value, &mut bytes)?;
109        Ok(bytes)
110    }
111
112    pub fn from_slice<T: DeserializeOwned>(
113        bytes: &[u8],
114    ) -> Result<T, ciborium::de::Error<std::io::Error>> {
115        crate::cbor::from_slice_exact(bytes)
116    }
117
118    pub fn to_value<T: Serialize>(value: T) -> Result<Value, ciborium::value::Error> {
119        Value::serialized(&value)
120    }
121
122    pub fn map_insert(map: &mut Vec<(Value, Value)>, key: Value, value: Value) {
123        map.push((key, value));
124    }
125}
126
127/// Stable collections and traits from this crate's exact substrate dependency.
128///
129/// Use the upstream collections with [`RuntimeMemory`] handles obtained through
130/// the owned runtime. This re-export preserves upstream type identity.
131pub use ic_stable_structures;
132
133pub use bootstrap::{
134    AllocationBootstrap, BootstrapError, BootstrapReservationError, BootstrapRetirementError,
135    PendingBootstrapCommit,
136};
137pub use capability::{CommittedAllocations, ValidatedAllocations};
138pub use constants::{
139    MAX_LEDGER_BYTES, MAX_LEDGER_GENERATIONS, MAX_LEDGER_NESTING, MAX_LEDGER_RECORD_BYTES,
140    WASM_PAGE_SIZE_BYTES,
141};
142pub use declaration::{
143    AllocationDeclaration, DeclarationCollector, DeclarationSnapshot, DeclarationSnapshotError,
144};
145pub use diagnostics::{
146    DiagnosticCheck, DiagnosticCode, DiagnosticDeclaration, DiagnosticExport, DiagnosticFailure,
147    DiagnosticGeneration, DiagnosticMemorySize, DiagnosticMemorySizeOutcome,
148    DiagnosticRangeAuthority, DiagnosticRecord, DiagnosticRuntimeBinding, DiagnosticStableCell,
149    DiagnosticStableCellStatus, MemoryRuntimeDoctorReport,
150};
151pub use key::{StableKey, StableKeyError};
152pub use ledger::{
153    AllocationHistory, AllocationLedger, AllocationRecord, AllocationReservationError,
154    AllocationRetirement, AllocationRetirementError, AllocationStageError, AllocationState,
155    GenerationRecord, LEDGER_PAYLOAD_FORMAT_VERSION, LedgerCommitError, LedgerCommitStore,
156    LedgerIntegrityError, LedgerPayloadEnvelope, LedgerPayloadEnvelopeError, RecoveredLedger,
157    SchemaMetadataRecord,
158};
159pub use physical::{
160    CommitRecoveryError, CommitSlotDiagnostic, CommitStoreDiagnostic, CommittedGenerationBytes,
161    DualCommitStore,
162};
163pub use policy::{AllocationPolicy, PolicyIdentity, PolicyIdentityError, RuntimeBootstrapPolicy};
164pub use registry::{
165    MemoryRequest, SealedDeclarationFingerprint, SealedDeclarationSnapshot,
166    StaticMemoryDeclaration, StaticMemoryDeclarationError, StaticMemoryRangeDeclaration,
167    register_memory_request, register_static_memory_declaration,
168    register_static_memory_manager_declaration,
169    register_static_memory_manager_declaration_with_schema, register_static_memory_manager_range,
170    register_static_memory_range_declaration, sealed_declaration_snapshot,
171};
172pub use runtime::{
173    AllocationBinding, AllocationRangeClaim, BootstrapAdmission, BootstrapAdmissionError,
174    GenericRangePolicy, MemoryAllocation, MemoryAllocationSummary, MemoryAllocations,
175    MemoryBindingSummary, MemoryManagerConfig, MemoryManagerLayoutError, MemoryResolutionError,
176    MemoryRuntime, RecoveredAllocationMetadata, RuntimeAdoptionError, RuntimeBootstrapError,
177    RuntimeConstructionError, RuntimeDiagnosticError, RuntimeGrowError, RuntimeMemory,
178    RuntimeOpenError, RuntimePolicyError, RuntimeStateError, bootstrap_default_memory_manager,
179    bootstrap_default_memory_manager_with_config, bootstrap_default_memory_manager_with_policy,
180    committed_allocations, default_memory_manager_commit_recovery_diagnostic,
181    default_memory_manager_diagnostic_export, default_memory_manager_doctor_report,
182    default_memory_manager_doctor_report_with_policy,
183    default_memory_manager_memory_allocation_summary, default_memory_manager_memory_allocations,
184    default_memory_manager_memory_id, is_default_memory_manager_bootstrapped,
185    open_default_memory_manager_memory, open_default_memory_manager_memory_by_key,
186    verify_default_memory_manager_authority,
187};
188pub use schema::{SchemaMetadata, SchemaMetadataError};
189pub use slot::{
190    AllocationSlot, AllocationSlotDescriptor, IC_MEMORY_AUTHORITY_OWNER,
191    IC_MEMORY_AUTHORITY_PURPOSE, IC_MEMORY_LEDGER_LABEL, IC_MEMORY_LEDGER_STABLE_KEY,
192    IC_MEMORY_STABLE_KEY_PREFIX, MEMORY_MANAGER_GOVERNANCE_MAX_ID, MEMORY_MANAGER_INVALID_ID,
193    MEMORY_MANAGER_LEDGER_ID, MEMORY_MANAGER_MAX_ID, MEMORY_MANAGER_MIN_ID,
194    MemoryManagerAuthorityRecord, MemoryManagerIdRange, MemoryManagerRangeAuthority,
195    MemoryManagerRangeAuthorityError, MemoryManagerRangeError, MemoryManagerRangeMode,
196    MemoryManagerSlotError, is_ic_memory_stable_key, memory_manager_governance_range,
197    validate_memory_manager_id,
198};
199pub use stable_cell::{
200    STABLE_CELL_HEADER_SIZE, STABLE_CELL_LAYOUT_VERSION, STABLE_CELL_MAGIC,
201    STABLE_CELL_VALUE_OFFSET, StableCellLedgerError, StableCellLedgerRecord,
202    StableCellPayloadError, decode_stable_cell_ledger_record, decode_stable_cell_payload,
203    validate_stable_cell_ledger_memory,
204};
205pub use validation::{AllocationValidationError, Validate, validate_allocations};
206
207#[doc(hidden)]
208pub use registry::{defer_eager_init, defer_static_memory_registration};
209
210#[doc(hidden)]
211pub mod __reexports {
212    pub use ctor;
213}
214
215/// Register a `MemoryManager` allocation declaration during static initialization.
216///
217/// The explicit authority is stable policy identity shared with the matching
218/// range declaration. A string literal or shared compile-time string constant
219/// may be used. Internal `ic-memory` authority is unavailable to callers.
220///
221/// This macro only registers declaration metadata. It does not open stable
222/// memory. The bootstrap owner still has to collect/seal declarations, validate
223/// them against the ledger, commit the generation, and then open memory handles.
224#[macro_export]
225macro_rules! ic_memory_declaration {
226    (authority = $authority:expr, key = $stable_key:literal $(,)?) => {
227        const _: () = {
228            fn __ic_memory_register_request() -> Result<(), $crate::StaticMemoryDeclarationError> {
229                $crate::register_memory_request($crate::MemoryRequest::new(
230                    $authority, $stable_key, $crate::SchemaMetadata::default(),
231                )?)
232            }
233            #[ $crate::__reexports::ctor::ctor(unsafe, anonymous, crate_path = $crate::__reexports::ctor) ]
234            fn __ic_memory_defer_request() {
235                $crate::defer_static_memory_registration(__ic_memory_register_request);
236            }
237        };
238    };
239    (authority = $authority:expr, key = $stable_key:literal, ty = $label:path, id = $id:expr $(,)?) => {
240        const _: () = {
241            const __IC_MEMORY_AUTHORITY: &str = $authority;
242
243            fn __ic_memory_register_static_declaration() -> Result<(), $crate::StaticMemoryDeclarationError> {
244                let _ = core::marker::PhantomData::<$label>;
245                $crate::register_static_memory_manager_declaration(
246                    $id,
247                    __IC_MEMORY_AUTHORITY,
248                    stringify!($label),
249                    $stable_key,
250                )
251            }
252
253            #[ $crate::__reexports::ctor::ctor(unsafe, anonymous, crate_path = $crate::__reexports::ctor) ]
254            fn __ic_memory_defer_static_declaration() {
255                $crate::defer_static_memory_registration(__ic_memory_register_static_declaration);
256            }
257        };
258    };
259    (authority = $authority:expr, key = $stable_key:literal, label = $label:literal, id = $id:expr $(,)?) => {
260        const _: () = {
261            const __IC_MEMORY_AUTHORITY: &str = $authority;
262
263            fn __ic_memory_register_static_declaration() -> Result<(), $crate::StaticMemoryDeclarationError> {
264                $crate::register_static_memory_manager_declaration(
265                    $id,
266                    __IC_MEMORY_AUTHORITY,
267                    $label,
268                    $stable_key,
269                )
270            }
271
272            #[ $crate::__reexports::ctor::ctor(unsafe, anonymous, crate_path = $crate::__reexports::ctor) ]
273            fn __ic_memory_defer_static_declaration() {
274                $crate::defer_static_memory_registration(__ic_memory_register_static_declaration);
275            }
276        };
277    };
278}
279
280/// Declare a `MemoryManager` allocation range during static initialization.
281///
282/// The explicit authority must match every declaration that uses this range.
283/// A shared compile-time string constant can keep those declarations aligned.
284#[macro_export]
285macro_rules! ic_memory_range {
286    (authority = $authority:expr, start = $start:expr, end = $end:expr $(,)?) => {
287        $crate::ic_memory_range!(
288            authority = $authority,
289            start = $start,
290            end = $end,
291            mode = Reserved,
292        );
293    };
294    (authority = $authority:expr, start = $start:expr, end = $end:expr, mode = $mode:ident $(,)?) => {
295        const _: () = {
296            const __IC_MEMORY_AUTHORITY: &str = $authority;
297
298            fn __ic_memory_register_static_range() -> Result<(), $crate::StaticMemoryDeclarationError> {
299                $crate::register_static_memory_manager_range(
300                    $start,
301                    $end,
302                    __IC_MEMORY_AUTHORITY,
303                    $crate::MemoryManagerRangeMode::$mode,
304                    None,
305                )
306            }
307
308            #[ $crate::__reexports::ctor::ctor(unsafe, anonymous, crate_path = $crate::__reexports::ctor) ]
309            fn __ic_memory_defer_static_range() {
310                $crate::defer_static_memory_registration(__ic_memory_register_static_range);
311            }
312        };
313    };
314}
315
316/// Declare and open a committed `MemoryManager` slot by stable key.
317///
318/// The macro registers declaration metadata during static initialization and
319/// returns the typed default-runtime open result at expression use time.
320#[macro_export]
321macro_rules! ic_memory_key {
322    (authority = $authority:expr, key = $stable_key:literal $(,)?) => {{
323        $crate::ic_memory_declaration!(authority = $authority, key = $stable_key);
324        $crate::open_default_memory_manager_memory_by_key($stable_key)
325    }};
326    (authority = $authority:expr, key = $stable_key:literal, ty = $label:path, id = $id:expr $(,)?) => {{
327        $crate::ic_memory_declaration!(
328            authority = $authority,
329            key = $stable_key,
330            ty = $label,
331            id = $id,
332        );
333        $crate::open_default_memory_manager_memory($stable_key, $id)
334    }};
335    (authority = $authority:expr, key = $stable_key:literal, label = $label:literal, id = $id:expr $(,)?) => {{
336        $crate::ic_memory_declaration!(
337            authority = $authority,
338            key = $stable_key,
339            label = $label,
340            id = $id,
341        );
342        $crate::open_default_memory_manager_memory($stable_key, $id)
343    }};
344}
345
346/// Register one pre-bootstrap hook.
347#[macro_export]
348macro_rules! eager_init {
349    ($body:block) => {
350        const _: () = {
351            fn __ic_memory_registered_eager_init_body() {
352                $body
353            }
354
355            #[ $crate::__reexports::ctor::ctor(unsafe, anonymous, crate_path = $crate::__reexports::ctor) ]
356            fn __ic_memory_register_eager_init() {
357                $crate::defer_eager_init(__ic_memory_registered_eager_init_body);
358            }
359        };
360    };
361}