Skip to content
This repository was archived by the owner on Nov 15, 2023. It is now read-only.
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion bin/node/runtime/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -83,7 +83,7 @@ pub const VERSION: RuntimeVersion = RuntimeVersion {
// and set impl_version to 0. If only runtime
// implementation changes and behavior does not, then leave spec_version as
// is and increment impl_version.
spec_version: 239,
spec_version: 240,
impl_version: 0,
apis: RUNTIME_API_VERSIONS,
};
Expand Down
126 changes: 92 additions & 34 deletions frame/elections-phragmen/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -158,13 +158,11 @@ decl_storage! {
/// The total number of vote rounds that have happened, excluding the upcoming one.
pub ElectionRounds get(fn election_rounds): u32 = Zero::zero();

/// Votes of a particular voter, with the round index of the votes.
pub VotesOf get(fn votes_of): map hasher(twox_64_concat) T::AccountId => Vec<T::AccountId>;
/// Locked stake of a voter.
pub StakeOf get(fn stake_of): map hasher(twox_64_concat) T::AccountId => BalanceOf<T>;
/// Votes and locked stake of a particular voter.
pub Voting: map hasher(twox_64_concat) T::AccountId => (BalanceOf<T>, Vec<T::AccountId>);

/// The present candidate list. Sorted based on account-id. A current member or a runner can
/// never enter this vector and is always implicitly assumed to be a candidate.
/// The present candidate list. Sorted based on account-id. A current member or runner-up
/// can never enter this vector and is always implicitly assumed to be a candidate.
pub Candidates get(fn candidates): Vec<T::AccountId>;
}
}
Expand Down Expand Up @@ -203,12 +201,32 @@ decl_error! {
}
}

mod migration {
use super::*;
use frame_support::{migration::{StorageKeyIterator, take_storage_item}, Twox64Concat};
pub fn migrate<T: Trait>() {
for (who, votes) in StorageKeyIterator
::<T::AccountId, Vec<T::AccountId>, Twox64Concat>
::new(b"PhragmenElection", b"VotesOf")
.drain()
{
if let Some(stake) = take_storage_item::<_, BalanceOf<T>, Twox64Concat>(b"PhragmenElection", b"StakeOf", &who) {
Voting::<T>::insert(who, (stake, votes));
}
}
}
}

decl_module! {
pub struct Module<T: Trait> for enum Call where origin: T::Origin {
type Error = Error<T>;

fn deposit_event() = default;

fn on_runtime_upgrade() {
migration::migrate::<T>();
}

const CandidacyBond: BalanceOf<T> = T::CandidacyBond::get();
const VotingBond: BalanceOf<T> = T::VotingBond::get();
const DesiredMembers: u32 = T::DesiredMembers::get();
Expand Down Expand Up @@ -264,8 +282,8 @@ decl_module! {
locked_balance,
WithdrawReasons::except(WithdrawReason::TransactionPayment),
);
<StakeOf<T>>::insert(&who, locked_balance);
<VotesOf<T>>::insert(&who, votes);

Voting::<T>::insert(&who, (locked_balance, votes));
}

/// Remove `origin` as a voter. This removes the lock and returns the bond.
Expand Down Expand Up @@ -522,7 +540,7 @@ impl<T: Trait> Module<T> {
///
/// State: O(1).
fn is_voter(who: &T::AccountId) -> bool {
<StakeOf<T>>::contains_key(who)
Voting::<T>::contains_key(who)
}

/// Check if `who` is currently an active member.
Expand Down Expand Up @@ -585,8 +603,7 @@ impl<T: Trait> Module<T> {
/// lock. Optionally, it would also return the reserved voting bond if indicated by `unreserve`.
fn do_remove_voter(who: &T::AccountId, unreserve: bool) {
// remove storage and lock.
<VotesOf<T>>::remove(who);
<StakeOf<T>>::remove(who);
Voting::<T>::remove(who);
T::Currency::remove_lock(MODULE_ID, who);

if unreserve {
Expand All @@ -596,7 +613,12 @@ impl<T: Trait> Module<T> {

/// The locked stake of a voter.
fn locked_stake_of(who: &T::AccountId) -> BalanceOf<T> {
Self::stake_of(who)
Voting::<T>::get(who).0
}

/// The locked stake of a voter.
fn votes_of(who: &T::AccountId) -> Vec<T::AccountId> {
Voting::<T>::get(who).1
}

/// Check there's nothing to do this block.
Expand Down Expand Up @@ -627,7 +649,7 @@ impl<T: Trait> Module<T> {
let num_to_elect = desired_runners_up + desired_seats;

let mut candidates = Self::candidates();
// candidates who explicitly called `submit_candidacy`. Only these folks are at the risk of
// candidates who explicitly called `submit_candidacy`. Only these folks are at risk of
// losing their bond.
let exposed_candidates = candidates.clone();
// current members are always a candidate for the next round as well.
Expand All @@ -636,15 +658,14 @@ impl<T: Trait> Module<T> {
// previous runners_up are also always candidates for the next round.
candidates.append(&mut Self::runners_up_ids());

let voters_and_votes = VotesOf::<T>::iter()
.map(|(v, i)| (v, i))
.collect::<Vec<(T::AccountId, Vec<T::AccountId>)>>();
let maybe_phragmen_result = sp_phragmen::elect::<_, _, _, T::CurrencyToVote, Perbill>(
let voters_and_votes = Voting::<T>::iter()
.map(|(voter, (stake, targets))| { (voter, stake, targets) })
.collect::<Vec<_>>();
let maybe_phragmen_result = sp_phragmen::elect::<_, _, T::CurrencyToVote, Perbill>(
num_to_elect,
0,
candidates,
voters_and_votes,
Self::locked_stake_of,
voters_and_votes.clone(),
);

if let Some(phragmen_result) = maybe_phragmen_result {
Expand Down Expand Up @@ -689,12 +710,24 @@ impl<T: Trait> Module<T> {
// split new set into winners and runners up.
let split_point = desired_seats.min(new_set_with_stake.len());
let mut new_members = (&new_set_with_stake[..split_point]).to_vec();
let most_popular = new_members.first().map(|x| x.0.clone());

// save the runners up as-is. They are sorted based on desirability.
// sort and save the members.
new_members.sort_by(|i, j| i.0.cmp(&j.0));

let mut prime_votes: Vec<_> = new_members.iter().map(|c| (&c.0, BalanceOf::<T>::zero())).collect();
for (_, stake, targets) in voters_and_votes.into_iter() {
for (votes, who) in targets.iter()
.enumerate()
.map(|(votes, who)| ((MAXIMUM_VOTE - votes) as u32, who))
{
if let Ok(i) = prime_votes.binary_search_by_key(&who, |k| k.0) {
prime_votes[i].1 += stake * votes.into();
}
}
}
let prime = prime_votes.into_iter().max_by_key(|x| x.1).map(|x| x.0.clone());

// new_members_ids is sorted by account id.
let new_members_ids = new_members
.iter()
Expand Down Expand Up @@ -722,7 +755,7 @@ impl<T: Trait> Module<T> {
&outgoing.clone(),
&new_members_ids,
);
T::ChangeMembers::set_prime(most_popular);
T::ChangeMembers::set_prime(prime);

// outgoing candidates lose their bond.
let mut to_burn_bond = outgoing.to_vec();
Expand Down Expand Up @@ -1012,7 +1045,7 @@ mod tests {
}

fn all_voters() -> Vec<u64> {
<VotesOf<Test>>::iter().map(|(v, _)| v).collect::<Vec<u64>>()
Voting::<Test>::iter().map(|(v, _)| v).collect::<Vec<u64>>()
}

fn balances(who: &u64) -> (u64, u64) {
Expand Down Expand Up @@ -1231,13 +1264,13 @@ mod tests {

assert_eq!(balances(&2), (18, 2));
assert_eq!(has_lock(&2), 20);
assert_eq!(Elections::stake_of(2), 20);
assert_eq!(Elections::locked_stake_of(&2), 20);

// can update; different stake; different lock and reserve.
assert_ok!(Elections::vote(Origin::signed(2), vec![5, 4], 15));
assert_eq!(balances(&2), (18, 2));
assert_eq!(has_lock(&2), 15);
assert_eq!(Elections::stake_of(2), 15);
assert_eq!(Elections::locked_stake_of(&2), 15);
});
}

Expand Down Expand Up @@ -1293,6 +1326,31 @@ mod tests {
});
}

#[test]
fn prime_votes_for_exiting_members_are_removed() {
ExtBuilder::default().build().execute_with(|| {
assert_ok!(Elections::submit_candidacy(Origin::signed(3)));
assert_ok!(Elections::submit_candidacy(Origin::signed(4)));
assert_ok!(Elections::submit_candidacy(Origin::signed(5)));

assert_ok!(Elections::vote(Origin::signed(1), vec![4, 3], 10));
assert_ok!(Elections::vote(Origin::signed(2), vec![4], 20));
assert_ok!(Elections::vote(Origin::signed(3), vec![3], 30));
assert_ok!(Elections::vote(Origin::signed(4), vec![4], 40));
assert_ok!(Elections::vote(Origin::signed(5), vec![5], 50));

assert_ok!(Elections::renounce_candidacy(Origin::signed(4)));

System::set_block_number(5);
assert_ok!(Elections::end_block(System::block_number()));

assert_eq!(Elections::members_ids(), vec![3, 5]);
assert_eq!(Elections::candidates(), vec![]);

assert_eq!(PRIME.with(|p| *p.borrow()), Some(5));
});
}

#[test]
fn cannot_vote_for_more_than_candidates() {
ExtBuilder::default().build().execute_with(|| {
Expand Down Expand Up @@ -1327,7 +1385,7 @@ mod tests {

assert_ok!(Elections::vote(Origin::signed(2), vec![4, 5], 30));
// you can lie but won't get away with it.
assert_eq!(Elections::stake_of(2), 20);
assert_eq!(Elections::locked_stake_of(&2), 20);
assert_eq!(has_lock(&2), 20);
});
}
Expand All @@ -1341,16 +1399,16 @@ mod tests {
assert_ok!(Elections::vote(Origin::signed(3), vec![5], 30));

assert_eq_uvec!(all_voters(), vec![2, 3]);
assert_eq!(Elections::stake_of(2), 20);
assert_eq!(Elections::stake_of(3), 30);
assert_eq!(Elections::votes_of(2), vec![5]);
assert_eq!(Elections::votes_of(3), vec![5]);
assert_eq!(Elections::locked_stake_of(&2), 20);
assert_eq!(Elections::locked_stake_of(&3), 30);
assert_eq!(Elections::votes_of(&2), vec![5]);
assert_eq!(Elections::votes_of(&3), vec![5]);

assert_ok!(Elections::remove_voter(Origin::signed(2)));

assert_eq_uvec!(all_voters(), vec![3]);
assert_eq!(Elections::votes_of(2), vec![]);
assert_eq!(Elections::stake_of(2), 0);
assert_eq!(Elections::votes_of(&2), vec![]);
assert_eq!(Elections::locked_stake_of(&2), 0);

assert_eq!(balances(&2), (20, 0));
assert_eq!(Balances::locks(&2).len(), 0);
Expand Down Expand Up @@ -1521,9 +1579,9 @@ mod tests {

assert_eq_uvec!(all_voters(), vec![2, 3, 4]);

assert_eq!(Elections::votes_of(2), vec![5]);
assert_eq!(Elections::votes_of(3), vec![3]);
assert_eq!(Elections::votes_of(4), vec![4]);
assert_eq!(Elections::votes_of(&2), vec![5]);
assert_eq!(Elections::votes_of(&3), vec![3]);
assert_eq!(Elections::votes_of(&4), vec![4]);

assert_eq!(Elections::candidates(), vec![3, 4, 5]);
assert_eq!(<Candidates<Test>>::decode_len().unwrap(), 3);
Expand Down
12 changes: 7 additions & 5 deletions frame/staking/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1810,11 +1810,11 @@ impl<T: Trait> Module<T> {
///
/// Assumes storage is coherent with the declaration.
fn select_validators(current_era: EraIndex) -> Option<Vec<T::AccountId>> {
let mut all_nominators: Vec<(T::AccountId, Vec<T::AccountId>)> = Vec::new();
let mut all_nominators: Vec<(T::AccountId, BalanceOf<T>, Vec<T::AccountId>)> = Vec::new();
let mut all_validators_and_prefs = BTreeMap::new();
let mut all_validators = Vec::new();
for (validator, preference) in <Validators<T>>::iter() {
let self_vote = (validator.clone(), vec![validator.clone()]);
let self_vote = (validator.clone(), Self::slashable_balance_of(&validator), vec![validator.clone()]);
all_nominators.push(self_vote);
all_validators_and_prefs.insert(validator.clone(), preference);
all_validators.push(validator);
Expand All @@ -1834,14 +1834,16 @@ impl<T: Trait> Module<T> {

(nominator, targets)
});
all_nominators.extend(nominator_votes);
all_nominators.extend(nominator_votes.map(|(n, ns)| {
let s = Self::slashable_balance_of(&n);
(n, s, ns)
}));

let maybe_phragmen_result = sp_phragmen::elect::<_, _, _, T::CurrencyToVote, Perbill>(
let maybe_phragmen_result = sp_phragmen::elect::<_, _, T::CurrencyToVote, Perbill>(
Self::validator_count() as usize,
Self::minimum_validator_count().max(1) as usize,
all_validators,
all_nominators,
Self::slashable_balance_of,
);

if let Some(phragmen_result) = maybe_phragmen_result {
Expand Down
9 changes: 9 additions & 0 deletions frame/support/src/storage/migration.rs
Original file line number Diff line number Diff line change
Expand Up @@ -185,3 +185,12 @@ pub fn remove_storage_prefix(module: &[u8], item: &[u8], hash: &[u8]) {
key[32..].copy_from_slice(hash);
frame_support::storage::unhashed::kill_prefix(&key)
}

/// Get a particular value in storage by the `module`, the map's `item` name and the key `hash`.
pub fn take_storage_item<K: Encode + Sized, T: Decode + Sized, H: StorageHasher>(
module: &[u8],
item: &[u8],
key: K,
) -> Option<T> {
take_storage_value(module, item, key.using_encoded(H::hash).as_ref())
}
9 changes: 3 additions & 6 deletions primitives/phragmen/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -149,16 +149,14 @@ pub type SupportMap<A> = BTreeMap<A, Support<A>>;
/// 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<AccountId, Balance, FS, C, R>(
pub fn elect<AccountId, Balance, C, R>(
candidate_count: usize,
minimum_candidate_count: usize,
initial_candidates: Vec<AccountId>,
initial_voters: Vec<(AccountId, Vec<AccountId>)>,
stake_of: FS,
initial_voters: Vec<(AccountId, Balance, Vec<AccountId>)>,
) -> Option<PhragmenResult<AccountId, R>> where
AccountId: Default + Ord + Member,
Balance: Default + Copy + AtLeast32Bit,
for<'r> FS: Fn(&'r AccountId) -> Balance,
C: Convert<Balance, u64> + Convert<u128, Balance>,
R: PerThing,
{
Expand Down Expand Up @@ -191,8 +189,7 @@ pub fn elect<AccountId, Balance, FS, C, R>(

// 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);
voters.extend(initial_voters.into_iter().map(|(who, voter_stake, votes)| {
let mut edges: Vec<Edge<AccountId>> = Vec::with_capacity(votes.len());
for v in votes {
if let Some(idx) = c_idx_cache.get(&v) {
Expand Down
5 changes: 2 additions & 3 deletions primitives/phragmen/src/mock.rs
Original file line number Diff line number Diff line change
Expand Up @@ -335,12 +335,11 @@ pub(crate) fn run_and_compare(
min_to_elect: usize,
) {
// run fixed point code.
let PhragmenResult { winners, assignments } = elect::<_, _, _, TestCurrencyToVote, Perbill>(
let PhragmenResult { winners, assignments } = elect::<_, _, TestCurrencyToVote, Perbill>(
to_elect,
min_to_elect,
candidates.clone(),
voters.clone(),
&stake_of,
voters.iter().map(|(ref v, ref vs)| (v.clone(), stake_of(v), vs.clone())).collect::<Vec<_>>(),
).unwrap();

// run float poc code.
Expand Down
Loading