Bump version to 1.0.0 globally (#2149)

* Bump versionb globally

* Rebuild and fix

* Rename fixes

* Rebuild

* Minor fix and code formatting for validator election

* Fix tests

* More test fixes

* Fix several bugs in phragmen elections.

* Rebuild, remove pointless closures
This commit is contained in:
Gav Wood
2019-04-01 15:16:06 +02:00
committed by GitHub
parent fbbd79e778
commit eca163ba64
89 changed files with 1626 additions and 1652 deletions
+5 -9
View File
@@ -944,16 +944,12 @@ impl<T: Trait> Module<T> {
///
/// Returns the new SlotStake value.
fn select_validators() -> BalanceOf<T> {
let rounds = || <ValidatorCount<T>>::get() as usize;
let validators = || <Validators<T>>::enumerate();
let nominators = || <Nominators<T>>::enumerate();
let min_validator_count = Self::minimum_validator_count() as usize;
let maybe_elected_candidates = elect::<T, _, _, _, _>(
rounds,
validators,
nominators,
let maybe_elected_candidates = elect::<T, _, _, _>(
Self::validator_count() as usize,
Self::minimum_validator_count().max(1) as usize,
<Validators<T>>::enumerate(),
<Nominators<T>>::enumerate(),
Self::slashable_balance_of,
min_validator_count,
ElectionConfig::<BalanceOf<T>> {
equalize: false,
tolerance: <BalanceOf<T>>::sa(10 as u64),
+7 -6
View File
@@ -101,7 +101,7 @@ pub struct ExtBuilder {
nominate: bool,
validator_count: u32,
minimum_validator_count: u32,
fare: bool,
fair: bool,
}
impl Default for ExtBuilder {
@@ -116,7 +116,7 @@ impl Default for ExtBuilder {
nominate: true,
validator_count: 2,
minimum_validator_count: 0,
fare: true
fair: true
}
}
}
@@ -154,8 +154,8 @@ impl ExtBuilder {
self.minimum_validator_count = count;
self
}
pub fn fare(mut self, is_fare: bool) -> Self {
self.fare = is_fare;
pub fn fair(mut self, is_fair: bool) -> Self {
self.fair = is_fair;
self
}
pub fn build(self) -> runtime_io::TestExternalities<Blake2Hasher> {
@@ -205,7 +205,7 @@ impl ExtBuilder {
stakers: if self.validator_pool {
vec![
(11, 10, balance_factor * 1000, StakerStatus::<AccountIdType>::Validator),
(21, 20, balance_factor * if self.fare { 1000 } else { 2000 }, StakerStatus::<AccountIdType>::Validator),
(21, 20, balance_factor * if self.fair { 1000 } else { 2000 }, StakerStatus::<AccountIdType>::Validator),
(31, 30, balance_factor * 1000, if self.validator_pool { StakerStatus::<AccountIdType>::Validator } else { StakerStatus::<AccountIdType>::Idle }),
(41, 40, balance_factor * 1000, if self.validator_pool { StakerStatus::<AccountIdType>::Validator } else { StakerStatus::<AccountIdType>::Idle }),
// nominator
@@ -214,7 +214,8 @@ impl ExtBuilder {
} else {
vec![
(11, 10, balance_factor * 1000, StakerStatus::<AccountIdType>::Validator),
(21, 20, balance_factor * if self.fare { 1000 } else { 2000 }, StakerStatus::<AccountIdType>::Validator),
(21, 20, balance_factor * if self.fair { 1000 } else { 2000 }, StakerStatus::<AccountIdType>::Validator),
(31, 30, 1, StakerStatus::<AccountIdType>::Validator),
// nominator
(101, 100, balance_factor * 500, if self.nominate { StakerStatus::<AccountIdType>::Nominator(vec![11, 21]) } else { StakerStatus::<AccountIdType>::Nominator(vec![]) })
]
+78 -92
View File
@@ -92,59 +92,62 @@ pub struct Edge<AccountId> {
///
/// Returns an Option of elected candidates, if election is performed.
/// Returns None if not enough candidates exist.
pub fn elect<T: Trait + 'static, FR, FN, FV, FS>(
get_rounds: FR,
get_validators: FV,
get_nominators: FN,
stash_of: FS,
minimum_validator_count: usize,
config: ElectionConfig<BalanceOf<T>>,
pub fn elect<T: Trait + 'static, FV, FN, FS>(
validator_count: usize,
minimum_validator_count: usize,
validator_iter: FV,
nominator_iter: FN,
stash_of: FS,
config: ElectionConfig<BalanceOf<T>>,
) -> Option<Vec<Candidate<T::AccountId, BalanceOf<T>>>> where
FR: Fn() -> usize,
FV: Fn() -> Box<dyn Iterator<
Item =(T::AccountId, ValidatorPrefs<BalanceOf<T>>)
>>,
FN: Fn() -> Box<dyn Iterator<
Item =(T::AccountId, Vec<T::AccountId>)
>>,
FV: Iterator<Item=(T::AccountId, ValidatorPrefs<BalanceOf<T>>)>,
FN: Iterator<Item=(T::AccountId, Vec<T::AccountId>)>,
for <'r> FS: Fn(&'r T::AccountId) -> BalanceOf<T>,
{
let expand = |b: BalanceOf<T>| <T::CurrencyToVote as Convert<BalanceOf<T>, u64>>::convert(b) as ExtendedBalance;
let shrink = |b: ExtendedBalance| <T::CurrencyToVote as Convert<ExtendedBalance, BalanceOf<T>>>::convert(b);
let rounds = get_rounds();
let into_currency = |b: BalanceOf<T>| <T::CurrencyToVote as Convert<BalanceOf<T>, u64>>::convert(b) as ExtendedBalance;
let into_votes = |b: ExtendedBalance| <T::CurrencyToVote as Convert<ExtendedBalance, BalanceOf<T>>>::convert(b);
let mut elected_candidates;
// 1- Pre-process candidates and place them in a container
let mut candidates = get_validators().map(|(who, _)| {
let stash_balance = stash_of(&who);
Candidate {
who,
exposure: Exposure { total: stash_balance, own: stash_balance, others: vec![] },
..Default::default()
}
}).collect::<Vec<Candidate<T::AccountId, BalanceOf<T>>>>();
// 1.1- Add phantom votes.
let mut nominators: Vec<Nominator<T::AccountId>> = Vec::with_capacity(candidates.len());
candidates.iter_mut().enumerate().for_each(|(idx, c)| {
c.approval_stake += expand(c.exposure.total);
nominators.push(Nominator {
who: c.who.clone(),
edges: vec![ Edge { who: c.who.clone(), candidate_index: idx, ..Default::default() }],
budget: expand(c.exposure.total),
load: Fraction::zero(),
// 1- Pre-process candidates and place them in a container, optimisation and add phantom votes.
// Candidates who have 0 stake => have no votes or all null-votes. Kick them out not.
let mut nominators: Vec<Nominator<T::AccountId>> = Vec::with_capacity(validator_iter.size_hint().0 + nominator_iter.size_hint().0);
let mut candidates = validator_iter.map(|(who, _)| {
let stash_balance = stash_of(&who);
Candidate {
who,
exposure: Exposure { total: stash_balance, own: stash_balance, others: vec![] },
..Default::default()
}
})
});
.filter_map(|mut c| {
c.approval_stake += into_currency(c.exposure.total);
if c.approval_stake.is_zero() {
None
} else {
Some(c)
}
})
.enumerate()
.map(|(idx, c)| {
nominators.push(Nominator {
who: c.who.clone(),
edges: vec![ Edge { who: c.who.clone(), candidate_index: idx, ..Default::default() }],
budget: into_currency(c.exposure.total),
load: Fraction::zero(),
});
c
})
.collect::<Vec<Candidate<T::AccountId, BalanceOf<T>>>>();
// 2- Collect the nominators with the associated votes.
// Also collect approval stake along the way.
nominators.extend(get_nominators().map(|(who, nominees)| {
nominators.extend(nominator_iter.map(|(who, nominees)| {
let nominator_stake = stash_of(&who);
let mut edges: Vec<Edge<T::AccountId>> = Vec::with_capacity(nominees.len());
for n in &nominees {
if let Some(idx) = candidates.iter_mut().position(|i| i.who == *n) {
candidates[idx].approval_stake = candidates[idx].approval_stake
.saturating_add(expand(nominator_stake));
.saturating_add(into_currency(nominator_stake));
edges.push(Edge { who: n.clone(), candidate_index: idx, ..Default::default() });
}
}
@@ -152,22 +155,18 @@ pub fn elect<T: Trait + 'static, FR, FN, FV, FS>(
Nominator {
who,
edges: edges,
budget: expand(nominator_stake),
budget: into_currency(nominator_stake),
load: Fraction::zero(),
}
}));
// 3- optimization:
// Candidates who have 0 stake => have no votes or all null-votes. Kick them out not.
let mut candidates = candidates.into_iter().filter(|c| c.approval_stake > 0)
.collect::<Vec<Candidate<T::AccountId, BalanceOf<T>>>>();
// 4- If we have more candidates then needed, run Phragmén.
if candidates.len() >= rounds {
elected_candidates = Vec::with_capacity(rounds);
if candidates.len() >= minimum_validator_count {
let validator_count = validator_count.min(candidates.len());
elected_candidates = Vec::with_capacity(validator_count);
// Main election loop
for _round in 0..rounds {
for _round in 0..validator_count {
// Loop 1: initialize score
for c in &mut candidates {
if !c.elected {
@@ -178,7 +177,7 @@ pub fn elect<T: Trait + 'static, FR, FN, FV, FS>(
for n in &nominators {
for e in &n.edges {
let c = &mut candidates[e.candidate_index];
if !c.elected {
if !c.elected && !c.approval_stake.is_zero() {
let temp = n.budget.saturating_mul(*n.load) / c.approval_stake;
c.score = Fraction::from_max_value((*c.score).saturating_add(temp));
}
@@ -186,25 +185,28 @@ pub fn elect<T: Trait + 'static, FR, FN, FV, FS>(
}
// Find the best
let winner = candidates
if let Some(winner) = candidates
.iter_mut()
.filter(|c| !c.elected)
.min_by_key(|c| *c.score)
.expect("candidates length is checked to be >0; qed");
// loop 3: update nominator and edge load
winner.elected = true;
for n in &mut nominators {
for e in &mut n.edges {
if e.who == winner.who {
e.load = Fraction::from_max_value(*winner.score - *n.load);
n.load = winner.score;
{
// loop 3: update nominator and edge load
winner.elected = true;
for n in &mut nominators {
for e in &mut n.edges {
if e.who == winner.who {
e.load = Fraction::from_max_value(*winner.score - *n.load);
n.load = winner.score;
}
}
}
}
elected_candidates.push(winner.clone());
} // end of all rounds
elected_candidates.push(winner.clone());
} else {
break
}
}
// end of all rounds
// 4.1- Update backing stake of candidates and nominators
for n in &mut nominators {
@@ -213,13 +215,13 @@ pub fn elect<T: Trait + 'static, FR, FN, FV, FS>(
if let Some(c) = elected_candidates.iter_mut().find(|c| c.who == e.who) {
e.elected = true;
// NOTE: for now, always divide last to avoid collapse to zero.
e.backing_stake = n.budget.saturating_mul(*e.load) / *n.load;
e.backing_stake = n.budget.saturating_mul(*e.load) / n.load.max(1);
c.backing_stake = c.backing_stake.saturating_add(e.backing_stake);
if c.who != n.who {
// Only update the exposure if this vote is from some other account.
c.exposure.total = c.exposure.total.saturating_add(shrink(e.backing_stake));
c.exposure.total = c.exposure.total.saturating_add(into_votes(e.backing_stake));
c.exposure.others.push(
IndividualExposure { who: n.who.clone(), value: shrink(e.backing_stake) }
IndividualExposure { who: n.who.clone(), value: into_votes(e.backing_stake) }
);
}
}
@@ -254,24 +256,8 @@ pub fn elect<T: Trait + 'static, FR, FN, FV, FS>(
}
}
} else {
if candidates.len() > minimum_validator_count {
// if we don't have enough candidates, just choose all that have some vote.
elected_candidates = candidates;
for n in &mut nominators {
let nominator = n.who.clone();
for e in &mut n.edges {
if let Some(c) = elected_candidates.iter_mut().find(|c| c.who == e.who && c.who != nominator) {
c.exposure.total = c.exposure.total.saturating_add(shrink(n.budget));
c.exposure.others.push(
IndividualExposure { who: n.who.clone(), value: shrink(n.budget) }
);
}
}
}
} else {
// if we have less than minimum, use the previous validator set.
return None
}
// if we have less than minimum, use the previous validator set.
return None
}
Some(elected_candidates)
}
@@ -285,9 +271,9 @@ pub fn equalize<T: Trait + 'static>(
elected_candidates: &mut Vec<Candidate<T::AccountId, BalanceOf<T>>>,
_tolerance: BalanceOf<T>
) -> BalanceOf<T> {
let expand = |b: BalanceOf<T>| <T::CurrencyToVote as Convert<BalanceOf<T>, u64>>::convert(b) as ExtendedBalance;
let shrink = |b: ExtendedBalance| <T::CurrencyToVote as Convert<ExtendedBalance, BalanceOf<T>>>::convert(b);
let tolerance = expand(_tolerance);
let into_currency = |b: BalanceOf<T>| <T::CurrencyToVote as Convert<BalanceOf<T>, u64>>::convert(b) as ExtendedBalance;
let into_votes = |b: ExtendedBalance| <T::CurrencyToVote as Convert<ExtendedBalance, BalanceOf<T>>>::convert(b);
let tolerance = into_currency(_tolerance);
let mut elected_edges = nominator.edges
.iter_mut()
@@ -324,7 +310,7 @@ pub fn equalize<T: Trait + 'static>(
difference = max_stake.saturating_sub(min_stake);
difference = difference.saturating_add(nominator.budget.saturating_sub(stake_used));
if difference < tolerance {
return shrink(difference);
return into_votes(difference);
}
} else {
difference = nominator.budget;
@@ -335,7 +321,7 @@ pub fn equalize<T: Trait + 'static>(
// NOTE: no assertions in the runtime, but this should nonetheless be indicative.
//assert_eq!(elected_candidates[e.elected_idx].who, e.who);
elected_candidates[e.elected_idx].backing_stake -= e.backing_stake;
elected_candidates[e.elected_idx].exposure.total -= shrink(e.backing_stake);
elected_candidates[e.elected_idx].exposure.total -= into_votes(e.backing_stake);
e.backing_stake = 0;
});
@@ -367,11 +353,11 @@ pub fn equalize<T: Trait + 'static>(
e.backing_stake = (excess / split_ways as ExtendedBalance)
.saturating_add(last_stake)
.saturating_sub(c.backing_stake);
c.exposure.total = c.exposure.total.saturating_add(shrink(e.backing_stake));
c.exposure.total = c.exposure.total.saturating_add(into_votes(e.backing_stake));
c.backing_stake = c.backing_stake.saturating_add(e.backing_stake);
if let Some(i_expo) = c.exposure.others.iter_mut().find(|i| i.who == nominator_address) {
i_expo.value = shrink(e.backing_stake);
i_expo.value = into_votes(e.backing_stake);
}
});
shrink(difference)
into_votes(difference)
}
+29 -41
View File
@@ -45,6 +45,7 @@ fn basic_setup_works() {
// ValidatorPrefs are default, thus unstake_threshold is 3, other values are default for their type
assert_eq!(<Validators<Test>>::enumerate().collect::<Vec<_>>(), vec![
(31, ValidatorPrefs { unstake_threshold: 3, validator_payment: 0 }),
(21, ValidatorPrefs { unstake_threshold: 3, validator_payment: 0 }),
(11, ValidatorPrefs { unstake_threshold: 3, validator_payment: 0 })
]);
@@ -435,7 +436,7 @@ fn staking_should_work() {
with_externalities(&mut ExtBuilder::default()
.sessions_per_era(3)
.nominate(false)
.fare(false) // to give 20 more staked value
.fair(false) // to give 20 more staked value
.build(),
|| {
// remember + compare this along with the test.
@@ -518,17 +519,14 @@ fn less_than_needed_candidates_works() {
// The expected behavior is to choose all the candidates that have some vote.
with_externalities(&mut ExtBuilder::default()
.minimum_validator_count(1)
.validator_count(3)
.validator_count(4)
.nominate(false)
.build(),
|| {
assert_eq!(Staking::era_length(), 1);
assert_eq!(Staking::validator_count(), 3);
assert_eq!(Staking::validator_count(), 4);
assert_eq!(Staking::minimum_validator_count(), 1);
// initial validators
assert_eq_uvec!(Session::validators(), vec![20, 10]);
// 10 and 20 are now valid candidates.
// trigger era
System::set_block_number(1);
@@ -536,11 +534,12 @@ fn less_than_needed_candidates_works() {
assert_eq!(Staking::current_era(), 1);
// both validators will be chosen again. NO election algorithm is even executed.
assert_eq_uvec!(Session::validators(), vec![20, 10]);
assert_eq_uvec!(Session::validators(), vec![30, 20, 10]);
// But the exposure is updated in a simple way. No external votes exists. This is purely self-vote.
assert_eq!(Staking::stakers(10).others.iter().map(|e| e.who).collect::<Vec<BalanceOf<Test>>>(), vec![]);
assert_eq!(Staking::stakers(20).others.iter().map(|e| e.who).collect::<Vec<BalanceOf<Test>>>(), vec![]);
assert_eq!(Staking::stakers(30).others.iter().map(|e| e.who).collect::<Vec<BalanceOf<Test>>>(), vec![]);
});
}
@@ -702,9 +701,7 @@ fn nominators_also_get_slashed() {
assert_eq!(Staking::offline_slash_grace(), 0);
// Account 10 has not been reported offline
assert_eq!(Staking::slash_count(&10), 0);
// initial validators
assert_eq_uvec!(Session::validators(), vec![20, 10]);
<OfflineSlash<Test>>::put(Perbill::from_percent(12));
<OfflineSlash<Test>>::put(Perbill::from_percent(12));
// Set payee to controller
assert_ok!(Staking::set_payee(Origin::signed(10), RewardDestination::Controller));
@@ -879,7 +876,7 @@ fn cannot_transfer_staked_balance_2() {
// 21 has 2000 free balance but 1000 at stake
with_externalities(&mut ExtBuilder::default()
.nominate(false)
.fare(true)
.fair(true)
.build(),
|| {
// Confirm account 21 is stashed
@@ -1182,7 +1179,7 @@ fn slot_stake_is_least_staked_validator_and_exposure_defines_maximum_punishment(
// Test that slot_stake is the maximum punishment that can happen to a validator
with_externalities(&mut ExtBuilder::default()
.nominate(false)
.fare(false)
.fair(false)
.build(),
|| {
// Confirm validator count is 2
@@ -1482,7 +1479,7 @@ fn phragmen_election_works_with_post_processing() {
assert_eq_uvec!(Session::validators(), vec![20, 10]);
// Bond [30, 31] as the third validator
assert_ok!(Staking::bond(Origin::signed(31), 30, 1000, RewardDestination::default()));
assert_ok!(Staking::bond_extra(Origin::signed(31), 999));
assert_ok!(Staking::validate(Origin::signed(30), ValidatorPrefs::default()));
// bond [2,1](A), [4,3](B), as 2 nominators
@@ -1494,17 +1491,12 @@ fn phragmen_election_works_with_post_processing() {
assert_ok!(Staking::bond(Origin::signed(3), 4, 1000, RewardDestination::default()));
assert_ok!(Staking::nominate(Origin::signed(4), vec![11, 31]));
let rounds = || 2 as usize;
let validators = || <Validators<Test>>::enumerate();
let nominators = || <Nominators<Test>>::enumerate();
let min_validator_count = Staking::minimum_validator_count() as usize;
let winners = phragmen::elect::<Test, _, _, _, _>(
rounds,
validators,
nominators,
let winners = phragmen::elect::<Test, _, _, _>(
2,
Staking::minimum_validator_count() as usize,
<Validators<Test>>::enumerate(),
<Nominators<Test>>::enumerate(),
Staking::slashable_balance_of,
min_validator_count,
ElectionConfig::<BalanceOf<Test>> {
equalize: true,
tolerance: <BalanceOf<Test>>::sa(10 as u64),
@@ -1646,21 +1638,18 @@ fn bond_with_no_staked_value() {
let initial_balance_2 = Balances::free_balance(&2);
let initial_balance_4 = Balances::free_balance(&4);
// initial validators
assert_eq_uvec!(Session::validators(), vec![20, 10]);
// Stingy validator.
assert_ok!(Staking::bond(Origin::signed(1), 2, 0, RewardDestination::Controller));
assert_ok!(Staking::bond(Origin::signed(1), 2, 1, RewardDestination::Controller));
assert_ok!(Staking::validate(Origin::signed(2), ValidatorPrefs::default()));
System::set_block_number(1);
Session::check_rotate_session(System::block_number());
// Not elected even though we want 3.
assert_eq_uvec!(Session::validators(), vec![20, 10]);
assert_eq_uvec!(Session::validators(), vec![30, 20, 10]);
// min of 10 and 20.
assert_eq!(Staking::slot_stake(), 1000);
// min of 10, 20 and 30 (30 got a payout into staking so it raised it from 1 to 11).
assert_eq!(Staking::slot_stake(), 11);
// let's make the stingy one elected.
assert_ok!(Staking::bond(Origin::signed(3), 4, 500, RewardDestination::Controller));
@@ -1675,9 +1664,9 @@ fn bond_with_no_staked_value() {
// Stingy one is selected
assert_eq_uvec!(Session::validators(), vec![20, 10, 2]);
assert_eq!(Staking::stakers(1), Exposure { own: 0, total: 500, others: vec![IndividualExposure { who: 3, value: 500}]});
assert_eq!(Staking::stakers(1), Exposure { own: 1, total: 501, others: vec![IndividualExposure { who: 3, value: 500}]});
// New slot stake.
assert_eq!(Staking::slot_stake(), 500);
assert_eq!(Staking::slot_stake(), 501);
// no rewards paid to 2 and 4 yet
assert_eq!(Balances::free_balance(&2), initial_balance_2);
@@ -1687,10 +1676,10 @@ fn bond_with_no_staked_value() {
Session::check_rotate_session(System::block_number());
let reward = Staking::current_session_reward();
// 2 will not get any reward
// 4 will get all the reward share
assert_eq!(Balances::free_balance(&2), initial_balance_2);
assert_eq!(Balances::free_balance(&4), initial_balance_4 + reward);
// 2 will not get a reward of only 1
// 4 will get the rest
assert_eq!(Balances::free_balance(&2), initial_balance_2 + 1);
assert_eq!(Balances::free_balance(&4), initial_balance_4 + reward - 1);
});
}
@@ -1704,14 +1693,13 @@ fn bond_with_little_staked_value_bounded_by_slot_stake() {
.minimum_validator_count(1)
.build(),
|| {
// setup
assert_ok!(Staking::chill(Origin::signed(30)));
assert_ok!(Staking::set_payee(Origin::signed(10), RewardDestination::Controller));
let initial_balance_2 = Balances::free_balance(&2);
let initial_balance_10 = Balances::free_balance(&10);
// initial validators
assert_eq_uvec!(Session::validators(), vec![20, 10]);
// Stingy validator.
assert_ok!(Staking::bond(Origin::signed(1), 2, 1, RewardDestination::Controller));
assert_ok!(Staking::validate(Origin::signed(2), ValidatorPrefs::default()));
@@ -1750,7 +1738,7 @@ fn phragmen_linear_worse_case_equalize() {
with_externalities(&mut ExtBuilder::default()
.nominate(false)
.validator_pool(true)
.fare(true)
.fair(true)
.build(),
|| {
let bond_validator = |a, b| {
@@ -1810,7 +1798,7 @@ fn phragmen_chooses_correct_number_of_validators() {
with_externalities(&mut ExtBuilder::default()
.nominate(true)
.validator_pool(true)
.fare(true)
.fair(true)
.validator_count(1)
.build(),
|| {