mirror of
https://github.com/pezkuwichain/pezkuwi-subxt.git
synced 2026-05-30 16:21:02 +00:00
Fix compilation in wasm (#465)
Also fix the weird file structure by making `wasm_executor.rs` -> `wasm_executor/mod.rs`.
This commit is contained in:
committed by
Robert Habermeier
parent
9c7845b824
commit
dd0009a006
@@ -0,0 +1,455 @@
|
||||
// Copyright 2017 Parity Technologies (UK) Ltd.
|
||||
// This file is part of Polkadot.
|
||||
|
||||
// Polkadot is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
|
||||
// Polkadot is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Polkadot. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
//! WASM re-execution of a parachain candidate.
|
||||
//! In the context of relay-chain candidate evaluation, there are some additional
|
||||
//! steps to ensure that the provided input parameters are correct.
|
||||
//! 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};
|
||||
|
||||
#[cfg(not(target_os = "unknown"))]
|
||||
pub use validation_host::{run_worker, EXECUTION_TIMEOUT_SEC};
|
||||
|
||||
mod validation_host;
|
||||
|
||||
// maximum memory in bytes
|
||||
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;
|
||||
|
||||
/// Post a message to this parachain's relay chain.
|
||||
pub const POST_UPWARDS_MESSAGE: usize = 2;
|
||||
}
|
||||
|
||||
/// WASM code execution mode.
|
||||
///
|
||||
/// > Note: When compiling for WASM, the `Remote` variants are not available.
|
||||
pub enum ExecutionMode {
|
||||
/// Execute in-process. The execution can not be interrupted or aborted.
|
||||
Local,
|
||||
/// Remote execution in a spawned process.
|
||||
Remote,
|
||||
/// Remote execution in a spawned test runner.
|
||||
RemoteTest,
|
||||
}
|
||||
|
||||
/// 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),
|
||||
/// 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,
|
||||
#[display(fmt = "Validation function timeout.")]
|
||||
Timeout,
|
||||
#[display(fmt = "IO error: {}", _0)]
|
||||
Io(std::io::Error),
|
||||
#[display(fmt = "System error: {}", _0)]
|
||||
System(Box<dyn std::error::Error>),
|
||||
#[display(fmt = "WASM worker error: {}", _0)]
|
||||
External(String),
|
||||
}
|
||||
|
||||
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::Io(ref err) => Some(err),
|
||||
Error::System(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 {
|
||||
/// Called when a message is to be posted to another parachain.
|
||||
fn post_message(&mut self, message: MessageRef) -> Result<(), ExternalitiesError>;
|
||||
|
||||
/// 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_UPWARDS_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_UPWARDS_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(())
|
||||
}
|
||||
}
|
||||
|
||||
/// 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,
|
||||
options: ExecutionMode,
|
||||
) -> Result<ValidationResult, Error>
|
||||
{
|
||||
match options {
|
||||
ExecutionMode::Local => {
|
||||
validate_candidate_internal(validation_code, ¶ms.encode(), externalities)
|
||||
},
|
||||
#[cfg(not(target_os = "unknown"))]
|
||||
ExecutionMode::Remote =>
|
||||
validation_host::validate_candidate(validation_code, params, externalities, false),
|
||||
#[cfg(not(target_os = "unknown"))]
|
||||
ExecutionMode::RemoteTest =>
|
||||
validation_host::validate_candidate(validation_code, params, externalities, true),
|
||||
#[cfg(target_os = "unknown")]
|
||||
ExecutionMode::Remote =>
|
||||
Err(Error::System("Remote validator not available".to_string().into())),
|
||||
#[cfg(target_os = "unknown")]
|
||||
ExecutionMode::RemoteTest =>
|
||||
Err(Error::System("Remote validator not available".to_string().into())),
|
||||
}
|
||||
}
|
||||
|
||||
/// 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>(
|
||||
validation_code: &[u8],
|
||||
encoded_call_data: &[u8],
|
||||
externalities: &mut E,
|
||||
) -> Result<ValidationResult, Error> {
|
||||
use wasmi::LINEAR_MEMORY_PAGE_SIZE;
|
||||
|
||||
// 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(
|
||||
"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())
|
||||
})?;
|
||||
|
||||
match output {
|
||||
Some(RuntimeValue::I32(len_offset)) => {
|
||||
let len_offset = len_offset as u32;
|
||||
|
||||
let mut len_bytes = [0u8; 4];
|
||||
memory.get_into(len_offset, &mut len_bytes)?;
|
||||
let len_offset = len_offset as usize;
|
||||
|
||||
let len = u32::decode(&mut &len_bytes[..])
|
||||
.map_err(|_| Error::BadReturn)? as usize;
|
||||
|
||||
let return_offset = if len > len_offset {
|
||||
return Err(Error::BadReturn);
|
||||
} else {
|
||||
len_offset - len
|
||||
};
|
||||
|
||||
memory.with_direct_access(|mem| {
|
||||
if mem.len() < return_offset + len {
|
||||
return Err(Error::BadReturn);
|
||||
}
|
||||
|
||||
ValidationResult::decode(&mut &mem[return_offset..][..len])
|
||||
.map_err(|_| Error::BadReturn.into())
|
||||
})
|
||||
}
|
||||
_ => Err(Error::BadReturn),
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user