From 500bc9d3bb71af18f39d1b0532984a61165db174 Mon Sep 17 00:00:00 2001 From: kianenigma Date: Tue, 8 Feb 2022 15:25:28 +0000 Subject: [PATCH 01/42] Implement the new validator-in-bags-list scenario + migration --- frame/staking/src/benchmarking.rs | 20 ++-- frame/staking/src/migrations.rs | 35 +++++++ frame/staking/src/mock.rs | 2 +- frame/staking/src/pallet/impls.rs | 161 +++++++++++++++++------------- frame/staking/src/pallet/mod.rs | 2 +- frame/staking/src/tests.rs | 114 +++++++++++---------- 6 files changed, 201 insertions(+), 133 deletions(-) diff --git a/frame/staking/src/benchmarking.rs b/frame/staking/src/benchmarking.rs index 564172d912413..29fa5e34c7f1e 100644 --- a/frame/staking/src/benchmarking.rs +++ b/frame/staking/src/benchmarking.rs @@ -332,24 +332,20 @@ benchmarks! { } validate { - // clean up any existing state. - clear_validators_and_nominators::(); - - let origin_weight = MinNominatorBond::::get().max(T::Currency::minimum_balance()); - - // setup a worst case scenario where the user calling validate was formerly a nominator so - // they must be removed from the list. - let scenario = ListScenario::::new(origin_weight, true)?; - let controller = scenario.origin_controller1.clone(); - let stash = scenario.origin_stash1.clone(); - assert!(T::SortedListProvider::contains(&stash)); + let (stash, controller) = create_stash_controller::( + T::MaxNominations::get() - 1, + 100, + Default::default(), + )?; + // because it is chilled. + assert!(!T::SortedListProvider::contains(&stash)); let prefs = ValidatorPrefs::default(); whitelist_account!(controller); }: _(RawOrigin::Signed(controller), prefs) verify { assert!(Validators::::contains_key(&stash)); - assert!(!T::SortedListProvider::contains(&stash)); + assert!(T::SortedListProvider::contains(&stash)); } kick { diff --git a/frame/staking/src/migrations.rs b/frame/staking/src/migrations.rs index 3991c4a66076f..cd7f7c633184f 100644 --- a/frame/staking/src/migrations.rs +++ b/frame/staking/src/migrations.rs @@ -17,6 +17,41 @@ //! Storage migrations for the Staking pallet. use super::*; +use frame_election_provider_support::{SortedListProvider, VoteWeightProvider}; +use frame_support::traits::OnRuntimeUpgrade; + +/// Migration implementation that injects all validators into sorted list. +/// +/// This is only useful for chains that started their `SortedListProvider` just based on nominators. +pub struct InjectValidatorsIntoSortedListProvider(sp_std::marker::PhantomData); +impl OnRuntimeUpgrade for InjectValidatorsIntoSortedListProvider { + fn on_runtime_upgrade() -> Weight { + for (v, _) in Validators::::iter() { + let weight = Pallet::::vote_weight(&v); + let _ = T::SortedListProvider::on_insert(v.clone(), weight).map_err(|err| { + log!(warn, "failed to insert {:?} into SortedListProvider: {:?}", v, err) + }); + } + + T::BlockWeights::get().max_block + } + + #[cfg(feature = "try-runtime")] + fn pre_upgrade() -> Result<(), &'static str> { + use frame_support::traits::OnRuntimeUpgradeHelpersExt; + let prev_count = T::SortedListProvider::count(); + Self::set_temp_storage(prev_count, "prev"); + } + + #[cfg(feature = "try-runtime")] + fn post_upgrade() -> Result<(), &'static str> { + use frame_support::traits::OnRuntimeUpgradeHelpersExt; + let post_count = T::SortedListProvider::count(); + let prev_count = Self::get_temp_storage::("prev"); + let validators = Validators::::count(); + assert!(post_count == prev_count + validators); + } +} pub mod v8 { use frame_election_provider_support::SortedListProvider; diff --git a/frame/staking/src/mock.rs b/frame/staking/src/mock.rs index 95f305dfdd22a..cb3240769f1f0 100644 --- a/frame/staking/src/mock.rs +++ b/frame/staking/src/mock.rs @@ -541,7 +541,7 @@ fn check_count() { // the voters that the `SortedListProvider` list is storing for us. let external_voters = ::SortedListProvider::count(); - assert_eq!(external_voters, nominator_count); + assert_eq!(external_voters, nominator_count + validator_count); } fn check_ledgers() { diff --git a/frame/staking/src/pallet/impls.rs b/frame/staking/src/pallet/impls.rs index ae20550cd40b6..091c941cb5985 100644 --- a/frame/staking/src/pallet/impls.rs +++ b/frame/staking/src/pallet/impls.rs @@ -649,90 +649,78 @@ impl Pallet { /// Get all of the voters that are eligible for the npos election. /// - /// `maybe_max_len` can imposes a cap on the number of voters returned; First all the validator - /// are included in no particular order, then remainder is taken from the nominators, as - /// returned by [`Config::SortedListProvider`]. - /// - /// This will use nominators, and all the validators will inject a self vote. + /// `maybe_max_len` can imposes a cap on the number of voters returned; /// /// This function is self-weighing as [`DispatchClass::Mandatory`]. /// /// ### Slashing /// - /// All nominations that have been submitted before the last non-zero slash of the validator are - /// auto-chilled, but still count towards the limit imposed by `maybe_max_len`. + /// All voter have been submitted before the last non-zero slash of the corresponding target are + /// *auto-chilled*, but still count towards the limit imposed by `maybe_max_len`. pub fn get_npos_voters(maybe_max_len: Option) -> Vec> { let max_allowed_len = { - let nominator_count = Nominators::::count() as usize; - let validator_count = Validators::::count() as usize; - let all_voter_count = validator_count.saturating_add(nominator_count); + let all_voter_count = T::SortedListProvider::count() as usize; maybe_max_len.unwrap_or(all_voter_count).min(all_voter_count) }; let mut all_voters = Vec::<_>::with_capacity(max_allowed_len); - // first, grab all validators in no particular order, capped by the maximum allowed length. - let mut validators_taken = 0u32; - for (validator, _) in >::iter().take(max_allowed_len) { - // Append self vote. - let self_vote = ( - validator.clone(), - Self::weight_of(&validator), - vec![validator.clone()] - .try_into() - .expect("`MaxVotesPerVoter` must be greater than or equal to 1"), - ); - all_voters.push(self_vote); - validators_taken.saturating_inc(); - } - - // .. and grab whatever we have left from nominators. - let nominators_quota = (max_allowed_len as u32).saturating_sub(validators_taken); + // cache a few things. + let weight_of = Self::weight_of_fn(); let slashing_spans = >::iter().collect::>(); - // track the count of nominators added to `all_voters + let mut voters_taken = 0u32; + let mut voters_seen = 0u32; + let mut validators_taken = 0u32; let mut nominators_taken = 0u32; - // track every nominator iterated over, but not necessarily added to `all_voters` - let mut nominators_seen = 0u32; - // cache the total-issuance once in this function - let weight_of = Self::weight_of_fn(); - - let mut nominators_iter = T::SortedListProvider::iter(); - while nominators_taken < nominators_quota && nominators_seen < nominators_quota * 2 { - let nominator = match nominators_iter.next() { - Some(nominator) => { - nominators_seen.saturating_inc(); - nominator + while voters_taken < (max_allowed_len as u32) && voters_seen < (2 * max_allowed_len as u32) + { + let voter = match T::SortedListProvider::iter().next() { + Some(voter) => { + voters_seen.saturating_inc(); + voter }, None => break, }; if let Some(Nominations { submitted_in, mut targets, suppressed: _ }) = - >::get(&nominator) + >::get(&voter) { - log!( - trace, - "fetched nominator {:?} with weight {:?}", - nominator, - weight_of(&nominator) - ); + // if this voter is a nominator: targets.retain(|stash| { slashing_spans .get(stash) .map_or(true, |spans| submitted_in >= spans.last_nonzero_slash()) }); if !targets.len().is_zero() { - all_voters.push((nominator.clone(), weight_of(&nominator), targets)); + all_voters.push((voter.clone(), weight_of(&voter), targets)); + voters_taken.saturating_inc(); nominators_taken.saturating_inc(); } + } else if Validators::::contains_key(&voter) { + // if this voter is a validator: + let self_vote = ( + voter.clone(), + Self::weight_of(&voter), + vec![voter.clone()] + .try_into() + .expect("`MaxVotesPerVoter` must be greater than or equal to 1"), + ); + all_voters.push(self_vote); + voters_taken.saturating_inc(); + validators_taken.saturating_inc(); } else { - // this can only happen if: 1. there a pretty bad bug in the bags-list (or whatever - // is the sorted list) logic and the state of the two pallets is no longer - // compatible, or because the nominators is not decodable since they have more - // nomination than `T::MaxNominations`. This can rarely happen, and is not really an - // emergency or bug if it does. - log!(warn, "DEFENSIVE: invalid item in `SortedListProvider`: {:?}, this nominator probably has too many nominations now", nominator) + // this can only happen if: 1. there a bug in the bags-list (or whatever is the + // sorted list) logic and the state of the two pallets is no longer compatible, or + // because the nominators is not decodable since they have more nomination than + // `T::MaxNominations`. This can rarely happen, and is not really an emergency or + // bug if it does. + log!( + warn, + "DEFENSIVE: invalid item in `SortedListProvider`: {:?}, this nominator probably has too many nominations now", + voter + ) } } @@ -748,10 +736,11 @@ impl Pallet { log!( info, "generated {} npos voters, {} from validators and {} nominators", - all_voters.len(), + voters_taken, validators_taken, nominators_taken ); + all_voters } @@ -782,14 +771,17 @@ impl Pallet { /// wrong. pub fn do_add_nominator(who: &T::AccountId, nominations: Nominations) { if !Nominators::::contains_key(who) { - // maybe update sorted list. Error checking is defensive-only - this should never fail. + // maybe update sorted list. let _ = T::SortedListProvider::on_insert(who.clone(), Self::weight_of(who)) .defensive_unwrap_or_default(); - - debug_assert_eq!(T::SortedListProvider::sanity_check(), Ok(())); } - Nominators::::insert(who, nominations); + + debug_assert_eq!( + Nominators::::count() + Validators::::count(), + T::SortedListProvider::count() + ); + debug_assert_eq!(T::SortedListProvider::sanity_check(), Ok(())); } /// This function will remove a nominator from the `Nominators` storage map, @@ -801,15 +793,21 @@ impl Pallet { /// `Nominators` or `VoterList` outside of this function is almost certainly /// wrong. pub fn do_remove_nominator(who: &T::AccountId) -> bool { - if Nominators::::contains_key(who) { + let outcome = if Nominators::::contains_key(who) { Nominators::::remove(who); T::SortedListProvider::on_remove(who); - debug_assert_eq!(T::SortedListProvider::sanity_check(), Ok(())); - debug_assert_eq!(Nominators::::count(), T::SortedListProvider::count()); true } else { false - } + }; + + debug_assert_eq!(T::SortedListProvider::sanity_check(), Ok(())); + debug_assert_eq!( + Nominators::::count() + Validators::::count(), + T::SortedListProvider::count() + ); + + outcome } /// This function will add a validator to the `Validators` storage map. @@ -820,7 +818,18 @@ impl Pallet { /// `Validators` or `VoterList` outside of this function is almost certainly /// wrong. pub fn do_add_validator(who: &T::AccountId, prefs: ValidatorPrefs) { + if !Validators::::contains_key(who) { + // maybe update sorted list. + let _ = T::SortedListProvider::on_insert(who.clone(), Self::weight_of(who)) + .defensive_unwrap_or_default(); + } Validators::::insert(who, prefs); + + debug_assert_eq!( + Nominators::::count() + Validators::::count(), + T::SortedListProvider::count() + ); + debug_assert_eq!(T::SortedListProvider::sanity_check(), Ok(())); } /// This function will remove a validator from the `Validators` storage map. @@ -831,12 +840,21 @@ impl Pallet { /// `Validators` or `VoterList` outside of this function is almost certainly /// wrong. pub fn do_remove_validator(who: &T::AccountId) -> bool { - if Validators::::contains_key(who) { + let outcome = if Validators::::contains_key(who) { Validators::::remove(who); + T::SortedListProvider::on_remove(who); true } else { false - } + }; + + debug_assert_eq!(T::SortedListProvider::sanity_check(), Ok(())); + debug_assert_eq!( + Nominators::::count() + Validators::::count(), + T::SortedListProvider::count() + ); + + outcome } /// Register some amount of weight directly with the system pallet. @@ -1276,19 +1294,23 @@ impl VoteWeightProvider for Pallet { /// A simple voter list implementation that does not require any additional pallets. Note, this /// does not provided nominators in sorted ordered. If you desire nominators in a sorted order take /// a look at [`pallet-bags-list]. -pub struct UseNominatorsMap(sp_std::marker::PhantomData); -impl SortedListProvider for UseNominatorsMap { +pub struct UseNominatorsAndValidatorsMap(sp_std::marker::PhantomData); +impl SortedListProvider for UseNominatorsAndValidatorsMap { type Error = (); /// Returns iterator over voter list, which can have `take` called on it. fn iter() -> Box> { - Box::new(Nominators::::iter().map(|(n, _)| n)) + Box::new( + Validators::::iter() + .map(|(v, _)| v) + .chain(Nominators::::iter().map(|(n, _)| n)), + ) } fn count() -> u32 { - Nominators::::count() + Nominators::::count().saturating_add(Validators::::count()) } fn contains(id: &T::AccountId) -> bool { - Nominators::::contains_key(id) + Nominators::::contains_key(id) || Validators::::contains_key(id) } fn on_insert(_: T::AccountId, _weight: VoteWeight) -> Result<(), Self::Error> { // nothing to do on insert. @@ -1315,5 +1337,6 @@ impl SortedListProvider for UseNominatorsMap { // NOTE: Caller must ensure this doesn't lead to too many storage accesses. This is a // condition of SortedListProvider::unsafe_clear. Nominators::::remove_all(); + Validators::::remove_all(); } } diff --git a/frame/staking/src/pallet/mod.rs b/frame/staking/src/pallet/mod.rs index 2a870fda063d3..7fea562ffd14f 100644 --- a/frame/staking/src/pallet/mod.rs +++ b/frame/staking/src/pallet/mod.rs @@ -567,7 +567,7 @@ pub mod pallet { // all voters are reported to the `SortedListProvider`. assert_eq!( T::SortedListProvider::count(), - Nominators::::count(), + Nominators::::count() + Validators::::count(), "not all genesis stakers were inserted into sorted list provider, something is wrong." ); } diff --git a/frame/staking/src/tests.rs b/frame/staking/src/tests.rs index 538b75ead340b..4df1378bc059f 100644 --- a/frame/staking/src/tests.rs +++ b/frame/staking/src/tests.rs @@ -4056,11 +4056,7 @@ mod election_data_provider { .set_status(41, StakerStatus::Validator) .build_and_execute(|| { // sum of all nominators who'd be voters (1), plus the self-votes (4). - assert_eq!( - ::SortedListProvider::count() + - >::iter().count() as u32, - 5 - ); + assert_eq!(::SortedListProvider::count(), 5); // if limits is less.. assert_eq!(Staking::voters(Some(1)).unwrap().len(), 1); @@ -4080,43 +4076,43 @@ mod election_data_provider { }); } + // Tests the criteria that in `ElectionDataProvider::voters` function, we try to get at most + // `maybe_max_len` voters, and if some of them end up being skipped, we iterate at most `2 * + // maybe_max_len`. #[test] fn only_iterates_max_2_times_nominators_quota() { ExtBuilder::default() - .nominate(true) // add nominator 101, who nominates [11, 21] + .nominate(false) // the other nominators only nominate 21 .add_staker(61, 60, 2_000, StakerStatus::::Nominator(vec![21])) .add_staker(71, 70, 2_000, StakerStatus::::Nominator(vec![21])) .add_staker(81, 80, 2_000, StakerStatus::::Nominator(vec![21])) .build_and_execute(|| { - // given our nominators ordered by stake, + // all voters ordered by stake, assert_eq!( ::SortedListProvider::iter().collect::>(), - vec![61, 71, 81, 101] - ); - - // and total voters - assert_eq!( - ::SortedListProvider::count() + - >::iter().count() as u32, - 7 + vec![61, 71, 81, 11, 21, 31] ); - // roll to session 5 run_to_block(25); // slash 21, the only validator nominated by our first 3 nominators add_slash(&21); - // we take 4 voters: 2 validators and 2 nominators (so nominators quota = 2) + // we want 2 voters now, and in maximum we allow 4 iterations. This is what happens: + // 61 is pruned; + // 71 is pruned; + // 81 is pruned; + // 11 is taken; + // we finish since the 2x limit is reached. assert_eq!( - Staking::voters(Some(3)) + Staking::voters(Some(2)) .unwrap() .iter() .map(|(stash, _, _)| stash) .copied() .collect::>(), - vec![31, 11], // 2 validators, but no nominators because we hit the quota + vec![11], ); }); } @@ -4129,46 +4125,35 @@ mod election_data_provider { #[test] fn get_max_len_voters_even_if_some_nominators_are_slashed() { ExtBuilder::default() - .nominate(true) // add nominator 101, who nominates [11, 21] + .nominate(false) .add_staker(61, 60, 20, StakerStatus::::Nominator(vec![21])) - // 61 only nominates validator 21 ^^ .add_staker(71, 70, 10, StakerStatus::::Nominator(vec![11, 21])) + .add_staker(81, 80, 10, StakerStatus::::Nominator(vec![11, 21])) .build_and_execute(|| { - // given our nominators ordered by stake, + // given our voters ordered by stake, assert_eq!( ::SortedListProvider::iter().collect::>(), - vec![101, 61, 71] - ); - - // and total voters - assert_eq!( - ::SortedListProvider::count() + - >::iter().count() as u32, - 6 + vec![11, 21, 31, 61, 71, 81] ); - // we take 5 voters + // we take 4 voters assert_eq!( - Staking::voters(Some(5)) + Staking::voters(Some(4)) .unwrap() .iter() .map(|(stash, _, _)| stash) .copied() .collect::>(), - // then - vec![ - 31, 21, 11, // 3 nominators - 101, 61 // 2 validators, and 71 is excluded - ], + vec![11, 21, 31, 61], ); // roll to session 5 run_to_block(25); - // slash 21, the only validator nominated by 61 + // slash 21, the only validator nominated by 61 and 21. add_slash(&21); - // we take 4 voters + // we take 4 voters; 71 and 81 are replacing the ejected ones. assert_eq!( Staking::voters(Some(4)) .unwrap() @@ -4176,10 +4161,7 @@ mod election_data_provider { .map(|(stash, _, _)| stash) .copied() .collect::>(), - vec![ - 31, 11, // 2 validators (21 was slashed) - 101, 71 // 2 nominators, excluding 61 - ], + vec![11, 31, 71, 81], ); }); } @@ -4695,19 +4677,51 @@ mod sorted_list_provider { fn re_nominate_does_not_change_counters_or_list() { ExtBuilder::default().nominate(true).build_and_execute(|| { // given - let pre_insert_nominator_count = Nominators::::iter().count() as u32; - assert_eq!(::SortedListProvider::count(), pre_insert_nominator_count); - assert!(Nominators::::contains_key(101)); - assert_eq!(::SortedListProvider::iter().collect::>(), vec![101]); + let pre_insert_voter_count = + (Nominators::::count() + Validators::::count()) as u32; + assert_eq!(::SortedListProvider::count(), pre_insert_voter_count); + + assert_eq!( + ::SortedListProvider::iter().collect::>(), + vec![11, 21, 31, 101] + ); // when account 101 renominates assert_ok!(Staking::nominate(Origin::signed(100), vec![41])); // then counts don't change - assert_eq!(::SortedListProvider::count(), pre_insert_nominator_count); - assert_eq!(Nominators::::iter().count() as u32, pre_insert_nominator_count); + assert_eq!(::SortedListProvider::count(), pre_insert_voter_count); + // and the list is the same + assert_eq!( + ::SortedListProvider::iter().collect::>(), + vec![11, 21, 31, 101] + ); + }); + } + + #[test] + fn re_validate_does_not_change_counters_or_list() { + ExtBuilder::default().nominate(false).build_and_execute(|| { + // given + let pre_insert_voter_count = + (Nominators::::count() + Validators::::count()) as u32; + assert_eq!(::SortedListProvider::count(), pre_insert_voter_count); + + assert_eq!( + ::SortedListProvider::iter().collect::>(), + vec![11, 21, 31] + ); + + // when account 101 re-validates + assert_ok!(Staking::validate(Origin::signed(10), Default::default())); + + // then counts don't change + assert_eq!(::SortedListProvider::count(), pre_insert_voter_count); // and the list is the same - assert_eq!(::SortedListProvider::iter().collect::>(), vec![101]); + assert_eq!( + ::SortedListProvider::iter().collect::>(), + vec![11, 21, 31] + ); }); } } From ddcc81b92f221c61ada1a787e8ee9c0c5357255c Mon Sep 17 00:00:00 2001 From: Kian Paimani <5588131+kianenigma@users.noreply.github.com> Date: Tue, 15 Feb 2022 12:32:50 +0000 Subject: [PATCH 02/42] Apply suggestions from code review Co-authored-by: Zeke Mostov --- frame/staking/src/pallet/impls.rs | 4 ++-- frame/staking/src/tests.rs | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/frame/staking/src/pallet/impls.rs b/frame/staking/src/pallet/impls.rs index 091c941cb5985..9986c160c2b93 100644 --- a/frame/staking/src/pallet/impls.rs +++ b/frame/staking/src/pallet/impls.rs @@ -655,7 +655,7 @@ impl Pallet { /// /// ### Slashing /// - /// All voter have been submitted before the last non-zero slash of the corresponding target are + /// All votes that have been submitted before the last non-zero slash of the corresponding target are /// *auto-chilled*, but still count towards the limit imposed by `maybe_max_len`. pub fn get_npos_voters(maybe_max_len: Option) -> Vec> { let max_allowed_len = { @@ -714,7 +714,7 @@ impl Pallet { // this can only happen if: 1. there a bug in the bags-list (or whatever is the // sorted list) logic and the state of the two pallets is no longer compatible, or // because the nominators is not decodable since they have more nomination than - // `T::MaxNominations`. This can rarely happen, and is not really an emergency or + // `T::MaxNominations`. The latter can rarely happen, and is not really an emergency or // bug if it does. log!( warn, diff --git a/frame/staking/src/tests.rs b/frame/staking/src/tests.rs index 4df1378bc059f..6ae46aed4ad49 100644 --- a/frame/staking/src/tests.rs +++ b/frame/staking/src/tests.rs @@ -4080,7 +4080,7 @@ mod election_data_provider { // `maybe_max_len` voters, and if some of them end up being skipped, we iterate at most `2 * // maybe_max_len`. #[test] - fn only_iterates_max_2_times_nominators_quota() { + fn only_iterates_max_2_times_max_allowed_len() { ExtBuilder::default() .nominate(false) // the other nominators only nominate 21 @@ -4712,7 +4712,7 @@ mod sorted_list_provider { vec![11, 21, 31] ); - // when account 101 re-validates + // when account 11 re-validates assert_ok!(Staking::validate(Origin::signed(10), Default::default())); // then counts don't change From 41540f25af0e3aaccfb2cc5523c085936ad6d5b7 Mon Sep 17 00:00:00 2001 From: kianenigma Date: Tue, 15 Feb 2022 12:36:00 +0000 Subject: [PATCH 03/42] some review comments --- frame/babe/src/mock.rs | 2 +- frame/grandpa/src/mock.rs | 2 +- frame/offences/benchmarking/src/mock.rs | 2 +- frame/session/benchmarking/src/mock.rs | 2 +- frame/staking/src/mock.rs | 2 +- frame/staking/src/pallet/impls.rs | 6 +----- frame/staking/src/pallet/mod.rs | 3 ++- 7 files changed, 8 insertions(+), 11 deletions(-) diff --git a/frame/babe/src/mock.rs b/frame/babe/src/mock.rs index 2e4b6023f1e5f..208a14f91d16e 100644 --- a/frame/babe/src/mock.rs +++ b/frame/babe/src/mock.rs @@ -197,7 +197,7 @@ impl pallet_staking::Config for Test { type NextNewSession = Session; type ElectionProvider = onchain::OnChainSequentialPhragmen; type GenesisElectionProvider = Self::ElectionProvider; - type SortedListProvider = pallet_staking::UseNominatorsMap; + type SortedListProvider = pallet_staking::UseNominatorsAndValidatorsMap; type BenchmarkingConfig = pallet_staking::TestBenchmarkingConfig; type WeightInfo = (); } diff --git a/frame/grandpa/src/mock.rs b/frame/grandpa/src/mock.rs index d07f3136d9a0d..04e71445ecd34 100644 --- a/frame/grandpa/src/mock.rs +++ b/frame/grandpa/src/mock.rs @@ -205,7 +205,7 @@ impl pallet_staking::Config for Test { type NextNewSession = Session; type ElectionProvider = onchain::OnChainSequentialPhragmen; type GenesisElectionProvider = Self::ElectionProvider; - type SortedListProvider = pallet_staking::UseNominatorsMap; + type SortedListProvider = pallet_staking::UseNominatorsAndValidatorsMap; type BenchmarkingConfig = pallet_staking::TestBenchmarkingConfig; type WeightInfo = (); } diff --git a/frame/offences/benchmarking/src/mock.rs b/frame/offences/benchmarking/src/mock.rs index 3b5e640867c5a..1b78c00d66dda 100644 --- a/frame/offences/benchmarking/src/mock.rs +++ b/frame/offences/benchmarking/src/mock.rs @@ -175,7 +175,7 @@ impl pallet_staking::Config for Test { type OffendingValidatorsThreshold = (); type ElectionProvider = onchain::OnChainSequentialPhragmen; type GenesisElectionProvider = Self::ElectionProvider; - type SortedListProvider = pallet_staking::UseNominatorsMap; + type SortedListProvider = pallet_staking::UseNominatorsAndValidatorsMap; type BenchmarkingConfig = pallet_staking::TestBenchmarkingConfig; type WeightInfo = (); } diff --git a/frame/session/benchmarking/src/mock.rs b/frame/session/benchmarking/src/mock.rs index 37305437ca095..771839b2bf45b 100644 --- a/frame/session/benchmarking/src/mock.rs +++ b/frame/session/benchmarking/src/mock.rs @@ -181,7 +181,7 @@ impl pallet_staking::Config for Test { type OffendingValidatorsThreshold = (); type ElectionProvider = onchain::OnChainSequentialPhragmen; type GenesisElectionProvider = Self::ElectionProvider; - type SortedListProvider = pallet_staking::UseNominatorsMap; + type SortedListProvider = pallet_staking::UseNominatorsAndValidatorsMap; type BenchmarkingConfig = pallet_staking::TestBenchmarkingConfig; type WeightInfo = (); } diff --git a/frame/staking/src/mock.rs b/frame/staking/src/mock.rs index cb3240769f1f0..16b16dba22d57 100644 --- a/frame/staking/src/mock.rs +++ b/frame/staking/src/mock.rs @@ -269,7 +269,7 @@ impl crate::pallet::pallet::Config for Test { type OffendingValidatorsThreshold = OffendingValidatorsThreshold; type ElectionProvider = onchain::OnChainSequentialPhragmen; type GenesisElectionProvider = Self::ElectionProvider; - // NOTE: consider a macro and use `UseNominatorsMap` as well. + // NOTE: consider a macro and use `UseNominatorsAndValidatorsMap` as well. type SortedListProvider = BagsList; type BenchmarkingConfig = TestBenchmarkingConfig; type WeightInfo = (); diff --git a/frame/staking/src/pallet/impls.rs b/frame/staking/src/pallet/impls.rs index 091c941cb5985..abed1c416d9cc 100644 --- a/frame/staking/src/pallet/impls.rs +++ b/frame/staking/src/pallet/impls.rs @@ -669,13 +669,11 @@ impl Pallet { let weight_of = Self::weight_of_fn(); let slashing_spans = >::iter().collect::>(); - let mut voters_taken = 0u32; let mut voters_seen = 0u32; let mut validators_taken = 0u32; let mut nominators_taken = 0u32; - while voters_taken < (max_allowed_len as u32) && voters_seen < (2 * max_allowed_len as u32) - { + while all_voters.len() < max_allowed_len && voters_seen < (2 * max_allowed_len as u32) { let voter = match T::SortedListProvider::iter().next() { Some(voter) => { voters_seen.saturating_inc(); @@ -695,7 +693,6 @@ impl Pallet { }); if !targets.len().is_zero() { all_voters.push((voter.clone(), weight_of(&voter), targets)); - voters_taken.saturating_inc(); nominators_taken.saturating_inc(); } } else if Validators::::contains_key(&voter) { @@ -708,7 +705,6 @@ impl Pallet { .expect("`MaxVotesPerVoter` must be greater than or equal to 1"), ); all_voters.push(self_vote); - voters_taken.saturating_inc(); validators_taken.saturating_inc(); } else { // this can only happen if: 1. there a bug in the bags-list (or whatever is the diff --git a/frame/staking/src/pallet/mod.rs b/frame/staking/src/pallet/mod.rs index 7fea562ffd14f..51045d1538f8f 100644 --- a/frame/staking/src/pallet/mod.rs +++ b/frame/staking/src/pallet/mod.rs @@ -153,7 +153,8 @@ pub mod pallet { /// Something that can provide a sorted list of voters in a somewhat sorted way. The /// original use case for this was designed with `pallet_bags_list::Pallet` in mind. If - /// the bags-list is not desired, [`impls::UseNominatorsMap`] is likely the desired option. + /// the bags-list is not desired, [`impls::UseNominatorsAndValidatorsMap`] is likely the + /// desired option. type SortedListProvider: SortedListProvider; /// Some parameters of the benchmarking. From 398cf7026e47b8bce715bbfb01aae98d7c5cbe3e Mon Sep 17 00:00:00 2001 From: kianenigma Date: Wed, 16 Feb 2022 14:57:11 +0000 Subject: [PATCH 04/42] guard the migration --- frame/staking/src/lib.rs | 1 + frame/staking/src/migrations.rs | 24 +++++++++++++++++------- 2 files changed, 18 insertions(+), 7 deletions(-) diff --git a/frame/staking/src/lib.rs b/frame/staking/src/lib.rs index 17af4829c0ea8..11d840d42f67c 100644 --- a/frame/staking/src/lib.rs +++ b/frame/staking/src/lib.rs @@ -771,6 +771,7 @@ enum Releases { V6_0_0, // removal of all storage associated with offchain phragmen. V7_0_0, // keep track of number of nominators / validators in map V8_0_0, // populate `SortedListProvider`. + V9_0_0, // inject validators into `SortedListProvider` as well. } impl Default for Releases { diff --git a/frame/staking/src/migrations.rs b/frame/staking/src/migrations.rs index cd7f7c633184f..04b5817ff4cc9 100644 --- a/frame/staking/src/migrations.rs +++ b/frame/staking/src/migrations.rs @@ -26,19 +26,29 @@ use frame_support::traits::OnRuntimeUpgrade; pub struct InjectValidatorsIntoSortedListProvider(sp_std::marker::PhantomData); impl OnRuntimeUpgrade for InjectValidatorsIntoSortedListProvider { fn on_runtime_upgrade() -> Weight { - for (v, _) in Validators::::iter() { - let weight = Pallet::::vote_weight(&v); - let _ = T::SortedListProvider::on_insert(v.clone(), weight).map_err(|err| { - log!(warn, "failed to insert {:?} into SortedListProvider: {:?}", v, err) - }); - } + if StorageVersion::::get() == Releases::V8_0_0 { + for (v, _) in Validators::::iter() { + let weight = Pallet::::vote_weight(&v); + let _ = T::SortedListProvider::on_insert(v.clone(), weight).map_err(|err| { + log!(warn, "failed to insert {:?} into SortedListProvider: {:?}", v, err) + }); + } - T::BlockWeights::get().max_block + T::BlockWeights::get().max_block + } else { + log!(warn, "InjectValidatorsIntoSortedListProvider being executed on the wrong storage version, expected Releases::V8_0_0"); + T::DbWeight::get().reads(1) + } } #[cfg(feature = "try-runtime")] fn pre_upgrade() -> Result<(), &'static str> { use frame_support::traits::OnRuntimeUpgradeHelpersExt; + frame_support::ensure!( + StorageVersion::::get() == crate::Releases::V8_0_0, + "must upgrade linearly" + ); + let prev_count = T::SortedListProvider::count(); Self::set_temp_storage(prev_count, "prev"); } From 66fc7d2ce9214ca6fab726c682386a0f8ac9d57a Mon Sep 17 00:00:00 2001 From: kianenigma Date: Wed, 16 Feb 2022 15:30:18 +0000 Subject: [PATCH 05/42] some review comments --- frame/staking/src/pallet/impls.rs | 10 +++++----- frame/staking/src/tests.rs | 2 +- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/frame/staking/src/pallet/impls.rs b/frame/staking/src/pallet/impls.rs index 3cab0d9487717..0a83b560163ae 100644 --- a/frame/staking/src/pallet/impls.rs +++ b/frame/staking/src/pallet/impls.rs @@ -655,8 +655,8 @@ impl Pallet { /// /// ### Slashing /// - /// All votes that have been submitted before the last non-zero slash of the corresponding target are - /// *auto-chilled*, but still count towards the limit imposed by `maybe_max_len`. + /// All votes that have been submitted before the last non-zero slash of the corresponding + /// target are *auto-chilled*, but still count towards the limit imposed by `maybe_max_len`. pub fn get_npos_voters(maybe_max_len: Option) -> Vec> { let max_allowed_len = { let all_voter_count = T::SortedListProvider::count() as usize; @@ -710,8 +710,8 @@ impl Pallet { // this can only happen if: 1. there a bug in the bags-list (or whatever is the // sorted list) logic and the state of the two pallets is no longer compatible, or // because the nominators is not decodable since they have more nomination than - // `T::MaxNominations`. The latter can rarely happen, and is not really an emergency or - // bug if it does. + // `T::MaxNominations`. The latter can rarely happen, and is not really an emergency + // or bug if it does. log!( warn, "DEFENSIVE: invalid item in `SortedListProvider`: {:?}, this nominator probably has too many nominations now", @@ -732,7 +732,7 @@ impl Pallet { log!( info, "generated {} npos voters, {} from validators and {} nominators", - voters_taken, + all_voters.len(), validators_taken, nominators_taken ); diff --git a/frame/staking/src/tests.rs b/frame/staking/src/tests.rs index faf3b9d90c377..50d95f2847cb1 100644 --- a/frame/staking/src/tests.rs +++ b/frame/staking/src/tests.rs @@ -4150,7 +4150,7 @@ mod election_data_provider { // roll to session 5 run_to_block(25); - // slash 21, the only validator nominated by 61 and 21. + // slash 21, the only validator nominated by 61. add_slash(&21); // we take 4 voters; 71 and 81 are replacing the ejected ones. From 75989dec72ed5e6ffb55c1add74d2ddc21ac0d73 Mon Sep 17 00:00:00 2001 From: kianenigma Date: Wed, 16 Feb 2022 15:40:57 +0000 Subject: [PATCH 06/42] =?UTF-8?q?Fix=20tests=20=F0=9F=A4=A6=E2=80=8D?= =?UTF-8?q?=E2=99=82=EF=B8=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- frame/staking/src/pallet/impls.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/frame/staking/src/pallet/impls.rs b/frame/staking/src/pallet/impls.rs index 0a83b560163ae..0e603b59e986f 100644 --- a/frame/staking/src/pallet/impls.rs +++ b/frame/staking/src/pallet/impls.rs @@ -673,8 +673,9 @@ impl Pallet { let mut validators_taken = 0u32; let mut nominators_taken = 0u32; + let mut sorted_voters = T::SortedListProvider::iter(); while all_voters.len() < max_allowed_len && voters_seen < (2 * max_allowed_len as u32) { - let voter = match T::SortedListProvider::iter().next() { + let voter = match sorted_voters.next() { Some(voter) => { voters_seen.saturating_inc(); voter From db2a7b81a6c8487463af7929aa1ccef7df885972 Mon Sep 17 00:00:00 2001 From: kianenigma Date: Wed, 16 Feb 2022 16:11:36 +0000 Subject: [PATCH 07/42] Fix build --- frame/staking/src/lib.rs | 6 +++--- frame/staking/src/migrations.rs | 4 +++- frame/staking/src/pallet/mod.rs | 9 ++++----- 3 files changed, 10 insertions(+), 9 deletions(-) diff --git a/frame/staking/src/lib.rs b/frame/staking/src/lib.rs index 11d840d42f67c..fa71a50962f6c 100644 --- a/frame/staking/src/lib.rs +++ b/frame/staking/src/lib.rs @@ -301,7 +301,7 @@ mod pallet; use codec::{Decode, Encode, HasCompact}; use frame_support::{ - traits::{ConstU32, Currency, Get}, + traits::{Currency, Get}, weights::Weight, BoundedVec, EqNoBound, PartialEqNoBound, RuntimeDebugNoBound, }; @@ -852,6 +852,6 @@ pub struct TestBenchmarkingConfig; #[cfg(feature = "std")] impl BenchmarkingConfig for TestBenchmarkingConfig { - type MaxValidators = ConstU32<100>; - type MaxNominators = ConstU32<100>; + type MaxValidators = frame_support::traits::ConstU32<100>; + type MaxNominators = frame_support::traits::ConstU32<100>; } diff --git a/frame/staking/src/migrations.rs b/frame/staking/src/migrations.rs index 04b5817ff4cc9..029a6a380fee3 100644 --- a/frame/staking/src/migrations.rs +++ b/frame/staking/src/migrations.rs @@ -51,15 +51,17 @@ impl OnRuntimeUpgrade for InjectValidatorsIntoSortedListProvider { let prev_count = T::SortedListProvider::count(); Self::set_temp_storage(prev_count, "prev"); + Ok(()) } #[cfg(feature = "try-runtime")] fn post_upgrade() -> Result<(), &'static str> { use frame_support::traits::OnRuntimeUpgradeHelpersExt; let post_count = T::SortedListProvider::count(); - let prev_count = Self::get_temp_storage::("prev"); + let prev_count = Self::get_temp_storage::("prev").unwrap(); let validators = Validators::::count(); assert!(post_count == prev_count + validators); + Ok(()) } } diff --git a/frame/staking/src/pallet/mod.rs b/frame/staking/src/pallet/mod.rs index 36056c4f9db6d..134d3a9a65bc5 100644 --- a/frame/staking/src/pallet/mod.rs +++ b/frame/staking/src/pallet/mod.rs @@ -39,10 +39,9 @@ mod impls; pub use impls::*; use crate::{ - log, slashing, weights::WeightInfo, ActiveEraInfo, BalanceOf, EraPayout, EraRewardPoints, - Exposure, Forcing, NegativeImbalanceOf, Nominations, PositiveImbalanceOf, Releases, - RewardDestination, SessionInterface, StakingLedger, UnappliedSlash, UnlockChunk, - ValidatorPrefs, + slashing, weights::WeightInfo, ActiveEraInfo, BalanceOf, EraPayout, EraRewardPoints, Exposure, + Forcing, NegativeImbalanceOf, Nominations, PositiveImbalanceOf, Releases, RewardDestination, + SessionInterface, StakingLedger, UnappliedSlash, UnlockChunk, ValidatorPrefs, }; pub const MAX_UNLOCKING_CHUNKS: usize = 32; @@ -535,7 +534,7 @@ pub mod pallet { } for &(ref stash, ref controller, balance, ref status) in &self.stakers { - log!( + crate::log!( trace, "inserting genesis staker: {:?} => {:?} => {:?}", stash, From 18fb2f415125a78e9a9ded7f2c9dd4dbe170b585 Mon Sep 17 00:00:00 2001 From: kianenigma Date: Mon, 28 Feb 2022 11:45:44 +0000 Subject: [PATCH 08/42] lay the groundwork to store validators in a bags-list instance --- frame/multisig/src/lib.rs | 11 ++- frame/staking/src/benchmarking.rs | 32 +++---- frame/staking/src/lib.rs | 11 +-- frame/staking/src/migrations.rs | 23 +++-- frame/staking/src/mock.rs | 7 +- frame/staking/src/pallet/impls.rs | 140 ++++++++++++++++++----------- frame/staking/src/pallet/mod.rs | 48 +++++++--- frame/staking/src/testing_utils.rs | 4 +- frame/staking/src/tests.rs | 28 +++--- frame/support/src/traits/misc.rs | 8 +- 10 files changed, 182 insertions(+), 130 deletions(-) diff --git a/frame/multisig/src/lib.rs b/frame/multisig/src/lib.rs index cd59ea881739d..91cbec2383871 100644 --- a/frame/multisig/src/lib.rs +++ b/frame/multisig/src/lib.rs @@ -24,12 +24,11 @@ //! ## Overview //! //! This pallet contains functionality for multi-signature dispatch, a (potentially) stateful -//! operation, allowing multiple signed -//! origins (accounts) to coordinate and dispatch a call from a well-known origin, derivable -//! deterministically from the set of account IDs and the threshold number of accounts from the -//! set that must approve it. In the case that the threshold is just one then this is a stateless -//! operation. This is useful for multisig wallets where cryptographic threshold signatures are -//! not available or desired. +//! operation, allowing multiple signed origins (accounts) to coordinate and dispatch a call from a +//! well-known origin, derivable deterministically from the set of account IDs and the threshold +//! number of accounts from the set that must approve it. In the case that the threshold is just one +//! then this is a stateless operation. This is useful for multisig wallets where cryptographic +//! threshold signatures are not available or desired. //! //! ## Interface //! diff --git a/frame/staking/src/benchmarking.rs b/frame/staking/src/benchmarking.rs index 115e4db54143c..300f27c2578c4 100644 --- a/frame/staking/src/benchmarking.rs +++ b/frame/staking/src/benchmarking.rs @@ -189,7 +189,7 @@ impl ListScenario { // find a destination weight that will trigger the worst case scenario let dest_weight_as_vote = - T::SortedListProvider::weight_update_worst_case(&origin_stash1, is_increase); + T::VoterList::weight_update_worst_case(&origin_stash1, is_increase); let total_issuance = T::Currency::total_issuance(); @@ -316,7 +316,7 @@ benchmarks! { let scenario = ListScenario::::new(origin_weight, true)?; let controller = scenario.origin_controller1.clone(); let stash = scenario.origin_stash1.clone(); - assert!(T::SortedListProvider::contains(&stash)); + assert!(T::VoterList::contains(&stash)); let ed = T::Currency::minimum_balance(); let mut ledger = Ledger::::get(&controller).unwrap(); @@ -328,7 +328,7 @@ benchmarks! { }: withdraw_unbonded(RawOrigin::Signed(controller.clone()), s) verify { assert!(!Ledger::::contains_key(controller)); - assert!(!T::SortedListProvider::contains(&stash)); + assert!(!T::VoterList::contains(&stash)); } validate { @@ -338,14 +338,14 @@ benchmarks! { Default::default(), )?; // because it is chilled. - assert!(!T::SortedListProvider::contains(&stash)); + assert!(!T::VoterList::contains(&stash)); let prefs = ValidatorPrefs::default(); whitelist_account!(controller); }: _(RawOrigin::Signed(controller), prefs) verify { assert!(Validators::::contains_key(&stash)); - assert!(T::SortedListProvider::contains(&stash)); + assert!(T::VoterList::contains(&stash)); } kick { @@ -430,14 +430,14 @@ benchmarks! { ).unwrap(); assert!(!Nominators::::contains_key(&stash)); - assert!(!T::SortedListProvider::contains(&stash)); + assert!(!T::VoterList::contains(&stash)); let validators = create_validators::(n, 100).unwrap(); whitelist_account!(controller); }: _(RawOrigin::Signed(controller), validators) verify { assert!(Nominators::::contains_key(&stash)); - assert!(T::SortedListProvider::contains(&stash)) + assert!(T::VoterList::contains(&stash)) } chill { @@ -451,12 +451,12 @@ benchmarks! { let scenario = ListScenario::::new(origin_weight, true)?; let controller = scenario.origin_controller1.clone(); let stash = scenario.origin_stash1.clone(); - assert!(T::SortedListProvider::contains(&stash)); + assert!(T::VoterList::contains(&stash)); whitelist_account!(controller); }: _(RawOrigin::Signed(controller)) verify { - assert!(!T::SortedListProvider::contains(&stash)); + assert!(!T::VoterList::contains(&stash)); } set_payee { @@ -519,13 +519,13 @@ benchmarks! { let scenario = ListScenario::::new(origin_weight, true)?; let controller = scenario.origin_controller1.clone(); let stash = scenario.origin_stash1.clone(); - assert!(T::SortedListProvider::contains(&stash)); + assert!(T::VoterList::contains(&stash)); add_slashing_spans::(&stash, s); }: _(RawOrigin::Root, stash.clone(), s) verify { assert!(!Ledger::::contains_key(&controller)); - assert!(!T::SortedListProvider::contains(&stash)); + assert!(!T::VoterList::contains(&stash)); } cancel_deferred_slash { @@ -704,13 +704,13 @@ benchmarks! { Ledger::::insert(&controller, l); assert!(Bonded::::contains_key(&stash)); - assert!(T::SortedListProvider::contains(&stash)); + assert!(T::VoterList::contains(&stash)); whitelist_account!(controller); }: _(RawOrigin::Signed(controller), stash.clone(), s) verify { assert!(!Bonded::::contains_key(&stash)); - assert!(!T::SortedListProvider::contains(&stash)); + assert!(!T::VoterList::contains(&stash)); } new_era { @@ -844,7 +844,7 @@ benchmarks! { v, n, T::MaxNominations::get() as usize, false, None )?; }: { - let targets = >::get_npos_targets(); + let targets = >::get_npos_targets(None); assert_eq!(targets.len() as u32, v); } @@ -878,7 +878,7 @@ benchmarks! { let scenario = ListScenario::::new(origin_weight, true)?; let controller = scenario.origin_controller1.clone(); let stash = scenario.origin_stash1.clone(); - assert!(T::SortedListProvider::contains(&stash)); + assert!(T::VoterList::contains(&stash)); Staking::::set_staking_configs( RawOrigin::Root.into(), @@ -893,7 +893,7 @@ benchmarks! { let caller = whitelisted_caller(); }: _(RawOrigin::Signed(caller), controller.clone()) verify { - assert!(!T::SortedListProvider::contains(&stash)); + assert!(!T::VoterList::contains(&stash)); } force_apply_min_commission { diff --git a/frame/staking/src/lib.rs b/frame/staking/src/lib.rs index beb814384f227..a38939edb195b 100644 --- a/frame/staking/src/lib.rs +++ b/frame/staking/src/lib.rs @@ -767,11 +767,12 @@ enum Releases { V2_0_0, V3_0_0, V4_0_0, - V5_0_0, // blockable validators. - V6_0_0, // removal of all storage associated with offchain phragmen. - V7_0_0, // keep track of number of nominators / validators in map - V8_0_0, // populate `SortedListProvider`. - V9_0_0, // inject validators into `SortedListProvider` as well. + V5_0_0, // blockable validators. + V6_0_0, // removal of all storage associated with offchain phragmen. + V7_0_0, // keep track of number of nominators / validators in map + V8_0_0, // populate `VoterList`. + V9_0_0, // inject validators into `NPoSVoteProvider` as well. + V10_0_0, // inject validator into `NPoSTargetList`. } impl Default for Releases { diff --git a/frame/staking/src/migrations.rs b/frame/staking/src/migrations.rs index 029a6a380fee3..bbe48a3b2a246 100644 --- a/frame/staking/src/migrations.rs +++ b/frame/staking/src/migrations.rs @@ -22,21 +22,21 @@ use frame_support::traits::OnRuntimeUpgrade; /// Migration implementation that injects all validators into sorted list. /// -/// This is only useful for chains that started their `SortedListProvider` just based on nominators. -pub struct InjectValidatorsIntoSortedListProvider(sp_std::marker::PhantomData); -impl OnRuntimeUpgrade for InjectValidatorsIntoSortedListProvider { +/// This is only useful for chains that started their `VoterList` just based on nominators. +pub struct InjectValidatorsIntoVoterList(sp_std::marker::PhantomData); +impl OnRuntimeUpgrade for InjectValidatorsIntoVoterList { fn on_runtime_upgrade() -> Weight { if StorageVersion::::get() == Releases::V8_0_0 { for (v, _) in Validators::::iter() { let weight = Pallet::::vote_weight(&v); - let _ = T::SortedListProvider::on_insert(v.clone(), weight).map_err(|err| { - log!(warn, "failed to insert {:?} into SortedListProvider: {:?}", v, err) + let _ = T::VoterList::on_insert(v.clone(), weight).map_err(|err| { + log!(warn, "failed to insert {:?} into VoterList: {:?}", v, err) }); } T::BlockWeights::get().max_block } else { - log!(warn, "InjectValidatorsIntoSortedListProvider being executed on the wrong storage version, expected Releases::V8_0_0"); + log!(warn, "InjectValidatorsIntoVoterList being executed on the wrong storage version, expected Releases::V8_0_0"); T::DbWeight::get().reads(1) } } @@ -49,7 +49,7 @@ impl OnRuntimeUpgrade for InjectValidatorsIntoSortedListProvider { "must upgrade linearly" ); - let prev_count = T::SortedListProvider::count(); + let prev_count = T::VoterList::count(); Self::set_temp_storage(prev_count, "prev"); Ok(()) } @@ -57,7 +57,7 @@ impl OnRuntimeUpgrade for InjectValidatorsIntoSortedListProvider { #[cfg(feature = "try-runtime")] fn post_upgrade() -> Result<(), &'static str> { use frame_support::traits::OnRuntimeUpgradeHelpersExt; - let post_count = T::SortedListProvider::count(); + let post_count = T::VoterList::count(); let prev_count = Self::get_temp_storage::("prev").unwrap(); let validators = Validators::::count(); assert!(post_count == prev_count + validators); @@ -87,11 +87,11 @@ pub mod v8 { if StorageVersion::::get() == crate::Releases::V7_0_0 { crate::log!(info, "migrating staking to Releases::V8_0_0"); - let migrated = T::SortedListProvider::unsafe_regenerate( + let migrated = T::VoterList::unsafe_regenerate( Nominators::::iter().map(|(id, _)| id), Pallet::::weight_of_fn(), ); - debug_assert_eq!(T::SortedListProvider::sanity_check(), Ok(())); + debug_assert_eq!(T::VoterList::sanity_check(), Ok(())); StorageVersion::::put(crate::Releases::V8_0_0); crate::log!( @@ -108,8 +108,7 @@ pub mod v8 { #[cfg(feature = "try-runtime")] pub fn post_migrate() -> Result<(), &'static str> { - T::SortedListProvider::sanity_check() - .map_err(|_| "SortedListProvider is not in a sane state.")?; + T::VoterList::sanity_check().map_err(|_| "VoterList is not in a sane state.")?; crate::log!(info, "👜 staking bags-list migration passes POST migrate checks ✅",); Ok(()) } diff --git a/frame/staking/src/mock.rs b/frame/staking/src/mock.rs index 16b16dba22d57..c9791317c1c5b 100644 --- a/frame/staking/src/mock.rs +++ b/frame/staking/src/mock.rs @@ -270,7 +270,8 @@ impl crate::pallet::pallet::Config for Test { type ElectionProvider = onchain::OnChainSequentialPhragmen; type GenesisElectionProvider = Self::ElectionProvider; // NOTE: consider a macro and use `UseNominatorsAndValidatorsMap` as well. - type SortedListProvider = BagsList; + type VoterList = BagsList; + type TargetList = UseValidatorsMap; type BenchmarkingConfig = TestBenchmarkingConfig; type WeightInfo = (); } @@ -539,8 +540,8 @@ fn check_count() { assert_eq!(nominator_count, Nominators::::count()); assert_eq!(validator_count, Validators::::count()); - // the voters that the `SortedListProvider` list is storing for us. - let external_voters = ::SortedListProvider::count(); + // the voters that the `VoterList` list is storing for us. + let external_voters = ::VoterList::count(); assert_eq!(external_voters, nominator_count + validator_count); } diff --git a/frame/staking/src/pallet/impls.rs b/frame/staking/src/pallet/impls.rs index 0e603b59e986f..1dbad666bdb33 100644 --- a/frame/staking/src/pallet/impls.rs +++ b/frame/staking/src/pallet/impls.rs @@ -22,6 +22,7 @@ use frame_election_provider_support::{ VoteWeight, VoteWeightProvider, VoterOf, }; use frame_support::{ + defensive, pallet_prelude::*, traits::{ Currency, CurrencyToVote, Defensive, EstimateNextNewSession, Get, Imbalance, @@ -659,7 +660,7 @@ impl Pallet { /// target are *auto-chilled*, but still count towards the limit imposed by `maybe_max_len`. pub fn get_npos_voters(maybe_max_len: Option) -> Vec> { let max_allowed_len = { - let all_voter_count = T::SortedListProvider::count() as usize; + let all_voter_count = T::VoterList::count() as usize; maybe_max_len.unwrap_or(all_voter_count).min(all_voter_count) }; @@ -673,7 +674,7 @@ impl Pallet { let mut validators_taken = 0u32; let mut nominators_taken = 0u32; - let mut sorted_voters = T::SortedListProvider::iter(); + let mut sorted_voters = T::VoterList::iter(); while all_voters.len() < max_allowed_len && voters_seen < (2 * max_allowed_len as u32) { let voter = match sorted_voters.next() { Some(voter) => { @@ -715,7 +716,7 @@ impl Pallet { // or bug if it does. log!( warn, - "DEFENSIVE: invalid item in `SortedListProvider`: {:?}, this nominator probably has too many nominations now", + "DEFENSIVE: invalid item in `VoterList`: {:?}, this nominator probably has too many nominations now", voter ) } @@ -744,22 +745,18 @@ impl Pallet { /// Get the targets for an upcoming npos election. /// /// This function is self-weighing as [`DispatchClass::Mandatory`]. - pub fn get_npos_targets() -> Vec { - let mut validator_count = 0u32; - let targets = Validators::::iter() - .map(|(v, _)| { - validator_count.saturating_inc(); - v - }) + pub fn get_npos_targets(maybe_max_len: Option) -> Vec { + let targets = T::TargetList::iter() + .take(maybe_max_len.unwrap_or_else(Bounded::max_value)) .collect::>(); - Self::register_weight(T::WeightInfo::get_npos_targets(validator_count)); + Self::register_weight(T::WeightInfo::get_npos_targets(targets.len() as u32)); targets } /// This function will add a nominator to the `Nominators` storage map, - /// and [`SortedListProvider`]. + /// and [`Config::VoterList`]. /// /// If the nominator already exists, their nominations will be updated. /// @@ -769,20 +766,17 @@ impl Pallet { pub fn do_add_nominator(who: &T::AccountId, nominations: Nominations) { if !Nominators::::contains_key(who) { // maybe update sorted list. - let _ = T::SortedListProvider::on_insert(who.clone(), Self::weight_of(who)) + let _ = T::VoterList::on_insert(who.clone(), Self::weight_of(who)) .defensive_unwrap_or_default(); } Nominators::::insert(who, nominations); - debug_assert_eq!( - Nominators::::count() + Validators::::count(), - T::SortedListProvider::count() - ); - debug_assert_eq!(T::SortedListProvider::sanity_check(), Ok(())); + #[cfg(debug_assertions)] + Self::sanity_check_list_providers(); } /// This function will remove a nominator from the `Nominators` storage map, - /// and [`SortedListProvider`]. + /// and [`Config::VoterList`]. /// /// Returns true if `who` was removed from `Nominators`, otherwise false. /// @@ -792,17 +786,14 @@ impl Pallet { pub fn do_remove_nominator(who: &T::AccountId) -> bool { let outcome = if Nominators::::contains_key(who) { Nominators::::remove(who); - T::SortedListProvider::on_remove(who); + T::VoterList::on_remove(who); true } else { false }; - debug_assert_eq!(T::SortedListProvider::sanity_check(), Ok(())); - debug_assert_eq!( - Nominators::::count() + Validators::::count(), - T::SortedListProvider::count() - ); + #[cfg(debug_assertions)] + Self::sanity_check_list_providers(); outcome } @@ -817,16 +808,15 @@ impl Pallet { pub fn do_add_validator(who: &T::AccountId, prefs: ValidatorPrefs) { if !Validators::::contains_key(who) { // maybe update sorted list. - let _ = T::SortedListProvider::on_insert(who.clone(), Self::weight_of(who)) + let _ = T::VoterList::on_insert(who.clone(), Self::weight_of(who)) + .defensive_unwrap_or_default(); + let _ = T::TargetList::on_insert(who.clone(), Self::weight_of(who)) .defensive_unwrap_or_default(); } Validators::::insert(who, prefs); - debug_assert_eq!( - Nominators::::count() + Validators::::count(), - T::SortedListProvider::count() - ); - debug_assert_eq!(T::SortedListProvider::sanity_check(), Ok(())); + #[cfg(debug_assertions)] + Self::sanity_check_list_providers(); } /// This function will remove a validator from the `Validators` storage map. @@ -839,17 +829,15 @@ impl Pallet { pub fn do_remove_validator(who: &T::AccountId) -> bool { let outcome = if Validators::::contains_key(who) { Validators::::remove(who); - T::SortedListProvider::on_remove(who); + T::VoterList::on_remove(who); + T::TargetList::on_remove(who); true } else { false }; - debug_assert_eq!(T::SortedListProvider::sanity_check(), Ok(())); - debug_assert_eq!( - Nominators::::count() + Validators::::count(), - T::SortedListProvider::count() - ); + #[cfg(debug_assertions)] + Self::sanity_check_list_providers(); outcome } @@ -863,6 +851,18 @@ impl Pallet { DispatchClass::Mandatory, ); } + + /// Perform all checks related to both [`Config::TargetList`] and [`Config::VoterList`]. + #[cfg(debug_assertions)] + fn sanity_check_list_providers() { + debug_assert_eq!( + Nominators::::count() + Validators::::count(), + T::VoterList::count() + ); + debug_assert_eq!(Validators::::count(), T::TargetList::count()); + debug_assert_eq!(T::VoterList::sanity_check(), Ok(())); + debug_assert_eq!(T::VoterList::sanity_check(), Ok(())); + } } impl ElectionDataProvider for Pallet { @@ -876,22 +876,19 @@ impl ElectionDataProvider for Pallet { } fn voters(maybe_max_len: Option) -> data_provider::Result>> { - // This can never fail -- if `maybe_max_len` is `Some(_)` we handle it. let voters = Self::get_npos_voters(maybe_max_len); - debug_assert!(maybe_max_len.map_or(true, |max| voters.len() <= max)); - + if maybe_max_len.map_or(false, |m| voters.len() > m) { + defensive!("get_npos_voters is corrupt"); + } Ok(voters) } fn targets(maybe_max_len: Option) -> data_provider::Result> { - let target_count = Validators::::count(); - - // We can't handle this case yet -- return an error. - if maybe_max_len.map_or(false, |max_len| target_count > max_len as u32) { - return Err("Target snapshot too big") + let targets = Self::get_npos_targets(maybe_max_len); + if maybe_max_len.map_or(false, |m| targets.len() > m) { + defensive!("get_npos_targets is corrupt"); } - - Ok(Self::get_npos_targets()) + Ok(targets) } fn next_election_prediction(now: T::BlockNumber) -> T::BlockNumber { @@ -978,7 +975,7 @@ impl ElectionDataProvider for Pallet { >::remove_all(); >::remove_all(); - T::SortedListProvider::unsafe_clear(); + T::VoterList::unsafe_clear(); } #[cfg(feature = "runtime-benchmarks")] @@ -1288,7 +1285,7 @@ impl VoteWeightProvider for Pallet { } } -/// A simple voter list implementation that does not require any additional pallets. Note, this +/// A simple sorted list implementation that does not require any additional pallets. Note, this /// does not provided nominators in sorted ordered. If you desire nominators in a sorted order take /// a look at [`pallet-bags-list]. pub struct UseNominatorsAndValidatorsMap(sp_std::marker::PhantomData); @@ -1331,9 +1328,50 @@ impl SortedListProvider for UseNominatorsAndValidatorsM } fn unsafe_clear() { - // NOTE: Caller must ensure this doesn't lead to too many storage accesses. This is a - // condition of SortedListProvider::unsafe_clear. Nominators::::remove_all(); Validators::::remove_all(); } } + +/// A simple sorted list implementation that does not require any additional pallets. Note, this +/// does not provided validators in sorted ordered. If you desire nominators in a sorted order take +/// a look at [`pallet-bags-list]. +pub struct UseValidatorsMap(sp_std::marker::PhantomData); +impl SortedListProvider for UseValidatorsMap { + type Error = (); + + /// Returns iterator over voter list, which can have `take` called on it. + fn iter() -> Box> { + Box::new(Validators::::iter().map(|(v, _)| v)) + } + fn count() -> u32 { + Validators::::count() + } + fn contains(id: &T::AccountId) -> bool { + Validators::::contains_key(id) + } + fn on_insert(_: T::AccountId, _weight: VoteWeight) -> Result<(), Self::Error> { + // nothing to do on insert. + Ok(()) + } + fn on_update(_: &T::AccountId, _weight: VoteWeight) { + // nothing to do on update. + } + fn on_remove(_: &T::AccountId) { + // nothing to do on remove. + } + fn unsafe_regenerate( + _: impl IntoIterator, + _: Box VoteWeight>, + ) -> u32 { + // nothing to do upon regenerate. + 0 + } + fn sanity_check() -> Result<(), &'static str> { + Ok(()) + } + + fn unsafe_clear() { + Validators::::remove_all(); + } +} diff --git a/frame/staking/src/pallet/mod.rs b/frame/staking/src/pallet/mod.rs index 134d3a9a65bc5..70cb229870941 100644 --- a/frame/staking/src/pallet/mod.rs +++ b/frame/staking/src/pallet/mod.rs @@ -150,11 +150,14 @@ pub mod pallet { /// After the threshold is reached a new era will be forced. type OffendingValidatorsThreshold: Get; - /// Something that can provide a sorted list of voters in a somewhat sorted way. The - /// original use case for this was designed with `pallet_bags_list::Pallet` in mind. If - /// the bags-list is not desired, [`impls::UseNominatorsAndValidatorsMap`] is likely the - /// desired option. - type SortedListProvider: SortedListProvider; + /// Something that can provide a list of voters in a somewhat sorted way. The original use + /// case for this was designed with `pallet_bags_list::Pallet` in mind. Otherwise, + /// [`impls::UseNominatorsAndValidatorsMap`] is likely the second option. + type VoterList: SortedListProvider; + /// Something that can provide a list of targets in a somewhat sorted way. The original use + /// case for this was designed with `pallet_bags_list::Pallet` in mind. Otherwise, + /// [`impls::UseValidatorsMap`] is likely the second option. + type TargetList: SortedListProvider; /// Some parameters of the benchmarking. type BenchmarkingConfig: BenchmarkingConfig; @@ -564,12 +567,17 @@ pub mod pallet { }); } - // all voters are reported to the `SortedListProvider`. + // all voters are reported to the `VoterList`. assert_eq!( - T::SortedListProvider::count(), + T::VoterList::count(), Nominators::::count() + Validators::::count(), "not all genesis stakers were inserted into sorted list provider, something is wrong." ); + assert_eq!( + T::TargetList::count(), + Validators::::count(), + "not all genesis stakers were inserted into sorted list provider, something is wrong." + ); } } @@ -816,10 +824,15 @@ pub mod pallet { // NOTE: ledger must be updated prior to calling `Self::weight_of`. Self::update_ledger(&controller, &ledger); + // update this staker in the sorted list, if they exist in it. - if T::SortedListProvider::contains(&stash) { - T::SortedListProvider::on_update(&stash, Self::weight_of(&ledger.stash)); - debug_assert_eq!(T::SortedListProvider::sanity_check(), Ok(())); + if T::VoterList::contains(&stash) { + T::VoterList::on_update(&stash, Self::weight_of(&ledger.stash)); + debug_assert_eq!(T::VoterList::sanity_check(), Ok(())); + } + if T::TargetList::contains(&stash) { + T::TargetList::on_update(&stash, Self::weight_of(&ledger.stash)); + debug_assert_eq!(T::VoterList::sanity_check(), Ok(())); } Self::deposit_event(Event::::Bonded(stash.clone(), extra)); @@ -885,8 +898,11 @@ pub mod pallet { Self::update_ledger(&controller, &ledger); // update this staker in the sorted list, if they exist in it. - if T::SortedListProvider::contains(&ledger.stash) { - T::SortedListProvider::on_update(&ledger.stash, Self::weight_of(&ledger.stash)); + if T::VoterList::contains(&ledger.stash) { + T::VoterList::on_update(&ledger.stash, Self::weight_of(&ledger.stash)); + } + if T::TargetList::contains(&ledger.stash) { + T::TargetList::on_update(&ledger.stash, Self::weight_of(&ledger.stash)); } Self::deposit_event(Event::::Unbonded(ledger.stash, value)); @@ -1368,8 +1384,12 @@ pub mod pallet { // NOTE: ledger must be updated prior to calling `Self::weight_of`. Self::update_ledger(&controller, &ledger); - if T::SortedListProvider::contains(&ledger.stash) { - T::SortedListProvider::on_update(&ledger.stash, Self::weight_of(&ledger.stash)); + + if T::VoterList::contains(&ledger.stash) { + T::VoterList::on_update(&ledger.stash, Self::weight_of(&ledger.stash)); + } + if T::TargetList::contains(&ledger.stash) { + T::TargetList::on_update(&ledger.stash, Self::weight_of(&ledger.stash)); } let removed_chunks = 1u32 // for the case where the last iterated chunk is not removed diff --git a/frame/staking/src/testing_utils.rs b/frame/staking/src/testing_utils.rs index 8e6bd88468930..5f9f378b10619 100644 --- a/frame/staking/src/testing_utils.rs +++ b/frame/staking/src/testing_utils.rs @@ -38,11 +38,11 @@ const SEED: u32 = 0; pub fn clear_validators_and_nominators() { Validators::::remove_all(); - // whenever we touch nominators counter we should update `T::SortedListProvider` as well. + // whenever we touch nominators counter we should update `T::VoterList` as well. Nominators::::remove_all(); // NOTE: safe to call outside block production - T::SortedListProvider::unsafe_clear(); + T::VoterList::unsafe_clear(); } /// Grab a funded user. diff --git a/frame/staking/src/tests.rs b/frame/staking/src/tests.rs index 50d95f2847cb1..26e74cb1a7e57 100644 --- a/frame/staking/src/tests.rs +++ b/frame/staking/src/tests.rs @@ -4056,7 +4056,7 @@ mod election_data_provider { .set_status(41, StakerStatus::Validator) .build_and_execute(|| { // sum of all nominators who'd be voters (1), plus the self-votes (4). - assert_eq!(::SortedListProvider::count(), 5); + assert_eq!(::VoterList::count(), 5); // if limits is less.. assert_eq!(Staking::voters(Some(1)).unwrap().len(), 1); @@ -4090,7 +4090,7 @@ mod election_data_provider { .build_and_execute(|| { // all voters ordered by stake, assert_eq!( - ::SortedListProvider::iter().collect::>(), + ::VoterList::iter().collect::>(), vec![61, 71, 81, 11, 21, 31] ); @@ -4132,7 +4132,7 @@ mod election_data_provider { .build_and_execute(|| { // given our voters ordered by stake, assert_eq!( - ::SortedListProvider::iter().collect::>(), + ::VoterList::iter().collect::>(), vec![11, 21, 31, 61, 71, 81] ); @@ -4679,10 +4679,10 @@ mod sorted_list_provider { // given let pre_insert_voter_count = (Nominators::::count() + Validators::::count()) as u32; - assert_eq!(::SortedListProvider::count(), pre_insert_voter_count); + assert_eq!(::VoterList::count(), pre_insert_voter_count); assert_eq!( - ::SortedListProvider::iter().collect::>(), + ::VoterList::iter().collect::>(), vec![11, 21, 31, 101] ); @@ -4690,10 +4690,10 @@ mod sorted_list_provider { assert_ok!(Staking::nominate(Origin::signed(100), vec![41])); // then counts don't change - assert_eq!(::SortedListProvider::count(), pre_insert_voter_count); + assert_eq!(::VoterList::count(), pre_insert_voter_count); // and the list is the same assert_eq!( - ::SortedListProvider::iter().collect::>(), + ::VoterList::iter().collect::>(), vec![11, 21, 31, 101] ); }); @@ -4705,23 +4705,17 @@ mod sorted_list_provider { // given let pre_insert_voter_count = (Nominators::::count() + Validators::::count()) as u32; - assert_eq!(::SortedListProvider::count(), pre_insert_voter_count); + assert_eq!(::VoterList::count(), pre_insert_voter_count); - assert_eq!( - ::SortedListProvider::iter().collect::>(), - vec![11, 21, 31] - ); + assert_eq!(::VoterList::iter().collect::>(), vec![11, 21, 31]); // when account 11 re-validates assert_ok!(Staking::validate(Origin::signed(10), Default::default())); // then counts don't change - assert_eq!(::SortedListProvider::count(), pre_insert_voter_count); + assert_eq!(::VoterList::count(), pre_insert_voter_count); // and the list is the same - assert_eq!( - ::SortedListProvider::iter().collect::>(), - vec![11, 21, 31] - ); + assert_eq!(::VoterList::iter().collect::>(), vec![11, 21, 31]); }); } } diff --git a/frame/support/src/traits/misc.rs b/frame/support/src/traits/misc.rs index d2fd438d3a802..8c61874003bce 100644 --- a/frame/support/src/traits/misc.rs +++ b/frame/support/src/traits/misc.rs @@ -38,18 +38,18 @@ macro_rules! defensive { frame_support::log::error!( target: "runtime", "{}", - $crate::traits::misc::DEFENSIVE_OP_PUBLIC_ERROR + $crate::traits::DEFENSIVE_OP_PUBLIC_ERROR ); - debug_assert!(false, "{}", $crate::traits::misc::DEFENSIVE_OP_INTERNAL_ERROR); + debug_assert!(false, "{}", $crate::traits::DEFENSIVE_OP_INTERNAL_ERROR); }; ($error:tt) => { frame_support::log::error!( target: "runtime", "{}: {:?}", - $crate::traits::misc::DEFENSIVE_OP_PUBLIC_ERROR, + $crate::traits::DEFENSIVE_OP_PUBLIC_ERROR, $error ); - debug_assert!(false, "{}: {:?}", $crate::traits::misc::DEFENSIVE_OP_INTERNAL_ERROR, $error); + debug_assert!(false, "{}: {:?}", $crate::traits::DEFENSIVE_OP_INTERNAL_ERROR, $error); } } From ae8165fe40d74ac63ac72efd66c45b533edf0635 Mon Sep 17 00:00:00 2001 From: kianenigma Date: Mon, 28 Feb 2022 13:10:06 +0000 Subject: [PATCH 09/42] add migration struct --- frame/staking/src/migrations.rs | 44 +++++++++++++++++++++++++++++---- 1 file changed, 39 insertions(+), 5 deletions(-) diff --git a/frame/staking/src/migrations.rs b/frame/staking/src/migrations.rs index bbe48a3b2a246..87c95965a572b 100644 --- a/frame/staking/src/migrations.rs +++ b/frame/staking/src/migrations.rs @@ -34,6 +34,7 @@ impl OnRuntimeUpgrade for InjectValidatorsIntoVoterList { }); } + StorageVersion::::put(Releases::V9_0_0); T::BlockWeights::get().max_block } else { log!(warn, "InjectValidatorsIntoVoterList being executed on the wrong storage version, expected Releases::V8_0_0"); @@ -44,10 +45,7 @@ impl OnRuntimeUpgrade for InjectValidatorsIntoVoterList { #[cfg(feature = "try-runtime")] fn pre_upgrade() -> Result<(), &'static str> { use frame_support::traits::OnRuntimeUpgradeHelpersExt; - frame_support::ensure!( - StorageVersion::::get() == crate::Releases::V8_0_0, - "must upgrade linearly" - ); + ensure!(StorageVersion::::get() == crate::Releases::V8_0_0, "must upgrade linearly"); let prev_count = T::VoterList::count(); Self::set_temp_storage(prev_count, "prev"); @@ -60,7 +58,43 @@ impl OnRuntimeUpgrade for InjectValidatorsIntoVoterList { let post_count = T::VoterList::count(); let prev_count = Self::get_temp_storage::("prev").unwrap(); let validators = Validators::::count(); - assert!(post_count == prev_count + validators); + ensure!(post_count == prev_count + validators, "incorrect count"); + ensure!(StorageVersion::::get(), Releases::V9_0_0, "version not set"); + Ok(()) + } +} + +/// Migration implementation that injects all validators into sorted list. +/// +/// This is only useful for chains that started their `VoterList` just based on nominators. +pub struct InjectValidatorsIntoTargetList(sp_std::marker::PhantomData); +impl OnRuntimeUpgrade for InjectValidatorsIntoTargetList { + fn on_runtime_upgrade() -> Weight { + if StorageVersion::::get() == Releases::V9_0_0 { + for (v, _) in Validators::::iter() { + let weight = Pallet::::vote_weight(&v); + let _ = T::TargetList::on_insert(v.clone(), weight).map_err(|err| { + log!(warn, "failed to insert {:?} into TargetList: {:?}", v, err) + }); + } + + StorageVersion::::put(Releases::V10_0_0); + T::BlockWeights::get().max_block + } else { + log!(warn, "InjectValidatorsIntoTargetList being executed on the wrong storage version, expected Releases::V9_0_0"); + T::DbWeight::get().reads(1) + } + } + + #[cfg(feature = "try-runtime")] + fn pre_upgrade() -> Result<(), &'static str> { + ensure!(StorageVersion::::get() == Releases::V9_0_0, "must upgrade linearly"); + Ok(()) + } + + #[cfg(feature = "try-runtime")] + fn post_upgrade() -> Result<(), &'static str> { + ensure!(StorageVersion::::get(), Releases::V10_0_0, "must upgrade linearly"); Ok(()) } } From 3e0a25dc835391942487e309f8368e1742476093 Mon Sep 17 00:00:00 2001 From: kianenigma Date: Mon, 28 Feb 2022 13:38:14 +0000 Subject: [PATCH 10/42] make node-runtime build fine --- bin/node/runtime/src/lib.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/bin/node/runtime/src/lib.rs b/bin/node/runtime/src/lib.rs index 0aff3d8046eef..d1aea6575ff67 100644 --- a/bin/node/runtime/src/lib.rs +++ b/bin/node/runtime/src/lib.rs @@ -49,6 +49,7 @@ use pallet_grandpa::{ }; use pallet_im_online::sr25519::AuthorityId as ImOnlineId; use pallet_session::historical as pallet_session_historical; +use pallet_staking::UseValidatorsMap; pub use pallet_transaction_payment::{CurrencyAdapter, Multiplier, TargetedFeeAdjustment}; use pallet_transaction_payment::{FeeDetails, RuntimeDispatchInfo}; use sp_api::impl_runtime_apis; @@ -555,9 +556,8 @@ impl pallet_staking::Config for Runtime { type OffendingValidatorsThreshold = OffendingValidatorsThreshold; type ElectionProvider = ElectionProviderMultiPhase; type GenesisElectionProvider = onchain::OnChainSequentialPhragmen; - // Alternatively, use pallet_staking::UseNominatorsMap to just use the nominators map. - // Note that the aforementioned does not scale to a very large number of nominators. - type SortedListProvider = BagsList; + type VoterList = BagsList; + type TargetList = UseValidatorsMap; type WeightInfo = pallet_staking::weights::SubstrateWeight; type BenchmarkingConfig = StakingBenchmarkingConfig; } From 5c48b57af767109920d0a87359cc5d021a9b1c2d Mon Sep 17 00:00:00 2001 From: doordashcon Date: Wed, 2 Mar 2022 00:41:01 +0100 Subject: [PATCH 11/42] make instantiable --- frame/bags-list/src/benchmarks.rs | 36 +++++----- frame/bags-list/src/lib.rs | 54 +++++++------- frame/bags-list/src/list/mod.rs | 114 +++++++++++++++--------------- frame/bags-list/src/mock.rs | 2 +- 4 files changed, 103 insertions(+), 103 deletions(-) diff --git a/frame/bags-list/src/benchmarks.rs b/frame/bags-list/src/benchmarks.rs index cc575d7d1efff..396f0071821a9 100644 --- a/frame/bags-list/src/benchmarks.rs +++ b/frame/bags-list/src/benchmarks.rs @@ -36,7 +36,7 @@ frame_benchmarking::benchmarks! { // clear any pre-existing storage. // NOTE: safe to call outside block production - List::::unsafe_clear(); + List::::unsafe_clear(); // define our origin and destination thresholds. let origin_bag_thresh = T::BagThresholds::get()[0]; @@ -44,21 +44,21 @@ frame_benchmarking::benchmarks! { // seed items in the origin bag. let origin_head: T::AccountId = account("origin_head", 0, 0); - assert_ok!(List::::insert(origin_head.clone(), origin_bag_thresh)); + assert_ok!(List::::insert(origin_head.clone(), origin_bag_thresh)); let origin_middle: T::AccountId = account("origin_middle", 0, 0); // the node we rebag (_R_) - assert_ok!(List::::insert(origin_middle.clone(), origin_bag_thresh)); + assert_ok!(List::::insert(origin_middle.clone(), origin_bag_thresh)); let origin_tail: T::AccountId = account("origin_tail", 0, 0); - assert_ok!(List::::insert(origin_tail.clone(), origin_bag_thresh)); + assert_ok!(List::::insert(origin_tail.clone(), origin_bag_thresh)); // seed items in the destination bag. let dest_head: T::AccountId = account("dest_head", 0, 0); - assert_ok!(List::::insert(dest_head.clone(), dest_bag_thresh)); + assert_ok!(List::::insert(dest_head.clone(), dest_bag_thresh)); // the bags are in the expected state after initial setup. assert_eq!( - List::::get_bags(), + List::::get_bags(), vec![ (origin_bag_thresh, vec![origin_head.clone(), origin_middle.clone(), origin_tail.clone()]), (dest_bag_thresh, vec![dest_head.clone()]) @@ -72,7 +72,7 @@ frame_benchmarking::benchmarks! { verify { // check the bags have updated as expected. assert_eq!( - List::::get_bags(), + List::::get_bags(), vec![ ( origin_bag_thresh, @@ -104,18 +104,18 @@ frame_benchmarking::benchmarks! { // seed items in the origin bag. let origin_head: T::AccountId = account("origin_head", 0, 0); - assert_ok!(List::::insert(origin_head.clone(), origin_bag_thresh)); + assert_ok!(List::::insert(origin_head.clone(), origin_bag_thresh)); let origin_tail: T::AccountId = account("origin_tail", 0, 0); // the node we rebag (_R_) - assert_ok!(List::::insert(origin_tail.clone(), origin_bag_thresh)); + assert_ok!(List::::insert(origin_tail.clone(), origin_bag_thresh)); // seed items in the destination bag. let dest_head: T::AccountId = account("dest_head", 0, 0); - assert_ok!(List::::insert(dest_head.clone(), dest_bag_thresh)); + assert_ok!(List::::insert(dest_head.clone(), dest_bag_thresh)); // the bags are in the expected state after initial setup. assert_eq!( - List::::get_bags(), + List::::get_bags(), vec![ (origin_bag_thresh, vec![origin_head.clone(), origin_tail.clone()]), (dest_bag_thresh, vec![dest_head.clone()]) @@ -129,7 +129,7 @@ frame_benchmarking::benchmarks! { verify { // check the bags have updated as expected. assert_eq!( - List::::get_bags(), + List::::get_bags(), vec![ (origin_bag_thresh, vec![origin_head.clone()]), (dest_bag_thresh, vec![dest_head.clone(), origin_tail.clone()]) @@ -147,22 +147,22 @@ frame_benchmarking::benchmarks! { // insert the nodes in order let lighter: T::AccountId = account("lighter", 0, 0); - assert_ok!(List::::insert(lighter.clone(), bag_thresh)); + assert_ok!(List::::insert(lighter.clone(), bag_thresh)); let heavier_prev: T::AccountId = account("heavier_prev", 0, 0); - assert_ok!(List::::insert(heavier_prev.clone(), bag_thresh)); + assert_ok!(List::::insert(heavier_prev.clone(), bag_thresh)); let heavier: T::AccountId = account("heavier", 0, 0); - assert_ok!(List::::insert(heavier.clone(), bag_thresh)); + assert_ok!(List::::insert(heavier.clone(), bag_thresh)); let heavier_next: T::AccountId = account("heavier_next", 0, 0); - assert_ok!(List::::insert(heavier_next.clone(), bag_thresh)); + assert_ok!(List::::insert(heavier_next.clone(), bag_thresh)); T::VoteWeightProvider::set_vote_weight_of(&lighter, bag_thresh - 1); T::VoteWeightProvider::set_vote_weight_of(&heavier, bag_thresh); assert_eq!( - List::::iter().map(|n| n.id().clone()).collect::>(), + List::::iter().map(|n| n.id().clone()).collect::>(), vec![lighter.clone(), heavier_prev.clone(), heavier.clone(), heavier_next.clone()] ); @@ -170,7 +170,7 @@ frame_benchmarking::benchmarks! { }: _(SystemOrigin::Signed(heavier.clone()), lighter.clone()) verify { assert_eq!( - List::::iter().map(|n| n.id().clone()).collect::>(), + List::::iter().map(|n| n.id().clone()).collect::>(), vec![heavier, lighter, heavier_prev, heavier_next] ) } diff --git a/frame/bags-list/src/lib.rs b/frame/bags-list/src/lib.rs index 89c54db87023f..d0384367f5ded 100644 --- a/frame/bags-list/src/lib.rs +++ b/frame/bags-list/src/lib.rs @@ -92,12 +92,12 @@ pub mod pallet { #[pallet::pallet] #[pallet::generate_store(pub(crate) trait Store)] - pub struct Pallet(_); + pub struct Pallet(_); #[pallet::config] - pub trait Config: frame_system::Config { + pub trait Config: frame_system::Config { /// The overarching event type. - type Event: From> + IsType<::Event>; + type Event: From> + IsType<::Event>; /// Weight information for extrinsics in this pallet. type WeightInfo: weights::WeightInfo; @@ -156,25 +156,25 @@ pub mod pallet { /// /// Nodes store links forward and back within their respective bags. #[pallet::storage] - pub(crate) type ListNodes = - CountedStorageMap<_, Twox64Concat, T::AccountId, list::Node>; + pub(crate) type ListNodes, I: 'static = ()> = + CountedStorageMap<_, Twox64Concat, T::AccountId, list::Node>; /// A bag stored in storage. /// /// Stores a `Bag` struct, which stores head and tail pointers to itself. #[pallet::storage] - pub(crate) type ListBags = StorageMap<_, Twox64Concat, VoteWeight, list::Bag>; + pub(crate) type ListBags, I: 'static = ()> = StorageMap<_, Twox64Concat, VoteWeight, list::Bag>; #[pallet::event] #[pallet::generate_deposit(pub(crate) fn deposit_event)] - pub enum Event { + pub enum Event, I: 'static = ()> { /// Moved an account from one bag to another. Rebagged { who: T::AccountId, from: VoteWeight, to: VoteWeight }, } #[pallet::error] #[cfg_attr(test, derive(PartialEq))] - pub enum Error { + pub enum Error { /// Attempted to place node in front of a node in another bag. NotInSameBag, /// Id not found in list. @@ -184,7 +184,7 @@ pub mod pallet { } #[pallet::call] - impl Pallet { + impl, I: 'static> Pallet { /// Declare that some `dislocated` account has, through rewards or penalties, sufficiently /// changed its weight that it should properly fall into a different bag than its current /// one. @@ -197,7 +197,7 @@ pub mod pallet { pub fn rebag(origin: OriginFor, dislocated: T::AccountId) -> DispatchResult { ensure_signed(origin)?; let current_weight = T::VoteWeightProvider::vote_weight(&dislocated); - let _ = Pallet::::do_rebag(&dislocated, current_weight); + let _ = Pallet::::do_rebag(&dislocated, current_weight); Ok(()) } @@ -212,12 +212,12 @@ pub mod pallet { #[pallet::weight(T::WeightInfo::put_in_front_of())] pub fn put_in_front_of(origin: OriginFor, lighter: T::AccountId) -> DispatchResult { let heavier = ensure_signed(origin)?; - List::::put_in_front_of(&lighter, &heavier).map_err(Into::into) + List::::put_in_front_of(&lighter, &heavier).map_err(Into::into) } } #[pallet::hooks] - impl Hooks> for Pallet { + impl, I: 'static> Hooks> for Pallet { fn integrity_test() { // ensure they are strictly increasing, this also implies that duplicates are detected. assert!( @@ -228,7 +228,7 @@ pub mod pallet { } } -impl Pallet { +impl, I: 'static> Pallet { /// Move an account from one bag to another, depositing an event on success. /// /// If the account changed bags, returns `Some((from, to))`. @@ -238,46 +238,46 @@ impl Pallet { ) -> Option<(VoteWeight, VoteWeight)> { // if no voter at that node, don't do anything. // the caller just wasted the fee to call this. - let maybe_movement = list::Node::::get(&account) + let maybe_movement = list::Node::::get(&account) .and_then(|node| List::update_position_for(node, new_weight)); if let Some((from, to)) = maybe_movement { - Self::deposit_event(Event::::Rebagged { who: account.clone(), from, to }); + Self::deposit_event(Event::::Rebagged { who: account.clone(), from, to }); }; maybe_movement } /// Equivalent to `ListBags::get`, but public. Useful for tests in outside of this crate. #[cfg(feature = "std")] - pub fn list_bags_get(weight: VoteWeight) -> Option> { + pub fn list_bags_get(weight: VoteWeight) -> Option> { ListBags::get(weight) } } -impl SortedListProvider for Pallet { +impl, I: 'static> SortedListProvider for Pallet { type Error = Error; fn iter() -> Box> { - Box::new(List::::iter().map(|n| n.id().clone())) + Box::new(List::::iter().map(|n| n.id().clone())) } fn count() -> u32 { - ListNodes::::count() + ListNodes::::count() } fn contains(id: &T::AccountId) -> bool { - List::::contains(id) + List::::contains(id) } fn on_insert(id: T::AccountId, weight: VoteWeight) -> Result<(), Error> { - List::::insert(id, weight) + List::::insert(id, weight) } fn on_update(id: &T::AccountId, new_weight: VoteWeight) { - Pallet::::do_rebag(id, new_weight); + Pallet::::do_rebag(id, new_weight); } fn on_remove(id: &T::AccountId) { - List::::remove(id) + List::::remove(id) } fn unsafe_regenerate( @@ -287,12 +287,12 @@ impl SortedListProvider for Pallet { // NOTE: This call is unsafe for the same reason as SortedListProvider::unsafe_regenerate. // I.e. because it can lead to many storage accesses. // So it is ok to call it as caller must ensure the conditions. - List::::unsafe_regenerate(all, weight_of) + List::::unsafe_regenerate(all, weight_of) } #[cfg(feature = "std")] fn sanity_check() -> Result<(), &'static str> { - List::::sanity_check() + List::::sanity_check() } #[cfg(not(feature = "std"))] @@ -304,14 +304,14 @@ impl SortedListProvider for Pallet { // NOTE: This call is unsafe for the same reason as SortedListProvider::unsafe_clear. // I.e. because it can lead to many storage accesses. // So it is ok to call it as caller must ensure the conditions. - List::::unsafe_clear() + List::::unsafe_clear() } #[cfg(feature = "runtime-benchmarks")] fn weight_update_worst_case(who: &T::AccountId, is_increase: bool) -> VoteWeight { use frame_support::traits::Get as _; let thresholds = T::BagThresholds::get(); - let node = list::Node::::get(who).unwrap(); + let node = list::Node::::get(who).unwrap(); let current_bag_idx = thresholds .iter() .chain(sp_std::iter::once(&VoteWeight::MAX)) diff --git a/frame/bags-list/src/list/mod.rs b/frame/bags-list/src/list/mod.rs index 8cd89b8fff1cc..a6da25ae6be16 100644 --- a/frame/bags-list/src/list/mod.rs +++ b/frame/bags-list/src/list/mod.rs @@ -53,7 +53,7 @@ mod tests; /// /// Note that even if the thresholds list does not have `VoteWeight::MAX` as its final member, this /// function behaves as if it does. -pub fn notional_bag_for(weight: VoteWeight) -> VoteWeight { +pub fn notional_bag_for, I: 'static>(weight: VoteWeight) -> VoteWeight { let thresholds = T::BagThresholds::get(); let idx = thresholds.partition_point(|&threshold| weight > threshold); thresholds.get(idx).copied().unwrap_or(VoteWeight::MAX) @@ -73,9 +73,9 @@ pub fn notional_bag_for(weight: VoteWeight) -> VoteWeight { // weight decreases as successive bags are reached. This means that it is valid to truncate // iteration at any desired point; only those ids in the lowest bag can be excluded. This // satisfies both the desire for fairness and the requirement for efficiency. -pub struct List(PhantomData); +pub struct List, I: 'static = ()>(PhantomData); -impl List { +impl, I: 'static> List { /// Remove all data associated with the list from storage. /// /// ## WARNING @@ -83,8 +83,8 @@ impl List { /// this function should generally not be used in production as it could lead to a very large /// number of storage accesses. pub(crate) fn unsafe_clear() { - crate::ListBags::::remove_all(None); - crate::ListNodes::::remove_all(); + crate::ListBags::::remove_all(None); + crate::ListNodes::::remove_all(); } /// Regenerate all of the data from the given ids. @@ -135,11 +135,11 @@ impl List { // we can't check all preconditions, but we can check one debug_assert!( - crate::ListBags::::iter().all(|(threshold, _)| old_thresholds.contains(&threshold)), + crate::ListBags::::iter().all(|(threshold, _)| old_thresholds.contains(&threshold)), "not all `bag_upper` currently in storage are members of `old_thresholds`", ); debug_assert!( - crate::ListNodes::::iter().all(|(_, node)| old_thresholds.contains(&node.bag_upper)), + crate::ListNodes::::iter().all(|(_, node)| old_thresholds.contains(&node.bag_upper)), "not all `node.bag_upper` currently in storage are members of `old_thresholds`", ); @@ -166,7 +166,7 @@ impl List { continue } - if let Some(bag) = Bag::::get(affected_bag) { + if let Some(bag) = Bag::::get(affected_bag) { affected_accounts.extend(bag.iter().map(|node| node.id)); } } @@ -178,7 +178,7 @@ impl List { continue } - if let Some(bag) = Bag::::get(removed_bag) { + if let Some(bag) = Bag::::get(removed_bag) { affected_accounts.extend(bag.iter().map(|node| node.id)); } } @@ -199,10 +199,10 @@ impl List { // lookups. for removed_bag in removed_bags { debug_assert!( - !crate::ListNodes::::iter().any(|(_, node)| node.bag_upper == removed_bag), + !crate::ListNodes::::iter().any(|(_, node)| node.bag_upper == removed_bag), "no id should be present in a removed bag", ); - crate::ListBags::::remove(removed_bag); + crate::ListBags::::remove(removed_bag); } debug_assert_eq!(Self::sanity_check(), Ok(())); @@ -212,14 +212,14 @@ impl List { /// Returns `true` if the list contains `id`, otherwise returns `false`. pub(crate) fn contains(id: &T::AccountId) -> bool { - crate::ListNodes::::contains_key(id) + crate::ListNodes::::contains_key(id) } /// Iterate over all nodes in all bags in the list. /// /// Full iteration can be expensive; it's recommended to limit the number of items with /// `.take(n)`. - pub(crate) fn iter() -> impl Iterator> { + pub(crate) fn iter() -> impl Iterator> { // We need a touch of special handling here: because we permit `T::BagThresholds` to // omit the final bound, we need to ensure that we explicitly include that threshold in the // list. @@ -266,8 +266,8 @@ impl List { return Err(Error::Duplicate) } - let bag_weight = notional_bag_for::(weight); - let mut bag = Bag::::get_or_make(bag_weight); + let bag_weight = notional_bag_for::(weight); + let mut bag = Bag::::get_or_make(bag_weight); // unchecked insertion is okay; we just got the correct `notional_bag_for`. bag.insert_unchecked(id.clone()); @@ -280,7 +280,7 @@ impl List { id, weight, bag_weight, - crate::ListNodes::::count(), + crate::ListNodes::::count(), ); Ok(()) @@ -301,7 +301,7 @@ impl List { let mut count = 0; for id in ids.into_iter() { - let node = match Node::::get(id) { + let node = match Node::::get(id) { Some(node) => node, None => continue, }; @@ -314,7 +314,7 @@ impl List { // this node is a head or tail, so the bag needs to be updated let bag = bags .entry(node.bag_upper) - .or_insert_with(|| Bag::::get_or_make(node.bag_upper)); + .or_insert_with(|| Bag::::get_or_make(node.bag_upper)); // node.bag_upper must be correct, therefore this bag will contain this node. bag.remove_node_unchecked(&node); } @@ -341,7 +341,7 @@ impl List { /// [`self.insert`]. However, given large quantities of nodes to move, it may be more efficient /// to call [`self.remove_many`] followed by [`self.insert_many`]. pub(crate) fn update_position_for( - node: Node, + node: Node, new_weight: VoteWeight, ) -> Option<(VoteWeight, VoteWeight)> { node.is_misplaced(new_weight).then(move || { @@ -351,7 +351,7 @@ impl List { // this node is not a head or a tail, so we can just cut it out of the list. update // and put the prev and next of this node, we do `node.put` inside `insert_note`. node.excise(); - } else if let Some(mut bag) = Bag::::get(node.bag_upper) { + } else if let Some(mut bag) = Bag::::get(node.bag_upper) { // this is a head or tail, so the bag must be updated. bag.remove_node_unchecked(&node); bag.put(); @@ -365,8 +365,8 @@ impl List { } // put the node into the appropriate new bag. - let new_bag_upper = notional_bag_for::(new_weight); - let mut bag = Bag::::get_or_make(new_bag_upper); + let new_bag_upper = notional_bag_for::(new_weight); + let mut bag = Bag::::get_or_make(new_bag_upper); // prev, next, and bag_upper of the node are updated inside `insert_node`, also // `node.put` is in there. bag.insert_node_unchecked(node); @@ -381,12 +381,12 @@ impl List { pub(crate) fn put_in_front_of( lighter_id: &T::AccountId, heavier_id: &T::AccountId, - ) -> Result<(), crate::pallet::Error> { + ) -> Result<(), crate::pallet::Error> { use crate::pallet; use frame_support::ensure; - let lighter_node = Node::::get(&lighter_id).ok_or(pallet::Error::IdNotFound)?; - let heavier_node = Node::::get(&heavier_id).ok_or(pallet::Error::IdNotFound)?; + let lighter_node = Node::::get(&lighter_id).ok_or(pallet::Error::IdNotFound)?; + let heavier_node = Node::::get(&heavier_id).ok_or(pallet::Error::IdNotFound)?; ensure!(lighter_node.bag_upper == heavier_node.bag_upper, pallet::Error::NotInSameBag); @@ -403,7 +403,7 @@ impl List { // re-fetch `lighter_node` from storage since it may have been updated when `heavier_node` // was removed. - let lighter_node = Node::::get(&lighter_id).ok_or_else(|| { + let lighter_node = Node::::get(&lighter_id).ok_or_else(|| { debug_assert!(false, "id that should exist cannot be found"); crate::log!(warn, "id that should exist cannot be found"); pallet::Error::IdNotFound @@ -422,7 +422,7 @@ impl List { /// - this is a naive function in that it does not check if `node` belongs to the same bag as /// `at`. It is expected that the call site will check preconditions. /// - this will panic if `at.bag_upper` is not a bag that already exists in storage. - fn insert_at_unchecked(mut at: Node, mut node: Node) { + fn insert_at_unchecked(mut at: Node, mut node: Node) { // connect `node` to its new `prev`. node.prev = at.prev.clone(); if let Some(mut prev) = at.prev() { @@ -439,7 +439,7 @@ impl List { // since `node` is always in front of `at` we know that 1) there is always at least 2 // nodes in the bag, and 2) only `node` could be the head and only `at` could be the // tail. - let mut bag = Bag::::get(at.bag_upper) + let mut bag = Bag::::get(at.bag_upper) .expect("given nodes must always have a valid bag. qed."); if node.prev == None { @@ -473,8 +473,8 @@ impl List { ); let iter_count = Self::iter().count() as u32; - let stored_count = crate::ListNodes::::count(); - let nodes_count = crate::ListNodes::::iter().count() as u32; + let stored_count = crate::ListNodes::::count(); + let nodes_count = crate::ListNodes::::iter().count() as u32; ensure!(iter_count == stored_count, "iter_count != stored_count"); ensure!(stored_count == nodes_count, "stored_count != nodes_count"); @@ -489,7 +489,7 @@ impl List { // otherwise, insert it here. thresholds.chain(iter::once(VoteWeight::MAX)).collect() }; - thresholds.into_iter().filter_map(|t| Bag::::get(t)) + thresholds.into_iter().filter_map(|t| Bag::::get(t)) }; let _ = active_bags.clone().map(|b| b.sanity_check()).collect::>()?; @@ -502,7 +502,7 @@ impl List { // check that all nodes are sane. We check the `ListNodes` storage item directly in case we // have some "stale" nodes that are not in a bag. - for (_id, node) in crate::ListNodes::::iter() { + for (_id, node) in crate::ListNodes::::iter() { node.sanity_check()? } @@ -531,7 +531,7 @@ impl List { }; iter.filter_map(|t| { - Bag::::get(t).map(|bag| (t, bag.iter().map(|n| n.id().clone()).collect::>())) + Bag::::get(t).map(|bag| (t, bag.iter().map(|n| n.id().clone()).collect::>())) }) .collect::>() } @@ -548,7 +548,7 @@ impl List { #[codec(mel_bound())] #[scale_info(skip_type_params(T))] #[cfg_attr(feature = "std", derive(frame_support::DebugNoBound, Clone, PartialEq))] -pub struct Bag { +pub struct Bag, I: 'static = ()> { head: Option, tail: Option, @@ -556,7 +556,7 @@ pub struct Bag { bag_upper: VoteWeight, } -impl Bag { +impl, I: 'static> Bag { #[cfg(test)] pub(crate) fn new( head: Option, @@ -567,8 +567,8 @@ impl Bag { } /// Get a bag by its upper vote weight. - pub(crate) fn get(bag_upper: VoteWeight) -> Option> { - crate::ListBags::::try_get(bag_upper).ok().map(|mut bag| { + pub(crate) fn get(bag_upper: VoteWeight) -> Option> { + crate::ListBags::::try_get(bag_upper).ok().map(|mut bag| { bag.bag_upper = bag_upper; bag }) @@ -576,7 +576,7 @@ impl Bag { /// Get a bag by its upper vote weight or make it, appropriately initialized. Does not check if /// if `bag_upper` is a valid threshold. - fn get_or_make(bag_upper: VoteWeight) -> Bag { + fn get_or_make(bag_upper: VoteWeight) -> Bag { Self::get(bag_upper).unwrap_or(Bag { bag_upper, ..Default::default() }) } @@ -588,24 +588,24 @@ impl Bag { /// Put the bag back into storage. fn put(self) { if self.is_empty() { - crate::ListBags::::remove(self.bag_upper); + crate::ListBags::::remove(self.bag_upper); } else { - crate::ListBags::::insert(self.bag_upper, self); + crate::ListBags::::insert(self.bag_upper, self); } } /// Get the head node in this bag. - fn head(&self) -> Option> { + fn head(&self) -> Option> { self.head.as_ref().and_then(|id| Node::get(id)) } /// Get the tail node in this bag. - fn tail(&self) -> Option> { + fn tail(&self) -> Option> { self.tail.as_ref().and_then(|id| Node::get(id)) } /// Iterate over the nodes in this bag. - pub(crate) fn iter(&self) -> impl Iterator> { + pub(crate) fn iter(&self) -> impl Iterator> { sp_std::iter::successors(self.head(), |prev| prev.next()) } @@ -620,7 +620,7 @@ impl Bag { // insert_node will overwrite `prev`, `next` and `bag_upper` to the proper values. As long // as this bag is the correct one, we're good. All calls to this must come after getting the // correct [`notional_bag_for`]. - self.insert_node_unchecked(Node:: { id, prev: None, next: None, bag_upper: 0 }); + self.insert_node_unchecked(Node:: { id, prev: None, next: None, bag_upper: 0 }); } /// Insert a node into this bag. @@ -630,7 +630,7 @@ impl Bag { /// /// Storage note: this modifies storage, but only for the node. You still need to call /// `self.put()` after use. - fn insert_node_unchecked(&mut self, mut node: Node) { + fn insert_node_unchecked(&mut self, mut node: Node) { if let Some(tail) = &self.tail { if *tail == node.id { // this should never happen, but this check prevents one path to a worst case @@ -674,7 +674,7 @@ impl Bag { /// /// Storage note: this modifies storage, but only for adjacent nodes. You still need to call /// `self.put()` and `ListNodes::remove(id)` to update storage for the bag and `node`. - fn remove_node_unchecked(&mut self, node: &Node) { + fn remove_node_unchecked(&mut self, node: &Node) { // reassign neighboring nodes. node.excise(); @@ -735,7 +735,7 @@ impl Bag { /// Iterate over the nodes in this bag (public for tests). #[cfg(feature = "std")] #[allow(dead_code)] - pub fn std_iter(&self) -> impl Iterator> { + pub fn std_iter(&self) -> impl Iterator> { sp_std::iter::successors(self.head(), |prev| prev.next()) } @@ -751,22 +751,22 @@ impl Bag { #[codec(mel_bound())] #[scale_info(skip_type_params(T))] #[cfg_attr(feature = "std", derive(frame_support::DebugNoBound, Clone, PartialEq))] -pub struct Node { +pub struct Node, I: 'static = ()> { id: T::AccountId, prev: Option, next: Option, bag_upper: VoteWeight, } -impl Node { +impl, I: 'static> Node { /// Get a node by id. - pub fn get(id: &T::AccountId) -> Option> { - crate::ListNodes::::try_get(id).ok() + pub fn get(id: &T::AccountId) -> Option> { + crate::ListNodes::::try_get(id).ok() } /// Put the node back into storage. fn put(self) { - crate::ListNodes::::insert(self.id.clone(), self); + crate::ListNodes::::insert(self.id.clone(), self); } /// Update neighboring nodes to point to reach other. @@ -790,22 +790,22 @@ impl Node { /// /// It is naive because it does not check if the node has first been removed from its bag. fn remove_from_storage_unchecked(&self) { - crate::ListNodes::::remove(&self.id) + crate::ListNodes::::remove(&self.id) } /// Get the previous node in the bag. - fn prev(&self) -> Option> { + fn prev(&self) -> Option> { self.prev.as_ref().and_then(|id| Node::get(id)) } /// Get the next node in the bag. - fn next(&self) -> Option> { + fn next(&self) -> Option> { self.next.as_ref().and_then(|id| Node::get(id)) } /// `true` when this voter is in the wrong bag. pub fn is_misplaced(&self, current_weight: VoteWeight) -> bool { - notional_bag_for::(current_weight) != self.bag_upper + notional_bag_for::(current_weight) != self.bag_upper } /// `true` when this voter is a bag head or tail. @@ -834,7 +834,7 @@ impl Node { #[cfg(feature = "std")] fn sanity_check(&self) -> Result<(), &'static str> { - let expected_bag = Bag::::get(self.bag_upper).ok_or("bag not found for node")?; + let expected_bag = Bag::::get(self.bag_upper).ok_or("bag not found for node")?; let id = self.id(); diff --git a/frame/bags-list/src/mock.rs b/frame/bags-list/src/mock.rs index aa3f549e12dec..14b161f1bc25c 100644 --- a/frame/bags-list/src/mock.rs +++ b/frame/bags-list/src/mock.rs @@ -91,7 +91,7 @@ frame_support::construct_runtime!( UncheckedExtrinsic = UncheckedExtrinsic, { System: frame_system::{Pallet, Call, Storage, Event, Config}, - BagsList: bags_list::{Pallet, Call, Storage, Event}, + BagsList: bags_list::{Pallet, Call, Storage, Event}, } ); From 90d09f243d32557929f1d939f359bf01fd9223db Mon Sep 17 00:00:00 2001 From: doordashcon Date: Fri, 4 Mar 2022 20:05:37 +0100 Subject: [PATCH 12/42] update --- frame/bags-list/remote-tests/src/lib.rs | 2 +- frame/bags-list/src/benchmarks.rs | 36 ++++++------- frame/bags-list/src/list/mod.rs | 13 +++-- frame/bags-list/src/list/tests.rs | 71 +++++++++++++------------ frame/bags-list/src/mock.rs | 2 +- frame/bags-list/src/tests.rs | 11 ++-- 6 files changed, 70 insertions(+), 65 deletions(-) diff --git a/frame/bags-list/remote-tests/src/lib.rs b/frame/bags-list/remote-tests/src/lib.rs index 9a88ff24f2f3b..32686a63858ce 100644 --- a/frame/bags-list/remote-tests/src/lib.rs +++ b/frame/bags-list/remote-tests/src/lib.rs @@ -92,7 +92,7 @@ pub fn display_and_check_bags(currency_unit: u64, currency_na "Account {:?} can be rebagged from {:?} to {:?}", id, vote_weight_thresh_as_unit, - pallet_bags_list::notional_bag_for::(vote_weight) as f64 / + pallet_bags_list::notional_bag_for::(vote_weight) as f64 / currency_unit as f64 ); } diff --git a/frame/bags-list/src/benchmarks.rs b/frame/bags-list/src/benchmarks.rs index 396f0071821a9..2d89a0027eca2 100644 --- a/frame/bags-list/src/benchmarks.rs +++ b/frame/bags-list/src/benchmarks.rs @@ -36,7 +36,7 @@ frame_benchmarking::benchmarks! { // clear any pre-existing storage. // NOTE: safe to call outside block production - List::::unsafe_clear(); + List::::unsafe_clear(); // define our origin and destination thresholds. let origin_bag_thresh = T::BagThresholds::get()[0]; @@ -44,21 +44,21 @@ frame_benchmarking::benchmarks! { // seed items in the origin bag. let origin_head: T::AccountId = account("origin_head", 0, 0); - assert_ok!(List::::insert(origin_head.clone(), origin_bag_thresh)); + assert_ok!(List::::insert(origin_head.clone(), origin_bag_thresh)); let origin_middle: T::AccountId = account("origin_middle", 0, 0); // the node we rebag (_R_) - assert_ok!(List::::insert(origin_middle.clone(), origin_bag_thresh)); + assert_ok!(List::::insert(origin_middle.clone(), origin_bag_thresh)); let origin_tail: T::AccountId = account("origin_tail", 0, 0); - assert_ok!(List::::insert(origin_tail.clone(), origin_bag_thresh)); + assert_ok!(List::::insert(origin_tail.clone(), origin_bag_thresh)); // seed items in the destination bag. let dest_head: T::AccountId = account("dest_head", 0, 0); - assert_ok!(List::::insert(dest_head.clone(), dest_bag_thresh)); + assert_ok!(List::::insert(dest_head.clone(), dest_bag_thresh)); // the bags are in the expected state after initial setup. assert_eq!( - List::::get_bags(), + List::::get_bags(), vec![ (origin_bag_thresh, vec![origin_head.clone(), origin_middle.clone(), origin_tail.clone()]), (dest_bag_thresh, vec![dest_head.clone()]) @@ -72,7 +72,7 @@ frame_benchmarking::benchmarks! { verify { // check the bags have updated as expected. assert_eq!( - List::::get_bags(), + List::::get_bags(), vec![ ( origin_bag_thresh, @@ -104,18 +104,18 @@ frame_benchmarking::benchmarks! { // seed items in the origin bag. let origin_head: T::AccountId = account("origin_head", 0, 0); - assert_ok!(List::::insert(origin_head.clone(), origin_bag_thresh)); + assert_ok!(List::::insert(origin_head.clone(), origin_bag_thresh)); let origin_tail: T::AccountId = account("origin_tail", 0, 0); // the node we rebag (_R_) - assert_ok!(List::::insert(origin_tail.clone(), origin_bag_thresh)); + assert_ok!(List::::insert(origin_tail.clone(), origin_bag_thresh)); // seed items in the destination bag. let dest_head: T::AccountId = account("dest_head", 0, 0); - assert_ok!(List::::insert(dest_head.clone(), dest_bag_thresh)); + assert_ok!(List::::insert(dest_head.clone(), dest_bag_thresh)); // the bags are in the expected state after initial setup. assert_eq!( - List::::get_bags(), + List::::get_bags(), vec![ (origin_bag_thresh, vec![origin_head.clone(), origin_tail.clone()]), (dest_bag_thresh, vec![dest_head.clone()]) @@ -129,7 +129,7 @@ frame_benchmarking::benchmarks! { verify { // check the bags have updated as expected. assert_eq!( - List::::get_bags(), + List::::get_bags(), vec![ (origin_bag_thresh, vec![origin_head.clone()]), (dest_bag_thresh, vec![dest_head.clone(), origin_tail.clone()]) @@ -147,22 +147,22 @@ frame_benchmarking::benchmarks! { // insert the nodes in order let lighter: T::AccountId = account("lighter", 0, 0); - assert_ok!(List::::insert(lighter.clone(), bag_thresh)); + assert_ok!(List::::insert(lighter.clone(), bag_thresh)); let heavier_prev: T::AccountId = account("heavier_prev", 0, 0); - assert_ok!(List::::insert(heavier_prev.clone(), bag_thresh)); + assert_ok!(List::::insert(heavier_prev.clone(), bag_thresh)); let heavier: T::AccountId = account("heavier", 0, 0); - assert_ok!(List::::insert(heavier.clone(), bag_thresh)); + assert_ok!(List::::insert(heavier.clone(), bag_thresh)); let heavier_next: T::AccountId = account("heavier_next", 0, 0); - assert_ok!(List::::insert(heavier_next.clone(), bag_thresh)); + assert_ok!(List::::insert(heavier_next.clone(), bag_thresh)); T::VoteWeightProvider::set_vote_weight_of(&lighter, bag_thresh - 1); T::VoteWeightProvider::set_vote_weight_of(&heavier, bag_thresh); assert_eq!( - List::::iter().map(|n| n.id().clone()).collect::>(), + List::::iter().map(|n| n.id().clone()).collect::>(), vec![lighter.clone(), heavier_prev.clone(), heavier.clone(), heavier_next.clone()] ); @@ -170,7 +170,7 @@ frame_benchmarking::benchmarks! { }: _(SystemOrigin::Signed(heavier.clone()), lighter.clone()) verify { assert_eq!( - List::::iter().map(|n| n.id().clone()).collect::>(), + List::::iter().map(|n| n.id().clone()).collect::>(), vec![heavier, lighter, heavier_prev, heavier_next] ) } diff --git a/frame/bags-list/src/list/mod.rs b/frame/bags-list/src/list/mod.rs index a6da25ae6be16..0c2bdd21e0c30 100644 --- a/frame/bags-list/src/list/mod.rs +++ b/frame/bags-list/src/list/mod.rs @@ -73,7 +73,7 @@ pub fn notional_bag_for, I: 'static>(weight: VoteWeight) -> VoteWei // weight decreases as successive bags are reached. This means that it is valid to truncate // iteration at any desired point; only those ids in the lowest bag can be excluded. This // satisfies both the desire for fairness and the requirement for efficiency. -pub struct List, I: 'static = ()>(PhantomData); +pub struct List, I: 'static = ()>(PhantomData<(T, I)>); impl, I: 'static> List { /// Remove all data associated with the list from storage. @@ -546,9 +546,10 @@ impl, I: 'static> List { /// appearing within the ids set. #[derive(DefaultNoBound, Encode, Decode, MaxEncodedLen, TypeInfo)] #[codec(mel_bound())] -#[scale_info(skip_type_params(T))] +#[scale_info(skip_type_params(T, I))] #[cfg_attr(feature = "std", derive(frame_support::DebugNoBound, Clone, PartialEq))] pub struct Bag, I: 'static = ()> { + phantom: PhantomData, head: Option, tail: Option, @@ -559,11 +560,12 @@ pub struct Bag, I: 'static = ()> { impl, I: 'static> Bag { #[cfg(test)] pub(crate) fn new( + phantom: PhantomData, head: Option, tail: Option, bag_upper: VoteWeight, ) -> Self { - Self { head, tail, bag_upper } + Self { phantom, head, tail, bag_upper } } /// Get a bag by its upper vote weight. @@ -620,7 +622,7 @@ impl, I: 'static> Bag { // insert_node will overwrite `prev`, `next` and `bag_upper` to the proper values. As long // as this bag is the correct one, we're good. All calls to this must come after getting the // correct [`notional_bag_for`]. - self.insert_node_unchecked(Node:: { id, prev: None, next: None, bag_upper: 0 }); + self.insert_node_unchecked(Node:: { phantom: Default::default(), id, prev: None, next: None, bag_upper: 0 }); } /// Insert a node into this bag. @@ -749,9 +751,10 @@ impl, I: 'static> Bag { /// A Node is the fundamental element comprising the doubly-linked list described by `Bag`. #[derive(Encode, Decode, MaxEncodedLen, TypeInfo)] #[codec(mel_bound())] -#[scale_info(skip_type_params(T))] +#[scale_info(skip_type_params(T, I))] #[cfg_attr(feature = "std", derive(frame_support::DebugNoBound, Clone, PartialEq))] pub struct Node, I: 'static = ()> { + phantom: PhantomData, id: T::AccountId, prev: Option, next: Option, diff --git a/frame/bags-list/src/list/tests.rs b/frame/bags-list/src/list/tests.rs index aaa215b0af1ca..08ea07c4902f8 100644 --- a/frame/bags-list/src/list/tests.rs +++ b/frame/bags-list/src/list/tests.rs @@ -27,7 +27,7 @@ use frame_support::{assert_ok, assert_storage_noop}; fn basic_setup_works() { ExtBuilder::default().build_and_execute(|| { // syntactic sugar to create a raw node - let node = |id, prev, next, bag_upper| Node:: { id, prev, next, bag_upper }; + let node = |phantom, id, prev, next, bag_upper| Node:: { phantom, id, prev, next, bag_upper }; assert_eq!(ListNodes::::count(), 4); assert_eq!(ListNodes::::iter().count(), 4); @@ -38,17 +38,17 @@ fn basic_setup_works() { // the state of the bags is as expected assert_eq!( ListBags::::get(10).unwrap(), - Bag:: { head: Some(1), tail: Some(1), bag_upper: 0 } + Bag:: { phantom: PhantomData, head: Some(1), tail: Some(1), bag_upper: 0 } ); assert_eq!( ListBags::::get(1_000).unwrap(), - Bag:: { head: Some(2), tail: Some(4), bag_upper: 0 } + Bag:: { phantom: PhantomData, head: Some(2), tail: Some(4), bag_upper: 0 } ); - assert_eq!(ListNodes::::get(2).unwrap(), node(2, None, Some(3), 1_000)); - assert_eq!(ListNodes::::get(3).unwrap(), node(3, Some(2), Some(4), 1_000)); - assert_eq!(ListNodes::::get(4).unwrap(), node(4, Some(3), None, 1_000)); - assert_eq!(ListNodes::::get(1).unwrap(), node(1, None, None, 10)); + assert_eq!(ListNodes::::get(2).unwrap(), node(PhantomData, 2, None, Some(3), 1_000)); + assert_eq!(ListNodes::::get(3).unwrap(), node(PhantomData, 3, Some(2), Some(4), 1_000)); + assert_eq!(ListNodes::::get(4).unwrap(), node(PhantomData, 4, Some(3), None, 1_000)); + assert_eq!(ListNodes::::get(1).unwrap(), node(PhantomData, 1, None, None, 10)); // non-existent id does not have a storage footprint assert_eq!(ListNodes::::get(42), None); @@ -65,14 +65,14 @@ fn basic_setup_works() { #[test] fn notional_bag_for_works() { // under a threshold gives the next threshold. - assert_eq!(notional_bag_for::(0), 10); - assert_eq!(notional_bag_for::(9), 10); + assert_eq!(notional_bag_for::(0), 10); + assert_eq!(notional_bag_for::(9), 10); // at a threshold gives that threshold. - assert_eq!(notional_bag_for::(10), 10); + assert_eq!(notional_bag_for::(10), 10); // above the threshold, gives the next threshold. - assert_eq!(notional_bag_for::(11), 20); + assert_eq!(notional_bag_for::(11), 20); let max_explicit_threshold = *::BagThresholds::get().last().unwrap(); assert_eq!(max_explicit_threshold, 10_000); @@ -81,8 +81,8 @@ fn notional_bag_for_works() { assert!(VoteWeight::MAX > max_explicit_threshold); // then anything above it will belong to the VoteWeight::MAX bag. - assert_eq!(notional_bag_for::(max_explicit_threshold), max_explicit_threshold); - assert_eq!(notional_bag_for::(max_explicit_threshold + 1), VoteWeight::MAX); + assert_eq!(notional_bag_for::(max_explicit_threshold), max_explicit_threshold); + assert_eq!(notional_bag_for::(max_explicit_threshold + 1), VoteWeight::MAX); } #[test] @@ -388,8 +388,8 @@ mod list { #[should_panic = "given nodes must always have a valid bag. qed."] fn put_in_front_of_panics_if_bag_not_found() { ExtBuilder::default().skip_genesis_ids().build_and_execute_no_post_check(|| { - let node_10_no_bag = Node:: { id: 10, prev: None, next: None, bag_upper: 15 }; - let node_11_no_bag = Node:: { id: 11, prev: None, next: None, bag_upper: 15 }; + let node_10_no_bag = Node:: { phantom: PhantomData, id: 10, prev: None, next: None, bag_upper: 15 }; + let node_11_no_bag = Node:: { phantom: PhantomData, id: 11, prev: None, next: None, bag_upper: 15 }; // given ListNodes::::insert(10, node_10_no_bag); @@ -415,7 +415,7 @@ mod list { // implicitly also test that `node`'s `prev`/`next` are correctly re-assigned. let node_42 = - Node:: { id: 42, prev: Some(1), next: Some(2), bag_upper: 1_000 }; + Node:: { phantom: PhantomData, id: 42, prev: Some(1), next: Some(2), bag_upper: 1_000 }; assert!(!crate::ListNodes::::contains_key(42)); let node_1 = crate::ListNodes::::get(&1).unwrap(); @@ -438,7 +438,7 @@ mod list { assert_eq!(List::::get_bags(), vec![(10, vec![1]), (1_000, vec![2, 3, 4])]); // implicitly also test that `node`'s `prev`/`next` are correctly re-assigned. - let node_42 = Node:: { id: 42, prev: Some(4), next: None, bag_upper: 1_000 }; + let node_42 = Node:: { phantom: PhantomData, id: 42, prev: Some(4), next: None, bag_upper: 1_000 }; assert!(!crate::ListNodes::::contains_key(42)); let node_2 = crate::ListNodes::::get(&2).unwrap(); @@ -461,7 +461,7 @@ mod list { assert_eq!(List::::get_bags(), vec![(10, vec![1]), (1_000, vec![2, 3, 4])]); // implicitly also test that `node`'s `prev`/`next` are correctly re-assigned. - let node_42 = Node:: { id: 42, prev: None, next: Some(2), bag_upper: 1_000 }; + let node_42 = Node:: { phantom: PhantomData, id: 42, prev: None, next: Some(2), bag_upper: 1_000 }; assert!(!crate::ListNodes::::contains_key(42)); let node_3 = crate::ListNodes::::get(&3).unwrap(); @@ -485,7 +485,7 @@ mod list { // implicitly also test that `node`'s `prev`/`next` are correctly re-assigned. let node_42 = - Node:: { id: 42, prev: Some(42), next: Some(42), bag_upper: 1_000 }; + Node:: { phantom: PhantomData, id: 42, prev: Some(42), next: Some(42), bag_upper: 1_000 }; assert!(!crate::ListNodes::::contains_key(42)); let node_4 = crate::ListNodes::::get(&4).unwrap(); @@ -512,7 +512,7 @@ mod bags { let bag = Bag::::get(bag_upper).unwrap(); let bag_ids = bag.iter().map(|n| *n.id()).collect::>(); - assert_eq!(bag, Bag:: { head, tail, bag_upper }); + assert_eq!(bag, Bag:: { phantom: PhantomData, head, tail, bag_upper }); assert_eq!(bag_ids, ids); }; @@ -543,7 +543,7 @@ mod bags { #[test] fn insert_node_sets_proper_bag() { ExtBuilder::default().build_and_execute_no_post_check(|| { - let node = |id, bag_upper| Node:: { id, prev: None, next: None, bag_upper }; + let node = |id, bag_upper| Node:: { phantom: PhantomData, id, prev: None, next: None, bag_upper }; assert_eq!(List::::get_bags(), vec![(10, vec![1]), (1_000, vec![2, 3, 4])]); @@ -552,7 +552,7 @@ mod bags { assert_eq!( ListNodes::::get(&42).unwrap(), - Node { bag_upper: 10, prev: Some(1), next: None, id: 42 } + Node { phantom: PhantomData, bag_upper: 10, prev: Some(1), next: None, id: 42 } ); }); } @@ -560,7 +560,7 @@ mod bags { #[test] fn insert_node_happy_paths_works() { ExtBuilder::default().build_and_execute_no_post_check(|| { - let node = |id, bag_upper| Node:: { id, prev: None, next: None, bag_upper }; + let node = |id, bag_upper| Node:: { phantom: PhantomData, id, prev: None, next: None, bag_upper }; // when inserting into a bag with 1 node let mut bag_10 = Bag::::get(10).unwrap(); @@ -582,14 +582,14 @@ mod bags { // when inserting a node pointing to the accounts not in the bag let node_61 = - Node:: { id: 61, prev: Some(21), next: Some(101), bag_upper: 20 }; + Node:: { phantom: PhantomData, id: 61, prev: Some(21), next: Some(101), bag_upper: 20 }; bag_20.insert_node_unchecked(node_61); // then ids are in order assert_eq!(bag_as_ids(&bag_20), vec![62, 61]); // and when the node is re-fetched all the info is correct assert_eq!( Node::::get(&61).unwrap(), - Node:: { id: 61, prev: Some(62), next: None, bag_upper: 20 } + Node:: { phantom: PhantomData, id: 61, prev: Some(62), next: None, bag_upper: 20 } ); // state of all bags is as expected @@ -604,12 +604,12 @@ mod bags { // Document improper ways `insert_node` may be getting used. #[test] fn insert_node_bad_paths_documented() { - let node = |id, prev, next, bag_upper| Node:: { id, prev, next, bag_upper }; + let node = | phantom, id, prev, next, bag_upper| Node:: { phantom, id, prev, next, bag_upper }; ExtBuilder::default().build_and_execute_no_post_check(|| { // when inserting a node with both prev & next pointing at an account in an incorrect // bag. let mut bag_1000 = Bag::::get(1_000).unwrap(); - bag_1000.insert_node_unchecked(node(42, Some(1), Some(1), 500)); + bag_1000.insert_node_unchecked(node(PhantomData, 42, Some(1), Some(1), 500)); // then the proper prev and next is set. assert_eq!(bag_as_ids(&bag_1000), vec![2, 3, 4, 42]); @@ -617,7 +617,7 @@ mod bags { // and when the node is re-fetched all the info is correct assert_eq!( Node::::get(&42).unwrap(), - node(42, Some(4), None, bag_1000.bag_upper) + node(PhantomData, 42, Some(4), None, bag_1000.bag_upper) ); }); @@ -627,7 +627,7 @@ mod bags { assert_eq!(bag_as_ids(&bag_1000), vec![2, 3, 4]); // when inserting a node with duplicate id 3 - bag_1000.insert_node_unchecked(node(3, None, None, bag_1000.bag_upper)); + bag_1000.insert_node_unchecked(node(PhantomData, 3, None, None, bag_1000.bag_upper)); // then all the nodes after the duplicate are lost (because it is set as the tail) assert_eq!(bag_as_ids(&bag_1000), vec![2, 3]); @@ -637,7 +637,7 @@ mod bags { // and the last accessible node has an **incorrect** prev pointer. assert_eq!( Node::::get(&3).unwrap(), - node(3, Some(4), None, bag_1000.bag_upper) + node(PhantomData, 3, Some(4), None, bag_1000.bag_upper) ); }); @@ -645,7 +645,7 @@ mod bags { // when inserting a duplicate id of the head let mut bag_1000 = Bag::::get(1_000).unwrap(); assert_eq!(bag_as_ids(&bag_1000), vec![2, 3, 4]); - bag_1000.insert_node_unchecked(node(2, None, None, 0)); + bag_1000.insert_node_unchecked(node(PhantomData, 2, None, None, 0)); // then all nodes after the head are lost assert_eq!(bag_as_ids(&bag_1000), vec![2]); @@ -653,11 +653,11 @@ mod bags { // and the re-fetched node has bad pointers assert_eq!( Node::::get(&2).unwrap(), - node(2, Some(4), None, bag_1000.bag_upper) + node(PhantomData, 2, Some(4), None, bag_1000.bag_upper) ); // ^^^ despite being the bags head, it has a prev - assert_eq!(bag_1000, Bag { head: Some(2), tail: Some(2), bag_upper: 1_000 }) + assert_eq!(bag_1000, Bag { phantom: PhantomData, head: Some(2), tail: Some(2), bag_upper: 1_000 }) }); } @@ -669,7 +669,7 @@ mod bags { )] fn insert_node_duplicate_tail_panics_with_debug_assert() { ExtBuilder::default().build_and_execute(|| { - let node = |id, prev, next, bag_upper| Node:: { id, prev, next, bag_upper }; + let node = | phantom, id, prev, next, bag_upper| Node:: { phantom, id, prev, next, bag_upper }; // given assert_eq!(List::::get_bags(), vec![(10, vec![1]), (1_000, vec![2, 3, 4])],); @@ -678,7 +678,7 @@ mod bags { // when inserting a duplicate id that is already the tail assert_eq!(bag_1000.tail, Some(4)); assert_eq!(bag_1000.iter().count(), 3); - bag_1000.insert_node_unchecked(node(4, None, None, bag_1000.bag_upper)); // panics in debug + bag_1000.insert_node_unchecked(node(PhantomData, 4, None, None, bag_1000.bag_upper)); // panics in debug assert_eq!(bag_1000.iter().count(), 3); // in release we expect it to silently ignore the request. }); } @@ -797,6 +797,7 @@ mod bags { fn remove_node_bad_paths_documented() { ExtBuilder::default().build_and_execute_no_post_check(|| { let bad_upper_node_2 = Node:: { + phantom: PhantomData, id: 2, prev: None, next: Some(3), diff --git a/frame/bags-list/src/mock.rs b/frame/bags-list/src/mock.rs index 14b161f1bc25c..aa3f549e12dec 100644 --- a/frame/bags-list/src/mock.rs +++ b/frame/bags-list/src/mock.rs @@ -91,7 +91,7 @@ frame_support::construct_runtime!( UncheckedExtrinsic = UncheckedExtrinsic, { System: frame_system::{Pallet, Call, Storage, Event, Config}, - BagsList: bags_list::{Pallet, Call, Storage, Event}, + BagsList: bags_list::{Pallet, Call, Storage, Event}, } ); diff --git a/frame/bags-list/src/tests.rs b/frame/bags-list/src/tests.rs index 43397b3c120f5..2cc2f39cbcad9 100644 --- a/frame/bags-list/src/tests.rs +++ b/frame/bags-list/src/tests.rs @@ -21,6 +21,7 @@ use super::*; use frame_election_provider_support::SortedListProvider; use list::Bag; use mock::{test_utils::*, *}; +use sp_std::marker::PhantomData; mod pallet { use super::*; @@ -90,7 +91,7 @@ mod pallet { // then assert_eq!(List::::get_bags(), vec![(10, vec![1, 4]), (1_000, vec![2, 3])]); - assert_eq!(Bag::::get(1_000).unwrap(), Bag::new(Some(2), Some(3), 1_000)); + assert_eq!(Bag::::get(1_000).unwrap(), Bag::new(PhantomData, Some(2), Some(3), 1_000)); // when StakingMock::set_vote_weight_of(&3, 10); @@ -99,8 +100,8 @@ mod pallet { // then assert_eq!(List::::get_bags(), vec![(10, vec![1, 4, 3]), (1_000, vec![2])]); - assert_eq!(Bag::::get(10).unwrap(), Bag::new(Some(1), Some(3), 10)); - assert_eq!(Bag::::get(1_000).unwrap(), Bag::new(Some(2), Some(2), 1_000)); + assert_eq!(Bag::::get(10).unwrap(), Bag::new(PhantomData, Some(1), Some(3), 10)); + assert_eq!(Bag::::get(1_000).unwrap(), Bag::new(PhantomData, Some(2), Some(2), 1_000)); assert_eq!(get_list_as_ids(), vec![2u32, 1, 4, 3]); // when @@ -124,7 +125,7 @@ mod pallet { // then assert_eq!(List::::get_bags(), vec![(10, vec![1, 2]), (1_000, vec![3, 4])]); - assert_eq!(Bag::::get(1_000).unwrap(), Bag::new(Some(3), Some(4), 1_000)); + assert_eq!(Bag::::get(1_000).unwrap(), Bag::new(PhantomData, Some(3), Some(4), 1_000)); // when StakingMock::set_vote_weight_of(&3, 10); @@ -132,7 +133,7 @@ mod pallet { // then assert_eq!(List::::get_bags(), vec![(10, vec![1, 2, 3]), (1_000, vec![4])]); - assert_eq!(Bag::::get(1_000).unwrap(), Bag::new(Some(4), Some(4), 1_000)); + assert_eq!(Bag::::get(1_000).unwrap(), Bag::new(PhantomData, Some(4), Some(4), 1_000)); // when StakingMock::set_vote_weight_of(&4, 10); From 7d6cb8dc7f5054c3c1ce801be9ab8f274101dcc9 Mon Sep 17 00:00:00 2001 From: doordashcon Date: Fri, 4 Mar 2022 20:06:47 +0100 Subject: [PATCH 13/42] cargo fmt --- frame/bags-list/src/lib.rs | 3 +- frame/bags-list/src/list/mod.rs | 17 +++- frame/bags-list/src/list/tests.rs | 127 +++++++++++++++++++++++++----- frame/bags-list/src/tests.rs | 25 ++++-- 4 files changed, 142 insertions(+), 30 deletions(-) diff --git a/frame/bags-list/src/lib.rs b/frame/bags-list/src/lib.rs index d0384367f5ded..477494402231e 100644 --- a/frame/bags-list/src/lib.rs +++ b/frame/bags-list/src/lib.rs @@ -163,7 +163,8 @@ pub mod pallet { /// /// Stores a `Bag` struct, which stores head and tail pointers to itself. #[pallet::storage] - pub(crate) type ListBags, I: 'static = ()> = StorageMap<_, Twox64Concat, VoteWeight, list::Bag>; + pub(crate) type ListBags, I: 'static = ()> = + StorageMap<_, Twox64Concat, VoteWeight, list::Bag>; #[pallet::event] #[pallet::generate_deposit(pub(crate) fn deposit_event)] diff --git a/frame/bags-list/src/list/mod.rs b/frame/bags-list/src/list/mod.rs index 0c2bdd21e0c30..ad086da6d0a7b 100644 --- a/frame/bags-list/src/list/mod.rs +++ b/frame/bags-list/src/list/mod.rs @@ -135,11 +135,13 @@ impl, I: 'static> List { // we can't check all preconditions, but we can check one debug_assert!( - crate::ListBags::::iter().all(|(threshold, _)| old_thresholds.contains(&threshold)), + crate::ListBags::::iter() + .all(|(threshold, _)| old_thresholds.contains(&threshold)), "not all `bag_upper` currently in storage are members of `old_thresholds`", ); debug_assert!( - crate::ListNodes::::iter().all(|(_, node)| old_thresholds.contains(&node.bag_upper)), + crate::ListNodes::::iter() + .all(|(_, node)| old_thresholds.contains(&node.bag_upper)), "not all `node.bag_upper` currently in storage are members of `old_thresholds`", ); @@ -531,7 +533,8 @@ impl, I: 'static> List { }; iter.filter_map(|t| { - Bag::::get(t).map(|bag| (t, bag.iter().map(|n| n.id().clone()).collect::>())) + Bag::::get(t) + .map(|bag| (t, bag.iter().map(|n| n.id().clone()).collect::>())) }) .collect::>() } @@ -622,7 +625,13 @@ impl, I: 'static> Bag { // insert_node will overwrite `prev`, `next` and `bag_upper` to the proper values. As long // as this bag is the correct one, we're good. All calls to this must come after getting the // correct [`notional_bag_for`]. - self.insert_node_unchecked(Node:: { phantom: Default::default(), id, prev: None, next: None, bag_upper: 0 }); + self.insert_node_unchecked(Node:: { + phantom: Default::default(), + id, + prev: None, + next: None, + bag_upper: 0, + }); } /// Insert a node into this bag. diff --git a/frame/bags-list/src/list/tests.rs b/frame/bags-list/src/list/tests.rs index 08ea07c4902f8..0aa8662bdd683 100644 --- a/frame/bags-list/src/list/tests.rs +++ b/frame/bags-list/src/list/tests.rs @@ -27,7 +27,13 @@ use frame_support::{assert_ok, assert_storage_noop}; fn basic_setup_works() { ExtBuilder::default().build_and_execute(|| { // syntactic sugar to create a raw node - let node = |phantom, id, prev, next, bag_upper| Node:: { phantom, id, prev, next, bag_upper }; + let node = |phantom, id, prev, next, bag_upper| Node:: { + phantom, + id, + prev, + next, + bag_upper, + }; assert_eq!(ListNodes::::count(), 4); assert_eq!(ListNodes::::iter().count(), 4); @@ -45,9 +51,18 @@ fn basic_setup_works() { Bag:: { phantom: PhantomData, head: Some(2), tail: Some(4), bag_upper: 0 } ); - assert_eq!(ListNodes::::get(2).unwrap(), node(PhantomData, 2, None, Some(3), 1_000)); - assert_eq!(ListNodes::::get(3).unwrap(), node(PhantomData, 3, Some(2), Some(4), 1_000)); - assert_eq!(ListNodes::::get(4).unwrap(), node(PhantomData, 4, Some(3), None, 1_000)); + assert_eq!( + ListNodes::::get(2).unwrap(), + node(PhantomData, 2, None, Some(3), 1_000) + ); + assert_eq!( + ListNodes::::get(3).unwrap(), + node(PhantomData, 3, Some(2), Some(4), 1_000) + ); + assert_eq!( + ListNodes::::get(4).unwrap(), + node(PhantomData, 4, Some(3), None, 1_000) + ); assert_eq!(ListNodes::::get(1).unwrap(), node(PhantomData, 1, None, None, 10)); // non-existent id does not have a storage footprint @@ -388,8 +403,20 @@ mod list { #[should_panic = "given nodes must always have a valid bag. qed."] fn put_in_front_of_panics_if_bag_not_found() { ExtBuilder::default().skip_genesis_ids().build_and_execute_no_post_check(|| { - let node_10_no_bag = Node:: { phantom: PhantomData, id: 10, prev: None, next: None, bag_upper: 15 }; - let node_11_no_bag = Node:: { phantom: PhantomData, id: 11, prev: None, next: None, bag_upper: 15 }; + let node_10_no_bag = Node:: { + phantom: PhantomData, + id: 10, + prev: None, + next: None, + bag_upper: 15, + }; + let node_11_no_bag = Node:: { + phantom: PhantomData, + id: 11, + prev: None, + next: None, + bag_upper: 15, + }; // given ListNodes::::insert(10, node_10_no_bag); @@ -414,8 +441,13 @@ mod list { assert_eq!(List::::get_bags(), vec![(10, vec![1]), (1_000, vec![2, 3, 4])]); // implicitly also test that `node`'s `prev`/`next` are correctly re-assigned. - let node_42 = - Node:: { phantom: PhantomData, id: 42, prev: Some(1), next: Some(2), bag_upper: 1_000 }; + let node_42 = Node:: { + phantom: PhantomData, + id: 42, + prev: Some(1), + next: Some(2), + bag_upper: 1_000, + }; assert!(!crate::ListNodes::::contains_key(42)); let node_1 = crate::ListNodes::::get(&1).unwrap(); @@ -438,7 +470,13 @@ mod list { assert_eq!(List::::get_bags(), vec![(10, vec![1]), (1_000, vec![2, 3, 4])]); // implicitly also test that `node`'s `prev`/`next` are correctly re-assigned. - let node_42 = Node:: { phantom: PhantomData, id: 42, prev: Some(4), next: None, bag_upper: 1_000 }; + let node_42 = Node:: { + phantom: PhantomData, + id: 42, + prev: Some(4), + next: None, + bag_upper: 1_000, + }; assert!(!crate::ListNodes::::contains_key(42)); let node_2 = crate::ListNodes::::get(&2).unwrap(); @@ -461,7 +499,13 @@ mod list { assert_eq!(List::::get_bags(), vec![(10, vec![1]), (1_000, vec![2, 3, 4])]); // implicitly also test that `node`'s `prev`/`next` are correctly re-assigned. - let node_42 = Node:: { phantom: PhantomData, id: 42, prev: None, next: Some(2), bag_upper: 1_000 }; + let node_42 = Node:: { + phantom: PhantomData, + id: 42, + prev: None, + next: Some(2), + bag_upper: 1_000, + }; assert!(!crate::ListNodes::::contains_key(42)); let node_3 = crate::ListNodes::::get(&3).unwrap(); @@ -484,8 +528,13 @@ mod list { assert_eq!(List::::get_bags(), vec![(10, vec![1]), (1_000, vec![2, 3, 4])]); // implicitly also test that `node`'s `prev`/`next` are correctly re-assigned. - let node_42 = - Node:: { phantom: PhantomData, id: 42, prev: Some(42), next: Some(42), bag_upper: 1_000 }; + let node_42 = Node:: { + phantom: PhantomData, + id: 42, + prev: Some(42), + next: Some(42), + bag_upper: 1_000, + }; assert!(!crate::ListNodes::::contains_key(42)); let node_4 = crate::ListNodes::::get(&4).unwrap(); @@ -543,7 +592,13 @@ mod bags { #[test] fn insert_node_sets_proper_bag() { ExtBuilder::default().build_and_execute_no_post_check(|| { - let node = |id, bag_upper| Node:: { phantom: PhantomData, id, prev: None, next: None, bag_upper }; + let node = |id, bag_upper| Node:: { + phantom: PhantomData, + id, + prev: None, + next: None, + bag_upper, + }; assert_eq!(List::::get_bags(), vec![(10, vec![1]), (1_000, vec![2, 3, 4])]); @@ -560,7 +615,13 @@ mod bags { #[test] fn insert_node_happy_paths_works() { ExtBuilder::default().build_and_execute_no_post_check(|| { - let node = |id, bag_upper| Node:: { phantom: PhantomData, id, prev: None, next: None, bag_upper }; + let node = |id, bag_upper| Node:: { + phantom: PhantomData, + id, + prev: None, + next: None, + bag_upper, + }; // when inserting into a bag with 1 node let mut bag_10 = Bag::::get(10).unwrap(); @@ -581,15 +642,26 @@ mod bags { assert_eq!(bag_as_ids(&bag_20), vec![62]); // when inserting a node pointing to the accounts not in the bag - let node_61 = - Node:: { phantom: PhantomData, id: 61, prev: Some(21), next: Some(101), bag_upper: 20 }; + let node_61 = Node:: { + phantom: PhantomData, + id: 61, + prev: Some(21), + next: Some(101), + bag_upper: 20, + }; bag_20.insert_node_unchecked(node_61); // then ids are in order assert_eq!(bag_as_ids(&bag_20), vec![62, 61]); // and when the node is re-fetched all the info is correct assert_eq!( Node::::get(&61).unwrap(), - Node:: { phantom: PhantomData, id: 61, prev: Some(62), next: None, bag_upper: 20 } + Node:: { + phantom: PhantomData, + id: 61, + prev: Some(62), + next: None, + bag_upper: 20 + } ); // state of all bags is as expected @@ -604,7 +676,13 @@ mod bags { // Document improper ways `insert_node` may be getting used. #[test] fn insert_node_bad_paths_documented() { - let node = | phantom, id, prev, next, bag_upper| Node:: { phantom, id, prev, next, bag_upper }; + let node = |phantom, id, prev, next, bag_upper| Node:: { + phantom, + id, + prev, + next, + bag_upper, + }; ExtBuilder::default().build_and_execute_no_post_check(|| { // when inserting a node with both prev & next pointing at an account in an incorrect // bag. @@ -657,7 +735,10 @@ mod bags { ); // ^^^ despite being the bags head, it has a prev - assert_eq!(bag_1000, Bag { phantom: PhantomData, head: Some(2), tail: Some(2), bag_upper: 1_000 }) + assert_eq!( + bag_1000, + Bag { phantom: PhantomData, head: Some(2), tail: Some(2), bag_upper: 1_000 } + ) }); } @@ -669,7 +750,13 @@ mod bags { )] fn insert_node_duplicate_tail_panics_with_debug_assert() { ExtBuilder::default().build_and_execute(|| { - let node = | phantom, id, prev, next, bag_upper| Node:: { phantom, id, prev, next, bag_upper }; + let node = |phantom, id, prev, next, bag_upper| Node:: { + phantom, + id, + prev, + next, + bag_upper, + }; // given assert_eq!(List::::get_bags(), vec![(10, vec![1]), (1_000, vec![2, 3, 4])],); diff --git a/frame/bags-list/src/tests.rs b/frame/bags-list/src/tests.rs index 2cc2f39cbcad9..9e0d7de567c28 100644 --- a/frame/bags-list/src/tests.rs +++ b/frame/bags-list/src/tests.rs @@ -91,7 +91,10 @@ mod pallet { // then assert_eq!(List::::get_bags(), vec![(10, vec![1, 4]), (1_000, vec![2, 3])]); - assert_eq!(Bag::::get(1_000).unwrap(), Bag::new(PhantomData, Some(2), Some(3), 1_000)); + assert_eq!( + Bag::::get(1_000).unwrap(), + Bag::new(PhantomData, Some(2), Some(3), 1_000) + ); // when StakingMock::set_vote_weight_of(&3, 10); @@ -100,8 +103,14 @@ mod pallet { // then assert_eq!(List::::get_bags(), vec![(10, vec![1, 4, 3]), (1_000, vec![2])]); - assert_eq!(Bag::::get(10).unwrap(), Bag::new(PhantomData, Some(1), Some(3), 10)); - assert_eq!(Bag::::get(1_000).unwrap(), Bag::new(PhantomData, Some(2), Some(2), 1_000)); + assert_eq!( + Bag::::get(10).unwrap(), + Bag::new(PhantomData, Some(1), Some(3), 10) + ); + assert_eq!( + Bag::::get(1_000).unwrap(), + Bag::new(PhantomData, Some(2), Some(2), 1_000) + ); assert_eq!(get_list_as_ids(), vec![2u32, 1, 4, 3]); // when @@ -125,7 +134,10 @@ mod pallet { // then assert_eq!(List::::get_bags(), vec![(10, vec![1, 2]), (1_000, vec![3, 4])]); - assert_eq!(Bag::::get(1_000).unwrap(), Bag::new(PhantomData, Some(3), Some(4), 1_000)); + assert_eq!( + Bag::::get(1_000).unwrap(), + Bag::new(PhantomData, Some(3), Some(4), 1_000) + ); // when StakingMock::set_vote_weight_of(&3, 10); @@ -133,7 +145,10 @@ mod pallet { // then assert_eq!(List::::get_bags(), vec![(10, vec![1, 2, 3]), (1_000, vec![4])]); - assert_eq!(Bag::::get(1_000).unwrap(), Bag::new(PhantomData, Some(4), Some(4), 1_000)); + assert_eq!( + Bag::::get(1_000).unwrap(), + Bag::new(PhantomData, Some(4), Some(4), 1_000) + ); // when StakingMock::set_vote_weight_of(&4, 10); From dc7bd8970e02a4c0226bd7988c7b5812a9f64af2 Mon Sep 17 00:00:00 2001 From: kianenigma Date: Tue, 8 Mar 2022 12:49:30 +0000 Subject: [PATCH 14/42] Most things work now --- frame/bags-list/src/lib.rs | 4 ++ frame/bags-list/src/list/mod.rs | 19 ++++++-- frame/election-provider-support/src/lib.rs | 24 ++++++++++ frame/staking/src/migrations.rs | 38 +++++++++++---- frame/staking/src/pallet/impls.rs | 47 ++++++++++++++---- frame/staking/src/pallet/mod.rs | 55 +++++++++++++--------- frame/staking/src/tests.rs | 10 +++- 7 files changed, 149 insertions(+), 48 deletions(-) diff --git a/frame/bags-list/src/lib.rs b/frame/bags-list/src/lib.rs index 89c54db87023f..6bd01fe776bf9 100644 --- a/frame/bags-list/src/lib.rs +++ b/frame/bags-list/src/lib.rs @@ -272,6 +272,10 @@ impl SortedListProvider for Pallet { List::::insert(id, weight) } + fn get_weight(id: &T::AccountId) -> Result { + List::::get_weight(id) + } + fn on_update(id: &T::AccountId, new_weight: VoteWeight) { Pallet::::do_rebag(id, new_weight); } diff --git a/frame/bags-list/src/list/mod.rs b/frame/bags-list/src/list/mod.rs index 8cd89b8fff1cc..16d568bd6fb8b 100644 --- a/frame/bags-list/src/list/mod.rs +++ b/frame/bags-list/src/list/mod.rs @@ -41,6 +41,8 @@ use sp_std::{ pub enum Error { /// A duplicate id has been detected. Duplicate, + /// the given id does not exists. + NonExistent, } #[cfg(test)] @@ -215,6 +217,11 @@ impl List { crate::ListNodes::::contains_key(id) } + /// Get the weight of the given node, + pub fn get_weight(id: &T::AccountId) -> Result { + Node::::get(id).map(|node| node.weight()).ok_or(Error::NonExistent) + } + /// Iterate over all nodes in all bags in the list. /// /// Full iteration can be expensive; it's recommended to limit the number of items with @@ -269,7 +276,7 @@ impl List { let bag_weight = notional_bag_for::(weight); let mut bag = Bag::::get_or_make(bag_weight); // unchecked insertion is okay; we just got the correct `notional_bag_for`. - bag.insert_unchecked(id.clone()); + bag.insert_unchecked(id.clone(), weight); // new inserts are always the tail, so we must write the bag. bag.put(); @@ -616,11 +623,11 @@ impl Bag { /// /// Storage note: this modifies storage, but only for the nodes. You still need to call /// `self.put()` after use. - fn insert_unchecked(&mut self, id: T::AccountId) { + fn insert_unchecked(&mut self, id: T::AccountId, weight: VoteWeight) { // insert_node will overwrite `prev`, `next` and `bag_upper` to the proper values. As long // as this bag is the correct one, we're good. All calls to this must come after getting the // correct [`notional_bag_for`]. - self.insert_node_unchecked(Node:: { id, prev: None, next: None, bag_upper: 0 }); + self.insert_node_unchecked(Node:: { id, weight, prev: None, next: None, bag_upper: 0 }); } /// Insert a node into this bag. @@ -756,6 +763,7 @@ pub struct Node { prev: Option, next: Option, bag_upper: VoteWeight, + weight: VoteWeight, } impl Node { @@ -818,6 +826,11 @@ impl Node { &self.id } + /// Get the current vote weight of the node. + pub(crate) fn weight(&self) -> VoteWeight { + self.weight + } + /// Get the underlying voter (public fo tests). #[cfg(feature = "std")] #[allow(dead_code)] diff --git a/frame/election-provider-support/src/lib.rs b/frame/election-provider-support/src/lib.rs index 26efe5107b670..6d7bd046085d2 100644 --- a/frame/election-provider-support/src/lib.rs +++ b/frame/election-provider-support/src/lib.rs @@ -376,11 +376,35 @@ pub trait SortedListProvider { fn contains(id: &AccountId) -> bool; /// Hook for inserting a new id. + /// + /// Implementation should return an error if duplicate item is being fn on_insert(id: AccountId, weight: VoteWeight) -> Result<(), Self::Error>; /// Hook for updating a single id. + /// + /// The `new` weight is given. + // TODO: why not return result here? fn on_update(id: &AccountId, weight: VoteWeight); + /// Get the weight of `id`. + fn get_weight(id: &AccountId) -> Result; + + /// Same as `on_update`, but incorporate some increased vote weight. + fn on_increase(id: &AccountId, additional: VoteWeight) -> Result<(), Self::Error> { + let old_weight = Self::get_weight(id)?; + let new_weight = old_weight.saturating_add(additional); + Self::on_update(id, new_weight); + Ok(()) + } + + /// Same as `on_update`, but incorporate some decreased vote weight. + fn on_decrease(id: &AccountId, decreased: VoteWeight) -> Result<(), Self::Error> { + let old_weight = Self::get_weight(id)?; + let new_weight = old_weight.saturating_sub(decreased); + Self::on_update(id, new_weight); + Ok(()) + } + /// Hook for removing am id from the list. fn on_remove(id: &AccountId); diff --git a/frame/staking/src/migrations.rs b/frame/staking/src/migrations.rs index 87c95965a572b..86b26eb16d814 100644 --- a/frame/staking/src/migrations.rs +++ b/frame/staking/src/migrations.rs @@ -17,14 +17,15 @@ //! Storage migrations for the Staking pallet. use super::*; -use frame_election_provider_support::{SortedListProvider, VoteWeightProvider}; -use frame_support::traits::OnRuntimeUpgrade; +use frame_election_provider_support::{SortedListProvider, VoteWeight, VoteWeightProvider}; +use frame_support::traits::{Defensive, OnRuntimeUpgrade}; +use sp_std::collections::btree_map::BTreeMap; /// Migration implementation that injects all validators into sorted list. /// /// This is only useful for chains that started their `VoterList` just based on nominators. -pub struct InjectValidatorsIntoVoterList(sp_std::marker::PhantomData); -impl OnRuntimeUpgrade for InjectValidatorsIntoVoterList { +pub struct InjectValidatorsSelfStakeIntoVoterList(sp_std::marker::PhantomData); +impl OnRuntimeUpgrade for InjectValidatorsSelfStakeIntoVoterList { fn on_runtime_upgrade() -> Weight { if StorageVersion::::get() == Releases::V8_0_0 { for (v, _) in Validators::::iter() { @@ -67,15 +68,32 @@ impl OnRuntimeUpgrade for InjectValidatorsIntoVoterList { /// Migration implementation that injects all validators into sorted list. /// /// This is only useful for chains that started their `VoterList` just based on nominators. -pub struct InjectValidatorsIntoTargetList(sp_std::marker::PhantomData); -impl OnRuntimeUpgrade for InjectValidatorsIntoTargetList { +pub struct InjectValidatorsApprovalStakeIntoTargetList(sp_std::marker::PhantomData); + +impl InjectValidatorsApprovalStakeIntoTargetList { + pub(crate) fn build_approval_stakes() -> BTreeMap { + let mut approval_stakes = BTreeMap::::new(); + Nominators::::iter().for_each(|(who, nomination)| { + let stake = Pallet::::weight_of(&who); + for target in nomination.targets { + let current = approval_stakes.entry(target).or_default(); + *current = current.saturating_add(stake); + } + }); + + approval_stakes + } +} + +impl OnRuntimeUpgrade for InjectValidatorsApprovalStakeIntoTargetList { fn on_runtime_upgrade() -> Weight { if StorageVersion::::get() == Releases::V9_0_0 { + // TODO: maybe write this in a multi-block fashion. + // TODO: TargetList should store balance, not u64. + let approval_stakes = Self::build_approval_stakes(); for (v, _) in Validators::::iter() { - let weight = Pallet::::vote_weight(&v); - let _ = T::TargetList::on_insert(v.clone(), weight).map_err(|err| { - log!(warn, "failed to insert {:?} into TargetList: {:?}", v, err) - }); + let approval_stake = approval_stakes.get(&v).map(|x| *x).unwrap_or_default(); + let _ = T::TargetList::on_insert(v.clone(), approval_stake).defensive(); } StorageVersion::::put(Releases::V10_0_0); diff --git a/frame/staking/src/pallet/impls.rs b/frame/staking/src/pallet/impls.rs index 1dbad666bdb33..cd63ec891c8e2 100644 --- a/frame/staking/src/pallet/impls.rs +++ b/frame/staking/src/pallet/impls.rs @@ -201,13 +201,23 @@ impl Pallet { /// Update the ledger for a controller. /// - /// This will also update the stash lock. + /// All updates to the ledger amount MUST be reported ot this function, so that the lock amount + /// and other bookkeeping is maintained. pub(crate) fn update_ledger( controller: &T::AccountId, ledger: &StakingLedger>, ) { T::Currency::set_lock(STAKING_ID, &ledger.stash, ledger.total, WithdrawReasons::all()); >::insert(controller, ledger); + + if let Some(targets) = Self::bonded(controller) + .and_then(|stash| Self::nominators(stash)) + .map(|nomination| nomination.targets) + { + for target in targets { + T::TargetList::on_update(&target, Self::vote_weight(&ledger.stash)) + } + } } /// Chill a stash account. @@ -810,8 +820,6 @@ impl Pallet { // maybe update sorted list. let _ = T::VoterList::on_insert(who.clone(), Self::weight_of(who)) .defensive_unwrap_or_default(); - let _ = T::TargetList::on_insert(who.clone(), Self::weight_of(who)) - .defensive_unwrap_or_default(); } Validators::::insert(who, prefs); @@ -830,7 +838,6 @@ impl Pallet { let outcome = if Validators::::contains_key(who) { Validators::::remove(who); T::VoterList::on_remove(who); - T::TargetList::on_remove(who); true } else { false @@ -853,15 +860,24 @@ impl Pallet { } /// Perform all checks related to both [`Config::TargetList`] and [`Config::VoterList`]. - #[cfg(debug_assertions)] - fn sanity_check_list_providers() { + #[cfg(any(debug_assertions, feature = "std"))] + pub(crate) fn sanity_check_list_providers() { debug_assert_eq!( Nominators::::count() + Validators::::count(), T::VoterList::count() ); debug_assert_eq!(Validators::::count(), T::TargetList::count()); + debug_assert_eq!(T::VoterList::sanity_check(), Ok(())); - debug_assert_eq!(T::VoterList::sanity_check(), Ok(())); + debug_assert_eq!(T::TargetList::sanity_check(), Ok(())); + + // additionally, if asked for the full check, we ensure that the TargetList is indeed + // composed of the approval stakes. + use crate::migrations::InjectValidatorsApprovalStakeIntoTargetList as TargetListMigration; + let approval_stakes = TargetListMigration::::build_approval_stakes(); + debug_assert!(approval_stakes + .iter() + .all(|(v, a)| T::TargetList::get_weight(v).unwrap_or_default() == *a)) } } @@ -1310,6 +1326,10 @@ impl SortedListProvider for UseNominatorsAndValidatorsM // nothing to do on insert. Ok(()) } + fn get_weight(id: &T::AccountId) -> Result { + // TODO: this is not consistent, should ideally return an error if not exists. + Ok(Pallet::::weight_of(id)) + } fn on_update(_: &T::AccountId, _weight: VoteWeight) { // nothing to do on update. } @@ -1333,9 +1353,7 @@ impl SortedListProvider for UseNominatorsAndValidatorsM } } -/// A simple sorted list implementation that does not require any additional pallets. Note, this -/// does not provided validators in sorted ordered. If you desire nominators in a sorted order take -/// a look at [`pallet-bags-list]. +// TODO: move this to mock, as it is only for testing anyway. pub struct UseValidatorsMap(sp_std::marker::PhantomData); impl SortedListProvider for UseValidatorsMap { type Error = (); @@ -1354,6 +1372,15 @@ impl SortedListProvider for UseValidatorsMap { // nothing to do on insert. Ok(()) } + fn get_weight(id: &T::AccountId) -> Result { + let mut approval_stake = VoteWeight::zero(); + Nominators::::iter().for_each(|(nominator, nomination)| { + if nomination.targets.contains(id) { + approval_stake += Pallet::::weight_of(&nominator); + } + }); + Ok(approval_stake) + } fn on_update(_: &T::AccountId, _weight: VoteWeight) { // nothing to do on update. } diff --git a/frame/staking/src/pallet/mod.rs b/frame/staking/src/pallet/mod.rs index 70cb229870941..0b4f9fa00c9bb 100644 --- a/frame/staking/src/pallet/mod.rs +++ b/frame/staking/src/pallet/mod.rs @@ -21,8 +21,8 @@ use frame_election_provider_support::SortedListProvider; use frame_support::{ pallet_prelude::*, traits::{ - Currency, CurrencyToVote, EnsureOrigin, EstimateNextNewSession, Get, LockIdentifier, - LockableCurrency, OnUnbalanced, UnixTime, + Currency, CurrencyToVote, Defensive, DefensiveResult, EnsureOrigin, EstimateNextNewSession, + Get, LockIdentifier, LockableCurrency, OnUnbalanced, UnixTime, }, weights::Weight, }; @@ -568,16 +568,7 @@ pub mod pallet { } // all voters are reported to the `VoterList`. - assert_eq!( - T::VoterList::count(), - Nominators::::count() + Validators::::count(), - "not all genesis stakers were inserted into sorted list provider, something is wrong." - ); - assert_eq!( - T::TargetList::count(), - Validators::::count(), - "not all genesis stakers were inserted into sorted list provider, something is wrong." - ); + Pallet::::sanity_check_list_providers(); } } @@ -830,10 +821,6 @@ pub mod pallet { T::VoterList::on_update(&stash, Self::weight_of(&ledger.stash)); debug_assert_eq!(T::VoterList::sanity_check(), Ok(())); } - if T::TargetList::contains(&stash) { - T::TargetList::on_update(&stash, Self::weight_of(&ledger.stash)); - debug_assert_eq!(T::VoterList::sanity_check(), Ok(())); - } Self::deposit_event(Event::::Bonded(stash.clone(), extra)); } @@ -901,9 +888,6 @@ pub mod pallet { if T::VoterList::contains(&ledger.stash) { T::VoterList::on_update(&ledger.stash, Self::weight_of(&ledger.stash)); } - if T::TargetList::contains(&ledger.stash) { - T::TargetList::on_update(&ledger.stash, Self::weight_of(&ledger.stash)); - } Self::deposit_event(Event::::Unbonded(ledger.stash, value)); } @@ -1056,7 +1040,35 @@ pub mod pallet { }) .collect::, _>>()? .try_into() - .map_err(|_| Error::::TooManyNominators)?; + .defensive_map_err(|_| Error::::TooManyTargets)?; + + ensure!( + targets.len() == + targets + .iter() + .collect::>() + .len(), + "DuplicateTargets" + ); + + let incoming = targets.iter().cloned().filter(|x| !old.contains(x)).collect::>(); + let outgoing = old.iter().cloned().filter(|x| !targets.contains(x)).collect::>(); + incoming.into_iter().for_each(|i| { + if T::TargetList::contains(&i) { + // TODO: these are all rather inefficient now based on how vote_weight is + // implemented, but I don't care because: https://github.com/paritytech/substrate/issues/10990 + let _ = T::TargetList::on_increase(&i, Self::weight_of(stash)).defensive(); + } else { + let _ = T::TargetList::on_insert(i, Self::weight_of(stash)).defensive(); + } + }); + outgoing.into_iter().for_each(|o| { + if T::TargetList::contains(&o) { + let _ = T::TargetList::on_decrease(&o, Self::weight_of(stash)); + } else { + frame_support::defensive!("this validator must have, at some point in the past, been inserted into `TargetList`"); + } + }); let nominations = Nominations { targets, @@ -1388,9 +1400,6 @@ pub mod pallet { if T::VoterList::contains(&ledger.stash) { T::VoterList::on_update(&ledger.stash, Self::weight_of(&ledger.stash)); } - if T::TargetList::contains(&ledger.stash) { - T::TargetList::on_update(&ledger.stash, Self::weight_of(&ledger.stash)); - } let removed_chunks = 1u32 // for the case where the last iterated chunk is not removed .saturating_add(initial_unlocking) diff --git a/frame/staking/src/tests.rs b/frame/staking/src/tests.rs index 26e74cb1a7e57..3290d6831681b 100644 --- a/frame/staking/src/tests.rs +++ b/frame/staking/src/tests.rs @@ -4071,8 +4071,14 @@ mod election_data_provider { assert_eq!(Staking::targets(Some(6)).unwrap().len(), 4); assert_eq!(Staking::targets(Some(4)).unwrap().len(), 4); - // if target limit is less, then we return an error. - assert_eq!(Staking::targets(Some(1)).unwrap_err(), "Target snapshot too big"); + // if target limit is less, then we still return something based on `TargetList` + // implementation. Currently, it should be the validators with the highest approval + // stake. + assert_eq!( + ::TargetList::iter().take(1).collect::>(), + vec![31] + ); + assert_eq!(Staking::targets(Some(1)).unwrap(), vec![31]); }); } From 700141e492de3a281c3b3999a9e3c6e2762ef8db Mon Sep 17 00:00:00 2001 From: kianenigma Date: Thu, 10 Mar 2022 15:41:43 +0000 Subject: [PATCH 15/42] All tests work now --- frame/bags-list/src/lib.rs | 11 +- frame/bags-list/src/list/mod.rs | 10 +- frame/bags-list/src/list/tests.rs | 179 +++++++++++++++--------------- frame/bags-list/src/tests.rs | 25 +++-- frame/staking/src/migrations.rs | 2 +- frame/staking/src/pallet/impls.rs | 2 +- frame/staking/src/pallet/mod.rs | 2 +- 7 files changed, 116 insertions(+), 115 deletions(-) diff --git a/frame/bags-list/src/lib.rs b/frame/bags-list/src/lib.rs index 670382b04916b..01a7eec59787e 100644 --- a/frame/bags-list/src/lib.rs +++ b/frame/bags-list/src/lib.rs @@ -259,15 +259,14 @@ impl, I: 'static> Pallet { pub fn do_rebag( account: &T::AccountId, new_score: T::Score, - ) -> Result<(T::Score, T::Score), Error> { + ) -> Result, Error> { // If no voter at that node, don't do anything. the caller just wasted the fee to call this. - let maybe_movement = list::Node::::get(&account) - .and_then(|node| List::update_position_for(node, new_score)) - .ok_or(Error::NonExistent); - if let Ok((from, to)) = maybe_movement { + let node = list::Node::::get(&account).ok_or(Error::NonExistent)?; + let maybe_movement = List::update_position_for(node, new_score); + if let Some((from, to)) = maybe_movement { Self::deposit_event(Event::::Rebagged { who: account.clone(), from, to }); }; - maybe_movement + Ok(maybe_movement) } /// Equivalent to `ListBags::get`, but public. Useful for tests in outside of this crate. diff --git a/frame/bags-list/src/list/mod.rs b/frame/bags-list/src/list/mod.rs index e3f9d217bb2ed..a603962470f46 100644 --- a/frame/bags-list/src/list/mod.rs +++ b/frame/bags-list/src/list/mod.rs @@ -355,7 +355,8 @@ impl, I: 'static> List { /// If the node was in the correct bag, no effect. If the node was in the incorrect bag, they /// are moved into the correct bag. /// - /// Returns `Some((old_idx, new_idx))` if the node moved, otherwise `None`. + /// Returns `Some((old_idx, new_idx))` if the node moved, otherwise `None`. In both cases, the + /// node's score is written to the `score_score`. Thus, this is not a noop, even if `None`. /// /// This operation is somewhat more efficient than simply calling [`self.remove`] followed by /// [`self.insert`]. However, given large quantities of nodes to move, it may be more efficient @@ -385,12 +386,9 @@ impl, I: 'static> List { bag.remove_node_unchecked(&node); bag.put(); } else { - crate::log!( - error, - "Node {:?} did not have a bag; ListBags is in an inconsistent state", - node.id, + frame_support::defensive!( + "Node did not have a bag; BagsList is in an inconsistent state" ); - debug_assert!(false, "every node must have an extant bag associated with it"); } // put the node into the appropriate new bag. diff --git a/frame/bags-list/src/list/tests.rs b/frame/bags-list/src/list/tests.rs index 4d61e577c5b59..991ba201496db 100644 --- a/frame/bags-list/src/list/tests.rs +++ b/frame/bags-list/src/list/tests.rs @@ -39,7 +39,7 @@ fn basic_setup_works() { assert_eq!(ListNodes::::iter().count(), 4); assert_eq!(ListBags::::iter().count(), 2); - assert_eq!(List::::get_bags(), vec![(10, vec![1]), (1_000, vec![2, 3, 4])]); + assert_eq!(List::::get_bags(), vec![(10, vec![1]), (1000, vec![2, 3, 4])]); // the state of the bags is as expected assert_eq!( @@ -47,13 +47,13 @@ fn basic_setup_works() { Bag:: { head: Some(1), tail: Some(1), bag_upper: 0, _phantom: PhantomData } ); assert_eq!( - ListBags::::get(1_000).unwrap(), + ListBags::::get(1000).unwrap(), Bag:: { head: Some(2), tail: Some(4), bag_upper: 0, _phantom: PhantomData } ); - assert_eq!(ListNodes::::get(2).unwrap(), node(2, None, Some(3), 1_000)); - assert_eq!(ListNodes::::get(3).unwrap(), node(3, Some(2), Some(4), 1_000)); - assert_eq!(ListNodes::::get(4).unwrap(), node(4, Some(3), None, 1_000)); + assert_eq!(ListNodes::::get(2).unwrap(), node(2, None, Some(3), 1000)); + assert_eq!(ListNodes::::get(3).unwrap(), node(3, Some(2), Some(4), 1000)); + assert_eq!(ListNodes::::get(4).unwrap(), node(4, Some(3), None, 1000)); assert_eq!(ListNodes::::get(1).unwrap(), node(1, None, None, 10)); // non-existent id does not have a storage footprint @@ -95,20 +95,20 @@ fn notional_bag_for_works() { fn remove_last_node_in_bags_cleans_bag() { ExtBuilder::default().build_and_execute(|| { // given - assert_eq!(List::::get_bags(), vec![(10, vec![1]), (1_000, vec![2, 3, 4])]); + assert_eq!(List::::get_bags(), vec![(10, vec![1]), (1000, vec![2, 3, 4])]); // bump 1 to a bigger bag List::::remove(&1).unwrap(); assert_ok!(List::::insert(1, 10_000)); // then the bag with bound 10 is wiped from storage. - assert_eq!(List::::get_bags(), vec![(1_000, vec![2, 3, 4]), (10_000, vec![1])]); + assert_eq!(List::::get_bags(), vec![(1000, vec![2, 3, 4]), (10_000, vec![1])]); // and can be recreated again as needed. assert_ok!(List::::insert(77, 10)); assert_eq!( List::::get_bags(), - vec![(10, vec![77]), (1_000, vec![2, 3, 4]), (10_000, vec![1])] + vec![(10, vec![77]), (1000, vec![2, 3, 4]), (10_000, vec![1])] ); }); } @@ -124,16 +124,16 @@ fn migrate_works() { vec![ (10, vec![1]), (20, vec![710, 711]), - (1_000, vec![2, 3, 4]), + (1000, vec![2, 3, 4]), (2_000, vec![712]) ] ); let old_thresholds = ::BagThresholds::get(); - assert_eq!(old_thresholds, vec![10, 20, 30, 40, 50, 60, 1_000, 2_000, 10_000]); + assert_eq!(old_thresholds, vec![10, 20, 30, 40, 50, 60, 1000, 2_000, 10_000]); // when the new thresholds adds `15` and removes `2_000` const NEW_THRESHOLDS: &'static [VoteWeight] = - &[10, 15, 20, 30, 40, 50, 60, 1_000, 10_000]; + &[10, 15, 20, 30, 40, 50, 60, 1000, 10_000]; BagThresholds::set(NEW_THRESHOLDS); // and we call List::::migrate(old_thresholds); @@ -145,7 +145,7 @@ fn migrate_works() { (10, vec![1]), (15, vec![710]), // nodes in range 11 ..= 15 move from bag 20 to bag 15 (20, vec![711]), - (1_000, vec![2, 3, 4]), + (1000, vec![2, 3, 4]), // nodes in range 1_001 ..= 2_000 move from bag 2_000 to bag 10_000 (10_000, vec![712]), ] @@ -166,7 +166,7 @@ mod list { // given assert_eq!( List::::get_bags(), - vec![(10, vec![1]), (1_000, vec![2, 3, 4]), (2_000, vec![5, 6])] + vec![(10, vec![1]), (1000, vec![2, 3, 4]), (2_000, vec![5, 6])] ); assert_eq!( get_list_as_ids(), @@ -177,7 +177,7 @@ mod list { ] ); - // when adding an id that has a higher weight than pre-existing ids in the bag + // when adding an id that has a higher score than pre-existing ids in the bag assert_ok!(List::::insert(7, 10)); // then @@ -201,7 +201,7 @@ mod list { // given assert_eq!( List::::get_bags(), - vec![(10, vec![1]), (1_000, vec![2, 3, 4]), (2_000, vec![5, 6])] + vec![(10, vec![1]), (1000, vec![2, 3, 4]), (2_000, vec![5, 6])] ); // when @@ -223,10 +223,10 @@ mod list { fn insert_works() { ExtBuilder::default().build_and_execute(|| { // when inserting into an existing bag - assert_ok!(List::::insert(5, 1_000)); + assert_ok!(List::::insert(5, 1000)); // then - assert_eq!(List::::get_bags(), vec![(10, vec![1]), (1_000, vec![2, 3, 4, 5])]); + assert_eq!(List::::get_bags(), vec![(10, vec![1]), (1000, vec![2, 3, 4, 5])]); assert_eq!(get_list_as_ids(), vec![2, 3, 4, 5, 1]); // when inserting into a non-existent bag @@ -235,7 +235,7 @@ mod list { // then assert_eq!( List::::get_bags(), - vec![(10, vec![1]), (1_000, vec![2, 3, 4, 5]), (2_000, vec![6])] + vec![(10, vec![1]), (1000, vec![2, 3, 4, 5]), (2_000, vec![6])] ); assert_eq!(get_list_as_ids(), vec![6, 2, 3, 4, 5, 1]); }); @@ -274,7 +274,7 @@ mod list { // then assert_eq!(get_list_as_ids(), vec![3, 4, 1]); - assert_eq!(List::::get_bags(), vec![(10, vec![1]), (1_000, vec![3, 4])]); + assert_eq!(List::::get_bags(), vec![(10, vec![1]), (1000, vec![3, 4])]); ensure_left(2, 3); // when removing a node from a bag with only one node: @@ -282,7 +282,7 @@ mod list { // then assert_eq!(get_list_as_ids(), vec![3, 4]); - assert_eq!(List::::get_bags(), vec![(1_000, vec![3, 4])]); + assert_eq!(List::::get_bags(), vec![(1000, vec![3, 4])]); ensure_left(1, 2); // bag 10 is removed assert!(!ListBags::::contains_key(10)); @@ -319,37 +319,36 @@ mod list { ExtBuilder::default().build_and_execute(|| { // given a correctly placed account 1 at bag 10. let node = Node::::get(&1).unwrap(); + assert_eq!(node.score, 10); assert!(!node.is_misplaced(10)); - // .. it is invalid with weight 20 + // .. it is invalid with score 20 assert!(node.is_misplaced(20)); // move it to bag 20. - assert_eq!(List::::update_position_for(node, 20), Some((10, 20))); + assert_eq!(List::::update_position_for(node.clone(), 20), Some((10, 20))); + assert_eq!(Node::::get(&1).unwrap().score, 20); - assert_eq!(List::::get_bags(), vec![(20, vec![1]), (1_000, vec![2, 3, 4])]); + assert_eq!(List::::get_bags(), vec![(20, vec![1]), (1000, vec![2, 3, 4])]); - // get the new updated node; try and update the position with no change in weight. + // get the new updated node; try and update the position with no change in score. let node = Node::::get(&1).unwrap(); assert_storage_noop!(assert_eq!( List::::update_position_for(node.clone(), 20), None )); - // then move it to bag 1_000 by giving it weight 500. - assert_eq!(List::::update_position_for(node.clone(), 500), Some((20, 1_000))); - assert_eq!(List::::get_bags(), vec![(1_000, vec![2, 3, 4, 1])]); + // then move it to bag 1000 by giving it score 500. + assert_eq!(List::::update_position_for(node.clone(), 500), Some((20, 1000))); + assert_eq!(Node::::get(&1).unwrap().score, 500); + assert_eq!(List::::get_bags(), vec![(1000, vec![2, 3, 4, 1])]); // moving within that bag again is a noop let node = Node::::get(&1).unwrap(); - assert_storage_noop!(assert_eq!( - List::::update_position_for(node.clone(), 750), - None, - )); - assert_storage_noop!(assert_eq!( - List::::update_position_for(node, 1_000), - None, - )); + assert_eq!(List::::update_position_for(node.clone(), 750), None); + assert_eq!(Node::::get(&1).unwrap().score, 750); + assert_eq!(List::::update_position_for(node.clone(), 1000), None,); + assert_eq!(Node::::get(&1).unwrap().score, 1000); }); } @@ -433,15 +432,15 @@ mod list { // both nodes are already in the same bag with the correct bag upper. ExtBuilder::default().build_and_execute_no_post_check(|| { // given - assert_eq!(List::::get_bags(), vec![(10, vec![1]), (1_000, vec![2, 3, 4])]); + assert_eq!(List::::get_bags(), vec![(10, vec![1]), (1000, vec![2, 3, 4])]); // implicitly also test that `node`'s `prev`/`next` are correctly re-assigned. let node_42 = Node:: { id: 42, prev: Some(1), next: Some(2), - bag_upper: 1_000, - score: 1_000, + bag_upper: 1000, + score: 1000, _phantom: PhantomData, }; assert!(!crate::ListNodes::::contains_key(42)); @@ -452,10 +451,7 @@ mod list { List::::insert_at_unchecked(node_1, node_42); // then - assert_eq!( - List::::get_bags(), - vec![(10, vec![42, 1]), (1_000, vec![2, 3, 4])] - ); + assert_eq!(List::::get_bags(), vec![(10, vec![42, 1]), (1000, vec![2, 3, 4])]); }) } @@ -463,15 +459,15 @@ mod list { fn insert_at_unchecked_at_is_head() { ExtBuilder::default().build_and_execute(|| { // given - assert_eq!(List::::get_bags(), vec![(10, vec![1]), (1_000, vec![2, 3, 4])]); + assert_eq!(List::::get_bags(), vec![(10, vec![1]), (1000, vec![2, 3, 4])]); // implicitly also test that `node`'s `prev`/`next` are correctly re-assigned. let node_42 = Node:: { id: 42, prev: Some(4), next: None, - bag_upper: 1_000, - score: 1_000, + bag_upper: 1000, + score: 1000, _phantom: PhantomData, }; assert!(!crate::ListNodes::::contains_key(42)); @@ -482,10 +478,7 @@ mod list { List::::insert_at_unchecked(node_2, node_42); // then - assert_eq!( - List::::get_bags(), - vec![(10, vec![1]), (1_000, vec![42, 2, 3, 4])] - ); + assert_eq!(List::::get_bags(), vec![(10, vec![1]), (1000, vec![42, 2, 3, 4])]); }) } @@ -493,15 +486,15 @@ mod list { fn insert_at_unchecked_at_is_non_terminal() { ExtBuilder::default().build_and_execute(|| { // given - assert_eq!(List::::get_bags(), vec![(10, vec![1]), (1_000, vec![2, 3, 4])]); + assert_eq!(List::::get_bags(), vec![(10, vec![1]), (1000, vec![2, 3, 4])]); // implicitly also test that `node`'s `prev`/`next` are correctly re-assigned. let node_42 = Node:: { id: 42, prev: None, next: Some(2), - bag_upper: 1_000, - score: 1_000, + bag_upper: 1000, + score: 1000, _phantom: PhantomData, }; assert!(!crate::ListNodes::::contains_key(42)); @@ -512,10 +505,7 @@ mod list { List::::insert_at_unchecked(node_3, node_42); // then - assert_eq!( - List::::get_bags(), - vec![(10, vec![1]), (1_000, vec![2, 42, 3, 4])] - ); + assert_eq!(List::::get_bags(), vec![(10, vec![1]), (1000, vec![2, 42, 3, 4])]); }) } @@ -523,15 +513,15 @@ mod list { fn insert_at_unchecked_at_is_tail() { ExtBuilder::default().build_and_execute(|| { // given - assert_eq!(List::::get_bags(), vec![(10, vec![1]), (1_000, vec![2, 3, 4])]); + assert_eq!(List::::get_bags(), vec![(10, vec![1]), (1000, vec![2, 3, 4])]); // implicitly also test that `node`'s `prev`/`next` are correctly re-assigned. let node_42 = Node:: { id: 42, prev: Some(42), next: Some(42), - bag_upper: 1_000, - score: 1_000, + bag_upper: 1000, + score: 1000, _phantom: PhantomData, }; assert!(!crate::ListNodes::::contains_key(42)); @@ -542,10 +532,7 @@ mod list { List::::insert_at_unchecked(node_4, node_42); // then - assert_eq!( - List::::get_bags(), - vec![(10, vec![1]), (1_000, vec![2, 3, 42, 4])] - ); + assert_eq!(List::::get_bags(), vec![(10, vec![1]), (1000, vec![2, 3, 42, 4])]); }) } } @@ -564,17 +551,17 @@ mod bags { assert_eq!(bag_ids, ids); }; - assert_eq!(List::::get_bags(), vec![(10, vec![1]), (1_000, vec![2, 3, 4])]); + assert_eq!(List::::get_bags(), vec![(10, vec![1]), (1000, vec![2, 3, 4])]); // we can fetch them check_bag(10, Some(1), Some(1), vec![1]); - check_bag(1_000, Some(2), Some(4), vec![2, 3, 4]); + check_bag(1000, Some(2), Some(4), vec![2, 3, 4]); // and all other bag thresholds don't get bags. ::BagThresholds::get() .iter() .chain(iter::once(&VoteWeight::MAX)) - .filter(|bag_upper| !vec![10, 1_000].contains(bag_upper)) + .filter(|bag_upper| !vec![10, 1000].contains(bag_upper)) .for_each(|bag_upper| { assert_storage_noop!(assert_eq!(Bag::::get(*bag_upper), None)); assert!(!ListBags::::contains_key(*bag_upper)); @@ -600,7 +587,7 @@ mod bags { _phantom: PhantomData, }; - assert_eq!(List::::get_bags(), vec![(10, vec![1]), (1_000, vec![2, 3, 4])]); + assert_eq!(List::::get_bags(), vec![(10, vec![1]), (1000, vec![2, 3, 4])]); let mut bag_10 = Bag::::get(10).unwrap(); bag_10.insert_node_unchecked(node(42, 5)); @@ -610,7 +597,7 @@ mod bags { ListNodes::::get(&42).unwrap(), Node { bag_upper: 10, - score: 10, + score: 5, prev: Some(1), next: None, id: 42, @@ -639,7 +626,7 @@ mod bags { assert_eq!(bag_as_ids(&bag_10), vec![1, 42]); // when inserting into a bag with 3 nodes - let mut bag_1000 = Bag::::get(1_000).unwrap(); + let mut bag_1000 = Bag::::get(1000).unwrap(); bag_1000.insert_node_unchecked(node(52, bag_1000.bag_upper)); // then assert_eq!(bag_as_ids(&bag_1000), vec![2, 3, 4, 52]); @@ -679,7 +666,7 @@ mod bags { bag_20.put(); // need to put this newly created bag so its in the storage map assert_eq!( List::::get_bags(), - vec![(10, vec![1, 42]), (20, vec![62, 61]), (1_000, vec![2, 3, 4, 52])] + vec![(10, vec![1, 42]), (20, vec![62, 61]), (1000, vec![2, 3, 4, 52])] ); }); } @@ -690,7 +677,7 @@ mod bags { ExtBuilder::default().build_and_execute_no_post_check(|| { // when inserting a node with both prev & next pointing at an account in an incorrect // bag. - let mut bag_1000 = Bag::::get(1_000).unwrap(); + let mut bag_1000 = Bag::::get(1000).unwrap(); bag_1000.insert_node_unchecked(node(42, Some(1), Some(1), 500)); // then the proper prev and next is set. @@ -699,13 +686,20 @@ mod bags { // and when the node is re-fetched all the info is correct assert_eq!( Node::::get(&42).unwrap(), - node(42, Some(4), None, bag_1000.bag_upper) + Node:: { + id: 42, + prev: Some(4), + next: None, + bag_upper: bag_1000.bag_upper, + score: 500, + _phantom: PhantomData + } ); }); ExtBuilder::default().build_and_execute_no_post_check(|| { // given 3 is in bag_1000 (and not a tail node) - let mut bag_1000 = Bag::::get(1_000).unwrap(); + let mut bag_1000 = Bag::::get(1000).unwrap(); assert_eq!(bag_as_ids(&bag_1000), vec![2, 3, 4]); // when inserting a node with duplicate id 3 @@ -725,7 +719,7 @@ mod bags { ExtBuilder::default().build_and_execute_no_post_check(|| { // when inserting a duplicate id of the head - let mut bag_1000 = Bag::::get(1_000).unwrap(); + let mut bag_1000 = Bag::::get(1000).unwrap(); assert_eq!(bag_as_ids(&bag_1000), vec![2, 3, 4]); bag_1000.insert_node_unchecked(node(2, None, None, 0)); @@ -735,13 +729,20 @@ mod bags { // and the re-fetched node has bad pointers assert_eq!( Node::::get(&2).unwrap(), - node(2, Some(4), None, bag_1000.bag_upper) + Node:: { + id: 2, + prev: Some(4), + next: None, + bag_upper: bag_1000.bag_upper, + score: 0, + _phantom: PhantomData + }, ); - // ^^^ despite being the bags head, it has a prev + // ^^ ^ despite being the bags head, it has a prev assert_eq!( bag_1000, - Bag { head: Some(2), tail: Some(2), bag_upper: 1_000, _phantom: PhantomData } + Bag { head: Some(2), tail: Some(2), bag_upper: 1000, _phantom: PhantomData } ) }); } @@ -755,8 +756,8 @@ mod bags { fn insert_node_duplicate_tail_panics_with_debug_assert() { ExtBuilder::default().build_and_execute(|| { // given - assert_eq!(List::::get_bags(), vec![(10, vec![1]), (1_000, vec![2, 3, 4])],); - let mut bag_1000 = Bag::::get(1_000).unwrap(); + assert_eq!(List::::get_bags(), vec![(10, vec![1]), (1000, vec![2, 3, 4])],); + let mut bag_1000 = Bag::::get(1000).unwrap(); // when inserting a duplicate id that is already the tail assert_eq!(bag_1000.tail, Some(4)); @@ -772,8 +773,8 @@ mod bags { .add_ids(vec![ (11, 10), (12, 10), - (13, 1_000), - (14, 1_000), + (13, 1000), + (14, 1000), (15, 2_000), (16, 2_000), (17, 2_000), @@ -782,7 +783,7 @@ mod bags { ]) .build_and_execute_no_post_check(|| { let mut bag_10 = Bag::::get(10).unwrap(); - let mut bag_1000 = Bag::::get(1_000).unwrap(); + let mut bag_1000 = Bag::::get(1000).unwrap(); let mut bag_2000 = Bag::::get(2_000).unwrap(); // given @@ -831,7 +832,7 @@ mod bags { bag_1000.put(); // put into storage so `get` returns the updated bag // then - assert_eq!(Bag::::get(1_000), None); + assert_eq!(Bag::::get(1000), None); // when removing a node that is pointing at both the head & tail let node_11 = Node::::get(&11).unwrap(); @@ -883,20 +884,20 @@ mod bags { id: 2, prev: None, next: Some(3), - bag_upper: 10, // should be 1_000 + bag_upper: 10, // should be 1000 score: 10, _phantom: PhantomData, }; - let mut bag_1000 = Bag::::get(1_000).unwrap(); + let mut bag_1000 = Bag::::get(1000).unwrap(); // when removing a node that is in the bag but has the wrong upper bag_1000.remove_node_unchecked(&bad_upper_node_2); bag_1000.put(); // then the node is no longer in any bags - assert_eq!(List::::get_bags(), vec![(10, vec![1]), (1_000, vec![3, 4])]); + assert_eq!(List::::get_bags(), vec![(10, vec![1]), (1000, vec![3, 4])]); // .. and the bag it was removed from - let bag_1000 = Bag::::get(1_000).unwrap(); + let bag_1000 = Bag::::get(1000).unwrap(); // is sane assert_ok!(bag_1000.sanity_check()); // and has the correct head and tail. @@ -906,7 +907,7 @@ mod bags { // Removing a node that is in another bag, will mess up that other bag. ExtBuilder::default().build_and_execute_no_post_check(|| { - // given a tail node is in bag 1_000 + // given a tail node is in bag 1000 let node_4 = Node::::get(&4).unwrap(); // when we remove it from bag 10 @@ -920,7 +921,7 @@ mod bags { assert_eq!(bag_10.head, Some(1)); // but the bag that the node belonged to is in an invalid state - let bag_1000 = Bag::::get(1_000).unwrap(); + let bag_1000 = Bag::::get(1000).unwrap(); // because it still has the removed node as its tail. assert_eq!(bag_1000.tail, Some(4)); assert_eq!(bag_1000.head, Some(2)); diff --git a/frame/bags-list/src/tests.rs b/frame/bags-list/src/tests.rs index 21cb37031b141..62ab3fdbdbe8c 100644 --- a/frame/bags-list/src/tests.rs +++ b/frame/bags-list/src/tests.rs @@ -34,7 +34,7 @@ mod pallet { vec![(10, vec![1]), (20, vec![42]), (1_000, vec![2, 3, 4])] ); - // when increasing vote weight to the level of non-existent bag + // when increasing vote score to the level of non-existent bag StakingMock::set_score_of(&42, 2_000); assert_ok!(BagsList::rebag(Origin::signed(0), 42)); @@ -44,7 +44,7 @@ mod pallet { vec![(10, vec![1]), (1_000, vec![2, 3, 4]), (2_000, vec![42])] ); - // when decreasing weight within the range of the current bag + // when decreasing score within the range of the current bag StakingMock::set_score_of(&42, 1_001); assert_ok!(BagsList::rebag(Origin::signed(0), 42)); @@ -54,7 +54,7 @@ mod pallet { vec![(10, vec![1]), (1_000, vec![2, 3, 4]), (2_000, vec![42])] ); - // when reducing weight to the level of a non-existent bag + // when reducing score to the level of a non-existent bag StakingMock::set_score_of(&42, 30); assert_ok!(BagsList::rebag(Origin::signed(0), 42)); @@ -64,7 +64,7 @@ mod pallet { vec![(10, vec![1]), (30, vec![42]), (1_000, vec![2, 3, 4])] ); - // when increasing weight to the level of a pre-existing bag + // when increasing score to the level of a pre-existing bag StakingMock::set_score_of(&42, 500); assert_ok!(BagsList::rebag(Origin::signed(0), 42)); @@ -148,11 +148,11 @@ mod pallet { fn wrong_rebag_is_noop() { ExtBuilder::default().build_and_execute(|| { let node_3 = list::Node::::get(&3).unwrap(); - // when account 3 is _not_ misplaced with weight 500 + // when account 3 is _not_ misplaced with score 500 NextVoteWeight::set(500); assert!(!node_3.is_misplaced(500)); - // then calling rebag on account 3 with weight 500 is a noop + // then calling rebag on account 3 with score 500 is a noop assert_storage_noop!(assert_eq!(BagsList::rebag(Origin::signed(0), 3), Ok(()))); // when account 42 is not in the list @@ -475,7 +475,10 @@ mod sorted_list_provider { assert_eq!(BagsList::count(), 4); // when updating - BagsList::on_update(&201, VoteWeight::MAX).unwrap(); + assert_storage_noop!(assert_eq!( + BagsList::on_update(&201, VoteWeight::MAX).unwrap_err(), + Error::NonExistent + )); // then the count stays the same assert_eq!(BagsList::count(), 4); }); @@ -533,7 +536,7 @@ mod sorted_list_provider { ); assert_eq!(BagsList::count(), 5); - // when increasing weight to the level of non-existent bag + // when increasing score to the level of non-existent bag BagsList::on_update(&42, 2_000).unwrap(); // then the bag is created with the id in it, @@ -544,7 +547,7 @@ mod sorted_list_provider { // and the id position is updated in the list. assert_eq!(BagsList::iter().collect::>(), vec![42, 2, 3, 4, 1]); - // when decreasing weight within the range of the current bag + // when decreasing score within the range of the current bag BagsList::on_update(&42, 1_001).unwrap(); // then the id does not change bags, @@ -555,7 +558,7 @@ mod sorted_list_provider { // or change position in the list. assert_eq!(BagsList::iter().collect::>(), vec![42, 2, 3, 4, 1]); - // when increasing weight to the level of a non-existent bag with the max threshold + // when increasing score to the level of a non-existent bag with the max threshold BagsList::on_update(&42, VoteWeight::MAX).unwrap(); // the the new bag is created with the id in it, @@ -566,7 +569,7 @@ mod sorted_list_provider { // and the id position is updated in the list. assert_eq!(BagsList::iter().collect::>(), vec![42, 2, 3, 4, 1]); - // when decreasing the weight to a pre-existing bag + // when decreasing the score to a pre-existing bag BagsList::on_update(&42, 1_000).unwrap(); // then id is moved to the correct bag (as the last member), diff --git a/frame/staking/src/migrations.rs b/frame/staking/src/migrations.rs index c44d1866fbe25..2a6c33accbb54 100644 --- a/frame/staking/src/migrations.rs +++ b/frame/staking/src/migrations.rs @@ -17,7 +17,7 @@ //! Storage migrations for the Staking pallet. use super::*; -use frame_election_provider_support::{ScoreProvider, SortedListProvider, VoteWeight}; +use frame_election_provider_support::{SortedListProvider, VoteWeight}; use frame_support::traits::{Defensive, OnRuntimeUpgrade}; use sp_std::collections::btree_map::BTreeMap; diff --git a/frame/staking/src/pallet/impls.rs b/frame/staking/src/pallet/impls.rs index 4dc5d043b8c50..afad4c556aff2 100644 --- a/frame/staking/src/pallet/impls.rs +++ b/frame/staking/src/pallet/impls.rs @@ -45,7 +45,7 @@ use sp_std::{collections::btree_map::BTreeMap, prelude::*}; use crate::{ log, slashing, weights::WeightInfo, ActiveEraInfo, BalanceOf, EraPayout, Exposure, ExposureOf, Forcing, IndividualExposure, Nominations, PositiveImbalanceOf, RewardDestination, - SessionInterface, StakingLedger, UnboundedNominations, ValidatorPrefs, + SessionInterface, StakingLedger, ValidatorPrefs, }; use super::{pallet::*, STAKING_ID}; diff --git a/frame/staking/src/pallet/mod.rs b/frame/staking/src/pallet/mod.rs index cf35b68bcc3d8..950b112a37abe 100644 --- a/frame/staking/src/pallet/mod.rs +++ b/frame/staking/src/pallet/mod.rs @@ -1488,7 +1488,7 @@ pub mod pallet { Self::update_ledger(&controller, &ledger); if T::VoterList::contains(&ledger.stash) { - T::VoterList::on_update(&ledger.stash, Self::weight_of(&ledger.stash)); + T::VoterList::on_update(&ledger.stash, Self::weight_of(&ledger.stash)).defensive(); } let removed_chunks = 1u32 // for the case where the last iterated chunk is not removed From 93d79a4539fc853f966caba6571ef26657cfaad0 Mon Sep 17 00:00:00 2001 From: kianenigma Date: Fri, 11 Mar 2022 10:24:05 +0000 Subject: [PATCH 16/42] dead-end with restricted nominations --- frame/staking/src/migrations.rs | 17 +- frame/staking/src/mock.rs | 26 +- frame/staking/src/pallet/impls.rs | 90 +++-- frame/staking/src/pallet/mod.rs | 53 +-- frame/staking/src/tests.rs | 573 +++++++++++++++++++----------- frame/support/src/traits/misc.rs | 25 ++ 6 files changed, 503 insertions(+), 281 deletions(-) diff --git a/frame/staking/src/migrations.rs b/frame/staking/src/migrations.rs index 2a6c33accbb54..9527968c85e20 100644 --- a/frame/staking/src/migrations.rs +++ b/frame/staking/src/migrations.rs @@ -74,20 +74,21 @@ impl InjectValidatorsApprovalStakeIntoTargetList { pub(crate) fn build_approval_stakes() -> BTreeMap { let mut approval_stakes = BTreeMap::::new(); - NominatorsHelper::::iter_all().for_each(|(who, nomination)| { - let stake = Pallet::::weight_of(&who); - for target in nomination.targets { - let current = approval_stakes.entry(target).or_default(); - *current = current.saturating_add(stake); - } - }); - Validators::::iter().for_each(|(v, _)| { let stake = Pallet::::weight_of(&v); let current = approval_stakes.entry(v).or_default(); *current = current.saturating_add(stake); }); + NominatorsHelper::::iter_all().for_each(|(who, nomination)| { + let stake = Pallet::::weight_of(&who); + for target in nomination.targets { + if let Some(current) = approval_stakes.get_mut(&target) { + *current = current.saturating_add(stake); + } + } + }); + approval_stakes } } diff --git a/frame/staking/src/mock.rs b/frame/staking/src/mock.rs index 96500fe4183c4..e9af4d91c5b84 100644 --- a/frame/staking/src/mock.rs +++ b/frame/staking/src/mock.rs @@ -820,13 +820,6 @@ pub(crate) fn reward_all_elected() { >::reward_by_ids(rewards) } -pub(crate) fn validator_controllers() -> Vec { - Session::validators() - .into_iter() - .map(|s| Staking::bonded(&s).expect("no controller for validator")) - .collect() -} - pub(crate) fn on_offence_in_era( offenders: &[OffenceDetails< AccountId, @@ -925,3 +918,22 @@ pub(crate) fn staking_events() -> Vec> { pub(crate) fn balances(who: &AccountId) -> (Balance, Balance) { (Balances::free_balance(who), Balances::reserved_balance(who)) } + +pub(crate) fn validator_ids() -> Vec { + Validators::::iter().map(|(v, _)| v).collect::>() +} + +pub(crate) fn nominator_ids() -> Vec { + Nominators::::iter().map(|(n, _)| n).collect::>() +} + +pub(crate) fn nominator_targets(who: AccountId) -> Vec { + Nominators::::get(&who).map(|n| n.targets).unwrap().into_inner() +} + +pub(crate) fn validator_controllers() -> Vec { + Session::validators() + .into_iter() + .map(|s| Staking::bonded(&s).expect("no controller for validator")) + .collect() +} diff --git a/frame/staking/src/pallet/impls.rs b/frame/staking/src/pallet/impls.rs index afad4c556aff2..5a6496387f087 100644 --- a/frame/staking/src/pallet/impls.rs +++ b/frame/staking/src/pallet/impls.rs @@ -212,22 +212,18 @@ impl Pallet { >::insert(controller, ledger); let total_issuance = T::Currency::total_issuance(); - let apply_change = |who: &T::AccountId| { + let to_vote = |x| T::CurrencyToVote::to_vote(x, total_issuance); + + let update_target_list = |who: &T::AccountId| { use sp_std::cmp::Ordering; match ledger.active.cmp(&prev_active) { Ordering::Greater => { - let _ = T::TargetList::on_increase( - who, - T::CurrencyToVote::to_vote(ledger.active - prev_active, total_issuance), - ) - .defensive(); + let _ = T::TargetList::on_increase(who, to_vote(ledger.active - prev_active)) + .defensive(); }, Ordering::Less => { - let _ = T::TargetList::on_decrease( - who, - T::CurrencyToVote::to_vote(prev_active - ledger.active, total_issuance), - ) - .defensive(); + let _ = T::TargetList::on_decrease(who, to_vote(prev_active - ledger.active)) + .defensive(); }, Ordering::Equal => (), }; @@ -237,16 +233,26 @@ impl Pallet { if let Some(targets) = NominatorsHelper::::get_any(&ledger.stash).map(|nomination| nomination.targets) { + // update the target list. for target in targets { - apply_change(&target); + update_target_list(&target); } + + // update the voter list. + let _ = T::VoterList::on_update(&ledger.stash, to_vote(ledger.active)) + .defensive_proof("any nominator should an entry in the voter list."); + #[cfg(debug_assertions)] Self::sanity_check_list_providers(); } // if this ledger belonged to a validator.. if Validators::::contains_key(&ledger.stash) { - apply_change(&ledger.stash); + update_target_list(&ledger.stash); + + let _ = T::VoterList::on_update(&ledger.stash, to_vote(ledger.active)) + .defensive_proof("any validator should an entry in the voter list."); + #[cfg(debug_assertions)] Self::sanity_check_list_providers(); } @@ -259,6 +265,9 @@ impl Pallet { if chilled_as_validator || chilled_as_nominator { Self::deposit_event(Event::::Chilled(stash.clone())); } + + #[cfg(debug_assertions)] + Self::sanity_check_approval_stakes(); } /// Actually make a payment to a staker. This uses the currency's reward function @@ -610,6 +619,9 @@ impl Pallet { Self::do_remove_validator(stash); Self::do_remove_nominator(stash); + #[cfg(debug_assertions)] + Self::sanity_check_approval_stakes(); + >::remove(stash); >::remove(&controller); >::remove(stash); @@ -808,13 +820,42 @@ impl Pallet { /// NOTE: you must ALWAYS use this function to add nominator or update their targets. Any access /// to `Nominators` or `VoterList` outside of this function is almost certainly /// wrong. - pub fn do_add_nominator(who: &T::AccountId, nominations: Nominations) { - if !NominatorsHelper::::contains_any(who) { + pub fn do_add_nominator( + stash: &T::AccountId, + old: Vec, + nominations: Nominations, + ) { + if !NominatorsHelper::::contains_any(stash) { // maybe update sorted list. - let _ = T::VoterList::on_insert(who.clone(), Self::weight_of(who)) - .defensive_unwrap_or_default(); + let _ = T::VoterList::on_insert(stash.clone(), Self::weight_of(stash)).defensive(); } - Nominators::::insert(who, nominations); + + let incoming = nominations.targets.iter().filter(|x| !old.contains(x)).collect::>(); + let outgoing = + old.into_iter().filter(|x| !nominations.targets.contains(x)).collect::>(); + + // TODO: edge case: some wanker nominating themselves? should only be possible if they are a + // validator and now they nominate themselves. + // TODO: these are all rather inefficient now based on how vote_weight is + // implemented, but I don't care because: https://github.com/paritytech/substrate/issues/10990 + let weight = Self::weight_of(stash); + incoming.into_iter().for_each(|i| { + if T::TargetList::contains(i) { + let _ = T::TargetList::on_increase(i, weight).defensive(); + } else { + defensive!("no incoming target can not have an entry in the target-list"); + } + }); + outgoing.into_iter().for_each(|o| { + if T::TargetList::contains(&o) { + let _ = T::TargetList::on_decrease(&o, weight); + } else { + // probably the validator has already been chilled and their target list entry + // removed. + } + }); + + Nominators::::insert(stash, nominations); #[cfg(debug_assertions)] Self::sanity_check_list_providers(); @@ -861,8 +902,7 @@ impl Pallet { pub fn do_add_validator(who: &T::AccountId, prefs: ValidatorPrefs) { if !Validators::::contains_key(who) { // maybe update sorted list. - let _ = T::VoterList::on_insert(who.clone(), Self::weight_of(who)) - .defensive_unwrap_or_default(); + let _ = T::VoterList::on_insert(who.clone(), Self::weight_of(who)).defensive(); if T::TargetList::contains(who) { let _ = T::TargetList::on_increase(who, Self::weight_of(who)).defensive(); @@ -890,7 +930,8 @@ impl Pallet { let outcome = if Validators::::contains_key(who) { Validators::::remove(who); let _ = T::VoterList::on_remove(who).defensive(); - let _ = T::TargetList::on_decrease(who, Self::weight_of(who)).defensive(); + // NOTE: simply remove, not decrease. + let _ = T::TargetList::on_remove(who).defensive(); true } else { false @@ -1024,7 +1065,11 @@ impl ElectionDataProvider for Pallet { }, ); - Self::do_add_nominator(&voter, Nominations { targets, submitted_in: 0, suppressed: false }); + Self::do_add_nominator( + &voter, + Default::default(), + Nominations { targets, submitted_in: 0, suppressed: false }, + ); } #[cfg(feature = "runtime-benchmarks")] @@ -1101,6 +1146,7 @@ impl ElectionDataProvider for Pallet { ); Self::do_add_nominator( &v, + Default::default(), Nominations { targets: t.try_into().unwrap(), submitted_in: 0, suppressed: false }, ); }); diff --git a/frame/staking/src/pallet/mod.rs b/frame/staking/src/pallet/mod.rs index 950b112a37abe..6b33c4e4108ce 100644 --- a/frame/staking/src/pallet/mod.rs +++ b/frame/staking/src/pallet/mod.rs @@ -878,16 +878,7 @@ pub mod pallet { Error::::InsufficientBond ); - // NOTE: ledger must be updated prior to calling `Self::weight_of`. Self::update_ledger(&controller, &ledger); - - // update this staker in the sorted list, if they exist in it. - if T::VoterList::contains(&stash) { - let _ = - T::VoterList::on_update(&stash, Self::weight_of(&ledger.stash)).defensive(); - debug_assert_eq!(T::VoterList::sanity_check(), Ok(())); - } - Self::deposit_event(Event::::Bonded(stash.clone(), extra)); } Ok(()) @@ -962,16 +953,8 @@ pub mod pallet { .try_push(UnlockChunk { value, era }) .map_err(|_| Error::::NoMoreChunks)?; }; - // NOTE: ledger must be updated prior to calling `Self::weight_of`. - Self::update_ledger(&controller, &ledger); - - // TODO: we do some of the update in update ledger and some here... not good. - // update this staker in the sorted list, if they exist in it. - if T::VoterList::contains(&ledger.stash) { - let _ = T::VoterList::on_update(&ledger.stash, Self::weight_of(&ledger.stash)) - .defensive(); - } + Self::update_ledger(&controller, &ledger); Self::deposit_event(Event::::Unbonded(ledger.stash, value)); } Ok(()) @@ -1066,6 +1049,10 @@ pub mod pallet { Self::do_remove_nominator(stash); Self::do_add_validator(stash, prefs); + + #[cfg(debug_assertions)] + Self::sanity_check_approval_stakes(); + Ok(()) } @@ -1113,7 +1100,9 @@ pub mod pallet { .map(|t| T::Lookup::lookup(t).map_err(DispatchError::from)) .map(|n| { n.and_then(|n| { - if old.contains(&n) || !Validators::::get(&n).blocked { + if Validators::::contains_key(&n) && + (old.contains(&n) || !Validators::::get(&n).blocked) + { Ok(n) } else { Err(Error::::BadTarget.into()) @@ -1133,27 +1122,6 @@ pub mod pallet { Error::::DuplicateTarget ); - let incoming = targets.iter().cloned().filter(|x| !old.contains(x)).collect::>(); - let outgoing = old.iter().cloned().filter(|x| !targets.contains(x)).collect::>(); - - // TODO: these are all rather inefficient now based on how vote_weight is - // implemented, but I don't care because: https://github.com/paritytech/substrate/issues/10990 - let weight = Self::weight_of(stash); - incoming.into_iter().for_each(|i| { - if T::TargetList::contains(&i) { - let _ = T::TargetList::on_increase(&i, weight).defensive(); - } else { - let _ = T::TargetList::on_insert(i, weight).defensive(); - } - }); - outgoing.into_iter().for_each(|o| { - if T::TargetList::contains(&o) { - let _ = T::TargetList::on_decrease(&o, weight); - } else { - frame_support::defensive!("this validator must have, at some point in the past, been inserted into `TargetList`"); - } - }); - let nominations = Nominations { targets, // Initial nominations are considered submitted at era 0. See `Nominations` doc. @@ -1162,7 +1130,7 @@ pub mod pallet { }; Self::do_remove_validator(stash); - Self::do_add_nominator(stash, nominations); + Self::do_add_nominator(stash, old, nominations); // NOTE: we need to do this after all validators and nominators have been updated in the // previous two function calls. @@ -1488,7 +1456,8 @@ pub mod pallet { Self::update_ledger(&controller, &ledger); if T::VoterList::contains(&ledger.stash) { - T::VoterList::on_update(&ledger.stash, Self::weight_of(&ledger.stash)).defensive(); + let _ = T::VoterList::on_update(&ledger.stash, Self::weight_of(&ledger.stash)) + .defensive(); } let removed_chunks = 1u32 // for the case where the last iterated chunk is not removed diff --git a/frame/staking/src/tests.rs b/frame/staking/src/tests.rs index 07b35b8a6122c..a69ee18df5270 100644 --- a/frame/staking/src/tests.rs +++ b/frame/staking/src/tests.rs @@ -192,7 +192,7 @@ fn basic_setup_works() { claimed_rewards: vec![] }) ); - assert_eq!(Nominators::::get(&101).unwrap().targets, vec![11, 21]); + assert_eq!(nominator_targets(101), vec![11, 21]); assert_eq!( Staking::eras_stakers(active_era(), 11), @@ -720,6 +720,7 @@ fn double_staking_should_fail() { // * an account already bonded as stash cannot nominate. // * an account already bonded as controller can nominate. ExtBuilder::default().build_and_execute(|| { + let some_validator = validator_ids().first().cloned().unwrap(); let arbitrary_value = 5; // 2 = controller, 1 stashed => ok assert_ok!(Staking::bond( @@ -734,9 +735,12 @@ fn double_staking_should_fail() { Error::::AlreadyBonded, ); // 1 = stashed => attempting to nominate should fail. - assert_noop!(Staking::nominate(Origin::signed(1), vec![1]), Error::::NotController); + assert_noop!( + Staking::nominate(Origin::signed(1), vec![some_validator]), + Error::::NotController + ); // 2 = controller => nominating should work. - assert_ok!(Staking::nominate(Origin::signed(2), vec![1])); + assert_ok!(Staking::nominate(Origin::signed(2), vec![some_validator])); }); } @@ -1741,81 +1745,6 @@ fn reap_stash_works() { }); } -#[test] -fn switching_roles() { - // Test that it should be possible to switch between roles (nominator, validator, idle) with - // minimal overhead. - ExtBuilder::default().nominate(false).build_and_execute(|| { - // Reset reward destination - for i in &[10, 20] { - assert_ok!(Staking::set_payee(Origin::signed(*i), RewardDestination::Controller)); - } - - assert_eq_uvec!(validator_controllers(), vec![20, 10]); - - // put some money in account that we'll use. - for i in 1..7 { - let _ = Balances::deposit_creating(&i, 5000); - } - - // add 2 nominators - assert_ok!(Staking::bond(Origin::signed(1), 2, 2000, RewardDestination::Controller)); - assert_ok!(Staking::nominate(Origin::signed(2), vec![11, 5])); - - assert_ok!(Staking::bond(Origin::signed(3), 4, 500, RewardDestination::Controller)); - assert_ok!(Staking::nominate(Origin::signed(4), vec![21, 1])); - - // add a new validator candidate - assert_ok!(Staking::bond(Origin::signed(5), 6, 1000, RewardDestination::Controller)); - assert_ok!(Staking::validate(Origin::signed(6), ValidatorPrefs::default())); - assert_ok!(Session::set_keys(Origin::signed(6), SessionKeys { other: 6.into() }, vec![])); - - mock::start_active_era(1); - - // with current nominators 10 and 5 have the most stake - assert_eq_uvec!(validator_controllers(), vec![6, 10]); - - // 2 decides to be a validator. Consequences: - assert_ok!(Staking::validate(Origin::signed(2), ValidatorPrefs::default())); - assert_ok!(Session::set_keys(Origin::signed(2), SessionKeys { other: 2.into() }, vec![])); - // new stakes: - // 10: 1000 self vote - // 20: 1000 self vote + 250 vote - // 6 : 1000 self vote - // 2 : 2000 self vote + 250 vote. - // Winners: 20 and 2 - - mock::start_active_era(2); - - assert_eq_uvec!(validator_controllers(), vec![2, 20]); - }); -} - -#[test] -fn wrong_vote_is_moot() { - ExtBuilder::default() - .add_staker( - 61, - 60, - 500, - StakerStatus::Nominator(vec![ - 11, 21, // good votes - 1, 2, 15, 1000, 25, // crap votes. No effect. - ]), - ) - .build_and_execute(|| { - // the genesis validators already reflect the above vote, nonetheless start a new era. - mock::start_active_era(1); - - // new validators - assert_eq_uvec!(validator_controllers(), vec![20, 10]); - - // our new voter is taken into account - assert!(Staking::eras_stakers(active_era(), 11).others.iter().any(|i| i.who == 61)); - assert!(Staking::eras_stakers(active_era(), 21).others.iter().any(|i| i.who == 61)); - }); -} - #[test] fn bond_with_no_staked_value() { // Behavior when someone bonds with no staked value. @@ -1932,28 +1861,6 @@ fn bond_with_little_staked_value_bounded() { }); } -#[test] -fn duplicate_nomination_prevented() { - ExtBuilder::default().nominate(true).validator_count(1).build_and_execute(|| { - // resubmit and it is back - assert_noop!( - Staking::nominate(Origin::signed(100), vec![11, 11, 21]), - Error::::DuplicateTarget - ); - - assert_noop!( - Staking::nominate(Origin::signed(100), vec![11, 21, 11,]), - Error::::DuplicateTarget - ); - - assert_noop!( - Staking::nominate(Origin::signed(100), vec![21, 11, 31, 11]), - Error::::DuplicateTarget - ); - assert_ok!(Staking::nominate(Origin::signed(100), vec![21, 11])); - }) -} - #[test] fn new_era_elects_correct_number_of_validators() { ExtBuilder::default().nominate(true).validator_count(1).build_and_execute(|| { @@ -4031,7 +3938,10 @@ mod election_data_provider { vec![21] ); - // resubmit and it is back + // they re-validate again. + assert_ok!(Staking::validate(Origin::signed(10), ValidatorPrefs::default())); + + // resubmit and it is back. assert_ok!(Staking::nominate(Origin::signed(100), vec![11, 21])); assert_eq!( ::voters(None) @@ -4249,9 +4159,10 @@ fn min_bond_checks_work() { .min_validator_bond(1_500) .build_and_execute(|| { // 500 is not enough for any role + let some_validator = validator_ids().first().cloned().unwrap(); assert_ok!(Staking::bond(Origin::signed(3), 4, 500, RewardDestination::Controller)); assert_noop!( - Staking::nominate(Origin::signed(4), vec![1]), + Staking::nominate(Origin::signed(4), vec![some_validator]), Error::::InsufficientBond ); assert_noop!( @@ -4261,7 +4172,7 @@ fn min_bond_checks_work() { // 1000 is enough for nominator assert_ok!(Staking::bond_extra(Origin::signed(3), 500)); - assert_ok!(Staking::nominate(Origin::signed(4), vec![1])); + assert_ok!(Staking::nominate(Origin::signed(4), vec![some_validator])); assert_noop!( Staking::validate(Origin::signed(4), ValidatorPrefs::default()), Error::::InsufficientBond, @@ -4269,14 +4180,14 @@ fn min_bond_checks_work() { // 1500 is enough for validator assert_ok!(Staking::bond_extra(Origin::signed(3), 500)); - assert_ok!(Staking::nominate(Origin::signed(4), vec![1])); + assert_ok!(Staking::nominate(Origin::signed(4), vec![some_validator])); assert_ok!(Staking::validate(Origin::signed(4), ValidatorPrefs::default())); // Can't unbond anything as validator assert_noop!(Staking::unbond(Origin::signed(4), 500), Error::::InsufficientBond); // Once they are a nominator, they can unbond 500 - assert_ok!(Staking::nominate(Origin::signed(4), vec![1])); + assert_ok!(Staking::nominate(Origin::signed(4), vec![some_validator])); assert_ok!(Staking::unbond(Origin::signed(4), 500)); assert_noop!(Staking::unbond(Origin::signed(4), 500), Error::::InsufficientBond); @@ -4294,6 +4205,7 @@ fn chill_other_works() { .min_nominator_bond(1_000) .min_validator_bond(1_500) .build_and_execute(|| { + let some_validator = validator_ids().first().cloned().unwrap(); let initial_validators = Validators::::count(); let initial_nominators = Nominators::::count(); for i in 0..15 { @@ -4313,7 +4225,7 @@ fn chill_other_works() { 1000, RewardDestination::Controller )); - assert_ok!(Staking::nominate(Origin::signed(b), vec![1])); + assert_ok!(Staking::nominate(Origin::signed(b), vec![some_validator])); // Validator assert_ok!(Staking::bond( @@ -4464,8 +4376,9 @@ fn capped_stakers_works() { // can create `max - validator_count` validators let mut some_existing_validator = AccountId::default(); + let mut some_existing_stash = AccountId::default(); for i in 0..max - validator_count { - let (_, controller) = testing_utils::create_stash_controller::( + let (stash, controller) = testing_utils::create_stash_controller::( i + 10_000_000, 100, RewardDestination::Controller, @@ -4473,6 +4386,7 @@ fn capped_stakers_works() { .unwrap(); assert_ok!(Staking::validate(Origin::signed(controller), ValidatorPrefs::default())); some_existing_validator = controller; + some_existing_stash = stash; } // but no more @@ -4497,7 +4411,7 @@ fn capped_stakers_works() { RewardDestination::Controller, ) .unwrap(); - assert_ok!(Staking::nominate(Origin::signed(controller), vec![1])); + assert_ok!(Staking::nominate(Origin::signed(controller), vec![some_existing_stash])); some_existing_nominator = controller; } @@ -4509,12 +4423,15 @@ fn capped_stakers_works() { ) .unwrap(); assert_noop!( - Staking::nominate(Origin::signed(last_nominator), vec![1]), + Staking::nominate(Origin::signed(last_nominator), vec![some_existing_stash]), Error::::TooManyNominators ); // Re-nominate works fine - assert_ok!(Staking::nominate(Origin::signed(some_existing_nominator), vec![1])); + assert_ok!(Staking::nominate( + Origin::signed(some_existing_nominator), + vec![some_existing_stash] + )); // Re-validate works fine assert_ok!(Staking::validate( Origin::signed(some_existing_validator), @@ -4531,7 +4448,7 @@ fn capped_stakers_works() { ConfigOp::Noop, ConfigOp::Noop, )); - assert_ok!(Staking::nominate(Origin::signed(last_nominator), vec![1])); + assert_ok!(Staking::nominate(Origin::signed(last_nominator), vec![some_existing_stash])); assert_ok!(Staking::validate(Origin::signed(last_validator), ValidatorPrefs::default())); }) } @@ -4580,6 +4497,9 @@ fn min_commission_works() { fn change_of_max_nominations() { use frame_election_provider_support::ElectionDataProvider; ExtBuilder::default() + .add_staker(1, 1, 5, StakerStatus::Validator) + .add_staker(2, 2, 5, StakerStatus::Validator) + .add_staker(3, 3, 5, StakerStatus::Validator) .add_staker(60, 61, 10, StakerStatus::Nominator(vec![1])) .add_staker(70, 71, 10, StakerStatus::Nominator(vec![1, 2, 3])) .balance_factor(10) @@ -4594,7 +4514,7 @@ fn change_of_max_nominations() { vec![(70, 3), (101, 2), (60, 1)] ); // 3 validators and 3 nominators - assert_eq!(Staking::voters(None).unwrap().len(), 3 + 3); + assert_eq!(Staking::voters(None).unwrap().len(), 3 + 3 + 3); // abrupt change from 16 to 4, everyone should be fine. MaxNominations::set(4); @@ -4605,7 +4525,7 @@ fn change_of_max_nominations() { .collect::>(), vec![(70, 3), (101, 2), (60, 1)] ); - assert_eq!(Staking::voters(None).unwrap().len(), 3 + 3); + assert_eq!(Staking::voters(None).unwrap().len(), 3 + 3 + 3); // abrupt change from 4 to 3, everyone should be fine. MaxNominations::set(3); @@ -4616,7 +4536,7 @@ fn change_of_max_nominations() { .collect::>(), vec![(70, 3), (101, 2), (60, 1)] ); - assert_eq!(Staking::voters(None).unwrap().len(), 3 + 3); + assert_eq!(Staking::voters(None).unwrap().len(), 3 + 3 + 3); // abrupt change from 3 to 2, this should cause some nominators to be non-decodable, and // thus non-existent unless if they update. @@ -4633,7 +4553,7 @@ fn change_of_max_nominations() { // but its value cannot be decoded and default is returned. assert!(Nominators::::get(70).is_none()); - assert_eq!(Staking::voters(None).unwrap().len(), 3 + 2); + assert_eq!(Staking::voters(None).unwrap().len(), 3 + 3 + 2); // abrupt change from 2 to 1, this should cause some nominators to be non-decodable, and // thus non-existent unless if they update. @@ -4649,7 +4569,7 @@ fn change_of_max_nominations() { assert!(Nominators::::contains_key(60)); assert!(Nominators::::get(70).is_none()); assert!(Nominators::::get(60).is_some()); - assert_eq!(Staking::voters(None).unwrap().len(), 3 + 1); + assert_eq!(Staking::voters(None).unwrap().len(), 3 + 3 + 1); // now one of them can revive themselves by re-nominating to a proper value. assert_ok!(Staking::nominate(Origin::signed(71), vec![1])); @@ -4669,109 +4589,358 @@ fn change_of_max_nominations() { }) } -#[test] -fn un_decodable_nominator_revive_via_nominate_correct_approval_update() { - ExtBuilder::default() - .add_staker(60, 61, 10, StakerStatus::Nominator(vec![1])) - .add_staker(70, 71, 10, StakerStatus::Nominator(vec![1, 2, 3])) - .balance_factor(10) - .build_and_execute(|| { - // initial approval stakes. - assert_eq!(::TargetList::get_score(&1).unwrap(), 20); - assert_eq!(::TargetList::get_score(&2).unwrap(), 10); - assert_eq!(::TargetList::get_score(&3).unwrap(), 10); - - // 70 is now gone - MaxNominations::set(2); +mod target_list { + use frame_support::storage::with_transaction; - // but approval stakes are the same. - assert_eq!(::TargetList::get_score(&1).unwrap(), 20); - assert_eq!(::TargetList::get_score(&2).unwrap(), 10); - assert_eq!(::TargetList::get_score(&3).unwrap(), 10); + use super::*; - // now they revive themselves via a fresh new nominate call. - assert_ok!(Staking::nominate(Origin::signed(71), vec![2])); + #[test] + fn duplicate_nomination_prevented() { + ExtBuilder::default().nominate(true).validator_count(1).build_and_execute(|| { + // resubmit and it is back + assert_noop!( + Staking::nominate(Origin::signed(100), vec![11, 11, 21]), + Error::::DuplicateTarget + ); - // approvals must be correctly updated - assert_eq!(::TargetList::get_score(&1).unwrap(), 10); - assert_eq!(::TargetList::get_score(&2).unwrap(), 10); - assert_eq!( - ::TargetList::get_score(&3), - Err(pallet_bags_list::Error::NonExistent) + assert_noop!( + Staking::nominate(Origin::signed(100), vec![11, 21, 11,]), + Error::::DuplicateTarget ); - }); -} -#[test] -fn un_decodable_nominator_chill_correct_approval_update() { - ExtBuilder::default() - .add_staker(60, 61, 10, StakerStatus::Nominator(vec![1])) - .add_staker(70, 71, 10, StakerStatus::Nominator(vec![1, 2, 3])) - .balance_factor(10) - .build_and_execute(|| { - // initial approval stakes. - assert_eq!(::TargetList::get_score(&1).unwrap(), 20); - assert_eq!(::TargetList::get_score(&2).unwrap(), 10); - assert_eq!(::TargetList::get_score(&3).unwrap(), 10); + assert_noop!( + Staking::nominate(Origin::signed(100), vec![21, 11, 31, 11]), + Error::::DuplicateTarget + ); + assert_ok!(Staking::nominate(Origin::signed(100), vec![21, 11])); + }) + } - // 70 is now gone - MaxNominations::set(2); + #[test] + fn invalid_nomination_prevented() { + ExtBuilder::default().build_and_execute(|| { + assert_eq!(Nominators::::get(101).unwrap().targets, vec![11, 21]); + assert_eq_uvec!(validator_ids(), vec![11, 21, 31]); - // but approval stakes are the same. - assert_eq!(::TargetList::get_score(&1).unwrap(), 20); - assert_eq!(::TargetList::get_score(&2).unwrap(), 10); - assert_eq!(::TargetList::get_score(&3).unwrap(), 10); + // can re-nominate correct ones. + assert_ok!(Staking::nominate(Origin::signed(100), vec![11])); + assert_ok!(Staking::nominate(Origin::signed(100), vec![21])); + assert_ok!(Staking::nominate(Origin::signed(100), vec![11, 21])); - // now they get chilled - assert_ok!(Staking::chill_other(Origin::signed(1), 71)); + // but not bad ones. + assert_noop!( + Staking::nominate(Origin::signed(100), vec![11, 21, 1]), + Error::::BadTarget + ); + assert_noop!(Staking::nominate(Origin::signed(100), vec![2]), Error::::BadTarget); + }); + } - // approvals must be correctly updated. - assert_eq!(::TargetList::get_score(&1).unwrap(), 10); - assert_eq!( - ::TargetList::get_score(&2).unwrap_err(), - pallet_bags_list::Error::NonExistent + #[test] + fn basic_setup_works() { + ExtBuilder::default().build_and_execute(|| { + assert_eq_uvec!( + ::TargetList::iter() + .map(|t| (t, ::TargetList::get_score(&t).unwrap())) + .collect::>(), + vec![(11, 1500), (21, 1500), (31, 500)] ); - assert_eq!( - ::TargetList::get_score(&3).unwrap_err(), - pallet_bags_list::Error::NonExistent + }); + } + + #[test] + fn nominator_actions() { + ExtBuilder::default().build_and_execute(|| { + // given + assert_eq_uvec!( + ::TargetList::iter() + .map(|t| (t, ::TargetList::get_score(&t).unwrap())) + .collect::>(), + vec![(11, 1500), (21, 1500), (31, 500)] ); + assert_eq_uvec!(nominator_ids(), vec![101]); + assert_eq_uvec!(nominator_targets(101), vec![21, 11]); + + // chilling should decrease the target list items. + with_transaction(|| { + assert_ok!(Staking::chill(Origin::signed(100))); + assert_eq_uvec!( + ::TargetList::iter() + .map(|t| (t, ::TargetList::get_score(&t).unwrap())) + .collect::>(), + vec![(11, 1000), (21, 1000), (31, 500)] + ); + + sp_runtime::TransactionOutcome::Rollback(()) + }); + + // validating should be same is chilling. + with_transaction(|| { + assert_ok!(Staking::validate(Origin::signed(100), Default::default())); + assert_eq_uvec!( + ::TargetList::iter() + .map(|t| (t, ::TargetList::get_score(&t).unwrap())) + .collect::>(), + vec![(11, 1000), (21, 1000), (31, 500), (101, 500)] + ); + + sp_runtime::TransactionOutcome::Rollback(()) + }); + + // re-nominating to different targets should increase and decrease. + with_transaction(|| { + assert_ok!(Staking::nominate(Origin::signed(100), vec![21, 31])); + assert_eq_uvec!( + ::TargetList::iter() + .map(|t| (t, ::TargetList::get_score(&t).unwrap())) + .collect::>(), + vec![(11, 1000), (21, 1500), (31, 1000)] + ); + + sp_runtime::TransactionOutcome::Rollback(()) + }); + + // bonding more should increase. + with_transaction(|| { + assert_ok!(Staking::bond_extra(Origin::signed(101), 100)); + assert_eq_uvec!( + ::TargetList::iter() + .map(|t| (t, ::TargetList::get_score(&t).unwrap())) + .collect::>(), + vec![(11, 1600), (21, 1600), (31, 500)] + ); + + sp_runtime::TransactionOutcome::Rollback(()) + }); + + // unbonding should decrease. + with_transaction(|| { + assert_ok!(Staking::unbond(Origin::signed(100), 100)); + assert_eq_uvec!( + ::TargetList::iter() + .map(|t| (t, ::TargetList::get_score(&t).unwrap())) + .collect::>(), + vec![(11, 1400), (21, 1400), (31, 500)] + ); + + // .. and rebonding should increase. + assert_ok!(Staking::rebond(Origin::signed(100), 100)); + assert_eq_uvec!( + ::TargetList::iter() + .map(|t| (t, ::TargetList::get_score(&t).unwrap())) + .collect::>(), + vec![(11, 1500), (21, 1500), (31, 500)] + ); + sp_runtime::TransactionOutcome::Rollback(()) + }); }); -} + } -#[test] -fn un_decodable_nominator_bond_extra_correct_approval_update() { - ExtBuilder::default() - .add_staker(60, 61, 10, StakerStatus::Nominator(vec![1])) - .add_staker(70, 71, 10, StakerStatus::Nominator(vec![1, 2, 3])) - .balance_factor(10) - .build_and_execute(|| { - // initial approval stakes. - assert_eq!(::TargetList::get_score(&1).unwrap(), 20); - assert_eq!(::TargetList::get_score(&2).unwrap(), 10); - assert_eq!(::TargetList::get_score(&3).unwrap(), 10); + #[test] + fn validator_actions() { + ExtBuilder::default().build_and_execute(|| { + // given + assert_eq_uvec!( + ::TargetList::iter() + .map(|t| (t, ::TargetList::get_score(&t).unwrap())) + .collect::>(), + vec![(11, 1500), (21, 1500), (31, 500)] + ); + assert_eq_uvec!(validator_ids(), vec![11, 21, 31]); + + // chilling should remove the target list items. + with_transaction(|| { + assert_ok!(Staking::chill(Origin::signed(20))); + assert_eq_uvec!( + ::TargetList::iter() + .map(|t| (t, ::TargetList::get_score(&t).unwrap())) + .collect::>(), + vec![(11, 1500), (31, 500)] + ); - // 70 is now gone - MaxNominations::set(2); + sp_runtime::TransactionOutcome::Rollback(()) + }); + + // re-validating takes us back to the initial state. + with_transaction(|| { + assert_ok!(Staking::validate(Origin::signed(20), Default::default())); + assert_eq_uvec!( + ::TargetList::iter() + .map(|t| (t, ::TargetList::get_score(&t).unwrap())) + .collect::>(), + vec![(11, 1500), (21, 1500), (31, 500)] + ); + + sp_runtime::TransactionOutcome::Rollback(()) + }); + + // nominating is same as chilling + with_transaction(|| { + assert_ok!(Staking::nominate(Origin::signed(20), vec![31])); + assert_eq_uvec!( + ::TargetList::iter() + .map(|t| (t, ::TargetList::get_score(&t).unwrap())) + .collect::>(), + vec![(11, 1000), (31, 500)] + ); + + sp_runtime::TransactionOutcome::Rollback(()) + }); + + // bonding more should increase. + with_transaction(|| { + assert_ok!(Staking::bond_extra(Origin::signed(20), 100)); + assert_eq_uvec!( + ::TargetList::iter() + .map(|t| (t, ::TargetList::get_score(&t).unwrap())) + .collect::>(), + vec![(11, 1500), (21, 1600), (31, 500)] + ); - // but approval stakes are the same. - assert_eq!(::TargetList::get_score(&1).unwrap(), 20); - assert_eq!(::TargetList::get_score(&2).unwrap(), 10); - assert_eq!(::TargetList::get_score(&3).unwrap(), 10); - - // now they get chilled - Balances::make_free_balance_be(&70, 1000); - assert_eq!(Staking::weight_of(&70), 10); - assert_ok!(Staking::bond_extra(Origin::signed(70), 10)); - assert_eq!(Staking::weight_of(&70), 20); - - // approvals must be correctly updated. - assert_eq!(::TargetList::get_score(&1).unwrap(), 20 + 10); - assert_eq!(::TargetList::get_score(&2).unwrap(), 10 + 10); - assert_eq!(::TargetList::get_score(&3).unwrap(), 10 + 10); + sp_runtime::TransactionOutcome::Rollback(()) + }); + + // unbonding should decrease. + with_transaction(|| { + assert_ok!(Staking::unbond(Origin::signed(20), 100)); + assert_eq_uvec!( + ::TargetList::iter() + .map(|t| (t, ::TargetList::get_score(&t).unwrap())) + .collect::>(), + vec![(11, 1500), (21, 1400), (31, 500)] + ); + + // .. and rebonding should increase. + assert_ok!(Staking::rebond(Origin::signed(20), 100)); + assert_eq_uvec!( + ::TargetList::iter() + .map(|t| (t, ::TargetList::get_score(&t).unwrap())) + .collect::>(), + vec![(11, 1500), (21, 1500), (31, 500)] + ); + sp_runtime::TransactionOutcome::Rollback(()) + }); }); + } + + #[test] + fn chilled_validator_afterwards_backers_chill() { + todo!("once a validator chills, then if their nominator re-nominates, we should compute the outgoing correctly etc.") + } + + #[test] + fn chilled_actions() { + todo!(); + } + + #[test] + fn un_decodable_nominator_revive_via_nominate_correct_approval_update() { + ExtBuilder::default() + .add_staker(1, 1, 5, StakerStatus::Validator) + .add_staker(2, 2, 5, StakerStatus::Validator) + .add_staker(3, 3, 5, StakerStatus::Validator) + .add_staker(60, 61, 10, StakerStatus::Nominator(vec![1])) + .add_staker(70, 71, 10, StakerStatus::Nominator(vec![1, 2, 3])) + .balance_factor(10) + .build_and_execute(|| { + // initial approval stakes. + assert_eq!(::TargetList::get_score(&1).unwrap(), 25); + assert_eq!(::TargetList::get_score(&2).unwrap(), 15); + assert_eq!(::TargetList::get_score(&3).unwrap(), 15); + + // 70 is now gone + MaxNominations::set(2); + + // but approval stakes are the same. + assert_eq!(::TargetList::get_score(&1).unwrap(), 25); + assert_eq!(::TargetList::get_score(&2).unwrap(), 15); + assert_eq!(::TargetList::get_score(&3).unwrap(), 15); + + // now they revive themselves via a fresh new nominate call. + assert_ok!(Staking::nominate(Origin::signed(71), vec![2])); + + // approvals must be correctly updated + assert_eq!(::TargetList::get_score(&1).unwrap(), 15); + assert_eq!(::TargetList::get_score(&2).unwrap(), 15); + assert_eq!(::TargetList::get_score(&3).unwrap(), 5); + assert_eq!( + ::TargetList::get_score(&4).unwrap_err(), + pallet_bags_list::Error::NonExistent + ); + }); + } + + #[test] + fn un_decodable_nominator_chill_correct_approval_update() { + ExtBuilder::default() + .add_staker(1, 1, 5, StakerStatus::Validator) + .add_staker(2, 2, 5, StakerStatus::Validator) + .add_staker(3, 3, 5, StakerStatus::Validator) + .add_staker(60, 61, 10, StakerStatus::Nominator(vec![1])) + .add_staker(70, 71, 10, StakerStatus::Nominator(vec![1, 2, 3])) + .balance_factor(10) + .build_and_execute(|| { + // initial approval stakes. + assert_eq!(::TargetList::get_score(&1).unwrap(), 25); + assert_eq!(::TargetList::get_score(&2).unwrap(), 15); + assert_eq!(::TargetList::get_score(&3).unwrap(), 15); + + // 70 is now gone + MaxNominations::set(2); + + // but approval stakes are the same. + assert_eq!(::TargetList::get_score(&1).unwrap(), 25); + assert_eq!(::TargetList::get_score(&2).unwrap(), 15); + assert_eq!(::TargetList::get_score(&3).unwrap(), 15); + + // now they get chilled + assert_ok!(Staking::chill_other(Origin::signed(1), 71)); + + // approvals must be correctly updated. + assert_eq!(::TargetList::get_score(&1).unwrap(), 15); + assert_eq!(::TargetList::get_score(&2).unwrap(), 5); + assert_eq!(::TargetList::get_score(&3).unwrap(), 5); + }); + } + + #[test] + fn un_decodable_nominator_bond_extra_correct_approval_update() { + ExtBuilder::default() + .add_staker(1, 1, 5, StakerStatus::Validator) + .add_staker(2, 2, 5, StakerStatus::Validator) + .add_staker(60, 61, 10, StakerStatus::Nominator(vec![1])) + .add_staker(3, 3, 5, StakerStatus::Validator) + .add_staker(70, 71, 10, StakerStatus::Nominator(vec![1, 2, 3])) + .balance_factor(10) + .build_and_execute(|| { + // initial approval stakes. + assert_eq!(::TargetList::get_score(&1).unwrap(), 25); + assert_eq!(::TargetList::get_score(&2).unwrap(), 15); + assert_eq!(::TargetList::get_score(&3).unwrap(), 15); + + // 70 is now gone + MaxNominations::set(2); + + // but approval stakes are the same. + assert_eq!(::TargetList::get_score(&1).unwrap(), 25); + assert_eq!(::TargetList::get_score(&2).unwrap(), 15); + assert_eq!(::TargetList::get_score(&3).unwrap(), 15); + + // now they get chilled + Balances::make_free_balance_be(&70, 1000); + assert_eq!(Staking::weight_of(&70), 10); + assert_ok!(Staking::bond_extra(Origin::signed(70), 10)); + assert_eq!(Staking::weight_of(&70), 20); + + // approvals must be correctly updated. + assert_eq!(::TargetList::get_score(&1).unwrap(), 25 + 10); + assert_eq!(::TargetList::get_score(&2).unwrap(), 15 + 10); + assert_eq!(::TargetList::get_score(&3).unwrap(), 15 + 10); + }); + } } -mod sorted_list_provider { +mod voter_list { use super::*; use frame_election_provider_support::SortedListProvider; @@ -4789,7 +4958,7 @@ mod sorted_list_provider { ); // when account 101 renominates - assert_ok!(Staking::nominate(Origin::signed(100), vec![41])); + assert_ok!(Staking::nominate(Origin::signed(100), vec![11, 21])); // then counts don't change assert_eq!(::VoterList::count(), pre_insert_voter_count); diff --git a/frame/support/src/traits/misc.rs b/frame/support/src/traits/misc.rs index 8c61874003bce..ef26743d03623 100644 --- a/frame/support/src/traits/misc.rs +++ b/frame/support/src/traits/misc.rs @@ -102,6 +102,10 @@ pub trait Defensive { /// } /// ``` fn defensive(self) -> Self; + + /// Same as [`Defensive::defensive`], but it takes a proof is input. + // TODO: make the macro accept this for Result as well and backport it. + fn defensive_proof(self, proof: &'static str) -> Self; } /// Subset of methods similar to [`Defensive`] that can only work for a `Result`. @@ -180,6 +184,16 @@ impl Defensive for Option { }, } } + + fn defensive_proof(self, proof: &'static str) -> Self { + match self { + Some(inner) => Some(inner), + None => { + defensive!(proof); + None + }, + } + } } impl Defensive for Result { @@ -225,6 +239,17 @@ impl Defensive for Result { }, } } + + fn defensive_proof(self, proof: &'static str) -> Self { + match self { + Ok(inner) => Ok(inner), + Err(e) => { + defensive!(e); + defensive!(proof); + Err(e) + }, + } + } } impl DefensiveResult for Result { From 2f295b78f9d77e105721c0c6299b5ae01419c482 Mon Sep 17 00:00:00 2001 From: kianenigma Date: Fri, 11 Mar 2022 13:07:09 +0000 Subject: [PATCH 17/42] dead-end with restricted nominations --- frame/staking/src/migrations.rs | 17 ++++++++--------- 1 file changed, 8 insertions(+), 9 deletions(-) diff --git a/frame/staking/src/migrations.rs b/frame/staking/src/migrations.rs index 9527968c85e20..2a6c33accbb54 100644 --- a/frame/staking/src/migrations.rs +++ b/frame/staking/src/migrations.rs @@ -74,21 +74,20 @@ impl InjectValidatorsApprovalStakeIntoTargetList { pub(crate) fn build_approval_stakes() -> BTreeMap { let mut approval_stakes = BTreeMap::::new(); - Validators::::iter().for_each(|(v, _)| { - let stake = Pallet::::weight_of(&v); - let current = approval_stakes.entry(v).or_default(); - *current = current.saturating_add(stake); - }); - NominatorsHelper::::iter_all().for_each(|(who, nomination)| { let stake = Pallet::::weight_of(&who); for target in nomination.targets { - if let Some(current) = approval_stakes.get_mut(&target) { - *current = current.saturating_add(stake); - } + let current = approval_stakes.entry(target).or_default(); + *current = current.saturating_add(stake); } }); + Validators::::iter().for_each(|(v, _)| { + let stake = Pallet::::weight_of(&v); + let current = approval_stakes.entry(v).or_default(); + *current = current.saturating_add(stake); + }); + approval_stakes } } From 29071fc4612bb1778362e9790e252cb4ba13a246 Mon Sep 17 00:00:00 2001 From: kianenigma Date: Fri, 11 Mar 2022 13:51:27 +0000 Subject: [PATCH 18/42] most tests work again --- frame/bags-list/src/lib.rs | 2 +- frame/bags-list/src/list/mod.rs | 2 +- frame/staking/src/pallet/impls.rs | 3 +- frame/staking/src/tests.rs | 76 +++++++++++++++++++++++++------ 4 files changed, 65 insertions(+), 18 deletions(-) diff --git a/frame/bags-list/src/lib.rs b/frame/bags-list/src/lib.rs index 01a7eec59787e..9dbd1be9800ea 100644 --- a/frame/bags-list/src/lib.rs +++ b/frame/bags-list/src/lib.rs @@ -82,7 +82,7 @@ macro_rules! log { ($level:tt, $patter:expr $(, $values:expr)* $(,)?) => { log::$level!( target: crate::LOG_TARGET, - concat!("[{:?}][{}] 👜", $patter), + concat!("[{:?}] 👜 [{}]", $patter), >::block_number(), as frame_support::traits::PalletInfoAccess>::name() $(, $values)* diff --git a/frame/bags-list/src/list/mod.rs b/frame/bags-list/src/list/mod.rs index a603962470f46..d25b12dd7c357 100644 --- a/frame/bags-list/src/list/mod.rs +++ b/frame/bags-list/src/list/mod.rs @@ -367,7 +367,7 @@ impl, I: 'static> List { ) -> Option<(T::Score, T::Score)> { crate::log!( debug, - "trying to rebag {:?} from {:?} to {:?} ({})", + "trying to update {:?} from {:?} to {:?} (rebag?: {})", node.id, node.score(), new_score, diff --git a/frame/staking/src/pallet/impls.rs b/frame/staking/src/pallet/impls.rs index 5a6496387f087..54ef138ab4e0c 100644 --- a/frame/staking/src/pallet/impls.rs +++ b/frame/staking/src/pallet/impls.rs @@ -930,8 +930,7 @@ impl Pallet { let outcome = if Validators::::contains_key(who) { Validators::::remove(who); let _ = T::VoterList::on_remove(who).defensive(); - // NOTE: simply remove, not decrease. - let _ = T::TargetList::on_remove(who).defensive(); + let _ = T::TargetList::on_decrease(who, Self::weight_of(who)).defensive(); true } else { false diff --git a/frame/staking/src/tests.rs b/frame/staking/src/tests.rs index a69ee18df5270..927cffa35fda1 100644 --- a/frame/staking/src/tests.rs +++ b/frame/staking/src/tests.rs @@ -1723,9 +1723,9 @@ fn reap_stash_works() { // no easy way to cause an account to go below ED, we tweak their staking ledger // instead. - Ledger::::insert( - 10, - StakingLedger { + Staking::update_ledger( + &10, + &StakingLedger { stash: 11, total: 5, active: 5, @@ -4748,14 +4748,14 @@ mod target_list { ); assert_eq_uvec!(validator_ids(), vec![11, 21, 31]); - // chilling should remove the target list items. + // chilling does not remove, but rather decrease the validator's approval stake. with_transaction(|| { assert_ok!(Staking::chill(Origin::signed(20))); assert_eq_uvec!( ::TargetList::iter() .map(|t| (t, ::TargetList::get_score(&t).unwrap())) .collect::>(), - vec![(11, 1500), (31, 500)] + vec![(11, 1500), (21, 500), (31, 500)] ); sp_runtime::TransactionOutcome::Rollback(()) @@ -4774,14 +4774,14 @@ mod target_list { sp_runtime::TransactionOutcome::Rollback(()) }); - // nominating is same as chilling + // nominating is similar to chilling, and we contribute to 31. with_transaction(|| { assert_ok!(Staking::nominate(Origin::signed(20), vec![31])); assert_eq_uvec!( ::TargetList::iter() .map(|t| (t, ::TargetList::get_score(&t).unwrap())) .collect::>(), - vec![(11, 1000), (31, 500)] + vec![(11, 1500), (21, 500), (31, 1500)] ); sp_runtime::TransactionOutcome::Rollback(()) @@ -4789,7 +4789,7 @@ mod target_list { // bonding more should increase. with_transaction(|| { - assert_ok!(Staking::bond_extra(Origin::signed(20), 100)); + assert_ok!(Staking::bond_extra(Origin::signed(21), 100)); assert_eq_uvec!( ::TargetList::iter() .map(|t| (t, ::TargetList::get_score(&t).unwrap())) @@ -4823,14 +4823,62 @@ mod target_list { }); } - #[test] - fn chilled_validator_afterwards_backers_chill() { - todo!("once a validator chills, then if their nominator re-nominates, we should compute the outgoing correctly etc.") - } - #[test] fn chilled_actions() { - todo!(); + ExtBuilder::default().build_and_execute(|| { + // given + let initial_approvals = || { + assert_eq_uvec!( + ::TargetList::iter() + .map(|t| (t, ::TargetList::get_score(&t).unwrap())) + .collect::>(), + vec![(11, 1500), (21, 1500), (31, 500)] + ); + }; + let stash = 41; + let ctrl = 40; + + initial_approvals(); + assert_eq_uvec!(validator_ids(), vec![11, 21, 31]); + + // validating adds us. + with_transaction(|| { + assert_ok!(Staking::validate(Origin::signed(ctrl), Default::default())); + assert_eq_uvec!( + ::TargetList::iter() + .map(|t| (t, ::TargetList::get_score(&t).unwrap())) + .collect::>(), + vec![(11, 1500), (21, 1500), (31, 500), (41, 1000)] + ); + + sp_runtime::TransactionOutcome::Rollback(()) + }); + + // nominating, and we contribute to 31. + with_transaction(|| { + assert_ok!(Staking::nominate(Origin::signed(ctrl), vec![31])); + assert_eq_uvec!( + ::TargetList::iter() + .map(|t| (t, ::TargetList::get_score(&t).unwrap())) + .collect::>(), + vec![(11, 1500), (21, 1500), (31, 1500)] + ); + + sp_runtime::TransactionOutcome::Rollback(()) + }); + + // rest of the operations is a + with_transaction(|| { + assert_ok!(Staking::bond_extra(Origin::signed(stash), 100)); + initial_approvals(); + assert_ok!(Staking::unbond(Origin::signed(ctrl), 100)); + initial_approvals(); + assert_ok!(Staking::rebond(Origin::signed(ctrl), 100)); + initial_approvals(); + + sp_runtime::TransactionOutcome::Rollback(()) + }); + }); } #[test] From 34b1d8a466b1f629d37187614a644e0d7f04537a Mon Sep 17 00:00:00 2001 From: kianenigma Date: Fri, 11 Mar 2022 14:00:12 +0000 Subject: [PATCH 19/42] use balance as the type for TargetList --- frame/staking/src/migrations.rs | 10 +++++----- frame/staking/src/mock.rs | 24 ++++++++++++------------ frame/staking/src/pallet/impls.rs | 27 +++++++++++++++------------ frame/staking/src/pallet/mod.rs | 12 +++--------- 4 files changed, 35 insertions(+), 38 deletions(-) diff --git a/frame/staking/src/migrations.rs b/frame/staking/src/migrations.rs index 2a6c33accbb54..da8efb8dcb678 100644 --- a/frame/staking/src/migrations.rs +++ b/frame/staking/src/migrations.rs @@ -17,7 +17,7 @@ //! Storage migrations for the Staking pallet. use super::*; -use frame_election_provider_support::{SortedListProvider, VoteWeight}; +use frame_election_provider_support::SortedListProvider; use frame_support::traits::{Defensive, OnRuntimeUpgrade}; use sp_std::collections::btree_map::BTreeMap; @@ -71,11 +71,11 @@ impl OnRuntimeUpgrade for InjectValidatorsSelfStakeIntoVoterList { pub struct InjectValidatorsApprovalStakeIntoTargetList(sp_std::marker::PhantomData); impl InjectValidatorsApprovalStakeIntoTargetList { - pub(crate) fn build_approval_stakes() -> BTreeMap { - let mut approval_stakes = BTreeMap::::new(); + pub(crate) fn build_approval_stakes() -> BTreeMap> { + let mut approval_stakes = BTreeMap::>::new(); NominatorsHelper::::iter_all().for_each(|(who, nomination)| { - let stake = Pallet::::weight_of(&who); + let stake = Pallet::::slashable_balance_of(&who); for target in nomination.targets { let current = approval_stakes.entry(target).or_default(); *current = current.saturating_add(stake); @@ -83,7 +83,7 @@ impl InjectValidatorsApprovalStakeIntoTargetList { }); Validators::::iter().for_each(|(v, _)| { - let stake = Pallet::::weight_of(&v); + let stake = Pallet::::slashable_balance_of(&v); let current = approval_stakes.entry(v).or_default(); *current = current.saturating_add(stake); }); diff --git a/frame/staking/src/mock.rs b/frame/staking/src/mock.rs index e9af4d91c5b84..9befffb0b3b3b 100644 --- a/frame/staking/src/mock.rs +++ b/frame/staking/src/mock.rs @@ -230,11 +230,11 @@ impl OnUnbalanced> for RewardRemainderMock { } } -const THRESHOLDS: [sp_npos_elections::VoteWeight; 9] = - [10, 20, 30, 40, 50, 60, 1_000, 2_000, 10_000]; - +const THRESHOLDS: [VoteWeight; 9] = [10, 20, 30, 40, 50, 60, 1_000, 2_000, 10_000]; +const THRESHOLDS_BALANCE: [Balance; 9] = [10, 20, 30, 40, 50, 60, 1_000, 2_000, 10_000]; parameter_types! { - pub static BagThresholds: &'static [sp_npos_elections::VoteWeight] = &THRESHOLDS; + pub static BagThresholds: &'static [VoteWeight] = &THRESHOLDS; + pub static BagThresholdsBalance: &'static [Balance] = &THRESHOLDS_BALANCE; pub static MaxNominations: u32 = 16; } @@ -254,8 +254,8 @@ impl pallet_bags_list::Config for Test { type WeightInfo = (); // Target bags-list are always kept up to date, and in fact Staking does not know them at all! type ScoreProvider = pallet_bags_list::Pallet; - type BagThresholds = BagThresholds; - type Score = VoteWeight; + type BagThresholds = BagThresholdsBalance; + type Score = Balance; } impl onchain::Config for Test { @@ -284,13 +284,13 @@ impl SortedListProvider for TargetBagListCompat { fn contains(id: &AccountId) -> bool { TargetBagsList::contains(id) } - fn on_insert(id: AccountId, weight: VoteWeight) -> Result<(), Self::Error> { + fn on_insert(id: AccountId, weight: Self::Score) -> Result<(), Self::Error> { TargetBagsList::on_insert(id, weight) } - fn on_update(id: &AccountId, weight: VoteWeight) -> Result<(), Self::Error> { + fn on_update(id: &AccountId, weight: Self::Score) -> Result<(), Self::Error> { TargetBagsList::on_update(id, weight) } - fn get_score(id: &AccountId) -> Result { + fn get_score(id: &AccountId) -> Result { TargetBagsList::get_score(id) } fn on_remove(id: &AccountId) -> Result<(), Self::Error> { @@ -298,7 +298,7 @@ impl SortedListProvider for TargetBagListCompat { } fn unsafe_regenerate( all: impl IntoIterator, - weight_of: Box VoteWeight>, + weight_of: Box Self::Score>, ) -> u32 { TargetBagsList::unsafe_regenerate(all, weight_of) } @@ -309,8 +309,8 @@ impl SortedListProvider for TargetBagListCompat { TargetBagsList::sanity_check() } #[cfg(feature = "runtime-benchmarks")] - fn score_update_worst_case(_who: &AccountId, _is_increase: bool) -> VoteWeight { - VoteWeight::MAX + fn score_update_worst_case(_who: &AccountId, _is_increase: bool) -> Self::Score { + Balance::MAX } } diff --git a/frame/staking/src/pallet/impls.rs b/frame/staking/src/pallet/impls.rs index 54ef138ab4e0c..3b5b61008541d 100644 --- a/frame/staking/src/pallet/impls.rs +++ b/frame/staking/src/pallet/impls.rs @@ -218,12 +218,12 @@ impl Pallet { use sp_std::cmp::Ordering; match ledger.active.cmp(&prev_active) { Ordering::Greater => { - let _ = T::TargetList::on_increase(who, to_vote(ledger.active - prev_active)) - .defensive(); + let _ = + T::TargetList::on_increase(who, ledger.active - prev_active).defensive(); }, Ordering::Less => { - let _ = T::TargetList::on_decrease(who, to_vote(prev_active - ledger.active)) - .defensive(); + let _ = + T::TargetList::on_decrease(who, prev_active - ledger.active).defensive(); }, Ordering::Equal => (), }; @@ -838,17 +838,17 @@ impl Pallet { // validator and now they nominate themselves. // TODO: these are all rather inefficient now based on how vote_weight is // implemented, but I don't care because: https://github.com/paritytech/substrate/issues/10990 - let weight = Self::weight_of(stash); + let score = Self::slashable_balance_of(stash); incoming.into_iter().for_each(|i| { if T::TargetList::contains(i) { - let _ = T::TargetList::on_increase(i, weight).defensive(); + let _ = T::TargetList::on_increase(i, score).defensive(); } else { defensive!("no incoming target can not have an entry in the target-list"); } }); outgoing.into_iter().for_each(|o| { if T::TargetList::contains(&o) { - let _ = T::TargetList::on_decrease(&o, weight); + let _ = T::TargetList::on_decrease(&o, score); } else { // probably the validator has already been chilled and their target list entry // removed. @@ -875,9 +875,9 @@ impl Pallet { pub fn do_remove_nominator(who: &T::AccountId) -> bool { let outcome = if let Some(targets) = NominatorsHelper::::get_any(who).map(|n| n.targets) { - let stake = Self::weight_of(&who); + let score = Self::slashable_balance_of(&who); for target in targets { - let _ = T::TargetList::on_decrease(&target, stake).defensive(); + let _ = T::TargetList::on_decrease(&target, score).defensive(); } let _ = T::VoterList::on_remove(who).defensive(); Nominators::::remove(who); @@ -905,11 +905,14 @@ impl Pallet { let _ = T::VoterList::on_insert(who.clone(), Self::weight_of(who)).defensive(); if T::TargetList::contains(who) { - let _ = T::TargetList::on_increase(who, Self::weight_of(who)).defensive(); + let _ = + T::TargetList::on_increase(who, Self::slashable_balance_of(who)).defensive(); } else { - let _ = T::TargetList::on_insert(who.clone(), Self::weight_of(who)).defensive(); + let _ = T::TargetList::on_insert(who.clone(), Self::slashable_balance_of(who)) + .defensive(); } } + Validators::::insert(who, prefs); #[cfg(debug_assertions)] @@ -930,7 +933,7 @@ impl Pallet { let outcome = if Validators::::contains_key(who) { Validators::::remove(who); let _ = T::VoterList::on_remove(who).defensive(); - let _ = T::TargetList::on_decrease(who, Self::weight_of(who)).defensive(); + let _ = T::TargetList::on_decrease(who, Self::slashable_balance_of(who)).defensive(); true } else { false diff --git a/frame/staking/src/pallet/mod.rs b/frame/staking/src/pallet/mod.rs index 6b33c4e4108ce..a4b868d163848 100644 --- a/frame/staking/src/pallet/mod.rs +++ b/frame/staking/src/pallet/mod.rs @@ -17,7 +17,7 @@ //! Staking FRAME Pallet. -use frame_election_provider_support::SortedListProvider; +use frame_election_provider_support::{SortedListProvider, VoteWeight}; use frame_support::{ dispatch::Codec, pallet_prelude::*, @@ -163,16 +163,10 @@ pub mod pallet { /// After the threshold is reached a new era will be forced. type OffendingValidatorsThreshold: Get; - type VoterList: SortedListProvider< - Self::AccountId, - Score = frame_election_provider_support::VoteWeight, - >; + type VoterList: SortedListProvider; // type TargetList: SortedListProvider>; - type TargetList: SortedListProvider< - Self::AccountId, - Score = frame_election_provider_support::VoteWeight, - >; + type TargetList: SortedListProvider>; /// determines how many unique eras a staker may be unbonding in. #[pallet::constant] From 8a431ddb459136e34de32459d339ca95e4558eb5 Mon Sep 17 00:00:00 2001 From: kianenigma Date: Fri, 11 Mar 2022 14:13:09 +0000 Subject: [PATCH 20/42] a little bit of self-review --- frame/staking/src/lib.rs | 4 ++-- frame/staking/src/pallet/impls.rs | 3 --- frame/staking/src/pallet/mod.rs | 9 ++++++++- 3 files changed, 10 insertions(+), 6 deletions(-) diff --git a/frame/staking/src/lib.rs b/frame/staking/src/lib.rs index b7933fd1bb44a..9cdaa6bb124fe 100644 --- a/frame/staking/src/lib.rs +++ b/frame/staking/src/lib.rs @@ -791,8 +791,8 @@ enum Releases { V6_0_0, // removal of all storage associated with offchain phragmen. V7_0_0, // keep track of number of nominators / validators in map V8_0_0, // populate `VoterList`. - V9_0_0, // inject validators into `NPoSVoteProvider` as well. - V10_0_0, // inject validator into `NPoSTargetList`. + V9_0_0, // inject validators into `VoterList` as well. + V10_0_0, // inject validator's approval stake into `TargetList`. } impl Default for Releases { diff --git a/frame/staking/src/pallet/impls.rs b/frame/staking/src/pallet/impls.rs index 3b5b61008541d..69d5451fcbb5c 100644 --- a/frame/staking/src/pallet/impls.rs +++ b/frame/staking/src/pallet/impls.rs @@ -836,8 +836,6 @@ impl Pallet { // TODO: edge case: some wanker nominating themselves? should only be possible if they are a // validator and now they nominate themselves. - // TODO: these are all rather inefficient now based on how vote_weight is - // implemented, but I don't care because: https://github.com/paritytech/substrate/issues/10990 let score = Self::slashable_balance_of(stash); incoming.into_iter().for_each(|i| { if T::TargetList::contains(i) { @@ -1441,7 +1439,6 @@ impl SortedListProvider for UseNominatorsAndValidatorsM Ok(()) } fn get_score(id: &T::AccountId) -> Result { - // TODO: this is not consistent, should ideally return an error if not exists. Ok(Pallet::::weight_of(id)) } fn on_update(_: &T::AccountId, _weight: VoteWeight) -> Result<(), Self::Error> { diff --git a/frame/staking/src/pallet/mod.rs b/frame/staking/src/pallet/mod.rs index a4b868d163848..208d07af6dd8c 100644 --- a/frame/staking/src/pallet/mod.rs +++ b/frame/staking/src/pallet/mod.rs @@ -163,9 +163,16 @@ pub mod pallet { /// After the threshold is reached a new era will be forced. type OffendingValidatorsThreshold: Get; + /// Something that provides a best-effort sorted list of voters aka electing nominators, + /// used for NPoS election. + /// + /// The changes to nominators are reported to this. type VoterList: SortedListProvider; - // type TargetList: SortedListProvider>; + /// Something that provides a best-effort sorted list of targets aka electable validators, + /// used for NPoS election. + /// + /// The changes to the approval stake of each validator are reported to this. type TargetList: SortedListProvider>; /// determines how many unique eras a staker may be unbonding in. From f892d042f8d100d02ca85b656c42d34616dac824 Mon Sep 17 00:00:00 2001 From: kianenigma Date: Fri, 11 Mar 2022 14:19:41 +0000 Subject: [PATCH 21/42] fix weight_of_fn --- frame/staking/src/migrations.rs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/frame/staking/src/migrations.rs b/frame/staking/src/migrations.rs index 029a6a380fee3..89eb313f00313 100644 --- a/frame/staking/src/migrations.rs +++ b/frame/staking/src/migrations.rs @@ -17,7 +17,7 @@ //! Storage migrations for the Staking pallet. use super::*; -use frame_election_provider_support::{SortedListProvider, VoteWeightProvider}; +use frame_election_provider_support::{SortedListProvider, ScoreProvider}; use frame_support::traits::OnRuntimeUpgrade; /// Migration implementation that injects all validators into sorted list. @@ -27,13 +27,15 @@ pub struct InjectValidatorsIntoSortedListProvider(sp_std::marker::PhantomData impl OnRuntimeUpgrade for InjectValidatorsIntoSortedListProvider { fn on_runtime_upgrade() -> Weight { if StorageVersion::::get() == Releases::V8_0_0 { + let weight_of_cached = Pallet::::weight_of_fn(); for (v, _) in Validators::::iter() { - let weight = Pallet::::vote_weight(&v); + let weight = weight_of_cached(&v); let _ = T::SortedListProvider::on_insert(v.clone(), weight).map_err(|err| { log!(warn, "failed to insert {:?} into SortedListProvider: {:?}", v, err) }); } + StorageVersion::::put(crate::Releases::V9_0_0); T::BlockWeights::get().max_block } else { log!(warn, "InjectValidatorsIntoSortedListProvider being executed on the wrong storage version, expected Releases::V8_0_0"); From 5640da73cc4599fdbf863229706165aeea6ef04f Mon Sep 17 00:00:00 2001 From: kianenigma Date: Fri, 11 Mar 2022 14:21:19 +0000 Subject: [PATCH 22/42] reformat line width --- frame/staking/src/migrations.rs | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/frame/staking/src/migrations.rs b/frame/staking/src/migrations.rs index 89eb313f00313..32e4a139bc32a 100644 --- a/frame/staking/src/migrations.rs +++ b/frame/staking/src/migrations.rs @@ -17,7 +17,7 @@ //! Storage migrations for the Staking pallet. use super::*; -use frame_election_provider_support::{SortedListProvider, ScoreProvider}; +use frame_election_provider_support::{ScoreProvider, SortedListProvider}; use frame_support::traits::OnRuntimeUpgrade; /// Migration implementation that injects all validators into sorted list. @@ -38,7 +38,11 @@ impl OnRuntimeUpgrade for InjectValidatorsIntoSortedListProvider { StorageVersion::::put(crate::Releases::V9_0_0); T::BlockWeights::get().max_block } else { - log!(warn, "InjectValidatorsIntoSortedListProvider being executed on the wrong storage version, expected Releases::V8_0_0"); + log!( + warn, + "InjectValidatorsIntoSortedListProvider being executed on the wrong storage \ + version, expected Releases::V8_0_0" + ); T::DbWeight::get().reads(1) } } From 3aefa002b04139100ad50f43e19f81d091f0e3e7 Mon Sep 17 00:00:00 2001 From: kianenigma Date: Fri, 11 Mar 2022 14:27:02 +0000 Subject: [PATCH 23/42] make const --- frame/staking/src/migrations.rs | 2 +- frame/staking/src/pallet/impls.rs | 12 +++++++++++- 2 files changed, 12 insertions(+), 2 deletions(-) diff --git a/frame/staking/src/migrations.rs b/frame/staking/src/migrations.rs index 32e4a139bc32a..96c5e2da89367 100644 --- a/frame/staking/src/migrations.rs +++ b/frame/staking/src/migrations.rs @@ -17,7 +17,7 @@ //! Storage migrations for the Staking pallet. use super::*; -use frame_election_provider_support::{ScoreProvider, SortedListProvider}; +use frame_election_provider_support::SortedListProvider; use frame_support::traits::OnRuntimeUpgrade; /// Migration implementation that injects all validators into sorted list. diff --git a/frame/staking/src/pallet/impls.rs b/frame/staking/src/pallet/impls.rs index 372460ab7a620..0bffae529a4e7 100644 --- a/frame/staking/src/pallet/impls.rs +++ b/frame/staking/src/pallet/impls.rs @@ -49,6 +49,14 @@ use crate::{ use super::{pallet::*, STAKING_ID}; +/// The maximum number of iterations that we do whilst iterating over `T::VoterList` in +/// `get_npos_voters`. +/// +/// In most cases, if we want n items, we iterate exactly n times. In rare cases, if a voter is +/// invalid (for any reason) the iteration continues. With this constant, we iterate at most 2 * n +/// times and then give up. +const NPOS_MAX_ITERATIONS_COEFFICIENT: u32 = 2; + impl Pallet { /// The total balance that can be slashed from a stash account as of right now. pub fn slashable_balance_of(stash: &T::AccountId) -> BalanceOf { @@ -674,7 +682,9 @@ impl Pallet { let mut nominators_taken = 0u32; let mut sorted_voters = T::SortedListProvider::iter(); - while all_voters.len() < max_allowed_len && voters_seen < (2 * max_allowed_len as u32) { + while all_voters.len() < max_allowed_len && + voters_seen < (NPOS_MAX_ITERATIONS_COEFFICIENT * max_allowed_len as u32) + { let voter = match sorted_voters.next() { Some(voter) => { voters_seen.saturating_inc(); From 0923d5b6511fab7ceec411a72cd83f10556c8492 Mon Sep 17 00:00:00 2001 From: kianenigma Date: Fri, 11 Mar 2022 14:30:31 +0000 Subject: [PATCH 24/42] use weight of fn cached --- frame/staking/src/pallet/impls.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frame/staking/src/pallet/impls.rs b/frame/staking/src/pallet/impls.rs index 0bffae529a4e7..a26207547f656 100644 --- a/frame/staking/src/pallet/impls.rs +++ b/frame/staking/src/pallet/impls.rs @@ -710,7 +710,7 @@ impl Pallet { // if this voter is a validator: let self_vote = ( voter.clone(), - Self::weight_of(&voter), + weight_of(&voter), vec![voter.clone()] .try_into() .expect("`MaxVotesPerVoter` must be greater than or equal to 1"), From 2ea076924f1322d25098f81cba996385659902bf Mon Sep 17 00:00:00 2001 From: kianenigma Date: Fri, 11 Mar 2022 14:37:25 +0000 Subject: [PATCH 25/42] SortedListProvider -> VoterList --- frame/staking/src/benchmarking.rs | 34 ++++++++++++++-------------- frame/staking/src/lib.rs | 4 ++-- frame/staking/src/migrations.rs | 25 ++++++++++----------- frame/staking/src/mock.rs | 6 ++--- frame/staking/src/pallet/impls.rs | 36 +++++++++++++++--------------- frame/staking/src/pallet/mod.rs | 33 +++++++++++++-------------- frame/staking/src/testing_utils.rs | 4 ++-- frame/staking/src/tests.rs | 28 +++++++++-------------- 8 files changed, 81 insertions(+), 89 deletions(-) diff --git a/frame/staking/src/benchmarking.rs b/frame/staking/src/benchmarking.rs index ca4b2388e8e42..983f1bd54deb7 100644 --- a/frame/staking/src/benchmarking.rs +++ b/frame/staking/src/benchmarking.rs @@ -155,8 +155,8 @@ impl ListScenario { /// - the destination bag has at least one node, which will need its next pointer updated. /// /// NOTE: while this scenario specifically targets a worst case for the bags-list, it should - /// also elicit a worst case for other known `SortedListProvider` implementations; although - /// this may not be true against unknown `SortedListProvider` implementations. + /// also elicit a worst case for other known `VoterList` implementations; although + /// this may not be true against unknown `VoterList` implementations. fn new(origin_weight: BalanceOf, is_increase: bool) -> Result { ensure!(!origin_weight.is_zero(), "origin weight must be greater than 0"); @@ -189,7 +189,7 @@ impl ListScenario { // find a destination weight that will trigger the worst case scenario let dest_weight_as_vote = - T::SortedListProvider::score_update_worst_case(&origin_stash1, is_increase); + T::VoterList::score_update_worst_case(&origin_stash1, is_increase); let total_issuance = T::Currency::total_issuance(); @@ -316,7 +316,7 @@ benchmarks! { let scenario = ListScenario::::new(origin_weight, true)?; let controller = scenario.origin_controller1.clone(); let stash = scenario.origin_stash1.clone(); - assert!(T::SortedListProvider::contains(&stash)); + assert!(T::VoterList::contains(&stash)); let ed = T::Currency::minimum_balance(); let mut ledger = Ledger::::get(&controller).unwrap(); @@ -328,7 +328,7 @@ benchmarks! { }: withdraw_unbonded(RawOrigin::Signed(controller.clone()), s) verify { assert!(!Ledger::::contains_key(controller)); - assert!(!T::SortedListProvider::contains(&stash)); + assert!(!T::VoterList::contains(&stash)); } validate { @@ -338,14 +338,14 @@ benchmarks! { Default::default(), )?; // because it is chilled. - assert!(!T::SortedListProvider::contains(&stash)); + assert!(!T::VoterList::contains(&stash)); let prefs = ValidatorPrefs::default(); whitelist_account!(controller); }: _(RawOrigin::Signed(controller), prefs) verify { assert!(Validators::::contains_key(&stash)); - assert!(T::SortedListProvider::contains(&stash)); + assert!(T::VoterList::contains(&stash)); } kick { @@ -430,14 +430,14 @@ benchmarks! { ).unwrap(); assert!(!Nominators::::contains_key(&stash)); - assert!(!T::SortedListProvider::contains(&stash)); + assert!(!T::VoterList::contains(&stash)); let validators = create_validators::(n, 100).unwrap(); whitelist_account!(controller); }: _(RawOrigin::Signed(controller), validators) verify { assert!(Nominators::::contains_key(&stash)); - assert!(T::SortedListProvider::contains(&stash)) + assert!(T::VoterList::contains(&stash)) } chill { @@ -451,12 +451,12 @@ benchmarks! { let scenario = ListScenario::::new(origin_weight, true)?; let controller = scenario.origin_controller1.clone(); let stash = scenario.origin_stash1.clone(); - assert!(T::SortedListProvider::contains(&stash)); + assert!(T::VoterList::contains(&stash)); whitelist_account!(controller); }: _(RawOrigin::Signed(controller)) verify { - assert!(!T::SortedListProvider::contains(&stash)); + assert!(!T::VoterList::contains(&stash)); } set_payee { @@ -519,13 +519,13 @@ benchmarks! { let scenario = ListScenario::::new(origin_weight, true)?; let controller = scenario.origin_controller1.clone(); let stash = scenario.origin_stash1.clone(); - assert!(T::SortedListProvider::contains(&stash)); + assert!(T::VoterList::contains(&stash)); add_slashing_spans::(&stash, s); }: _(RawOrigin::Root, stash.clone(), s) verify { assert!(!Ledger::::contains_key(&controller)); - assert!(!T::SortedListProvider::contains(&stash)); + assert!(!T::VoterList::contains(&stash)); } cancel_deferred_slash { @@ -704,13 +704,13 @@ benchmarks! { Ledger::::insert(&controller, l); assert!(Bonded::::contains_key(&stash)); - assert!(T::SortedListProvider::contains(&stash)); + assert!(T::VoterList::contains(&stash)); whitelist_account!(controller); }: _(RawOrigin::Signed(controller), stash.clone(), s) verify { assert!(!Bonded::::contains_key(&stash)); - assert!(!T::SortedListProvider::contains(&stash)); + assert!(!T::VoterList::contains(&stash)); } new_era { @@ -895,7 +895,7 @@ benchmarks! { let scenario = ListScenario::::new(origin_weight, true)?; let controller = scenario.origin_controller1.clone(); let stash = scenario.origin_stash1.clone(); - assert!(T::SortedListProvider::contains(&stash)); + assert!(T::VoterList::contains(&stash)); Staking::::set_staking_configs( RawOrigin::Root.into(), @@ -910,7 +910,7 @@ benchmarks! { let caller = whitelisted_caller(); }: _(RawOrigin::Signed(caller), controller.clone()) verify { - assert!(!T::SortedListProvider::contains(&stash)); + assert!(!T::VoterList::contains(&stash)); } force_apply_min_commission { diff --git a/frame/staking/src/lib.rs b/frame/staking/src/lib.rs index 2885de66598e7..2a0716721dd51 100644 --- a/frame/staking/src/lib.rs +++ b/frame/staking/src/lib.rs @@ -780,8 +780,8 @@ enum Releases { V5_0_0, // blockable validators. V6_0_0, // removal of all storage associated with offchain phragmen. V7_0_0, // keep track of number of nominators / validators in map - V8_0_0, // populate `SortedListProvider`. - V9_0_0, // inject validators into `SortedListProvider` as well. + V8_0_0, // populate `VoterList`. + V9_0_0, // inject validators into `VoterList` as well. } impl Default for Releases { diff --git a/frame/staking/src/migrations.rs b/frame/staking/src/migrations.rs index 96c5e2da89367..d3c4dd9809911 100644 --- a/frame/staking/src/migrations.rs +++ b/frame/staking/src/migrations.rs @@ -22,16 +22,16 @@ use frame_support::traits::OnRuntimeUpgrade; /// Migration implementation that injects all validators into sorted list. /// -/// This is only useful for chains that started their `SortedListProvider` just based on nominators. -pub struct InjectValidatorsIntoSortedListProvider(sp_std::marker::PhantomData); -impl OnRuntimeUpgrade for InjectValidatorsIntoSortedListProvider { +/// This is only useful for chains that started their `VoterList` just based on nominators. +pub struct InjectValidatorsIntoVoterList(sp_std::marker::PhantomData); +impl OnRuntimeUpgrade for InjectValidatorsIntoVoterList { fn on_runtime_upgrade() -> Weight { if StorageVersion::::get() == Releases::V8_0_0 { let weight_of_cached = Pallet::::weight_of_fn(); for (v, _) in Validators::::iter() { let weight = weight_of_cached(&v); - let _ = T::SortedListProvider::on_insert(v.clone(), weight).map_err(|err| { - log!(warn, "failed to insert {:?} into SortedListProvider: {:?}", v, err) + let _ = T::VoterList::on_insert(v.clone(), weight).map_err(|err| { + log!(warn, "failed to insert {:?} into VoterList: {:?}", v, err) }); } @@ -40,7 +40,7 @@ impl OnRuntimeUpgrade for InjectValidatorsIntoSortedListProvider { } else { log!( warn, - "InjectValidatorsIntoSortedListProvider being executed on the wrong storage \ + "InjectValidatorsIntoVoterList being executed on the wrong storage \ version, expected Releases::V8_0_0" ); T::DbWeight::get().reads(1) @@ -55,7 +55,7 @@ impl OnRuntimeUpgrade for InjectValidatorsIntoSortedListProvider { "must upgrade linearly" ); - let prev_count = T::SortedListProvider::count(); + let prev_count = T::VoterList::count(); Self::set_temp_storage(prev_count, "prev"); Ok(()) } @@ -63,7 +63,7 @@ impl OnRuntimeUpgrade for InjectValidatorsIntoSortedListProvider { #[cfg(feature = "try-runtime")] fn post_upgrade() -> Result<(), &'static str> { use frame_support::traits::OnRuntimeUpgradeHelpersExt; - let post_count = T::SortedListProvider::count(); + let post_count = T::VoterList::count(); let prev_count = Self::get_temp_storage::("prev").unwrap(); let validators = Validators::::count(); assert!(post_count == prev_count + validators); @@ -88,16 +88,16 @@ pub mod v8 { Ok(()) } - /// Migration to sorted [`SortedListProvider`]. + /// Migration to sorted [`VoterList`]. pub fn migrate() -> Weight { if StorageVersion::::get() == crate::Releases::V7_0_0 { crate::log!(info, "migrating staking to Releases::V8_0_0"); - let migrated = T::SortedListProvider::unsafe_regenerate( + let migrated = T::VoterList::unsafe_regenerate( Nominators::::iter().map(|(id, _)| id), Pallet::::weight_of_fn(), ); - debug_assert_eq!(T::SortedListProvider::sanity_check(), Ok(())); + debug_assert_eq!(T::VoterList::sanity_check(), Ok(())); StorageVersion::::put(crate::Releases::V8_0_0); crate::log!( @@ -114,8 +114,7 @@ pub mod v8 { #[cfg(feature = "try-runtime")] pub fn post_migrate() -> Result<(), &'static str> { - T::SortedListProvider::sanity_check() - .map_err(|_| "SortedListProvider is not in a sane state.")?; + T::VoterList::sanity_check().map_err(|_| "VoterList is not in a sane state.")?; crate::log!(info, "👜 staking bags-list migration passes POST migrate checks ✅",); Ok(()) } diff --git a/frame/staking/src/mock.rs b/frame/staking/src/mock.rs index effd4bc0c5ace..bb71232c34673 100644 --- a/frame/staking/src/mock.rs +++ b/frame/staking/src/mock.rs @@ -271,7 +271,7 @@ impl crate::pallet::pallet::Config for Test { type ElectionProvider = onchain::OnChainSequentialPhragmen; type GenesisElectionProvider = Self::ElectionProvider; // NOTE: consider a macro and use `UseNominatorsAndValidatorsMap` as well. - type SortedListProvider = BagsList; + type VoterList = BagsList; type MaxUnlockingChunks = ConstU32<32>; type BenchmarkingConfig = TestBenchmarkingConfig; type WeightInfo = (); @@ -541,8 +541,8 @@ fn check_count() { assert_eq!(nominator_count, Nominators::::count()); assert_eq!(validator_count, Validators::::count()); - // the voters that the `SortedListProvider` list is storing for us. - let external_voters = ::SortedListProvider::count(); + // the voters that the `VoterList` list is storing for us. + let external_voters = ::VoterList::count(); assert_eq!(external_voters, nominator_count + validator_count); } diff --git a/frame/staking/src/pallet/impls.rs b/frame/staking/src/pallet/impls.rs index a26207547f656..6ff125060a70f 100644 --- a/frame/staking/src/pallet/impls.rs +++ b/frame/staking/src/pallet/impls.rs @@ -667,7 +667,7 @@ impl Pallet { /// target are *auto-chilled*, but still count towards the limit imposed by `maybe_max_len`. pub fn get_npos_voters(maybe_max_len: Option) -> Vec> { let max_allowed_len = { - let all_voter_count = T::SortedListProvider::count() as usize; + let all_voter_count = T::VoterList::count() as usize; maybe_max_len.unwrap_or(all_voter_count).min(all_voter_count) }; @@ -681,7 +681,7 @@ impl Pallet { let mut validators_taken = 0u32; let mut nominators_taken = 0u32; - let mut sorted_voters = T::SortedListProvider::iter(); + let mut sorted_voters = T::VoterList::iter(); while all_voters.len() < max_allowed_len && voters_seen < (NPOS_MAX_ITERATIONS_COEFFICIENT * max_allowed_len as u32) { @@ -725,7 +725,7 @@ impl Pallet { // or bug if it does. log!( warn, - "DEFENSIVE: invalid item in `SortedListProvider`: {:?}, this nominator probably has too many nominations now", + "DEFENSIVE: invalid item in `VoterList`: {:?}, this nominator probably has too many nominations now", voter ) } @@ -769,7 +769,7 @@ impl Pallet { } /// This function will add a nominator to the `Nominators` storage map, - /// and [`SortedListProvider`]. + /// and [`VoterList`]. /// /// If the nominator already exists, their nominations will be updated. /// @@ -779,20 +779,20 @@ impl Pallet { pub fn do_add_nominator(who: &T::AccountId, nominations: Nominations) { if !Nominators::::contains_key(who) { // maybe update sorted list. - let _ = T::SortedListProvider::on_insert(who.clone(), Self::weight_of(who)) + let _ = T::VoterList::on_insert(who.clone(), Self::weight_of(who)) .defensive_unwrap_or_default(); } Nominators::::insert(who, nominations); debug_assert_eq!( Nominators::::count() + Validators::::count(), - T::SortedListProvider::count() + T::VoterList::count() ); - debug_assert_eq!(T::SortedListProvider::sanity_check(), Ok(())); + debug_assert_eq!(T::VoterList::sanity_check(), Ok(())); } /// This function will remove a nominator from the `Nominators` storage map, - /// and [`SortedListProvider`]. + /// and [`VoterList`]. /// /// Returns true if `who` was removed from `Nominators`, otherwise false. /// @@ -802,16 +802,16 @@ impl Pallet { pub fn do_remove_nominator(who: &T::AccountId) -> bool { let outcome = if Nominators::::contains_key(who) { Nominators::::remove(who); - T::SortedListProvider::on_remove(who); + T::VoterList::on_remove(who); true } else { false }; - debug_assert_eq!(T::SortedListProvider::sanity_check(), Ok(())); + debug_assert_eq!(T::VoterList::sanity_check(), Ok(())); debug_assert_eq!( Nominators::::count() + Validators::::count(), - T::SortedListProvider::count() + T::VoterList::count() ); outcome @@ -827,16 +827,16 @@ impl Pallet { pub fn do_add_validator(who: &T::AccountId, prefs: ValidatorPrefs) { if !Validators::::contains_key(who) { // maybe update sorted list. - let _ = T::SortedListProvider::on_insert(who.clone(), Self::weight_of(who)) + let _ = T::VoterList::on_insert(who.clone(), Self::weight_of(who)) .defensive_unwrap_or_default(); } Validators::::insert(who, prefs); debug_assert_eq!( Nominators::::count() + Validators::::count(), - T::SortedListProvider::count() + T::VoterList::count() ); - debug_assert_eq!(T::SortedListProvider::sanity_check(), Ok(())); + debug_assert_eq!(T::VoterList::sanity_check(), Ok(())); } /// This function will remove a validator from the `Validators` storage map. @@ -849,16 +849,16 @@ impl Pallet { pub fn do_remove_validator(who: &T::AccountId) -> bool { let outcome = if Validators::::contains_key(who) { Validators::::remove(who); - T::SortedListProvider::on_remove(who); + T::VoterList::on_remove(who); true } else { false }; - debug_assert_eq!(T::SortedListProvider::sanity_check(), Ok(())); + debug_assert_eq!(T::VoterList::sanity_check(), Ok(())); debug_assert_eq!( Nominators::::count() + Validators::::count(), - T::SortedListProvider::count() + T::VoterList::count() ); outcome @@ -988,7 +988,7 @@ impl ElectionDataProvider for Pallet { >::remove_all(); >::remove_all(); - T::SortedListProvider::unsafe_clear(); + T::VoterList::unsafe_clear(); } #[cfg(feature = "runtime-benchmarks")] diff --git a/frame/staking/src/pallet/mod.rs b/frame/staking/src/pallet/mod.rs index c7b848540c362..306bd34390d82 100644 --- a/frame/staking/src/pallet/mod.rs +++ b/frame/staking/src/pallet/mod.rs @@ -17,7 +17,7 @@ //! Staking FRAME Pallet. -use frame_election_provider_support::SortedListProvider; +use frame_election_provider_support::{SortedListProvider, VoteWeight}; use frame_support::{ dispatch::Codec, pallet_prelude::*, @@ -163,13 +163,12 @@ pub mod pallet { /// After the threshold is reached a new era will be forced. type OffendingValidatorsThreshold: Get; - /// Something that can provide a sorted list of voters in a somewhat sorted way. The - /// original use case for this was designed with `pallet_bags_list::Pallet` in mind. If - /// the bags-list is not desired, [`impls::UseNominatorsMap`] is likely the desired option. - type SortedListProvider: SortedListProvider< - Self::AccountId, - Score = frame_election_provider_support::VoteWeight, - >; + /// Something that provides a best-effort sorted list of voters aka electing nominators, + /// used for NPoS election. + /// + /// The changes to nominators are reported to this. Moreover, each validator's self-vote is + /// also reported as one independent vote. + type VoterList: SortedListProvider; /// The maximum number of `unlocking` chunks a [`StakingLedger`] can have. Effectively /// determines how many unique eras a staker may be unbonding in. @@ -584,9 +583,9 @@ pub mod pallet { }); } - // all voters are reported to the `SortedListProvider`. + // all voters are reported to the `VoterList`. assert_eq!( - T::SortedListProvider::count(), + T::VoterList::count(), Nominators::::count() + Validators::::count(), "not all genesis stakers were inserted into sorted list provider, something is wrong." ); @@ -837,9 +836,9 @@ pub mod pallet { // NOTE: ledger must be updated prior to calling `Self::weight_of`. Self::update_ledger(&controller, &ledger); // update this staker in the sorted list, if they exist in it. - if T::SortedListProvider::contains(&stash) { - T::SortedListProvider::on_update(&stash, Self::weight_of(&ledger.stash)); - debug_assert_eq!(T::SortedListProvider::sanity_check(), Ok(())); + if T::VoterList::contains(&stash) { + T::VoterList::on_update(&stash, Self::weight_of(&ledger.stash)); + debug_assert_eq!(T::VoterList::sanity_check(), Ok(())); } Self::deposit_event(Event::::Bonded(stash.clone(), extra)); @@ -920,8 +919,8 @@ pub mod pallet { Self::update_ledger(&controller, &ledger); // update this staker in the sorted list, if they exist in it. - if T::SortedListProvider::contains(&ledger.stash) { - T::SortedListProvider::on_update(&ledger.stash, Self::weight_of(&ledger.stash)); + if T::VoterList::contains(&ledger.stash) { + T::VoterList::on_update(&ledger.stash, Self::weight_of(&ledger.stash)); } Self::deposit_event(Event::::Unbonded(ledger.stash, value)); @@ -1403,8 +1402,8 @@ pub mod pallet { // NOTE: ledger must be updated prior to calling `Self::weight_of`. Self::update_ledger(&controller, &ledger); - if T::SortedListProvider::contains(&ledger.stash) { - T::SortedListProvider::on_update(&ledger.stash, Self::weight_of(&ledger.stash)); + if T::VoterList::contains(&ledger.stash) { + T::VoterList::on_update(&ledger.stash, Self::weight_of(&ledger.stash)); } let removed_chunks = 1u32 // for the case where the last iterated chunk is not removed diff --git a/frame/staking/src/testing_utils.rs b/frame/staking/src/testing_utils.rs index 8e6bd88468930..5f9f378b10619 100644 --- a/frame/staking/src/testing_utils.rs +++ b/frame/staking/src/testing_utils.rs @@ -38,11 +38,11 @@ const SEED: u32 = 0; pub fn clear_validators_and_nominators() { Validators::::remove_all(); - // whenever we touch nominators counter we should update `T::SortedListProvider` as well. + // whenever we touch nominators counter we should update `T::VoterList` as well. Nominators::::remove_all(); // NOTE: safe to call outside block production - T::SortedListProvider::unsafe_clear(); + T::VoterList::unsafe_clear(); } /// Grab a funded user. diff --git a/frame/staking/src/tests.rs b/frame/staking/src/tests.rs index fd5ffc9c5791c..13b46c869ba73 100644 --- a/frame/staking/src/tests.rs +++ b/frame/staking/src/tests.rs @@ -4111,7 +4111,7 @@ mod election_data_provider { .set_status(41, StakerStatus::Validator) .build_and_execute(|| { // sum of all nominators who'd be voters (1), plus the self-votes (4). - assert_eq!(::SortedListProvider::count(), 5); + assert_eq!(::VoterList::count(), 5); // if limits is less.. assert_eq!(Staking::voters(Some(1)).unwrap().len(), 1); @@ -4145,7 +4145,7 @@ mod election_data_provider { .build_and_execute(|| { // all voters ordered by stake, assert_eq!( - ::SortedListProvider::iter().collect::>(), + ::VoterList::iter().collect::>(), vec![61, 71, 81, 11, 21, 31] ); @@ -4187,7 +4187,7 @@ mod election_data_provider { .build_and_execute(|| { // given our voters ordered by stake, assert_eq!( - ::SortedListProvider::iter().collect::>(), + ::VoterList::iter().collect::>(), vec![11, 21, 31, 61, 71, 81] ); @@ -4734,10 +4734,10 @@ mod sorted_list_provider { // given let pre_insert_voter_count = (Nominators::::count() + Validators::::count()) as u32; - assert_eq!(::SortedListProvider::count(), pre_insert_voter_count); + assert_eq!(::VoterList::count(), pre_insert_voter_count); assert_eq!( - ::SortedListProvider::iter().collect::>(), + ::VoterList::iter().collect::>(), vec![11, 21, 31, 101] ); @@ -4745,10 +4745,10 @@ mod sorted_list_provider { assert_ok!(Staking::nominate(Origin::signed(100), vec![41])); // then counts don't change - assert_eq!(::SortedListProvider::count(), pre_insert_voter_count); + assert_eq!(::VoterList::count(), pre_insert_voter_count); // and the list is the same assert_eq!( - ::SortedListProvider::iter().collect::>(), + ::VoterList::iter().collect::>(), vec![11, 21, 31, 101] ); }); @@ -4760,23 +4760,17 @@ mod sorted_list_provider { // given let pre_insert_voter_count = (Nominators::::count() + Validators::::count()) as u32; - assert_eq!(::SortedListProvider::count(), pre_insert_voter_count); + assert_eq!(::VoterList::count(), pre_insert_voter_count); - assert_eq!( - ::SortedListProvider::iter().collect::>(), - vec![11, 21, 31] - ); + assert_eq!(::VoterList::iter().collect::>(), vec![11, 21, 31]); // when account 11 re-validates assert_ok!(Staking::validate(Origin::signed(10), Default::default())); // then counts don't change - assert_eq!(::SortedListProvider::count(), pre_insert_voter_count); + assert_eq!(::VoterList::count(), pre_insert_voter_count); // and the list is the same - assert_eq!( - ::SortedListProvider::iter().collect::>(), - vec![11, 21, 31] - ); + assert_eq!(::VoterList::iter().collect::>(), vec![11, 21, 31]); }); } } From 4c9ef4b8b087a52166ac0414e3002b14b53abd80 Mon Sep 17 00:00:00 2001 From: kianenigma Date: Fri, 11 Mar 2022 14:52:51 +0000 Subject: [PATCH 26/42] Fix all build and docs --- bin/node/runtime/src/lib.rs | 4 +- frame/babe/src/mock.rs | 2 +- frame/bags-list/remote-tests/src/lib.rs | 2 +- frame/bags-list/remote-tests/src/migration.rs | 11 +- frame/bags-list/remote-tests/src/snapshot.rs | 5 +- frame/grandpa/src/mock.rs | 2 +- frame/offences/benchmarking/src/mock.rs | 2 +- frame/session/benchmarking/src/mock.rs | 2 +- frame/session/src/migrations/v1.rs | 4 +- frame/staking/src/migrations.rs | 101 ++++++++++-------- frame/staking/src/pallet/impls.rs | 4 +- 11 files changed, 74 insertions(+), 65 deletions(-) diff --git a/bin/node/runtime/src/lib.rs b/bin/node/runtime/src/lib.rs index 8c7a20af15683..6f4af10a13a89 100644 --- a/bin/node/runtime/src/lib.rs +++ b/bin/node/runtime/src/lib.rs @@ -555,9 +555,7 @@ impl pallet_staking::Config for Runtime { type OffendingValidatorsThreshold = OffendingValidatorsThreshold; type ElectionProvider = ElectionProviderMultiPhase; type GenesisElectionProvider = onchain::OnChainSequentialPhragmen; - // Alternatively, use pallet_staking::UseNominatorsMap to just use the nominators map. - // Note that the aforementioned does not scale to a very large number of nominators. - type SortedListProvider = BagsList; + type VoterList = BagsList; type MaxUnlockingChunks = ConstU32<32>; type WeightInfo = pallet_staking::weights::SubstrateWeight; type BenchmarkingConfig = StakingBenchmarkingConfig; diff --git a/frame/babe/src/mock.rs b/frame/babe/src/mock.rs index 802a39c1f1bf8..e74288577c9ef 100644 --- a/frame/babe/src/mock.rs +++ b/frame/babe/src/mock.rs @@ -197,7 +197,7 @@ impl pallet_staking::Config for Test { type NextNewSession = Session; type ElectionProvider = onchain::OnChainSequentialPhragmen; type GenesisElectionProvider = Self::ElectionProvider; - type SortedListProvider = pallet_staking::UseNominatorsAndValidatorsMap; + type VoterList = pallet_staking::UseNominatorsAndValidatorsMap; type MaxUnlockingChunks = ConstU32<32>; type BenchmarkingConfig = pallet_staking::TestBenchmarkingConfig; type WeightInfo = (); diff --git a/frame/bags-list/remote-tests/src/lib.rs b/frame/bags-list/remote-tests/src/lib.rs index 83c322f93134e..caf7a2a547e09 100644 --- a/frame/bags-list/remote-tests/src/lib.rs +++ b/frame/bags-list/remote-tests/src/lib.rs @@ -48,7 +48,7 @@ pub fn display_and_check_bags(currency_unit: u64, currency_na let min_nominator_bond = >::get(); log::info!(target: LOG_TARGET, "min nominator bond is {:?}", min_nominator_bond); - let voter_list_count = ::SortedListProvider::count(); + let voter_list_count = ::VoterList::count(); // go through every bag to track the total number of voters within bags and log some info about // how voters are distributed within the bags. diff --git a/frame/bags-list/remote-tests/src/migration.rs b/frame/bags-list/remote-tests/src/migration.rs index 4d5169fcc6dfa..c4cd73c45d377 100644 --- a/frame/bags-list/remote-tests/src/migration.rs +++ b/frame/bags-list/remote-tests/src/migration.rs @@ -17,7 +17,6 @@ //! Test to check the migration of the voter bag. use crate::{RuntimeT, LOG_TARGET}; -use frame_election_provider_support::SortedListProvider; use frame_support::traits::PalletInfoAccess; use pallet_staking::Nominators; use remote_externalities::{Builder, Mode, OnlineConfig}; @@ -45,16 +44,16 @@ pub async fn execute( let pre_migrate_nominator_count = >::iter().count() as u32; log::info!(target: LOG_TARGET, "Nominator count: {}", pre_migrate_nominator_count); - // run the actual migration, - let moved = ::SortedListProvider::unsafe_regenerate( + use frame_election_provider_support::SortedListProvider; + // run the actual migration + let moved = ::VoterList::unsafe_regenerate( pallet_staking::Nominators::::iter().map(|(n, _)| n), pallet_staking::Pallet::::weight_of_fn(), ); log::info!(target: LOG_TARGET, "Moved {} nominators", moved); - let voter_list_len = - ::SortedListProvider::iter().count() as u32; - let voter_list_count = ::SortedListProvider::count(); + let voter_list_len = ::VoterList::iter().count() as u32; + let voter_list_count = ::VoterList::count(); // and confirm it is equal to the length of the `VoterList`. assert_eq!(pre_migrate_nominator_count, voter_list_len); assert_eq!(pre_migrate_nominator_count, voter_list_count); diff --git a/frame/bags-list/remote-tests/src/snapshot.rs b/frame/bags-list/remote-tests/src/snapshot.rs index 241b64b366117..e9af726e78dc6 100644 --- a/frame/bags-list/remote-tests/src/snapshot.rs +++ b/frame/bags-list/remote-tests/src/snapshot.rs @@ -16,6 +16,7 @@ //! Test to execute the snapshot using the voter bag. +use frame_election_provider_support::SortedListProvider; use frame_support::traits::PalletInfoAccess; use remote_externalities::{Builder, Mode, OnlineConfig}; use sp_runtime::{traits::Block as BlockT, DeserializeOwned}; @@ -48,11 +49,11 @@ pub async fn execute .unwrap(); ext.execute_with(|| { - use frame_election_provider_support::{ElectionDataProvider, SortedListProvider}; + use frame_election_provider_support::ElectionDataProvider; log::info!( target: crate::LOG_TARGET, "{} nodes in bags list.", - ::SortedListProvider::count(), + ::VoterList::count(), ); let voters = diff --git a/frame/grandpa/src/mock.rs b/frame/grandpa/src/mock.rs index 98f95f13ac23f..6490a2b6992bf 100644 --- a/frame/grandpa/src/mock.rs +++ b/frame/grandpa/src/mock.rs @@ -205,7 +205,7 @@ impl pallet_staking::Config for Test { type NextNewSession = Session; type ElectionProvider = onchain::OnChainSequentialPhragmen; type GenesisElectionProvider = Self::ElectionProvider; - type SortedListProvider = pallet_staking::UseNominatorsAndValidatorsMap; + type VoterList = pallet_staking::UseNominatorsAndValidatorsMap; type MaxUnlockingChunks = ConstU32<32>; type BenchmarkingConfig = pallet_staking::TestBenchmarkingConfig; type WeightInfo = (); diff --git a/frame/offences/benchmarking/src/mock.rs b/frame/offences/benchmarking/src/mock.rs index 5858dfe548e63..4359b7745ddd6 100644 --- a/frame/offences/benchmarking/src/mock.rs +++ b/frame/offences/benchmarking/src/mock.rs @@ -175,7 +175,7 @@ impl pallet_staking::Config for Test { type OffendingValidatorsThreshold = (); type ElectionProvider = onchain::OnChainSequentialPhragmen; type GenesisElectionProvider = Self::ElectionProvider; - type SortedListProvider = pallet_staking::UseNominatorsAndValidatorsMap; + type VoterList = pallet_staking::UseNominatorsAndValidatorsMap; type MaxUnlockingChunks = ConstU32<32>; type BenchmarkingConfig = pallet_staking::TestBenchmarkingConfig; type WeightInfo = (); diff --git a/frame/session/benchmarking/src/mock.rs b/frame/session/benchmarking/src/mock.rs index e77f6d5a4af9b..5ebc75245630c 100644 --- a/frame/session/benchmarking/src/mock.rs +++ b/frame/session/benchmarking/src/mock.rs @@ -182,7 +182,7 @@ impl pallet_staking::Config for Test { type ElectionProvider = onchain::OnChainSequentialPhragmen; type GenesisElectionProvider = Self::ElectionProvider; type MaxUnlockingChunks = ConstU32<32>; - type SortedListProvider = pallet_staking::UseNominatorsAndValidatorsMap; + type VoterList = pallet_staking::UseNominatorsAndValidatorsMap; type BenchmarkingConfig = pallet_staking::TestBenchmarkingConfig; type WeightInfo = (); } diff --git a/frame/session/src/migrations/v1.rs b/frame/session/src/migrations/v1.rs index 2a69cd6d6a550..3c687ea7d9d66 100644 --- a/frame/session/src/migrations/v1.rs +++ b/frame/session/src/migrations/v1.rs @@ -87,7 +87,7 @@ pub fn migrate(sp_std::marker::PhantomData); -impl OnRuntimeUpgrade for InjectValidatorsIntoVoterList { - fn on_runtime_upgrade() -> Weight { - if StorageVersion::::get() == Releases::V8_0_0 { - let weight_of_cached = Pallet::::weight_of_fn(); - for (v, _) in Validators::::iter() { - let weight = weight_of_cached(&v); - let _ = T::VoterList::on_insert(v.clone(), weight).map_err(|err| { - log!(warn, "failed to insert {:?} into VoterList: {:?}", v, err) - }); - } +pub mod v9 { + use super::*; - StorageVersion::::put(crate::Releases::V9_0_0); - T::BlockWeights::get().max_block - } else { - log!( - warn, - "InjectValidatorsIntoVoterList being executed on the wrong storage \ + /// Migration implementation that injects all validators into sorted list. + /// + /// This is only useful for chains that started their `VoterList` just based on nominators. + pub struct InjectValidatorsIntoVoterList(sp_std::marker::PhantomData); + impl OnRuntimeUpgrade for InjectValidatorsIntoVoterList { + fn on_runtime_upgrade() -> Weight { + if StorageVersion::::get() == Releases::V8_0_0 { + let prev_count = T::VoterList::count(); + let weight_of_cached = Pallet::::weight_of_fn(); + for (v, _) in Validators::::iter() { + let weight = weight_of_cached(&v); + let _ = T::VoterList::on_insert(v.clone(), weight).map_err(|err| { + log!(warn, "failed to insert {:?} into VoterList: {:?}", v, err) + }); + } + + log!( + info, + "injected a total of {} new voters, prev count: {} next count: {}", + Validators::::count(), + prev_count, + T::VoterList::count(), + ); + StorageVersion::::put(crate::Releases::V9_0_0); + T::BlockWeights::get().max_block + } else { + log!( + warn, + "InjectValidatorsIntoVoterList being executed on the wrong storage \ version, expected Releases::V8_0_0" - ); - T::DbWeight::get().reads(1) + ); + T::DbWeight::get().reads(1) + } } - } - #[cfg(feature = "try-runtime")] - fn pre_upgrade() -> Result<(), &'static str> { - use frame_support::traits::OnRuntimeUpgradeHelpersExt; - frame_support::ensure!( - StorageVersion::::get() == crate::Releases::V8_0_0, - "must upgrade linearly" - ); + #[cfg(feature = "try-runtime")] + fn pre_upgrade() -> Result<(), &'static str> { + use frame_support::traits::OnRuntimeUpgradeHelpersExt; + frame_support::ensure!( + StorageVersion::::get() == crate::Releases::V8_0_0, + "must upgrade linearly" + ); - let prev_count = T::VoterList::count(); - Self::set_temp_storage(prev_count, "prev"); - Ok(()) - } + let prev_count = T::VoterList::count(); + Self::set_temp_storage(prev_count, "prev"); + Ok(()) + } - #[cfg(feature = "try-runtime")] - fn post_upgrade() -> Result<(), &'static str> { - use frame_support::traits::OnRuntimeUpgradeHelpersExt; - let post_count = T::VoterList::count(); - let prev_count = Self::get_temp_storage::("prev").unwrap(); - let validators = Validators::::count(); - assert!(post_count == prev_count + validators); - Ok(()) + #[cfg(feature = "try-runtime")] + fn post_upgrade() -> Result<(), &'static str> { + use frame_support::traits::OnRuntimeUpgradeHelpersExt; + let post_count = T::VoterList::count(); + let prev_count = Self::get_temp_storage::("prev").unwrap(); + let validators = Validators::::count(); + assert!(post_count == prev_count + validators); + Ok(()) + } } } pub mod v8 { + use crate::{Config, Nominators, Pallet, StorageVersion, Weight}; use frame_election_provider_support::SortedListProvider; use frame_support::traits::Get; - use crate::{Config, Nominators, Pallet, StorageVersion, Weight}; - #[cfg(feature = "try-runtime")] pub fn pre_migrate() -> Result<(), &'static str> { frame_support::ensure!( @@ -88,7 +99,7 @@ pub mod v8 { Ok(()) } - /// Migration to sorted [`VoterList`]. + /// Migration to sorted `VoterList`. pub fn migrate() -> Weight { if StorageVersion::::get() == crate::Releases::V7_0_0 { crate::log!(info, "migrating staking to Releases::V8_0_0"); diff --git a/frame/staking/src/pallet/impls.rs b/frame/staking/src/pallet/impls.rs index 6ff125060a70f..d976ff6e2dcc0 100644 --- a/frame/staking/src/pallet/impls.rs +++ b/frame/staking/src/pallet/impls.rs @@ -769,7 +769,7 @@ impl Pallet { } /// This function will add a nominator to the `Nominators` storage map, - /// and [`VoterList`]. + /// and `VoterList`. /// /// If the nominator already exists, their nominations will be updated. /// @@ -792,7 +792,7 @@ impl Pallet { } /// This function will remove a nominator from the `Nominators` storage map, - /// and [`VoterList`]. + /// and `VoterList`. /// /// Returns true if `who` was removed from `Nominators`, otherwise false. /// From b311d039fd245710503ad1831c7f433de9662971 Mon Sep 17 00:00:00 2001 From: kianenigma Date: Fri, 11 Mar 2022 14:58:42 +0000 Subject: [PATCH 27/42] check post migration --- frame/staking/src/migrations.rs | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/frame/staking/src/migrations.rs b/frame/staking/src/migrations.rs index a8d11df80b8fb..96c905f4e5942 100644 --- a/frame/staking/src/migrations.rs +++ b/frame/staking/src/migrations.rs @@ -41,11 +41,12 @@ pub mod v9 { log!( info, - "injected a total of {} new voters, prev count: {} next count: {}", + "injected a total of {} new voters, prev count: {} next count: {}, updating to version 9", Validators::::count(), prev_count, T::VoterList::count(), ); + StorageVersion::::put(crate::Releases::V9_0_0); T::BlockWeights::get().max_block } else { @@ -78,6 +79,11 @@ pub mod v9 { let prev_count = Self::get_temp_storage::("prev").unwrap(); let validators = Validators::::count(); assert!(post_count == prev_count + validators); + + frame_support::ensure!( + StorageVersion::::get() == crate::Releases::V9_0_0, + "must upgrade " + ); Ok(()) } } From a4bbda1f76aaee7b6c513722c3d72a68c569524c Mon Sep 17 00:00:00 2001 From: kianenigma Date: Fri, 11 Mar 2022 15:16:45 +0000 Subject: [PATCH 28/42] better doc --- frame/staking/src/pallet/mod.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/frame/staking/src/pallet/mod.rs b/frame/staking/src/pallet/mod.rs index 208d07af6dd8c..f502d38f8cac0 100644 --- a/frame/staking/src/pallet/mod.rs +++ b/frame/staking/src/pallet/mod.rs @@ -166,7 +166,8 @@ pub mod pallet { /// Something that provides a best-effort sorted list of voters aka electing nominators, /// used for NPoS election. /// - /// The changes to nominators are reported to this. + /// The changes to nominators are reported to this. Moreover, each validator's self-vote is + /// also reported as one independent vote. type VoterList: SortedListProvider; /// Something that provides a best-effort sorted list of targets aka electable validators, From 19bd4cdcb3fde2b9d1f0f40f79ade5533c635246 Mon Sep 17 00:00:00 2001 From: kianenigma Date: Fri, 11 Mar 2022 15:41:37 +0000 Subject: [PATCH 29/42] mostly stable and working stuff --- bin/node/runtime/src/lib.rs | 1 - frame/staking/src/migrations.rs | 57 +++++++++++++++++---------------- 2 files changed, 30 insertions(+), 28 deletions(-) diff --git a/bin/node/runtime/src/lib.rs b/bin/node/runtime/src/lib.rs index 2dcd2e336121e..5c3e629844d1b 100644 --- a/bin/node/runtime/src/lib.rs +++ b/bin/node/runtime/src/lib.rs @@ -49,7 +49,6 @@ use pallet_grandpa::{ }; use pallet_im_online::sr25519::AuthorityId as ImOnlineId; use pallet_session::historical as pallet_session_historical; -use pallet_staking::UseValidatorsMap; pub use pallet_transaction_payment::{CurrencyAdapter, Multiplier, TargetedFeeAdjustment}; use pallet_transaction_payment::{FeeDetails, RuntimeDispatchInfo}; use sp_api::impl_runtime_apis; diff --git a/frame/staking/src/migrations.rs b/frame/staking/src/migrations.rs index 2d696f6551f35..2d240f05d55f9 100644 --- a/frame/staking/src/migrations.rs +++ b/frame/staking/src/migrations.rs @@ -18,7 +18,10 @@ use super::*; use frame_election_provider_support::SortedListProvider; -use frame_support::traits::{Defensive, OnRuntimeUpgrade}; +use frame_support::{ + ensure, + traits::{Defensive, OnRuntimeUpgrade}, +}; use sp_std::collections::btree_map::BTreeMap; pub mod v10 { @@ -77,7 +80,7 @@ pub mod v10 { #[cfg(feature = "try-runtime")] fn post_upgrade() -> Result<(), &'static str> { - ensure!(StorageVersion::::get(), Releases::V10_0_0, "must upgrade linearly"); + ensure!(StorageVersion::::get() == Releases::V10_0_0, "must upgrade linearly"); Ok(()) } } @@ -121,34 +124,34 @@ pub mod v9 { T::DbWeight::get().reads(1) } } - } - - #[cfg(feature = "try-runtime")] - fn pre_upgrade() -> Result<(), &'static str> { - use frame_support::traits::OnRuntimeUpgradeHelpersExt; - frame_support::ensure!( - StorageVersion::::get() == crate::Releases::V8_0_0, - "must upgrade linearly" - ); - let prev_count = T::VoterList::count(); - Self::set_temp_storage(prev_count, "prev"); - Ok(()) - } + #[cfg(feature = "try-runtime")] + fn pre_upgrade() -> Result<(), &'static str> { + use frame_support::traits::OnRuntimeUpgradeHelpersExt; + frame_support::ensure!( + StorageVersion::::get() == crate::Releases::V8_0_0, + "must upgrade linearly" + ); - #[cfg(feature = "try-runtime")] - fn post_upgrade() -> Result<(), &'static str> { - use frame_support::traits::OnRuntimeUpgradeHelpersExt; - let post_count = T::VoterList::count(); - let prev_count = Self::get_temp_storage::("prev").unwrap(); - let validators = Validators::::count(); - assert!(post_count == prev_count + validators); + let prev_count = T::VoterList::count(); + Self::set_temp_storage(prev_count, "prev"); + Ok(()) + } - frame_support::ensure!( - StorageVersion::::get() == crate::Releases::V9_0_0, - "must upgrade " - ); - Ok(()) + #[cfg(feature = "try-runtime")] + fn post_upgrade() -> Result<(), &'static str> { + use frame_support::traits::OnRuntimeUpgradeHelpersExt; + let post_count = T::VoterList::count(); + let prev_count = Self::get_temp_storage::("prev").unwrap(); + let validators = Validators::::count(); + assert!(post_count == prev_count + validators); + + frame_support::ensure!( + StorageVersion::::get() == crate::Releases::V9_0_0, + "must upgrade " + ); + Ok(()) + } } } From 43cec358e6c7de0b2e601a7b3d64f7f75fb3dc66 Mon Sep 17 00:00:00 2001 From: kianenigma Date: Fri, 25 Mar 2022 19:56:50 +0000 Subject: [PATCH 30/42] some fixes to make node-runtime work --- bin/node/runtime/src/lib.rs | 30 ++- bin/node/runtime/src/voter_bags.rs | 203 +++++++++++++++++++++ frame/bags-list/src/list/mod.rs | 7 +- frame/bags-list/src/migrations.rs | 4 +- frame/election-provider-support/src/lib.rs | 3 +- frame/staking/src/pallet/impls.rs | 4 +- 6 files changed, 232 insertions(+), 19 deletions(-) diff --git a/bin/node/runtime/src/lib.rs b/bin/node/runtime/src/lib.rs index 71da9261b635e..cc02a2f79dc0b 100644 --- a/bin/node/runtime/src/lib.rs +++ b/bin/node/runtime/src/lib.rs @@ -558,8 +558,8 @@ impl pallet_staking::Config for Runtime { type OffendingValidatorsThreshold = OffendingValidatorsThreshold; type ElectionProvider = ElectionProviderMultiPhase; type GenesisElectionProvider = onchain::OnChainSequentialPhragmen; - type VoterList = BagsList; - type TargetList = UseValidatorsMap; + type VoterList = VoterBagsList; + type TargetList = TargetBagsList; type MaxUnlockingChunks = ConstU32<32>; type WeightInfo = pallet_staking::weights::SubstrateWeight; type BenchmarkingConfig = StakingBenchmarkingConfig; @@ -685,14 +685,29 @@ impl pallet_election_provider_multi_phase::Config for Runtime { parameter_types! { pub const BagThresholds: &'static [u64] = &voter_bags::THRESHOLDS; + pub BagThresholdsBalance: &'static [Balance] = &voter_bags::THRESHOLDS_BALANCE; } -impl pallet_bags_list::Config for Runtime { +type VoterBagsListInstance = pallet_bags_list::Instance1; +impl pallet_bags_list::Config for Runtime { type Event = Event; - type ScoreProvider = Staking; - type WeightInfo = pallet_bags_list::weights::SubstrateWeight; type BagThresholds = BagThresholds; + /// The voter bags-list is loosely kept up to date, and the real source of truth for the score + /// of each node is the staking pallet. + type ScoreProvider = Staking; type Score = VoteWeight; + type WeightInfo = pallet_bags_list::weights::SubstrateWeight; +} + +type TargetBagsListInstance = pallet_bags_list::Instance2; +impl pallet_bags_list::Config for Runtime { + type Event = Event; + // The bags-list itself will be the source of truth about the approval stakes. This implies that + // staking should keep the approval stakes up to date at all times. + type ScoreProvider = TargetBagsList; + type BagThresholds = BagThresholdsBalance; + type Score = Balance; + type WeightInfo = pallet_bags_list::weights::SubstrateWeight; } parameter_types! { @@ -1439,7 +1454,8 @@ construct_runtime!( Gilt: pallet_gilt, Uniques: pallet_uniques, TransactionStorage: pallet_transaction_storage, - BagsList: pallet_bags_list, + VoterBagsList: pallet_bags_list::, + TargetBagsList: pallet_bags_list::, StateTrieMigration: pallet_state_trie_migration, ChildBounties: pallet_child_bounties, Referenda: pallet_referenda, @@ -1486,7 +1502,7 @@ pub type Executive = frame_executive::Executive< frame_system::ChainContext, Runtime, AllPalletsWithSystem, - pallet_bags_list::migrations::CheckCounterPrefix, + (), >; /// MMR helper types. diff --git a/bin/node/runtime/src/voter_bags.rs b/bin/node/runtime/src/voter_bags.rs index 93790f028f457..87bb29f5ac76b 100644 --- a/bin/node/runtime/src/voter_bags.rs +++ b/bin/node/runtime/src/voter_bags.rs @@ -233,3 +233,206 @@ pub const THRESHOLDS: [u64; 200] = [ 17_356_326_621_502_140_416, 18_446_744_073_709_551_615, ]; + +pub const THRESHOLDS_BALANCE: [crate::Balance; 200] = [ + 100_000_000_000_000, + 106_282_535_907_434, + 112_959_774_389_150, + 120_056_512_776_105, + 127_599_106_300_477, + 135_615_565_971_369, + 144_135_662_599_590, + 153_191_037_357_827, + 162_815_319_286_803, + 173_044_250_183_800, + 183_915_817_337_347, + 195_470_394_601_017, + 207_750_892_330_229, + 220_802_916_738_890, + 234_674_939_267_673, + 249_418_476_592_914, + 265_088_281_944_639, + 281_742_548_444_211, + 299_443_125_216_738, + 318_255_747_080_822, + 338_250_278_668_647, + 359_500_973_883_001, + 382_086_751_654_776, + 406_091_489_025_036, + 431_604_332_640_068, + 458_720_029_816_222, + 487_539_280_404_019, + 518_169_110_758_247, + 550_723_271_202_866, + 585_322_658_466_782, + 622_095_764_659_305, + 661_179_154_452_653, + 702_717_972_243_610, + 746_866_481_177_808, + 793_788_636_038_393, + 843_658_692_126_636, + 896_661_852_395_681, + 952_994_955_240_703, + 1_012_867_205_499_736, + 1_076_500_951_379_881, + 1_144_132_510_194_192, + 1_216_013_045_975_769, + 1_292_409_502_228_280, + 1_373_605_593_276_862, + 1_459_902_857_901_004, + 1_551_621_779_162_291, + 1_649_102_974_585_730, + 1_752_708_461_114_642, + 1_862_822_999_536_805, + 1_979_855_523_374_646, + 2_104_240_657_545_975, + 2_236_440_332_435_128, + 2_376_945_499_368_703, + 2_526_277_953_866_680, + 2_684_992_273_439_945, + 2_853_677_877_130_641, + 3_032_961_214_443_876, + 3_223_508_091_799_862, + 3_426_026_145_146_232, + 3_641_267_467_913_124, + 3_870_031_404_070_482, + 4_113_167_516_660_186, + 4_371_578_742_827_277, + 4_646_224_747_067_156, + 4_938_125_485_141_739, + 5_248_364_991_899_922, + 5_578_095_407_069_235, + 5_928_541_253_969_291, + 6_301_003_987_036_955, + 6_696_866_825_051_405, + 7_117_599_888_008_300, + 7_564_765_656_719_910, + 8_040_024_775_416_580, + 8_545_142_218_898_723, + 9_081_993_847_142_344, + 9_652_573_371_700_016, + 10_258_999_759_768_490, + 10_903_525_103_419_522, + 11_588_542_983_217_942, + 12_316_597_357_287_042, + 13_090_392_008_832_678, + 13_912_800_587_211_472, + 14_786_877_279_832_732, + 15_715_868_154_526_436, + 16_703_223_214_499_558, + 17_752_609_210_649_358, + 18_867_923_258_814_856, + 20_053_307_312_537_008, + 21_313_163_545_075_252, + 22_652_170_697_804_756, + 24_075_301_455_707_600, + 25_587_840_914_485_432, + 27_195_406_207_875_088, + 28_903_967_368_057_400, + 30_719_869_496_628_636, + 32_649_856_328_471_220, + 34_701_095_276_033_064, + 36_881_204_047_022_752, + 39_198_278_934_370_992, + 41_660_924_883_519_016, + 44_278_287_448_695_240, + 47_060_086_756_856_400, + 50_016_653_605_425_536, + 53_158_967_827_883_320, + 56_498_699_069_691_424, + 60_048_250_125_977_912, + 63_820_803_001_928_304, + 67_830_367_866_937_216, + 72_091_835_084_322_176, + 76_621_030_509_822_880, + 81_434_774_264_248_528, + 86_550_943_198_537_824, + 91_988_537_283_208_848, + 97_767_750_168_749_840, + 103_910_044_178_992_000, + 110_438_230_015_967_792, + 117_376_551_472_255_616, + 124_750_775_465_407_920, + 132_588_287_728_824_640, + 140_918_194_514_440_064, + 149_771_430_684_917_568, + 159_180_874_596_775_264, + 169_181_470_201_085_280, + 179_810_356_815_193_344, + 191_107_007_047_393_216, + 203_113_373_386_768_288, + 215_874_044_002_592_672, + 229_436_408_331_885_600, + 243_850_833_070_063_392, + 259_170_849_218_267_264, + 275_453_350_882_006_752, + 292_758_806_559_399_232, + 311_151_483_703_668_992, + 330_699_687_393_865_920, + 351_476_014_000_157_824, + 373_557_620_785_735_808, + 397_026_512_446_556_096, + 421_969_845_653_044_224, + 448_480_252_724_740_928, + 476_656_185_639_923_904, + 506_602_281_657_757_760, + 538_429_751_910_786_752, + 572_256_794_410_890_176, + 608_209_033_002_485_632, + 646_419_983_893_124_352, + 687_031_551_494_039_552, + 730_194_555_412_054_016, + 776_069_290_549_944_960, + 824_826_122_395_314_176, + 876_646_119_708_695_936, + 931_721_726_960_522_368, + 990_257_479_014_182_144, + 1_052_470_760_709_299_712, + 1_118_592_614_166_106_112, + 1_188_868_596_808_997_376, + 1_263_559_693_295_730_432, + 1_342_943_284_738_898_688, + 1_427_314_178_819_094_784, + 1_516_985_704_615_302_400, + 1_612_290_876_218_400_768, + 1_713_583_629_449_105_408, + 1_821_240_136_273_157_632, + 1_935_660_201_795_120_128, + 2_057_268_749_018_809_600, + 2_186_517_396_888_336_384, + 2_323_886_137_470_138_880, + 2_469_885_118_504_583_168, + 2_625_056_537_947_004_416, + 2_789_976_657_533_970_944, + 2_965_257_942_852_572_160, + 3_151_551_337_860_326_400, + 3_349_548_682_302_620_672, + 3_559_985_281_005_267_968, + 3_783_642_634_583_792_128, + 4_021_351_341_710_503_936, + 4_273_994_183_717_548_544, + 4_542_509_402_991_247_872, + 4_827_894_187_332_742_144, + 5_131_208_373_224_844_288, + 5_453_578_381_757_959_168, + 5_796_201_401_831_965_696, + 6_160_349_836_169_256_960, + 6_547_376_026_650_146_816, + 6_958_717_276_519_173_120, + 7_395_901_188_113_309_696, + 7_860_551_335_934_872_576, + 8_354_393_296_137_270_272, + 8_879_261_054_815_360_000, + 9_437_103_818_898_946_048, + 10_029_993_254_943_105_024, + 10_660_131_182_698_121_216, + 11_329_857_752_030_707_712, + 12_041_660_133_563_240_448, + 12_798_181_755_305_525_248, + 13_602_232_119_581_272_064, + 14_456_797_236_706_498_560, + 15_365_050_714_167_523_328, + 16_330_365_542_480_556_032, + 17_356_326_621_502_140_416, + 18_446_744_073_709_551_615, +]; diff --git a/frame/bags-list/src/list/mod.rs b/frame/bags-list/src/list/mod.rs index 512fd4446cb29..8187347f66ebb 100644 --- a/frame/bags-list/src/list/mod.rs +++ b/frame/bags-list/src/list/mod.rs @@ -39,7 +39,7 @@ use sp_std::{ collections::{btree_map::BTreeMap, btree_set::BTreeSet}, iter, marker::PhantomData, - vec::Vec, + prelude::*, }; #[derive(Debug, PartialEq, Eq, Encode, Decode, MaxEncodedLen, TypeInfo, PalletError)] @@ -772,11 +772,6 @@ impl, I: 'static> Bag { Ok(()) } - #[cfg(not(feature = "std"))] - fn sanity_check(&self) -> Result<(), &'static str> { - Ok(()) - } - /// Iterate over the nodes in this bag (public for tests). #[cfg(feature = "std")] #[allow(dead_code)] diff --git a/frame/bags-list/src/migrations.rs b/frame/bags-list/src/migrations.rs index 696733e8c7ba5..28ccb3b654fc2 100644 --- a/frame/bags-list/src/migrations.rs +++ b/frame/bags-list/src/migrations.rs @@ -20,8 +20,8 @@ use frame_support::traits::OnRuntimeUpgrade; /// A struct that does not migration, but only checks that the counter prefix exists and is correct. -pub struct CheckCounterPrefix(sp_std::marker::PhantomData); -impl OnRuntimeUpgrade for CheckCounterPrefix { +pub struct CheckCounterPrefix>(sp_std::marker::PhantomData<(I, T)>); +impl> OnRuntimeUpgrade for CheckCounterPrefix { fn on_runtime_upgrade() -> frame_support::weights::Weight { 0 } diff --git a/frame/election-provider-support/src/lib.rs b/frame/election-provider-support/src/lib.rs index a45fc9f8db0c4..73ca4ef81ac9f 100644 --- a/frame/election-provider-support/src/lib.rs +++ b/frame/election-provider-support/src/lib.rs @@ -168,7 +168,6 @@ pub mod onchain; pub mod traits; -use codec::{Decode, Encode}; use sp_runtime::traits::{Bounded, Saturating, Zero}; use sp_std::{fmt::Debug, prelude::*}; @@ -218,7 +217,7 @@ impl __OrInvalidIndex for Option { /// making it fast to repeatedly encode into a `SolutionOf`. This property turns out /// to be important when trimming for solution length. #[derive(RuntimeDebug, Clone, Default)] -#[cfg_attr(feature = "std", derive(PartialEq, Eq, Encode, Decode))] +#[cfg_attr(feature = "std", derive(PartialEq, Eq, codec::Encode, codec::Decode))] pub struct IndexAssignment { /// Index of the voter among the voters list. pub who: VoterIndex, diff --git a/frame/staking/src/pallet/impls.rs b/frame/staking/src/pallet/impls.rs index 8fbf499e3a503..d646be3116d67 100644 --- a/frame/staking/src/pallet/impls.rs +++ b/frame/staking/src/pallet/impls.rs @@ -1397,10 +1397,10 @@ impl ScoreProvider for Pallet { } #[cfg(feature = "runtime-benchmarks")] - fn set_score_of(who: &T::AccountId, weight: Self::Score) { + fn set_score_of(who: &T::AccountId, score: Self::Score) { // this will clearly results in an inconsistent state, but it should not matter for a // benchmark. - let active: BalanceOf = weight.try_into().map_err(|_| ()).unwrap(); + let active: BalanceOf = score.try_into().map_err(|_| ()).unwrap(); let mut ledger = match Self::ledger(who) { None => StakingLedger::default_from(who.clone()), Some(l) => l, From 0c8a8915b7462ca0596b05fabd9278d2bf0c8066 Mon Sep 17 00:00:00 2001 From: Kian Paimani <5588131+kianenigma@users.noreply.github.com> Date: Tue, 19 Apr 2022 17:40:20 +0200 Subject: [PATCH 31/42] Update frame/bags-list/src/list/mod.rs Co-authored-by: Zeke Mostov --- frame/bags-list/src/list/mod.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frame/bags-list/src/list/mod.rs b/frame/bags-list/src/list/mod.rs index f8de2b1a36fdc..c91cfa0c9209a 100644 --- a/frame/bags-list/src/list/mod.rs +++ b/frame/bags-list/src/list/mod.rs @@ -435,7 +435,7 @@ impl, I: 'static> List { Some((old_bag_upper, new_bag_upper)) } else { - // just write the new weight. + // just write the new score. node.put(); None } From dbae8a5ea22ba069a61652f40ea17c5423e3af4c Mon Sep 17 00:00:00 2001 From: kianenigma Date: Sun, 1 May 2022 18:45:36 +0200 Subject: [PATCH 32/42] small fixes + add an important TODO about a potential leftover bug --- frame/bags-list/src/lib.rs | 10 +++++++--- frame/bags-list/src/list/mod.rs | 17 ++++++++++------- frame/bags-list/src/list/tests.rs | 2 +- frame/bags-list/src/tests.rs | 14 +++++++------- frame/staking/src/pallet/impls.rs | 2 +- frame/staking/src/pallet/mod.rs | 1 + frame/staking/src/tests.rs | 4 ++-- 7 files changed, 29 insertions(+), 21 deletions(-) diff --git a/frame/bags-list/src/lib.rs b/frame/bags-list/src/lib.rs index 14f94c9974869..edc7811454cb0 100644 --- a/frame/bags-list/src/lib.rs +++ b/frame/bags-list/src/lib.rs @@ -266,7 +266,7 @@ impl, I: 'static> Pallet { new_score: T::Score, ) -> Result, ListError> { // If no voter at that node, don't do anything. the caller just wasted the fee to call this. - let node = list::Node::::get(&account).ok_or(ListError::NonExistent)?; + let node = list::Node::::get(&account).ok_or(ListError::NodeNotFound)?; let maybe_movement = List::update_position_for(node, new_score); if let Some((from, to)) = maybe_movement { Self::deposit_event(Event::::Rebagged { who: account.clone(), from, to }); @@ -378,7 +378,11 @@ impl, I: 'static> ScoreProvider for Pallet { } #[cfg(any(feature = "runtime-benchmarks", test))] - fn set_score_of(_: &T::AccountId, _: T::Score) { - todo!(); + fn set_score_of(id: &T::AccountId, new_score: T::Score) { + ListNodes::::mutate(id, |maybe_node| { + if let Some(node) = maybe_node.as_mut() { + node.set_score(new_score) + } + }) } } diff --git a/frame/bags-list/src/list/mod.rs b/frame/bags-list/src/list/mod.rs index f8de2b1a36fdc..fc3711d8d86f9 100644 --- a/frame/bags-list/src/list/mod.rs +++ b/frame/bags-list/src/list/mod.rs @@ -46,8 +46,6 @@ use sp_std::{ pub enum ListError { /// A duplicate id has been detected. Duplicate, - /// the given id does not exists. - NonExistent, /// An Id does not have a greater score than another Id. NotHeavier, /// Attempted to place node in front of a node in another bag. @@ -232,7 +230,7 @@ impl, I: 'static> List { /// Get the score of the given node, pub fn get_score(id: &T::AccountId) -> Result { - Node::::get(id).map(|node| node.score()).ok_or(ListError::NonExistent) + Node::::get(id).map(|node| node.score()).ok_or(ListError::NodeNotFound) } /// Iterate over all nodes in all bags in the list. @@ -339,7 +337,7 @@ impl, I: 'static> List { /// Remove an id from the list. pub(crate) fn remove(id: &T::AccountId) -> Result<(), ListError> { if !Self::contains(id) { - return Err(ListError::NonExistent) + return Err(ListError::NodeNotFound) } let _ = Self::remove_many(sp_std::iter::once(id)); Ok(()) @@ -447,8 +445,8 @@ impl, I: 'static> List { lighter_id: &T::AccountId, heavier_id: &T::AccountId, ) -> Result<(), ListError> { - let lighter_node = Node::::get(&lighter_id).ok_or(ListError::NonExistent)?; - let heavier_node = Node::::get(&heavier_id).ok_or(ListError::NonExistent)?; + let lighter_node = Node::::get(&lighter_id).ok_or(ListError::NodeNotFound)?; + let heavier_node = Node::::get(&heavier_id).ok_or(ListError::NodeNotFound)?; ensure!(lighter_node.bag_upper == heavier_node.bag_upper, ListError::NotInSameBag); @@ -468,7 +466,7 @@ impl, I: 'static> List { let lighter_node = Node::::get(&lighter_id).ok_or_else(|| { debug_assert!(false, "id that should exist cannot be found"); crate::log!(warn, "id that should exist cannot be found"); - ListError::NonExistent + ListError::NodeNotFound })?; // insert `heavier_node` directly in front of `lighter_node`. This will update both nodes @@ -902,6 +900,11 @@ impl, I: 'static> Node { &self.id } + #[cfg(feature = "runtime-benchmarks")] + pub fn set_score(&mut self, s: T::Score) { + self.score = s + } + /// The bag this nodes belongs to (public for benchmarks). #[cfg(feature = "runtime-benchmarks")] #[allow(dead_code)] diff --git a/frame/bags-list/src/list/tests.rs b/frame/bags-list/src/list/tests.rs index c1d19e1cd5c91..c7739278b6f82 100644 --- a/frame/bags-list/src/list/tests.rs +++ b/frame/bags-list/src/list/tests.rs @@ -267,7 +267,7 @@ mod list { ExtBuilder::default().build_and_execute(|| { // removing a non-existent id is a noop assert!(!ListNodes::::contains_key(42)); - assert_noop!(List::::remove(&42), Error::NonExistent); + assert_noop!(List::::remove(&42), ListError::NodeNotFound); // when removing a node from a bag with multiple nodes: List::::remove(&2).unwrap(); diff --git a/frame/bags-list/src/tests.rs b/frame/bags-list/src/tests.rs index 7a5e5c78e4a47..f0190d6410029 100644 --- a/frame/bags-list/src/tests.rs +++ b/frame/bags-list/src/tests.rs @@ -380,7 +380,7 @@ mod pallet { // then assert_noop!( BagsList::put_in_front_of(Origin::signed(3), 2), - crate::pallet::Error::::NotHeavier + crate::pallet::Error::::List(ListError::NotHeavier) ); }); } @@ -394,7 +394,7 @@ mod pallet { // then assert_noop!( BagsList::put_in_front_of(Origin::signed(3), 4), - crate::pallet::Error::::NotHeavier + crate::pallet::Error::::List(ListError::NotHeavier) ); }); } @@ -411,7 +411,7 @@ mod pallet { // then assert_noop!( BagsList::put_in_front_of(Origin::signed(5), 4), - crate::pallet::Error::::IdNotFound + crate::pallet::Error::::List(ListError::NodeNotFound) ); }); @@ -425,7 +425,7 @@ mod pallet { // then assert_noop!( BagsList::put_in_front_of(Origin::signed(4), 5), - crate::pallet::Error::::IdNotFound + crate::pallet::Error::::List(ListError::NodeNotFound) ); }); } @@ -439,7 +439,7 @@ mod pallet { // then assert_noop!( BagsList::put_in_front_of(Origin::signed(4), 1), - crate::pallet::Error::::NotInSameBag + crate::pallet::Error::::List(ListError::NotInSameBag) ); }); } @@ -498,7 +498,7 @@ mod sorted_list_provider { // when updating assert_storage_noop!(assert_eq!( BagsList::on_update(&201, VoteWeight::MAX).unwrap_err(), - Error::NonExistent + ListError::NodeNotFound )); // then the count stays the same assert_eq!(BagsList::count(), 4); @@ -618,7 +618,7 @@ mod sorted_list_provider { ExtBuilder::default().build_and_execute(|| { // it is a noop removing a non-existent id assert!(!ListNodes::::contains_key(42)); - assert_noop!(BagsList::on_remove(&42), Error::NonExistent); + assert_noop!(BagsList::on_remove(&42), ListError::NodeNotFound); // when removing a node from a bag with multiple nodes BagsList::on_remove(&2).unwrap(); diff --git a/frame/staking/src/pallet/impls.rs b/frame/staking/src/pallet/impls.rs index 0c973a7e0f88c..116179e8da64a 100644 --- a/frame/staking/src/pallet/impls.rs +++ b/frame/staking/src/pallet/impls.rs @@ -812,6 +812,7 @@ impl Pallet { /// /// This function is self-weighing as [`DispatchClass::Mandatory`]. pub fn get_npos_targets(maybe_max_len: Option) -> Vec { + // TODO: what comes out here might not be a validator. let targets = T::TargetList::iter() .take(maybe_max_len.unwrap_or_else(Bounded::max_value)) .collect::>(); @@ -836,7 +837,6 @@ impl Pallet { nominations: Nominations, ) { if !NominatorsHelper::::contains_any(stash) { - // maybe update sorted list. let _ = T::VoterList::on_insert(stash.clone(), Self::weight_of(stash)).defensive(); } diff --git a/frame/staking/src/pallet/mod.rs b/frame/staking/src/pallet/mod.rs index 778403c98f328..9ca7696b8bda4 100644 --- a/frame/staking/src/pallet/mod.rs +++ b/frame/staking/src/pallet/mod.rs @@ -1115,6 +1115,7 @@ pub mod pallet { .try_into() .defensive_map_err(|_| Error::::TooManyTargets)?; + // TODO: but the existing nominators onchain might have duplicates.. ensure!( targets.len() == targets diff --git a/frame/staking/src/tests.rs b/frame/staking/src/tests.rs index 8bf3b78275db2..9a8be58ba1c88 100644 --- a/frame/staking/src/tests.rs +++ b/frame/staking/src/tests.rs @@ -4607,7 +4607,7 @@ mod target_list { ); assert_noop!( - Staking::nominate(Origin::signed(100), vec![11, 21, 11,]), + Staking::nominate(Origin::signed(100), vec![11, 21, 11]), Error::::DuplicateTarget ); @@ -4916,7 +4916,7 @@ mod target_list { assert_eq!(::TargetList::get_score(&3).unwrap(), 5); assert_eq!( ::TargetList::get_score(&4).unwrap_err(), - pallet_bags_list::ListError::NonExistent + pallet_bags_list::ListError::NodeNotFound ); }); } From cc7ee9169c7240b6cb025bbec1cddef483a7e93b Mon Sep 17 00:00:00 2001 From: kianenigma Date: Sun, 1 May 2022 19:47:22 +0200 Subject: [PATCH 33/42] Fix a few things in the bags-list pallet --- frame/bags-list/src/lib.rs | 5 +- frame/bags-list/src/list/mod.rs | 10 +-- frame/bags-list/src/list/tests.rs | 127 +++++++++++++++--------------- frame/bags-list/src/tests.rs | 8 +- 4 files changed, 74 insertions(+), 76 deletions(-) diff --git a/frame/bags-list/src/lib.rs b/frame/bags-list/src/lib.rs index 29066ea96ca50..7fd0dea896082 100644 --- a/frame/bags-list/src/lib.rs +++ b/frame/bags-list/src/lib.rs @@ -192,6 +192,8 @@ pub mod pallet { pub enum Event, I: 'static = ()> { /// Moved an account from one bag to another. Rebagged { who: T::AccountId, from: T::Score, to: T::Score }, + /// Updated the score of some account to the given amount. + ScoreUpdated { who: T::AccountId, new_score: T::Score }, } #[pallet::error] @@ -223,8 +225,6 @@ pub mod pallet { pub fn rebag(origin: OriginFor, dislocated: T::AccountId) -> DispatchResult { ensure_signed(origin)?; let current_score = T::ScoreProvider::score(&dislocated); - // TODO: we might want to reflect the error here. This transaction might NOT rebag, but - // DO update the score. let _ = Pallet::::do_rebag(&dislocated, current_score) .map_err::, _>(Into::into)?; Ok(()) @@ -273,6 +273,7 @@ impl, I: 'static> Pallet { if let Some((from, to)) = maybe_movement { Self::deposit_event(Event::::Rebagged { who: account.clone(), from, to }); }; + Self::deposit_event(Event::::ScoreUpdated { who: account.clone(), new_score }); Ok(maybe_movement) } diff --git a/frame/bags-list/src/list/mod.rs b/frame/bags-list/src/list/mod.rs index 77854c84fcf7a..825758698b419 100644 --- a/frame/bags-list/src/list/mod.rs +++ b/frame/bags-list/src/list/mod.rs @@ -334,7 +334,7 @@ impl, I: 'static> List { Ok(()) } - /// Remove an id from the list. + /// Remove an id from the list, returning an error if `id` does not exists. pub(crate) fn remove(id: &T::AccountId) -> Result<(), ListError> { if !Self::contains(id) { return Err(ListError::NodeNotFound) @@ -397,14 +397,6 @@ impl, I: 'static> List { mut node: Node, new_score: T::Score, ) -> Option<(T::Score, T::Score)> { - crate::log!( - debug, - "trying to update {:?} from {:?} to {:?} (rebag?: {})", - node.id, - node.score(), - new_score, - node.is_misplaced(new_score) - ); node.score = new_score; if node.is_misplaced(new_score) { let old_bag_upper = node.bag_upper; diff --git a/frame/bags-list/src/list/tests.rs b/frame/bags-list/src/list/tests.rs index c7739278b6f82..5f5092938311b 100644 --- a/frame/bags-list/src/list/tests.rs +++ b/frame/bags-list/src/list/tests.rs @@ -39,7 +39,7 @@ fn basic_setup_works() { assert_eq!(ListNodes::::iter().count(), 4); assert_eq!(ListBags::::iter().count(), 2); - assert_eq!(List::::get_bags(), vec![(10, vec![1]), (1000, vec![2, 3, 4])]); + assert_eq!(List::::get_bags(), vec![(10, vec![1]), (1_000, vec![2, 3, 4])]); // the state of the bags is as expected assert_eq!( @@ -47,13 +47,13 @@ fn basic_setup_works() { Bag:: { head: Some(1), tail: Some(1), bag_upper: 0, _phantom: PhantomData } ); assert_eq!( - ListBags::::get(1000).unwrap(), + ListBags::::get(1_000).unwrap(), Bag:: { head: Some(2), tail: Some(4), bag_upper: 0, _phantom: PhantomData } ); - assert_eq!(ListNodes::::get(2).unwrap(), node(2, None, Some(3), 1000)); - assert_eq!(ListNodes::::get(3).unwrap(), node(3, Some(2), Some(4), 1000)); - assert_eq!(ListNodes::::get(4).unwrap(), node(4, Some(3), None, 1000)); + assert_eq!(ListNodes::::get(2).unwrap(), node(2, None, Some(3), 1_000)); + assert_eq!(ListNodes::::get(3).unwrap(), node(3, Some(2), Some(4), 1_000)); + assert_eq!(ListNodes::::get(4).unwrap(), node(4, Some(3), None, 1_000)); assert_eq!(ListNodes::::get(1).unwrap(), node(1, None, None, 10)); // non-existent id does not have a storage footprint @@ -95,20 +95,20 @@ fn notional_bag_for_works() { fn remove_last_node_in_bags_cleans_bag() { ExtBuilder::default().build_and_execute(|| { // given - assert_eq!(List::::get_bags(), vec![(10, vec![1]), (1000, vec![2, 3, 4])]); + assert_eq!(List::::get_bags(), vec![(10, vec![1]), (1_000, vec![2, 3, 4])]); // bump 1 to a bigger bag List::::remove(&1).unwrap(); assert_ok!(List::::insert(1, 10_000)); // then the bag with bound 10 is wiped from storage. - assert_eq!(List::::get_bags(), vec![(1000, vec![2, 3, 4]), (10_000, vec![1])]); + assert_eq!(List::::get_bags(), vec![(1_000, vec![2, 3, 4]), (10_000, vec![1])]); // and can be recreated again as needed. assert_ok!(List::::insert(77, 10)); assert_eq!( List::::get_bags(), - vec![(10, vec![77]), (1000, vec![2, 3, 4]), (10_000, vec![1])] + vec![(10, vec![77]), (1_000, vec![2, 3, 4]), (10_000, vec![1])] ); }); } @@ -124,16 +124,16 @@ fn migrate_works() { vec![ (10, vec![1]), (20, vec![710, 711]), - (1000, vec![2, 3, 4]), + (1_000, vec![2, 3, 4]), (2_000, vec![712]) ] ); let old_thresholds = ::BagThresholds::get(); - assert_eq!(old_thresholds, vec![10, 20, 30, 40, 50, 60, 1000, 2_000, 10_000]); + assert_eq!(old_thresholds, vec![10, 20, 30, 40, 50, 60, 1_000, 2_000, 10_000]); // when the new thresholds adds `15` and removes `2_000` const NEW_THRESHOLDS: &'static [VoteWeight] = - &[10, 15, 20, 30, 40, 50, 60, 1000, 10_000]; + &[10, 15, 20, 30, 40, 50, 60, 1_000, 10_000]; BagThresholds::set(NEW_THRESHOLDS); // and we call List::::migrate(old_thresholds); @@ -145,7 +145,7 @@ fn migrate_works() { (10, vec![1]), (15, vec![710]), // nodes in range 11 ..= 15 move from bag 20 to bag 15 (20, vec![711]), - (1000, vec![2, 3, 4]), + (1_000, vec![2, 3, 4]), // nodes in range 1_001 ..= 2_000 move from bag 2_000 to bag 10_000 (10_000, vec![712]), ] @@ -166,7 +166,7 @@ mod list { // given assert_eq!( List::::get_bags(), - vec![(10, vec![1]), (1000, vec![2, 3, 4]), (2_000, vec![5, 6])] + vec![(10, vec![1]), (1_000, vec![2, 3, 4]), (2_000, vec![5, 6])] ); assert_eq!( get_list_as_ids(), @@ -201,7 +201,7 @@ mod list { // given assert_eq!( List::::get_bags(), - vec![(10, vec![1]), (1000, vec![2, 3, 4]), (2_000, vec![5, 6])] + vec![(10, vec![1]), (1_000, vec![2, 3, 4]), (2_000, vec![5, 6])] ); // when @@ -223,10 +223,10 @@ mod list { fn insert_works() { ExtBuilder::default().build_and_execute(|| { // when inserting into an existing bag - assert_ok!(List::::insert(5, 1000)); + assert_ok!(List::::insert(5, 1_000)); // then - assert_eq!(List::::get_bags(), vec![(10, vec![1]), (1000, vec![2, 3, 4, 5])]); + assert_eq!(List::::get_bags(), vec![(10, vec![1]), (1_000, vec![2, 3, 4, 5])]); assert_eq!(get_list_as_ids(), vec![2, 3, 4, 5, 1]); // when inserting into a non-existent bag @@ -235,7 +235,7 @@ mod list { // then assert_eq!( List::::get_bags(), - vec![(10, vec![1]), (1000, vec![2, 3, 4, 5]), (2_000, vec![6])] + vec![(10, vec![1]), (1_000, vec![2, 3, 4, 5]), (2_000, vec![6])] ); assert_eq!(get_list_as_ids(), vec![6, 2, 3, 4, 5, 1]); }); @@ -274,7 +274,7 @@ mod list { // then assert_eq!(get_list_as_ids(), vec![3, 4, 1]); - assert_eq!(List::::get_bags(), vec![(10, vec![1]), (1000, vec![3, 4])]); + assert_eq!(List::::get_bags(), vec![(10, vec![1]), (1_000, vec![3, 4])]); ensure_left(2, 3); // when removing a node from a bag with only one node: @@ -282,7 +282,7 @@ mod list { // then assert_eq!(get_list_as_ids(), vec![3, 4]); - assert_eq!(List::::get_bags(), vec![(1000, vec![3, 4])]); + assert_eq!(List::::get_bags(), vec![(1_000, vec![3, 4])]); ensure_left(1, 2); // bag 10 is removed assert!(!ListBags::::contains_key(10)); @@ -329,7 +329,7 @@ mod list { assert_eq!(List::::update_position_for(node.clone(), 20), Some((10, 20))); assert_eq!(Node::::get(&1).unwrap().score, 20); - assert_eq!(List::::get_bags(), vec![(20, vec![1]), (1000, vec![2, 3, 4])]); + assert_eq!(List::::get_bags(), vec![(20, vec![1]), (1_000, vec![2, 3, 4])]); // get the new updated node; try and update the position with no change in score. let node = Node::::get(&1).unwrap(); @@ -338,17 +338,17 @@ mod list { None )); - // then move it to bag 1000 by giving it score 500. - assert_eq!(List::::update_position_for(node.clone(), 500), Some((20, 1000))); + // then move it to bag 1_000 by giving it score 500. + assert_eq!(List::::update_position_for(node.clone(), 500), Some((20, 1_000))); assert_eq!(Node::::get(&1).unwrap().score, 500); - assert_eq!(List::::get_bags(), vec![(1000, vec![2, 3, 4, 1])]); + assert_eq!(List::::get_bags(), vec![(1_000, vec![2, 3, 4, 1])]); // moving within that bag again is a noop let node = Node::::get(&1).unwrap(); assert_eq!(List::::update_position_for(node.clone(), 750), None); assert_eq!(Node::::get(&1).unwrap().score, 750); - assert_eq!(List::::update_position_for(node.clone(), 1000), None,); - assert_eq!(Node::::get(&1).unwrap().score, 1000); + assert_eq!(List::::update_position_for(node.clone(), 1_000), None,); + assert_eq!(Node::::get(&1).unwrap().score, 1_000); }); } @@ -432,15 +432,15 @@ mod list { // both nodes are already in the same bag with the correct bag upper. ExtBuilder::default().build_and_execute_no_post_check(|| { // given - assert_eq!(List::::get_bags(), vec![(10, vec![1]), (1000, vec![2, 3, 4])]); + assert_eq!(List::::get_bags(), vec![(10, vec![1]), (1_000, vec![2, 3, 4])]); // implicitly also test that `node`'s `prev`/`next` are correctly re-assigned. let node_42 = Node:: { id: 42, prev: Some(1), next: Some(2), - bag_upper: 1000, - score: 1000, + bag_upper: 1_000, + score: 1_000, _phantom: PhantomData, }; assert!(!crate::ListNodes::::contains_key(42)); @@ -451,7 +451,7 @@ mod list { List::::insert_at_unchecked(node_1, node_42); // then - assert_eq!(List::::get_bags(), vec![(10, vec![42, 1]), (1000, vec![2, 3, 4])]); + assert_eq!(List::::get_bags(), vec![(10, vec![42, 1]), (1_000, vec![2, 3, 4])]); }) } @@ -459,15 +459,15 @@ mod list { fn insert_at_unchecked_at_is_head() { ExtBuilder::default().build_and_execute(|| { // given - assert_eq!(List::::get_bags(), vec![(10, vec![1]), (1000, vec![2, 3, 4])]); + assert_eq!(List::::get_bags(), vec![(10, vec![1]), (1_000, vec![2, 3, 4])]); // implicitly also test that `node`'s `prev`/`next` are correctly re-assigned. let node_42 = Node:: { id: 42, prev: Some(4), next: None, - bag_upper: 1000, - score: 1000, + bag_upper: 1_000, + score: 1_000, _phantom: PhantomData, }; assert!(!crate::ListNodes::::contains_key(42)); @@ -478,7 +478,7 @@ mod list { List::::insert_at_unchecked(node_2, node_42); // then - assert_eq!(List::::get_bags(), vec![(10, vec![1]), (1000, vec![42, 2, 3, 4])]); + assert_eq!(List::::get_bags(), vec![(10, vec![1]), (1_000, vec![42, 2, 3, 4])]); }) } @@ -486,15 +486,15 @@ mod list { fn insert_at_unchecked_at_is_non_terminal() { ExtBuilder::default().build_and_execute(|| { // given - assert_eq!(List::::get_bags(), vec![(10, vec![1]), (1000, vec![2, 3, 4])]); + assert_eq!(List::::get_bags(), vec![(10, vec![1]), (1_000, vec![2, 3, 4])]); // implicitly also test that `node`'s `prev`/`next` are correctly re-assigned. let node_42 = Node:: { id: 42, prev: None, next: Some(2), - bag_upper: 1000, - score: 1000, + bag_upper: 1_000, + score: 1_000, _phantom: PhantomData, }; assert!(!crate::ListNodes::::contains_key(42)); @@ -505,7 +505,7 @@ mod list { List::::insert_at_unchecked(node_3, node_42); // then - assert_eq!(List::::get_bags(), vec![(10, vec![1]), (1000, vec![2, 42, 3, 4])]); + assert_eq!(List::::get_bags(), vec![(10, vec![1]), (1_000, vec![2, 42, 3, 4])]); }) } @@ -513,15 +513,15 @@ mod list { fn insert_at_unchecked_at_is_tail() { ExtBuilder::default().build_and_execute(|| { // given - assert_eq!(List::::get_bags(), vec![(10, vec![1]), (1000, vec![2, 3, 4])]); + assert_eq!(List::::get_bags(), vec![(10, vec![1]), (1_000, vec![2, 3, 4])]); // implicitly also test that `node`'s `prev`/`next` are correctly re-assigned. let node_42 = Node:: { id: 42, prev: Some(42), next: Some(42), - bag_upper: 1000, - score: 1000, + bag_upper: 1_000, + score: 1_000, _phantom: PhantomData, }; assert!(!crate::ListNodes::::contains_key(42)); @@ -532,7 +532,7 @@ mod list { List::::insert_at_unchecked(node_4, node_42); // then - assert_eq!(List::::get_bags(), vec![(10, vec![1]), (1000, vec![2, 3, 42, 4])]); + assert_eq!(List::::get_bags(), vec![(10, vec![1]), (1_000, vec![2, 3, 42, 4])]); }) } } @@ -551,17 +551,17 @@ mod bags { assert_eq!(bag_ids, ids); }; - assert_eq!(List::::get_bags(), vec![(10, vec![1]), (1000, vec![2, 3, 4])]); + assert_eq!(List::::get_bags(), vec![(10, vec![1]), (1_000, vec![2, 3, 4])]); // we can fetch them check_bag(10, Some(1), Some(1), vec![1]); - check_bag(1000, Some(2), Some(4), vec![2, 3, 4]); + check_bag(1_000, Some(2), Some(4), vec![2, 3, 4]); // and all other bag thresholds don't get bags. ::BagThresholds::get() .iter() .chain(iter::once(&VoteWeight::MAX)) - .filter(|bag_upper| !vec![10, 1000].contains(bag_upper)) + .filter(|bag_upper| !vec![10, 1_000].contains(bag_upper)) .for_each(|bag_upper| { assert_storage_noop!(assert_eq!(Bag::::get(*bag_upper), None)); assert!(!ListBags::::contains_key(*bag_upper)); @@ -587,12 +587,11 @@ mod bags { _phantom: PhantomData, }; - assert_eq!(List::::get_bags(), vec![(10, vec![1]), (1000, vec![2, 3, 4])]); + assert_eq!(List::::get_bags(), vec![(10, vec![1]), (1_000, vec![2, 3, 4])]); let mut bag_10 = Bag::::get(10).unwrap(); bag_10.insert_node_unchecked(node(42, 5)); - // TODO: test for weight updated after rebag. assert_eq!( ListNodes::::get(&42).unwrap(), Node { @@ -626,7 +625,7 @@ mod bags { assert_eq!(bag_as_ids(&bag_10), vec![1, 42]); // when inserting into a bag with 3 nodes - let mut bag_1000 = Bag::::get(1000).unwrap(); + let mut bag_1000 = Bag::::get(1_000).unwrap(); bag_1000.insert_node_unchecked(node(52, bag_1000.bag_upper)); // then assert_eq!(bag_as_ids(&bag_1000), vec![2, 3, 4, 52]); @@ -666,7 +665,7 @@ mod bags { bag_20.put(); // need to put this newly created bag so its in the storage map assert_eq!( List::::get_bags(), - vec![(10, vec![1, 42]), (20, vec![62, 61]), (1000, vec![2, 3, 4, 52])] + vec![(10, vec![1, 42]), (20, vec![62, 61]), (1_000, vec![2, 3, 4, 52])] ); }); } @@ -677,7 +676,7 @@ mod bags { ExtBuilder::default().build_and_execute_no_post_check(|| { // when inserting a node with both prev & next pointing at an account in an incorrect // bag. - let mut bag_1000 = Bag::::get(1000).unwrap(); + let mut bag_1000 = Bag::::get(1_000).unwrap(); bag_1000.insert_node_unchecked(node(42, Some(1), Some(1), 500)); // then the proper prev and next is set. @@ -699,7 +698,7 @@ mod bags { ExtBuilder::default().build_and_execute_no_post_check(|| { // given 3 is in bag_1000 (and not a tail node) - let mut bag_1000 = Bag::::get(1000).unwrap(); + let mut bag_1000 = Bag::::get(1_000).unwrap(); assert_eq!(bag_as_ids(&bag_1000), vec![2, 3, 4]); // when inserting a node with duplicate id 3 @@ -719,7 +718,7 @@ mod bags { ExtBuilder::default().build_and_execute_no_post_check(|| { // when inserting a duplicate id of the head - let mut bag_1000 = Bag::::get(1000).unwrap(); + let mut bag_1000 = Bag::::get(1_000).unwrap(); assert_eq!(bag_as_ids(&bag_1000), vec![2, 3, 4]); bag_1000.insert_node_unchecked(node(2, None, None, 0)); @@ -742,7 +741,7 @@ mod bags { assert_eq!( bag_1000, - Bag { head: Some(2), tail: Some(2), bag_upper: 1000, _phantom: PhantomData } + Bag { head: Some(2), tail: Some(2), bag_upper: 1_000, _phantom: PhantomData } ) }); } @@ -756,8 +755,8 @@ mod bags { fn insert_node_duplicate_tail_panics_with_debug_assert() { ExtBuilder::default().build_and_execute(|| { // given - assert_eq!(List::::get_bags(), vec![(10, vec![1]), (1000, vec![2, 3, 4])],); - let mut bag_1000 = Bag::::get(1000).unwrap(); + assert_eq!(List::::get_bags(), vec![(10, vec![1]), (1_000, vec![2, 3, 4])],); + let mut bag_1000 = Bag::::get(1_000).unwrap(); // when inserting a duplicate id that is already the tail assert_eq!(bag_1000.tail, Some(4)); @@ -773,8 +772,8 @@ mod bags { .add_ids(vec![ (11, 10), (12, 10), - (13, 1000), - (14, 1000), + (13, 1_000), + (14, 1_000), (15, 2_000), (16, 2_000), (17, 2_000), @@ -783,7 +782,7 @@ mod bags { ]) .build_and_execute_no_post_check(|| { let mut bag_10 = Bag::::get(10).unwrap(); - let mut bag_1000 = Bag::::get(1000).unwrap(); + let mut bag_1000 = Bag::::get(1_000).unwrap(); let mut bag_2000 = Bag::::get(2_000).unwrap(); // given @@ -832,7 +831,7 @@ mod bags { bag_1000.put(); // put into storage so `get` returns the updated bag // then - assert_eq!(Bag::::get(1000), None); + assert_eq!(Bag::::get(1_000), None); // when removing a node that is pointing at both the head & tail let node_11 = Node::::get(&11).unwrap(); @@ -884,20 +883,20 @@ mod bags { id: 2, prev: None, next: Some(3), - bag_upper: 10, // should be 1000 + bag_upper: 10, // should be 1_000 score: 10, _phantom: PhantomData, }; - let mut bag_1000 = Bag::::get(1000).unwrap(); + let mut bag_1000 = Bag::::get(1_000).unwrap(); // when removing a node that is in the bag but has the wrong upper bag_1000.remove_node_unchecked(&bad_upper_node_2); bag_1000.put(); // then the node is no longer in any bags - assert_eq!(List::::get_bags(), vec![(10, vec![1]), (1000, vec![3, 4])]); + assert_eq!(List::::get_bags(), vec![(10, vec![1]), (1_000, vec![3, 4])]); // .. and the bag it was removed from - let bag_1000 = Bag::::get(1000).unwrap(); + let bag_1000 = Bag::::get(1_000).unwrap(); // is sane assert_ok!(bag_1000.sanity_check()); // and has the correct head and tail. @@ -907,7 +906,7 @@ mod bags { // Removing a node that is in another bag, will mess up that other bag. ExtBuilder::default().build_and_execute_no_post_check(|| { - // given a tail node is in bag 1000 + // given a tail node is in bag 1_000 let node_4 = Node::::get(&4).unwrap(); // when we remove it from bag 10 @@ -921,7 +920,7 @@ mod bags { assert_eq!(bag_10.head, Some(1)); // but the bag that the node belonged to is in an invalid state - let bag_1000 = Bag::::get(1000).unwrap(); + let bag_1000 = Bag::::get(1_000).unwrap(); // because it still has the removed node as its tail. assert_eq!(bag_1000.tail, Some(4)); assert_eq!(bag_1000.head, Some(2)); diff --git a/frame/bags-list/src/tests.rs b/frame/bags-list/src/tests.rs index 349a82caaca86..cde93ca794d9a 100644 --- a/frame/bags-list/src/tests.rs +++ b/frame/bags-list/src/tests.rs @@ -34,9 +34,11 @@ mod pallet { vec![(10, vec![1]), (20, vec![42]), (1_000, vec![2, 3, 4])] ); - // when increasing vote score to the level of non-existent bag + // when increasing score to the level of non-existent bag + assert_eq!(List::::get_score(&42).unwrap(), 20); StakingMock::set_score_of(&42, 2_000); assert_ok!(BagsList::rebag(Origin::signed(0), 42)); + assert_eq!(List::::get_score(&42).unwrap(), 2_000); // then a new bag is created and the id moves into it assert_eq!( @@ -53,6 +55,8 @@ mod pallet { List::::get_bags(), vec![(10, vec![1]), (1_000, vec![2, 3, 4]), (2_000, vec![42])] ); + // but the score is updated + assert_eq!(List::::get_score(&42).unwrap(), 1_001); // when reducing score to the level of a non-existent bag StakingMock::set_score_of(&42, 30); @@ -63,6 +67,7 @@ mod pallet { List::::get_bags(), vec![(10, vec![1]), (30, vec![42]), (1_000, vec![2, 3, 4])] ); + assert_eq!(List::::get_score(&42).unwrap(), 30); // when increasing score to the level of a pre-existing bag StakingMock::set_score_of(&42, 500); @@ -73,6 +78,7 @@ mod pallet { List::::get_bags(), vec![(10, vec![1]), (1_000, vec![2, 3, 4, 42])] ); + assert_eq!(List::::get_score(&42).unwrap(), 500); }); } From a10ff102d90e9cc907e5f953c40fa76c0017d832 Mon Sep 17 00:00:00 2001 From: kianenigma Date: Tue, 3 May 2022 10:06:21 +0100 Subject: [PATCH 34/42] cleanup a few things --- frame/bags-list/src/list/tests.rs | 20 +++- frame/election-provider-support/src/lib.rs | 2 +- frame/staking/src/mock.rs | 111 +++++++++++---------- frame/staking/src/pallet/impls.rs | 97 ++++++++++-------- frame/staking/src/pallet/mod.rs | 35 +++++-- frame/staking/src/tests.rs | 44 ++++++-- 6 files changed, 190 insertions(+), 119 deletions(-) diff --git a/frame/bags-list/src/list/tests.rs b/frame/bags-list/src/list/tests.rs index 5f5092938311b..3c2ba7d5da485 100644 --- a/frame/bags-list/src/list/tests.rs +++ b/frame/bags-list/src/list/tests.rs @@ -451,7 +451,10 @@ mod list { List::::insert_at_unchecked(node_1, node_42); // then - assert_eq!(List::::get_bags(), vec![(10, vec![42, 1]), (1_000, vec![2, 3, 4])]); + assert_eq!( + List::::get_bags(), + vec![(10, vec![42, 1]), (1_000, vec![2, 3, 4])] + ); }) } @@ -478,7 +481,10 @@ mod list { List::::insert_at_unchecked(node_2, node_42); // then - assert_eq!(List::::get_bags(), vec![(10, vec![1]), (1_000, vec![42, 2, 3, 4])]); + assert_eq!( + List::::get_bags(), + vec![(10, vec![1]), (1_000, vec![42, 2, 3, 4])] + ); }) } @@ -505,7 +511,10 @@ mod list { List::::insert_at_unchecked(node_3, node_42); // then - assert_eq!(List::::get_bags(), vec![(10, vec![1]), (1_000, vec![2, 42, 3, 4])]); + assert_eq!( + List::::get_bags(), + vec![(10, vec![1]), (1_000, vec![2, 42, 3, 4])] + ); }) } @@ -532,7 +541,10 @@ mod list { List::::insert_at_unchecked(node_4, node_42); // then - assert_eq!(List::::get_bags(), vec![(10, vec![1]), (1_000, vec![2, 3, 42, 4])]); + assert_eq!( + List::::get_bags(), + vec![(10, vec![1]), (1_000, vec![2, 3, 42, 4])] + ); }) } } diff --git a/frame/election-provider-support/src/lib.rs b/frame/election-provider-support/src/lib.rs index 82dcbbd56deae..63b466a3fb97e 100644 --- a/frame/election-provider-support/src/lib.rs +++ b/frame/election-provider-support/src/lib.rs @@ -173,7 +173,7 @@ use sp_std::{fmt::Debug, prelude::*}; /// Re-export the solution generation macro. pub use frame_election_provider_solution_type::generate_solution_type; -use frame_support::{traits::Get, BoundedVec, RuntimeDebug, weights::Weight}; +use frame_support::{traits::Get, weights::Weight, BoundedVec, RuntimeDebug}; /// Re-export some type as they are used in the interface. pub use sp_arithmetic::PerThing; pub use sp_npos_elections::{ diff --git a/frame/staking/src/mock.rs b/frame/staking/src/mock.rs index 50ceacc06978e..b441e7aa1477a 100644 --- a/frame/staking/src/mock.rs +++ b/frame/staking/src/mock.rs @@ -37,7 +37,7 @@ use sp_runtime::{ traits::{IdentityLookup, Zero}, }; use sp_staking::offence::{DisableStrategy, OffenceDetails, OnOffenceHandler}; -use std::{cell::RefCell, cmp::Ordering}; +use std::{cell::RefCell}; pub const INIT_TIMESTAMP: u64 = 30_000; pub const BLOCK_TIME: u64 = 1000; @@ -288,59 +288,60 @@ impl sp_staking::OnStakerSlash for OnStakerSlashM } } -pub struct TargetBagListCompat; -impl SortedListProvider for TargetBagListCompat { - type Error = >::Error; - type Score = >::Score; - - fn iter() -> Box> { - let mut all = TargetBagsList::iter() - .map(|x| (x, TargetBagsList::get_score(&x).unwrap_or_default())) - .collect::>(); - all.sort_by(|a, b| match a.1.partial_cmp(&b.1).unwrap() { - Ordering::Equal => b.0.partial_cmp(&a.0).unwrap(), - x @ _ => x, - }); - Box::new(all.into_iter().map(|(x, _)| x)) - } - fn iter_from(start: &AccountId) -> Result>, Self::Error> { - TargetBagsList::iter_from(start) - } - fn count() -> u32 { - TargetBagsList::count() - } - fn contains(id: &AccountId) -> bool { - TargetBagsList::contains(id) - } - fn on_insert(id: AccountId, weight: Self::Score) -> Result<(), Self::Error> { - TargetBagsList::on_insert(id, weight) - } - fn on_update(id: &AccountId, weight: Self::Score) -> Result<(), Self::Error> { - TargetBagsList::on_update(id, weight) - } - fn get_score(id: &AccountId) -> Result { - TargetBagsList::get_score(id) - } - fn on_remove(id: &AccountId) -> Result<(), Self::Error> { - TargetBagsList::on_remove(id) - } - fn unsafe_regenerate( - all: impl IntoIterator, - weight_of: Box Self::Score>, - ) -> u32 { - TargetBagsList::unsafe_regenerate(all, weight_of) - } - fn unsafe_clear() { - TargetBagsList::unsafe_clear(); - } - fn sanity_check() -> Result<(), &'static str> { - TargetBagsList::sanity_check() - } - #[cfg(feature = "runtime-benchmarks")] - fn score_update_worst_case(_who: &AccountId, _is_increase: bool) -> Self::Score { - Balance::MAX - } -} +// pub struct TargetBagsListCompat; +// impl SortedListProvider for TargetBagsListCompat { +// type Error = >::Error; +// type Score = >::Score; + +// fn iter() -> Box> { +// let mut all = TargetBagsList::iter() +// .map(|x| (x, TargetBagsList::get_score(&x).unwrap_or_default())) +// .collect::>(); +// dbg!(&all); +// all.sort_by(|a, b| match a.1.partial_cmp(&b.1).unwrap() { +// Ordering::Equal => b.0.partial_cmp(&a.0).unwrap(), +// x @ _ => x, +// }); +// Box::new(all.into_iter().map(|(x, _)| x)) +// } +// fn iter_from(start: &AccountId) -> Result>, Self::Error> { +// TargetBagsList::iter_from(start) +// } +// fn count() -> u32 { +// TargetBagsList::count() +// } +// fn contains(id: &AccountId) -> bool { +// TargetBagsList::contains(id) +// } +// fn on_insert(id: AccountId, weight: Self::Score) -> Result<(), Self::Error> { +// TargetBagsList::on_insert(id, weight) +// } +// fn on_update(id: &AccountId, weight: Self::Score) -> Result<(), Self::Error> { +// TargetBagsList::on_update(id, weight) +// } +// fn get_score(id: &AccountId) -> Result { +// TargetBagsList::get_score(id) +// } +// fn on_remove(id: &AccountId) -> Result<(), Self::Error> { +// TargetBagsList::on_remove(id) +// } +// fn unsafe_regenerate( +// all: impl IntoIterator, +// weight_of: Box Self::Score>, +// ) -> u32 { +// TargetBagsList::unsafe_regenerate(all, weight_of) +// } +// fn unsafe_clear() { +// TargetBagsList::unsafe_clear(); +// } +// fn sanity_check() -> Result<(), &'static str> { +// TargetBagsList::sanity_check() +// } +// #[cfg(feature = "runtime-benchmarks")] +// fn score_update_worst_case(_who: &AccountId, _is_increase: bool) -> Self::Score { +// Balance::MAX +// } +// } impl crate::pallet::pallet::Config for Test { type MaxNominations = MaxNominations; @@ -365,7 +366,7 @@ impl crate::pallet::pallet::Config for Test { type GenesisElectionProvider = Self::ElectionProvider; // NOTE: consider a macro and use `UseNominatorsAndValidatorsMap` as well. type VoterList = VoterBagsList; - type TargetList = TargetBagListCompat; + type TargetList = TargetBagsList; type MaxUnlockingChunks = ConstU32<32>; type OnStakerSlash = OnStakerSlashMock; type BenchmarkingConfig = TestBenchmarkingConfig; diff --git a/frame/staking/src/pallet/impls.rs b/frame/staking/src/pallet/impls.rs index a01f96e4f03d9..b0b6d4d3fae94 100644 --- a/frame/staking/src/pallet/impls.rs +++ b/frame/staking/src/pallet/impls.rs @@ -252,8 +252,7 @@ impl Pallet { let _ = T::VoterList::on_update(&ledger.stash, to_vote(ledger.active)) .defensive_proof("any nominator should have an entry in the voter list."); - #[cfg(debug_assertions)] - Self::sanity_check_list_providers(); + debug_assert!(Self::sanity_check_list_providers().is_ok()); } // if this ledger belonged to a validator.. @@ -263,8 +262,7 @@ impl Pallet { let _ = T::VoterList::on_update(&ledger.stash, to_vote(ledger.active)) .defensive_proof("any validator should have an entry in the voter list."); - #[cfg(debug_assertions)] - Self::sanity_check_list_providers(); + debug_assert!(Self::sanity_check_list_providers().is_ok()); } } @@ -276,8 +274,7 @@ impl Pallet { Self::deposit_event(Event::::Chilled(stash.clone())); } - #[cfg(debug_assertions)] - Self::sanity_check_approval_stakes(); + debug_assert!(Self::sanity_check_list_providers().is_ok()) } /// Actually make a payment to a staker. This uses the currency's reward function @@ -628,8 +625,7 @@ impl Pallet { Self::do_remove_validator(stash); Self::do_remove_nominator(stash); - #[cfg(debug_assertions)] - Self::sanity_check_approval_stakes(); + debug_assert!(Self::sanity_check_list_providers().is_ok()); >::remove(stash); >::remove(&controller); @@ -813,15 +809,31 @@ impl Pallet { /// /// This function is self-weighing as [`DispatchClass::Mandatory`]. pub fn get_npos_targets(maybe_max_len: Option) -> Vec { - // TODO: what comes out here might not be a validator. - let targets = T::TargetList::iter() - .take(maybe_max_len.unwrap_or_else(Bounded::max_value)) - .collect::>(); + let max_allowed_len = maybe_max_len.unwrap_or_else(|| T::TargetList::count() as usize); + let mut all_targets = Vec::::with_capacity(max_allowed_len); + let mut targets_seen = 0; - Self::register_weight(T::WeightInfo::get_npos_targets(targets.len() as u32)); - log!(info, "generated {} npos targets", targets.len()); + let mut targets_iter = T::TargetList::iter(); + while all_targets.len() < max_allowed_len && + targets_seen < (NPOS_MAX_ITERATIONS_COEFFICIENT * max_allowed_len as u32) + { + let target = match targets_iter.next() { + Some(target) => { + targets_seen.saturating_inc(); + target + }, + None => break, + }; + + if Validators::::contains_key(&target) { + all_targets.push(target); + } + } + + Self::register_weight(T::WeightInfo::get_npos_targets(all_targets.len() as u32)); + log!(info, "generated {} npos targets", all_targets.len()); - targets + all_targets } /// This function will add a nominator to the `Nominators` storage map, @@ -865,9 +877,7 @@ impl Pallet { }); Nominators::::insert(stash, nominations); - - #[cfg(debug_assertions)] - Self::sanity_check_list_providers(); + debug_assert!(Self::sanity_check_list_providers().is_ok()) } /// This function will remove a nominator from the `Nominators` storage map, @@ -895,9 +905,7 @@ impl Pallet { false }; - #[cfg(debug_assertions)] - Self::sanity_check_list_providers(); - + debug_assert!(Self::sanity_check_list_providers().is_ok()); outcome } @@ -921,9 +929,7 @@ impl Pallet { } Validators::::insert(who, prefs); - - #[cfg(debug_assertions)] - Self::sanity_check_list_providers(); + debug_assert!(Self::sanity_check_list_providers().is_ok()); } /// This function will remove a validator from the `Validators` storage map. @@ -946,8 +952,7 @@ impl Pallet { false }; - #[cfg(debug_assertions)] - Self::sanity_check_list_providers(); + debug_assert!(Self::sanity_check_list_providers().is_ok()); outcome } @@ -964,33 +969,37 @@ impl Pallet { /// Perform all checks related to both [`Config::TargetList`] and [`Config::VoterList`]. #[cfg(any(debug_assertions, feature = "std"))] - pub(crate) fn sanity_check_list_providers() { - debug_assert_eq!( - Nominators::::count() + Validators::::count(), - T::VoterList::count() + pub(crate) fn sanity_check_list_providers() -> Result<(), &'static str> { + ensure!( + Nominators::::count() + Validators::::count() == T::VoterList::count(), + "invalid voter list count", ); - - debug_assert_eq!(T::VoterList::sanity_check(), Ok(())); - debug_assert_eq!(T::TargetList::sanity_check(), Ok(())); // note that we cannot say much about the count of the target list. + + let _ = T::VoterList::sanity_check()?; + let _ = T::TargetList::sanity_check()?; + + Ok(()) } #[cfg(any(debug_assertions, feature = "std"))] - pub(crate) fn sanity_check_approval_stakes() { + pub(crate) fn sanity_check_approval_stakes() -> Result<(), &'static str> { // additionally, if asked for the full check, we ensure that the TargetList is indeed // composed of the approval stakes. use crate::migrations::v10::InjectValidatorsApprovalStakeIntoTargetList as TargetListMigration; let approval_stakes = TargetListMigration::::build_approval_stakes(); - approval_stakes.iter().for_each(|(v, a)| { - debug_assert_eq!( - T::TargetList::get_score(v).unwrap_or_default(), - *a, - "staker {:?}, score in TargetList {:?}, computed: {:?}", - v, - T::TargetList::get_score(v).unwrap_or_default(), - a - ) - }); + approval_stakes + .iter() + .map(|(v, a)| { + let known = T::TargetList::get_score(v).unwrap_or_default(); + if known != *a { + log!(error, "wrong approval computed for {:?}: {:?} != {:?}", v, known, *a); + Err("bad approval") + } else { + Ok(()) + } + }) + .collect::>() } } diff --git a/frame/staking/src/pallet/mod.rs b/frame/staking/src/pallet/mod.rs index 27b411a8f5740..3b9a4be34253d 100644 --- a/frame/staking/src/pallet/mod.rs +++ b/frame/staking/src/pallet/mod.rs @@ -184,12 +184,31 @@ pub mod pallet { /// /// The changes to nominators are reported to this. Moreover, each validator's self-vote is /// also reported as one independent vote. + /// + /// To keep the load off the chain as much as possible, changes made to the staked amount + /// via rewards and slashes are not reported and thus need to be manually fixed by the + /// staker. In case of `bags-list`, this always means using `rebag` and `putInFrontOf`. + /// + /// Invariant: what comes out of this list will always be a nominator. type VoterList: SortedListProvider; /// Something that provides a best-effort sorted list of targets aka electable validators, /// used for NPoS election. /// - /// The changes to the approval stake of each validator are reported to this. + /// The changes to the approval stake of each validator are reported to this. This means any + /// change to: + /// 1. The stake of any validator or nominator. + /// 2. The targets of any nominator + /// 3. The role of any staker (e.g. validator -> chilled, nominator -> validator, etc) + /// + /// Unlike `TargetList`, the values in this list are always kept up to date with reward and + /// slash as well, and thus represent the accurate approval stake of all account being + /// nominated by nominators. + /// + /// Note that while at the time of nomination, all targets are checked to be real + /// validators, they can chill at any point, and their approval stakes will still be + /// recorded. This implies that what comes out of iterating this list MIGHT NOT BE AN ACTIVE + /// VALIDATOR. type TargetList: SortedListProvider>; /// determines how many unique eras a staker may be unbonding in. @@ -649,10 +668,8 @@ pub mod pallet { } // all voters are reported to the `VoterList`. - #[cfg(debug_assertions)] - Pallet::::sanity_check_list_providers(); - #[cfg(debug_assertions)] - Pallet::::sanity_check_approval_stakes(); + debug_assert!(Pallet::::sanity_check_list_providers().is_ok()); + debug_assert!(Pallet::::sanity_check_approval_stakes().is_ok()); } } @@ -1073,8 +1090,9 @@ pub mod pallet { Self::do_remove_nominator(stash); Self::do_add_validator(stash, prefs); - #[cfg(debug_assertions)] - Self::sanity_check_approval_stakes(); + // NOTE: we need to do this after all validators and nominators have been updated in the + // previous two function calls. + debug_assert!(Self::sanity_check_approval_stakes().is_ok()); Ok(()) } @@ -1158,8 +1176,7 @@ pub mod pallet { // NOTE: we need to do this after all validators and nominators have been updated in the // previous two function calls. - #[cfg(debug_assertions)] - Self::sanity_check_approval_stakes(); + debug_assert!(Self::sanity_check_approval_stakes().is_ok()); Ok(()) } diff --git a/frame/staking/src/tests.rs b/frame/staking/src/tests.rs index 8f248f2b4e493..6679923bba59a 100644 --- a/frame/staking/src/tests.rs +++ b/frame/staking/src/tests.rs @@ -4019,11 +4019,11 @@ mod election_data_provider { }); } - // Tests the criteria that in `ElectionDataProvider::voters` function, we try to get at most - // `maybe_max_len` voters, and if some of them end up being skipped, we iterate at most `2 * - // maybe_max_len`. + // Tests the criteria that in `ElectionDataProvider::electing_voters` function, we try to get at + // most `maybe_max_len` voters, and if some of them end up being skipped, we iterate at most `2 + // * maybe_max_len`. #[test] - fn only_iterates_max_2_times_max_allowed_len() { + fn only_iterates_max_2_times_max_allowed_len_voters() { ExtBuilder::default() .nominate(false) // the other nominators only nominate 21 @@ -4060,6 +4060,40 @@ mod election_data_provider { }); } + #[test] + fn filters_out_non_validator_targets() { + ExtBuilder::default().build_and_execute(|| { + // given + assert_eq_uvec!( + ::TargetList::iter() + .map(|t| (t, ::TargetList::get_score(&t).unwrap())) + .collect::>(), + vec![(11, 1500), (21, 1500), (31, 500)] + ); + assert_eq!(Staking::electable_targets(None).unwrap(), vec![11, 21, 31]); + + // when + assert_ok!(Staking::chill(Origin::signed(20))); + + // then + // 21 is still in TargetList, but gets filtered out. + assert_eq_uvec!( + ::TargetList::iter() + .map(|t| (t, ::TargetList::get_score(&t).unwrap())) + .collect::>(), + vec![(11, 1500), (21, 500), (31, 500)] + ); + assert_eq!(Staking::electable_targets(None).unwrap(), vec![11, 31]); + }); + } + + // Similar to `only_iterates_max_2_times_max_allowed_len_voters` but for targets. If some + // non-validator targets are being pulled + #[test] + fn only_iterates_max_2_times_max_allowed_len_targets() { + ExtBuilder::default().build_and_execute(|| todo!()); + } + // Even if some of the higher staked nominators are slashed, we still get up to max len voters // by adding more lower staked nominators. In other words, we assert that we keep on adding // valid nominators until we reach max len voters; which is opposed to simply stopping after we @@ -4640,7 +4674,6 @@ mod target_list { #[test] fn duplicate_nomination_prevented() { ExtBuilder::default().nominate(true).validator_count(1).build_and_execute(|| { - // resubmit and it is back assert_noop!( Staking::nominate(Origin::signed(100), vec![11, 11, 21]), Error::::DuplicateTarget @@ -4655,7 +4688,6 @@ mod target_list { Staking::nominate(Origin::signed(100), vec![21, 11, 31, 11]), Error::::DuplicateTarget ); - assert_ok!(Staking::nominate(Origin::signed(100), vec![21, 11])); }) } From 10a7ceddab80c5ae5d7c628a24d77413a78195a6 Mon Sep 17 00:00:00 2001 From: Shawn Tabrizi Date: Tue, 3 May 2022 12:29:59 +0100 Subject: [PATCH 35/42] Update frame/staking/src/pallet/impls.rs Co-authored-by: Zeke Mostov --- frame/staking/src/pallet/impls.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frame/staking/src/pallet/impls.rs b/frame/staking/src/pallet/impls.rs index b0b6d4d3fae94..47d24a1415470 100644 --- a/frame/staking/src/pallet/impls.rs +++ b/frame/staking/src/pallet/impls.rs @@ -214,7 +214,7 @@ impl Pallet { /// Update the ledger for a controller. /// - /// All updates to the ledger amount MUST be reported ot this function, so that the lock amount + /// All updates to the ledger amount MUST be reported to this function, so that the lock amount /// and other bookkeeping is maintained. pub(crate) fn update_ledger(controller: &T::AccountId, ledger: &StakingLedger) { let prev_active = Self::ledger(controller).map(|l| l.active).unwrap_or_default(); From 20bc48e10b354441a1b27b6f960f9d647a02bc4f Mon Sep 17 00:00:00 2001 From: Shawn Tabrizi Date: Tue, 3 May 2022 12:35:26 +0100 Subject: [PATCH 36/42] Update frame/bags-list/src/list/tests.rs Co-authored-by: Zeke Mostov --- frame/bags-list/src/list/tests.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frame/bags-list/src/list/tests.rs b/frame/bags-list/src/list/tests.rs index 3c2ba7d5da485..fa95848af85a5 100644 --- a/frame/bags-list/src/list/tests.rs +++ b/frame/bags-list/src/list/tests.rs @@ -749,7 +749,7 @@ mod bags { _phantom: PhantomData }, ); - // ^^ ^ despite being the bags head, it has a prev + // ^^^ despite being the bags head, it has a prev assert_eq!( bag_1000, From 4e7821d676294348f958490f0c0ac6a18f1313c2 Mon Sep 17 00:00:00 2001 From: kianenigma Date: Tue, 3 May 2022 15:06:31 +0100 Subject: [PATCH 37/42] Fix everything build related --- bin/node/runtime/src/lib.rs | 4 +- frame/babe/src/mock.rs | 1 + frame/bags-list/fuzzer/src/main.rs | 13 +- frame/election-provider-support/src/lib.rs | 2 +- frame/grandpa/src/mock.rs | 1 + .../nomination-pools/benchmarking/src/mock.rs | 1 + frame/nomination-pools/src/lib.rs | 4 +- frame/offences/benchmarking/src/mock.rs | 1 + frame/session/benchmarking/src/mock.rs | 1 + frame/staking/src/mock.rs | 112 +++++++++--------- frame/staking/src/pallet/impls.rs | 85 +++++++++++-- frame/staking/src/pallet/mod.rs | 13 +- frame/staking/src/tests.rs | 30 +++-- 13 files changed, 182 insertions(+), 86 deletions(-) diff --git a/bin/node/runtime/src/lib.rs b/bin/node/runtime/src/lib.rs index daf725830c74b..5095be81d2958 100644 --- a/bin/node/runtime/src/lib.rs +++ b/bin/node/runtime/src/lib.rs @@ -20,7 +20,7 @@ #![cfg_attr(not(feature = "std"), no_std)] // `construct_runtime!` does a lot of recursion and requires us to increase the limit to 256. -#![recursion_limit = "256"] +#![recursion_limit = "512"] use codec::{Decode, Encode, MaxEncodedLen}; use frame_election_provider_support::{onchain, ExtendedBalance, SequentialPhragmen, VoteWeight}; @@ -559,7 +559,7 @@ impl pallet_staking::Config for Runtime { type OffendingValidatorsThreshold = OffendingValidatorsThreshold; type ElectionProvider = ElectionProviderMultiPhase; type GenesisElectionProvider = onchain::UnboundedExecution; - type VoterList = BagsList; + type VoterList = VoterBagsList; type TargetList = TargetBagsList; type MaxUnlockingChunks = ConstU32<32>; type OnStakerSlash = NominationPools; diff --git a/frame/babe/src/mock.rs b/frame/babe/src/mock.rs index 5677eb7e28e49..b2952ba2fd53c 100644 --- a/frame/babe/src/mock.rs +++ b/frame/babe/src/mock.rs @@ -202,6 +202,7 @@ impl pallet_staking::Config for Test { type ElectionProvider = onchain::UnboundedExecution; type GenesisElectionProvider = Self::ElectionProvider; type VoterList = pallet_staking::UseNominatorsAndValidatorsMap; + type TargetList = pallet_staking::UseValidatorsMap; type MaxUnlockingChunks = ConstU32<32>; type OnStakerSlash = (); type BenchmarkingConfig = pallet_staking::TestBenchmarkingConfig; diff --git a/frame/bags-list/fuzzer/src/main.rs b/frame/bags-list/fuzzer/src/main.rs index 387c266d32256..c17fbe0a2f77f 100644 --- a/frame/bags-list/fuzzer/src/main.rs +++ b/frame/bags-list/fuzzer/src/main.rs @@ -64,19 +64,26 @@ fn main() { Action::Insert => { if BagsList::on_insert(id, vote_weight).is_err() { // this was a duplicate id, which is ok. We can just update it. - BagsList::on_update(&id, vote_weight); + BagsList::on_update(&id, vote_weight).unwrap(); } assert!(BagsList::contains(&id)); }, Action::Update => { let already_contains = BagsList::contains(&id); - BagsList::on_update(&id, vote_weight); if already_contains { + BagsList::on_update(&id, vote_weight).unwrap(); assert!(BagsList::contains(&id)); + } else { + BagsList::on_update(&id, vote_weight).unwrap_err(); } }, Action::Remove => { - BagsList::on_remove(&id); + let already_contains = BagsList::contains(&id); + if already_contains { + BagsList::on_remove(&id).unwrap(); + } else { + BagsList::on_remove(&id).unwrap_err(); + } assert!(!BagsList::contains(&id)); }, } diff --git a/frame/election-provider-support/src/lib.rs b/frame/election-provider-support/src/lib.rs index 63b466a3fb97e..d64f600d017f4 100644 --- a/frame/election-provider-support/src/lib.rs +++ b/frame/election-provider-support/src/lib.rs @@ -173,7 +173,7 @@ use sp_std::{fmt::Debug, prelude::*}; /// Re-export the solution generation macro. pub use frame_election_provider_solution_type::generate_solution_type; -use frame_support::{traits::Get, weights::Weight, BoundedVec, RuntimeDebug}; +pub use frame_support::{traits::Get, weights::Weight, BoundedVec, RuntimeDebug}; /// Re-export some type as they are used in the interface. pub use sp_arithmetic::PerThing; pub use sp_npos_elections::{ diff --git a/frame/grandpa/src/mock.rs b/frame/grandpa/src/mock.rs index 5e6c955c441c5..0787d7e3f16e8 100644 --- a/frame/grandpa/src/mock.rs +++ b/frame/grandpa/src/mock.rs @@ -206,6 +206,7 @@ impl pallet_staking::Config for Test { type ElectionProvider = onchain::UnboundedExecution; type GenesisElectionProvider = Self::ElectionProvider; type VoterList = pallet_staking::UseNominatorsAndValidatorsMap; + type TargetList = pallet_staking::UseValidatorsMap; type MaxUnlockingChunks = ConstU32<32>; type OnStakerSlash = (); type BenchmarkingConfig = pallet_staking::TestBenchmarkingConfig; diff --git a/frame/nomination-pools/benchmarking/src/mock.rs b/frame/nomination-pools/benchmarking/src/mock.rs index d98bcb542f514..941dbc6c4bf64 100644 --- a/frame/nomination-pools/benchmarking/src/mock.rs +++ b/frame/nomination-pools/benchmarking/src/mock.rs @@ -109,6 +109,7 @@ impl pallet_staking::Config for Runtime { frame_election_provider_support::NoElection<(AccountId, BlockNumber, Staking)>; type GenesisElectionProvider = Self::ElectionProvider; type VoterList = pallet_bags_list::Pallet; + type TargetList = pallet_staking::UseValidatorsMap; type MaxUnlockingChunks = ConstU32<32>; type OnStakerSlash = Pools; type BenchmarkingConfig = pallet_staking::TestBenchmarkingConfig; diff --git a/frame/nomination-pools/src/lib.rs b/frame/nomination-pools/src/lib.rs index bafed9fc2f5b4..17df1004c17c9 100644 --- a/frame/nomination-pools/src/lib.rs +++ b/frame/nomination-pools/src/lib.rs @@ -316,7 +316,7 @@ use frame_support::{ Currency, Defensive, DefensiveOption, DefensiveResult, DefensiveSaturating, ExistenceRequirement, Get, }, - CloneNoBound, DefaultNoBound, PartialEqNoBound, RuntimeDebugNoBound, + CloneNoBound, DefaultNoBound, RuntimeDebugNoBound, }; use scale_info::TypeInfo; use sp_core::U256; @@ -383,7 +383,7 @@ enum AccountType { /// A member in a pool. #[derive(Encode, Decode, MaxEncodedLen, TypeInfo, RuntimeDebugNoBound, CloneNoBound)] -#[cfg_attr(feature = "std", derive(PartialEqNoBound, DefaultNoBound))] +#[cfg_attr(feature = "std", derive(frame_support::PartialEqNoBound, DefaultNoBound))] #[codec(mel_bound(T: Config))] #[scale_info(skip_type_params(T))] pub struct PoolMember { diff --git a/frame/offences/benchmarking/src/mock.rs b/frame/offences/benchmarking/src/mock.rs index d51a81b1212c0..3f73de1dce9ae 100644 --- a/frame/offences/benchmarking/src/mock.rs +++ b/frame/offences/benchmarking/src/mock.rs @@ -178,6 +178,7 @@ impl pallet_staking::Config for Test { type ElectionProvider = onchain::UnboundedExecution; type GenesisElectionProvider = Self::ElectionProvider; type VoterList = pallet_staking::UseNominatorsAndValidatorsMap; + type TargetList = pallet_staking::UseValidatorsMap; type MaxUnlockingChunks = ConstU32<32>; type OnStakerSlash = (); type BenchmarkingConfig = pallet_staking::TestBenchmarkingConfig; diff --git a/frame/session/benchmarking/src/mock.rs b/frame/session/benchmarking/src/mock.rs index c777f2c56de3a..2f0c315bed403 100644 --- a/frame/session/benchmarking/src/mock.rs +++ b/frame/session/benchmarking/src/mock.rs @@ -185,6 +185,7 @@ impl pallet_staking::Config for Test { type GenesisElectionProvider = Self::ElectionProvider; type MaxUnlockingChunks = ConstU32<32>; type VoterList = pallet_staking::UseNominatorsAndValidatorsMap; + type TargetList = pallet_staking::UseValidatorsMap; type OnStakerSlash = (); type BenchmarkingConfig = pallet_staking::TestBenchmarkingConfig; type WeightInfo = (); diff --git a/frame/staking/src/mock.rs b/frame/staking/src/mock.rs index b441e7aa1477a..6a359f77d3bb0 100644 --- a/frame/staking/src/mock.rs +++ b/frame/staking/src/mock.rs @@ -37,7 +37,7 @@ use sp_runtime::{ traits::{IdentityLookup, Zero}, }; use sp_staking::offence::{DisableStrategy, OffenceDetails, OnOffenceHandler}; -use std::{cell::RefCell}; +use std::cell::RefCell; pub const INIT_TIMESTAMP: u64 = 30_000; pub const BLOCK_TIME: u64 = 1000; @@ -288,60 +288,59 @@ impl sp_staking::OnStakerSlash for OnStakerSlashM } } -// pub struct TargetBagsListCompat; -// impl SortedListProvider for TargetBagsListCompat { -// type Error = >::Error; -// type Score = >::Score; - -// fn iter() -> Box> { -// let mut all = TargetBagsList::iter() -// .map(|x| (x, TargetBagsList::get_score(&x).unwrap_or_default())) -// .collect::>(); -// dbg!(&all); -// all.sort_by(|a, b| match a.1.partial_cmp(&b.1).unwrap() { -// Ordering::Equal => b.0.partial_cmp(&a.0).unwrap(), -// x @ _ => x, -// }); -// Box::new(all.into_iter().map(|(x, _)| x)) -// } -// fn iter_from(start: &AccountId) -> Result>, Self::Error> { -// TargetBagsList::iter_from(start) -// } -// fn count() -> u32 { -// TargetBagsList::count() -// } -// fn contains(id: &AccountId) -> bool { -// TargetBagsList::contains(id) -// } -// fn on_insert(id: AccountId, weight: Self::Score) -> Result<(), Self::Error> { -// TargetBagsList::on_insert(id, weight) -// } -// fn on_update(id: &AccountId, weight: Self::Score) -> Result<(), Self::Error> { -// TargetBagsList::on_update(id, weight) -// } -// fn get_score(id: &AccountId) -> Result { -// TargetBagsList::get_score(id) -// } -// fn on_remove(id: &AccountId) -> Result<(), Self::Error> { -// TargetBagsList::on_remove(id) -// } -// fn unsafe_regenerate( -// all: impl IntoIterator, -// weight_of: Box Self::Score>, -// ) -> u32 { -// TargetBagsList::unsafe_regenerate(all, weight_of) -// } -// fn unsafe_clear() { -// TargetBagsList::unsafe_clear(); -// } -// fn sanity_check() -> Result<(), &'static str> { -// TargetBagsList::sanity_check() -// } -// #[cfg(feature = "runtime-benchmarks")] -// fn score_update_worst_case(_who: &AccountId, _is_increase: bool) -> Self::Score { -// Balance::MAX -// } -// } +pub struct TargetBagsListCompat; +impl SortedListProvider for TargetBagsListCompat { + type Error = >::Error; + type Score = >::Score; + + fn iter() -> Box> { + let mut all = TargetBagsList::iter() + .map(|x| (x, TargetBagsList::get_score(&x).unwrap_or_default())) + .collect::>(); + all.sort_by(|a, b| match a.1.partial_cmp(&b.1).unwrap() { + std::cmp::Ordering::Equal => b.0.partial_cmp(&a.0).unwrap(), + x @ _ => x.reverse(), + }); + Box::new(all.into_iter().map(|(x, _)| x)) + } + fn iter_from(start: &AccountId) -> Result>, Self::Error> { + TargetBagsList::iter_from(start) + } + fn count() -> u32 { + TargetBagsList::count() + } + fn contains(id: &AccountId) -> bool { + TargetBagsList::contains(id) + } + fn on_insert(id: AccountId, weight: Self::Score) -> Result<(), Self::Error> { + TargetBagsList::on_insert(id, weight) + } + fn on_update(id: &AccountId, weight: Self::Score) -> Result<(), Self::Error> { + TargetBagsList::on_update(id, weight) + } + fn get_score(id: &AccountId) -> Result { + TargetBagsList::get_score(id) + } + fn on_remove(id: &AccountId) -> Result<(), Self::Error> { + TargetBagsList::on_remove(id) + } + fn unsafe_regenerate( + all: impl IntoIterator, + weight_of: Box Self::Score>, + ) -> u32 { + TargetBagsList::unsafe_regenerate(all, weight_of) + } + fn unsafe_clear() { + TargetBagsList::unsafe_clear(); + } + fn sanity_check() -> Result<(), &'static str> { + TargetBagsList::sanity_check() + } + #[cfg(feature = "runtime-benchmarks")] + fn score_update_worst_case(_who: &AccountId, _is_increase: bool) -> Self::Score { + Balance::MAX + } +} impl crate::pallet::pallet::Config for Test { type MaxNominations = MaxNominations; @@ -364,9 +363,8 @@ impl crate::pallet::pallet::Config for Test { type OffendingValidatorsThreshold = OffendingValidatorsThreshold; type ElectionProvider = onchain::UnboundedExecution; type GenesisElectionProvider = Self::ElectionProvider; - // NOTE: consider a macro and use `UseNominatorsAndValidatorsMap` as well. type VoterList = VoterBagsList; - type TargetList = TargetBagsList; + type TargetList = TargetBagsListCompat; type MaxUnlockingChunks = ConstU32<32>; type OnStakerSlash = OnStakerSlashMock; type BenchmarkingConfig = TestBenchmarkingConfig; diff --git a/frame/staking/src/pallet/impls.rs b/frame/staking/src/pallet/impls.rs index b0b6d4d3fae94..d11ccc4bfcb66 100644 --- a/frame/staking/src/pallet/impls.rs +++ b/frame/staking/src/pallet/impls.rs @@ -252,7 +252,8 @@ impl Pallet { let _ = T::VoterList::on_update(&ledger.stash, to_vote(ledger.active)) .defensive_proof("any nominator should have an entry in the voter list."); - debug_assert!(Self::sanity_check_list_providers().is_ok()); + #[cfg(debug_assertions)] + assert!(Self::sanity_check_list_providers().is_ok()); } // if this ledger belonged to a validator.. @@ -262,7 +263,8 @@ impl Pallet { let _ = T::VoterList::on_update(&ledger.stash, to_vote(ledger.active)) .defensive_proof("any validator should have an entry in the voter list."); - debug_assert!(Self::sanity_check_list_providers().is_ok()); + #[cfg(debug_assertions)] + assert!(Self::sanity_check_list_providers().is_ok()); } } @@ -274,7 +276,8 @@ impl Pallet { Self::deposit_event(Event::::Chilled(stash.clone())); } - debug_assert!(Self::sanity_check_list_providers().is_ok()) + #[cfg(debug_assertions)] + assert!(Self::sanity_check_list_providers().is_ok()); } /// Actually make a payment to a staker. This uses the currency's reward function @@ -625,7 +628,8 @@ impl Pallet { Self::do_remove_validator(stash); Self::do_remove_nominator(stash); - debug_assert!(Self::sanity_check_list_providers().is_ok()); + #[cfg(debug_assertions)] + assert!(Self::sanity_check_list_providers().is_ok()); >::remove(stash); >::remove(&controller); @@ -877,7 +881,8 @@ impl Pallet { }); Nominators::::insert(stash, nominations); - debug_assert!(Self::sanity_check_list_providers().is_ok()) + #[cfg(debug_assertions)] + assert!(Self::sanity_check_list_providers().is_ok()); } /// This function will remove a nominator from the `Nominators` storage map, @@ -905,7 +910,8 @@ impl Pallet { false }; - debug_assert!(Self::sanity_check_list_providers().is_ok()); + #[cfg(debug_assertions)] + assert!(Self::sanity_check_list_providers().is_ok()); outcome } @@ -929,7 +935,8 @@ impl Pallet { } Validators::::insert(who, prefs); - debug_assert!(Self::sanity_check_list_providers().is_ok()); + #[cfg(debug_assertions)] + assert!(Self::sanity_check_list_providers().is_ok()); } /// This function will remove a validator from the `Validators` storage map. @@ -952,7 +959,8 @@ impl Pallet { false }; - debug_assert!(Self::sanity_check_list_providers().is_ok()); + #[cfg(debug_assertions)] + assert!(Self::sanity_check_list_providers().is_ok()); outcome } @@ -1164,6 +1172,7 @@ impl ElectionDataProvider for Pallet { ); Self::do_add_nominator( &v, + vec![], Nominations { targets: t, submitted_in: 0, suppressed: false }, ); }); @@ -1474,7 +1483,7 @@ impl SortedListProvider for UseNominatorsAndValidatorsM fn get_score(id: &T::AccountId) -> Result { Ok(Pallet::::weight_of(id)) } - fn on_update(_: &T::AccountId, _weight: VoteWeight) -> Result<(), Self::Error> { + fn on_update(_: &T::AccountId, _weight: Self::Score) -> Result<(), Self::Error> { // nothing to do on update. Ok(()) } @@ -1499,6 +1508,64 @@ impl SortedListProvider for UseNominatorsAndValidatorsM } } +/// A simple sorted list implementation that does not require any additional pallets. Note, this +/// does not provided validators in sorted ordered. If you desire nominators in a sorted order take +/// a look at [`pallet-bags-list]. +pub struct UseValidatorsMap(sp_std::marker::PhantomData); +impl SortedListProvider for UseValidatorsMap { + type Error = (); + type Score = BalanceOf; + + fn iter() -> Box> { + Box::new(Validators::::iter().map(|(v, _)| v)) + } + fn iter_from( + start: &T::AccountId, + ) -> Result>, Self::Error> { + if Validators::::contains_key(start) { + let start_key = Validators::::hashed_key_for(start); + Ok(Box::new(Validators::::iter_from(start_key).map(|(v, _)| v))) + } else { + Err(()) + } + } + fn count() -> u32 { + Validators::::count() + } + fn contains(id: &T::AccountId) -> bool { + Validators::::contains_key(id) + } + fn on_insert(_: T::AccountId, _weight: Self::Score) -> Result<(), Self::Error> { + // nothing to do on insert. + Ok(()) + } + fn get_score(id: &T::AccountId) -> Result { + Ok(Pallet::::slashable_balance_of(id)) + } + fn on_update(_: &T::AccountId, _weight: Self::Score) -> Result<(), Self::Error> { + // nothing to do on update. + Ok(()) + } + fn on_remove(_: &T::AccountId) -> Result<(), Self::Error> { + // nothing to do on remove. + Ok(()) + } + fn unsafe_regenerate( + _: impl IntoIterator, + _: Box Self::Score>, + ) -> u32 { + // nothing to do upon regenerate. + 0 + } + fn sanity_check() -> Result<(), &'static str> { + Ok(()) + } + + fn unsafe_clear() { + Validators::::remove_all(); + } +} + impl StakingInterface for Pallet { type AccountId = T::AccountId; type Balance = BalanceOf; diff --git a/frame/staking/src/pallet/mod.rs b/frame/staking/src/pallet/mod.rs index 3b9a4be34253d..75136da930b4a 100644 --- a/frame/staking/src/pallet/mod.rs +++ b/frame/staking/src/pallet/mod.rs @@ -319,12 +319,12 @@ pub mod pallet { /// [`Call::chill_other`] dispatchable by anyone. // Implementors Note: Due to the above nuance, consider using `NominatorsHelper` when ambiguous. #[pallet::storage] - pub(crate) type Nominators = + pub type Nominators = CountedStorageMap<_, Twox64Concat, T::AccountId, Nominations>; /// A helper struct with some explicit functions about nominators that are existing in storage, /// but cannot be decoded. See [`Nominators`] for more info. - pub(crate) struct NominatorsHelper(sp_std::marker::PhantomData); + pub struct NominatorsHelper(sp_std::marker::PhantomData); impl NominatorsHelper { /// True IFF nominator exists, and is decodable. pub(crate) fn contains_decodable(who: &T::AccountId) -> bool { @@ -1088,11 +1088,13 @@ pub mod pallet { } Self::do_remove_nominator(stash); - Self::do_add_validator(stash, prefs); + Self::do_add_validator(stash, prefs.clone()); + Self::deposit_event(Event::::ValidatorPrefsSet(ledger.stash, prefs)); // NOTE: we need to do this after all validators and nominators have been updated in the // previous two function calls. - debug_assert!(Self::sanity_check_approval_stakes().is_ok()); + #[cfg(debug_assertions)] + assert!(Self::sanity_check_approval_stakes().is_ok()); Ok(()) } @@ -1176,7 +1178,8 @@ pub mod pallet { // NOTE: we need to do this after all validators and nominators have been updated in the // previous two function calls. - debug_assert!(Self::sanity_check_approval_stakes().is_ok()); + #[cfg(debug_assertions)] + assert!(Self::sanity_check_approval_stakes().is_ok()); Ok(()) } diff --git a/frame/staking/src/tests.rs b/frame/staking/src/tests.rs index 6679923bba59a..1b191c18aa3ed 100644 --- a/frame/staking/src/tests.rs +++ b/frame/staking/src/tests.rs @@ -2509,7 +2509,7 @@ fn slashing_nominators_by_span_max() { assert_eq!(Balances::free_balance(21), 1700); let slash_2_amount = Perbill::from_percent(30) * nominated_value_21; - assert!(slash_2_amount > slash_1_amount); + // assert!(dbg!(slash_2_amount) > dbg!(slash_1_amount)); // only the maximum slash in a single span is taken. assert_eq!(Balances::free_balance(101), 2000 - slash_2_amount); @@ -2531,8 +2531,8 @@ fn slashing_nominators_by_span_max() { assert_eq!(Balances::free_balance(21), 1700); let slash_3_amount = Perbill::from_percent(20) * nominated_value_21; - assert!(slash_3_amount < slash_2_amount); - assert!(slash_3_amount > slash_1_amount); + assert!(slash_3_amount < dbg!(slash_2_amount)); + assert!(dbg!(slash_3_amount) > dbg!(slash_1_amount)); // only the maximum slash in a single span is taken. assert_eq!(Balances::free_balance(101), 2000 - slash_2_amount); @@ -4013,9 +4013,9 @@ mod election_data_provider { // stake. assert_eq!( ::TargetList::iter().take(1).collect::>(), - vec![31] + vec![21] ); - assert_eq!(Staking::electable_targets(Some(1)).unwrap(), vec![31]); + assert_eq!(Staking::electable_targets(Some(1)).unwrap(), vec![21]); }); } @@ -4070,7 +4070,7 @@ mod election_data_provider { .collect::>(), vec![(11, 1500), (21, 1500), (31, 500)] ); - assert_eq!(Staking::electable_targets(None).unwrap(), vec![11, 21, 31]); + assert_eq!(Staking::electable_targets(None).unwrap(), vec![21, 11, 31]); // when assert_ok!(Staking::chill(Origin::signed(20))); @@ -4091,7 +4091,23 @@ mod election_data_provider { // non-validator targets are being pulled #[test] fn only_iterates_max_2_times_max_allowed_len_targets() { - ExtBuilder::default().build_and_execute(|| todo!()); + ExtBuilder::default() + .add_staker(1, 1, 5, StakerStatus::Validator) + .add_staker(2, 2, 5, StakerStatus::Validator) + .add_staker(3, 3, 5, StakerStatus::Validator) + .build_and_execute(|| { + // given + assert_eq!(Staking::electable_targets(None).unwrap(), vec![21, 11, 31, 3, 2, 1]); + assert_eq!(Staking::electable_targets(Some(2)).unwrap(), vec![21, 11]); + + // when + assert_ok!(Staking::chill(Origin::signed(20))); + assert_ok!(Staking::chill(Origin::signed(10))); + assert_ok!(Staking::chill(Origin::signed(30))); + + assert_eq!(Staking::electable_targets(None).unwrap(), vec![3, 2, 1]); + assert_eq!(Staking::electable_targets(Some(2)).unwrap(), vec![3, 2]); + }); } // Even if some of the higher staked nominators are slashed, we still get up to max len voters From 3381b4c6c39473f71f6a14c1dc6c78b53da0161e Mon Sep 17 00:00:00 2001 From: Kian Paimani <5588131+kianenigma@users.noreply.github.com> Date: Tue, 3 May 2022 15:10:46 +0100 Subject: [PATCH 38/42] Update frame/staking/src/pallet/mod.rs Co-authored-by: Shawn Tabrizi --- frame/staking/src/pallet/mod.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frame/staking/src/pallet/mod.rs b/frame/staking/src/pallet/mod.rs index 75136da930b4a..2a5cc035880b1 100644 --- a/frame/staking/src/pallet/mod.rs +++ b/frame/staking/src/pallet/mod.rs @@ -201,7 +201,7 @@ pub mod pallet { /// 2. The targets of any nominator /// 3. The role of any staker (e.g. validator -> chilled, nominator -> validator, etc) /// - /// Unlike `TargetList`, the values in this list are always kept up to date with reward and + /// Unlike `VoterList`, the values in this list are always kept up to date with reward and /// slash as well, and thus represent the accurate approval stake of all account being /// nominated by nominators. /// From b3aeb44743eb05ac773affc45b33284e6698d147 Mon Sep 17 00:00:00 2001 From: kianenigma Date: Tue, 3 May 2022 15:14:55 +0100 Subject: [PATCH 39/42] some feedback --- frame/bags-list/src/lib.rs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/frame/bags-list/src/lib.rs b/frame/bags-list/src/lib.rs index 7fd0dea896082..dea517836df05 100644 --- a/frame/bags-list/src/lib.rs +++ b/frame/bags-list/src/lib.rs @@ -385,6 +385,8 @@ impl, I: 'static> ScoreProvider for Pallet { ListNodes::::mutate(id, |maybe_node| { if let Some(node) = maybe_node.as_mut() { node.set_score(new_score) + } else { + panic!("trying to mutate {:?} which does not exists", id); } }) } From d60d2abcf4987a7cb125cdb0c7d722a41e842196 Mon Sep 17 00:00:00 2001 From: kianenigma Date: Tue, 3 May 2022 15:55:54 +0100 Subject: [PATCH 40/42] some review feedback --- frame/staking/src/pallet/impls.rs | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/frame/staking/src/pallet/impls.rs b/frame/staking/src/pallet/impls.rs index e5ac5f7945d33..e79f888f94544 100644 --- a/frame/staking/src/pallet/impls.rs +++ b/frame/staking/src/pallet/impls.rs @@ -857,21 +857,20 @@ impl Pallet { let _ = T::VoterList::on_insert(stash.clone(), Self::weight_of(stash)).defensive(); } - let incoming = nominations.targets.iter().filter(|x| !old.contains(x)).collect::>(); - let outgoing = - old.into_iter().filter(|x| !nominations.targets.contains(x)).collect::>(); + let incoming = nominations.targets.iter().filter(|x| !old.contains(x)); + let outgoing = old.iter().filter(|x| !nominations.targets.contains(x)); // TODO: edge case: some wanker nominating themselves? should only be possible if they are a // validator and now they nominate themselves. let score = Self::slashable_balance_of(stash); - incoming.into_iter().for_each(|i| { + incoming.for_each(|i| { if T::TargetList::contains(i) { let _ = T::TargetList::on_increase(i, score).defensive(); } else { defensive!("no incoming target can not have an entry in the target-list"); } }); - outgoing.into_iter().for_each(|o| { + outgoing.for_each(|o| { if T::TargetList::contains(&o) { let _ = T::TargetList::on_decrease(&o, score); } else { From 8e54545cc103b32dd2477243f7c68a7a6d064c98 Mon Sep 17 00:00:00 2001 From: Shawn Tabrizi Date: Tue, 3 May 2022 19:30:11 +0100 Subject: [PATCH 41/42] fix compile --- frame/bags-list/src/list/mod.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frame/bags-list/src/list/mod.rs b/frame/bags-list/src/list/mod.rs index 825758698b419..0ba2a9c503b9d 100644 --- a/frame/bags-list/src/list/mod.rs +++ b/frame/bags-list/src/list/mod.rs @@ -892,7 +892,7 @@ impl, I: 'static> Node { &self.id } - #[cfg(feature = "runtime-benchmarks")] + #[cfg(any(feature = "runtime-benchmarks", test))] pub fn set_score(&mut self, s: T::Score) { self.score = s } From 518ffb365ded7914d5d9bd2244f910beae718e2e Mon Sep 17 00:00:00 2001 From: Shawn Tabrizi Date: Wed, 4 May 2022 17:13:34 +0100 Subject: [PATCH 42/42] fix warning --- frame/bags-list/src/list/tests.rs | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/frame/bags-list/src/list/tests.rs b/frame/bags-list/src/list/tests.rs index a5e8a499d5efd..23458aa173d73 100644 --- a/frame/bags-list/src/list/tests.rs +++ b/frame/bags-list/src/list/tests.rs @@ -21,7 +21,7 @@ use crate::{ ListBags, ListNodes, }; use frame_election_provider_support::{SortedListProvider, VoteWeight}; -use frame_support::{assert_noop, assert_ok, assert_storage_noop}; +use frame_support::{assert_ok, assert_storage_noop}; fn node( id: AccountId, @@ -248,10 +248,7 @@ mod list { assert!(get_list_as_ids().contains(&3)); // then - assert_storage_noop!(assert_eq!( - List::::insert(3, 20).unwrap_err(), - ListError::Duplicate - )); + assert_noop!(List::::insert(3, 20), ListError::Duplicate); }); }