b6d35f6faf
Updated 4763 files with dual copyright: - Parity Technologies (UK) Ltd. - Dijital Kurdistan Tech Institute
1239 lines
43 KiB
Rust
1239 lines
43 KiB
Rust
// This file is part of Bizinikiwi.
|
|
|
|
// Copyright (C) Parity Technologies (UK) Ltd. and Dijital Kurdistan Tech Institute
|
|
// SPDX-License-Identifier: Apache-2.0
|
|
|
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
// you may not use this file except in compliance with the License.
|
|
// You may obtain a copy of the License at
|
|
//
|
|
// http://www.apache.org/licenses/LICENSE-2.0
|
|
//
|
|
// Unless required by applicable law or agreed to in writing, software
|
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
// See the License for the specific language governing permissions and
|
|
// limitations under the License.
|
|
|
|
//! Primitive traits for providing election functionality.
|
|
//!
|
|
//! This crate provides two traits that could interact to enable extensible election functionality
|
|
//! within FRAME pallets.
|
|
//!
|
|
//! Something that will provide the functionality of election will implement
|
|
//! [`ElectionProvider`], whilst needing an associated [`ElectionProvider::DataProvider`], which
|
|
//! needs to be fulfilled by an entity implementing [`ElectionDataProvider`]. Most often, *the data
|
|
//! provider is* the receiver of the election, resulting in a diagram as below:
|
|
//!
|
|
//! ```ignore
|
|
//! ElectionDataProvider
|
|
//! <------------------------------------------+
|
|
//! | |
|
|
//! v |
|
|
//! +-----+----+ +------+---+
|
|
//! | | | |
|
|
//! pezpallet-do-election | | | | pezpallet-needs-election
|
|
//! | | | |
|
|
//! | | | |
|
|
//! +-----+----+ +------+---+
|
|
//! | ^
|
|
//! | |
|
|
//! +------------------------------------------+
|
|
//! ElectionProvider
|
|
//! ```
|
|
//!
|
|
//! > It could also be possible that a third party pezpallet (C), provides the data of election to
|
|
//! > an
|
|
//! > election provider (B), which then passes the election result to another pezpallet (A).
|
|
//!
|
|
//! ## Election Types
|
|
//!
|
|
//! Typically, two types of elections exist:
|
|
//!
|
|
//! 1. **Stateless**: Election data is provided, and the election result is immediately ready.
|
|
//! 2. **Stateful**: Election data is is queried ahead of time, and the election result might be
|
|
//! ready some number of blocks in the future.
|
|
//!
|
|
//! To accommodate both type of elections in one trait, the traits lean toward **stateful
|
|
//! election**, as it is more general than the stateless. This is why [`ElectionProvider::elect`]
|
|
//! does not receive election data as an input. All value and type parameter must be provided by the
|
|
//! [`ElectionDataProvider`] trait, even if the election happens immediately.
|
|
//!
|
|
//! ## Multi-page election support
|
|
//!
|
|
//! Both [`ElectionDataProvider`] and [`ElectionProvider`] traits are parameterized by page,
|
|
//! supporting an election to be performed over multiple pages. This enables the
|
|
//! [`ElectionDataProvider`] implementor to provide all the election data over multiple pages.
|
|
//! Similarly [`ElectionProvider::elect`] is parameterized by page index.
|
|
//!
|
|
//! ## Election Data
|
|
//!
|
|
//! The data associated with an election, essentially what the [`ElectionDataProvider`] must convey
|
|
//! is as follows:
|
|
//!
|
|
//! 1. A list of voters, with their stake.
|
|
//! 2. A list of targets (i.e. _candidates_).
|
|
//! 3. A number of desired targets to be elected (i.e. _winners_)
|
|
//!
|
|
//! In addition to that, the [`ElectionDataProvider`] must also hint [`ElectionProvider`] at when
|
|
//! the next election might happen ([`ElectionDataProvider::next_election_prediction`]). A stateless
|
|
//! election provider would probably ignore this. A stateful election provider can use this to
|
|
//! prepare the election result in advance.
|
|
//!
|
|
//! Nonetheless, an [`ElectionProvider`] shan't rely on this and should preferably provide some
|
|
//! means of fallback election as well, in case the `elect` was called immaturely early.
|
|
//!
|
|
//! ## Example
|
|
//!
|
|
//! ```rust
|
|
//! # use pezframe_election_provider_support::{*, data_provider};
|
|
//! # use pezsp_npos_elections::{Support, Assignment};
|
|
//! # use pezframe_support::traits::ConstU32;
|
|
//! # use pezsp_runtime::bounded_vec;
|
|
//!
|
|
//! type AccountId = u64;
|
|
//! type Balance = u64;
|
|
//! type BlockNumber = u32;
|
|
//!
|
|
//! mod data_provider_mod {
|
|
//! use super::*;
|
|
//!
|
|
//! pub trait Config: Sized {
|
|
//! type ElectionProvider: ElectionProvider<
|
|
//! AccountId = AccountId,
|
|
//! BlockNumber = BlockNumber,
|
|
//! DataProvider = Pezpallet<Self>,
|
|
//! >;
|
|
//! }
|
|
//!
|
|
//! pub struct Pezpallet<T: Config>(std::marker::PhantomData<T>);
|
|
//!
|
|
//! impl<T: Config> ElectionDataProvider for Pezpallet<T> {
|
|
//! type AccountId = AccountId;
|
|
//! type BlockNumber = BlockNumber;
|
|
//! type MaxVotesPerVoter = ConstU32<100>;
|
|
//!
|
|
//! fn desired_targets() -> data_provider::Result<u32> {
|
|
//! Ok(1)
|
|
//! }
|
|
//! fn electing_voters(bounds: DataProviderBounds, _page: PageIndex)
|
|
//! -> data_provider::Result<Vec<VoterOf<Self>>>
|
|
//! {
|
|
//! Ok(Default::default())
|
|
//! }
|
|
//! fn electable_targets(bounds: DataProviderBounds, _page: PageIndex) -> data_provider::Result<Vec<AccountId>> {
|
|
//! Ok(vec![10, 20, 30])
|
|
//! }
|
|
//! fn next_election_prediction(now: BlockNumber) -> BlockNumber {
|
|
//! 0
|
|
//! }
|
|
//! }
|
|
//! }
|
|
//!
|
|
//!
|
|
//! mod generic_election_provider {
|
|
//! use super::*;
|
|
//! use pezsp_runtime::traits::Zero;
|
|
//!
|
|
//! pub struct GenericElectionProvider<T: Config>(std::marker::PhantomData<T>);
|
|
//!
|
|
//! pub trait Config {
|
|
//! type DataProvider: ElectionDataProvider<AccountId=AccountId, BlockNumber = BlockNumber>;
|
|
//! type MaxWinnersPerPage: Get<u32>;
|
|
//! type MaxBackersPerWinner: Get<u32>;
|
|
//! type Pages: Get<u32>;
|
|
//! }
|
|
//!
|
|
//! impl<T: Config> ElectionProvider for GenericElectionProvider<T> {
|
|
//! type AccountId = AccountId;
|
|
//! type BlockNumber = BlockNumber;
|
|
//! type Error = &'static str;
|
|
//! type MaxBackersPerWinner = T::MaxBackersPerWinner;
|
|
//! type MaxBackersPerWinnerFinal = T::MaxBackersPerWinner;
|
|
//! type MaxWinnersPerPage = T::MaxWinnersPerPage;
|
|
//! type Pages = T::Pages;
|
|
//! type DataProvider = T::DataProvider;
|
|
//!
|
|
//! fn duration() -> <Self as pezframe_election_provider_support::ElectionProvider>::BlockNumber { todo!() }
|
|
//!
|
|
//! fn start() -> Result<(), <Self as pezframe_election_provider_support::ElectionProvider>::Error> { todo!() }
|
|
//!
|
|
//! fn elect(page: PageIndex) -> Result<BoundedSupportsOf<Self>, Self::Error> {
|
|
//! unimplemented!()
|
|
//! }
|
|
//!
|
|
//! fn status() -> Result<bool, ()> {
|
|
//! unimplemented!()
|
|
//! }
|
|
//! }
|
|
//! }
|
|
//!
|
|
//! mod runtime {
|
|
//! use pezframe_support::parameter_types;
|
|
//! use super::generic_election_provider;
|
|
//! use super::data_provider_mod;
|
|
//! use super::AccountId;
|
|
//!
|
|
//! parameter_types! {
|
|
//! pub static MaxWinnersPerPage: u32 = 10;
|
|
//! pub static MaxBackersPerWinner: u32 = 20;
|
|
//! pub static Pages: u32 = 2;
|
|
//! }
|
|
//!
|
|
//! struct Runtime;
|
|
//! impl generic_election_provider::Config for Runtime {
|
|
//! type DataProvider = data_provider_mod::Pezpallet<Runtime>;
|
|
//! type MaxWinnersPerPage = MaxWinnersPerPage;
|
|
//! type MaxBackersPerWinner = MaxBackersPerWinner;
|
|
//! type Pages = Pages;
|
|
//! }
|
|
//!
|
|
//! impl data_provider_mod::Config for Runtime {
|
|
//! type ElectionProvider = generic_election_provider::GenericElectionProvider<Runtime>;
|
|
//! }
|
|
//!
|
|
//! }
|
|
//!
|
|
//! # fn main() {}
|
|
//! ```
|
|
|
|
#![cfg_attr(not(feature = "std"), no_std)]
|
|
|
|
pub mod bounds;
|
|
pub mod onchain;
|
|
pub mod traits;
|
|
|
|
extern crate alloc;
|
|
|
|
use alloc::{boxed::Box, vec::Vec};
|
|
use core::fmt::Debug;
|
|
use pezframe_support::traits::{Defensive, DefensiveResult};
|
|
use pezsp_core::ConstU32;
|
|
use pezsp_runtime::{
|
|
traits::{Bounded, Saturating, Zero},
|
|
RuntimeDebug,
|
|
};
|
|
|
|
pub use bounds::DataProviderBounds;
|
|
pub use codec::{Decode, DecodeWithMemTracking, Encode, MaxEncodedLen};
|
|
/// Re-export the solution generation macro.
|
|
pub use pezframe_election_provider_solution_type::generate_solution_type;
|
|
pub use pezframe_support::{traits::Get, weights::Weight, BoundedVec, DefaultNoBound};
|
|
/// Re-export some type as they are used in the interface.
|
|
pub use pezsp_arithmetic::PerThing;
|
|
pub use pezsp_npos_elections::{
|
|
Assignment, BalancingConfig, ElectionResult, Error, ExtendedBalance, IdentifierT, PerThing128,
|
|
Support, Supports, VoteWeight,
|
|
};
|
|
use scale_info::TypeInfo;
|
|
pub use traits::NposSolution;
|
|
|
|
#[cfg(feature = "try-runtime")]
|
|
use pezsp_runtime::TryRuntimeError;
|
|
|
|
// re-export for the solution macro, with the dependencies of the macro.
|
|
#[doc(hidden)]
|
|
pub mod private {
|
|
pub use alloc::{
|
|
collections::{btree_map::BTreeMap, btree_set::BTreeSet},
|
|
vec::Vec,
|
|
};
|
|
pub use codec;
|
|
pub use pezsp_arithmetic;
|
|
pub use scale_info;
|
|
|
|
// Simple Extension trait to easily convert `None` from index closures to `Err`.
|
|
//
|
|
// This is only generated and re-exported for the solution code to use.
|
|
pub trait __OrInvalidIndex<T> {
|
|
fn or_invalid_index(self) -> Result<T, crate::Error>;
|
|
}
|
|
|
|
impl<T> __OrInvalidIndex<T> for Option<T> {
|
|
fn or_invalid_index(self) -> Result<T, crate::Error> {
|
|
self.ok_or(crate::Error::SolutionInvalidIndex)
|
|
}
|
|
}
|
|
}
|
|
|
|
use private::__OrInvalidIndex;
|
|
|
|
pub mod weights;
|
|
pub use weights::WeightInfo;
|
|
|
|
#[cfg(test)]
|
|
mod mock;
|
|
#[cfg(test)]
|
|
mod tests;
|
|
|
|
/// A page index for the multi-block elections pagination.
|
|
pub type PageIndex = u32;
|
|
|
|
/// The [`IndexAssignment`] type is an intermediate between the assignments list
|
|
/// ([`&[Assignment<T>]`][Assignment]) and `SolutionOf<T>`.
|
|
///
|
|
/// The voter and target identifiers have already been replaced with appropriate indices,
|
|
/// making it fast to repeatedly encode into a `SolutionOf<T>`. 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))]
|
|
pub struct IndexAssignment<VoterIndex, TargetIndex, P: PerThing> {
|
|
/// Index of the voter among the voters list.
|
|
pub who: VoterIndex,
|
|
/// The distribution of the voter's stake among winning targets.
|
|
///
|
|
/// Targets are identified by their index in the canonical list.
|
|
pub distribution: Vec<(TargetIndex, P)>,
|
|
}
|
|
|
|
impl<VoterIndex: core::fmt::Debug, TargetIndex: core::fmt::Debug, P: PerThing>
|
|
IndexAssignment<VoterIndex, TargetIndex, P>
|
|
{
|
|
pub fn new<AccountId: IdentifierT>(
|
|
assignment: &Assignment<AccountId, P>,
|
|
voter_index: impl Fn(&AccountId) -> Option<VoterIndex>,
|
|
target_index: impl Fn(&AccountId) -> Option<TargetIndex>,
|
|
) -> Result<Self, Error> {
|
|
Ok(Self {
|
|
who: voter_index(&assignment.who).or_invalid_index()?,
|
|
distribution: assignment
|
|
.distribution
|
|
.iter()
|
|
.map(|(target, proportion)| Some((target_index(target)?, *proportion)))
|
|
.collect::<Option<Vec<_>>>()
|
|
.or_invalid_index()?,
|
|
})
|
|
}
|
|
}
|
|
|
|
/// A type alias for [`IndexAssignment`] made from [`NposSolution`].
|
|
pub type IndexAssignmentOf<C> = IndexAssignment<
|
|
<C as NposSolution>::VoterIndex,
|
|
<C as NposSolution>::TargetIndex,
|
|
<C as NposSolution>::Accuracy,
|
|
>;
|
|
|
|
/// Types that are used by the data provider trait.
|
|
pub mod data_provider {
|
|
/// Alias for the result type of the election data provider.
|
|
pub type Result<T> = core::result::Result<T, &'static str>;
|
|
}
|
|
|
|
/// Something that can provide the data to an [`ElectionProvider`].
|
|
pub trait ElectionDataProvider {
|
|
/// The account identifier type.
|
|
type AccountId: Encode;
|
|
|
|
/// The block number type.
|
|
type BlockNumber;
|
|
|
|
/// Maximum number of votes per voter that this data provider is providing.
|
|
type MaxVotesPerVoter: Get<u32>;
|
|
|
|
/// Returns the possible targets for the election associated with the provided `page`, i.e. the
|
|
/// targets that could become elected, thus "electable".
|
|
///
|
|
/// This should be implemented as a self-weighing function. The implementor should register its
|
|
/// appropriate weight at the end of execution with the system pezpallet directly.
|
|
fn electable_targets(
|
|
bounds: DataProviderBounds,
|
|
page: PageIndex,
|
|
) -> data_provider::Result<Vec<Self::AccountId>>;
|
|
|
|
/// A state-less version of [`Self::electable_targets`].
|
|
///
|
|
/// An election-provider that only uses 1 page should use this.
|
|
fn electable_targets_stateless(
|
|
bounds: DataProviderBounds,
|
|
) -> data_provider::Result<Vec<Self::AccountId>> {
|
|
Self::electable_targets(bounds, 0)
|
|
}
|
|
|
|
/// All the voters that participate in the election associated with page `page`, thus
|
|
/// "electing".
|
|
///
|
|
/// Note that if a notion of self-vote exists, it should be represented here.
|
|
///
|
|
/// This should be implemented as a self-weighing function. The implementor should register its
|
|
/// appropriate weight at the end of execution with the system pezpallet directly.
|
|
fn electing_voters(
|
|
bounds: DataProviderBounds,
|
|
page: PageIndex,
|
|
) -> data_provider::Result<Vec<VoterOf<Self>>>;
|
|
|
|
/// A state-less version of [`Self::electing_voters`].
|
|
///
|
|
/// An election-provider that only uses 1 page should use this.
|
|
fn electing_voters_stateless(
|
|
bounds: DataProviderBounds,
|
|
) -> data_provider::Result<Vec<VoterOf<Self>>> {
|
|
Self::electing_voters(bounds, 0)
|
|
}
|
|
|
|
/// The number of targets to elect.
|
|
///
|
|
/// This should be implemented as a self-weighing function. The implementor should register its
|
|
/// appropriate weight at the end of execution with the system pezpallet directly.
|
|
///
|
|
/// 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/pezkuwichain/pezkuwi-sdk/issues/204>
|
|
fn desired_targets() -> data_provider::Result<u32>;
|
|
|
|
/// Provide a best effort prediction about when the next election is about to happen.
|
|
///
|
|
/// In essence, the implementor should predict with this function when it will trigger the
|
|
/// [`ElectionProvider::elect`].
|
|
///
|
|
/// This is only useful for stateful election providers.
|
|
fn next_election_prediction(now: Self::BlockNumber) -> Self::BlockNumber;
|
|
|
|
/// 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>>,
|
|
_targets: Vec<Self::AccountId>,
|
|
_target_stake: Option<VoteWeight>,
|
|
) {
|
|
}
|
|
|
|
/// Instruct the data provider to fetch a page of the solution.
|
|
///
|
|
/// This can be useful to measure the export process in benchmarking.
|
|
#[cfg(any(feature = "runtime-benchmarks", test))]
|
|
fn fetch_page(_page: PageIndex) {}
|
|
|
|
/// Utility function only to be used in benchmarking scenarios, to be implemented optionally,
|
|
/// else a noop.
|
|
///
|
|
/// Same as `put_snapshot`, but can add a single voter one by one.
|
|
#[cfg(any(feature = "runtime-benchmarks", test))]
|
|
fn add_voter(
|
|
_voter: Self::AccountId,
|
|
_weight: VoteWeight,
|
|
_targets: BoundedVec<Self::AccountId, Self::MaxVotesPerVoter>,
|
|
) {
|
|
}
|
|
|
|
/// Utility function only to be used in benchmarking scenarios, to be implemented optionally,
|
|
/// else a noop.
|
|
///
|
|
/// Same as `put_snapshot`, but can add a single voter one by one.
|
|
#[cfg(any(feature = "runtime-benchmarks", test))]
|
|
fn add_target(_target: Self::AccountId) {}
|
|
|
|
/// Clear all voters and targets.
|
|
#[cfg(any(feature = "runtime-benchmarks", test))]
|
|
fn clear() {}
|
|
|
|
/// Force set the desired targets in the snapshot.
|
|
#[cfg(any(feature = "runtime-benchmarks", test))]
|
|
fn set_desired_targets(_count: u32) {}
|
|
}
|
|
|
|
/// Something that can compute the result of an election and pass it back to the caller in a paged
|
|
/// way.
|
|
pub trait ElectionProvider {
|
|
/// The account ID identifier;
|
|
type AccountId;
|
|
|
|
/// The block number type.
|
|
type BlockNumber;
|
|
|
|
/// The error type returned by the provider;
|
|
type Error: Debug + PartialEq;
|
|
|
|
/// The maximum number of winners per page in results returned by this election provider.
|
|
///
|
|
/// A winner is an `AccountId` that is part of the final election result.
|
|
type MaxWinnersPerPage: Get<u32>;
|
|
|
|
/// The maximum number of backers that a single page may have in results returned by this
|
|
/// election provider.
|
|
///
|
|
/// A backer is an `AccountId` that "backs" one or more winners. For example, in the context of
|
|
/// nominated proof of stake, a backer is a voter that nominates a winner validator in the
|
|
/// election result.
|
|
type MaxBackersPerWinner: Get<u32>;
|
|
|
|
/// Same as [`Self::MaxBackersPerWinner`], but across all pages.
|
|
///
|
|
/// If [`Self::Pages`] is set to 0, a reasonable value is [`Self::MaxBackersPerWinner`]. For
|
|
/// multi-page elections, a reasonable value is the range of [`Self::MaxBackersPerWinner`] to
|
|
/// [`Self::Pages`] * [`Self::MaxBackersPerWinner`].
|
|
type MaxBackersPerWinnerFinal: Get<u32>;
|
|
|
|
/// The number of pages that this election provider supports.
|
|
type Pages: Get<PageIndex>;
|
|
|
|
/// The data provider of the election.
|
|
type DataProvider: ElectionDataProvider<
|
|
AccountId = Self::AccountId,
|
|
BlockNumber = Self::BlockNumber,
|
|
>;
|
|
|
|
/// Elect a new set of winners.
|
|
///
|
|
/// A complete election may require multiple calls to [`ElectionProvider::elect`] if
|
|
/// [`ElectionProvider::Pages`] is higher than one.
|
|
///
|
|
/// The result is returned in a target major format, namely as vector of supports.
|
|
fn elect(page: PageIndex) -> Result<BoundedSupportsOf<Self>, Self::Error>;
|
|
|
|
/// The index of the *most* significant page that this election provider supports.
|
|
fn msp() -> PageIndex {
|
|
Self::Pages::get().saturating_sub(1)
|
|
}
|
|
|
|
/// The index of the *least* significant page that this election provider supports.
|
|
fn lsp() -> PageIndex {
|
|
Zero::zero()
|
|
}
|
|
|
|
/// checked call to `Self::DataProvider::desired_targets()` ensuring the value never exceeds
|
|
/// [`Self::MaxWinnersPerPage`].
|
|
fn desired_targets_checked() -> data_provider::Result<u32> {
|
|
Self::DataProvider::desired_targets().and_then(|desired_targets| {
|
|
if desired_targets <= Self::MaxWinnersPerPage::get() {
|
|
Ok(desired_targets)
|
|
} else {
|
|
Err("desired_targets must not be greater than MaxWinners.")
|
|
}
|
|
})
|
|
}
|
|
|
|
/// Return the duration of your election.
|
|
///
|
|
/// This excludes the duration of the export. For that, use [`Self::duration_with_export`].
|
|
fn duration() -> Self::BlockNumber;
|
|
|
|
/// Return the duration of your election, including the export.
|
|
fn duration_with_export() -> Self::BlockNumber
|
|
where
|
|
Self::BlockNumber: From<PageIndex> + core::ops::Add<Output = Self::BlockNumber>,
|
|
{
|
|
let export: Self::BlockNumber = Self::Pages::get().into();
|
|
Self::duration() + export
|
|
}
|
|
|
|
/// Signal that the election should start
|
|
fn start() -> Result<(), Self::Error>;
|
|
|
|
/// Indicate whether this election provider is currently ongoing an asynchronous election.
|
|
///
|
|
/// `Err(())` should signal that we are not doing anything, and `elect` should def. not be
|
|
/// called. `Ok(false)` means we are doing something, but work is still ongoing. `elect` should
|
|
/// not be called. `Ok(true)` means we are done and ready for a call to `elect`.
|
|
fn status() -> Result<bool, ()>;
|
|
|
|
/// Signal the election provider that we are about to call `elect` asap, and it should prepare
|
|
/// itself.
|
|
#[cfg(any(feature = "runtime-benchmarks", feature = "std"))]
|
|
fn asap() {}
|
|
}
|
|
|
|
/// A (almost) marker trait that signifies an election provider as working synchronously. i.e. being
|
|
/// *instant*.
|
|
///
|
|
/// This must still use the same data provider as with [`ElectionProvider::DataProvider`].
|
|
/// However, it can optionally overwrite the amount of voters and targets that are fetched from the
|
|
/// data provider at runtime via `forced_input_voters_bound` and `forced_input_target_bound`.
|
|
pub trait InstantElectionProvider: ElectionProvider {
|
|
fn instant_elect(
|
|
voters: Vec<VoterOf<Self::DataProvider>>,
|
|
targets: Vec<Self::AccountId>,
|
|
desired_targets: u32,
|
|
) -> Result<BoundedSupportsOf<Self>, Self::Error>;
|
|
|
|
// Sine many instant election provider, like [`NoElection`] are meant to do nothing, this is a
|
|
// hint for the caller to call before, and if `false` is returned, not bother with passing all
|
|
// the info to `instant_elect`.
|
|
fn bother() -> bool;
|
|
}
|
|
|
|
/// An election provider that does nothing whatsoever.
|
|
pub struct NoElection<X>(core::marker::PhantomData<X>);
|
|
|
|
impl<AccountId, BlockNumber, DataProvider, MaxWinnersPerPage, MaxBackersPerWinner> ElectionProvider
|
|
for NoElection<(AccountId, BlockNumber, DataProvider, MaxWinnersPerPage, MaxBackersPerWinner)>
|
|
where
|
|
DataProvider: ElectionDataProvider<AccountId = AccountId, BlockNumber = BlockNumber>,
|
|
MaxWinnersPerPage: Get<u32>,
|
|
MaxBackersPerWinner: Get<u32>,
|
|
BlockNumber: Zero,
|
|
{
|
|
type AccountId = AccountId;
|
|
type BlockNumber = BlockNumber;
|
|
type Error = &'static str;
|
|
type Pages = ConstU32<1>;
|
|
type DataProvider = DataProvider;
|
|
type MaxWinnersPerPage = MaxWinnersPerPage;
|
|
type MaxBackersPerWinner = MaxBackersPerWinner;
|
|
type MaxBackersPerWinnerFinal = MaxBackersPerWinner;
|
|
|
|
fn elect(_page: PageIndex) -> Result<BoundedSupportsOf<Self>, Self::Error> {
|
|
Err("`NoElection` cannot do anything.")
|
|
}
|
|
|
|
fn start() -> Result<(), Self::Error> {
|
|
Err("`NoElection` cannot do anything.")
|
|
}
|
|
|
|
fn duration() -> Self::BlockNumber {
|
|
Zero::zero()
|
|
}
|
|
|
|
fn status() -> Result<bool, ()> {
|
|
Err(())
|
|
}
|
|
}
|
|
|
|
impl<AccountId, BlockNumber, DataProvider, MaxWinnersPerPage, MaxBackersPerWinner>
|
|
InstantElectionProvider
|
|
for NoElection<(AccountId, BlockNumber, DataProvider, MaxWinnersPerPage, MaxBackersPerWinner)>
|
|
where
|
|
DataProvider: ElectionDataProvider<AccountId = AccountId, BlockNumber = BlockNumber>,
|
|
MaxWinnersPerPage: Get<u32>,
|
|
MaxBackersPerWinner: Get<u32>,
|
|
BlockNumber: Zero,
|
|
{
|
|
fn instant_elect(
|
|
_: Vec<VoterOf<Self::DataProvider>>,
|
|
_: Vec<Self::AccountId>,
|
|
_: u32,
|
|
) -> Result<BoundedSupportsOf<Self>, Self::Error> {
|
|
Err("`NoElection` cannot do anything.")
|
|
}
|
|
|
|
fn bother() -> bool {
|
|
false
|
|
}
|
|
}
|
|
|
|
/// A utility trait for something to implement `ElectionDataProvider` in a sensible way.
|
|
///
|
|
/// This is generic over `AccountId` and it can represent a validator, a nominator, or any other
|
|
/// entity.
|
|
///
|
|
/// The scores (see [`Self::Score`]) are ascending, the higher, the better.
|
|
///
|
|
/// Something that implements this trait will do a best-effort sort over ids, and thus can be
|
|
/// used on the implementing side of [`ElectionDataProvider`].
|
|
pub trait SortedListProvider<AccountId> {
|
|
/// The list's error type.
|
|
type Error: core::fmt::Debug;
|
|
|
|
/// The type used by the list to compare nodes for ordering.
|
|
type Score: Bounded + Saturating + Zero + Default;
|
|
|
|
/// A typical range for this list.
|
|
///
|
|
/// By default, this would be implemented as `Bounded` impl of `Self::Score`.
|
|
///
|
|
/// If this is implemented by a bags-list instance, it will be the smallest and largest bags.
|
|
///
|
|
/// This is useful to help another pezpallet that consumes this trait generate an even
|
|
/// distribution of nodes for testing/genesis.
|
|
fn range() -> (Self::Score, Self::Score) {
|
|
(Self::Score::min_value(), Self::Score::max_value())
|
|
}
|
|
|
|
/// An iterator over the list, which can have `take` called on it.
|
|
fn iter() -> Box<dyn Iterator<Item = AccountId>>;
|
|
|
|
/// Lock the list.
|
|
///
|
|
/// This will prevent subsequent calls to
|
|
/// - [`Self::on_insert`]
|
|
/// - [`Self::on_update`]
|
|
/// - [`Self::on_decrease`]
|
|
/// - [`Self::on_increase`]
|
|
/// - [`Self::on_remove`]
|
|
fn lock();
|
|
|
|
/// Unlock the list. This will nullify the effects of [`Self::lock`].
|
|
fn unlock();
|
|
|
|
/// Returns an iterator over the list, starting right after from the given voter.
|
|
///
|
|
/// May return an error if `start` is invalid.
|
|
fn iter_from(start: &AccountId) -> Result<Box<dyn Iterator<Item = AccountId>>, Self::Error>;
|
|
|
|
/// The current count of ids in the list.
|
|
fn count() -> u32;
|
|
|
|
/// Return true if the list already contains `id`.
|
|
fn contains(id: &AccountId) -> bool;
|
|
|
|
/// Hook for inserting a new id.
|
|
///
|
|
/// Implementation should return an error if duplicate item is being inserted.
|
|
fn on_insert(id: AccountId, score: Self::Score) -> Result<(), Self::Error>;
|
|
|
|
/// Hook for updating a single id.
|
|
///
|
|
/// The `new` score is given.
|
|
///
|
|
/// Returns `Ok(())` iff it successfully updates an item, an `Err(_)` otherwise.
|
|
fn on_update(id: &AccountId, score: Self::Score) -> Result<(), Self::Error>;
|
|
|
|
/// Get the score of `id`.
|
|
fn get_score(id: &AccountId) -> Result<Self::Score, Self::Error>;
|
|
|
|
/// Same as `on_update`, but incorporate some increased score.
|
|
fn on_increase(id: &AccountId, additional: Self::Score) -> Result<(), Self::Error> {
|
|
let old_score = Self::get_score(id)?;
|
|
let new_score = old_score.saturating_add(additional);
|
|
Self::on_update(id, new_score)
|
|
}
|
|
|
|
/// Same as `on_update`, but incorporate some decreased score.
|
|
///
|
|
/// If the new score of the item is `Zero`, it is removed.
|
|
fn on_decrease(id: &AccountId, decreased: Self::Score) -> Result<(), Self::Error> {
|
|
let old_score = Self::get_score(id)?;
|
|
let new_score = old_score.saturating_sub(decreased);
|
|
if new_score.is_zero() {
|
|
Self::on_remove(id)
|
|
} else {
|
|
Self::on_update(id, new_score)
|
|
}
|
|
}
|
|
|
|
/// Hook for removing am id from the list.
|
|
///
|
|
/// Returns `Ok(())` iff it successfully removes an item, an `Err(_)` otherwise.
|
|
fn on_remove(id: &AccountId) -> Result<(), Self::Error>;
|
|
|
|
/// Regenerate this list from scratch. Returns the count of items inserted.
|
|
///
|
|
/// This should typically only be used at a runtime upgrade.
|
|
///
|
|
/// ## WARNING
|
|
///
|
|
/// This function should be called with care, regenerate will remove the current list write the
|
|
/// new list, which can lead to too many storage accesses, exhausting the block weight.
|
|
fn unsafe_regenerate(
|
|
all: impl IntoIterator<Item = AccountId>,
|
|
score_of: Box<dyn Fn(&AccountId) -> Option<Self::Score>>,
|
|
) -> u32;
|
|
|
|
/// Remove all items from the list.
|
|
///
|
|
/// ## WARNING
|
|
///
|
|
/// This function should never be called in production settings because it can lead to an
|
|
/// unbounded amount of storage accesses.
|
|
fn unsafe_clear();
|
|
|
|
/// Check internal state of the list. Only meant for debugging.
|
|
#[cfg(feature = "try-runtime")]
|
|
fn try_state() -> Result<(), TryRuntimeError>;
|
|
|
|
/// If `who` changes by the returned amount they are guaranteed to have a worst case change
|
|
/// in their list position.
|
|
#[cfg(feature = "runtime-benchmarks")]
|
|
fn score_update_worst_case(_who: &AccountId, _is_increase: bool) -> Self::Score;
|
|
}
|
|
|
|
/// Something that can provide the `Score` of an account. Similar to [`ElectionProvider`] and
|
|
/// [`ElectionDataProvider`], this should typically be implementing by whoever is supposed to *use*
|
|
/// `SortedListProvider`.
|
|
pub trait ScoreProvider<AccountId> {
|
|
type Score;
|
|
|
|
/// Get the current `Score` of `who`, `None` if `who` is not present.
|
|
///
|
|
/// `None` can be interpreted as a signal that the voter should be removed from the list.
|
|
fn score(who: &AccountId) -> Option<Self::Score>;
|
|
|
|
/// For tests, benchmarks and fuzzing, set the `score`.
|
|
#[cfg(any(feature = "runtime-benchmarks", feature = "fuzz", feature = "std"))]
|
|
fn set_score_of(_: &AccountId, _: Self::Score) {}
|
|
}
|
|
|
|
/// Something that can compute the result to an NPoS solution.
|
|
pub trait NposSolver {
|
|
/// The account identifier type of this solver.
|
|
type AccountId: pezsp_npos_elections::IdentifierT;
|
|
/// The accuracy of this solver. This will affect the accuracy of the output.
|
|
type Accuracy: PerThing128;
|
|
/// The error type of this implementation.
|
|
type Error: core::fmt::Debug + core::cmp::PartialEq;
|
|
|
|
/// Solve an NPoS solution with the given `voters`, `targets`, and select `to_elect` count
|
|
/// of `targets`.
|
|
fn solve(
|
|
to_elect: usize,
|
|
targets: Vec<Self::AccountId>,
|
|
voters: Vec<(
|
|
Self::AccountId,
|
|
VoteWeight,
|
|
impl Clone + IntoIterator<Item = Self::AccountId>,
|
|
)>,
|
|
) -> Result<ElectionResult<Self::AccountId, Self::Accuracy>, Self::Error>;
|
|
|
|
/// Measure the weight used in the calculation of the solver.
|
|
/// - `voters` is the number of voters.
|
|
/// - `targets` is the number of targets.
|
|
/// - `vote_degree` is the degree ie the maximum numbers of votes per voter.
|
|
fn weight<T: WeightInfo>(voters: u32, targets: u32, vote_degree: u32) -> Weight;
|
|
}
|
|
|
|
/// A quick and dirty solver, that produces a valid but probably worthless election result, but is
|
|
/// fast.
|
|
///
|
|
/// It choses a random number of winners without any consideration.
|
|
///
|
|
/// Then it iterates over the voters and assigns them to the winners.
|
|
///
|
|
/// It is only meant to be used in benchmarking.
|
|
pub struct QuickDirtySolver<AccountId, Accuracy>(core::marker::PhantomData<(AccountId, Accuracy)>);
|
|
impl<AccountId: IdentifierT, Accuracy: PerThing128> NposSolver
|
|
for QuickDirtySolver<AccountId, Accuracy>
|
|
{
|
|
type AccountId = AccountId;
|
|
type Accuracy = Accuracy;
|
|
type Error = &'static str;
|
|
|
|
fn solve(
|
|
to_elect: usize,
|
|
targets: Vec<Self::AccountId>,
|
|
voters: Vec<(
|
|
Self::AccountId,
|
|
VoteWeight,
|
|
impl Clone + IntoIterator<Item = Self::AccountId>,
|
|
)>,
|
|
) -> Result<ElectionResult<Self::AccountId, Self::Accuracy>, Self::Error> {
|
|
use pezsp_std::collections::btree_map::BTreeMap;
|
|
|
|
if to_elect > targets.len() {
|
|
return Err("to_elect is greater than the number of targets.");
|
|
}
|
|
|
|
let winners = targets.into_iter().take(to_elect).collect::<Vec<_>>();
|
|
|
|
let mut assignments = Vec::with_capacity(voters.len());
|
|
let mut final_winners = BTreeMap::<Self::AccountId, u128>::new();
|
|
|
|
for (voter, weight, votes) in voters {
|
|
let our_winners = winners
|
|
.iter()
|
|
.filter(|w| votes.clone().into_iter().any(|v| v == **w))
|
|
.collect::<Vec<_>>();
|
|
let our_winners_len = our_winners.len();
|
|
let distribution = our_winners
|
|
.into_iter()
|
|
.map(|w| {
|
|
*final_winners.entry(w.clone()).or_default() += weight as u128;
|
|
(w.clone(), Self::Accuracy::from_rational(1, our_winners_len as u128))
|
|
})
|
|
.collect::<Vec<_>>();
|
|
|
|
let mut assignment = Assignment { who: voter, distribution };
|
|
assignment.try_normalize().unwrap();
|
|
assignments.push(assignment);
|
|
}
|
|
|
|
let winners = final_winners.into_iter().collect::<Vec<_>>();
|
|
Ok(ElectionResult { winners, assignments })
|
|
}
|
|
|
|
fn weight<T: WeightInfo>(_: u32, _: u32, _: u32) -> Weight {
|
|
Default::default()
|
|
}
|
|
}
|
|
|
|
/// A wrapper for [`pezsp_npos_elections::seq_phragmen`] that implements [`NposSolver`]. See the
|
|
/// documentation of [`pezsp_npos_elections::seq_phragmen`] for more info.
|
|
pub struct SequentialPhragmen<AccountId, Accuracy, Balancing = ()>(
|
|
core::marker::PhantomData<(AccountId, Accuracy, Balancing)>,
|
|
);
|
|
|
|
impl<AccountId: IdentifierT, Accuracy: PerThing128, Balancing: Get<Option<BalancingConfig>>>
|
|
NposSolver for SequentialPhragmen<AccountId, Accuracy, Balancing>
|
|
{
|
|
type AccountId = AccountId;
|
|
type Accuracy = Accuracy;
|
|
type Error = pezsp_npos_elections::Error;
|
|
fn solve(
|
|
winners: usize,
|
|
targets: Vec<Self::AccountId>,
|
|
voters: Vec<(
|
|
Self::AccountId,
|
|
VoteWeight,
|
|
impl Clone + IntoIterator<Item = Self::AccountId>,
|
|
)>,
|
|
) -> Result<ElectionResult<Self::AccountId, Self::Accuracy>, Self::Error> {
|
|
pezsp_npos_elections::seq_phragmen(winners, targets, voters, Balancing::get())
|
|
}
|
|
|
|
fn weight<T: WeightInfo>(voters: u32, targets: u32, vote_degree: u32) -> Weight {
|
|
T::phragmen(voters, targets, vote_degree)
|
|
}
|
|
}
|
|
|
|
/// A wrapper for [`pezsp_npos_elections::phragmms()`] that implements [`NposSolver`]. See the
|
|
/// documentation of [`pezsp_npos_elections::phragmms()`] for more info.
|
|
pub struct PhragMMS<AccountId, Accuracy, Balancing = ()>(
|
|
core::marker::PhantomData<(AccountId, Accuracy, Balancing)>,
|
|
);
|
|
|
|
impl<AccountId: IdentifierT, Accuracy: PerThing128, Balancing: Get<Option<BalancingConfig>>>
|
|
NposSolver for PhragMMS<AccountId, Accuracy, Balancing>
|
|
{
|
|
type AccountId = AccountId;
|
|
type Accuracy = Accuracy;
|
|
type Error = pezsp_npos_elections::Error;
|
|
fn solve(
|
|
winners: usize,
|
|
targets: Vec<Self::AccountId>,
|
|
voters: Vec<(
|
|
Self::AccountId,
|
|
VoteWeight,
|
|
impl Clone + IntoIterator<Item = Self::AccountId>,
|
|
)>,
|
|
) -> Result<ElectionResult<Self::AccountId, Self::Accuracy>, Self::Error> {
|
|
pezsp_npos_elections::phragmms(winners, targets, voters, Balancing::get())
|
|
}
|
|
|
|
fn weight<T: WeightInfo>(voters: u32, targets: u32, vote_degree: u32) -> Weight {
|
|
T::phragmms(voters, targets, vote_degree)
|
|
}
|
|
}
|
|
|
|
/// A voter, at the level of abstraction of this crate.
|
|
pub type Voter<AccountId, Bound> = (AccountId, VoteWeight, BoundedVec<AccountId, Bound>);
|
|
|
|
/// Same as [`Voter`], but parameterized by an [`ElectionDataProvider`].
|
|
pub type VoterOf<D> =
|
|
Voter<<D as ElectionDataProvider>::AccountId, <D as ElectionDataProvider>::MaxVotesPerVoter>;
|
|
|
|
/// A bounded vector of supports. Bounded equivalent to [`pezsp_npos_elections::Supports`].
|
|
#[derive(
|
|
Default, Debug, Encode, Decode, DecodeWithMemTracking, scale_info::TypeInfo, MaxEncodedLen,
|
|
)]
|
|
#[codec(mel_bound(AccountId: MaxEncodedLen, Bound: Get<u32>))]
|
|
#[scale_info(skip_type_params(Bound))]
|
|
pub struct BoundedSupport<AccountId, Bound: Get<u32>> {
|
|
/// Total support.
|
|
pub total: ExtendedBalance,
|
|
/// Support from voters.
|
|
pub voters: BoundedVec<(AccountId, ExtendedBalance), Bound>,
|
|
}
|
|
|
|
impl<AccountId, Bound: Get<u32>> pezsp_npos_elections::Backings
|
|
for &BoundedSupport<AccountId, Bound>
|
|
{
|
|
fn total(&self) -> ExtendedBalance {
|
|
self.total
|
|
}
|
|
}
|
|
|
|
impl<AccountId: PartialEq, Bound: Get<u32>> PartialEq for BoundedSupport<AccountId, Bound> {
|
|
fn eq(&self, other: &Self) -> bool {
|
|
self.total == other.total && self.voters == other.voters
|
|
}
|
|
}
|
|
|
|
impl<AccountId, Bound: Get<u32>> From<BoundedSupport<AccountId, Bound>> for Support<AccountId> {
|
|
fn from(b: BoundedSupport<AccountId, Bound>) -> Self {
|
|
Support { total: b.total, voters: b.voters.into_inner() }
|
|
}
|
|
}
|
|
|
|
impl<AccountId: Clone, Bound: Get<u32>> Clone for BoundedSupport<AccountId, Bound> {
|
|
fn clone(&self) -> Self {
|
|
Self { voters: self.voters.clone(), total: self.total }
|
|
}
|
|
}
|
|
|
|
impl<AccountId, Bound: Get<u32>> TryFrom<pezsp_npos_elections::Support<AccountId>>
|
|
for BoundedSupport<AccountId, Bound>
|
|
{
|
|
type Error = &'static str;
|
|
fn try_from(s: pezsp_npos_elections::Support<AccountId>) -> Result<Self, Self::Error> {
|
|
let voters = s.voters.try_into().map_err(|_| "voters bound not respected")?;
|
|
Ok(Self { voters, total: s.total })
|
|
}
|
|
}
|
|
|
|
impl<AccountId: Clone, Bound: Get<u32>> BoundedSupport<AccountId, Bound> {
|
|
/// Try and construct a `BoundedSupport` from an unbounded version, and reside to sorting and
|
|
/// truncating if needed.
|
|
///
|
|
/// Returns the number of backers removed.
|
|
pub fn sorted_truncate_from(
|
|
mut support: pezsp_npos_elections::Support<AccountId>,
|
|
) -> (Self, u32) {
|
|
// If bounds meet, then short circuit.
|
|
if let Ok(bounded) = support.clone().try_into() {
|
|
return (bounded, 0);
|
|
}
|
|
|
|
let pre_len = support.voters.len();
|
|
// sort support based on stake of each backer, low to high.
|
|
// Note: we don't sort high to low and truncate because we would have to track `total`
|
|
// updates, so we need one iteration anyhow.
|
|
support.voters.sort_by(|a, b| a.1.cmp(&b.1));
|
|
// then do the truncation.
|
|
let mut bounded = Self { voters: Default::default(), total: 0 };
|
|
while let Some((voter, weight)) = support.voters.pop() {
|
|
if let Err(_) = bounded.voters.try_push((voter, weight)) {
|
|
break;
|
|
}
|
|
bounded.total += weight;
|
|
}
|
|
let post_len = bounded.voters.len();
|
|
(bounded, (pre_len - post_len) as u32)
|
|
}
|
|
}
|
|
|
|
/// A bounded vector of [`BoundedSupport`].
|
|
///
|
|
/// A [`BoundedSupports`] is a set of [`pezsp_npos_elections::Supports`] which are bounded in two
|
|
/// dimensions. `BInner` corresponds to the bound of the maximum backers per voter and `BOuter`
|
|
/// corresponds to the bound of the maximum winners that the bounded supports may contain.
|
|
///
|
|
/// With the bounds, we control the maximum size of a bounded supports instance.
|
|
#[derive(Encode, Decode, DecodeWithMemTracking, TypeInfo, DefaultNoBound, MaxEncodedLen)]
|
|
#[codec(mel_bound(AccountId: MaxEncodedLen, BOuter: Get<u32>, BInner: Get<u32>))]
|
|
#[scale_info(skip_type_params(BOuter, BInner))]
|
|
pub struct BoundedSupports<AccountId, BOuter: Get<u32>, BInner: Get<u32>>(
|
|
pub BoundedVec<(AccountId, BoundedSupport<AccountId, BInner>), BOuter>,
|
|
);
|
|
|
|
/// Try and build yourself from another `BoundedSupports` with a different set of types.
|
|
pub trait TryFromOtherBounds<AccountId, BOtherOuter: Get<u32>, BOtherInner: Get<u32>> {
|
|
fn try_from_other_bounds(
|
|
other: BoundedSupports<AccountId, BOtherOuter, BOtherInner>,
|
|
) -> Result<Self, crate::Error>
|
|
where
|
|
Self: Sized;
|
|
}
|
|
|
|
impl<
|
|
AccountId,
|
|
BOuter: Get<u32>,
|
|
BInner: Get<u32>,
|
|
BOtherOuter: Get<u32>,
|
|
BOuterInner: Get<u32>,
|
|
> TryFromOtherBounds<AccountId, BOtherOuter, BOuterInner>
|
|
for BoundedSupports<AccountId, BOuter, BInner>
|
|
{
|
|
fn try_from_other_bounds(
|
|
other: BoundedSupports<AccountId, BOtherOuter, BOuterInner>,
|
|
) -> Result<Self, crate::Error> {
|
|
// NOTE: we might as well do this with unsafe rust and do it faster.
|
|
if BOtherOuter::get() <= BOuter::get() && BOuterInner::get() <= BInner::get() {
|
|
// Both ouf our bounds are larger than the input's bound, can convert.
|
|
let supports = other
|
|
.into_iter()
|
|
.map(|(acc, b_support)| {
|
|
b_support
|
|
.try_into()
|
|
.defensive_map_err(|_| Error::BoundsExceeded)
|
|
.map(|b_support| (acc, b_support))
|
|
})
|
|
.collect::<Result<Vec<_>, _>>()
|
|
.defensive()?;
|
|
supports.try_into()
|
|
} else {
|
|
Err(crate::Error::BoundsExceeded)
|
|
}
|
|
}
|
|
}
|
|
|
|
impl<AccountId: Clone, BOuter: Get<u32>, BInner: Get<u32>>
|
|
BoundedSupports<AccountId, BOuter, BInner>
|
|
{
|
|
/// Try and construct a `BoundedSupports` from an unbounded version, and reside to sorting and
|
|
/// truncating if need ne.
|
|
///
|
|
/// Two u32s returned are number of winners and backers removed respectively.
|
|
pub fn sorted_truncate_from(supports: Supports<AccountId>) -> (Self, u32, u32) {
|
|
// if bounds, meet, short circuit
|
|
if let Ok(bounded) = supports.clone().try_into() {
|
|
return (bounded, 0, 0);
|
|
}
|
|
|
|
let pre_winners = supports.len();
|
|
let mut backers_removed = 0;
|
|
// first, convert all inner supports.
|
|
let mut inner_supports = supports
|
|
.into_iter()
|
|
.map(|(account, support)| {
|
|
let (bounded, removed) =
|
|
BoundedSupport::<AccountId, BInner>::sorted_truncate_from(support);
|
|
backers_removed += removed;
|
|
(account, bounded)
|
|
})
|
|
.collect::<Vec<_>>();
|
|
|
|
// then sort outer supports based on total stake, high to low
|
|
inner_supports.sort_by(|a, b| b.1.total.cmp(&a.1.total));
|
|
|
|
// then take the first slice that can fit.
|
|
let bounded = BoundedSupports(BoundedVec::<
|
|
(AccountId, BoundedSupport<AccountId, BInner>),
|
|
BOuter,
|
|
>::truncate_from(inner_supports));
|
|
let post_winners = bounded.len();
|
|
(bounded, (pre_winners - post_winners) as u32, backers_removed)
|
|
}
|
|
}
|
|
|
|
/// Helper trait for conversion of a vector of unbounded supports into a vector of bounded ones.
|
|
pub trait TryFromUnboundedPagedSupports<AccountId, BOuter: Get<u32>, BInner: Get<u32>> {
|
|
fn try_from_unbounded_paged(
|
|
self,
|
|
) -> Result<Vec<BoundedSupports<AccountId, BOuter, BInner>>, crate::Error>
|
|
where
|
|
Self: Sized;
|
|
}
|
|
|
|
impl<AccountId, BOuter: Get<u32>, BInner: Get<u32>>
|
|
TryFromUnboundedPagedSupports<AccountId, BOuter, BInner> for Vec<Supports<AccountId>>
|
|
{
|
|
fn try_from_unbounded_paged(
|
|
self,
|
|
) -> Result<Vec<BoundedSupports<AccountId, BOuter, BInner>>, crate::Error> {
|
|
self.into_iter()
|
|
.map(|s| s.try_into().map_err(|_| crate::Error::BoundsExceeded))
|
|
.collect::<Result<Vec<_>, _>>()
|
|
}
|
|
}
|
|
|
|
impl<AccountId, BOuter: Get<u32>, BInner: Get<u32>> pezsp_npos_elections::EvaluateSupport
|
|
for BoundedSupports<AccountId, BOuter, BInner>
|
|
{
|
|
fn evaluate(&self) -> pezsp_npos_elections::ElectionScore {
|
|
pezsp_npos_elections::evaluate_support(self.iter().map(|(_, s)| s))
|
|
}
|
|
}
|
|
|
|
impl<AccountId, BOuter: Get<u32>, BInner: Get<u32>> pezsp_std::ops::DerefMut
|
|
for BoundedSupports<AccountId, BOuter, BInner>
|
|
{
|
|
fn deref_mut(&mut self) -> &mut Self::Target {
|
|
&mut self.0
|
|
}
|
|
}
|
|
|
|
impl<AccountId: Debug, BOuter: Get<u32>, BInner: Get<u32>> Debug
|
|
for BoundedSupports<AccountId, BOuter, BInner>
|
|
{
|
|
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
|
|
for s in self.0.iter() {
|
|
write!(f, "({:?}, {:?}, {:?}) ", s.0, s.1.total, s.1.voters)?;
|
|
}
|
|
Ok(())
|
|
}
|
|
}
|
|
|
|
impl<AccountId: PartialEq, BOuter: Get<u32>, BInner: Get<u32>> PartialEq
|
|
for BoundedSupports<AccountId, BOuter, BInner>
|
|
{
|
|
fn eq(&self, other: &Self) -> bool {
|
|
self.0 == other.0
|
|
}
|
|
}
|
|
|
|
impl<AccountId, BOuter: Get<u32>, BInner: Get<u32>> Into<Supports<AccountId>>
|
|
for BoundedSupports<AccountId, BOuter, BInner>
|
|
{
|
|
fn into(self) -> Supports<AccountId> {
|
|
// NOTE: can be done faster with unsafe code.
|
|
self.0.into_iter().map(|(acc, b_support)| (acc, b_support.into())).collect()
|
|
}
|
|
}
|
|
|
|
impl<AccountId, BOuter: Get<u32>, BInner: Get<u32>>
|
|
From<BoundedVec<(AccountId, BoundedSupport<AccountId, BInner>), BOuter>>
|
|
for BoundedSupports<AccountId, BOuter, BInner>
|
|
{
|
|
fn from(t: BoundedVec<(AccountId, BoundedSupport<AccountId, BInner>), BOuter>) -> Self {
|
|
Self(t)
|
|
}
|
|
}
|
|
|
|
impl<AccountId: Clone, BOuter: Get<u32>, BInner: Get<u32>> Clone
|
|
for BoundedSupports<AccountId, BOuter, BInner>
|
|
{
|
|
fn clone(&self) -> Self {
|
|
Self(self.0.clone())
|
|
}
|
|
}
|
|
|
|
impl<AccountId, BOuter: Get<u32>, BInner: Get<u32>> pezsp_std::ops::Deref
|
|
for BoundedSupports<AccountId, BOuter, BInner>
|
|
{
|
|
type Target = BoundedVec<(AccountId, BoundedSupport<AccountId, BInner>), BOuter>;
|
|
|
|
fn deref(&self) -> &Self::Target {
|
|
&self.0
|
|
}
|
|
}
|
|
|
|
impl<AccountId, BOuter: Get<u32>, BInner: Get<u32>> IntoIterator
|
|
for BoundedSupports<AccountId, BOuter, BInner>
|
|
{
|
|
type Item = (AccountId, BoundedSupport<AccountId, BInner>);
|
|
type IntoIter = pezsp_std::vec::IntoIter<Self::Item>;
|
|
|
|
fn into_iter(self) -> Self::IntoIter {
|
|
self.0.into_iter()
|
|
}
|
|
}
|
|
|
|
impl<AccountId, BOuter: Get<u32>, BInner: Get<u32>> TryFrom<Supports<AccountId>>
|
|
for BoundedSupports<AccountId, BOuter, BInner>
|
|
{
|
|
type Error = crate::Error;
|
|
|
|
fn try_from(supports: Supports<AccountId>) -> Result<Self, Self::Error> {
|
|
// optimization note: pre-allocate outer bounded vec.
|
|
let mut outer_bounded_supports = BoundedVec::<
|
|
(AccountId, BoundedSupport<AccountId, BInner>),
|
|
BOuter,
|
|
>::with_bounded_capacity(
|
|
supports.len().min(BOuter::get() as usize)
|
|
);
|
|
|
|
// optimization note: avoid intermediate allocations.
|
|
supports
|
|
.into_iter()
|
|
.map(|(account, support)| (account, support.try_into().map_err(|_| ())))
|
|
.try_for_each(|(account, maybe_bounded_supports)| {
|
|
outer_bounded_supports
|
|
.try_push((account, maybe_bounded_supports?))
|
|
.map_err(|_| ())
|
|
})
|
|
.map_err(|_| crate::Error::BoundsExceeded)?;
|
|
|
|
Ok(outer_bounded_supports.into())
|
|
}
|
|
}
|
|
|
|
/// Same as `BoundedSupports` but parameterized by an `ElectionProvider`.
|
|
pub type BoundedSupportsOf<E> = BoundedSupports<
|
|
<E as ElectionProvider>::AccountId,
|
|
<E as ElectionProvider>::MaxWinnersPerPage,
|
|
<E as ElectionProvider>::MaxBackersPerWinner,
|
|
>;
|
|
|
|
pezsp_core::generate_feature_enabled_macro!(
|
|
runtime_benchmarks_enabled,
|
|
feature = "runtime-benchmarks",
|
|
$
|
|
);
|
|
|
|
pezsp_core::generate_feature_enabled_macro!(
|
|
runtime_benchmarks_or_std_enabled,
|
|
any(feature = "runtime-benchmarks", feature = "std"),
|
|
$
|
|
);
|