From 6cebbbf8b2c6574ffb89bd1da5423adf1b2c93eb Mon Sep 17 00:00:00 2001 From: Jim Posen Date: Tue, 8 Oct 2019 12:57:12 +0200 Subject: [PATCH] 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. --- substrate/core/cli/src/lib.rs | 3 + substrate/core/cli/src/params.rs | 43 +- substrate/core/client/src/genesis.rs | 2 +- substrate/core/client/src/lib.rs | 4 +- .../core/client/src/light/call_executor.rs | 12 +- substrate/core/client/src/light/fetcher.rs | 40 +- substrate/core/executor/src/error.rs | 15 + .../{wasm_executor.rs => host_interface.rs} | 803 +--------------- substrate/core/executor/src/lib.rs | 8 +- .../core/executor/src/native_executor.rs | 111 +-- substrate/core/executor/src/sandbox.rs | 37 +- substrate/core/executor/src/wasm_runtime.rs | 173 ++++ .../core/executor/src/wasm_runtimes_cache.rs | 353 ------- .../core/executor/src/wasmi_execution.rs | 902 ++++++++++++++++++ substrate/core/service/src/builder.rs | 10 +- substrate/core/service/src/config.rs | 4 + substrate/core/service/test/src/lib.rs | 1 + .../core/sr-api-macros/tests/runtime_calls.rs | 2 +- substrate/core/test-client/src/lib.rs | 12 +- substrate/core/test-runtime/client/src/lib.rs | 4 +- substrate/core/test-runtime/src/system.rs | 51 +- substrate/node/executor/src/lib.rs | 6 +- 22 files changed, 1359 insertions(+), 1237 deletions(-) rename substrate/core/executor/src/{wasm_executor.rs => host_interface.rs} (61%) create mode 100644 substrate/core/executor/src/wasm_runtime.rs delete mode 100644 substrate/core/executor/src/wasm_runtimes_cache.rs create mode 100644 substrate/core/executor/src/wasmi_execution.rs diff --git a/substrate/core/cli/src/lib.rs b/substrate/core/cli/src/lib.rs index 2e7619116c..d4bb4f6f0d 100644 --- a/substrate/core/cli/src/lib.rs +++ b/substrate/core/cli/src/lib.rs @@ -380,6 +380,7 @@ impl<'a> ParseAndPrepareImport<'a> { Exit: IntoExit { let mut config = create_config_with_db_path(spec_factory, &self.params.shared_params, self.version)?; + config.wasm_method = self.params.wasm_method.into(); config.execution_strategies = ExecutionStrategies { importing: self.params.execution.into(), other: self.params.execution.into(), @@ -692,6 +693,8 @@ where service::Roles::FULL }; + config.wasm_method = cli.wasm_method.into(); + let exec = cli.execution_strategies; let exec_all_or = |strat: params::ExecutionStrategy| exec.execution.unwrap_or(strat).into(); config.execution_strategies = ExecutionStrategies { diff --git a/substrate/core/cli/src/params.rs b/substrate/core/cli/src/params.rs index 6656e1653f..b453c7dabd 100644 --- a/substrate/core/cli/src/params.rs +++ b/substrate/core/cli/src/params.rs @@ -18,7 +18,6 @@ use crate::traits::{AugmentClap, GetLogFilter}; use std::path::PathBuf; use structopt::{StructOpt, clap::{arg_enum, App, AppSettings, _clap_count_exprs, SubCommand, Arg}}; -use client; pub use crate::execution_strategy::ExecutionStrategy; @@ -44,6 +43,24 @@ impl Into for ExecutionStrategy { } } +arg_enum! { + /// How to execute Wasm runtime code + #[allow(missing_docs)] + #[derive(Debug, Clone)] + pub enum WasmExecutionMethod { + // Uses an interpreter. + Interpreted, + } +} + +impl Into for WasmExecutionMethod { + fn into(self) -> service::config::WasmExecutionMethod { + match self { + WasmExecutionMethod::Interpreted => service::config::WasmExecutionMethod::Interpreted, + } + } +} + arg_enum! { /// Whether off-chain workers are enabled. #[allow(missing_docs)] @@ -404,6 +421,18 @@ pub struct RunCmd { )] pub offchain_worker: OffchainWorkerEnabled, + /// Method for executing Wasm runtime code. + #[structopt( + long = "wasm-execution", + value_name = "METHOD", + raw( + possible_values = "&WasmExecutionMethod::variants()", + case_insensitive = "true", + default_value = r#""Interpreted""# + ) + )] + pub wasm_method: WasmExecutionMethod, + #[allow(missing_docs)] #[structopt(flatten)] pub execution_strategies: ExecutionStrategies, @@ -651,6 +680,18 @@ pub struct ImportBlocksCmd { #[structopt(flatten)] pub shared_params: SharedParams, + /// Method for executing Wasm runtime code. + #[structopt( + long = "wasm-execution", + value_name = "METHOD", + raw( + possible_values = "&WasmExecutionMethod::variants()", + case_insensitive = "true", + default_value = r#""Interpreted""# + ) + )] + pub wasm_method: WasmExecutionMethod, + /// The means of execution used when calling into the runtime while importing blocks. #[structopt( long = "execution", diff --git a/substrate/core/client/src/genesis.rs b/substrate/core/client/src/genesis.rs index 0c0d49f8ea..51e0536613 100644 --- a/substrate/core/client/src/genesis.rs +++ b/substrate/core/client/src/genesis.rs @@ -63,7 +63,7 @@ mod tests { ); fn executor() -> executor::NativeExecutor { - executor::NativeExecutor::new(None) + executor::NativeExecutor::new(executor::WasmExecutionMethod::Interpreted, None) } fn construct_block( diff --git a/substrate/core/client/src/lib.rs b/substrate/core/client/src/lib.rs index 70c9b75926..5a2cb746f3 100644 --- a/substrate/core/client/src/lib.rs +++ b/substrate/core/client/src/lib.rs @@ -49,7 +49,7 @@ //! use substrate_client::{Client, in_mem::Backend, LocalCallExecutor}; //! use primitives::Blake2Hasher; //! use sr_primitives::{StorageOverlay, ChildrenStorageOverlay}; -//! use executor::NativeExecutor; +//! use executor::{NativeExecutor, WasmExecutionMethod}; //! //! // In this example, we're using the `Block` and `RuntimeApi` types from the //! // `substrate-test-runtime-client` crate. These types are automatically generated when @@ -62,7 +62,7 @@ //! backend.clone(), //! LocalCallExecutor::new( //! backend.clone(), -//! NativeExecutor::::new(None), +//! NativeExecutor::::new(WasmExecutionMethod::Interpreted, None), //! None, //! ), //! // This parameter provides the storage for the chain genesis. diff --git a/substrate/core/client/src/light/call_executor.rs b/substrate/core/client/src/light/call_executor.rs index ec182cca11..d969c39a5a 100644 --- a/substrate/core/client/src/light/call_executor.rs +++ b/substrate/core/client/src/light/call_executor.rs @@ -304,7 +304,7 @@ mod tests { use consensus::BlockOrigin; use primitives::offchain::NeverOffchainExt; use test_client::{self, runtime::{Header, Digest, Block}, ClientExt, TestClient}; - use executor::NativeExecutor; + use executor::{NativeExecutor, WasmExecutionMethod}; use crate::backend::{Backend, NewBlockState}; use crate::in_mem::Backend as InMemBackend; use super::*; @@ -399,6 +399,10 @@ mod tests { } } + fn local_executor() -> NativeExecutor { + NativeExecutor::new(WasmExecutionMethod::Interpreted, None) + } + #[test] fn execution_proof_is_generated_and_checked() { fn execute(remote_client: &TestClient, at: u64, method: &'static str) -> (Vec, Vec) { @@ -413,8 +417,7 @@ mod tests { ).unwrap(); // check remote execution proof locally - let local_executor = NativeExecutor::::new(None); - let local_result = check_execution_proof(&local_executor, &RemoteCallRequest { + let local_result = check_execution_proof(&local_executor(), &RemoteCallRequest { block: test_client::runtime::Hash::default(), header: remote_header, method: method.into(), @@ -437,9 +440,8 @@ mod tests { ).unwrap(); // check remote execution proof locally - let local_executor = NativeExecutor::::new(None); let execution_result = check_execution_proof_with_make_header( - &local_executor, + &local_executor(), &RemoteCallRequest { block: test_client::runtime::Hash::default(), header: remote_header, diff --git a/substrate/core/client/src/light/fetcher.rs b/substrate/core/client/src/light/fetcher.rs index 3c4387209a..51e1ed3b81 100644 --- a/substrate/core/client/src/light/fetcher.rs +++ b/substrate/core/client/src/light/fetcher.rs @@ -503,7 +503,7 @@ pub mod tests { use parking_lot::Mutex; use codec::Decode; use crate::client::tests::prepare_client_with_key_changes; - use executor::{self, NativeExecutor}; + use executor::{NativeExecutor, WasmExecutionMethod}; use crate::error::Error as ClientError; use test_client::{ self, ClientExt, blockchain::HeaderBackend, AccountKeyring, @@ -563,12 +563,16 @@ pub mod tests { } type TestChecker = LightDataChecker< - executor::NativeExecutor, + NativeExecutor, Blake2Hasher, Block, DummyStorage, >; + fn local_executor() -> NativeExecutor { + NativeExecutor::new(WasmExecutionMethod::Interpreted, None) + } + fn prepare_for_read_proof_check() -> (TestChecker, Header, Vec>, u32) { // prepare remote client let remote_client = test_client::new(); @@ -596,8 +600,10 @@ pub mod tests { None, crate::backend::NewBlockState::Final, ).unwrap(); - let local_executor = NativeExecutor::::new(None); - let local_checker = LightDataChecker::new(Arc::new(DummyBlockchain::new(DummyStorage::new())), local_executor); + let local_checker = LightDataChecker::new( + Arc::new(DummyBlockchain::new(DummyStorage::new())), + local_executor() + ); (local_checker, remote_block_header, remote_read_proof, heap_pages) } @@ -636,8 +642,10 @@ pub mod tests { None, crate::backend::NewBlockState::Final, ).unwrap(); - let local_executor = NativeExecutor::::new(None); - let local_checker = LightDataChecker::new(Arc::new(DummyBlockchain::new(DummyStorage::new())), local_executor); + let local_checker = LightDataChecker::new( + Arc::new(DummyBlockchain::new(DummyStorage::new())), + local_executor(), + ); (local_checker, remote_block_header, remote_read_proof, child_value) } @@ -662,8 +670,10 @@ pub mod tests { if insert_cht { local_storage.insert_cht_root(1, local_cht_root); } - let local_executor = NativeExecutor::::new(None); - let local_checker = LightDataChecker::new(Arc::new(DummyBlockchain::new(DummyStorage::new())), local_executor); + let local_checker = LightDataChecker::new( + Arc::new(DummyBlockchain::new(DummyStorage::new())), + local_executor(), + ); (local_checker, local_cht_root, remote_block_header, remote_header_proof) } @@ -744,7 +754,7 @@ pub mod tests { let (remote_client, local_roots, test_cases) = prepare_client_with_key_changes(); let local_checker = TestChecker::new( Arc::new(DummyBlockchain::new(DummyStorage::new())), - NativeExecutor::::new(None) + local_executor(), ); let local_checker = &local_checker as &dyn FetchChecker; let max = remote_client.info().chain.best_number; @@ -813,7 +823,7 @@ pub mod tests { local_storage.changes_tries_cht_roots.insert(0, local_cht_root); let local_checker = TestChecker::new( Arc::new(DummyBlockchain::new(local_storage)), - NativeExecutor::::new(None) + local_executor(), ); // check proof on local client @@ -842,7 +852,7 @@ pub mod tests { let (remote_client, local_roots, test_cases) = prepare_client_with_key_changes(); let local_checker = TestChecker::new( Arc::new(DummyBlockchain::new(DummyStorage::new())), - NativeExecutor::::new(None) + local_executor(), ); let local_checker = &local_checker as &dyn FetchChecker; let max = remote_client.info().chain.best_number; @@ -924,7 +934,7 @@ pub mod tests { // fails when changes trie CHT is missing from the local db let local_checker = TestChecker::new( Arc::new(DummyBlockchain::new(DummyStorage::new())), - NativeExecutor::::new(None) + local_executor(), ); assert!(local_checker.check_changes_tries_proof(4, &remote_proof.roots, remote_proof.roots_proof.clone()).is_err()); @@ -934,7 +944,7 @@ pub mod tests { local_storage.changes_tries_cht_roots.insert(0, local_cht_root); let local_checker = TestChecker::new( Arc::new(DummyBlockchain::new(local_storage)), - NativeExecutor::::new(None) + local_executor(), ); assert!(local_checker.check_changes_tries_proof(4, &remote_proof.roots, vec![]).is_err()); } @@ -948,7 +958,7 @@ pub mod tests { let local_checker = TestChecker::new( Arc::new(DummyBlockchain::new(DummyStorage::new())), - NativeExecutor::::new(None) + local_executor(), ); let body_request = RemoteBodyRequest { @@ -971,7 +981,7 @@ pub mod tests { let local_checker = TestChecker::new( Arc::new(DummyBlockchain::new(DummyStorage::new())), - NativeExecutor::::new(None) + local_executor(), ); let body_request = RemoteBodyRequest { diff --git a/substrate/core/executor/src/error.rs b/substrate/core/executor/src/error.rs index c5148241ee..6b3c45ee49 100644 --- a/substrate/core/executor/src/error.rs +++ b/substrate/core/executor/src/error.rs @@ -98,3 +98,18 @@ impl From 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), +} diff --git a/substrate/core/executor/src/wasm_executor.rs b/substrate/core/executor/src/host_interface.rs similarity index 61% rename from substrate/core/executor/src/wasm_executor.rs rename to substrate/core/executor/src/host_interface.rs index 6ded0adad6..7c99415f6c 100644 --- a/substrate/core/executor/src/wasm_executor.rs +++ b/substrate/core/executor/src/host_interface.rs @@ -14,33 +14,20 @@ // You should have received a copy of the GNU General Public License // along with Substrate. If not, see . -//! 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, - heap: allocator::FreeingBumpHeapAllocator, - memory: MemoryRef, - table: Option, -} - -impl FunctionExecutor { - fn new(m: MemoryRef, heap_base: u32, t: Option) -> Result { - 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.sandbox_store - } - fn store_mut(&mut self) -> &mut sandbox::Store { - &mut self.sandbox_store - } - fn allocate(&mut self, len: WordSize) -> Result> { - let heap = &mut self.heap; - self.memory.with_direct_access_mut(|mem| { - heap.allocate(mem, len) - }) - } - fn deallocate(&mut self, ptr: Pointer) -> Result<()> { - let heap = &mut self.heap; - self.memory.with_direct_access_mut(|mem| { - heap.deallocate(mem, ptr) - }) - } - fn write_memory(&mut self, ptr: Pointer, data: &[u8]) -> Result<()> { - self.memory.set(ptr.into(), data).map_err(Into::into) - } - fn read_memory(&self, ptr: Pointer, len: WordSize) -> Result> { - self.memory.get(ptr.into(), len as usize).map_err(Into::into) - } - - fn invoke( - &mut self, - dispatch_thunk: &Self::SupervisorFuncRef, - invoke_args_ptr: Pointer, - invoke_args_len: WordSize, - state: u32, - func_idx: sandbox::SupervisorFuncIndex, - ) -> Result - { - 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, dest: &mut [u8]) -> WResult<()> { - self.memory.get_into(address.into(), dest).map_err(|e| format!("{:?}", e)) - } - - fn write_memory(&mut self, address: Pointer, data: &[u8]) -> WResult<()> { - self.memory.set(address.into(), data).map_err(|e| format!("{:?}", e)) - } - - fn allocate_memory(&mut self, size: WordSize) -> WResult> { - 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) -> 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, - buf_len: WordSize, - ) -> WResult { - 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, - val_len: WordSize, - ) -> WResult { - 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 { - 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, - return_val_len: WordSize, - state: u32, - ) -> WResult { - trace!(target: "sr-sandbox", "invoke, instance_idx={}", instance_id); - - // Deserialize arguments and convert them into wasmi types. - let args = Vec::::decode(&mut &args[..]) - .map_err(|_| "Can't decode serialized arguments for the invocation")? - .into_iter() - .map(Into::into) - .collect::>(); - - 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 { - // 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 { - fn write_primitive(&mut self, ptr: Pointer, t: T) -> WResult<()>; -} - -impl WritePrimitive for &mut dyn FunctionContext { - fn write_primitive(&mut self, ptr: Pointer, t: u32) -> WResult<()> { - let r = t.to_le_bytes(); - self.write_memory(ptr.cast(), &r) - } -} - -trait ReadPrimitive { - fn read_primitive(&self, offset: Pointer) -> WResult; -} - -impl ReadPrimitive for &mut dyn FunctionContext { - fn read_primitive(&self, ptr: Pointer) -> WResult { - 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 { - 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 - { - 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, 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 { + fn write_primitive(&mut self, ptr: Pointer, t: T) -> WResult<()>; +} + +impl WritePrimitive for &mut dyn FunctionContext { + fn write_primitive(&mut self, ptr: Pointer, t: u32) -> WResult<()> { + let r = t.to_le_bytes(); + self.write_memory(ptr.cast(), &r) + } +} + +trait ReadPrimitive { + fn read_primitive(&self, offset: Pointer) -> WResult; +} + +impl ReadPrimitive for &mut dyn FunctionContext { + fn read_primitive(&self, ptr: Pointer) -> WResult { + 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(f: F) -> std::result::Result .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>( - &self, - ext: &mut E, - heap_pages: usize, - code: &[u8], - method: &str, - data: &[u8], - ) -> Result> { - 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, - F: FnOnce(&mut dyn FnMut(&[u8]) -> Result) -> Result>, - FR: FnOnce(Option, &MemoryRef) -> Result>, - R, - >( - &self, - ext: &mut E, - heap_pages: usize, - code: &[u8], - method: &str, - create_parameters: F, - filter_result: FR, - ) -> Result { - 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 { - 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 { - 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>( - &self, - ext: &mut E, - module_instance: &ModuleRef, - method: &str, - data: &[u8], - ) -> Result> { - 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, - F: FnOnce(&mut dyn FnMut(&[u8]) -> Result) -> Result>, - FR: FnOnce(Option, &MemoryRef) -> Result>, - R, - >( - &self, - ext: &mut E, - module_instance: &ModuleRef, - method: &str, - create_parameters: F, - filter_result: FR, - ) -> Result { - // 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 = 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 { - // 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 { + 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 = CoreTestExternalities; - - #[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::::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::::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::::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::::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::::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::::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] - ); - } -} diff --git a/substrate/core/executor/src/lib.rs b/substrate/core/executor/src/lib.rs index b5c5027951..8b833c9b08 100644 --- a/substrate/core/executor/src/lib.rs +++ b/substrate/core/executor/src/lib.rs @@ -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 { diff --git a/substrate/core/executor/src/native_executor.rs b/substrate/core/executor/src/native_executor.rs index cdf0be7f76..5f4e91b16d 100644 --- a/substrate/core/executor/src/native_executor.rs +++ b/substrate/core/executor/src/native_executor.rs @@ -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 = RefCell::new(RuntimesCache::new()); } +/// Default num of pages for the heap +const DEFAULT_HEAP_PAGES: u64 = 1024; + fn safe_call(f: F) -> Result where F: UnwindSafe + FnOnce() -> U { @@ -65,31 +66,52 @@ pub trait NativeExecutionDispatch: Send + Sync { pub struct NativeExecutor { /// Dummy field to avoid the compiler complaining about us not using `D`. _dummy: ::std::marker::PhantomData, - /// 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, + default_heap_pages: u64, } impl NativeExecutor { /// Create new instance. - pub fn new(default_heap_pages: Option) -> 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) -> 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( + &self, + ext: &mut E, + f: impl for <'a> FnOnce(&'a mut dyn WasmRuntime, &'a mut E) -> Result + ) -> Result + where E: Externalities + { + 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 Clone for NativeExecutor { 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 RuntimeInfo for NativeExecutor { &self, ext: &mut E, ) -> Option { - 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 CodeExecutor for NativeExecutor, ) -> (Result>, 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 CodeExecutor for NativeExecutor".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 CodeExecutor for NativeExecutor".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 CodeExecutor for NativeExecutor".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) } } diff --git a/substrate/core/executor/src/sandbox.rs b/substrate/core/executor/src/sandbox.rs index f09c246679..de49cc9f7c 100644 --- a/substrate/core/executor/src/sandbox.rs +++ b/substrate/core/executor/src/sandbox.rs @@ -586,14 +586,27 @@ impl Store { #[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 = CoreTestExternalities; + fn call_wasm>( + ext: &mut E, + heap_pages: u64, + code: &[u8], + method: &str, + data: &[u8], + ) -> Result> { + 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::::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], ); } diff --git a/substrate/core/executor/src/wasm_runtime.rs b/substrate/core/executor/src/wasm_runtime.rs new file mode 100644 index 0000000000..27b65d6551 --- /dev/null +++ b/substrate/core/executor/src/wasm_runtime.rs @@ -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 . + +//! 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, method: &str, data: &[u8]) + -> Result, 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; +} + +/// 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, 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)` 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>( + &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>( + ext: &mut E, + wasm_method: WasmExecutionMethod, + heap_pages: u64, +) -> Result, 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 { Box::new(runtime) }), + } +} diff --git a/substrate/core/executor/src/wasm_runtimes_cache.rs b/substrate/core/executor/src/wasm_runtimes_cache.rs deleted file mode 100644 index a615660777..0000000000 --- a/substrate/core/executor/src/wasm_runtimes_cache.rs +++ /dev/null @@ -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 . - -//! 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, - /// 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(&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 { - 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)>, - /// The list of all global mutable variables of the module in their sequential order. - global_mut_values: Vec, - heap_pages: u64, -} - -impl StateSnapshot { - // Returns `None` if instance is not valid. - fn take( - module_instance: &WasmModuleInstanceRef, - data_segments: Vec, - heap_pages: u64, - ) -> Option { - 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::>>()?; - - // 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, 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)` 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>( - &mut self, - wasm_executor: &WasmExecutor, - ext: &mut E, - default_heap_pages: Option, - ) -> Result, 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, 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>( - wasm_executor: &WasmExecutor, - ext: &mut E, - heap_pages: u64, - ) -> Result, 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, 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) -} diff --git a/substrate/core/executor/src/wasmi_execution.rs b/substrate/core/executor/src/wasmi_execution.rs new file mode 100644 index 0000000000..e228372bd3 --- /dev/null +++ b/substrate/core/executor/src/wasmi_execution.rs @@ -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 . + +//! 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, + heap: allocator::FreeingBumpHeapAllocator, + memory: MemoryRef, + table: Option, +} + +impl FunctionExecutor { + fn new(m: MemoryRef, heap_base: u32, t: Option) -> Result { + 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.sandbox_store + } + fn store_mut(&mut self) -> &mut sandbox::Store { + &mut self.sandbox_store + } + fn allocate(&mut self, len: WordSize) -> Result, Error> { + let heap = &mut self.heap; + self.memory.with_direct_access_mut(|mem| { + heap.allocate(mem, len) + }) + } + fn deallocate(&mut self, ptr: Pointer) -> 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, data: &[u8]) -> Result<(), Error> { + self.memory.set(ptr.into(), data).map_err(Into::into) + } + fn read_memory(&self, ptr: Pointer, len: WordSize) -> Result, 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, + invoke_args_len: WordSize, + state: u32, + func_idx: sandbox::SupervisorFuncIndex, + ) -> Result + { + 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, dest: &mut [u8]) -> WResult<()> { + self.memory.get_into(address.into(), dest).map_err(|e| format!("{:?}", e)) + } + + fn write_memory(&mut self, address: Pointer, data: &[u8]) -> WResult<()> { + self.memory.set(address.into(), data).map_err(|e| format!("{:?}", e)) + } + + fn allocate_memory(&mut self, size: WordSize) -> WResult> { + 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) -> 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, + buf_len: WordSize, + ) -> WResult { + 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, + val_len: WordSize, + ) -> WResult { + 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 { + 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, + return_val_len: WordSize, + state: u32, + ) -> WResult { + trace!(target: "sr-sandbox", "invoke, instance_idx={}", instance_id); + + // Deserialize arguments and convert them into wasmi types. + let args = Vec::::decode(&mut &args[..]) + .map_err(|_| "Can't decode serialized arguments for the invocation")? + .into_iter() + .map(Into::into) + .collect::>(); + + 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 { + // 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 + { + 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, 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 { + 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 { + 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, + module_instance: &ModuleRef, + method: &str, + data: &[u8], +) -> Result, 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) -> Result, Error>, + FR: FnOnce(Option, &MemoryRef) -> Result, Error>, + R, +>( + ext: &mut dyn Externalities, + module_instance: &ModuleRef, + method: &str, + create_parameters: F, + filter_result: FR, +) -> Result { + // 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 = 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 { + // 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)>, + /// The list of all global mutable variables of the module in their sequential order. + global_mut_values: Vec, + heap_pages: u64, +} + +impl StateSnapshot { + // Returns `None` if instance is not valid. + fn take( + module_instance: &ModuleRef, + data_segments: Vec, + heap_pages: u64, + ) -> Option { + 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::>>()?; + + // 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, + /// 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(&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, method: &str, data: &[u8]) + -> Result, Error> + { + self.with(|module| { + call_in_wasm_module(ext, module, method, data) + }) + } + + fn version(&self) -> Option { + self.version.clone() + } +} + +pub fn create_instance>(ext: &mut E, code: &[u8], heap_pages: u64) + -> Result +{ + 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, 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 = CoreTestExternalities; + + fn call>( + ext: &mut E, + heap_pages: u64, + code: &[u8], + method: &str, + data: &[u8], + ) -> Result, 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::::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::::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::::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::::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::::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::::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] + ); + } +} diff --git a/substrate/core/service/src/builder.rs b/substrate/core/service/src/builder.rs index f0860de1c1..e4b02150bf 100644 --- a/substrate/core/service/src/builder.rs +++ b/substrate/core/service/src/builder.rs @@ -167,7 +167,10 @@ where TGen: RuntimeGenesis, TCSExt: Extension { pruning: config.pruning.clone(), }; - let executor = NativeExecutor::::new(config.default_heap_pages); + let executor = NativeExecutor::::new( + config.wasm_method, + config.default_heap_pages, + ); let fork_blocks = config.chain_spec .extensions() @@ -239,7 +242,10 @@ where TGen: RuntimeGenesis, TCSExt: Extension { pruning: config.pruning.clone(), }; - let executor = NativeExecutor::::new(config.default_heap_pages); + let executor = NativeExecutor::::new( + config.wasm_method, + config.default_heap_pages, + ); let db_storage = client_db::light::LightStorage::new(db_settings)?; let light_blockchain = client::light::new_light_blockchain(db_storage); diff --git a/substrate/core/service/src/config.rs b/substrate/core/service/src/config.rs index c4690e53f7..a1ba83753f 100644 --- a/substrate/core/service/src/config.rs +++ b/substrate/core/service/src/config.rs @@ -19,6 +19,7 @@ pub use client::ExecutionStrategies; pub use client_db::PruningMode; pub use network::config::{ExtTransport, NetworkConfiguration, Roles}; +pub use substrate_executor::WasmExecutionMethod; use std::{path::PathBuf, net::SocketAddr}; use transaction_pool; @@ -60,6 +61,8 @@ pub struct Configuration { pub custom: C, /// Node name. pub name: String, + /// Wasm execution method. + pub wasm_method: WasmExecutionMethod, /// Execution strategies. pub execution_strategies: ExecutionStrategies, /// RPC over HTTP binding address. `None` if disabled. @@ -116,6 +119,7 @@ impl Configuration where state_cache_child_ratio: Default::default(), custom: Default::default(), pruning: PruningMode::default(), + wasm_method: WasmExecutionMethod::Interpreted, execution_strategies: Default::default(), rpc_http: None, rpc_ws: None, diff --git a/substrate/core/service/test/src/lib.rs b/substrate/core/service/test/src/lib.rs index 2d064f965b..806996576f 100644 --- a/substrate/core/service/test/src/lib.rs +++ b/substrate/core/service/test/src/lib.rs @@ -178,6 +178,7 @@ fn node_config ( chain_spec: (*spec).clone(), custom: Default::default(), name: format!("Node {}", index), + wasm_method: service::config::WasmExecutionMethod::Interpreted, execution_strategies: Default::default(), rpc_http: None, rpc_ws: None, diff --git a/substrate/core/sr-api-macros/tests/runtime_calls.rs b/substrate/core/sr-api-macros/tests/runtime_calls.rs index f33a9e257a..ce6300bc41 100644 --- a/substrate/core/sr-api-macros/tests/runtime_calls.rs +++ b/substrate/core/sr-api-macros/tests/runtime_calls.rs @@ -186,7 +186,7 @@ fn record_proof_works() { // Use the proof backend to execute `execute_block`. let mut overlay = Default::default(); - let executor = NativeExecutor::::new(None); + let executor = NativeExecutor::::new(WasmExecutionMethod::Interpreted, None); execution_proof_check_on_trie_backend( &backend, &mut overlay, diff --git a/substrate/core/test-client/src/lib.rs b/substrate/core/test-client/src/lib.rs index a2baf11be2..dbe4431456 100644 --- a/substrate/core/test-client/src/lib.rs +++ b/substrate/core/test-client/src/lib.rs @@ -24,7 +24,7 @@ pub use client::{ExecutionStrategies, blockchain, backend, self}; pub use client_db::{Backend, self}; pub use client_ext::ClientExt; pub use consensus; -pub use executor::{NativeExecutor, self}; +pub use executor::{NativeExecutor, WasmExecutionMethod, self}; pub use keyring::{ AccountKeyring, ed25519::Keyring as Ed25519Keyring, @@ -198,7 +198,7 @@ impl TestClientBuilder } impl TestClientBuilder< - client::LocalCallExecutor>, + client::LocalCallExecutor>, Backend, G, > { @@ -209,18 +209,20 @@ impl TestClientBuilder< ) -> ( client::Client< Backend, - client::LocalCallExecutor>, + client::LocalCallExecutor>, Block, RuntimeApi >, client::LongestChain, ) where - I: Into>>, + I: Into>>, E: executor::NativeExecutionDispatch, Backend: client::backend::Backend, Block: BlockT::Out>, { - let executor = executor.into().unwrap_or_else(|| executor::NativeExecutor::new(None)); + let executor = executor.into().unwrap_or_else(|| + NativeExecutor::new(WasmExecutionMethod::Interpreted, None) + ); let executor = LocalCallExecutor::new(self.backend.clone(), executor, self.keystore.take()); self.build_with_executor(executor) diff --git a/substrate/core/test-runtime/client/src/lib.rs b/substrate/core/test-runtime/client/src/lib.rs index 722aa0b2b4..affbae62c2 100644 --- a/substrate/core/test-runtime/client/src/lib.rs +++ b/substrate/core/test-runtime/client/src/lib.rs @@ -39,7 +39,7 @@ pub mod prelude { // Client structs pub use super::{ TestClient, TestClientBuilder, Backend, LightBackend, - Executor, LightExecutor, LocalExecutor, NativeExecutor, + Executor, LightExecutor, LocalExecutor, NativeExecutor, WasmExecutionMethod, }; // Keyring pub use super::{AccountKeyring, Sr25519Keyring}; @@ -261,7 +261,7 @@ pub fn new_light() -> ( let storage = client_db::light::LightStorage::new_test(); let blockchain = Arc::new(client::light::blockchain::Blockchain::new(storage)); let backend = Arc::new(LightBackend::new(blockchain.clone())); - let executor = NativeExecutor::new(None); + let executor = NativeExecutor::new(WasmExecutionMethod::Interpreted, None); let local_call_executor = client::LocalCallExecutor::new(backend.clone(), executor, None); let call_executor = LightExecutor::new( backend.clone(), diff --git a/substrate/core/test-runtime/src/system.rs b/substrate/core/test-runtime/src/system.rs index e61e72f3a0..12a656cf31 100644 --- a/substrate/core/test-runtime/src/system.rs +++ b/substrate/core/test-runtime/src/system.rs @@ -322,8 +322,19 @@ mod tests { use runtime_io::{with_externalities, TestExternalities}; use substrate_test_runtime_client::{AccountKeyring, Sr25519Keyring}; use crate::{Header, Transfer, WASM_BINARY}; - use primitives::{Blake2Hasher, map}; - use substrate_executor::WasmExecutor; + use primitives::{Blake2Hasher, NeverNativeValue, map, traits::CodeExecutor}; + use substrate_executor::{NativeExecutor, WasmExecutionMethod, native_executor_instance}; + + // Declare an instance of the native executor dispatch for the test runtime. + native_executor_instance!( + NativeDispatch, + crate::api::dispatch, + crate::native_version + ); + + fn executor() -> NativeExecutor { + NativeExecutor::new(WasmExecutionMethod::Interpreted, None) + } fn new_test_ext() -> TestExternalities { let authorities = vec![ @@ -331,13 +342,19 @@ mod tests { Sr25519Keyring::Bob.to_raw_public(), Sr25519Keyring::Charlie.to_raw_public() ]; - TestExternalities::new((map![ - twox_128(b"latest").to_vec() => vec![69u8; 32], - twox_128(b"sys:auth").to_vec() => authorities.encode(), - blake2_256(&AccountKeyring::Alice.to_raw_public().to_keyed_vec(b"balance:")).to_vec() => { - vec![111u8, 0, 0, 0, 0, 0, 0, 0] - } - ], map![])) + TestExternalities::new_with_code( + WASM_BINARY, + ( + map![ + twox_128(b"latest").to_vec() => vec![69u8; 32], + twox_128(b"sys:auth").to_vec() => authorities.encode(), + blake2_256(&AccountKeyring::Alice.to_raw_public().to_keyed_vec(b"balance:")).to_vec() => { + vec![111u8, 0, 0, 0, 0, 0, 0, 0] + } + ], + map![], + ) + ) } fn block_import_works(block_executor: F) where F: Fn(Block, &mut TestExternalities) { @@ -370,7 +387,13 @@ mod tests { #[test] fn block_import_works_wasm() { block_import_works(|b, ext| { - WasmExecutor::new().call(ext, 8, &WASM_BINARY, "Core_execute_block", &b.encode()).unwrap(); + executor().call::<_, NeverNativeValue, fn() -> _>( + ext, + "Core_execute_block", + &b.encode(), + false, + None, + ).0.unwrap(); }) } @@ -458,7 +481,13 @@ mod tests { #[test] fn block_import_with_transaction_works_wasm() { block_import_with_transaction_works(|b, ext| { - WasmExecutor::new().call(ext, 8, &WASM_BINARY, "Core_execute_block", &b.encode()).unwrap(); + executor().call::<_, NeverNativeValue, fn() -> _>( + ext, + "Core_execute_block", + &b.encode(), + false, + None, + ).0.unwrap(); }) } } diff --git a/substrate/node/executor/src/lib.rs b/substrate/node/executor/src/lib.rs index 864b51d6c7..38656b7bd4 100644 --- a/substrate/node/executor/src/lib.rs +++ b/substrate/node/executor/src/lib.rs @@ -22,7 +22,6 @@ #[cfg(feature = "benchmarks")] extern crate test; pub use substrate_executor::NativeExecutor; -pub use substrate_executor::RuntimesCache; use substrate_executor::native_executor_instance; // Declare an instance of the native executor named `Executor`. Include the wasm binary as the @@ -53,6 +52,7 @@ mod tests { transaction_validity::InvalidTransaction, weights::{WeightMultiplier, GetDispatchInfo}, }; use contracts::ContractAddressFor; + use substrate_executor::{NativeExecutor, WasmExecutionMethod}; use system::{EventRecord, Phase}; use node_runtime::{ Header, Block, UncheckedExtrinsic, CheckedExtrinsic, Call, Runtime, Balances, BuildStorage, @@ -117,8 +117,8 @@ mod tests { Header::new(n, Default::default(), Default::default(), [69; 32].into(), Default::default()) } - fn executor() -> ::substrate_executor::NativeExecutor { - substrate_executor::NativeExecutor::new(None) + fn executor() -> NativeExecutor { + NativeExecutor::new(WasmExecutionMethod::Interpreted, None) } fn set_heap_pages>(ext: &mut E, heap_pages: u64) {