mirror of
https://github.com/pezkuwichain/pezkuwi-subxt.git
synced 2026-05-30 14:01:02 +00:00
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:
@@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
@@ -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(¶ms[..], 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(¶ms[..], 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, ¶ms.encode(), externalities)
|
||||
validate_candidate_internal(validation_code, ¶ms.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) => {
|
||||
|
||||
Reference in New Issue
Block a user