TryDecodeEntireState check for storage types and pallets (#1805)

### This PR is a port of this [PR for
substrate](https://github.com/paritytech/substrate/pull/13013) by
@kianenigma

Add infrastructure needed to have a Pallet::decode_entire_state(), which
makes sure all "typed" storage items defined in the pallet are
decode-able.

This is not enforced in any way at the moment. Teams who wish to
integrate/use this in the try-runtime feature flag should add
frame_support::storage::migration::EnsureStateDecodes as the LAST ITEM
of the runtime's custom migrations, and pass it to frame-executive. This
will make it usable in try-runtime on-runtime-upgrade.

This now catches cases like
https://github.com/paritytech/polkadot-sdk/pull/1969:
```pre
ERROR runtime::executive] failed to decode the value at key: Failed to decode value at key: 0x94eadf0156a8ad5156507773d0471e4ab8ebad86f546c7e0b135a4212aace339. Storage info StorageInfo { pallet_name: Ok("ParaScheduler"), storage_name: Ok("AvailabilityCores"), prefix: Err(Utf8Error { valid_up_to: 0, error_len: Some(1) }), max_values: Some(1), max_size: None }. Raw value: Some("0x0c010101010101")
```

... or:

![image](https://github.com/paritytech/polkadot-sdk/assets/10380170/73052d4f-4da5-4b21-a8dd-b17004e5965e)

Closes #241

---------

Signed-off-by: Oliver Tale-Yazdi <oliver.tale-yazdi@parity.io>
Co-authored-by: Oliver Tale-Yazdi <oliver.tale-yazdi@parity.io>
Co-authored-by: Liam Aharon <liam.aharon@hotmail.com>
This commit is contained in:
Piet
2023-11-06 19:40:14 +01:00
committed by GitHub
parent 15df7f54d2
commit 32a974088c
19 changed files with 807 additions and 36 deletions
+59 -13
View File
@@ -139,9 +139,15 @@ use sp_runtime::{
use sp_std::{marker::PhantomData, prelude::*};
#[cfg(feature = "try-runtime")]
use log;
#[cfg(feature = "try-runtime")]
use sp_runtime::TryRuntimeError;
use ::{
frame_support::{
traits::{TryDecodeEntireStorage, TryDecodeEntireStorageError, TryState},
StorageNoopGuard,
},
frame_try_runtime::{TryStateSelect, UpgradeCheckSelect},
log,
sp_runtime::TryRuntimeError,
};
#[allow(dead_code)]
const LOG_TARGET: &str = "runtime::executive";
@@ -229,7 +235,8 @@ impl<
+ OnIdle<BlockNumberFor<System>>
+ OnFinalize<BlockNumberFor<System>>
+ OffchainWorker<BlockNumberFor<System>>
+ frame_support::traits::TryState<BlockNumberFor<System>>,
+ TryState<BlockNumberFor<System>>
+ TryDecodeEntireStorage,
COnRuntimeUpgrade: OnRuntimeUpgrade,
> Executive<System, Block, Context, UnsignedValidator, AllPalletsWithSystem, COnRuntimeUpgrade>
where
@@ -308,11 +315,15 @@ where
let _guard = frame_support::StorageNoopGuard::default();
<AllPalletsWithSystem as frame_support::traits::TryState<
BlockNumberFor<System>,
>>::try_state(*header.number(), select)
>>::try_state(*header.number(), select.clone())
.map_err(|e| {
log::error!(target: LOG_TARGET, "failure: {:?}", e);
e
})?;
if select.any() {
let res = AllPalletsWithSystem::try_decode_entire_state();
Self::log_decode_result(res)?;
}
drop(_guard);
// do some of the checks that would normally happen in `final_checks`, but perhaps skip
@@ -352,26 +363,61 @@ where
/// Execute all `OnRuntimeUpgrade` of this runtime.
///
/// The `checks` param determines whether to execute `pre/post_upgrade` and `try_state` hooks.
pub fn try_runtime_upgrade(
checks: frame_try_runtime::UpgradeCheckSelect,
) -> Result<Weight, TryRuntimeError> {
pub fn try_runtime_upgrade(checks: UpgradeCheckSelect) -> Result<Weight, TryRuntimeError> {
let weight =
<(COnRuntimeUpgrade, AllPalletsWithSystem) as OnRuntimeUpgrade>::try_on_runtime_upgrade(
checks.pre_and_post(),
)?;
// Nothing should modify the state after the migrations ran:
let _guard = StorageNoopGuard::default();
// The state must be decodable:
if checks.any() {
let res = AllPalletsWithSystem::try_decode_entire_state();
Self::log_decode_result(res)?;
}
// Check all storage invariants:
if checks.try_state() {
let _guard = frame_support::StorageNoopGuard::default();
<AllPalletsWithSystem as frame_support::traits::TryState<
BlockNumberFor<System>,
>>::try_state(
AllPalletsWithSystem::try_state(
frame_system::Pallet::<System>::block_number(),
frame_try_runtime::TryStateSelect::All,
TryStateSelect::All,
)?;
}
Ok(weight)
}
/// Logs the result of trying to decode the entire state.
fn log_decode_result(
res: Result<usize, Vec<TryDecodeEntireStorageError>>,
) -> Result<(), TryRuntimeError> {
match res {
Ok(bytes) => {
log::debug!(
target: LOG_TARGET,
"decoded the entire state ({bytes} bytes)",
);
Ok(())
},
Err(errors) => {
log::error!(
target: LOG_TARGET,
"`try_decode_entire_state` failed with {} errors",
errors.len(),
);
for (i, err) in errors.iter().enumerate() {
// We log the short version to `error` and then the full debug info to `debug`:
log::error!(target: LOG_TARGET, "- {i}. error: {err}");
log::debug!(target: LOG_TARGET, "- {i}. error: {err:?}");
}
Err("`try_decode_entire_state` failed".into())
},
}
}
}
impl<