canic/ops/request/
mod.rs

1//! Helpers that build requests routed through the root canister.
2//!
3//! Non-root canisters submit orchestration requests to root using the
4//! `canic_response` endpoint. This module owns the request envelope and
5//! high-level helpers for creating new canisters, triggering upgrades, or
6//! moving cycles between principals.
7
8mod response;
9
10pub use response::*;
11
12use crate::{
13    Error,
14    cdk::call::Call,
15    memory::{Env, topology::SubnetCanisterChildren},
16    ops::prelude::*,
17};
18use candid::encode_one;
19use thiserror::Error as ThisError;
20
21///
22/// RequestError
23/// Errors produced during request dispatch or response handling
24///
25
26#[derive(Debug, ThisError)]
27pub enum RequestError {
28    #[error("this request is not allowed to be called on root")]
29    RootNotAllowed,
30
31    #[error("invalid response type")]
32    InvalidResponseType,
33}
34
35impl From<RequestError> for Error {
36    fn from(err: RequestError) -> Self {
37        OpsError::from(err).into()
38    }
39}
40
41///
42/// Request
43/// Root-directed orchestration commands.
44///
45
46#[derive(CandidType, Clone, Debug, Deserialize)]
47pub enum Request {
48    CreateCanister(CreateCanisterRequest),
49    UpgradeCanister(UpgradeCanisterRequest),
50    Cycles(CyclesRequest),
51}
52
53///
54/// CreateCanisterRequest
55/// Payload for [`Request::CreateCanister`]
56///
57
58#[derive(CandidType, Clone, Debug, Deserialize)]
59pub struct CreateCanisterRequest {
60    pub canister_type: CanisterType,
61    pub parent: CreateCanisterParent,
62    pub extra_arg: Option<Vec<u8>>,
63}
64
65///
66/// CreateCanisterParent
67/// Parent-location choices for a new canister
68///
69
70#[derive(CandidType, Clone, Debug, Deserialize)]
71pub enum CreateCanisterParent {
72    Root,
73    Caller,
74    Canister(Principal),
75    Directory(CanisterType),
76}
77
78///
79/// UpgradeCanisterRequest
80/// Payload for [`Request::UpgradeCanister`]
81///
82
83#[derive(CandidType, Clone, Debug, Deserialize)]
84pub struct UpgradeCanisterRequest {
85    pub canister_pid: Principal,
86    pub canister_type: CanisterType,
87}
88
89///
90/// CyclesRequest
91/// Payload for [`Request::Cycles`]
92///
93
94#[derive(CandidType, Clone, Debug, Deserialize)]
95pub struct CyclesRequest {
96    pub cycles: u128,
97}
98
99/// Send a request to the root canister and decode its response.
100async fn request(request: Request) -> Result<Response, Error> {
101    let root_pid = Env::try_get_root_pid()?;
102
103    let call_response = Call::unbounded_wait(root_pid, "canic_response")
104        .with_arg(&request)
105        .await?;
106
107    call_response.candid::<Result<Response, Error>>()?
108}
109
110/// Ask root to create and install a canister of the given type.
111pub async fn create_canister_request<A>(
112    canister_type: &CanisterType,
113    parent: CreateCanisterParent,
114    extra: Option<A>,
115) -> Result<CreateCanisterResponse, Error>
116where
117    A: CandidType + Send + Sync,
118{
119    let encoded = match extra {
120        Some(extra) => Some(encode_one(extra)?),
121        None => None,
122    };
123
124    // build request
125    let q = Request::CreateCanister(CreateCanisterRequest {
126        canister_type: canister_type.clone(),
127        parent,
128        extra_arg: encoded,
129    });
130
131    match request(q).await? {
132        Response::CreateCanister(res) => Ok(res),
133        _ => Err(OpsError::RequestError(RequestError::InvalidResponseType))?,
134    }
135}
136
137/// Ask root to upgrade a child canister to its latest registered WASM.
138pub async fn upgrade_canister_request(
139    canister_pid: Principal,
140) -> Result<UpgradeCanisterResponse, Error> {
141    // check this is a valid child
142    let canister = SubnetCanisterChildren::try_find_by_pid(&canister_pid)?;
143
144    // send the request
145    let q = Request::UpgradeCanister(UpgradeCanisterRequest {
146        canister_pid: canister.pid,
147        canister_type: canister.ty,
148    });
149
150    match request(q).await? {
151        Response::UpgradeCanister(res) => Ok(res),
152        _ => Err(OpsError::RequestError(RequestError::InvalidResponseType))?,
153    }
154}
155
156/// Request a cycle transfer from root to the current canister.
157pub async fn cycles_request(cycles: u128) -> Result<CyclesResponse, Error> {
158    let q = Request::Cycles(CyclesRequest { cycles });
159
160    if Env::is_root() {
161        return Err(OpsError::RequestError(RequestError::RootNotAllowed))?;
162    }
163
164    match request(q).await? {
165        Response::Cycles(res) => Ok(res),
166        _ => Err(OpsError::RequestError(RequestError::InvalidResponseType))?,
167    }
168}