Skip to content
Open
Show file tree
Hide file tree
Changes from 2 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
1,672 changes: 106 additions & 1,566 deletions Cargo.Bazel.json.lock

Large diffs are not rendered by default.

395 changes: 66 additions & 329 deletions Cargo.Bazel.toml.lock

Large diffs are not rendered by default.

445 changes: 90 additions & 355 deletions Cargo.lock

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -416,6 +416,7 @@ members = [
"rs/rust_canisters/xrc_mock",
"rs/sns/audit",
"rs/sns/cli",
"rs/sns/dfx-core-vendored",
"rs/sns/governance",
"rs/sns/governance/api",
"rs/sns/governance/api_helpers",
Expand Down Expand Up @@ -641,7 +642,6 @@ curve25519-dalek = { version = "4.1.3", features = [
der = { version = "0.7", default-features = false, features = ["derive"] }
derive-new = "0.7.0"
devicemapper = "0.34"
dfx-core = { version = "0.4.0" }
ed25519-dalek = { version = "2.2.0", features = [
"std",
"zeroize",
Expand Down
37 changes: 35 additions & 2 deletions bazel/rust.MODULE.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,20 @@ crate.spec(
package = "aide",
version = "^0.14.2",
)
crate.spec(
features = [
"std",
],
package = "aes-gcm",
version = "^0.10.3",
)
crate.spec(
features = [
"std",
],
package = "argon2",
version = "^0.4",
)
crate.spec(
package = "arbitrary",
version = "^1.3.2",
Expand Down Expand Up @@ -388,8 +402,12 @@ crate.spec(
version = "0.34",
)
crate.spec(
package = "dfx-core",
version = "^0.4.0",
package = "dialoguer",
version = "^0.11.0",
)
crate.spec(
package = "directories-next",
version = "^2.0.0",
)
crate.spec(
package = "dyn-clone",
Expand Down Expand Up @@ -898,6 +916,17 @@ crate.spec(
package = "k256",
version = "^0.13.4",
)
crate.spec(
features = [
"apple-native",
"windows-native",
"linux-native",
"sync-secret-service",
"vendored",
],
package = "keyring",
version = "^3",
)
crate.spec(
package = "lazy_static",
version = "^1.4.0",
Expand Down Expand Up @@ -1466,6 +1495,10 @@ crate.spec(
package = "secp256k1",
version = "^0.22",
)
crate.spec(
package = "security-framework",
version = "^3",
)
crate.spec(
features = [
"serde",
Expand Down
6 changes: 3 additions & 3 deletions rs/sns/cli/BUILD.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ rust_library(
"//rs/nns/constants",
"//rs/nns/governance/api",
"//rs/nns/sns-wasm",
"//rs/sns/dfx-core-vendored",
"//rs/sns/governance",
"//rs/sns/governance/api",
"//rs/sns/init",
Expand All @@ -39,7 +40,6 @@ rust_library(
"@crate_index//:base64",
"@crate_index//:candid",
"@crate_index//:clap",
"@crate_index//:dfx-core",
"@crate_index//:futures",
"@crate_index//:hex",
"@crate_index//:ic-agent",
Expand Down Expand Up @@ -78,6 +78,7 @@ rust_binary(
"//rs/nns/constants",
"//rs/nns/governance/api",
"//rs/nns/sns-wasm",
"//rs/sns/dfx-core-vendored",
"//rs/sns/governance",
"//rs/sns/governance/api",
"//rs/sns/init",
Expand All @@ -88,7 +89,6 @@ rust_binary(
"@crate_index//:base64",
"@crate_index//:candid",
"@crate_index//:clap",
"@crate_index//:dfx-core",
"@crate_index//:futures",
"@crate_index//:hex",
"@crate_index//:ic-agent",
Expand Down Expand Up @@ -144,6 +144,7 @@ rust_test(
"//rs/nns/constants",
"//rs/nns/governance/api",
"//rs/nns/sns-wasm",
"//rs/sns/dfx-core-vendored",
"//rs/sns/governance",
"//rs/sns/governance/api",
"//rs/sns/init",
Expand All @@ -154,7 +155,6 @@ rust_test(
"@crate_index//:base64",
"@crate_index//:candid",
"@crate_index//:clap",
"@crate_index//:dfx-core",
"@crate_index//:futures",
"@crate_index//:hex",
"@crate_index//:ic-agent",
Expand Down
2 changes: 1 addition & 1 deletion rs/sns/cli/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ base64 = { workspace = true }
candid = { workspace = true }
candid-utils = { path = "../../nervous_system/candid_utils" }
clap = { workspace = true }
dfx-core = { workspace = true }
dfx-core-vendored = { path = "../dfx-core-vendored" }
futures = { workspace = true }
hex = { workspace = true }
ic-agent = { workspace = true }
Expand Down
39 changes: 11 additions & 28 deletions rs/sns/cli/src/utils.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
use anyhow::Context;
use anyhow::Result;
use dfx_core::interface::{builder::IdentityPicker, dfx::DfxInterface};
use futures::{StreamExt, stream};
use ic_agent::Agent;
use ic_nervous_system_agent::sns::Sns;
Expand All @@ -9,35 +8,19 @@ use serde::{Deserialize, Serialize};

/// Gets an agent for a given network and identity. This is similar to the code DFX uses internally to get an agent.
/// If no identity is provided, it will use the identity currently selected in the DFX CLI.
///
/// Network and identity resolution is handled by the in-tree `dfx-core-vendored`
/// crate (a trimmed vendoring of `dfx-core`), so this behaves like dfx for the
/// supported networks (`ic`, `local`, and IC HTTP endpoint URLs) and identity
/// storage modes (plaintext/encrypted PEM, keyring, and HSM).
pub async fn get_agent(network_name: &str, identity: Option<String>) -> Result<Agent> {
let interface = dfx_interface(network_name, identity)
dfx_core_vendored::get_agent(network_name, identity.clone())
.await
.context("Failed to get dfx interface")?;
Ok(interface.agent().clone())
}

/// Gets a dfx interface for a given network and identity. This is similar to the code DFX uses internally to build the interface.
/// So this function allows the DFX SNS Extension to use the same interface as DFX itself.
/// If no identity is provided, it will use the identity currently selected in the DFX CLI.
pub async fn dfx_interface(network_name: &str, identity: Option<String>) -> Result<DfxInterface> {
let interface_builder = {
let identity = identity
.clone()
.map(IdentityPicker::Named)
.unwrap_or(IdentityPicker::Selected);
DfxInterface::builder()
.with_identity(identity)
.with_network_named(network_name)
};
let interface = interface_builder.build().await.context(format!(
"Failed to build dfx interface with network `{network_name}` and identity `{identity:?}`"
))?;
if !interface.network_descriptor().is_ic {
interface.agent().fetch_root_key().await.context(format!(
"Failed to fetch root key from network `{network_name}`."
))?;
}
Ok(interface)
.with_context(|| {
format!(
"Failed to build agent for network `{network_name}` and identity `{identity:?}`"
)
})
}

#[derive(Debug, Serialize, Deserialize)]
Expand Down
33 changes: 33 additions & 0 deletions rs/sns/dfx-core-vendored/BUILD.bazel
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
load("@rules_rust//rust:defs.bzl", "rust_library")

package(default_visibility = ["//visibility:public"])

rust_library(
name = "dfx-core-vendored",
srcs = glob(["src/**/*.rs"]),
crate_name = "dfx_core_vendored",
version = "0.9.0",
deps = [
# Keep sorted.
"@crate_index//:aes-gcm",
"@crate_index//:argon2",
"@crate_index//:candid",
"@crate_index//:dialoguer",
"@crate_index//:directories-next",
"@crate_index//:hex",
"@crate_index//:ic-agent",
"@crate_index//:ic-identity-hsm",
"@crate_index//:keyring",
"@crate_index//:reqwest",
"@crate_index//:serde",
"@crate_index//:serde_json",
"@crate_index//:slog",
"@crate_index//:thiserror",
"@crate_index//:url",
] + select({
"@platforms//os:osx": [
"@crate_index//:security-framework",
],
"//conditions:default": [],
}),
)
36 changes: 36 additions & 0 deletions rs/sns/dfx-core-vendored/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
[package]
name = "dfx-core-vendored"
version.workspace = true
authors.workspace = true
edition.workspace = true
description.workspace = true
documentation.workspace = true

[lib]
path = "src/lib.rs"

[dependencies]
aes-gcm = { version = "0.10.3", features = ["std"] }
argon2 = { version = "0.4", features = ["std"] }
candid = { workspace = true }
dialoguer = { version = "0.11.0" }
directories-next = { version = "2.0.0" }
hex = { workspace = true }
ic-agent = { workspace = true }
ic-identity-hsm = { workspace = true }
keyring = { version = "3", features = [
"apple-native",
"windows-native",
"linux-native",
"sync-secret-service",
"vendored",
] }
reqwest = { workspace = true }
serde = { workspace = true }
serde_json = { workspace = true }
slog = { workspace = true }
thiserror = { workspace = true }
url = { workspace = true }

[target.'cfg(target_os = "macos")'.dependencies]
security-framework = { version = "3" }
68 changes: 68 additions & 0 deletions rs/sns/dfx-core-vendored/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
# `dfx-core-vendored`

A minimal, in-tree vendoring of the parts of the external
[`dfx-core`](https://crates.io/crates/dfx-core) crate that SNS tooling
(`ic-sns-cli`, `ic-sns-testing`) depends on: resolving a dfx identity + network
into an [`ic_agent::Agent`], and resolving a dfx identity name to its principal.

## Motivation

`dfx-core` re-exports `ic-agent` in its public API, so its **major version is
bumped in lockstep with `ic-agent`**. Depending on the published crate ties this
monorepo's `ic-agent` upgrades to the cadence of `dfx-core` releases: every time
we bump `ic-agent` to a new major, we would need a matching `dfx-core` release.

Once the [`dfinity/sdk`](https://github.com/dfinity/sdk) repository is archived,
those `dfx-core` releases can no longer be cut — which would block this monorepo
from ever upgrading `ic-agent` again. Vendoring the small slice we actually use
removes that coupling, while keeping behaviour identical for the supported
cases: dfx identities keep working (`~/.config/dfx/identity/...` is untouched),
and the SNS CLI behaves exactly as before. There is no user-facing change.

This crate was derived from **`dfx-core` `0.3.0`**, which depended on
`ic-agent 0.45` — the version used throughout this workspace at the time of
vendoring, so the initial copy compiled against the same API with no
adaptation. That version pairing was only the starting point, not a lasting
constraint: because the code now lives in-tree, it moves with this workspace's
`ic-agent` and is expected to be upgraded to future majors (`0.46`, `0.47`, …)
in place, alongside the rest of the monorepo, with no dependency on any further
`dfx-core` release.

## Maintenance expectation

**We do not expect to modify this crate going forward.** Its only consumers,
`ic-sns-cli` and `ic-sns-testing`, are in maintenance mode (bug fixes only), and
this crate exists solely to preserve their existing behaviour. It should be
treated as frozen vendored code:

- Do not add features or extend the surface to cover more of `dfx-core`. If a
broader dfx integration is ever needed, the intended path is an `icp sns`
subcommand on the `icp-cli` extension system, not growing this crate.
- The only expected changes are the mechanical adjustments required to keep it
compiling against future `ic-agent` majors (which is the entire reason it
exists), plus any bug fixes that also apply to `ic-sns-cli`.

## What is kept vs. dropped

Compared to `dfx-core 0.3.0`. Only identity **loading** and the `ic` / `local` /
URL network cases are retained; identity creation, wallets, extensions, and
general dfx project/network config parsing are not. The identity modules are
copied close to verbatim so they can be diffed against upstream; the network
resolution is a compact reimplementation.

| This crate | `dfx-core 0.3.0` origin | Difference |
| --- | --- | --- |
| `lib.rs` | `interface/builder.rs`, `interface/dfx.rs` | Rewritten. Replaces `DfxInterface`/`DfxInterfaceBuilder` (unused externally) with two functions, `get_agent` and `get_identity_principal`, plus private `build_identity`/`build_agent` that mirror `DfxInterfaceBuilder`'s non-anonymous identity and agent construction. |
| `network.rs` | `network/provider.rs`, `network/root_key.rs`, `config/model/{network_descriptor,local_server_descriptor,dfinity}.rs` | Rewritten. Compact resolver for `ic` (mainnet), `local` (shared or project, honouring a running replica's `webserver-port`, mirroring `LocalBindDetermination::ApplyRunningWebserverPort`), and an explicit IC HTTP endpoint URL. Playground, `networks.json`-defined named networks, and full `dfx.json` parsing (beyond the `local` bind) are dropped. `is_ic` and the default binds/gateways are kept identical to upstream. |
| `identity/mod.rs` | `identity/mod.rs` | Trimmed. Keeps `Identity` (anonymous / basic / secp256k1 / hardware / `new`), its `ic_agent::Identity` impl, and `IdentityType`. Drops all wallet handling (`WalletGlobalConfig`, load/save wallet config, `map_wallets_to_renamed_identity`), `CallSender`, and `display_linked_wallets`. |
| `identity/identity_manager.rs` | `identity/identity_manager.rs` | Trimmed to the **load** path: `new`, `instantiate_selected_identity`, `instantiate_identity_from_name`, `load_identity`, `require_identity_exists`, `get_identity_config_or_default`, config accessors. Drops identity creation (`create_new_identity`, `initialize`, key/mnemonic generation → also drops the `bip32`/`bip39`/`sec1`/`ring` deps), rename, remove, export, `use_identity_named`, `get_identity_names`, `IdentityCreationParameters`, `IdentityStorageMode`, and now-unused struct fields. `InitializeIdentity` is retained for API compatibility but `Allow` no longer auto-creates a default identity (dfx's job); SNS only ever passes `Disallow`. |
| `identity/pem_safekeeping.rs` | `identity/pem_safekeeping.rs` | Trimmed to the **load** path: `load_pem`, `load_pem_from_file`, `maybe_decrypt_pem`, `decrypt`, password prompt. Drops all saving/encrypting (`save_pem`, `write_pem_to_file`, `maybe_encrypt_pem`, `encrypt`). |
| `identity/keyring_mock.rs` | `identity/keyring_mock.rs` | Trimmed to the **load** path: `load_pem_from_keyring` and the `DFX_CI_MOCK_KEYRING_LOCATION` test hook. Drops `write_pem_to_keyring`, `delete_pem_from_keyring`, and `keyring_available`. |
| `identity/identity_file_locations.rs` | `identity/identity_file_locations.rs` | Verbatim, minus the unused `root()` accessor. |
| `config/directories.rs` | `config/directories.rs` | Trimmed. Keeps `project_dirs`, `get_shared_network_data_directory`, `get_user_dfx_config_dir`, and `DFX_CONFIG_ROOT`. Drops `get_shared_wallet_config_path` (wallets). |
| `foundation.rs` | `foundation/mod.rs` | Trimmed to `get_user_home` (drops `get_current_exe`). |
| `fs/mod.rs`, `fs/composite.rs` | `fs/mod.rs`, `fs/composite.rs` | Trimmed to the read-side helpers used by the load paths (`read`, `read_to_string`, `create_dir_all`, `ensure_dir_exists`). Drops archive extraction, all write/permission helpers, and `canonicalize` (→ also drops the `tar`/`flate2`/`dunce` deps). |
| `json.rs` | `json/mod.rs`, `json/structure.rs` | Trimmed to `load_json_file` (drops `save_json_file` and `structure.rs`). |
| `error/*.rs` | `error/*.rs` | Consolidated from 27 error modules to 7 (`config`, `encryption`, `fs`, `get_user_home`, `identity`, `keyring`, `structured_file`), keeping only the enums and variants reachable on the retained load/resolve paths. Messages are preserved for the retained variants. |

[`ic_agent::Agent`]: https://docs.rs/ic-agent/0.45/ic_agent/struct.Agent.html
55 changes: 55 additions & 0 deletions rs/sns/dfx-core-vendored/src/config/directories.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
//! Trimmed from `dfx_core::config::directories`: the user dfx config directory
//! and shared-network data directory resolution. Wallet-config path resolution
//! is not vendored.
use crate::error::config::ConfigError;
use crate::error::config::ConfigError::{
DetermineConfigDirectoryFailed, EnsureConfigDirectoryExistsFailed,
};
use crate::error::get_user_home::GetUserHomeError;
use crate::error::get_user_home::GetUserHomeError::NoHomeInEnvironment;
#[cfg(not(windows))]
use crate::foundation::get_user_home;
use crate::fs::composite::ensure_dir_exists;
use directories_next::ProjectDirs;
use std::ffi::OsString;
use std::path::PathBuf;
use std::sync::{LazyLock, Mutex};

pub fn project_dirs() -> Result<&'static ProjectDirs, GetUserHomeError> {
static DIRS: LazyLock<Option<ProjectDirs>> =
LazyLock::new(|| ProjectDirs::from("org", "dfinity", "dfx"));
DIRS.as_ref().ok_or(NoHomeInEnvironment())
}

pub fn get_shared_network_data_directory(network: &str) -> Result<PathBuf, GetUserHomeError> {
Ok(project_dirs()?
.data_local_dir()
.join("network")
.join(network))
}

pub fn get_user_dfx_config_dir() -> Result<PathBuf, ConfigError> {
let config_root = DFX_CONFIG_ROOT.lock().unwrap().clone();
// dirs-next is not used for *nix to preserve existing paths
#[cfg(not(windows))]
let p = {
let home = get_user_home().map_err(DetermineConfigDirectoryFailed)?;
let root = config_root.unwrap_or(home);
PathBuf::from(root).join(".config").join("dfx")
};
#[cfg(windows)]
let p = match config_root {
Some(var) => PathBuf::from(var),
None => project_dirs()
.map_err(DetermineConfigDirectoryFailed)?
.config_dir()
.to_owned(),
};
ensure_dir_exists(&p).map_err(EnsureConfigDirectoryExistsFailed)?;
Ok(p)
}

// tests want to be able to call set_var. set_var is unsafe. So, the env-var check is replaced
// with a global that the tests can modify.
pub(crate) static DFX_CONFIG_ROOT: LazyLock<Mutex<Option<OsString>>> =
LazyLock::new(|| Mutex::new(std::env::var_os("DFX_CONFIG_ROOT")));
1 change: 1 addition & 0 deletions rs/sns/dfx-core-vendored/src/config/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
pub mod directories;
Loading
Loading