refactor: Introduce new type CompoundCycles#9520
refactor: Introduce new type CompoundCycles#9520michael-weigelt merged 14 commits intodfinity:masterfrom
Conversation
|
I noticed two more cases where we need to update APIs to follow the new approach. Will upload a fix shortly. EDIT: Only one actually in the end. |
|
@schneiderstefan @michael-weigelt @eichhorl Please re-approve when you can. |
|
@dsarlis yeah sorry, I think that was just bad timing. When I rebased the PR I didn't realize I was pulling in this change too https://github.com/dfinity/ic/pull/9452/changes which changed the clippy command. |
|
Hmm okay sorry @dsarlis looks like there is still one more error I didn't see before. It's in |
Review dismissed by automation script.
Yeah that was my miss. Some doc tests needed to be updated after the last couple of commits here. I pushed a fix just now. |
|
Btw @cgundy I don't know if it's just me but it feels things are awfully slow for me to run locally, after merging master earlier this morning. CI also seems to have taken 1+ hour to tell me that some doc test failed in |
This is a follow up to #9520 where `CompoundCycles` were introduced. In the system API there can only be 3 cases of consumed cycles, specifically `BurnedCycles`, `Instructions` and `RequestAndResponseTransmission`. Currently, there's an awkward `panic` to deal with the possibility that the current `BTreeMap` where consumed cycles are stored cannot contain other variants. While this is easy to verify that is the case now (code inspection + type imports), it is unideal as it's error prone and we can do better with the stricter types introduced via `CompoundCycles`. The solution is to define a struct which captures exactly the possible cases that can be produced via the system API calls and use that throughout. There's some little extra boilerplate but this ensures that no unexpected cases can happen and removes the need to handle such cases. An alternative would have been to add a check in `validate_cycle_change` to raise a critical error instead but it's better if we can eliminate the possibility of errors altogether.
… callbacks (#9676) This PR follows up on #9520 and expands the use of `CompoundCycles` to a few more types and APIs, primarily related to DTS executions and callback executions. For various DTS executions, there is a need to store the prepayment made in the original context, something that is useful to resume a paused execution or when an aborted one will need to be restarted. Storing just a `Cycles` amount in these cases would be incorrect in the future when the `Cycles` and `NominalCycles` amounts can be different. Switching to `CompoundCycles` makes it so that no information loss happens when that switch is implemented. Similarly for callback executions the prepayment amounts need to be stored so that refunds can be applied correctly when the response is processed. One particular point of interest is the transformation in the respective protobuf files where a new field is added. The new field of type `CompoundCycles` will eventually replace the existing `Cycles` one. To populate the new field on the first restart, we will use the `Cycles` amount that's stored in the checkpoint now. Technically, a `CanisterCyclesCostSchedule` would be needed but to avoid unnecessary piping of this information (needs to be propagated all the way from `StateManager` down to `CanisterStateBits`) we take a shortcut and hardcode `Normal` schedule. This should not have an effect anyway as for subnets with `Free` schedule now the `Cycles` and `NominalCycles` should match so using a 0 amount for both is fine (obviously for subnets with `Normal` schedule this is fine as well).
This PR is an alternative to the original approach in #9063.
The original PR, while achieving the goal of having cycles for metrics be always updated irrespective of the subnet's cycles cost schedule, it achieved so by pushing the decision making to all callers that need to deal with cycles charges or metric updates. Specifically, the APIs in the
CyclesAccountManagerwere changed to not require acost_scheduleparameter, so each caller would then need to have some logic like:This was not ideal as the decision points were spread across the codebase making it also easier for mistakes to be introduced if new use cases were added as every caller would need to have all the knowledge of how to handle things.
The current PR proposes another approach which attempts to encapsulate better the logic around calculating the right amounts for cycles balance and metrics updates. A new type called
CompoundCyclesis introduced that given an original amount ofCycles, the use case and cost schedule, calculates the "real" and "nominal" amounts that can be used to update the cycles balance and metrics respectively.This is an improvement over the original approach as now the callers would have access to a
CompoundCyclesobject and when they need to updateCyclesamounts in theReplicatedStatethey can usecompound_cycles.real()while they can usecompound_cycles.nominal()forNominalCyclesamounts. This way the type system helps guide them to use the right amounts without having to do calculations themselves.However, making the new type capture more information than before introduces a new risk, namely that one could mix amounts for different use cases or even cost schedule. Imagine the following update in one of the
CyclesAccountManagerAPIs:in the above there's no guarantee that the user of the API does not use
CompoundCyclesforMemoryinstead ofInstructionsthat they should use and pass an incorrect amount inrefund_unused_execution_cycleswithout noticing. Additionally, in order to calculate the refund we should implement arithmetic operations onCompoundCyclesbut it also doesn't make sense to allow adding up compound cycles for different use cases.One solution to the above issue would be to make the operations fallible but then there would have to be a lot of error handling sprinkled all over the codebase. The PR takes another path which is to introduce generic arguments for
CompoundCyclesto capture the combinations of use cases. This ensures that operations can only happen between "apples" and "apples" and it also provides a nice way to clarify even more the APIs in theCyclesAccountManagerlike the following:The changes included can be summarized as follows:
CyclesAccountManagerandReplicatedStateare transformed to require or return the new type when they would need to deal with both the balance and metrics updates eventually. APIs that are only concerned with the balance of the canister or only with metrics are still using the existingCyclesandNominalCyclestypes.Note that the current PR performs only a refactoring to introduce the new type and update APIs but is still using the same values for "real" and "nominal" parts of
CompoundCycles. If the approach is agreed, then a follow up to perform the functional changes would be rather small -- a trivial change in the constructor ofCompoundCyclesas well as some test updates to fix assertions (and/or maybe adding some more tests as I don't think all cases are covered).