mirror of
https://github.com/pezkuwichain/pezkuwi-subxt.git
synced 2026-06-26 00:41:09 +00:00
Extrinsic to restore corrupt staking ledgers (#3706)
This PR adds a new extrinsic `Call::restore_ledger ` gated by `StakingAdmin` origin that restores a corrupted staking ledger. This extrinsic will be used to recover ledgers that were affected by the issue discussed in https://github.com/paritytech/polkadot-sdk/issues/3245. The extrinsic will re-write the storage items associated with a stash account provided as input parameter. The data used to reset the ledger can be either i) fetched on-chain or ii) partially/totally set by the input parameters of the call. In order to use on-chain data to restore the staking locks, we need a way to read the current lock in the balances pallet. This PR adds a `InspectLockableCurrency` trait and implements it in the pallet balances. An alternative would be to tightly couple staking with the pallet balances but that's inelegant (an example of how it would look like in [this branch](https://github.com/paritytech/polkadot-sdk/tree/gpestana/ledger-badstate-clean_tightly)). More details on the type of corruptions and corresponding fixes https://hackmd.io/DLb5jEYWSmmvqXC9ae4yRg?view#/ We verified that the `Call::restore_ledger` does fix all current corrupted ledgers in Polkadot and Kusama. You can verify it here https://hackmd.io/v-XNrEoGRpe7APR-EZGhOA. **Changes introduced** - Adds `Call::restore_ledger ` extrinsic to recover a corrupted ledger; - Adds trait `frame_support::traits::currency::InspectLockableCurrency` to allow external pallets to read current locks given an account and lock ID; - Implements the `InspectLockableCurrency` in the pallet-balances. - Adds staking locks try-runtime checks (https://github.com/paritytech/polkadot-sdk/issues/3751) **Todo** - [x] benchmark `Call::restore_ledger` - [x] throughout testing of all ledger recovering cases - [x] consider adding the staking locks try-runtime checks to this PR (https://github.com/paritytech/polkadot-sdk/issues/3751) - [x] simulate restoring all ledgers (https://hackmd.io/Dsa2tvhISNSs7zcqriTaxQ?view) in Polkadot and Kusama using chopsticks -- https://hackmd.io/v-XNrEoGRpe7APR-EZGhOA Related to https://github.com/paritytech/polkadot-sdk/issues/3245 Closes https://github.com/paritytech/polkadot-sdk/issues/3751 --------- Co-authored-by: command-bot <>
This commit is contained in:
@@ -25,8 +25,8 @@ use frame_election_provider_support::{
|
||||
use frame_support::{
|
||||
assert_ok, derive_impl, ord_parameter_types, parameter_types,
|
||||
traits::{
|
||||
ConstU64, Currency, EitherOfDiverse, FindAuthor, Get, Hooks, Imbalance, OnUnbalanced,
|
||||
OneSessionHandler,
|
||||
ConstU64, Currency, EitherOfDiverse, FindAuthor, Get, Hooks, Imbalance, LockableCurrency,
|
||||
OnUnbalanced, OneSessionHandler, WithdrawReasons,
|
||||
},
|
||||
weights::constants::RocksDbWeight,
|
||||
};
|
||||
@@ -786,55 +786,86 @@ pub(crate) fn bond_controller_stash(controller: AccountId, stash: AccountId) ->
|
||||
Ok(())
|
||||
}
|
||||
|
||||
// simulates `set_controller` without corrupted ledger checks for testing purposes.
|
||||
pub(crate) fn set_controller_no_checks(stash: &AccountId) {
|
||||
let controller = Bonded::<Test>::get(stash).expect("testing stash should be bonded");
|
||||
let ledger = Ledger::<Test>::get(&controller).expect("testing ledger should exist");
|
||||
|
||||
Ledger::<Test>::remove(&controller);
|
||||
Ledger::<Test>::insert(stash, ledger);
|
||||
Bonded::<Test>::insert(stash, stash);
|
||||
}
|
||||
|
||||
// simulates `bond_extra` without corrupted ledger checks for testing purposes.
|
||||
pub(crate) fn bond_extra_no_checks(stash: &AccountId, amount: Balance) {
|
||||
let controller = Bonded::<Test>::get(stash).expect("bond must exist to bond_extra");
|
||||
let mut ledger = Ledger::<Test>::get(&controller).expect("ledger must exist to bond_extra");
|
||||
|
||||
let new_total = ledger.total + amount;
|
||||
Balances::set_lock(crate::STAKING_ID, stash, new_total, WithdrawReasons::all());
|
||||
ledger.total = new_total;
|
||||
ledger.active = new_total;
|
||||
Ledger::<Test>::insert(controller, ledger);
|
||||
}
|
||||
|
||||
pub(crate) fn setup_double_bonded_ledgers() {
|
||||
assert_ok!(Staking::bond(RuntimeOrigin::signed(1), 10, RewardDestination::Staked));
|
||||
assert_ok!(Staking::bond(RuntimeOrigin::signed(2), 20, RewardDestination::Staked));
|
||||
assert_ok!(Staking::bond(RuntimeOrigin::signed(3), 20, RewardDestination::Staked));
|
||||
let init_ledgers = Ledger::<Test>::iter().count();
|
||||
|
||||
let _ = Balances::make_free_balance_be(&333, 2000);
|
||||
let _ = Balances::make_free_balance_be(&444, 2000);
|
||||
let _ = Balances::make_free_balance_be(&555, 2000);
|
||||
let _ = Balances::make_free_balance_be(&777, 2000);
|
||||
|
||||
assert_ok!(Staking::bond(RuntimeOrigin::signed(333), 10, RewardDestination::Staked));
|
||||
assert_ok!(Staking::bond(RuntimeOrigin::signed(444), 20, RewardDestination::Staked));
|
||||
assert_ok!(Staking::bond(RuntimeOrigin::signed(555), 20, RewardDestination::Staked));
|
||||
// not relevant to the test case, but ensures try-runtime checks pass.
|
||||
[1, 2, 3]
|
||||
[333, 444, 555]
|
||||
.iter()
|
||||
.for_each(|s| Payee::<Test>::insert(s, RewardDestination::Staked));
|
||||
|
||||
// we want to test the case where a controller can also be a stash of another ledger.
|
||||
// for that, we change the controller/stash bonding so that:
|
||||
// * 2 becomes controller of 1.
|
||||
// * 3 becomes controller of 2.
|
||||
// * 4 becomes controller of 3.
|
||||
let ledger_1 = Ledger::<Test>::get(1).unwrap();
|
||||
let ledger_2 = Ledger::<Test>::get(2).unwrap();
|
||||
let ledger_3 = Ledger::<Test>::get(3).unwrap();
|
||||
// * 444 becomes controller of 333.
|
||||
// * 555 becomes controller of 444.
|
||||
// * 777 becomes controller of 555.
|
||||
let ledger_333 = Ledger::<Test>::get(333).unwrap();
|
||||
let ledger_444 = Ledger::<Test>::get(444).unwrap();
|
||||
let ledger_555 = Ledger::<Test>::get(555).unwrap();
|
||||
|
||||
// 4 becomes controller of 3.
|
||||
Bonded::<Test>::mutate(3, |controller| *controller = Some(4));
|
||||
Ledger::<Test>::insert(4, ledger_3);
|
||||
// 777 becomes controller of 555.
|
||||
Bonded::<Test>::mutate(555, |controller| *controller = Some(777));
|
||||
Ledger::<Test>::insert(777, ledger_555);
|
||||
|
||||
// 3 becomes controller of 2.
|
||||
Bonded::<Test>::mutate(2, |controller| *controller = Some(3));
|
||||
Ledger::<Test>::insert(3, ledger_2);
|
||||
// 555 becomes controller of 444.
|
||||
Bonded::<Test>::mutate(444, |controller| *controller = Some(555));
|
||||
Ledger::<Test>::insert(555, ledger_444);
|
||||
|
||||
// 2 becomes controller of 1
|
||||
Bonded::<Test>::mutate(1, |controller| *controller = Some(2));
|
||||
Ledger::<Test>::insert(2, ledger_1);
|
||||
// 1 is not controller anymore.
|
||||
Ledger::<Test>::remove(1);
|
||||
// 444 becomes controller of 333.
|
||||
Bonded::<Test>::mutate(333, |controller| *controller = Some(444));
|
||||
Ledger::<Test>::insert(444, ledger_333);
|
||||
|
||||
// 333 is not controller anymore.
|
||||
Ledger::<Test>::remove(333);
|
||||
|
||||
// checks. now we have:
|
||||
// * 3 ledgers
|
||||
assert_eq!(Ledger::<Test>::iter().count(), 3);
|
||||
// * stash 1 has controller 2.
|
||||
assert_eq!(Bonded::<Test>::get(1), Some(2));
|
||||
assert_eq!(StakingLedger::<Test>::paired_account(StakingAccount::Stash(1)), Some(2));
|
||||
assert_eq!(Ledger::<Test>::get(2).unwrap().stash, 1);
|
||||
// * +3 ledgers
|
||||
assert_eq!(Ledger::<Test>::iter().count(), 3 + init_ledgers);
|
||||
|
||||
// * stash 2 has controller 3.
|
||||
assert_eq!(Bonded::<Test>::get(2), Some(3));
|
||||
assert_eq!(StakingLedger::<Test>::paired_account(StakingAccount::Stash(2)), Some(3));
|
||||
assert_eq!(Ledger::<Test>::get(3).unwrap().stash, 2);
|
||||
// * stash 333 has controller 444.
|
||||
assert_eq!(Bonded::<Test>::get(333), Some(444));
|
||||
assert_eq!(StakingLedger::<Test>::paired_account(StakingAccount::Stash(333)), Some(444));
|
||||
assert_eq!(Ledger::<Test>::get(444).unwrap().stash, 333);
|
||||
|
||||
// * stash 3 has controller 4.
|
||||
assert_eq!(Bonded::<Test>::get(3), Some(4));
|
||||
assert_eq!(StakingLedger::<Test>::paired_account(StakingAccount::Stash(3)), Some(4));
|
||||
assert_eq!(Ledger::<Test>::get(4).unwrap().stash, 3);
|
||||
// * stash 444 has controller 555.
|
||||
assert_eq!(Bonded::<Test>::get(444), Some(555));
|
||||
assert_eq!(StakingLedger::<Test>::paired_account(StakingAccount::Stash(444)), Some(555));
|
||||
assert_eq!(Ledger::<Test>::get(555).unwrap().stash, 444);
|
||||
|
||||
// * stash 555 has controller 777.
|
||||
assert_eq!(Bonded::<Test>::get(555), Some(777));
|
||||
assert_eq!(StakingLedger::<Test>::paired_account(StakingAccount::Stash(555)), Some(777));
|
||||
assert_eq!(Ledger::<Test>::get(777).unwrap().stash, 555);
|
||||
}
|
||||
|
||||
#[macro_export]
|
||||
|
||||
Reference in New Issue
Block a user