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

feat: embedding well known canisters at build time #3872

Open
wants to merge 14 commits into
base: master
Choose a base branch
from
18 changes: 11 additions & 7 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,10 @@

# UNRELEASED

### feat: embedding well known canisters at build time

From this feature onwards, dfx doesn't require `canister_ids.json` to contain information about all the well known canisters on nns. The list of well known canister's can be found [here](https://github.com/dfinity/ic/blob/master/rs/nns/canister_ids.json). This means that `dfx canister call <well-known-canister-name>` will automatically map to the correct canister id.

### feat: Also report Motoko stable compatibility warnings

Report upgrade compatibility warnings for Motoko, such as deleted stable variables, in addition to compatibility errors.
Expand Down Expand Up @@ -702,7 +706,7 @@ For reference, these formats were removed (any '-' characters were replaced by '

### feat: add `dfx canister logs <canister_id>` for fetching canister's logs (preview)

There is a new subcommand `logs` to fetch canister's logs.
There is a new subcommand `logs` to fetch canister's logs.
When printing the log entries it tries to guess if the content can be converted to UTF-8 text and prints an array of hex bytes if it fails.

**Note**
Expand All @@ -718,7 +722,7 @@ The query parameter format is not removed because Safari does not support localh

### fix: .env files sometimes missing some canister ids

Made it so `dfx deploy` and `dfx canister install` will always write
Made it so `dfx deploy` and `dfx canister install` will always write
environment variables for all canisters in the project that have canister ids
to the .env file, even if they aren't being deployed/installed
or a dependency of a canister being deployed/installed.
Expand All @@ -728,15 +732,15 @@ or a dependency of a canister being deployed/installed.
There are a few subcommands that take `--argument`/`--argument-file` options to set canister call/init arguments.

We unify the related logic to provide consistent user experience.

The notable changes are:

- `dfx deploy` now accepts `--argument-file`.
- `dfx deps init` now accepts `--argument-file`.

### feat: candid assist feature

Ask for user input when Candid argument is not provided in `dfx canister call`, `dfx canister install` and `dfx deploy`.
Ask for user input when Candid argument is not provided in `dfx canister call`, `dfx canister install` and `dfx deploy`.
Previously, we cannot call `dfx deploy --all` when multiple canisters require init args, unless the init args are specified in `dfx.json`. With the Candid assist feature, dfx now asks for init args in terminal when a canister requires init args.

### fix: restored access to URLs like http://localhost:8080/api/v2/status through icx-proxy
Expand Down Expand Up @@ -885,7 +889,7 @@ If you build with custom canister type, add the following into `dfx.json`:

```
"metadata": [
{
{
"name": "candid:service"
}
]
Expand Down Expand Up @@ -920,7 +924,7 @@ Fix the bug that when parsing `vec \{1;2;3\}` with `blob` type, dfx silently ign
### fix: support `import` for local did file

If the local did file contains `import` or init args, dfx will rewrite the did file when storing in canister metadata.
Due to current limitations of the Candid parser, comments will be dropped during rewriting.
Due to current limitations of the Candid parser, comments will be dropped during rewriting.
If the local did file doesn't contain `import` or init args, we will not perform the rewriting, thus preserving the comments.

### fix: subtyping check reports the special opt rule as error
Expand Down Expand Up @@ -1309,7 +1313,7 @@ This incorporates the following executed proposals:
- [124537](https://dashboard.internetcomputer.org/proposal/124537)
- [124488](https://dashboard.internetcomputer.org/proposal/124488)
- [124487](https://dashboard.internetcomputer.org/proposal/124487)

# 0.15.0

## DFX
Expand Down
1 change: 1 addition & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

14 changes: 14 additions & 0 deletions e2e/tests-dfx/call.bash
Original file line number Diff line number Diff line change
Expand Up @@ -297,3 +297,17 @@ teardown() {
assert_command dfx canister call inter2_mo read
assert_match '(8 : nat)'
}

@test "call well known canisters" {
assert_command dfx canister --ic call governance list_proposals '(
record {
include_reward_status = vec {};
omit_large_fields = null;
before_proposal = null;
limit = 1 : nat32;
exclude_topic = vec {};
include_all_manage_neuron_proposals = null;
include_status = vec {};
},
)'
}
9 changes: 9 additions & 0 deletions e2e/tests-dfx/sign_send.bash
Original file line number Diff line number Diff line change
Expand Up @@ -80,3 +80,12 @@ teardown() {

rm "$TMP_NAME_FILE"
}

@test "sign query message for a well known canister" {
cd "$E2E_TEMP_DIR"
mkdir not-a-project-dir
cd not-a-project-dir

assert_command dfx canister sign --query registry read --network ic
assert_match "Query message generated at \[message.json\]"
}
5 changes: 5 additions & 0 deletions src/dfx-core/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,11 @@ edition.workspace = true
repository.workspace = true
license.workspace = true
rust-version.workspace = true
build = "assets/build.rs"

[build-dependencies]
serde_json.workspace = true
itertools.workspace = true

[dependencies]
aes-gcm.workspace = true
Expand Down
53 changes: 53 additions & 0 deletions src/dfx-core/assets/build.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
use std::{env, fs::File, io::Write, path::Path};

use itertools::Itertools;
use serde_json::Value;

fn define_well_known_canisters() {
let well_known_canisters = serde_json::from_str::<Value>(
&std::fs::read_to_string(format!(
"{}/assets/canister_ids.json",
env!("CARGO_MANIFEST_DIR")
))
.unwrap(),
)
.unwrap();
let well_known_canisters = well_known_canisters.as_object().unwrap();
let well_known_canisters = well_known_canisters.iter().map(|(key, val)| {
(
key.as_str(),
val.as_object()
.unwrap()
.get("mainnet")
.unwrap()
.as_str()
.unwrap(),
)
});

let out_dir = env::var("OUT_DIR").unwrap();
let loader_path = Path::new(&out_dir).join("well_known_canisters.rs");
let mut f = File::create(loader_path).unwrap();
f.write_all(
format!(
"
const WELL_KNOWN_CANISTERS: &[(&str, &str)] = &[
{}
];

pub fn map_wellknown_canisters() -> HashMap<CanisterName, Principal> {{
WELL_KNOWN_CANISTERS.iter().map(|(key, value)| (key.to_string(), (*value).try_into().unwrap())).collect()
}}
",
well_known_canisters
.map(|(key, val)| format!("(\"{}\", \"{}\")", key, val))
.join(",\n")
)
.as_bytes(),
)
.unwrap()
Comment on lines +34 to +48

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Since this is a global mapping of well-known canister ids, could we initialize this into a LazyLock rather than adding it as a member to CanisterIdStore?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The version of rust used by sdk doesn't have LazyLock stabilized.

}

fn main() {
define_well_known_canisters();
}
62 changes: 62 additions & 0 deletions src/dfx-core/assets/canister_ids.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
{
"registry": {
"local": "rwlgt-iiaaa-aaaaa-aaaaa-cai",
"mainnet": "rwlgt-iiaaa-aaaaa-aaaaa-cai",
"small01": "rwlgt-iiaaa-aaaaa-aaaaa-cai"
},
"governance": {
"local": "rrkah-fqaaa-aaaaa-aaaaq-cai",
"mainnet": "rrkah-fqaaa-aaaaa-aaaaq-cai",
"small01": "rrkah-fqaaa-aaaaa-aaaaq-cai"
},
"ledger": {
"local": "ryjl3-tyaaa-aaaaa-aaaba-cai",
"mainnet": "ryjl3-tyaaa-aaaaa-aaaba-cai",
"small01": "ryjl3-tyaaa-aaaaa-aaaba-cai"
},
"icp-ledger-archive": {
"local": "qjdve-lqaaa-aaaaa-aaaeq-cai",
"mainnet": "qjdve-lqaaa-aaaaa-aaaeq-cai",
"small01": "qjdve-lqaaa-aaaaa-aaaeq-cai"
},
"icp-ledger-archive-1": {
"local": "qsgjb-riaaa-aaaaa-aaaga-cai",
"mainnet": "qsgjb-riaaa-aaaaa-aaaga-cai",
"small01": "qsgjb-riaaa-aaaaa-aaaga-cai"
},
"root": {
"local": "r7inp-6aaaa-aaaaa-aaabq-cai",
"mainnet": "r7inp-6aaaa-aaaaa-aaabq-cai",
"small01": "r7inp-6aaaa-aaaaa-aaabq-cai"
},
"cycles-minting": {
"local": "rkp4c-7iaaa-aaaaa-aaaca-cai",
"mainnet": "rkp4c-7iaaa-aaaaa-aaaca-cai",
"small01": "rkp4c-7iaaa-aaaaa-aaaca-cai"
},
"lifeline": {
"local": "rno2w-sqaaa-aaaaa-aaacq-cai",
"mainnet": "rno2w-sqaaa-aaaaa-aaacq-cai",
"small01": "rno2w-sqaaa-aaaaa-aaacq-cai"
},
"genesis-token": {
"local": "renrk-eyaaa-aaaaa-aaada-cai",
"mainnet": "renrk-eyaaa-aaaaa-aaada-cai",
"small01": "renrk-eyaaa-aaaaa-aaada-cai"
},
"sns-wasm": {
"local": "qaa6y-5yaaa-aaaaa-aaafa-cai",
"mainnet": "qaa6y-5yaaa-aaaaa-aaafa-cai",
"small01": "qaa6y-5yaaa-aaaaa-aaafa-cai"
},
"identity": {
"local": "rdmx6-jaaaa-aaaaa-aaadq-cai",
"mainnet": "rdmx6-jaaaa-aaaaa-aaadq-cai",
"small01": "rdmx6-jaaaa-aaaaa-aaadq-cai"
},
"nns-ui": {
"local": "qoctq-giaaa-aaaaa-aaaea-cai",
"mainnet": "qoctq-giaaa-aaaaa-aaaea-cai",
"small01": "qoctq-giaaa-aaaaa-aaaea-cai"
}
}
23 changes: 22 additions & 1 deletion src/dfx-core/src/config/model/canister_id_store.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,14 +9,17 @@ use candid::Principal as CanisterId;
use ic_agent::export::Principal;
use serde::{Deserialize, Serialize, Serializer};
use slog::{warn, Logger};
use std::collections::BTreeMap;
use std::collections::{BTreeMap, HashMap};
use std::ops::{Deref, DerefMut, Sub};
use std::path::PathBuf;
use std::str::FromStr;
use std::sync::Arc;
use std::time::{Duration, SystemTime};
use time::format_description::well_known::Rfc3339;
use time::OffsetDateTime;

include!(concat!(env!("OUT_DIR"), "/well_known_canisters.rs"));

pub type CanisterName = String;
pub type NetworkName = String;
pub type CanisterIdString = String;
Expand Down Expand Up @@ -94,6 +97,8 @@ pub struct CanisterIdStore {
// which does not include remote canister ids
ids: CanisterIds,

well_known_ids: HashMap<CanisterName, Principal>,

// Only canisters that will time out at some point have their timestamp of acquisition saved
acquisition_timestamps: CanisterTimestamps,

Expand Down Expand Up @@ -166,6 +171,7 @@ impl CanisterIdStore {
acquisition_timestamps,
remote_ids,
pull_ids,
well_known_ids: map_wellknown_canisters(),
};

if let NetworkTypeDescriptor::Playground {
Expand All @@ -191,6 +197,16 @@ impl CanisterIdStore {
.and_then(|remote_ids| self.get_name_in(canister_id, remote_ids))
.or_else(|| self.get_name_in_project(canister_id))
.or_else(|| self.get_name_in_pull_ids(canister_id))
.or_else(|| {
let principal = match Principal::from_str(canister_id) {
Ok(p) => p,
Err(_) => return None,
};
self.well_known_ids
.iter()
.find(|(_, id)| &&principal == id)
.map(|(name, _)| name)
})
NikolaMilosa marked this conversation as resolved.
Show resolved Hide resolved
}

pub fn get_name_in_project(&self, canister_id: &str) -> Option<&String> {
Expand Down Expand Up @@ -247,6 +263,7 @@ impl CanisterIdStore {
.and_then(|remote_ids| self.find_in(canister_name, remote_ids))
.or_else(|| self.find_in(canister_name, &self.ids))
.or_else(|| self.pull_ids.get(canister_name).copied())
.or_else(|| self.well_known_ids.get(canister_name).copied())
}
pub fn get_name_id_map(&self) -> BTreeMap<String, String> {
let mut ids: BTreeMap<_, _> = self
Expand Down Expand Up @@ -293,6 +310,10 @@ impl CanisterIdStore {
.and_then(|s| CanisterId::from_text(s).ok())
}

pub fn is_well_known(&self, canister_id: &CanisterId) -> bool {
self.well_known_ids.values().any(|val| val == canister_id)
}

pub fn get(&self, canister_name: &str) -> Result<CanisterId, CanisterIdStoreError> {
self.find(canister_name).ok_or_else(|| {
let network = if self.network_descriptor.name == "local" {
Expand Down
18 changes: 15 additions & 3 deletions src/dfx/src/commands/canister/sign.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,10 @@ use crate::commands::canister::call::get_effective_canister_id;
use crate::lib::environment::Environment;
use crate::lib::error::DfxResult;
use crate::lib::operations::canister::get_canister_id_and_candid_path;
use crate::lib::root_key::fetch_root_key_if_needed;
use crate::lib::sign::signed_message::SignedMessageV1;
use crate::util::clap::argument_from_cli::ArgumentFromCliPositionalOpt;
use crate::util::{blob_from_arguments, get_candid_type};
use crate::util::{blob_from_arguments, fetch_remote_did_file, get_candid_type};
use anyhow::{anyhow, bail};
use candid::Principal;
use candid_parser::utils::CandidSource;
Expand Down Expand Up @@ -81,8 +82,19 @@ pub async fn exec(
let (canister_id, maybe_candid_path) =
get_canister_id_and_candid_path(env, opts.canister_name.as_str())?;

let method_type =
maybe_candid_path.and_then(|path| get_candid_type(CandidSource::File(&path), method_name));
let method_type = match maybe_candid_path
.and_then(|path| get_candid_type(CandidSource::File(&path), method_name))
{
Some(mt) => Some(mt),
None => {
let agent = env.get_agent();
fetch_root_key_if_needed(env).await?;
fetch_remote_did_file(agent, canister_id)
.await
.and_then(|did| get_candid_type(CandidSource::Text(&did), method_name))
}
};

let is_query_method = method_type.as_ref().map(|(_, f)| f.is_query());

let (argument_from_cli, argument_type) = opts.argument_from_cli.get_argument_and_type()?;
Expand Down
10 changes: 9 additions & 1 deletion src/dfx/src/lib/operations/canister/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -357,13 +357,21 @@ pub fn get_canister_id_and_candid_path(
) -> DfxResult<(CanisterId, Option<PathBuf>)> {
let canister_id_store = env.get_canister_id_store()?;
let (canister_name, canister_id) = if let Ok(id) = Principal::from_text(canister) {
if canister_id_store.is_well_known(&id) {
return Ok((id, None));
}

if let Some(canister_name) = canister_id_store.get_name(canister) {
(canister_name.to_string(), id)
} else {
return Ok((id, None));
}
} else {
(canister.to_string(), canister_id_store.get(canister)?)
let canister_id = canister_id_store.get(canister)?;
if canister_id_store.is_well_known(&canister_id) {
return Ok((canister_id, None));
}
(canister.to_string(), canister_id)
};
let config = env.get_config_or_anyhow()?;
let candid_path = match CanisterInfo::load(&config, &canister_name, Some(canister_id)) {
Expand Down
Loading