Add get_global for Sandbox (#4756)

* Add `get_global` for `Sandbox`

This pr adds `get_global` to retrieve a `global` variable from an
instantiated sandbox wasm blob.

* Bump `spec_version`

* Update primitives/wasm-interface/src/lib.rs

Co-Authored-By: Sergei Pepyakin <sergei@parity.io>

* `get_global` -> `get_global_val`

Co-authored-by: Sergei Pepyakin <s.pepyakin@gmail.com>
Co-authored-by: Gavin Wood <github@gavwood.com>
This commit is contained in:
Bastian Köcher
2020-01-29 16:24:40 +01:00
committed by GitHub
parent ae1e9002d7
commit 4c36143375
23 changed files with 275 additions and 195 deletions
-93
View File
@@ -24,99 +24,6 @@ use sp_std::vec::Vec;
#[derive(crate::RuntimeDebug)]
pub struct HostError;
/// Representation of a typed wasm value.
#[derive(Clone, Copy, PartialEq, Encode, Decode)]
#[derive(crate::RuntimeDebug)]
pub enum TypedValue {
/// Value of 32-bit signed or unsigned integer.
#[codec(index = "1")]
I32(i32),
/// Value of 64-bit signed or unsigned integer.
#[codec(index = "2")]
I64(i64),
/// Value of 32-bit IEEE 754-2008 floating point number represented as a bit pattern.
#[codec(index = "3")]
F32(i32),
/// Value of 64-bit IEEE 754-2008 floating point number represented as a bit pattern.
#[codec(index = "4")]
F64(i64),
}
impl TypedValue {
/// Returns `Some` if this value of type `I32`.
pub fn as_i32(&self) -> Option<i32> {
match *self {
TypedValue::I32(v) => Some(v),
_ => None,
}
}
}
#[cfg(feature = "std")]
impl From<::wasmi::RuntimeValue> for TypedValue {
fn from(val: ::wasmi::RuntimeValue) -> TypedValue {
use ::wasmi::RuntimeValue;
match val {
RuntimeValue::I32(v) => TypedValue::I32(v),
RuntimeValue::I64(v) => TypedValue::I64(v),
RuntimeValue::F32(v) => TypedValue::F32(v.to_bits() as i32),
RuntimeValue::F64(v) => TypedValue::F64(v.to_bits() as i64),
}
}
}
#[cfg(feature = "std")]
impl From<TypedValue> for ::wasmi::RuntimeValue {
fn from(val: TypedValue) -> ::wasmi::RuntimeValue {
use ::wasmi::RuntimeValue;
use ::wasmi::nan_preserving_float::{F32, F64};
match val {
TypedValue::I32(v) => RuntimeValue::I32(v),
TypedValue::I64(v) => RuntimeValue::I64(v),
TypedValue::F32(v_bits) => RuntimeValue::F32(F32::from_bits(v_bits as u32)),
TypedValue::F64(v_bits) => RuntimeValue::F64(F64::from_bits(v_bits as u64)),
}
}
}
/// Typed value that can be returned from a function.
///
/// Basically a `TypedValue` plus `Unit`, for functions which return nothing.
#[derive(Clone, Copy, PartialEq, Encode, Decode)]
#[derive(crate::RuntimeDebug)]
pub enum ReturnValue {
/// For returning nothing.
Unit,
/// For returning some concrete value.
Value(TypedValue),
}
impl From<TypedValue> for ReturnValue {
fn from(v: TypedValue) -> ReturnValue {
ReturnValue::Value(v)
}
}
impl ReturnValue {
/// Maximum number of bytes `ReturnValue` might occupy when serialized with
/// `Codec`.
///
/// Breakdown:
/// 1 byte for encoding unit/value variant
/// 1 byte for encoding value type
/// 8 bytes for encoding the biggest value types available in wasm: f64, i64.
pub const ENCODED_MAX_SIZE: usize = 10;
}
#[test]
fn return_value_encoded_max_size() {
let encoded = ReturnValue::Value(TypedValue::I64(-1)).encode();
assert_eq!(encoded.len(), ReturnValue::ENCODED_MAX_SIZE);
}
/// Describes an entity to define or import into the environment.
#[derive(Clone, PartialEq, Eq, Encode, Decode)]
#[derive(crate::RuntimeDebug)]
+2
View File
@@ -12,6 +12,7 @@ sp-core = { version = "2.0.0", default-features = false, path = "../core" }
sp-std = { version = "2.0.0", default-features = false, path = "../std" }
libsecp256k1 = { version = "0.3.4", optional = true }
sp-state-machine = { version = "0.8", optional = true, path = "../../primitives/state-machine" }
sp-wasm-interface = { version = "2.0.0", path = "../../primitives/wasm-interface", default-features = false }
sp-runtime-interface = { version = "2.0.0", default-features = false, path = "../runtime-interface" }
sp-trie = { version = "2.0.0", optional = true, path = "../../primitives/trie" }
sp-externalities = { version = "0.8.0", optional = true, path = "../externalities" }
@@ -29,6 +30,7 @@ std = [
"libsecp256k1",
"sp-runtime-interface/std",
"sp-externalities",
"sp-wasm-interface/std",
"log",
]
+8
View File
@@ -850,6 +850,14 @@ pub trait Sandbox {
fn instance_teardown(&mut self, instance_idx: u32) {
self.sandbox().instance_teardown(instance_idx).expect("Failed to teardown sandbox instance")
}
/// Get the value from a global with the given `name`. The sandbox is determined by the given
/// `instance_idx`.
///
/// Returns `Some(_)` when the requested global variable could be found.
fn get_global_val(&mut self, instance_idx: u32, name: &str) -> Option<sp_wasm_interface::Value> {
self.sandbox().get_global_val(instance_idx, name).expect("Failed to get global from sandbox")
}
}
/// Allocator used by Substrate when executing the Wasm runtime.
@@ -6,7 +6,7 @@ edition = "2018"
license = "GPL-3.0"
[dependencies]
sp-wasm-interface = { version = "2.0.0", optional = true, path = "../wasm-interface" }
sp-wasm-interface = { version = "2.0.0", path = "../wasm-interface", default-features = false }
sp-std = { version = "2.0.0", default-features = false, path = "../std" }
sp-runtime-interface-proc-macro = { version = "2.0.0", path = "proc-macro" }
sp-externalities = { version = "0.8.0", optional = true, path = "../externalities" }
@@ -25,7 +25,7 @@ trybuild = "1.0.17"
[features]
default = [ "std" ]
std = [
"sp-wasm-interface",
"sp-wasm-interface/std",
"sp-std/std",
"codec/std",
"sp-externalities",
@@ -17,7 +17,7 @@
//! Provides implementations for the runtime interface traits.
use crate::{
RIType, Pointer, pass_by::{PassBy, Codec, Inner, PassByInner},
RIType, Pointer, pass_by::{PassBy, Codec, Inner, PassByInner, Enum},
util::{unpack_ptr_and_len, pack_ptr_and_len},
};
#[cfg(feature = "std")]
@@ -523,3 +523,11 @@ macro_rules! for_u128_i128 {
for_u128_i128!(u128);
for_u128_i128!(i128);
impl PassBy for sp_wasm_interface::ValueType {
type PassBy = Enum<sp_wasm_interface::ValueType>;
}
impl PassBy for sp_wasm_interface::Value {
type PassBy = Codec<sp_wasm_interface::Value>;
}
+1 -1
View File
@@ -7,7 +7,7 @@ license = "GPL-3.0"
[dependencies]
serde = { version = "1.0.101", optional = true, features = ["derive"] }
codec = { package = "parity-scale-codec", version = "1.1.0", default-features = false, features = ["derive"] }
codec = { package = "parity-scale-codec", version = "1.1.2", default-features = false, features = ["derive"] }
sp-core = { version = "2.0.0", default-features = false, path = "../core" }
sp-application-crypto = { version = "2.0.0", default-features = false, path = "../application-crypto" }
sp-arithmetic = { version = "2.0.0", default-features = false, path = "../arithmetic" }
+2
View File
@@ -10,6 +10,7 @@ wasmi = { version = "0.6.2", optional = true }
sp-core = { version = "2.0.0", default-features = false, path = "../core" }
sp-std = { version = "2.0.0", default-features = false, path = "../std" }
sp-io = { version = "2.0.0", default-features = false, path = "../io" }
sp-wasm-interface = { version = "2.0.0", default-features = false, path = "../wasm-interface" }
codec = { package = "parity-scale-codec", version = "1.0.0", default-features = false }
[dev-dependencies]
@@ -24,5 +25,6 @@ std = [
"sp-std/std",
"codec/std",
"sp-io/std",
"sp-wasm-interface/std",
]
strict = []
+11 -3
View File
@@ -40,7 +40,8 @@
use sp_std::prelude::*;
pub use sp_core::sandbox::{TypedValue, ReturnValue, HostError};
pub use sp_core::sandbox::HostError;
pub use sp_wasm_interface::{Value, ReturnValue};
mod imp {
#[cfg(feature = "std")]
@@ -75,7 +76,7 @@ impl From<Error> for HostError {
/// supervisor in [`EnvironmentDefinitionBuilder`].
///
/// [`EnvironmentDefinitionBuilder`]: struct.EnvironmentDefinitionBuilder.html
pub type HostFuncType<T> = fn(&mut T, &[TypedValue]) -> Result<ReturnValue, HostError>;
pub type HostFuncType<T> = fn(&mut T, &[Value]) -> Result<ReturnValue, HostError>;
/// Reference to a sandboxed linear memory, that
/// will be used by the guest module.
@@ -197,9 +198,16 @@ impl<T> Instance<T> {
pub fn invoke(
&mut self,
name: &str,
args: &[TypedValue],
args: &[Value],
state: &mut T,
) -> Result<ReturnValue, Error> {
self.inner.invoke(name, args, state)
}
/// Get the value from a global with the given `name`.
///
/// Returns `Some(_)` if the global could be found.
pub fn get_global_val(&self, name: &str) -> Option<Value> {
self.inner.get_global_val(name)
}
}
+28 -39
View File
@@ -23,7 +23,7 @@ use wasmi::{
RuntimeArgs, RuntimeValue, Signature, TableDescriptor, TableRef, Trap, TrapKind
};
use wasmi::memory_units::Pages;
use super::{Error, TypedValue, ReturnValue, HostFuncType, HostError};
use super::{Error, Value, ReturnValue, HostFuncType, HostError};
#[derive(Clone)]
pub struct Memory {
@@ -88,27 +88,7 @@ impl fmt::Display for DummyHostError {
}
}
impl wasmi::HostError for DummyHostError {
}
fn from_runtime_value(v: RuntimeValue) -> TypedValue {
match v {
RuntimeValue::I32(v) => TypedValue::I32(v),
RuntimeValue::I64(v) => TypedValue::I64(v),
RuntimeValue::F32(v) => TypedValue::F32(v.to_bits() as i32),
RuntimeValue::F64(v) => TypedValue::F64(v.to_bits() as i64),
}
}
fn to_runtime_value(v: TypedValue) -> RuntimeValue {
use wasmi::nan_preserving_float::{F32, F64};
match v {
TypedValue::I32(v) => RuntimeValue::I32(v as i32),
TypedValue::I64(v) => RuntimeValue::I64(v as i64),
TypedValue::F32(v_bits) => RuntimeValue::F32(F32::from_bits(v_bits as u32)),
TypedValue::F64(v_bits) => RuntimeValue::F64(F64::from_bits(v_bits as u64)),
}
}
impl wasmi::HostError for DummyHostError {}
struct GuestExternals<'a, T: 'a> {
state: &'a mut T,
@@ -124,13 +104,13 @@ impl<'a, T> Externals for GuestExternals<'a, T> {
let args = args.as_ref()
.iter()
.cloned()
.map(from_runtime_value)
.map(Into::into)
.collect::<Vec<_>>();
let result = (self.defined_host_functions.funcs[index])(self.state, &args);
match result {
Ok(value) => Ok(match value {
ReturnValue::Value(v) => Some(to_runtime_value(v)),
ReturnValue::Value(v) => Some(v.into()),
ReturnValue::Unit => None,
}),
Err(HostError) => Err(TrapKind::Host(Box::new(DummyHostError)).into()),
@@ -253,7 +233,7 @@ impl<T> ImportResolver for EnvironmentDefinitionBuilder<T> {
pub struct Instance<T> {
instance: ModuleRef,
defined_host_functions: DefinedHostFunctions<T>,
_marker: ::std::marker::PhantomData<T>,
_marker: std::marker::PhantomData<T>,
}
impl<T> Instance<T> {
@@ -281,14 +261,14 @@ impl<T> Instance<T> {
Ok(Instance {
instance,
defined_host_functions,
_marker: ::std::marker::PhantomData::<T>,
_marker: std::marker::PhantomData::<T>,
})
}
pub fn invoke(
&mut self,
name: &str,
args: &[TypedValue],
args: &[Value],
state: &mut T,
) -> Result<ReturnValue, Error> {
let args = args.iter().cloned().map(Into::into).collect::<Vec<_>>();
@@ -306,20 +286,29 @@ impl<T> Instance<T> {
Err(_err) => Err(Error::Execution),
}
}
pub fn get_global_val(&self, name: &str) -> Option<Value> {
let global = self.instance
.export_by_name(name)?
.as_global()?
.get();
Some(global.into())
}
}
#[cfg(test)]
mod tests {
use wabt;
use crate::{Error, TypedValue, ReturnValue, HostError, EnvironmentDefinitionBuilder, Instance};
use crate::{Error, Value, ReturnValue, HostError, EnvironmentDefinitionBuilder, Instance};
use assert_matches::assert_matches;
fn execute_sandboxed(code: &[u8], args: &[TypedValue]) -> Result<ReturnValue, HostError> {
fn execute_sandboxed(code: &[u8], args: &[Value]) -> Result<ReturnValue, HostError> {
struct State {
counter: u32,
}
fn env_assert(_e: &mut State, args: &[TypedValue]) -> Result<ReturnValue, HostError> {
fn env_assert(_e: &mut State, args: &[Value]) -> Result<ReturnValue, HostError> {
if args.len() != 1 {
return Err(HostError);
}
@@ -330,16 +319,16 @@ mod tests {
Err(HostError)
}
}
fn env_inc_counter(e: &mut State, args: &[TypedValue]) -> Result<ReturnValue, HostError> {
fn env_inc_counter(e: &mut State, args: &[Value]) -> Result<ReturnValue, HostError> {
if args.len() != 1 {
return Err(HostError);
}
let inc_by = args[0].as_i32().ok_or_else(|| HostError)?;
e.counter += inc_by as u32;
Ok(ReturnValue::Value(TypedValue::I32(e.counter as i32)))
Ok(ReturnValue::Value(Value::I32(e.counter as i32)))
}
/// Function that takes one argument of any type and returns that value.
fn env_polymorphic_id(_e: &mut State, args: &[TypedValue]) -> Result<ReturnValue, HostError> {
fn env_polymorphic_id(_e: &mut State, args: &[Value]) -> Result<ReturnValue, HostError> {
if args.len() != 1 {
return Err(HostError);
}
@@ -387,8 +376,8 @@ mod tests {
let result = execute_sandboxed(
&code,
&[
TypedValue::I32(0x12345678),
TypedValue::I64(0x1234567887654321),
Value::I32(0x12345678),
Value::I64(0x1234567887654321),
]
);
assert!(result.is_ok());
@@ -410,10 +399,10 @@ mod tests {
let return_val = execute_sandboxed(
&code,
&[
TypedValue::I32(0x1336),
Value::I32(0x1336),
]
).unwrap();
assert_eq!(return_val, ReturnValue::Value(TypedValue::I32(0x1337)));
assert_eq!(return_val, ReturnValue::Value(Value::I32(0x1337)));
}
#[test]
@@ -453,8 +442,8 @@ mod tests {
#[test]
fn cant_return_unmatching_type() {
fn env_returns_i32(_e: &mut (), _args: &[TypedValue]) -> Result<ReturnValue, HostError> {
Ok(ReturnValue::Value(TypedValue::I32(42)))
fn env_returns_i32(_e: &mut (), _args: &[Value]) -> Result<ReturnValue, HostError> {
Ok(ReturnValue::Value(Value::I32(42)))
}
let mut env_builder = EnvironmentDefinitionBuilder::new();
+9 -5
View File
@@ -18,7 +18,7 @@ use codec::{Decode, Encode};
use sp_core::sandbox as sandbox_primitives;
use sp_io::sandbox;
use sp_std::{prelude::*, slice, marker, mem, vec, rc::Rc};
use super::{Error, TypedValue, ReturnValue, HostFuncType};
use super::{Error, Value, ReturnValue, HostFuncType};
mod ffi {
use sp_std::mem;
@@ -183,7 +183,7 @@ extern "C" fn dispatch_thunk<T>(
slice::from_raw_parts(serialized_args_ptr, serialized_args_len)
}
};
let args = Vec::<TypedValue>::decode(&mut &serialized_args[..]).expect(
let args = Vec::<Value>::decode(&mut &serialized_args[..]).expect(
"serialized args should be provided by the runtime;
correctly serialized data should be deserializable;
qed",
@@ -244,11 +244,11 @@ impl<T> Instance<T> {
pub fn invoke(
&mut self,
name: &str,
args: &[TypedValue],
args: &[Value],
state: &mut T,
) -> Result<ReturnValue, Error> {
let serialized_args = args.to_vec().encode();
let mut return_val = vec![0u8; sandbox_primitives::ReturnValue::ENCODED_MAX_SIZE];
let mut return_val = vec![0u8; ReturnValue::ENCODED_MAX_SIZE];
let result = sandbox::invoke(
self.instance_idx,
@@ -261,7 +261,7 @@ impl<T> Instance<T> {
match result {
sandbox_primitives::ERR_OK => {
let return_val = sandbox_primitives::ReturnValue::decode(&mut &return_val[..])
let return_val = ReturnValue::decode(&mut &return_val[..])
.map_err(|_| Error::Execution)?;
Ok(return_val)
}
@@ -269,6 +269,10 @@ impl<T> Instance<T> {
_ => unreachable!(),
}
}
pub fn get_global_val(&self, name: &str) -> Option<Value> {
sandbox::get_global_val(self.instance_idx, name)
}
}
impl<T> Drop for Instance<T> {
@@ -9,7 +9,8 @@ license = "GPL-3.0"
wasmi = { version = "0.6.2", optional = true }
impl-trait-for-tuples = "0.1.2"
sp-std = { version = "2.0.0", path = "../std", default-features = false }
codec = { package = "parity-scale-codec", version = "1.1.2", default-features = false, features = ["derive"] }
[features]
default = [ "std" ]
std = [ "wasmi", "sp-std/std" ]
std = [ "wasmi", "sp-std/std", "codec/std" ]
+75 -1
View File
@@ -44,8 +44,33 @@ pub enum ValueType {
F64,
}
impl From<ValueType> for u8 {
fn from(val: ValueType) -> u8 {
match val {
ValueType::I32 => 0,
ValueType::I64 => 1,
ValueType::F32 => 2,
ValueType::F64 => 3,
}
}
}
impl sp_std::convert::TryFrom<u8> for ValueType {
type Error = ();
fn try_from(val: u8) -> sp_std::result::Result<ValueType, ()> {
match val {
0 => Ok(Self::I32),
1 => Ok(Self::I64),
2 => Ok(Self::F32),
3 => Ok(Self::F64),
_ => Err(()),
}
}
}
/// Values supported by Substrate on the boundary between host/Wasm.
#[derive(PartialEq, Debug, Clone, Copy)]
#[derive(PartialEq, Debug, Clone, Copy, codec::Encode, codec::Decode)]
pub enum Value {
/// A 32-bit integer.
I32(i32),
@@ -71,6 +96,14 @@ impl Value {
Value::F64(_) => ValueType::F64,
}
}
/// Return `Self` as `i32`.
pub fn as_i32(&self) -> Option<i32> {
match self {
Self::I32(val) => Some(*val),
_ => None,
}
}
}
/// Provides `Sealed` trait to prevent implementing trait `PointerType` outside of this crate.
@@ -298,6 +331,12 @@ pub trait Sandbox {
raw_env_def: &[u8],
state: u32,
) -> Result<u32>;
/// Get the value from a global with the given `name`. The sandbox is determined by the
/// given `instance_idx` instance.
///
/// Returns `Some(_)` when the requested global variable could be found.
fn get_global_val(&self, instance_idx: u32, name: &str) -> Result<Option<Value>>;
}
/// Something that provides implementations for host functions.
@@ -409,9 +448,37 @@ impl ReadPrimitive<u64> for &mut dyn FunctionContext {
}
}
/// Typed value that can be returned from a function.
///
/// Basically a `TypedValue` plus `Unit`, for functions which return nothing.
#[derive(Clone, Copy, PartialEq, codec::Encode, codec::Decode, Debug)]
pub enum ReturnValue {
/// For returning nothing.
Unit,
/// For returning some concrete value.
Value(Value),
}
impl From<Value> for ReturnValue {
fn from(v: Value) -> ReturnValue {
ReturnValue::Value(v)
}
}
impl ReturnValue {
/// Maximum number of bytes `ReturnValue` might occupy when serialized with `SCALE`.
///
/// Breakdown:
/// 1 byte for encoding unit/value variant
/// 1 byte for encoding value type
/// 8 bytes for encoding the biggest value types available in wasm: f64, i64.
pub const ENCODED_MAX_SIZE: usize = 10;
}
#[cfg(test)]
mod tests {
use super::*;
use codec::Encode;
#[test]
fn pointer_offset_works() {
@@ -425,4 +492,11 @@ mod tests {
assert_eq!(ptr.offset(10).unwrap(), Pointer::new(80));
assert_eq!(ptr.offset(32).unwrap(), Pointer::new(256));
}
#[test]
fn return_value_encoded_max_size() {
let encoded = ReturnValue::Value(Value::I64(-1)).encode();
assert_eq!(encoded.len(), ReturnValue::ENCODED_MAX_SIZE);
}
}