Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix: fetch effective canister id from PocketIC topology #3942

Merged
merged 6 commits into from
Oct 7, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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 "[: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
}
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
49 changes: 43 additions & 6 deletions src/dfx/src/actors/pocketic.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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};
Expand All @@ -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<u16>,
pub port_file: PathBuf,
Expand Down Expand Up @@ -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:#}");

Expand Down Expand Up @@ -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<usize> {
Expand Down Expand Up @@ -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!(
Expand Down Expand Up @@ -387,7 +424,7 @@ async fn initialize_pocketic(
}

#[cfg(not(unix))]
fn initialize_pocketic(_: u16, _: &ReplicaConfig, _: Logger) -> DfxResult<usize> {
fn initialize_pocketic(_: u16, _: &Path, _: &ReplicaConfig, _: Logger) -> DfxResult<usize> {
bail!("PocketIC not supported on this platform")
}

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
Loading