mirror of
https://github.com/pezkuwichain/pezkuwi-subxt.git
synced 2026-06-12 07:41:08 +00:00
Phase 1 of repo reorg (#719)
* Remove unneeded script * Rename Substrate Demo -> Substrate * Rename demo -> node * Build wasm from last rename. * Merge ed25519 into substrate-primitives * Minor tweak * Rename substrate -> core * Move substrate-runtime-support to core/runtime/support * Rename/move substrate-runtime-version * Move codec up a level * Rename substrate-codec -> parity-codec * Move environmental up a level * Move pwasm-* up to top, ready for removal * Remove requirement of s-r-support from s-r-primitives * Move core/runtime/primitives into core/runtime-primitives * Remove s-r-support dep from s-r-version * Remove dep of s-r-support from bft * Remove dep of s-r-support from node/consensus * Sever all other core deps from s-r-support * Forgot the no_std directive * Rename non-SRML modules to sr-* to avoid match clashes * Move runtime/* to srml/* * Rename substrate-runtime-* -> srml-* * Move srml to top-level
This commit is contained in:
committed by
Arkadiy Paronyan
parent
8fe5aa4c81
commit
1e01162505
@@ -0,0 +1,81 @@
|
||||
// Copyright 2017 Parity Technologies (UK) Ltd.
|
||||
// This file is part of Substrate.
|
||||
|
||||
// Substrate is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
|
||||
// Substrate is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Substrate. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
//! Rust executor possible errors.
|
||||
|
||||
use state_machine;
|
||||
use serializer;
|
||||
use wasmi;
|
||||
|
||||
error_chain! {
|
||||
foreign_links {
|
||||
InvalidData(serializer::Error) #[doc = "Unserializable Data"];
|
||||
Trap(wasmi::Trap) #[doc = "Trap occured during execution"];
|
||||
Wasmi(wasmi::Error) #[doc = "Wasmi loading/instantiating error"];
|
||||
}
|
||||
|
||||
errors {
|
||||
/// Method is not found
|
||||
MethodNotFound(t: String) {
|
||||
description("method not found"),
|
||||
display("Method not found: '{}'", t),
|
||||
}
|
||||
|
||||
/// Code is invalid (expected single byte)
|
||||
InvalidCode(c: Vec<u8>) {
|
||||
description("invalid code"),
|
||||
display("Invalid Code: {:?}", c),
|
||||
}
|
||||
|
||||
/// Could not get runtime version.
|
||||
VersionInvalid {
|
||||
description("Runtime version error"),
|
||||
display("On-chain runtime does not specify version"),
|
||||
}
|
||||
|
||||
/// Externalities have failed.
|
||||
Externalities {
|
||||
description("externalities failure"),
|
||||
display("Externalities error"),
|
||||
}
|
||||
|
||||
/// Invalid index.
|
||||
InvalidIndex {
|
||||
description("index given was not in range"),
|
||||
display("Invalid index provided"),
|
||||
}
|
||||
|
||||
/// Invalid return type.
|
||||
InvalidReturn {
|
||||
description("u64 was not returned"),
|
||||
display("Invalid type returned (should be u64)"),
|
||||
}
|
||||
|
||||
/// Runtime failed.
|
||||
Runtime {
|
||||
description("runtime failure"),
|
||||
display("Runtime error"),
|
||||
}
|
||||
|
||||
/// Runtime failed.
|
||||
InvalidMemoryReference {
|
||||
description("invalid memory reference"),
|
||||
display("Invalid memory reference"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl state_machine::Error for Error {}
|
||||
@@ -0,0 +1,93 @@
|
||||
// Copyright 2017 Parity Technologies (UK) Ltd.
|
||||
// This file is part of Substrate.
|
||||
|
||||
// Substrate is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
|
||||
// Substrate is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Substrate. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
// tag::description[]
|
||||
//! Temporary crate for contracts implementations.
|
||||
//!
|
||||
//! This will be replaced with WASM contracts stored on-chain.
|
||||
//! ** NOTE ***
|
||||
//! This is entirely deprecated with the idea of a single-module Wasm module for state transition.
|
||||
//! The dispatch table should be replaced with the specific functions needed:
|
||||
//! - execute_block(bytes)
|
||||
//! - init_block(PrevBlock?) -> InProgressBlock
|
||||
//! - add_transaction(InProgressBlock) -> InProgressBlock
|
||||
//! I leave it as is for now as it might be removed before this is ever done.
|
||||
// end::description[]
|
||||
|
||||
#![warn(missing_docs)]
|
||||
#![recursion_limit="128"]
|
||||
|
||||
extern crate parity_codec as codec;
|
||||
extern crate sr_io as runtime_io;
|
||||
extern crate substrate_primitives as primitives;
|
||||
extern crate substrate_serializer as serializer;
|
||||
extern crate substrate_state_machine as state_machine;
|
||||
extern crate sr_version as runtime_version;
|
||||
|
||||
extern crate serde;
|
||||
extern crate wasmi;
|
||||
extern crate byteorder;
|
||||
extern crate triehash;
|
||||
extern crate parking_lot;
|
||||
extern crate twox_hash;
|
||||
extern crate hashdb;
|
||||
|
||||
#[macro_use] extern crate log;
|
||||
|
||||
#[macro_use]
|
||||
extern crate lazy_static;
|
||||
|
||||
#[macro_use]
|
||||
extern crate error_chain;
|
||||
|
||||
#[cfg(test)]
|
||||
extern crate assert_matches;
|
||||
|
||||
#[cfg(test)]
|
||||
extern crate wabt;
|
||||
|
||||
#[cfg(test)]
|
||||
#[macro_use]
|
||||
extern crate hex_literal;
|
||||
|
||||
#[macro_use]
|
||||
mod wasm_utils;
|
||||
mod wasm_executor;
|
||||
#[macro_use]
|
||||
mod native_executor;
|
||||
mod sandbox;
|
||||
|
||||
pub mod error;
|
||||
pub use wasm_executor::WasmExecutor;
|
||||
pub use native_executor::{with_native_environment, NativeExecutor, NativeExecutionDispatch};
|
||||
pub use state_machine::Externalities;
|
||||
pub use runtime_version::RuntimeVersion;
|
||||
pub use codec::Codec;
|
||||
use primitives::Blake2Hasher;
|
||||
|
||||
/// Provides runtime information.
|
||||
pub trait RuntimeInfo {
|
||||
/// Native runtime information if any.
|
||||
const NATIVE_VERSION: Option<RuntimeVersion>;
|
||||
|
||||
/// Extract RuntimeVersion of given :code block
|
||||
fn runtime_version<E: Externalities<Blake2Hasher>> (
|
||||
&self,
|
||||
ext: &mut E,
|
||||
heap_pages: usize,
|
||||
code: &[u8]
|
||||
) -> Option<RuntimeVersion>;
|
||||
}
|
||||
@@ -0,0 +1,224 @@
|
||||
// Copyright 2017 Parity Technologies (UK) Ltd.
|
||||
// This file is part of Substrate.
|
||||
|
||||
// Substrate is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
|
||||
// Substrate is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Substrate. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
use error::{Error, ErrorKind, Result};
|
||||
use state_machine::{CodeExecutor, Externalities};
|
||||
use wasm_executor::WasmExecutor;
|
||||
use wasmi::Module as WasmModule;
|
||||
use runtime_version::RuntimeVersion;
|
||||
use std::collections::HashMap;
|
||||
use codec::Decode;
|
||||
use primitives::hashing::blake2_256;
|
||||
use parking_lot::{Mutex, MutexGuard};
|
||||
use RuntimeInfo;
|
||||
use primitives::Blake2Hasher;
|
||||
|
||||
// For the internal Runtime Cache:
|
||||
// Is it compatible enough to run this natively or do we need to fall back on the WasmModule
|
||||
|
||||
enum RuntimePreproc {
|
||||
InvalidCode,
|
||||
ValidCode(WasmModule, Option<RuntimeVersion>),
|
||||
}
|
||||
|
||||
type CacheType = HashMap<[u8; 32], RuntimePreproc>;
|
||||
|
||||
lazy_static! {
|
||||
static ref RUNTIMES_CACHE: Mutex<CacheType> = Mutex::new(HashMap::new());
|
||||
}
|
||||
|
||||
// helper function to generate low-over-head caching_keys
|
||||
// it is asserted that part of the audit process that any potential on-chain code change
|
||||
// will have done is to ensure that the two-x hash is different to that of any other
|
||||
// :code value from the same chain
|
||||
fn gen_cache_key(code: &[u8]) -> [u8; 32] {
|
||||
blake2_256(code)
|
||||
}
|
||||
|
||||
/// fetch a runtime version from the cache or if there is no cached version yet, create
|
||||
/// the runtime version entry for `code`, determines whether `Compatibility::IsCompatible`
|
||||
/// can be used by comparing returned RuntimeVersion to `ref_version`
|
||||
fn fetch_cached_runtime_version<'a, E: Externalities<Blake2Hasher>>(
|
||||
wasm_executor: &WasmExecutor,
|
||||
cache: &'a mut MutexGuard<CacheType>,
|
||||
ext: &mut E,
|
||||
heap_pages: usize,
|
||||
code: &[u8]
|
||||
) -> Result<(&'a WasmModule, &'a Option<RuntimeVersion>)> {
|
||||
let maybe_runtime_preproc = cache.entry(gen_cache_key(code))
|
||||
.or_insert_with(|| match WasmModule::from_buffer(code) {
|
||||
Ok(module) => {
|
||||
let version = wasm_executor.call_in_wasm_module(ext, heap_pages, &module, "version", &[])
|
||||
.ok()
|
||||
.and_then(|v| RuntimeVersion::decode(&mut v.as_slice()));
|
||||
RuntimePreproc::ValidCode(module, version)
|
||||
}
|
||||
Err(e) => {
|
||||
trace!(target: "executor", "Invalid code presented to executor ({:?})", e);
|
||||
RuntimePreproc::InvalidCode
|
||||
}
|
||||
});
|
||||
match maybe_runtime_preproc {
|
||||
RuntimePreproc::InvalidCode => Err(ErrorKind::InvalidCode(code.into()).into()),
|
||||
RuntimePreproc::ValidCode(m, v) => Ok((m, v)),
|
||||
}
|
||||
}
|
||||
|
||||
fn safe_call<F, U>(f: F) -> Result<U>
|
||||
where F: ::std::panic::UnwindSafe + FnOnce() -> U
|
||||
{
|
||||
// Substrate uses custom panic hook that terminates process on panic. Disable it for the native call.
|
||||
let hook = ::std::panic::take_hook();
|
||||
let result = ::std::panic::catch_unwind(f).map_err(|_| ErrorKind::Runtime.into());
|
||||
::std::panic::set_hook(hook);
|
||||
result
|
||||
}
|
||||
|
||||
/// Set up the externalities and safe calling environment to execute calls to a native runtime.
|
||||
///
|
||||
/// If the inner closure panics, it will be caught and return an error.
|
||||
pub fn with_native_environment<F, U>(ext: &mut Externalities<Blake2Hasher>, f: F) -> Result<U>
|
||||
where F: ::std::panic::UnwindSafe + FnOnce() -> U
|
||||
{
|
||||
::runtime_io::with_externalities(ext, move || safe_call(f))
|
||||
}
|
||||
|
||||
/// Delegate for dispatching a CodeExecutor call to native code.
|
||||
pub trait NativeExecutionDispatch: Send + Sync {
|
||||
/// Get the wasm code that the native dispatch will be equivalent to.
|
||||
fn native_equivalent() -> &'static [u8];
|
||||
|
||||
/// Dispatch a method and input data to be executed natively. Returns `Some` result or `None`
|
||||
/// if the `method` is unknown. Panics if there's an unrecoverable error.
|
||||
// fn dispatch<H: hashdb::Hasher>(ext: &mut Externalities<H>, method: &str, data: &[u8]) -> Result<Vec<u8>>;
|
||||
fn dispatch(ext: &mut Externalities<Blake2Hasher>, method: &str, data: &[u8]) -> Result<Vec<u8>>;
|
||||
|
||||
/// Get native runtime version.
|
||||
const VERSION: RuntimeVersion;
|
||||
|
||||
/// Construct corresponding `NativeExecutor`
|
||||
fn new() -> NativeExecutor<Self> where Self: Sized {
|
||||
NativeExecutor::new()
|
||||
}
|
||||
}
|
||||
|
||||
/// A generic `CodeExecutor` implementation that uses a delegate to determine wasm code equivalence
|
||||
/// and dispatch to native code when possible, falling back on `WasmExecutor` when not.
|
||||
#[derive(Debug)]
|
||||
pub struct NativeExecutor<D: NativeExecutionDispatch> {
|
||||
/// Dummy field to avoid the compiler complaining about us not using `D`.
|
||||
_dummy: ::std::marker::PhantomData<D>,
|
||||
/// The fallback executor in case native isn't available.
|
||||
fallback: WasmExecutor,
|
||||
}
|
||||
|
||||
impl<D: NativeExecutionDispatch> NativeExecutor<D> {
|
||||
/// Create new instance.
|
||||
pub fn new() -> Self {
|
||||
NativeExecutor {
|
||||
_dummy: Default::default(),
|
||||
fallback: WasmExecutor::new(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<D: NativeExecutionDispatch> Clone for NativeExecutor<D> {
|
||||
fn clone(&self) -> Self {
|
||||
NativeExecutor {
|
||||
_dummy: Default::default(),
|
||||
fallback: self.fallback.clone(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<D: NativeExecutionDispatch> RuntimeInfo for NativeExecutor<D> {
|
||||
const NATIVE_VERSION: Option<RuntimeVersion> = Some(D::VERSION);
|
||||
|
||||
fn runtime_version<E: Externalities<Blake2Hasher>>(
|
||||
&self,
|
||||
ext: &mut E,
|
||||
heap_pages: usize,
|
||||
code: &[u8],
|
||||
) -> Option<RuntimeVersion> {
|
||||
fetch_cached_runtime_version(&self.fallback, &mut RUNTIMES_CACHE.lock(), ext, heap_pages, code).ok()?.1.clone()
|
||||
}
|
||||
}
|
||||
|
||||
impl<D: NativeExecutionDispatch> CodeExecutor<Blake2Hasher> for NativeExecutor<D> {
|
||||
type Error = Error;
|
||||
|
||||
fn call<E: Externalities<Blake2Hasher>>(
|
||||
&self,
|
||||
ext: &mut E,
|
||||
heap_pages: usize,
|
||||
code: &[u8],
|
||||
method: &str,
|
||||
data: &[u8],
|
||||
use_native: bool,
|
||||
) -> (Result<Vec<u8>>, bool) {
|
||||
let mut c = RUNTIMES_CACHE.lock();
|
||||
let (module, onchain_version) = match fetch_cached_runtime_version(&self.fallback, &mut c, ext, heap_pages, code) {
|
||||
Ok((module, onchain_version)) => (module, onchain_version),
|
||||
Err(_) => return (Err(ErrorKind::InvalidCode(code.into()).into()), false),
|
||||
};
|
||||
match (use_native, onchain_version.as_ref().map_or(false, |v| v.can_call_with(&D::VERSION))) {
|
||||
(_, false) => {
|
||||
trace!(target: "executor", "Request for native execution failed (native: {}, chain: {})", D::VERSION, onchain_version.as_ref().map_or_else(||"<None>".into(), |v| format!("{}", v)));
|
||||
(self.fallback.call_in_wasm_module(ext, heap_pages, module, method, data), false)
|
||||
}
|
||||
(false, _) => {
|
||||
(self.fallback.call_in_wasm_module(ext, heap_pages, module, method, data), false)
|
||||
}
|
||||
_ => {
|
||||
trace!(target: "executor", "Request for native execution succeeded (native: {}, chain: {})", D::VERSION, onchain_version.as_ref().map_or_else(||"<None>".into(), |v| format!("{}", v)));
|
||||
(D::dispatch(ext, method, data), true)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[macro_export]
|
||||
macro_rules! native_executor_instance {
|
||||
(pub $name:ident, $dispatcher:path, $version:path, $code:expr) => {
|
||||
pub struct $name;
|
||||
native_executor_instance!(IMPL $name, $dispatcher, $version, $code);
|
||||
};
|
||||
($name:ident, $dispatcher:path, $version:path, $code:expr) => {
|
||||
/// A unit struct which implements `NativeExecutionDispatch` feeding in the hard-coded runtime.
|
||||
struct $name;
|
||||
native_executor_instance!(IMPL $name, $dispatcher, $version, $code);
|
||||
};
|
||||
(IMPL $name:ident, $dispatcher:path, $version:path, $code:expr) => {
|
||||
// TODO: this is not so great – I think I should go back to have dispatch take a type param and modify this macro to accept a type param and then pass it in from the test-client instead
|
||||
use primitives::Blake2Hasher as _Blake2Hasher;
|
||||
impl $crate::NativeExecutionDispatch for $name {
|
||||
const VERSION: $crate::RuntimeVersion = $version;
|
||||
fn native_equivalent() -> &'static [u8] {
|
||||
// WARNING!!! This assumes that the runtime was built *before* the main project. Until we
|
||||
// get a proper build script, this must be strictly adhered to or things will go wrong.
|
||||
$code
|
||||
}
|
||||
fn dispatch(ext: &mut $crate::Externalities<_Blake2Hasher>, method: &str, data: &[u8]) -> $crate::error::Result<Vec<u8>> {
|
||||
$crate::with_native_environment(ext, move || $dispatcher(method, data))?
|
||||
.ok_or_else(|| $crate::error::ErrorKind::MethodNotFound(method.to_owned()).into())
|
||||
}
|
||||
|
||||
fn new() -> $crate::NativeExecutor<$name> {
|
||||
$crate::NativeExecutor::new()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,676 @@
|
||||
// Copyright 2018 Parity Technologies (UK) Ltd.
|
||||
// This file is part of Substrate.
|
||||
|
||||
// Substrate is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
|
||||
// Substrate is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Substrate. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
#![warn(missing_docs)]
|
||||
|
||||
//! This module implements sandboxing support in the runtime.
|
||||
|
||||
use std::collections::HashMap;
|
||||
use std::rc::Rc;
|
||||
use codec::{Decode, Encode};
|
||||
use primitives::sandbox as sandbox_primitives;
|
||||
use wasm_utils::UserError;
|
||||
use wasmi;
|
||||
use wasmi::memory_units::Pages;
|
||||
use wasmi::{
|
||||
Externals, FuncRef, ImportResolver, MemoryInstance, MemoryRef, Module, ModuleInstance,
|
||||
ModuleRef, RuntimeArgs, RuntimeValue, Trap, TrapKind
|
||||
};
|
||||
|
||||
/// Index of a function inside the supervisor.
|
||||
///
|
||||
/// This is a typically an index in the default table of the supervisor, however
|
||||
/// the exact meaning of this index is depends on the implementation of dispatch function.
|
||||
#[derive(Copy, Clone, Debug, PartialEq)]
|
||||
struct SupervisorFuncIndex(usize);
|
||||
|
||||
/// Index of a function within guest index space.
|
||||
///
|
||||
/// This index is supposed to be used with as index for `Externals`.
|
||||
#[derive(Copy, Clone, Debug, PartialEq)]
|
||||
struct GuestFuncIndex(usize);
|
||||
|
||||
/// This struct holds a mapping from guest index space to supervisor.
|
||||
struct GuestToSupervisorFunctionMapping {
|
||||
funcs: Vec<SupervisorFuncIndex>,
|
||||
}
|
||||
|
||||
impl GuestToSupervisorFunctionMapping {
|
||||
fn new() -> GuestToSupervisorFunctionMapping {
|
||||
GuestToSupervisorFunctionMapping { funcs: Vec::new() }
|
||||
}
|
||||
|
||||
fn define(&mut self, supervisor_func: SupervisorFuncIndex) -> GuestFuncIndex {
|
||||
let idx = self.funcs.len();
|
||||
self.funcs.push(supervisor_func);
|
||||
GuestFuncIndex(idx)
|
||||
}
|
||||
|
||||
fn func_by_guest_index(&self, guest_func_idx: GuestFuncIndex) -> Option<SupervisorFuncIndex> {
|
||||
self.funcs.get(guest_func_idx.0).cloned()
|
||||
}
|
||||
}
|
||||
|
||||
struct Imports {
|
||||
func_map: HashMap<(Vec<u8>, Vec<u8>), GuestFuncIndex>,
|
||||
memories_map: HashMap<(Vec<u8>, Vec<u8>), MemoryRef>,
|
||||
}
|
||||
|
||||
impl ImportResolver for Imports {
|
||||
fn resolve_func(
|
||||
&self,
|
||||
module_name: &str,
|
||||
field_name: &str,
|
||||
signature: &::wasmi::Signature,
|
||||
) -> Result<FuncRef, ::wasmi::Error> {
|
||||
let key = (
|
||||
module_name.as_bytes().to_owned(),
|
||||
field_name.as_bytes().to_owned(),
|
||||
);
|
||||
let idx = *self.func_map.get(&key).ok_or_else(|| {
|
||||
::wasmi::Error::Instantiation(format!(
|
||||
"Export {}:{} not found",
|
||||
module_name, field_name
|
||||
))
|
||||
})?;
|
||||
Ok(::wasmi::FuncInstance::alloc_host(signature.clone(), idx.0))
|
||||
}
|
||||
|
||||
fn resolve_memory(
|
||||
&self,
|
||||
module_name: &str,
|
||||
field_name: &str,
|
||||
_memory_type: &::wasmi::MemoryDescriptor,
|
||||
) -> Result<MemoryRef, ::wasmi::Error> {
|
||||
let key = (
|
||||
module_name.as_bytes().to_vec(),
|
||||
field_name.as_bytes().to_vec(),
|
||||
);
|
||||
let mem = self.memories_map
|
||||
.get(&key)
|
||||
.ok_or_else(|| {
|
||||
::wasmi::Error::Instantiation(format!(
|
||||
"Export {}:{} not found",
|
||||
module_name, field_name
|
||||
))
|
||||
})?
|
||||
.clone();
|
||||
Ok(mem)
|
||||
}
|
||||
|
||||
fn resolve_global(
|
||||
&self,
|
||||
module_name: &str,
|
||||
field_name: &str,
|
||||
_global_type: &::wasmi::GlobalDescriptor,
|
||||
) -> Result<::wasmi::GlobalRef, ::wasmi::Error> {
|
||||
Err(::wasmi::Error::Instantiation(format!(
|
||||
"Export {}:{} not found",
|
||||
module_name, field_name
|
||||
)))
|
||||
}
|
||||
|
||||
fn resolve_table(
|
||||
&self,
|
||||
module_name: &str,
|
||||
field_name: &str,
|
||||
_table_type: &::wasmi::TableDescriptor,
|
||||
) -> Result<::wasmi::TableRef, ::wasmi::Error> {
|
||||
Err(::wasmi::Error::Instantiation(format!(
|
||||
"Export {}:{} not found",
|
||||
module_name, field_name
|
||||
)))
|
||||
}
|
||||
}
|
||||
|
||||
/// This trait encapsulates sandboxing capabilities.
|
||||
///
|
||||
/// Note that this functions are only called in the `supervisor` context.
|
||||
pub trait SandboxCapabilities {
|
||||
/// 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.
|
||||
fn allocate(&mut self, len: u32) -> u32;
|
||||
|
||||
/// Deallocate space specified by the pointer that was previously returned by [`allocate`].
|
||||
///
|
||||
/// [`allocate`]: #tymethod.allocate
|
||||
fn deallocate(&mut self, ptr: u32);
|
||||
|
||||
/// Write `data` into the supervisor memory at offset specified by `ptr`.
|
||||
///
|
||||
/// # Errors
|
||||
///
|
||||
/// Returns `Err` if `ptr + data.len()` is out of bounds.
|
||||
fn write_memory(&mut self, ptr: u32, data: &[u8]) -> Result<(), UserError>;
|
||||
|
||||
/// Read `len` bytes from the supervisor memory.
|
||||
///
|
||||
/// # Errors
|
||||
///
|
||||
/// Returns `Err` if `ptr + len` is out of bounds.
|
||||
fn read_memory(&self, ptr: u32, len: u32) -> Result<Vec<u8>, UserError>;
|
||||
}
|
||||
|
||||
/// Implementation of [`Externals`] that allows execution of guest module with
|
||||
/// [externals][`Externals`] that might refer functions defined by supervisor.
|
||||
///
|
||||
/// [`Externals`]: ../../wasmi/trait.Externals.html
|
||||
pub struct GuestExternals<'a, FE: SandboxCapabilities + Externals + 'a> {
|
||||
supervisor_externals: &'a mut FE,
|
||||
sandbox_instance: &'a SandboxInstance,
|
||||
state: u32,
|
||||
}
|
||||
|
||||
fn trap() -> Trap {
|
||||
TrapKind::Host(Box::new(UserError("Sandbox error"))).into()
|
||||
}
|
||||
|
||||
fn deserialize_result(serialized_result: &[u8]) -> Result<Option<RuntimeValue>, Trap> {
|
||||
use self::sandbox_primitives::{HostError, ReturnValue};
|
||||
let result_val = Result::<ReturnValue, HostError>::decode(&mut &serialized_result[..])
|
||||
.ok_or_else(|| trap())?;
|
||||
|
||||
match result_val {
|
||||
Ok(return_value) => Ok(match return_value {
|
||||
ReturnValue::Unit => None,
|
||||
ReturnValue::Value(typed_value) => Some(RuntimeValue::from(typed_value)),
|
||||
}),
|
||||
Err(HostError) => Err(trap()),
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, FE: SandboxCapabilities + Externals + 'a> Externals for GuestExternals<'a, FE> {
|
||||
fn invoke_index(
|
||||
&mut self,
|
||||
index: usize,
|
||||
args: RuntimeArgs,
|
||||
) -> Result<Option<RuntimeValue>, Trap> {
|
||||
// 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<u8> = args.as_ref()
|
||||
.iter()
|
||||
.cloned()
|
||||
.map(sandbox_primitives::TypedValue::from)
|
||||
.collect::<Vec<_>>()
|
||||
.encode();
|
||||
|
||||
let state = self.state;
|
||||
|
||||
// Move serialized arguments inside the memory and invoke dispatch thunk and
|
||||
// then free allocated memory.
|
||||
let invoke_args_ptr = self.supervisor_externals
|
||||
.allocate(invoke_args_data.len() as u32);
|
||||
self.supervisor_externals
|
||||
.write_memory(invoke_args_ptr, &invoke_args_data)?;
|
||||
let result = ::wasmi::FuncInstance::invoke(
|
||||
&dispatch_thunk,
|
||||
&[
|
||||
RuntimeValue::I32(invoke_args_ptr as i32),
|
||||
RuntimeValue::I32(invoke_args_data.len() as i32),
|
||||
RuntimeValue::I32(state as i32),
|
||||
RuntimeValue::I32(func_idx.0 as i32),
|
||||
],
|
||||
self.supervisor_externals,
|
||||
);
|
||||
self.supervisor_externals.deallocate(invoke_args_ptr);
|
||||
|
||||
// dispatch_thunk returns pointer to serialized arguments.
|
||||
let (serialized_result_val_ptr, serialized_result_val_len) = match result {
|
||||
// Unpack pointer and len of the serialized result data.
|
||||
Ok(Some(RuntimeValue::I64(v))) => {
|
||||
// Cast to u64 to use zero-extension.
|
||||
let v = v as u64;
|
||||
let ptr = (v as u64 >> 32) as u32;
|
||||
let len = (v & 0xFFFFFFFF) as u32;
|
||||
(ptr, len)
|
||||
}
|
||||
_ => return Err(trap()),
|
||||
};
|
||||
|
||||
let serialized_result_val = self.supervisor_externals
|
||||
.read_memory(serialized_result_val_ptr, serialized_result_val_len)?;
|
||||
self.supervisor_externals
|
||||
.deallocate(serialized_result_val_ptr);
|
||||
|
||||
// We do not have to check the signature here, because it's automatically
|
||||
// checked by wasmi.
|
||||
|
||||
deserialize_result(&serialized_result_val)
|
||||
}
|
||||
}
|
||||
|
||||
fn with_guest_externals<FE, R, F>(
|
||||
supervisor_externals: &mut FE,
|
||||
sandbox_instance: &SandboxInstance,
|
||||
state: u32,
|
||||
f: F,
|
||||
) -> R
|
||||
where
|
||||
FE: SandboxCapabilities + Externals,
|
||||
F: FnOnce(&mut GuestExternals<FE>) -> 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: 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<FE: SandboxCapabilities + Externals>(
|
||||
&self,
|
||||
export_name: &str,
|
||||
args: &[RuntimeValue],
|
||||
supervisor_externals: &mut FE,
|
||||
state: u32,
|
||||
) -> Result<Option<wasmi::RuntimeValue>, 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: &[Option<MemoryRef>],
|
||||
) -> Result<(Imports, GuestToSupervisorFunctionMapping), UserError> {
|
||||
let env_def = sandbox_primitives::EnvironmentDefinition::decode(&mut &raw_env_def[..]).ok_or_else(|| UserError("Sandbox error"))?;
|
||||
|
||||
let mut func_map = HashMap::new();
|
||||
let mut memories_map = HashMap::new();
|
||||
let mut guest_to_supervisor_mapping = GuestToSupervisorFunctionMapping::new();
|
||||
|
||||
for entry in &env_def.entries {
|
||||
let module = entry.module_name.clone();
|
||||
let field = entry.field_name.clone();
|
||||
|
||||
match entry.entity {
|
||||
sandbox_primitives::ExternEntity::Function(func_idx) => {
|
||||
let externals_idx =
|
||||
guest_to_supervisor_mapping.define(SupervisorFuncIndex(func_idx as usize));
|
||||
func_map.insert((module, field), externals_idx);
|
||||
}
|
||||
sandbox_primitives::ExternEntity::Memory(memory_idx) => {
|
||||
let memory_ref = memories
|
||||
.get(memory_idx as usize)
|
||||
.cloned()
|
||||
.ok_or_else(|| UserError("Sandbox error"))?
|
||||
.ok_or_else(|| UserError("Sandbox error"))?;
|
||||
memories_map.insert((module, field), memory_ref);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok((
|
||||
Imports {
|
||||
func_map,
|
||||
memories_map,
|
||||
},
|
||||
guest_to_supervisor_mapping,
|
||||
))
|
||||
}
|
||||
|
||||
/// 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<FE: SandboxCapabilities + Externals>(
|
||||
supervisor_externals: &mut FE,
|
||||
dispatch_thunk: FuncRef,
|
||||
wasm: &[u8],
|
||||
raw_env_def: &[u8],
|
||||
state: u32,
|
||||
) -> Result<u32, UserError> {
|
||||
let (imports, guest_to_supervisor_mapping) =
|
||||
decode_environment_definition(raw_env_def, &supervisor_externals.store().memories)?;
|
||||
|
||||
let module = Module::from_buffer(wasm).map_err(|_| UserError("Sandbox error"))?;
|
||||
let instance = ModuleInstance::new(&module, &imports).map_err(|_| UserError("Sandbox error"))?;
|
||||
|
||||
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(|_| UserError("Sandbox error"))
|
||||
},
|
||||
)?;
|
||||
|
||||
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 {
|
||||
// Memories and instances are `Some` untill torndown.
|
||||
instances: Vec<Option<Rc<SandboxInstance>>>,
|
||||
memories: Vec<Option<MemoryRef>>,
|
||||
}
|
||||
|
||||
impl Store {
|
||||
/// Create a new empty sandbox store.
|
||||
pub fn new() -> Store {
|
||||
Store {
|
||||
instances: Vec::new(),
|
||||
memories: Vec::new(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Create a new memory instance and return it's index.
|
||||
///
|
||||
/// # Errors
|
||||
///
|
||||
/// Returns `Err` if the memory couldn't be created.
|
||||
/// Typically happens if `initial` is more than `maximum`.
|
||||
pub fn new_memory(&mut self, initial: u32, maximum: u32) -> Result<u32, UserError> {
|
||||
let maximum = match maximum {
|
||||
sandbox_primitives::MEM_UNLIMITED => None,
|
||||
specified_limit => Some(Pages(specified_limit as usize)),
|
||||
};
|
||||
|
||||
let mem =
|
||||
MemoryInstance::alloc(Pages(initial as usize), maximum).map_err(|_| UserError("Sandbox error"))?;
|
||||
let mem_idx = self.memories.len();
|
||||
self.memories.push(Some(mem));
|
||||
Ok(mem_idx as u32)
|
||||
}
|
||||
|
||||
/// Returns `SandboxInstance` by `instance_idx`.
|
||||
///
|
||||
/// # Errors
|
||||
///
|
||||
/// Returns `Err` If `instance_idx` isn't a valid index of an instance or
|
||||
/// instance is already torndown.
|
||||
pub fn instance(&self, instance_idx: u32) -> Result<Rc<SandboxInstance>, UserError> {
|
||||
self.instances
|
||||
.get(instance_idx as usize)
|
||||
.cloned()
|
||||
.ok_or_else(|| UserError("Sandbox error"))?
|
||||
.ok_or_else(|| UserError("Sandbox error"))
|
||||
}
|
||||
|
||||
/// Returns reference to a memory instance by `memory_idx`.
|
||||
///
|
||||
/// # Errors
|
||||
///
|
||||
/// Returns `Err` If `memory_idx` isn't a valid index of an memory or
|
||||
/// memory is already torndown.
|
||||
pub fn memory(&self, memory_idx: u32) -> Result<MemoryRef, UserError> {
|
||||
self.memories
|
||||
.get(memory_idx as usize)
|
||||
.cloned()
|
||||
.ok_or_else(|| UserError("Sandbox error"))?
|
||||
.ok_or_else(|| UserError("Sandbox error"))
|
||||
}
|
||||
|
||||
/// Teardown the memory at the specified index.
|
||||
///
|
||||
/// # Errors
|
||||
///
|
||||
/// Returns `Err` if `memory_idx` isn't a valid index of an memory.
|
||||
pub fn memory_teardown(&mut self, memory_idx: u32) -> Result<(), UserError> {
|
||||
if memory_idx as usize >= self.memories.len() {
|
||||
return Err(UserError("Sandbox error"));
|
||||
}
|
||||
self.memories[memory_idx as usize] = None;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Teardown the instance at the specified index.
|
||||
pub fn instance_teardown(&mut self, instance_idx: u32) -> Result<(), UserError> {
|
||||
if instance_idx as usize >= self.instances.len() {
|
||||
return Err(UserError("Sandbox error"));
|
||||
}
|
||||
self.instances[instance_idx as usize] = None;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn register_sandbox_instance(&mut self, sandbox_instance: Rc<SandboxInstance>) -> u32 {
|
||||
let instance_idx = self.instances.len();
|
||||
self.instances.push(Some(sandbox_instance));
|
||||
instance_idx as u32
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use wasm_executor::WasmExecutor;
|
||||
use state_machine::TestExternalities;
|
||||
use wabt;
|
||||
|
||||
#[test]
|
||||
fn sandbox_should_work() {
|
||||
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)))
|
||||
(func (export "call")
|
||||
(drop
|
||||
(call $inc_counter (i32.const 5))
|
||||
)
|
||||
|
||||
(call $inc_counter (i32.const 3))
|
||||
;; current counter value is on the stack
|
||||
|
||||
;; check whether current == 8
|
||||
i32.const 8
|
||||
i32.eq
|
||||
|
||||
call $assert
|
||||
)
|
||||
)
|
||||
"#).unwrap();
|
||||
|
||||
assert_eq!(
|
||||
WasmExecutor::new().call(&mut ext, 8, &test_code[..], "test_sandbox", &code).unwrap(),
|
||||
vec![1],
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn sandbox_trap() {
|
||||
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)))
|
||||
(func (export "call")
|
||||
i32.const 0
|
||||
call $assert
|
||||
)
|
||||
)
|
||||
"#).unwrap();
|
||||
|
||||
assert_eq!(
|
||||
WasmExecutor::new().call(&mut ext, 8, &test_code[..], "test_sandbox", &code).unwrap(),
|
||||
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::new().call(&mut ext, 8, &test_code[..], "test_sandbox", &code).unwrap(),
|
||||
vec![1],
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn invoke_args() {
|
||||
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)))
|
||||
|
||||
(func (export "call") (param $x i32) (param $y i64)
|
||||
;; assert that $x = 0x12345678
|
||||
(call $assert
|
||||
(i32.eq
|
||||
(get_local $x)
|
||||
(i32.const 0x12345678)
|
||||
)
|
||||
)
|
||||
|
||||
(call $assert
|
||||
(i64.eq
|
||||
(get_local $y)
|
||||
(i64.const 0x1234567887654321)
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
"#).unwrap();
|
||||
|
||||
assert_eq!(
|
||||
WasmExecutor::new().call(&mut ext, 8, &test_code[..], "test_sandbox_args", &code).unwrap(),
|
||||
vec![1],
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn return_val() {
|
||||
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
|
||||
(func (export "call") (param $x i32) (result i32)
|
||||
(i32.add
|
||||
(get_local $x)
|
||||
(i32.const 1)
|
||||
)
|
||||
)
|
||||
)
|
||||
"#).unwrap();
|
||||
|
||||
assert_eq!(
|
||||
WasmExecutor::new().call(&mut ext, 8, &test_code[..], "test_sandbox_return_val", &code).unwrap(),
|
||||
vec![1],
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,720 @@
|
||||
// Copyright 2017 Parity Technologies (UK) Ltd.
|
||||
// This file is part of Substrate.
|
||||
|
||||
// Substrate is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
|
||||
// Substrate is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Substrate. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
//! Rust implementation of Substrate contracts.
|
||||
|
||||
use std::cmp::Ordering;
|
||||
use std::collections::HashMap;
|
||||
|
||||
use wasmi::{
|
||||
Module, ModuleInstance, MemoryInstance, MemoryRef, TableRef, ImportsBuilder
|
||||
};
|
||||
use wasmi::RuntimeValue::{I32, I64};
|
||||
use wasmi::memory_units::{Pages, Bytes};
|
||||
use state_machine::Externalities;
|
||||
use error::{Error, ErrorKind, Result};
|
||||
use wasm_utils::UserError;
|
||||
use primitives::{blake2_256, twox_128, twox_256, ed25519};
|
||||
use primitives::hexdisplay::HexDisplay;
|
||||
use primitives::sandbox as sandbox_primitives;
|
||||
use primitives::Blake2Hasher;
|
||||
use triehash::ordered_trie_root;
|
||||
use sandbox;
|
||||
|
||||
|
||||
struct Heap {
|
||||
end: u32,
|
||||
}
|
||||
|
||||
impl Heap {
|
||||
/// Construct new `Heap` struct with a given number of pages.
|
||||
///
|
||||
/// Returns `Err` if the heap couldn't allocate required
|
||||
/// number of pages.
|
||||
///
|
||||
/// This could mean that wasm binary specifies memory
|
||||
/// limit and we are trying to allocate beyond that limit.
|
||||
fn new(memory: &MemoryRef, pages: usize) -> Result<Self> {
|
||||
let prev_page_count = memory.initial();
|
||||
memory.grow(Pages(pages)).map_err(|_| Error::from(ErrorKind::Runtime))?;
|
||||
Ok(Heap {
|
||||
end: Bytes::from(prev_page_count).0 as u32,
|
||||
})
|
||||
}
|
||||
|
||||
fn allocate(&mut self, size: u32) -> u32 {
|
||||
let r = self.end;
|
||||
self.end += size;
|
||||
r
|
||||
}
|
||||
|
||||
fn deallocate(&mut self, _offset: u32) {
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature="wasm-extern-trace")]
|
||||
macro_rules! debug_trace {
|
||||
( $( $x:tt )* ) => ( trace!( $( $x )* ) )
|
||||
}
|
||||
#[cfg(not(feature="wasm-extern-trace"))]
|
||||
macro_rules! debug_trace {
|
||||
( $( $x:tt )* ) => ()
|
||||
}
|
||||
|
||||
struct FunctionExecutor<'e, E: Externalities<Blake2Hasher> + 'e> {
|
||||
sandbox_store: sandbox::Store,
|
||||
heap: Heap,
|
||||
memory: MemoryRef,
|
||||
table: Option<TableRef>,
|
||||
ext: &'e mut E,
|
||||
hash_lookup: HashMap<Vec<u8>, Vec<u8>>,
|
||||
}
|
||||
|
||||
impl<'e, E: Externalities<Blake2Hasher>> FunctionExecutor<'e, E> {
|
||||
fn new(m: MemoryRef, heap_pages: usize, t: Option<TableRef>, e: &'e mut E) -> Result<Self> {
|
||||
Ok(FunctionExecutor {
|
||||
sandbox_store: sandbox::Store::new(),
|
||||
heap: Heap::new(&m, heap_pages)?,
|
||||
memory: m,
|
||||
table: t,
|
||||
ext: e,
|
||||
hash_lookup: HashMap::new(),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl<'e, E: Externalities<Blake2Hasher>> sandbox::SandboxCapabilities for FunctionExecutor<'e, 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)
|
||||
}
|
||||
fn deallocate(&mut self, ptr: u32) {
|
||||
self.heap.deallocate(ptr)
|
||||
}
|
||||
fn write_memory(&mut self, ptr: u32, data: &[u8]) -> ::std::result::Result<(), UserError> {
|
||||
self.memory.set(ptr, data).map_err(|_| UserError("Invalid attempt to write_memory"))
|
||||
}
|
||||
fn read_memory(&self, ptr: u32, len: u32) -> ::std::result::Result<Vec<u8>, UserError> {
|
||||
self.memory.get(ptr, len as usize).map_err(|_| UserError("Invalid attempt to write_memory"))
|
||||
}
|
||||
}
|
||||
|
||||
trait WritePrimitive<T: Sized> {
|
||||
fn write_primitive(&self, offset: u32, t: T) -> ::std::result::Result<(), UserError>;
|
||||
}
|
||||
|
||||
impl WritePrimitive<u32> for MemoryInstance {
|
||||
fn write_primitive(&self, offset: u32, t: u32) -> ::std::result::Result<(), UserError> {
|
||||
use byteorder::{LittleEndian, ByteOrder};
|
||||
let mut r = [0u8; 4];
|
||||
LittleEndian::write_u32(&mut r, t);
|
||||
self.set(offset, &r).map_err(|_| UserError("Invalid attempt to write_primitive"))
|
||||
}
|
||||
}
|
||||
|
||||
trait ReadPrimitive<T: Sized> {
|
||||
fn read_primitive(&self, offset: u32) -> ::std::result::Result<T, UserError>;
|
||||
}
|
||||
|
||||
impl ReadPrimitive<u32> for MemoryInstance {
|
||||
fn read_primitive(&self, offset: u32) -> ::std::result::Result<u32, UserError> {
|
||||
use byteorder::{LittleEndian, ByteOrder};
|
||||
Ok(LittleEndian::read_u32(&self.get(offset, 4).map_err(|_| UserError("Invalid attempt to read_primitive"))?))
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: this macro does not support `where` clauses and that seems somewhat tricky to add
|
||||
impl_function_executor!(this: FunctionExecutor<'e, E>,
|
||||
ext_print_utf8(utf8_data: *const u8, utf8_len: u32) => {
|
||||
if let Ok(utf8) = this.memory.get(utf8_data, utf8_len as usize) {
|
||||
if let Ok(message) = String::from_utf8(utf8) {
|
||||
println!("{}", message);
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
},
|
||||
ext_print_hex(data: *const u8, len: u32) => {
|
||||
if let Ok(hex) = this.memory.get(data, len as usize) {
|
||||
println!("{}", HexDisplay::from(&hex));
|
||||
}
|
||||
Ok(())
|
||||
},
|
||||
ext_print_num(number: u64) => {
|
||||
println!("{}", number);
|
||||
Ok(())
|
||||
},
|
||||
ext_memcmp(s1: *const u8, s2: *const u8, n: usize) -> i32 => {
|
||||
let sl1 = this.memory.get(s1, n as usize).map_err(|_| UserError("Invalid attempt to read from memory in first arg of ext_memcmp"))?;
|
||||
let sl2 = this.memory.get(s2, n as usize).map_err(|_| UserError("Invalid attempt to read from memory in second arg of ext_memcmp"))?;
|
||||
Ok(match sl1.cmp(&sl2) {
|
||||
Ordering::Greater => 1,
|
||||
Ordering::Less => -1,
|
||||
Ordering::Equal => 0,
|
||||
})
|
||||
},
|
||||
ext_memcpy(dest: *mut u8, src: *const u8, count: usize) -> *mut u8 => {
|
||||
this.memory.copy_nonoverlapping(src as usize, dest as usize, count as usize)
|
||||
.map_err(|_| UserError("Invalid attempt to copy_nonoverlapping in ext_memcpy"))?;
|
||||
debug_trace!(target: "sr-io", "memcpy {} from {}, {} bytes", dest, src, count);
|
||||
Ok(dest)
|
||||
},
|
||||
ext_memmove(dest: *mut u8, src: *const u8, count: usize) -> *mut u8 => {
|
||||
this.memory.copy(src as usize, dest as usize, count as usize)
|
||||
.map_err(|_| UserError("Invalid attempt to copy in ext_memmove"))?;
|
||||
debug_trace!(target: "sr-io", "memmove {} from {}, {} bytes", dest, src, count);
|
||||
Ok(dest)
|
||||
},
|
||||
ext_memset(dest: *mut u8, val: u32, count: usize) -> *mut u8 => {
|
||||
debug_trace!(target: "sr-io", "memset {} with {}, {} bytes", dest, val, count);
|
||||
this.memory.clear(dest as usize, val as u8, count as usize)
|
||||
.map_err(|_| UserError("Invalid attempt to clear in ext_memset"))?;
|
||||
Ok(dest)
|
||||
},
|
||||
ext_malloc(size: usize) -> *mut u8 => {
|
||||
let r = this.heap.allocate(size);
|
||||
debug_trace!(target: "sr-io", "malloc {} bytes at {}", size, r);
|
||||
Ok(r)
|
||||
},
|
||||
ext_free(addr: *mut u8) => {
|
||||
this.heap.deallocate(addr);
|
||||
debug_trace!(target: "sr-io", "free {}", addr);
|
||||
Ok(())
|
||||
},
|
||||
ext_set_storage(key_data: *const u8, key_len: u32, value_data: *const u8, value_len: u32) => {
|
||||
let key = this.memory.get(key_data, key_len as usize).map_err(|_| UserError("Invalid attempt to determine key in ext_set_storage"))?;
|
||||
let value = this.memory.get(value_data, value_len as usize).map_err(|_| UserError("Invalid attempt to determine value in ext_set_storage"))?;
|
||||
if let Some(_preimage) = this.hash_lookup.get(&key) {
|
||||
debug_trace!(target: "wasm-trace", "*** Setting storage: %{} -> {} [k={}]", ::primitives::hexdisplay::ascii_format(&_preimage), HexDisplay::from(&value), HexDisplay::from(&key));
|
||||
} else {
|
||||
debug_trace!(target: "wasm-trace", "*** Setting storage: {} -> {} [k={}]", ::primitives::hexdisplay::ascii_format(&key), HexDisplay::from(&value), HexDisplay::from(&key));
|
||||
}
|
||||
this.ext.set_storage(key, value);
|
||||
Ok(())
|
||||
},
|
||||
ext_clear_storage(key_data: *const u8, key_len: u32) => {
|
||||
let key = this.memory.get(key_data, key_len as usize).map_err(|_| UserError("Invalid attempt to determine key in ext_clear_storage"))?;
|
||||
debug_trace!(target: "wasm-trace", "*** Clearing storage: {} [k={}]",
|
||||
if let Some(_preimage) = this.hash_lookup.get(&key) {
|
||||
format!("%{}", ::primitives::hexdisplay::ascii_format(&_preimage))
|
||||
} else {
|
||||
format!(" {}", ::primitives::hexdisplay::ascii_format(&key))
|
||||
}, HexDisplay::from(&key));
|
||||
this.ext.clear_storage(&key);
|
||||
Ok(())
|
||||
},
|
||||
ext_exists_storage(key_data: *const u8, key_len: u32) -> u32 => {
|
||||
let key = this.memory.get(key_data, key_len as usize).map_err(|_| UserError("Invalid attempt to determine key in ext_exists_storage"))?;
|
||||
Ok(if this.ext.exists_storage(&key) { 1 } else { 0 })
|
||||
},
|
||||
ext_clear_prefix(prefix_data: *const u8, prefix_len: u32) => {
|
||||
let prefix = this.memory.get(prefix_data, prefix_len as usize).map_err(|_| UserError("Invalid attempt to determine prefix in ext_clear_prefix"))?;
|
||||
this.ext.clear_prefix(&prefix);
|
||||
Ok(())
|
||||
},
|
||||
// return 0 and place u32::max_value() into written_out if no value exists for the key.
|
||||
ext_get_allocated_storage(key_data: *const u8, key_len: u32, written_out: *mut u32) -> *mut u8 => {
|
||||
let key = this.memory.get(key_data, key_len as usize).map_err(|_| UserError("Invalid attempt to determine key in ext_get_allocated_storage"))?;
|
||||
let maybe_value = this.ext.storage(&key);
|
||||
|
||||
debug_trace!(target: "wasm-trace", "*** Getting storage: {} == {} [k={}]",
|
||||
if let Some(_preimage) = this.hash_lookup.get(&key) {
|
||||
format!("%{}", ::primitives::hexdisplay::ascii_format(&_preimage))
|
||||
} else {
|
||||
format!(" {}", ::primitives::hexdisplay::ascii_format(&key))
|
||||
},
|
||||
if let Some(ref b) = maybe_value {
|
||||
format!("{}", HexDisplay::from(b))
|
||||
} else {
|
||||
"<empty>".to_owned()
|
||||
},
|
||||
HexDisplay::from(&key)
|
||||
);
|
||||
|
||||
if let Some(value) = maybe_value {
|
||||
let offset = this.heap.allocate(value.len() as u32) as u32;
|
||||
this.memory.set(offset, &value).map_err(|_| UserError("Invalid attempt to set memory in ext_get_allocated_storage"))?;
|
||||
this.memory.write_primitive(written_out, value.len() as u32)
|
||||
.map_err(|_| UserError("Invalid attempt to write written_out in ext_get_allocated_storage"))?;
|
||||
Ok(offset)
|
||||
} else {
|
||||
this.memory.write_primitive(written_out, u32::max_value())
|
||||
.map_err(|_| UserError("Invalid attempt to write failed written_out in ext_get_allocated_storage"))?;
|
||||
Ok(0)
|
||||
}
|
||||
},
|
||||
// return u32::max_value() if no value exists for the key.
|
||||
ext_get_storage_into(key_data: *const u8, key_len: u32, value_data: *mut u8, value_len: u32, value_offset: u32) -> u32 => {
|
||||
let key = this.memory.get(key_data, key_len as usize).map_err(|_| UserError("Invalid attempt to get key in ext_get_storage_into"))?;
|
||||
let maybe_value = this.ext.storage(&key);
|
||||
debug_trace!(target: "wasm-trace", "*** Getting storage: {} == {} [k={}]",
|
||||
if let Some(_preimage) = this.hash_lookup.get(&key) {
|
||||
format!("%{}", ::primitives::hexdisplay::ascii_format(&_preimage))
|
||||
} else {
|
||||
format!(" {}", ::primitives::hexdisplay::ascii_format(&key))
|
||||
},
|
||||
if let Some(ref b) = maybe_value {
|
||||
format!("{}", HexDisplay::from(b))
|
||||
} else {
|
||||
"<empty>".to_owned()
|
||||
},
|
||||
HexDisplay::from(&key)
|
||||
);
|
||||
|
||||
if let Some(value) = maybe_value {
|
||||
let value = &value[value_offset as usize..];
|
||||
let written = ::std::cmp::min(value_len as usize, value.len());
|
||||
this.memory.set(value_data, &value[..written]).map_err(|_| UserError("Invalid attempt to set value in ext_get_storage_into"))?;
|
||||
Ok(written as u32)
|
||||
} else {
|
||||
Ok(u32::max_value())
|
||||
}
|
||||
},
|
||||
ext_storage_root(result: *mut u8) => {
|
||||
let r = this.ext.storage_root();
|
||||
this.memory.set(result, r.as_ref()).map_err(|_| UserError("Invalid attempt to set memory in ext_storage_root"))?;
|
||||
Ok(())
|
||||
},
|
||||
ext_blake2_256_enumerated_trie_root(values_data: *const u8, lens_data: *const u32, lens_len: u32, result: *mut u8) => {
|
||||
let values = (0..lens_len)
|
||||
.map(|i| this.memory.read_primitive(lens_data + i * 4))
|
||||
.collect::<::std::result::Result<Vec<u32>, UserError>>()?
|
||||
.into_iter()
|
||||
.scan(0u32, |acc, v| { let o = *acc; *acc += v; Some((o, v)) })
|
||||
.map(|(offset, len)|
|
||||
this.memory.get(values_data + offset, len as usize)
|
||||
.map_err(|_| UserError("Invalid attempt to get memory in ext_blake2_256_enumerated_trie_root"))
|
||||
)
|
||||
.collect::<::std::result::Result<Vec<_>, UserError>>()?;
|
||||
let r = ordered_trie_root::<Blake2Hasher, _, _>(values.into_iter());
|
||||
this.memory.set(result, &r[..]).map_err(|_| UserError("Invalid attempt to set memory in ext_blake2_256_enumerated_trie_root"))?;
|
||||
Ok(())
|
||||
},
|
||||
ext_chain_id() -> u64 => {
|
||||
Ok(this.ext.chain_id())
|
||||
},
|
||||
ext_twox_128(data: *const u8, len: u32, out: *mut u8) => {
|
||||
let result = if len == 0 {
|
||||
let hashed = twox_128(&[0u8; 0]);
|
||||
debug_trace!(target: "xxhash", "XXhash: '' -> {}", HexDisplay::from(&hashed));
|
||||
this.hash_lookup.insert(hashed.to_vec(), vec![]);
|
||||
hashed
|
||||
} else {
|
||||
let key = this.memory.get(data, len as usize).map_err(|_| UserError("Invalid attempt to get key in ext_twox_128"))?;
|
||||
let hashed_key = twox_128(&key);
|
||||
debug_trace!(target: "xxhash", "XXhash: {} -> {}",
|
||||
if let Ok(_skey) = ::std::str::from_utf8(&key) {
|
||||
_skey.to_owned()
|
||||
} else {
|
||||
format!("{}", HexDisplay::from(&key))
|
||||
},
|
||||
HexDisplay::from(&hashed_key)
|
||||
);
|
||||
this.hash_lookup.insert(hashed_key.to_vec(), key);
|
||||
hashed_key
|
||||
};
|
||||
|
||||
this.memory.set(out, &result).map_err(|_| UserError("Invalid attempt to set result in ext_twox_128"))?;
|
||||
Ok(())
|
||||
},
|
||||
ext_twox_256(data: *const u8, len: u32, out: *mut u8) => {
|
||||
let result = if len == 0 {
|
||||
twox_256(&[0u8; 0])
|
||||
} else {
|
||||
twox_256(&this.memory.get(data, len as usize).map_err(|_| UserError("Invalid attempt to get data in ext_twox_256"))?)
|
||||
};
|
||||
this.memory.set(out, &result).map_err(|_| UserError("Invalid attempt to set result in ext_twox_256"))?;
|
||||
Ok(())
|
||||
},
|
||||
ext_blake2_256(data: *const u8, len: u32, out: *mut u8) => {
|
||||
let result = if len == 0 {
|
||||
blake2_256(&[0u8; 0])
|
||||
} else {
|
||||
blake2_256(&this.memory.get(data, len as usize).map_err(|_| UserError("Invalid attempt to get data in ext_blake2_256"))?)
|
||||
};
|
||||
this.memory.set(out, &result).map_err(|_| UserError("Invalid attempt to set result in ext_blake2_256"))?;
|
||||
Ok(())
|
||||
},
|
||||
ext_ed25519_verify(msg_data: *const u8, msg_len: u32, sig_data: *const u8, pubkey_data: *const u8) -> u32 => {
|
||||
let mut sig = [0u8; 64];
|
||||
this.memory.get_into(sig_data, &mut sig[..]).map_err(|_| UserError("Invalid attempt to get signature in ext_ed25519_verify"))?;
|
||||
let mut pubkey = [0u8; 32];
|
||||
this.memory.get_into(pubkey_data, &mut pubkey[..]).map_err(|_| UserError("Invalid attempt to get pubkey in ext_ed25519_verify"))?;
|
||||
let msg = this.memory.get(msg_data, msg_len as usize).map_err(|_| UserError("Invalid attempt to get message in ext_ed25519_verify"))?;
|
||||
|
||||
Ok(if ed25519::verify(&sig, &msg, &pubkey) {
|
||||
0
|
||||
} else {
|
||||
5
|
||||
})
|
||||
},
|
||||
ext_sandbox_instantiate(dispatch_thunk_idx: usize, wasm_ptr: *const u8, wasm_len: usize, imports_ptr: *const u8, imports_len: usize, state: usize) -> u32 => {
|
||||
let wasm = this.memory.get(wasm_ptr, wasm_len as usize).map_err(|_| UserError("Sandbox error"))?;
|
||||
let raw_env_def = this.memory.get(imports_ptr, imports_len as usize).map_err(|_| UserError("Sandbox error"))?;
|
||||
|
||||
// Extract a dispatch thunk from instance's table by the specified index.
|
||||
let dispatch_thunk = {
|
||||
let table = this.table.as_ref().ok_or_else(|| UserError("Sandbox error"))?;
|
||||
table.get(dispatch_thunk_idx)
|
||||
.map_err(|_| UserError("Sandbox error"))?
|
||||
.ok_or_else(|| UserError("Sandbox error"))?
|
||||
.clone()
|
||||
};
|
||||
|
||||
let instance_idx = sandbox::instantiate(this, dispatch_thunk, &wasm, &raw_env_def, state)?;
|
||||
|
||||
Ok(instance_idx as u32)
|
||||
},
|
||||
ext_sandbox_instance_teardown(instance_idx: u32) => {
|
||||
this.sandbox_store.instance_teardown(instance_idx)?;
|
||||
Ok(())
|
||||
},
|
||||
ext_sandbox_invoke(instance_idx: u32, export_ptr: *const u8, export_len: usize, args_ptr: *const u8, args_len: usize, return_val_ptr: *const u8, return_val_len: usize, state: usize) -> u32 => {
|
||||
use codec::{Decode, Encode};
|
||||
|
||||
trace!(target: "sr-sandbox", "invoke, instance_idx={}", instance_idx);
|
||||
let export = this.memory.get(export_ptr, export_len as usize)
|
||||
.map_err(|_| UserError("Sandbox error"))
|
||||
.and_then(|b|
|
||||
String::from_utf8(b)
|
||||
.map_err(|_| UserError("Sandbox error"))
|
||||
)?;
|
||||
|
||||
// Deserialize arguments and convert them into wasmi types.
|
||||
let serialized_args = this.memory.get(args_ptr, args_len as usize)
|
||||
.map_err(|_| UserError("Sandbox error"))?;
|
||||
let args = Vec::<sandbox_primitives::TypedValue>::decode(&mut &serialized_args[..])
|
||||
.ok_or_else(|| UserError("Sandbox error"))?
|
||||
.into_iter()
|
||||
.map(Into::into)
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
let instance = this.sandbox_store.instance(instance_idx)?;
|
||||
let result = instance.invoke(&export, &args, this, 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(UserError("Sandbox error"))?;
|
||||
}
|
||||
this.memory
|
||||
.set(return_val_ptr, val)
|
||||
.map_err(|_| UserError("Sandbox error"))?;
|
||||
Ok(sandbox_primitives::ERR_OK)
|
||||
})
|
||||
}
|
||||
Err(_) => Ok(sandbox_primitives::ERR_EXECUTION),
|
||||
}
|
||||
},
|
||||
ext_sandbox_memory_new(initial: u32, maximum: u32) -> u32 => {
|
||||
let mem_idx = this.sandbox_store.new_memory(initial, maximum)?;
|
||||
Ok(mem_idx)
|
||||
},
|
||||
ext_sandbox_memory_get(memory_idx: u32, offset: u32, buf_ptr: *mut u8, buf_len: usize) -> u32 => {
|
||||
let dst_memory = this.sandbox_store.memory(memory_idx)?;
|
||||
|
||||
let data: Vec<u8> = match dst_memory.get(offset, buf_len as usize) {
|
||||
Ok(data) => data,
|
||||
Err(_) => return Ok(sandbox_primitives::ERR_OUT_OF_BOUNDS),
|
||||
};
|
||||
match this.memory.set(buf_ptr, &data) {
|
||||
Err(_) => return Ok(sandbox_primitives::ERR_OUT_OF_BOUNDS),
|
||||
_ => {},
|
||||
}
|
||||
|
||||
Ok(sandbox_primitives::ERR_OK)
|
||||
},
|
||||
ext_sandbox_memory_set(memory_idx: u32, offset: u32, val_ptr: *const u8, val_len: usize) -> u32 => {
|
||||
let dst_memory = this.sandbox_store.memory(memory_idx)?;
|
||||
|
||||
let data = match this.memory.get(offset, val_len as usize) {
|
||||
Ok(data) => data,
|
||||
Err(_) => return Ok(sandbox_primitives::ERR_OUT_OF_BOUNDS),
|
||||
};
|
||||
match dst_memory.set(val_ptr, &data) {
|
||||
Err(_) => return Ok(sandbox_primitives::ERR_OUT_OF_BOUNDS),
|
||||
_ => {},
|
||||
}
|
||||
|
||||
Ok(sandbox_primitives::ERR_OK)
|
||||
},
|
||||
ext_sandbox_memory_teardown(memory_idx: u32) => {
|
||||
this.sandbox_store.memory_teardown(memory_idx)?;
|
||||
Ok(())
|
||||
},
|
||||
=> <'e, E: Externalities<Blake2Hasher> + 'e>
|
||||
);
|
||||
|
||||
/// 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.
|
||||
/// This should be used for tests only.
|
||||
pub fn call<E: Externalities<Blake2Hasher>>(
|
||||
&self,
|
||||
ext: &mut E,
|
||||
heap_pages: usize,
|
||||
code: &[u8],
|
||||
method: &str,
|
||||
data: &[u8],
|
||||
) -> Result<Vec<u8>> {
|
||||
let module = ::wasmi::Module::from_buffer(code).expect("all modules compiled with rustc are valid wasm code; qed");
|
||||
self.call_in_wasm_module(ext, heap_pages, &module, method, data)
|
||||
}
|
||||
|
||||
/// Call a given method in the given wasm-module runtime.
|
||||
pub fn call_in_wasm_module<E: Externalities<Blake2Hasher>>(
|
||||
&self,
|
||||
ext: &mut E,
|
||||
heap_pages: usize,
|
||||
module: &Module,
|
||||
method: &str,
|
||||
data: &[u8],
|
||||
) -> Result<Vec<u8>> {
|
||||
// start module instantiation. Don't run 'start' function yet.
|
||||
let intermediate_instance = ModuleInstance::new(
|
||||
module,
|
||||
&ImportsBuilder::new()
|
||||
.with_resolver("env", FunctionExecutor::<E>::resolver())
|
||||
)?;
|
||||
|
||||
// extract a reference to a linear memory, optional reference to a table
|
||||
// and then initialize FunctionExecutor.
|
||||
let memory = intermediate_instance
|
||||
.not_started_instance()
|
||||
.export_by_name("memory")
|
||||
// TODO: with code coming from the blockchain it isn't strictly been compiled with rustc anymore.
|
||||
// these assumptions are probably not true anymore
|
||||
.expect("all modules compiled with rustc should have an export named 'memory'; qed")
|
||||
.as_memory()
|
||||
.expect("in module generated by rustc export named 'memory' should be a memory; qed")
|
||||
.clone();
|
||||
let table: Option<TableRef> = intermediate_instance
|
||||
.not_started_instance()
|
||||
.export_by_name("__indirect_function_table")
|
||||
.and_then(|e| e.as_table().cloned());
|
||||
|
||||
let mut fec = FunctionExecutor::new(memory.clone(), heap_pages, table, ext)?;
|
||||
|
||||
// finish instantiation by running 'start' function (if any).
|
||||
let instance = intermediate_instance.run_start(&mut fec)?;
|
||||
|
||||
let size = data.len() as u32;
|
||||
let offset = fec.heap.allocate(size);
|
||||
memory.set(offset, &data)?;
|
||||
|
||||
let result = instance.invoke_export(
|
||||
method,
|
||||
&[
|
||||
I32(offset as i32),
|
||||
I32(size as i32)
|
||||
],
|
||||
&mut fec
|
||||
);
|
||||
|
||||
let returned = match result {
|
||||
Ok(x) => x,
|
||||
Err(e) => {
|
||||
trace!(target: "wasm-executor", "Failed to execute code with {} pages", heap_pages);
|
||||
return Err(e.into())
|
||||
},
|
||||
};
|
||||
|
||||
if let Some(I64(r)) = returned {
|
||||
let offset = r as u32;
|
||||
let length = (r >> 32) as u32 as usize;
|
||||
memory.get(offset, length)
|
||||
.map_err(|_| ErrorKind::Runtime.into())
|
||||
} else {
|
||||
Err(ErrorKind::InvalidReturn.into())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use codec::Encode;
|
||||
use state_machine::TestExternalities;
|
||||
|
||||
// TODO: move into own crate.
|
||||
macro_rules! map {
|
||||
($( $name:expr => $value:expr ),*) => (
|
||||
vec![ $( ( $name, $value ) ),* ].into_iter().collect()
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn returning_should_work() {
|
||||
let mut ext = TestExternalities::default();
|
||||
let test_code = include_bytes!("../wasm/target/wasm32-unknown-unknown/release/runtime_test.compact.wasm");
|
||||
|
||||
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 = include_bytes!("../wasm/target/wasm32-unknown-unknown/release/runtime_test.compact.wasm");
|
||||
|
||||
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", &[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 = include_bytes!("../wasm/target/wasm32-unknown-unknown/release/runtime_test.compact.wasm");
|
||||
|
||||
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<_> = 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()
|
||||
];
|
||||
assert_eq!(expected, ext);
|
||||
}
|
||||
|
||||
#[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 = include_bytes!("../wasm/target/wasm32-unknown-unknown/release/runtime_test.compact.wasm");
|
||||
|
||||
// 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<_> = 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()
|
||||
];
|
||||
assert_eq!(expected, ext);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn blake2_256_should_work() {
|
||||
let mut ext = TestExternalities::default();
|
||||
let test_code = include_bytes!("../wasm/target/wasm32-unknown-unknown/release/runtime_test.compact.wasm");
|
||||
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 twox_256_should_work() {
|
||||
let mut ext = TestExternalities::default();
|
||||
let test_code = include_bytes!("../wasm/target/wasm32-unknown-unknown/release/runtime_test.compact.wasm");
|
||||
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 = include_bytes!("../wasm/target/wasm32-unknown-unknown/release/runtime_test.compact.wasm");
|
||||
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 = include_bytes!("../wasm/target/wasm32-unknown-unknown/release/runtime_test.compact.wasm");
|
||||
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 enumerated_trie_root_should_work() {
|
||||
let mut ext = TestExternalities::default();
|
||||
let test_code = include_bytes!("../wasm/target/wasm32-unknown-unknown/release/runtime_test.compact.wasm");
|
||||
assert_eq!(
|
||||
WasmExecutor::new().call(&mut ext, 8, &test_code[..], "test_enumerated_trie_root", &[]).unwrap(),
|
||||
ordered_trie_root::<Blake2Hasher, _, _>(vec![b"zero".to_vec(), b"one".to_vec(), b"two".to_vec()]).0.encode()
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -0,0 +1,199 @@
|
||||
// Copyright 2017 Parity Technologies (UK) Ltd.
|
||||
// This file is part of Substrate.
|
||||
|
||||
// Substrate is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
|
||||
// Substrate is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Substrate. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
//! Rust implementation of Substrate contracts.
|
||||
|
||||
use wasmi::{ValueType, RuntimeValue, HostError};
|
||||
use wasmi::nan_preserving_float::{F32, F64};
|
||||
use std::fmt;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct UserError(pub &'static str);
|
||||
impl fmt::Display for UserError {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
write!(f, "UserError: {}", self.0)
|
||||
}
|
||||
}
|
||||
impl HostError for UserError {
|
||||
}
|
||||
|
||||
pub trait ConvertibleToWasm { const VALUE_TYPE: ValueType; type NativeType; fn to_runtime_value(self) -> RuntimeValue; }
|
||||
impl ConvertibleToWasm for i32 { type NativeType = i32; const VALUE_TYPE: ValueType = ValueType::I32; fn to_runtime_value(self) -> RuntimeValue { RuntimeValue::I32(self) } }
|
||||
impl ConvertibleToWasm for u32 { type NativeType = u32; const VALUE_TYPE: ValueType = ValueType::I32; fn to_runtime_value(self) -> RuntimeValue { RuntimeValue::I32(self as i32) } }
|
||||
impl ConvertibleToWasm for i64 { type NativeType = i64; const VALUE_TYPE: ValueType = ValueType::I64; fn to_runtime_value(self) -> RuntimeValue { RuntimeValue::I64(self) } }
|
||||
impl ConvertibleToWasm for u64 { type NativeType = u64; const VALUE_TYPE: ValueType = ValueType::I64; fn to_runtime_value(self) -> RuntimeValue { RuntimeValue::I64(self as i64) } }
|
||||
impl ConvertibleToWasm for F32 { type NativeType = F32; const VALUE_TYPE: ValueType = ValueType::F32; fn to_runtime_value(self) -> RuntimeValue { RuntimeValue::F32(self) } }
|
||||
impl ConvertibleToWasm for F64 { type NativeType = F64; const VALUE_TYPE: ValueType = ValueType::F64; fn to_runtime_value(self) -> RuntimeValue { RuntimeValue::F64(self) } }
|
||||
impl ConvertibleToWasm for isize { type NativeType = i32; const VALUE_TYPE: ValueType = ValueType::I32; fn to_runtime_value(self) -> RuntimeValue { RuntimeValue::I32(self as i32) } }
|
||||
impl ConvertibleToWasm for usize { type NativeType = u32; const VALUE_TYPE: ValueType = ValueType::I32; fn to_runtime_value(self) -> RuntimeValue { RuntimeValue::I32(self as u32 as i32) } }
|
||||
impl<T> ConvertibleToWasm for *const T { type NativeType = u32; const VALUE_TYPE: ValueType = ValueType::I32; fn to_runtime_value(self) -> RuntimeValue { RuntimeValue::I32(self as isize as i32) } }
|
||||
impl<T> ConvertibleToWasm for *mut T { type NativeType = u32; const VALUE_TYPE: ValueType = ValueType::I32; fn to_runtime_value(self) -> RuntimeValue { RuntimeValue::I32(self as isize as i32) } }
|
||||
|
||||
#[macro_export]
|
||||
macro_rules! convert_args {
|
||||
() => ([]);
|
||||
( $( $t:ty ),* ) => ( [ $( { use $crate::wasm_utils::ConvertibleToWasm; <$t>::VALUE_TYPE }, )* ] );
|
||||
}
|
||||
|
||||
#[macro_export]
|
||||
macro_rules! gen_signature {
|
||||
( ( $( $params: ty ),* ) ) => (
|
||||
{
|
||||
$crate::wasmi::Signature::new(&convert_args!($($params),*)[..], None)
|
||||
}
|
||||
);
|
||||
|
||||
( ( $( $params: ty ),* ) -> $returns: ty ) => (
|
||||
{
|
||||
$crate::wasmi::Signature::new(&convert_args!($($params),*)[..], Some({
|
||||
use $crate::wasm_utils::ConvertibleToWasm; <$returns>::VALUE_TYPE
|
||||
}))
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
macro_rules! resolve_fn {
|
||||
(@iter $index:expr, $sig_var:ident, $name_var:ident) => ();
|
||||
(@iter $index:expr, $sig_var:ident, $name_var:ident $name:ident ( $( $params:ty ),* ) $( -> $returns:ty )* => $($tail:tt)* ) => (
|
||||
if $name_var == stringify!($name) {
|
||||
let signature = gen_signature!( ( $( $params ),* ) $( -> $returns )* );
|
||||
if $sig_var != &signature {
|
||||
return Err($crate::wasmi::Error::Instantiation(
|
||||
format!("Export {} has different signature {:?}", $name_var, $sig_var),
|
||||
));
|
||||
}
|
||||
return Ok($crate::wasmi::FuncInstance::alloc_host(signature, $index));
|
||||
}
|
||||
resolve_fn!(@iter $index + 1, $sig_var, $name_var $($tail)*)
|
||||
);
|
||||
|
||||
($sig_var:ident, $name_var:ident, $($tail:tt)* ) => (
|
||||
resolve_fn!(@iter 0, $sig_var, $name_var $($tail)*);
|
||||
);
|
||||
}
|
||||
|
||||
#[macro_export]
|
||||
macro_rules! unmarshall_args {
|
||||
( $body:tt, $objectname:ident, $args_iter:ident, $( $names:ident : $params:ty ),*) => ({
|
||||
$(
|
||||
let $names : <$params as $crate::wasm_utils::ConvertibleToWasm>::NativeType =
|
||||
$args_iter.next()
|
||||
.and_then(|rt_val| rt_val.try_into())
|
||||
.expect(
|
||||
"`$args_iter` comes from an argument of Externals::invoke_index;
|
||||
args to an external call always matches the signature of the external;
|
||||
external signatures are built with count and types and in order defined by `$params`;
|
||||
here, we iterating on `$params`;
|
||||
qed;
|
||||
"
|
||||
);
|
||||
)*
|
||||
$body
|
||||
})
|
||||
}
|
||||
|
||||
/// Since we can't specify the type of closure directly at binding site:
|
||||
///
|
||||
/// ```rust,ignore
|
||||
/// let f: FnOnce() -> Result<<u32 as ConvertibleToWasm>::NativeType, _> = || { /* ... */ };
|
||||
/// ```
|
||||
///
|
||||
/// we use this function to constrain the type of the closure.
|
||||
#[inline(always)]
|
||||
pub fn constrain_closure<R, F>(f: F) -> F
|
||||
where
|
||||
F: FnOnce() -> Result<R, ::wasmi::Trap>
|
||||
{
|
||||
f
|
||||
}
|
||||
|
||||
#[macro_export]
|
||||
macro_rules! marshall {
|
||||
( $args_iter:ident, $objectname:ident, ( $( $names:ident : $params:ty ),* ) -> $returns:ty => $body:tt ) => ({
|
||||
let body = $crate::wasm_utils::constrain_closure::<
|
||||
<$returns as $crate::wasm_utils::ConvertibleToWasm>::NativeType, _
|
||||
>(|| {
|
||||
unmarshall_args!($body, $objectname, $args_iter, $( $names : $params ),*)
|
||||
});
|
||||
let r = body()?;
|
||||
return Ok(Some({ use $crate::wasm_utils::ConvertibleToWasm; r.to_runtime_value() }))
|
||||
});
|
||||
( $args_iter:ident, $objectname:ident, ( $( $names:ident : $params:ty ),* ) => $body:tt ) => ({
|
||||
let body = $crate::wasm_utils::constrain_closure::<(), _>(|| {
|
||||
unmarshall_args!($body, $objectname, $args_iter, $( $names : $params ),*)
|
||||
});
|
||||
body()?;
|
||||
return Ok(None)
|
||||
})
|
||||
}
|
||||
|
||||
macro_rules! dispatch_fn {
|
||||
( @iter $index:expr, $index_ident:ident, $objectname:ident, $args_iter:ident) => {
|
||||
// `$index` comes from an argument of Externals::invoke_index;
|
||||
// externals are always invoked with index given by resolve_fn! at resolve time;
|
||||
// For each next function resolve_fn! gives new index, starting from 0;
|
||||
// Both dispatch_fn! and resolve_fn! are called with the same list of functions;
|
||||
// qed;
|
||||
panic!("fn with index {} is undefined", $index);
|
||||
};
|
||||
|
||||
( @iter $index:expr, $index_ident:ident, $objectname:ident, $args_iter:ident, $name:ident ( $( $names:ident : $params:ty ),* ) $( -> $returns:ty )* => $body:tt $($tail:tt)*) => (
|
||||
if $index_ident == $index {
|
||||
{ marshall!($args_iter, $objectname, ( $( $names : $params ),* ) $( -> $returns )* => $body) }
|
||||
}
|
||||
dispatch_fn!( @iter $index + 1, $index_ident, $objectname, $args_iter $($tail)*)
|
||||
);
|
||||
|
||||
( $index_ident:ident, $objectname:ident, $args_iter:ident, $($tail:tt)* ) => (
|
||||
dispatch_fn!( @iter 0, $index_ident, $objectname, $args_iter, $($tail)*);
|
||||
);
|
||||
}
|
||||
|
||||
#[macro_export]
|
||||
macro_rules! impl_function_executor {
|
||||
( $objectname:ident : $structname:ty,
|
||||
$( $name:ident ( $( $names:ident : $params:ty ),* ) $( -> $returns:ty )* => $body:tt , )*
|
||||
=> $($pre:tt)+ ) => (
|
||||
impl $( $pre ) + $structname {
|
||||
#[allow(unused)]
|
||||
fn resolver() -> &'static $crate::wasmi::ModuleImportResolver {
|
||||
struct Resolver;
|
||||
impl $crate::wasmi::ModuleImportResolver for Resolver {
|
||||
fn resolve_func(&self, name: &str, signature: &$crate::wasmi::Signature) -> ::std::result::Result<$crate::wasmi::FuncRef, $crate::wasmi::Error> {
|
||||
resolve_fn!(signature, name, $( $name( $( $params ),* ) $( -> $returns )* => )*);
|
||||
|
||||
Err($crate::wasmi::Error::Instantiation(
|
||||
format!("Export {} not found", name),
|
||||
))
|
||||
}
|
||||
}
|
||||
&Resolver
|
||||
}
|
||||
}
|
||||
|
||||
impl $( $pre ) + $crate::wasmi::Externals for $structname {
|
||||
fn invoke_index(
|
||||
&mut self,
|
||||
index: usize,
|
||||
args: $crate::wasmi::RuntimeArgs,
|
||||
) -> ::std::result::Result<Option<$crate::wasmi::RuntimeValue>, $crate::wasmi::Trap> {
|
||||
let $objectname = self;
|
||||
let mut args = args.as_ref().iter();
|
||||
dispatch_fn!(index, $objectname, args, $( $name( $( $names : $params ),* ) $( -> $returns )* => $body ),*);
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user