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::WASM_PAGE_SIZE_BYTES;
139pub use declaration::{
140    AllocationDeclaration, DeclarationCollector, DeclarationSnapshot, DeclarationSnapshotError,
141};
142pub use diagnostics::{
143    DiagnosticCheck, DiagnosticCode, DiagnosticDeclaration, DiagnosticExport, DiagnosticFailure,
144    DiagnosticGeneration, DiagnosticMemorySize, DiagnosticMemorySizeOutcome,
145    DiagnosticRangeAuthority, DiagnosticRecord, DiagnosticRuntimeBinding, DiagnosticStableCell,
146    DiagnosticStableCellStatus, MemoryRuntimeDoctorReport,
147};
148pub use key::{StableKey, StableKeyError};
149pub use ledger::{
150    AllocationHistory, AllocationLedger, AllocationRecord, AllocationReservationError,
151    AllocationRetirement, AllocationRetirementError, AllocationStageError, AllocationState,
152    GenerationRecord, LEDGER_PAYLOAD_FORMAT_VERSION, LedgerCommitError, LedgerCommitStore,
153    LedgerIntegrityError, LedgerPayloadEnvelope, LedgerPayloadEnvelopeError, RecoveredLedger,
154    SchemaMetadataRecord,
155};
156pub use physical::{
157    CommitRecoveryError, CommitSlotDiagnostic, CommitStoreDiagnostic, CommittedGenerationBytes,
158    DualCommitStore,
159};
160pub use policy::{AllocationPolicy, PolicyIdentity, PolicyIdentityError, RuntimeBootstrapPolicy};
161pub use registry::{
162    SealedDeclarationFingerprint, SealedDeclarationSnapshot, StaticMemoryDeclaration,
163    StaticMemoryDeclarationError, StaticMemoryRangeDeclaration, register_static_memory_declaration,
164    register_static_memory_manager_declaration,
165    register_static_memory_manager_declaration_with_schema, register_static_memory_manager_range,
166    register_static_memory_range_declaration, sealed_declaration_snapshot,
167};
168pub use runtime::{
169    AllocationBinding, AllocationRangeClaim, MemoryAllocation, MemoryAllocations,
170    MemoryManagerConfig, MemoryManagerLayoutError, MemoryRuntime, RuntimeBootstrapError,
171    RuntimeConstructionError, RuntimeDiagnosticError, RuntimeMemory, RuntimeOpenError,
172    RuntimePolicyError, RuntimeStateError, bootstrap_default_memory_manager,
173    bootstrap_default_memory_manager_with_config, bootstrap_default_memory_manager_with_policy,
174    committed_allocations, default_memory_manager_commit_recovery_diagnostic,
175    default_memory_manager_diagnostic_export, default_memory_manager_doctor_report,
176    default_memory_manager_doctor_report_with_policy, default_memory_manager_memory_allocations,
177    is_default_memory_manager_bootstrapped, open_default_memory_manager_memory,
178};
179pub use schema::{SchemaMetadata, SchemaMetadataError};
180pub use slot::{
181    AllocationSlot, AllocationSlotDescriptor, IC_MEMORY_AUTHORITY_OWNER,
182    IC_MEMORY_AUTHORITY_PURPOSE, IC_MEMORY_LEDGER_LABEL, IC_MEMORY_LEDGER_STABLE_KEY,
183    IC_MEMORY_STABLE_KEY_PREFIX, MEMORY_MANAGER_GOVERNANCE_MAX_ID, MEMORY_MANAGER_INVALID_ID,
184    MEMORY_MANAGER_LEDGER_ID, MEMORY_MANAGER_MAX_ID, MEMORY_MANAGER_MIN_ID,
185    MemoryManagerAuthorityRecord, MemoryManagerIdRange, MemoryManagerRangeAuthority,
186    MemoryManagerRangeAuthorityError, MemoryManagerRangeError, MemoryManagerRangeMode,
187    MemoryManagerSlotError, is_ic_memory_stable_key, memory_manager_governance_range,
188    validate_memory_manager_id,
189};
190pub use stable_cell::{
191    STABLE_CELL_HEADER_SIZE, STABLE_CELL_LAYOUT_VERSION, STABLE_CELL_MAGIC,
192    STABLE_CELL_VALUE_OFFSET, StableCellLedgerError, StableCellLedgerRecord,
193    StableCellPayloadError, decode_stable_cell_ledger_record, decode_stable_cell_payload,
194    validate_stable_cell_ledger_memory,
195};
196pub use validation::{AllocationValidationError, Validate, validate_allocations};
197
198#[doc(hidden)]
199pub use registry::{defer_eager_init, defer_static_memory_registration};
200
201#[doc(hidden)]
202pub mod __reexports {
203    pub use ctor;
204}
205
206/// Register a `MemoryManager` allocation declaration during static initialization.
207///
208/// The explicit authority is stable policy identity shared with the matching
209/// range declaration. A string literal or shared compile-time string constant
210/// may be used. Internal `ic-memory` authority is unavailable to callers.
211///
212/// This macro only registers declaration metadata. It does not open stable
213/// memory. The bootstrap owner still has to collect/seal declarations, validate
214/// them against the ledger, commit the generation, and then open memory handles.
215#[macro_export]
216macro_rules! ic_memory_declaration {
217    (authority = $authority:expr, key = $stable_key:literal, ty = $label:path, id = $id:expr $(,)?) => {
218        const _: () = {
219            const __IC_MEMORY_AUTHORITY: &str = $authority;
220
221            fn __ic_memory_register_static_declaration() -> Result<(), $crate::StaticMemoryDeclarationError> {
222                let _ = core::marker::PhantomData::<$label>;
223                $crate::register_static_memory_manager_declaration(
224                    $id,
225                    __IC_MEMORY_AUTHORITY,
226                    stringify!($label),
227                    $stable_key,
228                )
229            }
230
231            #[ $crate::__reexports::ctor::ctor(unsafe, anonymous, crate_path = $crate::__reexports::ctor) ]
232            fn __ic_memory_defer_static_declaration() {
233                $crate::defer_static_memory_registration(__ic_memory_register_static_declaration);
234            }
235        };
236    };
237    (authority = $authority:expr, key = $stable_key:literal, label = $label:literal, id = $id:expr $(,)?) => {
238        const _: () = {
239            const __IC_MEMORY_AUTHORITY: &str = $authority;
240
241            fn __ic_memory_register_static_declaration() -> Result<(), $crate::StaticMemoryDeclarationError> {
242                $crate::register_static_memory_manager_declaration(
243                    $id,
244                    __IC_MEMORY_AUTHORITY,
245                    $label,
246                    $stable_key,
247                )
248            }
249
250            #[ $crate::__reexports::ctor::ctor(unsafe, anonymous, crate_path = $crate::__reexports::ctor) ]
251            fn __ic_memory_defer_static_declaration() {
252                $crate::defer_static_memory_registration(__ic_memory_register_static_declaration);
253            }
254        };
255    };
256}
257
258/// Declare a `MemoryManager` allocation range during static initialization.
259///
260/// The explicit authority must match every declaration that uses this range.
261/// A shared compile-time string constant can keep those declarations aligned.
262#[macro_export]
263macro_rules! ic_memory_range {
264    (authority = $authority:expr, start = $start:expr, end = $end:expr $(,)?) => {
265        $crate::ic_memory_range!(
266            authority = $authority,
267            start = $start,
268            end = $end,
269            mode = Reserved,
270        );
271    };
272    (authority = $authority:expr, start = $start:expr, end = $end:expr, mode = $mode:ident $(,)?) => {
273        const _: () = {
274            const __IC_MEMORY_AUTHORITY: &str = $authority;
275
276            fn __ic_memory_register_static_range() -> Result<(), $crate::StaticMemoryDeclarationError> {
277                $crate::register_static_memory_manager_range(
278                    $start,
279                    $end,
280                    __IC_MEMORY_AUTHORITY,
281                    $crate::MemoryManagerRangeMode::$mode,
282                    None,
283                )
284            }
285
286            #[ $crate::__reexports::ctor::ctor(unsafe, anonymous, crate_path = $crate::__reexports::ctor) ]
287            fn __ic_memory_defer_static_range() {
288                $crate::defer_static_memory_registration(__ic_memory_register_static_range);
289            }
290        };
291    };
292}
293
294/// Declare and open a committed `MemoryManager` slot by stable key.
295///
296/// The macro registers declaration metadata during static initialization and
297/// returns the typed default-runtime open result at expression use time.
298#[macro_export]
299macro_rules! ic_memory_key {
300    (authority = $authority:expr, key = $stable_key:literal, ty = $label:path, id = $id:expr $(,)?) => {{
301        $crate::ic_memory_declaration!(
302            authority = $authority,
303            key = $stable_key,
304            ty = $label,
305            id = $id,
306        );
307        $crate::open_default_memory_manager_memory($stable_key, $id)
308    }};
309    (authority = $authority:expr, key = $stable_key:literal, label = $label:literal, id = $id:expr $(,)?) => {{
310        $crate::ic_memory_declaration!(
311            authority = $authority,
312            key = $stable_key,
313            label = $label,
314            id = $id,
315        );
316        $crate::open_default_memory_manager_memory($stable_key, $id)
317    }};
318}
319
320/// Register one pre-bootstrap hook.
321#[macro_export]
322macro_rules! eager_init {
323    ($body:block) => {
324        const _: () = {
325            fn __ic_memory_registered_eager_init_body() {
326                $body
327            }
328
329            #[ $crate::__reexports::ctor::ctor(unsafe, anonymous, crate_path = $crate::__reexports::ctor) ]
330            fn __ic_memory_register_eager_init() {
331                $crate::defer_eager_init(__ic_memory_registered_eager_init_body);
332            }
333        };
334    };
335}