Traitify Runtime (#104)

* Factor out safe-mix and dispatch

* Refactor dispatch into something more modular.

* Fix wasm build.

* Fix up timestamp

* fix warnings.

* Borked timestamp example

* Fix build

* Timestamp as skeleton for traity runtime.

* New storage macro.

* Dispatch module has traity API.

* Move consensus module to new API

* Refactoring and outer dispatch

* Avoid unnecessary derives.

* Abstract the low-level half of system.

* nicer outer dispatch syntax.

* Make runtime compile again (albeit in a heavily simplified state)

* Reworking runtime and the upper levels of system.

* Initial reworking of runtime:

- Introduced executive module;
- Introduced trait primitives module;
- Provided an API endpoint.

* Expose an additional function in system

* Another couple of functions traitified in executive.

* another function in executive traitified.

* One more function traitified.

* Finish traitifying executive!

* Traitify session module.

* Cleanups and ensure session gets run.

* First part of traitification of staking module.

* Bit more of staking traitified.

* Additional stuff in staking. Fix up session.

* Penultimate part of staking module.

* Final part of staking (code)

* Update demo runtime to include staking.

* Final tweaks for staking integration.

* Remove old runtime files.

* Schedule staking.

* Minor fixes

* First bits of democracy.

* Democracy module integrated.

* Fix warning.

* Traitify and integrate council module

* Council voting.

* Runtime binary and tweaks.

* Binary update.

* Fix `*Type` grumble.

* Fix up genesis_map

* Remove NonTrivialSlicable

* Staking "test externalities" stuff along with refactor.

* Add session test externalities constructor

* Fixed executor tests.

* Make one test in executive module work.

* Remove test framework stuff into common module.

* Enable other tests in executive

* Session tests reinstated, minor refactoring of keyring.

* Fix staking tests.

* Fix up democracy tests.

* First few tests in council.

* Council tests reinstated :)

* Avoid hardcoding blake2 into Header.

* Fix last few tests.

* Make all primitives generic.

* Fix tests.

* Refactor runtime to remove genesismap.

* Streamline runtime more with macrofied config.

* Clean paths

* Fix warning.

* Consolidate demo runtime crate.

* Remove stale code.

* Refactor away dodgy trait.

* Add corresponding Aux type.

* Fixes

* Rename Digesty -> Digest

* Rename Headery -> Header

* Blocky -> Block

* Fix wasm build.

* kill warnings

* more docs

* minor cleanups
This commit is contained in:
Gav Wood
2018-04-04 12:06:39 +02:00
committed by GitHub
parent 3ec6d2dde6
commit bd066e27a6
92 changed files with 7890 additions and 5243 deletions
-27
View File
@@ -1,27 +0,0 @@
// 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 <http://www.gnu.org/licenses/>.
use runtime::{system, consensus, session};
impl_stubs!(
execute_block => |block| system::internal::execute_block(block),
initialise_block => |header| system::internal::initialise_block(&header),
execute_transaction => |utx| system::internal::execute_transaction(utx),
finalise_block => |()| system::internal::finalise_block(),
validator_count => |()| session::validator_count(),
validators => |()| session::validators(),
authorities => |()| consensus::authorities()
);
-52
View File
@@ -1,52 +0,0 @@
// 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 <http://www.gnu.org/licenses/>.
//! Block and header type definitions.
use rstd::prelude::*;
use codec::{Input, Slicable};
use transaction::UncheckedTransaction;
pub use demo_primitives::block::{Header, Digest, Log, Number, HeaderHash};
/// The block "body": A bunch of transactions.
pub type Body = Vec<UncheckedTransaction>;
/// A block on the chain.
#[derive(PartialEq, Eq, Clone)]
#[cfg_attr(feature = "std", derive(Serialize, Deserialize, Debug))]
pub struct Block {
/// The block header.
pub header: Header,
/// All relay-chain transactions.
pub transactions: Body,
}
impl Slicable for Block {
fn decode<I: Input>(input: &mut I) -> Option<Self> {
let (header, transactions) = Slicable::decode(input)?;
Some(Block { header, transactions })
}
fn encode(&self) -> Vec<u8> {
let mut v = Vec::new();
v.extend(self.header.encode());
v.extend(self.transactions.encode());
v
}
}
-246
View File
@@ -1,246 +0,0 @@
// 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 <http://www.gnu.org/licenses/>.
//! Dispatch system. Just dispatches calls.
use runtime::{staking, democracy};
pub use rstd::prelude::Vec;
pub use codec::{Slicable, Input, NonTrivialSlicable};
/// Implement a dispatch module to create a pairing of a dispatch trait and enum.
#[macro_export]
macro_rules! impl_dispatch {
(
pub mod $mod_name:ident;
$(
fn $fn_name:ident(
$(
$param_name:ident : $param:ty
),*
)
= $id:expr ;
)*
) => {
pub mod $mod_name {
use super::*;
#[derive(Clone, Copy, PartialEq, Eq)]
#[cfg_attr(feature = "std", derive(Serialize, Deserialize, Debug))]
#[repr(u32)]
#[allow(non_camel_case_types)]
enum Id {
$(
#[allow(non_camel_case_types)]
$fn_name = $id,
)*
}
impl Id {
/// Derive `Some` value from a `u8`, or `None` if it's invalid.
fn from_u8(value: u8) -> Option<Id> {
match value {
$(
$id => Some(Id::$fn_name),
)*
_ => None,
}
}
}
#[derive(Clone, PartialEq, Eq)]
#[cfg_attr(feature = "std", derive(Serialize, Deserialize, Debug))]
#[allow(missing_docs)]
pub enum Call {
$(
#[allow(non_camel_case_types)]
$fn_name ( $( $param ),* )
,)*
}
pub trait Dispatch: Sized {
$(
fn $fn_name (self, $( $param_name: $param ),* );
)*
}
impl Call {
pub fn dispatch<D: Dispatch>(self, d: D) {
match self {
$(
Call::$fn_name( $( $param_name ),* ) =>
d.$fn_name( $( $param_name ),* ),
)*
}
}
}
impl $crate::dispatch::Slicable for Call {
fn decode<I: $crate::dispatch::Input>(input: &mut I) -> Option<Self> {
let id = u8::decode(input).and_then(Id::from_u8)?;
Some(match id {
$(
Id::$fn_name => {
$(
let $param_name = $crate::dispatch::Slicable::decode(input)?;
)*
Call :: $fn_name( $( $param_name ),* )
}
)*
})
}
fn encode(&self) -> $crate::dispatch::Vec<u8> {
let mut v = $crate::dispatch::Vec::new();
match *self {
$(
Call::$fn_name(
$(
ref $param_name
),*
) => {
(Id::$fn_name as u8).using_encoded(|s| v.extend(s));
$(
$param_name.using_encoded(|s| v.extend(s));
)*
}
)*
}
v
}
fn using_encoded<R, F: FnOnce(&[u8]) -> R>(&self, f: F) -> R {
f(self.encode().as_slice())
}
}
impl $crate::dispatch::NonTrivialSlicable for Call {}
}
}
}
macro_rules! impl_meta_dispatch {
(
pub mod $super_name:ident;
path $path:ident;
trait $trait:ty;
$(
$camelcase:ident(mod $sub_name:ident) = $id:expr ;
)*
) => {
pub mod $super_name {
use super::*;
#[derive(Clone, Copy, PartialEq, Eq)]
#[cfg_attr(feature = "std", derive(Serialize, Deserialize, Debug))]
#[repr(u32)]
#[allow(non_camel_case_types)]
enum Id {
$(
#[allow(non_camel_case_types)]
$camelcase = $id,
)*
}
impl Id {
/// Derive `Some` value from a `u8`, or `None` if it's invalid.
fn from_u8(value: u8) -> Option<Id> {
match value {
$(
$id => Some(Id::$camelcase),
)*
_ => None,
}
}
}
#[derive(Clone, PartialEq, Eq)]
#[cfg_attr(feature = "std", derive(Serialize, Deserialize, Debug))]
#[allow(missing_docs)]
pub enum Call {
$(
#[allow(non_camel_case_types)]
$camelcase ( $crate::runtime::$sub_name::$path::Call )
,)*
}
impl Call {
pub fn dispatch(self, d: $trait) {
match self {
$(
Call::$camelcase(x) => x.dispatch(d),
)*
}
}
}
impl $crate::dispatch::Slicable for Call {
fn decode<I: $crate::dispatch::Input>(input: &mut I) -> Option<Self> {
let id = u8::decode(input).and_then(Id::from_u8)?;
Some(match id {
$(
Id::$camelcase =>
Call::$camelcase( $crate::dispatch::Slicable::decode(input)? ),
)*
})
}
fn encode(&self) -> Vec<u8> {
let mut v = $crate::dispatch::Vec::new();
match *self {
$(
Call::$camelcase( ref sub ) => {
(Id::$camelcase as u8).using_encoded(|s| v.extend(s));
sub.using_encoded(|s| v.extend(s));
}
)*
}
v
}
fn using_encoded<R, F: FnOnce(&[u8]) -> R>(&self, f: F) -> R {
f(self.encode().as_slice())
}
}
impl $crate::dispatch::NonTrivialSlicable for Call {}
}
}
}
impl_meta_dispatch! {
pub mod public;
path public;
trait staking::PublicPass;
Session(mod session) = 1;
Staking(mod staking) = 2;
Timestamp(mod timestamp) = 3;
Democracy(mod democracy) = 5;
Council(mod council) = 6;
CouncilVote(mod council) = 7;
}
impl_meta_dispatch! {
pub mod privileged;
path privileged;
trait democracy::PrivPass;
System(mod system) = 0;
Session(mod session) = 1;
Staking(mod staking) = 2;
Democracy(mod democracy) = 5;
Council(mod council) = 6;
CouncilVote(mod council) = 7;
}
pub use self::privileged::Call as PrivCall;
pub use self::public::Call as PubCall;
-129
View File
@@ -1,129 +0,0 @@
// 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 <http://www.gnu.org/licenses/>.
//! Tool for creating the genesis block.
use codec::{KeyedVec, Joiner};
use std::collections::HashMap;
use runtime_io::twox_128;
use runtime_support::{Hashable, StorageMap, StorageList, StorageValue};
use primitives::Block;
use demo_primitives::{BlockNumber, AccountId};
use runtime::staking::Balance;
use runtime::{staking, session, consensus, system, democracy, council, council_vote};
/// Configuration of a general Substrate Demo genesis block.
pub struct GenesisConfig {
pub validators: Vec<AccountId>,
pub authorities: Vec<AccountId>,
pub balances: Vec<(AccountId, Balance)>,
pub block_time: u64,
pub session_length: BlockNumber,
pub sessions_per_era: BlockNumber,
pub bonding_duration: BlockNumber,
pub launch_period: BlockNumber,
pub voting_period: BlockNumber,
pub minimum_deposit: Balance,
pub candidacy_bond: Balance,
pub voter_bond: Balance,
pub present_slash_per_voter: Balance,
pub carry_count: u32,
pub presentation_duration: BlockNumber,
pub council_election_voting_period: BlockNumber,
pub council_term_duration: BlockNumber,
pub desired_seats: u32,
pub inactive_grace_period: BlockNumber,
pub cooloff_period: BlockNumber,
pub council_proposal_voting_period: BlockNumber,
}
impl GenesisConfig {
pub fn new_simple(authorities_validators: Vec<AccountId>, balance: Balance) -> Self {
GenesisConfig {
validators: authorities_validators.clone(),
authorities: authorities_validators.clone(),
balances: authorities_validators.iter().map(|v| (v.clone(), balance)).collect(),
block_time: 30, // 30 second block time.
session_length: 120, // that's 1 hour per session.
sessions_per_era: 24, // 24 hours per era.
bonding_duration: 90, // 90 days per bond.
launch_period: 120 * 24 * 14, // 2 weeks per public referendum
voting_period: 120 * 24 * 28, // 4 weeks to discuss & vote on an active referendum
minimum_deposit: 1000, // 1000 as the minimum deposit for a referendum
candidacy_bond: 1000, // 1000 to become a council candidate
voter_bond: 100, // 100 down to vote for a candidate
present_slash_per_voter: 1, // slash by 1 per voter for an invalid presentation.
carry_count: 24, // carry over the 24 runners-up to the next council election
presentation_duration: 120 * 24, // one day for presenting winners.
council_election_voting_period: 7 * 120 * 24, // one week period between possible council elections.
council_term_duration: 180 * 120 * 24, // 180 day term duration for the council.
desired_seats: 0, // start with no council: we'll raise this once the stake has been dispersed a bit.
inactive_grace_period: 1, // one addition vote should go by before an inactive voter can be reaped.
cooloff_period: 90 * 120 * 24, // 90 day cooling off period if council member vetoes a proposal.
council_proposal_voting_period: 7 * 120 * 24, // 7 day voting period for council members.
}
}
pub fn genesis_map(&self) -> HashMap<Vec<u8>, Vec<u8>> {
let wasm_runtime = include_bytes!("../wasm/genesis.wasm").to_vec();
vec![
(session::SessionLength::key(), vec![].and(&self.session_length)),
(session::Validators::key(), vec![].and(&self.validators)),
(&staking::Intention::len_key()[..], vec![].and(&0u32)),
(&staking::SessionsPerEra::key()[..], vec![].and(&self.sessions_per_era)),
(&staking::CurrentEra::key()[..], vec![].and(&0u64)),
(democracy::LaunchPeriod::key(), vec![].and(&self.launch_period)),
(democracy::VotingPeriod::key(), vec![].and(&self.voting_period)),
(democracy::MinimumDeposit::key(), vec![].and(&self.minimum_deposit)),
(council::CandidacyBond::key(), vec![].and(&self.candidacy_bond)),
(council::VotingBond::key(), vec![].and(&self.voter_bond)),
(council::PresentSlashPerVoter::key(), vec![].and(&self.present_slash_per_voter)),
(council::CarryCount::key(), vec![].and(&self.carry_count)),
(council::PresentationDuration::key(), vec![].and(&self.presentation_duration)),
(council::VotingPeriod::key(), vec![].and(&self.council_election_voting_period)),
(council::TermDuration::key(), vec![].and(&self.council_term_duration)),
(council::DesiredSeats::key(), vec![].and(&self.desired_seats)),
(council::InactiveGracePeriod::key(), vec![].and(&self.inactive_grace_period)),
(council_vote::CooloffPeriod::key(), vec![].and(&self.cooloff_period)),
(council_vote::VotingPeriod::key(), vec![].and(&self.council_proposal_voting_period))
].into_iter()
.map(|(k, v)| (k.into(), v))
.chain(self.balances.iter()
.map(|&(account, balance)| (staking::FreeBalanceOf::key_for(&account), vec![].and(&balance)))
)
.map(|(k, v)| (twox_128(&k[..])[..].to_vec(), v.to_vec()))
.chain(vec![
(system::CODE.to_vec(), wasm_runtime),
(consensus::AUTHORITY_COUNT[..].into(), vec![].and(&(self.authorities.len() as u32))),
].into_iter())
.chain(self.authorities.iter()
.enumerate()
.map(|(i, account)| ((i as u32).to_keyed_vec(consensus::AUTHORITY_AT), vec![].and(account)))
)
.collect()
}
}
pub fn additional_storage_with_genesis(genesis_block: &Block) -> HashMap<Vec<u8>, Vec<u8>> {
use codec::Slicable;
map![
system::BlockHashAt::key_for(&0) => genesis_block.header.blake2_256().encode()
]
}
+138 -18
View File
@@ -18,30 +18,150 @@
#![cfg_attr(not(feature = "std"), no_std)]
#[allow(unused_imports)] #[macro_use] extern crate substrate_runtime_std as rstd;
#[macro_use] extern crate substrate_runtime_io as runtime_io;
#[macro_use] extern crate substrate_runtime_support as runtime_support;
#[cfg(any(feature = "std", test))] extern crate substrate_keyring as keyring;
#[macro_use]
extern crate substrate_runtime_io as runtime_io;
#[cfg(feature = "std")] #[macro_use] extern crate serde_derive;
#[cfg(feature = "std")] extern crate serde;
#[macro_use]
extern crate substrate_runtime_support as runtime_support;
#[cfg(feature = "std")] extern crate rustc_hex;
#[macro_use]
extern crate substrate_runtime_primitives as runtime_primitives;
extern crate substrate_codec as codec;
#[cfg(feature = "std")] #[macro_use] extern crate substrate_primitives as primitives;
extern crate substrate_runtime_std as rstd;
extern crate substrate_runtime_consensus as consensus;
extern crate substrate_runtime_council as council;
extern crate substrate_runtime_democracy as democracy;
extern crate substrate_runtime_executive as executive;
extern crate substrate_runtime_session as session;
extern crate substrate_runtime_staking as staking;
extern crate substrate_runtime_system as system;
extern crate substrate_runtime_timestamp as timestamp;
extern crate demo_primitives;
#[cfg(test)] #[macro_use] extern crate hex_literal;
use rstd::prelude::*;
use runtime_io::BlakeTwo256;
use demo_primitives::{AccountId, Balance, BlockNumber, Hash, Index, SessionKey, Signature};
use runtime_primitives::generic;
use runtime_primitives::traits::{Identity, HasPublicAux};
extern crate integer_sqrt;
#[cfg(any(feature = "std", test))]
pub use runtime_primitives::BuildExternalities;
#[macro_use] pub mod dispatch;
/// Concrete runtime type used to parameterize the various modules.
pub struct Concrete;
pub mod safe_mix;
pub mod block;
pub mod transaction;
pub mod runtime;
pub mod api;
impl HasPublicAux for Concrete {
type PublicAux = AccountId;
}
#[cfg(feature = "std")] pub mod genesismap;
impl timestamp::Trait for Concrete {
type Value = u64;
}
/// Timestamp module for this concrete runtime.
pub type Timestamp = timestamp::Module<Concrete>;
impl consensus::Trait for Concrete {
type SessionKey = SessionKey;
}
/// Consensus module for this concrete runtime.
pub type Consensus = consensus::Module<Concrete>;
impl system::Trait for Concrete {
type Index = Index;
type BlockNumber = BlockNumber;
type Hash = Hash;
type Hashing = BlakeTwo256;
type Digest = generic::Digest<Vec<u8>>;
type AccountId = AccountId;
type Header = generic::Header<BlockNumber, Hash, Vec<u8>>;
}
/// System module for this concrete runtime.
pub type System = system::Module<Concrete>;
impl session::Trait for Concrete {
type PublicAux = <Self as HasPublicAux>::PublicAux;
type ConvertAccountIdToSessionKey = Identity;
}
/// Session module for this concrete runtime.
pub type Session = session::Module<Concrete>;
impl staking::Trait for Concrete {
type Balance = Balance;
type DetermineContractAddress = BlakeTwo256;
}
/// Staking module for this concrete runtime.
pub type Staking = staking::Module<Concrete>;
impl democracy::Trait for Concrete {
type Proposal = PrivCall;
}
/// Democracy module for this concrete runtime.
pub type Democracy = democracy::Module<Concrete>;
impl council::Trait for Concrete {}
/// Council module for this concrete runtime.
pub type Council = council::Module<Concrete>;
/// Council voting module for this concrete runtime.
pub type CouncilVoting = council::voting::Module<Concrete>;
impl_outer_dispatch! {
pub enum Call where aux: <Concrete as HasPublicAux>::PublicAux {
Session = 1,
Staking = 2,
Timestamp = 3,
Democracy = 5,
Council = 6,
CouncilVoting = 7,
}
pub enum PrivCall {
Consensus = 0,
Session = 1,
Staking = 2,
Democracy = 5,
Council = 6,
CouncilVoting = 7,
}
}
/// Block header type as expected by this runtime.
pub type Header = generic::Header<BlockNumber, Hash, Vec<u8>>;
/// Block type as expected by this runtime.
pub type Block = generic::Block<BlockNumber, Hash, Vec<u8>, AccountId, Index, Call, Signature>;
/// Unchecked extrinsic type as expected by this runtime.
pub type UncheckedExtrinsic = generic::UncheckedExtrinsic<AccountId, Index, Call, Signature>;
/// Extrinsic type as expected by this runtime.
pub type Extrinsic = generic::Extrinsic<AccountId, Index, Call>;
/// Executive: handles dispatch to the various modules.
pub type Executive = executive::Executive<Concrete, Block, Staking,
(((((), Council), Democracy), Staking), Session)>;
impl_outer_config! {
pub struct GenesisConfig for Concrete {
ConsensusConfig => consensus,
SystemConfig => system,
SessionConfig => session,
StakingConfig => staking,
DemocracyConfig => democracy,
CouncilConfig => council,
}
}
pub mod api {
impl_stubs!(
authorities => |()| super::Consensus::authorities(),
initialise_block => |header| super::Executive::initialise_block(&header),
apply_extrinsic => |extrinsic| super::Executive::apply_extrinsic(extrinsic),
execute_block => |block| super::Executive::execute_block(block),
finalise_block => |()| super::Executive::finalise_block(),
validator_count => |()| super::Session::validator_count(),
validators => |()| super::Session::validators()
);
}
@@ -1,51 +0,0 @@
// 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 <http://www.gnu.org/licenses/>.
//! Conensus module for runtime; manages the authority set ready for the native code.
use rstd::prelude::*;
use runtime_support::storage::unhashed::StorageVec;
use demo_primitives::SessionKey;
pub const AUTHORITY_AT: &'static[u8] = b":auth:";
pub const AUTHORITY_COUNT: &'static[u8] = b":auth:len";
struct AuthorityStorageVec {}
impl StorageVec for AuthorityStorageVec {
type Item = SessionKey;
const PREFIX: &'static[u8] = AUTHORITY_AT;
}
/// Get the current set of authorities. These are the session keys.
pub fn authorities() -> Vec<SessionKey> {
AuthorityStorageVec::items()
}
pub mod internal {
use super::*;
/// Set the current set of authorities' session keys.
///
/// Called by `next_session` only.
pub fn set_authorities(authorities: &[SessionKey]) {
AuthorityStorageVec::set_items(authorities);
}
/// Set a single authority by index.
pub fn set_authority(index: u32, key: &SessionKey) {
AuthorityStorageVec::set_item(index, key);
}
}
File diff suppressed because it is too large Load Diff
@@ -1,493 +0,0 @@
// 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 <http://www.gnu.org/licenses/>.
//! Council voting system.
use rstd::prelude::*;
use rstd::borrow::Borrow;
use codec::{KeyedVec, Slicable, Input, NonTrivialSlicable};
use runtime_support::Hashable;
use runtime_support::{StorageValue, StorageMap};
use demo_primitives::{AccountId, Hash, BlockNumber};
use runtime::{system, democracy, council};
use runtime::staking::{PublicPass, Balance};
use runtime::democracy::PrivPass;
use dispatch::PrivCall as Proposal;
type ProposalHash = [u8; 32];
storage_items! {
pub CooloffPeriod get(cooloff_period): b"cov:cooloff" => required BlockNumber;
pub VotingPeriod get(voting_period): b"cov:period" => required BlockNumber;
pub Proposals get(proposals): b"cov:prs" => default Vec<(BlockNumber, ProposalHash)>; // ordered by expiry.
pub ProposalOf get(proposal_of): b"cov:pro" => map [ ProposalHash => Proposal ];
pub ProposalVoters get(proposal_voters): b"cov:voters:" => default map [ ProposalHash => Vec<AccountId> ];
pub CouncilVoteOf get(vote_of): b"cov:vote:" => map [ (ProposalHash, AccountId) => bool ];
pub VetoedProposal get(veto_of): b"cov:veto:" => map [ ProposalHash => (BlockNumber, Vec<AccountId>) ];
}
pub fn is_vetoed<B: Borrow<ProposalHash>>(proposal: B) -> bool {
VetoedProposal::get(proposal.borrow())
.map(|(expiry, _): (BlockNumber, Vec<AccountId>)| system::block_number() < expiry)
.unwrap_or(false)
}
fn set_veto_of(proposal: &ProposalHash, expiry: BlockNumber, vetoers: Vec<AccountId>) {
VetoedProposal::insert(proposal, (expiry, vetoers));
}
fn kill_veto_of(proposal: &ProposalHash) {
VetoedProposal::remove(proposal);
}
pub fn will_still_be_councillor_at(who: &AccountId, n: BlockNumber) -> bool {
council::active_council().iter()
.find(|&&(ref a, _)| a == who)
.map(|&(_, expires)| expires > n)
.unwrap_or(false)
}
pub fn is_councillor(who: &AccountId) -> bool {
council::active_council().iter()
.any(|&(ref a, _)| a == who)
}
pub fn tally(proposal_hash: &ProposalHash) -> (u32, u32, u32) {
generic_tally(proposal_hash, |w: &AccountId, p: &ProposalHash| CouncilVoteOf::get((*p, *w)))
}
fn take_tally(proposal_hash: &ProposalHash) -> (u32, u32, u32) {
generic_tally(proposal_hash, |w: &AccountId, p: &ProposalHash| CouncilVoteOf::take((*p, *w)))
}
fn generic_tally<F: Fn(&AccountId, &ProposalHash) -> Option<bool>>(proposal_hash: &ProposalHash, vote_of: F) -> (u32, u32, u32) {
let c = council::active_council();
let (approve, reject) = c.iter()
.filter_map(|&(ref a, _)| vote_of(a, proposal_hash))
.map(|approve| if approve { (1, 0) } else { (0, 1) })
.fold((0, 0), |(a, b), (c, d)| (a + c, b + d));
(approve, reject, c.len() as u32 - approve - reject)
}
fn set_proposals(p: &Vec<(BlockNumber, ProposalHash)>) {
Proposals::put(p);
}
fn take_proposal_if_expiring_at(n: BlockNumber) -> Option<(Proposal, ProposalHash)> {
let mut proposals = proposals();
match proposals.first() {
Some(&(expiry, hash)) if expiry == n => {
// yes this is horrible, but fixing it will need substantial work in storage.
set_proposals(&proposals[1..].to_vec());
let proposal = ProposalOf::take(hash).expect("all queued proposal hashes must have associated proposals");
Some((proposal, hash))
}
_ => None,
}
}
impl_dispatch! {
pub mod public;
fn propose(proposal: Box<Proposal>) = 0;
fn vote(proposal: ProposalHash, approve: bool) = 1;
fn veto(proposal_hash: ProposalHash) = 2;
}
impl<'a> public::Dispatch for PublicPass<'a> {
fn propose(self, proposal: Box<Proposal>) {
let expiry = system::block_number() + voting_period();
assert!(will_still_be_councillor_at(&self, expiry));
let proposal_hash = proposal.blake2_256();
assert!(!is_vetoed(&proposal_hash));
let mut proposals = proposals();
proposals.push((expiry, proposal_hash));
proposals.sort_by_key(|&(expiry, _)| expiry);
set_proposals(&proposals);
ProposalOf::insert(proposal_hash, *proposal);
ProposalVoters::insert(proposal_hash, vec![*self]);
CouncilVoteOf::insert((proposal_hash, *self), true);
}
fn vote(self, proposal: ProposalHash, approve: bool) {
if vote_of((*self, proposal)).is_none() {
let mut voters = proposal_voters(&proposal);
voters.push(*self);
ProposalVoters::insert(proposal, voters);
}
CouncilVoteOf::insert((proposal, *self), approve);
}
fn veto(self, proposal_hash: ProposalHash) {
assert!(is_councillor(&self), "only councillors may veto council proposals");
assert!(ProposalVoters::exists(&proposal_hash), "proposal must exist to be vetoed");
let mut existing_vetoers = veto_of(&proposal_hash)
.map(|pair| pair.1)
.unwrap_or_else(Vec::new);
let insert_position = existing_vetoers.binary_search(&self)
.expect_err("a councillor may not veto a proposal twice");
existing_vetoers.insert(insert_position, *self);
set_veto_of(&proposal_hash, system::block_number() + cooloff_period(), existing_vetoers);
set_proposals(&proposals().into_iter().filter(|&(_, h)| h != proposal_hash).collect::<Vec<_>>());
ProposalVoters::remove(proposal_hash);
ProposalOf::remove(proposal_hash);
for (c, _) in council::active_council() {
CouncilVoteOf::remove((proposal_hash, c));
}
}
}
impl_dispatch! {
pub mod privileged;
fn set_cooloff_period(blocks: BlockNumber) = 0;
fn set_voting_period(blocks: BlockNumber) = 1;
}
impl privileged::Dispatch for PrivPass {
fn set_cooloff_period(self, blocks: BlockNumber) {
CooloffPeriod::put(blocks);
}
fn set_voting_period(self, blocks: BlockNumber) {
VotingPeriod::put(blocks);
}
}
pub mod internal {
use super::*;
use runtime::democracy::privileged::Dispatch;
use runtime::democracy::VoteThreshold;
use runtime::democracy::internal::start_referendum;
pub fn end_block(now: BlockNumber) {
while let Some((proposal, proposal_hash)) = take_proposal_if_expiring_at(now) {
let tally = take_tally(&proposal_hash);
if let &Proposal::Democracy(democracy::privileged::Call::cancel_referendum(ref_index)) = &proposal {
if let (_, 0, 0) = tally {
democracy::internal::cancel_referendum(ref_index);
}
} else {
if tally.0 > tally.1 + tally.2 {
kill_veto_of(&proposal_hash);
match tally {
(_, 0, 0) => start_referendum(proposal, VoteThreshold::SuperMajorityAgainst),
_ => start_referendum(proposal, VoteThreshold::SimpleMajority),
};
}
}
}
}
}
#[cfg(test)]
pub mod testing {
use super::*;
use runtime_io::{twox_128, TestExternalities};
use keyring::Keyring::{Alice, Bob, Charlie};
use codec::Joiner;
use runtime::{council, democracy};
pub fn externalities() -> TestExternalities {
let expiry: BlockNumber = 10;
let extras: TestExternalities = map![
twox_128(council::ActiveCouncil::key()).to_vec() => vec![].and(&vec![
(Alice.to_raw_public(), expiry),
(Bob.into(), expiry),
(Charlie.into(), expiry)
]),
twox_128(CooloffPeriod::key()).to_vec() => vec![].and(&2u64),
twox_128(VotingPeriod::key()).to_vec() => vec![].and(&1u64),
twox_128(democracy::VotingPeriod::key()).to_vec() => vec![].and(&3u64)
];
council::testing::externalities()
.into_iter().chain(extras.into_iter()).collect()
}
}
#[cfg(test)]
mod tests {
use super::*;
use runtime_io::{with_externalities, twox_128, TestExternalities};
use codec::{KeyedVec, Joiner};
use keyring::Keyring::{Alice, Bob, Charlie, Dave};
use demo_primitives::AccountId;
use runtime::democracy::VoteThreshold;
use runtime::{staking, council, democracy};
use super::public::Dispatch;
use super::privileged::Dispatch as PrivDispatch;
fn new_test_ext() -> TestExternalities {
testing::externalities()
}
#[test]
fn basic_environment_works() {
with_externalities(&mut new_test_ext(), || {
system::testing::set_block_number(1);
assert_eq!(staking::bonding_duration(), 0);
assert_eq!(cooloff_period(), 2);
assert_eq!(voting_period(), 1);
assert_eq!(will_still_be_councillor_at(&Alice, 1), true);
assert_eq!(will_still_be_councillor_at(&Alice, 10), false);
assert_eq!(will_still_be_councillor_at(&Dave, 10), false);
assert_eq!(is_councillor(&Alice), true);
assert_eq!(is_councillor(&Dave), false);
assert_eq!(proposals(), Vec::<(BlockNumber, ProposalHash)>::new());
assert_eq!(proposal_voters(ProposalHash::default()), Vec::<AccountId>::new());
assert_eq!(is_vetoed(&ProposalHash::default()), false);
assert_eq!(vote_of((*Alice, ProposalHash::default())), None);
assert_eq!(tally(&ProposalHash::default()), (0, 0, 3));
});
}
fn sessions_per_era_proposal(value: u64) -> Proposal {
Proposal::Staking(staking::privileged::Call::set_sessions_per_era(value))
}
fn bonding_duration_proposal(value: u64) -> Proposal {
Proposal::Staking(staking::privileged::Call::set_bonding_duration(value))
}
fn cancel_referendum_proposal(id: u32) -> Proposal {
Proposal::Democracy(democracy::privileged::Call::cancel_referendum(id))
}
#[test]
fn referendum_cancellation_should_work_when_unanimous() {
with_externalities(&mut new_test_ext(), || {
system::testing::set_block_number(1);
let proposal = bonding_duration_proposal(42);
democracy::internal::start_referendum(proposal.clone(), VoteThreshold::SuperMajorityApprove);
assert_eq!(democracy::active_referendums(), vec![(0, 4, proposal, VoteThreshold::SuperMajorityApprove)]);
let cancellation = cancel_referendum_proposal(0);
let hash = cancellation.blake2_256();
PublicPass::new(&Alice).propose(Box::new(cancellation));
PublicPass::new(&Bob).vote(hash, true);
PublicPass::new(&Charlie).vote(hash, true);
assert_eq!(proposals(), vec![(2, hash)]);
internal::end_block(1);
system::testing::set_block_number(2);
internal::end_block(2);
assert_eq!(democracy::active_referendums(), vec![]);
assert_eq!(staking::bonding_duration(), 0);
});
}
#[test]
fn referendum_cancellation_should_fail_when_not_unanimous() {
with_externalities(&mut new_test_ext(), || {
system::testing::set_block_number(1);
let proposal = bonding_duration_proposal(42);
democracy::internal::start_referendum(proposal.clone(), VoteThreshold::SuperMajorityApprove);
let cancellation = cancel_referendum_proposal(0);
let hash = cancellation.blake2_256();
PublicPass::new(&Alice).propose(Box::new(cancellation));
PublicPass::new(&Bob).vote(hash, true);
PublicPass::new(&Charlie).vote(hash, false);
internal::end_block(1);
system::testing::set_block_number(2);
internal::end_block(2);
assert_eq!(democracy::active_referendums(), vec![(0, 4, proposal, VoteThreshold::SuperMajorityApprove)]);
});
}
#[test]
fn referendum_cancellation_should_fail_when_abstentions() {
with_externalities(&mut new_test_ext(), || {
system::testing::set_block_number(1);
let proposal = bonding_duration_proposal(42);
democracy::internal::start_referendum(proposal.clone(), VoteThreshold::SuperMajorityApprove);
let cancellation = cancel_referendum_proposal(0);
let hash = cancellation.blake2_256();
PublicPass::new(&Alice).propose(Box::new(cancellation));
PublicPass::new(&Bob).vote(hash, true);
internal::end_block(1);
system::testing::set_block_number(2);
internal::end_block(2);
assert_eq!(democracy::active_referendums(), vec![(0, 4, proposal, VoteThreshold::SuperMajorityApprove)]);
});
}
#[test]
fn veto_should_work() {
with_externalities(&mut new_test_ext(), || {
system::testing::set_block_number(1);
let proposal = bonding_duration_proposal(42);
let hash = proposal.blake2_256();
PublicPass::new(&Alice).propose(Box::new(proposal.clone()));
PublicPass::new(&Bob).veto(hash);
assert_eq!(proposals().len(), 0);
assert_eq!(democracy::active_referendums().len(), 0);
});
}
#[test]
#[should_panic]
fn double_veto_should_panic() {
with_externalities(&mut new_test_ext(), || {
system::testing::set_block_number(1);
let proposal = bonding_duration_proposal(42);
let hash = proposal.blake2_256();
PublicPass::new(&Alice).propose(Box::new(proposal.clone()));
PublicPass::new(&Bob).veto(hash);
system::testing::set_block_number(3);
PublicPass::new(&Alice).propose(Box::new(proposal.clone()));
PublicPass::new(&Bob).veto(hash);
});
}
#[test]
#[should_panic]
fn retry_in_cooloff_should_panic() {
with_externalities(&mut new_test_ext(), || {
system::testing::set_block_number(1);
let proposal = bonding_duration_proposal(42);
let hash = proposal.blake2_256();
PublicPass::new(&Alice).propose(Box::new(proposal.clone()));
PublicPass::new(&Bob).veto(hash);
system::testing::set_block_number(2);
PublicPass::new(&Alice).propose(Box::new(proposal.clone()));
});
}
#[test]
fn retry_after_cooloff_should_work() {
with_externalities(&mut new_test_ext(), || {
system::testing::set_block_number(1);
let proposal = bonding_duration_proposal(42);
let hash = proposal.blake2_256();
PublicPass::new(&Alice).propose(Box::new(proposal.clone()));
PublicPass::new(&Bob).veto(hash);
system::testing::set_block_number(3);
PublicPass::new(&Alice).propose(Box::new(proposal.clone()));
PublicPass::new(&Bob).vote(hash, false);
PublicPass::new(&Charlie).vote(hash, true);
internal::end_block(3);
system::testing::set_block_number(4);
internal::end_block(4);
assert_eq!(proposals().len(), 0);
assert_eq!(democracy::active_referendums(), vec![(0, 7, bonding_duration_proposal(42), VoteThreshold::SimpleMajority)]);
});
}
#[test]
fn alternative_double_veto_should_work() {
with_externalities(&mut new_test_ext(), || {
system::testing::set_block_number(1);
let proposal = bonding_duration_proposal(42);
let hash = proposal.blake2_256();
PublicPass::new(&Alice).propose(Box::new(proposal.clone()));
PublicPass::new(&Bob).veto(hash);
system::testing::set_block_number(3);
PublicPass::new(&Alice).propose(Box::new(proposal.clone()));
PublicPass::new(&Charlie).veto(hash);
assert_eq!(proposals().len(), 0);
assert_eq!(democracy::active_referendums().len(), 0);
});
}
#[test]
fn simple_propose_should_work() {
with_externalities(&mut new_test_ext(), || {
system::testing::set_block_number(1);
let proposal = bonding_duration_proposal(42);
let hash = proposal.blake2_256();
PublicPass::new(&Alice).propose(Box::new(proposal.clone()));
assert_eq!(proposals().len(), 1);
assert_eq!(proposal_voters(&hash), vec![Alice.to_raw_public()]);
assert_eq!(vote_of((hash, *Alice)), Some(true));
assert_eq!(tally(&hash), (1, 0, 2));
});
}
#[test]
fn unvoted_proposal_should_expire_without_action() {
with_externalities(&mut new_test_ext(), || {
system::testing::set_block_number(1);
let proposal = bonding_duration_proposal(42);
PublicPass::new(&Alice).propose(Box::new(proposal.clone()));
assert_eq!(tally(&proposal.blake2_256()), (1, 0, 2));
internal::end_block(1);
system::testing::set_block_number(2);
internal::end_block(2);
assert_eq!(proposals().len(), 0);
assert_eq!(democracy::active_referendums().len(), 0);
});
}
#[test]
fn unanimous_proposal_should_expire_with_biased_referendum() {
with_externalities(&mut new_test_ext(), || {
system::testing::set_block_number(1);
let proposal = bonding_duration_proposal(42);
PublicPass::new(&Alice).propose(Box::new(proposal.clone()));
PublicPass::new(&Bob).vote(proposal.blake2_256(), true);
PublicPass::new(&Charlie).vote(proposal.blake2_256(), true);
assert_eq!(tally(&proposal.blake2_256()), (3, 0, 0));
internal::end_block(1);
system::testing::set_block_number(2);
internal::end_block(2);
assert_eq!(proposals().len(), 0);
assert_eq!(democracy::active_referendums(), vec![(0, 5, proposal, VoteThreshold::SuperMajorityAgainst)]);
});
}
#[test]
fn majority_proposal_should_expire_with_unbiased_referendum() {
with_externalities(&mut new_test_ext(), || {
system::testing::set_block_number(1);
let proposal = bonding_duration_proposal(42);
PublicPass::new(&Alice).propose(Box::new(proposal.clone()));
PublicPass::new(&Bob).vote(proposal.blake2_256(), true);
PublicPass::new(&Charlie).vote(proposal.blake2_256(), false);
assert_eq!(tally(&proposal.blake2_256()), (2, 1, 0));
internal::end_block(1);
system::testing::set_block_number(2);
internal::end_block(2);
assert_eq!(proposals().len(), 0);
assert_eq!(democracy::active_referendums(), vec![(0, 5, proposal, VoteThreshold::SimpleMajority)]);
});
}
#[test]
#[should_panic]
fn propose_by_public_should_panic() {
with_externalities(&mut new_test_ext(), || {
system::testing::set_block_number(1);
let proposal = bonding_duration_proposal(42);
PublicPass::new(&Dave).propose(Box::new(proposal));
});
}
}
@@ -1,623 +0,0 @@
// 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 <http://www.gnu.org/licenses/>.
//! Democratic system: Handles administration of general stakeholder voting.
use rstd::prelude::*;
use integer_sqrt::IntegerSquareRoot;
use codec::{KeyedVec, Slicable, Input, NonTrivialSlicable};
use runtime_support::{StorageValue, StorageMap};
use demo_primitives::{AccountId, Hash, BlockNumber};
use dispatch::PrivCall as Proposal;
use runtime::{staking, system, session};
use runtime::staking::{PublicPass, Balance};
/// A token for privileged dispatch. Can only be created in this module.
pub struct PrivPass((),);
impl PrivPass {
fn new() -> PrivPass { PrivPass((),) }
#[cfg(test)]
pub fn test() -> PrivPass { PrivPass((),) }
}
/// A proposal index.
pub type PropIndex = u32;
/// A referendum index.
pub type ReferendumIndex = u32;
/// A means of determining if a vote is past pass threshold.
#[derive(Clone, Copy, PartialEq, Eq)]
#[cfg_attr(feature = "std", derive(Serialize, Deserialize, Debug))]
pub enum VoteThreshold {
/// A supermajority of approvals is needed to pass this vote.
SuperMajorityApprove,
/// A supermajority of rejects is needed to fail this vote.
SuperMajorityAgainst,
/// A simple majority of approvals is needed to pass this vote.
SimpleMajority,
}
impl Slicable for VoteThreshold {
fn decode<I: Input>(input: &mut I) -> Option<Self> {
u8::decode(input).and_then(|v| match v {
0 => Some(VoteThreshold::SuperMajorityApprove),
1 => Some(VoteThreshold::SuperMajorityAgainst),
2 => Some(VoteThreshold::SimpleMajority),
_ => None,
})
}
fn using_encoded<R, F: FnOnce(&[u8]) -> R>(&self, f: F) -> R {
match *self {
VoteThreshold::SuperMajorityApprove => 0u8,
VoteThreshold::SuperMajorityAgainst => 1u8,
VoteThreshold::SimpleMajority => 2u8,
}.using_encoded(f)
}
}
impl NonTrivialSlicable for VoteThreshold {}
trait Approved {
/// Given `approve` votes for and `against` votes against from a total electorate size of
/// `electorate` (`electorate - (approve + against)` are abstainers), then returns true if the
/// overall outcome is in favour of approval.
fn approved(&self, approve: Balance, against: Balance, electorate: Balance) -> bool;
}
impl Approved for VoteThreshold {
/// Given `approve` votes for and `against` votes against from a total electorate size of
/// `electorate` (`electorate - (approve + against)` are abstainers), then returns true if the
/// overall outcome is in favour of approval.
fn approved(&self, approve: Balance, against: Balance, electorate: Balance) -> bool {
let voters = approve + against;
match *self {
VoteThreshold::SuperMajorityApprove =>
voters.integer_sqrt() * approve / electorate.integer_sqrt() > against,
VoteThreshold::SuperMajorityAgainst =>
approve > voters.integer_sqrt() * against / electorate.integer_sqrt(),
VoteThreshold::SimpleMajority => approve > against,
}
}
}
storage_items! {
// The number of (public) proposals that have been made so far.
pub PublicPropCount get(public_prop_count): b"dem:ppc" => default PropIndex;
// The public proposals. Unsorted.
pub PublicProps get(public_props): b"dem:pub" => default Vec<(PropIndex, Proposal, AccountId)>;
// Those who have locked a deposit.
pub DepositOf get(deposit_lockers): b"dem:dep:" => map [ PropIndex => (Balance, Vec<AccountId>) ];
// How often (in blocks) new public referenda are launched.
pub LaunchPeriod get(launch_period): b"dem:lau" => required BlockNumber;
// The minimum amount to be used as a deposit for a public referendum proposal.
pub MinimumDeposit get(minimum_deposit): b"dem:min" => required Balance;
// How often (in blocks) to check for new votes.
pub VotingPeriod get(voting_period): b"dem:per" => required BlockNumber;
// The next free referendum index, aka the number of referendums started so far.
pub ReferendumCount get(next_free_ref_index): b"dem:rco" => default ReferendumIndex;
// The next referendum index that should be tallied.
pub NextTally get(next_tally): b"dem:nxt" => default ReferendumIndex;
// Information concerning any given referendum.
pub ReferendumInfoOf get(referendum_info): b"dem:pro:" => map [ ReferendumIndex => (BlockNumber, Proposal, VoteThreshold) ];
// Get the voters for the current proposal.
pub VotersFor get(voters_for): b"dem:vtr:" => default map [ ReferendumIndex => Vec<AccountId> ];
// Get the vote, if Some, of `who`.
pub VoteOf get(vote_of): b"dem:vot:" => map [ (ReferendumIndex, AccountId) => bool ];
}
// public proposals
/// Get the amount locked in support of `proposal`; false if proposal isn't a valid proposal
/// index.
pub fn locked_for(proposal: PropIndex) -> Option<Balance> {
deposit_lockers(proposal).map(|(d, l)| d * (l.len() as Balance))
}
/// Return true if `ref_index` is an on-going referendum.
pub fn is_active_referendum(ref_index: ReferendumIndex) -> bool {
ReferendumInfoOf::exists(ref_index)
}
/// Get all referendums currently active.
pub fn active_referendums() -> Vec<(ReferendumIndex, BlockNumber, Proposal, VoteThreshold)> {
let next = NextTally::get();
let last = ReferendumCount::get();
(next..last).into_iter()
.filter_map(|i| referendum_info(i).map(|(n, p, t)| (i, n, p, t)))
.collect()
}
/// Get all referendums ready for tally at block `n`.
pub fn maturing_referendums_at(n: BlockNumber) -> Vec<(ReferendumIndex, BlockNumber, Proposal, VoteThreshold)> {
let next = NextTally::get();
let last = ReferendumCount::get();
(next..last).into_iter()
.filter_map(|i| referendum_info(i).map(|(n, p, t)| (i, n, p, t)))
.take_while(|&(_, block_number, _, _)| block_number == n)
.collect()
}
/// Get the voters for the current proposal.
pub fn tally(ref_index: ReferendumIndex) -> (staking::Balance, staking::Balance) {
voters_for(ref_index).iter()
.map(|a| (staking::balance(a), vote_of((ref_index, *a)).expect("all items come from `voters`; for an item to be in `voters` there must be a vote registered; qed")))
.map(|(bal, vote)| if vote { (bal, 0) } else { (0, bal) })
.fold((0, 0), |(a, b), (c, d)| (a + c, b + d))
}
impl_dispatch! {
pub mod public;
fn propose(proposal: Box<Proposal>, value: Balance) = 0;
fn second(proposal: PropIndex) = 1;
fn vote(ref_index: ReferendumIndex, approve_proposal: bool) = 2;
}
impl<'a> public::Dispatch for PublicPass<'a> {
/// Propose a sensitive action to be taken.
fn propose(self, proposal: Box<Proposal>, value: Balance) {
assert!(value >= minimum_deposit());
assert!(staking::internal::deduct_unbonded(&self, value));
let index = PublicPropCount::get();
PublicPropCount::put(index + 1);
DepositOf::insert(index, (value, vec![*self]));
let mut props = public_props();
props.push((index, (*proposal).clone(), *self));
PublicProps::put(props);
}
/// Propose a sensitive action to be taken.
fn second(self, proposal: PropIndex) {
let mut deposit = DepositOf::get(proposal).expect("can only second an existing proposal");
assert!(staking::internal::deduct_unbonded(&self, deposit.0));
deposit.1.push(*self);
DepositOf::insert(proposal, deposit);
}
/// Vote in a referendum. If `approve_proposal` is true, the vote is to enact the proposal;
/// false would be a vote to keep the status quo..
fn vote(self, ref_index: ReferendumIndex, approve_proposal: bool) {
if !is_active_referendum(ref_index) {
panic!("vote given for invalid referendum.")
}
if staking::balance(&self) == 0 {
panic!("transactor must have balance to signal approval.");
}
if !VoteOf::exists(&(ref_index, *self)) {
let mut voters = voters_for(ref_index);
voters.push(self.clone());
VotersFor::insert(ref_index, voters);
}
VoteOf::insert(&(ref_index, *self), approve_proposal);
}
}
impl_dispatch! {
pub mod privileged;
fn start_referendum(proposal: Box<Proposal>, vote_threshold: VoteThreshold) = 0;
fn cancel_referendum(ref_index: ReferendumIndex) = 1;
}
impl privileged::Dispatch for PrivPass {
/// Start a referendum.
fn start_referendum(self, proposal: Box<Proposal>, vote_threshold: VoteThreshold) {
inject_referendum(system::block_number() + voting_period(), *proposal, vote_threshold);
}
/// Remove a referendum.
fn cancel_referendum(self, ref_index: ReferendumIndex) {
clear_referendum(ref_index);
}
}
pub mod internal {
use super::*;
use dispatch;
/// Can be called directly by the council.
pub fn start_referendum(proposal: Proposal, vote_threshold: VoteThreshold) {
inject_referendum(system::block_number() + voting_period(), proposal, vote_threshold);
}
/// Remove a referendum.
pub fn cancel_referendum(ref_index: ReferendumIndex) {
clear_referendum(ref_index);
}
/// Current era is ending; we should finish up any proposals.
pub fn end_block(now: BlockNumber) {
// pick out another public referendum if it's time.
if now % launch_period() == 0 {
let mut public_props = public_props();
if let Some((winner_index, _)) = public_props.iter()
.enumerate()
.max_by_key(|x| locked_for((x.1).0).expect("All current public proposals have an amount locked"))
{
let (prop_index, proposal, _) = public_props.swap_remove(winner_index);
let (deposit, depositors): (Balance, Vec<AccountId>) =
DepositOf::take(prop_index).expect("depositors always exist for current proposals");
// refund depositors
for d in &depositors {
staking::internal::refund(d, deposit);
}
PublicProps::put(public_props);
inject_referendum(now + voting_period(), proposal, VoteThreshold::SuperMajorityApprove);
}
}
// tally up votes for any expiring referenda.
for (index, _, proposal, vote_threshold) in maturing_referendums_at(now) {
let (approve, against) = tally(index);
let total_stake = staking::total_stake();
clear_referendum(index);
if vote_threshold.approved(approve, against, total_stake) {
proposal.dispatch(PrivPass::new());
}
NextTally::put(index + 1);
}
}
}
/// Start a referendum
fn inject_referendum(
end: BlockNumber,
proposal: Proposal,
vote_threshold: VoteThreshold
) -> ReferendumIndex {
let ref_index = next_free_ref_index();
if ref_index > 0 && referendum_info(ref_index - 1).map(|i| i.0 > end).unwrap_or(false) {
panic!("Cannot inject a referendum that ends earlier than preceeding referendum");
}
ReferendumCount::put(ref_index + 1);
ReferendumInfoOf::insert(ref_index, (end, proposal, vote_threshold));
ref_index
}
/// Remove all info on a referendum.
fn clear_referendum(ref_index: ReferendumIndex) {
ReferendumInfoOf::remove(ref_index);
VotersFor::remove(ref_index);
for v in voters_for(ref_index) {
VoteOf::remove((ref_index, v));
}
}
#[cfg(test)]
pub mod testing {
use super::*;
use runtime_io::{twox_128, TestExternalities};
use runtime_support::{StorageList, StorageValue, StorageMap};
use codec::Joiner;
use keyring::Keyring::*;
use runtime::{session, staking};
pub fn externalities() -> TestExternalities {
map![
twox_128(session::SessionLength::key()).to_vec() => vec![].and(&1u64),
twox_128(session::Validators::key()).to_vec() => vec![].and(&vec![Alice.to_raw_public(), Bob.into(), Charlie.into()]),
twox_128(&staking::Intention::len_key()).to_vec() => vec![].and(&3u32),
twox_128(&staking::Intention::key_for(0)).to_vec() => Alice.to_raw_public_vec(),
twox_128(&staking::Intention::key_for(1)).to_vec() => Bob.to_raw_public_vec(),
twox_128(&staking::Intention::key_for(2)).to_vec() => Charlie.to_raw_public_vec(),
twox_128(&staking::FreeBalanceOf::key_for(*Alice)).to_vec() => vec![].and(&10u64),
twox_128(&staking::FreeBalanceOf::key_for(*Bob)).to_vec() => vec![].and(&20u64),
twox_128(&staking::FreeBalanceOf::key_for(*Charlie)).to_vec() => vec![].and(&30u64),
twox_128(&staking::FreeBalanceOf::key_for(*Dave)).to_vec() => vec![].and(&40u64),
twox_128(&staking::FreeBalanceOf::key_for(*Eve)).to_vec() => vec![].and(&50u64),
twox_128(&staking::FreeBalanceOf::key_for(*Ferdie)).to_vec() => vec![].and(&60u64),
twox_128(&staking::FreeBalanceOf::key_for(*One)).to_vec() => vec![].and(&1u64),
twox_128(staking::TotalStake::key()).to_vec() => vec![].and(&210u64),
twox_128(staking::SessionsPerEra::key()).to_vec() => vec![].and(&1u64),
twox_128(staking::ValidatorCount::key()).to_vec() => vec![].and(&3u64),
twox_128(staking::CurrentEra::key()).to_vec() => vec![].and(&1u64),
twox_128(staking::TransactionFee::key()).to_vec() => vec![].and(&1u64),
twox_128(staking::BondingDuration::key()).to_vec() => vec![].and(&0u64),
twox_128(LaunchPeriod::key()).to_vec() => vec![].and(&1u64),
twox_128(VotingPeriod::key()).to_vec() => vec![].and(&1u64),
twox_128(MinimumDeposit::key()).to_vec() => vec![].and(&1u64)
]
}
}
#[cfg(test)]
mod tests {
use super::*;
use runtime_io::{with_externalities, twox_128, TestExternalities};
use codec::{KeyedVec, Joiner};
use keyring::Keyring::*;
use demo_primitives::AccountId;
use dispatch::PrivCall as Proposal;
use runtime::staking::PublicPass;
use super::public::Dispatch;
use super::privileged::Dispatch as PrivDispatch;
use runtime::{staking, session, democracy};
fn new_test_ext() -> TestExternalities {
testing::externalities()
}
#[test]
fn params_should_work() {
with_externalities(&mut new_test_ext(), || {
assert_eq!(launch_period(), 1u64);
assert_eq!(voting_period(), 1u64);
assert_eq!(minimum_deposit(), 1u64);
assert_eq!(next_free_ref_index(), 0u32);
assert_eq!(staking::sessions_per_era(), 1u64);
assert_eq!(staking::total_stake(), 210u64);
});
}
// TODO: test VoteThreshold
fn propose_sessions_per_era(who: &AccountId, value: u64, locked: Balance) {
PublicPass::test(who).
propose(Box::new(Proposal::Staking(staking::privileged::Call::set_sessions_per_era(value))), locked);
}
#[test]
fn locked_for_should_work() {
with_externalities(&mut new_test_ext(), || {
system::testing::set_block_number(1);
propose_sessions_per_era(&Alice, 2, 2u64);
propose_sessions_per_era(&Alice, 4, 4u64);
propose_sessions_per_era(&Alice, 3, 3u64);
assert_eq!(locked_for(0), Some(2));
assert_eq!(locked_for(1), Some(4));
assert_eq!(locked_for(2), Some(3));
});
}
#[test]
fn single_proposal_should_work() {
with_externalities(&mut new_test_ext(), || {
system::testing::set_block_number(1);
propose_sessions_per_era(&Alice, 2, 1u64);
democracy::internal::end_block(system::block_number());
system::testing::set_block_number(2);
let r = 0;
PublicPass::test(&Alice).vote(r, true);
assert_eq!(next_free_ref_index(), 1);
assert_eq!(voters_for(r), vec![Alice.to_raw_public()]);
assert_eq!(vote_of((r, *Alice)), Some(true));
assert_eq!(tally(r), (10, 0));
democracy::internal::end_block(system::block_number());
staking::internal::check_new_era();
assert_eq!(staking::era_length(), 2u64);
});
}
#[test]
fn deposit_for_proposals_should_be_taken() {
with_externalities(&mut new_test_ext(), || {
system::testing::set_block_number(1);
propose_sessions_per_era(&Alice, 2, 5u64);
PublicPass::test(&Bob).second(0);
PublicPass::test(&Eve).second(0);
PublicPass::test(&Eve).second(0);
PublicPass::test(&Eve).second(0);
assert_eq!(staking::balance(&Alice), 5u64);
assert_eq!(staking::balance(&Bob), 15u64);
assert_eq!(staking::balance(&Eve), 35u64);
});
}
#[test]
fn deposit_for_proposals_should_be_returned() {
with_externalities(&mut new_test_ext(), || {
system::testing::set_block_number(1);
propose_sessions_per_era(&Alice, 2, 5u64);
PublicPass::test(&Bob).second(0);
PublicPass::test(&Eve).second(0);
PublicPass::test(&Eve).second(0);
PublicPass::test(&Eve).second(0);
democracy::internal::end_block(system::block_number());
assert_eq!(staking::balance(&Alice), 10u64);
assert_eq!(staking::balance(&Bob), 20u64);
assert_eq!(staking::balance(&Eve), 50u64);
});
}
#[test]
#[should_panic]
fn proposal_with_deposit_below_minimum_should_panic() {
with_externalities(&mut new_test_ext(), || {
system::testing::set_block_number(1);
propose_sessions_per_era(&Alice, 2, 0u64);
});
}
#[test]
#[should_panic]
fn poor_proposer_should_panic() {
with_externalities(&mut new_test_ext(), || {
system::testing::set_block_number(1);
propose_sessions_per_era(&Alice, 2, 11u64);
});
}
#[test]
#[should_panic]
fn poor_seconder_should_panic() {
with_externalities(&mut new_test_ext(), || {
system::testing::set_block_number(1);
propose_sessions_per_era(&Bob, 2, 11u64);
PublicPass::test(&Alice).second(0);
});
}
fn propose_bonding_duration(who: &AccountId, value: u64, locked: Balance) {
PublicPass::test(who).
propose(Box::new(Proposal::Staking(staking::privileged::Call::set_bonding_duration(value))), locked);
}
#[test]
fn runners_up_should_come_after() {
with_externalities(&mut new_test_ext(), || {
system::testing::set_block_number(0);
propose_bonding_duration(&Alice, 2, 2u64);
propose_bonding_duration(&Alice, 4, 4u64);
propose_bonding_duration(&Alice, 3, 3u64);
democracy::internal::end_block(system::block_number());
system::testing::set_block_number(1);
PublicPass::test(&Alice).vote(0, true);
democracy::internal::end_block(system::block_number());
staking::internal::check_new_era();
assert_eq!(staking::bonding_duration(), 4u64);
system::testing::set_block_number(2);
PublicPass::test(&Alice).vote(1, true);
democracy::internal::end_block(system::block_number());
staking::internal::check_new_era();
assert_eq!(staking::bonding_duration(), 3u64);
system::testing::set_block_number(3);
PublicPass::test(&Alice).vote(2, true);
democracy::internal::end_block(system::block_number());
staking::internal::check_new_era();
assert_eq!(staking::bonding_duration(), 2u64);
});
}
fn sessions_per_era_propsal(value: u64) -> Proposal {
Proposal::Staking(staking::privileged::Call::set_sessions_per_era(value))
}
#[test]
fn simple_passing_should_work() {
with_externalities(&mut new_test_ext(), || {
system::testing::set_block_number(1);
let r = inject_referendum(1, sessions_per_era_propsal(2), VoteThreshold::SuperMajorityApprove);
PublicPass::test(&Alice).vote(r, true);
assert_eq!(voters_for(r), vec![Alice.to_raw_public()]);
assert_eq!(vote_of((r, *Alice)), Some(true));
assert_eq!(tally(r), (10, 0));
democracy::internal::end_block(system::block_number());
staking::internal::check_new_era();
assert_eq!(staking::era_length(), 2u64);
});
}
#[test]
fn cancel_referendum_should_work() {
with_externalities(&mut new_test_ext(), || {
system::testing::set_block_number(1);
let r = inject_referendum(1, sessions_per_era_propsal(2), VoteThreshold::SuperMajorityApprove);
PublicPass::test(&Alice).vote(r, true);
PrivPass::test().cancel_referendum(r);
democracy::internal::end_block(system::block_number());
staking::internal::check_new_era();
assert_eq!(staking::era_length(), 1u64);
});
}
#[test]
fn simple_failing_should_work() {
with_externalities(&mut new_test_ext(), || {
system::testing::set_block_number(1);
let r = inject_referendum(1, sessions_per_era_propsal(2), VoteThreshold::SuperMajorityApprove);
PublicPass::test(&Alice).vote(r, false);
assert_eq!(voters_for(r), vec![Alice.to_raw_public()]);
assert_eq!(vote_of((r, *Alice)), Some(false));
assert_eq!(tally(r), (0, 10));
democracy::internal::end_block(system::block_number());
staking::internal::check_new_era();
assert_eq!(staking::era_length(), 1u64);
});
}
#[test]
fn controversial_voting_should_work() {
with_externalities(&mut new_test_ext(), || {
system::testing::set_block_number(1);
let r = inject_referendum(1, sessions_per_era_propsal(2), VoteThreshold::SuperMajorityApprove);
PublicPass::test(&Alice).vote(r, true);
PublicPass::test(&Bob).vote(r, false);
PublicPass::test(&Charlie).vote(r, false);
PublicPass::test(&Dave).vote(r, true);
PublicPass::test(&Eve).vote(r, false);
PublicPass::test(&Ferdie).vote(r, true);
assert_eq!(tally(r), (110, 100));
democracy::internal::end_block(system::block_number());
staking::internal::check_new_era();
assert_eq!(staking::era_length(), 2u64);
});
}
#[test]
fn controversial_low_turnout_voting_should_work() {
with_externalities(&mut new_test_ext(), || {
system::testing::set_block_number(1);
let r = inject_referendum(1, sessions_per_era_propsal(2), VoteThreshold::SuperMajorityApprove);
PublicPass::test(&Eve).vote(r, false);
PublicPass::test(&Ferdie).vote(r, true);
assert_eq!(tally(r), (60, 50));
democracy::internal::end_block(system::block_number());
staking::internal::check_new_era();
assert_eq!(staking::era_length(), 1u64);
});
}
#[test]
fn passing_low_turnout_voting_should_work() {
with_externalities(&mut new_test_ext(), || {
assert_eq!(staking::era_length(), 1u64);
assert_eq!(staking::total_stake(), 210u64);
system::testing::set_block_number(1);
let r = inject_referendum(1, sessions_per_era_propsal(2), VoteThreshold::SuperMajorityApprove);
PublicPass::test(&Dave).vote(r, true);
PublicPass::test(&Eve).vote(r, false);
PublicPass::test(&Ferdie).vote(r, true);
assert_eq!(tally(r), (100, 50));
democracy::internal::end_block(system::block_number());
staking::internal::check_new_era();
assert_eq!(staking::era_length(), 2u64);
});
}
}
-34
View File
@@ -1,34 +0,0 @@
// 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 <http://www.gnu.org/licenses/>.
//! The Substrate Demo runtime.
#[allow(unused)]
pub mod system;
#[allow(unused)]
pub mod consensus;
#[allow(unused)]
pub mod staking;
#[allow(unused)]
pub mod timestamp;
#[allow(unused)]
pub mod session;
#[allow(unused)]
pub mod democracy;
#[allow(unused)]
pub mod council;
#[allow(unused)]
pub mod council_vote;
@@ -1,259 +0,0 @@
// 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 <http://www.gnu.org/licenses/>.
//! Session manager: is told the validators and allows them to manage their session keys for the
//! consensus module.
use rstd::prelude::*;
use codec::KeyedVec;
use runtime_support::{storage, StorageValue, StorageMap};
use demo_primitives::{AccountId, SessionKey, BlockNumber};
use runtime::{system, staking, consensus};
use runtime::democracy::PrivPass;
use runtime::staking::PublicPass;
storage_items!{
// The current set of validators.
pub Validators get(validators): b"ses:val" => required Vec<AccountId>;
// Current length of the session.
pub SessionLength get(length): b"ses:len" => required BlockNumber;
// Current index of the session.
pub CurrentIndex get(current_index): b"ses:ind" => required BlockNumber;
// Block at which the session length last changed.
LastLengthChange: b"ses:llc" => default BlockNumber;
// The next key for a given validator.
NextKeyFor: b"ses:nxt:" => map [ AccountId => SessionKey ];
// The next session length.
NextSessionLength: b"ses:nln" => BlockNumber;
}
/// The number of validators currently.
pub fn validator_count() -> u32 {
Validators::get().len() as u32 // TODO: can probably optimised
}
impl_dispatch! {
pub mod public;
fn set_key(key: SessionKey) = 0;
}
impl<'a> public::Dispatch for PublicPass<'a> {
/// Sets the session key of `_validator` to `_key`. This doesn't take effect until the next
/// session.
fn set_key(self, key: SessionKey) {
// set new value for next session
NextKeyFor::insert(*self, key)
}
}
impl_dispatch! {
pub mod privileged;
fn set_length(new: BlockNumber) = 0;
fn force_new_session() = 1;
}
impl privileged::Dispatch for PrivPass {
/// Set a new era length. Won't kick in until the next era change (at current length).
fn set_length(self, new: BlockNumber) {
NextSessionLength::put(new);
}
/// Forces a new session.
fn force_new_session(self) {
internal::rotate_session();
}
}
// INTERNAL API (available to other runtime modules)
pub mod internal {
use super::*;
/// Set the current set of validators.
///
/// Called by `staking::next_era()` only. `next_session` should be called after this in order to
/// update the session keys to the next validator set.
pub fn set_validators(new: &[AccountId]) {
Validators::put(&new.to_vec()); // TODO: optimise.
consensus::internal::set_authorities(new);
}
/// Hook to be called after transaction processing.
pub fn check_rotate_session() {
// do this last, after the staking system has had chance to switch out the authorities for the
// new set.
// check block number and call next_session if necessary.
if (system::block_number() - LastLengthChange::get()) % length() == 0 {
rotate_session();
}
}
/// Move onto next session: register the new authority set.
pub fn rotate_session() {
// Increment current session index.
CurrentIndex::put(CurrentIndex::get() + 1);
// Enact era length change.
if let Some(next_len) = NextSessionLength::take() {
SessionLength::put(next_len);
LastLengthChange::put(system::block_number());
}
// Update any changes in session keys.
validators().iter().enumerate().for_each(|(i, v)| {
if let Some(n) = NextKeyFor::take(v) {
consensus::internal::set_authority(i as u32, &n);
}
});
}
}
#[cfg(any(feature = "std", test))]
pub mod testing {
use super::*;
use runtime_io::{twox_128, TestExternalities};
use codec::{Joiner, KeyedVec};
use keyring::Keyring::*;
use runtime::system;
pub fn externalities(session_length: u64) -> TestExternalities {
let three = [3u8; 32];
let extras: TestExternalities = map![
twox_128(SessionLength::key()).to_vec() => vec![].and(&session_length),
twox_128(CurrentIndex::key()).to_vec() => vec![].and(&0u64),
twox_128(Validators::key()).to_vec() => vec![].and(&vec![One.into(), Two.into(), three])
];
system::testing::externalities().into_iter().chain(extras.into_iter()).collect()
}
}
#[cfg(test)]
mod tests {
use super::*;
use super::public::*;
use super::privileged::Dispatch as PrivDispatch;
use super::internal::*;
use runtime_io::{with_externalities, twox_128, TestExternalities};
use codec::{KeyedVec, Joiner};
use keyring::Keyring;
use demo_primitives::AccountId;
use runtime::{consensus, session};
fn simple_setup() -> TestExternalities {
map![
twox_128(SessionLength::key()).to_vec() => vec![].and(&2u64),
twox_128(CurrentIndex::key()).to_vec() => vec![].and(&0u64),
// the validators (10, 20, ...)
twox_128(Validators::key()).to_vec() => vec![].and(&vec![[10u8; 32], [20; 32]]),
// initial session keys (11, 21, ...)
b":auth:len".to_vec() => vec![].and(&2u32),
0u32.to_keyed_vec(b":auth:") => vec![11; 32],
1u32.to_keyed_vec(b":auth:") => vec![21; 32]
]
}
#[test]
fn simple_setup_should_work() {
let mut t = simple_setup();
with_externalities(&mut t, || {
assert_eq!(consensus::authorities(), vec![[11u8; 32], [21u8; 32]]);
assert_eq!(length(), 2u64);
assert_eq!(validators(), vec![[10u8; 32], [20u8; 32]]);
});
}
#[test]
fn session_length_change_should_work() {
let mut t = simple_setup();
with_externalities(&mut t, || {
// Block 1: Change to length 3; no visible change.
system::testing::set_block_number(1);
PrivPass::test().set_length(3);
check_rotate_session();
assert_eq!(length(), 2);
assert_eq!(current_index(), 0);
// Block 2: Length now changed to 3. Index incremented.
system::testing::set_block_number(2);
PrivPass::test().set_length(3);
check_rotate_session();
assert_eq!(length(), 3);
assert_eq!(current_index(), 1);
// Block 3: Length now changed to 3. Index incremented.
system::testing::set_block_number(3);
check_rotate_session();
assert_eq!(length(), 3);
assert_eq!(current_index(), 1);
// Block 4: Change to length 2; no visible change.
system::testing::set_block_number(4);
PrivPass::test().set_length(2);
check_rotate_session();
assert_eq!(length(), 3);
assert_eq!(current_index(), 1);
// Block 5: Length now changed to 2. Index incremented.
system::testing::set_block_number(5);
check_rotate_session();
assert_eq!(length(), 2);
assert_eq!(current_index(), 2);
// Block 6: No change.
system::testing::set_block_number(6);
check_rotate_session();
assert_eq!(length(), 2);
assert_eq!(current_index(), 2);
// Block 7: Next index.
system::testing::set_block_number(7);
check_rotate_session();
assert_eq!(length(), 2);
assert_eq!(current_index(), 3);
});
}
#[test]
fn session_change_should_work() {
let mut t = simple_setup();
with_externalities(&mut t, || {
// Block 1: No change
system::testing::set_block_number(1);
check_rotate_session();
assert_eq!(consensus::authorities(), vec![[11u8; 32], [21u8; 32]]);
// Block 2: Session rollover, but no change.
system::testing::set_block_number(2);
check_rotate_session();
assert_eq!(consensus::authorities(), vec![[11u8; 32], [21u8; 32]]);
// Block 3: Set new key for validator 2; no visible change.
system::testing::set_block_number(3);
PublicPass::test(&[20; 32]).set_key([22; 32]);
assert_eq!(consensus::authorities(), vec![[11u8; 32], [21u8; 32]]);
check_rotate_session();
assert_eq!(consensus::authorities(), vec![[11u8; 32], [21u8; 32]]);
// Block 4: Session rollover, authority 2 changes.
system::testing::set_block_number(4);
check_rotate_session();
assert_eq!(consensus::authorities(), vec![[11u8; 32], [22u8; 32]]);
});
}
}
@@ -1,885 +0,0 @@
// 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 <http://www.gnu.org/licenses/>.
//! Staking manager: Handles balances and periodically determines the best set of validators.
use rstd::prelude::*;
use rstd::{ops, cmp};
use rstd::cell::RefCell;
use rstd::collections::btree_map::{BTreeMap, Entry};
use runtime_io::{print, blake2_256};
use codec::{Slicable, Input, KeyedVec};
use runtime_support::{storage, StorageValue, StorageList, StorageMap};
use demo_primitives::{BlockNumber, AccountId};
use runtime::{system, session, democracy};
/// The balance of an account.
pub type Balance = u64;
/// The amount of bonding period left in an account. Measured in eras.
pub type Bondage = u64;
storage_items! {
// The length of the bonding duration in eras.
pub BondingDuration get(bonding_duration): b"sta:loc" => required BlockNumber;
// The length of a staking era in sessions.
pub ValidatorCount get(validator_count): b"sta:vac" => required u32;
// The length of a staking era in sessions.
pub SessionsPerEra get(sessions_per_era): b"sta:spe" => required BlockNumber;
// The total amount of stake on the system.
pub TotalStake get(total_stake): b"sta:tot" => required Balance;
// The fee to be paid for making a transaction.
pub TransactionFee get(transaction_fee): b"sta:fee" => required Balance;
// The current era index.
pub CurrentEra get(current_era): b"sta:era" => required BlockNumber;
// All the accounts with a desire to stake.
pub Intention: b"sta:wil:" => list [ AccountId ];
// The next value of sessions per era.
pub NextSessionsPerEra get(next_sessions_per_era): b"sta:nse" => BlockNumber;
// The block number at which the era length last changed.
pub LastEraLengthChange get(last_era_length_change): b"sta:lec" => default BlockNumber;
// The balance of a given account.
pub FreeBalanceOf get(free_balance_of): b"sta:bal:" => default map [ AccountId => Balance ];
// The amount of the balance of a given account that is exterally reserved; this can still get
// slashed, but gets slashed last of all.
pub ReservedBalanceOf get(reserved_balance_of): b"sta:lbo:" => default map [ AccountId => Balance ];
// The block at which the `who`'s funds become entirely liquid.
pub BondageOf get(bondage_of): b"sta:bon:" => default map [ AccountId => Bondage ];
// The code associated with an account.
pub CodeOf: b"sta:cod:" => default map [ AccountId => Vec<u8> ]; // TODO Vec<u8> values should be optimised to not do a length prefix.
// The storage items associated with an account/key.
pub StorageOf: b"sta:sto:" => map [ (AccountId, Vec<u8>) => Vec<u8> ]; // TODO: keys should also be able to take AsRef<KeyType> to ensure Vec<u8>s can be passed as &[u8]
}
/// The length of a staking era in blocks.
pub fn era_length() -> BlockNumber {
SessionsPerEra::get() * session::length()
}
/// The combined balance of `who`.
pub fn balance(who: &AccountId) -> Balance {
FreeBalanceOf::get(who) + ReservedBalanceOf::get(who)
}
/// Some result as `slash(who, value)` (but without the side-effects) asuming there are no
/// balance changes in the meantime.
pub fn can_slash(who: &AccountId, value: Balance) -> bool {
balance(who) >= value
}
#[derive(PartialEq, Copy, Clone)]
#[cfg_attr(test, derive(Debug))]
pub enum LockStatus {
Liquid,
LockedUntil(BlockNumber),
Staked,
}
/// The block at which the `who`'s funds become entirely liquid.
pub fn unlock_block(who: &AccountId) -> LockStatus {
match BondageOf::get(who) {
i if i == Bondage::max_value() => LockStatus::Staked,
i if i <= system::block_number() => LockStatus::Liquid,
i => LockStatus::LockedUntil(i),
}
}
pub struct PublicPass<'a> (&'a AccountId);
const NOBODY: AccountId = [0u8; 32];
impl<'a> PublicPass<'a> {
pub fn new(transactor: &AccountId) -> PublicPass {
let b = FreeBalanceOf::get(transactor);
let transaction_fee = TransactionFee::get();
assert!(b >= transaction_fee, "attempt to transact without enough funds to pay fee");
FreeBalanceOf::insert(transactor, b - transaction_fee);
PublicPass(transactor)
}
#[cfg(test)]
pub fn test(signed: &AccountId) -> PublicPass {
PublicPass(signed)
}
#[cfg(test)]
pub fn nobody() -> PublicPass<'static> {
PublicPass(&NOBODY)
}
/// Create a smart-contract account.
pub fn create(self, code: &[u8], value: Balance) {
// commit anything that made it this far to storage
if let Some(commit) = private::effect_create(self.0, code, value, private::DirectExt) {
private::commit_state(commit);
}
}
}
impl<'a> ops::Deref for PublicPass<'a> {
type Target = AccountId;
fn deref(&self) -> &AccountId {
self.0
}
}
impl_dispatch! {
pub mod public;
fn transfer(dest: AccountId, value: Balance) = 0;
fn stake() = 1;
fn unstake() = 2;
}
impl<'a> public::Dispatch for PublicPass<'a> {
/// Transfer some unlocked staking balance to another staker.
/// TODO: probably want to state gas-limit and gas-price.
fn transfer(self, dest: AccountId, value: Balance) {
// commit anything that made it this far to storage
if let Some(commit) = private::effect_transfer(&self, &dest, value, private::DirectExt) {
private::commit_state(commit);
}
}
/// Declare the desire to stake for the transactor.
///
/// Effects will be felt at the beginning of the next era.
fn stake(self) {
let mut intentions = Intention::items();
// can't be in the list twice.
assert!(intentions.iter().find(|&t| *t == *self).is_none(), "Cannot stake if already staked.");
intentions.push(self.clone());
Intention::set_items(&intentions);
BondageOf::insert(*self, u64::max_value());
}
/// Retract the desire to stake for the transactor.
///
/// Effects will be felt at the beginning of the next era.
fn unstake(self) {
let mut intentions = Intention::items();
if let Some(position) = intentions.iter().position(|&t| t == *self) {
intentions.swap_remove(position);
} else {
panic!("Cannot unstake if not already staked.");
}
Intention::set_items(&intentions);
BondageOf::insert(*self, CurrentEra::get() + BondingDuration::get());
}
}
impl_dispatch! {
pub mod privileged;
fn set_sessions_per_era(new: BlockNumber) = 0;
fn set_bonding_duration(new: BlockNumber) = 1;
fn set_validator_count(new: u32) = 2;
fn force_new_era() = 3;
}
impl privileged::Dispatch for democracy::PrivPass {
/// Set the number of sessions in an era.
fn set_sessions_per_era(self, new: BlockNumber) {
NextSessionsPerEra::put(&new);
}
/// The length of the bonding duration in eras.
fn set_bonding_duration(self, new: BlockNumber) {
BondingDuration::put(&new);
}
/// The length of a staking era in sessions.
fn set_validator_count(self, new: u32) {
ValidatorCount::put(&new);
}
/// Force there to be a new era. This also forces a new session immediately after.
fn force_new_era(self) {
new_era();
session::internal::rotate_session();
}
}
// Each identity's stake may be in one of three bondage states, given by an integer:
// - n | n <= CurrentEra::get(): inactive: free to be transferred.
// - ~0: active: currently representing a validator.
// - n | n > CurrentEra::get(): deactivating: recently representing a validator and not yet
// ready for transfer.
mod private {
use super::*;
#[derive(Default)]
pub struct ChangeEntry {
balance: Option<Balance>,
code: Option<Vec<u8>>,
storage: BTreeMap<Vec<u8>, Option<Vec<u8>>>,
}
impl ChangeEntry {
pub fn balance_changed(b: Balance) -> Self {
ChangeEntry { balance: Some(b), code: None, storage: Default::default() }
}
}
type State = BTreeMap<AccountId, ChangeEntry>;
pub trait Externalities {
fn get_storage(&self, account: &AccountId, location: &[u8]) -> Option<Vec<u8>>;
fn get_code(&self, account: &AccountId) -> Vec<u8>;
fn get_balance(&self, account: &AccountId) -> Balance;
}
struct Ext<F1, F3, F5> where
F1 : Fn(&AccountId, &[u8]) -> Option<Vec<u8>>,
F3 : Fn(&AccountId) -> Vec<u8>,
F5 : Fn(&AccountId) -> Balance
{
do_get_storage: F1,
do_get_code: F3,
do_get_balance: F5,
}
pub struct DirectExt;
impl Externalities for DirectExt {
fn get_storage(&self, account: &AccountId, location: &[u8]) -> Option<Vec<u8>> {
StorageOf::get(&(*account, location.to_vec()))
}
fn get_code(&self, account: &AccountId) -> Vec<u8> {
CodeOf::get(account)
}
fn get_balance(&self, account: &AccountId) -> Balance {
FreeBalanceOf::get(account)
}
}
impl<F1, F3, F5> Externalities for Ext<F1, F3, F5> where
F1 : Fn(&AccountId, &[u8]) -> Option<Vec<u8>>,
F3 : Fn(&AccountId) -> Vec<u8>,
F5 : Fn(&AccountId) -> Balance
{
fn get_storage(&self, account: &AccountId, location: &[u8]) -> Option<Vec<u8>> {
(self.do_get_storage)(account, location)
}
fn get_code(&self, account: &AccountId) -> Vec<u8> {
(self.do_get_code)(account)
}
fn get_balance(&self, account: &AccountId) -> Balance {
(self.do_get_balance)(account)
}
}
pub fn commit_state(s: State) {
for (address, changed) in s.into_iter() {
if let Some(balance) = changed.balance {
FreeBalanceOf::insert(address, balance);
}
if let Some(code) = changed.code {
CodeOf::insert(&address, &code);
}
for (k, v) in changed.storage.into_iter() {
if let Some(value) = v {
StorageOf::insert(&(address, k), &value);
} else {
StorageOf::remove(&(address, k));
}
}
}
}
fn merge_state(commit_state: State, local: &mut State) {
for (address, changed) in commit_state.into_iter() {
match local.entry(address) {
Entry::Occupied(e) => {
let mut value = e.into_mut();
if changed.balance.is_some() {
value.balance = changed.balance;
}
if changed.code.is_some() {
value.code = changed.code;
}
value.storage.extend(changed.storage.into_iter());
}
Entry::Vacant(e) => {
e.insert(changed);
}
}
}
}
pub fn effect_create<E: Externalities>(
transactor: &AccountId,
code: &[u8],
value: Balance,
ext: E
) -> Option<State> {
let from_balance = ext.get_balance(transactor);
// TODO: a fee.
assert!(from_balance >= value);
let mut dest_pre = blake2_256(code).to_vec();
dest_pre.extend(&transactor[..]);
let dest = blake2_256(&dest_pre);
// early-out if degenerate.
if &dest == transactor {
return None;
}
let mut local = BTreeMap::new();
// two inserts are safe
assert!(&dest != transactor);
local.insert(dest, ChangeEntry { balance: Some(value), code: Some(code.to_vec()), storage: Default::default() });
local.insert(transactor.clone(), ChangeEntry::balance_changed(from_balance - value));
Some(local)
}
pub fn effect_transfer<E: Externalities>(
transactor: &AccountId,
dest: &AccountId,
value: Balance,
ext: E
) -> Option<State> {
let from_balance = ext.get_balance(transactor);
assert!(from_balance >= value);
let to_balance = ext.get_balance(dest);
assert!(BondageOf::get(transactor) <= BondageOf::get(dest));
assert!(to_balance + value > to_balance); // no overflow
// TODO: a fee, based upon gaslimit/gasprice.
// TODO: consider storing upper-bound for contract's gas limit in fixed-length runtime
// code in contract itself and use that.
let local: RefCell<State> = RefCell::new(BTreeMap::new());
if transactor != dest {
let mut local = local.borrow_mut();
local.insert(transactor.clone(), ChangeEntry::balance_changed(from_balance - value));
local.insert(dest.clone(), ChangeEntry::balance_changed(to_balance + value));
}
let should_commit = {
// Our local ext: Should be used for any transfers and creates that happen internally.
let ext = || Ext {
do_get_storage: |account: &AccountId, location: &[u8]|
local.borrow().get(account)
.and_then(|a| a.storage.get(location))
.cloned()
.unwrap_or_else(|| ext.get_storage(account, location)),
do_get_code: |account: &AccountId|
local.borrow().get(account)
.and_then(|a| a.code.clone())
.unwrap_or_else(|| ext.get_code(account)),
do_get_balance: |account: &AccountId|
local.borrow().get(account)
.and_then(|a| a.balance)
.unwrap_or_else(|| ext.get_balance(account)),
};
let mut transfer = |inner_dest: &AccountId, value: Balance| {
if let Some(commit_state) = effect_transfer(dest, inner_dest, value, ext()) {
merge_state(commit_state, &mut *local.borrow_mut());
}
};
let mut create = |code: &[u8], value: Balance| {
if let Some(commit_state) = effect_create(dest, code, value, ext()) {
merge_state(commit_state, &mut *local.borrow_mut());
}
};
let mut put_storage = |location: Vec<u8>, value: Option<Vec<u8>>| {
local.borrow_mut()
.entry(dest.clone())
.or_insert(Default::default())
.storage.insert(location, value);
};
// TODO: logging (logs are just appended into a notable storage-based vector and cleared every
// block).
// TODO: execute code with ext(), put_storage, create and transfer as externalities.
true
};
if should_commit {
Some(local.into_inner())
} else {
None
}
}
}
pub mod internal {
use super::*;
/// Hook to be called after to transaction processing.
pub fn check_new_era() {
// check block number and call new_era if necessary.
if (system::block_number() - LastEraLengthChange::get()) % era_length() == 0 {
new_era();
}
}
/// Deduct from an unbonded balance. true if it happened.
pub fn deduct_unbonded(who: &AccountId, value: Balance) -> bool {
if let LockStatus::Liquid = unlock_block(who) {
let b = FreeBalanceOf::get(who);
if b >= value {
FreeBalanceOf::insert(who, &(b - value));
return true;
}
}
false
}
/// Refund some balance.
pub fn refund(who: &AccountId, value: Balance) {
FreeBalanceOf::insert(who, &(FreeBalanceOf::get(who) + value))
}
/// Will slash any balance, but prefer free over reserved.
pub fn slash(who: &AccountId, value: Balance) -> bool {
let free_balance = FreeBalanceOf::get(who);
let free_slash = cmp::min(free_balance, value);
FreeBalanceOf::insert(who, &(free_balance - free_slash));
if free_slash < value {
slash_reserved(who, value - free_slash)
} else {
true
}
}
/// Moves `value` from balance to reserved balance.
pub fn reserve_balance(who: &AccountId, value: Balance) {
let b = FreeBalanceOf::get(who);
assert!(b >= value);
FreeBalanceOf::insert(who, &(b - value));
ReservedBalanceOf::insert(who, &(ReservedBalanceOf::get(who) + value));
}
/// Moves `value` from reserved balance to balance.
pub fn unreserve_balance(who: &AccountId, value: Balance) {
let b = ReservedBalanceOf::get(who);
let value = cmp::min(b, value);
ReservedBalanceOf::insert(who, &(b - value));
FreeBalanceOf::insert(who, &(FreeBalanceOf::get(who) + value));
}
/// Moves `value` from reserved balance to balance.
pub fn slash_reserved(who: &AccountId, value: Balance) -> bool {
let b = ReservedBalanceOf::get(who);
let slash = cmp::min(b, value);
ReservedBalanceOf::insert(who, &(b - slash));
value == slash
}
/// Moves `value` from reserved balance to balance.
pub fn transfer_reserved_balance(slashed: &AccountId, beneficiary: &AccountId, value: Balance) -> bool {
let b = ReservedBalanceOf::get(slashed);
let slash = cmp::min(b, value);
ReservedBalanceOf::insert(slashed, &(b - slash));
FreeBalanceOf::insert(beneficiary, &(FreeBalanceOf::get(beneficiary) + slash));
slash == value
}
}
/// The era has changed - enact new staking set.
///
/// NOTE: This always happens immediately before a session change to ensure that new validators
/// get a chance to set their session keys.
fn new_era() {
// Increment current era.
CurrentEra::put(&(CurrentEra::get() + 1));
// Enact era length change.
if let Some(next_spe) = NextSessionsPerEra::get() {
if next_spe != SessionsPerEra::get() {
SessionsPerEra::put(&next_spe);
LastEraLengthChange::put(&system::block_number());
}
}
// evaluate desired staking amounts and nominations and optimise to find the best
// combination of validators, then use session::internal::set_validators().
// for now, this just orders would-be stakers by their balances and chooses the top-most
// ValidatorCount::get() of them.
let mut intentions = Intention::items()
.into_iter()
.map(|v| (balance(&v), v))
.collect::<Vec<_>>();
intentions.sort_unstable_by(|&(b1, _), &(b2, _)| b2.cmp(&b1));
session::internal::set_validators(
&intentions.into_iter()
.map(|(_, v)| v)
.take(ValidatorCount::get() as usize)
.collect::<Vec<_>>()
);
}
#[cfg(any(feature = "std", test))]
pub mod testing {
use super::*;
use runtime_io::{twox_128, TestExternalities};
use codec::{Joiner, KeyedVec};
use keyring::Keyring::*;
use runtime::session;
use super::public::{Call, Dispatch};
use super::privileged::{Dispatch as PrivDispatch, Call as PrivCall};
pub fn externalities(session_length: u64, sessions_per_era: u64, current_era: u64) -> TestExternalities {
let extras: TestExternalities = map![
twox_128(&Intention::len_key()).to_vec() => vec![].and(&3u32),
twox_128(&Intention::key_for(0)).to_vec() => Alice.to_raw_public_vec(),
twox_128(&Intention::key_for(1)).to_vec() => Bob.to_raw_public_vec(),
twox_128(&Intention::key_for(2)).to_vec() => Charlie.to_raw_public_vec(),
twox_128(SessionsPerEra::key()).to_vec() => vec![].and(&sessions_per_era),
twox_128(ValidatorCount::key()).to_vec() => vec![].and(&3u64),
twox_128(BondingDuration::key()).to_vec() => vec![].and(&0u64),
twox_128(TransactionFee::key()).to_vec() => vec![].and(&1u64),
twox_128(CurrentEra::key()).to_vec() => vec![].and(&current_era),
twox_128(&FreeBalanceOf::key_for(*Alice)).to_vec() => vec![111u8, 0, 0, 0, 0, 0, 0, 0]
];
session::testing::externalities(session_length).into_iter().chain(extras.into_iter()).collect()
}
}
#[cfg(test)]
mod tests {
use super::*;
use super::internal::*;
use super::privileged::*;
use runtime_io::{with_externalities, twox_128, TestExternalities};
use codec::{KeyedVec, Joiner};
use keyring::Keyring::*;
use demo_primitives::AccountId;
use runtime::{staking, session};
use runtime::democracy::PrivPass;
use runtime::staking::public::{Call, Dispatch};
use runtime::staking::privileged::{Call as PCall, Dispatch as PDispatch};
#[test]
fn staking_should_work() {
let mut t: TestExternalities = map![
twox_128(session::SessionLength::key()).to_vec() => vec![].and(&1u64),
twox_128(session::Validators::key()).to_vec() => vec![].and(&vec![[10u8; 32], [20; 32]]),
twox_128(CurrentEra::key()).to_vec() => vec![].and(&0u64),
twox_128(SessionsPerEra::key()).to_vec() => vec![].and(&2u64),
twox_128(ValidatorCount::key()).to_vec() => vec![].and(&2u32),
twox_128(BondingDuration::key()).to_vec() => vec![].and(&3u64),
twox_128(TotalStake::key()).to_vec() => vec![].and(&100u64),
twox_128(TransactionFee::key()).to_vec() => vec![].and(&0u64),
twox_128(&FreeBalanceOf::key_for(*Alice)).to_vec() => vec![].and(&10u64),
twox_128(&FreeBalanceOf::key_for(*Bob)).to_vec() => vec![].and(&20u64),
twox_128(&FreeBalanceOf::key_for(*Charlie)).to_vec() => vec![].and(&30u64),
twox_128(&FreeBalanceOf::key_for(*Dave)).to_vec() => vec![].and(&40u64)
];
with_externalities(&mut t, || {
assert_eq!(era_length(), 2u64);
assert_eq!(ValidatorCount::get(), 2);
assert_eq!(BondingDuration::get(), 3);
assert_eq!(session::validators(), vec![[10u8; 32], [20u8; 32]]);
// Block 1: Add three validators. No obvious change.
system::testing::set_block_number(1);
public::Call::stake().dispatch(PublicPass::new(&Alice));
PublicPass::new(&Bob).stake();
PublicPass::new(&Dave).stake();
check_new_era();
assert_eq!(session::validators(), vec![[10u8; 32], [20u8; 32]]);
// Block 2: New validator set now.
system::testing::set_block_number(2);
check_new_era();
assert_eq!(session::validators(), vec![Dave.to_raw_public(), Bob.into()]);
// Block 3: Unstake highest, introduce another staker. No change yet.
system::testing::set_block_number(3);
PublicPass::new(&Charlie).stake();
PublicPass::new(&Dave).unstake();
check_new_era();
// Block 4: New era - validators change.
system::testing::set_block_number(4);
check_new_era();
assert_eq!(session::validators(), vec![Charlie.to_raw_public(), Bob.into()]);
// Block 5: Transfer stake from highest to lowest. No change yet.
system::testing::set_block_number(5);
PublicPass::new(&Dave).transfer(Alice.to_raw_public(), 40);
check_new_era();
// Block 6: Lowest now validator.
system::testing::set_block_number(6);
check_new_era();
assert_eq!(session::validators(), vec![Alice.to_raw_public(), Charlie.into()]);
// Block 7: Unstake three. No change yet.
system::testing::set_block_number(7);
PublicPass::new(&Charlie).unstake();
check_new_era();
assert_eq!(session::validators(), vec![Alice.to_raw_public(), Charlie.into()]);
// Block 8: Back to one and two.
system::testing::set_block_number(8);
check_new_era();
assert_eq!(session::validators(), vec![Alice.to_raw_public(), Bob.into()]);
});
}
#[test]
fn staking_eras_work() {
let mut t: TestExternalities = map![
twox_128(session::SessionLength::key()).to_vec() => vec![].and(&1u64),
twox_128(SessionsPerEra::key()).to_vec() => vec![].and(&2u64),
twox_128(ValidatorCount::key()).to_vec() => vec![].and(&2u32),
twox_128(CurrentEra::key()).to_vec() => vec![].and(&0u64)
];
with_externalities(&mut t, || {
assert_eq!(era_length(), 2u64);
assert_eq!(SessionsPerEra::get(), 2u64);
assert_eq!(LastEraLengthChange::get(), 0u64);
assert_eq!(CurrentEra::get(), 0u64);
// Block 1: No change.
system::testing::set_block_number(1);
check_new_era();
assert_eq!(SessionsPerEra::get(), 2u64);
assert_eq!(LastEraLengthChange::get(), 0u64);
assert_eq!(CurrentEra::get(), 0u64);
// Block 2: Simple era change.
system::testing::set_block_number(2);
check_new_era();
assert_eq!(SessionsPerEra::get(), 2u64);
assert_eq!(LastEraLengthChange::get(), 0u64);
assert_eq!(CurrentEra::get(), 1u64);
// Block 3: Schedule an era length change; no visible changes.
system::testing::set_block_number(3);
PrivPass::test().set_sessions_per_era(3);
check_new_era();
assert_eq!(SessionsPerEra::get(), 2u64);
assert_eq!(LastEraLengthChange::get(), 0u64);
assert_eq!(CurrentEra::get(), 1u64);
// Block 4: Era change kicks in.
system::testing::set_block_number(4);
check_new_era();
assert_eq!(SessionsPerEra::get(), 3u64);
assert_eq!(LastEraLengthChange::get(), 4u64);
assert_eq!(CurrentEra::get(), 2u64);
// Block 5: No change.
system::testing::set_block_number(5);
check_new_era();
assert_eq!(SessionsPerEra::get(), 3u64);
assert_eq!(LastEraLengthChange::get(), 4u64);
assert_eq!(CurrentEra::get(), 2u64);
// Block 6: No change.
system::testing::set_block_number(6);
check_new_era();
assert_eq!(SessionsPerEra::get(), 3u64);
assert_eq!(LastEraLengthChange::get(), 4u64);
assert_eq!(CurrentEra::get(), 2u64);
// Block 7: Era increment.
system::testing::set_block_number(7);
check_new_era();
assert_eq!(SessionsPerEra::get(), 3u64);
assert_eq!(LastEraLengthChange::get(), 4u64);
assert_eq!(CurrentEra::get(), 3u64);
});
}
#[test]
fn staking_balance_works() {
with_externalities(&mut testing::externalities(1, 3, 1), || {
FreeBalanceOf::insert(*Alice, 42);
assert_eq!(FreeBalanceOf::get(*Alice), 42);
assert_eq!(ReservedBalanceOf::get(*Alice), 0);
assert_eq!(balance(&Alice), 42);
assert_eq!(FreeBalanceOf::get(*Bob), 0);
assert_eq!(ReservedBalanceOf::get(*Bob), 0);
assert_eq!(balance(&Bob), 0);
});
}
#[test]
fn staking_balance_transfer_works() {
with_externalities(&mut testing::externalities(1, 3, 1), || {
FreeBalanceOf::insert(*Alice, 112);
PublicPass::new(&Alice).transfer(Bob.to_raw_public(), 69);
assert_eq!(balance(&Alice), 42);
assert_eq!(balance(&Bob), 69);
});
}
#[test]
#[should_panic]
fn staking_balance_transfer_when_bonded_panics() {
with_externalities(&mut testing::externalities(1, 3, 1), || {
FreeBalanceOf::insert(*Alice, 111);
PublicPass::new(&Alice).stake();
PublicPass::new(&Alice).transfer(Bob.to_raw_public(), 69);
});
}
#[test]
fn reserving_balance_should_work() {
with_externalities(&mut testing::externalities(1, 3, 1), || {
FreeBalanceOf::insert(*Alice, 111);
assert_eq!(balance(&Alice), 111);
assert_eq!(FreeBalanceOf::get(*Alice), 111);
assert_eq!(ReservedBalanceOf::get(*Alice), 0);
reserve_balance(&Alice, 69);
assert_eq!(balance(&Alice), 111);
assert_eq!(FreeBalanceOf::get(*Alice), 42);
assert_eq!(ReservedBalanceOf::get(*Alice), 69);
});
}
#[test]
#[should_panic]
fn staking_balance_transfer_when_reserved_panics() {
with_externalities(&mut testing::externalities(1, 3, 1), || {
FreeBalanceOf::insert(*Alice, 111);
reserve_balance(&Alice, 69);
PublicPass::new(&Alice).transfer(Bob.to_raw_public(), 69);
});
}
#[test]
fn deducting_balance_should_work() {
with_externalities(&mut testing::externalities(1, 3, 1), || {
FreeBalanceOf::insert(*Alice, 111);
assert!(deduct_unbonded(&Alice, 69));
assert_eq!(FreeBalanceOf::get(*Alice), 42);
});
}
#[test]
fn deducting_balance_should_fail_when_bonded() {
let mut t: TestExternalities = map![
twox_128(&FreeBalanceOf::key_for(*Alice)).to_vec() => vec![].and(&111u64),
twox_128(&BondageOf::key_for(*Alice)).to_vec() => vec![].and(&2u64)
];
with_externalities(&mut t, || {
system::testing::set_block_number(1);
assert_eq!(unlock_block(&Alice), LockStatus::LockedUntil(2));
assert!(!deduct_unbonded(&Alice, 69));
});
}
#[test]
fn refunding_balance_should_work() {
with_externalities(&mut testing::externalities(1, 3, 1), || {
FreeBalanceOf::insert(*Alice, 42);
refund(&Alice, 69);
assert_eq!(FreeBalanceOf::get(*Alice), 111);
});
}
#[test]
fn slashing_balance_should_work() {
with_externalities(&mut testing::externalities(1, 3, 1), || {
FreeBalanceOf::insert(*Alice, 111);
reserve_balance(&Alice, 69);
assert!(slash(&Alice, 69));
assert_eq!(FreeBalanceOf::get(*Alice), 0);
assert_eq!(ReservedBalanceOf::get(*Alice), 42);
});
}
#[test]
fn slashing_incomplete_balance_should_work() {
with_externalities(&mut testing::externalities(1, 3, 1), || {
FreeBalanceOf::insert(*Alice, 42);
reserve_balance(&Alice, 21);
assert!(!slash(&Alice, 69));
assert_eq!(FreeBalanceOf::get(*Alice), 0);
assert_eq!(ReservedBalanceOf::get(*Alice), 0);
});
}
#[test]
fn unreserving_balance_should_work() {
with_externalities(&mut testing::externalities(1, 3, 1), || {
FreeBalanceOf::insert(*Alice, 111);
reserve_balance(&Alice, 111);
unreserve_balance(&Alice, 42);
assert_eq!(ReservedBalanceOf::get(*Alice), 69);
assert_eq!(FreeBalanceOf::get(*Alice), 42);
});
}
#[test]
fn slashing_reserved_balance_should_work() {
with_externalities(&mut testing::externalities(1, 3, 1), || {
FreeBalanceOf::insert(*Alice, 111);
reserve_balance(&Alice, 111);
assert!(slash_reserved(&Alice, 42));
assert_eq!(ReservedBalanceOf::get(*Alice), 69);
assert_eq!(FreeBalanceOf::get(*Alice), 0);
});
}
#[test]
fn slashing_incomplete_reserved_balance_should_work() {
with_externalities(&mut testing::externalities(1, 3, 1), || {
FreeBalanceOf::insert(*Alice, 111);
reserve_balance(&Alice, 42);
assert!(!slash_reserved(&Alice, 69));
assert_eq!(FreeBalanceOf::get(*Alice), 69);
assert_eq!(ReservedBalanceOf::get(*Alice), 0);
});
}
#[test]
fn transferring_reserved_balance_should_work() {
with_externalities(&mut testing::externalities(1, 3, 1), || {
FreeBalanceOf::insert(*Alice, 111);
reserve_balance(&Alice, 111);
assert!(transfer_reserved_balance(&Alice, &Bob, 42));
assert_eq!(ReservedBalanceOf::get(*Alice), 69);
assert_eq!(FreeBalanceOf::get(*Alice), 0);
assert_eq!(ReservedBalanceOf::get(*Bob), 0);
assert_eq!(FreeBalanceOf::get(*Bob), 42);
});
}
#[test]
fn transferring_incomplete_reserved_balance_should_work() {
with_externalities(&mut testing::externalities(1, 3, 1), || {
FreeBalanceOf::insert(*Alice, 111);
reserve_balance(&Alice, 42);
assert!(!transfer_reserved_balance(&Alice, &Bob, 69));
assert_eq!(ReservedBalanceOf::get(*Alice), 0);
assert_eq!(FreeBalanceOf::get(*Alice), 69);
assert_eq!(ReservedBalanceOf::get(*Bob), 0);
assert_eq!(FreeBalanceOf::get(*Bob), 42);
});
}
}
@@ -1,352 +0,0 @@
// 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 <http://www.gnu.org/licenses/>.
//! System manager: Handles all of the top-level stuff; executing block/transaction, setting code
//! and depositing logs.
use rstd::prelude::*;
use rstd::mem;
use runtime_io::{print, storage_root, enumerated_trie_root};
use codec::{KeyedVec, Slicable};
use runtime_support::{Hashable, storage, StorageValue, StorageMap};
use demo_primitives::{AccountId, Hash, TxOrder, BlockNumber, Header, Log};
use block::{self, Block};
use transaction::UncheckedTransaction;
use runtime::{staking, session};
use runtime::democracy::PrivPass;
use dispatch;
use safe_mix::TripletMix;
storage_items! {
pub Nonce get(nonce): b"sys:non" => default map [ AccountId => TxOrder ];
pub BlockHashAt get(block_hash): b"sys:old" => required map [ BlockNumber => Hash ];
RandomSeed get(random_seed): b"sys:rnd" => required Hash;
// The current block number being processed. Set by `execute_block`.
Number get(block_number): b"sys:num" => required BlockNumber;
ParentHash get(parent_hash): b"sys:pha" => required Hash;
TransactionsRoot get(transactions_root): b"sys:txr" => required Hash;
Digest: b"sys:dig" => default block::Digest;
}
pub const CODE: &'static[u8] = b":code";
impl_dispatch! {
pub mod privileged;
fn set_code(new: Vec<u8>) = 0;
}
impl privileged::Dispatch for PrivPass {
/// Set the new code.
fn set_code(self, new: Vec<u8>) {
storage::unhashed::put_raw(CODE, &new);
}
}
pub mod internal {
use super::*;
struct CheckedTransaction(UncheckedTransaction);
/// Deposits a log and ensures it matches the blocks log data.
pub fn deposit_log(log: Log) {
let mut l = Digest::get();
l.logs.push(log);
Digest::put(l);
}
/// Actually execute all transitioning for `block`.
pub fn execute_block(mut block: Block) {
initialise_block(&block.header);
// any initial checks
initial_checks(&block);
// execute transactions
block.transactions.iter().cloned().for_each(super::execute_transaction);
// post-transactional book-keeping.
staking::internal::check_new_era();
session::internal::check_rotate_session();
// any final checks
final_checks(&block);
// any stuff that we do after taking the storage root.
post_finalise(&block.header);
}
/// Start the execution of a particular block.
pub fn initialise_block(mut header: &Header) {
// populate environment from header.
Number::put(header.number);
ParentHash::put(header.parent_hash);
TransactionsRoot::put(header.transaction_root);
RandomSeed::put(calculate_random());
}
/// Execute a transaction outside of the block execution function.
/// This doesn't attempt to validate anything regarding the block.
pub fn execute_transaction(utx: UncheckedTransaction) {
super::execute_transaction(utx);
}
/// Finalise the block - it is up the caller to ensure that all header fields are valid
/// except state-root.
pub fn finalise_block() -> Header {
staking::internal::check_new_era();
session::internal::check_rotate_session();
RandomSeed::kill();
let header = Header {
number: Number::take(),
digest: Digest::take(),
parent_hash: ParentHash::take(),
transaction_root: TransactionsRoot::take(),
state_root: storage_root().into(),
};
post_finalise(&header);
header
}
}
fn execute_transaction(utx: UncheckedTransaction) {
use ::transaction;
// Verify the signature is good.
let tx = match transaction::check(utx) {
Ok(tx) => tx,
Err(_) => panic!("All transactions should be properly signed"),
};
{
// check nonce
let expected_nonce: TxOrder = Nonce::get(&tx.signed);
assert!(tx.nonce == expected_nonce, "All transactions should have the correct nonce");
// increment nonce in storage
Nonce::insert(&tx.signed, &(expected_nonce + 1));
}
// decode parameters and dispatch
let tx = tx.drain().transaction;
tx.function.dispatch(staking::PublicPass::new(&tx.signed));
}
fn initial_checks(block: &Block) {
let ref header = block.header;
// check parent_hash is correct.
assert!(
header.number > 0 && BlockHashAt::get(&(header.number - 1)) == header.parent_hash,
"Parent hash should be valid."
);
// check transaction trie root represents the transactions.
let txs = block.transactions.iter().map(Slicable::encode).collect::<Vec<_>>();
let txs = txs.iter().map(Vec::as_slice).collect::<Vec<_>>();
let txs_root = enumerated_trie_root(&txs).into();
info_expect_equal_hash(&header.transaction_root, &txs_root);
assert!(header.transaction_root == txs_root, "Transaction trie root must be valid.");
}
fn final_checks(block: &Block) {
let ref header = block.header;
// check digest
assert!(header.digest == Digest::get());
// remove temporaries.
kill_temps();
// check storage root.
let storage_root = storage_root().into();
info_expect_equal_hash(&header.state_root, &storage_root);
assert!(header.state_root == storage_root, "Storage root must match that calculated.");
}
fn kill_temps() {
Number::kill();
ParentHash::kill();
RandomSeed::kill();
Digest::kill();
TransactionsRoot::kill();
}
fn post_finalise(header: &Header) {
// store the header hash in storage; we can't do it before otherwise there would be a
// cyclic dependency.
BlockHashAt::insert(&header.number, &header.blake2_256().into());
}
fn calculate_random() -> Hash {
let c = block_number() - 1;
(0..81)
.map(|i| if c >= i { block_hash(c - i) } else { Default::default() })
.triplet_mix()
}
#[cfg(feature = "std")]
fn info_expect_equal_hash(given: &Hash, expected: &Hash) {
use primitives::hexdisplay::HexDisplay;
if given != expected {
println!("Hash: given={}, expected={}", HexDisplay::from(&given.0), HexDisplay::from(&expected.0));
}
}
#[cfg(not(feature = "std"))]
fn info_expect_equal_hash(given: &Hash, expected: &Hash) {
if given != expected {
print("Hash not equal");
print(&given.0[..]);
print(&expected.0[..]);
}
}
#[cfg(any(feature = "std", test))]
pub mod testing {
use super::*;
use runtime_io::{twox_128, TestExternalities};
use codec::Joiner;
pub fn externalities() -> TestExternalities {
map![
twox_128(&BlockHashAt::key_for(&0)).to_vec() => [69u8; 32].encode(),
twox_128(Number::key()).to_vec() => 1u64.encode(),
twox_128(ParentHash::key()).to_vec() => [69u8; 32].encode(),
twox_128(RandomSeed::key()).to_vec() => [0u8; 32].encode()
]
}
pub fn set_block_number(n: BlockNumber) {
Number::put(n);
}
}
#[cfg(test)]
mod tests {
use super::*;
use super::internal::*;
use runtime_io::{with_externalities, twox_128, TestExternalities};
use runtime_support::StorageValue;
use codec::{Joiner, KeyedVec, Slicable};
use keyring::Keyring::*;
use primitives::hexdisplay::HexDisplay;
use demo_primitives::{Header, Digest};
use transaction::{UncheckedTransaction, Transaction};
use runtime::staking;
use dispatch::public::Call as PubCall;
use runtime::staking::public::Call as StakingCall;
#[test]
fn staking_balance_transfer_dispatch_works() {
let mut t: TestExternalities = map![
twox_128(&staking::FreeBalanceOf::key_for(*One)).to_vec() => vec![111u8, 0, 0, 0, 0, 0, 0, 0],
twox_128(staking::TransactionFee::key()).to_vec() => vec![10u8, 0, 0, 0, 0, 0, 0, 0],
twox_128(&BlockHashAt::key_for(&0)).to_vec() => [69u8; 32].encode()
];
let tx = UncheckedTransaction {
transaction: Transaction {
signed: One.into(),
nonce: 0,
function: PubCall::Staking(StakingCall::transfer(Two.into(), 69)),
},
signature: hex!("3a682213cb10e8e375fe0817fe4d220a4622d910088809ed7fc8b4ea3871531dbadb22acfedd28a100a0b7bd2d274e0ff873655b13c88f4640b5569db3222706").into(),
};
with_externalities(&mut t, || {
internal::initialise_block(&Header::from_block_number(1));
internal::execute_transaction(tx);
assert_eq!(staking::balance(&One), 32);
assert_eq!(staking::balance(&Two), 69);
});
}
fn new_test_ext() -> TestExternalities {
staking::testing::externalities(2, 2, 0)
}
#[test]
fn block_import_works() {
let mut t = new_test_ext();
let h = Header {
parent_hash: [69u8; 32].into(),
number: 1,
state_root: hex!("cc3f1f5db826013193e502c76992b5e933b12367e37a269a9822b89218323e9f").into(),
transaction_root: hex!("56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421").into(),
digest: Digest { logs: vec![], },
};
let b = Block {
header: h,
transactions: vec![],
};
with_externalities(&mut t, || {
execute_block(b);
});
}
#[test]
#[should_panic]
fn block_import_of_bad_state_root_fails() {
let mut t = new_test_ext();
let h = Header {
parent_hash: [69u8; 32].into(),
number: 1,
state_root: [0u8; 32].into(),
transaction_root: hex!("56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421").into(),
digest: Digest { logs: vec![], },
};
let b = Block {
header: h,
transactions: vec![],
};
with_externalities(&mut t, || {
execute_block(b);
});
}
#[test]
#[should_panic]
fn block_import_of_bad_transaction_root_fails() {
let mut t = new_test_ext();
let h = Header {
parent_hash: [69u8; 32].into(),
number: 1,
state_root: hex!("1ab2dbb7d4868a670b181327b0b6a58dc64b10cfb9876f737a5aa014b8da31e0").into(),
transaction_root: [0u8; 32].into(),
digest: Digest { logs: vec![], },
};
let b = Block {
header: h,
transactions: vec![],
};
with_externalities(&mut t, || {
execute_block(b);
});
}
}
@@ -1,64 +0,0 @@
// 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 <http://www.gnu.org/licenses/>.
//! Timestamp manager: just handles the current timestamp.
use runtime_support::storage::StorageValue;
use runtime::staking::PublicPass;
pub type Timestamp = u64;
storage_items! {
pub Now: b"tim:val" => required Timestamp;
}
impl_dispatch! {
pub mod public;
fn set(now: Timestamp) = 0;
}
impl<'a> public::Dispatch for PublicPass<'a> {
/// Set the current time.
fn set(self, now: Timestamp) {
Now::put(&now);
}
}
#[cfg(test)]
mod tests {
use super::*;
use super::public::*;
use runtime_io::{with_externalities, twox_128, TestExternalities};
use runtime_support::storage::StorageValue;
use runtime::timestamp;
use codec::{Joiner, KeyedVec};
use demo_primitives::AccountId;
use runtime::staking::PublicPass;
#[test]
fn timestamp_works() {
let mut t: TestExternalities = map![
twox_128(Now::key()).to_vec() => vec![].and(&42u64)
];
with_externalities(&mut t, || {
assert_eq!(Now::get(), 42);
PublicPass::nobody().set(69);
assert_eq!(Now::get(), 69);
});
}
}
-138
View File
@@ -1,138 +0,0 @@
// 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 <http://www.gnu.org/licenses/>.
//! Means of mixing a series of hashes to create a single secure hash.
//!
//! Described in http://www.cs.huji.ac.il/~nati/PAPERS/coll_coin_fl.pdf
use rstd::ops::{BitAnd, BitOr};
pub const MAX_DEPTH: usize = 17;
fn sub_mix<T>(seeds: &[T]) -> T where
T: BitAnd<Output = T> + BitOr<Output = T> + Copy
{
(seeds[0] & seeds[1]) | (seeds[1] & seeds[2]) | (seeds[0] & seeds[2])
}
/// Mix a slice.
pub fn triplet_mix<T>(seeds: &[T]) -> Result<T, ()> where
T: BitAnd<Output = T> + BitOr<Output = T>,
T: Default + Copy
{
Ok(seeds.iter().cloned().triplet_mix())
}
/// The mixed trait for mixing a sequence.
pub trait TripletMix {
/// The items in the sequence and simultaneously the return of the mixing.
type Item;
/// The output of the mixing algorithm on the sequence. Items in the sequence beyond
/// the largest power of three that fits within the the sequence up until `3 ** MAX_DEPTH`
/// are ignored.
fn triplet_mix(self) -> Self::Item;
}
impl<I, T> TripletMix for I where
I: Iterator<Item = T>,
T: BitAnd<Output = T> + BitOr<Output = T> + Default + Copy
{
type Item = T;
fn triplet_mix(self) -> Self::Item {
let mut accum = [[T::default(); 3]; MAX_DEPTH];
let mut result = T::default();
for (i, seed) in self.enumerate() {
accum[0][i % 3] = seed;
let mut index_at_depth = i;
for depth in 0..MAX_DEPTH {
if index_at_depth % 3 != 2 {
break;
}
index_at_depth /= 3;
result = sub_mix(&accum[depth]);
// end of the threesome at depth.
if depth == MAX_DEPTH - 1 {
// end of our stack - bail with result.
break;
} else {
// save in the stack for parent computation
accum[depth + 1][index_at_depth % 3] = result;
}
}
}
result
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn sub_mix_works() {
assert_eq!(sub_mix(&[0, 0, 0][..]), 0);
assert_eq!(sub_mix(&[0, 0, 1][..]), 0);
assert_eq!(sub_mix(&[0, 1, 0][..]), 0);
assert_eq!(sub_mix(&[0, 1, 1][..]), 1);
assert_eq!(sub_mix(&[1, 0, 0][..]), 0);
assert_eq!(sub_mix(&[1, 0, 1][..]), 1);
assert_eq!(sub_mix(&[1, 1, 0][..]), 1);
assert_eq!(sub_mix(&[1, 1, 1][..]), 1);
assert_eq!(sub_mix(&[0, 0, 0][..]), 0);
assert_eq!(sub_mix(&[0, 0, 2][..]), 0);
assert_eq!(sub_mix(&[0, 2, 0][..]), 0);
assert_eq!(sub_mix(&[0, 2, 2][..]), 2);
assert_eq!(sub_mix(&[2, 0, 0][..]), 0);
assert_eq!(sub_mix(&[2, 0, 2][..]), 2);
assert_eq!(sub_mix(&[2, 2, 0][..]), 2);
assert_eq!(sub_mix(&[2, 2, 2][..]), 2);
}
#[test]
fn triplet_mix_works_on_first_level() {
assert_eq!(triplet_mix(&[0, 0, 0][..]).unwrap(), 0);
assert_eq!(triplet_mix(&[0, 0, 1][..]).unwrap(), 0);
assert_eq!(triplet_mix(&[0, 1, 0][..]).unwrap(), 0);
assert_eq!(triplet_mix(&[0, 1, 1][..]).unwrap(), 1);
assert_eq!(triplet_mix(&[1, 0, 0][..]).unwrap(), 0);
assert_eq!(triplet_mix(&[1, 0, 1][..]).unwrap(), 1);
assert_eq!(triplet_mix(&[1, 1, 0][..]).unwrap(), 1);
assert_eq!(triplet_mix(&[1, 1, 1][..]).unwrap(), 1);
assert_eq!(triplet_mix(&[0, 0, 0][..]).unwrap(), 0);
assert_eq!(triplet_mix(&[0, 0, 2][..]).unwrap(), 0);
assert_eq!(triplet_mix(&[0, 2, 0][..]).unwrap(), 0);
assert_eq!(triplet_mix(&[0, 2, 2][..]).unwrap(), 2);
assert_eq!(triplet_mix(&[2, 0, 0][..]).unwrap(), 0);
assert_eq!(triplet_mix(&[2, 0, 2][..]).unwrap(), 2);
assert_eq!(triplet_mix(&[2, 2, 0][..]).unwrap(), 2);
assert_eq!(triplet_mix(&[2, 2, 2][..]).unwrap(), 2);
}
#[test]
fn triplet_mix_works_on_second_level() {
assert_eq!(triplet_mix(&[0, 0, 0, 0, 0, 1, 0, 1, 0][..]).unwrap(), 0);
assert_eq!(triplet_mix(&[0, 1, 1, 1, 0, 0, 1, 0, 1][..]).unwrap(), 1);
assert_eq!(triplet_mix(&[1, 1, 0, 1, 1, 1, 0, 0, 0][..]).unwrap(), 1);
}
#[test]
fn triplet_mix_works_on_third_level() {
assert_eq!(triplet_mix(&[0, 0, 0, 0, 0, 1, 0, 1, 0, 0, 1, 1, 1, 0, 0, 1, 0, 1, 1, 1, 0, 1, 1, 1, 0, 0, 0][..]).unwrap(), 1);
}
}
-188
View File
@@ -1,188 +0,0 @@
// 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 <http://www.gnu.org/licenses/>.
//! Transaction type.
use rstd::prelude::*;
use rstd::ops;
use codec::{Input, Slicable};
use demo_primitives::{AccountId, TxOrder, Signature};
use dispatch::PubCall;
#[cfg(feature = "std")]
use std::fmt;
/// A vetted and verified transaction from the external world.
#[derive(PartialEq, Eq, Clone)]
#[cfg_attr(feature = "std", derive(Serialize, Deserialize, Debug))]
pub struct Transaction {
/// Who signed it (note this is not a signature).
pub signed: AccountId,
/// The number of transactions have come before from the same signer.
pub nonce: TxOrder,
/// The function that should be called.
pub function: PubCall,
}
impl Slicable for Transaction {
fn decode<I: Input>(input: &mut I) -> Option<Self> {
Some(Transaction {
signed: Slicable::decode(input)?,
nonce: Slicable::decode(input)?,
function: Slicable::decode(input)?,
})
}
fn encode(&self) -> Vec<u8> {
let mut v = Vec::new();
self.signed.using_encoded(|s| v.extend(s));
self.nonce.using_encoded(|s| v.extend(s));
self.function.using_encoded(|s| v.extend(s));
v
}
}
impl ::codec::NonTrivialSlicable for Transaction {}
/// A transactions right from the external world. Unchecked.
#[derive(Eq, Clone)]
#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]
pub struct UncheckedTransaction {
/// The actual transaction information.
pub transaction: Transaction,
/// The signature; should be an Ed25519 signature applied to the serialised `transaction` field.
pub signature: Signature,
}
impl Slicable for UncheckedTransaction {
fn decode<I: Input>(input: &mut I) -> Option<Self> {
// This is a little more complicated than usual since the binary format must be compatible
// with substrate's generic `Vec<u8>` type. Basically this just means accepting that there
// will be a prefix of u32, which has the total number of bytes following (we don't need
// to use this).
let _length_do_not_remove_me_see_above: u32 = Slicable::decode(input)?;
Some(UncheckedTransaction {
transaction: Slicable::decode(input)?,
signature: Slicable::decode(input)?,
})
}
fn encode(&self) -> Vec<u8> {
let mut v = Vec::new();
// need to prefix with the total length as u32 to ensure it's binary comptible with
// Vec<u8>. we'll make room for it here, then overwrite once we know the length.
v.extend(&[0u8; 4]);
self.transaction.signed.using_encoded(|s| v.extend(s));
self.transaction.nonce.using_encoded(|s| v.extend(s));
self.transaction.function.using_encoded(|s| v.extend(s));
self.signature.using_encoded(|s| v.extend(s));
let length = (v.len() - 4) as u32;
length.using_encoded(|s| v[0..4].copy_from_slice(s));
v
}
}
impl ::codec::NonTrivialSlicable for UncheckedTransaction {}
impl PartialEq for UncheckedTransaction {
fn eq(&self, other: &Self) -> bool {
self.signature.iter().eq(other.signature.iter()) && self.transaction == other.transaction
}
}
#[cfg(feature = "std")]
impl fmt::Debug for UncheckedTransaction {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "UncheckedTransaction({:?})", self.transaction)
}
}
/// A type-safe indicator that a transaction has been checked.
#[derive(PartialEq, Eq, Clone)]
#[cfg_attr(feature = "std", derive(Debug))]
pub struct CheckedTransaction(UncheckedTransaction);
impl CheckedTransaction {
/// Get a reference to the checked signature.
pub fn signature(&self) -> &Signature {
&self.0.signature
}
/// Get the inner object.
pub fn drain(self) -> UncheckedTransaction {
self.0
}
}
impl ops::Deref for CheckedTransaction {
type Target = Transaction;
fn deref(&self) -> &Transaction {
&self.0.transaction
}
}
/// Check the signature on a transaction.
///
/// On failure, return the transaction back.
pub fn check(tx: UncheckedTransaction) -> Result<CheckedTransaction, UncheckedTransaction> {
let msg = ::codec::Slicable::encode(&tx.transaction);
if ::runtime_io::ed25519_verify(&tx.signature.0, &msg, &tx.transaction.signed) {
Ok(CheckedTransaction(tx))
} else {
Err(tx)
}
}
#[cfg(test)]
mod tests {
use super::*;
use primitives;
use codec::Slicable;
use primitives::hexdisplay::HexDisplay;
use dispatch::public::Call;
use runtime::timestamp::public::Call as TimestampCall;
#[test]
fn serialize_unchecked() {
let tx = UncheckedTransaction {
transaction: Transaction {
signed: [1; 32],
nonce: 999u64,
function: Call::Timestamp(TimestampCall::set(135135)),
},
signature: primitives::hash::H512([0; 64]),
};
// 71000000
// 0101010101010101010101010101010101010101010101010101010101010101
// e703000000000000
// 00
// df0f0200
// 0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000
let v = Slicable::encode(&tx);
println!("{}", HexDisplay::from(&v));
assert_eq!(UncheckedTransaction::decode(&mut &v[..]).unwrap(), tx);
}
}