clean the interface of supports map (#9674)

* clean the interface of supports map, make it a bit cleaner and more efficients

* Fix stiff

* fix one test

* Fix warnings
This commit is contained in:
Kian Paimani
2021-09-10 19:39:48 +01:00
committed by GitHub
parent b3092ead23
commit be69e4d2b2
14 changed files with 67 additions and 189 deletions
@@ -143,7 +143,7 @@ fn solution_with_size<T: Config>(
let solution = let solution =
<SolutionOf<T>>::from_assignment(&assignments, &voter_index, &target_index).unwrap(); <SolutionOf<T>>::from_assignment(&assignments, &voter_index, &target_index).unwrap();
let score = solution.clone().score(&winners, stake_of, voter_at, target_at).unwrap(); let score = solution.clone().score(stake_of, voter_at, target_at).unwrap();
let round = <MultiPhase<T>>::round(); let round = <MultiPhase<T>>::round();
assert!(score[0] > 0, "score is zero, this probably means that the stakes are not set."); assert!(score[0] > 0, "score is zero, this probably means that the stakes are not set.");
@@ -535,8 +535,6 @@ pub enum FeasibilityError {
InvalidVote, InvalidVote,
/// A voter is invalid. /// A voter is invalid.
InvalidVoter, InvalidVoter,
/// A winner is invalid.
InvalidWinner,
/// The given score was invalid. /// The given score was invalid.
InvalidScore, InvalidScore,
/// The provided round is incorrect. /// The provided round is incorrect.
@@ -1395,17 +1393,8 @@ impl<T: Config> Pallet<T> {
let target_at = helpers::target_at_fn::<T>(&snapshot_targets); let target_at = helpers::target_at_fn::<T>(&snapshot_targets);
let voter_index = helpers::voter_index_fn_usize::<T>(&cache); let voter_index = helpers::voter_index_fn_usize::<T>(&cache);
// First, make sure that all the winners are sane. // Then convert solution -> assignment. This will fail if any of the indices are gibberish,
// OPTIMIZATION: we could first build the assignments, and then extract the winners directly // namely any of the voters or targets.
// from that, as that would eliminate a little bit of duplicate work. For now, we keep them
// separate: First extract winners separately from solution, and then assignments. This is
// also better, because we can reject solutions that don't meet `desired_targets` early on.
let winners = winners
.into_iter()
.map(|i| target_at(i).ok_or(FeasibilityError::InvalidWinner))
.collect::<Result<Vec<T::AccountId>, FeasibilityError>>()?;
// Then convert solution -> assignment. This will fail if any of the indices are gibberish.
let assignments = solution let assignments = solution
.into_assignment(voter_at, target_at) .into_assignment(voter_at, target_at)
.map_err::<FeasibilityError, _>(Into::into)?; .map_err::<FeasibilityError, _>(Into::into)?;
@@ -1441,14 +1430,10 @@ impl<T: Config> Pallet<T> {
// This might fail if the normalization fails. Very unlikely. See `integrity_test`. // This might fail if the normalization fails. Very unlikely. See `integrity_test`.
let staked_assignments = assignment_ratio_to_staked_normalized(assignments, stake_of) let staked_assignments = assignment_ratio_to_staked_normalized(assignments, stake_of)
.map_err::<FeasibilityError, _>(Into::into)?; .map_err::<FeasibilityError, _>(Into::into)?;
let supports = sp_npos_elections::to_supports(&staked_assignments);
// This might fail if one of the voter edges is pointing to a non-winner, which is not
// really possible anymore because all the winners come from the same `solution`.
let supports = sp_npos_elections::to_supports(&winners, &staked_assignments)
.map_err::<FeasibilityError, _>(Into::into)?;
// Finally, check that the claimed score was indeed correct. // Finally, check that the claimed score was indeed correct.
let known_score = (&supports).evaluate(); let known_score = supports.evaluate();
ensure!(known_score == score, FeasibilityError::InvalidScore); ensure!(known_score == score, FeasibilityError::InvalidScore);
Ok(ReadySolution { supports, compute, score }) Ok(ReadySolution { supports, compute, score })
@@ -1653,7 +1638,7 @@ mod feasibility_check {
}); });
assert_noop!( assert_noop!(
MultiPhase::feasibility_check(raw, COMPUTE), MultiPhase::feasibility_check(raw, COMPUTE),
FeasibilityError::InvalidWinner FeasibilityError::NposElection(sp_npos_elections::Error::SolutionInvalidIndex)
); );
}) })
} }
@@ -30,8 +30,8 @@ use sp_core::{
H256, H256,
}; };
use sp_npos_elections::{ use sp_npos_elections::{
assignment_ratio_to_staked_normalized, seq_phragmen, to_supports, to_without_backing, assignment_ratio_to_staked_normalized, seq_phragmen, to_supports, ElectionResult,
ElectionResult, EvaluateSupport, ExtendedBalance, NposSolution, EvaluateSupport, ExtendedBalance, NposSolution,
}; };
use sp_runtime::{ use sp_runtime::{
testing::Header, testing::Header,
@@ -157,7 +157,8 @@ pub fn raw_solution() -> RawSolution<SolutionOf<Runtime>> {
let RoundSnapshot { voters, targets } = MultiPhase::snapshot().unwrap(); let RoundSnapshot { voters, targets } = MultiPhase::snapshot().unwrap();
let desired_targets = MultiPhase::desired_targets().unwrap(); let desired_targets = MultiPhase::desired_targets().unwrap();
let ElectionResult { winners, assignments } = seq_phragmen::<_, SolutionAccuracyOf<Runtime>>( let ElectionResult { winners: _, assignments } =
seq_phragmen::<_, SolutionAccuracyOf<Runtime>>(
desired_targets as usize, desired_targets as usize,
targets.clone(), targets.clone(),
voters.clone(), voters.clone(),
@@ -171,11 +172,9 @@ pub fn raw_solution() -> RawSolution<SolutionOf<Runtime>> {
let target_index = helpers::target_index_fn_linear::<Runtime>(&targets); let target_index = helpers::target_index_fn_linear::<Runtime>(&targets);
let stake_of = helpers::stake_of_fn::<Runtime>(&voters, &cache); let stake_of = helpers::stake_of_fn::<Runtime>(&voters, &cache);
let winners = to_without_backing(winners);
let score = { let score = {
let staked = assignment_ratio_to_staked_normalized(assignments.clone(), &stake_of).unwrap(); let staked = assignment_ratio_to_staked_normalized(assignments.clone(), &stake_of).unwrap();
to_supports(&winners, &staked).unwrap().evaluate() to_supports(&staked).evaluate()
}; };
let solution = let solution =
<SolutionOf<Runtime>>::from_assignment(&assignments, &voter_index, &target_index).unwrap(); <SolutionOf<Runtime>>::from_assignment(&assignments, &voter_index, &target_index).unwrap();
@@ -26,7 +26,6 @@ use codec::{Decode, Encode, HasCompact};
use frame_support::{ use frame_support::{
storage::bounded_btree_map::BoundedBTreeMap, storage::bounded_btree_map::BoundedBTreeMap,
traits::{Currency, Get, OnUnbalanced, ReservableCurrency}, traits::{Currency, Get, OnUnbalanced, ReservableCurrency},
DebugNoBound,
}; };
use sp_arithmetic::traits::SaturatedConversion; use sp_arithmetic::traits::SaturatedConversion;
use sp_npos_elections::{is_score_better, ElectionScore, NposSolution}; use sp_npos_elections::{is_score_better, ElectionScore, NposSolution};
@@ -113,7 +112,7 @@ pub enum InsertResult<T: Config> {
/// Mask type which pretends to be a set of `SignedSubmissionOf<T>`, while in fact delegating to the /// Mask type which pretends to be a set of `SignedSubmissionOf<T>`, while in fact delegating to the
/// actual implementations in `SignedSubmissionIndices<T>`, `SignedSubmissionsMap<T>`, and /// actual implementations in `SignedSubmissionIndices<T>`, `SignedSubmissionsMap<T>`, and
/// `SignedSubmissionNextIndex<T>`. /// `SignedSubmissionNextIndex<T>`.
#[cfg_attr(feature = "std", derive(DebugNoBound))] #[cfg_attr(feature = "std", derive(frame_support::DebugNoBound))]
pub struct SignedSubmissions<T: Config> { pub struct SignedSubmissions<T: Config> {
indices: SubmissionIndicesOf<T>, indices: SubmissionIndicesOf<T>,
next_idx: u32, next_idx: u32,
@@ -315,7 +315,7 @@ impl<T: Config> Pallet<T> {
SolutionOf::<T>::try_from(assignments).map(|s| s.encoded_size()) SolutionOf::<T>::try_from(assignments).map(|s| s.encoded_size())
}; };
let ElectionResult { assignments, winners } = election_result; let ElectionResult { assignments, winners: _ } = election_result;
// Reduce (requires round-trip to staked form) // Reduce (requires round-trip to staked form)
let sorted_assignments = { let sorted_assignments = {
@@ -374,8 +374,7 @@ impl<T: Config> Pallet<T> {
let solution = SolutionOf::<T>::try_from(&index_assignments)?; let solution = SolutionOf::<T>::try_from(&index_assignments)?;
// re-calc score. // re-calc score.
let winners = sp_npos_elections::to_without_backing(winners); let score = solution.clone().score(stake_of, voter_at, target_at)?;
let score = solution.clone().score(&winners, stake_of, voter_at, target_at)?;
let round = Self::round(); let round = Self::round();
Ok((RawSolution { solution, score, round }, size)) Ok((RawSolution { solution, score, round }, size))
@@ -81,14 +81,13 @@ impl<T: Config> ElectionProvider<T::AccountId, T::BlockNumber> for OnChainSequen
let stake_of = let stake_of =
|w: &T::AccountId| -> VoteWeight { stake_map.get(w).cloned().unwrap_or_default() }; |w: &T::AccountId| -> VoteWeight { stake_map.get(w).cloned().unwrap_or_default() };
let ElectionResult { winners, assignments } = let ElectionResult { winners: _, assignments } =
seq_phragmen::<_, T::Accuracy>(desired_targets as usize, targets, voters, None) seq_phragmen::<_, T::Accuracy>(desired_targets as usize, targets, voters, None)
.map_err(Error::from)?; .map_err(Error::from)?;
let staked = assignment_ratio_to_staked_normalized(assignments, &stake_of)?; let staked = assignment_ratio_to_staked_normalized(assignments, &stake_of)?;
let winners = to_without_backing(winners);
to_supports(&winners, &staked).map_err(Error::from) Ok(to_supports(&staked))
} }
} }
+6 -5
View File
@@ -40,8 +40,8 @@ pub use impls::*;
use crate::{ use crate::{
migrations, slashing, weights::WeightInfo, ActiveEraInfo, BalanceOf, EraIndex, EraPayout, migrations, slashing, weights::WeightInfo, ActiveEraInfo, BalanceOf, EraIndex, EraPayout,
EraRewardPoints, Exposure, Forcing, NegativeImbalanceOf, Nominations, PositiveImbalanceOf, EraRewardPoints, Exposure, Forcing, NegativeImbalanceOf, Nominations, PositiveImbalanceOf,
Releases, RewardDestination, SessionInterface, StakerStatus, StakingLedger, UnappliedSlash, Releases, RewardDestination, SessionInterface, StakingLedger, UnappliedSlash, UnlockChunk,
UnlockChunk, ValidatorPrefs, ValidatorPrefs,
}; };
pub const MAX_UNLOCKING_CHUNKS: usize = 32; pub const MAX_UNLOCKING_CHUNKS: usize = 32;
@@ -453,7 +453,8 @@ pub mod pallet {
pub force_era: Forcing, pub force_era: Forcing,
pub slash_reward_fraction: Perbill, pub slash_reward_fraction: Perbill,
pub canceled_payout: BalanceOf<T>, pub canceled_payout: BalanceOf<T>,
pub stakers: Vec<(T::AccountId, T::AccountId, BalanceOf<T>, StakerStatus<T::AccountId>)>, pub stakers:
Vec<(T::AccountId, T::AccountId, BalanceOf<T>, crate::StakerStatus<T::AccountId>)>,
pub min_nominator_bond: BalanceOf<T>, pub min_nominator_bond: BalanceOf<T>,
pub min_validator_bond: BalanceOf<T>, pub min_validator_bond: BalanceOf<T>,
} }
@@ -502,11 +503,11 @@ pub mod pallet {
RewardDestination::Staked, RewardDestination::Staked,
)); ));
frame_support::assert_ok!(match status { frame_support::assert_ok!(match status {
StakerStatus::Validator => <Pallet<T>>::validate( crate::StakerStatus::Validator => <Pallet<T>>::validate(
T::Origin::from(Some(controller.clone()).into()), T::Origin::from(Some(controller.clone()).into()),
Default::default(), Default::default(),
), ),
StakerStatus::Nominator(votes) => <Pallet<T>>::nominate( crate::StakerStatus::Nominator(votes) => <Pallet<T>>::nominate(
T::Origin::from(Some(controller.clone()).into()), T::Origin::from(Some(controller.clone()).into()),
votes.iter().map(|l| T::Lookup::unlookup(l.clone())).collect(), votes.iter().map(|l| T::Lookup::unlookup(l.clone())).collect(),
), ),
@@ -24,7 +24,7 @@ use honggfuzz::fuzz;
use rand::{self, SeedableRng}; use rand::{self, SeedableRng};
use sp_npos_elections::{ use sp_npos_elections::{
assignment_ratio_to_staked_normalized, is_score_better, seq_phragmen, to_supports, assignment_ratio_to_staked_normalized, is_score_better, seq_phragmen, to_supports,
to_without_backing, EvaluateSupport, VoteWeight, EvaluateSupport, VoteWeight,
}; };
use sp_runtime::Perbill; use sp_runtime::Perbill;
@@ -58,8 +58,7 @@ fn main() {
&stake_of, &stake_of,
) )
.unwrap(); .unwrap();
let winners = to_without_backing(unbalanced.winners.clone()); let score = to_supports(staked.as_ref()).evaluate();
let score = to_supports(winners.as_ref(), staked.as_ref()).unwrap().evaluate();
if score[0] == 0 { if score[0] == 0 {
// such cases cannot be improved by balancing. // such cases cannot be improved by balancing.
@@ -83,8 +82,7 @@ fn main() {
&stake_of, &stake_of,
) )
.unwrap(); .unwrap();
let winners = to_without_backing(balanced.winners); to_supports(staked.as_ref()).evaluate()
to_supports(winners.as_ref(), staked.as_ref()).unwrap().evaluate()
}; };
let enhance = is_score_better(balanced_score, unbalanced_score, Perbill::zero()); let enhance = is_score_better(balanced_score, unbalanced_score, Perbill::zero());
@@ -23,8 +23,8 @@ use common::*;
use honggfuzz::fuzz; use honggfuzz::fuzz;
use rand::{self, SeedableRng}; use rand::{self, SeedableRng};
use sp_npos_elections::{ use sp_npos_elections::{
assignment_ratio_to_staked_normalized, is_score_better, phragmms, to_supports, assignment_ratio_to_staked_normalized, is_score_better, phragmms, to_supports, EvaluateSupport,
to_without_backing, EvaluateSupport, VoteWeight, VoteWeight,
}; };
use sp_runtime::Perbill; use sp_runtime::Perbill;
@@ -58,8 +58,7 @@ fn main() {
&stake_of, &stake_of,
) )
.unwrap(); .unwrap();
let winners = to_without_backing(unbalanced.winners.clone()); let score = to_supports(&staked).evaluate();
let score = to_supports(&winners, &staked).unwrap().evaluate();
if score[0] == 0 { if score[0] == 0 {
// such cases cannot be improved by balancing. // such cases cannot be improved by balancing.
@@ -80,8 +79,7 @@ fn main() {
let staked = let staked =
assignment_ratio_to_staked_normalized(balanced.assignments.clone(), &stake_of) assignment_ratio_to_staked_normalized(balanced.assignments.clone(), &stake_of)
.unwrap(); .unwrap();
let winners = to_without_backing(balanced.winners); to_supports(staked.as_ref()).evaluate()
to_supports(winners.as_ref(), staked.as_ref()).unwrap().evaluate()
}; };
let enhance = is_score_better(balanced_score, unbalanced_score, Perbill::zero()); let enhance = is_score_better(balanced_score, unbalanced_score, Perbill::zero());
@@ -104,13 +104,11 @@ fn generate_random_phragmen_assignment(
} }
fn assert_assignments_equal( fn assert_assignments_equal(
winners: &Vec<AccountId>,
ass1: &Vec<StakedAssignment<AccountId>>, ass1: &Vec<StakedAssignment<AccountId>>,
ass2: &Vec<StakedAssignment<AccountId>>, ass2: &Vec<StakedAssignment<AccountId>>,
) { ) {
let support_1 = to_support_map::<AccountId>(winners, ass1).unwrap(); let support_1 = to_support_map::<AccountId>(ass1);
let support_2 = to_support_map::<AccountId>(winners, ass2).unwrap(); let support_2 = to_support_map::<AccountId>(ass2);
for (who, support) in support_1.iter() { for (who, support) in support_1.iter() {
assert_eq!(support.total, support_2.get(who).unwrap().total); assert_eq!(support.total, support_2.get(who).unwrap().total);
} }
@@ -134,7 +132,7 @@ fn reduce_and_compare(assignment: &Vec<StakedAssignment<AccountId>>, winners: &V
num_changed, num_changed,
); );
assert_assignments_equal(winners, &assignment, &altered_assignment); assert_assignments_equal(&assignment, &altered_assignment);
} }
fn assignment_len(assignments: &[StakedAssignment<AccountId>]) -> u32 { fn assignment_len(assignments: &[StakedAssignment<AccountId>]) -> u32 {
@@ -17,9 +17,7 @@
//! Helper methods for npos-elections. //! Helper methods for npos-elections.
use crate::{ use crate::{Assignment, Error, IdentifierT, PerThing128, StakedAssignment, VoteWeight};
Assignment, Error, IdentifierT, PerThing128, StakedAssignment, VoteWeight, WithApprovalOf,
};
use sp_arithmetic::PerThing; use sp_arithmetic::PerThing;
use sp_std::prelude::*; use sp_std::prelude::*;
@@ -81,11 +79,6 @@ pub fn assignment_staked_to_ratio_normalized<A: IdentifierT, P: PerThing128>(
Ok(ratio) Ok(ratio)
} }
/// consumes a vector of winners with backing stake to just winners.
pub fn to_without_backing<A: IdentifierT>(winners: Vec<WithApprovalOf<A>>) -> Vec<A> {
winners.into_iter().map(|(who, _)| who).collect::<Vec<A>>()
}
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use super::*; use super::*;
+17 -77
View File
@@ -146,9 +146,6 @@ pub type ExtendedBalance = u128;
/// [`EvaluateSupport::evaluate`]. /// [`EvaluateSupport::evaluate`].
pub type ElectionScore = [ExtendedBalance; 3]; pub type ElectionScore = [ExtendedBalance; 3];
/// A winner, with their respective approval stake.
pub type WithApprovalOf<A> = (A, ExtendedBalance);
/// A pointer to a candidate struct with interior mutability. /// A pointer to a candidate struct with interior mutability.
pub type CandidatePtr<A> = Rc<RefCell<Candidate<A>>>; pub type CandidatePtr<A> = Rc<RefCell<Candidate<A>>>;
@@ -327,7 +324,7 @@ impl<AccountId: IdentifierT> Voter<AccountId> {
pub struct ElectionResult<AccountId, P: PerThing> { pub struct ElectionResult<AccountId, P: PerThing> {
/// Just winners zipped with their approval stake. Note that the approval stake is merely the /// Just winners zipped with their approval stake. Note that the approval stake is merely the
/// sub of their received stake and could be used for very basic sorting and approval voting. /// sub of their received stake and could be used for very basic sorting and approval voting.
pub winners: Vec<WithApprovalOf<AccountId>>, pub winners: Vec<(AccountId, ExtendedBalance)>,
/// Individual assignments. for each tuple, the first elements is a voter and the second is the /// Individual assignments. for each tuple, the first elements is a voter and the second is the
/// list of candidates that it supports. /// list of candidates that it supports.
pub assignments: Vec<Assignment<AccountId, P>>, pub assignments: Vec<Assignment<AccountId, P>>,
@@ -361,107 +358,50 @@ pub type Supports<A> = Vec<(A, Support<A>)>;
/// This is more helpful than a normal [`Supports`] as it allows faster error checking. /// This is more helpful than a normal [`Supports`] as it allows faster error checking.
pub type SupportMap<A> = BTreeMap<A, Support<A>>; pub type SupportMap<A> = BTreeMap<A, Support<A>>;
/// Helper trait to convert from a support map to a flat support vector. /// Build the support map from the assignments.
pub trait FlattenSupportMap<A> {
/// Flatten the support.
fn flatten(self) -> Supports<A>;
}
impl<A> FlattenSupportMap<A> for SupportMap<A> {
fn flatten(self) -> Supports<A> {
self.into_iter().collect::<Vec<_>>()
}
}
/// Build the support map from the winners and assignments.
///
/// The list of winners is basically a redundancy for error checking only; It ensures that all the
/// targets pointed to by the [`Assignment`] are present in the `winners`.
pub fn to_support_map<AccountId: IdentifierT>( pub fn to_support_map<AccountId: IdentifierT>(
winners: &[AccountId],
assignments: &[StakedAssignment<AccountId>], assignments: &[StakedAssignment<AccountId>],
) -> Result<SupportMap<AccountId>, Error> { ) -> SupportMap<AccountId> {
// Initialize the support of each candidate. let mut supports = <BTreeMap<AccountId, Support<AccountId>>>::new();
let mut supports = <SupportMap<AccountId>>::new();
winners.iter().for_each(|e| {
supports.insert(e.clone(), Default::default());
});
// build support struct. // build support struct.
for StakedAssignment { who, distribution } in assignments.iter() { for StakedAssignment { who, distribution } in assignments.into_iter() {
for (c, weight_extended) in distribution.iter() { for (c, weight_extended) in distribution.into_iter() {
if let Some(support) = supports.get_mut(c) { let mut support = supports.entry(c.clone()).or_default();
support.total = support.total.saturating_add(*weight_extended); support.total = support.total.saturating_add(*weight_extended);
support.voters.push((who.clone(), *weight_extended)); support.voters.push((who.clone(), *weight_extended));
} else {
return Err(Error::InvalidSupportEdge)
} }
} }
}
Ok(supports) supports
} }
/// Same as [`to_support_map`] except it calls `FlattenSupportMap` on top of the result to return a /// Same as [`to_support_map`] except it returns a
/// flat vector. /// flat vector.
///
/// Similar to [`to_support_map`], `winners` is used for error checking.
pub fn to_supports<AccountId: IdentifierT>( pub fn to_supports<AccountId: IdentifierT>(
winners: &[AccountId],
assignments: &[StakedAssignment<AccountId>], assignments: &[StakedAssignment<AccountId>],
) -> Result<Supports<AccountId>, Error> { ) -> Supports<AccountId> {
to_support_map(winners, assignments).map(FlattenSupportMap::flatten) to_support_map(assignments).into_iter().collect()
} }
/// Extension trait for evaluating a support map or vector. /// Extension trait for evaluating a support map or vector.
pub trait EvaluateSupport<K> { pub trait EvaluateSupport {
/// Evaluate a support map. The returned tuple contains: /// Evaluate a support map. The returned tuple contains:
/// ///
/// - Minimum support. This value must be **maximized**. /// - Minimum support. This value must be **maximized**.
/// - Sum of all supports. This value must be **maximized**. /// - Sum of all supports. This value must be **maximized**.
/// - Sum of all supports squared. This value must be **minimized**. /// - Sum of all supports squared. This value must be **minimized**.
fn evaluate(self) -> ElectionScore; fn evaluate(&self) -> ElectionScore;
} }
/// A common wrapper trait for both (&A, &B) and &(A, B). impl<AccountId: IdentifierT> EvaluateSupport for Supports<AccountId> {
/// fn evaluate(&self) -> ElectionScore {
/// This allows us to implemented something for both `Vec<_>` and `BTreeMap<_>`, such as
/// [`EvaluateSupport`].
pub trait TupleRef<K, V> {
fn extract(&self) -> (&K, &V);
}
impl<K, V> TupleRef<K, V> for &(K, V) {
fn extract(&self) -> (&K, &V) {
(&self.0, &self.1)
}
}
impl<K, V> TupleRef<K, V> for (K, V) {
fn extract(&self) -> (&K, &V) {
(&self.0, &self.1)
}
}
impl<K, V> TupleRef<K, V> for (&K, &V) {
fn extract(&self) -> (&K, &V) {
(self.0, self.1)
}
}
impl<A, C, I> EvaluateSupport<A> for C
where
C: IntoIterator<Item = I>,
I: TupleRef<A, Support<A>>,
A: IdentifierT,
{
fn evaluate(self) -> ElectionScore {
let mut min_support = ExtendedBalance::max_value(); let mut min_support = ExtendedBalance::max_value();
let mut sum: ExtendedBalance = Zero::zero(); let mut sum: ExtendedBalance = Zero::zero();
// NOTE: The third element might saturate but fine for now since this will run on-chain and // NOTE: The third element might saturate but fine for now since this will run on-chain and
// need to be fast. // need to be fast.
let mut sum_squared: ExtendedBalance = Zero::zero(); let mut sum_squared: ExtendedBalance = Zero::zero();
for item in self { for (_, support) in self {
let (_, support) = item.extract();
sum = sum.saturating_add(support.total); sum = sum.saturating_add(support.total);
let squared = support.total.saturating_mul(support.total); let squared = support.total.saturating_mul(support.total);
sum_squared = sum_squared.saturating_add(squared); sum_squared = sum_squared.saturating_add(squared);
@@ -19,8 +19,8 @@
use crate::{ use crate::{
balancing, helpers::*, is_score_better, mock::*, seq_phragmen, seq_phragmen_core, setup_inputs, balancing, helpers::*, is_score_better, mock::*, seq_phragmen, seq_phragmen_core, setup_inputs,
to_support_map, to_supports, Assignment, ElectionResult, EvaluateSupport, ExtendedBalance, to_support_map, Assignment, ElectionResult, ExtendedBalance, IndexAssignment, NposSolution,
IndexAssignment, NposSolution, StakedAssignment, Support, Voter, StakedAssignment, Support, Voter,
}; };
use rand::{self, SeedableRng}; use rand::{self, SeedableRng};
use sp_arithmetic::{PerU16, Perbill, Percent, Permill}; use sp_arithmetic::{PerU16, Perbill, Percent, Permill};
@@ -259,8 +259,7 @@ fn phragmen_poc_works() {
); );
let staked = assignment_ratio_to_staked(assignments, &stake_of); let staked = assignment_ratio_to_staked(assignments, &stake_of);
let winners = to_without_backing(winners); let support_map = to_support_map::<AccountId>(&staked);
let support_map = to_support_map::<AccountId>(&winners, &staked).unwrap();
assert_eq_uvec!( assert_eq_uvec!(
staked, staked,
@@ -315,8 +314,7 @@ fn phragmen_poc_works_with_balancing() {
); );
let staked = assignment_ratio_to_staked(assignments, &stake_of); let staked = assignment_ratio_to_staked(assignments, &stake_of);
let winners = to_without_backing(winners); let support_map = to_support_map::<AccountId>(&staked);
let support_map = to_support_map::<AccountId>(&winners, &staked).unwrap();
assert_eq_uvec!( assert_eq_uvec!(
staked, staked,
@@ -515,7 +513,7 @@ fn phragmen_large_scale_test() {
) )
.unwrap(); .unwrap();
assert_eq_uvec!(to_without_backing(winners.clone()), vec![24, 22]); assert_eq_uvec!(winners.iter().map(|(x, _)| *x).collect::<Vec<_>>(), vec![24, 22]);
check_assignments_sum(&assignments); check_assignments_sum(&assignments);
} }
@@ -649,8 +647,7 @@ fn phragmen_self_votes_should_be_kept() {
); );
let staked_assignments = assignment_ratio_to_staked(result.assignments, &stake_of); let staked_assignments = assignment_ratio_to_staked(result.assignments, &stake_of);
let winners = to_without_backing(result.winners); let supports = to_support_map::<AccountId>(&staked_assignments);
let supports = to_support_map::<AccountId>(&winners, &staked_assignments).unwrap();
assert_eq!(supports.get(&5u64), None); assert_eq!(supports.get(&5u64), None);
assert_eq!( assert_eq!(
@@ -670,9 +667,8 @@ fn duplicate_target_is_ignored() {
let ElectionResult { winners, assignments } = let ElectionResult { winners, assignments } =
seq_phragmen::<_, Perbill>(2, candidates, voters, None).unwrap(); seq_phragmen::<_, Perbill>(2, candidates, voters, None).unwrap();
let winners = to_without_backing(winners);
assert_eq!(winners, vec![(2), (3)]); assert_eq!(winners, vec![(2, 140), (3, 110)]);
assert_eq!( assert_eq!(
assignments assignments
.into_iter() .into_iter()
@@ -689,9 +685,8 @@ fn duplicate_target_is_ignored_when_winner() {
let ElectionResult { winners, assignments } = let ElectionResult { winners, assignments } =
seq_phragmen::<_, Perbill>(2, candidates, voters, None).unwrap(); seq_phragmen::<_, Perbill>(2, candidates, voters, None).unwrap();
let winners = to_without_backing(winners);
assert_eq!(winners, vec![1, 2]); assert_eq!(winners, vec![(1, 100), (2, 100)]);
assert_eq!( assert_eq!(
assignments assignments
.into_iter() .into_iter()
@@ -701,31 +696,6 @@ fn duplicate_target_is_ignored_when_winner() {
); );
} }
#[test]
fn support_map_and_vec_can_be_evaluated() {
let candidates = vec![1, 2, 3];
let voters = vec![(10, vec![1, 2]), (20, vec![1, 3]), (30, vec![2, 3])];
let stake_of = create_stake_of(&[(10, 10), (20, 20), (30, 30)]);
let ElectionResult { winners, assignments } = seq_phragmen::<_, Perbill>(
2,
candidates,
voters
.iter()
.map(|(ref v, ref vs)| (v.clone(), stake_of(v), vs.clone()))
.collect::<Vec<_>>(),
None,
)
.unwrap();
let staked = assignment_ratio_to_staked(assignments, &stake_of);
let winners = to_without_backing(winners);
let support_map = to_support_map::<AccountId>(&winners, &staked).unwrap();
let support_vec = to_supports(&winners, &staked).unwrap();
assert_eq!(support_map.evaluate(), support_vec.evaluate());
}
mod assignment_convert_normalize { mod assignment_convert_normalize {
use super::*; use super::*;
#[test] #[test]
@@ -112,7 +112,6 @@ where
/// Compute the score of this solution type. /// Compute the score of this solution type.
fn score<A, FS>( fn score<A, FS>(
self, self,
winners: &[A],
stake_of: FS, stake_of: FS,
voter_at: impl Fn(Self::VoterIndex) -> Option<A>, voter_at: impl Fn(Self::VoterIndex) -> Option<A>,
target_at: impl Fn(Self::TargetIndex) -> Option<A>, target_at: impl Fn(Self::TargetIndex) -> Option<A>,
@@ -123,7 +122,7 @@ where
{ {
let ratio = self.into_assignment(voter_at, target_at)?; let ratio = self.into_assignment(voter_at, target_at)?;
let staked = crate::helpers::assignment_ratio_to_staked_normalized(ratio, stake_of)?; let staked = crate::helpers::assignment_ratio_to_staked_normalized(ratio, stake_of)?;
let supports = crate::to_supports(winners, &staked)?; let supports = crate::to_supports(&staked);
Ok(supports.evaluate()) Ok(supports.evaluate())
} }