Skip to content
This repository was archived by the owner on Nov 15, 2023. It is now read-only.

Multi-block election part 0: Paginated election data provider #11105

Closed
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
28 commits
Select commit Hold shift + click to select a range
500bc9d
Implement the new validator-in-bags-list scenario + migration
kianenigma Feb 8, 2022
ddcc81b
Apply suggestions from code review
kianenigma Feb 15, 2022
41540f2
some review comments
kianenigma Feb 15, 2022
8ba2563
Merge branch 'kiz-validators-in-bags' of github.com:paritytech/substr…
kianenigma Feb 15, 2022
c293a0c
Merge branch 'master' of github.com:paritytech/substrate into kiz-val…
kianenigma Feb 16, 2022
398cf70
guard the migration
kianenigma Feb 16, 2022
66fc7d2
some review comments
kianenigma Feb 16, 2022
75989de
Fix tests 🤦‍♂️
kianenigma Feb 16, 2022
db2a7b8
Fix build
kianenigma Feb 16, 2022
8f74872
Merge branch 'master' of github.com:paritytech/substrate into kiz-val…
kianenigma Feb 28, 2022
7a55fad
Master.into()
kianenigma Mar 8, 2022
1ac8c34
Master.into()
kianenigma Mar 9, 2022
0ce73d5
Merge branch 'master' of github.com:paritytech/substrate into kiz-val…
kianenigma Mar 11, 2022
f892d04
fix weight_of_fn
kianenigma Mar 11, 2022
5640da7
reformat line width
kianenigma Mar 11, 2022
3aefa00
make const
kianenigma Mar 11, 2022
0923d5b
use weight of fn cached
kianenigma Mar 11, 2022
2ea0769
SortedListProvider -> VoterList
kianenigma Mar 11, 2022
4c9ef4b
Fix all build and docs
kianenigma Mar 11, 2022
b311d03
check post migration
kianenigma Mar 11, 2022
2b2ff9b
Master.into()
kianenigma Mar 20, 2022
8dcc94c
get everything going
kianenigma Mar 23, 2022
288848c
Master.into()
kianenigma Mar 23, 2022
fb556a0
Master.into()
kianenigma Apr 19, 2022
0d8b1b3
a good round of self-review
kianenigma Apr 19, 2022
56b02c6
remote tests
kianenigma Apr 19, 2022
bc5ca19
fix
kianenigma Apr 19, 2022
5ad46cc
remove todos
kianenigma Apr 19, 2022
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
356 changes: 179 additions & 177 deletions Cargo.lock

Large diffs are not rendered by default.

1 change: 1 addition & 0 deletions bin/node/runtime/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -559,6 +559,7 @@ impl pallet_staking::Config for Runtime {
type ElectionProvider = ElectionProviderMultiPhase;
type GenesisElectionProvider = onchain::UnboundedExecution<OnChainSeqPhragmen>;
type VoterList = BagsList;
type Pages = ConstU32<1>;
type MaxUnlockingChunks = ConstU32<32>;
type WeightInfo = pallet_staking::weights::SubstrateWeight<Runtime>;
type BenchmarkingConfig = StakingBenchmarkingConfig;
Expand Down
1 change: 1 addition & 0 deletions frame/babe/src/mock.rs
Original file line number Diff line number Diff line change
Expand Up @@ -186,6 +186,7 @@ impl pallet_staking::Config for Test {
type CurrencyToVote = frame_support::traits::SaturatingCurrencyToVote;
type Event = Event;
type Currency = Balances;
type Pages = ConstU32<1>;
type Slash = ();
type Reward = ();
type SessionsPerEra = SessionsPerEra;
Expand Down
69 changes: 40 additions & 29 deletions frame/bags-list/remote-tests/src/snapshot.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,19 +16,19 @@

//! Test to execute the snapshot using the voter bag.

use frame_election_provider_support::SortedListProvider;
use frame_support::traits::PalletInfoAccess;
use frame_election_provider_support::{ElectionDataProvider, SortedListProvider};
use frame_support::{storage::generator::StorageMap, traits::PalletInfoAccess};
use pallet_staking::Pallet as Staking;
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<Runtime: crate::RuntimeT, Block: BlockT + DeserializeOwned>(
voter_limit: Option<usize>,
page_limit: Option<usize>,
pages: usize,
currency_unit: u64,
ws_url: String,
) {
use frame_support::storage::generator::StorageMap;

let mut ext = Builder::<Block>::new()
.mode(Mode::Online(OnlineConfig {
transport: ws_url.to_string().into(),
Expand All @@ -49,38 +49,49 @@ pub async fn execute<Runtime: crate::RuntimeT, Block: BlockT + DeserializeOwned>
.unwrap();

ext.execute_with(|| {
use frame_election_provider_support::ElectionDataProvider;
log::info!(
target: crate::LOG_TARGET,
"{} nodes in bags list.",
<Runtime as pallet_staking::Config>::VoterList::count(),
);
log::info!(
target: crate::LOG_TARGET,
"generating {} pages of snapshot, ranging {:?}",
pages,
<Staking<Runtime>>::range(pages as u32).collect::<Vec<_>>(),
);

assert!(Staking::<Runtime>::last_iterated_nominator().is_none());

let voters =
<pallet_staking::Pallet<Runtime> as ElectionDataProvider>::electing_voters(voter_limit)
for page in <Staking<Runtime>>::range(pages as u32) {
let page_voters =
<pallet_staking::Pallet<Runtime> as ElectionDataProvider>::electing_voters_paged(
page_limit, page,
)
.unwrap();

let mut voters_nominator_only = voters
.iter()
.filter(|(v, _, _)| pallet_staking::Nominators::<Runtime>::contains_key(v))
.cloned()
.collect::<Vec<_>>();
voters_nominator_only.sort_by_key(|(_, w, _)| *w);
let currency_unit = currency_unit as f64;
let min_voter =
page_voters.last().map(|(x, y, _)| (x.clone(), *y as f64 / currency_unit));
let max_voter =
page_voters.first().map(|(x, y, _)| (x.clone(), *y as f64 / currency_unit));
assert!(
page == 0 ||
Staking::<Runtime>::last_iterated_nominator() ==
page_voters.last().map(|(x, _, _)| x.clone()),
);

let currency_unit = currency_unit as f64;
let min_voter = voters_nominator_only
.first()
.map(|(x, y, _)| (x.clone(), *y as f64 / currency_unit));
let max_voter = voters_nominator_only
.last()
.map(|(x, y, _)| (x.clone(), *y as f64 / currency_unit));
log::info!(
target: crate::LOG_TARGET,
"a snapshot with limit {:?} has been created, {} voters are taken. min nominator: {:?}, max: {:?}",
voter_limit,
voters.len(),
min_voter,
max_voter
);
log::info!(
target: crate::LOG_TARGET,
"took snapshot page {:?} with limit {:?}, {} voters are taken. min voter: {:?}, max: {:?}",
page,
page_limit,
page_voters.len(),
min_voter,
max_voter
);
}

assert!(Staking::<Runtime>::last_iterated_nominator().is_none());
});
}
1 change: 0 additions & 1 deletion frame/bags-list/src/list/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -255,7 +255,6 @@ impl<T: Config<I>, I: 'static> List<T, I> {
// We chain two iterators:
// 1. from the given `start` till the end of the bag
// 2. all the bags that come after `start`'s bag.

let start_node = Node::<T, I>::get(start).ok_or(ListError::NodeNotFound)?;
let start_node_upper = start_node.bag_upper;
let start_bag = sp_std::iter::successors(start_node.next(), |prev| prev.next());
Expand Down
13 changes: 10 additions & 3 deletions frame/election-provider-multi-phase/src/mock.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@
use super::*;
use crate as multi_phase;
use frame_election_provider_support::{
data_provider, onchain, ElectionDataProvider, NposSolution, SequentialPhragmen,
data_provider, onchain, ElectionDataProvider, NposSolution, PageIndex, SequentialPhragmen,
};
pub use frame_support::{assert_noop, assert_ok};
use frame_support::{
Expand Down Expand Up @@ -461,8 +461,13 @@ impl ElectionDataProvider for StakingMock {
type AccountId = AccountId;
type BlockNumber = u64;
type MaxVotesPerVoter = MaxNominations;
type Pages = ConstU32<1>;

fn electable_targets(maybe_max_len: Option<usize>) -> data_provider::Result<Vec<AccountId>> {
fn electable_targets_paged(
maybe_max_len: Option<usize>,
remaining: PageIndex,
) -> data_provider::Result<Vec<AccountId>> {
assert_eq!(remaining, 0, "this pallet only works with single page snapshots");
let targets = Targets::get();

if maybe_max_len.map_or(false, |max_len| targets.len() > max_len) {
Expand All @@ -472,9 +477,11 @@ impl ElectionDataProvider for StakingMock {
Ok(targets)
}

fn electing_voters(
fn electing_voters_paged(
maybe_max_len: Option<usize>,
remaining: PageIndex,
) -> data_provider::Result<Vec<VoterOf<Runtime>>> {
assert_eq!(remaining, 0, "this pallet only works with single page snapshots");
let mut voters = Voters::get();
if let Some(max_len) = maybe_max_len {
voters.truncate(max_len)
Expand Down
1 change: 1 addition & 0 deletions frame/election-provider-support/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ sp-runtime = { version = "6.0.0", default-features = false, path = "../../primit
frame-support = { version = "4.0.0-dev", default-features = false, path = "../support" }
frame-system = { version = "4.0.0-dev", default-features = false, path = "../system" }
frame-election-provider-solution-type = { version = "4.0.0-dev", path = "solution-type" }
log = { version = "0.4.16" }

[dev-dependencies]
rand = "0.7.3"
Expand Down
68 changes: 61 additions & 7 deletions frame/election-provider-support/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -103,16 +103,17 @@
//! type AccountId = AccountId;
//! type BlockNumber = BlockNumber;
//! type MaxVotesPerVoter = ConstU32<1>;
//! type Pages = ConstU32<1>;
//!
//! fn desired_targets() -> data_provider::Result<u32> {
//! Ok(1)
//! }
//! fn electing_voters(maybe_max_len: Option<usize>)
//! fn electing_voters_paged(maybe_max_len: Option<usize>, _: PageIndex)
//! -> data_provider::Result<Vec<VoterOf<Self>>>
//! {
//! Ok(Default::default())
//! }
//! fn electable_targets(maybe_max_len: Option<usize>) -> data_provider::Result<Vec<AccountId>> {
//! fn electable_targets_paged(maybe_max_len: Option<usize>, _: PageIndex) -> data_provider::Result<Vec<AccountId>> {
//! Ok(vec![10, 20, 30])
//! }
//! fn next_election_prediction(now: BlockNumber) -> BlockNumber {
Expand Down Expand Up @@ -170,13 +171,14 @@ pub mod onchain;
pub mod traits;
#[cfg(feature = "std")]
use codec::{Decode, Encode};
use frame_support::{weights::Weight, BoundedVec, RuntimeDebug};
use frame_support::{traits::DefensiveSaturating, weights::Weight, BoundedVec, RuntimeDebug};
use sp_runtime::traits::Bounded;
use sp_std::{fmt::Debug, prelude::*};

/// Re-export the solution generation macro.
pub use frame_election_provider_solution_type::generate_solution_type;
pub use frame_support::traits::Get;

/// Re-export some type as they are used in the interface.
pub use sp_arithmetic::PerThing;
pub use sp_npos_elections::{
Expand All @@ -185,6 +187,13 @@ pub use sp_npos_elections::{
};
pub use traits::NposSolution;

/// The index used to indicate the page number.
///
/// A u8 would have probably been enough until the end of universe, but since we use this as the
/// bound of `BoundedVec` and similar, using `u32` will save us some hassle.
// TODO: https://github.com/paritytech/substrate/issues/11076
pub type PageIndex = u32;

// re-export for the solution macro, with the dependencies of the macro.
#[doc(hidden)]
pub use codec;
Expand Down Expand Up @@ -275,6 +284,12 @@ pub trait ElectionDataProvider {
/// Maximum number of votes per voter that this data provider is providing.
type MaxVotesPerVoter: Get<u32>;

/// The maximum number of pages that this data provider is expected to be able to provide. An
/// implementation could use this to verify the value of `remaining: PageIndex`.
// NOTE: this should maybe be a generic, because a single type might want implement it with
// different bounds.
type Pages: Get<PageIndex>;

/// All possible targets for the election, i.e. the targets that could become elected, thus
/// "electable".
///
Expand All @@ -283,8 +298,9 @@ pub trait ElectionDataProvider {
///
/// This should be implemented as a self-weighing function. The implementor should register its
/// appropriate weight at the end of execution with the system pallet directly.
fn electable_targets(
fn electable_targets_paged(
maybe_max_len: Option<usize>,
remaining_pages: PageIndex,
) -> data_provider::Result<Vec<Self::AccountId>>;

/// All the voters that participate in the election, thus "electing".
Expand All @@ -296,7 +312,27 @@ pub trait ElectionDataProvider {
///
/// This should be implemented as a self-weighing function. The implementor should register its
/// appropriate weight at the end of execution with the system pallet directly.
fn electing_voters(maybe_max_len: Option<usize>) -> data_provider::Result<Vec<VoterOf<Self>>>;
fn electing_voters_paged(
maybe_max_len: Option<usize>,
remaining_pages: PageIndex,
) -> data_provider::Result<Vec<VoterOf<Self>>>;

/// Same as [`Self::electable_targets_paged`], but the most significant page is assumed.
///
/// This should almost only be used in the case where `Self::Pages` is `1`.
fn electable_targets(
maybe_max_len: Option<usize>,
) -> data_provider::Result<Vec<Self::AccountId>> {
Self::electable_targets_paged(maybe_max_len, Self::msp())
}

/// Same as [`Self::electing_voters_paged`], but the page 0 is assumed.
///
///
/// This should almost only be used in the case where `Self::Pages` is `1`.
fn electing_voters(maybe_max_len: Option<usize>) -> data_provider::Result<Vec<VoterOf<Self>>> {
Self::electing_voters_paged(maybe_max_len, Self::msp())
}

/// The number of targets to elect.
///
Expand All @@ -306,8 +342,6 @@ pub trait ElectionDataProvider {
/// A sensible implementation should use the minimum between this value and
/// [`Self::targets().len()`], since desiring a winner set larger than candidates is not
/// feasible.
///
/// This is documented further in issue: <https://github.com/paritytech/substrate/issues/9478>
fn desired_targets() -> data_provider::Result<u32>;

/// Provide a best effort prediction about when the next election is about to happen.
Expand All @@ -318,8 +352,28 @@ pub trait ElectionDataProvider {
/// This is only useful for stateful election providers.
fn next_election_prediction(now: Self::BlockNumber) -> Self::BlockNumber;

/// Shorthand for "most significant page".
///
/// The least significant, aka "first" page that this election provider is expected to receive.
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
/// The least significant, aka "first" page that this election provider is expected to receive.
/// The most significant, aka "last" page that this election provider is expected to receive.
Suggested change
/// The least significant, aka "first" page that this election provider is expected to receive.
/// The least significant, aka "first" page that this election provider is expected to receive.

Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
/// The least significant, aka "first" page that this election provider is expected to receive.
/// The most significant, aka "last" page that this election provider is expected to receive.
Suggested change
/// The least significant, aka "first" page that this election provider is expected to receive.
/// The least significant, aka "first" page that this election provider is expected to receive.

fn msp() -> PageIndex {
Self::Pages::get().defensive_saturating_sub(1)
}

/// Shorthand for "least significant page".
///
/// The most significant, aka "first" page that this election provider is expected to receive.
fn lsp() -> PageIndex {
0
}

/// The range of `take` pages, from most significant to least significant.
fn range(take: PageIndex) -> Box<dyn Iterator<Item = PageIndex>> {
Copy link
Contributor

Choose a reason for hiding this comment

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

Why use trait object? Can we just return the concrete type and save the v table look up?

Copy link
Contributor

Choose a reason for hiding this comment

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

Why use trait object? Can we just return the concrete type and save the v table look up?

Box::new((Self::lsp()..Self::Pages::get()).take(take as usize).rev())
}

/// Utility function only to be used in benchmarking scenarios, to be implemented optionally,
/// else a noop.

#[cfg(any(feature = "runtime-benchmarks", test))]
fn put_snapshot(
_voters: Vec<VoterOf<Self>>,
Expand Down
21 changes: 18 additions & 3 deletions frame/election-provider-support/src/onchain.rs
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,8 @@ use sp_std::{collections::btree_map::BTreeMap, marker::PhantomData, prelude::*};
/// Errors of the on-chain election.
#[derive(Eq, PartialEq, Debug)]
pub enum Error {
/// The number of pages is incorrect.
WrongPageSize,
/// An internal error in the NPoS elections crate.
NposElections(sp_npos_elections::Error),
/// Errors from the data provider.
Expand Down Expand Up @@ -97,6 +99,9 @@ fn elect_with<T: Config>(
maybe_max_voters: Option<usize>,
maybe_max_targets: Option<usize>,
) -> Result<Supports<<T::System as frame_system::Config>::AccountId>, Error> {
if <T::DataProvider as ElectionDataProvider>::Pages::get() != 1 {
return Err(Error::WrongPageSize)
}
let voters = T::DataProvider::electing_voters(maybe_max_voters).map_err(Error::DataProvider)?;
let targets =
T::DataProvider::electable_targets(maybe_max_targets).map_err(Error::DataProvider)?;
Expand Down Expand Up @@ -266,22 +271,32 @@ mod tests {
use frame_support::{bounded_vec, traits::ConstU32};

use super::*;
use crate::{data_provider, VoterOf};
use crate::{data_provider, PageIndex, VoterOf};

pub struct DataProvider;
impl ElectionDataProvider for DataProvider {
type AccountId = AccountId;
type BlockNumber = BlockNumber;
type MaxVotesPerVoter = ConstU32<2>;
fn electing_voters(_: Option<usize>) -> data_provider::Result<Vec<VoterOf<Self>>> {
type Pages = ConstU32<1>;

fn electing_voters_paged(
_: Option<usize>,
remaining: PageIndex,
) -> data_provider::Result<Vec<VoterOf<Self>>> {
assert_eq!(remaining, 0);
Ok(vec![
(1, 10, bounded_vec![10, 20]),
(2, 20, bounded_vec![30, 20]),
(3, 30, bounded_vec![10, 30]),
])
}

fn electable_targets(_: Option<usize>) -> data_provider::Result<Vec<AccountId>> {
fn electable_targets_paged(
_: Option<usize>,
remaining: PageIndex,
) -> data_provider::Result<Vec<AccountId>> {
assert_eq!(remaining, 0);
Ok(vec![10, 20, 30])
}

Expand Down
1 change: 1 addition & 0 deletions frame/grandpa/src/mock.rs
Original file line number Diff line number Diff line change
Expand Up @@ -194,6 +194,7 @@ impl pallet_staking::Config for Test {
type CurrencyToVote = frame_support::traits::SaturatingCurrencyToVote;
type Event = Event;
type Currency = Balances;
type Pages = ConstU32<1>;
type Slash = ();
type Reward = ();
type SessionsPerEra = SessionsPerEra;
Expand Down
1 change: 1 addition & 0 deletions frame/offences/benchmarking/src/mock.rs
Original file line number Diff line number Diff line change
Expand Up @@ -164,6 +164,7 @@ impl pallet_staking::Config for Test {
type UnixTime = pallet_timestamp::Pallet<Self>;
type CurrencyToVote = frame_support::traits::SaturatingCurrencyToVote;
type RewardRemainder = ();
type Pages = ConstU32<1>;
type Event = Event;
type Slash = ();
type Reward = ();
Expand Down
1 change: 1 addition & 0 deletions frame/session/benchmarking/src/mock.rs
Original file line number Diff line number Diff line change
Expand Up @@ -170,6 +170,7 @@ impl pallet_staking::Config for Test {
type UnixTime = pallet_timestamp::Pallet<Self>;
type CurrencyToVote = frame_support::traits::SaturatingCurrencyToVote;
type RewardRemainder = ();
type Pages = ConstU32<1>;
type Event = Event;
type Slash = ();
type Reward = ();
Expand Down
Loading