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
6 changes: 6 additions & 0 deletions rs/config/src/subnet_config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -411,6 +411,9 @@ pub struct CyclesAccountManagerConfig {
/// Fee for storing a GiB of data per second.
pub gib_storage_per_second_fee: Cycles,

/// Base fee charged per second for every canister, regardless of resource usage.
pub base_per_second_fee: Cycles,

/// Fee for each percent of the reserved compute allocation. Note that
/// reserved compute allocation is a scarce resource, and should be
/// appropriately charged for.
Expand Down Expand Up @@ -480,6 +483,7 @@ impl CyclesAccountManagerConfig {
ingress_byte_reception_fee: Cycles::new(2_000),
// 10 SDR per GiB per year => 10e12 Cycles per year
gib_storage_per_second_fee: Cycles::new(317_500),
base_per_second_fee: Cycles::new(10_000),
duration_between_allocation_charges: Duration::from_secs(10),
ecdsa_signature_fee: ECDSA_SIGNATURE_FEE,
schnorr_signature_fee: SCHNORR_SIGNATURE_FEE,
Expand Down Expand Up @@ -513,6 +517,7 @@ impl CyclesAccountManagerConfig {
ingress_message_reception_fee: Cycles::new(0),
ingress_byte_reception_fee: Cycles::new(0),
gib_storage_per_second_fee: Cycles::new(0),
base_per_second_fee: Cycles::new(0),
duration_between_allocation_charges: Duration::from_secs(10),
// ECDSA and Schnorr signature fees are the fees charged when creating a
// signature on this subnet. The request likely came from a
Expand Down Expand Up @@ -549,6 +554,7 @@ impl CyclesAccountManagerConfig {
ingress_message_reception_fee: Cycles::zero(),
ingress_byte_reception_fee: Cycles::zero(),
gib_storage_per_second_fee: Cycles::zero(),
base_per_second_fee: Cycles::zero(),
compute_percent_allocated_per_second_fee: Cycles::zero(),
duration_between_allocation_charges: Duration::from_secs(u64::MAX),
ecdsa_signature_fee: Cycles::zero(),
Expand Down
27 changes: 26 additions & 1 deletion rs/cycles_account_manager/src/cycles_account_manager.rs
Original file line number Diff line number Diff line change
Expand Up @@ -156,6 +156,15 @@ impl CyclesAccountManager {
)
}

/// Returns the base fee per second charged to every canister that has any memory usage.
pub fn base_per_second_fee(
&self,
subnet_size: usize,
cost_schedule: CanisterCyclesCostSchedule,
) -> CompoundCycles<Memory> {
self.scale_cost(self.config.base_per_second_fee, subnet_size, cost_schedule)
}

/// Returns the fee per byte of ingress message received in [`Cycles`].
pub fn ingress_byte_received_fee(
&self,
Expand Down Expand Up @@ -227,7 +236,8 @@ impl CyclesAccountManager {
let memory = memory_allocation.allocated_bytes(memory_usage);

CyclesBurnedRate {
memory: self.memory_cost(memory, DAY, subnet_size, cost_schedule),
memory: self.memory_cost(memory, DAY, subnet_size, cost_schedule)
+ self.canister_base_cost(memory, DAY, subnet_size, cost_schedule),
message_memory: self.memory_cost(
message_memory_usage.total(),
DAY,
Expand Down Expand Up @@ -700,6 +710,21 @@ impl CyclesAccountManager {
self.scale_cost(cycles, subnet_size, cost_schedule)
}

pub fn canister_base_cost(
&self,
bytes: NumBytes,
duration: Duration,
subnet_size: usize,
cost_schedule: CanisterCyclesCostSchedule,
) -> CompoundCycles<Memory> {
let cycles = if bytes > NumBytes::new(0) {
self.config.base_per_second_fee * duration.as_secs() as u128
} else {
Cycles::zero()
};
self.scale_cost(cycles, subnet_size, cost_schedule)
}

/// Returns the amount of reserved cycles required for allocating the given
/// number of bytes at the given resource saturation level.
pub fn storage_reservation_cycles(
Expand Down
19 changes: 16 additions & 3 deletions rs/cycles_account_manager/tests/cycles_account_manager.rs
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,9 @@ fn test_can_charge_application_subnets() {
.real()
+ cycles_account_manager
.memory_cost(memory, duration, subnet_size, cost_schedule)
.real()
+ cycles_account_manager
.canister_base_cost(memory, duration, subnet_size, cost_schedule)
.real();
let initial_cycles = expected_fee;
canister.system_state.add_cycles(initial_cycles);
Expand Down Expand Up @@ -530,7 +533,15 @@ fn charge_canister_for_memory_usage() {
assert_eq!(
cycles_account_manager
.memory_cost(memory_usage, HOUR, SMALL_APP_SUBNET_MAX_SIZE, cost_schedule)
.real(),
.real()
+ cycles_account_manager
.canister_base_cost(
memory_usage,
HOUR,
SMALL_APP_SUBNET_MAX_SIZE,
cost_schedule
)
.real(),
cycles_burned
)
})
Expand Down Expand Up @@ -1230,10 +1241,12 @@ fn withdraw_cycles_for_transfer_checks_reserved_balance() {
let mut system_state = SystemState::new_running_for_testing(
canister_test_id(1),
canister_test_id(2).get(),
Cycles::new(2_000_000),
Cycles::new(21_000_000),
NumSeconds::from(1_000),
);
system_state.reserve_cycles(Cycles::new(1_000_000)).unwrap();
system_state
.reserve_cycles(Cycles::new(20_000_000))
.unwrap();
let mut new_balance = system_state.balance();
cycles_account_manager
.withdraw_cycles_for_transfer(
Expand Down
6 changes: 4 additions & 2 deletions rs/embedders/tests/system_api.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1500,7 +1500,7 @@ fn call_perform_not_enough_cycles_does_not_trap() {
/// it clamps the amount to the available cycles minus freeze threshold.
#[test]
fn cycles_burn128_clamps_to_available_cycles() {
const INITIAL_CYCLES: Cycles = Cycles::new(1000);
const INITIAL_CYCLES: Cycles = Cycles::new(200_000);

let cycles_account_manager = CyclesAccountManagerBuilder::new()
.with_subnet_type(SubnetType::Application)
Expand Down Expand Up @@ -1560,7 +1560,9 @@ fn growing_wasm_memory_updates_subnet_available_memory() {
SubnetAvailableMemory::new_for_testing(subnet_available_memory_bytes, 0, 0);
let wasm_custom_sections_available_memory_before =
subnet_available_memory.get_wasm_custom_sections_memory();
let system_state = SystemStateBuilder::default().build();
let system_state = SystemStateBuilder::default()
.initial_cycles(Cycles::new(20_000_000_000_000))
.build();
let cycles_account_manager = CyclesAccountManagerBuilder::new().build();
let api_type = ApiTypeBuilder::build_update_api();
let execution_parameters = execution_parameters(api_type.execution_mode());
Expand Down
1 change: 1 addition & 0 deletions rs/execution_environment/src/canister_manager/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2839,6 +2839,7 @@ fn install_code_preserves_system_state_and_scheduler_state() {
let certified_data = vec![42];
let mut original_canister = CanisterStateBuilder::new()
.with_canister_id(canister_id)
.with_cycles(Cycles::new(15_000_000_000_000))
.with_status(CanisterStatusType::Running)
.with_controller(controller)
.with_certified_data(certified_data.clone())
Expand Down
8 changes: 4 additions & 4 deletions rs/execution_environment/src/execution/response/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2755,10 +2755,10 @@ fn cycles_balance_changes_applied_correctly() {
.with_instruction_limit(20_000_000_000)
.build();
let a_id = test
.universal_canister_with_cycles(Cycles::new(10_000_000_000_000))
.universal_canister_with_cycles(Cycles::new(20_000_000_000_000))
.unwrap();
let b_id = test
.universal_canister_with_cycles(Cycles::new(301_000_000_000))
.universal_canister_with_cycles(Cycles::new(400_000_000_000))
.unwrap();

test.ingress(
Expand Down Expand Up @@ -2811,7 +2811,7 @@ fn test_cycles_burn() {
let canister_memory_usage = NumBytes::from(1_000_000);
let canister_message_memory_usage = MessageMemoryUsage::ZERO;

let amount = 1_000_000_000;
let amount = 100_000_000_000;
let mut balance = Cycles::new(amount);
let amount_to_burn = Cycles::new(amount / 10);

Expand Down Expand Up @@ -2850,7 +2850,7 @@ fn cycles_burn_up_to_the_threshold_on_not_enough_cycles() {
Cycles::zero(),
);

let amount = 1_000_000_000;
let amount = 100_000_000_000;
let mut balance = Cycles::new(amount);

let burned = test.cycles_account_manager().cycles_burn(
Expand Down
4 changes: 2 additions & 2 deletions rs/execution_environment/src/execution/upgrade/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -258,7 +258,7 @@ fn upgrade_fails_on_not_enough_cycles() {
fn upgrade_fails_on_no_execution_state() {
let mut test = execution_test_with_max_rounds(1);
// Create canister with no binary and hence no execution state
let canister_id = test.create_canister(1_000_000_000_u64.into());
let canister_id = test.create_canister(30_000_000_000_u64.into());
let canister_state_before = test.canister_state(canister_id).clone();

let result = test.upgrade_canister(canister_id, new_empty_binary());
Expand Down Expand Up @@ -970,7 +970,7 @@ fn dts_uninstall_with_aborted_upgrade() {
fn upgrade_with_skip_pre_upgrade_fails_on_no_execution_state() {
let mut test = execution_test_with_max_rounds(1);
// Create canister with no binary and hence no execution state
let canister_id = test.create_canister(1_000_000_000_u64.into());
let canister_id = test.create_canister(30_000_000_000_u64.into());
let canister_state_before = test.canister_state(canister_id).clone();

let result = test.upgrade_canister_v2(
Expand Down
6 changes: 6 additions & 0 deletions rs/execution_environment/src/execution_environment/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -833,6 +833,12 @@ fn get_canister_status_from_another_canister_when_memory_low() {
.real()
.get())
/ one_gib
+ test
.cycles_account_manager()
.base_per_second_fee(test.subnet_size(), CanisterCyclesCostSchedule::Normal)
.real()
.get()
* seconds_per_day
);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -665,7 +665,7 @@ fn global_timer_refunds_cycles_for_request_in_prep() {
.unwrap();

let canister_id = env
.install_canister_with_cycles(binary, vec![], None, Cycles::new(301_000_000_000))
.install_canister_with_cycles(binary, vec![], None, Cycles::new(400_000_000_000))
.unwrap();

let result = env.execute_ingress(canister_id, "test", vec![]).unwrap();
Expand Down Expand Up @@ -729,7 +729,7 @@ fn global_timer_set_returns_zero_in_canister_global_timer_method() {
.unwrap();

let canister_id = env
.install_canister_with_cycles(binary, vec![], None, Cycles::new(301_000_000_000))
.install_canister_with_cycles(binary, vec![], None, Cycles::new(400_000_000_000))
.unwrap();

let result = env
Expand Down
13 changes: 13 additions & 0 deletions rs/execution_environment/src/scheduler/test_utilities.rs
Original file line number Diff line number Diff line change
Expand Up @@ -755,6 +755,19 @@ impl SchedulerTest {
)
}

pub fn canister_base_cost(
&self,
bytes: NumBytes,
duration: Duration,
) -> CompoundCycles<ic_types_cycles::Memory> {
self.scheduler.cycles_account_manager.canister_base_cost(
bytes,
duration,
self.subnet_size(),
self.state.as_ref().unwrap().get_own_cost_schedule(),
)
}

pub fn compute_allocation_cost(
&self,
compute_allocation: ComputeAllocation,
Expand Down
17 changes: 15 additions & 2 deletions rs/execution_environment/src/scheduler/tests/charging.rs
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,12 @@ fn only_charge_for_allocation_after_specified_duration() {
test.execute_round(ExecutionRoundType::OrdinaryRound);
assert_eq!(
test.canister_state(canister).system_state.balance().get(),
initial_cycles - 10,
initial_cycles
- 10
- test
.canister_base_cost(NumBytes::from(bytes_per_cycle), time_between_batches * 2,)
.real()
.get(),
);
}

Expand Down Expand Up @@ -129,6 +134,9 @@ fn charging_for_message_memory_works() {
assert_eq!(
canister_state.system_state.balance(),
balance_before
- test
.canister_base_cost(canister_state.memory_usage(), charge_duration)
.real()
- test
.memory_cost(
canister_state.message_memory_usage().total(),
Expand Down Expand Up @@ -186,6 +194,9 @@ fn charging_for_logging_memory_works() {
assert_eq!(
canister_state.system_state.balance(),
balance_before
- test
.canister_base_cost(canister_state.memory_usage(), charge_duration)
.real()
- test
.memory_cost(
canister_state.log_memory_store_memory_usage(),
Expand Down Expand Up @@ -339,7 +350,9 @@ fn dont_charge_allocations_for_paused_canisters() {
fn assert_balance_change(test: &SchedulerTest, canister: CanisterId, duration: Duration) {
assert_eq!(
test.canister_state(canister).system_state.balance(),
INITIAL_CYCLES - test.memory_cost(MEMORY_ALLOCATION, duration).real()
INITIAL_CYCLES
- test.memory_cost(MEMORY_ALLOCATION, duration).real()
- test.canister_base_cost(MEMORY_ALLOCATION, duration).real()
);
}
// Balance has changed for the canister with no paused execution.
Expand Down
18 changes: 6 additions & 12 deletions rs/execution_environment/src/scheduler/tests/metrics.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1184,18 +1184,17 @@ fn consumed_cycles_for_resource_allocations_are_updated_from_valid_canisters() {
&no_op_logger(),
);

let expected_memory_cycles = (test.memory_cost(memory_allocation, duration)
+ test.canister_base_cost(memory_allocation, duration))
.nominal()
.get() as f64;
assert_eq!(
fetch_gauge_vec(
test.metrics_registry(),
"replicated_state_consumed_cycles_from_replica_start",
),
metric_vec(&[
(
&[("use_case", "Memory")],
test.memory_cost(memory_allocation, duration)
.nominal()
.get() as f64
),
(&[("use_case", "Memory")], expected_memory_cycles),
(
&[("use_case", "ComputeAllocation")],
test.compute_allocation_cost(compute_allocation, duration)
Expand All @@ -1210,12 +1209,7 @@ fn consumed_cycles_for_resource_allocations_are_updated_from_valid_canisters() {
"replicated_state_consumed_cycles_from_replica_start_as_counters",
),
metric_vec(&[
(
&[("use_case", "Memory")],
test.memory_cost(memory_allocation, duration)
.nominal()
.get() as f64
),
(&[("use_case", "Memory")], expected_memory_cycles),
(
&[("use_case", "ComputeAllocation")],
test.compute_allocation_cost(compute_allocation, duration)
Expand Down
2 changes: 1 addition & 1 deletion rs/execution_environment/tests/canister_logging.rs
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ const MAX_INSTRUCTIONS_PER_ROUND: NumInstructions = NumInstructions::new(5 * B);
const MAX_INSTRUCTIONS_PER_MESSAGE: NumInstructions = NumInstructions::new(25 * B);
const MAX_INSTRUCTIONS_PER_SLICE: NumInstructions = NumInstructions::new(5 * B);

const CANISTER_INIT_CYCLES: Cycles = Cycles::new(310_000_000_000_u128);
const CANISTER_INIT_CYCLES: Cycles = Cycles::new(400_000_000_000_u128);

fn system_time_to_nanos(t: SystemTime) -> u64 {
t.duration_since(SystemTime::UNIX_EPOCH).unwrap().as_nanos() as u64
Expand Down
16 changes: 11 additions & 5 deletions rs/execution_environment/tests/dts.rs
Original file line number Diff line number Diff line change
Expand Up @@ -467,9 +467,15 @@ fn dts_install_code_with_concurrent_ingress_insufficient_cycles_and_freezing_thr
.compute_percent_allocated_per_second_fee
* freezing_threshold;

// A freshly created canister has non-zero memory (canister history), so the
// base_per_second_fee also contributes to the freeze threshold.
let base_per_second_fee_cycles =
config.cycles_account_manager_config.base_per_second_fee * freezing_threshold;

// The initial balance is sufficient to only pay the reservation for installing code
// and the compute allocation during the freezing threshold.
let initial_balance = max_install_code_cost() + compute_allocation_cycles;
// and the freeze threshold (compute allocation + base fee).
let initial_balance =
max_install_code_cost() + compute_allocation_cycles + base_per_second_fee_cycles;
Comment thread
schneiderstefan marked this conversation as resolved.

let canister_id = env.create_canister_with_cycles(
None,
Expand Down Expand Up @@ -506,7 +512,7 @@ fn dts_install_code_with_concurrent_ingress_insufficient_cycles_and_freezing_thr
// i.e., consuming any cycles would make the canister frozen.
assert_eq!(
env.cycle_balance(canister_id),
compute_allocation_cycles.get()
(compute_allocation_cycles + base_per_second_fee_cycles).get()
);
let sender = PrincipalId::new_anonymous();
let method = "update";
Expand Down Expand Up @@ -534,9 +540,9 @@ fn dts_install_code_with_concurrent_ingress_insufficient_cycles_and_freezing_thr
let err = env.await_ingress(install_code_ingress_id, 100).unwrap_err();
assert_eq!(err.code(), ErrorCode::CanisterInstructionLimitExceeded);

// The cycles to cover the compute allocation during the freezing threshold are only needed to keep the canister unfrozen
// The cycles to cover the freeze threshold are only needed to keep the canister unfrozen
// and are not actually used.
let unused_cycles = compute_allocation_cycles;
let unused_cycles = compute_allocation_cycles + base_per_second_fee_cycles;
assert_eq!(env.cycle_balance(canister_id), unused_cycles.get());
}

Expand Down
Loading
Loading