referrerpolicy=no-referrer-when-downgrade

pallet_election_provider_multi_block/verifier/
benchmarking.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
18use crate::{
19	signed::RewardSource,
20	verifier::{Config, Event, FeasibilityError, Pallet, Status, StatusStorage},
21	CurrentPhase, Phase,
22};
23use frame_benchmarking::v2::*;
24use frame_election_provider_support::{ElectionProvider, NposSolution};
25use frame_support::{
26	pallet_prelude::*,
27	traits::fungible::{Inspect, Mutate},
28};
29use sp_runtime::traits::Saturating;
30use sp_std::prelude::*;
31
32/// The currency configured on the signed pallet, which is what reward payouts are drawn from.
33type SignedCurrencyOf<T> = <T as crate::signed::Config>::Currency;
34
35#[benchmarks(where
36	T: crate::Config + crate::signed::Config + crate::unsigned::Config,
37	<T as frame_system::Config>::RuntimeEvent: TryInto<crate::verifier::Event<T>>
38)]
39mod benchmarks {
40	use super::*;
41
42	fn events_for<T: Config>() -> Vec<Event<T>>
43	where
44		<T as frame_system::Config>::RuntimeEvent: TryInto<Event<T>>,
45	{
46		frame_system::Pallet::<T>::read_events_for_pallet::<Event<T>>()
47	}
48
49	#[benchmark(pov_mode = Measured)]
50	fn verification_valid_non_terminal() -> Result<(), BenchmarkError> {
51		#[cfg(test)]
52		crate::mock::ElectionStart::set(sp_runtime::traits::Bounded::max_value());
53		crate::Pallet::<T>::start().unwrap();
54
55		// roll to signed validation, with a solution stored in the signed pallet
56		crate::Pallet::<T>::roll_to_signed_and_submit_full_solution()?;
57
58		// roll to verification
59		crate::Pallet::<T>::roll_until_matches(|| {
60			matches!(CurrentPhase::<T>::get(), Phase::SignedValidation(_))
61		});
62
63		// start signal must have been sent by now
64		assert_eq!(StatusStorage::<T>::get(), Status::Ongoing(crate::Pallet::<T>::msp()));
65
66		#[block]
67		{
68			crate::Pallet::<T>::roll_next(false);
69		}
70		assert_eq!(StatusStorage::<T>::get(), Status::Ongoing(crate::Pallet::<T>::msp() - 1));
71
72		Ok(())
73	}
74
75	#[benchmark(pov_mode = Measured)]
76	fn verification_valid_terminal() -> Result<(), BenchmarkError> {
77		#[cfg(test)]
78		crate::mock::ElectionStart::set(sp_runtime::traits::Bounded::max_value());
79		crate::Pallet::<T>::start().unwrap();
80
81		// roll to signed validation, with a solution stored in the signed pallet
82		assert!(
83			T::SignedValidationPhase::get() >= T::Pages::get().into(),
84			"Signed validation phase must be larger than the number of pages"
85		);
86
87		crate::Pallet::<T>::roll_to_signed_and_submit_full_solution()?;
88
89		// Accepting the solution pays the winner out of `RewardSource`, which holds nothing in
90		// genesis. Fund it so the measured path is the transfer that production takes every era,
91		// rather than the failed-transfer-then-defer fallback. The payout is `RewardBase` plus the
92		// submission's accrued fee, so fund generously; the measured work does not depend on how
93		// much the source holds.
94		if let Some(source) = <T as crate::signed::Config>::RewardSource::account() {
95			let funds = <T as crate::signed::Config>::RewardBase::get()
96				.saturating_mul(2u32.into())
97				.saturating_add(SignedCurrencyOf::<T>::minimum_balance());
98			SignedCurrencyOf::<T>::mint_into(&source, funds)?;
99		}
100
101		// roll to before the last page of verification
102		crate::Pallet::<T>::roll_until_matches(|| {
103			matches!(CurrentPhase::<T>::get(), Phase::SignedValidation(_))
104		});
105
106		// start signal must have been sent by now
107		assert_eq!(StatusStorage::<T>::get(), Status::Ongoing(crate::Pallet::<T>::msp()));
108		for _ in 0..(T::Pages::get() - 1) {
109			crate::Pallet::<T>::roll_next(false);
110		}
111
112		// we must have verified all pages by now, minus the last one.
113		assert!(matches!(
114			&events_for::<T>()[..],
115			[Event::Verified(_, _), .., Event::Verified(1, _)]
116		));
117
118		// verify the last page.
119		#[block]
120		{
121			crate::Pallet::<T>::roll_next(false);
122		}
123
124		// we are done
125		assert_eq!(StatusStorage::<T>::get(), Status::Nothing);
126		// last event is success
127		assert!(matches!(
128			&events_for::<T>()[..],
129			[Event::Verified(_, _), .., Event::Verified(0, _), Event::Queued(_, None)]
130		));
131
132		Ok(())
133	}
134
135	#[benchmark(pov_mode = Measured)]
136	fn verification_invalid_terminal() -> Result<(), BenchmarkError> {
137		// this is the verification of the current page + removing all of the previously valid
138		// pages. The worst case is therefore when the last page is invalid, for example the final
139		// score.
140		assert!(T::Pages::get() >= 2, "benchmark only works if we have more than 2 pages");
141
142		#[cfg(test)]
143		crate::mock::ElectionStart::set(sp_runtime::traits::Bounded::max_value());
144		crate::Pallet::<T>::start().unwrap();
145
146		// roll to signed validation, with a solution stored in the signed pallet
147
148		// but this solution is corrupt
149		let mut paged_solution = crate::Pallet::<T>::roll_to_signed_and_mine_full_solution();
150		paged_solution.score.minimal_stake -= 1;
151		crate::Pallet::<T>::submit_full_solution(paged_solution)?;
152
153		// roll to verification
154		crate::Pallet::<T>::roll_until_matches(|| {
155			matches!(CurrentPhase::<T>::get(), Phase::SignedValidation(_))
156		});
157
158		assert_eq!(StatusStorage::<T>::get(), Status::Ongoing(crate::Pallet::<T>::msp()));
159		// verify all pages, except for the last one.
160		for i in 0..T::Pages::get() - 1 {
161			crate::Pallet::<T>::roll_next(false);
162			assert_eq!(
163				StatusStorage::<T>::get(),
164				Status::Ongoing(crate::Pallet::<T>::msp() - 1 - i)
165			);
166		}
167
168		// next page to be verified is the last one
169		assert_eq!(StatusStorage::<T>::get(), Status::Ongoing(crate::Pallet::<T>::lsp()));
170		assert!(matches!(
171			&events_for::<T>()[..],
172			[Event::Verified(_, _), .., Event::Verified(1, _)]
173		));
174
175		#[block]
176		{
177			crate::Pallet::<T>::roll_next(false);
178		}
179
180		// we are now reset.
181		assert_eq!(StatusStorage::<T>::get(), Status::Nothing);
182		assert!(matches!(
183			&events_for::<T>()[..],
184			[
185				..,
186				Event::Verified(0, _),
187				Event::VerificationFailed(0, FeasibilityError::InvalidScore)
188			]
189		));
190
191		Ok(())
192	}
193
194	#[benchmark(pov_mode = Measured)]
195	fn verification_invalid_non_terminal(
196		// number of valid pages that have been verified, before we verify the non-terminal invalid
197		// page.
198		v: Linear<0, { T::Pages::get() - 1 }>,
199	) -> Result<(), BenchmarkError> {
200		assert!(T::Pages::get() >= 2, "benchmark only works if we have more than 2 pages");
201
202		#[cfg(test)]
203		crate::mock::ElectionStart::set(sp_runtime::traits::Bounded::max_value());
204		crate::Pallet::<T>::start().unwrap();
205
206		// roll to signed validation, with a solution stored in the signed pallet, but this solution
207		// is corrupt in its msp.
208		let mut paged_solution = crate::Pallet::<T>::roll_to_signed_and_mine_full_solution();
209		let page_to_corrupt = crate::Pallet::<T>::msp() - v;
210		crate::log!(
211			info,
212			"pages of solution: {:?}, to corrupt {}, v {}",
213			paged_solution.solution_pages.len(),
214			page_to_corrupt,
215			v
216		);
217		paged_solution.solution_pages[page_to_corrupt as usize].corrupt();
218		crate::Pallet::<T>::submit_full_solution(paged_solution)?;
219
220		// roll to verification
221		crate::Pallet::<T>::roll_until_matches(|| {
222			matches!(CurrentPhase::<T>::get(), Phase::SignedValidation(_))
223		});
224
225		// we should be ready to go
226		assert_eq!(StatusStorage::<T>::get(), Status::Ongoing(crate::Pallet::<T>::msp()));
227
228		// validate the the parameterized number of valid pages.
229		for _ in 0..v {
230			crate::Pallet::<T>::roll_next(false);
231		}
232
233		// we are still ready to continue
234		assert_eq!(StatusStorage::<T>::get(), Status::Ongoing(crate::Pallet::<T>::msp() - v));
235
236		// verify one page, which will be invalid.
237		#[block]
238		{
239			crate::Pallet::<T>::roll_next(false);
240		}
241
242		// we are now reset, because this page was invalid.
243		assert_eq!(StatusStorage::<T>::get(), Status::Nothing);
244
245		assert!(matches!(
246			&events_for::<T>()[..],
247			[.., Event::VerificationFailed(_, FeasibilityError::NposElection(_))]
248		));
249
250		Ok(())
251	}
252
253	impl_benchmark_test_suite!(
254		Pallet,
255		crate::mock::ExtBuilder::full().build_unchecked(),
256		crate::mock::Runtime
257	);
258}