Skip to content

Commit 5e24b5f

Browse files
paritytech-release-backport-bot[bot]kianenigmasigurpolgithub-actions[bot]
authored
[stable2509] Backport #9926 (#9970)
Backport #9926 into `stable2509` from kianenigma. See the [documentation](https://github.com/paritytech/polkadot-sdk/blob/master/docs/BACKPORT.md) on how to use this bot. <!-- # To be used by other automation, do not modify: original-pr-number: #${pull_number} --> Co-authored-by: Kian Paimani <[email protected]> Co-authored-by: Paolo La Camera <[email protected]> Co-authored-by: cmd[bot] <41898282+github-actions[bot]@users.noreply.github.com>
1 parent 5ab4013 commit 5e24b5f

File tree

3 files changed

+89
-3
lines changed

3 files changed

+89
-3
lines changed

prdoc/pr_9926.prdoc

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
title: 'Staking-Async: Chill stakers should not have a score'
2+
doc:
3+
- audience: Runtime Dev
4+
description: |-
5+
Async: Chill stakers should not have a score.
6+
7+
While no severe consequence, this bug could cause non-validator and non-nominator stakers to retain a spot in the bags-list pallet, preventing other legit nominators/validators from taking their place.
8+
9+
Note that previously, this was not a possibility, because `staking` would always issue a `T::VoterList::on_remove` when someone `chill`s, ensuring they are removed from the list. Moreover, an older version of `pallet_bags_list::Pallet::rebag` didn't allow new nodes to be added, only the score of existing nodes to be adjusted.
10+
11+
But, in recent versions of `bags-list`, we added a `Lock` ability that would block any changes to the bags list (during the election snapshot phase). This also had us update the `rebag` transaction to add or remove nodes from the list, which opened the door to this issue.
12+
crates:
13+
- name: pallet-staking-async
14+
bump: patch

substrate/frame/staking-async/src/pallet/impls.rs

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1343,12 +1343,20 @@ impl<T: Config> ScoreProvider<T::AccountId> for Pallet<T> {
13431343

13441344
fn score(who: &T::AccountId) -> Option<Self::Score> {
13451345
Self::ledger(Stash(who.clone()))
1346-
.map(|l| l.active)
1346+
.ok()
1347+
.and_then(|l| {
1348+
if Nominators::<T>::contains_key(&l.stash) ||
1349+
Validators::<T>::contains_key(&l.stash)
1350+
{
1351+
Some(l.active)
1352+
} else {
1353+
None
1354+
}
1355+
})
13471356
.map(|a| {
13481357
let issuance = asset::total_issuance::<T>();
13491358
T::CurrencyToVote::to_vote(a, issuance)
13501359
})
1351-
.ok()
13521360
}
13531361

13541362
#[cfg(feature = "runtime-benchmarks")]
@@ -1364,6 +1372,9 @@ impl<T: Config> ScoreProvider<T::AccountId> for Pallet<T> {
13641372

13651373
<Ledger<T>>::insert(who, ledger);
13661374
<Bonded<T>>::insert(who, who);
1375+
// we also need to appoint this staker to be validator or nominator, such that their score
1376+
// is actually there. Note that `fn score` above checks the role.
1377+
<Validators<T>>::insert(who, ValidatorPrefs::default());
13671378

13681379
// also, we play a trick to make sure that a issuance based-`CurrencyToVote` behaves well:
13691380
// This will make sure that total issuance is zero, thus the currency to vote will be a 1-1

substrate/frame/staking-async/src/tests/election_data_provider.rs

Lines changed: 62 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -622,7 +622,7 @@ mod paged_snapshot {
622622
}
623623

624624
#[test]
625-
fn target_snaposhot_multi_page_redundant() {
625+
fn target_snapshot_multi_page_redundant() {
626626
ExtBuilder::default().build_and_execute(|| {
627627
let all_targets = vec![31, 21, 11];
628628
assert_eq_uvec!(<Test as Config>::TargetList::iter().collect::<Vec<_>>(), all_targets,);
@@ -858,6 +858,67 @@ mod paged_snapshot {
858858
}
859859
}
860860

861+
mod score_provider {
862+
use frame_election_provider_support::ScoreProvider;
863+
864+
use super::*;
865+
866+
#[test]
867+
fn no_score_for_chilled_stakers() {
868+
ExtBuilder::default().build_and_execute(|| {
869+
// given 41 being a chilled staker
870+
assert!(
871+
Ledger::<Test>::get(41).is_some() &&
872+
!Validators::<Test>::contains_key(41) &&
873+
!Nominators::<Test>::contains_key(41)
874+
);
875+
876+
// then they will not have a score when bags-list wants to update it.
877+
assert!(<Staking as ScoreProvider<_>>::score(&41).is_none());
878+
});
879+
}
880+
881+
#[test]
882+
fn no_score_for_non_stakers() {
883+
ExtBuilder::default().build_and_execute(|| {
884+
// given 777 being neither a nominator nor a validator in this pallet.
885+
assert!(
886+
!Ledger::<Test>::get(777).is_some() &&
887+
!Validators::<Test>::contains_key(777) &&
888+
!Nominators::<Test>::contains_key(777)
889+
);
890+
891+
// then it will not have a score when bags-list wants to update it.
892+
assert!(<Staking as ScoreProvider<_>>::score(&777).is_none());
893+
});
894+
}
895+
896+
#[test]
897+
fn score_for_validators_nominators() {
898+
ExtBuilder::default().nominate(true).build_and_execute(|| {
899+
// Given 101 being a nominator
900+
assert!(
901+
Ledger::<Test>::get(101).unwrap().active == 500 &&
902+
!Validators::<Test>::contains_key(101) &&
903+
Nominators::<Test>::contains_key(101)
904+
);
905+
906+
// then it will have a score.
907+
assert_eq!(<Staking as ScoreProvider<_>>::score(&101), Some(500));
908+
909+
// given 11 being a validator
910+
assert!(
911+
Ledger::<Test>::get(11).unwrap().active == 1000 &&
912+
Validators::<Test>::contains_key(11) &&
913+
!Nominators::<Test>::contains_key(11)
914+
);
915+
916+
// then it will have a score.
917+
assert_eq!(<Staking as ScoreProvider<_>>::score(&11), Some(1000));
918+
});
919+
}
920+
}
921+
861922
#[test]
862923
fn from_most_staked_to_least_staked() {
863924
ExtBuilder::default()

0 commit comments

Comments
 (0)