From aa67fe378157ef00d4cc2c2e117a4348a9ba5436 Mon Sep 17 00:00:00 2001 From: Gav Date: Thu, 8 Feb 2018 14:29:30 +0100 Subject: [PATCH] Fix warning and directory restructure. --- polkadot/candidate-agreement/Cargo.toml | 9 + .../src/bft/accumulator.rs | 602 +++++++++ polkadot/candidate-agreement/src/bft/mod.rs | 721 ++++++++++ polkadot/candidate-agreement/src/bft/tests.rs | 350 +++++ .../src/handle_incoming.rs | 214 +++ polkadot/candidate-agreement/src/lib.rs | 625 +++++++++ .../candidate-agreement/src/round_robin.rs | 164 +++ polkadot/candidate-agreement/src/table.rs | 1191 +++++++++++++++++ polkadot/candidate-agreement/src/tests/mod.rs | 385 ++++++ polkadot/cli/Cargo.toml | 24 + polkadot/cli/src/cli.yml | 14 + polkadot/cli/src/error.rs | 29 + polkadot/cli/src/genesis.rs | 142 ++ polkadot/cli/src/lib.rs | 127 ++ polkadot/collator/Cargo.toml | 10 + polkadot/collator/src/lib.rs | 219 +++ polkadot/executor/Cargo.toml | 17 + polkadot/executor/src/lib.rs | 315 +++++ polkadot/network/Cargo.toml | 30 + polkadot/network/src/blocks.rs | 262 ++++ polkadot/network/src/chain.rs | 57 + polkadot/network/src/config.rs | 30 + polkadot/network/src/error.rs | 31 + polkadot/network/src/io.rs | 78 ++ polkadot/network/src/lib.rs | 64 + polkadot/network/src/message.rs | 189 +++ polkadot/network/src/protocol.rs | 344 +++++ polkadot/network/src/service.rs | 239 ++++ polkadot/network/src/sync.rs | 368 +++++ polkadot/network/src/test/mod.rs | 0 polkadot/primitives/Cargo.toml | 25 + polkadot/primitives/src/block.rs | 197 +++ polkadot/primitives/src/lib.rs | 78 ++ polkadot/primitives/src/parachain.rs | 173 +++ polkadot/primitives/src/transaction.rs | 403 ++++++ polkadot/primitives/src/validator.rs | 76 ++ polkadot/runtime/Cargo.toml | 25 + polkadot/runtime/src/genesismap.rs | 91 ++ polkadot/runtime/src/lib.rs | 121 ++ polkadot/runtime/src/runtime/consensus.rs | 48 + polkadot/runtime/src/runtime/governance.rs | 370 +++++ polkadot/runtime/src/runtime/mod.rs | 34 + polkadot/runtime/src/runtime/parachains.rs | 142 ++ polkadot/runtime/src/runtime/session.rs | 249 ++++ polkadot/runtime/src/runtime/staking.rs | 406 ++++++ polkadot/runtime/src/runtime/system.rs | 367 +++++ polkadot/runtime/src/runtime/timestamp.rs | 60 + polkadot/runtime/src/support/environment.rs | 82 ++ polkadot/runtime/src/support/hashable.rs | 38 + polkadot/runtime/src/support/mod.rs | 36 + polkadot/runtime/src/support/statichex.rs | 61 + polkadot/runtime/src/support/storage.rs | 350 +++++ polkadot/runtime/src/support/testing.rs | 83 ++ polkadot/runtime/wasm/Cargo.lock | 879 ++++++++++++ polkadot/runtime/wasm/Cargo.toml | 24 + polkadot/runtime/wasm/build.sh | 8 + polkadot/runtime/wasm/genesis.wasm | Bin 0 -> 70449 bytes polkadot/runtime/wasm/init.sh | 6 + polkadot/runtime/wasm/src | 1 + .../release/polkadot_runtime.compact.wasm | Bin 0 -> 70370 bytes .../release/polkadot_runtime.wasm | Bin 0 -> 70449 bytes polkadot/src/main.rs | 30 + polkadot/validator/Cargo.toml | 11 + polkadot/validator/src/error.rs | 33 + polkadot/validator/src/lib.rs | 34 + polkadot/validator/src/parachains.rs | 68 + polkadot/validator/src/validator.rs | 101 ++ 67 files changed, 11560 insertions(+) create mode 100644 polkadot/candidate-agreement/Cargo.toml create mode 100644 polkadot/candidate-agreement/src/bft/accumulator.rs create mode 100644 polkadot/candidate-agreement/src/bft/mod.rs create mode 100644 polkadot/candidate-agreement/src/bft/tests.rs create mode 100644 polkadot/candidate-agreement/src/handle_incoming.rs create mode 100644 polkadot/candidate-agreement/src/lib.rs create mode 100644 polkadot/candidate-agreement/src/round_robin.rs create mode 100644 polkadot/candidate-agreement/src/table.rs create mode 100644 polkadot/candidate-agreement/src/tests/mod.rs create mode 100644 polkadot/cli/Cargo.toml create mode 100644 polkadot/cli/src/cli.yml create mode 100644 polkadot/cli/src/error.rs create mode 100644 polkadot/cli/src/genesis.rs create mode 100644 polkadot/cli/src/lib.rs create mode 100644 polkadot/collator/Cargo.toml create mode 100644 polkadot/collator/src/lib.rs create mode 100644 polkadot/executor/Cargo.toml create mode 100644 polkadot/executor/src/lib.rs create mode 100644 polkadot/network/Cargo.toml create mode 100644 polkadot/network/src/blocks.rs create mode 100644 polkadot/network/src/chain.rs create mode 100644 polkadot/network/src/config.rs create mode 100644 polkadot/network/src/error.rs create mode 100644 polkadot/network/src/io.rs create mode 100644 polkadot/network/src/lib.rs create mode 100644 polkadot/network/src/message.rs create mode 100644 polkadot/network/src/protocol.rs create mode 100644 polkadot/network/src/service.rs create mode 100644 polkadot/network/src/sync.rs create mode 100644 polkadot/network/src/test/mod.rs create mode 100644 polkadot/primitives/Cargo.toml create mode 100644 polkadot/primitives/src/block.rs create mode 100644 polkadot/primitives/src/lib.rs create mode 100644 polkadot/primitives/src/parachain.rs create mode 100644 polkadot/primitives/src/transaction.rs create mode 100644 polkadot/primitives/src/validator.rs create mode 100644 polkadot/runtime/Cargo.toml create mode 100644 polkadot/runtime/src/genesismap.rs create mode 100644 polkadot/runtime/src/lib.rs create mode 100644 polkadot/runtime/src/runtime/consensus.rs create mode 100644 polkadot/runtime/src/runtime/governance.rs create mode 100644 polkadot/runtime/src/runtime/mod.rs create mode 100644 polkadot/runtime/src/runtime/parachains.rs create mode 100644 polkadot/runtime/src/runtime/session.rs create mode 100644 polkadot/runtime/src/runtime/staking.rs create mode 100644 polkadot/runtime/src/runtime/system.rs create mode 100644 polkadot/runtime/src/runtime/timestamp.rs create mode 100644 polkadot/runtime/src/support/environment.rs create mode 100644 polkadot/runtime/src/support/hashable.rs create mode 100644 polkadot/runtime/src/support/mod.rs create mode 100644 polkadot/runtime/src/support/statichex.rs create mode 100644 polkadot/runtime/src/support/storage.rs create mode 100644 polkadot/runtime/src/support/testing.rs create mode 100644 polkadot/runtime/wasm/Cargo.lock create mode 100644 polkadot/runtime/wasm/Cargo.toml create mode 100755 polkadot/runtime/wasm/build.sh create mode 100644 polkadot/runtime/wasm/genesis.wasm create mode 100755 polkadot/runtime/wasm/init.sh create mode 120000 polkadot/runtime/wasm/src create mode 100644 polkadot/runtime/wasm/target/wasm32-unknown-unknown/release/polkadot_runtime.compact.wasm create mode 100644 polkadot/runtime/wasm/target/wasm32-unknown-unknown/release/polkadot_runtime.wasm create mode 100644 polkadot/src/main.rs create mode 100644 polkadot/validator/Cargo.toml create mode 100644 polkadot/validator/src/error.rs create mode 100644 polkadot/validator/src/lib.rs create mode 100644 polkadot/validator/src/parachains.rs create mode 100644 polkadot/validator/src/validator.rs 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 0000000000000000000000000000000000000000..91c7b475743cb7b9cbb48c6dcc162501fe9e43c0 GIT binary patch literal 70449 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. + +//! 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()) + } +}