Skip to content
Draft
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
46 changes: 46 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -500,6 +500,8 @@ members = [
"rs/tests/nested/nns_recovery",
"rs/tests/networking",
"rs/tests/networking/canister_http",
"rs/tests/networking/canister_http_correctness",
"rs/tests/networking/canister_http_flexible",
"rs/tests/networking/canisters",
"rs/tests/networking/firewall",
"rs/tests/networking/subnet_update_workload",
Expand Down
2 changes: 1 addition & 1 deletion rs/config/src/execution_environment.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ const TIB: u64 = 1024 * GIB;

const REPLICATED_INTER_CANISTER_LOG_FETCH_FEATURE: FlagStatus = FlagStatus::Enabled;

const FLEXIBLE_HTTP_REQUESTS_FEATURE: FlagStatus = FlagStatus::Disabled;
const FLEXIBLE_HTTP_REQUESTS_FEATURE: FlagStatus = FlagStatus::Enabled;

// TODO(DSM-105): remove after the feature is enabled by default.
pub const LOG_MEMORY_STORE_FEATURE_ENABLED: bool = true;
Expand Down
83 changes: 60 additions & 23 deletions rs/execution_environment/src/execution_environment/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3619,18 +3619,16 @@ fn execute_flexible_canister_http_request() {
}

#[test]
fn execute_flexible_canister_http_request_free_subnet_uses_legacy() {
// On a free subnet, flexible outcalls are available by default (without the
// `flexible_http_requests` feature flag) and fall back to legacy pricing,
// where pricing is moot because a free subnet charges nothing.
fn execute_flexible_canister_http_request_free_subnet_uses_pay_as_you_go() {
// Pay-as-you-go applies to every subnet, free ones included. Pricing is moot
// there — a free subnet charges nothing — but the request is still routed
// through the new pricing model rather than the legacy fallback.
let own_subnet = subnet_test_id(1);
let caller_canister = canister_test_id(10);
let mut test = ExecutionTestBuilder::new()
.with_own_subnet_id(own_subnet)
.with_caller(own_subnet, caller_canister)
.with_cost_schedule(CanisterCyclesCostSchedule::Free)
// The feature flag is deliberately left disabled: the free-subnet path
// does not depend on it.
.build();

let args = flexible_http_request_args(caller_canister);
Expand All @@ -3648,8 +3646,10 @@ fn execute_flexible_canister_http_request_free_subnet_uses_legacy() {
let http_request_context = canister_http_request_contexts
.get(&CallbackId::from(0))
.unwrap();
// The request is routed through legacy pricing with flexible replication.
assert_eq!(http_request_context.pricing_version, PricingVersion::Legacy);
assert_eq!(
http_request_context.pricing_version,
PricingVersion::PayAsYouGo
);
assert!(
matches!(
http_request_context.replication,
Expand All @@ -3659,9 +3659,9 @@ fn execute_flexible_canister_http_request_free_subnet_uses_legacy() {
http_request_context.replication
);

// Legacy pricing on a free subnet charges nothing: the full payment is
// retained (to be refunded when the response is delivered) and nothing is
// marked refundable through the pay-as-you-go mechanism.
// A free subnet charges nothing whatever the pricing model: the full payment
// is retained (to be refunded when the response is delivered) and there is no
// allowance to spend, hence nothing to refund out of one.
assert_eq!(http_request_context.request.payment, payment);
assert_eq!(
http_request_context.refund_status.refundable_cycles,
Expand All @@ -3674,17 +3674,14 @@ fn execute_flexible_canister_http_request_free_subnet_uses_legacy() {
}

#[test]
fn execute_flexible_canister_http_request_system_subnet_uses_legacy() {
fn execute_flexible_canister_http_request_system_subnet_uses_pay_as_you_go() {
// System subnets charge nothing for HTTP outcalls despite a normal cost
// schedule, so flexible outcalls are available there by default (without the
// feature flag) and fall back to legacy pricing.
// schedule. Like a free subnet, they are still routed through pay-as-you-go.
let own_subnet = subnet_test_id(1);
let caller_canister = canister_test_id(10);
let mut test = ExecutionTestBuilder::new()
.with_own_subnet_id(own_subnet)
.with_caller(own_subnet, caller_canister)
// A system subnet keeps the default (normal) cost schedule but charges
// zero for HTTP outcalls; the feature flag is left disabled.
.with_subnet_type(SubnetType::System)
.build();

Expand All @@ -3703,9 +3700,10 @@ fn execute_flexible_canister_http_request_system_subnet_uses_legacy() {
let http_request_context = canister_http_request_contexts
.get(&CallbackId::from(0))
.unwrap();
// Routed through legacy pricing with flexible replication, just like a
// free-cost-schedule subnet.
assert_eq!(http_request_context.pricing_version, PricingVersion::Legacy);
assert_eq!(
http_request_context.pricing_version,
PricingVersion::PayAsYouGo
);
assert!(
matches!(
http_request_context.replication,
Expand All @@ -3718,8 +3716,8 @@ fn execute_flexible_canister_http_request_system_subnet_uses_legacy() {
// A system subnet charges nothing for HTTP outcalls despite its normal cost
// schedule, so `try_add_http_context_to_replicated_state` treats it as free
// just like a free-cost-schedule subnet: the full payment is retained (to be
// refunded when the response is delivered) and nothing is marked refundable
// through the pay-as-you-go mechanism.
// refunded when the response is delivered) and there is no allowance to
// spend, hence nothing to refund out of one.
assert_eq!(http_request_context.request.payment, payment);
assert_eq!(
http_request_context.refund_status.refundable_cycles,
Expand Down Expand Up @@ -3823,13 +3821,14 @@ fn execute_flexible_canister_http_request_insufficient_payment() {
#[test]
fn execute_flexible_canister_http_request_disabled() {
// On a paying subnet, flexible outcalls under pay-as-you-go pricing are
// gated behind the `flexible_http_requests` feature flag, which is disabled
// by default.
// gated behind the `flexible_http_requests` feature flag. The flag now
// defaults to enabled, so turning it back off must still shut them out.
let own_subnet = subnet_test_id(1);
let caller_canister = canister_test_id(10);
let mut test = ExecutionTestBuilder::new()
.with_own_subnet_id(own_subnet)
.with_caller(own_subnet, caller_canister)
.with_flexible_http_requests_disabled()
.build();

let args = flexible_http_request_args(caller_canister);
Expand All @@ -3855,6 +3854,44 @@ fn execute_flexible_canister_http_request_disabled() {
);
}

#[test]
fn execute_flexible_canister_http_request_disabled_falls_back_to_legacy_when_free() {
// Turning the flag off does not take flexible outcalls away from subnets
// where they are free: there they fall back to legacy pricing, which is moot
// when nothing is charged. This is the one path that still distinguishes the
// flag being off from it being on.
let own_subnet = subnet_test_id(1);
let caller_canister = canister_test_id(10);
let mut test = ExecutionTestBuilder::new()
.with_own_subnet_id(own_subnet)
.with_caller(own_subnet, caller_canister)
.with_cost_schedule(CanisterCyclesCostSchedule::Free)
.with_flexible_http_requests_disabled()
.build();

let args = flexible_http_request_args(caller_canister);
test.inject_call_to_ic00(
Method::FlexibleHttpRequest,
args.encode(),
Cycles::new(1_000_000_000),
);
test.execute_all();

let canister_http_request_contexts = &test
.state()
.metadata
.subnet_call_context_manager
.canister_http_request_contexts;
assert_eq!(canister_http_request_contexts.len(), 1);
assert_eq!(
canister_http_request_contexts
.get(&CallbackId::from(0))
.unwrap()
.pricing_version,
PricingVersion::Legacy
);
}

fn get_reject_message(response: RequestOrResponse) -> String {
match response {
RequestOrResponse::Request(_) => panic!("Expected Response"),
Expand Down
26 changes: 0 additions & 26 deletions rs/https_outcalls/client/src/client.rs
Original file line number Diff line number Diff line change
Expand Up @@ -154,36 +154,10 @@ impl NonBlockingChannel<CanisterHttpRequest> for CanisterHttpAdapterClientImpl {
body: request_body,
http_method: request_http_method,
transform: request_transform,
pricing_version: request_pricing_version,
replication: request_replication,
..
} = request_context;

if request_pricing_version == ic_types::canister_http::PricingVersion::PayAsYouGo {
warn!(
log,
"Canister HTTP request with PayAsYouGo pricing is not supported yet: \
request_id {}, sender {}, process_id: {}",
request_id,
request_sender,
std::process::id(),
);
let _ = permit.send((
CanisterHttpResponse {
id: request_id,
canister_id: request_sender,
content: CanisterHttpResponseContent::Reject(CanisterHttpReject {
reject_code: RejectCode::SysFatal,
message:
"Canister HTTP request with PayAsYouGo pricing is not supported"
.to_string(),
}),
},
budget.create_payment_receipt(),
));
return;
}

let mut payload = async {
// Execute the HTTP request and get the adapter response.
let (adapter_response, downloaded_bytes, elapsed) = execute_http_request(
Expand Down
11 changes: 11 additions & 0 deletions rs/rust_canisters/proxy_canister/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -218,6 +218,17 @@ async fn check_response(
})
}

/// This canister's own cycle balance.
///
/// Under pay-as-you-go pricing an HTTP outcall's payment is taken up front and
/// the unspent part is credited back to the balance afterwards, rather than
/// returned as `msg_cycles_refunded` on the reply. Tests therefore observe what
/// an outcall actually cost by watching this.
#[query]
fn cycle_balance() -> u128 {
ic_cdk::api::canister_cycle_balance()
}

#[query]
fn transform(raw: TransformArgs) -> CanisterHttpResponsePayload {
let (response, _) = (raw.response, raw.context);
Expand Down
5 changes: 5 additions & 0 deletions rs/test_utilities/execution_environment/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2630,6 +2630,11 @@ impl ExecutionTestBuilder {
self
}

pub fn with_flexible_http_requests_disabled(mut self) -> Self {
self.execution_config.flexible_http_requests = FlagStatus::Disabled;
self
}

pub fn without_composite_queries(mut self) -> Self {
self.execution_config.composite_queries = FlagStatus::Disabled;
self
Expand Down
68 changes: 49 additions & 19 deletions rs/tests/networking/BUILD.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,24 @@ CANISTER_HTTP_BASE_DEPS = [
"@crate_index//:slog",
]

# The flexible-outcall test binaries are thin: the scenarios they run live in
# the shared library, so they only need it and `anyhow`.
CANISTER_HTTP_FLEXIBLE_DEPS = [
# Keep sorted.
"//rs/tests/networking/canister_http:canister_http",
"//rs/tests/networking/canister_http_flexible:canister_http_flexible",
"@crate_index//:anyhow",
]

# The correctness test binaries are thin: the scenarios they run live in the
# shared library, so they only need it, `anyhow` and the pricing-version constants.
CANISTER_HTTP_CORRECTNESS_DEPS = [
# Keep sorted.
"//rs/tests/networking/canister_http_correctness",
"//rs/types/management_canister_types",
"@crate_index//:anyhow",
]

COMMON_DEPS = [
# Keep sorted.
"//rs/limits",
Expand Down Expand Up @@ -63,6 +81,10 @@ system_test_nns(
deps = CANISTER_HTTP_BASE_DEPS + ["//rs/rust_canisters/canister_test"],
)

# The flexible-outcall scenarios run twice: once on a subnet where HTTP outcalls
# are free and once where they are paid for under pay-as-you-go. Both binaries
# share their scenarios via //rs/tests/networking/canister_http_flexible; the
# paying one adds the scenarios that only exist because it is charged.
system_test_nns(
name = "canister_http_flexible_test",
cpus = MIN_LOCAL_CPUS + 5 * DEFAULT_VCPUS_PER_VM + 1 * DEFAULT_VCPUS_PER_VM, # 5 IC Node VMs (1 system + 4 app) + 1 UVM (httpbin), 6 vCPUs each.
Expand All @@ -73,9 +95,20 @@ system_test_nns(
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",
deps = CANISTER_HTTP_FLEXIBLE_DEPS,
)

system_test_nns(
name = "canister_http_flexible_paying_test",
cpus = MIN_LOCAL_CPUS + 5 * DEFAULT_VCPUS_PER_VM + 1 * DEFAULT_VCPUS_PER_VM, # 5 IC Node VMs (1 system + 4 app) + 1 UVM (httpbin), 6 vCPUs each.
enable_uvm = True,
tags = [
"long_test", # since it exercises many outcall scenarios.
],
runtime_deps = CANISTER_HTTP_RUNTIME_DEPS | {
"PROXY_WASM_PATH": "//rs/rust_canisters/proxy_canister:proxy_canister",
},
deps = CANISTER_HTTP_FLEXIBLE_DEPS,
)

system_test_nns(
Expand Down Expand Up @@ -169,23 +202,20 @@ system_test_nns(
runtime_deps = CANISTER_HTTP_RUNTIME_DEPS | {
"PROXY_WASM_PATH": "//rs/rust_canisters/proxy_canister:proxy_canister",
},
deps = CANISTER_HTTP_BASE_DEPS + [
"//rs/config",
"//rs/cycles_account_manager",
"//rs/registry/subnet_type",
"//rs/rust_canisters/canister_test",
"//rs/test_utilities",
"//rs/test_utilities/types",
"//rs/types/base_types",
"//rs/types/cycles",
"//rs/types/types",
"@crate_index//:assert_matches",
"@crate_index//:ic-agent",
"@crate_index//:rand",
"@crate_index//:serde",
"@crate_index//:serde_json",
"@crate_index//:tokio",
],
deps = CANISTER_HTTP_CORRECTNESS_DEPS,
)

# The same correctness scenarios under the other pricing model. Both binaries share
# them via //rs/tests/networking/canister_http_correctness; each adds the scenarios
# that only exist under the model it picked.
system_test_nns(
name = "canister_http_correctness_pay_as_you_go_test",
cpus = MIN_LOCAL_CPUS + 5 * DEFAULT_VCPUS_PER_VM + 1 * DEFAULT_VCPUS_PER_VM, # 5 IC Node VMs (1 system + 4 app) + 1 UVM (httpbin), 6 vCPUs each.
enable_uvm = True,
runtime_deps = CANISTER_HTTP_RUNTIME_DEPS | {
"PROXY_WASM_PATH": "//rs/rust_canisters/proxy_canister:proxy_canister",
},
deps = CANISTER_HTTP_CORRECTNESS_DEPS,
)

system_test_nns(
Expand Down
Loading
Loading