// This file is part of Substrate.
// Copyright (C) 2019-2022 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 .
//! 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 codec::Decode;
use lru::LruCache;
use parking_lot::Mutex;
use sc_executor_common::{
runtime_blob::RuntimeBlob,
wasm_runtime::{WasmInstance, WasmModule},
};
use sp_core::traits::{Externalities, FetchRuntimeCode, RuntimeCode};
use sp_version::RuntimeVersion;
use std::{
num::NonZeroUsize,
panic::AssertUnwindSafe,
path::{Path, PathBuf},
sync::Arc,
};
use sp_wasm_interface::HostFunctions;
/// 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.
Compiled {
/// The instantiation strategy to use.
instantiation_strategy: sc_executor_wasmtime::InstantiationStrategy,
},
}
impl Default for WasmExecutionMethod {
fn default() -> WasmExecutionMethod {
WasmExecutionMethod::Interpreted
}
}
#[derive(Debug, PartialEq, Eq, Hash, Clone)]
struct VersionedRuntimeId {
/// Runtime code hash.
code_hash: Vec,
/// Wasm runtime type.
wasm_method: WasmExecutionMethod,
/// The number of WebAssembly heap pages this instance was created with.
heap_pages: u64,
}
/// A Wasm runtime object along with its cached runtime version.
struct VersionedRuntime {
/// Shared runtime that can spawn instances.
module: Arc,
/// Runtime version according to `Core_version` if any.
version: Option,
// TODO: Remove this once the legacy instance reuse instantiation strategy
// for `wasmtime` is gone, as this only makes sense with that particular strategy.
/// Cached instance pool.
instances: Arc>>>>,
}
impl VersionedRuntime {
/// Run the given closure `f` with an instance of this runtime.
fn with_instance(&self, ext: &mut dyn Externalities, f: F) -> Result
where
F: FnOnce(
&Arc,
&mut dyn WasmInstance,
Option<&RuntimeVersion>,
&mut dyn Externalities,
) -> Result,
{
// Find a free instance
let instance = self
.instances
.iter()
.enumerate()
.find_map(|(index, i)| i.try_lock().map(|i| (index, i)));
match instance {
Some((index, mut locked)) => {
let (mut instance, new_inst) = locked
.take()
.map(|r| Ok((r, false)))
.unwrap_or_else(|| self.module.new_instance().map(|i| (i, true)))?;
let result = f(&self.module, &mut *instance, self.version.as_ref(), ext);
if let Err(e) = &result {
if new_inst {
tracing::warn!(
target: "wasm-runtime",
error = %e,
"Fresh runtime instance failed",
)
} else {
tracing::warn!(
target: "wasm-runtime",
error = %e,
"Evicting failed runtime instance",
);
}
} else {
*locked = Some(instance);
if new_inst {
tracing::debug!(
target: "wasm-runtime",
"Allocated WASM instance {}/{}",
index + 1,
self.instances.len(),
);
}
}
result
},
None => {
tracing::warn!(target: "wasm-runtime", "Ran out of free WASM instances");
// Allocate a new instance
let mut instance = self.module.new_instance()?;
f(&self.module, &mut *instance, self.version.as_ref(), ext)
},
}
}
}
/// 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.
///
/// The size of cache is configurable via the cli option `--runtime-cache-size`.
pub struct RuntimeCache {
/// A cache of runtimes along with metadata.
///
/// Runtimes sorted by recent usage. The most recently used is at the front.
runtimes: Mutex>>,
/// The size of the instances cache for each runtime.
max_runtime_instances: usize,
cache_path: Option,
}
impl RuntimeCache {
/// Creates a new instance of a runtimes cache.
///
/// `max_runtime_instances` specifies the number of instances per runtime preserved in an
/// in-memory cache.
///
/// `cache_path` allows to specify an optional directory where the executor can store files
/// for caching.
///
/// `runtime_cache_size` specifies the number of different runtimes versions preserved in an
/// in-memory cache, must always be at least 1.
pub fn new(
max_runtime_instances: usize,
cache_path: Option,
runtime_cache_size: u8,
) -> RuntimeCache {
let cap =
NonZeroUsize::new(runtime_cache_size.max(1) as usize).expect("cache size is not zero");
RuntimeCache { runtimes: Mutex::new(LruCache::new(cap)), max_runtime_instances, cache_path }
}
/// Prepares a WASM module instance and executes given function for it.
///
/// This uses internal cache to find available instance or create a new one.
/// # Parameters
///
/// `runtime_code` - The runtime wasm code used setup the runtime.
///
/// `default_heap_pages` - Number of 64KB pages to allocate for Wasm execution.
///
/// `wasm_method` - Type of WASM backend to use.
///
/// `allow_missing_func_imports` - Ignore missing function imports.
///
/// `f` - Function to execute.
///
/// `H` - A compile-time list of host functions to expose to the runtime.
///
/// # Returns result of `f` wrapped in an additional result.
/// In case of failure one of two errors can be returned:
///
/// `Err::RuntimeConstruction` is returned for runtime construction issues.
///
/// `Error::InvalidMemoryReference` is returned if no memory export with the
/// identifier `memory` can be found in the runtime.
pub fn with_instance<'c, H, R, F>(
&self,
runtime_code: &'c RuntimeCode<'c>,
ext: &mut dyn Externalities,
wasm_method: WasmExecutionMethod,
default_heap_pages: u64,
allow_missing_func_imports: bool,
f: F,
) -> Result, Error>
where
H: HostFunctions,
F: FnOnce(
&Arc,
&mut dyn WasmInstance,
Option<&RuntimeVersion>,
&mut dyn Externalities,
) -> Result,
{
let code_hash = &runtime_code.hash;
let heap_pages = runtime_code.heap_pages.unwrap_or(default_heap_pages);
let versioned_runtime_id =
VersionedRuntimeId { code_hash: code_hash.clone(), heap_pages, wasm_method };
let mut runtimes = self.runtimes.lock(); // this must be released prior to calling f
let versioned_runtime = if let Some(versioned_runtime) = runtimes.get(&versioned_runtime_id)
{
versioned_runtime.clone()
} else {
let code = runtime_code.fetch_runtime_code().ok_or(WasmError::CodeNotFound)?;
let time = std::time::Instant::now();
let result = create_versioned_wasm_runtime::(
&code,
ext,
wasm_method,
heap_pages,
allow_missing_func_imports,
self.max_runtime_instances,
self.cache_path.as_deref(),
);
match result {
Ok(ref result) => {
tracing::debug!(
target: "wasm-runtime",
"Prepared new runtime version {:?} in {} ms.",
result.version,
time.elapsed().as_millis(),
);
},
Err(ref err) => {
tracing::warn!(target: "wasm-runtime", error = ?err, "Cannot create a runtime");
},
}
let versioned_runtime = Arc::new(result?);
// Save new versioned wasm runtime in cache
runtimes.put(versioned_runtime_id, versioned_runtime.clone());
versioned_runtime
};
// Lock must be released prior to calling f
drop(runtimes);
Ok(versioned_runtime.with_instance(ext, f))
}
}
/// Create a wasm runtime with the given `code`.
pub fn create_wasm_runtime_with_code(
wasm_method: WasmExecutionMethod,
heap_pages: u64,
blob: RuntimeBlob,
allow_missing_func_imports: bool,
cache_path: Option<&Path>,
) -> Result, WasmError>
where
H: HostFunctions,
{
match wasm_method {
WasmExecutionMethod::Interpreted => {
// Wasmi doesn't have any need in a cache directory.
//
// We drop the cache_path here to silence warnings that cache_path is not used if
// compiling without the `wasmtime` flag.
let _ = cache_path;
sc_executor_wasmi::create_runtime(
blob,
heap_pages,
H::host_functions(),
allow_missing_func_imports,
)
.map(|runtime| -> Arc { Arc::new(runtime) })
},
WasmExecutionMethod::Compiled { instantiation_strategy } =>
sc_executor_wasmtime::create_runtime::(
blob,
sc_executor_wasmtime::Config {
allow_missing_func_imports,
cache_path: cache_path.map(ToOwned::to_owned),
semantics: sc_executor_wasmtime::Semantics {
extra_heap_pages: heap_pages,
instantiation_strategy,
deterministic_stack_limit: None,
canonicalize_nans: false,
parallel_compilation: true,
max_memory_size: None,
},
},
)
.map(|runtime| -> Arc { Arc::new(runtime) }),
}
}
fn decode_version(mut version: &[u8]) -> Result {
Decode::decode(&mut version).map_err(|_| {
WasmError::Instantiation(
"failed to decode \"Core_version\" result using old runtime version".into(),
)
})
}
fn decode_runtime_apis(apis: &[u8]) -> Result, WasmError> {
use sp_api::RUNTIME_API_INFO_SIZE;
apis.chunks(RUNTIME_API_INFO_SIZE)
.map(|chunk| {
// `chunk` can be less than `RUNTIME_API_INFO_SIZE` if the total length of `apis`
// doesn't completely divide by `RUNTIME_API_INFO_SIZE`.
<[u8; RUNTIME_API_INFO_SIZE]>::try_from(chunk)
.map(sp_api::deserialize_runtime_api_info)
.map_err(|_| WasmError::Other("a clipped runtime api info declaration".to_owned()))
})
.collect::, WasmError>>()
}
/// Take the runtime blob and scan it for the custom wasm sections containing the version
/// information and construct the `RuntimeVersion` from them.
///
/// If there are no such sections, it returns `None`. If there is an error during decoding those
/// sections, `Err` will be returned.
pub fn read_embedded_version(blob: &RuntimeBlob) -> Result