[Staking] Use active era and not current era for bonding logic#8807
Open
[Staking] Use active era and not current era for bonding logic#8807
Conversation
bde32fe to
44b1fab
Compare
github-merge-queue Bot
pushed a commit
that referenced
this pull request
Jul 28, 2025
## Context The offence handling pipeline has four main stages: 1. **Reporting on RC**: Offences are reported on the Relay Chain (RC) and exported to Asset Hub (AH) via RC::AHClient. 2. **Queueing**: AH staking pallet receives the offence in `fn on_new_offence`, performs sanity checks, and enqueues it in `OffenceQueue` and `OffenceQueueEras`. 3. **Processing**: Offences are processed one by one, starting from the oldest era in the queue. Processed items are stored in `UnappliedSlashes`. 4. **Application**: Finally, slashes are applied one page per block after the slash defer duration from the offence era. --- ## Problem While unlikely, a spam of offence reports could slow down processing enough that some offences remain unhandled even after their bonding period ends. This creates a rare corner case: a withdrawal could happen for an era that still has pending offences, which breaks slashing guarantees. Also, slash application happens gradually (one page per block). If some slashes are left unapplied at the end of their application era (due to chain stalls or similar), they must be manually applied using the permissionless `apply_slash` call. Both scenarios are rare, but they expose risks to the integrity of slashing. --- ## What this PR Changes ### 1. Block withdrawals for eras with unprocessed offences Withdrawals are now restricted to the **minimum of:** - The active era, and - The last fully processed offence era. This ensures withdrawals don't happen for eras that still have pending offences. **Why not block withdrawals per account instead?** That would require scanning each page of `ErasStakersPaged` for the validator the staker is exposed to — which is costly. Since this is an edge case, blocking at the era level is simpler and sufficient. --- ### 2. Block withdrawals if unapplied slashes remain in the previous era Introduces a new safefguard: withdrawals are blocked if the immediately concluded era has unapplied slashes. Once the era is cleared, withdrawals resume as normal. We also only care about previous era, and if this ends up not enough to nudge participants to clear the unapplied slashes, the withdrawals should resume again in the next era (provided no new unapplied slashes remain in current era as well). When this happens, trying to withdraw would emit the error `UnappliedSlashesInPreviousEra`. Anyone can look up the unapplied slashes in the previous era through the storage `UnappliedSlashes` and apply these via the permissionless call `apply_slash`. This light enforcement should be enough to maintain slashing guarantees without being too disruptive. --- ### 3. Ensure a full era for applying slashes Previously, it was possible to receive an offence report at the very end of the era when its slashes were meant to be applied. We now reject offences that arrive **after** the end of the era *before* their application era. An event `OffenceTooOld` is emitted when this happens to make the behavior visible. **Open question:** We may want to update the `prune_up_to` value sent from AH to RC to `ActiveEra - SlashDeferDuration + 1` instead of `ActiveEra - BondingDuration`. This could further guarantee that late offences never reach the staking pallet. --- ### 4. Unbonding chunks are keyed by active era We’re moving away from using `CurrentEra` in business logic (except for elections). This change aligns unbonding with `ActiveEra`. The rest of the code will be refactored in [#8807](#8807). --- ### 5. More checks on offence pipeline health Added extra try state checks to ensure the offence processing state is healthy. --- ## Notes This is mostly a defensive improvement. These situations are extremely rare, but the added safeguards ensure slashing guarantees are upheld even in these extreme cases.
paritytech-release-backport-bot Bot
pushed a commit
that referenced
this pull request
Jul 30, 2025
## Context The offence handling pipeline has four main stages: 1. **Reporting on RC**: Offences are reported on the Relay Chain (RC) and exported to Asset Hub (AH) via RC::AHClient. 2. **Queueing**: AH staking pallet receives the offence in `fn on_new_offence`, performs sanity checks, and enqueues it in `OffenceQueue` and `OffenceQueueEras`. 3. **Processing**: Offences are processed one by one, starting from the oldest era in the queue. Processed items are stored in `UnappliedSlashes`. 4. **Application**: Finally, slashes are applied one page per block after the slash defer duration from the offence era. --- ## Problem While unlikely, a spam of offence reports could slow down processing enough that some offences remain unhandled even after their bonding period ends. This creates a rare corner case: a withdrawal could happen for an era that still has pending offences, which breaks slashing guarantees. Also, slash application happens gradually (one page per block). If some slashes are left unapplied at the end of their application era (due to chain stalls or similar), they must be manually applied using the permissionless `apply_slash` call. Both scenarios are rare, but they expose risks to the integrity of slashing. --- ## What this PR Changes ### 1. Block withdrawals for eras with unprocessed offences Withdrawals are now restricted to the **minimum of:** - The active era, and - The last fully processed offence era. This ensures withdrawals don't happen for eras that still have pending offences. **Why not block withdrawals per account instead?** That would require scanning each page of `ErasStakersPaged` for the validator the staker is exposed to — which is costly. Since this is an edge case, blocking at the era level is simpler and sufficient. --- ### 2. Block withdrawals if unapplied slashes remain in the previous era Introduces a new safefguard: withdrawals are blocked if the immediately concluded era has unapplied slashes. Once the era is cleared, withdrawals resume as normal. We also only care about previous era, and if this ends up not enough to nudge participants to clear the unapplied slashes, the withdrawals should resume again in the next era (provided no new unapplied slashes remain in current era as well). When this happens, trying to withdraw would emit the error `UnappliedSlashesInPreviousEra`. Anyone can look up the unapplied slashes in the previous era through the storage `UnappliedSlashes` and apply these via the permissionless call `apply_slash`. This light enforcement should be enough to maintain slashing guarantees without being too disruptive. --- ### 3. Ensure a full era for applying slashes Previously, it was possible to receive an offence report at the very end of the era when its slashes were meant to be applied. We now reject offences that arrive **after** the end of the era *before* their application era. An event `OffenceTooOld` is emitted when this happens to make the behavior visible. **Open question:** We may want to update the `prune_up_to` value sent from AH to RC to `ActiveEra - SlashDeferDuration + 1` instead of `ActiveEra - BondingDuration`. This could further guarantee that late offences never reach the staking pallet. --- ### 4. Unbonding chunks are keyed by active era We’re moving away from using `CurrentEra` in business logic (except for elections). This change aligns unbonding with `ActiveEra`. The rest of the code will be refactored in [#8807](#8807). --- ### 5. More checks on offence pipeline health Added extra try state checks to ensure the offence processing state is healthy. --- ## Notes This is mostly a defensive improvement. These situations are extremely rare, but the added safeguards ensure slashing guarantees are upheld even in these extreme cases. (cherry picked from commit 204c916)
Ank4n
commented
Jul 31, 2025
| fn on_idle_unstake(b: Linear<1, { T::BatchSize::get() }>) { | ||
| ErasToCheckPerBlock::<T>::put(1); | ||
| // initialise the era. | ||
| T::Staking::set_active_era(0); |
Contributor
Author
There was a problem hiding this comment.
remove and use from testing utils
Contributor
|
maybe it's just me but can we also rename CurrentEra in PlannedEra or something like that in the scope of this task? |
alvicsam
pushed a commit
that referenced
this pull request
Oct 17, 2025
## Context The offence handling pipeline has four main stages: 1. **Reporting on RC**: Offences are reported on the Relay Chain (RC) and exported to Asset Hub (AH) via RC::AHClient. 2. **Queueing**: AH staking pallet receives the offence in `fn on_new_offence`, performs sanity checks, and enqueues it in `OffenceQueue` and `OffenceQueueEras`. 3. **Processing**: Offences are processed one by one, starting from the oldest era in the queue. Processed items are stored in `UnappliedSlashes`. 4. **Application**: Finally, slashes are applied one page per block after the slash defer duration from the offence era. --- ## Problem While unlikely, a spam of offence reports could slow down processing enough that some offences remain unhandled even after their bonding period ends. This creates a rare corner case: a withdrawal could happen for an era that still has pending offences, which breaks slashing guarantees. Also, slash application happens gradually (one page per block). If some slashes are left unapplied at the end of their application era (due to chain stalls or similar), they must be manually applied using the permissionless `apply_slash` call. Both scenarios are rare, but they expose risks to the integrity of slashing. --- ## What this PR Changes ### 1. Block withdrawals for eras with unprocessed offences Withdrawals are now restricted to the **minimum of:** - The active era, and - The last fully processed offence era. This ensures withdrawals don't happen for eras that still have pending offences. **Why not block withdrawals per account instead?** That would require scanning each page of `ErasStakersPaged` for the validator the staker is exposed to — which is costly. Since this is an edge case, blocking at the era level is simpler and sufficient. --- ### 2. Block withdrawals if unapplied slashes remain in the previous era Introduces a new safefguard: withdrawals are blocked if the immediately concluded era has unapplied slashes. Once the era is cleared, withdrawals resume as normal. We also only care about previous era, and if this ends up not enough to nudge participants to clear the unapplied slashes, the withdrawals should resume again in the next era (provided no new unapplied slashes remain in current era as well). When this happens, trying to withdraw would emit the error `UnappliedSlashesInPreviousEra`. Anyone can look up the unapplied slashes in the previous era through the storage `UnappliedSlashes` and apply these via the permissionless call `apply_slash`. This light enforcement should be enough to maintain slashing guarantees without being too disruptive. --- ### 3. Ensure a full era for applying slashes Previously, it was possible to receive an offence report at the very end of the era when its slashes were meant to be applied. We now reject offences that arrive **after** the end of the era *before* their application era. An event `OffenceTooOld` is emitted when this happens to make the behavior visible. **Open question:** We may want to update the `prune_up_to` value sent from AH to RC to `ActiveEra - SlashDeferDuration + 1` instead of `ActiveEra - BondingDuration`. This could further guarantee that late offences never reach the staking pallet. --- ### 4. Unbonding chunks are keyed by active era We’re moving away from using `CurrentEra` in business logic (except for elections). This change aligns unbonding with `ActiveEra`. The rest of the code will be refactored in [#8807](#8807). --- ### 5. More checks on offence pipeline health Added extra try state checks to ensure the offence processing state is healthy. --- ## Notes This is mostly a defensive improvement. These situations are extremely rare, but the added safeguards ensure slashing guarantees are upheld even in these extreme cases.
|
All GitHub workflows were cancelled due to failure one of the required jobs. |
7 tasks
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.
We are using
CurrentErafor operations like payout stakers, unbond, etc. This is incorrect usage of it.CurrentEraonly indicates that we are preparing for a new era to be activated. It should be only mutated as part ofelection logicand nowhere else.As part of this PR, we should make current era read/write functions private to the
session_rotation::Rotatorand ensure its not accessed or used anywhere else directly (better not use in tests as well but may be acceptable). We should useactive_erainstead.We should also ensure this doesn’t break any other pallets that depend on
pallet-staking-async(fast-unstake, nomination-pools, delegated-staking), by testing those pallets againstpallet-staking-asyncdirectly instead ofpallet-staking. This is handled by #9016 so let's be sure to merge it before the current PR.