mirror of
https://github.com/pezkuwichain/pezkuwi-subxt.git
synced 2026-05-30 12:51:02 +00:00
Collator for the "adder" (formerly basic-add) parachain and various small fixes (#438)
* update basic_add wasm * wasm feature and collator feature * move test parachains around a little * fix wasm build for basic_add * move basic_add to adder, introduce README * minimal basic_add collator * ensure collator messages are sent in the right order * more logging * route consensus statements to all peers * minor bugfixes for parachains * genesis builder accounts for parachain heads * fix parachains tests * targets for txpool * tweak runtime + collator * fix version in adder-collator * consistency for overflowing * adjust comment * fix stable test run * remove dummy registration test * final grumbles
This commit is contained in:
committed by
GitHub
parent
f5aa4f6f79
commit
f4cd995558
@@ -60,6 +60,7 @@ extern crate polkadot_primitives;
|
|||||||
extern crate log;
|
extern crate log;
|
||||||
|
|
||||||
use std::collections::{BTreeSet, BTreeMap, HashSet};
|
use std::collections::{BTreeSet, BTreeMap, HashSet};
|
||||||
|
use std::fmt;
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
use std::time::{Duration, Instant};
|
use std::time::{Duration, Instant};
|
||||||
|
|
||||||
@@ -68,12 +69,36 @@ use client::BlockchainEvents;
|
|||||||
use polkadot_api::PolkadotApi;
|
use polkadot_api::PolkadotApi;
|
||||||
use polkadot_primitives::{AccountId, BlockId, SessionKey};
|
use polkadot_primitives::{AccountId, BlockId, SessionKey};
|
||||||
use polkadot_primitives::parachain::{self, BlockData, DutyRoster, HeadData, ConsolidatedIngress, Message, Id as ParaId};
|
use polkadot_primitives::parachain::{self, BlockData, DutyRoster, HeadData, ConsolidatedIngress, Message, Id as ParaId};
|
||||||
use polkadot_cli::{ServiceComponents, Service, CustomConfiguration, VersionInfo};
|
use polkadot_cli::{ServiceComponents, Service, CustomConfiguration};
|
||||||
use polkadot_cli::{Worker, IntoExit};
|
use polkadot_cli::{Worker, IntoExit};
|
||||||
use tokio::timer::Deadline;
|
use tokio::timer::Deadline;
|
||||||
|
|
||||||
|
pub use polkadot_cli::VersionInfo;
|
||||||
|
|
||||||
const COLLATION_TIMEOUT: Duration = Duration::from_secs(30);
|
const COLLATION_TIMEOUT: Duration = Duration::from_secs(30);
|
||||||
|
|
||||||
|
/// Error to return when the head data was invalid.
|
||||||
|
#[derive(Clone, Copy, Debug)]
|
||||||
|
pub struct InvalidHead;
|
||||||
|
|
||||||
|
/// Collation errors.
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub enum Error<R> {
|
||||||
|
/// Error on the relay-chain side of things.
|
||||||
|
Polkadot(R),
|
||||||
|
/// Error on the collator side of things.
|
||||||
|
Collator(InvalidHead),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<R: fmt::Display> fmt::Display for Error<R> {
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||||
|
match *self {
|
||||||
|
Error::Polkadot(ref err) => write!(f, "Polkadot node error: {}", err),
|
||||||
|
Error::Collator(_) => write!(f, "Collator node error: Invalid head data"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Parachain context needed for collation.
|
/// Parachain context needed for collation.
|
||||||
///
|
///
|
||||||
/// This can be implemented through an externally attached service or a stub.
|
/// This can be implemented through an externally attached service or a stub.
|
||||||
@@ -84,7 +109,7 @@ pub trait ParachainContext: Clone {
|
|||||||
&self,
|
&self,
|
||||||
last_head: HeadData,
|
last_head: HeadData,
|
||||||
ingress: I,
|
ingress: I,
|
||||||
) -> (BlockData, HeadData);
|
) -> Result<(BlockData, HeadData), InvalidHead>;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Relay chain context needed to collate.
|
/// Relay chain context needed to collate.
|
||||||
@@ -154,18 +179,18 @@ pub fn collate<'a, R, P>(
|
|||||||
para_context: P,
|
para_context: P,
|
||||||
key: Arc<ed25519::Pair>,
|
key: Arc<ed25519::Pair>,
|
||||||
)
|
)
|
||||||
-> impl Future<Item=parachain::Collation, Error=R::Error> + 'a
|
-> impl Future<Item=parachain::Collation, Error=Error<R::Error>> + 'a
|
||||||
where
|
where
|
||||||
R: RelayChainContext + 'a,
|
R: RelayChainContext + 'a,
|
||||||
R::Error: 'a,
|
R::Error: 'a,
|
||||||
R::FutureEgress: 'a,
|
R::FutureEgress: 'a,
|
||||||
P: ParachainContext + 'a,
|
P: ParachainContext + 'a,
|
||||||
{
|
{
|
||||||
collate_ingress(relay_context).map(move |ingress| {
|
collate_ingress(relay_context).map_err(Error::Polkadot).and_then(move |ingress| {
|
||||||
let (block_data, head_data) = para_context.produce_candidate(
|
let (block_data, head_data) = para_context.produce_candidate(
|
||||||
last_head,
|
last_head,
|
||||||
ingress.0.iter().flat_map(|&(id, ref msgs)| msgs.iter().cloned().map(move |msg| (id, msg)))
|
ingress.0.iter().flat_map(|&(id, ref msgs)| msgs.iter().cloned().map(move |msg| (id, msg)))
|
||||||
);
|
).map_err(Error::Collator)?;
|
||||||
|
|
||||||
let block_data_hash = block_data.hash();
|
let block_data_hash = block_data.hash();
|
||||||
let signature = key.sign(&block_data_hash.0[..]).into();
|
let signature = key.sign(&block_data_hash.0[..]).into();
|
||||||
@@ -181,10 +206,10 @@ pub fn collate<'a, R, P>(
|
|||||||
block_data_hash,
|
block_data_hash,
|
||||||
};
|
};
|
||||||
|
|
||||||
parachain::Collation {
|
Ok(parachain::Collation {
|
||||||
receipt,
|
receipt,
|
||||||
block_data,
|
block_data,
|
||||||
}
|
})
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -248,7 +273,7 @@ impl<P, E> Worker for CollationNode<P, E> where
|
|||||||
($e:expr) => {
|
($e:expr) => {
|
||||||
match $e {
|
match $e {
|
||||||
Ok(x) => x,
|
Ok(x) => x,
|
||||||
Err(e) => return future::Either::A(future::err(e)),
|
Err(e) => return future::Either::A(future::err(Error::Polkadot(e))),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -323,17 +348,19 @@ fn compute_targets(para_id: ParaId, session_keys: &[SessionKey], roster: DutyRos
|
|||||||
///
|
///
|
||||||
/// Provide a future which resolves when the node should exit.
|
/// Provide a future which resolves when the node should exit.
|
||||||
/// This function blocks until done.
|
/// This function blocks until done.
|
||||||
pub fn run_collator<P, E>(
|
pub fn run_collator<P, E, I, ArgT>(
|
||||||
parachain_context: P,
|
parachain_context: P,
|
||||||
para_id: ParaId,
|
para_id: ParaId,
|
||||||
exit: E,
|
exit: E,
|
||||||
key: Arc<ed25519::Pair>,
|
key: Arc<ed25519::Pair>,
|
||||||
args: Vec<::std::ffi::OsString>,
|
args: I,
|
||||||
version: VersionInfo,
|
version: VersionInfo,
|
||||||
) -> polkadot_cli::error::Result<()> where
|
) -> polkadot_cli::error::Result<()> where
|
||||||
P: ParachainContext + Send + 'static,
|
P: ParachainContext + Send + 'static,
|
||||||
E: IntoFuture<Item=(),Error=()>,
|
E: IntoFuture<Item=(),Error=()>,
|
||||||
E::Future: Send + Clone + 'static,
|
E::Future: Send + Clone + 'static,
|
||||||
|
I: IntoIterator<Item=ArgT>,
|
||||||
|
ArgT: Into<std::ffi::OsString> + Clone,
|
||||||
{
|
{
|
||||||
let node_logic = CollationNode { parachain_context, exit: exit.into_future(), para_id, key };
|
let node_logic = CollationNode { parachain_context, exit: exit.into_future(), para_id, key };
|
||||||
polkadot_cli::run(args, node_logic, version)
|
polkadot_cli::run(args, node_logic, version)
|
||||||
|
|||||||
@@ -273,8 +273,13 @@ impl<C, N, P> bft::Environment<Block> for ProposerFactory<C, N, P>
|
|||||||
sign_with.public().into(),
|
sign_with.public().into(),
|
||||||
)?;
|
)?;
|
||||||
|
|
||||||
|
info!("Starting consensus session on top of parent {:?}. Local parachain duty is {:?}",
|
||||||
|
parent_hash, local_duty.validation);
|
||||||
|
|
||||||
let active_parachains = self.client.active_parachains(&id)?;
|
let active_parachains = self.client.active_parachains(&id)?;
|
||||||
|
|
||||||
|
debug!(target: "consensus", "Active parachains: {:?}", active_parachains);
|
||||||
|
|
||||||
let n_parachains = active_parachains.len();
|
let n_parachains = active_parachains.len();
|
||||||
let table = Arc::new(SharedTable::new(group_info, sign_with.clone(), parent_hash));
|
let table = Arc::new(SharedTable::new(group_info, sign_with.clone(), parent_hash));
|
||||||
let (router, input, output) = self.network.communication_for(
|
let (router, input, output) = self.network.communication_for(
|
||||||
|
|||||||
@@ -143,6 +143,7 @@ impl SharedTableInner {
|
|||||||
fetch_block_data,
|
fetch_block_data,
|
||||||
fetch_extrinsic,
|
fetch_extrinsic,
|
||||||
evaluate: checking_validity,
|
evaluate: checking_validity,
|
||||||
|
ensure_available: checking_availability,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -206,6 +207,7 @@ struct Work<D: Future, E: Future> {
|
|||||||
fetch_block_data: future::Fuse<D>,
|
fetch_block_data: future::Fuse<D>,
|
||||||
fetch_extrinsic: Option<future::Fuse<E>>,
|
fetch_extrinsic: Option<future::Fuse<E>>,
|
||||||
evaluate: bool,
|
evaluate: bool,
|
||||||
|
ensure_available: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Primed statement producer.
|
/// Primed statement producer.
|
||||||
@@ -235,31 +237,35 @@ impl<D, E, C, Err> Future for PrimedStatementProducer<D, E, C>
|
|||||||
});
|
});
|
||||||
|
|
||||||
let hash = work.candidate_receipt.hash();
|
let hash = work.candidate_receipt.hash();
|
||||||
|
|
||||||
|
debug!(target: "consensus", "Making validity statement about candidate {}: is_good? {:?}", hash, is_good);
|
||||||
self.inner.produced_statements.validity = match is_good {
|
self.inner.produced_statements.validity = match is_good {
|
||||||
Some(true) => Some(GenericStatement::Valid(hash)),
|
Some(true) => Some(GenericStatement::Valid(hash)),
|
||||||
Some(false) => Some(GenericStatement::Invalid(hash)),
|
Some(false) => Some(GenericStatement::Invalid(hash)),
|
||||||
None => None,
|
None => None,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
work.evaluate = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Some(ref mut fetch_extrinsic) = work.fetch_extrinsic {
|
if let Async::Ready(Some(extrinsic)) = work.fetch_extrinsic.poll()? {
|
||||||
if let Async::Ready(extrinsic) = fetch_extrinsic.poll()? {
|
if work.ensure_available {
|
||||||
|
let hash = work.candidate_receipt.hash();
|
||||||
|
debug!(target: "consensus", "Claiming candidate {} available.", hash);
|
||||||
|
|
||||||
|
// TODO: actually wait for block data and then ensure availability.
|
||||||
self.inner.produced_statements.extrinsic = Some(extrinsic);
|
self.inner.produced_statements.extrinsic = Some(extrinsic);
|
||||||
|
self.inner.produced_statements.availability =
|
||||||
|
Some(GenericStatement::Available(hash));
|
||||||
|
|
||||||
|
work.ensure_available = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let done = self.inner.produced_statements.block_data.is_some() && {
|
let done = match (work.evaluate, work.ensure_available) {
|
||||||
if work.evaluate {
|
(false, false) => true,
|
||||||
true
|
_ => false,
|
||||||
} else if self.inner.produced_statements.extrinsic.is_some() {
|
|
||||||
self.inner.produced_statements.availability =
|
|
||||||
Some(GenericStatement::Available(work.candidate_receipt.hash()));
|
|
||||||
|
|
||||||
true
|
|
||||||
} else {
|
|
||||||
false
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
if done {
|
if done {
|
||||||
@@ -356,10 +362,25 @@ impl SharedTable {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Sign and import a local statement.
|
/// Sign and import a local statement.
|
||||||
pub fn sign_and_import(&self, statement: table::Statement) -> SignedStatement {
|
///
|
||||||
let proposed_digest = match statement {
|
/// For candidate statements, this may also produce a second signed statement
|
||||||
GenericStatement::Candidate(ref c) => Some(c.hash()),
|
/// concerning the availability of the candidate data.
|
||||||
_ => None,
|
pub fn sign_and_import(&self, statement: table::Statement)
|
||||||
|
-> (SignedStatement, Option<SignedStatement>)
|
||||||
|
{
|
||||||
|
let (proposed_digest, availability) = match statement {
|
||||||
|
GenericStatement::Candidate(ref c) => {
|
||||||
|
let mut availability = None;
|
||||||
|
let hash = c.hash();
|
||||||
|
|
||||||
|
// TODO: actually store the data in an availability store of some kind.
|
||||||
|
if self.context.is_availability_guarantor_of(&self.context.local_id(), &c.parachain_index) {
|
||||||
|
availability = Some(self.context.sign_statement(GenericStatement::Available(hash)));
|
||||||
|
}
|
||||||
|
|
||||||
|
(Some(hash), availability)
|
||||||
|
}
|
||||||
|
_ => (None, None),
|
||||||
};
|
};
|
||||||
|
|
||||||
let signed_statement = self.context.sign_statement(statement);
|
let signed_statement = self.context.sign_statement(statement);
|
||||||
@@ -370,7 +391,13 @@ impl SharedTable {
|
|||||||
}
|
}
|
||||||
|
|
||||||
inner.table.import_statement(&*self.context, signed_statement.clone());
|
inner.table.import_statement(&*self.context, signed_statement.clone());
|
||||||
signed_statement
|
|
||||||
|
// ensure the availability statement is imported after the candidate.
|
||||||
|
if let Some(a) = availability.clone() {
|
||||||
|
inner.table.import_statement(&*self.context, a);
|
||||||
|
}
|
||||||
|
|
||||||
|
(signed_statement, availability)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Execute a closure using a specific candidate.
|
/// Execute a closure using a specific candidate.
|
||||||
@@ -543,5 +570,6 @@ mod tests {
|
|||||||
|
|
||||||
assert!(producer.work.fetch_extrinsic.is_some(), "should fetch extrinsic when guaranteeing availability");
|
assert!(producer.work.fetch_extrinsic.is_some(), "should fetch extrinsic when guaranteeing availability");
|
||||||
assert!(!producer.work.evaluate, "should not evaluate validity");
|
assert!(!producer.work.evaluate, "should not evaluate validity");
|
||||||
|
assert!(producer.work.ensure_available);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -176,6 +176,7 @@ impl<P: LocalPolkadotApi + Send + Sync + 'static> MessageProcessTask<P> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
ConsensusMessage::ChainSpecific(msg, _) => {
|
ConsensusMessage::ChainSpecific(msg, _) => {
|
||||||
|
debug!(target: "consensus", "Processing consensus statement for live consensus");
|
||||||
if let Some(Message::Statement(parent_hash, statement)) = Decode::decode(&mut msg.as_slice()) {
|
if let Some(Message::Statement(parent_hash, statement)) = Decode::decode(&mut msg.as_slice()) {
|
||||||
if ::polkadot_consensus::check_statement(&statement.statement, &statement.signature, statement.sender, &parent_hash) {
|
if ::polkadot_consensus::check_statement(&statement.statement, &statement.signature, statement.sender, &parent_hash) {
|
||||||
self.table_router.import_statement(statement);
|
self.table_router.import_statement(statement);
|
||||||
|
|||||||
+83
-43
@@ -111,10 +111,36 @@ struct BlockDataRequest {
|
|||||||
sender: oneshot::Sender<BlockData>,
|
sender: oneshot::Sender<BlockData>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ensures collator-protocol messages are sent in correct order.
|
||||||
|
// session key must be sent before collator role.
|
||||||
|
enum CollatorState {
|
||||||
|
Fresh,
|
||||||
|
RolePending(Role),
|
||||||
|
Primed,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl CollatorState {
|
||||||
|
fn send_key<F: FnMut(Message)>(&mut self, key: SessionKey, mut f: F) {
|
||||||
|
f(Message::SessionKey(key));
|
||||||
|
if let CollatorState::RolePending(role) = ::std::mem::replace(self, CollatorState::Primed) {
|
||||||
|
f(Message::CollatorRole(role));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn set_role<F: FnMut(Message)>(&mut self, role: Role, mut f: F) {
|
||||||
|
if let CollatorState::Primed = *self {
|
||||||
|
f(Message::CollatorRole(role));
|
||||||
|
} else {
|
||||||
|
*self = CollatorState::RolePending(role);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
struct PeerInfo {
|
struct PeerInfo {
|
||||||
collating_for: Option<(AccountId, ParaId)>,
|
collating_for: Option<(AccountId, ParaId)>,
|
||||||
validator_key: Option<SessionKey>,
|
validator_key: Option<SessionKey>,
|
||||||
claimed_validator: bool,
|
claimed_validator: bool,
|
||||||
|
collator_state: CollatorState,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Default)]
|
#[derive(Default)]
|
||||||
@@ -281,8 +307,8 @@ impl PolkadotProtocol {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Send a statement to a validator.
|
/// Gossip a consensus statement.
|
||||||
fn send_statement(&mut self, ctx: &mut Context<Block>, _val: SessionKey, parent_hash: Hash, statement: SignedStatement) {
|
fn gossip_statement(&mut self, ctx: &mut Context<Block>, parent_hash: Hash, statement: SignedStatement) {
|
||||||
// TODO: something more targeted than gossip.
|
// TODO: something more targeted than gossip.
|
||||||
let raw = Message::Statement(parent_hash, statement).encode();
|
let raw = Message::Statement(parent_hash, statement).encode();
|
||||||
self.consensus_gossip.multicast_chain_specific(ctx, raw, parent_hash);
|
self.consensus_gossip.multicast_chain_specific(ctx, raw, parent_hash);
|
||||||
@@ -309,14 +335,14 @@ impl PolkadotProtocol {
|
|||||||
let old_data = self.live_consensus.as_ref().map(|c| (c.parent_hash, c.local_session_key));
|
let old_data = self.live_consensus.as_ref().map(|c| (c.parent_hash, c.local_session_key));
|
||||||
|
|
||||||
if Some(&consensus.local_session_key) != old_data.as_ref().map(|&(_, ref key)| key) {
|
if Some(&consensus.local_session_key) != old_data.as_ref().map(|&(_, ref key)| key) {
|
||||||
for (id, _) in self.peers.iter()
|
for (id, peer_data) in self.peers.iter_mut()
|
||||||
.filter(|&(_, ref info)| info.claimed_validator || info.collating_for.is_some())
|
.filter(|&(_, ref info)| info.claimed_validator || info.collating_for.is_some())
|
||||||
{
|
{
|
||||||
send_polkadot_message(
|
peer_data.collator_state.send_key(consensus.local_session_key, |msg| send_polkadot_message(
|
||||||
ctx,
|
ctx,
|
||||||
*id,
|
*id,
|
||||||
Message::SessionKey(consensus.local_session_key)
|
msg
|
||||||
);
|
));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -452,12 +478,15 @@ impl PolkadotProtocol {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
debug!(target: "p_net", "New collator role {:?} from {}", role, who);
|
||||||
|
|
||||||
match info.validator_key {
|
match info.validator_key {
|
||||||
None => ctx.report_peer(
|
None => ctx.report_peer(
|
||||||
who,
|
who,
|
||||||
Severity::Bad("Sent collator role without registering first as validator"),
|
Severity::Bad("Sent collator role without registering first as validator"),
|
||||||
),
|
),
|
||||||
Some(key) => for (relay_parent, collation) in self.local_collations.note_validator_role(key, role) {
|
Some(key) => for (relay_parent, collation) in self.local_collations.note_validator_role(key, role) {
|
||||||
|
debug!(target: "p_net", "Broadcasting collation on relay parent {:?}", relay_parent);
|
||||||
send_polkadot_message(
|
send_polkadot_message(
|
||||||
ctx,
|
ctx,
|
||||||
who,
|
who,
|
||||||
@@ -481,38 +510,41 @@ impl Specialization<Block> for PolkadotProtocol {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
let validator = status.roles.contains(substrate_network::Roles::AUTHORITY);
|
||||||
|
let send_key = validator || local_status.collating_for.is_some();
|
||||||
|
|
||||||
|
let mut peer_info = PeerInfo {
|
||||||
|
collating_for: local_status.collating_for,
|
||||||
|
validator_key: None,
|
||||||
|
claimed_validator: validator,
|
||||||
|
collator_state: CollatorState::Fresh,
|
||||||
|
};
|
||||||
|
|
||||||
if let Some((ref acc_id, ref para_id)) = local_status.collating_for {
|
if let Some((ref acc_id, ref para_id)) = local_status.collating_for {
|
||||||
if self.collator_peer_id(acc_id.clone()).is_some() {
|
if self.collator_peer(acc_id.clone()).is_some() {
|
||||||
ctx.report_peer(who, Severity::Useless("Unknown Polkadot-specific reason"));
|
ctx.report_peer(who, Severity::Useless("Unknown Polkadot-specific reason"));
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
let collator_role = self.collators.on_new_collator(acc_id.clone(), para_id.clone());
|
let collator_role = self.collators.on_new_collator(acc_id.clone(), para_id.clone());
|
||||||
send_polkadot_message(
|
|
||||||
|
peer_info.collator_state.set_role(collator_role, |msg| send_polkadot_message(
|
||||||
ctx,
|
ctx,
|
||||||
who,
|
who,
|
||||||
Message::CollatorRole(collator_role),
|
msg,
|
||||||
);
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
let validator = status.roles.contains(substrate_network::Roles::AUTHORITY);
|
|
||||||
let send_key = validator || local_status.collating_for.is_some();
|
|
||||||
|
|
||||||
self.peers.insert(who, PeerInfo {
|
|
||||||
collating_for: local_status.collating_for,
|
|
||||||
validator_key: None,
|
|
||||||
claimed_validator: validator,
|
|
||||||
});
|
|
||||||
|
|
||||||
self.consensus_gossip.new_peer(ctx, who, status.roles);
|
|
||||||
if let (true, &Some(ref consensus)) = (send_key, &self.live_consensus) {
|
if let (true, &Some(ref consensus)) = (send_key, &self.live_consensus) {
|
||||||
send_polkadot_message(
|
peer_info.collator_state.send_key(consensus.local_session_key, |msg| send_polkadot_message(
|
||||||
ctx,
|
ctx,
|
||||||
who,
|
who,
|
||||||
Message::SessionKey(consensus.local_session_key)
|
msg,
|
||||||
);
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
self.peers.insert(who, peer_info);
|
||||||
|
self.consensus_gossip.new_peer(ctx, who, status.roles);
|
||||||
self.dispatch_pending_requests(ctx);
|
self.dispatch_pending_requests(ctx);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -520,14 +552,14 @@ impl Specialization<Block> for PolkadotProtocol {
|
|||||||
if let Some(info) = self.peers.remove(&who) {
|
if let Some(info) = self.peers.remove(&who) {
|
||||||
if let Some((acc_id, _)) = info.collating_for {
|
if let Some((acc_id, _)) = info.collating_for {
|
||||||
let new_primary = self.collators.on_disconnect(acc_id)
|
let new_primary = self.collators.on_disconnect(acc_id)
|
||||||
.and_then(|new_primary| self.collator_peer_id(new_primary));
|
.and_then(|new_primary| self.collator_peer(new_primary));
|
||||||
|
|
||||||
if let Some(new_primary) = new_primary {
|
if let Some((new_primary, primary_info)) = new_primary {
|
||||||
send_polkadot_message(
|
primary_info.collator_state.set_role(Role::Primary, |msg| send_polkadot_message(
|
||||||
ctx,
|
ctx,
|
||||||
new_primary,
|
new_primary,
|
||||||
Message::CollatorRole(Role::Primary),
|
msg,
|
||||||
)
|
));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -592,12 +624,12 @@ impl Specialization<Block> for PolkadotProtocol {
|
|||||||
for collator_action in self.collators.maintain_peers() {
|
for collator_action in self.collators.maintain_peers() {
|
||||||
match collator_action {
|
match collator_action {
|
||||||
Action::Disconnect(collator) => self.disconnect_bad_collator(ctx, collator),
|
Action::Disconnect(collator) => self.disconnect_bad_collator(ctx, collator),
|
||||||
Action::NewRole(account_id, role) => if let Some(collator) = self.collator_peer_id(account_id) {
|
Action::NewRole(account_id, role) => if let Some((collator, info)) = self.collator_peer(account_id) {
|
||||||
send_polkadot_message(
|
info.collator_state.set_role(role, |msg| send_polkadot_message(
|
||||||
ctx,
|
ctx,
|
||||||
collator,
|
collator,
|
||||||
Message::CollatorRole(role),
|
msg,
|
||||||
)
|
))
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -622,6 +654,7 @@ impl PolkadotProtocol {
|
|||||||
Some((ref acc_id, ref para_id)) => {
|
Some((ref acc_id, ref para_id)) => {
|
||||||
let structurally_valid = para_id == &collation_para && acc_id == &collated_acc;
|
let structurally_valid = para_id == &collation_para && acc_id == &collated_acc;
|
||||||
if structurally_valid && collation.receipt.check_signature().is_ok() {
|
if structurally_valid && collation.receipt.check_signature().is_ok() {
|
||||||
|
debug!(target: "p_net", "Received collation for parachain {:?} from peer {}", para_id, from);
|
||||||
self.collators.on_collation(acc_id.clone(), relay_parent, collation)
|
self.collators.on_collation(acc_id.clone(), relay_parent, collation)
|
||||||
} else {
|
} else {
|
||||||
ctx.report_peer(from, Severity::Bad("Sent malformed collation"))
|
ctx.report_peer(from, Severity::Bad("Sent malformed collation"))
|
||||||
@@ -633,27 +666,28 @@ impl PolkadotProtocol {
|
|||||||
|
|
||||||
fn await_collation(&mut self, relay_parent: Hash, para_id: ParaId) -> oneshot::Receiver<Collation> {
|
fn await_collation(&mut self, relay_parent: Hash, para_id: ParaId) -> oneshot::Receiver<Collation> {
|
||||||
let (tx, rx) = oneshot::channel();
|
let (tx, rx) = oneshot::channel();
|
||||||
|
debug!(target: "p_net", "Attempting to get collation for parachain {:?} on relay parent {:?}", para_id, relay_parent);
|
||||||
self.collators.await_collation(relay_parent, para_id, tx);
|
self.collators.await_collation(relay_parent, para_id, tx);
|
||||||
rx
|
rx
|
||||||
}
|
}
|
||||||
|
|
||||||
// get connected peer with given account ID for collation.
|
// get connected peer with given account ID for collation.
|
||||||
fn collator_peer_id(&self, account_id: AccountId) -> Option<NodeIndex> {
|
fn collator_peer(&mut self, account_id: AccountId) -> Option<(NodeIndex, &mut PeerInfo)> {
|
||||||
let check_info = |info: &PeerInfo| info
|
let check_info = |info: &PeerInfo| info
|
||||||
.collating_for
|
.collating_for
|
||||||
.as_ref()
|
.as_ref()
|
||||||
.map_or(false, |&(ref acc_id, _)| acc_id == &account_id);
|
.map_or(false, |&(ref acc_id, _)| acc_id == &account_id);
|
||||||
|
|
||||||
self.peers
|
self.peers
|
||||||
.iter()
|
.iter_mut()
|
||||||
.filter(|&(_, info)| check_info(info))
|
.filter(|&(_, ref info)| check_info(&**info))
|
||||||
.map(|(who, _)| *who)
|
.map(|(who, info)| (*who, info))
|
||||||
.next()
|
.next()
|
||||||
}
|
}
|
||||||
|
|
||||||
// disconnect a collator by account-id.
|
// disconnect a collator by account-id.
|
||||||
fn disconnect_bad_collator(&self, ctx: &mut Context<Block>, account_id: AccountId) {
|
fn disconnect_bad_collator(&mut self, ctx: &mut Context<Block>, account_id: AccountId) {
|
||||||
if let Some(who) = self.collator_peer_id(account_id) {
|
if let Some((who, _)) = self.collator_peer(account_id) {
|
||||||
ctx.report_peer(who, Severity::Bad("Consensus layer determined the given collator misbehaved"))
|
ctx.report_peer(who, Severity::Bad("Consensus layer determined the given collator misbehaved"))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -668,13 +702,19 @@ impl PolkadotProtocol {
|
|||||||
targets: HashSet<SessionKey>,
|
targets: HashSet<SessionKey>,
|
||||||
collation: Collation,
|
collation: Collation,
|
||||||
) {
|
) {
|
||||||
|
debug!(target: "p_net", "Importing local collation on relay parent {:?} and parachain {:?}",
|
||||||
|
relay_parent, collation.receipt.parachain_index);
|
||||||
|
|
||||||
for (primary, cloned_collation) in self.local_collations.add_collation(relay_parent, targets, collation.clone()) {
|
for (primary, cloned_collation) in self.local_collations.add_collation(relay_parent, targets, collation.clone()) {
|
||||||
match self.validators.get(&primary) {
|
match self.validators.get(&primary) {
|
||||||
Some(who) => send_polkadot_message(
|
Some(who) => {
|
||||||
ctx,
|
debug!(target: "p_net", "Sending local collation to {:?}", primary);
|
||||||
*who,
|
send_polkadot_message(
|
||||||
Message::Collation(relay_parent, cloned_collation),
|
ctx,
|
||||||
),
|
*who,
|
||||||
|
Message::Collation(relay_parent, cloned_collation),
|
||||||
|
)
|
||||||
|
},
|
||||||
None =>
|
None =>
|
||||||
warn!(target: "polkadot_network", "Encountered tracked but disconnected validator {:?}", primary),
|
warn!(target: "polkadot_network", "Encountered tracked but disconnected validator {:?}", primary),
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -25,7 +25,7 @@
|
|||||||
use polkadot_api::{PolkadotApi, LocalPolkadotApi};
|
use polkadot_api::{PolkadotApi, LocalPolkadotApi};
|
||||||
use polkadot_consensus::{SharedTable, TableRouter, SignedStatement, GenericStatement, StatementProducer};
|
use polkadot_consensus::{SharedTable, TableRouter, SignedStatement, GenericStatement, StatementProducer};
|
||||||
use polkadot_primitives::{Hash, BlockId, SessionKey};
|
use polkadot_primitives::{Hash, BlockId, SessionKey};
|
||||||
use polkadot_primitives::parachain::{BlockData, Extrinsic, CandidateReceipt, Id as ParaId};
|
use polkadot_primitives::parachain::{BlockData, Extrinsic, CandidateReceipt};
|
||||||
|
|
||||||
use futures::prelude::*;
|
use futures::prelude::*;
|
||||||
use tokio::runtime::TaskExecutor;
|
use tokio::runtime::TaskExecutor;
|
||||||
@@ -89,14 +89,16 @@ impl<P: PolkadotApi> Clone for Router<P> {
|
|||||||
impl<P: LocalPolkadotApi + Send + Sync + 'static> Router<P> {
|
impl<P: LocalPolkadotApi + Send + Sync + 'static> Router<P> {
|
||||||
/// Import a statement whose signature has been checked already.
|
/// Import a statement whose signature has been checked already.
|
||||||
pub(crate) fn import_statement(&self, statement: SignedStatement) {
|
pub(crate) fn import_statement(&self, statement: SignedStatement) {
|
||||||
|
trace!(target: "p_net", "importing consensus statement {:?}", statement.statement);
|
||||||
|
|
||||||
// defer any statements for which we haven't imported the candidate yet
|
// defer any statements for which we haven't imported the candidate yet
|
||||||
let (c_hash, parachain_index) = {
|
let c_hash = {
|
||||||
let candidate_data = match statement.statement {
|
let candidate_data = match statement.statement {
|
||||||
GenericStatement::Candidate(ref c) => Some((c.hash(), c.parachain_index)),
|
GenericStatement::Candidate(ref c) => Some(c.hash()),
|
||||||
GenericStatement::Valid(ref hash)
|
GenericStatement::Valid(ref hash)
|
||||||
| GenericStatement::Invalid(ref hash)
|
| GenericStatement::Invalid(ref hash)
|
||||||
| GenericStatement::Available(ref hash)
|
| GenericStatement::Available(ref hash)
|
||||||
=> self.table.with_candidate(hash, |c| c.map(|c| (*hash, c.parachain_index))),
|
=> self.table.with_candidate(hash, |c| c.map(|_| *hash)),
|
||||||
};
|
};
|
||||||
match candidate_data {
|
match candidate_data {
|
||||||
Some(x) => x,
|
Some(x) => x,
|
||||||
@@ -115,6 +117,7 @@ impl<P: LocalPolkadotApi + Send + Sync + 'static> Router<P> {
|
|||||||
};
|
};
|
||||||
|
|
||||||
// prepend the candidate statement.
|
// prepend the candidate statement.
|
||||||
|
debug!(target: "consensus", "Importing statements about candidate {:?}", c_hash);
|
||||||
statements.insert(0, statement);
|
statements.insert(0, statement);
|
||||||
let producers: Vec<_> = self.table.import_remote_statements(
|
let producers: Vec<_> = self.table.import_remote_statements(
|
||||||
self,
|
self,
|
||||||
@@ -122,17 +125,16 @@ impl<P: LocalPolkadotApi + Send + Sync + 'static> Router<P> {
|
|||||||
);
|
);
|
||||||
// dispatch future work as necessary.
|
// dispatch future work as necessary.
|
||||||
for (producer, statement) in producers.into_iter().zip(statements) {
|
for (producer, statement) in producers.into_iter().zip(statements) {
|
||||||
let producer = match producer {
|
|
||||||
Some(p) => p,
|
|
||||||
None => continue, // statement redundant
|
|
||||||
};
|
|
||||||
|
|
||||||
self.knowledge.lock().note_statement(statement.sender, &statement.statement);
|
self.knowledge.lock().note_statement(statement.sender, &statement.statement);
|
||||||
self.dispatch_work(c_hash, producer, parachain_index);
|
|
||||||
|
if let Some(producer) = producer {
|
||||||
|
trace!(target: "consensus", "driving statement work to completion");
|
||||||
|
self.dispatch_work(c_hash, producer);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn dispatch_work<D, E>(&self, candidate_hash: Hash, producer: StatementProducer<D, E>, parachain: ParaId) where
|
fn dispatch_work<D, E>(&self, candidate_hash: Hash, producer: StatementProducer<D, E>) where
|
||||||
D: Future<Item=BlockData,Error=()> + Send + 'static,
|
D: Future<Item=BlockData,Error=()> + Send + 'static,
|
||||||
E: Future<Item=Extrinsic,Error=()> + Send + 'static,
|
E: Future<Item=Extrinsic,Error=()> + Send + 'static,
|
||||||
{
|
{
|
||||||
@@ -160,13 +162,13 @@ impl<P: LocalPolkadotApi + Send + Sync + 'static> Router<P> {
|
|||||||
|
|
||||||
// propagate the statements
|
// propagate the statements
|
||||||
if let Some(validity) = produced.validity {
|
if let Some(validity) = produced.validity {
|
||||||
let signed = table.sign_and_import(validity.clone());
|
let signed = table.sign_and_import(validity.clone()).0;
|
||||||
route_statement(&*network, &*table, parachain, parent_hash, signed);
|
network.with_spec(|spec, ctx| spec.gossip_statement(ctx, parent_hash, signed));
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Some(availability) = produced.availability {
|
if let Some(availability) = produced.availability {
|
||||||
let signed = table.sign_and_import(availability);
|
let signed = table.sign_and_import(availability).0;
|
||||||
route_statement(&*network, &*table, parachain, parent_hash, signed);
|
network.with_spec(|spec, ctx| spec.gossip_statement(ctx, parent_hash, signed));
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -182,11 +184,15 @@ impl<P: LocalPolkadotApi + Send> TableRouter for Router<P> {
|
|||||||
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.
|
||||||
let hash = receipt.hash();
|
let hash = receipt.hash();
|
||||||
let para_id = receipt.parachain_index;
|
let (candidate, availability) = self.table.sign_and_import(GenericStatement::Candidate(receipt));
|
||||||
let signed = self.table.sign_and_import(GenericStatement::Candidate(receipt));
|
|
||||||
|
|
||||||
self.knowledge.lock().note_candidate(hash, Some(block_data), Some(extrinsic));
|
self.knowledge.lock().note_candidate(hash, Some(block_data), Some(extrinsic));
|
||||||
route_statement(&*self.network, &*self.table, para_id, self.parent_hash, signed);
|
self.network.with_spec(|spec, ctx| {
|
||||||
|
spec.gossip_statement(ctx, self.parent_hash, candidate);
|
||||||
|
if let Some(availability) = availability {
|
||||||
|
spec.gossip_statement(ctx, self.parent_hash, availability);
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
fn fetch_block_data(&self, candidate: &CandidateReceipt) -> BlockDataReceiver {
|
fn fetch_block_data(&self, candidate: &CandidateReceipt) -> BlockDataReceiver {
|
||||||
@@ -217,32 +223,6 @@ impl Future for BlockDataReceiver {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// get statement to relevant validators.
|
|
||||||
fn route_statement(network: &NetworkService, table: &SharedTable, para_id: ParaId, parent_hash: Hash, statement: SignedStatement) {
|
|
||||||
let broadcast = |i: &mut Iterator<Item=&SessionKey>| {
|
|
||||||
let local_key = table.session_key();
|
|
||||||
network.with_spec(|spec, ctx| {
|
|
||||||
for val in i.filter(|&x| x != &local_key) {
|
|
||||||
spec.send_statement(ctx, *val, parent_hash, statement.clone());
|
|
||||||
}
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
let g_info = table
|
|
||||||
.group_info()
|
|
||||||
.get(¶_id)
|
|
||||||
.expect("statements only produced about groups which exist");
|
|
||||||
|
|
||||||
match statement.statement {
|
|
||||||
GenericStatement::Candidate(_) =>
|
|
||||||
broadcast(&mut g_info.validity_guarantors.iter().chain(g_info.availability_guarantors.iter())),
|
|
||||||
GenericStatement::Valid(_) | GenericStatement::Invalid(_) =>
|
|
||||||
broadcast(&mut g_info.validity_guarantors.iter()),
|
|
||||||
GenericStatement::Available(_) =>
|
|
||||||
broadcast(&mut g_info.availability_guarantors.iter()),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// A unique trace for valid statements issued by a validator.
|
// A unique trace for valid statements issued by a validator.
|
||||||
#[derive(Hash, PartialEq, Eq, Clone, Debug)]
|
#[derive(Hash, PartialEq, Eq, Clone, Debug)]
|
||||||
enum StatementTrace {
|
enum StatementTrace {
|
||||||
|
|||||||
@@ -1,25 +0,0 @@
|
|||||||
[package]
|
|
||||||
name = "basic_add"
|
|
||||||
version = "0.1.0"
|
|
||||||
authors = ["Parity Technologies <admin@parity.io>"]
|
|
||||||
description = "Test parachain which adds to a number as its state transition"
|
|
||||||
|
|
||||||
[lib]
|
|
||||||
crate-type = ["cdylib"]
|
|
||||||
|
|
||||||
[dependencies]
|
|
||||||
polkadot-parachain = { path = "../../", default-features = false }
|
|
||||||
wee_alloc = "0.4.1"
|
|
||||||
tiny-keccak = "1.4"
|
|
||||||
pwasm-libc = "0.2"
|
|
||||||
|
|
||||||
[features]
|
|
||||||
default = ["std"]
|
|
||||||
std = ["polkadot-parachain/std"]
|
|
||||||
|
|
||||||
[profile.release]
|
|
||||||
panic = "abort"
|
|
||||||
lto = true
|
|
||||||
|
|
||||||
[workspace]
|
|
||||||
members = []
|
|
||||||
@@ -1,12 +0,0 @@
|
|||||||
#!/usr/bin/env bash
|
|
||||||
set -e
|
|
||||||
|
|
||||||
# Make LLD produce a binary that imports memory from the outside environment.
|
|
||||||
export RUSTFLAGS="-C link-arg=--import-memory"
|
|
||||||
|
|
||||||
cargo +nightly build --target=wasm32-unknown-unknown --release --no-default-features
|
|
||||||
|
|
||||||
for i in basic_add
|
|
||||||
do
|
|
||||||
wasm-gc target/wasm32-unknown-unknown/release/$i.wasm target/wasm32-unknown-unknown/release/$i.compact.wasm
|
|
||||||
done
|
|
||||||
@@ -1,142 +0,0 @@
|
|||||||
// Copyright 2017 Parity Technologies (UK) Ltd.
|
|
||||||
// This file is part of Polkadot.
|
|
||||||
|
|
||||||
// Polkadot is free software: you can redistribute it and/or modify
|
|
||||||
// it under the terms of the GNU General Public License as published by
|
|
||||||
// the Free Software Foundation, either version 3 of the License, or
|
|
||||||
// (at your option) any later version.
|
|
||||||
|
|
||||||
// Polkadot is distributed in the hope that it will be useful,
|
|
||||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
// GNU General Public License for more details.
|
|
||||||
|
|
||||||
// You should have received a copy of the GNU General Public License
|
|
||||||
// along with Polkadot. If not, see <http://www.gnu.org/licenses/>.
|
|
||||||
|
|
||||||
//! Basic parachain that adds a number as part of its state.
|
|
||||||
|
|
||||||
#![cfg_attr(not(feature = "std"), no_std)]
|
|
||||||
#![cfg_attr(
|
|
||||||
not(feature = "std"),
|
|
||||||
feature(
|
|
||||||
alloc, core_intrinsics, lang_items, panic_implementation, core_panic_info,
|
|
||||||
alloc_error_handler
|
|
||||||
)
|
|
||||||
)]
|
|
||||||
|
|
||||||
#[cfg(not(feature = "std"))]
|
|
||||||
extern crate alloc;
|
|
||||||
|
|
||||||
extern crate polkadot_parachain as parachain;
|
|
||||||
extern crate wee_alloc;
|
|
||||||
extern crate tiny_keccak;
|
|
||||||
extern crate pwasm_libc;
|
|
||||||
|
|
||||||
use parachain::codec::{Decode, Encode, Input, Output};
|
|
||||||
|
|
||||||
#[cfg(not(feature = "std"))]
|
|
||||||
mod wasm;
|
|
||||||
|
|
||||||
#[cfg(not(feature = "std"))]
|
|
||||||
pub use wasm::*;
|
|
||||||
|
|
||||||
// Define global allocator.
|
|
||||||
#[cfg(not(feature = "std"))]
|
|
||||||
#[global_allocator]
|
|
||||||
static ALLOC: wee_alloc::WeeAlloc = wee_alloc::WeeAlloc::INIT;
|
|
||||||
|
|
||||||
// Head data for this parachain.
|
|
||||||
#[derive(Default, Clone)]
|
|
||||||
struct HeadData {
|
|
||||||
// Block number
|
|
||||||
number: u64,
|
|
||||||
// parent block keccak256
|
|
||||||
parent_hash: [u8; 32],
|
|
||||||
// hash of post-execution state.
|
|
||||||
post_state: [u8; 32],
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Encode for HeadData {
|
|
||||||
fn encode_to<T: Output>(&self, dest: &mut T) {
|
|
||||||
dest.push(&self.number);
|
|
||||||
dest.push(&self.parent_hash);
|
|
||||||
dest.push(&self.post_state);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Decode for HeadData {
|
|
||||||
fn decode<I: Input>(input: &mut I) -> Option<Self> {
|
|
||||||
Some(HeadData {
|
|
||||||
number: Decode::decode(input)?,
|
|
||||||
parent_hash: Decode::decode(input)?,
|
|
||||||
post_state: Decode::decode(input)?,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Block data for this parachain.
|
|
||||||
#[derive(Default, Clone)]
|
|
||||||
struct BlockData {
|
|
||||||
// State to begin from.
|
|
||||||
state: u64,
|
|
||||||
// Amount to add (overflowing)
|
|
||||||
add: u64,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Encode for BlockData {
|
|
||||||
fn encode_to<T: Output>(&self, dest: &mut T) {
|
|
||||||
dest.push(&self.state);
|
|
||||||
dest.push(&self.add);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Decode for BlockData {
|
|
||||||
fn decode<I: Input>(input: &mut I) -> Option<Self> {
|
|
||||||
Some(BlockData {
|
|
||||||
state: Decode::decode(input)?,
|
|
||||||
add: Decode::decode(input)?,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(test)]
|
|
||||||
mod tests {
|
|
||||||
use super::*;
|
|
||||||
use parachain::ValidationParams;
|
|
||||||
|
|
||||||
const TEST_CODE: &[u8] = include_bytes!("../wasm/test.wasm");
|
|
||||||
|
|
||||||
fn hash_state(state: u64) -> [u8; 32] {
|
|
||||||
::tiny_keccak::keccak256(state.encode().as_slice())
|
|
||||||
}
|
|
||||||
|
|
||||||
fn hash_head(head: &HeadData) -> [u8; 32] {
|
|
||||||
::tiny_keccak::keccak256(head.encode().as_slice())
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn execute_good_on_parent() {
|
|
||||||
let parent_head = HeadData {
|
|
||||||
number: 0,
|
|
||||||
parent_hash: [0; 32],
|
|
||||||
post_state: hash_state(0),
|
|
||||||
};
|
|
||||||
|
|
||||||
let block_data = BlockData {
|
|
||||||
state: 0,
|
|
||||||
add: 512,
|
|
||||||
};
|
|
||||||
|
|
||||||
let ret = parachain::wasm::validate_candidate(TEST_CODE, ValidationParams {
|
|
||||||
parent_head: parent_head.encode(),
|
|
||||||
block_data: block_data.encode(),
|
|
||||||
}).unwrap();
|
|
||||||
|
|
||||||
let new_head = HeadData::decode(&mut &ret.head_data[..]).unwrap();
|
|
||||||
|
|
||||||
assert_eq!(new_head.number, 1);
|
|
||||||
assert_eq!(new_head.parent_hash, hash_head(&parent_head));
|
|
||||||
assert_eq!(new_head.post_state, hash_state(512));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -76,7 +76,7 @@ impl Decode for BlockData {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const TEST_CODE: &[u8] = include_bytes!("res/basic_add.wasm");
|
const TEST_CODE: &[u8] = include_bytes!("res/adder.wasm");
|
||||||
|
|
||||||
fn hash_state(state: u64) -> [u8; 32] {
|
fn hash_state(state: u64) -> [u8; 32] {
|
||||||
::tiny_keccak::keccak256(state.encode().as_slice())
|
::tiny_keccak::keccak256(state.encode().as_slice())
|
||||||
Binary file not shown.
Binary file not shown.
@@ -194,8 +194,8 @@ impl<T: Trait> Executable for Module<T> {
|
|||||||
#[serde(rename_all = "camelCase")]
|
#[serde(rename_all = "camelCase")]
|
||||||
#[serde(deny_unknown_fields)]
|
#[serde(deny_unknown_fields)]
|
||||||
pub struct GenesisConfig<T: Trait> {
|
pub struct GenesisConfig<T: Trait> {
|
||||||
/// The initial parachains, mapped to code.
|
/// The initial parachains, mapped to code and initial head data
|
||||||
pub parachains: Vec<(Id, Vec<u8>)>,
|
pub parachains: Vec<(Id, Vec<u8>, Vec<u8>)>,
|
||||||
/// Phantom data.
|
/// Phantom data.
|
||||||
#[serde(skip)]
|
#[serde(skip)]
|
||||||
pub phantom: PhantomData<T>,
|
pub phantom: PhantomData<T>,
|
||||||
@@ -218,18 +218,21 @@ impl<T: Trait> runtime_primitives::BuildStorage for GenesisConfig<T>
|
|||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
use codec::Encode;
|
use codec::Encode;
|
||||||
|
|
||||||
self.parachains.sort_unstable_by_key(|&(ref id, _)| id.clone());
|
self.parachains.sort_unstable_by_key(|&(ref id, _, _)| id.clone());
|
||||||
self.parachains.dedup_by_key(|&mut (ref id, _)| id.clone());
|
self.parachains.dedup_by_key(|&mut (ref id, _, _)| id.clone());
|
||||||
|
|
||||||
let only_ids: Vec<_> = self.parachains.iter().map(|&(ref id, _)| id).cloned().collect();
|
let only_ids: Vec<_> = self.parachains.iter().map(|&(ref id, _, _)| id).cloned().collect();
|
||||||
|
|
||||||
let mut map: HashMap<_, _> = map![
|
let mut map: HashMap<_, _> = map![
|
||||||
Self::hash(<Parachains<T>>::key()).to_vec() => only_ids.encode()
|
Self::hash(<Parachains<T>>::key()).to_vec() => only_ids.encode()
|
||||||
];
|
];
|
||||||
|
|
||||||
for (id, code) in self.parachains {
|
for (id, code, genesis) in self.parachains {
|
||||||
let key = Self::hash(&<Code<T>>::key_for(&id)).to_vec();
|
let code_key = Self::hash(&<Code<T>>::key_for(&id)).to_vec();
|
||||||
map.insert(key, code.encode());
|
let head_key = Self::hash(&<Heads<T>>::key_for(&id)).to_vec();
|
||||||
|
|
||||||
|
map.insert(code_key, code.encode());
|
||||||
|
map.insert(head_key, genesis.encode());
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(map.into())
|
Ok(map.into())
|
||||||
@@ -280,7 +283,7 @@ mod tests {
|
|||||||
|
|
||||||
type Parachains = Module<Test>;
|
type Parachains = Module<Test>;
|
||||||
|
|
||||||
fn new_test_ext(parachains: Vec<(Id, Vec<u8>)>) -> runtime_io::TestExternalities {
|
fn new_test_ext(parachains: Vec<(Id, Vec<u8>, Vec<u8>)>) -> runtime_io::TestExternalities {
|
||||||
let mut t = system::GenesisConfig::<Test>::default().build_storage().unwrap();
|
let mut t = system::GenesisConfig::<Test>::default().build_storage().unwrap();
|
||||||
t.extend(consensus::GenesisConfig::<Test>{
|
t.extend(consensus::GenesisConfig::<Test>{
|
||||||
code: vec![],
|
code: vec![],
|
||||||
@@ -301,8 +304,8 @@ mod tests {
|
|||||||
#[test]
|
#[test]
|
||||||
fn active_parachains_should_work() {
|
fn active_parachains_should_work() {
|
||||||
let parachains = vec![
|
let parachains = vec![
|
||||||
(5u32.into(), vec![1,2,3]),
|
(5u32.into(), vec![1,2,3], vec![1]),
|
||||||
(100u32.into(), vec![4,5,6]),
|
(100u32.into(), vec![4,5,6], vec![2]),
|
||||||
];
|
];
|
||||||
|
|
||||||
with_externalities(&mut new_test_ext(parachains), || {
|
with_externalities(&mut new_test_ext(parachains), || {
|
||||||
@@ -315,8 +318,8 @@ mod tests {
|
|||||||
#[test]
|
#[test]
|
||||||
fn register_deregister() {
|
fn register_deregister() {
|
||||||
let parachains = vec![
|
let parachains = vec![
|
||||||
(5u32.into(), vec![1,2,3]),
|
(5u32.into(), vec![1,2,3], vec![1]),
|
||||||
(100u32.into(), vec![4,5,6]),
|
(100u32.into(), vec![4,5,6], vec![2,]),
|
||||||
];
|
];
|
||||||
|
|
||||||
with_externalities(&mut new_test_ext(parachains), || {
|
with_externalities(&mut new_test_ext(parachains), || {
|
||||||
@@ -340,8 +343,8 @@ mod tests {
|
|||||||
#[test]
|
#[test]
|
||||||
fn duty_roster_works() {
|
fn duty_roster_works() {
|
||||||
let parachains = vec![
|
let parachains = vec![
|
||||||
(0u32.into(), vec![]),
|
(0u32.into(), vec![], vec![]),
|
||||||
(1u32.into(), vec![]),
|
(1u32.into(), vec![], vec![]),
|
||||||
];
|
];
|
||||||
|
|
||||||
with_externalities(&mut new_test_ext(parachains), || {
|
with_externalities(&mut new_test_ext(parachains), || {
|
||||||
|
|||||||
Generated
+4
-4
@@ -730,7 +730,7 @@ dependencies = [
|
|||||||
"substrate-runtime-std 0.1.0",
|
"substrate-runtime-std 0.1.0",
|
||||||
"twox-hash 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
"twox-hash 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
"uint 0.1.2 (git+https://github.com/rphmeier/primitives.git?branch=compile-for-wasm)",
|
"uint 0.1.2 (git+https://github.com/rphmeier/primitives.git?branch=compile-for-wasm)",
|
||||||
"wasmi 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
"wasmi 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -846,7 +846,7 @@ dependencies = [
|
|||||||
"substrate-primitives 0.1.0",
|
"substrate-primitives 0.1.0",
|
||||||
"substrate-runtime-io 0.1.0",
|
"substrate-runtime-io 0.1.0",
|
||||||
"substrate-runtime-std 0.1.0",
|
"substrate-runtime-std 0.1.0",
|
||||||
"wasmi 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
"wasmi 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -1107,7 +1107,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "wasmi"
|
name = "wasmi"
|
||||||
version = "0.3.0"
|
version = "0.4.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"byteorder 1.2.3 (registry+https://github.com/rust-lang/crates.io-index)",
|
"byteorder 1.2.3 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
@@ -1231,7 +1231,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||||||
"checksum untrusted 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)" = "f392d7819dbe58833e26872f5f6f0d68b7bbbe90fc3667e98731c4a15ad9a7ae"
|
"checksum untrusted 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)" = "f392d7819dbe58833e26872f5f6f0d68b7bbbe90fc3667e98731c4a15ad9a7ae"
|
||||||
"checksum utf8-ranges 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "662fab6525a98beff2921d7f61a39e7d59e0b425ebc7d0d9e66d316e55124122"
|
"checksum utf8-ranges 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "662fab6525a98beff2921d7f61a39e7d59e0b425ebc7d0d9e66d316e55124122"
|
||||||
"checksum void 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)" = "6a02e4885ed3bc0f2de90ea6dd45ebcbb66dacffe03547fadbb0eeae2770887d"
|
"checksum void 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)" = "6a02e4885ed3bc0f2de90ea6dd45ebcbb66dacffe03547fadbb0eeae2770887d"
|
||||||
"checksum wasmi 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)" = "9b4a6d379e9332b1b1f52c5a87f2481c85c7c931d8ec411963dfb8f26b1ec1e3"
|
"checksum wasmi 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "522fe3fdd44a56f25cd5ddcd8ccdb1cf2e982ceb28fcb00f41d8a018ae5245a8"
|
||||||
"checksum winapi 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)" = "04e3bd221fcbe8a271359c04f21a76db7d0c6028862d1bb5512d85e1e2eb5bb3"
|
"checksum winapi 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)" = "04e3bd221fcbe8a271359c04f21a76db7d0c6028862d1bb5512d85e1e2eb5bb3"
|
||||||
"checksum winapi-i686-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6"
|
"checksum winapi-i686-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6"
|
||||||
"checksum winapi-x86_64-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
|
"checksum winapi-x86_64-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
|
||||||
|
|||||||
BIN
Binary file not shown.
Binary file not shown.
@@ -212,7 +212,7 @@ pub fn new_full(config: Configuration, executor: TaskExecutor)
|
|||||||
consensus_net,
|
consensus_net,
|
||||||
service.extrinsic_pool(),
|
service.extrinsic_pool(),
|
||||||
executor,
|
executor,
|
||||||
::std::time::Duration::from_millis(4000), // TODO: dynamic
|
::std::time::Duration::from_secs(4), // TODO: dynamic
|
||||||
key,
|
key,
|
||||||
))
|
))
|
||||||
} else {
|
} else {
|
||||||
@@ -302,13 +302,13 @@ impl<B, E, A> network::TransactionPool<Block> for TransactionPoolAdapter<B, E, A
|
|||||||
Err(e) => match *e.kind() {
|
Err(e) => match *e.kind() {
|
||||||
transaction_pool::ErrorKind::AlreadyImported(hash) => Some(hash[..].into()),
|
transaction_pool::ErrorKind::AlreadyImported(hash) => Some(hash[..].into()),
|
||||||
_ => {
|
_ => {
|
||||||
debug!("Error adding transaction to the pool: {:?}", e);
|
debug!(target: "txpool", "Error adding transaction to the pool: {:?}", e);
|
||||||
None
|
None
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
debug!("Error decoding transaction");
|
debug!(target: "txpool", "Error decoding transaction");
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,5 @@
|
|||||||
|
# Test Parachains
|
||||||
|
|
||||||
|
Each parachain consists of three parts: a `#![no_std]` library with the main execution logic, a WASM crate which wraps this logic, and a collator node.
|
||||||
|
|
||||||
|
Run `build.sh` in this directory to build all registered test parachains and copy the generated WASM to the `parachain/tests/res` folder.
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
[package]
|
||||||
|
name = "adder"
|
||||||
|
version = "0.1.0"
|
||||||
|
authors = ["Parity Technologies <admin@parity.io>"]
|
||||||
|
description = "Test parachain which adds to a number as its state transition"
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
polkadot-parachain = { path = "../../parachain/", default-features = false }
|
||||||
|
tiny-keccak = "1.4"
|
||||||
@@ -0,0 +1,15 @@
|
|||||||
|
[package]
|
||||||
|
name = "adder-collator"
|
||||||
|
version = "0.1.0"
|
||||||
|
authors = ["Parity Technologies <admin@parity.io>"]
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
adder = { path = ".." }
|
||||||
|
polkadot-parachain = { path = "../../../parachain" }
|
||||||
|
polkadot-collator = { path = "../../../collator" }
|
||||||
|
polkadot-primitives = { path = "../../../primitives" }
|
||||||
|
ed25519 = { path = "../../../../substrate/ed25519" }
|
||||||
|
parking_lot = "0.4"
|
||||||
|
ctrlc = { git = "https://github.com/paritytech/rust-ctrlc.git" }
|
||||||
|
futures = "0.1"
|
||||||
|
exit-future = "0.1.2"
|
||||||
@@ -0,0 +1,145 @@
|
|||||||
|
// Copyright 2018 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/>.
|
||||||
|
|
||||||
|
//! Collator for polkadot
|
||||||
|
|
||||||
|
extern crate adder;
|
||||||
|
extern crate polkadot_parachain as parachain;
|
||||||
|
extern crate polkadot_primitives as primitives;
|
||||||
|
extern crate polkadot_collator as collator;
|
||||||
|
extern crate ed25519;
|
||||||
|
extern crate parking_lot;
|
||||||
|
extern crate ctrlc;
|
||||||
|
extern crate futures;
|
||||||
|
extern crate exit_future;
|
||||||
|
|
||||||
|
use std::cell::RefCell;
|
||||||
|
use std::collections::HashMap;
|
||||||
|
use std::sync::Arc;
|
||||||
|
|
||||||
|
use adder::{HeadData as AdderHead, BlockData as AdderBody};
|
||||||
|
use ed25519::Pair;
|
||||||
|
use parachain::codec::{Encode, Decode};
|
||||||
|
use primitives::parachain::{HeadData, BlockData, Id as ParaId, Message};
|
||||||
|
use collator::{InvalidHead, ParachainContext, VersionInfo};
|
||||||
|
use parking_lot::Mutex;
|
||||||
|
|
||||||
|
const GENESIS: AdderHead = AdderHead {
|
||||||
|
number: 0,
|
||||||
|
parent_hash: [0; 32],
|
||||||
|
post_state: [1, 27, 77, 3, 221, 140, 1, 241, 4, 145, 67, 207, 156, 76, 129, 126, 75, 22, 127, 29, 27, 131, 229, 198, 240, 241, 13, 137, 186, 30, 123, 206],
|
||||||
|
};
|
||||||
|
|
||||||
|
const GENESIS_BODY: AdderBody = AdderBody {
|
||||||
|
state: 0,
|
||||||
|
add: 0,
|
||||||
|
};
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
|
struct AdderContext {
|
||||||
|
db: Arc<Mutex<HashMap<AdderHead, AdderBody>>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The parachain context.
|
||||||
|
impl ParachainContext for AdderContext {
|
||||||
|
fn produce_candidate<I: IntoIterator<Item=(ParaId, Message)>>(
|
||||||
|
&self,
|
||||||
|
last_head: HeadData,
|
||||||
|
_ingress: I,
|
||||||
|
) -> Result<(BlockData, HeadData), InvalidHead>
|
||||||
|
{
|
||||||
|
let adder_head = AdderHead::decode(&mut &last_head.0[..])
|
||||||
|
.ok_or(InvalidHead)?;
|
||||||
|
|
||||||
|
let mut db = self.db.lock();
|
||||||
|
|
||||||
|
let last_body = if adder_head == GENESIS {
|
||||||
|
GENESIS_BODY
|
||||||
|
} else {
|
||||||
|
db.get(&adder_head)
|
||||||
|
.expect("All past bodies stored since this is the only collator")
|
||||||
|
.clone()
|
||||||
|
};
|
||||||
|
|
||||||
|
let next_body = AdderBody {
|
||||||
|
state: last_body.state.overflowing_add(last_body.add).0,
|
||||||
|
add: adder_head.number % 100,
|
||||||
|
};
|
||||||
|
|
||||||
|
let next_head = ::adder::execute(adder_head.hash(), adder_head, &next_body)
|
||||||
|
.expect("good execution params; qed");
|
||||||
|
|
||||||
|
let encoded_head = HeadData(next_head.encode());
|
||||||
|
let encoded_body = BlockData(next_body.encode());
|
||||||
|
|
||||||
|
println!("Created collation for #{}, post-state={}",
|
||||||
|
next_head.number, next_body.state.overflowing_add(next_body.add).0);
|
||||||
|
|
||||||
|
db.insert(next_head.clone(), next_body);
|
||||||
|
Ok((encoded_body, encoded_head))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
let key = Arc::new(Pair::from_seed(&[1; 32]));
|
||||||
|
let id: ParaId = 100.into();
|
||||||
|
|
||||||
|
println!("Starting adder collator with genesis: ");
|
||||||
|
|
||||||
|
{
|
||||||
|
let encoded = GENESIS.encode();
|
||||||
|
println!("Dec: {:?}", encoded);
|
||||||
|
print!("Hex: 0x");
|
||||||
|
for byte in encoded {
|
||||||
|
print!("{:02x}", byte);
|
||||||
|
}
|
||||||
|
|
||||||
|
println!();
|
||||||
|
}
|
||||||
|
|
||||||
|
// can't use signal directly here because CtrlC takes only `Fn`.
|
||||||
|
let (exit_send, exit) = exit_future::signal();
|
||||||
|
|
||||||
|
let exit_send_cell = RefCell::new(Some(exit_send));
|
||||||
|
ctrlc::CtrlC::set_handler(move || {
|
||||||
|
if let Some(exit_send) = exit_send_cell.try_borrow_mut().expect("signal handler not reentrant; qed").take() {
|
||||||
|
exit_send.fire();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
let context = AdderContext {
|
||||||
|
db: Arc::new(Mutex::new(HashMap::new())),
|
||||||
|
};
|
||||||
|
|
||||||
|
let res = ::collator::run_collator(
|
||||||
|
context,
|
||||||
|
id,
|
||||||
|
exit,
|
||||||
|
key,
|
||||||
|
::std::env::args(),
|
||||||
|
VersionInfo {
|
||||||
|
version: "<unknown>",
|
||||||
|
commit: "<unknown>",
|
||||||
|
executable_name: "adder-collator",
|
||||||
|
description: "collator for adder parachain",
|
||||||
|
author: "parity technologies",
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
if let Err(e) = res {
|
||||||
|
println!("{}", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,110 @@
|
|||||||
|
// Copyright 2017 Parity Technologies (UK) Ltd.
|
||||||
|
// This file is part of Polkadot.
|
||||||
|
|
||||||
|
// Polkadot is free software: you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU General Public License as published by
|
||||||
|
// the Free Software Foundation, either version 3 of the License, or
|
||||||
|
// (at your option) any later version.
|
||||||
|
|
||||||
|
// Polkadot is distributed in the hope that it will be useful,
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
// GNU General Public License for more details.
|
||||||
|
|
||||||
|
// You should have received a copy of the GNU General Public License
|
||||||
|
// along with Polkadot. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
//! Basic parachain that adds a number as part of its state.
|
||||||
|
|
||||||
|
#![no_std]
|
||||||
|
|
||||||
|
extern crate polkadot_parachain as parachain;
|
||||||
|
extern crate tiny_keccak;
|
||||||
|
|
||||||
|
use parachain::codec::{Decode, Encode, Input, Output};
|
||||||
|
|
||||||
|
/// Head data for this parachain.
|
||||||
|
#[derive(Default, Clone, Hash, Eq, PartialEq)]
|
||||||
|
pub struct HeadData {
|
||||||
|
/// Block number
|
||||||
|
pub number: u64,
|
||||||
|
/// parent block keccak256
|
||||||
|
pub parent_hash: [u8; 32],
|
||||||
|
/// hash of post-execution state.
|
||||||
|
pub post_state: [u8; 32],
|
||||||
|
}
|
||||||
|
|
||||||
|
impl HeadData {
|
||||||
|
pub fn hash(&self) -> [u8; 32] {
|
||||||
|
::tiny_keccak::keccak256(&self.encode())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Encode for HeadData {
|
||||||
|
fn encode_to<T: Output>(&self, dest: &mut T) {
|
||||||
|
dest.push(&self.number);
|
||||||
|
dest.push(&self.parent_hash);
|
||||||
|
dest.push(&self.post_state);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Decode for HeadData {
|
||||||
|
fn decode<I: Input>(input: &mut I) -> Option<Self> {
|
||||||
|
Some(HeadData {
|
||||||
|
number: Decode::decode(input)?,
|
||||||
|
parent_hash: Decode::decode(input)?,
|
||||||
|
post_state: Decode::decode(input)?,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Block data for this parachain.
|
||||||
|
#[derive(Default, Clone)]
|
||||||
|
pub struct BlockData {
|
||||||
|
/// State to begin from.
|
||||||
|
pub state: u64,
|
||||||
|
/// Amount to add (overflowing)
|
||||||
|
pub add: u64,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Encode for BlockData {
|
||||||
|
fn encode_to<T: Output>(&self, dest: &mut T) {
|
||||||
|
dest.push(&self.state);
|
||||||
|
dest.push(&self.add);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Decode for BlockData {
|
||||||
|
fn decode<I: Input>(input: &mut I) -> Option<Self> {
|
||||||
|
Some(BlockData {
|
||||||
|
state: Decode::decode(input)?,
|
||||||
|
add: Decode::decode(input)?,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn hash_state(state: u64) -> [u8; 32] {
|
||||||
|
::tiny_keccak::keccak256(state.encode().as_slice())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Start state mismatched with parent header's state hash.
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct StateMismatch;
|
||||||
|
|
||||||
|
/// Execute a block body on top of given parent head, producing new parent head
|
||||||
|
/// if valid.
|
||||||
|
pub fn execute(parent_hash: [u8; 32], parent_head: HeadData, block_data: &BlockData) -> Result<HeadData, StateMismatch> {
|
||||||
|
debug_assert_eq!(parent_hash, parent_head.hash());
|
||||||
|
|
||||||
|
if hash_state(block_data.state) != parent_head.post_state {
|
||||||
|
return Err(StateMismatch);
|
||||||
|
}
|
||||||
|
|
||||||
|
let new_state = block_data.state.overflowing_add(block_data.add).0;
|
||||||
|
|
||||||
|
Ok(HeadData {
|
||||||
|
number: parent_head.number + 1,
|
||||||
|
parent_hash,
|
||||||
|
post_state: hash_state(new_state),
|
||||||
|
})
|
||||||
|
}
|
||||||
@@ -0,0 +1,20 @@
|
|||||||
|
[package]
|
||||||
|
name = "adder-wasm"
|
||||||
|
version = "0.1.0"
|
||||||
|
authors = ["Parity Technologies <admin@parity.io>"]
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
adder = { path = ".." }
|
||||||
|
polkadot-parachain = { path = "../../../parachain", default-features = false }
|
||||||
|
wee_alloc = { version = "0.4.1" }
|
||||||
|
pwasm-libc = { version = "0.2" }
|
||||||
|
tiny-keccak = "1.4"
|
||||||
|
|
||||||
|
[lib]
|
||||||
|
crate-type = ["cdylib"]
|
||||||
|
|
||||||
|
[target.release]
|
||||||
|
panic = "abort"
|
||||||
|
lto = true
|
||||||
|
|
||||||
|
[workspace]
|
||||||
+28
-16
@@ -14,12 +14,30 @@
|
|||||||
// You should have received a copy of the GNU General Public License
|
// You should have received a copy of the GNU General Public License
|
||||||
// along with Polkadot. If not, see <http://www.gnu.org/licenses/>.
|
// along with Polkadot. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
//! Defines WASM module logic.
|
//! WASM validation for adder parachain.
|
||||||
|
|
||||||
use core::{intrinsics, panic, alloc};
|
#![no_std]
|
||||||
use parachain::{self, ValidationResult};
|
|
||||||
|
#![feature(
|
||||||
|
alloc, core_intrinsics, lang_items, panic_implementation, core_panic_info,
|
||||||
|
alloc_error_handler
|
||||||
|
)]
|
||||||
|
|
||||||
|
extern crate alloc;
|
||||||
|
extern crate wee_alloc;
|
||||||
|
extern crate pwasm_libc;
|
||||||
|
extern crate adder;
|
||||||
|
extern crate polkadot_parachain as parachain;
|
||||||
|
extern crate tiny_keccak;
|
||||||
|
|
||||||
|
// Define global allocator.
|
||||||
|
#[global_allocator]
|
||||||
|
static ALLOC: wee_alloc::WeeAlloc = wee_alloc::WeeAlloc::INIT;
|
||||||
|
|
||||||
|
use core::{intrinsics, panic};
|
||||||
|
use parachain::ValidationResult;
|
||||||
use parachain::codec::{Encode, Decode};
|
use parachain::codec::{Encode, Decode};
|
||||||
use super::{HeadData, BlockData};
|
use adder::{HeadData, BlockData};
|
||||||
|
|
||||||
#[panic_implementation]
|
#[panic_implementation]
|
||||||
#[no_mangle]
|
#[no_mangle]
|
||||||
@@ -31,7 +49,7 @@ pub fn panic(_info: &panic::PanicInfo) -> ! {
|
|||||||
|
|
||||||
#[alloc_error_handler]
|
#[alloc_error_handler]
|
||||||
#[no_mangle]
|
#[no_mangle]
|
||||||
pub fn oom(_: alloc::Layout) -> ! {
|
pub fn oom(_: ::core::alloc::Layout) -> ! {
|
||||||
unsafe {
|
unsafe {
|
||||||
intrinsics::abort();
|
intrinsics::abort();
|
||||||
}
|
}
|
||||||
@@ -39,8 +57,6 @@ pub fn oom(_: 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 hash_state = |state: u64| ::tiny_keccak::keccak256(state.encode().as_slice());
|
|
||||||
|
|
||||||
let params = unsafe { ::parachain::load_params(offset, len) };
|
let params = unsafe { ::parachain::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.");
|
||||||
@@ -48,14 +64,10 @@ pub extern fn validate(offset: usize, len: usize) -> usize {
|
|||||||
let block_data = BlockData::decode(&mut ¶ms.block_data[..])
|
let block_data = BlockData::decode(&mut ¶ms.block_data[..])
|
||||||
.expect("invalid block data format.");
|
.expect("invalid block data format.");
|
||||||
|
|
||||||
assert_eq!(hash_state(block_data.state), parent_head.post_state, "wrong post-state proof");
|
let parent_hash = ::tiny_keccak::keccak256(¶ms.parent_head[..]);
|
||||||
let new_state = block_data.state.saturating_add(block_data.add);
|
|
||||||
|
|
||||||
let new_head = HeadData {
|
match ::adder::execute(parent_hash, parent_head, &block_data) {
|
||||||
number: parent_head.number + 1,
|
Ok(new_head) => parachain::write_result(ValidationResult { head_data: new_head.encode() }),
|
||||||
parent_hash: ::tiny_keccak::keccak256(¶ms.parent_head[..]),
|
Err(_) => panic!("execution failure"),
|
||||||
post_state: hash_state(new_state),
|
}
|
||||||
};
|
|
||||||
|
|
||||||
parachain::write_result(ValidationResult { head_data: new_head.encode() })
|
|
||||||
}
|
}
|
||||||
Executable
+15
@@ -0,0 +1,15 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
set -e
|
||||||
|
|
||||||
|
# Make LLD produce a binary that imports memory from the outside environment.
|
||||||
|
export RUSTFLAGS="-C link-arg=--import-memory -C lto=fat -C panic=abort"
|
||||||
|
|
||||||
|
for i in adder
|
||||||
|
do
|
||||||
|
cd $i/wasm
|
||||||
|
cargo +nightly build --target=wasm32-unknown-unknown --release --no-default-features --target-dir target
|
||||||
|
wasm-gc target/wasm32-unknown-unknown/release/$i'_'wasm.wasm target/wasm32-unknown-unknown/release/$i.wasm
|
||||||
|
cp target/wasm32-unknown-unknown/release/$i.wasm ../../../parachain/tests/res/
|
||||||
|
rm -rf target
|
||||||
|
cd ../..
|
||||||
|
done
|
||||||
Reference in New Issue
Block a user