diff --git a/substrate/Cargo.lock b/substrate/Cargo.lock index 1101568b0f..c724f4c016 100644 --- a/substrate/Cargo.lock +++ b/substrate/Cargo.lock @@ -3817,6 +3817,7 @@ dependencies = [ "srml-system 2.0.0", "srml-timestamp 2.0.0", "substrate-primitives 2.0.0", + "substrate-trie 2.0.0", ] [[package]] diff --git a/substrate/core/sr-primitives/src/lib.rs b/substrate/core/sr-primitives/src/lib.rs index 8201d9237c..607fdff376 100644 --- a/substrate/core/sr-primitives/src/lib.rs +++ b/substrate/core/sr-primitives/src/lib.rs @@ -51,6 +51,9 @@ pub mod transaction_validity; /// Re-export these since they're only "kind of" generic. pub use generic::{DigestItem, Digest}; +/// Re-export this since it's part of the API of this crate. +pub use substrate_primitives::crypto::{key_types, KeyTypeId}; + /// A message indicating an invalid signature in extrinsic. pub const BAD_SIGNATURE: &str = "bad signature in extrinsic"; diff --git a/substrate/core/sr-primitives/src/testing.rs b/substrate/core/sr-primitives/src/testing.rs index 35f3ec476f..f8df25ec59 100644 --- a/substrate/core/sr-primitives/src/testing.rs +++ b/substrate/core/sr-primitives/src/testing.rs @@ -19,8 +19,8 @@ use serde::{Serialize, Serializer, Deserialize, de::Error as DeError, Deserializer}; use std::{fmt::Debug, ops::Deref, fmt}; use crate::codec::{Codec, Encode, Decode}; -use crate::traits::{self, Checkable, Applyable, BlakeTwo256, OpaqueKeys}; -use crate::generic; +use crate::traits::{self, Checkable, Applyable, BlakeTwo256, OpaqueKeys, TypedKey}; +use crate::{generic, KeyTypeId}; use crate::weights::{Weighable, Weight}; pub use substrate_primitives::H256; use substrate_primitives::U256; @@ -37,12 +37,28 @@ impl Into for UintAuthorityId { } } +/// The key-type of the `UintAuthorityId` +pub const UINT_DUMMY_KEY: KeyTypeId = 0xdeadbeef; + +impl TypedKey for UintAuthorityId { + const KEY_TYPE: KeyTypeId = UINT_DUMMY_KEY; +} + impl OpaqueKeys for UintAuthorityId { - fn count() -> usize { 1 } + type KeyTypeIds = std::iter::Cloned>; + + fn key_ids() -> Self::KeyTypeIds { [UINT_DUMMY_KEY].iter().cloned() } // Unsafe, i know, but it's test code and it's just there because it's really convenient to // keep `UintAuthorityId` as a u64 under the hood. - fn get_raw(&self, _: usize) -> &[u8] { unsafe { &std::mem::transmute::<_, &[u8; 8]>(&self.0)[..] } } - fn get(&self, _: usize) -> Option { self.0.using_encoded(|mut x| T::decode(&mut x)) } + fn get_raw(&self, _: KeyTypeId) -> &[u8] { + unsafe { + std::slice::from_raw_parts( + &self.0 as *const _ as *const u8, + std::mem::size_of::(), + ) + } + } + fn get(&self, _: KeyTypeId) -> Option { self.0.using_encoded(|mut x| T::decode(&mut x)) } } /// Digest item diff --git a/substrate/core/sr-primitives/src/traits.rs b/substrate/core/sr-primitives/src/traits.rs index b568af679f..f4b9e8c7b4 100644 --- a/substrate/core/sr-primitives/src/traits.rs +++ b/substrate/core/sr-primitives/src/traits.rs @@ -25,6 +25,7 @@ use substrate_primitives::{self, Hasher, Blake2Hasher}; use crate::codec::{Codec, Encode, Decode, HasCompact}; use crate::transaction_validity::TransactionValidity; use crate::generic::{Digest, DigestItem}; +pub use substrate_primitives::crypto::TypedKey; pub use integer_sqrt::IntegerSquareRoot; pub use num_traits::{ Zero, One, Bounded, CheckedAdd, CheckedSub, CheckedMul, CheckedDiv, @@ -162,6 +163,13 @@ impl Convert for Identity { fn convert(a: T) -> T { a } } +/// A structure that performs standard conversion using the standard Rust conversion traits. +pub struct ConvertInto; + +impl> Convert for ConvertInto { + fn convert(a: A) -> B { a.into() } +} + /// A meta trait for arithmetic. /// /// Arithmetic types do all the usual stuff you'd expect numbers to do. They are guaranteed to @@ -835,12 +843,15 @@ pub trait ValidateUnsigned { /// Opaque datatype that may be destructured into a series of raw byte slices (which represent /// individual keys). pub trait OpaqueKeys: Clone { - /// Return the number of encoded keys. - fn count() -> usize { 0 } - /// Get the raw bytes of key with index `i`. - fn get_raw(&self, i: usize) -> &[u8]; + /// An iterator over the type IDs of keys that this holds. + type KeyTypeIds: IntoIterator; + + /// Return an iterator over the key-type IDs supported by this set. + fn key_ids() -> Self::KeyTypeIds; + /// Get the raw bytes of key with key-type ID `i`. + fn get_raw(&self, i: super::KeyTypeId) -> &[u8]; /// Get the decoded key with index `i`. - fn get(&self, i: usize) -> Option { T::decode(&mut self.get_raw(i)) } + fn get(&self, i: super::KeyTypeId) -> Option { T::decode(&mut self.get_raw(i)) } /// Verify a proof of ownership for the keys. fn ownership_proof_is_valid(&self, _proof: &[u8]) -> bool { true } } @@ -969,41 +980,62 @@ macro_rules! count { }; } -#[macro_export] -/// Just implement `OpaqueKeys` for a given tuple-struct. +/// Implement `OpaqueKeys` for a described struct. /// Would be much nicer for this to be converted to `derive` code. +/// +/// Every field type must be equivalent implement `as_ref()`, which is expected +/// to hold the standard SCALE-encoded form of that key. This is typically +/// just the bytes of the key. +/// +/// ```rust +/// use sr_primitives::{impl_opaque_keys, key_types, KeyTypeId}; +/// +/// impl_opaque_keys! { +/// pub struct Keys { +/// #[id(key_types::ED25519)] +/// pub ed25519: [u8; 32], +/// #[id(key_types::SR25519)] +/// pub sr25519: [u8; 32], +/// } +/// } +/// ``` +#[macro_export] macro_rules! impl_opaque_keys { ( - pub struct $name:ident ( $( $t:ty ),* $(,)* ); - ) => { - impl_opaque_keys! { - pub struct $name ( $( $t ,)* ); - impl OpaqueKeys for _ {} - } - }; - ( - pub struct $name:ident ( $( $t:ty ),* $(,)* ); - impl OpaqueKeys for _ { - $($rest:tt)* + pub struct $name:ident { + $( + #[id($key_id:expr)] + pub $field:ident: $type:ty, + )* } ) => { #[derive(Default, Clone, PartialEq, Eq, $crate::codec::Encode, $crate::codec::Decode)] #[cfg_attr(feature = "std", derive(Debug, $crate::serde::Serialize, $crate::serde::Deserialize))] - pub struct $name($( pub $t ,)*); + pub struct $name { + $( + pub $field: $type, + )* + } + impl $crate::traits::OpaqueKeys for $name { - fn count() -> usize { - let mut c = 0; - $( let _: $t; c += 1; )* - c + type KeyTypeIds = $crate::rstd::iter::Cloned< + $crate::rstd::slice::Iter<'static, $crate::KeyTypeId> + >; + + fn key_ids() -> Self::KeyTypeIds { + [ + $($key_id),* + ].iter().cloned() } - fn get_raw(&self, i: usize) -> &[u8] { - $crate::count!(impl_opaque_keys (!! self i) $($t),*); - &[] + + fn get_raw(&self, i: $crate::KeyTypeId) -> &[u8] { + match i { + $( + i if i == $key_id => self.$field.as_ref(), + )* + _ => &[], + } } - $($rest)* } }; - ( !! $self:ident $param_i:ident $i:tt) => { - if $param_i == $i { return $self.$i.as_ref() } - } } diff --git a/substrate/core/state-machine/src/testing.rs b/substrate/core/state-machine/src/testing.rs index 68b9d28752..759ffb6b96 100644 --- a/substrate/core/state-machine/src/testing.rs +++ b/substrate/core/state-machine/src/testing.rs @@ -41,7 +41,7 @@ pub struct TestExternalities { } impl TestExternalities { - /// Create a new instance of `TestExternalities` + /// Create a new instance of `TestExternalities`. pub fn new(inner: HashMap, Vec>) -> Self { Self::new_with_code(&[], inner) } diff --git a/substrate/core/trie/src/lib.rs b/substrate/core/trie/src/lib.rs index ec6f50d6e3..fe45c4aaf1 100644 --- a/substrate/core/trie/src/lib.rs +++ b/substrate/core/trie/src/lib.rs @@ -36,6 +36,8 @@ pub use node_codec::NodeCodec; pub use trie_db::{Trie, TrieMut, DBValue, Recorder, Query}; /// Various re-exports from the `memory-db` crate. pub use memory_db::{KeyFunction, prefixed_key}; +/// Various re-exports from the `hash-db` crate. +pub use hash_db::HashDB as HashDBT; /// As in `trie_db`, but less generic, error type for the crate. pub type TrieError = trie_db::TrieError; diff --git a/substrate/node/cli/src/chain_spec.rs b/substrate/node/cli/src/chain_spec.rs index 1dd3a602ed..78f74a6e29 100644 --- a/substrate/node/cli/src/chain_spec.rs +++ b/substrate/node/cli/src/chain_spec.rs @@ -40,6 +40,10 @@ pub fn flaming_fir_config() -> Result { ChainSpec::from_embedded(include_bytes!("../res/flaming-fir.json")) } +fn session_keys(key: ed25519::Public) -> SessionKeys { + SessionKeys { ed25519: key } +} + fn staging_testnet_config_genesis() -> GenesisConfig { // stash, controller, session-key // generated with secret: @@ -112,7 +116,7 @@ fn staging_testnet_config_genesis() -> GenesisConfig { .collect::>(), }), session: Some(SessionConfig { - keys: initial_authorities.iter().map(|x| (x.1.clone(), SessionKeys(x.2.clone(),x.2.clone()))).collect::>(), + keys: initial_authorities.iter().map(|x| (x.1.clone(), session_keys(x.2.clone()))).collect::>(), }), staking: Some(StakingConfig { current_era: 0, @@ -239,7 +243,7 @@ pub fn testnet_genesis( vesting: vec![], }), session: Some(SessionConfig { - keys: initial_authorities.iter().map(|x| (x.1.clone(), SessionKeys(x.2.clone(), x.2.clone()))).collect::>(), + keys: initial_authorities.iter().map(|x| (x.1.clone(), session_keys(x.2.clone()))).collect::>(), }), staking: Some(StakingConfig { current_era: 0, diff --git a/substrate/node/executor/src/lib.rs b/substrate/node/executor/src/lib.rs index 8f3dfc8db3..9cf85c2357 100644 --- a/substrate/node/executor/src/lib.rs +++ b/substrate/node/executor/src/lib.rs @@ -55,7 +55,8 @@ mod tests { use node_runtime::{ Header, Block, UncheckedExtrinsic, CheckedExtrinsic, Call, Runtime, Balances, BuildStorage, GenesisConfig, BalancesConfig, SessionConfig, StakingConfig, System, SystemConfig, - GrandpaConfig, IndicesConfig, ContractsConfig, Event, SessionKeys, CENTS, DOLLARS, MILLICENTS + GrandpaConfig, IndicesConfig, ContractsConfig, Event, SessionKeys, + CENTS, DOLLARS, MILLICENTS, }; use wabt; use primitives::map; @@ -290,7 +291,9 @@ mod tests { } fn to_session_keys(ring: &AuthorityKeyring) -> SessionKeys { - SessionKeys(ring.to_owned().into(), ring.to_owned().into()) + SessionKeys { + ed25519: ring.to_owned().into(), + } } fn new_test_ext(code: &[u8], support_changes_trie: bool) -> TestExternalities { diff --git a/substrate/node/runtime/Cargo.toml b/substrate/node/runtime/Cargo.toml index 6d11463834..9796455957 100644 --- a/substrate/node/runtime/Cargo.toml +++ b/substrate/node/runtime/Cargo.toml @@ -26,7 +26,7 @@ executive = { package = "srml-executive", path = "../../srml/executive", default finality-tracker = { package = "srml-finality-tracker", path = "../../srml/finality-tracker", default-features = false } grandpa = { package = "srml-grandpa", path = "../../srml/grandpa", default-features = false } indices = { package = "srml-indices", path = "../../srml/indices", default-features = false } -session = { package = "srml-session", path = "../../srml/session", default-features = false } +session = { package = "srml-session", path = "../../srml/session", default-features = false, features = ["historical"] } staking = { package = "srml-staking", path = "../../srml/staking", default-features = false } system = { package = "srml-system", path = "../../srml/system", default-features = false } timestamp = { package = "srml-timestamp", path = "../../srml/timestamp", default-features = false } diff --git a/substrate/node/runtime/src/lib.rs b/substrate/node/runtime/src/lib.rs index 0206b01375..b3e37fe3c0 100644 --- a/substrate/node/runtime/src/lib.rs +++ b/substrate/node/runtime/src/lib.rs @@ -34,7 +34,7 @@ use client::{ block_builder::api::{self as block_builder_api, InherentData, CheckInherentsResult}, runtime_api as client_api, impl_runtime_apis }; -use runtime_primitives::{ApplyResult, generic, create_runtime_str}; +use runtime_primitives::{ApplyResult, impl_opaque_keys, generic, create_runtime_str, key_types}; use runtime_primitives::transaction_validity::TransactionValidity; use runtime_primitives::traits::{ BlakeTwo256, Block as BlockT, DigestFor, NumberFor, StaticLookup, Convert, @@ -54,7 +54,7 @@ pub use runtime_primitives::BuildStorage; pub use timestamp::Call as TimestampCall; pub use balances::Call as BalancesCall; pub use contracts::Gas; -pub use runtime_primitives::{Permill, Perbill, impl_opaque_keys}; +pub use runtime_primitives::{Permill, Perbill}; pub use support::StorageValue; pub use staking::StakerStatus; @@ -71,7 +71,8 @@ pub const VERSION: RuntimeVersion = RuntimeVersion { // and set impl_version to equal spec_version. If only runtime // implementation changes and behavior does not, then leave spec_version as // is and increment impl_version. - spec_version: 104, + + spec_version: 105, impl_version: 105, apis: RUNTIME_API_VERSIONS, }; @@ -181,8 +182,12 @@ parameter_types! { } type SessionHandlers = (Grandpa, Aura); + impl_opaque_keys! { - pub struct SessionKeys(grandpa::AuthorityId, AuraId); + pub struct SessionKeys { + #[id(key_types::ED25519)] + pub ed25519: GrandpaId, + } } // NOTE: `SessionHandler` and `SessionKeys` are co-dependent: One key will be used for each handler. @@ -197,9 +202,16 @@ impl session::Trait for Runtime { type ShouldEndSession = session::PeriodicSessions; type Event = Event; type Keys = SessionKeys; + type ValidatorId = AccountId; + type ValidatorIdOf = staking::StashOf; type SelectInitialValidators = Staking; } +impl session::historical::Trait for Runtime { + type FullIdentification = staking::Exposure; + type FullIdentificationOf = staking::ExposureOf; +} + parameter_types! { pub const SessionsPerEra: session::SessionIndex = 6; pub const BondingDuration: staking::EraIndex = 24 * 28; @@ -228,6 +240,7 @@ impl staking::Trait for Runtime { type Reward = (); type SessionsPerEra = SessionsPerEra; type BondingDuration = BondingDuration; + type SessionInterface = Self; } parameter_types! { diff --git a/substrate/srml/aura/src/lib.rs b/substrate/srml/aura/src/lib.rs index 43f2da88af..56485b04c1 100644 --- a/substrate/srml/aura/src/lib.rs +++ b/substrate/srml/aura/src/lib.rs @@ -53,7 +53,10 @@ pub use timestamp; use rstd::{result, prelude::*}; use parity_codec::Encode; use srml_support::{decl_storage, decl_module, Parameter, storage::StorageValue}; -use primitives::{traits::{SaturatedConversion, Saturating, Zero, One, Member}, generic::DigestItem}; +use primitives::{ + traits::{SaturatedConversion, Saturating, Zero, One, Member, TypedKey}, + generic::DigestItem, +}; use timestamp::OnTimestampSet; #[cfg(feature = "std")] use timestamp::TimestampInherentData; @@ -153,7 +156,7 @@ pub trait Trait: timestamp::Trait { type HandleReport: HandleReport; /// The identifier type for an authority. - type AuthorityId: Member + Parameter + Default; + type AuthorityId: Member + Parameter + TypedKey + Default; } decl_storage! { @@ -184,6 +187,7 @@ impl Module { impl session::OneSessionHandler for Module { type Key = T::AuthorityId; + fn on_new_session<'a, I: 'a>(changed: bool, validators: I) where I: Iterator { @@ -274,7 +278,8 @@ pub struct StakingSlasher(::rstd::marker::PhantomData); impl HandleReport for StakingSlasher { fn handle_report(report: AuraReport) { - let validators = session::Module::::validators(); + use staking::SessionInterface; + let validators = T::SessionInterface::validators(); report.punish( validators.len(), diff --git a/substrate/srml/aura/src/mock.rs b/substrate/srml/aura/src/mock.rs index 900ef21b32..181a09febb 100644 --- a/substrate/srml/aura/src/mock.rs +++ b/substrate/srml/aura/src/mock.rs @@ -18,7 +18,11 @@ #![cfg(test)] -use primitives::{traits::IdentityLookup, testing::{Header, UintAuthorityId}}; +use primitives::{ + KeyTypeId, + traits::IdentityLookup, + testing::{UINT_DUMMY_KEY, Header, UintAuthorityId}, +}; use srml_support::impl_outer_origin; use runtime_io; use substrate_primitives::{H256, Blake2Hasher}; diff --git a/substrate/srml/babe/src/lib.rs b/substrate/srml/babe/src/lib.rs index 95367de222..32d5271970 100644 --- a/substrate/srml/babe/src/lib.rs +++ b/substrate/srml/babe/src/lib.rs @@ -218,6 +218,7 @@ impl OnTimestampSet for Module { impl session::OneSessionHandler for Module { type Key = AuthorityId; + fn on_new_session<'a, I: 'a>(changed: bool, validators: I) where I: Iterator { diff --git a/substrate/srml/grandpa/src/lib.rs b/substrate/srml/grandpa/src/lib.rs index 1ec1e20680..9ed8fa079e 100644 --- a/substrate/srml/grandpa/src/lib.rs +++ b/substrate/srml/grandpa/src/lib.rs @@ -269,6 +269,7 @@ impl Module { impl session::OneSessionHandler for Module { type Key = AuthorityId; + fn on_new_session<'a, I: 'a>(changed: bool, validators: I) where I: Iterator { diff --git a/substrate/srml/session/Cargo.toml b/substrate/srml/session/Cargo.toml index 6c1556bfff..51b613497f 100644 --- a/substrate/srml/session/Cargo.toml +++ b/substrate/srml/session/Cargo.toml @@ -10,17 +10,19 @@ safe-mix = { version = "1.0", default-features = false} parity-codec = { version = "4.1.1", default-features = false, features = ["derive"] } rstd = { package = "sr-std", path = "../../core/sr-std", default-features = false } primitives = { package = "sr-primitives", path = "../../core/sr-primitives", default-features = false } -runtime_io = { package = "sr-io", path = "../../core/sr-io", default-features = false } srml-support = { path = "../support", default-features = false } system = { package = "srml-system", path = "../system", default-features = false } timestamp = { package = "srml-timestamp", path = "../timestamp", default-features = false } +substrate-trie = { path = "../../core/trie", default-features = false, optional = true } +runtime_io = { package = "sr-io", path = "../../core/sr-io", default-features = false } [dev-dependencies] substrate-primitives = { path = "../../core/primitives" } lazy_static = "1.0" [features] -default = ["std"] +default = ["std", "historical"] +historical = ["substrate-trie"] std = [ "serde", "safe-mix/std", @@ -28,5 +30,6 @@ std = [ "rstd/std", "srml-support/std", "primitives/std", - "timestamp/std" + "timestamp/std", + "substrate-trie/std" ] diff --git a/substrate/srml/session/src/historical.rs b/substrate/srml/session/src/historical.rs new file mode 100644 index 0000000000..99d7b8572b --- /dev/null +++ b/substrate/srml/session/src/historical.rs @@ -0,0 +1,450 @@ +// Copyright 2019 Parity Technologies (UK) Ltd. +// This file is part of Substrate. + +// Substrate 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 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. If not, see . + +//! An opt-in utility for tracking historical sessions in SRML-session. +//! +//! This is generally useful when implementing blockchains that require accountable +//! safety where validators from some amount f prior sessions must remain slashable. +//! +//! Rather than store the full session data for any given session, we instead commit +//! to the roots of merkle tries containing the session data. +//! +//! These roots and proofs of inclusion can be generated at any time during the current session. +//! Afterwards, the proofs can be fed to a consensus module when reporting misbehavior. + +use rstd::prelude::*; +use parity_codec::{Encode, Decode}; +use primitives::KeyTypeId; +use primitives::traits::{Convert, OpaqueKeys, Hash as HashT}; +use srml_support::{ + StorageValue, StorageMap, decl_module, decl_storage, +}; +use srml_support::{Parameter, print}; +use substrate_trie::{MemoryDB, Trie, TrieMut, TrieDBMut, TrieDB, Recorder}; + +use super::{SessionIndex, Module as SessionModule}; + +/// Trait necessary for the historical module. +pub trait Trait: super::Trait { + /// Full identification of the validator. + type FullIdentification: Parameter; + + /// A conversion from validator ID to full identification. + /// + /// This should contain any references to economic actors associated with the + /// validator, since they may be outdated by the time this is queried from a + /// historical trie. + /// + /// This mapping is expected to remain stable in between calls to + /// `Self::OnSessionEnding::on_session_ending` which return new validators. + type FullIdentificationOf: Convert>; +} + +decl_storage! { + trait Store for Module as Session { + /// Mapping from historical session indices to session-data root hash. + HistoricalSessions get(historical_root): map SessionIndex => Option; + /// Queued full identifications for queued sessions whose validators have become obsolete. + CachedObsolete get(cached_obsolete): map SessionIndex + => Option>; + /// The range of historical sessions we store. [first, last) + StoredRange: Option<(SessionIndex, SessionIndex)>; + } +} + +decl_module! { + pub struct Module for enum Call where origin: T::Origin { } +} + +impl Module { + /// Prune historical stored session roots up to (but not including) + /// `up_to`. + pub fn prune_up_to(up_to: SessionIndex) { + ::StoredRange::mutate(|range| { + let (start, end) = match *range { + Some(range) => range, + None => return, // nothing to prune. + }; + + let up_to = rstd::cmp::min(up_to, end); + + if up_to < start { + return // out of bounds. harmless. + } + + (start..up_to).for_each(::HistoricalSessions::remove); + + let new_start = up_to; + *range = if new_start == end { + None // nothing is stored. + } else { + Some((new_start, end)) + } + }) + } +} + +/// Specialization of the crate-level `OnSessionEnding` which returns the old +/// set of full identification when changing the validator set. +pub trait OnSessionEnding: crate::OnSessionEnding { + /// Returns the set of new validators, if any, along with the old validators + /// and their full identifications. + fn on_session_ending(ending: SessionIndex, applied_at: SessionIndex) + -> Option<(Vec, Vec<(ValidatorId, FullIdentification)>)>; +} + +/// An `OnSessionEnding` implementation that wraps an inner `I` and also +/// sets the historical trie root of the ending session. +pub struct NoteHistoricalRoot(rstd::marker::PhantomData<(T, I)>); + +impl crate::OnSessionEnding for NoteHistoricalRoot + where I: OnSessionEnding +{ + fn on_session_ending(ending: SessionIndex, applied_at: SessionIndex) -> Option> { + StoredRange::mutate(|range| { + range.get_or_insert_with(|| (ending, ending)).1 = ending + 1; + }); + + // do all of this _before_ calling the other `on_session_ending` impl + // so that we have e.g. correct exposures from the _current_. + + match ProvingTrie::::generate_for(ending) { + Ok(trie) => >::insert(ending, &trie.root), + Err(reason) => { + print("Failed to generate historical ancestry-inclusion proof."); + print(reason); + } + }; + + // trie has been generated for this session, so it's no longer queued. + >::remove(&ending); + + let (new_validators, old_exposures) = >::on_session_ending(ending, applied_at)?; + + // every session from `ending+1 .. applied_at` now has obsolete `FullIdentification` + // now that a new validator election has occurred. + // we cache these in the trie until those sessions themselves end. + for obsolete in (ending + 1) .. applied_at { + >::insert(obsolete, &old_exposures); + } + + Some(new_validators) + } +} + +type HasherOf = <::Hashing as HashT>::Hasher; + +/// A tuple of the validator's ID and their full identification. +pub type IdentificationTuple = (::ValidatorId, ::FullIdentification); + +/// a trie instance for checking and generating proofs. +pub struct ProvingTrie { + db: MemoryDB>, + root: T::Hash, +} + +impl ProvingTrie { + fn generate_for(now: SessionIndex) -> Result { + let mut db = MemoryDB::default(); + let mut root = Default::default(); + + fn build(root: &mut T::Hash, db: &mut MemoryDB>, validators: I) + -> Result<(), &'static str> + where I: IntoIterator)> + { + let mut trie = TrieDBMut::new(db, root); + for (i, (validator, full_id)) in validators.into_iter().enumerate() { + let i = i as u32; + let keys = match >::load_keys(&validator) { + None => continue, + Some(k) => k, + }; + + let full_id = full_id.or_else(|| T::FullIdentificationOf::convert(validator.clone())); + let full_id = match full_id { + None => return Err("no full identification for a current validator"), + Some(full) => (validator, full), + }; + + // map each key to the owner index. + for key_id in T::Keys::key_ids() { + let key = keys.get_raw(key_id); + let res = (key_id, key).using_encoded(|k| + i.using_encoded(|v| + trie.insert(k, v) + ) + ); + + let _ = res.map_err(|_| "failed to insert into trie")?; + } + + // map each owner index to the full identification. + let _ = i.using_encoded(|k| full_id.using_encoded(|v| trie.insert(k, v))) + .map_err(|_| "failed to insert into trie")?; + } + + Ok(()) + } + + // if the current session's full identifications are obsolete but cached, + // use those. + if let Some(obsolete) = >::get(&now) { + build::(&mut root, &mut db, obsolete.into_iter().map(|(v, f)| (v, Some(f))))? + } else { + let validators = >::validators(); + build::(&mut root, &mut db, validators.into_iter().map(|v| (v, None)))? + } + + Ok(ProvingTrie { + db, + root, + }) + } + + fn from_nodes(root: T::Hash, nodes: &[Vec]) -> Self { + use substrate_trie::HashDBT; + + let mut memory_db = MemoryDB::default(); + for node in nodes { + HashDBT::insert(&mut memory_db, &[], &node[..]); + } + + ProvingTrie { + db: memory_db, + root, + } + } + + /// Prove the full verification data for a given key and key ID. + pub fn prove(&self, key_id: KeyTypeId, key_data: &[u8]) -> Option>> { + let trie = TrieDB::new(&self.db, &self.root).ok()?; + let mut recorder = Recorder::new(); + let val_idx = (key_id, key_data).using_encoded(|s| { + trie.get_with(s, &mut recorder) + .ok()? + .and_then(|raw| u32::decode(&mut &*raw)) + })?; + + val_idx.using_encoded(|s| { + trie.get_with(s, &mut recorder) + .ok()? + .and_then(|raw| >::decode(&mut &*raw)) + })?; + + Some(recorder.drain().into_iter().map(|r| r.data).collect()) + } + + /// Access the underlying trie root. + pub fn root(&self) -> &T::Hash { + &self.root + } + + // Check a proof contained within the current memory-db. Returns `None` if the + // nodes within the current `MemoryDB` are insufficient to query the item. + fn query(&self, key_id: KeyTypeId, key_data: &[u8]) -> Option> { + let trie = TrieDB::new(&self.db, &self.root).ok()?; + let val_idx = (key_id, key_data).using_encoded(|s| trie.get(s)) + .ok()? + .and_then(|raw| u32::decode(&mut &*raw))?; + + val_idx.using_encoded(|s| trie.get(s)) + .ok()? + .and_then(|raw| >::decode(&mut &*raw)) + } + +} + +/// Proof of ownership of a specific key. +#[derive(Encode, Decode, Clone)] +pub struct Proof { + session: SessionIndex, + trie_nodes: Vec>, +} + +impl> srml_support::traits::KeyOwnerProofSystem<(KeyTypeId, D)> + for Module +{ + type Proof = Proof; + type FullIdentification = IdentificationTuple; + + fn prove(key: (KeyTypeId, D)) -> Option { + let session = >::current_index(); + let trie = ProvingTrie::::generate_for(session).ok()?; + + let (id, data) = key; + + trie.prove(id, data.as_ref()).map(|trie_nodes| Proof { + session, + trie_nodes, + }) + } + + fn check_proof(key: (KeyTypeId, D), proof: Proof) -> Option> { + let (id, data) = key; + + if proof.session == >::current_index() { + >::key_owner(id, data.as_ref()).and_then(|owner| + T::FullIdentificationOf::convert(owner.clone()).map(move |id| (owner, id)) + ) + } else { + let root = >::get(&proof.session)?; + let trie = ProvingTrie::::from_nodes(root, &proof.trie_nodes); + + trie.query(id, data.as_ref()) + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + use runtime_io::with_externalities; + use substrate_primitives::Blake2Hasher; + use primitives::{ + traits::OnInitialize, + testing::{UintAuthorityId, UINT_DUMMY_KEY}, + }; + use crate::mock::{ + NEXT_VALIDATORS, force_new_session, + set_next_validators, Test, System, Session, + }; + use srml_support::traits::KeyOwnerProofSystem; + + type Historical = Module; + + fn new_test_ext() -> runtime_io::TestExternalities { + let mut t = system::GenesisConfig::default().build_storage::().unwrap().0; + t.extend(timestamp::GenesisConfig:: { + minimum_period: 5, + }.build_storage().unwrap().0); + let (storage, _child_storage) = crate::GenesisConfig:: { + keys: NEXT_VALIDATORS.with(|l| + l.borrow().iter().cloned().map(|i| (i, UintAuthorityId(i))).collect() + ), + }.build_storage().unwrap(); + t.extend(storage); + runtime_io::TestExternalities::new(t) + } + + #[test] + fn generated_proof_is_good() { + with_externalities(&mut new_test_ext(), || { + set_next_validators(vec![1, 2]); + force_new_session(); + + System::set_block_number(1); + Session::on_initialize(1); + + let encoded_key_1 = UintAuthorityId(1).encode(); + let proof = Historical::prove((UINT_DUMMY_KEY, &encoded_key_1[..])).unwrap(); + + // proof-checking in the same session is OK. + assert!( + Historical::check_proof( + (UINT_DUMMY_KEY, &encoded_key_1[..]), + proof.clone(), + ).is_some() + ); + + set_next_validators(vec![1, 2, 4]); + force_new_session(); + + assert!(Historical::cached_obsolete(&(proof.session + 1)).is_none()); + + System::set_block_number(2); + Session::on_initialize(2); + + assert!(Historical::cached_obsolete(&(proof.session + 1)).is_some()); + + assert!(Historical::historical_root(proof.session).is_some()); + assert!(Session::current_index() > proof.session); + + // proof-checking in the next session is also OK. + assert!( + Historical::check_proof( + (UINT_DUMMY_KEY, &encoded_key_1[..]), + proof.clone(), + ).is_some() + ); + + set_next_validators(vec![1, 2, 5]); + + force_new_session(); + System::set_block_number(3); + Session::on_initialize(3); + + assert!(Historical::cached_obsolete(&(proof.session + 1)).is_none()); + }); + } + + #[test] + fn prune_up_to_works() { + with_externalities(&mut new_test_ext(), || { + for i in 1..101u64 { + set_next_validators(vec![i]); + force_new_session(); + + System::set_block_number(i); + Session::on_initialize(i); + + } + + assert_eq!(StoredRange::get(), Some((0, 100))); + + for i in 1..100 { + assert!(Historical::historical_root(i).is_some()) + } + + Historical::prune_up_to(10); + assert_eq!(StoredRange::get(), Some((10, 100))); + + Historical::prune_up_to(9); + assert_eq!(StoredRange::get(), Some((10, 100))); + + for i in 10..100 { + assert!(Historical::historical_root(i).is_some()) + } + + Historical::prune_up_to(99); + assert_eq!(StoredRange::get(), Some((99, 100))); + + Historical::prune_up_to(100); + assert_eq!(StoredRange::get(), None); + + for i in 101..201u64 { + set_next_validators(vec![i]); + force_new_session(); + + System::set_block_number(i); + Session::on_initialize(i); + + } + + assert_eq!(StoredRange::get(), Some((100, 200))); + + for i in 101..200 { + assert!(Historical::historical_root(i).is_some()) + } + + Historical::prune_up_to(9999); + assert_eq!(StoredRange::get(), None); + + for i in 101..200 { + assert!(Historical::historical_root(i).is_none()) + } + }); + } +} diff --git a/substrate/srml/session/src/lib.rs b/substrate/srml/session/src/lib.rs index 981cc930eb..744e00fa10 100644 --- a/substrate/srml/session/src/lib.rs +++ b/substrate/srml/session/src/lib.rs @@ -29,13 +29,17 @@ //! //! //! - **Session:** A session is a period of time that has a constant set of validators. Validators can only join -//! or exit the validator set at a session change. It is measured in block numbers and set with `set_length` -//! during a session for use in subsequent sessions. +//! or exit the validator set at a session change. It is measured in block numbers. The block where a session is +//! ended is determined by the `ShouldSessionEnd` trait. When the session is ending, a new validator set +//! can be chosen by `OnSessionEnding` implementations. //! - **Session key:** A session key is actually several keys kept together that provide the various signing //! functions required by network authorities/validators in pursuit of their duties. +//! - **Validator ID:** Every account has an associated validator ID. For some simple staking systems, this +//! may just be the same as the account ID. For staking systems using a stash/controller model, +//! the validator ID would be the stash account ID of the controller. //! - **Session key configuration process:** A session key is set using `set_key` for use in the -//! next session. It is stored in `NextKeyFor`, a mapping between the caller's `AccountId` and the session -//! key provided. `set_key` allows users to set their session key prior to becoming a validator. +//! next session. It is stored in `NextKeyFor`, a mapping between the caller's `ValidatorId` and the session +//! keys provided. `set_key` allows users to set their session key prior to being selected as validator. //! It is a public call since it uses `ensure_signed`, which checks that the origin is a signed account. //! As such, the account ID of the origin stored in in `NextKeyFor` may not necessarily be associated with //! a block author or a validator. The session keys of accounts are removed once their account balance is zero. @@ -116,23 +120,30 @@ #![cfg_attr(not(feature = "std"), no_std)] use rstd::{prelude::*, marker::PhantomData, ops::{Sub, Rem}}; -#[cfg(not(feature = "std"))] -use rstd::alloc::borrow::ToOwned; -#[cfg(feature = "std")] -use runtime_io::with_storage; -use parity_codec::Decode; -use primitives::traits::{Zero, Member, OpaqueKeys}; +use parity_codec::{Decode, Encode}; +use primitives::KeyTypeId; +use primitives::traits::{Convert, Zero, Member, OpaqueKeys, TypedKey, Hash}; use srml_support::{ - ConsensusEngineId, StorageValue, StorageMap, for_each_tuple, decl_module, + dispatch::Result, + storage, + ConsensusEngineId, StorageValue, for_each_tuple, decl_module, decl_event, decl_storage, }; -use srml_support::{ensure, traits::{OnFreeBalanceZero, Get, FindAuthor}, Parameter, print}; -use system::ensure_signed; +use srml_support::{ensure, traits::{OnFreeBalanceZero, Get, FindAuthor}, Parameter}; +use system::{self, ensure_signed}; + +#[cfg(test)] +mod mock; + +#[cfg(feature = "historical")] +pub mod historical; /// Simple index type with which we can count sessions. pub type SessionIndex = u32; +/// Decides whether the session should be ended. pub trait ShouldEndSession { + /// Return `true` if the session should be ended. fn should_end_session(now: BlockNumber) -> bool; } @@ -157,28 +168,37 @@ impl< } } -pub trait OnSessionEnding { +/// An event handler for when the session is ending. +pub trait OnSessionEnding { /// Handle the fact that the session is ending, and optionally provide the new validator set. - fn on_session_ending(i: SessionIndex) -> Option>; + /// + /// `ending_index` is the index of the currently ending session. + /// The returned validator set, if any, will not be applied until `next_index`. + /// `next_index` is guaranteed to be at least `ending_index + 1`, since session indices don't + /// repeat. + fn on_session_ending(ending_index: SessionIndex, next_index: SessionIndex) -> Option>; } impl OnSessionEnding for () { - fn on_session_ending(_: SessionIndex) -> Option> { None } + fn on_session_ending(_: SessionIndex, _: SessionIndex) -> Option> { None } } /// Handler for when a session keys set changes. -pub trait SessionHandler { +pub trait SessionHandler { /// Session set has changed; act appropriately. - fn on_new_session(changed: bool, validators: &[(AccountId, Ks)]); + fn on_new_session(changed: bool, validators: &[(ValidatorId, Ks)]); /// A validator got disabled. Act accordingly until a new session begins. fn on_disabled(validator_index: usize); } -pub trait OneSessionHandler { - type Key: Decode + Default; +/// One session-key type handler. +pub trait OneSessionHandler { + /// The key type expected. + type Key: Decode + Default + TypedKey; + fn on_new_session<'a, I: 'a>(changed: bool, validators: I) - where I: Iterator, AccountId: 'a; + where I: Iterator, ValidatorId: 'a; fn on_disabled(i: usize); } @@ -193,11 +213,10 @@ macro_rules! impl_session_handlers { ( $($t:ident)* ) => { impl ),*> SessionHandler for ( $( $t , )* ) { fn on_new_session(changed: bool, validators: &[(AId, Ks)]) { - let mut i: usize = 0; $( - i += 1; let our_keys = validators.iter() - .map(|k| (&k.0, k.1.get::<$t::Key>(i - 1).unwrap_or_default())); + .map(|k| (&k.0, k.1.get::<$t::Key>(<$t::Key as TypedKey>::KEY_TYPE) + .unwrap_or_default())); $t::on_new_session(changed, our_keys); )* } @@ -213,15 +232,17 @@ macro_rules! impl_session_handlers { for_each_tuple!(impl_session_handlers); /// Handler for selecting the genesis validator set. -pub trait SelectInitialValidators { +pub trait SelectInitialValidators { /// Returns the initial validator set. If `None` is returned /// all accounts that have session keys set in the genesis block /// will be validators. - fn select_initial_validators() -> Option>; + fn select_initial_validators() -> Option>; } -impl SelectInitialValidators for () { - fn select_initial_validators() -> Option> { +/// Implementation of `SelectInitialValidators` that does nothing. +pub struct ConfigValidators; +impl SelectInitialValidators for ConfigValidators { + fn select_initial_validators() -> Option> { None } } @@ -230,28 +251,35 @@ pub trait Trait: system::Trait { /// The overarching event type. type Event: From + Into<::Event>; + /// A stable ID for a validator. + type ValidatorId: Member + Parameter; + + /// A conversion to validator ID to account ID. + type ValidatorIdOf: Convert>; + /// Indicator for when to end the session. type ShouldEndSession: ShouldEndSession; /// Handler for when a session is about to end. - type OnSessionEnding: OnSessionEnding; + type OnSessionEnding: OnSessionEnding; /// Handler when a session has changed. - type SessionHandler: SessionHandler; + type SessionHandler: SessionHandler; /// The keys. type Keys: OpaqueKeys + Member + Parameter + Default; /// Select initial validators. - type SelectInitialValidators: SelectInitialValidators; + type SelectInitialValidators: SelectInitialValidators; } -type OpaqueKey = Vec; +const DEDUP_KEY_LEN: usize = 13; +const DEDUP_KEY_PREFIX: &[u8; DEDUP_KEY_LEN] = b":session:keys"; decl_storage! { trait Store for Module as Session { /// The current set of validators. - Validators get(validators): Vec; + Validators get(validators): Vec; /// Current index of the session. CurrentIndex get(current_index): SessionIndex; @@ -259,56 +287,48 @@ decl_storage! { /// True if anything has changed in this session. Changed: bool; - /// The next key to be used for a given validator. At the end of the session, they - /// will be moved into the `QueuedKeys` and so changes here will not take effect for - /// at least one whole session. - NextKeyFor get(next_key_for): map T::AccountId => Option; - /// Queued keys changed. QueuedChanged: bool; /// The queued keys for the next session. When the next session begins, these keys /// will be used to determine the validator's session keys. - QueuedKeys get(queued_keys): Vec<(T::AccountId, T::Keys)>; + QueuedKeys get(queued_keys): Vec<(T::ValidatorId, T::Keys)>; - /// The keys that are currently active. - Active: map u32 => Vec; } add_extra_genesis { - config(keys): Vec<(T::AccountId, T::Keys)>; - build(|storage, _, config: &GenesisConfig| { - with_storage(storage, || { - let all_validators = config.keys.iter() - .map(|(validator, _)| validator.to_owned()) - .collect::>(); - let all_keys = (0..T::Keys::count()).map(|i| ( - i as u32, - config.keys.iter() - .map(|x| x.1.get_raw(i).to_vec()) - .collect::>(), - )).collect::)>>(); - >::put(all_validators.clone()); - for (v, sk) in config.keys.clone() { - >::insert(v, sk); - } - for (i, keys) in all_keys { - Active::insert(i, keys); + config(keys): Vec<(T::ValidatorId, T::Keys)>; + build(| + storage: &mut primitives::StorageOverlay, + _: &mut primitives::ChildrenStorageOverlay, + config: &GenesisConfig + | { + runtime_io::with_storage(storage, || { + for (who, keys) in config.keys.iter().cloned() { + assert!( + >::load_keys(&who).is_none(), + "genesis config contained duplicate validator {:?}", who, + ); + + >::do_set_keys(&who, keys) + .expect("genesis config must not contain duplicates; qed"); } - let selected_validators = - T::SelectInitialValidators::select_initial_validators() - .unwrap_or(all_validators); - let selected_keys = selected_validators.iter().map(|validator| { - ( - validator.to_owned(), - >::get(validator) - .unwrap_or_default() - ) - }).collect::>(); - >::put(selected_validators); - >::put(selected_keys); - }) - }) + let initial_validators = T::SelectInitialValidators::select_initial_validators() + .unwrap_or_else(|| config.keys.iter().map(|(ref v, _)| v.clone()).collect()); + + let queued_keys: Vec<_> = initial_validators + .iter() + .cloned() + .map(|v| ( + v.clone(), + >::load_keys(&v).unwrap_or_default(), + )) + .collect(); + + >::put(initial_validators); + >::put(queued_keys); + }); + }); } } @@ -331,51 +351,25 @@ decl_module! { /// The dispatch origin of this function must be signed. /// /// # - /// - O(1). + /// - O(log n) in number of accounts. /// - One extra DB entry. /// # - fn set_keys(origin, keys: T::Keys, proof: Vec) { + fn set_keys(origin, keys: T::Keys, proof: Vec) -> Result { let who = ensure_signed(origin)?; ensure!(keys.ownership_proof_is_valid(&proof), "invalid ownership proof"); - let old_keys = >::get(&who); - let mut updates = vec![]; + let who = match T::ValidatorIdOf::convert(who) { + Some(val_id) => val_id, + None => return Err("no associated validator ID for account."), + }; - for i in 0..T::Keys::count() { - let new_key = keys.get_raw(i); - let maybe_old_key = old_keys.as_ref().map(|o| o.get_raw(i)); - if maybe_old_key == Some(new_key) { - // no change. - updates.push(None); - continue; - } - let mut active = Active::get(i as u32); - match active.binary_search_by(|k| k[..].cmp(&new_key)) { - Ok(_) => return Err("duplicate key provided"), - Err(pos) => active.insert(pos, new_key.to_owned()), - } - if let Some(old_key) = maybe_old_key { - match active.binary_search_by(|k| k[..].cmp(&old_key)) { - Ok(pos) => { active.remove(pos); } - Err(_) => { - // unreachable as long as our state is valid. we don't want to panic if - // it isn't, though. - print("ERROR: active doesn't contain outgoing key"); - } - } - } - updates.push(Some((i, active))); - } + Self::do_set_keys(&who, keys)?; - // Update the active sets. - for (i, active) in updates.into_iter().filter_map(|x| x) { - Active::insert(i as u32, active); - } - // Set new keys value for next session. - >::insert(who, keys); // Something changed. Changed::put(true); + + Ok(()) } /// Called when a block is finalized. Will rotate session if it is the last @@ -401,12 +395,14 @@ impl Module { // Get queued session keys and validators. let session_keys = >::get(); let validators = session_keys.iter() - .map(|(validator, _)| validator.to_owned()) + .map(|(validator, _)| validator.clone()) .collect::>(); - >::put(validators); + >::put(&validators); + + let applied_at = session_index + 2; // Get next validator set. - let maybe_validators = T::OnSessionEnding::on_session_ending(session_index); + let maybe_validators = T::OnSessionEnding::on_session_ending(session_index, applied_at); let next_validators = if let Some(validators) = maybe_validators { next_changed = true; validators @@ -419,10 +415,11 @@ impl Module { CurrentIndex::put(session_index); // Queue next session keys. - let next_session_keys = next_validators.into_iter() - .map(|a| { let k = >::get(&a).unwrap_or_default(); (a, k) }) + let queued_amalgamated = next_validators.into_iter() + .map(|a| { let k = Self::load_keys(&a).unwrap_or_default(); (a, k) }) .collect::>(); - >::put(next_session_keys); + + >::put(queued_amalgamated); QueuedChanged::put(next_changed); // Record that this happened. @@ -439,15 +436,96 @@ impl Module { } /// Disable the validator identified by `c`. (If using with the staking module, this would be - /// their *controller* account.) - pub fn disable(c: &T::AccountId) -> rstd::result::Result<(), ()> { + /// their *stash* account.) + pub fn disable(c: &T::ValidatorId) -> rstd::result::Result<(), ()> { Self::validators().iter().position(|i| i == c).map(Self::disable_index).ok_or(()) } + + // perform the set_key operation, checking for duplicates. + // does not set `Changed`. + fn do_set_keys(who: &T::ValidatorId, keys: T::Keys) -> Result { + let old_keys = Self::load_keys(&who); + + for id in T::Keys::key_ids() { + let key = keys.get_raw(id); + + // ensure keys are without duplication. + ensure!( + Self::key_owner(id, key).map_or(true, |owner| &owner == who), + "registered duplicate key" + ); + + if let Some(old) = old_keys.as_ref().map(|k| k.get_raw(id)) { + if key == old { + continue; + } + + Self::clear_key_owner(id, old); + } + + Self::put_key_owner(id, key, &who); + } + + Self::put_keys(&who, &keys); + + Ok(()) + } + + fn prune_dead_keys(who: &T::ValidatorId) { + if let Some(old_keys) = Self::take_keys(who) { + for id in T::Keys::key_ids() { + let key_data = old_keys.get_raw(id); + Self::clear_key_owner(id, key_data); + } + + Changed::put(true); + } + } + + // Child trie storage. + + fn load_keys(v: &T::ValidatorId) -> Option { + storage::unhashed::get(&dedup_trie_key::(v)) + } + + fn take_keys(v: &T::ValidatorId) -> Option { + storage::unhashed::take(&dedup_trie_key::(v)) + } + + fn put_keys(v: &T::ValidatorId, keys: &T::Keys) { + storage::unhashed::put(&dedup_trie_key::(v), keys) + } + + fn key_owner(id: KeyTypeId, key_data: &[u8]) -> Option { + storage::unhashed::get(&dedup_trie_key::(&(id, key_data))) + } + + fn put_key_owner(id: KeyTypeId, key_data: &[u8], v: &T::ValidatorId) { + storage::unhashed::put(&dedup_trie_key::(&(id, key_data)), v); + } + + fn clear_key_owner(id: KeyTypeId, key_data: &[u8]) { + storage::unhashed::kill(&dedup_trie_key::(&(id, key_data))); + } } -impl OnFreeBalanceZero for Module { - fn on_free_balance_zero(who: &T::AccountId) { - >::remove(who); +fn dedup_trie_key(key: &K) -> [u8; 32 + DEDUP_KEY_LEN] { + key.using_encoded(|s| { + // take at most 32 bytes from the hash of the value. + let hash = ::Hashing::hash(s); + let hash: &[u8] = hash.as_ref(); + let len = rstd::cmp::min(hash.len(), 32); + + let mut data = [0; 32 + DEDUP_KEY_LEN]; + data[..DEDUP_KEY_LEN].copy_from_slice(DEDUP_KEY_PREFIX); + data[DEDUP_KEY_LEN..][..len].copy_from_slice(hash); + data + }) +} + +impl OnFreeBalanceZero for Module { + fn on_free_balance_zero(who: &T::ValidatorId) { + Self::prune_dead_keys(who); } } @@ -456,10 +534,10 @@ impl OnFreeBalanceZero for Module { /// registering account-ID of that session key index. pub struct FindAccountFromAuthorIndex(rstd::marker::PhantomData<(T, Inner)>); -impl> FindAuthor +impl> FindAuthor for FindAccountFromAuthorIndex { - fn find_author<'a, I>(digests: I) -> Option + fn find_author<'a, I>(digests: I) -> Option where I: 'a + IntoIterator { let i = Inner::find_author(digests)?; @@ -472,102 +550,17 @@ impl> FindAuthor #[cfg(test)] mod tests { use super::*; - use std::cell::RefCell; - use srml_support::{impl_outer_origin, assert_ok}; + use srml_support::assert_ok; use runtime_io::with_externalities; - use substrate_primitives::{H256, Blake2Hasher}; + use substrate_primitives::Blake2Hasher; use primitives::{ - traits::{BlakeTwo256, IdentityLookup, OnInitialize}, testing::{Header, UintAuthorityId} + traits::OnInitialize, + testing::UintAuthorityId, + }; + use mock::{ + NEXT_VALIDATORS, SESSION_CHANGED, TEST_SESSION_CHANGED, authorities, force_new_session, + set_next_validators, set_session_length, session_changed, Test, Origin, System, Session, }; - - impl_outer_origin!{ - pub enum Origin for Test {} - } - - thread_local!{ - static NEXT_VALIDATORS: RefCell> = RefCell::new(vec![1, 2, 3]); - static AUTHORITIES: RefCell> = - RefCell::new(vec![UintAuthorityId(1), UintAuthorityId(2), UintAuthorityId(3)]); - static FORCE_SESSION_END: RefCell = RefCell::new(false); - static SESSION_LENGTH: RefCell = RefCell::new(2); - static SESSION_CHANGED: RefCell = RefCell::new(false); - static TEST_SESSION_CHANGED: RefCell = RefCell::new(false); - } - - pub struct TestShouldEndSession; - impl ShouldEndSession for TestShouldEndSession { - fn should_end_session(now: u64) -> bool { - let l = SESSION_LENGTH.with(|l| *l.borrow()); - now % l == 0 || FORCE_SESSION_END.with(|l| { let r = *l.borrow(); *l.borrow_mut() = false; r }) - } - } - - pub struct TestSessionHandler; - impl SessionHandler for TestSessionHandler { - fn on_new_session(changed: bool, validators: &[(u64, T)]) { - SESSION_CHANGED.with(|l| *l.borrow_mut() = changed); - AUTHORITIES.with(|l| - *l.borrow_mut() = validators.iter().map(|(_, id)| id.get::(0).unwrap_or_default()).collect() - ); - } - fn on_disabled(_validator_index: usize) {} - } - - pub struct TestOnSessionEnding; - impl OnSessionEnding for TestOnSessionEnding { - fn on_session_ending(_: SessionIndex) -> Option> { - if !TEST_SESSION_CHANGED.with(|l| *l.borrow()) { - Some(NEXT_VALIDATORS.with(|l| l.borrow().clone())) - } else { - None - } - } - } - - fn authorities() -> Vec { - AUTHORITIES.with(|l| l.borrow().to_vec()) - } - - fn force_new_session() { - FORCE_SESSION_END.with(|l| *l.borrow_mut() = true ) - } - - fn set_session_length(x: u64) { - SESSION_LENGTH.with(|l| *l.borrow_mut() = x ) - } - - fn session_changed() -> bool { - SESSION_CHANGED.with(|l| *l.borrow()) - } - - #[derive(Clone, Eq, PartialEq)] - pub struct Test; - impl system::Trait for Test { - type Origin = Origin; - type Index = u64; - type BlockNumber = u64; - type Hash = H256; - type Hashing = BlakeTwo256; - type AccountId = u64; - type Lookup = IdentityLookup; - type Header = Header; - type Event = (); - } - impl timestamp::Trait for Test { - type Moment = u64; - type OnTimestampSet = (); - } - impl Trait for Test { - type ShouldEndSession = TestShouldEndSession; - type OnSessionEnding = TestOnSessionEnding; - type SessionHandler = TestSessionHandler; - type Keys = UintAuthorityId; - type Event = (); - type SelectInitialValidators = (); - } - - type System = system::Module; - type Session = Module; fn new_test_ext() -> runtime_io::TestExternalities { TEST_SESSION_CHANGED.with(|l| *l.borrow_mut() = false); @@ -597,11 +590,36 @@ mod tests { }); } + #[test] + fn put_get_keys() { + with_externalities(&mut new_test_ext(), || { + Session::put_keys(&10, &UintAuthorityId(10)); + assert_eq!(Session::load_keys(&10), Some(UintAuthorityId(10))); + }) + } + + #[test] + fn keys_cleared_on_kill() { + let mut ext = new_test_ext(); + with_externalities(&mut ext, || { + assert_eq!(Session::validators(), vec![1, 2, 3]); + assert_eq!(Session::load_keys(&1), Some(UintAuthorityId(1))); + + let id = ::KEY_TYPE; + assert_eq!(Session::key_owner(id, UintAuthorityId(1).get_raw(id)), Some(1)); + + Session::on_free_balance_zero(&1); + assert_eq!(Session::load_keys(&1), None); + assert_eq!(Session::key_owner(id, UintAuthorityId(1).get_raw(id)), None); + + assert!(Changed::get()); + }) + } + #[test] fn authorities_should_track_validators() { with_externalities(&mut new_test_ext(), || { - NEXT_VALIDATORS.with(|v| *v.borrow_mut() = vec![1, 2]); - + set_next_validators(vec![1, 2]); force_new_session(); initialize_block(1); assert_eq!(Session::queued_keys(), vec![ @@ -620,7 +638,7 @@ mod tests { assert_eq!(Session::validators(), vec![1, 2]); assert_eq!(authorities(), vec![UintAuthorityId(1), UintAuthorityId(2)]); - NEXT_VALIDATORS.with(|v| *v.borrow_mut() = vec![1, 2, 4]); + set_next_validators(vec![1, 2, 4]); assert_ok!(Session::set_keys(Origin::signed(4), UintAuthorityId(4), vec![])); force_new_session(); initialize_block(3); @@ -697,6 +715,19 @@ mod tests { }); } + #[test] + fn duplicates_are_not_allowed() { + with_externalities(&mut new_test_ext(), || { + System::set_block_number(1); + Session::on_initialize(1); + assert!(Session::set_keys(Origin::signed(4), UintAuthorityId(1), vec![]).is_err()); + assert!(Session::set_keys(Origin::signed(1), UintAuthorityId(10), vec![]).is_ok()); + + // is fine now that 1 has migrated off. + assert!(Session::set_keys(Origin::signed(4), UintAuthorityId(1), vec![]).is_ok()); + }); + } + #[test] fn session_changed_flag_works() { with_externalities(&mut new_test_ext(), || { diff --git a/substrate/srml/session/src/mock.rs b/substrate/srml/session/src/mock.rs new file mode 100644 index 0000000000..171098fc97 --- /dev/null +++ b/substrate/srml/session/src/mock.rs @@ -0,0 +1,148 @@ +// Copyright 2019 Parity Technologies (UK) Ltd. +// This file is part of Substrate. + +// Substrate 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 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. If not, see . + +//! Mock helpers for Session. + +use super::*; +use std::cell::RefCell; +use srml_support::impl_outer_origin; +use substrate_primitives::H256; +use primitives::{ + traits::{BlakeTwo256, IdentityLookup, ConvertInto}, + testing::{Header, UintAuthorityId} +}; + + +impl_outer_origin! { + pub enum Origin for Test {} +} + +thread_local! { + pub static NEXT_VALIDATORS: RefCell> = RefCell::new(vec![1, 2, 3]); + pub static AUTHORITIES: RefCell> = + RefCell::new(vec![UintAuthorityId(1), UintAuthorityId(2), UintAuthorityId(3)]); + pub static FORCE_SESSION_END: RefCell = RefCell::new(false); + pub static SESSION_LENGTH: RefCell = RefCell::new(2); + pub static SESSION_CHANGED: RefCell = RefCell::new(false); + pub static TEST_SESSION_CHANGED: RefCell = RefCell::new(false); +} + +pub struct TestShouldEndSession; +impl ShouldEndSession for TestShouldEndSession { + fn should_end_session(now: u64) -> bool { + let l = SESSION_LENGTH.with(|l| *l.borrow()); + now % l == 0 || FORCE_SESSION_END.with(|l| { let r = *l.borrow(); *l.borrow_mut() = false; r }) + } +} + +pub struct TestSessionHandler; +impl SessionHandler for TestSessionHandler { + fn on_new_session(changed: bool, validators: &[(u64, T)]) { + SESSION_CHANGED.with(|l| *l.borrow_mut() = changed); + AUTHORITIES.with(|l| + *l.borrow_mut() = validators.iter().map(|(_, id)| id.get::(0).unwrap_or_default()).collect() + ); + } + fn on_disabled(_validator_index: usize) {} +} + +pub struct TestOnSessionEnding; +impl OnSessionEnding for TestOnSessionEnding { + fn on_session_ending(_: SessionIndex, _: SessionIndex) -> Option> { + if !TEST_SESSION_CHANGED.with(|l| *l.borrow()) { + Some(NEXT_VALIDATORS.with(|l| l.borrow().clone())) + } else { + None + } + } +} + +#[cfg(feature = "historical")] +impl crate::historical::OnSessionEnding for TestOnSessionEnding { + fn on_session_ending(_: SessionIndex, _: SessionIndex) + -> Option<(Vec, Vec<(u64, u64)>)> + { + if !TEST_SESSION_CHANGED.with(|l| *l.borrow()) { + let last_validators = Session::validators(); + let last_identifications = last_validators.into_iter().map(|v| (v, v)).collect(); + Some((NEXT_VALIDATORS.with(|l| l.borrow().clone()), last_identifications)) + } else { + None + } + } +} + +pub fn authorities() -> Vec { + AUTHORITIES.with(|l| l.borrow().to_vec()) +} + +pub fn force_new_session() { + FORCE_SESSION_END.with(|l| *l.borrow_mut() = true ) +} + +pub fn set_session_length(x: u64) { + SESSION_LENGTH.with(|l| *l.borrow_mut() = x ) +} + +pub fn session_changed() -> bool { + SESSION_CHANGED.with(|l| *l.borrow()) +} + +pub fn set_next_validators(next: Vec) { + NEXT_VALIDATORS.with(|v| *v.borrow_mut() = next); +} + +#[derive(Clone, Eq, PartialEq)] +pub struct Test; +impl system::Trait for Test { + type Origin = Origin; + type Index = u64; + type BlockNumber = u64; + type Hash = H256; + type Hashing = BlakeTwo256; + type AccountId = u64; + type Lookup = IdentityLookup; + type Header = Header; + type Event = (); +} +impl timestamp::Trait for Test { + type Moment = u64; + type OnTimestampSet = (); +} + + +impl Trait for Test { + type ShouldEndSession = TestShouldEndSession; + #[cfg(feature = "historical")] + type OnSessionEnding = crate::historical::NoteHistoricalRoot; + #[cfg(not(feature = "historical"))] + type OnSessionEnding = TestOnSessionEnding; + type SessionHandler = TestSessionHandler; + type ValidatorId = u64; + type ValidatorIdOf = ConvertInto; + type Keys = UintAuthorityId; + type Event = (); + type SelectInitialValidators = crate::ConfigValidators; +} + +#[cfg(feature = "historical")] +impl crate::historical::Trait for Test { + type FullIdentification = u64; + type FullIdentificationOf = primitives::traits::ConvertInto; +} + +pub type System = system::Module; +pub type Session = Module; diff --git a/substrate/srml/staking/Cargo.toml b/substrate/srml/staking/Cargo.toml index 97c5d1c927..7438449531 100644 --- a/substrate/srml/staking/Cargo.toml +++ b/substrate/srml/staking/Cargo.toml @@ -14,7 +14,7 @@ runtime_io = { package = "sr-io", path = "../../core/sr-io", default-features = primitives = { package = "sr-primitives", path = "../../core/sr-primitives", default-features = false } srml-support = { path = "../support", default-features = false } system = { package = "srml-system", path = "../system", default-features = false } -session = { package = "srml-session", path = "../session", default-features = false } +session = { package = "srml-session", path = "../session", default-features = false, features = ["historical"] } [dev-dependencies] substrate-primitives = { path = "../../core/primitives" } diff --git a/substrate/srml/staking/src/lib.rs b/substrate/srml/staking/src/lib.rs index e91ab5dd12..1a2d1dda28 100644 --- a/substrate/srml/staking/src/lib.rs +++ b/substrate/srml/staking/src/lib.rs @@ -281,10 +281,10 @@ use srml_support::{ WithdrawReasons, OnUnbalanced, Imbalance, Get } }; -use session::{OnSessionEnding, SelectInitialValidators, SessionIndex}; +use session::{historical::OnSessionEnding, SelectInitialValidators, SessionIndex}; use primitives::Perbill; use primitives::traits::{ - Convert, Zero, One, StaticLookup, CheckedSub, CheckedShl, Saturating, Bounded + Convert, Zero, One, StaticLookup, CheckedSub, CheckedShl, Saturating, Bounded, }; #[cfg(feature = "std")] use primitives::{Serialize, Deserialize}; @@ -445,7 +445,43 @@ type ExpoMap = BTreeMap< pub const DEFAULT_SESSIONS_PER_ERA: u32 = 3; pub const DEFAULT_BONDING_DURATION: u32 = 1; -pub trait Trait: system::Trait + session::Trait { +/// Means for interacting with a specialized version of the `session` trait. +/// +/// This is needed because `Staking` sets the `ValidatorId` of the `session::Trait` +pub trait SessionInterface: system::Trait { + /// Disable a given validator by stash ID. + fn disable_validator(validator: &AccountId) -> Result<(), ()>; + /// Get the validators from session. + fn validators() -> Vec; + /// Prune historical session tries up to but not including the given index. + fn prune_historical_up_to(up_to: session::SessionIndex); +} + +impl SessionInterface<::AccountId> for T where + T: session::Trait::AccountId>, + T: session::historical::Trait< + FullIdentification = Exposure<::AccountId, BalanceOf>, + FullIdentificationOf = ExposureOf, + >, + T::SessionHandler: session::SessionHandler<::AccountId>, + T::OnSessionEnding: session::OnSessionEnding<::AccountId>, + T::SelectInitialValidators: session::SelectInitialValidators<::AccountId>, + T::ValidatorIdOf: Convert<::AccountId, Option<::AccountId>> +{ + fn disable_validator(validator: &::AccountId) -> Result<(), ()> { + >::disable(validator) + } + + fn validators() -> Vec<::AccountId> { + >::validators() + } + + fn prune_historical_up_to(up_to: session::SessionIndex) { + >::prune_up_to(up_to); + } +} + +pub trait Trait: system::Trait { /// The staking balance. type Currency: LockableCurrency; @@ -473,6 +509,9 @@ pub trait Trait: system::Trait + session::Trait { /// Number of eras that staked funds must remain bonded for. type BondingDuration: Get; + + /// Interface for interacting with a session module. + type SessionInterface: self::SessionInterface; } decl_storage! { @@ -516,15 +555,6 @@ decl_storage! { /// This is keyed by the stash account. pub Stakers get(stakers): map T::AccountId => Exposure>; - // The historical validators and their nominations for a given era. Stored as a trie root - // of the mapping `T::AccountId` => `Exposure>`, which is just - // the contents of `Stakers`, under a key that is the `era`. - // - // Every era change, this will be appended with the trie root of the contents of `Stakers`, - // and the oldest entry removed down to a specific number of entries (probably around 90 for - // a 3 month history). - // pub HistoricalStakers get(historical_stakers): map T::BlockNumber => Option; - /// The currently elected validator set keyed by stash account ID. pub CurrentElected get(current_elected): Vec; @@ -555,6 +585,9 @@ decl_storage! { /// True if the next session change will be a new era regardless of index. pub ForceNewEra get(forcing_new_era): bool; + + /// A mapping from still-bonded eras to the first session index of that era. + BondedEras: Vec<(EraIndex, SessionIndex)>; } add_extra_genesis { config(stakers): @@ -1003,14 +1036,22 @@ impl Module { T::Reward::on_unbalanced(imbalance); } - /// Session has just ended. Provide the validator set for the next session if it's an era-end. - fn new_session(session_index: SessionIndex) -> Option> { + /// Session has just ended. Provide the validator set for the next session if it's an era-end, along + /// with the exposure of the prior validator set. + fn new_session(session_index: SessionIndex) + -> Option<(Vec, Vec<(T::AccountId, Exposure>)>)> + { // accumulate good session reward let reward = Self::current_session_reward(); >::mutate(|r| *r += reward); if ForceNewEra::take() || session_index % T::SessionsPerEra::get() == 0 { - Self::new_era() + let validators = T::SessionInterface::validators(); + let prior = validators.into_iter() + .map(|v| { let e = Self::stakers(&v); (v, e) }) + .collect(); + + Self::new_era(session_index).map(move |new| (new, prior)) } else { None } @@ -1020,7 +1061,7 @@ impl Module { /// /// 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() -> Option> { + fn new_era(start_session_index: SessionIndex) -> Option> { // Payout let reward = >::take(); if !reward.is_zero() { @@ -1037,7 +1078,26 @@ impl Module { } // Increment current era. - CurrentEra::mutate(|s| *s += 1); + let current_era = CurrentEra::mutate(|s| { *s += 1; *s }); + let bonding_duration = T::BondingDuration::get(); + + if current_era > bonding_duration { + let first_kept = current_era - bonding_duration; + BondedEras::mutate(|bonded| { + bonded.push((current_era, start_session_index)); + + // prune out everything that's from before the first-kept index. + let n_to_prune = bonded.iter() + .take_while(|&&(era_idx, _)| era_idx < first_kept) + .count(); + + bonded.drain(..n_to_prune); + + if let Some(&(_, first_session)) = bonded.first() { + T::SessionInterface::prune_historical_up_to(first_session); + } + }) + } // Reassign all Stakers. let (slot_stake, maybe_new_validators) = Self::select_validators(); @@ -1054,7 +1114,7 @@ impl Module { /// Select a new validator set from the assembled stakers and their role preferences. /// - /// Returns the new `SlotStake` value. + /// Returns the new `SlotStake` value and a set of newly selected _stash_ IDs. fn select_validators() -> (BalanceOf, Option>) { let maybe_elected_set = elect::( Self::validator_count() as usize, @@ -1157,10 +1217,8 @@ impl Module { // Set the new validator set in sessions. >::put(&elected_stashes); - let validators = elected_stashes.into_iter() - .map(|s| Self::bonded(s).unwrap_or_default()) - .collect::>(); - (slot_stake, Some(validators)) + + (slot_stake, Some(elected_stashes)) } else { // There were not enough candidates for even our minimal level of functionality. // This is bad. @@ -1222,7 +1280,7 @@ impl Module { .map(|x| x.min(slash_exposure)) .unwrap_or(slash_exposure); let _ = Self::slash_validator(&stash, slash); - let _ = >::disable(&controller); + let _ = T::SessionInterface::disable_validator(&stash); RawEvent::OfflineSlash(stash.clone(), slash) } else { @@ -1234,9 +1292,17 @@ impl Module { } } -impl OnSessionEnding for Module { - fn on_session_ending(i: SessionIndex) -> Option> { - Self::new_session(i + 1) +impl session::OnSessionEnding for Module { + fn on_session_ending(_ending: SessionIndex, start_session: SessionIndex) -> Option> { + Self::new_session(start_session - 1).map(|(new, _old)| new) + } +} + +impl OnSessionEnding>> for Module { + fn on_session_ending(_ending: SessionIndex, start_session: SessionIndex) + -> Option<(Vec, Vec<(T::AccountId, Exposure>)>)> + { + Self::new_session(start_session - 1) } } @@ -1252,7 +1318,29 @@ impl OnFreeBalanceZero for Module { } } -impl SelectInitialValidators for Module { +/// A `Convert` implementation that finds the stash of the given controller account, +/// if any. +pub struct StashOf(rstd::marker::PhantomData); + +impl Convert> for StashOf { + fn convert(controller: T::AccountId) -> Option { + >::ledger(&controller).map(|l| l.stash) + } +} + +/// A typed conversion from stash account ID to the current exposure of nominators +/// on that account. +pub struct ExposureOf(rstd::marker::PhantomData); + +impl Convert>>> + for ExposureOf +{ + fn convert(validator: T::AccountId) -> Option>> { + Some(>::stakers(&validator)) + } +} + +impl SelectInitialValidators for Module { fn select_initial_validators() -> Option> { >::select_validators().1 } diff --git a/substrate/srml/staking/src/mock.rs b/substrate/srml/staking/src/mock.rs index 6a51d407bc..02a05168c6 100644 --- a/substrate/srml/staking/src/mock.rs +++ b/substrate/srml/staking/src/mock.rs @@ -52,23 +52,24 @@ thread_local! { pub struct TestSessionHandler; impl session::SessionHandler for TestSessionHandler { fn on_new_session(_changed: bool, validators: &[(AccountId, Ks)]) { - SESSION.with(|x| + SESSION.with(|x| { + let v = validators.iter().map(|(ref a, _)| a).cloned().collect::>(); *x.borrow_mut() = (validators.iter().map(|x| x.0.clone()).collect(), HashSet::new()) - ); + }); } fn on_disabled(validator_index: usize) { SESSION.with(|d| { let mut d = d.borrow_mut(); let value = d.0[validator_index]; - println!("on_disabled {} -> {}", validator_index, value); d.1.insert(value); }) } } pub fn is_disabled(validator: AccountId) -> bool { - SESSION.with(|d| d.borrow().1.contains(&validator)) + let stash = Staking::ledger(&validator).unwrap().stash; + SESSION.with(|d| d.borrow().1.contains(&stash)) } pub struct ExistentialDeposit; @@ -103,7 +104,7 @@ parameter_types! { pub const TransactionByteFee: u64 = 0; } impl balances::Trait for Test { - type Balance = u64; + type Balance = Balance; type OnFreeBalanceZero = Staking; type OnNewAccount = (); type Event = (); @@ -121,13 +122,21 @@ parameter_types! { pub const Offset: BlockNumber = 0; } impl session::Trait for Test { - type OnSessionEnding = Staking; + type OnSessionEnding = session::historical::NoteHistoricalRoot; type Keys = UintAuthorityId; type ShouldEndSession = session::PeriodicSessions; type SessionHandler = TestSessionHandler; type Event = (); + type ValidatorId = AccountId; + type ValidatorIdOf = crate::StashOf; type SelectInitialValidators = Staking; } + +impl session::historical::Trait for Test { + type FullIdentification = crate::Exposure; + type FullIdentificationOf = crate::ExposureOf; +} + impl timestamp::Trait for Test { type Moment = u64; type OnTimestampSet = (); @@ -145,6 +154,7 @@ impl Trait for Test { type Reward = (); type SessionsPerEra = SessionsPerEra; type BondingDuration = BondingDuration; + type SessionInterface = Self; } pub struct ExtBuilder { @@ -216,7 +226,7 @@ impl ExtBuilder { let num_validators = self.num_validators.unwrap_or(self.validator_count); let validators = (0..num_validators) - .map(|x| ((x + 1) * 10) as u64) + .map(|x| ((x + 1) * 10 + 1) as u64) .collect::>(); let _ = balances::GenesisConfig::{ @@ -360,6 +370,7 @@ pub fn start_session(session_index: session::SessionIndex) { System::set_block_number((i + 1).into()); Session::on_initialize(System::block_number()); } + assert_eq!(Session::current_index(), session_index); } @@ -367,3 +378,7 @@ pub fn start_era(era_index: EraIndex) { start_session((era_index * 3).into()); assert_eq!(Staking::current_era(), era_index); } + +pub fn validator_controllers() -> Vec { + Session::validators().into_iter().map(|s| Staking::bonded(&s).expect("no controller for validator")).collect() +} diff --git a/substrate/srml/staking/src/tests.rs b/substrate/srml/staking/src/tests.rs index 7362aae820..1491ebe0df 100644 --- a/substrate/srml/staking/src/tests.rs +++ b/substrate/srml/staking/src/tests.rs @@ -458,7 +458,7 @@ fn staking_should_work() { .build(), || { // remember + compare this along with the test. - assert_eq_uvec!(Session::validators(), vec![20, 10]); + assert_eq_uvec!(validator_controllers(), vec![20, 10]); // put some money in account that we'll use. for i in 1..5 { let _ = Balances::make_free_balance_be(&i, 2000); } @@ -470,13 +470,13 @@ fn staking_should_work() { assert_ok!(Staking::validate(Origin::signed(4), ValidatorPrefs::default())); // No effects will be seen so far. - assert_eq_uvec!(Session::validators(), vec![20, 10]); + assert_eq_uvec!(validator_controllers(), vec![20, 10]); // --- Block 2: start_session(2); // No effects will be seen so far. Era has not been yet triggered. - assert_eq_uvec!(Session::validators(), vec![20, 10]); + assert_eq_uvec!(validator_controllers(), vec![20, 10]); // --- Block 3: the validators will now be queued. @@ -486,18 +486,18 @@ fn staking_should_work() { // --- Block 4: the validators will now be changed. start_session(4); - assert_eq_uvec!(Session::validators(), vec![20, 4]); + assert_eq_uvec!(validator_controllers(), vec![20, 4]); // --- Block 4: Unstake 4 as a validator, freeing up the balance stashed in 3 // 4 will chill Staking::chill(Origin::signed(4)).unwrap(); // --- Block 5: nothing. 4 is still there. start_session(5); - assert_eq_uvec!(Session::validators(), vec![20, 4]); + assert_eq_uvec!(validator_controllers(), vec![20, 4]); // --- Block 6: 4 will not be a validator. start_session(7); - assert_eq_uvec!(Session::validators(), vec![20, 10]); + assert_eq_uvec!(validator_controllers(), vec![20, 10]); // Note: the stashed value of 4 is still lock assert_eq!( @@ -521,12 +521,12 @@ fn less_than_needed_candidates_works() { || { assert_eq!(Staking::validator_count(), 4); assert_eq!(Staking::minimum_validator_count(), 1); - assert_eq_uvec!(Session::validators(), vec![30, 20, 10]); + assert_eq_uvec!(validator_controllers(), vec![30, 20, 10]); start_era(1); // Previous set is selected. NO election algorithm is even executed. - assert_eq_uvec!(Session::validators(), vec![30, 20, 10]); + assert_eq_uvec!(validator_controllers(), vec![30, 20, 10]); // But the exposure is updated in a simple way. No external votes exists. This is purely self-vote. assert_eq!(Staking::stakers(10).others.len(), 0); @@ -549,10 +549,14 @@ fn no_candidate_emergency_condition() { .nominate(false) .build(), || { - assert_eq!(Staking::validator_count(), 15); // initial validators - assert_eq_uvec!(Session::validators(), vec![10, 20, 30, 40]); + assert_eq_uvec!(validator_controllers(), vec![10, 20, 30, 40]); + + // set the minimum validator count. + ::MinimumValidatorCount::put(10); + ::ValidatorCount::put(15); + assert_eq!(Staking::validator_count(), 15); let _ = Staking::chill(Origin::signed(10)); @@ -561,7 +565,7 @@ fn no_candidate_emergency_condition() { Session::on_initialize(System::block_number()); // Previous ones are elected. chill is invalidates. TODO: #2494 - assert_eq_uvec!(Session::validators(), vec![10, 20, 30, 40]); + assert_eq_uvec!(validator_controllers(), vec![10, 20, 30, 40]); assert_eq!(Staking::current_elected().len(), 0); }); } @@ -610,7 +614,7 @@ fn nominating_and_rewards_should_work() { .build(), || { // initial validators -- everyone is actually even. - assert_eq_uvec!(Session::validators(), vec![40, 30]); + assert_eq_uvec!(validator_controllers(), vec![40, 30]); // Set payee to controller assert_ok!(Staking::set_payee(Origin::signed(10), RewardDestination::Controller)); @@ -639,7 +643,7 @@ fn nominating_and_rewards_should_work() { start_era(1); // 10 and 20 have more votes, they will be chosen by phragmen. - assert_eq_uvec!(Session::validators(), vec![20, 10]); + assert_eq_uvec!(validator_controllers(), vec![20, 10]); // OLD validators must have already received some rewards. assert_eq!(Balances::total_balance(&40), 1 + 3 * session_reward); @@ -1478,7 +1482,7 @@ fn phragmen_poc_works() { .build(), || { // We don't really care about this. At this point everything is even. - assert_eq_uvec!(Session::validators(), vec![40, 30]); + assert_eq_uvec!(validator_controllers(), vec![40, 30]); // Set payees to Controller assert_ok!(Staking::set_payee(Origin::signed(10), RewardDestination::Controller)); @@ -1502,7 +1506,7 @@ fn phragmen_poc_works() { // New era => election algorithm will trigger start_era(1); - assert_eq_uvec!(Session::validators(), vec![20, 10]); + assert_eq_uvec!(validator_controllers(), vec![20, 10]); assert_eq!(Staking::stakers(11).own, 1000); assert_eq!(Staking::stakers(21).own, 1000); @@ -1574,7 +1578,7 @@ fn phragmen_poc_2_works() { // 30 is elected with stake 1344.2622950819673 and score 0.0007439024390243903 with_externalities(&mut ExtBuilder::default().nominate(false).build(), || { // initial setup of 10 and 20, both validators - assert_eq_uvec!(Session::validators(), vec![20, 10]); + assert_eq_uvec!(validator_controllers(), vec![20, 10]); // Bond [30, 31] as the third validator assert_ok!(Staking::bond_extra(Origin::signed(31), 999)); @@ -1620,7 +1624,7 @@ fn switching_roles() { // Reset reward destination for i in &[10, 20] { assert_ok!(Staking::set_payee(Origin::signed(*i), RewardDestination::Controller)); } - assert_eq_uvec!(Session::validators(), vec![20, 10]); + assert_eq_uvec!(validator_controllers(), vec![20, 10]); // put some money in account that we'll use. for i in 1..7 { let _ = Balances::deposit_creating(&i, 5000); } @@ -1640,19 +1644,19 @@ fn switching_roles() { start_session(1); // no change - assert_eq_uvec!(Session::validators(), vec![20, 10]); + assert_eq_uvec!(validator_controllers(), vec![20, 10]); // new block start_session(2); // no change - assert_eq_uvec!(Session::validators(), vec![20, 10]); + assert_eq_uvec!(validator_controllers(), vec![20, 10]); // new block --> ne era --> new validators start_session(3); // with current nominators 10 and 5 have the most stake - assert_eq_uvec!(Session::validators(), vec![6, 10]); + assert_eq_uvec!(validator_controllers(), vec![6, 10]); // 2 decides to be a validator. Consequences: assert_ok!(Staking::validate(Origin::signed(2), ValidatorPrefs::default())); @@ -1664,14 +1668,14 @@ fn switching_roles() { // Winners: 20 and 2 start_session(4); - assert_eq_uvec!(Session::validators(), vec![6, 10]); + assert_eq_uvec!(validator_controllers(), vec![6, 10]); start_session(5); - assert_eq_uvec!(Session::validators(), vec![6, 10]); + assert_eq_uvec!(validator_controllers(), vec![6, 10]); // ne era start_session(6); - assert_eq_uvec!(Session::validators(), vec![2, 20]); + assert_eq_uvec!(validator_controllers(), vec![2, 20]); check_exposure_all(); check_nominator_all(); @@ -1685,7 +1689,7 @@ fn wrong_vote_is_null() { .validator_pool(true) .build(), || { - assert_eq_uvec!(Session::validators(), vec![40, 30]); + assert_eq_uvec!(validator_controllers(), vec![40, 30]); // put some money in account that we'll use. for i in 1..3 { let _ = Balances::deposit_creating(&i, 5000); } @@ -1700,7 +1704,7 @@ fn wrong_vote_is_null() { // new block start_era(1); - assert_eq_uvec!(Session::validators(), vec![20, 10]); + assert_eq_uvec!(validator_controllers(), vec![20, 10]); }); } @@ -1725,7 +1729,7 @@ fn bond_with_no_staked_value() { start_era(1); - assert_eq_uvec!(Session::validators(), vec![30, 20, 10]); + assert_eq_uvec!(validator_controllers(), vec![30, 20, 10]); // min of 10, 20 and 30 (30 got a payout into staking so it raised it from 1 to 31). assert_eq!(Staking::slot_stake(), 31); @@ -1741,7 +1745,7 @@ fn bond_with_no_staked_value() { start_era(2); // Stingy one is selected - assert_eq_uvec!(Session::validators(), vec![20, 10, 2]); + assert_eq_uvec!(validator_controllers(), vec![20, 10, 2]); assert_eq!(Staking::stakers(1), Exposure { own: 1, total: 501, @@ -1791,7 +1795,7 @@ fn bond_with_little_staked_value_bounded_by_slot_stake() { // 2 is elected. // and fucks up the slot stake. - assert_eq_uvec!(Session::validators(), vec![20, 10, 2]); + assert_eq_uvec!(validator_controllers(), vec![20, 10, 2]); assert_eq!(Staking::slot_stake(), 1); // Old ones are rewarded. @@ -1801,7 +1805,7 @@ fn bond_with_little_staked_value_bounded_by_slot_stake() { start_era(2); - assert_eq_uvec!(Session::validators(), vec![20, 10, 2]); + assert_eq_uvec!(validator_controllers(), vec![20, 10, 2]); assert_eq!(Staking::slot_stake(), 1); let reward = Staking::current_session_reward(); @@ -1841,12 +1845,12 @@ fn phragmen_linear_worse_case_equalize() { assert_ok!(Staking::set_payee(Origin::signed(*i), RewardDestination::Controller)); } - assert_eq_uvec!(Session::validators(), vec![40, 30]); + assert_eq_uvec!(validator_controllers(), vec![40, 30]); assert_ok!(Staking::set_validator_count(7)); start_era(1); - assert_eq_uvec!(Session::validators(), vec![10, 60, 40, 20, 50, 30, 70]); + assert_eq_uvec!(validator_controllers(), vec![10, 60, 40, 20, 50, 30, 70]); assert_eq!(Staking::stakers(11).total, 3000); assert_eq!(Staking::stakers(21).total, 2254); @@ -1871,12 +1875,12 @@ fn phragmen_chooses_correct_number_of_validators() { .build(), || { assert_eq!(Staking::validator_count(), 1); - assert_eq!(Session::validators().len(), 1); + assert_eq!(validator_controllers().len(), 1); System::set_block_number(1); Session::on_initialize(System::block_number()); - assert_eq!(Session::validators().len(), 1); + assert_eq!(validator_controllers().len(), 1); check_exposure_all(); check_nominator_all(); }) @@ -1896,7 +1900,7 @@ fn phragmen_score_should_be_accurate_on_large_stakes() { start_era(1); - assert_eq!(Session::validators(), vec![4, 2]); + assert_eq!(validator_controllers(), vec![4, 2]); check_exposure_all(); check_nominator_all(); }) @@ -1919,7 +1923,7 @@ fn phragmen_should_not_overflow_validators() { start_era(1); - assert_eq_uvec!(Session::validators(), vec![4, 2]); + assert_eq_uvec!(validator_controllers(), vec![4, 2]); // This test will fail this. Will saturate. // check_exposure_all(); @@ -1945,7 +1949,7 @@ fn phragmen_should_not_overflow_nominators() { start_era(1); - assert_eq_uvec!(Session::validators(), vec![4, 2]); + assert_eq_uvec!(validator_controllers(), vec![4, 2]); // Saturate. assert_eq!(Staking::stakers(3).total, u64::max_value()); @@ -1967,7 +1971,7 @@ fn phragmen_should_not_overflow_ultimate() { start_era(1); - assert_eq_uvec!(Session::validators(), vec![4, 2]); + assert_eq_uvec!(validator_controllers(), vec![4, 2]); // Saturate. assert_eq!(Staking::stakers(3).total, u64::max_value()); diff --git a/substrate/srml/support/src/traits.rs b/substrate/srml/support/src/traits.rs index 7dc10a6684..ae33aeff46 100644 --- a/substrate/srml/support/src/traits.rs +++ b/substrate/srml/support/src/traits.rs @@ -126,6 +126,29 @@ pub trait VerifySeal { fn verify_seal(header: &Header) -> Result, &'static str>; } +/// Something which can compute and check proofs of +/// a historical key owner and return full identification data of that +/// key owner. +pub trait KeyOwnerProofSystem { + /// The proof of membership itself. + type Proof: Codec; + /// The full identification of a key owner. + type FullIdentification: Codec; + + /// Prove membership of a key owner in the current block-state. + /// + /// This should typically only be called off-chain, since it may be + /// computationally heavy. + /// + /// Returns `Some` iff the key owner referred to by the given `key` is a + /// member of the current set. + fn prove(key: Key) -> Option; + + /// Check a proof of membership on-chain. Return `Some` iff the proof is + /// valid and recent enough to check. + fn check_proof(key: Key, proof: Self::Proof) -> Option; +} + /// Handler for when some currency "account" decreased in balance for /// some reason. ///