mirror of
https://github.com/pezkuwichain/pezkuwi-subxt.git
synced 2026-06-13 04:41:02 +00:00
Runtime State Test + Integration with try-runtime (#10174)
* add missing version to dependencies * Huh * add features more * more fixing * last touches * it all finally works * remove some feature gates * remove unused * fix old macro * make it work again * fmt * remove unused import * ".git/.scripts/fmt.sh" 1 * Cleanup more * fix and rename everything * a few clippy fixes * Add try-runtime feature Signed-off-by: Oliver Tale-Yazdi <oliver.tale-yazdi@parity.io> * small fixes * fmt * Update bin/node-template/runtime/src/lib.rs * fix build * Update utils/frame/try-runtime/cli/src/lib.rs Co-authored-by: David <dvdplm@gmail.com> * Update utils/frame/try-runtime/cli/src/commands/execute_block.rs Co-authored-by: David <dvdplm@gmail.com> * address all review comments * fix typos * revert spec change * last touches * update docs * fmt * remove some debug_assertions * fmt Signed-off-by: Oliver Tale-Yazdi <oliver.tale-yazdi@parity.io> Co-authored-by: command-bot <> Co-authored-by: Oliver Tale-Yazdi <oliver.tale-yazdi@parity.io> Co-authored-by: David <dvdplm@gmail.com>
This commit is contained in:
@@ -88,7 +88,7 @@ fn main() {
|
||||
},
|
||||
}
|
||||
|
||||
assert!(BagsList::sanity_check().is_ok());
|
||||
assert!(BagsList::try_state().is_ok());
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
@@ -24,8 +24,8 @@ use sp_std::prelude::*;
|
||||
pub const LOG_TARGET: &str = "runtime::bags-list::remote-tests";
|
||||
|
||||
pub mod migration;
|
||||
pub mod sanity_check;
|
||||
pub mod snapshot;
|
||||
pub mod try_state;
|
||||
|
||||
/// A wrapper for a runtime that the functions of this crate expect.
|
||||
///
|
||||
|
||||
+1
-1
@@ -44,7 +44,7 @@ pub async fn execute<Runtime: crate::RuntimeT, Block: BlockT + DeserializeOwned>
|
||||
|
||||
ext.execute_with(|| {
|
||||
sp_core::crypto::set_default_ss58_version(Runtime::SS58Prefix::get().try_into().unwrap());
|
||||
pallet_bags_list::Pallet::<Runtime>::sanity_check().unwrap();
|
||||
pallet_bags_list::Pallet::<Runtime>::try_state().unwrap();
|
||||
log::info!(target: crate::LOG_TARGET, "executed bags-list sanity check with no errors.");
|
||||
|
||||
crate::display_and_check_bags::<Runtime>(currency_unit, currency_name);
|
||||
@@ -263,6 +263,11 @@ pub mod pallet {
|
||||
"thresholds must strictly increase, and have no duplicates",
|
||||
);
|
||||
}
|
||||
|
||||
#[cfg(feature = "try-runtime")]
|
||||
fn try_state(_: BlockNumberFor<T>) -> Result<(), &'static str> {
|
||||
<Self as SortedListProvider<T::AccountId>>::try_state()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -340,14 +345,8 @@ impl<T: Config<I>, I: 'static> SortedListProvider<T::AccountId> for Pallet<T, I>
|
||||
List::<T, I>::unsafe_regenerate(all, score_of)
|
||||
}
|
||||
|
||||
#[cfg(feature = "std")]
|
||||
fn sanity_check() -> Result<(), &'static str> {
|
||||
List::<T, I>::sanity_check()
|
||||
}
|
||||
|
||||
#[cfg(not(feature = "std"))]
|
||||
fn sanity_check() -> Result<(), &'static str> {
|
||||
Ok(())
|
||||
fn try_state() -> Result<(), &'static str> {
|
||||
List::<T, I>::try_state()
|
||||
}
|
||||
|
||||
fn unsafe_clear() {
|
||||
|
||||
@@ -28,8 +28,8 @@ use crate::Config;
|
||||
use codec::{Decode, Encode, MaxEncodedLen};
|
||||
use frame_election_provider_support::ScoreProvider;
|
||||
use frame_support::{
|
||||
ensure,
|
||||
traits::{Defensive, Get},
|
||||
defensive, ensure,
|
||||
traits::{Defensive, DefensiveOption, Get},
|
||||
DefaultNoBound, PalletError,
|
||||
};
|
||||
use scale_info::TypeInfo;
|
||||
@@ -220,7 +220,8 @@ impl<T: Config<I>, I: 'static> List<T, I> {
|
||||
crate::ListBags::<T, I>::remove(removed_bag);
|
||||
}
|
||||
|
||||
debug_assert_eq!(Self::sanity_check(), Ok(()));
|
||||
#[cfg(feature = "std")]
|
||||
debug_assert_eq!(Self::try_state(), Ok(()));
|
||||
|
||||
num_affected
|
||||
}
|
||||
@@ -325,8 +326,7 @@ impl<T: Config<I>, I: 'static> List<T, I> {
|
||||
|
||||
crate::log!(
|
||||
debug,
|
||||
"inserted {:?} with score {:?
|
||||
} into bag {:?}, new count is {}",
|
||||
"inserted {:?} with score {:?} into bag {:?}, new count is {}",
|
||||
id,
|
||||
score,
|
||||
bag_score,
|
||||
@@ -457,11 +457,8 @@ impl<T: Config<I>, I: 'static> List<T, I> {
|
||||
|
||||
// re-fetch `lighter_node` from storage since it may have been updated when `heavier_node`
|
||||
// was removed.
|
||||
let lighter_node = Node::<T, I>::get(lighter_id).ok_or_else(|| {
|
||||
debug_assert!(false, "id that should exist cannot be found");
|
||||
crate::log!(warn, "id that should exist cannot be found");
|
||||
ListError::NodeNotFound
|
||||
})?;
|
||||
let lighter_node =
|
||||
Node::<T, I>::get(lighter_id).defensive_ok_or_else(|| ListError::NodeNotFound)?;
|
||||
|
||||
// insert `heavier_node` directly in front of `lighter_node`. This will update both nodes
|
||||
// in storage and update the node counter.
|
||||
@@ -508,7 +505,7 @@ impl<T: Config<I>, I: 'static> List<T, I> {
|
||||
node.put();
|
||||
}
|
||||
|
||||
/// Sanity check the list.
|
||||
/// Check the internal state of the list.
|
||||
///
|
||||
/// This should be called from the call-site, whenever one of the mutating apis (e.g. `insert`)
|
||||
/// is being used, after all other staking data (such as counter) has been updated. It checks:
|
||||
@@ -517,8 +514,7 @@ impl<T: Config<I>, I: 'static> List<T, I> {
|
||||
/// * length of this list is in sync with `ListNodes::count()`,
|
||||
/// * and sanity-checks all bags and nodes. This will cascade down all the checks and makes sure
|
||||
/// all bags and nodes are checked per *any* update to `List`.
|
||||
#[cfg(feature = "std")]
|
||||
pub(crate) fn sanity_check() -> Result<(), &'static str> {
|
||||
pub(crate) fn try_state() -> Result<(), &'static str> {
|
||||
let mut seen_in_list = BTreeSet::new();
|
||||
ensure!(
|
||||
Self::iter().map(|node| node.id).all(|id| seen_in_list.insert(id)),
|
||||
@@ -546,7 +542,7 @@ impl<T: Config<I>, I: 'static> List<T, I> {
|
||||
thresholds.into_iter().filter_map(|t| Bag::<T, I>::get(t))
|
||||
};
|
||||
|
||||
let _ = active_bags.clone().try_for_each(|b| b.sanity_check())?;
|
||||
let _ = active_bags.clone().try_for_each(|b| b.try_state())?;
|
||||
|
||||
let nodes_in_bags_count =
|
||||
active_bags.clone().fold(0u32, |acc, cur| acc + cur.iter().count() as u32);
|
||||
@@ -557,17 +553,12 @@ impl<T: Config<I>, I: 'static> List<T, I> {
|
||||
// check that all nodes are sane. We check the `ListNodes` storage item directly in case we
|
||||
// have some "stale" nodes that are not in a bag.
|
||||
for (_id, node) in crate::ListNodes::<T, I>::iter() {
|
||||
node.sanity_check()?
|
||||
node.try_state()?
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[cfg(not(feature = "std"))]
|
||||
pub(crate) fn sanity_check() -> Result<(), &'static str> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Returns the nodes of all non-empty bags. For testing and benchmarks.
|
||||
#[cfg(any(feature = "std", feature = "runtime-benchmarks"))]
|
||||
#[allow(dead_code)]
|
||||
@@ -701,8 +692,7 @@ impl<T: Config<I>, I: 'static> Bag<T, I> {
|
||||
if *tail == node.id {
|
||||
// this should never happen, but this check prevents one path to a worst case
|
||||
// infinite loop.
|
||||
debug_assert!(false, "system logic error: inserting a node who has the id of tail");
|
||||
crate::log!(warn, "system logic error: inserting a node who has the id of tail");
|
||||
defensive!("system logic error: inserting a node who has the id of tail");
|
||||
return
|
||||
};
|
||||
}
|
||||
@@ -753,7 +743,7 @@ impl<T: Config<I>, I: 'static> Bag<T, I> {
|
||||
}
|
||||
}
|
||||
|
||||
/// Sanity check this bag.
|
||||
/// Check the internal state of the bag.
|
||||
///
|
||||
/// Should be called by the call-site, after any mutating operation on a bag. The call site of
|
||||
/// this struct is always `List`.
|
||||
@@ -761,8 +751,7 @@ impl<T: Config<I>, I: 'static> Bag<T, I> {
|
||||
/// * Ensures head has no prev.
|
||||
/// * Ensures tail has no next.
|
||||
/// * Ensures there are no loops, traversal from head to tail is correct.
|
||||
#[cfg(feature = "std")]
|
||||
fn sanity_check(&self) -> Result<(), &'static str> {
|
||||
fn try_state(&self) -> Result<(), &'static str> {
|
||||
frame_support::ensure!(
|
||||
self.head()
|
||||
.map(|head| head.prev().is_none())
|
||||
@@ -801,7 +790,6 @@ impl<T: Config<I>, I: 'static> Bag<T, I> {
|
||||
}
|
||||
|
||||
/// Check if the bag contains a node with `id`.
|
||||
#[cfg(feature = "std")]
|
||||
fn contains(&self, id: &T::AccountId) -> bool {
|
||||
self.iter().any(|n| n.id() == id)
|
||||
}
|
||||
@@ -906,8 +894,7 @@ impl<T: Config<I>, I: 'static> Node<T, I> {
|
||||
self.bag_upper
|
||||
}
|
||||
|
||||
#[cfg(feature = "std")]
|
||||
fn sanity_check(&self) -> Result<(), &'static str> {
|
||||
fn try_state(&self) -> Result<(), &'static str> {
|
||||
let expected_bag = Bag::<T, I>::get(self.bag_upper).ok_or("bag not found for node")?;
|
||||
|
||||
let id = self.id();
|
||||
|
||||
@@ -350,15 +350,15 @@ mod list {
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn sanity_check_works() {
|
||||
fn try_state_works() {
|
||||
ExtBuilder::default().build_and_execute_no_post_check(|| {
|
||||
assert_ok!(List::<Runtime>::sanity_check());
|
||||
assert_ok!(List::<Runtime>::try_state());
|
||||
});
|
||||
|
||||
// make sure there are no duplicates.
|
||||
ExtBuilder::default().build_and_execute_no_post_check(|| {
|
||||
Bag::<Runtime>::get(10).unwrap().insert_unchecked(2, 10);
|
||||
assert_eq!(List::<Runtime>::sanity_check(), Err("duplicate identified"));
|
||||
assert_eq!(List::<Runtime>::try_state(), Err("duplicate identified"));
|
||||
});
|
||||
|
||||
// ensure count is in sync with `ListNodes::count()`.
|
||||
@@ -372,7 +372,7 @@ mod list {
|
||||
CounterForListNodes::<Runtime>::mutate(|counter| *counter += 1);
|
||||
assert_eq!(crate::ListNodes::<Runtime>::count(), 5);
|
||||
|
||||
assert_eq!(List::<Runtime>::sanity_check(), Err("iter_count != stored_count"));
|
||||
assert_eq!(List::<Runtime>::try_state(), Err("iter_count != stored_count"));
|
||||
});
|
||||
}
|
||||
|
||||
@@ -804,7 +804,7 @@ mod bags {
|
||||
|
||||
// then
|
||||
assert_eq!(bag_as_ids(&bag_1000), vec![2, 3, 13, 14]);
|
||||
assert_ok!(bag_1000.sanity_check());
|
||||
assert_ok!(bag_1000.try_state());
|
||||
// and the node isn't mutated when its removed
|
||||
assert_eq!(node_4, node_4_pre_remove);
|
||||
|
||||
@@ -814,7 +814,7 @@ mod bags {
|
||||
|
||||
// then
|
||||
assert_eq!(bag_as_ids(&bag_1000), vec![3, 13, 14]);
|
||||
assert_ok!(bag_1000.sanity_check());
|
||||
assert_ok!(bag_1000.try_state());
|
||||
|
||||
// when removing a tail that is not pointing at the head
|
||||
let node_14 = Node::<Runtime>::get(&14).unwrap();
|
||||
@@ -822,7 +822,7 @@ mod bags {
|
||||
|
||||
// then
|
||||
assert_eq!(bag_as_ids(&bag_1000), vec![3, 13]);
|
||||
assert_ok!(bag_1000.sanity_check());
|
||||
assert_ok!(bag_1000.try_state());
|
||||
|
||||
// when removing a tail that is pointing at the head
|
||||
let node_13 = Node::<Runtime>::get(&13).unwrap();
|
||||
@@ -830,7 +830,7 @@ mod bags {
|
||||
|
||||
// then
|
||||
assert_eq!(bag_as_ids(&bag_1000), vec![3]);
|
||||
assert_ok!(bag_1000.sanity_check());
|
||||
assert_ok!(bag_1000.try_state());
|
||||
|
||||
// when removing a node that is both the head & tail
|
||||
let node_3 = Node::<Runtime>::get(&3).unwrap();
|
||||
@@ -846,7 +846,7 @@ mod bags {
|
||||
|
||||
// then
|
||||
assert_eq!(bag_as_ids(&bag_10), vec![1, 12]);
|
||||
assert_ok!(bag_10.sanity_check());
|
||||
assert_ok!(bag_10.try_state());
|
||||
|
||||
// when removing a head that is pointing at the tail
|
||||
let node_1 = Node::<Runtime>::get(&1).unwrap();
|
||||
@@ -854,7 +854,7 @@ mod bags {
|
||||
|
||||
// then
|
||||
assert_eq!(bag_as_ids(&bag_10), vec![12]);
|
||||
assert_ok!(bag_10.sanity_check());
|
||||
assert_ok!(bag_10.try_state());
|
||||
// and since we updated the bag's head/tail, we need to write this storage so we
|
||||
// can correctly `get` it again in later checks
|
||||
bag_10.put();
|
||||
@@ -865,7 +865,7 @@ mod bags {
|
||||
|
||||
// then
|
||||
assert_eq!(bag_as_ids(&bag_2000), vec![15, 17, 18, 19]);
|
||||
assert_ok!(bag_2000.sanity_check());
|
||||
assert_ok!(bag_2000.try_state());
|
||||
|
||||
// when removing a node that is pointing at tail, but not head
|
||||
let node_18 = Node::<Runtime>::get(&18).unwrap();
|
||||
@@ -873,7 +873,7 @@ mod bags {
|
||||
|
||||
// then
|
||||
assert_eq!(bag_as_ids(&bag_2000), vec![15, 17, 19]);
|
||||
assert_ok!(bag_2000.sanity_check());
|
||||
assert_ok!(bag_2000.try_state());
|
||||
|
||||
// finally, when reading from storage, the state of all bags is as expected
|
||||
assert_eq!(
|
||||
@@ -905,7 +905,7 @@ mod bags {
|
||||
// .. and the bag it was removed from
|
||||
let bag_1000 = Bag::<Runtime>::get(1_000).unwrap();
|
||||
// is sane
|
||||
assert_ok!(bag_1000.sanity_check());
|
||||
assert_ok!(bag_1000.try_state());
|
||||
// and has the correct head and tail.
|
||||
assert_eq!(bag_1000.head, Some(3));
|
||||
assert_eq!(bag_1000.tail, Some(4));
|
||||
|
||||
@@ -147,7 +147,7 @@ impl ExtBuilder {
|
||||
pub fn build_and_execute(self, test: impl FnOnce() -> ()) {
|
||||
self.build().execute_with(|| {
|
||||
test();
|
||||
List::<Runtime>::sanity_check().expect("Sanity check post condition failed")
|
||||
List::<Runtime>::try_state().expect("Try-state post condition failed")
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
@@ -50,3 +50,4 @@ std = [
|
||||
"sp-runtime/std",
|
||||
"sp-std/std",
|
||||
]
|
||||
try-runtime = ["frame-support/try-runtime"]
|
||||
|
||||
@@ -37,3 +37,4 @@ std = [
|
||||
"sp-runtime/std",
|
||||
"sp-std/std",
|
||||
]
|
||||
try-runtime = ["frame-support/try-runtime"]
|
||||
|
||||
@@ -513,8 +513,8 @@ pub trait SortedListProvider<AccountId> {
|
||||
/// unbounded amount of storage accesses.
|
||||
fn unsafe_clear();
|
||||
|
||||
/// Sanity check internal state of list. Only meant for debug compilation.
|
||||
fn sanity_check() -> Result<(), &'static str>;
|
||||
/// Check internal state of list. Only meant for debugging.
|
||||
fn try_state() -> Result<(), &'static str>;
|
||||
|
||||
/// If `who` changes by the returned amount they are guaranteed to have a worst case change
|
||||
/// in their list position.
|
||||
|
||||
@@ -19,6 +19,7 @@ codec = { package = "parity-scale-codec", version = "3.0.0", default-features =
|
||||
scale-info = { version = "2.1.1", default-features = false, features = ["derive"] }
|
||||
frame-support = { version = "4.0.0-dev", default-features = false, path = "../support" }
|
||||
frame-system = { version = "4.0.0-dev", default-features = false, path = "../system" }
|
||||
frame-try-runtime = { version = "0.10.0-dev", default-features = false, path = "../try-runtime", optional = true }
|
||||
sp-core = { version = "6.0.0", default-features = false, path = "../../primitives/core" }
|
||||
sp-io = { version = "6.0.0", default-features = false, path = "../../primitives/io" }
|
||||
sp-runtime = { version = "6.0.0", default-features = false, path = "../../primitives/runtime" }
|
||||
@@ -48,4 +49,4 @@ std = [
|
||||
"sp-std/std",
|
||||
"sp-tracing/std",
|
||||
]
|
||||
try-runtime = ["frame-support/try-runtime"]
|
||||
try-runtime = ["frame-support/try-runtime", "frame-try-runtime" ]
|
||||
|
||||
@@ -202,6 +202,99 @@ where
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "try-runtime")]
|
||||
impl<
|
||||
System: frame_system::Config + EnsureInherentsAreFirst<Block>,
|
||||
Block: traits::Block<Header = System::Header, Hash = System::Hash>,
|
||||
Context: Default,
|
||||
UnsignedValidator,
|
||||
AllPalletsWithSystem: OnRuntimeUpgrade
|
||||
+ OnInitialize<System::BlockNumber>
|
||||
+ OnIdle<System::BlockNumber>
|
||||
+ OnFinalize<System::BlockNumber>
|
||||
+ OffchainWorker<System::BlockNumber>
|
||||
+ frame_support::traits::TryState<System::BlockNumber>,
|
||||
COnRuntimeUpgrade: OnRuntimeUpgrade,
|
||||
> Executive<System, Block, Context, UnsignedValidator, AllPalletsWithSystem, COnRuntimeUpgrade>
|
||||
where
|
||||
Block::Extrinsic: Checkable<Context> + Codec,
|
||||
CheckedOf<Block::Extrinsic, Context>: Applyable + GetDispatchInfo,
|
||||
CallOf<Block::Extrinsic, Context>:
|
||||
Dispatchable<Info = DispatchInfo, PostInfo = PostDispatchInfo>,
|
||||
OriginOf<Block::Extrinsic, Context>: From<Option<System::AccountId>>,
|
||||
UnsignedValidator: ValidateUnsigned<Call = CallOf<Block::Extrinsic, Context>>,
|
||||
{
|
||||
/// Execute given block, but don't as strict is the normal block execution.
|
||||
///
|
||||
/// Some consensus related checks such as the state root check can be switched off via
|
||||
/// `try_state_root`. Some additional non-consensus checks can be additionally enabled via
|
||||
/// `try_state`.
|
||||
///
|
||||
/// Should only be used for testing ONLY.
|
||||
pub fn try_execute_block(
|
||||
block: Block,
|
||||
try_state_root: bool,
|
||||
select: frame_try_runtime::TryStateSelect,
|
||||
) -> Result<frame_support::weights::Weight, &'static str> {
|
||||
use frame_support::traits::TryState;
|
||||
|
||||
Self::initialize_block(block.header());
|
||||
Self::initial_checks(&block);
|
||||
|
||||
let (header, extrinsics) = block.deconstruct();
|
||||
|
||||
Self::execute_extrinsics_with_book_keeping(extrinsics, *header.number());
|
||||
|
||||
// run the try-state checks of all pallets.
|
||||
<AllPalletsWithSystem as TryState<System::BlockNumber>>::try_state(
|
||||
*header.number(),
|
||||
select,
|
||||
)
|
||||
.map_err(|e| {
|
||||
frame_support::log::error!(target: "runtime::executive", "failure: {:?}", e);
|
||||
e
|
||||
})?;
|
||||
|
||||
// do some of the checks that would normally happen in `final_checks`, but perhaps skip
|
||||
// the state root check.
|
||||
{
|
||||
let new_header = <frame_system::Pallet<System>>::finalize();
|
||||
let items_zip = header.digest().logs().iter().zip(new_header.digest().logs().iter());
|
||||
for (header_item, computed_item) in items_zip {
|
||||
header_item.check_equal(computed_item);
|
||||
assert!(header_item == computed_item, "Digest item must match that calculated.");
|
||||
}
|
||||
|
||||
if try_state_root {
|
||||
let storage_root = new_header.state_root();
|
||||
header.state_root().check_equal(storage_root);
|
||||
assert!(
|
||||
header.state_root() == storage_root,
|
||||
"Storage root must match that calculated."
|
||||
);
|
||||
}
|
||||
|
||||
assert!(
|
||||
header.extrinsics_root() == new_header.extrinsics_root(),
|
||||
"Transaction trie root must be valid.",
|
||||
);
|
||||
}
|
||||
|
||||
Ok(frame_system::Pallet::<System>::block_weight().total())
|
||||
}
|
||||
|
||||
/// Execute all `OnRuntimeUpgrade` of this runtime, including the pre and post migration checks.
|
||||
///
|
||||
/// This should only be used for testing.
|
||||
pub fn try_runtime_upgrade() -> Result<frame_support::weights::Weight, &'static str> {
|
||||
<(COnRuntimeUpgrade, AllPalletsWithSystem) as OnRuntimeUpgrade>::pre_upgrade().unwrap();
|
||||
let weight = Self::execute_on_runtime_upgrade();
|
||||
<(COnRuntimeUpgrade, AllPalletsWithSystem) as OnRuntimeUpgrade>::post_upgrade().unwrap();
|
||||
|
||||
Ok(weight)
|
||||
}
|
||||
}
|
||||
|
||||
impl<
|
||||
System: frame_system::Config + EnsureInherentsAreFirst<Block>,
|
||||
Block: traits::Block<Header = System::Header, Hash = System::Hash>,
|
||||
@@ -227,50 +320,6 @@ where
|
||||
<(COnRuntimeUpgrade, AllPalletsWithSystem) as OnRuntimeUpgrade>::on_runtime_upgrade()
|
||||
}
|
||||
|
||||
/// Execute given block, but don't do any of the `final_checks`.
|
||||
///
|
||||
/// Should only be used for testing.
|
||||
#[cfg(feature = "try-runtime")]
|
||||
pub fn execute_block_no_check(block: Block) -> frame_support::weights::Weight {
|
||||
Self::initialize_block(block.header());
|
||||
Self::initial_checks(&block);
|
||||
|
||||
let (header, extrinsics) = block.deconstruct();
|
||||
|
||||
Self::execute_extrinsics_with_book_keeping(extrinsics, *header.number());
|
||||
|
||||
// do some of the checks that would normally happen in `final_checks`, but definitely skip
|
||||
// the state root check.
|
||||
{
|
||||
let new_header = <frame_system::Pallet<System>>::finalize();
|
||||
let items_zip = header.digest().logs().iter().zip(new_header.digest().logs().iter());
|
||||
for (header_item, computed_item) in items_zip {
|
||||
header_item.check_equal(computed_item);
|
||||
assert!(header_item == computed_item, "Digest item must match that calculated.");
|
||||
}
|
||||
|
||||
assert!(
|
||||
header.extrinsics_root() == new_header.extrinsics_root(),
|
||||
"Transaction trie root must be valid.",
|
||||
);
|
||||
}
|
||||
|
||||
frame_system::Pallet::<System>::block_weight().total()
|
||||
}
|
||||
|
||||
/// Execute all `OnRuntimeUpgrade` of this runtime, including the pre and post migration checks.
|
||||
///
|
||||
/// This should only be used for testing.
|
||||
#[cfg(feature = "try-runtime")]
|
||||
pub fn try_runtime_upgrade() -> Result<frame_support::weights::Weight, &'static str> {
|
||||
<(COnRuntimeUpgrade, AllPalletsWithSystem) as OnRuntimeUpgrade>::pre_upgrade().unwrap();
|
||||
let weight = Self::execute_on_runtime_upgrade();
|
||||
|
||||
<(COnRuntimeUpgrade, AllPalletsWithSystem) as OnRuntimeUpgrade>::post_upgrade().unwrap();
|
||||
|
||||
Ok(weight)
|
||||
}
|
||||
|
||||
/// Start the execution of a particular block.
|
||||
pub fn initialize_block(header: &System::Header) {
|
||||
sp_io::init_tracing();
|
||||
|
||||
@@ -32,7 +32,7 @@ sp-tracing = { version = "5.0.0", path = "../../primitives/tracing" }
|
||||
|
||||
[features]
|
||||
runtime-benchmarks = []
|
||||
try-runtime = []
|
||||
try-runtime = [ "frame-support/try-runtime" ]
|
||||
default = ["std"]
|
||||
std = [
|
||||
"codec/std",
|
||||
|
||||
@@ -201,11 +201,11 @@ impl<T: Config> ListScenario<T> {
|
||||
|
||||
Pools::<T>::join(Origin::Signed(joiner.clone()).into(), amount, 1).unwrap();
|
||||
|
||||
// Sanity check that the vote weight is still the same as the original bonded
|
||||
// check that the vote weight is still the same as the original bonded
|
||||
let weight_of = pallet_staking::Pallet::<T>::weight_of_fn();
|
||||
assert_eq!(vote_to_balance::<T>(weight_of(&self.origin1)).unwrap(), original_bonded);
|
||||
|
||||
// Sanity check the member was added correctly
|
||||
// check the member was added correctly
|
||||
let member = PoolMembers::<T>::get(&joiner).unwrap();
|
||||
assert_eq!(member.points, amount);
|
||||
assert_eq!(member.pool_id, 1);
|
||||
|
||||
@@ -1110,7 +1110,7 @@ impl<T: Config> SubPools<T> {
|
||||
}
|
||||
|
||||
/// The sum of all unbonding balance, regardless of whether they are actually unlocked or not.
|
||||
#[cfg(any(test, debug_assertions))]
|
||||
#[cfg(any(feature = "try-runtime", test, debug_assertions))]
|
||||
fn sum_unbonding_balance(&self) -> BalanceOf<T> {
|
||||
self.no_era.balance.saturating_add(
|
||||
self.with_era
|
||||
@@ -2138,6 +2138,11 @@ pub mod pallet {
|
||||
|
||||
#[pallet::hooks]
|
||||
impl<T: Config> Hooks<BlockNumberFor<T>> for Pallet<T> {
|
||||
#[cfg(feature = "try-runtime")]
|
||||
fn try_state(_n: BlockNumberFor<T>) -> Result<(), &'static str> {
|
||||
Self::do_try_state(u8::MAX)
|
||||
}
|
||||
|
||||
fn integrity_test() {
|
||||
assert!(
|
||||
T::MaxPointsToBalance::get() > 0,
|
||||
@@ -2389,9 +2394,9 @@ impl<T: Config> Pallet<T> {
|
||||
///
|
||||
/// To cater for tests that want to escape parts of these checks, this function is split into
|
||||
/// multiple `level`s, where the higher the level, the more checks we performs. So,
|
||||
/// `sanity_check(255)` is the strongest sanity check, and `0` performs no checks.
|
||||
#[cfg(any(test, debug_assertions))]
|
||||
pub fn sanity_checks(level: u8) -> Result<(), &'static str> {
|
||||
/// `try_state(255)` is the strongest sanity check, and `0` performs no checks.
|
||||
#[cfg(any(feature = "try-runtime", test, debug_assertions))]
|
||||
pub fn do_try_state(level: u8) -> Result<(), &'static str> {
|
||||
if level.is_zero() {
|
||||
return Ok(())
|
||||
}
|
||||
@@ -2401,7 +2406,8 @@ impl<T: Config> Pallet<T> {
|
||||
let reward_pools = RewardPools::<T>::iter_keys().collect::<Vec<_>>();
|
||||
assert_eq!(bonded_pools, reward_pools);
|
||||
|
||||
assert!(Metadata::<T>::iter_keys().all(|k| bonded_pools.contains(&k)));
|
||||
// TODO: can't check this right now: https://github.com/paritytech/substrate/issues/12077
|
||||
// assert!(Metadata::<T>::iter_keys().all(|k| bonded_pools.contains(&k)));
|
||||
assert!(SubPoolsStorage::<T>::iter_keys().all(|k| bonded_pools.contains(&k)));
|
||||
|
||||
assert!(MaxPools::<T>::get().map_or(true, |max| bonded_pools.len() <= (max as usize)));
|
||||
|
||||
@@ -304,7 +304,7 @@ impl ExtBuilder {
|
||||
pub fn build_and_execute(self, test: impl FnOnce() -> ()) {
|
||||
self.build().execute_with(|| {
|
||||
test();
|
||||
Pools::sanity_checks(CheckLevel::get()).unwrap();
|
||||
Pools::do_try_state(CheckLevel::get()).unwrap();
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -41,3 +41,4 @@ std = [
|
||||
"sp-runtime/std",
|
||||
"sp-std/std",
|
||||
]
|
||||
try-runtime = [ "frame-support/try-runtime" ]
|
||||
|
||||
@@ -153,7 +153,7 @@ pub mod v8 {
|
||||
Nominators::<T>::iter().map(|(id, _)| id),
|
||||
Pallet::<T>::weight_of_fn(),
|
||||
);
|
||||
debug_assert_eq!(T::VoterList::sanity_check(), Ok(()));
|
||||
debug_assert_eq!(T::VoterList::try_state(), Ok(()));
|
||||
|
||||
StorageVersion::<T>::put(crate::Releases::V8_0_0);
|
||||
crate::log!(
|
||||
@@ -170,7 +170,7 @@ pub mod v8 {
|
||||
|
||||
#[cfg(feature = "try-runtime")]
|
||||
pub fn post_migrate<T: Config>() -> Result<(), &'static str> {
|
||||
T::VoterList::sanity_check().map_err(|_| "VoterList is not in a sane state.")?;
|
||||
T::VoterList::try_state().map_err(|_| "VoterList is not in a sane state.")?;
|
||||
crate::log!(info, "👜 staking bags-list migration passes POST migrate checks ✅",);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -789,7 +789,6 @@ impl<T: Config> Pallet<T> {
|
||||
Nominators::<T>::count() + Validators::<T>::count(),
|
||||
T::VoterList::count()
|
||||
);
|
||||
debug_assert_eq!(T::VoterList::sanity_check(), Ok(()));
|
||||
}
|
||||
|
||||
/// This function will remove a nominator from the `Nominators` storage map,
|
||||
@@ -809,7 +808,6 @@ impl<T: Config> Pallet<T> {
|
||||
false
|
||||
};
|
||||
|
||||
debug_assert_eq!(T::VoterList::sanity_check(), Ok(()));
|
||||
debug_assert_eq!(
|
||||
Nominators::<T>::count() + Validators::<T>::count(),
|
||||
T::VoterList::count()
|
||||
@@ -837,7 +835,6 @@ impl<T: Config> Pallet<T> {
|
||||
Nominators::<T>::count() + Validators::<T>::count(),
|
||||
T::VoterList::count()
|
||||
);
|
||||
debug_assert_eq!(T::VoterList::sanity_check(), Ok(()));
|
||||
}
|
||||
|
||||
/// This function will remove a validator from the `Validators` storage map.
|
||||
@@ -856,7 +853,6 @@ impl<T: Config> Pallet<T> {
|
||||
false
|
||||
};
|
||||
|
||||
debug_assert_eq!(T::VoterList::sanity_check(), Ok(()));
|
||||
debug_assert_eq!(
|
||||
Nominators::<T>::count() + Validators::<T>::count(),
|
||||
T::VoterList::count()
|
||||
@@ -1369,7 +1365,7 @@ impl<T: Config> SortedListProvider<T::AccountId> for UseNominatorsAndValidatorsM
|
||||
// nothing to do upon regenerate.
|
||||
0
|
||||
}
|
||||
fn sanity_check() -> Result<(), &'static str> {
|
||||
fn try_state() -> Result<(), &'static str> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -1452,3 +1448,104 @@ impl<T: Config> StakingInterface for Pallet<T> {
|
||||
Nominators::<T>::get(who).map(|n| n.targets.into_inner())
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "try-runtime")]
|
||||
impl<T: Config> Pallet<T> {
|
||||
pub(crate) fn do_try_state(_: BlockNumberFor<T>) -> Result<(), &'static str> {
|
||||
T::VoterList::try_state()?;
|
||||
Self::check_nominators()?;
|
||||
Self::check_exposures()?;
|
||||
Self::check_ledgers()?;
|
||||
Self::check_count()
|
||||
}
|
||||
|
||||
fn check_count() -> Result<(), &'static str> {
|
||||
ensure!(
|
||||
<T as Config>::VoterList::count() ==
|
||||
Nominators::<T>::count() + Validators::<T>::count(),
|
||||
"wrong external count"
|
||||
);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn check_ledgers() -> Result<(), &'static str> {
|
||||
Bonded::<T>::iter()
|
||||
.map(|(_, ctrl)| Self::ensure_ledger_consistent(ctrl))
|
||||
.collect::<Result<_, _>>()
|
||||
}
|
||||
|
||||
fn check_exposures() -> Result<(), &'static str> {
|
||||
// a check per validator to ensure the exposure struct is always sane.
|
||||
let era = Self::active_era().unwrap().index;
|
||||
ErasStakers::<T>::iter_prefix_values(era)
|
||||
.map(|expo| {
|
||||
ensure!(
|
||||
expo.total ==
|
||||
expo.own +
|
||||
expo.others
|
||||
.iter()
|
||||
.map(|e| e.value)
|
||||
.fold(Zero::zero(), |acc, x| acc + x),
|
||||
"wrong total exposure.",
|
||||
);
|
||||
Ok(())
|
||||
})
|
||||
.collect::<Result<_, _>>()
|
||||
}
|
||||
|
||||
fn check_nominators() -> Result<(), &'static str> {
|
||||
// a check per nominator to ensure their entire stake is correctly distributed. Will only
|
||||
// kick-in if the nomination was submitted before the current era.
|
||||
let era = Self::active_era().unwrap().index;
|
||||
<Nominators<T>>::iter()
|
||||
.filter_map(
|
||||
|(nominator, nomination)| {
|
||||
if nomination.submitted_in > era {
|
||||
Some(nominator)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
},
|
||||
)
|
||||
.map(|nominator| {
|
||||
// must be bonded.
|
||||
Self::ensure_is_stash(&nominator)?;
|
||||
let mut sum = BalanceOf::<T>::zero();
|
||||
T::SessionInterface::validators()
|
||||
.iter()
|
||||
.map(|v| Self::eras_stakers(era, v))
|
||||
.map(|e| {
|
||||
let individual =
|
||||
e.others.iter().filter(|e| e.who == nominator).collect::<Vec<_>>();
|
||||
let len = individual.len();
|
||||
match len {
|
||||
0 => { /* not supporting this validator at all. */ },
|
||||
1 => sum += individual[0].value,
|
||||
_ => return Err("nominator cannot back a validator more than once."),
|
||||
};
|
||||
Ok(())
|
||||
})
|
||||
.collect::<Result<_, _>>()
|
||||
})
|
||||
.collect::<Result<_, _>>()
|
||||
}
|
||||
|
||||
fn ensure_is_stash(who: &T::AccountId) -> Result<(), &'static str> {
|
||||
ensure!(Self::bonded(who).is_some(), "Not a stash.");
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn ensure_ledger_consistent(ctrl: T::AccountId) -> Result<(), &'static str> {
|
||||
// ensures ledger.total == ledger.active + sum(ledger.unlocking).
|
||||
let ledger = Self::ledger(ctrl.clone()).ok_or("Not a controller.")?;
|
||||
let real_total: BalanceOf<T> =
|
||||
ledger.unlocking.iter().fold(ledger.active, |a, c| a + c.value);
|
||||
ensure!(real_total == ledger.total, "ledger.total corrupt");
|
||||
|
||||
if !(ledger.active >= T::Currency::minimum_balance() || ledger.active.is_zero()) {
|
||||
log!(warn, "ledger.active less than ED: {:?}, {:?}", ctrl, ledger)
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -744,6 +744,11 @@ pub mod pallet {
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "try-runtime")]
|
||||
fn try_state(n: BlockNumberFor<T>) -> Result<(), &'static str> {
|
||||
Self::do_try_state(n)
|
||||
}
|
||||
}
|
||||
|
||||
#[pallet::call]
|
||||
@@ -856,7 +861,6 @@ pub mod pallet {
|
||||
if T::VoterList::contains(&stash) {
|
||||
let _ =
|
||||
T::VoterList::on_update(&stash, Self::weight_of(&ledger.stash)).defensive();
|
||||
debug_assert_eq!(T::VoterList::sanity_check(), Ok(()));
|
||||
}
|
||||
|
||||
Self::deposit_event(Event::<T>::Bonded(stash, extra));
|
||||
|
||||
@@ -17,7 +17,6 @@
|
||||
|
||||
use crate::pallet::Def;
|
||||
|
||||
///
|
||||
/// * implement the individual traits using the Hooks trait
|
||||
pub fn expand_hooks(def: &mut Def) -> proc_macro2::TokenStream {
|
||||
let (where_clause, span, has_runtime_upgrade) = match def.hooks.as_ref() {
|
||||
@@ -59,6 +58,19 @@ pub fn expand_hooks(def: &mut Def) -> proc_macro2::TokenStream {
|
||||
}
|
||||
};
|
||||
|
||||
let log_try_state = quote::quote! {
|
||||
let pallet_name = <
|
||||
<T as #frame_system::Config>::PalletInfo
|
||||
as
|
||||
#frame_support::traits::PalletInfo
|
||||
>::name::<Self>().expect("Every active pallet has a name in the runtime; qed");
|
||||
#frame_support::log::debug!(
|
||||
target: #frame_support::LOG_TARGET,
|
||||
"🩺 try-state pallet {:?}",
|
||||
pallet_name,
|
||||
);
|
||||
};
|
||||
|
||||
let hooks_impl = if def.hooks.is_none() {
|
||||
let frame_system = &def.frame_system;
|
||||
quote::quote! {
|
||||
@@ -191,5 +203,23 @@ pub fn expand_hooks(def: &mut Def) -> proc_macro2::TokenStream {
|
||||
>::integrity_test()
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "try-runtime")]
|
||||
impl<#type_impl_gen>
|
||||
#frame_support::traits::TryState<<T as #frame_system::Config>::BlockNumber>
|
||||
for #pallet_ident<#type_use_gen> #where_clause
|
||||
{
|
||||
fn try_state(
|
||||
n: <T as #frame_system::Config>::BlockNumber,
|
||||
_s: #frame_support::traits::TryStateSelect
|
||||
) -> Result<(), &'static str> {
|
||||
#log_try_state
|
||||
<
|
||||
Self as #frame_support::traits::Hooks<
|
||||
<T as #frame_system::Config>::BlockNumber
|
||||
>
|
||||
>::try_state(n)
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
@@ -1549,6 +1549,35 @@ macro_rules! decl_module {
|
||||
{}
|
||||
};
|
||||
|
||||
(@impl_try_state_default
|
||||
{ $system:ident }
|
||||
$module:ident<$trait_instance:ident: $trait_name:ident$(<I>, $instance:ident: $instantiable:path)?>;
|
||||
{ $( $other_where_bounds:tt )* }
|
||||
) => {
|
||||
#[cfg(feature = "try-runtime")]
|
||||
impl<$trait_instance: $system::Config + $trait_name$(<I>, $instance: $instantiable)?>
|
||||
$crate::traits::TryState<<$trait_instance as $system::Config>::BlockNumber>
|
||||
for $module<$trait_instance$(, $instance)?> where $( $other_where_bounds )*
|
||||
{
|
||||
fn try_state(
|
||||
_: <$trait_instance as $system::Config>::BlockNumber,
|
||||
_: $crate::traits::TryStateSelect,
|
||||
) -> Result<(), &'static str> {
|
||||
let pallet_name = <<
|
||||
$trait_instance
|
||||
as
|
||||
$system::Config
|
||||
>::PalletInfo as $crate::traits::PalletInfo>::name::<Self>().unwrap_or("<unknown pallet name>");
|
||||
$crate::log::debug!(
|
||||
target: $crate::LOG_TARGET,
|
||||
"⚠️ pallet {} cannot have try-state because it is using decl_module!",
|
||||
pallet_name,
|
||||
);
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
(@impl_on_runtime_upgrade
|
||||
{ $system:ident }
|
||||
$module:ident<$trait_instance:ident: $trait_name:ident$(<I>, $instance:ident: $instantiable:path)?>;
|
||||
@@ -2026,6 +2055,13 @@ macro_rules! decl_module {
|
||||
$( $on_initialize )*
|
||||
}
|
||||
|
||||
$crate::decl_module! {
|
||||
@impl_try_state_default
|
||||
{ $system }
|
||||
$mod_type<$trait_instance: $trait_name $(<I>, $instance: $instantiable)?>;
|
||||
{ $( $other_where_bounds )* }
|
||||
}
|
||||
|
||||
$crate::decl_module! {
|
||||
@impl_on_runtime_upgrade
|
||||
{ $system }
|
||||
|
||||
@@ -84,8 +84,6 @@ pub use hooks::{
|
||||
Hooks, IntegrityTest, OnFinalize, OnGenesis, OnIdle, OnInitialize, OnRuntimeUpgrade,
|
||||
OnTimestampSet,
|
||||
};
|
||||
#[cfg(feature = "try-runtime")]
|
||||
pub use hooks::{OnRuntimeUpgradeHelpersExt, ON_RUNTIME_UPGRADE_PREFIX};
|
||||
|
||||
pub mod schedule;
|
||||
mod storage;
|
||||
@@ -106,3 +104,8 @@ pub use voting::{
|
||||
ClassCountOf, CurrencyToVote, PollStatus, Polling, SaturatingCurrencyToVote,
|
||||
U128CurrencyToVote, VoteTally,
|
||||
};
|
||||
|
||||
#[cfg(feature = "try-runtime")]
|
||||
mod try_runtime;
|
||||
#[cfg(feature = "try-runtime")]
|
||||
pub use try_runtime::{OnRuntimeUpgradeHelpersExt, Select as TryStateSelect, TryState};
|
||||
|
||||
@@ -20,6 +20,7 @@
|
||||
use crate::weights::Weight;
|
||||
use impl_trait_for_tuples::impl_for_tuples;
|
||||
use sp_runtime::traits::AtLeast32BitUnsigned;
|
||||
use sp_std::prelude::*;
|
||||
|
||||
/// The block initialization trait.
|
||||
///
|
||||
@@ -93,9 +94,9 @@ impl<BlockNumber: Copy + AtLeast32BitUnsigned> OnIdle<BlockNumber> for Tuple {
|
||||
let start_index = start_index.try_into().ok().expect(
|
||||
"`start_index % len` always fits into `usize`, because `len` can be in maximum `usize::MAX`; qed"
|
||||
);
|
||||
for on_idle in on_idle_functions.iter().cycle().skip(start_index).take(len) {
|
||||
for on_idle_fn in on_idle_functions.iter().cycle().skip(start_index).take(len) {
|
||||
let adjusted_remaining_weight = remaining_weight.saturating_sub(weight);
|
||||
weight = weight.saturating_add(on_idle(n, adjusted_remaining_weight));
|
||||
weight = weight.saturating_add(on_idle_fn(n, adjusted_remaining_weight));
|
||||
}
|
||||
weight
|
||||
}
|
||||
@@ -114,47 +115,6 @@ pub trait OnGenesis {
|
||||
fn on_genesis() {}
|
||||
}
|
||||
|
||||
/// Prefix to be used (optionally) for implementing [`OnRuntimeUpgradeHelpersExt::storage_key`].
|
||||
#[cfg(feature = "try-runtime")]
|
||||
pub const ON_RUNTIME_UPGRADE_PREFIX: &[u8] = b"__ON_RUNTIME_UPGRADE__";
|
||||
|
||||
/// Some helper functions for [`OnRuntimeUpgrade`] during `try-runtime` testing.
|
||||
#[cfg(feature = "try-runtime")]
|
||||
pub trait OnRuntimeUpgradeHelpersExt {
|
||||
/// Generate a storage key unique to this runtime upgrade.
|
||||
///
|
||||
/// This can be used to communicate data from pre-upgrade to post-upgrade state and check
|
||||
/// them. See [`Self::set_temp_storage`] and [`Self::get_temp_storage`].
|
||||
#[cfg(feature = "try-runtime")]
|
||||
fn storage_key(ident: &str) -> [u8; 32] {
|
||||
crate::storage::storage_prefix(ON_RUNTIME_UPGRADE_PREFIX, ident.as_bytes())
|
||||
}
|
||||
|
||||
/// Get temporary storage data written by [`Self::set_temp_storage`].
|
||||
///
|
||||
/// Returns `None` if either the data is unavailable or un-decodable.
|
||||
///
|
||||
/// A `at` storage identifier must be provided to indicate where the storage is being read from.
|
||||
#[cfg(feature = "try-runtime")]
|
||||
fn get_temp_storage<T: codec::Decode>(at: &str) -> Option<T> {
|
||||
sp_io::storage::get(&Self::storage_key(at))
|
||||
.and_then(|bytes| codec::Decode::decode(&mut &*bytes).ok())
|
||||
}
|
||||
|
||||
/// Write some temporary data to a specific storage that can be read (potentially in
|
||||
/// post-upgrade hook) via [`Self::get_temp_storage`].
|
||||
///
|
||||
/// A `at` storage identifier must be provided to indicate where the storage is being written
|
||||
/// to.
|
||||
#[cfg(feature = "try-runtime")]
|
||||
fn set_temp_storage<T: codec::Encode>(data: T, at: &str) {
|
||||
sp_io::storage::set(&Self::storage_key(at), &data.encode());
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "try-runtime")]
|
||||
impl<U: OnRuntimeUpgrade> OnRuntimeUpgradeHelpersExt for U {}
|
||||
|
||||
/// The runtime upgrade trait.
|
||||
///
|
||||
/// Implementing this lets you express what should happen when the runtime upgrades,
|
||||
@@ -272,6 +232,15 @@ pub trait Hooks<BlockNumber> {
|
||||
Weight::new()
|
||||
}
|
||||
|
||||
/// Execute the sanity checks of this pallet, per block.
|
||||
///
|
||||
/// It should focus on certain checks to ensure that the state is sensible. This is never
|
||||
/// executed in a consensus code-path, therefore it can consume as much weight as it needs.
|
||||
#[cfg(feature = "try-runtime")]
|
||||
fn try_state(_n: BlockNumber) -> Result<(), &'static str> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Execute some pre-checks prior to a runtime upgrade.
|
||||
///
|
||||
/// This hook is never meant to be executed on-chain but is meant to be used by testing tools.
|
||||
|
||||
@@ -0,0 +1,174 @@
|
||||
// This file is part of Substrate.
|
||||
|
||||
// Copyright (C) 2022 Parity Technologies (UK) Ltd.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
//! Try-runtime specific traits and types.
|
||||
|
||||
use super::*;
|
||||
use impl_trait_for_tuples::impl_for_tuples;
|
||||
use sp_arithmetic::traits::AtLeast32BitUnsigned;
|
||||
use sp_std::prelude::*;
|
||||
|
||||
/// Prefix to be used (optionally) for implementing [`OnRuntimeUpgradeHelpersExt::storage_key`].
|
||||
const ON_RUNTIME_UPGRADE_PREFIX: &[u8] = b"__ON_RUNTIME_UPGRADE__";
|
||||
|
||||
/// Some helper functions for [`OnRuntimeUpgrade`] during `try-runtime` testing.
|
||||
pub trait OnRuntimeUpgradeHelpersExt {
|
||||
/// Generate a storage key unique to this runtime upgrade.
|
||||
///
|
||||
/// This can be used to communicate data from pre-upgrade to post-upgrade state and check
|
||||
/// them. See [`Self::set_temp_storage`] and [`Self::get_temp_storage`].
|
||||
fn storage_key(ident: &str) -> [u8; 32] {
|
||||
crate::storage::storage_prefix(ON_RUNTIME_UPGRADE_PREFIX, ident.as_bytes())
|
||||
}
|
||||
|
||||
/// Get temporary storage data written by [`Self::set_temp_storage`].
|
||||
///
|
||||
/// Returns `None` if either the data is unavailable or un-decodable.
|
||||
///
|
||||
/// A `at` storage identifier must be provided to indicate where the storage is being read from.
|
||||
fn get_temp_storage<T: codec::Decode>(at: &str) -> Option<T> {
|
||||
sp_io::storage::get(&Self::storage_key(at))
|
||||
.and_then(|bytes| codec::Decode::decode(&mut &*bytes).ok())
|
||||
}
|
||||
|
||||
/// Write some temporary data to a specific storage that can be read (potentially in
|
||||
/// post-upgrade hook) via [`Self::get_temp_storage`].
|
||||
///
|
||||
/// A `at` storage identifier must be provided to indicate where the storage is being written
|
||||
/// to.
|
||||
fn set_temp_storage<T: codec::Encode>(data: T, at: &str) {
|
||||
sp_io::storage::set(&Self::storage_key(at), &data.encode());
|
||||
}
|
||||
}
|
||||
|
||||
impl<U: OnRuntimeUpgrade> OnRuntimeUpgradeHelpersExt for U {}
|
||||
|
||||
// Which state tests to execute.
|
||||
#[derive(codec::Encode, codec::Decode, Clone)]
|
||||
pub enum Select {
|
||||
/// None of them.
|
||||
None,
|
||||
/// All of them.
|
||||
All,
|
||||
/// Run a fixed number of them in a round robin manner.
|
||||
RoundRobin(u32),
|
||||
/// Run only pallets who's name matches the given list.
|
||||
///
|
||||
/// Pallet names are obtained from [`PalletInfoAccess`].
|
||||
Only(Vec<Vec<u8>>),
|
||||
}
|
||||
|
||||
impl Default for Select {
|
||||
fn default() -> Self {
|
||||
Select::None
|
||||
}
|
||||
}
|
||||
|
||||
impl sp_std::fmt::Debug for Select {
|
||||
fn fmt(&self, f: &mut sp_std::fmt::Formatter<'_>) -> sp_std::fmt::Result {
|
||||
match self {
|
||||
Select::RoundRobin(x) => write!(f, "RoundRobin({})", x),
|
||||
Select::Only(x) => write!(
|
||||
f,
|
||||
"Only({:?})",
|
||||
x.iter()
|
||||
.map(|x| sp_std::str::from_utf8(x).unwrap_or("<invalid?>"))
|
||||
.collect::<Vec<_>>(),
|
||||
),
|
||||
Select::All => write!(f, "All"),
|
||||
Select::None => write!(f, "None"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "std")]
|
||||
impl sp_std::str::FromStr for Select {
|
||||
type Err = &'static str;
|
||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||
match s {
|
||||
"all" | "All" => Ok(Select::All),
|
||||
"none" | "None" => Ok(Select::None),
|
||||
_ =>
|
||||
if s.starts_with("rr-") {
|
||||
let count = s
|
||||
.split_once('-')
|
||||
.and_then(|(_, count)| count.parse::<u32>().ok())
|
||||
.ok_or("failed to parse count")?;
|
||||
Ok(Select::RoundRobin(count))
|
||||
} else {
|
||||
let pallets = s.split(',').map(|x| x.as_bytes().to_vec()).collect::<Vec<_>>();
|
||||
Ok(Select::Only(pallets))
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Execute some checks to ensure the internal state of a pallet is consistent.
|
||||
///
|
||||
/// Usually, these checks should check all of the invariants that are expected to be held on all of
|
||||
/// the storage items of your pallet.
|
||||
pub trait TryState<BlockNumber> {
|
||||
/// Execute the state checks.
|
||||
fn try_state(_: BlockNumber, _: Select) -> Result<(), &'static str>;
|
||||
}
|
||||
|
||||
#[cfg_attr(all(not(feature = "tuples-96"), not(feature = "tuples-128")), impl_for_tuples(64))]
|
||||
#[cfg_attr(all(feature = "tuples-96", not(feature = "tuples-128")), impl_for_tuples(96))]
|
||||
#[cfg_attr(all(feature = "tuples-128"), impl_for_tuples(128))]
|
||||
impl<BlockNumber: Clone + sp_std::fmt::Debug + AtLeast32BitUnsigned> TryState<BlockNumber>
|
||||
for Tuple
|
||||
{
|
||||
for_tuples!( where #( Tuple: crate::traits::PalletInfoAccess )* );
|
||||
fn try_state(n: BlockNumber, targets: Select) -> Result<(), &'static str> {
|
||||
match targets {
|
||||
Select::None => Ok(()),
|
||||
Select::All => {
|
||||
let mut result = Ok(());
|
||||
for_tuples!( #( result = result.and(Tuple::try_state(n.clone(), targets.clone())); )* );
|
||||
result
|
||||
},
|
||||
Select::RoundRobin(len) => {
|
||||
let functions: &[fn(BlockNumber, Select) -> Result<(), &'static str>] =
|
||||
&[for_tuples!(#( Tuple::try_state ),*)];
|
||||
let skip = n.clone() % (functions.len() as u32).into();
|
||||
let skip: u32 =
|
||||
skip.try_into().unwrap_or_else(|_| sp_runtime::traits::Bounded::max_value());
|
||||
let mut result = Ok(());
|
||||
for try_state_fn in functions.iter().cycle().skip(skip as usize).take(len as usize)
|
||||
{
|
||||
result = result.and(try_state_fn(n.clone(), targets.clone()));
|
||||
}
|
||||
result
|
||||
},
|
||||
Select::Only(ref pallet_names) => {
|
||||
let try_state_fns: &[(
|
||||
&'static str,
|
||||
fn(BlockNumber, Select) -> Result<(), &'static str>,
|
||||
)] = &[for_tuples!(
|
||||
#( (<Tuple as crate::traits::PalletInfoAccess>::name(), Tuple::try_state) ),*
|
||||
)];
|
||||
let mut result = Ok(());
|
||||
for (name, try_state_fn) in try_state_fns {
|
||||
if pallet_names.iter().any(|n| n == name.as_bytes()) {
|
||||
result = result.and(try_state_fn(n.clone(), targets.clone()));
|
||||
}
|
||||
}
|
||||
result
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1309,9 +1309,10 @@ impl<T: Config> Pallet<T> {
|
||||
pub fn finalize() -> T::Header {
|
||||
log::debug!(
|
||||
target: "runtime::system",
|
||||
"[{:?}] length: {} (normal {}%, op: {}%, mandatory {}%) / normal weight: {} ({}%) \
|
||||
/ op weight {} ({}%) / mandatory weight {} ({}%)",
|
||||
"[{:?}] {} extrinsics, length: {} (normal {}%, op: {}%, mandatory {}%) / normal weight:\
|
||||
{} ({}%) op weight {} ({}%) / mandatory weight {} ({}%)",
|
||||
Self::block_number(),
|
||||
Self::extrinsic_index().unwrap_or_default(),
|
||||
Self::all_extrinsics_len(),
|
||||
sp_runtime::Percent::from_rational(
|
||||
Self::all_extrinsics_len(),
|
||||
|
||||
@@ -48,3 +48,4 @@ std = [
|
||||
"sp-std/std",
|
||||
"sp-transaction-storage-proof/std",
|
||||
]
|
||||
try-runtime = ["frame-support/try-runtime"]
|
||||
|
||||
@@ -13,7 +13,8 @@ readme = "README.md"
|
||||
targets = ["x86_64-unknown-linux-gnu"]
|
||||
|
||||
[dependencies]
|
||||
frame-support = { version = "4.0.0-dev", default-features = false, path = "../support" }
|
||||
codec = { package = "parity-scale-codec", version = "3.0.0", default-features = false, features = ["derive"]}
|
||||
frame-support = { version = "4.0.0-dev", default-features = false, features = [ "try-runtime" ], path = "../support" }
|
||||
sp-api = { version = "4.0.0-dev", default-features = false, path = "../../primitives/api" }
|
||||
sp-runtime = { version = "6.0.0", default-features = false, path = "../../primitives/runtime" }
|
||||
sp-std = { version = "4.0.0", default-features = false, path = "../../primitives/std" }
|
||||
@@ -21,6 +22,7 @@ sp-std = { version = "4.0.0", default-features = false, path = "../../primitives
|
||||
[features]
|
||||
default = [ "std" ]
|
||||
std = [
|
||||
"codec/std",
|
||||
"frame-support/std",
|
||||
"sp-api/std",
|
||||
"sp-runtime/std",
|
||||
|
||||
@@ -19,6 +19,7 @@
|
||||
|
||||
#![cfg_attr(not(feature = "std"), no_std)]
|
||||
|
||||
pub use frame_support::traits::TryStateSelect;
|
||||
use frame_support::weights::Weight;
|
||||
|
||||
sp_api::decl_runtime_apis! {
|
||||
@@ -37,6 +38,6 @@ sp_api::decl_runtime_apis! {
|
||||
///
|
||||
/// This is only sensible where the incoming block is from a different network, yet it has
|
||||
/// the same block format as the runtime implementing this API.
|
||||
fn execute_block_no_check(block: Block) -> Weight;
|
||||
fn execute_block(block: Block, state_root_check: bool, try_state: TryStateSelect) -> Weight;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user