Skip to content

Commit

Permalink
fix: fetch effective canister id from PocketIC topology
Browse files Browse the repository at this point in the history
  • Loading branch information
mraszyk committed Oct 7, 2024
1 parent ab9b342 commit d6e504e
Show file tree
Hide file tree
Showing 6 changed files with 90 additions and 14 deletions.
37 changes: 37 additions & 0 deletions e2e/tests-dfx/create.bash
Original file line number Diff line number Diff line change
Expand Up @@ -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 a-z A-Z |
fold -w 8 | xargs -n1 printf '%-8s' | tr ' ' = |
base32 -d | xxd -p | tr -d '\n' | cut -b9- | tr a-z A-Z
}

@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
}
16 changes: 12 additions & 4 deletions src/dfx-core/src/config/model/replica_config.rs
Original file line number Diff line number Diff line change
@@ -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;
Expand Down Expand Up @@ -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<Principal>,
#[serde(flatten)]
pub config: CachedReplicaConfig<'a>,
}
Expand All @@ -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<Principal>,
) -> 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<Principal> {
self.effective_canister_id
}
}
1 change: 1 addition & 0 deletions src/dfx/src/actors/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
44 changes: 38 additions & 6 deletions src/dfx/src/actors/pocketic.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,16 @@ 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};
use crate::lib::info::replica_rev;
use actix::{
Actor, ActorContext, ActorFutureExt, Addr, AsyncContext, Context, Handler, Recipient,
ResponseActFuture, Running, WrapFuture,
};
use anyhow::{anyhow, bail};
use candid::Principal;
use crossbeam::channel::{unbounded, Receiver, Sender};
use dfx_core::config::model::replica_config::ReplicaConfig;
use dfx_core::config::model::replica_config::{CachedConfig, ReplicaConfig};
use dfx_core::json::save_json_file;
use slog::{debug, error, info, warn, Logger};
use std::ops::ControlFlow::{self, *};
use std::path::{Path, PathBuf};
Expand All @@ -30,13 +32,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<u16>,
pub port_file: PathBuf,
Expand Down Expand Up @@ -257,7 +257,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:#}");

Expand Down Expand Up @@ -311,6 +316,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<usize> {
Expand Down Expand Up @@ -357,7 +363,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!(
Expand Down
2 changes: 1 addition & 1 deletion src/dfx/src/commands/start.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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())
};
Expand Down
4 changes: 1 addition & 3 deletions src/dfx/src/lib/environment.rs
Original file line number Diff line number Diff line change
@@ -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;
Expand Down Expand Up @@ -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
};
Expand Down

0 comments on commit d6e504e

Please sign in to comment.