Skip to content
10 changes: 10 additions & 0 deletions packages/ic-management-canister-types/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -161,6 +161,14 @@ pub struct CanisterSettings {
///
/// Default value: `null` (i.e., no environment variables provided).
pub environment_variables: Option<Vec<EnvironmentVariable>>,
/// Indicates the minimum number of cycles required for an incoming message
/// from another canister. Messages with fewer cycles are rejected with a
/// `CanisterError`. Ingress messages are not affected.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ingress messages are not affected.

I was thinking this kind of defeats the purpose. Took me a while to realize that there's [whatever the single-replica ingress message filtering mechanism is called]. Maybe mention that, just in case someone is looking at the implementation, trying to figure out how to deal with call spam.

///
/// Must be a number between 0 and 2<sup>128</sup>-1, inclusively.
///
/// Default value: `0` (i.e., no minimum enforced).
pub minimum_msg_cycles_available: Option<Nat>,
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

When I first read the name I thought this was some sort of freezing threshold for cycles reserved for outgoing calls. Maybe we should call it something like "minimum [call] payment"? Or, if you think "pauyment" is only used in the implementation, not the spec, and is thus potentially confusing, then "attached cycles"?

"Available" sounds too much like "balance" or "reservation".

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The motivation for this name was the system API ic0.msg_cycles_available which returns how many cycles have been transferred by the caller (and not yet accepted by the callee) so the new setting is a lower bound for the value of that system API at the very beginning of the call.

But I see your confusion: how does minimum_incoming_call_cycles or minimum_canister_call_cycles sound? (Focus on either incoming/outgoing and ingress/inter-canister, respectively, the combined minimum_incoming_canister_call_cycles is the most precise, but pretty long.)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

minimum_canister_call_cycles sounds more obvious.

I understand now where it comes from, but I suppose msg_cycles_available is both somewhat more specific and somewhat unfortunate too. (o:


/// # Definite Canister Settings
Expand Down Expand Up @@ -194,6 +202,8 @@ pub struct DefiniteCanisterSettings {
pub wasm_memory_threshold: Nat,
/// A list of environment variables.
pub environment_variables: Vec<EnvironmentVariable>,
/// Minimum number of cycles required for an incoming canister-to-canister message.
pub minimum_msg_cycles_available: Nat,
}

/// # Create Canister Args
Expand Down
2 changes: 2 additions & 0 deletions packages/ic-management-canister-types/tests/ic.did
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ type canister_settings = record {
wasm_memory_limit : opt nat;
wasm_memory_threshold : opt nat;
environment_variables : opt vec environment_variable;
minimum_msg_cycles_available : opt nat;
};

type definite_canister_settings = record {
Expand All @@ -45,6 +46,7 @@ type definite_canister_settings = record {
wasm_memory_limit : nat;
wasm_memory_threshold : nat;
environment_variables : vec environment_variable;
minimum_msg_cycles_available : nat;
};

type change_origin = variant {
Expand Down
2 changes: 1 addition & 1 deletion rs/cycles_account_manager/src/cycles_account_manager.rs
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ const DAY: Duration = Duration::from_secs(SECONDS_PER_DAY as u64);

/// Maximum payload size of a management call to update_settings
/// overriding the canister's freezing threshold.
const MAX_DELAYED_INGRESS_COST_PAYLOAD_SIZE: usize = 338;
const MAX_DELAYED_INGRESS_COST_PAYLOAD_SIZE: usize = 352;
Comment thread
mraszyk marked this conversation as resolved.

struct CyclesBurnedRate {
memory: CompoundCycles<Memory>,
Expand Down
7 changes: 7 additions & 0 deletions rs/execution_environment/src/canister_manager.rs
Original file line number Diff line number Diff line change
Expand Up @@ -367,6 +367,11 @@ impl CanisterManager {
canister.system_state.wasm_memory_limit = Some(wasm_memory_limit);
}

// Minimum message cycles available: apply.
if let Some(minimum_msg_cycles_available) = settings.minimum_msg_cycles_available() {
canister.system_state.minimum_msg_cycles_available = minimum_msg_cycles_available;
}

// Environment variables: validate and apply.
if let Some(environment_variables) = settings.environment_variables() {
self.validate_environment_variables(environment_variables)?;
Expand Down Expand Up @@ -1106,6 +1111,7 @@ impl CanisterManager {
let log_memory_limit = canister.log_memory_limit().get();
let wasm_memory_limit = canister.system_state.wasm_memory_limit;
let wasm_memory_threshold = canister.system_state.wasm_memory_threshold;
let minimum_msg_cycles_available = canister.system_state.minimum_msg_cycles_available;

Ok(CanisterStatusResultV2::new(
canister.status(),
Expand Down Expand Up @@ -1153,6 +1159,7 @@ impl CanisterManager {
wasm_memory_limit.map(|x| x.get()),
wasm_memory_threshold.get(),
canister.system_state.environment_variables.clone(),
minimum_msg_cycles_available.get(),
))
}

Expand Down
34 changes: 34 additions & 0 deletions rs/execution_environment/src/canister_settings.rs
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ pub(crate) struct CanisterSettings {
pub(crate) log_memory_limit: Option<NumBytes>,
pub(crate) wasm_memory_limit: Option<NumBytes>,
pub(crate) environment_variables: Option<EnvironmentVariables>,
pub(crate) minimum_msg_cycles_available: Option<Cycles>,
}

impl CanisterSettings {
Expand All @@ -45,6 +46,7 @@ impl CanisterSettings {
log_memory_limit: Option<NumBytes>,
wasm_memory_limit: Option<NumBytes>,
environment_variables: Option<EnvironmentVariables>,
minimum_msg_cycles_available: Option<Cycles>,
) -> Self {
Self {
controllers,
Expand All @@ -58,6 +60,7 @@ impl CanisterSettings {
log_memory_limit,
wasm_memory_limit,
environment_variables,
minimum_msg_cycles_available,
}
}

Expand Down Expand Up @@ -104,6 +107,10 @@ impl CanisterSettings {
pub fn environment_variables(&self) -> Option<&EnvironmentVariables> {
self.environment_variables.as_ref()
}

pub fn minimum_msg_cycles_available(&self) -> Option<Cycles> {
self.minimum_msg_cycles_available
}
}

impl TryFrom<CanisterSettingsArgs> for CanisterSettings {
Expand Down Expand Up @@ -189,6 +196,13 @@ impl TryFrom<CanisterSettingsArgs> for CanisterSettings {
None => None,
};

let minimum_msg_cycles_available = match input.minimum_msg_cycles_available {
Some(min) => Some(Cycles::from(min.0.to_u128().ok_or(
UpdateSettingsError::MinimumMsgCyclesAvailableOutOfRange { provided: min },
)?)),
None => None,
};

Ok(CanisterSettings::new(
input
.controllers
Expand All @@ -203,6 +217,7 @@ impl TryFrom<CanisterSettingsArgs> for CanisterSettings {
log_memory_limit,
wasm_memory_limit,
environment_variables,
minimum_msg_cycles_available,
))
}
}
Expand Down Expand Up @@ -230,6 +245,7 @@ pub(crate) struct CanisterSettingsBuilder {
log_memory_limit: Option<NumBytes>,
wasm_memory_limit: Option<NumBytes>,
environment_variables: Option<EnvironmentVariables>,
minimum_msg_cycles_available: Option<Cycles>,
}

#[allow(dead_code)]
Expand All @@ -247,6 +263,7 @@ impl CanisterSettingsBuilder {
log_memory_limit: None,
wasm_memory_limit: None,
environment_variables: None,
minimum_msg_cycles_available: None,
}
}

Expand All @@ -263,6 +280,7 @@ impl CanisterSettingsBuilder {
log_memory_limit: self.log_memory_limit,
wasm_memory_limit: self.wasm_memory_limit,
environment_variables: self.environment_variables,
minimum_msg_cycles_available: self.minimum_msg_cycles_available,
}
}

Expand Down Expand Up @@ -342,6 +360,13 @@ impl CanisterSettingsBuilder {
..self
}
}

pub fn with_minimum_msg_cycles_available(self, minimum_msg_cycles_available: Cycles) -> Self {
Self {
minimum_msg_cycles_available: Some(minimum_msg_cycles_available),
..self
}
}
}

pub enum UpdateSettingsError {
Expand All @@ -353,6 +378,7 @@ pub enum UpdateSettingsError {
WasmMemoryThresholdOutOfRange { provided: candid::Nat },
DuplicateEnvironmentVariables,
LogMemoryLimitOutOfRange { provided: candid::Nat },
MinimumMsgCyclesAvailableOutOfRange { provided: candid::Nat },
}

impl From<UpdateSettingsError> for UserError {
Expand Down Expand Up @@ -405,6 +431,14 @@ impl From<UpdateSettingsError> for UserError {
"Log memory limit expected to be in the range of [0..2^64-1], got {provided}"
),
),
UpdateSettingsError::MinimumMsgCyclesAvailableOutOfRange { provided } => {
UserError::new(
ErrorCode::CanisterContractViolation,
format!(
"Minimum message cycles available expected to be in the range of [0..2^128-1], got {provided}"
),
)
}
}
}
}
Expand Down
28 changes: 28 additions & 0 deletions rs/execution_environment/src/execution_environment.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2300,6 +2300,34 @@ impl ExecutionEnvironment {
);
}
CanisterMessageOrTask::Message(CanisterMessage::Request(request)) => {
let min_cycles = canister.system_state.minimum_msg_cycles_available;
if request.payment < min_cycles {
let canister_id = canister.canister_id();
let payment = request.payment;
let originator = request.sender;
let originator_reply_callback = request.sender_reply_callback;
let deadline = request.deadline;
return ExecuteMessageResult::Finished {
canister,
response: ExecutionResponse::Request(Response {
originator,
respondent: canister_id,
originator_reply_callback,
refund: payment,
response_payload: Payload::Reject(RejectContext::new(
RejectCode::CanisterError,
format!(
"Canister {} requires at least {} transferred cycles for incoming inter-canister messages, but the message only has {} cycles.",
canister_id, min_cycles, payment,
),
Comment thread
mraszyk marked this conversation as resolved.
)),
deadline,
}),
instructions_used: NumInstructions::from(0),
heap_delta: NumBytes::from(0),
call_duration: None,
};
}
CanisterCall::Request(request)
}
CanisterMessageOrTask::Message(CanisterMessage::Ingress(ingress)) => {
Expand Down
Loading
Loading