Skip to content
This repository was archived by the owner on Nov 15, 2023. It is now read-only.
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
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
21 changes: 4 additions & 17 deletions frame/election-provider-multi-phase/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1398,17 +1398,8 @@ impl<T: Config> Pallet<T> {
let target_at = helpers::target_at_fn::<T>(&snapshot_targets);
let voter_index = helpers::voter_index_fn_usize::<T>(&cache);

// First, make sure that all the winners are sane.
// OPTIMIZATION: we could first build the assignments, and then extract the winners directly
// 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.
// Then convert solution -> assignment. This will fail if any of the indices are gibberish,
// namely any of the voters or targets.
let assignments = solution
.into_assignment(voter_at, target_at)
.map_err::<FeasibilityError, _>(Into::into)?;
Expand Down Expand Up @@ -1444,14 +1435,10 @@ impl<T: Config> Pallet<T> {
// This might fail if the normalization fails. Very unlikely. See `integrity_test`.
let staked_assignments = assignment_ratio_to_staked_normalized(assignments, stake_of)
.map_err::<FeasibilityError, _>(Into::into)?;

// 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)?;
let supports = sp_npos_elections::to_supports(&staked_assignments);

// 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);

Ok(ReadySolution { supports, compute, score })
Expand Down
23 changes: 11 additions & 12 deletions frame/election-provider-multi-phase/src/mock.rs
Original file line number Diff line number Diff line change
Expand Up @@ -30,8 +30,8 @@ use sp_core::{
H256,
};
use sp_npos_elections::{
assignment_ratio_to_staked_normalized, seq_phragmen, to_supports, to_without_backing,
ElectionResult, EvaluateSupport, NposSolution,
assignment_ratio_to_staked_normalized, seq_phragmen, to_supports, ElectionResult,
EvaluateSupport, NposSolution,
};
use sp_runtime::{
testing::Header,
Expand Down Expand Up @@ -157,25 +157,24 @@ pub fn raw_solution() -> RawSolution<SolutionOf<Runtime>> {
let RoundSnapshot { voters, targets } = MultiPhase::snapshot().unwrap();
let desired_targets = MultiPhase::desired_targets().unwrap();

let ElectionResult { winners, assignments } = seq_phragmen::<_, SolutionAccuracyOf<Runtime>>(
desired_targets as usize,
targets.clone(),
voters.clone(),
None,
)
.unwrap();
let ElectionResult { winners: _, assignments } =
seq_phragmen::<_, SolutionAccuracyOf<Runtime>>(
desired_targets as usize,
targets.clone(),
voters.clone(),
None,
)
.unwrap();

// closures
let cache = helpers::generate_voter_cache::<Runtime>(&voters);
let voter_index = helpers::voter_index_fn_linear::<Runtime>(&voters);
let target_index = helpers::target_index_fn_linear::<Runtime>(&targets);
let stake_of = helpers::stake_of_fn::<Runtime>(&voters, &cache);

let winners = to_without_backing(winners);

let score = {
let staked = assignment_ratio_to_staked_normalized(assignments.clone(), &stake_of).unwrap();
to_supports(&winners, &staked).unwrap().evaluate()
to_supports(&staked).evaluate()
};
let solution =
<SolutionOf<Runtime>>::from_assignment(&assignments, &voter_index, &target_index).unwrap();
Expand Down
5 changes: 2 additions & 3 deletions frame/election-provider-multi-phase/src/unsigned.rs
Original file line number Diff line number Diff line change
Expand Up @@ -312,7 +312,7 @@ impl<T: Config> Pallet<T> {
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)
let sorted_assignments = {
Expand Down Expand Up @@ -371,8 +371,7 @@ impl<T: Config> Pallet<T> {
let solution = SolutionOf::<T>::try_from(&index_assignments)?;

// re-calc score.
let winners = sp_npos_elections::to_without_backing(winners);
let score = solution.clone().score(&winners, stake_of, voter_at, target_at)?;
let score = solution.clone().score(stake_of, voter_at, target_at)?;

let round = Self::round();
Ok((RawSolution { solution, score, round }, size))
Expand Down
5 changes: 2 additions & 3 deletions frame/election-provider-support/src/onchain.rs
Original file line number Diff line number Diff line change
Expand Up @@ -82,14 +82,13 @@ impl<T: Config> ElectionProvider<T::AccountId, T::BlockNumber> for OnChainSequen
let stake_of =
|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)
.map_err(Error::from)?;

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))
}
}

Expand Down
8 changes: 3 additions & 5 deletions primitives/npos-elections/fuzzer/src/phragmen_balancing.rs
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ use honggfuzz::fuzz;
use rand::{self, SeedableRng};
use sp_npos_elections::{
assignment_ratio_to_staked_normalized, is_score_better, seq_phragmen, to_supports,
to_without_backing, EvaluateSupport, VoteWeight,
EvaluateSupport, VoteWeight,
};
use sp_runtime::Perbill;

Expand Down Expand Up @@ -58,8 +58,7 @@ fn main() {
&stake_of,
)
.unwrap();
let winners = to_without_backing(unbalanced.winners.clone());
let score = to_supports(winners.as_ref(), staked.as_ref()).unwrap().evaluate();
let score = to_supports(staked.as_ref()).evaluate();

if score[0] == 0 {
// such cases cannot be improved by balancing.
Expand All @@ -83,8 +82,7 @@ fn main() {
&stake_of,
)
.unwrap();
let winners = to_without_backing(balanced.winners);
to_supports(winners.as_ref(), staked.as_ref()).unwrap().evaluate()
to_supports(staked.as_ref()).evaluate()
};

let enhance = is_score_better(balanced_score, unbalanced_score, Perbill::zero());
Expand Down
10 changes: 4 additions & 6 deletions primitives/npos-elections/fuzzer/src/phragmms_balancing.rs
Original file line number Diff line number Diff line change
Expand Up @@ -23,8 +23,8 @@ use common::*;
use honggfuzz::fuzz;
use rand::{self, SeedableRng};
use sp_npos_elections::{
assignment_ratio_to_staked_normalized, is_score_better, phragmms, to_supports,
to_without_backing, EvaluateSupport, VoteWeight,
assignment_ratio_to_staked_normalized, is_score_better, phragmms, to_supports, EvaluateSupport,
VoteWeight,
};
use sp_runtime::Perbill;

Expand Down Expand Up @@ -58,8 +58,7 @@ fn main() {
&stake_of,
)
.unwrap();
let winners = to_without_backing(unbalanced.winners.clone());
let score = to_supports(&winners, &staked).unwrap().evaluate();
let score = to_supports(&staked).evaluate();

if score[0] == 0 {
// such cases cannot be improved by balancing.
Expand All @@ -80,8 +79,7 @@ fn main() {
let staked =
assignment_ratio_to_staked_normalized(balanced.assignments.clone(), &stake_of)
.unwrap();
let winners = to_without_backing(balanced.winners);
to_supports(winners.as_ref(), staked.as_ref()).unwrap().evaluate()
to_supports(staked.as_ref()).evaluate()
};

let enhance = is_score_better(balanced_score, unbalanced_score, Perbill::zero());
Expand Down
8 changes: 3 additions & 5 deletions primitives/npos-elections/fuzzer/src/reduce.rs
Original file line number Diff line number Diff line change
Expand Up @@ -104,13 +104,11 @@ fn generate_random_phragmen_assignment(
}

fn assert_assignments_equal(
winners: &Vec<AccountId>,
ass1: &Vec<StakedAssignment<AccountId>>,
ass2: &Vec<StakedAssignment<AccountId>>,
) {
let support_1 = to_support_map::<AccountId>(winners, ass1).unwrap();
let support_2 = to_support_map::<AccountId>(winners, ass2).unwrap();

let support_1 = to_support_map::<AccountId>(ass1);
let support_2 = to_support_map::<AccountId>(ass2);
for (who, support) in support_1.iter() {
assert_eq!(support.total, support_2.get(who).unwrap().total);
}
Expand All @@ -134,7 +132,7 @@ fn reduce_and_compare(assignment: &Vec<StakedAssignment<AccountId>>, winners: &V
num_changed,
);

assert_assignments_equal(winners, &assignment, &altered_assignment);
assert_assignments_equal(&assignment, &altered_assignment);
}

fn assignment_len(assignments: &[StakedAssignment<AccountId>]) -> u32 {
Expand Down
93 changes: 18 additions & 75 deletions primitives/npos-elections/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -361,107 +361,50 @@ pub type Supports<A> = Vec<(A, Support<A>)>;
/// This is more helpful than a normal [`Supports`] as it allows faster error checking.
pub type SupportMap<A> = BTreeMap<A, Support<A>>;

/// Helper trait to convert from a support map to a flat support vector.
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`.
/// Build the support map from the assignments.
pub fn to_support_map<AccountId: IdentifierT>(
winners: &[AccountId],
assignments: &[StakedAssignment<AccountId>],
) -> Result<SupportMap<AccountId>, Error> {
// Initialize the support of each candidate.
let mut supports = <SupportMap<AccountId>>::new();
winners.iter().for_each(|e| {
supports.insert(e.clone(), Default::default());
});
) -> SupportMap<AccountId> {
let mut supports = <BTreeMap<AccountId, Support<AccountId>>>::new();

// build support struct.
for StakedAssignment { who, distribution } in assignments.iter() {
for (c, weight_extended) in distribution.iter() {
if let Some(support) = supports.get_mut(c) {
support.total = support.total.saturating_add(*weight_extended);
support.voters.push((who.clone(), *weight_extended));
} else {
return Err(Error::InvalidSupportEdge)
}
for StakedAssignment { who, distribution } in assignments.into_iter() {
for (c, weight_extended) in distribution.into_iter() {
let mut support = supports.entry(c.clone()).or_default();
support.total = support.total.saturating_add(*weight_extended);
support.voters.push((who.clone(), *weight_extended));
}
}
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.
///
/// Similar to [`to_support_map`], `winners` is used for error checking.
pub fn to_supports<AccountId: IdentifierT>(
winners: &[AccountId],
assignments: &[StakedAssignment<AccountId>],
) -> Result<Supports<AccountId>, Error> {
to_support_map(winners, assignments).map(FlattenSupportMap::flatten)
) -> Supports<AccountId> {
to_support_map(assignments).into_iter().collect()
}

/// Extension trait for evaluating a support map or vector.
pub trait EvaluateSupport<K> {
pub trait EvaluateSupport {
/// Evaluate a support map. The returned tuple contains:
///
/// - Minimum support. This value must be **maximized**.
/// - Sum of all supports. This value must be **maximized**.
/// - Sum of all supports squared. This value must be **minimized**.
fn evaluate(self) -> ElectionScore;
}

/// A common wrapper trait for both (&A, &B) and &(A, B).
///
/// 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)
}
fn evaluate(&self) -> ElectionScore;
}

impl<A, C, I> EvaluateSupport<A> for C
where
C: IntoIterator<Item = I>,
I: TupleRef<A, Support<A>>,
A: IdentifierT,
{
fn evaluate(self) -> ElectionScore {
impl<AccountId: IdentifierT> EvaluateSupport for Supports<AccountId> {
fn evaluate(&self) -> ElectionScore {
let mut min_support = ExtendedBalance::max_value();
let mut sum: ExtendedBalance = Zero::zero();
// NOTE: The third element might saturate but fine for now since this will run on-chain and
// need to be fast.
let mut sum_squared: ExtendedBalance = Zero::zero();
for item in self {
let (_, support) = item.extract();
for (_, support) in self {
sum = sum.saturating_add(support.total);
let squared = support.total.saturating_mul(support.total);
sum_squared = sum_squared.saturating_add(squared);
Expand Down
Loading