Force new era only if 1/3 validators is disabled. (#3533)

* Force new era only when over third validators is disabled.

* Update srml/session/src/lib.rs

Co-Authored-By: Bastian Köcher <bkchr@users.noreply.github.com>

* Update srml/staking/src/lib.rs

Co-Authored-By: Bastian Köcher <bkchr@users.noreply.github.com>

* Parametrize the threshold.

* Bump runtime version.

* Update node/runtime/src/lib.rs

* Fix build.

* Fix im-online test.
This commit is contained in:
Tomasz Drwięga
2019-09-19 12:04:48 +02:00
committed by Gavin Wood
parent bfe240d1b0
commit 668acca9ed
7 changed files with 95 additions and 12 deletions
+4
View File
@@ -211,6 +211,9 @@ impl_opaque_keys! {
// `SessionKeys`.
// TODO: Introduce some structure to tie these together to make it a bit less of a footgun. This
// should be easy, since OneSessionHandler trait provides the `Key` as an associated type. #2858
parameter_types! {
pub const DisabledValidatorsThreshold: Perbill = Perbill::from_percent(17);
}
impl session::Trait for Runtime {
type OnSessionEnding = Staking;
@@ -221,6 +224,7 @@ impl session::Trait for Runtime {
type ValidatorId = AccountId;
type ValidatorIdOf = staking::StashOf<Self>;
type SelectInitialValidators = Staking;
type DisabledValidatorsThreshold = DisabledValidatorsThreshold;
}
impl session::historical::Trait for Runtime {
@@ -155,6 +155,10 @@ mod tests {
}
}
parameter_types! {
pub const DisabledValidatorsThreshold: Perbill = Perbill::from_percent(33);
}
impl session::Trait for Test {
type OnSessionEnding = TestOnSessionEnding;
type Keys = UintAuthorityId;
@@ -164,6 +168,7 @@ mod tests {
type ValidatorId = AuthorityId;
type ValidatorIdOf = ConvertInto;
type SelectInitialValidators = ();
type DisabledValidatorsThreshold = DisabledValidatorsThreshold;
}
impl session::historical::Trait for Test {
+5
View File
@@ -125,6 +125,10 @@ parameter_types! {
pub const Offset: u64 = 0;
}
parameter_types! {
pub const DisabledValidatorsThreshold: Perbill = Perbill::from_percent(33);
}
impl session::Trait for Runtime {
type ShouldEndSession = session::PeriodicSessions<Period, Offset>;
type OnSessionEnding = session::historical::NoteHistoricalRoot<Runtime, TestOnSessionEnding>;
@@ -134,6 +138,7 @@ impl session::Trait for Runtime {
type Keys = UintAuthorityId;
type Event = ();
type SelectInitialValidators = ();
type DisabledValidatorsThreshold = DisabledValidatorsThreshold;
}
impl session::historical::Trait for Runtime {
+63 -6
View File
@@ -121,7 +121,7 @@
use rstd::{prelude::*, marker::PhantomData, ops::{Sub, Rem}};
use codec::Decode;
use sr_primitives::{KeyTypeId, RuntimeAppPublic};
use sr_primitives::{KeyTypeId, Perbill, RuntimeAppPublic};
use sr_primitives::weights::SimpleDispatchInfo;
use sr_primitives::traits::{Convert, Zero, Member, OpaqueKeys};
use sr_staking_primitives::SessionIndex;
@@ -334,6 +334,12 @@ pub trait Trait: system::Trait {
/// The keys.
type Keys: OpaqueKeys + Member + Parameter + Default;
/// The fraction of validators set that is safe to be disabled.
///
/// After the threshold is reached `disabled` method starts to return true,
/// which in combination with `srml_staking` forces a new era.
type DisabledValidatorsThreshold: Get<Perbill>;
/// Select initial validators.
type SelectInitialValidators: SelectInitialValidators<Self::ValidatorId>;
}
@@ -356,6 +362,11 @@ decl_storage! {
/// will be used to determine the validator's session keys.
QueuedKeys get(queued_keys): Vec<(T::ValidatorId, T::Keys)>;
/// Indices of disabled validators.
///
/// The set is cleared when `on_session_ending` returns a new set of identities.
DisabledValidators get(disabled_validators): Vec<u32>;
/// The next session keys for a validator.
///
/// The first key is always `DEDUP_KEY_PREFIX` to have all the data in the same branch of
@@ -475,6 +486,11 @@ impl<T: Trait> Module<T> {
.collect::<Vec<_>>();
<Validators<T>>::put(&validators);
if changed {
// reset disabled validators
DisabledValidators::take();
}
let applied_at = session_index + 2;
// Get next validator set.
@@ -539,13 +555,36 @@ impl<T: Trait> Module<T> {
}
/// Disable the validator of index `i`.
pub fn disable_index(i: usize) {
T::SessionHandler::on_disabled(i);
///
/// Returns `true` if this causes a `DisabledValidatorsThreshold` of validators
/// to be already disabled.
pub fn disable_index(i: usize) -> bool {
let (fire_event, threshold_reached) = DisabledValidators::mutate(|disabled| {
let i = i as u32;
if let Err(index) = disabled.binary_search(&i) {
let count = <Validators<T>>::decode_len().unwrap_or(0) as u32;
let threshold = T::DisabledValidatorsThreshold::get() * count;
disabled.insert(index, i);
(true, disabled.len() as u32 > threshold)
} else {
(false, false)
}
});
if fire_event {
T::SessionHandler::on_disabled(i);
}
threshold_reached
}
/// Disable the validator identified by `c`. (If using with the staking module, this would be
/// their *stash* account.)
pub fn disable(c: &T::ValidatorId) -> rstd::result::Result<(), ()> {
/// Disable the validator identified by `c`. (If using with the staking module,
/// this would be their *stash* account.)
///
/// Returns `Ok(true)` if more than `DisabledValidatorsThreshold` validators in current
/// session is already disabled.
/// If used with the staking module it allows to force a new era in such case.
pub fn disable(c: &T::ValidatorId) -> rstd::result::Result<bool, ()> {
Self::validators().iter().position(|i| i == c).map(Self::disable_index).ok_or(())
}
@@ -924,4 +963,22 @@ mod tests {
);
});
}
#[test]
fn return_true_if_more_than_third_is_disabled() {
with_externalities(&mut new_test_ext(), || {
set_next_validators(vec![1, 2, 3, 4, 5, 6, 7]);
force_new_session();
initialize_block(1);
// apply the new validator set
force_new_session();
initialize_block(2);
assert_eq!(Session::disable_index(0), false);
assert_eq!(Session::disable_index(1), false);
assert_eq!(Session::disable_index(2), true);
assert_eq!(Session::disable_index(3), true);
});
}
}
+5
View File
@@ -183,6 +183,10 @@ impl timestamp::Trait for Test {
type MinimumPeriod = MinimumPeriod;
}
parameter_types! {
pub const DisabledValidatorsThreshold: Perbill = Perbill::from_percent(33);
}
impl Trait for Test {
type ShouldEndSession = TestShouldEndSession;
#[cfg(feature = "historical")]
@@ -195,6 +199,7 @@ impl Trait for Test {
type Keys = MockSessionKeys;
type Event = ();
type SelectInitialValidators = ();
type DisabledValidatorsThreshold = DisabledValidatorsThreshold;
}
#[cfg(feature = "historical")]
+11 -6
View File
@@ -454,7 +454,11 @@ type MomentOf<T>= <<T as Trait>::Time as Time>::Moment;
/// This is needed because `Staking` sets the `ValidatorIdOf` of the `session::Trait`
pub trait SessionInterface<AccountId>: system::Trait {
/// Disable a given validator by stash ID.
fn disable_validator(validator: &AccountId) -> Result<(), ()>;
///
/// Returns `true` if new era should be forced at the end of this session.
/// This allows preventing a situation where there is too many validators
/// disabled and block production stalls.
fn disable_validator(validator: &AccountId) -> Result<bool, ()>;
/// Get the validators from session.
fn validators() -> Vec<AccountId>;
/// Prune historical session tries up to but not including the given index.
@@ -472,7 +476,7 @@ impl<T: Trait> SessionInterface<<T as system::Trait>::AccountId> for T where
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<(), ()> {
fn disable_validator(validator: &<T as system::Trait>::AccountId) -> Result<bool, ()> {
<session::Module<T>>::disable(validator)
}
@@ -1531,10 +1535,11 @@ impl <T: Trait> OnOffenceHandler<T::AccountId, session::historical::Identificati
continue;
}
// make sure to disable validator in next sessions
let _ = T::SessionInterface::disable_validator(stash);
// force a new era, to select a new validator set
ForceEra::put(Forcing::ForceNew);
// make sure to disable validator till the end of this session
if T::SessionInterface::disable_validator(stash).unwrap_or(false) {
// force a new era, to select a new validator set
ForceEra::put(Forcing::ForceNew);
}
// actually slash the validator
let slashed_amount = Self::slash_validator(stash, amount, exposure, &mut journal);
+2
View File
@@ -150,6 +150,7 @@ parameter_types! {
pub const Period: BlockNumber = 1;
pub const Offset: BlockNumber = 0;
pub const UncleGenerations: u64 = 0;
pub const DisabledValidatorsThreshold: Perbill = Perbill::from_percent(33);
}
impl session::Trait for Test {
type OnSessionEnding = session::historical::NoteHistoricalRoot<Test, Staking>;
@@ -160,6 +161,7 @@ impl session::Trait for Test {
type ValidatorId = AccountId;
type ValidatorIdOf = crate::StashOf<Test>;
type SelectInitialValidators = Staking;
type DisabledValidatorsThreshold = DisabledValidatorsThreshold;
}
impl session::historical::Trait for Test {