diff --git a/substrate/Cargo.lock b/substrate/Cargo.lock index ffcf958203..ee78c31645 100644 --- a/substrate/Cargo.lock +++ b/substrate/Cargo.lock @@ -10649,6 +10649,7 @@ dependencies = [ "sp-blockchain", "sp-core", "sp-externalities", + "sp-io", "sp-keystore", "sp-runtime", "sp-state-machine", diff --git a/substrate/frame/election-provider-multi-phase/src/lib.rs b/substrate/frame/election-provider-multi-phase/src/lib.rs index 2bb47a8778..2864ca518d 100644 --- a/substrate/frame/election-provider-multi-phase/src/lib.rs +++ b/substrate/frame/election-provider-multi-phase/src/lib.rs @@ -608,7 +608,7 @@ pub mod pallet { type Fallback: Get; /// Origin that can control this pallet. Note that any action taken by this origin (such) - /// as providing an emergency solution is not checked. Thus, it must be a trusted origin. + /// as providing an emergency solution is not checked. Thus, it must be a trusted origin. type ForceOrigin: EnsureOrigin; /// The configuration of benchmarking. diff --git a/substrate/utils/frame/remote-externalities/src/lib.rs b/substrate/utils/frame/remote-externalities/src/lib.rs index a77650d042..4b6738f3b9 100644 --- a/substrate/utils/frame/remote-externalities/src/lib.rs +++ b/substrate/utils/frame/remote-externalities/src/lib.rs @@ -43,10 +43,12 @@ type KeyPair = (StorageKey, StorageData); const LOG_TARGET: &str = "remote-ext"; const DEFAULT_TARGET: &str = "wss://rpc.polkadot.io"; -const BATCH_SIZE: usize = 512; +const BATCH_SIZE: usize = 1000; jsonrpsee_proc_macros::rpc_client_api! { RpcApi { + #[rpc(method = "state_getStorage", positional_params)] + fn get_storage(prefix: StorageKey, hash: Option) -> StorageData; #[rpc(method = "state_getKeysPaged", positional_params)] fn get_keys_paged( prefix: Option, @@ -107,7 +109,7 @@ impl From for Transport { /// A state snapshot config may be present and will be written to in that case. #[derive(Clone)] pub struct OnlineConfig { - /// The block number at which to connect. Will be latest finalized head if not provided. + /// The block hash at which to get the runtime state. Will be latest finalized head if not provided. pub at: Option, /// An optional state snapshot file to WRITE to, not for reading. Not written if set to `None`. pub state_snapshot: Option, @@ -159,8 +161,11 @@ impl Default for SnapshotConfig { pub struct Builder { /// Custom key-pairs to be injected into the externalities. inject: Vec, - /// Storage entry key prefixes to be injected into the externalities. The *hashed* prefix must be given. + /// Storage entry key prefixes to be injected into the externalities. The *hashed* prefix must + /// be given. hashed_prefixes: Vec>, + /// Storage entry keys to be injected into the externalities. The *hashed* key must be given. + hashed_keys: Vec>, /// connectivity mode, online or offline. mode: Mode, } @@ -169,7 +174,12 @@ pub struct Builder { // that. impl Default for Builder { fn default() -> Self { - Self { inject: Default::default(), mode: Default::default(), hashed_prefixes: Default::default() } + Self { + inject: Default::default(), + mode: Default::default(), + hashed_prefixes: Default::default(), + hashed_keys: Default::default(), + } } } @@ -192,6 +202,17 @@ impl Builder { // RPC methods impl Builder { + async fn rpc_get_storage( + &self, + key: StorageKey, + maybe_at: Option, + ) -> Result { + trace!(target: LOG_TARGET, "rpc: get_storage"); + RpcApi::::get_storage(self.as_online().rpc_client(), key, maybe_at).await.map_err(|e| { + error!("Error = {:?}", e); + "rpc get_storage failed." + }) + } /// Get the latest finalized head. async fn rpc_get_head(&self) -> Result { trace!(target: LOG_TARGET, "rpc: finalized_head"); @@ -281,7 +302,7 @@ impl Builder { let values = client.batch_request::>(batch) .await .map_err(|e| { - log::error!(target: LOG_TARGET, "failed to execute batch {:?} due to {:?}", chunk_keys, e); + log::error!(target: LOG_TARGET, "failed to execute batch: {:?}. Error: {:?}", chunk_keys, e); "batch failed." })?; assert_eq!(chunk_keys.len(), values.len()); @@ -356,11 +377,23 @@ impl Builder { }; for prefix in &self.hashed_prefixes { - info!(target: LOG_TARGET, "adding data for hashed prefix: {:?}", HexDisplay::from(prefix)); - let additional_key_values = self.rpc_get_pairs_paged(StorageKey(prefix.to_vec()), at).await?; + debug!( + target: LOG_TARGET, + "adding data for hashed prefix: {:?}", + HexDisplay::from(prefix) + ); + let additional_key_values = + self.rpc_get_pairs_paged(StorageKey(prefix.to_vec()), at).await?; keys_and_values.extend(additional_key_values); } + for key in &self.hashed_keys { + let key = StorageKey(key.to_vec()); + debug!(target: LOG_TARGET, "adding data for hashed key: {:?}", HexDisplay::from(&key)); + let value = self.rpc_get_storage(key.clone(), Some(at)).await?; + keys_and_values.push((key, value)); + } + Ok(keys_and_values) } @@ -400,7 +433,7 @@ impl Builder { info!( target: LOG_TARGET, - "extending externalities with {} manually injected keys", + "extending externalities with {} manually injected key-values", self.inject.len() ); base_kv.extend(self.inject.clone()); @@ -416,19 +449,29 @@ impl Builder { } /// Inject a manual list of key and values to the storage. - pub fn inject(mut self, injections: &[KeyPair]) -> Self { + pub fn inject_key_value(mut self, injections: &[KeyPair]) -> Self { for i in injections { self.inject.push(i.clone()); } self } - /// Inject a hashed prefix. This is treated as-is, and should be pre-hashed. + /// Inject a hashed prefix. This is treated as-is, and should be pre-hashed. + /// + /// This should be used to inject a "PREFIX", like a storage (double) map. pub fn inject_hashed_prefix(mut self, hashed: &[u8]) -> Self { self.hashed_prefixes.push(hashed.to_vec()); self } + /// Inject a hashed key to scrape. This is treated as-is, and should be pre-hashed. + /// + /// This should be used to inject a "KEY", like a storage value. + pub fn inject_hashed_key(mut self, hashed: &[u8]) -> Self { + self.hashed_keys.push(hashed.to_vec()); + self + } + /// Configure a state snapshot to be used. pub fn mode(mut self, mode: Mode) -> Self { self.mode = mode; diff --git a/substrate/utils/frame/remote-externalities/src/rpc_api.rs b/substrate/utils/frame/remote-externalities/src/rpc_api.rs index e7fd021bac..6773bfd54b 100644 --- a/substrate/utils/frame/remote-externalities/src/rpc_api.rs +++ b/substrate/utils/frame/remote-externalities/src/rpc_api.rs @@ -18,36 +18,65 @@ //! WS RPC API for one off RPC calls to a substrate node. // TODO: Consolidate one off RPC calls https://github.com/paritytech/substrate/issues/8988 -use super::*; +use sp_runtime::{generic::SignedBlock, traits::{Block as BlockT, Header as HeaderT}}; +use jsonrpsee_ws_client::{WsClientBuilder, WsClient, v2::params::JsonRpcParams, traits::Client}; /// Get the header of the block identified by `at` -pub async fn get_header>(from: S, at: B::Hash) -> Result +pub async fn get_header(from: S, at: Block::Hash) -> Result where - B::Header: serde::de::DeserializeOwned, + Block: BlockT, + Block::Header: serde::de::DeserializeOwned, + S: AsRef, { - use jsonrpsee_ws_client::traits::Client; - let at = serde_json::to_value(at) - .map_err(|e| format!("Block hash could not be converted to JSON due to {:?}", e))?; - let params = vec![at]; - let client = WsClientBuilder::default() - .max_request_body_size(u32::MAX) - .build(from.as_ref()) + let params = vec![hash_to_json::(at)?]; + let client = build_client(from).await?; + + client.request::("chain_getHeader", JsonRpcParams::Array(params)) .await - .map_err(|e| format!("`WsClientBuilder` failed to build do to {:?}", e))?; - client.request::("chain_getHeader", JsonRpcParams::Array(params)) - .await - .map_err(|e| format!("chain_getHeader request failed due to {:?}", e)) + .map_err(|e| format!("chain_getHeader request failed: {:?}", e)) } /// Get the finalized head -pub async fn get_finalized_head>(from: S) -> Result { - use jsonrpsee_ws_client::traits::Client; - let client = WsClientBuilder::default() +pub async fn get_finalized_head(from: S) -> Result +where + Block: BlockT, + S: AsRef, +{ + let client = build_client(from).await?; + + client.request::("chain_getFinalizedHead", JsonRpcParams::NoParams) + .await + .map_err(|e| format!("chain_getFinalizedHead request failed: {:?}", e)) +} + +/// Get the signed block identified by `at`. +pub async fn get_block(from: S, at: Block::Hash) -> Result +where + S: AsRef, + Block: BlockT + serde::de::DeserializeOwned, + Block::Header: HeaderT, +{ + let params = vec![hash_to_json::(at)?]; + let client = build_client(from).await?; + let signed_block = client + .request::>("chain_getBlock", JsonRpcParams::Array(params)) + .await + .map_err(|e| format!("chain_getBlock request failed: {:?}", e))?; + + Ok(signed_block.block) +} + +/// Convert a block hash to a serde json value. +fn hash_to_json(hash: Block::Hash) -> Result { + serde_json::to_value(hash) + .map_err(|e| format!("Block hash could not be converted to JSON: {:?}", e)) +} + +/// Build a website client that connects to `from`. +async fn build_client>(from: S) -> Result { + WsClientBuilder::default() .max_request_body_size(u32::MAX) .build(from.as_ref()) .await - .map_err(|e| format!("`WsClientBuilder` failed to build do to {:?}", e))?; - client.request::("chain_getFinalizedHead", JsonRpcParams::NoParams) - .await - .map_err(|e| format!("chain_getFinalizedHead request failed due to {:?}", e)) + .map_err(|e| format!("`WsClientBuilder` failed to build: {:?}", e)) } diff --git a/substrate/utils/frame/try-runtime/cli/Cargo.toml b/substrate/utils/frame/try-runtime/cli/Cargo.toml index f262ba4812..2e2335bc5f 100644 --- a/substrate/utils/frame/try-runtime/cli/Cargo.toml +++ b/substrate/utils/frame/try-runtime/cli/Cargo.toml @@ -29,6 +29,7 @@ sp-blockchain = { version = "3.0.0", path = "../../../../primitives/blockchain" sp-runtime = { version = "3.0.0", path = "../../../../primitives/runtime" } sp-externalities = { version = "0.9.0", path = "../../../../primitives/externalities" } sp-core = { version = "3.0.0", path = "../../../../primitives/core" } +sp-io = { version = "3.0.0", path = "../../../../primitives/io" } sp-keystore = { version = "0.9.0", path = "../../../../primitives/keystore" } frame-try-runtime = { version = "0.9.0", path = "../../../../frame/try-runtime" } diff --git a/substrate/utils/frame/try-runtime/cli/src/lib.rs b/substrate/utils/frame/try-runtime/cli/src/lib.rs index dc4cb7cd33..e0d09ff7fb 100644 --- a/substrate/utils/frame/try-runtime/cli/src/lib.rs +++ b/substrate/utils/frame/try-runtime/cli/src/lib.rs @@ -25,13 +25,14 @@ use sc_executor::NativeExecutor; use sc_service::NativeExecutionDispatch; use sc_chain_spec::ChainSpec; use sp_state_machine::StateMachine; -use sp_runtime::traits::{Block as BlockT, NumberFor}; +use sp_runtime::traits::{Block as BlockT, NumberFor, Header as HeaderT}; use sp_core::{ offchain::{ OffchainWorkerExt, OffchainDbExt, TransactionPoolExt, - testing::{TestOffchainExt, TestTransactionPoolExt} + testing::{TestOffchainExt, TestTransactionPoolExt}, }, storage::{StorageData, StorageKey, well_known_keys}, + hashing::twox_128, }; use sp_keystore::{KeystoreExt, testing::KeyStore}; use remote_externalities::{Builder, Mode, SnapshotConfig, OfflineConfig, OnlineConfig, rpc_api}; @@ -45,6 +46,8 @@ pub enum Command { 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), } #[derive(Debug, Clone, structopt::StructOpt)] @@ -55,17 +58,14 @@ pub struct OnRuntimeUpgradeCmd { #[derive(Debug, Clone, structopt::StructOpt)] pub struct OffchainWorkerCmd { - /// Hash of the block whose header to use to execute the offchain worker. - #[structopt(short, long, multiple = false, parse(try_from_str = parse::hash))] - pub header_at: String, - #[structopt(subcommand)] pub state: State, +} - /// Whether or not to overwrite the code from state with the code from - /// the specified chain spec. - #[structopt(long)] - pub overwrite_code: bool, +#[derive(Debug, Clone, structopt::StructOpt)] +pub struct ExecuteBlockCmd { + #[structopt(subcommand)] + pub state: State, } #[derive(Debug, Clone, structopt::StructOpt)] @@ -99,6 +99,46 @@ 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, + /// 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")] + ) + )] + block_at: String, + + /// Whether or not to overwrite the code from state with the code from + /// the specified chain spec. + #[structopt(long)] + pub overwrite_code: bool, + + /// 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, +} + +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 + .parse::<::Hash>() + .map_err(|e| format!("Could not parse block hash: {:?}", e).into()) + } } /// Various commands to try out against runtime state at a specific block. @@ -114,11 +154,10 @@ pub struct TryRuntimeCmd { /// The source of runtime state to try operations against. #[derive(Debug, Clone, structopt::StructOpt)] pub enum State { - /// Use a state snapshot as the source of runtime state. NOTE: for the offchain-worker command this - /// is only partially supported at the moment and you must have a relevant archive node exposed on - /// localhost:9944 in order to query the block header. - // TODO https://github.com/paritytech/substrate/issues/9027 + /// 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. Snap { + #[structopt(short, long)] snapshot_path: PathBuf, }, @@ -128,25 +167,16 @@ pub enum State { #[structopt(short, long)] snapshot_path: Option, - /// The block hash at which to connect. - /// Will be latest finalized head if not provided. - #[structopt(short, long, multiple = false, parse(try_from_str = parse::hash))] - block_at: Option, - /// The modules to scrape. If empty, entire chain state will be scraped. #[structopt(short, long, require_delimiter = true)] modules: Option>, - - /// The url to connect to. - #[structopt(default_value = "ws://localhost:9944", parse(try_from_str = parse::url))] - url: String, - }, + } } async fn on_runtime_upgrade( shared: SharedParams, command: OnRuntimeUpgradeCmd, - config: Configuration + config: Configuration, ) -> sc_cli::Result<()> where Block: BlockT, @@ -158,11 +188,7 @@ where { let wasm_method = shared.wasm_method; let execution = shared.execution; - let heap_pages = if shared.heap_pages.is_some() { - shared.heap_pages - } else { - config.default_heap_pages - }; + let heap_pages = shared.heap_pages.or(config.default_heap_pages); let mut changes = Default::default(); let max_runtime_instances = config.max_runtime_instances; @@ -180,22 +206,22 @@ where })) }, State::Live { - url, snapshot_path, - block_at, modules } => Builder::::new().mode(Mode::Online(OnlineConfig { - transport: url.to_owned().into(), + transport: shared.url.to_owned().into(), state_snapshot: snapshot_path.as_ref().map(SnapshotConfig::new), modules: modules.to_owned().unwrap_or_default(), - at: block_at.as_ref() - .map(|b| b.parse().map_err(|e| format!("Could not parse hash: {:?}", e))).transpose()?, + at: Some(shared.block_at::()?), ..Default::default() })), }; let (code_key, code) = extract_code(config.chain_spec)?; - builder.inject(&[(code_key, code)]).build().await? + 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( @@ -211,10 +237,10 @@ where sp_core::testing::TaskExecutor::new(), ) .execute(execution.into()) - .map_err(|e| format!("failed to execute 'TryRuntime_on_runtime_upgrade' due to {:?}", e))?; + .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 due to {:?}", e))?; + .map_err(|e| format!("failed to decode output: {:?}", e))?; log::info!( "TryRuntime_on_runtime_upgrade executed without errors. Consumed weight = {}, total weight = {} ({})", weight, @@ -229,7 +255,7 @@ async fn offchain_worker( shared: SharedParams, command: OffchainWorkerCmd, config: Configuration, -)-> sc_cli::Result<()> +) -> sc_cli::Result<()> where Block: BlockT, Block::Hash: FromStr, @@ -241,11 +267,7 @@ where { let wasm_method = shared.wasm_method; let execution = shared.execution; - let heap_pages = if shared.heap_pages.is_some() { - shared.heap_pages - } else { - config.default_heap_pages - }; + let heap_pages = shared.heap_pages.or(config.default_heap_pages); let mut changes = Default::default(); let max_runtime_instances = config.max_runtime_instances; @@ -255,47 +277,43 @@ where max_runtime_instances, ); - let (mode, url) = match command.state { + let mode = match command.state { State::Live { - url, snapshot_path, - block_at, modules } => { + let at = shared.block_at::()?; let online_config = OnlineConfig { - transport: url.to_owned().into(), + transport: shared.url.to_owned().into(), state_snapshot: snapshot_path.as_ref().map(SnapshotConfig::new), modules: modules.to_owned().unwrap_or_default(), - at: block_at.as_ref() - .map(|b| b.parse().map_err(|e| format!("Could not parse hash: {:?}", e))).transpose()?, + at: Some(at), ..Default::default() }; - (Mode::Online(online_config), url) + Mode::Online(online_config) }, State::Snap { snapshot_path } => { - // TODO This is a temporary hack; the url is used just to get the header. We should try - // and get the header out of state, OR use an arbitrary header if thats ok, OR allow - // the user to feed in a header via file. - // https://github.com/paritytech/substrate/issues/9027 - // This assumes you have a node running on local host default - let url = "ws://127.0.0.1:9944".to_string(); let mode = Mode::Offline(OfflineConfig { state_snapshot: SnapshotConfig::new(snapshot_path), }); - (mode, url) + mode } }; - let builder = Builder::::new().mode(mode); - let mut ext = if command.overwrite_code { + 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 (code_key, code) = extract_code(config.chain_spec)?; - builder.inject(&[(code_key, code)]).build().await? + builder.inject_key_value(&[(code_key, code)]).build().await? } else { - builder.build().await? + 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())); @@ -303,10 +321,8 @@ where ext.register_extension(KeystoreExt(Arc::new(KeyStore::new()))); ext.register_extension(TransactionPoolExt::new(pool)); - let header_hash: Block::Hash = command.header_at - .parse() - .map_err(|e| format!("Could not parse header hash: {:?}", e))?; - let header = rpc_api::get_header::(url, header_hash).await?; + let header_hash = shared.block_at::()?; + let header = rpc_api::get_header::(shared.url, header_hash).await?; let _ = StateMachine::<_, _, NumberFor, _>::new( &ext.backend, @@ -321,17 +337,120 @@ where sp_core::testing::TaskExecutor::new(), ) .execute(execution.into()) - .map_err(|e| format!("failed to execute 'OffchainWorkerApi_offchain_worker' due to {:?}", e))?; + .map_err(|e| format!("failed to execute 'OffchainWorkerApi_offchain_worker': {:?}", e))?; log::info!("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 = NativeExecutor::::new( + wasm_method.into(), + heap_pages, + max_runtime_instances, + ); + + let block_hash = shared.block_at::()?; + let block: Block = rpc_api::get_block::(shared.url.clone(), block_hash).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.to_owned().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_code { + 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!("Core_execute_block executed without errors."); + + Ok(()) +} + impl TryRuntimeCmd { pub async fn run(&self, config: Configuration) -> sc_cli::Result<()> where - Block: BlockT, + Block: BlockT + serde::de::DeserializeOwned, Block::Header: serde::de::DeserializeOwned, Block::Hash: FromStr, ::Err: Debug, @@ -346,6 +465,9 @@ impl TryRuntimeCmd { Command::OffchainWorker(cmd) => { offchain_worker::(self.shared.clone(), cmd.clone(), config).await } + Command::ExecuteBlock(cmd) => { + execute_block::(self.shared.clone(), cmd.clone(), config).await + } } } } @@ -363,7 +485,7 @@ impl CliConfiguration for TryRuntimeCmd { } } -/// Extract `:code` from the given chain spec and return as `StorageData` along with the +/// 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)> { let genesis_storage = spec.build_storage()?;