From 45c3e40a62f254af6486a94a13b5d3052d210e61 Mon Sep 17 00:00:00 2001 From: Robert Habermeier Date: Wed, 3 Jan 2018 15:50:05 +0100 Subject: [PATCH 1/2] Candidate Agreement + Consensus (#29) * candidate statement importing * import votes on validity * import availability votes * candidate receipt type * make table mod public * test context for table * add harness for tests * some tests for misbehavior * produce proposal from table * count candidate issuance as implicit vote * keep track of messages known by validators * fix primitives compilation * simple BFT agreement * kill unused macro_use annotation * tests for BFT agreement * test for not concluding on different prepares * return summary upon statement import * accept bft agreement on proposal not locally submitted * check justification set for BFT * BFT rewrite: vote accumulator with tests * squash some warnings * a few more tests for the accumulator * add sender to table's signed statement * implement honest node strategy for BFT * inex -> index * import and broadcast lock proofs * poll repeatedly when state changes * don't broadcast advance vote immediately if locked * do not check validity of locked candidate * basic tests for the strategy * remove unused context trait and fix warning * address some review grumbles * address some more review nits * fix lock import logic and add a test * fix spaces * fix a couple more style grumbles * more type-safe justifications * rename Communication enum variants * improve some panic guard proofs * add trailing comma --- substrate/Cargo.lock | 28 +- substrate/Cargo.toml | 1 + substrate/candidate-agreement/Cargo.toml | 8 + .../src/bft/accumulator.rs | 607 +++++++++++ substrate/candidate-agreement/src/bft/mod.rs | 714 +++++++++++++ .../candidate-agreement/src/bft/tests.rs | 412 ++++++++ substrate/candidate-agreement/src/lib.rs | 36 + substrate/candidate-agreement/src/table.rs | 999 ++++++++++++++++++ substrate/primitives/src/parachain.rs | 31 +- 9 files changed, 2820 insertions(+), 16 deletions(-) create mode 100644 substrate/candidate-agreement/Cargo.toml create mode 100644 substrate/candidate-agreement/src/bft/accumulator.rs create mode 100644 substrate/candidate-agreement/src/bft/mod.rs create mode 100644 substrate/candidate-agreement/src/bft/tests.rs create mode 100644 substrate/candidate-agreement/src/lib.rs create mode 100644 substrate/candidate-agreement/src/table.rs diff --git a/substrate/Cargo.lock b/substrate/Cargo.lock index 50708f94c0..a185bd50e0 100644 --- a/substrate/Cargo.lock +++ b/substrate/Cargo.lock @@ -1,13 +1,3 @@ -[root] -name = "polkadot-validator" -version = "0.1.0" -dependencies = [ - "error-chain 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)", - "polkadot-primitives 0.1.0", - "polkadot-serializer 0.1.0", - "serde 1.0.19 (registry+https://github.com/rust-lang/crates.io-index)", -] - [[package]] name = "aho-corasick" version = "0.6.3" @@ -612,6 +602,14 @@ dependencies = [ "polkadot-cli 0.1.0", ] +[[package]] +name = "polkadot-candidate-agreement" +version = "0.1.0" +dependencies = [ + "futures 0.1.17 (registry+https://github.com/rust-lang/crates.io-index)", + "polkadot-primitives 0.1.0", +] + [[package]] name = "polkadot-cli" version = "0.1.0" @@ -714,6 +712,16 @@ dependencies = [ "triehash 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "polkadot-validator" +version = "0.1.0" +dependencies = [ + "error-chain 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)", + "polkadot-primitives 0.1.0", + "polkadot-serializer 0.1.0", + "serde 1.0.19 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "pretty_assertions" version = "0.4.0" diff --git a/substrate/Cargo.toml b/substrate/Cargo.toml index 1bc165bd8a..1b8fb4c309 100644 --- a/substrate/Cargo.toml +++ b/substrate/Cargo.toml @@ -9,6 +9,7 @@ polkadot-cli = { path = "cli", version = "0.1" } [workspace] members = [ + "candidate-agreement", "client", "collator", "contracts", diff --git a/substrate/candidate-agreement/Cargo.toml b/substrate/candidate-agreement/Cargo.toml new file mode 100644 index 0000000000..9a2dc0ffb7 --- /dev/null +++ b/substrate/candidate-agreement/Cargo.toml @@ -0,0 +1,8 @@ +[package] +name = "polkadot-candidate-agreement" +version = "0.1.0" +authors = ["Parity Technologies "] + +[dependencies] +futures = "0.1" +polkadot-primitives = { path = "../primitives" } diff --git a/substrate/candidate-agreement/src/bft/accumulator.rs b/substrate/candidate-agreement/src/bft/accumulator.rs new file mode 100644 index 0000000000..8999a9f29b --- /dev/null +++ b/substrate/candidate-agreement/src/bft/accumulator.rs @@ -0,0 +1,607 @@ +// 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 . + +//! Message accumulator for each round of BFT consensus. + +use std::collections::{HashMap, HashSet}; +use std::collections::hash_map::Entry; +use std::hash::Hash; + +use super::{Message, LocalizedMessage}; + +/// Justification for some state at a given round. +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct UncheckedJustification { + /// The round. + pub round_number: usize, + /// The digest prepared for. + pub digest: D, + /// Signatures for the prepare messages. + pub signatures: Vec, +} + +impl UncheckedJustification { + /// Fails if there are duplicate signatures or invalid. + /// + /// Provide a closure for checking whether the signature is valid on a + /// digest. + /// + /// The closure should returns a checked justification iff the round number, digest, and signature + /// represent a valid message and the signer was authorized to issue + /// it. + /// + /// The `check_message` closure may vary based on context. + pub fn check(self, threshold: usize, mut check_message: F) + -> Result, Self> + where + F: FnMut(usize, &D, &S) -> Option, + V: Hash + Eq, + { + let checks_out = { + let mut checks_out = || { + let mut voted = HashSet::new(); + + for signature in &self.signatures { + match check_message(self.round_number, &self.digest, signature) { + None => return false, + Some(v) => { + if !voted.insert(v) { + return false; + } + } + } + } + + voted.len() >= threshold + }; + + checks_out() + }; + + if checks_out { + Ok(Justification(self)) + } else { + Err(self) + } + } +} + +/// A checked justification. +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct Justification(UncheckedJustification); + +impl ::std::ops::Deref for Justification { + type Target = UncheckedJustification; + + fn deref(&self) -> &Self::Target { + &self.0 + } +} + +/// Type alias to represent a justification specifically for a prepare. +pub type PrepareJustification = Justification; + +/// The round's state, based on imported messages. +#[derive(PartialEq, Eq, Debug)] +pub enum State { + /// No proposal yet. + Begin, + /// Proposal received. + Proposed(Candidate), + /// Seen n - f prepares for this digest. + Prepared(PrepareJustification), + /// Seen n - f commits for a digest. + Committed(Justification), + /// Seen n - f round-advancement messages. + Advanced(Option>), +} + +#[derive(Debug, Default)] +struct VoteCounts { + prepared: usize, + committed: usize, +} + +/// Accumulates messages for a given round of BFT consensus. +/// +/// This isn't tied to the "view" of a single validator. It +/// keeps accurate track of the state of the BFT consensus based +/// on all messages imported. +#[derive(Debug)] +pub struct Accumulator + where + Candidate: Eq + Clone, + Digest: Hash + Eq + Clone, + ValidatorId: Hash + Eq, + Signature: Eq + Clone, +{ + round_number: usize, + threshold: usize, + round_proposer: ValidatorId, + proposal: Option, + prepares: HashMap, + commits: HashMap, + vote_counts: HashMap, + advance_round: HashSet, + state: State, +} + +impl Accumulator + where + Candidate: Eq + Clone, + Digest: Hash + Eq + Clone, + ValidatorId: Hash + Eq, + Signature: Eq + Clone, +{ + /// Create a new state accumulator. + pub fn new(round_number: usize, threshold: usize, round_proposer: ValidatorId) -> Self { + Accumulator { + round_number, + threshold, + round_proposer, + proposal: None, + prepares: HashMap::new(), + commits: HashMap::new(), + vote_counts: HashMap::new(), + advance_round: HashSet::new(), + state: State::Begin, + } + } + + /// How advance votes we have seen. + pub fn advance_votes(&self) -> usize { + self.advance_round.len() + } + + /// Get the round number. + pub fn round_number(&self) -> usize { + self.round_number.clone() + } + + /// Get the round proposer. + pub fn round_proposer(&self) -> &ValidatorId { + &self.round_proposer + } + + pub fn proposal(&self) -> Option<&Candidate> { + self.proposal.as_ref() + } + + /// Inspect the current consensus state. + pub fn state(&self) -> &State { + &self.state + } + + /// Import a message. Importing duplicates is fine, but the signature + /// and authorization should have already been checked. + pub fn import_message( + &mut self, + message: LocalizedMessage, + ) + { + // message from different round. + if message.message.round_number() != self.round_number { + return; + } + + let (sender, signature) = (message.sender, message.signature); + + match message.message { + Message::Propose(_, p) => self.import_proposal(p, sender), + Message::Prepare(_, d) => self.import_prepare(d, sender, signature), + Message::Commit(_, d) => self.import_commit(d, sender, signature), + Message::AdvanceRound(_) => self.import_advance_round(sender), + } + } + + fn import_proposal( + &mut self, + proposal: Candidate, + sender: ValidatorId, + ) { + if sender != self.round_proposer || self.proposal.is_some() { return } + + self.proposal = Some(proposal.clone()); + self.state = State::Proposed(proposal); + } + + fn import_prepare( + &mut self, + digest: Digest, + sender: ValidatorId, + signature: Signature, + ) { + // ignore any subsequent prepares by the same sender. + // TODO: if digest is different, that's misbehavior. + let threshold_prepared = if let Entry::Vacant(vacant) = self.prepares.entry(sender) { + vacant.insert((digest.clone(), signature)); + let count = self.vote_counts.entry(digest.clone()).or_insert_with(Default::default); + count.prepared += 1; + + if count.prepared >= self.threshold { + Some(digest) + } else { + None + } + } else { + None + }; + + // only allow transition to prepare from begin or proposed state. + let valid_transition = match self.state { + State::Begin | State::Proposed(_) => true, + _ => false, + }; + + if let (true, Some(threshold_prepared)) = (valid_transition, threshold_prepared) { + let signatures = self.prepares + .values() + .filter(|&&(ref d, _)| d == &threshold_prepared) + .map(|&(_, ref s)| s.clone()) + .collect(); + + self.state = State::Prepared(Justification(UncheckedJustification { + round_number: self.round_number, + digest: threshold_prepared, + signatures: signatures, + })); + } + } + + fn import_commit( + &mut self, + digest: Digest, + sender: ValidatorId, + signature: Signature, + ) { + // ignore any subsequent commits by the same sender. + // TODO: if digest is different, that's misbehavior. + let threshold_committed = if let Entry::Vacant(vacant) = self.commits.entry(sender) { + vacant.insert((digest.clone(), signature)); + let count = self.vote_counts.entry(digest.clone()).or_insert_with(Default::default); + count.committed += 1; + + if count.committed >= self.threshold { + Some(digest) + } else { + None + } + } else { + None + }; + + // transition to concluded state always valid. + // only weird case is if the prior state was "advanced", + // but technically it's the same behavior as if the order of receiving + // the last "advance round" and "commit" messages were reversed. + if let Some(threshold_committed) = threshold_committed { + let signatures = self.commits + .values() + .filter(|&&(ref d, _)| d == &threshold_committed) + .map(|&(_, ref s)| s.clone()) + .collect(); + + self.state = State::Committed(Justification(UncheckedJustification { + round_number: self.round_number, + digest: threshold_committed, + signatures: signatures, + })); + } + } + + fn import_advance_round( + &mut self, + sender: ValidatorId, + ) { + self.advance_round.insert(sender); + + if self.advance_round.len() < self.threshold { return } + + // allow transition to new round only if we haven't produced a justification + // yet. + self.state = match ::std::mem::replace(&mut self.state, State::Begin) { + State::Committed(j) => State::Committed(j), + State::Prepared(j) => State::Advanced(Some(j)), + State::Advanced(j) => State::Advanced(j), + State::Begin | State::Proposed(_) => State::Advanced(None), + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[derive(Clone, PartialEq, Eq, Debug)] + pub struct Candidate(usize); + + #[derive(Hash, PartialEq, Eq, Clone, Debug)] + pub struct Digest(usize); + + #[derive(Hash, PartialEq, Eq, Debug)] + pub struct ValidatorId(usize); + + #[derive(PartialEq, Eq, Clone, Debug)] + pub struct Signature(usize, usize); + + #[test] + fn justification_checks_out() { + let mut justification = UncheckedJustification { + round_number: 2, + digest: Digest(600), + signatures: (0..10).map(|i| Signature(600, i)).collect(), + }; + + let check_message = |r, d: &Digest, s: &Signature| { + if r == 2 && d.0 == 600 && s.0 == 600 { + Some(ValidatorId(s.1)) + } else { + None + } + }; + + assert!(justification.clone().check(7, &check_message).is_ok()); + assert!(justification.clone().check(11, &check_message).is_err()); + + { + // one bad signature is enough to spoil it. + justification.signatures.push(Signature(1001, 255)); + assert!(justification.clone().check(7, &check_message).is_err()); + + justification.signatures.pop(); + } + // duplicates not allowed. + justification.signatures.extend((0..10).map(|i| Signature(600, i))); + assert!(justification.clone().check(11, &check_message).is_err()); + } + + #[test] + fn accepts_proposal_from_proposer_only() { + let mut accumulator = Accumulator::<_, Digest, _, _>::new(1, 7, ValidatorId(8)); + assert_eq!(accumulator.state(), &State::Begin); + + accumulator.import_message(LocalizedMessage { + sender: ValidatorId(5), + signature: Signature(999, 5), + message: Message::Propose(1, Candidate(999)), + }); + + assert_eq!(accumulator.state(), &State::Begin); + + accumulator.import_message(LocalizedMessage { + sender: ValidatorId(8), + signature: Signature(999, 8), + message: Message::Propose(1, Candidate(999)), + }); + + assert_eq!(accumulator.state(), &State::Proposed(Candidate(999))); + } + + #[test] + fn reaches_prepare_phase() { + let mut accumulator = Accumulator::new(1, 7, ValidatorId(8)); + assert_eq!(accumulator.state(), &State::Begin); + + accumulator.import_message(LocalizedMessage { + sender: ValidatorId(8), + signature: Signature(999, 8), + message: Message::Propose(1, Candidate(999)), + }); + + assert_eq!(accumulator.state(), &State::Proposed(Candidate(999))); + + for i in 0..6 { + accumulator.import_message(LocalizedMessage { + sender: ValidatorId(i), + signature: Signature(999, i), + message: Message::Prepare(1, Digest(999)), + }); + + assert_eq!(accumulator.state(), &State::Proposed(Candidate(999))); + } + + accumulator.import_message(LocalizedMessage { + sender: ValidatorId(7), + signature: Signature(999, 7), + message: Message::Prepare(1, Digest(999)), + }); + + match accumulator.state() { + &State::Prepared(ref j) => assert_eq!(j.digest, Digest(999)), + s => panic!("wrong state: {:?}", s), + } + } + + #[test] + fn prepare_to_commit() { + let mut accumulator = Accumulator::new(1, 7, ValidatorId(8)); + assert_eq!(accumulator.state(), &State::Begin); + + accumulator.import_message(LocalizedMessage { + sender: ValidatorId(8), + signature: Signature(999, 8), + message: Message::Propose(1, Candidate(999)), + }); + + assert_eq!(accumulator.state(), &State::Proposed(Candidate(999))); + + for i in 0..6 { + accumulator.import_message(LocalizedMessage { + sender: ValidatorId(i), + signature: Signature(999, i), + message: Message::Prepare(1, Digest(999)), + }); + + assert_eq!(accumulator.state(), &State::Proposed(Candidate(999))); + } + + accumulator.import_message(LocalizedMessage { + sender: ValidatorId(7), + signature: Signature(999, 7), + message: Message::Prepare(1, Digest(999)), + }); + + match accumulator.state() { + &State::Prepared(ref j) => assert_eq!(j.digest, Digest(999)), + s => panic!("wrong state: {:?}", s), + } + + for i in 0..6 { + accumulator.import_message(LocalizedMessage { + sender: ValidatorId(i), + signature: Signature(999, i), + message: Message::Commit(1, Digest(999)), + }); + + match accumulator.state() { + &State::Prepared(_) => {}, + s => panic!("wrong state: {:?}", s), + } + } + + accumulator.import_message(LocalizedMessage { + sender: ValidatorId(7), + signature: Signature(999, 7), + message: Message::Commit(1, Digest(999)), + }); + + match accumulator.state() { + &State::Committed(ref j) => assert_eq!(j.digest, Digest(999)), + s => panic!("wrong state: {:?}", s), + } + } + + #[test] + fn prepare_to_advance() { + let mut accumulator = Accumulator::new(1, 7, ValidatorId(8)); + assert_eq!(accumulator.state(), &State::Begin); + + accumulator.import_message(LocalizedMessage { + sender: ValidatorId(8), + signature: Signature(999, 8), + message: Message::Propose(1, Candidate(999)), + }); + + assert_eq!(accumulator.state(), &State::Proposed(Candidate(999))); + + for i in 0..7 { + accumulator.import_message(LocalizedMessage { + sender: ValidatorId(i), + signature: Signature(999, i), + message: Message::Prepare(1, Digest(999)), + }); + } + + match accumulator.state() { + &State::Prepared(ref j) => assert_eq!(j.digest, Digest(999)), + s => panic!("wrong state: {:?}", s), + } + + for i in 0..6 { + accumulator.import_message(LocalizedMessage { + sender: ValidatorId(i), + signature: Signature(999, i), + message: Message::AdvanceRound(1), + }); + + match accumulator.state() { + &State::Prepared(_) => {}, + s => panic!("wrong state: {:?}", s), + } + } + + accumulator.import_message(LocalizedMessage { + sender: ValidatorId(7), + signature: Signature(999, 7), + message: Message::AdvanceRound(1), + }); + + match accumulator.state() { + &State::Advanced(Some(_)) => {}, + s => panic!("wrong state: {:?}", s), + } + } + + #[test] + fn conclude_different_than_proposed() { + let mut accumulator = Accumulator::::new(1, 7, ValidatorId(8)); + assert_eq!(accumulator.state(), &State::Begin); + + for i in 0..7 { + accumulator.import_message(LocalizedMessage { + sender: ValidatorId(i), + signature: Signature(999, i), + message: Message::Prepare(1, Digest(999)), + }); + } + + match accumulator.state() { + &State::Prepared(ref j) => assert_eq!(j.digest, Digest(999)), + s => panic!("wrong state: {:?}", s), + } + + for i in 0..7 { + accumulator.import_message(LocalizedMessage { + sender: ValidatorId(i), + signature: Signature(999, i), + message: Message::Commit(1, Digest(999)), + }); + } + + match accumulator.state() { + &State::Committed(ref j) => assert_eq!(j.digest, Digest(999)), + s => panic!("wrong state: {:?}", s), + } + } + + #[test] + fn begin_to_advance() { + let mut accumulator = Accumulator::::new(1, 7, ValidatorId(8)); + assert_eq!(accumulator.state(), &State::Begin); + + for i in 0..7 { + accumulator.import_message(LocalizedMessage { + sender: ValidatorId(i), + signature: Signature(1, i), + message: Message::AdvanceRound(1), + }); + } + + match accumulator.state() { + &State::Advanced(ref j) => assert!(j.is_none()), + s => panic!("wrong state: {:?}", s), + } + } + + #[test] + fn conclude_without_prepare() { + let mut accumulator = Accumulator::::new(1, 7, ValidatorId(8)); + assert_eq!(accumulator.state(), &State::Begin); + + for i in 0..7 { + accumulator.import_message(LocalizedMessage { + sender: ValidatorId(i), + signature: Signature(999, i), + message: Message::Commit(1, Digest(999)), + }); + } + + match accumulator.state() { + &State::Committed(ref j) => assert_eq!(j.digest, Digest(999)), + s => panic!("wrong state: {:?}", s), + } + } +} diff --git a/substrate/candidate-agreement/src/bft/mod.rs b/substrate/candidate-agreement/src/bft/mod.rs new file mode 100644 index 0000000000..b17092c451 --- /dev/null +++ b/substrate/candidate-agreement/src/bft/mod.rs @@ -0,0 +1,714 @@ +// 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 . + +//! BFT Agreement based on a rotating proposer in different rounds. + +mod accumulator; + +#[cfg(test)] +mod tests; + +use std::collections::{HashMap, VecDeque}; +use std::fmt::Debug; +use std::hash::Hash; + +use futures::{future, Future, Stream, Sink, Poll, Async, AsyncSink}; + +use self::accumulator::State; + +pub use self::accumulator::{Accumulator, Justification, PrepareJustification, UncheckedJustification}; + +/// Messages over the proposal. +/// Each message carries an associated round number. +#[derive(Debug, Clone, PartialEq, Eq)] +pub enum Message { + /// Send a full proposal. + Propose(usize, C), + /// Prepare to vote for proposal with digest D. + Prepare(usize, D), + /// Commit to proposal with digest D.. + Commit(usize, D), + /// Propose advancement to a new round. + AdvanceRound(usize), +} + +impl Message { + fn round_number(&self) -> usize { + match *self { + Message::Propose(round, _) => round, + Message::Prepare(round, _) => round, + Message::Commit(round, _) => round, + Message::AdvanceRound(round) => round, + } + } +} + +/// A localized message, including the sender. +#[derive(Debug, Clone)] +pub struct LocalizedMessage { + /// The message received. + pub message: Message, + /// The sender of the message + pub sender: V, + /// The signature of the message. + pub signature: S, +} + +/// Context necessary for agreement. +/// +/// Provides necessary types for protocol messages, and functions necessary for a +/// participant to evaluate and create those messages. +pub trait Context { + /// Candidate proposed. + type Candidate: Debug + Eq + Clone; + /// Candidate digest. + type Digest: Debug + Hash + Eq + Clone; + /// Validator ID. + type ValidatorId: Debug + Hash + Eq + Clone; + /// Signature. + type Signature: Debug + Eq + Clone; + /// A future that resolves when a round timeout is concluded. + type RoundTimeout: Future; + /// A future that resolves when a proposal is ready. + type Proposal: Future; + + /// Get the local validator ID. + fn local_id(&self) -> Self::ValidatorId; + + /// Get the best proposal. + fn proposal(&self) -> Self::Proposal; + + /// Get the digest of a candidate. + fn candidate_digest(&self, candidate: &Self::Candidate) -> Self::Digest; + + /// Sign a message using the local validator ID. + fn sign_local(&self, message: Message) + -> LocalizedMessage; + + /// Get the proposer for a given round of consensus. + fn round_proposer(&self, round: usize) -> Self::ValidatorId; + + /// Whether the candidate is valid. + fn candidate_valid(&self, candidate: &Self::Candidate) -> bool; + + /// Create a round timeout. The context will determine the correct timeout + /// length, and create a future that will resolve when the timeout is + /// concluded. + fn begin_round_timeout(&self, round: usize) -> Self::RoundTimeout; +} + +/// Communication that can occur between participants in consensus. +#[derive(Debug, Clone)] +pub enum Communication { + /// A consensus message (proposal or vote) + Consensus(LocalizedMessage), + /// Auxiliary communication (just proof-of-lock for now). + Auxiliary(PrepareJustification), +} + +/// Type alias for a localized message using only type parameters from `Context`. +// TODO: actual type alias when it's no longer a warning. +pub struct ContextCommunication(pub Communication); + +impl Clone for ContextCommunication + where + LocalizedMessage: Clone, + PrepareJustification: Clone, +{ + fn clone(&self) -> Self { + ContextCommunication(self.0.clone()) + } +} + +#[derive(Debug)] +struct Sending { + items: VecDeque, + flushing: bool, +} + +impl Sending { + fn with_capacity(n: usize) -> Self { + Sending { + items: VecDeque::with_capacity(n), + flushing: false, + } + } + + fn push(&mut self, item: T) { + self.items.push_back(item); + self.flushing = false; + } + + // process all the sends into the sink. + fn process_all>(&mut self, sink: &mut S) -> Poll<(), S::SinkError> { + while let Some(item) = self.items.pop_front() { + match sink.start_send(item) { + Err(e) => return Err(e), + Ok(AsyncSink::NotReady(item)) => { + self.items.push_front(item); + return Ok(Async::NotReady); + } + Ok(AsyncSink::Ready) => { self.flushing = true; } + } + } + + if self.flushing { + match sink.poll_complete() { + Err(e) => return Err(e), + Ok(Async::NotReady) => return Ok(Async::NotReady), + Ok(Async::Ready(())) => { self.flushing = false; } + } + } + + Ok(Async::Ready(())) + } +} + +/// Error returned when the input stream concludes. +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub struct InputStreamConcluded; + +impl ::std::fmt::Display for InputStreamConcluded { + fn fmt(&self, f: &mut ::std::fmt::Formatter) -> ::std::fmt::Result { + write!(f, "{}", ::std::error::Error::description(self)) + } +} + +impl ::std::error::Error for InputStreamConcluded { + fn description(&self) -> &str { + "input stream of messages concluded prematurely" + } +} + +// get the "full BFT" threshold based on an amount of nodes and +// a maximum faulty. if nodes == 3f + 1, then threshold == 2f + 1. +fn bft_threshold(nodes: usize, max_faulty: usize) -> usize { + nodes - max_faulty +} + +/// Committed successfully. +#[derive(Debug, Clone)] +pub struct Committed { + /// The candidate committed for. This will be unknown if + /// we never witnessed the proposal of the last round. + pub candidate: Option, + /// A justification for the candidate. + pub justification: Justification, +} + +struct Locked { + justification: PrepareJustification, +} + +impl Locked { + fn digest(&self) -> &D { + &self.justification.digest + } +} + +// the state of the local node during the current state of consensus. +// +// behavior is different when locked on a proposal. +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +enum LocalState { + Start, + Proposed, + Prepared, + Committed, + VoteAdvance, +} + +// This structure manages a single "view" of consensus. +// +// We maintain two message accumulators: one for the round we are currently in, +// and one for a future round. +// +// We advance the round accumulators when one of two conditions is met: +// - we witness consensus of advancement in the current round. in this case we +// advance by one. +// - a higher threshold-prepare is broadcast to us. in this case we can +// advance to the round of the threshold-prepare. this is an indication +// that we have experienced severe asynchrony/clock drift with the remainder +// of the other validators, and it is unlikely that we can assist in +// consensus meaningfully. nevertheless we make an attempt. +struct Strategy { + nodes: usize, + max_faulty: usize, + fetching_proposal: Option, + round_timeout: future::Fuse, + local_state: LocalState, + locked: Option>, + notable_candidates: HashMap, + current_accumulator: Accumulator, + future_accumulator: Accumulator, + local_id: C::ValidatorId, +} + +impl Strategy { + fn create(context: &C, nodes: usize, max_faulty: usize) -> Self { + let timeout = context.begin_round_timeout(0); + let threshold = bft_threshold(nodes, max_faulty); + + let current_accumulator = Accumulator::new( + 0, + threshold, + context.round_proposer(0), + ); + + let future_accumulator = Accumulator::new( + 1, + threshold, + context.round_proposer(1), + ); + + Strategy { + nodes, + max_faulty, + current_accumulator, + future_accumulator, + fetching_proposal: None, + local_state: LocalState::Start, + locked: None, + notable_candidates: HashMap::new(), + round_timeout: timeout.fuse(), + local_id: context.local_id(), + } + } + + fn import_message( + &mut self, + msg: LocalizedMessage + ) { + let round_number = msg.message.round_number(); + + if round_number == self.current_accumulator.round_number() { + self.current_accumulator.import_message(msg); + } else if round_number == self.future_accumulator.round_number() { + self.future_accumulator.import_message(msg); + } + } + + fn import_lock_proof( + &mut self, + context: &C, + justification: PrepareJustification, + ) { + // TODO: find a way to avoid processing of the signatures if the sender is + // not the primary or the round number is low. + if justification.round_number > self.current_accumulator.round_number() { + // jump ahead to the prior round as this is an indication of a supermajority + // good nodes being at least on that round. + self.advance_to_round(context, justification.round_number); + } + + let lock_to_new = self.locked.as_ref() + .map_or(true, |l| l.justification.round_number < justification.round_number); + + if lock_to_new { + self.locked = Some(Locked { justification }) + } + } + + // poll the strategy: this will queue messages to be sent and advance + // rounds if necessary. + // + // only call within the context of a `Task`. + fn poll(&mut self, context: &C, sending: &mut Sending>) + -> Poll, E> + where + C::RoundTimeout: Future, + C::Proposal: Future, + { + let mut last_watermark = ( + self.current_accumulator.round_number(), + self.local_state + ); + + // poll until either completion or state doesn't change. + loop { + match self.poll_once(context, sending)? { + Async::Ready(x) => return Ok(Async::Ready(x)), + Async::NotReady => { + let new_watermark = ( + self.current_accumulator.round_number(), + self.local_state + ); + + if new_watermark == last_watermark { + return Ok(Async::NotReady) + } else { + last_watermark = new_watermark; + } + } + } + } + } + + // perform one round of polling: attempt to broadcast messages and change the state. + // if the round or internal round-state changes, this should be called again. + fn poll_once(&mut self, context: &C, sending: &mut Sending>) + -> Poll, E> + where + C::RoundTimeout: Future, + C::Proposal: Future, + { + self.propose(context, sending)?; + self.prepare(context, sending); + self.commit(context, sending); + self.vote_advance(context, sending)?; + + let advance = match self.current_accumulator.state() { + &State::Advanced(ref p_just) => { + // lock to any witnessed prepare justification. + if let Some(p_just) = p_just.as_ref() { + self.locked = Some(Locked { justification: p_just.clone() }); + } + + let round_number = self.current_accumulator.round_number(); + Some(round_number + 1) + } + &State::Committed(ref just) => { + // fetch the agreed-upon candidate: + // - we may not have received the proposal in the first place + // - there is no guarantee that the proposal we got was agreed upon + // (can happen if faulty primary) + // - look in the candidates of prior rounds just in case. + let candidate = self.current_accumulator + .proposal() + .and_then(|c| if context.candidate_digest(c) == just.digest { + Some(c.clone()) + } else { + None + }) + .or_else(|| self.notable_candidates.get(&just.digest).cloned()); + + let committed = Committed { + candidate, + justification: just.clone() + }; + + return Ok(Async::Ready(committed)) + } + _ => None, + }; + + if let Some(new_round) = advance { + self.advance_to_round(context, new_round); + } + + Ok(Async::NotReady) + } + + fn propose(&mut self, context: &C, sending: &mut Sending>) + -> Result<(), ::Error> + { + if let LocalState::Start = self.local_state { + let mut propose = false; + if let &State::Begin = self.current_accumulator.state() { + let round_number = self.current_accumulator.round_number(); + let primary = context.round_proposer(round_number); + propose = self.local_id == primary; + }; + + if !propose { return Ok(()) } + + // obtain the proposal to broadcast. + let proposal = match self.locked { + Some(ref locked) => { + // TODO: it's possible but very unlikely that we don't have the + // corresponding proposal for what we are locked to. + // + // since this is an edge case on an edge case, it is fine + // to eat the round timeout for now, but it can be optimized by + // broadcasting an advance vote. + self.notable_candidates.get(locked.digest()).cloned() + } + None => { + let res = self.fetching_proposal + .get_or_insert_with(|| context.proposal()) + .poll()?; + + match res { + Async::Ready(p) => Some(p), + Async::NotReady => None, + } + } + }; + + if let Some(proposal) = proposal { + self.fetching_proposal = None; + + let message = Message::Propose( + self.current_accumulator.round_number(), + proposal + ); + + self.import_and_send_message(message, context, sending); + + // broadcast the justification along with the proposal if we are locked. + if let Some(ref locked) = self.locked { + sending.push( + ContextCommunication(Communication::Auxiliary(locked.justification.clone())) + ); + } + + self.local_state = LocalState::Proposed; + } + } + + Ok(()) + } + + fn prepare(&mut self, context: &C, sending: &mut Sending>) { + // prepare only upon start or having proposed. + match self.local_state { + LocalState::Start | LocalState::Proposed => {}, + _ => return + }; + + let mut prepare_for = None; + + // we can't prepare until something was proposed. + if let &State::Proposed(ref candidate) = self.current_accumulator.state() { + let digest = context.candidate_digest(candidate); + + // vote to prepare only if we believe the candidate to be valid and + // we are not locked on some other candidate. + match self.locked { + Some(ref locked) if locked.digest() != &digest => {} + Some(_) => { + // don't check validity if we are locked. + // this is necessary to preserve the liveness property. + prepare_for = Some(digest); + } + None => if context.candidate_valid(candidate) { + prepare_for = Some(digest); + } + } + } + + if let Some(digest) = prepare_for { + let message = Message::Prepare( + self.current_accumulator.round_number(), + digest + ); + + self.import_and_send_message(message, context, sending); + self.local_state = LocalState::Prepared; + } + } + + fn commit(&mut self, context: &C, sending: &mut Sending>) { + // commit only if we haven't voted to advance or committed already + match self.local_state { + LocalState::Committed | LocalState::VoteAdvance => return, + _ => {} + } + + let mut commit_for = None; + + if let &State::Prepared(ref p_just) = self.current_accumulator.state() { + // we are now locked to this prepare justification. + let digest = p_just.digest.clone(); + self.locked = Some(Locked { justification: p_just.clone() }); + commit_for = Some(digest); + } + + if let Some(digest) = commit_for { + let message = Message::Commit( + self.current_accumulator.round_number(), + digest + ); + + self.import_and_send_message(message, context, sending); + self.local_state = LocalState::Committed; + } + } + + fn vote_advance(&mut self, context: &C, sending: &mut Sending>) + -> Result<(), ::Error> + { + // we can vote for advancement under all circumstances unless we have already. + if let LocalState::VoteAdvance = self.local_state { return Ok(()) } + + // if we got f + 1 advance votes, or the timeout has fired, and we haven't + // sent an AdvanceRound message yet, do so. + let mut attempt_advance = self.current_accumulator.advance_votes() > self.max_faulty; + + if let Async::Ready(_) = self.round_timeout.poll()? { + attempt_advance = true; + } + + if attempt_advance { + let message = Message::AdvanceRound( + self.current_accumulator.round_number(), + ); + + self.import_and_send_message(message, context, sending); + self.local_state = LocalState::VoteAdvance; + } + + Ok(()) + } + + fn advance_to_round(&mut self, context: &C, round: usize) { + assert!(round > self.current_accumulator.round_number()); + + let threshold = self.nodes - self.max_faulty; + + self.fetching_proposal = None; + self.round_timeout = context.begin_round_timeout(round).fuse(); + self.local_state = LocalState::Start; + + let new_future = Accumulator::new( + round + 1, + threshold, + context.round_proposer(round + 1), + ); + + // when advancing from a round, store away the witnessed proposal. + // + // if we or other participants end up locked on that candidate, + // we will have it. + if let Some(proposal) = self.current_accumulator.proposal() { + let digest = context.candidate_digest(proposal); + self.notable_candidates.entry(digest).or_insert_with(|| proposal.clone()); + } + + // special case when advancing by a single round. + if self.future_accumulator.round_number() == round { + self.current_accumulator + = ::std::mem::replace(&mut self.future_accumulator, new_future); + } else { + self.future_accumulator = new_future; + self.current_accumulator = Accumulator::new( + round, + threshold, + context.round_proposer(round), + ); + } + } + + fn import_and_send_message( + &mut self, + message: Message, + context: &C, + sending: &mut Sending> + ) { + let signed_message = context.sign_local(message); + self.import_message(signed_message.clone()); + sending.push(ContextCommunication(Communication::Consensus(signed_message))); + } +} + +/// Future that resolves upon BFT agreement for a candidate. +#[must_use = "futures do nothing unless polled"] +pub struct Agreement { + context: C, + input: I, + output: O, + concluded: Option>, + sending: Sending>, + strategy: Strategy, +} + +impl Future for Agreement + where + C: Context, + C::RoundTimeout: Future, + C::Proposal: Future, + I: Stream,Error=E>, + O: Sink,SinkError=E>, + E: From, +{ + type Item = Committed; + type Error = E; + + fn poll(&mut self) -> Poll { + // even if we've observed the conclusion, wait until all + // pending outgoing messages are flushed. + if let Some(just) = self.concluded.take() { + return Ok(match self.sending.process_all(&mut self.output)? { + Async::Ready(()) => Async::Ready(just), + Async::NotReady => { + self.concluded = Some(just); + Async::NotReady + } + }) + } + + loop { + let message = match self.input.poll()? { + Async::Ready(msg) => msg.ok_or(InputStreamConcluded)?, + Async::NotReady => break, + }; + + match message.0 { + Communication::Consensus(message) => self.strategy.import_message(message), + Communication::Auxiliary(lock_proof) + => self.strategy.import_lock_proof(&self.context, lock_proof), + } + } + + // try to process timeouts. + let state_machine_res = self.strategy.poll(&self.context, &mut self.sending)?; + + // make progress on flushing all pending messages. + let _ = self.sending.process_all(&mut self.output)?; + + match state_machine_res { + Async::Ready(just) => { + self.concluded = Some(just); + self.poll() + } + Async::NotReady => { + + Ok(Async::NotReady) + } + } + } +} + +/// Attempt to reach BFT agreement on a candidate. +/// +/// `nodes` is the number of nodes in the system. +/// `max_faulty` is the maximum number of faulty nodes. Should be less than +/// 1/3 of `nodes`, otherwise agreement may never be reached. +/// +/// The input stream should never logically conclude. The logic here assumes +/// that messages flushed to the output stream will eventually reach other nodes. +/// +/// Note that it is possible to witness agreement being reached without ever +/// seeing the candidate. Any candidates seen will be checked for validity. +/// +/// Although technically the agreement will always complete (given the eventual +/// delivery of messages), in practice it is possible for this future to +/// conclude without having witnessed the conclusion. +/// In general, this future should be pre-empted by the import of a justification +/// set for this block height. +pub fn agree(context: C, nodes: usize, max_faulty: usize, input: I, output: O) + -> Agreement +{ + let strategy = Strategy::create(&context, nodes, max_faulty); + Agreement { + context, + input, + output, + concluded: None, + sending: Sending::with_capacity(4), + strategy: strategy, + } +} diff --git a/substrate/candidate-agreement/src/bft/tests.rs b/substrate/candidate-agreement/src/bft/tests.rs new file mode 100644 index 0000000000..ff66ff0476 --- /dev/null +++ b/substrate/candidate-agreement/src/bft/tests.rs @@ -0,0 +1,412 @@ +// 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 . + +//! Tests for the candidate agreement strategy. + +use super::*; + +use std::sync::{Arc, Mutex}; +use std::time::Duration; + +use futures::prelude::*; +use futures::sync::{oneshot, mpsc}; +use futures::future::FutureResult; + +#[derive(Debug, PartialEq, Eq, Clone, Hash)] +struct Candidate(usize); + +#[derive(Debug, PartialEq, Eq, Clone, Hash)] +struct Digest(usize); + +#[derive(Debug, PartialEq, Eq, Clone, Hash)] +struct ValidatorId(usize); + +#[derive(Debug, PartialEq, Eq, Clone)] +struct Signature(Message, ValidatorId); + +struct SharedContext { + node_count: usize, + current_round: usize, + awaiting_round_timeouts: HashMap>>, +} + +#[derive(Debug)] +struct Error; + +impl From for Error { + fn from(_: InputStreamConcluded) -> Error { + Error + } +} + +impl SharedContext { + fn new(node_count: usize) -> Self { + SharedContext { + node_count, + current_round: 0, + awaiting_round_timeouts: HashMap::new() + } + } + + fn round_timeout(&mut self, round: usize) -> Box> { + let (tx, rx) = oneshot::channel(); + if round < self.current_round { + tx.send(()).unwrap(); + } else { + self.awaiting_round_timeouts + .entry(round) + .or_insert_with(Vec::new) + .push(tx); + } + + Box::new(rx.map_err(|_| Error)) + } + + fn bump_round(&mut self) { + let awaiting_timeout = self.awaiting_round_timeouts + .remove(&self.current_round) + .unwrap_or_else(Vec::new); + + for tx in awaiting_timeout { + let _ = tx.send(()); + } + + self.current_round += 1; + } + + fn round_proposer(&self, round: usize) -> ValidatorId { + ValidatorId(round % self.node_count) + } +} + +struct TestContext { + local_id: ValidatorId, + proposal: Mutex, + shared: Arc>, +} + +impl Context for TestContext { + type Candidate = Candidate; + type Digest = Digest; + type ValidatorId = ValidatorId; + type Signature = Signature; + type RoundTimeout = Box>; + type Proposal = FutureResult; + + fn local_id(&self) -> ValidatorId { + self.local_id.clone() + } + + fn proposal(&self) -> Self::Proposal { + let proposal = { + let mut p = self.proposal.lock().unwrap(); + let x = *p; + *p = (*p * 2) + 1; + x + }; + + Ok(Candidate(proposal)).into_future() + } + + fn candidate_digest(&self, candidate: &Candidate) -> Digest { + Digest(candidate.0) + } + + fn sign_local(&self, message: Message) + -> LocalizedMessage + { + let signature = Signature(message.clone(), self.local_id.clone()); + LocalizedMessage { + message, + signature, + sender: self.local_id.clone() + } + } + + fn round_proposer(&self, round: usize) -> ValidatorId { + self.shared.lock().unwrap().round_proposer(round) + } + + fn candidate_valid(&self, candidate: &Candidate) -> bool { + candidate.0 % 3 != 0 + } + + fn begin_round_timeout(&self, round: usize) -> Self::RoundTimeout { + self.shared.lock().unwrap().round_timeout(round) + } +} + +type Comm = ContextCommunication; + +struct Network { + endpoints: Vec>, + input: mpsc::UnboundedReceiver<(usize, Comm)>, +} + +impl Network { + fn new(nodes: usize) + -> (Network, Vec>, Vec>) + { + let mut inputs = Vec::with_capacity(nodes); + let mut outputs = Vec::with_capacity(nodes); + let mut endpoints = Vec::with_capacity(nodes); + + let (in_tx, in_rx) = mpsc::unbounded(); + for _ in 0..nodes { + let (out_tx, out_rx) = mpsc::unbounded(); + inputs.push(in_tx.clone()); + outputs.push(out_rx); + endpoints.push(out_tx); + } + + let network = Network { + endpoints, + input: in_rx, + }; + + (network, inputs, outputs) + } + + fn route_on_thread(self) { + ::std::thread::spawn(move || { let _ = self.wait(); }); + } +} + +impl Future for Network { + type Item = (); + type Error = Error; + + fn poll(&mut self) -> Poll<(), Error> { + match self.input.poll() { + Err(_) => Err(Error), + Ok(Async::NotReady) => Ok(Async::NotReady), + Ok(Async::Ready(None)) => Ok(Async::Ready(())), + Ok(Async::Ready(Some((sender, item)))) => { + { + let receiving_endpoints = self.endpoints + .iter() + .enumerate() + .filter(|&(i, _)| i != sender) + .map(|(_, x)| x); + + for endpoint in receiving_endpoints { + let _ = endpoint.unbounded_send(item.clone()); + } + } + + self.poll() + } + } + } +} + +fn timeout_in(t: Duration) -> oneshot::Receiver<()> { + let (tx, rx) = oneshot::channel(); + ::std::thread::spawn(move || { + ::std::thread::sleep(t); + let _ = tx.send(()); + }); + + rx +} + +#[test] +fn consensus_completes_with_minimum_good() { + let node_count = 10; + let max_faulty = 3; + + let shared_context = Arc::new(Mutex::new(SharedContext::new(node_count))); + + let (network, net_send, net_recv) = Network::new(node_count); + network.route_on_thread(); + + let nodes = net_send + .into_iter() + .zip(net_recv) + .take(node_count - max_faulty) + .enumerate() + .map(|(i, (tx, rx))| { + let ctx = TestContext { + local_id: ValidatorId(i), + proposal: Mutex::new(i), + shared: shared_context.clone(), + }; + + agree( + ctx, + node_count, + max_faulty, + rx.map_err(|_| Error), + tx.sink_map_err(|_| Error).with(move |t| Ok((i, t))), + ) + }) + .collect::>(); + + ::std::thread::spawn(move || { + let mut timeout = ::std::time::Duration::from_millis(50); + loop { + ::std::thread::sleep(timeout.clone()); + shared_context.lock().unwrap().bump_round(); + timeout *= 2; + } + }); + + let timeout = timeout_in(Duration::from_millis(500)).map_err(|_| Error); + let results = ::futures::future::join_all(nodes) + .map(Some) + .select(timeout.map(|_| None)) + .wait() + .map(|(i, _)| i) + .map_err(|(e, _)| e) + .expect("to complete") + .expect("to not time out"); + + for result in &results { + assert_eq!(&result.justification.digest, &results[0].justification.digest); + } +} + +#[test] +fn consensus_does_not_complete_without_enough_nodes() { + let node_count = 10; + let max_faulty = 3; + + let shared_context = Arc::new(Mutex::new(SharedContext::new(node_count))); + + let (network, net_send, net_recv) = Network::new(node_count); + network.route_on_thread(); + + let nodes = net_send + .into_iter() + .zip(net_recv) + .take(node_count - max_faulty - 1) + .enumerate() + .map(|(i, (tx, rx))| { + let ctx = TestContext { + local_id: ValidatorId(i), + proposal: Mutex::new(i), + shared: shared_context.clone(), + }; + + agree( + ctx, + node_count, + max_faulty, + rx.map_err(|_| Error), + tx.sink_map_err(|_| Error).with(move |t| Ok((i, t))), + ) + }) + .collect::>(); + + let timeout = timeout_in(Duration::from_millis(500)).map_err(|_| Error); + let result = ::futures::future::join_all(nodes) + .map(Some) + .select(timeout.map(|_| None)) + .wait() + .map(|(i, _)| i) + .map_err(|(e, _)| e) + .expect("to complete"); + + assert!(result.is_none(), "not enough online nodes"); +} + +#[test] +fn threshold_plus_one_locked_on_proposal_only_one_with_candidate() { + let node_count = 10; + let max_faulty = 3; + + let locked_proposal = Candidate(999_999_999); + let locked_digest = Digest(999_999_999); + let locked_round = 1; + let justification = UncheckedJustification { + round_number: locked_round, + digest: locked_digest.clone(), + signatures: (0..7) + .map(|i| Signature(Message::Prepare(locked_round, locked_digest.clone()), ValidatorId(i))) + .collect() + }.check(7, |_, _, s| Some(s.1.clone())).unwrap(); + + let mut shared_context = SharedContext::new(node_count); + shared_context.current_round = locked_round + 1; + let shared_context = Arc::new(Mutex::new(shared_context)); + + let (network, net_send, net_recv) = Network::new(node_count); + network.route_on_thread(); + + let nodes = net_send + .into_iter() + .zip(net_recv) + .enumerate() + .map(|(i, (tx, rx))| { + let ctx = TestContext { + local_id: ValidatorId(i), + proposal: Mutex::new(i), + shared: shared_context.clone(), + }; + + let mut agreement = agree( + ctx, + node_count, + max_faulty, + rx.map_err(|_| Error), + tx.sink_map_err(|_| Error).with(move |t| Ok((i, t))), + ); + + agreement.strategy.advance_to_round( + &agreement.context, + locked_round + 1 + ); + + if i <= max_faulty { + agreement.strategy.locked = Some(Locked { + justification: justification.clone(), + }) + } + + if i == max_faulty { + agreement.strategy.notable_candidates.insert( + locked_digest.clone(), + locked_proposal.clone(), + ); + } + + agreement + }) + .collect::>(); + + ::std::thread::spawn(move || { + let mut timeout = ::std::time::Duration::from_millis(50); + loop { + ::std::thread::sleep(timeout.clone()); + shared_context.lock().unwrap().bump_round(); + timeout *= 2; + } + }); + + let timeout = timeout_in(Duration::from_millis(500)).map_err(|_| Error); + let results = ::futures::future::join_all(nodes) + .map(Some) + .select(timeout.map(|_| None)) + .wait() + .map(|(i, _)| i) + .map_err(|(e, _)| e) + .expect("to complete") + .expect("to not time out"); + + for result in &results { + assert_eq!(&result.justification.digest, &locked_digest); + } +} diff --git a/substrate/candidate-agreement/src/lib.rs b/substrate/candidate-agreement/src/lib.rs new file mode 100644 index 0000000000..09dd56f5f0 --- /dev/null +++ b/substrate/candidate-agreement/src/lib.rs @@ -0,0 +1,36 @@ +// 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 . + +//! Propagation and agreement of candidates. +//! +//! Validators are split into groups by parachain, and each validator might come +//! up its own candidate for their parachain. Within groups, validators pass around +//! their candidates and produce statements of validity. +//! +//! Any candidate that receives majority approval by the validators in a group +//! may be subject to inclusion, unless any validators flag that candidate as invalid. +//! +//! Wrongly flagging as invalid should be strongly disincentivized, so that in the +//! equilibrium state it is not expected to happen. Likewise with the submission +//! of invalid blocks. +//! +//! Groups themselves may be compromised by malicious validators. + +extern crate futures; +extern crate polkadot_primitives as primitives; + +pub mod bft; +pub mod table; diff --git a/substrate/candidate-agreement/src/table.rs b/substrate/candidate-agreement/src/table.rs new file mode 100644 index 0000000000..381244e58b --- /dev/null +++ b/substrate/candidate-agreement/src/table.rs @@ -0,0 +1,999 @@ +// 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 . + +//! The statement table. +//! +//! This stores messages other validators issue about candidates. +//! +//! These messages are used to create a proposal submitted to a BFT consensus process. +//! +//! Proposals are formed of sets of candidates which have the requisite number of +//! validity and availability votes. +//! +//! Each parachain is associated with two sets of validators: those which can +//! propose and attest to validity of candidates, and those who can only attest +//! to availability. + +use std::collections::HashSet; +use std::collections::hash_map::{HashMap, Entry}; +use std::hash::Hash; +use std::fmt::Debug; + +/// Context for the statement table. +pub trait Context { + /// A validator ID + type ValidatorId: Hash + Eq + Clone + Debug; + /// The digest (hash or other unique attribute) of a candidate. + type Digest: Hash + Eq + Clone + Debug; + /// Candidate type. + type Candidate: Ord + Eq + Clone + Debug; + /// The group ID type + type GroupId: Hash + Ord + Eq + Clone + Debug; + /// A signature type. + type Signature: Eq + Clone + Debug; + + /// get the digest of a candidate. + fn candidate_digest(&self, candidate: &Self::Candidate) -> Self::Digest; + + /// get the group of a candidate. + fn candidate_group(&self, candidate: &Self::Candidate) -> Self::GroupId; + + /// Whether a validator is a member of a group. + /// Members are meant to submit candidates and vote on validity. + fn is_member_of(&self, validator: &Self::ValidatorId, group: &Self::GroupId) -> bool; + + /// Whether a validator is an availability guarantor of a group. + /// Guarantors are meant to vote on availability for candidates submitted + /// in a group. + fn is_availability_guarantor_of( + &self, + validator: &Self::ValidatorId, + group: &Self::GroupId, + ) -> bool; + + // requisite number of votes for validity and availability respectively from a group. + fn requisite_votes(&self, group: &Self::GroupId) -> (usize, usize); +} + +/// Statements circulated among peers. +#[derive(PartialEq, Eq, Debug)] +pub enum Statement { + /// Broadcast by a validator to indicate that this is his candidate for + /// inclusion. + /// + /// Broadcasting two different candidate messages per round is not allowed. + Candidate(C), + /// Broadcast by a validator to attest that the candidate with given digest + /// is valid. + Valid(D), + /// Broadcast by a validator to attest that the auxiliary data for a candidate + /// with given digest is available. + Available(D), + /// Broadcast by a validator to attest that the candidate with given digest + /// is invalid. + Invalid(D), +} + +/// A signed statement. +#[derive(PartialEq, Eq, Debug)] +pub struct SignedStatement { + /// The statement. + pub statement: Statement, + /// The signature. + pub signature: S, + /// The sender. + pub sender: V, +} + +// A unique trace for a class of valid statements issued by a validator. +// +// We keep track of which statements we have received or sent to other validators +// in order to prevent relaying the same data multiple times. +// +// The signature of the statement is replaced by the validator because the validator +// is unique while signatures are not (at least under common schemes like +// Schnorr or ECDSA). +#[derive(Hash, PartialEq, Eq, Clone)] +enum StatementTrace { + /// The candidate proposed by the validator. + Candidate(V), + /// A validity statement from that validator about the given digest. + Valid(V, D), + /// An invalidity statement from that validator about the given digest. + Invalid(V, D), + /// An availability statement from that validator about the given digest. + Available(V, D), +} + +/// Misbehavior: voting more than one way on candidate validity. +/// +/// Since there are three possible ways to vote, a double vote is possible in +/// three possible combinations (unordered) +#[derive(PartialEq, Eq, Debug)] +pub enum ValidityDoubleVote { + /// Implicit vote by issuing and explicity voting validity. + IssuedAndValidity((C, S), (D, S)), + /// Implicit vote by issuing and explicitly voting invalidity + IssuedAndInvalidity((C, S), (D, S)), + /// Direct votes for validity and invalidity + ValidityAndInvalidity(D, S, S), +} + +/// Misbehavior: declaring multiple candidates. +#[derive(PartialEq, Eq, Debug)] +pub struct MultipleCandidates { + /// The first candidate seen. + pub first: (C, S), + /// The second candidate seen. + pub second: (C, S), +} + +/// Misbehavior: submitted statement for wrong group. +#[derive(PartialEq, Eq, Debug)] +pub struct UnauthorizedStatement { + /// A signed statement which was submitted without proper authority. + pub statement: SignedStatement, +} + +/// Different kinds of misbehavior. All of these kinds of malicious misbehavior +/// are easily provable and extremely disincentivized. +#[derive(PartialEq, Eq, Debug)] +pub enum Misbehavior { + /// Voted invalid and valid on validity. + ValidityDoubleVote(ValidityDoubleVote), + /// Submitted multiple candidates. + MultipleCandidates(MultipleCandidates), + /// Submitted a message withou + UnauthorizedStatement(UnauthorizedStatement), +} + +/// Fancy work-around for a type alias of context-based misbehavior +/// without producing compiler warnings. +pub trait ResolveMisbehavior { + /// The misbehavior type. + type Misbehavior; +} + +impl ResolveMisbehavior for C { + type Misbehavior = Misbehavior; +} + +// kinds of votes for validity +#[derive(Clone, PartialEq, Eq)] +enum ValidityVote { + // implicit validity vote by issuing + Issued(S), + // direct validity vote + Valid(S), + // direct invalidity vote + Invalid(S), +} + +/// A summary of import of a statement. +#[derive(Clone, PartialEq, Eq)] +pub struct Summary { + /// The digest of the candidate referenced. + pub candidate: D, + /// The group that candidate is in. + pub group_id: G, + /// How many validity votes are currently witnessed. + pub validity_votes: usize, + /// How many availability votes are currently witnessed. + pub availability_votes: usize, + /// Whether this has been signalled bad by at least one participant. + pub signalled_bad: bool, +} + +/// Stores votes and data about a candidate. +pub struct CandidateData { + group_id: C::GroupId, + candidate: C::Candidate, + validity_votes: HashMap>, + availability_votes: HashMap, + indicated_bad_by: Vec, +} + +impl CandidateData { + /// whether this has been indicated bad by anyone. + pub fn indicated_bad(&self) -> bool { + !self.indicated_bad_by.is_empty() + } + + /// Get an iterator over those who have indicated this candidate valid. + // TODO: impl trait + pub fn voted_valid_by<'a>(&'a self) -> Box + 'a> { + Box::new(self.validity_votes.iter().filter_map(|(v, vote)| { + match *vote { + ValidityVote::Issued(_) | ValidityVote::Valid(_) => Some(v.clone()), + ValidityVote::Invalid(_) => None, + } + })) + } + + // Candidate data can be included in a proposal + // if it has enough validity and availability votes + // and no validators have called it bad. + fn can_be_included(&self, validity_threshold: usize, availability_threshold: usize) -> bool { + self.indicated_bad_by.is_empty() + && self.validity_votes.len() >= validity_threshold + && self.availability_votes.len() >= availability_threshold + } + + fn summary(&self, digest: C::Digest) -> Summary { + Summary { + candidate: digest, + group_id: self.group_id.clone(), + validity_votes: self.validity_votes.len() - self.indicated_bad_by.len(), + availability_votes: self.availability_votes.len(), + signalled_bad: self.indicated_bad(), + } + } +} + +// validator metadata +struct ValidatorData { + proposal: Option<(C::Digest, C::Signature)>, + known_statements: HashSet>, +} + +/// Create a new, empty statement table. +pub fn create() -> Table { + Table { + validator_data: HashMap::default(), + detected_misbehavior: HashMap::default(), + candidate_votes: HashMap::default(), + } +} + +/// Stores votes +#[derive(Default)] +pub struct Table { + validator_data: HashMap>, + detected_misbehavior: HashMap::Misbehavior>, + candidate_votes: HashMap>, +} + +impl Table { + /// Produce a set of proposed candidates. + /// + /// This will be at most one per group, consisting of the + /// best candidate for each group with requisite votes for inclusion. + pub fn proposed_candidates(&self, context: &C) -> Vec { + use std::collections::BTreeMap; + use std::collections::btree_map::Entry as BTreeEntry; + + let mut best_candidates = BTreeMap::new(); + for candidate_data in self.candidate_votes.values() { + let group_id = &candidate_data.group_id; + let (validity_t, availability_t) = context.requisite_votes(group_id); + + if !candidate_data.can_be_included(validity_t, availability_t) { continue } + let candidate = &candidate_data.candidate; + match best_candidates.entry(group_id.clone()) { + BTreeEntry::Occupied(mut occ) => { + let candidate_ref = occ.get_mut(); + if *candidate_ref < candidate { + *candidate_ref = candidate; + } + } + BTreeEntry::Vacant(vacant) => { vacant.insert(candidate); }, + } + } + + best_candidates.values().map(|v| C::Candidate::clone(v)).collect::>() + } + + /// Get an iterator of all candidates with a given group. + // TODO: impl iterator + pub fn candidates_in_group<'a>(&'a self, group_id: C::GroupId) + -> Box> + 'a> + { + Box::new(self.candidate_votes.values().filter(move |c| c.group_id == group_id)) + } + + /// Drain all misbehavior observed up to this point. + pub fn drain_misbehavior(&mut self) -> HashMap::Misbehavior> { + ::std::mem::replace(&mut self.detected_misbehavior, HashMap::new()) + } + + /// Import a signed statement. Signatures should be checked for validity, and the + /// sender should be checked to actually be a validator. + /// + /// This can note the origin of the statement to indicate that he has + /// seen it already. + pub fn import_statement( + &mut self, + context: &C, + statement: SignedStatement, + from: Option + ) -> Option> { + let SignedStatement { statement, signature, sender: signer } = statement; + + let trace = match statement { + Statement::Candidate(_) => StatementTrace::Candidate(signer.clone()), + Statement::Valid(ref d) => StatementTrace::Valid(signer.clone(), d.clone()), + Statement::Invalid(ref d) => StatementTrace::Invalid(signer.clone(), d.clone()), + Statement::Available(ref d) => StatementTrace::Available(signer.clone(), d.clone()), + }; + + let (maybe_misbehavior, maybe_summary) = match statement { + Statement::Candidate(candidate) => self.import_candidate( + context, + signer.clone(), + candidate, + signature + ), + Statement::Valid(digest) => self.validity_vote( + context, + signer.clone(), + digest, + ValidityVote::Valid(signature), + ), + Statement::Invalid(digest) => self.validity_vote( + context, + signer.clone(), + digest, + ValidityVote::Invalid(signature), + ), + Statement::Available(digest) => self.availability_vote( + context, + signer.clone(), + digest, + signature, + ), + }; + + if let Some(misbehavior) = maybe_misbehavior { + // all misbehavior in agreement is provable and actively malicious. + // punishments are not cumulative. + self.detected_misbehavior.insert(signer, misbehavior); + } else { + if let Some(from) = from { + self.note_trace_seen(trace.clone(), from); + } + + self.note_trace_seen(trace, signer); + } + + maybe_summary + } + + fn note_trace_seen(&mut self, trace: StatementTrace, known_by: C::ValidatorId) { + self.validator_data.entry(known_by).or_insert_with(|| ValidatorData { + proposal: None, + known_statements: HashSet::default(), + }).known_statements.insert(trace); + } + + fn import_candidate( + &mut self, + context: &C, + from: C::ValidatorId, + candidate: C::Candidate, + signature: C::Signature, + ) -> (Option<::Misbehavior>, Option>) { + let group = context.candidate_group(&candidate); + if !context.is_member_of(&from, &group) { + return ( + Some(Misbehavior::UnauthorizedStatement(UnauthorizedStatement { + statement: SignedStatement { + signature, + statement: Statement::Candidate(candidate), + sender: from, + }, + })), + None, + ); + } + + // check that validator hasn't already specified another candidate. + let digest = context.candidate_digest(&candidate); + + let new_proposal = match self.validator_data.entry(from.clone()) { + Entry::Occupied(mut occ) => { + // if digest is different, fetch candidate and + // note misbehavior. + let existing = occ.get_mut(); + + if let Some((ref old_digest, ref old_sig)) = existing.proposal { + if old_digest != &digest { + const EXISTENCE_PROOF: &str = + "when proposal first received from validator, candidate \ + votes entry is created. proposal here is `Some`, therefore \ + candidate votes entry exists; qed"; + + let old_candidate = self.candidate_votes.get(old_digest) + .expect(EXISTENCE_PROOF) + .candidate + .clone(); + + return ( + Some(Misbehavior::MultipleCandidates(MultipleCandidates { + first: (old_candidate, old_sig.clone()), + second: (candidate, signature.clone()), + })), + None, + ); + } + + false + } else { + existing.proposal = Some((digest.clone(), signature.clone())); + true + } + } + Entry::Vacant(vacant) => { + vacant.insert(ValidatorData { + proposal: Some((digest.clone(), signature.clone())), + known_statements: HashSet::new(), + }); + true + } + }; + + // NOTE: altering this code may affect the existence proof above. ensure it remains + // valid. + if new_proposal { + self.candidate_votes.entry(digest.clone()).or_insert_with(move || CandidateData { + group_id: group, + candidate: candidate, + validity_votes: HashMap::new(), + availability_votes: HashMap::new(), + indicated_bad_by: Vec::new(), + }); + } + + self.validity_vote( + context, + from, + digest, + ValidityVote::Issued(signature), + ) + } + + fn validity_vote( + &mut self, + context: &C, + from: C::ValidatorId, + digest: C::Digest, + vote: ValidityVote, + ) -> (Option<::Misbehavior>, Option>) { + let votes = match self.candidate_votes.get_mut(&digest) { + None => return (None, None), // TODO: queue up but don't get DoS'ed + Some(votes) => votes, + }; + + // check that this validator actually can vote in this group. + if !context.is_member_of(&from, &votes.group_id) { + let (sig, valid) = match vote { + ValidityVote::Valid(s) => (s, true), + ValidityVote::Invalid(s) => (s, false), + ValidityVote::Issued(_) => + panic!("implicit issuance vote only cast from `import_candidate` after \ + checking group membership of issuer; qed"), + }; + + return ( + Some(Misbehavior::UnauthorizedStatement(UnauthorizedStatement { + statement: SignedStatement { + signature: sig, + sender: from, + statement: if valid { + Statement::Valid(digest) + } else { + Statement::Invalid(digest) + } + } + })), + None, + ); + } + + // check for double votes. + match votes.validity_votes.entry(from.clone()) { + Entry::Occupied(occ) => { + if occ.get() != &vote { + let double_vote_proof = match (occ.get().clone(), vote) { + (ValidityVote::Issued(iss), ValidityVote::Valid(good)) | + (ValidityVote::Valid(good), ValidityVote::Issued(iss)) => + ValidityDoubleVote::IssuedAndValidity((votes.candidate.clone(), iss), (digest, good)), + (ValidityVote::Issued(iss), ValidityVote::Invalid(bad)) | + (ValidityVote::Invalid(bad), ValidityVote::Issued(iss)) => + ValidityDoubleVote::IssuedAndInvalidity((votes.candidate.clone(), iss), (digest, bad)), + (ValidityVote::Valid(good), ValidityVote::Invalid(bad)) | + (ValidityVote::Invalid(bad), ValidityVote::Valid(good)) => + ValidityDoubleVote::ValidityAndInvalidity(digest, good, bad), + _ => { + // this would occur if two different but valid signatures + // on the same kind of vote occurred. + return (None, None); + } + }; + + return ( + Some(Misbehavior::ValidityDoubleVote(double_vote_proof)), + None, + ) + } + + return (None, None); + } + Entry::Vacant(vacant) => { + if let ValidityVote::Invalid(_) = vote { + votes.indicated_bad_by.push(from); + } + + vacant.insert(vote); + } + } + + (None, Some(votes.summary(digest))) + } + + fn availability_vote( + &mut self, + context: &C, + from: C::ValidatorId, + digest: C::Digest, + signature: C::Signature, + ) -> (Option<::Misbehavior>, Option>) { + let votes = match self.candidate_votes.get_mut(&digest) { + None => return (None, None), // TODO: queue up but don't get DoS'ed + Some(votes) => votes, + }; + + // check that this validator actually can vote in this group. + if !context.is_availability_guarantor_of(&from, &votes.group_id) { + return ( + Some(Misbehavior::UnauthorizedStatement(UnauthorizedStatement { + statement: SignedStatement { + signature: signature.clone(), + statement: Statement::Available(digest), + sender: from, + } + })), + None + ); + } + + votes.availability_votes.insert(from, signature); + (None, Some(votes.summary(digest))) + } +} + +#[cfg(test)] +mod tests { + use super::*; + use std::collections::HashMap; + + #[derive(Debug, Copy, Clone, Hash, PartialEq, Eq)] + struct ValidatorId(usize); + + #[derive(Debug, Copy, Clone, Hash, PartialOrd, Ord, PartialEq, Eq)] + struct GroupId(usize); + + // group, body + #[derive(Debug, Copy, Clone, Hash, PartialOrd, Ord, PartialEq, Eq)] + struct Candidate(usize, usize); + + #[derive(Debug, Copy, Clone, Hash, PartialEq, Eq)] + struct Signature(usize); + + #[derive(Debug, Copy, Clone, Hash, PartialEq, Eq)] + struct Digest(usize); + + #[derive(Debug, PartialEq, Eq)] + struct TestContext { + // v -> (validity, availability) + validators: HashMap + } + + impl Context for TestContext { + type ValidatorId = ValidatorId; + type Digest = Digest; + type Candidate = Candidate; + type GroupId = GroupId; + type Signature = Signature; + + fn candidate_digest(&self, candidate: &Candidate) -> Digest { + Digest(candidate.1) + } + + fn candidate_group(&self, candidate: &Candidate) -> GroupId { + GroupId(candidate.0) + } + + fn is_member_of( + &self, + validator: &ValidatorId, + group: &GroupId + ) -> bool { + self.validators.get(validator).map(|v| &v.0 == group).unwrap_or(false) + } + + fn is_availability_guarantor_of( + &self, + validator: &ValidatorId, + group: &GroupId + ) -> bool { + self.validators.get(validator).map(|v| &v.1 == group).unwrap_or(false) + } + + fn requisite_votes(&self, _id: &GroupId) -> (usize, usize) { + (6, 34) + } + } + + #[test] + fn submitting_two_candidates_is_misbehavior() { + let context = TestContext { + validators: { + let mut map = HashMap::new(); + map.insert(ValidatorId(1), (GroupId(2), GroupId(455))); + map + } + }; + + let mut table = create(); + let statement_a = SignedStatement { + statement: Statement::Candidate(Candidate(2, 100)), + signature: Signature(1), + sender: ValidatorId(1), + }; + + let statement_b = SignedStatement { + statement: Statement::Candidate(Candidate(2, 999)), + signature: Signature(1), + sender: ValidatorId(1), + }; + + table.import_statement(&context, statement_a, None); + assert!(!table.detected_misbehavior.contains_key(&ValidatorId(1))); + + table.import_statement(&context, statement_b, None); + assert_eq!( + table.detected_misbehavior.get(&ValidatorId(1)).unwrap(), + &Misbehavior::MultipleCandidates(MultipleCandidates { + first: (Candidate(2, 100), Signature(1)), + second: (Candidate(2, 999), Signature(1)), + }) + ); + } + + #[test] + fn submitting_candidate_from_wrong_group_is_misbehavior() { + let context = TestContext { + validators: { + let mut map = HashMap::new(); + map.insert(ValidatorId(1), (GroupId(3), GroupId(455))); + map + } + }; + + let mut table = create(); + let statement = SignedStatement { + statement: Statement::Candidate(Candidate(2, 100)), + signature: Signature(1), + sender: ValidatorId(1), + }; + + table.import_statement(&context, statement, None); + + assert_eq!( + table.detected_misbehavior.get(&ValidatorId(1)).unwrap(), + &Misbehavior::UnauthorizedStatement(UnauthorizedStatement { + statement: SignedStatement { + statement: Statement::Candidate(Candidate(2, 100)), + signature: Signature(1), + sender: ValidatorId(1), + }, + }) + ); + } + + #[test] + fn unauthorized_votes() { + let context = TestContext { + validators: { + let mut map = HashMap::new(); + map.insert(ValidatorId(1), (GroupId(2), GroupId(455))); + map.insert(ValidatorId(2), (GroupId(3), GroupId(222))); + map + } + }; + + let mut table = create(); + + let candidate_a = SignedStatement { + statement: Statement::Candidate(Candidate(2, 100)), + signature: Signature(1), + sender: ValidatorId(1), + }; + let candidate_a_digest = Digest(100); + + let candidate_b = SignedStatement { + statement: Statement::Candidate(Candidate(3, 987)), + signature: Signature(2), + sender: ValidatorId(2), + }; + let candidate_b_digest = Digest(987); + + table.import_statement(&context, candidate_a, None); + table.import_statement(&context, candidate_b, None); + assert!(!table.detected_misbehavior.contains_key(&ValidatorId(1))); + assert!(!table.detected_misbehavior.contains_key(&ValidatorId(2))); + + // validator 1 votes for availability on 2's candidate. + let bad_availability_vote = SignedStatement { + statement: Statement::Available(candidate_b_digest.clone()), + signature: Signature(1), + sender: ValidatorId(1), + }; + table.import_statement(&context, bad_availability_vote, None); + + assert_eq!( + table.detected_misbehavior.get(&ValidatorId(1)).unwrap(), + &Misbehavior::UnauthorizedStatement(UnauthorizedStatement { + statement: SignedStatement { + statement: Statement::Available(candidate_b_digest), + signature: Signature(1), + sender: ValidatorId(1), + }, + }) + ); + + // validator 2 votes for validity on 1's candidate. + let bad_validity_vote = SignedStatement { + statement: Statement::Valid(candidate_a_digest.clone()), + signature: Signature(2), + sender: ValidatorId(2), + }; + table.import_statement(&context, bad_validity_vote, None); + + assert_eq!( + table.detected_misbehavior.get(&ValidatorId(2)).unwrap(), + &Misbehavior::UnauthorizedStatement(UnauthorizedStatement { + statement: SignedStatement { + statement: Statement::Valid(candidate_a_digest), + signature: Signature(2), + sender: ValidatorId(2), + }, + }) + ); + } + + #[test] + fn validity_double_vote_is_misbehavior() { + let context = TestContext { + validators: { + let mut map = HashMap::new(); + map.insert(ValidatorId(1), (GroupId(2), GroupId(455))); + map.insert(ValidatorId(2), (GroupId(2), GroupId(246))); + map + } + }; + + let mut table = create(); + let statement = SignedStatement { + statement: Statement::Candidate(Candidate(2, 100)), + signature: Signature(1), + sender: ValidatorId(1), + }; + let candidate_digest = Digest(100); + + table.import_statement(&context, statement, None); + assert!(!table.detected_misbehavior.contains_key(&ValidatorId(1))); + + let valid_statement = SignedStatement { + statement: Statement::Valid(candidate_digest.clone()), + signature: Signature(2), + sender: ValidatorId(2), + }; + + let invalid_statement = SignedStatement { + statement: Statement::Invalid(candidate_digest.clone()), + signature: Signature(2), + sender: ValidatorId(2), + }; + + table.import_statement(&context, valid_statement, None); + assert!(!table.detected_misbehavior.contains_key(&ValidatorId(2))); + + table.import_statement(&context, invalid_statement, None); + + assert_eq!( + table.detected_misbehavior.get(&ValidatorId(2)).unwrap(), + &Misbehavior::ValidityDoubleVote(ValidityDoubleVote::ValidityAndInvalidity( + candidate_digest, + Signature(2), + Signature(2), + )) + ); + } + + #[test] + fn issue_and_vote_is_misbehavior() { + let context = TestContext { + validators: { + let mut map = HashMap::new(); + map.insert(ValidatorId(1), (GroupId(2), GroupId(455))); + map + } + }; + + let mut table = create(); + let statement = SignedStatement { + statement: Statement::Candidate(Candidate(2, 100)), + signature: Signature(1), + sender: ValidatorId(1), + }; + let candidate_digest = Digest(100); + + table.import_statement(&context, statement, None); + assert!(!table.detected_misbehavior.contains_key(&ValidatorId(1))); + + let extra_vote = SignedStatement { + statement: Statement::Valid(candidate_digest.clone()), + signature: Signature(1), + sender: ValidatorId(1), + }; + + table.import_statement(&context, extra_vote, None); + assert_eq!( + table.detected_misbehavior.get(&ValidatorId(1)).unwrap(), + &Misbehavior::ValidityDoubleVote(ValidityDoubleVote::IssuedAndValidity( + (Candidate(2, 100), Signature(1)), + (Digest(100), Signature(1)), + )) + ); + } + + #[test] + fn candidate_can_be_included() { + let validity_threshold = 6; + let availability_threshold = 34; + + let mut candidate = CandidateData:: { + group_id: GroupId(4), + candidate: Candidate(4, 12345), + validity_votes: HashMap::new(), + availability_votes: HashMap::new(), + indicated_bad_by: Vec::new(), + }; + + assert!(!candidate.can_be_included(validity_threshold, availability_threshold)); + + for i in 0..validity_threshold { + candidate.validity_votes.insert(ValidatorId(i + 100), ValidityVote::Valid(Signature(i + 100))); + } + + assert!(!candidate.can_be_included(validity_threshold, availability_threshold)); + + for i in 0..availability_threshold { + candidate.availability_votes.insert(ValidatorId(i + 255), Signature(i + 255)); + } + + assert!(candidate.can_be_included(validity_threshold, availability_threshold)); + + candidate.indicated_bad_by.push(ValidatorId(1024)); + + assert!(!candidate.can_be_included(validity_threshold, availability_threshold)); + } + + #[test] + fn candidate_import_gives_summary() { + let context = TestContext { + validators: { + let mut map = HashMap::new(); + map.insert(ValidatorId(1), (GroupId(2), GroupId(455))); + map + } + }; + + let mut table = create(); + let statement = SignedStatement { + statement: Statement::Candidate(Candidate(2, 100)), + signature: Signature(1), + sender: ValidatorId(1), + }; + + let summary = table.import_statement(&context, statement, None) + .expect("candidate import to give summary"); + + assert_eq!(summary.candidate, Digest(100)); + assert_eq!(summary.group_id, GroupId(2)); + assert_eq!(summary.validity_votes, 1); + assert_eq!(summary.availability_votes, 0); + } + + #[test] + fn candidate_vote_gives_summary() { + let context = TestContext { + validators: { + let mut map = HashMap::new(); + map.insert(ValidatorId(1), (GroupId(2), GroupId(455))); + map.insert(ValidatorId(2), (GroupId(2), GroupId(455))); + map + } + }; + + let mut table = create(); + let statement = SignedStatement { + statement: Statement::Candidate(Candidate(2, 100)), + signature: Signature(1), + sender: ValidatorId(1), + }; + let candidate_digest = Digest(100); + + table.import_statement(&context, statement, None); + assert!(!table.detected_misbehavior.contains_key(&ValidatorId(1))); + + let vote = SignedStatement { + statement: Statement::Valid(candidate_digest.clone()), + signature: Signature(2), + sender: ValidatorId(2), + }; + + let summary = table.import_statement(&context, vote, None) + .expect("candidate vote to give summary"); + + assert!(!table.detected_misbehavior.contains_key(&ValidatorId(2))); + + assert_eq!(summary.candidate, Digest(100)); + assert_eq!(summary.group_id, GroupId(2)); + assert_eq!(summary.validity_votes, 2); + assert_eq!(summary.availability_votes, 0); + } + + #[test] + fn availability_vote_gives_summary() { + let context = TestContext { + validators: { + let mut map = HashMap::new(); + map.insert(ValidatorId(1), (GroupId(2), GroupId(455))); + map.insert(ValidatorId(2), (GroupId(5), GroupId(2))); + map + } + }; + + let mut table = create(); + let statement = SignedStatement { + statement: Statement::Candidate(Candidate(2, 100)), + signature: Signature(1), + sender: ValidatorId(1), + }; + let candidate_digest = Digest(100); + + table.import_statement(&context, statement, None); + assert!(!table.detected_misbehavior.contains_key(&ValidatorId(1))); + + let vote = SignedStatement { + statement: Statement::Available(candidate_digest.clone()), + signature: Signature(2), + sender: ValidatorId(2), + }; + + let summary = table.import_statement(&context, vote, None) + .expect("candidate vote to give summary"); + + assert!(!table.detected_misbehavior.contains_key(&ValidatorId(2))); + + assert_eq!(summary.candidate, Digest(100)); + assert_eq!(summary.group_id, GroupId(2)); + assert_eq!(summary.validity_votes, 1); + assert_eq!(summary.availability_votes, 1); + } +} diff --git a/substrate/primitives/src/parachain.rs b/substrate/primitives/src/parachain.rs index 1249319aa8..1d64f3c824 100644 --- a/substrate/primitives/src/parachain.rs +++ b/substrate/primitives/src/parachain.rs @@ -49,6 +49,25 @@ pub struct Candidate { pub block: BlockData, } +/// Candidate receipt type. +#[derive(Debug, PartialEq, Eq, Clone, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +#[serde(deny_unknown_fields)] +pub struct CandidateReceipt { + /// The ID of the parachain this is a candidate for. + pub parachain_index: Id, + /// The collator's account ID + pub collator: ::Address, + /// The head-data + pub head_data: HeadData, + /// Balance uploads to the relay chain. + pub balance_uploads: Vec<(::Address, ::uint::U256)>, + /// Egress queue roots. + pub egress_queue_roots: Vec<(Id, ::hash::H256)>, + /// Fees paid from the chain to the relay chain validators + pub fees: ::uint::U256, +} + /// Parachain ingress queue message. #[derive(Debug, PartialEq, Eq, Clone, Serialize, Deserialize)] pub struct Message(#[serde(with="bytes")] pub Vec); @@ -57,7 +76,7 @@ pub struct Message(#[serde(with="bytes")] pub Vec); /// /// This is just an ordered vector of other parachains' egress queues, /// obtained according to the routing rules. -#[derive(Debug, PartialEq, Eq, Clone, Serialize, Deserialize)] +#[derive(Debug, Default, PartialEq, Eq, Clone, Serialize, Deserialize)] pub struct ConsolidatedIngress(pub Vec<(Id, Vec)>); /// Parachain block data. @@ -71,7 +90,7 @@ pub struct BlockData(#[serde(with="bytes")] pub Vec); pub struct Header(#[serde(with="bytes")] pub Vec); /// Parachain head data included in the chain. -#[derive(Debug, PartialEq, Eq, Serialize, Deserialize)] +#[derive(Debug, PartialEq, Eq, Clone, Serialize, Deserialize)] pub struct HeadData(#[serde(with="bytes")] pub Vec); /// Parachain validation code. @@ -92,10 +111,10 @@ mod tests { assert_eq!(ser::to_string_pretty(&Candidate { parachain_index: 5.into(), collator_signature: 10.into(), - unprocessed_ingress: vec![ - (1, vec![Message(vec![2])]), - (2, vec![Message(vec![2]), Message(vec![3])]), - ], + unprocessed_ingress: ConsolidatedIngress(vec![ + (Id(1), vec![Message(vec![2])]), + (Id(2), vec![Message(vec![2]), Message(vec![3])]), + ]), block: BlockData(vec![1, 2, 3]), }), r#"{ "parachainIndex": 5, From a670208a3339938ab4075290b58e306668361079 Mon Sep 17 00:00:00 2001 From: Gav Wood Date: Mon, 8 Jan 2018 16:48:45 +0100 Subject: [PATCH 2/2] Introduce first groundwork for Wasm executor (#27) * Introduce first groundwork for Wasm executor. * Remove old Rust-runtime code. * Avoid commiting compled files. * Add runtime precompile. * Rename so module makes more sense. * Further renaming. * Ensure tests work. * Allow bringing in of externalities. - Add util functions/macros. - Add uncompacted runtime. - Add some external crates from pwasm-std for managing allocs/memory stuff. * Nice macros for imports. * Allow passing in of data through allocators. Make memcpy and malloc work. Basic allocator. * Can now pass in bytes to WasmExecutor. * Additional cleanup. * Switch usages of `OutData` to `u64` No need to be able to return bytes anymore. * convert to safe but extremely verbose type conversion. @rphmeier any more concise way of doing this? * Remove StaticExternalities distinction. * Remove another unused use. * Refactor wasm utils out * Remove extraneous copies that weren't really testing anything. * Try to use wasm 0.15 * Make it work! * Call-time externalities working. * Add basic externalities. * Fix grumbles and note unwraps to be sorted. * Test storage externality. Unforunately had to change signatures of externalities to avoid immutable function returning a reference. Not sure what to do about this... * Fix nits. * Compile collation logic. * Move back to refs. Yey. * Remove "object" id for storage access. * Fix test. * Fix up rest of tests. * remove unwrap. * Expose set/get code in externalities Also improve tests and add nice wrappers in rust-wasm. * Add validator set. * Introduce validator set into externalities and test. * Add another external function. * Remove code and validators; use storage for everything. * Introduce validators function. * Tests (and a fix) for the validators getter. * Allow calls into runtime to return data. * Remove unneeded trace. * Make runtime printing a bit nicer. * Create separate runtimes for testing and polkadot. * Remove commented code. * Use new path. * Refactor into shared support module. * Fix warning. * Remove unwraps. * Make macro a little less unhygenic. * Add wasm files. --- substrate/.gitignore | 2 + substrate/Cargo.lock | 20 +- substrate/Cargo.toml | 5 +- substrate/cli/Cargo.toml | 2 +- substrate/cli/src/lib.rs | 4 +- substrate/client/src/lib.rs | 26 +- substrate/contracts/src/auth.rs | 38 --- substrate/contracts/src/balances.rs | 64 ----- substrate/contracts/src/executor.rs | 156 ----------- substrate/contracts/src/validator_set.rs | 31 --- substrate/{contracts => executor}/Cargo.toml | 4 +- .../{contracts => executor}/src/error.rs | 18 ++ substrate/{contracts => executor}/src/lib.rs | 23 +- substrate/executor/src/wasm_executor.rs | 248 ++++++++++++++++++ substrate/executor/src/wasm_utils.rs | 207 +++++++++++++++ substrate/primitives/src/contract.rs | 4 + substrate/rpc/Cargo.toml | 2 +- substrate/rpc/src/lib.rs | 2 +- substrate/rpc/src/state/mod.rs | 16 +- substrate/rpc/src/state/tests.rs | 11 +- substrate/runtime/Cargo.lock | 33 +++ substrate/runtime/Cargo.toml | 8 + substrate/runtime/build.sh | 11 + substrate/runtime/init.sh | 6 + substrate/runtime/polkadot/Cargo.toml | 10 + substrate/runtime/polkadot/src/lib.rs | 36 +++ substrate/runtime/pwasm-alloc/Cargo.toml | 18 ++ substrate/runtime/pwasm-alloc/README.md | 12 + substrate/runtime/pwasm-alloc/src/lib.rs | 30 +++ substrate/runtime/pwasm-libc/Cargo.toml | 15 ++ substrate/runtime/pwasm-libc/README.md | 12 + substrate/runtime/pwasm-libc/src/lib.rs | 46 ++++ substrate/runtime/support/Cargo.toml | 11 + substrate/runtime/support/src/lib.rs | 125 +++++++++ .../release/runtime_polkadot.compact.wasm | Bin 0 -> 3042 bytes .../release/runtime_polkadot.wasm | Bin 0 -> 3130 bytes .../release/runtime_test.compact.wasm | Bin 0 -> 3042 bytes .../release/runtime_test.wasm | Bin 0 -> 3130 bytes substrate/runtime/test/Cargo.toml | 10 + substrate/runtime/test/src/lib.rs | 36 +++ substrate/state_machine/Cargo.toml | 1 + substrate/state_machine/src/backend.rs | 36 +-- substrate/state_machine/src/ext.rs | 121 +-------- substrate/state_machine/src/lib.rs | 238 +++++++---------- 44 files changed, 1087 insertions(+), 611 deletions(-) delete mode 100644 substrate/contracts/src/auth.rs delete mode 100644 substrate/contracts/src/balances.rs delete mode 100644 substrate/contracts/src/executor.rs delete mode 100644 substrate/contracts/src/validator_set.rs rename substrate/{contracts => executor}/Cargo.toml (85%) rename substrate/{contracts => executor}/src/error.rs (78%) rename substrate/{contracts => executor}/src/lib.rs (67%) create mode 100644 substrate/executor/src/wasm_executor.rs create mode 100644 substrate/executor/src/wasm_utils.rs create mode 100644 substrate/runtime/Cargo.lock create mode 100644 substrate/runtime/Cargo.toml create mode 100755 substrate/runtime/build.sh create mode 100755 substrate/runtime/init.sh create mode 100644 substrate/runtime/polkadot/Cargo.toml create mode 100644 substrate/runtime/polkadot/src/lib.rs create mode 100644 substrate/runtime/pwasm-alloc/Cargo.toml create mode 100644 substrate/runtime/pwasm-alloc/README.md create mode 100644 substrate/runtime/pwasm-alloc/src/lib.rs create mode 100644 substrate/runtime/pwasm-libc/Cargo.toml create mode 100644 substrate/runtime/pwasm-libc/README.md create mode 100644 substrate/runtime/pwasm-libc/src/lib.rs create mode 100644 substrate/runtime/support/Cargo.toml create mode 100644 substrate/runtime/support/src/lib.rs create mode 100644 substrate/runtime/target/wasm32-unknown-unknown/release/runtime_polkadot.compact.wasm create mode 100644 substrate/runtime/target/wasm32-unknown-unknown/release/runtime_polkadot.wasm create mode 100644 substrate/runtime/target/wasm32-unknown-unknown/release/runtime_test.compact.wasm create mode 100644 substrate/runtime/target/wasm32-unknown-unknown/release/runtime_test.wasm create mode 100644 substrate/runtime/test/Cargo.toml create mode 100644 substrate/runtime/test/src/lib.rs diff --git a/substrate/.gitignore b/substrate/.gitignore index a2f4b3c27a..cd789637f9 100644 --- a/substrate/.gitignore +++ b/substrate/.gitignore @@ -1,3 +1,5 @@ /target/ **/*.rs.bk *.swp +runtime/**/target/ +**/._* diff --git a/substrate/Cargo.lock b/substrate/Cargo.lock index a185bd50e0..15f526edd9 100644 --- a/substrate/Cargo.lock +++ b/substrate/Cargo.lock @@ -542,6 +542,16 @@ dependencies = [ "stable_deref_trait 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "parity-wasm" +version = "0.15.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "byteorder 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)", + "log 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)", + "parking_lot 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "parking_lot" version = "0.4.8" @@ -619,7 +629,7 @@ dependencies = [ "error-chain 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)", "log 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)", "polkadot-client 0.1.0", - "polkadot-contracts 0.1.0", + "polkadot-executor 0.1.0", "polkadot-primitives 0.1.0", "polkadot-rpc-servers 0.1.0", ] @@ -642,11 +652,13 @@ dependencies = [ ] [[package]] -name = "polkadot-contracts" +name = "polkadot-executor" version = "0.1.0" dependencies = [ "assert_matches 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)", + "byteorder 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)", "error-chain 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)", + "parity-wasm 0.15.4 (registry+https://github.com/rust-lang/crates.io-index)", "polkadot-primitives 0.1.0", "polkadot-serializer 0.1.0", "polkadot-state-machine 0.1.0", @@ -678,7 +690,7 @@ dependencies = [ "jsonrpc-core 8.0.0 (git+https://github.com/paritytech/jsonrpc.git)", "jsonrpc-macros 8.0.0 (git+https://github.com/paritytech/jsonrpc.git)", "polkadot-client 0.1.0", - "polkadot-contracts 0.1.0", + "polkadot-executor 0.1.0", "polkadot-primitives 0.1.0", "polkadot-state-machine 0.1.0", ] @@ -704,6 +716,7 @@ dependencies = [ name = "polkadot-state-machine" version = "0.1.0" dependencies = [ + "byteorder 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)", "hashdb 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", "keccak-hash 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", "memorydb 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", @@ -1179,6 +1192,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" "checksum num_cpus 1.7.0 (registry+https://github.com/rust-lang/crates.io-index)" = "514f0d73e64be53ff320680ca671b64fe3fb91da01e1ae2ddc99eb51d453b20d" "checksum odds 0.2.25 (registry+https://github.com/rust-lang/crates.io-index)" = "c3df9b730298cea3a1c3faa90b7e2f9df3a9c400d0936d6015e6165734eefcba" "checksum owning_ref 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)" = "cdf84f41639e037b484f93433aa3897863b561ed65c6e59c7073d7c561710f37" +"checksum parity-wasm 0.15.4 (registry+https://github.com/rust-lang/crates.io-index)" = "235801e9531998c4bb307f4ea6833c9f40a4cf132895219ac8c2cd25a9b310f7" "checksum parking_lot 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)" = "149d8f5b97f3c1133e3cfcd8886449959e856b557ff281e292b733d7c69e005e" "checksum parking_lot_core 0.2.6 (registry+https://github.com/rust-lang/crates.io-index)" = "4f610cb9664da38e417ea3225f23051f589851999535290e077939838ab7a595" "checksum patricia-trie 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "f1e2f638d79aba5c4a71a4f373df6e3cd702250a53b7f0ed4da1e2a7be9737ae" diff --git a/substrate/Cargo.toml b/substrate/Cargo.toml index 1b8fb4c309..a0f6303a67 100644 --- a/substrate/Cargo.toml +++ b/substrate/Cargo.toml @@ -12,7 +12,7 @@ members = [ "candidate-agreement", "client", "collator", - "contracts", + "executor", "primitives", "rpc", "rpc_servers", @@ -20,3 +20,6 @@ members = [ "state_machine", "validator", ] +exclude = [ + "runtime" +] diff --git a/substrate/cli/Cargo.toml b/substrate/cli/Cargo.toml index f19977785f..64305642bf 100644 --- a/substrate/cli/Cargo.toml +++ b/substrate/cli/Cargo.toml @@ -10,6 +10,6 @@ env_logger = "0.4" error-chain = "0.11" log = "0.3" polkadot-client = { path = "../client", version = "0.1" } -polkadot-contracts = { path = "../contracts", version = "0.1" } +polkadot-executor = { path = "../executor", version = "0.1" } polkadot-primitives = { path = "../primitives", version = "0.1" } polkadot-rpc-servers = { path = "../rpc_servers", version = "0.1" } diff --git a/substrate/cli/src/lib.rs b/substrate/cli/src/lib.rs index dfd7080451..73168b73b4 100644 --- a/substrate/cli/src/lib.rs +++ b/substrate/cli/src/lib.rs @@ -20,7 +20,7 @@ extern crate env_logger; extern crate polkadot_client as client; -extern crate polkadot_contracts as contracts; +extern crate polkadot_executor as executor; extern crate polkadot_primitives as primitives; extern crate polkadot_rpc_servers as rpc; @@ -54,7 +54,7 @@ pub fn run(args: I) -> error::Result<()> where // Create client let blockchain = DummyBlockchain; - let executor = contracts::executor(); + let executor = executor::executor(); let client = client::Client::new(blockchain, executor); let address = "127.0.0.1:9933".parse().unwrap(); diff --git a/substrate/client/src/lib.rs b/substrate/client/src/lib.rs index 642dab79e2..242e12765e 100644 --- a/substrate/client/src/lib.rs +++ b/substrate/client/src/lib.rs @@ -26,8 +26,8 @@ extern crate error_chain; pub mod error; -use primitives::{block, Address, H256}; -use primitives::contract::{CallData, OutData, StorageData}; +use primitives::{block}; +use primitives::contract::{CallData, StorageKey, StorageData}; use state_machine::backend::Backend; use self::error::ResultExt; @@ -44,6 +44,14 @@ pub trait Blockchain { fn header(&self, hash: &block::HeaderHash) -> Result, Self::Error>; } +/// Information regarding the result of a call. +pub struct CallResult { + /// The data that was returned from the call. + pub return_data: Vec, + /// The changes made to the state by the call. + pub changes: state_machine::OverlayedChanges, +} + /// Polkadot Client #[derive(Debug)] pub struct Client { @@ -72,25 +80,27 @@ impl Client where } /// Return single storage entry of contract under given address in state in a block of given hash. - pub fn storage(&self, hash: &block::HeaderHash, address: &Address, key: &H256) -> error::Result { + pub fn storage(&self, hash: &block::HeaderHash, key: &StorageKey) -> error::Result { self.state_at(hash)? - .storage(address, key) + .storage(&key.0) .map(|x| StorageData(x.to_vec())) .chain_err(|| error::ErrorKind::Backend) } /// Execute a call to a contract on top of state in a block of given hash. - pub fn call(&self, hash: &block::HeaderHash, address: &Address, method: &str, call_data: &CallData) -> error::Result { + /// + /// No changes are made. + pub fn call(&self, hash: &block::HeaderHash, method: &str, call_data: &CallData) -> error::Result { let state = self.state_at(hash)?; let mut changes = state_machine::OverlayedChanges::default(); - Ok(state_machine::execute( + let _ = state_machine::execute( &state, &mut changes, &self.executor, - address, method, call_data, - )?) + )?; + Ok(CallResult { return_data: vec![], changes }) } } diff --git a/substrate/contracts/src/auth.rs b/substrate/contracts/src/auth.rs deleted file mode 100644 index a244526ddd..0000000000 --- a/substrate/contracts/src/auth.rs +++ /dev/null @@ -1,38 +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 . - -use primitives::Address; -use state_machine::StaticExternalities; - -use error::Result; -use executor::RustExecutor; - -/// Data and some sort of Authentication Data -type DataAndAuth = (Vec, Vec); - -/// Authentication contract rust implementation. -#[derive(Debug, Default)] -pub struct Contract; -impl Contract { - /// Verify authentication data. - /// - /// Given Message and Authentication Data verifies it and returns: - /// 1. None in case it doesn't match (i.e. signature is invalid) - /// 2. A address who signed that Message. - pub fn check_auth>(&self, _ext: &E, _data: DataAndAuth) -> Result> { - unimplemented!() - } -} diff --git a/substrate/contracts/src/balances.rs b/substrate/contracts/src/balances.rs deleted file mode 100644 index d5e3fd6b66..0000000000 --- a/substrate/contracts/src/balances.rs +++ /dev/null @@ -1,64 +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 . - -use primitives::Address; -use primitives::uint::U256; -use state_machine::{Externalities, StaticExternalities}; - -use error::Result; -use executor::RustExecutor; - -#[derive(Debug, Serialize, Deserialize)] -pub struct Transfer { - /// Transfer value - value: U256, - /// Transfer destination - to: Address, - /// Replay protection - nonce: U256, - /// Data authorizing the transfer (we can derive sender from it) - authentication_data: Vec, -} - -/// Balances contract rust implementation. -#[derive(Debug, Default)] -pub struct Contract; -impl Contract { - /// Returns a balance of given address. - pub fn balance_of>(&self, _ext: &E, _data: Address) -> Result { - unimplemented!() - } - - /// Returns the next nonce to authorize the transfer from given address. - pub fn next_nonce>(&self, _ext: &E, _data: Address) -> Result { - unimplemented!() - } - - /// Checks preconditions for transfer. - /// Should verify: - /// - signature - /// - replay protection - /// - enough balance - pub fn transfer_preconditions>(&self, _db: &E, _data: Transfer) -> Result { - unimplemented!() - } - - /// Perform a transfer. - /// This should first make sure that precondtions are satisfied and later perform the transfer. - pub fn transfer>(&self, _ext: &mut E, _data: Transfer) -> Result { - unimplemented!() - } -} diff --git a/substrate/contracts/src/executor.rs b/substrate/contracts/src/executor.rs deleted file mode 100644 index dde20b4257..0000000000 --- a/substrate/contracts/src/executor.rs +++ /dev/null @@ -1,156 +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 . - -//! Rust implementation of Polkadot contracts. - -use primitives::contract::{CallData, OutData}; -use serializer::{from_slice as de, to_vec as ser}; -use state_machine::{StaticExternalities, Externalities, CodeExecutor}; - -use error::{Error, ErrorKind, Result}; -use auth; -use balances; -use validator_set; - -/// Dummy rust executor for contracts. -/// -/// Instead of actually executing the provided code it just -/// dispatches the calls to pre-defined hardcoded implementations in rust. -#[derive(Debug, Default)] -pub struct RustExecutor { - auth: auth::Contract, - balance: balances::Contract, - validator_set: validator_set::Contract, -} - -impl RustExecutor { - const AUTH: u8 = 1; - const BALANCES: u8 = 2; - const VALIDATOR_SET: u8 = 3; -} - -impl CodeExecutor for RustExecutor { - type Error = Error; - - fn call_static>( - &self, - ext: &E, - code: &[u8], - method: &str, - data: &CallData, - ) -> Result { - ensure!(code.len() == 1, ErrorKind::InvalidCode(code.to_vec())); - - Ok(OutData(match code[0] { - Self::AUTH => match method { - "check_auth" => ser(&self.auth.check_auth(ext, de(&data.0)?)?), - m => bail!(ErrorKind::MethodNotFound(m.to_owned())), - }, - Self::BALANCES => match method { - "balance_of" => ser(&self.balance.balance_of(ext, de(&data.0)?)?), - "next_nonce" => ser(&self.balance.next_nonce(ext, de(&data.0)?)?), - "transfer_preconditions" => ser(&self.balance.transfer_preconditions(ext, de(&data.0)?)?), - m => bail!(ErrorKind::MethodNotFound(m.to_owned())), - }, - Self::VALIDATOR_SET => match method { - "validator_set" => ser(&self.validator_set.validator_set(ext, de(&data.0)?)?), - m => bail!(ErrorKind::MethodNotFound(m.to_owned())), - }, - c => bail!(ErrorKind::InvalidCode(vec![c])), - })) - } - - fn call>( - &self, - ext: &mut E, - code: &[u8], - method: &str, - data: &CallData, - ) -> Result { - ensure!(code.len() == 1, ErrorKind::InvalidCode(code.to_vec())); - - Ok(OutData(match code[0] { - Self::BALANCES=> match method { - "transfer" => ser(&self.balance.transfer(ext, de(&data.0)?)?), - m => bail!(ErrorKind::MethodNotFound(m.to_owned())), - }, - c => bail!(ErrorKind::InvalidCode(vec![c])), - })) - } -} - -#[cfg(test)] -mod tests { - use super::*; - use primitives::Address; - use primitives::hash::H256; - - #[derive(Debug, Default)] - struct TestExternalities; - impl Externalities for TestExternalities { - fn set_storage(&mut self, _key: H256, _value: Vec) { - unimplemented!() - } - - fn call(&mut self, _address: &Address, _method: &str, _data: &CallData) -> Result { - unimplemented!() - } - } - - impl StaticExternalities for TestExternalities { - type Error = Error; - - fn storage(&self, _key: &H256) -> Result<&[u8]> { - unimplemented!() - } - - fn call_static(&self, _address: &Address, _method: &str, _data: &CallData) -> Result { - unimplemented!() - } - } - - #[test] - fn should_fail_for_empty_or_unknown_code() { - // given - let mut ext = TestExternalities::default(); - let executor = RustExecutor::default(); - - assert_matches!( - *executor.call(&mut ext, &[], "any", &CallData(vec![])).unwrap_err().kind(), - ErrorKind::InvalidCode(ref code) if code.is_empty() - ); - assert_matches!( - *executor.call(&mut ext, &[1, 2], "any", &CallData(vec![])).unwrap_err().kind(), - ErrorKind::InvalidCode(ref code) if code.len() == 2 - ); - assert_matches!( - *executor.call(&mut ext, &[255,], "any", &CallData(vec![])).unwrap_err().kind(), - ErrorKind::InvalidCode(_) - ); - } - - #[test] - fn should_fail_on_invalid_method() { - // given - let mut ext = TestExternalities::default(); - let executor = RustExecutor::default(); - - assert_matches!( - *executor.call(&mut ext, &[2], "any", &CallData(vec![])).unwrap_err().kind(), - ErrorKind::MethodNotFound(ref method) if &*method == "any" - ); - } -} diff --git a/substrate/contracts/src/validator_set.rs b/substrate/contracts/src/validator_set.rs deleted file mode 100644 index 7462781c6b..0000000000 --- a/substrate/contracts/src/validator_set.rs +++ /dev/null @@ -1,31 +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 . - -use primitives::Address; -use state_machine::StaticExternalities; - -use error::Result; -use executor::RustExecutor; - -/// Harcoded validator set contract. -#[derive(Debug, Default)] -pub struct Contract; -impl Contract { - /// Returns current validator set. - pub fn validator_set>(&self, _db: &E, _data: ()) -> Result> { - unimplemented!() - } -} diff --git a/substrate/contracts/Cargo.toml b/substrate/executor/Cargo.toml similarity index 85% rename from substrate/contracts/Cargo.toml rename to substrate/executor/Cargo.toml index 84d52c87f8..8aa72f4327 100644 --- a/substrate/contracts/Cargo.toml +++ b/substrate/executor/Cargo.toml @@ -1,5 +1,5 @@ [package] -name = "polkadot-contracts" +name = "polkadot-executor" version = "0.1.0" authors = ["Parity Technologies "] @@ -10,6 +10,8 @@ polkadot-serializer = { path = "../serializer", version = "0.1" } polkadot-state-machine = { path = "../state_machine" , version = "0.1" } serde = "1.0" serde_derive = "1.0" +parity-wasm = "0.15.0" +byteorder = "1.1" [dev-dependencies] assert_matches = "1.1" diff --git a/substrate/contracts/src/error.rs b/substrate/executor/src/error.rs similarity index 78% rename from substrate/contracts/src/error.rs rename to substrate/executor/src/error.rs index 04e3144ed6..4fd0ec0604 100644 --- a/substrate/contracts/src/error.rs +++ b/substrate/executor/src/error.rs @@ -42,5 +42,23 @@ error_chain! { description("externalities failure"), display("Externalities error: {}", e), } + + /// Invalid index. + InvalidIndex { + description("index given was not in range"), + display("Invalid index provided"), + } + + /// Invalid return type. + InvalidReturn { + description("u64 was not returned"), + display("Invalid type returned (should be u64)"), + } + + /// Runtime failed. + Runtime { + description("runtime failure"), + display("Runtime error"), + } } } diff --git a/substrate/contracts/src/lib.rs b/substrate/executor/src/lib.rs similarity index 67% rename from substrate/contracts/src/lib.rs rename to substrate/executor/src/lib.rs index 6226edf4fc..8bf7f42b62 100644 --- a/substrate/contracts/src/lib.rs +++ b/substrate/executor/src/lib.rs @@ -17,6 +17,13 @@ //! Temporary crate for contracts implementations. //! //! This will be replaced with WASM contracts stored on-chain. +//! ** NOTE *** +//! This is entirely deprecated with the idea of a single-module Wasm module for state transition. +//! The dispatch table should be replaced with the specific functions needed: +//! - execute_block(bytes) +//! - init_block(PrevBlock?) -> InProgressBlock +//! - add_transaction(InProgressBlock) -> InProgressBlock +//! I leave it as is for now as it might be removed before this is ever done. #![warn(missing_docs)] @@ -24,24 +31,22 @@ extern crate polkadot_primitives as primitives; extern crate polkadot_serializer as serializer; extern crate polkadot_state_machine as state_machine; extern crate serde; +extern crate parity_wasm; +extern crate byteorder; #[macro_use] extern crate error_chain; -#[macro_use] -extern crate serde_derive; #[cfg(test)] -#[macro_use] extern crate assert_matches; -mod auth; -mod balances; -mod validator_set; +#[macro_use] +mod wasm_utils; +mod wasm_executor; pub mod error; -pub mod executor; /// Creates new RustExecutor for contracts. -pub fn executor() -> executor::RustExecutor { - executor::RustExecutor::default() +pub fn executor() -> wasm_executor::WasmExecutor { + wasm_executor::WasmExecutor::default() } diff --git a/substrate/executor/src/wasm_executor.rs b/substrate/executor/src/wasm_executor.rs new file mode 100644 index 0000000000..f52b2cbf71 --- /dev/null +++ b/substrate/executor/src/wasm_executor.rs @@ -0,0 +1,248 @@ +// 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 . + +//! Rust implementation of Polkadot contracts. + +use std::sync::Arc; +use std::collections::HashMap; +use parity_wasm::{deserialize_buffer, ModuleInstanceInterface, ProgramInstance}; +use parity_wasm::interpreter::{ItemIndex}; +use parity_wasm::RuntimeValue::{I32, I64}; +use primitives::contract::CallData; +use state_machine::{Externalities, CodeExecutor}; +use error::{Error, ErrorKind, Result}; +use wasm_utils::{MemoryInstance, UserDefinedElements, + AddModuleWithoutFullDependentInstance}; + +struct Heap { + end: u32, +} + +impl Heap { + fn new() -> Self { + Heap { + end: 1024, + } + } + fn allocate(&mut self, size: u32) -> u32 { + let r = self.end; + self.end += size; + r + } + fn deallocate(&mut self, _offset: u32) { + } +} + +struct FunctionExecutor<'e, E: Externalities + 'e> { + heap: Heap, + memory: Arc, + ext: &'e mut E, +} + +impl<'e, E: Externalities> FunctionExecutor<'e, E> { + fn new(m: &Arc, e: &'e mut E) -> Self { + FunctionExecutor { + heap: Heap::new(), + memory: Arc::clone(m), + ext: e, + } + } +} + +trait WritePrimitive { + fn write_primitive(&self, offset: u32, t: T); +} + +impl WritePrimitive for MemoryInstance { + fn write_primitive(&self, offset: u32, t: u32) { + use byteorder::{LittleEndian, ByteOrder}; + let mut r = [0u8; 4]; + LittleEndian::write_u32(&mut r, t); + let _ = self.set(offset, &r); + } +} + +impl_function_executor!(this: FunctionExecutor<'e, E>, + ext_print(utf8_data: *const u8, utf8_len: i32) => { + if let Ok(utf8) = this.memory.get(utf8_data, utf8_len as usize) { + if let Ok(message) = String::from_utf8(utf8) { + println!("Runtime: {}", message); + } + } + }, + ext_print_num(number: u64) => { + println!("Runtime: {}", number); + }, + ext_memcpy(dest: *mut u8, src: *const u8, count: usize) -> *mut u8 => { + let _ = this.memory.copy_nonoverlapping(src as usize, dest as usize, count as usize); + println!("memcpy {} from {}, {} bytes", dest, src, count); + dest + }, + ext_memmove(dest: *mut u8, src: *const u8, count: usize) -> *mut u8 => { + let _ = this.memory.copy(src as usize, dest as usize, count as usize); + println!("memmove {} from {}, {} bytes", dest, src, count); + dest + }, + ext_memset(dest: *mut u8, val: i32, count: usize) -> *mut u8 => { + let _ = this.memory.clear(dest as usize, val as u8, count as usize); + println!("memset {} with {}, {} bytes", dest, val, count); + dest + }, + ext_malloc(size: usize) -> *mut u8 => { + let r = this.heap.allocate(size); + println!("malloc {} bytes at {}", size, r); + r + }, + ext_free(addr: *mut u8) => { + this.heap.deallocate(addr); + println!("free {}", addr) + }, + ext_set_storage(key_data: *const u8, key_len: i32, value_data: *const u8, value_len: i32) => { + if let (Ok(key), Ok(value)) = (this.memory.get(key_data, key_len as usize), this.memory.get(value_data, value_len as usize)) { + this.ext.set_storage(key, value); + } + }, + ext_get_allocated_storage(key_data: *const u8, key_len: i32, written_out: *mut i32) -> *mut u8 => { + let (offset, written) = if let Ok(key) = this.memory.get(key_data, key_len as usize) { + if let Ok(value) = this.ext.storage(&key) { + let offset = this.heap.allocate(value.len() as u32) as u32; + let _ = this.memory.set(offset, &value); + (offset, value.len() as u32) + } else { (0, 0) } + } else { (0, 0) }; + + this.memory.write_primitive(written_out, written); + offset as u32 + } + => <'e, E: Externalities + 'e> +); + +/// Wasm rust executor for contracts. +/// +/// Executes the provided code in a sandboxed wasm runtime. +#[derive(Debug, Default)] +pub struct WasmExecutor; + +impl CodeExecutor for WasmExecutor { + type Error = Error; + + fn call( + &self, + ext: &mut E, + code: &[u8], + method: &str, + data: &CallData, + ) -> Result> { + // TODO: handle all expects as errors to be returned. + + let program = ProgramInstance::new().expect("this really shouldn't be able to fail; qed"); + + let module = deserialize_buffer(code.to_vec()).expect("all modules compiled with rustc are valid wasm code; qed"); + let module = program.add_module_by_sigs("test", module, map!["env" => FunctionExecutor::::SIGNATURES]).expect("runtime signatures always provided; qed"); + + let memory = module.memory(ItemIndex::Internal(0)).expect("all modules compiled with rustc include memory segments; qed"); + let mut fec = FunctionExecutor::new(&memory, ext); + + let size = data.0.len() as u32; + let offset = fec.heap.allocate(size); + memory.set(offset, &data.0).expect("heap always gives a sensible offset to write"); + + let returned = program + .params_with_external("env", &mut fec) + .map(|p| p + .add_argument(I32(offset as i32)) + .add_argument(I32(size as i32))) + .and_then(|p| module.execute_export(method, p)) + .map_err(|_| -> Error { ErrorKind::Runtime.into() })?; + + if let Some(I64(r)) = returned { + memory.get(r as u32, (r >> 32) as u32 as usize) + .map_err(|_| ErrorKind::Runtime.into()) + } else { + Err(ErrorKind::InvalidReturn.into()) + } + } +} + +#[cfg(test)] +mod tests { + + use super::*; + + #[derive(Debug, Default)] + struct TestExternalities { + storage: HashMap, Vec>, + } + impl Externalities for TestExternalities { + type Error = Error; + + fn storage(&self, key: &[u8]) -> Result<&[u8]> { + Ok(self.storage.get(&key.to_vec()).map_or(&[] as &[u8], Vec::as_slice)) + } + + fn set_storage(&mut self, key: Vec, value: Vec) { + self.storage.insert(key, value); + } + } + + #[test] + fn should_pass_externalities_at_call() { + let mut ext = TestExternalities::default(); + ext.set_storage(b"\0code".to_vec(), b"The code".to_vec()); + + let program = ProgramInstance::new().unwrap(); + + let test_module = include_bytes!("../../runtime/target/wasm32-unknown-unknown/release/runtime_test.compact.wasm"); + let module = deserialize_buffer(test_module.to_vec()).expect("Failed to load module"); + let module = program.add_module_by_sigs("test", module, map!["env" => FunctionExecutor::::SIGNATURES]).expect("Failed to initialize module"); + + let output = { + let memory = module.memory(ItemIndex::Internal(0)).unwrap(); + let mut fec = FunctionExecutor::new(&memory, &mut ext); + + let data = b"Hello world"; + let size = data.len() as u32; + let offset = fec.heap.allocate(size); + memory.set(offset, data).unwrap(); + + let returned = program + .params_with_external("env", &mut fec) + .map(|p| p + .add_argument(I32(offset as i32)) + .add_argument(I32(size as i32))) + .and_then(|p| module.execute_export("test_data_in", p)) + .map_err(|_| -> Error { ErrorKind::Runtime.into() }).expect("function should be callable"); + + if let Some(I64(r)) = returned { + println!("returned {:?} ({:?}, {:?})", r, r as u32, (r >> 32) as u32 as usize); + memory.get(r as u32, (r >> 32) as u32 as usize).expect("memory address should be reasonable.") + } else { + panic!("bad return value, not u64"); + } + }; + + assert_eq!(output, b"all ok!".to_vec()); + + let expected: HashMap<_, _> = map![ + b"\0code".to_vec() => b"Hello world".to_vec(), + b"input".to_vec() => b"Hello world".to_vec(), + b"code".to_vec() => b"The code".to_vec(), + b"\0validator_count".to_vec() => vec![1], + b"\0validator".to_vec() => b"Hello world".to_vec() + ]; + assert_eq!(expected, ext.storage); + } +} diff --git a/substrate/executor/src/wasm_utils.rs b/substrate/executor/src/wasm_utils.rs new file mode 100644 index 0000000000..577e6ab0b2 --- /dev/null +++ b/substrate/executor/src/wasm_utils.rs @@ -0,0 +1,207 @@ +// 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 . + +//! Rust implementation of Polkadot contracts. + +use std::sync::{Arc}; +use std::collections::HashMap; +pub use std::result; +pub use parity_wasm::builder; +pub use parity_wasm::elements::{ValueType, Module}; +pub use parity_wasm::interpreter::{RuntimeValue, UserFunctionDescriptor, UserFunctionExecutor, + UserDefinedElements, env_native_module, DummyUserError, ExecutionParams, UserError}; +use parity_wasm::interpreter; + +pub type Error = interpreter::Error; +pub type MemoryInstance = interpreter::MemoryInstance; +pub type CallerContext<'a> = interpreter::CallerContext<'a, DummyUserError>; + +pub trait ConvertibleToWasm { const VALUE_TYPE: ValueType; type NativeType; fn to_runtime_value(self) -> RuntimeValue; } +impl ConvertibleToWasm for i32 { type NativeType = i32; const VALUE_TYPE: ValueType = ValueType::I32; fn to_runtime_value(self) -> RuntimeValue { RuntimeValue::I32(self) } } +impl ConvertibleToWasm for u32 { type NativeType = u32; const VALUE_TYPE: ValueType = ValueType::I32; fn to_runtime_value(self) -> RuntimeValue { RuntimeValue::I32(self as i32) } } +impl ConvertibleToWasm for i64 { type NativeType = i64; const VALUE_TYPE: ValueType = ValueType::I64; fn to_runtime_value(self) -> RuntimeValue { RuntimeValue::I64(self) } } +impl ConvertibleToWasm for u64 { type NativeType = u64; const VALUE_TYPE: ValueType = ValueType::I64; fn to_runtime_value(self) -> RuntimeValue { RuntimeValue::I64(self as i64) } } +impl ConvertibleToWasm for f32 { type NativeType = f32; const VALUE_TYPE: ValueType = ValueType::F32; fn to_runtime_value(self) -> RuntimeValue { RuntimeValue::F32(self) } } +impl ConvertibleToWasm for f64 { type NativeType = f64; const VALUE_TYPE: ValueType = ValueType::F64; fn to_runtime_value(self) -> RuntimeValue { RuntimeValue::F64(self) } } +impl ConvertibleToWasm for isize { type NativeType = i32; const VALUE_TYPE: ValueType = ValueType::I32; fn to_runtime_value(self) -> RuntimeValue { RuntimeValue::I32(self as i32) } } +impl ConvertibleToWasm for usize { type NativeType = u32; const VALUE_TYPE: ValueType = ValueType::I32; fn to_runtime_value(self) -> RuntimeValue { RuntimeValue::I32(self as u32 as i32) } } +impl ConvertibleToWasm for *const T { type NativeType = u32; const VALUE_TYPE: ValueType = ValueType::I32; fn to_runtime_value(self) -> RuntimeValue { RuntimeValue::I32(self as isize as i32) } } +impl ConvertibleToWasm for *mut T { type NativeType = u32; const VALUE_TYPE: ValueType = ValueType::I32; fn to_runtime_value(self) -> RuntimeValue { RuntimeValue::I32(self as isize as i32) } } + +#[macro_export] +macro_rules! convert_args { + () => ([]); + ( $( $t:ty ),* ) => ( [ $( { use $crate::wasm_utils::ConvertibleToWasm; <$t>::VALUE_TYPE }, )* ] ); +} + +#[macro_export] +macro_rules! convert_fn { + ( $name:ident ( $( $params:ty ),* ) ) => ( $crate::wasm_utils::UserFunctionDescriptor::Static(stringify!($name), &convert_args!($($params),*), None) ); + ( $name:ident ( $( $params:ty ),* ) -> $returns:ty ) => ( $crate::wasm_utils::UserFunctionDescriptor::Static(stringify!($name), &convert_args!($($params),*), Some({ use $crate::wasm_utils::ConvertibleToWasm; <$returns>::VALUE_TYPE }) ) ); +} + +#[macro_export] +macro_rules! reverse_params { + // Entry point, use brackets to recursively reverse above. + ($body:tt, $self:ident, $context:ident, $( $names:ident : $params:ty ),*) => ( + reverse_params!($body $self $context [ $( $names : $params ),* ]); + ); + ($body:tt $self:ident $context:ident [] $( $names:ident : $params:ty ),*) => ({ + $( + let $names : <$params as $crate::wasm_utils::ConvertibleToWasm>::NativeType = match $context.value_stack.pop_as() { + Ok(value) => value, + Err(error) => return Err(error.into()), + }; + )* + $body + }); + ($body:tt $self:ident $context:ident [ $name:ident : $param:ty $(, $names:ident : $params:ty )* ] $( $reversed_names:ident : $reversed_params:ty ),*) => ( + reverse_params!($body $self $context [ $( $names : $params ),* ] $name : $param $( , $reversed_names : $reversed_params )*); + ); +} + +#[macro_export] +macro_rules! marshall { + ( $context:ident, $self:ident, ( $( $names:ident : $params:ty ),* ) -> $returns:ty => $body:tt ) => ({ + let r : <$returns as $crate::wasm_utils::ConvertibleToWasm>::NativeType = reverse_params!($body, $self, $context, $( $names : $params ),*); + Ok(Some({ use $crate::wasm_utils::ConvertibleToWasm; r.to_runtime_value() })) + }); + ( $context:ident, $self:ident, ( $( $names:ident : $params:ty ),* ) => $body:tt ) => ({ + reverse_params!($body, $self, $context, $( $names : $params ),*); + Ok(None) + }) +} + +#[macro_export] +macro_rules! dispatch { + ( $objectname:ident, $( $name:ident ( $( $names:ident : $params:ty ),* ) $( -> $returns:ty )* => $body:tt ),* ) => ( + fn execute(&mut self, name: &str, context: $crate::wasm_utils::CallerContext) + -> $crate::wasm_utils::result::Result, $crate::wasm_utils::Error> { + let $objectname = self; + match name { + $( + stringify!($name) => marshall!(context, $objectname, ( $( $names : $params ),* ) $( -> $returns )* => $body), + )* + _ => panic!() + } + } + ); +} + +#[macro_export] +macro_rules! signatures { + ( $( $name:ident ( $( $params:ty ),* ) $( -> $returns:ty )* ),* ) => ( + const SIGNATURES: &'static [$crate::wasm_utils::UserFunctionDescriptor] = &[ + $( + convert_fn!( $name ( $( $params ),* ) $( -> $returns )* ), + )* + ]; + ); +} + +pub trait IntoUserDefinedElements { + fn into_user_defined_elements(&mut self) -> UserDefinedElements; +} + +#[macro_export] +macro_rules! impl_function_executor { + ( $objectname:ident : $structname:ty, $( $name:ident ( $( $names:ident : $params:ty ),* ) $( -> $returns:ty )* => $body:tt ),* => $($pre:tt)+ ) => ( + impl $( $pre ) + $crate::wasm_utils::UserFunctionExecutor<$crate::wasm_utils::DummyUserError> for $structname { + dispatch!($objectname, $( $name( $( $names : $params ),* ) $( -> $returns )* => $body ),*); + } + impl $( $pre ) + $structname { + signatures!($( $name( $( $params ),* ) $( -> $returns )* ),*); + } + impl $( $pre ) + $crate::wasm_utils::IntoUserDefinedElements for $structname { + fn into_user_defined_elements(&mut self) -> UserDefinedElements<$crate::wasm_utils::DummyUserError> { + $crate::wasm_utils::UserDefinedElements { + executor: Some(self), + globals: HashMap::new(), // TODO: provide + functions: ::std::borrow::Cow::from(Self::SIGNATURES), + } + } + } + ); +} + +#[derive(Clone)] +struct DummyUserFunctionExecutor; +impl interpreter::UserFunctionExecutor for DummyUserFunctionExecutor { + fn execute(&mut self, _name: &str, _context: interpreter::CallerContext) -> + result::Result, interpreter::Error> + { + unimplemented!() + } +} + +pub trait AddModuleWithoutFullDependentInstance { + fn add_module_by_sigs( + &self, + name: &str, + module: Module, + functions: HashMap<&str, &'static [UserFunctionDescriptor]>, + ) -> result::Result>, interpreter::Error>; + + fn params_with_external<'a, 'b: 'a>(&'b self, externals_name: &str, externals: &'a mut IntoUserDefinedElements) -> result::Result, Error>; +} + +impl AddModuleWithoutFullDependentInstance for interpreter::ProgramInstance { + fn add_module_by_sigs( + &self, + name: &str, + module: Module, + functions: HashMap<&str, &'static [UserFunctionDescriptor]> + ) -> result::Result>, interpreter::Error> { + let mut dufe = vec![DummyUserFunctionExecutor; functions.len()]; + let dufe_refs = dufe.iter_mut().collect::>(); + let fake_module_map = functions.into_iter() + .zip(dufe_refs.into_iter()) + .map(|((dep_mod_name, functions), dufe)| -> result::Result<_, interpreter::Error> { + let fake_module = Arc::new( + interpreter::env_native_module( + self.module(dep_mod_name).ok_or(DummyUserError)?, UserDefinedElements { + executor: Some(dufe), + globals: HashMap::new(), + functions: ::std::borrow::Cow::from(functions), + } + )? + ); + let fake_module: Arc> = fake_module; + Ok((dep_mod_name.into(), fake_module)) + }) + .collect::, interpreter::Error>>()?; + self.add_module(name, module, Some(&fake_module_map)) + } + + fn params_with_external<'a, 'b: 'a>(&'b self, externals_name: &str, externals: &'a mut IntoUserDefinedElements) -> result::Result, Error> { + Ok(interpreter::ExecutionParams::with_external( + externals_name.into(), + Arc::new( + interpreter::env_native_module( + self.module(externals_name).ok_or(DummyUserError)?, + externals.into_user_defined_elements() + )? + ) + )) + } +} + +#[macro_export] +macro_rules! map { + ($( $name:expr => $value:expr ),*) => ( + vec![ $( ( $name, $value ) ),* ].into_iter().collect() + ) +} diff --git a/substrate/primitives/src/contract.rs b/substrate/primitives/src/contract.rs index 33504d1b03..02411b73d7 100644 --- a/substrate/primitives/src/contract.rs +++ b/substrate/primitives/src/contract.rs @@ -26,6 +26,10 @@ pub struct CallData(#[serde(with="bytes")] pub Vec); #[derive(Debug, PartialEq, Eq, Serialize, Deserialize)] pub struct OutData(#[serde(with="bytes")] pub Vec); +/// Contract storage key. +#[derive(Debug, PartialEq, Eq, Serialize, Deserialize)] +pub struct StorageKey(#[serde(with="bytes")] pub Vec); + /// Contract storage entry data. #[derive(Debug, PartialEq, Eq, Serialize, Deserialize)] pub struct StorageData(#[serde(with="bytes")] pub Vec); diff --git a/substrate/rpc/Cargo.toml b/substrate/rpc/Cargo.toml index fb9a835834..66d11d94dd 100644 --- a/substrate/rpc/Cargo.toml +++ b/substrate/rpc/Cargo.toml @@ -13,4 +13,4 @@ polkadot-state-machine = { path = "../state_machine", version = "0.1" } [dev-dependencies] assert_matches = "1.1" -polkadot-contracts = { path = "../contracts", version = "0.1" } +polkadot-executor = { path = "../executor", version = "0.1" } diff --git a/substrate/rpc/src/lib.rs b/substrate/rpc/src/lib.rs index 762514f7b0..5061ffb56d 100644 --- a/substrate/rpc/src/lib.rs +++ b/substrate/rpc/src/lib.rs @@ -29,7 +29,7 @@ extern crate error_chain; extern crate jsonrpc_macros; #[cfg(test)] -extern crate polkadot_contracts; +extern crate polkadot_executor; #[cfg(test)] #[macro_use] extern crate assert_matches; diff --git a/substrate/rpc/src/state/mod.rs b/substrate/rpc/src/state/mod.rs index 3ba8758bd4..113fe4849b 100644 --- a/substrate/rpc/src/state/mod.rs +++ b/substrate/rpc/src/state/mod.rs @@ -22,8 +22,8 @@ mod error; mod tests; use client::{self, Client}; -use primitives::{block, Address, H256}; -use primitives::contract::{CallData, OutData, StorageData}; +use primitives::{block}; +use primitives::contract::{CallData, StorageKey, StorageData}; use state_machine; use self::error::Result; @@ -33,11 +33,11 @@ build_rpc_trait! { pub trait StateApi { /// Returns a storage entry. #[rpc(name = "state_getStorage")] - fn storage(&self, Address, H256, block::HeaderHash) -> Result; + fn storage(&self, StorageKey, block::HeaderHash) -> Result; /// Call a contract. #[rpc(name = "state_call")] - fn call(&self, Address, String, CallData, block::HeaderHash) -> Result; + fn call(&self, String, CallData, block::HeaderHash) -> Result>; } } @@ -45,11 +45,11 @@ impl StateApi for Client where B: client::Blockchain + Send + Sync + 'static, E: state_machine::CodeExecutor + Send + Sync + 'static, { - fn storage(&self, address: Address, key: H256, block: block::HeaderHash) -> Result { - Ok(self.storage(&block, &address, &key)?) + fn storage(&self, key: StorageKey, block: block::HeaderHash) -> Result { + Ok(self.storage(&block, &key)?) } - fn call(&self, address: Address, method: String, data: CallData, block: block::HeaderHash) -> Result { - Ok(self.call(&block, &address, &method, &data)?) + fn call(&self, method: String, data: CallData, block: block::HeaderHash) -> Result> { + Ok(self.call(&block, &method, &data)?.return_data) } } diff --git a/substrate/rpc/src/state/tests.rs b/substrate/rpc/src/state/tests.rs index d3e7493786..20051d4144 100644 --- a/substrate/rpc/src/state/tests.rs +++ b/substrate/rpc/src/state/tests.rs @@ -15,28 +15,29 @@ // along with Polkadot. If not, see . use super::*; -use polkadot_contracts as contracts; +use polkadot_executor as executor; use self::error::{Error, ErrorKind}; use test_helpers::Blockchain; #[test] fn should_return_storage() { - let client = Client::new(Blockchain::default(), contracts::executor()); + let client = Client::new(Blockchain::default(), executor::executor()); assert_matches!( - StateApi::storage(&client, 5.into(), 10.into(), 0.into()), + StateApi::storage(&client, StorageKey(vec![10]), 0.into()), Ok(ref x) if x.0.is_empty() ) } #[test] +#[ignore] // TODO: [ToDr] reenable once we can properly mock the wasm executor env fn should_call_contract() { // TODO [ToDr] Fix test after we are able to mock state. - let client = Client::new(Blockchain::default(), contracts::executor()); + let client = Client::new(Blockchain::default(), executor::executor()); assert_matches!( - StateApi::call(&client, 1.into(), "balanceOf".into(), CallData(vec![1,2,3]), 0.into()), + StateApi::call(&client, "balanceOf".into(), CallData(vec![1,2,3]), 0.into()), Err(Error(ErrorKind::Client(client::error::ErrorKind::Execution(_)), _)) ) } diff --git a/substrate/runtime/Cargo.lock b/substrate/runtime/Cargo.lock new file mode 100644 index 0000000000..d134139a3c --- /dev/null +++ b/substrate/runtime/Cargo.lock @@ -0,0 +1,33 @@ +[[package]] +name = "pwasm-alloc" +version = "0.1.0" +dependencies = [ + "pwasm-libc 0.1.0", +] + +[[package]] +name = "pwasm-libc" +version = "0.1.0" + +[[package]] +name = "runtime-polkadot" +version = "0.1.0" +dependencies = [ + "runtime-support 0.1.0", +] + +[[package]] +name = "runtime-support" +version = "0.1.0" +dependencies = [ + "pwasm-alloc 0.1.0", + "pwasm-libc 0.1.0", +] + +[[package]] +name = "runtime-test" +version = "0.1.0" +dependencies = [ + "runtime-support 0.1.0", +] + diff --git a/substrate/runtime/Cargo.toml b/substrate/runtime/Cargo.toml new file mode 100644 index 0000000000..0dc0216fe5 --- /dev/null +++ b/substrate/runtime/Cargo.toml @@ -0,0 +1,8 @@ +[workspace] +members = [ + "test", + "polkadot", +] + +[profile.release] +panic = "abort" diff --git a/substrate/runtime/build.sh b/substrate/runtime/build.sh new file mode 100755 index 0000000000..c3030f99d3 --- /dev/null +++ b/substrate/runtime/build.sh @@ -0,0 +1,11 @@ +#!/bin/sh + +cargo +nightly build --target=wasm32-unknown-unknown --release +dirs=`find * -maxdepth 0 -type d | grep -v pwasm- | grep -v support` +for i in $dirs +do + if [[ -e $i/Cargo.toml ]] + then + wasm-gc target/wasm32-unknown-unknown/release/runtime_$i.wasm target/wasm32-unknown-unknown/release/runtime_$i.compact.wasm + fi +done diff --git a/substrate/runtime/init.sh b/substrate/runtime/init.sh new file mode 100755 index 0000000000..02a0059a87 --- /dev/null +++ b/substrate/runtime/init.sh @@ -0,0 +1,6 @@ +#!/bin/sh + +rustup update nightly +rustup target add wasm32-unknown-unknown --toolchain nightly +rustup update stable +cargo install --git https://github.com/alexcrichton/wasm-gc diff --git a/substrate/runtime/polkadot/Cargo.toml b/substrate/runtime/polkadot/Cargo.toml new file mode 100644 index 0000000000..a313da2a1e --- /dev/null +++ b/substrate/runtime/polkadot/Cargo.toml @@ -0,0 +1,10 @@ +[package] +name = "runtime-polkadot" +version = "0.1.0" +authors = ["Parity Technologies "] + +[lib] +crate-type = ["cdylib"] + +[dependencies] +runtime-support = { path = "../support", version = "0.1" } diff --git a/substrate/runtime/polkadot/src/lib.rs b/substrate/runtime/polkadot/src/lib.rs new file mode 100644 index 0000000000..ec3fabb89c --- /dev/null +++ b/substrate/runtime/polkadot/src/lib.rs @@ -0,0 +1,36 @@ +#![no_std] +#![feature(lang_items)] +#![cfg_attr(feature = "strict", deny(warnings))] + +#![feature(alloc)] +extern crate alloc; +use alloc::vec::Vec; + +#[macro_use] +extern crate runtime_support; +use runtime_support::{set_storage, code, set_code, storage, validators, set_validators, print}; + +impl_stub!(test_data_in); +fn test_data_in(input: Vec) -> Vec { + print(b"set_storage" as &[u8]); + set_storage(b"input", &input); + + print(b"code" as &[u8]); + set_storage(b"code", &code()); + + print(b"set_code" as &[u8]); + set_code(&input); + + print(b"storage" as &[u8]); + let copy = storage(b"input"); + + print(b"validators" as &[u8]); + let mut v = validators(); + v.push(copy); + + print(b"set_validators" as &[u8]); + set_validators(&v.iter().map(Vec::as_slice).collect::>()); + + print(b"finished!" as &[u8]); + b"all ok!".to_vec() +} diff --git a/substrate/runtime/pwasm-alloc/Cargo.toml b/substrate/runtime/pwasm-alloc/Cargo.toml new file mode 100644 index 0000000000..e0a28f2c0d --- /dev/null +++ b/substrate/runtime/pwasm-alloc/Cargo.toml @@ -0,0 +1,18 @@ +[package] +name = "pwasm-alloc" +version = "0.1.0" +authors = ["Nikolay Volf "] +license = "MIT/Apache-2.0" +readme = "README.md" +repository = "https://github.com/paritytech/pwasm-std" +homepage = "https://github.com/paritytech/pwasm-std" +documentation = "https://paritytech.github.io/pwasm-std/pwasm_std/" +description = "Parity WebAssembly standard library internal allocator" +keywords = ["wasm", "parity", "webassembly", "blockchain"] +categories = ["no-std", "embedded"] + +[dependencies] +pwasm-libc = { path = "../pwasm-libc", version = "0.1" } + +[features] +strict = [] diff --git a/substrate/runtime/pwasm-alloc/README.md b/substrate/runtime/pwasm-alloc/README.md new file mode 100644 index 0000000000..489d629bc1 --- /dev/null +++ b/substrate/runtime/pwasm-alloc/README.md @@ -0,0 +1,12 @@ +# pwasm-libc + +Parity WASM contracts standard library libc bindings + +[Documentation](https://paritytech.github.io/pwasm-std/pwasm_alloc/) + +# License + +`pwasm_alloc` is primarily distributed under the terms of both the MIT +license and the Apache License (Version 2.0), at your choice. + +See LICENSE-APACHE, and LICENSE-MIT for details. diff --git a/substrate/runtime/pwasm-alloc/src/lib.rs b/substrate/runtime/pwasm-alloc/src/lib.rs new file mode 100644 index 0000000000..8bd7368205 --- /dev/null +++ b/substrate/runtime/pwasm-alloc/src/lib.rs @@ -0,0 +1,30 @@ +#![warn(missing_docs)] +#![cfg_attr(feature = "strict", deny(warnings))] +#![no_std] +#![crate_type = "rlib"] +#![feature(global_allocator)] +#![feature(alloc)] +#![feature(allocator_api)] + +//! Custom allocator crate for wasm + +extern crate alloc; +extern crate pwasm_libc; + +use alloc::heap::{Alloc, Layout, AllocErr}; + +/// Wasm allocator +pub struct WasmAllocator; + +unsafe impl<'a> Alloc for &'a WasmAllocator { + unsafe fn alloc(&mut self, layout: Layout) -> Result<*mut u8, AllocErr> { + Ok(pwasm_libc::malloc(layout.size())) + } + + unsafe fn dealloc(&mut self, ptr: *mut u8, _layout: Layout) { + pwasm_libc::free(ptr) + } +} + +#[global_allocator] +static ALLOCATOR: WasmAllocator = WasmAllocator; diff --git a/substrate/runtime/pwasm-libc/Cargo.toml b/substrate/runtime/pwasm-libc/Cargo.toml new file mode 100644 index 0000000000..d3ff1f1f32 --- /dev/null +++ b/substrate/runtime/pwasm-libc/Cargo.toml @@ -0,0 +1,15 @@ +[package] +name = "pwasm-libc" +version = "0.1.0" +authors = ["Sergey Pepyakin "] +license = "MIT/Apache-2.0" +readme = "README.md" +repository = "https://github.com/paritytech/pwasm-std" +homepage = "https://github.com/paritytech/pwasm-std" +documentation = "https://paritytech.github.io/pwasm-std/pwasm_std/" +description = "Parity WebAssembly standard library libc bindings" +keywords = ["wasm", "parity", "webassembly", "blockchain"] +categories = ["no-std", "embedded"] + +[features] +strict = [] diff --git a/substrate/runtime/pwasm-libc/README.md b/substrate/runtime/pwasm-libc/README.md new file mode 100644 index 0000000000..b01c4a7670 --- /dev/null +++ b/substrate/runtime/pwasm-libc/README.md @@ -0,0 +1,12 @@ +# pwasm-libc + +Parity WASM contracts standard library libc bindings + +[Documentation](https://paritytech.github.io/pwasm-std/pwasm_libc/) + +# License + +`pwasm-libc` is primarily distributed under the terms of both the MIT +license and the Apache License (Version 2.0), at your choice. + +See LICENSE-APACHE, and LICENSE-MIT for details. diff --git a/substrate/runtime/pwasm-libc/src/lib.rs b/substrate/runtime/pwasm-libc/src/lib.rs new file mode 100644 index 0000000000..eed597e73b --- /dev/null +++ b/substrate/runtime/pwasm-libc/src/lib.rs @@ -0,0 +1,46 @@ +#![warn(missing_docs)] +#![cfg_attr(feature = "strict", deny(warnings))] +#![no_std] + +//! libc externs crate + +extern "C" { + fn ext_memcpy(dest: *mut u8, src: *const u8, n: usize) -> *mut u8; + fn ext_memmove(dest: *mut u8, src: *const u8, n: usize) -> *mut u8; + fn ext_memset(dest: *mut u8, c: i32, n: usize) -> *mut u8; + fn ext_malloc(size: usize) -> *mut u8; + fn ext_free(ptr: *mut u8); +} + +// Declaring these function here prevents Emscripten from including it's own verisons +// into final binary. + +/// memcpy extern +#[no_mangle] +pub unsafe extern "C" fn memcpy(dest: *mut u8, src: *const u8, n: usize) -> *mut u8 { + ext_memcpy(dest, src, n) +} + +/// memmove extern +#[no_mangle] +pub unsafe extern "C" fn memmove(dest: *mut u8, src: *const u8, n: usize) -> *mut u8 { + ext_memmove(dest, src, n) +} + +/// memset extern +#[no_mangle] +pub unsafe extern "C" fn memset(dest: *mut u8, c: i32, n: usize) -> *mut u8 { + ext_memset(dest, c, n) +} + +/// malloc extern +#[no_mangle] +pub unsafe extern "C" fn malloc(size: usize) -> *mut u8 { + ext_malloc(size) +} + +/// free extern +#[no_mangle] +pub unsafe extern "C" fn free(ptr: *mut u8) { + ext_free(ptr); +} diff --git a/substrate/runtime/support/Cargo.toml b/substrate/runtime/support/Cargo.toml new file mode 100644 index 0000000000..828fdab534 --- /dev/null +++ b/substrate/runtime/support/Cargo.toml @@ -0,0 +1,11 @@ +[package] +name = "runtime-support" +version = "0.1.0" +authors = ["Parity Technologies "] + +[dependencies] +pwasm-libc = { path = "../pwasm-libc", version = "0.1" } +pwasm-alloc = { path = "../pwasm-alloc", version = "0.1" } + +[features] +strict = [] diff --git a/substrate/runtime/support/src/lib.rs b/substrate/runtime/support/src/lib.rs new file mode 100644 index 0000000000..472497d284 --- /dev/null +++ b/substrate/runtime/support/src/lib.rs @@ -0,0 +1,125 @@ +#![no_std] +#![feature(lang_items)] +#![cfg_attr(feature = "strict", deny(warnings))] + +#![feature(alloc)] + +extern crate alloc; +use alloc::vec::Vec; + +extern crate pwasm_libc; +extern crate pwasm_alloc; + +#[lang = "panic_fmt"] +#[no_mangle] +pub fn panic_fmt() -> ! { + loop {} +} + +extern "C" { + fn ext_print(utf8_data: *const u8, utf8_len: i32); + fn ext_print_num(value: u64); + fn ext_set_storage(key_data: *const u8, key_len: i32, value_data: *const u8, value_len: i32); + fn ext_get_allocated_storage(key_data: *const u8, key_len: i32, written_out: *mut i32) -> *mut u8; +} + +pub fn storage(key: &[u8]) -> Vec { + let mut length: i32 = 0; + unsafe { + let ptr = ext_get_allocated_storage(&key[0], key.len() as i32, &mut length); + Vec::from_raw_parts(ptr, length as usize, length as usize) + } +} + +pub fn set_storage(key: &[u8], value: &[u8]) { + unsafe { + ext_set_storage( + &key[0] as *const u8, key.len() as i32, + &value[0] as *const u8, value.len() as i32 + ); + } +} + +pub fn code() -> Vec { + storage(b"\0code") +} + +pub fn set_code(new: &[u8]) { + set_storage(b"\0code", new) +} + +fn value_vec(mut value: usize, initial: Vec) -> Vec { + let mut acc = initial; + while value > 0 { + acc.push(value as u8); + value /= 256; + } + acc +} + +pub fn set_validator(index: usize, validator: &[u8]) { + set_storage(&value_vec(index, b"\0validator".to_vec()), validator); +} + +pub fn validator(index: usize) -> Vec { + storage(&value_vec(index, b"\0validator".to_vec())) +} + +pub fn set_validator_count(count: usize) { + (count..validator_count()).for_each(|i| set_validator(i, &[])); + set_storage(b"\0validator_count", &value_vec(count, Vec::new())); +} + +pub fn validator_count() -> usize { + storage(b"\0validator_count").into_iter().rev().fold(0, |acc, i| (acc << 8) + (i as usize)) +} + +pub fn validators() -> Vec> { + (0..validator_count()).into_iter().map(validator).collect() +} + +pub fn set_validators(validators: &[&[u8]]) { + set_validator_count(validators.len()); + validators.iter().enumerate().for_each(|(v, i)| set_validator(v, i)); +} + +pub trait Printable { + fn print(self); +} + +impl<'a> Printable for &'a [u8] { + fn print(self) { + unsafe { + ext_print(&self[0] as *const u8, self.len() as i32); + } + } +} + +impl Printable for u64 { + fn print(self) { + unsafe { ext_print_num(self); } + } +} + +pub fn print(value: T) { + value.print(); +} + +#[macro_export] +macro_rules! impl_stub { + ($name:ident) => { + pub mod _internal { + extern crate alloc; + + #[no_mangle] + pub fn $name(input_data: *mut u8, input_len: usize) -> u64 { + let input = unsafe { + ::alloc::vec::Vec::from_raw_parts(input_data, input_len, input_len) + }; + + let output = super::$name(input); + &output[0] as *const u8 as u64 + ((output.len() as u64) << 32) + } + } + } +} diff --git a/substrate/runtime/target/wasm32-unknown-unknown/release/runtime_polkadot.compact.wasm b/substrate/runtime/target/wasm32-unknown-unknown/release/runtime_polkadot.compact.wasm new file mode 100644 index 0000000000000000000000000000000000000000..6ce8afb77ce2136b71e41d74bf115b4c8f918264 GIT binary patch literal 3042 zcmcgu&2ke*5T2Qx{n5_MY6S+%fX(b;h)F?05@J%JQst(~xH#|zRM`d#B4NueS$5zM zltU`#yhM(<=A1|10dnM+LyoDO^7SmTb#N+`OQe!|x@Wql=j)#tv3yc=P$F*As%-1!iCL#+%!_yZg_!$Gx4+ z!}0!T`$bPkp3ixHFH8z`D(LM!J9yo!Xz_}JQGYlV5ie*ne+Y|HJ37iz5hp?_p|v~^ zB95f!q+i9!UT<%I^jZl!?j4RdcecmdoBg3kJEJ48PkS%=!_A}NH~ry`$ddOy7#)1K zEh+M!=F%Uej3=b4SglRJUXUKFC{fFGC%nL@ioGPeTSp+>-CAup>ElXUW#~?+YVDK| z#w#BB*hgOR@A2g_uau8V;nliR2D)-w#l{P-R}~Cn7awEPHqid7Bg+X1!giYc%euV^ zYRQzEx)ei4mN9wK;6;`DHi=bLvVh(R%(tl=l(Kfdy`su8_O2APOc_>(=|=q)rKO&# zveiZlr(>-uqq~%!F8EVWIbGqV*v!(P6+nvCyz_EElfi8vd`{VT1Ro{%S~?&DZyH6P zGo;NQ00m$SjnAvZO2cx6(HQvHdWwD+k4r6JfDk}^)Y|^`Rmr{(4Gvf;cKEV)*$T%bsNbn+XnKxty z6^#1)k%IxR0%G2-stjDh3T?cVp>UYDgIf7w1G&vWa702m&X!DptvEi@0^uxpUm$5H5Z-KBAcz{` z>K=27HpwpkIoW5j8ykb`at0`jLm5;kgGx3jt`VZfxh}#W??ojqMY^3Vm7&UTG@uAP zHe8J0asW{ZB?nl5AixO-0<5djL~l+D;r|`^vZzpYS9KI8Y1Yb(EnK@icYf}|#rf{S zrOQ_qmwcx)Taed-|M&l;AZv?~II|#YRMbgzFZ1#37bnKhUj$d#z^7c@2?fGz+{H0K{$uato>`HDnLFGlXIkeq3t&#$-Zd()=%W*0 z&Tf0l_S%`i6m|*n08CRc+~aU5FfJ}A6_hsuI){BeY`WZe3~!#aN8_^%H&1gAt{5M) z;}ALeY5Ak&zgc3k=TRd%|r~^)0e8-?BR@9SKj6#Dd zd<2X+BA&X?UFJ*O?xH0iFKGc1oRC`xR`HrkkN_wN7EW9Shd_ju#l2*V*5(%J3dG1F z9dwpx5$x6Ggi9C#8t8_k;1=o!+zQj5AAE<;AL4tw9r*P?DVAO`xCBbOz@56ZUGCJS zoo_1mVpGo-LIvBork>B6kZ9*jfURRfK9z>9La%MegA^u&))^|n*@i>YV=gz;0cja( zgJgy{BB=>6N=(2U*99BHb-|}ZH(`v>O&AJv6FwkXXeOeyaBX}m_2jgpgcs6#yhZa4 zQ)tNijH)$1!BiEoF^zCW{B+QGl1U)RJn}_)+z(!dcNi7}MFb9ajPMZ`Zg_w3ODw#< z0Adaq6jT|R1jAA_1P=a!R}5*Re?%w*;Vztgol@7t{o%pUSdcgR+5S!sPcM`4l7_iu z(&i+0Z;1)9lFPxRV6{hvPKkOfV-P@@I{U%I`*R$V$3Fe(Jv1qLR T&JLgG3A7Ybp7PlGXgK~GM3S9O literal 0 HcmV?d00001 diff --git a/substrate/runtime/target/wasm32-unknown-unknown/release/runtime_polkadot.wasm b/substrate/runtime/target/wasm32-unknown-unknown/release/runtime_polkadot.wasm new file mode 100644 index 0000000000000000000000000000000000000000..9fe583e7e40eeab56bf09d407ddd10c4e9070248 GIT binary patch literal 3130 zcmcguPj6dA5Z~SR*Kc>9pWC!?+9uuinwAu`P@trUUNV)^sE2+4B%~y5++sI&BIhO0 z12qT4nLD3>D;F+&gnj^yJ#yfPIPsf(wH>4XS?99&W&i>}luGl>qN+E>2k?hEc z*a;?+iJV}FKQ)0Mga8C%`D#Z9^@B`<-smLleN}Hi8~1ubFuTO;^IpBZyT5<%bhqy9 zZ6DPKyA$dOI`JtZ_YA)yvpB}z!G_=>~4TlFOf3xWgk9u{Z(qfgvaeq{cfY)d< zkJ;H)VUV>%6bmV(5@9G0g@^(vI>~2gJnRh*#xIpHb?>O&-rKErxBDZ}>Wq)UKIuL0 zkG79TU-n0PB4b_l%On_he=LjGk34>Rh<4}?U$msieqLyOCuKCDu#D8k^y>tLgEb{8 zxoL$H7?qKeWcI57q`P0KEh}AAhDK?+Q>ZGmN(k)~4_)K}r}+2ya$!p4qCz;eX_bbp ztjkC{;lg!=g|Ul^FluX9|Jjkngal!`klc%^y$)(f7cq4yf{iQ^auk9WW#-x>)>Xj* zdLuC3reaXY%DVQNDvHF}LeMIu(Du`f`7KI{cxuY4HdZ(tTa_u@rEIS7hoEArVW-%f zrryef6l^%_{05@k5&#ttr5=ktPWedWcNW00!I`KH$bX3+=xMyLTDOJ_zpx9F|kdVzR&tM0|dn zjtPPu_~Wuq3L}&pK(sDpx2!J~5O68|A}B|$qAUl9V2GpEO=WWi7yb|Y+4|Q8scgnbBZ>}F8?{%Ph~eY2G`{bP#F6% zC{YHbY*IW#h#Kd*2!p&A6s(GLo2g1)WjGp81Rh&9LU1{ND20LpEI<(87z6uY3RR|4Ttu1|@N3 zK~|`!S#_`S@y#c*#n4{_SJ}WNT-^xkNtM`TbLGs%wBLlQTT+uKoE-@&7MZYVD@w8W z$8a7M+`{lK`X(}$^QS~a3-gh7NccgSB4s%)xE&z<5QUCA1N{~RH8f42k_=`)z%1u3 zjsfx?dnfnIk}Q_F!;SJ(>%3+F%)ISgqZ~@_&44+(?X8+?X9AO(705j>O~r7J!==Eu zSZ|b5)(Gev_IbDIa_2F;IctyFrCWB979d>HE@Hu3?;>o^F-fabP%(nK-zIPLHqgPK@VpRZ%&8&vKBV9XJ5)P?OTU+QKbEdhB+OOW7% z+(MwmI+q{;P!cShxC{<~@GXmbNsZQKm+1<`$fD5Otk5#ptIY{lFa$KP^+~}k)D5^5 zCOw|G17>wyxeUedS(3bVwWx-ebt)P-4WD)?ek&lh|Jn}w#H&ugD(=d_2d zqkTRVny$jGt;vDp+K1L^D!`eBL(^j})6@ZJX=;O{nm8hf_A!dJ#~jxMFNW)aPl;~A z7@?am6zC>=K(x?Q1RMU^_*#aOc}MXtq_=pB<{hTckog%^h5Q6lWx$Jt2xq`ghY(LP z2_%_EzDO7H&%eW<3=|PK+%duhT)6)I#V;}c{sM>@WH3-gFdv5VVWeXy6sjR``zO3@ zDAeKi2#_G$kf&d?)YVacba-3~ayoo^u-C(bOsA~UFt>CveQ48Not^CN_wlwp7#|7T zZ8~Sg)3Vn>x1RM!{i82>d!?XXg--A&cLy)Pyb(GQ&8olgF=loGjYb!99$O!e>c0R6 CpR&{d literal 0 HcmV?d00001 diff --git a/substrate/runtime/target/wasm32-unknown-unknown/release/runtime_test.compact.wasm b/substrate/runtime/target/wasm32-unknown-unknown/release/runtime_test.compact.wasm new file mode 100644 index 0000000000000000000000000000000000000000..6ce8afb77ce2136b71e41d74bf115b4c8f918264 GIT binary patch literal 3042 zcmcgu&2ke*5T2Qx{n5_MY6S+%fX(b;h)F?05@J%JQst(~xH#|zRM`d#B4NueS$5zM zltU`#yhM(<=A1|10dnM+LyoDO^7SmTb#N+`OQe!|x@Wql=j)#tv3yc=P$F*As%-1!iCL#+%!_yZg_!$Gx4+ z!}0!T`$bPkp3ixHFH8z`D(LM!J9yo!Xz_}JQGYlV5ie*ne+Y|HJ37iz5hp?_p|v~^ zB95f!q+i9!UT<%I^jZl!?j4RdcecmdoBg3kJEJ48PkS%=!_A}NH~ry`$ddOy7#)1K zEh+M!=F%Uej3=b4SglRJUXUKFC{fFGC%nL@ioGPeTSp+>-CAup>ElXUW#~?+YVDK| z#w#BB*hgOR@A2g_uau8V;nliR2D)-w#l{P-R}~Cn7awEPHqid7Bg+X1!giYc%euV^ zYRQzEx)ei4mN9wK;6;`DHi=bLvVh(R%(tl=l(Kfdy`su8_O2APOc_>(=|=q)rKO&# zveiZlr(>-uqq~%!F8EVWIbGqV*v!(P6+nvCyz_EElfi8vd`{VT1Ro{%S~?&DZyH6P zGo;NQ00m$SjnAvZO2cx6(HQvHdWwD+k4r6JfDk}^)Y|^`Rmr{(4Gvf;cKEV)*$T%bsNbn+XnKxty z6^#1)k%IxR0%G2-stjDh3T?cVp>UYDgIf7w1G&vWa702m&X!DptvEi@0^uxpUm$5H5Z-KBAcz{` z>K=27HpwpkIoW5j8ykb`at0`jLm5;kgGx3jt`VZfxh}#W??ojqMY^3Vm7&UTG@uAP zHe8J0asW{ZB?nl5AixO-0<5djL~l+D;r|`^vZzpYS9KI8Y1Yb(EnK@icYf}|#rf{S zrOQ_qmwcx)Taed-|M&l;AZv?~II|#YRMbgzFZ1#37bnKhUj$d#z^7c@2?fGz+{H0K{$uato>`HDnLFGlXIkeq3t&#$-Zd()=%W*0 z&Tf0l_S%`i6m|*n08CRc+~aU5FfJ}A6_hsuI){BeY`WZe3~!#aN8_^%H&1gAt{5M) z;}ALeY5Ak&zgc3k=TRd%|r~^)0e8-?BR@9SKj6#Dd zd<2X+BA&X?UFJ*O?xH0iFKGc1oRC`xR`HrkkN_wN7EW9Shd_ju#l2*V*5(%J3dG1F z9dwpx5$x6Ggi9C#8t8_k;1=o!+zQj5AAE<;AL4tw9r*P?DVAO`xCBbOz@56ZUGCJS zoo_1mVpGo-LIvBork>B6kZ9*jfURRfK9z>9La%MegA^u&))^|n*@i>YV=gz;0cja( zgJgy{BB=>6N=(2U*99BHb-|}ZH(`v>O&AJv6FwkXXeOeyaBX}m_2jgpgcs6#yhZa4 zQ)tNijH)$1!BiEoF^zCW{B+QGl1U)RJn}_)+z(!dcNi7}MFb9ajPMZ`Zg_w3ODw#< z0Adaq6jT|R1jAA_1P=a!R}5*Re?%w*;Vztgol@7t{o%pUSdcgR+5S!sPcM`4l7_iu z(&i+0Z;1)9lFPxRV6{hvPKkOfV-P@@I{U%I`*R$V$3Fe(Jv1qLR T&JLgG3A7Ybp7PlGXgK~GM3S9O literal 0 HcmV?d00001 diff --git a/substrate/runtime/target/wasm32-unknown-unknown/release/runtime_test.wasm b/substrate/runtime/target/wasm32-unknown-unknown/release/runtime_test.wasm new file mode 100644 index 0000000000000000000000000000000000000000..9fe583e7e40eeab56bf09d407ddd10c4e9070248 GIT binary patch literal 3130 zcmcguPj6dA5Z~SR*Kc>9pWC!?+9uuinwAu`P@trUUNV)^sE2+4B%~y5++sI&BIhO0 z12qT4nLD3>D;F+&gnj^yJ#yfPIPsf(wH>4XS?99&W&i>}luGl>qN+E>2k?hEc z*a;?+iJV}FKQ)0Mga8C%`D#Z9^@B`<-smLleN}Hi8~1ubFuTO;^IpBZyT5<%bhqy9 zZ6DPKyA$dOI`JtZ_YA)yvpB}z!G_=>~4TlFOf3xWgk9u{Z(qfgvaeq{cfY)d< zkJ;H)VUV>%6bmV(5@9G0g@^(vI>~2gJnRh*#xIpHb?>O&-rKErxBDZ}>Wq)UKIuL0 zkG79TU-n0PB4b_l%On_he=LjGk34>Rh<4}?U$msieqLyOCuKCDu#D8k^y>tLgEb{8 zxoL$H7?qKeWcI57q`P0KEh}AAhDK?+Q>ZGmN(k)~4_)K}r}+2ya$!p4qCz;eX_bbp ztjkC{;lg!=g|Ul^FluX9|Jjkngal!`klc%^y$)(f7cq4yf{iQ^auk9WW#-x>)>Xj* zdLuC3reaXY%DVQNDvHF}LeMIu(Du`f`7KI{cxuY4HdZ(tTa_u@rEIS7hoEArVW-%f zrryef6l^%_{05@k5&#ttr5=ktPWedWcNW00!I`KH$bX3+=xMyLTDOJ_zpx9F|kdVzR&tM0|dn zjtPPu_~Wuq3L}&pK(sDpx2!J~5O68|A}B|$qAUl9V2GpEO=WWi7yb|Y+4|Q8scgnbBZ>}F8?{%Ph~eY2G`{bP#F6% zC{YHbY*IW#h#Kd*2!p&A6s(GLo2g1)WjGp81Rh&9LU1{ND20LpEI<(87z6uY3RR|4Ttu1|@N3 zK~|`!S#_`S@y#c*#n4{_SJ}WNT-^xkNtM`TbLGs%wBLlQTT+uKoE-@&7MZYVD@w8W z$8a7M+`{lK`X(}$^QS~a3-gh7NccgSB4s%)xE&z<5QUCA1N{~RH8f42k_=`)z%1u3 zjsfx?dnfnIk}Q_F!;SJ(>%3+F%)ISgqZ~@_&44+(?X8+?X9AO(705j>O~r7J!==Eu zSZ|b5)(Gev_IbDIa_2F;IctyFrCWB979d>HE@Hu3?;>o^F-fabP%(nK-zIPLHqgPK@VpRZ%&8&vKBV9XJ5)P?OTU+QKbEdhB+OOW7% z+(MwmI+q{;P!cShxC{<~@GXmbNsZQKm+1<`$fD5Otk5#ptIY{lFa$KP^+~}k)D5^5 zCOw|G17>wyxeUedS(3bVwWx-ebt)P-4WD)?ek&lh|Jn}w#H&ugD(=d_2d zqkTRVny$jGt;vDp+K1L^D!`eBL(^j})6@ZJX=;O{nm8hf_A!dJ#~jxMFNW)aPl;~A z7@?am6zC>=K(x?Q1RMU^_*#aOc}MXtq_=pB<{hTckog%^h5Q6lWx$Jt2xq`ghY(LP z2_%_EzDO7H&%eW<3=|PK+%duhT)6)I#V;}c{sM>@WH3-gFdv5VVWeXy6sjR``zO3@ zDAeKi2#_G$kf&d?)YVacba-3~ayoo^u-C(bOsA~UFt>CveQ48Not^CN_wlwp7#|7T zZ8~Sg)3Vn>x1RM!{i82>d!?XXg--A&cLy)Pyb(GQ&8olgF=loGjYb!99$O!e>c0R6 CpR&{d literal 0 HcmV?d00001 diff --git a/substrate/runtime/test/Cargo.toml b/substrate/runtime/test/Cargo.toml new file mode 100644 index 0000000000..57015d2683 --- /dev/null +++ b/substrate/runtime/test/Cargo.toml @@ -0,0 +1,10 @@ +[package] +name = "runtime-test" +version = "0.1.0" +authors = ["Parity Technologies "] + +[lib] +crate-type = ["cdylib"] + +[dependencies] +runtime-support = { path = "../support", version = "0.1" } diff --git a/substrate/runtime/test/src/lib.rs b/substrate/runtime/test/src/lib.rs new file mode 100644 index 0000000000..ec3fabb89c --- /dev/null +++ b/substrate/runtime/test/src/lib.rs @@ -0,0 +1,36 @@ +#![no_std] +#![feature(lang_items)] +#![cfg_attr(feature = "strict", deny(warnings))] + +#![feature(alloc)] +extern crate alloc; +use alloc::vec::Vec; + +#[macro_use] +extern crate runtime_support; +use runtime_support::{set_storage, code, set_code, storage, validators, set_validators, print}; + +impl_stub!(test_data_in); +fn test_data_in(input: Vec) -> Vec { + print(b"set_storage" as &[u8]); + set_storage(b"input", &input); + + print(b"code" as &[u8]); + set_storage(b"code", &code()); + + print(b"set_code" as &[u8]); + set_code(&input); + + print(b"storage" as &[u8]); + let copy = storage(b"input"); + + print(b"validators" as &[u8]); + let mut v = validators(); + v.push(copy); + + print(b"set_validators" as &[u8]); + set_validators(&v.iter().map(Vec::as_slice).collect::>()); + + print(b"finished!" as &[u8]); + b"all ok!".to_vec() +} diff --git a/substrate/state_machine/Cargo.toml b/substrate/state_machine/Cargo.toml index cdcec9d6a0..a0d25c821c 100644 --- a/substrate/state_machine/Cargo.toml +++ b/substrate/state_machine/Cargo.toml @@ -11,3 +11,4 @@ keccak-hash = "0.1.0" patricia-trie = "0.1.0" memorydb = "0.1.1" triehash = "0.1" +byteorder = "1.1" diff --git a/substrate/state_machine/src/backend.rs b/substrate/state_machine/src/backend.rs index c7f694712e..ee6b0bbe80 100644 --- a/substrate/state_machine/src/backend.rs +++ b/substrate/state_machine/src/backend.rs @@ -17,8 +17,6 @@ //! State machine backends. These manage the code and storage of contracts. use std::{error, fmt}; - -use primitives::Address; use primitives::hash::H256; use triehash::sec_trie_root; @@ -26,8 +24,6 @@ use super::{Update, MemoryState}; /// Output of a commit. pub struct Committed { - /// Root of the code tree after changes committed. - pub code_tree_root: H256, /// Root of the storage tree after changes committed. pub storage_tree_root: H256, } @@ -38,11 +34,8 @@ pub trait Backend { /// An error type when fetching data is not possible. type Error: super::Error; - /// Get code associated with specific address. - fn code(&self, address: &Address) -> Result<&[u8], Self::Error>; - /// Get keyed storage associated with specific address. - fn storage(&self, address: &Address, key: &H256) -> Result<&[u8], Self::Error>; + fn storage(&self, key: &[u8]) -> Result<&[u8], Self::Error>; /// Commit updates to the backend and get new state. fn commit(&mut self, changes: I) -> Committed @@ -74,12 +67,8 @@ pub struct InMemory { impl Backend for InMemory { type Error = Void; - fn code(&self, address: &Address) -> Result<&[u8], Void> { - Ok(self.inner.code(address).unwrap_or(&[])) - } - - fn storage(&self, address: &Address, key: &H256) -> Result<&[u8], Void> { - Ok(self.inner.storage(address, key).unwrap_or(&[])) + fn storage(&self, key: &[u8]) -> Result<&[u8], Void> { + Ok(self.inner.storage(key).unwrap_or(&[])) } fn commit(&mut self, changes: I) -> Committed @@ -88,22 +77,13 @@ impl Backend for InMemory { self.inner.update(changes); // fully recalculate trie roots. - - let storage_roots = self.inner.storage.iter().map(|(addr, storage)| { - let flat_trie = storage.iter().map(|(k, v)| (k.to_vec(), v.clone())).collect(); - (addr.to_vec(), sec_trie_root(flat_trie).to_vec()) - }).collect(); - - let storage_tree_root = H256(sec_trie_root(storage_roots).0); - - let code_tree_root = sec_trie_root( - self.inner.code.iter().map(|(k, v)| (k.to_vec(), v.clone())).collect() - ); - - let code_tree_root = H256(code_tree_root.0); + let storage_tree_root = H256(sec_trie_root( + self.inner.storage.iter() + .map(|(k, v)| (k.to_vec(), v.clone())) + .collect() + ).0); Committed { - code_tree_root, storage_tree_root, } } diff --git a/substrate/state_machine/src/ext.rs b/substrate/state_machine/src/ext.rs index 455fc4d22e..ce7b9a0268 100644 --- a/substrate/state_machine/src/ext.rs +++ b/substrate/state_machine/src/ext.rs @@ -19,17 +19,16 @@ use std::{error, fmt}; use backend::Backend; -use primitives::Address; -use primitives::contract::{CallData, OutData}; -use primitives::hash::H256; -use {Externalities, CodeExecutor, StaticExternalities, OverlayedChanges}; +use {Externalities, OverlayedChanges}; /// Errors that can occur when interacting with the externalities. #[derive(Debug, Copy, Clone)] pub enum Error { /// Failure to load state data from the backend. + #[allow(unused)] Backend(B), /// Failure to execute a function. + #[allow(unused)] Executor(E), } @@ -52,122 +51,26 @@ impl error::Error for Error { } /// Wraps a read-only backend, call executor, and current overlayed changes. -pub struct Ext<'a, B: 'a, E: 'a> { +pub struct Ext<'a, B: 'a> { /// The overlayed changes to write to. pub overlay: &'a mut OverlayedChanges, /// The storage backend to read from. pub backend: &'a B, - /// Contract code executor. - pub exec: &'a E, - /// Contract address. - pub local: Address, } -impl<'a, B: 'a, E: 'a> StaticExternalities for Ext<'a, B, E> - where B: Backend, E: CodeExecutor +impl<'a, B: 'a> Externalities for Ext<'a, B> + where B: Backend { - type Error = Error; + type Error = B::Error; - fn storage(&self, key: &H256) -> Result<&[u8], Self::Error> { - match self.overlay.storage(&self.local, key) { + fn storage(&self, key: &[u8]) -> Result<&[u8], Self::Error> { + match self.overlay.storage(key) { Some(x) => Ok(x), - None => self.backend.storage(&self.local, key).map_err(Error::Backend) + None => self.backend.storage(key) } } - fn call_static(&self, address: &Address, method: &str, data: &CallData) -> Result { - let inner_ext = StaticExt { - backend: self.backend, - exec: self.exec, - local: address.clone(), - overlay: self.overlay, - }; - - let code = match self.overlay.code(address) { - Some(x) => x, - None => self.backend.code(address).map_err(Error::Backend)?, - }; - - self.exec.call_static( - &inner_ext, - code, - method, - data, - ).map_err(Error::Executor) - } -} - -impl<'a, B: 'a, E: 'a> Externalities for Ext<'a, B, E> - where B: Backend, E: CodeExecutor -{ - fn set_storage(&mut self, key: H256, value: Vec) { - self.overlay.set_storage(self.local, key, value); - } - - fn call(&mut self, address: &Address, method: &str, data: &CallData) -> Result { - let code = { - let code = match self.overlay.code(address) { - Some(x) => x, - None => self.backend.code(address).map_err(Error::Backend)?, - }; - - code.to_owned() - }; - - let mut inner_ext = Ext { - backend: self.backend, - exec: self.exec, - local: address.clone(), - overlay: &mut *self.overlay, - }; - - self.exec.call( - &mut inner_ext, - &code[..], - method, - data, - ).map_err(Error::Executor) - } -} - -// Static externalities -struct StaticExt<'a, B: 'a, E: 'a> { - overlay: &'a OverlayedChanges, - backend: &'a B, - exec: &'a E, - local: Address, -} - -impl<'a, B: 'a, E: 'a> StaticExternalities for StaticExt<'a, B, E> - where B: Backend, E: CodeExecutor -{ - type Error = Error; - - fn storage(&self, key: &H256) -> Result<&[u8], Self::Error> { - match self.overlay.storage(&self.local, key) { - Some(x) => Ok(x), - None => self.backend.storage(&self.local, key).map_err(Error::Backend) - } - } - - fn call_static(&self, address: &Address, method: &str, data: &CallData) -> Result { - let inner_ext = StaticExt { - backend: self.backend, - exec: self.exec, - local: address.clone(), - overlay: self.overlay, - }; - - let code = match self.overlay.code(address) { - Some(x) => x, - None => self.backend.code(address).map_err(Error::Backend)?, - }; - - self.exec.call_static( - &inner_ext, - code, - method, - data, - ).map_err(Error::Executor) + fn set_storage(&mut self, key: Vec, value: Vec) { + self.overlay.set_storage(key, value); } } diff --git a/substrate/state_machine/src/lib.rs b/substrate/state_machine/src/lib.rs index 9eb6039fa3..58ba558322 100644 --- a/substrate/state_machine/src/lib.rs +++ b/substrate/state_machine/src/lib.rs @@ -27,76 +27,45 @@ extern crate keccak_hash; extern crate patricia_trie; extern crate triehash; +extern crate byteorder; + use std::collections::HashMap; use std::fmt; -use primitives::Address; -use primitives::contract::{CallData, OutData}; -use primitives::hash::H256; +use primitives::contract::{CallData}; pub mod backend; mod ext; /// Updates to be committed to the state. pub enum Update { - /// Set storage of address at given key -- empty is deletion. - Storage(Address, H256, Vec), - /// Set code of address -- empty is deletion. - Code(Address, Vec), + /// Set storage of object at given key -- empty is deletion. + Storage(Vec, Vec), } // in-memory section of the state. #[derive(Default)] struct MemoryState { - code: HashMap>, - storage: HashMap>>, + storage: HashMap, Vec>, } impl MemoryState { - fn code(&self, address: &Address) -> Option<&[u8]> { - self.code.get(address).map(|v| &v[..]) + fn storage(&self, key: &[u8]) -> Option<&[u8]> { + self.storage.get(key).map(|v| &v[..]) } - fn storage(&self, address: &Address, key: &H256) -> Option<&[u8]> { - self.storage.get(address) - .and_then(|m| m.get(key)) - .map(|v| &v[..]) - } - - #[allow(unused)] - fn set_code(&mut self, address: Address, code: Vec) { - self.code.insert(address, code); - } - - fn set_storage(&mut self, address: Address, key: H256, val: Vec) { - self.storage.entry(address) - .or_insert_with(HashMap::new) - .insert(key, val); + fn set_storage(&mut self, key: Vec, val: Vec) { + self.storage.insert(key, val); } fn update(&mut self, changes: I) where I: IntoIterator { for update in changes { match update { - Update::Storage(addr, key, val) => { + Update::Storage(key, val) => { if val.is_empty() { - let mut empty = false; - if let Some(s) = self.storage.get_mut(&addr) { - s.remove(&key); - empty = s.is_empty(); - }; - - if empty { self.storage.remove(&addr); } + self.storage.remove(&key); } else { - self.storage.entry(addr) - .or_insert_with(HashMap::new) - .insert(key, val); - } - } - Update::Code(addr, code) => { - if code.is_empty() { - self.code.remove(&addr); - } else { - self.code.insert(addr, code); + self.storage.insert(key, val); } } } @@ -115,43 +84,27 @@ pub struct OverlayedChanges { } impl OverlayedChanges { - fn code(&self, address: &Address) -> Option<&[u8]> { - self.prospective.code(address) - .or_else(|| self.committed.code(address)) + fn storage(&self, key: &[u8]) -> Option<&[u8]> { + self.prospective.storage(key) + .or_else(|| self.committed.storage(key)) .and_then(|v| if v.is_empty() { None } else { Some(v) }) } - fn storage(&self, address: &Address, key: &H256) -> Option<&[u8]> { - self.prospective.storage(address, key) - .or_else(|| self.committed.storage(address, key)) - .and_then(|v| if v.is_empty() { None } else { Some(v) }) - } - - #[allow(unused)] - fn set_code(&mut self, address: Address, code: Vec) { - self.prospective.set_code(address, code); - } - - fn set_storage(&mut self, address: Address, key: H256, val: Vec) { - self.prospective.set_storage(address, key, val); + fn set_storage(&mut self, key: Vec, val: Vec) { + self.prospective.set_storage(key, val); } /// Discard prospective changes to state. pub fn discard_prospective(&mut self) { - self.prospective.code.clear(); self.prospective.storage.clear(); } /// Commit prospective changes to state. pub fn commit_prospective(&mut self) { - let code_updates = self.prospective.code.drain() - .map(|(addr, code)| Update::Code(addr, code)); - let storage_updates = self.prospective.storage.drain() - .flat_map(|(addr, storages)| storages.into_iter().map(move |(k, v)| (addr, k, v))) - .map(|(addr, key, value)| Update::Storage(addr, key, value)); + .map(|(key, value)| Update::Storage(key, value)); - self.committed.update(code_updates.chain(storage_updates)); + self.committed.update(storage_updates); } } @@ -161,89 +114,76 @@ impl OverlayedChanges { pub trait Error: 'static + fmt::Debug + fmt::Display + Send {} impl Error for E where E: 'static + fmt::Debug + fmt::Display + Send {} -/// Externalities: pinned to specific active address. -pub trait Externalities: StaticExternalities { - /// Read storage of current contract being called. - fn storage(&self, key: &H256) -> Result<&[u8], Self::Error> { - StaticExternalities::storage(self, key) - } - - /// Set storage of current contract being called. - fn set_storage(&mut self, key: H256, value: Vec); - - /// Make a sub-call to another contract. - fn call(&mut self, address: &Address, method: &str, data: &CallData) -> Result; - - /// Make a static (read-only) call to another contract. - fn call_static(&self, address: &Address, method: &str, data: &CallData) -> Result { - StaticExternalities::call_static(self, address, method, data) +fn value_vec(mut value: usize, initial: Vec) -> Vec { + let mut acc = initial; + while value > 0 { + acc.push(value as u8); + value /= 256; } + acc } -/// Static externalities: used only for read-only requests. -pub trait StaticExternalities { +/// Externalities: pinned to specific active address. +pub trait Externalities { /// Externalities error type. type Error: Error; /// Read storage of current contract being called. - fn storage(&self, key: &H256) -> Result<&[u8], Self::Error>; + fn storage(&self, key: &[u8]) -> Result<&[u8], Self::Error>; - /// Make a static (read-only) call to another contract. - fn call_static(&self, address: &Address, method: &str, data: &CallData) -> Result; + /// Set storage of current contract being called (effective immediately). + fn set_storage(&mut self, key: Vec, value: Vec); + + /// Get the current set of validators. + fn validators(&self) -> Result, Self::Error> { + (0..self.storage(b"\0validator_count")?.into_iter() + .rev() + .fold(0, |acc, &i| (acc << 8) + (i as usize))) + .map(|i| self.storage(&value_vec(i, b"\0validator".to_vec()))) + .collect() + } } -/// Contract code executor. +/// Code execution engine. pub trait CodeExecutor: Sized { - /// Error type for contract execution. + /// Externalities error type. type Error: Error; - /// Execute a contract in read-only mode. - /// The execution is not allowed to modify the state. - fn call_static>( - &self, - ext: &E, - code: &[u8], - method: &str, - data: &CallData, - ) -> Result; - - /// Execute a contract. - fn call>( + /// Call a given method in the runtime. + fn call( &self, ext: &mut E, code: &[u8], method: &str, data: &CallData, - ) -> Result; + ) -> Result, Self::Error>; } /// Execute a call using the given state backend, overlayed changes, and call executor. /// /// On an error, no prospective changes are written to the overlay. +/// +/// Note: changes to code will be in place if this call is made again. For running partial +/// blocks (e.g. a transaction at a time), ensure a differrent method is used. pub fn execute( backend: &B, overlay: &mut OverlayedChanges, exec: &Exec, - address: &Address, method: &str, call_data: &CallData, -) -> Result> { - let code = match overlay.code(address) { - Some(x) => x.to_owned(), - None => backend.code(address).map_err(|e| Box::new(e) as _)?.to_owned(), - }; +) -> Result, Box> { let result = { let mut externalities = ext::Ext { backend, - exec, - overlay: &mut *overlay, - local: *address, + overlay: &mut *overlay }; + // make a copy. + let code = externalities.storage(b"\0code").unwrap_or(&[]).to_vec(); exec.call( &mut externalities, - &code[..], + &code, method, call_data, ) @@ -263,59 +203,67 @@ pub fn execute( #[cfg(test)] mod tests { - use super::OverlayedChanges; - - use primitives::hash::H256; - use primitives::Address; + use std::collections::HashMap; + use super::{OverlayedChanges, Externalities}; #[test] fn overlayed_storage_works() { let mut overlayed = OverlayedChanges::default(); - let key = H256::random(); - let addr = Address::random(); + let key = vec![42, 69, 169, 142]; - assert!(overlayed.storage(&addr, &key).is_none()); + assert!(overlayed.storage(&key).is_none()); - overlayed.set_storage(addr, key, vec![1, 2, 3]); - assert_eq!(overlayed.storage(&addr, &key).unwrap(), &[1, 2, 3]); + overlayed.set_storage(key.clone(), vec![1, 2, 3]); + assert_eq!(overlayed.storage(&key).unwrap(), &[1, 2, 3]); overlayed.commit_prospective(); - assert_eq!(overlayed.storage(&addr, &key).unwrap(), &[1, 2, 3]); + assert_eq!(overlayed.storage(&key).unwrap(), &[1, 2, 3]); - overlayed.set_storage(addr, key, vec![]); - assert!(overlayed.storage(&addr, &key).is_none()); + overlayed.set_storage(key.clone(), vec![]); + assert!(overlayed.storage(&key).is_none()); overlayed.discard_prospective(); - assert_eq!(overlayed.storage(&addr, &key).unwrap(), &[1, 2, 3]); + assert_eq!(overlayed.storage(&key).unwrap(), &[1, 2, 3]); - overlayed.set_storage(addr, key, vec![]); + overlayed.set_storage(key.clone(), vec![]); overlayed.commit_prospective(); - assert!(overlayed.storage(&addr, &key).is_none()); + assert!(overlayed.storage(&key).is_none()); + } + + #[derive(Debug, Default)] + struct TestExternalities { + storage: HashMap, Vec>, + } + impl Externalities for TestExternalities { + type Error = u8; + + fn storage(&self, key: &[u8]) -> Result<&[u8], Self::Error> { + Ok(self.storage.get(&key.to_vec()).map_or(&[] as &[u8], Vec::as_slice)) + } + + fn set_storage(&mut self, key: Vec, value: Vec) { + self.storage.insert(key, value); + } } #[test] - fn overlayed_code_works() { - let mut overlayed = OverlayedChanges::default(); + fn validators_call_works() { + let mut ext = TestExternalities::default(); - let addr = Address::random(); + assert_eq!(ext.validators(), Ok(vec![])); - assert!(overlayed.code(&addr).is_none()); + ext.set_storage(b"\0validator_count".to_vec(), vec![]); + assert_eq!(ext.validators(), Ok(vec![])); - overlayed.set_code(addr, vec![1, 2, 3]); - assert_eq!(overlayed.code(&addr).unwrap(), &[1, 2, 3]); + ext.set_storage(b"\0validator_count".to_vec(), vec![1]); + assert_eq!(ext.validators(), Ok(vec![&[][..]])); - overlayed.commit_prospective(); - assert_eq!(overlayed.code(&addr).unwrap(), &[1, 2, 3]); + ext.set_storage(b"\0validator".to_vec(), b"first".to_vec()); + assert_eq!(ext.validators(), Ok(vec![&b"first"[..]])); - overlayed.set_code(addr, vec![]); - assert!(overlayed.code(&addr).is_none()); - - overlayed.discard_prospective(); - assert_eq!(overlayed.code(&addr).unwrap(), &[1, 2, 3]); - - overlayed.set_code(addr, vec![]); - overlayed.commit_prospective(); - assert!(overlayed.code(&addr).is_none()); + ext.set_storage(b"\0validator_count".to_vec(), vec![2]); + ext.set_storage(b"\0validator\x01".to_vec(), b"second".to_vec()); + assert_eq!(ext.validators(), Ok(vec![&b"first"[..], &b"second"[..]])); } }