referrerpolicy=no-referrer-when-downgrade

frame_support/traits/try_runtime/
mod.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//! Try-runtime specific traits and types.
19
20pub mod decode_entire_state;
21pub use decode_entire_state::{TryDecodeEntireStorage, TryDecodeEntireStorageError};
22
23use super::StorageInstance;
24
25use alloc::vec::Vec;
26use impl_trait_for_tuples::impl_for_tuples;
27use sp_arithmetic::traits::AtLeast32BitUnsigned;
28use sp_runtime::TryRuntimeError;
29
30/// Which state tests to execute.
31#[derive(codec::Encode, codec::Decode, Clone, scale_info::TypeInfo, PartialEq)]
32pub enum Select {
33	/// None of them.
34	None,
35	/// All of them.
36	All,
37	/// Run a fixed number of them in a round robin manner.
38	RoundRobin(u32),
39	/// Run only pallets who's name matches the given list.
40	///
41	/// Pallet names are obtained from [`super::PalletInfoAccess`].
42	Only(Vec<Vec<u8>>),
43	/// Run all pallets except those whose names match the given list.
44	///
45	/// Pallet names are obtained from [`super::PalletInfoAccess`].
46	AllExcept(Vec<Vec<u8>>),
47}
48
49impl Select {
50	/// Whether to run any checks at all.
51	pub fn any(&self) -> bool {
52		!matches!(self, Select::None)
53	}
54}
55
56impl Default for Select {
57	fn default() -> Self {
58		Select::None
59	}
60}
61
62impl core::fmt::Debug for Select {
63	fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
64		match self {
65			Select::RoundRobin(x) => write!(f, "RoundRobin({})", x),
66			Select::Only(x) => write!(
67				f,
68				"Only({:?})",
69				x.iter()
70					.map(|x| alloc::str::from_utf8(x).unwrap_or("<invalid?>"))
71					.collect::<Vec<_>>(),
72			),
73			Select::AllExcept(x) => write!(
74				f,
75				"AllExcept({:?})",
76				x.iter()
77					.map(|x| alloc::str::from_utf8(x).unwrap_or("<invalid?>"))
78					.collect::<Vec<_>>(),
79			),
80			Select::All => write!(f, "All"),
81			Select::None => write!(f, "None"),
82		}
83	}
84}
85
86#[cfg(feature = "std")]
87impl std::str::FromStr for Select {
88	type Err = &'static str;
89	fn from_str(s: &str) -> Result<Self, Self::Err> {
90		match s {
91			"all" | "All" => Ok(Select::All),
92			"none" | "None" => Ok(Select::None),
93			_ => {
94				if s.starts_with("rr-") {
95					let count = s
96						.split_once('-')
97						.and_then(|(_, count)| count.parse::<u32>().ok())
98						.ok_or("failed to parse count")?;
99					Ok(Select::RoundRobin(count))
100				} else if s.starts_with("all-except-") {
101					let pallets = s
102						.strip_prefix("all-except-")
103						.ok_or("failed to parse all-except prefix")?
104						.split(',')
105						.map(|x| x.as_bytes().to_vec())
106						.collect::<Vec<_>>();
107					Ok(Select::AllExcept(pallets))
108				} else {
109					let pallets = s.split(',').map(|x| x.as_bytes().to_vec()).collect::<Vec<_>>();
110					Ok(Select::Only(pallets))
111				}
112			},
113		}
114	}
115}
116
117/// Select which checks should be run when trying a runtime upgrade upgrade.
118#[derive(codec::Encode, codec::Decode, Clone, Debug, Copy, scale_info::TypeInfo, PartialEq)]
119pub enum UpgradeCheckSelect {
120	/// Run no checks.
121	None,
122	/// Run the `try_state`, `pre_upgrade` and `post_upgrade` checks.
123	All,
124	/// Run the `pre_upgrade` and `post_upgrade` checks.
125	PreAndPost,
126	/// Run the `try_state` checks.
127	TryState,
128}
129
130impl UpgradeCheckSelect {
131	/// Whether the pre- and post-upgrade checks are selected.
132	pub fn pre_and_post(&self) -> bool {
133		matches!(self, Self::All | Self::PreAndPost)
134	}
135
136	/// Whether the try-state checks are selected.
137	pub fn try_state(&self) -> bool {
138		matches!(self, Self::All | Self::TryState)
139	}
140
141	/// Whether to run any checks at all.
142	pub fn any(&self) -> bool {
143		!matches!(self, Self::None)
144	}
145}
146
147#[cfg(feature = "std")]
148impl core::str::FromStr for UpgradeCheckSelect {
149	type Err = &'static str;
150
151	fn from_str(s: &str) -> Result<Self, Self::Err> {
152		match s.to_lowercase().as_str() {
153			"none" => Ok(Self::None),
154			"all" => Ok(Self::All),
155			"pre-and-post" => Ok(Self::PreAndPost),
156			"try-state" => Ok(Self::TryState),
157			_ => Err("Invalid CheckSelector"),
158		}
159	}
160}
161
162/// Execute some checks to ensure the internal state of a pallet is consistent.
163///
164/// Usually, these checks should check all of the invariants that are expected to be held on all of
165/// the storage items of your pallet.
166///
167/// This hook should not alter any storage.
168pub trait TryState<BlockNumber> {
169	/// Execute the state checks.
170	fn try_state(_: BlockNumber, _: Select) -> Result<(), TryRuntimeError>;
171}
172
173#[cfg_attr(all(not(feature = "tuples-96"), not(feature = "tuples-128")), impl_for_tuples(64))]
174#[cfg_attr(all(feature = "tuples-96", not(feature = "tuples-128")), impl_for_tuples(96))]
175#[cfg_attr(all(feature = "tuples-128"), impl_for_tuples(128))]
176impl<BlockNumber: Clone + core::fmt::Debug + AtLeast32BitUnsigned> TryState<BlockNumber> for Tuple {
177	for_tuples!( where #( Tuple: crate::traits::PalletInfoAccess )* );
178	fn try_state(n: BlockNumber, targets: Select) -> Result<(), TryRuntimeError> {
179		match targets {
180			Select::None => Ok(()),
181			Select::All => {
182				let mut errors = Vec::<TryRuntimeError>::new();
183
184				for_tuples!(#(
185					if let Err(err) = Tuple::try_state(n.clone(), targets.clone()) {
186						errors.push(err);
187					}
188				)*);
189
190				if !errors.is_empty() {
191					log::error!(
192						target: "try-runtime",
193						"Detected errors while executing `try_state`:",
194					);
195
196					errors.iter().for_each(|err| {
197						log::error!(
198							target: "try-runtime",
199							"{:?}",
200							err
201						);
202					});
203
204					return Err(
205						"Detected errors while executing `try_state` checks. See logs for more \
206						info."
207							.into(),
208					);
209				}
210
211				Ok(())
212			},
213			Select::RoundRobin(len) => {
214				let functions: &[fn(BlockNumber, Select) -> Result<(), TryRuntimeError>] =
215					&[for_tuples!(#( Tuple::try_state ),*)];
216				let skip = n.clone() % (functions.len() as u32).into();
217				let skip: u32 =
218					skip.try_into().unwrap_or_else(|_| sp_runtime::traits::Bounded::max_value());
219				let mut result = Ok(());
220				for try_state_fn in functions.iter().cycle().skip(skip as usize).take(len as usize)
221				{
222					result = result.and(try_state_fn(n.clone(), targets.clone()));
223				}
224				result
225			},
226			Select::Only(ref pallet_names) => {
227				let try_state_fns: &[(
228					&'static str,
229					fn(BlockNumber, Select) -> Result<(), TryRuntimeError>,
230				)] = &[for_tuples!(
231					#( (<Tuple as crate::traits::PalletInfoAccess>::name(), Tuple::try_state) ),*
232				)];
233				let mut result = Ok(());
234				pallet_names.iter().for_each(|pallet_name| {
235					if let Some((name, try_state_fn)) =
236						try_state_fns.iter().find(|(name, _)| name.as_bytes() == pallet_name)
237					{
238						result = result.and(try_state_fn(n.clone(), targets.clone()));
239					} else {
240						log::warn!(
241							"Pallet {:?} not found",
242							alloc::str::from_utf8(pallet_name).unwrap_or_default()
243						);
244					}
245				});
246
247				result
248			},
249			Select::AllExcept(ref excluded_pallet_names) => {
250				let try_state_fns: &[(
251					&'static str,
252					fn(BlockNumber, Select) -> Result<(), TryRuntimeError>,
253				)] = &[for_tuples!(
254					#( (<Tuple as crate::traits::PalletInfoAccess>::name(), Tuple::try_state) ),*
255				)];
256
257				excluded_pallet_names.iter().for_each(|excluded_name| {
258					if !try_state_fns.iter().any(|(name, _)| name.as_bytes() == excluded_name) {
259						log::warn!(
260							"Pallet {:?} not found while trying to filter it out in Select::AllExcept",
261							alloc::str::from_utf8(excluded_name).unwrap_or_default()
262						);
263					}
264				});
265
266				let try_state_fns: Vec<_> = try_state_fns
267					.iter()
268					.filter(|(name, _)| {
269						!excluded_pallet_names
270							.iter()
271							.any(|excluded_name| name.as_bytes() == excluded_name)
272					})
273					.collect();
274
275				let mut errors = Vec::<TryRuntimeError>::new();
276
277				try_state_fns.iter().for_each(|(name, try_state_fn)| {
278					if let Err(err) = try_state_fn(n.clone(), targets.clone()) {
279						errors.push(err);
280					}
281				});
282
283				if !errors.is_empty() {
284					log::error!(
285						target: "try-runtime",
286						"Detected errors while executing `try_state`:",
287					);
288
289					errors.iter().for_each(|err| {
290						log::error!(
291							target: "try-runtime",
292							"{:?}",
293							err
294						);
295					});
296
297					return Err(
298						"Detected errors while executing `try_state` checks. See logs for more \
299						info."
300							.into(),
301					);
302				}
303
304				Ok(())
305			},
306		}
307	}
308}