canic_core/ops/runtime/
env.rs

1use crate::{
2    Error, ThisError,
3    cdk::{api::canister_self, types::Principal},
4    dto::{env::EnvView, subnet::SubnetIdentity},
5    ids::{CanisterRole, SubnetRole},
6    infra::ic::{Network, build_network},
7    model::memory::Env,
8    ops::{
9        adapter::env::{env_data_from_view, env_data_to_view},
10        runtime::RuntimeOpsError,
11    },
12};
13
14use crate::model::memory::env::EnvData;
15
16///
17/// EnvOpsError
18///
19
20#[derive(Debug, ThisError)]
21pub enum EnvOpsError {
22    /// Raised when a function requires root context, but was called from a child.
23    #[error("operation must be called from the root canister")]
24    NotRoot,
25
26    /// Raised when a function must not be called from root.
27    #[error("operation cannot be called from the root canister")]
28    IsRoot,
29
30    #[error("failed to determine current canister role")]
31    CanisterRoleUnavailable,
32
33    #[error("env import missing required fields: {0}")]
34    MissingFields(String),
35
36    #[error("failed to determine current parent principal")]
37    ParentPidUnavailable,
38
39    #[error("failed to determine current root principal")]
40    RootPidUnavailable,
41
42    #[error("failed to determine current subnet principal")]
43    SubnetPidUnavailable,
44
45    #[error("failed to determine current subnet role")]
46    SubnetRoleUnavailable,
47}
48
49impl EnvOpsError {}
50
51impl From<EnvOpsError> for Error {
52    fn from(err: EnvOpsError) -> Self {
53        RuntimeOpsError::from(err).into()
54    }
55}
56
57///
58/// EnvOps
59///
60/// NOTE:
61/// - `try_*` getters are test-only helpers for incomplete env setup.
62/// - Non-`try_*` getters assume the environment has been fully initialized
63///   during canister startup and will return errors if called earlier.
64/// - After initialization, absence of environment fields is a programmer error.
65///
66
67pub struct EnvOps;
68
69impl EnvOps {
70    // ---------------------------------------------------------------------
71    // Initialization / import
72    // ---------------------------------------------------------------------
73
74    /// Initialize environment state for the root canister during init.
75    ///
76    /// This must only be called from the IC `init` hook.
77    pub fn init_root(identity: SubnetIdentity) -> Result<(), Error> {
78        let self_pid = canister_self();
79
80        let (subnet_pid, subnet_role, prime_root_pid) = match identity {
81            SubnetIdentity::Prime => {
82                // Prime subnet: root == prime root == subnet
83                (self_pid, SubnetRole::PRIME, self_pid)
84            }
85
86            SubnetIdentity::Standard(params) => {
87                // Standard subnet syncing from prime
88                (self_pid, params.subnet_type, params.prime_root_pid)
89            }
90
91            SubnetIdentity::Manual(pid) => {
92                // Test/support only: explicit subnet override
93                (pid, SubnetRole::MANUAL, pid)
94            }
95        };
96
97        let env = EnvData {
98            prime_root_pid: Some(prime_root_pid),
99            root_pid: Some(self_pid),
100            subnet_pid: Some(subnet_pid),
101            subnet_role: Some(subnet_role),
102            canister_role: Some(CanisterRole::ROOT),
103            parent_pid: Some(prime_root_pid),
104        };
105
106        Self::import_data(env)
107    }
108
109    /// Initialize environment state for a non-root canister during init.
110    ///
111    /// This function must only be called from the IC `init` hook.
112    pub fn init(env: EnvView, role: CanisterRole) -> Result<(), Error> {
113        let mut env = env_data_from_view(env);
114        // Override contextual role (do not trust payload blindly)
115        env.canister_role = Some(role.clone());
116        env = ensure_nonroot_env(role, env)?;
117
118        // Import validates required fields and persists
119        Self::import_data(env)
120    }
121
122    pub fn import(env: EnvView) -> Result<(), Error> {
123        let env = env_data_from_view(env);
124        Self::import_data(env)
125    }
126
127    fn import_data(env: EnvData) -> Result<(), Error> {
128        let mut missing = Vec::new();
129        if env.prime_root_pid.is_none() {
130            missing.push("prime_root_pid");
131        }
132        if env.subnet_role.is_none() {
133            missing.push("subnet_role");
134        }
135        if env.subnet_pid.is_none() {
136            missing.push("subnet_pid");
137        }
138        if env.root_pid.is_none() {
139            missing.push("root_pid");
140        }
141        if env.canister_role.is_none() {
142            missing.push("canister_role");
143        }
144        if env.parent_pid.is_none() {
145            missing.push("parent_pid");
146        }
147
148        if !missing.is_empty() {
149            return Err(EnvOpsError::MissingFields(missing.join(", ")).into());
150        }
151
152        Env::import(env);
153        Ok(())
154    }
155
156    pub fn set_prime_root_pid(pid: Principal) {
157        Env::set_prime_root_pid(pid);
158    }
159
160    pub fn set_subnet_role(role: SubnetRole) {
161        Env::set_subnet_role(role);
162    }
163
164    pub fn set_subnet_pid(pid: Principal) {
165        Env::set_subnet_pid(pid);
166    }
167
168    pub fn set_root_pid(pid: Principal) {
169        Env::set_root_pid(pid);
170    }
171
172    pub fn set_canister_role(role: CanisterRole) {
173        Env::set_canister_role(role);
174    }
175
176    // ---------------------------------------------------------------------
177    // Environment predicates
178    // ---------------------------------------------------------------------
179
180    #[must_use]
181    pub fn is_prime_root() -> bool {
182        let Some(prime_root) = Env::get_prime_root_pid() else {
183            return false;
184        };
185        let Some(root_pid) = Env::get_root_pid() else {
186            return false;
187        };
188        prime_root == root_pid
189    }
190
191    #[must_use]
192    pub fn is_prime_subnet() -> bool {
193        Env::get_subnet_role().is_some_and(|role| role.is_prime())
194    }
195
196    #[must_use]
197    pub fn is_root() -> bool {
198        Env::get_root_pid().is_some_and(|pid| pid == canister_self())
199    }
200
201    // ---------------------------------------------------------------------
202    // Steady-state / required accessors
203    // (env must be initialized; missing values are errors)
204    // ---------------------------------------------------------------------
205
206    pub fn subnet_role() -> Result<SubnetRole, Error> {
207        Env::get_subnet_role().ok_or_else(|| EnvOpsError::SubnetRoleUnavailable.into())
208    }
209
210    pub fn canister_role() -> Result<CanisterRole, Error> {
211        Env::get_canister_role().ok_or_else(|| EnvOpsError::CanisterRoleUnavailable.into())
212    }
213
214    pub fn subnet_pid() -> Result<Principal, Error> {
215        Env::get_subnet_pid().ok_or_else(|| EnvOpsError::SubnetPidUnavailable.into())
216    }
217
218    pub fn root_pid() -> Result<Principal, Error> {
219        Env::get_root_pid().ok_or_else(|| EnvOpsError::RootPidUnavailable.into())
220    }
221
222    pub fn prime_root_pid() -> Result<Principal, Error> {
223        Env::get_prime_root_pid().ok_or_else(|| EnvOpsError::RootPidUnavailable.into())
224    }
225
226    pub fn parent_pid() -> Result<Principal, Error> {
227        Env::get_parent_pid().ok_or_else(|| EnvOpsError::ParentPidUnavailable.into())
228    }
229
230    // ---------------------------------------------------------------------
231    // Errors
232    // ---------------------------------------------------------------------
233
234    /// Ensure the caller is the root canister.
235    pub fn require_root() -> Result<(), Error> {
236        let root_pid = Self::root_pid()?;
237
238        if root_pid == canister_self() {
239            Ok(())
240        } else {
241            Err(EnvOpsError::NotRoot.into())
242        }
243    }
244
245    /// Ensure the caller is not the root canister.
246    pub fn deny_root() -> Result<(), Error> {
247        let root_pid = Self::root_pid()?; // explicit mapping
248
249        if root_pid == canister_self() {
250            Err(EnvOpsError::IsRoot.into())
251        } else {
252            Ok(())
253        }
254    }
255
256    // ---------------------------------------------------------------------
257    // Restore
258    // ---------------------------------------------------------------------
259
260    // NOTE:
261    // Restore functions are intended to be called ONLY from lifecycle adapters.
262    // Calling them during steady-state execution is a logic error.
263
264    /// Restore root environment context after upgrade.
265    ///
266    /// Root identity and subnet metadata must already be present.
267    pub fn restore_root() -> Result<(), Error> {
268        // Ensure environment was initialized before upgrade
269        assert_initialized()?;
270
271        // Root canister role is implicit
272        Env::set_canister_role(CanisterRole::ROOT);
273        Ok(())
274    }
275
276    /// Restore canister role context after upgrade.
277    ///
278    /// Environment data is expected to already exist in stable memory.
279    /// Failure indicates a programmer error or corrupted state.
280    pub fn restore_role(role: CanisterRole) -> Result<(), Error> {
281        // Ensure environment was initialized before upgrade
282        assert_initialized()?;
283
284        // Restore the role context explicitly
285        Env::set_canister_role(role);
286        Ok(())
287    }
288
289    // ---------------------------------------------------------------------
290    // Export
291    // ---------------------------------------------------------------------
292
293    /// Export a snapshot of the current environment metadata.
294    #[must_use]
295    pub fn export() -> EnvView {
296        env_data_to_view(Env::export())
297    }
298}
299
300fn assert_initialized() -> Result<(), Error> {
301    let mut missing = Vec::new();
302    if Env::get_root_pid().is_none() {
303        missing.push("root_pid");
304    }
305    if Env::get_subnet_pid().is_none() {
306        missing.push("subnet_pid");
307    }
308    if Env::get_prime_root_pid().is_none() {
309        missing.push("prime_root_pid");
310    }
311
312    if missing.is_empty() {
313        Ok(())
314    } else {
315        Err(EnvOpsError::MissingFields(missing.join(", ")).into())
316    }
317}
318
319fn ensure_nonroot_env(canister_role: CanisterRole, mut env: EnvData) -> Result<EnvData, Error> {
320    let mut missing = Vec::new();
321    if env.prime_root_pid.is_none() {
322        missing.push("prime_root_pid");
323    }
324    if env.subnet_role.is_none() {
325        missing.push("subnet_role");
326    }
327    if env.subnet_pid.is_none() {
328        missing.push("subnet_pid");
329    }
330    if env.root_pid.is_none() {
331        missing.push("root_pid");
332    }
333    if env.canister_role.is_none() {
334        missing.push("canister_role");
335    }
336    if env.parent_pid.is_none() {
337        missing.push("parent_pid");
338    }
339
340    if missing.is_empty() {
341        return Ok(env);
342    }
343
344    if build_network() == Some(Network::Ic) {
345        return Err(EnvOpsError::MissingFields(missing.join(", ")).into());
346    }
347
348    let root_pid = Principal::from_slice(&[0xBB; 29]);
349    let subnet_pid = Principal::from_slice(&[0xAA; 29]);
350
351    env.prime_root_pid.get_or_insert(root_pid);
352    env.subnet_role.get_or_insert(SubnetRole::PRIME);
353    env.subnet_pid.get_or_insert(subnet_pid);
354    env.root_pid.get_or_insert(root_pid);
355    env.canister_role.get_or_insert(canister_role);
356    env.parent_pid.get_or_insert(root_pid);
357
358    Ok(env)
359}