mirror of
https://github.com/pezkuwichain/pezkuwi-subxt.git
synced 2026-05-09 09:27:59 +00:00
Fix warning and directory restructure.
This commit is contained in:
@@ -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),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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),
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
@@ -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" }
|
||||
@@ -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
|
||||
@@ -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"];
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -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.");
|
||||
}
|
||||
@@ -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" }
|
||||
@@ -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])),
|
||||
]
|
||||
))
|
||||
}
|
||||
}
|
||||
@@ -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" }
|
||||
@@ -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);
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -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" }
|
||||
@@ -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<_>>()[..]);
|
||||
}
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
@@ -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,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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 {
|
||||
}
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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()
|
||||
}
|
||||
@@ -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,
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -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));
|
||||
}
|
||||
}
|
||||
@@ -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",
|
||||
]
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
@@ -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"
|
||||
}"#);
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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"
|
||||
]
|
||||
}"#);
|
||||
}
|
||||
}
|
||||
@@ -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"
|
||||
]
|
||||
@@ -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()
|
||||
]
|
||||
}
|
||||
@@ -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);
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
@@ -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);
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -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]]);
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -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()
|
||||
})
|
||||
}
|
||||
@@ -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())
|
||||
}
|
||||
}
|
||||
@@ -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};
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -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]);
|
||||
Generated
+879
@@ -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"
|
||||
@@ -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 = []
|
||||
Executable
+8
@@ -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.
Executable
+6
@@ -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
|
||||
Symlink
+1
@@ -0,0 +1 @@
|
||||
../src
|
||||
BIN
Binary file not shown.
Binary file not shown.
@@ -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())
|
||||
}
|
||||
@@ -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" }
|
||||
@@ -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),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
@@ -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!()
|
||||
}
|
||||
}
|
||||
@@ -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: ¶chain::BlockData,
|
||||
previous_head_data: ¶chain::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: ¶chain::BlockData,
|
||||
previous_head_data: ¶chain::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: ¶chain::BlockData,
|
||||
previous_head_data: ¶chain::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())
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user