Skip to main content

ic_memory/runtime/
bootstrap.rs

1//! Recover, admit and persist allocation bindings before publishing authority.
2use super::{
3    BootstrapAdmission, CommittedRuntime, MemoryRuntime, RuntimeBootstrapBinding,
4    RuntimeBootstrapError, RuntimeLifecycle, RuntimeMemory,
5};
6use crate::{
7    LedgerCommitStore, PolicyIdentity, RuntimeBootstrapPolicy, STABLE_CELL_VALUE_OFFSET,
8    SealedDeclarationSnapshot,
9    slot::MEMORY_MANAGER_LEDGER_ID,
10    stable_cell::{EncodedLedgerRecord, decode_stable_cell_commit_store_from_memory},
11};
12use ic_platform::stable_structures::{Cell, Memory};
13
14impl<M: Memory> MemoryRuntime<M> {
15    /// Bootstrap this backing memory from one immutable declaration snapshot.
16    ///
17    /// Recovery, metadata admission, logical resolution, policy evaluation,
18    /// staging, persistence and binding publication are local to this runtime.
19    /// A repeated call is idempotent only when the sealed declaration snapshot,
20    /// canonical host pool and [`RuntimeBootstrapPolicy::runtime_bootstrap_identity`] match the
21    /// successful bootstrap. A mismatch returns a typed error without
22    /// advancing the durable generation or re-evaluating policy.
23    /// Independently sealed snapshots match when their canonical contents are equal.
24    ///
25    /// # Panics
26    ///
27    /// Panics if a private runtime or encoding invariant is broken, or backing
28    /// memory or a policy callback panics.
29    pub fn bootstrap<P: RuntimeBootstrapPolicy>(
30        &mut self,
31        declarations: &SealedDeclarationSnapshot,
32        pool: &crate::MemoryAllocationPool,
33        policy: &P,
34    ) -> Result<u64, RuntimeBootstrapError<P::Error>> {
35        match &self.lifecycle {
36            RuntimeLifecycle::Unbootstrapped => self.bootstrap_unbootstrapped(
37                declarations,
38                pool,
39                policy,
40                policy.runtime_bootstrap_identity(),
41            ),
42            RuntimeLifecycle::Bootstrapped(_) => self.verify_bootstrap(declarations, pool, policy),
43        }
44    }
45
46    /// Verify the exact host bootstrap binding through a shared runtime reference.
47    ///
48    /// Returns the established generation only when declarations, pool and the
49    /// policy identity match. Evaluates the policy identity once, but never runs
50    /// admission or allocation validation, reads backing memory, writes, grows,
51    /// or opens an allocation. An unbootstrapped runtime is refused rather than
52    /// initialized. Hosts may share one committed runtime through `Rc` without
53    /// reconstructing its binding checks or attempting mutable bootstrap again.
54    pub fn verify_bootstrap<P: RuntimeBootstrapPolicy>(
55        &self,
56        declarations: &SealedDeclarationSnapshot,
57        pool: &crate::MemoryAllocationPool,
58        policy: &P,
59    ) -> Result<u64, RuntimeBootstrapError<P::Error>> {
60        let committed = self
61            .committed()
62            .map_err(|_| RuntimeBootstrapError::NotBootstrapped)?;
63        committed
64            .binding
65            .validate(declarations, pool, &policy.runtime_bootstrap_identity())?;
66        Ok(committed.generation)
67    }
68
69    pub(super) fn validate_pool_custody(
70        &self,
71        occupied: &[bool; crate::constants::MAX_ALLOCATIONS],
72        pool: &crate::MemoryAllocationPool,
73    ) -> Result<(), super::MemoryResolutionError> {
74        for id in 0..crate::MEMORY_MANAGER_INVALID_ID {
75            if pool.contains(id) && self.memory_size_pages(id) != 0 && !occupied[usize::from(id)] {
76                return Err(super::MemoryResolutionError::UnmanagedAllocation { id });
77            }
78        }
79        Ok(())
80    }
81
82    fn bootstrap_unbootstrapped<P: RuntimeBootstrapPolicy>(
83        &mut self,
84        declarations: &SealedDeclarationSnapshot,
85        pool: &crate::MemoryAllocationPool,
86        policy: &P,
87        policy_identity: PolicyIdentity,
88    ) -> Result<u64, RuntimeBootstrapError<P::Error>> {
89        self.growth.verify()?;
90        let mut memory = self
91            .memory(MEMORY_MANAGER_LEDGER_ID)
92            .expect("governance memory is not publicly leasable");
93        let mut record = decode_stable_cell_commit_store_from_memory(&memory)?;
94        let recovered = record.recover_or_genesis()?;
95        let occupied = recovered.ledger().occupied_slots();
96        self.validate_pool_custody(&occupied, pool)?;
97        if memory.size() == 0 {
98            // Empty memory decodes to an uninitialized record. Preserve fresh
99            // cell acquisition before admission without persisting genesis.
100            memory = write_ledger_cell(memory, &LedgerCommitStore::default())?;
101        }
102        let mut admission = BootstrapAdmission::new(recovered.ledger(), declarations, pool);
103        let preparation = policy.prepare_bootstrap(&mut admission);
104        let historical = admission.complete()?;
105        preparation.map_err(RuntimeBootstrapError::AdmissionPolicy)?;
106        let resolved = declarations.resolve(&recovered, &occupied, historical, pool)?;
107        let (generation, resolved) = {
108            let (staged, resolved) = crate::ledger::stage_generation(recovered, resolved, policy)?;
109            record.commit_generation(&staged)?;
110            (staged.current_generation(), resolved)
111        };
112        drop(write_ledger_cell(memory, &record)?);
113        // Publish application bindings only after the durable Cell write succeeds.
114        let mut allocations = resolved;
115        allocations.retain(|declaration| {
116            !crate::is_ic_memory_stable_key(declaration.stable_key().as_str())
117        });
118        self.lifecycle = RuntimeLifecycle::Bootstrapped(CommittedRuntime {
119            allocations,
120            generation,
121            binding: RuntimeBootstrapBinding {
122                source: declarations.clone(),
123                policy_identity,
124                pool: pool.clone(),
125            },
126        });
127        Ok(generation)
128    }
129}
130
131impl RuntimeBootstrapBinding {
132    pub(super) fn validate<P>(
133        &self,
134        declarations: &SealedDeclarationSnapshot,
135        pool: &crate::MemoryAllocationPool,
136        policy_identity: &PolicyIdentity,
137    ) -> Result<(), RuntimeBootstrapError<P>> {
138        if &self.source != declarations {
139            return Err(RuntimeBootstrapError::DeclarationSnapshotMismatch);
140        }
141        if &self.pool != pool {
142            return Err(RuntimeBootstrapError::AllocationPoolMismatch);
143        }
144        if &self.policy_identity != policy_identity {
145            return Err(RuntimeBootstrapError::PolicyIdentityMismatch {
146                established: self.policy_identity,
147                requested: *policy_identity,
148            });
149        }
150        Ok(())
151    }
152}
153
154fn write_ledger_cell<M: Memory, P>(
155    memory: RuntimeMemory<M>,
156    record: &LedgerCommitStore,
157) -> Result<RuntimeMemory<M>, RuntimeBootstrapError<P>> {
158    let encoded =
159        EncodedLedgerRecord::new(record).map_err(RuntimeBootstrapError::StableCellLedgerEncode)?;
160    let value_size = encoded.len();
161    // The record limit is below u32::MAX, including the eight-byte cell header.
162    let required_bytes = STABLE_CELL_VALUE_OFFSET + value_size as u64;
163    let available_bytes = memory.size().saturating_mul(crate::WASM_PAGE_SIZE_BYTES);
164    if required_bytes > available_bytes {
165        let grow_by = (required_bytes - available_bytes).div_ceil(crate::WASM_PAGE_SIZE_BYTES);
166        memory.grow(grow_by)?;
167    }
168    Ok(Cell::new(memory, encoded).into_memory())
169}