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

Add system API endpoints for cycle cost calculation #4110

Open
wants to merge 17 commits into
base: master
Choose a base branch
from
Open
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
107 changes: 105 additions & 2 deletions docs/references/ic-interface-spec.md
Original file line number Diff line number Diff line change
Expand Up @@ -1487,8 +1487,16 @@ defaulting to `I = i32` if the canister declares no memory.
ic0.time : () -> (timestamp : i64); // *
ic0.global_timer_set : (timestamp : i64) -> i64; // I G U Ry Rt C T
ic0.performance_counter : (counter_type : i32) -> (counter : i64); // * s
ic0.is_controller: (src : I, size : I) -> ( result: i32); // * s
ic0.in_replicated_execution: () -> (result: i32); // * s
ic0.is_controller : (src : I, size : I) -> ( result : i32); // * s
ic0.in_replicated_execution : () -> (result : i32); // * s

ic0.cost_call : (method_name_size: i64, payload_size : i64, dst : I) -> (); // * s
ic0.cost_create_canister : (dst : I) -> (); // * s
ic0.cost_http_request : (request_size : i64, max_res_bytes : i64, dst : I) -> (); // * s
ic0.cost_sign_with_ecdsa : (src : I, size : I, ecdsa_curve: i32, dst : I) -> (); // * s
ic0.cost_sign_with_schnorr : (src : I, size : I, algorithm: i32, dst : I) -> (); // * s
ic0.cost_vetkd_derive_encrypted_key : (src : I, size : I, vetkd_curve: i32, dst : I) -> (); // * s
ic0.replication_factor : (src : I, size : I) -> i32; // * s
michael-weigelt marked this conversation as resolved.
Show resolved Hide resolved

ic0.debug_print : (src : I, size : I) -> (); // * s
ic0.trap : (src : I, size : I) -> (); // * s
Expand Down Expand Up @@ -2027,6 +2035,53 @@ When executing a query or composite query method via a query call (i.e. in non-r

This traps if `ic0.data_certificate_present()` returns `0`.

### Cycle cost calculation {#system-api-cycle-cost}

Inter-canister calls have an implicit cost, and some calls to the management canister require the caller to attach cycles to the call explicitly.
The various cost factors may change over time, so the following system calls give the canister programmatic, up-to-date information about the costs.

These system calls return costs in Cycles, represented by 128 bits, which will be written to the heap memory starting at offset `dst`.

- `ic0.cost_call : (method_name_size: i64, payload_size : i64, dst : I) -> ()`; `I ∈ {i32, i64}`

The cost of an inter-canister call. `method_name_size` is the byte length of the method name, and `payload_size` is the byte length of the argument to the method.

- `ic0.cost_create_canister : (dst : I) -> ()`; `I ∈ {i32, i64}`

The cost of creating a canister on the same subnet as the calling canister via [`create_canister`](#ic-create_canister). Note that canister creation via a call to the CMC can have a different cost if the target subnet has a different replication factor. In order to facilitate conversions, see `ic0.replication_factor`. When converting costs using ratios of subnet sizes, be mindful of rounding issues and consider adding a safety margin.

- `ic0.cost_http_request(request_size : i64, max_res_bytes : i64, dst : I) -> ()`; `I ∈ {i32, i64}`

The cost of a canister http outcall via [`http_request`](#ic-http_request). `request_size` is the sum of the byte lengths of the following components of an http request:
- url
- method
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
- method

this is not part of the formula and it'd be unclear anyway how to compute the byte length of the enumeration variant

- headers - i.e., the sum of the lengths of all keys and values
- body
- transform - i.e., the sum of the transform method name length and the length of the transform context

`max_res_bytes` is the maximum response length the caller wishes to accept. Note that this argument is not optional like in the call to the management canister. The cost depends on `max_res_bytes`, so the caller must provide it explicitly. See the [`http_request`](#ic-http_request) call to the management canister API to learn about the current default and maximum values.
mraszyk marked this conversation as resolved.
Show resolved Hide resolved

- `ic0.cost_sign_with_ecdsa(src : I, size : I, ecdsa_curve: i32, dst : I) -> ()`; `I ∈ {i32, i64}`

- `ic0.cost_sign_with_schnorr(src : I, size : I, algorithm: i32, dst : I) -> ()`; `I ∈ {i32, i64}`

- `ic0.cost_vetkd_derive_encrypted_key(src : I, vetkd_curve: i32, size : I, dst : I) -> ()`; `I ∈ {i32, i64}`
Copy link
Contributor

Choose a reason for hiding this comment

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

Shouldn't src and size be next to each other?


These system calls accept a key name via a textual representation for the specific signing scheme / key of a given size stored in the heap memory starting at offset `src`. They also accept an `i32` with the following interpretations:
- `ecdsa_curve: 0 → secp256k1`
- `schnorr_algorithm: 0 → bip340secp256k1, 1 → ed25519`
- `vetkd_curve: 0 → bls12_381`

See [`sign_with_ecdsa`](#ic-sign_with_ecdsa), [`sign_with_schnorr`](#ic-sign_with_schnorr) and [`vetkd_encrypted_key`](#ic-vetkd_encrypted_key) for more information.

These system calls trap if the string represented by `src` + `size` does not correspond to a valid key name, such as `dfx_test_key`, `test_key_1` or `key_1`, or if the provided curve enum is an unspecified variant.

- `ic0.replication_factor : (src : I, size : I) -> i32`; `I ∈ {i32, i64}`

Returns the replication factor (subnet size) of the subnet identified by the `Principal` at `src` + `size`.

This system call traps if `src` + `size` do not represent a valid `Principal` or the given `Principal` is not a subnet.

### Debugging aids

In a local canister execution environment, the canister needs a way to emit textual trace messages. On the "real" network, these do not do anything.
Expand Down Expand Up @@ -7465,6 +7520,54 @@ ic0.in_replicated_execution<es>() : i32 =
then return 1
else return 0

I ∈ {i32, i64}
ic0.cost_call<es>(method_name_size: i64, payload_size: i64, dst: I) : () =
copy_cycles_to_canister<es>(dst, arbitrary())

I ∈ {i32, i64}
ic0.cost_create_canister<es>(dst: I) : () =
copy_cycles_to_canister<es>(dst, arbitrary())

I ∈ {i32, i64}
ic0.cost_cost_http_request<es>(request_size: i64, max_res_bytes: i64, dst: I) : () =
copy_cycles_to_canister<es>(dst, arbitrary())

I ∈ {i32, i64}
ic0.cost_sign_with_ecdsa<es>(src: I, size: I, ecdsa_curve: i32, dst: I) : () =
principal_bytes = copy_from_canister<es>(src, size)
if not principal_bytes encode a principal then
Trap {cycles_used = es.cycles_used;}
if principal_bytes ∉ es.subnets then
Trap {cycles_used = es.cycles_used;}
copy_cycles_to_canister<es>(dst, arbitrary())

I ∈ {i32, i64}
ic0.cost_sign_with_schnorr<es>(src: I, size: I, algorithm: i32, dst: I) : () =
principal_bytes = copy_from_canister<es>(src, size)
if not principal_bytes encode a principal then
Trap {cycles_used = es.cycles_used;}
if principal_bytes ∉ es.subnets then
Trap {cycles_used = es.cycles_used;}
copy_cycles_to_canister<es>(dst, arbitrary())

I ∈ {i32, i64}
ic0.cost_vetkd_derive_encrypted_key<es>(src: I, size: I, vetkd_curve: i32, dst: I) : () =
principal_bytes = copy_from_canister<es>(src, size)
if not principal_bytes encode a principal then
Trap {cycles_used = es.cycles_used;}
if principal_bytes ∉ es.subnets then
Trap {cycles_used = es.cycles_used;}
copy_cycles_to_canister<es>(dst, arbitrary())

I ∈ {i32, i64}
ic0.replication_factor<es, S>(src: I, size: I) : i32 =
principal_bytes = copy_from_canister<es>(src, size)
if not principal_bytes encode a principal then
Trap {cycles_used = es.cycles_used;}
if principal_bytes ∉ es.subnets then
Trap {cycles_used = es.cycles_used;}
return arbitrary()

I ∈ {i32, i64}
ic0.debug_print<es>(src : I, size : I) =
return
Expand Down