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
47 changes: 10 additions & 37 deletions packages/pocket-ic/tests/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2848,10 +2848,12 @@ fn test_canister_http_derived_spend_is_only_charged_under_pay_as_you_go() {
/// Which outcalls are available, and which pricing model they end up with, on the
/// three kinds of instance a test can build: one with beta features, a plain one,
/// and one whose subnet does not charge for HTTP outcalls.
///
/// The pay-as-you-go pricing model is enabled on every subnet, so all three offer
/// flexible outcalls and price them the same way; what differs between them is
/// only whether anything is actually charged.
#[test]
fn test_canister_http_availability_and_pricing() {
// (label, instance, canister, whether the pay-as-you-go pricing model is
// enabled, whether HTTP outcalls are free)
let beta = flexible_outcalls_pic();
let beta_canister = deploy_test_canister(&beta);
let default = PocketIc::new();
Expand All @@ -2862,10 +2864,10 @@ fn test_canister_http_availability_and_pricing() {
system.add_cycles(system_canister, INIT_CYCLES);
system.install_canister(system_canister, test_canister_wasm(), vec![], None);

for (env, pic, canister_id, pay_as_you_go_enabled, outcalls_are_free) in [
("beta features", &beta, beta_canister, true, false),
("default instance", &default, default_canister, false, false),
("system subnet", &system, system_canister, false, true),
for (env, pic, canister_id) in [
("beta features", &beta, beta_canister),
("default instance", &default, default_canister),
("system subnet", &system, system_canister),
] {
for outcall in [
Outcall::FullyReplicated {
Expand All @@ -2883,38 +2885,9 @@ fn test_canister_http_availability_and_pricing() {
})),
] {
let label = format!("{env}, {}", outcall.label());
// A flexible outcall is only offered where the pay-as-you-go pricing
// model is enabled or where HTTP outcalls are free.
if outcall.is_flexible() && !pay_as_you_go_enabled && !outcalls_are_free {
Comment thread
pierugo-dfinity marked this conversation as resolved.
Resolved
let (method, args) = outcall.call(None);
let reply = pic
.update_call(
canister_id,
Principal::anonymous(),
"proxy_call",
proxy_call_arg(method, args, OUTCALL_CYCLES),
)
.unwrap();
let (reject_code, message) = decode_sync_rejection(&reply);
assert!(
matches!(reject_code, RejectionCode::CanisterError),
"{label}: unexpected reject code {reject_code:?} ({message})"
);
assert!(
message.contains("This API is not enabled on this subnet"),
"{label}: unexpected rejection message {message:?}"
);
continue;
}
// `submit_outcall` asserts the reported replication and pricing model.
let (call_id, request) = submit_outcall(
pic,
canister_id,
&outcall,
None,
OUTCALL_CYCLES,
pay_as_you_go_enabled,
);
let (call_id, request) =
submit_outcall(pic, canister_id, &outcall, None, OUTCALL_CYCLES, true);
let body = b"hello".to_vec();
if outcall.is_flexible() {
mock_committee(
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;

pub const TEST_DEFAULT_LOG_MEMORY_LIMIT: u64 = 4 * KIB;
pub const TEST_DEFAULT_LOG_MEMORY_USAGE: u64 = 4 * KIB + 4 * KIB + TEST_DEFAULT_LOG_MEMORY_LIMIT; // header, index table, data region
Expand Down
139 changes: 112 additions & 27 deletions rs/execution_environment/src/execution_environment/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3984,12 +3984,14 @@ fn execute_canister_http_request_pricing_version() {

let own_subnet = subnet_test_id(1);
let caller_canister = canister_test_id(10);
let mut builder = ExecutionTestBuilder::new()
let builder = ExecutionTestBuilder::new()
.with_own_subnet_id(own_subnet)
.with_caller(own_subnet, caller_canister);
if flexible_http_requests_enabled {
builder = builder.with_flexible_http_requests_enabled();
}
let builder = if flexible_http_requests_enabled {
builder.with_flexible_http_requests_enabled()
} else {
builder.with_flexible_http_requests_disabled()
};
let mut test = builder.build();
std::sync::Arc::make_mut(&mut test.state_mut().metadata.own_subnet_info)
.subnet_features
Expand Down Expand Up @@ -4022,18 +4024,18 @@ fn execute_canister_http_request_pricing_version() {
}

#[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() {
// With the `flexible_http_requests` feature flag enabled, 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.
.with_flexible_http_requests_enabled()
.build();

let args = flexible_http_request_args(caller_canister);
Expand All @@ -4051,8 +4053,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
);
Comment thread
pierugo-dfinity marked this conversation as resolved.
Resolved
assert!(
matches!(
http_request_context.replication,
Expand All @@ -4062,9 +4066,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 @@ -4077,18 +4081,17 @@ 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
// once the `flexible_http_requests` feature flag is enabled.
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)
.with_flexible_http_requests_enabled()
.build();

let args = flexible_http_request_args(caller_canister);
Expand All @@ -4106,9 +4109,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 @@ -4121,8 +4125,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 @@ -4230,13 +4234,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: with the flag
// disabled they are not offered at all.
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 @@ -4262,6 +4267,86 @@ fn execute_flexible_canister_http_request_disabled() {
);
}

#[test]
fn execute_flexible_canister_http_request_disabled_falls_back_to_legacy_when_free() {
// A disabled flag 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. These fallbacks are what 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
);
}

#[test]
fn execute_flexible_canister_http_request_disabled_falls_back_to_legacy_on_system_subnet() {
// Same fallback as on a free cost schedule, via a different route: a system
// subnet keeps a normal cost schedule but charges zero for HTTP outcalls, so
// it is treated as free here. Flexible outcalls therefore remain available
// with the flag disabled, under legacy pricing.
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_subnet_type(SubnetType::System)
.with_flexible_http_requests_disabled()
.build();
assert_eq!(
test.state().get_own_cost_schedule(),
CanisterCyclesCostSchedule::Normal
);

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
);
}

#[test]
fn execute_flexible_canister_http_request_disabled_by_subnet_feature() {
/// The configurations in which flexible outcalls would be available if the
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 @@ -2719,6 +2719,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
Loading
Loading