referrerpolicy=no-referrer-when-downgrade

pallet_offences_benchmarking/
inner.rs

1// This file is part of Substrate.
2
3// Copyright (C) Parity Technologies (UK) Ltd.
4// SPDX-License-Identifier: Apache-2.0
5
6// Licensed under the Apache License, Version 2.0 (the "License");
7// you may not use this file except in compliance with the License.
8// You may obtain a copy of the License at
9//
10// 	http://www.apache.org/licenses/LICENSE-2.0
11//
12// Unless required by applicable law or agreed to in writing, software
13// distributed under the License is distributed on an "AS IS" BASIS,
14// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15// See the License for the specific language governing permissions and
16// limitations under the License.
17
18//! Offences pallet benchmarking.
19
20use alloc::{vec, vec::Vec};
21use codec::Decode;
22use frame_benchmarking::v2::*;
23use frame_support::traits::Get;
24use frame_system::{Config as SystemConfig, Pallet as System, RawOrigin};
25use pallet_babe::EquivocationOffence as BabeEquivocationOffence;
26use pallet_balances::Config as BalancesConfig;
27use pallet_grandpa::{
28	EquivocationOffence as GrandpaEquivocationOffence, TimeSlot as GrandpaTimeSlot,
29};
30use pallet_offences::{Config as OffencesConfig, Pallet as Offences};
31use pallet_session::{
32	historical::{Config as HistoricalConfig, IdentificationTuple},
33	Config as SessionConfig, Pallet as Session,
34};
35use pallet_staking::{
36	Config as StakingConfig, Exposure, IndividualExposure, MaxNominationsOf, Pallet as Staking,
37	RewardDestination, ValidatorPrefs,
38};
39use sp_runtime::{
40	traits::{Convert, Saturating, StaticLookup},
41	Perbill,
42};
43use sp_staking::offence::ReportOffence;
44
45const SEED: u32 = 0;
46
47const MAX_NOMINATORS: u32 = 100;
48
49pub struct Pallet<T: Config>(Offences<T>);
50
51pub trait Config:
52	SessionConfig<ValidatorId = <Self as frame_system::Config>::AccountId>
53	+ StakingConfig
54	+ OffencesConfig
55	+ HistoricalConfig
56	+ BalancesConfig
57	+ IdTupleConvert<Self>
58{
59}
60
61/// A helper trait to make sure we can convert `IdentificationTuple` coming from historical
62/// and the one required by offences.
63pub trait IdTupleConvert<T: HistoricalConfig + OffencesConfig> {
64	/// Convert identification tuple from `historical` trait to the one expected by `offences`.
65	fn convert(id: IdentificationTuple<T>) -> <T as OffencesConfig>::IdentificationTuple;
66}
67
68impl<T: HistoricalConfig + OffencesConfig> IdTupleConvert<T> for T
69where
70	<T as OffencesConfig>::IdentificationTuple: From<IdentificationTuple<T>>,
71{
72	fn convert(id: IdentificationTuple<T>) -> <T as OffencesConfig>::IdentificationTuple {
73		id.into()
74	}
75}
76
77type LookupSourceOf<T> = <<T as SystemConfig>::Lookup as StaticLookup>::Source;
78type BalanceOf<T> = <T as StakingConfig>::CurrencyBalance;
79
80struct Offender<T: Config> {
81	pub controller: T::AccountId,
82	#[allow(dead_code)]
83	pub stash: T::AccountId,
84	#[allow(dead_code)]
85	pub nominator_stashes: Vec<T::AccountId>,
86}
87
88fn bond_amount<T: Config>() -> BalanceOf<T> {
89	pallet_staking::asset::existential_deposit::<T>().saturating_mul(10_000u32.into())
90}
91
92fn create_offender<T: Config>(n: u32, nominators: u32) -> Result<Offender<T>, &'static str> {
93	let stash: T::AccountId = account("stash", n, SEED);
94	let stash_lookup: LookupSourceOf<T> = T::Lookup::unlookup(stash.clone());
95	let reward_destination = RewardDestination::Staked;
96	let amount = bond_amount::<T>();
97	// add twice as much balance to prevent the account from being killed.
98	let free_amount = amount.saturating_mul(2u32.into());
99	pallet_staking::asset::set_stakeable_balance::<T>(&stash, free_amount);
100	Staking::<T>::bond(
101		RawOrigin::Signed(stash.clone()).into(),
102		amount,
103		reward_destination.clone(),
104	)?;
105
106	let validator_prefs =
107		ValidatorPrefs { commission: Perbill::from_percent(50), ..Default::default() };
108	Staking::<T>::validate(RawOrigin::Signed(stash.clone()).into(), validator_prefs)?;
109
110	// set some fake keys for the validators.
111	let keys =
112		<T as SessionConfig>::Keys::decode(&mut sp_runtime::traits::TrailingZeroInput::zeroes())
113			.unwrap();
114	let proof: Vec<u8> = vec![0, 1, 2, 3];
115	Session::<T>::ensure_can_pay_key_deposit(&stash)?;
116	Session::<T>::set_keys(RawOrigin::Signed(stash.clone()).into(), keys, proof)?;
117
118	let mut individual_exposures = vec![];
119	let mut nominator_stashes = vec![];
120	// Create n nominators
121	for i in 0..nominators {
122		let nominator_stash: T::AccountId =
123			account("nominator stash", n * MAX_NOMINATORS + i, SEED);
124		pallet_staking::asset::set_stakeable_balance::<T>(&nominator_stash, free_amount);
125
126		Staking::<T>::bond(
127			RawOrigin::Signed(nominator_stash.clone()).into(),
128			amount,
129			reward_destination.clone(),
130		)?;
131
132		let selected_validators: Vec<LookupSourceOf<T>> = vec![stash_lookup.clone()];
133		Staking::<T>::nominate(
134			RawOrigin::Signed(nominator_stash.clone()).into(),
135			selected_validators,
136		)?;
137
138		individual_exposures
139			.push(IndividualExposure { who: nominator_stash.clone(), value: amount });
140		nominator_stashes.push(nominator_stash.clone());
141	}
142
143	let exposure = Exposure { total: amount * n.into(), own: amount, others: individual_exposures };
144	let current_era = 0u32;
145	Staking::<T>::add_era_stakers(current_era, stash.clone(), exposure);
146
147	Ok(Offender { controller: stash.clone(), stash, nominator_stashes })
148}
149
150fn make_offenders<T: Config>(
151	num_offenders: u32,
152	num_nominators: u32,
153) -> Result<Vec<IdentificationTuple<T>>, &'static str> {
154	let mut offenders = vec![];
155	for i in 0..num_offenders {
156		let offender = create_offender::<T>(i + 1, num_nominators)?;
157		// add them to the session validators -- this is needed since `FullIdentificationOf` usually
158		// checks this.
159		pallet_session::Validators::<T>::mutate(|v| v.push(offender.controller.clone()));
160		offenders.push(offender);
161	}
162
163	let id_tuples = offenders
164		.iter()
165		.map(|offender| {
166			<T as SessionConfig>::ValidatorIdOf::convert(offender.controller.clone())
167				.expect("failed to get validator id from account id")
168		})
169		.map(|validator_id| {
170			<T as HistoricalConfig>::FullIdentificationOf::convert(validator_id.clone())
171				.map(|full_id| (validator_id, full_id))
172				.unwrap()
173		})
174		.collect::<Vec<IdentificationTuple<T>>>();
175
176	if pallet_staking::ActiveEra::<T>::get().is_none() {
177		pallet_staking::ActiveEra::<T>::put(pallet_staking::ActiveEraInfo {
178			index: 0,
179			start: Some(0),
180		});
181	}
182
183	Ok(id_tuples)
184}
185
186#[cfg(test)]
187fn assert_all_slashes_applied<T>(offender_count: usize)
188where
189	T: Config,
190	<T as frame_system::Config>::RuntimeEvent: TryInto<pallet_staking::Event<T>>,
191	<T as frame_system::Config>::RuntimeEvent: TryInto<pallet_balances::Event<T>>,
192	<T as frame_system::Config>::RuntimeEvent: TryInto<pallet_offences::Event>,
193	<T as frame_system::Config>::RuntimeEvent: TryInto<frame_system::Event<T>>,
194{
195	// make sure that all slashes have been applied and TotalIssuance adjusted(BurnedDebt).
196	// deposit to reporter + reporter account endowed.
197	assert_eq!(System::<T>::read_events_for_pallet::<pallet_balances::Event<T>>().len(), 3);
198	// (n nominators + one validator) * slashed + Slash Reported + Slash Computed
199	assert_eq!(
200		System::<T>::read_events_for_pallet::<pallet_staking::Event<T>>().len(),
201		1 * (offender_count + 1) as usize + 1
202	);
203	// offence
204	assert_eq!(System::<T>::read_events_for_pallet::<pallet_offences::Event>().len(), 1);
205	// reporter new account
206	assert_eq!(System::<T>::read_events_for_pallet::<frame_system::Event<T>>().len(), 1);
207}
208
209#[benchmarks(
210	where
211		<T as frame_system::Config>::RuntimeEvent: TryInto<pallet_staking::Event<T>>,
212		<T as frame_system::Config>::RuntimeEvent: TryInto<pallet_balances::Event<T>>,
213		<T as frame_system::Config>::RuntimeEvent: TryInto<pallet_offences::Event>,
214		<T as frame_system::Config>::RuntimeEvent: TryInto<frame_system::Event<T>>,
215)]
216mod benchmarks {
217	use super::*;
218
219	#[benchmark]
220	pub fn report_offence_grandpa(
221		n: Linear<0, { MAX_NOMINATORS.min(MaxNominationsOf::<T>::get()) }>,
222	) -> Result<(), BenchmarkError> {
223		// for grandpa equivocation reports the number of reporters
224		// and offenders is always 1
225		let reporters = vec![account("reporter", 1, SEED)];
226
227		// make sure reporters actually get rewarded
228		Staking::<T>::set_slash_reward_fraction(Perbill::one());
229
230		let mut offenders = make_offenders::<T>(1, n)?;
231		let validator_set_count = Session::<T>::validators().len() as u32;
232
233		let offence = GrandpaEquivocationOffence {
234			time_slot: GrandpaTimeSlot { set_id: 0, round: 0 },
235			session_index: 0,
236			validator_set_count,
237			offender: T::convert(offenders.pop().unwrap()),
238		};
239		assert_eq!(System::<T>::event_count(), 0);
240
241		#[block]
242		{
243			let _ = Offences::<T>::report_offence(reporters, offence);
244		}
245
246		#[cfg(test)]
247		{
248			assert_all_slashes_applied::<T>(n as usize);
249		}
250
251		Ok(())
252	}
253
254	#[benchmark]
255	fn report_offence_babe(
256		n: Linear<0, { MAX_NOMINATORS.min(MaxNominationsOf::<T>::get()) }>,
257	) -> Result<(), BenchmarkError> {
258		// for babe equivocation reports the number of reporters
259		// and offenders is always 1
260		let reporters = vec![account("reporter", 1, SEED)];
261
262		// make sure reporters actually get rewarded
263		Staking::<T>::set_slash_reward_fraction(Perbill::one());
264
265		let mut offenders = make_offenders::<T>(1, n)?;
266		let validator_set_count = Session::<T>::validators().len() as u32;
267
268		let offence = BabeEquivocationOffence {
269			slot: 0u64.into(),
270			session_index: 0,
271			validator_set_count,
272			offender: T::convert(offenders.pop().unwrap()),
273		};
274		assert_eq!(System::<T>::event_count(), 0);
275
276		#[block]
277		{
278			let _ = Offences::<T>::report_offence(reporters, offence);
279		}
280		#[cfg(test)]
281		{
282			assert_all_slashes_applied::<T>(n as usize);
283		}
284
285		Ok(())
286	}
287
288	impl_benchmark_test_suite!(Pallet, crate::mock::new_test_ext(), crate::mock::Test);
289}