---
type: slide
---
# Corrupted Staking ledgers
### walkthrough and recovery
<br>
- Possible corruption states
- `Call::restore_ledger()`
- Status in Polkadot and Kusama
- Tests with [chopsticks](https://github.com/AcalaNetwork/chopsticks)
<style>
.reveal {
font-size: 30px;
}
section {
text-align: left;
}
</style>
---
## Possible corruption states
Note\: per ledger (two ledgers may be affected)
<br>
- Double bonded ledger (not corrupted)
- Case 1: Corrupted Ledger
- Case 2: Corrupted and killed
- Case 3: Corrupted and killed (other)
- Case 4: Corrupted with `bond_extra`
---
## Refresher
The main issue is a combination of:
- Controller can become a stash of *another* ledger;
- Ledgers are keyed by controllers;
- `set_controller` assumes the stash can become a controller (and not overlap with other ledgers);
<br>
Thus, a ledger (and all data that is keyed based on the ledger state e.g. lock) may be overwritten.
---
## Double bonded ledger
This is not a corrupted state (but latent until `v1.1.3`)
```
Bonded(A) = B
Bonded(B) = C
Ledger(B) = Ledger_Ab { stash = A, ctrl = B }
Ledger(C) = Ledger_Bc { stash = B, ctrl = C }
```
<br>
`Ledger_Ab` will get corrupted if stash `B` calls `set_controller`.
---
## Case 1: Corrupted Ledger
```
// init state: double bonded
Bonded(A) = B
Bonded(B) = C
Ledger(B) = Ledger_Ab { stash = A, ctrl = B }
Ledger(C) = Ledger_Bc { stash = B, ctrl = C }
// calling `set_controller` corrupts one ledger
set_controller(C)
Bonded(A) = B
Bonded(B) = B
Ledger(B) = Ledger_Bb { stash = B, ctrl = B } ❌ Ledger_Ab overwritten
```
To fix in this case:
1. Restore `Ledger_Ab` (fetch `ledger.total` from staking lock of A)
2. Set controller of `A` to stash (`Bonded(A) = A`)
---
## Case 2: Corrupted and killed
```
// starting with corrupted ledger
Bonded(A) = B
Bonded(B) = B
Ledger(B) = Ledger_Bb { stash = B, ctrl = B } ❌ Ledger_Ab overwritten
kill(A) // corrupted ledger calls `kill`
StakingLock(A) != 0 ❌
Bonded(A) = None
StakingLock(B) == 0 ❌
Bonded(B) = B
Ledger(B) = None ❌
```
To fix in this case:
1. Release lock `StakingLock(A)`
2. Restore ledger `Ledger_Bb` with amount *before* kill
3. Restore `Ledger_Bb` with same amount as the lock
Note that in this case, we need to provide explicitly the `ledger.amount`/ lock of the new restored ledger (that data is not on-chain anymore).
---
## Case 3: Corrupted and killed (other)
```
// starting with corrupted ledger
Bonded(A) = B
Bonded(B) = B
Ledger(B) = Ledger_Bb { stash = B, ctrl = B } ❌ Ledger_Ab overwritten
kill(B) // ledger overlapping corrupted calls `kill`
StakingLock(A) = ledger.total
Bonded(A) = B
Ledger(A) = None ❌
(everything with B has been cleared, OK)
```
To fix in this case:
1. Restore `Ledger(A)` with `ledger.total = StakingLock(A)`
---
## Case 4: Corrupted with `bond_extra`
```
// starting with corrupted ledger
Bonded(A) = B
Bonded(B) = B
Ledger(B) = Ledger_Bb { stash = B, ctrl = B } ❌ Ledger_Ab overwritten
bond_extra(A, amount)
StakingLock(A) = amount_before_bond_extra ❌
StakingLock(B) = ledger.total + amount ❌
```
To fix in this case:
1. (Same as Case 1.)
2. Ensure `Ledger_A.total == StakingLock(A)`
3. Ensure `Ledger_B.total == StakingLock(B)`
---
# `Call::restore_ledger()`
Called with a `stash`, `maybe_total` and `maybe_controller` as inputs.
1. Identifies the `(stash, ledger)` tuple state. May be one of
- `Ok(LedgerIntegrityState::Ok)`
- `Ok(LedgerIntegrityState::Corrupted)`
- `Ok(LedgerIntegrityState::LockCorrupted)`
- `Err(BadState)`
2. Restores ledger and lock based on the tuple's state
https://github.com/paritytech/polkadot-sdk/pull/3706
---
# Status in Polkadot and Kusama
Reports https://hackmd.io/Dsa2tvhISNSs7zcqriTaxQ?view
---