mirror of
https://github.com/pezkuwichain/pezkuwi-subxt.git
synced 2026-07-20 17:25:41 +00:00
Parachain execution yields messages to send (#96)
* read head-data directly out of WASM memory * implement ext_post_message for parachain WASM * further refactoring of the parachain module * add externalities error type * accumulate posted messages when validating parachain candidate * define Extrinsic type in primitives * availability-store: store extrinsic data * compute extrinsic and check against candidate * add some egress queue tests * grumbles & substrate update * ensure everything builds
This commit is contained in:
committed by
Gav Wood
parent
152bb30889
commit
fe6351ca65
Generated
+347
-86
File diff suppressed because it is too large
Load Diff
@@ -128,11 +128,11 @@ impl Store {
|
|||||||
data.block_data.encode()
|
data.block_data.encode()
|
||||||
);
|
);
|
||||||
|
|
||||||
if let Some(_extrinsic) = data.extrinsic {
|
if let Some(extrinsic) = data.extrinsic {
|
||||||
tx.put_vec(
|
tx.put_vec(
|
||||||
columns::DATA,
|
columns::DATA,
|
||||||
extrinsic_key(&data.relay_parent, &data.candidate_hash).as_slice(),
|
extrinsic_key(&data.relay_parent, &data.candidate_hash).as_slice(),
|
||||||
vec![],
|
extrinsic.encode(),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -182,7 +182,9 @@ impl Store {
|
|||||||
pub fn extrinsic(&self, relay_parent: Hash, candidate_hash: Hash) -> Option<Extrinsic> {
|
pub fn extrinsic(&self, relay_parent: Hash, candidate_hash: Hash) -> Option<Extrinsic> {
|
||||||
let encoded_key = extrinsic_key(&relay_parent, &candidate_hash);
|
let encoded_key = extrinsic_key(&relay_parent, &candidate_hash);
|
||||||
match self.inner.get(columns::DATA, &encoded_key[..]) {
|
match self.inner.get(columns::DATA, &encoded_key[..]) {
|
||||||
Ok(Some(_raw)) => Some(Extrinsic),
|
Ok(Some(raw)) => Some(
|
||||||
|
Extrinsic::decode(&mut &raw[..]).expect("all stored data serialized correctly; qed")
|
||||||
|
),
|
||||||
Ok(None) => None,
|
Ok(None) => None,
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
warn!(target: "availability", "Error reading from availability store: {:?}", e);
|
warn!(target: "availability", "Error reading from availability store: {:?}", e);
|
||||||
@@ -215,7 +217,7 @@ mod tests {
|
|||||||
parachain_id: para_id_1,
|
parachain_id: para_id_1,
|
||||||
candidate_hash: candidate_1,
|
candidate_hash: candidate_1,
|
||||||
block_data: block_data_1.clone(),
|
block_data: block_data_1.clone(),
|
||||||
extrinsic: Some(Extrinsic),
|
extrinsic: Some(Extrinsic { outgoing_messages: Vec::new() }),
|
||||||
}).unwrap();
|
}).unwrap();
|
||||||
|
|
||||||
store.make_available(Data {
|
store.make_available(Data {
|
||||||
@@ -223,7 +225,7 @@ mod tests {
|
|||||||
parachain_id: para_id_2,
|
parachain_id: para_id_2,
|
||||||
candidate_hash: candidate_2,
|
candidate_hash: candidate_2,
|
||||||
block_data: block_data_2.clone(),
|
block_data: block_data_2.clone(),
|
||||||
extrinsic: Some(Extrinsic),
|
extrinsic: Some(Extrinsic { outgoing_messages: Vec::new() }),
|
||||||
}).unwrap();
|
}).unwrap();
|
||||||
|
|
||||||
assert_eq!(store.block_data(relay_parent, candidate_1).unwrap(), block_data_1);
|
assert_eq!(store.block_data(relay_parent, candidate_1).unwrap(), block_data_1);
|
||||||
|
|||||||
@@ -23,6 +23,7 @@ substrate-primitives = { git = "https://github.com/paritytech/substrate" }
|
|||||||
substrate-transaction-pool = { git = "https://github.com/paritytech/substrate" }
|
substrate-transaction-pool = { git = "https://github.com/paritytech/substrate" }
|
||||||
srml-support = { git = "https://github.com/paritytech/substrate" }
|
srml-support = { git = "https://github.com/paritytech/substrate" }
|
||||||
substrate-client = { git = "https://github.com/paritytech/substrate" }
|
substrate-client = { git = "https://github.com/paritytech/substrate" }
|
||||||
|
substrate-trie = { git = "https://github.com/paritytech/substrate" }
|
||||||
sr-primitives = { git = "https://github.com/paritytech/substrate" }
|
sr-primitives = { git = "https://github.com/paritytech/substrate" }
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
|
|||||||
@@ -22,9 +22,10 @@
|
|||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
|
||||||
use polkadot_primitives::{Block, Hash, AccountId, BlockId};
|
use polkadot_primitives::{Block, Hash, AccountId, BlockId};
|
||||||
use polkadot_primitives::parachain::{Id as ParaId, Collation, Extrinsic};
|
use polkadot_primitives::parachain::{Id as ParaId, Collation, Extrinsic, OutgoingMessage};
|
||||||
use polkadot_primitives::parachain::ParachainHost;
|
use polkadot_primitives::parachain::{CandidateReceipt, ParachainHost};
|
||||||
use runtime_primitives::traits::ProvideRuntimeApi;
|
use runtime_primitives::traits::ProvideRuntimeApi;
|
||||||
|
use parachain::{wasm_executor::{self, ExternalitiesError}, MessageRef};
|
||||||
|
|
||||||
use futures::prelude::*;
|
use futures::prelude::*;
|
||||||
|
|
||||||
@@ -100,7 +101,9 @@ impl<C: Collators, P: ProvideRuntimeApi> Future for CollationFetch<C, P>
|
|||||||
};
|
};
|
||||||
|
|
||||||
match validate_collation(&*self.client, &self.relay_parent, &x) {
|
match validate_collation(&*self.client, &self.relay_parent, &x) {
|
||||||
Ok(e) => return Ok(Async::Ready((x, e))),
|
Ok(e) => {
|
||||||
|
return Ok(Async::Ready((x, e)))
|
||||||
|
}
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
debug!("Failed to validate parachain due to API error: {}", e);
|
debug!("Failed to validate parachain due to API error: {}", e);
|
||||||
|
|
||||||
@@ -117,36 +120,137 @@ impl<C: Collators, P: ProvideRuntimeApi> Future for CollationFetch<C, P>
|
|||||||
error_chain! {
|
error_chain! {
|
||||||
types { Error, ErrorKind, ResultExt; }
|
types { Error, ErrorKind, ResultExt; }
|
||||||
|
|
||||||
|
links {
|
||||||
|
Client(::client::error::Error, ::client::error::ErrorKind);
|
||||||
|
WasmValidation(wasm_executor::Error, wasm_executor::ErrorKind);
|
||||||
|
}
|
||||||
|
|
||||||
errors {
|
errors {
|
||||||
InactiveParachain(id: ParaId) {
|
InactiveParachain(id: ParaId) {
|
||||||
description("Collated for inactive parachain"),
|
description("Collated for inactive parachain"),
|
||||||
display("Collated for inactive parachain: {:?}", id),
|
display("Collated for inactive parachain: {:?}", id),
|
||||||
}
|
}
|
||||||
ValidationFailure {
|
EgressRootMismatch(id: ParaId, expected: Hash, got: Hash) {
|
||||||
description("Parachain candidate failed validation."),
|
description("Got unexpected egress route."),
|
||||||
display("Parachain candidate failed validation."),
|
display(
|
||||||
|
"Got unexpected egress route to {:?}. (expected: {:?}, got {:?})",
|
||||||
|
id, expected, got
|
||||||
|
),
|
||||||
|
}
|
||||||
|
MissingEgressRoute(expected: Option<ParaId>, got: Option<ParaId>) {
|
||||||
|
description("Missing or extra egress route."),
|
||||||
|
display("Missing or extra egress route. (expected: {:?}, got {:?})", expected, got),
|
||||||
}
|
}
|
||||||
WrongHeadData(expected: Vec<u8>, got: Vec<u8>) {
|
WrongHeadData(expected: Vec<u8>, got: Vec<u8>) {
|
||||||
description("Parachain validation produced wrong head data."),
|
description("Parachain validation produced wrong head data."),
|
||||||
display("Parachain validation produced wrong head data (expected: {:?}, got {:?}", expected, got),
|
display("Parachain validation produced wrong head data (expected: {:?}, got {:?}", expected, got),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
links {
|
/// Compute the egress trie root for a set of messages.
|
||||||
Client(::client::error::Error, ::client::error::ErrorKind);
|
pub fn egress_trie_root<A, I: IntoIterator<Item=A>>(messages: I) -> Hash
|
||||||
|
where A: AsRef<[u8]>
|
||||||
|
{
|
||||||
|
::trie::ordered_trie_root::<primitives::Blake2Hasher, _, _>(messages)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn check_and_compute_extrinsic(
|
||||||
|
mut outgoing: Vec<OutgoingMessage>,
|
||||||
|
expected_egress_roots: &[(ParaId, Hash)],
|
||||||
|
) -> Result<Extrinsic, Error> {
|
||||||
|
// stable sort messages by parachain ID.
|
||||||
|
outgoing.sort_by_key(|msg| ParaId::from(msg.target));
|
||||||
|
|
||||||
|
{
|
||||||
|
let mut messages_iter = outgoing.iter().peekable();
|
||||||
|
let mut expected_egress_roots = expected_egress_roots.iter();
|
||||||
|
while let Some(batch_target) = messages_iter.peek().map(|o| o.target) {
|
||||||
|
let expected_root = match expected_egress_roots.next() {
|
||||||
|
None => return Err(ErrorKind::MissingEgressRoute(Some(batch_target), None).into()),
|
||||||
|
Some(&(id, ref root)) => if id == batch_target {
|
||||||
|
root
|
||||||
|
} else {
|
||||||
|
return Err(ErrorKind::MissingEgressRoute(Some(batch_target), Some(id)).into());
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// we borrow the iterator mutably to ensure it advances so the
|
||||||
|
// next iteration of the loop starts with `messages_iter` pointing to
|
||||||
|
// the next batch.
|
||||||
|
let messages_to = messages_iter
|
||||||
|
.clone()
|
||||||
|
.take_while(|o| o.target == batch_target)
|
||||||
|
.map(|o| { let _ = messages_iter.next(); &o.data[..] });
|
||||||
|
|
||||||
|
let computed_root = egress_trie_root(messages_to);
|
||||||
|
if &computed_root != expected_root {
|
||||||
|
return Err(ErrorKind::EgressRootMismatch(
|
||||||
|
batch_target,
|
||||||
|
expected_root.clone(),
|
||||||
|
computed_root,
|
||||||
|
).into());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// also check that there are no more additional expected roots.
|
||||||
|
if let Some((next_target, _)) = expected_egress_roots.next() {
|
||||||
|
return Err(ErrorKind::MissingEgressRoute(None, Some(*next_target)).into());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(Extrinsic { outgoing_messages: outgoing })
|
||||||
|
}
|
||||||
|
|
||||||
|
struct Externalities {
|
||||||
|
parachain_index: ParaId,
|
||||||
|
outgoing: Vec<OutgoingMessage>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl wasm_executor::Externalities for Externalities {
|
||||||
|
fn post_message(&mut self, message: MessageRef) -> Result<(), ExternalitiesError> {
|
||||||
|
// TODO: https://github.com/paritytech/polkadot/issues/92
|
||||||
|
// check per-message and per-byte fees for the parachain.
|
||||||
|
let target: ParaId = message.target.into();
|
||||||
|
if target == self.parachain_index {
|
||||||
|
return Err(ExternalitiesError::CannotPostMessage("posted message to self"));
|
||||||
|
}
|
||||||
|
|
||||||
|
self.outgoing.push(OutgoingMessage {
|
||||||
|
target,
|
||||||
|
data: message.data.to_vec(),
|
||||||
|
});
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Externalities {
|
||||||
|
// Performs final checks of validity, producing the extrinsic data.
|
||||||
|
fn final_checks(
|
||||||
|
self,
|
||||||
|
candidate: &CandidateReceipt,
|
||||||
|
) -> Result<Extrinsic, Error> {
|
||||||
|
check_and_compute_extrinsic(
|
||||||
|
self.outgoing,
|
||||||
|
&candidate.egress_queue_roots[..],
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Check whether a given collation is valid. Returns `Ok` on success, error otherwise.
|
/// Check whether a given collation is valid. Returns `Ok` on success, error otherwise.
|
||||||
|
///
|
||||||
|
/// This assumes that basic validity checks have been done:
|
||||||
|
/// - Block data hash is the same as linked in candidate receipt.
|
||||||
pub fn validate_collation<P>(
|
pub fn validate_collation<P>(
|
||||||
client: &P,
|
client: &P,
|
||||||
relay_parent: &BlockId,
|
relay_parent: &BlockId,
|
||||||
collation: &Collation
|
collation: &Collation
|
||||||
) -> Result<Extrinsic, Error> where
|
) -> Result<Extrinsic, Error> where
|
||||||
P: ProvideRuntimeApi,
|
P: ProvideRuntimeApi,
|
||||||
P::Api: ParachainHost<Block>
|
P::Api: ParachainHost<Block>,
|
||||||
{
|
{
|
||||||
use parachain::{self, ValidationParams};
|
use parachain::ValidationParams;
|
||||||
|
|
||||||
let api = client.runtime_api();
|
let api = client.runtime_api();
|
||||||
let para_id = collation.receipt.parachain_index;
|
let para_id = collation.receipt.parachain_index;
|
||||||
@@ -161,10 +265,15 @@ pub fn validate_collation<P>(
|
|||||||
block_data: collation.block_data.0.clone(),
|
block_data: collation.block_data.0.clone(),
|
||||||
};
|
};
|
||||||
|
|
||||||
match parachain::wasm::validate_candidate(&validation_code, params) {
|
let mut ext = Externalities {
|
||||||
|
parachain_index: collation.receipt.parachain_index.clone(),
|
||||||
|
outgoing: Vec::new(),
|
||||||
|
};
|
||||||
|
|
||||||
|
match wasm_executor::validate_candidate(&validation_code, params, &mut ext) {
|
||||||
Ok(result) => {
|
Ok(result) => {
|
||||||
if result.head_data == collation.receipt.head_data.0 {
|
if result.head_data == collation.receipt.head_data.0 {
|
||||||
Ok(Extrinsic)
|
ext.final_checks(&collation.receipt)
|
||||||
} else {
|
} else {
|
||||||
Err(ErrorKind::WrongHeadData(
|
Err(ErrorKind::WrongHeadData(
|
||||||
collation.receipt.head_data.0.clone(),
|
collation.receipt.head_data.0.clone(),
|
||||||
@@ -172,6 +281,60 @@ pub fn validate_collation<P>(
|
|||||||
).into())
|
).into())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Err(_) => Err(ErrorKind::ValidationFailure.into())
|
Err(e) => Err(e.into())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
use parachain::wasm_executor::Externalities as ExternalitiesTrait;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn egress_roots() {
|
||||||
|
let messages = vec![
|
||||||
|
OutgoingMessage { target: 3.into(), data: vec![1, 1, 1] },
|
||||||
|
OutgoingMessage { target: 1.into(), data: vec![1, 2, 3] },
|
||||||
|
OutgoingMessage { target: 2.into(), data: vec![4, 5, 6] },
|
||||||
|
OutgoingMessage { target: 1.into(), data: vec![7, 8, 9] },
|
||||||
|
];
|
||||||
|
|
||||||
|
let root_1 = egress_trie_root(&[vec![1, 2, 3], vec![7, 8, 9]]);
|
||||||
|
let root_2 = egress_trie_root(&[vec![4, 5, 6]]);
|
||||||
|
let root_3 = egress_trie_root(&[vec![1, 1, 1]]);
|
||||||
|
|
||||||
|
assert!(check_and_compute_extrinsic(
|
||||||
|
messages.clone(),
|
||||||
|
&[(1.into(), root_1), (2.into(), root_2), (3.into(), root_3)],
|
||||||
|
).is_ok());
|
||||||
|
|
||||||
|
// missing root.
|
||||||
|
assert!(check_and_compute_extrinsic(
|
||||||
|
messages.clone(),
|
||||||
|
&[(1.into(), root_1), (3.into(), root_3)],
|
||||||
|
).is_err());
|
||||||
|
|
||||||
|
// extra root.
|
||||||
|
assert!(check_and_compute_extrinsic(
|
||||||
|
messages.clone(),
|
||||||
|
&[(1.into(), root_1), (2.into(), root_2), (3.into(), root_3), (4.into(), Default::default())],
|
||||||
|
).is_err());
|
||||||
|
|
||||||
|
// root mismatch.
|
||||||
|
assert!(check_and_compute_extrinsic(
|
||||||
|
messages.clone(),
|
||||||
|
&[(1.into(), root_2), (2.into(), root_1), (3.into(), root_3)],
|
||||||
|
).is_err());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn ext_rejects_local_message() {
|
||||||
|
let mut ext = Externalities {
|
||||||
|
parachain_index: 5.into(),
|
||||||
|
outgoing: Vec::new(),
|
||||||
|
};
|
||||||
|
|
||||||
|
assert!(ext.post_message(MessageRef { target: 1, data: &[] }).is_ok());
|
||||||
|
assert!(ext.post_message(MessageRef { target: 5, data: &[] }).is_err());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -41,6 +41,7 @@ extern crate substrate_primitives as primitives;
|
|||||||
extern crate srml_support as runtime_support;
|
extern crate srml_support as runtime_support;
|
||||||
extern crate sr_primitives as runtime_primitives;
|
extern crate sr_primitives as runtime_primitives;
|
||||||
extern crate substrate_client as client;
|
extern crate substrate_client as client;
|
||||||
|
extern crate substrate_trie as trie;
|
||||||
|
|
||||||
extern crate exit_future;
|
extern crate exit_future;
|
||||||
extern crate tokio;
|
extern crate tokio;
|
||||||
@@ -90,7 +91,7 @@ use futures::future::{self, Either};
|
|||||||
use collation::CollationFetch;
|
use collation::CollationFetch;
|
||||||
use dynamic_inclusion::DynamicInclusion;
|
use dynamic_inclusion::DynamicInclusion;
|
||||||
|
|
||||||
pub use self::collation::{validate_collation, Collators};
|
pub use self::collation::{validate_collation, egress_trie_root, Collators};
|
||||||
pub use self::error::{ErrorKind, Error};
|
pub use self::error::{ErrorKind, Error};
|
||||||
pub use self::shared_table::{SharedTable, ParachainWork, PrimedParachainWork, Validated, Statement, SignedStatement, GenericStatement};
|
pub use self::shared_table::{SharedTable, ParachainWork, PrimedParachainWork, Validated, Statement, SignedStatement, GenericStatement};
|
||||||
|
|
||||||
@@ -113,8 +114,6 @@ pub trait TableRouter: Clone {
|
|||||||
type Error;
|
type Error;
|
||||||
/// Future that resolves when candidate data is fetched.
|
/// Future that resolves when candidate data is fetched.
|
||||||
type FetchCandidate: IntoFuture<Item=BlockData,Error=Self::Error>;
|
type FetchCandidate: IntoFuture<Item=BlockData,Error=Self::Error>;
|
||||||
/// Future that resolves when extrinsic candidate data is fetched.
|
|
||||||
type FetchExtrinsic: IntoFuture<Item=ParachainExtrinsic,Error=Self::Error>;
|
|
||||||
|
|
||||||
/// Call with local candidate data. This will make the data available on the network,
|
/// Call with local candidate data. This will make the data available on the network,
|
||||||
/// and sign, import, and broadcast a statement about the candidate.
|
/// and sign, import, and broadcast a statement about the candidate.
|
||||||
@@ -122,9 +121,6 @@ pub trait TableRouter: Clone {
|
|||||||
|
|
||||||
/// Fetch block data for a specific candidate.
|
/// Fetch block data for a specific candidate.
|
||||||
fn fetch_block_data(&self, candidate: &CandidateReceipt) -> Self::FetchCandidate;
|
fn fetch_block_data(&self, candidate: &CandidateReceipt) -> Self::FetchCandidate;
|
||||||
|
|
||||||
/// Fetch extrinsic data for a specific candidate.
|
|
||||||
fn fetch_extrinsic_data(&self, candidate: &CandidateReceipt) -> Self::FetchExtrinsic;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A long-lived network which can create parachain statement and BFT message routing processes on demand.
|
/// A long-lived network which can create parachain statement and BFT message routing processes on demand.
|
||||||
@@ -229,7 +225,6 @@ struct ParachainConsensus<C, N, P> {
|
|||||||
extrinsic_store: ExtrinsicStore,
|
extrinsic_store: ExtrinsicStore,
|
||||||
/// Live agreements.
|
/// Live agreements.
|
||||||
live_instances: Mutex<HashMap<Hash, Arc<AttestationTracker>>>,
|
live_instances: Mutex<HashMap<Hash, Arc<AttestationTracker>>>,
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<C, N, P> ParachainConsensus<C, N, P> where
|
impl<C, N, P> ParachainConsensus<C, N, P> where
|
||||||
|
|||||||
@@ -156,7 +156,7 @@ pub struct Validated {
|
|||||||
/// Block data to ensure availability of.
|
/// Block data to ensure availability of.
|
||||||
pub block_data: BlockData,
|
pub block_data: BlockData,
|
||||||
/// Extrinsic data to ensure availability of.
|
/// Extrinsic data to ensure availability of.
|
||||||
pub extrinsic: Extrinsic,
|
pub extrinsic: Option<Extrinsic>,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Future that performs parachain validation work.
|
/// Future that performs parachain validation work.
|
||||||
@@ -172,7 +172,7 @@ impl<D: Future> ParachainWork<D> {
|
|||||||
pub fn prime<P: ProvideRuntimeApi>(self, api: Arc<P>)
|
pub fn prime<P: ProvideRuntimeApi>(self, api: Arc<P>)
|
||||||
-> PrimedParachainWork<
|
-> PrimedParachainWork<
|
||||||
D,
|
D,
|
||||||
impl Send + FnMut(&BlockId, &Collation) -> bool,
|
impl Send + FnMut(&BlockId, &Collation) -> Result<Extrinsic, ()>,
|
||||||
>
|
>
|
||||||
where
|
where
|
||||||
P: Send + Sync + 'static,
|
P: Send + Sync + 'static,
|
||||||
@@ -186,10 +186,10 @@ impl<D: Future> ParachainWork<D> {
|
|||||||
);
|
);
|
||||||
|
|
||||||
match res {
|
match res {
|
||||||
Ok(_) => true,
|
Ok(e) => Ok(e),
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
debug!(target: "consensus", "Encountered bad collation: {}", e);
|
debug!(target: "consensus", "Encountered bad collation: {}", e);
|
||||||
false
|
Err(())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@@ -199,7 +199,7 @@ impl<D: Future> ParachainWork<D> {
|
|||||||
|
|
||||||
/// Prime the parachain work with a custom validation function.
|
/// Prime the parachain work with a custom validation function.
|
||||||
pub fn prime_with<F>(self, validate: F) -> PrimedParachainWork<D, F>
|
pub fn prime_with<F>(self, validate: F) -> PrimedParachainWork<D, F>
|
||||||
where F: FnMut(&BlockId, &Collation) -> bool
|
where F: FnMut(&BlockId, &Collation) -> Result<Extrinsic, ()>
|
||||||
{
|
{
|
||||||
PrimedParachainWork { inner: self, validate }
|
PrimedParachainWork { inner: self, validate }
|
||||||
}
|
}
|
||||||
@@ -219,7 +219,7 @@ pub struct PrimedParachainWork<D: Future, F> {
|
|||||||
impl<D, F, Err> Future for PrimedParachainWork<D, F>
|
impl<D, F, Err> Future for PrimedParachainWork<D, F>
|
||||||
where
|
where
|
||||||
D: Future<Item=BlockData,Error=Err>,
|
D: Future<Item=BlockData,Error=Err>,
|
||||||
F: FnMut(&BlockId, &Collation) -> bool,
|
F: FnMut(&BlockId, &Collation) -> Result<Extrinsic, ()>,
|
||||||
Err: From<::std::io::Error>,
|
Err: From<::std::io::Error>,
|
||||||
{
|
{
|
||||||
type Item = Validated;
|
type Item = Validated;
|
||||||
@@ -230,27 +230,30 @@ impl<D, F, Err> Future for PrimedParachainWork<D, F>
|
|||||||
let candidate = &work.candidate_receipt;
|
let candidate = &work.candidate_receipt;
|
||||||
|
|
||||||
let block = try_ready!(work.fetch_block_data.poll());
|
let block = try_ready!(work.fetch_block_data.poll());
|
||||||
let is_good = (self.validate)(
|
let validation_res = (self.validate)(
|
||||||
&BlockId::hash(self.inner.relay_parent),
|
&BlockId::hash(self.inner.relay_parent),
|
||||||
&Collation { block_data: block.clone(), receipt: candidate.clone() },
|
&Collation { block_data: block.clone(), receipt: candidate.clone() },
|
||||||
);
|
);
|
||||||
|
|
||||||
let candidate_hash = candidate.hash();
|
let candidate_hash = candidate.hash();
|
||||||
|
|
||||||
debug!(target: "consensus", "Making validity statement about candidate {}: is_good? {:?}", candidate_hash, is_good);
|
debug!(target: "consensus", "Making validity statement about candidate {}: is_good? {:?}",
|
||||||
let validity_statement = match is_good {
|
candidate_hash, validation_res.is_ok());
|
||||||
true => GenericStatement::Valid(candidate_hash),
|
|
||||||
false => GenericStatement::Invalid(candidate_hash),
|
|
||||||
};
|
|
||||||
|
|
||||||
let extrinsic = Extrinsic;
|
let (extrinsic, validity_statement) = match validation_res {
|
||||||
self.inner.extrinsic_store.make_available(Data {
|
Err(()) => (None, GenericStatement::Invalid(candidate_hash)),
|
||||||
relay_parent: self.inner.relay_parent,
|
Ok(extrinsic) => {
|
||||||
parachain_id: work.candidate_receipt.parachain_index,
|
self.inner.extrinsic_store.make_available(Data {
|
||||||
candidate_hash,
|
relay_parent: self.inner.relay_parent,
|
||||||
block_data: block.clone(),
|
parachain_id: work.candidate_receipt.parachain_index,
|
||||||
extrinsic: Some(extrinsic.clone()),
|
candidate_hash,
|
||||||
})?;
|
block_data: block.clone(),
|
||||||
|
extrinsic: Some(extrinsic.clone()),
|
||||||
|
})?;
|
||||||
|
|
||||||
|
(Some(extrinsic), GenericStatement::Valid(candidate_hash))
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
Ok(Async::Ready(Validated {
|
Ok(Async::Ready(Validated {
|
||||||
validity: validity_statement,
|
validity: validity_statement,
|
||||||
@@ -444,7 +447,6 @@ mod tests {
|
|||||||
impl TableRouter for DummyRouter {
|
impl TableRouter for DummyRouter {
|
||||||
type Error = ::std::io::Error;
|
type Error = ::std::io::Error;
|
||||||
type FetchCandidate = ::futures::future::FutureResult<BlockData,Self::Error>;
|
type FetchCandidate = ::futures::future::FutureResult<BlockData,Self::Error>;
|
||||||
type FetchExtrinsic = ::futures::future::FutureResult<Extrinsic,Self::Error>;
|
|
||||||
|
|
||||||
fn local_candidate(&self, _candidate: CandidateReceipt, _block_data: BlockData, _extrinsic: Extrinsic) {
|
fn local_candidate(&self, _candidate: CandidateReceipt, _block_data: BlockData, _extrinsic: Extrinsic) {
|
||||||
|
|
||||||
@@ -452,9 +454,6 @@ mod tests {
|
|||||||
fn fetch_block_data(&self, _candidate: &CandidateReceipt) -> Self::FetchCandidate {
|
fn fetch_block_data(&self, _candidate: &CandidateReceipt) -> Self::FetchCandidate {
|
||||||
future::ok(BlockData(vec![1, 2, 3, 4, 5]))
|
future::ok(BlockData(vec![1, 2, 3, 4, 5]))
|
||||||
}
|
}
|
||||||
fn fetch_extrinsic_data(&self, _candidate: &CandidateReceipt) -> Self::FetchExtrinsic {
|
|
||||||
future::ok(Extrinsic)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
@@ -586,7 +585,9 @@ mod tests {
|
|||||||
extrinsic_store: store.clone(),
|
extrinsic_store: store.clone(),
|
||||||
};
|
};
|
||||||
|
|
||||||
let produced = producer.prime_with(|_, _| true).wait().unwrap();
|
let produced = producer.prime_with(|_, _| Ok(Extrinsic { outgoing_messages: Vec::new() }))
|
||||||
|
.wait()
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
assert_eq!(produced.block_data, block_data);
|
assert_eq!(produced.block_data, block_data);
|
||||||
assert_eq!(produced.validity, GenericStatement::Valid(hash));
|
assert_eq!(produced.validity, GenericStatement::Valid(hash));
|
||||||
@@ -624,7 +625,9 @@ mod tests {
|
|||||||
extrinsic_store: store.clone(),
|
extrinsic_store: store.clone(),
|
||||||
};
|
};
|
||||||
|
|
||||||
let produced = producer.prime_with(|_, _| true).wait().unwrap();
|
let produced = producer.prime_with(|_, _| Ok(Extrinsic { outgoing_messages: Vec::new() }))
|
||||||
|
.wait()
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
assert_eq!(produced.block_data, block_data);
|
assert_eq!(produced.block_data, block_data);
|
||||||
|
|
||||||
|
|||||||
@@ -231,13 +231,21 @@ impl Knowledge {
|
|||||||
|
|
||||||
/// Note a statement seen from another validator.
|
/// Note a statement seen from another validator.
|
||||||
pub(crate) fn note_statement(&mut self, from: SessionKey, statement: &Statement) {
|
pub(crate) fn note_statement(&mut self, from: SessionKey, statement: &Statement) {
|
||||||
|
// those proposing the candidate or declaring it valid know everything.
|
||||||
|
// those claiming it invalid do not have the extrinsic data as it is
|
||||||
|
// generated by valid execution.
|
||||||
match *statement {
|
match *statement {
|
||||||
GenericStatement::Candidate(ref c) => {
|
GenericStatement::Candidate(ref c) => {
|
||||||
let mut entry = self.candidates.entry(c.hash()).or_insert_with(Default::default);
|
let mut entry = self.candidates.entry(c.hash()).or_insert_with(Default::default);
|
||||||
entry.knows_block_data.push(from);
|
entry.knows_block_data.push(from);
|
||||||
entry.knows_extrinsic.push(from);
|
entry.knows_extrinsic.push(from);
|
||||||
}
|
}
|
||||||
GenericStatement::Valid(ref hash) | GenericStatement::Invalid(ref hash) => self.candidates.entry(*hash)
|
GenericStatement::Valid(ref hash) => {
|
||||||
|
let mut entry = self.candidates.entry(*hash).or_insert_with(Default::default);
|
||||||
|
entry.knows_block_data.push(from);
|
||||||
|
entry.knows_extrinsic.push(from);
|
||||||
|
}
|
||||||
|
GenericStatement::Invalid(ref hash) => self.candidates.entry(*hash)
|
||||||
.or_insert_with(Default::default)
|
.or_insert_with(Default::default)
|
||||||
.knows_block_data
|
.knows_block_data
|
||||||
.push(from),
|
.push(from),
|
||||||
|
|||||||
@@ -167,7 +167,7 @@ impl<P: ProvideRuntimeApi + Send + Sync + 'static> Router<P>
|
|||||||
knowledge.lock().note_candidate(
|
knowledge.lock().note_candidate(
|
||||||
candidate_hash,
|
candidate_hash,
|
||||||
Some(produced.block_data),
|
Some(produced.block_data),
|
||||||
Some(produced.extrinsic),
|
produced.extrinsic,
|
||||||
);
|
);
|
||||||
|
|
||||||
let mut gossip = network.consensus_gossip().write();
|
let mut gossip = network.consensus_gossip().write();
|
||||||
@@ -188,7 +188,6 @@ impl<P: ProvideRuntimeApi + Send> TableRouter for Router<P>
|
|||||||
{
|
{
|
||||||
type Error = io::Error;
|
type Error = io::Error;
|
||||||
type FetchCandidate = BlockDataReceiver;
|
type FetchCandidate = BlockDataReceiver;
|
||||||
type FetchExtrinsic = Result<Extrinsic, Self::Error>;
|
|
||||||
|
|
||||||
fn local_candidate(&self, receipt: CandidateReceipt, block_data: BlockData, extrinsic: Extrinsic) {
|
fn local_candidate(&self, receipt: CandidateReceipt, block_data: BlockData, extrinsic: Extrinsic) {
|
||||||
// give to network to make available.
|
// give to network to make available.
|
||||||
@@ -207,10 +206,6 @@ impl<P: ProvideRuntimeApi + Send> TableRouter for Router<P>
|
|||||||
let rx = self.network.with_spec(|spec, ctx| { spec.fetch_block_data(ctx, candidate, parent_hash) });
|
let rx = self.network.with_spec(|spec, ctx| { spec.fetch_block_data(ctx, candidate, parent_hash) });
|
||||||
BlockDataReceiver { inner: rx }
|
BlockDataReceiver { inner: rx }
|
||||||
}
|
}
|
||||||
|
|
||||||
fn fetch_extrinsic_data(&self, _candidate: &CandidateReceipt) -> Self::FetchExtrinsic {
|
|
||||||
Ok(Extrinsic)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<P> Drop for Router<P> {
|
impl<P> Drop for Router<P> {
|
||||||
|
|||||||
@@ -15,4 +15,5 @@ tiny-keccak = "1.4"
|
|||||||
|
|
||||||
[features]
|
[features]
|
||||||
default = ["std"]
|
default = ["std"]
|
||||||
|
wasm-api = []
|
||||||
std = ["parity-codec/std", "wasmi", "error-chain"]
|
std = ["parity-codec/std", "wasmi", "error-chain"]
|
||||||
|
|||||||
@@ -37,8 +37,8 @@
|
|||||||
//! ^~~returned pointer
|
//! ^~~returned pointer
|
||||||
//! ```
|
//! ```
|
||||||
//!
|
//!
|
||||||
//! The `load_params` and `write_result` functions provide utilities for setting up
|
//! The `wasm_api` module (enabled only with the wasm-api feature) provides utilities
|
||||||
//! a parachain WASM module in Rust.
|
//! for setting up a parachain WASM module in Rust.
|
||||||
|
|
||||||
#![cfg_attr(not(feature = "std"), no_std)]
|
#![cfg_attr(not(feature = "std"), no_std)]
|
||||||
#![cfg_attr(not(feature = "std"), feature(alloc))]
|
#![cfg_attr(not(feature = "std"), feature(alloc))]
|
||||||
@@ -64,15 +64,17 @@ extern crate error_chain;
|
|||||||
|
|
||||||
#[cfg(not(feature = "std"))]
|
#[cfg(not(feature = "std"))]
|
||||||
use alloc::vec::Vec;
|
use alloc::vec::Vec;
|
||||||
use codec::{Encode, Decode};
|
|
||||||
|
|
||||||
#[cfg(feature = "std")]
|
#[cfg(feature = "std")]
|
||||||
pub mod wasm;
|
pub mod wasm_executor;
|
||||||
|
|
||||||
|
#[cfg(feature = "wasm-api")]
|
||||||
|
pub mod wasm_api;
|
||||||
|
|
||||||
/// Validation parameters for evaluating the parachain validity function.
|
/// Validation parameters for evaluating the parachain validity function.
|
||||||
// TODO: consolidated ingress and balance downloads
|
// TODO: consolidated ingress and balance downloads
|
||||||
#[derive(PartialEq, Eq, Encode, Decode)]
|
#[derive(PartialEq, Eq, Decode)]
|
||||||
#[cfg_attr(feature = "std", derive(Debug))]
|
#[cfg_attr(feature = "std", derive(Debug, Encode))]
|
||||||
pub struct ValidationParams {
|
pub struct ValidationParams {
|
||||||
/// The collation body.
|
/// The collation body.
|
||||||
pub block_data: Vec<u8>,
|
pub block_data: Vec<u8>,
|
||||||
@@ -82,38 +84,19 @@ pub struct ValidationParams {
|
|||||||
|
|
||||||
/// The result of parachain validation.
|
/// The result of parachain validation.
|
||||||
// TODO: egress and balance uploads
|
// TODO: egress and balance uploads
|
||||||
#[derive(PartialEq, Eq, Encode, Decode)]
|
#[derive(PartialEq, Eq, Encode)]
|
||||||
#[cfg_attr(feature = "std", derive(Debug))]
|
#[cfg_attr(feature = "std", derive(Debug, Decode))]
|
||||||
pub struct ValidationResult {
|
pub struct ValidationResult {
|
||||||
/// New head data that should be included in the relay chain state.
|
/// New head data that should be included in the relay chain state.
|
||||||
pub head_data: Vec<u8>,
|
pub head_data: Vec<u8>,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Load the validation params from memory when implementing a Rust parachain.
|
/// A reference to a message.
|
||||||
///
|
#[cfg(feature = "std")]
|
||||||
/// Offset and length must have been provided by the validation
|
pub struct MessageRef<'a> {
|
||||||
/// function's entry point.
|
/// The target parachain.
|
||||||
pub unsafe fn load_params(offset: usize, len: usize) -> ValidationParams {
|
pub target: u32,
|
||||||
let mut slice = ::core::slice::from_raw_parts(offset as *const u8, len);
|
/// Underlying data of the message.
|
||||||
|
pub data: &'a [u8],
|
||||||
ValidationParams::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.
|
|
||||||
::core::mem::forget(encoded);
|
|
||||||
end_ptr
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -0,0 +1,63 @@
|
|||||||
|
// Copyright 2019 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/>.
|
||||||
|
|
||||||
|
//! Utilities for writing parachain WASM.
|
||||||
|
|
||||||
|
use codec::{Encode, Decode};
|
||||||
|
use super::{ValidationParams, ValidationResult, Message};
|
||||||
|
|
||||||
|
mod ll {
|
||||||
|
extern "C" {
|
||||||
|
pub(super) fn ext_post_message(target: u32, data_ptr: *const u8, data_len: u32);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Load the validation params from memory when implementing a Rust parachain.
|
||||||
|
///
|
||||||
|
/// Offset and length must have been provided by the validation
|
||||||
|
/// function's entry point.
|
||||||
|
pub unsafe fn load_params(offset: usize, len: usize) -> ValidationParams {
|
||||||
|
let mut slice = ::core::slice::from_raw_parts(offset as *const u8, len);
|
||||||
|
|
||||||
|
ValidationParams::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.
|
||||||
|
::core::mem::forget(encoded);
|
||||||
|
end_ptr
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Post a message to another parachain.
|
||||||
|
pub fn post_message(message: &Message) {
|
||||||
|
let data_ptr = message.data.as_ptr();
|
||||||
|
let data_len = message.data.len();
|
||||||
|
|
||||||
|
unsafe { ll::ext_post_message(message.target, data_ptr, data_len as u32) }
|
||||||
|
}
|
||||||
@@ -22,19 +22,25 @@
|
|||||||
|
|
||||||
use codec::{Decode, Encode};
|
use codec::{Decode, Encode};
|
||||||
|
|
||||||
use wasmi::{self, Module, ModuleInstance, MemoryInstance, MemoryDescriptor, MemoryRef, ModuleImportResolver};
|
use wasmi::{self, Module, ModuleInstance, Trap, MemoryInstance, MemoryDescriptor, MemoryRef, ModuleImportResolver};
|
||||||
use wasmi::{memory_units, RuntimeValue};
|
use wasmi::{memory_units, RuntimeValue, Externals, Error as WasmError, ValueType};
|
||||||
use wasmi::Error as WasmError;
|
|
||||||
use wasmi::memory_units::{Bytes, Pages, RoundUpTo};
|
use wasmi::memory_units::{Bytes, Pages, RoundUpTo};
|
||||||
|
|
||||||
use super::{ValidationParams, ValidationResult};
|
use super::{ValidationParams, ValidationResult, MessageRef};
|
||||||
|
|
||||||
use std::cell::RefCell;
|
use std::cell::RefCell;
|
||||||
|
use std::fmt;
|
||||||
|
|
||||||
|
mod ids {
|
||||||
|
/// Post a message to another parachain.
|
||||||
|
pub const POST_MESSAGE: usize = 1;
|
||||||
|
}
|
||||||
|
|
||||||
error_chain! {
|
error_chain! {
|
||||||
types { Error, ErrorKind, ResultExt; }
|
types { Error, ErrorKind, ResultExt; }
|
||||||
foreign_links {
|
foreign_links {
|
||||||
Wasm(WasmError);
|
Wasm(WasmError);
|
||||||
|
Externalities(ExternalitiesError);
|
||||||
}
|
}
|
||||||
errors {
|
errors {
|
||||||
/// Call data too big. WASM32 only has a 32-bit address space.
|
/// Call data too big. WASM32 only has a 32-bit address space.
|
||||||
@@ -50,12 +56,68 @@ error_chain! {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// 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>;
|
||||||
|
}
|
||||||
|
|
||||||
|
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 {
|
struct Resolver {
|
||||||
max_memory: u32, // in pages.
|
max_memory: u32, // in pages.
|
||||||
memory: RefCell<Option<MemoryRef>>,
|
memory: RefCell<Option<MemoryRef>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ModuleImportResolver for Resolver {
|
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,
|
||||||
|
))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ => {
|
||||||
|
Err(WasmError::Instantiation(
|
||||||
|
format!("Export {} not found", field_name),
|
||||||
|
))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
fn resolve_memory(
|
fn resolve_memory(
|
||||||
&self,
|
&self,
|
||||||
field_name: &str,
|
field_name: &str,
|
||||||
@@ -79,17 +141,69 @@ impl ModuleImportResolver for Resolver {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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,
|
||||||
|
data: &mem[data_ptr..][..data_len],
|
||||||
|
});
|
||||||
|
|
||||||
|
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),
|
||||||
|
_ => panic!("no externality at given index"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Validate a candidate under the given validation code.
|
/// Validate a candidate under the given validation code.
|
||||||
///
|
///
|
||||||
/// This will fail if the validation code is not a proper parachain validation module.
|
/// This will fail if the validation code is not a proper parachain validation module.
|
||||||
pub fn validate_candidate(validation_code: &[u8], params: ValidationParams) -> Result<ValidationResult, Error> {
|
pub fn validate_candidate<E: Externalities>(
|
||||||
|
validation_code: &[u8],
|
||||||
|
params: ValidationParams,
|
||||||
|
externalities: &mut E,
|
||||||
|
) -> Result<ValidationResult, Error> {
|
||||||
use wasmi::LINEAR_MEMORY_PAGE_SIZE;
|
use wasmi::LINEAR_MEMORY_PAGE_SIZE;
|
||||||
|
|
||||||
// maximum memory in bytes
|
// maximum memory in bytes
|
||||||
const MAX_MEM: u32 = 1024 * 1024 * 1024; // 1 GiB
|
const MAX_MEM: u32 = 1024 * 1024 * 1024; // 1 GiB
|
||||||
|
|
||||||
// instantiate the module.
|
// instantiate the module.
|
||||||
let (module, memory) = {
|
let memory;
|
||||||
|
let mut externals;
|
||||||
|
let module = {
|
||||||
let module = Module::from_buffer(validation_code)?;
|
let module = Module::from_buffer(validation_code)?;
|
||||||
|
|
||||||
let module_resolver = Resolver {
|
let module_resolver = Resolver {
|
||||||
@@ -100,14 +214,19 @@ pub fn validate_candidate(validation_code: &[u8], params: ValidationParams) -> R
|
|||||||
let module = ModuleInstance::new(
|
let module = ModuleInstance::new(
|
||||||
&module,
|
&module,
|
||||||
&wasmi::ImportsBuilder::new().with_resolver("env", &module_resolver),
|
&wasmi::ImportsBuilder::new().with_resolver("env", &module_resolver),
|
||||||
)?.run_start(&mut wasmi::NopExternals).map_err(WasmError::Trap)?;
|
)?;
|
||||||
|
|
||||||
let memory = module_resolver.memory.borrow()
|
memory = module_resolver.memory.borrow()
|
||||||
.as_ref()
|
.as_ref()
|
||||||
.ok_or_else(|| WasmError::Instantiation("No imported memory instance".to_owned()))?
|
.ok_or_else(|| WasmError::Instantiation("No imported memory instance".to_owned()))?
|
||||||
.clone();
|
.clone();
|
||||||
|
|
||||||
(module, memory)
|
externals = ValidationExternals {
|
||||||
|
externalities,
|
||||||
|
memory: &memory,
|
||||||
|
};
|
||||||
|
|
||||||
|
module.run_start(&mut externals).map_err(WasmError::Trap)?
|
||||||
};
|
};
|
||||||
|
|
||||||
// allocate call data in memory.
|
// allocate call data in memory.
|
||||||
@@ -135,8 +254,14 @@ pub fn validate_candidate(validation_code: &[u8], params: ValidationParams) -> R
|
|||||||
let output = module.invoke_export(
|
let output = module.invoke_export(
|
||||||
"validate",
|
"validate",
|
||||||
&[RuntimeValue::I32(offset as i32), RuntimeValue::I32(len as i32)],
|
&[RuntimeValue::I32(offset as i32), RuntimeValue::I32(len as i32)],
|
||||||
&mut wasmi::NopExternals,
|
&mut externals,
|
||||||
)?;
|
)
|
||||||
|
.map_err(|e| -> Error {
|
||||||
|
e.as_host_error()
|
||||||
|
.and_then(|he| he.downcast_ref::<ExternalitiesError>())
|
||||||
|
.map(|ee| ErrorKind::Externalities(ee.clone()).into())
|
||||||
|
.unwrap_or_else(move || e.into())
|
||||||
|
})?;
|
||||||
|
|
||||||
match output {
|
match output {
|
||||||
Some(RuntimeValue::I32(len_offset)) => {
|
Some(RuntimeValue::I32(len_offset)) => {
|
||||||
@@ -144,9 +269,10 @@ pub fn validate_candidate(validation_code: &[u8], params: ValidationParams) -> R
|
|||||||
|
|
||||||
let mut len_bytes = [0u8; 4];
|
let mut len_bytes = [0u8; 4];
|
||||||
memory.get_into(len_offset, &mut len_bytes)?;
|
memory.get_into(len_offset, &mut len_bytes)?;
|
||||||
|
let len_offset = len_offset as usize;
|
||||||
|
|
||||||
let len = u32::decode(&mut &len_bytes[..])
|
let len = u32::decode(&mut &len_bytes[..])
|
||||||
.ok_or_else(|| ErrorKind::BadReturn)?;
|
.ok_or_else(|| ErrorKind::BadReturn)? as usize;
|
||||||
|
|
||||||
let return_offset = if len > len_offset {
|
let return_offset = if len > len_offset {
|
||||||
bail!(ErrorKind::BadReturn);
|
bail!(ErrorKind::BadReturn);
|
||||||
@@ -154,11 +280,15 @@ pub fn validate_candidate(validation_code: &[u8], params: ValidationParams) -> R
|
|||||||
len_offset - len
|
len_offset - len
|
||||||
};
|
};
|
||||||
|
|
||||||
// TODO: optimize when `wasmi` lets you inspect memory with a closure.
|
memory.with_direct_access(|mem| {
|
||||||
let raw_return = memory.get(return_offset, len as usize)?;
|
if mem.len() < return_offset + len {
|
||||||
ValidationResult::decode(&mut &raw_return[..])
|
return Err(ErrorKind::BadReturn.into());
|
||||||
.ok_or_else(|| ErrorKind::BadReturn)
|
}
|
||||||
.map_err(Into::into)
|
|
||||||
|
ValidationResult::decode(&mut &mem[return_offset..][..len])
|
||||||
|
.ok_or_else(|| ErrorKind::BadReturn)
|
||||||
|
.map_err(Into::into)
|
||||||
|
})
|
||||||
}
|
}
|
||||||
_ => bail!(ErrorKind::BadReturn),
|
_ => bail!(ErrorKind::BadReturn),
|
||||||
}
|
}
|
||||||
@@ -22,7 +22,8 @@ extern crate parity_codec as codec;
|
|||||||
extern crate polkadot_parachain as parachain;
|
extern crate polkadot_parachain as parachain;
|
||||||
extern crate tiny_keccak;
|
extern crate tiny_keccak;
|
||||||
|
|
||||||
use parachain::ValidationParams;
|
use parachain::{MessageRef, ValidationParams};
|
||||||
|
use parachain::wasm_executor::{Externalities, ExternalitiesError};
|
||||||
use codec::{Decode, Encode};
|
use codec::{Decode, Encode};
|
||||||
|
|
||||||
/// Head data for this parachain.
|
/// Head data for this parachain.
|
||||||
@@ -45,6 +46,13 @@ struct BlockData {
|
|||||||
add: u64,
|
add: u64,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
struct DummyExt;
|
||||||
|
impl Externalities for DummyExt {
|
||||||
|
fn post_message(&mut self, _message: MessageRef) -> Result<(), ExternalitiesError> {
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const TEST_CODE: &[u8] = include_bytes!("res/adder.wasm");
|
const TEST_CODE: &[u8] = include_bytes!("res/adder.wasm");
|
||||||
|
|
||||||
fn hash_state(state: u64) -> [u8; 32] {
|
fn hash_state(state: u64) -> [u8; 32] {
|
||||||
@@ -68,10 +76,14 @@ fn execute_good_on_parent() {
|
|||||||
add: 512,
|
add: 512,
|
||||||
};
|
};
|
||||||
|
|
||||||
let ret = parachain::wasm::validate_candidate(TEST_CODE, ValidationParams {
|
let ret = parachain::wasm_executor::validate_candidate(
|
||||||
parent_head: parent_head.encode(),
|
TEST_CODE,
|
||||||
block_data: block_data.encode(),
|
ValidationParams {
|
||||||
}).unwrap();
|
parent_head: parent_head.encode(),
|
||||||
|
block_data: block_data.encode(),
|
||||||
|
},
|
||||||
|
&mut DummyExt,
|
||||||
|
).unwrap();
|
||||||
|
|
||||||
let new_head = HeadData::decode(&mut &ret.head_data[..]).unwrap();
|
let new_head = HeadData::decode(&mut &ret.head_data[..]).unwrap();
|
||||||
|
|
||||||
@@ -98,10 +110,14 @@ fn execute_good_chain_on_parent() {
|
|||||||
add,
|
add,
|
||||||
};
|
};
|
||||||
|
|
||||||
let ret = parachain::wasm::validate_candidate(TEST_CODE, ValidationParams {
|
let ret = parachain::wasm_executor::validate_candidate(
|
||||||
parent_head: parent_head.encode(),
|
TEST_CODE,
|
||||||
block_data: block_data.encode(),
|
ValidationParams {
|
||||||
}).unwrap();
|
parent_head: parent_head.encode(),
|
||||||
|
block_data: block_data.encode(),
|
||||||
|
},
|
||||||
|
&mut DummyExt,
|
||||||
|
).unwrap();
|
||||||
|
|
||||||
let new_head = HeadData::decode(&mut &ret.head_data[..]).unwrap();
|
let new_head = HeadData::decode(&mut &ret.head_data[..]).unwrap();
|
||||||
|
|
||||||
@@ -128,8 +144,12 @@ fn execute_bad_on_parent() {
|
|||||||
add: 256,
|
add: 256,
|
||||||
};
|
};
|
||||||
|
|
||||||
let _ret = parachain::wasm::validate_candidate(TEST_CODE, ValidationParams {
|
let _ret = parachain::wasm_executor::validate_candidate(
|
||||||
parent_head: parent_head.encode(),
|
TEST_CODE,
|
||||||
block_data: block_data.encode(),
|
ValidationParams {
|
||||||
}).unwrap_err();
|
parent_head: parent_head.encode(),
|
||||||
|
block_data: block_data.encode(),
|
||||||
|
},
|
||||||
|
&mut DummyExt,
|
||||||
|
).unwrap_err();
|
||||||
}
|
}
|
||||||
|
|||||||
Binary file not shown.
@@ -66,12 +66,44 @@ pub struct DutyRoster {
|
|||||||
pub validator_duty: Vec<Chain>,
|
pub validator_duty: Vec<Chain>,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Extrinsic data for a parachain.
|
/// An outgoing message
|
||||||
#[derive(PartialEq, Eq, Clone)]
|
#[derive(Clone, PartialEq, Eq)]
|
||||||
#[cfg_attr(feature = "std", derive(Serialize, Deserialize, Debug))]
|
#[cfg_attr(feature = "std", derive(Serialize, Deserialize, Debug, Encode, Decode))]
|
||||||
#[cfg_attr(feature = "std", serde(rename_all = "camelCase"))]
|
#[cfg_attr(feature = "std", serde(rename_all = "camelCase"))]
|
||||||
#[cfg_attr(feature = "std", serde(deny_unknown_fields))]
|
#[cfg_attr(feature = "std", serde(deny_unknown_fields))]
|
||||||
pub struct Extrinsic;
|
pub struct OutgoingMessage {
|
||||||
|
/// The target parachain.
|
||||||
|
pub target: Id,
|
||||||
|
/// The message data.
|
||||||
|
pub data: Vec<u8>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl PartialOrd for OutgoingMessage {
|
||||||
|
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
|
||||||
|
Some(self.target.cmp(&other.target))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Ord for OutgoingMessage {
|
||||||
|
fn cmp(&self, other: &Self) -> Ordering {
|
||||||
|
self.target.cmp(&other.target)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Extrinsic data for a parachain candidate.
|
||||||
|
///
|
||||||
|
/// This is data produced by evaluating the candidate. It contains
|
||||||
|
/// full records of all outgoing messages to other parachains.
|
||||||
|
#[derive(PartialEq, Eq, Clone)]
|
||||||
|
#[cfg_attr(feature = "std", derive(Serialize, Deserialize, Debug, Encode, Decode))]
|
||||||
|
#[cfg_attr(feature = "std", serde(rename_all = "camelCase"))]
|
||||||
|
#[cfg_attr(feature = "std", serde(deny_unknown_fields))]
|
||||||
|
pub struct Extrinsic {
|
||||||
|
/// The outgoing messages from the execution of the parachain.
|
||||||
|
///
|
||||||
|
/// This must be sorted in ascending order by parachain ID.
|
||||||
|
pub outgoing_messages: Vec<OutgoingMessage>
|
||||||
|
}
|
||||||
|
|
||||||
/// Candidate receipt type.
|
/// Candidate receipt type.
|
||||||
#[derive(PartialEq, Eq, Clone, Encode, Decode)]
|
#[derive(PartialEq, Eq, Clone, Encode, Decode)]
|
||||||
@@ -89,7 +121,8 @@ pub struct CandidateReceipt {
|
|||||||
pub head_data: HeadData,
|
pub head_data: HeadData,
|
||||||
/// Balance uploads to the relay chain.
|
/// Balance uploads to the relay chain.
|
||||||
pub balance_uploads: Vec<(super::AccountId, u64)>,
|
pub balance_uploads: Vec<(super::AccountId, u64)>,
|
||||||
/// Egress queue roots.
|
/// Egress queue roots. Must be sorted lexicographically (ascending)
|
||||||
|
/// by parachain ID.
|
||||||
pub egress_queue_roots: Vec<(Id, Hash)>,
|
pub egress_queue_roots: Vec<(Id, Hash)>,
|
||||||
/// Fees paid from the chain to the relay chain validators
|
/// Fees paid from the chain to the relay chain validators
|
||||||
pub fees: u64,
|
pub fees: u64,
|
||||||
|
|||||||
BIN
Binary file not shown.
Binary file not shown.
@@ -1 +0,0 @@
|
|||||||
src
|
|
||||||
@@ -5,7 +5,7 @@ authors = ["Parity Technologies <admin@parity.io>"]
|
|||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
adder = { path = ".." }
|
adder = { path = ".." }
|
||||||
polkadot-parachain = { path = "../../../parachain", default-features = false }
|
polkadot-parachain = { path = "../../../parachain", default-features = false, features = ["wasm-api"] }
|
||||||
wee_alloc = { version = "0.4.1" }
|
wee_alloc = { version = "0.4.1" }
|
||||||
pwasm-libc = { version = "0.2" }
|
pwasm-libc = { version = "0.2" }
|
||||||
tiny-keccak = "1.4"
|
tiny-keccak = "1.4"
|
||||||
|
|||||||
@@ -23,13 +23,11 @@
|
|||||||
)]
|
)]
|
||||||
|
|
||||||
extern crate alloc;
|
extern crate alloc;
|
||||||
extern crate wee_alloc;
|
|
||||||
extern crate pwasm_libc;
|
extern crate pwasm_libc;
|
||||||
extern crate adder;
|
extern crate adder;
|
||||||
extern crate polkadot_parachain as parachain;
|
extern crate polkadot_parachain as parachain;
|
||||||
extern crate tiny_keccak;
|
extern crate tiny_keccak;
|
||||||
|
|
||||||
// Define global allocator.
|
|
||||||
#[global_allocator]
|
#[global_allocator]
|
||||||
static ALLOC: wee_alloc::WeeAlloc = wee_alloc::WeeAlloc::INIT;
|
static ALLOC: wee_alloc::WeeAlloc = wee_alloc::WeeAlloc::INIT;
|
||||||
|
|
||||||
@@ -56,7 +54,7 @@ pub fn oom(_: ::core::alloc::Layout) -> ! {
|
|||||||
|
|
||||||
#[no_mangle]
|
#[no_mangle]
|
||||||
pub extern fn validate(offset: usize, len: usize) -> usize {
|
pub extern fn validate(offset: usize, len: usize) -> usize {
|
||||||
let params = unsafe { ::parachain::load_params(offset, len) };
|
let params = unsafe { ::parachain::wasm_api::load_params(offset, len) };
|
||||||
let parent_head = HeadData::decode(&mut ¶ms.parent_head[..])
|
let parent_head = HeadData::decode(&mut ¶ms.parent_head[..])
|
||||||
.expect("invalid parent head format.");
|
.expect("invalid parent head format.");
|
||||||
|
|
||||||
@@ -66,7 +64,9 @@ pub extern fn validate(offset: usize, len: usize) -> usize {
|
|||||||
let parent_hash = ::tiny_keccak::keccak256(¶ms.parent_head[..]);
|
let parent_hash = ::tiny_keccak::keccak256(¶ms.parent_head[..]);
|
||||||
|
|
||||||
match ::adder::execute(parent_hash, parent_head, &block_data) {
|
match ::adder::execute(parent_hash, parent_head, &block_data) {
|
||||||
Ok(new_head) => parachain::write_result(ValidationResult { head_data: new_head.encode() }),
|
Ok(new_head) => parachain::wasm_api::write_result(
|
||||||
|
ValidationResult { head_data: new_head.encode() }
|
||||||
|
),
|
||||||
Err(_) => panic!("execution failure"),
|
Err(_) => panic!("execution failure"),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user