Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions rs/embedders/src/wasmtime_embedder/system_api/routing.rs
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,7 @@ pub(super) fn resolve_destination(
| Ok(Ic00Method::RawRand)
| Ok(Ic00Method::ProvisionalCreateCanisterWithCycles)
| Ok(Ic00Method::HttpRequest)
| Ok(Ic00Method::FlexibleHttpRequest)
| Ok(Ic00Method::BitcoinSendTransactionInternal)
| Ok(Ic00Method::BitcoinGetSuccessors) => Ok(own_subnet.get()),
// This message needs to be routed to the NNS subnet. We assume that
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -265,6 +265,7 @@ impl SystemStateModifications {
| Ok(Ic00Method::DeleteCanister)
| Ok(Ic00Method::RawRand)
| Ok(Ic00Method::DepositCycles)
| Ok(Ic00Method::FlexibleHttpRequest)
| Ok(Ic00Method::HttpRequest)
| Ok(Ic00Method::SetupInitialDKG)
| Ok(Ic00Method::ECDSAPublicKey)
Expand Down
1 change: 1 addition & 0 deletions rs/execution_environment/src/canister_manager.rs
Original file line number Diff line number Diff line change
Expand Up @@ -160,6 +160,7 @@ impl CanisterManager {
// cannot carry cycles, it does not make sense to allow them from users.
| Ok(Ic00Method::DepositCycles)
| Ok(Ic00Method::HttpRequest)
| Ok(Ic00Method::FlexibleHttpRequest)
// Nobody pays for `raw_rand`, so this cannot be used via ingress messages
| Ok(Ic00Method::RawRand)
// Bitcoin messages require cycles, so we reject all ingress messages.
Expand Down
43 changes: 32 additions & 11 deletions rs/execution_environment/src/execution_environment.rs
Original file line number Diff line number Diff line change
Expand Up @@ -39,17 +39,17 @@ use ic_management_canister_types_private::{
CanisterChangeOrigin, CanisterHttpRequestArgs, CanisterIdRecord, CanisterInfoRequest,
CanisterInfoResponse, CanisterMetadataRequest, CanisterStatusType, ClearChunkStoreArgs,
CreateCanisterArgs, DeleteCanisterSnapshotArgs, ECDSAPublicKeyArgs, ECDSAPublicKeyResponse,
EmptyBlob, FetchCanisterLogsRequest, IC_00, InstallChunkedCodeArgs, InstallCodeArgsV2,
ListCanisterSnapshotArgs, LoadCanisterSnapshotArgs, MasterPublicKeyId, Method as Ic00Method,
NodeMetricsHistoryArgs, Payload as Ic00Payload, ProvisionalCreateCanisterWithCyclesArgs,
ProvisionalTopUpCanisterArgs, ReadCanisterSnapshotDataArgs, ReadCanisterSnapshotMetadataArgs,
RenameCanisterArgs, ReshareChainKeyArgs, SchnorrAlgorithm, SchnorrPublicKeyArgs,
SchnorrPublicKeyResponse, SetupInitialDKGArgs, SignWithECDSAArgs, SignWithSchnorrArgs,
SignWithSchnorrAux, StoredChunksArgs, SubnetInfoArgs, SubnetInfoResponse,
TakeCanisterSnapshotArgs, UninstallCodeArgs, UpdateSettingsArgs,
UploadCanisterSnapshotDataArgs, UploadCanisterSnapshotMetadataArgs,
UploadCanisterSnapshotMetadataResponse, UploadChunkArgs, VetKdDeriveKeyArgs,
VetKdPublicKeyArgs, VetKdPublicKeyResult,
EmptyBlob, FetchCanisterLogsRequest, FlexibleCanisterHttpRequestArgs, IC_00,
InstallChunkedCodeArgs, InstallCodeArgsV2, ListCanisterSnapshotArgs, LoadCanisterSnapshotArgs,
MasterPublicKeyId, Method as Ic00Method, NodeMetricsHistoryArgs, Payload as Ic00Payload,
ProvisionalCreateCanisterWithCyclesArgs, ProvisionalTopUpCanisterArgs,
ReadCanisterSnapshotDataArgs, ReadCanisterSnapshotMetadataArgs, RenameCanisterArgs,
ReshareChainKeyArgs, SchnorrAlgorithm, SchnorrPublicKeyArgs, SchnorrPublicKeyResponse,
SetupInitialDKGArgs, SignWithECDSAArgs, SignWithSchnorrArgs, SignWithSchnorrAux,
StoredChunksArgs, SubnetInfoArgs, SubnetInfoResponse, TakeCanisterSnapshotArgs,
UninstallCodeArgs, UpdateSettingsArgs, UploadCanisterSnapshotDataArgs,
UploadCanisterSnapshotMetadataArgs, UploadCanisterSnapshotMetadataResponse, UploadChunkArgs,
VetKdDeriveKeyArgs, VetKdPublicKeyArgs, VetKdPublicKeyResult,
};
use ic_metrics::MetricsRegistry;
use ic_registry_provisional_whitelist::ProvisionalWhitelist;
Expand Down Expand Up @@ -1003,6 +1003,27 @@ impl ExecutionEnvironment {
Ok(args) => self.deposit_cycles(args.get_canister_id(), &mut msg, &mut state),
},

Ok(Ic00Method::FlexibleHttpRequest) => match &msg {
CanisterCall::Request(_) => {
match FlexibleCanisterHttpRequestArgs::decode(payload) {
Err(err) => ExecuteSubnetMessageResult::Finished {
response: Err(err),
refund: msg.take_cycles(),
},
Ok(_) => ExecuteSubnetMessageResult::Finished {
response: Err(UserError::new(
ErrorCode::CanisterRejectedMessage,
"FlexibleHttpRequest is not yet implemented".to_string(),
)),
refund: msg.take_cycles(),
},
}
}
CanisterCall::Ingress(_) => {
self.reject_unexpected_ingress(Ic00Method::FlexibleHttpRequest)
}
},

Ok(Ic00Method::HttpRequest) => match state.metadata.own_subnet_features.http_requests {
true => match &msg {
CanisterCall::Request(request) => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -327,6 +327,7 @@ impl ExecutionEnvironmentMetrics {
| ic00::Method::InstallChunkedCode
| ic00::Method::StopCanister
| ic00::Method::HttpRequest
| ic00::Method::FlexibleHttpRequest
| ic00::Method::SignWithECDSA
| ic00::Method::SignWithSchnorr
| ic00::Method::VetKdDeriveKey
Expand Down
5 changes: 4 additions & 1 deletion rs/execution_environment/src/ic00_permissions.rs
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,10 @@ impl Ic00MethodPermissions {
does_not_run_on_aborted_canister: false,
installs_code: false,
},
Ic00Method::CreateCanister | Ic00Method::HttpRequest | Ic00Method::RawRand => Self {
Ic00Method::CreateCanister
| Ic00Method::HttpRequest
| Ic00Method::FlexibleHttpRequest
| Ic00Method::RawRand => Self {
method,
allow_remote_subnet_sender: false,
allow_only_nns_subnet_sender: false,
Expand Down
1 change: 1 addition & 0 deletions rs/execution_environment/src/scheduler.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2274,6 +2274,7 @@ fn get_instructions_limits_for_subnet_message(
| ECDSAPublicKey
| RawRand
| HttpRequest
| FlexibleHttpRequest
| SetupInitialDKG
| SignWithECDSA
| ReshareChainKey
Expand Down
1 change: 1 addition & 0 deletions rs/execution_environment/tests/dts.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1135,6 +1135,7 @@ fn dts_aborted_execution_does_not_block_subnet_messages() {
// No effective canister id.
Method::CreateCanister
| Method::HttpRequest
| Method::FlexibleHttpRequest
| Method::ECDSAPublicKey
| Method::RawRand
| Method::SetupInitialDKG
Expand Down
9 changes: 8 additions & 1 deletion rs/rust_canisters/proxy_canister/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,8 @@ use std::time::Duration;
use candid::{CandidType, Deserialize};
use ic_cdk::api::call::RejectionCode;
use ic_management_canister_types_private::{
BoundedHttpHeaders, HttpHeader, HttpMethod, Payload, TransformContext,
BoundedHttpHeaders, FlexibleCanisterHttpRequestArgs, HttpHeader, HttpMethod, Payload,
TransformContext,
};

#[derive(Clone, Debug, CandidType, Deserialize)]
Expand All @@ -27,6 +28,12 @@ pub struct RemoteHttpStressRequest {
pub count: u64,
}

#[derive(Clone, Debug, CandidType, Deserialize)]
pub struct FlexibleRemoteHttpRequest {
pub request: FlexibleCanisterHttpRequestArgs,
pub cycles: u64,
}

/// We create a custom type instead of reusing [`ic_management_canister_types_private::CanisterHttpRequestArgs`]
/// as we don't want the body to be deserialized as a bounded vec.
/// This allows us to test sending headers that are longer than the default limit and test.
Expand Down
20 changes: 18 additions & 2 deletions rs/rust_canisters/proxy_canister/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,8 @@ use ic_management_canister_types_private::{
CanisterHttpResponsePayload, HttpHeader, Payload, TransformArgs,
};
use proxy_canister::{
RemoteHttpRequest, RemoteHttpResponse, RemoteHttpStressRequest, RemoteHttpStressResponse,
ResponseWithRefundedCycles,
FlexibleRemoteHttpRequest, RemoteHttpRequest, RemoteHttpResponse, RemoteHttpStressRequest,
RemoteHttpStressResponse, ResponseWithRefundedCycles,
};
use std::cell::RefCell;
use std::collections::HashMap;
Expand All @@ -31,6 +31,21 @@ thread_local! {

const MAX_TRANSFORM_SIZE: usize = 2_000_000;

#[update]
async fn send_flexible_request(
request: FlexibleRemoteHttpRequest,
) -> Result<Vec<u8>, (RejectionCode, String)> {
let FlexibleRemoteHttpRequest { request, cycles } = request;

ic_cdk::api::call::call_raw(
Principal::management_canister(),
"flexible_http_request",
&request.encode(),
cycles,
)
.await
}

#[update]
async fn send_requests_in_parallel(
request: RemoteHttpStressRequest,
Expand Down Expand Up @@ -116,6 +131,7 @@ async fn send_request_with_refund_callback(
let RemoteHttpRequest { request, cycles } = request;
let request_url = request.url.clone();
println!("send_request making IC call.");

let result = match ic_cdk::api::call::call_raw(
Principal::management_canister(),
"http_request",
Expand Down
10 changes: 10 additions & 0 deletions rs/tests/networking/BUILD.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,16 @@ system_test_nns(
deps = CANISTER_HTTP_BASE_DEPS + ["//rs/rust_canisters/canister_test"],
)

system_test_nns(
name = "canister_http_flexible_test",
runtime_deps = CANISTER_HTTP_RUNTIME_DEPS | {
"PROXY_WASM_PATH": "//rs/rust_canisters/proxy_canister:proxy_canister",
},
deps = CANISTER_HTTP_BASE_DEPS + [
"//rs/rust_canisters/canister_test",
],
)

system_test_nns(
name = "canister_http_stress_test",
enable_metrics = True,
Expand Down
4 changes: 4 additions & 0 deletions rs/tests/networking/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,10 @@ path = "canister_http_stress_test.rs"
name = "ic-systest-canister-http-soak"
path = "canister_http_soak_test.rs"

[[bin]]
name = "ic-systest-canister-http-flexible"
path = "canister_http_flexible_test.rs"

[[bin]]
name = "ic-systest-canister-http"
path = "canister_http_test.rs"
Expand Down
132 changes: 132 additions & 0 deletions rs/tests/networking/canister_http_flexible_test.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,132 @@
/* tag::catalog[]
Title:: Basic HTTP requests from canisters

Goal:: Ensure simple HTTP requests can be made from canisters.

Runbook::
0. Instantiate a universal VM with a webserver
1. Instantiate an IC with one application subnet with the HTTP feature enabled.
2. Install NNS canisters
3. Install the proxy canister
4. Make an update call to the proxy canister.

Success::
1. Received http response with status 200.

end::catalog[] */
#![allow(deprecated)]

use anyhow::Result;
use anyhow::bail;
use canister_http::*;
use canister_test::Canister;
use dfn_candid::candid_one;
use ic_cdk::api::call::RejectionCode;
use ic_management_canister_types_private::{
BoundedHttpHeaders, FlexibleCanisterHttpRequestArgs, HttpMethod, TransformContext,
TransformFunc,
};
use ic_system_test_driver::driver::group::SystemTestGroup;
use ic_system_test_driver::driver::{
test_env::TestEnv,
test_env_api::{READY_WAIT_TIMEOUT, RETRY_BACKOFF},
};
use ic_system_test_driver::systest;
use ic_system_test_driver::util::block_on;
use proxy_canister::FlexibleRemoteHttpRequest;
use slog::{Logger, info};

fn main() -> Result<()> {
SystemTestGroup::new()
.with_setup(canister_http::setup)
.add_test(systest!(test))
.execute_from_args()?;

Ok(())
}

pub fn test(env: TestEnv) {
let logger = env.logger();
let mut nodes = get_node_snapshots(&env);
let node = nodes.next().expect("there is no application node");
let runtime = get_runtime_from_node(&node);
let proxy_canister = create_proxy_canister(&env, &runtime, &node);
let webserver_ipv6 = get_universal_vm_address(&env);

block_on(async {
test_proxy_canister(
&proxy_canister,
format!("https://[{webserver_ipv6}]"),
logger,
)
.await;
});
}

async fn test_proxy_canister(proxy_canister: &Canister<'_>, url: String, logger: Logger) {
ic_system_test_driver::retry_with_msg_async!(
format!(
"calling send_request of proxy canister {} with URL {}",
proxy_canister.canister_id(),
url
),
&logger,
READY_WAIT_TIMEOUT,
RETRY_BACKOFF,
|| async {
let context = "There is context to be appended in body";
let res = proxy_canister
.update_(
"send_flexible_request",
candid_one::<
Result<Vec<u8>, (RejectionCode, String)>,
FlexibleRemoteHttpRequest,
>,
FlexibleRemoteHttpRequest {
request: FlexibleCanisterHttpRequestArgs {
url: url.clone(),
headers: BoundedHttpHeaders::new(vec![]),
body: None,
transform: Some(TransformContext {
function: TransformFunc(candid::Func {
principal: proxy_canister.canister_id().get().0,
method: "transform_with_context".to_string(),
}),
context: context.as_bytes().to_vec(),
}),
method: HttpMethod::GET,
replication: None,
},
cycles: 500_000_000_000,
},
)
.await
.expect("Update call to proxy canister failed");

let expected_error_msg = "FlexibleHttpRequest is not yet implemented";

match res {
Ok(_) => {
bail!("Update call succeeded unexpectedly.");
}
Err((code, message)) => {
if message == expected_error_msg {
info!(
&logger,
"Http request failed with expected error. Code: {:?}, Message: {}",
code, message
);
Ok(())
} else {
bail!(
"Http request failed with unexpected error. Code: {:?}, Message: '{}'. Expected: '{}'",
code, message, expected_error_msg
);
}
}
}
}
)
.await
.expect("Timeout waiting for http call to fail with the correct error");
}
46 changes: 46 additions & 0 deletions rs/types/management_canister_types/src/http.rs
Original file line number Diff line number Diff line change
Expand Up @@ -120,6 +120,52 @@ impl CanisterHttpRequestArgs {
}
}

/// Struct used for encoding/decoding
/// ```text
/// record {
/// url : text;
/// headers : vec http_header;
/// method : variant { get; head; post };
/// body : opt blob;
/// transform : opt record {
/// function : func (record {response : http_response; context : blob}) -> (http_response) query;
/// context : blob;
/// };
/// replication: opt record {
/// min_responses: nat32;
/// max_responses: nat32;
/// total_requests: nat32;
/// };
/// }
/// ```s
#[derive(Clone, PartialEq, Debug, CandidType, Deserialize)]
pub struct FlexibleCanisterHttpRequestArgs {
pub url: String,
pub headers: BoundedHttpHeaders,
#[serde(deserialize_with = "ic_utils::deserialize::deserialize_option_blob")]
pub body: Option<Vec<u8>>,
pub method: HttpMethod,
pub transform: Option<TransformContext>,
pub replication: Option<ReplicationCounts>,
}

impl Payload<'_> for FlexibleCanisterHttpRequestArgs {}

/// Struct used for encoding/decoding
/// ```text
/// record {
/// min_responses: nat32;
/// max_responses: nat32;
/// total_requests: nat32;
/// };
/// ```
#[derive(CandidType, Deserialize, Debug, Clone, Default, PartialEq)]
pub struct ReplicationCounts {
pub total_requests: u32,
pub min_responses: u32,
pub max_responses: u32,
}

#[test]
fn test_http_headers_max_number() {
// This test verifies the number of HTTP headers stays within the allowed limit.
Expand Down
Loading
Loading