commit aa67fe378157ef00d4cc2c2e117a4348a9ba5436 Author: Gav Date: Thu Feb 8 14:29:30 2018 +0100 Fix warning and directory restructure. diff --git a/polkadot/candidate-agreement/Cargo.toml b/polkadot/candidate-agreement/Cargo.toml new file mode 100644 index 0000000000..8aa2d0001b --- /dev/null +++ b/polkadot/candidate-agreement/Cargo.toml @@ -0,0 +1,9 @@ +[package] +name = "polkadot-candidate-agreement" +version = "0.1.0" +authors = ["Parity Technologies "] + +[dependencies] +futures = "0.1.17" +parking_lot = "0.4" +tokio-timer = "0.1.2" diff --git a/polkadot/candidate-agreement/src/bft/accumulator.rs b/polkadot/candidate-agreement/src/bft/accumulator.rs new file mode 100644 index 0000000000..ab035737fb --- /dev/null +++ b/polkadot/candidate-agreement/src/bft/accumulator.rs @@ -0,0 +1,602 @@ +// 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 authority. 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, + AuthorityId: Hash + Eq, + Signature: Eq + Clone, +{ + round_number: usize, + threshold: usize, + round_proposer: AuthorityId, + proposal: Option, + prepares: HashMap, + commits: HashMap, + vote_counts: HashMap, + advance_round: HashSet, + state: State, +} + +impl Accumulator + where + Candidate: Eq + Clone, + Digest: Hash + Eq + Clone, + AuthorityId: Hash + Eq, + Signature: Eq + Clone, +{ + /// Create a new state accumulator. + pub fn new(round_number: usize, threshold: usize, round_proposer: AuthorityId) -> 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() + } + + 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: AuthorityId, + ) { + 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: AuthorityId, + 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: AuthorityId, + 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: AuthorityId, + ) { + 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 AuthorityId(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(AuthorityId(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, AuthorityId(8)); + assert_eq!(accumulator.state(), &State::Begin); + + accumulator.import_message(LocalizedMessage { + sender: AuthorityId(5), + signature: Signature(999, 5), + message: Message::Propose(1, Candidate(999)), + }); + + assert_eq!(accumulator.state(), &State::Begin); + + accumulator.import_message(LocalizedMessage { + sender: AuthorityId(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, AuthorityId(8)); + assert_eq!(accumulator.state(), &State::Begin); + + accumulator.import_message(LocalizedMessage { + sender: AuthorityId(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: AuthorityId(i), + signature: Signature(999, i), + message: Message::Prepare(1, Digest(999)), + }); + + assert_eq!(accumulator.state(), &State::Proposed(Candidate(999))); + } + + accumulator.import_message(LocalizedMessage { + sender: AuthorityId(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, AuthorityId(8)); + assert_eq!(accumulator.state(), &State::Begin); + + accumulator.import_message(LocalizedMessage { + sender: AuthorityId(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: AuthorityId(i), + signature: Signature(999, i), + message: Message::Prepare(1, Digest(999)), + }); + + assert_eq!(accumulator.state(), &State::Proposed(Candidate(999))); + } + + accumulator.import_message(LocalizedMessage { + sender: AuthorityId(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: AuthorityId(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: AuthorityId(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, AuthorityId(8)); + assert_eq!(accumulator.state(), &State::Begin); + + accumulator.import_message(LocalizedMessage { + sender: AuthorityId(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: AuthorityId(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: AuthorityId(i), + signature: Signature(999, i), + message: Message::AdvanceRound(1), + }); + + match accumulator.state() { + &State::Prepared(_) => {}, + s => panic!("wrong state: {:?}", s), + } + } + + accumulator.import_message(LocalizedMessage { + sender: AuthorityId(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, AuthorityId(8)); + assert_eq!(accumulator.state(), &State::Begin); + + for i in 0..7 { + accumulator.import_message(LocalizedMessage { + sender: AuthorityId(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: AuthorityId(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, AuthorityId(8)); + assert_eq!(accumulator.state(), &State::Begin); + + for i in 0..7 { + accumulator.import_message(LocalizedMessage { + sender: AuthorityId(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, AuthorityId(8)); + assert_eq!(accumulator.state(), &State::Begin); + + for i in 0..7 { + accumulator.import_message(LocalizedMessage { + sender: AuthorityId(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/polkadot/candidate-agreement/src/bft/mod.rs b/polkadot/candidate-agreement/src/bft/mod.rs new file mode 100644 index 0000000000..f131e44e1f --- /dev/null +++ b/polkadot/candidate-agreement/src/bft/mod.rs @@ -0,0 +1,721 @@ +// 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; + /// Authority ID. + type AuthorityId: 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 CreateProposal: Future; + + /// Get the local authority ID. + fn local_id(&self) -> Self::AuthorityId; + + /// Get the best proposal. + fn proposal(&self) -> Self::CreateProposal; + + /// Get the digest of a candidate. + fn candidate_digest(&self, candidate: &Self::Candidate) -> Self::Digest; + + /// Sign a message using the local authority ID. + fn sign_local(&self, message: Message) + -> LocalizedMessage; + + /// Get the proposer for a given round of consensus. + fn round_proposer(&self, round: usize) -> Self::AuthorityId; + + /// 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 authorities, 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::AuthorityId, +} + +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::CreateProposal: 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::CreateProposal: 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::CreateProposal: 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 + where + C: Context, + C::RoundTimeout: Future, + C::CreateProposal: Future, + I: Stream,Error=E>, + O: Sink,SinkError=E>, + E: From, +{ + let strategy = Strategy::create(&context, nodes, max_faulty); + Agreement { + context, + input, + output, + concluded: None, + sending: Sending::with_capacity(4), + strategy: strategy, + } +} diff --git a/polkadot/candidate-agreement/src/bft/tests.rs b/polkadot/candidate-agreement/src/bft/tests.rs new file mode 100644 index 0000000000..10ef932124 --- /dev/null +++ b/polkadot/candidate-agreement/src/bft/tests.rs @@ -0,0 +1,350 @@ +// 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 tests::Network; + +use std::sync::{Arc, Mutex}; +use std::time::Duration; + +use futures::prelude::*; +use futures::sync::oneshot; +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 AuthorityId(usize); + +#[derive(Debug, PartialEq, Eq, Clone)] +struct Signature(Message, AuthorityId); + +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) -> AuthorityId { + AuthorityId(round % self.node_count) + } +} + +struct TestContext { + local_id: AuthorityId, + proposal: Mutex, + shared: Arc>, +} + +impl Context for TestContext { + type Candidate = Candidate; + type Digest = Digest; + type AuthorityId = AuthorityId; + type Signature = Signature; + type RoundTimeout = Box>; + type CreateProposal = FutureResult; + + fn local_id(&self) -> AuthorityId { + self.local_id.clone() + } + + fn proposal(&self) -> Self::CreateProposal { + 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) -> AuthorityId { + 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) + } +} + +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: AuthorityId(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: AuthorityId(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()), AuthorityId(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: AuthorityId(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/polkadot/candidate-agreement/src/handle_incoming.rs b/polkadot/candidate-agreement/src/handle_incoming.rs new file mode 100644 index 0000000000..625c950784 --- /dev/null +++ b/polkadot/candidate-agreement/src/handle_incoming.rs @@ -0,0 +1,214 @@ +// 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 . + +//! A stream that handles incoming messages to the BFT agreement module and statement +//! table. It forwards as necessary, and dispatches requests for determining availability +//! and validity of candidates as necessary. + +use std::collections::HashSet; + +use futures::prelude::*; +use futures::stream::{Fuse, FuturesUnordered}; +use futures::sync::mpsc; + +use table::{self, Statement, Context as TableContext}; + +use super::{Context, CheckedMessage, SharedTable, TypeResolve}; + +enum CheckResult { + Available, + Unavailable, + Valid, + Invalid, +} + +enum Checking { + Availability(D, A), + Validity(D, V), +} + +impl Future for Checking + where + D: Clone, + A: Future, + V: Future, +{ + type Item = (D, CheckResult); + type Error = E; + + fn poll(&mut self) -> Poll { + Ok(Async::Ready(match *self { + Checking::Availability(ref digest, ref mut f) => { + match try_ready!(f.poll()) { + true => (digest.clone(), CheckResult::Available), + false => (digest.clone(), CheckResult::Unavailable), + } + } + Checking::Validity(ref digest, ref mut f) => { + match try_ready!(f.poll()) { + true => (digest.clone(), CheckResult::Valid), + false => (digest.clone(), CheckResult::Invalid), + } + } + })) + } +} + +/// Handles incoming messages to the BFT service and statement table. +/// +/// Also triggers requests for determining validity and availability of other +/// parachain candidates. +pub struct HandleIncoming { + table: SharedTable, + messages_in: Fuse, + bft_out: mpsc::UnboundedSender<::BftCommunication>, + local_id: C::AuthorityId, + requesting_about: FuturesUnordered::Future, + ::Future, + >>, + checked_validity: HashSet, + checked_availability: HashSet, +} + +impl HandleIncoming { + fn sign_and_import_statement(&self, digest: C::Digest, result: CheckResult) { + let statement = match result { + CheckResult::Valid => Statement::Valid(digest), + CheckResult::Invalid => Statement::Invalid(digest), + CheckResult::Available => Statement::Available(digest), + CheckResult::Unavailable => return, // no such statement and not provable. + }; + + // TODO: trigger broadcast to peers immediately? + self.table.sign_and_import(statement); + } + + fn import_message(&mut self, origin: C::AuthorityId, message: CheckedMessage) { + match message { + CheckedMessage::Bft(msg) => { let _ = self.bft_out.unbounded_send(msg); } + CheckedMessage::Table(table_messages) => { + // import all table messages and check for any that we + // need to produce statements for. + let msg_iter = table_messages + .into_iter() + .map(|m| (m, Some(origin.clone()))); + let summaries: Vec<_> = self.table.import_statements(msg_iter); + + for summary in summaries { + self.dispatch_on_summary(summary) + } + } + } + } + + // on new candidates in our group, begin checking validity. + // on new candidates in our availability sphere, begin checking availability. + fn dispatch_on_summary(&mut self, summary: table::Summary) { + let is_validity_member = + self.table.context().is_member_of(&self.local_id, &summary.group_id); + + let is_availability_member = + self.table.context().is_availability_guarantor_of(&self.local_id, &summary.group_id); + + let digest = &summary.candidate; + + // TODO: consider a strategy based on the number of candidate votes as well. + let checking_validity = + is_validity_member && + self.checked_validity.insert(digest.clone()) && + self.table.proposed_digest() != Some(digest.clone()); + + let checking_availability = is_availability_member && self.checked_availability.insert(digest.clone()); + + if checking_validity || checking_availability { + let context = &*self.table.context(); + let requesting_about = &mut self.requesting_about; + self.table.with_candidate(digest, |c| match c { + None => {} // TODO: handle table inconsistency somehow? + Some(candidate) => { + if checking_validity { + let future = context.check_validity(candidate).into_future(); + let checking = Checking::Validity(digest.clone(), future); + requesting_about.push(checking); + } + + if checking_availability { + let future = context.check_availability(candidate).into_future(); + let checking = Checking::Availability(digest.clone(), future); + requesting_about.push(checking); + } + } + }) + } + } +} + +impl HandleIncoming + where + C: Context, + I: Stream),Error=E>, + C::CheckAvailability: IntoFuture, + C::CheckCandidate: IntoFuture, +{ + pub fn new( + table: SharedTable, + messages_in: I, + bft_out: mpsc::UnboundedSender<::BftCommunication>, + ) -> Self { + let local_id = table.context().local_id(); + + HandleIncoming { + table, + bft_out, + local_id, + messages_in: messages_in.fuse(), + requesting_about: FuturesUnordered::new(), + checked_validity: HashSet::new(), + checked_availability: HashSet::new(), + } + } +} + +impl Future for HandleIncoming + where + C: Context, + I: Stream),Error=E>, + C::CheckAvailability: IntoFuture, + C::CheckCandidate: IntoFuture, +{ + type Item = (); + type Error = E; + + fn poll(&mut self) -> Poll<(), E> { + loop { + // FuturesUnordered is safe to poll after it has completed. + while let Async::Ready(Some((d, r))) = self.requesting_about.poll()? { + self.sign_and_import_statement(d, r); + } + + match try_ready!(self.messages_in.poll()) { + None => if self.requesting_about.is_empty() { + return Ok(Async::Ready(())) + } else { + return Ok(Async::NotReady) + }, + Some((origin, msg)) => self.import_message(origin, msg), + } + } + } +} diff --git a/polkadot/candidate-agreement/src/lib.rs b/polkadot/candidate-agreement/src/lib.rs new file mode 100644 index 0000000000..2cf4be5c54 --- /dev/null +++ b/polkadot/candidate-agreement/src/lib.rs @@ -0,0 +1,625 @@ +// 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. +//! +//! Authorities are split into groups by parachain, and each authority might come +//! up its own candidate for their parachain. Within groups, authorities pass around +//! their candidates and produce statements of validity. +//! +//! Any candidate that receives majority approval by the authorities in a group +//! may be subject to inclusion, unless any authorities 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 authorities. + +#[macro_use] +extern crate futures; +extern crate parking_lot; +extern crate tokio_timer; + +use std::collections::{HashMap, HashSet}; +use std::fmt::Debug; +use std::hash::Hash; +use std::sync::Arc; +use std::time::Duration; + +use futures::prelude::*; +use futures::sync::{mpsc, oneshot}; +use parking_lot::Mutex; +use tokio_timer::Timer; + +use table::Table; + +mod bft; +mod handle_incoming; +mod round_robin; +mod table; + +#[cfg(test)] +pub mod tests; + +/// Context necessary for agreement. +pub trait Context: Send + Clone { + /// A authority ID + type AuthorityId: Debug + Hash + Eq + Clone + Ord; + /// The digest (hash or other unique attribute) of a candidate. + type Digest: Debug + Hash + Eq + Clone; + /// The group ID type + type GroupId: Debug + Hash + Ord + Eq + Clone; + /// A signature type. + type Signature: Debug + Eq + Clone; + /// Candidate type. In practice this will be a candidate receipt. + type ParachainCandidate: Debug + Ord + Eq + Clone; + /// The actual block proposal type. This is what is agreed upon, and + /// is composed of multiple candidates. + type Proposal: Debug + Eq + Clone; + + /// A future that resolves when a candidate is checked for validity. + /// + /// In Polkadot, this will involve fetching the corresponding block data, + /// producing the necessary ingress, and running the parachain validity function. + type CheckCandidate: IntoFuture; + + /// A future that resolves when availability of a candidate's external + /// data is checked. + type CheckAvailability: IntoFuture; + + /// The statement batch type. + type StatementBatch: StatementBatch< + Self::AuthorityId, + table::SignedStatement, + >; + + /// Get the digest of a candidate. + fn candidate_digest(candidate: &Self::ParachainCandidate) -> Self::Digest; + + /// Get the digest of a proposal. + fn proposal_digest(proposal: &Self::Proposal) -> Self::Digest; + + /// Get the group of a candidate. + fn candidate_group(candidate: &Self::ParachainCandidate) -> Self::GroupId; + + /// Get the primary for a given round. + fn round_proposer(&self, round: usize) -> Self::AuthorityId; + + /// Check a candidate for validity. + fn check_validity(&self, candidate: &Self::ParachainCandidate) -> Self::CheckCandidate; + + /// Check availability of candidate data. + fn check_availability(&self, candidate: &Self::ParachainCandidate) -> Self::CheckAvailability; + + /// Attempt to combine a set of parachain candidates into a proposal. + /// + /// This may arbitrarily return `None`, but the intent is for `Some` + /// to only be returned when candidates from enough groups are known. + /// + /// "enough" may be subjective as well. + fn create_proposal(&self, candidates: Vec<&Self::ParachainCandidate>) + -> Option; + + /// Check validity of a proposal. This should call out to the `check_candidate` + /// function for all parachain candidates contained within it, as well as + /// checking other validity constraints of the proposal. + fn proposal_valid(&self, proposal: &Self::Proposal, check_candidate: F) -> bool + where F: FnMut(&Self::ParachainCandidate) -> bool; + + /// Get the local authority ID. + fn local_id(&self) -> Self::AuthorityId; + + /// Sign a table validity statement with the local key. + fn sign_table_statement( + &self, + statement: &table::Statement + ) -> Self::Signature; + + /// Sign a BFT agreement message. + fn sign_bft_message(&self, &bft::Message) -> Self::Signature; +} + +/// Helper for type resolution for contexts until type aliases apply bounds. +pub trait TypeResolve { + type SignedTableStatement; + type BftCommunication; + type BftCommitted; + type Misbehavior; +} + +impl TypeResolve for C { + type SignedTableStatement = table::SignedStatement; + type BftCommunication = bft::Communication; + type BftCommitted = bft::Committed; + type Misbehavior = table::Misbehavior; +} + +/// Information about a specific group. +#[derive(Debug, Clone)] +pub struct GroupInfo { + /// Authorities meant to check validity of candidates. + pub validity_guarantors: HashSet, + /// Authorities meant to check availability of candidate data. + pub availability_guarantors: HashSet, + /// Number of votes needed for validity. + pub needed_validity: usize, + /// Number of votes needed for availability. + pub needed_availability: usize, +} + +struct TableContext { + context: C, + groups: HashMap>, +} + +impl ::std::ops::Deref for TableContext { + type Target = C; + + fn deref(&self) -> &C { + &self.context + } +} + +impl table::Context for TableContext { + type AuthorityId = C::AuthorityId; + type Digest = C::Digest; + type GroupId = C::GroupId; + type Signature = C::Signature; + type Candidate = C::ParachainCandidate; + + fn candidate_digest(candidate: &Self::Candidate) -> Self::Digest { + C::candidate_digest(candidate) + } + + fn candidate_group(candidate: &Self::Candidate) -> Self::GroupId { + C::candidate_group(candidate) + } + + fn is_member_of(&self, authority: &Self::AuthorityId, group: &Self::GroupId) -> bool { + self.groups.get(group).map_or(false, |g| g.validity_guarantors.contains(authority)) + } + + fn is_availability_guarantor_of(&self, authority: &Self::AuthorityId, group: &Self::GroupId) -> bool { + self.groups.get(group).map_or(false, |g| g.availability_guarantors.contains(authority)) + } + + fn requisite_votes(&self, group: &Self::GroupId) -> (usize, usize) { + self.groups.get(group).map_or( + (usize::max_value(), usize::max_value()), + |g| (g.needed_validity, g.needed_availability), + ) + } +} + +// A shared table object. +struct SharedTableInner { + table: Table>, + proposed_digest: Option, + awaiting_proposal: Vec>, +} + +impl SharedTableInner { + fn import_statement( + &mut self, + context: &TableContext, + statement: ::SignedTableStatement, + received_from: Option + ) -> Option> { + self.table.import_statement(context, statement, received_from) + } + + fn update_proposal(&mut self, context: &TableContext) { + if self.awaiting_proposal.is_empty() { return } + let proposal_candidates = self.table.proposed_candidates(context); + if let Some(proposal) = context.context.create_proposal(proposal_candidates) { + for sender in self.awaiting_proposal.drain(..) { + let _ = sender.send(proposal.clone()); + } + } + } + + fn get_proposal(&mut self, context: &TableContext) -> oneshot::Receiver { + let (tx, rx) = oneshot::channel(); + self.awaiting_proposal.push(tx); + self.update_proposal(context); + rx + } + + fn proposal_valid(&mut self, context: &TableContext, proposal: &C::Proposal) -> bool { + context.context.proposal_valid(proposal, |contained_candidate| { + // check that the candidate is valid (has enough votes) + let digest = C::candidate_digest(contained_candidate); + self.table.candidate_includable(&digest, context) + }) + } +} + +/// A shared table object. +pub struct SharedTable { + context: Arc>, + inner: Arc>>, +} + +impl Clone for SharedTable { + fn clone(&self) -> Self { + SharedTable { + context: self.context.clone(), + inner: self.inner.clone() + } + } +} + +impl SharedTable { + /// Create a new shared table. + pub fn new(context: C, groups: HashMap>) -> Self { + SharedTable { + context: Arc::new(TableContext { context, groups }), + inner: Arc::new(Mutex::new(SharedTableInner { + table: Table::default(), + awaiting_proposal: Vec::new(), + proposed_digest: None, + })) + } + } + + /// Import a single statement. + pub fn import_statement( + &self, + statement: ::SignedTableStatement, + received_from: Option, + ) -> Option> { + self.inner.lock().import_statement(&*self.context, statement, received_from) + } + + /// Sign and import a local statement. + pub fn sign_and_import( + &self, + statement: table::Statement, + ) -> Option> { + let proposed_digest = match statement { + table::Statement::Candidate(ref c) => Some(C::candidate_digest(c)), + _ => None, + }; + + let signed_statement = table::SignedStatement { + signature: self.context.sign_table_statement(&statement), + sender: self.context.local_id(), + statement, + }; + + let mut inner = self.inner.lock(); + if proposed_digest.is_some() { + inner.proposed_digest = proposed_digest; + } + + inner.import_statement(&*self.context, signed_statement, None) + } + + /// Import many statements at once. + /// + /// Provide an iterator yielding pairs of (statement, received_from). + pub fn import_statements(&self, iterable: I) -> U + where + I: IntoIterator::SignedTableStatement, Option)>, + U: ::std::iter::FromIterator>, + { + let mut inner = self.inner.lock(); + + iterable.into_iter().filter_map(move |(statement, received_from)| { + inner.import_statement(&*self.context, statement, received_from) + }).collect() + } + + /// Update the proposal sealing. + pub fn update_proposal(&self) { + self.inner.lock().update_proposal(&*self.context) + } + + /// Register interest in receiving a proposal when ready. + /// If one is ready immediately, it will be provided. + pub fn get_proposal(&self) -> oneshot::Receiver { + self.inner.lock().get_proposal(&*self.context) + } + + /// Check if a proposal is valid. + pub fn proposal_valid(&self, proposal: &C::Proposal) -> bool { + self.inner.lock().proposal_valid(&*self.context, proposal) + } + + /// Execute a closure using a specific candidate. + /// + /// Deadlocks if called recursively. + pub fn with_candidate(&self, digest: &C::Digest, f: F) -> U + where F: FnOnce(Option<&C::ParachainCandidate>) -> U + { + let inner = self.inner.lock(); + f(inner.table.get_candidate(digest)) + } + + /// Get all witnessed misbehavior. + pub fn get_misbehavior(&self) -> HashMap::Misbehavior> { + self.inner.lock().table.get_misbehavior().clone() + } + + /// Fill a statement batch. + pub fn fill_batch(&self, batch: &mut C::StatementBatch) { + self.inner.lock().table.fill_batch(batch); + } + + /// Get the local proposed candidate digest. + pub fn proposed_digest(&self) -> Option { + self.inner.lock().proposed_digest.clone() + } + + // Get a handle to the table context. + fn context(&self) -> &TableContext { + &*self.context + } +} + +/// Errors that can occur during agreement. +#[derive(Debug, Clone, Copy, PartialEq)] +pub enum Error { + IoTerminated, + FaultyTimer, + CannotPropose, +} + +impl From for Error { + fn from(_: bft::InputStreamConcluded) -> Error { + Error::IoTerminated + } +} + +/// Context owned by the BFT future necessary to execute the logic. +pub struct BftContext { + context: C, + table: SharedTable, + timer: Timer, + round_timeout_multiplier: u64, +} + +impl bft::Context for BftContext + where C::Proposal: 'static, +{ + type AuthorityId = C::AuthorityId; + type Digest = C::Digest; + type Signature = C::Signature; + type Candidate = C::Proposal; + type RoundTimeout = Box>; + type CreateProposal = Box>; + + fn local_id(&self) -> Self::AuthorityId { + self.context.local_id() + } + + fn proposal(&self) -> Self::CreateProposal { + Box::new(self.table.get_proposal().map_err(|_| Error::CannotPropose)) + } + + fn candidate_digest(&self, candidate: &Self::Candidate) -> Self::Digest { + C::proposal_digest(candidate) + } + + fn sign_local(&self, message: bft::Message) + -> bft::LocalizedMessage + { + let sender = self.local_id(); + let signature = self.context.sign_bft_message(&message); + bft::LocalizedMessage { + message, + sender, + signature, + } + } + + fn round_proposer(&self, round: usize) -> Self::AuthorityId { + self.context.round_proposer(round) + } + + fn candidate_valid(&self, proposal: &Self::Candidate) -> bool { + self.table.proposal_valid(proposal) + } + + fn begin_round_timeout(&self, round: usize) -> Self::RoundTimeout { + let round = ::std::cmp::min(63, round) as u32; + let timeout = 1u64.checked_shl(round) + .unwrap_or_else(u64::max_value) + .saturating_mul(self.round_timeout_multiplier); + + Box::new(self.timer.sleep(Duration::from_secs(timeout)) + .map_err(|_| Error::FaultyTimer)) + } +} + + +/// Parameters necessary for agreement. +pub struct AgreementParams { + /// The context itself. + pub context: C, + /// For scheduling timeouts. + pub timer: Timer, + /// The statement table. + pub table: SharedTable, + /// The number of nodes. + pub nodes: usize, + /// The maximum number of faulty nodes. + pub max_faulty: usize, + /// The round timeout multiplier: 2^round_number is multiplied by this. + pub round_timeout_multiplier: u64, + /// The maximum amount of messages to queue. + pub message_buffer_size: usize, + /// Interval to attempt forming proposals over. + pub form_proposal_interval: Duration, +} + +/// Recovery for messages +pub trait MessageRecovery { + /// The unchecked message type. This implies that work hasn't been done + /// to decode the payload and check and authenticate a signature. + type UncheckedMessage; + + /// Attempt to transform a checked message into an unchecked. + fn check_message(&self, Self::UncheckedMessage) -> Option>; +} + +/// A batch of statements to send out. +pub trait StatementBatch { + /// Get the target authorities of these statements. + fn targets(&self) -> &[V]; + + /// If the batch is empty. + fn is_empty(&self) -> bool; + + /// Push a statement onto the batch. Returns false when the batch is full. + /// + /// This is meant to do work like incrementally serializing the statements + /// into a vector of bytes while making sure the length is below a certain + /// amount. + fn push(&mut self, statement: T) -> bool; +} + +/// Recovered and fully checked messages. +pub enum CheckedMessage { + /// Messages meant for the BFT agreement logic. + Bft(::BftCommunication), + /// Statements circulating about the table. + Table(Vec<::SignedTableStatement>), +} + +/// Outgoing messages to the network. +#[derive(Debug, Clone)] +pub enum OutgoingMessage { + /// Messages meant for BFT agreement peers. + Bft(::BftCommunication), + /// Batches of table statements. + Table(C::StatementBatch), +} + +/// Create an agreement future, and I/O streams. +// TODO: kill 'static bounds and use impl Future. +pub fn agree< + Context, + NetIn, + NetOut, + Recovery, + PropagateStatements, + LocalCandidate, + Err, +>( + params: AgreementParams, + net_in: NetIn, + net_out: NetOut, + recovery: Recovery, + propagate_statements: PropagateStatements, + local_candidate: LocalCandidate, +) + -> Box::BftCommitted,Error=Error>> + where + Context: ::Context + 'static, + Context::CheckCandidate: IntoFuture, + Context::CheckAvailability: IntoFuture, + NetIn: Stream),Error=Err> + 'static, + NetOut: Sink> + 'static, + Recovery: MessageRecovery + 'static, + PropagateStatements: Stream + 'static, + LocalCandidate: IntoFuture + 'static +{ + let (bft_in_in, bft_in_out) = mpsc::unbounded(); + let (bft_out_in, bft_out_out) = mpsc::unbounded(); + + let agreement = { + let bft_context = BftContext { + context: params.context, + table: params.table.clone(), + timer: params.timer.clone(), + round_timeout_multiplier: params.round_timeout_multiplier, + }; + + bft::agree( + bft_context, + params.nodes, + params.max_faulty, + bft_in_out.map(bft::ContextCommunication).map_err(|_| Error::IoTerminated), + bft_out_in.sink_map_err(|_| Error::IoTerminated), + ) + }; + + let route_messages_in = { + let round_robin = round_robin::RoundRobinBuffer::new(net_in, params.message_buffer_size); + + let round_robin_recovered = round_robin + .filter_map(move |(sender, msg)| recovery.check_message(msg).map(move |x| (sender, x))); + + handle_incoming::HandleIncoming::new( + params.table.clone(), + round_robin_recovered, + bft_in_in, + ).map_err(|_| Error::IoTerminated) + }; + + let route_messages_out = { + let table = params.table.clone(); + let periodic_table_statements = propagate_statements + .or_else(|_| ::futures::future::empty()) // halt the stream instead of error. + .map(move |mut batch| { table.fill_batch(&mut batch); batch }) + .filter(|b| !b.is_empty()) + .map(OutgoingMessage::Table); + + let complete_out_stream = bft_out_out + .map_err(|_| Error::IoTerminated) + .map(|bft::ContextCommunication(x)| x) + .map(OutgoingMessage::Bft) + .select(periodic_table_statements); + + net_out.sink_map_err(|_| Error::IoTerminated).send_all(complete_out_stream) + }; + + let import_local_candidate = { + let table = params.table.clone(); + local_candidate + .into_future() + .map(table::Statement::Candidate) + .map(Some) + .or_else(|_| Ok(None)) + .map(move |s| if let Some(s) = s { + table.sign_and_import(s); + }) + }; + + let create_proposal_on_interval = { + let table = params.table; + params.timer.interval(params.form_proposal_interval) + .map_err(|_| Error::FaultyTimer) + .for_each(move |_| { table.update_proposal(); Ok(()) }) + }; + + // if these auxiliary futures terminate before the agreement, then + // that is an error. + let auxiliary_futures = route_messages_in.join4( + create_proposal_on_interval, + route_messages_out, + import_local_candidate, + ).and_then(|_| Err(Error::IoTerminated)); + + let future = agreement + .select(auxiliary_futures) + .map(|(committed, _)| committed) + .map_err(|(e, _)| e); + + Box::new(future) +} diff --git a/polkadot/candidate-agreement/src/round_robin.rs b/polkadot/candidate-agreement/src/round_robin.rs new file mode 100644 index 0000000000..3f98507cab --- /dev/null +++ b/polkadot/candidate-agreement/src/round_robin.rs @@ -0,0 +1,164 @@ +// 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 . + +//! Round-robin buffer for incoming messages. +//! +//! This takes batches of messages associated with a sender as input, +//! and yields messages in a fair order by sender. + +use std::collections::{Bound, BTreeMap, VecDeque}; + +use futures::prelude::*; +use futures::stream::Fuse; + +/// Implementation of the round-robin buffer for incoming messages. +#[derive(Debug)] +pub struct RoundRobinBuffer { + buffer: BTreeMap>, + last_processed_from: Option, + stored_messages: usize, + max_messages: usize, + inner: Fuse, +} + +impl RoundRobinBuffer { + /// Create a new round-robin buffer which holds up to a maximum + /// amount of messages. + pub fn new(stream: S, buffer_size: usize) -> Self { + RoundRobinBuffer { + buffer: BTreeMap::new(), + last_processed_from: None, + stored_messages: 0, + max_messages: buffer_size, + inner: stream.fuse(), + } + } +} + +impl RoundRobinBuffer { + fn next_message(&mut self) -> Option<(V, M)> { + if self.stored_messages == 0 { + return None + } + + // first pick up from the last authority we processed a message from + let mut next = { + let lower_bound = match self.last_processed_from { + None => Bound::Unbounded, + Some(ref x) => Bound::Excluded(x.clone()), + }; + + self.buffer.range_mut((lower_bound, Bound::Unbounded)) + .filter_map(|(k, v)| v.pop_front().map(|v| (k.clone(), v))) + .next() + }; + + // but wrap around to the beginning again if we got nothing. + if next.is_none() { + next = self.buffer.iter_mut() + .filter_map(|(k, v)| v.pop_front().map(|v| (k.clone(), v))) + .next(); + } + + if let Some((ref authority, _)) = next { + self.stored_messages -= 1; + self.last_processed_from = Some(authority.clone()); + } + + next + } + + // import messages, discarding when the buffer is full. + fn import_messages(&mut self, sender: V, messages: Vec) { + let space_remaining = self.max_messages - self.stored_messages; + self.stored_messages += ::std::cmp::min(space_remaining, messages.len()); + + let v = self.buffer.entry(sender).or_insert_with(VecDeque::new); + v.extend(messages.into_iter().take(space_remaining)); + } +} + +impl Stream for RoundRobinBuffer + where S: Stream)> +{ + type Item = (V, M); + type Error = S::Error; + + fn poll(&mut self) -> Poll, S::Error> { + loop { + match self.inner.poll()? { + Async::NotReady | Async::Ready(None) => break, + Async::Ready(Some((sender, msgs))) => self.import_messages(sender, msgs), + } + } + + let done = self.inner.is_done(); + Ok(match self.next_message() { + Some(msg) => Async::Ready(Some(msg)), + None => if done { Async::Ready(None) } else { Async::NotReady }, + }) + } +} + +#[cfg(test)] +mod tests { + use super::*; + use futures::stream::{self, Stream}; + + #[derive(Debug, PartialEq, Eq)] + struct UncheckedMessage { data: Vec } + + #[test] + fn is_fair_and_wraps_around() { + let stream = stream::iter_ok(vec![ + (1, vec![ + UncheckedMessage { data: vec![1, 3, 5] }, + UncheckedMessage { data: vec![3, 5, 7] }, + UncheckedMessage { data: vec![5, 7, 9] }, + ]), + (2, vec![ + UncheckedMessage { data: vec![2, 4, 6] }, + UncheckedMessage { data: vec![4, 6, 8] }, + UncheckedMessage { data: vec![6, 8, 10] }, + ]), + ]); + + let round_robin = RoundRobinBuffer::new(stream, 100); + let output = round_robin.wait().collect::, ()>>().unwrap(); + + assert_eq!(output, vec![ + (1, UncheckedMessage { data: vec![1, 3, 5] }), + (2, UncheckedMessage { data: vec![2, 4, 6] }), + (1, UncheckedMessage { data: vec![3, 5, 7] }), + + (2, UncheckedMessage { data: vec![4, 6, 8] }), + (1, UncheckedMessage { data: vec![5, 7, 9] }), + (2, UncheckedMessage { data: vec![6, 8, 10] }), + ]); + } + + #[test] + fn discards_when_full() { + let stream = stream::iter_ok(vec![ + (1, (0..200).map(|i| UncheckedMessage { data: vec![i] }).collect()) + ]); + + let round_robin = RoundRobinBuffer::new(stream, 100); + let output = round_robin.wait().collect::, ()>>().unwrap(); + + assert_eq!(output.len(), 100); + } +} diff --git a/polkadot/candidate-agreement/src/table.rs b/polkadot/candidate-agreement/src/table.rs new file mode 100644 index 0000000000..2909d219c6 --- /dev/null +++ b/polkadot/candidate-agreement/src/table.rs @@ -0,0 +1,1191 @@ +// 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 authorities 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 authorities: 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; + +use super::StatementBatch; + +/// Context for the statement table. +pub trait Context { + /// A authority ID + type AuthorityId: Debug + Hash + Eq + Clone; + /// The digest (hash or other unique attribute) of a candidate. + type Digest: Debug + Hash + Eq + Clone; + /// The group ID type + type GroupId: Debug + Hash + Ord + Eq + Clone; + /// A signature type. + type Signature: Debug + Eq + Clone; + /// Candidate type. In practice this will be a candidate receipt. + type Candidate: Debug + Ord + Eq + Clone; + + /// get the digest of a candidate. + fn candidate_digest(candidate: &Self::Candidate) -> Self::Digest; + + /// get the group of a candidate. + fn candidate_group(candidate: &Self::Candidate) -> Self::GroupId; + + /// Whether a authority is a member of a group. + /// Members are meant to submit candidates and vote on validity. + fn is_member_of(&self, authority: &Self::AuthorityId, group: &Self::GroupId) -> bool; + + /// Whether a authority 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, + authority: &Self::AuthorityId, + 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, Clone)] +pub enum Statement { + /// Broadcast by a authority to indicate that this is his candidate for + /// inclusion. + /// + /// Broadcasting two different candidate messages per round is not allowed. + Candidate(C), + /// Broadcast by a authority to attest that the candidate with given digest + /// is valid. + Valid(D), + /// Broadcast by a authority to attest that the auxiliary data for a candidate + /// with given digest is available. + Available(D), + /// Broadcast by a authority to attest that the candidate with given digest + /// is invalid. + Invalid(D), +} + +/// A signed statement. +#[derive(PartialEq, Eq, Debug, Clone)] +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 authority. +// +// We keep track of which statements we have received or sent to other authorities +// in order to prevent relaying the same data multiple times. +// +// The signature of the statement is replaced by the authority because the authority +// 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 authority. + Candidate(V), + /// A validity statement from that authority about the given digest. + Valid(V, D), + /// An invalidity statement from that authority about the given digest. + Invalid(V, D), + /// An availability statement from that authority 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, Clone)] +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, Clone)] +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, Clone)] +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, Clone)] +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() + } + + // Candidate data can be included in a proposal + // if it has enough validity and availability votes + // and no authorities 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(), + } + } +} + +// authority metadata +struct AuthorityData { + proposal: Option<(C::Digest, C::Signature)>, + known_statements: HashSet>, +} + +impl Default for AuthorityData { + fn default() -> Self { + AuthorityData { + proposal: None, + known_statements: HashSet::default(), + } + } +} + +/// Stores votes +pub struct Table { + authority_data: HashMap>, + detected_misbehavior: HashMap::Misbehavior>, + candidate_votes: HashMap>, +} + +impl Default for Table { + fn default() -> Self { + Table { + authority_data: HashMap::new(), + detected_misbehavior: HashMap::new(), + candidate_votes: HashMap::new(), + } + } +} + +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. + /// + /// The vector is sorted in ascending order by group id. + pub fn proposed_candidates<'a>(&'a self, context: &C) -> Vec<&'a C::Candidate> { + 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().cloned().collect::>() + } + + /// Whether a candidate can be included. + pub fn candidate_includable(&self, digest: &C::Digest, context: &C) -> bool { + self.candidate_votes.get(digest).map_or(false, |data| { + let (v_threshold, a_threshold) = context.requisite_votes(&data.group_id); + data.can_be_included(v_threshold, a_threshold) + }) + } + + /// Import a signed statement. Signatures should be checked for validity, and the + /// sender should be checked to actually be a authority. + /// + /// 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 + } + + /// Get a candidate by digest. + pub fn get_candidate(&self, digest: &C::Digest) -> Option<&C::Candidate> { + self.candidate_votes.get(digest).map(|d| &d.candidate) + } + + /// Access all witnessed misbehavior. + pub fn get_misbehavior(&self) + -> &HashMap::Misbehavior> + { + &self.detected_misbehavior + } + + /// Fill a statement batch and note messages seen by the targets. + pub fn fill_batch(&mut self, batch: &mut B) + where B: StatementBatch< + C::AuthorityId, + SignedStatement, + > + { + // naively iterate all statements so far, taking any that + // at least one of the targets has not seen. + + // workaround for the fact that it's inconvenient to borrow multiple + // entries out of a hashmap mutably -- we just move them out and + // replace them when we're done. + struct SwappedTargetData<'a, C: 'a + Context> { + authority_data: &'a mut HashMap>, + target_data: Vec<(C::AuthorityId, AuthorityData)>, + } + + impl<'a, C: 'a + Context> Drop for SwappedTargetData<'a, C> { + fn drop(&mut self) { + for (id, data) in self.target_data.drain(..) { + self.authority_data.insert(id, data); + } + } + } + + // pre-fetch authority data for all the targets. + let mut target_data = { + let authority_data = &mut self.authority_data; + let mut target_data = Vec::with_capacity(batch.targets().len()); + for target in batch.targets() { + let active_data = match authority_data.get_mut(target) { + None => Default::default(), + Some(x) => ::std::mem::replace(x, Default::default()), + }; + + target_data.push((target.clone(), active_data)); + } + + SwappedTargetData { + authority_data, + target_data + } + }; + + let target_data = &mut target_data.target_data; + + macro_rules! attempt_send { + ($trace:expr, sender=$sender:expr, sig=$sig:expr, statement=$statement:expr) => {{ + let trace = $trace; + let can_send = target_data.iter() + .any(|t| !t.1.known_statements.contains(&trace)); + + if can_send { + let statement = SignedStatement { + statement: $statement, + signature: $sig, + sender: $sender, + }; + + if batch.push(statement) { + for target in target_data.iter_mut() { + target.1.known_statements.insert(trace.clone()); + } + } else { + return; + } + } + }} + } + + // reconstruct statements for anything whose trace passes the filter. + for (digest, candidate) in self.candidate_votes.iter() { + let issuance_iter = candidate.validity_votes.iter() + .filter(|&(_, x)| if let ValidityVote::Issued(_) = *x { true } else { false }); + + let validity_iter = candidate.validity_votes.iter() + .filter(|&(_, x)| if let ValidityVote::Issued(_) = *x { false } else { true }); + + // send issuance statements before votes. + for (sender, vote) in issuance_iter.chain(validity_iter) { + match *vote { + ValidityVote::Issued(ref sig) => { + attempt_send!( + StatementTrace::Candidate(sender.clone()), + sender = sender.clone(), + sig = sig.clone(), + statement = Statement::Candidate(candidate.candidate.clone()) + ) + } + ValidityVote::Valid(ref sig) => { + attempt_send!( + StatementTrace::Valid(sender.clone(), digest.clone()), + sender = sender.clone(), + sig = sig.clone(), + statement = Statement::Valid(digest.clone()) + ) + } + ValidityVote::Invalid(ref sig) => { + attempt_send!( + StatementTrace::Invalid(sender.clone(), digest.clone()), + sender = sender.clone(), + sig = sig.clone(), + statement = Statement::Invalid(digest.clone()) + ) + } + } + }; + + + // and lastly send availability. + for (sender, sig) in candidate.availability_votes.iter() { + attempt_send!( + StatementTrace::Available(sender.clone(), digest.clone()), + sender = sender.clone(), + sig = sig.clone(), + statement = Statement::Available(digest.clone()) + ) + } + } + + } + + fn note_trace_seen(&mut self, trace: StatementTrace, known_by: C::AuthorityId) { + self.authority_data.entry(known_by).or_insert_with(|| AuthorityData { + proposal: None, + known_statements: HashSet::default(), + }).known_statements.insert(trace); + } + + fn import_candidate( + &mut self, + context: &C, + from: C::AuthorityId, + candidate: C::Candidate, + signature: C::Signature, + ) -> (Option<::Misbehavior>, Option>) { + let group = C::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 authority hasn't already specified another candidate. + let digest = C::candidate_digest(&candidate); + + let new_proposal = match self.authority_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 authority, 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(AuthorityData { + 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::AuthorityId, + 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 authority 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::AuthorityId, + 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 authority 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 ::tests::VecBatch; + use std::collections::HashMap; + + fn create() -> Table { + Table { + authority_data: HashMap::default(), + detected_misbehavior: HashMap::default(), + candidate_votes: HashMap::default(), + } + } + + #[derive(Debug, Copy, Clone, Hash, PartialEq, Eq)] + struct AuthorityId(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) + authorities: HashMap + } + + impl Context for TestContext { + type AuthorityId = AuthorityId; + type Digest = Digest; + type Candidate = Candidate; + type GroupId = GroupId; + type Signature = Signature; + + fn candidate_digest(candidate: &Candidate) -> Digest { + Digest(candidate.1) + } + + fn candidate_group(candidate: &Candidate) -> GroupId { + GroupId(candidate.0) + } + + fn is_member_of( + &self, + authority: &AuthorityId, + group: &GroupId + ) -> bool { + self.authorities.get(authority).map(|v| &v.0 == group).unwrap_or(false) + } + + fn is_availability_guarantor_of( + &self, + authority: &AuthorityId, + group: &GroupId + ) -> bool { + self.authorities.get(authority).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 { + authorities: { + let mut map = HashMap::new(); + map.insert(AuthorityId(1), (GroupId(2), GroupId(455))); + map + } + }; + + let mut table = create(); + let statement_a = SignedStatement { + statement: Statement::Candidate(Candidate(2, 100)), + signature: Signature(1), + sender: AuthorityId(1), + }; + + let statement_b = SignedStatement { + statement: Statement::Candidate(Candidate(2, 999)), + signature: Signature(1), + sender: AuthorityId(1), + }; + + table.import_statement(&context, statement_a, None); + assert!(!table.detected_misbehavior.contains_key(&AuthorityId(1))); + + table.import_statement(&context, statement_b, None); + assert_eq!( + table.detected_misbehavior.get(&AuthorityId(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 { + authorities: { + let mut map = HashMap::new(); + map.insert(AuthorityId(1), (GroupId(3), GroupId(455))); + map + } + }; + + let mut table = create(); + let statement = SignedStatement { + statement: Statement::Candidate(Candidate(2, 100)), + signature: Signature(1), + sender: AuthorityId(1), + }; + + table.import_statement(&context, statement, None); + + assert_eq!( + table.detected_misbehavior.get(&AuthorityId(1)).unwrap(), + &Misbehavior::UnauthorizedStatement(UnauthorizedStatement { + statement: SignedStatement { + statement: Statement::Candidate(Candidate(2, 100)), + signature: Signature(1), + sender: AuthorityId(1), + }, + }) + ); + } + + #[test] + fn unauthorized_votes() { + let context = TestContext { + authorities: { + let mut map = HashMap::new(); + map.insert(AuthorityId(1), (GroupId(2), GroupId(455))); + map.insert(AuthorityId(2), (GroupId(3), GroupId(222))); + map + } + }; + + let mut table = create(); + + let candidate_a = SignedStatement { + statement: Statement::Candidate(Candidate(2, 100)), + signature: Signature(1), + sender: AuthorityId(1), + }; + let candidate_a_digest = Digest(100); + + let candidate_b = SignedStatement { + statement: Statement::Candidate(Candidate(3, 987)), + signature: Signature(2), + sender: AuthorityId(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(&AuthorityId(1))); + assert!(!table.detected_misbehavior.contains_key(&AuthorityId(2))); + + // authority 1 votes for availability on 2's candidate. + let bad_availability_vote = SignedStatement { + statement: Statement::Available(candidate_b_digest.clone()), + signature: Signature(1), + sender: AuthorityId(1), + }; + table.import_statement(&context, bad_availability_vote, None); + + assert_eq!( + table.detected_misbehavior.get(&AuthorityId(1)).unwrap(), + &Misbehavior::UnauthorizedStatement(UnauthorizedStatement { + statement: SignedStatement { + statement: Statement::Available(candidate_b_digest), + signature: Signature(1), + sender: AuthorityId(1), + }, + }) + ); + + // authority 2 votes for validity on 1's candidate. + let bad_validity_vote = SignedStatement { + statement: Statement::Valid(candidate_a_digest.clone()), + signature: Signature(2), + sender: AuthorityId(2), + }; + table.import_statement(&context, bad_validity_vote, None); + + assert_eq!( + table.detected_misbehavior.get(&AuthorityId(2)).unwrap(), + &Misbehavior::UnauthorizedStatement(UnauthorizedStatement { + statement: SignedStatement { + statement: Statement::Valid(candidate_a_digest), + signature: Signature(2), + sender: AuthorityId(2), + }, + }) + ); + } + + #[test] + fn validity_double_vote_is_misbehavior() { + let context = TestContext { + authorities: { + let mut map = HashMap::new(); + map.insert(AuthorityId(1), (GroupId(2), GroupId(455))); + map.insert(AuthorityId(2), (GroupId(2), GroupId(246))); + map + } + }; + + let mut table = create(); + let statement = SignedStatement { + statement: Statement::Candidate(Candidate(2, 100)), + signature: Signature(1), + sender: AuthorityId(1), + }; + let candidate_digest = Digest(100); + + table.import_statement(&context, statement, None); + assert!(!table.detected_misbehavior.contains_key(&AuthorityId(1))); + + let valid_statement = SignedStatement { + statement: Statement::Valid(candidate_digest.clone()), + signature: Signature(2), + sender: AuthorityId(2), + }; + + let invalid_statement = SignedStatement { + statement: Statement::Invalid(candidate_digest.clone()), + signature: Signature(2), + sender: AuthorityId(2), + }; + + table.import_statement(&context, valid_statement, None); + assert!(!table.detected_misbehavior.contains_key(&AuthorityId(2))); + + table.import_statement(&context, invalid_statement, None); + + assert_eq!( + table.detected_misbehavior.get(&AuthorityId(2)).unwrap(), + &Misbehavior::ValidityDoubleVote(ValidityDoubleVote::ValidityAndInvalidity( + candidate_digest, + Signature(2), + Signature(2), + )) + ); + } + + #[test] + fn issue_and_vote_is_misbehavior() { + let context = TestContext { + authorities: { + let mut map = HashMap::new(); + map.insert(AuthorityId(1), (GroupId(2), GroupId(455))); + map + } + }; + + let mut table = create(); + let statement = SignedStatement { + statement: Statement::Candidate(Candidate(2, 100)), + signature: Signature(1), + sender: AuthorityId(1), + }; + let candidate_digest = Digest(100); + + table.import_statement(&context, statement, None); + assert!(!table.detected_misbehavior.contains_key(&AuthorityId(1))); + + let extra_vote = SignedStatement { + statement: Statement::Valid(candidate_digest.clone()), + signature: Signature(1), + sender: AuthorityId(1), + }; + + table.import_statement(&context, extra_vote, None); + assert_eq!( + table.detected_misbehavior.get(&AuthorityId(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(AuthorityId(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(AuthorityId(i + 255), Signature(i + 255)); + } + + assert!(candidate.can_be_included(validity_threshold, availability_threshold)); + + candidate.indicated_bad_by.push(AuthorityId(1024)); + + assert!(!candidate.can_be_included(validity_threshold, availability_threshold)); + } + + #[test] + fn candidate_import_gives_summary() { + let context = TestContext { + authorities: { + let mut map = HashMap::new(); + map.insert(AuthorityId(1), (GroupId(2), GroupId(455))); + map + } + }; + + let mut table = create(); + let statement = SignedStatement { + statement: Statement::Candidate(Candidate(2, 100)), + signature: Signature(1), + sender: AuthorityId(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 { + authorities: { + let mut map = HashMap::new(); + map.insert(AuthorityId(1), (GroupId(2), GroupId(455))); + map.insert(AuthorityId(2), (GroupId(2), GroupId(455))); + map + } + }; + + let mut table = create(); + let statement = SignedStatement { + statement: Statement::Candidate(Candidate(2, 100)), + signature: Signature(1), + sender: AuthorityId(1), + }; + let candidate_digest = Digest(100); + + table.import_statement(&context, statement, None); + assert!(!table.detected_misbehavior.contains_key(&AuthorityId(1))); + + let vote = SignedStatement { + statement: Statement::Valid(candidate_digest.clone()), + signature: Signature(2), + sender: AuthorityId(2), + }; + + let summary = table.import_statement(&context, vote, None) + .expect("candidate vote to give summary"); + + assert!(!table.detected_misbehavior.contains_key(&AuthorityId(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 { + authorities: { + let mut map = HashMap::new(); + map.insert(AuthorityId(1), (GroupId(2), GroupId(455))); + map.insert(AuthorityId(2), (GroupId(5), GroupId(2))); + map + } + }; + + let mut table = create(); + let statement = SignedStatement { + statement: Statement::Candidate(Candidate(2, 100)), + signature: Signature(1), + sender: AuthorityId(1), + }; + let candidate_digest = Digest(100); + + table.import_statement(&context, statement, None); + assert!(!table.detected_misbehavior.contains_key(&AuthorityId(1))); + + let vote = SignedStatement { + statement: Statement::Available(candidate_digest.clone()), + signature: Signature(2), + sender: AuthorityId(2), + }; + + let summary = table.import_statement(&context, vote, None) + .expect("candidate vote to give summary"); + + assert!(!table.detected_misbehavior.contains_key(&AuthorityId(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); + } + + #[test] + fn filling_batch_sets_known_flag() { + let context = TestContext { + authorities: { + let mut map = HashMap::new(); + for i in 1..10 { + map.insert(AuthorityId(i), (GroupId(2), GroupId(400 + i))); + } + map + } + }; + + let mut table = create(); + let statement = SignedStatement { + statement: Statement::Candidate(Candidate(2, 100)), + signature: Signature(1), + sender: AuthorityId(1), + }; + + table.import_statement(&context, statement, None); + + for i in 2..10 { + let statement = SignedStatement { + statement: Statement::Valid(Digest(100)), + signature: Signature(i), + sender: AuthorityId(i), + }; + + table.import_statement(&context, statement, None); + } + + let mut batch = VecBatch { + max_len: 5, + targets: (1..10).map(AuthorityId).collect(), + items: Vec::new(), + }; + + // 9 statements in the table, each seen by one. + table.fill_batch(&mut batch); + assert_eq!(batch.items.len(), 5); + + // 9 statements in the table, 5 of which seen by all targets. + batch.items.clear(); + table.fill_batch(&mut batch); + assert_eq!(batch.items.len(), 4); + + batch.items.clear(); + table.fill_batch(&mut batch); + assert!(batch.items.is_empty()); + } +} diff --git a/polkadot/candidate-agreement/src/tests/mod.rs b/polkadot/candidate-agreement/src/tests/mod.rs new file mode 100644 index 0000000000..1599a94aa6 --- /dev/null +++ b/polkadot/candidate-agreement/src/tests/mod.rs @@ -0,0 +1,385 @@ +// 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 and test helpers for the candidate agreement. + +const VALIDITY_CHECK_DELAY_MS: u64 = 100; +const AVAILABILITY_CHECK_DELAY_MS: u64 = 100; +const PROPOSAL_FORMATION_TICK_MS: u64 = 50; +const PROPAGATE_STATEMENTS_TICK_MS: u64 = 200; +const TIMER_TICK_DURATION_MS: u64 = 10; + +use std::collections::HashMap; + +use futures::prelude::*; +use futures::sync::mpsc; +use tokio_timer::Timer; + +use super::*; + +#[derive(PartialEq, Eq, PartialOrd, Ord, Debug, Hash, Clone, Copy)] +struct AuthorityId(usize); + +#[derive(PartialEq, Eq, PartialOrd, Ord, Debug, Hash, Clone)] +struct Digest(Vec); + +#[derive(PartialEq, Eq, PartialOrd, Ord, Debug, Hash, Clone)] +struct GroupId(usize); + +#[derive(PartialEq, Eq, PartialOrd, Ord, Debug, Hash, Clone)] +struct ParachainCandidate { + group: GroupId, + data: usize, +} + +#[derive(PartialEq, Eq, Debug, Clone)] +struct Proposal { + candidates: Vec, +} + +#[derive(PartialEq, Eq, Debug, Clone)] +enum Signature { + Table(AuthorityId, table::Statement), + Bft(AuthorityId, bft::Message), +} + +enum Error { + Timer(tokio_timer::TimerError), + NetOut, + NetIn, +} + +#[derive(Debug, Clone)] +struct SharedTestContext { + n_authorities: usize, + n_groups: usize, + timer: Timer, +} + +#[derive(Debug, Clone)] +struct TestContext { + shared: Arc, + local_id: AuthorityId, +} + +impl Context for TestContext { + type AuthorityId = AuthorityId; + type Digest = Digest; + type GroupId = GroupId; + type Signature = Signature; + type Proposal = Proposal; + type ParachainCandidate = ParachainCandidate; + + type CheckCandidate = Box>; + type CheckAvailability = Box>; + + type StatementBatch = VecBatch< + AuthorityId, + table::SignedStatement + >; + + fn candidate_digest(candidate: &ParachainCandidate) -> Digest { + Digest(vec![candidate.group.0, candidate.data]) + } + + fn proposal_digest(candidate: &Proposal) -> Digest { + Digest(candidate.candidates.iter().fold(Vec::new(), |mut a, c| { + a.extend(Self::candidate_digest(c).0); + a + })) + } + + fn candidate_group(candidate: &ParachainCandidate) -> GroupId { + candidate.group.clone() + } + + fn round_proposer(&self, round: usize) -> AuthorityId { + AuthorityId(round % self.shared.n_authorities) + } + + fn check_validity(&self, _candidate: &ParachainCandidate) -> Self::CheckCandidate { + let future = self.shared.timer + .sleep(::std::time::Duration::from_millis(VALIDITY_CHECK_DELAY_MS)) + .map_err(Error::Timer) + .map(|_| true); + + Box::new(future) + } + + fn check_availability(&self, _candidate: &ParachainCandidate) -> Self::CheckAvailability { + let future = self.shared.timer + .sleep(::std::time::Duration::from_millis(AVAILABILITY_CHECK_DELAY_MS)) + .map_err(Error::Timer) + .map(|_| true); + + Box::new(future) + } + + fn create_proposal(&self, candidates: Vec<&ParachainCandidate>) + -> Option + { + let t = self.shared.n_groups * 2 / 3; + if candidates.len() >= t { + Some(Proposal { + candidates: candidates.iter().map(|x| (&**x).clone()).collect() + }) + } else { + None + } + } + + fn proposal_valid(&self, proposal: &Proposal, check_candidate: F) -> bool + where F: FnMut(&ParachainCandidate) -> bool + { + if proposal.candidates.len() >= self.shared.n_groups * 2 / 3 { + proposal.candidates.iter().all(check_candidate) + } else { + false + } + } + + fn local_id(&self) -> AuthorityId { + self.local_id.clone() + } + + fn sign_table_statement( + &self, + statement: &table::Statement + ) -> Signature { + Signature::Table(self.local_id(), statement.clone()) + } + + fn sign_bft_message(&self, message: &bft::Message) -> Signature { + Signature::Bft(self.local_id(), message.clone()) + } +} + +struct TestRecovery; + +impl MessageRecovery for TestRecovery { + type UncheckedMessage = OutgoingMessage; + + fn check_message(&self, msg: Self::UncheckedMessage) -> Option> { + Some(match msg { + OutgoingMessage::Bft(c) => CheckedMessage::Bft(c), + OutgoingMessage::Table(batch) => CheckedMessage::Table(batch.items), + }) + } +} + +pub struct Network { + endpoints: Vec>, + input: mpsc::UnboundedReceiver<(usize, T)>, +} + +impl Network { + pub fn new(nodes: usize) + -> (Self, 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) + } + + pub fn route_on_thread(self) { + ::std::thread::spawn(move || { let _ = self.wait(); }); + } +} + +impl Future for Network { + type Item = (); + type Error = (); + + fn poll(&mut self) -> Poll<(), Self::Error> { + match try_ready!(self.input.poll()) { + None => 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() + } + } + } +} + +#[derive(Debug, Clone)] +pub struct VecBatch { + pub max_len: usize, + pub targets: Vec, + pub items: Vec, +} + +impl ::StatementBatch for VecBatch { + fn targets(&self) -> &[V] { &self.targets } + fn is_empty(&self) -> bool { self.items.is_empty() } + fn push(&mut self, item: T) -> bool { + if self.items.len() == self.max_len { + false + } else { + self.items.push(item); + true + } + } +} + +fn make_group_assignments(n_authorities: usize, n_groups: usize) + -> HashMap> +{ + let mut map = HashMap::new(); + let threshold = (n_authorities / n_groups) / 2; + let make_blank_group = || { + GroupInfo { + validity_guarantors: HashSet::new(), + availability_guarantors: HashSet::new(), + needed_validity: threshold, + needed_availability: threshold, + } + }; + + // every authority checks validity of his ID modulo n_groups and + // guarantees availability for the group above that. + for a_id in 0..n_authorities { + let primary_group = a_id % n_groups; + let availability_groups = [ + (a_id + 1) % n_groups, + a_id.wrapping_sub(1) % n_groups, + ]; + + map.entry(GroupId(primary_group)) + .or_insert_with(&make_blank_group) + .validity_guarantors + .insert(AuthorityId(a_id)); + + for &availability_group in &availability_groups { + map.entry(GroupId(availability_group)) + .or_insert_with(&make_blank_group) + .availability_guarantors + .insert(AuthorityId(a_id)); + } + } + + map +} + +fn make_blank_batch(n_authorities: usize) -> VecBatch { + VecBatch { + max_len: 20, + targets: (0..n_authorities).map(AuthorityId).collect(), + items: Vec::new(), + } +} + +#[test] +fn consensus_completes_with_minimum_good() { + let n = 50; + let f = 16; + let n_groups = 10; + + let timer = ::tokio_timer::wheel() + .tick_duration(Duration::from_millis(TIMER_TICK_DURATION_MS)) + .num_slots(1 << 16) + .build(); + + let (network, inputs, outputs) = Network::<(AuthorityId, OutgoingMessage)>::new(n - f); + network.route_on_thread(); + + let shared_test_context = Arc::new(SharedTestContext { + n_authorities: n, + n_groups: n_groups, + timer: timer.clone(), + }); + + let groups = make_group_assignments(n, n_groups); + + let authorities = inputs.into_iter().zip(outputs).enumerate().map(|(raw_id, (input, output))| { + let id = AuthorityId(raw_id); + let context = TestContext { + shared: shared_test_context.clone(), + local_id: id, + }; + + let shared_table = SharedTable::new(context.clone(), groups.clone()); + let params = AgreementParams { + context, + timer: timer.clone(), + table: shared_table, + nodes: n, + max_faulty: f, + round_timeout_multiplier: 4, + message_buffer_size: 100, + form_proposal_interval: Duration::from_millis(PROPOSAL_FORMATION_TICK_MS), + }; + + let net_out = input + .sink_map_err(|_| Error::NetOut) + .with(move |x| Ok::<_, Error>((id.0, (id, x))) ); + + let net_in = output + .map_err(|_| Error::NetIn) + .map(move |(v, msg)| (v, vec![msg])); + + let propagate_statements = timer + .interval(Duration::from_millis(PROPAGATE_STATEMENTS_TICK_MS)) + .map(move |()| make_blank_batch(n)) + .map_err(Error::Timer); + + let local_candidate = if raw_id < n_groups { + let candidate = ParachainCandidate { + group: GroupId(raw_id), + data: raw_id, + }; + ::futures::future::Either::A(Ok::<_, Error>(candidate).into_future()) + } else { + ::futures::future::Either::B(::futures::future::empty()) + }; + + agree::<_, _, _, _, _, _, Error>( + params, + net_in, + net_out, + TestRecovery, + propagate_statements, + local_candidate + ) + }).collect::>(); + + futures::future::join_all(authorities).wait().unwrap(); +} diff --git a/polkadot/cli/Cargo.toml b/polkadot/cli/Cargo.toml new file mode 100644 index 0000000000..4bb48abf2b --- /dev/null +++ b/polkadot/cli/Cargo.toml @@ -0,0 +1,24 @@ +[package] +name = "polkadot-cli" +version = "0.1.0" +authors = ["Parity Technologies "] +description = "Polkadot node implementation in Rust." + +[dependencies] +clap = { version = "2.27", features = ["yaml"] } +env_logger = "0.4" +error-chain = "0.11" +log = "0.3" +hex-literal = "0.1" +triehash = "0.1" +ed25519 = { path = "../../substrate/ed25519" } +substrate-client = { path = "../../substrate/client" } +substrate-codec = { path = "../../substrate/codec" } +substrate-runtime-io = { path = "../../substrate/runtime-io" } +substrate-state-machine = { path = "../../substrate/state-machine" } +substrate-executor = { path = "../../substrate/executor" } +substrate-primitives = { path = "../../substrate/primitives" } +substrate-rpc-servers = { path = "../../substrate/rpc-servers" } +polkadot-primitives = { path = "../primitives" } +polkadot-executor = { path = "../executor" } +polkadot-runtime = { path = "../runtime" } diff --git a/polkadot/cli/src/cli.yml b/polkadot/cli/src/cli.yml new file mode 100644 index 0000000000..a23ba5c235 --- /dev/null +++ b/polkadot/cli/src/cli.yml @@ -0,0 +1,14 @@ +name: polkadot +author: "Parity Team " +about: Polkadot Node Rust Implementation +args: + - log: + short: l + value_name: LOG_PATTERN + help: Sets a custom logging + takes_value: true +subcommands: + - collator: + about: Run collator node + - validator: + about: Run validator node diff --git a/polkadot/cli/src/error.rs b/polkadot/cli/src/error.rs new file mode 100644 index 0000000000..6b1e2b6024 --- /dev/null +++ b/polkadot/cli/src/error.rs @@ -0,0 +1,29 @@ +// 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 . + +//! Initialization errors. + +use client; + +error_chain! { + foreign_links { + Io(::std::io::Error) #[doc="IO error"]; + Cli(::clap::Error) #[doc="CLI error"]; + } + links { + Client(client::error::Error, client::error::ErrorKind) #[doc="Client error"]; + } +} diff --git a/polkadot/cli/src/genesis.rs b/polkadot/cli/src/genesis.rs new file mode 100644 index 0000000000..74e6ff6a3a --- /dev/null +++ b/polkadot/cli/src/genesis.rs @@ -0,0 +1,142 @@ +// Copyright 2017 Parity Technologies (UK) Ltd. +// This file is part of Substrate. + +// Substrate 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. + +// Substrate 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 Substrate. If not, see . + +//! Tool for creating the genesis block. + +use std::collections::HashMap; +use polkadot_primitives::{Block, Header}; +use triehash::trie_root; + +/// Create a genesis block, given the initial storage. +pub fn construct_genesis_block(storage: &HashMap, Vec>) -> Block { + let state_root = trie_root(storage.clone().into_iter()).0.into(); + let header = Header { + parent_hash: Default::default(), + number: 0, + state_root, + transaction_root: trie_root(vec![].into_iter()).0.into(), + digest: Default::default(), + }; + Block { + header, + transactions: vec![], + } +} + +#[cfg(test)] +mod tests { + use super::*; + use codec::{Slicable, Joiner}; + use polkadot_runtime::support::{one, two, Hashable}; + use polkadot_runtime::genesismap::{GenesisConfig, additional_storage_with_genesis}; + use state_machine::execute; + use state_machine::OverlayedChanges; + use state_machine::backend::InMemory; + use polkadot_executor::executor; + use polkadot_primitives::{AccountId, Hash, BlockNumber, Header, Digest, UncheckedTransaction, + Transaction, Function}; + use ed25519::Pair; + + fn secret_for(who: &AccountId) -> Option { + match who { + x if *x == one() => Some(Pair::from_seed(b"12345678901234567890123456789012")), + x if *x == two() => Some("9d61b19deffd5a60ba844af492ec2cc44449c5697b326919703bac031cae7f60".into()), + _ => None, + } + } + + fn construct_block(backend: &InMemory, number: BlockNumber, parent_hash: Hash, state_root: Hash, txs: Vec) -> (Vec, Hash) { + use triehash::ordered_trie_root; + + let transactions = txs.into_iter().map(|transaction| { + let signature = secret_for(&transaction.signed).unwrap() + .sign(&transaction.to_vec()); + + UncheckedTransaction { transaction, signature } + }).collect::>(); + + let transaction_root = ordered_trie_root(transactions.iter().map(Slicable::to_vec)).0.into(); + + let mut header = Header { + parent_hash, + number, + state_root, + transaction_root, + digest: Digest { logs: vec![], }, + }; + let hash = header.blake2_256(); + + let mut overlay = OverlayedChanges::default(); + + for tx in transactions.iter() { + let ret_data = execute( + backend, + &mut overlay, + &executor(), + "execute_transaction", + &vec![].join(&header).join(tx) + ).unwrap(); + header = Header::from_slice(&mut &ret_data[..]).unwrap(); + } + + let ret_data = execute( + backend, + &mut overlay, + &executor(), + "finalise_block", + &vec![].join(&header) + ).unwrap(); + header = Header::from_slice(&mut &ret_data[..]).unwrap(); + + (vec![].join(&Block { header, transactions }), hash.into()) + } + + fn block1(genesis_hash: Hash, backend: &InMemory) -> (Vec, Hash) { + construct_block( + backend, + 1, + genesis_hash, + hex!("25e5b37074063ab75c889326246640729b40d0c86932edc527bc80db0e04fe5c").into(), + vec![Transaction { + signed: one(), + nonce: 0, + function: Function::StakingTransfer(two(), 69), + }] + ) + } + + #[test] + fn construct_genesis_should_work() { + let mut storage = GenesisConfig::new_simple( + vec![one(), two()], 1000 + ).genesis_map(); + let block = construct_genesis_block(&storage); + let genesis_hash = block.header.blake2_256().into(); + storage.extend(additional_storage_with_genesis(&block).into_iter()); + + let mut overlay = OverlayedChanges::default(); + let backend = InMemory::from(storage); + let (b1data, _b1hash) = block1(genesis_hash, &backend); + + let _ = execute( + &backend, + &mut overlay, + &executor(), + "execute_block", + &b1data + ).unwrap(); + } +} diff --git a/polkadot/cli/src/lib.rs b/polkadot/cli/src/lib.rs new file mode 100644 index 0000000000..fdfe35b822 --- /dev/null +++ b/polkadot/cli/src/lib.rs @@ -0,0 +1,127 @@ +// 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 . + +//! Polkadot CLI library. + +#![warn(missing_docs)] + +extern crate env_logger; +extern crate ed25519; +extern crate triehash; +extern crate substrate_codec as codec; +extern crate substrate_state_machine as state_machine; +extern crate substrate_client as client; +extern crate substrate_primitives as primitives; +extern crate substrate_rpc_servers as rpc; +extern crate polkadot_primitives; +extern crate polkadot_executor; +extern crate polkadot_runtime; + +#[macro_use] +extern crate hex_literal; +#[macro_use] +extern crate clap; +#[macro_use] +extern crate error_chain; +#[macro_use] +extern crate log; + +mod genesis; +pub mod error; + +use codec::Slicable; +use polkadot_runtime::genesismap::{additional_storage_with_genesis, GenesisConfig}; + +/// Parse command line arguments and start the node. +/// +/// IANA unassigned port ranges that we could use: +/// 6717-6766 Unassigned +/// 8504-8553 Unassigned +/// 9556-9591 Unassigned +/// 9803-9874 Unassigned +/// 9926-9949 Unassigned +pub fn run(args: I) -> error::Result<()> where + I: IntoIterator, + T: Into + Clone, +{ + let yaml = load_yaml!("./cli.yml"); + let matches = clap::App::from_yaml(yaml).version(crate_version!()).get_matches_from_safe(args)?; + + // TODO [ToDr] Split parameters parsing from actual execution. + let log_pattern = matches.value_of("log").unwrap_or(""); + init_logger(log_pattern); + + // Create client + let executor = polkadot_executor::executor(); + let mut storage = Default::default(); + let god_key = hex!["3d866ec8a9190c8343c2fc593d21d8a6d0c5c4763aaab2349de3a6111d64d124"]; + + let genesis_config = GenesisConfig { + validators: vec![god_key.clone()], + authorities: vec![god_key.clone()], + balances: vec![(god_key.clone(), 1u64 << 63)].into_iter().collect(), + block_time: 5, // 5 second block time. + session_length: 720, // that's 1 hour per session. + sessions_per_era: 24, // 24 hours per era. + bonding_duration: 90, // 90 days per bond. + approval_ratio: 667, // 66.7% approvals required for legislation. + }; + let prepare_genesis = || { + storage = genesis_config.genesis_map(); + let block = genesis::construct_genesis_block(&storage); + storage.extend(additional_storage_with_genesis(&block)); + (primitives::block::Header::from_slice(&mut block.header.to_vec().as_ref()).expect("to_vec() always gives a valid serialisation; qed"), storage.into_iter().collect()) + }; + let client = client::new_in_mem(executor, prepare_genesis)?; + + let address = "127.0.0.1:9933".parse().unwrap(); + let handler = rpc::rpc_handler(client); + let server = rpc::start_http(&address, handler)?; + + if let Some(_) = matches.subcommand_matches("collator") { + info!("Starting collator."); + server.wait(); + return Ok(()); + } + + if let Some(_) = matches.subcommand_matches("validator") { + info!("Starting validator."); + server.wait(); + return Ok(()); + } + + println!("No command given.\n"); + let _ = clap::App::from_yaml(yaml).print_long_help(); + + Ok(()) +} + +fn init_logger(pattern: &str) { + let mut builder = env_logger::LogBuilder::new(); + // Disable info logging by default for some modules: + builder.filter(Some("hyper"), log::LogLevelFilter::Warn); + // Enable info for others. + builder.filter(None, log::LogLevelFilter::Info); + + if let Ok(lvl) = std::env::var("RUST_LOG") { + builder.parse(&lvl); + } + + builder.parse(pattern); + + + builder.init().expect("Logger initialized only once."); +} diff --git a/polkadot/collator/Cargo.toml b/polkadot/collator/Cargo.toml new file mode 100644 index 0000000000..6f1fa16302 --- /dev/null +++ b/polkadot/collator/Cargo.toml @@ -0,0 +1,10 @@ +[package] +name = "polkadot-collator" +version = "0.1.0" +authors = ["Parity Technologies "] +description = "Abstract collation logic" + +[dependencies] +futures = "0.1.17" +substrate-primitives = { path = "../../substrate/primitives", version = "0.1" } +polkadot-primitives = { path = "../primitives", version = "0.1" } diff --git a/polkadot/collator/src/lib.rs b/polkadot/collator/src/lib.rs new file mode 100644 index 0000000000..b520b269fc --- /dev/null +++ b/polkadot/collator/src/lib.rs @@ -0,0 +1,219 @@ +// 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 . + +//! Collation Logic. +//! +//! A collator node lives on a distinct parachain and submits a proposal for +//! a state transition, along with a proof for its validity +//! (what we might call a witness or block data). +//! +//! One of collators' other roles is to route messages between chains. +//! Each parachain produces a list of "egress" posts of messages for each other +//! parachain on each block, for a total of N^2 lists all together. +//! +//! We will refer to the egress list at relay chain block X of parachain A with +//! destination B as egress(X)[A -> B] +//! +//! On every block, each parachain will be intended to route messages from some +//! subset of all the other parachains. +//! +//! Since the egress information is unique to every block, when routing from a +//! parachain a collator must gather all egress posts from that parachain +//! up to the last point in history that messages were successfully routed +//! from that parachain, accounting for relay chain blocks where no candidate +//! from the collator's parachain was produced. +//! +//! In the case that all parachains route to each other and a candidate for the +//! collator's parachain was included in the last relay chain block, the collator +//! only has to gather egress posts from other parachains one block back in relay +//! chain history. +//! +//! This crate defines traits which provide context necessary for collation logic +//! to be performed, as the collation logic itself. + +extern crate futures; +extern crate substrate_primitives as primitives; +extern crate polkadot_primitives; + +use std::collections::{BTreeSet, BTreeMap}; + +use futures::{stream, Stream, Future, IntoFuture}; +use polkadot_primitives::parachain::{self, ConsolidatedIngress, Message, Id as ParaId}; + +/// Parachain context needed for collation. +/// +/// This can be implemented through an externally attached service or a stub. +pub trait ParachainContext { + /// Produce a candidate, given the latest ingress queue information. + fn produce_candidate>( + &self, + ingress: I, + ) -> (parachain::BlockData, polkadot_primitives::Signature); +} + +/// Relay chain context needed to collate. +/// This encapsulates a network and local database which may store +/// some of the input. +pub trait RelayChainContext { + type Error; + + /// Future that resolves to the un-routed egress queues of a parachain. + /// The first item is the oldest. + type FutureEgress: IntoFuture>, Error=Self::Error>; + + /// Provide a set of all parachains meant to be routed to at a block. + fn routing_parachains(&self) -> BTreeSet; + + /// Get un-routed egress queues from a parachain to the local parachain. + fn unrouted_egress(&self, id: ParaId) -> Self::FutureEgress; +} + +/// Collate the necessary ingress queue using the given context. +// TODO: impl trait +pub fn collate_ingress<'a, R>(relay_context: R) + -> Box + 'a> + where + R: RelayChainContext, + R::Error: 'a, + R::FutureEgress: 'a, +{ + let mut egress_fetch = Vec::new(); + + for routing_parachain in relay_context.routing_parachains() { + let fetch = relay_context + .unrouted_egress(routing_parachain) + .into_future() + .map(move |egresses| (routing_parachain, egresses)); + + egress_fetch.push(fetch); + } + + // create a map ordered first by the depth of the egress queue + // and then by the parachain ID. + // + // then transform that into the consolidated egress queue. + let future = stream::futures_unordered(egress_fetch) + .fold(BTreeMap::new(), |mut map, (routing_id, egresses)| { + for (depth, egress) in egresses.into_iter().rev().enumerate() { + let depth = -(depth as i64); + map.insert((depth, routing_id), egress); + } + + Ok(map) + }) + .map(|ordered| ordered.into_iter().map(|((_, id), egress)| (id, egress))) + .map(|i| i.collect::>()) + .map(ConsolidatedIngress); + + Box::new(future) +} + +/// Produce a candidate for the parachain. +pub fn collate<'a, R, P>(local_id: ParaId, relay_context: R, para_context: P) + -> Box + 'a> + where + R: RelayChainContext, + R::Error: 'a, + R::FutureEgress: 'a, + P: ParachainContext + 'a, +{ + Box::new(collate_ingress(relay_context).map(move |ingress| { + let (block_data, signature) = para_context.produce_candidate( + ingress.0.iter().flat_map(|&(id, ref msgs)| msgs.iter().cloned().map(move |msg| (id, msg))) + ); + + parachain::Candidate { + parachain_index: local_id, + collator_signature: signature, + block: block_data, + unprocessed_ingress: ingress, + } + })) +} + +#[cfg(test)] +mod tests { + use super::*; + + use std::collections::{HashMap, BTreeSet}; + + use futures::Future; + use polkadot_primitives::parachain::{Message, Id as ParaId}; + + pub struct DummyRelayChainCtx { + egresses: HashMap>>, + currently_routing: BTreeSet, + } + + impl RelayChainContext for DummyRelayChainCtx { + type Error = (); + type FutureEgress = Result>, ()>; + + fn routing_parachains(&self) -> BTreeSet { + self.currently_routing.clone() + } + + fn unrouted_egress(&self, id: ParaId) -> Result>, ()> { + Ok(self.egresses.get(&id).cloned().unwrap_or_default()) + } + } + + #[test] + fn collates_ingress() { + let route_from = |x: &[ParaId]| { + let mut set = BTreeSet::new(); + set.extend(x.iter().cloned()); + set + }; + + let message = |x: Vec| vec![Message(x)]; + + let dummy_ctx = DummyRelayChainCtx { + currently_routing: route_from(&[2.into(), 3.into()]), + egresses: vec![ + // egresses for `2`: last routed successfully 5 blocks ago. + (2.into(), vec![ + message(vec![1, 2, 3]), + message(vec![4, 5, 6]), + message(vec![7, 8]), + message(vec![10]), + message(vec![12]), + ]), + + // egresses for `3`: last routed successfully 3 blocks ago. + (3.into(), vec![ + message(vec![9]), + message(vec![11]), + message(vec![13]), + ]), + ].into_iter().collect(), + }; + + assert_eq!( + collate_ingress(dummy_ctx).wait().unwrap(), + ConsolidatedIngress(vec![ + (2.into(), message(vec![1, 2, 3])), + (2.into(), message(vec![4, 5, 6])), + (2.into(), message(vec![7, 8])), + (3.into(), message(vec![9])), + (2.into(), message(vec![10])), + (3.into(), message(vec![11])), + (2.into(), message(vec![12])), + (3.into(), message(vec![13])), + ] + )) + } +} diff --git a/polkadot/executor/Cargo.toml b/polkadot/executor/Cargo.toml new file mode 100644 index 0000000000..2c268ad51d --- /dev/null +++ b/polkadot/executor/Cargo.toml @@ -0,0 +1,17 @@ +[package] +name = "polkadot-executor" +version = "0.1.0" +authors = ["Parity Technologies "] +description = "Polkadot node implementation in Rust." + +[dependencies] +hex-literal = "0.1" +triehash = { version = "0.1" } +ed25519 = { path = "../../substrate/ed25519" } +substrate-codec = { path = "../../substrate/codec" } +substrate-runtime-io = { path = "../../substrate/runtime-io" } +substrate-state-machine = { path = "../../substrate/state-machine" } +substrate-executor = { path = "../../substrate/executor" } +substrate-primitives = { path = "../../substrate/primitives" } +polkadot-primitives = { path = "../primitives" } +polkadot-runtime = { path = "../runtime" } diff --git a/polkadot/executor/src/lib.rs b/polkadot/executor/src/lib.rs new file mode 100644 index 0000000000..1c2ec994b6 --- /dev/null +++ b/polkadot/executor/src/lib.rs @@ -0,0 +1,315 @@ +// Copyright 2017 Parity Technologies (UK) Ltd. +// This file is part of Substrate. + +// Substrate 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. + +// Substrate 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 Substrate. If not, see . + +//! A `CodeExecutor` specialisation which uses natively compiled runtime when the wasm to be +//! executed is equivalent to the natively compiled code. + +extern crate polkadot_runtime; +extern crate substrate_executor; +extern crate substrate_codec as codec; +extern crate substrate_state_machine as state_machine; +extern crate substrate_runtime_io as runtime_io; +extern crate substrate_primitives as primitives; +extern crate polkadot_primitives as polkadot_primitives; +extern crate ed25519; +extern crate triehash; +#[cfg(test)] +#[macro_use] +extern crate hex_literal; + +use polkadot_runtime as runtime; +use substrate_executor::{NativeExecutionDispatch, NativeExecutor}; + +/// A null struct which implements `NativeExecutionDispatch` feeding in the hard-coded runtime. +pub struct LocalNativeExecutionDispatch; + +impl NativeExecutionDispatch for LocalNativeExecutionDispatch { + fn native_equivalent() -> &'static [u8] { + // WARNING!!! This assumes that the runtime was built *before* the main project. Until we + // get a proper build script, this must be strictly adhered to or things will go wrong. + include_bytes!("../../runtime/wasm/target/wasm32-unknown-unknown/release/polkadot_runtime.compact.wasm") + } + + fn dispatch(method: &str, data: &[u8]) -> Option> { + runtime::dispatch(method, data) + } +} + +/// Creates new RustExecutor for contracts. +pub fn executor() -> NativeExecutor { + NativeExecutor { _dummy: ::std::marker::PhantomData } +} + +#[cfg(test)] +mod tests { + use runtime_io; + use super::*; + use substrate_executor::WasmExecutor; + use codec::{KeyedVec, Slicable, Joiner}; + use polkadot_runtime::support::{one, two, Hashable}; + use polkadot_runtime::runtime::staking::balance; + use state_machine::{CodeExecutor, TestExternalities}; + use primitives::twox_128; + use polkadot_primitives::{Hash, Header, BlockNumber, Block, Digest, Transaction, + UncheckedTransaction, Function, AccountId}; + use ed25519::Pair; + + const BLOATY_CODE: &[u8] = include_bytes!("../../runtime/wasm/target/wasm32-unknown-unknown/release/polkadot_runtime.wasm"); + const COMPACT_CODE: &[u8] = include_bytes!("../../runtime/wasm/target/wasm32-unknown-unknown/release/polkadot_runtime.compact.wasm"); + + // TODO: move into own crate. + macro_rules! map { + ($( $name:expr => $value:expr ),*) => ( + vec![ $( ( $name, $value ) ),* ].into_iter().collect() + ) + } + + fn tx() -> UncheckedTransaction { + let transaction = Transaction { + signed: one(), + nonce: 0, + function: Function::StakingTransfer(two(), 69), + }; + let signature = secret_for(&transaction.signed).unwrap() + .sign(&transaction.to_vec()); + + UncheckedTransaction { transaction, signature } + } + + #[test] + fn panic_execution_with_foreign_code_gives_error() { + let one = one(); + let mut t = TestExternalities { storage: map![ + twox_128(&one.to_keyed_vec(b"sta:bal:")).to_vec() => vec![68u8, 0, 0, 0, 0, 0, 0, 0] + ], }; + + let r = executor().call(&mut t, BLOATY_CODE, "execute_transaction", &vec![].join(&Header::from_block_number(1u64)).join(&tx())); + assert!(r.is_err()); + } + + #[test] + fn panic_execution_with_native_equivalent_code_gives_error() { + let one = one(); + let mut t = TestExternalities { storage: map![ + twox_128(&one.to_keyed_vec(b"sta:bal:")).to_vec() => vec![68u8, 0, 0, 0, 0, 0, 0, 0] + ], }; + + let r = executor().call(&mut t, COMPACT_CODE, "execute_transaction", &vec![].join(&Header::from_block_number(1u64)).join(&tx())); + assert!(r.is_err()); + } + + #[test] + fn successful_execution_with_native_equivalent_code_gives_ok() { + let one = one(); + let two = two(); + + let mut t = TestExternalities { storage: map![ + twox_128(&one.to_keyed_vec(b"sta:bal:")).to_vec() => vec![111u8, 0, 0, 0, 0, 0, 0, 0] + ], }; + + let r = executor().call(&mut t, COMPACT_CODE, "execute_transaction", &vec![].join(&Header::from_block_number(1u64)).join(&tx())); + assert!(r.is_ok()); + + runtime_io::with_externalities(&mut t, || { + assert_eq!(balance(&one), 42); + assert_eq!(balance(&two), 69); + }); + } + + #[test] + fn successful_execution_with_foreign_code_gives_ok() { + let one = one(); + let two = two(); + + let mut t = TestExternalities { storage: map![ + twox_128(&one.to_keyed_vec(b"sta:bal:")).to_vec() => vec![111u8, 0, 0, 0, 0, 0, 0, 0] + ], }; + + let r = executor().call(&mut t, BLOATY_CODE, "execute_transaction", &vec![].join(&Header::from_block_number(1u64)).join(&tx())); + assert!(r.is_ok()); + + runtime_io::with_externalities(&mut t, || { + assert_eq!(balance(&one), 42); + assert_eq!(balance(&two), 69); + }); + } + + fn new_test_ext() -> TestExternalities { + let one = one(); + let two = two(); + let three = [3u8; 32]; + + TestExternalities { storage: map![ + twox_128(&0u64.to_keyed_vec(b"sys:old:")).to_vec() => [69u8; 32].to_vec(), + twox_128(b"gov:apr").to_vec() => vec![].join(&667u32), + twox_128(b"ses:len").to_vec() => vec![].join(&2u64), + twox_128(b"ses:val:len").to_vec() => vec![].join(&3u32), + twox_128(&0u32.to_keyed_vec(b"ses:val:")).to_vec() => one.to_vec(), + twox_128(&1u32.to_keyed_vec(b"ses:val:")).to_vec() => two.to_vec(), + twox_128(&2u32.to_keyed_vec(b"ses:val:")).to_vec() => three.to_vec(), + twox_128(b"sta:wil:len").to_vec() => vec![].join(&3u32), + twox_128(&0u32.to_keyed_vec(b"sta:wil:")).to_vec() => one.to_vec(), + twox_128(&1u32.to_keyed_vec(b"sta:wil:")).to_vec() => two.to_vec(), + twox_128(&2u32.to_keyed_vec(b"sta:wil:")).to_vec() => three.to_vec(), + twox_128(b"sta:spe").to_vec() => vec![].join(&2u64), + twox_128(b"sta:vac").to_vec() => vec![].join(&3u64), + twox_128(b"sta:era").to_vec() => vec![].join(&0u64), + twox_128(&one.to_keyed_vec(b"sta:bal:")).to_vec() => vec![111u8, 0, 0, 0, 0, 0, 0, 0] + ], } + } + + fn secret_for(who: &AccountId) -> Option { + match who { + x if *x == one() => Some(Pair::from_seed(b"12345678901234567890123456789012")), + x if *x == two() => Some("9d61b19deffd5a60ba844af492ec2cc44449c5697b326919703bac031cae7f60".into()), + _ => None, + } + } + + fn construct_block(number: BlockNumber, parent_hash: Hash, state_root: Hash, txs: Vec) -> (Vec, Hash) { + use triehash::ordered_trie_root; + + let transactions = txs.into_iter().map(|transaction| { + let signature = secret_for(&transaction.signed).unwrap() + .sign(&transaction.to_vec()); + + UncheckedTransaction { transaction, signature } + }).collect::>(); + + let transaction_root = ordered_trie_root(transactions.iter().map(Slicable::to_vec)).0.into(); + + let header = Header { + parent_hash, + number, + state_root, + transaction_root, + digest: Digest { logs: vec![], }, + }; + let hash = header.blake2_256(); + + (Block { header, transactions }.to_vec(), hash.into()) + } + + fn block1() -> (Vec, Hash) { + construct_block( + 1, + [69u8; 32].into(), + hex!("2481853da20b9f4322f34650fea5f240dcbfb266d02db94bfa0153c31f4a29db").into(), + vec![Transaction { + signed: one(), + nonce: 0, + function: Function::StakingTransfer(two(), 69), + }] + ) + } + + fn block2() -> (Vec, Hash) { + construct_block( + 2, + block1().1, + hex!("1feb4d3a2e587079e6ce1685fa79994efd995e33cb289d39cded67aac1bb46a9").into(), + vec![ + Transaction { + signed: two(), + nonce: 0, + function: Function::StakingTransfer(one(), 5), + }, + Transaction { + signed: one(), + nonce: 1, + function: Function::StakingTransfer(two(), 15), + } + ] + ) + } + + #[test] + fn test_execution_works() { + let mut t = new_test_ext(); + println!("Testing Wasm..."); + let r = WasmExecutor.call(&mut t, COMPACT_CODE, "run_tests", &block2().0); + assert!(r.is_ok()); + } + + #[test] + fn full_native_block_import_works() { + let mut t = new_test_ext(); + + executor().call(&mut t, COMPACT_CODE, "execute_block", &block1().0).unwrap(); + + runtime_io::with_externalities(&mut t, || { + assert_eq!(balance(&one()), 42); + assert_eq!(balance(&two()), 69); + }); + + executor().call(&mut t, COMPACT_CODE, "execute_block", &block2().0).unwrap(); + + runtime_io::with_externalities(&mut t, || { + assert_eq!(balance(&one()), 32); + assert_eq!(balance(&two()), 79); + }); + } + + #[test] + fn full_wasm_block_import_works() { + let mut t = new_test_ext(); + + WasmExecutor.call(&mut t, COMPACT_CODE, "execute_block", &block1().0).unwrap(); + + runtime_io::with_externalities(&mut t, || { + assert_eq!(balance(&one()), 42); + assert_eq!(balance(&two()), 69); + }); + + WasmExecutor.call(&mut t, COMPACT_CODE, "execute_block", &block2().0).unwrap(); + + runtime_io::with_externalities(&mut t, || { + assert_eq!(balance(&one()), 32); + assert_eq!(balance(&two()), 79); + }); + } + + #[test] + fn panic_execution_gives_error() { + let one = one(); + let mut t = TestExternalities { storage: map![ + twox_128(&one.to_keyed_vec(b"sta:bal:")).to_vec() => vec![68u8, 0, 0, 0, 0, 0, 0, 0] + ], }; + + let foreign_code = include_bytes!("../../runtime/wasm/target/wasm32-unknown-unknown/release/polkadot_runtime.wasm"); + let r = WasmExecutor.call(&mut t, &foreign_code[..], "execute_transaction", &vec![].join(&Header::from_block_number(1u64)).join(&tx())); + assert!(r.is_err()); + } + + #[test] + fn successful_execution_gives_ok() { + let one = one(); + let two = two(); + + let mut t = TestExternalities { storage: map![ + twox_128(&one.to_keyed_vec(b"sta:bal:")).to_vec() => vec![111u8, 0, 0, 0, 0, 0, 0, 0] + ], }; + + let foreign_code = include_bytes!("../../runtime/wasm/target/wasm32-unknown-unknown/release/polkadot_runtime.compact.wasm"); + let r = WasmExecutor.call(&mut t, &foreign_code[..], "execute_transaction", &vec![].join(&Header::from_block_number(1u64)).join(&tx())); + assert!(r.is_ok()); + + runtime_io::with_externalities(&mut t, || { + assert_eq!(balance(&one), 42); + assert_eq!(balance(&two), 69); + }); + } +} diff --git a/polkadot/network/Cargo.toml b/polkadot/network/Cargo.toml new file mode 100644 index 0000000000..e016be7d30 --- /dev/null +++ b/polkadot/network/Cargo.toml @@ -0,0 +1,30 @@ +[package] +description = "Polkadot network protocol" +name = "polkadot-network" +version = "0.1.0" +license = "GPL-3.0" +authors = ["Parity Technologies "] + +[lib] + +[dependencies] +log = "0.3" +env_logger = "0.4" +rand = "0.3" +heapsize = "0.4" +semver = "0.6" +smallvec = { version = "0.4", features = ["heapsizeof"] } +parking_lot = "0.4" +ipnetwork = "0.12" +error-chain = "0.11" +bitflags = "1.0" +serde = "1.0" +serde_derive = "1.0" +serde_json = "1.0" +ethcore-network = { git = "https://github.com/paritytech/parity.git" } +ethcore-io = { git = "https://github.com/paritytech/parity.git" } +substrate-primitives = { path = "../../substrate/primitives" } +substrate-client = { path = "../../substrate/client" } +substrate-state-machine = { path = "../../substrate/state-machine" } +substrate-serializer = { path = "../../substrate/serializer" } +polkadot-primitives = { path = "../primitives" } diff --git a/polkadot/network/src/blocks.rs b/polkadot/network/src/blocks.rs new file mode 100644 index 0000000000..8d0118222b --- /dev/null +++ b/polkadot/network/src/blocks.rs @@ -0,0 +1,262 @@ +// 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 std::mem; +use std::cmp; +use std::ops::Range; +use std::collections::{HashMap, BTreeMap}; +use std::collections::hash_map::Entry; +use network::PeerId; +use primitives::block::Number as BlockNumber; +use message; + +const MAX_PARALLEL_DOWNLOADS: u32 = 1; + +/// Block data with origin. +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct BlockData { + pub block: message::BlockData, + pub origin: PeerId, +} + +#[derive(Debug)] +enum BlockRangeState { + Downloading { + len: BlockNumber, + downloading: u32, + }, + Complete(Vec), +} + +impl BlockRangeState { + pub fn len(&self) -> BlockNumber { + match *self { + BlockRangeState::Downloading { len, .. } => len, + BlockRangeState::Complete(ref blocks) => blocks.len() as BlockNumber, + } + } +} + +/// A collection of blocks being downloaded. +#[derive(Default)] +pub struct BlockCollection { + /// Downloaded blocks. + blocks: BTreeMap, + peer_requests: HashMap, +} + +impl BlockCollection { + /// Create a new instance. + pub fn new() -> BlockCollection { + BlockCollection { + blocks: BTreeMap::new(), + peer_requests: HashMap::new(), + } + } + + /// Clear everything. + pub fn clear(&mut self) { + self.blocks.clear(); + self.peer_requests.clear(); + } + + /// Insert a set of blocks into collection. + pub fn insert(&mut self, start: BlockNumber, blocks: Vec, peer_id: PeerId) { + if blocks.is_empty() { + return; + } + + match self.blocks.get(&start) { + Some(&BlockRangeState::Downloading { .. }) => { + trace!(target: "sync", "Ignored block data still marked as being downloaded: {}", start); + debug_assert!(false); + return; + }, + Some(&BlockRangeState::Complete(ref existing)) if existing.len() >= blocks.len() => { + trace!(target: "sync", "Ignored block data already downloaded: {}", start); + return; + }, + _ => (), + } + + self.blocks.insert(start, BlockRangeState::Complete(blocks.into_iter().map(|b| BlockData { origin: peer_id, block: b }).collect())); + } + + /// Returns a set of block hashes that require a header download. The returned set is marked as being downloaded. + pub fn needed_blocks(&mut self, peer_id: PeerId, count: usize, peer_best: BlockNumber, common: BlockNumber) -> Option> { + // First block number that we need to download + let first_different = common + 1; + let count = count as BlockNumber; + let (mut range, downloading) = { + let mut downloading_iter = self.blocks.iter().peekable(); + let mut prev: Option<(&BlockNumber, &BlockRangeState)> = None; + loop { + let next = downloading_iter.next(); + break match &(prev, next) { + &(Some((start, &BlockRangeState::Downloading { ref len, downloading })), _) if downloading < MAX_PARALLEL_DOWNLOADS => + (*start .. *start + *len, downloading), + &(Some((start, r)), Some((next_start, _))) if start + r.len() < *next_start => + (*start + r.len() .. cmp::min(*next_start, *start + count), 0), // gap + &(Some((start, r)), None) => + (start + r.len() .. start + r.len() + count, 0), // last range + &(None, None) => + (first_different .. first_different + count, 0), // empty + &(None, Some((start, _))) if *start > first_different => + (first_different .. cmp::min(first_different + count, *start), 0), // gap at the start + _ => { + prev = next; + continue + }, + } + } + }; + + // crop to peers best + if range.start >= peer_best { + return None; + } + range.end = cmp::min(peer_best, range.end); + + self.peer_requests.insert(peer_id, range.start); + self.blocks.insert(range.start, BlockRangeState::Downloading{ len: range.end - range.start, downloading: downloading + 1 }); + Some(range) + } + + /// Get a valid chain of blocks ordered in descending order and ready for importing into blockchain. + pub fn drain(&mut self, from: BlockNumber) -> Vec { + let mut drained = Vec::new(); + let mut ranges = Vec::new(); + { + let mut prev = from; + for (start, range_data) in &mut self.blocks { + match range_data { + &mut BlockRangeState::Complete(ref mut blocks) if *start <= prev => { + prev = *start + blocks.len() as BlockNumber; + let mut blocks = mem::replace(blocks, Vec::new()); + drained.append(&mut blocks); + ranges.push(*start); + }, + _ => break, + } + } + } + for r in ranges { + self.blocks.remove(&r); + } + trace!(target: "sync", "Drained {} blocks", drained.len()); + drained + } + + pub fn clear_peer_download(&mut self, peer_id: PeerId) { + match self.peer_requests.entry(peer_id) { + Entry::Occupied(entry) => { + let start = entry.remove(); + let remove = match self.blocks.get_mut(&start) { + Some(&mut BlockRangeState::Downloading { ref mut downloading, .. }) if *downloading > 1 => { + *downloading = *downloading - 1; + false + }, + Some(&mut BlockRangeState::Downloading { .. }) => { + true + }, + _ => { + debug_assert!(false); + false + } + }; + if remove { + self.blocks.remove(&start); + } + }, + _ => (), + } + } +} + +#[cfg(test)] +mod test { + use super::{BlockCollection, BlockData}; + use message; + use primitives::block::HeaderHash; + + fn is_empty(bc: &BlockCollection) -> bool { + bc.blocks.is_empty() && + bc.peer_requests.is_empty() + } + + fn generate_blocks(n: usize) -> Vec { + (0 .. n).map(|_| message::BlockData { + hash: HeaderHash::random(), + header: None, + body: None, + message_queue: None, + receipt: None, + }).collect() + } + + #[test] + fn create_clear() { + let mut bc = BlockCollection::new(); + assert!(is_empty(&bc)); + bc.insert(1, generate_blocks(100), 0); + assert!(!is_empty(&bc)); + bc.clear(); + assert!(is_empty(&bc)); + } + + #[test] + fn insert_blocks() { + let mut bc = BlockCollection::new(); + assert!(is_empty(&bc)); + let peer0 = 0; + let peer1 = 1; + let peer2 = 2; + + let blocks = generate_blocks(150); + assert_eq!(bc.needed_blocks(peer0, 40, 150, 0), Some(1 .. 41)); + assert_eq!(bc.needed_blocks(peer1, 40, 150, 0), Some(41 .. 81)); + assert_eq!(bc.needed_blocks(peer2, 40, 150, 0), Some(81 .. 121)); + + bc.clear_peer_download(peer1); + bc.insert(41, blocks[41..81].to_vec(), peer1); + assert_eq!(bc.drain(1), vec![]); + assert_eq!(bc.needed_blocks(peer1, 40, 150, 0), Some(121 .. 150)); + bc.clear_peer_download(peer0); + bc.insert(1, blocks[1..11].to_vec(), peer0); + + assert_eq!(bc.needed_blocks(peer0, 40, 150, 0), Some(11 .. 41)); + assert_eq!(bc.drain(1), blocks[1..11].iter().map(|b| BlockData { block: b.clone(), origin: 0 }).collect::>()); + + bc.clear_peer_download(peer0); + bc.insert(11, blocks[11..41].to_vec(), peer0); + + let drained = bc.drain(12); + assert_eq!(drained[..30], blocks[11..41].iter().map(|b| BlockData { block: b.clone(), origin: 0 }).collect::>()[..]); + assert_eq!(drained[30..], blocks[41..81].iter().map(|b| BlockData { block: b.clone(), origin: 1 }).collect::>()[..]); + + bc.clear_peer_download(peer2); + assert_eq!(bc.needed_blocks(peer2, 40, 150, 80), Some(81 .. 121)); + bc.clear_peer_download(peer2); + bc.insert(81, blocks[81..121].to_vec(), peer2); + bc.clear_peer_download(peer1); + bc.insert(121, blocks[121..150].to_vec(), peer1); + + assert_eq!(bc.drain(80), vec![]); + let drained = bc.drain(81); + assert_eq!(drained[..40], blocks[81..121].iter().map(|b| BlockData { block: b.clone(), origin: 2 }).collect::>()[..]); + assert_eq!(drained[40..], blocks[121..150].iter().map(|b| BlockData { block: b.clone(), origin: 1 }).collect::>()[..]); + } +} diff --git a/polkadot/network/src/chain.rs b/polkadot/network/src/chain.rs new file mode 100644 index 0000000000..9390b727ec --- /dev/null +++ b/polkadot/network/src/chain.rs @@ -0,0 +1,57 @@ +// 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 . + +//! Blockchain access trait + +use client::{self, Client as PolkadotClient, ImportResult, ClientInfo, BlockStatus}; +use client::error::Error; +use state_machine; +use primitives::block; + +pub trait Client : Send + Sync { + /// Given a hash return a header + fn import(&self, header: block::Header, body: Option) -> Result; + + /// Get blockchain info. + fn info(&self) -> Result; + + /// Get block status. + fn block_status(&self, hash: &block::HeaderHash) -> Result; + + /// Get block hash by number. + fn block_hash(&self, block_number: block::Number) -> Result, Error>; +} + +impl Client for PolkadotClient where + B: client::backend::Backend + Send + Sync + 'static, + E: state_machine::CodeExecutor + Send + Sync + 'static, +{ + fn import(&self, header: block::Header, body: Option) -> Result { + (self as &Client).import(header, body) + } + + fn info(&self) -> Result { + (self as &Client).info() + } + + fn block_status(&self, hash: &block::HeaderHash) -> Result { + (self as &Client).block_status(hash) + } + + fn block_hash(&self, block_number: block::Number) -> Result, Error> { + (self as &Client).block_hash(block_number) + } +} diff --git a/polkadot/network/src/config.rs b/polkadot/network/src/config.rs new file mode 100644 index 0000000000..b826213280 --- /dev/null +++ b/polkadot/network/src/config.rs @@ -0,0 +1,30 @@ +// 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 service::Role; + +/// Protocol configuration +pub struct ProtocolConfig { + pub roles: Role, +} + +impl Default for ProtocolConfig { + fn default() -> ProtocolConfig { + ProtocolConfig { + roles: Role::FULL, + } + } +} diff --git a/polkadot/network/src/error.rs b/polkadot/network/src/error.rs new file mode 100644 index 0000000000..9583a29861 --- /dev/null +++ b/polkadot/network/src/error.rs @@ -0,0 +1,31 @@ +// 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 network::Error as NetworkError; +use client; + +error_chain! { + foreign_links { + Network(NetworkError) #[doc = "Devp2p error."]; + } + + links { + Client(client::error::Error, client::error::ErrorKind); + } + + errors { + } +} diff --git a/polkadot/network/src/io.rs b/polkadot/network/src/io.rs new file mode 100644 index 0000000000..339e80e9ad --- /dev/null +++ b/polkadot/network/src/io.rs @@ -0,0 +1,78 @@ +// 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 network::{NetworkContext, PeerId, Error as NetworkError, SessionInfo}; + +/// IO interface for the syncing handler. +/// Provides peer connection management and an interface to the blockchain client. +pub trait SyncIo { + /// Disable a peer + fn disable_peer(&mut self, peer_id: PeerId); + /// Disconnect peer + fn disconnect_peer(&mut self, peer_id: PeerId); + /// Send a packet to a peer. + fn send(&mut self, peer_id: PeerId, data: Vec) -> Result<(), NetworkError>; + /// Returns peer identifier string + fn peer_info(&self, peer_id: PeerId) -> String { + peer_id.to_string() + } + /// Returns information on p2p session + fn peer_session_info(&self, peer_id: PeerId) -> Option; + /// Check if the session is expired + fn is_expired(&self) -> bool; +} + +/// Wraps `NetworkContext` and the blockchain client +pub struct NetSyncIo<'s, 'h> where 'h: 's { + network: &'s NetworkContext<'h>, +} + +impl<'s, 'h> NetSyncIo<'s, 'h> { + /// Creates a new instance from the `NetworkContext` and the blockchain client reference. + pub fn new(network: &'s NetworkContext<'h>) -> NetSyncIo<'s, 'h> { + NetSyncIo { + network: network, + } + } +} + +impl<'s, 'h> SyncIo for NetSyncIo<'s, 'h> { + fn disable_peer(&mut self, peer_id: PeerId) { + self.network.disable_peer(peer_id); + } + + fn disconnect_peer(&mut self, peer_id: PeerId) { + self.network.disconnect_peer(peer_id); + } + + fn send(&mut self, peer_id: PeerId, data: Vec) -> Result<(), NetworkError>{ + self.network.send(peer_id, 0, data) + } + + fn peer_session_info(&self, peer_id: PeerId) -> Option { + self.network.session_info(peer_id) + } + + fn is_expired(&self) -> bool { + self.network.is_expired() + } + + fn peer_info(&self, peer_id: PeerId) -> String { + self.network.peer_client_version(peer_id) + } +} + + diff --git a/polkadot/network/src/lib.rs b/polkadot/network/src/lib.rs new file mode 100644 index 0000000000..bdd7b41ca6 --- /dev/null +++ b/polkadot/network/src/lib.rs @@ -0,0 +1,64 @@ +// 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 .? + +#![warn(missing_docs)] + +//! Implements polkadot protocol version as specified here: +//! https://github.com/paritytech/polkadot/wiki/Network-protocol + +extern crate ethcore_network as network; +extern crate ethcore_io as core_io; +extern crate env_logger; +extern crate rand; +extern crate semver; +extern crate parking_lot; +extern crate smallvec; +extern crate ipnetwork; +extern crate substrate_primitives as primitives; +extern crate substrate_state_machine as state_machine; +extern crate substrate_serializer as ser; +extern crate serde; +extern crate serde_json; +// TODO: remove these two; split off dependent logic into polkadot-network and rename this crate +// to substrate-network. +extern crate polkadot_primitives as polkadot_primitives; +extern crate substrate_client as client; +#[macro_use] extern crate serde_derive; +#[macro_use] extern crate log; +#[macro_use] extern crate bitflags; +#[macro_use] extern crate error_chain; + +mod service; +mod sync; +mod protocol; +mod io; +mod message; +mod error; +mod config; +mod chain; +mod blocks; + +#[cfg(test)] +mod test; + +pub use service::Service; +pub use protocol::{ProtocolStatus}; +pub use network::{NonReservedPeerMode, ConnectionFilter, ConnectionDirection, NetworkConfiguration}; + +// TODO: move it elsewhere +fn header_hash(header: &primitives::Header) -> primitives::block::HeaderHash { + primitives::hashing::blake2_256(&ser::to_vec(header)).into() +} diff --git a/polkadot/network/src/message.rs b/polkadot/network/src/message.rs new file mode 100644 index 0000000000..feefb56595 --- /dev/null +++ b/polkadot/network/src/message.rs @@ -0,0 +1,189 @@ +// 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 .? + +//! Network packet message types. These get serialized and put into the lower level protocol payload. + +use std::borrow::Borrow; +use primitives::AuthorityId; +use primitives::block::{Number as BlockNumber, HeaderHash, Header, Body}; +use service::Role as RoleFlags; +use polkadot_primitives::parachain::Id as ParachainId; + +pub type RequestId = u64; +type Bytes = Vec; + +type Signature = ::primitives::hash::H256; //TODO: + +/// Configured node role. +#[derive(Debug, PartialEq, Eq, Serialize, Deserialize)] +pub enum Role { + /// Full relay chain client with no additional responsibilities. + Full, + /// Relay chain light client. + Light, + /// Parachain validator. + Validator, + /// Parachain collator. + Collator, +} + +impl From for RoleFlags where T: Borrow<[Role]> { + fn from(roles: T) -> RoleFlags { + let mut flags = RoleFlags::NONE; + let roles: &[Role] = roles.borrow(); + for r in roles { + match *r { + Role::Full => flags = flags | RoleFlags::FULL, + Role::Light => flags = flags | RoleFlags::LIGHT, + Role::Validator => flags = flags | RoleFlags::VALIDATOR, + Role::Collator => flags = flags | RoleFlags::COLLATOR, + } + } + flags + } +} + +impl From for Vec where { + fn from(flags: RoleFlags) -> Vec { + let mut roles = Vec::new(); + if !(flags & RoleFlags::FULL).is_empty() { + roles.push(Role::Full); + } + if !(flags & RoleFlags::LIGHT).is_empty() { + roles.push(Role::Light); + } + if !(flags & RoleFlags::VALIDATOR).is_empty() { + roles.push(Role::Validator); + } + if !(flags & RoleFlags::COLLATOR).is_empty() { + roles.push(Role::Collator); + } + roles + } +} + +#[derive(Debug, PartialEq, Eq, Serialize, Deserialize, Copy, Clone)] +/// Bits of block data and associated artefacts to request. +pub enum BlockAttribute { + /// Include block header. + Header, + /// Include block body. + Body, + /// Include block receipt. + Receipt, + /// Include block message queue. + MessageQueue, +} + +#[derive(Debug, PartialEq, Eq, Clone, Serialize, Deserialize)] +/// Block data sent in the response. +pub struct BlockData { + /// Block header hash. + pub hash: HeaderHash, + /// Block header if requested. + pub header: Option
, + /// Block body if requested. + pub body: Option, + /// Block receipt if requested. + pub receipt: Option, + /// Block message queue if requested. + pub message_queue: Option, +} + +#[serde(untagged)] +#[derive(Debug, PartialEq, Eq, Clone, Serialize, Deserialize)] +/// Identifies starting point of a block sequence. +pub enum FromBlock { + /// Start with given hash. + Hash(HeaderHash), + /// Start with given block number. + Number(BlockNumber), +} + +#[derive(Debug, PartialEq, Eq, Clone, Serialize, Deserialize)] +/// Block enumeration direction. +pub enum Direction { + /// Enumerate in ascending order (from child to parent). + Ascending, + /// Enumerate in descendfing order (from parent to canonical child). + Descending, +} + +#[derive(Debug, PartialEq, Eq, Serialize, Deserialize)] +/// A network message. +pub enum Message { + /// Status packet. + Status(Status), + /// Block request. + BlockRequest(BlockRequest), + /// Block response. + BlockResponse(BlockResponse), + /// Block announce. + BlockAnnounce(BlockAnnounce), +} + +#[derive(Debug, PartialEq, Eq, Serialize, Deserialize)] +pub struct Status { + /// Protocol version. + pub version: u32, + /// Supported roles. + pub roles: Vec, + /// Best block number. + pub best_number: BlockNumber, + /// Best block hash. + pub best_hash: HeaderHash, + /// Genesis block hash. + pub genesis_hash: HeaderHash, + /// Signatue of `best_hash` made with validator address. Required for the validator role. + pub validator_signature: Option, + /// Validator address. Required for the validator role. + pub validator_id: Option, + /// Parachain id. Required for the collator role. + pub parachain_id: Option, +} + +#[derive(Debug, PartialEq, Eq, Clone, Serialize, Deserialize)] +/// Request block data from a peer. +pub struct BlockRequest { + /// Unique request id. + pub id: RequestId, + /// Bits of block data to request. + pub fields: Vec, + /// Start from this block. + pub from: FromBlock, + /// End at this block. An implementation defined maximum is used when unspecified. + pub to: Option, + /// Sequence direction. + pub direction: Direction, + /// Maximum number of block to return. An implementation defined maximum is used when unspecified. + pub max: Option, +} + +#[derive(Debug, PartialEq, Eq, Serialize, Deserialize)] +/// Response to `BlockRequest` +pub struct BlockResponse { + /// Id of a request this response was made for. + pub id: RequestId, + /// Block data for the requested sequence. + pub blocks: Vec, +} + +#[derive(Debug, PartialEq, Eq, Serialize, Deserialize)] +/// Announce a new complete relay chain block on the network. +pub struct BlockAnnounce { + /// New block header. + pub header: Header, +} diff --git a/polkadot/network/src/protocol.rs b/polkadot/network/src/protocol.rs new file mode 100644 index 0000000000..bd9dd614fe --- /dev/null +++ b/polkadot/network/src/protocol.rs @@ -0,0 +1,344 @@ +// 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 std::collections::{HashMap, HashSet, BTreeMap}; +use std::mem; +use std::sync::Arc; +use parking_lot::RwLock; +use serde_json; +use std::time; +use primitives::block::{HeaderHash, TransactionHash, Number as BlockNumber, Header}; +use network::{PeerId, NodeId}; + +use message::{self, Message}; +use sync::{ChainSync, Status as SyncStatus}; +use service::Role; +use config::ProtocolConfig; +use chain::Client; +use io::SyncIo; +use error; + +const REQUEST_TIMEOUT_SEC: u64 = 15; +const PROTOCOL_VERSION: u32 = 0; + +// Lock must always be taken in order declared here. +pub struct Protocol { + config: ProtocolConfig, + chain: Arc, + genesis_hash: HeaderHash, + sync: RwLock, + /// All connected peers + peers: RwLock>, + /// Connected peers pending Status message. + handshaking_peers: RwLock>, +} + +/// Syncing status and statistics +#[derive(Clone, Copy)] +pub struct ProtocolStatus { + /// Sync status. + pub sync: SyncStatus, + /// Total number of connected peers + pub num_peers: usize, + /// Total number of active peers. + pub num_active_peers: usize, +} + +/// Peer information +struct Peer { + /// Protocol version + protocol_version: u32, + /// Roles + roles: Role, + /// Peer best block hash + best_hash: HeaderHash, + /// Peer best block number + best_number: BlockNumber, + /// Pending block request if any + block_request: Option, + /// Request timestamp + request_timestamp: Option, + /// Holds a set of transactions recently sent to this peer to avoid spamming. + _last_sent_transactions: HashSet, + /// Request counter, + request_id: message::RequestId, +} + +#[derive(Debug)] +pub struct PeerInfo { + /// Roles + pub roles: Role, + /// Protocol version + pub protocol_version: u32, + /// Peer best block hash + pub best_hash: HeaderHash, + /// Peer best block number + pub best_number: BlockNumber, +} + +/// Transaction stats +#[derive(Debug)] +pub struct TransactionStats { + /// Block number where this TX was first seen. + pub first_seen: u64, + /// Peers it was propagated to. + pub propagated_to: BTreeMap, +} + +impl Protocol { + /// Create a new instance. + pub fn new(config: ProtocolConfig, chain: Arc) -> error::Result { + let info = chain.info()?; + let protocol = Protocol { + config: config, + chain: chain, + genesis_hash: info.chain.genesis_hash, + sync: RwLock::new(ChainSync::new(&info)), + peers: RwLock::new(HashMap::new()), + handshaking_peers: RwLock::new(HashMap::new()), + }; + Ok(protocol) + } + + /// Returns protocol status + pub fn status(&self) -> ProtocolStatus { + let sync = self.sync.read(); + let peers = self.peers.read(); + ProtocolStatus { + sync: sync.status(), + num_peers: peers.values().count(), + num_active_peers: peers.values().filter(|p| p.block_request.is_some()).count(), + } + } + + pub fn handle_packet(&self, io: &mut SyncIo, peer_id: PeerId, data: &[u8]) { + let message: Message = match serde_json::from_slice(data) { + Ok(m) => m, + Err(e) => { + debug!("Invalid packet from {}: {}", peer_id, e); + io.disable_peer(peer_id); + return; + } + }; + + match message { + Message::Status(s) => self.on_status_message(io, peer_id, s), + Message::BlockRequest(r) => self.on_block_request(io, peer_id, r), + Message::BlockResponse(r) => { + let request = { + let mut peers = self.peers.write(); + if let Some(ref mut peer) = peers.get_mut(&peer_id) { + peer.request_timestamp = None; + match mem::replace(&mut peer.block_request, None) { + Some(r) => r, + None => { + debug!("Unexpected response packet from {}", peer_id); + io.disable_peer(peer_id); + return; + } + } + } else { + debug!("Unexpected packet from {}", peer_id); + io.disable_peer(peer_id); + return; + } + }; + if request.id != r.id { + trace!("Ignoring mismatched response packet from {}", peer_id); + return; + } + self.on_block_response(io, peer_id, request, r); + }, + Message::BlockAnnounce(announce) => { + self.on_block_announce(io, peer_id, announce); + } + } + } + + pub fn send_message(&self, io: &mut SyncIo, peer_id: PeerId, mut message: Message) { + let mut peers = self.peers.write(); + if let Some(ref mut peer) = peers.get_mut(&peer_id) { + match &mut message { + &mut Message::BlockRequest(ref mut r) => { + peer.block_request = Some(r.clone()); + peer.request_timestamp = Some(time::Instant::now()); + r.id = peer.request_id; + peer.request_id = peer.request_id + 1; + }, + _ => (), + } + } + let data = serde_json::to_vec(&message).expect("Serializer is infallible; qed"); + if let Err(e) = io.send(peer_id, data) { + debug!(target:"sync", "Error sending message: {:?}", e); + io.disconnect_peer(peer_id); + } + } + + /// Called when a new peer is connected + pub fn on_peer_connected(&self, io: &mut SyncIo, peer_id: PeerId) { + trace!(target: "sync", "Connected {}: {}", peer_id, io.peer_info(peer_id)); + self.handshaking_peers.write().insert(peer_id, time::Instant::now()); + self.send_status(io, peer_id); + } + + /// Called by peer when it is disconnecting + pub fn on_peer_disconnected(&self, io: &mut SyncIo, peer: PeerId) { + trace!(target: "sync", "Disconnecting {}: {}", peer, io.peer_info(peer)); + let removed = { + let mut peers = self.peers.write(); + let mut handshaking_peers = self.handshaking_peers.write(); + handshaking_peers.remove(&peer); + peers.remove(&peer).is_some() + }; + if removed { + self.sync.write().peer_disconnected(io, self, peer); + } + } + + pub fn on_block_request(&self, _io: &mut SyncIo, _peer_id: PeerId, _request: message::BlockRequest) { + } + + pub fn on_block_response(&self, io: &mut SyncIo, peer_id: PeerId, request: message::BlockRequest, response: message::BlockResponse) { + // TODO: validate response + self.sync.write().on_block_data(io, self, peer_id, request, response); + } + + pub fn tick(&self, io: &mut SyncIo) { + self.maintain_peers(io); + } + + fn maintain_peers(&self, io: &mut SyncIo) { + let tick = time::Instant::now(); + let mut aborting = Vec::new(); + { + let peers = self.peers.read(); + let handshaking_peers = self.handshaking_peers.read(); + for (peer_id, timestamp) in peers.iter() + .filter_map(|(id, peer)| peer.request_timestamp.as_ref().map(|r| (id, r))) + .chain(handshaking_peers.iter()) { + if (tick - *timestamp).as_secs() > REQUEST_TIMEOUT_SEC { + trace!(target:"sync", "Timeout {}", peer_id); + io.disconnect_peer(*peer_id); + aborting.push(*peer_id); + } + } + } + for p in aborting { + self.on_peer_disconnected(io, p); + } + } + + pub fn peer_info(&self, peer: PeerId) -> Option { + self.peers.read().get(&peer).map(|p| { + PeerInfo { + roles: p.roles, + protocol_version: p.protocol_version, + best_hash: p.best_hash, + best_number: p.best_number, + } + }) + } + + /// Called by peer to report status + fn on_status_message(&self, io: &mut SyncIo, peer_id: PeerId, status: message::Status) { + trace!(target: "sync", "New peer {} {:?}", peer_id, status); + if io.is_expired() { + trace!(target: "sync", "Status packet from expired session {}:{}", peer_id, io.peer_info(peer_id)); + return; + } + + { + let mut peers = self.peers.write(); + let mut handshaking_peers = self.handshaking_peers.write(); + if peers.contains_key(&peer_id) { + debug!(target: "sync", "Unexpected status packet from {}:{}", peer_id, io.peer_info(peer_id)); + return; + } + if status.genesis_hash != self.genesis_hash { + io.disable_peer(peer_id); + trace!(target: "sync", "Peer {} genesis hash mismatch (ours: {}, theirs: {})", peer_id, self.genesis_hash, status.genesis_hash); + return; + } + if status.version != PROTOCOL_VERSION { + io.disable_peer(peer_id); + trace!(target: "sync", "Peer {} unsupported eth protocol ({})", peer_id, status.version); + return; + } + + let peer = Peer { + protocol_version: status.version, + roles: status.roles.into(), + best_hash: status.best_hash, + best_number: status.best_number, + block_request: None, + request_timestamp: None, + _last_sent_transactions: HashSet::new(), + request_id: 0, + }; + peers.insert(peer_id.clone(), peer); + handshaking_peers.remove(&peer_id); + debug!(target: "sync", "Connected {} {}", peer_id, io.peer_info(peer_id)); + } + self.sync.write().new_peer(io, self, peer_id); + } + + /// Send Status message + fn send_status(&self, io: &mut SyncIo, peer_id: PeerId) { + if let Ok(info) = self.chain.info() { + let status = message::Status { + version: PROTOCOL_VERSION, + genesis_hash: info.chain.genesis_hash, + roles: self.config.roles.into(), + best_number: info.chain.best_number, + best_hash: info.chain.best_hash, + validator_signature: None, + validator_id: None, + parachain_id: None, + }; + self.send_message(io, peer_id, Message::Status(status)) + } + } + + pub fn abort(&self) { + let mut sync = self.sync.write(); + let mut peers = self.peers.write(); + let mut handshaking_peers = self.handshaking_peers.write(); + sync.clear(); + peers.clear(); + handshaking_peers.clear(); + } + + pub fn on_block_announce(&self, io: &mut SyncIo, peer_id: PeerId, announce: message::BlockAnnounce) { + let header = announce.header; + self.sync.write().on_block_announce(io, self, peer_id, &header); + } + + pub fn on_block_imported(&self, header: &Header) { + self.sync.write().update_chain_info(&header); + } + + pub fn on_new_transactions(&self) { + } + + pub fn transactions_stats(&self) -> BTreeMap { + BTreeMap::new() + } + + pub fn chain(&self) -> &Client { + &*self.chain + } +} diff --git a/polkadot/network/src/service.rs b/polkadot/network/src/service.rs new file mode 100644 index 0000000000..e3832249db --- /dev/null +++ b/polkadot/network/src/service.rs @@ -0,0 +1,239 @@ +// 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 std::sync::Arc; +use std::collections::{BTreeMap}; +use std::io; +use network::{NetworkProtocolHandler, NetworkService, NetworkContext, HostInfo, PeerId, ProtocolId, +NetworkConfiguration , NonReservedPeerMode, ErrorKind}; +use primitives::block::{TransactionHash, Header}; +use core_io::{TimerToken}; +use io::NetSyncIo; +use protocol::{Protocol, ProtocolStatus, PeerInfo as ProtocolPeerInfo, TransactionStats}; +use config::{ProtocolConfig}; +use error::Error; +use chain::Client; + +/// Polkadot devp2p protocol id +pub const DOT_PROTOCOL_ID: ProtocolId = *b"dot"; + +bitflags! { + pub struct Role: u32 { + const NONE = 0b00000000; + const FULL = 0b00000001; + const LIGHT = 0b00000010; + const VALIDATOR = 0b00000100; + const COLLATOR = 0b00001000; + } +} + +/// Sync status +pub trait SyncProvider: Send + Sync { + /// Get sync status + fn status(&self) -> ProtocolStatus; + /// Get peers information + fn peers(&self) -> Vec; + /// Get this node id if available. + fn node_id(&self) -> Option; + /// Returns propagation count for pending transactions. + fn transactions_stats(&self) -> BTreeMap; +} + +/// Peer connection information +#[derive(Debug)] +pub struct PeerInfo { + /// Public node id + pub id: Option, + /// Node client ID + pub client_version: String, + /// Capabilities + pub capabilities: Vec, + /// Remote endpoint address + pub remote_address: String, + /// Local endpoint address + pub local_address: String, + /// Dot protocol info. + pub dot_info: Option, +} + +/// Service initialization parameters. +pub struct Params { + /// Configuration. + pub config: ProtocolConfig, + /// Network layer configuration. + pub network_config: NetworkConfiguration, + /// Polkadot relay chain access point. + pub chain: Arc, +} + +/// Polkadot network service. Handles network IO and manages connectivity. +pub struct Service { + /// Network service + network: NetworkService, + /// Devp2p protocol handler + handler: Arc, +} + +impl Service { + /// Creates and register protocol with the network service + pub fn new(params: Params) -> Result, Error> { + + let service = NetworkService::new(params.network_config.clone(), None)?; + + let sync = Arc::new(Service { + network: service, + handler: Arc::new(ProtocolHandler { + protocol: Protocol::new(params.config, params.chain.clone())?, + }), + }); + + Ok(sync) + } + + /// Called when a new block is imported by the client. + pub fn on_block_imported(&self, header: &Header) { + self.handler.protocol.on_block_imported(header) + } + + /// Called when new transactons are imported by the client. + pub fn on_new_transactions(&self) { + self.handler.protocol.on_new_transactions() + } + + fn start(&self) { + match self.network.start().map_err(Into::into) { + Err(ErrorKind::Io(ref e)) if e.kind() == io::ErrorKind::AddrInUse => + warn!("Network port {:?} is already in use, make sure that another instance of Polkadot client is not running or change the port using the --port option.", self.network.config().listen_address.expect("Listen address is not set.")), + Err(err) => warn!("Error starting network: {}", err), + _ => {}, + }; + self.network.register_protocol(self.handler.clone(), DOT_PROTOCOL_ID, 1, &[0u8]) + .unwrap_or_else(|e| warn!("Error registering polkadot protocol: {:?}", e)); + } + + fn stop(&self) { + self.handler.protocol.abort(); + self.network.stop().unwrap_or_else(|e| warn!("Error stopping network: {:?}", e)); + } +} + +impl SyncProvider for Service { + /// Get sync status + fn status(&self) -> ProtocolStatus { + self.handler.protocol.status() + } + + /// Get sync peers + fn peers(&self) -> Vec { + self.network.with_context_eval(DOT_PROTOCOL_ID, |ctx| { + let peer_ids = self.network.connected_peers(); + + peer_ids.into_iter().filter_map(|peer_id| { + let session_info = match ctx.session_info(peer_id) { + None => return None, + Some(info) => info, + }; + + Some(PeerInfo { + id: session_info.id.map(|id| format!("{:x}", id)), + client_version: session_info.client_version, + capabilities: session_info.peer_capabilities.into_iter().map(|c| c.to_string()).collect(), + remote_address: session_info.remote_address, + local_address: session_info.local_address, + dot_info: self.handler.protocol.peer_info(peer_id), + }) + }).collect() + }).unwrap_or_else(Vec::new) + } + + fn node_id(&self) -> Option { + self.network.external_url() + } + + fn transactions_stats(&self) -> BTreeMap { + self.handler.protocol.transactions_stats() + } +} + +struct ProtocolHandler { + /// Protocol handler + protocol: Protocol, +} + +impl NetworkProtocolHandler for ProtocolHandler { + fn initialize(&self, io: &NetworkContext, _host_info: &HostInfo) { + io.register_timer(0, 1000).expect("Error registering sync timer"); + } + + fn read(&self, io: &NetworkContext, peer: &PeerId, _packet_id: u8, data: &[u8]) { + self.protocol.handle_packet(&mut NetSyncIo::new(io), *peer, data); + } + + fn connected(&self, io: &NetworkContext, peer: &PeerId) { + self.protocol.on_peer_connected(&mut NetSyncIo::new(io), *peer); + } + + fn disconnected(&self, io: &NetworkContext, peer: &PeerId) { + self.protocol.on_peer_disconnected(&mut NetSyncIo::new(io), *peer); + } + + fn timeout(&self, io: &NetworkContext, _timer: TimerToken) { + self.protocol.tick(&mut NetSyncIo::new(io)); + } +} + +/// Trait for managing network +pub trait ManageNetwork : Send + Sync { + /// Set to allow unreserved peers to connect + fn accept_unreserved_peers(&self); + /// Set to deny unreserved peers to connect + fn deny_unreserved_peers(&self); + /// Remove reservation for the peer + fn remove_reserved_peer(&self, peer: String) -> Result<(), String>; + /// Add reserved peer + fn add_reserved_peer(&self, peer: String) -> Result<(), String>; + /// Start network + fn start_network(&self); + /// Stop network + fn stop_network(&self); +} + + +impl ManageNetwork for Service { + fn accept_unreserved_peers(&self) { + self.network.set_non_reserved_mode(NonReservedPeerMode::Accept); + } + + fn deny_unreserved_peers(&self) { + self.network.set_non_reserved_mode(NonReservedPeerMode::Deny); + } + + fn remove_reserved_peer(&self, peer: String) -> Result<(), String> { + self.network.remove_reserved_peer(&peer).map_err(|e| format!("{:?}", e)) + } + + fn add_reserved_peer(&self, peer: String) -> Result<(), String> { + self.network.add_reserved_peer(&peer).map_err(|e| format!("{:?}", e)) + } + + fn start_network(&self) { + self.start(); + } + + fn stop_network(&self) { + self.stop(); + } +} diff --git a/polkadot/network/src/sync.rs b/polkadot/network/src/sync.rs new file mode 100644 index 0000000000..e7d2847b83 --- /dev/null +++ b/polkadot/network/src/sync.rs @@ -0,0 +1,368 @@ +// 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 std::collections::HashMap; +use io::SyncIo; +use protocol::Protocol; +use network::PeerId; +use client::{ImportResult, BlockStatus, ClientInfo}; +use primitives::block::{HeaderHash, Number as BlockNumber, Header}; +use blocks::{self, BlockCollection}; +use message::{self, Message}; +use super::header_hash; + +// Maximum parallel requests for a block. +const MAX_BLOCK_DOWNLOAD: usize = 1; + +struct PeerSync { + pub common_hash: HeaderHash, + pub common_number: BlockNumber, + pub best_hash: HeaderHash, + pub best_number: BlockNumber, + pub state: PeerSyncState, +} + +#[derive(Copy, Clone, Eq, PartialEq, Debug)] +pub enum PeerSyncState { + AncestorSearch(BlockNumber), + Available, + DownloadingNew(BlockNumber), + DownloadingStale(HeaderHash), +} + +/// Relay chain sync strategy. +pub struct ChainSync { + genesis_hash: HeaderHash, + peers: HashMap, + blocks: BlockCollection, + best_queued_number: BlockNumber, + best_queued_hash: HeaderHash, + required_block_attributes: Vec, +} + +/// Syncing status and statistics +#[derive(Clone, Copy)] +pub struct Status; + +impl ChainSync { + pub fn new(info: &ClientInfo) -> ChainSync { + ChainSync { + genesis_hash: info.chain.genesis_hash, + peers: HashMap::new(), + blocks: BlockCollection::new(), + best_queued_hash: info.best_queued_hash.unwrap_or(info.chain.best_hash), + best_queued_number: info.best_queued_number.unwrap_or(info.chain.best_number), + required_block_attributes: vec![message::BlockAttribute::Header, message::BlockAttribute::Body], + } + } + + /// Returns sync status + pub fn status(&self) -> Status { + Status + } + + pub fn new_peer(&mut self, io: &mut SyncIo, protocol: &Protocol, peer_id: PeerId) { + if let Some(info) = protocol.peer_info(peer_id) { + match (protocol.chain().block_status(&info.best_hash), info.best_number) { + (Err(e), _) => { + debug!(target:"sync", "Error reading blockchain: {:?}", e); + io.disconnect_peer(peer_id); + }, + (Ok(BlockStatus::KnownBad), _) => { + debug!(target:"sync", "New peer with known bad best block {} ({}).", info.best_hash, info.best_number); + io.disable_peer(peer_id); + }, + (Ok(BlockStatus::Unknown), 0) => { + debug!(target:"sync", "New peer with unkown genesis hash {} ({}).", info.best_hash, info.best_number); + io.disable_peer(peer_id); + }, + (Ok(BlockStatus::Unknown), _) => { + debug!(target:"sync", "New peer with unkown best hash {} ({}), searching for common ancestor.", info.best_hash, info.best_number); + self.peers.insert(peer_id, PeerSync { + common_hash: self.genesis_hash, + common_number: 0, + best_hash: info.best_hash, + best_number: info.best_number, + state: PeerSyncState::AncestorSearch(info.best_number - 1), + }); + Self::request_ancestry(io, protocol, peer_id, info.best_number - 1) + }, + (Ok(BlockStatus::Queued), _) | (Ok(BlockStatus::InChain), _) => { + debug!(target:"sync", "New peer with known best hash {} ({}).", info.best_hash, info.best_number); + self.peers.insert(peer_id, PeerSync { + common_hash: info.best_hash, + common_number: info.best_number, + best_hash: info.best_hash, + best_number: info.best_number, + state: PeerSyncState::Available, + }); + } + } + } + } + + pub fn on_block_data(&mut self, io: &mut SyncIo, protocol: &Protocol, peer_id: PeerId, _request: message::BlockRequest, response: message::BlockResponse) { + let count = response.blocks.len(); + let mut imported: usize = 0; + let new_blocks = if let Some(ref mut peer) = self.peers.get_mut(&peer_id) { + match peer.state { + PeerSyncState::DownloadingNew(start_block) => { + self.blocks.clear_peer_download(peer_id); + peer.state = PeerSyncState::Available; + + self.blocks.insert(start_block, response.blocks, peer_id); + self.blocks.drain(self.best_queued_number + 1) + }, + PeerSyncState::DownloadingStale(_) => { + peer.state = PeerSyncState::Available; + response.blocks.into_iter().map(|b| blocks::BlockData { + origin: peer_id, + block: b + }).collect() + }, + PeerSyncState::AncestorSearch(n) => { + match response.blocks.get(0) { + Some(ref block) => { + match protocol.chain().block_hash(n) { + Ok(Some(block_hash)) if block_hash == block.hash => { + peer.common_hash = block.hash; + peer.common_number = n; + peer.state = PeerSyncState::Available; + trace!(target:"sync", "Found common ancestor for peer {}: {} ({})", peer_id, block.hash, n); + vec![] + }, + Ok(_) if n > 0 => { + let n = n - 1; + peer.state = PeerSyncState::AncestorSearch(n); + Self::request_ancestry(io, protocol, peer_id, n); + return; + }, + Ok(_) => { // genesis mismatch + io.disable_peer(peer_id); + return; + }, + Err(e) => { + debug!(target:"sync", "Error reading blockchain: {:?}", e); + io.disconnect_peer(peer_id); + return; + } + } + }, + None => { + trace!(target:"sync", "Invalid response when searching for ancestor from {}", peer_id); + io.disconnect_peer(peer_id); + return; + } + } + }, + PeerSyncState::Available => Vec::new(), + } + } else { + vec![] + }; + + // Blocks in the response/drain should be in ascending order. + for block in new_blocks { + let origin = block.origin; + let block = block.block; + if let Some(header) = block.header { + let number = header.number; + let hash = header_hash(&header); + let parent = header.parent_hash; + let result = protocol.chain().import(header, block.body); + match result { + Ok(ImportResult::AlreadyInChain) => { + trace!(target: "sync", "Block already in chain {}: {:?}", number, hash); + self.block_imported(&hash, number); + }, + Ok(ImportResult::AlreadyQueued) => { + trace!(target: "sync", "Block already queued {}: {:?}", number, hash); + self.block_imported(&hash, number); + }, + Ok(ImportResult::Queued) => { + trace!(target: "sync", "Block queued {}: {:?}", number, hash); + self.block_imported(&hash, number); + imported = imported + 1; + }, + Ok(ImportResult::UnknownParent) => { + debug!(target: "sync", "Block with unknown parent {}: {:?}, parent: {:?}", number, hash, parent); + self.restart(io, protocol); + return; + }, + Ok(ImportResult::KnownBad) => { + debug!(target: "sync", "Bad block {}: {:?}", number, hash); + io.disable_peer(origin); //TODO: use persistent ID + self.restart(io, protocol); + return; + } + Err(e) => { + debug!(target: "sync", "Error importing block {}: {:?}: {:?}", number, hash, e); + self.restart(io, protocol); + return; + } + } + } + } + trace!(target: "sync", "Imported {} of {}", imported, count); + self.maintain_sync(io, protocol); + } + + fn maintain_sync(&mut self, io: &mut SyncIo, protocol: &Protocol) { + let peers: Vec = self.peers.keys().map(|p| *p).collect(); + for peer in peers { + self.download_new(io, protocol, peer); + } + } + + fn block_imported(&mut self, hash: &HeaderHash, number: BlockNumber) { + if number > self.best_queued_number { + self.best_queued_number = number; + self.best_queued_hash = *hash; + } + // Update common blocks + for (_, peer) in self.peers.iter_mut() { + if peer.best_number >= number { + peer.common_number = number; + peer.common_hash = *hash; + } + } + } + + pub fn update_chain_info(&mut self, best_header: &Header ) { + let hash = header_hash(&best_header); + self.block_imported(&hash, best_header.number) + } + + pub fn on_block_announce(&mut self, io: &mut SyncIo, protocol: &Protocol, peer_id: PeerId, header: &Header) { + let hash = header_hash(&header); + if let Some(ref mut peer) = self.peers.get_mut(&peer_id) { + if header.number > peer.best_number { + peer.best_number = header.number; + peer.best_hash = hash; + } + } else { + return; + } + + if !self.is_known_or_already_downloading(protocol, &hash) { + let stale = header.number <= self.best_queued_number; + if stale { + if !self.is_known_or_already_downloading(protocol, &header.parent_hash) { + trace!(target: "sync", "Ignoring unkown stale block announce from {}: {} {:?}", peer_id, hash, header); + } else { + trace!(target: "sync", "Downloading new stale block announced from {}: {} {:?}", peer_id, hash, header); + self.download_stale(io, protocol, peer_id, &hash); + } + } else { + trace!(target: "sync", "Downloading new block announced from {}: {} {:?}", peer_id, hash, header); + self.download_new(io, protocol, peer_id); + } + } else { + trace!(target: "sync", "Known block announce from {}: {}", peer_id, hash); + } + } + + fn is_known_or_already_downloading(&self, protocol: &Protocol, hash: &HeaderHash) -> bool { + self.peers.iter().any(|(_, p)| p.state == PeerSyncState::DownloadingStale(*hash)) + || protocol.chain().block_status(hash).ok().map_or(false, |s| s != BlockStatus::Unknown) + } + + pub fn peer_disconnected(&mut self, io: &mut SyncIo, protocol: &Protocol, peer_id: PeerId) { + self.blocks.clear_peer_download(peer_id); + self.peers.remove(&peer_id); + self.maintain_sync(io, protocol); + } + + pub fn restart(&mut self, io: &mut SyncIo, protocol: &Protocol) { + self.blocks.clear(); + let ids: Vec = self.peers.keys().map(|p| *p).collect(); + for id in ids { + self.new_peer(io, protocol, id); + } + match protocol.chain().info() { + Ok(info) => { + self.best_queued_hash = info.best_queued_hash.unwrap_or(info.chain.best_hash); + self.best_queued_number = info.best_queued_number.unwrap_or(info.chain.best_number); + }, + Err(e) => { + debug!(target:"sync", "Error reading blockchain: {:?}", e); + self.best_queued_hash = self.genesis_hash; + self.best_queued_number = 0; + } + } + } + + pub fn clear(&mut self) { + self.blocks.clear(); + self.peers.clear(); + } + + // Download old block. + fn download_stale(&mut self, io: &mut SyncIo, protocol: &Protocol, peer_id: PeerId, hash: &HeaderHash) { + if let Some(ref mut peer) = self.peers.get_mut(&peer_id) { + match peer.state { + PeerSyncState::Available => { + let request = message::BlockRequest { + id: 0, + fields: self.required_block_attributes.clone(), + from: message::FromBlock::Hash(*hash), + to: None, + direction: message::Direction::Ascending, + max: Some(1), + }; + peer.state = PeerSyncState::DownloadingStale(*hash); + protocol.send_message(io, peer_id, Message::BlockRequest(request)); + }, + _ => (), + } + } + } + + // Issue a request for a peer to download new blocks, if any are available + fn download_new(&mut self, io: &mut SyncIo, protocol: &Protocol, peer_id: PeerId) { + if let Some(ref mut peer) = self.peers.get_mut(&peer_id) { + match peer.state { + PeerSyncState::Available => { + if let Some(range) = self.blocks.needed_blocks(peer_id, MAX_BLOCK_DOWNLOAD, peer.common_number, peer.best_number) { + let request = message::BlockRequest { + id: 0, + fields: self.required_block_attributes.clone(), + from: message::FromBlock::Number(range.start), + to: None, + direction: message::Direction::Ascending, + max: Some((range.end - range.start) as u32), + }; + peer.state = PeerSyncState::DownloadingNew(range.start); + protocol.send_message(io, peer_id, Message::BlockRequest(request)); + } + }, + _ => (), + } + } + } + + fn request_ancestry(io: &mut SyncIo, protocol: &Protocol, peer_id: PeerId, block: BlockNumber) { + let request = message::BlockRequest { + id: 0, + fields: vec![message::BlockAttribute::Header], + from: message::FromBlock::Number(block), + to: None, + direction: message::Direction::Ascending, + max: Some(1), + }; + protocol.send_message(io, peer_id, Message::BlockRequest(request)); + } +} diff --git a/polkadot/network/src/test/mod.rs b/polkadot/network/src/test/mod.rs new file mode 100644 index 0000000000..e69de29bb2 diff --git a/polkadot/primitives/Cargo.toml b/polkadot/primitives/Cargo.toml new file mode 100644 index 0000000000..ec2433c9a7 --- /dev/null +++ b/polkadot/primitives/Cargo.toml @@ -0,0 +1,25 @@ +[package] +name = "polkadot-primitives" +version = "0.1.0" +authors = ["Parity Technologies "] + +[dependencies] +serde = { version = "1.0", default_features = false } +serde_derive = { version = "1.0", optional = true } +substrate-codec = { path = "../../substrate/codec", default_features = false } +substrate-primitives = { path = "../../substrate/primitives", default_features = false } +substrate-runtime-std = { path = "../../substrate/runtime-std", default_features = false } + +[dev-dependencies] +substrate-serializer = { path = "../../substrate/serializer" } +pretty_assertions = "0.4" + +[features] +default = ["std"] +std = [ + "substrate-codec/std", + "substrate-primitives/std", + "substrate-runtime-std/std", + "serde_derive", + "serde/std", +] diff --git a/polkadot/primitives/src/block.rs b/polkadot/primitives/src/block.rs new file mode 100644 index 0000000000..5ebe53007f --- /dev/null +++ b/polkadot/primitives/src/block.rs @@ -0,0 +1,197 @@ +// 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 . + +//! Block and header type definitions. + +#[cfg(feature = "std")] +use primitives::bytes; +use primitives::H256; +use rstd::vec::Vec; +use codec::Slicable; +use transaction::UncheckedTransaction; + +/// Used to refer to a block number. +pub type Number = u64; + +/// Hash used to refer to a block hash. +pub type HeaderHash = H256; + +/// Hash used to refer to a transaction hash. +pub type TransactionHash = H256; + +/// Execution log (event) +#[derive(PartialEq, Eq, Clone)] +#[cfg_attr(feature = "std", derive(Serialize, Deserialize, Debug))] +pub struct Log(#[cfg_attr(feature = "std", serde(with="bytes"))] pub Vec); + +impl Slicable for Log { + fn from_slice(value: &mut &[u8]) -> Option { + Vec::::from_slice(value).map(Log) + } + + fn as_slice_then R>(&self, f: F) -> R { + self.0.as_slice_then(f) + } +} + +impl ::codec::NonTrivialSlicable for Log { } + +/// The digest of a block, useful for light-clients. +#[derive(Clone, Default, PartialEq, Eq)] +#[cfg_attr(feature = "std", derive(Serialize, Deserialize, Debug))] +pub struct Digest { + /// All logs that have happened in the block. + pub logs: Vec, +} + +impl Slicable for Digest { + fn from_slice(value: &mut &[u8]) -> Option { + Vec::::from_slice(value).map(|logs| Digest { logs }) + } + + fn as_slice_then R>(&self, f: F) -> R { + self.logs.as_slice_then(f) + } +} + +/// The block "body": A bunch of transactions. +pub type Body = Vec; + +/// A Polkadot relay chain block. +#[derive(PartialEq, Eq, Clone)] +#[cfg_attr(feature = "std", derive(Serialize, Deserialize, Debug))] +pub struct Block { + /// The block header. + pub header: Header, + /// All relay-chain transactions. + pub transactions: Body, +} + +impl Slicable for Block { + fn from_slice(value: &mut &[u8]) -> Option { + Some(Block { + header: try_opt!(Slicable::from_slice(value)), + transactions: try_opt!(Slicable::from_slice(value)), + }) + } + + fn to_vec(&self) -> Vec { + let mut v = Vec::new(); + + v.extend(self.header.to_vec()); + v.extend(self.transactions.to_vec()); + + v + } + + fn as_slice_then R>(&self, f: F) -> R { + f(self.to_vec().as_slice()) + } +} + +/// A relay chain block header. +/// +/// https://github.com/w3f/polkadot-spec/blob/master/spec.md#header +#[derive(PartialEq, Eq, Clone)] +#[cfg_attr(feature = "std", derive(Serialize, Deserialize, Debug))] +#[cfg_attr(feature = "std", serde(rename_all = "camelCase"))] +#[cfg_attr(feature = "std", serde(deny_unknown_fields))] +pub struct Header { + /// Block parent's hash. + pub parent_hash: HeaderHash, + /// Block number. + pub number: Number, + /// State root after this transition. + pub state_root: H256, + /// The root of the trie that represents this block's transactions, indexed by a 32-byte integer. + pub transaction_root: H256, + /// The digest of activity on the block. + pub digest: Digest, +} + +impl Header { + /// Create a new instance with default fields except `number`, which is given as an argument. + pub fn from_block_number(number: Number) -> Self { + Header { + parent_hash: Default::default(), + number, + state_root: Default::default(), + transaction_root: Default::default(), + digest: Default::default(), + } + } +} + +impl Slicable for Header { + fn from_slice(value: &mut &[u8]) -> Option { + Some(Header { + parent_hash: try_opt!(Slicable::from_slice(value)), + number: try_opt!(Slicable::from_slice(value)), + state_root: try_opt!(Slicable::from_slice(value)), + transaction_root: try_opt!(Slicable::from_slice(value)), + digest: try_opt!(Slicable::from_slice(value)), + }) + } + + fn to_vec(&self) -> Vec { + let mut v = Vec::new(); + + self.parent_hash.as_slice_then(|s| v.extend(s)); + self.number.as_slice_then(|s| v.extend(s)); + self.state_root.as_slice_then(|s| v.extend(s)); + self.transaction_root.as_slice_then(|s| v.extend(s)); + self.digest.as_slice_then(|s| v.extend(s)); + + v + } + + fn as_slice_then R>(&self, f: F) -> R { + f(self.to_vec().as_slice()) + } +} + +#[cfg(test)] +mod tests { + use super::*; + use codec::Slicable; + use substrate_serializer as ser; + + #[test] + fn test_header_serialization() { + let header = Header { + parent_hash: 5.into(), + number: 67, + state_root: 3.into(), + transaction_root: 6.into(), + digest: Digest { logs: vec![Log(vec![1])] }, + }; + + assert_eq!(ser::to_string_pretty(&header), r#"{ + "parentHash": "0x0000000000000000000000000000000000000000000000000000000000000005", + "number": 67, + "stateRoot": "0x0000000000000000000000000000000000000000000000000000000000000003", + "transactionRoot": "0x0000000000000000000000000000000000000000000000000000000000000006", + "digest": { + "logs": [ + "0x01" + ] + } +}"#); + + let v = header.to_vec(); + assert_eq!(Header::from_slice(&mut &v[..]).unwrap(), header); + } +} diff --git a/polkadot/primitives/src/lib.rs b/polkadot/primitives/src/lib.rs new file mode 100644 index 0000000000..83cb3be1cd --- /dev/null +++ b/polkadot/primitives/src/lib.rs @@ -0,0 +1,78 @@ +// 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 . + +//! Shareable Polkadot types. + +#![warn(missing_docs)] + +#![cfg_attr(not(feature = "std"), no_std)] +#![cfg_attr(not(feature = "std"), feature(alloc))] + + +#[cfg(feature = "std")] +#[macro_use] +extern crate serde_derive; +#[cfg(feature = "std")] +extern crate serde; + +extern crate substrate_runtime_std as rstd; +extern crate substrate_codec as codec; +extern crate substrate_primitives as primitives; +#[cfg(test)] +extern crate substrate_serializer; + +macro_rules! try_opt { + ($e: expr) => { + match $e { + Some(x) => x, + None => return None, + } + } +} + +pub mod parachain; +pub mod validator; +pub mod block; +pub mod transaction; + +pub use self::block::{Header, Block, Log, Digest}; +pub use self::block::Number as BlockNumber; +pub use self::transaction::{Transaction, UncheckedTransaction, Function, Proposal}; + +/// Virtual account ID that represents the idea of a dispatch/statement being signed by everybody +/// (who matters). Essentially this means that a majority of validators have decided it is +/// "correct". +pub const EVERYBODY: AccountId = [255u8; 32]; + +/// Alias to Ed25519 pubkey that identifies an account on the relay chain. This will almost +/// certainly continue to be the same as the substrate's `AuthorityId`. +pub type AccountId = primitives::AuthorityId; + +/// The Ed25519 pub key of an session that belongs to an authority of the relay chain. This is +/// exactly equivalent to what the substrate calls an "authority". +pub type SessionKey = primitives::AuthorityId; + +/// Indentifier for a chain. +pub type ChainID = u64; + +/// Index of a transaction in the relay chain. +pub type TxOrder = u64; + +/// A hash of some data used by the relay chain. +pub type Hash = primitives::H256; + +/// Alias to 520-bit hash when used in the context of a signature on the relay chain. +pub type Signature = primitives::hash::H512; diff --git a/polkadot/primitives/src/parachain.rs b/polkadot/primitives/src/parachain.rs new file mode 100644 index 0000000000..75d5b6a326 --- /dev/null +++ b/polkadot/primitives/src/parachain.rs @@ -0,0 +1,173 @@ +// 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 . + +//! Parachain data types. + +#[cfg(feature = "std")] +use primitives::bytes; +use primitives; +use rstd::vec::Vec; + +/// Unique identifier of a parachain. +#[derive(PartialEq, Eq, PartialOrd, Ord, Hash, Clone, Copy)] +#[cfg_attr(feature = "std", derive(Serialize, Deserialize, Debug))] +pub struct Id(u64); + +impl From for u64 { + fn from(x: Id) -> Self { x.0 } +} + +impl From for Id { + fn from(x: u64) -> Self { Id(x) } +} + +impl ::codec::Slicable for Id { + fn from_slice(value: &mut &[u8]) -> Option { + u64::from_slice(value).map(Id) + } + + fn as_slice_then R>(&self, f: F) -> R { + self.0.as_slice_then(f) + } +} + +/// Candidate parachain block. +/// +/// https://github.com/w3f/polkadot-spec/blob/master/spec.md#candidate-para-chain-block +#[derive(PartialEq, Eq, Clone)] +#[cfg_attr(feature = "std", derive(Serialize, Deserialize, Debug))] +#[cfg_attr(feature = "std", serde(rename_all = "camelCase"))] +#[cfg_attr(feature = "std", serde(deny_unknown_fields))] +pub struct Candidate { + /// The ID of the parachain this is a proposal for. + pub parachain_index: Id, + /// Collator's signature + pub collator_signature: ::Signature, + /// Unprocessed ingress queue. + /// + /// Ordered by parachain ID and block number. + pub unprocessed_ingress: ConsolidatedIngress, + /// Block data + pub block: BlockData, +} + +/// Candidate receipt type. +#[derive(PartialEq, Eq, Clone)] +#[cfg_attr(feature = "std", derive(Debug, Serialize, Deserialize))] +#[cfg_attr(feature = "std", serde(rename_all = "camelCase"))] +#[cfg_attr(feature = "std", serde(deny_unknown_fields))] +pub struct CandidateReceipt { + /// The ID of the parachain this is a candidate for. + pub parachain_index: Id, + /// The collator's relay-chain account ID + pub collator: ::AccountId, + /// The head-data + pub head_data: HeadData, + /// Balance uploads to the relay chain. + pub balance_uploads: Vec<(::AccountId, u64)>, + /// Egress queue roots. + pub egress_queue_roots: Vec<(Id, primitives::H256)>, + /// Fees paid from the chain to the relay chain validators + pub fees: u64, +} + +/// Parachain ingress queue message. +#[derive(PartialEq, Eq, Clone)] +#[cfg_attr(feature = "std", derive(Serialize, Deserialize, Debug))] +pub struct Message(#[cfg_attr(feature = "std", serde(with="bytes"))] pub Vec); + +/// Consolidated ingress queue data. +/// +/// This is just an ordered vector of other parachains' egress queues, +/// obtained according to the routing rules. +#[derive(Default, PartialEq, Eq, Clone)] +#[cfg_attr(feature = "std", derive(Serialize, Deserialize, Debug))] +pub struct ConsolidatedIngress(pub Vec<(Id, Vec)>); + +/// Parachain block data. +/// +/// contains everything required to validate para-block, may contain block and witness data +#[derive(PartialEq, Eq, Clone)] +#[cfg_attr(feature = "std", derive(Serialize, Deserialize, Debug))] +pub struct BlockData(#[cfg_attr(feature = "std", serde(with="bytes"))] pub Vec); + +/// Parachain header raw bytes wrapper type. +#[derive(PartialEq, Eq)] +#[cfg_attr(feature = "std", derive(Serialize, Deserialize, Debug))] +pub struct Header(#[cfg_attr(feature = "std", serde(with="bytes"))] pub Vec); + +/// Parachain head data included in the chain. +#[derive(PartialEq, Eq, Clone)] +#[cfg_attr(feature = "std", derive(Serialize, Deserialize, Debug))] +pub struct HeadData(#[cfg_attr(feature = "std", serde(with="bytes"))] pub Vec); + +/// Parachain validation code. +#[derive(PartialEq, Eq)] +#[cfg_attr(feature = "std", derive(Serialize, Deserialize, Debug))] +pub struct ValidationCode(#[cfg_attr(feature = "std", serde(with="bytes"))] pub Vec); + +/// Activitiy bit field +#[derive(PartialEq, Eq, Clone, Default)] +#[cfg_attr(feature = "std", derive(Serialize, Deserialize, Debug))] +pub struct Activity(#[cfg_attr(feature = "std", serde(with="bytes"))] pub Vec); + +impl ::codec::Slicable for Activity { + fn from_slice(value: &mut &[u8]) -> Option { + Vec::::from_slice(value).map(Activity) + } + + fn as_slice_then R>(&self, f: F) -> R { + self.0.as_slice_then(f) + } +} + +#[cfg(test)] +mod tests { + use super::*; + use substrate_serializer as ser; + + #[test] + fn test_candidate() { + assert_eq!(ser::to_string_pretty(&Candidate { + parachain_index: 5.into(), + collator_signature: 10.into(), + 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, + "collatorSignature": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000a", + "unprocessedIngress": [ + [ + 1, + [ + "0x02" + ] + ], + [ + 2, + [ + "0x02", + "0x03" + ] + ] + ], + "block": "0x010203" +}"#); + } +} diff --git a/polkadot/primitives/src/transaction.rs b/polkadot/primitives/src/transaction.rs new file mode 100644 index 0000000000..062d8c563d --- /dev/null +++ b/polkadot/primitives/src/transaction.rs @@ -0,0 +1,403 @@ +// 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 . + +//! Transaction type. + +use rstd::vec::Vec; +use codec::Slicable; + +#[cfg(feature = "std")] +use std::fmt; + +use block::Number as BlockNumber; + +#[derive(Clone, Copy, PartialEq, Eq)] +#[cfg_attr(feature = "std", derive(Serialize, Deserialize, Debug))] +#[repr(u8)] +enum InternalFunctionId { + /// Set the system's code. + SystemSetCode = 0x00, + + /// Set the session length. + SessionSetLength = 0x10, + /// Force a new session. + SessionForceNewSession = 0x11, + + /// Set the number of sessions per era. + StakingSetSessionsPerEra = 0x20, + /// Set the minimum bonding duration for staking. + StakingSetBondingDuration = 0x21, + /// Set the validator count for staking. + StakingSetValidatorCount = 0x22, + /// Force a new staking era. + StakingForceNewEra = 0x23, + + /// Set the per-mille of validator approval required for governance changes. + GovernanceSetApprovalPpmRequired = 0x30, + +} + +impl InternalFunctionId { + /// Derive `Some` value from a `u8`, or `None` if it's invalid. + fn from_u8(value: u8) -> Option { + let functions = [ + InternalFunctionId::SystemSetCode, + InternalFunctionId::SessionSetLength, + InternalFunctionId::SessionForceNewSession, + InternalFunctionId::StakingSetSessionsPerEra, + InternalFunctionId::StakingSetBondingDuration, + InternalFunctionId::StakingSetValidatorCount, + InternalFunctionId::StakingForceNewEra, + InternalFunctionId::GovernanceSetApprovalPpmRequired, + ]; + functions.iter().map(|&f| f).find(|&f| value == f as u8) + } +} + +/// Internal functions that can be dispatched to. +#[derive(Clone, PartialEq, Eq)] +#[cfg_attr(feature = "std", derive(Serialize, Deserialize, Debug))] +pub enum Proposal { + /// Set the system's code. + SystemSetCode(Vec), + /// Set the session length. + SessionSetLength(BlockNumber), + /// Force a new session. + SessionForceNewSession, + /// Set the number of sessions per era. + StakingSetSessionsPerEra(BlockNumber), + /// Set the minimum bonding duration for staking. + StakingSetBondingDuration(BlockNumber), + /// Set the validator count for staking. + StakingSetValidatorCount(u32), + /// Force a new staking era. + StakingForceNewEra, + /// Set the per-mille of validator approval required for governance changes. + GovernanceSetApprovalPpmRequired(u32), + +} + +impl Slicable for Proposal { + fn from_slice(value: &mut &[u8]) -> Option { + let id = try_opt!(u8::from_slice(value).and_then(InternalFunctionId::from_u8)); + let function = match id { + InternalFunctionId::SystemSetCode => + Proposal::SystemSetCode(try_opt!(Slicable::from_slice(value))), + InternalFunctionId::SessionSetLength => + Proposal::SessionSetLength(try_opt!(Slicable::from_slice(value))), + InternalFunctionId::SessionForceNewSession => Proposal::SessionForceNewSession, + InternalFunctionId::StakingSetSessionsPerEra => + Proposal::StakingSetSessionsPerEra(try_opt!(Slicable::from_slice(value))), + InternalFunctionId::StakingSetBondingDuration => + Proposal::StakingSetBondingDuration(try_opt!(Slicable::from_slice(value))), + InternalFunctionId::StakingSetValidatorCount => + Proposal::StakingSetValidatorCount(try_opt!(Slicable::from_slice(value))), + InternalFunctionId::StakingForceNewEra => Proposal::StakingForceNewEra, + InternalFunctionId::GovernanceSetApprovalPpmRequired => + Proposal::GovernanceSetApprovalPpmRequired(try_opt!(Slicable::from_slice(value))), + }; + + Some(function) + } + + fn to_vec(&self) -> Vec { + let mut v = Vec::new(); + match *self { + Proposal::SystemSetCode(ref data) => { + (InternalFunctionId::SystemSetCode as u8).as_slice_then(|s| v.extend(s)); + data.as_slice_then(|s| v.extend(s)); + } + Proposal::SessionSetLength(ref data) => { + (InternalFunctionId::SessionSetLength as u8).as_slice_then(|s| v.extend(s)); + data.as_slice_then(|s| v.extend(s)); + } + Proposal::SessionForceNewSession => { + (InternalFunctionId::SessionForceNewSession as u8).as_slice_then(|s| v.extend(s)); + } + Proposal::StakingSetSessionsPerEra(ref data) => { + (InternalFunctionId::StakingSetSessionsPerEra as u8).as_slice_then(|s| v.extend(s)); + data.as_slice_then(|s| v.extend(s)); + } + Proposal::StakingSetBondingDuration(ref data) => { + (InternalFunctionId::StakingSetBondingDuration as u8).as_slice_then(|s| v.extend(s)); + data.as_slice_then(|s| v.extend(s)); + } + Proposal::StakingSetValidatorCount(ref data) => { + (InternalFunctionId::StakingSetValidatorCount as u8).as_slice_then(|s| v.extend(s)); + data.as_slice_then(|s| v.extend(s)); + } + Proposal::StakingForceNewEra => { + (InternalFunctionId::StakingForceNewEra as u8).as_slice_then(|s| v.extend(s)); + } + Proposal::GovernanceSetApprovalPpmRequired(ref data) => { + (InternalFunctionId::GovernanceSetApprovalPpmRequired as u8).as_slice_then(|s| v.extend(s)); + data.as_slice_then(|s| v.extend(s)); + } + } + + v + } + + fn as_slice_then R>(&self, f: F) -> R { + f(self.to_vec().as_slice()) + } +} + + +/// Public functions that can be dispatched to. +#[derive(Clone, Copy, PartialEq, Eq)] +#[cfg_attr(feature = "std", derive(Serialize, Deserialize, Debug))] +#[repr(u8)] +enum FunctionId { + /// Set the timestamp. + TimestampSet = 0x00, + /// Set temporary session key as a validator. + SessionSetKey = 0x10, + /// Staking subsystem: begin staking. + StakingStake = 0x20, + /// Staking subsystem: stop staking. + StakingUnstake = 0x21, + /// Staking subsystem: transfer stake. + StakingTransfer = 0x22, + /// Make a proposal for the governance system. + GovernancePropose = 0x30, + /// Approve a proposal for the governance system. + GovernanceApprove = 0x31, +} + +impl FunctionId { + /// Derive `Some` value from a `u8`, or `None` if it's invalid. + fn from_u8(value: u8) -> Option { + use self::*; + let functions = [FunctionId::StakingStake, FunctionId::StakingUnstake, + FunctionId::StakingTransfer, FunctionId::SessionSetKey, FunctionId::TimestampSet, + FunctionId::GovernancePropose, FunctionId::GovernanceApprove]; + functions.iter().map(|&f| f).find(|&f| value == f as u8) + } +} + +/// Functions on the runtime. +#[derive(Clone, PartialEq, Eq)] +#[cfg_attr(feature = "std", derive(Serialize, Deserialize, Debug))] +pub enum Function { + /// Set the timestamp. + TimestampSet(u64), + /// Set temporary session key as a validator. + SessionSetKey(::SessionKey), + /// Staking subsystem: begin staking. + StakingStake, + /// Staking subsystem: stop staking. + StakingUnstake, + /// Staking subsystem: transfer stake. + StakingTransfer(::AccountId, u64), + /// Make a proposal for the governance system. + GovernancePropose(Proposal), + /// Approve a proposal for the governance system. + GovernanceApprove(BlockNumber), +} + +impl Slicable for Function { + fn from_slice(value: &mut &[u8]) -> Option { + let id = try_opt!(u8::from_slice(value).and_then(FunctionId::from_u8)); + Some(match id { + FunctionId::TimestampSet => + Function::TimestampSet(try_opt!(Slicable::from_slice(value))), + FunctionId::SessionSetKey => + Function::SessionSetKey(try_opt!(Slicable::from_slice(value))), + FunctionId::StakingStake => Function::StakingStake, + FunctionId::StakingUnstake => Function::StakingUnstake, + FunctionId::StakingTransfer => { + let to = try_opt!(Slicable::from_slice(value)); + let amount = try_opt!(Slicable::from_slice(value)); + + Function::StakingTransfer(to, amount) + } + FunctionId::GovernancePropose => + Function::GovernancePropose(try_opt!(Slicable::from_slice(value))), + FunctionId::GovernanceApprove => + Function::GovernanceApprove(try_opt!(Slicable::from_slice(value))), + }) + } + + fn to_vec(&self) -> Vec { + let mut v = Vec::new(); + match *self { + Function::TimestampSet(ref data) => { + (FunctionId::TimestampSet as u8).as_slice_then(|s| v.extend(s)); + data.as_slice_then(|s| v.extend(s)); + } + Function::SessionSetKey(ref data) => { + (FunctionId::SessionSetKey as u8).as_slice_then(|s| v.extend(s)); + data.as_slice_then(|s| v.extend(s)); + } + Function::StakingStake => { + (FunctionId::StakingStake as u8).as_slice_then(|s| v.extend(s)); + } + Function::StakingUnstake => { + (FunctionId::StakingUnstake as u8).as_slice_then(|s| v.extend(s)); + } + Function::StakingTransfer(ref to, ref amount) => { + (FunctionId::StakingTransfer as u8).as_slice_then(|s| v.extend(s)); + to.as_slice_then(|s| v.extend(s)); + amount.as_slice_then(|s| v.extend(s)); + } + Function::GovernancePropose(ref data) => { + (FunctionId::GovernancePropose as u8).as_slice_then(|s| v.extend(s)); + data.as_slice_then(|s| v.extend(s)); + } + Function::GovernanceApprove(ref data) => { + (FunctionId::GovernanceApprove as u8).as_slice_then(|s| v.extend(s)); + data.as_slice_then(|s| v.extend(s)); + } + } + + v + } + + fn as_slice_then R>(&self, f: F) -> R { + f(self.to_vec().as_slice()) + } +} + +/// A vetted and verified transaction from the external world. +#[derive(PartialEq, Eq, Clone)] +#[cfg_attr(feature = "std", derive(Serialize, Deserialize, Debug))] +pub struct Transaction { + /// Who signed it (note this is not a signature). + pub signed: super::AccountId, + /// The number of transactions have come before from the same signer. + pub nonce: super::TxOrder, + /// The function that should be called. + pub function: Function, +} + +impl Slicable for Transaction { + fn from_slice(value: &mut &[u8]) -> Option { + Some(Transaction { + signed: try_opt!(Slicable::from_slice(value)), + nonce: try_opt!(Slicable::from_slice(value)), + function: try_opt!(Slicable::from_slice(value)), + }) + } + + fn to_vec(&self) -> Vec { + let mut v = Vec::new(); + + self.signed.as_slice_then(|s| v.extend(s)); + self.nonce.as_slice_then(|s| v.extend(s)); + self.function.as_slice_then(|s| v.extend(s)); + + v + } + + fn as_slice_then R>(&self, f: F) -> R { + f(self.to_vec().as_slice()) + } +} + +impl ::codec::NonTrivialSlicable for Transaction {} + +/// A transactions right from the external world. Unchecked. +#[derive(Eq, Clone)] +#[cfg_attr(feature = "std", derive(Serialize, Deserialize))] +pub struct UncheckedTransaction { + /// The actual transaction information. + pub transaction: Transaction, + /// The signature; should be an Ed25519 signature applied to the serialised `transaction` field. + pub signature: super::Signature, +} + +impl Slicable for UncheckedTransaction { + fn from_slice(value: &mut &[u8]) -> Option { + // This is a little more complicated than usua since the binary format must be compatible + // with substrate's generic `Vec` type. Basically this just means accepting that there + // will be a prefix of u32, which has the total number of bytes following (we don't need + // to use this). + let _length_do_not_remove_me_see_above: u32 = try_opt!(Slicable::from_slice(value)); + + Some(UncheckedTransaction { + transaction: try_opt!(Slicable::from_slice(value)), + signature: try_opt!(Slicable::from_slice(value)), + }) + } + + fn to_vec(&self) -> Vec { + let mut v = Vec::new(); + + // need to prefix with the total length as u32 to ensure it's binary comptible with + // Vec. we'll make room for it here, then overwrite once we know the length. + v.extend(&[0u8; 4]); + + self.transaction.signed.as_slice_then(|s| v.extend(s)); + self.transaction.nonce.as_slice_then(|s| v.extend(s)); + self.transaction.function.as_slice_then(|s| v.extend(s)); + self.signature.as_slice_then(|s| v.extend(s)); + + let length = (v.len() - 4) as u32; + length.as_slice_then(|s| v[0..4].copy_from_slice(s)); + + v + } + + fn as_slice_then R>(&self, f: F) -> R { + f(self.to_vec().as_slice()) + } +} + +impl ::codec::NonTrivialSlicable for UncheckedTransaction {} + +impl PartialEq for UncheckedTransaction { + fn eq(&self, other: &Self) -> bool { + self.signature.iter().eq(other.signature.iter()) && self.transaction == other.transaction + } +} + +#[cfg(feature = "std")] +impl fmt::Debug for UncheckedTransaction { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "UncheckedTransaction({:?})", self.transaction) + } +} + +#[cfg(test)] +mod tests { + use super::*; + use primitives; + use ::codec::Slicable; + use primitives::hexdisplay::HexDisplay; + + #[test] + fn serialize_unchecked() { + let tx = UncheckedTransaction { + transaction: Transaction { + signed: [1; 32], + nonce: 999u64, + function: Function::TimestampSet(135135), + }, + signature: primitives::hash::H512([0; 64]), + }; + // 71000000 + // 0101010101010101010101010101010101010101010101010101010101010101 + // e703000000000000 + // 00 + // df0f0200 + // 0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 + + let v = Slicable::to_vec(&tx); + println!("{}", HexDisplay::from(&v)); + assert_eq!(UncheckedTransaction::from_slice(&mut &v[..]).unwrap(), tx); + } +} diff --git a/polkadot/primitives/src/validator.rs b/polkadot/primitives/src/validator.rs new file mode 100644 index 0000000000..fa622fb629 --- /dev/null +++ b/polkadot/primitives/src/validator.rs @@ -0,0 +1,76 @@ +// 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 . + +//! Validator primitives. + +#[cfg(feature = "std")] +use primitives::bytes; +use rstd::vec::Vec; +use parachain; + +/// Parachain outgoing message. +#[derive(PartialEq, Eq)] +#[cfg_attr(feature = "std", derive(Serialize, Deserialize, Debug))] +pub struct EgressPost(#[cfg_attr(feature = "std", serde(with="bytes"))] pub Vec); + +/// Balance upload. +#[derive(PartialEq, Eq)] +#[cfg_attr(feature = "std", derive(Serialize, Deserialize, Debug))] +pub struct BalanceUpload(#[cfg_attr(feature = "std", serde(with="bytes"))] pub Vec); + +/// Balance download. +#[derive(PartialEq, Eq)] +#[cfg_attr(feature = "std", derive(Serialize, Deserialize, Debug))] +pub struct BalanceDownload(#[cfg_attr(feature = "std", serde(with="bytes"))] pub Vec); + +/// The result of parachain validation. +#[derive(PartialEq, Eq)] +#[cfg_attr(feature = "std", derive(Serialize, Deserialize, Debug))] +#[cfg_attr(feature = "std", serde(rename_all = "camelCase"))] +#[cfg_attr(feature = "std", serde(deny_unknown_fields))] +pub struct ValidationResult { + /// New head data that should be included in the relay chain state. + pub head_data: parachain::HeadData, + /// Outgoing messages (a vec for each parachain). + pub egress_queues: Vec>, + /// Balance uploads + pub balance_uploads: Vec, +} + +#[cfg(test)] +mod tests { + use super::*; + use substrate_serializer as ser; + + #[test] + fn test_validation_result() { + assert_eq!(ser::to_string_pretty(&ValidationResult { + head_data: parachain::HeadData(vec![1]), + egress_queues: vec![vec![EgressPost(vec![1])]], + balance_uploads: vec![BalanceUpload(vec![2])], + }), r#"{ + "headData": "0x01", + "egressQueues": [ + [ + "0x01" + ] + ], + "balanceUploads": [ + "0x02" + ] +}"#); + } +} diff --git a/polkadot/runtime/Cargo.toml b/polkadot/runtime/Cargo.toml new file mode 100644 index 0000000000..d3b3cfb34f --- /dev/null +++ b/polkadot/runtime/Cargo.toml @@ -0,0 +1,25 @@ +[package] +name = "polkadot-runtime" +version = "0.1.0" +authors = ["Parity Technologies "] + +[dependencies] +rustc-hex = "1.0" +hex-literal = "0.1.0" +log = { version = "0.3", optional = true } +substrate-codec = { path = "../../substrate/codec" } +substrate-runtime-std = { path = "../../substrate/runtime-std" } +substrate-runtime-io = { path = "../../substrate/runtime-io" } +substrate-primitives = { path = "../../substrate/primitives" } +polkadot-primitives = { path = "../primitives" } + +[features] +default = ["std"] +std = [ + "substrate-codec/std", + "substrate-runtime-std/std", + "substrate-runtime-io/std", + "substrate-primitives/std", + "polkadot-primitives/std", + "log" +] diff --git a/polkadot/runtime/src/genesismap.rs b/polkadot/runtime/src/genesismap.rs new file mode 100644 index 0000000000..a12086cc47 --- /dev/null +++ b/polkadot/runtime/src/genesismap.rs @@ -0,0 +1,91 @@ +// 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 . + +//! Tool for creating the genesis block. + +use std::collections::HashMap; +use runtime_io::twox_128; +use codec::{KeyedVec, Joiner}; +use support::Hashable; +use polkadot_primitives::{BlockNumber, Block, AccountId}; +use runtime::staking::Balance; + +/// Configuration of a general Polkadot genesis block. +pub struct GenesisConfig { + pub validators: Vec, + pub authorities: Vec, + pub balances: Vec<(AccountId, Balance)>, + pub block_time: u64, + pub session_length: BlockNumber, + pub sessions_per_era: BlockNumber, + pub bonding_duration: BlockNumber, + pub approval_ratio: u32, +} + +impl GenesisConfig { + pub fn new_simple(authorities_validators: Vec, balance: Balance) -> Self { + GenesisConfig { + validators: authorities_validators.clone(), + authorities: authorities_validators.clone(), + balances: authorities_validators.iter().map(|v| (v.clone(), balance)).collect(), + block_time: 5, // 5 second block time. + session_length: 720, // that's 1 hour per session. + sessions_per_era: 24, // 24 hours per era. + bonding_duration: 90, // 90 days per bond. + approval_ratio: 667, // 66.7% approvals required for legislation. + } + } + + pub fn genesis_map(&self) -> HashMap, Vec> { + let wasm_runtime = include_bytes!("../wasm/genesis.wasm").to_vec(); + vec![ + (&b"gov:apr"[..], vec![].join(&self.approval_ratio)), + (&b"ses:len"[..], vec![].join(&self.session_length)), + (&b"ses:val:len"[..], vec![].join(&(self.validators.len() as u32))), + (&b"sta:wil:len"[..], vec![].join(&0u32)), + (&b"sta:spe"[..], vec![].join(&self.sessions_per_era)), + (&b"sta:vac"[..], vec![].join(&(self.validators.len() as u32))), + (&b"sta:era"[..], vec![].join(&0u64)), + ].into_iter() + .map(|(k, v)| (k.into(), v)) + .chain(self.validators.iter() + .enumerate() + .map(|(i, account)| ((i as u32).to_keyed_vec(b"ses:val:"), vec![].join(account))) + ).chain(self.authorities.iter() + .enumerate() + .map(|(i, account)| ((i as u32).to_keyed_vec(b":auth:"), vec![].join(account))) + ).chain(self.balances.iter() + .map(|&(account, balance)| (account.to_keyed_vec(b"sta:bal:"), vec![].join(&balance))) + ) + .map(|(k, v)| (twox_128(&k[..])[..].to_vec(), v.to_vec())) + .chain(vec![ + (b":code"[..].into(), wasm_runtime), + (b":auth:len"[..].into(), vec![].join(&(self.authorities.len() as u32))), + ].into_iter()) + .chain(self.authorities.iter() + .enumerate() + .map(|(i, account)| ((i as u32).to_keyed_vec(b":auth:"), vec![].join(account))) + ) + .collect() + } +} + +pub fn additional_storage_with_genesis(genesis_block: &Block) -> HashMap, Vec> { + use codec::Slicable; + map![ + twox_128(&0u64.to_keyed_vec(b"sys:old:")).to_vec() => genesis_block.header.blake2_256().to_vec() + ] +} diff --git a/polkadot/runtime/src/lib.rs b/polkadot/runtime/src/lib.rs new file mode 100644 index 0000000000..46e4b6c860 --- /dev/null +++ b/polkadot/runtime/src/lib.rs @@ -0,0 +1,121 @@ +// 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 Polkadot runtime. This can be compiled with #[no_std], ready for Wasm. + +#![cfg_attr(not(feature = "std"), no_std)] + +extern crate substrate_runtime_std as rstd; + +#[macro_use] +extern crate substrate_runtime_io as runtime_io; + +#[cfg(feature = "std")] +extern crate rustc_hex; + +extern crate substrate_codec as codec; +extern crate substrate_primitives; +extern crate polkadot_primitives; + +#[cfg(test)] +#[macro_use] +extern crate hex_literal; + +#[macro_use] +pub mod support; +pub mod runtime; + +#[cfg(feature = "std")] +pub mod genesismap; + +use rstd::prelude::*; +use codec::Slicable; +use polkadot_primitives::{Header, Block, UncheckedTransaction}; + +/// Type definitions and helpers for transactions. +pub mod transaction { + use rstd::ops; + use polkadot_primitives::Signature; + pub use polkadot_primitives::{Transaction, UncheckedTransaction}; + + /// A type-safe indicator that a transaction has been checked. + #[derive(PartialEq, Eq, Clone)] + #[cfg_attr(feature = "std", derive(Debug))] + pub struct CheckedTransaction(UncheckedTransaction); + + impl CheckedTransaction { + /// Get a reference to the checked signature. + pub fn signature(&self) -> &Signature { + &self.0.signature + } + } + + impl ops::Deref for CheckedTransaction { + type Target = Transaction; + + fn deref(&self) -> &Transaction { + &self.0.transaction + } + } + + /// Check the signature on a transaction. + /// + /// On failure, return the transaction back. + pub fn check(tx: UncheckedTransaction) -> Result { + let msg = ::codec::Slicable::to_vec(&tx.transaction); + if ::runtime_io::ed25519_verify(&tx.signature.0, &msg, &tx.transaction.signed) { + Ok(CheckedTransaction(tx)) + } else { + Err(tx) + } + } +} + +/// Execute a block, with `input` being the canonical serialisation of the block. Returns the +/// empty vector. +pub fn execute_block(mut input: &[u8]) -> Vec { + runtime::system::internal::execute_block(Block::from_slice(&mut input).unwrap()); + Vec::new() +} + +/// Execute a given, serialised, transaction. Returns the empty vector. +pub fn execute_transaction(mut input: &[u8]) -> Vec { + let header = Header::from_slice(&mut input).unwrap(); + let utx = UncheckedTransaction::from_slice(&mut input).unwrap(); + let header = runtime::system::internal::execute_transaction(utx, header); + header.to_vec() +} + +/// Execute a given, serialised, transaction. Returns the empty vector. +pub fn finalise_block(mut input: &[u8]) -> Vec { + let header = Header::from_slice(&mut input).unwrap(); + let header = runtime::system::internal::finalise_block(header); + header.to_vec() +} + +/// Run whatever tests we have. +pub fn run_tests(mut input: &[u8]) -> Vec { + use runtime_io::print; + + print("run_tests..."); + let block = Block::from_slice(&mut input).unwrap(); + print("deserialised block."); + let stxs = block.transactions.iter().map(Slicable::to_vec).collect::>(); + print("reserialised transactions."); + [stxs.len() as u8].to_vec() +} + +impl_stubs!(execute_block, execute_transaction, finalise_block, run_tests); diff --git a/polkadot/runtime/src/runtime/consensus.rs b/polkadot/runtime/src/runtime/consensus.rs new file mode 100644 index 0000000000..bbc62eeba0 --- /dev/null +++ b/polkadot/runtime/src/runtime/consensus.rs @@ -0,0 +1,48 @@ +// 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 . + +//! Conensus module for runtime; manages the authority set ready for the native code. + +use rstd::prelude::*; +use support::storage::unhashed::StorageVec; +use polkadot_primitives::SessionKey; + +struct AuthorityStorageVec {} +impl StorageVec for AuthorityStorageVec { + type Item = SessionKey; + const PREFIX: &'static[u8] = b":auth:"; +} + +/// Get the current set of authorities. These are the session keys. +pub fn authorities() -> Vec { + AuthorityStorageVec::items() +} + +pub mod internal { + use super::*; + + /// Set the current set of authorities' session keys. + /// + /// Called by `next_session` only. + pub fn set_authorities(authorities: &[SessionKey]) { + AuthorityStorageVec::set_items(authorities); + } + + /// Set a single authority by index. + pub fn set_authority(index: u32, key: &SessionKey) { + AuthorityStorageVec::set_item(index, key); + } +} diff --git a/polkadot/runtime/src/runtime/governance.rs b/polkadot/runtime/src/runtime/governance.rs new file mode 100644 index 0000000000..a7d2baf677 --- /dev/null +++ b/polkadot/runtime/src/runtime/governance.rs @@ -0,0 +1,370 @@ +// 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 . + +//! Governance system: Handles administration and dispatch of sensitive operations including +//! setting new code, minting new tokens and changing parameters. +//! +//! For now this is limited to a simple qualified majority vote (whose parameter is retrieved from +//! storage) between validators. A single vote may be proposed per era, and at most one approval +//! vote may be cast by each validator. The tally is maintained through a simple tag in storage for +//! each validator that has approved. +//! +//! At the end of the era, all validators approvals are tallied and if there are sufficient to pass +//! the proposal then it is enacted. All items in storage concerning the proposal are reset. + +use rstd::prelude::*; +use codec::KeyedVec; +use support::storage; +use polkadot_primitives::{Proposal, AccountId, Hash, BlockNumber}; +use runtime::{staking, system, session}; + +const APPROVALS_REQUIRED: &[u8] = b"gov:apr"; +const CURRENT_PROPOSAL: &[u8] = b"gov:pro"; +const APPROVAL_OF: &[u8] = b"gov:app:"; + +/// The proportion of validators required for a propsal to be approved measured as the number out +/// of 1000. +pub fn approval_ppm_required() -> u32 { + storage::get_or(APPROVALS_REQUIRED, 1000) +} + +/// The number of concrete validator approvals required for a proposal to pass. +pub fn approvals_required() -> u32 { + approval_ppm_required() * session::validator_count() / 1000 +} + +pub mod public { + use super::*; + + /// Propose a sensitive action to be taken. Any action that is enactable by `Proposal` is valid. + /// Proposal is by the `transactor` and will automatically count as an approval. Transactor must + /// be a current validator. It is illegal to propose when there is already a proposal in effect. + pub fn propose(validator: &AccountId, proposal: &Proposal) { + if storage::exists(CURRENT_PROPOSAL) { + panic!("there may only be one proposal per era."); + } + storage::put(CURRENT_PROPOSAL, proposal); + approve(validator, staking::current_era()); + } + + /// Approve the current era's proposal. Transactor must be a validator. This may not be done more + /// than once for any validator in an era. + pub fn approve(validator: &AccountId, era_index: BlockNumber) { + if era_index != staking::current_era() { + panic!("approval vote applied on non-current era.") + } + if !storage::exists(CURRENT_PROPOSAL) { + panic!("there must be a proposal in order to approve."); + } + if session::validators().into_iter().position(|v| &v == validator).is_none() { + panic!("transactor must be a validator to approve."); + } + let key = validator.to_keyed_vec(APPROVAL_OF); + if storage::exists(&key) { + panic!("transactor may not approve a proposal twice in one era."); + } + storage::put(&key, &true); + } +} + +pub mod privileged { + use super::*; + + /// Set the proportion of validators that must approve for a proposal to be enacted at the end of + /// its era. The value, `ppm`, is measured as a fraction of 1000 rounded down to the nearest whole + /// validator. `1000` would require the approval of all validators; `667` would require two-thirds + /// (or there abouts) of validators. + pub fn set_approval_ppm_required(ppm: u32) { + storage::put(APPROVALS_REQUIRED, &ppm); + } +} + +pub mod internal { + use super::*; + use polkadot_primitives::Proposal; + + /// Current era is ending; we should finish up any proposals. + pub fn end_of_an_era() { + // tally up votes for the current proposal, if any. enact if there are sufficient approvals. + if let Some(proposal) = storage::take::(CURRENT_PROPOSAL) { + let approvals_required = approvals_required(); + let approved = session::validators().into_iter() + .filter_map(|v| storage::take::(&v.to_keyed_vec(APPROVAL_OF))) + .take(approvals_required as usize) + .count() as u32; + if approved == approvals_required { + enact_proposal(proposal); + } + } + } + + fn enact_proposal(proposal: Proposal) { + match proposal { + Proposal::SystemSetCode(code) => { + system::privileged::set_code(&code); + } + Proposal::SessionSetLength(value) => { + session::privileged::set_length(value); + } + Proposal::SessionForceNewSession => { + session::privileged::force_new_session(); + } + Proposal::StakingSetSessionsPerEra(value) => { + staking::privileged::set_sessions_per_era(value); + } + Proposal::StakingSetBondingDuration(value) => { + staking::privileged::set_bonding_duration(value); + } + Proposal::StakingSetValidatorCount(value) => { + staking::privileged::set_validator_count(value); + } + Proposal::StakingForceNewEra => { + staking::privileged::force_new_era() + } + Proposal::GovernanceSetApprovalPpmRequired(value) => { + self::privileged::set_approval_ppm_required(value); + } + + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + use runtime_io::{with_externalities, twox_128, TestExternalities}; + use codec::{KeyedVec, Joiner}; + use support::{one, two, with_env}; + use polkadot_primitives::{AccountId, Proposal}; + use runtime::{staking, session}; + + fn new_test_ext() -> TestExternalities { + let one = one(); + let two = two(); + let three = [3u8; 32]; + + TestExternalities { storage: map![ + twox_128(APPROVALS_REQUIRED).to_vec() => vec![].join(&667u32), + twox_128(b"ses:len").to_vec() => vec![].join(&1u64), + twox_128(b"ses:val:len").to_vec() => vec![].join(&3u32), + twox_128(&0u32.to_keyed_vec(b"ses:val:")).to_vec() => one.to_vec(), + twox_128(&1u32.to_keyed_vec(b"ses:val:")).to_vec() => two.to_vec(), + twox_128(&2u32.to_keyed_vec(b"ses:val:")).to_vec() => three.to_vec(), + twox_128(b"sta:wil:len").to_vec() => vec![].join(&3u32), + twox_128(&0u32.to_keyed_vec(b"sta:wil:")).to_vec() => one.to_vec(), + twox_128(&1u32.to_keyed_vec(b"sta:wil:")).to_vec() => two.to_vec(), + twox_128(&2u32.to_keyed_vec(b"sta:wil:")).to_vec() => three.to_vec(), + twox_128(b"sta:spe").to_vec() => vec![].join(&1u64), + twox_128(b"sta:vac").to_vec() => vec![].join(&3u64), + twox_128(b"sta:era").to_vec() => vec![].join(&1u64) + ], } + } + + #[test] + fn majority_voting_should_work() { + let one = one(); + let two = two(); + let three = [3u8; 32]; + let mut t = new_test_ext(); + + with_externalities(&mut t, || { + assert_eq!(staking::era_length(), 1u64); + assert_eq!(staking::current_era(), 1u64); + assert_eq!(session::validator_count(), 3u32); + assert_eq!(session::validators(), vec![one.clone(), two.clone(), three.clone()]); + assert!(!session::validators().into_iter().position(|v| &v == &one).is_none()); + + // Block 1: Make proposal. Approve it. Era length changes. + with_env(|e| e.block_number = 1); + public::propose(&one, &Proposal::StakingSetSessionsPerEra(2)); + public::approve(&two, 1); + staking::internal::check_new_era(); + assert_eq!(staking::era_length(), 2); + }); + } + + #[test] + fn majority_voting_should_work_after_unsuccessful_previous() { + let one = one(); + let two = two(); + let three = [3u8; 32]; + let mut t = new_test_ext(); + + with_externalities(&mut t, || { + assert_eq!(staking::era_length(), 1u64); + assert_eq!(staking::current_era(), 1u64); + assert_eq!(session::validator_count(), 3u32); + assert_eq!(session::validators(), vec![one.clone(), two.clone(), three.clone()]); + assert!(!session::validators().into_iter().position(|v| &v == &one).is_none()); + + // Block 1: Make proposal. Fail it. + with_env(|e| e.block_number = 1); + public::propose(&one, &Proposal::StakingSetSessionsPerEra(2)); + staking::internal::check_new_era(); + assert_eq!(staking::era_length(), 1); + + // Block 2: Make proposal. Approve it. It should change era length. + with_env(|e| e.block_number = 2); + public::propose(&one, &Proposal::StakingSetSessionsPerEra(2)); + public::approve(&two, 2); + staking::internal::check_new_era(); + assert_eq!(staking::era_length(), 2); + }); + } + + #[test] + fn minority_voting_should_not_succeed() { + let one = one(); + let two = two(); + let three = [3u8; 32]; + let mut t = new_test_ext(); + + with_externalities(&mut t, || { + assert_eq!(staking::era_length(), 1u64); + assert_eq!(staking::current_era(), 1u64); + assert_eq!(session::validator_count(), 3u32); + assert_eq!(session::validators(), vec![one.clone(), two.clone(), three.clone()]); + assert!(!session::validators().into_iter().position(|v| &v == &one).is_none()); + + // Block 1: Make proposal. Will have only 1 vote. No change. + with_env(|e| e.block_number = 1); + public::propose(&one, &Proposal::StakingSetSessionsPerEra(2)); + staking::internal::check_new_era(); + assert_eq!(staking::era_length(), 1); + }); + } + + #[test] + #[should_panic] + fn old_voting_should_be_illegal() { + let one = one(); + let two = two(); + let three = [3u8; 32]; + let mut t = new_test_ext(); + + with_externalities(&mut t, || { + assert_eq!(staking::era_length(), 1u64); + assert_eq!(staking::current_era(), 1u64); + assert_eq!(session::validator_count(), 3u32); + assert_eq!(session::validators(), vec![one.clone(), two.clone(), three.clone()]); + assert!(!session::validators().into_iter().position(|v| &v == &one).is_none()); + + // Block 1: Make proposal. Will have only 1 vote. No change. + with_env(|e| e.block_number = 1); + public::propose(&one, &Proposal::StakingSetSessionsPerEra(2)); + public::approve(&two, 0); + staking::internal::check_new_era(); + assert_eq!(staking::era_length(), 1); + }); + } + + #[test] + #[should_panic] + fn double_voting_should_be_illegal() { + let one = one(); + let two = two(); + let three = [3u8; 32]; + let mut t = new_test_ext(); + + with_externalities(&mut t, || { + assert_eq!(staking::era_length(), 1u64); + assert_eq!(staking::current_era(), 1u64); + assert_eq!(session::validator_count(), 3u32); + assert_eq!(session::validators(), vec![one.clone(), two.clone(), three.clone()]); + assert!(!session::validators().into_iter().position(|v| &v == &one).is_none()); + + // Block 1: Make proposal. Will have only 1 vote. No change. + with_env(|e| e.block_number = 1); + public::propose(&one, &Proposal::StakingSetSessionsPerEra(2)); + public::approve(&two, 1); + public::approve(&two, 1); + staking::internal::check_new_era(); + assert_eq!(staking::era_length(), 1); + }); + } + + #[test] + #[should_panic] + fn over_proposing_should_be_illegal() { + let one = one(); + let two = two(); + let three = [3u8; 32]; + let mut t = new_test_ext(); + + with_externalities(&mut t, || { + assert_eq!(staking::era_length(), 1u64); + assert_eq!(staking::current_era(), 1u64); + assert_eq!(session::validator_count(), 3u32); + assert_eq!(session::validators(), vec![one.clone(), two.clone(), three.clone()]); + assert!(!session::validators().into_iter().position(|v| &v == &one).is_none()); + + // Block 1: Make proposal. Will have only 1 vote. No change. + with_env(|e| e.block_number = 1); + public::propose(&one, &Proposal::StakingSetSessionsPerEra(2)); + public::propose(&two, &Proposal::StakingSetSessionsPerEra(2)); + staking::internal::check_new_era(); + assert_eq!(staking::era_length(), 1); + }); + } + + #[test] + #[should_panic] + fn approving_without_proposal_should_be_illegal() { + let one = one(); + let two = two(); + let three = [3u8; 32]; + let mut t = new_test_ext(); + + with_externalities(&mut t, || { + assert_eq!(staking::era_length(), 1u64); + assert_eq!(staking::current_era(), 1u64); + assert_eq!(session::validator_count(), 3u32); + assert_eq!(session::validators(), vec![one.clone(), two.clone(), three.clone()]); + assert!(!session::validators().into_iter().position(|v| &v == &one).is_none()); + + // Block 1: Make proposal. Will have only 1 vote. No change. + with_env(|e| e.block_number = 1); + public::approve(&two, 1); + staking::internal::check_new_era(); + assert_eq!(staking::era_length(), 1); + }); + } + + #[test] + #[should_panic] + fn non_validator_approving_should_be_illegal() { + let one = one(); + let two = two(); + let three = [3u8; 32]; + let four = [4u8; 32]; + let mut t = new_test_ext(); + + with_externalities(&mut t, || { + assert_eq!(staking::era_length(), 1u64); + assert_eq!(staking::current_era(), 1u64); + assert_eq!(session::validator_count(), 3u32); + assert_eq!(session::validators(), vec![one.clone(), two.clone(), three.clone()]); + assert!(!session::validators().into_iter().position(|v| &v == &one).is_none()); + + // Block 1: Make proposal. Will have only 1 vote. No change. + with_env(|e| e.block_number = 1); + public::propose(&one, &Proposal::StakingSetSessionsPerEra(2)); + public::approve(&four, 1); + staking::internal::check_new_era(); + assert_eq!(staking::era_length(), 1); + }); + } +} diff --git a/polkadot/runtime/src/runtime/mod.rs b/polkadot/runtime/src/runtime/mod.rs new file mode 100644 index 0000000000..6a54fa7310 --- /dev/null +++ b/polkadot/runtime/src/runtime/mod.rs @@ -0,0 +1,34 @@ +// 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 Polkadot runtime. + +#[allow(unused)] +pub mod system; +#[allow(unused)] +pub mod consensus; +#[allow(unused)] +pub mod staking; +#[allow(unused)] +pub mod timestamp; +#[allow(unused)] +pub mod session; +#[allow(unused)] +pub mod governance; +#[allow(unused)] +pub mod parachains; + +// TODO: polkadao diff --git a/polkadot/runtime/src/runtime/parachains.rs b/polkadot/runtime/src/runtime/parachains.rs new file mode 100644 index 0000000000..d0c4f5c845 --- /dev/null +++ b/polkadot/runtime/src/runtime/parachains.rs @@ -0,0 +1,142 @@ +// 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 . + +//! Main parachains logic. For now this is just the determination of which validators do what. + +use rstd::prelude::*; +use rstd::mem; +use codec::{Slicable, Joiner}; +use support::{Hashable, with_env, storage}; +use runtime::session; + +const PARACHAIN_COUNT: &[u8] = b"par:cou"; + +/// Identifier for a chain, either one of a number of parachains or the relay chain. +#[derive(Copy, Clone, PartialEq)] +#[cfg_attr(test, derive(Debug))] +pub enum Chain { + /// The relay chain. + Relay, + /// A parachain of the given index. + Parachain(u32), +} + +/// The duty roster specifying what jobs each validator must do. +#[derive(Clone, PartialEq)] +#[cfg_attr(test, derive(Default, Debug))] +pub struct DutyRoster { + /// Lookup from validator index to chain on which that validator has a duty to validate. + pub validator_duty: Vec, + /// Lookup from validator index to chain on which that validator has a duty to guarantee + /// availability. + pub guarantor_duty: Vec, +} + +/// Get the number of parachains registered at present. +pub fn parachain_count() -> u32 { + storage::get_or(PARACHAIN_COUNT, 0) +} + +/// Calculate the current block's duty roster. +pub fn calculate_duty_roster() -> DutyRoster { + let parachain_count = parachain_count(); + let validator_count = session::validator_count() as u32; + let validators_per_parachain = (validator_count - 1) / parachain_count; + + let mut roles_val = (0..validator_count).map(|i| match i { + i if i < parachain_count * validators_per_parachain => Chain::Parachain(i / validators_per_parachain as u32), + _ => Chain::Relay, + }).collect::>(); + let mut roles_gua = roles_val.clone(); + + let h = with_env(|e| e.parent_hash.clone()); + let mut seed = Vec::::new().join(&h).join(b"validator_role_pairs").blake2_256(); + + // shuffle + for i in 0..(validator_count - 1) { + // 8 bytes of entropy used per cycle, 32 bytes entropy per hash + let offset = (i * 8 % 32) as usize; + + // number of roles remaining to select from. + let remaining = (validator_count - i) as usize; + + // 4 * 2 32-bit ints per 256-bit seed. + let val_index = u32::from_slice(&mut &seed[offset..offset + 4]).expect("using 4 bytes for a 32-bit quantity") as usize % remaining; + let gua_index = u32::from_slice(&mut &seed[offset + 4..offset + 8]).expect("using 4 bytes for a 32-bit quantity") as usize % remaining; + + if offset == 24 { + // into the last 8 bytes - rehash to gather new entropy + seed = seed.blake2_256(); + } + + // exchange last item with randomly chosen first. + roles_val.swap(remaining - 1, val_index); + roles_gua.swap(remaining - 1, gua_index); + } + + DutyRoster { + validator_duty: roles_val, + guarantor_duty: roles_gua, + } +} + +#[cfg(test)] +mod tests { + use super::*; + use runtime_io::{with_externalities, twox_128, TestExternalities}; + use codec::{KeyedVec, Joiner}; + use support::{one, two, with_env}; + use runtime::{consensus, session}; + + fn simple_setup() -> TestExternalities { + TestExternalities { storage: map![ + twox_128(b"ses:val:len").to_vec() => vec![].join(&8u32), + twox_128(b"par:cou").to_vec() => vec![].join(&2u32) + ], } + } + + #[test] + fn should_work() { + let mut t = simple_setup(); + with_externalities(&mut t, || { + let check_roster = |duty_roster: &DutyRoster| { + assert_eq!(duty_roster.validator_duty.len(), 8); + assert_eq!(duty_roster.guarantor_duty.len(), 8); + for i in 0..2 { + assert_eq!(duty_roster.validator_duty.iter().filter(|&&j| j == Chain::Parachain(i)).count(), 3); + assert_eq!(duty_roster.guarantor_duty.iter().filter(|&&j| j == Chain::Parachain(i)).count(), 3); + } + assert_eq!(duty_roster.validator_duty.iter().filter(|&&j| j == Chain::Relay).count(), 2); + assert_eq!(duty_roster.guarantor_duty.iter().filter(|&&j| j == Chain::Relay).count(), 2); + }; + + with_env(|e| e.parent_hash = [0u8; 32].into()); + let duty_roster_0 = calculate_duty_roster(); + check_roster(&duty_roster_0); + + with_env(|e| e.parent_hash = [1u8; 32].into()); + let duty_roster_1 = calculate_duty_roster(); + check_roster(&duty_roster_1); + assert!(duty_roster_0 != duty_roster_1); + + with_env(|e| e.parent_hash = [2u8; 32].into()); + let duty_roster_2 = calculate_duty_roster(); + check_roster(&duty_roster_2); + assert!(duty_roster_0 != duty_roster_2); + assert!(duty_roster_1 != duty_roster_2); + }); + } +} diff --git a/polkadot/runtime/src/runtime/session.rs b/polkadot/runtime/src/runtime/session.rs new file mode 100644 index 0000000000..2e3f81b645 --- /dev/null +++ b/polkadot/runtime/src/runtime/session.rs @@ -0,0 +1,249 @@ +// 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 . + +//! Session manager: is told the validators and allows them to manage their session keys for the +//! consensus module. + +use rstd::prelude::*; +use codec::KeyedVec; +use support::{storage, StorageVec}; +use polkadot_primitives::{AccountId, SessionKey, BlockNumber}; +use runtime::{system, staking, consensus}; + +const SESSION_LENGTH: &[u8] = b"ses:len"; +const CURRENT_INDEX: &[u8] = b"ses:ind"; +const LAST_LENGTH_CHANGE: &[u8] = b"ses:llc"; +const NEXT_KEY_FOR: &[u8] = b"ses:nxt:"; +const NEXT_SESSION_LENGTH: &[u8] = b"ses:nln"; + +struct ValidatorStorageVec {} +impl StorageVec for ValidatorStorageVec { + type Item = AccountId; + const PREFIX: &'static[u8] = b"ses:val:"; +} + +/// Get the current set of authorities. These are the session keys. +pub fn validators() -> Vec { + ValidatorStorageVec::items() +} + +/// The number of blocks in each session. +pub fn length() -> BlockNumber { + storage::get_or(SESSION_LENGTH, 0) +} + +/// The number of validators currently. +pub fn validator_count() -> u32 { + ValidatorStorageVec::count() as u32 +} + +/// The current era index. +pub fn current_index() -> BlockNumber { + storage::get_or(CURRENT_INDEX, 0) +} + +/// The block number at which the era length last changed. +pub fn last_length_change() -> BlockNumber { + storage::get_or(LAST_LENGTH_CHANGE, 0) +} + +pub mod public { + use super::*; + + /// Sets the session key of `_validator` to `_key`. This doesn't take effect until the next + /// session. + pub fn set_key(validator: &AccountId, key: &SessionKey) { + // set new value for next session + storage::put(&validator.to_keyed_vec(NEXT_KEY_FOR), key); + } +} + +pub mod privileged { + use super::*; + + /// Set a new era length. Won't kick in until the next era change (at current length). + pub fn set_length(new: BlockNumber) { + storage::put(NEXT_SESSION_LENGTH, &new); + } + + /// Forces a new session. + pub fn force_new_session() { + rotate_session(); + } +} + +// INTERNAL API (available to other runtime modules) + +pub mod internal { + use super::*; + + /// Set the current set of validators. + /// + /// Called by staking::next_era() only. `next_session` should be called after this in order to + /// update the session keys to the next validator set. + pub fn set_validators(new: &[AccountId]) { + ValidatorStorageVec::set_items(new); + consensus::internal::set_authorities(new); + } + + /// Hook to be called after transaction processing. + pub fn check_rotate_session() { + // do this last, after the staking system has had chance to switch out the authorities for the + // new set. + // check block number and call next_session if necessary. + if (system::block_number() - last_length_change()) % length() == 0 { + rotate_session(); + } + } +} + +/// Move onto next session: register the new authority set. +fn rotate_session() { + // Increment current session index. + storage::put(CURRENT_INDEX, &(current_index() + 1)); + + // Enact era length change. + if let Some(next_len) = storage::get::(NEXT_SESSION_LENGTH) { + storage::put(SESSION_LENGTH, &next_len); + storage::put(LAST_LENGTH_CHANGE, &system::block_number()); + storage::kill(NEXT_SESSION_LENGTH); + } + + // Update any changes in session keys. + validators().iter().enumerate().for_each(|(i, v)| { + let k = v.to_keyed_vec(NEXT_KEY_FOR); + if let Some(n) = storage::take(&k) { + consensus::internal::set_authority(i as u32, &n); + } + }); +} + +#[cfg(test)] +mod tests { + use super::*; + use super::public::*; + use super::privileged::*; + use super::internal::*; + use runtime_io::{with_externalities, twox_128, TestExternalities}; + use codec::{KeyedVec, Joiner}; + use support::{one, two, with_env}; + use polkadot_primitives::AccountId; + use runtime::{consensus, session}; + + fn simple_setup() -> TestExternalities { + TestExternalities { storage: map![ + twox_128(SESSION_LENGTH).to_vec() => vec![].join(&2u64), + // the validators (10, 20, ...) + twox_128(b"ses:val:len").to_vec() => vec![].join(&2u32), + twox_128(&0u32.to_keyed_vec(ValidatorStorageVec::PREFIX)).to_vec() => vec![10; 32], + twox_128(&1u32.to_keyed_vec(ValidatorStorageVec::PREFIX)).to_vec() => vec![20; 32], + // initial session keys (11, 21, ...) + b":auth:len".to_vec() => vec![].join(&2u32), + 0u32.to_keyed_vec(b":auth:") => vec![11; 32], + 1u32.to_keyed_vec(b":auth:") => vec![21; 32] + ], } + } + + #[test] + fn simple_setup_should_work() { + let mut t = simple_setup(); + with_externalities(&mut t, || { + assert_eq!(consensus::authorities(), vec![[11u8; 32], [21u8; 32]]); + assert_eq!(length(), 2u64); + assert_eq!(validators(), vec![[10u8; 32], [20u8; 32]]); + }); + } + + #[test] + fn session_length_change_should_work() { + let mut t = simple_setup(); + with_externalities(&mut t, || { + // Block 1: Change to length 3; no visible change. + with_env(|e| e.block_number = 1); + set_length(3); + check_rotate_session(); + assert_eq!(length(), 2); + assert_eq!(current_index(), 0); + + // Block 2: Length now changed to 3. Index incremented. + with_env(|e| e.block_number = 2); + set_length(3); + check_rotate_session(); + assert_eq!(length(), 3); + assert_eq!(current_index(), 1); + + // Block 3: Length now changed to 3. Index incremented. + with_env(|e| e.block_number = 3); + check_rotate_session(); + assert_eq!(length(), 3); + assert_eq!(current_index(), 1); + + // Block 4: Change to length 2; no visible change. + with_env(|e| e.block_number = 4); + set_length(2); + check_rotate_session(); + assert_eq!(length(), 3); + assert_eq!(current_index(), 1); + + // Block 5: Length now changed to 2. Index incremented. + with_env(|e| e.block_number = 5); + check_rotate_session(); + assert_eq!(length(), 2); + assert_eq!(current_index(), 2); + + // Block 6: No change. + with_env(|e| e.block_number = 6); + check_rotate_session(); + assert_eq!(length(), 2); + assert_eq!(current_index(), 2); + + // Block 7: Next index. + with_env(|e| e.block_number = 7); + check_rotate_session(); + assert_eq!(length(), 2); + assert_eq!(current_index(), 3); + }); + } + + #[test] + fn session_change_should_work() { + let mut t = simple_setup(); + with_externalities(&mut t, || { + // Block 1: No change + with_env(|e| e.block_number = 1); + check_rotate_session(); + assert_eq!(consensus::authorities(), vec![[11u8; 32], [21u8; 32]]); + + // Block 2: Session rollover, but no change. + with_env(|e| e.block_number = 2); + check_rotate_session(); + assert_eq!(consensus::authorities(), vec![[11u8; 32], [21u8; 32]]); + + // Block 3: Set new key for validator 2; no visible change. + with_env(|e| e.block_number = 3); + set_key(&[20; 32], &[22; 32]); + assert_eq!(consensus::authorities(), vec![[11u8; 32], [21u8; 32]]); + + check_rotate_session(); + assert_eq!(consensus::authorities(), vec![[11u8; 32], [21u8; 32]]); + + // Block 4: Session rollover, authority 2 changes. + with_env(|e| e.block_number = 4); + check_rotate_session(); + assert_eq!(consensus::authorities(), vec![[11u8; 32], [22u8; 32]]); + }); + } +} diff --git a/polkadot/runtime/src/runtime/staking.rs b/polkadot/runtime/src/runtime/staking.rs new file mode 100644 index 0000000000..210c518146 --- /dev/null +++ b/polkadot/runtime/src/runtime/staking.rs @@ -0,0 +1,406 @@ +// 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 . + +//! Staking manager: Handles balances and periodically determines the best set of validators. + +use rstd::prelude::*; +use rstd::cell::RefCell; +use runtime_io::print; +use codec::KeyedVec; +use support::{storage, StorageVec}; +use polkadot_primitives::{BlockNumber, AccountId}; +use runtime::{system, session, governance}; + +/// The balance of an account. +pub type Balance = u64; + +/// The amount of bonding period left in an account. Measured in eras. +pub type Bondage = u64; + +struct IntentionStorageVec {} +impl StorageVec for IntentionStorageVec { + type Item = AccountId; + const PREFIX: &'static[u8] = b"sta:wil:"; +} + +const BONDING_DURATION: &[u8] = b"sta:loc"; +const VALIDATOR_COUNT: &[u8] = b"sta:vac"; +const SESSIONS_PER_ERA: &[u8] = b"sta:spe"; +const NEXT_SESSIONS_PER_ERA: &[u8] = b"sta:nse"; +const CURRENT_ERA: &[u8] = b"sta:era"; +const LAST_ERA_LENGTH_CHANGE: &[u8] = b"sta:lec"; +const BALANCE_OF: &[u8] = b"sta:bal:"; +const BONDAGE_OF: &[u8] = b"sta:bon:"; + +/// The length of the bonding duration in eras. +pub fn bonding_duration() -> BlockNumber { + storage::get_or_default(BONDING_DURATION) +} + +/// The length of a staking era in sessions. +pub fn validator_count() -> usize { + storage::get_or_default::(VALIDATOR_COUNT) as usize +} + +/// The length of a staking era in blocks. +pub fn era_length() -> BlockNumber { + sessions_per_era() * session::length() +} + +/// The length of a staking era in sessions. +pub fn sessions_per_era() -> BlockNumber { + storage::get_or_default(SESSIONS_PER_ERA) +} + +/// The current era index. +pub fn current_era() -> BlockNumber { + storage::get_or_default(CURRENT_ERA) +} + +/// The block number at which the era length last changed. +pub fn last_era_length_change() -> BlockNumber { + storage::get_or_default(LAST_ERA_LENGTH_CHANGE) +} + +/// The balance of a given account. +pub fn balance(who: &AccountId) -> Balance { + storage::get_or_default(&who.to_keyed_vec(BALANCE_OF)) +} + +/// The liquidity-state of a given account. +pub fn bondage(who: &AccountId) -> Bondage { + storage::get_or_default(&who.to_keyed_vec(BONDAGE_OF)) +} + +// Each identity's stake may be in one of three bondage states, given by an integer: +// - n | n <= current_era(): inactive: free to be transferred. +// - ~0: active: currently representing a validator. +// - n | n > current_era(): deactivating: recently representing a validator and not yet +// ready for transfer. + +pub mod public { + use super::*; + + /// Transfer some unlocked staking balance to another staker. + pub fn transfer(transactor: &AccountId, dest: &AccountId, value: Balance) { + let from_key = transactor.to_keyed_vec(BALANCE_OF); + let from_balance = storage::get_or_default::(&from_key); + assert!(from_balance >= value); + let to_key = dest.to_keyed_vec(BALANCE_OF); + let to_balance: Balance = storage::get_or_default(&to_key); + assert!(bondage(transactor) <= bondage(dest)); + assert!(to_balance + value > to_balance); // no overflow + storage::put(&from_key, &(from_balance - value)); + storage::put(&to_key, &(to_balance + value)); + } + + /// Declare the desire to stake for the transactor. + /// + /// Effects will be felt at the beginning of the next era. + pub fn stake(transactor: &AccountId) { + let mut intentions = IntentionStorageVec::items(); + // can't be in the list twice. + assert!(intentions.iter().find(|t| *t == transactor).is_none(), "Cannot stake if already staked."); + intentions.push(transactor.clone()); + IntentionStorageVec::set_items(&intentions); + storage::put(&transactor.to_keyed_vec(BONDAGE_OF), &u64::max_value()); + } + + /// Retract the desire to stake for the transactor. + /// + /// Effects will be felt at the beginning of the next era. + pub fn unstake(transactor: &AccountId) { + let mut intentions = IntentionStorageVec::items(); + if let Some(position) = intentions.iter().position(|t| t == transactor) { + intentions.swap_remove(position); + } else { + panic!("Cannot unstake if not already staked."); + } + IntentionStorageVec::set_items(&intentions); + storage::put(&transactor.to_keyed_vec(BONDAGE_OF), &(current_era() + bonding_duration())); + } +} + +pub mod privileged { + use super::*; + + /// Set the number of sessions in an era. + pub fn set_sessions_per_era(new: BlockNumber) { + storage::put(NEXT_SESSIONS_PER_ERA, &new); + } + + /// The length of the bonding duration in eras. + pub fn set_bonding_duration(new: BlockNumber) { + storage::put(BONDING_DURATION, &new); + } + + /// The length of a staking era in sessions. + pub fn set_validator_count(new: u32) { + storage::put(VALIDATOR_COUNT, &new); + } + + /// Force there to be a new era. This also forces a new session immediately after. + pub fn force_new_era() { + new_era(); + session::privileged::force_new_session(); + } +} + +pub mod internal { + use super::*; + + /// Hook to be called after to transaction processing. + pub fn check_new_era() { + // check block number and call new_era if necessary. + if (system::block_number() - last_era_length_change()) % era_length() == 0 { + new_era(); + } + } +} + +/// The era has changed - enact new staking set. +/// +/// NOTE: This always happens immediately before a session change to ensure that new validators +/// get a chance to set their session keys. +fn new_era() { + // Inform governance module that it's the end of an era + governance::internal::end_of_an_era(); + + // Increment current era. + storage::put(CURRENT_ERA, &(current_era() + 1)); + + // Enact era length change. + let next_spe: u64 = storage::get_or_default(NEXT_SESSIONS_PER_ERA); + if next_spe > 0 && next_spe != sessions_per_era() { + storage::put(SESSIONS_PER_ERA, &next_spe); + storage::put(LAST_ERA_LENGTH_CHANGE, &system::block_number()); + } + + // evaluate desired staking amounts and nominations and optimise to find the best + // combination of validators, then use session::internal::set_validators(). + // for now, this just orders would-be stakers by their balances and chooses the top-most + // validator_count() of them. + let mut intentions = IntentionStorageVec::items() + .into_iter() + .map(|v| (balance(&v), v)) + .collect::>(); + intentions.sort_unstable_by(|&(b1, _), &(b2, _)| b2.cmp(&b1)); + session::internal::set_validators( + &intentions.into_iter() + .map(|(_, v)| v) + .take(validator_count()) + .collect::>() + ); +} + +#[cfg(test)] +mod tests { + use super::*; + use super::internal::*; + use super::public::*; + use super::privileged::*; + + use runtime_io::{with_externalities, twox_128, TestExternalities}; + use codec::{KeyedVec, Joiner}; + use support::{one, two, with_env}; + use polkadot_primitives::AccountId; + use runtime::{staking, session}; + + #[test] + fn staking_should_work() { + let one = one(); + let two = two(); + let three = [3u8; 32]; + let four = [4u8; 32]; + + let mut t = TestExternalities { storage: map![ + twox_128(b"ses:len").to_vec() => vec![].join(&1u64), + twox_128(b"ses:val:len").to_vec() => vec![].join(&2u32), + twox_128(&0u32.to_keyed_vec(b"ses:val:")).to_vec() => vec![10; 32], + twox_128(&1u32.to_keyed_vec(b"ses:val:")).to_vec() => vec![20; 32], + twox_128(SESSIONS_PER_ERA).to_vec() => vec![].join(&2u64), + twox_128(VALIDATOR_COUNT).to_vec() => vec![].join(&2u32), + twox_128(BONDING_DURATION).to_vec() => vec![].join(&3u64), + twox_128(&one.to_keyed_vec(BALANCE_OF)).to_vec() => vec![].join(&10u64), + twox_128(&two.to_keyed_vec(BALANCE_OF)).to_vec() => vec![].join(&20u64), + twox_128(&three.to_keyed_vec(BALANCE_OF)).to_vec() => vec![].join(&30u64), + twox_128(&four.to_keyed_vec(BALANCE_OF)).to_vec() => vec![].join(&40u64) + ], }; + + with_externalities(&mut t, || { + assert_eq!(era_length(), 2u64); + assert_eq!(validator_count(), 2usize); + assert_eq!(bonding_duration(), 3u64); + assert_eq!(session::validators(), vec![[10u8; 32], [20u8; 32]]); + + // Block 1: Add three validators. No obvious change. + with_env(|e| e.block_number = 1); + stake(&one); + stake(&two); + stake(&four); + check_new_era(); + assert_eq!(session::validators(), vec![[10u8; 32], [20u8; 32]]); + + // Block 2: New validator set now. + with_env(|e| e.block_number = 2); + check_new_era(); + assert_eq!(session::validators(), vec![four.clone(), two.clone()]); + + // Block 3: Unstake highest, introduce another staker. No change yet. + with_env(|e| e.block_number = 3); + stake(&three); + unstake(&four); + check_new_era(); + + // Block 4: New era - validators change. + with_env(|e| e.block_number = 4); + check_new_era(); + assert_eq!(session::validators(), vec![three.clone(), two.clone()]); + + // Block 5: Transfer stake from highest to lowest. No change yet. + with_env(|e| e.block_number = 5); + transfer(&four, &one, 40); + check_new_era(); + + // Block 6: Lowest now validator. + with_env(|e| e.block_number = 6); + check_new_era(); + assert_eq!(session::validators(), vec![one.clone(), three.clone()]); + + // Block 7: Unstake three. No change yet. + with_env(|e| e.block_number = 7); + unstake(&three); + check_new_era(); + assert_eq!(session::validators(), vec![one.clone(), three.clone()]); + + // Block 8: Back to one and two. + with_env(|e| e.block_number = 8); + check_new_era(); + assert_eq!(session::validators(), vec![one.clone(), two.clone()]); + }); + } + + #[test] + fn staking_eras_work() { + let mut t = TestExternalities { storage: map![ + twox_128(b"ses:len").to_vec() => vec![].join(&1u64), + twox_128(SESSIONS_PER_ERA).to_vec() => vec![].join(&2u64) + ], }; + with_externalities(&mut t, || { + assert_eq!(era_length(), 2u64); + assert_eq!(sessions_per_era(), 2u64); + assert_eq!(last_era_length_change(), 0u64); + assert_eq!(current_era(), 0u64); + + // Block 1: No change. + with_env(|e| e.block_number = 1); + check_new_era(); + assert_eq!(sessions_per_era(), 2u64); + assert_eq!(last_era_length_change(), 0u64); + assert_eq!(current_era(), 0u64); + + // Block 2: Simple era change. + with_env(|e| e.block_number = 2); + check_new_era(); + assert_eq!(sessions_per_era(), 2u64); + assert_eq!(last_era_length_change(), 0u64); + assert_eq!(current_era(), 1u64); + + // Block 3: Schedule an era length change; no visible changes. + with_env(|e| e.block_number = 3); + set_sessions_per_era(3); + check_new_era(); + assert_eq!(sessions_per_era(), 2u64); + assert_eq!(last_era_length_change(), 0u64); + assert_eq!(current_era(), 1u64); + + // Block 4: Era change kicks in. + with_env(|e| e.block_number = 4); + check_new_era(); + assert_eq!(sessions_per_era(), 3u64); + assert_eq!(last_era_length_change(), 4u64); + assert_eq!(current_era(), 2u64); + + // Block 5: No change. + with_env(|e| e.block_number = 5); + check_new_era(); + assert_eq!(sessions_per_era(), 3u64); + assert_eq!(last_era_length_change(), 4u64); + assert_eq!(current_era(), 2u64); + + // Block 6: No change. + with_env(|e| e.block_number = 6); + check_new_era(); + assert_eq!(sessions_per_era(), 3u64); + assert_eq!(last_era_length_change(), 4u64); + assert_eq!(current_era(), 2u64); + + // Block 7: Era increment. + with_env(|e| e.block_number = 7); + check_new_era(); + assert_eq!(sessions_per_era(), 3u64); + assert_eq!(last_era_length_change(), 4u64); + assert_eq!(current_era(), 3u64); + }); + } + + #[test] + fn staking_balance_works() { + let one = one(); + let two = two(); + + let mut t = TestExternalities { storage: map![ + twox_128(&one.to_keyed_vec(BALANCE_OF)).to_vec() => vec![].join(&42u64) + ], }; + + with_externalities(&mut t, || { + assert_eq!(balance(&one), 42); + assert_eq!(balance(&two), 0); + }); + } + + #[test] + fn staking_balance_transfer_works() { + let one = one(); + let two = two(); + + let mut t = TestExternalities { storage: map![ + twox_128(&one.to_keyed_vec(BALANCE_OF)).to_vec() => vec![].join(&111u64) + ], }; + + with_externalities(&mut t, || { + transfer(&one, &two, 69); + assert_eq!(balance(&one), 42); + assert_eq!(balance(&two), 69); + }); + } + + #[test] + #[should_panic] + fn staking_balance_transfer_when_bonded_doesnt_work() { + let one = one(); + let two = two(); + + let mut t = TestExternalities { storage: map![ + twox_128(&one.to_keyed_vec(BALANCE_OF)).to_vec() => vec![].join(&111u64) + ], }; + + with_externalities(&mut t, || { + stake(&one); + transfer(&one, &two, 69); + }); + } +} diff --git a/polkadot/runtime/src/runtime/system.rs b/polkadot/runtime/src/runtime/system.rs new file mode 100644 index 0000000000..a82d911ea9 --- /dev/null +++ b/polkadot/runtime/src/runtime/system.rs @@ -0,0 +1,367 @@ +// 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 . + +//! System manager: Handles all of the top-level stuff; executing block/transaction, setting code +//! and depositing logs. + +use rstd::prelude::*; +use rstd::mem; +use runtime_io::{print, storage_root, enumerated_trie_root}; +use codec::{KeyedVec, Slicable}; +use support::{Hashable, storage, with_env}; +use polkadot_primitives::{AccountId, Hash, TxOrder, BlockNumber, Block, Header, + UncheckedTransaction, Function, Log}; +use runtime::{staking, session}; + +const NONCE_OF: &[u8] = b"sys:non:"; +const BLOCK_HASH_AT: &[u8] = b"sys:old:"; +const CODE: &[u8] = b"sys:cod"; + +/// The current block number being processed. Set by `execute_block`. +pub fn block_number() -> BlockNumber { + with_env(|e| e.block_number) +} + +/// Get the block hash of a given block (uses storage). +pub fn block_hash(number: BlockNumber) -> Hash { + storage::get_or_default(&number.to_keyed_vec(BLOCK_HASH_AT)) +} + +pub mod privileged { + use super::*; + + /// Set the new code. + pub fn set_code(new: &[u8]) { + storage::unhashed::put_raw(b":code", new); + } +} + +pub mod internal { + use super::*; + + struct CheckedTransaction(UncheckedTransaction); + + /// Deposits a log and ensures it matches the blocks log data. + pub fn deposit_log(log: Log) { + with_env(|e| e.digest.logs.push(log)); + } + + /// Actually execute all transitioning for `block`. + pub fn execute_block(mut block: Block) { + // populate environment from header. + with_env(|e| { + e.block_number = block.header.number; + e.parent_hash = block.header.parent_hash; + }); + + // any initial checks + initial_checks(&block); + + // execute transactions + block.transactions.iter().cloned().for_each(super::execute_transaction); + + // post-transactional book-keeping. + staking::internal::check_new_era(); + session::internal::check_rotate_session(); + + // any final checks + final_checks(&block); + + // any stuff that we do after taking the storage root. + post_finalise(&block.header); + } + + /// Execute a transaction outside of the block execution function. + /// This doesn't attempt to validate anything regarding the block. + pub fn execute_transaction(utx: UncheckedTransaction, mut header: Header) -> Header { + // populate environment from header. + with_env(|e| { + e.block_number = header.number; + e.parent_hash = header.parent_hash; + mem::swap(&mut header.digest, &mut e.digest); + }); + + super::execute_transaction(utx); + + with_env(|e| { + mem::swap(&mut header.digest, &mut e.digest); + }); + header + } + + /// Finalise the block - it is up the caller to ensure that all header fields are valid + /// except state-root. + pub fn finalise_block(mut header: Header) -> Header { + // populate environment from header. + with_env(|e| { + e.block_number = header.number; + e.parent_hash = header.parent_hash; + mem::swap(&mut header.digest, &mut e.digest); + }); + + staking::internal::check_new_era(); + session::internal::check_rotate_session(); + + header.state_root = storage_root().into(); + with_env(|e| { + mem::swap(&mut header.digest, &mut e.digest); + }); + + post_finalise(&header); + + header + } + + /// Dispatch a function. + pub fn dispatch_function(function: &Function, transactor: &AccountId) { + match *function { + Function::StakingStake => { + ::runtime::staking::public::stake(transactor); + } + Function::StakingUnstake => { + ::runtime::staking::public::unstake(transactor); + } + Function::StakingTransfer(dest, value) => { + ::runtime::staking::public::transfer(transactor, &dest, value); + } + Function::SessionSetKey(session) => { + ::runtime::session::public::set_key(transactor, &session); + } + Function::TimestampSet(t) => { + ::runtime::timestamp::public::set(t); + } + Function::GovernancePropose(ref proposal) => { + ::runtime::governance::public::propose(transactor, proposal); + } + Function::GovernanceApprove(era_index) => { + ::runtime::governance::public::approve(transactor, era_index); + } + } + } +} + +fn execute_transaction(utx: UncheckedTransaction) { + use ::transaction; + + // Verify the signature is good. + let tx = match transaction::check(utx) { + Ok(tx) => tx, + Err(_) => panic!("All transactions should be properly signed"), + }; + + // check nonce + let nonce_key = tx.signed.to_keyed_vec(NONCE_OF); + let expected_nonce: TxOrder = storage::get_or(&nonce_key, 0); + assert!(tx.nonce == expected_nonce, "All transactions should have the correct nonce"); + + // increment nonce in storage + storage::put(&nonce_key, &(expected_nonce + 1)); + + // decode parameters and dispatch + internal::dispatch_function(&tx.function, &tx.signed); +} + +fn initial_checks(block: &Block) { + let ref header = block.header; + + // check parent_hash is correct. + assert!( + header.number > 0 && block_hash(header.number - 1) == header.parent_hash, + "Parent hash should be valid." + ); + + // check transaction trie root represents the transactions. + let txs = block.transactions.iter().map(Slicable::to_vec).collect::>(); + let txs = txs.iter().map(Vec::as_slice).collect::>(); + let txs_root = enumerated_trie_root(&txs).into(); + info_expect_equal_hash(&header.transaction_root, &txs_root); + assert!(header.transaction_root == txs_root, "Transaction trie root must be valid."); +} + +fn final_checks(block: &Block) { + let ref header = block.header; + + // check digest + with_env(|e| { + assert!(header.digest == e.digest); + }); + + // check storage root. + let storage_root = storage_root().into(); + info_expect_equal_hash(&header.state_root, &storage_root); + assert!(header.state_root == storage_root, "Storage root must match that calculated."); +} + +fn post_finalise(header: &Header) { + // store the header hash in storage; we can't do it before otherwise there would be a + // cyclic dependency. + storage::put(&header.number.to_keyed_vec(BLOCK_HASH_AT), &header.blake2_256()); +} + +#[cfg(feature = "std")] +fn info_expect_equal_hash(given: &Hash, expected: &Hash) { + use support::HexDisplay; + if given != expected { + println!("Hash: given={}, expected={}", HexDisplay::from(&given.0), HexDisplay::from(&expected.0)); + } +} + +#[cfg(not(feature = "std"))] +fn info_expect_equal_hash(given: &Hash, expected: &Hash) { + if given != expected { + print("Hash not equal"); + print(&given.0[..]); + print(&expected.0[..]); + } +} + +#[cfg(test)] +mod tests { + use super::*; + use super::internal::*; + + use runtime_io::{with_externalities, twox_128, TestExternalities}; + use codec::{Joiner, KeyedVec, Slicable}; + use support::{StaticHexInto, HexDisplay, one, two}; + use polkadot_primitives::{Header, Digest, UncheckedTransaction, Transaction, Function}; + use runtime::staking; + + #[test] + fn staking_balance_transfer_dispatch_works() { + let one = one(); + let two = two(); + + let mut t = TestExternalities { storage: map![ + twox_128(&one.to_keyed_vec(b"sta:bal:")).to_vec() => vec![111u8, 0, 0, 0, 0, 0, 0, 0] + ], }; + + let tx = UncheckedTransaction { + transaction: Transaction { + signed: one.clone(), + nonce: 0, + function: Function::StakingTransfer(two, 69), + }, + signature: "5f9832c5a4a39e2dd4a3a0c5b400e9836beb362cb8f7d845a8291a2ae6fe366612e080e4acd0b5a75c3d0b6ee69614a68fb63698c1e76bf1f2dcd8fa617ddf05".parse().unwrap(), + }; + + with_externalities(&mut t, || { + internal::execute_transaction(tx, Header::from_block_number(1)); + assert_eq!(staking::balance(&one), 42); + assert_eq!(staking::balance(&two), 69); + }); + } + + fn new_test_ext() -> TestExternalities { + let one = one(); + let two = two(); + let three = [3u8; 32]; + + TestExternalities { storage: map![ + twox_128(&0u64.to_keyed_vec(b"sys:old:")).to_vec() => [69u8; 32].to_vec(), + twox_128(b"gov:apr").to_vec() => vec![].join(&667u32), + twox_128(b"ses:len").to_vec() => vec![].join(&2u64), + twox_128(b"ses:val:len").to_vec() => vec![].join(&3u32), + twox_128(&0u32.to_keyed_vec(b"ses:val:")).to_vec() => one.to_vec(), + twox_128(&1u32.to_keyed_vec(b"ses:val:")).to_vec() => two.to_vec(), + twox_128(&2u32.to_keyed_vec(b"ses:val:")).to_vec() => three.to_vec(), + twox_128(b"sta:wil:len").to_vec() => vec![].join(&3u32), + twox_128(&0u32.to_keyed_vec(b"sta:wil:")).to_vec() => one.to_vec(), + twox_128(&1u32.to_keyed_vec(b"sta:wil:")).to_vec() => two.to_vec(), + twox_128(&2u32.to_keyed_vec(b"sta:wil:")).to_vec() => three.to_vec(), + twox_128(b"sta:spe").to_vec() => vec![].join(&2u64), + twox_128(b"sta:vac").to_vec() => vec![].join(&3u64), + twox_128(b"sta:era").to_vec() => vec![].join(&0u64), + twox_128(&one.to_keyed_vec(b"sta:bal:")).to_vec() => vec![111u8, 0, 0, 0, 0, 0, 0, 0] + ], } + } + + #[test] + fn block_import_works() { + let one = one(); + let two = two(); + + let mut t = new_test_ext(); + + let h = Header { + parent_hash: [69u8; 32].into(), + number: 1, + state_root: hex!("1ab2dbb7d4868a670b181327b0b6a58dc64b10cfb9876f737a5aa014b8da31e0").into(), + transaction_root: hex!("56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421").into(), + digest: Digest { logs: vec![], }, + }; + + let b = Block { + header: h, + transactions: vec![], + }; + + with_externalities(&mut t, || { + execute_block(b); + }); + } + + #[test] + #[should_panic] + fn block_import_of_bad_state_root_fails() { + let one = one(); + let two = two(); + + let mut t = new_test_ext(); + + let h = Header { + parent_hash: [69u8; 32].into(), + number: 1, + state_root: [0u8; 32].into(), + transaction_root: hex!("56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421").into(), + digest: Digest { logs: vec![], }, + }; + + let b = Block { + header: h, + transactions: vec![], + }; + + with_externalities(&mut t, || { + execute_block(b); + }); + } + + #[test] + #[should_panic] + fn block_import_of_bad_transaction_root_fails() { + let one = one(); + let two = two(); + + let mut t = new_test_ext(); + + let h = Header { + parent_hash: [69u8; 32].into(), + number: 1, + state_root: hex!("1ab2dbb7d4868a670b181327b0b6a58dc64b10cfb9876f737a5aa014b8da31e0").into(), + transaction_root: [0u8; 32].into(), + digest: Digest { logs: vec![], }, + }; + + let b = Block { + header: h, + transactions: vec![], + }; + + with_externalities(&mut t, || { + execute_block(b); + }); + } +} diff --git a/polkadot/runtime/src/runtime/timestamp.rs b/polkadot/runtime/src/runtime/timestamp.rs new file mode 100644 index 0000000000..45ebdcbe70 --- /dev/null +++ b/polkadot/runtime/src/runtime/timestamp.rs @@ -0,0 +1,60 @@ +// 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 . + +//! Timestamp manager: just handles the current timestamp. + +use support::storage; + +pub type Timestamp = u64; + +const CURRENT_TIMESTAMP: &[u8] = b"tim:val"; + +/// Get the current time. +pub fn get() -> Timestamp { + storage::get_or_default(CURRENT_TIMESTAMP) +} + +pub mod public { + use super::*; + + /// Set the current time. + pub fn set(now: Timestamp) { + storage::put(CURRENT_TIMESTAMP, &now); + } +} + +#[cfg(test)] +mod tests { + use super::*; + use super::public::*; + + use runtime_io::{with_externalities, twox_128, TestExternalities}; + use runtime::timestamp; + use codec::{Joiner, KeyedVec}; + + #[test] + fn timestamp_works() { + let mut t = TestExternalities { storage: map![ + twox_128(CURRENT_TIMESTAMP).to_vec() => vec![].join(&42u64) + ], }; + + with_externalities(&mut t, || { + assert_eq!(get(), 42); + set(69); + assert_eq!(get(), 69); + }); + } +} diff --git a/polkadot/runtime/src/support/environment.rs b/polkadot/runtime/src/support/environment.rs new file mode 100644 index 0000000000..d7651c575a --- /dev/null +++ b/polkadot/runtime/src/support/environment.rs @@ -0,0 +1,82 @@ +// 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 . + +//! Environment API: Allows certain information to be accessed throughout the runtime. + +use rstd::boxed::Box; +use rstd::mem; +use rstd::cell::RefCell; +use rstd::rc::Rc; + +use polkadot_primitives::{BlockNumber, Digest, Hash}; + +#[derive(Default)] +/// The information that can be accessed globally. +pub struct Environment { + /// The current block number. + pub block_number: BlockNumber, + /// The current block's parent hash. + pub parent_hash: Hash, + /// The current block digest. + pub digest: Digest, +} + +/// Do something with the environment and return its value. Keep the function short. +pub fn with_env T>(f: F) -> T { + let e = env(); + let mut eb = e.borrow_mut(); + f(&mut *eb) +} + +#[cfg(target_arch = "wasm32")] +fn env() -> Rc> { + // Initialize it to a null value + static mut SINGLETON: *const Rc> = 0 as *const Rc>; + + unsafe { + if SINGLETON == 0 as *const Rc> { + // Make it + let singleton: Rc> = Rc::new(RefCell::new(Default::default())); + + // Put it in the heap so it can outlive this call + SINGLETON = mem::transmute(Box::new(singleton)); + } + + // Now we give out a copy of the data that is safe to use concurrently. + (*SINGLETON).clone() + } +} + +#[cfg(not(target_arch = "wasm32"))] +fn env() -> Rc> { + // Initialize it to a null value + thread_local!{ + static SINGLETON: RefCell<*const Rc>> = RefCell::new(0 as *const Rc>); + } + + SINGLETON.with(|s| unsafe { + if *s.borrow() == 0 as *const Rc> { + // Make it + let singleton: Rc> = Rc::new(RefCell::new(Default::default())); + + // Put it in the heap so it can outlive this call + *s.borrow_mut() = mem::transmute(Box::new(singleton)); + } + + // Now we give out a copy of the data that is safe to use concurrently. + (**s.borrow()).clone() + }) +} diff --git a/polkadot/runtime/src/support/hashable.rs b/polkadot/runtime/src/support/hashable.rs new file mode 100644 index 0000000000..b6ad775396 --- /dev/null +++ b/polkadot/runtime/src/support/hashable.rs @@ -0,0 +1,38 @@ +// 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 . + +//! Hashable trait. + +use codec::Slicable; +use runtime_io::{blake2_256, twox_128, twox_256}; + +pub trait Hashable: Sized { + fn blake2_256(&self) -> [u8; 32]; + fn twox_128(&self) -> [u8; 16]; + fn twox_256(&self) -> [u8; 32]; +} + +impl Hashable for T { + fn blake2_256(&self) -> [u8; 32] { + blake2_256(&self.to_vec()) + } + fn twox_128(&self) -> [u8; 16] { + twox_128(&self.to_vec()) + } + fn twox_256(&self) -> [u8; 32] { + twox_256(&self.to_vec()) + } +} diff --git a/polkadot/runtime/src/support/mod.rs b/polkadot/runtime/src/support/mod.rs new file mode 100644 index 0000000000..652fb82aa6 --- /dev/null +++ b/polkadot/runtime/src/support/mod.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 . + +//! Support code for the runtime. + +mod environment; +pub mod storage; +mod hashable; +#[cfg(feature = "std")] +mod statichex; +#[macro_use] +#[cfg(feature = "std")] +mod testing; + +pub use self::environment::with_env; +pub use self::storage::StorageVec; +pub use self::hashable::Hashable; + +#[cfg(feature = "std")] +pub use self::statichex::{StaticHexConversion, StaticHexInto}; + +#[cfg(feature = "std")] +pub use self::testing::{AsBytesRef, HexDisplay, one, two}; diff --git a/polkadot/runtime/src/support/statichex.rs b/polkadot/runtime/src/support/statichex.rs new file mode 100644 index 0000000000..b750a8ca10 --- /dev/null +++ b/polkadot/runtime/src/support/statichex.rs @@ -0,0 +1,61 @@ +// 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 . + +//! Interpret a static string of hex as a desired type. + +use rustc_hex::FromHex; + +/// Trait to allow conversion from a static hex string to an instance. +pub trait StaticHexConversion: Sized { + /// Convert the static str into Self. Use just like `From::from`. + fn from_static_hex(hex: &'static str) -> Self; +} + +macro_rules! impl_sizes { + ( $( $t:expr ),* ) => { $( + impl StaticHexConversion for [u8; $t] { + fn from_static_hex(hex: &'static str) -> Self { + let mut r = [0u8; $t]; + r.copy_from_slice(&FromHex::from_hex(hex).unwrap()); + r + } + } + )* } +} + +impl StaticHexConversion for Vec { + fn from_static_hex(hex: &'static str) -> Self { + FromHex::from_hex(hex).unwrap() + } +} + +impl_sizes!(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, + 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, + 33, 34, 35, 36, 37, 38, 39, 40, 451, 42, 43, 44, 45, 46, 47, 48, + 56, 64, 80, 96, 112, 128); + +/// Trait to allow converting from itself (only implemented for a static str) into some useful +/// type (which must implement `StaticHexConversion`). +pub trait StaticHexInto { + /// Convert self (i.e. a static str) into the appropriate type. Use just like `Into::into`. + fn convert(self) -> T; +} + +impl StaticHexInto for &'static str { + fn convert(self) -> T { + T::from_static_hex(self) + } +} diff --git a/polkadot/runtime/src/support/storage.rs b/polkadot/runtime/src/support/storage.rs new file mode 100644 index 0000000000..5878113b13 --- /dev/null +++ b/polkadot/runtime/src/support/storage.rs @@ -0,0 +1,350 @@ +// 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 . + +//! Stuff to do with the runtime's storage. + +use rstd::prelude::*; +use runtime_io::{self, twox_128}; +use codec::{Slicable, KeyedVec}; + +// TODO: consider using blake256 to avoid possible preimage attack. + +/// Return the value of the item in storage under `key`, or `None` if there is no explicit entry. +pub fn get(key: &[u8]) -> Option { + let raw = runtime_io::storage(&twox_128(key)[..]); + Slicable::from_slice(&mut &raw[..]) +} + +/// Return the value of the item in storage under `key`, or the type's default if there is no +/// explicit entry. +pub fn get_or_default(key: &[u8]) -> T { + get(key).unwrap_or_else(Default::default) +} + +/// Return the value of the item in storage under `key`, or `default_value` if there is no +/// explicit entry. +pub fn get_or(key: &[u8], default_value: T) -> T { + get(key).unwrap_or(default_value) +} + +/// Return the value of the item in storage under `key`, or `default_value()` if there is no +/// explicit entry. +pub fn get_or_else T>(key: &[u8], default_value: F) -> T { + get(key).unwrap_or_else(default_value) +} + +/// Please `value` in storage under `key`. +pub fn put(key: &[u8], value: &T) { + value.as_slice_then(|slice| runtime_io::set_storage(&twox_128(key)[..], slice)); +} + +/// Please `value` in storage under `key`. +pub fn place(key: &[u8], value: T) { + value.as_slice_then(|slice| runtime_io::set_storage(&twox_128(key)[..], slice)); +} + +/// Remove `key` from storage, returning its value if it had an explicit entry or `None` otherwise. +pub fn take(key: &[u8]) -> Option { + let r = get(key); + if r.is_some() { + kill(key); + } + r +} + +/// Remove `key` from storage, returning its value, or, if there was no explicit entry in storage, +/// the default for its type. +pub fn take_or_default(key: &[u8]) -> T { + take(key).unwrap_or_else(Default::default) +} + +/// Return the value of the item in storage under `key`, or `default_value` if there is no +/// explicit entry. Ensure there is no explicit entry on return. +pub fn take_or(key: &[u8], default_value: T) -> T { + take(key).unwrap_or(default_value) +} + +/// Return the value of the item in storage under `key`, or `default_value()` if there is no +/// explicit entry. Ensure there is no explicit entry on return. +pub fn take_or_else T>(key: &[u8], default_value: F) -> T { + take(key).unwrap_or_else(default_value) +} + +/// Check to see if `key` has an explicit entry in storage. +pub fn exists(key: &[u8]) -> bool { + let mut x = [0u8; 1]; + runtime_io::read_storage(&twox_128(key)[..], &mut x[..], 0) >= 1 +} + +/// Ensure `key` has no explicit entry in storage. +pub fn kill(key: &[u8]) { + runtime_io::set_storage(&twox_128(key)[..], b""); +} + +/// Get a Vec of bytes from storage. +pub fn get_raw(key: &[u8]) -> Vec { + runtime_io::storage(&twox_128(key)[..]) +} + +/// Put a raw byte slice into storage. +pub fn put_raw(key: &[u8], value: &[u8]) { + runtime_io::set_storage(&twox_128(key)[..], value) +} + +/// A trait to conveniently store a vector of storable data. +// TODO: add iterator support +pub trait StorageVec { + type Item: Default + Sized + Slicable; + const PREFIX: &'static [u8]; + + /// Get the current set of items. + fn items() -> Vec { + (0..Self::count()).into_iter().map(Self::item).collect() + } + + /// Set the current set of items. + fn set_items(items: &[Self::Item]) { + Self::set_count(items.len() as u32); + items.iter().enumerate().for_each(|(v, ref i)| Self::set_item(v as u32, i)); + } + + fn set_item(index: u32, item: &Self::Item) { + if index < Self::count() { + put(&index.to_keyed_vec(Self::PREFIX), item); + } + } + + fn item(index: u32) -> Self::Item { + get_or_default(&index.to_keyed_vec(Self::PREFIX)) + } + + fn set_count(count: u32) { + (count..Self::count()).for_each(|i| Self::set_item(i, &Self::Item::default())); + put(&b"len".to_keyed_vec(Self::PREFIX), &count); + } + + fn count() -> u32 { + get_or_default(&b"len".to_keyed_vec(Self::PREFIX)) + } +} + +pub mod unhashed { + use super::{runtime_io, Slicable, KeyedVec, Vec}; + + /// Return the value of the item in storage under `key`, or `None` if there is no explicit entry. + pub fn get(key: &[u8]) -> Option { + let raw = runtime_io::storage(key); + T::from_slice(&mut &raw[..]) + } + + /// Return the value of the item in storage under `key`, or the type's default if there is no + /// explicit entry. + pub fn get_or_default(key: &[u8]) -> T { + get(key).unwrap_or_else(Default::default) + } + + /// Return the value of the item in storage under `key`, or `default_value` if there is no + /// explicit entry. + pub fn get_or(key: &[u8], default_value: T) -> T { + get(key).unwrap_or(default_value) + } + + /// Return the value of the item in storage under `key`, or `default_value()` if there is no + /// explicit entry. + pub fn get_or_else T>(key: &[u8], default_value: F) -> T { + get(key).unwrap_or_else(default_value) + } + + /// Please `value` in storage under `key`. + pub fn put(key: &[u8], value: &T) { + value.as_slice_then(|slice| runtime_io::set_storage(key, slice)); + } + + /// Please `value` in storage under `key`. + pub fn place(key: &[u8], value: T) { + value.as_slice_then(|slice| runtime_io::set_storage(key, slice)); + } + + /// Remove `key` from storage, returning its value if it had an explicit entry or `None` otherwise. + pub fn take(key: &[u8]) -> Option { + let r = get(key); + if r.is_some() { + kill(key); + } + r + } + + /// Remove `key` from storage, returning its value, or, if there was no explicit entry in storage, + /// the default for its type. + pub fn take_or_default(key: &[u8]) -> T { + take(key).unwrap_or_else(Default::default) + } + + /// Return the value of the item in storage under `key`, or `default_value` if there is no + /// explicit entry. Ensure there is no explicit entry on return. + pub fn take_or(key: &[u8], default_value: T) -> T { + take(key).unwrap_or(default_value) + } + + /// Return the value of the item in storage under `key`, or `default_value()` if there is no + /// explicit entry. Ensure there is no explicit entry on return. + pub fn take_or_else T>(key: &[u8], default_value: F) -> T { + take(key).unwrap_or_else(default_value) + } + + /// Check to see if `key` has an explicit entry in storage. + pub fn exists(key: &[u8]) -> bool { + let mut x = [0u8; 1]; + runtime_io::read_storage(key, &mut x[..], 0) >= 1 + } + + /// Ensure `key` has no explicit entry in storage. + pub fn kill(key: &[u8]) { + runtime_io::set_storage(key, b""); + } + + /// Get a Vec of bytes from storage. + pub fn get_raw(key: &[u8]) -> Vec { + runtime_io::storage(key) + } + + /// Put a raw byte slice into storage. + pub fn put_raw(key: &[u8], value: &[u8]) { + runtime_io::set_storage(key, value) + } + + /// A trait to conveniently store a vector of storable data. + // TODO: add iterator support + pub trait StorageVec { + type Item: Default + Sized + Slicable; + const PREFIX: &'static [u8]; + + /// Get the current set of items. + fn items() -> Vec { + (0..Self::count()).into_iter().map(Self::item).collect() + } + + /// Set the current set of items. + fn set_items(items: &[Self::Item]) { + Self::set_count(items.len() as u32); + items.iter().enumerate().for_each(|(v, ref i)| Self::set_item(v as u32, i)); + } + + fn set_item(index: u32, item: &Self::Item) { + if index < Self::count() { + put(&index.to_keyed_vec(Self::PREFIX), item); + } + } + + fn item(index: u32) -> Self::Item { + get_or_default(&index.to_keyed_vec(Self::PREFIX)) + } + + fn set_count(count: u32) { + (count..Self::count()).for_each(|i| Self::set_item(i, &Self::Item::default())); + put(&b"len".to_keyed_vec(Self::PREFIX), &count); + } + + fn count() -> u32 { + get_or_default(&b"len".to_keyed_vec(Self::PREFIX)) + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + use std::collections::HashMap; + use support::HexDisplay; + use runtime_io::{storage, twox_128, TestExternalities, with_externalities}; + + #[test] + fn integers_can_be_stored() { + let mut t = TestExternalities { storage: HashMap::new(), }; + with_externalities(&mut t, || { + let x = 69u32; + put(b":test", &x); + let y: u32 = get(b":test").unwrap(); + assert_eq!(x, y); + }); + with_externalities(&mut t, || { + let x = 69426942i64; + put(b":test", &x); + let y: i64 = get(b":test").unwrap(); + assert_eq!(x, y); + }); + } + + #[test] + fn bools_can_be_stored() { + let mut t = TestExternalities { storage: HashMap::new(), }; + with_externalities(&mut t, || { + let x = true; + put(b":test", &x); + let y: bool = get(b":test").unwrap(); + assert_eq!(x, y); + }); + + with_externalities(&mut t, || { + let x = false; + put(b":test", &x); + let y: bool = get(b":test").unwrap(); + assert_eq!(x, y); + }); + } + + #[test] + fn vecs_can_be_retrieved() { + let mut t = TestExternalities { storage: HashMap::new(), }; + with_externalities(&mut t, || { + runtime_io::set_storage(&twox_128(b":test"), b"\x0b\0\0\0Hello world"); + let x = b"Hello world".to_vec(); + println!("Hex: {}", HexDisplay::from(&storage(&twox_128(b":test")))); + let y = get::>(b":test").unwrap(); + assert_eq!(x, y); + + }); + } + + #[test] + fn vecs_can_be_stored() { + let mut t = TestExternalities { storage: HashMap::new(), }; + let x = b"Hello world".to_vec(); + + with_externalities(&mut t, || { + put(b":test", &x); + }); + + println!("Ext is {:?}", t); + with_externalities(&mut t, || { + println!("Hex: {}", HexDisplay::from(&storage(&twox_128(b":test")))); + let y: Vec = get(b":test").unwrap(); + assert_eq!(x, y); + }); + } + + #[test] + fn proposals_can_be_stored() { + use polkadot_primitives::Proposal; + let mut t = TestExternalities { storage: HashMap::new(), }; + with_externalities(&mut t, || { + let x = Proposal::StakingSetSessionsPerEra(25519); + put(b":test", &x); + let y: Proposal = get(b":test").unwrap(); + assert_eq!(x, y); + }); + } +} diff --git a/polkadot/runtime/src/support/testing.rs b/polkadot/runtime/src/support/testing.rs new file mode 100644 index 0000000000..a230ded26f --- /dev/null +++ b/polkadot/runtime/src/support/testing.rs @@ -0,0 +1,83 @@ +// 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 . + +//! Testing helpers. + +use polkadot_primitives::AccountId; +use super::statichex::StaticHexInto; + +#[macro_export] +macro_rules! map { + ($( $name:expr => $value:expr ),*) => ( + vec![ $( ( $name, $value ) ),* ].into_iter().collect() + ) +} + +/// One account (to which we know the secret key). +pub fn one() -> AccountId { + "2f8c6129d816cf51c374bc7f08c3e63ed156cf78aefb4a6550d97b87997977ee".convert() +} +/// Another account (secret key known). +pub fn two() -> AccountId { + "d75a980182b10ab7d54bfed3c964073a0ee172f3daa62325af021a68f707511a".convert() +} + +/// Hex display, this time for no_std. See main codebase for documentation. +pub struct HexDisplay<'a>(&'a [u8]); + +impl<'a> HexDisplay<'a> { + /// See main codebase for documentation. + pub fn from(d: &'a AsBytesRef) -> Self { HexDisplay(d.as_bytes_ref()) } +} + +impl<'a> ::std::fmt::Display for HexDisplay<'a> { + fn fmt(&self, fmtr: &mut ::std::fmt::Formatter) -> Result<(), ::std::fmt::Error> { + for byte in self.0 { + try!( fmtr.write_fmt(format_args!("{:02x}", byte))); + } + Ok(()) + } +} + +/// See main codebase for documentation. +pub trait AsBytesRef { + /// See main codebase for documentation. + fn as_bytes_ref(&self) -> &[u8]; +} + +impl AsBytesRef for [u8] { + fn as_bytes_ref(&self) -> &[u8] { &self } +} + +impl<'a> AsBytesRef for &'a[u8] { + fn as_bytes_ref(&self) -> &[u8] { self } +} + +impl AsBytesRef for Vec { + fn as_bytes_ref(&self) -> &[u8] { &self[..] } +} + +macro_rules! impl_non_endians { + ( $( $t:ty ),* ) => { $( + impl AsBytesRef for $t { + fn as_bytes_ref(&self) -> &[u8] { &self[..] } + } + )* } +} + +impl_non_endians!([u8; 1], [u8; 2], [u8; 3], [u8; 4], [u8; 5], [u8; 6], [u8; 7], [u8; 8], + [u8; 10], [u8; 12], [u8; 14], [u8; 16], [u8; 20], [u8; 24], [u8; 28], [u8; 32], [u8; 40], + [u8; 48], [u8; 56], [u8; 64], [u8; 80], [u8; 96], [u8; 112], [u8; 128]); diff --git a/polkadot/runtime/wasm/Cargo.lock b/polkadot/runtime/wasm/Cargo.lock new file mode 100644 index 0000000000..506b138461 --- /dev/null +++ b/polkadot/runtime/wasm/Cargo.lock @@ -0,0 +1,879 @@ +[[package]] +name = "aho-corasick" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "memchr 2.0.1 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "ansi_term" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "arrayvec" +version = "0.3.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "nodrop 0.1.12 (registry+https://github.com/rust-lang/crates.io-index)", + "odds 0.2.26 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "arrayvec" +version = "0.4.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "nodrop 0.1.12 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "bigint" +version = "4.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "byteorder 1.2.1 (registry+https://github.com/rust-lang/crates.io-index)", + "crunchy 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)", + "heapsize 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)", + "rustc-hex 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)", + "rustc_version 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "bitflags" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "blake2-rfc" +version = "0.2.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "arrayvec 0.4.7 (registry+https://github.com/rust-lang/crates.io-index)", + "constant_time_eq 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "byteorder" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "cc" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "cfg-if" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "coco" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "either 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)", + "scopeguard 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "constant_time_eq" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "crunchy" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "ed25519" +version = "0.1.0" +dependencies = [ + "ring 0.12.1 (registry+https://github.com/rust-lang/crates.io-index)", + "rustc-hex 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)", + "substrate-primitives 0.1.0", + "untrusted 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "either" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "elastic-array" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "heapsize 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "env_logger" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "log 0.3.9 (registry+https://github.com/rust-lang/crates.io-index)", + "regex 0.2.5 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "environmental" +version = "0.1.0" + +[[package]] +name = "ethcore-bigint" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "bigint 4.2.0 (registry+https://github.com/rust-lang/crates.io-index)", + "heapsize 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.36 (registry+https://github.com/rust-lang/crates.io-index)", + "plain_hasher 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", + "rand 0.3.22 (registry+https://github.com/rust-lang/crates.io-index)", + "rustc-hex 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "ethcore-bytes" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "ethcore-logger" +version = "1.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "ansi_term 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)", + "arrayvec 0.3.25 (registry+https://github.com/rust-lang/crates.io-index)", + "env_logger 0.4.3 (registry+https://github.com/rust-lang/crates.io-index)", + "isatty 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)", + "lazy_static 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)", + "log 0.3.9 (registry+https://github.com/rust-lang/crates.io-index)", + "parking_lot 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)", + "regex 0.2.5 (registry+https://github.com/rust-lang/crates.io-index)", + "time 0.1.39 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "fixed-hash" +version = "0.1.3" +source = "git+https://github.com/rphmeier/primitives.git?branch=compile-for-wasm#8dc457899afdaf968ff7f16140b03d1e37b01d71" +dependencies = [ + "rand 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)", + "rustc-hex 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "fuchsia-zircon" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "bitflags 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)", + "fuchsia-zircon-sys 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "fuchsia-zircon-sys" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "gcc" +version = "0.3.54" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "hashdb" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "elastic-array 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)", + "ethcore-bigint 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "heapsize" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "winapi 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "hex-literal" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "hex-literal-impl 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", + "proc-macro-hack 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "hex-literal-impl" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "proc-macro-hack 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "isatty" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "kernel32-sys 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.36 (registry+https://github.com/rust-lang/crates.io-index)", + "termion 1.5.1 (registry+https://github.com/rust-lang/crates.io-index)", + "winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "keccak-hash" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "cc 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)", + "ethcore-bigint 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)", + "tiny-keccak 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "kernel32-sys" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)", + "winapi-build 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "lazy_static" +version = "0.2.11" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "lazy_static" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "libc" +version = "0.2.36" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "log" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "log 0.4.1 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "log" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "cfg-if 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "memchr" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "libc 0.2.36 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "memorydb" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "bigint 4.2.0 (registry+https://github.com/rust-lang/crates.io-index)", + "elastic-array 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)", + "ethcore-bigint 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)", + "hashdb 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", + "heapsize 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)", + "keccak-hash 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", + "rlp 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "nodrop" +version = "0.1.12" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "num_cpus" +version = "1.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "libc 0.2.36 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "odds" +version = "0.2.26" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "owning_ref" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "stable_deref_trait 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "parking_lot" +version = "0.4.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "owning_ref 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)", + "parking_lot_core 0.2.10 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "parking_lot_core" +version = "0.2.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "libc 0.2.36 (registry+https://github.com/rust-lang/crates.io-index)", + "rand 0.3.22 (registry+https://github.com/rust-lang/crates.io-index)", + "smallvec 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)", + "winapi 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "patricia-trie" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "elastic-array 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)", + "ethcore-bigint 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)", + "ethcore-bytes 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", + "ethcore-logger 1.9.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)", + "log 0.3.9 (registry+https://github.com/rust-lang/crates.io-index)", + "memorydb 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", + "rand 0.3.22 (registry+https://github.com/rust-lang/crates.io-index)", + "rlp 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)", + "triehash 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "plain_hasher" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "crunchy 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "polkadot-primitives" +version = "0.1.0" +dependencies = [ + "serde 1.0.27 (registry+https://github.com/rust-lang/crates.io-index)", + "serde_derive 1.0.27 (registry+https://github.com/rust-lang/crates.io-index)", + "substrate-codec 0.1.0", + "substrate-primitives 0.1.0", + "substrate-runtime-std 0.1.0", +] + +[[package]] +name = "polkadot-runtime" +version = "0.1.0" +dependencies = [ + "polkadot-primitives 0.1.0", + "substrate-codec 0.1.0", + "substrate-primitives 0.1.0", + "substrate-runtime-io 0.1.0", + "substrate-runtime-std 0.1.0", +] + +[[package]] +name = "proc-macro-hack" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "proc-macro-hack-impl 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "proc-macro-hack-impl" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "pwasm-alloc" +version = "0.1.0" +dependencies = [ + "pwasm-libc 0.1.0", + "rustc_version 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "pwasm-libc" +version = "0.1.0" + +[[package]] +name = "quote" +version = "0.3.15" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "rand" +version = "0.3.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "fuchsia-zircon 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.36 (registry+https://github.com/rust-lang/crates.io-index)", + "rand 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "rand" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "fuchsia-zircon 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.36 (registry+https://github.com/rust-lang/crates.io-index)", + "winapi 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "rayon" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "rayon-core 1.3.0 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "rayon-core" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "coco 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", + "lazy_static 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.36 (registry+https://github.com/rust-lang/crates.io-index)", + "num_cpus 1.8.0 (registry+https://github.com/rust-lang/crates.io-index)", + "rand 0.3.22 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "redox_syscall" +version = "0.1.37" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "redox_termios" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "redox_syscall 0.1.37 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "regex" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "aho-corasick 0.6.4 (registry+https://github.com/rust-lang/crates.io-index)", + "memchr 2.0.1 (registry+https://github.com/rust-lang/crates.io-index)", + "regex-syntax 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)", + "thread_local 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)", + "utf8-ranges 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "regex-syntax" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "ring" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "gcc 0.3.54 (registry+https://github.com/rust-lang/crates.io-index)", + "lazy_static 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.36 (registry+https://github.com/rust-lang/crates.io-index)", + "rayon 0.8.2 (registry+https://github.com/rust-lang/crates.io-index)", + "untrusted 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "rlp" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "byteorder 1.2.1 (registry+https://github.com/rust-lang/crates.io-index)", + "elastic-array 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)", + "ethcore-bigint 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)", + "lazy_static 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)", + "rustc-hex 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "rustc-hex" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "rustc-hex" +version = "2.0.0" +source = "git+https://github.com/rphmeier/rustc-hex.git#ee2ec40b9062ac7769ccb9dc891d6dc2cc9009d7" + +[[package]] +name = "rustc_version" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "semver 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "scopeguard" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "semver" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "semver-parser 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "semver-parser" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "serde" +version = "1.0.27" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "serde_derive" +version = "1.0.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "quote 0.3.15 (registry+https://github.com/rust-lang/crates.io-index)", + "serde_derive_internals 0.19.0 (registry+https://github.com/rust-lang/crates.io-index)", + "syn 0.11.11 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "serde_derive_internals" +version = "0.19.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "syn 0.11.11 (registry+https://github.com/rust-lang/crates.io-index)", + "synom 0.11.3 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "smallvec" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "stable_deref_trait" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "substrate-codec" +version = "0.1.0" +dependencies = [ + "substrate-runtime-std 0.1.0", +] + +[[package]] +name = "substrate-primitives" +version = "0.1.0" +dependencies = [ + "blake2-rfc 0.2.18 (registry+https://github.com/rust-lang/crates.io-index)", + "byteorder 1.2.1 (registry+https://github.com/rust-lang/crates.io-index)", + "crunchy 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)", + "fixed-hash 0.1.3 (git+https://github.com/rphmeier/primitives.git?branch=compile-for-wasm)", + "rustc-hex 2.0.0 (git+https://github.com/rphmeier/rustc-hex.git)", + "serde 1.0.27 (registry+https://github.com/rust-lang/crates.io-index)", + "serde_derive 1.0.27 (registry+https://github.com/rust-lang/crates.io-index)", + "substrate-codec 0.1.0", + "substrate-runtime-std 0.1.0", + "twox-hash 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)", + "uint 0.1.2 (git+https://github.com/rphmeier/primitives.git?branch=compile-for-wasm)", +] + +[[package]] +name = "substrate-runtime-io" +version = "0.1.0" +dependencies = [ + "ed25519 0.1.0", + "environmental 0.1.0", + "rustc_version 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)", + "substrate-codec 0.1.0", + "substrate-primitives 0.1.0", + "substrate-runtime-std 0.1.0", + "substrate-state-machine 0.1.0", + "triehash 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "substrate-runtime-std" +version = "0.1.0" +dependencies = [ + "pwasm-alloc 0.1.0", + "pwasm-libc 0.1.0", + "rustc_version 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "substrate-state-machine" +version = "0.1.0" +dependencies = [ + "byteorder 1.2.1 (registry+https://github.com/rust-lang/crates.io-index)", + "hashdb 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", + "hex-literal 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)", + "patricia-trie 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", + "substrate-primitives 0.1.0", + "triehash 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "syn" +version = "0.11.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "quote 0.3.15 (registry+https://github.com/rust-lang/crates.io-index)", + "synom 0.11.3 (registry+https://github.com/rust-lang/crates.io-index)", + "unicode-xid 0.0.4 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "synom" +version = "0.11.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "unicode-xid 0.0.4 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "termion" +version = "1.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "libc 0.2.36 (registry+https://github.com/rust-lang/crates.io-index)", + "redox_syscall 0.1.37 (registry+https://github.com/rust-lang/crates.io-index)", + "redox_termios 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "thread_local" +version = "0.3.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "lazy_static 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)", + "unreachable 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "time" +version = "0.1.39" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "libc 0.2.36 (registry+https://github.com/rust-lang/crates.io-index)", + "redox_syscall 0.1.37 (registry+https://github.com/rust-lang/crates.io-index)", + "winapi 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "tiny-keccak" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "triehash" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "ethcore-bigint 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)", + "keccak-hash 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", + "rlp 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "twox-hash" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "rand 0.3.22 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "uint" +version = "0.1.2" +source = "git+https://github.com/rphmeier/primitives.git?branch=compile-for-wasm#8dc457899afdaf968ff7f16140b03d1e37b01d71" +dependencies = [ + "byteorder 1.2.1 (registry+https://github.com/rust-lang/crates.io-index)", + "rustc-hex 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)", + "rustc_version 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "unicode-xid" +version = "0.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "unreachable" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "void 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "untrusted" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "utf8-ranges" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "void" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "winapi" +version = "0.2.8" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "winapi" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "winapi-i686-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)", + "winapi-x86_64-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "winapi-build" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "winapi-i686-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "winapi-x86_64-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[metadata] +"checksum aho-corasick 0.6.4 (registry+https://github.com/rust-lang/crates.io-index)" = "d6531d44de723825aa81398a6415283229725a00fa30713812ab9323faa82fc4" +"checksum ansi_term 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)" = "23ac7c30002a5accbf7e8987d0632fa6de155b7c3d39d0067317a391e00a2ef6" +"checksum arrayvec 0.3.25 (registry+https://github.com/rust-lang/crates.io-index)" = "06f59fe10306bb78facd90d28c2038ad23ffaaefa85bac43c8a434cde383334f" +"checksum arrayvec 0.4.7 (registry+https://github.com/rust-lang/crates.io-index)" = "a1e964f9e24d588183fcb43503abda40d288c8657dfc27311516ce2f05675aef" +"checksum bigint 4.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "5442186ef6560f30f1ee4b9c1e4c87a35a6879d3644550cc248ec2b955eb5fcd" +"checksum bitflags 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)" = "b3c30d3802dfb7281680d6285f2ccdaa8c2d8fee41f93805dba5c4cf50dc23cf" +"checksum blake2-rfc 0.2.18 (registry+https://github.com/rust-lang/crates.io-index)" = "5d6d530bdd2d52966a6d03b7a964add7ae1a288d25214066fd4b600f0f796400" +"checksum byteorder 1.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "652805b7e73fada9d85e9a6682a4abd490cb52d96aeecc12e33a0de34dfd0d23" +"checksum cc 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)" = "deaf9ec656256bb25b404c51ef50097207b9cbb29c933d31f92cae5a8a0ffee0" +"checksum cfg-if 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "d4c819a1287eb618df47cc647173c5c4c66ba19d888a6e50d605672aed3140de" +"checksum coco 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "c06169f5beb7e31c7c67ebf5540b8b472d23e3eade3b2ec7d1f5b504a85f91bd" +"checksum constant_time_eq 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)" = "8ff012e225ce166d4422e0e78419d901719760f62ae2b7969ca6b564d1b54a9e" +"checksum crunchy 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)" = "a2f4a431c5c9f662e1200b7c7f02c34e91361150e382089a8f2dec3ba680cbda" +"checksum either 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "740178ddf48b1a9e878e6d6509a1442a2d42fd2928aae8e7a6f8a36fb01981b3" +"checksum elastic-array 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)" = "258ff6a9a94f648d0379dbd79110e057edbb53eb85cc237e33eadf8e5a30df85" +"checksum env_logger 0.4.3 (registry+https://github.com/rust-lang/crates.io-index)" = "3ddf21e73e016298f5cb37d6ef8e8da8e39f91f9ec8b0df44b7deb16a9f8cd5b" +"checksum ethcore-bigint 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "bcb5af77e74a8f70e9c3337e069c37bc82178ef1b459c02091f73c4ad5281eb5" +"checksum ethcore-bytes 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "3977c772cd6c5c22e1c7cfa208e4c3b746bd6c3a6c8eeec0999a6b2103015ad5" +"checksum ethcore-logger 1.9.0 (registry+https://github.com/rust-lang/crates.io-index)" = "1fd5813e49546030be7d134e775088d56b8ff4ab60617b90e93d4f0513da4c5b" +"checksum fixed-hash 0.1.3 (git+https://github.com/rphmeier/primitives.git?branch=compile-for-wasm)" = "" +"checksum fuchsia-zircon 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)" = "2e9763c69ebaae630ba35f74888db465e49e259ba1bc0eda7d06f4a067615d82" +"checksum fuchsia-zircon-sys 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)" = "3dcaa9ae7725d12cdb85b3ad99a434db70b468c09ded17e012d86b5c1010f7a7" +"checksum gcc 0.3.54 (registry+https://github.com/rust-lang/crates.io-index)" = "5e33ec290da0d127825013597dbdfc28bee4964690c7ce1166cbc2a7bd08b1bb" +"checksum hashdb 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "d97be07c358c5b461268b4ce60304024c5fa5acfd4bd8cd743639f0252003cf5" +"checksum heapsize 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)" = "1679e6ea370dee694f91f1dc469bf94cf8f52051d147aec3e1f9497c6fc22461" +"checksum hex-literal 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "bd546ef520ab3745f1aae5f2cdc6de9e6498e94d1ab138b9eb3ddfbf335847fb" +"checksum hex-literal-impl 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "2ea76da4c7f1a54d01d54985566d3fdd960b2bbd7b970da024821c883c2d9631" +"checksum isatty 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)" = "8f2a233726c7bb76995cec749d59582e5664823b7245d4970354408f1d79a7a2" +"checksum keccak-hash 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "1f300c1f149cd9ca5214eed24f6e713a597517420fb8b15499824aa916259ec1" +"checksum kernel32-sys 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "7507624b29483431c0ba2d82aece8ca6cdba9382bff4ddd0f7490560c056098d" +"checksum lazy_static 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)" = "76f033c7ad61445c5b347c7382dd1237847eb1bce590fe50365dcb33d546be73" +"checksum lazy_static 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "c8f31047daa365f19be14b47c29df4f7c3b581832407daabe6ae77397619237d" +"checksum libc 0.2.36 (registry+https://github.com/rust-lang/crates.io-index)" = "1e5d97d6708edaa407429faa671b942dc0f2727222fb6b6539bf1db936e4b121" +"checksum log 0.3.9 (registry+https://github.com/rust-lang/crates.io-index)" = "e19e8d5c34a3e0e2223db8e060f9e8264aeeb5c5fc64a4ee9965c062211c024b" +"checksum log 0.4.1 (registry+https://github.com/rust-lang/crates.io-index)" = "89f010e843f2b1a31dbd316b3b8d443758bc634bed37aabade59c686d644e0a2" +"checksum memchr 2.0.1 (registry+https://github.com/rust-lang/crates.io-index)" = "796fba70e76612589ed2ce7f45282f5af869e0fdd7cc6199fa1aa1f1d591ba9d" +"checksum memorydb 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "013b7e4c5e10c764936ebc6bd3662d8e3c92292d267bf6a42ef3f5cad9c793ee" +"checksum nodrop 0.1.12 (registry+https://github.com/rust-lang/crates.io-index)" = "9a2228dca57108069a5262f2ed8bd2e82496d2e074a06d1ccc7ce1687b6ae0a2" +"checksum num_cpus 1.8.0 (registry+https://github.com/rust-lang/crates.io-index)" = "c51a3322e4bca9d212ad9a158a02abc6934d005490c054a2778df73a70aa0a30" +"checksum odds 0.2.26 (registry+https://github.com/rust-lang/crates.io-index)" = "4eae0151b9dacf24fcc170d9995e511669a082856a91f958a2fe380bfab3fb22" +"checksum owning_ref 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)" = "cdf84f41639e037b484f93433aa3897863b561ed65c6e59c7073d7c561710f37" +"checksum parking_lot 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)" = "149d8f5b97f3c1133e3cfcd8886449959e856b557ff281e292b733d7c69e005e" +"checksum parking_lot_core 0.2.10 (registry+https://github.com/rust-lang/crates.io-index)" = "9f35048d735bb93dd115a0030498785971aab3234d311fbe273d020084d26bd8" +"checksum patricia-trie 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "f1e2f638d79aba5c4a71a4f373df6e3cd702250a53b7f0ed4da1e2a7be9737ae" +"checksum plain_hasher 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "83ae80873992f511142c07d0ec6c44de5636628fdb7e204abd655932ea79d995" +"checksum proc-macro-hack 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "3ba8d4f9257b85eb6cdf13f055cea3190520aab1409ca2ab43493ea4820c25f0" +"checksum proc-macro-hack-impl 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "d5cb6f960ad471404618e9817c0e5d10b1ae74cfdf01fab89ea0641fe7fb2892" +"checksum quote 0.3.15 (registry+https://github.com/rust-lang/crates.io-index)" = "7a6e920b65c65f10b2ae65c831a81a073a89edd28c7cce89475bff467ab4167a" +"checksum rand 0.3.22 (registry+https://github.com/rust-lang/crates.io-index)" = "15a732abf9d20f0ad8eeb6f909bf6868722d9a06e1e50802b6a70351f40b4eb1" +"checksum rand 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)" = "eba5f8cb59cc50ed56be8880a5c7b496bfd9bd26394e176bc67884094145c2c5" +"checksum rayon 0.8.2 (registry+https://github.com/rust-lang/crates.io-index)" = "b614fe08b6665cb9a231d07ac1364b0ef3cb3698f1239ee0c4c3a88a524f54c8" +"checksum rayon-core 1.3.0 (registry+https://github.com/rust-lang/crates.io-index)" = "e64b609139d83da75902f88fd6c01820046840a18471e4dfcd5ac7c0f46bea53" +"checksum redox_syscall 0.1.37 (registry+https://github.com/rust-lang/crates.io-index)" = "0d92eecebad22b767915e4d529f89f28ee96dbbf5a4810d2b844373f136417fd" +"checksum redox_termios 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "7e891cfe48e9100a70a3b6eb652fef28920c117d366339687bd5576160db0f76" +"checksum regex 0.2.5 (registry+https://github.com/rust-lang/crates.io-index)" = "744554e01ccbd98fff8c457c3b092cd67af62a555a43bfe97ae8a0451f7799fa" +"checksum regex-syntax 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)" = "8e931c58b93d86f080c734bfd2bce7dd0079ae2331235818133c8be7f422e20e" +"checksum ring 0.12.1 (registry+https://github.com/rust-lang/crates.io-index)" = "6f7d28b30a72c01b458428e0ae988d4149c20d902346902be881e3edc4bb325c" +"checksum rlp 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "babe6fce20c0ca9b1582998734c4569082d0ad08e43772a1c6c40aef4f106ef9" +"checksum rustc-hex 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "0ceb8ce7a5e520de349e1fa172baeba4a9e8d5ef06c47471863530bc4972ee1e" +"checksum rustc-hex 2.0.0 (git+https://github.com/rphmeier/rustc-hex.git)" = "" +"checksum rustc_version 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "b9743a7670d88d5d52950408ecdb7c71d8986251ab604d4689dd2ca25c9bca69" +"checksum scopeguard 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)" = "94258f53601af11e6a49f722422f6e3425c52b06245a5cf9bc09908b174f5e27" +"checksum semver 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)" = "7a3186ec9e65071a2095434b1f5bb24838d4e8e130f584c790f6033c79943537" +"checksum semver-parser 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)" = "388a1df253eca08550bef6c72392cfe7c30914bf41df5269b68cbd6ff8f570a3" +"checksum serde 1.0.27 (registry+https://github.com/rust-lang/crates.io-index)" = "db99f3919e20faa51bb2996057f5031d8685019b5a06139b1ce761da671b8526" +"checksum serde_derive 1.0.27 (registry+https://github.com/rust-lang/crates.io-index)" = "f4ba7591cfe93755e89eeecdbcc668885624829b020050e6aec99c2a03bd3fd0" +"checksum serde_derive_internals 0.19.0 (registry+https://github.com/rust-lang/crates.io-index)" = "6e03f1c9530c3fb0a0a5c9b826bdd9246a5921ae995d75f512ac917fc4dd55b5" +"checksum smallvec 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)" = "44db0ecb22921ef790d17ae13a3f6d15784183ff5f2a01aa32098c7498d2b4b9" +"checksum stable_deref_trait 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "15132e0e364248108c5e2c02e3ab539be8d6f5d52a01ca9bbf27ed657316f02b" +"checksum syn 0.11.11 (registry+https://github.com/rust-lang/crates.io-index)" = "d3b891b9015c88c576343b9b3e41c2c11a51c219ef067b264bd9c8aa9b441dad" +"checksum synom 0.11.3 (registry+https://github.com/rust-lang/crates.io-index)" = "a393066ed9010ebaed60b9eafa373d4b1baac186dd7e008555b0f702b51945b6" +"checksum termion 1.5.1 (registry+https://github.com/rust-lang/crates.io-index)" = "689a3bdfaab439fd92bc87df5c4c78417d3cbe537487274e9b0b2dce76e92096" +"checksum thread_local 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)" = "279ef31c19ededf577bfd12dfae728040a21f635b06a24cd670ff510edd38963" +"checksum time 0.1.39 (registry+https://github.com/rust-lang/crates.io-index)" = "a15375f1df02096fb3317256ce2cee6a1f42fc84ea5ad5fc8c421cfe40c73098" +"checksum tiny-keccak 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "3e9241752647ca572f12c9b520a5d360d9099360c527770647e694001646a1d0" +"checksum triehash 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "9291c7f0fae44858b5e087dd462afb382354120003778f1695b44aab98c7abd7" +"checksum twox-hash 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "475352206e7a290c5fccc27624a163e8d0d115f7bb60ca18a64fc9ce056d7435" +"checksum uint 0.1.2 (git+https://github.com/rphmeier/primitives.git?branch=compile-for-wasm)" = "" +"checksum unicode-xid 0.0.4 (registry+https://github.com/rust-lang/crates.io-index)" = "8c1f860d7d29cf02cb2f3f359fd35991af3d30bac52c57d265a3c461074cb4dc" +"checksum unreachable 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "382810877fe448991dfc7f0dd6e3ae5d58088fd0ea5e35189655f84e6814fa56" +"checksum untrusted 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)" = "f392d7819dbe58833e26872f5f6f0d68b7bbbe90fc3667e98731c4a15ad9a7ae" +"checksum utf8-ranges 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "662fab6525a98beff2921d7f61a39e7d59e0b425ebc7d0d9e66d316e55124122" +"checksum void 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)" = "6a02e4885ed3bc0f2de90ea6dd45ebcbb66dacffe03547fadbb0eeae2770887d" +"checksum winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)" = "167dc9d6949a9b857f3451275e911c3f44255842c1f7a76f33c55103a909087a" +"checksum winapi 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)" = "04e3bd221fcbe8a271359c04f21a76db7d0c6028862d1bb5512d85e1e2eb5bb3" +"checksum winapi-build 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "2d315eee3b34aca4797b2da6b13ed88266e6d612562a0c46390af8299fc699bc" +"checksum winapi-i686-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" +"checksum winapi-x86_64-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" diff --git a/polkadot/runtime/wasm/Cargo.toml b/polkadot/runtime/wasm/Cargo.toml new file mode 100644 index 0000000000..054d05c658 --- /dev/null +++ b/polkadot/runtime/wasm/Cargo.toml @@ -0,0 +1,24 @@ +[package] +name = "polkadot-runtime" +version = "0.1.0" +authors = ["Parity Technologies "] + +[lib] +crate-type = ["cdylib"] + +[dependencies] +substrate-codec = { path = "../../codec", default-features = false } +substrate-runtime-std = { path = "../../runtime-std", default-features = false } +substrate-runtime-io = { path = "../../runtime-io", default-features = false } +substrate-primitives = { path = "../../primitives", default-features = false } +polkadot-primitives = { path = "../../polkadot-primitives", default-features = false } + +[features] +default = [] +std = ["substrate-codec/std", "substrate-runtime-io/std", "substrate-runtime-std/std", "substrate-primitives/std", "polkadot-primitives/std"] + +[profile.release] +panic = "abort" + +[workspace] +members = [] diff --git a/polkadot/runtime/wasm/build.sh b/polkadot/runtime/wasm/build.sh new file mode 100755 index 0000000000..d48d10a062 --- /dev/null +++ b/polkadot/runtime/wasm/build.sh @@ -0,0 +1,8 @@ +#!/bin/sh +set -e + +cargo +nightly build --target=wasm32-unknown-unknown --release +for i in polkadot_runtime +do + wasm-gc target/wasm32-unknown-unknown/release/$i.wasm target/wasm32-unknown-unknown/release/$i.compact.wasm +done diff --git a/polkadot/runtime/wasm/genesis.wasm b/polkadot/runtime/wasm/genesis.wasm new file mode 100644 index 0000000000..91c7b47574 Binary files /dev/null and b/polkadot/runtime/wasm/genesis.wasm differ diff --git a/polkadot/runtime/wasm/init.sh b/polkadot/runtime/wasm/init.sh new file mode 100755 index 0000000000..02a0059a87 --- /dev/null +++ b/polkadot/runtime/wasm/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/polkadot/runtime/wasm/src b/polkadot/runtime/wasm/src new file mode 120000 index 0000000000..5cd551cf26 --- /dev/null +++ b/polkadot/runtime/wasm/src @@ -0,0 +1 @@ +../src \ No newline at end of file diff --git a/polkadot/runtime/wasm/target/wasm32-unknown-unknown/release/polkadot_runtime.compact.wasm b/polkadot/runtime/wasm/target/wasm32-unknown-unknown/release/polkadot_runtime.compact.wasm new file mode 100644 index 0000000000..325669d0e0 Binary files /dev/null and b/polkadot/runtime/wasm/target/wasm32-unknown-unknown/release/polkadot_runtime.compact.wasm differ diff --git a/polkadot/runtime/wasm/target/wasm32-unknown-unknown/release/polkadot_runtime.wasm b/polkadot/runtime/wasm/target/wasm32-unknown-unknown/release/polkadot_runtime.wasm new file mode 100644 index 0000000000..91c7b47574 Binary files /dev/null and b/polkadot/runtime/wasm/target/wasm32-unknown-unknown/release/polkadot_runtime.wasm differ diff --git a/polkadot/src/main.rs b/polkadot/src/main.rs new file mode 100644 index 0000000000..50ff18462e --- /dev/null +++ b/polkadot/src/main.rs @@ -0,0 +1,30 @@ +// 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 . + +//! Polkadot CLI + +#![warn(missing_docs)] + +extern crate polkadot_cli as cli; + +#[macro_use] +extern crate error_chain; + +quick_main!(run); + +fn run() -> cli::error::Result<()> { + cli::run(::std::env::args()) +} diff --git a/polkadot/validator/Cargo.toml b/polkadot/validator/Cargo.toml new file mode 100644 index 0000000000..204c356a99 --- /dev/null +++ b/polkadot/validator/Cargo.toml @@ -0,0 +1,11 @@ +[package] +name = "polkadot-validator" +version = "0.1.0" +authors = ["Parity Technologies "] + +[dependencies] +error-chain = "0.11" +serde = "1.0" +substrate-primitives = { path = "../../substrate/primitives" } +substrate-serializer = { path = "../../substrate/serializer" } +polkadot-primitives = { path = "../primitives" } diff --git a/polkadot/validator/src/error.rs b/polkadot/validator/src/error.rs new file mode 100644 index 0000000000..1c8caf739f --- /dev/null +++ b/polkadot/validator/src/error.rs @@ -0,0 +1,33 @@ +// 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 serializer; + +error_chain! { + foreign_links { + Serialization(serializer::Error); + } + errors { + Timeout { + description("Validation task has timed-out."), + display("Validation timeout."), + } + InvalidCode(details: String) { + description("The code is invalid."), + display("invalid code: '{}'", details), + } + } +} diff --git a/polkadot/validator/src/lib.rs b/polkadot/validator/src/lib.rs new file mode 100644 index 0000000000..dc139e77d5 --- /dev/null +++ b/polkadot/validator/src/lib.rs @@ -0,0 +1,34 @@ +// 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 . + +//! Validator implementation. + +#[warn(missing_docs)] + +extern crate substrate_primitives as primitives; +extern crate substrate_serializer as serializer; +extern crate polkadot_primitives; +extern crate serde; + +#[macro_use] +extern crate error_chain; + +mod error; +mod parachains; +mod validator; + +pub use error::{Error, ErrorKind, Result}; +pub use validator::Validator; diff --git a/polkadot/validator/src/parachains.rs b/polkadot/validator/src/parachains.rs new file mode 100644 index 0000000000..67ba56ed1f --- /dev/null +++ b/polkadot/validator/src/parachains.rs @@ -0,0 +1,68 @@ +// 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 std::fmt; + +use polkadot_primitives::validator; +use serde::de::DeserializeOwned; + +use error::Result; + +/// Parachain code implementation. +pub trait ParachainCode: fmt::Debug { + /// Deserialized message type. + type Message: DeserializeOwned; + /// Balance download. + type Download: DeserializeOwned; + /// Deserialized block data type. + type BlockData: DeserializeOwned; + /// Parachain head data. + type HeadData: DeserializeOwned; + /// Result + type Result: Into; + + /// Given decoded messages and proof validate it and return egress posts. + fn check( + &self, + messages: Vec<(u64, Vec)>, + downloads: Vec, + block_data: Self::BlockData, + head_data: Self::HeadData, + ) -> Result; +} + +/// Dummy implementation of the first parachain validation. +#[derive(Debug)] +pub struct ParaChain1; + +impl ParachainCode for ParaChain1 { + type Message = (); + type Download = (); + type BlockData = (); + type HeadData = (); + type Result = validator::ValidationResult; + + fn check( + &self, + _messages: Vec<(u64, Vec)>, + _downloads: Vec, + _block_data: Self::BlockData, + _head_data: Self::HeadData, + ) -> Result + { + unimplemented!() + } +} diff --git a/polkadot/validator/src/validator.rs b/polkadot/validator/src/validator.rs new file mode 100644 index 0000000000..354bbf73dc --- /dev/null +++ b/polkadot/validator/src/validator.rs @@ -0,0 +1,101 @@ +// 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 std::fmt; + +use polkadot_primitives::{validator, parachain}; +use serde::de::DeserializeOwned; +use serializer; + +use error::{ErrorKind, Result}; +use parachains::{ParachainCode, ParaChain1}; + +/// A dummy validator implementation. +#[derive(Debug)] +pub struct Validator { + codes: Vec>, +} + +impl Validator { + /// Create a new validator. + pub fn new() -> Self { + Validator { + codes: vec![ + Box::new(ParaChain1) as Box + ], + } + } +} + +impl Validator { + pub fn validate( + &self, + code: &[u8], + consolidated_ingress: &[(u64, Vec)], + balance_downloads: &[validator::BalanceDownload], + block_data: ¶chain::BlockData, + previous_head_data: ¶chain::HeadData, + ) -> Result { + ensure!(code.len() == 1, ErrorKind::InvalidCode(format!("The code should be a single byte."))); + + match self.codes.get(code[0] as usize) { + Some(code) => code.check(consolidated_ingress, balance_downloads, block_data, previous_head_data), + None => bail!(ErrorKind::InvalidCode(format!("Unknown parachain code."))), + } + } +} + +/// Simplified parachain code verification +trait Code: fmt::Debug { + /// Given parachain candidate block data returns it's validity + /// and possible generated egress posts. + fn check( + &self, + consolidated_ingress: &[(u64, Vec)], + balance_downloads: &[validator::BalanceDownload], + block_data: ¶chain::BlockData, + previous_head_data: ¶chain::HeadData, + ) -> Result; +} + +impl Code for T where + M: DeserializeOwned, + B: DeserializeOwned, + R: Into, + T: ParachainCode, +{ + fn check( + &self, + consolidated_ingress: &[(u64, Vec)], + balance_downloads: &[validator::BalanceDownload], + block_data: ¶chain::BlockData, + previous_head_data: ¶chain::HeadData, + ) -> Result { + let messages = consolidated_ingress.iter() + .map(|&(ref block, ref vec)| Ok((*block, vec.iter() + .map(|msg| serializer::from_slice(&msg.0).map_err(Into::into)) + .collect::>>()? + ))) + .collect::>>()?; + let downloads = balance_downloads.iter() + .map(|download| serializer::from_slice(&download.0).map_err(Into::into)) + .collect::>>()?; + let block_data = serializer::from_slice(&block_data.0)?; + let head_data = serializer::from_slice(&previous_head_data.0)?; + + Ok(self.check(messages, downloads, block_data, head_data)?.into()) + } +}