mirror of
https://github.com/pezkuwichain/pezkuwi-subxt.git
synced 2026-06-12 04:11:07 +00:00
WASM Local-blob override (#7317)
* Provide WASM overwrite functionality in LocalCallExecutor - add a new module `wasm_overwrite.rs` in client - scrapes given folder for runtimes - add two new CLI Options `wasm-overwrite` and `wasm_overwrite_path` * formatting * Make comment clearer remove sc-runtime-test from dev-dependencies * comments * Update client/service/src/client/wasm_overwrite.rs Co-authored-by: Bastian Köcher <bkchr@users.noreply.github.com> * Update client/service/src/client/wasm_overwrite.rs Co-authored-by: Bastian Köcher <bkchr@users.noreply.github.com> * Fix spaces, remove call into backend for 'heap_pages' in 'try_replace' * Error if path is not a directory, Comments, Doc Comment for WasmOverwrite * make WasmOverwrite Option<> * Change to one CLI argument for overwrites - move getting runtime version into LocalCallExecutor * change unwrap() to expect() * comment * Remove `check_overwrites` * Encapsulate checking for overwrites in LocalCallExecutor * move duplicate code into function * Update client/cli/src/params/import_params.rs Co-authored-by: Bastian Köcher <bkchr@users.noreply.github.com> * comma * Update client/service/src/client/wasm_overwrite.rs Co-authored-by: Bastian Köcher <bkchr@users.noreply.github.com> * Update client/service/src/client/wasm_overwrite.rs Co-authored-by: Bastian Köcher <bkchr@users.noreply.github.com> * cache hash in WasmBlob * Update client/service/src/client/wasm_overwrite.rs Co-authored-by: Bastian Köcher <bkchr@users.noreply.github.com> * Update client/service/src/client/client.rs Co-authored-by: Bastian Köcher <bkchr@users.noreply.github.com> * move getting overwrite into its own function * fix error when directory is not a directory * Error on duplicate WASM runtimes * better comment, grammar * docs * Revert StateBackend back to _ * Update client/service/src/client/wasm_overwrite.rs Co-authored-by: Bastian Köcher <bkchr@users.noreply.github.com> * Update client/service/src/client/wasm_overwrite.rs Co-authored-by: Bastian Köcher <bkchr@users.noreply.github.com> * Update client/service/src/client/call_executor.rs Co-authored-by: Bastian Köcher <bkchr@users.noreply.github.com> * Add two tests, fix doc comments Add a test for the runtime_version method of WasmOverwrite Add a test for check_overwrite method of LocalCallExecutor * remove redundant `Return` from expect msg * Update client/cli/src/params/import_params.rs Co-authored-by: David <dvdplm@gmail.com> * Update client/service/src/client/call_executor.rs Co-authored-by: David <dvdplm@gmail.com> * Update client/service/src/client/wasm_overwrite.rs Co-authored-by: David <dvdplm@gmail.com> * Update client/service/src/config.rs Co-authored-by: David <dvdplm@gmail.com> * Update client/service/src/client/wasm_overwrite.rs Co-authored-by: David <dvdplm@gmail.com> * Add Module Documentation, match on '.wasm' extension * Add test for scraping WASM blob * fix expect * remove creating another block in LocalCallExecutor test * remove unused import * add tests for duplicates and scraping wasm * make tests a bit nicer * add test for ignoring non-.wasm files * check error message in test * Update client/service/src/client/wasm_overwrite.rs Co-authored-by: Bastian Köcher <bkchr@users.noreply.github.com> * remove println * Update client/service/src/client/wasm_overwrite.rs Co-authored-by: Bastian Köcher <bkchr@users.noreply.github.com> * make tests prettier * Update client/service/src/client/wasm_overwrite.rs Co-authored-by: Bastian Köcher <bkchr@users.noreply.github.com> * comment for seemingly random client * locally-built -> custom * remove unused import * fix comment * rename all references to overwrite with override * fix cli flag in module documentation Co-authored-by: Bastian Köcher <bkchr@users.noreply.github.com> Co-authored-by: David <dvdplm@gmail.com>
This commit is contained in:
Generated
+1
@@ -7321,6 +7321,7 @@ dependencies = [
|
||||
"sp-utils",
|
||||
"sp-version",
|
||||
"substrate-prometheus-endpoint",
|
||||
"substrate-test-runtime",
|
||||
"substrate-test-runtime-client",
|
||||
"tempfile",
|
||||
"tokio 0.2.22",
|
||||
|
||||
@@ -278,6 +278,15 @@ pub trait CliConfiguration<DCV: DefaultConfigurationValues = ()>: Sized {
|
||||
.unwrap_or_default())
|
||||
}
|
||||
|
||||
/// Get the path where WASM overrides live.
|
||||
///
|
||||
/// By default this is `None`.
|
||||
fn wasm_runtime_overrides(&self) -> Option<PathBuf> {
|
||||
self.import_params()
|
||||
.map(|x| x.wasm_runtime_overrides())
|
||||
.unwrap_or_default()
|
||||
}
|
||||
|
||||
/// Get the execution strategies.
|
||||
///
|
||||
/// By default this is retrieved from `ImportParams` if it is available. Otherwise its
|
||||
@@ -492,6 +501,7 @@ pub trait CliConfiguration<DCV: DefaultConfigurationValues = ()>: Sized {
|
||||
state_cache_child_ratio: self.state_cache_child_ratio()?,
|
||||
pruning: self.pruning(unsafe_pruning, &role)?,
|
||||
wasm_method: self.wasm_method()?,
|
||||
wasm_runtime_overrides: self.wasm_runtime_overrides(),
|
||||
execution_strategies: self.execution_strategies(is_dev, is_validator)?,
|
||||
rpc_http: self.rpc_http(DCV::rpc_http_listen_port())?,
|
||||
rpc_ws: self.rpc_ws(DCV::rpc_ws_listen_port())?,
|
||||
|
||||
@@ -25,6 +25,7 @@ use crate::params::DatabaseParams;
|
||||
use crate::params::PruningParams;
|
||||
use sc_client_api::execution_extensions::ExecutionStrategies;
|
||||
use structopt::StructOpt;
|
||||
use std::path::PathBuf;
|
||||
|
||||
/// Parameters for block import.
|
||||
#[derive(Debug, StructOpt)]
|
||||
@@ -55,6 +56,12 @@ pub struct ImportParams {
|
||||
)]
|
||||
pub wasm_method: WasmExecutionMethod,
|
||||
|
||||
/// Specify the path where local WASM runtimes are stored.
|
||||
///
|
||||
/// These runtimes will override on-chain runtimes when the version matches.
|
||||
#[structopt(long, value_name = "PATH", parse(from_os_str))]
|
||||
pub wasm_runtime_overrides: Option<PathBuf>,
|
||||
|
||||
#[allow(missing_docs)]
|
||||
#[structopt(flatten)]
|
||||
pub execution_strategies: ExecutionStrategiesParams,
|
||||
@@ -103,6 +110,12 @@ impl ImportParams {
|
||||
self.wasm_method.into()
|
||||
}
|
||||
|
||||
/// Enable overriding on-chain WASM with locally-stored WASM
|
||||
/// by specifying the path where local WASM is stored.
|
||||
pub fn wasm_runtime_overrides(&self) -> Option<PathBuf> {
|
||||
self.wasm_runtime_overrides.clone()
|
||||
}
|
||||
|
||||
/// Get execution strategies for the parameters
|
||||
pub fn execution_strategies(&self, is_dev: bool, is_validator: bool) -> ExecutionStrategies {
|
||||
let exec = &self.execution_strategies;
|
||||
|
||||
@@ -86,6 +86,7 @@ directories = "2.0.2"
|
||||
|
||||
[dev-dependencies]
|
||||
substrate-test-runtime-client = { version = "2.0.0", path = "../../test-utils/runtime/client" }
|
||||
substrate-test-runtime = { versino = "2.0.0", path = "../../test-utils/runtime/" }
|
||||
sp-consensus-babe = { version = "0.8.0", path = "../../primitives/consensus/babe" }
|
||||
grandpa = { version = "0.8.0", package = "sc-finality-grandpa", path = "../finality-grandpa" }
|
||||
grandpa-primitives = { version = "2.0.0", package = "sp-finality-grandpa", path = "../../primitives/finality-grandpa" }
|
||||
|
||||
@@ -303,8 +303,9 @@ pub fn new_full_parts<TBl, TRtApi, TExecDisp>(
|
||||
Box::new(task_manager.spawn_handle()),
|
||||
config.prometheus_config.as_ref().map(|config| config.registry.clone()),
|
||||
ClientConfig {
|
||||
offchain_worker_enabled : config.offchain_worker.enabled ,
|
||||
offchain_worker_enabled : config.offchain_worker.enabled,
|
||||
offchain_indexing_api: config.offchain_worker.indexing_enabled,
|
||||
wasm_runtime_overrides: config.wasm_runtime_overrides.clone(),
|
||||
},
|
||||
)?
|
||||
};
|
||||
@@ -396,7 +397,7 @@ pub fn new_client<E, Block, RA>(
|
||||
const CANONICALIZATION_DELAY: u64 = 4096;
|
||||
|
||||
let backend = Arc::new(Backend::new(settings, CANONICALIZATION_DELAY)?);
|
||||
let executor = crate::client::LocalCallExecutor::new(backend.clone(), executor, spawn_handle, config.clone());
|
||||
let executor = crate::client::LocalCallExecutor::new(backend.clone(), executor, spawn_handle, config.clone())?;
|
||||
Ok((
|
||||
crate::client::Client::new(
|
||||
backend.clone(),
|
||||
|
||||
@@ -28,36 +28,71 @@ use sp_state_machine::{
|
||||
use sc_executor::{RuntimeVersion, RuntimeInfo, NativeVersion};
|
||||
use sp_externalities::Extensions;
|
||||
use sp_core::{
|
||||
NativeOrEncoded, NeverNativeValue, traits::{CodeExecutor, SpawnNamed},
|
||||
NativeOrEncoded, NeverNativeValue, traits::{CodeExecutor, SpawnNamed, RuntimeCode},
|
||||
offchain::storage::OffchainOverlayedChanges,
|
||||
};
|
||||
use sp_api::{ProofRecorder, InitializeBlock, StorageTransactionCache};
|
||||
use sc_client_api::{backend, call_executor::CallExecutor};
|
||||
use super::client::ClientConfig;
|
||||
use super::{client::ClientConfig, wasm_override::WasmOverride};
|
||||
|
||||
/// Call executor that executes methods locally, querying all required
|
||||
/// data from local backend.
|
||||
pub struct LocalCallExecutor<B, E> {
|
||||
backend: Arc<B>,
|
||||
executor: E,
|
||||
wasm_override: Option<WasmOverride<E>>,
|
||||
spawn_handle: Box<dyn SpawnNamed>,
|
||||
client_config: ClientConfig,
|
||||
}
|
||||
|
||||
impl<B, E> LocalCallExecutor<B, E> {
|
||||
impl<B, E> LocalCallExecutor<B, E>
|
||||
where
|
||||
E: CodeExecutor + RuntimeInfo + Clone + 'static
|
||||
{
|
||||
/// Creates new instance of local call executor.
|
||||
pub fn new(
|
||||
backend: Arc<B>,
|
||||
executor: E,
|
||||
spawn_handle: Box<dyn SpawnNamed>,
|
||||
client_config: ClientConfig,
|
||||
) -> Self {
|
||||
LocalCallExecutor {
|
||||
) -> sp_blockchain::Result<Self> {
|
||||
let wasm_override = client_config.wasm_runtime_overrides
|
||||
.as_ref()
|
||||
.map(|p| WasmOverride::new(p.clone(), executor.clone()))
|
||||
.transpose()?;
|
||||
|
||||
Ok(LocalCallExecutor {
|
||||
backend,
|
||||
executor,
|
||||
wasm_override,
|
||||
spawn_handle,
|
||||
client_config,
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
/// Check if local runtime code overrides are enabled and one is available
|
||||
/// for the given `BlockId`. If yes, return it; otherwise return the same
|
||||
/// `RuntimeCode` instance that was passed.
|
||||
fn check_override<'a, Block>(
|
||||
&'a self,
|
||||
onchain_code: RuntimeCode<'a>,
|
||||
id: &BlockId<Block>,
|
||||
) -> sp_blockchain::Result<RuntimeCode<'a>>
|
||||
where
|
||||
Block: BlockT,
|
||||
B: backend::Backend<Block>,
|
||||
{
|
||||
let code = self.wasm_override
|
||||
.as_ref()
|
||||
.map::<sp_blockchain::Result<Option<RuntimeCode>>, _>(|o| {
|
||||
let spec = self.runtime_version(id)?.spec_version;
|
||||
Ok(o.get(&spec, onchain_code.heap_pages))
|
||||
})
|
||||
.transpose()?
|
||||
.flatten()
|
||||
.unwrap_or(onchain_code);
|
||||
|
||||
Ok(code)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -66,6 +101,7 @@ impl<B, E> Clone for LocalCallExecutor<B, E> where E: Clone {
|
||||
LocalCallExecutor {
|
||||
backend: self.backend.clone(),
|
||||
executor: self.executor.clone(),
|
||||
wasm_override: self.wasm_override.clone(),
|
||||
spawn_handle: self.spawn_handle.clone(),
|
||||
client_config: self.client_config.clone(),
|
||||
}
|
||||
@@ -101,6 +137,8 @@ where
|
||||
)?;
|
||||
let state = self.backend.state_at(*id)?;
|
||||
let state_runtime_code = sp_state_machine::backend::BackendRuntimeCode::new(&state);
|
||||
let runtime_code = self.check_override(state_runtime_code.runtime_code()?, id)?;
|
||||
|
||||
let return_data = StateMachine::new(
|
||||
&state,
|
||||
changes_trie,
|
||||
@@ -110,7 +148,7 @@ where
|
||||
method,
|
||||
call_data,
|
||||
extensions.unwrap_or_default(),
|
||||
&state_runtime_code.runtime_code()?,
|
||||
&runtime_code,
|
||||
self.spawn_handle.clone(),
|
||||
).execute_using_consensus_failure_handler::<_, NeverNativeValue, fn() -> _>(
|
||||
strategy.get_manager(),
|
||||
@@ -173,7 +211,7 @@ where
|
||||
let state_runtime_code = sp_state_machine::backend::BackendRuntimeCode::new(&trie_state);
|
||||
// It is important to extract the runtime code here before we create the proof
|
||||
// recorder.
|
||||
let runtime_code = state_runtime_code.runtime_code()?;
|
||||
let runtime_code = self.check_override(state_runtime_code.runtime_code()?, at)?;
|
||||
|
||||
let backend = sp_state_machine::ProvingBackend::new_with_recorder(
|
||||
trie_state,
|
||||
@@ -198,7 +236,8 @@ where
|
||||
},
|
||||
None => {
|
||||
let state_runtime_code = sp_state_machine::backend::BackendRuntimeCode::new(&state);
|
||||
let runtime_code = state_runtime_code.runtime_code()?;
|
||||
let runtime_code = self.check_override(state_runtime_code.runtime_code()?, at)?;
|
||||
|
||||
let mut state_machine = StateMachine::new(
|
||||
&state,
|
||||
changes_trie_state,
|
||||
@@ -279,3 +318,66 @@ impl<B, E, Block> sp_version::GetRuntimeVersion<Block> for LocalCallExecutor<B,
|
||||
CallExecutor::runtime_version(self, at).map_err(|e| format!("{:?}", e))
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use substrate_test_runtime_client::{LocalExecutor, GenesisInit, runtime};
|
||||
use sc_executor::{NativeExecutor, WasmExecutionMethod};
|
||||
use sp_core::{traits::{WrappedRuntimeCode, FetchRuntimeCode}, testing::TaskExecutor};
|
||||
use sc_client_api::in_mem;
|
||||
|
||||
#[test]
|
||||
fn should_get_override_if_exists() {
|
||||
let executor =
|
||||
NativeExecutor::<LocalExecutor>::new(WasmExecutionMethod::Interpreted, Some(128), 1);
|
||||
|
||||
let overrides = crate::client::wasm_override::dummy_overrides(&executor);
|
||||
let onchain_code = WrappedRuntimeCode(substrate_test_runtime::wasm_binary_unwrap().into());
|
||||
let onchain_code = RuntimeCode {
|
||||
code_fetcher: &onchain_code,
|
||||
heap_pages: Some(128),
|
||||
hash: vec![0, 0, 0, 0],
|
||||
};
|
||||
|
||||
let backend = Arc::new(in_mem::Backend::<runtime::Block>::new());
|
||||
|
||||
// wasm_runtime_overrides is `None` here because we construct the
|
||||
// LocalCallExecutor directly later on
|
||||
let client_config = ClientConfig {
|
||||
offchain_worker_enabled: false,
|
||||
offchain_indexing_api: false,
|
||||
wasm_runtime_overrides: None,
|
||||
};
|
||||
|
||||
// client is used for the convenience of creating and inserting the genesis block.
|
||||
let _client = substrate_test_runtime_client::client::new_with_backend::<
|
||||
_,
|
||||
_,
|
||||
runtime::Block,
|
||||
_,
|
||||
runtime::RuntimeApi,
|
||||
>(
|
||||
backend.clone(),
|
||||
executor.clone(),
|
||||
&substrate_test_runtime_client::GenesisParameters::default().genesis_storage(),
|
||||
None,
|
||||
Box::new(TaskExecutor::new()),
|
||||
None,
|
||||
Default::default(),
|
||||
).expect("Creates a client");
|
||||
|
||||
let call_executor = LocalCallExecutor {
|
||||
backend: backend.clone(),
|
||||
executor,
|
||||
wasm_override: Some(overrides),
|
||||
spawn_handle: Box::new(TaskExecutor::new()),
|
||||
client_config,
|
||||
};
|
||||
|
||||
let check = call_executor.check_override(onchain_code, &BlockId::Number(Default::default()))
|
||||
.expect("RuntimeCode override");
|
||||
|
||||
assert_eq!(Some(vec![2, 2, 2, 2, 2, 2, 2, 2]), check.fetch_runtime_code().map(Into::into));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -22,6 +22,7 @@ use std::{
|
||||
marker::PhantomData,
|
||||
collections::{HashSet, BTreeMap, HashMap},
|
||||
sync::Arc, panic::UnwindSafe, result,
|
||||
path::PathBuf
|
||||
};
|
||||
use log::{info, trace, warn};
|
||||
use parking_lot::{Mutex, RwLock};
|
||||
@@ -181,6 +182,8 @@ pub struct ClientConfig {
|
||||
pub offchain_worker_enabled: bool,
|
||||
/// If true, allows access from the runtime to write into offchain worker db.
|
||||
pub offchain_indexing_api: bool,
|
||||
/// Path where WASM files exist to override the on-chain WASM.
|
||||
pub wasm_runtime_overrides: Option<PathBuf>,
|
||||
}
|
||||
|
||||
/// Create a client with the explicitly provided backend.
|
||||
@@ -201,7 +204,7 @@ pub fn new_with_backend<B, E, Block, S, RA>(
|
||||
Block: BlockT,
|
||||
B: backend::LocalBackend<Block> + 'static,
|
||||
{
|
||||
let call_executor = LocalCallExecutor::new(backend.clone(), executor, spawn_handle, config.clone());
|
||||
let call_executor = LocalCallExecutor::new(backend.clone(), executor, spawn_handle, config.clone())?;
|
||||
let extensions = ExecutionExtensions::new(Default::default(), keystore);
|
||||
Client::new(
|
||||
backend,
|
||||
|
||||
@@ -60,7 +60,7 @@ pub fn new_light<B, S, RA, E>(
|
||||
code_executor,
|
||||
spawn_handle.clone(),
|
||||
ClientConfig::default()
|
||||
);
|
||||
)?;
|
||||
let executor = GenesisCallExecutor::new(backend.clone(), local_executor);
|
||||
Client::new(
|
||||
backend,
|
||||
|
||||
@@ -49,6 +49,7 @@ pub mod light;
|
||||
mod call_executor;
|
||||
mod client;
|
||||
mod block_rules;
|
||||
mod wasm_override;
|
||||
|
||||
pub use self::{
|
||||
call_executor::LocalCallExecutor,
|
||||
|
||||
@@ -0,0 +1,267 @@
|
||||
// This file is part of Substrate.
|
||||
|
||||
// Copyright (C) 2020 Parity Technologies (UK) Ltd.
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0
|
||||
|
||||
// This program is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
//! # WASM Local Blob-Override
|
||||
//!
|
||||
//! WASM Local blob override provides tools to replace on-chain WASM with custom WASM.
|
||||
//! These customized WASM blobs may include functionality that is not included in the
|
||||
//! on-chain WASM, such as tracing or debugging information. This extra information is especially
|
||||
//! useful in external scenarios, like exchanges or archive nodes.
|
||||
//!
|
||||
//! ## Usage
|
||||
//!
|
||||
//! WASM overrides may be enabled with the `--wasm-runtime-overrides` argument. The argument
|
||||
//! expects a path to a directory that holds custom WASM.
|
||||
//!
|
||||
//! Any file ending in '.wasm' will be scraped and instantiated as a WASM blob. WASM can be built by
|
||||
//! compiling the required runtime with the changes needed. For example, compiling a runtime with
|
||||
//! tracing enabled would produce a WASM blob that can used.
|
||||
//!
|
||||
//! A custom WASM blob will override on-chain WASM if the spec version matches. If it is
|
||||
//! required to overrides multiple runtimes, multiple WASM blobs matching each of the spec versions
|
||||
//! needed must be provided in the given directory.
|
||||
//!
|
||||
use std::{
|
||||
fs, collections::{HashMap, hash_map::DefaultHasher}, path::Path,
|
||||
hash::Hasher as _,
|
||||
};
|
||||
use sp_core::traits::FetchRuntimeCode;
|
||||
use sp_state_machine::BasicExternalities;
|
||||
use sp_blockchain::Result;
|
||||
use sc_executor::RuntimeInfo;
|
||||
use sp_version::RuntimeVersion;
|
||||
use sp_core::traits::RuntimeCode;
|
||||
|
||||
#[derive(Clone, Debug, PartialEq)]
|
||||
/// Auxiliary structure that holds a wasm blob and its hash.
|
||||
struct WasmBlob {
|
||||
code: Vec<u8>,
|
||||
hash: Vec<u8>,
|
||||
}
|
||||
|
||||
impl WasmBlob {
|
||||
fn new(code: Vec<u8>) -> Self {
|
||||
let hash = make_hash(&code);
|
||||
Self { code, hash }
|
||||
}
|
||||
|
||||
fn runtime_code(&self, heap_pages: Option<u64>) -> RuntimeCode {
|
||||
RuntimeCode {
|
||||
code_fetcher: self,
|
||||
hash: self.hash.clone(),
|
||||
heap_pages,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Make a hash out of a byte string using the default rust hasher
|
||||
fn make_hash<K: std::hash::Hash + ?Sized>(val: &K) -> Vec<u8> {
|
||||
let mut state = DefaultHasher::new();
|
||||
val.hash(&mut state);
|
||||
state.finish().to_le_bytes().to_vec()
|
||||
}
|
||||
|
||||
impl FetchRuntimeCode for WasmBlob {
|
||||
fn fetch_runtime_code<'a>(&'a self) -> Option<std::borrow::Cow<'a, [u8]>> {
|
||||
Some(self.code.as_slice().into())
|
||||
}
|
||||
}
|
||||
|
||||
/// Scrapes WASM from a folder and returns WASM from that folder
|
||||
/// if the runtime spec version matches.
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct WasmOverride<E> {
|
||||
// Map of runtime spec version -> Wasm Blob
|
||||
overrides: HashMap<u32, WasmBlob>,
|
||||
executor: E,
|
||||
}
|
||||
|
||||
impl<E> WasmOverride<E>
|
||||
where
|
||||
E: RuntimeInfo + Clone + 'static
|
||||
{
|
||||
pub fn new<P>(path: P, executor: E) -> Result<Self>
|
||||
where
|
||||
P: AsRef<Path>,
|
||||
{
|
||||
let overrides = Self::scrape_overrides(path.as_ref(), &executor)?;
|
||||
Ok(Self { overrides, executor })
|
||||
}
|
||||
|
||||
/// Gets an override by it's runtime spec version.
|
||||
///
|
||||
/// Returns `None` if an override for a spec version does not exist.
|
||||
pub fn get<'a, 'b: 'a>(
|
||||
&'b self,
|
||||
spec: &u32,
|
||||
pages: Option<u64>,
|
||||
) -> Option<RuntimeCode<'a>> {
|
||||
self.overrides
|
||||
.get(spec)
|
||||
.map(|w| w.runtime_code(pages))
|
||||
}
|
||||
|
||||
/// Scrapes a folder for WASM runtimes.
|
||||
/// Returns a hashmap of the runtime version and wasm runtime code.
|
||||
fn scrape_overrides(dir: &Path, executor: &E) -> Result<HashMap<u32, WasmBlob>> {
|
||||
let handle_err = |e: std::io::Error | -> sp_blockchain::Error {
|
||||
sp_blockchain::Error::Msg(format!("{}", e.to_string()))
|
||||
};
|
||||
|
||||
if !dir.is_dir() {
|
||||
return Err(sp_blockchain::Error::Msg(format!(
|
||||
"Overwriting WASM requires a directory where \
|
||||
local WASM is stored. {:?} is not a directory",
|
||||
dir,
|
||||
)));
|
||||
}
|
||||
|
||||
let mut overrides = HashMap::new();
|
||||
let mut duplicates = Vec::new();
|
||||
for entry in fs::read_dir(dir).map_err(handle_err)? {
|
||||
let entry = entry.map_err(handle_err)?;
|
||||
let path = entry.path();
|
||||
match path.extension().map(|e| e.to_str()).flatten() {
|
||||
Some("wasm") => {
|
||||
let wasm = WasmBlob::new(fs::read(&path).map_err(handle_err)?);
|
||||
let version = Self::runtime_version(executor, &wasm, Some(128))?;
|
||||
if let Some(_duplicate) = overrides.insert(version.spec_version, wasm) {
|
||||
duplicates.push(format!("{}", path.display()));
|
||||
}
|
||||
}
|
||||
_ => ()
|
||||
}
|
||||
}
|
||||
|
||||
if !duplicates.is_empty() {
|
||||
let duplicate_file_list = duplicates.join("\n");
|
||||
let msg = format!("Duplicate WASM Runtimes found: \n{}\n", duplicate_file_list);
|
||||
return Err(sp_blockchain::Error::Msg(msg));
|
||||
}
|
||||
|
||||
Ok(overrides)
|
||||
}
|
||||
|
||||
fn runtime_version(
|
||||
executor: &E,
|
||||
code: &WasmBlob,
|
||||
heap_pages: Option<u64>,
|
||||
) -> Result<RuntimeVersion> {
|
||||
let mut ext = BasicExternalities::default();
|
||||
executor.runtime_version(&mut ext, &code.runtime_code(heap_pages))
|
||||
.map_err(|e| sp_blockchain::Error::VersionInvalid(format!("{:?}", e)).into())
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns a WasmOverride struct filled with dummy data for testing.
|
||||
#[cfg(test)]
|
||||
pub fn dummy_overrides<E>(executor: &E) -> WasmOverride<E>
|
||||
where
|
||||
E: RuntimeInfo + Clone + 'static
|
||||
{
|
||||
let mut overrides = HashMap::new();
|
||||
overrides.insert(0, WasmBlob::new(vec![0, 0, 0, 0, 0, 0, 0, 0]));
|
||||
overrides.insert(1, WasmBlob::new(vec![1, 1, 1, 1, 1, 1, 1, 1]));
|
||||
overrides.insert(2, WasmBlob::new(vec![2, 2, 2, 2, 2, 2, 2, 2]));
|
||||
WasmOverride {
|
||||
overrides,
|
||||
executor: executor.clone()
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use sc_executor::{NativeExecutor, WasmExecutionMethod};
|
||||
use substrate_test_runtime_client::LocalExecutor;
|
||||
use std::fs::{self, File};
|
||||
|
||||
fn wasm_test<F>(fun: F)
|
||||
where
|
||||
F: Fn(&Path, &[u8], &NativeExecutor::<LocalExecutor>)
|
||||
{
|
||||
let exec = NativeExecutor::<substrate_test_runtime_client::LocalExecutor>::new(
|
||||
WasmExecutionMethod::Interpreted,
|
||||
Some(128),
|
||||
1,
|
||||
);
|
||||
let bytes = substrate_test_runtime::wasm_binary_unwrap();
|
||||
let dir = tempfile::tempdir().expect("Create a temporary directory");
|
||||
fun(dir.path(), bytes, &exec);
|
||||
dir.close().expect("Temporary Directory should close");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn should_get_runtime_version() {
|
||||
let wasm = WasmBlob::new(substrate_test_runtime::wasm_binary_unwrap().to_vec());
|
||||
let executor =
|
||||
NativeExecutor::<LocalExecutor>::new(WasmExecutionMethod::Interpreted, Some(128), 1);
|
||||
|
||||
let version = WasmOverride::runtime_version(&executor, &wasm, Some(128))
|
||||
.expect("should get the `RuntimeVersion` of the test-runtime wasm blob");
|
||||
assert_eq!(version.spec_version, 2);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn should_scrape_wasm() {
|
||||
wasm_test(|dir, wasm_bytes, exec| {
|
||||
fs::write(dir.join("test.wasm"), wasm_bytes).expect("Create test file");
|
||||
let overrides = WasmOverride::scrape_overrides(dir, exec)
|
||||
.expect("HashMap of u32 and WasmBlob");
|
||||
let wasm = overrides.get(&2).expect("WASM binary");
|
||||
assert_eq!(wasm.code, substrate_test_runtime::wasm_binary_unwrap().to_vec())
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn should_check_for_duplicates() {
|
||||
wasm_test(|dir, wasm_bytes, exec| {
|
||||
fs::write(dir.join("test0.wasm"), wasm_bytes).expect("Create test file");
|
||||
fs::write(dir.join("test1.wasm"), wasm_bytes).expect("Create test file");
|
||||
let scraped = WasmOverride::scrape_overrides(dir, exec);
|
||||
|
||||
match scraped {
|
||||
Err(e) => {
|
||||
match e {
|
||||
sp_blockchain::Error::Msg(msg) => {
|
||||
let is_match = msg
|
||||
.matches("Duplicate WASM Runtimes found")
|
||||
.map(ToString::to_string)
|
||||
.collect::<Vec<String>>();
|
||||
assert!(is_match.len() >= 1)
|
||||
},
|
||||
_ => panic!("Test should end with Msg Error Variant")
|
||||
}
|
||||
},
|
||||
_ => panic!("Test should end in error")
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn should_ignore_non_wasm() {
|
||||
wasm_test(|dir, wasm_bytes, exec| {
|
||||
File::create(dir.join("README.md")).expect("Create test file");
|
||||
File::create(dir.join("LICENSE")).expect("Create a test file");
|
||||
fs::write(dir.join("test0.wasm"), wasm_bytes).expect("Create test file");
|
||||
let scraped = WasmOverride::scrape_overrides(dir, exec)
|
||||
.expect("HashMap of u32 and WasmBlob");
|
||||
assert_eq!(scraped.len(), 1);
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -62,6 +62,10 @@ pub struct Configuration {
|
||||
pub chain_spec: Box<dyn ChainSpec>,
|
||||
/// Wasm execution method.
|
||||
pub wasm_method: WasmExecutionMethod,
|
||||
/// Directory where local WASM runtimes live. These runtimes take precedence
|
||||
/// over on-chain runtimes when the spec version matches. Set to `None` to
|
||||
/// disable overrides (default).
|
||||
pub wasm_runtime_overrides: Option<PathBuf>,
|
||||
/// Execution strategies.
|
||||
pub execution_strategies: ExecutionStrategies,
|
||||
/// RPC over HTTP binding address. `None` if disabled.
|
||||
|
||||
@@ -252,6 +252,7 @@ fn node_config<G: RuntimeGenesis + 'static, E: ChainSpecExtension + Clone + 'sta
|
||||
pruning: Default::default(),
|
||||
chain_spec: Box::new((*spec).clone()),
|
||||
wasm_method: sc_service::config::WasmExecutionMethod::Interpreted,
|
||||
wasm_runtime_overrides: Default::default(),
|
||||
execution_strategies: Default::default(),
|
||||
rpc_http: None,
|
||||
rpc_ipc: None,
|
||||
|
||||
@@ -259,7 +259,7 @@ impl<Block: BlockT, E, Backend, G: GenesisInit> TestClientBuilder<
|
||||
executor,
|
||||
Box::new(sp_core::testing::TaskExecutor::new()),
|
||||
Default::default(),
|
||||
);
|
||||
).expect("Creates LocalCallExecutor");
|
||||
|
||||
self.build_with_executor(executor)
|
||||
}
|
||||
|
||||
@@ -355,7 +355,7 @@ pub fn new_light() -> (
|
||||
executor,
|
||||
Box::new(sp_core::testing::TaskExecutor::new()),
|
||||
Default::default(),
|
||||
);
|
||||
).expect("Creates LocalCallExecutor");
|
||||
let call_executor = LightExecutor::new(
|
||||
backend.clone(),
|
||||
local_call_executor,
|
||||
|
||||
@@ -98,6 +98,7 @@ where
|
||||
tracing_targets: Default::default(),
|
||||
transaction_pool: Default::default(),
|
||||
wasm_method: Default::default(),
|
||||
wasm_runtime_overrides: Default::default(),
|
||||
max_runtime_instances: 8,
|
||||
announce_block: true,
|
||||
base_path: None,
|
||||
|
||||
Reference in New Issue
Block a user