staking: only disable slashed validators and keep them disabled for whole era (#9448)

* session: remove disabled validators threshold logic

* staking: add logic to track offending validators

* staking: disable validators for the whole era

* frame: fix tests

* staking: add tests for disabling validators handling

* staking: fix adding offending validator when already slashed in era

* address review comments

* session, staking: add comments about sorted vecs

Co-authored-by: Andronik Ordian <write@reusable.software>
This commit is contained in:
André Silva
2021-10-06 17:22:34 +01:00
committed by GitHub
parent 12e9e7ceb3
commit 7e5c022aea
25 changed files with 282 additions and 134 deletions
+25 -37
View File
@@ -116,7 +116,7 @@ pub mod weights;
use sp_runtime::{
traits::{AtLeast32BitUnsigned, Convert, Member, One, OpaqueKeys, Zero},
ConsensusEngineId, KeyTypeId, Perbill, Permill, RuntimeAppPublic,
ConsensusEngineId, KeyTypeId, Permill, RuntimeAppPublic,
};
use sp_staking::SessionIndex;
use sp_std::{
@@ -298,7 +298,7 @@ pub trait SessionHandler<ValidatorId> {
fn on_before_session_ending() {}
/// A validator got disabled. Act accordingly until a new session begins.
fn on_disabled(validator_index: usize);
fn on_disabled(validator_index: u32);
}
#[impl_trait_for_tuples::impl_for_tuples(1, 30)]
@@ -342,7 +342,7 @@ impl<AId> SessionHandler<AId> for Tuple {
for_tuples!( #( Tuple::on_before_session_ending(); )* )
}
fn on_disabled(i: usize) {
fn on_disabled(i: u32) {
for_tuples!( #( Tuple::on_disabled(i); )* )
}
}
@@ -354,7 +354,7 @@ impl<AId> SessionHandler<AId> for TestSessionHandler {
fn on_genesis_session<Ks: OpaqueKeys>(_: &[(AId, Ks)]) {}
fn on_new_session<Ks: OpaqueKeys>(_: bool, _: &[(AId, Ks)], _: &[(AId, Ks)]) {}
fn on_before_session_ending() {}
fn on_disabled(_: usize) {}
fn on_disabled(_: u32) {}
}
#[frame_support::pallet]
@@ -401,12 +401,6 @@ pub mod pallet {
/// The keys.
type Keys: OpaqueKeys + Member + Parameter + Default + MaybeSerializeDeserialize;
/// 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 `pallet_staking` forces a new era.
type DisabledValidatorsThreshold: Get<Perbill>;
/// Weight information for extrinsics in this pallet.
type WeightInfo: WeightInfo;
}
@@ -514,7 +508,9 @@ pub mod pallet {
/// Indices of disabled validators.
///
/// The set is cleared when `on_session_ending` returns a new set of identities.
/// The vec is always kept sorted so that we can find whether a given validator is
/// disabled using binary search. It gets cleared when `on_session_ending` returns
/// a new set of identities.
#[pallet::storage]
#[pallet::getter(fn disabled_validators)]
pub type DisabledValidators<T> = StorageValue<_, Vec<u32>, ValueQuery>;
@@ -705,42 +701,34 @@ impl<T: Config> Pallet<T> {
T::SessionHandler::on_new_session::<T::Keys>(changed, &session_keys, &queued_amalgamated);
}
/// Disable the validator of index `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<T>>::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);
/// Disable the validator of index `i`, returns `false` if the validator was already disabled.
pub fn disable_index(i: u32) -> bool {
if i >= Validators::<T>::decode_len().unwrap_or(0) as u32 {
return false
}
threshold_reached
<DisabledValidators<T>>::mutate(|disabled| {
if let Err(index) = disabled.binary_search(&i) {
disabled.insert(index, i);
T::SessionHandler::on_disabled(i);
return true
}
false
})
}
/// Disable the validator identified by `c`. (If using with the staking pallet,
/// 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 pallet it allows to force a new era in such case.
pub fn disable(c: &T::ValidatorId) -> sp_std::result::Result<bool, ()> {
/// Returns `false` either if the validator could not be found or it was already
/// disabled.
pub fn disable(c: &T::ValidatorId) -> bool {
Self::validators()
.iter()
.position(|i| i == c)
.map(Self::disable_index)
.ok_or(())
.map(|i| Self::disable_index(i as u32))
.unwrap_or(false)
}
/// Upgrade the key type from some old type to a new type. Supports adding
+1 -7
View File
@@ -29,7 +29,6 @@ use sp_runtime::{
impl_opaque_keys,
testing::{Header, UintAuthorityId},
traits::{BlakeTwo256, ConvertInto, IdentityLookup},
Perbill,
};
use sp_staking::SessionIndex;
@@ -144,7 +143,7 @@ impl SessionHandler<u64> for TestSessionHandler {
.collect()
});
}
fn on_disabled(_validator_index: usize) {
fn on_disabled(_validator_index: u32) {
DISABLED.with(|l| *l.borrow_mut() = true)
}
fn on_before_session_ending() {
@@ -269,10 +268,6 @@ impl pallet_timestamp::Config for Test {
type WeightInfo = ();
}
parameter_types! {
pub const DisabledValidatorsThreshold: Perbill = Perbill::from_percent(33);
}
impl Config for Test {
type ShouldEndSession = TestShouldEndSession;
#[cfg(feature = "historical")]
@@ -284,7 +279,6 @@ impl Config for Test {
type ValidatorIdOf = ConvertInto;
type Keys = MockSessionKeys;
type Event = Event;
type DisabledValidatorsThreshold = DisabledValidatorsThreshold;
type NextSessionRotation = ();
type WeightInfo = ();
}
+3 -4
View File
@@ -338,7 +338,7 @@ fn session_keys_generate_output_works_as_set_keys_input() {
}
#[test]
fn return_true_if_more_than_third_is_disabled() {
fn disable_index_returns_false_if_already_disabled() {
new_test_ext().execute_with(|| {
set_next_validators(vec![1, 2, 3, 4, 5, 6, 7]);
force_new_session();
@@ -347,10 +347,9 @@ fn return_true_if_more_than_third_is_disabled() {
force_new_session();
initialize_block(2);
assert_eq!(Session::disable_index(0), true);
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);
assert_eq!(Session::disable_index(1), true);
});
}