Skip to content

[Staking] Allow nominators to be non-slashable and fast unbondable#10502

Merged
Ank4n merged 50 commits intomasterfrom
ankn-nominators-can-unbond-very-fast
Jan 14, 2026
Merged

[Staking] Allow nominators to be non-slashable and fast unbondable#10502
Ank4n merged 50 commits intomasterfrom
ankn-nominators-can-unbond-very-fast

Conversation

@Ank4n
Copy link
Copy Markdown
Contributor

@Ank4n Ank4n commented Dec 2, 2025

Context

We want to make nominators unslashable (configurable via a storage set), and once they are unslashable, they can also unbond and withdraw in 2 era instead of the full BondingDuration (28 eras).

Storage Changes

  • AreNominatorsSlashable: StorageValue<bool> (default: true): Runtime-configurable flag. Made this a storage value (not a config constant) so it can be enabled together with MinValidatorBond and MinCommission via set_staking_configs.
  • ErasNominatorsSlashable: StorageMap<EraIndex, bool> (default: true): Per-era snapshot of slashability setting. This ensures offences are processed with the rules that were in effect at the time of the offence, not the current rules. Cleaned up automatically for eras outside bonding window.
  • LastValidatorEra to track if a staker was a validator in a recent era and hence needs to follow full unbonding time. Does not need migration as long as we disable nominator slash (in other words: reduce their unbond time) at least one era after these changes are applied.

Slashing logic

  • Added process_offence_validator_only as a separate code path instead of overloading the same function. See process_offence_for_era in substrate/frame/staking-async/src/slashing.rs.
  • We might want to remove nominator slashing code completely at some point.

Unbonding logic:

  • Introduce new config constant NominatorFastUnbondDuration that determines the fast unbond duration (recommended value: 2 eras) when nominators are not slashable.
  • Added nominator_bonding_duration() to StakingInterface trait (returns NominatorFastUnbondDuration era when not slashable, full unbond duration otherwise).
  • Nomination pools now use nominator_bonding_duration(), so pool members also benefit from fast unbonding.
  • Ported auto-chill on full unbond from pallet-staking (PR Full Unbond in Staking #3811) to prevent InsufficientBond errors.
  • Nominators unbonding the era before the nominators become unslashable will still have 28 days of unbonding.

Era pruning:

  • Moved pruning of ValidatorSlashInEra as well as ErasNominatorsSlashable in lazy pruning. This has a minor (I believe acceptable) side effect that they will be cleaned up in 84 eras instead of 28 eras.

TODO

  • Ensure delegator slash works correctly (nomination pool).
  • Ensure pool members can unbond in 1 day as well.
  • Benchmark update.
  • Document how all three can be changed in one go: MinCommission, MinValidatorBond, and AreNominatorsSlashable.
  • Regenerate weight
  • Make nominator unbonding time configurable and set it to 2 eras.
  • Refactor compute slash to avoid calling slash_nominator completely.

@burdges
Copy link
Copy Markdown
Contributor

burdges commented Dec 6, 2025

We do need minimum comission and maybe minimum self stake before that.

@sigurpol
Copy link
Copy Markdown
Contributor

sigurpol commented Dec 6, 2025

We do need minimum comission and maybe minimum self stake before that.

Yes, this is the plan: deliver both min self stake + min commission (and support for external self-stake) together with changes for nominators (no slashing, 1-day unbonding) for March, together with the new issuance curve.

@Ank4n Ank4n marked this pull request as ready for review December 10, 2025 09:56
@Ank4n Ank4n requested a review from a team as a code owner December 10, 2025 09:56
@Ank4n Ank4n changed the title [WIP] No slashing nominators [Staking] Allow nominators to be unslashable and remove unbonding time if so Dec 16, 2025
@Ank4n Ank4n added T2-pallets This PR/Issue is related to a particular pallet. A4-backport-stable2512 Pull request must be backported to the stable2512 release branch labels Dec 16, 2025
let unbond_duration = if is_nominator {
<Self as sp_staking::StakingInterface>::nominator_bonding_duration()
} else {
T::BondingDuration::get()
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

rename the bonding_duration in StakingInterface to validator_bonding_duration.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

I wouldn't mind renaming BondingDuration to ValidatorBondingDuration, as we discussed, but it's not a hill I'm willing to die on 😅

Copy link
Copy Markdown
Contributor

@sigurpol sigurpol left a comment

Choose a reason for hiding this comment

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

Whole logic looks solid 🚀
Just some questions / suggestions.
I still have to go through the tests

ErasNominatorsSlashable::<T>::insert(era, AreNominatorsSlashable::<T>::get());

// Clean up old era entry that's now outside the bonding window
if let Some(old_era) = era.checked_sub(T::BondingDuration::get() + 1) {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

we discussed already but I believe it's a bit cleaner if this method just set the value for the given era and no other side effects. I believe cleaning up fits very well in the lazy era pruning so I would just add something like

 pub(crate) fn clear_era_information(era_index: EraIndex) {
      // ... existing clears ...
      ErasNominatorsSlashable::<T>::remove(era_index);
  }

Ok, we might end up keeping information for 84 eras and not 28 - but it's 1 bool per era so very neglibile

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

But now we use the lazy pruning right? I looked at it and it seems we are having individual steps for each storage, but I think its a bit overkill to break down single storage kills into different steps. To align with current approach, I can do the same, but wondering if you think we should refactor and combine single storage kills in one step?

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

yes I believe it would definitely make sense to combine all the cheap operation like single storage kills in a single step!!!!

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Some findings

  • Lazy pruning works only with history depth (84 eras) based era cleanups.
  • There is an existing era based storage (ValidatorsSlashInEra cleaned up every 28 eras) that is not lazily cleaned, and is unbounded (though implicitly bounded by max validators, or if we assume 67% honest validators then 33% of validator set size).

I have few options:

  • Keep bonded era (28 eras) based cleanups together (ValidatorSlashInEra + ErasNominatorsSlashable + BondedEras) and eager.
  • Move bonded era cleanups also along with lazy pruning that cleans it up 84 eras (I am leaning towards this).
  • Create another separate pruning mechanism for bonded era based storages.

@sigurpol any thoughts?

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Move bonded era cleanups also along with lazy pruning that cleans it up 84 eras (I am leaning towards this).

this seems the cleanest solution imho

let unbond_duration = if is_nominator {
<Self as sp_staking::StakingInterface>::nominator_bonding_duration()
} else {
T::BondingDuration::get()
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

I wouldn't mind renaming BondingDuration to ValidatorBondingDuration, as we discussed, but it's not a hill I'm willing to die on 😅

@sigurpol
Copy link
Copy Markdown
Contributor

feedback as outcome of the discussion with @Ank4n , @michalisFr , Jonas: let's say we are at era 100 and planning for 101. Snapshot for 101 is taken at session 1 of era 100. If you (nominator) unbond at Era 100 no matter when, then you are exposed for Era 101 and therefore unlock should happen at era 102 so at most 2d after the unbond. Basically you are always unlocked at the end of next era (so at era 102 in the example).

Copy link
Copy Markdown
Contributor

@kianenigma kianenigma left a comment

Choose a reason for hiding this comment

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

Perfectly done! thanks for bearing with my questions, in the main one I was hallucinating :)

@Ank4n Ank4n enabled auto-merge January 14, 2026 21:06
@Ank4n Ank4n added this pull request to the merge queue Jan 14, 2026
Merged via the queue into master with commit b3bfba6 Jan 14, 2026
236 of 240 checks passed
@Ank4n Ank4n deleted the ankn-nominators-can-unbond-very-fast branch January 14, 2026 22:19
@paritytech-release-backport-bot
Copy link
Copy Markdown

Created backport PR for stable2512:

Please cherry-pick the changes locally and resolve any conflicts.

git fetch origin backport-10502-to-stable2512
git worktree add --checkout .worktree/backport-10502-to-stable2512 backport-10502-to-stable2512
cd .worktree/backport-10502-to-stable2512
git reset --hard HEAD^
git cherry-pick -x b3bfba618e98f2aa10ee8d4233a15c1e09fef50f
git push --force-with-lease

sigurpol added a commit that referenced this pull request Jan 20, 2026
Backport #10502 into `stable2512` from Ank4n.

NOTE: this PR introduces a breaking change in the signature of the
extrinsic `set_staking_configs` that now has a new extra input
parameter: `are_nominators_slashable`. This is needed to be able to
deliver the feature of having non slashable nominator past March 14th.
Because of that, we have put `validate:false` in the related section of
the prdoc around pallet-staking-async. Please note that only Root origin
can call `set_staking_configs` so the practical impact of the breaking
change is basically zero. We need a Governance referendum to tune the
parameters and set nominators as non slashable around March 14th so we
need OpenGov to actually use the new version to be able to deliver the
requested feature, we can't really avoid that. Adding an extra extrinsic
just to avoid the breaking change seems an unnecessary overkill.

See the
[documentation](https://github.com/paritytech/polkadot-sdk/blob/master/docs/BACKPORT.md)
on how to use this bot.

<!--
  # To be used by other automation, do not modify:
  original-pr-number: #${pull_number}
-->

---------

Co-authored-by: Ankan <10196091+Ank4n@users.noreply.github.com>
Co-authored-by: Ankan <ankan.anurag@gmail.com>
Co-authored-by: Egor_P <egor@parity.io>
Co-authored-by: Paolo La Camera <paolo@parity.io>
@redzsina redzsina moved this from Scheduled to In progress in Security Audit (PRs) - SRLabs Feb 2, 2026
@rachsrl rachsrl moved this from In progress to Scheduled in Security Audit (PRs) - SRLabs Feb 2, 2026
sigurpol added a commit that referenced this pull request Feb 11, 2026
Leftover from #10810 backport. The function was replaced by
prune_era_single_entry_cleanups in #10502.
sigurpol added a commit that referenced this pull request Feb 11, 2026
Leftover from #10810 backport. The function was replaced by
prune_era_single_entry_cleanups in #10502.

---------

Co-authored-by: cmd[bot] <41898282+github-actions[bot]@users.noreply.github.com>
@redzsina redzsina moved this from Scheduled to In progress in Security Audit (PRs) - SRLabs Feb 12, 2026
@redzsina redzsina moved this from In progress to Audited in Security Audit (PRs) - SRLabs Mar 16, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

A4-backport-stable2512 Pull request must be backported to the stable2512 release branch T2-pallets This PR/Issue is related to a particular pallet.

Projects

Status: Audited
Status: Done

Development

Successfully merging this pull request may close these issues.

8 participants