From 6956c612b89e3f56f809716c86e025a362794221 Mon Sep 17 00:00:00 2001 From: Gav Date: Tue, 27 Feb 2018 23:31:27 +0100 Subject: [PATCH] Design of the approval voting module for council election. --- substrate/demo/runtime/src/runtime/council.rs | 272 ++++++++++++++++++ .../demo/runtime/src/runtime/democracy.rs | 6 +- 2 files changed, 275 insertions(+), 3 deletions(-) create mode 100644 substrate/demo/runtime/src/runtime/council.rs diff --git a/substrate/demo/runtime/src/runtime/council.rs b/substrate/demo/runtime/src/runtime/council.rs new file mode 100644 index 0000000000..d82b1d902f --- /dev/null +++ b/substrate/demo/runtime/src/runtime/council.rs @@ -0,0 +1,272 @@ +// Copyright 2017 Parity Technologies (UK) Ltd. +// This file is part of Substrate Demo. + +// Substrate Demo 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 Demo 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 Demo. If not, see . + +//! Council system: Handles the voting in and maintenance of council members. + +use rstd::prelude::*; +use codec::KeyedVec; +use runtime_support::storage; +use demo_primitives::{Proposal, AccountId, Hash, BlockNumber}; +use runtime::{staking, system, session}; +use runtime::staking::Balance; + +// no polynomial attack: +// all public operations should be constant time. +// all protected operations may be at most O(public operations) + +// public operations: +// - express approval (ideally, express all approvals) +// - re-express approval (ideally, re-express all approvals) +// - clear all approvals +// - submit candidacy +// protected operations: +// - remove candidacy (remove all votes for a candidate) + +// for an approval vote of C councilers: + +// top K candidates are maintained between votes. all others are discarded. +// - candidate removed & bond returned when elected. +// - candidate removed & bond burned when discarded. + +// at the point that the vote ends, all voters' balances are snapshotted. + +// for B blocks following, there's a counting period whereby each of the candidates that believe +// they fall in the top K+C voted can present themselves. they get the total stake +// recorded (based on the snapshot); an ordered list is maintained. Noone may present themselves +// that, if elected, would result in being included twice on the council (important since existing +// councilers will will have their approval votes as it may be that they don't get removed), nor +// if existing presenters would mean they're not in the top K+C. + +// following B blocks, the top C+K that presented themselves have their bond returned and the +// top C candidates are elected. the top C candidates have their bond returned. + +// the top C candidates and all other candidates beyond the top C+K are cleared and the clearing +// mask is appended to mask list (ready to be applied to vote arrays). + +// vote-clearing happens lazily, with previous write also storing the round and all subsequent +// rounds' operations applied at the next read/write time. + +// vote buffer size increases as required. +// bond taken for initial approval, returned when clearing. + +// vec (list of addresses, each bonded, can contain "holes" of null addresses). Order important. +// vec (all voters. order unimportant - just need to be enumerable.) +// voter -> (last round, Vec) + +const CURRENT_VOTE: &[u8] = b"cou:cur"; +const APPROVALS_OF: &[u8] = b"cou:apr:"; +const VOTERS: &[u8] = b"cou:vrs"; +const CANDIDATES: &[u8] = b"cou:can"; + +const CANDIDACY_BOND: &[u8] = b"cou:cbo"; +const VOTING_BOND: &[u8] = b"cou:vbo"; + +const CARRY_COUNT: &[u8] = b"cou:cco"; +const PRESENTATION_DURATION: &[u8] = b"cou:pdu"; + +const WINNERS: &[u8] = b"cou:win"; + +/// How much should be locked up in order to submit one's candidacy. +pub fn candidacy_bond() -> Balance { + unimplemented!(); +} + +/// How much should be locked up in order to be able to submit votes. +pub fn voting_bond() -> Balance { + unimplemented!(); +} + +/// How long to give each top candidate to present themselves after the vote ends. +pub fn presentation_duration() -> BlockNumber { + unimplemented!(); +} + +/// How many runners-up should have their approvals persist until the next vote. +pub fn carry_count() -> u32 { + unimplemented!(); +} + +/// The current council. When there's a vote going on, this should still be used for executive +/// matters. +pub fn active_council() -> Vec { + unimplemented!(); +} + +/// The information on the current vote: +/// - The block number where voting will end; +/// - The specific council members to be replaced. +pub fn current_vote() -> Option<(BlockNumber, Vec)> { + unimplemented!(); +} + +/// The total number of votes that have happened or are in progress. +pub fn vote_index() -> VoteIndex { + unimplemented!(); +} + +/// The queue of candidate indices that will be cleared. +pub fn candidate_clear_queue() -> { + unimplemented!(); +} + +/// The last cleared vote index that this voter was last active at. +pub fn voter_last_active(voter: &AddressId) -> VoteIndex { + unimplemented!(); +} + +pub mod public { + use super::*; + + /// Remove a voter. For it not to panic, the combination of candidate_clear_queue from the + /// when it was last active until the penultimate vote should result in no approvals. + /// + /// May be called by anyone. Returns the voter deposit to `signed`. + pub fn kill_inactive_voter(signed: &AccountId, who: &AddressId) { + unimplemented!(); + } + + /// Remove a voter. All votes are cancelled and the voter deposit is returned. + pub fn retract_voter(signed: AccountId) { + unimplemented!(); + } + + /// Submit oneself for candidacy. + /// + /// Account must have enough cash in it to pay the bond. + pub fn submit_candidacy(signed: &AccountId) { + unimplemented!(); + } + + /// Claim that `signed` is one of the top carry_count() + current_vote().1 candidates. + /// Only works if the block number >= current_vote().0 and < current_vote().0 + presentation_duration() + pub fn present(signed: &AccountId) { + unimplemented!(); + } +} + +pub mod privileged { + use super::*; + + /// Set the desired member count; if lower than the current count, then seats will not be up + /// election when they expire. If more, then a new vote will be started if one is not already + /// in progress. + pub fn set_desired_seats(count: u32) { + unimplemented!(); + } + + /// Remove a particular member. A new vote will be started if one is not already in progress. + /// This is effective immediately. + pub fn remove_member(who: AddressId) { + unimplemented!(); + } +} + +pub mod internal { + use super::*; + use demo_primitives::Proposal; + use dispatch::enact_proposal; + + /// Current era is ending; we should finish up any proposals. + pub fn end_block() { + if let Some((number, removals)) = current_vote() { + if system::block_number() == number { + close_voting(removals.len() as u32); + } + if system::block_number() == number + presentation_duration() { + finalise_vote(removals); + } + } + } +} + +/// Close the voting, snapshot the staking and the number of seats that are actually up for grabs. +fn close_voting(removal_count: u32) { + unimplemented!(); +} + +/// Finalise the vote, removing each of the `removals` and inserting `seats` of the most approved +/// candidates in their place. If the total council members is less than the desired membership +/// a new vote is started. +/// Clears all presented candidates, returning the bond of the elected ones. +fn finalise_vote(seats: u32, removals: Vec) { + unimplemented!(); +} + +#[cfg(test)] +mod tests { + use super::*; + use runtime_io::{with_externalities, twox_128, TestExternalities}; + use codec::{KeyedVec, Joiner}; + use keyring::Keyring; + use environment::with_env; + use demo_primitives::{AccountId, Proposal}; + use runtime::{staking, session, democracy}; + + fn new_test_ext() -> TestExternalities { + let alice = Keyring::Alice.to_raw_public(); + let bob = Keyring::Bob.to_raw_public(); + let charlie = Keyring::Charlie.to_raw_public(); + let dave = Keyring::Dave.to_raw_public(); + let eve = Keyring::Eve.to_raw_public(); + let ferdie = Keyring::Ferdie.to_raw_public(); + let one = Keyring::One.to_raw_public(); + + map![ + twox_128(b"ses:len").to_vec() => vec![].and(&1u64), + twox_128(b"ses:val:len").to_vec() => vec![].and(&3u32), + twox_128(&0u32.to_keyed_vec(b"ses:val:")).to_vec() => alice.to_vec(), + twox_128(&1u32.to_keyed_vec(b"ses:val:")).to_vec() => bob.to_vec(), + twox_128(&2u32.to_keyed_vec(b"ses:val:")).to_vec() => charlie.to_vec(), + twox_128(b"sta:wil:len").to_vec() => vec![].and(&3u32), + twox_128(&0u32.to_keyed_vec(b"sta:wil:")).to_vec() => alice.to_vec(), + twox_128(&1u32.to_keyed_vec(b"sta:wil:")).to_vec() => bob.to_vec(), + twox_128(&2u32.to_keyed_vec(b"sta:wil:")).to_vec() => charlie.to_vec(), + twox_128(&alice.to_keyed_vec(b"sta:bal:")).to_vec() => vec![].and(&10u64), + twox_128(&bob.to_keyed_vec(b"sta:bal:")).to_vec() => vec![].and(&20u64), + twox_128(&charlie.to_keyed_vec(b"sta:bal:")).to_vec() => vec![].and(&30u64), + twox_128(&dave.to_keyed_vec(b"sta:bal:")).to_vec() => vec![].and(&40u64), + twox_128(&eve.to_keyed_vec(b"sta:bal:")).to_vec() => vec![].and(&50u64), + twox_128(&ferdie.to_keyed_vec(b"sta:bal:")).to_vec() => vec![].and(&60u64), + twox_128(&one.to_keyed_vec(b"sta:bal:")).to_vec() => vec![].and(&1u64), + twox_128(b"sta:tot").to_vec() => vec![].and(&210u64), + twox_128(b"sta:spe").to_vec() => vec![].and(&1u64), + twox_128(b"sta:vac").to_vec() => vec![].and(&3u64), + twox_128(b"sta:era").to_vec() => vec![].and(&1u64) + ] + } + + #[test] + fn simple_passing_should_work() { + let alice = Keyring::Alice.to_raw_public(); + let mut t = new_test_ext(); + + with_externalities(&mut t, || { + assert_eq!(staking::era_length(), 1u64); + assert_eq!(staking::total_stake(), 210u64); + + with_env(|e| e.block_number = 1); + public::propose(&alice, &Proposal::StakingSetSessionsPerEra(2)); + public::vote(&alice, 1, true); + + assert_eq!(public::tally(), (10, 0)); + + democracy::internal::end_of_an_era(); + staking::internal::check_new_era(); + + assert_eq!(staking::era_length(), 2u64); + }); + } +} diff --git a/substrate/demo/runtime/src/runtime/democracy.rs b/substrate/demo/runtime/src/runtime/democracy.rs index 1e74b48fc3..fa27e7e020 100644 --- a/substrate/demo/runtime/src/runtime/democracy.rs +++ b/substrate/demo/runtime/src/runtime/democracy.rs @@ -23,9 +23,9 @@ use runtime_support::storage; use demo_primitives::{Proposal, AccountId, Hash, BlockNumber}; use runtime::{staking, system, session}; -const CURRENT_PROPOSAL: &[u8] = b"gov:pro"; -const VOTE_OF: &[u8] = b"gov:vot:"; -const VOTERS: &[u8] = b"gov:vtr:"; +const CURRENT_PROPOSAL: &[u8] = b"dem:pro"; +const VOTE_OF: &[u8] = b"dem:vot:"; +const VOTERS: &[u8] = b"dem:vtr:"; pub mod public { use super::*;