Skip to content
This repository has been archived by the owner on Nov 15, 2023. It is now read-only.

pallet-offences-benchmarking: Box events in verify #13151

Merged
merged 3 commits into from
Jan 16, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions frame/offences/benchmarking/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ pallet-staking = { version = "4.0.0-dev", default-features = false, path = "../.
sp-runtime = { version = "7.0.0", default-features = false, path = "../../../primitives/runtime" }
sp-staking = { version = "4.0.0-dev", default-features = false, path = "../../../primitives/staking" }
sp-std = { version = "5.0.0", default-features = false, path = "../../../primitives/std" }
log = { version = "0.4.17", default-features = false }

[dev-dependencies]
pallet-staking-reward-curve = { version = "4.0.0-dev", path = "../../staking/reward-curve" }
Expand All @@ -55,6 +56,7 @@ std = [
"sp-runtime/std",
"sp-staking/std",
"sp-std/std",
"log/std",
]

runtime-benchmarks = [
Expand Down
189 changes: 101 additions & 88 deletions frame/offences/benchmarking/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -28,8 +28,10 @@ use frame_benchmarking::{account, benchmarks};
use frame_support::traits::{Currency, Get, ValidatorSet, ValidatorSetWithIdentification};
use frame_system::{Config as SystemConfig, Pallet as System, RawOrigin};

#[cfg(test)]
use sp_runtime::traits::UniqueSaturatedInto;
use sp_runtime::{
traits::{Convert, Saturating, StaticLookup, UniqueSaturatedInto},
traits::{Convert, Saturating, StaticLookup},
Perbill,
};
use sp_staking::offence::{Offence, ReportOffence};
Expand All @@ -43,9 +45,11 @@ use pallet_session::{
historical::{Config as HistoricalConfig, IdentificationTuple},
Config as SessionConfig, SessionManager,
};
#[cfg(test)]
use pallet_staking::Event as StakingEvent;
use pallet_staking::{
Config as StakingConfig, Event as StakingEvent, Exposure, IndividualExposure,
Pallet as Staking, RewardDestination, ValidatorPrefs,
Config as StakingConfig, Exposure, IndividualExposure, Pallet as Staking, RewardDestination,
ValidatorPrefs,
};

const SEED: u32 = 0;
Expand Down Expand Up @@ -89,7 +93,9 @@ type BalanceOf<T> =

struct Offender<T: Config> {
pub controller: T::AccountId,
#[allow(dead_code)]
pub stash: T::AccountId,
#[allow(dead_code)]
pub nominator_stashes: Vec<T::AccountId>,
}

Expand Down Expand Up @@ -217,55 +223,63 @@ fn make_offenders_im_online<T: Config>(
}

#[cfg(test)]
fn check_events<T: Config, I: Iterator<Item = <T as SystemConfig>::RuntimeEvent>>(expected: I) {
fn check_events<
T: Config,
I: Iterator<Item = Item>,
Item: sp_std::borrow::Borrow<<T as SystemConfig>::RuntimeEvent> + sp_std::fmt::Debug,
>(
expected: I,
) {
let events = System::<T>::events()
.into_iter()
.map(|frame_system::EventRecord { event, .. }| event)
.collect::<Vec<_>>();
let expected = expected.collect::<Vec<_>>();

fn pretty<D: std::fmt::Debug>(header: &str, ev: &[D], offset: usize) {
println!("{}", header);
fn pretty<D: sp_std::fmt::Debug>(header: &str, ev: &[D], offset: usize) {
log::info!("{}", header);
for (idx, ev) in ev.iter().enumerate() {
println!("\t[{:04}] {:?}", idx + offset, ev);
log::info!("\t[{:04}] {:?}", idx + offset, ev);
}
}
fn print_events<D: std::fmt::Debug>(idx: usize, events: &[D], expected: &[D]) {
fn print_events<D: sp_std::fmt::Debug, E: sp_std::fmt::Debug>(
idx: usize,
events: &[D],
expected: &[E],
) {
let window = 10;
let start = idx.saturating_sub(window / 2);
let end_got = (idx + window / 2).min(events.len());
pretty("Got(window):", &events[start..end_got], start);
let end_expected = (idx + window / 2).min(expected.len());
pretty("Expected(window):", &expected[start..end_expected], start);
println!("---------------");
log::info!("---------------");
let start_got = events.len().saturating_sub(window);
pretty("Got(end):", &events[start_got..], start_got);
let start_expected = expected.len().saturating_sub(window);
pretty("Expected(end):", &expected[start_expected..], start_expected);
}
let events_copy = events.clone();
let expected_copy = expected.clone();

for (idx, (a, b)) in events.into_iter().zip(expected).enumerate() {
if a != b {
print_events(idx, &events_copy, &expected_copy);
println!("Mismatch at: {}", idx);
println!(" Got: {:?}", b);
println!("Expected: {:?}", a);
if events_copy.len() != expected_copy.len() {
println!(

for (idx, (a, b)) in events.iter().zip(expected.iter()).enumerate() {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could also have been done with the new StorageStreamIter, or?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes good idea!

if a != sp_std::borrow::Borrow::borrow(b) {
print_events(idx, &events, &expected);
log::info!("Mismatch at: {}", idx);
log::info!(" Got: {:?}", b);
log::info!("Expected: {:?}", a);
if events.len() != expected.len() {
log::info!(
"Mismatching lengths. Got: {}, Expected: {}",
events_copy.len(),
expected_copy.len()
events.len(),
expected.len()
)
}
panic!("Mismatching events.");
}
}

if events_copy.len() != expected_copy.len() {
print_events(0, &events_copy, &expected_copy);
panic!("Mismatching lengths. Got: {}, Expected: {}", events_copy.len(), expected_copy.len())
if events.len() != expected.len() {
print_events(0, &events, &expected);
panic!("Mismatching lengths. Got: {}, Expected: {}", events.len(), expected.len(),)
}
}

Expand Down Expand Up @@ -304,79 +318,78 @@ benchmarks! {
);
}
verify {
let bond_amount: u32 = UniqueSaturatedInto::<u32>::unique_saturated_into(bond_amount::<T>());
let slash_amount = slash_fraction * bond_amount;
let reward_amount = slash_amount.saturating_mul(1 + n) / 2;
let reward = reward_amount / r;
let slash_report = |id| core::iter::once(
<T as StakingConfig>::RuntimeEvent::from(StakingEvent::<T>::SlashReported{ validator: id, fraction: slash_fraction, slash_era: 0})
);
let slash = |id| core::iter::once(
<T as StakingConfig>::RuntimeEvent::from(StakingEvent::<T>::Slashed{ staker: id, amount: BalanceOf::<T>::from(slash_amount) })
);
let balance_slash = |id| core::iter::once(
<T as BalancesConfig>::RuntimeEvent::from(pallet_balances::Event::<T>::Slashed{ who: id, amount: slash_amount.into() })
);
let chill = |id| core::iter::once(
<T as StakingConfig>::RuntimeEvent::from(StakingEvent::<T>::Chilled{ stash: id })
);
let balance_deposit = |id, amount: u32|
#[cfg(test)]
{
let bond_amount: u32 = UniqueSaturatedInto::<u32>::unique_saturated_into(bond_amount::<T>());
let slash_amount = slash_fraction * bond_amount;
let reward_amount = slash_amount.saturating_mul(1 + n) / 2;
let reward = reward_amount / r;
let slash_report = |id| core::iter::once(
<T as StakingConfig>::RuntimeEvent::from(StakingEvent::<T>::SlashReported{ validator: id, fraction: slash_fraction, slash_era: 0})
);
let slash = |id| core::iter::once(
<T as StakingConfig>::RuntimeEvent::from(StakingEvent::<T>::Slashed{ staker: id, amount: BalanceOf::<T>::from(slash_amount) })
);
let balance_slash = |id| core::iter::once(
<T as BalancesConfig>::RuntimeEvent::from(pallet_balances::Event::<T>::Slashed{ who: id, amount: slash_amount.into() })
);
let chill = |id| core::iter::once(
<T as StakingConfig>::RuntimeEvent::from(StakingEvent::<T>::Chilled{ stash: id })
);
let balance_deposit = |id, amount: u32|
<T as BalancesConfig>::RuntimeEvent::from(pallet_balances::Event::<T>::Deposit{ who: id, amount: amount.into() });
let mut first = true;
let slash_events = raw_offenders.into_iter()
.flat_map(|offender| {
let nom_slashes = offender.nominator_stashes.into_iter().flat_map(|nom| {
balance_slash(nom.clone()).map(Into::into)
.chain(slash(nom).map(Into::into))
});

let mut events = chill(offender.stash.clone()).map(Into::into)
.chain(slash_report(offender.stash.clone()).map(Into::into))
.chain(balance_slash(offender.stash.clone()).map(Into::into))
.chain(slash(offender.stash).map(Into::into))
.chain(nom_slashes)
.collect::<Vec<_>>();

// the first deposit creates endowed events, see `endowed_reward_events`
if first {
first = false;
let mut reward_events = reporters.clone().into_iter()
.flat_map(|reporter| vec![
balance_deposit(reporter.clone(), reward).into(),
frame_system::Event::<T>::NewAccount { account: reporter.clone() }.into(),
<T as BalancesConfig>::RuntimeEvent::from(
pallet_balances::Event::<T>::Endowed{account: reporter, free_balance: reward.into()}
).into(),
])
let mut first = true;

// We need to box all events to prevent running into too big allocations in wasm.
// The event in FRAME is represented as an enum and the size of the enum depends on the biggest variant.
// So, instead of requiring `size_of<Event>() * expected_events` we only need to
// allocate `size_of<Box<Event>>() * expected_events`.
let slash_events = raw_offenders.into_iter()
.flat_map(|offender| {
let nom_slashes = offender.nominator_stashes.into_iter().flat_map(|nom| {
balance_slash(nom.clone()).map(Into::into).chain(slash(nom).map(Into::into)).map(Box::new)
});

let events = chill(offender.stash.clone()).map(Into::into).map(Box::new)
.chain(slash_report(offender.stash.clone()).map(Into::into).map(Box::new))
.chain(balance_slash(offender.stash.clone()).map(Into::into).map(Box::new))
.chain(slash(offender.stash).map(Into::into).map(Box::new))
.chain(nom_slashes)
.collect::<Vec<_>>();
events.append(&mut reward_events);
events.into_iter()
} else {
let mut reward_events = reporters.clone().into_iter()
.map(|reporter| balance_deposit(reporter, reward).into())
.collect::<Vec<_>>();
events.append(&mut reward_events);
events.into_iter()
}
})
.collect::<Vec<_>>();


// the first deposit creates endowed events, see `endowed_reward_events`
if first {
first = false;
let reward_events = reporters.iter()
.flat_map(|reporter| vec![
Box::new(balance_deposit(reporter.clone(), reward).into()),
Box::new(frame_system::Event::<T>::NewAccount { account: reporter.clone() }.into()),
Box::new(<T as BalancesConfig>::RuntimeEvent::from(
pallet_balances::Event::<T>::Endowed{ account: reporter.clone(), free_balance: reward.into() }
).into()),
])
.collect::<Vec<_>>();
events.into_iter().chain(reward_events)
} else {
let reward_events = reporters.iter()
.map(|reporter| Box::new(balance_deposit(reporter.clone(), reward).into()))
.collect::<Vec<_>>();
events.into_iter().chain(reward_events)
}
});

#[cfg(test)]
{
// In case of error it's useful to see the inputs
println!("Inputs: r: {}, o: {}, n: {}", r, o, n);
log::info!("Inputs: r: {}, o: {}, n: {}", r, o, n);
// make sure that all slashes have been applied
check_events::<T, _>(
std::iter::empty()
.chain(slash_events.into_iter().map(Into::into))
.chain(std::iter::once(<T as OffencesConfig>::RuntimeEvent::from(
check_events::<T, _, _>(
sp_std::iter::empty()
.chain(slash_events)
.chain(sp_std::iter::once(Box::new(<T as OffencesConfig>::RuntimeEvent::from(
pallet_offences::Event::Offence{
kind: UnresponsivenessOffence::<T>::ID,
timeslot: 0_u32.to_le_bytes().to_vec(),
}
).into()))
).into())))
);
}
}
Expand Down