diff --git a/bin/node/runtime/src/lib.rs b/bin/node/runtime/src/lib.rs index 2f0efcaa56740..a25e663feeb13 100644 --- a/bin/node/runtime/src/lib.rs +++ b/bin/node/runtime/src/lib.rs @@ -568,7 +568,9 @@ impl pallet_staking::Config for Runtime { type OffendingValidatorsThreshold = OffendingValidatorsThreshold; type ElectionProvider = ElectionProviderMultiPhase; type GenesisElectionProvider = onchain::UnboundedExecution; - type VoterList = BagsList; + type VoterList = VoterBagsList; + // This a placeholder, to be introduced in the next PR as an instance of bags-list + type TargetList = pallet_staking::UseValidatorsMap; type MaxUnlockingChunks = ConstU32<32>; type OnStakerSlash = NominationPools; type WeightInfo = pallet_staking::weights::SubstrateWeight; @@ -724,12 +726,15 @@ parameter_types! { pub const BagThresholds: &'static [u64] = &voter_bags::THRESHOLDS; } -impl pallet_bags_list::Config for Runtime { +type VoterBagsListInstance = pallet_bags_list::Instance1; +impl pallet_bags_list::Config for Runtime { type RuntimeEvent = RuntimeEvent; + /// 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 WeightInfo = pallet_bags_list::weights::SubstrateWeight; type BagThresholds = BagThresholds; type Score = VoteWeight; + type WeightInfo = pallet_bags_list::weights::SubstrateWeight; } parameter_types! { @@ -1636,7 +1641,7 @@ construct_runtime!( Gilt: pallet_gilt, Uniques: pallet_uniques, TransactionStorage: pallet_transaction_storage, - BagsList: pallet_bags_list, + VoterBagsList: pallet_bags_list::, StateTrieMigration: pallet_state_trie_migration, ChildBounties: pallet_child_bounties, Referenda: pallet_referenda, @@ -1722,7 +1727,7 @@ mod benches { [pallet_alliance, Alliance] [pallet_assets, Assets] [pallet_babe, Babe] - [pallet_bags_list, BagsList] + [pallet_bags_list, VoterBagsList] [pallet_balances, Balances] [pallet_bounties, Bounties] [pallet_child_bounties, ChildBounties] diff --git a/bin/node/runtime/src/voter_bags.rs b/bin/node/runtime/src/voter_bags.rs index 93790f028f457..eb540c27abcc7 100644 --- a/bin/node/runtime/src/voter_bags.rs +++ b/bin/node/runtime/src/voter_bags.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2021-2022 Parity Technologies (UK) Ltd. +// Copyright (C) 2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -15,9 +15,12 @@ // See the License for the specific language governing permissions and // limitations under the License. -//! Autogenerated voter bag thresholds. +//! Autogenerated bag thresholds. //! -//! Generated on 2021-07-05T09:17:40.469754927+00:00 +//! Generated on 2022-08-15T19:26:59.939787+00:00 +//! Arguments +//! Total issuance: 100000000000000 +//! Minimum balance: 100000000000000 //! for the node runtime. /// Existential weight for this runtime. diff --git a/frame/babe/src/mock.rs b/frame/babe/src/mock.rs index 0ac8214f45c23..fb251457db9ca 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/remote-tests/src/lib.rs b/frame/bags-list/remote-tests/src/lib.rs index 927c3dc91cb58..fc25e3b65ddb1 100644 --- a/frame/bags-list/remote-tests/src/lib.rs +++ b/frame/bags-list/remote-tests/src/lib.rs @@ -18,6 +18,7 @@ //! Utilities for remote-testing pallet-bags-list. use frame_election_provider_support::ScoreProvider; +use pallet_bags_list::Instance1; use sp_std::prelude::*; /// A common log target to use. @@ -30,18 +31,26 @@ pub mod try_state; /// A wrapper for a runtime that the functions of this crate expect. /// /// For example, this can be the `Runtime` type of the Polkadot runtime. -pub trait RuntimeT: - pallet_staking::Config + pallet_bags_list::Config + frame_system::Config +pub trait RuntimeT: + pallet_staking::Config + pallet_bags_list::Config + frame_system::Config +{ +} +impl< + I: 'static, + T: pallet_staking::Config + pallet_bags_list::Config + frame_system::Config, + > RuntimeT for T { } -impl RuntimeT for T {} fn percent(portion: u32, total: u32) -> f64 { (portion as f64 / total as f64) * 100f64 } /// Display the number of nodes in each bag, while identifying those that need a rebag. -pub fn display_and_check_bags(currency_unit: u64, currency_name: &'static str) { +pub fn display_and_check_bags>( + currency_unit: u64, + currency_name: &'static str, +) { use frame_election_provider_support::SortedListProvider; use frame_support::traits::Get; @@ -55,7 +64,8 @@ pub fn display_and_check_bags(currency_unit: u64, currency_na let mut seen_in_bags = 0; let mut rebaggable = 0; let mut active_bags = 0; - for vote_weight_thresh in ::BagThresholds::get() { + for vote_weight_thresh in >::BagThresholds::get() + { let vote_weight_thresh_u64: u64 = (*vote_weight_thresh) .try_into() .map_err(|_| "runtime must configure score to at most u64 to use this test") @@ -64,7 +74,9 @@ pub fn display_and_check_bags(currency_unit: u64, currency_na let vote_weight_thresh_as_unit = vote_weight_thresh_u64 as f64 / currency_unit as f64; let pretty_thresh = format!("Threshold: {}. {}", vote_weight_thresh_as_unit, currency_name); - let bag = match pallet_bags_list::Pallet::::list_bags_get(*vote_weight_thresh) { + let bag = match pallet_bags_list::Pallet::::list_bags_get( + *vote_weight_thresh, + ) { Some(bag) => bag, None => { log::info!(target: LOG_TARGET, "{} NO VOTERS.", pretty_thresh); @@ -75,7 +87,8 @@ pub fn display_and_check_bags(currency_unit: u64, currency_na active_bags += 1; for id in bag.std_iter().map(|node| node.std_id().clone()) { - let vote_weight = ::ScoreProvider::score(&id); + let vote_weight = + >::ScoreProvider::score(&id); let vote_weight_thresh_u64: u64 = (*vote_weight_thresh) .try_into() .map_err(|_| "runtime must configure score to at most u64 to use this test") @@ -92,8 +105,8 @@ pub fn display_and_check_bags(currency_unit: u64, currency_na ); } - let node = - pallet_bags_list::Node::::get(&id).expect("node in bag must exist."); + let node = pallet_bags_list::Node::::get(&id) + .expect("node in bag must exist."); if node.is_misplaced(vote_weight) { rebaggable += 1; let notional_bag = pallet_bags_list::notional_bag_for::(vote_weight); @@ -141,7 +154,7 @@ pub fn display_and_check_bags(currency_unit: u64, currency_na "a total of {} nodes are in {} active bags [{} total bags], {} of which can be rebagged.", voter_list_count, active_bags, - ::BagThresholds::get().len(), + >::BagThresholds::get().len(), rebaggable, ); } diff --git a/frame/bags-list/remote-tests/src/migration.rs b/frame/bags-list/remote-tests/src/migration.rs index c4cd73c45d377..675dfbe072670 100644 --- a/frame/bags-list/remote-tests/src/migration.rs +++ b/frame/bags-list/remote-tests/src/migration.rs @@ -24,7 +24,10 @@ use sp_runtime::{traits::Block as BlockT, DeserializeOwned}; /// Test voter bags migration. `currency_unit` is the number of planks per the the runtimes `UNITS` /// (i.e. number of decimal places per DOT, KSM etc) -pub async fn execute( +pub async fn execute< + Runtime: RuntimeT, + Block: BlockT + DeserializeOwned, +>( currency_unit: u64, currency_name: &'static str, ws_url: String, diff --git a/frame/bags-list/remote-tests/src/snapshot.rs b/frame/bags-list/remote-tests/src/snapshot.rs index 408f5f2bd8aa2..655de10c4af9b 100644 --- a/frame/bags-list/remote-tests/src/snapshot.rs +++ b/frame/bags-list/remote-tests/src/snapshot.rs @@ -22,7 +22,10 @@ use remote_externalities::{Builder, Mode, OnlineConfig}; use sp_runtime::{traits::Block as BlockT, DeserializeOwned}; /// Execute create a snapshot from pallet-staking. -pub async fn execute( +pub async fn execute< + Runtime: crate::RuntimeT, + Block: BlockT + DeserializeOwned, +>( voter_limit: Option, currency_unit: u64, ws_url: String, @@ -34,7 +37,8 @@ pub async fn execute transport: ws_url.to_string().into(), // NOTE: we don't scrape pallet-staking, this kinda ensures that the source of the data // is bags-list. - pallets: vec![pallet_bags_list::Pallet::::name().to_string()], + pallets: vec![pallet_bags_list::Pallet::::name() + .to_string()], at: None, ..Default::default() })) diff --git a/frame/bags-list/remote-tests/src/try_state.rs b/frame/bags-list/remote-tests/src/try_state.rs index 11278c20eb8ed..9817ef4ceb9e4 100644 --- a/frame/bags-list/remote-tests/src/try_state.rs +++ b/frame/bags-list/remote-tests/src/try_state.rs @@ -25,7 +25,10 @@ use remote_externalities::{Builder, Mode, OnlineConfig}; use sp_runtime::{traits::Block as BlockT, DeserializeOwned}; /// Execute the sanity check of the bags-list. -pub async fn execute( +pub async fn execute< + Runtime: crate::RuntimeT, + Block: BlockT + DeserializeOwned, +>( currency_unit: u64, currency_name: &'static str, ws_url: String, @@ -33,7 +36,8 @@ pub async fn execute let mut ext = Builder::::new() .mode(Mode::Online(OnlineConfig { transport: ws_url.to_string().into(), - pallets: vec![pallet_bags_list::Pallet::::name().to_string()], + pallets: vec![pallet_bags_list::Pallet::::name() + .to_string()], ..Default::default() })) .inject_hashed_prefix(&>::prefix_hash()) @@ -44,7 +48,7 @@ pub async fn execute ext.execute_with(|| { sp_core::crypto::set_default_ss58_version(Runtime::SS58Prefix::get().try_into().unwrap()); - pallet_bags_list::Pallet::::try_state().unwrap(); + pallet_bags_list::Pallet::::try_state().unwrap(); log::info!(target: crate::LOG_TARGET, "executed bags-list sanity check with no errors."); crate::display_and_check_bags::(currency_unit, currency_name); diff --git a/frame/grandpa/src/mock.rs b/frame/grandpa/src/mock.rs index 5aae751de9ea6..19caf0766a3e0 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/lib.rs b/frame/nomination-pools/benchmarking/src/lib.rs index 07d5c63493ef8..6c8672b714335 100644 --- a/frame/nomination-pools/benchmarking/src/lib.rs +++ b/frame/nomination-pools/benchmarking/src/lib.rs @@ -41,8 +41,11 @@ type CurrencyOf = ::Currency; const USER_SEED: u32 = 0; const MAX_SPANS: u32 = 100; +type VoterBagsListInstance = pallet_bags_list::Instance1; pub trait Config: - pallet_nomination_pools::Config + pallet_staking::Config + pallet_bags_list::Config + pallet_nomination_pools::Config + + pallet_staking::Config + + pallet_bags_list::Config { } diff --git a/frame/nomination-pools/benchmarking/src/mock.rs b/frame/nomination-pools/benchmarking/src/mock.rs index aad38ee8475d2..2dcac34d39a11 100644 --- a/frame/nomination-pools/benchmarking/src/mock.rs +++ b/frame/nomination-pools/benchmarking/src/mock.rs @@ -15,6 +15,7 @@ // See the License for the specific language governing permissions and // limitations under the License. +use crate::VoterBagsListInstance; use frame_election_provider_support::VoteWeight; use frame_support::{pallet_prelude::*, parameter_types, traits::ConstU64, PalletId}; use sp_runtime::{ @@ -111,7 +112,8 @@ impl pallet_staking::Config for Runtime { type ElectionProvider = frame_election_provider_support::NoElection<(AccountId, BlockNumber, Staking)>; type GenesisElectionProvider = Self::ElectionProvider; - type VoterList = pallet_bags_list::Pallet; + type VoterList = VoterList; + type TargetList = pallet_staking::UseValidatorsMap; type MaxUnlockingChunks = ConstU32<32>; type OnStakerSlash = Pools; type BenchmarkingConfig = pallet_staking::TestBenchmarkingConfig; @@ -122,7 +124,7 @@ parameter_types! { pub static BagThresholds: &'static [VoteWeight] = &[10, 20, 30, 40, 50, 60, 1_000, 2_000, 10_000]; } -impl pallet_bags_list::Config for Runtime { +impl pallet_bags_list::Config for Runtime { type RuntimeEvent = RuntimeEvent; type WeightInfo = (); type BagThresholds = BagThresholds; @@ -180,7 +182,7 @@ frame_support::construct_runtime!( Timestamp: pallet_timestamp::{Pallet, Call, Storage, Inherent}, Balances: pallet_balances::{Pallet, Call, Storage, Config, Event}, Staking: pallet_staking::{Pallet, Call, Config, Storage, Event}, - BagsList: pallet_bags_list::{Pallet, Call, Storage, Event}, + VoterList: pallet_bags_list::::{Pallet, Call, Storage, Event}, Pools: pallet_nomination_pools::{Pallet, Call, Storage, Event}, } ); diff --git a/frame/nomination-pools/test-staking/src/mock.rs b/frame/nomination-pools/test-staking/src/mock.rs index d616ee1ffc180..cb63ad04d1a38 100644 --- a/frame/nomination-pools/test-staking/src/mock.rs +++ b/frame/nomination-pools/test-staking/src/mock.rs @@ -126,7 +126,8 @@ impl pallet_staking::Config for Runtime { type ElectionProvider = frame_election_provider_support::NoElection<(AccountId, BlockNumber, Staking)>; type GenesisElectionProvider = Self::ElectionProvider; - type VoterList = pallet_bags_list::Pallet; + type VoterList = VoterList; + type TargetList = pallet_staking::UseValidatorsMap; type MaxUnlockingChunks = ConstU32<32>; type OnStakerSlash = Pools; type BenchmarkingConfig = pallet_staking::TestBenchmarkingConfig; @@ -137,7 +138,8 @@ parameter_types! { pub static BagThresholds: &'static [VoteWeight] = &[10, 20, 30, 40, 50, 60, 1_000, 2_000, 10_000]; } -impl pallet_bags_list::Config for Runtime { +type VoterBagsListInstance = pallet_bags_list::Instance1; +impl pallet_bags_list::Config for Runtime { type RuntimeEvent = RuntimeEvent; type WeightInfo = (); type BagThresholds = BagThresholds; @@ -193,7 +195,7 @@ frame_support::construct_runtime!( Timestamp: pallet_timestamp::{Pallet, Call, Storage, Inherent}, Balances: pallet_balances::{Pallet, Call, Storage, Config, Event}, Staking: pallet_staking::{Pallet, Call, Config, Storage, Event}, - BagsList: pallet_bags_list::{Pallet, Call, Storage, Event}, + VoterList: pallet_bags_list::::{Pallet, Call, Storage, Event}, Pools: pallet_nomination_pools::{Pallet, Call, Storage, Event}, } ); diff --git a/frame/offences/benchmarking/src/mock.rs b/frame/offences/benchmarking/src/mock.rs index 3483f5ec240a5..43c248a09a0b8 100644 --- a/frame/offences/benchmarking/src/mock.rs +++ b/frame/offences/benchmarking/src/mock.rs @@ -180,6 +180,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 01be3605601f4..de2f4ae23f423 100644 --- a/frame/session/benchmarking/src/mock.rs +++ b/frame/session/benchmarking/src/mock.rs @@ -175,6 +175,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/benchmarking.rs b/frame/staking/src/benchmarking.rs index 2d5943b51758d..a79eac3c4dc46 100644 --- a/frame/staking/src/benchmarking.rs +++ b/frame/staking/src/benchmarking.rs @@ -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); } diff --git a/frame/staking/src/lib.rs b/frame/staking/src/lib.rs index 4a1f0cb886ed0..4ff9e99306727 100644 --- a/frame/staking/src/lib.rs +++ b/frame/staking/src/lib.rs @@ -886,11 +886,12 @@ enum Releases { V8_0_0, // populate `VoterList`. V9_0_0, // inject validators into `VoterList` as well. V10_0_0, // remove `EarliestUnappliedSlash`. + V11_0_0, // Move pallet storage prefix, e.g. BagsList -> VoterBagsList } impl Default for Releases { fn default() -> Self { - Releases::V10_0_0 + Releases::V11_0_0 } } diff --git a/frame/staking/src/migrations.rs b/frame/staking/src/migrations.rs index ff01e1fe3b09b..2556f6d989e1b 100644 --- a/frame/staking/src/migrations.rs +++ b/frame/staking/src/migrations.rs @@ -20,6 +20,99 @@ use super::*; use frame_election_provider_support::SortedListProvider; use frame_support::traits::OnRuntimeUpgrade; +pub mod v11 { + use super::*; + use frame_support::{ + storage::migration::move_pallet, + traits::{GetStorageVersion, PalletInfoAccess}, + }; + #[cfg(feature = "try-runtime")] + use sp_io::hashing::twox_128; + + pub struct MigrateToV11(sp_std::marker::PhantomData<(T, P, N)>); + impl> OnRuntimeUpgrade + for MigrateToV11 + { + #[cfg(feature = "try-runtime")] + fn pre_upgrade() -> Result<(), &'static str> { + frame_support::ensure!( + StorageVersion::::get() == crate::Releases::V10_0_0, + "must upgrade linearly" + ); + let old_pallet_prefix = twox_128(N::get().as_bytes()); + + frame_support::ensure!( + sp_io::storage::next_key(&old_pallet_prefix).is_some(), + "no data for the old pallet name has been detected" + ); + + Ok(()) + } + + /// Migrate the entire storage of this pallet to a new prefix. + /// + /// This new prefix must be the same as the one set in construct_runtime. For safety, use + /// `PalletInfo` to get it, as: + /// `::PalletInfo::name::`. + /// + /// The migration will look into the storage version in order to avoid triggering a + /// migration on an up to date storage. + fn on_runtime_upgrade() -> Weight { + let old_pallet_name = N::get(); + let new_pallet_name =

::name(); + + if StorageVersion::::get() == Releases::V10_0_0 { + // bump version anyway, even if we don't need to move the prefix + StorageVersion::::put(Releases::V11_0_0); + if new_pallet_name == old_pallet_name { + log!( + warn, + "new bags-list name is equal to the old one, only bumping the version" + ); + return T::DbWeight::get().reads(1).saturating_add(T::DbWeight::get().writes(1)) + } + + move_pallet(old_pallet_name.as_bytes(), new_pallet_name.as_bytes()); + ::BlockWeights::get().max_block + } else { + log!(warn, "v11::migrate should be removed."); + T::DbWeight::get().reads(1) + } + } + + #[cfg(feature = "try-runtime")] + fn post_upgrade() -> Result<(), &'static str> { + frame_support::ensure!( + StorageVersion::::get() == crate::Releases::V11_0_0, + "wrong version after the upgrade" + ); + + let old_pallet_name = N::get(); + let new_pallet_name =

::name(); + + // skip storage prefix checks for the same pallet names + if new_pallet_name == old_pallet_name { + return Ok(()) + } + + let old_pallet_prefix = twox_128(N::get().as_bytes()); + frame_support::ensure!( + sp_io::storage::next_key(&old_pallet_prefix).is_none(), + "old pallet data hasn't been removed" + ); + + let new_pallet_name =

::name(); + let new_pallet_prefix = twox_128(new_pallet_name.as_bytes()); + frame_support::ensure!( + sp_io::storage::next_key(&new_pallet_prefix).is_some(), + "new pallet data hasn't been created" + ); + + Ok(()) + } + } +} + pub mod v10 { use super::*; use frame_support::storage_alias; diff --git a/frame/staking/src/mock.rs b/frame/staking/src/mock.rs index b8f6254d9a8e8..5980e6b40d15d 100644 --- a/frame/staking/src/mock.rs +++ b/frame/staking/src/mock.rs @@ -99,7 +99,7 @@ frame_support::construct_runtime!( Staking: pallet_staking::{Pallet, Call, Config, Storage, Event}, Session: pallet_session::{Pallet, Call, Storage, Event, Config}, Historical: pallet_session::historical::{Pallet, Storage}, - BagsList: pallet_bags_list::{Pallet, Call, Storage, Event}, + VoterBagsList: pallet_bags_list::::{Pallet, Call, Storage, Event}, } ); @@ -240,9 +240,11 @@ parameter_types! { pub static LedgerSlashPerEra: (BalanceOf, BTreeMap>) = (Zero::zero(), BTreeMap::new()); } -impl pallet_bags_list::Config for Test { +type VoterBagsListInstance = pallet_bags_list::Instance1; +impl pallet_bags_list::Config for Test { type RuntimeEvent = RuntimeEvent; type WeightInfo = (); + // Staking is the source of truth for voter bags list, since they are not kept up to date. type ScoreProvider = Staking; type BagThresholds = BagThresholds; type Score = VoteWeight; @@ -296,7 +298,8 @@ impl crate::pallet::pallet::Config for Test { type ElectionProvider = onchain::UnboundedExecution; type GenesisElectionProvider = Self::ElectionProvider; // NOTE: consider a macro and use `UseNominatorsAndValidatorsMap` as well. - type VoterList = BagsList; + type VoterList = VoterBagsList; + type TargetList = UseValidatorsMap; 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 13038527bc2f8..b7c30cf16db94 100644 --- a/frame/staking/src/pallet/impls.rs +++ b/frame/staking/src/pallet/impls.rs @@ -756,18 +756,32 @@ 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 - }) - .collect::>(); + pub fn get_npos_targets(maybe_max_len: Option) -> Vec { + 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; + + 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(validator_count)); + 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, @@ -899,7 +913,7 @@ impl ElectionDataProvider for Pallet { return Err("Target snapshot too big") } - Ok(Self::get_npos_targets()) + Ok(Self::get_npos_targets(None)) } fn next_election_prediction(now: T::BlockNumber) -> T::BlockNumber { @@ -1306,6 +1320,65 @@ impl ScoreProvider for Pallet { } } +/// A simple sorted list implementation that does not require any additional pallets. Note, this +/// does not provide validators in sorted order. 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 Score = BalanceOf; + 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 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(|(n, _)| n))) + } 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::::weight_of(id).into()) + } + 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 try_state() -> Result<(), &'static str> { + Ok(()) + } + fn unsafe_clear() { + #[allow(deprecated)] + Validators::::remove_all(); + } +} + /// 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]. @@ -1453,6 +1526,10 @@ impl StakingInterface for Pallet { #[cfg(feature = "try-runtime")] impl Pallet { pub(crate) fn do_try_state(_: BlockNumberFor) -> Result<(), &'static str> { + ensure!( + T::VoterList::iter().all(|x| >::contains_key(&x)), + "VoterList contains non-nominators" + ); T::VoterList::try_state()?; Self::check_nominators()?; Self::check_exposures()?; diff --git a/frame/staking/src/pallet/mod.rs b/frame/staking/src/pallet/mod.rs index 1b38f236fc6f3..b8a457beb5eae 100644 --- a/frame/staking/src/pallet/mod.rs +++ b/frame/staking/src/pallet/mod.rs @@ -184,8 +184,36 @@ 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; + /// WIP: This is a noop as of now, the actual business logic that's described below is going + /// to be introduced in a follow-up PR. + /// + /// 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. 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 `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. + /// + /// 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>; + /// The maximum number of `unlocking` chunks a [`StakingLedger`] can have. Effectively /// determines how many unique eras a staker may be unbonding in. #[pallet::constant] diff --git a/utils/frame/generate-bags/src/lib.rs b/utils/frame/generate-bags/src/lib.rs index d4507c3be33ef..23da131a668d8 100644 --- a/utils/frame/generate-bags/src/lib.rs +++ b/utils/frame/generate-bags/src/lib.rs @@ -207,6 +207,10 @@ pub fn generate_thresholds( writeln!(buf, "//! Autogenerated bag thresholds.")?; writeln!(buf, "//!")?; writeln!(buf, "//! Generated on {}", now.to_rfc3339())?; + writeln!(buf, "//! Arguments")?; + writeln!(buf, "//! Total issuance: {}", &total_issuance)?; + writeln!(buf, "//! Minimum balance: {}", &minimum_balance)?; + writeln!( buf, "//! for the {} runtime.", @@ -234,6 +238,17 @@ pub fn generate_thresholds( writeln!(buf)?; writeln!(buf, "/// Upper thresholds delimiting the bag list.")?; writeln!(buf, "pub const THRESHOLDS: [u64; {}] = [", thresholds.len())?; + for threshold in &thresholds { + num_buf.write_formatted(threshold, &format); + // u64::MAX, with spacers every 3 digits, is 26 characters wide + writeln!(buf, " {:>26},", num_buf.as_str())?; + } + writeln!(buf, "];")?; + + // thresholds balance + writeln!(buf)?; + writeln!(buf, "/// Upper thresholds delimiting the bag list.")?; + writeln!(buf, "pub const THRESHOLDS_BALANCES: [u128; {}] = [", thresholds.len())?; for threshold in thresholds { num_buf.write_formatted(&threshold, &format); // u64::MAX, with spacers every 3 digits, is 26 characters wide