[Staking] Bounded Slashing: Paginated Offence Processing & Slash Application (#7424)
closes https://github.com/paritytech/polkadot-sdk/issues/3610. helps https://github.com/paritytech/polkadot-sdk/issues/6344, but need to migrate storage `Offences::Reports` before we can remove exposure dependency in RC pallets. replaces https://github.com/paritytech/polkadot-sdk/issues/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](https://github.com/paritytech/polkadot-sdk/issues/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 <g6pestana@gmail.com> Co-authored-by: command-bot <> Co-authored-by:
Kian Paimani <5588131+kianenigma@users.noreply.github.com> Co-authored-by:
Guillaume Thiolliere <gui.thiolliere@gmail.com> Co-authored-by:
kianenigma <kian@parity.io> Co-authored-by:
Giuseppe Re <giuseppe.re@parity.io> Co-authored-by:
cmd[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Showing
- polkadot/runtime/test-runtime/src/lib.rs 2 additions, 2 deletionspolkadot/runtime/test-runtime/src/lib.rs
- polkadot/runtime/westend/src/lib.rs 1 addition, 0 deletionspolkadot/runtime/westend/src/lib.rs
- polkadot/runtime/westend/src/weights/pallet_staking.rs 4 additions, 0 deletionspolkadot/runtime/westend/src/weights/pallet_staking.rs
- prdoc/pr_7424.prdoc 37 additions, 0 deletionsprdoc/pr_7424.prdoc
- substrate/bin/node/runtime/src/lib.rs 17 additions, 37 deletionssubstrate/bin/node/runtime/src/lib.rs
- substrate/frame/babe/src/mock.rs 2 additions, 2 deletionssubstrate/frame/babe/src/mock.rs
- substrate/frame/beefy/src/mock.rs 2 additions, 2 deletionssubstrate/frame/beefy/src/mock.rs
- substrate/frame/election-provider-multi-phase/test-staking-e2e/src/mock.rs 3 additions, 6 deletions...lection-provider-multi-phase/test-staking-e2e/src/mock.rs
- substrate/frame/grandpa/src/mock.rs 2 additions, 2 deletionssubstrate/frame/grandpa/src/mock.rs
- substrate/frame/offences/benchmarking/src/inner.rs 13 additions, 2 deletionssubstrate/frame/offences/benchmarking/src/inner.rs
- substrate/frame/offences/benchmarking/src/mock.rs 2 additions, 3 deletionssubstrate/frame/offences/benchmarking/src/mock.rs
- substrate/frame/root-offences/src/lib.rs 5 additions, 12 deletionssubstrate/frame/root-offences/src/lib.rs
- substrate/frame/root-offences/src/mock.rs 8 additions, 3 deletionssubstrate/frame/root-offences/src/mock.rs
- substrate/frame/root-offences/src/tests.rs 11 additions, 1 deletionsubstrate/frame/root-offences/src/tests.rs
- substrate/frame/session/benchmarking/src/mock.rs 3 additions, 3 deletionssubstrate/frame/session/benchmarking/src/mock.rs
- substrate/frame/staking/src/benchmarking.rs 61 additions, 9 deletionssubstrate/frame/staking/src/benchmarking.rs
- substrate/frame/staking/src/lib.rs 17 additions, 25 deletionssubstrate/frame/staking/src/lib.rs
- substrate/frame/staking/src/migrations.rs 84 additions, 259 deletionssubstrate/frame/staking/src/migrations.rs
- substrate/frame/staking/src/mock.rs 32 additions, 10 deletionssubstrate/frame/staking/src/mock.rs
- substrate/frame/staking/src/pallet/impls.rs 187 additions, 107 deletionssubstrate/frame/staking/src/pallet/impls.rs