canic_core/ops/request/
request.rs

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