canic_core/ops/request/
response.rs

1//! Root-side handlers that fulfil orchestration requests.
2//!
3//! The root canister exposes `canic_response`, which accepts a
4//! [`Request`](crate::ops::request::Request) and returns a [`Response`]. This
5//! module contains the implementations for create/upgrade/cycle flows plus the
6//! corresponding response payloads.
7
8use crate::{
9    Error,
10    interface::ic::{canister::upgrade_canister, deposit_cycles},
11    log::Topic,
12    ops::{
13        canister::create_and_install_canister,
14        model::memory::topology::subnet::SubnetCanisterRegistryOps,
15        prelude::*,
16        request::{
17            CreateCanisterParent, CreateCanisterRequest, CyclesRequest, Request, RequestOpsError,
18            UpgradeCanisterRequest,
19        },
20        wasm::WasmOps,
21    },
22};
23
24///
25/// Response
26/// Response payloads produced by root for orchestration requests.
27///
28
29#[derive(CandidType, Clone, Debug, Deserialize)]
30pub enum Response {
31    CreateCanister(CreateCanisterResponse),
32    UpgradeCanister(UpgradeCanisterResponse),
33    Cycles(CyclesResponse),
34}
35
36///
37/// CreateCanisterResponse
38/// Result of creating and installing a new canister.
39///
40
41#[derive(CandidType, Clone, Debug, Deserialize)]
42pub struct CreateCanisterResponse {
43    pub new_canister_pid: Principal,
44}
45
46///
47/// UpgradeCanisterResponse
48/// Result of an upgrade request (currently empty, reserved for metadata)
49///
50
51#[derive(CandidType, Clone, Debug, Deserialize)]
52pub struct UpgradeCanisterResponse {}
53
54///
55/// CyclesResponse
56/// Result of transferring cycles to a child canister
57///
58
59#[derive(CandidType, Clone, Debug, Deserialize)]
60pub struct CyclesResponse {
61    pub cycles_transferred: u128,
62}
63
64/// Handle a root-bound orchestration request and produce a [`Response`].
65pub async fn response(req: Request) -> Result<Response, Error> {
66    OpsError::require_root()?;
67
68    match req {
69        Request::CreateCanister(req) => create_canister_response(&req).await,
70        Request::UpgradeCanister(req) => upgrade_canister_response(&req).await,
71        Request::Cycles(req) => cycles_response(&req).await,
72    }
73}
74
75// create_canister_response
76async fn create_canister_response(req: &CreateCanisterRequest) -> Result<Response, Error> {
77    let caller = msg_caller();
78    let role = req.canister_role.clone();
79    let parent_desc = format!("{:?}", &req.parent);
80
81    let result: Result<Response, Error> = async {
82        // Look up parent
83        let parent_pid = match &req.parent {
84            CreateCanisterParent::Canister(pid) => *pid,
85            CreateCanisterParent::Root => canister_self(),
86            CreateCanisterParent::ThisCanister => caller,
87
88            CreateCanisterParent::Parent => SubnetCanisterRegistryOps::try_get_parent(caller)
89                .map_err(|_| RequestOpsError::ParentNotFound(caller))?,
90
91            CreateCanisterParent::Directory(ty) => {
92                SubnetCanisterRegistryOps::try_get_type(ty)
93                    .map_err(|_| RequestOpsError::CanisterRoleNotFound(ty.clone()))?
94                    .pid
95            }
96        };
97
98        let new_canister_pid =
99            create_and_install_canister(&req.canister_role, parent_pid, req.extra_arg.clone())
100                .await?;
101
102        Ok(Response::CreateCanister(CreateCanisterResponse {
103            new_canister_pid,
104        }))
105    }
106    .await;
107
108    if let Err(err) = &result {
109        log!(
110            Topic::CanisterLifecycle,
111            Warn,
112            "create_canister_response failed (caller={caller}, role={role}, parent={parent_desc}): {err}"
113        );
114    }
115
116    result
117}
118
119// upgrade_canister_response
120async fn upgrade_canister_response(req: &UpgradeCanisterRequest) -> Result<Response, Error> {
121    let caller = msg_caller();
122    let registry_entry = SubnetCanisterRegistryOps::try_get(req.canister_pid)
123        .map_err(|_| RequestOpsError::ChildNotFound(req.canister_pid))?;
124
125    if registry_entry.parent_pid != Some(caller) {
126        return Err(RequestOpsError::NotChildOfCaller(req.canister_pid, caller).into());
127    }
128
129    // Use the registry's type to avoid trusting request payload.
130    let wasm = WasmOps::try_get(&registry_entry.ty)?;
131    upgrade_canister(registry_entry.pid, wasm.bytes()).await?;
132    SubnetCanisterRegistryOps::update_module_hash(registry_entry.pid, wasm.module_hash())?;
133
134    Ok(Response::UpgradeCanister(UpgradeCanisterResponse {}))
135}
136
137// cycles_response
138async fn cycles_response(req: &CyclesRequest) -> Result<Response, Error> {
139    deposit_cycles(msg_caller(), req.cycles).await?;
140
141    let cycles_transferred = req.cycles;
142
143    Ok(Response::Cycles(CyclesResponse { cycles_transferred }))
144}