...@@ -146,7 +146,6 @@ parameter_types! { ...@@ -146,7 +146,6 @@ parameter_types! {
pub const SessionsPerEra: SessionIndex = 3; pub const SessionsPerEra: SessionIndex = 3;
pub const BondingDuration: EraIndex = 3; pub const BondingDuration: EraIndex = 3;
pub const RewardCurve: &'static PiecewiseLinear<'static> = &REWARD_CURVE; pub const RewardCurve: &'static PiecewiseLinear<'static> = &REWARD_CURVE;
pub const OffendingValidatorsThreshold: Perbill = Perbill::from_percent(17);
pub static ElectionsBoundsOnChain: ElectionBounds = ElectionBoundsBuilder::default().build(); pub static ElectionsBoundsOnChain: ElectionBounds = ElectionBoundsBuilder::default().build();
} }
...@@ -176,7 +175,6 @@ impl pallet_staking::Config for Test { ...@@ -176,7 +175,6 @@ impl pallet_staking::Config for Test {
type UnixTime = pallet_timestamp::Pallet<Test>; type UnixTime = pallet_timestamp::Pallet<Test>;
type EraPayout = pallet_staking::ConvertCurve<RewardCurve>; type EraPayout = pallet_staking::ConvertCurve<RewardCurve>;
type MaxExposurePageSize = ConstU32<64>; type MaxExposurePageSize = ConstU32<64>;
type OffendingValidatorsThreshold = OffendingValidatorsThreshold;
type NextNewSession = Session; type NextNewSession = Session;
type ElectionProvider = onchain::OnChainExecution<OnChainSeqPhragmen>; type ElectionProvider = onchain::OnChainExecution<OnChainSeqPhragmen>;
type GenesisElectionProvider = Self::ElectionProvider; type GenesisElectionProvider = Self::ElectionProvider;
...@@ -189,6 +187,7 @@ impl pallet_staking::Config for Test { ...@@ -189,6 +187,7 @@ impl pallet_staking::Config for Test {
type EventListeners = (); type EventListeners = ();
type BenchmarkingConfig = pallet_staking::TestBenchmarkingConfig; type BenchmarkingConfig = pallet_staking::TestBenchmarkingConfig;
type WeightInfo = (); type WeightInfo = ();
type DisablingStrategy = pallet_staking::UpToLimitDisablingStrategy;
} }
impl pallet_offences::Config for Test { impl pallet_offences::Config for Test {
......
...@@ -104,7 +104,7 @@ use sp_runtime::{ ...@@ -104,7 +104,7 @@ use sp_runtime::{
PerThing, Perbill, Permill, RuntimeDebug, SaturatedConversion, PerThing, Perbill, Permill, RuntimeDebug, SaturatedConversion,
}; };
use sp_staking::{ use sp_staking::{
offence::{DisableStrategy, Kind, Offence, ReportOffence}, offence::{Kind, Offence, ReportOffence},
SessionIndex, SessionIndex,
}; };
use sp_std::prelude::*; use sp_std::prelude::*;
...@@ -847,10 +847,6 @@ impl<Offender: Clone> Offence<Offender> for UnresponsivenessOffence<Offender> { ...@@ -847,10 +847,6 @@ impl<Offender: Clone> Offence<Offender> for UnresponsivenessOffence<Offender> {
self.session_index self.session_index
} }
fn disable_strategy(&self) -> DisableStrategy {
DisableStrategy::Never
}
fn slash_fraction(&self, offenders: u32) -> Perbill { fn slash_fraction(&self, offenders: u32) -> Perbill {
// the formula is min((3 * (k - (n / 10 + 1))) / n, 1) * 0.07 // the formula is min((3 * (k - (n / 10 + 1))) / n, 1) * 0.07
// basically, 10% can be offline with no slash, but after that, it linearly climbs up to 7% // basically, 10% can be offline with no slash, but after that, it linearly climbs up to 7%
......
...@@ -50,9 +50,6 @@ fn test_unresponsiveness_slash_fraction() { ...@@ -50,9 +50,6 @@ fn test_unresponsiveness_slash_fraction() {
dummy_offence.slash_fraction(17), dummy_offence.slash_fraction(17),
Perbill::from_parts(46200000), // 4.62% Perbill::from_parts(46200000), // 4.62%
); );
// Offline offences should never lead to being disabled.
assert_eq!(dummy_offence.disable_strategy(), DisableStrategy::Never);
} }
#[test] #[test]
......
...@@ -765,6 +765,13 @@ enum MessageExecutionStatus { ...@@ -765,6 +765,13 @@ enum MessageExecutionStatus {
Processed, Processed,
/// The message was processed and resulted in a, possibly permanent, error. /// The message was processed and resulted in a, possibly permanent, error.
Unprocessable { permanent: bool }, Unprocessable { permanent: bool },
/// The stack depth limit was reached.
///
/// We cannot just return `Unprocessable` in this case, because the processability of the
/// message depends on how the function was called. This may be a permanent error if it was
/// called by a top-level function, or a transient error if it was already called in a nested
/// function.
StackLimitReached,
} }
impl<T: Config> Pallet<T> { impl<T: Config> Pallet<T> {
...@@ -984,7 +991,8 @@ impl<T: Config> Pallet<T> { ...@@ -984,7 +991,8 @@ impl<T: Config> Pallet<T> {
// additional overweight event being deposited. // additional overweight event being deposited.
) { ) {
Overweight | InsufficientWeight => Err(Error::<T>::InsufficientWeight), Overweight | InsufficientWeight => Err(Error::<T>::InsufficientWeight),
Unprocessable { permanent: false } => Err(Error::<T>::TemporarilyUnprocessable), StackLimitReached | Unprocessable { permanent: false } =>
Err(Error::<T>::TemporarilyUnprocessable),
Unprocessable { permanent: true } | Processed => { Unprocessable { permanent: true } | Processed => {
page.note_processed_at_pos(pos); page.note_processed_at_pos(pos);
book_state.message_count.saturating_dec(); book_state.message_count.saturating_dec();
...@@ -1250,7 +1258,7 @@ impl<T: Config> Pallet<T> { ...@@ -1250,7 +1258,7 @@ impl<T: Config> Pallet<T> {
let is_processed = match res { let is_processed = match res {
InsufficientWeight => return ItemExecutionStatus::Bailed, InsufficientWeight => return ItemExecutionStatus::Bailed,
Unprocessable { permanent: false } => return ItemExecutionStatus::NoProgress, Unprocessable { permanent: false } => return ItemExecutionStatus::NoProgress,
Processed | Unprocessable { permanent: true } => true, Processed | Unprocessable { permanent: true } | StackLimitReached => true,
Overweight => false, Overweight => false,
}; };
...@@ -1461,6 +1469,10 @@ impl<T: Config> Pallet<T> { ...@@ -1461,6 +1469,10 @@ impl<T: Config> Pallet<T> {
Self::deposit_event(Event::<T>::ProcessingFailed { id: id.into(), origin, error }); Self::deposit_event(Event::<T>::ProcessingFailed { id: id.into(), origin, error });
MessageExecutionStatus::Unprocessable { permanent: true } MessageExecutionStatus::Unprocessable { permanent: true }
}, },
Err(error @ StackLimitReached) => {
Self::deposit_event(Event::<T>::ProcessingFailed { id: id.into(), origin, error });
MessageExecutionStatus::StackLimitReached
},
Ok(success) => { Ok(success) => {
// Success // Success
let weight_used = meter.consumed().saturating_sub(prev_consumed); let weight_used = meter.consumed().saturating_sub(prev_consumed);
......
...@@ -198,6 +198,7 @@ impl ProcessMessage for RecordingMessageProcessor { ...@@ -198,6 +198,7 @@ impl ProcessMessage for RecordingMessageProcessor {
parameter_types! { parameter_types! {
pub static Callback: Box<fn (&MessageOrigin, u32)> = Box::new(|_, _| {}); pub static Callback: Box<fn (&MessageOrigin, u32)> = Box::new(|_, _| {});
pub static IgnoreStackOvError: bool = false;
} }
/// Processed a mocked message. Messages that end with `badformat`, `corrupt`, `unsupported` or /// Processed a mocked message. Messages that end with `badformat`, `corrupt`, `unsupported` or
...@@ -216,6 +217,8 @@ fn processing_message(msg: &[u8], origin: &MessageOrigin) -> Result<(), ProcessM ...@@ -216,6 +217,8 @@ fn processing_message(msg: &[u8], origin: &MessageOrigin) -> Result<(), ProcessM
Err(ProcessMessageError::Unsupported) Err(ProcessMessageError::Unsupported)
} else if msg.ends_with("yield") { } else if msg.ends_with("yield") {
Err(ProcessMessageError::Yield) Err(ProcessMessageError::Yield)
} else if msg.ends_with("stacklimitreached") && !IgnoreStackOvError::get() {
Err(ProcessMessageError::StackLimitReached)
} else { } else {
Ok(()) Ok(())
} }
......
...@@ -174,9 +174,10 @@ fn service_queues_failing_messages_works() { ...@@ -174,9 +174,10 @@ fn service_queues_failing_messages_works() {
MessageQueue::enqueue_message(msg("badformat"), Here); MessageQueue::enqueue_message(msg("badformat"), Here);
MessageQueue::enqueue_message(msg("corrupt"), Here); MessageQueue::enqueue_message(msg("corrupt"), Here);
MessageQueue::enqueue_message(msg("unsupported"), Here); MessageQueue::enqueue_message(msg("unsupported"), Here);
MessageQueue::enqueue_message(msg("stacklimitreached"), Here);
MessageQueue::enqueue_message(msg("yield"), Here); MessageQueue::enqueue_message(msg("yield"), Here);
// Starts with four pages. // Starts with four pages.
assert_pages(&[0, 1, 2, 3]); assert_pages(&[0, 1, 2, 3, 4]);
assert_eq!(MessageQueue::service_queues(1.into_weight()), 1.into_weight()); assert_eq!(MessageQueue::service_queues(1.into_weight()), 1.into_weight());
assert_last_event::<Test>( assert_last_event::<Test>(
...@@ -206,9 +207,9 @@ fn service_queues_failing_messages_works() { ...@@ -206,9 +207,9 @@ fn service_queues_failing_messages_works() {
.into(), .into(),
); );
assert_eq!(MessageQueue::service_queues(1.into_weight()), 1.into_weight()); assert_eq!(MessageQueue::service_queues(1.into_weight()), 1.into_weight());
assert_eq!(System::events().len(), 3); assert_eq!(System::events().len(), 4);
// Last page with the `yield` stays in. // Last page with the `yield` stays in.
assert_pages(&[3]); assert_pages(&[4]);
}); });
} }
...@@ -1880,3 +1881,97 @@ fn process_enqueued_on_idle_requires_enough_weight() { ...@@ -1880,3 +1881,97 @@ fn process_enqueued_on_idle_requires_enough_weight() {
assert_eq!(MessagesProcessed::take(), vec![]); assert_eq!(MessagesProcessed::take(), vec![]);
}) })
} }
/// A message that reports `StackLimitReached` will not be put into the overweight queue when
/// executed from the top level.
#[test]
fn process_discards_stack_ov_message() {
use MessageOrigin::*;
build_and_execute::<Test>(|| {
MessageQueue::enqueue_message(msg("stacklimitreached"), Here);
MessageQueue::service_queues(10.into_weight());
assert_last_event::<Test>(
Event::ProcessingFailed {
id: blake2_256(b"stacklimitreached").into(),
origin: MessageOrigin::Here,
error: ProcessMessageError::StackLimitReached,
}
.into(),
);
assert!(MessagesProcessed::take().is_empty());
// Message is gone and not overweight:
assert_pages(&[]);
});
}
/// A message that reports `StackLimitReached` will stay in the overweight queue when it is executed
/// by `execute_overweight`.
#[test]
fn execute_overweight_keeps_stack_ov_message() {
use MessageOrigin::*;
build_and_execute::<Test>(|| {
// We need to create a mocked message that first reports insufficient weight, and then
// `StackLimitReached`:
IgnoreStackOvError::set(true);
MessageQueue::enqueue_message(msg("stacklimitreached"), Here);
MessageQueue::service_queues(0.into_weight());
assert_last_event::<Test>(
Event::OverweightEnqueued {
id: blake2_256(b"stacklimitreached"),
origin: MessageOrigin::Here,
message_index: 0,
page_index: 0,
}
.into(),
);
// Does not count as 'processed':
assert!(MessagesProcessed::take().is_empty());
assert_pages(&[0]);
// Now let it return `StackLimitReached`. Note that this case would normally not happen,
// since we assume that the top-level execution is the one with the most remaining stack
// depth.
IgnoreStackOvError::set(false);
// Ensure that trying to execute the message does not change any state (besides events).
System::reset_events();
let storage_noop = StorageNoopGuard::new();
assert_eq!(
<MessageQueue as ServiceQueues>::execute_overweight(3.into_weight(), (Here, 0, 0)),
Err(ExecuteOverweightError::Other)
);
assert_last_event::<Test>(
Event::ProcessingFailed {
id: blake2_256(b"stacklimitreached").into(),
origin: MessageOrigin::Here,
error: ProcessMessageError::StackLimitReached,
}
.into(),
);
System::reset_events();
drop(storage_noop);
// Now let's process it normally:
IgnoreStackOvError::set(true);
assert_eq!(
<MessageQueue as ServiceQueues>::execute_overweight(1.into_weight(), (Here, 0, 0))
.unwrap(),
1.into_weight()
);
assert_last_event::<Test>(
Event::Processed {
id: blake2_256(b"stacklimitreached").into(),
origin: MessageOrigin::Here,
weight_used: 1.into_weight(),
success: true,
}
.into(),
);
assert_pages(&[]);
System::reset_events();
});
}
...@@ -111,7 +111,6 @@ impl pallet_staking::Config for Runtime { ...@@ -111,7 +111,6 @@ impl pallet_staking::Config for Runtime {
type EraPayout = pallet_staking::ConvertCurve<RewardCurve>; type EraPayout = pallet_staking::ConvertCurve<RewardCurve>;
type NextNewSession = (); type NextNewSession = ();
type MaxExposurePageSize = ConstU32<64>; type MaxExposurePageSize = ConstU32<64>;
type OffendingValidatorsThreshold = ();
type ElectionProvider = type ElectionProvider =
frame_election_provider_support::NoElection<(AccountId, BlockNumber, Staking, ())>; frame_election_provider_support::NoElection<(AccountId, BlockNumber, Staking, ())>;
type GenesisElectionProvider = Self::ElectionProvider; type GenesisElectionProvider = Self::ElectionProvider;
...@@ -124,6 +123,7 @@ impl pallet_staking::Config for Runtime { ...@@ -124,6 +123,7 @@ impl pallet_staking::Config for Runtime {
type EventListeners = Pools; type EventListeners = Pools;
type BenchmarkingConfig = pallet_staking::TestBenchmarkingConfig; type BenchmarkingConfig = pallet_staking::TestBenchmarkingConfig;
type WeightInfo = (); type WeightInfo = ();
type DisablingStrategy = pallet_staking::UpToLimitDisablingStrategy;
} }
parameter_types! { parameter_types! {
......
...@@ -125,7 +125,6 @@ impl pallet_staking::Config for Runtime { ...@@ -125,7 +125,6 @@ impl pallet_staking::Config for Runtime {
type EraPayout = pallet_staking::ConvertCurve<RewardCurve>; type EraPayout = pallet_staking::ConvertCurve<RewardCurve>;
type NextNewSession = (); type NextNewSession = ();
type MaxExposurePageSize = ConstU32<64>; type MaxExposurePageSize = ConstU32<64>;
type OffendingValidatorsThreshold = ();
type ElectionProvider = type ElectionProvider =
frame_election_provider_support::NoElection<(AccountId, BlockNumber, Staking, ())>; frame_election_provider_support::NoElection<(AccountId, BlockNumber, Staking, ())>;
type GenesisElectionProvider = Self::ElectionProvider; type GenesisElectionProvider = Self::ElectionProvider;
...@@ -138,6 +137,7 @@ impl pallet_staking::Config for Runtime { ...@@ -138,6 +137,7 @@ impl pallet_staking::Config for Runtime {
type EventListeners = Pools; type EventListeners = Pools;
type BenchmarkingConfig = pallet_staking::TestBenchmarkingConfig; type BenchmarkingConfig = pallet_staking::TestBenchmarkingConfig;
type WeightInfo = (); type WeightInfo = ();
type DisablingStrategy = pallet_staking::UpToLimitDisablingStrategy;
} }
parameter_types! { parameter_types! {
......
...@@ -174,7 +174,6 @@ impl pallet_staking::Config for Test { ...@@ -174,7 +174,6 @@ impl pallet_staking::Config for Test {
type EraPayout = pallet_staking::ConvertCurve<RewardCurve>; type EraPayout = pallet_staking::ConvertCurve<RewardCurve>;
type NextNewSession = Session; type NextNewSession = Session;
type MaxExposurePageSize = ConstU32<64>; type MaxExposurePageSize = ConstU32<64>;
type OffendingValidatorsThreshold = ();
type ElectionProvider = onchain::OnChainExecution<OnChainSeqPhragmen>; type ElectionProvider = onchain::OnChainExecution<OnChainSeqPhragmen>;
type GenesisElectionProvider = Self::ElectionProvider; type GenesisElectionProvider = Self::ElectionProvider;
type VoterList = pallet_staking::UseNominatorsAndValidatorsMap<Self>; type VoterList = pallet_staking::UseNominatorsAndValidatorsMap<Self>;
...@@ -186,6 +185,7 @@ impl pallet_staking::Config for Test { ...@@ -186,6 +185,7 @@ impl pallet_staking::Config for Test {
type EventListeners = (); type EventListeners = ();
type BenchmarkingConfig = pallet_staking::TestBenchmarkingConfig; type BenchmarkingConfig = pallet_staking::TestBenchmarkingConfig;
type WeightInfo = (); type WeightInfo = ();
type DisablingStrategy = pallet_staking::UpToLimitDisablingStrategy;
} }
impl pallet_im_online::Config for Test { impl pallet_im_online::Config for Test {
......
...@@ -132,7 +132,6 @@ where ...@@ -132,7 +132,6 @@ where
&concurrent_offenders, &concurrent_offenders,
&slash_perbill, &slash_perbill,
offence.session_index(), offence.session_index(),
offence.disable_strategy(),
); );
// Deposit the event. // Deposit the event.
......
...@@ -23,7 +23,7 @@ use frame_support::{ ...@@ -23,7 +23,7 @@ use frame_support::{
weights::Weight, weights::Weight,
Twox64Concat, Twox64Concat,
}; };
use sp_staking::offence::{DisableStrategy, OnOffenceHandler}; use sp_staking::offence::OnOffenceHandler;
use sp_std::vec::Vec; use sp_std::vec::Vec;
#[cfg(feature = "try-runtime")] #[cfg(feature = "try-runtime")]
...@@ -106,12 +106,7 @@ pub fn remove_deferred_storage<T: Config>() -> Weight { ...@@ -106,12 +106,7 @@ pub fn remove_deferred_storage<T: Config>() -> Weight {
let deferred = <DeferredOffences<T>>::take(); let deferred = <DeferredOffences<T>>::take();
log::info!(target: LOG_TARGET, "have {} deferred offences, applying.", deferred.len()); log::info!(target: LOG_TARGET, "have {} deferred offences, applying.", deferred.len());
for (offences, perbill, session) in deferred.iter() { for (offences, perbill, session) in deferred.iter() {
let consumed = T::OnOffenceHandler::on_offence( let consumed = T::OnOffenceHandler::on_offence(offences, perbill, *session);
offences,
perbill,
*session,
DisableStrategy::WhenSlashed,
);
weight = weight.saturating_add(consumed); weight = weight.saturating_add(consumed);
} }
......
...@@ -33,7 +33,7 @@ use sp_runtime::{ ...@@ -33,7 +33,7 @@ use sp_runtime::{
BuildStorage, Perbill, BuildStorage, Perbill,
}; };
use sp_staking::{ use sp_staking::{
offence::{self, DisableStrategy, Kind, OffenceDetails}, offence::{self, Kind, OffenceDetails},
SessionIndex, SessionIndex,
}; };
...@@ -51,7 +51,6 @@ impl<Reporter, Offender> offence::OnOffenceHandler<Reporter, Offender, Weight> ...@@ -51,7 +51,6 @@ impl<Reporter, Offender> offence::OnOffenceHandler<Reporter, Offender, Weight>
_offenders: &[OffenceDetails<Reporter, Offender>], _offenders: &[OffenceDetails<Reporter, Offender>],
slash_fraction: &[Perbill], slash_fraction: &[Perbill],
_offence_session: SessionIndex, _offence_session: SessionIndex,
_disable_strategy: DisableStrategy,
) -> Weight { ) -> Weight {
OnOffencePerbill::mutate(|f| { OnOffencePerbill::mutate(|f| {
*f = slash_fraction.to_vec(); *f = slash_fraction.to_vec();
......
...@@ -33,7 +33,7 @@ use alloc::vec::Vec; ...@@ -33,7 +33,7 @@ use alloc::vec::Vec;
use pallet_session::historical::IdentificationTuple; use pallet_session::historical::IdentificationTuple;
use pallet_staking::{BalanceOf, Exposure, ExposureOf, Pallet as Staking}; use pallet_staking::{BalanceOf, Exposure, ExposureOf, Pallet as Staking};
use sp_runtime::Perbill; use sp_runtime::Perbill;
use sp_staking::offence::{DisableStrategy, OnOffenceHandler}; use sp_staking::offence::OnOffenceHandler;
pub use pallet::*; pub use pallet::*;
...@@ -128,7 +128,7 @@ pub mod pallet { ...@@ -128,7 +128,7 @@ pub mod pallet {
T::AccountId, T::AccountId,
IdentificationTuple<T>, IdentificationTuple<T>,
Weight, Weight,
>>::on_offence(&offenders, &slash_fraction, session_index, DisableStrategy::WhenSlashed); >>::on_offence(&offenders, &slash_fraction, session_index);
} }
} }
} }
...@@ -133,7 +133,6 @@ parameter_types! { ...@@ -133,7 +133,6 @@ parameter_types! {
pub static SlashDeferDuration: EraIndex = 0; pub static SlashDeferDuration: EraIndex = 0;
pub const BondingDuration: EraIndex = 3; pub const BondingDuration: EraIndex = 3;
pub static LedgerSlashPerEra: (BalanceOf<Test>, BTreeMap<EraIndex, BalanceOf<Test>>) = (Zero::zero(), BTreeMap::new()); pub static LedgerSlashPerEra: (BalanceOf<Test>, BTreeMap<EraIndex, BalanceOf<Test>>) = (Zero::zero(), BTreeMap::new());
pub const OffendingValidatorsThreshold: Perbill = Perbill::from_percent(75);
} }
impl pallet_staking::Config for Test { impl pallet_staking::Config for Test {
...@@ -153,7 +152,6 @@ impl pallet_staking::Config for Test { ...@@ -153,7 +152,6 @@ impl pallet_staking::Config for Test {
type EraPayout = pallet_staking::ConvertCurve<RewardCurve>; type EraPayout = pallet_staking::ConvertCurve<RewardCurve>;
type NextNewSession = Session; type NextNewSession = Session;
type MaxExposurePageSize = ConstU32<64>; type MaxExposurePageSize = ConstU32<64>;
type OffendingValidatorsThreshold = OffendingValidatorsThreshold;
type ElectionProvider = onchain::OnChainExecution<OnChainSeqPhragmen>; type ElectionProvider = onchain::OnChainExecution<OnChainSeqPhragmen>;
type GenesisElectionProvider = Self::ElectionProvider; type GenesisElectionProvider = Self::ElectionProvider;
type TargetList = pallet_staking::UseValidatorsMap<Self>; type TargetList = pallet_staking::UseValidatorsMap<Self>;
...@@ -165,6 +163,7 @@ impl pallet_staking::Config for Test { ...@@ -165,6 +163,7 @@ impl pallet_staking::Config for Test {
type EventListeners = (); type EventListeners = ();
type BenchmarkingConfig = pallet_staking::TestBenchmarkingConfig; type BenchmarkingConfig = pallet_staking::TestBenchmarkingConfig;
type WeightInfo = (); type WeightInfo = ();
type DisablingStrategy = pallet_staking::UpToLimitDisablingStrategy;
} }
impl pallet_session::historical::Config for Test { impl pallet_session::historical::Config for Test {
......
...@@ -174,7 +174,6 @@ impl pallet_staking::Config for Test { ...@@ -174,7 +174,6 @@ impl pallet_staking::Config for Test {
type EraPayout = pallet_staking::ConvertCurve<RewardCurve>; type EraPayout = pallet_staking::ConvertCurve<RewardCurve>;
type NextNewSession = Session; type NextNewSession = Session;
type MaxExposurePageSize = ConstU32<64>; type MaxExposurePageSize = ConstU32<64>;
type OffendingValidatorsThreshold = ();
type ElectionProvider = onchain::OnChainExecution<OnChainSeqPhragmen>; type ElectionProvider = onchain::OnChainExecution<OnChainSeqPhragmen>;
type GenesisElectionProvider = Self::ElectionProvider; type GenesisElectionProvider = Self::ElectionProvider;
type MaxUnlockingChunks = ConstU32<32>; type MaxUnlockingChunks = ConstU32<32>;
...@@ -186,6 +185,7 @@ impl pallet_staking::Config for Test { ...@@ -186,6 +185,7 @@ impl pallet_staking::Config for Test {
type EventListeners = (); type EventListeners = ();
type BenchmarkingConfig = pallet_staking::TestBenchmarkingConfig; type BenchmarkingConfig = pallet_staking::TestBenchmarkingConfig;
type WeightInfo = (); type WeightInfo = ();
type DisablingStrategy = pallet_staking::UpToLimitDisablingStrategy;
} }
impl crate::Config for Test {} impl crate::Config for Test {}
......
...@@ -627,7 +627,7 @@ impl<T: Config> Pallet<T> { ...@@ -627,7 +627,7 @@ impl<T: Config> Pallet<T> {
Validators::<T>::put(&validators); Validators::<T>::put(&validators);
if changed { if changed {
// reset disabled validators // reset disabled validators if active set was changed
<DisabledValidators<T>>::take(); <DisabledValidators<T>>::take();
} }
......
...@@ -7,6 +7,25 @@ on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). We maintain a ...@@ -7,6 +7,25 @@ on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). We maintain a
single integer version number for staking pallet to keep track of all storage single integer version number for staking pallet to keep track of all storage
migrations. migrations.
## [v15]
### Added
- New trait `DisablingStrategy` which is responsible for making a decision which offenders should be
disabled on new offence.
- Default implementation of `DisablingStrategy` - `UpToLimitDisablingStrategy`. It
disables each new offender up to a threshold (1/3 by default). Offenders are not runtime disabled for
offences in previous era(s). But they will be low-priority node-side disabled for dispute initiation.
- `OffendingValidators` storage item is replaced with `DisabledValidators`. The former keeps all
offenders and if they are disabled or not. The latter just keeps a list of all offenders as they
are disabled by default.
### Deprecated
- `enum DisableStrategy` is no longer needed because disabling is not related to the type of the
offence anymore. A decision if a offender is disabled or not is made by a `DisablingStrategy`
implementation.
## [v14] ## [v14]
### Added ### Added
......
...@@ -1239,3 +1239,79 @@ impl BenchmarkingConfig for TestBenchmarkingConfig { ...@@ -1239,3 +1239,79 @@ impl BenchmarkingConfig for TestBenchmarkingConfig {
type MaxValidators = frame_support::traits::ConstU32<100>; type MaxValidators = frame_support::traits::ConstU32<100>;
type MaxNominators = frame_support::traits::ConstU32<100>; type MaxNominators = frame_support::traits::ConstU32<100>;
} }
/// Controls validator disabling
pub trait DisablingStrategy<T: Config> {
/// Make a disabling decision. Returns the index of the validator to disable or `None` if no new
/// validator should be disabled.
fn decision(
offender_stash: &T::AccountId,
slash_era: EraIndex,
currently_disabled: &Vec<u32>,
) -> Option<u32>;
}
/// Implementation of [`DisablingStrategy`] which disables validators from the active set up to a
/// threshold. `DISABLING_LIMIT_FACTOR` is the factor of the maximum disabled validators in the
/// active set. E.g. setting this value to `3` means no more than 1/3 of the validators in the
/// active set can be disabled in an era.
/// By default a factor of 3 is used which is the byzantine threshold.
pub struct UpToLimitDisablingStrategy<const DISABLING_LIMIT_FACTOR: usize = 3>;
impl<const DISABLING_LIMIT_FACTOR: usize> UpToLimitDisablingStrategy<DISABLING_LIMIT_FACTOR> {
/// Disabling limit calculated from the total number of validators in the active set. When
/// reached no more validators will be disabled.
pub fn disable_limit(validators_len: usize) -> usize {
validators_len
.saturating_sub(1)
.checked_div(DISABLING_LIMIT_FACTOR)
.unwrap_or_else(|| {
defensive!("DISABLING_LIMIT_FACTOR should not be 0");
0
})
}
}
impl<T: Config, const DISABLING_LIMIT_FACTOR: usize> DisablingStrategy<T>
for UpToLimitDisablingStrategy<DISABLING_LIMIT_FACTOR>
{
fn decision(
offender_stash: &T::AccountId,
slash_era: EraIndex,
currently_disabled: &Vec<u32>,
) -> Option<u32> {
let active_set = T::SessionInterface::validators();
// We don't disable more than the limit
if currently_disabled.len() >= Self::disable_limit(active_set.len()) {
log!(
debug,
"Won't disable: reached disabling limit {:?}",
Self::disable_limit(active_set.len())
);
return None
}
// We don't disable for offences in previous eras
if ActiveEra::<T>::get().map(|e| e.index).unwrap_or_default() > slash_era {
log!(
debug,
"Won't disable: current_era {:?} > slash_era {:?}",
Pallet::<T>::current_era().unwrap_or_default(),
slash_era
);
return None
}
let offender_idx = if let Some(idx) = active_set.iter().position(|i| i == offender_stash) {
idx as u32
} else {
log!(debug, "Won't disable: offender not in active set",);
return None
};
log!(debug, "Will disable {:?}", offender_idx);
Some(offender_idx)
}
}
...@@ -20,9 +20,10 @@ ...@@ -20,9 +20,10 @@
use super::*; use super::*;
use frame_election_provider_support::SortedListProvider; use frame_election_provider_support::SortedListProvider;
use frame_support::{ use frame_support::{
migrations::VersionedMigration,
pallet_prelude::ValueQuery, pallet_prelude::ValueQuery,
storage_alias, storage_alias,
traits::{GetStorageVersion, OnRuntimeUpgrade}, traits::{GetStorageVersion, OnRuntimeUpgrade, UncheckedOnRuntimeUpgrade},
}; };
#[cfg(feature = "try-runtime")] #[cfg(feature = "try-runtime")]
...@@ -59,11 +60,61 @@ impl Default for ObsoleteReleases { ...@@ -59,11 +60,61 @@ impl Default for ObsoleteReleases {
#[storage_alias] #[storage_alias]
type StorageVersion<T: Config> = StorageValue<Pallet<T>, ObsoleteReleases, ValueQuery>; type StorageVersion<T: Config> = StorageValue<Pallet<T>, ObsoleteReleases, ValueQuery>;
/// Migrating `OffendingValidators` from `Vec<(u32, bool)>` to `Vec<u32>`
pub mod v15 {
use super::*;
// The disabling strategy used by staking pallet
type DefaultDisablingStrategy = UpToLimitDisablingStrategy;
pub struct VersionUncheckedMigrateV14ToV15<T>(sp_std::marker::PhantomData<T>);
impl<T: Config> UncheckedOnRuntimeUpgrade for VersionUncheckedMigrateV14ToV15<T> {
fn on_runtime_upgrade() -> Weight {
let mut migrated = v14::OffendingValidators::<T>::take()
.into_iter()
.filter(|p| p.1) // take only disabled validators
.map(|p| p.0)
.collect::<Vec<_>>();
// Respect disabling limit
migrated.truncate(DefaultDisablingStrategy::disable_limit(
T::SessionInterface::validators().len(),
));
DisabledValidators::<T>::set(migrated);
log!(info, "v15 applied successfully.");
T::DbWeight::get().reads_writes(1, 1)
}
#[cfg(feature = "try-runtime")]
fn post_upgrade(_state: Vec<u8>) -> Result<(), TryRuntimeError> {
frame_support::ensure!(
v14::OffendingValidators::<T>::decode_len().is_none(),
"OffendingValidators is not empty after the migration"
);
Ok(())
}
}
pub type MigrateV14ToV15<T> = VersionedMigration<
14,
15,
VersionUncheckedMigrateV14ToV15<T>,
Pallet<T>,
<T as frame_system::Config>::DbWeight,
>;
}
/// Migration of era exposure storage items to paged exposures. /// Migration of era exposure storage items to paged exposures.
/// Changelog: [v14.](https://github.com/paritytech/substrate/blob/ankan/paged-rewards-rebased2/frame/staking/CHANGELOG.md#14) /// Changelog: [v14.](https://github.com/paritytech/substrate/blob/ankan/paged-rewards-rebased2/frame/staking/CHANGELOG.md#14)
pub mod v14 { pub mod v14 {
use super::*; use super::*;
#[frame_support::storage_alias]
pub(crate) type OffendingValidators<T: Config> =
StorageValue<Pallet<T>, Vec<(u32, bool)>, ValueQuery>;
pub struct MigrateToV14<T>(core::marker::PhantomData<T>); pub struct MigrateToV14<T>(core::marker::PhantomData<T>);
impl<T: Config> OnRuntimeUpgrade for MigrateToV14<T> { impl<T: Config> OnRuntimeUpgrade for MigrateToV14<T> {
fn on_runtime_upgrade() -> Weight { fn on_runtime_upgrade() -> Weight {
...@@ -73,10 +124,10 @@ pub mod v14 { ...@@ -73,10 +124,10 @@ pub mod v14 {
if in_code == 14 && on_chain == 13 { if in_code == 14 && on_chain == 13 {
in_code.put::<Pallet<T>>(); in_code.put::<Pallet<T>>();
log!(info, "v14 applied successfully."); log!(info, "staking v14 applied successfully.");
T::DbWeight::get().reads_writes(1, 1) T::DbWeight::get().reads_writes(1, 1)
} else { } else {
log!(warn, "v14 not applied."); log!(warn, "staking v14 not applied.");
T::DbWeight::get().reads(1) T::DbWeight::get().reads(1)
} }
} }
......
...@@ -34,7 +34,7 @@ use frame_system::{EnsureRoot, EnsureSignedBy}; ...@@ -34,7 +34,7 @@ use frame_system::{EnsureRoot, EnsureSignedBy};
use sp_io; use sp_io;
use sp_runtime::{curve::PiecewiseLinear, testing::UintAuthorityId, traits::Zero, BuildStorage}; use sp_runtime::{curve::PiecewiseLinear, testing::UintAuthorityId, traits::Zero, BuildStorage};
use sp_staking::{ use sp_staking::{
offence::{DisableStrategy, OffenceDetails, OnOffenceHandler}, offence::{OffenceDetails, OnOffenceHandler},
OnStakingUpdate, OnStakingUpdate,
}; };
...@@ -186,7 +186,6 @@ pallet_staking_reward_curve::build! { ...@@ -186,7 +186,6 @@ pallet_staking_reward_curve::build! {
parameter_types! { parameter_types! {
pub const BondingDuration: EraIndex = 3; pub const BondingDuration: EraIndex = 3;
pub const RewardCurve: &'static PiecewiseLinear<'static> = &I_NPOS; pub const RewardCurve: &'static PiecewiseLinear<'static> = &I_NPOS;
pub const OffendingValidatorsThreshold: Perbill = Perbill::from_percent(75);
} }
parameter_types! { parameter_types! {
...@@ -267,6 +266,9 @@ impl OnStakingUpdate<AccountId, Balance> for EventListenerMock { ...@@ -267,6 +266,9 @@ impl OnStakingUpdate<AccountId, Balance> for EventListenerMock {
} }
} }
// Disabling threshold for `UpToLimitDisablingStrategy`
pub(crate) const DISABLING_LIMIT_FACTOR: usize = 3;
impl crate::pallet::pallet::Config for Test { impl crate::pallet::pallet::Config for Test {
type Currency = Balances; type Currency = Balances;
type CurrencyBalance = <Self as pallet_balances::Config>::Balance; type CurrencyBalance = <Self as pallet_balances::Config>::Balance;
...@@ -284,7 +286,6 @@ impl crate::pallet::pallet::Config for Test { ...@@ -284,7 +286,6 @@ impl crate::pallet::pallet::Config for Test {
type EraPayout = ConvertCurve<RewardCurve>; type EraPayout = ConvertCurve<RewardCurve>;
type NextNewSession = Session; type NextNewSession = Session;
type MaxExposurePageSize = MaxExposurePageSize; type MaxExposurePageSize = MaxExposurePageSize;
type OffendingValidatorsThreshold = OffendingValidatorsThreshold;
type ElectionProvider = onchain::OnChainExecution<OnChainSeqPhragmen>; type ElectionProvider = onchain::OnChainExecution<OnChainSeqPhragmen>;
type GenesisElectionProvider = Self::ElectionProvider; type GenesisElectionProvider = Self::ElectionProvider;
// NOTE: consider a macro and use `UseNominatorsAndValidatorsMap<Self>` as well. // NOTE: consider a macro and use `UseNominatorsAndValidatorsMap<Self>` as well.
...@@ -297,6 +298,7 @@ impl crate::pallet::pallet::Config for Test { ...@@ -297,6 +298,7 @@ impl crate::pallet::pallet::Config for Test {
type EventListeners = EventListenerMock; type EventListeners = EventListenerMock;
type BenchmarkingConfig = TestBenchmarkingConfig; type BenchmarkingConfig = TestBenchmarkingConfig;
type WeightInfo = (); type WeightInfo = ();
type DisablingStrategy = pallet_staking::UpToLimitDisablingStrategy<DISABLING_LIMIT_FACTOR>;
} }
pub struct WeightedNominationsQuota<const MAX: u32>; pub struct WeightedNominationsQuota<const MAX: u32>;
...@@ -461,6 +463,8 @@ impl ExtBuilder { ...@@ -461,6 +463,8 @@ impl ExtBuilder {
(31, self.balance_factor * 2000), (31, self.balance_factor * 2000),
(41, self.balance_factor * 2000), (41, self.balance_factor * 2000),
(51, self.balance_factor * 2000), (51, self.balance_factor * 2000),
(201, self.balance_factor * 2000),
(202, self.balance_factor * 2000),
// optional nominator // optional nominator
(100, self.balance_factor * 2000), (100, self.balance_factor * 2000),
(101, self.balance_factor * 2000), (101, self.balance_factor * 2000),
...@@ -488,8 +492,10 @@ impl ExtBuilder { ...@@ -488,8 +492,10 @@ impl ExtBuilder {
(31, 31, self.balance_factor * 500, StakerStatus::<AccountId>::Validator), (31, 31, self.balance_factor * 500, StakerStatus::<AccountId>::Validator),
// an idle validator // an idle validator
(41, 41, self.balance_factor * 1000, StakerStatus::<AccountId>::Idle), (41, 41, self.balance_factor * 1000, StakerStatus::<AccountId>::Idle),
]; (51, 51, self.balance_factor * 1000, StakerStatus::<AccountId>::Idle),
// optionally add a nominator (201, 201, self.balance_factor * 1000, StakerStatus::<AccountId>::Idle),
(202, 202, self.balance_factor * 1000, StakerStatus::<AccountId>::Idle),
]; // optionally add a nominator
if self.nominate { if self.nominate {
stakers.push(( stakers.push((
101, 101,
...@@ -728,12 +734,11 @@ pub(crate) fn on_offence_in_era( ...@@ -728,12 +734,11 @@ pub(crate) fn on_offence_in_era(
>], >],
slash_fraction: &[Perbill], slash_fraction: &[Perbill],
era: EraIndex, era: EraIndex,
disable_strategy: DisableStrategy,
) { ) {
let bonded_eras = crate::BondedEras::<Test>::get(); let bonded_eras = crate::BondedEras::<Test>::get();
for &(bonded_era, start_session) in bonded_eras.iter() { for &(bonded_era, start_session) in bonded_eras.iter() {
if bonded_era == era { if bonded_era == era {
let _ = Staking::on_offence(offenders, slash_fraction, start_session, disable_strategy); let _ = Staking::on_offence(offenders, slash_fraction, start_session);
return return
} else if bonded_era > era { } else if bonded_era > era {
break break
...@@ -745,7 +750,6 @@ pub(crate) fn on_offence_in_era( ...@@ -745,7 +750,6 @@ pub(crate) fn on_offence_in_era(
offenders, offenders,
slash_fraction, slash_fraction,
Staking::eras_start_session_index(era).unwrap(), Staking::eras_start_session_index(era).unwrap(),
disable_strategy,
); );
} else { } else {
panic!("cannot slash in era {}", era); panic!("cannot slash in era {}", era);
...@@ -760,7 +764,7 @@ pub(crate) fn on_offence_now( ...@@ -760,7 +764,7 @@ pub(crate) fn on_offence_now(
slash_fraction: &[Perbill], slash_fraction: &[Perbill],
) { ) {
let now = Staking::active_era().unwrap().index; let now = Staking::active_era().unwrap().index;
on_offence_in_era(offenders, slash_fraction, now, DisableStrategy::WhenSlashed) on_offence_in_era(offenders, slash_fraction, now)
} }
pub(crate) fn add_slash(who: &AccountId) { pub(crate) fn add_slash(who: &AccountId) {
......