Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: [EXC-1753] Add mint_cycles128 API #2589

Merged
merged 50 commits into from
Dec 11, 2024
Merged
Show file tree
Hide file tree
Changes from 23 commits
Commits
Show all changes
50 commits
Select commit Hold shift + click to select a range
2b0b6c6
add test
Nov 13, 2024
4a4e1c0
implement ic0_mint_cycles128
Nov 13, 2024
8b60e47
Merge remote-tracking branch 'origin' into mwe/cycles128
Nov 13, 2024
9416944
tests
Nov 13, 2024
c4d6081
Merge branch 'master' into mwe/cycles128
michael-weigelt Nov 15, 2024
9861e23
Merge remote-tracking branch 'origin' into mwe/cycles128
Nov 19, 2024
fb6ff99
Merge branch 'mwe/cycles128' of github.com:dfinity/ic into mwe/cycles128
Nov 19, 2024
a906690
Merge remote-tracking branch 'origin' into mwe/cycles128
Nov 19, 2024
c778b09
todo markers
Nov 19, 2024
795835c
uni canister
Nov 19, 2024
db9ed21
added SystemApiCallId
Nov 20, 2024
9c5d9ac
uni canister public endpoint
Nov 21, 2024
feee57f
use uni canister in mint_cycles128 tests
Nov 21, 2024
8676bb3
comment
Nov 21, 2024
481cf48
add benchmark code
Nov 21, 2024
1779139
benchmark costs
Nov 21, 2024
fb87092
benchmark types
Nov 22, 2024
ff1aacb
update benchmark numbers
michael-weigelt Nov 22, 2024
9f11542
syscall
Nov 22, 2024
aa9fcce
Merge remote-tracking branch 'origin' into mwe/cycles128
Nov 22, 2024
c38b8ce
clippy
Nov 22, 2024
2625f15
Merge branch 'master' into mwe/cycles128
michael-weigelt Nov 25, 2024
f6896c7
Merge branch 'master' into mwe/cycles128
michael-weigelt Nov 25, 2024
72e6b5e
systests
michael-weigelt Nov 25, 2024
179131f
Merge branch 'mwe/cycles128' of github.com:dfinity/ic into mwe/cycles128
michael-weigelt Nov 25, 2024
86cd708
fix
michael-weigelt Nov 25, 2024
6d39991
Merge branch 'master' into mwe/cycles128
michael-weigelt Nov 25, 2024
e5ff2f3
Merge branch 'master' into mwe/cycles128
michael-weigelt Nov 25, 2024
a2afe58
change interface to cycles
michael-weigelt Dec 9, 2024
54240a6
removed doc line
michael-weigelt Dec 9, 2024
0ac0940
small fixes
michael-weigelt Dec 9, 2024
0ab93bc
return actually minted cycles
michael-weigelt Dec 9, 2024
16b74f8
comments
michael-weigelt Dec 9, 2024
5f814ae
clippy
michael-weigelt Dec 9, 2024
345761c
add saturation tests
michael-weigelt Dec 9, 2024
33cbf1a
comment
michael-weigelt Dec 9, 2024
254f006
factor out shared code
michael-weigelt Dec 9, 2024
5973999
comments
michael-weigelt Dec 9, 2024
340e2a5
test
michael-weigelt Dec 10, 2024
2f53076
clippy
michael-weigelt Dec 10, 2024
edbf373
Merge remote-tracking branch 'origin' into mwe/cycles128
michael-weigelt Dec 10, 2024
01bc808
comments
michael-weigelt Dec 11, 2024
047d0b4
comments
michael-weigelt Dec 11, 2024
ea6218c
Merge remote-tracking branch 'origin' into mwe/cycles128
michael-weigelt Dec 11, 2024
b5a3782
Update rs/tests/execution/general_execution_tests/nns_shielding.rs
michael-weigelt Dec 11, 2024
ec9bb66
Merge branch 'master' into mwe/cycles128
michael-weigelt Dec 11, 2024
1982338
bracket
michael-weigelt Dec 11, 2024
aded590
Merge branch 'mwe/cycles128' of github.com:dfinity/ic into mwe/cycles128
michael-weigelt Dec 11, 2024
a9276b4
Merge branch 'master' into mwe/cycles128
michael-weigelt Dec 11, 2024
436e909
Merge branch 'master' into mwe/cycles128
michael-weigelt Dec 11, 2024
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
10 changes: 10 additions & 0 deletions rs/embedders/src/wasm_utils/validation.rs
Original file line number Diff line number Diff line change
Expand Up @@ -527,6 +527,16 @@ fn get_valid_system_apis_common(I: ValType) -> HashMap<String, HashMap<String, F
},
)],
),
(
"mint_cycles128",
vec![(
API_VERSION_IC0,
FunctionSignature {
param_types: vec![ValType::I64, ValType::I64, I],
return_type: vec![],
},
)],
),
(
"call_cycles_add128",
vec![(
Expand Down
12 changes: 12 additions & 0 deletions rs/embedders/src/wasmtime_embedder/system_api.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1190,6 +1190,18 @@ pub fn syscalls<
})
.unwrap();

linker
.func_wrap("ic0", "mint_cycles128", {
move |mut caller: Caller<'_, StoreData>, amount_high: u64, amount_low: u64, dst: I| {
with_memory_and_system_api(&mut caller, |s, memory| {
let dst: usize = dst.try_into().expect("Failed to convert I to usize");
s.ic0_mint_cycles128(amount_high, amount_low, dst, memory)
})
.map_err(|e| anyhow::Error::msg(format!("ic0_mint_cycles128 failed: {}", e)))
}
})
.unwrap();

linker
.func_wrap("ic0", "cycles_burn128", {
move |mut caller: Caller<'_, StoreData>, amount_high: u64, amount_low: u64, dst: I| {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,11 @@ set -ue
## | update/ic0_canister_status() | 1.27G | 1.34G | +5% | 3.73s |
## | inspect/ic0_msg_method_name_size() | - | 1.28G | - | 23.92s |

if ! which bazel rg >/dev/null; then
echo "Error checking dependencies: please ensure 'bazel' and 'rg' are installed"
exit 1
fi

## To quickly assess the new changes, run benchmarks just once
QUICK=${QUICK:-}
if [ -n "${QUICK}" ]; then
Expand Down
26 changes: 23 additions & 3 deletions rs/execution_environment/benches/system_api/execute_update.rs
Original file line number Diff line number Diff line change
Expand Up @@ -336,7 +336,7 @@ pub fn execute_update_bench(c: &mut Criterion) {
Result::No,
Wasm64::Enabled,
), // 10B max
529001006,
529004006,
),
common::Benchmark(
"wasm32/ic0_debug_print()/1B".into(),
Expand Down Expand Up @@ -736,7 +736,7 @@ pub fn execute_update_bench(c: &mut Criterion) {
Result::No,
Wasm64::Enabled,
),
517001006,
517004006,
),
common::Benchmark(
"wasm32/ic0_msg_cycles_available()".into(),
Expand Down Expand Up @@ -878,6 +878,26 @@ pub fn execute_update_bench(c: &mut Criterion) {
Module::Test.from_ic0("mint_cycles", Param1(1_i64), Result::I64, Wasm64::Enabled),
18000006,
),
common::Benchmark(
"wasm32/ic0_mint_cycles128()".into(),
Module::Test.from_ic0(
"mint_cycles128",
Params3(1_i64, 2_i64, 3_i32),
Result::No,
Wasm64::Disabled,
),
19001006,
),
common::Benchmark(
"wasm64/ic0_mint_cycles128()".into(),
Module::Test.from_ic0(
"mint_cycles128",
Params3(1_i64, 2_i64, 3_i64),
Result::No,
Wasm64::Enabled,
),
19004006,
),
common::Benchmark(
"wasm32/ic0_is_controller()".into(),
Module::Test.from_ic0(
Expand Down Expand Up @@ -922,7 +942,7 @@ pub fn execute_update_bench(c: &mut Criterion) {
"wasm32/ic0_cycles_burn128()".into(),
Module::Test.from_ic0(
"cycles_burn128",
Params3(1_i64, 2_i64, 3),
Params3(1_i64, 2_i64, 3_i32),
Result::No,
Wasm64::Disabled,
),
Expand Down
82 changes: 82 additions & 0 deletions rs/execution_environment/src/hypervisor/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2272,6 +2272,88 @@ fn ic0_mint_cycles_succeeds_on_cmc() {
);
}

#[test]
fn ic0_mint_cycles128_fails_on_application_subnet() {
let mut test = ExecutionTestBuilder::new().build();
let canister_id = test.universal_canister().unwrap();
let initial_cycles = test.canister_state(canister_id).system_state.balance();
let payload = wasm()
.mint_cycles128(Cycles::from((1u128 << 64) + 2u128))
.reply_data_append()
.reply()
.build();
let err = test.ingress(canister_id, "update", payload).unwrap_err();
assert_eq!(ErrorCode::CanisterContractViolation, err.code());
assert!(err
.description()
.contains("ic0.mint_cycles cannot be executed"));
michael-weigelt marked this conversation as resolved.
Show resolved Hide resolved
let canister_state = test.canister_state(canister_id);
assert_eq!(0, canister_state.system_state.queues().output_queues_len());
assert_balance_equals(
initial_cycles,
canister_state.system_state.balance(),
BALANCE_EPSILON,
);
}

#[test]
fn ic0_mint_cycles128_fails_on_system_subnet_non_cmc() {
let mut test = ExecutionTestBuilder::new()
.with_subnet_type(SubnetType::System)
.build();
let canister_id = test.universal_canister().unwrap();
let initial_cycles = test.canister_state(canister_id).system_state.balance();
let payload = wasm()
.mint_cycles128(Cycles::from((1u128 << 64) + 2u128))
.reply_data_append()
.reply()
.build();
let err = test.ingress(canister_id, "update", payload).unwrap_err();
assert_eq!(ErrorCode::CanisterContractViolation, err.code());
assert!(err
.description()
.contains("ic0.mint_cycles cannot be executed"));
let canister_state = test.canister_state(canister_id);
assert_eq!(0, canister_state.system_state.queues().output_queues_len());
assert_balance_equals(
initial_cycles,
canister_state.system_state.balance(),
BALANCE_EPSILON,
);
michael-weigelt marked this conversation as resolved.
Show resolved Hide resolved
}

#[test]
fn ic0_mint_cycles128_succeeds_on_cmc() {
let mut test = ExecutionTestBuilder::new()
.with_subnet_type(SubnetType::System)
.build();
let mut canister_id = test.universal_canister().unwrap();
// This loop should finish after four iterations.
michael-weigelt marked this conversation as resolved.
Show resolved Hide resolved
while canister_id != CYCLES_MINTING_CANISTER_ID {
canister_id = test.universal_canister().unwrap();
}
let initial_cycles = test.canister_state(canister_id).system_state.balance();
let payload = wasm()
.mint_cycles128(Cycles::from((1u128 << 64) + 2u128))
.reply_data_append()
.reply()
.build();
let result = test.ingress(canister_id, "update", payload).unwrap();
// (high=1, low=2) => 2^64 + 2^1
assert_eq!(
WasmResult::Reply(vec![2, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0]),
michael-weigelt marked this conversation as resolved.
Show resolved Hide resolved
result
);
let canister_state = test.canister_state(canister_id);

assert_eq!(0, canister_state.system_state.queues().output_queues_len());
assert_balance_equals(
initial_cycles + Cycles::new(18446744073709551618),
michael-weigelt marked this conversation as resolved.
Show resolved Hide resolved
canister_state.system_state.balance(),
BALANCE_EPSILON,
);
}

#[test]
fn ic0_call_enqueues_request() {
let mut test = ExecutionTestBuilder::new().build();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1488,6 +1488,7 @@ fn query_cache_future_proof_test() {
| SystemApiCallId::InReplicatedExecution
| SystemApiCallId::IsController
| SystemApiCallId::MintCycles
| SystemApiCallId::MintCycles128
| SystemApiCallId::MsgArgDataCopy
| SystemApiCallId::MsgArgDataSize
| SystemApiCallId::MsgCallerCopy
Expand Down
22 changes: 22 additions & 0 deletions rs/interfaces/src/execution_environment.rs
Original file line number Diff line number Diff line change
Expand Up @@ -175,6 +175,8 @@ pub enum SystemApiCallId {
IsController,
/// Tracker for `ic0.mint_cycles()`
MintCycles,
/// Tracker for `ic0.mint_cycles128()`
MintCycles128,
/// Tracker for `ic0.msg_arg_data_copy()`
MsgArgDataCopy,
/// Tracker for `ic0.msg_arg_data_size()`
Expand Down Expand Up @@ -1132,6 +1134,26 @@ pub trait SystemApi {
/// Returns the amount of cycles added to the canister's balance.
fn ic0_mint_cycles(&mut self, amount: u64) -> HypervisorResult<u64>;

/// Mints the `amount` cycles
/// Adds cycles to the canister's balance.
///
/// Adds no more cycles than `amount`.
///
/// The canister balance afterwards does not exceed
/// maximum amount of cycles it can hold.
/// However, canisters on system subnets have no balance limit.
michael-weigelt marked this conversation as resolved.
Show resolved Hide resolved
///
/// The amount of cycles added to the canister's balance is
/// represented by a 128-bit value and is copied in the canister
/// memory starting starting at the location `dst`.
michael-weigelt marked this conversation as resolved.
Show resolved Hide resolved
fn ic0_mint_cycles128(
&mut self,
amount_high: u64,
amount_low: u64,
michael-weigelt marked this conversation as resolved.
Show resolved Hide resolved
dst: usize,
heap: &mut [u8],
) -> HypervisorResult<()>;

/// Checks whether the principal identified by src/size is one of the
/// controllers of the canister. If yes, then a value of 1 is returned,
/// otherwise a 0 is returned. It can be called multiple times.
Expand Down
38 changes: 37 additions & 1 deletion rs/system_api/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3168,7 +3168,8 @@ impl SystemApi for SystemApiImpl {
trace_syscall!(self, CanisterStatus, result);
result
}

// TODO: This can be removed (in favour of ic0_mint_cycles128) once the CMC is upgraded, so it
berestovskyy marked this conversation as resolved.
Show resolved Hide resolved
// doesn't make sense to deduplicate the shared code.
fn ic0_mint_cycles(&mut self, amount: u64) -> HypervisorResult<u64> {
let result = match self.api_type {
ApiType::Start { .. }
Expand Down Expand Up @@ -3197,6 +3198,41 @@ impl SystemApi for SystemApiImpl {
result
}

fn ic0_mint_cycles128(
&mut self,
amount_high: u64,
amount_low: u64,
dst: usize,
heap: &mut [u8],
) -> HypervisorResult<()> {
let cycles = Cycles::from_parts(amount_high, amount_low);
let result = match self.api_type {
ApiType::Start { .. }
| ApiType::Init { .. }
| ApiType::PreUpgrade { .. }
| ApiType::Cleanup { .. }
| ApiType::ReplicatedQuery { .. }
| ApiType::NonReplicatedQuery { .. }
| ApiType::InspectMessage { .. } => Err(self.error_for("ic0_mint_cycles128")),
ApiType::Update { .. }
| ApiType::SystemTask { .. }
| ApiType::ReplyCallback { .. }
| ApiType::RejectCallback { .. } => {
if self.execution_parameters.execution_mode == ExecutionMode::NonReplicated {
// Non-replicated mode means we are handling a composite query.
// Access to this syscall not permitted.
Err(self.error_for("ic0_mint_cycles128"))
} else {
self.sandbox_safe_system_state.mint_cycles(cycles)?;
michael-weigelt marked this conversation as resolved.
Show resolved Hide resolved
copy_cycles_to_heap(cycles, dst, heap, "ic0_mint_cycles_128")?;
Ok(())
}
}
};
trace_syscall!(self, MintCycles128, result, cycles);
result
}

fn ic0_debug_print(&self, src: usize, size: usize, heap: &[u8]) -> HypervisorResult<()> {
const MAX_DEBUG_MESSAGE_SIZE: usize = 32 * 1024;
let size = size.min(MAX_DEBUG_MESSAGE_SIZE);
Expand Down
42 changes: 42 additions & 0 deletions rs/system_api/tests/sandbox_safe_system_state.rs
Original file line number Diff line number Diff line change
Expand Up @@ -349,6 +349,48 @@ fn mint_cycles_fails_caller_not_on_nns() {
);
}

#[test]
michael-weigelt marked this conversation as resolved.
Show resolved Hide resolved
fn mint_cycles_very_large_value() {
let cycles_account_manager = CyclesAccountManagerBuilder::new()
.with_subnet_type(SubnetType::System)
.build();
let mut system_state = SystemStateBuilder::new()
.canister_id(CYCLES_MINTING_CANISTER_ID)
.build();

system_state.add_cycles(
Cycles::from(1_000_000_000_000_000_u128),
CyclesUseCase::NonConsumed,
);

let api_type = ApiTypeBuilder::build_update_api();
let mut api = get_system_api(api_type, &system_state, cycles_account_manager);
let mut balance_before = [0u8; 16];
api.ic0_canister_cycle_balance128(0, &mut balance_before)
.unwrap();
let balance_before = u128::from_le_bytes(balance_before);
michael-weigelt marked this conversation as resolved.
Show resolved Hide resolved

let amount_high = u64::MAX;
let amount_low = 50;
michael-weigelt marked this conversation as resolved.
Show resolved Hide resolved
let mut heap = [0u8; 16];
// Canisters on the System subnet can hold any amount of cycles
api.ic0_mint_cycles128(amount_high, amount_low, 0, &mut heap)
.unwrap();
let cycles_minted = u128::from_le_bytes(heap);
assert_eq!(
cycles_minted,
Cycles::from_parts(amount_high, amount_low).get()
michael-weigelt marked this conversation as resolved.
Show resolved Hide resolved
);
let mut balance_after = [0u8; 16];
api.ic0_canister_cycle_balance128(0, &mut balance_after)
.unwrap();
let balance_after = u128::from_le_bytes(balance_after);
assert_eq!(
balance_after - balance_before,
Cycles::from_parts(amount_high, amount_low).get()
);
}

#[test]
fn is_controller_test() {
let mut system_state = SystemStateBuilder::default().build();
Expand Down
18 changes: 16 additions & 2 deletions rs/system_api/tests/system_api.rs
Original file line number Diff line number Diff line change
Expand Up @@ -293,7 +293,8 @@ fn is_supported(api_type: SystemApiCallId, context: &str) -> bool {
SystemApiCallId::InReplicatedExecution => vec!["*", "s"],
SystemApiCallId::DebugPrint => vec!["*", "s"],
SystemApiCallId::Trap => vec!["*", "s"],
SystemApiCallId::MintCycles => vec!["U", "Ry", "Rt", "T"]
SystemApiCallId::MintCycles => vec!["U", "Ry", "Rt", "T"],
SystemApiCallId::MintCycles128 => vec!["U", "Ry", "Rt", "T"]
};
// the semantics of "*" is to cover all modes except for "s"
matrix.get(&api_type).unwrap().contains(&context)
Expand Down Expand Up @@ -745,6 +746,11 @@ fn api_availability_test(
let mut api = get_system_api(api_type, &system_state, cycles_account_manager);
assert_api_not_supported(api.ic0_mint_cycles(0));
}
SystemApiCallId::MintCycles128 => {
// ic0.mint_cycles128 is only supported for CMC which is tested separately
let mut api = get_system_api(api_type, &system_state, cycles_account_manager);
assert_api_not_supported(api.ic0_mint_cycles128(0, 0, 0, &mut [0u8; 16]));
}
SystemApiCallId::IsController => {
assert_api_availability(
|api| api.ic0_is_controller(0, 0, &[42; 128]),
Expand Down Expand Up @@ -822,7 +828,7 @@ fn system_api_availability() {
let api = get_system_api(api_type.clone(), &system_state, cycles_account_manager);
check_stable_apis_support(api);

// check ic0.mint_cycles API availability for CMC
// check ic0.mint_cycles, ic0.mint_cycles128 API availability for CMC
let cmc_system_state = get_cmc_system_state();
assert_api_availability(
|mut api| api.ic0_mint_cycles(0),
Expand All @@ -832,6 +838,14 @@ fn system_api_availability() {
SystemApiCallId::MintCycles,
context,
);
assert_api_availability(
|mut api| api.ic0_mint_cycles128(0, 0, 0, &mut [0u8; 16]),
api_type.clone(),
&cmc_system_state,
cycles_account_manager,
SystemApiCallId::MintCycles128,
context,
);

// now check all other API availability for non-CMC
for api_type_enum in SystemApiCallId::iter() {
Expand Down
Loading
Loading