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-1768] Add system API to get costs of management canister calls. #3584

Open
wants to merge 82 commits into
base: master
Choose a base branch
from

Conversation

michael-weigelt
Copy link
Contributor

@michael-weigelt michael-weigelt commented Jan 23, 2025

This PR introduces system API endpoints that allow the caller to predict the cost of certain calls:

  • The cost of creating a canister
  • The cost of making an http outcall
  • The cost of an inter-canister call, i.e., the amount of cycles above the freezing threshold a canister must have in order to successfully perfom a call
  • The cost of signing with Ecdsa, Schnorr and deriving a VetKd key.

Design Doc

Spec PR

@github-actions github-actions bot added the feat label Jan 23, 2025
rs/interfaces/src/execution_environment.rs Outdated Show resolved Hide resolved
rs/interfaces/src/execution_environment.rs Outdated Show resolved Hide resolved
rs/interfaces/src/execution_environment.rs Outdated Show resolved Hide resolved
rs/interfaces/src/execution_environment.rs Outdated Show resolved Hide resolved
rs/types/management_canister_types/src/lib.rs Outdated Show resolved Hide resolved
rs/types/types/src/consensus/idkg/common.rs Outdated Show resolved Hide resolved
let cost = self
.sandbox_safe_system_state
.cycles_account_manager
.xnet_call_total_fee((method_name_size + payload_size).into(), execution_mode);
Copy link
Contributor Author

Choose a reason for hiding this comment

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

If the caller provides u64::MAX for both arguments, this sum will overflow and the call will return a wrong cost. However, the method name of an actual call is limited to 10'000 bytes. So the input u64::MAX for method_name_size is "wrong", so I would accept this behaviour.

Copy link
Contributor

Choose a reason for hiding this comment

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

But in this case, method_name_size = 9999 and payload_size = u64::MAX - 1 are valid inputs, and there will be an overflow. So I will suggest adding a check and explaining it in the doc comment of system API.

Copy link
Contributor

Choose a reason for hiding this comment

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

Another question: is ic0_cost_call is used only to determine the cost of xnet calls?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Actually, payload_size = u64::MAX - 1 is not a valid input, they only go up to 2MB.
While the limits may change, the are very unlikely to ever multiply to even close to u64::MAX.
I would argue that giving a bad result for bad inputs is inevitable, and it does not make it more correct if we e.g. saturate: The result would still be wrong.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

The xnet prefix is used in the cycles account manager for all inter-canister calls.

Copy link
Contributor

@dragoljub-duric dragoljub-duric Feb 14, 2025

Choose a reason for hiding this comment

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

I believe that we should make consistent behavior of ic0_call and ic0_cost_call. Taking that into account in the solution you proposed, if the user calls ic0_cost_call with payload_size 3MB, it will receive the response with some cost, if, after that, the user calls ic0_call with a payload of 3MB call will fail because it is above the payload limit. What is your opinion on that scenario?

Copy link
Contributor Author

@michael-weigelt michael-weigelt Feb 14, 2025

Choose a reason for hiding this comment

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

I think the cost endpoint should only care about the potential cost, not about other things like limits or whether e.g. the payload is malformed or anything like that. I mean, ic0.call_perform can also fail because of many other reasons, we don't want to reflect them in the cost endpoint. Imagine e.g. an empty method name. That would give a legal cost, but is not a legal argument to call_perform.

In other words, I propose:

  • If call_perform would fail because we don't have enough cycles, then cost_call should help detecting that
  • If call_perform would fail for any other reason (e.g. too large inputs), that has nothing to do with the cost, so cost_call will be agnostic about it.

If we want to return an error code for the cost_call endpoint, we have to specify it (or extend the one from ic0.call_perform), and I don't think it's worth specifying "your inputs to cost calculation were too large".

Copy link
Contributor

Choose a reason for hiding this comment

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

If the payload is malformed that is not information that we have at the moment of calling ic0_cost_call so we cannot know what the payload will be hence we should not do anything. But we know payload_size in the moment of calling ic0_cost_call so with that information if the payload_size is greater than the limit will most certainly tell us that the call will not succeed, hence in my opinion that function that should predict the cost of such call(ic0_cost_call) returns some cost when we know that such call will not succeed.

Regarding the empty method name, I also wanted to mention that the check for that should be added for the same reason as payload_size, we know a call with such a method name will fail so it does not make sense that function that should predict the cost of such call(ic0_cost_call) returns some cost when in the moment of calling it we know such call will not succeed.

Copy link
Contributor

Choose a reason for hiding this comment

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

How about we meet on zoom to discuss this?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

IMO this would not separate concerns properly and leak error states/-handling of call_perform into cost_call.

Regarding the second paragraph: E.g. I think it's not wrong to say that e.g. cost_call(0, 0) = 0, instead of cost_call(0, 0) = error (or some base cost instead of 0, but that's not the point). It's just about the potential cost, nothing else should matter.

Ok, we can discuss it afk.

@michael-weigelt michael-weigelt marked this pull request as ready for review February 12, 2025 14:44
@michael-weigelt michael-weigelt requested a review from a team as a code owner February 12, 2025 14:44
@michael-weigelt michael-weigelt marked this pull request as draft February 12, 2025 14:56
@michael-weigelt michael-weigelt marked this pull request as ready for review February 12, 2025 15:12
@michael-weigelt michael-weigelt added the CI_ALL_BAZEL_TARGETS Runs all bazel targets and uploads them to S3 label Feb 12, 2025
rs/config/src/subnet_config.rs Show resolved Hide resolved
rs/cycles_account_manager/src/lib.rs Outdated Show resolved Hide resolved
rs/interfaces/src/execution_environment.rs Outdated Show resolved Hide resolved
let cost = self
.sandbox_safe_system_state
.cycles_account_manager
.xnet_call_total_fee((method_name_size + payload_size).into(), execution_mode);
Copy link
Contributor

Choose a reason for hiding this comment

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

But in this case, method_name_size = 9999 and payload_size = u64::MAX - 1 are valid inputs, and there will be an overflow. So I will suggest adding a check and explaining it in the doc comment of system API.

let cost = self
.sandbox_safe_system_state
.cycles_account_manager
.xnet_call_total_fee((method_name_size + payload_size).into(), execution_mode);
Copy link
Contributor

Choose a reason for hiding this comment

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

Another question: is ic0_cost_call is used only to determine the cost of xnet calls?

rs/system_api/src/sandbox_safe_system_state.rs Outdated Show resolved Hide resolved
rs/system_api/src/sandbox_safe_system_state.rs Outdated Show resolved Hide resolved
rs/system_api/tests/system_api.rs Outdated Show resolved Hide resolved
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
CI_ALL_BAZEL_TARGETS Runs all bazel targets and uploads them to S3 @execution feat
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants