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
+49 -37
View File
@@ -29,7 +29,8 @@ dependencies = [
"ctrlc 3.1.3 (registry+https://github.com/rust-lang/crates.io-index)",
"exit-future 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)",
"futures 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)",
"parking_lot 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)",
"parity-scale-codec 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
"parking_lot 0.10.0 (registry+https://github.com/rust-lang/crates.io-index)",
"polkadot-collator 0.7.15",
"polkadot-parachain 0.7.15",
"polkadot-primitives 0.7.15",
@@ -2623,7 +2624,6 @@ name = "lock_api"
version = "0.1.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"owning_ref 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
"scopeguard 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)",
]
@@ -3500,14 +3500,6 @@ dependencies = [
"winapi 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "parity-wasm"
version = "0.31.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"byteorder 1.3.2 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "parity-wasm"
version = "0.41.0"
@@ -3542,6 +3534,15 @@ dependencies = [
"rustc_version 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "parking_lot"
version = "0.10.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"lock_api 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)",
"parking_lot_core 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "parking_lot_core"
version = "0.4.0"
@@ -3583,6 +3584,19 @@ dependencies = [
"winapi 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "parking_lot_core"
version = "0.7.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"cfg-if 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)",
"cloudabi 0.0.3 (registry+https://github.com/rust-lang/crates.io-index)",
"libc 0.2.65 (registry+https://github.com/rust-lang/crates.io-index)",
"redox_syscall 0.1.56 (registry+https://github.com/rust-lang/crates.io-index)",
"smallvec 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)",
"winapi 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "paste"
version = "0.1.6"
@@ -3803,18 +3817,21 @@ name = "polkadot-parachain"
version = "0.7.15"
dependencies = [
"adder 0.7.15",
"derive_more 0.14.1 (registry+https://github.com/rust-lang/crates.io-index)",
"derive_more 0.99.2 (registry+https://github.com/rust-lang/crates.io-index)",
"halt 0.7.15",
"lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
"log 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)",
"parity-scale-codec 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
"parking_lot 0.7.1 (registry+https://github.com/rust-lang/crates.io-index)",
"parking_lot 0.10.0 (registry+https://github.com/rust-lang/crates.io-index)",
"sc-executor 2.0.0 (git+https://github.com/paritytech/substrate?branch=polkadot-master)",
"serde 1.0.102 (registry+https://github.com/rust-lang/crates.io-index)",
"shared_memory 0.8.2 (registry+https://github.com/rust-lang/crates.io-index)",
"shared_memory 0.10.0 (registry+https://github.com/rust-lang/crates.io-index)",
"sp-core 2.0.0 (git+https://github.com/paritytech/substrate?branch=polkadot-master)",
"sp-externalities 2.0.0 (git+https://github.com/paritytech/substrate?branch=polkadot-master)",
"sp-io 2.0.0 (git+https://github.com/paritytech/substrate?branch=polkadot-master)",
"sp-runtime-interface 2.0.0 (git+https://github.com/paritytech/substrate?branch=polkadot-master)",
"sp-std 2.0.0 (git+https://github.com/paritytech/substrate?branch=polkadot-master)",
"tiny-keccak 1.5.0 (registry+https://github.com/rust-lang/crates.io-index)",
"wasmi 0.4.5 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
@@ -5412,19 +5429,32 @@ dependencies = [
[[package]]
name = "shared_memory"
version = "0.8.2"
version = "0.10.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"cfg-if 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)",
"enum_primitive 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)",
"libc 0.2.65 (registry+https://github.com/rust-lang/crates.io-index)",
"log 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)",
"memrange 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)",
"nix 0.10.0 (registry+https://github.com/rust-lang/crates.io-index)",
"quick-error 1.2.2 (registry+https://github.com/rust-lang/crates.io-index)",
"rand 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)",
"shared_memory_derive 0.10.0 (registry+https://github.com/rust-lang/crates.io-index)",
"theban_interval_tree 0.7.1 (registry+https://github.com/rust-lang/crates.io-index)",
"winapi 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "shared_memory_derive"
version = "0.10.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"proc-macro2 0.4.30 (registry+https://github.com/rust-lang/crates.io-index)",
"quote 0.6.13 (registry+https://github.com/rust-lang/crates.io-index)",
"syn 0.15.44 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "shell32-sys"
version = "0.1.2"
@@ -6897,16 +6927,6 @@ dependencies = [
"web-sys 0.3.32 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "wasmi"
version = "0.4.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"memory_units 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)",
"parity-wasm 0.31.3 (registry+https://github.com/rust-lang/crates.io-index)",
"wasmi-validation 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "wasmi"
version = "0.6.2"
@@ -6920,14 +6940,6 @@ dependencies = [
"wasmi-validation 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "wasmi-validation"
version = "0.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"parity-wasm 0.31.3 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "wasmi-validation"
version = "0.3.0"
@@ -7556,14 +7568,15 @@ dependencies = [
"checksum parity-scale-codec-derive 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "492ac3aa93d6caa5d20e4e3e0b75d08e2dcd9dd8a50d19529548b6fe11b3f295"
"checksum parity-send-wrapper 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "aa9777aa91b8ad9dd5aaa04a9b6bcb02c7f1deb952fca5a66034d5e63afc5c6f"
"checksum parity-util-mem 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)" = "8174d85e62c4d615fddd1ef67966bdc5757528891d0742f15b131ad04667b3f9"
"checksum parity-wasm 0.31.3 (registry+https://github.com/rust-lang/crates.io-index)" = "511379a8194230c2395d2f5fa627a5a7e108a9f976656ce723ae68fca4097bfc"
"checksum parity-wasm 0.41.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ddfc878dac00da22f8f61e7af3157988424567ab01d9920b962ef7dcbd7cd865"
"checksum parking_lot 0.10.0 (registry+https://github.com/rust-lang/crates.io-index)" = "92e98c49ab0b7ce5b222f2cc9193fc4efe11c6d0bd4f648e374684a6857b1cfc"
"checksum parking_lot 0.7.1 (registry+https://github.com/rust-lang/crates.io-index)" = "ab41b4aed082705d1056416ae4468b6ea99d52599ecf3169b00088d43113e337"
"checksum parking_lot 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)" = "fa7767817701cce701d5585b9c4db3cdd02086398322c1d7e8bf5094a96a2ce7"
"checksum parking_lot 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)" = "f842b1982eb6c2fe34036a4fbfb06dd185a3f5c8edfaacdf7d1ea10b07de6252"
"checksum parking_lot_core 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "94c8c7923936b28d546dfd14d4472eaf34c99b14e1c973a32b3e6d4eb04298c9"
"checksum parking_lot_core 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)" = "cb88cb1cb3790baa6776844f968fea3be44956cf184fa1be5a03341f5491278c"
"checksum parking_lot_core 0.6.2 (registry+https://github.com/rust-lang/crates.io-index)" = "b876b1b9e7ac6e1a74a6da34d25c42e17e8862aa409cbbbdcfc8d86c6f3bc62b"
"checksum parking_lot_core 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)" = "7582838484df45743c8434fbff785e8edf260c28748353d44bc0da32e0ceabf1"
"checksum paste 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)" = "423a519e1c6e828f1e73b720f9d9ed2fa643dce8a7737fb43235ce0b41eeaa49"
"checksum paste-impl 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)" = "4214c9e912ef61bf42b81ba9a47e8aad1b2ffaf739ab162bf96d1e011f54e6c5"
"checksum pbkdf2 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)" = "006c038a43a45995a9670da19e67600114740e8511d4333bf97a56e66a7542d9"
@@ -7683,7 +7696,8 @@ dependencies = [
"checksum sha1 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)" = "2579985fda508104f7587689507983eadd6a6e84dd35d6d115361f530916fa0d"
"checksum sha2 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)" = "7b4d8bfd0e469f417657573d8451fb33d16cfe0989359b93baf3a1ffc639543d"
"checksum sha3 0.8.2 (registry+https://github.com/rust-lang/crates.io-index)" = "dd26bc0e7a2e3a7c959bc494caf58b72ee0c71d67704e9520f736ca7e4853ecf"
"checksum shared_memory 0.8.2 (registry+https://github.com/rust-lang/crates.io-index)" = "be289420c5900abb177b756f39625ca7a0df68069cfb242fb31feb6e8c480f04"
"checksum shared_memory 0.10.0 (registry+https://github.com/rust-lang/crates.io-index)" = "bf3ab0cdff84d6c66fc9e268010ea6508e58ee942575afb66f2cf194bb218bb4"
"checksum shared_memory_derive 0.10.0 (registry+https://github.com/rust-lang/crates.io-index)" = "767a14f1304be2f0b04e69860252f8ae9cfae0afaa9cc07b675147c43425dd3a"
"checksum shell32-sys 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "9ee04b46101f57121c9da2b151988283b6beb79b34f5bb29a58ee48cb695122c"
"checksum shlex 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "7fdf1b9db47230893d76faad238fd6097fd6d6a9245cd7a4d90dbd639536bbd2"
"checksum slab 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)" = "c111b5bd5695e56cffe5129854aa230b39c93a305372fdbb2668ca2394eea9f8"
@@ -7829,9 +7843,7 @@ dependencies = [
"checksum wasm-bindgen-shared 0.2.55 (registry+https://github.com/rust-lang/crates.io-index)" = "ca0b78d6d3be8589b95d1d49cdc0794728ca734adf36d7c9f07e6459508bb53d"
"checksum wasm-bindgen-webidl 0.2.55 (registry+https://github.com/rust-lang/crates.io-index)" = "3126356474ceb717c8fb5549ae387c9fbf4872818454f4d87708bee997214bb5"
"checksum wasm-timer 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)" = "aa3e01d234bb71760e685cfafa5e2c96f8ad877c161a721646356651069e26ac"
"checksum wasmi 0.4.5 (registry+https://github.com/rust-lang/crates.io-index)" = "aebbaef470840d157a5c47c8c49f024da7b1b80e90ff729ca982b2b80447e78b"
"checksum wasmi 0.6.2 (registry+https://github.com/rust-lang/crates.io-index)" = "bf617d864d25af3587aa745529f7aaa541066c876d57e050c0d0c85c61c92aff"
"checksum wasmi-validation 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ab380192444b3e8522ae79c0a1976e42a82920916ccdfbce3def89f456ea33f3"
"checksum wasmi-validation 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ea78c597064ba73596099281e2f4cfc019075122a65cdda3205af94f0b264d93"
"checksum wasmparser 0.39.3 (registry+https://github.com/rust-lang/crates.io-index)" = "c702914acda5feeeffbc29e4d953e5b9ce79d8b98da4dbf18a77086e116c5470"
"checksum wasmtime-debug 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)" = "5008729ad53f75020f28fa0d682269335d6f0eac0b3ffafe31f185b2f33aca74"
+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();
+1 -31
View File
@@ -35,7 +35,7 @@ use application_crypto::KeyTypeId;
use trie::TrieConfiguration;
pub use polkadot_parachain::{
Id, ParachainDispatchOrigin, LOWEST_USER_ID, UpwardMessage,
Id, ParachainDispatchOrigin, LOWEST_USER_ID, UpwardMessage, TargetedMessage,
};
/// The key type ID for a collator key.
@@ -164,36 +164,6 @@ pub struct DutyRoster {
pub validator_duty: Vec<Chain>,
}
/// A message targeted to a specific parachain.
#[derive(Clone, PartialEq, Eq, Encode, Decode)]
#[cfg_attr(feature = "std", derive(Serialize, 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)
}
}
/// Outgoing message data for a parachain candidate.
///
/// This is data produced by evaluating the candidate. It contains
+1 -1
View File
@@ -20,6 +20,6 @@ fn main() {
build_current_project_with_rustflags(
"wasm_binary.rs",
WasmBuilderSource::Crates("1.0.7"),
"-C link-arg=--import-memory",
"-C link-arg=--export=__heap_base",
);
}
@@ -12,7 +12,8 @@ primitives = { package = "polkadot-primitives", path = "../../../primitives" }
sp-core = { git = "https://github.com/paritytech/substrate", branch = "polkadot-master" }
client = { package = "sc-client", git = "https://github.com/paritytech/substrate", branch = "polkadot-master" }
client-api = { package = "sc-client-api", git = "https://github.com/paritytech/substrate", branch = "polkadot-master" }
parking_lot = "0.9.0"
parking_lot = "0.10.0"
codec = { package = "parity-scale-codec", version = "1.1.0" }
ctrlc = { version = "3.1.3", features = ["termination"] }
futures = "0.3.1"
exit-future = "0.2.0"
@@ -22,7 +22,7 @@ use std::sync::Arc;
use adder::{HeadData as AdderHead, BlockData as AdderBody};
use sp_core::{Pair, Blake2Hasher};
use parachain::codec::{Encode, Decode};
use codec::{Encode, Decode};
use primitives::{
Hash, Block,
parachain::{
@@ -19,7 +19,7 @@
use crate::{HeadData, BlockData};
use core::{intrinsics, panic};
use parachain::ValidationResult;
use parachain::codec::{Encode, Decode};
use codec::{Encode, Decode};
#[panic_handler]
#[no_mangle]
@@ -38,8 +38,8 @@ pub fn oom(_: core::alloc::Layout) -> ! {
}
#[no_mangle]
pub extern fn validate_block(params: *const u8, len: usize) -> usize {
let params = unsafe { parachain::wasm_api::load_params(params, len) };
pub extern fn validate_block(params: *const u8, len: usize) -> u64 {
let params = unsafe { parachain::load_params(params, len) };
let parent_head = HeadData::decode(&mut &params.parent_head[..])
.expect("invalid parent head format.");
@@ -55,9 +55,9 @@ pub extern fn validate_block(params: *const u8, len: usize) -> usize {
);
match crate::execute(parent_hash, parent_head, &block_data, from_messages) {
Ok(new_head) => parachain::wasm_api::write_result(
ValidationResult { head_data: new_head.encode() }
Ok(new_head) => parachain::write_result(
&ValidationResult { head_data: new_head.encode() }
),
Err(_) => panic!("execution failure"),
}
}
}
+1 -1
View File
@@ -20,6 +20,6 @@ fn main() {
build_current_project_with_rustflags(
"wasm_binary.rs",
WasmBuilderSource::Crates("1.0.7"),
"-C link-arg=--import-memory",
"-C link-arg=--export=__heap_base",
);
}
+76 -48
View File
@@ -21,17 +21,23 @@
use std::sync::Arc;
use polkadot_primitives::{BlakeTwo256, Block, Hash, HashT, BlockId, Balance, parachain::{
CollatorId, ConsolidatedIngress, StructuredUnroutedIngress, CandidateReceipt, CollationInfo, ParachainHost,
Id as ParaId, Collation, TargetedMessage, OutgoingMessages, UpwardMessage, FeeSchedule, ErasureChunk,
HeadData, PoVBlock,
}};
use polkadot_erasure_coding::{self as erasure};
use polkadot_primitives::{
BlakeTwo256, Block, Hash, HashT, BlockId, Balance,
parachain::{
CollatorId, ConsolidatedIngress, StructuredUnroutedIngress, CandidateReceipt, CollationInfo,
ParachainHost, Id as ParaId, Collation, OutgoingMessages, FeeSchedule, ErasureChunk,
HeadData, PoVBlock,
},
};
use polkadot_erasure_coding as erasure;
use runtime_primitives::traits::ProvideRuntimeApi;
use parachain::{wasm_executor::{self, ExternalitiesError, ExecutionMode}, MessageRef, UpwardMessageRef};
use parachain::{
wasm_executor::{self, ExecutionMode}, TargetedMessage, UpwardMessage,
};
use trie::TrieConfiguration;
use futures::prelude::*;
use log::debug;
use parking_lot::Mutex;
/// Encapsulates connections to collators and allows collation on any parachain.
///
@@ -239,7 +245,7 @@ fn check_egress(
Ok(OutgoingMessages { outgoing_messages: outgoing })
}
struct Externalities {
struct ExternalitiesInner {
parachain_index: ParaId,
outgoing: Vec<TargetedMessage>,
upward: Vec<UpwardMessage>,
@@ -248,41 +254,44 @@ struct Externalities {
fee_schedule: FeeSchedule,
}
impl wasm_executor::Externalities for Externalities {
fn post_message(&mut self, message: MessageRef) -> Result<(), ExternalitiesError> {
let target: ParaId = message.target.into();
if target == self.parachain_index {
return Err(ExternalitiesError::CannotPostMessage("posted message to self"));
impl wasm_executor::Externalities for ExternalitiesInner {
fn post_message(&mut self, message: TargetedMessage) -> Result<(), String> {
if message.target == self.parachain_index {
return Err("posted message to self".into())
}
self.apply_message_fee(message.data.len())?;
self.outgoing.push(TargetedMessage {
target,
data: message.data.to_vec(),
});
self.outgoing.push(message);
Ok(())
}
fn post_upward_message(&mut self, message: UpwardMessageRef)
-> Result<(), ExternalitiesError>
{
fn post_upward_message(&mut self, message: UpwardMessage) -> Result<(), String> {
self.apply_message_fee(message.data.len())?;
self.upward.push(UpwardMessage {
origin: message.origin,
data: message.data.to_vec(),
});
self.upward.push(message);
Ok(())
}
}
impl Externalities {
fn apply_message_fee(&mut self, message_len: usize) -> Result<(), ExternalitiesError> {
impl ExternalitiesInner {
fn new(parachain_index: ParaId, free_balance: Balance, fee_schedule: FeeSchedule) -> Self {
Self {
parachain_index,
free_balance,
fee_schedule,
fees_charged: 0,
upward: Vec::new(),
outgoing: Vec::new(),
}
}
fn apply_message_fee(&mut self, message_len: usize) -> Result<(), String> {
let fee = self.fee_schedule.compute_fee(message_len);
let new_fees_charged = self.fees_charged.saturating_add(fee);
if new_fees_charged > self.free_balance {
Err(ExternalitiesError::CannotPostMessage("could not cover fee."))
Err("could not cover fee.".into())
} else {
self.fees_charged = new_fees_charged;
Ok(())
@@ -291,7 +300,7 @@ impl Externalities {
// Performs final checks of validity, producing the outgoing message data.
fn final_checks(
self,
&mut self,
upward_messages: &[UpwardMessage],
egress_queue_roots: &[(ParaId, Hash)],
fees_charged: Option<Balance>,
@@ -313,7 +322,7 @@ impl Externalities {
}
let messages = check_egress(
self.outgoing,
std::mem::replace(&mut self.outgoing, Vec::new()),
&egress_queue_roots[..],
)?;
@@ -321,6 +330,27 @@ impl Externalities {
}
}
#[derive(Clone)]
struct Externalities(Arc<Mutex<ExternalitiesInner>>);
impl Externalities {
fn new(parachain_index: ParaId, free_balance: Balance, fee_schedule: FeeSchedule) -> Self {
Self(Arc::new(Mutex::new(
ExternalitiesInner::new(parachain_index, free_balance, fee_schedule)
)))
}
}
impl wasm_executor::Externalities for Externalities {
fn post_message(&mut self, message: TargetedMessage) -> Result<(), String> {
self.0.lock().post_message(message)
}
fn post_upward_message(&mut self, message: UpwardMessage) -> Result<(), String> {
self.0.lock().post_upward_message(message)
}
}
/// Validate an erasure chunk against an expected root.
pub fn validate_chunk(
root: &Hash,
@@ -425,19 +455,17 @@ fn do_validation<P>(
.collect()
};
let mut ext = Externalities {
parachain_index: para_id.clone(),
outgoing: Vec::new(),
upward: Vec::new(),
free_balance: chain_status.balance,
fee_schedule: chain_status.fee_schedule,
fees_charged: 0,
};
let ext = Externalities::new(para_id.clone(), chain_status.balance, chain_status.fee_schedule);
match wasm_executor::validate_candidate(&validation_code, params, &mut ext, ExecutionMode::Remote) {
match wasm_executor::validate_candidate(
&validation_code,
params,
ext.clone(),
ExecutionMode::Remote,
) {
Ok(result) => {
if result.head_data == head_data.0 {
let (messages, fees) = ext.final_checks(
let (messages, fees) = ext.0.lock().final_checks(
upward_messages,
queue_roots,
fees_charged
@@ -634,7 +662,7 @@ mod tests {
#[test]
fn ext_rejects_local_message() {
let mut ext = Externalities {
let mut ext = ExternalitiesInner {
parachain_index: 5.into(),
outgoing: Vec::new(),
upward: Vec::new(),
@@ -646,13 +674,13 @@ mod tests {
},
};
assert!(ext.post_message(MessageRef { target: 1.into(), data: &[] }).is_ok());
assert!(ext.post_message(MessageRef { target: 5.into(), data: &[] }).is_err());
assert!(ext.post_message(TargetedMessage { target: 1.into(), data: Vec::new() }).is_ok());
assert!(ext.post_message(TargetedMessage { target: 5.into(), data: Vec::new() }).is_err());
}
#[test]
fn ext_checks_upward_messages() {
let ext = || Externalities {
let ext = || ExternalitiesInner {
parachain_index: 5.into(),
outgoing: Vec::new(),
upward: vec![
@@ -742,7 +770,7 @@ mod tests {
#[test]
fn ext_checks_fees_and_updates_correctly() {
let mut ext = Externalities {
let mut ext = ExternalitiesInner {
parachain_index: 5.into(),
outgoing: Vec::new(),
upward: vec![
@@ -759,15 +787,15 @@ mod tests {
ext.apply_message_fee(100).unwrap();
assert_eq!(ext.fees_charged, 2000);
ext.post_message(MessageRef {
ext.post_message(TargetedMessage {
target: 1.into(),
data: &[0u8; 100],
data: vec![0u8; 100],
}).unwrap();
assert_eq!(ext.fees_charged, 4000);
ext.post_upward_message(UpwardMessageRef {
ext.post_upward_message(UpwardMessage {
origin: ParachainDispatchOrigin::Signed,
data: &[0u8; 100],
data: vec![0u8; 100],
}).unwrap();
assert_eq!(ext.fees_charged, 6000);