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:
Robert Habermeier
2019-07-08 14:36:46 +02:00
committed by GitHub
parent bb7ff32e77
commit 7df8e52cfe
23 changed files with 1188 additions and 341 deletions
+115 -27
View File
@@ -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
}