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:
Jim Posen
2019-10-08 12:57:12 +02:00
committed by GitHub
parent 2c77262c8f
commit 6cebbbf8b2
22 changed files with 1359 additions and 1237 deletions
+15
View File
@@ -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),
}
@@ -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, &parameters, &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]
);
}
}
+4 -4
View File
@@ -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 {
+56 -55
View File
@@ -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)
}
}
+25 -12
View File
@@ -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],
);
}
+173
View File
@@ -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, &parameters, &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]
);
}
}