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:
Kian Paimani
2022-09-01 11:33:22 +01:00
committed by GitHub
parent d8e951758c
commit f67c06ce22
39 changed files with 651 additions and 209 deletions
+3
View File
@@ -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")
}
}
}
+13 -7
View File
@@ -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",
]
+15 -2
View File
@@ -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()
}
}
+1 -1
View File
@@ -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.
///
@@ -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);
+7 -8
View File
@@ -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() {
+15 -28
View File
@@ -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();
+13 -13
View File
@@ -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));
+1 -1
View File
@@ -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")
})
}
+1
View File
@@ -50,3 +50,4 @@ std = [
"sp-runtime/std",
"sp-std/std",
]
try-runtime = ["frame-support/try-runtime"]
+1
View File
@@ -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.
+2 -1
View File
@@ -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" ]
+93 -44
View File
@@ -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();
+1 -1
View File
@@ -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);
+11 -5
View File
@@ -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)));
+1 -1
View File
@@ -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();
})
}
}
+1
View File
@@ -41,3 +41,4 @@ std = [
"sp-runtime/std",
"sp-std/std",
]
try-runtime = [ "frame-support/try-runtime" ]
+2 -2
View File
@@ -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(())
}
+102 -5
View File
@@ -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(())
}
}
+5 -1
View File
@@ -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)
}
}
)
}
+36
View File
@@ -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 }
+5 -2
View File
@@ -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};
+12 -43
View File
@@ -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
},
}
}
}
+3 -2
View File
@@ -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"]
+3 -1
View File
@@ -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",
+2 -1
View File
@@ -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()),