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 codec::Decode;
26use core::cmp;
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 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 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 keys<T: Config + session::Config>(c: u32) -> <T as session::Config>::Keys {
58 use rand::{RngCore, SeedableRng};
59
60 let keys = {
61 let mut keys = [0u8; 128];
62
63 if c > 0 {
64 let mut rng = rand::rngs::StdRng::seed_from_u64(c as u64);
65 rng.fill_bytes(&mut keys);
66 }
67
68 keys
69 };
70
71 Decode::decode(&mut &keys[..]).unwrap()
72}
73
74fn validator<T: Config + session::Config>(c: u32) -> (T::AccountId, <T as session::Config>::Keys) {
75 (create_funded_user::<T>("candidate", c, 1000), keys::<T>(c))
76}
77
78fn register_validators<T: Config + session::Config>(count: u32) -> Vec<T::AccountId> {
79 let validators = (0..count).map(|c| validator::<T>(c)).collect::<Vec<_>>();
80
81 for (who, keys) in validators.clone() {
82 <session::Pallet<T>>::ensure_can_pay_key_deposit(&who).unwrap();
83 <session::Pallet<T>>::set_keys(RawOrigin::Signed(who).into(), keys, Vec::new()).unwrap();
84 }
85
86 validators.into_iter().map(|(who, _)| who).collect()
87}
88
89fn register_candidates<T: Config>(count: u32) {
90 let candidates = (0..count).map(|c| account("candidate", c, SEED)).collect::<Vec<_>>();
91 assert!(CandidacyBond::<T>::get() > 0u32.into(), "Bond cannot be zero!");
92
93 for who in candidates {
94 <T as pallet::Config>::Currency::make_free_balance_be(
95 &who,
96 CandidacyBond::<T>::get() * 3u32.into(),
97 );
98 <CollatorSelection<T>>::register_as_candidate(RawOrigin::Signed(who).into()).unwrap();
99 }
100}
101
102fn min_candidates<T: Config>() -> u32 {
103 let min_collators = T::MinEligibleCollators::get();
104 let invulnerable_length = Invulnerables::<T>::get().len();
105 min_collators.saturating_sub(invulnerable_length.try_into().unwrap())
106}
107
108fn min_invulnerables<T: Config>() -> u32 {
109 let min_collators = T::MinEligibleCollators::get();
110 let candidates_length = CandidateList::<T>::decode_len()
111 .unwrap_or_default()
112 .try_into()
113 .unwrap_or_default();
114 min_collators.saturating_sub(candidates_length)
115}
116
117#[benchmarks(where T: pallet_authorship::Config + session::Config)]
118mod benchmarks {
119 use super::*;
120
121 #[benchmark]
122 fn set_invulnerables(
123 b: Linear<1, { T::MaxInvulnerables::get() }>,
124 ) -> Result<(), BenchmarkError> {
125 let origin =
126 T::UpdateOrigin::try_successful_origin().map_err(|_| BenchmarkError::Weightless)?;
127
128 let new_invulnerables = register_validators::<T>(b);
129 let mut sorted_new_invulnerables = new_invulnerables.clone();
130 sorted_new_invulnerables.sort();
131
132 #[extrinsic_call]
133 _(origin as T::RuntimeOrigin, new_invulnerables.clone());
134
135 assert_last_event::<T>(
137 Event::NewInvulnerables { invulnerables: sorted_new_invulnerables }.into(),
138 );
139 Ok(())
140 }
141
142 #[benchmark]
143 fn add_invulnerable(
144 b: Linear<1, { T::MaxInvulnerables::get() - 1 }>,
145 c: Linear<1, { T::MaxCandidates::get() - 1 }>,
146 ) -> Result<(), BenchmarkError> {
147 let origin =
148 T::UpdateOrigin::try_successful_origin().map_err(|_| BenchmarkError::Weightless)?;
149
150 CandidacyBond::<T>::put(<T as pallet::Config>::Currency::minimum_balance());
152 DesiredCandidates::<T>::put(c);
153 let mut candidates = (0..c).map(|cc| validator::<T>(cc)).collect::<Vec<_>>();
155 let (new_invulnerable, new_invulnerable_keys) = validator::<T>(b.max(c) + 1);
159 candidates.push((new_invulnerable.clone(), new_invulnerable_keys));
160 for (who, keys) in candidates.clone() {
162 <session::Pallet<T>>::ensure_can_pay_key_deposit(&who).unwrap();
163 <session::Pallet<T>>::set_keys(RawOrigin::Signed(who).into(), keys, Vec::new())
164 .unwrap();
165 }
166 for (who, _) in candidates.iter() {
168 let deposit = CandidacyBond::<T>::get();
169 <T as pallet::Config>::Currency::make_free_balance_be(who, deposit * 1000_u32.into());
170 CandidateList::<T>::try_mutate(|list| {
171 list.try_push(CandidateInfo { who: who.clone(), deposit }).unwrap();
172 Ok::<(), BenchmarkError>(())
173 })
174 .unwrap();
175 <T as pallet::Config>::Currency::reserve(who, deposit)?;
176 LastAuthoredBlock::<T>::insert(
177 who.clone(),
178 frame_system::Pallet::<T>::block_number() + T::KickThreshold::get(),
179 );
180 }
181
182 let mut invulnerables = register_validators::<T>(b);
184 invulnerables.sort();
185 let invulnerables: frame_support::BoundedVec<_, T::MaxInvulnerables> =
186 frame_support::BoundedVec::try_from(invulnerables).unwrap();
187 Invulnerables::<T>::put(invulnerables);
188
189 #[extrinsic_call]
190 _(origin as T::RuntimeOrigin, new_invulnerable.clone());
191
192 assert_last_event::<T>(Event::InvulnerableAdded { account_id: new_invulnerable }.into());
193 Ok(())
194 }
195
196 #[benchmark]
197 fn remove_invulnerable(
198 b: Linear<{ min_invulnerables::<T>() + 1 }, { T::MaxInvulnerables::get() }>,
199 ) -> Result<(), BenchmarkError> {
200 let origin =
201 T::UpdateOrigin::try_successful_origin().map_err(|_| BenchmarkError::Weightless)?;
202 let mut invulnerables = register_validators::<T>(b);
203 invulnerables.sort();
204 let invulnerables: frame_support::BoundedVec<_, T::MaxInvulnerables> =
205 frame_support::BoundedVec::try_from(invulnerables).unwrap();
206 Invulnerables::<T>::put(invulnerables);
207 let to_remove = Invulnerables::<T>::get().first().unwrap().clone();
208
209 #[extrinsic_call]
210 _(origin as T::RuntimeOrigin, to_remove.clone());
211
212 assert_last_event::<T>(Event::InvulnerableRemoved { account_id: to_remove }.into());
213 Ok(())
214 }
215
216 #[benchmark]
217 fn set_desired_candidates() -> Result<(), BenchmarkError> {
218 let max: u32 = T::MaxCandidates::get();
219 let origin =
220 T::UpdateOrigin::try_successful_origin().map_err(|_| BenchmarkError::Weightless)?;
221
222 #[extrinsic_call]
223 _(origin as T::RuntimeOrigin, max);
224
225 assert_last_event::<T>(Event::NewDesiredCandidates { desired_candidates: max }.into());
226 Ok(())
227 }
228
229 #[benchmark]
230 fn set_candidacy_bond(
231 c: Linear<0, { T::MaxCandidates::get() }>,
232 k: Linear<0, { T::MaxCandidates::get() }>,
233 ) -> Result<(), BenchmarkError> {
234 let initial_bond_amount: BalanceOf<T> =
235 <T as pallet::Config>::Currency::minimum_balance() * 2u32.into();
236 CandidacyBond::<T>::put(initial_bond_amount);
237 register_validators::<T>(c);
238 register_candidates::<T>(c);
239 let kicked = cmp::min(k, c);
240 let origin =
241 T::UpdateOrigin::try_successful_origin().map_err(|_| BenchmarkError::Weightless)?;
242 let bond_amount = if k > 0 {
243 CandidateList::<T>::mutate(|candidates| {
244 for info in candidates.iter_mut().skip(kicked as usize) {
245 info.deposit = <T as pallet::Config>::Currency::minimum_balance() * 3u32.into();
246 }
247 });
248 <T as pallet::Config>::Currency::minimum_balance() * 3u32.into()
249 } else {
250 <T as pallet::Config>::Currency::minimum_balance()
251 };
252
253 #[extrinsic_call]
254 _(origin as T::RuntimeOrigin, bond_amount);
255
256 assert_last_event::<T>(Event::NewCandidacyBond { bond_amount }.into());
257 Ok(())
258 }
259
260 #[benchmark]
261 fn update_bond(
262 c: Linear<{ min_candidates::<T>() + 1 }, { T::MaxCandidates::get() }>,
263 ) -> Result<(), BenchmarkError> {
264 CandidacyBond::<T>::put(<T as pallet::Config>::Currency::minimum_balance());
265 DesiredCandidates::<T>::put(c);
266
267 register_validators::<T>(c);
268 register_candidates::<T>(c);
269
270 let caller = CandidateList::<T>::get()[0].who.clone();
271 v2::whitelist!(caller);
272
273 let bond_amount: BalanceOf<T> = <T as pallet::Config>::Currency::minimum_balance() +
274 <T as pallet::Config>::Currency::minimum_balance();
275
276 #[extrinsic_call]
277 _(RawOrigin::Signed(caller.clone()), bond_amount);
278
279 assert_last_event::<T>(
280 Event::CandidateBondUpdated { account_id: caller, deposit: bond_amount }.into(),
281 );
282 assert!(
283 CandidateList::<T>::get().iter().last().unwrap().deposit ==
284 <T as pallet::Config>::Currency::minimum_balance() * 2u32.into()
285 );
286 Ok(())
287 }
288
289 #[benchmark]
292 fn register_as_candidate(c: Linear<1, { T::MaxCandidates::get() - 1 }>) {
293 CandidacyBond::<T>::put(<T as pallet::Config>::Currency::minimum_balance());
294 DesiredCandidates::<T>::put(c + 1);
295
296 register_validators::<T>(c);
297 register_candidates::<T>(c);
298
299 let caller: T::AccountId = whitelisted_caller();
300 let bond: BalanceOf<T> = <T as pallet::Config>::Currency::minimum_balance() * 2u32.into();
301 <T as pallet::Config>::Currency::make_free_balance_be(&caller, bond);
302
303 <session::Pallet<T>>::ensure_can_pay_key_deposit(&caller).unwrap();
304 <session::Pallet<T>>::set_keys(
305 RawOrigin::Signed(caller.clone()).into(),
306 keys::<T>(c + 1),
307 Vec::new(),
308 )
309 .unwrap();
310
311 #[extrinsic_call]
312 _(RawOrigin::Signed(caller.clone()));
313
314 assert_last_event::<T>(
315 Event::CandidateAdded { account_id: caller, deposit: bond / 2u32.into() }.into(),
316 );
317 }
318
319 #[benchmark]
320 fn take_candidate_slot(c: Linear<{ min_candidates::<T>() + 1 }, { T::MaxCandidates::get() }>) {
321 CandidacyBond::<T>::put(<T as pallet::Config>::Currency::minimum_balance());
322 DesiredCandidates::<T>::put(1);
323
324 register_validators::<T>(c);
325 register_candidates::<T>(c);
326
327 let caller: T::AccountId = whitelisted_caller();
328 let bond: BalanceOf<T> = <T as pallet::Config>::Currency::minimum_balance() * 10u32.into();
329 <T as pallet::Config>::Currency::make_free_balance_be(&caller, bond);
330
331 <session::Pallet<T>>::ensure_can_pay_key_deposit(&caller).unwrap();
332 <session::Pallet<T>>::set_keys(
333 RawOrigin::Signed(caller.clone()).into(),
334 keys::<T>(c + 1),
335 Vec::new(),
336 )
337 .unwrap();
338
339 let target = CandidateList::<T>::get().iter().last().unwrap().who.clone();
340
341 #[extrinsic_call]
342 _(RawOrigin::Signed(caller.clone()), bond / 2u32.into(), target.clone());
343
344 assert_last_event::<T>(
345 Event::CandidateReplaced { old: target, new: caller, deposit: bond / 2u32.into() }
346 .into(),
347 );
348 }
349
350 #[benchmark]
352 fn leave_intent(c: Linear<{ min_candidates::<T>() + 1 }, { T::MaxCandidates::get() }>) {
353 CandidacyBond::<T>::put(<T as pallet::Config>::Currency::minimum_balance());
354 DesiredCandidates::<T>::put(c);
355
356 register_validators::<T>(c);
357 register_candidates::<T>(c);
358
359 let leaving = CandidateList::<T>::get().iter().last().unwrap().who.clone();
360 v2::whitelist!(leaving);
361
362 #[extrinsic_call]
363 _(RawOrigin::Signed(leaving.clone()));
364
365 assert_last_event::<T>(Event::CandidateRemoved { account_id: leaving }.into());
366 }
367
368 #[benchmark]
370 fn note_author() {
371 CandidacyBond::<T>::put(<T as pallet::Config>::Currency::minimum_balance());
372 <T as pallet::Config>::Currency::make_free_balance_be(
373 &<CollatorSelection<T>>::account_id(),
374 <T as pallet::Config>::Currency::minimum_balance() * 4u32.into(),
375 );
376 let author = account("author", 0, SEED);
377 let new_block: BlockNumberFor<T> = 10u32.into();
378
379 frame_system::Pallet::<T>::set_block_number(new_block);
380 assert!(<T as pallet::Config>::Currency::free_balance(&author) == 0u32.into());
381
382 #[block]
383 {
384 <CollatorSelection<T> as EventHandler<_, _>>::note_author(author.clone())
385 }
386
387 assert!(<T as pallet::Config>::Currency::free_balance(&author) > 0u32.into());
388 assert_eq!(frame_system::Pallet::<T>::block_number(), new_block);
389 }
390
391 #[benchmark]
393 fn new_session(
394 r: Linear<1, { T::MaxCandidates::get() }>,
395 c: Linear<1, { T::MaxCandidates::get() }>,
396 ) {
397 CandidacyBond::<T>::put(<T as pallet::Config>::Currency::minimum_balance());
398 DesiredCandidates::<T>::put(c);
399 frame_system::Pallet::<T>::set_block_number(0u32.into());
400
401 register_validators::<T>(c);
402 register_candidates::<T>(c);
403
404 let new_block: BlockNumberFor<T> = T::KickThreshold::get();
405 let zero_block: BlockNumberFor<T> = 0u32.into();
406 let candidates: Vec<T::AccountId> = CandidateList::<T>::get()
407 .iter()
408 .map(|candidate_info| candidate_info.who.clone())
409 .collect();
410
411 let non_removals = c.saturating_sub(r);
412
413 for i in 0..c {
414 LastAuthoredBlock::<T>::insert(candidates[i as usize].clone(), zero_block);
415 }
416
417 if non_removals > 0 {
418 for i in 0..non_removals {
419 LastAuthoredBlock::<T>::insert(candidates[i as usize].clone(), new_block);
420 }
421 } else {
422 for i in 0..c {
423 LastAuthoredBlock::<T>::insert(candidates[i as usize].clone(), new_block);
424 }
425 }
426
427 let min_candidates = min_candidates::<T>();
428 let pre_length = CandidateList::<T>::decode_len().unwrap_or_default();
429
430 frame_system::Pallet::<T>::set_block_number(new_block);
431
432 let current_length: u32 = CandidateList::<T>::decode_len()
433 .unwrap_or_default()
434 .try_into()
435 .unwrap_or_default();
436 assert!(c == current_length);
437 #[block]
438 {
439 <CollatorSelection<T> as SessionManager<_>>::new_session(0);
440 }
441
442 if c > r && non_removals >= min_candidates {
443 assert!(CandidateList::<T>::decode_len().unwrap_or_default() < pre_length);
447 } else if c > r && non_removals < min_candidates {
448 let current_length: u32 = CandidateList::<T>::decode_len()
452 .unwrap_or_default()
453 .try_into()
454 .unwrap_or_default();
455 assert!(min_candidates == current_length);
456 } else {
457 assert!(CandidateList::<T>::decode_len().unwrap_or_default() == pre_length);
460 }
461 }
462
463 impl_benchmark_test_suite!(CollatorSelection, crate::mock::new_test_ext(), crate::mock::Test);
464}