Files
pezkuwi-subxt/substrate/client/executor/src/wasm_runtime.rs
T
Sergei Pepyakin 49af986ad4 executor: Migrate wasmtime backend to a high-level API (#4686)
* Migrate wasmtime backend to wasmtime-api

* Port to a newer version of wasmtime

* Update to the latest changes.

* Rejig the sandbox module a bit

* Materialze

* Fixes.

* executor wasm_runtime fix

* Refactor everything

* More refactoring

* Even more refactorings

* More cleaning.

* Update to the latest wasmtime

* Reformat

* Renames

* Refactoring and comments.

* Docs

* Rename FunctionExecutor to host.

* Imrpove docs.

* fmt

* Remove panic

* Assert the number of arguments are equal between wasmtime and hostfunc.

* Comment a possible panic if there is no corresponding value variant.

* Check signature of the entrypoint.

* Use git version of wasmtime

* Refine and doc the sandbox code.

* Comment RefCells.

* Update wasmtime to the latest-ish master.

This may solve a problem with segfaults.

* Apply suggestions from code review

Co-Authored-By: Tomasz Drwięga <tomusdrw@users.noreply.github.com>

* Use full SHA1 hash of wasmtime commit.

* Add a panic message.

* Add some documentation

* Update wasmtime version to include SIGSEGV fix

* Update to crates.io version of wasmtime

* Make it work.

* Move the creation of memory into `InstanceWrapper::new`

* Make `InstanceWrapper` !Send & !Sync

* Avoid using `take_mut`

* Update client/executor/wasmtime/Cargo.toml

Co-Authored-By: Bastian Köcher <bkchr@users.noreply.github.com>

* Limit maximum size of memory.

* Rename `init_state` to `with_initialized_state`

Co-authored-by: Tomasz Drwięga <tomusdrw@users.noreply.github.com>
Co-authored-by: Bastian Köcher <bkchr@users.noreply.github.com>
2020-02-13 14:54:19 +01:00

257 lines
8.9 KiB
Rust

// Copyright 2019-2020 Parity Technologies (UK) Ltd.
// This file is part of Substrate.
// Substrate 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.
// Substrate 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 Substrate. If not, see <http://www.gnu.org/licenses/>.
//! Traits and accessor functions for calling into the Substrate Wasm runtime.
//!
//! The primary means of accessing the runtimes is through a cache which saves the reusable
//! components of the runtime that are expensive to initialize.
use crate::error::{Error, WasmError};
use log::{trace, warn};
use codec::Decode;
use sp_core::{storage::well_known_keys, traits::Externalities};
use sp_version::RuntimeVersion;
use std::{collections::hash_map::{Entry, HashMap}, panic::AssertUnwindSafe};
use sc_executor_common::wasm_runtime::WasmRuntime;
use sp_wasm_interface::Function;
/// Specification of different methods of executing the runtime Wasm code.
#[derive(Debug, PartialEq, Eq, Hash, Copy, Clone)]
pub enum WasmExecutionMethod {
/// Uses the Wasmi interpreter.
Interpreted,
/// Uses the Wasmtime compiled runtime.
#[cfg(feature = "wasmtime")]
Compiled,
}
/// A Wasm runtime object along with its cached runtime version.
struct VersionedRuntime {
runtime: Box<dyn WasmRuntime>,
/// The number of WebAssembly heap pages this instance was created with.
heap_pages: u64,
/// Runtime version according to `Core_version`.
version: RuntimeVersion,
}
/// Cache for the runtimes.
///
/// When an instance is requested for the first time it is added to this cache. Metadata is kept
/// with the instance so that it can be efficiently reinitialized.
///
/// When using the Wasmi interpreter execution method, the metadata includes the initial memory and
/// values of mutable globals. Follow-up requests to fetch a runtime return this one instance with
/// the memory reset to the initial memory. So, one runtime instance is reused for every fetch
/// request.
///
/// For now the cache grows indefinitely, but that should be fine for now since runtimes can only be
/// upgraded rarely and there are no other ways to make the node to execute some other runtime.
pub struct RuntimesCache {
/// A cache of runtime instances along with metadata, ready to be reused.
///
/// Instances are keyed by the Wasm execution method and the hash of their code.
instances: HashMap<(WasmExecutionMethod, Vec<u8>), Result<VersionedRuntime, WasmError>>,
}
impl RuntimesCache {
/// Creates a new instance of a runtimes cache.
pub fn new() -> RuntimesCache {
RuntimesCache {
instances: HashMap::new(),
}
}
/// Fetches an instance of the runtime.
///
/// On first use we create a new runtime instance, save it to the cache
/// and persist its initial memory.
///
/// Each subsequent request will return this instance, with its memory restored
/// to the persisted initial memory. Thus, we reuse one single runtime instance
/// for every `fetch_runtime` invocation.
///
/// # Parameters
///
/// `ext` - Externalities to use for the runtime. This is used for setting
/// up an initial runtime instance.
///
/// `default_heap_pages` - Number of 64KB pages to allocate for Wasm execution.
///
/// `host_functions` - The host functions that should be registered for the Wasm runtime.
///
/// # Return value
///
/// If no error occurred a tuple `(&mut WasmRuntime, H256)` is
/// returned. `H256` is the hash of the runtime code.
///
/// In case of failure one of two errors can be returned:
///
/// `Err::InvalidCode` is returned for runtime code issues.
///
/// `Error::InvalidMemoryReference` is returned if no memory export with the
/// identifier `memory` can be found in the runtime.
pub fn fetch_runtime<E: Externalities>(
&mut self,
ext: &mut E,
wasm_method: WasmExecutionMethod,
default_heap_pages: u64,
host_functions: &[&'static dyn Function],
) -> Result<(&mut (dyn WasmRuntime + 'static), &RuntimeVersion, Vec<u8>), Error> {
let code_hash = ext
.original_storage_hash(well_known_keys::CODE)
.ok_or(Error::InvalidCode("`CODE` not found in storage.".into()))?;
let heap_pages = ext
.storage(well_known_keys::HEAP_PAGES)
.and_then(|pages| u64::decode(&mut &pages[..]).ok())
.unwrap_or(default_heap_pages);
let result = match self.instances.entry((wasm_method, code_hash.clone())) {
Entry::Occupied(o) => {
let result = o.into_mut();
if let Ok(ref mut cached_runtime) = result {
let heap_pages_changed = cached_runtime.heap_pages != heap_pages;
let host_functions_changed = cached_runtime.runtime.host_functions()
!= host_functions;
if heap_pages_changed || host_functions_changed {
let changed = if heap_pages_changed {
"heap_pages"
} else {
"host functions"
};
trace!(
target: "runtimes_cache",
"{} were changed. Reinstantiating the instance",
changed,
);
*result = create_versioned_wasm_runtime(
ext,
wasm_method,
heap_pages,
host_functions.into(),
);
if let Err(ref err) = result {
warn!(target: "runtimes_cache", "cannot create a runtime: {:?}", err);
}
}
}
result
},
Entry::Vacant(v) => {
trace!(target: "runtimes_cache", "no instance found in cache, creating now.");
let result = create_versioned_wasm_runtime(
ext,
wasm_method,
heap_pages,
host_functions.into(),
);
if let Err(ref err) = result {
warn!(target: "runtimes_cache", "cannot create a runtime: {:?}", err);
}
v.insert(result)
}
};
result.as_mut()
.map(|entry| (entry.runtime.as_mut(), &entry.version, code_hash))
.map_err(|ref e| Error::InvalidCode(format!("{:?}", e)))
}
/// Invalidate the runtime for the given `wasm_method` and `code_hash`.
///
/// Invalidation of a runtime is useful when there was a `panic!` in native while executing it.
/// The `panic!` maybe have brought the runtime into a poisoned state and so, it is better to
/// invalidate this runtime instance.
pub fn invalidate_runtime(
&mut self,
wasm_method: WasmExecutionMethod,
code_hash: Vec<u8>,
) {
// Just remove the instance, it will be re-created the next time it is requested.
self.instances.remove(&(wasm_method, code_hash));
}
}
/// Create a wasm runtime with the given `code`.
pub fn create_wasm_runtime_with_code(
wasm_method: WasmExecutionMethod,
heap_pages: u64,
code: &[u8],
host_functions: Vec<&'static dyn Function>,
allow_missing_func_imports: bool,
) -> Result<Box<dyn WasmRuntime>, WasmError> {
match wasm_method {
WasmExecutionMethod::Interpreted =>
sc_executor_wasmi::create_instance(code, heap_pages, host_functions, allow_missing_func_imports)
.map(|runtime| -> Box<dyn WasmRuntime> { Box::new(runtime) }),
#[cfg(feature = "wasmtime")]
WasmExecutionMethod::Compiled =>
sc_executor_wasmtime::create_instance(code, heap_pages, host_functions, allow_missing_func_imports)
.map(|runtime| -> Box<dyn WasmRuntime> { Box::new(runtime) }),
}
}
fn create_versioned_wasm_runtime<E: Externalities>(
ext: &mut E,
wasm_method: WasmExecutionMethod,
heap_pages: u64,
host_functions: Vec<&'static dyn Function>,
) -> Result<VersionedRuntime, WasmError> {
let code = ext
.original_storage(well_known_keys::CODE)
.ok_or(WasmError::CodeNotFound)?;
let mut runtime = create_wasm_runtime_with_code(wasm_method, heap_pages, &code, host_functions, false)?;
// Call to determine runtime version.
let version_result = {
// `ext` is already implicitly handled as unwind safe, as we store it in a global variable.
let mut ext = AssertUnwindSafe(ext);
// The following unwind safety assertion is OK because if the method call panics, the
// runtime will be dropped.
let mut runtime = AssertUnwindSafe(runtime.as_mut());
crate::native_executor::with_externalities_safe(
&mut **ext,
move || runtime.call("Core_version", &[])
).map_err(|_| WasmError::Instantiation("panic in call to get runtime version".into()))?
};
let encoded_version = version_result
.map_err(|e| WasmError::Instantiation(format!("failed to call \"Core_version\": {}", e)))?;
let version = RuntimeVersion::decode(&mut encoded_version.as_slice())
.map_err(|_| WasmError::Instantiation("failed to decode \"Core_version\" result".into()))?;
Ok(VersionedRuntime {
runtime,
version,
heap_pages,
})
}
#[cfg(test)]
mod tests {
use sp_wasm_interface::HostFunctions;
#[test]
fn host_functions_are_equal() {
let host_functions = sp_io::SubstrateHostFunctions::host_functions();
let equal = &host_functions[..] == &host_functions[..];
assert!(equal, "Host functions are not equal");
}
}