Staking Async Pallet
This pallet is a fork of the original pallet-staking, with a number of key differences:
- It no longer has access to a secure timestamp, previously used to calculate the duration of an era.
- It no longer has access to a pallet-session.
- It no longer has access to a pallet-authorship.
- It is capable of working with a multi-page
ElectionProvider``, aka.pallet-election-provider-multi-block`.
While pallet-staking was somewhat general-purpose, this pallet is absolutely NOT right from
the get-go: It is designed to be used ONLY in Polkadot/Kusama AssetHub system parachains.
The workings of this pallet can be divided into a number of subsystems, as follows.
User Interactions
TODO
Session and Era Rotation
TODO
Exposure Collection
TODO
Slashing Pipeline and Withdrawal Restrictions
This pallet implements a robust slashing mechanism that ensures the integrity of the staking system while preventing stakers from withdrawing funds that might still be subject to slashing.
Overview of the Slashing Pipeline
The slashing process consists of multiple phases:
- Offence Reporting: Offences are reported from the relay chain through
on_new_offences - Queuing: Valid offences are added to the
OffenceQueuefor processing - Processing: Offences are processed incrementally over multiple blocks
- Application: Slashes are either applied immediately or deferred based on configuration
Phase 1: Offence Reporting
Offences are reported from the relay chain (e.g., from BABE, GRANDPA, BEEFY, or parachain
modules) through the on_new_offences function:
struct Offence {
offender: AccountId, // The validator being slashed
reporters: Vec<AccountId>, // Who reported the offence (may be empty)
slash_fraction: Perbill, // Percentage of stake to slash
}
Reporting Deadlines:
- With deferred slashing: Offences must be reported within
SlashDeferDuration - 1eras - With immediate slashing: Offences can be reported up to
BondingDurationeras old
Example: If SlashDeferDuration = 27 and current era is 100:
- Oldest reportable offence: Era 74 (100 - 26)
- Offences from era 73 or earlier are rejected
Phase 2: Queuing
When an offence passes validation, it's added to the queue:
- Storage: Added to
OffenceQueue:(EraIndex, AccountId) -> OffenceRecord - Era Tracking: Era added to
OffenceQueueEras(sorted vector of eras with offences) - Duplicate Handling: If an offence already exists for the same validator in the same era, only the higher slash fraction is kept
Phase 3: Processing
Offences are processed incrementally in on_initialize each block:
1. Load oldest offence from queue
2. Move to `ProcessingOffence` storage
3. For each exposure page (from last to first):
- Calculate slash for validator's own stake
- Calculate slash for each nominator (pro-rata based on exposure)
- Track total slash and reward amounts
4. Once all pages processed, create `UnappliedSlash`
Key Features:
- Page-by-page processing: Large validator sets don't overwhelm a single block
- Pro-rata slashing: Nominators slashed proportionally to their stake
- Reward calculation: A portion goes to reporters (if any)
Phase 4: Application
Based on SlashDeferDuration, slashes are either:
Immediate (SlashDeferDuration = 0):
- Applied right away in the same block
- Funds deducted from staking ledger immediately
Deferred (SlashDeferDuration > 0):
- Stored in
UnappliedSlashesfor future application - Applied at era:
offence_era + SlashDeferDuration - Can be cancelled by governance before application
Storage Items Involved
OffenceQueue: Pending offences to processOffenceQueueEras: Sorted list of eras with offencesProcessingOffence: Currently processing offenceValidatorSlashInEra: Tracks highest slash per validator per eraUnappliedSlashes: Deferred slashes waiting for application
Withdrawal Restrictions
To maintain slashing guarantees, withdrawals are restricted:
Withdrawal Era Calculation:
earliest_era_to_withdraw = min(
active_era,
last_fully_processed_offence_era + BondingDuration
)
Example:
- Active era: 100
- Oldest unprocessed offence: Era 70
- BondingDuration: 28
- Withdrawal allowed only for chunks with era ≤ 97 (70 - 1 + 28)
Withdrawal Timeline Example with an Offence:
Era: 90 91 92 93 94 95 96 97 98 99 100 ... 117 118
| | | | | | | | | | | | |
Unbond: U
Offence: X
Reported: R
Processed: P (within next few blocks)
Slash Applied: S
Withdraw: ❌ ✓
With BondingDuration = 28 and SlashDeferDuration = 27:
- User unbonds in era 90
- Offence occurs in era 90
- Reported in era 92 (typically within 2 days, but reportable until Era 116)
- Processed in era 92 (within next few blocks after reporting)
- Slash deferred for 27 eras, applied at era 117 (90 + 27)
- Cannot withdraw unbonded chunks until era 118 (90 + 28)
The 28-era bonding duration ensures that any offences committed before or during
unbonding have time to be reported, processed, and applied before funds can be
withdrawn. This provides a window for governance to cancel slashes that may have
resulted from software bugs.
Key Restrictions:
- Cannot withdraw if previous era has unapplied slashes
- Cannot withdraw funds from eras with unprocessed offences