Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Extrinsic to restore corrupt staking ledgers #3706

Merged
merged 34 commits into from
Mar 27, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
34 commits
Select commit Hold shift + click to select a range
03d821c
init
gpestana Mar 14, 2024
68df5d2
adds clean bad state bond
gpestana Mar 14, 2024
fc57112
adds more checks to the test
gpestana Mar 14, 2024
4038ca1
do not clean validator ledgers
gpestana Mar 14, 2024
af010de
Adds non-destructive ledger recover call
gpestana Mar 18, 2024
87ad1e5
Rethinks the clean/reset extrinsics
gpestana Mar 20, 2024
03cf845
Adds InspectLockableCurrency and impls it for Balances pallet
gpestana Mar 20, 2024
9eea773
uses InspectLockableCurrency in staking pallet to fetch lock of a stash
gpestana Mar 20, 2024
fbbf7f8
Adds simple tests for reset ledger
gpestana Mar 20, 2024
64cbee1
removes call::force_clean and replaces it with fast_unstake
gpestana Mar 21, 2024
a6cb392
nits
gpestana Mar 21, 2024
8f52d0a
nits2
gpestana Mar 21, 2024
c3ed2c3
removes clean ledger
gpestana Mar 21, 2024
11920e4
remove unecessary CannotForceCleanLedger error variant
gpestana Mar 21, 2024
42525dc
Merge branch 'master' into gpestana/ledger-badstate-clean
gpestana Mar 21, 2024
c9aba77
Adds benchmarks
gpestana Mar 21, 2024
ae4a192
removes metadata from ledger reset
gpestana Mar 21, 2024
df0cf74
simplify InspectLockableCurrency trait
gpestana Mar 21, 2024
a57a699
Ensures there's 0 imbalance on locks after ledger reset
gpestana Mar 22, 2024
e0bcbd3
Adds guards for option to reset controller of the ledger
gpestana Mar 22, 2024
209f3b0
Implements inspect_bond_state and refactors recover_ledger with tests…
gpestana Mar 23, 2024
b3e4a73
Adds bond_extra ledger recovery
gpestana Mar 24, 2024
42bb49d
Adds more integrity checks to restore_ledger
gpestana Mar 24, 2024
7099763
comment nit
gpestana Mar 24, 2024
aaba944
Update substrate/frame/staking/src/pallet/mod.rs
gpestana Mar 26, 2024
5da7bc8
Addresses PR comments
gpestana Mar 26, 2024
61eb993
Merge branch 'master' into gpestana/ledger-badstate-clean
gpestana Mar 26, 2024
5f04bb9
moves pallet-balances as dev-dependency only again
gpestana Mar 26, 2024
46360fe
fixes benches; adds prdoc
gpestana Mar 27, 2024
2ed2ed6
Adds prdoc
gpestana Mar 27, 2024
9426202
".git/.scripts/commands/bench/bench.sh" --subcommand=pallet --runtime…
Mar 27, 2024
10fc869
Adds new total checked balance
gpestana Mar 27, 2024
f27ef5b
Merge branch 'master' of https://github.com/paritytech/polkadot-sdk i…
Mar 27, 2024
a3100f8
".git/.scripts/commands/bench/bench.sh" --subcommand=pallet --runtime…
Mar 27, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
286 changes: 156 additions & 130 deletions polkadot/runtime/westend/src/weights/pallet_staking.rs

Large diffs are not rendered by default.

20 changes: 20 additions & 0 deletions prdoc/pr_3706.prdoc
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
# Schema: Polkadot SDK PRDoc Schema (prdoc) v1.0.0
# See doc at https://raw.githubusercontent.com/paritytech/polkadot-sdk/master/prdoc/schema_user.json

title: Extrinsic to restore corrupted staking ledgers

doc:
- audience: Runtime User
description: |
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.

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)

crates:
- name: pallet-staking
- name: pallet-balances
13 changes: 11 additions & 2 deletions substrate/frame/balances/src/impl_currency.rs
Original file line number Diff line number Diff line change
Expand Up @@ -28,8 +28,8 @@ use frame_support::{
tokens::{fungible, BalanceStatus as Status, Fortitude::Polite, Precision::BestEffort},
Currency, DefensiveSaturating, ExistenceRequirement,
ExistenceRequirement::AllowDeath,
Get, Imbalance, LockIdentifier, LockableCurrency, NamedReservableCurrency,
ReservableCurrency, SignedImbalance, TryDrop, WithdrawReasons,
Get, Imbalance, InspectLockableCurrency, LockIdentifier, LockableCurrency,
NamedReservableCurrency, ReservableCurrency, SignedImbalance, TryDrop, WithdrawReasons,
},
};
use frame_system::pallet_prelude::BlockNumberFor;
Expand Down Expand Up @@ -918,3 +918,12 @@ where
Self::update_locks(who, &locks[..]);
}
}

impl<T: Config<I>, I: 'static> InspectLockableCurrency<T::AccountId> for Pallet<T, I> {
fn balance_locked(id: LockIdentifier, who: &T::AccountId) -> Self::Balance {
Self::locks(who)
.into_iter()
.filter(|l| l.id == id)
.fold(Zero::zero(), |acc, l| acc + l.amount)
}
}
22 changes: 20 additions & 2 deletions substrate/frame/balances/src/tests/currency_tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -24,8 +24,8 @@ use frame_support::{
BalanceStatus::{Free, Reserved},
Currency,
ExistenceRequirement::{self, AllowDeath, KeepAlive},
Hooks, LockIdentifier, LockableCurrency, NamedReservableCurrency, ReservableCurrency,
WithdrawReasons,
Hooks, InspectLockableCurrency, LockIdentifier, LockableCurrency, NamedReservableCurrency,
ReservableCurrency, WithdrawReasons,
},
StorageNoopGuard,
};
Expand Down Expand Up @@ -88,6 +88,24 @@ fn basic_locking_should_work() {
});
}

#[test]
fn inspect_lock_should_work() {
ExtBuilder::default()
.existential_deposit(1)
.monied(true)
.build_and_execute_with(|| {
Balances::set_lock(ID_1, &1, 10, WithdrawReasons::all());
Balances::set_lock(ID_2, &1, 10, WithdrawReasons::all());
Balances::set_lock(ID_1, &2, 20, WithdrawReasons::all());

assert_eq!(<Balances as InspectLockableCurrency<_>>::balance_locked(ID_1, &1), 10);
assert_eq!(<Balances as InspectLockableCurrency<_>>::balance_locked(ID_2, &1), 10);
assert_eq!(<Balances as InspectLockableCurrency<_>>::balance_locked(ID_1, &2), 20);
assert_eq!(<Balances as InspectLockableCurrency<_>>::balance_locked(ID_2, &2), 0);
assert_eq!(<Balances as InspectLockableCurrency<_>>::balance_locked(ID_1, &3), 0);
})
}

#[test]
fn account_should_be_reaped() {
ExtBuilder::default()
Expand Down
2 changes: 1 addition & 1 deletion substrate/frame/staking/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -40,10 +40,10 @@ frame-benchmarking = { path = "../benchmarking", default-features = false, optio
rand_chacha = { version = "0.2", default-features = false, optional = true }

[dev-dependencies]
pallet-balances = { path = "../balances" }
sp-tracing = { path = "../../primitives/tracing" }
sp-core = { path = "../../primitives/core" }
sp-npos-elections = { path = "../../primitives/npos-elections" }
pallet-balances = { path = "../balances" }
pallet-timestamp = { path = "../timestamp" }
pallet-staking-reward-curve = { path = "reward-curve" }
pallet-bags-list = { path = "../bags-list" }
Expand Down
9 changes: 9 additions & 0 deletions substrate/frame/staking/src/benchmarking.rs
Original file line number Diff line number Diff line change
Expand Up @@ -953,6 +953,15 @@ benchmarks! {
assert_eq!(MinCommission::<T>::get(), Perbill::from_percent(100));
}

restore_ledger {
let (stash, controller) = create_stash_controller::<T>(0, 100, RewardDestination::Staked)?;
// corrupt ledger.
Ledger::<T>::remove(controller);
}: _(RawOrigin::Root, stash.clone(), None, None, None)
verify {
assert_eq!(Staking::<T>::inspect_bond_state(&stash), Ok(LedgerIntegrityState::Ok));
}

impl_benchmark_test_suite!(
Staking,
crate::mock::ExtBuilder::default().has_stakers(true),
Expand Down
14 changes: 14 additions & 0 deletions substrate/frame/staking/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -491,6 +491,20 @@ pub struct StakingLedger<T: Config> {
controller: Option<T::AccountId>,
}

/// State of a ledger with regards with its data and metadata integrity.
#[derive(PartialEq, Debug)]
enum LedgerIntegrityState {
/// Ledger, bond and corresponding staking lock is OK.
Ok,
/// Ledger and/or bond is corrupted. This means that the bond has a ledger with a different
/// stash than the bonded stash.
Corrupted,
gpestana marked this conversation as resolved.
Show resolved Hide resolved
/// Ledger was corrupted and it has been killed.
CorruptedKilled,
/// Ledger and bond are OK, however the ledger's stash lock is out of sync.
LockCorrupted,
}

impl<T: Config> StakingLedger<T> {
/// Remove entries from `unlocking` that are sufficiently old and reduce the
/// total by the sum of their balances.
Expand Down
115 changes: 73 additions & 42 deletions substrate/frame/staking/src/mock.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
};
Expand Down Expand Up @@ -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();

// 4 becomes controller of 3.
Bonded::<Test>::mutate(3, |controller| *controller = Some(4));
Ledger::<Test>::insert(4, ledger_3);

// 3 becomes controller of 2.
Bonded::<Test>::mutate(2, |controller| *controller = Some(3));
Ledger::<Test>::insert(3, ledger_2);

// 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.
// * 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();

// 777 becomes controller of 555.
Bonded::<Test>::mutate(555, |controller| *controller = Some(777));
Ledger::<Test>::insert(777, ledger_555);

// 555 becomes controller of 444.
Bonded::<Test>::mutate(444, |controller| *controller = Some(555));
Ledger::<Test>::insert(555, ledger_444);

// 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);

// * 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 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);
// * +3 ledgers
assert_eq!(Ledger::<Test>::iter().count(), 3 + init_ledgers);

// * 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 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]
Expand Down
60 changes: 46 additions & 14 deletions substrate/frame/staking/src/pallet/impls.rs
Original file line number Diff line number Diff line change
Expand Up @@ -27,8 +27,8 @@ use frame_support::{
dispatch::WithPostDispatchInfo,
pallet_prelude::*,
traits::{
Currency, Defensive, DefensiveSaturating, EstimateNextNewSession, Get, Imbalance, Len,
OnUnbalanced, TryCollect, UnixTime,
Currency, Defensive, DefensiveSaturating, EstimateNextNewSession, Get, Imbalance,
InspectLockableCurrency, Len, OnUnbalanced, TryCollect, UnixTime,
},
weights::Weight,
};
Expand All @@ -50,8 +50,8 @@ use sp_std::prelude::*;
use crate::{
election_size_tracker::StaticTracker, log, slashing, weights::WeightInfo, ActiveEraInfo,
BalanceOf, EraInfo, EraPayout, Exposure, ExposureOf, Forcing, IndividualExposure,
MaxNominationsOf, MaxWinnersOf, Nominations, NominationsQuota, PositiveImbalanceOf,
RewardDestination, SessionInterface, StakingLedger, ValidatorPrefs,
LedgerIntegrityState, MaxNominationsOf, MaxWinnersOf, Nominations, NominationsQuota,
PositiveImbalanceOf, RewardDestination, SessionInterface, StakingLedger, ValidatorPrefs,
};

use super::pallet::*;
Expand Down Expand Up @@ -84,6 +84,38 @@ impl<T: Config> Pallet<T> {
StakingLedger::<T>::paired_account(Stash(stash.clone()))
}

/// Inspects and returns the corruption state of a ledger and bond, if any.
///
/// Note: all operations in this method access directly the `Bonded` and `Ledger` storage maps
/// instead of using the [`StakingLedger`] API since the bond and/or ledger may be corrupted.
pub(crate) fn inspect_bond_state(
stash: &T::AccountId,
) -> Result<LedgerIntegrityState, Error<T>> {
let lock = T::Currency::balance_locked(crate::STAKING_ID, &stash);

let controller = <Bonded<T>>::get(stash).ok_or_else(|| {
if lock == Zero::zero() {
Error::<T>::NotStash
} else {
Error::<T>::BadState
Copy link
Contributor

@Ank4n Ank4n Mar 26, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could return something like LedgerIntgrityState::DanglingLock.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

and this branch should be defensive as in to the best of our knowledge, no ledger should be in this error state right?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

and this branch should be defensive as in to the best of our knowledge, no ledger should be in this error state right?

We actually may get into this state when the stash associated with a corrupted ledger calls kill on the ledger, this specific case is explained here: https://hackmd.io/DLb5jEYWSmmvqXC9ae4yRg#/5

}
})?;

match Ledger::<T>::get(controller) {
Some(ledger) =>
if ledger.stash != *stash {
Ok(LedgerIntegrityState::Corrupted)
Ank4n marked this conversation as resolved.
Show resolved Hide resolved
} else {
if lock != ledger.total {
Ok(LedgerIntegrityState::LockCorrupted)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A ledger can also have both lock out of sync and stash mismatch?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

if the stash is a mismatch, it means it is someone else's ledger, so I think it is kinda meaningless to even check LockCorrupted. Corrupted always also implies LockCorrupted as well.

And I think we only have Corrupted now, or do we have any LockCorrupted atm as well?

Copy link
Contributor Author

@gpestana gpestana Mar 26, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A ledger can also have both lock out of sync and stash mismatch?

Yes, it can. If there's a bond_extra on a corrupted ledger, we may get that issue (see e.g. this case).

The reason to differentiate between Corrupted and LockCorrupted is that the LockCorrupted restore requires the maybe_total to be Some, i.e. in this case, we want to ensure that the lock and the ledger's total is re-written. On the other hand, Corrupted we will use the lock's value to reset the ledger's total (or, optionally, re-write it with maybe_total).

We could also collapse both and return an error if the total to reset the ledger is 0, ie.

let new_total = maybe_total.unwrap_or(lock);
ensure!(new_total != 0, Error::<T>::CannotResetLedger);

} else {
Ok(LedgerIntegrityState::Ok)
}
},
None => Ok(LedgerIntegrityState::CorruptedKilled),
}
}

/// The total balance that can be slashed from a stash account as of right now.
pub fn slashable_balance_of(stash: &T::AccountId) -> BalanceOf<T> {
// Weight note: consider making the stake accessible through stash.
Expand Down Expand Up @@ -1837,12 +1869,12 @@ impl<T: Config> Pallet<T> {
"VoterList contains non-staker"
);

Self::check_ledgers()?;
Self::check_bonded_consistency()?;
Self::check_payees()?;
Self::check_nominators()?;
Self::check_exposures()?;
Self::check_paged_exposures()?;
Self::check_ledgers()?;
Self::check_count()
}

Expand All @@ -1851,6 +1883,7 @@ impl<T: Config> Pallet<T> {
/// * A bonded (stash, controller) pair should have only one associated ledger. I.e. if the
/// ledger is bonded by stash, the controller account must not bond a different ledger.
/// * A bonded (stash, controller) pair must have an associated ledger.
///
/// NOTE: these checks result in warnings only. Once
/// <https://github.com/paritytech/polkadot-sdk/issues/3245> is resolved, turn warns into check
/// failures.
Expand Down Expand Up @@ -1945,19 +1978,18 @@ impl<T: Config> Pallet<T> {
}

/// Invariants:
/// * `ledger.controller` is not stored in the storage (but populated at retrieval).
/// * Stake consistency: ledger.total == ledger.active + sum(ledger.unlocking).
/// * The controller keying the ledger and the ledger stash matches the state of the `Bonded`
/// storage.
/// * The ledger's controller and stash matches the associated `Bonded` tuple.
/// * Staking locked funds for every bonded stash should be the same as its ledger's total.
/// * Staking ledger and bond are not corrupted.
fn check_ledgers() -> Result<(), TryRuntimeError> {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How is the running time for this try state check? Don't mind putting this in release for now if it is not super long but want to point out that you are reading some storages multiple times and it can be optimized to be much faster.
Ideally we read any storage only once and keep it in memory for further checks.

Bonded::<T>::iter()
.map(|(stash, ctrl)| {
// `ledger.controller` is never stored in raw storage.
let raw = Ledger::<T>::get(stash).unwrap_or_else(|| {
Ledger::<T>::get(ctrl.clone())
.expect("try_check: bonded stash/ctrl does not have an associated ledger")
});
ensure!(raw.controller.is_none(), "raw storage controller should be None");
// ensure locks consistency.
ensure!(
Self::inspect_bond_state(&stash) == Ok(LedgerIntegrityState::Ok),
"bond, ledger and/or staking lock inconsistent for a bonded stash."
);

// ensure ledger consistency.
Self::ensure_ledger_consistent(ctrl)
Expand Down
Loading
Loading