Skip to main content

ic_memory/
lib.rs

1#![forbid(unsafe_code)]
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 a committed allocation
26//!    session.
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 a committed allocation session that can
43//! open slots through a storage substrate.
44//!
45//! [`AllocationBootstrap`] is the golden path for whichever layer owns a given
46//! ledger store. Canic may own bootstrap for a framework canister and compose
47//! IcyDB/application declarations through its registry; IcyDB may own bootstrap
48//! directly for generated database stores; or a standalone application canister
49//! may own bootstrap itself. Exactly one owner should bootstrap one ledger
50//! store. Multiple layers in the same canister must either compose declarations
51//! into that owner or use distinct ledger stores and allocation domains.
52//!
53//! `ic-stable-structures` `MemoryManager` IDs are the first-class supported
54//! physical slot substrate. That ID domain is `u8`: IDs `0..=254` are usable,
55//! and ID `255` is always the `ic-stable-structures` unallocated sentinel.
56//! The crate still keeps narrow internal abstractions for storage adapters and
57//! diagnostics, but the native IC path is
58//! `MemoryManager` ID 0 -> `ic-stable-structures::Cell<StableCellLedgerRecord,
59//! _>` -> [`LedgerCommitStore`] -> [`CommittedGenerationBytes`] ->
60//! [`LedgerPayloadEnvelope`] -> [`RecoveredLedger`] -> [`ValidatedAllocations`]
61//! -> [`CommittedAllocations`].
62//!
63//! `ic-memory` is not a replacement for `ic-stable-structures` collections and
64//! does not wrap typed stores such as `StableBTreeMap`.
65
66mod bootstrap;
67mod cbor;
68mod constants;
69mod declaration;
70mod diagnostics;
71mod key;
72mod ledger;
73mod physical;
74mod policy;
75mod registry;
76mod runtime;
77mod schema;
78mod session;
79mod slot;
80mod stable_cell;
81mod substrate;
82mod validation;
83
84#[cfg(test)]
85mod test_cbor {
86    use serde::{Serialize, de::DeserializeOwned};
87
88    pub use ciborium::Value;
89
90    pub fn to_vec<T: Serialize>(
91        value: &T,
92    ) -> Result<Vec<u8>, ciborium::ser::Error<std::io::Error>> {
93        let mut bytes = Vec::new();
94        ciborium::into_writer(value, &mut bytes)?;
95        Ok(bytes)
96    }
97
98    pub fn from_slice<T: DeserializeOwned>(
99        bytes: &[u8],
100    ) -> Result<T, ciborium::de::Error<std::io::Error>> {
101        crate::cbor::from_slice_exact(bytes)
102    }
103
104    pub fn to_value<T: Serialize>(value: T) -> Result<Value, ciborium::value::Error> {
105        Value::serialized(&value)
106    }
107
108    pub fn map_insert(map: &mut Vec<(Value, Value)>, key: Value, value: Value) {
109        map.push((key, value));
110    }
111}
112
113pub use bootstrap::{
114    AllocationBootstrap, BootstrapError, BootstrapReservationError, BootstrapRetirementError,
115    PendingBootstrapCommit,
116};
117pub use constants::WASM_PAGE_SIZE_BYTES;
118pub use declaration::{
119    AllocationDeclaration, DeclarationCollector, DeclarationSnapshot, DeclarationSnapshotError,
120};
121pub use diagnostics::{
122    DefaultMemoryManagerDoctorReport, DiagnosticCheck, DiagnosticCheckStatus,
123    DiagnosticDeclaration, DiagnosticExport, DiagnosticGeneration, DiagnosticMemorySize,
124    DiagnosticRangeAuthority, DiagnosticRecord, DiagnosticStableCell, DiagnosticStableCellStatus,
125};
126pub use key::{StableKey, StableKeyError};
127pub use ledger::{
128    AllocationHistory, AllocationLedger, AllocationRecord, AllocationReservationError,
129    AllocationRetirement, AllocationRetirementError, AllocationStageError, AllocationState,
130    GenerationRecord, LedgerCommitError, LedgerCommitStore, LedgerIntegrityError,
131    LedgerPayloadEnvelope, LedgerPayloadEnvelopeError, RecoveredLedger, SchemaMetadataRecord,
132};
133pub use physical::{
134    AuthoritativeSlot, CommitRecoveryError, CommitSlotDiagnostic, CommitSlotIndex,
135    CommitStoreDiagnostic, CommittedGenerationBytes, DualCommitStore, DualProtectedCommitStore,
136    ProtectedGenerationSlot, select_authoritative_slot,
137};
138pub use policy::AllocationPolicy;
139pub use registry::{
140    StaticMemoryDeclaration, StaticMemoryDeclarationError, StaticMemoryRangeDeclaration,
141    collect_static_memory_declarations, register_static_memory_declaration,
142    register_static_memory_manager_declaration,
143    register_static_memory_manager_declaration_with_schema, register_static_memory_manager_range,
144    register_static_memory_range_declaration, static_memory_declaration_snapshot,
145    static_memory_declarations, static_memory_range_authority, static_memory_range_declarations,
146};
147pub use runtime::{
148    RuntimeBootstrapError, RuntimeDiagnosticError, RuntimeOpenError, RuntimePolicyError,
149    bootstrap_default_memory_manager, bootstrap_default_memory_manager_with_policy,
150    committed_allocations, default_memory_manager_commit_recovery_diagnostic,
151    default_memory_manager_diagnostic_export, default_memory_manager_doctor_report,
152    is_default_memory_manager_bootstrapped, open_default_memory_manager_memory,
153};
154pub use schema::{SchemaMetadata, SchemaMetadataError};
155pub use session::{
156    AllocationSession, AllocationSessionError, CommittedAllocations, ValidatedAllocations,
157};
158pub use slot::{
159    AllocationSlot, AllocationSlotDescriptor, IC_MEMORY_AUTHORITY_OWNER,
160    IC_MEMORY_AUTHORITY_PURPOSE, IC_MEMORY_LEDGER_LABEL, IC_MEMORY_LEDGER_STABLE_KEY,
161    IC_MEMORY_STABLE_KEY_PREFIX, MEMORY_MANAGER_GOVERNANCE_MAX_ID, MEMORY_MANAGER_INVALID_ID,
162    MEMORY_MANAGER_LEDGER_ID, MEMORY_MANAGER_MAX_ID, MEMORY_MANAGER_MIN_ID,
163    MemoryManagerAuthorityRecord, MemoryManagerIdRange, MemoryManagerRangeAuthority,
164    MemoryManagerRangeAuthorityError, MemoryManagerRangeError, MemoryManagerRangeMode,
165    MemoryManagerSlotError, is_ic_memory_stable_key, memory_manager_governance_range,
166    validate_memory_manager_id,
167};
168pub use stable_cell::{
169    STABLE_CELL_HEADER_SIZE, STABLE_CELL_LAYOUT_VERSION, STABLE_CELL_MAGIC,
170    STABLE_CELL_VALUE_OFFSET, StableCellLedgerError, StableCellLedgerRecord,
171    StableCellPayloadError, decode_stable_cell_ledger_record, decode_stable_cell_payload,
172    validate_stable_cell_ledger_memory,
173};
174pub use substrate::{LedgerAnchor, StorageSubstrate};
175pub use validation::{AllocationValidationError, Validate, validate_allocations};
176
177#[doc(hidden)]
178pub use runtime::defer_eager_init;
179
180#[doc(hidden)]
181pub mod __reexports {
182    pub use ctor;
183}
184
185/// Register a `MemoryManager` allocation declaration during static initialization.
186///
187/// The explicit authority is stable policy identity shared with the matching
188/// range declaration. Internal `ic-memory` authority is unavailable to callers.
189///
190/// This macro only registers declaration metadata. It does not open stable
191/// memory. The bootstrap owner still has to collect/seal declarations, validate
192/// them against the ledger, commit the generation, and then open memory handles.
193#[macro_export]
194macro_rules! ic_memory_declaration {
195    (authority = $authority:literal, key = $stable_key:literal, ty = $label:path, id = $id:expr $(,)?) => {
196        const _: () = {
197            #[ $crate::__reexports::ctor::ctor(unsafe, anonymous, crate_path = $crate::__reexports::ctor) ]
198            fn __ic_memory_register_static_declaration() {
199                let _ = core::marker::PhantomData::<$label>;
200                $crate::register_static_memory_manager_declaration(
201                    $id,
202                    $authority,
203                    stringify!($label),
204                    $stable_key,
205                )
206                .expect("ic-memory static memory declaration failed");
207            }
208        };
209    };
210    (authority = $authority:literal, key = $stable_key:literal, label = $label:literal, id = $id:expr $(,)?) => {
211        const _: () = {
212            #[ $crate::__reexports::ctor::ctor(unsafe, anonymous, crate_path = $crate::__reexports::ctor) ]
213            fn __ic_memory_register_static_declaration() {
214                $crate::register_static_memory_manager_declaration(
215                    $id,
216                    $authority,
217                    $label,
218                    $stable_key,
219                )
220                .expect("ic-memory static memory declaration failed");
221            }
222        };
223    };
224}
225
226/// Declare a `MemoryManager` allocation range during static initialization.
227///
228/// The explicit authority must match every declaration that uses this range.
229#[macro_export]
230macro_rules! ic_memory_range {
231    (authority = $authority:literal, start = $start:expr, end = $end:expr $(,)?) => {
232        $crate::ic_memory_range!(
233            authority = $authority,
234            start = $start,
235            end = $end,
236            mode = Reserved,
237        );
238    };
239    (authority = $authority:literal, start = $start:expr, end = $end:expr, mode = $mode:ident $(,)?) => {
240        const _: () = {
241            #[ $crate::__reexports::ctor::ctor(unsafe, anonymous, crate_path = $crate::__reexports::ctor) ]
242            fn __ic_memory_register_static_range() {
243                $crate::register_static_memory_manager_range(
244                    $start,
245                    $end,
246                    $authority,
247                    $crate::MemoryManagerRangeMode::$mode,
248                    None,
249                )
250                .expect("ic-memory static memory range declaration failed");
251            }
252        };
253    };
254}
255
256/// Declare and open a committed `MemoryManager` slot by stable key.
257///
258/// The macro registers declaration metadata during static initialization and
259/// returns the committed default-runtime memory handle at expression use time.
260#[macro_export]
261macro_rules! ic_memory_key {
262    (authority = $authority:literal, key = $stable_key:literal, ty = $label:path, id = $id:expr $(,)?) => {{
263        $crate::ic_memory_declaration!(authority = $authority, key = $stable_key, ty = $label, id = $id,);
264        $crate::open_default_memory_manager_memory($stable_key, $id)
265            .expect("ic-memory failed to open committed stable memory; bootstrap must run first and the stable key/id must match the committed declaration")
266    }};
267    (authority = $authority:literal, key = $stable_key:literal, label = $label:literal, id = $id:expr $(,)?) => {{
268        $crate::ic_memory_declaration!(authority = $authority, key = $stable_key, label = $label, id = $id,);
269        $crate::open_default_memory_manager_memory($stable_key, $id)
270            .expect("ic-memory failed to open committed stable memory; bootstrap must run first and the stable key/id must match the committed declaration")
271    }};
272}
273
274/// Register one pre-bootstrap hook.
275#[macro_export]
276macro_rules! eager_init {
277    ($body:block) => {
278        const _: () = {
279            fn __ic_memory_registered_eager_init_body() {
280                $body
281            }
282
283            #[ $crate::__reexports::ctor::ctor(unsafe, anonymous, crate_path = $crate::__reexports::ctor) ]
284            fn __ic_memory_register_eager_init() {
285                $crate::defer_eager_init(__ic_memory_registered_eager_init_body);
286            }
287        };
288    };
289}