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

Refactors staking and stake-tracker so that target and voter lists include idle stakers #2497

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
9 changes: 7 additions & 2 deletions substrate/frame/staking/src/mock.rs
Original file line number Diff line number Diff line change
Expand Up @@ -602,7 +602,7 @@ impl ExtBuilder {
let mut ext = self.build();
ext.execute_with(test);
ext.execute_with(|| {
Staking::do_try_state(System::block_number())
let _ = Staking::do_try_state(System::block_number())
.map_err(|err| println!(" 🕵️‍♂️ try_state failure: {:?}", err))
.unwrap();
});
Expand Down Expand Up @@ -881,7 +881,12 @@ pub(crate) fn balances(who: &AccountId) -> (Balance, Balance) {
pub(crate) fn stake_tracker_sanity_tests() -> Result<(), &'static str> {
use sp_staking::StakingInterface;

assert_eq!(Nominators::<Test>::count() + Validators::<Test>::count(), VoterBagsList::count());
assert_eq!(
Nominators::<Test>::count() + Validators::<Test>::count(),
VoterBagsList::iter()
.filter(|v| Staking::status(&v) != Ok(StakerStatus::Idle))
gpestana marked this conversation as resolved.
Show resolved Hide resolved
.count() as u32,
);

// recalculate the target's stake based on voter's nominations and compare with the score in the
// target list.
Expand Down
178 changes: 133 additions & 45 deletions substrate/frame/staking/src/pallet/impls.rs
Original file line number Diff line number Diff line change
Expand Up @@ -318,9 +318,10 @@ impl<T: Config> Pallet<T> {

/// Chill a stash account.
pub(crate) fn chill_stash(stash: &T::AccountId) {
let chilled_as_validator = Self::do_remove_validator(stash);
let chilled_as_nominator = Self::do_remove_nominator(stash);
let chilled_as_validator = Self::do_chill_validator(stash);
let chilled_as_nominator = Self::do_chill_nominator(stash);
if chilled_as_validator || chilled_as_nominator {
debug_assert_eq!(Self::status(stash), Ok(StakerStatus::Idle));
Self::deposit_event(Event::<T>::Chilled { stash: stash.clone() });
}
}
Expand Down Expand Up @@ -832,7 +833,9 @@ impl<T: Config> Pallet<T> {
let mut nominators_taken = 0u32;
let mut min_active_stake = u64::MAX;

let mut sorted_voters = T::VoterList::iter();
// voter list also contains chilled/idle voters, filter those.
let mut sorted_voters =
T::VoterList::iter().filter(|v| Self::status(&v) != Ok(StakerStatus::Idle));
Ank4n marked this conversation as resolved.
Show resolved Hide resolved
while all_voters.len() < final_predicted_len as usize &&
voters_seen < (NPOS_MAX_ITERATIONS_COEFFICIENT * final_predicted_len as u32)
{
Expand Down Expand Up @@ -940,7 +943,9 @@ impl<T: Config> Pallet<T> {
let mut all_targets = Vec::<T::AccountId>::with_capacity(final_predicted_len as usize);
let mut targets_seen = 0;

let mut targets_iter = T::TargetList::iter();
// target list also contains chilled/idle validators, filter those.
let mut targets_iter =
T::TargetList::iter().filter(|t| Self::status(&t) != Ok(StakerStatus::Idle));
Ank4n marked this conversation as resolved.
Show resolved Hide resolved
while all_targets.len() < final_predicted_len as usize &&
targets_seen < (NPOS_MAX_ITERATIONS_COEFFICIENT * final_predicted_len as u32)
{
Expand Down Expand Up @@ -980,28 +985,52 @@ impl<T: Config> Pallet<T> {
/// to `Nominators` or `VoterList` outside of this function is almost certainly
/// wrong.
pub fn do_add_nominator(who: &T::AccountId, nominations: Nominations<T>) {
if !Nominators::<T>::contains_key(who) {
// new nominator.
Nominators::<T>::insert(who, nominations);
<T::EventListeners as OnStakingUpdate<T::AccountId, BalanceOf<T>>>::on_nominator_add(
who,
);
} else {
// update nominations.
let prev_nominations = Self::nominations(who).unwrap_or_default();
Nominators::<T>::insert(who, nominations);
<T::EventListeners as OnStakingUpdate<T::AccountId, BalanceOf<T>>>::on_nominator_update(
who,
prev_nominations,
);
}
match (Nominators::<T>::contains_key(who), T::VoterList::contains(who)) {
(false, false) => {
// new nomination
Nominators::<T>::insert(who, nominations);
<T::EventListeners as OnStakingUpdate<T::AccountId, BalanceOf<T>>>::on_nominator_add(
who,
);
},
(true, true) | (false, true) => {
// update nominations or un-chill nominator.
let prev_nominations = Self::nominations(who).unwrap_or_default();
Nominators::<T>::insert(who, nominations);
<T::EventListeners as OnStakingUpdate<T::AccountId, BalanceOf<T>>>::on_nominator_update(
who,
prev_nominations,
);
},
(true, false) => {
defensive!("unexpected state.");
},
};

debug_assert_eq!(
Nominators::<T>::count() + Validators::<T>::count(),
T::VoterList::count()
T::VoterList::iter()
.filter(|v| Self::status(&v) != Ok(StakerStatus::Idle))
.count() as u32,
);
}

pub(crate) fn do_chill_nominator(who: &T::AccountId) -> bool {
let outcome = if Nominators::<T>::contains_key(who) {
<T::EventListeners as OnStakingUpdate<T::AccountId, BalanceOf<T>>>::on_nominator_idle(
who,
Self::nominations(who).unwrap_or_default(),
);

Nominators::<T>::remove(who);
true
} else {
false
};

outcome
}

/// This function will remove a nominator from the `Nominators` storage map,
/// and `VoterList`.
///
Expand All @@ -1011,20 +1040,44 @@ impl<T: Config> Pallet<T> {
/// `Nominators` or `VoterList` outside of this function is almost certainly
/// wrong.
pub fn do_remove_nominator(who: &T::AccountId) -> bool {
let outcome = if Nominators::<T>::contains_key(who) {
<T::EventListeners as OnStakingUpdate<T::AccountId, BalanceOf<T>>>::on_nominator_remove(
who,
Self::nominations(who).unwrap_or_default(),
);
Nominators::<T>::remove(who);
true
} else {
false
let nominations = Self::nominations(who).unwrap_or_default();

let outcome = match (Nominators::<T>::contains_key(who), T::VoterList::contains(who)) {
// ensure that the voter is idle before removing it.
(true, true) => {
// first chill nominator.
Self::do_chill_nominator(who);

<T::EventListeners as OnStakingUpdate<T::AccountId, BalanceOf<T>>>::on_nominator_remove(
who,
nominations,
);
Nominators::<T>::remove(who);
true
},
// nominator is idle already, remove it.
(false, true) => {
<T::EventListeners as OnStakingUpdate<T::AccountId, BalanceOf<T>>>::on_nominator_remove(
who,
nominations,
);
true
},
(true, false) => {
defensive!(
"inconsistent state: staker is in nominators list but not in voter list"
);
false
},
// nominator has been already removed.
(false, false) => false,
};

debug_assert_eq!(
Nominators::<T>::count() + Validators::<T>::count(),
T::VoterList::count()
T::VoterList::iter()
.filter(|v| Self::status(&v) != Ok(StakerStatus::Idle))
.count() as u32,
);

outcome
Expand All @@ -1049,10 +1102,21 @@ impl<T: Config> Pallet<T> {

debug_assert_eq!(
Nominators::<T>::count() + Validators::<T>::count(),
T::VoterList::count()
T::VoterList::iter()
.filter(|v| Self::status(&v) != Ok(StakerStatus::Idle))
.count() as u32,
);
}

pub(crate) fn do_chill_validator(who: &T::AccountId) -> bool {
if Validators::<T>::contains_key(who) {
Validators::<T>::remove(who);
true
} else {
false
}
}

/// This function will remove a validator from the `Validators` storage map.
///
/// Returns true if `who` was removed from `Validators`, otherwise false.
Expand All @@ -1061,19 +1125,39 @@ impl<T: Config> Pallet<T> {
/// `Validators` or `VoterList` outside of this function is almost certainly
/// wrong.
pub fn do_remove_validator(who: &T::AccountId) -> bool {
let outcome = if Validators::<T>::contains_key(who) {
Validators::<T>::remove(who);
<T::EventListeners as OnStakingUpdate<T::AccountId, BalanceOf<T>>>::on_validator_remove(
who,
);
true
} else {
false
let outcome = match (Validators::<T>::contains_key(who), T::TargetList::contains(who)) {
// ensure the validator is idle before removing it.
(true, true) => {
Self::do_chill_validator(who);

<T::EventListeners as OnStakingUpdate<T::AccountId, BalanceOf<T>>>::on_validator_remove(
who,
);
Validators::<T>::remove(who);
true
},
// validator is idle, remove it.
(false, true) => {
<T::EventListeners as OnStakingUpdate<T::AccountId, BalanceOf<T>>>::on_validator_remove(
who,
);
true
},
(true, false) => {
defensive!(
"inconsistent state: staker is in validators list but not in targets list"
);
false
},
// validator has been already removed.
(false, false) => false,
};

debug_assert_eq!(
Nominators::<T>::count() + Validators::<T>::count(),
T::VoterList::count()
T::VoterList::iter()
.filter(|v| Self::status(&v) != Ok(StakerStatus::Idle))
.count() as u32,
);

outcome
Expand Down Expand Up @@ -1824,8 +1908,9 @@ impl<T: Config> StakingInterface for Pallet<T> {
impl<T: Config> Pallet<T> {
pub(crate) fn do_try_state(_: BlockNumberFor<T>) -> Result<(), TryRuntimeError> {
ensure!(
T::VoterList::iter()
.all(|x| <Nominators<T>>::contains_key(&x) || <Validators<T>>::contains_key(&x)),
T::VoterList::iter().all(|x| <Nominators<T>>::contains_key(&x) ||
<Validators<T>>::contains_key(&x) ||
Self::status(&x) == Ok(StakerStatus::Idle)),
"VoterList contains non-staker"
);

Expand All @@ -1837,12 +1922,15 @@ impl<T: Config> Pallet<T> {

fn check_count() -> Result<(), TryRuntimeError> {
ensure!(
<T as Config>::VoterList::count() ==
Nominators::<T>::count() + Validators::<T>::count(),
<T as Config>::VoterList::iter()
.filter(|v| Self::status(&v) != Ok(StakerStatus::Idle))
.count() as u32 == Nominators::<T>::count() + Validators::<T>::count(),
"wrong external count (VoterList.count != Nominators.count + Validators.count)"
);
ensure!(
<T as Config>::TargetList::count() == Validators::<T>::count(),
<T as Config>::TargetList::iter()
.filter(|t| Self::status(&t) != Ok(StakerStatus::Idle))
.count() as u32 == Validators::<T>::count(),
"wrong external count (TargetList.count != Validators.count)"
);
ensure!(
Expand Down
5 changes: 3 additions & 2 deletions substrate/frame/staking/src/pallet/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1151,7 +1151,7 @@ pub mod pallet {
}
}

Self::do_remove_nominator(stash);
Self::do_chill_nominator(stash);
Self::do_add_validator(stash, prefs.clone());
Self::deposit_event(Event::<T>::ValidatorPrefsSet { stash: ledger.stash, prefs });

Expand Down Expand Up @@ -1225,7 +1225,7 @@ pub mod pallet {
suppressed: false,
};

Self::do_remove_validator(stash);
Self::do_chill_validator(stash);
Self::do_add_nominator(stash, nominations);

Ok(())
Expand Down Expand Up @@ -1646,6 +1646,7 @@ pub mod pallet {
if let Some(ref mut nom) = maybe_nom {
if let Some(pos) = nom.targets.iter().position(|v| v == stash) {
nom.targets.swap_remove(pos);

// update nominations and trickle down to target list score.
Self::do_add_nominator(&nom_stash, nom.clone());

Expand Down
Loading
Loading