Skip to content
This repository was archived by the owner on Nov 15, 2023. It is now read-only.

Commit 6a504b0

Browse files
kianenigmagpestana
andauthored
Configurable voting-degree in council elections pallet (#13305)
* configurable council elections pallet * configurable council elections pallet * add warning * reduce sizes * ".git/.scripts/commands/bench/bench.sh" pallet dev pallet_elections_phragmen * fix stuff * make assert * fix docs * fix docs again * fix docs again * Update frame/elections-phragmen/src/lib.rs Co-authored-by: Gonçalo Pestana <[email protected]> * Update frame/elections-phragmen/src/lib.rs Co-authored-by: Gonçalo Pestana <[email protected]> * Update frame/elections-phragmen/src/lib.rs Co-authored-by: Gonçalo Pestana <[email protected]> * fix docs --------- Co-authored-by: command-bot <> Co-authored-by: Gonçalo Pestana <[email protected]>
1 parent 2a636e2 commit 6a504b0

File tree

5 files changed

+216
-240
lines changed

5 files changed

+216
-240
lines changed

bin/node/runtime/src/lib.rs

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1003,8 +1003,9 @@ parameter_types! {
10031003
pub const TermDuration: BlockNumber = 7 * DAYS;
10041004
pub const DesiredMembers: u32 = 13;
10051005
pub const DesiredRunnersUp: u32 = 7;
1006-
pub const MaxVoters: u32 = 10 * 1000;
1007-
pub const MaxCandidates: u32 = 1000;
1006+
pub const MaxVotesPerVoter: u32 = 16;
1007+
pub const MaxVoters: u32 = 512;
1008+
pub const MaxCandidates: u32 = 64;
10081009
pub const ElectionsPhragmenPalletId: LockIdentifier = *b"phrelect";
10091010
}
10101011

@@ -1029,6 +1030,7 @@ impl pallet_elections_phragmen::Config for Runtime {
10291030
type DesiredRunnersUp = DesiredRunnersUp;
10301031
type TermDuration = TermDuration;
10311032
type MaxVoters = MaxVoters;
1033+
type MaxVotesPerVoter = MaxVotesPerVoter;
10321034
type MaxCandidates = MaxCandidates;
10331035
type WeightInfo = pallet_elections_phragmen::weights::SubstrateWeight<Runtime>;
10341036
}

frame/elections-phragmen/src/benchmarking.rs

Lines changed: 13 additions & 75 deletions
Original file line numberDiff line numberDiff line change
@@ -148,7 +148,7 @@ fn clean<T: Config>() {
148148
benchmarks! {
149149
// -- Signed ones
150150
vote_equal {
151-
let v in 1 .. (MAXIMUM_VOTE as u32);
151+
let v in 1 .. T::MaxVotesPerVoter::get();
152152
clean::<T>();
153153

154154
// create a bunch of candidates.
@@ -168,7 +168,7 @@ benchmarks! {
168168
}: vote(RawOrigin::Signed(caller), votes, stake)
169169

170170
vote_more {
171-
let v in 2 .. (MAXIMUM_VOTE as u32);
171+
let v in 2 .. T::MaxVotesPerVoter::get();
172172
clean::<T>();
173173

174174
// create a bunch of candidates.
@@ -190,7 +190,7 @@ benchmarks! {
190190
}: vote(RawOrigin::Signed(caller), votes, stake / <BalanceOf<T>>::from(10u32))
191191

192192
vote_less {
193-
let v in 2 .. (MAXIMUM_VOTE as u32);
193+
let v in 2 .. T::MaxVotesPerVoter::get();
194194
clean::<T>();
195195

196196
// create a bunch of candidates.
@@ -212,7 +212,7 @@ benchmarks! {
212212

213213
remove_voter {
214214
// we fix the number of voted candidates to max
215-
let v = MAXIMUM_VOTE as u32;
215+
let v = T::MaxVotesPerVoter::get();
216216
clean::<T>();
217217

218218
// create a bunch of candidates.
@@ -368,7 +368,7 @@ benchmarks! {
368368
clean::<T>();
369369

370370
let all_candidates = submit_candidates::<T>(T::MaxCandidates::get(), "candidates")?;
371-
distribute_voters::<T>(all_candidates, v, MAXIMUM_VOTE)?;
371+
distribute_voters::<T>(all_candidates, v, T::MaxVotesPerVoter::get() as usize)?;
372372

373373
// all candidates leave.
374374
<Candidates<T>>::kill();
@@ -389,17 +389,17 @@ benchmarks! {
389389
// that we give all candidates a self vote to make sure they are all considered.
390390
let c in 1 .. T::MaxCandidates::get();
391391
let v in 1 .. T::MaxVoters::get();
392-
let e in (T::MaxVoters::get()) .. T::MaxVoters::get() as u32 * MAXIMUM_VOTE as u32;
392+
let e in (T::MaxVoters::get()) .. T::MaxVoters::get() * T::MaxVotesPerVoter::get();
393393
clean::<T>();
394394

395395
// so we have a situation with v and e. we want e to basically always be in the range of `e
396-
// -> e * MAXIMUM_VOTE`, but we cannot express that now with the benchmarks. So what we do
397-
// is: when c is being iterated, v, and e are max and fine. when v is being iterated, e is
398-
// being set to max and this is a problem. In these cases, we cap e to a lower value, namely
399-
// v * MAXIMUM_VOTE. when e is being iterated, v is at max, and again fine. all in all,
400-
// votes_per_voter can never be more than MAXIMUM_VOTE. Note that this might cause `v` to be
401-
// an overestimate.
402-
let votes_per_voter = (e / v).min(MAXIMUM_VOTE as u32);
396+
// -> e * T::MaxVotesPerVoter::get()`, but we cannot express that now with the benchmarks.
397+
// So what we do is: when c is being iterated, v, and e are max and fine. when v is being
398+
// iterated, e is being set to max and this is a problem. In these cases, we cap e to a
399+
// lower value, namely v * T::MaxVotesPerVoter::get(). when e is being iterated, v is at
400+
// max, and again fine. all in all, votes_per_voter can never be more than
401+
// T::MaxVotesPerVoter::get(). Note that this might cause `v` to be an overestimate.
402+
let votes_per_voter = (e / v).min(T::MaxVotesPerVoter::get());
403403

404404
let all_candidates = submit_candidates_with_self_vote::<T>(c, "candidates")?;
405405
let _ = distribute_voters::<T>(all_candidates, v.saturating_sub(c), votes_per_voter as usize)?;
@@ -421,68 +421,6 @@ benchmarks! {
421421
}
422422
}
423423

424-
#[extra]
425-
election_phragmen_c_e {
426-
let c in 1 .. T::MaxCandidates::get();
427-
let e in (T::MaxVoters::get()) .. T::MaxVoters::get() * MAXIMUM_VOTE as u32;
428-
let fixed_v = T::MaxVoters::get();
429-
clean::<T>();
430-
431-
let votes_per_voter = e / fixed_v;
432-
433-
let all_candidates = submit_candidates_with_self_vote::<T>(c, "candidates")?;
434-
let _ = distribute_voters::<T>(
435-
all_candidates,
436-
fixed_v - c,
437-
votes_per_voter as usize,
438-
)?;
439-
}: {
440-
<Elections<T>>::on_initialize(T::TermDuration::get());
441-
}
442-
verify {
443-
assert_eq!(<Elections<T>>::members().len() as u32, T::DesiredMembers::get().min(c));
444-
assert_eq!(
445-
<Elections<T>>::runners_up().len() as u32,
446-
T::DesiredRunnersUp::get().min(c.saturating_sub(T::DesiredMembers::get())),
447-
);
448-
449-
#[cfg(test)]
450-
{
451-
// reset members in between benchmark tests.
452-
use crate::tests::MEMBERS;
453-
MEMBERS.with(|m| *m.borrow_mut() = vec![]);
454-
}
455-
}
456-
457-
#[extra]
458-
election_phragmen_v {
459-
let v in 4 .. 16;
460-
let fixed_c = T::MaxCandidates::get() / 10;
461-
let fixed_e = 64;
462-
clean::<T>();
463-
464-
let votes_per_voter = fixed_e / v;
465-
466-
let all_candidates = submit_candidates_with_self_vote::<T>(fixed_c, "candidates")?;
467-
let _ = distribute_voters::<T>(all_candidates, v, votes_per_voter as usize)?;
468-
}: {
469-
<Elections<T>>::on_initialize(T::TermDuration::get());
470-
}
471-
verify {
472-
assert_eq!(<Elections<T>>::members().len() as u32, T::DesiredMembers::get().min(fixed_c));
473-
assert_eq!(
474-
<Elections<T>>::runners_up().len() as u32,
475-
T::DesiredRunnersUp::get().min(fixed_c.saturating_sub(T::DesiredMembers::get())),
476-
);
477-
478-
#[cfg(test)]
479-
{
480-
// reset members in between benchmark tests.
481-
use crate::tests::MEMBERS;
482-
MEMBERS.with(|m| *m.borrow_mut() = vec![]);
483-
}
484-
}
485-
486424
impl_benchmark_test_suite!(
487425
Elections,
488426
crate::tests::ExtBuilder::default().desired_members(13).desired_runners_up(7),

frame/elections-phragmen/src/lib.rs

Lines changed: 65 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -43,14 +43,14 @@
4343
//! ### Voting
4444
//!
4545
//! Voters can vote for a limited number of the candidates by providing a list of account ids,
46-
//! bounded by [`MAXIMUM_VOTE`]. Invalid votes (voting for non-candidates) and duplicate votes are
47-
//! ignored during election. Yet, a voter _might_ vote for a future candidate. Voters reserve a bond
48-
//! as they vote. Each vote defines a `value`. This amount is locked from the account of the voter
49-
//! and indicates the weight of the vote. Voters can update their votes at any time by calling
50-
//! `vote()` again. This can update the vote targets (which might update the deposit) or update the
51-
//! vote's stake ([`Voter::stake`]). After a round, votes are kept and might still be valid for
52-
//! further rounds. A voter is responsible for calling `remove_voter` once they are done to have
53-
//! their bond back and remove the lock.
46+
//! bounded by [`Config::MaxVotesPerVoter`]. Invalid votes (voting for non-candidates) and duplicate
47+
//! votes are ignored during election. Yet, a voter _might_ vote for a future candidate. Voters
48+
//! reserve a bond as they vote. Each vote defines a `value`. This amount is locked from the account
49+
//! of the voter and indicates the weight of the vote. Voters can update their votes at any time by
50+
//! calling `vote()` again. This can update the vote targets (which might update the deposit) or
51+
//! update the vote's stake ([`Voter::stake`]). After a round, votes are kept and might still be
52+
//! valid for further rounds. A voter is responsible for calling `remove_voter` once they are done
53+
//! to have their bond back and remove the lock.
5454
//!
5555
//! See [`Call::vote`], [`Call::remove_voter`].
5656
//!
@@ -124,9 +124,6 @@ pub mod migrations;
124124

125125
const LOG_TARGET: &str = "runtime::elections-phragmen";
126126

127-
/// The maximum votes allowed per voter.
128-
pub const MAXIMUM_VOTE: usize = 16;
129-
130127
type BalanceOf<T> =
131128
<<T as Config>::Currency as Currency<<T as frame_system::Config>::AccountId>>::Balance;
132129
type NegativeImbalanceOf<T> = <<T as Config>::Currency as Currency<
@@ -254,19 +251,29 @@ pub mod pallet {
254251

255252
/// The maximum number of candidates in a phragmen election.
256253
///
257-
/// Warning: The election happens onchain, and this value will determine
258-
/// the size of the election. When this limit is reached no more
259-
/// candidates are accepted in the election.
254+
/// Warning: This impacts the size of the election which is run onchain. Chose wisely, and
255+
/// consider how it will impact `T::WeightInfo::election_phragmen`.
256+
///
257+
/// When this limit is reached no more candidates are accepted in the election.
260258
#[pallet::constant]
261259
type MaxCandidates: Get<u32>;
262260

263261
/// The maximum number of voters to allow in a phragmen election.
264262
///
265-
/// Warning: This impacts the size of the election which is run onchain.
263+
/// Warning: This impacts the size of the election which is run onchain. Chose wisely, and
264+
/// consider how it will impact `T::WeightInfo::election_phragmen`.
265+
///
266266
/// When the limit is reached the new voters are ignored.
267267
#[pallet::constant]
268268
type MaxVoters: Get<u32>;
269269

270+
/// Maximum numbers of votes per voter.
271+
///
272+
/// Warning: This impacts the size of the election which is run onchain. Chose wisely, and
273+
/// consider how it will impact `T::WeightInfo::election_phragmen`.
274+
#[pallet::constant]
275+
type MaxVotesPerVoter: Get<u32>;
276+
270277
/// Weight information for extrinsics in this pallet.
271278
type WeightInfo: WeightInfo;
272279
}
@@ -284,6 +291,41 @@ pub mod pallet {
284291
Weight::zero()
285292
}
286293
}
294+
295+
fn integrity_test() {
296+
let block_weight = T::BlockWeights::get().max_block;
297+
// mind the order.
298+
let election_weight = T::WeightInfo::election_phragmen(
299+
T::MaxCandidates::get(),
300+
T::MaxVoters::get(),
301+
T::MaxVotesPerVoter::get() * T::MaxVoters::get(),
302+
);
303+
304+
let to_seconds = |w: &Weight| {
305+
w.ref_time() as f32 /
306+
frame_support::weights::constants::WEIGHT_REF_TIME_PER_SECOND as f32
307+
};
308+
309+
frame_support::log::debug!(
310+
target: LOG_TARGET,
311+
"election weight {}s ({:?}) // chain's block weight {}s ({:?})",
312+
to_seconds(&election_weight),
313+
election_weight,
314+
to_seconds(&block_weight),
315+
block_weight,
316+
);
317+
assert!(
318+
election_weight.all_lt(block_weight),
319+
"election weight {}s ({:?}) will exceed a {}s chain's block weight ({:?}) (MaxCandidates {}, MaxVoters {}, MaxVotesPerVoter {} -- tweak these parameters)",
320+
election_weight,
321+
to_seconds(&election_weight),
322+
to_seconds(&block_weight),
323+
block_weight,
324+
T::MaxCandidates::get(),
325+
T::MaxVoters::get(),
326+
T::MaxVotesPerVoter::get(),
327+
);
328+
}
287329
}
288330

289331
#[pallet::call]
@@ -307,10 +349,6 @@ pub mod pallet {
307349
///
308350
/// It is the responsibility of the caller to **NOT** place all of their balance into the
309351
/// lock and keep some for further operations.
310-
///
311-
/// # <weight>
312-
/// We assume the maximum weight among all 3 cases: vote_equal, vote_more and vote_less.
313-
/// # </weight>
314352
#[pallet::call_index(0)]
315353
#[pallet::weight(
316354
T::WeightInfo::vote_more(votes.len() as u32)
@@ -324,8 +362,10 @@ pub mod pallet {
324362
) -> DispatchResultWithPostInfo {
325363
let who = ensure_signed(origin)?;
326364

327-
// votes should not be empty and more than `MAXIMUM_VOTE` in any case.
328-
ensure!(votes.len() <= MAXIMUM_VOTE, Error::<T>::MaximumVotesExceeded);
365+
ensure!(
366+
votes.len() <= T::MaxVotesPerVoter::get() as usize,
367+
Error::<T>::MaximumVotesExceeded
368+
);
329369
ensure!(!votes.is_empty(), Error::<T>::NoVotes);
330370

331371
let candidates_count = <Candidates<T>>::decode_len().unwrap_or(0);
@@ -1006,15 +1046,15 @@ impl<T: Config> Pallet<T> {
10061046
// count](https://en.wikipedia.org/wiki/Borda_count). We weigh everyone's vote for
10071047
// that new member by a multiplier based on the order of the votes. i.e. the
10081048
// first person a voter votes for gets a 16x multiplier, the next person gets a
1009-
// 15x multiplier, an so on... (assuming `MAXIMUM_VOTE` = 16)
1049+
// 15x multiplier, an so on... (assuming `T::MaxVotesPerVoter` = 16)
10101050
let mut prime_votes = new_members_sorted_by_id
10111051
.iter()
10121052
.map(|c| (&c.0, BalanceOf::<T>::zero()))
10131053
.collect::<Vec<_>>();
10141054
for (_, stake, votes) in voters_and_stakes.into_iter() {
10151055
for (vote_multiplier, who) in
10161056
votes.iter().enumerate().map(|(vote_position, who)| {
1017-
((MAXIMUM_VOTE - vote_position) as u32, who)
1057+
((T::MaxVotesPerVoter::get() as usize - vote_position) as u32, who)
10181058
}) {
10191059
if let Ok(i) = prime_votes.binary_search_by_key(&who, |k| k.0) {
10201060
prime_votes[i].1 = prime_votes[i]
@@ -1173,16 +1213,9 @@ mod tests {
11731213
};
11741214
use substrate_test_utils::assert_eq_uvec;
11751215

1176-
parameter_types! {
1177-
pub BlockWeights: frame_system::limits::BlockWeights =
1178-
frame_system::limits::BlockWeights::simple_max(
1179-
frame_support::weights::Weight::from_ref_time(1024).set_proof_size(u64::MAX),
1180-
);
1181-
}
1182-
11831216
impl frame_system::Config for Test {
11841217
type BaseCallFilter = frame_support::traits::Everything;
1185-
type BlockWeights = BlockWeights;
1218+
type BlockWeights = ();
11861219
type BlockLength = ();
11871220
type DbWeight = ();
11881221
type RuntimeOrigin = RuntimeOrigin;
@@ -1297,6 +1330,7 @@ mod tests {
12971330
type KickedMember = ();
12981331
type WeightInfo = ();
12991332
type MaxVoters = PhragmenMaxVoters;
1333+
type MaxVotesPerVoter = ConstU32<16>;
13001334
type MaxCandidates = PhragmenMaxCandidates;
13011335
}
13021336

0 commit comments

Comments
 (0)