PhragMMS election. (#6685)

* Revamp npos-elections and implement phragmms

* Update primitives/npos-elections/src/phragmms.rs

* Fix build

* Some review grumbles

* Add some stuff for remote testing

* fix some of the grumbles.

* Add remote testing stuff.

* Cleanup

* fix docs

* Update primitives/arithmetic/src/rational.rs

Co-authored-by: Dan Forbes <dan@danforbes.dev>

* Small config change

* Better handling of approval_stake == 0

* Final touhces.

* Clean fuzzer a bit

* Clean fuzzer a bit

* Update primitives/npos-elections/src/balancing.rs

Co-authored-by: Shawn Tabrizi <shawntabrizi@gmail.com>

* Fix fuzzer.

* Better api for normalize

* Add noramlize_up

* A large number of small fixes.

* make it merge ready

* Fix warns

* bump

* Fix fuzzers a bit.

* Fix warns as well.

* Fix more tests.

Co-authored-by: Dan Forbes <dan@danforbes.dev>
Co-authored-by: Shawn Tabrizi <shawntabrizi@gmail.com>
This commit is contained in:
Kian Paimani
2020-09-23 10:16:10 +02:00
committed by GitHub
parent ecdc94420e
commit 313f86ec23
32 changed files with 2074 additions and 914 deletions
+62 -31
View File
@@ -304,7 +304,7 @@ use frame_support::{
};
use pallet_session::historical;
use sp_runtime::{
Percent, Perbill, PerU16, PerThing, RuntimeDebug, DispatchError,
Percent, Perbill, PerU16, PerThing, InnerOf, RuntimeDebug, DispatchError,
curve::PiecewiseLinear,
traits::{
Convert, Zero, StaticLookup, CheckedSub, Saturating, SaturatedConversion,
@@ -707,18 +707,18 @@ pub struct ElectionSize {
impl<BlockNumber: PartialEq> ElectionStatus<BlockNumber> {
fn is_open_at(&self, n: BlockNumber) -> bool {
pub fn is_open_at(&self, n: BlockNumber) -> bool {
*self == Self::Open(n)
}
fn is_closed(&self) -> bool {
pub fn is_closed(&self) -> bool {
match self {
Self::Closed => true,
_ => false
}
}
fn is_open(&self) -> bool {
pub fn is_open(&self) -> bool {
!self.is_closed()
}
}
@@ -1372,6 +1372,25 @@ decl_module! {
T::BondingDuration::get(),
)
);
use sp_runtime::UpperOf;
// see the documentation of `Assignment::try_normalize`. Now we can ensure that this
// will always return `Ok`.
// 1. Maximum sum of Vec<ChainAccuracy> must fit into `UpperOf<ChainAccuracy>`.
assert!(
<usize as TryInto<UpperOf<ChainAccuracy>>>::try_into(MAX_NOMINATIONS)
.unwrap()
.checked_mul(<ChainAccuracy>::one().deconstruct().try_into().unwrap())
.is_some()
);
// 2. Maximum sum of Vec<OffchainAccuracy> must fit into `UpperOf<OffchainAccuracy>`.
assert!(
<usize as TryInto<UpperOf<OffchainAccuracy>>>::try_into(MAX_NOMINATIONS)
.unwrap()
.checked_mul(<OffchainAccuracy>::one().deconstruct().try_into().unwrap())
.is_some()
);
}
/// Take the origin account as a stash and lock up `value` of its balance. `controller` will
@@ -2165,7 +2184,7 @@ impl<T: Trait> Module<T> {
}
/// internal impl of [`slashable_balance_of`] that returns [`VoteWeight`].
fn slashable_balance_of_vote_weight(stash: &T::AccountId) -> VoteWeight {
pub fn slashable_balance_of_vote_weight(stash: &T::AccountId) -> VoteWeight {
<T::CurrencyToVote as Convert<BalanceOf<T>, VoteWeight>>::convert(
Self::slashable_balance_of(stash)
)
@@ -2577,14 +2596,10 @@ impl<T: Trait> Module<T> {
);
// build the support map thereof in order to evaluate.
// OPTIMIZATION: loop to create the staked assignments but it would bloat the code. Okay for
// now as it does not add to the complexity order.
let (supports, num_error) = build_support_map::<T::AccountId>(
let supports = build_support_map::<T::AccountId>(
&winners,
&staked_assignments,
);
// This technically checks that all targets in all nominators were among the winners.
ensure!(num_error == 0, Error::<T>::OffchainElectionBogusEdge);
).map_err(|_| Error::<T>::OffchainElectionBogusEdge)?;
// Check if the score is the same as the claimed one.
let submitted_score = evaluate_support(&supports);
@@ -2811,7 +2826,7 @@ impl<T: Trait> Module<T> {
fn try_do_election() -> Option<ElectionResult<T::AccountId, BalanceOf<T>>> {
// an election result from either a stored submission or locally executed one.
let next_result = <QueuedElected<T>>::take().or_else(||
Self::do_phragmen_with_post_processing::<ChainAccuracy>(ElectionCompute::OnChain)
Self::do_on_chain_phragmen()
);
// either way, kill this. We remove it here to make sure it always has the exact same
@@ -2828,13 +2843,8 @@ impl<T: Trait> Module<T> {
/// `PrimitiveElectionResult` into `ElectionResult`.
///
/// No storage item is updated.
fn do_phragmen_with_post_processing<Accuracy: PerThing>(compute: ElectionCompute)
-> Option<ElectionResult<T::AccountId, BalanceOf<T>>>
where
Accuracy: sp_std::ops::Mul<ExtendedBalance, Output=ExtendedBalance>,
ExtendedBalance: From<<Accuracy as PerThing>::Inner>,
{
if let Some(phragmen_result) = Self::do_phragmen::<Accuracy>() {
fn do_on_chain_phragmen() -> Option<ElectionResult<T::AccountId, BalanceOf<T>>> {
if let Some(phragmen_result) = Self::do_phragmen::<ChainAccuracy>(0) {
let elected_stashes = phragmen_result.winners.iter()
.map(|(s, _)| s.clone())
.collect::<Vec<T::AccountId>>();
@@ -2845,10 +2855,17 @@ impl<T: Trait> Module<T> {
Self::slashable_balance_of_vote_weight,
);
let (supports, _) = build_support_map::<T::AccountId>(
let supports = build_support_map::<T::AccountId>(
&elected_stashes,
&staked_assignments,
);
)
.map_err(|_|
log!(
error,
"💸 on-chain phragmen is failing due to a problem in the result. This must be a bug."
)
)
.ok()?;
// collect exposures
let exposures = Self::collect_exposure(supports);
@@ -2860,7 +2877,7 @@ impl<T: Trait> Module<T> {
Some(ElectionResult::<T::AccountId, BalanceOf<T>> {
elected_stashes,
exposures,
compute,
compute: ElectionCompute::OnChain,
})
} else {
// There were not enough candidates for even our minimal level of functionality. This is
@@ -2874,10 +2891,14 @@ impl<T: Trait> Module<T> {
/// Execute phragmen election and return the new results. No post-processing is applied and the
/// raw edge weights are returned.
///
/// Self votes are added and nominations before the most recent slashing span are reaped.
/// Self votes are added and nominations before the most recent slashing span are ignored.
///
/// No storage item is updated.
fn do_phragmen<Accuracy: PerThing>() -> Option<PrimitiveElectionResult<T::AccountId, Accuracy>> {
pub fn do_phragmen<Accuracy: PerThing>(
iterations: usize,
) -> Option<PrimitiveElectionResult<T::AccountId, Accuracy>>
where ExtendedBalance: From<InnerOf<Accuracy>>
{
let mut all_nominators: Vec<(T::AccountId, VoteWeight, Vec<T::AccountId>)> = Vec::new();
let mut all_validators = Vec::new();
for (validator, _) in <Validators<T>>::iter() {
@@ -2906,16 +2927,26 @@ impl<T: Trait> Module<T> {
(n, s, ns)
}));
seq_phragmen::<_, Accuracy>(
Self::validator_count() as usize,
Self::minimum_validator_count().max(1) as usize,
all_validators,
all_nominators,
)
if all_validators.len() < Self::minimum_validator_count().max(1) as usize {
// If we don't have enough candidates, nothing to do.
log!(error, "💸 Chain does not have enough staking candidates to operate. Era {:?}.", Self::current_era());
None
} else {
seq_phragmen::<_, Accuracy>(
Self::validator_count() as usize,
all_validators,
all_nominators,
Some((iterations, 0)), // exactly run `iterations` rounds.
)
.map_err(|err| log!(error, "Call to seq-phragmen failed due to {}", err))
.ok()
}
}
/// Consume a set of [`Supports`] from [`sp_npos_elections`] and collect them into a [`Exposure`]
fn collect_exposure(supports: SupportMap<T::AccountId>) -> Vec<(T::AccountId, Exposure<T::AccountId, BalanceOf<T>>)> {
fn collect_exposure(
supports: SupportMap<T::AccountId>,
) -> Vec<(T::AccountId, Exposure<T::AccountId, BalanceOf<T>>)> {
let to_balance = |e: ExtendedBalance|
<T::CurrencyToVote as Convert<ExtendedBalance, BalanceOf<T>>>::convert(e);