From 9de9bb791fbcb10d7b6d0b14cc4c28d3f9553325 Mon Sep 17 00:00:00 2001 From: Xavier Lau Date: Thu, 13 Feb 2020 21:34:19 +0800 Subject: [PATCH 01/11] remove: unused files --- primitives/phragmen/benches/phragmen.rs | 212 ------------- primitives/phragmen/src/mock.rs | 405 ------------------------ primitives/phragmen/src/tests.rs | 350 -------------------- 3 files changed, 967 deletions(-) delete mode 100644 primitives/phragmen/benches/phragmen.rs delete mode 100644 primitives/phragmen/src/mock.rs delete mode 100644 primitives/phragmen/src/tests.rs diff --git a/primitives/phragmen/benches/phragmen.rs b/primitives/phragmen/benches/phragmen.rs deleted file mode 100644 index 780deaa8c..000000000 --- a/primitives/phragmen/benches/phragmen.rs +++ /dev/null @@ -1,212 +0,0 @@ -// Copyright 2019 Parity Technologies -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// 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` -//! - Everything is executed in native environment. -#![cfg(feature = "bench")] -#![feature(test)] - -extern crate test; -use test::Bencher; - -use rand::{self, Rng}; -extern crate sp_phragmen as phragmen; -use phragmen::{PhragmenStakedAssignment, Support, SupportMap}; - -use sp_runtime::traits::{Convert, SaturatedConversion}; -use std::collections::BTreeMap; - -const VALIDATORS: u64 = 1000; -const NOMINATORS: u64 = 10_000; -const EDGES: u64 = 2; -const TO_ELECT: usize = 100; -const STAKE: Balance = 1000; - -type Balance = u128; -type AccountId = u64; - -pub struct TestCurrencyToVote; -impl Convert for TestCurrencyToVote { - fn convert(x: Balance) -> u64 { - x.saturated_into() - } -} -impl Convert for TestCurrencyToVote { - fn convert(x: u128) -> Balance { - x.saturated_into() - } -} - -fn do_phragmen( - b: &mut Bencher, - num_vals: u64, - num_noms: u64, - count: usize, - votes_per: u64, - eq_iters: usize, - _eq_tolerance: u128, -) { - assert!(num_vals > votes_per); - let rr = |a, b| rand::thread_rng().gen_range(a as usize, b as usize) as Balance; - - // prefix to distinguish the validator and nominator account ranges. - let np = 10_000; - - let mut candidates = Vec::with_capacity(num_vals as usize); - let mut slashable_balance_of: BTreeMap = BTreeMap::new(); - - (1..=num_vals).for_each(|acc| { - candidates.push(acc); - slashable_balance_of.insert(acc, STAKE + rr(10, 50)); - }); - - let mut voters = Vec::with_capacity(num_noms as usize); - (np..=(np + num_noms)).for_each(|acc| { - let mut stashes_to_vote = candidates.clone(); - let votes = (0..votes_per) - .map(|_| stashes_to_vote.remove(rr(0, stashes_to_vote.len()) as usize)) - .collect::>(); - voters.push((acc, votes)); - slashable_balance_of.insert(acc, STAKE + rr(10, 50)); - }); - - let slashable_balance = |who: &AccountId| -> Balance { *slashable_balance_of.get(who).unwrap() }; - - b.iter(|| { - let r = phragmen::elect::( - count, - 1_usize, - candidates.clone(), - voters.clone(), - slashable_balance, - true, - ) - .unwrap(); - - // Do the benchmarking with equalize. - if eq_iters > 0 { - let elected_stashes = r.winners; - let assignments = r.assignments; - - let to_votes = |b: Balance| >::convert(b) as u128; - - // Initialize the support of each candidate. - let mut supports = >::new(); - elected_stashes - .iter() - .map(|(e, _)| (e, to_votes(slashable_balance(e)))) - .for_each(|(e, s)| { - let item = Support { - own: s, - total: s, - ..Default::default() - }; - supports.insert(e.clone(), item); - }); - - // build support struct. - for (n, assignment) in assignments.iter() { - for (c, per_thing) in assignment.iter() { - let nominator_stake = to_votes(slashable_balance(n)); - let other_stake = *per_thing * nominator_stake; - if let Some(support) = supports.get_mut(c) { - support.total = support.total.saturating_add(other_stake); - support.others.push((n.clone(), other_stake)); - } - } - } - - let mut staked_assignments: Vec<(AccountId, Vec>)> = - Vec::with_capacity(assignments.len()); - for (n, assignment) in assignments.iter() { - let mut staked_assignment: Vec> = - Vec::with_capacity(assignment.len()); - for (c, per_thing) in assignment.iter() { - let nominator_stake = to_votes(slashable_balance(n)); - let other_stake = *per_thing * nominator_stake; - staked_assignment.push((c.clone(), other_stake)); - } - staked_assignments.push((n.clone(), staked_assignment)); - } - - let tolerance = 0_u128; - let iterations = 2_usize; - phragmen::equalize::<_, _, TestCurrencyToVote, _>( - staked_assignments, - &mut supports, - tolerance, - iterations, - slashable_balance, - ); - } - }) -} - -macro_rules! phragmen_benches { - ($($name:ident: $tup:expr,)*) => { - $( - #[bench] - fn $name(b: &mut Bencher) { - let (v, n, t, e, eq_iter, eq_tol) = $tup; - println!("----------------------"); - println!( - "++ Benchmark: {} Validators // {} Nominators // {} Edges-per-nominator // {} \ - total edges // electing {} // Equalize: {} iterations -- {} tolerance", - v, n, e, e * n, t, eq_iter, eq_tol, - ); - do_phragmen(b, v, n, t, e, eq_iter, eq_tol); - } - )* - } -} - -phragmen_benches! { - bench_1_1: (VALIDATORS, NOMINATORS, TO_ELECT, EDGES, 0, 0), - bench_1_2: (VALIDATORS*2, NOMINATORS, TO_ELECT, EDGES, 0, 0), - bench_1_3: (VALIDATORS*4, NOMINATORS, TO_ELECT, EDGES, 0, 0), - bench_1_4: (VALIDATORS*8, NOMINATORS, TO_ELECT, EDGES, 0, 0), - bench_1_1_eq: (VALIDATORS, NOMINATORS, TO_ELECT, EDGES, 2, 0), - bench_1_2_eq: (VALIDATORS*2, NOMINATORS, TO_ELECT, EDGES, 2, 0), - bench_1_3_eq: (VALIDATORS*4, NOMINATORS, TO_ELECT, EDGES, 2, 0), - bench_1_4_eq: (VALIDATORS*8, NOMINATORS, TO_ELECT, EDGES, 2, 0), - - bench_0_1: (VALIDATORS, NOMINATORS, TO_ELECT, EDGES, 0, 0), - bench_0_2: (VALIDATORS, NOMINATORS, TO_ELECT * 4, EDGES, 0, 0), - bench_0_3: (VALIDATORS, NOMINATORS, TO_ELECT * 8, EDGES, 0, 0), - bench_0_4: (VALIDATORS, NOMINATORS, TO_ELECT * 16, EDGES , 0, 0), - bench_0_1_eq: (VALIDATORS, NOMINATORS, TO_ELECT, EDGES, 2, 0), - bench_0_2_eq: (VALIDATORS, NOMINATORS, TO_ELECT * 4, EDGES, 2, 0), - bench_0_3_eq: (VALIDATORS, NOMINATORS, TO_ELECT * 8, EDGES, 2, 0), - bench_0_4_eq: (VALIDATORS, NOMINATORS, TO_ELECT * 16, EDGES , 2, 0), - - bench_2_1: (VALIDATORS, NOMINATORS, TO_ELECT, EDGES, 0, 0), - bench_2_2: (VALIDATORS, NOMINATORS*2, TO_ELECT, EDGES, 0, 0), - bench_2_3: (VALIDATORS, NOMINATORS*4, TO_ELECT, EDGES, 0, 0), - bench_2_4: (VALIDATORS, NOMINATORS*8, TO_ELECT, EDGES, 0, 0), - bench_2_1_eq: (VALIDATORS, NOMINATORS, TO_ELECT, EDGES, 2, 0), - bench_2_2_eq: (VALIDATORS, NOMINATORS*2, TO_ELECT, EDGES, 2, 0), - bench_2_3_eq: (VALIDATORS, NOMINATORS*4, TO_ELECT, EDGES, 2, 0), - bench_2_4_eq: (VALIDATORS, NOMINATORS*8, TO_ELECT, EDGES, 2, 0), - - bench_3_1: (VALIDATORS, NOMINATORS, TO_ELECT, EDGES, 0, 0 ), - bench_3_2: (VALIDATORS, NOMINATORS, TO_ELECT, EDGES*2, 0, 0), - bench_3_3: (VALIDATORS, NOMINATORS, TO_ELECT, EDGES*4, 0, 0), - bench_3_4: (VALIDATORS, NOMINATORS, TO_ELECT, EDGES*8, 0, 0), - bench_3_1_eq: (VALIDATORS, NOMINATORS, TO_ELECT, EDGES, 2, 0), - bench_3_2_eq: (VALIDATORS, NOMINATORS, TO_ELECT, EDGES*2, 2, 0), - bench_3_3_eq: (VALIDATORS, NOMINATORS, TO_ELECT, EDGES*4, 2, 0), - bench_3_4_eq: (VALIDATORS, NOMINATORS, TO_ELECT, EDGES*8, 2, 0), -} diff --git a/primitives/phragmen/src/mock.rs b/primitives/phragmen/src/mock.rs deleted file mode 100644 index 4d3ea0495..000000000 --- a/primitives/phragmen/src/mock.rs +++ /dev/null @@ -1,405 +0,0 @@ -// Copyright 2019 Parity Technologies (UK) Ltd. -// This file is part of Substrate. - -// Substrate is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. - -// Substrate is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. - -// You should have received a copy of the GNU General Public License -// along with Substrate. If not, see . - -//! Mock file for phragmen. - -#![cfg(test)] - -use crate::{elect, PhragmenAssignment, PhragmenResult}; -use sp_runtime::{ - assert_eq_error_rate, - traits::{Convert, Member, SaturatedConversion}, - Perbill, -}; -use sp_std::collections::btree_map::BTreeMap; - -pub(crate) struct TestCurrencyToVote; -impl Convert for TestCurrencyToVote { - fn convert(x: Balance) -> u64 { - x.saturated_into() - } -} -impl Convert for TestCurrencyToVote { - fn convert(x: u128) -> Balance { - x - } -} - -#[derive(Default, Debug)] -pub(crate) struct _Candidate { - who: A, - score: f64, - approval_stake: f64, - elected: bool, -} - -#[derive(Default, Debug)] -pub(crate) struct _Voter { - who: A, - edges: Vec<_Edge>, - budget: f64, - load: f64, -} - -#[derive(Default, Debug)] -pub(crate) struct _Edge { - who: A, - load: f64, - candidate_index: usize, -} - -#[derive(Default, Debug, PartialEq)] -pub(crate) struct _Support { - pub own: f64, - pub total: f64, - pub others: Vec<_PhragmenAssignment>, -} - -pub(crate) type _PhragmenAssignment = (A, f64); -pub(crate) type _SupportMap = BTreeMap>; - -pub(crate) type Balance = u128; -pub(crate) type AccountId = u64; - -#[derive(Debug, Clone)] -pub(crate) struct _PhragmenResult { - pub winners: Vec<(A, Balance)>, - pub assignments: Vec<(A, Vec<_PhragmenAssignment>)>, -} - -pub(crate) fn auto_generate_self_voters(candidates: &[A]) -> Vec<(A, Vec)> { - candidates.iter().map(|c| (c.clone(), vec![c.clone()])).collect() -} - -pub(crate) fn elect_float( - candidate_count: usize, - minimum_candidate_count: usize, - initial_candidates: Vec, - initial_voters: Vec<(A, Vec)>, - stake_of: FS, -) -> Option<_PhragmenResult> -where - A: Default + Ord + Member + Copy, - for<'r> FS: Fn(&'r A) -> Balance, -{ - let mut elected_candidates: Vec<(A, Balance)>; - let mut assigned: Vec<(A, Vec<_PhragmenAssignment>)>; - let mut c_idx_cache = BTreeMap::::new(); - let num_voters = initial_candidates.len() + initial_voters.len(); - let mut voters: Vec<_Voter> = Vec::with_capacity(num_voters); - - let mut candidates = initial_candidates - .into_iter() - .enumerate() - .map(|(idx, who)| { - c_idx_cache.insert(who.clone(), idx); - _Candidate { - who, - ..Default::default() - } - }) - .collect::>>(); - - if candidates.len() < minimum_candidate_count { - return None; - } - - voters.extend(initial_voters.into_iter().map(|(who, votes)| { - let voter_stake = stake_of(&who) as f64; - let mut edges: Vec<_Edge> = Vec::with_capacity(votes.len()); - 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() - }); - } - } - _Voter { - who, - edges, - budget: voter_stake, - load: 0f64, - } - })); - - let to_elect = candidate_count.min(candidates.len()); - elected_candidates = Vec::with_capacity(candidate_count); - assigned = Vec::with_capacity(candidate_count); - - for _round in 0..to_elect { - for c in &mut candidates { - if !c.elected { - c.score = 1.0 / c.approval_stake; - } - } - for n in &voters { - for e in &n.edges { - let c = &mut candidates[e.candidate_index]; - if !c.elected && !(c.approval_stake == 0f64) { - c.score += n.budget * n.load / c.approval_stake; - } - } - } - - if let Some(winner) = candidates - .iter_mut() - .filter(|c| !c.elected) - .min_by(|x, y| x.score.partial_cmp(&y.score).unwrap_or(sp_std::cmp::Ordering::Equal)) - { - winner.elected = true; - for n in &mut voters { - for e in &mut n.edges { - if e.who == winner.who { - e.load = winner.score - n.load; - n.load = winner.score; - } - } - } - - elected_candidates.push((winner.who.clone(), winner.approval_stake as Balance)); - } else { - break; - } - } - - 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 c != n.who { - let ratio = e.load / n.load; - assignment.1.push((e.who.clone(), ratio)); - } - } - } - if assignment.1.len() > 0 { - assigned.push(assignment); - } - } - - Some(_PhragmenResult { - winners: elected_candidates, - assignments: assigned, - }) -} - -pub(crate) fn equalize_float( - mut assignments: Vec<(A, Vec<_PhragmenAssignment>)>, - supports: &mut _SupportMap, - tolerance: f64, - iterations: usize, - stake_of: FS, -) where - for<'r> FS: Fn(&'r A) -> Balance, - A: Ord + Clone + std::fmt::Debug, -{ - for _i in 0..iterations { - 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; - } - } - - if max_diff < tolerance { - break; - } - } -} - -pub(crate) fn do_equalize_float( - voter: &A, - budget_balance: Balance, - elected_edges: &mut Vec<_PhragmenAssignment>, - support_map: &mut _SupportMap, - tolerance: f64, -) -> f64 -where - A: Ord + Clone, -{ - let budget = budget_balance as f64; - if elected_edges.is_empty() { - return 0.0; - } - - 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 backing_backed_stake = elected_edges - .iter() - .filter(|e| e.1 > 0.0) - .filter_map(|e| support_map.get(&e.0)) - .map(|e| e.total) - .collect::>(); - - let mut difference; - if backing_backed_stake.len() > 0 { - let max_stake = backing_backed_stake - .iter() - .max_by(|x, y| x.partial_cmp(&y).unwrap_or(sp_std::cmp::Ordering::Equal)) - .expect("vector with positive length will have a max; qed"); - let min_stake = backed_stakes_iter - .min_by(|x, y| x.partial_cmp(&y).unwrap_or(sp_std::cmp::Ordering::Equal)) - .expect("iterator with positive length will have a min; qed"); - - difference = max_stake - min_stake; - difference = difference + budget - stake_used; - if difference < tolerance { - return difference; - } - } else { - difference = budget; - } - - // Undo updates to support - elected_edges.iter_mut().for_each(|e| { - if let Some(support) = support_map.get_mut(&e.0) { - support.total = support.total - e.1; - support.others.retain(|i_support| i_support.0 != *voter); - } - e.1 = 0.0; - }); - - elected_edges.sort_unstable_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; - elected_edges.iter_mut().enumerate().for_each(|(idx, e)| { - if let Some(support) = support_map.get_mut(&e.0) { - let stake = support.total; - let stake_mul = stake * (idx as f64); - let stake_sub = stake_mul - cumulative_stake; - if stake_sub > budget { - last_index = idx.checked_sub(1).unwrap_or(0); - return; - } - cumulative_stake = cumulative_stake + stake; - } - }); - - let last_stake = elected_edges[last_index].1; - let split_ways = last_index + 1; - let excess = budget + cumulative_stake - last_stake * (split_ways as f64); - elected_edges.iter_mut().take(split_ways).for_each(|e| { - if let Some(support) = support_map.get_mut(&e.0) { - e.1 = excess / (split_ways as f64) + last_stake - support.total; - support.total = support.total + e.1; - support.others.push((voter.clone(), e.1)); - } - }); - - difference -} - -pub(crate) fn create_stake_of(stakes: &[(AccountId, Balance)]) -> Box Balance> { - let mut storage = BTreeMap::::new(); - stakes.iter().for_each(|s| { - storage.insert(s.0, s.1); - }); - let stake_of = move |who: &AccountId| -> Balance { storage.get(who).unwrap().to_owned() }; - Box::new(stake_of) -} - -pub fn check_assignments(assignments: Vec<(AccountId, Vec>)>) { - for (_, a) in assignments { - let sum: u32 = a.iter().map(|(_, p)| p.deconstruct()).sum(); - assert_eq_error_rate!(sum, Perbill::accuracy(), 5); - } -} - -pub(crate) fn run_and_compare( - candidates: Vec, - voters: Vec<(AccountId, Vec)>, - stake_of: Box Balance>, - to_elect: usize, - min_to_elect: usize, -) { - // run fixed point code. - let PhragmenResult { winners, assignments } = - elect::<_, _, _, TestCurrencyToVote>(to_elect, min_to_elect, candidates.clone(), voters.clone(), &stake_of) - .unwrap(); - - // run float poc code. - let truth_value = elect_float(to_elect, min_to_elect, candidates, voters, &stake_of).unwrap(); - - assert_eq!(winners, truth_value.winners); - - for (nominator, assigned) in assignments.clone() { - if let Some(float_assignments) = truth_value.assignments.iter().find(|x| x.0 == nominator) { - for (candidate, per_thingy) in assigned { - if let Some(float_assignment) = float_assignments.1.iter().find(|x| x.0 == candidate) { - assert_eq_error_rate!( - Perbill::from_fraction(float_assignment.1).deconstruct(), - per_thingy.deconstruct(), - 1, - ); - } else { - panic!("candidate mismatch. This should never happen.") - } - } - } else { - panic!("nominator mismatch. This should never happen.") - } - } - - check_assignments(assignments); -} - -pub(crate) fn build_support_map(result: &mut _PhragmenResult, stake_of: FS) -> _SupportMap -where - for<'r> FS: Fn(&'r AccountId) -> Balance, -{ - let mut supports = <_SupportMap>::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); - }); - - for (n, assignment) in result.assignments.iter_mut() { - for (c, r) in assignment.iter_mut() { - let nominator_stake = stake_of(n) as f64; - let other_stake = nominator_stake * *r; - if let Some(support) = supports.get_mut(c) { - support.total = support.total + other_stake; - support.others.push((n.clone(), other_stake)); - } - *r = other_stake; - } - } - supports -} diff --git a/primitives/phragmen/src/tests.rs b/primitives/phragmen/src/tests.rs deleted file mode 100644 index 332761298..000000000 --- a/primitives/phragmen/src/tests.rs +++ /dev/null @@ -1,350 +0,0 @@ -// Copyright 2019 Parity Technologies (UK) Ltd. -// This file is part of Substrate. - -// Substrate is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. - -// Substrate is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. - -// You should have received a copy of the GNU General Public License -// along with Substrate. If not, see . - -//! Tests for phragmen. - -#![cfg(test)] - -use crate::mock::*; -use crate::{elect, PhragmenResult}; -use sp_runtime::Perbill; -use substrate_test_utils::assert_eq_uvec; - -#[test] -fn float_phragmen_poc_works() { - let candidates = vec![1, 2, 3]; - let voters = vec![(10, vec![1, 2]), (20, vec![1, 3]), (30, vec![2, 3])]; - let stake_of = create_stake_of(&[(10, 10), (20, 20), (30, 30), (1, 0), (2, 0), (3, 0)]); - let mut phragmen_result = elect_float(2, 2, candidates, voters, &stake_of).unwrap(); - let winners = phragmen_result.clone().winners; - let assignments = phragmen_result.clone().assignments; - - assert_eq_uvec!(winners, vec![(2, 40), (3, 50)]); - assert_eq_uvec!( - assignments, - vec![ - (10, vec![(2, 1.0)]), - (20, vec![(3, 1.0)]), - (30, vec![(2, 0.5), (3, 0.5)]), - ] - ); - - let mut support_map = build_support_map(&mut phragmen_result, &stake_of); - - assert_eq!( - support_map.get(&2).unwrap(), - &_Support { - own: 0.0, - total: 25.0, - others: vec![(10u64, 10.0), (30u64, 15.0)] - } - ); - assert_eq!( - support_map.get(&3).unwrap(), - &_Support { - own: 0.0, - total: 35.0, - others: vec![(20u64, 20.0), (30u64, 15.0)] - } - ); - - equalize_float(phragmen_result.assignments, &mut support_map, 0.0, 2, stake_of); - - assert_eq!( - support_map.get(&2).unwrap(), - &_Support { - own: 0.0, - total: 30.0, - others: vec![(10u64, 10.0), (30u64, 20.0)] - } - ); - assert_eq!( - support_map.get(&3).unwrap(), - &_Support { - own: 0.0, - total: 30.0, - others: vec![(20u64, 20.0), (30u64, 10.0)] - } - ); -} - -#[test] -fn phragmen_poc_works() { - let candidates = vec![1, 2, 3]; - let voters = vec![(10, vec![1, 2]), (20, vec![1, 3]), (30, vec![2, 3])]; - - let PhragmenResult { winners, assignments } = elect::<_, _, _, TestCurrencyToVote>( - 2, - 2, - candidates, - voters, - create_stake_of(&[(10, 10), (20, 20), (30, 30)]), - ) - .unwrap(); - - assert_eq_uvec!(winners, vec![(2, 40), (3, 50)]); - assert_eq_uvec!( - assignments, - vec![ - (10, vec![(2, Perbill::from_percent(100))]), - (20, vec![(3, Perbill::from_percent(100))]), - ( - 30, - vec![(2, Perbill::from_percent(100 / 2)), (3, Perbill::from_percent(100 / 2))] - ), - ] - ); -} - -#[test] -fn phragmen_poc_2_works() { - let candidates = vec![10, 20, 30]; - let voters = vec![(2, vec![10, 20, 30]), (4, vec![10, 20, 40])]; - let stake_of = create_stake_of(&[(10, 1000), (20, 1000), (30, 1000), (40, 1000), (2, 500), (4, 500)]); - - run_and_compare(candidates, voters, stake_of, 2, 2); -} - -#[test] -fn phragmen_poc_3_works() { - let candidates = vec![10, 20, 30]; - let voters = vec![(2, vec![10, 20, 30]), (4, vec![10, 20, 40])]; - let stake_of = create_stake_of(&[(10, 1000), (20, 1000), (30, 1000), (2, 50), (4, 1000)]); - - run_and_compare(candidates, voters, stake_of, 2, 2); -} - -#[test] -fn phragmen_accuracy_on_large_scale_only_validators() { - // because of this particular situation we had per_u128 and now rational128. In practice, a - // candidate can have the maximum amount of tokens, and also supported by the maximum. - let candidates = vec![1, 2, 3, 4, 5]; - let stake_of = create_stake_of(&[ - (1, (u64::max_value() - 1).into()), - (2, (u64::max_value() - 4).into()), - (3, (u64::max_value() - 5).into()), - (4, (u64::max_value() - 3).into()), - (5, (u64::max_value() - 2).into()), - ]); - - let PhragmenResult { winners, assignments } = elect::<_, _, _, TestCurrencyToVote>( - 2, - 2, - candidates.clone(), - auto_generate_self_voters(&candidates), - stake_of, - ) - .unwrap(); - - assert_eq_uvec!( - winners, - vec![(1, 18446744073709551614u128), (5, 18446744073709551613u128)] - ); - assert_eq!(assignments.len(), 2); - check_assignments(assignments); -} - -#[test] -fn phragmen_accuracy_on_large_scale_validators_and_nominators() { - let candidates = vec![1, 2, 3, 4, 5]; - let mut voters = vec![(13, vec![1, 3, 5]), (14, vec![2, 4])]; - voters.extend(auto_generate_self_voters(&candidates)); - let stake_of = create_stake_of(&[ - (1, (u64::max_value() - 1).into()), - (2, (u64::max_value() - 4).into()), - (3, (u64::max_value() - 5).into()), - (4, (u64::max_value() - 3).into()), - (5, (u64::max_value() - 2).into()), - (13, (u64::max_value() - 10).into()), - (14, u64::max_value().into()), - ]); - - let PhragmenResult { winners, assignments } = - elect::<_, _, _, TestCurrencyToVote>(2, 2, candidates, voters, stake_of).unwrap(); - - assert_eq_uvec!( - winners, - vec![(2, 36893488147419103226u128), (1, 36893488147419103219u128)] - ); - assert_eq!( - assignments, - vec![ - (13, vec![(1, Perbill::one())]), - (14, vec![(2, Perbill::one())]), - (1, vec![(1, Perbill::one())]), - (2, vec![(2, Perbill::one())]), - ] - ); - check_assignments(assignments); -} - -#[test] -fn phragmen_accuracy_on_small_scale_self_vote() { - let candidates = vec![40, 10, 20, 30]; - let voters = auto_generate_self_voters(&candidates); - let stake_of = create_stake_of(&[(40, 0), (10, 1), (20, 2), (30, 1)]); - - let PhragmenResult { - winners, - assignments: _, - } = elect::<_, _, _, TestCurrencyToVote>(3, 3, candidates, voters, stake_of).unwrap(); - - assert_eq_uvec!(winners, vec![(20, 2), (10, 1), (30, 1)]); -} - -#[test] -fn phragmen_accuracy_on_small_scale_no_self_vote() { - let candidates = vec![40, 10, 20, 30]; - let voters = vec![(1, vec![10]), (2, vec![20]), (3, vec![30]), (4, vec![40])]; - let stake_of = create_stake_of(&[ - (40, 1000), // don't care - (10, 1000), // don't care - (20, 1000), // don't care - (30, 1000), // don't care - (4, 0), - (1, 1), - (2, 2), - (3, 1), - ]); - - let PhragmenResult { - winners, - assignments: _, - } = elect::<_, _, _, TestCurrencyToVote>(3, 3, candidates, voters, stake_of).unwrap(); - - assert_eq_uvec!(winners, vec![(20, 2), (10, 1), (30, 1)]); -} - -#[test] -fn phragmen_large_scale_test() { - let candidates = vec![2, 4, 6, 8, 10, 12, 14, 16, 18, 20, 22, 24]; - let mut voters = vec![(50, vec![2, 4, 6, 8, 10, 12, 14, 16, 18, 20, 22, 24])]; - voters.extend(auto_generate_self_voters(&candidates)); - let stake_of = create_stake_of(&[ - (2, 1), - (4, 100), - (6, 1000000), - (8, 100000000001000), - (10, 100000000002000), - (12, 100000000003000), - (14, 400000000000000), - (16, 400000000001000), - (18, 18000000000000000), - (20, 20000000000000000), - (22, 500000000000100000), - (24, 500000000000200000), - (50, 990000000000000000), - ]); - - let PhragmenResult { winners, assignments } = - elect::<_, _, _, TestCurrencyToVote>(2, 2, candidates, voters, stake_of).unwrap(); - - assert_eq_uvec!( - winners, - vec![(24, 1490000000000200000u128), (22, 1490000000000100000u128)] - ); - check_assignments(assignments); -} - -#[test] -fn phragmen_large_scale_test_2() { - let nom_budget: u64 = 1_000_000_000_000_000_000; - let c_budget: u64 = 4_000_000; - - let candidates = vec![2, 4]; - let mut voters = vec![(50, vec![2, 4])]; - voters.extend(auto_generate_self_voters(&candidates)); - - let stake_of = create_stake_of(&[(2, c_budget.into()), (4, c_budget.into()), (50, nom_budget.into())]); - - let PhragmenResult { winners, assignments } = - elect::<_, _, _, TestCurrencyToVote>(2, 2, candidates, voters, stake_of).unwrap(); - - assert_eq_uvec!( - winners, - vec![(2, 1000000000004000000u128), (4, 1000000000004000000u128)] - ); - assert_eq!( - assignments, - vec![ - ( - 50, - vec![(2, Perbill::from_parts(500000001)), (4, Perbill::from_parts(499999999))] - ), - (2, vec![(2, Perbill::one())]), - (4, vec![(4, Perbill::one())]), - ], - ); - check_assignments(assignments); -} - -#[test] -fn phragmen_linear_equalize() { - let candidates = vec![11, 21, 31, 41, 51, 61, 71]; - let voters = vec![ - (2, vec![11]), - (4, vec![11, 21]), - (6, vec![21, 31]), - (8, vec![31, 41]), - (110, vec![41, 51]), - (120, vec![51, 61]), - (130, vec![61, 71]), - ]; - let stake_of = create_stake_of(&[ - (11, 1000), - (21, 1000), - (31, 1000), - (41, 1000), - (51, 1000), - (61, 1000), - (71, 1000), - (2, 2000), - (4, 1000), - (6, 1000), - (8, 1000), - (110, 1000), - (120, 1000), - (130, 1000), - ]); - - run_and_compare(candidates, voters, stake_of, 2, 2); -} - -#[test] -fn elect_has_no_entry_barrier() { - let candidates = vec![10, 20, 30]; - let voters = vec![(1, vec![10]), (2, vec![20])]; - let stake_of = create_stake_of(&[(1, 10), (2, 10)]); - - let PhragmenResult { - winners, - assignments: _, - } = elect::<_, _, _, TestCurrencyToVote>(3, 3, candidates, voters, stake_of).unwrap(); - - // 30 is elected with stake 0. The caller is responsible for stripping this. - assert_eq_uvec!(winners, vec![(10, 10), (20, 10), (30, 0),]); -} - -#[test] -fn minimum_to_elect_is_respected() { - let candidates = vec![10, 20, 30]; - let voters = vec![(1, vec![10]), (2, vec![20])]; - let stake_of = create_stake_of(&[(1, 10), (2, 10)]); - - let maybe_result = elect::<_, _, _, TestCurrencyToVote>(10, 10, candidates, voters, stake_of); - - assert!(maybe_result.is_none()); -} From 0612cb1cda27e1ad9ca9376472c3078c987bbd80 Mon Sep 17 00:00:00 2001 From: Xavier Lau Date: Thu, 13 Feb 2020 21:35:11 +0800 Subject: [PATCH 02/11] fix: #238, #262 --- Cargo.lock | 2 + bin/node/primitives/src/lib.rs | 5 +- bin/node/runtime/src/impls.rs | 26 +++---- bin/node/runtime/src/lib.rs | 6 +- frame/staking/src/inflation.rs | 6 +- frame/staking/src/lib.rs | 20 +++--- frame/support/Cargo.toml | 2 + frame/support/src/lib.rs | 123 ++++++++++++++++++++++++++++++++- primitives/phragmen/Cargo.toml | 4 ++ primitives/phragmen/src/lib.rs | 120 +++++++++++++++----------------- 10 files changed, 221 insertions(+), 93 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 74ceb5316..648fa3858 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -898,6 +898,7 @@ dependencies = [ name = "darwinia-phragmen" version = "0.4.0" dependencies = [ + "darwinia-support", "rand 0.7.2", "serde", "sp-io", @@ -953,6 +954,7 @@ name = "darwinia-support" version = "0.2.0" dependencies = [ "frame-support", + "num-traits", "parity-scale-codec", "sp-runtime", "sp-std", diff --git a/bin/node/primitives/src/lib.rs b/bin/node/primitives/src/lib.rs index ecf0ebe12..2456cd876 100644 --- a/bin/node/primitives/src/lib.rs +++ b/bin/node/primitives/src/lib.rs @@ -42,7 +42,10 @@ pub type AccountIndex = u32; pub type Balance = u128; /// Power of an account. -pub type Power = u128; +pub type Power = u32; + +/// Votes of an account. +pub type Votes = u32; /// Type used for expressing timestamp. pub type Moment = u64; diff --git a/bin/node/runtime/src/impls.rs b/bin/node/runtime/src/impls.rs index 85f7436d1..f3769c0fd 100644 --- a/bin/node/runtime/src/impls.rs +++ b/bin/node/runtime/src/impls.rs @@ -25,8 +25,8 @@ use sp_runtime::{ {Fixed64, Perbill}, }; -use crate::{constants::supply::TOTAL_POWER, Authorship, Balances, MaximumBlockWeight, NegativeImbalance, System}; -use node_primitives::{Balance, Power}; +use crate::{Authorship, Balances, MaximumBlockWeight, NegativeImbalance, System}; +use node_primitives::{Balance, Power, Votes}; pub struct Author; impl OnUnbalanced for Author { @@ -37,25 +37,25 @@ impl OnUnbalanced for Author { /// Struct that handles the conversion of Balance -> `u64`. This is used for staking's election /// calculation. -pub struct PowerToVoteHandler; +pub struct PowerToVotesHandler; -impl PowerToVoteHandler { +impl PowerToVotesHandler { fn factor() -> Power { - (TOTAL_POWER / u64::max_value() as Power).max(1) + 1 } } -impl Convert for PowerToVoteHandler { - fn convert(x: Power) -> u64 { - (x / Self::factor()) as u64 +impl Convert for PowerToVotesHandler { + fn convert(x: Power) -> Votes { + (x / Self::factor()) as Power } } -impl Convert for PowerToVoteHandler { - fn convert(x: u128) -> Power { - x * Self::factor() - } -} +//impl Convert for PowerToVoteHandler { +// fn convert(x: Vote) -> Power { +// x as Vote * Self::factor() +// } +//} /// Convert from weight to balance via a simple coefficient multiplication /// The associated type C encapsulates a constant in units of balance per weight diff --git a/bin/node/runtime/src/lib.rs b/bin/node/runtime/src/lib.rs index e4c971e7f..516b3a193 100644 --- a/bin/node/runtime/src/lib.rs +++ b/bin/node/runtime/src/lib.rs @@ -22,7 +22,7 @@ /// Implementations of some helper traits passed into runtime modules as associated types. pub mod impls; -use impls::{Author, LinearWeightToFee, PowerToVoteHandler, TargetedFeeAdjustment}; +use impls::{Author, LinearWeightToFee, PowerToVotesHandler, TargetedFeeAdjustment}; /// Constant values used within the runtime. pub mod constants; use constants::{currency::*, supply::*, time::*}; @@ -242,7 +242,7 @@ impl pallet_session::Trait for Runtime { } impl pallet_session::historical::Trait for Runtime { - type FullIdentification = Exposure; + type FullIdentification = Exposure; type FullIdentificationOf = ExposureOf; } @@ -459,7 +459,7 @@ parameter_types! { impl pallet_staking::Trait for Runtime { type Time = Timestamp; - type PowerToVote = PowerToVoteHandler; + type PowerToVotes = PowerToVotesHandler; type Event = Event; type SessionsPerEra = SessionsPerEra; type BondingDurationInEra = BondingDurationInEra; diff --git a/frame/staking/src/inflation.rs b/frame/staking/src/inflation.rs index 73cac74b4..35c0da317 100644 --- a/frame/staking/src/inflation.rs +++ b/frame/staking/src/inflation.rs @@ -17,8 +17,10 @@ where T: Trait, S: TryInto, { - Perquintill::from_rational_approximation(active.saturated_into::(), pool.saturated_into::().max(1)) - * (T::TotalPower::get() / 2) + Perbill::from_rational_approximation( + active.saturated_into::(), + pool.saturated_into::().max(1), + ) * (T::TotalPower::get() as Balance / 2) as _ } // 1 - (99 / 100) ^ sqrt(year) diff --git a/frame/staking/src/lib.rs b/frame/staking/src/lib.rs index b4dab64bd..2acac03c8 100644 --- a/frame/staking/src/lib.rs +++ b/frame/staking/src/lib.rs @@ -262,7 +262,9 @@ mod types { /// Type used for expressing timestamp. pub type Moment = Timestamp; /// Power of an account. - pub type Power = u128; + pub type Power = u32; + /// Votes of an account. + pub type Votes = u32; pub type RingBalance = as Currency>>::Balance; pub type RingPositiveImbalance = as Currency>>::PositiveImbalance; @@ -307,7 +309,7 @@ use frame_support::{ }; use frame_system::{self as system, ensure_root, ensure_signed}; use pallet_session::{historical::OnSessionEnding, SelectInitialValidators}; -use sp_phragmen::{ExtendedBalance as Votes, PhragmenStakedAssignment}; +use sp_phragmen::PhragmenStakedAssignment; use sp_runtime::{ traits::{ Bounded, CheckedSub, Convert, EnsureOrigin, One, SaturatedConversion, Saturating, SimpleArithmetic, @@ -650,7 +652,7 @@ pub trait Trait: frame_system::Trait { /// TODO: #1377 /// The backward convert should be removed as the new Phragmen API returns ratio. /// The post-processing needs it but will be moved to off-chain. TODO: #2908 - type PowerToVote: Convert + Convert; + type PowerToVotes: Convert; /// The overarching event type. type Event: From> + Into<::Event>; @@ -1873,7 +1875,7 @@ impl Module { all_nominators.extend(nominator_votes); - let maybe_phragmen_result = sp_phragmen::elect::<_, _, _, T::PowerToVote>( + let maybe_phragmen_result = sp_phragmen::elect::<_, _, _, T::PowerToVotes>( Self::validator_count() as usize, Self::minimum_validator_count().max(1) as usize, all_validators, @@ -1888,9 +1890,9 @@ impl Module { .map(|(s, _)| s.clone()) .collect::>(); let assignments = phragmen_result.assignments; - let to_votes = |p: Power| >::convert(p) as Votes; - let to_power = |v: Votes| >::convert(v); - let mut supports = sp_phragmen::build_support_map::<_, _, _, T::PowerToVote>( + let to_votes = |p: Power| >::convert(p); + let to_power = |v: Votes| >::convert(v); + let mut supports = sp_phragmen::build_support_map::<_, _, _, T::PowerToVotes>( &elected_stashes, &assignments, Self::slashable_power_of, @@ -1917,9 +1919,9 @@ impl Module { staked_assignments.push((n.clone(), staked_assignment)); } - let tolerance = 0_u128; + let tolerance: Votes = 0; let iterations = 2_usize; - sp_phragmen::equalize::<_, _, T::PowerToVote, _>( + sp_phragmen::equalize::<_, _, _, T::PowerToVotes>( staked_assignments, &mut supports, tolerance, diff --git a/frame/support/Cargo.toml b/frame/support/Cargo.toml index 1f9395b62..11d7a44e2 100644 --- a/frame/support/Cargo.toml +++ b/frame/support/Cargo.toml @@ -7,6 +7,7 @@ edition = "2018" [dependencies] # crates.io codec = { package = "parity-scale-codec", version = "1.0.0", default-features = false, features = ["derive"] } +num-traits = { version = "0.2.8", default-features = false } # github.com frame-support = { git = "https://github.com/paritytech/substrate.git", rev = "c2fccb36ffacd118fc3502aa93453580a07dc402", default-features = false } @@ -17,6 +18,7 @@ sp-std = { package = "sp-std", git = "https://github.com/paritytech/substrate.gi default = ["std"] std = [ "codec/std", + "num-traits/std", "frame-support/std", "sp-runtime/std", diff --git a/frame/support/src/lib.rs b/frame/support/src/lib.rs index 41f2c77a0..65d70a181 100644 --- a/frame/support/src/lib.rs +++ b/frame/support/src/lib.rs @@ -8,8 +8,10 @@ pub use traits::*; mod structs { use codec::{Decode, Encode}; + use num_traits::Zero; + use sp_runtime::{traits::SimpleArithmetic, RuntimeDebug}; - use sp_std::vec::Vec; + use sp_std::{cmp::Ordering, vec::Vec}; use crate::{LockIdentifier, WithdrawReasons}; @@ -91,6 +93,125 @@ mod structs { self.unbondings.retain(|unbonding| unbonding.valid_at(at)); } } + + /// A wrapper for any rational number with a u32 bit numerator and denominator. + #[derive(Clone, Copy, Default, Eq, RuntimeDebug)] + pub struct Rational32(u32, u32); + + impl Rational32 { + /// Nothing. + pub fn zero() -> Self { + Self(0, 1) + } + + /// If it is zero or not + pub fn is_zero(&self) -> bool { + self.0.is_zero() + } + + /// Build from a raw `n/d`. + pub fn from(n: u32, d: u32) -> Self { + Self(n, d.max(1)) + } + + /// Build from a raw `n/d`. This could lead to / 0 if not properly handled. + pub fn from_unchecked(n: u32, d: u32) -> Self { + Self(n, d) + } + + /// Return the numerator. + pub fn n(&self) -> u32 { + self.0 + } + + /// Return the denominator. + pub fn d(&self) -> u32 { + self.1 + } + + /// A saturating add that assumes `self` and `other` have the same denominator. + pub fn lazy_add(self, other: Self) -> Self { + if other.is_zero() { + self + } else { + Self(self.0 + other.0, self.1) + } + } + + /// A saturating subtraction that assumes `self` and `other` have the same denominator. + pub fn lazy_sub(self, other: Self) -> Self { + if other.is_zero() { + self + } else { + Self(self.0 - other.0, self.1) + } + } + + /// Safely and accurately compute `a * b / c`. The approach is: + /// - Simply try `a * b / c`. + /// - Else, convert them both into big numbers and re-try. + /// + /// Invariant: c must be greater than or equal to 1. + pub fn multiply_by_rational(a: u32, b: u32, mut c: u32) -> u32 { + if a.is_zero() || b.is_zero() { + return 0; + } + c = c.max(1); + + // a and b are interchangeable by definition in this function. It always helps to assume the + // bigger of which is being multiplied by a `0 < b/c < 1`. Hence, a should be the bigger and + // b the smaller one. + let (mut a, mut b) = if a > b { (a, b) } else { (b, a) }; + + // Attempt to perform the division first + if a % c == 0 { + a /= c; + c = 1; + } else if b % c == 0 { + b /= c; + c = 1; + } + + ((a as u64 * b as u64) / c as u64) as _ + } + } + + impl PartialOrd for Rational32 { + fn partial_cmp(&self, other: &Self) -> Option { + Some(self.cmp(other)) + } + } + + impl Ord for Rational32 { + fn cmp(&self, other: &Self) -> Ordering { + // handle some edge cases. + if self.1 == other.1 { + self.0.cmp(&other.0) + } else if self.1.is_zero() { + Ordering::Greater + } else if other.1.is_zero() { + Ordering::Less + } else { + // Don't even compute gcd. + let self_n = self.0 as u64 * other.1 as u64; + let other_n = other.0 as u64 * self.1 as u64; + self_n.cmp(&other_n) + } + } + } + + impl PartialEq for Rational32 { + fn eq(&self, other: &Self) -> bool { + // handle some edge cases. + if self.1 == other.1 { + self.0.eq(&other.0) + } else { + let self_n = self.0 as u64 * other.1 as u64; + let other_n = other.0 as u64 * self.1 as u64; + self_n.eq(&other_n) + } + } + } } mod traits { diff --git a/primitives/phragmen/Cargo.toml b/primitives/phragmen/Cargo.toml index 4978328c2..d0aa81fe2 100644 --- a/primitives/phragmen/Cargo.toml +++ b/primitives/phragmen/Cargo.toml @@ -9,6 +9,8 @@ serde = { version = "1.0.101", optional = true, features = ["derive"] } sp-std = { version = "2.0.0", default-features = false, git = "https://github.com/paritytech/substrate.git", rev = "c2fccb36ffacd118fc3502aa93453580a07dc402" } sp-runtime = { version = "2.0.0", default-features = false, git = "https://github.com/paritytech/substrate.git", rev = "c2fccb36ffacd118fc3502aa93453580a07dc402" } +darwinia-support = { default-features = false, path = "../../frame/support" } + [dev-dependencies] substrate-test-utils = { version = "2.0.0", git = "https://github.com/paritytech/substrate.git", rev = "c2fccb36ffacd118fc3502aa93453580a07dc402" } sp-io ={ version = "2.0.0", git = "https://github.com/paritytech/substrate.git", rev = "c2fccb36ffacd118fc3502aa93453580a07dc402" } @@ -20,4 +22,6 @@ std = [ "serde", "sp-std/std", "sp-runtime/std", + + "darwinia-support/std", ] diff --git a/primitives/phragmen/src/lib.rs b/primitives/phragmen/src/lib.rs index d7fd5961d..7969caa89 100644 --- a/primitives/phragmen/src/lib.rs +++ b/primitives/phragmen/src/lib.rs @@ -33,28 +33,23 @@ #![cfg_attr(not(feature = "std"), no_std)] -use sp_runtime::traits::{Bounded, Convert, Member, Saturating, SimpleArithmetic, Zero}; -use sp_runtime::RuntimeDebug; -use sp_runtime::{helpers_128bit::multiply_by_rational, Perbill, Rational128}; +use sp_runtime::{ + traits::{Convert, Member, Saturating, SimpleArithmetic, Zero}, + Perbill, RuntimeDebug, +}; use sp_std::{collections::btree_map::BTreeMap, prelude::*}; -#[cfg(test)] -mod mock; -#[cfg(test)] -mod tests; +use darwinia_support::Rational32; -/// A type in which performing operations on balances and stakes of candidates and voters are safe. +/// A type in which performing operations on power and stakes of candidates and voters are safe. /// -/// This module's functions expect a `Convert` type to convert all balances to u64. Hence, u128 is -/// a safe type for arithmetic operations over them. -/// -/// Balance types converted to `ExtendedBalance` are referred to as `Votes`. -pub type ExtendedBalance = u128; +/// This module's functions expect a `Convert` type to convert `Power` to `Vote`. +pub type Votes = u32; /// The denominator used for loads. Since votes are collected as u64, the smallest ratio that we /// might collect is `1/approval_stake` where approval stake is the sum of votes. Hence, some number /// bigger than u64::max_value() is needed. For maximum accuracy we simply use u128; -const DEN: u128 = u128::max_value(); +const DEN: Votes = Votes::max_value(); /// A candidate entity for phragmen election. #[derive(Clone, Default, RuntimeDebug)] @@ -62,9 +57,9 @@ pub struct Candidate { /// Identifier. pub who: AccountId, /// Intermediary value used to sort candidates. - pub score: Rational128, + pub score: Rational32, /// Sum of the stake of this candidate based on received votes. - approval_stake: ExtendedBalance, + approval_stake: Votes, /// Flag for being elected. elected: bool, } @@ -77,9 +72,9 @@ pub struct Voter { /// List of candidates proposed by this voter. edges: Vec>, /// The stake of this voter. - budget: ExtendedBalance, + budget: Votes, /// Incremented each time a candidate that this voter voted for has been elected. - load: Rational128, + load: Rational32, } /// A candidate being backed by a voter. @@ -88,7 +83,7 @@ pub struct Edge { /// Identifier. who: AccountId, /// Load of this vote. - load: Rational128, + load: Rational32, /// Index of the candidate stored in the 'candidates' vector. candidate_index: usize, } @@ -96,15 +91,15 @@ pub struct Edge { /// Means a particular `AccountId` was backed by `Perbill`th of a nominator's stake. pub type PhragmenAssignment = (AccountId, Perbill); -/// Means a particular `AccountId` was backed by `ExtendedBalance` of a nominator's stake. -pub type PhragmenStakedAssignment = (AccountId, ExtendedBalance); +/// Means a particular `AccountId` was backed by `Votes` of a nominator's stake. +pub type PhragmenStakedAssignment = (AccountId, Votes); /// Final result of the phragmen election. #[derive(RuntimeDebug)] pub struct PhragmenResult { /// Just winners zipped with their approval stake. Note that the approval stake is merely the /// sub of their received stake and could be used for very basic sorting and approval voting. - pub winners: Vec<(AccountId, ExtendedBalance)>, + pub winners: Vec<(AccountId, Votes)>, /// Individual assignments. for each tuple, the first elements is a voter and the second /// is the list of candidates that it supports. pub assignments: Vec<(AccountId, Vec>)>, @@ -121,9 +116,9 @@ pub struct PhragmenResult { #[cfg_attr(feature = "std", derive(serde::Serialize, serde::Deserialize))] pub struct Support { /// The amount of support as the effect of self-vote. - pub own: ExtendedBalance, + pub own: Votes, /// Total support. - pub total: ExtendedBalance, + pub total: Votes, /// Support from voters. pub others: Vec>, } @@ -147,7 +142,7 @@ pub type SupportMap = BTreeMap>; /// responsibility of the caller to make sure only those candidates who have a sensible economic /// value are passed in. From the perspective of this function, a candidate can easily be among the /// winner with no backing stake. -pub fn elect( +pub fn elect( candidate_count: usize, minimum_candidate_count: usize, initial_candidates: Vec, @@ -156,14 +151,14 @@ pub fn elect( ) -> Option> where AccountId: Default + Ord + Member, - Balance: Default + Copy + SimpleArithmetic, - for<'r> FS: Fn(&'r AccountId) -> Balance, - C: Convert + Convert, + Power: Default + Copy + SimpleArithmetic, + for<'r> FS: Fn(&'r AccountId) -> Power, + C: Convert, { - let to_votes = |b: Balance| >::convert(b) as ExtendedBalance; + let to_votes = |b: Power| >::convert(b); // return structures - let mut elected_candidates: Vec<(AccountId, ExtendedBalance)>; + let mut elected_candidates: Vec<(AccountId, Votes)>; let mut assigned: Vec<(AccountId, Vec>)>; // used to cache and access candidates index. @@ -212,7 +207,7 @@ where who, edges, budget: to_votes(voter_stake), - load: Rational128::zero(), + load: Rational32::zero(), } })); @@ -230,9 +225,9 @@ where // 1 / approval_stake == (DEN / approval_stake) / DEN. If approval_stake is zero, // then the ratio should be as large as possible, essentially `infinity`. if c.approval_stake.is_zero() { - c.score = Rational128::from_unchecked(DEN, 0); + c.score = Rational32::from_unchecked(DEN, 0); } else { - c.score = Rational128::from(DEN / c.approval_stake, DEN); + c.score = Rational32::from(DEN / c.approval_stake, DEN); } } } @@ -242,11 +237,10 @@ where for e in &n.edges { let c = &mut candidates[e.candidate_index]; if !c.elected && !c.approval_stake.is_zero() { - let temp_n = - multiply_by_rational(n.load.n(), n.budget, c.approval_stake).unwrap_or(Bounded::max_value()); + let temp_n = Rational32::multiply_by_rational(n.load.n(), n.budget, c.approval_stake); let temp_d = n.load.d(); - let temp = Rational128::from(temp_n, temp_d); - c.score = c.score.lazy_saturating_add(temp); + let temp = Rational32::from(temp_n, temp_d); + c.score = c.score.lazy_add(temp); } } } @@ -258,7 +252,7 @@ where for n in &mut voters { for e in &mut n.edges { if e.who == winner.who { - e.load = winner.score.lazy_saturating_sub(n.load); + e.load = winner.score.lazy_sub(n.load); n.load = winner.score; } } @@ -282,8 +276,8 @@ where } else { if e.load.d() == n.load.d() { // return e.load / n.load. - let desired_scale: u128 = Perbill::accuracy().into(); - multiply_by_rational(desired_scale, e.load.n(), n.load.n()).unwrap_or(Bounded::max_value()) + let desired_scale: Votes = Perbill::accuracy().into(); + Rational32::multiply_by_rational(desired_scale, e.load.n(), n.load.n()) } else { // defensive only. Both edge and nominator loads are built from // scores, hence MUST have the same denominator. @@ -292,7 +286,7 @@ where } }; // safer to .min() inside as well to argue as u32 is safe. - let per_thing = Perbill::from_parts(per_bill_parts.min(Perbill::accuracy().into()) as u32); + let per_thing = Perbill::from_parts(per_bill_parts.min(Perbill::accuracy().into())); assignment.1.push((e.who.clone(), per_thing)); } } @@ -334,18 +328,18 @@ where } /// Build the support map from the given phragmen result. -pub fn build_support_map( +pub fn build_support_map( elected_stashes: &Vec, assignments: &Vec<(AccountId, Vec>)>, stake_of: FS, ) -> SupportMap where AccountId: Default + Ord + Member, - Balance: Default + Copy + SimpleArithmetic, - C: Convert + Convert, - for<'r> FS: Fn(&'r AccountId) -> Balance, + Power: Default + Copy + SimpleArithmetic, + for<'r> FS: Fn(&'r AccountId) -> Power, + C: Convert, { - let to_votes = |b: Balance| >::convert(b) as ExtendedBalance; + let to_votes = |b: Power| >::convert(b); // Initialize the support of each candidate. let mut supports = >::new(); elected_stashes.iter().for_each(|e| { @@ -391,16 +385,16 @@ where /// * `tolerance`: maximum difference that can occur before an early quite happens. /// * `iterations`: maximum number of iterations that will be processed. /// * `stake_of`: something that can return the stake stake of a particular candidate or voter. -pub fn equalize( +pub fn equalize( mut assignments: Vec<(AccountId, Vec>)>, supports: &mut SupportMap, - tolerance: ExtendedBalance, + tolerance: Votes, iterations: usize, stake_of: FS, ) where - C: Convert + Convert, - for<'r> FS: Fn(&'r AccountId) -> Balance, AccountId: Ord + Clone, + for<'r> FS: Fn(&'r AccountId) -> Power, + C: Convert, { // prepare the data for equalise for _i in 0..iterations { @@ -423,18 +417,18 @@ pub fn equalize( /// actually perform equalize. same interface is `equalize`. Just called in loops with a check for /// maximum difference. -fn do_equalize( +fn do_equalize( voter: &AccountId, - budget_balance: Balance, + budget_balance: Power, elected_edges: &mut Vec>, support_map: &mut SupportMap, - tolerance: ExtendedBalance, -) -> ExtendedBalance + tolerance: Votes, +) -> Votes where - C: Convert + Convert, AccountId: Ord + Clone, + C: Convert, { - let to_votes = |b: Balance| >::convert(b) as ExtendedBalance; + let to_votes = |b: Power| >::convert(b); let budget = to_votes(budget_balance); // Nothing to do. This voter had nothing useful. @@ -443,9 +437,7 @@ where return 0; } - let stake_used = elected_edges - .iter() - .fold(0 as ExtendedBalance, |s, e| s.saturating_add(e.1)); + let stake_used = elected_edges.iter().fold(0 as Votes, |s, e| s.saturating_add(e.1)); let backed_stakes_iter = elected_edges .iter() @@ -457,7 +449,7 @@ where .filter(|e| e.1 > 0) .filter_map(|e| support_map.get(&e.0)) .map(|e| e.total) - .collect::>(); + .collect::>(); let mut difference; if backing_backed_stake.len() > 0 { @@ -495,13 +487,13 @@ where } }); - let mut cumulative_stake: ExtendedBalance = 0; + let mut cumulative_stake: Votes = 0; let mut last_index = elected_edges.len() - 1; let mut idx = 0usize; for e in &mut elected_edges[..] { if let Some(support) = support_map.get_mut(&e.0) { let stake = support.total; - let stake_mul = stake.saturating_mul(idx as ExtendedBalance); + let stake_mul = stake.saturating_mul(idx as Votes); let stake_sub = stake_mul.saturating_sub(cumulative_stake); if stake_sub > budget { last_index = idx.checked_sub(1).unwrap_or(0); @@ -516,10 +508,10 @@ where let split_ways = last_index + 1; let excess = budget .saturating_add(cumulative_stake) - .saturating_sub(last_stake.saturating_mul(split_ways as ExtendedBalance)); + .saturating_sub(last_stake.saturating_mul(split_ways as Votes)); elected_edges.iter_mut().take(split_ways).for_each(|e| { if let Some(support) = support_map.get_mut(&e.0) { - e.1 = (excess / split_ways as ExtendedBalance) + e.1 = (excess / split_ways as Votes) .saturating_add(last_stake) .saturating_sub(support.total); support.total = support.total.saturating_add(e.1); From 8c225ea41916377b5d2943dfdb312add67563199 Mon Sep 17 00:00:00 2001 From: Xavier Lau Date: Thu, 13 Feb 2020 22:52:21 +0800 Subject: [PATCH 03/11] update: currency to power --- frame/staking/src/inflation.rs | 18 +----------------- frame/staking/src/lib.rs | 21 ++++++++++++++++----- 2 files changed, 17 insertions(+), 22 deletions(-) diff --git a/frame/staking/src/inflation.rs b/frame/staking/src/inflation.rs index 35c0da317..c865c103d 100644 --- a/frame/staking/src/inflation.rs +++ b/frame/staking/src/inflation.rs @@ -1,4 +1,3 @@ -use frame_support::traits::Get; use sp_core::U256; use sp_runtime::{ traits::{IntegerSquareRoot, SaturatedConversion}, @@ -6,22 +5,7 @@ use sp_runtime::{ }; use sp_std::convert::TryInto; -use crate::{KtonBalance, Moment, MomentOf, Power, RingBalance, Trait}; - -type Balance = u128; - -// power is a mixture of ring and kton -// power = ring_ratio * POWER_COUNT / 2 + kton_ratio * POWER_COUNT / 2 -pub fn compute_balance_power(active: S, pool: S) -> Power -where - T: Trait, - S: TryInto, -{ - Perbill::from_rational_approximation( - active.saturated_into::(), - pool.saturated_into::().max(1), - ) * (T::TotalPower::get() as Balance / 2) as _ -} +use crate::{Balance, KtonBalance, Moment, MomentOf, RingBalance, Trait}; // 1 - (99 / 100) ^ sqrt(year) // () -> RingBalance diff --git a/frame/staking/src/lib.rs b/frame/staking/src/lib.rs index 2acac03c8..28635ae6f 100644 --- a/frame/staking/src/lib.rs +++ b/frame/staking/src/lib.rs @@ -261,6 +261,8 @@ mod types { pub type Points = u32; /// Type used for expressing timestamp. pub type Moment = Timestamp; + /// Balance of an account. + pub type Balance = u128; /// Power of an account. pub type Power = u32; /// Votes of an account. @@ -315,7 +317,7 @@ use sp_runtime::{ Bounded, CheckedSub, Convert, EnsureOrigin, One, SaturatedConversion, Saturating, SimpleArithmetic, StaticLookup, Zero, }, - Perbill, RuntimeDebug, + Perbill, Perquintill, RuntimeDebug, }; #[cfg(feature = "std")] use sp_runtime::{Deserialize, Serialize}; @@ -323,7 +325,7 @@ use sp_staking::{ offence::{Offence, OffenceDetails, OnOffenceHandler, ReportOffence}, SessionIndex, }; -use sp_std::{borrow::ToOwned, marker::PhantomData, vec, vec::Vec}; +use sp_std::{borrow::ToOwned, convert::TryInto, marker::PhantomData, vec, vec::Vec}; use darwinia_support::{ LockIdentifier, LockableCurrency, NormalLock, StakingLock, WithdrawLock, WithdrawReason, WithdrawReasons, @@ -780,7 +782,7 @@ decl_storage! { config .stakers .iter() - .map(|&(_, _, r, _)| inflation::compute_balance_power::(r, >::ring_pool())) + .map(|&(_, _, r, _)| >::currency_to_power::<_>(r, >::ring_pool())) .min() .unwrap_or_default() }): Power; @@ -1542,13 +1544,22 @@ decl_module! { impl Module { // PUBLIC IMMUTABLES + // power is a mixture of ring and kton + // power = ring_ratio * POWER_COUNT / 2 + kton_ratio * POWER_COUNT / 2 + pub fn currency_to_power>(active: S, pool: S) -> Power { + (Perquintill::from_rational_approximation( + active.saturated_into::(), + pool.saturated_into::().max(1), + ) * (T::TotalPower::get() as Balance / 2)) as _ + } + /// The total power that can be slashed from a stash account as of right now. pub fn slashable_power_of(stash: &T::AccountId) -> Power { Self::bonded(stash) .and_then(Self::ledger) .map(|l| { - inflation::compute_balance_power::(l.active_ring, Self::ring_pool()) - + inflation::compute_balance_power::(l.active_kton, Self::kton_pool()) + Self::currency_to_power::<_>(l.active_ring, Self::ring_pool()) + + Self::currency_to_power::<_>(l.active_kton, Self::kton_pool()) }) .unwrap_or_default() } From 6bd66d04c06891ce83fb43d34c394265cf62f01b Mon Sep 17 00:00:00 2001 From: Xavier Lau Date: Thu, 13 Feb 2020 22:53:22 +0800 Subject: [PATCH 04/11] update: import --- bin/node/runtime/src/lib.rs | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/bin/node/runtime/src/lib.rs b/bin/node/runtime/src/lib.rs index 516b3a193..ce656eb25 100644 --- a/bin/node/runtime/src/lib.rs +++ b/bin/node/runtime/src/lib.rs @@ -49,17 +49,15 @@ use pallet_transaction_payment_rpc_runtime_api::RuntimeDispatchInfo; use sp_api::impl_runtime_apis; use sp_authority_discovery::AuthorityId as AuthorityDiscoveryId; use sp_core::{ - u32_trait::{_1, _2, _3, _4}, + u32_trait::{_1, _3, _4}, OpaqueMetadata, }; use sp_inherents::{CheckInherentsResult, InherentData}; use sp_runtime::transaction_validity::TransactionValidity; use sp_runtime::{ - create_runtime_str, - curve::PiecewiseLinear, - generic, impl_opaque_keys, + create_runtime_str, generic, impl_opaque_keys, traits::{self, BlakeTwo256, Block as BlockT, NumberFor, OpaqueKeys, SaturatedConversion, StaticLookup}, - ApplyExtrinsicResult, Perbill, Permill, + ApplyExtrinsicResult, Perbill, }; use sp_staking::SessionIndex; use sp_std::vec::Vec; From 7968c47231904fd56c7c97ba2925b6de148ebc64 Mon Sep 17 00:00:00 2001 From: Xavier Lau Date: Thu, 13 Feb 2020 23:17:00 +0800 Subject: [PATCH 05/11] update: total power is `1_000_000_000` which will never overflow `u32` --- primitives/phragmen/src/lib.rs | 24 ++++++++++-------------- 1 file changed, 10 insertions(+), 14 deletions(-) diff --git a/primitives/phragmen/src/lib.rs b/primitives/phragmen/src/lib.rs index 7969caa89..58523d254 100644 --- a/primitives/phragmen/src/lib.rs +++ b/primitives/phragmen/src/lib.rs @@ -195,7 +195,7 @@ where for v in votes { if let Some(idx) = c_idx_cache.get(&v) { // This candidate is valid + already cached. - candidates[*idx].approval_stake = candidates[*idx].approval_stake.saturating_add(to_votes(voter_stake)); + candidates[*idx].approval_stake += to_votes(voter_stake); edges.push(Edge { who: v.clone(), candidate_index: *idx, @@ -358,14 +358,14 @@ where // This is a nomination from `n` to themselves. This will increase both the // `own` and `total` field. debug_assert!(*per_thing == Perbill::one()); // TODO: deal with this: do we want it? - support.own = support.own.saturating_add(other_stake); - support.total = support.total.saturating_add(other_stake); + support.own += other_stake; + support.total += other_stake; } else { // This is a nomination from `n` to someone else. Increase `total` and add an entry // inside `others`. // For an astronomically rich validator with more astronomically rich // set of nominators, this might saturate. - support.total = support.total.saturating_add(other_stake); + support.total += other_stake; support.others.push((n.clone(), other_stake)); } } @@ -437,7 +437,7 @@ where return 0; } - let stake_used = elected_edges.iter().fold(0 as Votes, |s, e| s.saturating_add(e.1)); + let stake_used = elected_edges.iter().fold(0 as Votes, |s, e| s + e.1); let backed_stakes_iter = elected_edges .iter() @@ -462,7 +462,7 @@ where .expect("iterator with positive length will have a min; qed"); difference = max_stake.saturating_sub(min_stake); - difference = difference.saturating_add(budget.saturating_sub(stake_used)); + difference += budget.saturating_sub(stake_used); if difference < tolerance { return difference; } @@ -499,22 +499,18 @@ where last_index = idx.checked_sub(1).unwrap_or(0); break; } - cumulative_stake = cumulative_stake.saturating_add(stake); + cumulative_stake += stake; } idx += 1; } let last_stake = elected_edges[last_index].1; let split_ways = last_index + 1; - let excess = budget - .saturating_add(cumulative_stake) - .saturating_sub(last_stake.saturating_mul(split_ways as Votes)); + let excess = (budget + cumulative_stake).saturating_sub(last_stake.saturating_mul(split_ways as Votes)); elected_edges.iter_mut().take(split_ways).for_each(|e| { if let Some(support) = support_map.get_mut(&e.0) { - e.1 = (excess / split_ways as Votes) - .saturating_add(last_stake) - .saturating_sub(support.total); - support.total = support.total.saturating_add(e.1); + e.1 = ((excess / split_ways as Votes) + last_stake).saturating_sub(support.total); + support.total += e.1; support.others.push((voter.clone(), e.1)); } }); From 951623499c595717bf063b12f3e56b0ccceb7849 Mon Sep 17 00:00:00 2001 From: Xavier Lau Date: Thu, 13 Feb 2020 23:37:17 +0800 Subject: [PATCH 06/11] udpate: `Votes` is `Power` --- bin/node/runtime/src/impls.rs | 38 ++++++++++++------------- bin/node/runtime/src/lib.rs | 4 +-- frame/staking/src/lib.rs | 36 ++++++++++-------------- primitives/phragmen/src/lib.rs | 51 ++++++++++++---------------------- 4 files changed, 52 insertions(+), 77 deletions(-) diff --git a/bin/node/runtime/src/impls.rs b/bin/node/runtime/src/impls.rs index f3769c0fd..6cbc1fd86 100644 --- a/bin/node/runtime/src/impls.rs +++ b/bin/node/runtime/src/impls.rs @@ -35,27 +35,27 @@ impl OnUnbalanced for Author { } } -/// Struct that handles the conversion of Balance -> `u64`. This is used for staking's election -/// calculation. -pub struct PowerToVotesHandler; - -impl PowerToVotesHandler { - fn factor() -> Power { - 1 - } -} - -impl Convert for PowerToVotesHandler { - fn convert(x: Power) -> Votes { - (x / Self::factor()) as Power - } -} - -//impl Convert for PowerToVoteHandler { -// fn convert(x: Vote) -> Power { -// x as Vote * Self::factor() +///// Struct that handles the conversion of Balance -> `u64`. This is used for staking's election +///// calculation. +//pub struct PowerToVotesHandler; +// +//impl PowerToVotesHandler { +// fn factor() -> Power { +// 1 +// } +//} +// +//impl Convert for PowerToVotesHandler { +// fn convert(x: Power) -> Votes { +// (x / Self::factor()) as Power // } //} +// +////impl Convert for PowerToVoteHandler { +//// fn convert(x: Vote) -> Power { +//// x as Vote * Self::factor() +//// } +////} /// Convert from weight to balance via a simple coefficient multiplication /// The associated type C encapsulates a constant in units of balance per weight diff --git a/bin/node/runtime/src/lib.rs b/bin/node/runtime/src/lib.rs index ce656eb25..01194a0ab 100644 --- a/bin/node/runtime/src/lib.rs +++ b/bin/node/runtime/src/lib.rs @@ -22,7 +22,7 @@ /// Implementations of some helper traits passed into runtime modules as associated types. pub mod impls; -use impls::{Author, LinearWeightToFee, PowerToVotesHandler, TargetedFeeAdjustment}; +use impls::{Author, LinearWeightToFee, TargetedFeeAdjustment}; /// Constant values used within the runtime. pub mod constants; use constants::{currency::*, supply::*, time::*}; @@ -457,7 +457,7 @@ parameter_types! { impl pallet_staking::Trait for Runtime { type Time = Timestamp; - type PowerToVotes = PowerToVotesHandler; + // type PowerToVotes = PowerToVotesHandler; type Event = Event; type SessionsPerEra = SessionsPerEra; type BondingDurationInEra = BondingDurationInEra; diff --git a/frame/staking/src/lib.rs b/frame/staking/src/lib.rs index 28635ae6f..14f14ea09 100644 --- a/frame/staking/src/lib.rs +++ b/frame/staking/src/lib.rs @@ -649,12 +649,12 @@ pub trait Trait: frame_system::Trait { /// Time used for computing era duration. type Time: Time; - /// Convert a balance into a number used for election calculation. - /// This must fit into a `u64` but is allowed to be sensibly lossy. - /// TODO: #1377 - /// The backward convert should be removed as the new Phragmen API returns ratio. - /// The post-processing needs it but will be moved to off-chain. TODO: #2908 - type PowerToVotes: Convert; + // /// Convert a balance into a number used for election calculation. + // /// This must fit into a `u64` but is allowed to be sensibly lossy. + // /// TODO: #1377 + // /// The backward convert should be removed as the new Phragmen API returns ratio. + // /// The post-processing needs it but will be moved to off-chain. TODO: #2908 + // type PowerToVotes: Convert; /// The overarching event type. type Event: From> + Into<::Event>; @@ -1886,7 +1886,7 @@ impl Module { all_nominators.extend(nominator_votes); - let maybe_phragmen_result = sp_phragmen::elect::<_, _, _, T::PowerToVotes>( + let maybe_phragmen_result = sp_phragmen::elect::<_, _>( Self::validator_count() as usize, Self::minimum_validator_count().max(1) as usize, all_validators, @@ -1901,13 +1901,8 @@ impl Module { .map(|(s, _)| s.clone()) .collect::>(); let assignments = phragmen_result.assignments; - let to_votes = |p: Power| >::convert(p); - let to_power = |v: Votes| >::convert(v); - let mut supports = sp_phragmen::build_support_map::<_, _, _, T::PowerToVotes>( - &elected_stashes, - &assignments, - Self::slashable_power_of, - ); + let mut supports = + sp_phragmen::build_support_map::<_, _>(&elected_stashes, &assignments, Self::slashable_power_of); if cfg!(feature = "equalize") { let mut staked_assignments: Vec<(T::AccountId, Vec>)> = @@ -1923,7 +1918,7 @@ impl Module { continue; } for (c, per_thing) in assignment.iter() { - let nominator_stake = to_votes(Self::slashable_power_of(n)); + let nominator_stake = Self::slashable_power_of(n); let other_stake = *per_thing * nominator_stake; staked_assignment.push((c.clone(), other_stake)); } @@ -1932,7 +1927,7 @@ impl Module { let tolerance: Votes = 0; let iterations = 2_usize; - sp_phragmen::equalize::<_, _, _, T::PowerToVotes>( + sp_phragmen::equalize::<_, _>( staked_assignments, &mut supports, tolerance, @@ -1951,19 +1946,16 @@ impl Module { for (c, s) in supports.into_iter() { // build `struct exposure` from `support` let exposure = Exposure { - own: to_power(s.own), + own: s.own, // This might reasonably saturate and we cannot do much about it. The sum of // someone's stake might exceed the balance type if they have the maximum amount // of balance and receive some support. This is super unlikely to happen, yet // we simulate it in some tests. - total: to_power(s.total), + total: s.total, others: s .others .into_iter() - .map(|(who, value)| IndividualExposure { - who, - value: to_power(value), - }) + .map(|(who, value)| IndividualExposure { who, value }) .collect::>>(), }; if exposure.total < slot_stake { diff --git a/primitives/phragmen/src/lib.rs b/primitives/phragmen/src/lib.rs index 58523d254..f0dddbc04 100644 --- a/primitives/phragmen/src/lib.rs +++ b/primitives/phragmen/src/lib.rs @@ -41,15 +41,11 @@ use sp_std::{collections::btree_map::BTreeMap, prelude::*}; use darwinia_support::Rational32; -/// A type in which performing operations on power and stakes of candidates and voters are safe. -/// -/// This module's functions expect a `Convert` type to convert `Power` to `Vote`. +/// `Votes` is `Power`. pub type Votes = u32; -/// The denominator used for loads. Since votes are collected as u64, the smallest ratio that we -/// might collect is `1/approval_stake` where approval stake is the sum of votes. Hence, some number -/// bigger than u64::max_value() is needed. For maximum accuracy we simply use u128; -const DEN: Votes = Votes::max_value(); +/// The denominator (total power) used for loads. +const DEN: Votes = 1_000_000_000; /// A candidate entity for phragmen election. #[derive(Clone, Default, RuntimeDebug)] @@ -142,7 +138,7 @@ pub type SupportMap = BTreeMap>; /// responsibility of the caller to make sure only those candidates who have a sensible economic /// value are passed in. From the perspective of this function, a candidate can easily be among the /// winner with no backing stake. -pub fn elect( +pub fn elect( candidate_count: usize, minimum_candidate_count: usize, initial_candidates: Vec, @@ -151,12 +147,8 @@ pub fn elect( ) -> Option> where AccountId: Default + Ord + Member, - Power: Default + Copy + SimpleArithmetic, - for<'r> FS: Fn(&'r AccountId) -> Power, - C: Convert, + for<'r> FS: Fn(&'r AccountId) -> Votes, { - let to_votes = |b: Power| >::convert(b); - // return structures let mut elected_candidates: Vec<(AccountId, Votes)>; let mut assigned: Vec<(AccountId, Vec>)>; @@ -195,7 +187,7 @@ where for v in votes { if let Some(idx) = c_idx_cache.get(&v) { // This candidate is valid + already cached. - candidates[*idx].approval_stake += to_votes(voter_stake); + candidates[*idx].approval_stake += voter_stake; edges.push(Edge { who: v.clone(), candidate_index: *idx, @@ -206,7 +198,7 @@ where Voter { who, edges, - budget: to_votes(voter_stake), + budget: voter_stake, load: Rational32::zero(), } })); @@ -328,18 +320,15 @@ where } /// Build the support map from the given phragmen result. -pub fn build_support_map( +pub fn build_support_map( elected_stashes: &Vec, assignments: &Vec<(AccountId, Vec>)>, stake_of: FS, ) -> SupportMap where AccountId: Default + Ord + Member, - Power: Default + Copy + SimpleArithmetic, - for<'r> FS: Fn(&'r AccountId) -> Power, - C: Convert, + for<'r> FS: Fn(&'r AccountId) -> Votes, { - let to_votes = |b: Power| >::convert(b); // Initialize the support of each candidate. let mut supports = >::new(); elected_stashes.iter().for_each(|e| { @@ -349,7 +338,7 @@ where // build support struct. for (n, assignment) in assignments.iter() { for (c, per_thing) in assignment.iter() { - let nominator_stake = to_votes(stake_of(n)); + let nominator_stake = stake_of(n); // AUDIT: it is crucially important for the `Mul` implementation of all // per-things to be sound. let other_stake = *per_thing * nominator_stake; @@ -385,7 +374,7 @@ where /// * `tolerance`: maximum difference that can occur before an early quite happens. /// * `iterations`: maximum number of iterations that will be processed. /// * `stake_of`: something that can return the stake stake of a particular candidate or voter. -pub fn equalize( +pub fn equalize( mut assignments: Vec<(AccountId, Vec>)>, supports: &mut SupportMap, tolerance: Votes, @@ -393,8 +382,7 @@ pub fn equalize( stake_of: FS, ) where AccountId: Ord + Clone, - for<'r> FS: Fn(&'r AccountId) -> Power, - C: Convert, + for<'r> FS: Fn(&'r AccountId) -> Votes, { // prepare the data for equalise for _i in 0..iterations { @@ -403,7 +391,7 @@ pub fn equalize( for (voter, assignment) in assignments.iter_mut() { let voter_budget = stake_of(&voter); - let diff = do_equalize::<_, _, C>(voter, voter_budget, assignment, supports, tolerance); + let diff = do_equalize::<_>(voter, voter_budget, assignment, supports, tolerance); if diff > max_diff { max_diff = diff; } @@ -417,19 +405,14 @@ pub fn equalize( /// actually perform equalize. same interface is `equalize`. Just called in loops with a check for /// maximum difference. -fn do_equalize( +fn do_equalize( voter: &AccountId, - budget_balance: Power, + budget_balance: Votes, elected_edges: &mut Vec>, support_map: &mut SupportMap, tolerance: Votes, -) -> Votes -where - AccountId: Ord + Clone, - C: Convert, -{ - let to_votes = |b: Power| >::convert(b); - let budget = to_votes(budget_balance); +) -> Votes { + let budget = budget_balance; // Nothing to do. This voter had nothing useful. // Defensive only. Assignment list should always be populated. From c8423ad5c52cf2acfaeb1e5859f9855390167777 Mon Sep 17 00:00:00 2001 From: Xavier Lau Date: Thu, 13 Feb 2020 23:39:08 +0800 Subject: [PATCH 07/11] remove: unused type --- bin/node/primitives/src/lib.rs | 3 --- bin/node/runtime/src/impls.rs | 24 +----------------------- 2 files changed, 1 insertion(+), 26 deletions(-) diff --git a/bin/node/primitives/src/lib.rs b/bin/node/primitives/src/lib.rs index 2456cd876..bd880fa7b 100644 --- a/bin/node/primitives/src/lib.rs +++ b/bin/node/primitives/src/lib.rs @@ -44,9 +44,6 @@ pub type Balance = u128; /// Power of an account. pub type Power = u32; -/// Votes of an account. -pub type Votes = u32; - /// Type used for expressing timestamp. pub type Moment = u64; diff --git a/bin/node/runtime/src/impls.rs b/bin/node/runtime/src/impls.rs index 6cbc1fd86..1aec5f821 100644 --- a/bin/node/runtime/src/impls.rs +++ b/bin/node/runtime/src/impls.rs @@ -26,7 +26,7 @@ use sp_runtime::{ }; use crate::{Authorship, Balances, MaximumBlockWeight, NegativeImbalance, System}; -use node_primitives::{Balance, Power, Votes}; +use node_primitives::Balance; pub struct Author; impl OnUnbalanced for Author { @@ -35,28 +35,6 @@ impl OnUnbalanced for Author { } } -///// Struct that handles the conversion of Balance -> `u64`. This is used for staking's election -///// calculation. -//pub struct PowerToVotesHandler; -// -//impl PowerToVotesHandler { -// fn factor() -> Power { -// 1 -// } -//} -// -//impl Convert for PowerToVotesHandler { -// fn convert(x: Power) -> Votes { -// (x / Self::factor()) as Power -// } -//} -// -////impl Convert for PowerToVoteHandler { -//// fn convert(x: Vote) -> Power { -//// x as Vote * Self::factor() -//// } -////} - /// Convert from weight to balance via a simple coefficient multiplication /// The associated type C encapsulates a constant in units of balance per weight pub struct LinearWeightToFee(sp_std::marker::PhantomData); From 090280c7c8119f383e3bd110e9fd7b8ca2bfa8fe Mon Sep 17 00:00:00 2001 From: Xavier Lau Date: Thu, 13 Feb 2020 23:41:54 +0800 Subject: [PATCH 08/11] update: rename `slashable_power_of` to `power_of` --- frame/staking/src/lib.rs | 17 +++++------------ 1 file changed, 5 insertions(+), 12 deletions(-) diff --git a/frame/staking/src/lib.rs b/frame/staking/src/lib.rs index 14f14ea09..1ab8a01fa 100644 --- a/frame/staking/src/lib.rs +++ b/frame/staking/src/lib.rs @@ -1554,7 +1554,7 @@ impl Module { } /// The total power that can be slashed from a stash account as of right now. - pub fn slashable_power_of(stash: &T::AccountId) -> Power { + pub fn power_of(stash: &T::AccountId) -> Power { Self::bonded(stash) .and_then(Self::ledger) .map(|l| { @@ -1891,7 +1891,7 @@ impl Module { Self::minimum_validator_count().max(1) as usize, all_validators, all_nominators, - Self::slashable_power_of, + Self::power_of, ); if let Some(phragmen_result) = maybe_phragmen_result { @@ -1901,8 +1901,7 @@ impl Module { .map(|(s, _)| s.clone()) .collect::>(); let assignments = phragmen_result.assignments; - let mut supports = - sp_phragmen::build_support_map::<_, _>(&elected_stashes, &assignments, Self::slashable_power_of); + let mut supports = sp_phragmen::build_support_map::<_, _>(&elected_stashes, &assignments, Self::power_of); if cfg!(feature = "equalize") { let mut staked_assignments: Vec<(T::AccountId, Vec>)> = @@ -1918,7 +1917,7 @@ impl Module { continue; } for (c, per_thing) in assignment.iter() { - let nominator_stake = Self::slashable_power_of(n); + let nominator_stake = Self::power_of(n); let other_stake = *per_thing * nominator_stake; staked_assignment.push((c.clone(), other_stake)); } @@ -1927,13 +1926,7 @@ impl Module { let tolerance: Votes = 0; let iterations = 2_usize; - sp_phragmen::equalize::<_, _>( - staked_assignments, - &mut supports, - tolerance, - iterations, - Self::slashable_power_of, - ); + sp_phragmen::equalize::<_, _>(staked_assignments, &mut supports, tolerance, iterations, Self::power_of); } // Clear Stakers. From d7fc7aceb6e2cb207c70299f9e30891a1eb897e7 Mon Sep 17 00:00:00 2001 From: Xavier Lau Date: Thu, 13 Feb 2020 23:49:41 +0800 Subject: [PATCH 09/11] update: use total power as denominator --- frame/staking/src/lib.rs | 1 + primitives/phragmen/src/lib.rs | 26 ++++++++++++-------------- 2 files changed, 13 insertions(+), 14 deletions(-) diff --git a/frame/staking/src/lib.rs b/frame/staking/src/lib.rs index 1ab8a01fa..95de7a436 100644 --- a/frame/staking/src/lib.rs +++ b/frame/staking/src/lib.rs @@ -1892,6 +1892,7 @@ impl Module { all_validators, all_nominators, Self::power_of, + T::TotalPower::get(), ); if let Some(phragmen_result) = maybe_phragmen_result { diff --git a/primitives/phragmen/src/lib.rs b/primitives/phragmen/src/lib.rs index f0dddbc04..dfcc4cc76 100644 --- a/primitives/phragmen/src/lib.rs +++ b/primitives/phragmen/src/lib.rs @@ -44,9 +44,6 @@ use darwinia_support::Rational32; /// `Votes` is `Power`. pub type Votes = u32; -/// The denominator (total power) used for loads. -const DEN: Votes = 1_000_000_000; - /// A candidate entity for phragmen election. #[derive(Clone, Default, RuntimeDebug)] pub struct Candidate { @@ -132,7 +129,7 @@ pub type SupportMap = BTreeMap>; /// `None` is returned. /// * `initial_candidates`: candidates list to be elected from. /// * `initial_voters`: voters list. -/// * `stake_of`: something that can return the stake stake of a particular candidate or voter. +/// * `power_of`: something that can return the stake stake of a particular candidate or voter. /// /// This function does not strip out candidates who do not have any backing stake. It is the /// responsibility of the caller to make sure only those candidates who have a sensible economic @@ -143,7 +140,8 @@ pub fn elect( minimum_candidate_count: usize, initial_candidates: Vec, initial_voters: Vec<(AccountId, Vec)>, - stake_of: FS, + power_of: FS, + total_power: Votes, ) -> Option> where AccountId: Default + Ord + Member, @@ -182,7 +180,7 @@ where // collect voters. use `c_idx_cache` for fast access and aggregate `approval_stake` of // candidates. voters.extend(initial_voters.into_iter().map(|(who, votes)| { - let voter_stake = stake_of(&who); + let voter_stake = power_of(&who); let mut edges: Vec> = Vec::with_capacity(votes.len()); for v in votes { if let Some(idx) = c_idx_cache.get(&v) { @@ -214,12 +212,12 @@ where // loop 1: initialize score for c in &mut candidates { if !c.elected { - // 1 / approval_stake == (DEN / approval_stake) / DEN. If approval_stake is zero, + // 1 / approval_stake == (total_power / approval_stake) / total_power. If approval_stake is zero, // then the ratio should be as large as possible, essentially `infinity`. if c.approval_stake.is_zero() { - c.score = Rational32::from_unchecked(DEN, 0); + c.score = Rational32::from_unchecked(total_power, 0); } else { - c.score = Rational32::from(DEN / c.approval_stake, DEN); + c.score = Rational32::from(total_power / c.approval_stake, total_power); } } } @@ -323,7 +321,7 @@ where pub fn build_support_map( elected_stashes: &Vec, assignments: &Vec<(AccountId, Vec>)>, - stake_of: FS, + power_of: FS, ) -> SupportMap where AccountId: Default + Ord + Member, @@ -338,7 +336,7 @@ where // build support struct. for (n, assignment) in assignments.iter() { for (c, per_thing) in assignment.iter() { - let nominator_stake = stake_of(n); + let nominator_stake = power_of(n); // AUDIT: it is crucially important for the `Mul` implementation of all // per-things to be sound. let other_stake = *per_thing * nominator_stake; @@ -373,13 +371,13 @@ where /// * `supports`: mutable reference to s `SupportMap`. This parameter is updated. /// * `tolerance`: maximum difference that can occur before an early quite happens. /// * `iterations`: maximum number of iterations that will be processed. -/// * `stake_of`: something that can return the stake stake of a particular candidate or voter. +/// * `power_of`: something that can return the stake stake of a particular candidate or voter. pub fn equalize( mut assignments: Vec<(AccountId, Vec>)>, supports: &mut SupportMap, tolerance: Votes, iterations: usize, - stake_of: FS, + power_of: FS, ) where AccountId: Ord + Clone, for<'r> FS: Fn(&'r AccountId) -> Votes, @@ -389,7 +387,7 @@ pub fn equalize( let mut max_diff = 0; for (voter, assignment) in assignments.iter_mut() { - let voter_budget = stake_of(&voter); + let voter_budget = power_of(&voter); let diff = do_equalize::<_>(voter, voter_budget, assignment, supports, tolerance); if diff > max_diff { From c762582942211005a3aa262dfc9a84ec66cdcd37 Mon Sep 17 00:00:00 2001 From: Xavier Lau Date: Fri, 14 Feb 2020 13:36:52 +0800 Subject: [PATCH 10/11] remove: unused import --- primitives/phragmen/src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/primitives/phragmen/src/lib.rs b/primitives/phragmen/src/lib.rs index dfcc4cc76..d1f31e94b 100644 --- a/primitives/phragmen/src/lib.rs +++ b/primitives/phragmen/src/lib.rs @@ -34,7 +34,7 @@ #![cfg_attr(not(feature = "std"), no_std)] use sp_runtime::{ - traits::{Convert, Member, Saturating, SimpleArithmetic, Zero}, + traits::{Member, Saturating, Zero}, Perbill, RuntimeDebug, }; use sp_std::{collections::btree_map::BTreeMap, prelude::*}; From 6048c319462e408f41639c24be11e700a2c7160d Mon Sep 17 00:00:00 2001 From: Xavier Lau Date: Fri, 14 Feb 2020 13:37:03 +0800 Subject: [PATCH 11/11] update: format --- bin/node/runtime/src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bin/node/runtime/src/lib.rs b/bin/node/runtime/src/lib.rs index 01194a0ab..e0e8211ff 100644 --- a/bin/node/runtime/src/lib.rs +++ b/bin/node/runtime/src/lib.rs @@ -505,7 +505,7 @@ construct_runtime!( Offences: pallet_offences::{Module, Call, Storage, Event}, RandomnessCollectiveFlip: pallet_randomness_collective_flip::{Module, Call, Storage}, Nicks: pallet_nicks::{Module, Call, Storage, Event}, - + Balances: pallet_ring::{default, Error}, Kton: pallet_kton::{default, Error}, Staking: pallet_staking::{default, OfflineWorker},