You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
fix: remove the ordering on CompoundCycles (#11529)
# Summary
`CompoundCycles` pairs a **real** amount, which moves the canister's
cycles balance,
with a **nominal** one, which feeds the consumed cycles metrics. It
derived `Ord`,
which orders lexicographically, i.e. by the real part first, so a
comparison ignored
the nominal parts whenever the real parts differed.
There is no meaningful order on a pair of independently accounted
amounts, and that one
is actively wrong for what the cycles accounting in
`ic-cycles-account-manager` compares:
an amount recorded when a call is performed against one derived when its
response is
executed. Under the free cost schedule the real part of an amount made
free is zero
however large its nominal part, so the amount made free always compares
as the smaller
one.
The derive is removed, so such a comparison is now a compile error, and
the type
documents why it has no ordering. This also corrects a claim in the same
doc comment:
the generics enforce the same `CyclesUseCase`, not the same cost
schedule, which is
not part of the type at all.
# The four `min` calls
| site | before | after |
| --- | --- | --- |
| `settle_prepayment_for_unexecuted_response` | `p - base_fee.min(p)` |
`p - base_fee` |
| `refund_for_response_transmission` | `p - cost.min(p)` | `p - cost` |
| `refund_unused_execution_cycles` | `refund.min(p)` |
`refund.component_wise_min(p)` |
| `CanisterManager::load_canister_snapshot` | `p - cost.min(p)` | `p -
cost.component_wise_min(p)` |
Subtraction saturates in both parts, so `x - y` already equals
`x - x.component_wise_min(y)`: capping a cost before subtracting it from
that same
prepayment was redundant. Where a cap is wanted, `component_wise_min`
replaces the
ordering with the minimum of the real parts paired with the minimum of
the nominal
parts. `load_canister_snapshot` keeps its redundant cap so that it stays
identical to
the `refund_unused_execution_cycles` refund it recomputes.
# Motivation
Hardening rather than a fix. Under a single cost schedule the two parts
of an amount
agree on every ordering previously consulted, so the lexicographic
tie-break was never
reached: no cycles balance and no consumed cycles metric changes.
Removing the derive
keeps it out of reach as the accounting changes.
Concretely, were the two amounts ever to carry different cost schedules,
the removed
ordering would produce:
- `settle_prepayment_for_unexecuted_response`: for a prepayment made
under the free
cost schedule and settled under the normal one, the ordering picks the
whole
prepayment as the charge. The consumed cycles counter then reports the
nominal
prepayment for the entire instruction limit — for a callback that never
ran — instead
of the fixed per-message execution fee.
- `refund_for_response_transmission`: same shape, same direction.
Nothing is refunded
and the whole nominal prepayment is reported as consumed.
- `refund_unused_execution_cycles`: the cap is decided by the real
parts, so it need
not bound the nominal one at all. For a prepayment made under the normal
cost
schedule and refunded under the free one, it selects the refund and
leaves that
refund's nominal part uncapped: exceeding the prepayment there trips the
debug
assertion in `refund_cycles`, and saturates both metrics in a release
build. The
other way round it selects the whole prepayment, refunding all of it and
reporting
the response as having consumed nothing.
None of the three moves a cycles balance: wherever the two orderings
disagree, one of
the amounts carries the free cost schedule and hence a zero real part,
so only the
nominal parts, i.e. the consumed cycles metrics, can differ.
One comparison of this kind is left after this PR, in
`adjust_prepayment_for_response_execution`, which weighs the prepayment
for a response
against the requirement derived when that response is executed. It is
likewise sound
only while both carry the same cost schedule. #11431 removes it.
# Note
#11431 builds on this and currently contains these hunks too. It will be
rebased onto
this PR, so review this one on its own.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
---------
Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
0 commit comments