From 3d9123a6179b989c5ab7ed5fea4731e531794993 Mon Sep 17 00:00:00 2001 From: kianenigma Date: Wed, 15 Sep 2021 11:55:56 +0200 Subject: [PATCH 01/16] deadlock, need to ask someone to help now --- Cargo.lock | 2 + bin/node/runtime/src/lib.rs | 4 + frame/executive/src/lib.rs | 29 ++- frame/system/src/extensions/check_genesis.rs | 3 + frame/try-runtime/src/lib.rs | 6 + .../src/generic/unchecked_extrinsic.rs | 10 + primitives/runtime/src/lib.rs | 3 + primitives/state-machine/src/testing.rs | 2 +- utils/frame/remote-externalities/Cargo.toml | 2 +- utils/frame/try-runtime/cli/Cargo.toml | 4 + utils/frame/try-runtime/cli/src/lib.rs | 179 +++++++++++++++++- 11 files changed, 236 insertions(+), 8 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index f1f3b7e00c65a..7a3c860b68093 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -10490,6 +10490,7 @@ checksum = "59547bce71d9c38b83d9c0e92b6066c4253371f15005def0c30d9657f50c7642" name = "try-runtime-cli" version = "0.10.0-dev" dependencies = [ + "jsonrpsee-ws-client", "log 0.4.14", "parity-scale-codec", "remote-externalities", @@ -10499,6 +10500,7 @@ dependencies = [ "sc-service", "serde", "sp-core", + "sp-externalities", "sp-keystore", "sp-runtime", "sp-state-machine", diff --git a/bin/node/runtime/src/lib.rs b/bin/node/runtime/src/lib.rs index d7257a9ea71b5..6adfc4ca5049e 100644 --- a/bin/node/runtime/src/lib.rs +++ b/bin/node/runtime/src/lib.rs @@ -1549,6 +1549,10 @@ impl_runtime_apis! { let weight = Executive::try_runtime_upgrade()?; Ok((weight, RuntimeBlockWeights::get().max_block)) } + + fn execute_block_no_state_root_check(block: Block) -> Weight { + Executive::execute_block_no_state_root_check(block) + } } #[cfg(feature = "runtime-benchmarks")] diff --git a/frame/executive/src/lib.rs b/frame/executive/src/lib.rs index 5f1ae23c2f531..9a8c5bfbceaa8 100644 --- a/frame/executive/src/lib.rs +++ b/frame/executive/src/lib.rs @@ -220,6 +220,28 @@ 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_state_root_check(block: Block) -> frame_support::weights::Weight { + Self::initialize_block(block.header()); + Self::initial_checks(&block); + + let (header, extrinsics) = block.deconstruct(); + + let signature_batching = sp_runtime::SignatureBatching::start(); + Self::execute_extrinsics_with_book_keeping(extrinsics, *header.number()); + if !signature_batching.verify() { + panic!("Signature verification failed."); + } + + // don't call `final_checks`, but do finalize the block. + let _header = >::finalize(); + + frame_system::Pallet::::block_weight().total() + } + /// Execute all `OnRuntimeUpgrade` of this runtime, including the pre and post migration checks. /// /// This should only be used for testing. @@ -297,6 +319,9 @@ where let last = frame_system::LastRuntimeUpgrade::::get(); let current = >::get(); + sp_std::if_std! { + println!("lat {:?}, current {:?}", last, current); + } if last.map(|v| v.was_upgraded(¤t)).unwrap_or(true) { frame_system::LastRuntimeUpgrade::::put( frame_system::LastRuntimeUpgradeInfo::from(current), @@ -307,7 +332,7 @@ where } } - fn initial_checks(block: &Block) { + pub fn initial_checks(block: &Block) { sp_tracing::enter_span!(sp_tracing::Level::TRACE, "initial_checks"); let header = block.header(); @@ -359,7 +384,7 @@ where extrinsics.into_iter().for_each(|e| { if let Err(e) = Self::apply_extrinsic(e) { let err: &'static str = e.into(); - panic!("{}", err) + panic!("{:?}: {}", e, err) } }); diff --git a/frame/system/src/extensions/check_genesis.rs b/frame/system/src/extensions/check_genesis.rs index 4f561f17c3564..f81142bae4ec6 100644 --- a/frame/system/src/extensions/check_genesis.rs +++ b/frame/system/src/extensions/check_genesis.rs @@ -53,6 +53,9 @@ impl SignedExtension for CheckGenesis { const IDENTIFIER: &'static str = "CheckGenesis"; fn additional_signed(&self) -> Result { + sp_std::if_std! { + println!(">::block_hash(T::BlockNumber::zero()) {:?}", >::block_hash(T::BlockNumber::zero())); + } Ok(>::block_hash(T::BlockNumber::zero())) } } diff --git a/frame/try-runtime/src/lib.rs b/frame/try-runtime/src/lib.rs index b2dfdfac6429e..4bfc4c746fd07 100644 --- a/frame/try-runtime/src/lib.rs +++ b/frame/try-runtime/src/lib.rs @@ -33,5 +33,11 @@ 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>; + + /// 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_state_root_check(block: Block) -> Weight; } } diff --git a/primitives/runtime/src/generic/unchecked_extrinsic.rs b/primitives/runtime/src/generic/unchecked_extrinsic.rs index f0e00b7b82971..45c2355983678 100644 --- a/primitives/runtime/src/generic/unchecked_extrinsic.rs +++ b/primitives/runtime/src/generic/unchecked_extrinsic.rs @@ -111,10 +111,19 @@ where Ok(match self.signature { Some((signed, signature, extra)) => { let signed = lookup.lookup(signed)?; + sp_std::if_std! { + println!("checking {:?} from {:?} with extras {:?} => sig {:?}", self.function, signed, extra, signature); + } let raw_payload = SignedPayload::new(self.function, extra)?; + sp_std::if_std! { + println!("rw_payload created"); + } if !raw_payload.using_encoded(|payload| signature.verify(payload, &signed)) { return Err(InvalidTransaction::BadProof.into()) } + sp_std::if_std! { + println!("done"); + } let (function, extra, _) = raw_payload.deconstruct(); CheckedExtrinsic { signed: Some((signed, extra)), function } @@ -138,6 +147,7 @@ where /// Note that the payload that we sign to produce unchecked extrinsic signature /// is going to be different than the `SignaturePayload` - so the thing the extrinsic /// actually contains. +#[derive(Debug)] pub struct SignedPayload((Call, Extra, Extra::AdditionalSigned)); impl SignedPayload diff --git a/primitives/runtime/src/lib.rs b/primitives/runtime/src/lib.rs index 4a9c6087fa5cc..bed6f667180f9 100644 --- a/primitives/runtime/src/lib.rs +++ b/primitives/runtime/src/lib.rs @@ -399,6 +399,9 @@ impl std::fmt::Display for MultiSigner { impl Verify for MultiSignature { type Signer = MultiSigner; fn verify>(&self, mut msg: L, signer: &AccountId32) -> bool { + sp_std::if_std! { + println!("{:?}, verifying msg from {:?}", self, signer); + } match (self, signer) { (Self::Ed25519(ref sig), who) => sig.verify(msg, &ed25519::Public::from_slice(who.as_ref())), diff --git a/primitives/state-machine/src/testing.rs b/primitives/state-machine/src/testing.rs index ec1772ba8666f..23f66ee14d87e 100644 --- a/primitives/state-machine/src/testing.rs +++ b/primitives/state-machine/src/testing.rs @@ -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 { + pub fn as_backend(&self) -> InMemoryBackend { let top: Vec<_> = self.overlay.changes().map(|(k, v)| (k.clone(), v.value().cloned())).collect(); let mut transaction = vec![(None, top)]; diff --git a/utils/frame/remote-externalities/Cargo.toml b/utils/frame/remote-externalities/Cargo.toml index d255499d6c3ad..ce774679f94c2 100644 --- a/utils/frame/remote-externalities/Cargo.toml +++ b/utils/frame/remote-externalities/Cargo.toml @@ -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" diff --git a/utils/frame/try-runtime/cli/Cargo.toml b/utils/frame/try-runtime/cli/Cargo.toml index 5cc5ae6ee58bb..a115017b91785 100644 --- a/utils/frame/try-runtime/cli/Cargo.toml +++ b/utils/frame/try-runtime/cli/Cargo.toml @@ -26,5 +26,9 @@ sp-state-machine = { version = "0.10.0-dev", path = "../../../../primitives/stat sp-runtime = { version = "4.0.0-dev", path = "../../../../primitives/runtime" } sp-core = { version = "4.0.0-dev", path = "../../../../primitives/core" } sp-keystore = { version = "0.10.0-dev", path = "../../../../primitives/keystore" } +sp-externalities = { version = "0.10.0-dev", path = "../../../../primitives/externalities" } remote-externalities = { version = "0.10.0-dev", path = "../../remote-externalities" } +jsonrpsee-ws-client = { version = "0.3.0", default-features = false, features = [ + "tokio1", +]} diff --git a/utils/frame/try-runtime/cli/src/lib.rs b/utils/frame/try-runtime/cli/src/lib.rs index c92c3959535e9..dbcb79f73fb98 100644 --- a/utils/frame/try-runtime/cli/src/lib.rs +++ b/utils/frame/try-runtime/cli/src/lib.rs @@ -30,12 +30,16 @@ use sp_core::{ OffchainDbExt, OffchainWorkerExt, TransactionPoolExt, }, storage::{well_known_keys, StorageData, StorageKey}, + testing::TaskExecutor, + traits::TaskExecutorExt, }; use sp_keystore::{testing::KeyStore, KeystoreExt}; use sp_runtime::traits::{Block as BlockT, Header as HeaderT, NumberFor}; use sp_state_machine::StateMachine; use std::{fmt::Debug, path::PathBuf, str::FromStr, sync::Arc}; +const LOG_TARGET: &'static str = "try-runtime::cli"; + mod parse; /// Possible subcommands of `try-runtime`. @@ -48,6 +52,10 @@ pub enum Command { /// Execute "Core_execute_block" using the given block and the runtime state of the parent /// block. ExecuteBlock(ExecuteBlockCmd), + /// Follow the given chain's finalized blocks and apply all of its extrinsics. + /// + /// The initial state is the state of the first finalized block received. + FollowChain, } #[derive(Debug, Clone, structopt::StructOpt)] @@ -116,6 +124,7 @@ pub struct SharedParams { /// Whether or not to overwrite the code from state with the code from /// the specified chain spec. #[structopt(long)] + // TODO: should not be an option in on-runtime-upgrade and follow-chain pub overwrite_code: bool, /// The url to connect to. @@ -141,6 +150,23 @@ impl SharedParams { } } +#[derive(structopt::StructOpt, Debug, Clone)] +pub enum DummyCommands { + OnRuntimeUpgrade, + OffchainWorker, +} + +impl std::str::FromStr for DummyCommands { + type Err = &'static str; + fn from_str(s: &str) -> Result { + match s.to_lowercase().as_str() { + "on-runtime-upgrade" => Ok(DummyCommands::OnRuntimeUpgrade), + "offchain-worker" => Ok(DummyCommands::OffchainWorker), + _ => Err("Err"), + } + } +} + /// Various commands to try out against runtime state at a specific block. #[derive(Debug, Clone, structopt::StructOpt)] pub struct TryRuntimeCmd { @@ -173,6 +199,142 @@ pub enum State { }, } +async fn follow_chain( + shared: SharedParams, + config: Configuration, +) -> sc_cli::Result<()> +where + Block: BlockT + serde::de::DeserializeOwned, + Block::Hash: FromStr, + Block::Header: serde::de::DeserializeOwned, + ::Err: Debug, + NumberFor: FromStr, + as FromStr>::Err: Debug, + ExecDispatch: NativeExecutionDispatch + 'static, +{ + use jsonrpsee_ws_client::{ + types::{traits::SubscriptionClient, v2::params::JsonRpcParams, Subscription}, + WsClientBuilder, + }; + use sp_state_machine::Backend; + + check_spec_name::(shared.url.clone(), config.chain_spec.name().to_string()).await; + + let mut maybe_state_ext = None; + + let sub = "chain_subscribeFinalizedHeads"; + let unsub = "chain_unsubscribeFinalizedHeads"; + + let client = WsClientBuilder::default() + .connection_timeout(std::time::Duration::new(20, 0)) + .max_request_body_size(u32::MAX) + .build(&shared.url) + .await + .unwrap(); + + log::info!(target: LOG_TARGET, "subscribing to {:?} / {:?}", sub, unsub); + let mut subscription: Subscription = + client.subscribe(&sub, JsonRpcParams::NoParams, &unsub).await.unwrap(); + + let (code_key, code) = extract_code(config.chain_spec)?; + + while let Some(header) = subscription.next().await.unwrap() { + let hash = header.hash(); + let number = header.number(); + + let block = remote_externalities::rpc_api::get_block::(&shared.url, hash) + .await + .unwrap(); + + log::debug!( + target: LOG_TARGET, + "new block event: {:?} => {:?}, extrinsics: {}", + hash, + number, + block.extrinsics().len() + ); + + // create an ext at the state of this block, whatever is the first subscription event. + if maybe_state_ext.is_none() { + let builder = Builder::::new().mode(Mode::Online(OnlineConfig { + transport: shared.url.to_owned().into(), + at: Some(header.parent_hash().clone()), + ..Default::default() + })); + + let new_ext = + builder.inject_key_value(&[(code_key.clone(), code.clone())]).build().await?; + log::info!( + target: LOG_TARGET, + "initialized state externalities at {:?}, storage root {:?}", + number, + new_ext.as_backend().root() + ); + maybe_state_ext = Some(new_ext); + } + + let state_ext = + maybe_state_ext.as_mut().expect("state_ext either existed or was just created"); + + let wasm_method = shared.wasm_method; + let execution = shared.execution; + let heap_pages = shared.heap_pages.or(config.default_heap_pages); + + let mut changes = Default::default(); + let max_runtime_instances = config.max_runtime_instances; + let executor = NativeElseWasmExecutor::::new( + wasm_method.into(), + heap_pages, + max_runtime_instances, + ); + + let mut extensions = sp_externalities::Extensions::default(); + extensions.register(TaskExecutorExt::new(TaskExecutor::new())); + + let encoded_result = StateMachine::<_, _, NumberFor, _>::new( + &state_ext.backend, + None, + &mut changes, + &executor, + "TryRuntime_execute_block_no_state_root_check", + block.encode().as_ref(), + extensions, + &sp_state_machine::backend::BackendRuntimeCode::new(&state_ext.backend) + .runtime_code()?, + sp_core::testing::TaskExecutor::new(), + ) + .execute(execution.into()) + .map_err(|e| { + format!("failed to execute 'TryRuntime_execute_block_no_state_root_check': {:?}", e) + })?; + + let consumed_weight = ::decode(&mut &*encoded_result) + .map_err(|e| format!("failed to decode output: {:?}", e))?; + log::info!( + target: LOG_TARGET, + "before commit overlay changes empty: {:?}, backend root {:?}", + state_ext.overlayed_changes().is_empty(), + state_ext.as_backend().root(), + ); + state_ext.commit_all().unwrap(); + log::info!( + target: LOG_TARGET, + "after commit overlay changes empty: {:?}, backend root {:?}", + state_ext.overlayed_changes().is_empty(), + state_ext.as_backend().root(), + ); + log::info!( + target: LOG_TARGET, + "executed block {}, consumed weight {}, new storage root {:?}", + number, + consumed_weight, + state_ext.as_backend().root(), + ); + } + + Ok(()) +} + async fn on_runtime_upgrade( shared: SharedParams, command: OnRuntimeUpgradeCmd, @@ -241,6 +403,7 @@ where let (weight, total_weight) = <(u64, u64) as Decode>::decode(&mut &*encoded_result) .map_err(|e| format!("failed to decode output: {:?}", e))?; log::info!( + target: LOG_TARGET, "TryRuntime_on_runtime_upgrade executed without errors. Consumed weight = {}, total weight = {} ({})", weight, total_weight, @@ -332,7 +495,7 @@ where .execute(execution.into()) .map_err(|e| format!("failed to execute 'OffchainWorkerApi_offchain_worker': {:?}", e))?; - log::info!("OffchainWorkerApi_offchain_worker executed without errors."); + log::info!(target: LOG_TARGET, "OffchainWorkerApi_offchain_worker executed without errors."); Ok(()) } @@ -433,7 +596,7 @@ where .map_err(|e| format!("failed to execute 'Core_execute_block': {:?}", e))?; debug_assert!(_encoded_result == vec![1]); - log::info!("Core_execute_block executed without errors."); + log::info!(target: LOG_TARGET, "Core_execute_block executed without errors."); Ok(()) } @@ -458,6 +621,8 @@ impl TryRuntimeCmd { .await, Command::ExecuteBlock(cmd) => execute_block::(self.shared.clone(), cmd.clone(), config).await, + Command::FollowChain => + follow_chain::(self.shared.clone(), config).await, } } } @@ -505,17 +670,23 @@ async fn check_spec_name( .map(|spec_name| spec_name.to_lowercase()) { Ok(spec) if spec == expected_spec_name => { - log::debug!("found matching spec name: {:?}", spec); + log::debug!(target: LOG_TARGET, "found matching spec name: {:?}", spec); }, Ok(spec) => { log::warn!( + target: LOG_TARGET, "version mismatch: remote spec name: '{}', expected (local chain spec, aka. `--chain`): '{}'", spec, expected_spec_name, ); }, Err(why) => { - log::error!("failed to fetch runtime version from {}: {:?}", uri, why); + log::error!( + target: LOG_TARGET, + "failed to fetch runtime version from {}: {:?}", + uri, + why + ); }, } } From 58e2e652283443e33a8c11daa7d20b3240b87716 Mon Sep 17 00:00:00 2001 From: kianenigma Date: Wed, 15 Sep 2021 14:46:20 +0200 Subject: [PATCH 02/16] Finally it seems to be working.. at least for a few blocks --- Cargo.lock | 1 + frame/executive/src/lib.rs | 8 - frame/system/src/extensions/check_genesis.rs | 3 - .../src/generic/unchecked_extrinsic.rs | 9 - primitives/runtime/src/lib.rs | 3 - utils/frame/remote-externalities/src/lib.rs | 2 +- utils/frame/try-runtime/cli/Cargo.toml | 1 + utils/frame/try-runtime/cli/src/lib.rs | 154 +++++++++++------- 8 files changed, 94 insertions(+), 87 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 7a3c860b68093..2104d32342260 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -10501,6 +10501,7 @@ dependencies = [ "serde", "sp-core", "sp-externalities", + "sp-io", "sp-keystore", "sp-runtime", "sp-state-machine", diff --git a/frame/executive/src/lib.rs b/frame/executive/src/lib.rs index 9a8c5bfbceaa8..8f50f9858489e 100644 --- a/frame/executive/src/lib.rs +++ b/frame/executive/src/lib.rs @@ -230,12 +230,7 @@ where let (header, extrinsics) = block.deconstruct(); - let signature_batching = sp_runtime::SignatureBatching::start(); Self::execute_extrinsics_with_book_keeping(extrinsics, *header.number()); - if !signature_batching.verify() { - panic!("Signature verification failed."); - } - // don't call `final_checks`, but do finalize the block. let _header = >::finalize(); @@ -319,9 +314,6 @@ where let last = frame_system::LastRuntimeUpgrade::::get(); let current = >::get(); - sp_std::if_std! { - println!("lat {:?}, current {:?}", last, current); - } if last.map(|v| v.was_upgraded(¤t)).unwrap_or(true) { frame_system::LastRuntimeUpgrade::::put( frame_system::LastRuntimeUpgradeInfo::from(current), diff --git a/frame/system/src/extensions/check_genesis.rs b/frame/system/src/extensions/check_genesis.rs index f81142bae4ec6..4f561f17c3564 100644 --- a/frame/system/src/extensions/check_genesis.rs +++ b/frame/system/src/extensions/check_genesis.rs @@ -53,9 +53,6 @@ impl SignedExtension for CheckGenesis { const IDENTIFIER: &'static str = "CheckGenesis"; fn additional_signed(&self) -> Result { - sp_std::if_std! { - println!(">::block_hash(T::BlockNumber::zero()) {:?}", >::block_hash(T::BlockNumber::zero())); - } Ok(>::block_hash(T::BlockNumber::zero())) } } diff --git a/primitives/runtime/src/generic/unchecked_extrinsic.rs b/primitives/runtime/src/generic/unchecked_extrinsic.rs index 45c2355983678..b591cd8e054e4 100644 --- a/primitives/runtime/src/generic/unchecked_extrinsic.rs +++ b/primitives/runtime/src/generic/unchecked_extrinsic.rs @@ -111,19 +111,10 @@ where Ok(match self.signature { Some((signed, signature, extra)) => { let signed = lookup.lookup(signed)?; - sp_std::if_std! { - println!("checking {:?} from {:?} with extras {:?} => sig {:?}", self.function, signed, extra, signature); - } let raw_payload = SignedPayload::new(self.function, extra)?; - sp_std::if_std! { - println!("rw_payload created"); - } if !raw_payload.using_encoded(|payload| signature.verify(payload, &signed)) { return Err(InvalidTransaction::BadProof.into()) } - sp_std::if_std! { - println!("done"); - } let (function, extra, _) = raw_payload.deconstruct(); CheckedExtrinsic { signed: Some((signed, extra)), function } diff --git a/primitives/runtime/src/lib.rs b/primitives/runtime/src/lib.rs index bed6f667180f9..4a9c6087fa5cc 100644 --- a/primitives/runtime/src/lib.rs +++ b/primitives/runtime/src/lib.rs @@ -399,9 +399,6 @@ impl std::fmt::Display for MultiSigner { impl Verify for MultiSignature { type Signer = MultiSigner; fn verify>(&self, mut msg: L, signer: &AccountId32) -> bool { - sp_std::if_std! { - println!("{:?}, verifying msg from {:?}", self, signer); - } match (self, signer) { (Self::Ed25519(ref sig), who) => sig.verify(msg, &ed25519::Public::from_slice(who.as_ref())), diff --git a/utils/frame/remote-externalities/src/lib.rs b/utils/frame/remote-externalities/src/lib.rs index addb3d1dd3c17..88b74c52d6f79 100644 --- a/utils/frame/remote-externalities/src/lib.rs +++ b/utils/frame/remote-externalities/src/lib.rs @@ -487,7 +487,7 @@ impl Builder { 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 diff --git a/utils/frame/try-runtime/cli/Cargo.toml b/utils/frame/try-runtime/cli/Cargo.toml index a115017b91785..85e9c3d08705c 100644 --- a/utils/frame/try-runtime/cli/Cargo.toml +++ b/utils/frame/try-runtime/cli/Cargo.toml @@ -25,6 +25,7 @@ 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" } diff --git a/utils/frame/try-runtime/cli/src/lib.rs b/utils/frame/try-runtime/cli/src/lib.rs index dbcb79f73fb98..849559c628ea1 100644 --- a/utils/frame/try-runtime/cli/src/lib.rs +++ b/utils/frame/try-runtime/cli/src/lib.rs @@ -32,6 +32,7 @@ use sp_core::{ storage::{well_known_keys, StorageData, StorageKey}, testing::TaskExecutor, traits::TaskExecutorExt, + H256, }; use sp_keystore::{testing::KeyStore, KeystoreExt}; use sp_runtime::traits::{Block as BlockT, Header as HeaderT, NumberFor}; @@ -47,11 +48,14 @@ mod parse; pub enum Command { /// Execute "TryRuntime_on_runtime_upgrade" against the given runtime state. OnRuntimeUpgrade(OnRuntimeUpgradeCmd), + /// Execute "OffchainWorkerApi_offchain_worker" against the given runtime state. OffchainWorker(OffchainWorkerCmd), + /// Execute "Core_execute_block" using the given block and the runtime state of the parent /// block. ExecuteBlock(ExecuteBlockCmd), + /// Follow the given chain's finalized blocks and apply all of its extrinsics. /// /// The initial state is the state of the first finalized block received. @@ -108,32 +112,51 @@ pub struct SharedParams { #[structopt(long)] pub heap_pages: Option, - /// The block hash at which to read state. This is required for execute-block, offchain-worker, - /// or any command that used the live subcommand. + /// The block hash at which to read state. + /// + /// This is required for `execute-block`, `offchain-worker`, 'offchain-worker', or any command + /// that used the `live` subcommand. #[structopt( short, long, multiple = false, parse(try_from_str = parse::hash), required_ifs( - &[("command", "offchain-worker"), ("command", "execute-block"), ("subcommand", "live")] - ) + &[ + ("command", "offchain-worker"), + ("command", "execute-block"), + ("command", "follow-chain"), + ("subcommand", "live") + ] + ), + requires("block-at") )] - block_at: String, - - /// Whether or not to overwrite the code from state with the code from - /// the specified chain spec. - #[structopt(long)] - // TODO: should not be an option in on-runtime-upgrade and follow-chain - pub overwrite_code: bool, + block_at: Option, /// The url to connect to. - // TODO having this a shared parm is a temporary hack; the url is used just - // to get the header/block. We should try and get that out of state, OR allow - // the user to feed in a header/block via file. - // https://github.com/paritytech/substrate/issues/9027 - #[structopt(short, long, default_value = "ws://localhost:9944", parse(try_from_str = parse::url))] - url: String, + /// + /// Needs to be provided if: + /// + /// Only needed when `block-at` needs to be provided. + #[structopt( + short, + long, + parse(try_from_str = parse::url), + )] + url: Option, + + /// Whether or not to overwrite the code from state with the code from the specified chain + /// spec. + /// + /// This is only an option in `offchain-worker` and `execute-block`. In the other commands, it + /// is always enabled. + #[structopt( + long, + required_ifs( + &[("command", "offchain-worker"), ("command", "execute-block")] + ) + )] + pub overwrite_wasm_code: Option, } impl SharedParams { @@ -145,25 +168,18 @@ impl SharedParams { <::Hash as FromStr>::Err: Debug, { self.block_at + .as_ref() + .expect("block at should only be needed in commands that mandate it") .parse::<::Hash>() .map_err(|e| format!("Could not parse block hash: {:?}", e).into()) } -} - -#[derive(structopt::StructOpt, Debug, Clone)] -pub enum DummyCommands { - OnRuntimeUpgrade, - OffchainWorker, -} -impl std::str::FromStr for DummyCommands { - type Err = &'static str; - fn from_str(s: &str) -> Result { - match s.to_lowercase().as_str() { - "on-runtime-upgrade" => Ok(DummyCommands::OnRuntimeUpgrade), - "offchain-worker" => Ok(DummyCommands::OffchainWorker), - _ => Err("Err"), - } + /// Get the configured value of `url`, assuming that it exists. + pub fn url_expect(&self) -> String { + self.url + .as_ref() + .expect("calling this command without --url must be prevented.") + .clone() } } @@ -204,7 +220,7 @@ async fn follow_chain( config: Configuration, ) -> sc_cli::Result<()> where - Block: BlockT + serde::de::DeserializeOwned, + Block: BlockT + serde::de::DeserializeOwned, Block::Hash: FromStr, Block::Header: serde::de::DeserializeOwned, ::Err: Debug, @@ -216,9 +232,8 @@ where types::{traits::SubscriptionClient, v2::params::JsonRpcParams, Subscription}, WsClientBuilder, }; - use sp_state_machine::Backend; - check_spec_name::(shared.url.clone(), config.chain_spec.name().to_string()).await; + check_spec_name::(shared.url_expect(), config.chain_spec.name().to_string()).await; let mut maybe_state_ext = None; @@ -228,7 +243,7 @@ where let client = WsClientBuilder::default() .connection_timeout(std::time::Duration::new(20, 0)) .max_request_body_size(u32::MAX) - .build(&shared.url) + .build(&shared.url_expect()) .await .unwrap(); @@ -241,10 +256,12 @@ where while let Some(header) = subscription.next().await.unwrap() { let hash = header.hash(); let number = header.number(); + let parent = header.parent_hash(); - let block = remote_externalities::rpc_api::get_block::(&shared.url, hash) - .await - .unwrap(); + let block = + remote_externalities::rpc_api::get_block::(&shared.url_expect(), hash) + .await + .unwrap(); log::debug!( target: LOG_TARGET, @@ -257,7 +274,7 @@ where // create an ext at the state of this block, whatever is the first subscription event. if maybe_state_ext.is_none() { let builder = Builder::::new().mode(Mode::Online(OnlineConfig { - transport: shared.url.to_owned().into(), + transport: shared.url_expect().into(), at: Some(header.parent_hash().clone()), ..Default::default() })); @@ -290,6 +307,12 @@ where let mut extensions = sp_externalities::Extensions::default(); extensions.register(TaskExecutorExt::new(TaskExecutor::new())); + let (offchain, _offchain_state) = TestOffchainExt::new(); + let (pool, _pool_state) = TestTransactionPoolExt::new(); + extensions.register(OffchainDbExt::new(offchain.clone())); + extensions.register(OffchainWorkerExt::new(offchain)); + extensions.register(KeystoreExt(Arc::new(KeyStore::new()))); + extensions.register(TransactionPoolExt::new(pool)); let encoded_result = StateMachine::<_, _, NumberFor, _>::new( &state_ext.backend, @@ -303,6 +326,7 @@ where .runtime_code()?, sp_core::testing::TaskExecutor::new(), ) + .set_parent_hash(*parent) .execute(execution.into()) .map_err(|e| { format!("failed to execute 'TryRuntime_execute_block_no_state_root_check': {:?}", e) @@ -310,19 +334,20 @@ where let consumed_weight = ::decode(&mut &*encoded_result) .map_err(|e| format!("failed to decode output: {:?}", e))?; - log::info!( - target: LOG_TARGET, - "before commit overlay changes empty: {:?}, backend root {:?}", - state_ext.overlayed_changes().is_empty(), - state_ext.as_backend().root(), - ); - state_ext.commit_all().unwrap(); - log::info!( - target: LOG_TARGET, - "after commit overlay changes empty: {:?}, backend root {:?}", - state_ext.overlayed_changes().is_empty(), - state_ext.as_backend().root(), + + let storage_changes = changes + .drain_storage_changes::<_, _, NumberFor>( + &state_ext.backend, + None, + Default::default(), + &mut Default::default(), + ) + .unwrap(); + state_ext.backend.apply_transaction( + storage_changes.transaction_storage_root, + storage_changes.transaction, ); + log::info!( target: LOG_TARGET, "executed block {}, consumed weight {}, new storage root {:?}", @@ -332,6 +357,7 @@ where ); } + log::error!(target: LOG_TARGET, "ws subscription must have terminated."); Ok(()) } @@ -360,7 +386,9 @@ where max_runtime_instances, ); - check_spec_name::(shared.url.clone(), config.chain_spec.name().to_string()).await; + if shared.url.is_some() { + check_spec_name::(shared.url_expect(), config.chain_spec.name().to_string()).await; + } let ext = { let builder = match command.state { @@ -370,7 +398,7 @@ where })), State::Live { snapshot_path, modules } => Builder::::new().mode(Mode::Online(OnlineConfig { - transport: shared.url.to_owned().into(), + transport: shared.url_expect().into(), state_snapshot: snapshot_path.as_ref().map(SnapshotConfig::new), modules: modules.to_owned().unwrap_or_default(), at: Some(shared.block_at::()?), @@ -439,13 +467,13 @@ where max_runtime_instances, ); - check_spec_name::(shared.url.clone(), config.chain_spec.name().to_string()).await; + check_spec_name::(shared.url_expect(), config.chain_spec.name().to_string()).await; let mode = match command.state { State::Live { snapshot_path, modules } => { let at = shared.block_at::()?; let online_config = OnlineConfig { - transport: shared.url.to_owned().into(), + transport: shared.url_expect().into(), state_snapshot: snapshot_path.as_ref().map(SnapshotConfig::new), modules: modules.to_owned().unwrap_or_default(), at: Some(at), @@ -464,7 +492,7 @@ where let builder = Builder::::new() .mode(mode) .inject_hashed_key(&[twox_128(b"System"), twox_128(b"LastRuntimeUpgrade")].concat()); - let mut ext = if shared.overwrite_code { + let mut ext = if shared.overwrite_wasm_code.unwrap_or(false) { let (code_key, code) = extract_code(config.chain_spec)?; builder.inject_key_value(&[(code_key, code)]).build().await? } else { @@ -479,7 +507,7 @@ where ext.register_extension(TransactionPoolExt::new(pool)); let header_hash = shared.block_at::()?; - let header = rpc_api::get_header::(shared.url, header_hash).await?; + let header = rpc_api::get_header::(shared.url_expect(), header_hash).await?; let _ = StateMachine::<_, _, NumberFor, _>::new( &ext.backend, @@ -526,9 +554,9 @@ where ); let block_hash = shared.block_at::()?; - let block: Block = rpc_api::get_block::(shared.url.clone(), block_hash).await?; + let block: Block = rpc_api::get_block::(shared.url_expect(), block_hash).await?; - check_spec_name::(shared.url.clone(), config.chain_spec.name().to_string()).await; + check_spec_name::(shared.url_expect(), config.chain_spec.name().to_string()).await; let mode = match command.state { State::Snap { snapshot_path } => { @@ -541,7 +569,7 @@ where let parent_hash = block.header().parent_hash(); let mode = Mode::Online(OnlineConfig { - transport: shared.url.to_owned().into(), + transport: shared.url_expect().into(), state_snapshot: snapshot_path.as_ref().map(SnapshotConfig::new), modules: modules.to_owned().unwrap_or_default(), at: Some(parent_hash.to_owned()), @@ -556,7 +584,7 @@ where let builder = Builder::::new() .mode(mode) .inject_hashed_key(&[twox_128(b"System"), twox_128(b"LastRuntimeUpgrade")].concat()); - let mut ext = if shared.overwrite_code { + let mut ext = if shared.overwrite_wasm_code.unwrap_or(false) { let (code_key, code) = extract_code(config.chain_spec)?; builder.inject_key_value(&[(code_key, code)]).build().await? } else { @@ -604,7 +632,7 @@ where impl TryRuntimeCmd { pub async fn run(&self, config: Configuration) -> sc_cli::Result<()> where - Block: BlockT + serde::de::DeserializeOwned, + Block: BlockT + serde::de::DeserializeOwned, Block::Header: serde::de::DeserializeOwned, Block::Hash: FromStr, ::Err: Debug, From c8f988631033161d5d3d4947a30e2edbc0ab4c43 Mon Sep 17 00:00:00 2001 From: kianenigma Date: Wed, 15 Sep 2021 15:19:15 +0200 Subject: [PATCH 03/16] self-review --- frame/executive/src/lib.rs | 4 ++-- primitives/runtime/src/generic/unchecked_extrinsic.rs | 1 - utils/frame/try-runtime/cli/src/lib.rs | 6 +++--- 3 files changed, 5 insertions(+), 6 deletions(-) diff --git a/frame/executive/src/lib.rs b/frame/executive/src/lib.rs index 8f50f9858489e..8ac5b26cacd92 100644 --- a/frame/executive/src/lib.rs +++ b/frame/executive/src/lib.rs @@ -324,7 +324,7 @@ where } } - pub fn initial_checks(block: &Block) { + fn initial_checks(block: &Block) { sp_tracing::enter_span!(sp_tracing::Level::TRACE, "initial_checks"); let header = block.header(); @@ -376,7 +376,7 @@ where extrinsics.into_iter().for_each(|e| { if let Err(e) = Self::apply_extrinsic(e) { let err: &'static str = e.into(); - panic!("{:?}: {}", e, err) + panic!("{}", err) } }); diff --git a/primitives/runtime/src/generic/unchecked_extrinsic.rs b/primitives/runtime/src/generic/unchecked_extrinsic.rs index b591cd8e054e4..f0e00b7b82971 100644 --- a/primitives/runtime/src/generic/unchecked_extrinsic.rs +++ b/primitives/runtime/src/generic/unchecked_extrinsic.rs @@ -138,7 +138,6 @@ where /// Note that the payload that we sign to produce unchecked extrinsic signature /// is going to be different than the `SignaturePayload` - so the thing the extrinsic /// actually contains. -#[derive(Debug)] pub struct SignedPayload((Call, Extra, Extra::AdditionalSigned)); impl SignedPayload diff --git a/utils/frame/try-runtime/cli/src/lib.rs b/utils/frame/try-runtime/cli/src/lib.rs index 849559c628ea1..dafc5d7b2e001 100644 --- a/utils/frame/try-runtime/cli/src/lib.rs +++ b/utils/frame/try-runtime/cli/src/lib.rs @@ -114,7 +114,7 @@ pub struct SharedParams { /// The block hash at which to read state. /// - /// This is required for `execute-block`, `offchain-worker`, 'offchain-worker', or any command + /// This is required for `execute-block`, `offchain-worker`, 'follow-chain', or any command /// that used the `live` subcommand. #[structopt( short, @@ -126,10 +126,10 @@ pub struct SharedParams { ("command", "offchain-worker"), ("command", "execute-block"), ("command", "follow-chain"), - ("subcommand", "live") + ("state", "live") ] ), - requires("block-at") + requires("url") )] block_at: Option, From 120c30f5a037a65f9c986aa3c18a998f51eeb8ff Mon Sep 17 00:00:00 2001 From: kianenigma Date: Wed, 15 Sep 2021 18:48:03 +0200 Subject: [PATCH 04/16] major mega revamp --- utils/frame/remote-externalities/src/lib.rs | 11 + .../cli/src/commands/execute_block.rs | 180 ++++++ .../cli/src/commands/follow_chain.rs | 172 +++++ .../frame/try-runtime/cli/src/commands/mod.rs | 4 + .../cli/src/commands/offchain_worker.rs | 173 +++++ .../cli/src/commands/on_runtime_upgrade.rs | 83 +++ utils/frame/try-runtime/cli/src/lib.rs | 602 +++--------------- 7 files changed, 705 insertions(+), 520 deletions(-) create mode 100644 utils/frame/try-runtime/cli/src/commands/execute_block.rs create mode 100644 utils/frame/try-runtime/cli/src/commands/follow_chain.rs create mode 100644 utils/frame/try-runtime/cli/src/commands/mod.rs create mode 100644 utils/frame/try-runtime/cli/src/commands/offchain_worker.rs create mode 100644 utils/frame/try-runtime/cli/src/commands/on_runtime_upgrade.rs diff --git a/utils/frame/remote-externalities/src/lib.rs b/utils/frame/remote-externalities/src/lib.rs index 88b74c52d6f79..cb3755f00b29a 100644 --- a/utils/frame/remote-externalities/src/lib.rs +++ b/utils/frame/remote-externalities/src/lib.rs @@ -482,6 +482,17 @@ impl Builder { 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 { let kv = self.pre_build().await?; diff --git a/utils/frame/try-runtime/cli/src/commands/execute_block.rs b/utils/frame/try-runtime/cli/src/commands/execute_block.rs new file mode 100644 index 0000000000000..7278b32cad687 --- /dev/null +++ b/utils/frame/try-runtime/cli/src/commands/execute_block.rs @@ -0,0 +1,180 @@ +use crate::{check_spec_name, extract_code, hash_of, parse, SharedParams, State, LOG_TARGET}; +use remote_externalities::rpc_api; +use sc_executor::NativeElseWasmExecutor; +use sc_service::{Configuration, NativeExecutionDispatch}; +use sp_core::{ + hashing::twox_128, + offchain::{ + testing::{TestOffchainExt, TestTransactionPoolExt}, + OffchainDbExt, OffchainWorkerExt, TransactionPoolExt, + }, + storage::well_known_keys, +}; +use sp_keystore::{testing::KeyStore, KeystoreExt}; +use sp_runtime::traits::{Block as BlockT, Header as HeaderT, NumberFor}; +use sp_state_machine::StateMachine; +use std::{fmt::Debug, str::FromStr, sync::Arc}; + +#[derive(Debug, Clone, structopt::StructOpt)] +pub struct ExecuteBlockCmd { + #[structopt(long)] + overwrite_wasm_code: 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 = parse::hash) + )] + block_at: Option, + + /// The block 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 = parse::hash) + )] + block_uri: Option, + + /// The state type to use. + /// + /// For this command only, if the `live` is used, then state of the parent block is fetched. + #[structopt(subcommand)] + state: State, +} + +impl ExecuteBlockCmd { + fn block_at(&self) -> sc_cli::Result + where + Block::Hash: FromStr, + ::Err: Debug, + { + match (&self.block_at, &self.state) { + (Some(block_at), State::Snap { .. }) => hash_of::(&block_at), + (Some(block_at), State::Live { .. }) => { + log::warn!(target: LOG_TARGET, "--block-at is provided while state type is live, this will most likely lead to a nonsensical result."); + hash_of::(&block_at) + }, + (None, State::Live { at, .. }) => hash_of::(&at), + (None, State::Snap { .. }) => { + panic!("either `--block-at` must be provided, or state must be `live`"); + }, + } + } + + fn block_uri(&self) -> String + where + Block::Hash: FromStr, + ::Err: Debug, + { + match (&self.block_uri, &self.state) { + (Some(block_uri), State::Snap { .. }) => block_uri.to_owned(), + (Some(block_uri), State::Live { .. }) => { + log::warn!(target: LOG_TARGET, "--block-uri is provided while state type is live, this will most likely lead to a nonsensical result."); + block_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 async fn execute_block( + shared: SharedParams, + command: ExecuteBlockCmd, + config: Configuration, +) -> sc_cli::Result<()> +where + Block: BlockT + serde::de::DeserializeOwned, + Block::Hash: FromStr, + ::Err: Debug, + NumberFor: FromStr, + as FromStr>::Err: Debug, + ExecDispatch: NativeExecutionDispatch + 'static, +{ + let wasm_method = shared.wasm_method; + let execution = shared.execution; + let heap_pages = shared.heap_pages.or(config.default_heap_pages); + + let mut changes = Default::default(); + let max_runtime_instances = config.max_runtime_instances; + let executor = NativeElseWasmExecutor::::new( + wasm_method.into(), + heap_pages, + max_runtime_instances, + ); + + let block_at = command.block_at::()?; + let block_uri = command.block_uri::(); + let block: Block = rpc_api::get_block::(block_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_uri, + parent_hash + ); + + check_spec_name::(block_uri.clone(), config.chain_spec.name().to_string()).await; + + let ext = { + let builder = command + .state + .builder::()? + // make sure the state is being build with the parent hash, if it is online. + .overwrite_online_at(parent_hash.to_owned()) + .inject_hashed_key(&[twox_128(b"System"), twox_128(b"LastRuntimeUpgrade")].concat()); + + 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) + }; + + let mut ext = builder.build().await?; + + // register externality extensions in order to provide host interface for OCW to the + // runtime. + let (offchain, _offchain_state) = TestOffchainExt::new(); + let (pool, _pool_state) = TestTransactionPoolExt::new(); + ext.register_extension(OffchainDbExt::new(offchain.clone())); + ext.register_extension(OffchainWorkerExt::new(offchain)); + ext.register_extension(KeystoreExt(Arc::new(KeyStore::new()))); + ext.register_extension(TransactionPoolExt::new(pool)); + + ext + }; + + // 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 _encoded_result = StateMachine::<_, _, NumberFor, _>::new( + &ext.backend, + None, + &mut changes, + &executor, + "Core_execute_block", + block.encode().as_ref(), + ext.extensions, + &sp_state_machine::backend::BackendRuntimeCode::new(&ext.backend).runtime_code()?, + sp_core::testing::TaskExecutor::new(), + ) + .execute(execution.into()) + .map_err(|e| format!("failed to execute 'Core_execute_block': {:?}", e))?; + + log::info!(target: LOG_TARGET, "Core_execute_block executed without errors."); + + Ok(()) +} diff --git a/utils/frame/try-runtime/cli/src/commands/follow_chain.rs b/utils/frame/try-runtime/cli/src/commands/follow_chain.rs new file mode 100644 index 0000000000000..51729ea4a3028 --- /dev/null +++ b/utils/frame/try-runtime/cli/src/commands/follow_chain.rs @@ -0,0 +1,172 @@ +use crate::{check_spec_name, extract_code, parse, SharedParams, LOG_TARGET}; +use jsonrpsee_ws_client::{ + types::{traits::SubscriptionClient, v2::params::JsonRpcParams, Subscription}, + WsClientBuilder, +}; +use parity_scale_codec::Decode; +use remote_externalities::{rpc_api, Builder, Mode, OnlineConfig}; +use sc_executor::{NativeElseWasmExecutor, NativeExecutionDispatch}; +use sc_service::Configuration; +use sp_core::{ + offchain::{ + testing::{TestOffchainExt, TestTransactionPoolExt}, + OffchainDbExt, OffchainWorkerExt, TransactionPoolExt, + }, + testing::TaskExecutor, + traits::TaskExecutorExt, + H256, +}; +use sp_keystore::{testing::KeyStore, KeystoreExt}; +use sp_runtime::traits::{Block as BlockT, Header, NumberFor}; +use sp_state_machine::StateMachine; +use std::{fmt::Debug, str::FromStr, sync::Arc}; + +#[derive(Debug, Clone, structopt::StructOpt)] +pub struct FollowChainCmd { + /// The url to connect to + #[structopt( + short, + long, + parse(try_from_str = parse::url), + )] + uri: String, +} + +pub async fn follow_chain( + shared: SharedParams, + command: FollowChainCmd, + config: Configuration, +) -> sc_cli::Result<()> +where + Block: BlockT + serde::de::DeserializeOwned, + Block::Hash: FromStr, + Block::Header: serde::de::DeserializeOwned, + ::Err: Debug, + NumberFor: FromStr, + as FromStr>::Err: Debug, + ExecDispatch: NativeExecutionDispatch + 'static, +{ + check_spec_name::(command.uri.clone(), config.chain_spec.name().to_string()).await; + + let mut maybe_state_ext = None; + + let sub = "chain_subscribeFinalizedHeads"; + let unsub = "chain_unsubscribeFinalizedHeads"; + + let client = WsClientBuilder::default() + .connection_timeout(std::time::Duration::new(20, 0)) + .max_request_body_size(u32::MAX) + .build(&command.uri) + .await + .unwrap(); + + log::info!(target: LOG_TARGET, "subscribing to {:?} / {:?}", sub, unsub); + let mut subscription: Subscription = + client.subscribe(&sub, JsonRpcParams::NoParams, &unsub).await.unwrap(); + + let (code_key, code) = extract_code(config.chain_spec)?; + + while let Some(header) = subscription.next().await.unwrap() { + let hash = header.hash(); + let number = header.number(); + let parent = header.parent_hash(); + + let block = rpc_api::get_block::(&command.uri, hash).await.unwrap(); + + log::debug!( + target: LOG_TARGET, + "new block event: {:?} => {:?}, extrinsics: {}", + hash, + number, + block.extrinsics().len() + ); + + // create an ext at the state of this block, whatever is the first subscription event. + if maybe_state_ext.is_none() { + let builder = Builder::::new().mode(Mode::Online(OnlineConfig { + transport: command.uri.clone().into(), + at: Some(header.parent_hash().clone()), + ..Default::default() + })); + + let new_ext = + builder.inject_key_value(&[(code_key.clone(), code.clone())]).build().await?; + log::info!( + target: LOG_TARGET, + "initialized state externalities at {:?}, storage root {:?}", + number, + new_ext.as_backend().root() + ); + maybe_state_ext = Some(new_ext); + } + + let state_ext = + maybe_state_ext.as_mut().expect("state_ext either existed or was just created"); + + let wasm_method = shared.wasm_method; + let execution = shared.execution; + let heap_pages = shared.heap_pages.or(config.default_heap_pages); + + let mut changes = Default::default(); + let max_runtime_instances = config.max_runtime_instances; + let executor = NativeElseWasmExecutor::::new( + wasm_method.into(), + heap_pages, + max_runtime_instances, + ); + + let mut extensions = sp_externalities::Extensions::default(); + extensions.register(TaskExecutorExt::new(TaskExecutor::new())); + let (offchain, _offchain_state) = TestOffchainExt::new(); + let (pool, _pool_state) = TestTransactionPoolExt::new(); + extensions.register(OffchainDbExt::new(offchain.clone())); + extensions.register(OffchainWorkerExt::new(offchain)); + extensions.register(KeystoreExt(Arc::new(KeyStore::new()))); + extensions.register(TransactionPoolExt::new(pool)); + + let encoded_result = StateMachine::<_, _, NumberFor, _>::new( + &state_ext.backend, + None, + &mut changes, + &executor, + "TryRuntime_execute_block_no_state_root_check", + block.encode().as_ref(), + extensions, + &sp_state_machine::backend::BackendRuntimeCode::new(&state_ext.backend) + .runtime_code()?, + sp_core::testing::TaskExecutor::new(), + ) + .set_parent_hash(*parent) + .execute(execution.into()) + .map_err(|e| { + format!("failed to execute 'TryRuntime_execute_block_no_state_root_check': {:?}", e) + })?; + + let consumed_weight = ::decode(&mut &*encoded_result) + .map_err(|e| format!("failed to decode output: {:?}", e))?; + + let storage_changes = changes + .drain_storage_changes::<_, _, NumberFor>( + &state_ext.backend, + None, + Default::default(), + &mut Default::default(), + ) + .unwrap(); + state_ext.backend.apply_transaction( + storage_changes.transaction_storage_root, + storage_changes.transaction, + ); + + log::info!( + target: LOG_TARGET, + "executed block {}, consumed weight {}, new storage root {:?}", + number, + consumed_weight, + state_ext.as_backend().root(), + ); + } + + log::error!(target: LOG_TARGET, "ws subscription must have terminated."); + Ok(()) +} diff --git a/utils/frame/try-runtime/cli/src/commands/mod.rs b/utils/frame/try-runtime/cli/src/commands/mod.rs new file mode 100644 index 0000000000000..fda1d82f1449b --- /dev/null +++ b/utils/frame/try-runtime/cli/src/commands/mod.rs @@ -0,0 +1,4 @@ +pub mod execute_block; +pub mod follow_chain; +pub mod offchain_worker; +pub mod on_runtime_upgrade; diff --git a/utils/frame/try-runtime/cli/src/commands/offchain_worker.rs b/utils/frame/try-runtime/cli/src/commands/offchain_worker.rs new file mode 100644 index 0000000000000..9c968e38e3e4d --- /dev/null +++ b/utils/frame/try-runtime/cli/src/commands/offchain_worker.rs @@ -0,0 +1,173 @@ +use crate::{check_spec_name, extract_code, hash_of, parse, SharedParams, State, LOG_TARGET}; +use parity_scale_codec::Encode; +use remote_externalities::rpc_api; +use sc_executor::{NativeElseWasmExecutor, NativeExecutionDispatch}; +use sc_service::Configuration; +use sp_core::{ + offchain::{ + testing::{TestOffchainExt, TestTransactionPoolExt}, + OffchainDbExt, OffchainWorkerExt, TransactionPoolExt, + }, + storage::well_known_keys, + testing::TaskExecutor, + twox_128, +}; +use sp_keystore::{testing::KeyStore, KeystoreExt}; +use sp_runtime::traits::{Block as BlockT, Header, NumberFor}; +use sp_state_machine::{backend::BackendRuntimeCode, StateMachine}; +use std::{fmt::Debug, str::FromStr, sync::Arc}; + +#[derive(Debug, Clone, structopt::StructOpt)] +pub struct OffchainWorkerCmd { + #[structopt(long)] + overwrite_wasm_code: 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 = parse::hash) + )] + header_at: Option, + + /// The block 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 = parse::hash) + )] + header_uri: Option, + + /// The state type to use. + #[structopt(subcommand)] + pub state: State, +} + +impl OffchainWorkerCmd { + fn header_at(&self) -> sc_cli::Result + where + Block::Hash: FromStr, + ::Err: Debug, + { + match (&self.header_at, &self.state) { + (Some(header_at), State::Snap { .. }) => hash_of::(&header_at), + (Some(header_at), State::Live { .. }) => { + log::warn!(target: LOG_TARGET, "--header-at is provided while state type is live, this will most likely lead to a nonsensical result."); + hash_of::(&header_at) + }, + (None, State::Live { at, .. }) => hash_of::(&at), + (None, State::Snap { .. }) => { + panic!("either `--header-at` must be provided, or state must be `live`"); + }, + } + } + + fn header_uri(&self) -> String + where + Block::Hash: FromStr, + ::Err: Debug, + { + match (&self.header_uri, &self.state) { + (Some(header_uri), State::Snap { .. }) => header_uri.to_owned(), + (Some(header_uri), State::Live { .. }) => { + log::warn!(target: LOG_TARGET, "--header-uri is provided while state type is live, this will most likely lead to a nonsensical result."); + header_uri.to_owned() + }, + (None, State::Live { uri, .. }) => uri.clone(), + (None, State::Snap { .. }) => { + panic!("either `--header-uri` must be provided, or state must be `live`"); + }, + } + } +} + +pub async fn offchain_worker( + shared: SharedParams, + command: OffchainWorkerCmd, + config: Configuration, +) -> sc_cli::Result<()> +where + Block: BlockT + serde::de::DeserializeOwned, + Block::Hash: FromStr, + Block::Header: serde::de::DeserializeOwned, + ::Err: Debug, + NumberFor: FromStr, + as FromStr>::Err: Debug, + ExecDispatch: NativeExecutionDispatch + 'static, +{ + let wasm_method = shared.wasm_method; + let execution = shared.execution; + let heap_pages = shared.heap_pages.or(config.default_heap_pages); + + let mut changes = Default::default(); + let max_runtime_instances = config.max_runtime_instances; + let executor = NativeElseWasmExecutor::::new( + wasm_method.into(), + heap_pages, + max_runtime_instances, + ); + + let header_at = command.header_at::()?; + let header_uri = command.header_uri::(); + + let header = rpc_api::get_header::(header_uri.clone(), header_at).await?; + log::info!( + target: LOG_TARGET, + "fetched header from {:?}, block number: {:?}", + header_uri, + header.number() + ); + + check_spec_name::(header_uri, config.chain_spec.name().to_string()).await; + + let ext = { + let builder = command + .state + .builder::()? + .inject_hashed_key(&[twox_128(b"System"), twox_128(b"LastRuntimeUpgrade")].concat()); + + 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) + }; + + let mut ext = builder.build().await?; + + // register externality extensions in order to provide host interface for OCW to the + // runtime. + let (offchain, _offchain_state) = TestOffchainExt::new(); + let (pool, _pool_state) = TestTransactionPoolExt::new(); + ext.register_extension(OffchainDbExt::new(offchain.clone())); + ext.register_extension(OffchainWorkerExt::new(offchain)); + ext.register_extension(KeystoreExt(Arc::new(KeyStore::new()))); + ext.register_extension(TransactionPoolExt::new(pool)); + + ext + }; + + let _ = StateMachine::<_, _, NumberFor, _>::new( + &ext.backend, + None, + &mut changes, + &executor, + "OffchainWorkerApi_offchain_worker", + header.encode().as_ref(), + ext.extensions, + &BackendRuntimeCode::new(&ext.backend).runtime_code()?, + TaskExecutor::new(), + ) + .execute(execution.into()) + .map_err(|e| format!("failed to execute 'OffchainWorkerApi_offchain_worker': {:?}", e))?; + + log::info!(target: LOG_TARGET, "OffchainWorkerApi_offchain_worker executed without errors."); + + Ok(()) +} diff --git a/utils/frame/try-runtime/cli/src/commands/on_runtime_upgrade.rs b/utils/frame/try-runtime/cli/src/commands/on_runtime_upgrade.rs new file mode 100644 index 0000000000000..a3d47da5f1b97 --- /dev/null +++ b/utils/frame/try-runtime/cli/src/commands/on_runtime_upgrade.rs @@ -0,0 +1,83 @@ +use std::{fmt::Debug, str::FromStr}; + +use parity_scale_codec::Decode; +use sc_executor::{NativeElseWasmExecutor, NativeExecutionDispatch}; +use sc_service::Configuration; +use sp_core::twox_128; +use sp_runtime::traits::{Block as BlockT, NumberFor}; +use sp_state_machine::StateMachine; + +use crate::{check_spec_name, extract_code, SharedParams, State, LOG_TARGET}; + +#[derive(Debug, Clone, structopt::StructOpt)] +pub struct OnRuntimeUpgradeCmd { + /// The state type to use. + #[structopt(subcommand)] + pub state: State, +} + +pub async fn on_runtime_upgrade( + shared: SharedParams, + command: OnRuntimeUpgradeCmd, + config: Configuration, +) -> sc_cli::Result<()> +where + Block: BlockT + serde::de::DeserializeOwned, + Block::Hash: FromStr, + ::Err: Debug, + NumberFor: FromStr, + as FromStr>::Err: Debug, + ExecDispatch: NativeExecutionDispatch + 'static, +{ + let wasm_method = shared.wasm_method; + let execution = shared.execution; + let heap_pages = shared.heap_pages.or(config.default_heap_pages); + + let mut changes = Default::default(); + let max_runtime_instances = config.max_runtime_instances; + let executor = NativeElseWasmExecutor::::new( + wasm_method.into(), + heap_pages, + max_runtime_instances, + ); + + if let Some(uri) = command.state.live_uri() { + check_spec_name::(uri, config.chain_spec.name().to_string()).await; + } + + let ext = { + let builder = command.state.builder::()?; + let (code_key, code) = extract_code(config.chain_spec)?; + builder + .inject_key_value(&[(code_key, code)]) + .inject_hashed_key(&[twox_128(b"System"), twox_128(b"LastRuntimeUpgrade")].concat()) + .build() + .await? + }; + + let encoded_result = StateMachine::<_, _, NumberFor, _>::new( + &ext.backend, + None, + &mut changes, + &executor, + "TryRuntime_on_runtime_upgrade", + &[], + ext.extensions, + &sp_state_machine::backend::BackendRuntimeCode::new(&ext.backend).runtime_code()?, + sp_core::testing::TaskExecutor::new(), + ) + .execute(execution.into()) + .map_err(|e| format!("failed to execute 'TryRuntime_on_runtime_upgrade': {:?}", e))?; + + let (weight, total_weight) = <(u64, u64) as Decode>::decode(&mut &*encoded_result) + .map_err(|e| format!("failed to decode output: {:?}", e))?; + log::info!( + target: LOG_TARGET, + "TryRuntime_on_runtime_upgrade executed without errors. Consumed weight = {}, total weight = {} ({})", + weight, + total_weight, + weight as f64 / total_weight as f64 + ); + + Ok(()) +} diff --git a/utils/frame/try-runtime/cli/src/lib.rs b/utils/frame/try-runtime/cli/src/lib.rs index dafc5d7b2e001..f76c63c45d99c 100644 --- a/utils/frame/try-runtime/cli/src/lib.rs +++ b/utils/frame/try-runtime/cli/src/lib.rs @@ -17,67 +17,38 @@ //! `Structopt`-ready structs for `try-runtime`. -use parity_scale_codec::{Decode, Encode}; -use remote_externalities::{rpc_api, Builder, Mode, OfflineConfig, OnlineConfig, SnapshotConfig}; +use remote_externalities::{Builder, Mode, OfflineConfig, OnlineConfig, SnapshotConfig}; use sc_chain_spec::ChainSpec; use sc_cli::{CliConfiguration, ExecutionStrategy, WasmExecutionMethod}; -use sc_executor::NativeElseWasmExecutor; use sc_service::{Configuration, NativeExecutionDispatch}; use sp_core::{ - hashing::twox_128, - offchain::{ - testing::{TestOffchainExt, TestTransactionPoolExt}, - OffchainDbExt, OffchainWorkerExt, TransactionPoolExt, - }, storage::{well_known_keys, StorageData, StorageKey}, - testing::TaskExecutor, - traits::TaskExecutorExt, H256, }; -use sp_keystore::{testing::KeyStore, KeystoreExt}; -use sp_runtime::traits::{Block as BlockT, Header as HeaderT, NumberFor}; -use sp_state_machine::StateMachine; -use std::{fmt::Debug, path::PathBuf, str::FromStr, sync::Arc}; - -const LOG_TARGET: &'static str = "try-runtime::cli"; +use sp_runtime::traits::{Block as BlockT, NumberFor}; +use std::{fmt::Debug, path::PathBuf, str::FromStr}; -mod parse; +mod commands; +pub(crate) mod parse; +pub(crate) const LOG_TARGET: &'static str = "try-runtime::cli"; /// Possible subcommands of `try-runtime`. #[derive(Debug, Clone, structopt::StructOpt)] pub enum Command { /// Execute "TryRuntime_on_runtime_upgrade" against the given runtime state. - OnRuntimeUpgrade(OnRuntimeUpgradeCmd), + OnRuntimeUpgrade(commands::on_runtime_upgrade::OnRuntimeUpgradeCmd), /// Execute "OffchainWorkerApi_offchain_worker" against the given runtime state. - OffchainWorker(OffchainWorkerCmd), + OffchainWorker(commands::offchain_worker::OffchainWorkerCmd), /// Execute "Core_execute_block" using the given block and the runtime state of the parent /// block. - ExecuteBlock(ExecuteBlockCmd), + ExecuteBlock(commands::execute_block::ExecuteBlockCmd), /// Follow the given chain's finalized blocks and apply all of its extrinsics. /// /// The initial state is the state of the first finalized block received. - FollowChain, -} - -#[derive(Debug, Clone, structopt::StructOpt)] -pub struct OnRuntimeUpgradeCmd { - #[structopt(subcommand)] - pub state: State, -} - -#[derive(Debug, Clone, structopt::StructOpt)] -pub struct OffchainWorkerCmd { - #[structopt(subcommand)] - pub state: State, -} - -#[derive(Debug, Clone, structopt::StructOpt)] -pub struct ExecuteBlockCmd { - #[structopt(subcommand)] - pub state: State, + FollowChain(commands::follow_chain::FollowChainCmd), } #[derive(Debug, Clone, structopt::StructOpt)] @@ -111,76 +82,6 @@ pub struct SharedParams { /// sc_service::Configuration.default_heap_pages. #[structopt(long)] pub heap_pages: Option, - - /// The block hash at which to read state. - /// - /// This is required for `execute-block`, `offchain-worker`, 'follow-chain', or any command - /// that used the `live` subcommand. - #[structopt( - short, - long, - multiple = false, - parse(try_from_str = parse::hash), - required_ifs( - &[ - ("command", "offchain-worker"), - ("command", "execute-block"), - ("command", "follow-chain"), - ("state", "live") - ] - ), - requires("url") - )] - block_at: Option, - - /// The url to connect to. - /// - /// Needs to be provided if: - /// - /// Only needed when `block-at` needs to be provided. - #[structopt( - short, - long, - parse(try_from_str = parse::url), - )] - url: Option, - - /// Whether or not to overwrite the code from state with the code from the specified chain - /// spec. - /// - /// This is only an option in `offchain-worker` and `execute-block`. In the other commands, it - /// is always enabled. - #[structopt( - long, - required_ifs( - &[("command", "offchain-worker"), ("command", "execute-block")] - ) - )] - pub overwrite_wasm_code: Option, -} - -impl SharedParams { - /// Get the configured value of `block_at`, interpreted as the hash type of `Block`. - pub fn block_at(&self) -> sc_cli::Result - where - Block: BlockT, - ::Hash: FromStr, - <::Hash as FromStr>::Err: Debug, - { - self.block_at - .as_ref() - .expect("block at should only be needed in commands that mandate it") - .parse::<::Hash>() - .map_err(|e| format!("Could not parse block hash: {:?}", e).into()) - } - - /// Get the configured value of `url`, assuming that it exists. - pub fn url_expect(&self) -> String { - self.url - .as_ref() - .expect("calling this command without --url must be prevented.") - .clone() - } } /// Various commands to try out against runtime state at a specific block. @@ -205,6 +106,23 @@ pub enum State { /// Use a live chain as the source of runtime state. Live { + /// The url to connect to + #[structopt( + short, + long, + parse(try_from_str = parse::url), + )] + uri: String, + + /// The block hash at which to fetch the state. + #[structopt( + short, + long, + multiple = false, + parse(try_from_str = parse::hash), + )] + at: String, + /// An optional state snapshot file to WRITE to. Not written if set to `None`. #[structopt(short, long)] snapshot_path: Option, @@ -215,418 +133,34 @@ pub enum State { }, } -async fn follow_chain( - shared: SharedParams, - config: Configuration, -) -> sc_cli::Result<()> -where - Block: BlockT + serde::de::DeserializeOwned, - Block::Hash: FromStr, - Block::Header: serde::de::DeserializeOwned, - ::Err: Debug, - NumberFor: FromStr, - as FromStr>::Err: Debug, - ExecDispatch: NativeExecutionDispatch + 'static, -{ - use jsonrpsee_ws_client::{ - types::{traits::SubscriptionClient, v2::params::JsonRpcParams, Subscription}, - WsClientBuilder, - }; - - check_spec_name::(shared.url_expect(), config.chain_spec.name().to_string()).await; - - let mut maybe_state_ext = None; - - let sub = "chain_subscribeFinalizedHeads"; - let unsub = "chain_unsubscribeFinalizedHeads"; - - let client = WsClientBuilder::default() - .connection_timeout(std::time::Duration::new(20, 0)) - .max_request_body_size(u32::MAX) - .build(&shared.url_expect()) - .await - .unwrap(); - - log::info!(target: LOG_TARGET, "subscribing to {:?} / {:?}", sub, unsub); - let mut subscription: Subscription = - client.subscribe(&sub, JsonRpcParams::NoParams, &unsub).await.unwrap(); - - let (code_key, code) = extract_code(config.chain_spec)?; - - while let Some(header) = subscription.next().await.unwrap() { - let hash = header.hash(); - let number = header.number(); - let parent = header.parent_hash(); - - let block = - remote_externalities::rpc_api::get_block::(&shared.url_expect(), hash) - .await - .unwrap(); - - log::debug!( - target: LOG_TARGET, - "new block event: {:?} => {:?}, extrinsics: {}", - hash, - number, - block.extrinsics().len() - ); - - // create an ext at the state of this block, whatever is the first subscription event. - if maybe_state_ext.is_none() { - let builder = Builder::::new().mode(Mode::Online(OnlineConfig { - transport: shared.url_expect().into(), - at: Some(header.parent_hash().clone()), - ..Default::default() - })); - - let new_ext = - builder.inject_key_value(&[(code_key.clone(), code.clone())]).build().await?; - log::info!( - target: LOG_TARGET, - "initialized state externalities at {:?}, storage root {:?}", - number, - new_ext.as_backend().root() - ); - maybe_state_ext = Some(new_ext); - } - - let state_ext = - maybe_state_ext.as_mut().expect("state_ext either existed or was just created"); - - let wasm_method = shared.wasm_method; - let execution = shared.execution; - let heap_pages = shared.heap_pages.or(config.default_heap_pages); - - let mut changes = Default::default(); - let max_runtime_instances = config.max_runtime_instances; - let executor = NativeElseWasmExecutor::::new( - wasm_method.into(), - heap_pages, - max_runtime_instances, - ); - - let mut extensions = sp_externalities::Extensions::default(); - extensions.register(TaskExecutorExt::new(TaskExecutor::new())); - let (offchain, _offchain_state) = TestOffchainExt::new(); - let (pool, _pool_state) = TestTransactionPoolExt::new(); - extensions.register(OffchainDbExt::new(offchain.clone())); - extensions.register(OffchainWorkerExt::new(offchain)); - extensions.register(KeystoreExt(Arc::new(KeyStore::new()))); - extensions.register(TransactionPoolExt::new(pool)); - - let encoded_result = StateMachine::<_, _, NumberFor, _>::new( - &state_ext.backend, - None, - &mut changes, - &executor, - "TryRuntime_execute_block_no_state_root_check", - block.encode().as_ref(), - extensions, - &sp_state_machine::backend::BackendRuntimeCode::new(&state_ext.backend) - .runtime_code()?, - sp_core::testing::TaskExecutor::new(), - ) - .set_parent_hash(*parent) - .execute(execution.into()) - .map_err(|e| { - format!("failed to execute 'TryRuntime_execute_block_no_state_root_check': {:?}", e) - })?; - - let consumed_weight = ::decode(&mut &*encoded_result) - .map_err(|e| format!("failed to decode output: {:?}", e))?; - - let storage_changes = changes - .drain_storage_changes::<_, _, NumberFor>( - &state_ext.backend, - None, - Default::default(), - &mut Default::default(), - ) - .unwrap(); - state_ext.backend.apply_transaction( - storage_changes.transaction_storage_root, - storage_changes.transaction, - ); - - log::info!( - target: LOG_TARGET, - "executed block {}, consumed weight {}, new storage root {:?}", - number, - consumed_weight, - state_ext.as_backend().root(), - ); - } - - log::error!(target: LOG_TARGET, "ws subscription must have terminated."); - Ok(()) -} - -async fn on_runtime_upgrade( - shared: SharedParams, - command: OnRuntimeUpgradeCmd, - config: Configuration, -) -> sc_cli::Result<()> -where - Block: BlockT + serde::de::DeserializeOwned, - Block::Hash: FromStr, - ::Err: Debug, - NumberFor: FromStr, - as FromStr>::Err: Debug, - ExecDispatch: NativeExecutionDispatch + 'static, -{ - let wasm_method = shared.wasm_method; - let execution = shared.execution; - let heap_pages = shared.heap_pages.or(config.default_heap_pages); - - let mut changes = Default::default(); - let max_runtime_instances = config.max_runtime_instances; - let executor = NativeElseWasmExecutor::::new( - wasm_method.into(), - heap_pages, - max_runtime_instances, - ); - - if shared.url.is_some() { - check_spec_name::(shared.url_expect(), config.chain_spec.name().to_string()).await; - } - - let ext = { - let builder = match command.state { +impl State { + pub(crate) fn builder(&self) -> sc_cli::Result> + where + Block::Hash: FromStr, + ::Err: Debug, + { + Ok(match self { State::Snap { snapshot_path } => Builder::::new().mode(Mode::Offline(OfflineConfig { state_snapshot: SnapshotConfig::new(snapshot_path), })), - State::Live { snapshot_path, modules } => + State::Live { snapshot_path, modules, uri, at } => Builder::::new().mode(Mode::Online(OnlineConfig { - transport: shared.url_expect().into(), + transport: uri.to_owned().into(), state_snapshot: snapshot_path.as_ref().map(SnapshotConfig::new), modules: modules.to_owned().unwrap_or_default(), - at: Some(shared.block_at::()?), + at: Some(hash_of::(at)?), ..Default::default() })), - }; - - let (code_key, code) = extract_code(config.chain_spec)?; - builder - .inject_key_value(&[(code_key, code)]) - .inject_hashed_key(&[twox_128(b"System"), twox_128(b"LastRuntimeUpgrade")].concat()) - .build() - .await? - }; - - let encoded_result = StateMachine::<_, _, NumberFor, _>::new( - &ext.backend, - None, - &mut changes, - &executor, - "TryRuntime_on_runtime_upgrade", - &[], - ext.extensions, - &sp_state_machine::backend::BackendRuntimeCode::new(&ext.backend).runtime_code()?, - sp_core::testing::TaskExecutor::new(), - ) - .execute(execution.into()) - .map_err(|e| format!("failed to execute 'TryRuntime_on_runtime_upgrade': {:?}", e))?; - - let (weight, total_weight) = <(u64, u64) as Decode>::decode(&mut &*encoded_result) - .map_err(|e| format!("failed to decode output: {:?}", e))?; - log::info!( - target: LOG_TARGET, - "TryRuntime_on_runtime_upgrade executed without errors. Consumed weight = {}, total weight = {} ({})", - weight, - total_weight, - weight as f64 / total_weight as f64 - ); - - Ok(()) -} - -async fn offchain_worker( - shared: SharedParams, - command: OffchainWorkerCmd, - config: Configuration, -) -> sc_cli::Result<()> -where - Block: BlockT + serde::de::DeserializeOwned, - Block::Hash: FromStr, - Block::Header: serde::de::DeserializeOwned, - ::Err: Debug, - NumberFor: FromStr, - as FromStr>::Err: Debug, - ExecDispatch: NativeExecutionDispatch + 'static, -{ - let wasm_method = shared.wasm_method; - let execution = shared.execution; - let heap_pages = shared.heap_pages.or(config.default_heap_pages); - - let mut changes = Default::default(); - let max_runtime_instances = config.max_runtime_instances; - let executor = NativeElseWasmExecutor::::new( - wasm_method.into(), - heap_pages, - max_runtime_instances, - ); - - check_spec_name::(shared.url_expect(), config.chain_spec.name().to_string()).await; - - let mode = match command.state { - State::Live { snapshot_path, modules } => { - let at = shared.block_at::()?; - let online_config = OnlineConfig { - transport: shared.url_expect().into(), - state_snapshot: snapshot_path.as_ref().map(SnapshotConfig::new), - modules: modules.to_owned().unwrap_or_default(), - at: Some(at), - ..Default::default() - }; - - Mode::Online(online_config) - }, - State::Snap { snapshot_path } => { - let mode = - Mode::Offline(OfflineConfig { state_snapshot: SnapshotConfig::new(snapshot_path) }); - - mode - }, - }; - let builder = Builder::::new() - .mode(mode) - .inject_hashed_key(&[twox_128(b"System"), twox_128(b"LastRuntimeUpgrade")].concat()); - let mut ext = if shared.overwrite_wasm_code.unwrap_or(false) { - let (code_key, code) = extract_code(config.chain_spec)?; - builder.inject_key_value(&[(code_key, code)]).build().await? - } else { - builder.inject_hashed_key(well_known_keys::CODE).build().await? - }; - - let (offchain, _offchain_state) = TestOffchainExt::new(); - let (pool, _pool_state) = TestTransactionPoolExt::new(); - ext.register_extension(OffchainDbExt::new(offchain.clone())); - ext.register_extension(OffchainWorkerExt::new(offchain)); - ext.register_extension(KeystoreExt(Arc::new(KeyStore::new()))); - ext.register_extension(TransactionPoolExt::new(pool)); - - let header_hash = shared.block_at::()?; - let header = rpc_api::get_header::(shared.url_expect(), header_hash).await?; - - let _ = StateMachine::<_, _, NumberFor, _>::new( - &ext.backend, - None, - &mut changes, - &executor, - "OffchainWorkerApi_offchain_worker", - header.encode().as_ref(), - ext.extensions, - &sp_state_machine::backend::BackendRuntimeCode::new(&ext.backend).runtime_code()?, - sp_core::testing::TaskExecutor::new(), - ) - .execute(execution.into()) - .map_err(|e| format!("failed to execute 'OffchainWorkerApi_offchain_worker': {:?}", e))?; - - log::info!(target: LOG_TARGET, "OffchainWorkerApi_offchain_worker executed without errors."); - - Ok(()) -} - -async fn execute_block( - shared: SharedParams, - command: ExecuteBlockCmd, - config: Configuration, -) -> sc_cli::Result<()> -where - Block: BlockT + serde::de::DeserializeOwned, - Block::Hash: FromStr, - ::Err: Debug, - NumberFor: FromStr, - as FromStr>::Err: Debug, - ExecDispatch: NativeExecutionDispatch + 'static, -{ - let wasm_method = shared.wasm_method; - let execution = shared.execution; - let heap_pages = shared.heap_pages.or(config.default_heap_pages); - - let mut changes = Default::default(); - let max_runtime_instances = config.max_runtime_instances; - let executor = NativeElseWasmExecutor::::new( - wasm_method.into(), - heap_pages, - max_runtime_instances, - ); - - let block_hash = shared.block_at::()?; - let block: Block = rpc_api::get_block::(shared.url_expect(), block_hash).await?; - - check_spec_name::(shared.url_expect(), config.chain_spec.name().to_string()).await; - - let mode = match command.state { - State::Snap { snapshot_path } => { - let mode = - Mode::Offline(OfflineConfig { state_snapshot: SnapshotConfig::new(snapshot_path) }); - - mode - }, - State::Live { snapshot_path, modules } => { - let parent_hash = block.header().parent_hash(); - - let mode = Mode::Online(OnlineConfig { - transport: shared.url_expect().into(), - state_snapshot: snapshot_path.as_ref().map(SnapshotConfig::new), - modules: modules.to_owned().unwrap_or_default(), - at: Some(parent_hash.to_owned()), - ..Default::default() - }); - - mode - }, - }; - - let ext = { - let builder = Builder::::new() - .mode(mode) - .inject_hashed_key(&[twox_128(b"System"), twox_128(b"LastRuntimeUpgrade")].concat()); - let mut ext = if shared.overwrite_wasm_code.unwrap_or(false) { - let (code_key, code) = extract_code(config.chain_spec)?; - builder.inject_key_value(&[(code_key, code)]).build().await? - } else { - builder.inject_hashed_key(well_known_keys::CODE).build().await? - }; - - // register externality extensions in order to provide host interface for OCW to the - // runtime. - let (offchain, _offchain_state) = TestOffchainExt::new(); - let (pool, _pool_state) = TestTransactionPoolExt::new(); - ext.register_extension(OffchainDbExt::new(offchain.clone())); - ext.register_extension(OffchainWorkerExt::new(offchain)); - ext.register_extension(KeystoreExt(Arc::new(KeyStore::new()))); - ext.register_extension(TransactionPoolExt::new(pool)); - - ext - }; - - // 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 _encoded_result = StateMachine::<_, _, NumberFor, _>::new( - &ext.backend, - None, - &mut changes, - &executor, - "Core_execute_block", - block.encode().as_ref(), - ext.extensions, - &sp_state_machine::backend::BackendRuntimeCode::new(&ext.backend).runtime_code()?, - sp_core::testing::TaskExecutor::new(), - ) - .execute(execution.into()) - .map_err(|e| format!("failed to execute 'Core_execute_block': {:?}", e))?; - debug_assert!(_encoded_result == vec![1]); - - log::info!(target: LOG_TARGET, "Core_execute_block executed without errors."); + }) + } - Ok(()) + pub(crate) fn live_uri(&self) -> Option { + match self { + State::Live { uri, .. } => Some(uri.clone()), + _ => None, + } + } } impl TryRuntimeCmd { @@ -642,15 +176,33 @@ impl TryRuntimeCmd { { match &self.command { Command::OnRuntimeUpgrade(ref cmd) => - on_runtime_upgrade::(self.shared.clone(), cmd.clone(), config) - .await, + commands::on_runtime_upgrade::on_runtime_upgrade::( + self.shared.clone(), + cmd.clone(), + config, + ) + .await, Command::OffchainWorker(cmd) => - offchain_worker::(self.shared.clone(), cmd.clone(), config) - .await, + commands::offchain_worker::offchain_worker::( + self.shared.clone(), + cmd.clone(), + config, + ) + .await, Command::ExecuteBlock(cmd) => - execute_block::(self.shared.clone(), cmd.clone(), config).await, - Command::FollowChain => - follow_chain::(self.shared.clone(), config).await, + commands::execute_block::execute_block::( + self.shared.clone(), + cmd.clone(), + config, + ) + .await, + Command::FollowChain(cmd) => + commands::follow_chain::follow_chain::( + self.shared.clone(), + cmd.clone(), + config, + ) + .await, } } } @@ -670,7 +222,7 @@ impl CliConfiguration for TryRuntimeCmd { /// Extract `:code` from the given chain spec and return as `StorageData` along with the /// corresponding `StorageKey`. -fn extract_code(spec: Box) -> sc_cli::Result<(StorageKey, StorageData)> { +pub(crate) fn extract_code(spec: Box) -> sc_cli::Result<(StorageKey, StorageData)> { let genesis_storage = spec.build_storage()?; let code = StorageData( genesis_storage @@ -684,10 +236,20 @@ fn extract_code(spec: Box) -> sc_cli::Result<(StorageKey, Storage Ok((code_key, code)) } +pub(crate) fn hash_of(hash_str: &str) -> sc_cli::Result +where + Block::Hash: FromStr, + ::Err: Debug, +{ + hash_str + .parse::<::Hash>() + .map_err(|e| format!("Could not parse block hash: {:?}", e).into()) +} + /// Check the spec_name of an `ext` /// /// If the version does not exist, or if it does not match with the given, it emits a warning. -async fn check_spec_name( +pub(crate) async fn check_spec_name( uri: String, expected_spec_name: String, ) { From 9356bf3e8f59c9426d46dbe8fe4fd641bfbc6588 Mon Sep 17 00:00:00 2001 From: kianenigma Date: Wed, 15 Sep 2021 19:20:26 +0200 Subject: [PATCH 05/16] some small fixes --- utils/frame/try-runtime/cli/src/commands/execute_block.rs | 2 +- utils/frame/try-runtime/cli/src/commands/offchain_worker.rs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/utils/frame/try-runtime/cli/src/commands/execute_block.rs b/utils/frame/try-runtime/cli/src/commands/execute_block.rs index 7278b32cad687..a1a5426733476 100644 --- a/utils/frame/try-runtime/cli/src/commands/execute_block.rs +++ b/utils/frame/try-runtime/cli/src/commands/execute_block.rs @@ -38,7 +38,7 @@ pub struct ExecuteBlockCmd { #[structopt( long, multiple = false, - parse(try_from_str = parse::hash) + parse(try_from_str = parse::url) )] block_uri: Option, diff --git a/utils/frame/try-runtime/cli/src/commands/offchain_worker.rs b/utils/frame/try-runtime/cli/src/commands/offchain_worker.rs index 9c968e38e3e4d..f80895e70d7ff 100644 --- a/utils/frame/try-runtime/cli/src/commands/offchain_worker.rs +++ b/utils/frame/try-runtime/cli/src/commands/offchain_worker.rs @@ -40,7 +40,7 @@ pub struct OffchainWorkerCmd { #[structopt( long, multiple = false, - parse(try_from_str = parse::hash) + parse(try_from_str = parse::url) )] header_uri: Option, From 39e4c4b5c74e6d2d19a65c9ef6f65aab36d43cff Mon Sep 17 00:00:00 2001 From: kianenigma Date: Thu, 16 Sep 2021 16:08:44 +0200 Subject: [PATCH 06/16] another mega refactor --- Cargo.lock | 1 + bin/node/runtime/src/lib.rs | 9 +- frame/executive/src/lib.rs | 35 ++ frame/try-runtime/src/lib.rs | 2 +- utils/frame/remote-externalities/src/lib.rs | 20 +- utils/frame/try-runtime/cli/Cargo.toml | 1 + .../cli/src/commands/execute_block.rs | 90 ++---- .../cli/src/commands/follow_chain.rs | 72 ++--- .../cli/src/commands/offchain_worker.rs | 79 ++--- .../cli/src/commands/on_runtime_upgrade.rs | 52 ++- utils/frame/try-runtime/cli/src/lib.rs | 300 +++++++++++++++--- 11 files changed, 412 insertions(+), 249 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 2104d32342260..769cb74222c01 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -10505,6 +10505,7 @@ dependencies = [ "sp-keystore", "sp-runtime", "sp-state-machine", + "sp-version", "structopt", ] diff --git a/bin/node/runtime/src/lib.rs b/bin/node/runtime/src/lib.rs index 6adfc4ca5049e..2f77408dc4580 100644 --- a/bin/node/runtime/src/lib.rs +++ b/bin/node/runtime/src/lib.rs @@ -1545,9 +1545,12 @@ impl_runtime_apis! { #[cfg(feature = "try-runtime")] impl frame_try_runtime::TryRuntime 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_state_root_check(block: Block) -> Weight { diff --git a/frame/executive/src/lib.rs b/frame/executive/src/lib.rs index 8ac5b26cacd92..7430a8c66df82 100644 --- a/frame/executive/src/lib.rs +++ b/frame/executive/src/lib.rs @@ -237,6 +237,41 @@ where frame_system::Pallet::::block_weight().total() } + // #[cfg(feature = "try-runtime")] + // fn execute_extrinsics_with_book_keeping_no_signature_check( + // extrinsics: Vec, + // block_number: NumberFor, + // ) { + // extrinsics.into_iter().for_each(|uxt| { + // let encoded = uxt.encode(); + // let encoded_len = encoded.len(); + + // // skip signature verification + // let xt = Self::blind_check(uxt)?; + + // // We don't need to make sure to `note_extrinsic` only after we know it's going to be + // // executed to prevent it from leaking in storage since at this point, it will either + // // execute or panic (and revert storage changes). + // >::note_extrinsic(encoded); + + // // AUDIT: Under no circumstances may this function panic from here onwards. + + // // Decode parameters and dispatch + // let dispatch_info = xt.get_dispatch_info(); + // let r = Applyable::apply::(xt, &dispatch_info, encoded_len) + // .map(|_| ()) + // .map_err(|e| e.error) + // .unwrap(); + + // >::note_applied_extrinsic(&r, dispatch_info); + // }); + + // // post-extrinsics book-keeping + // >::note_finished_extrinsics(); + + // Self::idle_and_finalize_hook(block_number); + // } + /// Execute all `OnRuntimeUpgrade` of this runtime, including the pre and post migration checks. /// /// This should only be used for testing. diff --git a/frame/try-runtime/src/lib.rs b/frame/try-runtime/src/lib.rs index 4bfc4c746fd07..ded69bb5a01d2 100644 --- a/frame/try-runtime/src/lib.rs +++ b/frame/try-runtime/src/lib.rs @@ -32,7 +32,7 @@ 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. /// diff --git a/utils/frame/remote-externalities/src/lib.rs b/utils/frame/remote-externalities/src/lib.rs index cb3755f00b29a..2052780286c66 100644 --- a/utils/frame/remote-externalities/src/lib.rs +++ b/utils/frame/remote-externalities/src/lib.rs @@ -112,8 +112,8 @@ pub struct OnlineConfig { pub at: Option, /// An optional state snapshot file to WRITE to, not for reading. Not written if set to `None`. pub state_snapshot: Option, - /// The modules to scrape. If empty, entire chain state will be scraped. - pub modules: Vec, + /// The pallets to scrape. If empty, entire chain state will be scraped. + pub pallets: Vec, /// Transport config. pub transport: Transport, } @@ -134,7 +134,7 @@ impl Default for OnlineConfig { transport: Transport { uri: DEFAULT_TARGET.to_owned(), client: None }, at: None, state_snapshot: None, - modules: vec![], + pallets: vec![], } } } @@ -360,9 +360,9 @@ impl Builder { .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!( @@ -376,7 +376,7 @@ impl Builder { } 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? }; @@ -552,7 +552,7 @@ mod remote_tests { init_logger(); Builder::::new() .mode(Mode::Online(OnlineConfig { - modules: vec!["System".to_owned()], + pallets: vec!["System".to_owned()], ..Default::default() })) .build() @@ -566,7 +566,7 @@ mod remote_tests { init_logger(); Builder::::new() .mode(Mode::Online(OnlineConfig { - modules: vec![ + pallets: vec![ "Proxy".to_owned(), "Multisig".to_owned(), "PhragmenElection".to_owned(), @@ -594,7 +594,7 @@ mod remote_tests { init_logger(); Builder::::new() .mode(Mode::Online(OnlineConfig { - modules: vec!["PhragmenElection".to_owned()], + pallets: vec!["PhragmenElection".to_owned()], ..Default::default() })) .build() @@ -620,7 +620,7 @@ mod remote_tests { Builder::::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() diff --git a/utils/frame/try-runtime/cli/Cargo.toml b/utils/frame/try-runtime/cli/Cargo.toml index 85e9c3d08705c..11b899db4ca47 100644 --- a/utils/frame/try-runtime/cli/Cargo.toml +++ b/utils/frame/try-runtime/cli/Cargo.toml @@ -28,6 +28,7 @@ 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 = [ diff --git a/utils/frame/try-runtime/cli/src/commands/execute_block.rs b/utils/frame/try-runtime/cli/src/commands/execute_block.rs index a1a5426733476..a70c42c2119be 100644 --- a/utils/frame/try-runtime/cli/src/commands/execute_block.rs +++ b/utils/frame/try-runtime/cli/src/commands/execute_block.rs @@ -1,25 +1,24 @@ -use crate::{check_spec_name, extract_code, hash_of, parse, SharedParams, State, LOG_TARGET}; +use crate::{ + build_executor, ensure_matching_spec_name, extract_code, full_extensions, hash_of, + local_spec_name, state_machine_call, SharedParams, State, LOG_TARGET, +}; use remote_externalities::rpc_api; -use sc_executor::NativeElseWasmExecutor; use sc_service::{Configuration, NativeExecutionDispatch}; -use sp_core::{ - hashing::twox_128, - offchain::{ - testing::{TestOffchainExt, TestTransactionPoolExt}, - OffchainDbExt, OffchainWorkerExt, TransactionPoolExt, - }, - storage::well_known_keys, -}; -use sp_keystore::{testing::KeyStore, KeystoreExt}; +use sp_core::storage::well_known_keys; use sp_runtime::traits::{Block as BlockT, Header as HeaderT, NumberFor}; -use sp_state_machine::StateMachine; -use std::{fmt::Debug, str::FromStr, sync::Arc}; +use std::{fmt::Debug, str::FromStr}; #[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_state_root_check` instead of `Core_execute_block`. + #[structopt(long)] + no_state_root_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 @@ -27,7 +26,7 @@ pub struct ExecuteBlockCmd { #[structopt( long, multiple = false, - parse(try_from_str = parse::hash) + parse(try_from_str = crate::parse::hash) )] block_at: Option, @@ -38,7 +37,7 @@ pub struct ExecuteBlockCmd { #[structopt( long, multiple = false, - parse(try_from_str = parse::url) + parse(try_from_str = crate::parse::url) )] block_uri: Option, @@ -61,9 +60,9 @@ impl ExecuteBlockCmd { log::warn!(target: LOG_TARGET, "--block-at is provided while state type is live, this will most likely lead to a nonsensical result."); hash_of::(&block_at) }, - (None, State::Live { at, .. }) => hash_of::(&at), - (None, State::Snap { .. }) => { - panic!("either `--block-at` must be provided, or state must be `live`"); + (None, State::Live { at: Some(at), .. }) => hash_of::(&at), + _ => { + panic!("either `--block-at` must be provided, or state must be `live with a proper `--at``"); }, } } @@ -100,17 +99,8 @@ where as FromStr>::Err: Debug, ExecDispatch: NativeExecutionDispatch + 'static, { - let wasm_method = shared.wasm_method; + let executor = build_executor::(&shared, &config); let execution = shared.execution; - let heap_pages = shared.heap_pages.or(config.default_heap_pages); - - let mut changes = Default::default(); - let max_runtime_instances = config.max_runtime_instances; - let executor = NativeElseWasmExecutor::::new( - wasm_method.into(), - heap_pages, - max_runtime_instances, - ); let block_at = command.block_at::()?; let block_uri = command.block_uri::(); @@ -123,35 +113,21 @@ where parent_hash ); - check_spec_name::(block_uri.clone(), config.chain_spec.name().to_string()).await; - let ext = { let builder = command .state .builder::()? // make sure the state is being build with the parent hash, if it is online. - .overwrite_online_at(parent_hash.to_owned()) - .inject_hashed_key(&[twox_128(b"System"), twox_128(b"LastRuntimeUpgrade")].concat()); + .overwrite_online_at(parent_hash.to_owned()); let builder = if command.overwrite_wasm_code { - let (code_key, code) = extract_code(config.chain_spec)?; + 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) }; - let mut ext = builder.build().await?; - - // register externality extensions in order to provide host interface for OCW to the - // runtime. - let (offchain, _offchain_state) = TestOffchainExt::new(); - let (pool, _pool_state) = TestTransactionPoolExt::new(); - ext.register_extension(OffchainDbExt::new(offchain.clone())); - ext.register_extension(OffchainWorkerExt::new(offchain)); - ext.register_extension(KeystoreExt(Arc::new(KeyStore::new()))); - ext.register_extension(TransactionPoolExt::new(pool)); - - ext + builder.build().await? }; // A digest item gets added when the runtime is processing the block, so we need to pop @@ -160,19 +136,21 @@ where header.digest_mut().pop(); let block = Block::new(header, extrinsics); - let _encoded_result = StateMachine::<_, _, NumberFor, _>::new( - &ext.backend, - None, - &mut changes, + let expected_spec_name = local_spec_name::(&ext, &executor); + ensure_matching_spec_name::(block_uri.clone(), expected_spec_name).await; + + let _ = state_machine_call::( + &ext, &executor, - "Core_execute_block", + execution, + if command.no_state_root_check { + "Core_execute_block" + } else { + "TryRuntime_execute_block_no_state_root_check" + }, block.encode().as_ref(), - ext.extensions, - &sp_state_machine::backend::BackendRuntimeCode::new(&ext.backend).runtime_code()?, - sp_core::testing::TaskExecutor::new(), - ) - .execute(execution.into()) - .map_err(|e| format!("failed to execute 'Core_execute_block': {:?}", e))?; + full_extensions(), + )?; log::info!(target: LOG_TARGET, "Core_execute_block executed without errors."); diff --git a/utils/frame/try-runtime/cli/src/commands/follow_chain.rs b/utils/frame/try-runtime/cli/src/commands/follow_chain.rs index 51729ea4a3028..d9b84f487f241 100644 --- a/utils/frame/try-runtime/cli/src/commands/follow_chain.rs +++ b/utils/frame/try-runtime/cli/src/commands/follow_chain.rs @@ -1,25 +1,18 @@ -use crate::{check_spec_name, extract_code, parse, SharedParams, LOG_TARGET}; +use crate::{ + build_executor, ensure_matching_spec_name, extract_code, full_extensions, local_spec_name, + parse, state_machine_call, SharedParams, LOG_TARGET, +}; use jsonrpsee_ws_client::{ types::{traits::SubscriptionClient, v2::params::JsonRpcParams, Subscription}, WsClientBuilder, }; use parity_scale_codec::Decode; use remote_externalities::{rpc_api, Builder, Mode, OnlineConfig}; -use sc_executor::{NativeElseWasmExecutor, NativeExecutionDispatch}; +use sc_executor::NativeExecutionDispatch; use sc_service::Configuration; -use sp_core::{ - offchain::{ - testing::{TestOffchainExt, TestTransactionPoolExt}, - OffchainDbExt, OffchainWorkerExt, TransactionPoolExt, - }, - testing::TaskExecutor, - traits::TaskExecutorExt, - H256, -}; -use sp_keystore::{testing::KeyStore, KeystoreExt}; +use sp_core::H256; use sp_runtime::traits::{Block as BlockT, Header, NumberFor}; -use sp_state_machine::StateMachine; -use std::{fmt::Debug, str::FromStr, sync::Arc}; +use std::{fmt::Debug, str::FromStr}; #[derive(Debug, Clone, structopt::StructOpt)] pub struct FollowChainCmd { @@ -46,8 +39,6 @@ where as FromStr>::Err: Debug, ExecDispatch: NativeExecutionDispatch + 'static, { - check_spec_name::(command.uri.clone(), config.chain_spec.name().to_string()).await; - let mut maybe_state_ext = None; let sub = "chain_subscribeFinalizedHeads"; @@ -64,7 +55,9 @@ where let mut subscription: Subscription = client.subscribe(&sub, JsonRpcParams::NoParams, &unsub).await.unwrap(); - let (code_key, code) = extract_code(config.chain_spec)?; + let (code_key, code) = extract_code(&config.chain_spec)?; + let executor = build_executor::(&shared, &config); + let execution = shared.execution; while let Some(header) = subscription.next().await.unwrap() { let hash = header.hash(); @@ -97,50 +90,25 @@ where number, new_ext.as_backend().root() ); + + let expected_spec_name = local_spec_name::(&new_ext, &executor); + ensure_matching_spec_name::(command.uri.clone(), expected_spec_name).await; + maybe_state_ext = Some(new_ext); } let state_ext = maybe_state_ext.as_mut().expect("state_ext either existed or was just created"); - let wasm_method = shared.wasm_method; - let execution = shared.execution; - let heap_pages = shared.heap_pages.or(config.default_heap_pages); - - let mut changes = Default::default(); - let max_runtime_instances = config.max_runtime_instances; - let executor = NativeElseWasmExecutor::::new( - wasm_method.into(), - heap_pages, - max_runtime_instances, - ); - - let mut extensions = sp_externalities::Extensions::default(); - extensions.register(TaskExecutorExt::new(TaskExecutor::new())); - let (offchain, _offchain_state) = TestOffchainExt::new(); - let (pool, _pool_state) = TestTransactionPoolExt::new(); - extensions.register(OffchainDbExt::new(offchain.clone())); - extensions.register(OffchainWorkerExt::new(offchain)); - extensions.register(KeystoreExt(Arc::new(KeyStore::new()))); - extensions.register(TransactionPoolExt::new(pool)); - - let encoded_result = StateMachine::<_, _, NumberFor, _>::new( - &state_ext.backend, - None, - &mut changes, + let (mut changes, encoded_result) = state_machine_call::( + &state_ext, &executor, + execution, "TryRuntime_execute_block_no_state_root_check", block.encode().as_ref(), - extensions, - &sp_state_machine::backend::BackendRuntimeCode::new(&state_ext.backend) - .runtime_code()?, - sp_core::testing::TaskExecutor::new(), - ) - .set_parent_hash(*parent) - .execute(execution.into()) - .map_err(|e| { - format!("failed to execute 'TryRuntime_execute_block_no_state_root_check': {:?}", e) - })?; + full_extensions(), + )?; + // .set_parent_hash(*parent) let consumed_weight = ::decode(&mut &*encoded_result) .map_err(|e| format!("failed to decode output: {:?}", e))?; diff --git a/utils/frame/try-runtime/cli/src/commands/offchain_worker.rs b/utils/frame/try-runtime/cli/src/commands/offchain_worker.rs index f80895e70d7ff..94d640c7c57f3 100644 --- a/utils/frame/try-runtime/cli/src/commands/offchain_worker.rs +++ b/utils/frame/try-runtime/cli/src/commands/offchain_worker.rs @@ -1,24 +1,18 @@ -use crate::{check_spec_name, extract_code, hash_of, parse, SharedParams, State, LOG_TARGET}; +use crate::{ + build_executor, ensure_matching_spec_name, extract_code, full_extensions, hash_of, + local_spec_name, parse, state_machine_call, SharedParams, State, LOG_TARGET, +}; use parity_scale_codec::Encode; use remote_externalities::rpc_api; -use sc_executor::{NativeElseWasmExecutor, NativeExecutionDispatch}; +use sc_executor::NativeExecutionDispatch; use sc_service::Configuration; -use sp_core::{ - offchain::{ - testing::{TestOffchainExt, TestTransactionPoolExt}, - OffchainDbExt, OffchainWorkerExt, TransactionPoolExt, - }, - storage::well_known_keys, - testing::TaskExecutor, - twox_128, -}; -use sp_keystore::{testing::KeyStore, KeystoreExt}; +use sp_core::storage::well_known_keys; use sp_runtime::traits::{Block as BlockT, Header, NumberFor}; -use sp_state_machine::{backend::BackendRuntimeCode, StateMachine}; -use std::{fmt::Debug, str::FromStr, sync::Arc}; +use std::{fmt::Debug, str::FromStr}; #[derive(Debug, Clone, structopt::StructOpt)] pub struct OffchainWorkerCmd { + /// Overwrite the wasm code in state or not. #[structopt(long)] overwrite_wasm_code: bool, @@ -61,9 +55,9 @@ impl OffchainWorkerCmd { log::warn!(target: LOG_TARGET, "--header-at is provided while state type is live, this will most likely lead to a nonsensical result."); hash_of::(&header_at) }, - (None, State::Live { at, .. }) => hash_of::(&at), - (None, State::Snap { .. }) => { - panic!("either `--header-at` must be provided, or state must be `live`"); + (None, State::Live { at: Some(at), .. }) => hash_of::(&at), + _ => { + panic!("either `--header-at` must be provided, or state must be `live` with a proper `--at`"); }, } } @@ -101,17 +95,8 @@ where as FromStr>::Err: Debug, ExecDispatch: NativeExecutionDispatch + 'static, { - let wasm_method = shared.wasm_method; + let executor = build_executor(&shared, &config); let execution = shared.execution; - let heap_pages = shared.heap_pages.or(config.default_heap_pages); - - let mut changes = Default::default(); - let max_runtime_instances = config.max_runtime_instances; - let executor = NativeElseWasmExecutor::::new( - wasm_method.into(), - heap_pages, - max_runtime_instances, - ); let header_at = command.header_at::()?; let header_uri = command.header_uri::(); @@ -124,48 +109,30 @@ where header.number() ); - check_spec_name::(header_uri, config.chain_spec.name().to_string()).await; - let ext = { - let builder = command - .state - .builder::()? - .inject_hashed_key(&[twox_128(b"System"), twox_128(b"LastRuntimeUpgrade")].concat()); + let builder = command.state.builder::()?; let builder = if command.overwrite_wasm_code { - let (code_key, code) = extract_code(config.chain_spec)?; + 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) }; - let mut ext = builder.build().await?; - - // register externality extensions in order to provide host interface for OCW to the - // runtime. - let (offchain, _offchain_state) = TestOffchainExt::new(); - let (pool, _pool_state) = TestTransactionPoolExt::new(); - ext.register_extension(OffchainDbExt::new(offchain.clone())); - ext.register_extension(OffchainWorkerExt::new(offchain)); - ext.register_extension(KeystoreExt(Arc::new(KeyStore::new()))); - ext.register_extension(TransactionPoolExt::new(pool)); - - ext + builder.build().await? }; - let _ = StateMachine::<_, _, NumberFor, _>::new( - &ext.backend, - None, - &mut changes, + let expected_spec_name = local_spec_name::(&ext, &executor); + ensure_matching_spec_name::(header_uri, expected_spec_name).await; + + let _ = state_machine_call::( + &ext, &executor, + execution, "OffchainWorkerApi_offchain_worker", header.encode().as_ref(), - ext.extensions, - &BackendRuntimeCode::new(&ext.backend).runtime_code()?, - TaskExecutor::new(), - ) - .execute(execution.into()) - .map_err(|e| format!("failed to execute 'OffchainWorkerApi_offchain_worker': {:?}", e))?; + full_extensions(), + )?; log::info!(target: LOG_TARGET, "OffchainWorkerApi_offchain_worker executed without errors."); diff --git a/utils/frame/try-runtime/cli/src/commands/on_runtime_upgrade.rs b/utils/frame/try-runtime/cli/src/commands/on_runtime_upgrade.rs index a3d47da5f1b97..ea9c5a93b244a 100644 --- a/utils/frame/try-runtime/cli/src/commands/on_runtime_upgrade.rs +++ b/utils/frame/try-runtime/cli/src/commands/on_runtime_upgrade.rs @@ -1,13 +1,14 @@ use std::{fmt::Debug, str::FromStr}; use parity_scale_codec::Decode; -use sc_executor::{NativeElseWasmExecutor, NativeExecutionDispatch}; +use sc_executor::NativeExecutionDispatch; use sc_service::Configuration; -use sp_core::twox_128; use sp_runtime::traits::{Block as BlockT, NumberFor}; -use sp_state_machine::StateMachine; -use crate::{check_spec_name, extract_code, SharedParams, State, LOG_TARGET}; +use crate::{ + build_executor, ensure_matching_spec_name, extract_code, local_spec_name, state_machine_call, + SharedParams, State, LOG_TARGET, +}; #[derive(Debug, Clone, structopt::StructOpt)] pub struct OnRuntimeUpgradeCmd { @@ -29,45 +30,28 @@ where as FromStr>::Err: Debug, ExecDispatch: NativeExecutionDispatch + 'static, { - let wasm_method = shared.wasm_method; + let executor = build_executor(&shared, &config); let execution = shared.execution; - let heap_pages = shared.heap_pages.or(config.default_heap_pages); - - let mut changes = Default::default(); - let max_runtime_instances = config.max_runtime_instances; - let executor = NativeElseWasmExecutor::::new( - wasm_method.into(), - heap_pages, - max_runtime_instances, - ); - - if let Some(uri) = command.state.live_uri() { - check_spec_name::(uri, config.chain_spec.name().to_string()).await; - } let ext = { let builder = command.state.builder::()?; - let (code_key, code) = extract_code(config.chain_spec)?; - builder - .inject_key_value(&[(code_key, code)]) - .inject_hashed_key(&[twox_128(b"System"), twox_128(b"LastRuntimeUpgrade")].concat()) - .build() - .await? + let (code_key, code) = extract_code(&config.chain_spec)?; + builder.inject_key_value(&[(code_key, code)]).build().await? }; - let encoded_result = StateMachine::<_, _, NumberFor, _>::new( - &ext.backend, - None, - &mut changes, + if let Some(uri) = command.state.live_uri() { + let expected_spec_name = local_spec_name::(&ext, &executor); + ensure_matching_spec_name::(uri, expected_spec_name).await; + } + + let (_, encoded_result) = state_machine_call::( + &ext, &executor, + execution, "TryRuntime_on_runtime_upgrade", &[], - ext.extensions, - &sp_state_machine::backend::BackendRuntimeCode::new(&ext.backend).runtime_code()?, - sp_core::testing::TaskExecutor::new(), - ) - .execute(execution.into()) - .map_err(|e| format!("failed to execute 'TryRuntime_on_runtime_upgrade': {:?}", e))?; + Default::default(), // we don't really need any extensions here. + )?; let (weight, total_weight) = <(u64, u64) as Decode>::decode(&mut &*encoded_result) .map_err(|e| format!("failed to decode output: {:?}", e))?; diff --git a/utils/frame/try-runtime/cli/src/lib.rs b/utils/frame/try-runtime/cli/src/lib.rs index f76c63c45d99c..ef2ffa93235f3 100644 --- a/utils/frame/try-runtime/cli/src/lib.rs +++ b/utils/frame/try-runtime/cli/src/lib.rs @@ -15,50 +15,185 @@ // See the License for the specific language governing permissions and // limitations under the License. -//! `Structopt`-ready structs for `try-runtime`. +//! # Try-runtime +//! +//! Substrate's ultimate testing framework for the power users. +//! +//! > As the name suggests, `try-runtime` is a detailed testing framework that gives you a lot of +//! control over what is being executed in which environment. It is recommended that user's first +//! familiarize themselves with substrate in depth, particularly the execution model. It is critical +//! to deeply understand how the wasm/native interactions, and the runtime apis work in the +//! substrate runtime. +//! +//! #### Resources +//! +//! Some resources about the above: +//! +//! 1. https://substrate.dev/docs/en/knowledgebase/integrate/try-runtime +//! 2. https://www.crowdcast.io/e/substrate-seminar/41 +//! 3. https://substrate.dev/docs/en/knowledgebase/advanced/executor +//! +//! --- +//! +//! ## Overview +//! +//! The basis of all try-runtime commands is the same: connect to a live node, scrape its *state* +//! and put it inside a `TestExternalities`, then call into a *specific runtime-api* using the given +//! state and some *runtime*. +//! +//! All of the variables in the above statement are made *italic*. Let's look at each of them: +//! +//! 1. **State** is the key-value pairs of data that consist the important information that any +//! blockchain is keeping. A state can be full (all key-value pairs), or be partial (only pairs +//! related to some pallets). Moreover, some keys are special and live outside of pallets, known as +//! [`well_known_keys`] in substrate. The most important of these is the `:CODE:` key, which +//! contains the code used for execution, when `--execution Wasm` is used. +//! 2. *A runtime-api* is a call into the function defined in the runtime, *on top of a given +//! state*. Each subcommand of `try-runtime` utilizes a specific one of these. +//! 3. Finally, the **runtime** is the actual code that is used to execute the aforementioned +//! runtime-api. All substrate based chains always have two runtimes: native and wasm. The decision +//! of which one is chosen is slightly non-trivial. First, let's look at the options: +//! +//! 1. Native: this means that the runtime that is **in your codebase**, aka whatever you see in +//! your editor is being used. This runtime is easier for diagnostics. In the rest of the +//! documentation, by "local runtime", we mean the na 2. Wasm: this means that whatever is stored in +//! the `:CODE:` key of the state that your scrape is being used. In plain sight, since the entire +//! state (including `:CODE:`) is scraped from a remote chain, you could conclude that the wasm +//! runtime, if used, is always equal to the canonical runtime. That's factually true, but then the +//! testing would be quite lame. Typically, with try-runtime, you don't want to execute whatever +//! code is already on the live chain. Instead, you want your local runtime (which typically +//! includes a non-released feature) to be used. This is why try-runtime overwrites the wasm runtime +//! (at `:CODE:`) with the local runtime as well. That being said, this behavior can be controlled +//! in certain subcommands with a special flag. +//! +//! The decision of which runtime is eventually used is based on two facts: +//! +//! 1. `--execution` flag. If you specify `wasm`, then it is *always* wasm. If it is `native`, then +//! if and ONLY IF the spec versions match, then the native runtime is used. Else, wasm runtime is +//! used again. +//! 2. `--chain` flag (if present in your cli), which determines *which native runtime*, if needed, +//! might be used. +//! +//! All in all, if the term "local runtime" is used in the rest of this crate's documentation, it +//! means either the native runtime, or the wasm runtime when overwritten inside `:CODE:`. In other +//! words, it means your... well, "local runtime", regardless of wasm or native. +//! +//! To make sure there are no errors regarding this, always run any try-runtime command with +//! `executor=trace` logging targets, which will specify which runtime is being used per api call. +//! +//! Furthermore, other relevant log targets are: `try-runtime::cli`, `remote-ext`, and `runtime`. +//! +//! ## Commands +//! +//! See [`Command`] for more info. -use remote_externalities::{Builder, Mode, OfflineConfig, OnlineConfig, SnapshotConfig}; +use parity_scale_codec::Decode; +use remote_externalities::{ + Builder, Mode, OfflineConfig, OnlineConfig, SnapshotConfig, TestExternalities, +}; use sc_chain_spec::ChainSpec; use sc_cli::{CliConfiguration, ExecutionStrategy, WasmExecutionMethod}; +use sc_executor::NativeElseWasmExecutor; use sc_service::{Configuration, NativeExecutionDispatch}; use sp_core::{ + offchain::{ + testing::{TestOffchainExt, TestTransactionPoolExt}, + OffchainDbExt, OffchainWorkerExt, TransactionPoolExt, + }, storage::{well_known_keys, StorageData, StorageKey}, - H256, + testing::TaskExecutor, + traits::TaskExecutorExt, + twox_128, H256, }; +use sp_externalities::Extensions; +use sp_keystore::{testing::KeyStore, KeystoreExt}; use sp_runtime::traits::{Block as BlockT, NumberFor}; +use sp_state_machine::{OverlayedChanges, StateMachine}; use std::{fmt::Debug, path::PathBuf, str::FromStr}; mod commands; pub(crate) mod parse; pub(crate) const LOG_TARGET: &'static str = "try-runtime::cli"; -/// Possible subcommands of `try-runtime`. +/// Possible commands of `try-runtime`. #[derive(Debug, Clone, structopt::StructOpt)] pub enum Command { - /// Execute "TryRuntime_on_runtime_upgrade" against the given runtime state. + /// Execute the migrations of the "local runtime". + /// + /// This uses a custom runtime api call, namely "TryRuntime_on_runtime_upgrade". + /// + /// This always overwrites the wasm code with the local runtime, to ensure the new migrations + /// are being executed. Re-executing already existing migrations is evidently not very + /// exciting. OnRuntimeUpgrade(commands::on_runtime_upgrade::OnRuntimeUpgradeCmd), - /// Execute "OffchainWorkerApi_offchain_worker" against the given runtime state. - OffchainWorker(commands::offchain_worker::OffchainWorkerCmd), - - /// Execute "Core_execute_block" using the given block and the runtime state of the parent - /// block. + /// Executes the given block against some state. + /// + /// Unlike [`Command:::OnRuntimeUpgrade`], this command needs two inputs: the state, and the + /// block data. Since the state could be cached (see [`State::Snap`]), different flags are + /// provided for both. `--block-at` and `--block-uri`, if provided, are only used for fetching + /// the block. For convenience, these flags can be both emitted, if the [`State::Live`] is + /// being used. + /// + /// Note that by default, this command does not overwrite the code, so in wasm execution, the + /// live chain's code is used. This can be disabled if desired, see + /// [`ExecuteBlockCmd::overwrite_wasm_code`]. Note that if you do overwrite the wasm code, or + /// generally use the local runtime for this, you might + /// - not be able to decode the block, if the block format has changed. + /// - almost certainly get a state root mismatch. + /// + /// To make testing slightly more dynamic, you can disable the state root check by enabling + /// [`ExecuteBlockCmd::no_state_root_check`]. + /// + /// 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 has of the target block is used to scrape the state. If + /// [`State::Snap`] is being used, then this needs to manually taken into consideration. + /// + /// This executes the same runtime api as normal block import, namely `Core_execute_block`. If + /// [`ExecuteBlockCmd::no_state_root_check`] is set, it uses a custom, try-runtime-only runtime + /// api called `TryRuntime_execute_block_no_state_root_check`. ExecuteBlock(commands::execute_block::ExecuteBlockCmd), + /// Executes *the offchain worker hooks* of a given block against some state. + /// + /// Similar to [`Command:::ExecuteBlock`], this command needs two inputs: the state, and the + /// header data. Likewise, `--header-at` and `--header-uri` can be filled, or omitted if + /// [`State::Live`] is used. + /// + /// Similar to [`Command:::ExecuteBlock`], this command does not overwrite the code, so in wasm + /// execution, the live chain's code is used. This can be disabled if desired, see + /// [`OffchainWorkerCmd::overwrite_wasm_code`]. + /// + /// This executes the same runtime api as normal block import, namely + /// `OffchainWorkerApi_offchain_worker`. + OffchainWorker(commands::offchain_worker::OffchainWorkerCmd), + /// Follow the given chain's finalized blocks and apply all of its extrinsics. /// - /// The initial state is the state of the first finalized block received. + /// This is essentially repeated calls to [`Command::ExecuteBlock`], whilst the local runtime + /// is always at use, the state root check is disabled, and the state is persisted between + /// executions. + /// + /// This allows the behavior of a new runtime to be inspected over a long period of time, with + /// realistic transactions coming as input. + /// + /// This does not support snapshot states, and can only work with a remote chain. Upon first + /// connections, starts listening for finalized block events. Upon first notification, it + /// initializes the state from the remote node, and starts applying that block, and all the + /// ones that follows, to the same growing state. FollowChain(commands::follow_chain::FollowChainCmd), } +/// Shared parameters of the `try-runtime` commands #[derive(Debug, Clone, structopt::StructOpt)] pub struct SharedParams { - /// The shared parameters + /// Shared parameters of substrate cli. #[allow(missing_docs)] #[structopt(flatten)] pub shared_params: sc_cli::SharedParams, - /// The execution strategy that should be used for benchmarks + /// The execution strategy that should be used. #[structopt( long = "execution", value_name = "STRATEGY", @@ -68,7 +203,7 @@ pub struct SharedParams { )] pub execution: ExecutionStrategy, - /// Method for executing Wasm runtime code. + /// Type of wasm execution used. #[structopt( long = "wasm-execution", value_name = "METHOD", @@ -79,12 +214,14 @@ pub struct SharedParams { pub wasm_method: WasmExecutionMethod, /// The number of 64KB pages to allocate for Wasm execution. Defaults to - /// sc_service::Configuration.default_heap_pages. + /// [`sc_service::Configuration.default_heap_pages`]. #[structopt(long)] pub heap_pages: Option, } -/// Various commands to try out against runtime state at a specific block. +/// Our `try-runtime` command. +/// +/// See [`Command`] for more info. #[derive(Debug, Clone, structopt::StructOpt)] pub struct TryRuntimeCmd { #[structopt(flatten)] @@ -94,11 +231,12 @@ pub struct TryRuntimeCmd { pub command: Command, } -/// The source of runtime state to try operations against. +/// The source of runtime *state* to use. #[derive(Debug, Clone, structopt::StructOpt)] pub enum State { - /// Use a state snapshot as the source of runtime state. NOTE: for the offchain-worker and - /// execute-block command this is only partially supported and requires a archive node url. + /// Use a state snapshot as the source of runtime state. + /// + /// This can be crated by passing a value to [`State::Live::snapshot_path`]. Snap { #[structopt(short, long)] snapshot_path: PathBuf, @@ -106,7 +244,7 @@ pub enum State { /// Use a live chain as the source of runtime state. Live { - /// The url to connect to + /// The url to connect to. #[structopt( short, long, @@ -115,25 +253,29 @@ pub enum State { uri: String, /// The block hash at which to fetch the state. + /// + /// If non provided, then the latest finalized head is used. This is particularly useful + /// for [`Command::OnRuntimeUpgrade`]. #[structopt( short, long, multiple = false, parse(try_from_str = parse::hash), )] - at: String, + at: Option, /// An optional state snapshot file to WRITE to. Not written if set to `None`. #[structopt(short, long)] snapshot_path: Option, - /// The modules to scrape. If empty, entire chain state will be scraped. + /// The pallets to scrape. If empty, entire chain state will be scraped. #[structopt(short, long, require_delimiter = true)] - modules: Option>, + pallets: Option>, }, } impl State { + /// Create the [`remote_externalities::Builder`] from self. pub(crate) fn builder(&self) -> sc_cli::Result> where Block::Hash: FromStr, @@ -144,17 +286,26 @@ impl State { Builder::::new().mode(Mode::Offline(OfflineConfig { state_snapshot: SnapshotConfig::new(snapshot_path), })), - State::Live { snapshot_path, modules, uri, at } => - Builder::::new().mode(Mode::Online(OnlineConfig { - transport: uri.to_owned().into(), - state_snapshot: snapshot_path.as_ref().map(SnapshotConfig::new), - modules: modules.to_owned().unwrap_or_default(), - at: Some(hash_of::(at)?), - ..Default::default() - })), + State::Live { snapshot_path, pallets, uri, at } => { + let at = match at { + Some(at_str) => Some(hash_of::(at_str)?), + None => None, + }; + Builder::::new() + .mode(Mode::Online(OnlineConfig { + transport: uri.to_owned().into(), + state_snapshot: snapshot_path.as_ref().map(SnapshotConfig::new), + pallets: pallets.to_owned().unwrap_or_default(), + at, + })) + .inject_hashed_key( + &[twox_128(b"System"), twox_128(b"LastRuntimeUpgrade")].concat(), + ) + }, }) } + /// Get the uri, if self is `Live`. pub(crate) fn live_uri(&self) -> Option { match self { State::Live { uri, .. } => Some(uri.clone()), @@ -222,7 +373,7 @@ impl CliConfiguration for TryRuntimeCmd { /// Extract `:code` from the given chain spec and return as `StorageData` along with the /// corresponding `StorageKey`. -pub(crate) fn extract_code(spec: Box) -> sc_cli::Result<(StorageKey, StorageData)> { +pub(crate) fn extract_code(spec: &Box) -> sc_cli::Result<(StorageKey, StorageData)> { let genesis_storage = spec.build_storage()?; let code = StorageData( genesis_storage @@ -236,6 +387,7 @@ pub(crate) fn extract_code(spec: Box) -> sc_cli::Result<(StorageK Ok((code_key, code)) } +/// Get the hash type of the generic `Block` from a `hash_str`. pub(crate) fn hash_of(hash_str: &str) -> sc_cli::Result where Block::Hash: FromStr, @@ -249,11 +401,10 @@ where /// Check the spec_name of an `ext` /// /// If the version does not exist, or if it does not match with the given, it emits a warning. -pub(crate) async fn check_spec_name( +pub(crate) async fn ensure_matching_spec_name( uri: String, expected_spec_name: String, ) { - let expected_spec_name = expected_spec_name.to_lowercase(); match remote_externalities::rpc_api::get_runtime_version::(uri.clone(), None) .await .map(|version| String::from(version.spec_name.clone())) @@ -263,8 +414,7 @@ pub(crate) async fn check_spec_name log::debug!(target: LOG_TARGET, "found matching spec name: {:?}", spec); }, Ok(spec) => { - log::warn!( - target: LOG_TARGET, + panic!( "version mismatch: remote spec name: '{}', expected (local chain spec, aka. `--chain`): '{}'", spec, expected_spec_name, @@ -273,10 +423,86 @@ pub(crate) async fn check_spec_name Err(why) => { log::error!( target: LOG_TARGET, - "failed to fetch runtime version from {}: {:?}", + "failed to fetch runtime version from {}: {:?}. Skipping the check", uri, why ); }, } } + +/// Build all extensions that we typically use. +pub(crate) fn full_extensions() -> Extensions { + let mut extensions = Extensions::default(); + extensions.register(TaskExecutorExt::new(TaskExecutor::new())); + let (offchain, _offchain_state) = TestOffchainExt::new(); + let (pool, _pool_state) = TestTransactionPoolExt::new(); + extensions.register(OffchainDbExt::new(offchain.clone())); + extensions.register(OffchainWorkerExt::new(offchain)); + extensions.register(KeystoreExt(std::sync::Arc::new(KeyStore::new()))); + extensions.register(TransactionPoolExt::new(pool)); + + extensions +} + +/// Build a default execution that we typically use. +pub(crate) fn build_executor( + shared: &SharedParams, + config: &sc_service::Configuration, +) -> NativeElseWasmExecutor { + let wasm_method = shared.wasm_method; + let heap_pages = shared.heap_pages.or(config.default_heap_pages); + let max_runtime_instances = config.max_runtime_instances; + + NativeElseWasmExecutor::::new(wasm_method.into(), heap_pages, max_runtime_instances) +} + +/// Execute the given `method` and `data` on top of `ext`, returning the results (encoded) and the +/// state `changes`. +pub(crate) fn state_machine_call( + ext: &TestExternalities, + executor: &NativeElseWasmExecutor, + execution: sc_cli::ExecutionStrategy, + method: &'static str, + data: &[u8], + extensions: Extensions, +) -> sc_cli::Result<(OverlayedChanges, Vec)> { + let mut changes = Default::default(); + let encoded_results = StateMachine::<_, _, NumberFor, _>::new( + &ext.backend, + None, + &mut changes, + executor, + method, + data, + extensions, + &sp_state_machine::backend::BackendRuntimeCode::new(&ext.backend).runtime_code()?, + sp_core::testing::TaskExecutor::new(), + ) + .execute(execution.into()) + .map_err(|e| format!("failed to execute 'TryRuntime_on_runtime_upgrade': {:?}", e)) + .map_err::(Into::into)?; + + Ok((changes, encoded_results)) +} + +/// Get the spec name from the local runtime. +pub(crate) fn local_spec_name( + ext: &TestExternalities, + executor: &NativeElseWasmExecutor, +) -> String { + let (_, encoded) = state_machine_call::( + &ext, + &executor, + sc_cli::ExecutionStrategy::NativeElseWasm, + "Core_version", + &[], + Default::default(), + ) + .expect("all runtimes should have version; qed"); + ::decode(&mut &*encoded) + .map_err(|e| format!("failed to decode output: {:?}", e)) + .map(|v| v.spec_name) + .expect("all runtimes should have version; qed") + .into() +} From cc7a546767fd6e8ca5edb4ca5dbbf04bbc41f4d0 Mon Sep 17 00:00:00 2001 From: kianenigma Date: Thu, 16 Sep 2021 16:33:15 +0200 Subject: [PATCH 07/16] add license --- .../cli/src/commands/execute_block.rs | 20 ++++++++++++++- .../cli/src/commands/follow_chain.rs | 22 +++++++++++++--- .../frame/try-runtime/cli/src/commands/mod.rs | 25 ++++++++++++++++--- .../cli/src/commands/offchain_worker.rs | 20 ++++++++++++++- .../cli/src/commands/on_runtime_upgrade.rs | 20 ++++++++++++++- 5 files changed, 97 insertions(+), 10 deletions(-) diff --git a/utils/frame/try-runtime/cli/src/commands/execute_block.rs b/utils/frame/try-runtime/cli/src/commands/execute_block.rs index a70c42c2119be..4ae6631d638a6 100644 --- a/utils/frame/try-runtime/cli/src/commands/execute_block.rs +++ b/utils/frame/try-runtime/cli/src/commands/execute_block.rs @@ -1,3 +1,20 @@ +// 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_name, extract_code, full_extensions, hash_of, local_spec_name, state_machine_call, SharedParams, State, LOG_TARGET, @@ -8,6 +25,7 @@ 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. @@ -86,7 +104,7 @@ impl ExecuteBlockCmd { } } -pub async fn execute_block( +pub(crate) async fn execute_block( shared: SharedParams, command: ExecuteBlockCmd, config: Configuration, diff --git a/utils/frame/try-runtime/cli/src/commands/follow_chain.rs b/utils/frame/try-runtime/cli/src/commands/follow_chain.rs index d9b84f487f241..8454b75e906a9 100644 --- a/utils/frame/try-runtime/cli/src/commands/follow_chain.rs +++ b/utils/frame/try-runtime/cli/src/commands/follow_chain.rs @@ -1,3 +1,20 @@ +// 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_name, extract_code, full_extensions, local_spec_name, parse, state_machine_call, SharedParams, LOG_TARGET, @@ -14,9 +31,10 @@ use sp_core::H256; use sp_runtime::traits::{Block as BlockT, Header, NumberFor}; use std::{fmt::Debug, str::FromStr}; +/// Configurations of the [`Command::FollowChain`]. #[derive(Debug, Clone, structopt::StructOpt)] pub struct FollowChainCmd { - /// The url to connect to + /// The url to connect to. #[structopt( short, long, @@ -62,7 +80,6 @@ where while let Some(header) = subscription.next().await.unwrap() { let hash = header.hash(); let number = header.number(); - let parent = header.parent_hash(); let block = rpc_api::get_block::(&command.uri, hash).await.unwrap(); @@ -108,7 +125,6 @@ where block.encode().as_ref(), full_extensions(), )?; - // .set_parent_hash(*parent) let consumed_weight = ::decode(&mut &*encoded_result) .map_err(|e| format!("failed to decode output: {:?}", e))?; diff --git a/utils/frame/try-runtime/cli/src/commands/mod.rs b/utils/frame/try-runtime/cli/src/commands/mod.rs index fda1d82f1449b..bfd8290fb31c1 100644 --- a/utils/frame/try-runtime/cli/src/commands/mod.rs +++ b/utils/frame/try-runtime/cli/src/commands/mod.rs @@ -1,4 +1,21 @@ -pub mod execute_block; -pub mod follow_chain; -pub mod offchain_worker; -pub mod on_runtime_upgrade; +// 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. + +pub(crate) mod execute_block; +pub(crate) mod follow_chain; +pub(crate) mod offchain_worker; +pub(crate) mod on_runtime_upgrade; diff --git a/utils/frame/try-runtime/cli/src/commands/offchain_worker.rs b/utils/frame/try-runtime/cli/src/commands/offchain_worker.rs index 94d640c7c57f3..9adb80644a392 100644 --- a/utils/frame/try-runtime/cli/src/commands/offchain_worker.rs +++ b/utils/frame/try-runtime/cli/src/commands/offchain_worker.rs @@ -1,3 +1,20 @@ +// 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_name, extract_code, full_extensions, hash_of, local_spec_name, parse, state_machine_call, SharedParams, State, LOG_TARGET, @@ -10,6 +27,7 @@ use sp_core::storage::well_known_keys; use sp_runtime::traits::{Block as BlockT, Header, NumberFor}; use std::{fmt::Debug, str::FromStr}; +/// Configurations of the [`Command::OffchainWorker`]. #[derive(Debug, Clone, structopt::StructOpt)] pub struct OffchainWorkerCmd { /// Overwrite the wasm code in state or not. @@ -81,7 +99,7 @@ impl OffchainWorkerCmd { } } -pub async fn offchain_worker( +pub(crate) async fn offchain_worker( shared: SharedParams, command: OffchainWorkerCmd, config: Configuration, diff --git a/utils/frame/try-runtime/cli/src/commands/on_runtime_upgrade.rs b/utils/frame/try-runtime/cli/src/commands/on_runtime_upgrade.rs index ea9c5a93b244a..796802985b74b 100644 --- a/utils/frame/try-runtime/cli/src/commands/on_runtime_upgrade.rs +++ b/utils/frame/try-runtime/cli/src/commands/on_runtime_upgrade.rs @@ -1,3 +1,20 @@ +// 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 std::{fmt::Debug, str::FromStr}; use parity_scale_codec::Decode; @@ -10,6 +27,7 @@ use crate::{ SharedParams, State, LOG_TARGET, }; +/// Configurations of the [`Command::OnRuntimeUpgrade`]. #[derive(Debug, Clone, structopt::StructOpt)] pub struct OnRuntimeUpgradeCmd { /// The state type to use. @@ -17,7 +35,7 @@ pub struct OnRuntimeUpgradeCmd { pub state: State, } -pub async fn on_runtime_upgrade( +pub(crate) async fn on_runtime_upgrade( shared: SharedParams, command: OnRuntimeUpgradeCmd, config: Configuration, From 8469695bee5b59727343ecf1a56469bc45926b25 Mon Sep 17 00:00:00 2001 From: Kian Paimani <5588131+kianenigma@users.noreply.github.com> Date: Thu, 16 Sep 2021 15:35:59 +0100 Subject: [PATCH 08/16] Apply suggestions from code review --- utils/frame/try-runtime/cli/src/commands/follow_chain.rs | 2 +- utils/frame/try-runtime/cli/src/lib.rs | 4 ++++ 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/utils/frame/try-runtime/cli/src/commands/follow_chain.rs b/utils/frame/try-runtime/cli/src/commands/follow_chain.rs index 8454b75e906a9..3b868d34b0410 100644 --- a/utils/frame/try-runtime/cli/src/commands/follow_chain.rs +++ b/utils/frame/try-runtime/cli/src/commands/follow_chain.rs @@ -43,7 +43,7 @@ pub struct FollowChainCmd { uri: String, } -pub async fn follow_chain( +pub(crate) async fn follow_chain( shared: SharedParams, command: FollowChainCmd, config: Configuration, diff --git a/utils/frame/try-runtime/cli/src/lib.rs b/utils/frame/try-runtime/cli/src/lib.rs index ef2ffa93235f3..8982a3d5c8108 100644 --- a/utils/frame/try-runtime/cli/src/lib.rs +++ b/utils/frame/try-runtime/cli/src/lib.rs @@ -86,6 +86,10 @@ //! ## Commands //! //! See [`Command`] for more info. +//! +//! ## Examples +//! +//! TODO use parity_scale_codec::Decode; use remote_externalities::{ From a53043646d1c6709b13daebe948e723973b230ee Mon Sep 17 00:00:00 2001 From: kianenigma Date: Fri, 17 Sep 2021 10:06:12 +0200 Subject: [PATCH 09/16] hack around signature verification --- bin/node/runtime/src/lib.rs | 4 +- frame/executive/Cargo.toml | 2 +- frame/executive/src/lib.rs | 73 ++++++++++--------- frame/try-runtime/src/lib.rs | 2 +- primitives/runtime/Cargo.toml | 1 + .../src/generic/unchecked_extrinsic.rs | 28 +++++++ primitives/runtime/src/traits.rs | 15 ++++ .../cli/src/commands/execute_block.rs | 12 ++- .../cli/src/commands/follow_chain.rs | 33 +++++++-- .../cli/src/commands/offchain_worker.rs | 3 +- .../cli/src/commands/on_runtime_upgrade.rs | 3 +- utils/frame/try-runtime/cli/src/lib.rs | 30 ++++++-- 12 files changed, 149 insertions(+), 57 deletions(-) diff --git a/bin/node/runtime/src/lib.rs b/bin/node/runtime/src/lib.rs index d3f8974bfb750..dbcb158886633 100644 --- a/bin/node/runtime/src/lib.rs +++ b/bin/node/runtime/src/lib.rs @@ -1587,8 +1587,8 @@ impl_runtime_apis! { (weight, RuntimeBlockWeights::get().max_block) } - fn execute_block_no_state_root_check(block: Block) -> Weight { - Executive::execute_block_no_state_root_check(block) + fn execute_block_no_state_root_and_signature_check(block: Block) -> Weight { + Executive::execute_block_no_state_root_and_signature_check(block) } } diff --git a/frame/executive/Cargo.toml b/frame/executive/Cargo.toml index 1abbf50e6a4c4..706fee36d6c28 100644 --- a/frame/executive/Cargo.toml +++ b/frame/executive/Cargo.toml @@ -47,4 +47,4 @@ std = [ "sp-tracing/std", "sp-std/std", ] -try-runtime = ["frame-support/try-runtime"] +try-runtime = ["frame-support/try-runtime", "sp-runtime/try-runtime"] diff --git a/frame/executive/src/lib.rs b/frame/executive/src/lib.rs index ed561e0fc3097..c1add3176f2a7 100644 --- a/frame/executive/src/lib.rs +++ b/frame/executive/src/lib.rs @@ -224,53 +224,56 @@ where /// /// Should only be used for testing. #[cfg(feature = "try-runtime")] - pub fn execute_block_no_state_root_check(block: Block) -> frame_support::weights::Weight { + pub fn execute_block_no_state_root_and_signature_check( + block: Block, + ) -> frame_support::weights::Weight + where + Block::Extrinsic: sp_runtime::traits::CheckableNoCheck, + >::Checked: + Applyable> + GetDispatchInfo, + { Self::initialize_block(block.header()); Self::initial_checks(&block); let (header, extrinsics) = block.deconstruct(); - Self::execute_extrinsics_with_book_keeping(extrinsics, *header.number()); + Self::execute_extrinsics_with_book_keeping_no_signature_check(extrinsics, *header.number()); // don't call `final_checks`, but do finalize the block. let _header = >::finalize(); frame_system::Pallet::::block_weight().total() } - // #[cfg(feature = "try-runtime")] - // fn execute_extrinsics_with_book_keeping_no_signature_check( - // extrinsics: Vec, - // block_number: NumberFor, - // ) { - // extrinsics.into_iter().for_each(|uxt| { - // let encoded = uxt.encode(); - // let encoded_len = encoded.len(); - - // // skip signature verification - // let xt = Self::blind_check(uxt)?; - - // // We don't need to make sure to `note_extrinsic` only after we know it's going to be - // // executed to prevent it from leaking in storage since at this point, it will either - // // execute or panic (and revert storage changes). - // >::note_extrinsic(encoded); - - // // AUDIT: Under no circumstances may this function panic from here onwards. - - // // Decode parameters and dispatch - // let dispatch_info = xt.get_dispatch_info(); - // let r = Applyable::apply::(xt, &dispatch_info, encoded_len) - // .map(|_| ()) - // .map_err(|e| e.error) - // .unwrap(); - - // >::note_applied_extrinsic(&r, dispatch_info); - // }); - - // // post-extrinsics book-keeping - // >::note_finished_extrinsics(); + #[cfg(feature = "try-runtime")] + fn execute_extrinsics_with_book_keeping_no_signature_check( + extrinsics: Vec, + block_number: NumberFor, + ) where + Block::Extrinsic: sp_runtime::traits::CheckableNoCheck, + >::Checked: + Applyable> + GetDispatchInfo, + { + use sp_runtime::traits::CheckableNoCheck; + + extrinsics.into_iter().for_each(|uxt| { + let encoded = uxt.encode(); + let encoded_len = encoded.len(); + + // skip signature verification + let xt = uxt.check_i_know_i_should_not_be_using_this(&Default::default()).unwrap(); + >::note_extrinsic(encoded); + + // dispatch + let dispatch_info = xt.get_dispatch_info(); + let r = Applyable::apply::(xt, &dispatch_info, encoded_len).unwrap(); + + >::note_applied_extrinsic(&r, dispatch_info); + }); - // Self::idle_and_finalize_hook(block_number); - // } + // post-extrinsics book-keeping + >::note_finished_extrinsics(); + Self::idle_and_finalize_hook(block_number); + } /// Execute all `OnRuntimeUpgrade` of this runtime, including the pre and post migration checks. /// diff --git a/frame/try-runtime/src/lib.rs b/frame/try-runtime/src/lib.rs index ded69bb5a01d2..ff2a9735fd820 100644 --- a/frame/try-runtime/src/lib.rs +++ b/frame/try-runtime/src/lib.rs @@ -38,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_state_root_check(block: Block) -> Weight; + fn execute_block_no_state_root_and_signature_check(block: Block) -> Weight; } } diff --git a/primitives/runtime/Cargo.toml b/primitives/runtime/Cargo.toml index 5ac5bcf1963e0..c933a291b7837 100644 --- a/primitives/runtime/Cargo.toml +++ b/primitives/runtime/Cargo.toml @@ -58,3 +58,4 @@ std = [ "hash256-std-hasher/std", "either/use_std", ] +try-runtime = [] diff --git a/primitives/runtime/src/generic/unchecked_extrinsic.rs b/primitives/runtime/src/generic/unchecked_extrinsic.rs index 95f4f2f3584d9..62d8537af9917 100644 --- a/primitives/runtime/src/generic/unchecked_extrinsic.rs +++ b/primitives/runtime/src/generic/unchecked_extrinsic.rs @@ -159,6 +159,34 @@ where } } +#[cfg(feature = "try-runtime")] +impl traits::CheckableNoCheck + for UncheckedExtrinsic +where + Address: Member + MaybeDisplay, + Call: Encode + Member, + Signature: Member + traits::Verify, + ::Signer: IdentifyAccount, + Extra: SignedExtension, + AccountId: Member + MaybeDisplay, + Lookup: traits::Lookup, +{ + type Checked = CheckedExtrinsic; + + fn check_i_know_i_should_not_be_using_this( + self, + lookup: &Lookup, + ) -> Result { + Ok(match self.signature { + Some((signed, _, extra)) => { + let signed = lookup.lookup(signed)?; + CheckedExtrinsic { signed: Some((signed, extra)), function: self.function } + }, + None => CheckedExtrinsic { signed: None, function: self.function }, + }) + } +} + impl ExtrinsicMetadata for UncheckedExtrinsic where diff --git a/primitives/runtime/src/traits.rs b/primitives/runtime/src/traits.rs index 65c063fde1696..ffdab2ab0f1bf 100644 --- a/primitives/runtime/src/traits.rs +++ b/primitives/runtime/src/traits.rs @@ -779,6 +779,21 @@ pub trait Checkable: Sized { fn check(self, c: &Context) -> Result; } +/// A variant if `Checkable` that avoids any checks. +/// +/// Only useful for testing, without breaking the generic assumptions of the runtime. +#[cfg(feature = "try-runtime")] +pub trait CheckableNoCheck: Sized { + /// Returned if `check` succeeds. + type Checked; + + /// Check self, given an instance of Context. + fn check_i_know_i_should_not_be_using_this( + self, + c: &Context, + ) -> Result; +} + /// A "checkable" piece of information, used by the standard Substrate Executive in order to /// check the validity of a piece of extrinsic information, usually by verifying the signature. /// Implement for pieces of information that don't require additional context in order to be diff --git a/utils/frame/try-runtime/cli/src/commands/execute_block.rs b/utils/frame/try-runtime/cli/src/commands/execute_block.rs index 4ae6631d638a6..b7e6bcf3f6d9e 100644 --- a/utils/frame/try-runtime/cli/src/commands/execute_block.rs +++ b/utils/frame/try-runtime/cli/src/commands/execute_block.rs @@ -33,7 +33,8 @@ pub struct ExecuteBlockCmd { overwrite_wasm_code: bool, /// If set, then the state root check is disabled by the virtue of calling into - /// `TryRuntime_execute_block_no_state_root_check` instead of `Core_execute_block`. + /// `TryRuntime_execute_block_no_state_root_and_signature_check` instead of + /// `Core_execute_block`. #[structopt(long)] no_state_root_check: bool, @@ -155,7 +156,12 @@ where let block = Block::new(header, extrinsics); let expected_spec_name = local_spec_name::(&ext, &executor); - ensure_matching_spec_name::(block_uri.clone(), expected_spec_name).await; + ensure_matching_spec_name::( + block_uri.clone(), + expected_spec_name, + shared.no_spec_name_check, + ) + .await; let _ = state_machine_call::( &ext, @@ -164,7 +170,7 @@ where if command.no_state_root_check { "Core_execute_block" } else { - "TryRuntime_execute_block_no_state_root_check" + "TryRuntime_execute_block_no_state_root_and_signature_check" }, block.encode().as_ref(), full_extensions(), diff --git a/utils/frame/try-runtime/cli/src/commands/follow_chain.rs b/utils/frame/try-runtime/cli/src/commands/follow_chain.rs index 3b868d34b0410..a4d79f69f3c0b 100644 --- a/utils/frame/try-runtime/cli/src/commands/follow_chain.rs +++ b/utils/frame/try-runtime/cli/src/commands/follow_chain.rs @@ -31,6 +31,9 @@ use sp_core::H256; use sp_runtime::traits::{Block as BlockT, Header, NumberFor}; use std::{fmt::Debug, str::FromStr}; +const SUB: &'static str = "chain_subscribeFinalizedHeads"; +const UN_SUB: &'static str = "chain_unsubscribeFinalizedHeads"; + /// Configurations of the [`Command::FollowChain`]. #[derive(Debug, Clone, structopt::StructOpt)] pub struct FollowChainCmd { @@ -59,9 +62,6 @@ where { let mut maybe_state_ext = None; - let sub = "chain_subscribeFinalizedHeads"; - let unsub = "chain_unsubscribeFinalizedHeads"; - let client = WsClientBuilder::default() .connection_timeout(std::time::Duration::new(20, 0)) .max_request_body_size(u32::MAX) @@ -69,15 +69,27 @@ where .await .unwrap(); - log::info!(target: LOG_TARGET, "subscribing to {:?} / {:?}", sub, unsub); + log::info!(target: LOG_TARGET, "subscribing to {:?} / {:?}", SUB, UN_SUB); let mut subscription: Subscription = - client.subscribe(&sub, JsonRpcParams::NoParams, &unsub).await.unwrap(); + client.subscribe(&SUB, JsonRpcParams::NoParams, &UN_SUB).await.unwrap(); let (code_key, code) = extract_code(&config.chain_spec)?; let executor = build_executor::(&shared, &config); let execution = shared.execution; - while let Some(header) = subscription.next().await.unwrap() { + loop { + let header = match subscription.next().await { + Ok(Some(header)) => header, + Ok(None) => { + log::warn!("subscription returned `None`. Probably decoding has failed."); + break + }, + Err(why) => { + log::warn!("subscription returned error: {:?}.", why); + continue + }, + }; + let hash = header.hash(); let number = header.number(); @@ -109,7 +121,12 @@ where ); let expected_spec_name = local_spec_name::(&new_ext, &executor); - ensure_matching_spec_name::(command.uri.clone(), expected_spec_name).await; + ensure_matching_spec_name::( + command.uri.clone(), + expected_spec_name, + shared.no_spec_name_check, + ) + .await; maybe_state_ext = Some(new_ext); } @@ -121,7 +138,7 @@ where &state_ext, &executor, execution, - "TryRuntime_execute_block_no_state_root_check", + "TryRuntime_execute_block_no_state_root_and_signature_check", block.encode().as_ref(), full_extensions(), )?; diff --git a/utils/frame/try-runtime/cli/src/commands/offchain_worker.rs b/utils/frame/try-runtime/cli/src/commands/offchain_worker.rs index 9adb80644a392..4fe429326d841 100644 --- a/utils/frame/try-runtime/cli/src/commands/offchain_worker.rs +++ b/utils/frame/try-runtime/cli/src/commands/offchain_worker.rs @@ -141,7 +141,8 @@ where }; let expected_spec_name = local_spec_name::(&ext, &executor); - ensure_matching_spec_name::(header_uri, expected_spec_name).await; + ensure_matching_spec_name::(header_uri, expected_spec_name, shared.no_spec_name_check) + .await; let _ = state_machine_call::( &ext, diff --git a/utils/frame/try-runtime/cli/src/commands/on_runtime_upgrade.rs b/utils/frame/try-runtime/cli/src/commands/on_runtime_upgrade.rs index 796802985b74b..09fa1976644bf 100644 --- a/utils/frame/try-runtime/cli/src/commands/on_runtime_upgrade.rs +++ b/utils/frame/try-runtime/cli/src/commands/on_runtime_upgrade.rs @@ -59,7 +59,8 @@ where if let Some(uri) = command.state.live_uri() { let expected_spec_name = local_spec_name::(&ext, &executor); - ensure_matching_spec_name::(uri, expected_spec_name).await; + ensure_matching_spec_name::(uri, expected_spec_name, shared.no_spec_name_check) + .await; } let (_, encoded_result) = state_machine_call::( diff --git a/utils/frame/try-runtime/cli/src/lib.rs b/utils/frame/try-runtime/cli/src/lib.rs index 8982a3d5c8108..4e7f46d353015 100644 --- a/utils/frame/try-runtime/cli/src/lib.rs +++ b/utils/frame/try-runtime/cli/src/lib.rs @@ -86,9 +86,19 @@ //! ## Commands //! //! See [`Command`] for more info. -//! +//! +//! ## Spec name check +//! +//! A common pitfall is that you might be running some test on top of the state of chain `x`, with +//! the runtime of chain `y`. To avoid this all commands do a spec-name check before executing +//! anything by default. This will check the spec name of the remote node your are connected to, +//! with the spec name of your local runtime and ensure that they match. +//! +//! Should you need to disable this on certain occasions, a top level flag of `--no-spec-name-check` +//! can be used. +//! //! ## Examples -//! +//! //! TODO use parity_scale_codec::Decode; @@ -156,7 +166,7 @@ pub enum Command { /// /// This executes the same runtime api as normal block import, namely `Core_execute_block`. If /// [`ExecuteBlockCmd::no_state_root_check`] is set, it uses a custom, try-runtime-only runtime - /// api called `TryRuntime_execute_block_no_state_root_check`. + /// api called `TryRuntime_execute_block_no_state_root_and_signature_check`. ExecuteBlock(commands::execute_block::ExecuteBlockCmd), /// Executes *the offchain worker hooks* of a given block against some state. @@ -221,6 +231,10 @@ pub struct SharedParams { /// [`sc_service::Configuration.default_heap_pages`]. #[structopt(long)] pub heap_pages: Option, + + /// When enabled, the spec name check will not panic, and instead only show a warning. + #[structopt(long)] + pub no_spec_name_check: bool, } /// Our `try-runtime` command. @@ -408,6 +422,7 @@ where pub(crate) async fn ensure_matching_spec_name( uri: String, expected_spec_name: String, + relaxed: bool, ) { match remote_externalities::rpc_api::get_runtime_version::(uri.clone(), None) .await @@ -418,11 +433,16 @@ pub(crate) async fn ensure_matching_spec_name { - panic!( + let msg = format!( "version mismatch: remote spec name: '{}', expected (local chain spec, aka. `--chain`): '{}'", spec, - expected_spec_name, + expected_spec_name ); + if relaxed { + log::warn!(target: LOG_TARGET, "{}", msg); + } else { + panic!("{}", msg); + } }, Err(why) => { log::error!( From 5580b4f429b0fd911e236f7234ffbee516fe7259 Mon Sep 17 00:00:00 2001 From: kianenigma Date: Fri, 17 Sep 2021 12:26:16 +0200 Subject: [PATCH 10/16] Some fixes --- bin/node/runtime/src/lib.rs | 4 +- frame/executive/src/lib.rs | 8 +- frame/try-runtime/src/lib.rs | 2 +- .../cli/src/commands/execute_block.rs | 40 +++--- .../cli/src/commands/follow_chain.rs | 13 +- .../cli/src/commands/offchain_worker.rs | 36 +++--- .../cli/src/commands/on_runtime_upgrade.rs | 14 ++- utils/frame/try-runtime/cli/src/lib.rs | 118 +++++++++++++----- 8 files changed, 157 insertions(+), 78 deletions(-) diff --git a/bin/node/runtime/src/lib.rs b/bin/node/runtime/src/lib.rs index dbcb158886633..f4b481f9428ed 100644 --- a/bin/node/runtime/src/lib.rs +++ b/bin/node/runtime/src/lib.rs @@ -1587,8 +1587,8 @@ impl_runtime_apis! { (weight, RuntimeBlockWeights::get().max_block) } - fn execute_block_no_state_root_and_signature_check(block: Block) -> Weight { - Executive::execute_block_no_state_root_and_signature_check(block) + fn execute_block_no_check(block: Block) -> Weight { + Executive::execute_block_no_check(block) } } diff --git a/frame/executive/src/lib.rs b/frame/executive/src/lib.rs index c1add3176f2a7..86e47ce960e48 100644 --- a/frame/executive/src/lib.rs +++ b/frame/executive/src/lib.rs @@ -224,9 +224,7 @@ where /// /// Should only be used for testing. #[cfg(feature = "try-runtime")] - pub fn execute_block_no_state_root_and_signature_check( - block: Block, - ) -> frame_support::weights::Weight + pub fn execute_block_no_check(block: Block) -> frame_support::weights::Weight where Block::Extrinsic: sp_runtime::traits::CheckableNoCheck, >::Checked: @@ -238,7 +236,9 @@ where let (header, extrinsics) = block.deconstruct(); Self::execute_extrinsics_with_book_keeping_no_signature_check(extrinsics, *header.number()); - // don't call `final_checks`, but do finalize the block. + + // don't call `final_checks`, but do finalize the block. This is critical to clear transient + // storage items, such as weight. let _header = >::finalize(); frame_system::Pallet::::block_weight().total() diff --git a/frame/try-runtime/src/lib.rs b/frame/try-runtime/src/lib.rs index ff2a9735fd820..754fc1d2a3303 100644 --- a/frame/try-runtime/src/lib.rs +++ b/frame/try-runtime/src/lib.rs @@ -38,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_state_root_and_signature_check(block: Block) -> Weight; + fn execute_block_no_check(block: Block) -> Weight; } } diff --git a/utils/frame/try-runtime/cli/src/commands/execute_block.rs b/utils/frame/try-runtime/cli/src/commands/execute_block.rs index b7e6bcf3f6d9e..b1011df81fd27 100644 --- a/utils/frame/try-runtime/cli/src/commands/execute_block.rs +++ b/utils/frame/try-runtime/cli/src/commands/execute_block.rs @@ -16,8 +16,8 @@ // limitations under the License. use crate::{ - build_executor, ensure_matching_spec_name, extract_code, full_extensions, hash_of, - local_spec_name, state_machine_call, SharedParams, State, LOG_TARGET, + 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}; @@ -33,10 +33,10 @@ pub struct ExecuteBlockCmd { overwrite_wasm_code: bool, /// If set, then the state root check is disabled by the virtue of calling into - /// `TryRuntime_execute_block_no_state_root_and_signature_check` instead of + /// `TryRuntime_execute_block_no_check` instead of /// `Core_execute_block`. #[structopt(long)] - no_state_root_check: bool, + no_check: bool, /// The block hash at which to fetch the block. /// @@ -49,7 +49,7 @@ pub struct ExecuteBlockCmd { )] block_at: Option, - /// The block uri from which to fetch the block. + /// 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. @@ -58,7 +58,7 @@ pub struct ExecuteBlockCmd { multiple = false, parse(try_from_str = crate::parse::url) )] - block_uri: Option, + block_ws_uri: Option, /// The state type to use. /// @@ -86,16 +86,16 @@ impl ExecuteBlockCmd { } } - fn block_uri(&self) -> String + fn block_ws_uri(&self) -> String where Block::Hash: FromStr, ::Err: Debug, { - match (&self.block_uri, &self.state) { - (Some(block_uri), State::Snap { .. }) => block_uri.to_owned(), - (Some(block_uri), State::Live { .. }) => { + 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::warn!(target: LOG_TARGET, "--block-uri is provided while state type is live, this will most likely lead to a nonsensical result."); - block_uri.to_owned() + block_ws_uri.to_owned() }, (None, State::Live { uri, .. }) => uri.clone(), (None, State::Snap { .. }) => { @@ -122,13 +122,13 @@ where let execution = shared.execution; let block_at = command.block_at::()?; - let block_uri = command.block_uri::(); - let block: Block = rpc_api::get_block::(block_uri.clone(), block_at).await?; + let block_ws_uri = command.block_ws_uri::(); + let block: Block = rpc_api::get_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_uri, + block_ws_uri, parent_hash ); @@ -155,10 +155,12 @@ where header.digest_mut().pop(); let block = Block::new(header, extrinsics); - let expected_spec_name = local_spec_name::(&ext, &executor); - ensure_matching_spec_name::( - block_uri.clone(), + let (expected_spec_name, expected_spec_version) = + local_spec::(&ext, &executor); + ensure_matching_spec::( + block_ws_uri.clone(), expected_spec_name, + expected_spec_version, shared.no_spec_name_check, ) .await; @@ -167,10 +169,10 @@ where &ext, &executor, execution, - if command.no_state_root_check { + if command.no_check { "Core_execute_block" } else { - "TryRuntime_execute_block_no_state_root_and_signature_check" + "TryRuntime_execute_block_no_check" }, block.encode().as_ref(), full_extensions(), diff --git a/utils/frame/try-runtime/cli/src/commands/follow_chain.rs b/utils/frame/try-runtime/cli/src/commands/follow_chain.rs index a4d79f69f3c0b..0526f5d327fb2 100644 --- a/utils/frame/try-runtime/cli/src/commands/follow_chain.rs +++ b/utils/frame/try-runtime/cli/src/commands/follow_chain.rs @@ -16,8 +16,8 @@ // limitations under the License. use crate::{ - build_executor, ensure_matching_spec_name, extract_code, full_extensions, local_spec_name, - parse, state_machine_call, SharedParams, LOG_TARGET, + build_executor, ensure_matching_spec, extract_code, full_extensions, local_spec, parse, + state_machine_call, SharedParams, LOG_TARGET, }; use jsonrpsee_ws_client::{ types::{traits::SubscriptionClient, v2::params::JsonRpcParams, Subscription}, @@ -64,6 +64,7 @@ where let client = WsClientBuilder::default() .connection_timeout(std::time::Duration::new(20, 0)) + .max_notifs_per_subscription(1024) .max_request_body_size(u32::MAX) .build(&command.uri) .await @@ -120,10 +121,12 @@ where new_ext.as_backend().root() ); - let expected_spec_name = local_spec_name::(&new_ext, &executor); - ensure_matching_spec_name::( + let (expected_spec_name, expected_spec_version) = + local_spec::(&new_ext, &executor); + ensure_matching_spec::( command.uri.clone(), expected_spec_name, + expected_spec_version, shared.no_spec_name_check, ) .await; @@ -138,7 +141,7 @@ where &state_ext, &executor, execution, - "TryRuntime_execute_block_no_state_root_and_signature_check", + "TryRuntime_execute_block_no_check", block.encode().as_ref(), full_extensions(), )?; diff --git a/utils/frame/try-runtime/cli/src/commands/offchain_worker.rs b/utils/frame/try-runtime/cli/src/commands/offchain_worker.rs index 4fe429326d841..97046dc89d562 100644 --- a/utils/frame/try-runtime/cli/src/commands/offchain_worker.rs +++ b/utils/frame/try-runtime/cli/src/commands/offchain_worker.rs @@ -16,8 +16,8 @@ // limitations under the License. use crate::{ - build_executor, ensure_matching_spec_name, extract_code, full_extensions, hash_of, - local_spec_name, parse, state_machine_call, SharedParams, State, LOG_TARGET, + build_executor, ensure_matching_spec, extract_code, full_extensions, hash_of, local_spec, + parse, state_machine_call, SharedParams, State, LOG_TARGET, }; use parity_scale_codec::Encode; use remote_externalities::rpc_api; @@ -45,7 +45,7 @@ pub struct OffchainWorkerCmd { )] header_at: Option, - /// The block uri from which to fetch the block. + /// The ws uri from which to fetch the header. /// /// 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. @@ -54,7 +54,7 @@ pub struct OffchainWorkerCmd { multiple = false, parse(try_from_str = parse::url) )] - header_uri: Option, + header_ws_uri: Option, /// The state type to use. #[structopt(subcommand)] @@ -80,16 +80,16 @@ impl OffchainWorkerCmd { } } - fn header_uri(&self) -> String + fn header_ws_uri(&self) -> String where Block::Hash: FromStr, ::Err: Debug, { - match (&self.header_uri, &self.state) { - (Some(header_uri), State::Snap { .. }) => header_uri.to_owned(), - (Some(header_uri), State::Live { .. }) => { + match (&self.header_ws_uri, &self.state) { + (Some(header_ws_uri), State::Snap { .. }) => header_ws_uri.to_owned(), + (Some(header_ws_uri), State::Live { .. }) => { log::warn!(target: LOG_TARGET, "--header-uri is provided while state type is live, this will most likely lead to a nonsensical result."); - header_uri.to_owned() + header_ws_uri.to_owned() }, (None, State::Live { uri, .. }) => uri.clone(), (None, State::Snap { .. }) => { @@ -117,13 +117,13 @@ where let execution = shared.execution; let header_at = command.header_at::()?; - let header_uri = command.header_uri::(); + let header_ws_uri = command.header_ws_uri::(); - let header = rpc_api::get_header::(header_uri.clone(), header_at).await?; + let header = rpc_api::get_header::(header_ws_uri.clone(), header_at).await?; log::info!( target: LOG_TARGET, "fetched header from {:?}, block number: {:?}", - header_uri, + header_ws_uri, header.number() ); @@ -140,9 +140,15 @@ where builder.build().await? }; - let expected_spec_name = local_spec_name::(&ext, &executor); - ensure_matching_spec_name::(header_uri, expected_spec_name, shared.no_spec_name_check) - .await; + let (expected_spec_name, expected_spec_version) = + local_spec::(&ext, &executor); + ensure_matching_spec::( + header_ws_uri, + expected_spec_name, + expected_spec_version, + shared.no_spec_name_check, + ) + .await; let _ = state_machine_call::( &ext, diff --git a/utils/frame/try-runtime/cli/src/commands/on_runtime_upgrade.rs b/utils/frame/try-runtime/cli/src/commands/on_runtime_upgrade.rs index 09fa1976644bf..5a6a92fe185c5 100644 --- a/utils/frame/try-runtime/cli/src/commands/on_runtime_upgrade.rs +++ b/utils/frame/try-runtime/cli/src/commands/on_runtime_upgrade.rs @@ -23,7 +23,7 @@ use sc_service::Configuration; use sp_runtime::traits::{Block as BlockT, NumberFor}; use crate::{ - build_executor, ensure_matching_spec_name, extract_code, local_spec_name, state_machine_call, + build_executor, ensure_matching_spec, extract_code, local_spec, state_machine_call, SharedParams, State, LOG_TARGET, }; @@ -58,9 +58,15 @@ where }; if let Some(uri) = command.state.live_uri() { - let expected_spec_name = local_spec_name::(&ext, &executor); - ensure_matching_spec_name::(uri, expected_spec_name, shared.no_spec_name_check) - .await; + let (expected_spec_name, expected_spec_version) = + local_spec::(&ext, &executor); + ensure_matching_spec::( + uri, + expected_spec_name, + expected_spec_version, + shared.no_spec_name_check, + ) + .await; } let (_, encoded_result) = state_machine_call::( diff --git a/utils/frame/try-runtime/cli/src/lib.rs b/utils/frame/try-runtime/cli/src/lib.rs index 4e7f46d353015..d3b0d946d6462 100644 --- a/utils/frame/try-runtime/cli/src/lib.rs +++ b/utils/frame/try-runtime/cli/src/lib.rs @@ -99,7 +99,47 @@ //! //! ## Examples //! -//! TODO +//! Run the migrations of the local runtime on the state of polkadot, from the polkadot repo where +//! we have `--chain polkadot-dev`, on the latest finalized block's state +//! +//! ``` +//! RUST_LOG=runtime=trace,try-runtime::cli=trace,executor=trace \ +//! cargo run try-runtime \ +//! --execution Native \ +//! --chain polkadot-dev \ +//! on-runtime-upgrade \ +//! --uri wss://rpc.polkadot.io +//! # note that we don't pass any --at, nothing means latest block. +//! ``` +//! +//! Same as previous one, but let's say we want to run this command from the substrate repo, where +//! we don't have a matching spec name/version. +//! +//! ``` +//! RUST_LOG=runtime=trace,try-runtime::cli=trace,executor=trace \ +//! cargo run try-runtime \ +//! --execution Native \ +//! --chain dev \ +//! --no-spec-name-check \ # mind this one! +//! on-runtime-upgrade \ +//! --uri wss://rpc.polkadot.io +//! ``` +//! +//! Same as the previous one, but run it at specific block number's state. This means that this +//! block hash's state shall not yet have been pruned in `rpc.polkadot.io`. +//! +//! //! ``` +//! RUST_LOG=runtime=trace,try-runtime::cli=trace,executor=trace \ +//! cargo run try-runtime \ +//! --execution Native \ +//! --chain dev \ +//! --no-spec-name-check \ # mind this one! +//! on-runtime-upgrade \ +//! --uri wss://rpc.polkadot.io +//! --at +//! ``` +//! +//! Moving to `execute-block` and `offchain-workers`. For these commands, you always needs to specify a block hash. For the rest of these examples, we assume we're in the polkadot repo. use parity_scale_codec::Decode; use remote_externalities::{ @@ -151,13 +191,19 @@ pub enum Command { /// /// Note that by default, this command does not overwrite the code, so in wasm execution, the /// live chain's code is used. This can be disabled if desired, see - /// [`ExecuteBlockCmd::overwrite_wasm_code`]. Note that if you do overwrite the wasm code, or - /// generally use the local runtime for this, you might + /// [`ExecuteBlockCmd::overwrite_wasm_code`]. + /// + /// Note that if you do overwrite the wasm code, or generally use the local runtime for this, + /// you might /// - not be able to decode the block, if the block format has changed. - /// - almost certainly get a state root mismatch. + /// - quite possible get a signature verification failure, since the spec and transaction + /// version are part of the signature's payload, and if they differ between your local + /// runtime and the remote counterparts, the signatures cannot be verified. + /// - almost certainly get a state root mismatch, since, well, you are executing a different + /// state transition function. /// - /// To make testing slightly more dynamic, you can disable the state root check by enabling - /// [`ExecuteBlockCmd::no_state_root_check`]. + /// To make testing slightly more dynamic, you can disable the state root and signature check + /// by enabling [`ExecuteBlockCmd::no_check`]. /// /// 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 @@ -165,8 +211,8 @@ pub enum Command { /// [`State::Snap`] is being used, then this needs to manually taken into consideration. /// /// This executes the same runtime api as normal block import, namely `Core_execute_block`. If - /// [`ExecuteBlockCmd::no_state_root_check`] is set, it uses a custom, try-runtime-only runtime - /// api called `TryRuntime_execute_block_no_state_root_and_signature_check`. + /// [`ExecuteBlockCmd::no_check`] is set, it uses a custom, try-runtime-only runtime + /// api called `TryRuntime_execute_block_no_check`. ExecuteBlock(commands::execute_block::ExecuteBlockCmd), /// Executes *the offchain worker hooks* of a given block against some state. @@ -192,6 +238,9 @@ pub enum Command { /// This allows the behavior of a new runtime to be inspected over a long period of time, with /// realistic transactions coming as input. /// + /// NOTE: this does NOT execute the offchain worker hooks of mirrored blocks. This might be + /// added in the future. + /// /// This does not support snapshot states, and can only work with a remote chain. Upon first /// connections, starts listening for finalized block events. Upon first notification, it /// initializes the state from the remote node, and starts applying that block, and all the @@ -419,29 +468,43 @@ where /// Check the spec_name of an `ext` /// /// If the version does not exist, or if it does not match with the given, it emits a warning. -pub(crate) async fn ensure_matching_spec_name( +pub(crate) async fn ensure_matching_spec( uri: String, expected_spec_name: String, + expected_spec_version: u32, relaxed: bool, ) { match remote_externalities::rpc_api::get_runtime_version::(uri.clone(), None) .await - .map(|version| String::from(version.spec_name.clone())) - .map(|spec_name| spec_name.to_lowercase()) + .map(|version| (String::from(version.spec_name.clone()), version.spec_version)) + .map(|(spec_name, spec_version)| (spec_name.to_lowercase(), spec_version)) { - Ok(spec) if spec == expected_spec_name => { - log::debug!(target: LOG_TARGET, "found matching spec name: {:?}", spec); - }, - Ok(spec) => { - let msg = format!( - "version mismatch: remote spec name: '{}', expected (local chain spec, aka. `--chain`): '{}'", - spec, - expected_spec_name - ); - if relaxed { - log::warn!(target: LOG_TARGET, "{}", msg); + Ok((name, version)) => { + // first, deal with spec name + if expected_spec_name == name { + log::info!(target: LOG_TARGET, "found matching spec name: {:?}", name); + } else { + let msg = format!( + "version mismatch: remote spec name: '{}', expected (local chain spec, aka. `--chain`): '{}'", + name, + expected_spec_name + ); + if relaxed { + log::warn!(target: LOG_TARGET, "{}", msg); + } else { + panic!("{}", msg); + } + } + + if expected_spec_version == version { + log::info!(target: LOG_TARGET, "found matching spec version: {:?}", version); } else { - panic!("{}", msg); + log::warn!( + target: LOG_TARGET, + "spec version mismatch (local {} != remote {}). This could cause some issues.", + expected_spec_version, + version + ); } }, Err(why) => { @@ -510,11 +573,11 @@ pub(crate) fn state_machine_call( +/// Get the spec `(name, version)` from the local runtime. +pub(crate) fn local_spec( ext: &TestExternalities, executor: &NativeElseWasmExecutor, -) -> String { +) -> (String, u32) { let (_, encoded) = state_machine_call::( &ext, &executor, @@ -526,7 +589,6 @@ pub(crate) fn local_spec_name::decode(&mut &*encoded) .map_err(|e| format!("failed to decode output: {:?}", e)) - .map(|v| v.spec_name) + .map(|v| (v.spec_name.into(), v.spec_version)) .expect("all runtimes should have version; qed") - .into() } From 93affb93575eab26bfd9e06a28008a46d97af285 Mon Sep 17 00:00:00 2001 From: Kian Paimani <5588131+kianenigma@users.noreply.github.com> Date: Fri, 17 Sep 2021 11:28:19 +0100 Subject: [PATCH 11/16] Update utils/frame/try-runtime/cli/src/lib.rs Co-authored-by: Zeke Mostov <32168567+emostov@users.noreply.github.com> --- utils/frame/try-runtime/cli/src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/utils/frame/try-runtime/cli/src/lib.rs b/utils/frame/try-runtime/cli/src/lib.rs index d3b0d946d6462..fbaecdf14bad8 100644 --- a/utils/frame/try-runtime/cli/src/lib.rs +++ b/utils/frame/try-runtime/cli/src/lib.rs @@ -43,7 +43,7 @@ //! //! All of the variables in the above statement are made *italic*. Let's look at each of them: //! -//! 1. **State** is the key-value pairs of data that consist the important information that any +//! 1. **State** is the key-value pairs of data that comprise the canonical information that any //! blockchain is keeping. A state can be full (all key-value pairs), or be partial (only pairs //! related to some pallets). Moreover, some keys are special and live outside of pallets, known as //! [`well_known_keys`] in substrate. The most important of these is the `:CODE:` key, which From e235fbfbb8f502746bb9d1e167144c0e0489da40 Mon Sep 17 00:00:00 2001 From: Kian Paimani <5588131+kianenigma@users.noreply.github.com> Date: Fri, 17 Sep 2021 11:28:31 +0100 Subject: [PATCH 12/16] Update utils/frame/try-runtime/cli/src/lib.rs Co-authored-by: Zeke Mostov <32168567+emostov@users.noreply.github.com> --- utils/frame/try-runtime/cli/src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/utils/frame/try-runtime/cli/src/lib.rs b/utils/frame/try-runtime/cli/src/lib.rs index fbaecdf14bad8..bdef219ccf0ab 100644 --- a/utils/frame/try-runtime/cli/src/lib.rs +++ b/utils/frame/try-runtime/cli/src/lib.rs @@ -45,7 +45,7 @@ //! //! 1. **State** is the key-value pairs of data that comprise the canonical information that any //! blockchain is keeping. A state can be full (all key-value pairs), or be partial (only pairs -//! related to some pallets). Moreover, some keys are special and live outside of pallets, known as +//! related to some pallets). Moreover, some keys are special and are not related to specific pallets, known as //! [`well_known_keys`] in substrate. The most important of these is the `:CODE:` key, which //! contains the code used for execution, when `--execution Wasm` is used. //! 2. *A runtime-api* is a call into the function defined in the runtime, *on top of a given From 211142364a54a96322f2c8ac4b8087e8b791e12a Mon Sep 17 00:00:00 2001 From: Kian Paimani <5588131+kianenigma@users.noreply.github.com> Date: Fri, 17 Sep 2021 11:30:45 +0100 Subject: [PATCH 13/16] Update utils/frame/try-runtime/cli/src/lib.rs Co-authored-by: Zeke Mostov <32168567+emostov@users.noreply.github.com> --- utils/frame/try-runtime/cli/src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/utils/frame/try-runtime/cli/src/lib.rs b/utils/frame/try-runtime/cli/src/lib.rs index bdef219ccf0ab..e9a1f0ed56056 100644 --- a/utils/frame/try-runtime/cli/src/lib.rs +++ b/utils/frame/try-runtime/cli/src/lib.rs @@ -49,7 +49,7 @@ //! [`well_known_keys`] in substrate. The most important of these is the `:CODE:` key, which //! contains the code used for execution, when `--execution Wasm` is used. //! 2. *A runtime-api* is a call into the function defined in the runtime, *on top of a given -//! state*. Each subcommand of `try-runtime` utilizes a specific one of these. +state*. Each subcommand of `try-runtime` utilizes a specific *runtime-api*. //! 3. Finally, the **runtime** is the actual code that is used to execute the aforementioned //! runtime-api. All substrate based chains always have two runtimes: native and wasm. The decision //! of which one is chosen is slightly non-trivial. First, let's look at the options: From 7d60da9dbad08dccb0f4018d1ce59baeb14734d1 Mon Sep 17 00:00:00 2001 From: kianenigma Date: Fri, 17 Sep 2021 14:02:21 +0200 Subject: [PATCH 14/16] final tweaks, hopefully. --- frame/executive/src/lib.rs | 40 +--- .../src/generic/unchecked_extrinsic.rs | 28 --- primitives/runtime/src/traits.rs | 15 -- .../cli/src/commands/execute_block.rs | 12 +- .../cli/src/commands/offchain_worker.rs | 6 +- utils/frame/try-runtime/cli/src/lib.rs | 213 ++++++++++++------ 6 files changed, 158 insertions(+), 156 deletions(-) diff --git a/frame/executive/src/lib.rs b/frame/executive/src/lib.rs index 86e47ce960e48..4dc75f3303864 100644 --- a/frame/executive/src/lib.rs +++ b/frame/executive/src/lib.rs @@ -224,18 +224,13 @@ where /// /// Should only be used for testing. #[cfg(feature = "try-runtime")] - pub fn execute_block_no_check(block: Block) -> frame_support::weights::Weight - where - Block::Extrinsic: sp_runtime::traits::CheckableNoCheck, - >::Checked: - Applyable> + GetDispatchInfo, - { + 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_no_signature_check(extrinsics, *header.number()); + 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. @@ -244,37 +239,6 @@ where frame_system::Pallet::::block_weight().total() } - #[cfg(feature = "try-runtime")] - fn execute_extrinsics_with_book_keeping_no_signature_check( - extrinsics: Vec, - block_number: NumberFor, - ) where - Block::Extrinsic: sp_runtime::traits::CheckableNoCheck, - >::Checked: - Applyable> + GetDispatchInfo, - { - use sp_runtime::traits::CheckableNoCheck; - - extrinsics.into_iter().for_each(|uxt| { - let encoded = uxt.encode(); - let encoded_len = encoded.len(); - - // skip signature verification - let xt = uxt.check_i_know_i_should_not_be_using_this(&Default::default()).unwrap(); - >::note_extrinsic(encoded); - - // dispatch - let dispatch_info = xt.get_dispatch_info(); - let r = Applyable::apply::(xt, &dispatch_info, encoded_len).unwrap(); - - >::note_applied_extrinsic(&r, dispatch_info); - }); - - // post-extrinsics book-keeping - >::note_finished_extrinsics(); - Self::idle_and_finalize_hook(block_number); - } - /// Execute all `OnRuntimeUpgrade` of this runtime, including the pre and post migration checks. /// /// This should only be used for testing. diff --git a/primitives/runtime/src/generic/unchecked_extrinsic.rs b/primitives/runtime/src/generic/unchecked_extrinsic.rs index 62d8537af9917..95f4f2f3584d9 100644 --- a/primitives/runtime/src/generic/unchecked_extrinsic.rs +++ b/primitives/runtime/src/generic/unchecked_extrinsic.rs @@ -159,34 +159,6 @@ where } } -#[cfg(feature = "try-runtime")] -impl traits::CheckableNoCheck - for UncheckedExtrinsic -where - Address: Member + MaybeDisplay, - Call: Encode + Member, - Signature: Member + traits::Verify, - ::Signer: IdentifyAccount, - Extra: SignedExtension, - AccountId: Member + MaybeDisplay, - Lookup: traits::Lookup, -{ - type Checked = CheckedExtrinsic; - - fn check_i_know_i_should_not_be_using_this( - self, - lookup: &Lookup, - ) -> Result { - Ok(match self.signature { - Some((signed, _, extra)) => { - let signed = lookup.lookup(signed)?; - CheckedExtrinsic { signed: Some((signed, extra)), function: self.function } - }, - None => CheckedExtrinsic { signed: None, function: self.function }, - }) - } -} - impl ExtrinsicMetadata for UncheckedExtrinsic where diff --git a/primitives/runtime/src/traits.rs b/primitives/runtime/src/traits.rs index ffdab2ab0f1bf..65c063fde1696 100644 --- a/primitives/runtime/src/traits.rs +++ b/primitives/runtime/src/traits.rs @@ -779,21 +779,6 @@ pub trait Checkable: Sized { fn check(self, c: &Context) -> Result; } -/// A variant if `Checkable` that avoids any checks. -/// -/// Only useful for testing, without breaking the generic assumptions of the runtime. -#[cfg(feature = "try-runtime")] -pub trait CheckableNoCheck: Sized { - /// Returned if `check` succeeds. - type Checked; - - /// Check self, given an instance of Context. - fn check_i_know_i_should_not_be_using_this( - self, - c: &Context, - ) -> Result; -} - /// A "checkable" piece of information, used by the standard Substrate Executive in order to /// check the validity of a piece of extrinsic information, usually by verifying the signature. /// Implement for pieces of information that don't require additional context in order to be diff --git a/utils/frame/try-runtime/cli/src/commands/execute_block.rs b/utils/frame/try-runtime/cli/src/commands/execute_block.rs index b1011df81fd27..19422db90119f 100644 --- a/utils/frame/try-runtime/cli/src/commands/execute_block.rs +++ b/utils/frame/try-runtime/cli/src/commands/execute_block.rs @@ -63,6 +63,8 @@ pub struct ExecuteBlockCmd { /// 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, } @@ -76,7 +78,7 @@ impl ExecuteBlockCmd { match (&self.block_at, &self.state) { (Some(block_at), State::Snap { .. }) => hash_of::(&block_at), (Some(block_at), State::Live { .. }) => { - log::warn!(target: LOG_TARGET, "--block-at is provided while state type is live, this will most likely lead to a nonsensical result."); + log::warn!(target: LOG_TARGET, "--block-at is provided while state type is live. the `Live::at` will be ignored"); hash_of::(&block_at) }, (None, State::Live { at: Some(at), .. }) => hash_of::(&at), @@ -94,7 +96,7 @@ impl ExecuteBlockCmd { 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::warn!(target: LOG_TARGET, "--block-uri is provided while state type is live, this will most likely lead to a nonsensical result."); + 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(), @@ -169,11 +171,7 @@ where &ext, &executor, execution, - if command.no_check { - "Core_execute_block" - } else { - "TryRuntime_execute_block_no_check" - }, + if command.no_check { "TryRuntime_execute_block_no_check" } else { "Core_execute_block" }, block.encode().as_ref(), full_extensions(), )?; diff --git a/utils/frame/try-runtime/cli/src/commands/offchain_worker.rs b/utils/frame/try-runtime/cli/src/commands/offchain_worker.rs index 97046dc89d562..6f37e4b3849fa 100644 --- a/utils/frame/try-runtime/cli/src/commands/offchain_worker.rs +++ b/utils/frame/try-runtime/cli/src/commands/offchain_worker.rs @@ -34,7 +34,7 @@ pub struct OffchainWorkerCmd { #[structopt(long)] overwrite_wasm_code: bool, - /// The block hash at which to fetch the block. + /// The block hash at which to fetch the header. /// /// 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. @@ -70,7 +70,7 @@ impl OffchainWorkerCmd { match (&self.header_at, &self.state) { (Some(header_at), State::Snap { .. }) => hash_of::(&header_at), (Some(header_at), State::Live { .. }) => { - log::warn!(target: LOG_TARGET, "--header-at is provided while state type is live, this will most likely lead to a nonsensical result."); + log::error!(target: LOG_TARGET, "--header-at is provided while state type is live, this will most likely lead to a nonsensical result."); hash_of::(&header_at) }, (None, State::Live { at: Some(at), .. }) => hash_of::(&at), @@ -88,7 +88,7 @@ impl OffchainWorkerCmd { match (&self.header_ws_uri, &self.state) { (Some(header_ws_uri), State::Snap { .. }) => header_ws_uri.to_owned(), (Some(header_ws_uri), State::Live { .. }) => { - log::warn!(target: LOG_TARGET, "--header-uri is provided while state type is live, this will most likely lead to a nonsensical result."); + log::error!(target: LOG_TARGET, "--header-uri is provided while state type is live, this will most likely lead to a nonsensical result."); header_ws_uri.to_owned() }, (None, State::Live { uri, .. }) => uri.clone(), diff --git a/utils/frame/try-runtime/cli/src/lib.rs b/utils/frame/try-runtime/cli/src/lib.rs index d3b0d946d6462..108f41cdbd36f 100644 --- a/utils/frame/try-runtime/cli/src/lib.rs +++ b/utils/frame/try-runtime/cli/src/lib.rs @@ -44,48 +44,56 @@ //! All of the variables in the above statement are made *italic*. Let's look at each of them: //! //! 1. **State** is the key-value pairs of data that consist the important information that any -//! blockchain is keeping. A state can be full (all key-value pairs), or be partial (only pairs -//! related to some pallets). Moreover, some keys are special and live outside of pallets, known as -//! [`well_known_keys`] in substrate. The most important of these is the `:CODE:` key, which -//! contains the code used for execution, when `--execution Wasm` is used. +//! blockchain is keeping. A state can be full (all key-value pairs), or be partial (only pairs +//! related to some pallets). Moreover, some keys are special and live outside of pallets, known +//! as [`well_known_keys`] in substrate. The most important of these is the `:CODE:` key, which +//! contains the code used for execution, when `--execution Wasm` is used. +//! //! 2. *A runtime-api* is a call into the function defined in the runtime, *on top of a given -//! state*. Each subcommand of `try-runtime` utilizes a specific one of these. +//! state*. Each subcommand of `try-runtime` utilizes a specific one of these. +//! //! 3. Finally, the **runtime** is the actual code that is used to execute the aforementioned -//! runtime-api. All substrate based chains always have two runtimes: native and wasm. The decision -//! of which one is chosen is slightly non-trivial. First, let's look at the options: -//! -//! 1. Native: this means that the runtime that is **in your codebase**, aka whatever you see in -//! your editor is being used. This runtime is easier for diagnostics. In the rest of the -//! documentation, by "local runtime", we mean the na 2. Wasm: this means that whatever is stored in -//! the `:CODE:` key of the state that your scrape is being used. In plain sight, since the entire -//! state (including `:CODE:`) is scraped from a remote chain, you could conclude that the wasm -//! runtime, if used, is always equal to the canonical runtime. That's factually true, but then the -//! testing would be quite lame. Typically, with try-runtime, you don't want to execute whatever -//! code is already on the live chain. Instead, you want your local runtime (which typically -//! includes a non-released feature) to be used. This is why try-runtime overwrites the wasm runtime -//! (at `:CODE:`) with the local runtime as well. That being said, this behavior can be controlled -//! in certain subcommands with a special flag. +//! runtime-api. All substrate based chains always have two runtimes: native and wasm. The +//! decision of which one is chosen is slightly non-trivial. First, let's look at the options: +//! +//! 1. Native: this means that the runtime that is **in your codebase**, aka whatever you see in +//! your editor, is being used. This runtime is easier for diagnostics. We refer to this as +//! the "local runtime". +//! +//! 2. Wasm: this means that whatever is stored in the `:CODE:` key of the state that your +//! scrape is being used. In plain sight, since the entire state (including `:CODE:`) is +//! scraped from a remote chain, you could conclude that the wasm runtime, if used, is always +//! equal to the canonical runtime of the live chain (i.e. NOT the "local runtime"). That's +//! factually true, but then the testing would be quite lame. Typically, with try-runtime, +//! you don't want to execute whatever code is already on the live chain. Instead, you want +//! your local runtime (which typically includes a non-released feature) to be used. This is +//! why try-runtime overwrites the wasm runtime (at `:CODE:`) with the local runtime as well. +//! That being said, this behavior can be controlled in certain subcommands with a special +//! flag (`--overwrite-wasm-code`). //! //! The decision of which runtime is eventually used is based on two facts: //! //! 1. `--execution` flag. If you specify `wasm`, then it is *always* wasm. If it is `native`, then -//! if and ONLY IF the spec versions match, then the native runtime is used. Else, wasm runtime is -//! used again. -//! 2. `--chain` flag (if present in your cli), which determines *which native runtime*, if needed, -//! might be used. +//! if and ONLY IF the spec versions match, then the native runtime is used. Else, wasm runtime +//! is used again. +//! 2. `--chain` flag (if present in your cli), which determines *which local runtime*, is selected. +//! This will specify: +//! 1. which native runtime is used, if you select `--execution Native` +//! 2. which wasm runtime is used to replace the `:CODE:`, if try-runtime is instructed to do +//! so. //! //! All in all, if the term "local runtime" is used in the rest of this crate's documentation, it //! means either the native runtime, or the wasm runtime when overwritten inside `:CODE:`. In other //! words, it means your... well, "local runtime", regardless of wasm or native. //! -//! To make sure there are no errors regarding this, always run any try-runtime command with -//! `executor=trace` logging targets, which will specify which runtime is being used per api call. +//! //! See [`Command`] for more information about each command's specific customization flags, and +//! assumptions regarding the runtime being used. //! -//! Furthermore, other relevant log targets are: `try-runtime::cli`, `remote-ext`, and `runtime`. -//! -//! ## Commands +//! Finally, To make sure there are no errors regarding this, always run any `try-runtime` command +//! with `executor=trace` logging targets, which will specify which runtime is being used per api +//! call. //! -//! See [`Command`] for more info. +//! Furthermore, other relevant log targets are: `try-runtime::cli`, `remote-ext`, and `runtime`. //! //! ## Spec name check //! @@ -97,6 +105,18 @@ //! Should you need to disable this on certain occasions, a top level flag of `--no-spec-name-check` //! can be used. //! +//! The spec version is also always inspected, but if it is a mismatch, it will only emit a warning. +//! +//! ## Note nodes that operate with `try-runtime` +//! +//! There are a number of flags that need to be preferably set on a running node in order to work +//! well with try-runtime's expensive RPC queries: +//! +//! - set `--rpc-max-payload 1000` to ensure large RPC queries can work. +//! - set `--rpc-cors all` to ensure ws connections can come through. +//! +//! Note that *none* of the try-runtime operations need unsafe RPCs. +//! //! ## Examples //! //! Run the migrations of the local runtime on the state of polkadot, from the polkadot repo where @@ -104,12 +124,13 @@ //! //! ``` //! RUST_LOG=runtime=trace,try-runtime::cli=trace,executor=trace \ -//! cargo run try-runtime \ -//! --execution Native \ -//! --chain polkadot-dev \ -//! on-runtime-upgrade \ -//! --uri wss://rpc.polkadot.io -//! # note that we don't pass any --at, nothing means latest block. +//! cargo run try-runtime \ +//! --execution Native \ +//! --chain polkadot-dev \ +//! on-runtime-upgrade \ +//! live \ +//! --uri wss://rpc.polkadot.io +//! # note that we don't pass any --at, nothing means latest block. //! ``` //! //! Same as previous one, but let's say we want to run this command from the substrate repo, where @@ -117,29 +138,90 @@ //! //! ``` //! RUST_LOG=runtime=trace,try-runtime::cli=trace,executor=trace \ -//! cargo run try-runtime \ -//! --execution Native \ -//! --chain dev \ -//! --no-spec-name-check \ # mind this one! -//! on-runtime-upgrade \ -//! --uri wss://rpc.polkadot.io +//! cargo run try-runtime \ +//! --execution Native \ +//! --chain dev \ +//! --no-spec-name-check \ # mind this one! +//! on-runtime-upgrade \ +//! live \ +//! --uri wss://rpc.polkadot.io //! ``` //! //! Same as the previous one, but run it at specific block number's state. This means that this //! block hash's state shall not yet have been pruned in `rpc.polkadot.io`. //! -//! //! ``` +//! ``` +//! RUST_LOG=runtime=trace,try-runtime::cli=trace,executor=trace \ +//! cargo run try-runtime \ +//! --execution Native \ +//! --chain dev \ +//! --no-spec-name-check \ # mind this one! on-runtime-upgrade \ +//! on-runtime-upgrade \ +//! live \ +//! --uri wss://rpc.polkadot.io \ +//! --at +//! ``` +//! +//! Moving to `execute-block` and `offchain-workers`. For these commands, you always needs to +//! specify a block hash. For the rest of these examples, we assume we're in the polkadot repo. +//! +//! First, let's assume you are in a branch that has the same spec name/version as the live polkadot +//! network. +//! +//! ``` +//! RUST_LOG=runtime=trace,try-runtime::cli=trace,executor=trace \ +//! cargo run try-runtime \ +//! --execution Wasm \ +//! --chain polkadot-dev \ +//! --uri wss://rpc.polkadot.io \ +//! execute-block \ +//! live \ +//! --at +//! ``` +//! +//! This is wasm, so it will technically execute the code that lives on the live network. Let's say +//! you want to execute your local runtime. Since you have a matching spec versions, you can simply +//! change `--execution Wasm` to `--execution Native` to achieve this. Your logs of `executor=trace` +//! should show something among the lines of: +//! +//! ``` +//! Request for native execution succeeded (native: polkadot-9900 (parity-polkadot-0.tx7.au0), chain: polkadot-9900 (parity-polkadot-0.tx7.au0)) +//! ``` +//! +//! If you don't have matching spec versions, then are doomed to execute wasm. In this case, you can +//! manually overwrite the wasm code with your local runtime: +//! +//! ``` //! RUST_LOG=runtime=trace,try-runtime::cli=trace,executor=trace \ -//! cargo run try-runtime \ -//! --execution Native \ -//! --chain dev \ -//! --no-spec-name-check \ # mind this one! -//! on-runtime-upgrade \ -//! --uri wss://rpc.polkadot.io -//! --at +//! cargo run try-runtime \ +//! --execution Wasm \ +//! --chain polkadot-dev \ +//! execute-block \ +//! live \ +//! --uri wss://rpc.polkadot.io \ +//! --at \ +//! --overwrite-wasm-code //! ``` //! -//! Moving to `execute-block` and `offchain-workers`. For these commands, you always needs to specify a block hash. For the rest of these examples, we assume we're in the polkadot repo. +//! For all of these blocks, the block with hash `` is being used, and the initial state +//! is the state of the parent hash. This is because by omitting [`ExecuteBlockCmd::block_at`], the +//! `--at` is used for both. This should be good enough for 99% of the cases. The only case where +//! you need to specify `block-at` and `block-ws-uri` is with snapshots. Let's say you have a file +//! `snap` and you know it corresponds to the state of the parent block of `X`. Then you'd do: +//! +//! ``` +//! RUST_LOG=runtime=trace,try-runtime::cli=trace,executor=trace \ +//! cargo run try-runtime \ +//! --execution Wasm \ +//! --chain polkadot-dev \ +//! --uri wss://rpc.polkadot.io \ +//! execute-block \ +//! --block-at \ +//! --block-ws-uri wss://rpc.polkadot.io \ +//! --overwrite-wasm-code \ +//! snap \ +//! -s snap \ +//! ``` use parity_scale_codec::Decode; use remote_externalities::{ @@ -176,9 +258,9 @@ pub enum Command { /// /// This uses a custom runtime api call, namely "TryRuntime_on_runtime_upgrade". /// - /// This always overwrites the wasm code with the local runtime, to ensure the new migrations - /// are being executed. Re-executing already existing migrations is evidently not very - /// exciting. + /// This always overwrites the wasm code with the local runtime (specified by `--chain`), to + /// ensure the new migrations are being executed. Re-executing already existing migrations is + /// evidently not very exciting. OnRuntimeUpgrade(commands::on_runtime_upgrade::OnRuntimeUpgradeCmd), /// Executes the given block against some state. @@ -196,19 +278,20 @@ pub enum Command { /// Note that if you do overwrite the wasm code, or generally use the local runtime for this, /// you might /// - not be able to decode the block, if the block format has changed. - /// - quite possible get a signature verification failure, since the spec and transaction - /// version are part of the signature's payload, and if they differ between your local - /// runtime and the remote counterparts, the signatures cannot be verified. - /// - almost certainly get a state root mismatch, since, well, you are executing a different - /// state transition function. + /// - quite possibly will get a signature verification failure, since the spec and + /// transaction version are part of the signature's payload, and if they differ between + /// your local runtime and the remote counterparts, the signatures cannot be verified. + /// - almost certainly will get a state root mismatch, since, well, you are executing a + /// different state transition function. /// - /// To make testing slightly more dynamic, you can disable the state root and signature check - /// by enabling [`ExecuteBlockCmd::no_check`]. + /// 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. /// /// 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 has of the target block is used to scrape the state. If - /// [`State::Snap`] is being used, then this needs to manually taken into consideration. + /// 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 @@ -242,9 +325,9 @@ pub enum Command { /// added in the future. /// /// This does not support snapshot states, and can only work with a remote chain. Upon first - /// connections, starts listening for finalized block events. Upon first notification, it - /// initializes the state from the remote node, and starts applying that block, and all the - /// ones that follows, to the same growing state. + /// connections, starts listening for finalized block events. Upon first block notification, it + /// initializes the state from the remote node, and starts applying that block, plus all the + /// blocks that follow, to the same growing state. FollowChain(commands::follow_chain::FollowChainCmd), } From b34d236eb700fd755df72b7798f8ae9699eaf5a2 Mon Sep 17 00:00:00 2001 From: kianenigma Date: Mon, 20 Sep 2021 19:43:17 +0200 Subject: [PATCH 15/16] a little self-review --- bin/node/runtime/src/lib.rs | 2 +- frame/executive/Cargo.toml | 2 +- primitives/runtime/Cargo.toml | 1 - utils/frame/try-runtime/cli/src/lib.rs | 32 ++++++++++++++------------ 4 files changed, 19 insertions(+), 18 deletions(-) diff --git a/bin/node/runtime/src/lib.rs b/bin/node/runtime/src/lib.rs index f4b481f9428ed..374f3f070fb74 100644 --- a/bin/node/runtime/src/lib.rs +++ b/bin/node/runtime/src/lib.rs @@ -1581,7 +1581,7 @@ impl_runtime_apis! { impl frame_try_runtime::TryRuntime for Runtime { 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 + // 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) diff --git a/frame/executive/Cargo.toml b/frame/executive/Cargo.toml index 706fee36d6c28..1abbf50e6a4c4 100644 --- a/frame/executive/Cargo.toml +++ b/frame/executive/Cargo.toml @@ -47,4 +47,4 @@ std = [ "sp-tracing/std", "sp-std/std", ] -try-runtime = ["frame-support/try-runtime", "sp-runtime/try-runtime"] +try-runtime = ["frame-support/try-runtime"] diff --git a/primitives/runtime/Cargo.toml b/primitives/runtime/Cargo.toml index c933a291b7837..5ac5bcf1963e0 100644 --- a/primitives/runtime/Cargo.toml +++ b/primitives/runtime/Cargo.toml @@ -58,4 +58,3 @@ std = [ "hash256-std-hasher/std", "either/use_std", ] -try-runtime = [] diff --git a/utils/frame/try-runtime/cli/src/lib.rs b/utils/frame/try-runtime/cli/src/lib.rs index 1bdc81e488465..e7407f2f408fa 100644 --- a/utils/frame/try-runtime/cli/src/lib.rs +++ b/utils/frame/try-runtime/cli/src/lib.rs @@ -23,7 +23,7 @@ //! control over what is being executed in which environment. It is recommended that user's first //! familiarize themselves with substrate in depth, particularly the execution model. It is critical //! to deeply understand how the wasm/native interactions, and the runtime apis work in the -//! substrate runtime. +//! substrate runtime, before commencing to working with `try-runtime`. //! //! #### Resources //! @@ -47,13 +47,14 @@ //! blockchain is keeping. A state can be full (all key-value pairs), or be partial (only pairs //! related to some pallets). Moreover, some keys are special and are not related to specific //! pallets, known as [`well_known_keys`] in substrate. The most important of these is the -//! `:CODE:` key, which contains the code used for execution, when `--execution Wasm` is used. +//! `:CODE:` key, which contains the code used for execution, when wasm execution is chosen. //! -//! 2. *A runtime-api* is a call into the function defined in the runtime, *on top of a given -//! state*. Each subcommand of `try-runtime` utilizes a specific *runtime-api*. 3. Finally, the -//! **runtime** is the actual code that is used to execute the aforementioned runtime-api. All -//! substrate based chains always have two runtimes: native and wasm. The decision of which -//! one is chosen is slightly non-trivial. First, let's look at the options: +//! 2. *A runtime-api* call is a call into a function defined in the runtime, *on top of a given +//! state*. Each subcommand of `try-runtime` utilizes a specific *runtime-api*. +//! +//! 3. Finally, the **runtime** is the actual code that is used to execute the aforementioned +//! runtime-api. All substrate based chains always have two runtimes: native and wasm. The +//! decision of which one is chosen is non-trivial. First, let's look at the options: //! //! 1. Native: this means that the runtime that is **in your codebase**, aka whatever you see in //! your editor, is being used. This runtime is easier for diagnostics. We refer to this as @@ -121,7 +122,7 @@ //! Run the migrations of the local runtime on the state of polkadot, from the polkadot repo where //! we have `--chain polkadot-dev`, on the latest finalized block's state //! -//! ``` +//! ```ignore //! RUST_LOG=runtime=trace,try-runtime::cli=trace,executor=trace \ //! cargo run try-runtime \ //! --execution Native \ @@ -135,7 +136,7 @@ //! Same as previous one, but let's say we want to run this command from the substrate repo, where //! we don't have a matching spec name/version. //! -//! ``` +//! ```ignore //! RUST_LOG=runtime=trace,try-runtime::cli=trace,executor=trace \ //! cargo run try-runtime \ //! --execution Native \ @@ -149,7 +150,7 @@ //! Same as the previous one, but run it at specific block number's state. This means that this //! block hash's state shall not yet have been pruned in `rpc.polkadot.io`. //! -//! ``` +//! ```ignore //! RUST_LOG=runtime=trace,try-runtime::cli=trace,executor=trace \ //! cargo run try-runtime \ //! --execution Native \ @@ -167,7 +168,7 @@ //! First, let's assume you are in a branch that has the same spec name/version as the live polkadot //! network. //! -//! ``` +//! ```ignore //! RUST_LOG=runtime=trace,try-runtime::cli=trace,executor=trace \ //! cargo run try-runtime \ //! --execution Wasm \ @@ -183,14 +184,14 @@ //! change `--execution Wasm` to `--execution Native` to achieve this. Your logs of `executor=trace` //! should show something among the lines of: //! -//! ``` +//! ```ignore //! Request for native execution succeeded (native: polkadot-9900 (parity-polkadot-0.tx7.au0), chain: polkadot-9900 (parity-polkadot-0.tx7.au0)) //! ``` //! //! If you don't have matching spec versions, then are doomed to execute wasm. In this case, you can //! manually overwrite the wasm code with your local runtime: //! -//! ``` +//! ```ignore //! RUST_LOG=runtime=trace,try-runtime::cli=trace,executor=trace \ //! cargo run try-runtime \ //! --execution Wasm \ @@ -208,7 +209,7 @@ //! you need to specify `block-at` and `block-ws-uri` is with snapshots. Let's say you have a file //! `snap` and you know it corresponds to the state of the parent block of `X`. Then you'd do: //! -//! ``` +//! ```ignore //! RUST_LOG=runtime=trace,try-runtime::cli=trace,executor=trace \ //! cargo run try-runtime \ //! --execution Wasm \ @@ -549,7 +550,8 @@ where /// Check the spec_name of an `ext` /// -/// If the version does not exist, or if it does not match with the given, it emits a warning. +/// If the spec names don't match, if `relaxed`, then it emits a warning, else it panics. +/// If the spec versions don't match, it only ever emits a warning. pub(crate) async fn ensure_matching_spec( uri: String, expected_spec_name: String, From 474a02b1a9945097c2259b86efdc7d3f287e41fa Mon Sep 17 00:00:00 2001 From: kianenigma Date: Tue, 21 Sep 2021 14:14:01 +0200 Subject: [PATCH 16/16] Add the ext root check --- frame/executive/src/lib.rs | 18 +++++++++++++++--- .../cli/src/commands/on_runtime_upgrade.rs | 2 +- 2 files changed, 16 insertions(+), 4 deletions(-) diff --git a/frame/executive/src/lib.rs b/frame/executive/src/lib.rs index 4dc75f3303864..244253185d238 100644 --- a/frame/executive/src/lib.rs +++ b/frame/executive/src/lib.rs @@ -232,9 +232,21 @@ where 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 = >::finalize(); + // do some of the checks that would normally happen in `final_checks`, but definitely skip + // the state root check. + { + let new_header = >::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::::block_weight().total() } diff --git a/utils/frame/try-runtime/cli/src/commands/on_runtime_upgrade.rs b/utils/frame/try-runtime/cli/src/commands/on_runtime_upgrade.rs index 5a6a92fe185c5..86f5548b8aafa 100644 --- a/utils/frame/try-runtime/cli/src/commands/on_runtime_upgrade.rs +++ b/utils/frame/try-runtime/cli/src/commands/on_runtime_upgrade.rs @@ -85,7 +85,7 @@ where "TryRuntime_on_runtime_upgrade executed without errors. Consumed weight = {}, total weight = {} ({})", weight, total_weight, - weight as f64 / total_weight as f64 + weight as f64 / total_weight.max(1) as f64 ); Ok(())