mirror of
https://github.com/pezkuwichain/pezkuwi-subxt.git
synced 2026-05-30 09:21:04 +00:00
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:
@@ -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),] }
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
@@ -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() {
|
||||
|
||||
@@ -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]);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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(¤t) && 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(¤t) && 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
Reference in New Issue
Block a user