mirror of
https://github.com/pezkuwichain/pezkuwi-subxt.git
synced 2026-06-11 09:31:12 +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:
Generated
+3
@@ -2228,6 +2228,7 @@ version = "4.0.0-dev"
|
||||
dependencies = [
|
||||
"frame-support",
|
||||
"frame-system",
|
||||
"frame-try-runtime",
|
||||
"hex-literal",
|
||||
"pallet-balances",
|
||||
"pallet-transaction-payment",
|
||||
@@ -2414,6 +2415,7 @@ name = "frame-try-runtime"
|
||||
version = "0.10.0-dev"
|
||||
dependencies = [
|
||||
"frame-support",
|
||||
"parity-scale-codec",
|
||||
"sp-api",
|
||||
"sp-runtime",
|
||||
"sp-std",
|
||||
@@ -11103,6 +11105,7 @@ name = "try-runtime-cli"
|
||||
version = "0.10.0-dev"
|
||||
dependencies = [
|
||||
"clap 3.1.18",
|
||||
"frame-try-runtime",
|
||||
"jsonrpsee",
|
||||
"log",
|
||||
"parity-scale-codec",
|
||||
|
||||
@@ -63,6 +63,7 @@ std = [
|
||||
"frame-support/std",
|
||||
"frame-system-rpc-runtime-api/std",
|
||||
"frame-system/std",
|
||||
"frame-try-runtime/std",
|
||||
"pallet-aura/std",
|
||||
"pallet-balances/std",
|
||||
"pallet-grandpa/std",
|
||||
@@ -97,9 +98,10 @@ runtime-benchmarks = [
|
||||
"sp-runtime/runtime-benchmarks",
|
||||
]
|
||||
try-runtime = [
|
||||
"frame-executive/try-runtime",
|
||||
"frame-try-runtime",
|
||||
"frame-executive/try-runtime",
|
||||
"frame-system/try-runtime",
|
||||
"frame-support/try-runtime",
|
||||
"pallet-aura/try-runtime",
|
||||
"pallet-balances/try-runtime",
|
||||
"pallet-grandpa/try-runtime",
|
||||
|
||||
@@ -545,8 +545,14 @@ impl_runtime_apis! {
|
||||
(weight, BlockWeights::get().max_block)
|
||||
}
|
||||
|
||||
fn execute_block_no_check(block: Block) -> Weight {
|
||||
Executive::execute_block_no_check(block)
|
||||
fn execute_block(
|
||||
block: Block,
|
||||
state_root_check: bool,
|
||||
select: frame_try_runtime::TryStateSelect
|
||||
) -> Weight {
|
||||
// NOTE: intentional unwrap: we don't want to propagate the error backwards, and want to
|
||||
// have a backtrace here.
|
||||
Executive::try_execute_block(block, state_root_check, select).expect("execute-block failed")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -245,14 +245,16 @@ runtime-benchmarks = [
|
||||
"hex-literal",
|
||||
]
|
||||
try-runtime = [
|
||||
"frame-executive/try-runtime",
|
||||
"frame-try-runtime",
|
||||
"frame-executive/try-runtime",
|
||||
"frame-system/try-runtime",
|
||||
"frame-support/try-runtime",
|
||||
"pallet-alliance/try-runtime",
|
||||
"pallet-assets/try-runtime",
|
||||
"pallet-authority-discovery/try-runtime",
|
||||
"pallet-authorship/try-runtime",
|
||||
"pallet-babe/try-runtime",
|
||||
"pallet-bags-list/try-runtime",
|
||||
"pallet-balances/try-runtime",
|
||||
"pallet-bounties/try-runtime",
|
||||
"pallet-child-bounties/try-runtime",
|
||||
@@ -264,32 +266,36 @@ try-runtime = [
|
||||
"pallet-elections-phragmen/try-runtime",
|
||||
"pallet-gilt/try-runtime",
|
||||
"pallet-grandpa/try-runtime",
|
||||
"pallet-identity/try-runtime",
|
||||
"pallet-im-online/try-runtime",
|
||||
"pallet-indices/try-runtime",
|
||||
"pallet-identity/try-runtime",
|
||||
"pallet-lottery/try-runtime",
|
||||
"pallet-membership/try-runtime",
|
||||
"pallet-mmr/try-runtime",
|
||||
"pallet-multisig/try-runtime",
|
||||
"pallet-nomination-pools/try-runtime",
|
||||
"pallet-offences/try-runtime",
|
||||
"pallet-preimage/try-runtime",
|
||||
"pallet-proxy/try-runtime",
|
||||
"pallet-ranked-collective/try-runtime",
|
||||
"pallet-randomness-collective-flip/try-runtime",
|
||||
"pallet-ranked-collective/try-runtime",
|
||||
"pallet-recovery/try-runtime",
|
||||
"pallet-referenda/try-runtime",
|
||||
"pallet-scheduler/try-runtime",
|
||||
"pallet-remark/try-runtime",
|
||||
"pallet-session/try-runtime",
|
||||
"pallet-society/try-runtime",
|
||||
"pallet-staking/try-runtime",
|
||||
"pallet-state-trie-migration/try-runtime",
|
||||
"pallet-scheduler/try-runtime",
|
||||
"pallet-society/try-runtime",
|
||||
"pallet-sudo/try-runtime",
|
||||
"pallet-timestamp/try-runtime",
|
||||
"pallet-tips/try-runtime",
|
||||
"pallet-transaction-payment/try-runtime",
|
||||
"pallet-treasury/try-runtime",
|
||||
"pallet-uniques/try-runtime",
|
||||
"pallet-utility/try-runtime",
|
||||
"pallet-transaction-payment/try-runtime",
|
||||
"pallet-asset-tx-payment/try-runtime",
|
||||
"pallet-transaction-storage/try-runtime",
|
||||
"pallet-uniques/try-runtime",
|
||||
"pallet-vesting/try-runtime",
|
||||
"pallet-whitelist/try-runtime",
|
||||
]
|
||||
|
||||
@@ -2079,8 +2079,21 @@ impl_runtime_apis! {
|
||||
(weight, RuntimeBlockWeights::get().max_block)
|
||||
}
|
||||
|
||||
fn execute_block_no_check(block: Block) -> Weight {
|
||||
Executive::execute_block_no_check(block)
|
||||
fn execute_block(
|
||||
block: Block,
|
||||
state_root_check: bool,
|
||||
select: frame_try_runtime::TryStateSelect
|
||||
) -> Weight {
|
||||
log::info!(
|
||||
target: "node-runtime",
|
||||
"try-runtime: executing block {:?} / root checks: {:?} / try-state-select: {:?}",
|
||||
block.header.hash(),
|
||||
state_root_check,
|
||||
select,
|
||||
);
|
||||
// NOTE: intentional unwrap: we don't want to propagate the error backwards, and want to
|
||||
// have a backtrace here.
|
||||
Executive::try_execute_block(block, state_root_check, select).unwrap()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -56,7 +56,7 @@ type ChildKeyValues = Vec<(ChildInfo, Vec<KeyValue>)>;
|
||||
const LOG_TARGET: &str = "remote-ext";
|
||||
const DEFAULT_TARGET: &str = "wss://rpc.polkadot.io:443";
|
||||
const BATCH_SIZE: usize = 1000;
|
||||
const PAGE: u32 = 512;
|
||||
const PAGE: u32 = 1000;
|
||||
|
||||
#[rpc(client)]
|
||||
pub trait RpcApi<Hash> {
|
||||
|
||||
@@ -31,3 +31,4 @@ sp-keystore = { version = "0.12.0", path = "../../../../primitives/keystore" }
|
||||
sp-runtime = { version = "6.0.0", path = "../../../../primitives/runtime" }
|
||||
sp-state-machine = { version = "0.12.0", path = "../../../../primitives/state-machine" }
|
||||
sp-version = { version = "5.0.0", path = "../../../../primitives/version" }
|
||||
frame-try-runtime = { path = "../../../../frame/try-runtime" }
|
||||
|
||||
@@ -19,6 +19,7 @@ use crate::{
|
||||
build_executor, ensure_matching_spec, extract_code, full_extensions, hash_of, local_spec,
|
||||
state_machine_call_with_proof, SharedParams, State, LOG_TARGET,
|
||||
};
|
||||
use parity_scale_codec::Encode;
|
||||
use remote_externalities::rpc_api;
|
||||
use sc_service::{Configuration, NativeExecutionDispatch};
|
||||
use sp_core::storage::well_known_keys;
|
||||
@@ -26,17 +27,30 @@ use sp_runtime::traits::{Block as BlockT, Header as HeaderT, NumberFor};
|
||||
use std::{fmt::Debug, str::FromStr};
|
||||
|
||||
/// Configurations of the [`Command::ExecuteBlock`].
|
||||
///
|
||||
/// This will always call into `TryRuntime_execute_block`, which can optionally skip the state-root
|
||||
/// check (useful for trying a unreleased runtime), and can execute runtime sanity checks as well.
|
||||
#[derive(Debug, Clone, clap::Parser)]
|
||||
pub struct ExecuteBlockCmd {
|
||||
/// Overwrite the wasm code in state or not.
|
||||
#[clap(long)]
|
||||
overwrite_wasm_code: bool,
|
||||
|
||||
/// If set, then the state root check is disabled by the virtue of calling into
|
||||
/// `TryRuntime_execute_block_no_check` instead of
|
||||
/// `Core_execute_block`.
|
||||
/// If set the state root check is disabled.
|
||||
#[clap(long)]
|
||||
no_check: bool,
|
||||
no_state_root_check: bool,
|
||||
|
||||
/// Which try-state targets to execute when running this command.
|
||||
///
|
||||
/// Expected values:
|
||||
/// - `all`
|
||||
/// - `none`
|
||||
/// - A comma separated list of pallets, as per pallet names in `construct_runtime!()` (e.g.
|
||||
/// `Staking, System`).
|
||||
/// - `rr-[x]` where `[x]` is a number. Then, the given number of pallets are checked in a
|
||||
/// round-robin fashion.
|
||||
#[clap(long, default_value = "none")]
|
||||
try_state: frame_try_runtime::TryStateSelect,
|
||||
|
||||
/// The block hash at which to fetch the block.
|
||||
///
|
||||
@@ -70,7 +84,7 @@ pub struct ExecuteBlockCmd {
|
||||
}
|
||||
|
||||
impl ExecuteBlockCmd {
|
||||
fn block_at<Block: BlockT>(&self) -> sc_cli::Result<Block::Hash>
|
||||
async fn block_at<Block: BlockT>(&self, ws_uri: String) -> sc_cli::Result<Block::Hash>
|
||||
where
|
||||
Block::Hash: FromStr,
|
||||
<Block::Hash as FromStr>::Err: Debug,
|
||||
@@ -81,6 +95,15 @@ impl ExecuteBlockCmd {
|
||||
log::warn!(target: LOG_TARGET, "--block-at is provided while state type is live. the `Live::at` will be ignored");
|
||||
hash_of::<Block>(block_at)
|
||||
},
|
||||
(None, State::Live { at: None, .. }) => {
|
||||
log::warn!(
|
||||
target: LOG_TARGET,
|
||||
"No --block-at or --at provided, using the latest finalized block instead"
|
||||
);
|
||||
remote_externalities::rpc_api::get_finalized_head::<Block, _>(ws_uri)
|
||||
.await
|
||||
.map_err(Into::into)
|
||||
},
|
||||
(None, State::Live { at: Some(at), .. }) => hash_of::<Block>(at),
|
||||
_ => {
|
||||
panic!("either `--block-at` must be provided, or state must be `live with a proper `--at``");
|
||||
@@ -123,13 +146,14 @@ where
|
||||
let executor = build_executor::<ExecDispatch>(&shared, &config);
|
||||
let execution = shared.execution;
|
||||
|
||||
let block_at = command.block_at::<Block>()?;
|
||||
let block_ws_uri = command.block_ws_uri::<Block>();
|
||||
let block_at = command.block_at::<Block>(block_ws_uri.clone()).await?;
|
||||
let block: Block = rpc_api::get_block::<Block, _>(block_ws_uri.clone(), block_at).await?;
|
||||
let parent_hash = block.header().parent_hash();
|
||||
log::info!(
|
||||
target: LOG_TARGET,
|
||||
"fetched block from {:?}, parent_hash to fetch the state {:?}",
|
||||
"fetched block #{:?} from {:?}, parent_hash to fetch the state {:?}",
|
||||
block.header().number(),
|
||||
block_ws_uri,
|
||||
parent_hash
|
||||
);
|
||||
@@ -162,6 +186,7 @@ where
|
||||
let (mut header, extrinsics) = block.deconstruct();
|
||||
header.digest_mut().pop();
|
||||
let block = Block::new(header, extrinsics);
|
||||
let payload = (block.clone(), !command.no_state_root_check, command.try_state).encode();
|
||||
|
||||
let (expected_spec_name, expected_spec_version, _) =
|
||||
local_spec::<Block, ExecDispatch>(&ext, &executor);
|
||||
@@ -177,8 +202,8 @@ where
|
||||
&ext,
|
||||
&executor,
|
||||
execution,
|
||||
if command.no_check { "TryRuntime_execute_block_no_check" } else { "Core_execute_block" },
|
||||
block.encode().as_ref(),
|
||||
"TryRuntime_execute_block",
|
||||
&payload,
|
||||
full_extensions(),
|
||||
)?;
|
||||
|
||||
|
||||
@@ -23,7 +23,7 @@ use jsonrpsee::{
|
||||
core::client::{Subscription, SubscriptionClientT},
|
||||
ws_client::WsClientBuilder,
|
||||
};
|
||||
use parity_scale_codec::Decode;
|
||||
use parity_scale_codec::{Decode, Encode};
|
||||
use remote_externalities::{rpc_api, Builder, Mode, OnlineConfig};
|
||||
use sc_executor::NativeExecutionDispatch;
|
||||
use sc_service::Configuration;
|
||||
@@ -40,6 +40,22 @@ pub struct FollowChainCmd {
|
||||
/// The url to connect to.
|
||||
#[clap(short, long, parse(try_from_str = parse::url))]
|
||||
uri: String,
|
||||
|
||||
/// If set, then the state root check is enabled.
|
||||
#[clap(long)]
|
||||
state_root_check: bool,
|
||||
|
||||
/// Which try-state targets to execute when running this command.
|
||||
///
|
||||
/// Expected values:
|
||||
/// - `all`
|
||||
/// - `none`
|
||||
/// - A comma separated list of pallets, as per pallet names in `construct_runtime!()` (e.g.
|
||||
/// `Staking, System`).
|
||||
/// - `rr-[x]` where `[x]` is a number. Then, the given number of pallets are checked in a
|
||||
/// round-robin fashion.
|
||||
#[clap(long, default_value = "none")]
|
||||
try_state: frame_try_runtime::TryStateSelect,
|
||||
}
|
||||
|
||||
pub(crate) async fn follow_chain<Block, ExecDispatch>(
|
||||
@@ -141,8 +157,8 @@ where
|
||||
state_ext,
|
||||
&executor,
|
||||
execution,
|
||||
"TryRuntime_execute_block_no_check",
|
||||
block.encode().as_ref(),
|
||||
"TryRuntime_execute_block",
|
||||
(block, command.state_root_check, command.try_state.clone()).encode().as_ref(),
|
||||
full_extensions(),
|
||||
)?;
|
||||
|
||||
|
||||
@@ -335,17 +335,18 @@ pub enum Command {
|
||||
/// different state transition function.
|
||||
///
|
||||
/// To make testing slightly more dynamic, you can disable the state root check by enabling
|
||||
/// `ExecuteBlockCmd::no_check`. If you get signature verification errors, you should
|
||||
/// manually tweak your local runtime's spec version to fix this.
|
||||
/// `ExecuteBlockCmd::no_check`. If you get signature verification errors, you should manually
|
||||
/// tweak your local runtime's spec version to fix this.
|
||||
///
|
||||
/// A subtle detail of execute block is that if you want to execute block 100 of a live chain
|
||||
/// again, you need to scrape the state of block 99. This is already done automatically if you
|
||||
/// use [`State::Live`], and the parent hash of the target block is used to scrape the state.
|
||||
/// If [`State::Snap`] is being used, then this needs to be manually taken into consideration.
|
||||
///
|
||||
/// This executes the same runtime api as normal block import, namely `Core_execute_block`. If
|
||||
/// `ExecuteBlockCmd::no_check` is set, it uses a custom, try-runtime-only runtime
|
||||
/// api called `TryRuntime_execute_block_no_check`.
|
||||
/// This does not execute the same runtime api as normal block import do, namely
|
||||
/// `Core_execute_block`. Instead, it uses `TryRuntime_execute_block`, which can optionally
|
||||
/// skip state-root check (useful for trying a unreleased runtime), and can execute runtime
|
||||
/// sanity checks as well.
|
||||
ExecuteBlock(commands::execute_block::ExecuteBlockCmd),
|
||||
|
||||
/// Executes *the offchain worker hooks* of a given block against some state.
|
||||
@@ -656,21 +657,27 @@ pub(crate) async fn ensure_matching_spec<Block: BlockT + serde::de::DeserializeO
|
||||
if expected_spec_version == version {
|
||||
log::info!(target: LOG_TARGET, "found matching spec version: {:?}", version);
|
||||
} else {
|
||||
log::warn!(
|
||||
target: LOG_TARGET,
|
||||
let msg = format!(
|
||||
"spec version mismatch (local {} != remote {}). This could cause some issues.",
|
||||
expected_spec_version,
|
||||
version
|
||||
expected_spec_version, version
|
||||
);
|
||||
if relaxed {
|
||||
log::warn!(target: LOG_TARGET, "{}", msg);
|
||||
} else {
|
||||
panic!("{}", msg);
|
||||
}
|
||||
}
|
||||
},
|
||||
Err(why) => {
|
||||
log::error!(
|
||||
target: LOG_TARGET,
|
||||
let msg = format!(
|
||||
"failed to fetch runtime version from {}: {:?}. Skipping the check",
|
||||
uri,
|
||||
why
|
||||
uri, why
|
||||
);
|
||||
if relaxed {
|
||||
log::error!(target: LOG_TARGET, "{}", msg);
|
||||
} else {
|
||||
panic!("{}", msg);
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
@@ -801,15 +808,15 @@ pub(crate) fn state_machine_call_with_proof<Block: BlockT, D: NativeExecutionDis
|
||||
)
|
||||
}
|
||||
};
|
||||
log::info!(
|
||||
log::debug!(
|
||||
target: LOG_TARGET,
|
||||
"proof: {} / {} nodes",
|
||||
HexDisplay::from(&proof_nodes.iter().flatten().cloned().collect::<Vec<_>>()),
|
||||
proof_nodes.len()
|
||||
);
|
||||
log::info!(target: LOG_TARGET, "proof size: {}", humanize(proof_size));
|
||||
log::info!(target: LOG_TARGET, "compact proof size: {}", humanize(compact_proof_size),);
|
||||
log::info!(
|
||||
log::debug!(target: LOG_TARGET, "proof size: {}", humanize(proof_size));
|
||||
log::debug!(target: LOG_TARGET, "compact proof size: {}", humanize(compact_proof_size),);
|
||||
log::debug!(
|
||||
target: LOG_TARGET,
|
||||
"zstd-compressed compact proof {}",
|
||||
humanize(compressed_proof.len()),
|
||||
|
||||
Reference in New Issue
Block a user