Fix warning and directory restructure.

This commit is contained in:
Gav
2018-02-08 14:29:30 +01:00
commit aa67fe3781
67 changed files with 11560 additions and 0 deletions
+9
View File
@@ -0,0 +1,9 @@
[package]
name = "polkadot-candidate-agreement"
version = "0.1.0"
authors = ["Parity Technologies <admin@parity.io>"]
[dependencies]
futures = "0.1.17"
parking_lot = "0.4"
tokio-timer = "0.1.2"
@@ -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 <http://www.gnu.org/licenses/>.
//! 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<D, S> {
/// The round.
pub round_number: usize,
/// The digest prepared for.
pub digest: D,
/// Signatures for the prepare messages.
pub signatures: Vec<S>,
}
impl<D, S> UncheckedJustification<D, S> {
/// 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<F, V>(self, threshold: usize, mut check_message: F)
-> Result<Justification<D, S>, Self>
where
F: FnMut(usize, &D, &S) -> Option<V>,
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<D,S>(UncheckedJustification<D,S>);
impl<D, S> ::std::ops::Deref for Justification<D, S> {
type Target = UncheckedJustification<D, S>;
fn deref(&self) -> &Self::Target {
&self.0
}
}
/// Type alias to represent a justification specifically for a prepare.
pub type PrepareJustification<D, S> = Justification<D, S>;
/// The round's state, based on imported messages.
#[derive(PartialEq, Eq, Debug)]
pub enum State<Candidate, Digest, Signature> {
/// No proposal yet.
Begin,
/// Proposal received.
Proposed(Candidate),
/// Seen n - f prepares for this digest.
Prepared(PrepareJustification<Digest, Signature>),
/// Seen n - f commits for a digest.
Committed(Justification<Digest, Signature>),
/// Seen n - f round-advancement messages.
Advanced(Option<PrepareJustification<Digest, Signature>>),
}
#[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<Candidate, Digest, AuthorityId, Signature>
where
Candidate: Eq + Clone,
Digest: Hash + Eq + Clone,
AuthorityId: Hash + Eq,
Signature: Eq + Clone,
{
round_number: usize,
threshold: usize,
round_proposer: AuthorityId,
proposal: Option<Candidate>,
prepares: HashMap<AuthorityId, (Digest, Signature)>,
commits: HashMap<AuthorityId, (Digest, Signature)>,
vote_counts: HashMap<Digest, VoteCounts>,
advance_round: HashSet<AuthorityId>,
state: State<Candidate, Digest, Signature>,
}
impl<Candidate, Digest, AuthorityId, Signature> Accumulator<Candidate, Digest, AuthorityId, Signature>
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<Candidate, Digest, Signature> {
&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<Candidate, Digest, AuthorityId, Signature>,
)
{
// 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::<Candidate, _, _, _>::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::<Candidate, Digest, _, _>::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::<Candidate, _, _, _>::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),
}
}
}
+721
View File
@@ -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 <http://www.gnu.org/licenses/>.
//! 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<C, D> {
/// 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<C, D> Message<C, D> {
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<C, D, V, S> {
/// The message received.
pub message: Message<C, D>,
/// 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<Item=()>;
/// A future that resolves when a proposal is ready.
type CreateProposal: Future<Item=Self::Candidate>;
/// 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<Self::Candidate, Self::Digest>)
-> LocalizedMessage<Self::Candidate, Self::Digest, Self::AuthorityId, Self::Signature>;
/// 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<C, D, V, S> {
/// A consensus message (proposal or vote)
Consensus(LocalizedMessage<C, D, V, S>),
/// Auxiliary communication (just proof-of-lock for now).
Auxiliary(PrepareJustification<D, S>),
}
/// 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<C: Context + ?Sized>(pub Communication<C::Candidate, C::Digest, C::AuthorityId, C::Signature>);
impl<C: Context + ?Sized> Clone for ContextCommunication<C>
where
LocalizedMessage<C::Candidate, C::Digest, C::AuthorityId, C::Signature>: Clone,
PrepareJustification<C::Digest, C::Signature>: Clone,
{
fn clone(&self) -> Self {
ContextCommunication(self.0.clone())
}
}
#[derive(Debug)]
struct Sending<T> {
items: VecDeque<T>,
flushing: bool,
}
impl<T> Sending<T> {
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<S: Sink<SinkItem=T>>(&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<C, D, S> {
/// The candidate committed for. This will be unknown if
/// we never witnessed the proposal of the last round.
pub candidate: Option<C>,
/// A justification for the candidate.
pub justification: Justification<D, S>,
}
struct Locked<D, S> {
justification: PrepareJustification<D, S>,
}
impl<D, S> Locked<D, S> {
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<C: Context> {
nodes: usize,
max_faulty: usize,
fetching_proposal: Option<C::CreateProposal>,
round_timeout: future::Fuse<C::RoundTimeout>,
local_state: LocalState,
locked: Option<Locked<C::Digest, C::Signature>>,
notable_candidates: HashMap<C::Digest, C::Candidate>,
current_accumulator: Accumulator<C::Candidate, C::Digest, C::AuthorityId, C::Signature>,
future_accumulator: Accumulator<C::Candidate, C::Digest, C::AuthorityId, C::Signature>,
local_id: C::AuthorityId,
}
impl<C: Context> Strategy<C> {
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<C::Candidate, C::Digest, C::AuthorityId, C::Signature>
) {
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<C::Digest, C::Signature>,
) {
// 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<E>(&mut self, context: &C, sending: &mut Sending<ContextCommunication<C>>)
-> Poll<Committed<C::Candidate, C::Digest, C::Signature>, E>
where
C::RoundTimeout: Future<Error=E>,
C::CreateProposal: Future<Error=E>,
{
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<E>(&mut self, context: &C, sending: &mut Sending<ContextCommunication<C>>)
-> Poll<Committed<C::Candidate, C::Digest, C::Signature>, E>
where
C::RoundTimeout: Future<Error=E>,
C::CreateProposal: Future<Error=E>,
{
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<ContextCommunication<C>>)
-> Result<(), <C::CreateProposal as Future>::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<ContextCommunication<C>>) {
// 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<ContextCommunication<C>>) {
// 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<ContextCommunication<C>>)
-> Result<(), <C::RoundTimeout as Future>::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<C::Candidate, C::Digest>,
context: &C,
sending: &mut Sending<ContextCommunication<C>>
) {
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<C: Context, I, O> {
context: C,
input: I,
output: O,
concluded: Option<Committed<C::Candidate, C::Digest, C::Signature>>,
sending: Sending<ContextCommunication<C>>,
strategy: Strategy<C>,
}
impl<C, I, O, E> Future for Agreement<C, I, O>
where
C: Context,
C::RoundTimeout: Future<Error=E>,
C::CreateProposal: Future<Error=E>,
I: Stream<Item=ContextCommunication<C>,Error=E>,
O: Sink<SinkItem=ContextCommunication<C>,SinkError=E>,
E: From<InputStreamConcluded>,
{
type Item = Committed<C::Candidate, C::Digest, C::Signature>;
type Error = E;
fn poll(&mut self) -> Poll<Self::Item, Self::Error> {
// 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<C: Context, I, O, E>(context: C, nodes: usize, max_faulty: usize, input: I, output: O)
-> Agreement<C, I, O>
where
C: Context,
C::RoundTimeout: Future<Error=E>,
C::CreateProposal: Future<Error=E>,
I: Stream<Item=ContextCommunication<C>,Error=E>,
O: Sink<SinkItem=ContextCommunication<C>,SinkError=E>,
E: From<InputStreamConcluded>,
{
let strategy = Strategy::create(&context, nodes, max_faulty);
Agreement {
context,
input,
output,
concluded: None,
sending: Sending::with_capacity(4),
strategy: strategy,
}
}
@@ -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 <http://www.gnu.org/licenses/>.
//! 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<Candidate, Digest>, AuthorityId);
struct SharedContext {
node_count: usize,
current_round: usize,
awaiting_round_timeouts: HashMap<usize, Vec<oneshot::Sender<()>>>,
}
#[derive(Debug)]
struct Error;
impl From<InputStreamConcluded> 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<Future<Item=(),Error=Error>> {
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<usize>,
shared: Arc<Mutex<SharedContext>>,
}
impl Context for TestContext {
type Candidate = Candidate;
type Digest = Digest;
type AuthorityId = AuthorityId;
type Signature = Signature;
type RoundTimeout = Box<Future<Item=(), Error=Error>>;
type CreateProposal = FutureResult<Candidate, Error>;
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<Candidate, Digest>)
-> LocalizedMessage<Candidate, Digest, AuthorityId, Signature>
{
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::<Vec<_>>();
::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::<Vec<_>>();
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::<Vec<_>>();
::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);
}
}
@@ -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 <http://www.gnu.org/licenses/>.
//! 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<D, A, V> {
Availability(D, A),
Validity(D, V),
}
impl<D, A, V, E> Future for Checking<D, A, V>
where
D: Clone,
A: Future<Item=bool,Error=E>,
V: Future<Item=bool,Error=E>,
{
type Item = (D, CheckResult);
type Error = E;
fn poll(&mut self) -> Poll<Self::Item, Self::Error> {
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<C: Context, I> {
table: SharedTable<C>,
messages_in: Fuse<I>,
bft_out: mpsc::UnboundedSender<<C as TypeResolve>::BftCommunication>,
local_id: C::AuthorityId,
requesting_about: FuturesUnordered<Checking<
C::Digest,
<C::CheckAvailability as IntoFuture>::Future,
<C::CheckCandidate as IntoFuture>::Future,
>>,
checked_validity: HashSet<C::Digest>,
checked_availability: HashSet<C::Digest>,
}
impl<C: Context, I> HandleIncoming<C, I> {
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<C>) {
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<C::Digest, C::GroupId>) {
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<C, I, E> HandleIncoming<C, I>
where
C: Context,
I: Stream<Item=(C::AuthorityId, CheckedMessage<C>),Error=E>,
C::CheckAvailability: IntoFuture<Error=E>,
C::CheckCandidate: IntoFuture<Error=E>,
{
pub fn new(
table: SharedTable<C>,
messages_in: I,
bft_out: mpsc::UnboundedSender<<C as TypeResolve>::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<C, I, E> Future for HandleIncoming<C, I>
where
C: Context,
I: Stream<Item=(C::AuthorityId, CheckedMessage<C>),Error=E>,
C::CheckAvailability: IntoFuture<Error=E>,
C::CheckCandidate: IntoFuture<Error=E>,
{
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),
}
}
}
}
+625
View File
@@ -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 <http://www.gnu.org/licenses/>.
//! 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<Item=bool>;
/// A future that resolves when availability of a candidate's external
/// data is checked.
type CheckAvailability: IntoFuture<Item=bool>;
/// The statement batch type.
type StatementBatch: StatementBatch<
Self::AuthorityId,
table::SignedStatement<Self::ParachainCandidate, Self::Digest, Self::AuthorityId, Self::Signature>,
>;
/// 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<Self::Proposal>;
/// 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<F>(&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::ParachainCandidate, Self::Digest>
) -> Self::Signature;
/// Sign a BFT agreement message.
fn sign_bft_message(&self, &bft::Message<Self::Proposal, Self::Digest>) -> 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<C: Context> TypeResolve for C {
type SignedTableStatement = table::SignedStatement<C::ParachainCandidate, C::Digest, C::AuthorityId, C::Signature>;
type BftCommunication = bft::Communication<C::Proposal, C::Digest, C::AuthorityId, C::Signature>;
type BftCommitted = bft::Committed<C::Proposal,C::Digest,C::Signature>;
type Misbehavior = table::Misbehavior<C::ParachainCandidate, C::Digest, C::AuthorityId, C::Signature>;
}
/// Information about a specific group.
#[derive(Debug, Clone)]
pub struct GroupInfo<V: Hash + Eq> {
/// Authorities meant to check validity of candidates.
pub validity_guarantors: HashSet<V>,
/// Authorities meant to check availability of candidate data.
pub availability_guarantors: HashSet<V>,
/// Number of votes needed for validity.
pub needed_validity: usize,
/// Number of votes needed for availability.
pub needed_availability: usize,
}
struct TableContext<C: Context> {
context: C,
groups: HashMap<C::GroupId, GroupInfo<C::AuthorityId>>,
}
impl<C: Context> ::std::ops::Deref for TableContext<C> {
type Target = C;
fn deref(&self) -> &C {
&self.context
}
}
impl<C: Context> table::Context for TableContext<C> {
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<C: Context> {
table: Table<TableContext<C>>,
proposed_digest: Option<C::Digest>,
awaiting_proposal: Vec<oneshot::Sender<C::Proposal>>,
}
impl<C: Context> SharedTableInner<C> {
fn import_statement(
&mut self,
context: &TableContext<C>,
statement: <C as TypeResolve>::SignedTableStatement,
received_from: Option<C::AuthorityId>
) -> Option<table::Summary<C::Digest, C::GroupId>> {
self.table.import_statement(context, statement, received_from)
}
fn update_proposal(&mut self, context: &TableContext<C>) {
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<C>) -> oneshot::Receiver<C::Proposal> {
let (tx, rx) = oneshot::channel();
self.awaiting_proposal.push(tx);
self.update_proposal(context);
rx
}
fn proposal_valid(&mut self, context: &TableContext<C>, 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<C: Context> {
context: Arc<TableContext<C>>,
inner: Arc<Mutex<SharedTableInner<C>>>,
}
impl<C: Context> Clone for SharedTable<C> {
fn clone(&self) -> Self {
SharedTable {
context: self.context.clone(),
inner: self.inner.clone()
}
}
}
impl<C: Context> SharedTable<C> {
/// Create a new shared table.
pub fn new(context: C, groups: HashMap<C::GroupId, GroupInfo<C::AuthorityId>>) -> 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: <C as TypeResolve>::SignedTableStatement,
received_from: Option<C::AuthorityId>,
) -> Option<table::Summary<C::Digest, C::GroupId>> {
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<C::ParachainCandidate, C::Digest>,
) -> Option<table::Summary<C::Digest, C::GroupId>> {
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<I, U>(&self, iterable: I) -> U
where
I: IntoIterator<Item=(<C as TypeResolve>::SignedTableStatement, Option<C::AuthorityId>)>,
U: ::std::iter::FromIterator<table::Summary<C::Digest, C::GroupId>>,
{
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<C::Proposal> {
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<F, U>(&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<C::AuthorityId, <C as TypeResolve>::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<C::Digest> {
self.inner.lock().proposed_digest.clone()
}
// Get a handle to the table context.
fn context(&self) -> &TableContext<C> {
&*self.context
}
}
/// Errors that can occur during agreement.
#[derive(Debug, Clone, Copy, PartialEq)]
pub enum Error {
IoTerminated,
FaultyTimer,
CannotPropose,
}
impl From<bft::InputStreamConcluded> for Error {
fn from(_: bft::InputStreamConcluded) -> Error {
Error::IoTerminated
}
}
/// Context owned by the BFT future necessary to execute the logic.
pub struct BftContext<C: Context> {
context: C,
table: SharedTable<C>,
timer: Timer,
round_timeout_multiplier: u64,
}
impl<C: Context> bft::Context for BftContext<C>
where C::Proposal: 'static,
{
type AuthorityId = C::AuthorityId;
type Digest = C::Digest;
type Signature = C::Signature;
type Candidate = C::Proposal;
type RoundTimeout = Box<Future<Item=(),Error=Error>>;
type CreateProposal = Box<Future<Item=Self::Candidate,Error=Error>>;
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<Self::Candidate, Self::Digest>)
-> bft::LocalizedMessage<Self::Candidate, Self::Digest, Self::AuthorityId, Self::Signature>
{
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<C: Context> {
/// The context itself.
pub context: C,
/// For scheduling timeouts.
pub timer: Timer,
/// The statement table.
pub table: SharedTable<C>,
/// 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<C: Context> {
/// 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<CheckedMessage<C>>;
}
/// A batch of statements to send out.
pub trait StatementBatch<V, T> {
/// 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<C: Context> {
/// Messages meant for the BFT agreement logic.
Bft(<C as TypeResolve>::BftCommunication),
/// Statements circulating about the table.
Table(Vec<<C as TypeResolve>::SignedTableStatement>),
}
/// Outgoing messages to the network.
#[derive(Debug, Clone)]
pub enum OutgoingMessage<C: Context> {
/// Messages meant for BFT agreement peers.
Bft(<C as TypeResolve>::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<Context>,
net_in: NetIn,
net_out: NetOut,
recovery: Recovery,
propagate_statements: PropagateStatements,
local_candidate: LocalCandidate,
)
-> Box<Future<Item=<Context as TypeResolve>::BftCommitted,Error=Error>>
where
Context: ::Context + 'static,
Context::CheckCandidate: IntoFuture<Error=Err>,
Context::CheckAvailability: IntoFuture<Error=Err>,
NetIn: Stream<Item=(Context::AuthorityId, Vec<Recovery::UncheckedMessage>),Error=Err> + 'static,
NetOut: Sink<SinkItem=OutgoingMessage<Context>> + 'static,
Recovery: MessageRecovery<Context> + 'static,
PropagateStatements: Stream<Item=Context::StatementBatch,Error=Err> + 'static,
LocalCandidate: IntoFuture<Item=Context::ParachainCandidate> + '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)
}
@@ -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 <http://www.gnu.org/licenses/>.
//! 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<V: Ord + Eq, S, M> {
buffer: BTreeMap<V, VecDeque<M>>,
last_processed_from: Option<V>,
stored_messages: usize,
max_messages: usize,
inner: Fuse<S>,
}
impl<V: Ord + Eq + Clone, S: Stream, M> RoundRobinBuffer<V, S, M> {
/// 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<V: Ord + Eq + Clone, S, M> RoundRobinBuffer<V, S, M> {
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<M>) {
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<V: Ord + Eq + Clone, S, M> Stream for RoundRobinBuffer<V, S, M>
where S: Stream<Item=(V, Vec<M>)>
{
type Item = (V, M);
type Error = S::Error;
fn poll(&mut self) -> Poll<Option<Self::Item>, 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<u8> }
#[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::<Result<Vec<_>, ()>>().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::<Result<Vec<_>, ()>>().unwrap();
assert_eq!(output.len(), 100);
}
}
File diff suppressed because it is too large Load Diff
@@ -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 <http://www.gnu.org/licenses/>.
//! 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<usize>);
#[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<ParachainCandidate>,
}
#[derive(PartialEq, Eq, Debug, Clone)]
enum Signature {
Table(AuthorityId, table::Statement<ParachainCandidate, Digest>),
Bft(AuthorityId, bft::Message<Proposal, Digest>),
}
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<SharedTestContext>,
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<Future<Item=bool,Error=Error>>;
type CheckAvailability = Box<Future<Item=bool,Error=Error>>;
type StatementBatch = VecBatch<
AuthorityId,
table::SignedStatement<ParachainCandidate, Digest, AuthorityId, Signature>
>;
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<Proposal>
{
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<F>(&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<ParachainCandidate, Digest>
) -> Signature {
Signature::Table(self.local_id(), statement.clone())
}
fn sign_bft_message(&self, message: &bft::Message<Proposal, Digest>) -> Signature {
Signature::Bft(self.local_id(), message.clone())
}
}
struct TestRecovery;
impl MessageRecovery<TestContext> for TestRecovery {
type UncheckedMessage = OutgoingMessage<TestContext>;
fn check_message(&self, msg: Self::UncheckedMessage) -> Option<CheckedMessage<TestContext>> {
Some(match msg {
OutgoingMessage::Bft(c) => CheckedMessage::Bft(c),
OutgoingMessage::Table(batch) => CheckedMessage::Table(batch.items),
})
}
}
pub struct Network<T> {
endpoints: Vec<mpsc::UnboundedSender<T>>,
input: mpsc::UnboundedReceiver<(usize, T)>,
}
impl<T: Clone + Send + 'static> Network<T> {
pub fn new(nodes: usize)
-> (Self, Vec<mpsc::UnboundedSender<(usize, T)>>, Vec<mpsc::UnboundedReceiver<T>>)
{
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<T: Clone> Future for Network<T> {
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<V, T> {
pub max_len: usize,
pub targets: Vec<V>,
pub items: Vec<T>,
}
impl<V, T> ::StatementBatch<V, T> for VecBatch<V, T> {
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<GroupId, GroupInfo<AuthorityId>>
{
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<T>(n_authorities: usize) -> VecBatch<AuthorityId, T> {
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<TestContext>)>::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::<Vec<_>>();
futures::future::join_all(authorities).wait().unwrap();
}
+24
View File
@@ -0,0 +1,24 @@
[package]
name = "polkadot-cli"
version = "0.1.0"
authors = ["Parity Technologies <admin@parity.io>"]
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" }
+14
View File
@@ -0,0 +1,14 @@
name: polkadot
author: "Parity Team <admin@polkadot.io>"
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
+29
View File
@@ -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 <http://www.gnu.org/licenses/>.
//! 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"];
}
}
+142
View File
@@ -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 <http://www.gnu.org/licenses/>.
//! 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<u8>, Vec<u8>>) -> 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<Pair> {
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<Transaction>) -> (Vec<u8>, 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::<Vec<_>>();
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<u8>, 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();
}
}
+127
View File
@@ -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 <http://www.gnu.org/licenses/>.
//! 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<I, T>(args: I) -> error::Result<()> where
I: IntoIterator<Item = T>,
T: Into<std::ffi::OsString> + 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.");
}
+10
View File
@@ -0,0 +1,10 @@
[package]
name = "polkadot-collator"
version = "0.1.0"
authors = ["Parity Technologies <rphmeier@gmail.com>"]
description = "Abstract collation logic"
[dependencies]
futures = "0.1.17"
substrate-primitives = { path = "../../substrate/primitives", version = "0.1" }
polkadot-primitives = { path = "../primitives", version = "0.1" }
+219
View File
@@ -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 <http://www.gnu.org/licenses/>.
//! 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<I: IntoIterator<Item=(ParaId, Message)>>(
&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<Item=Vec<Vec<Message>>, Error=Self::Error>;
/// Provide a set of all parachains meant to be routed to at a block.
fn routing_parachains(&self) -> BTreeSet<ParaId>;
/// 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<Future<Item=ConsolidatedIngress, Error=R::Error> + '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::<Vec<_>>())
.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<Future<Item=parachain::Candidate, Error=R::Error> + '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<ParaId, Vec<Vec<Message>>>,
currently_routing: BTreeSet<ParaId>,
}
impl RelayChainContext for DummyRelayChainCtx {
type Error = ();
type FutureEgress = Result<Vec<Vec<Message>>, ()>;
fn routing_parachains(&self) -> BTreeSet<ParaId> {
self.currently_routing.clone()
}
fn unrouted_egress(&self, id: ParaId) -> Result<Vec<Vec<Message>>, ()> {
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<u8>| 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])),
]
))
}
}
+17
View File
@@ -0,0 +1,17 @@
[package]
name = "polkadot-executor"
version = "0.1.0"
authors = ["Parity Technologies <admin@parity.io>"]
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" }
+315
View File
@@ -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 <http://www.gnu.org/licenses/>.
//! 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<Vec<u8>> {
runtime::dispatch(method, data)
}
}
/// Creates new RustExecutor for contracts.
pub fn executor() -> NativeExecutor<LocalNativeExecutionDispatch> {
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<Pair> {
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<Transaction>) -> (Vec<u8>, 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::<Vec<_>>();
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<u8>, Hash) {
construct_block(
1,
[69u8; 32].into(),
hex!("2481853da20b9f4322f34650fea5f240dcbfb266d02db94bfa0153c31f4a29db").into(),
vec![Transaction {
signed: one(),
nonce: 0,
function: Function::StakingTransfer(two(), 69),
}]
)
}
fn block2() -> (Vec<u8>, 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);
});
}
}
+30
View File
@@ -0,0 +1,30 @@
[package]
description = "Polkadot network protocol"
name = "polkadot-network"
version = "0.1.0"
license = "GPL-3.0"
authors = ["Parity Technologies <admin@parity.io>"]
[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" }
+262
View File
@@ -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 <http://www.gnu.org/licenses/>.?
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<BlockData>),
}
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<BlockNumber, BlockRangeState>,
peer_requests: HashMap<PeerId, BlockNumber>,
}
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<message::BlockData>, 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<Range<BlockNumber>> {
// 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<BlockData> {
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<message::BlockData> {
(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::<Vec<_>>());
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::<Vec<_>>()[..]);
assert_eq!(drained[30..], blocks[41..81].iter().map(|b| BlockData { block: b.clone(), origin: 1 }).collect::<Vec<_>>()[..]);
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::<Vec<_>>()[..]);
assert_eq!(drained[40..], blocks[121..150].iter().map(|b| BlockData { block: b.clone(), origin: 1 }).collect::<Vec<_>>()[..]);
}
}
+57
View File
@@ -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 <http://www.gnu.org/licenses/>.
//! 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<block::Body>) -> Result<ImportResult, Error>;
/// Get blockchain info.
fn info(&self) -> Result<ClientInfo, Error>;
/// Get block status.
fn block_status(&self, hash: &block::HeaderHash) -> Result<BlockStatus, Error>;
/// Get block hash by number.
fn block_hash(&self, block_number: block::Number) -> Result<Option<block::HeaderHash>, Error>;
}
impl<B, E> Client for PolkadotClient<B, E> where
B: client::backend::Backend + Send + Sync + 'static,
E: state_machine::CodeExecutor + Send + Sync + 'static,
{
fn import(&self, header: block::Header, body: Option<block::Body>) -> Result<ImportResult, Error> {
(self as &Client).import(header, body)
}
fn info(&self) -> Result<ClientInfo, Error> {
(self as &Client).info()
}
fn block_status(&self, hash: &block::HeaderHash) -> Result<BlockStatus, Error> {
(self as &Client).block_status(hash)
}
fn block_hash(&self, block_number: block::Number) -> Result<Option<block::HeaderHash>, Error> {
(self as &Client).block_hash(block_number)
}
}
+30
View File
@@ -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 <http://www.gnu.org/licenses/>.?
use service::Role;
/// Protocol configuration
pub struct ProtocolConfig {
pub roles: Role,
}
impl Default for ProtocolConfig {
fn default() -> ProtocolConfig {
ProtocolConfig {
roles: Role::FULL,
}
}
}
+31
View File
@@ -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 <http://www.gnu.org/licenses/>.?
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 {
}
}
+78
View File
@@ -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 <http://www.gnu.org/licenses/>.?
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<u8>) -> 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<SessionInfo>;
/// 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<u8>) -> Result<(), NetworkError>{
self.network.send(peer_id, 0, data)
}
fn peer_session_info(&self, peer_id: PeerId) -> Option<SessionInfo> {
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)
}
}
+64
View File
@@ -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 <http://www.gnu.org/licenses/>.?
#![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()
}
+189
View File
@@ -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 <http://www.gnu.org/licenses/>.?
//! 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<u8>;
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<T> From<T> 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<RoleFlags> for Vec<Role> where {
fn from(flags: RoleFlags) -> Vec<Role> {
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<Header>,
/// Block body if requested.
pub body: Option<Body>,
/// Block receipt if requested.
pub receipt: Option<Bytes>,
/// Block message queue if requested.
pub message_queue: Option<Bytes>,
}
#[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<Role>,
/// 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<Signature>,
/// Validator address. Required for the validator role.
pub validator_id: Option<AuthorityId>,
/// Parachain id. Required for the collator role.
pub parachain_id: Option<ParachainId>,
}
#[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<BlockAttribute>,
/// Start from this block.
pub from: FromBlock,
/// End at this block. An implementation defined maximum is used when unspecified.
pub to: Option<HeaderHash>,
/// Sequence direction.
pub direction: Direction,
/// Maximum number of block to return. An implementation defined maximum is used when unspecified.
pub max: Option<u32>,
}
#[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<BlockData>,
}
#[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,
}
+344
View File
@@ -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 <http://www.gnu.org/licenses/>.?
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<Client>,
genesis_hash: HeaderHash,
sync: RwLock<ChainSync>,
/// All connected peers
peers: RwLock<HashMap<PeerId, Peer>>,
/// Connected peers pending Status message.
handshaking_peers: RwLock<HashMap<PeerId, time::Instant>>,
}
/// 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<message::BlockRequest>,
/// Request timestamp
request_timestamp: Option<time::Instant>,
/// Holds a set of transactions recently sent to this peer to avoid spamming.
_last_sent_transactions: HashSet<TransactionHash>,
/// 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<NodeId, usize>,
}
impl Protocol {
/// Create a new instance.
pub fn new(config: ProtocolConfig, chain: Arc<Client>) -> error::Result<Protocol> {
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<PeerInfo> {
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<TransactionHash, TransactionStats> {
BTreeMap::new()
}
pub fn chain(&self) -> &Client {
&*self.chain
}
}
+239
View File
@@ -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 <http://www.gnu.org/licenses/>.?
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<PeerInfo>;
/// Get this node id if available.
fn node_id(&self) -> Option<String>;
/// Returns propagation count for pending transactions.
fn transactions_stats(&self) -> BTreeMap<TransactionHash, TransactionStats>;
}
/// Peer connection information
#[derive(Debug)]
pub struct PeerInfo {
/// Public node id
pub id: Option<String>,
/// Node client ID
pub client_version: String,
/// Capabilities
pub capabilities: Vec<String>,
/// Remote endpoint address
pub remote_address: String,
/// Local endpoint address
pub local_address: String,
/// Dot protocol info.
pub dot_info: Option<ProtocolPeerInfo>,
}
/// 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<Client>,
}
/// Polkadot network service. Handles network IO and manages connectivity.
pub struct Service {
/// Network service
network: NetworkService,
/// Devp2p protocol handler
handler: Arc<ProtocolHandler>,
}
impl Service {
/// Creates and register protocol with the network service
pub fn new(params: Params) -> Result<Arc<Service>, 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<PeerInfo> {
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<String> {
self.network.external_url()
}
fn transactions_stats(&self) -> BTreeMap<TransactionHash, TransactionStats> {
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();
}
}
+368
View File
@@ -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 <http://www.gnu.org/licenses/>.?
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<PeerId, PeerSync>,
blocks: BlockCollection,
best_queued_number: BlockNumber,
best_queued_hash: HeaderHash,
required_block_attributes: Vec<message::BlockAttribute>,
}
/// 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<PeerId> = 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<PeerId> = 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));
}
}
View File
+25
View File
@@ -0,0 +1,25 @@
[package]
name = "polkadot-primitives"
version = "0.1.0"
authors = ["Parity Technologies <admin@parity.io>"]
[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",
]
+197
View File
@@ -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 <http://www.gnu.org/licenses/>.
//! 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<u8>);
impl Slicable for Log {
fn from_slice(value: &mut &[u8]) -> Option<Self> {
Vec::<u8>::from_slice(value).map(Log)
}
fn as_slice_then<R, F: FnOnce(&[u8]) -> 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<Log>,
}
impl Slicable for Digest {
fn from_slice(value: &mut &[u8]) -> Option<Self> {
Vec::<Log>::from_slice(value).map(|logs| Digest { logs })
}
fn as_slice_then<R, F: FnOnce(&[u8]) -> R>(&self, f: F) -> R {
self.logs.as_slice_then(f)
}
}
/// The block "body": A bunch of transactions.
pub type Body = Vec<UncheckedTransaction>;
/// 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<Self> {
Some(Block {
header: try_opt!(Slicable::from_slice(value)),
transactions: try_opt!(Slicable::from_slice(value)),
})
}
fn to_vec(&self) -> Vec<u8> {
let mut v = Vec::new();
v.extend(self.header.to_vec());
v.extend(self.transactions.to_vec());
v
}
fn as_slice_then<R, F: FnOnce(&[u8]) -> 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<Self> {
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<u8> {
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, F: FnOnce(&[u8]) -> 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);
}
}
+78
View File
@@ -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 <http://www.gnu.org/licenses/>.
//! 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;
+173
View File
@@ -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 <http://www.gnu.org/licenses/>.
//! 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<Id> for u64 {
fn from(x: Id) -> Self { x.0 }
}
impl From<u64> for Id {
fn from(x: u64) -> Self { Id(x) }
}
impl ::codec::Slicable for Id {
fn from_slice(value: &mut &[u8]) -> Option<Self> {
u64::from_slice(value).map(Id)
}
fn as_slice_then<R, F: FnOnce(&[u8]) -> 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<u8>);
/// 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<Message>)>);
/// 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<u8>);
/// 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<u8>);
/// 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<u8>);
/// 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<u8>);
/// 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<u8>);
impl ::codec::Slicable for Activity {
fn from_slice(value: &mut &[u8]) -> Option<Self> {
Vec::<u8>::from_slice(value).map(Activity)
}
fn as_slice_then<R, F: FnOnce(&[u8]) -> 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"
}"#);
}
}
+403
View File
@@ -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 <http://www.gnu.org/licenses/>.
//! 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<InternalFunctionId> {
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<u8>),
/// 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<Self> {
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<u8> {
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, F: FnOnce(&[u8]) -> 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<FunctionId> {
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<Self> {
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<u8> {
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, F: FnOnce(&[u8]) -> 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<Self> {
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<u8> {
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, F: FnOnce(&[u8]) -> 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<Self> {
// This is a little more complicated than usua since the binary format must be compatible
// with substrate's generic `Vec<u8>` 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<u8> {
let mut v = Vec::new();
// need to prefix with the total length as u32 to ensure it's binary comptible with
// Vec<u8>. 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, F: FnOnce(&[u8]) -> 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);
}
}
+76
View File
@@ -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 <http://www.gnu.org/licenses/>.
//! 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<u8>);
/// 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<u8>);
/// 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<u8>);
/// 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<Vec<EgressPost>>,
/// Balance uploads
pub balance_uploads: Vec<BalanceUpload>,
}
#[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"
]
}"#);
}
}
+25
View File
@@ -0,0 +1,25 @@
[package]
name = "polkadot-runtime"
version = "0.1.0"
authors = ["Parity Technologies <admin@parity.io>"]
[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"
]
+91
View File
@@ -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 <http://www.gnu.org/licenses/>.
//! 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<AccountId>,
pub authorities: Vec<AccountId>,
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<AccountId>, 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<u8>, Vec<u8>> {
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<u8>, Vec<u8>> {
use codec::Slicable;
map![
twox_128(&0u64.to_keyed_vec(b"sys:old:")).to_vec() => genesis_block.header.blake2_256().to_vec()
]
}
+121
View File
@@ -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 <http://www.gnu.org/licenses/>.
//! 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<CheckedTransaction, UncheckedTransaction> {
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<u8> {
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<u8> {
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<u8> {
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<u8> {
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::<Vec<_>>();
print("reserialised transactions.");
[stxs.len() as u8].to_vec()
}
impl_stubs!(execute_block, execute_transaction, finalise_block, run_tests);
+48
View File
@@ -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 <http://www.gnu.org/licenses/>.
//! 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<SessionKey> {
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);
}
}
+370
View File
@@ -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 <http://www.gnu.org/licenses/>.
//! 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::<Proposal>(CURRENT_PROPOSAL) {
let approvals_required = approvals_required();
let approved = session::validators().into_iter()
.filter_map(|v| storage::take::<bool>(&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);
});
}
}
+34
View File
@@ -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 <http://www.gnu.org/licenses/>.
//! 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
+142
View File
@@ -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 <http://www.gnu.org/licenses/>.
//! 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<Chain>,
/// Lookup from validator index to chain on which that validator has a duty to guarantee
/// availability.
pub guarantor_duty: Vec<Chain>,
}
/// 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::<Vec<_>>();
let mut roles_gua = roles_val.clone();
let h = with_env(|e| e.parent_hash.clone());
let mut seed = Vec::<u8>::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);
});
}
}
+249
View File
@@ -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 <http://www.gnu.org/licenses/>.
//! 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<AccountId> {
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::<u64>(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]]);
});
}
}
+406
View File
@@ -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 <http://www.gnu.org/licenses/>.
//! 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::<u32>(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::<Balance>(&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::<Vec<_>>();
intentions.sort_unstable_by(|&(b1, _), &(b2, _)| b2.cmp(&b1));
session::internal::set_validators(
&intentions.into_iter()
.map(|(_, v)| v)
.take(validator_count())
.collect::<Vec<_>>()
);
}
#[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);
});
}
}
+367
View File
@@ -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 <http://www.gnu.org/licenses/>.
//! 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::<Vec<_>>();
let txs = txs.iter().map(Vec::as_slice).collect::<Vec<_>>();
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);
});
}
}
+60
View File
@@ -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 <http://www.gnu.org/licenses/>.
//! 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);
});
}
}
@@ -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 <http://www.gnu.org/licenses/>.
//! 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: FnOnce(&mut Environment) -> T>(f: F) -> T {
let e = env();
let mut eb = e.borrow_mut();
f(&mut *eb)
}
#[cfg(target_arch = "wasm32")]
fn env() -> Rc<RefCell<Environment>> {
// Initialize it to a null value
static mut SINGLETON: *const Rc<RefCell<Environment>> = 0 as *const Rc<RefCell<Environment>>;
unsafe {
if SINGLETON == 0 as *const Rc<RefCell<Environment>> {
// Make it
let singleton: Rc<RefCell<Environment>> = 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<RefCell<Environment>> {
// Initialize it to a null value
thread_local!{
static SINGLETON: RefCell<*const Rc<RefCell<Environment>>> = RefCell::new(0 as *const Rc<RefCell<Environment>>);
}
SINGLETON.with(|s| unsafe {
if *s.borrow() == 0 as *const Rc<RefCell<Environment>> {
// Make it
let singleton: Rc<RefCell<Environment>> = 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()
})
}
+38
View File
@@ -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 <http://www.gnu.org/licenses/>.
//! 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<T: Slicable> 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())
}
}
+36
View File
@@ -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 <http://www.gnu.org/licenses/>.
//! 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};
+61
View File
@@ -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 <http://www.gnu.org/licenses/>.
//! 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<u8> {
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<T: StaticHexConversion>(self) -> T;
}
impl StaticHexInto for &'static str {
fn convert<T: StaticHexConversion>(self) -> T {
T::from_static_hex(self)
}
}
+350
View File
@@ -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 <http://www.gnu.org/licenses/>.
//! 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<T: Slicable + Sized>(key: &[u8]) -> Option<T> {
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<T: Slicable + Sized + 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<T: Slicable + Sized>(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: Slicable + Sized, F: FnOnce() -> T>(key: &[u8], default_value: F) -> T {
get(key).unwrap_or_else(default_value)
}
/// Please `value` in storage under `key`.
pub fn put<T: Slicable>(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<T: Slicable>(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<T: Slicable + Sized>(key: &[u8]) -> Option<T> {
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<T: Slicable + Sized + 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<T: Slicable + Sized>(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: Slicable + Sized, F: FnOnce() -> 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<u8> {
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<Self::Item> {
(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<T: Slicable + Sized>(key: &[u8]) -> Option<T> {
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<T: Slicable + Sized + 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<T: Slicable + Sized>(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: Slicable + Sized, F: FnOnce() -> T>(key: &[u8], default_value: F) -> T {
get(key).unwrap_or_else(default_value)
}
/// Please `value` in storage under `key`.
pub fn put<T: Slicable>(key: &[u8], value: &T) {
value.as_slice_then(|slice| runtime_io::set_storage(key, slice));
}
/// Please `value` in storage under `key`.
pub fn place<T: Slicable>(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<T: Slicable + Sized>(key: &[u8]) -> Option<T> {
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<T: Slicable + Sized + 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<T: Slicable + Sized>(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: Slicable + Sized, F: FnOnce() -> 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<u8> {
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<Self::Item> {
(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::<Vec<u8>>(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<u8> = 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);
});
}
}
+83
View File
@@ -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 <http://www.gnu.org/licenses/>.
//! 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<u8> {
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]);
+879
View File
@@ -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)" = "<none>"
"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)" = "<none>"
"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)" = "<none>"
"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"
+24
View File
@@ -0,0 +1,24 @@
[package]
name = "polkadot-runtime"
version = "0.1.0"
authors = ["Parity Technologies <admin@parity.io>"]
[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 = []
+8
View File
@@ -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
Binary file not shown.
+6
View File
@@ -0,0 +1,6 @@
#!/bin/sh
rustup update nightly
rustup target add wasm32-unknown-unknown --toolchain nightly
rustup update stable
cargo install --git https://github.com/alexcrichton/wasm-gc
+1
View File
@@ -0,0 +1 @@
../src
+30
View File
@@ -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 <http://www.gnu.org/licenses/>.
//! 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())
}
+11
View File
@@ -0,0 +1,11 @@
[package]
name = "polkadot-validator"
version = "0.1.0"
authors = ["Parity Technologies <admin@parity.io>"]
[dependencies]
error-chain = "0.11"
serde = "1.0"
substrate-primitives = { path = "../../substrate/primitives" }
substrate-serializer = { path = "../../substrate/serializer" }
polkadot-primitives = { path = "../primitives" }
+33
View File
@@ -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 <http://www.gnu.org/licenses/>.
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),
}
}
}
+34
View File
@@ -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 <http://www.gnu.org/licenses/>.
//! 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;
+68
View File
@@ -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 <http://www.gnu.org/licenses/>.
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<validator::ValidationResult>;
/// Given decoded messages and proof validate it and return egress posts.
fn check(
&self,
messages: Vec<(u64, Vec<Self::Message>)>,
downloads: Vec<Self::Download>,
block_data: Self::BlockData,
head_data: Self::HeadData,
) -> Result<Self::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<Self::Message>)>,
_downloads: Vec<Self::Download>,
_block_data: Self::BlockData,
_head_data: Self::HeadData,
) -> Result<Self::Result>
{
unimplemented!()
}
}
+101
View File
@@ -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 <http://www.gnu.org/licenses/>.
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<Box<Code>>,
}
impl Validator {
/// Create a new validator.
pub fn new() -> Self {
Validator {
codes: vec![
Box::new(ParaChain1) as Box<Code>
],
}
}
}
impl Validator {
pub fn validate(
&self,
code: &[u8],
consolidated_ingress: &[(u64, Vec<parachain::Message>)],
balance_downloads: &[validator::BalanceDownload],
block_data: &parachain::BlockData,
previous_head_data: &parachain::HeadData,
) -> Result<validator::ValidationResult> {
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<parachain::Message>)],
balance_downloads: &[validator::BalanceDownload],
block_data: &parachain::BlockData,
previous_head_data: &parachain::HeadData,
) -> Result<validator::ValidationResult>;
}
impl<M, B, T, R> Code for T where
M: DeserializeOwned,
B: DeserializeOwned,
R: Into<validator::ValidationResult>,
T: ParachainCode<Message=M, BlockData=B, Result=R>,
{
fn check(
&self,
consolidated_ingress: &[(u64, Vec<parachain::Message>)],
balance_downloads: &[validator::BalanceDownload],
block_data: &parachain::BlockData,
previous_head_data: &parachain::HeadData,
) -> Result<validator::ValidationResult> {
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::<Result<Vec<_>>>()?
)))
.collect::<Result<Vec<_>>>()?;
let downloads = balance_downloads.iter()
.map(|download| serializer::from_slice(&download.0).map_err(Into::into))
.collect::<Result<Vec<_>>>()?;
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())
}
}