diff --git a/e2e/tests-dfx/impersonate.bash b/e2e/tests-dfx/impersonate.bash index 4b0180bc4a..ea4a227e27 100644 --- a/e2e/tests-dfx/impersonate.bash +++ b/e2e/tests-dfx/impersonate.bash @@ -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 @@ -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 @@ -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 {} })" } diff --git a/src/dfx/src/commands/canister/request_status.rs b/src/dfx/src/commands/canister/request_status.rs index c481bcf798..313d09af7d 100644 --- a/src/dfx/src/commands/canister/request_status.rs +++ b/src/dfx/src/commands/canister/request_status.rs @@ -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. @@ -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)?; diff --git a/src/dfx/src/lib/operations/canister/mod.rs b/src/dfx/src/lib/operations/canister/mod.rs index fbf10c9a97..57d54490d6 100644 --- a/src/dfx/src/lib/operations/canister/mod.rs +++ b/src/dfx/src/lib/operations/canister/mod.rs @@ -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 {