diff --git a/e2e/tests-dfx/create.bash b/e2e/tests-dfx/create.bash index 58f54097c9..881bee09fa 100644 --- a/e2e/tests-dfx/create.bash +++ b/e2e/tests-dfx/create.bash @@ -397,3 +397,40 @@ teardown() { assert_contains "${ALICE_PRINCIPAL}" assert_contains "${BOB_PRINCIPAL}" } + +# The following function decodes a canister id in the textual form into its binary form +# and is taken from the [IC Interface Specification](https://internetcomputer.org/docs/current/references/ic-interface-spec#principal). +function textual_decode() { + echo -n "$1" | tr -d - | tr "[:lower:]" "[:upper:]" | + fold -w 8 | xargs -n1 printf '%-8s' | tr ' ' = | + base32 -d | xxd -p | tr -d '\n' | cut -b9- | tr "[:lower:]" "[:upper:]" +} + +@test "create targets application subnet in PocketIC" { + [[ ! "$USE_POCKETIC" ]] && skip "skipped for replica: no support for multiple subnets" + dfx_start + # create the backend canister without a wallet canister so that the backend canister is the only canister ever created + assert_command dfx canister create e2e_project_backend --no-wallet + # actual canister id + CANISTER_ID="$(dfx canister id e2e_project_backend)" + # base64 encode the actual canister id + CANISTER_ID_BASE64="$(textual_decode "${CANISTER_ID}" | xxd -r -p | base64)" + # fetch topology from PocketIC server + TOPOLOGY="$(curl "http://127.0.0.1:$(dfx info replica-port)/instances/0/read/topology")" + # find application subnet id in the topology + for subnet_id in $(echo "${TOPOLOGY}" | jq keys[]) + do + SUBNET_KIND="$(echo "$TOPOLOGY" | jq -r ".${subnet_id}.\"subnet_kind\"")" + if [ "${SUBNET_KIND}" == "Application" ] + then + # find the expected canister id as the beginning of the first canister range of the app subnet + EXPECTED_CANISTER_ID_BASE64="$(echo "$TOPOLOGY" | jq -r ".${subnet_id}.\"canister_ranges\"[0].\"start\".\"canister_id\"")" + fi + done + # check if the actual canister id matches the expected canister id + if [ "${CANISTER_ID_BASE64}" != "${EXPECTED_CANISTER_ID_BASE64}" ] + then + echo "Canister id ${CANISTER_ID_BASE64} does not match expected canister id ${EXPECTED_CANISTER_ID_BASE64}" + exit 1 + fi +} diff --git a/src/dfx-core/src/config/model/replica_config.rs b/src/dfx-core/src/config/model/replica_config.rs index 389e6c3c86..ed4164b56d 100644 --- a/src/dfx-core/src/config/model/replica_config.rs +++ b/src/dfx-core/src/config/model/replica_config.rs @@ -1,4 +1,5 @@ use crate::config::model::dfinity::{ReplicaLogLevel, ReplicaSubnetType}; +use candid::Principal; use serde::{Deserialize, Serialize}; use std::borrow::Cow; use std::default::Default; @@ -192,6 +193,7 @@ pub enum CachedReplicaConfig<'a> { #[derive(Serialize, Deserialize, Debug, PartialEq, Eq)] pub struct CachedConfig<'a> { pub replica_rev: String, + pub effective_canister_id: Option, #[serde(flatten)] pub config: CachedReplicaConfig<'a>, } @@ -200,23 +202,29 @@ impl<'a> CachedConfig<'a> { pub fn replica(config: &'a ReplicaConfig, replica_rev: String) -> Self { Self { replica_rev, + effective_canister_id: None, config: CachedReplicaConfig::Replica { config: Cow::Borrowed(config), }, } } - pub fn pocketic(config: &'a ReplicaConfig, replica_rev: String) -> Self { + pub fn pocketic( + config: &'a ReplicaConfig, + replica_rev: String, + effective_canister_id: Option, + ) -> Self { Self { replica_rev, + effective_canister_id, config: CachedReplicaConfig::PocketIc { config: Cow::Borrowed(config), }, } } - pub fn is_pocketic(&self) -> bool { - matches!(self.config, CachedReplicaConfig::PocketIc { .. }) - } pub fn can_share_state(&self, other: &Self) -> bool { self == other } + pub fn get_effective_canister_id(&self) -> Option { + self.effective_canister_id + } } diff --git a/src/dfx/src/actors/mod.rs b/src/dfx/src/actors/mod.rs index ee44edd16b..721f35a73f 100644 --- a/src/dfx/src/actors/mod.rs +++ b/src/dfx/src/actors/mod.rs @@ -203,6 +203,7 @@ pub fn start_pocketic_actor( let actor_config = pocketic::Config { pocketic_path, + effective_config_path: local_server_descriptor.effective_config_path(), replica_config, port: local_server_descriptor.replica.port, port_file: pocketic_port_path, diff --git a/src/dfx/src/actors/pocketic.rs b/src/dfx/src/actors/pocketic.rs index 150e6f860f..3e8b2a8d3c 100644 --- a/src/dfx/src/actors/pocketic.rs +++ b/src/dfx/src/actors/pocketic.rs @@ -4,14 +4,21 @@ use crate::actors::shutdown_controller::signals::outbound::Shutdown; use crate::actors::shutdown_controller::signals::ShutdownSubscribe; use crate::actors::shutdown_controller::ShutdownController; use crate::lib::error::{DfxError, DfxResult}; +#[cfg(unix)] +use crate::lib::info::replica_rev; use actix::{ Actor, ActorContext, ActorFutureExt, Addr, AsyncContext, Context, Handler, Recipient, ResponseActFuture, Running, WrapFuture, }; use anyhow::{anyhow, bail}; +#[cfg(unix)] use candid::Principal; use crossbeam::channel::{unbounded, Receiver, Sender}; +#[cfg(unix)] +use dfx_core::config::model::replica_config::CachedConfig; use dfx_core::config::model::replica_config::ReplicaConfig; +#[cfg(unix)] +use dfx_core::json::save_json_file; use slog::{debug, error, info, warn, Logger}; use std::ops::ControlFlow::{self, *}; use std::path::{Path, PathBuf}; @@ -30,13 +37,11 @@ pub mod signals { } } -pub const POCKETIC_EFFECTIVE_CANISTER_ID: Principal = - Principal::from_slice(&[0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, 0x00, 0x00, 0x01, 0x01]); - /// The configuration for the PocketIC actor. #[derive(Clone)] pub struct Config { pub pocketic_path: PathBuf, + pub effective_config_path: PathBuf, pub replica_config: ReplicaConfig, pub port: Option, pub port_file: PathBuf, @@ -257,7 +262,12 @@ fn pocketic_start_thread( } } }; - let instance = match initialize_pocketic(port, &config.replica_config, logger.clone()) { + let instance = match initialize_pocketic( + port, + &config.effective_config_path, + &config.replica_config, + logger.clone(), + ) { Err(e) => { error!(logger, "Failed to initialize PocketIC: {e:#}"); @@ -311,6 +321,7 @@ fn pocketic_start_thread( #[tokio::main(flavor = "current_thread")] async fn initialize_pocketic( port: u16, + effective_config_path: &Path, replica_config: &ReplicaConfig, logger: Logger, ) -> DfxResult { @@ -357,7 +368,33 @@ async fn initialize_pocketic( CreateInstanceResponse::Error { message } => { bail!("PocketIC init error: {message}"); } - CreateInstanceResponse::Created { instance_id, .. } => instance_id, + CreateInstanceResponse::Created { + instance_id, + topology, + } => { + let subnets = match replica_config.subnet_type { + ReplicaSubnetType::Application => topology.get_app_subnets(), + ReplicaSubnetType::System => topology.get_system_subnets(), + ReplicaSubnetType::VerifiedApplication => topology.get_verified_app_subnets(), + }; + if subnets.len() != 1 { + return Err(anyhow!("Internal error: PocketIC topology contains multiple subnets of the same subnet kind.")); + } + let subnet_id = subnets[0]; + let subnet_config = topology.0.get(&subnet_id).ok_or(anyhow!( + "Internal error: subnet id {} not found in PocketIC topology", + subnet_id + ))?; + let effective_canister_id = + Principal::from_slice(&subnet_config.canister_ranges[0].start.canister_id); + let effective_config = CachedConfig::pocketic( + replica_config, + replica_rev().into(), + Some(effective_canister_id), + ); + save_json_file(effective_config_path, &effective_config)?; + instance_id + } }; init_client .post(format!( @@ -387,7 +424,7 @@ async fn initialize_pocketic( } #[cfg(not(unix))] -fn initialize_pocketic(_: u16, _: &ReplicaConfig, _: Logger) -> DfxResult { +fn initialize_pocketic(_: u16, _: &Path, _: &ReplicaConfig, _: Logger) -> DfxResult { bail!("PocketIC not supported on this platform") } diff --git a/src/dfx/src/commands/start.rs b/src/dfx/src/commands/start.rs index b5f96855f5..4e556cbecd 100644 --- a/src/dfx/src/commands/start.rs +++ b/src/dfx/src/commands/start.rs @@ -323,7 +323,7 @@ pub fn exec( }; let effective_config = if pocketic { - CachedConfig::pocketic(&replica_config, replica_rev().into()) + CachedConfig::pocketic(&replica_config, replica_rev().into(), None) } else { CachedConfig::replica(&replica_config, replica_rev().into()) }; diff --git a/src/dfx/src/lib/environment.rs b/src/dfx/src/lib/environment.rs index 0433478eb4..5b9ecc4881 100644 --- a/src/dfx/src/lib/environment.rs +++ b/src/dfx/src/lib/environment.rs @@ -1,4 +1,3 @@ -use crate::actors::pocketic::POCKETIC_EFFECTIVE_CANISTER_ID; use crate::config::cache::DiskBasedCache; use crate::config::dfx_version; use crate::lib::error::DfxResult; @@ -298,8 +297,7 @@ impl<'a> AgentEnvironment<'a> { let url = network_descriptor.first_provider()?; let effective_canister_id = if let Some(d) = &network_descriptor.local_server_descriptor { d.effective_config()? - .is_some_and(|c| c.is_pocketic()) - .then_some(POCKETIC_EFFECTIVE_CANISTER_ID) + .and_then(|c| c.get_effective_canister_id()) } else { None };