mirror of
https://github.com/pezkuwichain/pezkuwi-subxt.git
synced 2026-06-15 13:51:11 +00:00
Benchmarks for offences pallet. (#5851)
* really rough mock runtime * start to work on offences * Make sure to start the session. * Update to latest master. * Add verify. * Fix on_initialize benchmark. * Add grandpa offence. * Add Babe offence benchmarking. * Enable babe test. * Address review grumbles. * Address review grumbles. * Address review grumbles part 1/2 * use currency trait * features * Check events explicitly. * Auto-impl tuple converter. * Removed dead code. * add test feature flag * dont use std Co-authored-by: Shawn Tabrizi <shawntabrizi@gmail.com>
This commit is contained in:
@@ -18,28 +18,32 @@
|
||||
|
||||
#![cfg_attr(not(feature = "std"), no_std)]
|
||||
|
||||
mod mock;
|
||||
|
||||
use sp_std::prelude::*;
|
||||
use sp_std::vec;
|
||||
|
||||
use frame_system::RawOrigin;
|
||||
use frame_system::{RawOrigin, Module as System, Trait as SystemTrait};
|
||||
use frame_benchmarking::{benchmarks, account};
|
||||
use frame_support::traits::{Currency, OnInitialize};
|
||||
|
||||
use sp_runtime::{Perbill, traits::{Convert, StaticLookup}};
|
||||
use sp_staking::offence::ReportOffence;
|
||||
use sp_runtime::{Perbill, traits::{Convert, StaticLookup, Saturating, UniqueSaturatedInto}};
|
||||
use sp_staking::offence::{ReportOffence, Offence, OffenceDetails};
|
||||
|
||||
use pallet_balances::{Trait as BalancesTrait};
|
||||
use pallet_babe::BabeEquivocationOffence;
|
||||
use pallet_grandpa::{GrandpaEquivocationOffence, GrandpaTimeSlot};
|
||||
use pallet_im_online::{Trait as ImOnlineTrait, Module as ImOnline, UnresponsivenessOffence};
|
||||
use pallet_offences::{Trait as OffencesTrait, Module as Offences};
|
||||
use pallet_session::historical::{Trait as HistoricalTrait, IdentificationTuple};
|
||||
use pallet_session::{Trait as SessionTrait, SessionManager};
|
||||
use pallet_staking::{
|
||||
Module as Staking, Trait as StakingTrait, RewardDestination, ValidatorPrefs,
|
||||
Exposure, IndividualExposure, ElectionStatus
|
||||
Exposure, IndividualExposure, ElectionStatus, MAX_NOMINATIONS, Event as StakingEvent
|
||||
};
|
||||
use pallet_session::Trait as SessionTrait;
|
||||
use pallet_session::historical::{Trait as HistoricalTrait, IdentificationTuple};
|
||||
|
||||
const SEED: u32 = 0;
|
||||
|
||||
const MAX_USERS: u32 = 1000;
|
||||
const MAX_REPORTERS: u32 = 100;
|
||||
const MAX_OFFENDERS: u32 = 100;
|
||||
const MAX_NOMINATORS: u32 = 100;
|
||||
@@ -47,15 +51,54 @@ const MAX_DEFERRED_OFFENCES: u32 = 100;
|
||||
|
||||
pub struct Module<T: Trait>(Offences<T>);
|
||||
|
||||
pub trait Trait: SessionTrait + StakingTrait + OffencesTrait + ImOnlineTrait + HistoricalTrait {}
|
||||
pub trait Trait:
|
||||
SessionTrait
|
||||
+ StakingTrait
|
||||
+ OffencesTrait
|
||||
+ ImOnlineTrait
|
||||
+ HistoricalTrait
|
||||
+ BalancesTrait
|
||||
+ IdTupleConvert<Self>
|
||||
{}
|
||||
|
||||
fn create_offender<T: Trait>(n: u32, nominators: u32) -> Result<T::AccountId, &'static str> {
|
||||
/// A helper trait to make sure we can convert `IdentificationTuple` coming from historical
|
||||
/// and the one required by offences.
|
||||
pub trait IdTupleConvert<T: HistoricalTrait + OffencesTrait> {
|
||||
/// Convert identification tuple from `historical` trait to the one expected by `offences`.
|
||||
fn convert(id: IdentificationTuple<T>) -> <T as OffencesTrait>::IdentificationTuple;
|
||||
}
|
||||
|
||||
impl<T: HistoricalTrait + OffencesTrait> IdTupleConvert<T> for T where
|
||||
<T as OffencesTrait>::IdentificationTuple: From<IdentificationTuple<T>>
|
||||
{
|
||||
fn convert(id: IdentificationTuple<T>) -> <T as OffencesTrait>::IdentificationTuple {
|
||||
id.into()
|
||||
}
|
||||
}
|
||||
|
||||
type LookupSourceOf<T> = <<T as SystemTrait>::Lookup as StaticLookup>::Source;
|
||||
type BalanceOf<T> = <<T as StakingTrait>::Currency as Currency<<T as SystemTrait>::AccountId>>::Balance;
|
||||
|
||||
struct Offender<T: Trait> {
|
||||
pub controller: T::AccountId,
|
||||
pub stash: T::AccountId,
|
||||
pub nominator_stashes: Vec<T::AccountId>,
|
||||
}
|
||||
|
||||
fn bond_amount<T: Trait>() -> BalanceOf<T> {
|
||||
T::Currency::minimum_balance().saturating_mul(10_000.into())
|
||||
}
|
||||
|
||||
fn create_offender<T: Trait>(n: u32, nominators: u32) -> Result<Offender<T>, &'static str> {
|
||||
let stash: T::AccountId = account("stash", n, SEED);
|
||||
let controller: T::AccountId = account("controller", n, SEED);
|
||||
let controller_lookup: <T::Lookup as StaticLookup>::Source = T::Lookup::unlookup(controller.clone());
|
||||
let controller_lookup: LookupSourceOf<T> = T::Lookup::unlookup(controller.clone());
|
||||
let reward_destination = RewardDestination::Staked;
|
||||
let amount = T::Currency::minimum_balance();
|
||||
|
||||
let raw_amount = bond_amount::<T>();
|
||||
// add twice as much balance to prevent the account from being killed.
|
||||
let free_amount = raw_amount.saturating_mul(2.into());
|
||||
T::Currency::make_free_balance_be(&stash, free_amount);
|
||||
let amount: BalanceOf<T> = raw_amount.into();
|
||||
Staking::<T>::bond(
|
||||
RawOrigin::Signed(stash.clone()).into(),
|
||||
controller_lookup.clone(),
|
||||
@@ -69,27 +112,29 @@ fn create_offender<T: Trait>(n: u32, nominators: u32) -> Result<T::AccountId, &'
|
||||
Staking::<T>::validate(RawOrigin::Signed(controller.clone()).into(), validator_prefs)?;
|
||||
|
||||
let mut individual_exposures = vec![];
|
||||
|
||||
let mut nominator_stashes = vec![];
|
||||
// Create n nominators
|
||||
for i in 0 .. nominators {
|
||||
let nominator_stash: T::AccountId = account("nominator stash", n * MAX_NOMINATORS + i, SEED);
|
||||
let nominator_controller: T::AccountId = account("nominator controller", n * MAX_NOMINATORS + i, SEED);
|
||||
let nominator_controller_lookup: <T::Lookup as StaticLookup>::Source = T::Lookup::unlookup(nominator_controller.clone());
|
||||
let nominator_controller_lookup: LookupSourceOf<T> = T::Lookup::unlookup(nominator_controller.clone());
|
||||
T::Currency::make_free_balance_be(&nominator_stash, free_amount.into());
|
||||
|
||||
Staking::<T>::bond(
|
||||
RawOrigin::Signed(nominator_stash.clone()).into(),
|
||||
nominator_controller_lookup.clone(),
|
||||
amount,
|
||||
amount.clone(),
|
||||
reward_destination,
|
||||
)?;
|
||||
|
||||
let selected_validators: Vec<<T::Lookup as StaticLookup>::Source> = vec![controller_lookup.clone()];
|
||||
let selected_validators: Vec<LookupSourceOf<T>> = vec![controller_lookup.clone()];
|
||||
Staking::<T>::nominate(RawOrigin::Signed(nominator_controller.clone()).into(), selected_validators)?;
|
||||
|
||||
individual_exposures.push(IndividualExposure {
|
||||
who: nominator_controller.clone(),
|
||||
who: nominator_stash.clone(),
|
||||
value: amount.clone(),
|
||||
});
|
||||
nominator_stashes.push(nominator_stash.clone());
|
||||
}
|
||||
|
||||
let exposure = Exposure {
|
||||
@@ -100,76 +145,275 @@ fn create_offender<T: Trait>(n: u32, nominators: u32) -> Result<T::AccountId, &'
|
||||
let current_era = 0u32;
|
||||
Staking::<T>::add_era_stakers(current_era.into(), stash.clone().into(), exposure);
|
||||
|
||||
Ok(controller)
|
||||
Ok(Offender { controller, stash, nominator_stashes })
|
||||
}
|
||||
|
||||
fn make_offenders<T: Trait>(num_offenders: u32, num_nominators: u32) -> Result<Vec<IdentificationTuple<T>>, &'static str> {
|
||||
let mut offenders: Vec<T::AccountId> = vec![];
|
||||
fn make_offenders<T: Trait>(num_offenders: u32, num_nominators: u32) -> Result<
|
||||
(Vec<IdentificationTuple<T>>, Vec<Offender<T>>),
|
||||
&'static str
|
||||
> {
|
||||
Staking::<T>::new_session(0);
|
||||
|
||||
let mut offenders = vec![];
|
||||
for i in 0 .. num_offenders {
|
||||
let offender = create_offender::<T>(i, num_nominators)?;
|
||||
let offender = create_offender::<T>(i + 1, num_nominators)?;
|
||||
offenders.push(offender);
|
||||
}
|
||||
|
||||
Ok(offenders.iter()
|
||||
.map(|id|
|
||||
<T as SessionTrait>::ValidatorIdOf::convert(id.clone())
|
||||
Staking::<T>::start_session(0);
|
||||
|
||||
let id_tuples = offenders.iter()
|
||||
.map(|offender|
|
||||
<T as SessionTrait>::ValidatorIdOf::convert(offender.controller.clone())
|
||||
.expect("failed to get validator id from account id"))
|
||||
.map(|validator_id|
|
||||
<T as HistoricalTrait>::FullIdentificationOf::convert(validator_id.clone())
|
||||
.map(|full_id| (validator_id, full_id))
|
||||
.expect("failed to convert validator id to full identification"))
|
||||
.collect::<Vec<IdentificationTuple<T>>>())
|
||||
.collect::<Vec<IdentificationTuple<T>>>();
|
||||
Ok((id_tuples, offenders))
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
fn check_events<T: Trait, I: Iterator<Item = <T as SystemTrait>::Event>>(expected: I) {
|
||||
let events = System::<T>::events() .into_iter()
|
||||
.map(|frame_system::EventRecord { event, .. }| event).collect::<Vec<_>>();
|
||||
let expected = expected.collect::<Vec<_>>();
|
||||
let lengths = (events.len(), expected.len());
|
||||
let length_mismatch = if lengths.0 != lengths.1 {
|
||||
fn pretty<D: std::fmt::Debug>(header: &str, ev: &[D]) {
|
||||
println!("{}", header);
|
||||
for (idx, ev) in ev.iter().enumerate() {
|
||||
println!("\t[{:04}] {:?}", idx, ev);
|
||||
}
|
||||
}
|
||||
pretty("--Got:", &events);
|
||||
pretty("--Expected:", &expected);
|
||||
format!("Mismatching length. Got: {}, expected: {}", lengths.0, lengths.1)
|
||||
} else { Default::default() };
|
||||
|
||||
for (idx, (a, b)) in events.into_iter().zip(expected).enumerate() {
|
||||
assert_eq!(a, b, "Mismatch at: {}. {}", idx, length_mismatch);
|
||||
}
|
||||
|
||||
if !length_mismatch.is_empty() {
|
||||
panic!(length_mismatch);
|
||||
}
|
||||
}
|
||||
|
||||
benchmarks! {
|
||||
_ {
|
||||
let u in 1 .. MAX_USERS => ();
|
||||
let r in 1 .. MAX_REPORTERS => ();
|
||||
let o in 1 .. MAX_OFFENDERS => ();
|
||||
let n in 1 .. MAX_NOMINATORS => ();
|
||||
let d in 1 .. MAX_DEFERRED_OFFENCES => ();
|
||||
}
|
||||
_ { }
|
||||
|
||||
report_offence {
|
||||
let r in ...;
|
||||
let o in ...;
|
||||
let n in ...;
|
||||
report_offence_im_online {
|
||||
let r in 1 .. MAX_REPORTERS;
|
||||
// we skip 1 offender, because in such case there is no slashing
|
||||
let o in 2 .. MAX_OFFENDERS;
|
||||
let n in 0 .. MAX_NOMINATORS.min(MAX_NOMINATIONS as u32);
|
||||
|
||||
// Make r reporters
|
||||
let mut reporters = vec![];
|
||||
|
||||
for i in 0 .. r {
|
||||
let reporter = account("reporter", i, SEED);
|
||||
reporters.push(reporter);
|
||||
}
|
||||
|
||||
let offenders = make_offenders::<T>(o, n).expect("failed to create offenders");
|
||||
let keys = ImOnline::<T>::keys();
|
||||
|
||||
// make sure reporters actually get rewarded
|
||||
Staking::<T>::set_slash_reward_fraction(Perbill::one());
|
||||
|
||||
let (offenders, raw_offenders) = make_offenders::<T>(o, n)?;
|
||||
let keys = ImOnline::<T>::keys();
|
||||
let validator_set_count = keys.len() as u32;
|
||||
|
||||
let slash_fraction = UnresponsivenessOffence::<T::AccountId>::slash_fraction(
|
||||
offenders.len() as u32, validator_set_count,
|
||||
);
|
||||
let offence = UnresponsivenessOffence {
|
||||
session_index: 0,
|
||||
validator_set_count: keys.len() as u32,
|
||||
validator_set_count,
|
||||
offenders,
|
||||
};
|
||||
|
||||
assert_eq!(System::<T>::event_count(), 0);
|
||||
}: {
|
||||
let _ = <T as ImOnlineTrait>::ReportUnresponsiveness::report_offence(reporters, offence);
|
||||
let _ = <T as ImOnlineTrait>::ReportUnresponsiveness::report_offence(
|
||||
reporters.clone(),
|
||||
offence
|
||||
);
|
||||
}
|
||||
verify {
|
||||
// make sure the report was not deferred
|
||||
assert!(Offences::<T>::deferred_offences().is_empty());
|
||||
let slash_amount = slash_fraction * bond_amount::<T>().unique_saturated_into() as u32;
|
||||
let reward_amount = slash_amount * (1 + n) / 2;
|
||||
let mut slash_events = raw_offenders.into_iter()
|
||||
.flat_map(|offender| {
|
||||
core::iter::once(offender.stash).chain(offender.nominator_stashes.into_iter())
|
||||
})
|
||||
.map(|stash| <T as StakingTrait>::Event::from(
|
||||
StakingEvent::<T>::Slash(stash, BalanceOf::<T>::from(slash_amount))
|
||||
))
|
||||
.collect::<Vec<_>>();
|
||||
let reward_events = reporters.into_iter()
|
||||
.flat_map(|reporter| vec![
|
||||
frame_system::Event::<T>::NewAccount(reporter.clone()).into(),
|
||||
<T as BalancesTrait>::Event::from(
|
||||
pallet_balances::Event::<T>::Endowed(reporter.clone(), (reward_amount / r).into())
|
||||
).into()
|
||||
]);
|
||||
|
||||
// rewards are applied after first offender and it's nominators
|
||||
let slash_rest = slash_events.split_off(1 + n as usize);
|
||||
|
||||
// make sure that all slashes have been applied
|
||||
#[cfg(test)]
|
||||
check_events::<T, _>(
|
||||
std::iter::empty()
|
||||
.chain(slash_events.into_iter().map(Into::into))
|
||||
.chain(reward_events)
|
||||
.chain(slash_rest.into_iter().map(Into::into))
|
||||
.chain(std::iter::once(<T as OffencesTrait>::Event::from(
|
||||
pallet_offences::Event::Offence(
|
||||
UnresponsivenessOffence::<T>::ID,
|
||||
0_u32.to_le_bytes().to_vec(),
|
||||
true
|
||||
)
|
||||
).into()))
|
||||
);
|
||||
}
|
||||
|
||||
report_offence_grandpa {
|
||||
let r in 1 .. MAX_REPORTERS;
|
||||
let n in 0 .. MAX_NOMINATORS.min(MAX_NOMINATIONS as u32);
|
||||
let o = 1;
|
||||
|
||||
// Make r reporters
|
||||
let mut reporters = vec![];
|
||||
for i in 0 .. r {
|
||||
let reporter = account("reporter", i, SEED);
|
||||
reporters.push(reporter);
|
||||
}
|
||||
|
||||
// make sure reporters actually get rewarded
|
||||
Staking::<T>::set_slash_reward_fraction(Perbill::one());
|
||||
|
||||
let (mut offenders, raw_offenders) = make_offenders::<T>(o, n)?;
|
||||
let keys = ImOnline::<T>::keys();
|
||||
|
||||
let offence = GrandpaEquivocationOffence {
|
||||
time_slot: GrandpaTimeSlot { set_id: 0, round: 0 },
|
||||
session_index: 0,
|
||||
validator_set_count: keys.len() as u32,
|
||||
offender: T::convert(offenders.pop().unwrap()),
|
||||
};
|
||||
assert_eq!(System::<T>::event_count(), 0);
|
||||
}: {
|
||||
let _ = Offences::<T>::report_offence(reporters, offence);
|
||||
}
|
||||
verify {
|
||||
// make sure the report was not deferred
|
||||
assert!(Offences::<T>::deferred_offences().is_empty());
|
||||
// make sure that all slashes have been applied
|
||||
assert_eq!(
|
||||
System::<T>::event_count(), 0
|
||||
+ 1 // offence
|
||||
+ 2 * r // reporter (reward + endowment)
|
||||
+ o // offenders slashed
|
||||
+ o * n // nominators slashed
|
||||
);
|
||||
}
|
||||
|
||||
report_offence_babe {
|
||||
let r in 1 .. MAX_REPORTERS;
|
||||
let n in 0 .. MAX_NOMINATORS.min(MAX_NOMINATIONS as u32);
|
||||
let o = 1;
|
||||
|
||||
// Make r reporters
|
||||
let mut reporters = vec![];
|
||||
for i in 0 .. r {
|
||||
let reporter = account("reporter", i, SEED);
|
||||
reporters.push(reporter);
|
||||
}
|
||||
|
||||
// make sure reporters actually get rewarded
|
||||
Staking::<T>::set_slash_reward_fraction(Perbill::one());
|
||||
|
||||
let (mut offenders, raw_offenders) = make_offenders::<T>(o, n)?;
|
||||
let keys = ImOnline::<T>::keys();
|
||||
|
||||
let offence = BabeEquivocationOffence {
|
||||
slot: 0,
|
||||
session_index: 0,
|
||||
validator_set_count: keys.len() as u32,
|
||||
offender: T::convert(offenders.pop().unwrap()),
|
||||
};
|
||||
assert_eq!(System::<T>::event_count(), 0);
|
||||
}: {
|
||||
let _ = Offences::<T>::report_offence(reporters, offence);
|
||||
}
|
||||
verify {
|
||||
// make sure the report was not deferred
|
||||
assert!(Offences::<T>::deferred_offences().is_empty());
|
||||
// make sure that all slashes have been applied
|
||||
assert_eq!(
|
||||
System::<T>::event_count(), 0
|
||||
+ 1 // offence
|
||||
+ 2 * r // reporter (reward + endowment)
|
||||
+ o // offenders slashed
|
||||
+ o * n // nominators slashed
|
||||
);
|
||||
}
|
||||
|
||||
on_initialize {
|
||||
let d in ...;
|
||||
let d in 1 .. MAX_DEFERRED_OFFENCES;
|
||||
let o = 10;
|
||||
let n = 100;
|
||||
|
||||
Staking::<T>::put_election_status(ElectionStatus::Closed);
|
||||
|
||||
let mut deferred_offences = vec![];
|
||||
let offenders = make_offenders::<T>(o, n)?.0;
|
||||
let offence_details = offenders.into_iter()
|
||||
.map(|offender| OffenceDetails {
|
||||
offender: T::convert(offender),
|
||||
reporters: vec![],
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
for i in 0 .. d {
|
||||
deferred_offences.push((vec![], vec![], 0u32));
|
||||
let fractions = offence_details.iter()
|
||||
.map(|_| Perbill::from_percent(100 * (i + 1) / MAX_DEFERRED_OFFENCES))
|
||||
.collect::<Vec<_>>();
|
||||
deferred_offences.push((offence_details.clone(), fractions.clone(), 0u32));
|
||||
}
|
||||
|
||||
Offences::<T>::set_deferred_offences(deferred_offences);
|
||||
|
||||
assert!(!Offences::<T>::deferred_offences().is_empty());
|
||||
}: {
|
||||
Offences::<T>::on_initialize(u.into());
|
||||
Offences::<T>::on_initialize(0.into());
|
||||
}
|
||||
verify {
|
||||
// make sure that all deferred offences were reported with Ok status.
|
||||
assert!(Offences::<T>::deferred_offences().is_empty());
|
||||
assert_eq!(
|
||||
System::<T>::event_count(), d * (0
|
||||
+ o // offenders slashed
|
||||
+ o * n // nominators slashed
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::mock::{new_test_ext, Test};
|
||||
use frame_support::assert_ok;
|
||||
|
||||
#[test]
|
||||
fn test_benchmarks() {
|
||||
new_test_ext().execute_with(|| {
|
||||
assert_ok!(test_benchmark_report_offence_im_online::<Test>());
|
||||
assert_ok!(test_benchmark_report_offence_grandpa::<Test>());
|
||||
assert_ok!(test_benchmark_report_offence_babe::<Test>());
|
||||
assert_ok!(test_benchmark_on_initialize::<Test>());
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user