From c59235bd5ecd9e5f6c1d446eb43d7245064ad86b Mon Sep 17 00:00:00 2001 From: Louis Pahlavi Date: Tue, 10 Mar 2026 13:09:23 +0100 Subject: [PATCH 1/6] refactor: move proxy feature to `PocketIcRuntime` --- .github/workflows/ci.yml | 7 + Cargo.lock | 346 +++++++++++++++++++++++- Cargo.toml | 1 + examples/http_canister/Cargo.toml | 2 + examples/http_canister/src/main.rs | 40 ++- examples/http_canister/tests/tests.rs | 33 ++- ic-canister-runtime/Cargo.toml | 1 - ic-canister-runtime/src/lib.rs | 4 - ic-canister-runtime/src/proxy.rs | 155 ----------- ic-pocket-canister-runtime/Cargo.toml | 1 + ic-pocket-canister-runtime/src/lib.rs | 66 +++-- ic-pocket-canister-runtime/src/proxy.rs | 77 ++++++ test_fixtures/Cargo.toml | 1 + test_fixtures/src/lib.rs | 115 +++++++- 14 files changed, 649 insertions(+), 200 deletions(-) delete mode 100644 ic-canister-runtime/src/proxy.rs create mode 100644 ic-pocket-canister-runtime/src/proxy.rs diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index ff13063..b9933f5 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -13,6 +13,7 @@ on: env: CARGO_TERM_COLOR: always RUSTFLAGS: "-Dwarnings" + PROXY_CANISTER_VERSION: "v0.1.0" jobs: lint: @@ -75,6 +76,12 @@ jobs: echo "JSON_RPC_CANISTER_WASM_PATH=$GITHUB_WORKSPACE/target/wasm32-unknown-unknown/release/json_rpc_canister.wasm" >> "$GITHUB_ENV" echo "MULTI_CANISTER_WASM_PATH=$GITHUB_WORKSPACE/target/wasm32-unknown-unknown/release/multi_canister.wasm" >> "$GITHUB_ENV" + - name: 'Download proxy canister WASM' + run: | + mkdir -p $HOME/wasm + curl -L "https://github.com/dfinity/proxy-canister/releases/download/${PROXY_CANISTER_VERSION}/proxy.wasm" -o $HOME/wasm/proxy.wasm + echo "PROXY_CANISTER_WASM_PATH=$HOME/wasm/proxy.wasm" >> $GITHUB_ENV + - name: 'Install PocketIC server' uses: dfinity/pocketic@main with: diff --git a/Cargo.lock b/Cargo.lock index 0feeffa..ed5abba 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -137,6 +137,28 @@ version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8" +[[package]] +name = "aws-lc-rs" +version = "1.16.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94bffc006df10ac2a68c83692d734a465f8ee6c5b384d8545a636f81d858f4bf" +dependencies = [ + "aws-lc-sys", + "zeroize", +] + +[[package]] +name = "aws-lc-sys" +version = "0.38.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4321e568ed89bb5a7d291a7f37997c2c0df89809d7b6d12062c81ddb54aa782e" +dependencies = [ + "cc", + "cmake", + "dunce", + "fs_extra", +] + [[package]] name = "backoff" version = "0.4.0" @@ -366,9 +388,17 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "aebf35691d1bfb0ac386a69bac2fde4dd276fb618cf8bf4f5318fe285e821bb2" dependencies = [ "find-msvc-tools", + "jobserver", + "libc", "shlex", ] +[[package]] +name = "cesu8" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6d43a04d8753f35258c91f8ec639f792891f748a1edbd759cf1dcea3382ad83c" + [[package]] name = "cfg-if" version = "1.0.4" @@ -408,6 +438,25 @@ dependencies = [ "half 2.7.1", ] +[[package]] +name = "cmake" +version = "0.1.57" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75443c44cd6b379beb8c5b45d85d0773baf31cce901fe7bb252f4eff3008ef7d" +dependencies = [ + "cc", +] + +[[package]] +name = "combine" +version = "4.6.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba5a308b75df32fe02788e748662718f03fde005016435c444eea572398219fd" +dependencies = [ + "bytes", + "memchr", +] + [[package]] name = "concurrent-queue" version = "2.5.0" @@ -432,6 +481,16 @@ dependencies = [ "unicode-segmentation", ] +[[package]] +name = "core-foundation" +version = "0.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "91e195e091a93c46f7102ec7818a2aa394e1e1771c3ab4825963fa03e45afb8f" +dependencies = [ + "core-foundation-sys", + "libc", +] + [[package]] name = "core-foundation" version = "0.10.1" @@ -666,6 +725,12 @@ dependencies = [ "syn 2.0.117", ] +[[package]] +name = "dunce" +version = "1.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "92773504d58c093f6de2459af4af33faa518c13451eb8f2b5698ed3d36e7c813" + [[package]] name = "dyn-clone" version = "1.0.20" @@ -754,6 +819,15 @@ dependencies = [ "zeroize", ] +[[package]] +name = "encoding_rs" +version = "0.8.35" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75030f3c4f45dafd7586dd6780965a8c7e8e285a5ecb86713e63a79c5b2766f3" +dependencies = [ + "cfg-if", +] + [[package]] name = "equivalent" version = "1.0.2" @@ -876,6 +950,12 @@ dependencies = [ "percent-encoding", ] +[[package]] +name = "fs_extra" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42703706b716c37f96a77aea830392ad231f44c9e9a67872fa5548707e11b11c" + [[package]] name = "futures-channel" version = "0.3.32" @@ -1128,9 +1208,11 @@ dependencies = [ name = "http_canister" version = "1.0.0" dependencies = [ + "assert_matches", "candid", "canhttp", "http", + "ic-canister-runtime", "ic-cdk", "ic-management-canister-types", "ic-test-utilities-load-wasm", @@ -1205,9 +1287,11 @@ dependencies = [ "percent-encoding", "pin-project-lite", "socket2", + "system-configuration", "tokio", "tower-service", "tracing", + "windows-registry", ] [[package]] @@ -1245,7 +1329,7 @@ dependencies = [ "pkcs8", "rand 0.8.5", "rangemap", - "reqwest", + "reqwest 0.12.28", "sec1", "serde", "serde_bytes", @@ -1394,6 +1478,7 @@ dependencies = [ "ic-error-types", "pocket-ic", "serde", + "serde_bytes", "serde_json", "tokio", "url", @@ -1657,6 +1742,38 @@ version = "1.0.17" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "92ecc6618181def0457392ccd0ee51198e065e016d1d527a7ac1b6dc7c1f09d2" +[[package]] +name = "jni" +version = "0.21.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a87aa2bb7d2af34197c04845522473242e1aa17c12f4935d5856491a7fb8c97" +dependencies = [ + "cesu8", + "cfg-if", + "combine", + "jni-sys", + "log", + "thiserror 1.0.69", + "walkdir", + "windows-sys 0.45.0", +] + +[[package]] +name = "jni-sys" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8eaf4bc02d17cbdd7ff4c7438cafcdf7fb9a4613313ad11b4f8fefe7d3fa0130" + +[[package]] +name = "jobserver" +version = "0.1.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9afb3de4395d6b3e67a780b6de64b51c978ecf11cb9a462c66be7d4ca9039d33" +dependencies = [ + "getrandom 0.3.4", + "libc", +] + [[package]] name = "js-sys" version = "0.3.85" @@ -2055,7 +2172,7 @@ dependencies = [ "ic-certification", "ic-management-canister-types", "ic-transport-types 0.40.1", - "reqwest", + "reqwest 0.12.28", "schemars", "semver", "serde", @@ -2199,6 +2316,7 @@ version = "0.11.13" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f1906b49b0c3bc04b5fe5d86a77925ae6524a19b816ae38ce1e426255f1d8a31" dependencies = [ + "aws-lc-rs", "bytes", "getrandom 0.3.4", "lru-slab", @@ -2394,6 +2512,44 @@ dependencies = [ "webpki-roots", ] +[[package]] +name = "reqwest" +version = "0.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ab3f43e3283ab1488b624b44b0e988d0acea0b3214e694730a055cb6b2efa801" +dependencies = [ + "base64 0.22.1", + "bytes", + "encoding_rs", + "futures-core", + "h2", + "http", + "http-body", + "http-body-util", + "hyper", + "hyper-rustls", + "hyper-util", + "js-sys", + "log", + "mime", + "percent-encoding", + "pin-project-lite", + "quinn", + "rustls", + "rustls-pki-types", + "rustls-platform-verifier", + "sync_wrapper", + "tokio", + "tokio-rustls", + "tower", + "tower-http", + "tower-service", + "url", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-sys", +] + [[package]] name = "rfc6979" version = "0.4.0" @@ -2452,6 +2608,7 @@ version = "0.23.36" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c665f33d38cea657d9614f766881e4d510e0eda4239891eea56b4cadcf01801b" dependencies = [ + "aws-lc-rs", "once_cell", "ring", "rustls-pki-types", @@ -2482,12 +2639,40 @@ dependencies = [ "zeroize", ] +[[package]] +name = "rustls-platform-verifier" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d99feebc72bae7ab76ba994bb5e121b8d83d910ca40b36e0921f53becc41784" +dependencies = [ + "core-foundation 0.10.1", + "core-foundation-sys", + "jni", + "log", + "once_cell", + "rustls", + "rustls-native-certs", + "rustls-platform-verifier-android", + "rustls-webpki", + "security-framework", + "security-framework-sys", + "webpki-root-certs", + "windows-sys 0.61.2", +] + +[[package]] +name = "rustls-platform-verifier-android" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f87165f0995f63a9fbeea62b64d10b4d9d8e78ec6d7d51fb2125fda7bb36788f" + [[package]] name = "rustls-webpki" version = "0.103.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d7df23109aa6c1567d1c575b9952556388da57401e4ace1d15f79eedad0d8f53" dependencies = [ + "aws-lc-rs", "ring", "rustls-pki-types", "untrusted", @@ -2517,6 +2702,15 @@ version = "1.0.23" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9774ba4a74de5f7b1c1451ed6cd5285a32eddb5cccb8cc655a4e50009e06477f" +[[package]] +name = "same-file" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502" +dependencies = [ + "winapi-util", +] + [[package]] name = "schannel" version = "0.1.28" @@ -2577,7 +2771,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d17b898a6d6948c3a8ee4372c17cb384f90d2e6e912ef00895b14fd7ab54ec38" dependencies = [ "bitflags", - "core-foundation", + "core-foundation 0.10.1", "core-foundation-sys", "libc", "security-framework-sys", @@ -2834,7 +3028,6 @@ dependencies = [ "cfg-if", "libc", "psm", - "windows-sys 0.52.0", "windows-sys 0.59.0", ] @@ -2953,6 +3146,27 @@ dependencies = [ "syn 2.0.117", ] +[[package]] +name = "system-configuration" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a13f3d0daba03132c0aa9767f98351b3488edc2c100cda2d2ec2b04f3d8d3c8b" +dependencies = [ + "bitflags", + "core-foundation 0.9.4", + "system-configuration-sys", +] + +[[package]] +name = "system-configuration-sys" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e1d1b10ced5ca923a1fcb8d03e96b8d3268065d724548c0211415ff6ac6bac4" +dependencies = [ + "core-foundation-sys", + "libc", +] + [[package]] name = "tempfile" version = "3.26.0" @@ -2976,6 +3190,7 @@ dependencies = [ "ic-pocket-canister-runtime", "ic-test-utilities-load-wasm", "pocket-ic", + "reqwest 0.13.2", "serde", ] @@ -3376,6 +3591,16 @@ dependencies = [ "libc", ] +[[package]] +name = "walkdir" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "29790946404f91d9c5d06f9874efddea1dc06c5efe94541a7d6863108e3a5e4b" +dependencies = [ + "same-file", + "winapi-util", +] + [[package]] name = "want" version = "0.3.1" @@ -3535,6 +3760,15 @@ dependencies = [ "wasm-bindgen", ] +[[package]] +name = "webpki-root-certs" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "804f18a4ac2676ffb4e8b5b5fa9ae38af06df08162314f96a68d2a363e21a8ca" +dependencies = [ + "rustls-pki-types", +] + [[package]] name = "webpki-roots" version = "1.0.6" @@ -3544,12 +3778,59 @@ dependencies = [ "rustls-pki-types", ] +[[package]] +name = "winapi-util" +version = "0.1.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c2a7b1c03c876122aa43f3020e6c3c3ee5c05081c9a00739faf7503aeba10d22" +dependencies = [ + "windows-sys 0.61.2", +] + [[package]] name = "windows-link" version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5" +[[package]] +name = "windows-registry" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "02752bf7fbdcce7f2a27a742f798510f3e5ad88dbe84871e5168e2120c3d5720" +dependencies = [ + "windows-link", + "windows-result", + "windows-strings", +] + +[[package]] +name = "windows-result" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7781fa89eaf60850ac3d2da7af8e5242a5ea78d1a11c49bf2910bb5a73853eb5" +dependencies = [ + "windows-link", +] + +[[package]] +name = "windows-strings" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7837d08f69c77cf6b07689544538e017c1bfcf57e34b4c0ff58e6c2cd3b37091" +dependencies = [ + "windows-link", +] + +[[package]] +name = "windows-sys" +version = "0.45.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75283be5efb2831d37ea142365f009c02ec203cd29a3ebecbc093d52315b66d0" +dependencies = [ + "windows-targets 0.42.2", +] + [[package]] name = "windows-sys" version = "0.52.0" @@ -3586,6 +3867,21 @@ dependencies = [ "windows-link", ] +[[package]] +name = "windows-targets" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e5180c00cd44c9b1c88adb3693291f1cd93605ded80c250a75d472756b4d071" +dependencies = [ + "windows_aarch64_gnullvm 0.42.2", + "windows_aarch64_msvc 0.42.2", + "windows_i686_gnu 0.42.2", + "windows_i686_msvc 0.42.2", + "windows_x86_64_gnu 0.42.2", + "windows_x86_64_gnullvm 0.42.2", + "windows_x86_64_msvc 0.42.2", +] + [[package]] name = "windows-targets" version = "0.52.6" @@ -3619,6 +3915,12 @@ dependencies = [ "windows_x86_64_msvc 0.53.1", ] +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "597a5118570b68bc08d8d59125332c54f1ba9d9adeedeef5b99b02ba2b0698f8" + [[package]] name = "windows_aarch64_gnullvm" version = "0.52.6" @@ -3631,6 +3933,12 @@ version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a9d8416fa8b42f5c947f8482c43e7d89e73a173cead56d044f6a56104a6d1b53" +[[package]] +name = "windows_aarch64_msvc" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e08e8864a60f06ef0d0ff4ba04124db8b0fb3be5776a5cd47641e942e58c4d43" + [[package]] name = "windows_aarch64_msvc" version = "0.52.6" @@ -3643,6 +3951,12 @@ version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b9d782e804c2f632e395708e99a94275910eb9100b2114651e04744e9b125006" +[[package]] +name = "windows_i686_gnu" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c61d927d8da41da96a81f029489353e68739737d3beca43145c8afec9a31a84f" + [[package]] name = "windows_i686_gnu" version = "0.52.6" @@ -3667,6 +3981,12 @@ version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fa7359d10048f68ab8b09fa71c3daccfb0e9b559aed648a8f95469c27057180c" +[[package]] +name = "windows_i686_msvc" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "44d840b6ec649f480a41c8d80f9c65108b92d89345dd94027bfe06ac444d1060" + [[package]] name = "windows_i686_msvc" version = "0.52.6" @@ -3679,6 +3999,12 @@ version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1e7ac75179f18232fe9c285163565a57ef8d3c89254a30685b57d83a38d326c2" +[[package]] +name = "windows_x86_64_gnu" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8de912b8b8feb55c064867cf047dda097f92d51efad5b491dfb98f6bbb70cb36" + [[package]] name = "windows_x86_64_gnu" version = "0.52.6" @@ -3691,6 +4017,12 @@ version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9c3842cdd74a865a8066ab39c8a7a473c0778a3f29370b5fd6b4b9aa7df4a499" +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "26d41b46a36d453748aedef1486d5c7a85db22e56aff34643984ea85514e94a3" + [[package]] name = "windows_x86_64_gnullvm" version = "0.52.6" @@ -3703,6 +4035,12 @@ version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0ffa179e2d07eee8ad8f57493436566c7cc30ac536a3379fdf008f47f6bb7ae1" +[[package]] +name = "windows_x86_64_msvc" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9aec5da331524158c6d1a4ac0ab1541149c0b9505fde06423b02f5ef0106b9f0" + [[package]] name = "windows_x86_64_msvc" version = "0.52.6" diff --git a/Cargo.toml b/Cargo.toml index 228447b..dbcf788 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -43,6 +43,7 @@ pin-project = "1.1.11" pocket-ic = "12.0.0" proptest = "1.10.0" regex-lite = "0.1.9" +reqwest = "0.13.2" serde = "1.0" serde_bytes = "0.11.19" serde_json = "1.0" diff --git a/examples/http_canister/Cargo.toml b/examples/http_canister/Cargo.toml index f1dd604..a29af21 100644 --- a/examples/http_canister/Cargo.toml +++ b/examples/http_canister/Cargo.toml @@ -15,6 +15,8 @@ ic-cdk = { workspace = true } tower = { workspace = true } [dev-dependencies] +assert_matches = { workspace = true } +ic-canister-runtime = { workspace = true } ic-management-canister-types = { workspace = true } ic-test-utilities-load-wasm = { workspace = true } pocket-ic = { workspace = true } diff --git a/examples/http_canister/src/main.rs b/examples/http_canister/src/main.rs index e5e3434..3170132 100644 --- a/examples/http_canister/src/main.rs +++ b/examples/http_canister/src/main.rs @@ -1,19 +1,36 @@ //! Example of a canister using `canhttp` to issue HTTP requests. use canhttp::{ - cycles::{ChargeMyself, CyclesAccountingServiceBuilder}, + cycles::{ChargeCaller, ChargeMyself, CyclesAccountingServiceBuilder, CyclesChargingPolicy}, http::HttpConversionLayer, observability::ObservabilityLayer, CanisterReadyLayer, Client, MaxResponseBytesRequestExtension, }; -use http::Request; use ic_cdk::update; use tower::{BoxError, Service, ServiceBuilder, ServiceExt}; /// Make an HTTP POST request. #[update] pub async fn make_http_post_request() -> String { - let response = http_client() + let response = http_client(ChargeMyself::default()) + .ready() + .await + .expect("Client should be ready") + .call(request()) + .await + .expect("Request should succeed"); + + assert_eq!(response.status(), http::StatusCode::OK); + + String::from_utf8_lossy(response.body()).to_string() +} + +/// Make an HTTP POST request and charge the user cycles for it. +#[update] +pub async fn make_http_post_request_and_charge_user_cycles() -> String { + // Use cycles attached by the caller to pay for HTTPs outcalls and charge an additional flat + // fee of 1M cycles. + let response = http_client(ChargeCaller::new(|_request, cost| cost + 1_000_000)) .ready() .await .expect("Client should be ready") @@ -32,7 +49,7 @@ pub async fn make_http_post_request() -> String { pub async fn infinite_loop_make_http_post_request() -> String { let mut client = ServiceBuilder::new() .layer(CanisterReadyLayer) - .service(http_client()); + .service(http_client(ChargeMyself::default())); loop { match client.ready().await { @@ -45,8 +62,13 @@ pub async fn infinite_loop_make_http_post_request() -> String { } } -fn http_client( -) -> impl Service>, Response = http::Response>, Error = BoxError> { +fn http_client( + cycles_charging_policy: C, +) -> impl Service>, Response = http::Response>, Error = BoxError> +where + C: CyclesChargingPolicy + Clone, + ::Error: std::error::Error + Send + Sync + 'static, +{ ServiceBuilder::new() // Print request, response and errors to the console .layer( @@ -61,13 +83,13 @@ fn http_client( ) // Only deal with types from the http crate. .layer(HttpConversionLayer) - // Use cycles from the canister to pay for HTTPs outcalls - .cycles_accounting(ChargeMyself::default()) + // The strategy to use to charge cycles for the request + .cycles_accounting(cycles_charging_policy) // The actual client .service(Client::new_with_box_error()) } -fn request() -> Request> { +fn request() -> http::Request> { fn httpbin_base_url() -> String { option_env!("HTTPBIN_URL") .unwrap_or_else(|| "https://httpbin.org") diff --git a/examples/http_canister/tests/tests.rs b/examples/http_canister/tests/tests.rs index 4f4b9ac..f6c3e54 100644 --- a/examples/http_canister/tests/tests.rs +++ b/examples/http_canister/tests/tests.rs @@ -1,6 +1,7 @@ +use assert_matches::assert_matches; use candid::{Decode, Encode, Principal}; -use ic_management_canister_types::CanisterIdRecord; -use ic_management_canister_types::CanisterSettings; +use ic_canister_runtime::IcError; +use ic_management_canister_types::{CanisterIdRecord, CanisterSettings}; use pocket_ic::common::rest::{ CanisterHttpReply, CanisterHttpResponse, MockCanisterHttpResponse, RawEffectivePrincipal, }; @@ -20,6 +21,34 @@ async fn should_make_http_post_request() { assert!(http_request_result.contains("\"X-Id\": \"42\"")); } +#[tokio::test] +async fn should_attach_cycles_to_canister_call() { + const REQUIRED_CYCLES: u128 = 1_000_000; + + let setup = Setup::new("http_canister").await.with_proxy().await; + + let http_request_result = setup + .canister() + .try_update_call_with_cycles::<_, String>( + "make_http_post_request_and_charge_user_cycles", + (), + REQUIRED_CYCLES - 1, + ) + .await; + assert_matches!(http_request_result, Err(IcError::CallRejected { code: _, message }) if message.contains("InsufficientCyclesError")); + + let http_request_result = setup + .canister() + .update_call_with_cycles::<_, String>( + "make_http_post_request_and_charge_user_cycles", + (), + REQUIRED_CYCLES, + ) + .await; + assert!(http_request_result.contains("Hello, World!")); + assert!(http_request_result.contains("\"X-Id\": \"42\"")); +} + #[test] fn should_not_make_http_request_when_stopping() { let env = PocketIc::new(); diff --git a/ic-canister-runtime/Cargo.toml b/ic-canister-runtime/Cargo.toml index 0cd8521..d6833eb 100644 --- a/ic-canister-runtime/Cargo.toml +++ b/ic-canister-runtime/Cargo.toml @@ -11,7 +11,6 @@ repository.workspace = true documentation = "https://docs.rs/ic-canister-runtime" [features] -proxy = ["dep:serde_bytes"] wallet = ["dep:regex-lite", "dep:serde_bytes"] [dependencies] diff --git a/ic-canister-runtime/src/lib.rs b/ic-canister-runtime/src/lib.rs index e5cd54e..e77695b 100644 --- a/ic-canister-runtime/src/lib.rs +++ b/ic-canister-runtime/src/lib.rs @@ -10,16 +10,12 @@ use async_trait::async_trait; use candid::{utils::ArgumentEncoder, CandidType, Principal}; use ic_cdk::call::{Call, CallFailed, CandidDecodeFailed}; use ic_error_types::RejectCode; -#[cfg(feature = "proxy")] -pub use proxy::ProxyRuntime; use serde::de::DeserializeOwned; pub use stub::StubRuntime; use thiserror::Error; #[cfg(feature = "wallet")] pub use wallet::CyclesWalletRuntime; -#[cfg(feature = "proxy")] -mod proxy; mod stub; #[cfg(feature = "wallet")] mod wallet; diff --git a/ic-canister-runtime/src/proxy.rs b/ic-canister-runtime/src/proxy.rs deleted file mode 100644 index 46cfae7..0000000 --- a/ic-canister-runtime/src/proxy.rs +++ /dev/null @@ -1,155 +0,0 @@ -use crate::{IcError, Runtime}; -use async_trait::async_trait; -use candid::{decode_one, encode_args, utils::ArgumentEncoder, CandidType, Deserialize, Principal}; -use ic_error_types::RejectCode; -use serde::{de::DeserializeOwned, Serialize}; - -/// Runtime wrapping another [`Runtime`] instance, where update calls are forwarded through a -/// [proxy canister](https://github.com/dfinity/proxy-canister) to attach cycles to them. -pub struct ProxyRuntime { - runtime: R, - proxy_canister_id: Principal, -} - -impl ProxyRuntime { - /// Create a new [`ProxyRuntime`] wrapping the given [`Runtime`] by forwarding update calls - /// through the given proxy canister to attach cycles. - pub fn new(runtime: R, proxy_canister_id: Principal) -> Self { - ProxyRuntime { - runtime, - proxy_canister_id, - } - } - - /// Modify the underlying runtime by applying a transformation function. - /// - /// The transformation does not necessarily produce a runtime of the same type. - pub fn with_runtime S>(self, transformation: F) -> ProxyRuntime { - ProxyRuntime { - runtime: transformation(self.runtime), - proxy_canister_id: self.proxy_canister_id, - } - } -} - -impl AsRef for ProxyRuntime { - fn as_ref(&self) -> &R { - &self.runtime - } -} - -#[async_trait] -impl Runtime for ProxyRuntime { - async fn update_call( - &self, - id: Principal, - method: &str, - args: In, - cycles: u128, - ) -> Result - where - In: ArgumentEncoder + Send, - Out: CandidType + DeserializeOwned, - { - self.runtime - .update_call::<(ProxyArgs,), Result>( - self.proxy_canister_id, - "proxy", - (ProxyArgs::new(id, method, args, cycles),), - 0, - ) - .await - .and_then(decode_proxy_canister_response) - } - - async fn query_call( - &self, - id: Principal, - method: &str, - args: In, - ) -> Result - where - In: ArgumentEncoder + Send, - Out: CandidType + DeserializeOwned, - { - self.runtime.query_call(id, method, args).await - } -} - -fn decode_proxy_canister_response( - result: Result, -) -> Result -where - Out: CandidType + DeserializeOwned, -{ - match result { - Ok(ProxySucceed { result }) => { - decode_one(&result).map_err(|e| IcError::CandidDecodeFailed { - message: format!( - "failed to decode canister response as {}: {}", - std::any::type_name::(), - e - ), - }) - } - Err(error) => match error { - ProxyError::UnauthorizedUser => Err(IcError::CallRejected { - code: RejectCode::SysFatal, - message: "Unauthorized caller!".to_string(), - }), - ProxyError::InsufficientCycles { - available, - required, - } => Err(IcError::InsufficientLiquidCycleBalance { - available, - required, - }), - ProxyError::CallFailed { reason } => Err(IcError::CallRejected { - code: RejectCode::SysFatal, - message: reason, - }), - }, - } -} - -#[derive(CandidType, Serialize, Deserialize, Debug, Clone)] -struct ProxyArgs { - canister_id: Principal, - method: String, - #[serde(with = "serde_bytes")] - args: Vec, - cycles: u128, -} - -impl ProxyArgs { - pub fn new( - canister_id: Principal, - method: impl ToString, - args: In, - cycles: u128, - ) -> Self { - Self { - canister_id, - method: method.to_string(), - args: encode_args(args).unwrap_or_else(panic_when_encode_fails), - cycles, - } - } -} - -#[derive(CandidType, Serialize, Deserialize, Debug, Clone)] -struct ProxySucceed { - #[serde(with = "serde_bytes")] - result: Vec, -} - -#[derive(CandidType, Serialize, Deserialize, Debug, Clone)] -enum ProxyError { - InsufficientCycles { available: u128, required: u128 }, - CallFailed { reason: String }, - UnauthorizedUser, -} - -fn panic_when_encode_fails(err: candid::error::Error) -> Vec { - panic!("failed to encode args: {err}") -} diff --git a/ic-pocket-canister-runtime/Cargo.toml b/ic-pocket-canister-runtime/Cargo.toml index cc76b5e..bb17bf1 100644 --- a/ic-pocket-canister-runtime/Cargo.toml +++ b/ic-pocket-canister-runtime/Cargo.toml @@ -19,6 +19,7 @@ ic-cdk = { workspace = true } ic-error-types = { workspace = true } pocket-ic = { workspace = true } serde = { workspace = true } +serde_bytes = { workspace = true } serde_json = { workspace = true } tokio = { workspace = true } url = { workspace = true } diff --git a/ic-pocket-canister-runtime/src/lib.rs b/ic-pocket-canister-runtime/src/lib.rs index 7366157..46813ee 100644 --- a/ic-pocket-canister-runtime/src/lib.rs +++ b/ic-pocket-canister-runtime/src/lib.rs @@ -5,6 +5,7 @@ #![forbid(missing_docs)] mod mock; +mod proxy; use async_trait::async_trait; use candid::{decode_one, encode_args, utils::ArgumentEncoder, CandidType, Principal}; @@ -76,6 +77,7 @@ pub struct PocketIcRuntime<'a> { // the `Runtime::update_call` method using interior mutability. // This is necessary since `Runtime::update_call` takes an immutable reference to the runtime. mocks: Option>>, + proxy_canister_id: Option, } impl<'a> PocketIcRuntime<'a> { @@ -86,6 +88,7 @@ impl<'a> PocketIcRuntime<'a> { env, caller, mocks: None, + proxy_canister_id: None, } } @@ -138,26 +141,26 @@ impl<'a> PocketIcRuntime<'a> { self.mocks = Some(Mutex::new(Box::new(mocks))); self } -} -impl<'a> AsRef for PocketIcRuntime<'a> { - fn as_ref(&self) -> &'a PocketIc { - self.env + /// Route update calls through a [proxy canister](https://github.com/dfinity/proxy-canister) + /// to attach cycles to them. + /// + /// When a proxy canister is configured, all `update_call` requests are forwarded through + /// the proxy canister, which attaches the specified cycles before calling the target canister. + /// Query calls are not affected and go directly to the target canister. + pub fn with_proxy_canister(mut self, proxy_canister_id: Principal) -> Self { + self.proxy_canister_id = Some(proxy_canister_id); + self } -} -#[async_trait] -impl Runtime for PocketIcRuntime<'_> { - async fn update_call( + async fn submit_and_await_call( &self, id: Principal, method: &str, args: In, - _cycles: u128, - ) -> Result + ) -> Result, IcError> where In: ArgumentEncoder + Send, - Out: CandidType + DeserializeOwned, { let message_id = self .env @@ -180,8 +183,38 @@ impl Runtime for PocketIcRuntime<'_> { } else { self.env.await_call(message_id).await } - .map(decode_call_response) - .map_err(parse_reject_response)? + .map_err(parse_reject_response) + } +} + +impl<'a> AsRef for PocketIcRuntime<'a> { + fn as_ref(&self) -> &'a PocketIc { + self.env + } +} + +#[async_trait] +impl Runtime for PocketIcRuntime<'_> { + async fn update_call( + &self, + id: Principal, + method: &str, + args: In, + cycles: u128, + ) -> Result + where + In: ArgumentEncoder + Send, + Out: CandidType + DeserializeOwned, + { + let bytes = match self.proxy_canister_id { + Some(proxy_id) => { + let proxy_args = proxy::ProxyArgs::new(id, method, args, cycles); + let response = self.submit_and_await_call(proxy_id, "proxy", (proxy_args,)).await?; + proxy::decode_response(response)? + } + None => self.submit_and_await_call(id, method, args).await?, + }; + decode_call_response(bytes) } async fn query_call( @@ -194,7 +227,8 @@ impl Runtime for PocketIcRuntime<'_> { In: ArgumentEncoder + Send, Out: CandidType + DeserializeOwned, { - self.env + let bytes = self + .env .query_call( id, self.caller, @@ -202,8 +236,8 @@ impl Runtime for PocketIcRuntime<'_> { encode_args(args).unwrap_or_else(panic_when_encode_fails), ) .await - .map(decode_call_response) - .map_err(parse_reject_response)? + .map_err(parse_reject_response)?; + decode_call_response(bytes) } } diff --git a/ic-pocket-canister-runtime/src/proxy.rs b/ic-pocket-canister-runtime/src/proxy.rs new file mode 100644 index 0000000..3809ae9 --- /dev/null +++ b/ic-pocket-canister-runtime/src/proxy.rs @@ -0,0 +1,77 @@ +//! Proxy canister types for routing update calls through a proxy to attach cycles. + +use candid::{decode_one, encode_args, utils::ArgumentEncoder, CandidType, Deserialize, Principal}; +use ic_canister_runtime::IcError; +use ic_error_types::RejectCode; +use serde::Serialize; + +/// Arguments for calling the proxy canister. +#[derive(CandidType, Serialize, Deserialize, Debug, Clone)] +pub struct ProxyArgs { + canister_id: Principal, + method: String, + #[serde(with = "serde_bytes")] + args: Vec, + cycles: u128, +} + +impl ProxyArgs { + pub fn new( + canister_id: Principal, + method: impl ToString, + args: In, + cycles: u128, + ) -> Self { + Self { + canister_id, + method: method.to_string(), + args: encode_args(args).unwrap_or_else(panic_when_encode_fails), + cycles, + } + } +} + +#[derive(CandidType, Serialize, Deserialize, Debug, Clone)] +struct ProxySucceed { + #[serde(with = "serde_bytes")] + result: Vec, +} + +#[derive(CandidType, Serialize, Deserialize, Debug, Clone)] +enum ProxyError { + InsufficientCycles { available: u128, required: u128 }, + CallFailed { reason: String }, + UnauthorizedUser, +} + +pub fn decode_response(bytes: Vec) -> Result, IcError> { + let result: Result = + decode_one(&bytes).map_err(|e| IcError::CandidDecodeFailed { + message: format!("failed to decode proxy response: {}", e), + })?; + + match result { + Ok(ProxySucceed { result }) => Ok(result), + Err(error) => match error { + ProxyError::UnauthorizedUser => Err(IcError::CallRejected { + code: RejectCode::SysFatal, + message: "Unauthorized caller!".to_string(), + }), + ProxyError::InsufficientCycles { + available, + required, + } => Err(IcError::InsufficientLiquidCycleBalance { + available, + required, + }), + ProxyError::CallFailed { reason } => Err(IcError::CallRejected { + code: RejectCode::SysFatal, + message: reason, + }), + }, + } +} + +fn panic_when_encode_fails(err: candid::error::Error) -> Vec { + panic!("failed to encode args: {err}") +} diff --git a/test_fixtures/Cargo.toml b/test_fixtures/Cargo.toml index c83a3fc..0e834a0 100644 --- a/test_fixtures/Cargo.toml +++ b/test_fixtures/Cargo.toml @@ -10,4 +10,5 @@ ic-management-canister-types = { workspace = true } ic-pocket-canister-runtime = { workspace = true } ic-test-utilities-load-wasm = { workspace = true } pocket-ic = { workspace = true } +reqwest = { workspace = true } serde = { workspace = true } diff --git a/test_fixtures/src/lib.rs b/test_fixtures/src/lib.rs index dbb6bc9..c9e23c7 100644 --- a/test_fixtures/src/lib.rs +++ b/test_fixtures/src/lib.rs @@ -1,17 +1,19 @@ use candid::{utils::ArgumentEncoder, CandidType, Encode, Principal}; -use ic_canister_runtime::Runtime; +use ic_canister_runtime::{IcError, Runtime}; use ic_management_canister_types::{CanisterId, CanisterSettings}; use ic_pocket_canister_runtime::PocketIcRuntime; use pocket_ic::{nonblocking::PocketIc, PocketIcBuilder}; use serde::de::DeserializeOwned; -use std::{env::var, path::PathBuf, sync::Arc}; +use std::{env::var, fs, path::PathBuf, sync::Arc}; pub struct Setup { env: Arc, canister_id: CanisterId, + proxy_canister_id: Option, } impl Setup { + pub const DEFAULT_CALLER: Principal = Principal::from_slice(&[0x9d, 0xf7, 0x01]); pub const DEFAULT_CONTROLLER: Principal = Principal::from_slice(&[0x9d, 0xf7, 0x02]); pub async fn new(canister_binary_name: &str) -> Self { @@ -46,14 +48,56 @@ impl Setup { Self { env: Arc::new(env), canister_id, + proxy_canister_id: None, + } + } + + pub async fn with_proxy(self) -> Self { + let Setup { + env, + canister_id, + proxy_canister_id, + } = self; + assert!(proxy_canister_id.is_none(), "Proxy canister already setup"); + + let proxy_canister_id = env + .create_canister_with_settings( + None, + Some(CanisterSettings { + // Only controllers have access to the proxy service, so we also allow + // the default caller + controllers: Some(vec![Self::DEFAULT_CONTROLLER, Setup::DEFAULT_CALLER]), + ..CanisterSettings::default() + }), + ) + .await; + env.add_cycles(proxy_canister_id, u64::MAX as u128).await; + + env.install_canister( + proxy_canister_id, + proxy_wasm().await, + Encode!().unwrap(), + Some(Self::DEFAULT_CONTROLLER), + ) + .await; + + Self { + env, + canister_id, + proxy_canister_id: Some(proxy_canister_id), } } pub fn runtime(&self) -> PocketIcRuntime<'_> { - PocketIcRuntime::new(self.env.as_ref(), Principal::anonymous()) + let runtime = PocketIcRuntime::new(self.env.as_ref(), Self::DEFAULT_CALLER); + if let Some(proxy_canister_id) = self.proxy_canister_id { + runtime.with_proxy_canister(proxy_canister_id) + } else { + runtime + } } - pub fn canister(&self) -> Canister<'_> { + pub fn canister(&self) -> Canister> { Canister { runtime: self.runtime(), id: self.canister_id, @@ -61,22 +105,49 @@ impl Setup { } } -pub struct Canister<'a> { - runtime: PocketIcRuntime<'a>, +pub struct Canister { + runtime: R, id: CanisterId, } -impl Canister<'_> { +impl Canister { pub async fn update_call(&self, method: &str, args: In) -> Out where In: ArgumentEncoder + Send, Out: CandidType + DeserializeOwned, { - self.runtime - .update_call(self.id, method, args, 0) + self.update_call_with_cycles(method, args, 0).await + } + + pub async fn update_call_with_cycles( + &self, + method: &str, + args: In, + cycles: u128, + ) -> Out + where + In: ArgumentEncoder + Send, + Out: CandidType + DeserializeOwned, + { + self.try_update_call_with_cycles(method, args, cycles) .await .expect("Update call failed") } + + pub async fn try_update_call_with_cycles( + &self, + method: &str, + args: In, + cycles: u128, + ) -> Result + where + In: ArgumentEncoder + Send, + Out: CandidType + DeserializeOwned, + { + self.runtime + .update_call(self.id, method, args, cycles) + .await + } } pub fn canister_wasm(canister_binary_name: &str) -> Vec { @@ -86,3 +157,29 @@ pub fn canister_wasm(canister_binary_name: &str) -> Vec { &[], ) } + +async fn proxy_wasm() -> Vec { + const DEFAULT_PATH: &str = "../../test_fixtures/wasms/proxy.wasm"; + const DOWNLOAD_URL: &str = + "https://github.com/dfinity/proxy-canister/releases/download/v0.1.0/proxy.wasm"; + + let path = option_env!("PROXY_CANISTER_WASM_PATH") + .map(PathBuf::from) + .unwrap_or(PathBuf::from(var("CARGO_MANIFEST_DIR").unwrap()).join(DEFAULT_PATH)); + + if let Ok(wasm) = fs::read(&path) { + return wasm; + } + + let bytes = reqwest::get(DOWNLOAD_URL) + .await + .unwrap_or_else(|e| panic!("Failed to fetch canister WASM: {e:?}")) + .bytes() + .await + .unwrap_or_else(|e| panic!("Failed to read bytes from canister WASM: {e:?}")) + .to_vec(); + + fs::write(&path, &bytes).expect("Failed to save downloaded file"); + + bytes +} From 7eeb64f424459177c2580dc84271f3bbe04a2be0 Mon Sep 17 00:00:00 2001 From: Louis Pahlavi Date: Tue, 10 Mar 2026 14:42:51 +0100 Subject: [PATCH 2/6] Clippy --- ic-pocket-canister-runtime/src/lib.rs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/ic-pocket-canister-runtime/src/lib.rs b/ic-pocket-canister-runtime/src/lib.rs index 46813ee..e50e1c9 100644 --- a/ic-pocket-canister-runtime/src/lib.rs +++ b/ic-pocket-canister-runtime/src/lib.rs @@ -209,7 +209,9 @@ impl Runtime for PocketIcRuntime<'_> { let bytes = match self.proxy_canister_id { Some(proxy_id) => { let proxy_args = proxy::ProxyArgs::new(id, method, args, cycles); - let response = self.submit_and_await_call(proxy_id, "proxy", (proxy_args,)).await?; + let response = self + .submit_and_await_call(proxy_id, "proxy", (proxy_args,)) + .await?; proxy::decode_response(response)? } None => self.submit_and_await_call(id, method, args).await?, From caa47309fe471596730b150f05b5360cb4feed02 Mon Sep 17 00:00:00 2001 From: Louis Pahlavi Date: Tue, 10 Mar 2026 18:09:50 +0100 Subject: [PATCH 3/6] Remove `panic_when_encode_fails` method --- ic-pocket-canister-runtime/src/lib.rs | 8 ++++---- ic-pocket-canister-runtime/src/proxy.rs | 9 +++------ 2 files changed, 7 insertions(+), 10 deletions(-) diff --git a/ic-pocket-canister-runtime/src/lib.rs b/ic-pocket-canister-runtime/src/lib.rs index e50e1c9..51c783d 100644 --- a/ic-pocket-canister-runtime/src/lib.rs +++ b/ic-pocket-canister-runtime/src/lib.rs @@ -168,7 +168,7 @@ impl<'a> PocketIcRuntime<'a> { id, self.caller, method, - encode_args(args).unwrap_or_else(panic_when_encode_fails), + encode_args_or_panic(args), ) .await .map_err(parse_reject_response)?; @@ -235,7 +235,7 @@ impl Runtime for PocketIcRuntime<'_> { id, self.caller, method, - encode_args(args).unwrap_or_else(panic_when_encode_fails), + encode_args_or_panic(args), ) .await .map_err(parse_reject_response)?; @@ -316,8 +316,8 @@ where }) } -fn panic_when_encode_fails(err: candid::error::Error) -> Vec { - panic!("failed to encode args: {err}") +fn encode_args_or_panic(arguments: Tuple) -> Vec { + encode_args(arguments).unwrap_or_else(|e| panic!("failed to encode args: {e}")) } async fn tick_until_http_requests(env: &PocketIc) -> Vec { diff --git a/ic-pocket-canister-runtime/src/proxy.rs b/ic-pocket-canister-runtime/src/proxy.rs index 3809ae9..94938dc 100644 --- a/ic-pocket-canister-runtime/src/proxy.rs +++ b/ic-pocket-canister-runtime/src/proxy.rs @@ -1,6 +1,7 @@ //! Proxy canister types for routing update calls through a proxy to attach cycles. -use candid::{decode_one, encode_args, utils::ArgumentEncoder, CandidType, Deserialize, Principal}; +use super::encode_args_or_panic; +use candid::{decode_one, utils::ArgumentEncoder, CandidType, Deserialize, Principal}; use ic_canister_runtime::IcError; use ic_error_types::RejectCode; use serde::Serialize; @@ -25,7 +26,7 @@ impl ProxyArgs { Self { canister_id, method: method.to_string(), - args: encode_args(args).unwrap_or_else(panic_when_encode_fails), + args: encode_args_or_panic(args), cycles, } } @@ -71,7 +72,3 @@ pub fn decode_response(bytes: Vec) -> Result, IcError> { }, } } - -fn panic_when_encode_fails(err: candid::error::Error) -> Vec { - panic!("failed to encode args: {err}") -} From d1d4de2743d33080d47995de94cf1592c911eeac Mon Sep 17 00:00:00 2001 From: Louis Pahlavi Date: Tue, 10 Mar 2026 18:11:00 +0100 Subject: [PATCH 4/6] Use `Into` for trait bound --- examples/http_canister/src/main.rs | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/examples/http_canister/src/main.rs b/examples/http_canister/src/main.rs index 3170132..6982f81 100644 --- a/examples/http_canister/src/main.rs +++ b/examples/http_canister/src/main.rs @@ -62,13 +62,9 @@ pub async fn infinite_loop_make_http_post_request() -> String { } } -fn http_client( +fn http_client> + Clone>( cycles_charging_policy: C, -) -> impl Service>, Response = http::Response>, Error = BoxError> -where - C: CyclesChargingPolicy + Clone, - ::Error: std::error::Error + Send + Sync + 'static, -{ +) -> impl Service>, Response = http::Response>, Error = BoxError> { ServiceBuilder::new() // Print request, response and errors to the console .layer( From b966e417a8275bfcc731bec8fed2e23fe2d3b2d7 Mon Sep 17 00:00:00 2001 From: Louis Pahlavi Date: Tue, 10 Mar 2026 18:17:30 +0100 Subject: [PATCH 5/6] Download proxy WASM to `target/test-artifacts` --- .github/workflows/ci.yml | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index b9933f5..0927ff4 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -78,9 +78,10 @@ jobs: - name: 'Download proxy canister WASM' run: | - mkdir -p $HOME/wasm - curl -L "https://github.com/dfinity/proxy-canister/releases/download/${PROXY_CANISTER_VERSION}/proxy.wasm" -o $HOME/wasm/proxy.wasm - echo "PROXY_CANISTER_WASM_PATH=$HOME/wasm/proxy.wasm" >> $GITHUB_ENV + PROXY_CANISTER_WASM_PATH=$GITHUB_WORKSPACE/target/test-artifacts + mkdir -p $PROXY_CANISTER_WASM_PATH + curl -L "https://github.com/dfinity/proxy-canister/releases/download/${PROXY_CANISTER_VERSION}/proxy.wasm" -o "${PROXY_CANISTER_WASM_PATH}/proxy.wasm" + echo "PROXY_CANISTER_WASM_PATH=${PROXY_CANISTER_WASM_PATH}/proxy.wasm" >> $GITHUB_ENV - name: 'Install PocketIC server' uses: dfinity/pocketic@main From 69a07bee2f9c7a173aa0b190002ef100ab1a7ee5 Mon Sep 17 00:00:00 2001 From: Louis Pahlavi Date: Tue, 10 Mar 2026 18:25:54 +0100 Subject: [PATCH 6/6] Use `curl` instead of `reqwest` to download proxy WASM --- Cargo.lock | 342 +------------------------- Cargo.toml | 1 - ic-pocket-canister-runtime/src/lib.rs | 14 +- test_fixtures/Cargo.toml | 1 - test_fixtures/src/lib.rs | 19 +- 5 files changed, 11 insertions(+), 366 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index ed5abba..1cb5da4 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -137,28 +137,6 @@ version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8" -[[package]] -name = "aws-lc-rs" -version = "1.16.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "94bffc006df10ac2a68c83692d734a465f8ee6c5b384d8545a636f81d858f4bf" -dependencies = [ - "aws-lc-sys", - "zeroize", -] - -[[package]] -name = "aws-lc-sys" -version = "0.38.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4321e568ed89bb5a7d291a7f37997c2c0df89809d7b6d12062c81ddb54aa782e" -dependencies = [ - "cc", - "cmake", - "dunce", - "fs_extra", -] - [[package]] name = "backoff" version = "0.4.0" @@ -388,17 +366,9 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "aebf35691d1bfb0ac386a69bac2fde4dd276fb618cf8bf4f5318fe285e821bb2" dependencies = [ "find-msvc-tools", - "jobserver", - "libc", "shlex", ] -[[package]] -name = "cesu8" -version = "1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6d43a04d8753f35258c91f8ec639f792891f748a1edbd759cf1dcea3382ad83c" - [[package]] name = "cfg-if" version = "1.0.4" @@ -438,25 +408,6 @@ dependencies = [ "half 2.7.1", ] -[[package]] -name = "cmake" -version = "0.1.57" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "75443c44cd6b379beb8c5b45d85d0773baf31cce901fe7bb252f4eff3008ef7d" -dependencies = [ - "cc", -] - -[[package]] -name = "combine" -version = "4.6.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ba5a308b75df32fe02788e748662718f03fde005016435c444eea572398219fd" -dependencies = [ - "bytes", - "memchr", -] - [[package]] name = "concurrent-queue" version = "2.5.0" @@ -481,16 +432,6 @@ dependencies = [ "unicode-segmentation", ] -[[package]] -name = "core-foundation" -version = "0.9.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "91e195e091a93c46f7102ec7818a2aa394e1e1771c3ab4825963fa03e45afb8f" -dependencies = [ - "core-foundation-sys", - "libc", -] - [[package]] name = "core-foundation" version = "0.10.1" @@ -725,12 +666,6 @@ dependencies = [ "syn 2.0.117", ] -[[package]] -name = "dunce" -version = "1.0.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "92773504d58c093f6de2459af4af33faa518c13451eb8f2b5698ed3d36e7c813" - [[package]] name = "dyn-clone" version = "1.0.20" @@ -819,15 +754,6 @@ dependencies = [ "zeroize", ] -[[package]] -name = "encoding_rs" -version = "0.8.35" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "75030f3c4f45dafd7586dd6780965a8c7e8e285a5ecb86713e63a79c5b2766f3" -dependencies = [ - "cfg-if", -] - [[package]] name = "equivalent" version = "1.0.2" @@ -950,12 +876,6 @@ dependencies = [ "percent-encoding", ] -[[package]] -name = "fs_extra" -version = "1.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "42703706b716c37f96a77aea830392ad231f44c9e9a67872fa5548707e11b11c" - [[package]] name = "futures-channel" version = "0.3.32" @@ -1287,11 +1207,9 @@ dependencies = [ "percent-encoding", "pin-project-lite", "socket2", - "system-configuration", "tokio", "tower-service", "tracing", - "windows-registry", ] [[package]] @@ -1329,7 +1247,7 @@ dependencies = [ "pkcs8", "rand 0.8.5", "rangemap", - "reqwest 0.12.28", + "reqwest", "sec1", "serde", "serde_bytes", @@ -1742,38 +1660,6 @@ version = "1.0.17" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "92ecc6618181def0457392ccd0ee51198e065e016d1d527a7ac1b6dc7c1f09d2" -[[package]] -name = "jni" -version = "0.21.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1a87aa2bb7d2af34197c04845522473242e1aa17c12f4935d5856491a7fb8c97" -dependencies = [ - "cesu8", - "cfg-if", - "combine", - "jni-sys", - "log", - "thiserror 1.0.69", - "walkdir", - "windows-sys 0.45.0", -] - -[[package]] -name = "jni-sys" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8eaf4bc02d17cbdd7ff4c7438cafcdf7fb9a4613313ad11b4f8fefe7d3fa0130" - -[[package]] -name = "jobserver" -version = "0.1.34" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9afb3de4395d6b3e67a780b6de64b51c978ecf11cb9a462c66be7d4ca9039d33" -dependencies = [ - "getrandom 0.3.4", - "libc", -] - [[package]] name = "js-sys" version = "0.3.85" @@ -2172,7 +2058,7 @@ dependencies = [ "ic-certification", "ic-management-canister-types", "ic-transport-types 0.40.1", - "reqwest 0.12.28", + "reqwest", "schemars", "semver", "serde", @@ -2316,7 +2202,6 @@ version = "0.11.13" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f1906b49b0c3bc04b5fe5d86a77925ae6524a19b816ae38ce1e426255f1d8a31" dependencies = [ - "aws-lc-rs", "bytes", "getrandom 0.3.4", "lru-slab", @@ -2512,44 +2397,6 @@ dependencies = [ "webpki-roots", ] -[[package]] -name = "reqwest" -version = "0.13.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ab3f43e3283ab1488b624b44b0e988d0acea0b3214e694730a055cb6b2efa801" -dependencies = [ - "base64 0.22.1", - "bytes", - "encoding_rs", - "futures-core", - "h2", - "http", - "http-body", - "http-body-util", - "hyper", - "hyper-rustls", - "hyper-util", - "js-sys", - "log", - "mime", - "percent-encoding", - "pin-project-lite", - "quinn", - "rustls", - "rustls-pki-types", - "rustls-platform-verifier", - "sync_wrapper", - "tokio", - "tokio-rustls", - "tower", - "tower-http", - "tower-service", - "url", - "wasm-bindgen", - "wasm-bindgen-futures", - "web-sys", -] - [[package]] name = "rfc6979" version = "0.4.0" @@ -2608,7 +2455,6 @@ version = "0.23.36" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c665f33d38cea657d9614f766881e4d510e0eda4239891eea56b4cadcf01801b" dependencies = [ - "aws-lc-rs", "once_cell", "ring", "rustls-pki-types", @@ -2639,40 +2485,12 @@ dependencies = [ "zeroize", ] -[[package]] -name = "rustls-platform-verifier" -version = "0.6.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1d99feebc72bae7ab76ba994bb5e121b8d83d910ca40b36e0921f53becc41784" -dependencies = [ - "core-foundation 0.10.1", - "core-foundation-sys", - "jni", - "log", - "once_cell", - "rustls", - "rustls-native-certs", - "rustls-platform-verifier-android", - "rustls-webpki", - "security-framework", - "security-framework-sys", - "webpki-root-certs", - "windows-sys 0.61.2", -] - -[[package]] -name = "rustls-platform-verifier-android" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f87165f0995f63a9fbeea62b64d10b4d9d8e78ec6d7d51fb2125fda7bb36788f" - [[package]] name = "rustls-webpki" version = "0.103.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d7df23109aa6c1567d1c575b9952556388da57401e4ace1d15f79eedad0d8f53" dependencies = [ - "aws-lc-rs", "ring", "rustls-pki-types", "untrusted", @@ -2702,15 +2520,6 @@ version = "1.0.23" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9774ba4a74de5f7b1c1451ed6cd5285a32eddb5cccb8cc655a4e50009e06477f" -[[package]] -name = "same-file" -version = "1.0.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502" -dependencies = [ - "winapi-util", -] - [[package]] name = "schannel" version = "0.1.28" @@ -2771,7 +2580,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d17b898a6d6948c3a8ee4372c17cb384f90d2e6e912ef00895b14fd7ab54ec38" dependencies = [ "bitflags", - "core-foundation 0.10.1", + "core-foundation", "core-foundation-sys", "libc", "security-framework-sys", @@ -3146,27 +2955,6 @@ dependencies = [ "syn 2.0.117", ] -[[package]] -name = "system-configuration" -version = "0.7.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a13f3d0daba03132c0aa9767f98351b3488edc2c100cda2d2ec2b04f3d8d3c8b" -dependencies = [ - "bitflags", - "core-foundation 0.9.4", - "system-configuration-sys", -] - -[[package]] -name = "system-configuration-sys" -version = "0.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8e1d1b10ced5ca923a1fcb8d03e96b8d3268065d724548c0211415ff6ac6bac4" -dependencies = [ - "core-foundation-sys", - "libc", -] - [[package]] name = "tempfile" version = "3.26.0" @@ -3190,7 +2978,6 @@ dependencies = [ "ic-pocket-canister-runtime", "ic-test-utilities-load-wasm", "pocket-ic", - "reqwest 0.13.2", "serde", ] @@ -3591,16 +3378,6 @@ dependencies = [ "libc", ] -[[package]] -name = "walkdir" -version = "2.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "29790946404f91d9c5d06f9874efddea1dc06c5efe94541a7d6863108e3a5e4b" -dependencies = [ - "same-file", - "winapi-util", -] - [[package]] name = "want" version = "0.3.1" @@ -3760,15 +3537,6 @@ dependencies = [ "wasm-bindgen", ] -[[package]] -name = "webpki-root-certs" -version = "1.0.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "804f18a4ac2676ffb4e8b5b5fa9ae38af06df08162314f96a68d2a363e21a8ca" -dependencies = [ - "rustls-pki-types", -] - [[package]] name = "webpki-roots" version = "1.0.6" @@ -3778,59 +3546,12 @@ dependencies = [ "rustls-pki-types", ] -[[package]] -name = "winapi-util" -version = "0.1.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c2a7b1c03c876122aa43f3020e6c3c3ee5c05081c9a00739faf7503aeba10d22" -dependencies = [ - "windows-sys 0.61.2", -] - [[package]] name = "windows-link" version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5" -[[package]] -name = "windows-registry" -version = "0.6.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "02752bf7fbdcce7f2a27a742f798510f3e5ad88dbe84871e5168e2120c3d5720" -dependencies = [ - "windows-link", - "windows-result", - "windows-strings", -] - -[[package]] -name = "windows-result" -version = "0.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7781fa89eaf60850ac3d2da7af8e5242a5ea78d1a11c49bf2910bb5a73853eb5" -dependencies = [ - "windows-link", -] - -[[package]] -name = "windows-strings" -version = "0.5.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7837d08f69c77cf6b07689544538e017c1bfcf57e34b4c0ff58e6c2cd3b37091" -dependencies = [ - "windows-link", -] - -[[package]] -name = "windows-sys" -version = "0.45.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "75283be5efb2831d37ea142365f009c02ec203cd29a3ebecbc093d52315b66d0" -dependencies = [ - "windows-targets 0.42.2", -] - [[package]] name = "windows-sys" version = "0.52.0" @@ -3867,21 +3588,6 @@ dependencies = [ "windows-link", ] -[[package]] -name = "windows-targets" -version = "0.42.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8e5180c00cd44c9b1c88adb3693291f1cd93605ded80c250a75d472756b4d071" -dependencies = [ - "windows_aarch64_gnullvm 0.42.2", - "windows_aarch64_msvc 0.42.2", - "windows_i686_gnu 0.42.2", - "windows_i686_msvc 0.42.2", - "windows_x86_64_gnu 0.42.2", - "windows_x86_64_gnullvm 0.42.2", - "windows_x86_64_msvc 0.42.2", -] - [[package]] name = "windows-targets" version = "0.52.6" @@ -3915,12 +3621,6 @@ dependencies = [ "windows_x86_64_msvc 0.53.1", ] -[[package]] -name = "windows_aarch64_gnullvm" -version = "0.42.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "597a5118570b68bc08d8d59125332c54f1ba9d9adeedeef5b99b02ba2b0698f8" - [[package]] name = "windows_aarch64_gnullvm" version = "0.52.6" @@ -3933,12 +3633,6 @@ version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a9d8416fa8b42f5c947f8482c43e7d89e73a173cead56d044f6a56104a6d1b53" -[[package]] -name = "windows_aarch64_msvc" -version = "0.42.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e08e8864a60f06ef0d0ff4ba04124db8b0fb3be5776a5cd47641e942e58c4d43" - [[package]] name = "windows_aarch64_msvc" version = "0.52.6" @@ -3951,12 +3645,6 @@ version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b9d782e804c2f632e395708e99a94275910eb9100b2114651e04744e9b125006" -[[package]] -name = "windows_i686_gnu" -version = "0.42.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c61d927d8da41da96a81f029489353e68739737d3beca43145c8afec9a31a84f" - [[package]] name = "windows_i686_gnu" version = "0.52.6" @@ -3981,12 +3669,6 @@ version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fa7359d10048f68ab8b09fa71c3daccfb0e9b559aed648a8f95469c27057180c" -[[package]] -name = "windows_i686_msvc" -version = "0.42.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "44d840b6ec649f480a41c8d80f9c65108b92d89345dd94027bfe06ac444d1060" - [[package]] name = "windows_i686_msvc" version = "0.52.6" @@ -3999,12 +3681,6 @@ version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1e7ac75179f18232fe9c285163565a57ef8d3c89254a30685b57d83a38d326c2" -[[package]] -name = "windows_x86_64_gnu" -version = "0.42.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8de912b8b8feb55c064867cf047dda097f92d51efad5b491dfb98f6bbb70cb36" - [[package]] name = "windows_x86_64_gnu" version = "0.52.6" @@ -4017,12 +3693,6 @@ version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9c3842cdd74a865a8066ab39c8a7a473c0778a3f29370b5fd6b4b9aa7df4a499" -[[package]] -name = "windows_x86_64_gnullvm" -version = "0.42.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "26d41b46a36d453748aedef1486d5c7a85db22e56aff34643984ea85514e94a3" - [[package]] name = "windows_x86_64_gnullvm" version = "0.52.6" @@ -4035,12 +3705,6 @@ version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0ffa179e2d07eee8ad8f57493436566c7cc30ac536a3379fdf008f47f6bb7ae1" -[[package]] -name = "windows_x86_64_msvc" -version = "0.42.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9aec5da331524158c6d1a4ac0ab1541149c0b9505fde06423b02f5ef0106b9f0" - [[package]] name = "windows_x86_64_msvc" version = "0.52.6" diff --git a/Cargo.toml b/Cargo.toml index dbcf788..228447b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -43,7 +43,6 @@ pin-project = "1.1.11" pocket-ic = "12.0.0" proptest = "1.10.0" regex-lite = "0.1.9" -reqwest = "0.13.2" serde = "1.0" serde_bytes = "0.11.19" serde_json = "1.0" diff --git a/ic-pocket-canister-runtime/src/lib.rs b/ic-pocket-canister-runtime/src/lib.rs index 51c783d..7034a65 100644 --- a/ic-pocket-canister-runtime/src/lib.rs +++ b/ic-pocket-canister-runtime/src/lib.rs @@ -164,12 +164,7 @@ impl<'a> PocketIcRuntime<'a> { { let message_id = self .env - .submit_call( - id, - self.caller, - method, - encode_args_or_panic(args), - ) + .submit_call(id, self.caller, method, encode_args_or_panic(args)) .await .map_err(parse_reject_response)?; if let Some(mock) = &self.mocks { @@ -231,12 +226,7 @@ impl Runtime for PocketIcRuntime<'_> { { let bytes = self .env - .query_call( - id, - self.caller, - method, - encode_args_or_panic(args), - ) + .query_call(id, self.caller, method, encode_args_or_panic(args)) .await .map_err(parse_reject_response)?; decode_call_response(bytes) diff --git a/test_fixtures/Cargo.toml b/test_fixtures/Cargo.toml index 0e834a0..c83a3fc 100644 --- a/test_fixtures/Cargo.toml +++ b/test_fixtures/Cargo.toml @@ -10,5 +10,4 @@ ic-management-canister-types = { workspace = true } ic-pocket-canister-runtime = { workspace = true } ic-test-utilities-load-wasm = { workspace = true } pocket-ic = { workspace = true } -reqwest = { workspace = true } serde = { workspace = true } diff --git a/test_fixtures/src/lib.rs b/test_fixtures/src/lib.rs index c9e23c7..fb924c1 100644 --- a/test_fixtures/src/lib.rs +++ b/test_fixtures/src/lib.rs @@ -167,19 +167,12 @@ async fn proxy_wasm() -> Vec { .map(PathBuf::from) .unwrap_or(PathBuf::from(var("CARGO_MANIFEST_DIR").unwrap()).join(DEFAULT_PATH)); - if let Ok(wasm) = fs::read(&path) { - return wasm; + if !std::path::Path::new(&path).exists() { + std::process::Command::new("curl") + .args(["-L", "-o", path.to_str().unwrap(), DOWNLOAD_URL]) + .status() + .unwrap_or_else(|e| panic!("Failed to download canister WASM: {e:?}")); } - let bytes = reqwest::get(DOWNLOAD_URL) - .await - .unwrap_or_else(|e| panic!("Failed to fetch canister WASM: {e:?}")) - .bytes() - .await - .unwrap_or_else(|e| panic!("Failed to read bytes from canister WASM: {e:?}")) - .to_vec(); - - fs::write(&path, &bytes).expect("Failed to save downloaded file"); - - bytes + fs::read(&path).unwrap_or_else(|e| panic!("Failed to read proxy canister WASM: {e}")) }