mirror of
https://github.com/pezkuwichain/pezkuwi-subxt.git
synced 2026-04-27 23:18:01 +00:00
Refactor NativeExecutor to support multiple Wasm execution methods (#3677)
* executor: Move definitions of externals out of wasm_executor module. * executor: Create WasmRuntime trait. This will be used to decouple the runtime cache from wasmi execution. * executor: Remove WasmExecutor and move methods to wasmi_execution. These will now be crate-internal functions and there is no need for the struct. * executor: Set default default_heap_pages in NativeExecutor. * cli: CLI configuration for Wasm execution method. * executor: Remove wasmi-specific code from wasm_runtime. * Respond to review comments.
This commit is contained in:
@@ -98,3 +98,18 @@ impl From<String> for Error {
|
||||
Error::Other(err)
|
||||
}
|
||||
}
|
||||
|
||||
/// Type for errors occurring during Wasm runtime construction.
|
||||
#[derive(Debug, derive_more::Display)]
|
||||
pub enum WasmError {
|
||||
/// Code could not be read from the state.
|
||||
CodeNotFound,
|
||||
/// Failure to reinitialize runtime instance from snapshot.
|
||||
ApplySnapshotFailed,
|
||||
/// Wasm code failed validation.
|
||||
InvalidModule,
|
||||
/// Wasm code could not be deserialized.
|
||||
CantDeserializeWasm,
|
||||
/// Instantiation error.
|
||||
Instantiation(Error),
|
||||
}
|
||||
|
||||
+38
-765
@@ -14,33 +14,20 @@
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Substrate. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
//! Wasm interface module.
|
||||
//! Definition and implementation of the Substrate Wasm host interface.
|
||||
//!
|
||||
//! This module defines and implements the wasm part of Substrate Host Interface and provides
|
||||
//! an interface for calling into the wasm runtime.
|
||||
//! These are the host functions callable from within the Substrate runtime.
|
||||
|
||||
use crate::error::{Error, Result};
|
||||
|
||||
use codec::Encode;
|
||||
use std::{convert::TryFrom, str, panic};
|
||||
use tiny_keccak;
|
||||
use secp256k1;
|
||||
|
||||
use wasmi::{
|
||||
Module, ModuleInstance, MemoryInstance, MemoryRef, TableRef, ImportsBuilder, ModuleRef,
|
||||
memory_units::Pages, RuntimeValue::{I32, I64, self},
|
||||
};
|
||||
use super::{sandbox, allocator, error::{Error, Result}};
|
||||
use codec::{Encode, Decode};
|
||||
use primitives::{
|
||||
blake2_128, blake2_256, twox_64, twox_128, twox_256, ed25519, sr25519, Pair, crypto::KeyTypeId,
|
||||
offchain, sandbox as sandbox_primitives, Blake2Hasher,
|
||||
traits::Externalities,
|
||||
};
|
||||
use trie::TrieConfiguration;
|
||||
use trie::trie_types::Layout;
|
||||
use log::trace;
|
||||
use wasm_interface::{
|
||||
FunctionContext, HostFunctions, Pointer, WordSize, Sandbox, MemoryId, PointerType,
|
||||
Result as WResult,
|
||||
blake2_128, blake2_256, twox_64, twox_128, twox_256, ed25519, sr25519, Blake2Hasher, Pair,
|
||||
crypto::KeyTypeId, offchain,
|
||||
};
|
||||
use trie::{TrieConfiguration, trie_types::Layout};
|
||||
use wasm_interface::{FunctionContext, Pointer, PointerType, Result as WResult, WordSize};
|
||||
|
||||
#[cfg(feature="wasm-extern-trace")]
|
||||
macro_rules! debug_trace {
|
||||
@@ -52,317 +39,7 @@ macro_rules! debug_trace {
|
||||
( $( $x:tt )* ) => ()
|
||||
}
|
||||
|
||||
struct FunctionExecutor {
|
||||
sandbox_store: sandbox::Store<wasmi::FuncRef>,
|
||||
heap: allocator::FreeingBumpHeapAllocator,
|
||||
memory: MemoryRef,
|
||||
table: Option<TableRef>,
|
||||
}
|
||||
|
||||
impl FunctionExecutor {
|
||||
fn new(m: MemoryRef, heap_base: u32, t: Option<TableRef>) -> Result<Self> {
|
||||
Ok(FunctionExecutor {
|
||||
sandbox_store: sandbox::Store::new(),
|
||||
heap: allocator::FreeingBumpHeapAllocator::new(heap_base),
|
||||
memory: m,
|
||||
table: t,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl sandbox::SandboxCapabilities for FunctionExecutor {
|
||||
type SupervisorFuncRef = wasmi::FuncRef;
|
||||
|
||||
fn store(&self) -> &sandbox::Store<Self::SupervisorFuncRef> {
|
||||
&self.sandbox_store
|
||||
}
|
||||
fn store_mut(&mut self) -> &mut sandbox::Store<Self::SupervisorFuncRef> {
|
||||
&mut self.sandbox_store
|
||||
}
|
||||
fn allocate(&mut self, len: WordSize) -> Result<Pointer<u8>> {
|
||||
let heap = &mut self.heap;
|
||||
self.memory.with_direct_access_mut(|mem| {
|
||||
heap.allocate(mem, len)
|
||||
})
|
||||
}
|
||||
fn deallocate(&mut self, ptr: Pointer<u8>) -> Result<()> {
|
||||
let heap = &mut self.heap;
|
||||
self.memory.with_direct_access_mut(|mem| {
|
||||
heap.deallocate(mem, ptr)
|
||||
})
|
||||
}
|
||||
fn write_memory(&mut self, ptr: Pointer<u8>, data: &[u8]) -> Result<()> {
|
||||
self.memory.set(ptr.into(), data).map_err(Into::into)
|
||||
}
|
||||
fn read_memory(&self, ptr: Pointer<u8>, len: WordSize) -> Result<Vec<u8>> {
|
||||
self.memory.get(ptr.into(), len as usize).map_err(Into::into)
|
||||
}
|
||||
|
||||
fn invoke(
|
||||
&mut self,
|
||||
dispatch_thunk: &Self::SupervisorFuncRef,
|
||||
invoke_args_ptr: Pointer<u8>,
|
||||
invoke_args_len: WordSize,
|
||||
state: u32,
|
||||
func_idx: sandbox::SupervisorFuncIndex,
|
||||
) -> Result<i64>
|
||||
{
|
||||
let result = wasmi::FuncInstance::invoke(
|
||||
dispatch_thunk,
|
||||
&[
|
||||
RuntimeValue::I32(u32::from(invoke_args_ptr) as i32),
|
||||
RuntimeValue::I32(invoke_args_len as i32),
|
||||
RuntimeValue::I32(state as i32),
|
||||
RuntimeValue::I32(usize::from(func_idx) as i32),
|
||||
],
|
||||
self,
|
||||
);
|
||||
match result {
|
||||
Ok(Some(RuntimeValue::I64(val))) => Ok(val),
|
||||
Ok(_) => return Err("Supervisor function returned unexpected result!".into()),
|
||||
Err(err) => Err(Error::Trap(err)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl FunctionContext for FunctionExecutor {
|
||||
fn read_memory_into(&self, address: Pointer<u8>, dest: &mut [u8]) -> WResult<()> {
|
||||
self.memory.get_into(address.into(), dest).map_err(|e| format!("{:?}", e))
|
||||
}
|
||||
|
||||
fn write_memory(&mut self, address: Pointer<u8>, data: &[u8]) -> WResult<()> {
|
||||
self.memory.set(address.into(), data).map_err(|e| format!("{:?}", e))
|
||||
}
|
||||
|
||||
fn allocate_memory(&mut self, size: WordSize) -> WResult<Pointer<u8>> {
|
||||
let heap = &mut self.heap;
|
||||
self.memory.with_direct_access_mut(|mem| {
|
||||
heap.allocate(mem, size).map_err(|e| format!("{:?}", e))
|
||||
})
|
||||
}
|
||||
|
||||
fn deallocate_memory(&mut self, ptr: Pointer<u8>) -> WResult<()> {
|
||||
let heap = &mut self.heap;
|
||||
self.memory.with_direct_access_mut(|mem| {
|
||||
heap.deallocate(mem, ptr).map_err(|e| format!("{:?}", e))
|
||||
})
|
||||
}
|
||||
|
||||
fn sandbox(&mut self) -> &mut dyn Sandbox {
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
impl Sandbox for FunctionExecutor {
|
||||
fn memory_get(
|
||||
&self,
|
||||
memory_id: MemoryId,
|
||||
offset: WordSize,
|
||||
buf_ptr: Pointer<u8>,
|
||||
buf_len: WordSize,
|
||||
) -> WResult<u32> {
|
||||
let sandboxed_memory = self.sandbox_store.memory(memory_id).map_err(|e| format!("{:?}", e))?;
|
||||
|
||||
match MemoryInstance::transfer(
|
||||
&sandboxed_memory,
|
||||
offset as usize,
|
||||
&self.memory,
|
||||
buf_ptr.into(),
|
||||
buf_len as usize,
|
||||
) {
|
||||
Ok(()) => Ok(sandbox_primitives::ERR_OK),
|
||||
Err(_) => Ok(sandbox_primitives::ERR_OUT_OF_BOUNDS),
|
||||
}
|
||||
}
|
||||
|
||||
fn memory_set(
|
||||
&mut self,
|
||||
memory_id: MemoryId,
|
||||
offset: WordSize,
|
||||
val_ptr: Pointer<u8>,
|
||||
val_len: WordSize,
|
||||
) -> WResult<u32> {
|
||||
let sandboxed_memory = self.sandbox_store.memory(memory_id).map_err(|e| format!("{:?}", e))?;
|
||||
|
||||
match MemoryInstance::transfer(
|
||||
&self.memory,
|
||||
val_ptr.into(),
|
||||
&sandboxed_memory,
|
||||
offset as usize,
|
||||
val_len as usize,
|
||||
) {
|
||||
Ok(()) => Ok(sandbox_primitives::ERR_OK),
|
||||
Err(_) => Ok(sandbox_primitives::ERR_OUT_OF_BOUNDS),
|
||||
}
|
||||
}
|
||||
|
||||
fn memory_teardown(&mut self, memory_id: MemoryId) -> WResult<()> {
|
||||
self.sandbox_store.memory_teardown(memory_id).map_err(|e| format!("{:?}", e))
|
||||
}
|
||||
|
||||
fn memory_new(
|
||||
&mut self,
|
||||
initial: u32,
|
||||
maximum: u32,
|
||||
) -> WResult<MemoryId> {
|
||||
self.sandbox_store.new_memory(initial, maximum).map_err(|e| format!("{:?}", e))
|
||||
}
|
||||
|
||||
fn invoke(
|
||||
&mut self,
|
||||
instance_id: u32,
|
||||
export_name: &str,
|
||||
args: &[u8],
|
||||
return_val: Pointer<u8>,
|
||||
return_val_len: WordSize,
|
||||
state: u32,
|
||||
) -> WResult<u32> {
|
||||
trace!(target: "sr-sandbox", "invoke, instance_idx={}", instance_id);
|
||||
|
||||
// Deserialize arguments and convert them into wasmi types.
|
||||
let args = Vec::<sandbox_primitives::TypedValue>::decode(&mut &args[..])
|
||||
.map_err(|_| "Can't decode serialized arguments for the invocation")?
|
||||
.into_iter()
|
||||
.map(Into::into)
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
let instance = self.sandbox_store.instance(instance_id).map_err(|e| format!("{:?}", e))?;
|
||||
let result = instance.invoke(export_name, &args, self, state);
|
||||
|
||||
match result {
|
||||
Ok(None) => Ok(sandbox_primitives::ERR_OK),
|
||||
Ok(Some(val)) => {
|
||||
// Serialize return value and write it back into the memory.
|
||||
sandbox_primitives::ReturnValue::Value(val.into()).using_encoded(|val| {
|
||||
if val.len() > return_val_len as usize {
|
||||
Err("Return value buffer is too small")?;
|
||||
}
|
||||
self.write_memory(return_val, val).map_err(|_| "Return value buffer is OOB")?;
|
||||
Ok(sandbox_primitives::ERR_OK)
|
||||
})
|
||||
}
|
||||
Err(_) => Ok(sandbox_primitives::ERR_EXECUTION),
|
||||
}
|
||||
}
|
||||
|
||||
fn instance_teardown(&mut self, instance_id: u32) -> WResult<()> {
|
||||
self.sandbox_store.instance_teardown(instance_id).map_err(|e| format!("{:?}", e))
|
||||
}
|
||||
|
||||
fn instance_new(
|
||||
&mut self,
|
||||
dispatch_thunk_id: u32,
|
||||
wasm: &[u8],
|
||||
raw_env_def: &[u8],
|
||||
state: u32,
|
||||
) -> WResult<u32> {
|
||||
// Extract a dispatch thunk from instance's table by the specified index.
|
||||
let dispatch_thunk = {
|
||||
let table = self.table.as_ref()
|
||||
.ok_or_else(|| "Runtime doesn't have a table; sandbox is unavailable")?;
|
||||
table.get(dispatch_thunk_id)
|
||||
.map_err(|_| "dispatch_thunk_idx is out of the table bounds")?
|
||||
.ok_or_else(|| "dispatch_thunk_idx points on an empty table entry")?
|
||||
.clone()
|
||||
};
|
||||
|
||||
let instance_idx_or_err_code =
|
||||
match sandbox::instantiate(self, dispatch_thunk, wasm, raw_env_def, state) {
|
||||
Ok(instance_idx) => instance_idx,
|
||||
Err(sandbox::InstantiationError::StartTrapped) =>
|
||||
sandbox_primitives::ERR_EXECUTION,
|
||||
Err(_) => sandbox_primitives::ERR_MODULE,
|
||||
};
|
||||
|
||||
Ok(instance_idx_or_err_code as u32)
|
||||
}
|
||||
}
|
||||
|
||||
trait WritePrimitive<T: PointerType> {
|
||||
fn write_primitive(&mut self, ptr: Pointer<T>, t: T) -> WResult<()>;
|
||||
}
|
||||
|
||||
impl WritePrimitive<u32> for &mut dyn FunctionContext {
|
||||
fn write_primitive(&mut self, ptr: Pointer<u32>, t: u32) -> WResult<()> {
|
||||
let r = t.to_le_bytes();
|
||||
self.write_memory(ptr.cast(), &r)
|
||||
}
|
||||
}
|
||||
|
||||
trait ReadPrimitive<T: PointerType> {
|
||||
fn read_primitive(&self, offset: Pointer<T>) -> WResult<T>;
|
||||
}
|
||||
|
||||
impl ReadPrimitive<u32> for &mut dyn FunctionContext {
|
||||
fn read_primitive(&self, ptr: Pointer<u32>) -> WResult<u32> {
|
||||
let mut r = [0u8; 4];
|
||||
self.read_memory_into(ptr.cast(), &mut r)?;
|
||||
Ok(u32::from_le_bytes(r))
|
||||
}
|
||||
}
|
||||
|
||||
fn deadline_to_timestamp(deadline: u64) -> Option<offchain::Timestamp> {
|
||||
if deadline == 0 {
|
||||
None
|
||||
} else {
|
||||
Some(offchain::Timestamp::from_unix_millis(deadline))
|
||||
}
|
||||
}
|
||||
|
||||
impl FunctionExecutor {
|
||||
fn resolver() -> &'static dyn wasmi::ModuleImportResolver {
|
||||
struct Resolver;
|
||||
impl wasmi::ModuleImportResolver for Resolver {
|
||||
fn resolve_func(&self, name: &str, signature: &wasmi::Signature)
|
||||
-> std::result::Result<wasmi::FuncRef, wasmi::Error>
|
||||
{
|
||||
let signature = wasm_interface::Signature::from(signature);
|
||||
|
||||
if let Some((index, func)) = SubstrateExternals::functions().iter()
|
||||
.enumerate()
|
||||
.find(|f| name == f.1.name())
|
||||
{
|
||||
if signature == func.signature() {
|
||||
Ok(wasmi::FuncInstance::alloc_host(signature.into(), index))
|
||||
} else {
|
||||
Err(wasmi::Error::Instantiation(
|
||||
format!(
|
||||
"Invalid signature for function `{}` expected `{:?}`, got `{:?}`",
|
||||
func.name(),
|
||||
signature,
|
||||
func.signature(),
|
||||
)
|
||||
))
|
||||
}
|
||||
} else {
|
||||
Err(wasmi::Error::Instantiation(
|
||||
format!("Export {} not found", name),
|
||||
))
|
||||
}
|
||||
}
|
||||
}
|
||||
&Resolver
|
||||
}
|
||||
}
|
||||
|
||||
impl wasmi::Externals for FunctionExecutor {
|
||||
fn invoke_index(&mut self, index: usize, args: wasmi::RuntimeArgs)
|
||||
-> std::result::Result<Option<wasmi::RuntimeValue>, wasmi::Trap>
|
||||
{
|
||||
let mut args = args.as_ref().iter().copied().map(Into::into);
|
||||
let function = SubstrateExternals::functions().get(index).ok_or_else(||
|
||||
Error::from(
|
||||
format!("Could not find host function with index: {}", index),
|
||||
)
|
||||
)?;
|
||||
|
||||
function.execute(self, &mut args)
|
||||
.map_err(Error::FunctionExecution)
|
||||
.map_err(wasmi::Trap::from)
|
||||
.map(|v| v.map(Into::into))
|
||||
}
|
||||
}
|
||||
struct SubstrateExternals;
|
||||
pub struct SubstrateExternals;
|
||||
|
||||
impl_wasm_host_interface! {
|
||||
impl SubstrateExternals where context {
|
||||
@@ -1400,6 +1077,29 @@ impl_wasm_host_interface! {
|
||||
}
|
||||
}
|
||||
|
||||
trait WritePrimitive<T: PointerType> {
|
||||
fn write_primitive(&mut self, ptr: Pointer<T>, t: T) -> WResult<()>;
|
||||
}
|
||||
|
||||
impl WritePrimitive<u32> for &mut dyn FunctionContext {
|
||||
fn write_primitive(&mut self, ptr: Pointer<u32>, t: u32) -> WResult<()> {
|
||||
let r = t.to_le_bytes();
|
||||
self.write_memory(ptr.cast(), &r)
|
||||
}
|
||||
}
|
||||
|
||||
trait ReadPrimitive<T: PointerType> {
|
||||
fn read_primitive(&self, offset: Pointer<T>) -> WResult<T>;
|
||||
}
|
||||
|
||||
impl ReadPrimitive<u32> for &mut dyn FunctionContext {
|
||||
fn read_primitive(&self, ptr: Pointer<u32>) -> WResult<u32> {
|
||||
let mut r = [0u8; 4];
|
||||
self.read_memory_into(ptr.cast(), &mut r)?;
|
||||
Ok(u32::from_le_bytes(r))
|
||||
}
|
||||
}
|
||||
|
||||
/// Execute closure that access external storage.
|
||||
///
|
||||
/// All panics that happen within closure are captured and transformed into
|
||||
@@ -1419,438 +1119,11 @@ fn with_external_storage<T, F>(f: F) -> std::result::Result<T, String>
|
||||
.map_err(|err| format!("{}", err))
|
||||
}
|
||||
|
||||
/// Wasm rust executor for contracts.
|
||||
///
|
||||
/// Executes the provided code in a sandboxed wasm runtime.
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct WasmExecutor;
|
||||
|
||||
impl WasmExecutor {
|
||||
/// Create a new instance.
|
||||
pub fn new() -> Self {
|
||||
WasmExecutor
|
||||
}
|
||||
|
||||
/// Call a given method in the given code.
|
||||
///
|
||||
/// Signature of this method needs to be `(I32, I32) -> I64`.
|
||||
///
|
||||
/// This should be used for tests only.
|
||||
pub fn call<E: Externalities<Blake2Hasher>>(
|
||||
&self,
|
||||
ext: &mut E,
|
||||
heap_pages: usize,
|
||||
code: &[u8],
|
||||
method: &str,
|
||||
data: &[u8],
|
||||
) -> Result<Vec<u8>> {
|
||||
let module = wasmi::Module::from_buffer(code)?;
|
||||
let module = Self::instantiate_module(heap_pages, &module)?;
|
||||
|
||||
self.call_in_wasm_module(ext, &module, method, data)
|
||||
}
|
||||
|
||||
/// Call a given method with a custom signature in the given code.
|
||||
///
|
||||
/// This should be used for tests only.
|
||||
pub fn call_with_custom_signature<
|
||||
E: Externalities<Blake2Hasher>,
|
||||
F: FnOnce(&mut dyn FnMut(&[u8]) -> Result<u32>) -> Result<Vec<RuntimeValue>>,
|
||||
FR: FnOnce(Option<RuntimeValue>, &MemoryRef) -> Result<Option<R>>,
|
||||
R,
|
||||
>(
|
||||
&self,
|
||||
ext: &mut E,
|
||||
heap_pages: usize,
|
||||
code: &[u8],
|
||||
method: &str,
|
||||
create_parameters: F,
|
||||
filter_result: FR,
|
||||
) -> Result<R> {
|
||||
let module = wasmi::Module::from_buffer(code)?;
|
||||
let module = Self::instantiate_module(heap_pages, &module)?;
|
||||
|
||||
self.call_in_wasm_module_with_custom_signature(
|
||||
ext,
|
||||
&module,
|
||||
method,
|
||||
create_parameters,
|
||||
filter_result,
|
||||
)
|
||||
}
|
||||
|
||||
fn get_mem_instance(module: &ModuleRef) -> Result<MemoryRef> {
|
||||
Ok(module
|
||||
.export_by_name("memory")
|
||||
.ok_or_else(|| Error::InvalidMemoryReference)?
|
||||
.as_memory()
|
||||
.ok_or_else(|| Error::InvalidMemoryReference)?
|
||||
.clone())
|
||||
}
|
||||
|
||||
/// Find the global named `__heap_base` in the given wasm module instance and
|
||||
/// tries to get its value.
|
||||
fn get_heap_base(module: &ModuleRef) -> Result<u32> {
|
||||
let heap_base_val = module
|
||||
.export_by_name("__heap_base")
|
||||
.ok_or_else(|| Error::HeapBaseNotFoundOrInvalid)?
|
||||
.as_global()
|
||||
.ok_or_else(|| Error::HeapBaseNotFoundOrInvalid)?
|
||||
.get();
|
||||
|
||||
match heap_base_val {
|
||||
wasmi::RuntimeValue::I32(v) => Ok(v as u32),
|
||||
_ => Err(Error::HeapBaseNotFoundOrInvalid),
|
||||
}
|
||||
}
|
||||
|
||||
/// Call a given method in the given wasm-module runtime.
|
||||
pub fn call_in_wasm_module<E: Externalities<Blake2Hasher>>(
|
||||
&self,
|
||||
ext: &mut E,
|
||||
module_instance: &ModuleRef,
|
||||
method: &str,
|
||||
data: &[u8],
|
||||
) -> Result<Vec<u8>> {
|
||||
self.call_in_wasm_module_with_custom_signature(
|
||||
ext,
|
||||
module_instance,
|
||||
method,
|
||||
|alloc| {
|
||||
let offset = alloc(data)?;
|
||||
Ok(vec![I32(offset as i32), I32(data.len() as i32)])
|
||||
},
|
||||
|res, memory| {
|
||||
if let Some(I64(r)) = res {
|
||||
let offset = r as u32;
|
||||
let length = (r as u64 >> 32) as usize;
|
||||
memory.get(offset, length).map_err(|_| Error::Runtime).map(Some)
|
||||
} else {
|
||||
Ok(None)
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
/// Call a given method in the given wasm-module runtime.
|
||||
fn call_in_wasm_module_with_custom_signature<
|
||||
E: Externalities<Blake2Hasher>,
|
||||
F: FnOnce(&mut dyn FnMut(&[u8]) -> Result<u32>) -> Result<Vec<RuntimeValue>>,
|
||||
FR: FnOnce(Option<RuntimeValue>, &MemoryRef) -> Result<Option<R>>,
|
||||
R,
|
||||
>(
|
||||
&self,
|
||||
ext: &mut E,
|
||||
module_instance: &ModuleRef,
|
||||
method: &str,
|
||||
create_parameters: F,
|
||||
filter_result: FR,
|
||||
) -> Result<R> {
|
||||
// extract a reference to a linear memory, optional reference to a table
|
||||
// and then initialize FunctionExecutor.
|
||||
let memory = Self::get_mem_instance(module_instance)?;
|
||||
let table: Option<TableRef> = module_instance
|
||||
.export_by_name("__indirect_function_table")
|
||||
.and_then(|e| e.as_table().cloned());
|
||||
let heap_base = Self::get_heap_base(module_instance)?;
|
||||
|
||||
let mut fec = FunctionExecutor::new(
|
||||
memory.clone(),
|
||||
heap_base,
|
||||
table,
|
||||
)?;
|
||||
|
||||
let parameters = create_parameters(&mut |data: &[u8]| {
|
||||
let offset = fec.allocate_memory(data.len() as u32)?;
|
||||
fec.write_memory(offset, data).map(|_| offset.into()).map_err(Into::into)
|
||||
})?;
|
||||
|
||||
let result = runtime_io::with_externalities(
|
||||
ext,
|
||||
|| module_instance.invoke_export(method, ¶meters, &mut fec),
|
||||
);
|
||||
|
||||
match result {
|
||||
Ok(val) => match filter_result(val, &memory)? {
|
||||
Some(val) => Ok(val),
|
||||
None => Err(Error::InvalidReturn),
|
||||
},
|
||||
Err(e) => {
|
||||
trace!(
|
||||
target: "wasm-executor",
|
||||
"Failed to execute code with {} pages",
|
||||
memory.current_size().0
|
||||
);
|
||||
Err(e.into())
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
/// Prepare module instance
|
||||
pub fn instantiate_module(
|
||||
heap_pages: usize,
|
||||
module: &Module,
|
||||
) -> Result<ModuleRef> {
|
||||
// start module instantiation. Don't run 'start' function yet.
|
||||
let intermediate_instance = ModuleInstance::new(
|
||||
module,
|
||||
&ImportsBuilder::new()
|
||||
.with_resolver("env", FunctionExecutor::resolver())
|
||||
)?;
|
||||
|
||||
// Verify that the module has the heap base global variable.
|
||||
let _ = Self::get_heap_base(intermediate_instance.not_started_instance())?;
|
||||
|
||||
// Extract a reference to a linear memory.
|
||||
let memory = Self::get_mem_instance(intermediate_instance.not_started_instance())?;
|
||||
memory.grow(Pages(heap_pages)).map_err(|_| Error::Runtime)?;
|
||||
|
||||
if intermediate_instance.has_start() {
|
||||
// Runtime is not allowed to have the `start` function.
|
||||
Err(Error::RuntimeHasStartFn)
|
||||
} else {
|
||||
Ok(intermediate_instance.assert_no_start())
|
||||
}
|
||||
fn deadline_to_timestamp(deadline: u64) -> Option<offchain::Timestamp> {
|
||||
if deadline == 0 {
|
||||
None
|
||||
} else {
|
||||
Some(offchain::Timestamp::from_unix_millis(deadline))
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
use codec::Encode;
|
||||
|
||||
use state_machine::TestExternalities as CoreTestExternalities;
|
||||
use hex_literal::hex;
|
||||
use primitives::map;
|
||||
use runtime_test::WASM_BINARY;
|
||||
use substrate_offchain::testing;
|
||||
|
||||
type TestExternalities<H> = CoreTestExternalities<H, u64>;
|
||||
|
||||
#[test]
|
||||
fn returning_should_work() {
|
||||
let mut ext = TestExternalities::default();
|
||||
let test_code = WASM_BINARY;
|
||||
|
||||
let output = WasmExecutor::new().call(&mut ext, 8, &test_code[..], "test_empty_return", &[]).unwrap();
|
||||
assert_eq!(output, vec![0u8; 0]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn panicking_should_work() {
|
||||
let mut ext = TestExternalities::default();
|
||||
let test_code = WASM_BINARY;
|
||||
|
||||
let output = WasmExecutor::new().call(&mut ext, 8, &test_code[..], "test_panic", &[]);
|
||||
assert!(output.is_err());
|
||||
|
||||
let output = WasmExecutor::new().call(&mut ext, 8, &test_code[..], "test_conditional_panic", &[]);
|
||||
assert_eq!(output.unwrap(), vec![0u8; 0]);
|
||||
|
||||
let output = WasmExecutor::new().call(&mut ext, 8, &test_code[..], "test_conditional_panic", &[2]);
|
||||
assert!(output.is_err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn storage_should_work() {
|
||||
let mut ext = TestExternalities::default();
|
||||
ext.set_storage(b"foo".to_vec(), b"bar".to_vec());
|
||||
let test_code = WASM_BINARY;
|
||||
|
||||
let output = WasmExecutor::new().call(&mut ext, 8, &test_code[..], "test_data_in", b"Hello world").unwrap();
|
||||
|
||||
assert_eq!(output, b"all ok!".to_vec());
|
||||
|
||||
let expected = TestExternalities::new((map![
|
||||
b"input".to_vec() => b"Hello world".to_vec(),
|
||||
b"foo".to_vec() => b"bar".to_vec(),
|
||||
b"baz".to_vec() => b"bar".to_vec()
|
||||
], map![]));
|
||||
assert_eq!(ext, expected);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn clear_prefix_should_work() {
|
||||
let mut ext = TestExternalities::default();
|
||||
ext.set_storage(b"aaa".to_vec(), b"1".to_vec());
|
||||
ext.set_storage(b"aab".to_vec(), b"2".to_vec());
|
||||
ext.set_storage(b"aba".to_vec(), b"3".to_vec());
|
||||
ext.set_storage(b"abb".to_vec(), b"4".to_vec());
|
||||
ext.set_storage(b"bbb".to_vec(), b"5".to_vec());
|
||||
let test_code = WASM_BINARY;
|
||||
|
||||
// This will clear all entries which prefix is "ab".
|
||||
let output = WasmExecutor::new().call(&mut ext, 8, &test_code[..], "test_clear_prefix", b"ab").unwrap();
|
||||
|
||||
assert_eq!(output, b"all ok!".to_vec());
|
||||
|
||||
let expected = TestExternalities::new((map![
|
||||
b"aaa".to_vec() => b"1".to_vec(),
|
||||
b"aab".to_vec() => b"2".to_vec(),
|
||||
b"bbb".to_vec() => b"5".to_vec()
|
||||
], map![]));
|
||||
assert_eq!(expected, ext);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn blake2_256_should_work() {
|
||||
let mut ext = TestExternalities::default();
|
||||
let test_code = WASM_BINARY;
|
||||
assert_eq!(
|
||||
WasmExecutor::new().call(&mut ext, 8, &test_code[..], "test_blake2_256", &[]).unwrap(),
|
||||
blake2_256(&b""[..]).encode()
|
||||
);
|
||||
assert_eq!(
|
||||
WasmExecutor::new().call(&mut ext, 8, &test_code[..], "test_blake2_256", b"Hello world!").unwrap(),
|
||||
blake2_256(&b"Hello world!"[..]).encode()
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn blake2_128_should_work() {
|
||||
let mut ext = TestExternalities::default();
|
||||
let test_code = WASM_BINARY;
|
||||
assert_eq!(
|
||||
WasmExecutor::new().call(&mut ext, 8, &test_code[..], "test_blake2_128", &[]).unwrap(),
|
||||
blake2_128(&b""[..]).encode()
|
||||
);
|
||||
assert_eq!(
|
||||
WasmExecutor::new().call(&mut ext, 8, &test_code[..], "test_blake2_128", b"Hello world!").unwrap(),
|
||||
blake2_128(&b"Hello world!"[..]).encode()
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn twox_256_should_work() {
|
||||
let mut ext = TestExternalities::default();
|
||||
let test_code = WASM_BINARY;
|
||||
assert_eq!(
|
||||
WasmExecutor::new().call(&mut ext, 8, &test_code[..], "test_twox_256", &[]).unwrap(),
|
||||
hex!("99e9d85137db46ef4bbea33613baafd56f963c64b1f3685a4eb4abd67ff6203a"),
|
||||
);
|
||||
assert_eq!(
|
||||
WasmExecutor::new().call(&mut ext, 8, &test_code[..], "test_twox_256", b"Hello world!").unwrap(),
|
||||
hex!("b27dfd7f223f177f2a13647b533599af0c07f68bda23d96d059da2b451a35a74"),
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn twox_128_should_work() {
|
||||
let mut ext = TestExternalities::default();
|
||||
let test_code = WASM_BINARY;
|
||||
assert_eq!(
|
||||
WasmExecutor::new().call(&mut ext, 8, &test_code[..], "test_twox_128", &[]).unwrap(),
|
||||
hex!("99e9d85137db46ef4bbea33613baafd5")
|
||||
);
|
||||
assert_eq!(
|
||||
WasmExecutor::new().call(&mut ext, 8, &test_code[..], "test_twox_128", b"Hello world!").unwrap(),
|
||||
hex!("b27dfd7f223f177f2a13647b533599af")
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn ed25519_verify_should_work() {
|
||||
let mut ext = TestExternalities::<Blake2Hasher>::default();
|
||||
let test_code = WASM_BINARY;
|
||||
let key = ed25519::Pair::from_seed(&blake2_256(b"test"));
|
||||
let sig = key.sign(b"all ok!");
|
||||
let mut calldata = vec![];
|
||||
calldata.extend_from_slice(key.public().as_ref());
|
||||
calldata.extend_from_slice(sig.as_ref());
|
||||
|
||||
assert_eq!(
|
||||
WasmExecutor::new().call(&mut ext, 8, &test_code[..], "test_ed25519_verify", &calldata).unwrap(),
|
||||
vec![1]
|
||||
);
|
||||
|
||||
let other_sig = key.sign(b"all is not ok!");
|
||||
let mut calldata = vec![];
|
||||
calldata.extend_from_slice(key.public().as_ref());
|
||||
calldata.extend_from_slice(other_sig.as_ref());
|
||||
|
||||
assert_eq!(
|
||||
WasmExecutor::new().call(&mut ext, 8, &test_code[..], "test_ed25519_verify", &calldata).unwrap(),
|
||||
vec![0]
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn sr25519_verify_should_work() {
|
||||
let mut ext = TestExternalities::<Blake2Hasher>::default();
|
||||
let test_code = WASM_BINARY;
|
||||
let key = sr25519::Pair::from_seed(&blake2_256(b"test"));
|
||||
let sig = key.sign(b"all ok!");
|
||||
let mut calldata = vec![];
|
||||
calldata.extend_from_slice(key.public().as_ref());
|
||||
calldata.extend_from_slice(sig.as_ref());
|
||||
|
||||
assert_eq!(
|
||||
WasmExecutor::new().call(&mut ext, 8, &test_code[..], "test_sr25519_verify", &calldata).unwrap(),
|
||||
vec![1]
|
||||
);
|
||||
|
||||
let other_sig = key.sign(b"all is not ok!");
|
||||
let mut calldata = vec![];
|
||||
calldata.extend_from_slice(key.public().as_ref());
|
||||
calldata.extend_from_slice(other_sig.as_ref());
|
||||
|
||||
assert_eq!(
|
||||
WasmExecutor::new().call(&mut ext, 8, &test_code[..], "test_sr25519_verify", &calldata).unwrap(),
|
||||
vec![0]
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn ordered_trie_root_should_work() {
|
||||
let mut ext = TestExternalities::<Blake2Hasher>::default();
|
||||
let trie_input = vec![b"zero".to_vec(), b"one".to_vec(), b"two".to_vec()];
|
||||
let test_code = WASM_BINARY;
|
||||
assert_eq!(
|
||||
WasmExecutor::new().call(&mut ext, 8, &test_code[..], "test_ordered_trie_root", &[]).unwrap(),
|
||||
Layout::<Blake2Hasher>::ordered_trie_root(trie_input.iter()).as_fixed_bytes().encode()
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn offchain_local_storage_should_work() {
|
||||
use substrate_client::backend::OffchainStorage;
|
||||
|
||||
let mut ext = TestExternalities::<Blake2Hasher>::default();
|
||||
let (offchain, state) = testing::TestOffchainExt::new();
|
||||
ext.set_offchain_externalities(offchain);
|
||||
let test_code = WASM_BINARY;
|
||||
assert_eq!(
|
||||
WasmExecutor::new().call(&mut ext, 8, &test_code[..], "test_offchain_local_storage", &[]).unwrap(),
|
||||
vec![0]
|
||||
);
|
||||
assert_eq!(state.read().persistent_storage.get(b"", b"test"), Some(vec![]));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn offchain_http_should_work() {
|
||||
let mut ext = TestExternalities::<Blake2Hasher>::default();
|
||||
let (offchain, state) = testing::TestOffchainExt::new();
|
||||
ext.set_offchain_externalities(offchain);
|
||||
state.write().expect_request(
|
||||
0,
|
||||
testing::PendingRequest {
|
||||
method: "POST".into(),
|
||||
uri: "http://localhost:12345".into(),
|
||||
body: vec![1, 2, 3, 4],
|
||||
headers: vec![("X-Auth".to_owned(), "test".to_owned())],
|
||||
sent: true,
|
||||
response: Some(vec![1, 2, 3]),
|
||||
response_headers: vec![("X-Auth".to_owned(), "hello".to_owned())],
|
||||
..Default::default()
|
||||
},
|
||||
);
|
||||
|
||||
let test_code = WASM_BINARY;
|
||||
assert_eq!(
|
||||
WasmExecutor::new().call(&mut ext, 8, &test_code[..], "test_offchain_http", &[]).unwrap(),
|
||||
vec![0]
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -31,24 +31,24 @@
|
||||
|
||||
#[macro_use]
|
||||
mod wasm_utils;
|
||||
mod wasm_executor;
|
||||
mod wasmi_execution;
|
||||
#[macro_use]
|
||||
mod native_executor;
|
||||
mod sandbox;
|
||||
mod allocator;
|
||||
mod wasm_runtimes_cache;
|
||||
mod host_interface;
|
||||
mod wasm_runtime;
|
||||
|
||||
pub mod error;
|
||||
pub use wasmi;
|
||||
pub use wasm_executor::WasmExecutor;
|
||||
pub use native_executor::{with_native_environment, NativeExecutor, NativeExecutionDispatch};
|
||||
pub use wasm_runtimes_cache::RuntimesCache;
|
||||
pub use runtime_version::{RuntimeVersion, NativeVersion};
|
||||
pub use codec::Codec;
|
||||
#[doc(hidden)]
|
||||
pub use primitives::{Blake2Hasher, traits::Externalities};
|
||||
#[doc(hidden)]
|
||||
pub use wasm_interface;
|
||||
pub use wasm_runtime::WasmExecutionMethod;
|
||||
|
||||
/// Provides runtime information.
|
||||
pub trait RuntimeInfo {
|
||||
|
||||
@@ -16,19 +16,20 @@
|
||||
|
||||
use std::{result, cell::RefCell, panic::UnwindSafe};
|
||||
use crate::error::{Error, Result};
|
||||
use crate::wasm_executor::WasmExecutor;
|
||||
use crate::wasm_runtime::{RuntimesCache, WasmExecutionMethod, WasmRuntime};
|
||||
use crate::RuntimeInfo;
|
||||
use runtime_version::{NativeVersion, RuntimeVersion};
|
||||
use codec::{Decode, Encode};
|
||||
use crate::RuntimeInfo;
|
||||
use primitives::{Blake2Hasher, NativeOrEncoded, traits::{CodeExecutor, Externalities}};
|
||||
use log::{trace, warn};
|
||||
|
||||
use crate::RuntimesCache;
|
||||
|
||||
thread_local! {
|
||||
static RUNTIMES_CACHE: RefCell<RuntimesCache> = RefCell::new(RuntimesCache::new());
|
||||
}
|
||||
|
||||
/// Default num of pages for the heap
|
||||
const DEFAULT_HEAP_PAGES: u64 = 1024;
|
||||
|
||||
fn safe_call<F, U>(f: F) -> Result<U>
|
||||
where F: UnwindSafe + FnOnce() -> U
|
||||
{
|
||||
@@ -65,31 +66,52 @@ pub trait NativeExecutionDispatch: Send + Sync {
|
||||
pub struct NativeExecutor<D> {
|
||||
/// Dummy field to avoid the compiler complaining about us not using `D`.
|
||||
_dummy: ::std::marker::PhantomData<D>,
|
||||
/// The fallback executor in case native isn't available.
|
||||
fallback: WasmExecutor,
|
||||
/// Method used to execute fallback Wasm code.
|
||||
fallback_method: WasmExecutionMethod,
|
||||
/// Native runtime version info.
|
||||
native_version: NativeVersion,
|
||||
/// The number of 64KB pages to allocate for Wasm execution.
|
||||
default_heap_pages: Option<u64>,
|
||||
default_heap_pages: u64,
|
||||
}
|
||||
|
||||
impl<D: NativeExecutionDispatch> NativeExecutor<D> {
|
||||
/// Create new instance.
|
||||
pub fn new(default_heap_pages: Option<u64>) -> Self {
|
||||
///
|
||||
/// # Parameters
|
||||
///
|
||||
/// `fallback_method` - Method used to execute fallback Wasm code.
|
||||
///
|
||||
/// `default_heap_pages` - Number of 64KB pages to allocate for Wasm execution.
|
||||
/// Defaults to `DEFAULT_HEAP_PAGES` if `None` is provided.
|
||||
pub fn new(fallback_method: WasmExecutionMethod, default_heap_pages: Option<u64>) -> Self {
|
||||
NativeExecutor {
|
||||
_dummy: Default::default(),
|
||||
fallback: WasmExecutor::new(),
|
||||
fallback_method,
|
||||
native_version: D::native_version(),
|
||||
default_heap_pages: default_heap_pages,
|
||||
default_heap_pages: default_heap_pages.unwrap_or(DEFAULT_HEAP_PAGES),
|
||||
}
|
||||
}
|
||||
|
||||
fn with_runtime<E, R>(
|
||||
&self,
|
||||
ext: &mut E,
|
||||
f: impl for <'a> FnOnce(&'a mut dyn WasmRuntime, &'a mut E) -> Result<R>
|
||||
) -> Result<R>
|
||||
where E: Externalities<Blake2Hasher>
|
||||
{
|
||||
RUNTIMES_CACHE.with(|cache| {
|
||||
let mut cache = cache.borrow_mut();
|
||||
let runtime = cache.fetch_runtime(ext, self.fallback_method, self.default_heap_pages)?;
|
||||
f(runtime, ext)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl<D: NativeExecutionDispatch> Clone for NativeExecutor<D> {
|
||||
fn clone(&self) -> Self {
|
||||
NativeExecutor {
|
||||
_dummy: Default::default(),
|
||||
fallback: self.fallback.clone(),
|
||||
fallback_method: self.fallback_method,
|
||||
native_version: D::native_version(),
|
||||
default_heap_pages: self.default_heap_pages,
|
||||
}
|
||||
@@ -105,17 +127,13 @@ impl<D: NativeExecutionDispatch> RuntimeInfo for NativeExecutor<D> {
|
||||
&self,
|
||||
ext: &mut E,
|
||||
) -> Option<RuntimeVersion> {
|
||||
RUNTIMES_CACHE.with(|cache| {
|
||||
let cache = &mut cache.borrow_mut();
|
||||
|
||||
match cache.fetch_runtime(&self.fallback, ext, self.default_heap_pages) {
|
||||
Ok(runtime) => runtime.version(),
|
||||
Err(e) => {
|
||||
warn!(target: "executor", "Failed to fetch runtime: {:?}", e);
|
||||
None
|
||||
}
|
||||
match self.with_runtime(ext, |runtime, _ext| Ok(runtime.version())) {
|
||||
Ok(version) => version,
|
||||
Err(e) => {
|
||||
warn!(target: "executor", "Failed to fetch runtime: {:?}", e);
|
||||
None
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -135,15 +153,9 @@ impl<D: NativeExecutionDispatch> CodeExecutor<Blake2Hasher> for NativeExecutor<D
|
||||
use_native: bool,
|
||||
native_call: Option<NC>,
|
||||
) -> (Result<NativeOrEncoded<R>>, bool){
|
||||
RUNTIMES_CACHE.with(|cache| {
|
||||
let cache = &mut cache.borrow_mut();
|
||||
let cached_runtime = match cache.fetch_runtime(
|
||||
&self.fallback, ext, self.default_heap_pages,
|
||||
) {
|
||||
Ok(cached_runtime) => cached_runtime,
|
||||
Err(e) => return (Err(e), false),
|
||||
};
|
||||
let onchain_version = cached_runtime.version();
|
||||
let mut used_native = false;
|
||||
let result = self.with_runtime(ext, |runtime, ext| {
|
||||
let onchain_version = runtime.version();
|
||||
match (
|
||||
use_native,
|
||||
onchain_version
|
||||
@@ -160,25 +172,9 @@ impl<D: NativeExecutionDispatch> CodeExecutor<Blake2Hasher> for NativeExecutor<D
|
||||
.as_ref()
|
||||
.map_or_else(||"<None>".into(), |v| format!("{}", v))
|
||||
);
|
||||
(
|
||||
cached_runtime.with(|module|
|
||||
self.fallback
|
||||
.call_in_wasm_module(ext, module, method, data)
|
||||
.map(NativeOrEncoded::Encoded)
|
||||
),
|
||||
false
|
||||
)
|
||||
}
|
||||
(false, _, _) => {
|
||||
(
|
||||
cached_runtime.with(|module|
|
||||
self.fallback
|
||||
.call_in_wasm_module(ext, module, method, data)
|
||||
.map(NativeOrEncoded::Encoded)
|
||||
),
|
||||
false
|
||||
)
|
||||
runtime.call(ext, method, data).map(NativeOrEncoded::Encoded)
|
||||
}
|
||||
(false, _, _) => runtime.call(ext, method, data).map(NativeOrEncoded::Encoded),
|
||||
(true, true, Some(call)) => {
|
||||
trace!(
|
||||
target: "executor",
|
||||
@@ -188,11 +184,13 @@ impl<D: NativeExecutionDispatch> CodeExecutor<Blake2Hasher> for NativeExecutor<D
|
||||
.as_ref()
|
||||
.map_or_else(||"<None>".into(), |v| format!("{}", v))
|
||||
);
|
||||
(
|
||||
with_native_environment(ext, move || (call)())
|
||||
.and_then(|r| r.map(NativeOrEncoded::Native).map_err(|s| Error::ApiError(s.to_string()))),
|
||||
true
|
||||
)
|
||||
|
||||
used_native = true;
|
||||
with_native_environment(ext, move || (call)())
|
||||
.and_then(|r| r
|
||||
.map(NativeOrEncoded::Native)
|
||||
.map_err(|s| Error::ApiError(s.to_string()))
|
||||
)
|
||||
}
|
||||
_ => {
|
||||
trace!(
|
||||
@@ -201,10 +199,13 @@ impl<D: NativeExecutionDispatch> CodeExecutor<Blake2Hasher> for NativeExecutor<D
|
||||
self.native_version.runtime_version,
|
||||
onchain_version.as_ref().map_or_else(||"<None>".into(), |v| format!("{}", v))
|
||||
);
|
||||
(D::dispatch(ext, method, data).map(NativeOrEncoded::Encoded), true)
|
||||
|
||||
used_native = true;
|
||||
D::dispatch(ext, method, data).map(NativeOrEncoded::Encoded)
|
||||
}
|
||||
}
|
||||
})
|
||||
});
|
||||
(result, used_native)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -586,14 +586,27 @@ impl<FR> Store<FR> {
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use primitives::{Blake2Hasher};
|
||||
use crate::wasm_executor::WasmExecutor;
|
||||
use primitives::{Blake2Hasher, traits::Externalities};
|
||||
use crate::wasm_runtime::WasmRuntime;
|
||||
use crate::wasmi_execution;
|
||||
use state_machine::TestExternalities as CoreTestExternalities;
|
||||
use wabt;
|
||||
use runtime_test::WASM_BINARY;
|
||||
|
||||
type TestExternalities<H> = CoreTestExternalities<H, u64>;
|
||||
|
||||
fn call_wasm<E: Externalities<Blake2Hasher>>(
|
||||
ext: &mut E,
|
||||
heap_pages: u64,
|
||||
code: &[u8],
|
||||
method: &str,
|
||||
data: &[u8],
|
||||
) -> Result<Vec<u8>> {
|
||||
let mut instance = wasmi_execution::create_instance(ext, code, heap_pages)
|
||||
.map_err(|err| err.to_string())?;
|
||||
instance.call(ext, method, data)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn sandbox_should_work() {
|
||||
let mut ext = TestExternalities::<Blake2Hasher>::default();
|
||||
@@ -621,7 +634,7 @@ mod tests {
|
||||
"#).unwrap();
|
||||
|
||||
assert_eq!(
|
||||
WasmExecutor::new().call(&mut ext, 8, &test_code[..], "test_sandbox", &code).unwrap(),
|
||||
call_wasm(&mut ext, 8, &test_code[..], "test_sandbox", &code).unwrap(),
|
||||
vec![1],
|
||||
);
|
||||
}
|
||||
@@ -642,7 +655,7 @@ mod tests {
|
||||
"#).unwrap();
|
||||
|
||||
assert_eq!(
|
||||
WasmExecutor::new().call(&mut ext, 8, &test_code[..], "test_sandbox", &code).unwrap(),
|
||||
call_wasm(&mut ext, 8, &test_code[..], "test_sandbox", &code).unwrap(),
|
||||
vec![0],
|
||||
);
|
||||
}
|
||||
@@ -662,7 +675,7 @@ mod tests {
|
||||
)
|
||||
"#).unwrap();
|
||||
|
||||
let res = WasmExecutor::new().call(&mut ext, 8, &test_code[..], "test_exhaust_heap", &code);
|
||||
let res = call_wasm(&mut ext, 8, &test_code[..], "test_exhaust_heap", &code);
|
||||
assert_eq!(res.is_err(), true);
|
||||
if let Err(err) = res {
|
||||
assert_eq!(
|
||||
@@ -708,7 +721,7 @@ mod tests {
|
||||
"#).unwrap();
|
||||
|
||||
assert_eq!(
|
||||
WasmExecutor::new().call(&mut ext, 8, &test_code[..], "test_sandbox", &code).unwrap(),
|
||||
call_wasm(&mut ext, 8, &test_code[..], "test_sandbox", &code).unwrap(),
|
||||
vec![1],
|
||||
);
|
||||
}
|
||||
@@ -742,7 +755,7 @@ mod tests {
|
||||
"#).unwrap();
|
||||
|
||||
assert_eq!(
|
||||
WasmExecutor::new().call(&mut ext, 8, &test_code[..], "test_sandbox_args", &code).unwrap(),
|
||||
call_wasm(&mut ext, 8, &test_code[..], "test_sandbox_args", &code).unwrap(),
|
||||
vec![1],
|
||||
);
|
||||
}
|
||||
@@ -764,7 +777,7 @@ mod tests {
|
||||
"#).unwrap();
|
||||
|
||||
assert_eq!(
|
||||
WasmExecutor::new().call(&mut ext, 8, &test_code[..], "test_sandbox_return_val", &code).unwrap(),
|
||||
call_wasm(&mut ext, 8, &test_code[..], "test_sandbox_return_val", &code).unwrap(),
|
||||
vec![1],
|
||||
);
|
||||
}
|
||||
@@ -784,7 +797,7 @@ mod tests {
|
||||
"#).unwrap();
|
||||
|
||||
assert_eq!(
|
||||
WasmExecutor::new().call(&mut ext, 8, &test_code[..], "test_sandbox_instantiate", &code).unwrap(),
|
||||
call_wasm(&mut ext, 8, &test_code[..], "test_sandbox_instantiate", &code).unwrap(),
|
||||
vec![1],
|
||||
);
|
||||
}
|
||||
@@ -798,7 +811,7 @@ mod tests {
|
||||
let code = &[0, 0, 0, 0, 1, 0, 0, 0];
|
||||
|
||||
assert_eq!(
|
||||
WasmExecutor::new().call(&mut ext, 8, &test_code[..], "test_sandbox_instantiate", code).unwrap(),
|
||||
call_wasm(&mut ext, 8, &test_code[..], "test_sandbox_instantiate", code).unwrap(),
|
||||
vec![1],
|
||||
);
|
||||
}
|
||||
@@ -821,7 +834,7 @@ mod tests {
|
||||
"#).unwrap();
|
||||
|
||||
assert_eq!(
|
||||
WasmExecutor::new().call(&mut ext, 8, &test_code[..], "test_sandbox_instantiate", &code).unwrap(),
|
||||
call_wasm(&mut ext, 8, &test_code[..], "test_sandbox_instantiate", &code).unwrap(),
|
||||
vec![0],
|
||||
);
|
||||
}
|
||||
@@ -845,7 +858,7 @@ mod tests {
|
||||
"#).unwrap();
|
||||
|
||||
assert_eq!(
|
||||
WasmExecutor::new().call(&mut ext, 8, &test_code[..], "test_sandbox_instantiate", &code).unwrap(),
|
||||
call_wasm(&mut ext, 8, &test_code[..], "test_sandbox_instantiate", &code).unwrap(),
|
||||
vec![2],
|
||||
);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,173 @@
|
||||
// Copyright 2019 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 crate::wasmi_execution;
|
||||
use log::{trace, warn};
|
||||
use codec::Decode;
|
||||
use primitives::{storage::well_known_keys, Blake2Hasher, traits::Externalities};
|
||||
use runtime_version::RuntimeVersion;
|
||||
use std::{collections::hash_map::{Entry, HashMap}};
|
||||
|
||||
/// The Substrate Wasm runtime.
|
||||
pub trait WasmRuntime {
|
||||
/// Attempt to update the number of heap pages available during execution.
|
||||
///
|
||||
/// Returns false if the update cannot be applied. The function is guaranteed to return true if
|
||||
/// the heap pages would not change from its current value.
|
||||
fn update_heap_pages(&mut self, heap_pages: u64) -> bool;
|
||||
|
||||
/// Call a method in the Substrate runtime by name. Returns the encoded result on success.
|
||||
fn call(&mut self, ext: &mut dyn Externalities<Blake2Hasher>, method: &str, data: &[u8])
|
||||
-> Result<Vec<u8>, Error>;
|
||||
|
||||
/// Returns the version of this runtime.
|
||||
///
|
||||
/// Returns `None` if the runtime doesn't provide the information or there was an error
|
||||
/// while fetching it.
|
||||
fn version(&self) -> Option<RuntimeVersion>;
|
||||
}
|
||||
|
||||
/// 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,
|
||||
}
|
||||
|
||||
/// 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, [u8; 32]), Result<Box<dyn WasmRuntime>, 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. The parameter is only needed for calling
|
||||
/// into the Wasm module to find out the `Core_version`.
|
||||
///
|
||||
/// `default_heap_pages` - Number of 64KB pages to allocate for Wasm execution.
|
||||
///
|
||||
/// # Return value
|
||||
///
|
||||
/// If no error occurred a tuple `(wasmi::ModuleRef, Option<RuntimeVersion>)` is
|
||||
/// returned. `RuntimeVersion` is contained if the call to `Core_version` returned
|
||||
/// a version.
|
||||
///
|
||||
/// 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<Blake2Hasher>>(
|
||||
&mut self,
|
||||
ext: &mut E,
|
||||
wasm_method: WasmExecutionMethod,
|
||||
default_heap_pages: u64,
|
||||
) -> Result<&mut (dyn WasmRuntime + 'static), 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.into())) {
|
||||
Entry::Occupied(o) => {
|
||||
let result = o.into_mut();
|
||||
if let Ok(ref mut cached_runtime) = result {
|
||||
if !cached_runtime.update_heap_pages(heap_pages) {
|
||||
trace!(
|
||||
target: "runtimes_cache",
|
||||
"heap_pages were changed. Reinstantiating the instance"
|
||||
);
|
||||
*result = create_wasm_runtime(ext, wasm_method, heap_pages);
|
||||
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_wasm_runtime(ext, wasm_method, heap_pages);
|
||||
if let Err(ref err) = result {
|
||||
warn!(target: "runtimes_cache", "cannot create a runtime: {:?}", err);
|
||||
}
|
||||
v.insert(result)
|
||||
}
|
||||
};
|
||||
|
||||
result.as_mut()
|
||||
.map(|runtime| runtime.as_mut())
|
||||
.map_err(|ref e| Error::InvalidCode(format!("{:?}", e)))
|
||||
}
|
||||
}
|
||||
|
||||
fn create_wasm_runtime<E: Externalities<Blake2Hasher>>(
|
||||
ext: &mut E,
|
||||
wasm_method: WasmExecutionMethod,
|
||||
heap_pages: u64,
|
||||
) -> Result<Box<dyn WasmRuntime>, WasmError> {
|
||||
let code = ext
|
||||
.original_storage(well_known_keys::CODE)
|
||||
.ok_or(WasmError::CodeNotFound)?;
|
||||
match wasm_method {
|
||||
WasmExecutionMethod::Interpreted =>
|
||||
wasmi_execution::create_instance(ext, &code, heap_pages)
|
||||
.map(|runtime| -> Box<dyn WasmRuntime> { Box::new(runtime) }),
|
||||
}
|
||||
}
|
||||
@@ -1,353 +0,0 @@
|
||||
// Copyright 2019 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/>.
|
||||
|
||||
//! Implements a cache for pre-created Wasm runtime module instances.
|
||||
|
||||
use crate::error::Error;
|
||||
use crate::wasm_executor::WasmExecutor;
|
||||
use log::{trace, warn};
|
||||
use codec::Decode;
|
||||
use parity_wasm::elements::{deserialize_buffer, DataSegment, Instruction, Module as RawModule};
|
||||
use primitives::{storage::well_known_keys, Blake2Hasher, traits::Externalities};
|
||||
use runtime_version::RuntimeVersion;
|
||||
use std::{collections::hash_map::{Entry, HashMap}, mem, rc::Rc};
|
||||
use wasmi::{Module as WasmModule, ModuleRef as WasmModuleInstanceRef, RuntimeValue};
|
||||
|
||||
#[derive(Debug)]
|
||||
enum CacheError {
|
||||
CodeNotFound,
|
||||
ApplySnapshotFailed,
|
||||
InvalidModule,
|
||||
CantDeserializeWasm,
|
||||
Instantiation(Error),
|
||||
}
|
||||
|
||||
/// A runtime along with its version and initial state snapshot.
|
||||
#[derive(Clone)]
|
||||
pub struct CachedRuntime {
|
||||
/// A wasm module instance.
|
||||
instance: WasmModuleInstanceRef,
|
||||
/// Runtime version according to `Core_version`.
|
||||
///
|
||||
/// Can be `None` if the runtime doesn't expose this function.
|
||||
version: Option<RuntimeVersion>,
|
||||
/// The snapshot of the instance's state taken just after the instantiation.
|
||||
state_snapshot: StateSnapshot,
|
||||
}
|
||||
|
||||
impl CachedRuntime {
|
||||
/// Perform an operation with the clean version of the runtime wasm instance.
|
||||
pub fn with<R, F>(&self, f: F) -> R
|
||||
where
|
||||
F: FnOnce(&WasmModuleInstanceRef) -> R,
|
||||
{
|
||||
self.state_snapshot.apply(&self.instance).expect(
|
||||
"applying the snapshot can only fail if the passed instance is different
|
||||
from the one that was used for creation of the snapshot;
|
||||
we use the snapshot that is directly associated with the instance;
|
||||
thus the snapshot was created using the instance;
|
||||
qed",
|
||||
);
|
||||
f(&self.instance)
|
||||
}
|
||||
|
||||
/// Returns the version of this cached runtime.
|
||||
///
|
||||
/// Returns `None` if the runtime doesn't provide the information or there was an error
|
||||
/// while fetching it.
|
||||
pub fn version(&self) -> Option<RuntimeVersion> {
|
||||
self.version.clone()
|
||||
}
|
||||
}
|
||||
|
||||
/// A state snapshot of an instance taken just after instantiation.
|
||||
///
|
||||
/// It is used for restoring the state of the module after execution.
|
||||
#[derive(Clone)]
|
||||
struct StateSnapshot {
|
||||
/// The offset and the content of the memory segments that should be used to restore the snapshot
|
||||
data_segments: Vec<(u32, Vec<u8>)>,
|
||||
/// The list of all global mutable variables of the module in their sequential order.
|
||||
global_mut_values: Vec<RuntimeValue>,
|
||||
heap_pages: u64,
|
||||
}
|
||||
|
||||
impl StateSnapshot {
|
||||
// Returns `None` if instance is not valid.
|
||||
fn take(
|
||||
module_instance: &WasmModuleInstanceRef,
|
||||
data_segments: Vec<DataSegment>,
|
||||
heap_pages: u64,
|
||||
) -> Option<Self> {
|
||||
let prepared_segments = data_segments
|
||||
.into_iter()
|
||||
.map(|mut segment| {
|
||||
// Just replace contents of the segment since the segments will be discarded later
|
||||
// anyway.
|
||||
let contents = mem::replace(segment.value_mut(), vec![]);
|
||||
|
||||
let init_expr = match segment.offset() {
|
||||
Some(offset) => offset.code(),
|
||||
// Return if the segment is passive
|
||||
None => return None
|
||||
};
|
||||
|
||||
// [op, End]
|
||||
if init_expr.len() != 2 {
|
||||
return None;
|
||||
}
|
||||
let offset = match init_expr[0] {
|
||||
Instruction::I32Const(v) => v as u32,
|
||||
Instruction::GetGlobal(idx) => {
|
||||
let global_val = module_instance.globals().get(idx as usize)?.get();
|
||||
match global_val {
|
||||
RuntimeValue::I32(v) => v as u32,
|
||||
_ => return None,
|
||||
}
|
||||
}
|
||||
_ => return None,
|
||||
};
|
||||
|
||||
Some((offset, contents))
|
||||
})
|
||||
.collect::<Option<Vec<_>>>()?;
|
||||
|
||||
// Collect all values of mutable globals.
|
||||
let global_mut_values = module_instance
|
||||
.globals()
|
||||
.iter()
|
||||
.filter(|g| g.is_mutable())
|
||||
.map(|g| g.get())
|
||||
.collect();
|
||||
|
||||
Some(Self {
|
||||
data_segments: prepared_segments,
|
||||
global_mut_values,
|
||||
heap_pages,
|
||||
})
|
||||
}
|
||||
|
||||
/// Reset the runtime instance to the initial version by restoring
|
||||
/// the preserved memory and globals.
|
||||
///
|
||||
/// Returns `Err` if applying the snapshot is failed.
|
||||
fn apply(&self, instance: &WasmModuleInstanceRef) -> Result<(), CacheError> {
|
||||
let memory = instance
|
||||
.export_by_name("memory")
|
||||
.ok_or(CacheError::ApplySnapshotFailed)?
|
||||
.as_memory()
|
||||
.cloned()
|
||||
.ok_or(CacheError::ApplySnapshotFailed)?;
|
||||
|
||||
// First, erase the memory and copy the data segments into it.
|
||||
memory
|
||||
.erase()
|
||||
.map_err(|_| CacheError::ApplySnapshotFailed)?;
|
||||
for (offset, contents) in &self.data_segments {
|
||||
memory
|
||||
.set(*offset, contents)
|
||||
.map_err(|_| CacheError::ApplySnapshotFailed)?;
|
||||
}
|
||||
|
||||
// Second, restore the values of mutable globals.
|
||||
for (global_ref, global_val) in instance
|
||||
.globals()
|
||||
.iter()
|
||||
.filter(|g| g.is_mutable())
|
||||
.zip(self.global_mut_values.iter())
|
||||
{
|
||||
// the instance should be the same as used for preserving and
|
||||
// we iterate the same way it as we do it for preserving values that means that the
|
||||
// types should be the same and all the values are mutable. So no error is expected/
|
||||
global_ref
|
||||
.set(*global_val)
|
||||
.map_err(|_| CacheError::ApplySnapshotFailed)?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
/// Default num of pages for the heap
|
||||
const DEFAULT_HEAP_PAGES: u64 = 1024;
|
||||
|
||||
/// Cache for the runtimes.
|
||||
///
|
||||
/// When an instance is requested for the first time it is added to this
|
||||
/// cache. Furthermore its initial memory and values of mutable globals are preserved here. 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 hash of their code.
|
||||
instances: HashMap<[u8; 32], Result<Rc<CachedRuntime>, CacheError>>,
|
||||
}
|
||||
|
||||
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
|
||||
///
|
||||
/// `wasm_executor`- Rust wasm executor. Executes the provided code in a
|
||||
/// sandboxed Wasm runtime.
|
||||
///
|
||||
/// `ext` - Externalities to use for the runtime. This is used for setting
|
||||
/// up an initial runtime instance. The parameter is only needed for calling
|
||||
/// into the Wasm module to find out the `Core_version`.
|
||||
///
|
||||
/// `default_heap_pages` - Number of 64KB pages to allocate for Wasm execution.
|
||||
/// Defaults to `DEFAULT_HEAP_PAGES` if `None` is provided.
|
||||
///
|
||||
/// # Return value
|
||||
///
|
||||
/// If no error occurred a tuple `(wasmi::ModuleRef, Option<RuntimeVersion>)` is
|
||||
/// returned. `RuntimeVersion` is contained if the call to `Core_version` returned
|
||||
/// a version.
|
||||
///
|
||||
/// 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<Blake2Hasher>>(
|
||||
&mut self,
|
||||
wasm_executor: &WasmExecutor,
|
||||
ext: &mut E,
|
||||
default_heap_pages: Option<u64>,
|
||||
) -> Result<Rc<CachedRuntime>, 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())
|
||||
.or(default_heap_pages)
|
||||
.unwrap_or(DEFAULT_HEAP_PAGES);
|
||||
|
||||
// This is direct result from fighting with borrowck.
|
||||
let handle_result =
|
||||
|cached_result: &Result<Rc<CachedRuntime>, CacheError>| match *cached_result {
|
||||
Err(ref e) => Err(Error::InvalidCode(format!("{:?}", e))),
|
||||
Ok(ref cached_runtime) => Ok(Rc::clone(cached_runtime)),
|
||||
};
|
||||
|
||||
match self.instances.entry(code_hash.into()) {
|
||||
Entry::Occupied(mut o) => {
|
||||
let result = o.get_mut();
|
||||
if let Ok(ref cached_runtime) = result {
|
||||
if cached_runtime.state_snapshot.heap_pages != heap_pages {
|
||||
trace!(
|
||||
target: "runtimes_cache",
|
||||
"heap_pages were changed. Reinstantiating the instance"
|
||||
);
|
||||
*result = Self::create_wasm_instance(wasm_executor, ext, heap_pages);
|
||||
if let Err(ref err) = result {
|
||||
warn!(target: "runtimes_cache", "cannot create a runtime: {:?}", err);
|
||||
}
|
||||
}
|
||||
}
|
||||
handle_result(result)
|
||||
},
|
||||
Entry::Vacant(v) => {
|
||||
trace!(target: "runtimes_cache", "no instance found in cache, creating now.");
|
||||
let result = Self::create_wasm_instance(
|
||||
wasm_executor,
|
||||
ext,
|
||||
heap_pages,
|
||||
);
|
||||
if let Err(ref err) = result {
|
||||
warn!(target: "runtimes_cache", "cannot create a runtime: {:?}", err);
|
||||
}
|
||||
handle_result(v.insert(result))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn create_wasm_instance<E: Externalities<Blake2Hasher>>(
|
||||
wasm_executor: &WasmExecutor,
|
||||
ext: &mut E,
|
||||
heap_pages: u64,
|
||||
) -> Result<Rc<CachedRuntime>, CacheError> {
|
||||
let code = ext
|
||||
.original_storage(well_known_keys::CODE)
|
||||
.ok_or(CacheError::CodeNotFound)?;
|
||||
let module = WasmModule::from_buffer(&code).map_err(|_| CacheError::InvalidModule)?;
|
||||
|
||||
// Extract the data segments from the wasm code.
|
||||
//
|
||||
// A return of this error actually indicates that there is a problem in logic, since
|
||||
// we just loaded and validated the `module` above.
|
||||
let data_segments = extract_data_segments(&code)?;
|
||||
|
||||
// Instantiate this module.
|
||||
let instance = WasmExecutor::instantiate_module(heap_pages as usize, &module)
|
||||
.map_err(CacheError::Instantiation)?;
|
||||
|
||||
// Take state snapshot before executing anything.
|
||||
let state_snapshot = StateSnapshot::take(&instance, data_segments, heap_pages)
|
||||
.expect(
|
||||
"`take` returns `Err` if the module is not valid;
|
||||
we already loaded module above, thus the `Module` is proven to be valid at this point;
|
||||
qed
|
||||
",
|
||||
);
|
||||
|
||||
let version = wasm_executor
|
||||
.call_in_wasm_module(ext, &instance, "Core_version", &[])
|
||||
.ok()
|
||||
.and_then(|v| RuntimeVersion::decode(&mut v.as_slice()).ok());
|
||||
Ok(Rc::new(CachedRuntime {
|
||||
instance,
|
||||
version,
|
||||
state_snapshot,
|
||||
}))
|
||||
}
|
||||
}
|
||||
|
||||
/// Extract the data segments from the given wasm code.
|
||||
///
|
||||
/// Returns `Err` if the given wasm code cannot be deserialized.
|
||||
fn extract_data_segments(wasm_code: &[u8]) -> Result<Vec<DataSegment>, CacheError> {
|
||||
let raw_module: RawModule = deserialize_buffer(wasm_code)
|
||||
.map_err(|_| CacheError::CantDeserializeWasm)?;
|
||||
|
||||
let segments = raw_module
|
||||
.data_section()
|
||||
.map(|ds| ds.entries())
|
||||
.unwrap_or(&[])
|
||||
.to_vec();
|
||||
Ok(segments)
|
||||
}
|
||||
@@ -0,0 +1,902 @@
|
||||
// Copyright 2017-2019 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/>.
|
||||
|
||||
//! Implementation of a Wasm runtime using the Wasmi interpreter.
|
||||
|
||||
use std::{str, mem};
|
||||
use wasmi::{
|
||||
Module, ModuleInstance, MemoryInstance, MemoryRef, TableRef, ImportsBuilder, ModuleRef,
|
||||
memory_units::Pages, RuntimeValue::{I32, I64, self},
|
||||
};
|
||||
use crate::error::{Error, WasmError};
|
||||
use codec::{Encode, Decode};
|
||||
use primitives::{sandbox as sandbox_primitives, Blake2Hasher, traits::Externalities};
|
||||
use crate::host_interface::SubstrateExternals;
|
||||
use crate::sandbox;
|
||||
use crate::allocator;
|
||||
use crate::wasm_runtime::WasmRuntime;
|
||||
use log::trace;
|
||||
use parity_wasm::elements::{deserialize_buffer, DataSegment, Instruction, Module as RawModule};
|
||||
use runtime_version::RuntimeVersion;
|
||||
use wasm_interface::{
|
||||
FunctionContext, HostFunctions, Pointer, WordSize, Sandbox, MemoryId, Result as WResult,
|
||||
};
|
||||
|
||||
struct FunctionExecutor {
|
||||
sandbox_store: sandbox::Store<wasmi::FuncRef>,
|
||||
heap: allocator::FreeingBumpHeapAllocator,
|
||||
memory: MemoryRef,
|
||||
table: Option<TableRef>,
|
||||
}
|
||||
|
||||
impl FunctionExecutor {
|
||||
fn new(m: MemoryRef, heap_base: u32, t: Option<TableRef>) -> Result<Self, Error> {
|
||||
Ok(FunctionExecutor {
|
||||
sandbox_store: sandbox::Store::new(),
|
||||
heap: allocator::FreeingBumpHeapAllocator::new(heap_base),
|
||||
memory: m,
|
||||
table: t,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl sandbox::SandboxCapabilities for FunctionExecutor {
|
||||
type SupervisorFuncRef = wasmi::FuncRef;
|
||||
|
||||
fn store(&self) -> &sandbox::Store<Self::SupervisorFuncRef> {
|
||||
&self.sandbox_store
|
||||
}
|
||||
fn store_mut(&mut self) -> &mut sandbox::Store<Self::SupervisorFuncRef> {
|
||||
&mut self.sandbox_store
|
||||
}
|
||||
fn allocate(&mut self, len: WordSize) -> Result<Pointer<u8>, Error> {
|
||||
let heap = &mut self.heap;
|
||||
self.memory.with_direct_access_mut(|mem| {
|
||||
heap.allocate(mem, len)
|
||||
})
|
||||
}
|
||||
fn deallocate(&mut self, ptr: Pointer<u8>) -> Result<(), Error> {
|
||||
let heap = &mut self.heap;
|
||||
self.memory.with_direct_access_mut(|mem| {
|
||||
heap.deallocate(mem, ptr)
|
||||
})
|
||||
}
|
||||
fn write_memory(&mut self, ptr: Pointer<u8>, data: &[u8]) -> Result<(), Error> {
|
||||
self.memory.set(ptr.into(), data).map_err(Into::into)
|
||||
}
|
||||
fn read_memory(&self, ptr: Pointer<u8>, len: WordSize) -> Result<Vec<u8>, Error> {
|
||||
self.memory.get(ptr.into(), len as usize).map_err(Into::into)
|
||||
}
|
||||
|
||||
fn invoke(
|
||||
&mut self,
|
||||
dispatch_thunk: &Self::SupervisorFuncRef,
|
||||
invoke_args_ptr: Pointer<u8>,
|
||||
invoke_args_len: WordSize,
|
||||
state: u32,
|
||||
func_idx: sandbox::SupervisorFuncIndex,
|
||||
) -> Result<i64, Error>
|
||||
{
|
||||
let result = wasmi::FuncInstance::invoke(
|
||||
dispatch_thunk,
|
||||
&[
|
||||
RuntimeValue::I32(u32::from(invoke_args_ptr) as i32),
|
||||
RuntimeValue::I32(invoke_args_len as i32),
|
||||
RuntimeValue::I32(state as i32),
|
||||
RuntimeValue::I32(usize::from(func_idx) as i32),
|
||||
],
|
||||
self,
|
||||
);
|
||||
match result {
|
||||
Ok(Some(RuntimeValue::I64(val))) => Ok(val),
|
||||
Ok(_) => return Err("Supervisor function returned unexpected result!".into()),
|
||||
Err(err) => Err(Error::Trap(err)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl FunctionContext for FunctionExecutor {
|
||||
fn read_memory_into(&self, address: Pointer<u8>, dest: &mut [u8]) -> WResult<()> {
|
||||
self.memory.get_into(address.into(), dest).map_err(|e| format!("{:?}", e))
|
||||
}
|
||||
|
||||
fn write_memory(&mut self, address: Pointer<u8>, data: &[u8]) -> WResult<()> {
|
||||
self.memory.set(address.into(), data).map_err(|e| format!("{:?}", e))
|
||||
}
|
||||
|
||||
fn allocate_memory(&mut self, size: WordSize) -> WResult<Pointer<u8>> {
|
||||
let heap = &mut self.heap;
|
||||
self.memory.with_direct_access_mut(|mem| {
|
||||
heap.allocate(mem, size).map_err(|e| format!("{:?}", e))
|
||||
})
|
||||
}
|
||||
|
||||
fn deallocate_memory(&mut self, ptr: Pointer<u8>) -> WResult<()> {
|
||||
let heap = &mut self.heap;
|
||||
self.memory.with_direct_access_mut(|mem| {
|
||||
heap.deallocate(mem, ptr).map_err(|e| format!("{:?}", e))
|
||||
})
|
||||
}
|
||||
|
||||
fn sandbox(&mut self) -> &mut dyn Sandbox {
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
impl Sandbox for FunctionExecutor {
|
||||
fn memory_get(
|
||||
&self,
|
||||
memory_id: MemoryId,
|
||||
offset: WordSize,
|
||||
buf_ptr: Pointer<u8>,
|
||||
buf_len: WordSize,
|
||||
) -> WResult<u32> {
|
||||
let sandboxed_memory = self.sandbox_store.memory(memory_id).map_err(|e| format!("{:?}", e))?;
|
||||
|
||||
match MemoryInstance::transfer(
|
||||
&sandboxed_memory,
|
||||
offset as usize,
|
||||
&self.memory,
|
||||
buf_ptr.into(),
|
||||
buf_len as usize,
|
||||
) {
|
||||
Ok(()) => Ok(sandbox_primitives::ERR_OK),
|
||||
Err(_) => Ok(sandbox_primitives::ERR_OUT_OF_BOUNDS),
|
||||
}
|
||||
}
|
||||
|
||||
fn memory_set(
|
||||
&mut self,
|
||||
memory_id: MemoryId,
|
||||
offset: WordSize,
|
||||
val_ptr: Pointer<u8>,
|
||||
val_len: WordSize,
|
||||
) -> WResult<u32> {
|
||||
let sandboxed_memory = self.sandbox_store.memory(memory_id).map_err(|e| format!("{:?}", e))?;
|
||||
|
||||
match MemoryInstance::transfer(
|
||||
&self.memory,
|
||||
val_ptr.into(),
|
||||
&sandboxed_memory,
|
||||
offset as usize,
|
||||
val_len as usize,
|
||||
) {
|
||||
Ok(()) => Ok(sandbox_primitives::ERR_OK),
|
||||
Err(_) => Ok(sandbox_primitives::ERR_OUT_OF_BOUNDS),
|
||||
}
|
||||
}
|
||||
|
||||
fn memory_teardown(&mut self, memory_id: MemoryId) -> WResult<()> {
|
||||
self.sandbox_store.memory_teardown(memory_id).map_err(|e| format!("{:?}", e))
|
||||
}
|
||||
|
||||
fn memory_new(
|
||||
&mut self,
|
||||
initial: u32,
|
||||
maximum: u32,
|
||||
) -> WResult<MemoryId> {
|
||||
self.sandbox_store.new_memory(initial, maximum).map_err(|e| format!("{:?}", e))
|
||||
}
|
||||
|
||||
fn invoke(
|
||||
&mut self,
|
||||
instance_id: u32,
|
||||
export_name: &str,
|
||||
args: &[u8],
|
||||
return_val: Pointer<u8>,
|
||||
return_val_len: WordSize,
|
||||
state: u32,
|
||||
) -> WResult<u32> {
|
||||
trace!(target: "sr-sandbox", "invoke, instance_idx={}", instance_id);
|
||||
|
||||
// Deserialize arguments and convert them into wasmi types.
|
||||
let args = Vec::<sandbox_primitives::TypedValue>::decode(&mut &args[..])
|
||||
.map_err(|_| "Can't decode serialized arguments for the invocation")?
|
||||
.into_iter()
|
||||
.map(Into::into)
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
let instance = self.sandbox_store.instance(instance_id).map_err(|e| format!("{:?}", e))?;
|
||||
let result = instance.invoke(export_name, &args, self, state);
|
||||
|
||||
match result {
|
||||
Ok(None) => Ok(sandbox_primitives::ERR_OK),
|
||||
Ok(Some(val)) => {
|
||||
// Serialize return value and write it back into the memory.
|
||||
sandbox_primitives::ReturnValue::Value(val.into()).using_encoded(|val| {
|
||||
if val.len() > return_val_len as usize {
|
||||
Err("Return value buffer is too small")?;
|
||||
}
|
||||
self.write_memory(return_val, val).map_err(|_| "Return value buffer is OOB")?;
|
||||
Ok(sandbox_primitives::ERR_OK)
|
||||
})
|
||||
}
|
||||
Err(_) => Ok(sandbox_primitives::ERR_EXECUTION),
|
||||
}
|
||||
}
|
||||
|
||||
fn instance_teardown(&mut self, instance_id: u32) -> WResult<()> {
|
||||
self.sandbox_store.instance_teardown(instance_id).map_err(|e| format!("{:?}", e))
|
||||
}
|
||||
|
||||
fn instance_new(
|
||||
&mut self,
|
||||
dispatch_thunk_id: u32,
|
||||
wasm: &[u8],
|
||||
raw_env_def: &[u8],
|
||||
state: u32,
|
||||
) -> WResult<u32> {
|
||||
// Extract a dispatch thunk from instance's table by the specified index.
|
||||
let dispatch_thunk = {
|
||||
let table = self.table.as_ref()
|
||||
.ok_or_else(|| "Runtime doesn't have a table; sandbox is unavailable")?;
|
||||
table.get(dispatch_thunk_id)
|
||||
.map_err(|_| "dispatch_thunk_idx is out of the table bounds")?
|
||||
.ok_or_else(|| "dispatch_thunk_idx points on an empty table entry")?
|
||||
.clone()
|
||||
};
|
||||
|
||||
let instance_idx_or_err_code =
|
||||
match sandbox::instantiate(self, dispatch_thunk, wasm, raw_env_def, state) {
|
||||
Ok(instance_idx) => instance_idx,
|
||||
Err(sandbox::InstantiationError::StartTrapped) =>
|
||||
sandbox_primitives::ERR_EXECUTION,
|
||||
Err(_) => sandbox_primitives::ERR_MODULE,
|
||||
};
|
||||
|
||||
Ok(instance_idx_or_err_code as u32)
|
||||
}
|
||||
}
|
||||
|
||||
impl FunctionExecutor {
|
||||
fn resolver() -> &'static dyn wasmi::ModuleImportResolver {
|
||||
struct Resolver;
|
||||
impl wasmi::ModuleImportResolver for Resolver {
|
||||
fn resolve_func(&self, name: &str, signature: &wasmi::Signature)
|
||||
-> std::result::Result<wasmi::FuncRef, wasmi::Error>
|
||||
{
|
||||
let signature = wasm_interface::Signature::from(signature);
|
||||
|
||||
if let Some((index, func)) = SubstrateExternals::functions().iter()
|
||||
.enumerate()
|
||||
.find(|f| name == f.1.name())
|
||||
{
|
||||
if signature == func.signature() {
|
||||
Ok(wasmi::FuncInstance::alloc_host(signature.into(), index))
|
||||
} else {
|
||||
Err(wasmi::Error::Instantiation(
|
||||
format!(
|
||||
"Invalid signature for function `{}` expected `{:?}`, got `{:?}`",
|
||||
func.name(),
|
||||
signature,
|
||||
func.signature(),
|
||||
)
|
||||
))
|
||||
}
|
||||
} else {
|
||||
Err(wasmi::Error::Instantiation(
|
||||
format!("Export {} not found", name),
|
||||
))
|
||||
}
|
||||
}
|
||||
}
|
||||
&Resolver
|
||||
}
|
||||
}
|
||||
|
||||
impl wasmi::Externals for FunctionExecutor {
|
||||
fn invoke_index(&mut self, index: usize, args: wasmi::RuntimeArgs)
|
||||
-> Result<Option<wasmi::RuntimeValue>, wasmi::Trap>
|
||||
{
|
||||
let mut args = args.as_ref().iter().copied().map(Into::into);
|
||||
let function = SubstrateExternals::functions().get(index).ok_or_else(||
|
||||
Error::from(
|
||||
format!("Could not find host function with index: {}", index),
|
||||
)
|
||||
)?;
|
||||
|
||||
function.execute(self, &mut args)
|
||||
.map_err(Error::FunctionExecution)
|
||||
.map_err(wasmi::Trap::from)
|
||||
.map(|v| v.map(Into::into))
|
||||
}
|
||||
}
|
||||
|
||||
fn get_mem_instance(module: &ModuleRef) -> Result<MemoryRef, Error> {
|
||||
Ok(module
|
||||
.export_by_name("memory")
|
||||
.ok_or_else(|| Error::InvalidMemoryReference)?
|
||||
.as_memory()
|
||||
.ok_or_else(|| Error::InvalidMemoryReference)?
|
||||
.clone())
|
||||
}
|
||||
|
||||
/// Find the global named `__heap_base` in the given wasm module instance and
|
||||
/// tries to get its value.
|
||||
fn get_heap_base(module: &ModuleRef) -> Result<u32, Error> {
|
||||
let heap_base_val = module
|
||||
.export_by_name("__heap_base")
|
||||
.ok_or_else(|| Error::HeapBaseNotFoundOrInvalid)?
|
||||
.as_global()
|
||||
.ok_or_else(|| Error::HeapBaseNotFoundOrInvalid)?
|
||||
.get();
|
||||
|
||||
match heap_base_val {
|
||||
wasmi::RuntimeValue::I32(v) => Ok(v as u32),
|
||||
_ => Err(Error::HeapBaseNotFoundOrInvalid),
|
||||
}
|
||||
}
|
||||
|
||||
/// Call a given method in the given wasm-module runtime.
|
||||
fn call_in_wasm_module(
|
||||
ext: &mut dyn Externalities<Blake2Hasher>,
|
||||
module_instance: &ModuleRef,
|
||||
method: &str,
|
||||
data: &[u8],
|
||||
) -> Result<Vec<u8>, Error> {
|
||||
call_in_wasm_module_with_custom_signature(
|
||||
ext,
|
||||
module_instance,
|
||||
method,
|
||||
|alloc| {
|
||||
let offset = alloc(data)?;
|
||||
Ok(vec![I32(offset as i32), I32(data.len() as i32)])
|
||||
},
|
||||
|res, memory| {
|
||||
if let Some(I64(r)) = res {
|
||||
let offset = r as u32;
|
||||
let length = (r as u64 >> 32) as usize;
|
||||
memory.get(offset, length).map_err(|_| Error::Runtime).map(Some)
|
||||
} else {
|
||||
Ok(None)
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
/// Call a given method in the given wasm-module runtime.
|
||||
fn call_in_wasm_module_with_custom_signature<
|
||||
F: FnOnce(&mut dyn FnMut(&[u8]) -> Result<u32, Error>) -> Result<Vec<RuntimeValue>, Error>,
|
||||
FR: FnOnce(Option<RuntimeValue>, &MemoryRef) -> Result<Option<R>, Error>,
|
||||
R,
|
||||
>(
|
||||
ext: &mut dyn Externalities<Blake2Hasher>,
|
||||
module_instance: &ModuleRef,
|
||||
method: &str,
|
||||
create_parameters: F,
|
||||
filter_result: FR,
|
||||
) -> Result<R, Error> {
|
||||
// extract a reference to a linear memory, optional reference to a table
|
||||
// and then initialize FunctionExecutor.
|
||||
let memory = get_mem_instance(module_instance)?;
|
||||
let table: Option<TableRef> = module_instance
|
||||
.export_by_name("__indirect_function_table")
|
||||
.and_then(|e| e.as_table().cloned());
|
||||
let heap_base = get_heap_base(module_instance)?;
|
||||
|
||||
let mut fec = FunctionExecutor::new(
|
||||
memory.clone(),
|
||||
heap_base,
|
||||
table,
|
||||
)?;
|
||||
|
||||
let parameters = create_parameters(&mut |data: &[u8]| {
|
||||
let offset = fec.allocate_memory(data.len() as u32)?;
|
||||
fec.write_memory(offset, data).map(|_| offset.into()).map_err(Into::into)
|
||||
})?;
|
||||
|
||||
let result = runtime_io::with_externalities(
|
||||
ext,
|
||||
|| module_instance.invoke_export(method, ¶meters, &mut fec),
|
||||
);
|
||||
|
||||
match result {
|
||||
Ok(val) => match filter_result(val, &memory)? {
|
||||
Some(val) => Ok(val),
|
||||
None => Err(Error::InvalidReturn),
|
||||
},
|
||||
Err(e) => {
|
||||
trace!(
|
||||
target: "wasm-executor",
|
||||
"Failed to execute code with {} pages",
|
||||
memory.current_size().0
|
||||
);
|
||||
Err(e.into())
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
/// Prepare module instance
|
||||
fn instantiate_module(
|
||||
heap_pages: usize,
|
||||
module: &Module,
|
||||
) -> Result<ModuleRef, Error> {
|
||||
// start module instantiation. Don't run 'start' function yet.
|
||||
let intermediate_instance = ModuleInstance::new(
|
||||
module,
|
||||
&ImportsBuilder::new()
|
||||
.with_resolver("env", FunctionExecutor::resolver())
|
||||
)?;
|
||||
|
||||
// Verify that the module has the heap base global variable.
|
||||
let _ = get_heap_base(intermediate_instance.not_started_instance())?;
|
||||
|
||||
// Extract a reference to a linear memory.
|
||||
let memory = get_mem_instance(intermediate_instance.not_started_instance())?;
|
||||
memory.grow(Pages(heap_pages)).map_err(|_| Error::Runtime)?;
|
||||
|
||||
if intermediate_instance.has_start() {
|
||||
// Runtime is not allowed to have the `start` function.
|
||||
Err(Error::RuntimeHasStartFn)
|
||||
} else {
|
||||
Ok(intermediate_instance.assert_no_start())
|
||||
}
|
||||
}
|
||||
|
||||
/// A state snapshot of an instance taken just after instantiation.
|
||||
///
|
||||
/// It is used for restoring the state of the module after execution.
|
||||
#[derive(Clone)]
|
||||
struct StateSnapshot {
|
||||
/// The offset and the content of the memory segments that should be used to restore the snapshot
|
||||
data_segments: Vec<(u32, Vec<u8>)>,
|
||||
/// The list of all global mutable variables of the module in their sequential order.
|
||||
global_mut_values: Vec<RuntimeValue>,
|
||||
heap_pages: u64,
|
||||
}
|
||||
|
||||
impl StateSnapshot {
|
||||
// Returns `None` if instance is not valid.
|
||||
fn take(
|
||||
module_instance: &ModuleRef,
|
||||
data_segments: Vec<DataSegment>,
|
||||
heap_pages: u64,
|
||||
) -> Option<Self> {
|
||||
let prepared_segments = data_segments
|
||||
.into_iter()
|
||||
.map(|mut segment| {
|
||||
// Just replace contents of the segment since the segments will be discarded later
|
||||
// anyway.
|
||||
let contents = mem::replace(segment.value_mut(), vec![]);
|
||||
|
||||
let init_expr = match segment.offset() {
|
||||
Some(offset) => offset.code(),
|
||||
// Return if the segment is passive
|
||||
None => return None
|
||||
};
|
||||
|
||||
// [op, End]
|
||||
if init_expr.len() != 2 {
|
||||
return None;
|
||||
}
|
||||
let offset = match init_expr[0] {
|
||||
Instruction::I32Const(v) => v as u32,
|
||||
Instruction::GetGlobal(idx) => {
|
||||
let global_val = module_instance.globals().get(idx as usize)?.get();
|
||||
match global_val {
|
||||
RuntimeValue::I32(v) => v as u32,
|
||||
_ => return None,
|
||||
}
|
||||
}
|
||||
_ => return None,
|
||||
};
|
||||
|
||||
Some((offset, contents))
|
||||
})
|
||||
.collect::<Option<Vec<_>>>()?;
|
||||
|
||||
// Collect all values of mutable globals.
|
||||
let global_mut_values = module_instance
|
||||
.globals()
|
||||
.iter()
|
||||
.filter(|g| g.is_mutable())
|
||||
.map(|g| g.get())
|
||||
.collect();
|
||||
|
||||
Some(Self {
|
||||
data_segments: prepared_segments,
|
||||
global_mut_values,
|
||||
heap_pages,
|
||||
})
|
||||
}
|
||||
|
||||
/// Reset the runtime instance to the initial version by restoring
|
||||
/// the preserved memory and globals.
|
||||
///
|
||||
/// Returns `Err` if applying the snapshot is failed.
|
||||
fn apply(&self, instance: &ModuleRef) -> Result<(), WasmError> {
|
||||
let memory = instance
|
||||
.export_by_name("memory")
|
||||
.ok_or(WasmError::ApplySnapshotFailed)?
|
||||
.as_memory()
|
||||
.cloned()
|
||||
.ok_or(WasmError::ApplySnapshotFailed)?;
|
||||
|
||||
// First, erase the memory and copy the data segments into it.
|
||||
memory
|
||||
.erase()
|
||||
.map_err(|_| WasmError::ApplySnapshotFailed)?;
|
||||
for (offset, contents) in &self.data_segments {
|
||||
memory
|
||||
.set(*offset, contents)
|
||||
.map_err(|_| WasmError::ApplySnapshotFailed)?;
|
||||
}
|
||||
|
||||
// Second, restore the values of mutable globals.
|
||||
for (global_ref, global_val) in instance
|
||||
.globals()
|
||||
.iter()
|
||||
.filter(|g| g.is_mutable())
|
||||
.zip(self.global_mut_values.iter())
|
||||
{
|
||||
// the instance should be the same as used for preserving and
|
||||
// we iterate the same way it as we do it for preserving values that means that the
|
||||
// types should be the same and all the values are mutable. So no error is expected/
|
||||
global_ref
|
||||
.set(*global_val)
|
||||
.map_err(|_| WasmError::ApplySnapshotFailed)?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
/// A runtime along with its version and initial state snapshot.
|
||||
#[derive(Clone)]
|
||||
pub struct WasmiRuntime {
|
||||
/// A wasm module instance.
|
||||
instance: ModuleRef,
|
||||
/// Runtime version according to `Core_version`.
|
||||
///
|
||||
/// Can be `None` if the runtime doesn't expose this function.
|
||||
version: Option<RuntimeVersion>,
|
||||
/// The snapshot of the instance's state taken just after the instantiation.
|
||||
state_snapshot: StateSnapshot,
|
||||
}
|
||||
|
||||
impl WasmiRuntime {
|
||||
/// Perform an operation with the clean version of the runtime wasm instance.
|
||||
fn with<R, F>(&self, f: F) -> R
|
||||
where
|
||||
F: FnOnce(&ModuleRef) -> R,
|
||||
{
|
||||
self.state_snapshot.apply(&self.instance).expect(
|
||||
"applying the snapshot can only fail if the passed instance is different
|
||||
from the one that was used for creation of the snapshot;
|
||||
we use the snapshot that is directly associated with the instance;
|
||||
thus the snapshot was created using the instance;
|
||||
qed",
|
||||
);
|
||||
f(&self.instance)
|
||||
}
|
||||
}
|
||||
|
||||
impl WasmRuntime for WasmiRuntime {
|
||||
fn update_heap_pages(&mut self, heap_pages: u64) -> bool {
|
||||
self.state_snapshot.heap_pages == heap_pages
|
||||
}
|
||||
|
||||
fn call(&mut self, ext: &mut dyn Externalities<Blake2Hasher>, method: &str, data: &[u8])
|
||||
-> Result<Vec<u8>, Error>
|
||||
{
|
||||
self.with(|module| {
|
||||
call_in_wasm_module(ext, module, method, data)
|
||||
})
|
||||
}
|
||||
|
||||
fn version(&self) -> Option<RuntimeVersion> {
|
||||
self.version.clone()
|
||||
}
|
||||
}
|
||||
|
||||
pub fn create_instance<E: Externalities<Blake2Hasher>>(ext: &mut E, code: &[u8], heap_pages: u64)
|
||||
-> Result<WasmiRuntime, WasmError>
|
||||
{
|
||||
let module = Module::from_buffer(&code).map_err(|_| WasmError::InvalidModule)?;
|
||||
|
||||
// Extract the data segments from the wasm code.
|
||||
//
|
||||
// A return of this error actually indicates that there is a problem in logic, since
|
||||
// we just loaded and validated the `module` above.
|
||||
let data_segments = extract_data_segments(&code)?;
|
||||
|
||||
// Instantiate this module.
|
||||
let instance = instantiate_module(heap_pages as usize, &module)
|
||||
.map_err(WasmError::Instantiation)?;
|
||||
|
||||
// Take state snapshot before executing anything.
|
||||
let state_snapshot = StateSnapshot::take(&instance, data_segments, heap_pages)
|
||||
.expect(
|
||||
"`take` returns `Err` if the module is not valid;
|
||||
we already loaded module above, thus the `Module` is proven to be valid at this point;
|
||||
qed
|
||||
",
|
||||
);
|
||||
|
||||
let version = call_in_wasm_module(ext, &instance, "Core_version", &[])
|
||||
.ok()
|
||||
.and_then(|v| RuntimeVersion::decode(&mut v.as_slice()).ok());
|
||||
Ok(WasmiRuntime {
|
||||
instance,
|
||||
version,
|
||||
state_snapshot,
|
||||
})
|
||||
}
|
||||
|
||||
/// Extract the data segments from the given wasm code.
|
||||
///
|
||||
/// Returns `Err` if the given wasm code cannot be deserialized.
|
||||
fn extract_data_segments(wasm_code: &[u8]) -> Result<Vec<DataSegment>, WasmError> {
|
||||
let raw_module: RawModule = deserialize_buffer(wasm_code)
|
||||
.map_err(|_| WasmError::CantDeserializeWasm)?;
|
||||
|
||||
let segments = raw_module
|
||||
.data_section()
|
||||
.map(|ds| ds.entries())
|
||||
.unwrap_or(&[])
|
||||
.to_vec();
|
||||
Ok(segments)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
use state_machine::TestExternalities as CoreTestExternalities;
|
||||
use hex_literal::hex;
|
||||
use primitives::{blake2_128, blake2_256, ed25519, sr25519, map, Pair};
|
||||
use runtime_test::WASM_BINARY;
|
||||
use substrate_offchain::testing;
|
||||
use trie::{TrieConfiguration, trie_types::Layout};
|
||||
|
||||
type TestExternalities<H> = CoreTestExternalities<H, u64>;
|
||||
|
||||
fn call<E: Externalities<Blake2Hasher>>(
|
||||
ext: &mut E,
|
||||
heap_pages: u64,
|
||||
code: &[u8],
|
||||
method: &str,
|
||||
data: &[u8],
|
||||
) -> Result<Vec<u8>, Error> {
|
||||
let mut instance = create_instance(ext, code, heap_pages)
|
||||
.map_err(|err| err.to_string())?;
|
||||
instance.call(ext, method, data)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn returning_should_work() {
|
||||
let mut ext = TestExternalities::default();
|
||||
let test_code = WASM_BINARY;
|
||||
|
||||
let output = call(&mut ext, 8, &test_code[..], "test_empty_return", &[]).unwrap();
|
||||
assert_eq!(output, vec![0u8; 0]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn panicking_should_work() {
|
||||
let mut ext = TestExternalities::default();
|
||||
let test_code = WASM_BINARY;
|
||||
|
||||
let output = call(&mut ext, 8, &test_code[..], "test_panic", &[]);
|
||||
assert!(output.is_err());
|
||||
|
||||
let output = call(&mut ext, 8, &test_code[..], "test_conditional_panic", &[]);
|
||||
assert_eq!(output.unwrap(), vec![0u8; 0]);
|
||||
|
||||
let output = call(&mut ext, 8, &test_code[..], "test_conditional_panic", &[2]);
|
||||
assert!(output.is_err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn storage_should_work() {
|
||||
let mut ext = TestExternalities::default();
|
||||
ext.set_storage(b"foo".to_vec(), b"bar".to_vec());
|
||||
let test_code = WASM_BINARY;
|
||||
|
||||
let output = call(&mut ext, 8, &test_code[..], "test_data_in", b"Hello world").unwrap();
|
||||
|
||||
assert_eq!(output, b"all ok!".to_vec());
|
||||
|
||||
let expected = TestExternalities::new((map![
|
||||
b"input".to_vec() => b"Hello world".to_vec(),
|
||||
b"foo".to_vec() => b"bar".to_vec(),
|
||||
b"baz".to_vec() => b"bar".to_vec()
|
||||
], map![]));
|
||||
assert_eq!(ext, expected);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn clear_prefix_should_work() {
|
||||
let mut ext = TestExternalities::default();
|
||||
ext.set_storage(b"aaa".to_vec(), b"1".to_vec());
|
||||
ext.set_storage(b"aab".to_vec(), b"2".to_vec());
|
||||
ext.set_storage(b"aba".to_vec(), b"3".to_vec());
|
||||
ext.set_storage(b"abb".to_vec(), b"4".to_vec());
|
||||
ext.set_storage(b"bbb".to_vec(), b"5".to_vec());
|
||||
let test_code = WASM_BINARY;
|
||||
|
||||
// This will clear all entries which prefix is "ab".
|
||||
let output = call(&mut ext, 8, &test_code[..], "test_clear_prefix", b"ab").unwrap();
|
||||
|
||||
assert_eq!(output, b"all ok!".to_vec());
|
||||
|
||||
let expected = TestExternalities::new((map![
|
||||
b"aaa".to_vec() => b"1".to_vec(),
|
||||
b"aab".to_vec() => b"2".to_vec(),
|
||||
b"bbb".to_vec() => b"5".to_vec()
|
||||
], map![]));
|
||||
assert_eq!(expected, ext);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn blake2_256_should_work() {
|
||||
let mut ext = TestExternalities::default();
|
||||
let test_code = WASM_BINARY;
|
||||
assert_eq!(
|
||||
call(&mut ext, 8, &test_code[..], "test_blake2_256", &[]).unwrap(),
|
||||
blake2_256(&b""[..]).encode()
|
||||
);
|
||||
assert_eq!(
|
||||
call(&mut ext, 8, &test_code[..], "test_blake2_256", b"Hello world!").unwrap(),
|
||||
blake2_256(&b"Hello world!"[..]).encode()
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn blake2_128_should_work() {
|
||||
let mut ext = TestExternalities::default();
|
||||
let test_code = WASM_BINARY;
|
||||
assert_eq!(
|
||||
call(&mut ext, 8, &test_code[..], "test_blake2_128", &[]).unwrap(),
|
||||
blake2_128(&b""[..]).encode()
|
||||
);
|
||||
assert_eq!(
|
||||
call(&mut ext, 8, &test_code[..], "test_blake2_128", b"Hello world!").unwrap(),
|
||||
blake2_128(&b"Hello world!"[..]).encode()
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn twox_256_should_work() {
|
||||
let mut ext = TestExternalities::default();
|
||||
let test_code = WASM_BINARY;
|
||||
assert_eq!(
|
||||
call(&mut ext, 8, &test_code[..], "test_twox_256", &[]).unwrap(),
|
||||
hex!("99e9d85137db46ef4bbea33613baafd56f963c64b1f3685a4eb4abd67ff6203a"),
|
||||
);
|
||||
assert_eq!(
|
||||
call(&mut ext, 8, &test_code[..], "test_twox_256", b"Hello world!").unwrap(),
|
||||
hex!("b27dfd7f223f177f2a13647b533599af0c07f68bda23d96d059da2b451a35a74"),
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn twox_128_should_work() {
|
||||
let mut ext = TestExternalities::default();
|
||||
let test_code = WASM_BINARY;
|
||||
assert_eq!(
|
||||
call(&mut ext, 8, &test_code[..], "test_twox_128", &[]).unwrap(),
|
||||
hex!("99e9d85137db46ef4bbea33613baafd5")
|
||||
);
|
||||
assert_eq!(
|
||||
call(&mut ext, 8, &test_code[..], "test_twox_128", b"Hello world!").unwrap(),
|
||||
hex!("b27dfd7f223f177f2a13647b533599af")
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn ed25519_verify_should_work() {
|
||||
let mut ext = TestExternalities::<Blake2Hasher>::default();
|
||||
let test_code = WASM_BINARY;
|
||||
let key = ed25519::Pair::from_seed(&blake2_256(b"test"));
|
||||
let sig = key.sign(b"all ok!");
|
||||
let mut calldata = vec![];
|
||||
calldata.extend_from_slice(key.public().as_ref());
|
||||
calldata.extend_from_slice(sig.as_ref());
|
||||
|
||||
assert_eq!(
|
||||
call(&mut ext, 8, &test_code[..], "test_ed25519_verify", &calldata).unwrap(),
|
||||
vec![1]
|
||||
);
|
||||
|
||||
let other_sig = key.sign(b"all is not ok!");
|
||||
let mut calldata = vec![];
|
||||
calldata.extend_from_slice(key.public().as_ref());
|
||||
calldata.extend_from_slice(other_sig.as_ref());
|
||||
|
||||
assert_eq!(
|
||||
call(&mut ext, 8, &test_code[..], "test_ed25519_verify", &calldata).unwrap(),
|
||||
vec![0]
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn sr25519_verify_should_work() {
|
||||
let mut ext = TestExternalities::<Blake2Hasher>::default();
|
||||
let test_code = WASM_BINARY;
|
||||
let key = sr25519::Pair::from_seed(&blake2_256(b"test"));
|
||||
let sig = key.sign(b"all ok!");
|
||||
let mut calldata = vec![];
|
||||
calldata.extend_from_slice(key.public().as_ref());
|
||||
calldata.extend_from_slice(sig.as_ref());
|
||||
|
||||
assert_eq!(
|
||||
call(&mut ext, 8, &test_code[..], "test_sr25519_verify", &calldata).unwrap(),
|
||||
vec![1]
|
||||
);
|
||||
|
||||
let other_sig = key.sign(b"all is not ok!");
|
||||
let mut calldata = vec![];
|
||||
calldata.extend_from_slice(key.public().as_ref());
|
||||
calldata.extend_from_slice(other_sig.as_ref());
|
||||
|
||||
assert_eq!(
|
||||
call(&mut ext, 8, &test_code[..], "test_sr25519_verify", &calldata).unwrap(),
|
||||
vec![0]
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn ordered_trie_root_should_work() {
|
||||
let mut ext = TestExternalities::<Blake2Hasher>::default();
|
||||
let trie_input = vec![b"zero".to_vec(), b"one".to_vec(), b"two".to_vec()];
|
||||
let test_code = WASM_BINARY;
|
||||
assert_eq!(
|
||||
call(&mut ext, 8, &test_code[..], "test_ordered_trie_root", &[]).unwrap(),
|
||||
Layout::<Blake2Hasher>::ordered_trie_root(trie_input.iter()).as_fixed_bytes().encode()
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn offchain_local_storage_should_work() {
|
||||
use substrate_client::backend::OffchainStorage;
|
||||
|
||||
let mut ext = TestExternalities::<Blake2Hasher>::default();
|
||||
let (offchain, state) = testing::TestOffchainExt::new();
|
||||
ext.set_offchain_externalities(offchain);
|
||||
let test_code = WASM_BINARY;
|
||||
assert_eq!(
|
||||
call(&mut ext, 8, &test_code[..], "test_offchain_local_storage", &[]).unwrap(),
|
||||
vec![0]
|
||||
);
|
||||
assert_eq!(state.read().persistent_storage.get(b"", b"test"), Some(vec![]));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn offchain_http_should_work() {
|
||||
let mut ext = TestExternalities::<Blake2Hasher>::default();
|
||||
let (offchain, state) = testing::TestOffchainExt::new();
|
||||
ext.set_offchain_externalities(offchain);
|
||||
state.write().expect_request(
|
||||
0,
|
||||
testing::PendingRequest {
|
||||
method: "POST".into(),
|
||||
uri: "http://localhost:12345".into(),
|
||||
body: vec![1, 2, 3, 4],
|
||||
headers: vec![("X-Auth".to_owned(), "test".to_owned())],
|
||||
sent: true,
|
||||
response: Some(vec![1, 2, 3]),
|
||||
response_headers: vec![("X-Auth".to_owned(), "hello".to_owned())],
|
||||
..Default::default()
|
||||
},
|
||||
);
|
||||
|
||||
let test_code = WASM_BINARY;
|
||||
assert_eq!(
|
||||
call(&mut ext, 8, &test_code[..], "test_offchain_http", &[]).unwrap(),
|
||||
vec![0]
|
||||
);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user