mirror of
https://github.com/pezkuwichain/pezkuwi-subxt.git
synced 2026-06-09 20:11:09 +00:00
Slash and prove membership of prior sessions (#2970)
* skeleton for tracking historical sessions * refactor OpaqueKeys * some more skeleton work * adjust session to new OpaqueKeys API * further refactoring of key-type-ids * session gets validator ID parameter * run up against compiler * tweak staking to support new session changes * first run at child storage for deduplication * Make session use `AccountId` as `ValidatorId` * run up against child trie issues * switch to using normal trie but with a fixed prefix * clear out some println * add dedup test * flesh out historical module more * introduce ExposureOf for staking * test the historical module * WASM compiles * tests all compile * do some mock change * fix bulk of tests * fix staking tests * test obsolecence mechanic * Apply suggestions from code review Co-Authored-By: Bastian Köcher <bkchr@users.noreply.github.com> * some more style nits * a couple more nits * tweak tries * fix typo thie -> this
This commit is contained in:
committed by
GitHub
parent
bb7ff32e77
commit
7df8e52cfe
Generated
+1
@@ -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]]
|
||||
|
||||
@@ -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";
|
||||
|
||||
|
||||
@@ -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<AuthorityId> 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<std::slice::Iter<'static, KeyTypeId>>;
|
||||
|
||||
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<T: Decode>(&self, _: usize) -> Option<T> { 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::<u64>(),
|
||||
)
|
||||
}
|
||||
}
|
||||
fn get<T: Decode>(&self, _: KeyTypeId) -> Option<T> { self.0.using_encoded(|mut x| T::decode(&mut x)) }
|
||||
}
|
||||
|
||||
/// Digest item
|
||||
|
||||
@@ -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<T> Convert<T, T> for Identity {
|
||||
fn convert(a: T) -> T { a }
|
||||
}
|
||||
|
||||
/// A structure that performs standard conversion using the standard Rust conversion traits.
|
||||
pub struct ConvertInto;
|
||||
|
||||
impl<A, B: From<A>> Convert<A, B> 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<Item=super::KeyTypeId>;
|
||||
|
||||
/// 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<T: Decode>(&self, i: usize) -> Option<T> { T::decode(&mut self.get_raw(i)) }
|
||||
fn get<T: Decode>(&self, i: super::KeyTypeId) -> Option<T> { 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() }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -41,7 +41,7 @@ pub struct TestExternalities<H: Hasher, N: ChangesTrieBlockNumber> {
|
||||
}
|
||||
|
||||
impl<H: Hasher, N: ChangesTrieBlockNumber> TestExternalities<H, N> {
|
||||
/// Create a new instance of `TestExternalities`
|
||||
/// Create a new instance of `TestExternalities`.
|
||||
pub fn new(inner: HashMap<Vec<u8>, Vec<u8>>) -> Self {
|
||||
Self::new_with_code(&[], inner)
|
||||
}
|
||||
|
||||
@@ -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<H> = trie_db::TrieError<H, Error>;
|
||||
|
||||
@@ -40,6 +40,10 @@ pub fn flaming_fir_config() -> Result<ChainSpec, String> {
|
||||
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::<Vec<_>>(),
|
||||
}),
|
||||
session: Some(SessionConfig {
|
||||
keys: initial_authorities.iter().map(|x| (x.1.clone(), SessionKeys(x.2.clone(),x.2.clone()))).collect::<Vec<_>>(),
|
||||
keys: initial_authorities.iter().map(|x| (x.1.clone(), session_keys(x.2.clone()))).collect::<Vec<_>>(),
|
||||
}),
|
||||
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::<Vec<_>>(),
|
||||
keys: initial_authorities.iter().map(|x| (x.1.clone(), session_keys(x.2.clone()))).collect::<Vec<_>>(),
|
||||
}),
|
||||
staking: Some(StakingConfig {
|
||||
current_era: 0,
|
||||
|
||||
@@ -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<Blake2Hasher> {
|
||||
|
||||
@@ -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 }
|
||||
|
||||
@@ -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<Period, Offset>;
|
||||
type Event = Event;
|
||||
type Keys = SessionKeys;
|
||||
type ValidatorId = AccountId;
|
||||
type ValidatorIdOf = staking::StashOf<Self>;
|
||||
type SelectInitialValidators = Staking;
|
||||
}
|
||||
|
||||
impl session::historical::Trait for Runtime {
|
||||
type FullIdentification = staking::Exposure<AccountId, Balance>;
|
||||
type FullIdentificationOf = staking::ExposureOf<Runtime>;
|
||||
}
|
||||
|
||||
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! {
|
||||
|
||||
@@ -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<T: Trait> Module<T> {
|
||||
|
||||
impl<T: Trait> session::OneSessionHandler<T::AccountId> for Module<T> {
|
||||
type Key = T::AuthorityId;
|
||||
|
||||
fn on_new_session<'a, I: 'a>(changed: bool, validators: I)
|
||||
where I: Iterator<Item=(&'a T::AccountId, T::AuthorityId)>
|
||||
{
|
||||
@@ -274,7 +278,8 @@ pub struct StakingSlasher<T>(::rstd::marker::PhantomData<T>);
|
||||
|
||||
impl<T: staking::Trait + Trait> HandleReport for StakingSlasher<T> {
|
||||
fn handle_report(report: AuraReport) {
|
||||
let validators = session::Module::<T>::validators();
|
||||
use staking::SessionInterface;
|
||||
let validators = T::SessionInterface::validators();
|
||||
|
||||
report.punish(
|
||||
validators.len(),
|
||||
|
||||
@@ -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};
|
||||
|
||||
@@ -218,6 +218,7 @@ impl<T: Trait> OnTimestampSet<T::Moment> for Module<T> {
|
||||
|
||||
impl<T: Trait> session::OneSessionHandler<T::AccountId> for Module<T> {
|
||||
type Key = AuthorityId;
|
||||
|
||||
fn on_new_session<'a, I: 'a>(changed: bool, validators: I)
|
||||
where I: Iterator<Item=(&'a T::AccountId, AuthorityId)>
|
||||
{
|
||||
|
||||
@@ -269,6 +269,7 @@ impl<T: Trait> Module<T> {
|
||||
|
||||
impl<T: Trait> session::OneSessionHandler<T::AccountId> for Module<T> {
|
||||
type Key = AuthorityId;
|
||||
|
||||
fn on_new_session<'a, I: 'a>(changed: bool, validators: I)
|
||||
where I: Iterator<Item=(&'a T::AccountId, AuthorityId)>
|
||||
{
|
||||
|
||||
@@ -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"
|
||||
]
|
||||
|
||||
@@ -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 <http://www.gnu.org/licenses/>.
|
||||
|
||||
//! 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<Self::ValidatorId, Option<Self::FullIdentification>>;
|
||||
}
|
||||
|
||||
decl_storage! {
|
||||
trait Store for Module<T: Trait> as Session {
|
||||
/// Mapping from historical session indices to session-data root hash.
|
||||
HistoricalSessions get(historical_root): map SessionIndex => Option<T::Hash>;
|
||||
/// Queued full identifications for queued sessions whose validators have become obsolete.
|
||||
CachedObsolete get(cached_obsolete): map SessionIndex
|
||||
=> Option<Vec<(T::ValidatorId, T::FullIdentification)>>;
|
||||
/// The range of historical sessions we store. [first, last)
|
||||
StoredRange: Option<(SessionIndex, SessionIndex)>;
|
||||
}
|
||||
}
|
||||
|
||||
decl_module! {
|
||||
pub struct Module<T: Trait> for enum Call where origin: T::Origin { }
|
||||
}
|
||||
|
||||
impl<T: Trait> Module<T> {
|
||||
/// Prune historical stored session roots up to (but not including)
|
||||
/// `up_to`.
|
||||
pub fn prune_up_to(up_to: SessionIndex) {
|
||||
<Self as Store>::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(<Self as Store>::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<ValidatorId, FullIdentification>: crate::OnSessionEnding<ValidatorId> {
|
||||
/// 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<ValidatorId>, 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<T, I>(rstd::marker::PhantomData<(T, I)>);
|
||||
|
||||
impl<T: Trait, I> crate::OnSessionEnding<T::ValidatorId> for NoteHistoricalRoot<T, I>
|
||||
where I: OnSessionEnding<T::ValidatorId, T::FullIdentification>
|
||||
{
|
||||
fn on_session_ending(ending: SessionIndex, applied_at: SessionIndex) -> Option<Vec<T::ValidatorId>> {
|
||||
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::<T>::generate_for(ending) {
|
||||
Ok(trie) => <HistoricalSessions<T>>::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.
|
||||
<CachedObsolete<T>>::remove(&ending);
|
||||
|
||||
let (new_validators, old_exposures) = <I as OnSessionEnding<_, _>>::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 {
|
||||
<CachedObsolete<T>>::insert(obsolete, &old_exposures);
|
||||
}
|
||||
|
||||
Some(new_validators)
|
||||
}
|
||||
}
|
||||
|
||||
type HasherOf<T> = <<T as system::Trait>::Hashing as HashT>::Hasher;
|
||||
|
||||
/// A tuple of the validator's ID and their full identification.
|
||||
pub type IdentificationTuple<T> = (<T as crate::Trait>::ValidatorId, <T as Trait>::FullIdentification);
|
||||
|
||||
/// a trie instance for checking and generating proofs.
|
||||
pub struct ProvingTrie<T: Trait> {
|
||||
db: MemoryDB<HasherOf<T>>,
|
||||
root: T::Hash,
|
||||
}
|
||||
|
||||
impl<T: Trait> ProvingTrie<T> {
|
||||
fn generate_for(now: SessionIndex) -> Result<Self, &'static str> {
|
||||
let mut db = MemoryDB::default();
|
||||
let mut root = Default::default();
|
||||
|
||||
fn build<T: Trait, I>(root: &mut T::Hash, db: &mut MemoryDB<HasherOf<T>>, validators: I)
|
||||
-> Result<(), &'static str>
|
||||
where I: IntoIterator<Item=(T::ValidatorId, Option<T::FullIdentification>)>
|
||||
{
|
||||
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 <SessionModule<T>>::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) = <CachedObsolete<T>>::get(&now) {
|
||||
build::<T, _>(&mut root, &mut db, obsolete.into_iter().map(|(v, f)| (v, Some(f))))?
|
||||
} else {
|
||||
let validators = <SessionModule<T>>::validators();
|
||||
build::<T, _>(&mut root, &mut db, validators.into_iter().map(|v| (v, None)))?
|
||||
}
|
||||
|
||||
Ok(ProvingTrie {
|
||||
db,
|
||||
root,
|
||||
})
|
||||
}
|
||||
|
||||
fn from_nodes(root: T::Hash, nodes: &[Vec<u8>]) -> 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<Vec<Vec<u8>>> {
|
||||
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| <IdentificationTuple<T>>::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<IdentificationTuple<T>> {
|
||||
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| <IdentificationTuple<T>>::decode(&mut &*raw))
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/// Proof of ownership of a specific key.
|
||||
#[derive(Encode, Decode, Clone)]
|
||||
pub struct Proof {
|
||||
session: SessionIndex,
|
||||
trie_nodes: Vec<Vec<u8>>,
|
||||
}
|
||||
|
||||
impl<T: Trait, D: AsRef<[u8]>> srml_support::traits::KeyOwnerProofSystem<(KeyTypeId, D)>
|
||||
for Module<T>
|
||||
{
|
||||
type Proof = Proof;
|
||||
type FullIdentification = IdentificationTuple<T>;
|
||||
|
||||
fn prove(key: (KeyTypeId, D)) -> Option<Self::Proof> {
|
||||
let session = <SessionModule<T>>::current_index();
|
||||
let trie = ProvingTrie::<T>::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<IdentificationTuple<T>> {
|
||||
let (id, data) = key;
|
||||
|
||||
if proof.session == <SessionModule<T>>::current_index() {
|
||||
<SessionModule<T>>::key_owner(id, data.as_ref()).and_then(|owner|
|
||||
T::FullIdentificationOf::convert(owner.clone()).map(move |id| (owner, id))
|
||||
)
|
||||
} else {
|
||||
let root = <HistoricalSessions<T>>::get(&proof.session)?;
|
||||
let trie = ProvingTrie::<T>::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<Test>;
|
||||
|
||||
fn new_test_ext() -> runtime_io::TestExternalities<Blake2Hasher> {
|
||||
let mut t = system::GenesisConfig::default().build_storage::<Test>().unwrap().0;
|
||||
t.extend(timestamp::GenesisConfig::<Test> {
|
||||
minimum_period: 5,
|
||||
}.build_storage().unwrap().0);
|
||||
let (storage, _child_storage) = crate::GenesisConfig::<Test> {
|
||||
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())
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
+248
-217
@@ -29,13 +29,17 @@
|
||||
//! <!-- Original author of paragraph: @gavofyork -->
|
||||
//!
|
||||
//! - **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<BlockNumber> {
|
||||
/// Return `true` if the session should be ended.
|
||||
fn should_end_session(now: BlockNumber) -> bool;
|
||||
}
|
||||
|
||||
@@ -157,28 +168,37 @@ impl<
|
||||
}
|
||||
}
|
||||
|
||||
pub trait OnSessionEnding<AccountId> {
|
||||
/// An event handler for when the session is ending.
|
||||
pub trait OnSessionEnding<ValidatorId> {
|
||||
/// Handle the fact that the session is ending, and optionally provide the new validator set.
|
||||
fn on_session_ending(i: SessionIndex) -> Option<Vec<AccountId>>;
|
||||
///
|
||||
/// `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<Vec<ValidatorId>>;
|
||||
}
|
||||
|
||||
impl<A> OnSessionEnding<A> for () {
|
||||
fn on_session_ending(_: SessionIndex) -> Option<Vec<A>> { None }
|
||||
fn on_session_ending(_: SessionIndex, _: SessionIndex) -> Option<Vec<A>> { None }
|
||||
}
|
||||
|
||||
/// Handler for when a session keys set changes.
|
||||
pub trait SessionHandler<AccountId> {
|
||||
pub trait SessionHandler<ValidatorId> {
|
||||
/// Session set has changed; act appropriately.
|
||||
fn on_new_session<Ks: OpaqueKeys>(changed: bool, validators: &[(AccountId, Ks)]);
|
||||
fn on_new_session<Ks: OpaqueKeys>(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<AccountId> {
|
||||
type Key: Decode + Default;
|
||||
/// One session-key type handler.
|
||||
pub trait OneSessionHandler<ValidatorId> {
|
||||
/// The key type expected.
|
||||
type Key: Decode + Default + TypedKey;
|
||||
|
||||
fn on_new_session<'a, I: 'a>(changed: bool, validators: I)
|
||||
where I: Iterator<Item=(&'a AccountId, Self::Key)>, AccountId: 'a;
|
||||
where I: Iterator<Item=(&'a ValidatorId, Self::Key)>, ValidatorId: 'a;
|
||||
fn on_disabled(i: usize);
|
||||
}
|
||||
|
||||
@@ -193,11 +213,10 @@ macro_rules! impl_session_handlers {
|
||||
( $($t:ident)* ) => {
|
||||
impl<AId, $( $t: OneSessionHandler<AId> ),*> SessionHandler<AId> for ( $( $t , )* ) {
|
||||
fn on_new_session<Ks: OpaqueKeys>(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<T: Trait> {
|
||||
pub trait SelectInitialValidators<ValidatorId> {
|
||||
/// 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<Vec<T::AccountId>>;
|
||||
fn select_initial_validators() -> Option<Vec<ValidatorId>>;
|
||||
}
|
||||
|
||||
impl<T: Trait> SelectInitialValidators<T> for () {
|
||||
fn select_initial_validators() -> Option<Vec<T::AccountId>> {
|
||||
/// Implementation of `SelectInitialValidators` that does nothing.
|
||||
pub struct ConfigValidators;
|
||||
impl<V> SelectInitialValidators<V> for ConfigValidators {
|
||||
fn select_initial_validators() -> Option<Vec<V>> {
|
||||
None
|
||||
}
|
||||
}
|
||||
@@ -230,28 +251,35 @@ pub trait Trait: system::Trait {
|
||||
/// The overarching event type.
|
||||
type Event: From<Event> + Into<<Self as system::Trait>::Event>;
|
||||
|
||||
/// A stable ID for a validator.
|
||||
type ValidatorId: Member + Parameter;
|
||||
|
||||
/// A conversion to validator ID to account ID.
|
||||
type ValidatorIdOf: Convert<Self::AccountId, Option<Self::ValidatorId>>;
|
||||
|
||||
/// Indicator for when to end the session.
|
||||
type ShouldEndSession: ShouldEndSession<Self::BlockNumber>;
|
||||
|
||||
/// Handler for when a session is about to end.
|
||||
type OnSessionEnding: OnSessionEnding<Self::AccountId>;
|
||||
type OnSessionEnding: OnSessionEnding<Self::ValidatorId>;
|
||||
|
||||
/// Handler when a session has changed.
|
||||
type SessionHandler: SessionHandler<Self::AccountId>;
|
||||
type SessionHandler: SessionHandler<Self::ValidatorId>;
|
||||
|
||||
/// The keys.
|
||||
type Keys: OpaqueKeys + Member + Parameter + Default;
|
||||
|
||||
/// Select initial validators.
|
||||
type SelectInitialValidators: SelectInitialValidators<Self>;
|
||||
type SelectInitialValidators: SelectInitialValidators<Self::ValidatorId>;
|
||||
}
|
||||
|
||||
type OpaqueKey = Vec<u8>;
|
||||
const DEDUP_KEY_LEN: usize = 13;
|
||||
const DEDUP_KEY_PREFIX: &[u8; DEDUP_KEY_LEN] = b":session:keys";
|
||||
|
||||
decl_storage! {
|
||||
trait Store for Module<T: Trait> as Session {
|
||||
/// The current set of validators.
|
||||
Validators get(validators): Vec<T::AccountId>;
|
||||
Validators get(validators): Vec<T::ValidatorId>;
|
||||
|
||||
/// 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<T::Keys>;
|
||||
|
||||
/// 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<OpaqueKey>;
|
||||
}
|
||||
add_extra_genesis {
|
||||
config(keys): Vec<(T::AccountId, T::Keys)>;
|
||||
build(|storage, _, config: &GenesisConfig<T>| {
|
||||
with_storage(storage, || {
|
||||
let all_validators = config.keys.iter()
|
||||
.map(|(validator, _)| validator.to_owned())
|
||||
.collect::<Vec<_>>();
|
||||
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::<Vec<OpaqueKey>>(),
|
||||
)).collect::<Vec<(u32, Vec<OpaqueKey>)>>();
|
||||
<Validators<T>>::put(all_validators.clone());
|
||||
for (v, sk) in config.keys.clone() {
|
||||
<NextKeyFor<T>>::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<T>
|
||||
| {
|
||||
runtime_io::with_storage(storage, || {
|
||||
for (who, keys) in config.keys.iter().cloned() {
|
||||
assert!(
|
||||
<Module<T>>::load_keys(&who).is_none(),
|
||||
"genesis config contained duplicate validator {:?}", who,
|
||||
);
|
||||
|
||||
<Module<T>>::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(),
|
||||
<NextKeyFor<T>>::get(validator)
|
||||
.unwrap_or_default()
|
||||
)
|
||||
}).collect::<Vec<_>>();
|
||||
<Validators<T>>::put(selected_validators);
|
||||
<QueuedKeys<T>>::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(),
|
||||
<Module<T>>::load_keys(&v).unwrap_or_default(),
|
||||
))
|
||||
.collect();
|
||||
|
||||
<Validators<T>>::put(initial_validators);
|
||||
<QueuedKeys<T>>::put(queued_keys);
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -331,51 +351,25 @@ decl_module! {
|
||||
/// The dispatch origin of this function must be signed.
|
||||
///
|
||||
/// # <weight>
|
||||
/// - O(1).
|
||||
/// - O(log n) in number of accounts.
|
||||
/// - One extra DB entry.
|
||||
/// # </weight>
|
||||
fn set_keys(origin, keys: T::Keys, proof: Vec<u8>) {
|
||||
fn set_keys(origin, keys: T::Keys, proof: Vec<u8>) -> Result {
|
||||
let who = ensure_signed(origin)?;
|
||||
|
||||
ensure!(keys.ownership_proof_is_valid(&proof), "invalid ownership proof");
|
||||
|
||||
let old_keys = <NextKeyFor<T>>::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.
|
||||
<NextKeyFor<T>>::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<T: Trait> Module<T> {
|
||||
// Get queued session keys and validators.
|
||||
let session_keys = <QueuedKeys<T>>::get();
|
||||
let validators = session_keys.iter()
|
||||
.map(|(validator, _)| validator.to_owned())
|
||||
.map(|(validator, _)| validator.clone())
|
||||
.collect::<Vec<_>>();
|
||||
<Validators<T>>::put(validators);
|
||||
<Validators<T>>::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<T: Trait> Module<T> {
|
||||
CurrentIndex::put(session_index);
|
||||
|
||||
// Queue next session keys.
|
||||
let next_session_keys = next_validators.into_iter()
|
||||
.map(|a| { let k = <NextKeyFor<T>>::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::<Vec<_>>();
|
||||
<QueuedKeys<T>>::put(next_session_keys);
|
||||
|
||||
<QueuedKeys<T>>::put(queued_amalgamated);
|
||||
QueuedChanged::put(next_changed);
|
||||
|
||||
// Record that this happened.
|
||||
@@ -439,15 +436,96 @@ impl<T: Trait> Module<T> {
|
||||
}
|
||||
|
||||
/// 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<T::Keys> {
|
||||
storage::unhashed::get(&dedup_trie_key::<T, _>(v))
|
||||
}
|
||||
|
||||
fn take_keys(v: &T::ValidatorId) -> Option<T::Keys> {
|
||||
storage::unhashed::take(&dedup_trie_key::<T, _>(v))
|
||||
}
|
||||
|
||||
fn put_keys(v: &T::ValidatorId, keys: &T::Keys) {
|
||||
storage::unhashed::put(&dedup_trie_key::<T, _>(v), keys)
|
||||
}
|
||||
|
||||
fn key_owner(id: KeyTypeId, key_data: &[u8]) -> Option<T::ValidatorId> {
|
||||
storage::unhashed::get(&dedup_trie_key::<T, _>(&(id, key_data)))
|
||||
}
|
||||
|
||||
fn put_key_owner(id: KeyTypeId, key_data: &[u8], v: &T::ValidatorId) {
|
||||
storage::unhashed::put(&dedup_trie_key::<T, _>(&(id, key_data)), v);
|
||||
}
|
||||
|
||||
fn clear_key_owner(id: KeyTypeId, key_data: &[u8]) {
|
||||
storage::unhashed::kill(&dedup_trie_key::<T, _>(&(id, key_data)));
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Trait> OnFreeBalanceZero<T::AccountId> for Module<T> {
|
||||
fn on_free_balance_zero(who: &T::AccountId) {
|
||||
<NextKeyFor<T>>::remove(who);
|
||||
fn dedup_trie_key<T: Trait, K: Encode>(key: &K) -> [u8; 32 + DEDUP_KEY_LEN] {
|
||||
key.using_encoded(|s| {
|
||||
// take at most 32 bytes from the hash of the value.
|
||||
let hash = <T as system::Trait>::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<T: Trait> OnFreeBalanceZero<T::ValidatorId> for Module<T> {
|
||||
fn on_free_balance_zero(who: &T::ValidatorId) {
|
||||
Self::prune_dead_keys(who);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -456,10 +534,10 @@ impl<T: Trait> OnFreeBalanceZero<T::AccountId> for Module<T> {
|
||||
/// registering account-ID of that session key index.
|
||||
pub struct FindAccountFromAuthorIndex<T, Inner>(rstd::marker::PhantomData<(T, Inner)>);
|
||||
|
||||
impl<T: Trait, Inner: FindAuthor<u32>> FindAuthor<T::AccountId>
|
||||
impl<T: Trait, Inner: FindAuthor<u32>> FindAuthor<T::ValidatorId>
|
||||
for FindAccountFromAuthorIndex<T, Inner>
|
||||
{
|
||||
fn find_author<'a, I>(digests: I) -> Option<T::AccountId>
|
||||
fn find_author<'a, I>(digests: I) -> Option<T::ValidatorId>
|
||||
where I: 'a + IntoIterator<Item=(ConsensusEngineId, &'a [u8])>
|
||||
{
|
||||
let i = Inner::find_author(digests)?;
|
||||
@@ -472,102 +550,17 @@ impl<T: Trait, Inner: FindAuthor<u32>> FindAuthor<T::AccountId>
|
||||
#[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<Vec<u64>> = RefCell::new(vec![1, 2, 3]);
|
||||
static AUTHORITIES: RefCell<Vec<UintAuthorityId>> =
|
||||
RefCell::new(vec![UintAuthorityId(1), UintAuthorityId(2), UintAuthorityId(3)]);
|
||||
static FORCE_SESSION_END: RefCell<bool> = RefCell::new(false);
|
||||
static SESSION_LENGTH: RefCell<u64> = RefCell::new(2);
|
||||
static SESSION_CHANGED: RefCell<bool> = RefCell::new(false);
|
||||
static TEST_SESSION_CHANGED: RefCell<bool> = RefCell::new(false);
|
||||
}
|
||||
|
||||
pub struct TestShouldEndSession;
|
||||
impl ShouldEndSession<u64> 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<u64> for TestSessionHandler {
|
||||
fn on_new_session<T: OpaqueKeys>(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::<UintAuthorityId>(0).unwrap_or_default()).collect()
|
||||
);
|
||||
}
|
||||
fn on_disabled(_validator_index: usize) {}
|
||||
}
|
||||
|
||||
pub struct TestOnSessionEnding;
|
||||
impl OnSessionEnding<u64> for TestOnSessionEnding {
|
||||
fn on_session_ending(_: SessionIndex) -> Option<Vec<u64>> {
|
||||
if !TEST_SESSION_CHANGED.with(|l| *l.borrow()) {
|
||||
Some(NEXT_VALIDATORS.with(|l| l.borrow().clone()))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn authorities() -> Vec<UintAuthorityId> {
|
||||
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<Self::AccountId>;
|
||||
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<Test>;
|
||||
type Session = Module<Test>;
|
||||
|
||||
fn new_test_ext() -> runtime_io::TestExternalities<Blake2Hasher> {
|
||||
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 = <UintAuthorityId as TypedKey>::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(), || {
|
||||
|
||||
@@ -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 <http://www.gnu.org/licenses/>.
|
||||
|
||||
//! 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<Vec<u64>> = RefCell::new(vec![1, 2, 3]);
|
||||
pub static AUTHORITIES: RefCell<Vec<UintAuthorityId>> =
|
||||
RefCell::new(vec![UintAuthorityId(1), UintAuthorityId(2), UintAuthorityId(3)]);
|
||||
pub static FORCE_SESSION_END: RefCell<bool> = RefCell::new(false);
|
||||
pub static SESSION_LENGTH: RefCell<u64> = RefCell::new(2);
|
||||
pub static SESSION_CHANGED: RefCell<bool> = RefCell::new(false);
|
||||
pub static TEST_SESSION_CHANGED: RefCell<bool> = RefCell::new(false);
|
||||
}
|
||||
|
||||
pub struct TestShouldEndSession;
|
||||
impl ShouldEndSession<u64> 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<u64> for TestSessionHandler {
|
||||
fn on_new_session<T: OpaqueKeys>(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::<UintAuthorityId>(0).unwrap_or_default()).collect()
|
||||
);
|
||||
}
|
||||
fn on_disabled(_validator_index: usize) {}
|
||||
}
|
||||
|
||||
pub struct TestOnSessionEnding;
|
||||
impl OnSessionEnding<u64> for TestOnSessionEnding {
|
||||
fn on_session_ending(_: SessionIndex, _: SessionIndex) -> Option<Vec<u64>> {
|
||||
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<u64, u64> for TestOnSessionEnding {
|
||||
fn on_session_ending(_: SessionIndex, _: SessionIndex)
|
||||
-> Option<(Vec<u64>, 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<UintAuthorityId> {
|
||||
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<u64>) {
|
||||
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<Self::AccountId>;
|
||||
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<Test, TestOnSessionEnding>;
|
||||
#[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<Test>;
|
||||
pub type Session = Module<Test>;
|
||||
@@ -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" }
|
||||
|
||||
@@ -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<T> = 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<AccountId>: system::Trait {
|
||||
/// Disable a given validator by stash ID.
|
||||
fn disable_validator(validator: &AccountId) -> Result<(), ()>;
|
||||
/// Get the validators from session.
|
||||
fn validators() -> Vec<AccountId>;
|
||||
/// Prune historical session tries up to but not including the given index.
|
||||
fn prune_historical_up_to(up_to: session::SessionIndex);
|
||||
}
|
||||
|
||||
impl<T: Trait> SessionInterface<<T as system::Trait>::AccountId> for T where
|
||||
T: session::Trait<ValidatorId = <T as system::Trait>::AccountId>,
|
||||
T: session::historical::Trait<
|
||||
FullIdentification = Exposure<<T as system::Trait>::AccountId, BalanceOf<T>>,
|
||||
FullIdentificationOf = ExposureOf<T>,
|
||||
>,
|
||||
T::SessionHandler: session::SessionHandler<<T as system::Trait>::AccountId>,
|
||||
T::OnSessionEnding: session::OnSessionEnding<<T as system::Trait>::AccountId>,
|
||||
T::SelectInitialValidators: session::SelectInitialValidators<<T as system::Trait>::AccountId>,
|
||||
T::ValidatorIdOf: Convert<<T as system::Trait>::AccountId, Option<<T as system::Trait>::AccountId>>
|
||||
{
|
||||
fn disable_validator(validator: &<T as system::Trait>::AccountId) -> Result<(), ()> {
|
||||
<session::Module<T>>::disable(validator)
|
||||
}
|
||||
|
||||
fn validators() -> Vec<<T as system::Trait>::AccountId> {
|
||||
<session::Module<T>>::validators()
|
||||
}
|
||||
|
||||
fn prune_historical_up_to(up_to: session::SessionIndex) {
|
||||
<session::historical::Module<T>>::prune_up_to(up_to);
|
||||
}
|
||||
}
|
||||
|
||||
pub trait Trait: system::Trait {
|
||||
/// The staking balance.
|
||||
type Currency: LockableCurrency<Self::AccountId, Moment=Self::BlockNumber>;
|
||||
|
||||
@@ -473,6 +509,9 @@ pub trait Trait: system::Trait + session::Trait {
|
||||
|
||||
/// Number of eras that staked funds must remain bonded for.
|
||||
type BondingDuration: Get<EraIndex>;
|
||||
|
||||
/// Interface for interacting with a session module.
|
||||
type SessionInterface: self::SessionInterface<Self::AccountId>;
|
||||
}
|
||||
|
||||
decl_storage! {
|
||||
@@ -516,15 +555,6 @@ decl_storage! {
|
||||
/// This is keyed by the stash account.
|
||||
pub Stakers get(stakers): map T::AccountId => Exposure<T::AccountId, BalanceOf<T>>;
|
||||
|
||||
// The historical validators and their nominations for a given era. Stored as a trie root
|
||||
// of the mapping `T::AccountId` => `Exposure<T::AccountId, BalanceOf<T>>`, 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<H256>;
|
||||
|
||||
/// The currently elected validator set keyed by stash account ID.
|
||||
pub CurrentElected get(current_elected): Vec<T::AccountId>;
|
||||
|
||||
@@ -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<T: Trait> Module<T> {
|
||||
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<Vec<T::AccountId>> {
|
||||
/// 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<T::AccountId>, Vec<(T::AccountId, Exposure<T::AccountId, BalanceOf<T>>)>)>
|
||||
{
|
||||
// accumulate good session reward
|
||||
let reward = Self::current_session_reward();
|
||||
<CurrentEraReward<T>>::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<T: Trait> Module<T> {
|
||||
///
|
||||
/// 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<Vec<T::AccountId>> {
|
||||
fn new_era(start_session_index: SessionIndex) -> Option<Vec<T::AccountId>> {
|
||||
// Payout
|
||||
let reward = <CurrentEraReward<T>>::take();
|
||||
if !reward.is_zero() {
|
||||
@@ -1037,7 +1078,26 @@ impl<T: Trait> Module<T> {
|
||||
}
|
||||
|
||||
// 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<T: Trait> Module<T> {
|
||||
|
||||
/// 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<T>, Option<Vec<T::AccountId>>) {
|
||||
let maybe_elected_set = elect::<T, _, _, _>(
|
||||
Self::validator_count() as usize,
|
||||
@@ -1157,10 +1217,8 @@ impl<T: Trait> Module<T> {
|
||||
|
||||
// Set the new validator set in sessions.
|
||||
<CurrentElected<T>>::put(&elected_stashes);
|
||||
let validators = elected_stashes.into_iter()
|
||||
.map(|s| Self::bonded(s).unwrap_or_default())
|
||||
.collect::<Vec<_>>();
|
||||
(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<T: Trait> Module<T> {
|
||||
.map(|x| x.min(slash_exposure))
|
||||
.unwrap_or(slash_exposure);
|
||||
let _ = Self::slash_validator(&stash, slash);
|
||||
let _ = <session::Module<T>>::disable(&controller);
|
||||
let _ = T::SessionInterface::disable_validator(&stash);
|
||||
|
||||
RawEvent::OfflineSlash(stash.clone(), slash)
|
||||
} else {
|
||||
@@ -1234,9 +1292,17 @@ impl<T: Trait> Module<T> {
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Trait> OnSessionEnding<T::AccountId> for Module<T> {
|
||||
fn on_session_ending(i: SessionIndex) -> Option<Vec<T::AccountId>> {
|
||||
Self::new_session(i + 1)
|
||||
impl<T: Trait> session::OnSessionEnding<T::AccountId> for Module<T> {
|
||||
fn on_session_ending(_ending: SessionIndex, start_session: SessionIndex) -> Option<Vec<T::AccountId>> {
|
||||
Self::new_session(start_session - 1).map(|(new, _old)| new)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Trait> OnSessionEnding<T::AccountId, Exposure<T::AccountId, BalanceOf<T>>> for Module<T> {
|
||||
fn on_session_ending(_ending: SessionIndex, start_session: SessionIndex)
|
||||
-> Option<(Vec<T::AccountId>, Vec<(T::AccountId, Exposure<T::AccountId, BalanceOf<T>>)>)>
|
||||
{
|
||||
Self::new_session(start_session - 1)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1252,7 +1318,29 @@ impl<T: Trait> OnFreeBalanceZero<T::AccountId> for Module<T> {
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Trait> SelectInitialValidators<T> for Module<T> {
|
||||
/// A `Convert` implementation that finds the stash of the given controller account,
|
||||
/// if any.
|
||||
pub struct StashOf<T>(rstd::marker::PhantomData<T>);
|
||||
|
||||
impl<T: Trait> Convert<T::AccountId, Option<T::AccountId>> for StashOf<T> {
|
||||
fn convert(controller: T::AccountId) -> Option<T::AccountId> {
|
||||
<Module<T>>::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<T>(rstd::marker::PhantomData<T>);
|
||||
|
||||
impl<T: Trait> Convert<T::AccountId, Option<Exposure<T::AccountId, BalanceOf<T>>>>
|
||||
for ExposureOf<T>
|
||||
{
|
||||
fn convert(validator: T::AccountId) -> Option<Exposure<T::AccountId, BalanceOf<T>>> {
|
||||
Some(<Module<T>>::stakers(&validator))
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Trait> SelectInitialValidators<T::AccountId> for Module<T> {
|
||||
fn select_initial_validators() -> Option<Vec<T::AccountId>> {
|
||||
<Module<T>>::select_validators().1
|
||||
}
|
||||
|
||||
@@ -52,23 +52,24 @@ thread_local! {
|
||||
pub struct TestSessionHandler;
|
||||
impl session::SessionHandler<AccountId> for TestSessionHandler {
|
||||
fn on_new_session<Ks: OpaqueKeys>(_changed: bool, validators: &[(AccountId, Ks)]) {
|
||||
SESSION.with(|x|
|
||||
SESSION.with(|x| {
|
||||
let v = validators.iter().map(|(ref a, _)| a).cloned().collect::<Vec<_>>();
|
||||
*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<Test, Staking>;
|
||||
type Keys = UintAuthorityId;
|
||||
type ShouldEndSession = session::PeriodicSessions<Period, Offset>;
|
||||
type SessionHandler = TestSessionHandler;
|
||||
type Event = ();
|
||||
type ValidatorId = AccountId;
|
||||
type ValidatorIdOf = crate::StashOf<Test>;
|
||||
type SelectInitialValidators = Staking;
|
||||
}
|
||||
|
||||
impl session::historical::Trait for Test {
|
||||
type FullIdentification = crate::Exposure<AccountId, Balance>;
|
||||
type FullIdentificationOf = crate::ExposureOf<Test>;
|
||||
}
|
||||
|
||||
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::<Vec<_>>();
|
||||
|
||||
let _ = balances::GenesisConfig::<Test>{
|
||||
@@ -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<AccountId> {
|
||||
Session::validators().into_iter().map(|s| Staking::bonded(&s).expect("no controller for validator")).collect()
|
||||
}
|
||||
|
||||
@@ -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.
|
||||
<Staking as crate::Store>::MinimumValidatorCount::put(10);
|
||||
<Staking as crate::Store>::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());
|
||||
|
||||
@@ -126,6 +126,29 @@ pub trait VerifySeal<Header, Author> {
|
||||
fn verify_seal(header: &Header) -> Result<Option<Author>, &'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<Key> {
|
||||
/// 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<Self::Proof>;
|
||||
|
||||
/// 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<Self::FullIdentification>;
|
||||
}
|
||||
|
||||
/// Handler for when some currency "account" decreased in balance for
|
||||
/// some reason.
|
||||
///
|
||||
|
||||
Reference in New Issue
Block a user