referrerpolicy=no-referrer-when-downgrade

pallet_migrations/
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
18#![cfg(feature = "runtime-benchmarks")]
19
20use super::*;
21
22use core::array;
23use frame_benchmarking::{v2::*, BenchmarkError};
24use frame_system::{Pallet as System, RawOrigin};
25use sp_core::{twox_128, Get};
26use sp_io::{storage, KillStorageResult};
27use sp_runtime::traits::One;
28
29fn assert_has_event<T: Config>(generic_event: <T as Config>::RuntimeEvent) {
30	frame_system::Pallet::<T>::assert_has_event(generic_event.into());
31}
32
33fn assert_last_event<T: Config>(generic_event: <T as Config>::RuntimeEvent) {
34	frame_system::Pallet::<T>::assert_last_event(generic_event.into());
35}
36
37#[benchmarks]
38mod benches {
39	use super::*;
40	use frame_support::traits::Hooks;
41
42	#[benchmark]
43	fn onboard_new_mbms() {
44		T::Migrations::set_fail_after(0); // Should not be called anyway.
45		assert!(!Cursor::<T>::exists());
46
47		#[block]
48		{
49			Pallet::<T>::onboard_new_mbms();
50		}
51
52		assert_last_event::<T>(Event::UpgradeStarted { migrations: 1 }.into());
53	}
54
55	#[benchmark]
56	fn progress_mbms_none() {
57		T::Migrations::set_fail_after(0); // Should not be called anyway.
58		assert!(!Cursor::<T>::exists());
59
60		#[block]
61		{
62			Pallet::<T>::progress_mbms(One::one());
63		}
64	}
65
66	/// All migrations completed.
67	#[benchmark]
68	fn exec_migration_completed() -> Result<(), BenchmarkError> {
69		T::Migrations::set_fail_after(0); // Should not be called anyway.
70		assert_eq!(T::Migrations::len(), 1, "Setup failed");
71		let c = ActiveCursor { index: 1, inner_cursor: None, started_at: 0u32.into() };
72		let mut meter = WeightMeter::with_limit(T::MaxServiceWeight::get());
73		System::<T>::set_block_number(1u32.into());
74
75		#[block]
76		{
77			Pallet::<T>::exec_migration(c, false, &mut meter);
78		}
79
80		assert_last_event::<T>(Event::UpgradeCompleted {}.into());
81
82		Ok(())
83	}
84
85	/// No migration runs since it is skipped as historic.
86	#[benchmark]
87	fn exec_migration_skipped_historic() -> Result<(), BenchmarkError> {
88		T::Migrations::set_fail_after(0); // Should not be called anyway.
89		assert_eq!(T::Migrations::len(), 1, "Setup failed");
90		let c = ActiveCursor { index: 0, inner_cursor: None, started_at: 0u32.into() };
91
92		let id: IdentifierOf<T> = T::Migrations::nth_id(0).unwrap().try_into().unwrap();
93		Historic::<T>::insert(id, ());
94
95		let mut meter = WeightMeter::with_limit(T::MaxServiceWeight::get());
96		System::<T>::set_block_number(1u32.into());
97
98		#[block]
99		{
100			Pallet::<T>::exec_migration(c, false, &mut meter);
101		}
102
103		assert_last_event::<T>(Event::MigrationSkipped { index: 0 }.into());
104
105		Ok(())
106	}
107
108	/// Advance a migration by one step.
109	#[benchmark]
110	fn exec_migration_advance() -> Result<(), BenchmarkError> {
111		T::Migrations::set_success_after(1);
112		assert_eq!(T::Migrations::len(), 1, "Setup failed");
113		let c = ActiveCursor { index: 0, inner_cursor: None, started_at: 0u32.into() };
114		let mut meter = WeightMeter::with_limit(T::MaxServiceWeight::get());
115		System::<T>::set_block_number(1u32.into());
116
117		#[block]
118		{
119			Pallet::<T>::exec_migration(c, false, &mut meter);
120		}
121
122		assert_last_event::<T>(Event::MigrationAdvanced { index: 0, took: One::one() }.into());
123
124		Ok(())
125	}
126
127	/// Successfully complete a migration.
128	#[benchmark]
129	fn exec_migration_complete() -> Result<(), BenchmarkError> {
130		T::Migrations::set_success_after(0);
131		assert_eq!(T::Migrations::len(), 1, "Setup failed");
132		let c = ActiveCursor { index: 0, inner_cursor: None, started_at: 0u32.into() };
133		let mut meter = WeightMeter::with_limit(T::MaxServiceWeight::get());
134		System::<T>::set_block_number(1u32.into());
135
136		#[block]
137		{
138			Pallet::<T>::exec_migration(c, false, &mut meter);
139		}
140
141		assert_last_event::<T>(Event::MigrationCompleted { index: 0, took: One::one() }.into());
142
143		Ok(())
144	}
145
146	#[benchmark]
147	fn exec_migration_fail() -> Result<(), BenchmarkError> {
148		T::Migrations::set_fail_after(0);
149		assert_eq!(T::Migrations::len(), 1, "Setup failed");
150		let c = ActiveCursor { index: 0, inner_cursor: None, started_at: 0u32.into() };
151		let mut meter = WeightMeter::with_limit(T::MaxServiceWeight::get());
152		System::<T>::set_block_number(1u32.into());
153
154		#[block]
155		{
156			Pallet::<T>::exec_migration(c, false, &mut meter);
157		}
158
159		assert_has_event::<T>(Event::UpgradeFailed {}.into());
160
161		Ok(())
162	}
163
164	#[benchmark]
165	fn on_init_loop() {
166		T::Migrations::set_fail_after(0); // Should not be called anyway.
167		System::<T>::set_block_number(1u32.into());
168		<Pallet<T> as Hooks<BlockNumberFor<T>>>::on_runtime_upgrade();
169
170		#[block]
171		{
172			Pallet::<T>::on_initialize(1u32.into());
173		}
174	}
175
176	#[benchmark]
177	fn force_set_cursor() {
178		#[extrinsic_call]
179		_(RawOrigin::Root, Some(cursor::<T>()));
180	}
181
182	#[benchmark]
183	fn force_set_active_cursor() {
184		#[extrinsic_call]
185		_(RawOrigin::Root, 0, None, None);
186	}
187
188	#[benchmark]
189	fn force_onboard_mbms() {
190		#[extrinsic_call]
191		_(RawOrigin::Root);
192	}
193
194	#[benchmark]
195	fn clear_historic(n: Linear<0, { DEFAULT_HISTORIC_BATCH_CLEAR_SIZE * 2 }>) {
196		let id_max_len = <T as Config>::IdentifierMaxLen::get();
197		assert!(id_max_len >= 4, "Precondition violated");
198
199		for i in 0..DEFAULT_HISTORIC_BATCH_CLEAR_SIZE * 2 {
200			let id = IdentifierOf::<T>::truncate_from(
201				i.encode().into_iter().cycle().take(id_max_len as usize).collect::<Vec<_>>(),
202			);
203
204			Historic::<T>::insert(&id, ());
205		}
206
207		#[extrinsic_call]
208		_(
209			RawOrigin::Root,
210			HistoricCleanupSelector::Wildcard { limit: n.into(), previous_cursor: None },
211		);
212	}
213
214	#[benchmark(skip_meta, pov_mode = Measured)]
215	fn reset_pallet_migration(n: Linear<0, 2048>) -> Result<(), BenchmarkError> {
216		let prefix: [u8; 16] = twox_128(b"__ResetPalletBenchmarkPrefix__");
217
218		for i in 0..n {
219			// we need to avoid allocations here
220			let mut iter = prefix.into_iter().chain(i.to_le_bytes());
221			let key: [u8; 20] = array::from_fn(|_| iter.next().unwrap());
222			// 32 byte will trigger the worst case where the value is
223			// no longer stored inline
224			storage::set(&key, &[0u8; 32]);
225		}
226
227		let result;
228		#[block]
229		{
230			result = storage::clear_prefix(&prefix, None);
231		}
232
233		// It will always reports no keys removed because they are still in the overlay.
234		// However, the benchmarking PoV results are correctly dependent on the amount of
235		// keys removed.
236		match result {
237			KillStorageResult::AllRemoved(_i) => {
238				// during the test the storage is not comitted and `i` will always be 0
239				#[cfg(not(test))]
240				ensure!(_i == n, "Not all keys are removed");
241			},
242			_ => Err("Not all keys were removed")?,
243		}
244
245		Ok(())
246	}
247
248	fn cursor<T: Config>() -> CursorOf<T> {
249		// Note: The weight of a function can depend on the weight of reading the `inner_cursor`.
250		// `Cursor` is a user provided type. Now instead of requiring something like `Cursor:
251		// From<u32>`, we instead rely on the fact that it is MEL and the PoV benchmarking will
252		// therefore already take the MEL bound, even when the cursor in storage is `None`.
253		MigrationCursor::Active(ActiveCursor {
254			index: u32::MAX,
255			inner_cursor: None,
256			started_at: 0u32.into(),
257		})
258	}
259
260	// Implements a test for each benchmark. Execute with:
261	// `cargo test -p pallet-migrations --features runtime-benchmarks`.
262	impl_benchmark_test_suite!(Pallet, crate::mock::new_test_ext(), crate::mock::Test);
263}