diff --git a/substrate/substrate/executor/src/sandbox.rs b/substrate/substrate/executor/src/sandbox.rs
index 1ab242e9d5..1c6ce76002 100644
--- a/substrate/substrate/executor/src/sandbox.rs
+++ b/substrate/substrate/executor/src/sandbox.rs
@@ -14,12 +14,16 @@
// You should have received a copy of the GNU General Public License
// along with Substrate. If not, see .
+#![warn(missing_docs)]
+
//! This module implements sandboxing support in the runtime.
+use std::collections::HashMap;
+use std::rc::Rc;
use codec::Slicable;
use primitives::sandbox as sandbox_primitives;
-use std::collections::HashMap;
use wasm_utils::DummyUserError;
+use wasmi;
use wasmi::memory_units::Pages;
use wasmi::{Externals, FuncRef, ImportResolver, MemoryInstance, MemoryRef, Module, ModuleInstance,
ModuleRef, RuntimeArgs, RuntimeValue, Trap, TrapKind};
@@ -38,13 +42,13 @@ struct SupervisorFuncIndex(usize);
struct GuestFuncIndex(usize);
/// This struct holds a mapping from guest index space to supervisor.
-struct GuestToSuperviserFunctionMapping {
+struct GuestToSupervisorFunctionMapping {
funcs: Vec,
}
-impl GuestToSuperviserFunctionMapping {
- fn new() -> GuestToSuperviserFunctionMapping {
- GuestToSuperviserFunctionMapping { funcs: Vec::new() }
+impl GuestToSupervisorFunctionMapping {
+ fn new() -> GuestToSupervisorFunctionMapping {
+ GuestToSupervisorFunctionMapping { funcs: Vec::new() }
}
fn define(&mut self, supervisor_func: SupervisorFuncIndex) -> GuestFuncIndex {
@@ -130,9 +134,12 @@ impl ImportResolver for Imports {
///
/// Note that this functions are only called in the `supervisor` context.
pub trait SandboxCapabilities {
- /// Returns associated sandbox `Store`.
+ /// Returns a reference to an associated sandbox `Store`.
fn store(&self) -> &Store;
+ /// Returns a mutable reference to an associated sandbox `Store`.
+ fn store_mut(&mut self) -> &mut Store;
+
/// Allocate space of the specified length in the supervisor memory.
///
/// Returns pointer to the allocated block.
@@ -164,28 +171,10 @@ pub trait SandboxCapabilities {
/// [`Externals`]: ../../wasmi/trait.Externals.html
pub struct GuestExternals<'a, FE: SandboxCapabilities + Externals + 'a> {
supervisor_externals: &'a mut FE,
- instance_idx: u32,
+ sandbox_instance: &'a SandboxInstance,
state: u32,
}
-impl<'a, FE: SandboxCapabilities + Externals + 'a> GuestExternals<'a, FE> {
- /// Create a new instance of `GuestExternals`.
- ///
- /// It will use `supervisor_externals` to execute calls from guest to supervisor.
- /// `instance_idx` required to fetch settings for this particular instance, e.g
- /// associated dispatch thunk funtion and mapping between externals function ids to
- /// functions in supervisor module.
- /// `state` is just an integer that allows supervisor to have arbitrary state associated with the call,
- /// typically used for implementing runtime functions.
- pub fn new(supervisor_externals: &mut FE, instance_idx: u32, state: u32) -> GuestExternals {
- GuestExternals {
- supervisor_externals,
- instance_idx,
- state,
- }
- }
-}
-
fn trap() -> Trap {
TrapKind::Host(Box::new(DummyUserError)).into()
}
@@ -210,22 +199,19 @@ impl<'a, FE: SandboxCapabilities + Externals + 'a> Externals for GuestExternals<
index: usize,
args: RuntimeArgs,
) -> Result, Trap> {
- let (func_idx, dispatch_thunk) = {
- let instance = &self.supervisor_externals.store().instances[self.instance_idx as usize];
- let dispatch_thunk = instance.dispatch_thunk.clone();
- let func_idx = instance
- .guest_to_supervisor_mapping
- .func_by_guest_index(
- GuestFuncIndex(index)
- )
- .expect(
- "`invoke_index` is called with indexes registered via `FuncInstance::alloc_host`;
- `FuncInstance::alloc_host` is called with indexes that was obtained from `guest_to_supervisor_mapping`;
- `func_by_guest_index` called with `index` can't return `None`;
- qed"
- );
- (func_idx, dispatch_thunk)
- };
+ // Make `index` typesafe again.
+ let index = GuestFuncIndex(index);
+
+ let dispatch_thunk = self.sandbox_instance.dispatch_thunk.clone();
+ let func_idx = self.sandbox_instance
+ .guest_to_supervisor_mapping
+ .func_by_guest_index(index)
+ .expect(
+ "`invoke_index` is called with indexes registered via `FuncInstance::alloc_host`;
+ `FuncInstance::alloc_host` is called with indexes that was obtained from `guest_to_supervisor_mapping`;
+ `func_by_guest_index` called with `index` can't return `None`;
+ qed"
+ );
// Serialize arguments into a byte vector.
let invoke_args_data: Vec = args.as_ref()
@@ -279,21 +265,78 @@ impl<'a, FE: SandboxCapabilities + Externals + 'a> Externals for GuestExternals<
}
}
-struct SandboxInstance {
+fn with_guest_externals(
+ supervisor_externals: &mut FE,
+ sandbox_instance: &SandboxInstance,
+ state: u32,
+ f: F,
+) -> R
+where
+ FE: SandboxCapabilities + Externals,
+ F: FnOnce(&mut GuestExternals) -> R,
+{
+ let mut guest_externals = GuestExternals {
+ supervisor_externals,
+ sandbox_instance,
+ state,
+ };
+ f(&mut guest_externals)
+}
+
+/// Sandboxed instance of a wasm module.
+///
+/// It's primary purpose is to [`invoke`] exported functions on it.
+///
+/// All imports of this instance are specified at the creation time and
+/// imports are implemented by the supervisor.
+///
+/// Hence, in order to invoke an exported function on a sandboxed module instance,
+/// it's required to provide supervisor externals: it will be used to execute
+/// code in the supervisor context.
+///
+/// [`invoke`]: #method.invoke
+pub struct SandboxInstance {
instance: ModuleRef,
dispatch_thunk: FuncRef,
- guest_to_supervisor_mapping: GuestToSuperviserFunctionMapping,
+ guest_to_supervisor_mapping: GuestToSupervisorFunctionMapping,
+}
+
+impl SandboxInstance {
+ /// Invoke an exported function by a name.
+ ///
+ /// `supervisor_externals` is required to execute the implementations
+ /// of the syscalls that published to a sandboxed module instance.
+ ///
+ /// The `state` parameter can be used to provide custom data for
+ /// these syscall implementations.
+ pub fn invoke(
+ &self,
+ export_name: &str,
+ args: &[RuntimeValue],
+ supervisor_externals: &mut FE,
+ state: u32,
+ ) -> Result, wasmi::Error> {
+ with_guest_externals(
+ supervisor_externals,
+ self,
+ state,
+ |guest_externals| {
+ self.instance
+ .invoke_export(export_name, args, guest_externals)
+ },
+ )
+ }
}
fn decode_environment_definition(
raw_env_def: &[u8],
memories: &[MemoryRef],
-) -> Result<(Imports, GuestToSuperviserFunctionMapping), DummyUserError> {
+) -> Result<(Imports, GuestToSupervisorFunctionMapping), DummyUserError> {
let env_def = sandbox_primitives::EnvironmentDefinition::decode(&mut &raw_env_def[..]).ok_or_else(|| DummyUserError)?;
let mut func_map = HashMap::new();
let mut memories_map = HashMap::new();
- let mut guest_to_supervisor_mapping = GuestToSuperviserFunctionMapping::new();
+ let mut guest_to_supervisor_mapping = GuestToSupervisorFunctionMapping::new();
for entry in &env_def.entries {
let module = entry.module_name.clone();
@@ -323,14 +366,68 @@ fn decode_environment_definition(
))
}
+/// Instantiate a guest module and return it's index in the store.
+///
+/// The guest module's code is specified in `wasm`. Environment that will be available to
+/// guest module is specified in `raw_env_def` (serialized version of [`EnvironmentDefinition`]).
+/// `dispatch_thunk` is used as function that handle calls from guests.
+///
+/// # Errors
+///
+/// Returns `Err` if any of the following conditions happens:
+///
+/// - `raw_env_def` can't be deserialized as a [`EnvironmentDefinition`].
+/// - Module in `wasm` is invalid or couldn't be instantiated.
+///
+/// [`EnvironmentDefinition`]: ../../sandbox/struct.EnvironmentDefinition.html
+pub fn instantiate(
+ supervisor_externals: &mut FE,
+ dispatch_thunk: FuncRef,
+ wasm: &[u8],
+ raw_env_def: &[u8],
+ state: u32,
+) -> Result {
+ let (imports, guest_to_supervisor_mapping) =
+ decode_environment_definition(raw_env_def, &supervisor_externals.store().memories)?;
+
+ let module = Module::from_buffer(wasm).map_err(|_| DummyUserError)?;
+ let instance = ModuleInstance::new(&module, &imports).map_err(|_| DummyUserError)?;
+
+ let sandbox_instance = Rc::new(SandboxInstance {
+ // In general, it's not a very good idea to use `.not_started_instance()` for anything
+ // but for extracting memory and tables. But in this particular case, we are extracting
+ // for the purpose of running `start` function which should be ok.
+ instance: instance.not_started_instance().clone(),
+ dispatch_thunk,
+ guest_to_supervisor_mapping,
+ });
+
+ with_guest_externals(
+ supervisor_externals,
+ &sandbox_instance,
+ state,
+ |guest_externals| {
+ instance
+ .run_start(guest_externals)
+ .map_err(|_| DummyUserError)
+ },
+ )?;
+
+ let instance_idx = supervisor_externals
+ .store_mut()
+ .register_sandbox_instance(sandbox_instance);
+
+ Ok(instance_idx)
+}
+
/// This struct keeps track of all sandboxed components.
pub struct Store {
- instances: Vec,
+ instances: Vec>,
memories: Vec,
}
impl Store {
- /// Create new empty sandbox store.
+ /// Create a new empty sandbox store.
pub fn new() -> Store {
Store {
instances: Vec::new(),
@@ -338,47 +435,6 @@ impl Store {
}
}
- /// Instantiate a guest module and return it's index in the store.
- ///
- /// The guest module's code is specified in `wasm`. Environment that will be available to
- /// guest module is specified in `raw_env_def` (serialized version of [`EnvironmentDefinition`]).
- /// `dispatch_thunk` is used as function that handle calls from guests.
- ///
- /// # Errors
- ///
- /// Returns `Err` if any of the following conditions happens:
- ///
- /// - `raw_env_def` can't be deserialized as a [`EnvironmentDefinition`].
- /// - Module in `wasm` is invalid or couldn't be instantiated.
- ///
- /// [`EnvironmentDefinition`]: ../../sandbox/struct.EnvironmentDefinition.html
- pub fn instantiate(
- &mut self,
- dispatch_thunk: FuncRef,
- wasm: &[u8],
- raw_env_def: &[u8],
- _state: u32,
- ) -> Result {
- let (imports, guest_to_supervisor_mapping) =
- decode_environment_definition(raw_env_def, &self.memories)?;
-
- // TODO: Run `start`.
- let module = Module::from_buffer(wasm).map_err(|_| DummyUserError)?;
- let instance = ModuleInstance::new(&module, &imports)
- .map_err(|_| DummyUserError)?
- .assert_no_start();
-
- let instance_idx = self.instances.len();
-
- self.instances.push(SandboxInstance {
- instance,
- dispatch_thunk,
- guest_to_supervisor_mapping,
- });
-
- Ok(instance_idx as u32)
- }
-
/// Create a new memory instance and return it's index.
///
/// # Errors
@@ -391,21 +447,22 @@ impl Store {
specified_limit => Some(Pages(specified_limit as usize)),
};
- let mem = MemoryInstance::alloc(Pages(initial as usize), maximum).map_err(|_| DummyUserError)?;
+ let mem =
+ MemoryInstance::alloc(Pages(initial as usize), maximum).map_err(|_| DummyUserError)?;
+ let mem_idx = self.memories.len();
self.memories.push(mem);
- let mem_idx = self.memories.len() - 1;
Ok(mem_idx as u32)
}
- /// Returns `ModuleRef` by `instance_idx`.
+ /// Returns `SandboxInstance` by `instance_idx`.
///
/// # Errors
///
/// Returns `Err` If `instance_idx` isn't a valid index of an instance.
- pub fn instance(&self, instance_idx: u32) -> Result {
+ pub fn instance(&self, instance_idx: u32) -> Result, DummyUserError> {
self.instances
.get(instance_idx as usize)
- .map(|i| i.instance.clone())
+ .cloned()
.ok_or_else(|| DummyUserError)
}
@@ -420,6 +477,12 @@ impl Store {
.cloned()
.ok_or_else(|| DummyUserError)
}
+
+ fn register_sandbox_instance(&mut self, sandbox_instance: Rc) -> u32 {
+ let instance_idx = self.instances.len();
+ self.instances.push(sandbox_instance);
+ instance_idx as u32
+ }
}
#[cfg(test)]
@@ -480,4 +543,42 @@ mod tests {
vec![0],
);
}
+
+ #[test]
+ fn start_called() {
+ let mut ext = TestExternalities::default();
+ let test_code = include_bytes!("../wasm/target/wasm32-unknown-unknown/release/runtime_test.compact.wasm");
+
+ let code = wabt::wat2wasm(r#"
+ (module
+ (import "env" "assert" (func $assert (param i32)))
+ (import "env" "inc_counter" (func $inc_counter (param i32) (result i32)))
+
+ ;; Start function
+ (start $start)
+ (func $start
+ ;; Increment counter by 1
+ (drop
+ (call $inc_counter (i32.const 1))
+ )
+ )
+
+ (func (export "call")
+ ;; Increment counter by 1. The current value is placed on the stack.
+ (call $inc_counter (i32.const 1))
+
+ ;; Counter is incremented twice by 1, once there and once in `start` func.
+ ;; So check the returned value is equal to 2.
+ i32.const 2
+ i32.eq
+ call $assert
+ )
+ )
+ "#).unwrap();
+
+ assert_eq!(
+ WasmExecutor.call(&mut ext, &test_code[..], "test_sandbox", &code).unwrap(),
+ vec![1],
+ );
+ }
}
diff --git a/substrate/substrate/executor/src/wasm_executor.rs b/substrate/substrate/executor/src/wasm_executor.rs
index 532275b80b..905c924055 100644
--- a/substrate/substrate/executor/src/wasm_executor.rs
+++ b/substrate/substrate/executor/src/wasm_executor.rs
@@ -89,6 +89,9 @@ impl<'e, E: Externalities> sandbox::SandboxCapabilities for FunctionExecutor<'e,
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: u32) -> u32 {
self.heap.allocate(len)
}
@@ -340,13 +343,16 @@ impl_function_executor!(this: FunctionExecutor<'e, E>,
let wasm = this.memory.get(wasm_ptr, wasm_len as usize).map_err(|_| DummyUserError)?;
let raw_env_def = this.memory.get(imports_ptr, imports_len as usize).map_err(|_| DummyUserError)?;
- let table = this.table.as_ref().ok_or_else(|| DummyUserError)?;
- let dispatch_thunk = table.get(dispatch_thunk_idx)
- .map_err(|_| DummyUserError)?
- .ok_or_else(|| DummyUserError)?
- .clone();
+ // Extract a dispatch thunk from instance's table by the specified index.
+ let dispatch_thunk = {
+ let table = this.table.as_ref().ok_or_else(|| DummyUserError)?;
+ table.get(dispatch_thunk_idx)
+ .map_err(|_| DummyUserError)?
+ .ok_or_else(|| DummyUserError)?
+ .clone()
+ };
- let instance_idx = this.sandbox_store.instantiate(dispatch_thunk, &wasm, &raw_env_def, state)?;
+ let instance_idx = sandbox::instantiate(this, dispatch_thunk, &wasm, &raw_env_def, state)?;
Ok(instance_idx as u32)
},
@@ -360,10 +366,7 @@ impl_function_executor!(this: FunctionExecutor<'e, E>,
)?;
let instance = this.sandbox_store.instance(instance_idx)?;
-
- let mut guest_externals = sandbox::GuestExternals::new(this, instance_idx, state);
-
- let result = instance.invoke_export(&export, &[], &mut guest_externals);
+ let result = instance.invoke(&export, &[], this, state);
match result {
Ok(None) => Ok(sandbox_primitives::ERR_OK),
// TODO: Return value