Skip to content
This repository was archived by the owner on Nov 15, 2023. It is now read-only.
Merged
Show file tree
Hide file tree
Changes from 18 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

13 changes: 10 additions & 3 deletions bin/node/runtime/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1579,9 +1579,16 @@ impl_runtime_apis! {

#[cfg(feature = "try-runtime")]
impl frame_try_runtime::TryRuntime<Block> for Runtime {
fn on_runtime_upgrade() -> Result<(Weight, Weight), sp_runtime::RuntimeString> {
let weight = Executive::try_runtime_upgrade()?;
Ok((weight, RuntimeBlockWeights::get().max_block))
fn on_runtime_upgrade() -> (Weight, Weight) {
// NOTE: intentional unwrap: we don't want to propagate the error backwards, and want to
// have a backtrace here. If any of the pre/post migration checks fail, we shall stop
// right here and right now.
let weight = Executive::try_runtime_upgrade().unwrap();
(weight, RuntimeBlockWeights::get().max_block)
}

fn execute_block_no_check(block: Block) -> Weight {
Executive::execute_block_no_check(block)
}
}

Expand Down
19 changes: 19 additions & 0 deletions frame/executive/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -220,6 +220,25 @@ where
weight
}

/// 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());

// don't call `final_checks`, but do finalize the block. This is critical to clear transient
// storage items, such as weight.
let _header = <frame_system::Pallet<System>>::finalize();

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would wonder if there is some migration that would pass all the tests written, but then actually cause a problem on final checks, but we dont see that now

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

unlikely, but yeah that's just a pill that we have to swallow here. Final checks does:

  1. header digest check (don't know what it does -- not is it relevant IMO)
  2. state root check
  3. extrinsic root check

This actually makes me think: maybe I can re-introduce item 1 and 3, since they should work. Only the state root is guaranteed to fail, since :CODE: has changed.

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.
Expand Down
8 changes: 7 additions & 1 deletion frame/try-runtime/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,12 @@ sp_api::decl_runtime_apis! {
///
/// Returns the consumed weight of the migration in case of a successful one, combined with
/// the total allowed block weight of the runtime.
fn on_runtime_upgrade() -> Result<(Weight, Weight), sp_runtime::RuntimeString>;
fn on_runtime_upgrade() -> (Weight, Weight);

/// Execute the given block, but don't check that its state root matches that of yours.
///
/// 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;
}
}
2 changes: 1 addition & 1 deletion primitives/state-machine/src/testing.rs
Original file line number Diff line number Diff line change
Expand Up @@ -159,7 +159,7 @@ where
///
/// In contrast to [`commit_all`](Self::commit_all) this will not panic if there are open
/// transactions.
fn as_backend(&self) -> InMemoryBackend<H> {
pub fn as_backend(&self) -> InMemoryBackend<H> {
let top: Vec<_> =
self.overlay.changes().map(|(k, v)| (k.clone(), v.value().cloned())).collect();
let mut transaction = vec![(None, top)];
Expand Down
2 changes: 1 addition & 1 deletion utils/frame/remote-externalities/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ targets = ["x86_64-unknown-linux-gnu"]
[dependencies]
jsonrpsee-ws-client = { version = "0.3.0", default-features = false, features = [
"tokio1",
] }
]}
jsonrpsee-proc-macros = "0.3.0"

env_logger = "0.9"
Expand Down
33 changes: 22 additions & 11 deletions utils/frame/remote-externalities/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -112,8 +112,8 @@ pub struct OnlineConfig<B: BlockT> {
pub at: Option<B::Hash>,
/// An optional state snapshot file to WRITE to, not for reading. Not written if set to `None`.
pub state_snapshot: Option<SnapshotConfig>,
/// The modules to scrape. If empty, entire chain state will be scraped.
pub modules: Vec<String>,
/// The pallets to scrape. If empty, entire chain state will be scraped.
pub pallets: Vec<String>,
/// Transport config.
pub transport: Transport,
}
Expand All @@ -134,7 +134,7 @@ impl<B: BlockT> Default for OnlineConfig<B> {
transport: Transport { uri: DEFAULT_TARGET.to_owned(), client: None },
at: None,
state_snapshot: None,
modules: vec![],
pallets: vec![],
}
}
}
Expand Down Expand Up @@ -360,9 +360,9 @@ impl<B: BlockT> Builder<B> {
.clone();
info!(target: LOG_TARGET, "scraping key-pairs from remote @ {:?}", at);

let mut keys_and_values = if config.modules.len() > 0 {
let mut keys_and_values = if config.pallets.len() > 0 {
let mut filtered_kv = vec![];
for f in config.modules.iter() {
for f in config.pallets.iter() {
let hashed_prefix = StorageKey(twox_128(f.as_bytes()).to_vec());
let module_kv = self.rpc_get_pairs_paged(hashed_prefix.clone(), at).await?;
info!(
Expand All @@ -376,7 +376,7 @@ impl<B: BlockT> Builder<B> {
}
filtered_kv
} else {
info!(target: LOG_TARGET, "downloading data for all modules.");
info!(target: LOG_TARGET, "downloading data for all pallets.");
self.rpc_get_pairs_paged(StorageKey(vec![]), at).await?
};

Expand Down Expand Up @@ -482,12 +482,23 @@ impl<B: BlockT> Builder<B> {
self
}

/// overwrite the `at` value, if `mode` is set to [`Mode::Online`].
///
/// noop if `mode` is [`Mode::Offline`]
pub fn overwrite_online_at(mut self, at: B::Hash) -> Self {
if let Mode::Online(mut online) = self.mode.clone() {
online.at = Some(at);
self.mode = Mode::Online(online);
}
self
}

/// Build the test externalities.
pub async fn build(self) -> Result<TestExternalities, &'static str> {
let kv = self.pre_build().await?;
let mut ext = TestExternalities::new_empty();

debug!(target: LOG_TARGET, "injecting a total of {} keys", kv.len());
info!(target: LOG_TARGET, "injecting a total of {} keys", kv.len());
for (k, v) in kv {
let (k, v) = (k.0, v.0);
// Insert the key,value pair into the test trie backend
Expand Down Expand Up @@ -541,7 +552,7 @@ mod remote_tests {
init_logger();
Builder::<Block>::new()
.mode(Mode::Online(OnlineConfig {
modules: vec!["System".to_owned()],
pallets: vec!["System".to_owned()],
..Default::default()
}))
.build()
Expand All @@ -555,7 +566,7 @@ mod remote_tests {
init_logger();
Builder::<Block>::new()
.mode(Mode::Online(OnlineConfig {
modules: vec![
pallets: vec![
"Proxy".to_owned(),
"Multisig".to_owned(),
"PhragmenElection".to_owned(),
Expand Down Expand Up @@ -583,7 +594,7 @@ mod remote_tests {
init_logger();
Builder::<Block>::new()
.mode(Mode::Online(OnlineConfig {
modules: vec!["PhragmenElection".to_owned()],
pallets: vec!["PhragmenElection".to_owned()],
..Default::default()
}))
.build()
Expand All @@ -609,7 +620,7 @@ mod remote_tests {
Builder::<Block>::new()
.mode(Mode::Online(OnlineConfig {
state_snapshot: Some(SnapshotConfig::new("test_snapshot_to_remove.bin")),
modules: vec!["Balances".to_owned()],
pallets: vec!["Balances".to_owned()],
..Default::default()
}))
.build()
Expand Down
6 changes: 6 additions & 0 deletions utils/frame/try-runtime/cli/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,12 @@ sc-chain-spec = { version = "4.0.0-dev", path = "../../../../client/chain-spec"
sp-state-machine = { version = "0.10.0-dev", path = "../../../../primitives/state-machine" }
sp-runtime = { version = "4.0.0-dev", path = "../../../../primitives/runtime" }
sp-core = { version = "4.0.0-dev", path = "../../../../primitives/core" }
sp-io = { version = "4.0.0-dev", path = "../../../../primitives/io" }
sp-keystore = { version = "0.10.0-dev", path = "../../../../primitives/keystore" }
sp-externalities = { version = "0.10.0-dev", path = "../../../../primitives/externalities" }
sp-version = { version = "4.0.0-dev", path = "../../../../primitives/version" }

remote-externalities = { version = "0.10.0-dev", path = "../../remote-externalities" }
jsonrpsee-ws-client = { version = "0.3.0", default-features = false, features = [
"tokio1",
]}
182 changes: 182 additions & 0 deletions utils/frame/try-runtime/cli/src/commands/execute_block.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,182 @@
// This file is part of Substrate.

// Copyright (C) 2021 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.

use crate::{
build_executor, ensure_matching_spec, extract_code, full_extensions, hash_of, local_spec,
state_machine_call, SharedParams, State, LOG_TARGET,
};
use remote_externalities::rpc_api;
use sc_service::{Configuration, NativeExecutionDispatch};
use sp_core::storage::well_known_keys;
use sp_runtime::traits::{Block as BlockT, Header as HeaderT, NumberFor};
use std::{fmt::Debug, str::FromStr};

/// Configurations of the [`Command::ExecuteBlock`].
#[derive(Debug, Clone, structopt::StructOpt)]
pub struct ExecuteBlockCmd {
/// Overwrite the wasm code in state or not.
#[structopt(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`.
#[structopt(long)]
no_check: bool,

/// The block hash at which to fetch the block.
///
/// If the `live` state type is being used, then this can be omitted, and is equal to whatever
/// the `state::at` is. Only use this (with care) when combined with a snapshot.
#[structopt(
long,
multiple = false,
parse(try_from_str = crate::parse::hash)
)]
block_at: Option<String>,

/// The ws uri from which to fetch the block.
///
/// If the `live` state type is being used, then this can be omitted, and is equal to whatever
/// the `state::uri` is. Only use this (with care) when combined with a snapshot.
#[structopt(
long,
multiple = false,
parse(try_from_str = crate::parse::url)
)]
block_ws_uri: Option<String>,

/// The state type to use.
///
/// For this command only, if the `live` is used, then state of the parent block is fetched.
///
/// If `block_at` is provided, then the [`State::Live::at`] is being ignored.
#[structopt(subcommand)]
state: State,
}

impl ExecuteBlockCmd {
fn block_at<Block: BlockT>(&self) -> sc_cli::Result<Block::Hash>
where
Block::Hash: FromStr,
<Block::Hash as FromStr>::Err: Debug,
{
match (&self.block_at, &self.state) {
(Some(block_at), State::Snap { .. }) => hash_of::<Block>(&block_at),
(Some(block_at), State::Live { .. }) => {
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: Some(at), .. }) => hash_of::<Block>(&at),
_ => {
panic!("either `--block-at` must be provided, or state must be `live with a proper `--at``");
},
}
}

fn block_ws_uri<Block: BlockT>(&self) -> String
where
Block::Hash: FromStr,
<Block::Hash as FromStr>::Err: Debug,
{
match (&self.block_ws_uri, &self.state) {
(Some(block_ws_uri), State::Snap { .. }) => block_ws_uri.to_owned(),
(Some(block_ws_uri), State::Live { .. }) => {
log::error!(target: LOG_TARGET, "--block-uri is provided while state type is live, Are you sure you know what you are doing?");
block_ws_uri.to_owned()
},
(None, State::Live { uri, .. }) => uri.clone(),
(None, State::Snap { .. }) => {
panic!("either `--block-uri` must be provided, or state must be `live`");
},
}
}
}

pub(crate) async fn execute_block<Block, ExecDispatch>(
shared: SharedParams,
command: ExecuteBlockCmd,
config: Configuration,
) -> sc_cli::Result<()>
where
Block: BlockT + serde::de::DeserializeOwned,
Block::Hash: FromStr,
<Block::Hash as FromStr>::Err: Debug,
NumberFor<Block>: FromStr,
<NumberFor<Block> as FromStr>::Err: Debug,
ExecDispatch: NativeExecutionDispatch + 'static,
{
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: 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 {:?}",
block_ws_uri,
parent_hash
);

let ext = {
let builder = command
.state
.builder::<Block>()?
// make sure the state is being build with the parent hash, if it is online.
.overwrite_online_at(parent_hash.to_owned());

let builder = if command.overwrite_wasm_code {
let (code_key, code) = extract_code(&config.chain_spec)?;
builder.inject_key_value(&[(code_key, code)])
} else {
builder.inject_hashed_key(well_known_keys::CODE)
};

builder.build().await?
};

// A digest item gets added when the runtime is processing the block, so we need to pop
// the last one to be consistent with what a gossiped block would contain.
let (mut header, extrinsics) = block.deconstruct();
header.digest_mut().pop();
let block = Block::new(header, extrinsics);

let (expected_spec_name, expected_spec_version) =
local_spec::<Block, ExecDispatch>(&ext, &executor);
ensure_matching_spec::<Block>(
block_ws_uri.clone(),
expected_spec_name,
expected_spec_version,
shared.no_spec_name_check,
)
.await;

let _ = state_machine_call::<Block, ExecDispatch>(
&ext,
&executor,
execution,
if command.no_check { "TryRuntime_execute_block_no_check" } else { "Core_execute_block" },
block.encode().as_ref(),
full_extensions(),
)?;

log::info!(target: LOG_TARGET, "Core_execute_block executed without errors.");

Ok(())
}
Loading