pallet_collator_selection/
benchmarking.rs1#![cfg(feature = "runtime-benchmarks")]
19
20use super::*;
21
22#[allow(unused)]
23use crate::Pallet as CollatorSelection;
24use alloc::vec::Vec;
25use core::cmp;
26use cumulus_pallet_session_benchmarking as session_benchmarking;
27use frame_benchmarking::{account, v2::*, whitelisted_caller, BenchmarkError};
28use frame_support::traits::{Currency, EnsureOrigin, Get, ReservableCurrency};
29use frame_system::{pallet_prelude::BlockNumberFor, EventRecord, RawOrigin};
30use pallet_authorship::EventHandler;
31use pallet_session::{self as session, SessionManager};
32
33pub type BalanceOf<T> =
34 <<T as super::Config>::Currency as Currency<<T as frame_system::Config>::AccountId>>::Balance;
35
36const SEED: u32 = 0;
37
38fn assert_last_event<T: Config>(generic_event: <T as super::Config>::RuntimeEvent) {
39 let events = frame_system::Pallet::<T>::events();
40 let system_event: <T as frame_system::Config>::RuntimeEvent = generic_event.into();
41 let EventRecord { event, .. } = &events[events.len() - 1];
43 assert_eq!(event, &system_event);
44}
45
46fn create_funded_user<T: Config>(
47 string: &'static str,
48 n: u32,
49 balance_factor: u32,
50) -> T::AccountId {
51 let user = account(string, n, SEED);
52 let balance = <T as pallet::Config>::Currency::minimum_balance() * balance_factor.into();
53 let _ = <T as pallet::Config>::Currency::make_free_balance_be(&user, balance);
54 user
55}
56
57fn validator<T: Config + session_benchmarking::Config>(
58 c: u32,
59) -> (T::AccountId, <T as session::Config>::Keys, Vec<u8>) {
60 let validator = create_funded_user::<T>("candidate", c, 1000);
61 let (keys, proof) = T::generate_session_keys_and_proof(validator.clone());
62
63 (validator, keys, proof)
64}
65
66fn register_validators<T: Config + session_benchmarking::Config>(count: u32) -> Vec<T::AccountId> {
67 let validators = (0..count).map(|c| validator::<T>(c)).collect::<Vec<_>>();
68
69 for (who, keys, proof) in validators.clone() {
70 <session::Pallet<T>>::ensure_can_pay_key_deposit(&who).unwrap();
71 <session::Pallet<T>>::set_keys(RawOrigin::Signed(who).into(), keys, proof).unwrap();
72 }
73
74 validators.into_iter().map(|(who, _, _)| who).collect()
75}
76
77fn register_candidates<T: Config>(count: u32) {
78 let candidates = (0..count).map(|c| account("candidate", c, SEED)).collect::<Vec<_>>();
79 assert!(CandidacyBond::<T>::get() > 0u32.into(), "Bond cannot be zero!");
80
81 for who in candidates {
82 <T as pallet::Config>::Currency::make_free_balance_be(
83 &who,
84 CandidacyBond::<T>::get() * 3u32.into(),
85 );
86 <CollatorSelection<T>>::register_as_candidate(RawOrigin::Signed(who).into()).unwrap();
87 }
88}
89
90fn min_candidates<T: Config>() -> u32 {
91 let min_collators = T::MinEligibleCollators::get();
92 let invulnerable_length = Invulnerables::<T>::get().len();
93 min_collators.saturating_sub(invulnerable_length.try_into().unwrap())
94}
95
96fn min_invulnerables<T: Config>() -> u32 {
97 let min_collators = T::MinEligibleCollators::get();
98 let candidates_length = CandidateList::<T>::decode_len()
99 .unwrap_or_default()
100 .try_into()
101 .unwrap_or_default();
102 min_collators.saturating_sub(candidates_length)
103}
104
105#[benchmarks(where T: pallet_authorship::Config + session_benchmarking::Config)]
106mod benchmarks {
107 use super::*;
108
109 #[benchmark]
110 fn set_invulnerables(
111 b: Linear<1, { T::MaxInvulnerables::get() }>,
112 ) -> Result<(), BenchmarkError> {
113 let origin =
114 T::UpdateOrigin::try_successful_origin().map_err(|_| BenchmarkError::Weightless)?;
115
116 let new_invulnerables = register_validators::<T>(b);
117 let mut sorted_new_invulnerables = new_invulnerables.clone();
118 sorted_new_invulnerables.sort();
119
120 #[extrinsic_call]
121 _(origin as T::RuntimeOrigin, new_invulnerables.clone());
122
123 assert_last_event::<T>(
125 Event::NewInvulnerables { invulnerables: sorted_new_invulnerables }.into(),
126 );
127 Ok(())
128 }
129
130 #[benchmark]
131 fn add_invulnerable(
132 b: Linear<1, { T::MaxInvulnerables::get() - 1 }>,
133 c: Linear<1, { T::MaxCandidates::get() - 1 }>,
134 ) -> Result<(), BenchmarkError> {
135 let origin =
136 T::UpdateOrigin::try_successful_origin().map_err(|_| BenchmarkError::Weightless)?;
137
138 CandidacyBond::<T>::put(<T as pallet::Config>::Currency::minimum_balance());
140 DesiredCandidates::<T>::put(c);
141 let mut candidates = (0..c).map(|cc| validator::<T>(cc)).collect::<Vec<_>>();
143 let (new_invulnerable, new_invulnerable_keys, new_proof) = validator::<T>(b.max(c) + 1);
147 candidates.push((new_invulnerable.clone(), new_invulnerable_keys, new_proof));
148 for (who, keys, proof) in candidates.clone() {
150 <session::Pallet<T>>::ensure_can_pay_key_deposit(&who).unwrap();
151 <session::Pallet<T>>::set_keys(RawOrigin::Signed(who).into(), keys, proof).unwrap();
152 }
153 for (who, _, _) in candidates.iter() {
155 let deposit = CandidacyBond::<T>::get();
156 <T as pallet::Config>::Currency::make_free_balance_be(who, deposit * 1000_u32.into());
157 CandidateList::<T>::try_mutate(|list| {
158 list.try_push(CandidateInfo { who: who.clone(), deposit }).unwrap();
159 Ok::<(), BenchmarkError>(())
160 })
161 .unwrap();
162 <T as pallet::Config>::Currency::reserve(who, deposit)?;
163 LastAuthoredBlock::<T>::insert(
164 who.clone(),
165 frame_system::Pallet::<T>::block_number() + T::KickThreshold::get(),
166 );
167 }
168
169 let mut invulnerables = register_validators::<T>(b);
171 invulnerables.sort();
172 let invulnerables: frame_support::BoundedVec<_, T::MaxInvulnerables> =
173 frame_support::BoundedVec::try_from(invulnerables).unwrap();
174 Invulnerables::<T>::put(invulnerables);
175
176 #[extrinsic_call]
177 _(origin as T::RuntimeOrigin, new_invulnerable.clone());
178
179 assert_last_event::<T>(Event::InvulnerableAdded { account_id: new_invulnerable }.into());
180 Ok(())
181 }
182
183 #[benchmark]
184 fn remove_invulnerable(
185 b: Linear<{ min_invulnerables::<T>() + 1 }, { T::MaxInvulnerables::get() }>,
186 ) -> Result<(), BenchmarkError> {
187 let origin =
188 T::UpdateOrigin::try_successful_origin().map_err(|_| BenchmarkError::Weightless)?;
189 let mut invulnerables = register_validators::<T>(b);
190 invulnerables.sort();
191 let invulnerables: frame_support::BoundedVec<_, T::MaxInvulnerables> =
192 frame_support::BoundedVec::try_from(invulnerables).unwrap();
193 Invulnerables::<T>::put(invulnerables);
194 let to_remove = Invulnerables::<T>::get().first().unwrap().clone();
195
196 #[extrinsic_call]
197 _(origin as T::RuntimeOrigin, to_remove.clone());
198
199 assert_last_event::<T>(Event::InvulnerableRemoved { account_id: to_remove }.into());
200 Ok(())
201 }
202
203 #[benchmark]
204 fn set_desired_candidates() -> Result<(), BenchmarkError> {
205 let max: u32 = T::MaxCandidates::get();
206 let origin =
207 T::UpdateOrigin::try_successful_origin().map_err(|_| BenchmarkError::Weightless)?;
208
209 #[extrinsic_call]
210 _(origin as T::RuntimeOrigin, max);
211
212 assert_last_event::<T>(Event::NewDesiredCandidates { desired_candidates: max }.into());
213 Ok(())
214 }
215
216 #[benchmark]
217 fn set_candidacy_bond(
218 c: Linear<0, { T::MaxCandidates::get() }>,
219 k: Linear<0, { T::MaxCandidates::get() }>,
220 ) -> Result<(), BenchmarkError> {
221 let initial_bond_amount: BalanceOf<T> =
222 <T as pallet::Config>::Currency::minimum_balance() * 2u32.into();
223 CandidacyBond::<T>::put(initial_bond_amount);
224 register_validators::<T>(c);
225 register_candidates::<T>(c);
226 let kicked = cmp::min(k, c);
227 let origin =
228 T::UpdateOrigin::try_successful_origin().map_err(|_| BenchmarkError::Weightless)?;
229 let bond_amount = if k > 0 {
230 CandidateList::<T>::mutate(|candidates| {
231 for info in candidates.iter_mut().skip(kicked as usize) {
232 info.deposit = <T as pallet::Config>::Currency::minimum_balance() * 3u32.into();
233 }
234 });
235 <T as pallet::Config>::Currency::minimum_balance() * 3u32.into()
236 } else {
237 <T as pallet::Config>::Currency::minimum_balance()
238 };
239
240 #[extrinsic_call]
241 _(origin as T::RuntimeOrigin, bond_amount);
242
243 assert_last_event::<T>(Event::NewCandidacyBond { bond_amount }.into());
244 Ok(())
245 }
246
247 #[benchmark]
248 fn update_bond(
249 c: Linear<{ min_candidates::<T>() + 1 }, { T::MaxCandidates::get() }>,
250 ) -> Result<(), BenchmarkError> {
251 CandidacyBond::<T>::put(<T as pallet::Config>::Currency::minimum_balance());
252 DesiredCandidates::<T>::put(c);
253
254 register_validators::<T>(c);
255 register_candidates::<T>(c);
256
257 let caller = CandidateList::<T>::get()[0].who.clone();
258 v2::whitelist!(caller);
259
260 let bond_amount: BalanceOf<T> = <T as pallet::Config>::Currency::minimum_balance() +
261 <T as pallet::Config>::Currency::minimum_balance();
262
263 #[extrinsic_call]
264 _(RawOrigin::Signed(caller.clone()), bond_amount);
265
266 assert_last_event::<T>(
267 Event::CandidateBondUpdated { account_id: caller, deposit: bond_amount }.into(),
268 );
269 assert!(
270 CandidateList::<T>::get().iter().last().unwrap().deposit ==
271 <T as pallet::Config>::Currency::minimum_balance() * 2u32.into()
272 );
273 Ok(())
274 }
275
276 #[benchmark]
279 fn register_as_candidate(c: Linear<1, { T::MaxCandidates::get() - 1 }>) {
280 CandidacyBond::<T>::put(<T as pallet::Config>::Currency::minimum_balance());
281 DesiredCandidates::<T>::put(c + 1);
282
283 register_validators::<T>(c);
284 register_candidates::<T>(c);
285
286 let caller: T::AccountId = whitelisted_caller();
287 let bond: BalanceOf<T> = <T as pallet::Config>::Currency::minimum_balance() * 2u32.into();
288 <T as pallet::Config>::Currency::make_free_balance_be(&caller, bond);
289 let (keys, proof) = T::generate_session_keys_and_proof(caller.clone());
290
291 <session::Pallet<T>>::ensure_can_pay_key_deposit(&caller).unwrap();
292 <session::Pallet<T>>::set_keys(RawOrigin::Signed(caller.clone()).into(), keys, proof)
293 .unwrap();
294
295 #[extrinsic_call]
296 _(RawOrigin::Signed(caller.clone()));
297
298 assert_last_event::<T>(
299 Event::CandidateAdded { account_id: caller, deposit: bond / 2u32.into() }.into(),
300 );
301 }
302
303 #[benchmark]
304 fn take_candidate_slot(c: Linear<{ min_candidates::<T>() + 1 }, { T::MaxCandidates::get() }>) {
305 CandidacyBond::<T>::put(<T as pallet::Config>::Currency::minimum_balance());
306 DesiredCandidates::<T>::put(1);
307
308 register_validators::<T>(c);
309 register_candidates::<T>(c);
310
311 let caller: T::AccountId = whitelisted_caller();
312 let bond: BalanceOf<T> = <T as pallet::Config>::Currency::minimum_balance() * 10u32.into();
313 <T as pallet::Config>::Currency::make_free_balance_be(&caller, bond);
314
315 let (keys, proof) = T::generate_session_keys_and_proof(caller.clone());
316
317 <session::Pallet<T>>::ensure_can_pay_key_deposit(&caller).unwrap();
318 <session::Pallet<T>>::set_keys(RawOrigin::Signed(caller.clone()).into(), keys, proof)
319 .unwrap();
320
321 let target = CandidateList::<T>::get().iter().last().unwrap().who.clone();
322
323 #[extrinsic_call]
324 _(RawOrigin::Signed(caller.clone()), bond / 2u32.into(), target.clone());
325
326 assert_last_event::<T>(
327 Event::CandidateReplaced { old: target, new: caller, deposit: bond / 2u32.into() }
328 .into(),
329 );
330 }
331
332 #[benchmark]
334 fn leave_intent(c: Linear<{ min_candidates::<T>() + 1 }, { T::MaxCandidates::get() }>) {
335 CandidacyBond::<T>::put(<T as pallet::Config>::Currency::minimum_balance());
336 DesiredCandidates::<T>::put(c);
337
338 register_validators::<T>(c);
339 register_candidates::<T>(c);
340
341 let leaving = CandidateList::<T>::get().iter().last().unwrap().who.clone();
342 v2::whitelist!(leaving);
343
344 #[extrinsic_call]
345 _(RawOrigin::Signed(leaving.clone()));
346
347 assert_last_event::<T>(Event::CandidateRemoved { account_id: leaving }.into());
348 }
349
350 #[benchmark]
352 fn note_author() {
353 CandidacyBond::<T>::put(<T as pallet::Config>::Currency::minimum_balance());
354 <T as pallet::Config>::Currency::make_free_balance_be(
355 &<CollatorSelection<T>>::account_id(),
356 <T as pallet::Config>::Currency::minimum_balance() * 4u32.into(),
357 );
358 let author = account("author", 0, SEED);
359 let new_block: BlockNumberFor<T> = 10u32.into();
360
361 frame_system::Pallet::<T>::set_block_number(new_block);
362 assert!(<T as pallet::Config>::Currency::free_balance(&author) == 0u32.into());
363
364 #[block]
365 {
366 <CollatorSelection<T> as EventHandler<_, _>>::note_author(author.clone())
367 }
368
369 assert!(<T as pallet::Config>::Currency::free_balance(&author) > 0u32.into());
370 assert_eq!(frame_system::Pallet::<T>::block_number(), new_block);
371 }
372
373 #[benchmark]
375 fn new_session(
376 r: Linear<1, { T::MaxCandidates::get() }>,
377 c: Linear<1, { T::MaxCandidates::get() }>,
378 ) {
379 CandidacyBond::<T>::put(<T as pallet::Config>::Currency::minimum_balance());
380 DesiredCandidates::<T>::put(c);
381 frame_system::Pallet::<T>::set_block_number(0u32.into());
382
383 register_validators::<T>(c);
384 register_candidates::<T>(c);
385
386 let new_block: BlockNumberFor<T> = T::KickThreshold::get();
387 let zero_block: BlockNumberFor<T> = 0u32.into();
388 let candidates: Vec<T::AccountId> = CandidateList::<T>::get()
389 .iter()
390 .map(|candidate_info| candidate_info.who.clone())
391 .collect();
392
393 let non_removals = c.saturating_sub(r);
394
395 for i in 0..c {
396 LastAuthoredBlock::<T>::insert(candidates[i as usize].clone(), zero_block);
397 }
398
399 if non_removals > 0 {
400 for i in 0..non_removals {
401 LastAuthoredBlock::<T>::insert(candidates[i as usize].clone(), new_block);
402 }
403 } else {
404 for i in 0..c {
405 LastAuthoredBlock::<T>::insert(candidates[i as usize].clone(), new_block);
406 }
407 }
408
409 let min_candidates = min_candidates::<T>();
410 let pre_length = CandidateList::<T>::decode_len().unwrap_or_default();
411
412 frame_system::Pallet::<T>::set_block_number(new_block);
413
414 let current_length: u32 = CandidateList::<T>::decode_len()
415 .unwrap_or_default()
416 .try_into()
417 .unwrap_or_default();
418 assert!(c == current_length);
419 #[block]
420 {
421 <CollatorSelection<T> as SessionManager<_>>::new_session(0);
422 }
423
424 if c > r && non_removals >= min_candidates {
425 assert!(CandidateList::<T>::decode_len().unwrap_or_default() < pre_length);
429 } else if c > r && non_removals < min_candidates {
430 let current_length: u32 = CandidateList::<T>::decode_len()
434 .unwrap_or_default()
435 .try_into()
436 .unwrap_or_default();
437 assert!(min_candidates == current_length);
438 } else {
439 assert!(CandidateList::<T>::decode_len().unwrap_or_default() == pre_length);
442 }
443 }
444
445 impl_benchmark_test_suite!(CollatorSelection, crate::mock::new_test_ext(), crate::mock::Test);
446}