mirror of
https://github.com/pezkuwichain/pezkuwi-subxt.git
synced 2026-06-14 08:41:07 +00:00
74910c4806
* 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>
268 lines
8.4 KiB
Rust
268 lines
8.4 KiB
Rust
// 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);
|
|
});
|
|
}
|
|
}
|