From 0ce5097bb401f019c8db77fdaec220a68ab928ee Mon Sep 17 00:00:00 2001 From: Martin Raszyk Date: Mon, 25 Nov 2024 10:36:26 +0100 Subject: [PATCH] feat: impersonating sender of requests to a local PocketIC instance --- CHANGELOG.md | 6 + Cargo.lock | 176 ++++++++++-------- nix/sources.json | 12 +- src/dfx-core/src/canister/mod.rs | 3 + .../config/model/local_server_descriptor.rs | 33 +++- src/dfx-core/src/identity/mod.rs | 1 + src/dfx/Cargo.toml | 2 +- src/dfx/assets/dfx-asset-sources.toml | 8 +- src/dfx/src/commands/canister/call.rs | 80 +++++++- src/dfx/src/commands/canister/delete.rs | 6 + .../src/commands/canister/deposit_cycles.rs | 8 + src/dfx/src/commands/canister/status.rs | 12 +- .../src/commands/canister/update_settings.rs | 14 +- src/dfx/src/lib/environment.rs | 43 +++++ .../operations/canister/create_canister.rs | 10 + src/dfx/src/lib/operations/canister/mod.rs | 35 +++- 16 files changed, 347 insertions(+), 102 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 8ecc8f39df..5d2751e18d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,12 @@ # UNRELEASED +### feat: impersonating sender of requests to a local PocketIC instance + +`dfx canister call`, `dfx canister status`, and `dfx canister update-settings` take +an additional CLI argument `--impersonate` to specify a principal +on behalf of which requests to a local PocketIC instance are sent. + ### feat: `dfx start --pocketic` supports `--force` and shared networks. `dfx start --pocketic` is now compatible with `--force` and shared networks. diff --git a/Cargo.lock b/Cargo.lock index 8acb851d04..e67066e442 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -65,7 +65,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e01ed3140b2f8d422c68afa1ed2e85d996ea619c988ac834d255db32138655cb" dependencies = [ "quote", - "syn 2.0.61", + "syn 2.0.89", ] [[package]] @@ -86,7 +86,7 @@ checksum = "7c7db3d5a9718568e4cf4a537cfd7070e6e6ff7481510d0237fb529ac850f6d3" dependencies = [ "proc-macro2", "quote", - "syn 2.0.61", + "syn 2.0.89", ] [[package]] @@ -294,7 +294,7 @@ name = "apply-patch" version = "0.1.0" dependencies = [ "patch", - "thiserror", + "thiserror 1.0.64", ] [[package]] @@ -399,7 +399,7 @@ checksum = "c6fa2087f2753a7da8cc1c0dbfcf89579dd57458e36769de5ac750b4671737ca" dependencies = [ "proc-macro2", "quote", - "syn 2.0.61", + "syn 2.0.89", ] [[package]] @@ -637,7 +637,7 @@ dependencies = [ "proc-macro-crate 3.1.0", "proc-macro2", "quote", - "syn 2.0.61", + "syn 2.0.89", "syn_derive", ] @@ -746,7 +746,7 @@ dependencies = [ "hashbrown 0.12.3", "instant", "once_cell", - "thiserror", + "thiserror 1.0.64", ] [[package]] @@ -761,7 +761,7 @@ dependencies = [ "hashbrown 0.14.5", "instant", "once_cell", - "thiserror", + "thiserror 1.0.64", ] [[package]] @@ -774,7 +774,7 @@ dependencies = [ "hashbrown 0.14.5", "instant", "once_cell", - "thiserror", + "thiserror 1.0.64", ] [[package]] @@ -824,7 +824,7 @@ dependencies = [ "serde", "serde_bytes", "stacker", - "thiserror", + "thiserror 1.0.64", ] [[package]] @@ -836,7 +836,7 @@ dependencies = [ "lazy_static", "proc-macro2", "quote", - "syn 2.0.61", + "syn 2.0.89", ] [[package]] @@ -864,7 +864,7 @@ dependencies = [ "rand", "serde", "serde_dhall", - "thiserror", + "thiserror 1.0.64", ] [[package]] @@ -887,7 +887,7 @@ dependencies = [ "semver", "serde", "serde_json", - "thiserror", + "thiserror 1.0.64", ] [[package]] @@ -997,7 +997,7 @@ dependencies = [ "heck 0.5.0", "proc-macro2", "quote", - "syn 2.0.61", + "syn 2.0.89", ] [[package]] @@ -1345,7 +1345,7 @@ dependencies = [ "proc-macro2", "quote", "scratch", - "syn 2.0.61", + "syn 2.0.89", ] [[package]] @@ -1362,7 +1362,7 @@ checksum = "928bc249a7e3cd554fd2e8e08a426e9670c50bbfc9a621653cfa9accc9641783" dependencies = [ "proc-macro2", "quote", - "syn 2.0.61", + "syn 2.0.89", ] [[package]] @@ -1595,7 +1595,7 @@ dependencies = [ "tar", "tempfile", "term", - "thiserror", + "thiserror 1.0.64", "time", "tokio", "toml 0.7.8", @@ -1644,7 +1644,7 @@ dependencies = [ "slog", "tar", "tempfile", - "thiserror", + "thiserror 1.0.64", "time", "tiny-bip39", "tokio", @@ -1697,7 +1697,7 @@ dependencies = [ "fuzzy-matcher", "shell-words", "tempfile", - "thiserror", + "thiserror 1.0.64", "zeroize", ] @@ -1814,7 +1814,7 @@ dependencies = [ "rand_core", "serde", "sha2 0.9.9", - "thiserror", + "thiserror 1.0.64", "zeroize", ] @@ -2073,7 +2073,7 @@ checksum = "2cd66269887534af4b0c3e3337404591daa8dc8b9b2b3db71f9523beb4bafb41" dependencies = [ "proc-macro2", "quote", - "syn 2.0.61", + "syn 2.0.89", ] [[package]] @@ -2188,7 +2188,7 @@ checksum = "87750cf4b7a4c0625b1529e4c543c2182106e4dedc60a2a6455e00d212c489ac" dependencies = [ "proc-macro2", "quote", - "syn 2.0.61", + "syn 2.0.89", ] [[package]] @@ -2371,7 +2371,7 @@ dependencies = [ "pest_derive", "serde", "serde_json", - "thiserror", + "thiserror 1.0.64", ] [[package]] @@ -2707,7 +2707,7 @@ dependencies = [ "serde_repr", "sha2 0.10.8", "simple_asn1", - "thiserror", + "thiserror 1.0.64", "time", "tokio", "url", @@ -2741,7 +2741,7 @@ dependencies = [ "sha2 0.10.8", "slog", "tempfile", - "thiserror", + "thiserror 1.0.64", "tokio", "walkdir", ] @@ -2800,7 +2800,7 @@ dependencies = [ "ic-certification 2.6.0", "leb128", "nom", - "thiserror", + "thiserror 1.0.64", ] [[package]] @@ -2846,7 +2846,7 @@ dependencies = [ "nom", "parking_lot 0.12.2", "sha2 0.10.8", - "thiserror", + "thiserror 1.0.64", ] [[package]] @@ -2891,7 +2891,7 @@ dependencies = [ "serde", "serde-wasm-bindgen", "serde_cbor", - "thiserror", + "thiserror 1.0.64", "wasm-bindgen", "wasm-bindgen-console-logger", ] @@ -3024,7 +3024,7 @@ dependencies = [ "serde_cbor", "strum 0.25.0", "strum_macros 0.25.3", - "thiserror", + "thiserror 1.0.64", "zeroize", ] @@ -3056,7 +3056,7 @@ dependencies = [ "ic-protobuf", "serde", "serde_bytes", - "thiserror", + "thiserror 1.0.64", ] [[package]] @@ -3090,7 +3090,7 @@ dependencies = [ "ic-certification 2.6.0", "ic-representation-independent-hash", "serde", - "thiserror", + "thiserror 1.0.64", "urlencoding", ] @@ -3124,7 +3124,7 @@ dependencies = [ "pkcs11", "sha2 0.10.8", "simple_asn1", - "thiserror", + "thiserror 1.0.64", ] [[package]] @@ -3172,7 +3172,7 @@ dependencies = [ "log", "nom", "sha2 0.10.8", - "thiserror", + "thiserror 1.0.64", "urlencoding", ] @@ -3228,7 +3228,7 @@ dependencies = [ "serde_bytes", "serde_repr", "sha2 0.10.8", - "thiserror", + "thiserror 1.0.64", ] [[package]] @@ -3245,7 +3245,7 @@ dependencies = [ "serde_bytes", "serde_repr", "sha2 0.10.8", - "thiserror", + "thiserror 1.0.64", ] [[package]] @@ -3280,7 +3280,7 @@ dependencies = [ "serde_with", "strum 0.25.0", "strum_macros 0.25.3", - "thiserror", + "thiserror 1.0.64", "thousands", ] @@ -3298,7 +3298,7 @@ dependencies = [ "rand", "scoped_threadpool", "serde", - "thiserror", + "thiserror 1.0.64", ] [[package]] @@ -3318,7 +3318,7 @@ dependencies = [ "sha2 0.10.8", "strum 0.26.3", "strum_macros 0.26.4", - "thiserror", + "thiserror 1.0.64", "time", "tokio", ] @@ -3351,7 +3351,7 @@ dependencies = [ "serde", "serde_json", "tempfile", - "thiserror", + "thiserror 1.0.64", "walrus", "wasm-opt", ] @@ -3402,7 +3402,7 @@ dependencies = [ "data-encoding", "serde", "sha2 0.10.8", - "thiserror", + "thiserror 1.0.64", ] [[package]] @@ -3444,7 +3444,7 @@ dependencies = [ "slog", "slog-async", "slog-term", - "thiserror", + "thiserror 1.0.64", "time", "tokio", "walkdir", @@ -3643,7 +3643,7 @@ checksum = "ec9ad60d674508f3ca8f380a928cfe7b096bc729c4e2dbfe3852bc45da3ab30b" dependencies = [ "serde", "serde_json", - "thiserror", + "thiserror 1.0.64", ] [[package]] @@ -3901,7 +3901,7 @@ dependencies = [ "proc-macro2", "quote", "regex-syntax 0.6.29", - "syn 2.0.61", + "syn 2.0.89", ] [[package]] @@ -4423,7 +4423,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "560131c633294438da9f7c4b08189194b20946c8274c6b9e38881a7874dc8ee8" dependencies = [ "memchr", - "thiserror", + "thiserror 1.0.64", "ucd-trie", ] @@ -4469,7 +4469,7 @@ dependencies = [ "pest_meta", "proc-macro2", "quote", - "syn 2.0.61", + "syn 2.0.89", ] [[package]] @@ -4535,7 +4535,7 @@ checksum = "2f38a4412a78282e09a2cf38d195ea5420d15ba0602cb375210efbc877243965" dependencies = [ "proc-macro2", "quote", - "syn 2.0.61", + "syn 2.0.89", ] [[package]] @@ -4582,8 +4582,8 @@ dependencies = [ [[package]] name = "pocket-ic" -version = "5.0.0" -source = "git+https://github.com/dfinity/ic?rev=a62848817cec7ae50618a87a526c85d020283fd9#a62848817cec7ae50618a87a526c85d020283fd9" +version = "6.0.0" +source = "git+https://github.com/dfinity/ic?rev=ab625a1b5498d6c5f44c918a419afb6ff3f92fb6#ab625a1b5498d6c5f44c918a419afb6ff3f92fb6" dependencies = [ "base64 0.13.1", "candid", @@ -4600,7 +4600,7 @@ dependencies = [ "slog", "strum 0.26.3", "strum_macros 0.26.4", - "thiserror", + "thiserror 2.0.3", "tokio", "tracing", "tracing-appender", @@ -4749,9 +4749,9 @@ dependencies = [ [[package]] name = "proc-macro2" -version = "1.0.82" +version = "1.0.92" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8ad3d49ab951a01fbaafe34f2ec74122942fe18a3f9814c3268f1bb72042131b" +checksum = "37d3544b3f2748c54e147655edb5025752e2303145b5aefb3c3ea2c78b973bb0" dependencies = [ "unicode-ident", ] @@ -4796,7 +4796,7 @@ dependencies = [ "itertools 0.12.1", "proc-macro2", "quote", - "syn 2.0.61", + "syn 2.0.89", ] [[package]] @@ -4847,7 +4847,7 @@ dependencies = [ "rustc-hash 2.0.0", "rustls 0.23.7", "socket2 0.5.7", - "thiserror", + "thiserror 1.0.64", "tokio", "tracing", ] @@ -4864,7 +4864,7 @@ dependencies = [ "rustc-hash 2.0.0", "rustls 0.23.7", "slab", - "thiserror", + "thiserror 1.0.64", "tinyvec", "tracing", ] @@ -4997,7 +4997,7 @@ checksum = "bd283d9651eeda4b2a83a43c1c91b266c40fd76ecd39a50a8c630ae69dc72891" dependencies = [ "getrandom", "libredox", - "thiserror", + "thiserror 1.0.64", ] [[package]] @@ -5409,7 +5409,7 @@ dependencies = [ "proc-macro2", "quote", "serde_derive_internals", - "syn 2.0.61", + "syn 2.0.89", ] [[package]] @@ -5579,7 +5579,7 @@ checksum = "e0cd7e117be63d3c3678776753929474f3b04a43a080c744d6b0ae2a8c28e222" dependencies = [ "proc-macro2", "quote", - "syn 2.0.61", + "syn 2.0.89", ] [[package]] @@ -5590,7 +5590,7 @@ checksum = "330f01ce65a3a5fe59a60c82f3c9a024b573b8a6e875bd233fe5f934e71d54e3" dependencies = [ "proc-macro2", "quote", - "syn 2.0.61", + "syn 2.0.89", ] [[package]] @@ -5625,7 +5625,7 @@ checksum = "6c64451ba24fc7a6a2d60fc75dd9c83c90903b19028d4eff35e88fc1e86564e9" dependencies = [ "proc-macro2", "quote", - "syn 2.0.61", + "syn 2.0.89", ] [[package]] @@ -5780,7 +5780,7 @@ checksum = "adc4e5204eb1910f40f9cfa375f6f05b68c3abac4b6fd879c8ff5e7ae8a0a085" dependencies = [ "num-bigint 0.4.5", "num-traits", - "thiserror", + "thiserror 1.0.64", "time", ] @@ -5988,7 +5988,7 @@ dependencies = [ "proc-macro2", "quote", "rustversion", - "syn 2.0.61", + "syn 2.0.89", ] [[package]] @@ -6001,7 +6001,7 @@ dependencies = [ "proc-macro2", "quote", "rustversion", - "syn 2.0.61", + "syn 2.0.89", ] [[package]] @@ -6039,9 +6039,9 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.61" +version = "2.0.89" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c993ed8ccba56ae856363b1845da7266a7cb78e1d146c8a32d54b45a8b831fc9" +checksum = "44d46482f1c1c87acd84dea20c1bf5ebff4c757009ed6bf19cfd36fb10e92c4e" dependencies = [ "proc-macro2", "quote", @@ -6057,7 +6057,7 @@ dependencies = [ "proc-macro-error", "proc-macro2", "quote", - "syn 2.0.61", + "syn 2.0.89", ] [[package]] @@ -6155,7 +6155,16 @@ version = "1.0.64" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d50af8abc119fb8bb6dbabcfa89656f46f84aa0ac7688088608076ad2b459a84" dependencies = [ - "thiserror-impl", + "thiserror-impl 1.0.64", +] + +[[package]] +name = "thiserror" +version = "2.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c006c85c7651b3cf2ada4584faa36773bd07bac24acfb39f3c431b36d7e667aa" +dependencies = [ + "thiserror-impl 2.0.3", ] [[package]] @@ -6166,7 +6175,18 @@ checksum = "08904e7672f5eb876eaaf87e0ce17857500934f4981c4a0ab2b4aa98baac7fc3" dependencies = [ "proc-macro2", "quote", - "syn 2.0.61", + "syn 2.0.89", +] + +[[package]] +name = "thiserror-impl" +version = "2.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f077553d607adc1caf65430528a576c757a71ed73944b66ebb58ef2bbd243568" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.89", ] [[package]] @@ -6231,7 +6251,7 @@ dependencies = [ "rand", "rustc-hash 1.1.0", "sha2 0.10.8", - "thiserror", + "thiserror 1.0.64", "unicode-normalization", "wasm-bindgen", "zeroize", @@ -6263,9 +6283,9 @@ checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" [[package]] name = "tokio" -version = "1.40.0" +version = "1.41.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e2b070231665d27ad9ec9b8df639893f46727666c6767db40317fbe920a5d998" +checksum = "22cfb5bee7a6a52939ca9224d6ac897bb669134078daa8735560897f69de4d33" dependencies = [ "backtrace", "bytes", @@ -6287,7 +6307,7 @@ checksum = "693d596312e88961bc67d7f1f97af8a70227d9f90c31bba5806eec004978d752" dependencies = [ "proc-macro2", "quote", - "syn 2.0.61", + "syn 2.0.89", ] [[package]] @@ -6319,7 +6339,7 @@ checksum = "0d4770b8024672c1101b3f6733eab95b18007dbe0847a8afe341fcf79e06043f" dependencies = [ "either", "futures-util", - "thiserror", + "thiserror 1.0.64", "tokio", ] @@ -6437,7 +6457,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3566e8ce28cc0a3fe42519fc80e6b4c943cc4c8cef275620eb8dac2d3d4e06cf" dependencies = [ "crossbeam-channel", - "thiserror", + "thiserror 1.0.64", "time", "tracing-subscriber", ] @@ -6450,7 +6470,7 @@ checksum = "34704c8d6ebcbc939824180af020566b01a7c01f80641264eba0999f6c2b6be7" dependencies = [ "proc-macro2", "quote", - "syn 2.0.61", + "syn 2.0.89", ] [[package]] @@ -6742,7 +6762,7 @@ dependencies = [ "once_cell", "proc-macro2", "quote", - "syn 2.0.61", + "syn 2.0.89", "wasm-bindgen-shared", ] @@ -6786,7 +6806,7 @@ checksum = "e94f17b526d0a461a191c78ea52bbce64071ed5c04c9ffe424dcb38f74171bb7" dependencies = [ "proc-macro2", "quote", - "syn 2.0.61", + "syn 2.0.89", "wasm-bindgen-backend", "wasm-bindgen-shared", ] @@ -6817,7 +6837,7 @@ dependencies = [ "strum 0.24.1", "strum_macros 0.24.3", "tempfile", - "thiserror", + "thiserror 1.0.64", "wasm-opt-cxx-sys", "wasm-opt-sys", ] @@ -7218,7 +7238,7 @@ checksum = "15e934569e47891f7d9411f1a451d947a60e000ab3bd24fbb970f000387d1b3b" dependencies = [ "proc-macro2", "quote", - "syn 2.0.61", + "syn 2.0.89", ] [[package]] @@ -7238,7 +7258,7 @@ checksum = "ce36e65b0d2999d2aafac989fb249189a141aee1f53c612c1f37d72631959f69" dependencies = [ "proc-macro2", "quote", - "syn 2.0.61", + "syn 2.0.89", ] [[package]] diff --git a/nix/sources.json b/nix/sources.json index 79f8cf4d8b..526d0c6669 100644 --- a/nix/sources.json +++ b/nix/sources.json @@ -153,17 +153,17 @@ "version": "0.13.3" }, "pocket-ic-x86_64-darwin": { - "rev": "a62848817cec7ae50618a87a526c85d020283fd9", - "sha256": "1xlsd1lkv22raw0z9n02li6ghg6vpb30hygr8s1g0fvchzhsnizx", + "rev": "ab625a1b5498d6c5f44c918a419afb6ff3f92fb6", + "sha256": "9a601c1a7ebb231739c55f83c9dd646210a9836066abded2582871ebce7bd9ac", "type": "file", - "url": "https://download.dfinity.systems/ic/a62848817cec7ae50618a87a526c85d020283fd9/binaries/x86_64-darwin/pocket-ic.gz", + "url": "https://download.dfinity.systems/ic/ab625a1b5498d6c5f44c918a419afb6ff3f92fb6/binaries/x86_64-darwin/pocket-ic.gz", "url_template": "https://download.dfinity.systems/ic//binaries/x86_64-darwin/pocket-ic.gz" }, "pocket-ic-x86_64-linux": { - "rev": "a62848817cec7ae50618a87a526c85d020283fd9", - "sha256": "16ch04yghl21pr8lk9mcdrvw1f7l8rbpbslh34bsn96xjshy453v", + "rev": "ab625a1b5498d6c5f44c918a419afb6ff3f92fb6", + "sha256": "6dfee7582c9282868f2fae3acddb98085bf748a5a48beea0bcd68b3fcb9fd12c", "type": "file", - "url": "https://download.dfinity.systems/ic/a62848817cec7ae50618a87a526c85d020283fd9/binaries/x86_64-linux/pocket-ic.gz", + "url": "https://download.dfinity.systems/ic/ab625a1b5498d6c5f44c918a419afb6ff3f92fb6/binaries/x86_64-linux/pocket-ic.gz", "url_template": "https://download.dfinity.systems/ic//binaries/x86_64-linux/pocket-ic.gz" }, "replica-x86_64-darwin": { diff --git a/src/dfx-core/src/canister/mod.rs b/src/dfx-core/src/canister/mod.rs index 5e386c338c..beb9ffe126 100644 --- a/src/dfx-core/src/canister/mod.rs +++ b/src/dfx-core/src/canister/mod.rs @@ -71,6 +71,9 @@ YOU WILL LOSE ALL DATA IN THE CANISTER. .await .map_err(CanisterInstallError::InstallWasmError) } + CallSender::Impersonate(_) => { + unreachable!("Impersonating sender is not supported for canister installation.") + } CallSender::Wallet(wallet_id) => { let wallet = build_wallet_canister(*wallet_id, agent).await?; let install_args = CanisterInstall { diff --git a/src/dfx-core/src/config/model/local_server_descriptor.rs b/src/dfx-core/src/config/model/local_server_descriptor.rs index e14afa4727..7b9dce6e41 100644 --- a/src/dfx-core/src/config/model/local_server_descriptor.rs +++ b/src/dfx-core/src/config/model/local_server_descriptor.rs @@ -368,7 +368,6 @@ impl LocalServerDescriptor { logger: Option<&Logger>, ) -> Result, NetworkConfigError> { let replica_port_path = self.replica_port_path(); - let pocketic_port_path = self.pocketic_port_path(); match read_port_from(&replica_port_path)? { Some(port) => { if let Some(logger) = logger { @@ -376,15 +375,31 @@ impl LocalServerDescriptor { } Ok(Some(port)) } - None => match read_port_from(&pocketic_port_path)? { - Some(port) => { - if let Some(logger) = logger { - info!(logger, "Found local PocketIC running on port {}", port); - } - Ok(Some(port)) + None => { + let port = self + .get_running_pocketic_port(logger)? + .or(self.replica.port); + Ok(port) + } + } + } + + /// Gets the port of a local PocketIC instance. + /// + /// # Prerequisites + /// - A local PocketIC instance needs to be running, e.g. with `dfx start --pocketic`. + pub fn get_running_pocketic_port( + &self, + logger: Option<&Logger>, + ) -> Result, NetworkConfigError> { + match read_port_from(&self.pocketic_port_path())? { + Some(port) => { + if let Some(logger) = logger { + info!(logger, "Found local PocketIC running on port {}", port); } - None => Ok(self.replica.port), - }, + Ok(Some(port)) + } + None => Ok(None), } } } diff --git a/src/dfx-core/src/identity/mod.rs b/src/dfx-core/src/identity/mod.rs index 8fa5732fc4..580353e1ba 100644 --- a/src/dfx-core/src/identity/mod.rs +++ b/src/dfx-core/src/identity/mod.rs @@ -307,6 +307,7 @@ impl AsRef for Identity { #[derive(Debug, PartialEq, Eq, Copy, Clone)] pub enum CallSender { SelectedId, + Impersonate(Principal), Wallet(Principal), } diff --git a/src/dfx/Cargo.toml b/src/dfx/Cargo.toml index 34a0f5480b..06c0057d04 100644 --- a/src/dfx/Cargo.toml +++ b/src/dfx/Cargo.toml @@ -126,7 +126,7 @@ ci_info = "0.14" junction = "1.0.0" [target.'cfg(unix)'.dependencies] -pocket-ic = { git = "https://github.com/dfinity/ic", rev = "a62848817cec7ae50618a87a526c85d020283fd9" } +pocket-ic = { git = "https://github.com/dfinity/ic", rev = "ab625a1b5498d6c5f44c918a419afb6ff3f92fb6" } [dev-dependencies] env_logger = "0.10" diff --git a/src/dfx/assets/dfx-asset-sources.toml b/src/dfx/assets/dfx-asset-sources.toml index 1940059a57..94a0f2272e 100644 --- a/src/dfx/assets/dfx-asset-sources.toml +++ b/src/dfx/assets/dfx-asset-sources.toml @@ -49,8 +49,8 @@ url = 'https://download.dfinity.systems/ic/a62848817cec7ae50618a87a526c85d020283 sha256 = '54f6142fde9d4a7859d4f33855216020d7eb567473b6a156e12b20fb88e5d9d9' [x86_64-darwin.pocket-ic] -url = 'https://download.dfinity.systems/ic/a62848817cec7ae50618a87a526c85d020283fd9/binaries/x86_64-darwin/pocket-ic.gz' -sha256 = 'fd47abe1876c3bf08246f97908c6badb3cf84ca402d8f4015759883d69689af6' +url = 'https://download.dfinity.systems/ic/ab625a1b5498d6c5f44c918a419afb6ff3f92fb6/binaries/x86_64-darwin/pocket-ic.gz' +sha256 = '9a601c1a7ebb231739c55f83c9dd646210a9836066abded2582871ebce7bd9ac' [x86_64-darwin.motoko-base] url = 'https://github.com/dfinity/motoko/releases/download/0.13.3/motoko-base-library.tar.gz' @@ -108,8 +108,8 @@ url = 'https://download.dfinity.systems/ic/a62848817cec7ae50618a87a526c85d020283 sha256 = 'bf5f8c2150744da98a6b9f13312d2dbfc6f7711e7ce8d5c3e4d3afab02034465' [x86_64-linux.pocket-ic] -url = 'https://download.dfinity.systems/ic/a62848817cec7ae50618a87a526c85d020283fd9/binaries/x86_64-linux/pocket-ic.gz' -sha256 = '7b14e2a196dd24ab171990ea755746f4b8c0776eaca64951be4150f83c019099' +url = 'https://download.dfinity.systems/ic/ab625a1b5498d6c5f44c918a419afb6ff3f92fb6/binaries/x86_64-linux/pocket-ic.gz' +sha256 = '6dfee7582c9282868f2fae3acddb98085bf748a5a48beea0bcd68b3fcb9fd12c' [x86_64-linux.motoko-base] url = 'https://github.com/dfinity/motoko/releases/download/0.13.3/motoko-base-library.tar.gz' diff --git a/src/dfx/src/commands/canister/call.rs b/src/dfx/src/commands/canister/call.rs index 4157f5c2bc..ba511b3a4a 100644 --- a/src/dfx/src/commands/canister/call.rs +++ b/src/dfx/src/commands/canister/call.rs @@ -6,6 +6,7 @@ use crate::lib::root_key::fetch_root_key_if_needed; use crate::util::clap::argument_from_cli::ArgumentFromCliPositionalOpt; use crate::util::clap::parsers::cycle_amount_parser; use crate::util::{blob_from_arguments, fetch_remote_did_file, get_candid_type, print_idl_blob}; +use anyhow::bail; use anyhow::{anyhow, Context}; use candid::Principal as CanisterId; use candid::{CandidType, Decode, Deserialize, Principal}; @@ -14,11 +15,14 @@ use clap::Parser; use dfx_core::canister::build_wallet_canister; use dfx_core::identity::CallSender; use ic_agent::agent::CallResponse; +use ic_agent::RequestId; use ic_utils::canister::Argument; use ic_utils::interfaces::management_canister::builders::{CanisterInstall, CanisterSettings}; use ic_utils::interfaces::management_canister::MgmtMethod; use ic_utils::interfaces::wallet::{CallForwarder, CallResult}; use ic_utils::interfaces::WalletCanister; +use pocket_ic::common::rest::RawEffectivePrincipal; +use pocket_ic::WasmResult; use slog::warn; use std::option::Option; use std::path::PathBuf; @@ -83,6 +87,11 @@ pub struct CanisterCallOpts { conflicts_with("random") )] always_assist: bool, + + /// Send request on behalf of the specified principal. + /// This option only works for a local PocketIC instance. + #[arg(long)] + impersonate: Option, } #[derive(Clone, CandidType, Deserialize, Debug)] @@ -205,8 +214,13 @@ pub fn get_effective_canister_id( pub async fn exec( env: &dyn Environment, opts: CanisterCallOpts, - call_sender: &CallSender, + mut call_sender: &CallSender, ) -> DfxResult { + let call_sender_override = opts.impersonate.map(CallSender::Impersonate); + if let Some(ref call_sender_override) = call_sender_override { + call_sender = call_sender_override; + }; + let agent = env.get_agent(); fetch_root_key_if_needed(env).await?; @@ -334,6 +348,21 @@ To figure out the id of your wallet, run 'dfx identity get-wallet (--network ic) .with_arg(arg_value); query_builder.call().await.context("Failed query call.")? } + CallSender::Impersonate(sender) => { + let pocketic = env.get_pocketic(); + if let Some(pocketic) = pocketic { + let res = pocketic + .query_call(canister_id, *sender, method_name, arg_value) + .await + .map_err(|err| anyhow!("Failed to perform query call: {}", err))?; + match res { + WasmResult::Reply(data) => data, + WasmResult::Reject(err) => bail!("Query call rejected: {}", err), + } + } else { + bail!("Impersonating sender is only supported for a local PocketIC instance.") + } + } CallSender::Wallet(wallet_id) => { let wallet = build_wallet_canister(*wallet_id, agent).await?; do_wallet_call( @@ -359,6 +388,27 @@ To figure out the id of your wallet, run 'dfx identity get-wallet (--network ic) .call() .await .context("Failed update call.")?, + CallSender::Impersonate(sender) => { + let pocketic = env.get_pocketic(); + if let Some(pocketic) = pocketic { + let msg_id = pocketic + .submit_call_with_effective_principal( + canister_id, + RawEffectivePrincipal::CanisterId( + effective_canister_id.as_slice().to_vec(), + ), + *sender, + method_name, + arg_value, + ) + .await + .map_err(|err| anyhow!("Failed to submit update call: {}", err))? + .message_id; + CallResponse::Poll(RequestId::new(msg_id.as_slice().try_into().unwrap())) + } else { + bail!("Impersonating sender is only supported for a local PocketIC instance.") + } + } CallSender::Wallet(wallet_id) => { let wallet = build_wallet_canister(*wallet_id, agent).await?; let mut args = Argument::default(); @@ -387,6 +437,34 @@ To figure out the id of your wallet, run 'dfx identity get-wallet (--network ic) .with_arg(arg_value) .await .context("Failed update call.")?, + CallSender::Impersonate(sender) => { + let pocketic = env.get_pocketic(); + if let Some(pocketic) = pocketic { + let msg_id = pocketic + .submit_call_with_effective_principal( + canister_id, + RawEffectivePrincipal::CanisterId( + effective_canister_id.as_slice().to_vec(), + ), + *sender, + method_name, + arg_value, + ) + .await + .map_err(|err| { + anyhow!("Failed to submit management canister call: {}", err) + })?; + let res = pocketic.await_call(msg_id).await.map_err(|err| { + anyhow!("Failed to await management canister call response: {}", err) + })?; + match res { + WasmResult::Reply(data) => data, + WasmResult::Reject(err) => bail!("Unexpected reject: {}", err), + } + } else { + bail!("Impersonating sender is only supported for a local PocketIC instance.") + } + } CallSender::Wallet(wallet_id) => { let wallet = build_wallet_canister(*wallet_id, agent).await?; do_wallet_call( diff --git a/src/dfx/src/commands/canister/delete.rs b/src/dfx/src/commands/canister/delete.rs index 74a2c66748..92920a534d 100644 --- a/src/dfx/src/commands/canister/delete.rs +++ b/src/dfx/src/commands/canister/delete.rs @@ -127,6 +127,12 @@ async fn delete_canister( CallSender::Wallet(wallet_id) => WithdrawTarget::Canister { canister_id: *wallet_id, }, + CallSender::Impersonate(principal) => WithdrawTarget::CyclesLedger { + to: Account { + owner: *principal, + subaccount: to_cycles_ledger_subaccount, + }, + }, CallSender::SelectedId => { let network = env.get_network_descriptor(); let identity_name = env diff --git a/src/dfx/src/commands/canister/deposit_cycles.rs b/src/dfx/src/commands/canister/deposit_cycles.rs index 1db8196e38..79ce04e50b 100644 --- a/src/dfx/src/commands/canister/deposit_cycles.rs +++ b/src/dfx/src/commands/canister/deposit_cycles.rs @@ -66,6 +66,14 @@ async fn deposit_cycles( ) .await?; } + CallSender::Impersonate(_) => { + let pocketic = env.get_pocketic(); + if let Some(pocketic) = pocketic { + pocketic.add_cycles(canister_id, cycles).await; + } else { + bail!("Impersonating sender is only supported for a local PocketIC instance.") + } + } CallSender::Wallet(_) => { canister::deposit_cycles(env, canister_id, call_sender, cycles).await? } diff --git a/src/dfx/src/commands/canister/status.rs b/src/dfx/src/commands/canister/status.rs index d3183759b9..4fba11d516 100644 --- a/src/dfx/src/commands/canister/status.rs +++ b/src/dfx/src/commands/canister/status.rs @@ -15,6 +15,11 @@ pub struct CanisterStatusOpts { /// You must specify either a canister name or the --all flag. canister: Option, + /// Send request on behalf of the specified principal. + /// This option only works for a local PocketIC instance. + #[arg(long)] + impersonate: Option, + /// Returns status information for all of the canisters configured in the dfx.json file. #[arg(long, required_unless_present("canister"))] all: bool, @@ -87,8 +92,13 @@ async fn canister_status( pub async fn exec( env: &dyn Environment, opts: CanisterStatusOpts, - call_sender: &CallSender, + mut call_sender: &CallSender, ) -> DfxResult { + let call_sender_override = opts.impersonate.map(CallSender::Impersonate); + if let Some(ref call_sender_override) = call_sender_override { + call_sender = call_sender_override; + }; + fetch_root_key_if_needed(env).await?; if let Some(canister) = opts.canister.as_deref() { diff --git a/src/dfx/src/commands/canister/update_settings.rs b/src/dfx/src/commands/canister/update_settings.rs index f6867c58af..f84391e9a7 100644 --- a/src/dfx/src/commands/canister/update_settings.rs +++ b/src/dfx/src/commands/canister/update_settings.rs @@ -15,6 +15,7 @@ use crate::util::clap::parsers::{ use anyhow::{bail, Context}; use byte_unit::Byte; use candid::Principal as CanisterId; +use candid::Principal; use clap::{ArgAction, Parser}; use dfx_core::cli::ask_for_consent; use dfx_core::error::identity::InstantiateIdentityFromNameError::GetIdentityPrincipalFailed; @@ -100,13 +101,23 @@ pub struct UpdateSettingsOpts { /// so this is not recommended outside of CI. #[arg(long, short)] yes: bool, + + /// Send request on behalf of the specified principal. + /// This option only works for a local PocketIC instance. + #[arg(long)] + impersonate: Option, } pub async fn exec( env: &dyn Environment, opts: UpdateSettingsOpts, - call_sender: &CallSender, + mut call_sender: &CallSender, ) -> DfxResult { + let call_sender_override = opts.impersonate.map(CallSender::Impersonate); + if let Some(ref call_sender_override) = call_sender_override { + call_sender = call_sender_override; + }; + // sanity checks if let Some(threshold_in_seconds) = opts.freezing_threshold { if threshold_in_seconds > 50_000_000 /* ~1.5 years */ && !opts.confirm_very_long_freezing_threshold @@ -342,6 +353,7 @@ fn user_is_removing_themselves_as_controller( .get_selected_identity_principal() .context("Selected identity is not instantiated")? .to_string(), + CallSender::Impersonate(principal) => principal.to_string(), CallSender::Wallet(principal) => principal.to_string(), }; let removes_themselves = diff --git a/src/dfx/src/lib/environment.rs b/src/dfx/src/lib/environment.rs index 6962f5d975..4ce777e640 100644 --- a/src/dfx/src/lib/environment.rs +++ b/src/dfx/src/lib/environment.rs @@ -12,10 +12,12 @@ use dfx_core::config::model::network_descriptor::{NetworkDescriptor, NetworkType use dfx_core::error::canister_id_store::CanisterIdStoreError; use dfx_core::error::identity::NewIdentityManagerError; use dfx_core::error::load_dfx_config::LoadDfxConfigError; +use dfx_core::error::uri::UriError; use dfx_core::extension::manager::ExtensionManager; use dfx_core::identity::identity_manager::{IdentityManager, InitializeIdentity}; use fn_error_context::context; use ic_agent::{Agent, Identity}; +use pocket_ic::nonblocking::PocketIc; use semver::Version; use slog::{Logger, Record}; use std::borrow::Cow; @@ -23,6 +25,7 @@ use std::cell::RefCell; use std::path::PathBuf; use std::sync::Arc; use std::time::Duration; +use url::Url; pub trait Environment { fn get_cache(&self) -> Arc; @@ -44,6 +47,8 @@ pub trait Environment { #[allow(clippy::needless_lifetimes)] fn get_agent<'a>(&'a self) -> &'a Agent; + fn get_pocketic(&self) -> Option<&PocketIc>; + #[allow(clippy::needless_lifetimes)] fn get_network_descriptor<'a>(&'a self) -> &'a NetworkDescriptor; @@ -213,6 +218,10 @@ impl Environment for EnvironmentImpl { unreachable!("Agent only available from an AgentEnvironment"); } + fn get_pocketic(&self) -> Option<&PocketIc> { + unreachable!("PocketIC handle only available from an AgentEnvironment"); + } + fn get_network_descriptor(&self) -> &NetworkDescriptor { // It's not valid to call get_network_descriptor on an EnvironmentImpl. // All of the places that call this have an AgentEnvironment anyway. @@ -267,6 +276,7 @@ impl Environment for EnvironmentImpl { pub struct AgentEnvironment<'a> { backend: &'a dyn Environment, agent: Agent, + pocketic: Option, network_descriptor: NetworkDescriptor, identity_manager: IdentityManager, effective_canister_id: Option, @@ -314,9 +324,27 @@ impl<'a> AgentEnvironment<'a> { None }; + let pocketic = + if let Some(local_server_descriptor) = &network_descriptor.local_server_descriptor { + match local_server_descriptor.get_running_pocketic_port(None)? { + Some(port) => { + let mut socket_addr = local_server_descriptor.bind_address; + socket_addr.set_port(port); + let url = format!("http://{}", socket_addr); + let url = Url::parse(&url) + .map_err(|e| UriError::UrlParseError(url.to_string(), e))?; + Some(create_pocketic(&url, timeout)?) + } + None => None, + } + } else { + None + }; + Ok(AgentEnvironment { backend, agent: create_agent(logger, url, identity, timeout)?, + pocketic, network_descriptor: network_descriptor.clone(), identity_manager, effective_canister_id, @@ -359,6 +387,10 @@ impl<'a> Environment for AgentEnvironment<'a> { &self.agent } + fn get_pocketic(&self) -> Option<&PocketIc> { + self.pocketic.as_ref() + } + fn get_network_descriptor(&self) -> &NetworkDescriptor { &self.network_descriptor } @@ -422,3 +454,14 @@ pub fn create_agent( .build()?; Ok(agent) } + +#[context("Failed to create PocketIC handle with url {}.", url)] +pub fn create_pocketic(url: &Url, timeout: Duration) -> DfxResult { + let rt = tokio::runtime::Runtime::new().unwrap(); + let pocketic = rt.block_on(PocketIc::new_from_existing_instance( + url.clone(), + 0, + Some(timeout.as_millis().try_into().unwrap()), + )); + Ok(pocketic) +} diff --git a/src/dfx/src/lib/operations/canister/create_canister.rs b/src/dfx/src/lib/operations/canister/create_canister.rs index 8c4952afc7..d1c51797eb 100644 --- a/src/dfx/src/lib/operations/canister/create_canister.rs +++ b/src/dfx/src/lib/operations/canister/create_canister.rs @@ -173,6 +173,16 @@ The command line value will be used.", .await } } + CallSender::Impersonate(principal) => { + let pocketic = env.get_pocketic(); + if let Some(pocketic) = pocketic { + Ok(pocketic + .create_canister_with_settings(Some(principal), None) + .await) + } else { + bail!("Impersonating sender is only supported for a local PocketIC instance.") + } + } CallSender::Wallet(wallet_id) => { create_with_wallet(agent, &wallet_id, with_cycles, settings, subnet_selection).await } diff --git a/src/dfx/src/lib/operations/canister/mod.rs b/src/dfx/src/lib/operations/canister/mod.rs index 501dc9284d..b7d3b107f2 100644 --- a/src/dfx/src/lib/operations/canister/mod.rs +++ b/src/dfx/src/lib/operations/canister/mod.rs @@ -11,11 +11,12 @@ use crate::lib::canister_info::CanisterInfo; use crate::lib::environment::Environment; use crate::lib::error::DfxResult; use crate::lib::ic_attributes::CanisterSettings as DfxCanisterSettings; -use anyhow::{bail, Context}; +use anyhow::{anyhow, bail, Context}; use candid::utils::ArgumentDecoder; use candid::CandidType; use candid::Principal as CanisterId; use candid::Principal; +use candid::{decode_args, encode_args}; use dfx_core::canister::build_wallet_canister; use dfx_core::config::model::dfinity::Config; use dfx_core::identity::CallSender; @@ -27,6 +28,8 @@ use ic_utils::interfaces::management_canister::{ }; use ic_utils::interfaces::ManagementCanister; use ic_utils::Argument; +use pocket_ic::common::rest::RawEffectivePrincipal; +use pocket_ic::WasmResult; use std::collections::HashSet; use std::path::PathBuf; @@ -59,6 +62,31 @@ where .await .context("Update call (without wallet) failed.")? } + CallSender::Impersonate(sender) => { + let pocketic = env.get_pocketic(); + if let Some(pocketic) = pocketic { + let msg_id = pocketic + .submit_call_with_effective_principal( + Principal::management_canister(), + RawEffectivePrincipal::CanisterId(destination_canister.as_slice().to_vec()), + *sender, + method, + encode_args((arg,)).unwrap(), + ) + .await + .map_err(|err| anyhow!("Failed to submit management canister call: {}", err))?; + let res = pocketic.await_call(msg_id).await.map_err(|err| { + anyhow!("Failed to await management canister call response: {}", err) + })?; + match res { + WasmResult::Reply(data) => decode_args(&data) + .context("Could not decode management canister response.")?, + WasmResult::Reject(err) => bail!("Unexpected reject: {}", err), + } + } else { + bail!("Impersonating sender is only supported for a local PocketIC instance.") + } + } CallSender::Wallet(wallet_id) => { let wallet = build_wallet_canister(*wallet_id, agent).await?; let out: O = wallet @@ -106,6 +134,11 @@ where .await .context("Query call (without wallet) failed.")? } + CallSender::Impersonate(_) => { + unreachable!( + "Impersonating sender in management canister query calls in not supported." + ) + } CallSender::Wallet(wallet_id) => { let wallet = build_wallet_canister(*wallet_id, agent).await?; let out: O = wallet