Fix auto-renewal charging tasks for foreign workloads - #12750
Open
Szegoo wants to merge 5 commits into
Open
Conversation
mertwole
approved these changes
Jul 29, 2026
| } | ||
| impl<Balance> PotentialRenewalRecord<Balance> { | ||
| /// Return whether the workload to be renewed is complete and includes the given task. | ||
| pub fn complete_workload_includes(&self, task: TaskId) -> bool { |
Contributor
There was a problem hiding this comment.
Maybe make this a method on the CompletionStatus itself? Something like fn is_complete_and_contains_task
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
A core index doesn't permanently belong to a task. Core assignments are rebuilt every region, and a renewal buys a core in the next sale, so a workload can end up on a different core index each region. Because of this,
PotentialRenewalscan hold records for the same core index belonging to different tasks at the same time. For example, the pending renewal of the task running on that core in the current bulk period, and the renewal record of the task that will run on it in the next bulk period.enable_auto_renewdidn't account for this. It first checks whether there is any renewable workload at (core, current region begin), and if there is one it renews it immediately and charges the task's sovereign account, without checking whose workload it is and without looking at the hint. So a task following the documented flow (passing its own core and its own renewal record's timeslice as the hint) could end up paying for the renewal of a completely unrelated task. The stored auto-renewal record then keeps charging it for that foreign workload every sale.This happened on Polkadot Coretime: task 3428 enabled auto-renewal for its twelve cores with correct parameters, but five of those core indices were still carrying other tasks' pending renewals for the current sale. One went through, so task 3428 paid for task 2094's renewal: Link
Fix
enable_auto_renewonly renews immediately if the expiring workload includes the task. If the core is expiring with another task's workload, we fall through to the workload_end_hint path instead, which must point to the task's own renewal record (otherwise the call fails with the newTaskNotInWorkloaderror).renew_coresre-checks this before charging the sovereign account. On a mismatch it emitsAutoRenewalFailedand drops the record instead of charging. This also cleans up the mismatched record that is currently on-chain.next_renewalis now set to the end of the period that was just renewed. Previously theworkload_end_hintwas stored instead, so auto-renewal would skip the next renewal and the task would lose its core.