canic_core/ops/rpc/
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    log::Topic,
11    ops::{
12        ic::deposit_cycles,
13        orchestration::orchestrator::{CanisterLifecycleOrchestrator, LifecycleEvent},
14        prelude::*,
15        rpc::{
16            CreateCanisterParent, CreateCanisterRequest, CyclesRequest, Request, RequestOpsError,
17            UpgradeCanisterRequest,
18        },
19        storage::topology::subnet::SubnetCanisterRegistryOps,
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            role: 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(RequestOpsError::MissingNewCanisterPid)?;
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
129    let registry_entry = SubnetCanisterRegistryOps::try_get(req.canister_pid)
130        .map_err(|_| RequestOpsError::ChildNotFound(req.canister_pid))?;
131
132    if registry_entry.parent_pid != Some(caller) {
133        return Err(RequestOpsError::NotChildOfCaller(req.canister_pid, caller).into());
134    }
135
136    let event = LifecycleEvent::Upgrade {
137        pid: registry_entry.pid,
138    };
139
140    CanisterLifecycleOrchestrator::apply(event).await?;
141
142    Ok(Response::UpgradeCanister(UpgradeCanisterResponse {}))
143}
144
145// cycles_response
146async fn cycles_response(req: &CyclesRequest) -> Result<Response, Error> {
147    deposit_cycles(msg_caller(), req.cycles).await?;
148
149    let cycles_transferred = req.cycles;
150
151    Ok(Response::Cycles(CyclesResponse { cycles_transferred }))
152}