diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index c2c8ad41..71fbe804 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -184,7 +184,7 @@ jobs: - name: 'Install PocketIC server' uses: dfinity/pocketic@main with: - pocket-ic-server-version: "9.0.1" + pocket-ic-server-version: "11.0.0" - name: 'Install Solana CLI' run: | diff --git a/Cargo.lock b/Cargo.lock index 7517e236..b3b32ffc 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -538,9 +538,9 @@ dependencies = [ [[package]] name = "candid" -version = "0.10.17" +version = "0.10.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eaac522d18020d5fbc8320ecb12a9b13b2137ae31133da2d42fa256a825507c4" +checksum = "8037a01ec09d6c06883a38bad4f47b8d06158ad360b841e0ae5707c9884dfaf6" dependencies = [ "anyhow", "binread", @@ -561,9 +561,9 @@ dependencies = [ [[package]] name = "candid_derive" -version = "0.10.17" +version = "0.10.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8a1b4fddbd462182050989068d53604a91a3d0f117c3c8316c6818023df00add" +checksum = "fb45f4d5eff3805598ee633dd80f8afb306c023249d34b5b7dfdc2080ea1df2e" dependencies = [ "lazy_static", "proc-macro2", @@ -592,9 +592,9 @@ dependencies = [ [[package]] name = "canhttp" -version = "0.2.1" +version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b30b89e93857ec22d9b5f11b1647cdf9bb28c328e1d5ae871ecba2d94e38f8fb" +checksum = "13f7072785f309714ab665889c3c73c9d64d976fdc02b8cadb9de13799f69053" dependencies = [ "assert_matches", "ciborium", @@ -615,14 +615,14 @@ dependencies = [ [[package]] name = "canlog" -version = "0.1.1" +version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6a49cd58324d235ecc1ef9e47d018c10dfe8b60d62ab001f2f2cfcef1505a081" +checksum = "bd21b4c7140d033fe006495a65ec5ae24d79219eca550a74e0ba140ff9aa71dc" dependencies = [ "candid", "canlog_derive", "ic-canister-log", - "ic-cdk", + "ic0", "regex", "serde", "serde_json", @@ -1939,37 +1939,60 @@ dependencies = [ "serde", ] +[[package]] +name = "ic-canister-runtime" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "01a81a68bc6825ab81142a4e176ee04d0cc73deeed69753603e90e57b3ddcd3f" +dependencies = [ + "async-trait", + "candid", + "ic-cdk", + "ic-error-types", + "serde", + "thiserror 2.0.16", +] + [[package]] name = "ic-cdk" -version = "0.17.2" +version = "0.19.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "95a7344f41493cbf591f13ae9f90181076f808a83af799815c3074b19c693d2e" +checksum = "818d6d5416a8f0212e1b132703b0da51e36c55f2b96677e96f2bbe7702e1bd85" dependencies = [ "candid", "ic-cdk-executor", "ic-cdk-macros", + "ic-error-types", + "ic-management-canister-types", "ic0", + "pin-project-lite", "serde", "serde_bytes", + "slotmap", + "thiserror 2.0.16", ] [[package]] name = "ic-cdk-executor" -version = "0.1.0" +version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "903057edd3d4ff4b3fe44a64eaee1ceb73f579ba29e3ded372b63d291d7c16c2" +checksum = "33716b730ded33690b8a704bff3533fda87d229e58046823647d28816e9bcee7" +dependencies = [ + "ic0", + "slotmap", + "smallvec", +] [[package]] name = "ic-cdk-macros" -version = "0.17.2" +version = "0.19.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "84cbaa50fa36d3e0616114becf81faa95a099e0d60948ed6978f30f1c77399fd" +checksum = "66dad91a214945cb3605bc9ef6901b87e2ac41e3624284c2cabba49d43aa4f43" dependencies = [ "candid", + "darling", "proc-macro2", "quote", - "serde", - "serde_tokenstream", "syn 2.0.106", ] @@ -2024,9 +2047,9 @@ dependencies = [ [[package]] name = "ic-management-canister-types" -version = "0.3.3" +version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ea7e5b8a0f7c3b320d9450ac950547db4f24a31601b5d398f9680b64427455d2" +checksum = "3149217e24186df3f13dc45eee14cdb3e5cad07d0b2b67bd53555c1c55462957" dependencies = [ "candid", "serde", @@ -2035,9 +2058,9 @@ dependencies = [ [[package]] name = "ic-metrics-assert" -version = "0.1.1" +version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "38a58acbcd7c46554a52b8340e4f326d57ebf5c2d9dc7722f30ccf3e67dcb1c9" +checksum = "68eeac14a82b6b7f7cff3cb496e1981b252b51075566801aeaf1e4478a9442ec" dependencies = [ "async-trait", "candid", @@ -2045,8 +2068,6 @@ dependencies = [ "ic-management-canister-types", "pocket-ic", "regex", - "serde", - "serde_bytes", ] [[package]] @@ -2107,9 +2128,9 @@ dependencies = [ [[package]] name = "ic0" -version = "0.23.0" +version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8de254dd67bbd58073e23dc1c8553ba12fa1dc610a19de94ad2bbcd0460c067f" +checksum = "1499d08fd5be8f790d477e1865d63bab6a8d748300e141270c4296e6d5fdd6bc" [[package]] name = "ic_bls12_381" @@ -3052,9 +3073,9 @@ checksum = "7edddbd0b52d732b21ad9a5fab5c704c14cd949e5e9a1ec5929a24fded1b904c" [[package]] name = "pocket-ic" -version = "9.0.2" +version = "11.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4e523c23bda9dc26ae989aab647b8bd805b54c72a3f2f00d668830d8b490c9c8" +checksum = "67394a1de5e9bd67e92eef90c9034fbd28f26cfcb64854f187f3979191d6380c" dependencies = [ "backoff", "base64 0.13.1", @@ -3066,6 +3087,7 @@ dependencies = [ "ic-transport-types", "reqwest", "schemars 0.8.22", + "semver", "serde", "serde_bytes", "serde_cbor", @@ -3468,9 +3490,9 @@ checksum = "caf4aa5b0f434c91fe5c7f1ecb6a5ece2130b02ad2a590589dda5146df959001" [[package]] name = "reqwest" -version = "0.12.23" +version = "0.12.24" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d429f34c8092b2d42c7c93cec323bb4adeb7c67698f70839adec842ec10c7ceb" +checksum = "9d0946410b9f7b082a427e4ef5c8ff541a88b357bc6c637c40db3a68ac70a36f" dependencies = [ "async-compression", "base64 0.22.1", @@ -3924,18 +3946,6 @@ dependencies = [ "syn 2.0.106", ] -[[package]] -name = "serde_tokenstream" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "64060d864397305347a78851c51588fd283767e7e7589829e8121d65512340f1" -dependencies = [ - "proc-macro2", - "quote", - "serde", - "syn 2.0.106", -] - [[package]] name = "serde_tuple" version = "1.1.3" @@ -4113,6 +4123,15 @@ dependencies = [ "erased-serde", ] +[[package]] +name = "slotmap" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dbff4acf519f630b3a3ddcfaea6c06b42174d9a44bc70c620e9ed1649d58b82a" +dependencies = [ + "version_check", +] + [[package]] name = "smallvec" version = "1.15.1" @@ -4144,8 +4163,8 @@ dependencies = [ "hex", "http 1.3.1", "ic-cdk", - "ic-error-types", "ic-http-types", + "ic-management-canister-types", "ic-metrics-encoder", "ic-stable-structures", "maplit", @@ -4182,9 +4201,10 @@ dependencies = [ "bincode", "candid", "derive_more", + "ic-canister-runtime", "ic-cdk", "ic-ed25519", - "ic-error-types", + "ic-management-canister-types", "serde", "serde_json", "sol_rpc_types", @@ -4214,7 +4234,7 @@ dependencies = [ "async-trait", "candid", "ic-agent", - "ic-cdk", + "ic-canister-runtime", "ic-error-types", "serde", "serde_json", @@ -4245,6 +4265,7 @@ dependencies = [ "canlog", "const_format", "futures", + "ic-canister-runtime", "ic-cdk", "ic-error-types", "ic-http-types", @@ -4294,6 +4315,7 @@ dependencies = [ "canlog", "derive_more", "ic-cdk", + "ic-management-canister-types", "proptest", "regex", "serde", diff --git a/Cargo.toml b/Cargo.toml index 07e9505a..169710dd 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -24,8 +24,8 @@ bincode = "1.3.3" bs58 = "0.5.1" candid = "0.10.14" candid_parser = "0.1.4" -canhttp = "0.2.1" -canlog = { version = "0.1.1", features = ["derive"] } +canhttp = "0.4.0" +canlog = { version = "0.2.0", features = ["derive"] } ciborium = "0.2.2" const_format = "0.2.34" derive_more = { version = "2.0.1", features = ["from", "into"] } @@ -36,12 +36,13 @@ getrandom = { version = "*", default-features = false, features = ["custom"] } hex = "0.4.3" http = "1.3.1" ic-agent = "0.40.1" -ic-cdk = "0.17.2" +ic-canister-runtime = "0.1.0" +ic-cdk = "0.19.0" ic-ed25519 = "0.2.0" ic-error-types = "0.2" ic-http-types = "0.1.0" -ic-management-canister-types = "0.3" -ic-metrics-assert = "0.1.1" +ic-management-canister-types = "0.5.0" +ic-metrics-assert = "0.3.0" ic-metrics-encoder = "1.1" ic-stable-structures = "0.6.9" ic-test-utilities-load-wasm = { git = "https://github.com/dfinity/ic", tag = "release-2025-01-23_03-04-base" } @@ -49,7 +50,7 @@ maplit = "1.0.2" minicbor = { version = "1.1.0", features = ["alloc", "derive"] } num = "0.4.3" num-traits = "0.2.19" -pocket-ic = "9.0.2" +pocket-ic = "11.0.0" proptest = "1.7.0" rand = { version = "0.9.2", default-features = false } rand_chacha = { version = "0.9.0", default-features = false } diff --git a/canister/Cargo.toml b/canister/Cargo.toml index 99930ef1..429467b5 100644 --- a/canister/Cargo.toml +++ b/canister/Cargo.toml @@ -30,8 +30,8 @@ derive_more = { workspace = true } hex = { workspace = true } http = { workspace = true } ic-cdk = { workspace = true } -ic-error-types = { workspace = true } ic-http-types = { workspace = true } +ic-management-canister-types = { workspace = true } ic-metrics-encoder = { workspace = true } ic-stable-structures = { workspace = true } maplit = { workspace = true } diff --git a/canister/src/constants.rs b/canister/src/constants.rs index 7b0d8638..34771a3f 100644 --- a/canister/src/constants.rs +++ b/canister/src/constants.rs @@ -1,3 +1,6 @@ +// The default value of `max_response_bytes` for HTTP outcalls is 2MB. +pub const DEFAULT_MAX_RESPONSE_BYTES: u64 = 2_000_000; + // Cycles (per node) which must be passed with each RPC request // as processing fee. pub const COLLATERAL_CYCLES_PER_NODE: u128 = 10_000_000; diff --git a/canister/src/http/errors.rs b/canister/src/http/errors.rs index 19d6e63d..9e69b381 100644 --- a/canister/src/http/errors.rs +++ b/canister/src/http/errors.rs @@ -1,4 +1,5 @@ use canhttp::{ + cycles::ChargeCallerError, http::{ json::{ ConsistentResponseIdFilterError, JsonRequestConversionError, @@ -7,11 +8,10 @@ use canhttp::{ FilterNonSuccessfulHttpResponseError, HttpRequestConversionError, HttpResponseConversionError, }, - CyclesAccountingError, HttpsOutcallError, IcError, + HttpsOutcallError, IcError, }; use derive_more::From; -use ic_error_types::RejectCode; -use sol_rpc_types::{HttpOutcallError, ProviderError, RpcError}; +use sol_rpc_types::{HttpOutcallError, LegacyRejectionCode, ProviderError, RpcError}; use thiserror::Error; #[derive(Clone, Debug, Error, From)] @@ -21,7 +21,7 @@ pub enum HttpClientError { #[error("unknown error (most likely sign of a bug): {0}")] NotHandledError(String), #[error("cycles accounting error: {0}")] - CyclesAccountingError(CyclesAccountingError), + CyclesAccountingError(ChargeCallerError), #[error("HTTP response was not successful: {0}")] UnsuccessfulHttpResponse(FilterNonSuccessfulHttpResponseError>), #[error("Error converting response to JSON: {0}")] @@ -49,44 +49,56 @@ impl From for HttpClientError { } } -impl From for RpcError { - fn from(error: HttpClientError) -> Self { +#[derive(Error, Clone, Debug, PartialEq, Eq)] +#[error(transparent)] +pub struct UnrecoverableError(IcError); + +impl TryFrom for RpcError { + type Error = UnrecoverableError; + + fn try_from(error: HttpClientError) -> Result { match error { - HttpClientError::IcError(IcError { code, message }) => { - use ic_cdk::api::call::RejectionCode as IcCdkRejectionCode; - let code = match code { - RejectCode::SysFatal => IcCdkRejectionCode::SysFatal, - RejectCode::SysTransient => IcCdkRejectionCode::SysTransient, - RejectCode::DestinationInvalid => IcCdkRejectionCode::DestinationInvalid, - RejectCode::CanisterReject => IcCdkRejectionCode::CanisterReject, - RejectCode::CanisterError => IcCdkRejectionCode::CanisterError, - RejectCode::SysUnknown => IcCdkRejectionCode::Unknown, - }; - RpcError::HttpOutcallError(HttpOutcallError::IcError { code, message }) + HttpClientError::IcError(IcError::CallRejected { code, message }) => { + Ok(RpcError::HttpOutcallError(HttpOutcallError::IcError { + code: LegacyRejectionCode::from(code), + message, + })) } - HttpClientError::NotHandledError(e) => RpcError::ValidationError(e), + HttpClientError::IcError(e @ IcError::InsufficientLiquidCycleBalance { .. }) => { + Err(UnrecoverableError(e)) + } + HttpClientError::NotHandledError(e) => Ok(RpcError::ValidationError(e)), HttpClientError::CyclesAccountingError( - CyclesAccountingError::InsufficientCyclesError { expected, received }, - ) => RpcError::ProviderError(ProviderError::TooFewCycles { expected, received }), + ChargeCallerError::InsufficientCyclesError { expected, received }, + ) => Ok(RpcError::ProviderError(ProviderError::TooFewCycles { + expected, + received, + })), HttpClientError::InvalidJsonResponse( JsonResponseConversionError::InvalidJsonResponse { status, body, parsing_error, }, - ) => RpcError::HttpOutcallError(HttpOutcallError::InvalidHttpJsonRpcResponse { - status, - body, - parsing_error: Some(parsing_error), - }), + ) => Ok(RpcError::HttpOutcallError( + HttpOutcallError::InvalidHttpJsonRpcResponse { + status, + body, + parsing_error: Some(parsing_error), + }, + )), HttpClientError::UnsuccessfulHttpResponse( FilterNonSuccessfulHttpResponseError::UnsuccessfulResponse(response), - ) => RpcError::HttpOutcallError(HttpOutcallError::InvalidHttpJsonRpcResponse { - status: response.status().as_u16(), - body: String::from_utf8_lossy(response.body()).to_string(), - parsing_error: None, - }), - HttpClientError::InvalidJsonResponseId(e) => RpcError::ValidationError(e.to_string()), + ) => Ok(RpcError::HttpOutcallError( + HttpOutcallError::InvalidHttpJsonRpcResponse { + status: response.status().as_u16(), + body: String::from_utf8_lossy(response.body()).to_string(), + parsing_error: None, + }, + )), + HttpClientError::InvalidJsonResponseId(e) => { + Ok(RpcError::ValidationError(e.to_string())) + } } } } diff --git a/canister/src/http/mod.rs b/canister/src/http/mod.rs index ed1a2bc0..0e610f16 100644 --- a/canister/src/http/mod.rs +++ b/canister/src/http/mod.rs @@ -5,11 +5,13 @@ use crate::{ constants::{COLLATERAL_CYCLES_PER_NODE, CONTENT_TYPE_VALUE}, http::errors::HttpClientError, logs::Priority, - memory::{next_request_id, read_state, State}, + memory::{next_request_id, read_state}, metrics::{MetricRpcCallResponse, MetricRpcHost, MetricRpcMethod}, }; +use canhttp::cycles::CyclesAccounting; use canhttp::{ convert::ConvertRequestLayer, + cycles::ChargeCaller, http::{ json::{ ConsistentResponseIdFilterError, CreateJsonRpcIdFilter, HttpJsonRpcRequest, @@ -21,11 +23,11 @@ use canhttp::{ }, observability::ObservabilityLayer, retry::DoubleMaxResponseBytes, - ConvertServiceBuilder, CyclesAccounting, CyclesChargingPolicy, HttpsOutcallError, IcError, + ConvertServiceBuilder, HttpsOutcallError, IcError, }; use canlog::log; use http::{header::CONTENT_TYPE, HeaderValue}; -use ic_cdk::api::management_canister::http_request::CanisterHttpRequestArgument; +use ic_management_canister_types::HttpRequestArgs as IcHttpRequest; use serde::{de::DeserializeOwned, Serialize}; use sol_rpc_types::{JsonRpcError, RpcError}; use std::fmt::Debug; @@ -57,7 +59,10 @@ where }; ServiceBuilder::new() .map_result(extract_json_rpc_response) - .map_err(|e: HttpClientError| RpcError::from(e)) + .map_err(|e| RpcError::try_from(e).unwrap_or_else(|e| { + log!(Priority::Info, "Unrecoverable error: {}", e); + panic!("{}", e); + })) .option_layer(maybe_retry) .option_layer(maybe_unique_id) .layer( @@ -67,7 +72,7 @@ where method: rpc_method.clone(), host: MetricRpcHost(req.uri().host().unwrap().to_string()), request_id: req.body().id().clone(), - start_ns: ic_cdk::api::time() + start_ns: ic_cdk::api::time(), }; log!(Priority::TraceHttp, "JSON-RPC request with id `{}` to {}: {:?}", req_data.request_id, @@ -93,11 +98,23 @@ where }) .on_error( |req_data: MetricData, error: &HttpClientError| match error { - HttpClientError::IcError(IcError { code, message: _ }) => { + HttpClientError::IcError(error) => { if error.is_response_too_large() { observe_response(MetricRpcCallResponse::MaxResponseSizeExceeded, &req_data); } else { - observe_response(MetricRpcCallResponse::IcError(code.to_string()), &req_data); + log!( + Priority::TraceHttp, + "IC error for request with id `{}`: {}", + req_data.request_id, + error + ); + + match error { + IcError::CallRejected { code, .. } => { + observe_response(MetricRpcCallResponse::IcError(code.to_string()), &req_data); + } + IcError::InsufficientLiquidCycleBalance { .. } => {} + } } } HttpClientError::UnsuccessfulHttpResponse( @@ -148,10 +165,7 @@ where .convert_response(JsonResponseConverter::new()) .convert_response(FilterNonSuccessfulHttpResponse) .convert_response(HttpResponseConverter) - .convert_request(CyclesAccounting::new( - read_state(|s| s.get_num_subnet_nodes()), - ChargingPolicyWithCollateral::default(), - )) + .convert_request(CyclesAccounting::new(charging_policy_with_collateral())) .service(canhttp::Client::new_with_error::()) } @@ -235,50 +249,16 @@ pub fn service_request_builder() -> JsonRpcServiceBuilder { .convert_request(HttpRequestConverter) } -#[derive(Debug, Clone, Eq, PartialEq)] -pub struct ChargingPolicyWithCollateral { - charge_user: bool, - collateral_cycles: u128, -} - -impl ChargingPolicyWithCollateral { - pub fn new( - num_nodes_in_subnet: u32, - charge_user: bool, - collateral_cycles_per_node: u128, - ) -> Self { - let collateral_cycles = - collateral_cycles_per_node.saturating_mul(num_nodes_in_subnet as u128); - Self { - charge_user, - collateral_cycles, - } - } - - fn new_from_state(s: &State) -> Self { - Self::new( - s.get_num_subnet_nodes(), - !s.is_demo_mode_active(), - COLLATERAL_CYCLES_PER_NODE, - ) - } -} - -impl Default for ChargingPolicyWithCollateral { - fn default() -> Self { - read_state(Self::new_from_state) - } -} - -impl CyclesChargingPolicy for ChargingPolicyWithCollateral { - fn cycles_to_charge( - &self, - _request: &CanisterHttpRequestArgument, - attached_cycles: u128, - ) -> u128 { - if self.charge_user { - return attached_cycles.saturating_add(self.collateral_cycles); +pub fn charging_policy_with_collateral( +) -> ChargeCaller u128 + Clone> { + let charge_caller = if read_state(|s| s.is_demo_mode_active()) { + |_request: &IcHttpRequest, _request_cost| 0 + } else { + |_request: &IcHttpRequest, request_cost| { + let collateral_cycles = COLLATERAL_CYCLES_PER_NODE + .saturating_mul(read_state(|s| s.get_num_subnet_nodes()) as u128); + request_cost + collateral_cycles } - 0 - } + }; + ChargeCaller::new(charge_caller) } diff --git a/canister/src/main.rs b/canister/src/main.rs index 610ce84b..c5748e7f 100644 --- a/canister/src/main.rs +++ b/canister/src/main.rs @@ -24,7 +24,7 @@ use sol_rpc_types::{ use std::str::FromStr; pub fn require_api_key_principal_or_controller() -> Result<(), String> { - let caller = ic_cdk::caller(); + let caller = ic_cdk::api::msg_caller(); if read_state(|state| state.is_api_key_principal(&caller)) || is_controller(&caller) { Ok(()) } else { @@ -34,7 +34,7 @@ pub fn require_api_key_principal_or_controller() -> Result<(), String> { pub fn require_base_http_outcall_fee() -> Result<(), String> { if read_state(|state| state.is_demo_mode_active()) - || (ic_cdk::api::call::msg_cycles_available128() + || (ic_cdk::api::msg_cycles_available() >= mutate_state(|state| state.lazy_compute_base_http_outcall_fee())) { Ok(()) @@ -62,7 +62,7 @@ async fn update_api_keys(api_keys: Vec<(SupportedRpcProviderId, Option)> log!( Priority::Info, "[{}] Updating API keys for providers: {}", - ic_cdk::caller(), + ic_cdk::api::msg_caller(), api_keys .iter() .map(|(provider, _)| format!("{:?}", provider)) diff --git a/canister/src/memory/mod.rs b/canister/src/memory/mod.rs index a0d46b74..4e826203 100644 --- a/canister/src/memory/mod.rs +++ b/canister/src/memory/mod.rs @@ -62,7 +62,7 @@ impl ConfigState { } impl Storable for ConfigState { - fn to_bytes(&self) -> Cow<[u8]> { + fn to_bytes(&self) -> Cow<'_, [u8]> { match &self { ConfigState::Uninitialized => Cow::Borrowed(&[]), ConfigState::Initialized(config) => Cow::Owned(encode(config)), diff --git a/canister/src/metrics/mod.rs b/canister/src/metrics/mod.rs index 8879a946..d65a1a00 100644 --- a/canister/src/metrics/mod.rs +++ b/canister/src/metrics/mod.rs @@ -281,7 +281,7 @@ pub fn encode_metrics(w: &mut ic_metrics_encoder::MetricsEncoder>) -> st w.gauge_vec("cycle_balance", "Cycle balance of this canister")? .value( &[("canister", "solrpc")], - ic_cdk::api::canister_balance128().metric_value(), + ic_cdk::api::canister_cycle_balance().metric_value(), )?; w.encode_gauge( "solrpc_canister_version", @@ -290,7 +290,7 @@ pub fn encode_metrics(w: &mut ic_metrics_encoder::MetricsEncoder>) -> st )?; w.encode_gauge( "stable_memory_bytes", - ic_cdk::api::stable::stable_size() as f64 * WASM_PAGE_SIZE_IN_BYTES, + ic_cdk::api::stable_size() as f64 * WASM_PAGE_SIZE_IN_BYTES, "Size of the stable memory allocated by this canister.", )?; w.encode_gauge( diff --git a/canister/src/providers/mod.rs b/canister/src/providers/mod.rs index c863e3e1..831f860a 100644 --- a/canister/src/providers/mod.rs +++ b/canister/src/providers/mod.rs @@ -7,7 +7,7 @@ use crate::{ types::OverrideProvider, }; use canhttp::multi::{TimedSizedMap, TimedSizedVec, Timestamp}; -use ic_cdk::api::management_canister::http_request::HttpHeader; +use ic_management_canister_types::HttpHeader; use maplit::btreemap; use sol_rpc_types::{ ConsensusStrategy, ProviderError, RpcAccess, RpcAuth, RpcEndpoint, RpcError, RpcResult, diff --git a/canister/src/rpc_client/mod.rs b/canister/src/rpc_client/mod.rs index d36021a8..84de40f6 100644 --- a/canister/src/rpc_client/mod.rs +++ b/canister/src/rpc_client/mod.rs @@ -7,23 +7,27 @@ mod tests; use crate::{ add_metric_entry, candid_rpc::hostname, + constants::DEFAULT_MAX_RESPONSE_BYTES, http::{ - errors::HttpClientError, http_client, service_request_builder, ChargingPolicyWithCollateral, + charging_policy_with_collateral, errors::HttpClientError, http_client, + service_request_builder, }, - memory::{read_state, record_ok_result, State}, + logs::Priority, + memory::{read_state, record_ok_result}, metrics::MetricRpcMethod, providers::{get_provider, request_builder, resolve_rpc_provider, Providers}, rpc_client::sol_rpc::ResponseTransform, }; use canhttp::{ + cycles::CyclesChargingPolicy, http::json::JsonRpcRequest, multi::{MultiResults, Reduce, ReduceWithEquality, ReduceWithThreshold, Timestamp}, - CyclesChargingPolicy, CyclesCostEstimator, MaxResponseBytesRequestExtension, - TransformContextRequestExtension, + MaxResponseBytesRequestExtension, TransformContextRequestExtension, }; +use canlog::log; use http::{Request, Response}; -use ic_cdk::api::management_canister::http_request::{ - CanisterHttpRequestArgument as IcHttpRequest, TransformContext, +use ic_management_canister_types::{ + HttpRequestArgs as IcHttpRequest, TransformContext, TransformFunc, }; use serde::{de::DeserializeOwned, Serialize}; use sol_rpc_types::{ @@ -171,7 +175,7 @@ impl GetBlockRequest { fn response_size_estimate(params: &json::GetBlockParams) -> u64 { let mut cycles = HEADER_SIZE_LIMIT; cycles += match params.get_transaction_details() { - Some(TransactionDetails::Accounts) => CyclesCostEstimator::DEFAULT_MAX_RESPONSE_BYTES, + Some(TransactionDetails::Accounts) => DEFAULT_MAX_RESPONSE_BYTES, Some(TransactionDetails::Signatures) => 256 * 1024, Some(TransactionDetails::None) | None => 512, }; @@ -179,7 +183,7 @@ impl GetBlockRequest { Some(true) | None => 256, Some(false) => 0, }; - CyclesCostEstimator::DEFAULT_MAX_RESPONSE_BYTES.min(cycles) + DEFAULT_MAX_RESPONSE_BYTES.min(cycles) } } @@ -479,8 +483,8 @@ impl MultiRpcRequest { { async fn extract_request( request: IcHttpRequest, - ) -> Result, HttpClientError> { - Ok(http::Response::new(request)) + ) -> Result, HttpClientError> { + Ok(Response::new(request)) } let num_providers = self.providers.sources.len(); @@ -488,7 +492,12 @@ impl MultiRpcRequest { let client = service_request_builder() .service_fn(extract_request) - .map_err(RpcError::from) + .map_err(|e| { + RpcError::try_from(e).unwrap_or_else(|e| { + log!(Priority::Info, "Unrecoverable error: {}", e); + panic!("{}", e); + }) + }) .map_response(Response::into_body); let (requests, errors) = requests.into_inner(); @@ -511,11 +520,11 @@ impl MultiRpcRequest { ); let mut cycles_to_attach = 0_u128; - let estimator = CyclesCostEstimator::new(read_state(State::get_num_subnet_nodes)); - let policy = ChargingPolicyWithCollateral::default(); + + let policy = charging_policy_with_collateral(); for request in requests.into_values() { - cycles_to_attach += - policy.cycles_to_charge(&request, estimator.cost_of_http_request(&request)); + let request_cycles_cost = ic_cdk::management_canister::cost_http_request(&request); + cycles_to_attach += policy.cycles_to_charge(&request, request_cycles_cost); } Ok(cycles_to_attach) } @@ -538,10 +547,13 @@ impl MultiRpcRequest { .map(|builder| { builder .max_response_bytes(self.max_response_bytes) - .transform_context(TransformContext::from_name( - "cleanup_response".to_owned(), - transform_op.clone(), - )) + .transform_context(TransformContext { + function: TransformFunc(candid::Func { + method: "cleanup_response".to_string(), + principal: ic_cdk::api::canister_self(), + }), + context: transform_op.clone(), + }) .body(self.request.clone()) .expect("BUG: invalid request") }); diff --git a/canister/src/rpc_client/sol_rpc/mod.rs b/canister/src/rpc_client/sol_rpc/mod.rs index a2b86159..20a949f5 100644 --- a/canister/src/rpc_client/sol_rpc/mod.rs +++ b/canister/src/rpc_client/sol_rpc/mod.rs @@ -1,12 +1,9 @@ #[cfg(test)] mod tests; -use candid::candid_method; use canhttp::http::json::JsonRpcResponse; -use ic_cdk::{ - api::management_canister::http_request::{HttpResponse, TransformArgs}, - query, -}; +use ic_cdk::query; +use ic_management_canister_types::{HttpRequestResult, TransformArgs}; use minicbor::{Decode, Encode}; use serde::{de::DeserializeOwned, Deserialize, Serialize}; use serde_json::{from_slice, Value}; @@ -179,8 +176,8 @@ impl ResponseTransform { } #[query] -#[candid_method(query)] -fn cleanup_response(mut args: TransformArgs) -> HttpResponse { +fn cleanup_response(args: TransformArgs) -> HttpRequestResult { + let mut args = args; args.response.headers.clear(); let status_ok = args.response.status >= 200u16 && args.response.status < 300u16; if status_ok && !args.context.is_empty() { diff --git a/end_to_end_tests/Cargo.toml b/end_to_end_tests/Cargo.toml index d613c478..8446dd11 100644 --- a/end_to_end_tests/Cargo.toml +++ b/end_to_end_tests/Cargo.toml @@ -11,7 +11,7 @@ license.workspace = true async-trait = { workspace = true } candid = { workspace = true } ic-agent = { workspace = true } -ic-cdk = { workspace = true } +ic-canister-runtime = { workspace = true } ic-error-types = { workspace = true } serde = { workspace = true } serde_json = { workspace = true } diff --git a/end_to_end_tests/src/lib.rs b/end_to_end_tests/src/lib.rs index c1efb7ef..f2aaee00 100644 --- a/end_to_end_tests/src/lib.rs +++ b/end_to_end_tests/src/lib.rs @@ -1,6 +1,7 @@ use async_trait::async_trait; use candid::{utils::ArgumentEncoder, CandidType, Encode, Principal}; use ic_agent::{identity::Secp256k1Identity, Agent}; +use ic_canister_runtime::IcError; use ic_error_types::RejectCode; use serde::de::DeserializeOwned; use serde_json::json; @@ -50,18 +51,18 @@ impl Setup { } } - pub fn new_ic_agent_runtime(&self) -> IcAgentRuntime { + pub fn new_ic_agent_runtime(&self) -> IcAgentRuntime<'_> { IcAgentRuntime { agent: &self.agent, wallet_canister_id: self.wallet_canister_id, } } - pub fn client_builder(&self) -> ClientBuilder { + pub fn client_builder(&self) -> ClientBuilder> { SolRpcClient::builder(self.new_ic_agent_runtime(), self.sol_rpc_canister_id) } - pub fn client(&self) -> SolRpcClient { + pub fn client(&self) -> SolRpcClient> { self.client_builder() .with_rpc_sources(RpcSources::Custom(vec![ RpcSource::Supported(SupportedRpcProviderId::AnkrDevnet), @@ -216,7 +217,7 @@ impl Runtime for IcAgentRuntime<'_> { method: &str, args: In, cycles: u128, - ) -> Result + ) -> Result where In: ArgumentEncoder + Send, Out: CandidType + DeserializeOwned, @@ -228,7 +229,10 @@ impl Runtime for IcAgentRuntime<'_> { .with_arg(Encode!(&CallCanisterArgs::new(id, method, args, cycles)).unwrap()) .call_and_wait() .await - .map_err(|e| (RejectCode::SysFatal, e.to_string()))?; + .map_err(|e| IcError::CallRejected { + code: RejectCode::SysFatal, + message: e.to_string(), + })?; decode_cycles_wallet_response(result) } @@ -237,7 +241,7 @@ impl Runtime for IcAgentRuntime<'_> { id: Principal, method: &str, args: In, - ) -> Result + ) -> Result where In: ArgumentEncoder + Send, Out: CandidType + DeserializeOwned, @@ -248,7 +252,10 @@ impl Runtime for IcAgentRuntime<'_> { .with_arg(encode_args(args)) .call() .await - .map_err(|e| (RejectCode::SysFatal, e.to_string()))?; + .map_err(|e| IcError::CallRejected { + code: RejectCode::SysFatal, + message: e.to_string(), + })?; decode_call_response(result) } } diff --git a/integration_tests/Cargo.toml b/integration_tests/Cargo.toml index c77467f9..49c85549 100644 --- a/integration_tests/Cargo.toml +++ b/integration_tests/Cargo.toml @@ -14,6 +14,7 @@ canhttp = { workspace = true } canlog = { workspace = true } const_format = { workspace = true } futures = { workspace = true } +ic-canister-runtime = { workspace = true } ic-cdk = { workspace = true } ic-error-types = { workspace = true } ic-http-types = { workspace = true } diff --git a/integration_tests/src/lib.rs b/integration_tests/src/lib.rs index fa30dd94..a9f86c5a 100644 --- a/integration_tests/src/lib.rs +++ b/integration_tests/src/lib.rs @@ -2,6 +2,8 @@ use async_trait::async_trait; use candid::{decode_args, utils::ArgumentEncoder, CandidType, Encode, Principal}; use canhttp::http::json::ConstantSizeId; use canlog::{Log, LogEntry}; +use ic_canister_runtime::IcError; +use ic_cdk::call::{CallFailed, CallRejected}; use ic_error_types::RejectCode; use ic_http_types::{HttpRequest, HttpResponse}; use ic_management_canister_types::{CanisterId, CanisterSettings}; @@ -33,7 +35,7 @@ use wallet::CallCanisterArgs; const DEFAULT_MAX_RESPONSE_BYTES: u64 = 2_000_000; const MAX_TICKS: usize = 10; pub const DEFAULT_CALLER_TEST_ID: Principal = - Principal::from_slice(&[0x0, 0x0, 0x0, 0x0, 0x3, 0x31, 0x1, 0x8, 0x2, 0x2]); + Principal::from_slice(&[0x0, 0x0, 0x0, 0x0, 0x0, 0x31, 0x1, 0x8, 0x1, 0x1]); pub const DEFAULT_CONTROLLER_TEST_ID: Principal = Principal::from_slice(&[0x9d, 0xf7, 0x02]); const MOCK_API_KEY: &str = "mock-api-key"; @@ -178,11 +180,11 @@ impl Setup { .entries } - pub fn client(&self) -> ClientBuilder { + pub fn client(&self) -> ClientBuilder> { SolRpcClient::builder(self.new_pocket_ic_runtime(), self.sol_rpc_canister_id) } - pub fn client_live_mode(&self) -> ClientBuilder { + pub fn client_live_mode(&self) -> ClientBuilder> { SolRpcClient::builder(self.new_live_pocket_ic_runtime(), self.sol_rpc_canister_id) } @@ -197,7 +199,7 @@ impl Setup { .unwrap() } - fn new_pocket_ic_runtime(&self) -> PocketIcRuntime { + fn new_pocket_ic_runtime(&self) -> PocketIcRuntime<'_> { PocketIcRuntime { env: &self.env, caller: self.caller, @@ -207,7 +209,7 @@ impl Setup { } } - fn new_live_pocket_ic_runtime(&self) -> PocketIcLiveModeRuntime { + fn new_live_pocket_ic_runtime(&self) -> PocketIcLiveModeRuntime<'_> { PocketIcLiveModeRuntime { env: &self.env, caller: self.caller, @@ -297,7 +299,7 @@ impl Runtime for PocketIcRuntime<'_> { method: &str, args: In, cycles: u128, - ) -> Result + ) -> Result where In: ArgumentEncoder + Send, Out: CandidType + DeserializeOwned, @@ -327,7 +329,7 @@ impl Runtime for PocketIcRuntime<'_> { id: Principal, method: &str, args: In, - ) -> Result + ) -> Result where In: ArgumentEncoder + Send, Out: CandidType + DeserializeOwned, @@ -413,17 +415,12 @@ impl PocketIcRuntime<'_> { true } - fn parse_reject_response(response: RejectResponse) -> (RejectCode, String) { - use pocket_ic::RejectCode as PocketIcRejectCode; - let rejection_code = match response.reject_code { - PocketIcRejectCode::SysFatal => RejectCode::SysFatal, - PocketIcRejectCode::SysTransient => RejectCode::SysTransient, - PocketIcRejectCode::DestinationInvalid => RejectCode::DestinationInvalid, - PocketIcRejectCode::CanisterReject => RejectCode::CanisterReject, - PocketIcRejectCode::CanisterError => RejectCode::CanisterError, - PocketIcRejectCode::SysUnknown => RejectCode::SysUnknown, - }; - (rejection_code, response.reject_message) + fn parse_reject_response(response: RejectResponse) -> IcError { + CallFailed::CallRejected(CallRejected::with_rejection( + response.reject_code as u32, + response.reject_message, + )) + .into() } } @@ -448,7 +445,7 @@ impl Runtime for PocketIcLiveModeRuntime<'_> { method: &str, args: In, cycles: u128, - ) -> Result + ) -> Result where In: ArgumentEncoder + Send, Out: CandidType + DeserializeOwned, @@ -477,7 +474,7 @@ impl Runtime for PocketIcLiveModeRuntime<'_> { id: Principal, method: &str, args: In, - ) -> Result + ) -> Result where In: ArgumentEncoder + Send, Out: CandidType + DeserializeOwned, @@ -495,20 +492,20 @@ pub fn encode_args(args: In) -> Vec { candid::encode_args(args).expect("Failed to encode arguments.") } -pub fn decode_call_response(bytes: Vec) -> Result +pub fn decode_call_response(bytes: Vec) -> Result where Out: CandidType + DeserializeOwned, { - decode_args(&bytes).map(|(res,)| res).map_err(|e| { - ( - RejectCode::CanisterError, - format!( + decode_args(&bytes) + .map(|(res,)| res) + .map_err(|e| IcError::CallRejected { + code: RejectCode::CanisterError, + message: format!( "failed to decode canister response as {}: {}", std::any::type_name::(), e ), - ) - }) + }) } #[async_trait] diff --git a/integration_tests/src/mock.rs b/integration_tests/src/mock.rs index a4d4fa98..7ddec8db 100644 --- a/integration_tests/src/mock.rs +++ b/integration_tests/src/mock.rs @@ -1,5 +1,5 @@ use canhttp::http::json::JsonRpcRequest; -use ic_cdk::api::call::RejectionCode; +use ic_cdk::call::RejectCode; use pocket_ic::common::rest::{ CanisterHttpHeader, CanisterHttpMethod, CanisterHttpReject, CanisterHttpReply, CanisterHttpRequest, CanisterHttpResponse, @@ -56,7 +56,7 @@ impl MockOutcallBuilder { }) } - pub fn new_error(code: RejectionCode, message: impl ToString) -> Self { + pub fn new_error(code: RejectCode, message: impl ToString) -> Self { Self(MockOutcall { method: None, url: None, diff --git a/integration_tests/src/wallet.rs b/integration_tests/src/wallet.rs index 5f18dfe7..4bc72284 100644 --- a/integration_tests/src/wallet.rs +++ b/integration_tests/src/wallet.rs @@ -2,6 +2,7 @@ use crate::{decode_call_response, encode_args}; use candid::{utils::ArgumentEncoder, CandidType, Principal}; +use ic_canister_runtime::IcError; use ic_error_types::RejectCode; use ic_management_canister_types::CanisterId; use regex::Regex; @@ -42,7 +43,7 @@ pub struct CallResult { /// The cycles wallet canister formats the rejection code and error message from the target /// canister into a single string. Extract them back from the formatted string. -pub fn decode_cycles_wallet_response(response: Vec) -> Result +pub fn decode_cycles_wallet_response(response: Vec) -> Result where Out: CandidType + DeserializeOwned, { @@ -55,12 +56,15 @@ where { Some(captures) => { let (_, [code, message]) = captures.extract(); - Err(( - code.parse::().unwrap().try_into().unwrap(), - message.to_string(), - )) + Err(IcError::CallRejected { + code: code.parse::().unwrap().try_into().unwrap(), + message: message.to_string(), + }) } - None => Err((RejectCode::SysFatal, message)), + None => Err(IcError::CallRejected { + code: RejectCode::SysFatal, + message, + }), } } } diff --git a/integration_tests/tests/solana_test_validator.rs b/integration_tests/tests/solana_test_validator.rs index 5e322450..e5ed991e 100644 --- a/integration_tests/tests/solana_test_validator.rs +++ b/integration_tests/tests/solana_test_validator.rs @@ -620,7 +620,7 @@ impl Setup { } } - fn icp_client(&self) -> SolRpcClient { + fn icp_client(&self) -> SolRpcClient> { self.setup .client_live_mode() .with_default_commitment_level(CommitmentLevel::Confirmed) diff --git a/integration_tests/tests/tests.rs b/integration_tests/tests/tests.rs index 179f0be6..bb710829 100644 --- a/integration_tests/tests/tests.rs +++ b/integration_tests/tests/tests.rs @@ -3,7 +3,8 @@ use assert_matches::*; use candid::CandidType; use canhttp::http::json::{ConstantSizeId, Id}; use const_format::formatcp; -use ic_cdk::api::{call::RejectionCode, management_canister::http_request::HttpHeader}; +use ic_cdk::call::RejectCode; +use ic_management_canister_types::HttpHeader; use pocket_ic::common::rest::CanisterHttpMethod; use serde::de::DeserializeOwned; use serde_json::{json, Value}; @@ -16,9 +17,9 @@ use sol_rpc_int_tests::{ use sol_rpc_types::{ CommitmentLevel, ConfirmedTransactionStatusWithSignature, ConsensusStrategy, GetSignaturesForAddressLimit, GetSlotParams, GetTransactionEncoding, HttpOutcallError, - InstallArgs, InstructionError, Mode, MultiRpcResult, PrioritizationFee, ProviderError, - RpcAccess, RpcAuth, RpcEndpoint, RpcError, RpcResult, RpcSource, RpcSources, Slot, - SolanaCluster, SupportedRpcProvider, SupportedRpcProviderId, TransactionDetails, + InstallArgs, InstructionError, LegacyRejectionCode, Mode, MultiRpcResult, PrioritizationFee, + ProviderError, RpcAccess, RpcAuth, RpcEndpoint, RpcError, RpcResult, RpcSource, RpcSources, + Slot, SolanaCluster, SupportedRpcProvider, SupportedRpcProviderId, TransactionDetails, TransactionError, }; use solana_account_decoder_client_types::{ @@ -601,7 +602,7 @@ mod generic_request_tests { .with_cycles(HTTP_OUTCALL_BASE_FEE - 1) .try_send() .await; - assert!(result.is_err_and(|(_code, message)| message.contains("Not enough cycles"))); + assert!(result.is_err_and(|err| err.to_string().contains("Not enough cycles"))); } let setup = Setup::new().await.with_mock_api_keys().await; @@ -1295,7 +1296,7 @@ mod rpc_config_tests { SupportedRpcProviderId::AlchemyMainnet, )])) .mock_http_once( - MockOutcallBuilder::new_error(RejectionCode::SysFatal, "Unrecoverable error!") + MockOutcallBuilder::new_error(RejectCode::SysFatal, "Unrecoverable error!") .with_max_response_bytes(1_999_999), ) .build(); @@ -1308,7 +1309,7 @@ mod rpc_config_tests { result, MultiRpcResult::Consistent(Err(RpcError::HttpOutcallError( HttpOutcallError::IcError { - code: RejectionCode::SysFatal, + code: LegacyRejectionCode::SysFatal, message: "Unrecoverable error!".to_string() } ))) @@ -1402,7 +1403,7 @@ mod rpc_config_tests { .mock_http_sequence(vec![ MockOutcallBuilder::new(200, ok_result_0), MockOutcallBuilder::new(200, ok_result_1), - MockOutcallBuilder::new_error(RejectionCode::SysFatal, "Some error!"), + MockOutcallBuilder::new_error(RejectCode::SysFatal, "Some error!"), ]) .build(); @@ -1422,7 +1423,7 @@ mod rpc_config_tests { .mock_http_sequence(vec![ MockOutcallBuilder::new(200, ok_result_3), MockOutcallBuilder::new(200, ok_result_4), - MockOutcallBuilder::new_error(RejectionCode::SysFatal, "Some error!"), + MockOutcallBuilder::new_error(RejectCode::SysFatal, "Some error!"), ]) .build(); @@ -1847,7 +1848,7 @@ mod metrics_tests { ), MockOutcallBuilder::new(429, json!({})), MockOutcallBuilder::new(500, json!({})), - MockOutcallBuilder::new_error(RejectionCode::SysFatal, "Fatal error!"), + MockOutcallBuilder::new_error(RejectCode::SysFatal, "Fatal error!"), ]) .build() .get_slot() @@ -1861,7 +1862,7 @@ mod metrics_tests { SupportedRpcProviderId::AlchemyMainnet, )])) .mock_http(MockOutcallBuilder::new_error( - RejectionCode::SysFatal, + RejectCode::SysFatal, "Http body exceeds size limit of 2000000 bytes.", )) .with_response_size_estimate(2_000_000) @@ -1876,7 +1877,7 @@ mod metrics_tests { result, MultiRpcResult::Consistent(Err(RpcError::HttpOutcallError( HttpOutcallError::IcError { - code: RejectionCode::SysFatal, + code: LegacyRejectionCode::SysFatal, message: "Http body exceeds size limit of 2000000 bytes.".to_string() } ))) diff --git a/libs/client/Cargo.toml b/libs/client/Cargo.toml index 284168fc..e3cfdbec 100644 --- a/libs/client/Cargo.toml +++ b/libs/client/Cargo.toml @@ -26,9 +26,10 @@ async-trait = { workspace = true } bincode = { workspace = true } candid = { workspace = true } derive_more = { workspace = true } +ic-canister-runtime = { workspace = true } ic-cdk = { workspace = true } ic-ed25519 = { workspace = true, optional = true } -ic-error-types = { workspace = true } +ic-management-canister-types = { workspace = true } serde = { workspace = true } serde_json = { workspace = true } sol_rpc_types = { version = "3.0.0", path = "../types" } diff --git a/libs/client/src/ed25519.rs b/libs/client/src/ed25519.rs index f27a9fb1..7b6e7a4d 100644 --- a/libs/client/src/ed25519.rs +++ b/libs/client/src/ed25519.rs @@ -6,11 +6,11 @@ use crate::Runtime; use candid::Principal; use derive_more::{From, Into}; -use ic_cdk::api::management_canister::schnorr::{ - SchnorrAlgorithm, SchnorrKeyId, SchnorrPublicKeyArgument, SchnorrPublicKeyResponse, - SignWithSchnorrArgument, SignWithSchnorrResponse, +use ic_canister_runtime::IcError; +use ic_management_canister_types::{ + SchnorrAlgorithm, SchnorrKeyId, SchnorrPublicKeyArgs, SchnorrPublicKeyResult, + SignWithSchnorrArgs, SignWithSchnorrResult, }; -use ic_error_types::RejectCode; // Source: https://internetcomputer.org/docs/current/references/t-sigs-how-it-works/#fees-for-the-t-schnorr-test-key const SIGN_WITH_SCHNORR_TEST_FEE: u128 = 10_000_000_000; @@ -88,7 +88,7 @@ impl Ed25519KeyId { /// # async fn main() -> Result<(), Box> { /// # use sol_rpc_types::{ConfirmedBlock, MultiRpcResult}; /// # use std::str::FromStr; -/// # use ic_cdk::api::management_canister::schnorr::{SchnorrPublicKeyResponse, SignWithSchnorrResponse}; +/// # use ic_management_canister_types::{SchnorrPublicKeyResult, SignWithSchnorrResult}; /// let client = SolRpcClient::builder_for_ic() /// # .with_mocked_responses() /// # .with_response_for_method("getSlot", MultiRpcResult::Consistent(Ok(332_577_897_u64))) @@ -103,11 +103,11 @@ impl Ed25519KeyId { /// # num_reward_partitions: None, /// # transactions: None, /// # })))) -/// # .with_response_for_method("schnorr_public_key", SchnorrPublicKeyResponse { +/// # .with_response_for_method("schnorr_public_key", SchnorrPublicKeyResult { /// # public_key: pubkey!("BPebStjcgCPnWTK3FXZJ8KhqwNYLk9aubC9b4Cgqb6oE").as_ref().to_vec(), /// # chain_code: "UWbC6EgDnWEJIU4KFBqASTCYAzEiJGsR".as_bytes().to_vec(), /// # }) -/// # .with_response_for_method("sign_with_schnorr", SignWithSchnorrResponse { +/// # .with_response_for_method("sign_with_schnorr", SignWithSchnorrResult { /// # signature: Signature::from_str("37HbmunhjSC1xxnVsaFX2xaS8gYnb5JYiLy9B51Ky9Up69aF7Qra6dHSLMCaiurRYq3Y8ZxSVUwC5sntziWuhZee").unwrap().as_ref().to_vec() /// # }) /// .build(); @@ -164,16 +164,17 @@ pub async fn sign_message( message: &solana_message::Message, key_id: Ed25519KeyId, derivation_path: Option<&DerivationPath>, -) -> Result { - let arg = SignWithSchnorrArgument { +) -> Result { + let arg = SignWithSchnorrArgs { message: message.serialize(), derivation_path: derivation_path.cloned().unwrap_or_default().into(), key_id: SchnorrKeyId { algorithm: SchnorrAlgorithm::Ed25519, name: key_id.id().to_string(), }, + aux: None, }; - let SignWithSchnorrResponse { signature } = runtime + let SignWithSchnorrResult { signature } = runtime .update_call( Principal::management_canister(), "sign_with_schnorr", @@ -212,10 +213,10 @@ pub async fn sign_message( /// #[tokio::main] /// # async fn main() -> Result<(), Box> { /// # use sol_rpc_client::fixtures::MockRuntime; -/// # use ic_cdk::api::management_canister::schnorr::SchnorrPublicKeyResponse; +/// # use ic_management_canister_types::SchnorrPublicKeyResult; /// let runtime = IcRuntime; /// # let runtime = MockRuntime::default() -/// # .with_response_for_method("schnorr_public_key", SchnorrPublicKeyResponse { +/// # .with_response_for_method("schnorr_public_key", SchnorrPublicKeyResult { /// # public_key: pubkey!("BPebStjcgCPnWTK3FXZJ8KhqwNYLk9aubC9b4Cgqb6oE").as_ref().to_vec(), /// # chain_code: "UWbC6EgDnWEJIU4KFBqASTCYAzEiJGsR".as_bytes().to_vec(), /// # }); @@ -253,8 +254,8 @@ pub async fn get_pubkey( canister_id: Option, derivation_path: Option<&DerivationPath>, key_id: Ed25519KeyId, -) -> Result<(solana_pubkey::Pubkey, [u8; 32]), (RejectCode, String)> { - let arg = SchnorrPublicKeyArgument { +) -> Result<(solana_pubkey::Pubkey, [u8; 32]), IcError> { + let arg = SchnorrPublicKeyArgs { canister_id, derivation_path: derivation_path.cloned().unwrap_or_default().into(), key_id: SchnorrKeyId { @@ -262,7 +263,7 @@ pub async fn get_pubkey( name: key_id.id().to_string(), }, }; - let SchnorrPublicKeyResponse { + let SchnorrPublicKeyResult { public_key, chain_code, } = runtime diff --git a/libs/client/src/fixtures/mod.rs b/libs/client/src/fixtures/mod.rs index 314c20b8..07c87fb4 100644 --- a/libs/client/src/fixtures/mod.rs +++ b/libs/client/src/fixtures/mod.rs @@ -5,7 +5,7 @@ use crate::{ClientBuilder, Runtime}; use async_trait::async_trait; use candid::{utils::ArgumentEncoder, CandidType, Decode, Encode, Principal}; -use ic_error_types::RejectCode; +use ic_canister_runtime::IcError; use serde::de::DeserializeOwned; use sol_rpc_types::{AccountData, AccountEncoding, AccountInfo}; use std::collections::BTreeMap; @@ -83,7 +83,7 @@ impl MockRuntime { self } - fn call(&self, method: &str) -> Result + fn call(&self, method: &str) -> Result where Out: CandidType + DeserializeOwned, { @@ -110,7 +110,7 @@ impl Runtime for MockRuntime { method: &str, _args: In, _cycles: u128, - ) -> Result + ) -> Result where In: ArgumentEncoder + Send, Out: CandidType + DeserializeOwned, @@ -123,7 +123,7 @@ impl Runtime for MockRuntime { _id: Principal, method: &str, _args: In, - ) -> Result + ) -> Result where In: ArgumentEncoder + Send, Out: CandidType + DeserializeOwned, diff --git a/libs/client/src/lib.rs b/libs/client/src/lib.rs index eb23b960..313f224d 100644 --- a/libs/client/src/lib.rs +++ b/libs/client/src/lib.rs @@ -142,8 +142,8 @@ use crate::request::{ }; use async_trait::async_trait; use candid::{utils::ArgumentEncoder, CandidType, Principal}; -use ic_cdk::api::call::RejectionCode as IcCdkRejectionCode; -use ic_error_types::RejectCode; +use ic_canister_runtime::IcError; +use ic_cdk::call::Call; pub use request::{ EstimateBlockhashRequestBuilder, EstimateRecentBlockhashError, Request, RequestBuilder, SolRpcConfig, SolRpcEndpoint, SolRpcRequest, @@ -180,7 +180,7 @@ pub trait Runtime { method: &str, args: In, cycles: u128, - ) -> Result + ) -> Result where In: ArgumentEncoder + Send, Out: CandidType + DeserializeOwned; @@ -191,7 +191,7 @@ pub trait Runtime { id: Principal, method: &str, args: In, - ) -> Result + ) -> Result where In: ArgumentEncoder + Send, Out: CandidType + DeserializeOwned; @@ -1136,7 +1136,7 @@ impl SolRpcClient { async fn try_execute_request( &self, request: Request, - ) -> Result + ) -> Result where Config: CandidType + Send, Params: CandidType + Send, @@ -1193,15 +1193,17 @@ impl Runtime for IcRuntime { method: &str, args: In, cycles: u128, - ) -> Result + ) -> Result where In: ArgumentEncoder + Send, Out: CandidType + DeserializeOwned, { - ic_cdk::api::call::call_with_payment128(id, method, args, cycles) + Call::unbounded_wait(id, method) + .with_args(&args) + .with_cycles(cycles) .await - .map(|(res,)| res) - .map_err(|(code, message)| (convert_reject_code(code), message)) + .map_err(IcError::from) + .and_then(|response| response.candid::().map_err(IcError::from)) } async fn query_call( @@ -1209,33 +1211,15 @@ impl Runtime for IcRuntime { id: Principal, method: &str, args: In, - ) -> Result + ) -> Result where In: ArgumentEncoder + Send, Out: CandidType + DeserializeOwned, { - ic_cdk::api::call::call(id, method, args) + Call::unbounded_wait(id, method) + .with_args(&args) .await - .map(|(res,)| res) - .map_err(|(code, message)| (convert_reject_code(code), message)) - } -} - -fn convert_reject_code(code: IcCdkRejectionCode) -> RejectCode { - match code { - IcCdkRejectionCode::SysFatal => RejectCode::SysFatal, - IcCdkRejectionCode::SysTransient => RejectCode::SysTransient, - IcCdkRejectionCode::DestinationInvalid => RejectCode::DestinationInvalid, - IcCdkRejectionCode::CanisterReject => RejectCode::CanisterReject, - IcCdkRejectionCode::CanisterError => RejectCode::CanisterError, - IcCdkRejectionCode::Unknown => { - // This can only happen if there is a new error code on ICP that the CDK is not aware of. - // We map it to SysFatal since none of the other error codes apply. - // In particular, note that RejectCode::SysUnknown is only applicable to inter-canister calls that used ic0.call_with_best_effort_response. - RejectCode::SysFatal - } - IcCdkRejectionCode::NoError => { - unreachable!("inter-canister calls should never produce a RejectionCode::NoError error") - } + .map_err(IcError::from) + .and_then(|response| response.candid::().map_err(IcError::from)) } } diff --git a/libs/client/src/request/mod.rs b/libs/client/src/request/mod.rs index 490a3ab1..e57b96b1 100644 --- a/libs/client/src/request/mod.rs +++ b/libs/client/src/request/mod.rs @@ -4,7 +4,7 @@ mod tests; use crate::{Runtime, SolRpcClient}; use candid::CandidType; use derive_more::From; -use ic_error_types::RejectCode; +use ic_canister_runtime::IcError; use serde::de::DeserializeOwned; use sol_rpc_types::{ AccountInfo, CommitmentLevel, ConfirmedBlock, ConfirmedTransactionStatusWithSignature, @@ -883,7 +883,7 @@ impl /// Constructs the [`Request`] and sends it using the [`SolRpcClient`]. This method returns /// either the request response or any error that occurs while sending the request. - pub async fn try_send(self) -> Result + pub async fn try_send(self) -> Result where Config: CandidType + Send, Params: CandidType + Send, @@ -1044,8 +1044,10 @@ impl RequestCostBuilder { } fn set_default(default_value: Option, value: &mut Option) { - if default_value.is_some() && value.is_none() { - *value = Some(default_value.unwrap()) + if value.is_none() { + if let Some(default) = default_value { + *value = Some(default); + } } } diff --git a/libs/types/Cargo.toml b/libs/types/Cargo.toml index 4834beb3..6331ce35 100644 --- a/libs/types/Cargo.toml +++ b/libs/types/Cargo.toml @@ -17,6 +17,7 @@ candid = { workspace = true } canlog = { workspace = true } derive_more = { workspace = true } ic-cdk = { workspace = true } +ic-management-canister-types = { workspace = true } regex = { workspace = true } serde = { workspace = true } serde_json = { workspace = true } diff --git a/libs/types/src/lib.rs b/libs/types/src/lib.rs index ee7da1e7..a578862c 100644 --- a/libs/types/src/lib.rs +++ b/libs/types/src/lib.rs @@ -24,9 +24,10 @@ pub use lifecycle::{InstallArgs, Mode, NumSubnetNodes}; pub use response::MultiRpcResult; pub use rpc_client::{ ConsensusStrategy, GetRecentPrioritizationFeesRpcConfig, GetSlotRpcConfig, HttpHeader, - HttpOutcallError, JsonRpcError, NonZeroU8, OverrideProvider, ProviderError, RegexString, - RegexSubstitution, RoundingError, RpcAccess, RpcAuth, RpcConfig, RpcEndpoint, RpcError, - RpcResult, RpcSource, RpcSources, SolanaCluster, SupportedRpcProvider, SupportedRpcProviderId, + HttpOutcallError, JsonRpcError, LegacyRejectionCode, NonZeroU8, OverrideProvider, + ProviderError, RegexString, RegexSubstitution, RoundingError, RpcAccess, RpcAuth, RpcConfig, + RpcEndpoint, RpcError, RpcResult, RpcSource, RpcSources, SolanaCluster, SupportedRpcProvider, + SupportedRpcProviderId, }; use serde::{Serialize, Serializer}; pub use solana::{ diff --git a/libs/types/src/rpc_client/mod.rs b/libs/types/src/rpc_client/mod.rs index b8767955..c9620c16 100644 --- a/libs/types/src/rpc_client/mod.rs +++ b/libs/types/src/rpc_client/mod.rs @@ -6,8 +6,8 @@ use candid::{ CandidType, }; use derive_more::{From, Into}; -use ic_cdk::api::call::RejectionCode; -pub use ic_cdk::api::management_canister::http_request::HttpHeader; +use ic_cdk::call::RejectCode; +pub use ic_management_canister_types::HttpHeader; use regex::Regex; use serde::{Deserialize, Serialize}; use std::{fmt::Debug, num::TryFromIntError}; @@ -78,7 +78,7 @@ pub enum HttpOutcallError { #[error("IC error (code: {code:?}): {message}")] IcError { /// The error code. - code: RejectionCode, + code: LegacyRejectionCode, /// The error message. message: String, }, @@ -634,3 +634,60 @@ impl TryFrom for NonZeroU8 { std::num::NonZeroU8::try_from(value).map(Self) } } + +/// Rejection code from calling another canister. +/// +/// This implementation was [copied](https://github.com/dfinity/cdk-rs/blob/83ba5fc7b3316a6fa4e7f704b689c95c9e677029/src/ic-cdk/src/api/call.rs#L21) from ic-cdk v0.17. +/// +/// The `ic_cdk::api::call::RejectionCode` type is deprecated since ic-cdk v0.18. +/// The replacement `ic_cdk::call::RejectCode` re-exports the type defined in the `ic-error-types` +/// crate. We cannot simply switch to the replacement because the existing `RejectionCode` is a +/// public type in SOL RPC canister's interface. +/// To maintain compatibility, we retain the "outdated" definition here. +#[repr(i32)] +#[derive(CandidType, Deserialize, Clone, Copy, Hash, Debug, PartialEq, Eq, PartialOrd, Ord)] +#[serde(rename = "RejectionCode")] +pub enum LegacyRejectionCode { + /// No error occurred. + NoError = 0, + + /// Fatal system error, retry unlikely to be useful. + SysFatal = 1, + /// Transient system error, retry might be possible. + SysTransient = 2, + /// Invalid destination (e.g. canister/account does not exist). + DestinationInvalid = 3, + /// Explicit reject by the canister. + CanisterReject = 4, + /// Canister error (e.g., trap, no response). + CanisterError = 5, + + /// An unknown error occurred. + Unknown, +} + +impl From for LegacyRejectionCode { + fn from(value: RejectCode) -> Self { + match value { + RejectCode::SysFatal => Self::SysFatal, + RejectCode::SysTransient => Self::SysTransient, + RejectCode::DestinationInvalid => Self::DestinationInvalid, + RejectCode::CanisterReject => Self::CanisterReject, + RejectCode::CanisterError => Self::CanisterError, + RejectCode::SysUnknown => Self::Unknown, + } + } +} + +impl From for LegacyRejectionCode { + fn from(value: u32) -> Self { + match value { + 1 => LegacyRejectionCode::SysFatal, + 2 => LegacyRejectionCode::SysTransient, + 3 => LegacyRejectionCode::DestinationInvalid, + 4 => LegacyRejectionCode::CanisterReject, + 5 => LegacyRejectionCode::CanisterError, + _ => LegacyRejectionCode::Unknown, + } + } +} diff --git a/rust-toolchain.toml b/rust-toolchain.toml index fa288954..833206e1 100644 --- a/rust-toolchain.toml +++ b/rust-toolchain.toml @@ -1,4 +1,4 @@ [toolchain] -channel = "1.87.0" +channel = "1.90.0" components = ["rustfmt", "clippy"] targets = ["wasm32-unknown-unknown"]