canic_core/ops/request/
request.rs

1use crate::{
2    Error,
3    ids::CanisterRole,
4    log::Topic,
5    ops::{
6        model::memory::{EnvOps, topology::SubnetCanisterChildrenOps},
7        prelude::*,
8        request::{
9            CreateCanisterResponse, CyclesResponse, RequestOpsError, Response,
10            UpgradeCanisterResponse,
11        },
12    },
13};
14use candid::encode_one;
15
16///
17/// Request
18/// Root-directed orchestration commands.
19///
20
21#[derive(CandidType, Clone, Debug, Deserialize)]
22pub enum Request {
23    CreateCanister(CreateCanisterRequest),
24    UpgradeCanister(UpgradeCanisterRequest),
25    Cycles(CyclesRequest),
26}
27
28///
29/// CreateCanisterRequest
30/// Payload for [`Request::CreateCanister`]
31///
32
33#[derive(CandidType, Clone, Debug, Deserialize)]
34pub struct CreateCanisterRequest {
35    pub canister_role: CanisterRole,
36    pub parent: CreateCanisterParent,
37    pub extra_arg: Option<Vec<u8>>,
38}
39
40///
41/// CreateCanisterParent
42/// Parent-location choices for a new canister
43///
44
45#[derive(CandidType, Clone, Debug, Deserialize)]
46pub enum CreateCanisterParent {
47    Root,
48    /// Use the requesting canister as parent.
49    ThisCanister,
50    /// Use the requesting canister's parent (creates a sibling).
51    Parent,
52    Canister(Principal),
53    Directory(CanisterRole),
54}
55
56///
57/// UpgradeCanisterRequest
58/// Payload for [`Request::UpgradeCanister`]
59///
60
61#[derive(CandidType, Clone, Debug, Deserialize)]
62pub struct UpgradeCanisterRequest {
63    pub canister_pid: Principal,
64    pub canister_type: CanisterRole,
65}
66
67///
68/// CyclesRequest
69/// Payload for [`Request::Cycles`]
70///
71
72#[derive(CandidType, Clone, Debug, Deserialize)]
73pub struct CyclesRequest {
74    pub cycles: u128,
75}
76
77/// Send a request to the root canister and decode its response.
78async fn request(request: Request) -> Result<Response, Error> {
79    let root_pid = EnvOps::try_get_root_pid().map_err(|_| RequestOpsError::RootNotFound)?;
80
81    let call_response = Call::unbounded_wait(root_pid, "canic_response")
82        .with_arg(&request)
83        .await?;
84
85    call_response.candid::<Result<Response, Error>>()?
86}
87
88/// Ask root to create and install a canister of the given type.
89pub async fn create_canister_request<A>(
90    canister_role: &CanisterRole,
91    parent: CreateCanisterParent,
92    extra: Option<A>,
93) -> Result<CreateCanisterResponse, Error>
94where
95    A: CandidType + Send + Sync,
96{
97    let encoded = extra.map(|v| encode_one(v)).transpose()?;
98    let role = canister_role.clone();
99    let parent_desc = format!("{:?}", &parent);
100    let caller_ty =
101        EnvOps::try_get_canister_type().map_or_else(|_| "unknown".to_string(), |ty| ty.to_string());
102
103    // build request
104    let q = Request::CreateCanister(CreateCanisterRequest {
105        canister_role: canister_role.clone(),
106        parent,
107        extra_arg: encoded,
108    });
109
110    match request(q).await {
111        Ok(Response::CreateCanister(res)) => Ok(res),
112        Ok(_) => {
113            log!(
114                Topic::CanisterLifecycle,
115                Warn,
116                "create_canister_request: invalid response type (caller={caller_ty}, role={role}, parent={parent_desc})"
117            );
118
119            Err(RequestOpsError::InvalidResponseType.into())
120        }
121        Err(err) => {
122            log!(
123                Topic::CanisterLifecycle,
124                Warn,
125                "create_canister_request failed (caller={caller_ty}, role={role}, parent={parent_desc}): {err}"
126            );
127
128            Err(err)
129        }
130    }
131}
132
133/// Ask root to upgrade a child canister to its latest registered WASM.
134pub async fn upgrade_canister_request(
135    canister_pid: Principal,
136) -> Result<UpgradeCanisterResponse, Error> {
137    // check this is a valid child
138    let canister = SubnetCanisterChildrenOps::find_by_pid(&canister_pid)
139        .ok_or(RequestOpsError::ChildNotFound(canister_pid))?;
140
141    // send the request
142    let q = Request::UpgradeCanister(UpgradeCanisterRequest {
143        canister_pid: canister.pid,
144        canister_type: canister.ty,
145    });
146
147    match request(q).await? {
148        Response::UpgradeCanister(res) => Ok(res),
149        _ => Err(RequestOpsError::InvalidResponseType.into()),
150    }
151}
152
153/// Request a cycle transfer from root to the current canister.
154pub async fn cycles_request(cycles: u128) -> Result<CyclesResponse, Error> {
155    OpsError::deny_root()?;
156
157    let q = Request::Cycles(CyclesRequest { cycles });
158
159    match request(q).await? {
160        Response::Cycles(res) => Ok(res),
161        _ => Err(RequestOpsError::InvalidResponseType.into()),
162    }
163}