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}
44
45impl Select {
46	/// Whether to run any checks at all.
47	pub fn any(&self) -> bool {
48		!matches!(self, Select::None)
49	}
50}
51
52impl Default for Select {
53	fn default() -> Self {
54		Select::None
55	}
56}
57
58impl core::fmt::Debug for Select {
59	fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
60		match self {
61			Select::RoundRobin(x) => write!(f, "RoundRobin({})", x),
62			Select::Only(x) => write!(
63				f,
64				"Only({:?})",
65				x.iter()
66					.map(|x| alloc::str::from_utf8(x).unwrap_or("<invalid?>"))
67					.collect::<Vec<_>>(),
68			),
69			Select::All => write!(f, "All"),
70			Select::None => write!(f, "None"),
71		}
72	}
73}
74
75#[cfg(feature = "std")]
76impl std::str::FromStr for Select {
77	type Err = &'static str;
78	fn from_str(s: &str) -> Result<Self, Self::Err> {
79		match s {
80			"all" | "All" => Ok(Select::All),
81			"none" | "None" => Ok(Select::None),
82			_ =>
83				if s.starts_with("rr-") {
84					let count = s
85						.split_once('-')
86						.and_then(|(_, count)| count.parse::<u32>().ok())
87						.ok_or("failed to parse count")?;
88					Ok(Select::RoundRobin(count))
89				} else {
90					let pallets = s.split(',').map(|x| x.as_bytes().to_vec()).collect::<Vec<_>>();
91					Ok(Select::Only(pallets))
92				},
93		}
94	}
95}
96
97/// Select which checks should be run when trying a runtime upgrade upgrade.
98#[derive(codec::Encode, codec::Decode, Clone, Debug, Copy, scale_info::TypeInfo, PartialEq)]
99pub enum UpgradeCheckSelect {
100	/// Run no checks.
101	None,
102	/// Run the `try_state`, `pre_upgrade` and `post_upgrade` checks.
103	All,
104	/// Run the `pre_upgrade` and `post_upgrade` checks.
105	PreAndPost,
106	/// Run the `try_state` checks.
107	TryState,
108}
109
110impl UpgradeCheckSelect {
111	/// Whether the pre- and post-upgrade checks are selected.
112	pub fn pre_and_post(&self) -> bool {
113		matches!(self, Self::All | Self::PreAndPost)
114	}
115
116	/// Whether the try-state checks are selected.
117	pub fn try_state(&self) -> bool {
118		matches!(self, Self::All | Self::TryState)
119	}
120
121	/// Whether to run any checks at all.
122	pub fn any(&self) -> bool {
123		!matches!(self, Self::None)
124	}
125}
126
127#[cfg(feature = "std")]
128impl core::str::FromStr for UpgradeCheckSelect {
129	type Err = &'static str;
130
131	fn from_str(s: &str) -> Result<Self, Self::Err> {
132		match s.to_lowercase().as_str() {
133			"none" => Ok(Self::None),
134			"all" => Ok(Self::All),
135			"pre-and-post" => Ok(Self::PreAndPost),
136			"try-state" => Ok(Self::TryState),
137			_ => Err("Invalid CheckSelector"),
138		}
139	}
140}
141
142/// Execute some checks to ensure the internal state of a pallet is consistent.
143///
144/// Usually, these checks should check all of the invariants that are expected to be held on all of
145/// the storage items of your pallet.
146///
147/// This hook should not alter any storage.
148pub trait TryState<BlockNumber> {
149	/// Execute the state checks.
150	fn try_state(_: BlockNumber, _: Select) -> Result<(), TryRuntimeError>;
151}
152
153#[cfg_attr(all(not(feature = "tuples-96"), not(feature = "tuples-128")), impl_for_tuples(64))]
154#[cfg_attr(all(feature = "tuples-96", not(feature = "tuples-128")), impl_for_tuples(96))]
155#[cfg_attr(all(feature = "tuples-128"), impl_for_tuples(128))]
156impl<BlockNumber: Clone + core::fmt::Debug + AtLeast32BitUnsigned> TryState<BlockNumber> for Tuple {
157	for_tuples!( where #( Tuple: crate::traits::PalletInfoAccess )* );
158	fn try_state(n: BlockNumber, targets: Select) -> Result<(), TryRuntimeError> {
159		match targets {
160			Select::None => Ok(()),
161			Select::All => {
162				let mut errors = Vec::<TryRuntimeError>::new();
163
164				for_tuples!(#(
165					if let Err(err) = Tuple::try_state(n.clone(), targets.clone()) {
166						errors.push(err);
167					}
168				)*);
169
170				if !errors.is_empty() {
171					log::error!(
172						target: "try-runtime",
173						"Detected errors while executing `try_state`:",
174					);
175
176					errors.iter().for_each(|err| {
177						log::error!(
178							target: "try-runtime",
179							"{:?}",
180							err
181						);
182					});
183
184					return Err(
185						"Detected errors while executing `try_state` checks. See logs for more \
186						info."
187							.into(),
188					)
189				}
190
191				Ok(())
192			},
193			Select::RoundRobin(len) => {
194				let functions: &[fn(BlockNumber, Select) -> Result<(), TryRuntimeError>] =
195					&[for_tuples!(#( Tuple::try_state ),*)];
196				let skip = n.clone() % (functions.len() as u32).into();
197				let skip: u32 =
198					skip.try_into().unwrap_or_else(|_| sp_runtime::traits::Bounded::max_value());
199				let mut result = Ok(());
200				for try_state_fn in functions.iter().cycle().skip(skip as usize).take(len as usize)
201				{
202					result = result.and(try_state_fn(n.clone(), targets.clone()));
203				}
204				result
205			},
206			Select::Only(ref pallet_names) => {
207				let try_state_fns: &[(
208					&'static str,
209					fn(BlockNumber, Select) -> Result<(), TryRuntimeError>,
210				)] = &[for_tuples!(
211					#( (<Tuple as crate::traits::PalletInfoAccess>::name(), Tuple::try_state) ),*
212				)];
213				let mut result = Ok(());
214				pallet_names.iter().for_each(|pallet_name| {
215					if let Some((name, try_state_fn)) =
216						try_state_fns.iter().find(|(name, _)| name.as_bytes() == pallet_name)
217					{
218						result = result.and(try_state_fn(n.clone(), targets.clone()));
219					} else {
220						log::warn!(
221							"Pallet {:?} not found",
222							alloc::str::from_utf8(pallet_name).unwrap_or_default()
223						);
224					}
225				});
226
227				result
228			},
229		}
230	}
231}