Switch parachain interface to new runtime_interface macro (#665)

* Make use of `runtime_interface` for parachain externalities

This also changes the encoding of the `ValidationResult` return value to
match the default encoding used in Substrate.

* Fix compilation for web

* Update `Cargo.lock`

* Include feedback

* Move proc macro

* Update parachain/src/lib.rs

Co-Authored-By: Robert Habermeier <rphmeier@gmail.com>

Co-authored-by: Robert Habermeier <rphmeier@gmail.com>
This commit is contained in:
Bastian Köcher
2020-01-08 20:44:50 +01:00
committed by GitHub
parent 10f1f3a381
commit 06386558ae
16 changed files with 468 additions and 609 deletions
+13 -7
View File
@@ -7,17 +7,20 @@ edition = "2018"
[dependencies]
codec = { package = "parity-scale-codec", version = "1.1.0", default-features = false, features = [ "derive" ] }
wasmi = { version = "0.4.5", optional = true }
derive_more = { version = "0.14.1", optional = true }
serde = { version = "1.0.102", default-features = false, features = [ "derive" ] }
derive_more = { version = "0.99.2", optional = true }
serde = { version = "1.0.102", default-features = false, features = [ "derive" ], optional = true }
rstd = { package = "sp-std", git = "https://github.com/paritytech/substrate", branch = "polkadot-master", default-features = false }
sp-core = { git = "https://github.com/paritytech/substrate", branch = "polkadot-master", default-features = false }
sp-runtime-interface = { git = "https://github.com/paritytech/substrate", branch = "polkadot-master", default-features = false }
sp-externalities = { git = "https://github.com/paritytech/substrate", branch = "polkadot-master", optional = true }
sc-executor = { git = "https://github.com/paritytech/substrate", branch = "polkadot-master", optional = true }
sp-io = { git = "https://github.com/paritytech/substrate", branch = "polkadot-master", optional = true }
lazy_static = { version = "1.4.0", optional = true }
parking_lot = { version = "0.7.1", optional = true }
parking_lot = { version = "0.10.0", optional = true }
log = { version = "0.4.8", optional = true }
[target.'cfg(not(target_os = "unknown"))'.dependencies]
shared_memory = { version = "0.8.2", optional = true }
shared_memory = { version = "0.10.0", optional = true }
[dev-dependencies]
tiny-keccak = "1.5.0"
@@ -29,7 +32,6 @@ default = ["std"]
wasm-api = []
std = [
"codec/std",
"wasmi",
"derive_more",
"serde/std",
"rstd/std",
@@ -37,5 +39,9 @@ std = [
"sp-core/std",
"lazy_static",
"parking_lot",
"log"
"log",
"sp-runtime-interface/std",
"sp-externalities",
"sc-executor",
"sp-io",
]
+57 -42
View File
@@ -22,40 +22,40 @@
//! ## Parachain WASM
//!
//! Polkadot parachain WASM is in the form of a module which imports a memory
//! instance and exports a function `validate`.
//! instance and exports a function `validate_block`.
//!
//! `validate` accepts as input two `i32` values, representing a pointer/length pair
//! respectively, that encodes `ValidationParams`.
//! respectively, that encodes [`ValidationParams`].
//!
//! `validate` returns an `i32` which is a pointer to a little-endian 32-bit integer denoting a length.
//! Subtracting the length from the initial pointer will give a new pointer to the actual return data,
//! `validate` returns an `u64` which is a pointer to an `u8` array and its length.
//! The data in the array is expected to be a SCALE encoded [`ValidationResult`].
//!
//! ASCII-diagram demonstrating the return data format:
//!
//! ```ignore
//! [return data][len (LE-u32)]
//! ^~~returned pointer
//! [pointer][length]
//! 32bit 32bit
//! ^~~ returned pointer & length
//! ```
//!
//! The `wasm_api` module (enabled only with the wasm-api feature) provides utilities
//! for setting up a parachain WASM module in Rust.
//! The wasm-api (enabled only when `std` feature is not enabled and `wasm-api` feature is enabled)
//! provides utilities for setting up a parachain WASM module in Rust.
#![cfg_attr(not(feature = "std"), no_std)]
/// Re-export of parity-codec.
pub use codec;
#[cfg(feature = "std")]
pub mod wasm_executor;
#[cfg(feature = "wasm-api")]
pub mod wasm_api;
mod wasm_api;
use rstd::vec::Vec;
use rstd::{vec::Vec, cmp::Ordering};
use codec::{Encode, Decode, CompactAs};
use sp_core::{RuntimeDebug, TypeId};
#[cfg(all(not(feature = "std"), feature = "wasm-api"))]
pub use wasm_api::*;
/// Validation parameters for evaluating the parachain validity function.
// TODO: balance downloads (https://github.com/paritytech/polkadot/issues/220)
#[derive(PartialEq, Eq, Decode)]
@@ -155,7 +155,8 @@ pub trait AccountIdConversion<AccountId>: Sized {
fn try_from_account(a: &AccountId) -> Option<Self>;
}
/// Format is b"para" ++ encode(parachain ID) ++ 00.... where 00... is indefinite trailing zeroes to fill AccountId.
/// Format is b"para" ++ encode(parachain ID) ++ 00.... where 00... is indefinite trailing
/// zeroes to fill AccountId.
impl<T: Encode + Decode + Default> AccountIdConversion<T> for Id {
fn into_account(&self) -> T {
(b"para", self).using_encoded(|b|
@@ -177,24 +178,6 @@ impl<T: Encode + Decode + Default> AccountIdConversion<T> for Id {
}
}
/// An incoming message.
#[derive(PartialEq, Eq, Decode)]
#[cfg_attr(feature = "std", derive(Debug, Encode))]
pub struct IncomingMessage {
/// The source parachain.
pub source: Id,
/// The data of the message.
pub data: Vec<u8>,
}
/// A reference to a message.
pub struct MessageRef<'a> {
/// The target parachain.
pub target: Id,
/// Underlying data of the message.
pub data: &'a [u8],
}
/// Which origin a parachain's message to the relay chain should be dispatched from.
#[derive(Clone, PartialEq, Eq, Encode, Decode)]
#[cfg_attr(feature = "std", derive(Debug))]
@@ -224,16 +207,8 @@ impl rstd::convert::TryFrom<u8> for ParachainDispatchOrigin {
}
}
/// A reference to an upward message.
pub struct UpwardMessageRef<'a> {
/// The origin type.
pub origin: ParachainDispatchOrigin,
/// Underlying data of the message.
pub data: &'a [u8],
}
/// A message from a parachain to its Relay Chain.
#[derive(Clone, PartialEq, Eq, Encode, Decode)]
#[derive(Clone, PartialEq, Eq, Encode, Decode, sp_runtime_interface::pass_by::PassByCodec)]
#[cfg_attr(feature = "std", derive(Debug))]
pub struct UpwardMessage {
/// The origin for the message to be sent from.
@@ -241,3 +216,43 @@ pub struct UpwardMessage {
/// The message data.
pub data: Vec<u8>,
}
/// An incoming message.
#[derive(PartialEq, Eq, Decode)]
#[cfg_attr(feature = "std", derive(Debug, Encode))]
pub struct IncomingMessage {
/// The source parachain.
pub source: Id,
/// The data of the message.
pub data: Vec<u8>,
}
/// A message targeted to a specific parachain.
#[derive(Clone, PartialEq, Eq, Encode, Decode, sp_runtime_interface::pass_by::PassByCodec)]
#[cfg_attr(feature = "std", derive(serde::Serialize, serde::Deserialize, Debug))]
#[cfg_attr(feature = "std", serde(rename_all = "camelCase"))]
#[cfg_attr(feature = "std", serde(deny_unknown_fields))]
pub struct TargetedMessage {
/// The target parachain.
pub target: Id,
/// The message data.
pub data: Vec<u8>,
}
impl AsRef<[u8]> for TargetedMessage {
fn as_ref(&self) -> &[u8] {
&self.data[..]
}
}
impl PartialOrd for TargetedMessage {
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
Some(self.target.cmp(&other.target))
}
}
impl Ord for TargetedMessage {
fn cmp(&self, other: &Self) -> Ordering {
self.target.cmp(&other.target)
}
}
+30 -37
View File
@@ -16,13 +16,31 @@
//! Utilities for writing parachain WASM.
use codec::{Encode, Decode};
use super::{ValidationParams, ValidationResult, MessageRef, UpwardMessageRef};
use crate::{TargetedMessage, UpwardMessage};
use sp_runtime_interface::runtime_interface;
#[cfg(feature = "std")]
use sp_externalities::ExternalitiesExt;
mod ll {
extern "C" {
pub(super) fn ext_post_message(target: u32, data_ptr: *const u8, data_len: u32);
pub(super) fn ext_post_upward_message(origin: u32, data_ptr: *const u8, data_len: u32);
/// The parachain api for posting messages.
// Either activate on `std` to get access to the `HostFunctions` or when `wasm-api` is given and on
// `no_std`.
#[cfg(any(feature = "std", all(not(feature = "std"), feature = "wasm-api")))]
#[runtime_interface]
pub trait Parachain {
/// Post a message to another parachain.
fn post_message(&mut self, msg: TargetedMessage) {
self.extension::<crate::wasm_executor::ParachainExt>()
.expect("No `ParachainExt` associated with the current context.")
.post_message(msg)
.expect("Failed to post message")
}
/// Post a message to this parachain's relay chain.
fn post_upward_message(&mut self, msg: UpwardMessage) {
self.extension::<crate::wasm_executor::ParachainExt>()
.expect("No `ParachainExt` associated with the current context.")
.post_upward_message(msg)
.expect("Failed to post upward message")
}
}
@@ -30,43 +48,18 @@ mod ll {
///
/// Offset and length must have been provided by the validation
/// function's entry point.
pub unsafe fn load_params(params: *const u8, len: usize) -> ValidationParams {
#[cfg(not(feature = "std"))]
pub unsafe fn load_params(params: *const u8, len: usize) -> crate::ValidationParams {
let mut slice = rstd::slice::from_raw_parts(params, len);
ValidationParams::decode(&mut slice).expect("Invalid input data")
codec::Decode::decode(&mut slice).expect("Invalid input data")
}
/// Allocate the validation result in memory, getting the return-pointer back.
///
/// As described in the crate docs, this is a pointer to the appended length
/// of the vector.
pub fn write_result(result: ValidationResult) -> usize {
let mut encoded = result.encode();
let len = encoded.len();
assert!(len <= u32::max_value() as usize, "Len too large for parachain-WASM abi");
(len as u32).using_encoded(|s| encoded.extend(s));
// do not alter `encoded` beyond this point. may reallocate.
let end_ptr = &encoded[len] as *const u8 as usize;
// leak so it doesn't get zeroed.
rstd::mem::forget(encoded);
end_ptr
}
/// Post a message to another parachain.
pub fn post_message(message: MessageRef) {
let data_ptr = message.data.as_ptr();
let data_len = message.data.len();
unsafe { ll::ext_post_message(message.target.into(), data_ptr, data_len as u32) }
}
/// Post a message to this parachain's relay chain.
pub fn post_upward_message(message: UpwardMessageRef) {
let data_ptr = message.data.as_ptr();
let data_len = message.data.len();
unsafe { ll::ext_post_upward_message(u32::from(message.origin as u8), data_ptr, data_len as u32) }
#[cfg(not(feature = "std"))]
pub fn write_result(result: &crate::ValidationResult) -> u64 {
sp_core::to_substrate_wasm_fn_return_value(&result)
}
+141 -338
View File
@@ -20,16 +20,10 @@
//! Assuming the parameters are correct, this module provides a wrapper around
//! a WASM VM for re-execution of a parachain candidate.
use std::{cell::RefCell, fmt, convert::TryInto};
use crate::codec::{Decode, Encode};
use wasmi::{
self, Module, ModuleInstance, Trap, MemoryInstance, MemoryDescriptor, MemoryRef,
ModuleImportResolver, RuntimeValue, Externals, Error as WasmError, ValueType,
memory_units::{self, Bytes, Pages, RoundUpTo}
};
use super::{
ValidationParams, ValidationResult, MessageRef, UpwardMessageRef,
UpwardMessage, IncomingMessage};
use std::any::{TypeId, Any};
use crate::{ValidationParams, ValidationResult, UpwardMessage, TargetedMessage};
use codec::{Decode, Encode};
use sp_core::storage::{ChildStorageKey, ChildInfo};
#[cfg(not(target_os = "unknown"))]
pub use validation_host::{run_worker, EXECUTION_TIMEOUT_SEC};
@@ -40,12 +34,16 @@ mod validation_host;
const MAX_RUNTIME_MEM: usize = 1024 * 1024 * 1024; // 1 GiB
const MAX_CODE_MEM: usize = 16 * 1024 * 1024; // 16 MiB
mod ids {
/// Post a message to another parachain.
pub const POST_MESSAGE: usize = 1;
sp_externalities::decl_extension! {
/// The extension that is registered at the `Externalities` when validating a parachain state
/// transition.
pub(crate) struct ParachainExt(Box<dyn Externalities>);
}
/// Post a message to this parachain's relay chain.
pub const POST_UPWARD_MESSAGE: usize = 2;
impl ParachainExt {
pub fn new<T: Externalities + 'static>(ext: T) -> Self {
Self(Box::new(ext))
}
}
/// WASM code execution mode.
@@ -63,16 +61,16 @@ pub enum ExecutionMode {
/// Error type for the wasm executor
#[derive(Debug, derive_more::Display, derive_more::From)]
pub enum Error {
/// Wasm error
Wasm(WasmError),
/// Externalities error
Externalities(ExternalitiesError),
/// Wasm executor error.
#[display(fmt = "WASM executor error: {:?}", _0)]
WasmExecutor(sc_executor::error::Error),
/// Call data is too large.
#[display(fmt = "Validation parameters are {} bytes, max allowed is {}", _0, MAX_RUNTIME_MEM)]
#[from(ignore)]
ParamsTooLarge(usize),
/// Code size it too large.
#[display(fmt = "WASM code is {} bytes, max allowed is {}", _0, MAX_CODE_MEM)]
CodeTooLarge(usize),
/// Call data is too large.
#[display(fmt = "Validation parameters are {} bytes, max allowed is {}", _0, MAX_RUNTIME_MEM)]
ParamsTooLarge(usize),
/// Bad return data or type.
#[display(fmt = "Validation function returned invalid data.")]
BadReturn,
@@ -84,264 +82,54 @@ pub enum Error {
System(Box<dyn std::error::Error>),
#[display(fmt = "WASM worker error: {}", _0)]
External(String),
#[display(fmt = "Shared memory error: {}", _0)]
#[cfg(not(target_os = "unknown"))]
SharedMem(shared_memory::SharedMemError),
}
impl std::error::Error for Error {
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
match self {
Error::Wasm(ref err) => Some(err),
Error::Externalities(ref err) => Some(err),
Error::WasmExecutor(ref err) => Some(err),
Error::Io(ref err) => Some(err),
Error::System(ref err) => Some(&**err),
#[cfg(not(target_os = "unknown"))]
Error::SharedMem(ref err) => Some(err),
_ => None,
}
}
}
/// Errors that can occur in externalities of parachain validation.
#[derive(Debug, Clone)]
pub enum ExternalitiesError {
/// Unable to post a message due to the given reason.
CannotPostMessage(&'static str),
}
/// Externalities for parachain validation.
pub trait Externalities {
pub trait Externalities: Send {
/// Called when a message is to be posted to another parachain.
fn post_message(&mut self, message: MessageRef) -> Result<(), ExternalitiesError>;
fn post_message(&mut self, message: TargetedMessage) -> Result<(), String>;
/// Called when a message is to be posted to the parachain's relay chain.
fn post_upward_message(&mut self, message: UpwardMessageRef) -> Result<(), ExternalitiesError>;
}
impl fmt::Display for ExternalitiesError {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match *self {
ExternalitiesError::CannotPostMessage(ref s)
=> write!(f, "Cannot post message: {}", s),
}
}
}
impl wasmi::HostError for ExternalitiesError {}
impl std::error::Error for ExternalitiesError {}
struct Resolver {
max_memory: u32, // in pages.
memory: RefCell<Option<MemoryRef>>,
}
impl ModuleImportResolver for Resolver {
fn resolve_func(
&self,
field_name: &str,
signature: &wasmi::Signature
) -> Result<wasmi::FuncRef, WasmError> {
match field_name {
"ext_post_message" => {
let index = ids::POST_MESSAGE;
let (params, ret_ty): (&[ValueType], Option<ValueType>) =
(&[ValueType::I32, ValueType::I32, ValueType::I32], None);
if signature.params() != params || signature.return_type() != ret_ty {
Err(WasmError::Instantiation(
format!("Export {} has a bad signature", field_name)
))
} else {
Ok(wasmi::FuncInstance::alloc_host(
wasmi::Signature::new(&params[..], ret_ty),
index,
))
}
}
"ext_upwards_post_message" => {
let index = ids::POST_UPWARD_MESSAGE;
let (params, ret_ty): (&[ValueType], Option<ValueType>) =
(&[ValueType::I32, ValueType::I32], None);
if signature.params() != params || signature.return_type() != ret_ty {
Err(WasmError::Instantiation(
format!("Export {} has a bad signature", field_name)
))
} else {
Ok(wasmi::FuncInstance::alloc_host(
wasmi::Signature::new(&params[..], ret_ty),
index,
))
}
}
_ => {
Err(WasmError::Instantiation(
format!("Export {} not found", field_name),
))
}
}
}
fn resolve_memory(
&self,
field_name: &str,
descriptor: &MemoryDescriptor,
) -> Result<MemoryRef, WasmError> {
if field_name == "memory" {
let effective_max = descriptor.maximum().unwrap_or(self.max_memory);
if descriptor.initial() > self.max_memory || effective_max > self.max_memory {
Err(WasmError::Instantiation("Module requested too much memory".to_owned()))
} else {
let mem = MemoryInstance::alloc(
memory_units::Pages(descriptor.initial() as usize),
descriptor.maximum().map(|x| memory_units::Pages(x as usize)),
)?;
*self.memory.borrow_mut() = Some(mem.clone());
Ok(mem)
}
} else {
Err(WasmError::Instantiation("Memory imported under unknown name".to_owned()))
}
}
}
struct ValidationExternals<'a, E: 'a> {
externalities: &'a mut E,
memory: &'a MemoryRef,
}
impl<'a, E: 'a + Externalities> ValidationExternals<'a, E> {
/// Signature: post_message(u32, *const u8, u32) -> None
/// usage: post_message(target parachain, data ptr, data len).
/// Data is the raw data of the message.
fn ext_post_message(&mut self, args: ::wasmi::RuntimeArgs) -> Result<(), Trap> {
let target: u32 = args.nth_checked(0)?;
let data_ptr: u32 = args.nth_checked(1)?;
let data_len: u32 = args.nth_checked(2)?;
let (data_ptr, data_len) = (data_ptr as usize, data_len as usize);
self.memory.with_direct_access(|mem| {
if mem.len() < (data_ptr + data_len) {
Err(Trap::new(wasmi::TrapKind::MemoryAccessOutOfBounds))
} else {
let res = self.externalities.post_message(MessageRef {
target: target.into(),
data: &mem[data_ptr..][..data_len],
});
res.map_err(|e| Trap::new(wasmi::TrapKind::Host(
Box::new(e) as Box<_>
)))
}
})
}
/// Signature: post_upward_message(u32, *const u8, u32) -> None
/// usage: post_upward_message(origin, data ptr, data len).
/// Origin is the integer representation of the dispatch origin.
/// Data is the raw data of the message.
fn ext_post_upward_message(&mut self, args: ::wasmi::RuntimeArgs) -> Result<(), Trap> {
let origin: u32 = args.nth_checked(0)?;
let data_ptr: u32 = args.nth_checked(1)?;
let data_len: u32 = args.nth_checked(2)?;
let (data_ptr, data_len) = (data_ptr as usize, data_len as usize);
self.memory.with_direct_access(|mem| {
if mem.len() < (data_ptr + data_len) {
Err(Trap::new(wasmi::TrapKind::MemoryAccessOutOfBounds))
} else {
let origin = (origin as u8).try_into()
.map_err(|_| Trap::new(wasmi::TrapKind::UnexpectedSignature))?;
let message = UpwardMessageRef { origin, data: &mem[data_ptr..][..data_len] };
let res = self.externalities.post_upward_message(message);
res.map_err(|e| Trap::new(wasmi::TrapKind::Host(
Box::new(e) as Box<_>
)))
}
})
}
}
impl<'a, E: 'a + Externalities> Externals for ValidationExternals<'a, E> {
fn invoke_index(
&mut self,
index: usize,
args: ::wasmi::RuntimeArgs,
) -> Result<Option<RuntimeValue>, Trap> {
match index {
ids::POST_MESSAGE => self.ext_post_message(args).map(|_| None),
ids::POST_UPWARD_MESSAGE => self.ext_post_upward_message(args).map(|_| None),
_ => panic!("no externality at given index"),
}
}
}
/// Params header in shared memory. All offsets should be aligned to WASM page size.
#[derive(Encode, Decode, Debug)]
struct ValidationHeader {
code_size: u64,
params_size: u64,
}
#[derive(Encode, Decode, Debug)]
pub enum ValidationResultHeader {
Ok {
result: ValidationResult,
egress_message_count: u64,
up_message_count: u64,
},
Error(String),
}
#[derive(Default)]
struct WorkerExternalities {
egress_data: Vec<u8>,
egress_message_count: usize,
up_data: Vec<u8>,
up_message_count: usize,
}
impl Externalities for WorkerExternalities {
fn post_message(&mut self, message: MessageRef) -> Result<(), ExternalitiesError> {
IncomingMessage {
source: message.target,
data: message.data.to_vec(),
}
.encode_to(&mut self.egress_data);
self.egress_message_count += 1;
Ok(())
}
fn post_upward_message(&mut self, message: UpwardMessageRef) -> Result<(), ExternalitiesError> {
UpwardMessage {
origin: message.origin,
data: message.data.to_vec(),
}
.encode_to(&mut self.up_data);
self.up_message_count += 1;
Ok(())
}
fn post_upward_message(&mut self, message: UpwardMessage) -> Result<(), String>;
}
/// Validate a candidate under the given validation code.
///
/// This will fail if the validation code is not a proper parachain validation module.
pub fn validate_candidate<E: Externalities>(
pub fn validate_candidate<E: Externalities + 'static>(
validation_code: &[u8],
params: ValidationParams,
externalities: &mut E,
ext: E,
options: ExecutionMode,
) -> Result<ValidationResult, Error>
{
) -> Result<ValidationResult, Error> {
match options {
ExecutionMode::Local => {
validate_candidate_internal(validation_code, &params.encode(), externalities)
validate_candidate_internal(validation_code, &params.encode(), ext)
},
#[cfg(not(target_os = "unknown"))]
ExecutionMode::Remote =>
validation_host::validate_candidate(validation_code, params, externalities, false),
ExecutionMode::Remote => {
validation_host::validate_candidate(validation_code, params, ext, false)
},
#[cfg(not(target_os = "unknown"))]
ExecutionMode::RemoteTest =>
validation_host::validate_candidate(validation_code, params, externalities, true),
ExecutionMode::RemoteTest => {
validation_host::validate_candidate(validation_code, params, ext, true)
},
#[cfg(target_os = "unknown")]
ExecutionMode::Remote =>
Err(Error::System("Remote validator not available".to_string().into())),
@@ -351,105 +139,120 @@ pub fn validate_candidate<E: Externalities>(
}
}
/// The host functions provided by the wasm executor to the parachain wasm blob.
type HostFunctions = (sp_io::SubstrateHostFunctions, crate::wasm_api::parachain::HostFunctions);
/// Validate a candidate under the given validation code.
///
/// This will fail if the validation code is not a proper parachain validation module.
pub fn validate_candidate_internal<E: Externalities>(
pub fn validate_candidate_internal<E: Externalities + 'static>(
validation_code: &[u8],
encoded_call_data: &[u8],
externalities: &mut E,
) -> Result<ValidationResult, Error> {
use wasmi::LINEAR_MEMORY_PAGE_SIZE;
externalities: E,
) -> Result<ValidationResult, Error> {
let mut ext = ValidationExternalities(ParachainExt::new(externalities));
// instantiate the module.
let memory;
let mut externals;
let module = {
let module = Module::from_buffer(validation_code)?;
let module_resolver = Resolver {
max_memory: (MAX_RUNTIME_MEM / LINEAR_MEMORY_PAGE_SIZE.0) as u32,
memory: RefCell::new(None),
};
let module = ModuleInstance::new(
&module,
&wasmi::ImportsBuilder::new().with_resolver("env", &module_resolver),
)?;
memory = module_resolver.memory.borrow()
.as_ref()
.ok_or_else(|| WasmError::Instantiation("No imported memory instance".to_owned()))?
.clone();
externals = ValidationExternals {
externalities,
memory: &memory,
};
module.run_start(&mut externals).map_err(WasmError::Trap)?
};
// allocate call data in memory.
// we guarantee that:
// - `offset` has alignment at least of 8,
// - `len` is not zero.
let (offset, len) = {
// hard limit from WASM.
if encoded_call_data.len() > i32::max_value() as usize {
return Err(Error::ParamsTooLarge(encoded_call_data.len()));
}
// allocate sufficient amount of wasm pages to fit encoded call data.
let call_data_pages: Pages = Bytes(encoded_call_data.len()).round_up_to();
let allocated_mem_start: Bytes = memory.grow(call_data_pages)?.into();
memory.set(allocated_mem_start.0 as u32, &encoded_call_data)
.expect(
"enough memory allocated just before this; \
copying never fails if memory is large enough; qed"
);
(allocated_mem_start.0, encoded_call_data.len())
};
let output = module.invoke_export(
let res = sc_executor::call_in_wasm::<_, HostFunctions>(
"validate_block",
&[RuntimeValue::I32(offset as i32), RuntimeValue::I32(len as i32)],
&mut externals,
).map_err(|e| -> Error {
e.as_host_error()
.and_then(|he| he.downcast_ref::<ExternalitiesError>())
.map(|ee| Error::Externalities(ee.clone()))
.unwrap_or_else(move || e.into())
})?;
encoded_call_data,
sc_executor::WasmExecutionMethod::Interpreted,
&mut ext,
validation_code,
// TODO: Make sure we don't use more than 1GB: https://github.com/paritytech/polkadot/issues/699
1024,
)?;
match output {
Some(RuntimeValue::I32(len_offset)) => {
let len_offset = len_offset as u32;
ValidationResult::decode(&mut &res[..]).map_err(|_| Error::BadReturn.into())
}
let mut len_bytes = [0u8; 4];
memory.get_into(len_offset, &mut len_bytes)?;
let len_offset = len_offset as usize;
/// The validation externalities that will panic on any storage related access. They just provide
/// access to the parachain extension.
struct ValidationExternalities(ParachainExt);
let len = u32::decode(&mut &len_bytes[..])
.map_err(|_| Error::BadReturn)? as usize;
impl sp_externalities::Externalities for ValidationExternalities {
fn storage(&self, _: &[u8]) -> Option<Vec<u8>> {
panic!("storage: unsupported feature for parachain validation")
}
let return_offset = if len > len_offset {
return Err(Error::BadReturn);
} else {
len_offset - len
};
fn storage_hash(&self, _: &[u8]) -> Option<Vec<u8>> {
panic!("storage_hash: unsupported feature for parachain validation")
}
memory.with_direct_access(|mem| {
if mem.len() < return_offset + len {
return Err(Error::BadReturn);
}
fn child_storage_hash(&self, _: ChildStorageKey, _: ChildInfo, _: &[u8]) -> Option<Vec<u8>> {
panic!("child_storage_hash: unsupported feature for parachain validation")
}
ValidationResult::decode(&mut &mem[return_offset..][..len])
.map_err(|_| Error::BadReturn.into())
})
}
_ => Err(Error::BadReturn),
fn original_storage(&self, _: &[u8]) -> Option<Vec<u8>> {
panic!("original_sorage: unsupported feature for parachain validation")
}
fn original_child_storage(&self, _: ChildStorageKey, _: ChildInfo, _: &[u8]) -> Option<Vec<u8>> {
panic!("original_child_storage: unsupported feature for parachain validation")
}
fn original_storage_hash(&self, _: &[u8]) -> Option<Vec<u8>> {
panic!("original_storage_hash: unsupported feature for parachain validation")
}
fn original_child_storage_hash(&self, _: ChildStorageKey, _: ChildInfo, _: &[u8]) -> Option<Vec<u8>> {
panic!("original_child_storage_hash: unsupported feature for parachain validation")
}
fn child_storage(&self, _: ChildStorageKey, _: ChildInfo, _: &[u8]) -> Option<Vec<u8>> {
panic!("child_storage: unsupported feature for parachain validation")
}
fn kill_child_storage(&mut self, _: ChildStorageKey, _: ChildInfo) {
panic!("kill_child_storage: unsupported feature for parachain validation")
}
fn clear_prefix(&mut self, _: &[u8]) {
panic!("clear_prefix: unsupported feature for parachain validation")
}
fn clear_child_prefix(&mut self, _: ChildStorageKey, _: ChildInfo, _: &[u8]) {
panic!("clear_child_prefix: unsupported feature for parachain validation")
}
fn place_storage(&mut self, _: Vec<u8>, _: Option<Vec<u8>>) {
panic!("place_storage: unsupported feature for parachain validation")
}
fn place_child_storage(&mut self, _: ChildStorageKey, _: ChildInfo, _: Vec<u8>, _: Option<Vec<u8>>) {
panic!("place_child_storage: unsupported feature for parachain validation")
}
fn chain_id(&self) -> u64 {
panic!("chain_id: unsupported feature for parachain validation")
}
fn storage_root(&mut self) -> Vec<u8> {
panic!("storage_root: unsupported feature for parachain validation")
}
fn child_storage_root(&mut self, _: ChildStorageKey) -> Vec<u8> {
panic!("child_storage_root: unsupported feature for parachain validation")
}
fn storage_changes_root(&mut self, _: &[u8]) -> Result<Option<Vec<u8>>, ()> {
panic!("storage_changes_root: unsupported feature for parachain validation")
}
fn next_child_storage_key(&self, _: ChildStorageKey, _: ChildInfo, _: &[u8]) -> Option<Vec<u8>> {
panic!("next_child_storage_key: unsupported feature for parachain validation")
}
fn next_storage_key(&self, _: &[u8]) -> Option<Vec<u8>> {
panic!("next_storage_key: unsupported feature for parachain validation")
}
}
impl sp_externalities::ExtensionStore for ValidationExternalities {
fn extension_by_type_id(&mut self, type_id: TypeId) -> Option<&mut dyn Any> {
if type_id == TypeId::of::<ParachainExt>() {
Some(&mut self.0)
} else {
None
}
}
}
@@ -16,11 +16,10 @@
#![cfg(not(target_os = "unknown"))]
use std::{process, env, sync::Arc, sync::atomic};
use crate::codec::{Decode, Encode};
use crate::{ValidationParams, ValidationResult, MessageRef,
UpwardMessageRef, UpwardMessage, IncomingMessage};
use super::{validate_candidate_internal, Error, Externalities, WorkerExternalities};
use std::{process, env, sync::Arc, sync::atomic, mem};
use codec::{Decode, Encode, EncodeAppend};
use crate::{ValidationParams, ValidationResult, UpwardMessage, TargetedMessage};
use super::{validate_candidate_internal, Error, Externalities};
use super::{MAX_CODE_MEM, MAX_RUNTIME_MEM};
use shared_memory::{SharedMem, SharedMemConf, EventState, WriteLockable, EventWait, EventSet};
use parking_lot::Mutex;
@@ -39,6 +38,37 @@ const NUM_HOSTS: usize = 8;
/// Execution timeout in seconds;
pub const EXECUTION_TIMEOUT_SEC: u64 = 5;
#[derive(Default)]
struct WorkerExternalitiesInner {
egress_data: Vec<u8>,
up_data: Vec<u8>,
}
#[derive(Default, Clone)]
struct WorkerExternalities {
inner: Arc<Mutex<WorkerExternalitiesInner>>,
}
impl Externalities for WorkerExternalities {
fn post_message(&mut self, message: TargetedMessage) -> Result<(), String> {
let mut inner = self.inner.lock();
inner.egress_data = <Vec::<TargetedMessage> as EncodeAppend>::append_or_new(
mem::replace(&mut inner.egress_data, Vec::new()),
std::iter::once(message),
).map_err(|e| e.what())?;
Ok(())
}
fn post_upward_message(&mut self, message: UpwardMessage) -> Result<(), String> {
let mut inner = self.inner.lock();
inner.up_data = <Vec::<UpwardMessage> as EncodeAppend>::append_or_new(
mem::replace(&mut inner.up_data, Vec::new()),
std::iter::once(message),
).map_err(|e| e.what())?;
Ok(())
}
}
enum Event {
CandidateReady = 0,
ResultReady = 1,
@@ -59,7 +89,8 @@ pub fn run_worker(mem_id: &str) -> Result<(), String> {
return Err(format!("Error opening shared memory: {:?}", e));
}
};
let mut externalities = WorkerExternalities::default();
let worker_ext = WorkerExternalities::default();
let exit = Arc::new(atomic::AtomicBool::new(false));
// spawn parent monitor thread
@@ -67,7 +98,8 @@ pub fn run_worker(mem_id: &str) -> Result<(), String> {
std::thread::spawn(move || {
use std::io::Read;
let mut in_data = Vec::new();
std::io::stdin().read_to_end(&mut in_data).ok(); // pipe terminates when parent process exits
// pipe terminates when parent process exits
std::io::stdin().read_to_end(&mut in_data).ok();
debug!("Parent process is dead. Exiting");
exit.store(true, atomic::Ordering::Relaxed);
});
@@ -109,23 +141,25 @@ pub fn run_worker(mem_id: &str) -> Result<(), String> {
let (call_data, _) = call_data.split_at_mut(header.params_size as usize);
let message_data = rest;
let result = validate_candidate_internal(code, call_data, &mut externalities);
let result = validate_candidate_internal(code, call_data, worker_ext.clone());
debug!("Candidate validated: {:?}", result);
match result {
Ok(r) => {
if externalities.egress_data.len() + externalities.up_data.len() > MAX_MESSAGE_MEM {
let inner = worker_ext.inner.lock();
let egress_data = &inner.egress_data;
let e_len = egress_data.len();
let up_data = &inner.up_data;
let up_len = up_data.len();
if e_len + up_len > MAX_MESSAGE_MEM {
ValidationResultHeader::Error("Message data is too large".into())
} else {
let e_len = externalities.egress_data.len();
let up_len = externalities.up_data.len();
message_data[0..e_len].copy_from_slice(&externalities.egress_data);
message_data[e_len..(e_len + up_len)].copy_from_slice(&externalities.up_data);
ValidationResultHeader::Ok {
result: r,
egress_message_count: externalities.egress_message_count as u64,
up_message_count: externalities.up_message_count as u64,
}
message_data[0..e_len].copy_from_slice(egress_data);
message_data[e_len..(e_len + up_len)].copy_from_slice(&up_data);
ValidationResultHeader::Ok(r)
}
},
Err(e) => ValidationResultHeader::Error(e.to_string()),
@@ -150,11 +184,7 @@ struct ValidationHeader {
#[derive(Encode, Decode, Debug)]
pub enum ValidationResultHeader {
Ok {
result: ValidationResult,
egress_message_count: u64,
up_message_count: u64,
},
Ok(ValidationResult),
Error(String),
}
@@ -166,14 +196,13 @@ struct ValidationHost {
memory: Option<SharedMem>,
}
/// Validate a candidate under the given validation code.
///
/// This will fail if the validation code is not a proper parachain validation module.
pub fn validate_candidate<E: Externalities>(
validation_code: &[u8],
params: ValidationParams,
externalities: &mut E,
externalities: E,
test_mode: bool,
) -> Result<ValidationResult, Error> {
for host in HOSTS.iter() {
@@ -197,7 +226,7 @@ impl Drop for ValidationHost {
impl ValidationHost {
fn create_memory() -> Result<SharedMem, Error> {
let mem_size = MAX_RUNTIME_MEM + MAX_CODE_MEM + MAX_MESSAGE_MEM + 1024;
let mem_config = SharedMemConf::new()
let mem_config = SharedMemConf::default()
.set_size(mem_size)
.add_lock(shared_memory::LockType::Mutex, 0, mem_size)?
.add_event(shared_memory::EventType::Auto)? // Event::CandidateReady
@@ -226,7 +255,10 @@ impl ValidationHost {
.spawn()?;
self.worker = Some(worker);
memory.wait(Event::WorkerReady as usize, shared_memory::Timeout::Sec(EXECUTION_TIMEOUT_SEC as usize))?;
memory.wait(
Event::WorkerReady as usize,
shared_memory::Timeout::Sec(EXECUTION_TIMEOUT_SEC as usize),
)?;
self.memory = Some(memory);
Ok(())
}
@@ -238,7 +270,7 @@ impl ValidationHost {
&mut self,
validation_code: &[u8],
params: ValidationParams,
externalities: &mut E,
mut externalities: E,
test_mode: bool,
) -> Result<ValidationResult, Error> {
if validation_code.len() > MAX_CODE_MEM {
@@ -246,7 +278,8 @@ impl ValidationHost {
}
// First, check if need to spawn the child process
self.start_worker(test_mode)?;
let memory = self.memory.as_mut().expect("memory is always `Some` after `start_worker` completes successfully");
let memory = self.memory.as_mut()
.expect("memory is always `Some` after `start_worker` completes successfully");
{
// Put data in shared mem
let data: &mut[u8] = &mut **memory.wlock_as_slice(0)?;
@@ -293,23 +326,23 @@ impl ValidationHost {
let mut message_data: &[u8] = message_data;
let header = ValidationResultHeader::decode(&mut header_buf).unwrap();
match header {
ValidationResultHeader::Ok { result, egress_message_count, up_message_count } => {
for _ in 0 .. egress_message_count {
let message = IncomingMessage::decode(&mut message_data).unwrap();
let message_ref = MessageRef {
target: message.source,
data: &message.data,
};
externalities.post_message(message_ref)?;
}
for _ in 0 .. up_message_count {
let message = UpwardMessage::decode(&mut message_data).unwrap();
let message_ref = UpwardMessageRef {
origin: message.origin,
data: &message.data,
};
externalities.post_upward_message(message_ref)?;
}
ValidationResultHeader::Ok(result) => {
let egress = Vec::<TargetedMessage>::decode(&mut message_data)
.map_err(|e|
Error::External(
format!("Could not decode egress messages: {}", e.what())
)
)?;
egress.into_iter().try_for_each(|msg| externalities.post_message(msg))?;
let upwards = Vec::<UpwardMessage>::decode(&mut message_data)
.map_err(|e|
Error::External(
format!("Could not decode upward messages: {}", e.what())
)
)?;
upwards.into_iter().try_for_each(|msg| externalities.post_upward_message(msg))?;
Ok(result)
}
ValidationResultHeader::Error(message) => {
+5 -6
View File
@@ -18,8 +18,7 @@
use polkadot_parachain as parachain;
use crate::parachain::{IncomingMessage, ValidationParams};
use crate::DummyExt;
use crate::{DummyExt, parachain::{IncomingMessage, ValidationParams}};
use codec::{Decode, Encode};
/// Head data for this parachain.
@@ -78,7 +77,7 @@ pub fn execute_good_on_parent() {
block_data: block_data.encode(),
ingress: Vec::new(),
},
&mut DummyExt,
DummyExt,
parachain::wasm_executor::ExecutionMode::RemoteTest,
).unwrap();
@@ -114,7 +113,7 @@ fn execute_good_chain_on_parent() {
block_data: block_data.encode(),
ingress: Vec::new(),
},
&mut DummyExt,
DummyExt,
parachain::wasm_executor::ExecutionMode::RemoteTest,
).unwrap();
@@ -150,7 +149,7 @@ fn execute_bad_on_parent() {
block_data: block_data.encode(),
ingress: Vec::new(),
},
&mut DummyExt,
DummyExt,
parachain::wasm_executor::ExecutionMode::RemoteTest,
).unwrap_err();
}
@@ -182,7 +181,7 @@ fn processes_messages() {
IncomingMessage { source: 3.into(), data: (AddMessage { amount: 256 }).encode() },
],
},
&mut DummyExt,
DummyExt,
parachain::wasm_executor::ExecutionMode::RemoteTest,
).unwrap();
+3 -4
View File
@@ -19,16 +19,15 @@ mod wasm_executor;
use polkadot_parachain as parachain;
use crate::parachain::{
MessageRef, UpwardMessageRef,
wasm_executor::{Externalities, ExternalitiesError, run_worker},
TargetedMessage, UpwardMessage, wasm_executor::{Externalities, run_worker},
};
struct DummyExt;
impl Externalities for DummyExt {
fn post_message(&mut self, _message: MessageRef) -> Result<(), ExternalitiesError> {
fn post_message(&mut self, _: TargetedMessage) -> Result<(), String> {
Ok(())
}
fn post_upward_message(&mut self, _message: UpwardMessageRef) -> Result<(), ExternalitiesError> {
fn post_upward_message(&mut self, _: UpwardMessage) -> Result<(), String> {
Ok(())
}
}
@@ -32,7 +32,7 @@ fn terminates_on_timeout() {
block_data: Vec::new(),
ingress: Vec::new(),
},
&mut DummyExt,
DummyExt,
parachain::wasm_executor::ExecutionMode::RemoteTest,
);
match result {
@@ -55,7 +55,7 @@ fn parallel_execution() {
block_data: Vec::new(),
ingress: Vec::new(),
},
&mut DummyExt,
DummyExt,
parachain::wasm_executor::ExecutionMode::RemoteTest,
).ok());
let _ = parachain::wasm_executor::validate_candidate(
@@ -65,7 +65,7 @@ fn parallel_execution() {
block_data: Vec::new(),
ingress: Vec::new(),
},
&mut DummyExt,
DummyExt,
parachain::wasm_executor::ExecutionMode::RemoteTest,
);
thread.join().unwrap();