diff --git a/CHANGELOG.md b/CHANGELOG.md index 0a3f291221..85eddac7ac 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,8 @@ # UNRELEASED +### chore: improve error message when trying to use the local replica when it is not running + ### Frontend canister Allow setting permissions lists in init arguments just like in upgrade arguments. diff --git a/e2e/tests-dfx/error_context.bash b/e2e/tests-dfx/error_context.bash index 622116ae47..a09aaf365d 100644 --- a/e2e/tests-dfx/error_context.bash +++ b/e2e/tests-dfx/error_context.bash @@ -211,3 +211,13 @@ teardown() { assert_contains "it did not contain a function that dfx was looking for" assert_contains "dfx identity set-wallet --identity " } + +@test "Local replica not running has nice error messages" { + dfx_new + assert_command_fail dfx ping local + assert_contains "You are trying to connect to the local replica but dfx cannot connect to it." + assert_command_fail dfx deploy + assert_contains "You are trying to connect to the local replica but dfx cannot connect to it." + assert_command_fail dfx canister call um5iw-rqaaa-aaaaq-qaaba-cai some_method + assert_contains "You are trying to connect to the local replica but dfx cannot connect to it." +} diff --git a/src/dfx/src/lib/diagnosis.rs b/src/dfx/src/lib/diagnosis.rs index eb121f14ab..1eaa1b6f2c 100644 --- a/src/dfx/src/lib/diagnosis.rs +++ b/src/dfx/src/lib/diagnosis.rs @@ -1,5 +1,6 @@ use crate::lib::error_code; use anyhow::Error as AnyhowError; +use dfx_core::error::root_key::FetchRootKeyError; use ic_agent::agent::{RejectCode, RejectResponse}; use ic_agent::AgentError; use ic_asset::error::{GatherAssetDescriptorsError, SyncError, UploadContentError}; @@ -55,6 +56,10 @@ pub fn diagnose(err: &AnyhowError) -> Diagnosis { } } + if local_replica_not_running(err) { + return diagnose_local_replica_not_running(); + } + if let Some(sync_error) = err.downcast_ref::() { if duplicate_asset_key_dist_and_src(sync_error) { return diagnose_duplicate_asset_key_dist_and_src(); @@ -64,6 +69,32 @@ pub fn diagnose(err: &AnyhowError) -> Diagnosis { NULL_DIAGNOSIS } +fn local_replica_not_running(err: &AnyhowError) -> bool { + let maybe_agent_error = { + if let Some(FetchRootKeyError::AgentError(agent_error)) = + err.downcast_ref::() + { + Some(agent_error) + } else { + err.downcast_ref::() + } + }; + if let Some(AgentError::TransportError(transport_error)) = maybe_agent_error { + transport_error.is_connect() + && transport_error + .url() + .and_then(|url| url.host()) + .map(|host| match host { + url::Host::Domain(domain) => domain == "localhost", + url::Host::Ipv4(ipv4_addr) => ipv4_addr.is_loopback(), + url::Host::Ipv6(ipv6_addr) => ipv6_addr.is_loopback(), + }) + .unwrap_or(false) + } else { + false + } +} + fn not_a_controller(err: &AgentError) -> bool { // Newer replicas include the error code in the reject response. if matches!( @@ -119,6 +150,17 @@ The most common way this error is solved is by running 'dfx canister update-sett ) } +fn diagnose_local_replica_not_running() -> Diagnosis { + let error_explanation = + "You are trying to connect to the local replica but dfx cannot connect to it."; + let action_suggestion = + "Target a different network or run 'dfx start' to start the local replica."; + ( + Some(error_explanation.to_string()), + Some(action_suggestion.to_string()), + ) +} + fn subnet_not_authorized() -> Diagnosis { let action_suggestion = "If you are connecting to a node directly instead of a boundary node, try using --provisional-create-canister-effective-canister-id with a canister id in the subnet's canister range. First non-root subnet: 5v3p4-iyaaa-aaaaa-qaaaa-cai, second non-root subnet: jrlun-jiaaa-aaaab-aaaaa-cai"; (None, Some(action_suggestion.to_string()))