Skip to content

Commit

Permalink
dfx canister request-status
Browse files Browse the repository at this point in the history
  • Loading branch information
mraszyk committed Nov 29, 2024
1 parent 6b5743a commit 3376540
Show file tree
Hide file tree
Showing 3 changed files with 85 additions and 56 deletions.
39 changes: 25 additions & 14 deletions e2e/tests-dfx/impersonate.bash
Original file line number Diff line number Diff line change
Expand Up @@ -19,13 +19,14 @@ teardown() {

dfx_start
assert_command dfx deploy hello_backend
CANISTER_ID="$(dfx canister id hello_backend)"

# set the management canister as the only controller
assert_command dfx canister update-settings hello_backend --set-controller aaaaa-aa --yes

# updating settings now fails because the default identity does not control the canister anymore
assert_command_fail dfx canister update-settings hello_backend --freezing-threshold 0
assert_contains "Only controllers of canister $(dfx canister id hello_backend) can call ic00 method update_settings"
assert_contains "Only controllers of canister $CANISTER_ID can call ic00 method update_settings"

# updating settings succeeds when impersonating the management canister as the sender
assert_command dfx canister update-settings hello_backend --freezing-threshold 0 --impersonate aaaaa-aa
Expand All @@ -36,7 +37,7 @@ teardown() {

# canister status fails because the default identity does not control the canister anymore
assert_command_fail dfx canister status hello_backend
assert_contains "Only controllers of canister $(dfx canister id hello_backend) can call ic00 method canister_status"
assert_contains "Only controllers of canister $CANISTER_ID can call ic00 method canister_status"

# canister status succeeds when impersonating the management canister as the sender
assert_command dfx canister status hello_backend --impersonate aaaaa-aa
Expand All @@ -48,36 +49,46 @@ teardown() {

# test management canister call submission failure
assert_command_fail dfx canister status hello_backend --impersonate aaaaa-aa
assert_contains "Failed to submit management canister call: IC0207: Canister $(dfx canister id hello_backend) is out of cycles"
assert_contains "Failed to submit management canister call: IC0207: Canister $CANISTER_ID is out of cycles"

# test update call submission failure
assert_command_fail dfx canister call aaaaa-aa canister_status "(record { canister_id=principal\"$(dfx canister id hello_backend)\" })" --update --impersonate aaaaa-aa
assert_contains "Failed to submit canister call: IC0207: Canister $(dfx canister id hello_backend) is out of cycles"
assert_command_fail dfx canister call aaaaa-aa canister_status "(record { canister_id=principal\"$CANISTER_ID\" })" --update --impersonate aaaaa-aa
assert_contains "Failed to submit canister call: IC0207: Canister $CANISTER_ID is out of cycles"

# test async call submission failure
assert_command_fail dfx canister call aaaaa-aa canister_status "(record { canister_id=principal\"$(dfx canister id hello_backend)\" })" --async --impersonate aaaaa-aa
assert_contains "Failed to submit canister call: IC0207: Canister $(dfx canister id hello_backend) is out of cycles"
assert_command_fail dfx canister call aaaaa-aa canister_status "(record { canister_id=principal\"$CANISTER_ID\" })" --async --impersonate aaaaa-aa
assert_contains "Failed to submit canister call: IC0207: Canister $CANISTER_ID is out of cycles"

# unfreeze the canister
assert_command dfx canister update-settings hello_backend --freezing-threshold 0 --impersonate aaaaa-aa

# test update call failure
assert_command_fail dfx canister call aaaaa-aa delete_canister "(record { canister_id=principal\"$(dfx canister id hello_backend)\" })" --update --impersonate aaaaa-aa
assert_contains "Canister call failed: IC0510: Canister $(dfx canister id hello_backend) must be stopped before it is deleted."
assert_command_fail dfx canister call aaaaa-aa delete_canister "(record { canister_id=principal\"$CANISTER_ID\" })" --update --impersonate aaaaa-aa
assert_contains "Canister call failed: IC0510: Canister $CANISTER_ID must be stopped before it is deleted."

# test update call
assert_command dfx canister call aaaaa-aa start_canister "(record { canister_id=principal\"$(dfx canister id hello_backend)\" })" --update --impersonate aaaaa-aa
assert_command dfx canister call aaaaa-aa start_canister "(record { canister_id=principal\"$CANISTER_ID\" })" --update --impersonate aaaaa-aa
assert_contains "()"

# test async call
assert_command dfx canister call aaaaa-aa canister_status "(record { canister_id=principal\"$(dfx canister id hello_backend)\" })" --async --impersonate aaaaa-aa
assert_command dfx canister call aaaaa-aa canister_status "(record { canister_id=principal\"$CANISTER_ID\" })" --async --impersonate aaaaa-aa
assert_contains "Request ID:"

# test request status failure
RID=$(dfx canister call aaaaa-aa delete_canister "(record { canister_id=principal\"$CANISTER_ID\" })" --async --impersonate aaaaa-aa)
assert_command_fail dfx canister request-status "$RID" hello_backend
assert_contains "Canister call failed: IC0510: Canister $CANISTER_ID must be stopped before it is deleted."

# test request status
RID=$(dfx canister call aaaaa-aa canister_status "(record { canister_id=principal\"$CANISTER_ID\" })" --async --impersonate aaaaa-aa)
assert_command dfx canister request-status "$RID" hello_backend
assert_contains "record {"

# test query call failure
assert_command_fail dfx canister call aaaaa-aa fetch_canister_logs "(record { canister_id=principal\"$(dfx canister id hello_backend)\" })" --query --impersonate "$(dfx canister id hello_backend)"
assert_contains "Failed to perform query call: IC0406: Caller $(dfx canister id hello_backend) is not allowed to query ic00 method fetch_canister_logs"
assert_command_fail dfx canister call aaaaa-aa fetch_canister_logs "(record { canister_id=principal\"$CANISTER_ID\" })" --query --impersonate "$CANISTER_ID"
assert_contains "Failed to perform query call: IC0406: Caller $CANISTER_ID is not allowed to query ic00 method fetch_canister_logs"

# test query call
assert_command dfx canister call aaaaa-aa fetch_canister_logs "(record { canister_id=principal\"$(dfx canister id hello_backend)\" })" --query --impersonate aaaaa-aa
assert_command dfx canister call aaaaa-aa fetch_canister_logs "(record { canister_id=principal\"$CANISTER_ID\" })" --query --impersonate aaaaa-aa
assert_contains "(record { 1_754_302_831 = vec {} })"
}
93 changes: 55 additions & 38 deletions src/dfx/src/commands/canister/request_status.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,15 @@ use crate::lib::error::{DfxError, DfxResult};
use crate::lib::root_key::fetch_root_key_if_needed;
use crate::util::clap::parsers;
use crate::util::print_idl_blob;
use anyhow::Context;
use anyhow::{anyhow, bail, Context};
use backoff::backoff::Backoff;
use backoff::ExponentialBackoff;
use candid::Principal;
use clap::Parser;
use ic_agent::agent::RequestStatusResponse;
use ic_agent::{AgentError, RequestId};
use pocket_ic::common::rest::{RawEffectivePrincipal, RawMessageId};
use pocket_ic::WasmResult;
use std::str::FromStr;

/// Requests the status of a call from a canister.
Expand Down Expand Up @@ -46,46 +48,61 @@ pub async fn exec(env: &dyn Environment, opts: RequestStatusOpts) -> DfxResult {
let canister_id = Principal::from_text(callee_canister)
.or_else(|_| canister_id_store.get(callee_canister))?;

let mut retry_policy = ExponentialBackoff::default();
let blob = async {
let mut request_accepted = false;
loop {
match agent
.request_status_raw(&request_id, canister_id)
.await
.context("Failed to fetch request status.")?
{
RequestStatusResponse::Replied(reply) => return Ok(reply.arg),
RequestStatusResponse::Rejected(response) => {
return Err(DfxError::new(AgentError::CertifiedReject(response)))
}
RequestStatusResponse::Unknown => (),
RequestStatusResponse::Received | RequestStatusResponse::Processing => {
// The system will return Unknown until the request is accepted
// and we generally cannot know how long that will take.
// State transitions between Received and Processing may be
// instantaneous. Therefore, once we know the request is accepted,
// we restart the waiter so the request does not time out.
if !request_accepted {
retry_policy.reset();
request_accepted = true;
let blob = if let Some(pocketic) = env.get_pocketic() {
let msg_id = RawMessageId {
effective_principal: RawEffectivePrincipal::CanisterId(canister_id.as_slice().to_vec()),
message_id: request_id.as_slice().to_vec(),
};
let res = pocketic
.await_call_no_ticks(msg_id)
.await
.map_err(|err| anyhow!("Canister call failed: {}", err))?;
match res {
WasmResult::Reply(data) => data,
WasmResult::Reject(err) => bail!("Canister rejected: {}", err),
}
} else {
async {
let mut retry_policy = ExponentialBackoff::default();
let mut request_accepted = false;
loop {
match agent
.request_status_raw(&request_id, canister_id)
.await
.context("Failed to fetch request status.")?
{
RequestStatusResponse::Replied(reply) => return Ok(reply.arg),
RequestStatusResponse::Rejected(response) => {
return Err(DfxError::new(AgentError::CertifiedReject(response)))
}
RequestStatusResponse::Unknown => (),
RequestStatusResponse::Received | RequestStatusResponse::Processing => {
// The system will return Unknown until the request is accepted
// and we generally cannot know how long that will take.
// State transitions between Received and Processing may be
// instantaneous. Therefore, once we know the request is accepted,
// we restart the waiter so the request does not time out.
if !request_accepted {
retry_policy.reset();
request_accepted = true;
}
}
RequestStatusResponse::Done => {
return Err(DfxError::new(AgentError::RequestStatusDoneNoReply(
String::from(request_id),
)))
}
}
RequestStatusResponse::Done => {
return Err(DfxError::new(AgentError::RequestStatusDoneNoReply(
String::from(request_id),
)))
}
};
};

let interval = retry_policy
.next_backoff()
.ok_or_else(|| DfxError::new(AgentError::TimeoutWaitingForResponse()))?;
tokio::time::sleep(interval).await;
let interval = retry_policy
.next_backoff()
.ok_or_else(|| DfxError::new(AgentError::TimeoutWaitingForResponse()))?;
tokio::time::sleep(interval).await;
}
}
}
.await
.map_err(DfxError::from)?;
.await
.map_err(DfxError::from)?
};

let output_type = opts.output.as_deref();
print_idl_blob(&blob, output_type, &None)?;
Expand Down
9 changes: 5 additions & 4 deletions src/dfx/src/lib/operations/canister/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -149,11 +149,12 @@ where
encode_args((arg,)).unwrap(),
)
.await
.map_err(|err| anyhow!("Failed to perform management canister query call: {}", err))?;
.map_err(|err| {
anyhow!("Failed to perform management canister query call: {}", err)
})?;
match res {
WasmResult::Reply(data) => {
decode_args(&data).context("Failed to decode management canister query call response.")?
}
WasmResult::Reply(data) => decode_args(&data)
.context("Failed to decode management canister query call response.")?,
WasmResult::Reject(err) => bail!("Management canister rejected: {}", err),
}
} else {
Expand Down

0 comments on commit 3376540

Please sign in to comment.