-
Notifications
You must be signed in to change notification settings - Fork 859
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Validator disabling in session enhancements #7663
base: master
Are you sure you want to change the base?
Changes from all commits
d02401f
26b5a0b
dc9fc1b
030afd0
cc884c9
46e5d55
b605342
8a5772e
8623571
c533c10
c1030ae
c8fdd20
1324eff
e4551ac
7ed635a
1e1bab5
ca8757a
c30127d
8223665
0ac3abc
0f914a1
19435bd
d14134b
894b3a8
d7ca394
4a099a2
8c7909e
830d875
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,19 @@ | ||
# Schema: Polkadot SDK PRDoc Schema (prdoc) v1.0.0 | ||
# See doc at https://raw.githubusercontent.com/paritytech/polkadot-sdk/master/prdoc/schema_user.json | ||
|
||
title: Validator disabling in session enhancements | ||
|
||
doc: | ||
- audience: Runtime Dev | ||
description: | | ||
This PR introduces changes to the pallet-session interface. Disabled validators can | ||
still be disabled with just the index but it will default to highest possible severity. | ||
pallet-session also additionally exposes DisabledValidators with their severities. | ||
The staking primitive OffenceSeverity received min, max and default implementations | ||
for ease of use. | ||
|
||
crates: | ||
- name: pallet-staking | ||
bump: major | ||
- name: pallet-session | ||
bump: major | ||
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -138,13 +138,16 @@ | |
use frame_system::pallet_prelude::BlockNumberFor; | ||
use sp_runtime::{ | ||
traits::{AtLeast32BitUnsigned, Convert, Member, One, OpaqueKeys, Zero}, | ||
ConsensusEngineId, DispatchError, KeyTypeId, Perbill, Permill, RuntimeAppPublic, | ||
}; | ||
use sp_staking::{offence::OffenceSeverity, SessionIndex}; | ||
|
||
pub use pallet::*; | ||
pub use weights::WeightInfo; | ||
|
||
#[cfg(any(test, feature = "try-runtime"))] | ||
use sp_runtime::TryRuntimeError; | ||
|
||
pub(crate) const LOG_TARGET: &str = "runtime::session"; | ||
|
||
// syntactic sugar for logging. | ||
|
@@ -590,6 +593,11 @@ | |
Weight::zero() | ||
} | ||
} | ||
|
||
#[cfg(feature = "try-runtime")] | ||
fn try_state(_n: BlockNumberFor<T>) -> Result<(), TryRuntimeError> { | ||
Self::do_try_state() | ||
} | ||
} | ||
|
||
#[pallet::call] | ||
|
@@ -656,6 +664,11 @@ | |
DisabledValidators::<T>::get().iter().map(|(i, _)| *i).collect() | ||
} | ||
|
||
/// Public function to access the disabled validators with their severities. | ||
pub fn disabled_validators_with_severities() -> Vec<(u32, OffenceSeverity)> { | ||
DisabledValidators::<T>::get() | ||
} | ||
Comment on lines
+667
to
+670
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. is the plan to expose this in a runtime API or how is it planned to be used? |
||
|
||
/// Move on to next session. Register new validator set and session keys. Changes to the | ||
/// validator set have a session of delay to take effect. This allows for equivocation | ||
/// punishment after a fork. | ||
|
@@ -675,6 +688,7 @@ | |
Validators::<T>::put(&validators); | ||
|
||
if changed { | ||
log!(trace, "resetting disabled validators"); | ||
// reset disabled validators if active set was changed | ||
DisabledValidators::<T>::take(); | ||
} | ||
|
@@ -683,12 +697,12 @@ | |
let session_index = session_index + 1; | ||
CurrentIndex::<T>::put(session_index); | ||
T::SessionManager::start_session(session_index); | ||
log::trace!(target: "runtime::session", "starting_session {:?}", session_index); | ||
log!(trace, "starting_session {:?}", session_index); | ||
|
||
// Get next validator set. | ||
let maybe_next_validators = T::SessionManager::new_session(session_index + 1); | ||
log::trace!( | ||
target: "runtime::session", | ||
log!( | ||
trace, | ||
Overkillus marked this conversation as resolved.
Show resolved
Hide resolved
|
||
"planning_session {:?} with {:?} validators", | ||
session_index + 1, | ||
maybe_next_validators.as_ref().map(|v| v.len()) | ||
|
@@ -745,38 +759,6 @@ | |
T::SessionHandler::on_new_session::<T::Keys>(changed, &session_keys, &queued_amalgamated); | ||
} | ||
|
||
/// Disable the validator of index `i`, returns `false` if the validator was already disabled. | ||
/// | ||
/// Note: This sets the OffenceSeverity to the lowest value. | ||
pub fn disable_index(i: u32) -> bool { | ||
if i >= Validators::<T>::decode_len().defensive_unwrap_or(0) as u32 { | ||
return false | ||
} | ||
|
||
DisabledValidators::<T>::mutate(|disabled| { | ||
if let Err(index) = disabled.binary_search_by_key(&i, |(index, _)| *index) { | ||
disabled.insert(index, (i, OffenceSeverity(Perbill::zero()))); | ||
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 `false` either if the validator could not be found or it was already | ||
/// disabled. | ||
pub fn disable(c: &T::ValidatorId) -> bool { | ||
Validators::<T>::get() | ||
.iter() | ||
.position(|i| i == c) | ||
.map(|i| Self::disable_index(i as u32)) | ||
.unwrap_or(false) | ||
} | ||
|
||
Comment on lines
-767
to
-779
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. any reason to keep There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We can disable, disable with severity and re-enable. All those could be called with either index or validator id. Instead of having 6+ different functions we provide index ones and supply Do you think it would be sensible to duplicate all those calls with _validator_id where we just bake in the call to validator_id_to_index? |
||
/// Upgrade the key type from some old type to a new type. Supports adding | ||
/// and removing key types. | ||
/// | ||
|
@@ -928,45 +910,105 @@ | |
KeyOwner::<T>::remove((id, key_data)); | ||
} | ||
|
||
pub fn report_offence(validator: T::ValidatorId, severity: OffenceSeverity) { | ||
/// Disable the validator of index `i` with a specified severity, | ||
/// returns `false` if the validator is not found. | ||
/// | ||
/// Note: If validator is already disabled, the severity will | ||
/// be updated if the new one is higher. | ||
pub fn disable_index_with_severity(i: u32, severity: OffenceSeverity) -> bool { | ||
if i >= Validators::<T>::decode_len().defensive_unwrap_or(0) as u32 { | ||
return false; | ||
} | ||
|
||
DisabledValidators::<T>::mutate(|disabled| { | ||
let decision = T::DisablingStrategy::decision(&validator, severity, &disabled); | ||
|
||
if let Some(offender_idx) = decision.disable { | ||
// Check if the offender is already disabled | ||
match disabled.binary_search_by_key(&offender_idx, |(index, _)| *index) { | ||
// Offender is already disabled, update severity if the new one is higher | ||
Ok(index) => { | ||
let (_, old_severity) = &mut disabled[index]; | ||
if severity > *old_severity { | ||
*old_severity = severity; | ||
} | ||
}, | ||
Err(index) => { | ||
// Offender is not disabled, add to `DisabledValidators` and disable it | ||
disabled.insert(index, (offender_idx, severity)); | ||
// let the session handlers know that a validator got disabled | ||
T::SessionHandler::on_disabled(offender_idx); | ||
|
||
// Emit event that a validator got disabled | ||
Self::deposit_event(Event::ValidatorDisabled { | ||
validator: validator.clone(), | ||
}); | ||
}, | ||
} | ||
match disabled.binary_search_by_key(&i, |(index, _)| *index) { | ||
// Validator is already disabled, update severity if the new one is higher | ||
Ok(index) => { | ||
let current_severity = &mut disabled[index].1; | ||
if severity > *current_severity { | ||
log!( | ||
trace, | ||
"updating disablement severity of validator {:?} from {:?} to {:?}", | ||
i, | ||
*current_severity, | ||
severity | ||
); | ||
*current_severity = severity; | ||
} | ||
true | ||
}, | ||
// Validator is not disabled, add to `DisabledValidators` and disable it | ||
Err(index) => { | ||
log!(trace, "disabling validator {:?}", i); | ||
Self::deposit_event(Event::ValidatorDisabled { | ||
validator: Validators::<T>::get()[index as usize].clone(), | ||
}); | ||
disabled.insert(index, (i, severity)); | ||
T::SessionHandler::on_disabled(i); | ||
true | ||
}, | ||
} | ||
}) | ||
} | ||
|
||
if let Some(reenable_idx) = decision.reenable { | ||
// Remove the validator from `DisabledValidators` and re-enable it. | ||
if let Ok(index) = disabled.binary_search_by_key(&reenable_idx, |(index, _)| *index) | ||
{ | ||
disabled.remove(index); | ||
// Emit event that a validator got re-enabled | ||
let reenabled_stash = Validators::<T>::get()[reenable_idx as usize].clone(); | ||
Self::deposit_event(Event::ValidatorReenabled { validator: reenabled_stash }); | ||
} | ||
/// Disable the validator of index `i` with a default severity (defaults to most severe), | ||
/// returns `false` if the validator is not found. | ||
pub fn disable_index(i: u32) -> bool { | ||
let default_severity = OffenceSeverity::default(); | ||
Self::disable_index_with_severity(i, default_severity) | ||
} | ||
|
||
/// Re-enable the validator of index `i`, returns `false` if the validator was not disabled. | ||
pub fn reenable_index(i: u32) -> bool { | ||
if i >= Validators::<T>::decode_len().defensive_unwrap_or(0) as u32 { | ||
return false; | ||
} | ||
|
||
DisabledValidators::<T>::mutate(|disabled| { | ||
if let Ok(index) = disabled.binary_search_by_key(&i, |(index, _)| *index) { | ||
log!(trace, "reenabling validator {:?}", i); | ||
Self::deposit_event(Event::ValidatorReenabled { | ||
validator: Validators::<T>::get()[index as usize].clone(), | ||
}); | ||
disabled.remove(index); | ||
return true; | ||
} | ||
}); | ||
false | ||
}) | ||
} | ||
|
||
/// Convert a validator ID to an index. | ||
/// (If using with the staking pallet, this would be their *stash* account.) | ||
pub fn validator_id_to_index(id: &T::ValidatorId) -> Option<u32> { | ||
Validators::<T>::get().iter().position(|i| i == id).map(|i| i as u32) | ||
} | ||
|
||
/// Report an offence for the given validator and let disabling strategy decide | ||
/// what changes to disabled validators should be made. | ||
pub fn report_offence(validator: T::ValidatorId, severity: OffenceSeverity) { | ||
log!(trace, "reporting offence for {:?} with {:?}", validator, severity); | ||
let decision = | ||
T::DisablingStrategy::decision(&validator, severity, &DisabledValidators::<T>::get()); | ||
|
||
// Disable | ||
if let Some(offender_idx) = decision.disable { | ||
Self::disable_index_with_severity(offender_idx, severity); | ||
} | ||
|
||
// Re-enable | ||
if let Some(reenable_idx) = decision.reenable { | ||
Self::reenable_index(reenable_idx); | ||
} | ||
} | ||
|
||
#[cfg(any(test, feature = "try-runtime"))] | ||
pub fn do_try_state() -> Result<(), sp_runtime::TryRuntimeError> { | ||
// Ensure that the validators are sorted | ||
ensure!( | ||
DisabledValidators::<T>::get().windows(2).all(|pair| pair[0].0 <= pair[1].0), | ||
"DisabledValidators is not sorted" | ||
); | ||
Ok(()) | ||
} | ||
} | ||
|
||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
i believe it doesn't have to be major if you keep
disable
pub fnThere was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'd prefer removing old disable. It simplifies and unifies the interface while still allowing for old-scholl interface with the provided translation func (
pub fn validator_id_to_index
).