Run cargo fmt on the whole code base (#9394)

* Run cargo fmt on the whole code base

* Second run

* Add CI check

* Fix compilation

* More unnecessary braces

* Handle weights

* Use --all

* Use correct attributes...

* Fix UI tests

* AHHHHHHHHH

* 🤦

* Docs

* Fix compilation

* 🤷

* Please stop

* 🤦 x 2

* More

* make rustfmt.toml consistent with polkadot

Co-authored-by: André Silva <andrerfosilva@gmail.com>
This commit is contained in:
Bastian Köcher
2021-07-21 16:32:32 +02:00
committed by GitHub
parent d451c38c1c
commit 7b56ab15b4
1010 changed files with 53339 additions and 51208 deletions
@@ -12,7 +12,6 @@
// See the License for the specific language governing permissions and
// limitations under the License.
//! Benchmarks of the phragmen election algorithm.
//! Note that execution times will not be accurate in an absolute scale, since
//! - Everything is executed in the context of `TestExternalities`
@@ -27,13 +26,12 @@ use test::Bencher;
use rand::{self, Rng};
use sp_npos_elections::{ElectionResult, VoteWeight};
use std::collections::BTreeMap;
use sp_runtime::{Perbill, PerThing, traits::Zero};
use sp_npos_elections::{
balance_solution, assignment_ratio_to_staked, to_support_map, to_without_backing, VoteWeight,
ExtendedBalance, Assignment, StakedAssignment, IdentifierT, assignment_ratio_to_staked,
seq_phragmen,
assignment_ratio_to_staked, balance_solution, seq_phragmen, to_support_map, to_without_backing,
Assignment, ExtendedBalance, IdentifierT, StakedAssignment, VoteWeight,
};
use sp_runtime::{traits::Zero, PerThing, Perbill};
use std::collections::BTreeMap;
// default params. Each will be scaled by the benchmarks individually.
const VALIDATORS: u64 = 100;
@@ -69,15 +67,13 @@ mod bench_closure_and_slice {
ratio
.into_iter()
.zip(stakes.into_iter().map(|x| *x as ExtendedBalance))
.map(|(a, stake)| {
a.into_staked(stake.into(), true)
})
.map(|(a, stake)| a.into_staked(stake.into(), true))
.collect()
}
#[bench]
fn closure(b: &mut Bencher) {
let assignments = (0..1000).map(|_| random_assignment()).collect::<Vec<Assignment<_ ,_>>>();
let assignments = (0..1000).map(|_| random_assignment()).collect::<Vec<Assignment<_, _>>>();
let stake_of = |x: &u32| -> VoteWeight { (x * 2 + 100).into() };
// each have one clone of assignments
@@ -86,7 +82,7 @@ mod bench_closure_and_slice {
#[bench]
fn slice(b: &mut Bencher) {
let assignments = (0..1000).map(|_| random_assignment()).collect::<Vec<Assignment<_ ,_>>>();
let assignments = (0..1000).map(|_| random_assignment()).collect::<Vec<Assignment<_, _>>>();
let stake_of = |x: &u32| -> VoteWeight { (x * 2 + 100).into() };
b.iter(|| {
@@ -112,20 +108,19 @@ fn do_phragmen(
let mut candidates = Vec::with_capacity(num_validators as usize);
let mut stake_of_tree: BTreeMap<AccountId, VoteWeight> = BTreeMap::new();
(1 ..= num_validators).for_each(|acc| {
(1..=num_validators).for_each(|acc| {
candidates.push(acc);
stake_of_tree.insert(acc, STAKE + rr(10, 1000));
});
let mut voters = Vec::with_capacity(num_nominators as usize);
(PREFIX ..= (PREFIX + num_nominators)).for_each(|acc| {
(PREFIX..=(PREFIX + num_nominators)).for_each(|acc| {
// all possible targets
let mut all_targets = candidates.clone();
// we remove and pop into `targets` `edge_per_voter` times.
let targets = (0 .. edge_per_voter).map(|_| {
all_targets.remove(rr(0, all_targets.len()) as usize)
})
.collect::<Vec<AccountId>>();
let targets = (0..edge_per_voter)
.map(|_| all_targets.remove(rr(0, all_targets.len()) as usize))
.collect::<Vec<AccountId>>();
let stake = STAKE + rr(10, 1000);
stake_of_tree.insert(acc, stake);
@@ -138,20 +133,16 @@ fn do_phragmen(
Zero::zero(),
candidates.clone(),
voters.clone(),
).unwrap();
)
.unwrap();
let stake_of = |who: &AccountId| -> VoteWeight {
*stake_of_tree.get(who).unwrap()
};
let stake_of = |who: &AccountId| -> VoteWeight { *stake_of_tree.get(who).unwrap() };
// Do the benchmarking with balancing.
if eq_iters > 0 {
let staked = assignment_ratio_to_staked(assignments, &stake_of);
let winners = to_without_backing(winners);
let mut support = to_support_map(
winners.as_ref(),
staked.as_ref(),
).unwrap();
let mut support = to_support_map(winners.as_ref(), staked.as_ref()).unwrap();
balance_solution(
staked.into_iter().map(|a| (a.clone(), stake_of(&a.who))).collect(),
@@ -46,25 +46,29 @@ pub(crate) fn from_impl(count: usize) -> TokenStream2 {
),)
};
let from_impl_rest = (3..=count).map(|c| {
let inner = (0..c-1).map(|i|
quote!((index_of_target(&distribution[#i].0).or_invalid_index()?, distribution[#i].1),)
).collect::<TokenStream2>();
let field_name = field_name_for(c);
let last_index = c - 1;
let last = quote!(index_of_target(&distribution[#last_index].0).or_invalid_index()?);
quote!(
#c => compact.#field_name.push(
(
index_of_voter(&who).or_invalid_index()?,
[#inner],
#last,
let from_impl_rest = (3..=count)
.map(|c| {
let inner = (0..c - 1)
.map(
|i| quote!((index_of_target(&distribution[#i].0).or_invalid_index()?, distribution[#i].1),),
)
),
)
}).collect::<TokenStream2>();
.collect::<TokenStream2>();
let field_name = field_name_for(c);
let last_index = c - 1;
let last = quote!(index_of_target(&distribution[#last_index].0).or_invalid_index()?);
quote!(
#c => compact.#field_name.push(
(
index_of_voter(&who).or_invalid_index()?,
[#inner],
#last,
)
),
)
})
.collect::<TokenStream2>();
quote!(
#from_impl_single
@@ -113,39 +117,41 @@ pub(crate) fn into_impl(count: usize, per_thing: syn::Type) -> TokenStream2 {
)
};
let into_impl_rest = (3..=count).map(|c| {
let name = field_name_for(c);
quote!(
for (voter_index, inners, t_last_idx) in self.#name {
let mut sum = #per_thing::zero();
let mut inners_parsed = inners
.iter()
.map(|(ref t_idx, p)| {
sum = _npos::sp_arithmetic::traits::Saturating::saturating_add(sum, *p);
let target = target_at(*t_idx).or_invalid_index()?;
Ok((target, *p))
})
.collect::<Result<_npos::sp_std::prelude::Vec<(A, #per_thing)>, _npos::Error>>()?;
let into_impl_rest = (3..=count)
.map(|c| {
let name = field_name_for(c);
quote!(
for (voter_index, inners, t_last_idx) in self.#name {
let mut sum = #per_thing::zero();
let mut inners_parsed = inners
.iter()
.map(|(ref t_idx, p)| {
sum = _npos::sp_arithmetic::traits::Saturating::saturating_add(sum, *p);
let target = target_at(*t_idx).or_invalid_index()?;
Ok((target, *p))
})
.collect::<Result<_npos::sp_std::prelude::Vec<(A, #per_thing)>, _npos::Error>>()?;
if sum >= #per_thing::one() {
return Err(_npos::Error::CompactStakeOverflow);
if sum >= #per_thing::one() {
return Err(_npos::Error::CompactStakeOverflow);
}
// defensive only. Since Percent doesn't have `Sub`.
let p_last = _npos::sp_arithmetic::traits::Saturating::saturating_sub(
#per_thing::one(),
sum,
);
inners_parsed.push((target_at(t_last_idx).or_invalid_index()?, p_last));
assignments.push(_npos::Assignment {
who: voter_at(voter_index).or_invalid_index()?,
distribution: inners_parsed,
});
}
// defensive only. Since Percent doesn't have `Sub`.
let p_last = _npos::sp_arithmetic::traits::Saturating::saturating_sub(
#per_thing::one(),
sum,
);
inners_parsed.push((target_at(t_last_idx).or_invalid_index()?, p_last));
assignments.push(_npos::Assignment {
who: voter_at(voter_index).or_invalid_index()?,
distribution: inners_parsed,
});
}
)
}).collect::<TokenStream2>();
)
})
.collect::<TokenStream2>();
quote!(
#into_impl_single
@@ -80,39 +80,42 @@ fn decode_impl(
}
};
let decode_impl_rest = (3..=count).map(|c| {
let name = field_name_for(c);
let decode_impl_rest = (3..=count)
.map(|c| {
let name = field_name_for(c);
let inner_impl = (0..c-1).map(|i|
quote! { ( (inner[#i].0).0, (inner[#i].1).0 ), }
).collect::<TokenStream2>();
let inner_impl = (0..c - 1)
.map(|i| quote! { ( (inner[#i].0).0, (inner[#i].1).0 ), })
.collect::<TokenStream2>();
quote! {
let #name =
<
_npos::sp_std::prelude::Vec<(
_npos::codec::Compact<#voter_type>,
[(_npos::codec::Compact<#target_type>, _npos::codec::Compact<#weight_type>); #c-1],
_npos::codec::Compact<#target_type>,
)>
as _npos::codec::Decode
>::decode(value)?;
let #name = #name
.into_iter()
.map(|(v, inner, t_last)| (
v.0,
[ #inner_impl ],
t_last.0,
))
.collect::<_npos::sp_std::prelude::Vec<_>>();
}
}).collect::<TokenStream2>();
quote! {
let #name =
<
_npos::sp_std::prelude::Vec<(
_npos::codec::Compact<#voter_type>,
[(_npos::codec::Compact<#target_type>, _npos::codec::Compact<#weight_type>); #c-1],
_npos::codec::Compact<#target_type>,
)>
as _npos::codec::Decode
>::decode(value)?;
let #name = #name
.into_iter()
.map(|(v, inner, t_last)| (
v.0,
[ #inner_impl ],
t_last.0,
))
.collect::<_npos::sp_std::prelude::Vec<_>>();
}
})
.collect::<TokenStream2>();
let all_field_names = (1..=count).map(|c| {
let name = field_name_for(c);
quote! { #name, }
}).collect::<TokenStream2>();
let all_field_names = (1..=count)
.map(|c| {
let name = field_name_for(c);
quote! { #name, }
})
.collect::<TokenStream2>();
quote!(
impl _npos::codec::Decode for #ident {
@@ -165,29 +168,33 @@ fn encode_impl(ident: syn::Ident, count: usize) -> TokenStream2 {
}
};
let encode_impl_rest = (3..=count).map(|c| {
let name = field_name_for(c);
let encode_impl_rest = (3..=count)
.map(|c| {
let name = field_name_for(c);
// we use the knowledge of the length to avoid copy_from_slice.
let inners_compact_array = (0..c-1).map(|i|
quote!{(
_npos::codec::Compact(inner[#i].0.clone()),
_npos::codec::Compact(inner[#i].1.clone()),
),}
).collect::<TokenStream2>();
// we use the knowledge of the length to avoid copy_from_slice.
let inners_compact_array = (0..c - 1)
.map(|i| {
quote! {(
_npos::codec::Compact(inner[#i].0.clone()),
_npos::codec::Compact(inner[#i].1.clone()),
),}
})
.collect::<TokenStream2>();
quote! {
let #name = self.#name
.iter()
.map(|(v, inner, t_last)| (
_npos::codec::Compact(v.clone()),
[ #inners_compact_array ],
_npos::codec::Compact(t_last.clone()),
))
.collect::<_npos::sp_std::prelude::Vec<_>>();
#name.encode_to(&mut r);
}
}).collect::<TokenStream2>();
quote! {
let #name = self.#name
.iter()
.map(|(v, inner, t_last)| (
_npos::codec::Compact(v.clone()),
[ #inners_compact_array ],
_npos::codec::Compact(t_last.clone()),
))
.collect::<_npos::sp_std::prelude::Vec<_>>();
#name.encode_to(&mut r);
}
})
.collect::<TokenStream2>();
quote!(
impl _npos::codec::Encode for #ident {
@@ -65,7 +65,7 @@ pub(crate) fn from_impl(count: usize) -> TokenStream2 {
)
),
)
})
})
.collect::<TokenStream2>();
quote!(
@@ -18,7 +18,7 @@
//! Proc macro for a npos compact assignment.
use proc_macro::TokenStream;
use proc_macro2::{TokenStream as TokenStream2, Span, Ident};
use proc_macro2::{Ident, Span, TokenStream as TokenStream2};
use proc_macro_crate::{crate_name, FoundCrate};
use quote::quote;
use syn::parse::{Parse, ParseStream, Result};
@@ -82,15 +82,8 @@ pub(crate) fn syn_err(message: &'static str) -> syn::Error {
/// ```
#[proc_macro]
pub fn generate_solution_type(item: TokenStream) -> TokenStream {
let SolutionDef {
vis,
ident,
count,
voter_type,
target_type,
weight_type,
compact_encoding,
} = syn::parse_macro_input!(item as SolutionDef);
let SolutionDef { vis, ident, count, voter_type, target_type, weight_type, compact_encoding } =
syn::parse_macro_input!(item as SolutionDef);
let imports = imports().unwrap_or_else(|e| e.to_compile_error());
@@ -102,7 +95,8 @@ pub fn generate_solution_type(item: TokenStream) -> TokenStream {
target_type.clone(),
weight_type.clone(),
compact_encoding,
).unwrap_or_else(|e| e.to_compile_error());
)
.unwrap_or_else(|e| e.to_compile_error());
quote!(
#imports
@@ -167,7 +161,7 @@ fn struct_def(
weight_type.clone(),
count,
);
quote!{
quote! {
#compact_impl
#[derive(Default, PartialEq, Eq, Clone, Debug, PartialOrd, Ord)]
}
@@ -321,23 +315,27 @@ fn remove_voter_impl(count: usize) -> TokenStream2 {
}
fn len_impl(count: usize) -> TokenStream2 {
(1..=count).map(|c| {
let field_name = field_name_for(c);
quote!(
all_len = all_len.saturating_add(self.#field_name.len());
)
}).collect::<TokenStream2>()
(1..=count)
.map(|c| {
let field_name = field_name_for(c);
quote!(
all_len = all_len.saturating_add(self.#field_name.len());
)
})
.collect::<TokenStream2>()
}
fn edge_count_impl(count: usize) -> TokenStream2 {
(1..=count).map(|c| {
let field_name = field_name_for(c);
quote!(
all_edges = all_edges.saturating_add(
self.#field_name.len().saturating_mul(#c as usize)
);
)
}).collect::<TokenStream2>()
(1..=count)
.map(|c| {
let field_name = field_name_for(c);
quote!(
all_edges = all_edges.saturating_add(
self.#field_name.len().saturating_mul(#c as usize)
);
)
})
.collect::<TokenStream2>()
}
fn unique_targets_impl(count: usize) -> TokenStream2 {
@@ -360,17 +358,19 @@ fn unique_targets_impl(count: usize) -> TokenStream2 {
}
};
let unique_targets_impl_rest = (3..=count).map(|c| {
let field_name = field_name_for(c);
quote! {
self.#field_name.iter().for_each(|(_, inners, t_last)| {
inners.iter().for_each(|(t, _)| {
maybe_insert_target(*t);
let unique_targets_impl_rest = (3..=count)
.map(|c| {
let field_name = field_name_for(c);
quote! {
self.#field_name.iter().for_each(|(_, inners, t_last)| {
inners.iter().for_each(|(t, _)| {
maybe_insert_target(*t);
});
maybe_insert_target(*t_last);
});
maybe_insert_target(*t_last);
});
}
}).collect::<TokenStream2>();
}
})
.collect::<TokenStream2>();
quote! {
#unique_targets_impl_single
@@ -440,23 +440,29 @@ impl Parse for SolutionDef {
let expected_types = ["VoterIndex", "TargetIndex", "Accuracy"];
let mut types: Vec<syn::Type> = generics.args.iter().zip(expected_types.iter()).map(|(t, expected)|
match t {
let mut types: Vec<syn::Type> = generics
.args
.iter()
.zip(expected_types.iter())
.map(|(t, expected)| match t {
syn::GenericArgument::Type(ty) => {
// this is now an error
Err(syn::Error::new_spanned(ty, format!("Expected binding: `{} = ...`", expected)))
Err(syn::Error::new_spanned(
ty,
format!("Expected binding: `{} = ...`", expected),
))
},
syn::GenericArgument::Binding(syn::Binding{ident, ty, ..}) => {
syn::GenericArgument::Binding(syn::Binding { ident, ty, .. }) => {
// check that we have the right keyword for this position in the argument list
if ident == expected {
Ok(ty.clone())
} else {
Err(syn::Error::new_spanned(ident, format!("Expected `{}`", expected)))
}
}
},
_ => Err(syn_err("Wrong type of generic provided. Must be a `type`.")),
}
).collect::<Result<_>>()?;
})
.collect::<Result<_>>()?;
let weight_type = types.pop().expect("Vector of length 3 can be popped; qed");
let target_type = types.pop().expect("Vector of length 2 can be popped; qed");
@@ -467,15 +473,15 @@ impl Parse for SolutionDef {
let expr = count_expr.expr;
let expr_lit = match *expr {
syn::Expr::Lit(count_lit) => count_lit.lit,
_ => return Err(syn_err("Count must be literal."))
_ => return Err(syn_err("Count must be literal.")),
};
let int_lit = match expr_lit {
syn::Lit::Int(int_lit) => int_lit,
_ => return Err(syn_err("Count must be int literal."))
_ => return Err(syn_err("Count must be int literal.")),
};
let count = int_lit.base10_parse::<usize>()?;
Ok(Self { vis, ident, voter_type, target_type, weight_type, count, compact_encoding } )
Ok(Self { vis, ident, voter_type, target_type, weight_type, count, compact_encoding })
}
}
@@ -62,11 +62,7 @@ pub fn generate_random_npos_inputs(
candidate_count: usize,
voter_count: usize,
mut rng: impl Rng,
) -> (
usize,
Vec<AccountId>,
Vec<(AccountId, VoteWeight, Vec<AccountId>)>,
) {
) -> (usize, Vec<AccountId>, Vec<(AccountId, VoteWeight, Vec<AccountId>)>) {
// cache for fast generation of unique candidate and voter ids
let mut used_ids = HashSet::with_capacity(candidate_count + voter_count);
@@ -1,6 +1,5 @@
use honggfuzz::fuzz;
use sp_npos_elections::generate_solution_type;
use sp_npos_elections::sp_arithmetic::Percent;
use sp_npos_elections::{generate_solution_type, sp_arithmetic::Percent};
use sp_runtime::codec::{Encode, Error};
fn main() {
@@ -26,9 +25,8 @@ fn main() {
// The reencoded value should definitely be decodable (if unwrap() fails that is a valid
// panic/finding for the fuzzer):
let decoded2: InnerTestSolutionCompact =
<InnerTestSolutionCompact as codec::Decode>::decode(
&mut reencoded.as_slice(),
).unwrap();
<InnerTestSolutionCompact as codec::Decode>::decode(&mut reencoded.as_slice())
.unwrap();
// And it should be equal to the original decoded object (resulting from directly
// decoding fuzzer_data):
assert_eq!(decoded, decoded2);
@@ -21,23 +21,17 @@ mod common;
use common::*;
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,
};
use sp_runtime::Perbill;
use rand::{self, SeedableRng};
fn main() {
loop {
fuzz!(|data: (usize, usize, usize, usize, u64)| {
let (
mut target_count,
mut voter_count,
mut iterations,
mut to_elect,
seed,
) = data;
let (mut target_count, mut voter_count, mut iterations, mut to_elect, seed) = data;
let rng = rand::rngs::SmallRng::seed_from_u64(seed);
target_count = to_range(target_count, 100, 200);
voter_count = to_range(voter_count, 100, 200);
@@ -48,12 +42,7 @@ fn main() {
"++ [voter_count: {} / target_count:{} / to_elect:{} / iterations:{}]",
voter_count, target_count, to_elect, iterations,
);
let (
unbalanced,
candidates,
voters,
stake_of_tree,
) = generate_random_npos_result(
let (unbalanced, candidates, voters, stake_of_tree) = generate_random_npos_result(
voter_count as u64,
target_count as u64,
to_elect,
@@ -61,9 +50,7 @@ fn main() {
ElectionType::Phragmen(None),
);
let stake_of = |who: &AccountId| -> VoteWeight {
*stake_of_tree.get(who).unwrap()
};
let stake_of = |who: &AccountId| -> VoteWeight { *stake_of_tree.get(who).unwrap() };
let unbalanced_score = {
let staked = assignment_ratio_to_staked_normalized(
@@ -76,7 +63,7 @@ fn main() {
if score[0] == 0 {
// such cases cannot be improved by balancing.
return;
return
}
score
};
@@ -87,34 +74,32 @@ fn main() {
candidates,
voters,
Some((iterations, 0)),
).unwrap();
)
.unwrap();
let balanced_score = {
let staked = assignment_ratio_to_staked_normalized(
balanced.assignments.clone(),
&stake_of,
).unwrap();
)
.unwrap();
let winners = to_without_backing(balanced.winners);
to_supports(winners.as_ref(), staked.as_ref()).unwrap().evaluate()
};
let enhance = is_score_better(balanced_score, unbalanced_score, Perbill::zero());
println!(
"iter = {} // {:?} -> {:?} [{}]",
iterations,
unbalanced_score,
balanced_score,
enhance,
iterations, unbalanced_score, balanced_score, enhance,
);
// The only guarantee of balancing is such that the first and third element of the score
// cannot decrease.
assert!(
balanced_score[0] >= unbalanced_score[0] &&
balanced_score[1] == unbalanced_score[1] &&
balanced_score[2] <= unbalanced_score[2]
balanced_score[1] == unbalanced_score[1] &&
balanced_score[2] <= unbalanced_score[2]
);
}
});
@@ -37,7 +37,6 @@
//!
//! Once a panic is found, it can be debugged with
//! `HFUZZ_RUN_ARGS="-t 10" cargo hfuzz run-debug phragmen_pjr hfuzz_workspace/phragmen_pjr/*.fuzz`.
//!
#[cfg(fuzzing)]
use honggfuzz::fuzz;
@@ -21,23 +21,17 @@ mod common;
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,
};
use sp_runtime::Perbill;
use rand::{self, SeedableRng};
fn main() {
loop {
fuzz!(|data: (usize, usize, usize, usize, u64)| {
let (
mut target_count,
mut voter_count,
mut iterations,
mut to_elect,
seed,
) = data;
let (mut target_count, mut voter_count, mut iterations, mut to_elect, seed) = data;
let rng = rand::rngs::SmallRng::seed_from_u64(seed);
target_count = to_range(target_count, 100, 200);
voter_count = to_range(voter_count, 100, 200);
@@ -48,12 +42,7 @@ fn main() {
"++ [voter_count: {} / target_count:{} / to_elect:{} / iterations:{}]",
voter_count, target_count, to_elect, iterations,
);
let (
unbalanced,
candidates,
voters,
stake_of_tree,
) = generate_random_npos_result(
let (unbalanced, candidates, voters, stake_of_tree) = generate_random_npos_result(
voter_count as u64,
target_count as u64,
to_elect,
@@ -61,9 +50,7 @@ fn main() {
ElectionType::Phragmms(None),
);
let stake_of = |who: &AccountId| -> VoteWeight {
*stake_of_tree.get(who).unwrap()
};
let stake_of = |who: &AccountId| -> VoteWeight { *stake_of_tree.get(who).unwrap() };
let unbalanced_score = {
let staked = assignment_ratio_to_staked_normalized(
@@ -76,7 +63,7 @@ fn main() {
if score[0] == 0 {
// such cases cannot be improved by balancing.
return;
return
}
score
};
@@ -86,34 +73,30 @@ fn main() {
candidates,
voters,
Some((iterations, 0)),
).unwrap();
)
.unwrap();
let balanced_score = {
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(winners.as_ref(), staked.as_ref()).unwrap().evaluate()
};
let enhance = is_score_better(balanced_score, unbalanced_score, Perbill::zero());
println!(
"iter = {} // {:?} -> {:?} [{}]",
iterations,
unbalanced_score,
balanced_score,
enhance,
iterations, unbalanced_score, balanced_score, enhance,
);
// The only guarantee of balancing is such that the first and third element of the score
// cannot decrease.
assert!(
balanced_score[0] >= unbalanced_score[0] &&
balanced_score[1] == unbalanced_score[1] &&
balanced_score[2] <= unbalanced_score[2]
balanced_score[1] == unbalanced_score[1] &&
balanced_score[2] <= unbalanced_score[2]
);
});
}
@@ -34,8 +34,8 @@ use honggfuzz::fuzz;
mod common;
use common::to_range;
use sp_npos_elections::{reduce, to_support_map, ExtendedBalance, StakedAssignment};
use rand::{self, Rng, RngCore, SeedableRng};
use sp_npos_elections::{reduce, to_support_map, ExtendedBalance, StakedAssignment};
type Balance = u128;
type AccountId = u64;
@@ -50,13 +50,8 @@ fn main() {
let rng = rand::rngs::SmallRng::seed_from_u64(seed);
target_count = to_range(target_count, 100, 1000);
voter_count = to_range(voter_count, 100, 2000);
let (assignments, winners) = generate_random_phragmen_assignment(
voter_count,
target_count,
8,
8,
rng
);
let (assignments, winners) =
generate_random_phragmen_assignment(voter_count, target_count, 8, 8, rng);
reduce_and_compare(&assignments, &winners);
});
}
@@ -82,23 +77,27 @@ fn generate_random_phragmen_assignment(
(1..=voter_count).for_each(|acc| {
let mut targets_to_chose_from = all_targets.clone();
let targets_to_chose = if edge_per_voter_var > 0 { rng.gen_range(
avg_edge_per_voter - edge_per_voter_var,
avg_edge_per_voter + edge_per_voter_var,
) } else { avg_edge_per_voter };
let targets_to_chose = if edge_per_voter_var > 0 {
rng.gen_range(
avg_edge_per_voter - edge_per_voter_var,
avg_edge_per_voter + edge_per_voter_var,
)
} else {
avg_edge_per_voter
};
let distribution = (0..targets_to_chose).map(|_| {
let target = targets_to_chose_from.remove(rng.gen_range(0, targets_to_chose_from.len()));
if winners.iter().find(|w| **w == target).is_none() {
winners.push(target.clone());
}
(target, rng.gen_range(1 * KSM, 100 * KSM))
}).collect::<Vec<(AccountId, ExtendedBalance)>>();
let distribution = (0..targets_to_chose)
.map(|_| {
let target =
targets_to_chose_from.remove(rng.gen_range(0, targets_to_chose_from.len()));
if winners.iter().find(|w| **w == target).is_none() {
winners.push(target.clone());
}
(target, rng.gen_range(1 * KSM, 100 * KSM))
})
.collect::<Vec<(AccountId, ExtendedBalance)>>();
assignments.push(StakedAssignment {
who: (acc as AccountId),
distribution,
});
assignments.push(StakedAssignment { who: (acc as AccountId), distribution });
});
(assignments, winners)
@@ -117,10 +116,7 @@ fn assert_assignments_equal(
}
}
fn reduce_and_compare(
assignment: &Vec<StakedAssignment<AccountId>>,
winners: &Vec<AccountId>,
) {
fn reduce_and_compare(assignment: &Vec<StakedAssignment<AccountId>>, winners: &Vec<AccountId>) {
let mut altered_assignment = assignment.clone();
let n = assignment.len() as u32;
let m = winners.len() as u32;
@@ -138,15 +134,13 @@ fn reduce_and_compare(
num_changed,
);
assert_assignments_equal(
winners,
&assignment,
&altered_assignment,
);
assert_assignments_equal(winners, &assignment, &altered_assignment);
}
fn assignment_len(assignments: &[StakedAssignment<AccountId>]) -> u32 {
let mut counter = 0;
assignments.iter().for_each(|x| x.distribution.iter().for_each(|_| counter += 1));
assignments
.iter()
.for_each(|x| x.distribution.iter().for_each(|_| counter += 1));
counter
}
@@ -18,8 +18,11 @@
//! Structs and helpers for distributing a voter's stake among various winners.
use crate::{Error, ExtendedBalance, IdentifierT, PerThing128, __OrInvalidIndex};
use codec::{Encode, Decode};
use sp_arithmetic::{traits::{Bounded, Zero}, Normalizable, PerThing};
use codec::{Decode, Encode};
use sp_arithmetic::{
traits::{Bounded, Zero},
Normalizable, PerThing,
};
use sp_core::RuntimeDebug;
use sp_std::vec::Vec;
@@ -61,10 +64,7 @@ impl<AccountId: IdentifierT, P: PerThing128> Assignment<AccountId, P> {
})
.collect::<Vec<(AccountId, ExtendedBalance)>>();
StakedAssignment {
who: self.who,
distribution,
}
StakedAssignment { who: self.who, distribution }
}
/// Try and normalize this assignment.
@@ -83,12 +83,13 @@ impl<AccountId: IdentifierT, P: PerThing128> Assignment<AccountId, P> {
.map(|(_, p)| *p)
.collect::<Vec<_>>()
.normalize(P::one())
.map(|normalized_ratios|
self.distribution
.iter_mut()
.zip(normalized_ratios)
.for_each(|((_, old), corrected)| { *old = corrected; })
)
.map(|normalized_ratios| {
self.distribution.iter_mut().zip(normalized_ratios).for_each(
|((_, old), corrected)| {
*old = corrected;
},
)
})
}
}
@@ -118,7 +119,8 @@ impl<AccountId> StakedAssignment<AccountId> {
AccountId: IdentifierT,
{
let stake = self.total();
let distribution = self.distribution
let distribution = self
.distribution
.into_iter()
.filter_map(|(target, w)| {
let per_thing = P::from_rational(w, stake);
@@ -130,10 +132,7 @@ impl<AccountId> StakedAssignment<AccountId> {
})
.collect::<Vec<(AccountId, P)>>();
Assignment {
who: self.who,
distribution,
}
Assignment { who: self.who, distribution }
}
/// Try and normalize this assignment.
@@ -152,12 +151,13 @@ impl<AccountId> StakedAssignment<AccountId> {
.map(|(_, ref weight)| *weight)
.collect::<Vec<_>>()
.normalize(stake)
.map(|normalized_weights|
self.distribution
.iter_mut()
.zip(normalized_weights.into_iter())
.for_each(|((_, weight), corrected)| { *weight = corrected; })
)
.map(|normalized_weights| {
self.distribution.iter_mut().zip(normalized_weights.into_iter()).for_each(
|((_, weight), corrected)| {
*weight = corrected;
},
)
})
}
/// Get the total stake of this assignment (aka voter budget).
@@ -26,7 +26,7 @@
//!
//! See [`balance`] for more information.
use crate::{IdentifierT, Voter, ExtendedBalance, Edge};
use crate::{Edge, ExtendedBalance, IdentifierT, Voter};
use sp_arithmetic::traits::Zero;
use sp_std::prelude::*;
@@ -57,19 +57,23 @@ pub fn balance<AccountId: IdentifierT>(
iterations: usize,
tolerance: ExtendedBalance,
) -> usize {
if iterations == 0 { return 0; }
if iterations == 0 {
return 0
}
let mut iter = 0;
loop {
let mut max_diff = 0;
for voter in voters.iter_mut() {
let diff = balance_voter(voter, tolerance);
if diff > max_diff { max_diff = diff; }
if diff > max_diff {
max_diff = diff;
}
}
iter += 1;
if max_diff <= tolerance || iter >= iterations {
break iter;
break iter
}
}
}
@@ -80,7 +84,8 @@ pub(crate) fn balance_voter<AccountId: IdentifierT>(
tolerance: ExtendedBalance,
) -> ExtendedBalance {
// create a shallow copy of the elected ones. The original one will not be used henceforth.
let mut elected_edges = voter.edges
let mut elected_edges = voter
.edges
.iter_mut()
.filter(|e| e.candidate.borrow().elected)
.collect::<Vec<&mut Edge<AccountId>>>();
@@ -91,9 +96,8 @@ pub(crate) fn balance_voter<AccountId: IdentifierT>(
}
// amount of stake from this voter that is used in edges.
let stake_used = elected_edges
.iter()
.fold(0, |a: ExtendedBalance, e| a.saturating_add(e.weight));
let stake_used =
elected_edges.iter().fold(0, |a: ExtendedBalance, e| a.saturating_add(e.weight));
// backed stake of each of the elected edges.
let backed_stakes = elected_edges
@@ -104,13 +108,7 @@ pub(crate) fn balance_voter<AccountId: IdentifierT>(
// backed stake of all the edges for whom we've spent some stake.
let backing_backed_stake = elected_edges
.iter()
.filter_map(|e|
if e.weight > 0 {
Some(e.candidate.borrow().backed_stake)
} else {
None
}
)
.filter_map(|e| if e.weight > 0 { Some(e.candidate.borrow().backed_stake) } else { None })
.collect::<Vec<_>>();
let difference = if backing_backed_stake.len() > 0 {
@@ -125,7 +123,7 @@ pub(crate) fn balance_voter<AccountId: IdentifierT>(
let mut difference = max_stake.saturating_sub(*min_stake);
difference = difference.saturating_add(voter.budget.saturating_sub(stake_used));
if difference < tolerance {
return difference;
return difference
}
difference
} else {
@@ -156,12 +154,18 @@ pub(crate) fn balance_voter<AccountId: IdentifierT>(
cumulative_backed_stake = cumulative_backed_stake.saturating_add(backed_stake);
}
let last_stake = elected_edges.get(last_index).expect(
"length of elected_edges is greater than or equal 2; last_index index is at \
the minimum elected_edges.len() - 1; index is within range; qed"
).candidate.borrow().backed_stake;
let last_stake = elected_edges
.get(last_index)
.expect(
"length of elected_edges is greater than or equal 2; last_index index is at \
the minimum elected_edges.len() - 1; index is within range; qed",
)
.candidate
.borrow()
.backed_stake;
let ways_to_split = last_index + 1;
let excess = voter.budget
let excess = voter
.budget
.saturating_add(cumulative_backed_stake)
.saturating_sub(last_stake.saturating_mul(ways_to_split as ExtendedBalance));
@@ -17,7 +17,9 @@
//! Helper methods for npos-elections.
use crate::{Assignment, Error, IdentifierT, PerThing128, StakedAssignment, VoteWeight, WithApprovalOf};
use crate::{
Assignment, Error, IdentifierT, PerThing128, StakedAssignment, VoteWeight, WithApprovalOf,
};
use sp_arithmetic::PerThing;
use sp_std::prelude::*;
@@ -52,7 +54,8 @@ where
staked
.iter_mut()
.map(|a| {
a.try_normalize(stake_of(&a.who).into()).map_err(|err| Error::ArithmeticError(err))
a.try_normalize(stake_of(&a.who).into())
.map_err(|err| Error::ArithmeticError(err))
})
.collect::<Result<_, _>>()?;
Ok(staked)
@@ -113,14 +116,8 @@ mod tests {
assert_eq!(
staked,
vec![
StakedAssignment {
who: 1u32,
distribution: vec![(10u32, 50), (20, 50),]
},
StakedAssignment {
who: 2u32,
distribution: vec![(10u32, 33), (20, 67),]
}
StakedAssignment { who: 1u32, distribution: vec![(10u32, 50), (20, 50),] },
StakedAssignment { who: 2u32, distribution: vec![(10u32, 33), (20, 67),] }
]
);
}
+44 -50
View File
@@ -78,6 +78,7 @@ use sp_arithmetic::{
traits::{Bounded, UniqueSaturatedInto, Zero},
Normalizable, PerThing, Rational128, ThresholdOrd,
};
use sp_core::RuntimeDebug;
use sp_std::{
cell::RefCell,
cmp::Ordering,
@@ -88,7 +89,6 @@ use sp_std::{
prelude::*,
rc::Rc,
};
use sp_core::RuntimeDebug;
use codec::{Decode, Encode};
#[cfg(feature = "std")]
@@ -100,21 +100,21 @@ mod mock;
mod tests;
mod assignments;
pub mod phragmen;
pub mod balancing;
pub mod phragmms;
pub mod node;
pub mod reduce;
pub mod helpers;
pub mod node;
pub mod phragmen;
pub mod phragmms;
pub mod pjr;
pub mod reduce;
pub use assignments::{Assignment, IndexAssignment, StakedAssignment, IndexAssignmentOf};
pub use reduce::reduce;
pub use assignments::{Assignment, IndexAssignment, IndexAssignmentOf, StakedAssignment};
pub use balancing::*;
pub use helpers::*;
pub use phragmen::*;
pub use phragmms::*;
pub use balancing::*;
pub use pjr::*;
pub use reduce::reduce;
// re-export the compact macro, with the dependencies of the macro.
#[doc(hidden)]
@@ -206,9 +206,7 @@ where
/// Get the average edge count.
fn average_edge_count(&self) -> usize {
self.edge_count()
.checked_div(self.voter_count())
.unwrap_or(0)
self.edge_count().checked_div(self.voter_count()).unwrap_or(0)
}
/// Remove a certain voter.
@@ -379,9 +377,14 @@ impl<AccountId: IdentifierT> Voter<AccountId> {
.into_iter()
.filter_map(|e| {
let per_thing = P::from_rational(e.weight, budget);
// trim zero edges.
if per_thing.is_zero() { None } else { Some((e.who, per_thing)) }
}).collect::<Vec<_>>();
// trim zero edges.
if per_thing.is_zero() {
None
} else {
Some((e.who, per_thing))
}
})
.collect::<Vec<_>>();
if distribution.len() > 0 {
Some(Assignment { who, distribution })
@@ -611,10 +614,7 @@ pub fn is_score_better<P: PerThing>(this: ElectionScore, that: ElectionScore, ep
match this
.iter()
.zip(that.iter())
.map(|(thi, tha)| (
thi.ge(&tha),
thi.tcmp(&tha, epsilon.mul_ceil(*tha)),
))
.map(|(thi, tha)| (thi.ge(&tha), thi.tcmp(&tha, epsilon.mul_ceil(*tha))))
.collect::<Vec<(bool, Ordering)>>()
.as_slice()
{
@@ -653,40 +653,34 @@ pub fn setup_inputs<AccountId: IdentifierT>(
})
.collect::<Vec<CandidatePtr<AccountId>>>();
let voters = initial_voters.into_iter().filter_map(|(who, voter_stake, votes)| {
let mut edges: Vec<Edge<AccountId>> = Vec::with_capacity(votes.len());
for v in votes {
if edges.iter().any(|e| e.who == v) {
// duplicate edge.
continue;
}
if let Some(idx) = c_idx_cache.get(&v) {
// This candidate is valid + already cached.
let mut candidate = candidates[*idx].borrow_mut();
candidate.approval_stake =
candidate.approval_stake.saturating_add(voter_stake.into());
edges.push(
Edge {
let voters = initial_voters
.into_iter()
.filter_map(|(who, voter_stake, votes)| {
let mut edges: Vec<Edge<AccountId>> = Vec::with_capacity(votes.len());
for v in votes {
if edges.iter().any(|e| e.who == v) {
// duplicate edge.
continue
}
if let Some(idx) = c_idx_cache.get(&v) {
// This candidate is valid + already cached.
let mut candidate = candidates[*idx].borrow_mut();
candidate.approval_stake =
candidate.approval_stake.saturating_add(voter_stake.into());
edges.push(Edge {
who: v.clone(),
candidate: Rc::clone(&candidates[*idx]),
..Default::default()
}
);
} // else {} would be wrong votes. We don't really care about it.
}
if edges.is_empty() {
None
}
else {
Some(Voter {
who,
edges: edges,
budget: voter_stake.into(),
load: Rational128::zero(),
})
}
});
} // else {} would be wrong votes. We don't really care about it.
}
if edges.is_empty() {
None
} else {
Some(Voter { who, edges, budget: voter_stake.into(), load: Rational128::zero() })
}
})
.collect::<Vec<_>>();
}).collect::<Vec<_>>();
(candidates, voters,)
(candidates, voters)
}
+61 -71
View File
@@ -20,12 +20,12 @@
#![cfg(any(test, mocks))]
use std::{
collections::{HashSet, HashMap},
collections::{HashMap, HashSet},
convert::TryInto,
hash::Hash,
};
use rand::{self, Rng, seq::SliceRandom};
use rand::{self, seq::SliceRandom, Rng};
use sp_arithmetic::{
traits::{One, SaturatedConversion, Zero},
PerThing,
@@ -33,7 +33,7 @@ use sp_arithmetic::{
use sp_runtime::assert_eq_error_rate;
use sp_std::collections::btree_map::BTreeMap;
use crate::{Assignment, ElectionResult, ExtendedBalance, PerThing128, VoteWeight, seq_phragmen};
use crate::{seq_phragmen, Assignment, ElectionResult, ExtendedBalance, PerThing128, VoteWeight};
sp_npos_elections_compact::generate_solution_type!(
#[compact]
@@ -87,7 +87,7 @@ pub(crate) type _SupportMap<A> = BTreeMap<A, _Support<A>>;
#[derive(Debug, Clone)]
pub(crate) struct _ElectionResult<A: Clone> {
pub winners: Vec<(A, ExtendedBalance)>,
pub assignments: Vec<(A, Vec<_Assignment<A>>)>
pub assignments: Vec<(A, Vec<_Assignment<A>>)>,
}
pub(crate) fn auto_generate_self_voters<A: Clone>(candidates: &[A]) -> Vec<(A, Vec<A>)> {
@@ -99,7 +99,8 @@ pub(crate) fn elect_float<A>(
initial_candidates: Vec<A>,
initial_voters: Vec<(A, Vec<A>)>,
stake_of: impl Fn(&A) -> VoteWeight,
) -> Option<_ElectionResult<A>> where
) -> Option<_ElectionResult<A>>
where
A: Default + Ord + Copy,
{
let mut elected_candidates: Vec<(A, ExtendedBalance)>;
@@ -123,17 +124,10 @@ pub(crate) fn elect_float<A>(
for v in votes {
if let Some(idx) = c_idx_cache.get(&v) {
candidates[*idx].approval_stake = candidates[*idx].approval_stake + voter_stake;
edges.push(
_Edge { who: v.clone(), candidate_index: *idx, ..Default::default() }
);
edges.push(_Edge { who: v.clone(), candidate_index: *idx, ..Default::default() });
}
}
_Voter {
who,
edges: edges,
budget: voter_stake,
load: 0f64,
}
_Voter { who, edges, budget: voter_stake, load: 0f64 }
}));
let to_elect = candidate_count.min(candidates.len());
@@ -179,7 +173,9 @@ pub(crate) fn elect_float<A>(
for n in &mut voters {
let mut assignment = (n.who.clone(), vec![]);
for e in &mut n.edges {
if let Some(c) = elected_candidates.iter().cloned().map(|(c, _)| c).find(|c| *c == e.who) {
if let Some(c) =
elected_candidates.iter().cloned().map(|(c, _)| c).find(|c| *c == e.who)
{
if c != n.who {
let ratio = e.load / n.load;
assignment.1.push((e.who.clone(), ratio));
@@ -191,10 +187,7 @@ pub(crate) fn elect_float<A>(
}
}
Some(_ElectionResult {
winners: elected_candidates,
assignments: assigned,
})
Some(_ElectionResult { winners: elected_candidates, assignments: assigned })
}
pub(crate) fn equalize_float<A, FS>(
@@ -211,18 +204,14 @@ pub(crate) fn equalize_float<A, FS>(
let mut max_diff = 0.0;
for (voter, assignment) in assignments.iter_mut() {
let voter_budget = stake_of(&voter);
let diff = do_equalize_float(
voter,
voter_budget,
assignment,
supports,
tolerance,
);
if diff > max_diff { max_diff = diff; }
let diff = do_equalize_float(voter, voter_budget, assignment, supports, tolerance);
if diff > max_diff {
max_diff = diff;
}
}
if max_diff < tolerance {
break;
break
}
}
}
@@ -232,21 +221,20 @@ pub(crate) fn do_equalize_float<A>(
budget_balance: VoteWeight,
elected_edges: &mut Vec<_Assignment<A>>,
support_map: &mut _SupportMap<A>,
tolerance: f64
) -> f64 where
tolerance: f64,
) -> f64
where
A: Ord + Clone,
{
let budget = budget_balance as f64;
if elected_edges.is_empty() { return 0.0; }
if elected_edges.is_empty() {
return 0.0
}
let stake_used = elected_edges
.iter()
.fold(0.0, |s, e| s + e.1);
let stake_used = elected_edges.iter().fold(0.0, |s, e| s + e.1);
let backed_stakes_iter = elected_edges
.iter()
.filter_map(|e| support_map.get(&e.0))
.map(|e| e.total);
let backed_stakes_iter =
elected_edges.iter().filter_map(|e| support_map.get(&e.0)).map(|e| e.total);
let backing_backed_stake = elected_edges
.iter()
@@ -268,7 +256,7 @@ pub(crate) fn do_equalize_float<A>(
difference = max_stake - min_stake;
difference = difference + budget - stake_used;
if difference < tolerance {
return difference;
return difference
}
} else {
difference = budget;
@@ -283,11 +271,12 @@ pub(crate) fn do_equalize_float<A>(
e.1 = 0.0;
});
elected_edges.sort_by(|x, y|
support_map.get(&x.0)
elected_edges.sort_by(|x, y| {
support_map
.get(&x.0)
.and_then(|x| support_map.get(&y.0).and_then(|y| x.total.partial_cmp(&y.total)))
.unwrap_or(sp_std::cmp::Ordering::Equal)
);
});
let mut cumulative_stake = 0.0;
let mut last_index = elected_edges.len() - 1;
@@ -318,20 +307,22 @@ pub(crate) fn do_equalize_float<A>(
difference
}
pub(crate) fn create_stake_of(stakes: &[(AccountId, VoteWeight)])
-> impl Fn(&AccountId) -> VoteWeight
{
pub(crate) fn create_stake_of(
stakes: &[(AccountId, VoteWeight)],
) -> impl Fn(&AccountId) -> VoteWeight {
let mut storage = BTreeMap::<AccountId, VoteWeight>::new();
stakes.iter().for_each(|s| { storage.insert(s.0, s.1); });
stakes.iter().for_each(|s| {
storage.insert(s.0, s.1);
});
move |who: &AccountId| -> VoteWeight { storage.get(who).unwrap().to_owned() }
}
pub fn check_assignments_sum<T: PerThing>(assignments: &[Assignment<AccountId, T>]) {
for Assignment { distribution, .. } in assignments {
let mut sum: u128 = Zero::zero();
distribution.iter().for_each(|(_, p)| sum += p.deconstruct().saturated_into::<u128>());
distribution
.iter()
.for_each(|(_, p)| sum += p.deconstruct().saturated_into::<u128>());
assert_eq!(sum, T::ACCURACY.saturated_into(), "Assignment ratio sum is not 100%");
}
}
@@ -341,8 +332,7 @@ pub(crate) fn run_and_compare<Output: PerThing128, FS>(
voters: Vec<(AccountId, Vec<AccountId>)>,
stake_of: FS,
to_elect: usize,
)
where
) where
Output: PerThing128,
FS: Fn(&AccountId) -> VoteWeight,
{
@@ -350,24 +340,28 @@ where
let ElectionResult { winners, assignments } = seq_phragmen::<_, Output>(
to_elect,
candidates.clone(),
voters.iter().map(|(ref v, ref vs)| (v.clone(), stake_of(v), vs.clone())).collect::<Vec<_>>(),
None
).unwrap();
voters
.iter()
.map(|(ref v, ref vs)| (v.clone(), stake_of(v), vs.clone()))
.collect::<Vec<_>>(),
None,
)
.unwrap();
// run float poc code.
let truth_value = elect_float(
to_elect,
candidates,
voters,
&stake_of,
).unwrap();
let truth_value = elect_float(to_elect, candidates, voters, &stake_of).unwrap();
assert_eq!(winners.iter().map(|(x, _)| x).collect::<Vec<_>>(), truth_value.winners.iter().map(|(x, _)| x).collect::<Vec<_>>());
assert_eq!(
winners.iter().map(|(x, _)| x).collect::<Vec<_>>(),
truth_value.winners.iter().map(|(x, _)| x).collect::<Vec<_>>()
);
for Assignment { who, distribution } in assignments.iter() {
if let Some(float_assignments) = truth_value.assignments.iter().find(|x| x.0 == *who) {
for (candidate, per_thingy) in distribution {
if let Some(float_assignment) = float_assignments.1.iter().find(|x| x.0 == *candidate ) {
if let Some(float_assignment) =
float_assignments.1.iter().find(|x| x.0 == *candidate)
{
assert_eq_error_rate!(
Output::from_float(float_assignment.1).deconstruct(),
per_thingy.deconstruct(),
@@ -376,8 +370,7 @@ where
} else {
panic!(
"candidate mismatch. This should never happen. could not find ({:?}, {:?})",
candidate,
per_thingy,
candidate, per_thingy,
)
}
}
@@ -394,13 +387,10 @@ pub(crate) fn build_support_map_float(
stake_of: impl Fn(&AccountId) -> VoteWeight,
) -> _SupportMap<AccountId> {
let mut supports = <_SupportMap<AccountId>>::new();
result.winners
.iter()
.map(|(e, _)| (e, stake_of(e) as f64))
.for_each(|(e, s)| {
let item = _Support { own: s, total: s, ..Default::default() };
supports.insert(e.clone(), item);
});
result.winners.iter().map(|(e, _)| (e, stake_of(e) as f64)).for_each(|(e, s)| {
let item = _Support { own: s, total: s, ..Default::default() };
supports.insert(e.clone(), item);
});
for (n, assignment) in result.assignments.iter_mut() {
for (c, r) in assignment.iter_mut() {
+14 -41
View File
@@ -55,11 +55,7 @@ impl<A: fmt::Debug> sp_std::fmt::Debug for NodeId<A> {
f,
"Node({:?}, {:?})",
self.who,
if self.role == NodeRole::Voter {
"V"
} else {
"T"
}
if self.role == NodeRole::Voter { "V" } else { "T" }
)
}
}
@@ -84,12 +80,7 @@ impl<A: PartialEq> Eq for Node<A> {}
#[cfg(feature = "std")]
impl<A: fmt::Debug + Clone> fmt::Debug for Node<A> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(
f,
"({:?} --> {:?})",
self.id,
self.parent.as_ref().map(|p| p.borrow().id.clone())
)
write!(f, "({:?} --> {:?})", self.id, self.parent.as_ref().map(|p| p.borrow().id.clone()))
}
}
@@ -102,7 +93,7 @@ impl<A: PartialEq + Eq + Clone + fmt::Debug> Node<A> {
/// Returns true if `other` is the parent of `who`.
pub fn is_parent_of(who: &NodeRef<A>, other: &NodeRef<A>) -> bool {
if who.borrow().parent.is_none() {
return false;
return false
}
who.borrow().parent.as_ref() == Some(other)
}
@@ -136,7 +127,7 @@ impl<A: PartialEq + Eq + Clone + fmt::Debug> Node<A> {
while let Some(ref next_parent) = current.clone().borrow().parent {
if visited.contains(next_parent) {
break;
break
}
parent_path.push(next_parent.clone());
current = next_parent.clone();
@@ -164,16 +155,7 @@ mod tests {
#[test]
fn basic_create_works() {
let node = Node::new(id(10));
assert_eq!(
node,
Node {
id: NodeId {
who: 10,
role: NodeRole::Target
},
parent: None
}
);
assert_eq!(node, Node { id: NodeId { who: 10, role: NodeRole::Target }, parent: None });
}
#[test]
@@ -194,9 +176,9 @@ mod tests {
#[test]
fn get_root_works() {
// D <-- A <-- B <-- C
// \
// <-- E
// D <-- A <-- B <-- C
// \
// <-- E
let a = Node::new(id(1)).into_ref();
let b = Node::new(id(2)).into_ref();
let c = Node::new(id(3)).into_ref();
@@ -209,29 +191,20 @@ mod tests {
Node::set_parent_of(&e, &a);
Node::set_parent_of(&a, &d);
assert_eq!(
Node::root(&e),
(d.clone(), vec![e.clone(), a.clone(), d.clone()]),
);
assert_eq!(Node::root(&e), (d.clone(), vec![e.clone(), a.clone(), d.clone()]),);
assert_eq!(Node::root(&a), (d.clone(), vec![a.clone(), d.clone()]),);
assert_eq!(
Node::root(&c),
(d.clone(), vec![c.clone(), b.clone(), a.clone(), d.clone()]),
);
assert_eq!(Node::root(&c), (d.clone(), vec![c.clone(), b.clone(), a.clone(), d.clone()]),);
// D A <-- B <-- C
// F <-- / \
// <-- E
// D A <-- B <-- C
// F <-- / \
// <-- E
Node::set_parent_of(&a, &f);
assert_eq!(Node::root(&a), (f.clone(), vec![a.clone(), f.clone()]),);
assert_eq!(
Node::root(&c),
(f.clone(), vec![c.clone(), b.clone(), a.clone(), f.clone()]),
);
assert_eq!(Node::root(&c), (f.clone(), vec![c.clone(), b.clone(), a.clone(), f.clone()]),);
}
#[test]
@@ -75,11 +75,7 @@ pub fn seq_phragmen<AccountId: IdentifierT, P: PerThing128>(
) -> Result<ElectionResult<AccountId, P>, crate::Error> {
let (candidates, voters) = setup_inputs(initial_candidates, initial_voters);
let (candidates, mut voters) = seq_phragmen_core::<AccountId>(
rounds,
candidates,
voters,
)?;
let (candidates, mut voters) = seq_phragmen_core::<AccountId>(rounds, candidates, voters)?;
if let Some((iterations, tolerance)) = balance {
// NOTE: might create zero-edges, but we will strip them again when we convert voter into
@@ -152,7 +148,8 @@ pub fn seq_phragmen_core<AccountId: IdentifierT>(
voter.load.n(),
voter.budget,
candidate.approval_stake,
).unwrap_or(Bounded::max_value());
)
.unwrap_or(Bounded::max_value());
let temp_d = voter.load.d();
let temp = Rational128::from(temp_n, temp_d);
candidate.score = candidate.score.lazy_saturating_add(temp);
@@ -188,13 +185,9 @@ pub fn seq_phragmen_core<AccountId: IdentifierT>(
for edge in &mut voter.edges {
if edge.candidate.borrow().elected {
// update internal state.
edge.weight = multiply_by_rational(
voter.budget,
edge.load.n(),
voter.load.n(),
)
// If result cannot fit in u128. Not much we can do about it.
.unwrap_or(Bounded::max_value());
edge.weight = multiply_by_rational(voter.budget, edge.load.n(), voter.load.n())
// If result cannot fit in u128. Not much we can do about it.
.unwrap_or(Bounded::max_value());
} else {
edge.weight = 0
}
@@ -1,4 +1,4 @@
// This file is part of Substrate.
// This file is part of Substrate.
// Copyright (C) 2020-2021 Parity Technologies (UK) Ltd.
// SPDX-License-Identifier: Apache-2.0
@@ -22,10 +22,10 @@
//! MMS algorithm.
use crate::{
IdentifierT, ElectionResult, ExtendedBalance, setup_inputs, VoteWeight, Voter, CandidatePtr,
balance, PerThing128,
balance, setup_inputs, CandidatePtr, ElectionResult, ExtendedBalance, IdentifierT, PerThing128,
VoteWeight, Voter,
};
use sp_arithmetic::{PerThing, Rational128, traits::Bounded};
use sp_arithmetic::{traits::Bounded, PerThing, Rational128};
use sp_std::{prelude::*, rc::Rc};
/// Execute the phragmms method.
@@ -62,15 +62,17 @@ pub fn phragmms<AccountId: IdentifierT, P: PerThing128>(
balance(&mut voters, iterations, tolerance);
}
} else {
break;
break
}
}
let mut assignments = voters.into_iter().filter_map(|v| v.into_assignment()).collect::<Vec<_>>();
let mut assignments =
voters.into_iter().filter_map(|v| v.into_assignment()).collect::<Vec<_>>();
let _ = assignments.iter_mut().map(|a| a.try_normalize()).collect::<Result<(), _>>()?;
let winners = winners.into_iter().map(|w_ptr|
(w_ptr.borrow().who.clone(), w_ptr.borrow().backed_stake)
).collect();
let winners = winners
.into_iter()
.map(|w_ptr| (w_ptr.borrow().who.clone(), w_ptr.borrow().backed_stake))
.collect();
Ok(ElectionResult { winners, assignments })
}
@@ -101,10 +103,8 @@ pub(crate) fn calculate_max_score<AccountId: IdentifierT, P: PerThing>(
for edge in voter.edges.iter() {
let edge_candidate = edge.candidate.borrow();
if edge_candidate.elected {
let edge_contribution: ExtendedBalance = P::from_rational(
edge.weight,
edge_candidate.backed_stake,
).deconstruct().into();
let edge_contribution: ExtendedBalance =
P::from_rational(edge.weight, edge_candidate.backed_stake).deconstruct().into();
denominator_contribution += edge_contribution;
}
}
@@ -125,7 +125,7 @@ pub(crate) fn calculate_max_score<AccountId: IdentifierT, P: PerThing>(
for c_ptr in candidates.iter() {
let mut candidate = c_ptr.borrow_mut();
if candidate.approval_stake > 0 {
if candidate.approval_stake > 0 {
// finalise the score value.
let score_d = candidate.score.d();
let one: ExtendedBalance = P::ACCURACY.into();
@@ -153,7 +153,10 @@ pub(crate) fn calculate_max_score<AccountId: IdentifierT, P: PerThing>(
// `RationalInfinite` as the score type does not introduce significant overhead. Then we
// can switch the score type to `RationalInfinite` and ensure compatibility with any
// crazy token scale.
let score_n = candidate.approval_stake.checked_mul(one).unwrap_or_else(|| Bounded::max_value());
let score_n = candidate
.approval_stake
.checked_mul(one)
.unwrap_or_else(|| Bounded::max_value());
candidate.score = Rational128::from(score_n, score_d);
// check if we have a new winner.
@@ -180,7 +183,10 @@ pub(crate) fn apply_elected<AccountId: IdentifierT>(
elected_ptr: CandidatePtr<AccountId>,
) {
let elected_who = elected_ptr.borrow().who.clone();
let cutoff = elected_ptr.borrow().score.to_den(1)
let cutoff = elected_ptr
.borrow()
.score
.to_den(1)
.expect("(n / d) < u128::MAX and (n' / 1) == (n / d), thus n' < u128::MAX'; qed.")
.n();
@@ -193,18 +199,19 @@ pub(crate) fn apply_elected<AccountId: IdentifierT>(
elected_backed_stake = elected_backed_stake.saturating_add(new_edge_weight);
// Iterate over all other edges.
for (_, edge) in voter.edges
.iter_mut()
.enumerate()
.filter(|(edge_index, edge_inner)| *edge_index != new_edge_index && edge_inner.weight > 0)
{
for (_, edge) in
voter.edges.iter_mut().enumerate().filter(|(edge_index, edge_inner)| {
*edge_index != new_edge_index && edge_inner.weight > 0
}) {
let mut edge_candidate = edge.candidate.borrow_mut();
if edge_candidate.backed_stake > cutoff {
let stake_to_take = edge.weight.saturating_mul(cutoff) / edge_candidate.backed_stake.max(1);
let stake_to_take =
edge.weight.saturating_mul(cutoff) / edge_candidate.backed_stake.max(1);
// subtract this amount from this edge.
edge.weight = edge.weight.saturating_sub(stake_to_take);
edge_candidate.backed_stake = edge_candidate.backed_stake.saturating_sub(stake_to_take);
edge_candidate.backed_stake =
edge_candidate.backed_stake.saturating_sub(stake_to_take);
// inject it into the outer loop's edge.
elected_backed_stake = elected_backed_stake.saturating_add(stake_to_take);
@@ -223,7 +230,7 @@ pub(crate) fn apply_elected<AccountId: IdentifierT>(
#[cfg(test)]
mod tests {
use super::*;
use crate::{ElectionResult, Assignment};
use crate::{Assignment, ElectionResult};
use sp_runtime::{Perbill, Percent};
use sp_std::rc::Rc;
@@ -232,32 +239,31 @@ mod tests {
//! Manually run the internal steps of phragmms. In each round we select a new winner by
//! `max_score`, then apply this change by `apply_elected`, and finally do a `balance` round.
let candidates = vec![1, 2, 3];
let voters = vec![
(10, 10, vec![1, 2]),
(20, 20, vec![1, 3]),
(30, 30, vec![2, 3]),
];
let voters = vec![(10, 10, vec![1, 2]), (20, 20, vec![1, 3]), (30, 30, vec![2, 3])];
let (candidates, mut voters) = setup_inputs(candidates, voters);
// Round 1
let winner = calculate_max_score::<u32, Percent>(candidates.as_ref(), voters.as_ref()).unwrap();
let winner =
calculate_max_score::<u32, Percent>(candidates.as_ref(), voters.as_ref()).unwrap();
assert_eq!(winner.borrow().who, 3);
assert_eq!(winner.borrow().score, 50u32.into());
apply_elected(&mut voters, Rc::clone(&winner));
assert_eq!(
voters.iter().find(|x| x.who == 30).map(|v| (
v.who,
v.edges.iter().map(|e| (e.who, e.weight)).collect::<Vec<_>>()
)).unwrap(),
voters
.iter()
.find(|x| x.who == 30)
.map(|v| (v.who, v.edges.iter().map(|e| (e.who, e.weight)).collect::<Vec<_>>()))
.unwrap(),
(30, vec![(2, 0), (3, 30)]),
);
assert_eq!(
voters.iter().find(|x| x.who == 20).map(|v| (
v.who,
v.edges.iter().map(|e| (e.who, e.weight)).collect::<Vec<_>>()
)).unwrap(),
voters
.iter()
.find(|x| x.who == 20)
.map(|v| (v.who, v.edges.iter().map(|e| (e.who, e.weight)).collect::<Vec<_>>()))
.unwrap(),
(20, vec![(1, 0), (3, 20)]),
);
@@ -270,30 +276,34 @@ mod tests {
balance(&mut voters, 10, 0);
// round 2
let winner = calculate_max_score::<u32, Percent>(candidates.as_ref(), voters.as_ref()).unwrap();
let winner =
calculate_max_score::<u32, Percent>(candidates.as_ref(), voters.as_ref()).unwrap();
assert_eq!(winner.borrow().who, 2);
assert_eq!(winner.borrow().score, 25u32.into());
apply_elected(&mut voters, Rc::clone(&winner));
assert_eq!(
voters.iter().find(|x| x.who == 30).map(|v| (
v.who,
v.edges.iter().map(|e| (e.who, e.weight)).collect::<Vec<_>>()
)).unwrap(),
voters
.iter()
.find(|x| x.who == 30)
.map(|v| (v.who, v.edges.iter().map(|e| (e.who, e.weight)).collect::<Vec<_>>()))
.unwrap(),
(30, vec![(2, 15), (3, 15)]),
);
assert_eq!(
voters.iter().find(|x| x.who == 20).map(|v| (
v.who,
v.edges.iter().map(|e| (e.who, e.weight)).collect::<Vec<_>>()
)).unwrap(),
voters
.iter()
.find(|x| x.who == 20)
.map(|v| (v.who, v.edges.iter().map(|e| (e.who, e.weight)).collect::<Vec<_>>()))
.unwrap(),
(20, vec![(1, 0), (3, 20)]),
);
assert_eq!(
voters.iter().find(|x| x.who == 10).map(|v| (
v.who,
v.edges.iter().map(|e| (e.who, e.weight)).collect::<Vec<_>>()
)).unwrap(),
voters
.iter()
.find(|x| x.who == 10)
.map(|v| (v.who, v.edges.iter().map(|e| (e.who, e.weight)).collect::<Vec<_>>()))
.unwrap(),
(10, vec![(1, 0), (2, 10)]),
);
@@ -306,24 +316,27 @@ mod tests {
balance(&mut voters, 10, 0);
assert_eq!(
voters.iter().find(|x| x.who == 30).map(|v| (
v.who,
v.edges.iter().map(|e| (e.who, e.weight)).collect::<Vec<_>>()
)).unwrap(),
voters
.iter()
.find(|x| x.who == 30)
.map(|v| (v.who, v.edges.iter().map(|e| (e.who, e.weight)).collect::<Vec<_>>()))
.unwrap(),
(30, vec![(2, 20), (3, 10)]),
);
assert_eq!(
voters.iter().find(|x| x.who == 20).map(|v| (
v.who,
v.edges.iter().map(|e| (e.who, e.weight)).collect::<Vec<_>>()
)).unwrap(),
voters
.iter()
.find(|x| x.who == 20)
.map(|v| (v.who, v.edges.iter().map(|e| (e.who, e.weight)).collect::<Vec<_>>()))
.unwrap(),
(20, vec![(1, 0), (3, 20)]),
);
assert_eq!(
voters.iter().find(|x| x.who == 10).map(|v| (
v.who,
v.edges.iter().map(|e| (e.who, e.weight)).collect::<Vec<_>>()
)).unwrap(),
voters
.iter()
.find(|x| x.who == 10)
.map(|v| (v.who, v.edges.iter().map(|e| (e.who, e.weight)).collect::<Vec<_>>()))
.unwrap(),
(10, vec![(1, 0), (2, 10)]),
);
}
@@ -331,25 +344,16 @@ mod tests {
#[test]
fn basic_election_works() {
let candidates = vec![1, 2, 3];
let voters = vec![
(10, 10, vec![1, 2]),
(20, 20, vec![1, 3]),
(30, 30, vec![2, 3]),
];
let voters = vec![(10, 10, vec![1, 2]), (20, 20, vec![1, 3]), (30, 30, vec![2, 3])];
let ElectionResult { winners, assignments } = phragmms::<_, Perbill>(2, candidates, voters, Some((2, 0))).unwrap();
let ElectionResult { winners, assignments } =
phragmms::<_, Perbill>(2, candidates, voters, Some((2, 0))).unwrap();
assert_eq!(winners, vec![(3, 30), (2, 30)]);
assert_eq!(
assignments,
vec![
Assignment {
who: 10u64,
distribution: vec![(2, Perbill::one())],
},
Assignment {
who: 20,
distribution: vec![(3, Perbill::one())],
},
Assignment { who: 10u64, distribution: vec![(2, Perbill::one())] },
Assignment { who: 20, distribution: vec![(3, Perbill::one())] },
Assignment {
who: 30,
distribution: vec![
@@ -374,13 +378,9 @@ mod tests {
(130, 1000, vec![61, 71]),
];
let ElectionResult { winners, assignments: _ } = phragmms::<_, Perbill>(4, candidates, voters, Some((2, 0))).unwrap();
assert_eq!(winners, vec![
(11, 3000),
(31, 2000),
(51, 1500),
(61, 1500),
]);
let ElectionResult { winners, assignments: _ } =
phragmms::<_, Perbill>(4, candidates, voters, Some((2, 0))).unwrap();
assert_eq!(winners, vec![(11, 3000), (31, 2000), (51, 1500), (61, 1500),]);
}
#[test]
@@ -391,7 +391,8 @@ mod tests {
// give a bit more to 1 and 3.
voters.push((2, u64::MAX, vec![1, 3]));
let ElectionResult { winners, assignments: _ } = phragmms::<_, Perbill>(2, candidates, voters, Some((2, 0))).unwrap();
let ElectionResult { winners, assignments: _ } =
phragmms::<_, Perbill>(2, candidates, voters, Some((2, 0))).unwrap();
assert_eq!(winners.into_iter().map(|(w, _)| w).collect::<Vec<_>>(), vec![1u32, 3]);
}
}
+110 -115
View File
@@ -1,4 +1,4 @@
// This file is part of Substrate.
// This file is part of Substrate.
// Copyright (C) 2021 Parity Technologies (UK) Ltd.
// SPDX-License-Identifier: Apache-2.0
@@ -23,20 +23,11 @@
//! See [`pjr_check`] which is the main entry point of the module.
use crate::{
Candidate,
CandidatePtr,
Edge,
ExtendedBalance,
IdentifierT,
Support,
SupportMap,
Supports,
Voter,
VoteWeight,
Candidate, CandidatePtr, Edge, ExtendedBalance, IdentifierT, Support, SupportMap, Supports,
VoteWeight, Voter,
};
use sp_std::{rc::Rc, vec::Vec};
use sp_std::collections::btree_map::BTreeMap;
use sp_arithmetic::{traits::Zero, Perbill};
use sp_std::{collections::btree_map::BTreeMap, rc::Rc, vec::Vec};
/// The type used as the threshold.
///
/// Just some reading sugar; Must always be same as [`ExtendedBalance`];
@@ -60,10 +51,8 @@ pub fn standard_threshold(
) -> Threshold {
weights
.into_iter()
.fold(Threshold::zero(), |acc, elem| {
acc.saturating_add(elem)
})
/ committee_size.max(1) as Threshold
.fold(Threshold::zero(), |acc, elem| acc.saturating_add(elem)) /
committee_size.max(1) as Threshold
}
/// Check a solution to be PJR.
@@ -74,7 +63,10 @@ pub fn pjr_check<AccountId: IdentifierT>(
all_candidates: Vec<AccountId>,
all_voters: Vec<(AccountId, VoteWeight, Vec<AccountId>)>,
) -> Result<(), AccountId> {
let t = standard_threshold(supports.len(), all_voters.iter().map(|voter| voter.1 as ExtendedBalance));
let t = standard_threshold(
supports.len(),
all_voters.iter().map(|voter| voter.1 as ExtendedBalance),
);
t_pjr_check(supports, all_candidates, all_voters, t)
}
@@ -101,7 +93,6 @@ pub fn pjr_check<AccountId: IdentifierT>(
/// needs to inspect un-elected candidates and edges, thus `all_candidates` and `all_voters`.
///
/// [NPoS]: https://arxiv.org/pdf/2004.12990v1.pdf
//
// ### Implementation Notes
//
// The paper uses mathematical notation, which priorities single-symbol names. For programmer ease,
@@ -120,11 +111,7 @@ pub fn t_pjr_check<AccountId: IdentifierT>(
t: Threshold,
) -> Result<(), AccountId> {
// First order of business: derive `(candidates, voters)` from `supports`.
let (candidates, voters) = prepare_pjr_input(
supports,
all_candidates,
all_voters,
);
let (candidates, voters) = prepare_pjr_input(supports, all_candidates, all_voters);
// compute with threshold t.
pjr_check_core(candidates.as_ref(), voters.as_ref(), t)
}
@@ -141,7 +128,9 @@ pub fn pjr_check_core<AccountId: IdentifierT>(
t: Threshold,
) -> Result<(), AccountId> {
let unelected = candidates.iter().filter(|c| !c.borrow().elected);
let maybe_max_pre_score = unelected.map(|c| (pre_score(Rc::clone(c), voters, t), c.borrow().who.clone())).max();
let maybe_max_pre_score = unelected
.map(|c| (pre_score(Rc::clone(c), voters, t), c.borrow().who.clone()))
.max();
// if unelected is empty then the solution is indeed PJR.
match maybe_max_pre_score {
Some((max_pre_score, counter_example)) if max_pre_score >= t => Err(counter_example),
@@ -165,7 +154,10 @@ pub fn validate_pjr_challenge<AccountId: IdentifierT>(
all_candidates: Vec<AccountId>,
all_voters: Vec<(AccountId, VoteWeight, Vec<AccountId>)>,
) -> bool {
let threshold = standard_threshold(supports.len(), all_voters.iter().map(|voter| voter.1 as ExtendedBalance));
let threshold = standard_threshold(
supports.len(),
all_voters.iter().map(|voter| voter.1 as ExtendedBalance),
);
validate_t_pjr_challenge(counter_example, supports, all_candidates, all_voters, threshold)
}
@@ -186,11 +178,7 @@ pub fn validate_t_pjr_challenge<AccountId: IdentifierT>(
all_voters: Vec<(AccountId, VoteWeight, Vec<AccountId>)>,
threshold: Threshold,
) -> bool {
let (candidates, voters) = prepare_pjr_input(
supports,
all_candidates,
all_voters,
);
let (candidates, voters) = prepare_pjr_input(supports, all_candidates, all_voters);
validate_pjr_challenge_core(counter_example, &candidates, &voters, threshold)
}
@@ -219,10 +207,11 @@ fn validate_pjr_challenge_core<AccountId: IdentifierT>(
// unsafe code leveraging the existing `candidates_index`: allocate an uninitialized vector of
// appropriate length, then copy in all the elements. We'd really prefer to avoid unsafe code
// in the runtime, though.
let candidate = match candidates.iter().find(|candidate| candidate.borrow().who == counter_example) {
None => return false,
Some(candidate) => candidate.clone(),
};
let candidate =
match candidates.iter().find(|candidate| candidate.borrow().who == counter_example) {
None => return false,
Some(candidate) => candidate.clone(),
};
pre_score(candidate, &voters, threshold) >= threshold
}
@@ -261,10 +250,14 @@ fn prepare_pjr_input<AccountId: IdentifierT>(
let mut candidates_index: BTreeMap<AccountId, usize> = BTreeMap::new();
// dump the staked assignments in a voter-major map for faster access down the road.
let mut assignment_map: BTreeMap<AccountId, Vec<(AccountId, ExtendedBalance)>> = BTreeMap::new();
let mut assignment_map: BTreeMap<AccountId, Vec<(AccountId, ExtendedBalance)>> =
BTreeMap::new();
for (winner_id, Support { voters, .. }) in supports.iter() {
for (voter_id, support) in voters.iter() {
assignment_map.entry(voter_id.clone()).or_default().push((winner_id.clone(), *support));
assignment_map
.entry(voter_id.clone())
.or_default()
.push((winner_id.clone(), *support));
}
}
@@ -282,47 +275,56 @@ fn prepare_pjr_input<AccountId: IdentifierT>(
let supports: SupportMap<AccountId> = supports.iter().cloned().collect();
// collect all candidates and winners into a unified `Vec<CandidatePtr>`.
let candidates = all_candidates.into_iter().enumerate().map(|(i, c)| {
candidates_index.insert(c.clone(), i);
let candidates = all_candidates
.into_iter()
.enumerate()
.map(|(i, c)| {
candidates_index.insert(c.clone(), i);
// set the backing value and elected flag if the candidate is among the winners.
let who = c;
let maybe_support = supports.get(&who);
let elected = maybe_support.is_some();
let backed_stake = maybe_support.map(|support| support.total).unwrap_or_default();
// set the backing value and elected flag if the candidate is among the winners.
let who = c;
let maybe_support = supports.get(&who);
let elected = maybe_support.is_some();
let backed_stake = maybe_support.map(|support| support.total).unwrap_or_default();
Candidate { who, elected, backed_stake, ..Default::default() }.to_ptr()
}).collect::<Vec<_>>();
Candidate { who, elected, backed_stake, ..Default::default() }.to_ptr()
})
.collect::<Vec<_>>();
// collect all voters into a unified Vec<Voters>.
let voters = all_voters.into_iter().map(|(v, w, ts)| {
let mut edges: Vec<Edge<AccountId>> = Vec::with_capacity(ts.len());
for t in ts {
if edges.iter().any(|e| e.who == t) {
// duplicate edge.
continue;
let voters = all_voters
.into_iter()
.map(|(v, w, ts)| {
let mut edges: Vec<Edge<AccountId>> = Vec::with_capacity(ts.len());
for t in ts {
if edges.iter().any(|e| e.who == t) {
// duplicate edge.
continue
}
if let Some(idx) = candidates_index.get(&t) {
// if this edge is among the assignments, set the weight as well.
let weight = assignment_map
.get(&v)
.and_then(|d| {
d.iter().find_map(|(x, y)| if x == &t { Some(y) } else { None })
})
.cloned()
.unwrap_or_default();
edges.push(Edge {
who: t,
candidate: Rc::clone(&candidates[*idx]),
weight,
..Default::default()
});
}
}
if let Some(idx) = candidates_index.get(&t) {
// if this edge is among the assignments, set the weight as well.
let weight = assignment_map
.get(&v)
.and_then(|d| d.iter().find_map(|(x, y)| if x == &t { Some(y) } else { None }))
.cloned()
.unwrap_or_default();
edges.push(Edge {
who: t,
candidate: Rc::clone(&candidates[*idx]),
weight,
..Default::default()
});
}
}
let who = v;
let budget: ExtendedBalance = w.into();
Voter { who, budget, edges, ..Default::default() }
}).collect::<Vec<_>>();
let who = v;
let budget: ExtendedBalance = w.into();
Voter { who, budget, edges, ..Default::default() }
})
.collect::<Vec<_>>();
(candidates, voters)
}
@@ -345,7 +347,6 @@ fn pre_score<AccountId: IdentifierT>(
.fold(Zero::zero(), |acc: ExtendedBalance, voter| acc.saturating_add(slack(voter, t)))
}
/// The slack of a voter at a given state.
///
/// The slack of each voter, with threshold `t` is the total amount of stake that this voter can
@@ -363,8 +364,7 @@ fn slack<AccountId: IdentifierT>(voter: &Voter<AccountId>, t: Threshold) -> Exte
let candidate = edge.candidate.borrow();
if candidate.elected {
let extra =
Perbill::one().min(Perbill::from_rational(t, candidate.backed_stake))
* edge.weight;
Perbill::one().min(Perbill::from_rational(t, candidate.backed_stake)) * edge.weight;
acc.saturating_add(extra)
} else {
// No slack generated here.
@@ -383,13 +383,22 @@ mod tests {
fn setup_voter(who: u32, votes: Vec<(u32, u128, bool)>) -> Voter<u32> {
let mut voter = Voter::new(who);
let mut budget = 0u128;
let candidates = votes.into_iter().map(|(t, w, e)| {
budget += w;
Candidate { who: t, elected: e, backed_stake: w, ..Default::default() }
}).collect::<Vec<_>>();
let edges = candidates.into_iter().map(|c|
Edge { who: c.who, weight: c.backed_stake, candidate: c.to_ptr(), ..Default::default() }
).collect::<Vec<_>>();
let candidates = votes
.into_iter()
.map(|(t, w, e)| {
budget += w;
Candidate { who: t, elected: e, backed_stake: w, ..Default::default() }
})
.collect::<Vec<_>>();
let edges = candidates
.into_iter()
.map(|c| Edge {
who: c.who,
weight: c.backed_stake,
candidate: c.to_ptr(),
..Default::default()
})
.collect::<Vec<_>>();
voter.edges = edges;
voter.budget = budget;
voter
@@ -412,7 +421,6 @@ mod tests {
assert_eq!(slack(&voter, 17), 3);
assert_eq!(slack(&voter, 10), 10);
assert_eq!(slack(&voter, 5), 20);
}
#[test]
@@ -440,15 +448,11 @@ mod tests {
];
// tuples in voters vector are (AccountId, Balance)
let supports: Supports<u32> = vec![
(20, Support { total: 15, voters: vec![(1, 5), (2, 10)]}),
(40, Support { total: 15, voters: vec![(1, 5), (2, 10)]}),
(20, Support { total: 15, voters: vec![(1, 5), (2, 10)] }),
(40, Support { total: 15, voters: vec![(1, 5), (2, 10)] }),
];
let (candidates, voters) = prepare_pjr_input(
&supports,
all_candidates,
all_voters,
);
let (candidates, voters) = prepare_pjr_input(&supports, all_candidates, all_voters);
// elected flag and backing must be set correctly
assert_eq!(
@@ -467,7 +471,8 @@ mod tests {
v.who,
v.budget,
v.edges.iter().map(|e| (e.who, e.weight)).collect::<Vec<_>>(),
)).collect::<Vec<_>>(),
))
.collect::<Vec<_>>(),
vec![
(1, 10, vec![(10, 0), (20, 5), (30, 0), (40, 5)]),
(2, 20, vec![(10, 0), (20, 10), (30, 0), (40, 10)]),
@@ -498,15 +503,11 @@ mod tests {
];
// tuples in voters vector are (AccountId, Balance)
let supports: Supports<u32> = vec![
(20, Support { total: 15, voters: vec![(1, 5), (2, 10)]}),
(40, Support { total: 15, voters: vec![(1, 5), (2, 10)]}),
(20, Support { total: 15, voters: vec![(1, 5), (2, 10)] }),
(40, Support { total: 15, voters: vec![(1, 5), (2, 10)] }),
];
let (candidates, voters) = prepare_pjr_input(
&supports,
all_candidates,
all_voters,
);
let (candidates, voters) = prepare_pjr_input(&supports, all_candidates, all_voters);
find_threshold_phase_change_for_scenario(candidates, voters);
}
@@ -521,15 +522,11 @@ mod tests {
];
// tuples in voters vector are (AccountId, Balance)
let supports: Supports<u32> = vec![
(20, Support { total: 15, voters: vec![(1, 5), (2, 10)]}),
(40, Support { total: 15, voters: vec![(1, 5), (2, 10)]}),
(20, Support { total: 15, voters: vec![(1, 5), (2, 10)] }),
(40, Support { total: 15, voters: vec![(1, 5), (2, 10)] }),
];
let (candidates, voters) = prepare_pjr_input(
&supports,
all_candidates,
all_voters,
);
let (candidates, voters) = prepare_pjr_input(&supports, all_candidates, all_voters);
find_threshold_phase_change_for_scenario(candidates, voters);
}
@@ -544,22 +541,18 @@ mod tests {
];
// tuples in voters vector are (AccountId, Balance)
let supports: Supports<u32> = vec![
(20, Support { total: 15, voters: vec![(1, 5), (2, 10)]}),
(40, Support { total: 15, voters: vec![(1, 5), (2, 10)]}),
(20, Support { total: 15, voters: vec![(1, 5), (2, 10)] }),
(40, Support { total: 15, voters: vec![(1, 5), (2, 10)] }),
];
let (candidates, voters) = prepare_pjr_input(
&supports,
all_candidates,
all_voters,
);
let (candidates, voters) = prepare_pjr_input(&supports, all_candidates, all_voters);
find_threshold_phase_change_for_scenario(candidates, voters);
}
fn find_threshold_phase_change_for_scenario<AccountId: IdentifierT>(
candidates: Vec<CandidatePtr<AccountId>>,
voters: Vec<Voter<AccountId>>
voters: Vec<Voter<AccountId>>,
) -> Threshold {
let mut threshold = 1;
let mut prev_threshold = 0;
@@ -567,7 +560,9 @@ mod tests {
// find the binary range containing the threshold beyond which the PJR check succeeds
while pjr_check_core(&candidates, &voters, threshold).is_err() {
prev_threshold = threshold;
threshold = threshold.checked_mul(2).expect("pjr check must fail before we run out of capacity in u128");
threshold = threshold
.checked_mul(2)
.expect("pjr check must fail before we run out of capacity in u128");
}
// now binary search within that range to find the phase threshold
@@ -595,7 +590,7 @@ mod tests {
unexpected_successes.push(t);
}
}
for t in high_bound..(high_bound*2) {
for t in high_bound..(high_bound * 2) {
if pjr_check_core(&candidates, &voters, t).is_err() {
unexpected_failures.push(t);
}
+136 -314
View File
@@ -47,13 +47,15 @@
//!
//! 1. <https://hackmd.io/JOn9x98iS0e0DPWQ87zGWg?view>
use crate::node::{Node, NodeId, NodeRef, NodeRole};
use crate::{ExtendedBalance, IdentifierT, StakedAssignment};
use crate::{
node::{Node, NodeId, NodeRef, NodeRole},
ExtendedBalance, IdentifierT, StakedAssignment,
};
use sp_arithmetic::traits::{Bounded, Zero};
use sp_std::{
collections::btree_map::{BTreeMap, Entry::*},
vec,
prelude::*,
vec,
};
/// Map type used for reduce_4. Can be easily swapped with HashMap.
@@ -63,7 +65,7 @@ type Map<A> = BTreeMap<(A, A), A>;
fn combinations_2<T: Clone>(input: &[T]) -> Vec<(T, T)> {
let n = input.len();
if n < 2 {
return Default::default();
return Default::default()
}
let mut comb = Vec::with_capacity(n * (n - 1) / 2);
@@ -126,7 +128,7 @@ fn reduce_4<A: IdentifierT>(assignments: &mut Vec<StakedAssignment<A>>) -> u32 {
match combination_map.entry((v1.clone(), v2.clone())) {
Vacant(entry) => {
entry.insert(who.clone());
}
},
Occupied(mut entry) => {
let other_who = entry.get_mut();
@@ -141,29 +143,30 @@ fn reduce_4<A: IdentifierT>(assignments: &mut Vec<StakedAssignment<A>>) -> u32 {
.filter(|(t, _)| *t == v1 || *t == v2)
.count() != 2
{
continue;
continue
}
// check if other_who voted for the same pair v1, v2.
let maybe_other_assignments = assignments.iter().find(|a| a.who == *other_who);
if maybe_other_assignments.is_none() {
continue;
continue
}
let other_assignment =
maybe_other_assignments.expect("value is checked to be 'Some'");
// Collect potential cycle votes
let mut other_cycle_votes = other_assignment
.distribution
.iter()
.filter_map(|(t, w)| {
if *t == v1 || *t == v2 {
Some((t.clone(), *w))
} else {
None
}
})
.collect::<Vec<(A, ExtendedBalance)>>();
let mut other_cycle_votes =
other_assignment
.distribution
.iter()
.filter_map(|(t, w)| {
if *t == v1 || *t == v2 {
Some((t.clone(), *w))
} else {
None
}
})
.collect::<Vec<(A, ExtendedBalance)>>();
let other_votes_count = other_cycle_votes.len();
@@ -175,21 +178,18 @@ fn reduce_4<A: IdentifierT>(assignments: &mut Vec<StakedAssignment<A>>) -> u32 {
if other_votes_count < 2 {
// This is not a cycle. Replace and continue.
*other_who = who.clone();
continue;
continue
} else if other_votes_count == 2 {
// This is a cycle.
let mut who_cycle_votes: Vec<(A, ExtendedBalance)> = Vec::with_capacity(2);
assignments[assignment_index]
.distribution
.iter()
.for_each(|(t, w)| {
if *t == v1 || *t == v2 {
who_cycle_votes.push((t.clone(), *w));
}
});
assignments[assignment_index].distribution.iter().for_each(|(t, w)| {
if *t == v1 || *t == v2 {
who_cycle_votes.push((t.clone(), *w));
}
});
if who_cycle_votes.len() != 2 {
continue;
continue
}
// Align the targets similarly. This helps with the circulation below.
@@ -240,53 +240,39 @@ fn reduce_4<A: IdentifierT>(assignments: &mut Vec<StakedAssignment<A>>) -> u32 {
// apply changes
let mut remove_indices: Vec<usize> = Vec::with_capacity(1);
increase_indices.into_iter().for_each(|i| {
let voter = if i < 2 {
who.clone()
} else {
other_who.clone()
};
let voter = if i < 2 { who.clone() } else { other_who.clone() };
// Note: so this is pretty ambiguous. We should only look for one
// assignment that meets this criteria and if we find multiple then that
// is a corrupt input. Same goes for the next block.
assignments
.iter_mut()
.filter(|a| a.who == voter)
.for_each(|ass| {
ass.distribution
.iter_mut()
.position(|(t, _)| *t == cycle[i].0)
.map(|idx| {
let next_value =
ass.distribution[idx].1.saturating_add(min_value);
ass.distribution[idx].1 = next_value;
});
});
assignments.iter_mut().filter(|a| a.who == voter).for_each(|ass| {
ass.distribution
.iter_mut()
.position(|(t, _)| *t == cycle[i].0)
.map(|idx| {
let next_value =
ass.distribution[idx].1.saturating_add(min_value);
ass.distribution[idx].1 = next_value;
});
});
});
decrease_indices.into_iter().for_each(|i| {
let voter = if i < 2 {
who.clone()
} else {
other_who.clone()
};
assignments
.iter_mut()
.filter(|a| a.who == voter)
.for_each(|ass| {
ass.distribution
.iter_mut()
.position(|(t, _)| *t == cycle[i].0)
.map(|idx| {
let next_value =
ass.distribution[idx].1.saturating_sub(min_value);
if next_value.is_zero() {
ass.distribution.remove(idx);
remove_indices.push(i);
num_changed += 1;
} else {
ass.distribution[idx].1 = next_value;
}
});
});
let voter = if i < 2 { who.clone() } else { other_who.clone() };
assignments.iter_mut().filter(|a| a.who == voter).for_each(|ass| {
ass.distribution
.iter_mut()
.position(|(t, _)| *t == cycle[i].0)
.map(|idx| {
let next_value =
ass.distribution[idx].1.saturating_sub(min_value);
if next_value.is_zero() {
ass.distribution.remove(idx);
remove_indices.push(i);
num_changed += 1;
} else {
ass.distribution[idx].1 = next_value;
}
});
});
});
// remove either one of them.
@@ -297,21 +283,21 @@ fn reduce_4<A: IdentifierT>(assignments: &mut Vec<StakedAssignment<A>>) -> u32 {
match (who_removed, other_removed) {
(false, true) => {
*other_who = who.clone();
}
},
(true, false) => {
// nothing, other_who can stay there.
}
},
(true, true) => {
// remove and don't replace
entry.remove();
}
},
(false, false) => {
// Neither of the edges was removed? impossible.
panic!("Duplicate voter (or other corrupt input).");
}
},
}
}
}
},
}
}
}
@@ -350,7 +336,7 @@ fn reduce_all<A: IdentifierT>(assignments: &mut Vec<StakedAssignment<A>>) -> u32
let maybe_dist = assignments[assignment_index].distribution.get(dist_index);
if maybe_dist.is_none() {
// The rest of this loop is moot.
break;
break
}
let (target, _) = maybe_dist.expect("Value checked to be some").clone();
@@ -377,19 +363,19 @@ fn reduce_all<A: IdentifierT>(assignments: &mut Vec<StakedAssignment<A>>) -> u32
(false, false) => {
Node::set_parent_of(&target_node, &voter_node);
dist_index += 1;
continue;
}
continue
},
(false, true) => {
Node::set_parent_of(&voter_node, &target_node);
dist_index += 1;
continue;
}
continue
},
(true, false) => {
Node::set_parent_of(&target_node, &voter_node);
dist_index += 1;
continue;
}
(true, true) => { /* don't continue and execute the rest */ }
continue
},
(true, true) => { /* don't continue and execute the rest */ },
};
let (voter_root, voter_root_path) = Node::root(&voter_node);
@@ -405,10 +391,7 @@ fn reduce_all<A: IdentifierT>(assignments: &mut Vec<StakedAssignment<A>>) -> u32
// because roots are the same.
#[cfg(feature = "std")]
debug_assert_eq!(
target_root_path.last().unwrap(),
voter_root_path.last().unwrap()
);
debug_assert_eq!(target_root_path.last().unwrap(), voter_root_path.last().unwrap());
debug_assert!(common_count > 0);
// cycle part of each path will be `path[path.len() - common_count - 1 : 0]`
@@ -602,7 +585,7 @@ fn reduce_all<A: IdentifierT>(assignments: &mut Vec<StakedAssignment<A>>) -> u32
let current = voter_root_path[i].clone().borrow().id.who.clone();
let next = voter_root_path[i + 1].clone().borrow().id.who.clone();
if min_edge.contains(&current) && min_edge.contains(&next) {
break;
break
}
Node::set_parent_of(&voter_root_path[i + 1], &voter_root_path[i]);
}
@@ -613,7 +596,7 @@ fn reduce_all<A: IdentifierT>(assignments: &mut Vec<StakedAssignment<A>>) -> u32
let current = target_root_path[i].clone().borrow().id.who.clone();
let next = target_root_path[i + 1].clone().borrow().id.who.clone();
if min_edge.contains(&current) && min_edge.contains(&next) {
break;
break
}
Node::set_parent_of(&target_root_path[i + 1], &target_root_path[i]);
}
@@ -663,9 +646,9 @@ mod tests {
#[test]
fn merging_works() {
// D <-- A <-- B <-- C
// D <-- A <-- B <-- C
//
// F <-- E
// F <-- E
let d = Node::new(NodeId::from(1, NodeRole::Target)).into_ref();
let a = Node::new(NodeId::from(2, NodeRole::Target)).into_ref();
let b = Node::new(NodeId::from(3, NodeRole::Target)).into_ref();
@@ -682,17 +665,17 @@ mod tests {
let path2 = vec![e.clone(), f.clone()];
merge(path1, path2);
// D <-- A <-- B <-- C
// |
// F --> E --> -->
// D <-- A <-- B <-- C
// |
// F --> E --> -->
assert_eq!(e.borrow().clone().parent.unwrap().borrow().id.who, 4u32); // c
}
#[test]
fn merge_with_len_one() {
// D <-- A <-- B <-- C
// D <-- A <-- B <-- C
//
// F <-- E
// F <-- E
let d = Node::new(NodeId::from(1, NodeRole::Target)).into_ref();
let a = Node::new(NodeId::from(2, NodeRole::Target)).into_ref();
let b = Node::new(NodeId::from(3, NodeRole::Target)).into_ref();
@@ -707,9 +690,9 @@ mod tests {
let path2 = vec![f.clone()];
merge(path1, path2);
// D <-- A <-- B <-- C
// |
// F --> -->
// D <-- A <-- B <-- C
// |
// F --> -->
assert_eq!(f.borrow().clone().parent.unwrap().borrow().id.who, 4u32); // c
}
@@ -718,14 +701,8 @@ mod tests {
use super::*;
let assignments = vec![
StakedAssignment {
who: 1,
distribution: vec![(10, 25), (20, 75)],
},
StakedAssignment {
who: 2,
distribution: vec![(10, 50), (20, 50)],
},
StakedAssignment { who: 1, distribution: vec![(10, 25), (20, 75)] },
StakedAssignment { who: 2, distribution: vec![(10, 50), (20, 50)] },
];
let mut new_assignments = assignments.clone();
@@ -735,14 +712,8 @@ mod tests {
assert_eq!(
new_assignments,
vec![
StakedAssignment {
who: 1,
distribution: vec![(20, 100),],
},
StakedAssignment {
who: 2,
distribution: vec![(10, 75), (20, 25),],
},
StakedAssignment { who: 1, distribution: vec![(20, 100),] },
StakedAssignment { who: 2, distribution: vec![(10, 75), (20, 25),] },
],
);
}
@@ -750,26 +721,11 @@ mod tests {
#[test]
fn basic_reduce_all_cycles_works() {
let mut assignments = vec![
StakedAssignment {
who: 1,
distribution: vec![(10, 10)],
},
StakedAssignment {
who: 2,
distribution: vec![(10, 15), (20, 5)],
},
StakedAssignment {
who: 3,
distribution: vec![(20, 15), (40, 15)],
},
StakedAssignment {
who: 4,
distribution: vec![(20, 10), (30, 10), (40, 20)],
},
StakedAssignment {
who: 5,
distribution: vec![(20, 20), (30, 10), (40, 20)],
},
StakedAssignment { who: 1, distribution: vec![(10, 10)] },
StakedAssignment { who: 2, distribution: vec![(10, 15), (20, 5)] },
StakedAssignment { who: 3, distribution: vec![(20, 15), (40, 15)] },
StakedAssignment { who: 4, distribution: vec![(20, 10), (30, 10), (40, 20)] },
StakedAssignment { who: 5, distribution: vec![(20, 20), (30, 10), (40, 20)] },
];
assert_eq!(3, reduce_all(&mut assignments));
@@ -777,26 +733,11 @@ mod tests {
assert_eq!(
assignments,
vec![
StakedAssignment {
who: 1,
distribution: vec![(10, 10),]
},
StakedAssignment {
who: 2,
distribution: vec![(10, 15), (20, 5),],
},
StakedAssignment {
who: 3,
distribution: vec![(20, 30),],
},
StakedAssignment {
who: 4,
distribution: vec![(40, 40),]
},
StakedAssignment {
who: 5,
distribution: vec![(20, 15), (30, 20), (40, 15),],
},
StakedAssignment { who: 1, distribution: vec![(10, 10),] },
StakedAssignment { who: 2, distribution: vec![(10, 15), (20, 5),] },
StakedAssignment { who: 3, distribution: vec![(20, 30),] },
StakedAssignment { who: 4, distribution: vec![(40, 40),] },
StakedAssignment { who: 5, distribution: vec![(20, 15), (30, 20), (40, 15),] },
],
)
}
@@ -804,26 +745,11 @@ mod tests {
#[test]
fn basic_reduce_works() {
let mut assignments = vec![
StakedAssignment {
who: 1,
distribution: vec![(10, 10)],
},
StakedAssignment {
who: 2,
distribution: vec![(10, 15), (20, 5)],
},
StakedAssignment {
who: 3,
distribution: vec![(20, 15), (40, 15)],
},
StakedAssignment {
who: 4,
distribution: vec![(20, 10), (30, 10), (40, 20)],
},
StakedAssignment {
who: 5,
distribution: vec![(20, 20), (30, 10), (40, 20)],
},
StakedAssignment { who: 1, distribution: vec![(10, 10)] },
StakedAssignment { who: 2, distribution: vec![(10, 15), (20, 5)] },
StakedAssignment { who: 3, distribution: vec![(20, 15), (40, 15)] },
StakedAssignment { who: 4, distribution: vec![(20, 10), (30, 10), (40, 20)] },
StakedAssignment { who: 5, distribution: vec![(20, 20), (30, 10), (40, 20)] },
];
assert_eq!(3, reduce(&mut assignments));
@@ -831,26 +757,11 @@ mod tests {
assert_eq!(
assignments,
vec![
StakedAssignment {
who: 1,
distribution: vec![(10, 10),]
},
StakedAssignment {
who: 2,
distribution: vec![(10, 15), (20, 5),],
},
StakedAssignment {
who: 3,
distribution: vec![(20, 30),],
},
StakedAssignment {
who: 4,
distribution: vec![(40, 40),]
},
StakedAssignment {
who: 5,
distribution: vec![(20, 15), (30, 20), (40, 15),],
},
StakedAssignment { who: 1, distribution: vec![(10, 10),] },
StakedAssignment { who: 2, distribution: vec![(10, 15), (20, 5),] },
StakedAssignment { who: 3, distribution: vec![(20, 30),] },
StakedAssignment { who: 4, distribution: vec![(40, 40),] },
StakedAssignment { who: 5, distribution: vec![(20, 15), (30, 20), (40, 15),] },
],
)
}
@@ -858,35 +769,14 @@ mod tests {
#[test]
fn should_deal_with_self_vote() {
let mut assignments = vec![
StakedAssignment {
who: 1,
distribution: vec![(10, 10)],
},
StakedAssignment {
who: 2,
distribution: vec![(10, 15), (20, 5)],
},
StakedAssignment {
who: 3,
distribution: vec![(20, 15), (40, 15)],
},
StakedAssignment {
who: 4,
distribution: vec![(20, 10), (30, 10), (40, 20)],
},
StakedAssignment {
who: 5,
distribution: vec![(20, 20), (30, 10), (40, 20)],
},
StakedAssignment { who: 1, distribution: vec![(10, 10)] },
StakedAssignment { who: 2, distribution: vec![(10, 15), (20, 5)] },
StakedAssignment { who: 3, distribution: vec![(20, 15), (40, 15)] },
StakedAssignment { who: 4, distribution: vec![(20, 10), (30, 10), (40, 20)] },
StakedAssignment { who: 5, distribution: vec![(20, 20), (30, 10), (40, 20)] },
// self vote from 10 and 20 to itself.
StakedAssignment {
who: 10,
distribution: vec![(10, 100)],
},
StakedAssignment {
who: 20,
distribution: vec![(20, 200)],
},
StakedAssignment { who: 10, distribution: vec![(10, 100)] },
StakedAssignment { who: 20, distribution: vec![(20, 200)] },
];
assert_eq!(3, reduce(&mut assignments));
@@ -894,35 +784,14 @@ mod tests {
assert_eq!(
assignments,
vec![
StakedAssignment {
who: 1,
distribution: vec![(10, 10),]
},
StakedAssignment {
who: 2,
distribution: vec![(10, 15), (20, 5),],
},
StakedAssignment {
who: 3,
distribution: vec![(20, 30),],
},
StakedAssignment {
who: 4,
distribution: vec![(40, 40),]
},
StakedAssignment {
who: 5,
distribution: vec![(20, 15), (30, 20), (40, 15),],
},
StakedAssignment { who: 1, distribution: vec![(10, 10),] },
StakedAssignment { who: 2, distribution: vec![(10, 15), (20, 5),] },
StakedAssignment { who: 3, distribution: vec![(20, 30),] },
StakedAssignment { who: 4, distribution: vec![(40, 40),] },
StakedAssignment { who: 5, distribution: vec![(20, 15), (30, 20), (40, 15),] },
// should stay untouched.
StakedAssignment {
who: 10,
distribution: vec![(10, 100)]
},
StakedAssignment {
who: 20,
distribution: vec![(20, 200)]
},
StakedAssignment { who: 10, distribution: vec![(10, 100)] },
StakedAssignment { who: 20, distribution: vec![(20, 200)] },
],
)
}
@@ -930,55 +799,23 @@ mod tests {
#[test]
fn reduce_3_common_votes_same_weight() {
let mut assignments = vec![
StakedAssignment {
who: 4,
distribution: vec![
(
1000000,
100,
),
(
1000002,
100,
),
(
1000004,
100,
),
],
},
StakedAssignment {
who: 5,
distribution: vec![
(
1000000,
100,
),
(
1000002,
100,
),
(
1000004,
100,
),
],
},
];
StakedAssignment {
who: 4,
distribution: vec![(1000000, 100), (1000002, 100), (1000004, 100)],
},
StakedAssignment {
who: 5,
distribution: vec![(1000000, 100), (1000002, 100), (1000004, 100)],
},
];
reduce_4(&mut assignments);
assert_eq!(
assignments,
vec![
StakedAssignment {
who: 4,
distribution: vec![(1000000, 200,), (1000004, 100,),],
},
StakedAssignment {
who: 5,
distribution: vec![(1000002, 200,), (1000004, 100,),],
},
StakedAssignment { who: 4, distribution: vec![(1000000, 200,), (1000004, 100,),] },
StakedAssignment { who: 5, distribution: vec![(1000002, 200,), (1000004, 100,),] },
],
)
}
@@ -987,18 +824,9 @@ mod tests {
#[should_panic]
fn reduce_panics_on_duplicate_voter() {
let mut assignments = vec![
StakedAssignment {
who: 1,
distribution: vec![(10, 10), (20, 10)],
},
StakedAssignment {
who: 1,
distribution: vec![(10, 15), (20, 5)],
},
StakedAssignment {
who: 2,
distribution: vec![(10, 15), (20, 15)],
},
StakedAssignment { who: 1, distribution: vec![(10, 10), (20, 10)] },
StakedAssignment { who: 1, distribution: vec![(10, 15), (20, 5)] },
StakedAssignment { who: 2, distribution: vec![(10, 15), (20, 15)] },
];
reduce(&mut assignments);
@@ -1007,10 +835,7 @@ mod tests {
#[test]
fn should_deal_with_duplicates_target() {
let mut assignments = vec![
StakedAssignment {
who: 1,
distribution: vec![(10, 15), (20, 5)],
},
StakedAssignment { who: 1, distribution: vec![(10, 15), (20, 5)] },
StakedAssignment {
who: 2,
distribution: vec![
@@ -1029,10 +854,7 @@ mod tests {
assert_eq!(
assignments,
vec![
StakedAssignment {
who: 1,
distribution: vec![(10, 20),],
},
StakedAssignment { who: 1, distribution: vec![(10, 20),] },
StakedAssignment {
who: 2,
distribution: vec![
File diff suppressed because it is too large Load Diff