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/// Select which checks should be run when trying a runtime upgrade upgrade.
117#[derive(codec::Encode, codec::Decode, Clone, Debug, Copy, scale_info::TypeInfo, PartialEq)]
118pub enum UpgradeCheckSelect {
119	/// Run no checks.
120	None,
121	/// Run the `try_state`, `pre_upgrade` and `post_upgrade` checks.
122	All,
123	/// Run the `pre_upgrade` and `post_upgrade` checks.
124	PreAndPost,
125	/// Run the `try_state` checks.
126	TryState,
127}
128
129impl UpgradeCheckSelect {
130	/// Whether the pre- and post-upgrade checks are selected.
131	pub fn pre_and_post(&self) -> bool {
132		matches!(self, Self::All | Self::PreAndPost)
133	}
134
135	/// Whether the try-state checks are selected.
136	pub fn try_state(&self) -> bool {
137		matches!(self, Self::All | Self::TryState)
138	}
139
140	/// Whether to run any checks at all.
141	pub fn any(&self) -> bool {
142		!matches!(self, Self::None)
143	}
144}
145
146#[cfg(feature = "std")]
147impl core::str::FromStr for UpgradeCheckSelect {
148	type Err = &'static str;
149
150	fn from_str(s: &str) -> Result<Self, Self::Err> {
151		match s.to_lowercase().as_str() {
152			"none" => Ok(Self::None),
153			"all" => Ok(Self::All),
154			"pre-and-post" => Ok(Self::PreAndPost),
155			"try-state" => Ok(Self::TryState),
156			_ => Err("Invalid CheckSelector"),
157		}
158	}
159}
160
161/// Execute some checks to ensure the internal state of a pallet is consistent.
162///
163/// Usually, these checks should check all of the invariants that are expected to be held on all of
164/// the storage items of your pallet.
165///
166/// This hook should not alter any storage.
167pub trait TryState<BlockNumber> {
168	/// Execute the state checks.
169	fn try_state(_: BlockNumber, _: Select) -> Result<(), TryRuntimeError>;
170}
171
172#[cfg_attr(all(not(feature = "tuples-96"), not(feature = "tuples-128")), impl_for_tuples(64))]
173#[cfg_attr(all(feature = "tuples-96", not(feature = "tuples-128")), impl_for_tuples(96))]
174#[cfg_attr(all(feature = "tuples-128"), impl_for_tuples(128))]
175impl<BlockNumber: Clone + core::fmt::Debug + AtLeast32BitUnsigned> TryState<BlockNumber> for Tuple {
176	for_tuples!( where #( Tuple: crate::traits::PalletInfoAccess )* );
177	fn try_state(n: BlockNumber, targets: Select) -> Result<(), TryRuntimeError> {
178		match targets {
179			Select::None => Ok(()),
180			Select::All => {
181				let mut errors = Vec::<TryRuntimeError>::new();
182
183				for_tuples!(#(
184					if let Err(err) = Tuple::try_state(n.clone(), targets.clone()) {
185						errors.push(err);
186					}
187				)*);
188
189				if !errors.is_empty() {
190					log::error!(
191						target: "try-runtime",
192						"Detected errors while executing `try_state`:",
193					);
194
195					errors.iter().for_each(|err| {
196						log::error!(
197							target: "try-runtime",
198							"{:?}",
199							err
200						);
201					});
202
203					return Err(
204						"Detected errors while executing `try_state` checks. See logs for more \
205						info."
206							.into(),
207					)
208				}
209
210				Ok(())
211			},
212			Select::RoundRobin(len) => {
213				let functions: &[fn(BlockNumber, Select) -> Result<(), TryRuntimeError>] =
214					&[for_tuples!(#( Tuple::try_state ),*)];
215				let skip = n.clone() % (functions.len() as u32).into();
216				let skip: u32 =
217					skip.try_into().unwrap_or_else(|_| sp_runtime::traits::Bounded::max_value());
218				let mut result = Ok(());
219				for try_state_fn in functions.iter().cycle().skip(skip as usize).take(len as usize)
220				{
221					result = result.and(try_state_fn(n.clone(), targets.clone()));
222				}
223				result
224			},
225			Select::Only(ref pallet_names) => {
226				let try_state_fns: &[(
227					&'static str,
228					fn(BlockNumber, Select) -> Result<(), TryRuntimeError>,
229				)] = &[for_tuples!(
230					#( (<Tuple as crate::traits::PalletInfoAccess>::name(), Tuple::try_state) ),*
231				)];
232				let mut result = Ok(());
233				pallet_names.iter().for_each(|pallet_name| {
234					if let Some((name, try_state_fn)) =
235						try_state_fns.iter().find(|(name, _)| name.as_bytes() == pallet_name)
236					{
237						result = result.and(try_state_fn(n.clone(), targets.clone()));
238					} else {
239						log::warn!(
240							"Pallet {:?} not found",
241							alloc::str::from_utf8(pallet_name).unwrap_or_default()
242						);
243					}
244				});
245
246				result
247			},
248			Select::AllExcept(ref excluded_pallet_names) => {
249				let try_state_fns: &[(
250					&'static str,
251					fn(BlockNumber, Select) -> Result<(), TryRuntimeError>,
252				)] = &[for_tuples!(
253					#( (<Tuple as crate::traits::PalletInfoAccess>::name(), Tuple::try_state) ),*
254				)];
255
256				excluded_pallet_names.iter().for_each(|excluded_name| {
257					if !try_state_fns.iter().any(|(name, _)| name.as_bytes() == excluded_name) {
258						log::warn!(
259							"Pallet {:?} not found while trying to filter it out in Select::AllExcept",
260							alloc::str::from_utf8(excluded_name).unwrap_or_default()
261						);
262					}
263				});
264
265				let try_state_fns: Vec<_> = try_state_fns
266					.iter()
267					.filter(|(name, _)| {
268						!excluded_pallet_names
269							.iter()
270							.any(|excluded_name| name.as_bytes() == excluded_name)
271					})
272					.collect();
273
274				let mut errors = Vec::<TryRuntimeError>::new();
275
276				try_state_fns.iter().for_each(|(name, try_state_fn)| {
277					if let Err(err) = try_state_fn(n.clone(), targets.clone()) {
278						errors.push(err);
279					}
280				});
281
282				if !errors.is_empty() {
283					log::error!(
284						target: "try-runtime",
285						"Detected errors while executing `try_state`:",
286					);
287
288					errors.iter().for_each(|err| {
289						log::error!(
290							target: "try-runtime",
291							"{:?}",
292							err
293						);
294					});
295
296					return Err(
297						"Detected errors while executing `try_state` checks. See logs for more \
298						info."
299							.into(),
300					)
301				}
302
303				Ok(())
304			},
305		}
306	}
307}