Skip to content

Conversation

@Ank4n
Copy link
Contributor

@Ank4n Ank4n commented Feb 3, 2025

closes #3610.

helps #6344, but need to migrate storage Offences::Reports before we can remove exposure dependency in RC pallets.

replaces #6788.

Context

Slashing in staking is unbounded currently, which is a major blocker until staking can move to a parachain (AH).

Current Slashing Process (Unbounded)

  1. Offence Reported

    • Offences include multiple validators, each with potentially large exposure pages.
    • Slashes are computed immediately and scheduled for application after 28 eras.
  2. Slash Applied

    • All unapplied slashes are executed in one block at the start of the 28th era. This is an unbounded operation.

Proposed Slashing Process (Bounded)

  1. Offence Queueing

    • Offences are queued after basic sanity checks.
  2. Paged Offence Processing (Computing Slash)

    • Slashes are computed one validator exposure page at a time.
    • Unapplied slashes are stored in a double map:
      • Key 1 (k1): EraIndex
      • Key 2 (k2): (Validator, SlashFraction, PageIndex) — a unique identifier for each slash page
  3. Paged Slash Application

    • Slashes are applied one page at a time across multiple blocks.
    • Slash application starts at the 27th era (one era earlier than before) to ensure all slashes are applied before stakers can unbond (which starts from era 28 onwards).

Worst-Case Block Calculation for Slash Application

Polkadot:

  • 1 era = 24 hours, 1 block = 6s14,400 blocks/era
  • On parachains (12s blocks) → 7,200 blocks/era

Kusama:

  • 1 era = 6 hours, 1 block = 6s3,600 blocks/era
  • On parachains (12s blocks) → 1,800 blocks/era

Worst-Case Assumptions:

  • Total stakers: 40,000 nominators, 1000 validators. (Polkadot currently has ~23k nominators and 500 validators)
  • Max slashed: 50% so 20k nominators, 250 validators.
  • Page size: Validators with multiple page: (512 + 1)/2 = 256 , Validators with single page: 1

Calculation:

There might be a more accurate way to calculate this worst-case number, and this estimate could be significantly higher than necessary, but it shouldn’t exceed this value.

Blocks needed: 250 + 20k/256 = ~330 blocks.

Potential Improvement:

  • Consider adding an Offchain Worker (OCW) task to further optimize slash application in future updates.
  • Dynamically batch unapplied slashes based on number of nominators in the page, or process until reserved weight limit is exhausted.

Summary of Changes

Storage

  • New:

    • OffenceQueue (StorageDoubleMap)
      • K1: Era
      • K2: Offending validator account
      • V: OffenceRecord
    • OffenceQueueEras (StorageValue)
      • V: BoundedVec<EraIndex, BoundingDuration>
    • ProcessingOffence (StorageValue)
      • V: (Era, offending validator account, OffenceRecord)
  • Changed:

    • UnappliedSlashes:
      • Old: StorageMap<K -> Era, V -> Vec<UnappliedSlash>>
      • New: StorageDoubleMap<K1 -> Era, K2 -> (validator_acc, perbill, page_index), V -> UnappliedSlash>

Events

  • New:
    • SlashComputed { offence_era, slash_era, offender, page }
    • SlashCancelled { slash_era, slash_key, payout }

Error

  • Changed:
    • InvalidSlashIndex → Renamed to InvalidSlashRecord
  • Removed:
    • NotSortedAndUnique
  • Added:
    • EraNotStarted

Call

  • Changed:
    • cancel_deferred_slash(era, slash_indices: Vec<u32>)
      → Now takes Vec<(validator_acc, slash_fraction, page_index)>
  • New:
    • apply_slash(slash_era, slash_key: (validator_acc, slash_fraction, page_index))

Runtime Config

  • FullIdentification is now set to a unit type (()) / null identity,
    replacing the previous exposure type for all runtimes using pallet_session::historical.

TODO

  • Fixed broken CancelDeferredSlashes.
  • Ensure on_offence called only with validator account for identification everywhere.
  • Ensure we never need to read full exposure.
  • Tests for multi block processing and application of slash.
  • Migrate UnappliedSlashes
  • Bench (crude, needs proper bench as followup)
    • on_offence()
    • process_offence()
    • apply_slash()

Followups (tracker link)

  • OCW task to process offence + apply slashes.
  • Minimum time for governance to cancel deferred slash.
  • Allow root or staking admin to add a custom slash.
  • Test HistoricalSession proof works fine with eras before removing exposure as full identity.
  • Properly bench offence processing and slashing.
  • Handle Offences::Reports migration when removing validator exposure as identity.

gpestana and others added 30 commits October 13, 2024 15:34
@paritytech-review-bot paritytech-review-bot bot requested a review from a team February 17, 2025 15:24
Copy link
Contributor

@seadanda seadanda left a comment

Choose a reason for hiding this comment

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

High level LGTM

@seadanda seadanda added this pull request to the merge queue Feb 17, 2025
Merged via the queue into master with commit dda2cb5 Feb 17, 2025
222 of 234 checks passed
@seadanda seadanda deleted the ankan/paged-slashing branch February 17, 2025 23:40
clangenb pushed a commit to clangenb/polkadot-sdk that referenced this pull request Feb 19, 2025
## Multi Block Election Pallet

This PR adds the first iteration of the multi-block staking pallet. 

From this point onwards, the staking and its election provider pallets
are being customized to work in AssetHub. While usage in solo-chains is
still possible, it is not longer the main focus of this pallet. For a
safer usage, please fork and user an older version of this pallet.

---

## Replaces

- [x] paritytech#6034 
- [x] paritytech#5272

## Related PRs: 

- [x] paritytech#7483
- [ ] paritytech#7357
- [ ] paritytech#7424
- [ ] paritytech/polkadot-staking-miner#955

This branch can be periodically merged into
paritytech#7358 ->
paritytech#6996

## TODOs: 

- [x] rebase to master 
- Benchmarking for staking critical path
  - [x] snapshot
  - [x] election result
- Benchmarking for EPMB critical path
  - [x] snapshot
  - [x] verification
  - [x] submission
  - [x] unsigned submission
  - [ ] election results fetching
- [ ] Fix deletion weights. Either of
  - [ ] Garbage collector + lazy removal of all paged storage items
  - [ ] Confirm that deletion is small PoV footprint.
- [ ] Move election prediction to be push based. @tdimitrov 
- [ ] integrity checks for bounds 
- [ ] Properly benchmark this as a part of CI -- for now I will remove
them as they are too slow
- [x] add try-state to all pallets
- [x] Staking to allow genesis dev accounts to be created internally
- [x] Decouple miner config so @niklasad1 can work on the miner
72841b7
- [x] duplicate snapshot page reported by @niklasad1 
- [ ] paritytech#6520 or equivalent
-- during snapshot, `VoterList` must be locked
- [ ] Move target snapshot to a separate block

---------

Co-authored-by: Gonçalo Pestana <[email protected]>
Co-authored-by: Ankan <[email protected]>
Co-authored-by: command-bot <>
Co-authored-by: Guillaume Thiolliere <[email protected]>
Co-authored-by: Giuseppe Re <[email protected]>
Co-authored-by: cmd[bot] <41898282+github-actions[bot]@users.noreply.github.com>
clangenb pushed a commit to clangenb/polkadot-sdk that referenced this pull request Feb 19, 2025
…ication (paritytech#7424)

closes paritytech#3610.

helps paritytech#6344, but need
to migrate storage `Offences::Reports` before we can remove exposure
dependency in RC pallets.

replaces paritytech#6788.

## Context  
Slashing in staking is unbounded currently, which is a major blocker
until staking can move to a parachain (AH).

### Current Slashing Process (Unbounded)  

1. **Offence Reported**  
- Offences include multiple validators, each with potentially large
exposure pages.
- Slashes are **computed immediately** and scheduled for application
after **28 eras**.

2. **Slash Applied**  
- All unapplied slashes are executed in **one block** at the start of
the **28th era**. This is an **unbounded operation**.


### Proposed Slashing Process (Bounded)  

1. **Offence Queueing**  
   - Offences are **queued** after basic sanity checks.  

2. **Paged Offence Processing (Computing Slash)**  
   - Slashes are **computed one validator exposure page at a time**.  
   - **Unapplied slashes** are stored in a **double map**:  
     - **Key 1 (k1):** `EraIndex`  
- **Key 2 (k2):** `(Validator, SlashFraction, PageIndex)` — a unique
identifier for each slash page

3. **Paged Slash Application**  
- Slashes are **applied one page at a time** across multiple blocks.
- Slash application starts at the **27th era** (one era earlier than
before) to ensure all slashes are applied **before stakers can unbond**
(which starts from era 28 onwards).

---

## Worst-Case Block Calculation for Slash Application  

### Polkadot:  
- **1 era = 24 hours**, **1 block = 6s** → **14,400 blocks/era**  
- On parachains (**12s blocks**) → **7,200 blocks/era**  

### Kusama:  
- **1 era = 6 hours**, **1 block = 6s** → **3,600 blocks/era**  
- On parachains (**12s blocks**) → **1,800 blocks/era**  

### Worst-Case Assumptions:  
- **Total stakers:** 40,000 nominators, 1000 validators. (Polkadot
currently has ~23k nominators and 500 validators)
- **Max slashed:** 50% so 20k nominators, 250 validators.  
- **Page size:** Validators with multiple page: (512 + 1)/2 = 256 ,
Validators with single page: 1

### Calculation:  
There might be a more accurate way to calculate this worst-case number,
and this estimate could be significantly higher than necessary, but it
shouldn’t exceed this value.

Blocks needed: 250 + 20k/256 = ~330 blocks.

##  *Potential Improvement:*  
- Consider adding an **Offchain Worker (OCW)** task to further optimize
slash application in future updates.
- Dynamically batch unapplied slashes based on number of nominators in
the page, or process until reserved weight limit is exhausted.

----
## Summary of Changes  

### Storage  
- **New:**  
  - `OffenceQueue` *(StorageDoubleMap)*  
    - **K1:** Era  
    - **K2:** Offending validator account  
    - **V:** `OffenceRecord`  
  - `OffenceQueueEras` *(StorageValue)*  
    - **V:** `BoundedVec<EraIndex, BoundingDuration>`  
  - `ProcessingOffence` *(StorageValue)*  
    - **V:** `(Era, offending validator account, OffenceRecord)`  

- **Changed:**  
  - `UnappliedSlashes`:  
    - **Old:** `StorageMap<K -> Era, V -> Vec<UnappliedSlash>>`  
- **New:** `StorageDoubleMap<K1 -> Era, K2 -> (validator_acc, perbill,
page_index), V -> UnappliedSlash>`

### Events  
- **New:**  
  - `SlashComputed { offence_era, slash_era, offender, page }`  
  - `SlashCancelled { slash_era, slash_key, payout }`  

### Error  
- **Changed:**  
  - `InvalidSlashIndex` → Renamed to `InvalidSlashRecord`  
- **Removed:**  
  - `NotSortedAndUnique`  
- **Added:**  
  - `EraNotStarted`  

### Call  
- **Changed:**  
  - `cancel_deferred_slash(era, slash_indices: Vec<u32>)`  
    → Now takes `Vec<(validator_acc, slash_fraction, page_index)>`  
- **New:**  
- `apply_slash(slash_era, slash_key: (validator_acc, slash_fraction,
page_index))`

### Runtime Config  
- `FullIdentification` is now set to a unit type (`()`) / null identity,
replacing the previous exposure type for all runtimes using
`pallet_session::historical`.

## TODO
- [x] Fixed broken `CancelDeferredSlashes`.
- [x] Ensure on_offence called only with validator account for
identification everywhere.
- [ ] Ensure we never need to read full exposure.
- [x] Tests for multi block processing and application of slash.
- [x] Migrate UnappliedSlashes 
- [x] Bench (crude, needs proper bench as followup)
  - [x] on_offence()
  - [x] process_offence()
  - [x] apply_slash()
 
 
## Followups (tracker
[link](paritytech#7596))
- [ ] OCW task to process offence + apply slashes.
- [ ] Minimum time for governance to cancel deferred slash.
- [ ] Allow root or staking admin to add a custom slash.
- [ ] Test HistoricalSession proof works fine with eras before removing
exposure as full identity.
- [ ] Properly bench offence processing and slashing.
- [ ] Handle Offences::Reports migration when removing validator
exposure as identity.

---------

Co-authored-by: Gonçalo Pestana <[email protected]>
Co-authored-by: command-bot <>
Co-authored-by: Kian Paimani <[email protected]>
Co-authored-by: Guillaume Thiolliere <[email protected]>
Co-authored-by: kianenigma <[email protected]>
Co-authored-by: Giuseppe Re <[email protected]>
Co-authored-by: cmd[bot] <41898282+github-actions[bot]@users.noreply.github.com>
@kianenigma kianenigma mentioned this pull request Mar 14, 2025
40 tasks
tdimitrov added a commit that referenced this pull request Mar 16, 2025
Ank4n added a commit that referenced this pull request Mar 16, 2025
@miss-stake miss-stake moved this from In progress to Scheduled in Security Audit (PRs) - SRLabs Mar 24, 2025
github-merge-queue bot pushed a commit that referenced this pull request Mar 24, 2025
#7939)

Revert the following PRs which we are pulling from release stable2503:
- #7582
- #7424
- #7282

and leave pallet-staking in its pre-AHM state.

## Context

We are forking pallet-staking into `pallet-staking` (also referred as
staking-classic, this is the version that will stay on RC) and
`pallet-staking-next` which will live on AH post AHM.

Additional context:
#7858 (comment)

These changes in crate `pallet-staking` will become the staking classic.
The staking next version is worked in the PR #7601.

## For AHM migration
The `UnappliedSlashes` storage will need to be translated from
`rc::staking-classic` to `ah::staking-next`.
[Bookmarking](https://github.com/paritytech/polkadot-sdk/blob/master/substrate/frame/staking/src/migrations.rs#L91)
the code that can be referred for this.

## Follow ups
(cc: @tdimitrov and @seadanda )
1) Revert pallet-staking v17 migration in westend.
- Update `in code storage version` of pallet-staking storage from 17 to
16 (separate PR).
- Update `on chain storage version` of pallet-staking storage from 17 to
16. The storage key for pallet-staking on chain version is
`0x5f3e4907f716ac89b6347d15ececedca4e7b9012096b41c4eb3aaf947f6ea429`
which should be set currently to `0x1100`, and needs to be updated to
`0x1000`.
- After the runtime upgrade with the code from this PR is deployed on
Westend, kill the following storage prefixes under the Pallet prefix
`Staking`:
  - OffenceQueue
  - OffenceQueueEras
  - ProcessingOffence
- UnappliedSlashes: This also exists in staking-classic as a storage map
(one key) and in pre-revert code as double storage map (two keys).
Killing with prefix `UnappliedSlashes` may kill the ones created post
upgrade (but that's okay for westend).
  - VoterSnapshotStatus
  - NextElectionPage
  - ElectableStashes

2) Remove exposure dependency
Worked in the PR: #7936.

---------

Co-authored-by: Tsvetomir Dimitrov <[email protected]>
Co-authored-by: Maciej <[email protected]>
Ank4n added a commit that referenced this pull request Mar 25, 2025
#7939)

Revert the following PRs which we are pulling from release stable2503:
- #7582
- #7424
- #7282

and leave pallet-staking in its pre-AHM state.

We are forking pallet-staking into `pallet-staking` (also referred as
staking-classic, this is the version that will stay on RC) and
`pallet-staking-next` which will live on AH post AHM.

Additional context:
#7858 (comment)

These changes in crate `pallet-staking` will become the staking classic.
The staking next version is worked in the PR #7601.

The `UnappliedSlashes` storage will need to be translated from
`rc::staking-classic` to `ah::staking-next`.
[Bookmarking](https://github.com/paritytech/polkadot-sdk/blob/master/substrate/frame/staking/src/migrations.rs#L91)
the code that can be referred for this.

(cc: @tdimitrov and @seadanda )
1) Revert pallet-staking v17 migration in westend.
- Update `in code storage version` of pallet-staking storage from 17 to
16 (separate PR).
- Update `on chain storage version` of pallet-staking storage from 17 to
16. The storage key for pallet-staking on chain version is
`0x5f3e4907f716ac89b6347d15ececedca4e7b9012096b41c4eb3aaf947f6ea429`
which should be set currently to `0x1100`, and needs to be updated to
`0x1000`.
- After the runtime upgrade with the code from this PR is deployed on
Westend, kill the following storage prefixes under the Pallet prefix
`Staking`:
  - OffenceQueue
  - OffenceQueueEras
  - ProcessingOffence
- UnappliedSlashes: This also exists in staking-classic as a storage map
(one key) and in pre-revert code as double storage map (two keys).
Killing with prefix `UnappliedSlashes` may kill the ones created post
upgrade (but that's okay for westend).
  - VoterSnapshotStatus
  - NextElectionPage
  - ElectableStashes

2) Remove exposure dependency
Worked in the PR: #7936.

---------

Co-authored-by: Tsvetomir Dimitrov <[email protected]>
Co-authored-by: Maciej <[email protected]>
@miss-stake miss-stake moved this from Scheduled to In progress in Security Audit (PRs) - SRLabs Apr 29, 2025
@redzsina redzsina moved this from In progress to Waiting for fix in Security Audit (PRs) - SRLabs Jun 30, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

I4-refactor Code needs refactoring. T2-pallets This PR/Issue is related to a particular pallet.

Projects

Status: Done
Status: Waiting for fix

Development

Successfully merging this pull request may close these issues.

[NPoS] Pagify slashing

8 participants