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