pallet-staking-async 0.3.0

FRAME pallet staking async
Documentation

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:

  1. Offence Reporting: Offences are reported from the relay chain through on_new_offences
  2. Queuing: Valid offences are added to the OffenceQueue for processing
  3. Processing: Offences are processed incrementally over multiple blocks
  4. 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 - 1 eras
  • With immediate slashing: Offences can be reported up to BondingDuration eras 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:

  1. Storage: Added to OffenceQueue: (EraIndex, AccountId) -> OffenceRecord
  2. Era Tracking: Era added to OffenceQueueEras (sorted vector of eras with offences)
  3. 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 UnappliedSlashes for future application
  • Applied at era: offence_era + SlashDeferDuration
  • Can be cancelled by governance before application

Storage Items Involved

  • OffenceQueue: Pending offences to process
  • OffenceQueueEras: Sorted list of eras with offences
  • ProcessingOffence: Currently processing offence
  • ValidatorSlashInEra: Tracks highest slash per validator per era
  • UnappliedSlashes: 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:

  1. Cannot withdraw if previous era has unapplied slashes
  2. Cannot withdraw funds from eras with unprocessed offences