Merge branch 'master' into gav-optional-storage

This commit is contained in:
Gav Wood
2018-02-16 20:05:11 +01:00
committed by GitHub
31 changed files with 443 additions and 169 deletions
+2 -2
View File
@@ -1400,12 +1400,10 @@ dependencies = [
"error-chain 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)",
"futures 0.1.18 (registry+https://github.com/rust-lang/crates.io-index)",
"parking_lot 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)",
"substrate-client 0.1.0",
"substrate-codec 0.1.0",
"substrate-executor 0.1.0",
"substrate-keyring 0.1.0",
"substrate-primitives 0.1.0",
"substrate-state-machine 0.1.0",
"tokio-core 0.1.12 (registry+https://github.com/rust-lang/crates.io-index)",
"tokio-timer 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)",
]
@@ -1419,6 +1417,7 @@ dependencies = [
"hex-literal 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)",
"parking_lot 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)",
"substrate-bft 0.1.0",
"substrate-codec 0.1.0",
"substrate-executor 0.1.0",
"substrate-keyring 0.1.0",
@@ -1482,6 +1481,7 @@ 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)",
"serde_json 1.0.9 (registry+https://github.com/rust-lang/crates.io-index)",
"substrate-bft 0.1.0",
"substrate-client 0.1.0",
"substrate-codec 0.1.0",
"substrate-executor 0.1.0",
+1 -1
View File
@@ -28,12 +28,12 @@ extern crate substrate_state_machine as state_machine;
extern crate error_chain;
use client::backend::Backend;
use client::blockchain::BlockId;
use client::Client;
use polkadot_runtime::runtime;
use polkadot_executor::Executor as LocalDispatch;
use substrate_executor::{NativeExecutionDispatch, NativeExecutor};
use primitives::{AccountId, SessionKey};
use primitives::block::Id as BlockId;
use primitives::parachain::DutyRoster;
error_chain! {
@@ -23,6 +23,8 @@ use rstd::vec::Vec;
use codec::{Input, Slicable};
use transaction::UncheckedTransaction;
pub use primitives::block::Id;
/// Used to refer to a block number.
pub type Number = u64;
-2
View File
@@ -5,10 +5,8 @@ authors = ["Parity Technologies <admin@parity.io>"]
[dependencies]
futures = "0.1.17"
substrate-client = { path = "../client" }
substrate-codec = { path = "../codec" }
substrate-primitives = { path = "../primitives" }
substrate-state-machine = { path = "../state-machine" }
ed25519 = { path = "../ed25519" }
tokio-timer = "0.1.2"
parking_lot = "0.4"
+1 -1
View File
@@ -19,7 +19,7 @@
error_chain! {
errors {
/// Missing state at block with given Id.
StateUnavailable(b: ::client::BlockId) {
StateUnavailable(b: ::primitives::block::Id) {
description("State missing at given block."),
display("State unavailable at block {:?}", b),
}
@@ -83,6 +83,13 @@ impl<D, S> UncheckedJustification<D, S> {
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Justification<D,S>(UncheckedJustification<D,S>);
impl<D, S> Justification<D, S> {
/// Convert this justification back to unchecked.
pub fn uncheck(self) -> UncheckedJustification<D, S> {
self.0
}
}
impl<D, S> ::std::ops::Deref for Justification<D, S> {
type Target = UncheckedJustification<D, S>;
+187 -60
View File
@@ -20,9 +20,7 @@ pub mod error;
pub mod generic;
extern crate substrate_codec as codec;
extern crate substrate_client as client;
extern crate substrate_primitives as primitives;
extern crate substrate_state_machine as state_machine;
extern crate ed25519;
extern crate tokio_timer;
extern crate parking_lot;
@@ -37,13 +35,11 @@ use std::collections::HashMap;
use std::sync::Arc;
use std::sync::atomic::{AtomicBool, Ordering};
use client::{BlockId, Client};
use client::backend::Backend;
use codec::Slicable;
use ed25519::Signature;
use primitives::block::{Block, Header, HeaderHash};
use ed25519::LocalizedSignature;
use primitives::bft::{Message as PrimitiveMessage, Action as PrimitiveAction, Justification as PrimitiveJustification};
use primitives::block::{Block, Id as BlockId, Header, HeaderHash};
use primitives::AuthorityId;
use state_machine::CodeExecutor;
use futures::{stream, task, Async, Sink, Future, IntoFuture};
use futures::future::Executor;
@@ -63,20 +59,46 @@ pub type LocalizedMessage = generic::LocalizedMessage<
Block,
HeaderHash,
AuthorityId,
Signature
LocalizedSignature
>;
/// Justification of some hash.
pub type Justification = generic::Justification<HeaderHash, Signature>;
pub type Justification = generic::Justification<HeaderHash, LocalizedSignature>;
/// Justification of a prepare message.
pub type PrepareJustification = generic::PrepareJustification<HeaderHash, Signature>;
pub type PrepareJustification = generic::PrepareJustification<HeaderHash, LocalizedSignature>;
/// Unchecked justification.
pub type UncheckedJustification = generic::UncheckedJustification<HeaderHash, LocalizedSignature>;
impl From<PrimitiveJustification> for UncheckedJustification {
fn from(just: PrimitiveJustification) -> Self {
UncheckedJustification {
round_number: just.round_number as usize,
digest: just.hash,
signatures: just.signatures.into_iter().map(|(from, sig)| LocalizedSignature {
signer: ed25519::Public(from),
signature: sig,
}).collect(),
}
}
}
impl From<UncheckedJustification> for PrimitiveJustification {
fn from(just: UncheckedJustification) -> Self {
PrimitiveJustification {
round_number: just.round_number as u32,
hash: just.digest,
signatures: just.signatures.into_iter().map(|s| (s.signer.into(), s.signature)).collect(),
}
}
}
/// Result of a committed round of BFT
pub type Committed = generic::Committed<Block, HeaderHash, Signature>;
pub type Committed = generic::Committed<Block, HeaderHash, LocalizedSignature>;
/// Communication between BFT participants.
pub type Communication = generic::Communication<Block, HeaderHash, AuthorityId, Signature>;
pub type Communication = generic::Communication<Block, HeaderHash, AuthorityId, LocalizedSignature>;
/// Logic for a proposer.
///
@@ -108,30 +130,6 @@ pub trait Authorities {
fn authorities(&self, at: &BlockId) -> Result<Vec<AuthorityId>, Error>;
}
impl<B, E> BlockImport for Client<B, E>
where
B: Backend,
E: CodeExecutor,
client::error::Error: From<<B::State as state_machine::backend::Backend>::Error>
{
fn import_block(&self, block: Block, _justification: Justification) {
// TODO: use justification.
let _ = self.import_block(block.header, Some(block.transactions));
}
}
impl<B, E> Authorities for Client<B, E>
where
B: Backend,
E: CodeExecutor,
client::error::Error: From<<B::State as state_machine::backend::Backend>::Error>
{
fn authorities(&self, at: &BlockId) -> Result<Vec<AuthorityId>, Error> {
self.authorities_at(at).map_err(|_| ErrorKind::StateUnavailable(*at).into())
}
}
/// Instance of BFT agreement.
struct BftInstance<P> {
key: Arc<ed25519::Pair>,
@@ -145,7 +143,7 @@ struct BftInstance<P> {
impl<P: Proposer> generic::Context for BftInstance<P> {
type AuthorityId = AuthorityId;
type Digest = HeaderHash;
type Signature = Signature;
type Signature = LocalizedSignature;
type Candidate = Block;
type RoundTimeout = Box<Future<Item=(),Error=Error> + Send>;
type CreateProposal = <P::CreateProposal as IntoFuture>::Future;
@@ -163,28 +161,7 @@ impl<P: Proposer> generic::Context for BftInstance<P> {
}
fn sign_local(&self, message: Message) -> LocalizedMessage {
use primitives::bft::{Message as PrimitiveMessage, Action as PrimitiveAction};
let action = match message.clone() {
::generic::Message::Propose(r, p) => PrimitiveAction::Propose(r as u32, p),
::generic::Message::Prepare(r, h) => PrimitiveAction::Prepare(r as u32, h),
::generic::Message::Commit(r, h) => PrimitiveAction::Commit(r as u32, h),
::generic::Message::AdvanceRound(r) => PrimitiveAction::AdvanceRound(r as u32),
};
let primitive = PrimitiveMessage {
parent: self.parent_hash,
action,
};
let to_sign = Slicable::encode(&primitive);
let signature = self.key.sign(&to_sign);
LocalizedMessage {
message,
signature,
sender: self.key.public().0
}
sign_message(message, &*self.key, self.parent_hash.clone())
}
fn round_proposer(&self, round: usize) -> AuthorityId {
@@ -327,7 +304,7 @@ impl<P, E, I> BftService<P, E, I>
// TODO: check key is one of the authorities.
let authorities = self.client.authorities(&BlockId::Hash(hash))?;
let n = authorities.len();
let max_faulty = n.saturating_sub(1) / 3;
let max_faulty = max_faulty_of(n);
let bft_instance = BftInstance {
proposer,
@@ -372,6 +349,84 @@ impl<P, E, I> BftService<P, E, I>
}
}
/// Given a total number of authorities, yield the maximum faulty that would be allowed.
/// This will always be under 1/3.
pub fn max_faulty_of(n: usize) -> usize {
n.saturating_sub(1) / 3
}
fn check_justification_signed_message(authorities: &[AuthorityId], message: &[u8], just: UncheckedJustification)
-> Result<Justification, UncheckedJustification>
{
just.check(authorities.len() - max_faulty_of(authorities.len()), |_, _, sig| {
let auth_id = sig.signer.0;
if !authorities.contains(&auth_id) { return None }
if ed25519::verify_strong(&sig.signature, message, &sig.signer) {
Some(sig.signer.0)
} else {
None
}
})
}
/// Check a full justification for a header hash.
/// Provide all valid authorities.
///
/// On failure, returns the justification back.
pub fn check_justification(authorities: &[AuthorityId], parent: HeaderHash, just: UncheckedJustification)
-> Result<Justification, UncheckedJustification>
{
let message = Slicable::encode(&PrimitiveMessage {
parent,
action: PrimitiveAction::Commit(just.round_number as u32, just.digest),
});
check_justification_signed_message(authorities, &message[..], just)
}
/// Check a prepare justification for a header hash.
/// Provide all valid authorities.
///
/// On failure, returns the justification back.
pub fn check_prepare_justification(authorities: &[AuthorityId], parent: HeaderHash, just: UncheckedJustification)
-> Result<PrepareJustification, UncheckedJustification>
{
let message = Slicable::encode(&PrimitiveMessage {
parent,
action: PrimitiveAction::Prepare(just.round_number as u32, just.digest),
});
check_justification_signed_message(authorities, &message[..], just)
}
/// Sign a BFT message with the given key.
pub fn sign_message(message: Message, key: &ed25519::Pair, parent_hash: HeaderHash) -> LocalizedMessage {
let action = match message.clone() {
::generic::Message::Propose(r, p) => PrimitiveAction::Propose(r as u32, p),
::generic::Message::Prepare(r, h) => PrimitiveAction::Prepare(r as u32, h),
::generic::Message::Commit(r, h) => PrimitiveAction::Commit(r as u32, h),
::generic::Message::AdvanceRound(r) => PrimitiveAction::AdvanceRound(r as u32),
};
let primitive = PrimitiveMessage {
parent: parent_hash,
action,
};
let to_sign = Slicable::encode(&primitive);
let signature = LocalizedSignature {
signer: key.public(),
signature: key.sign(&to_sign),
};
LocalizedMessage {
message,
signature,
sender: key.public().0
}
}
#[cfg(test)]
mod tests {
use super::*;
@@ -470,4 +525,76 @@ mod tests {
core.turn(Some(::std::time::Duration::from_millis(100)));
}
#[test]
fn max_faulty() {
assert_eq!(max_faulty_of(3), 0);
assert_eq!(max_faulty_of(4), 1);
assert_eq!(max_faulty_of(100), 33);
assert_eq!(max_faulty_of(0), 0);
assert_eq!(max_faulty_of(11), 3);
assert_eq!(max_faulty_of(99), 32);
}
#[test]
fn justification_check_works() {
let parent_hash = Default::default();
let hash = [0xff; 32].into();
let authorities = vec![
Keyring::One.to_raw_public(),
Keyring::Two.to_raw_public(),
Keyring::Alice.to_raw_public(),
Keyring::Eve.to_raw_public(),
];
let authorities_keys = vec![
Keyring::One.into(),
Keyring::Two.into(),
Keyring::Alice.into(),
Keyring::Eve.into(),
];
let unchecked = UncheckedJustification {
digest: hash,
round_number: 1,
signatures: authorities_keys.iter().take(3).map(|key| {
sign_message(generic::Message::Commit(1, hash), key, parent_hash).signature
}).collect(),
};
assert!(check_justification(&authorities, parent_hash, unchecked).is_ok());
let unchecked = UncheckedJustification {
digest: hash,
round_number: 0, // wrong round number (vs. the signatures)
signatures: authorities_keys.iter().take(3).map(|key| {
sign_message(generic::Message::Commit(1, hash), key, parent_hash).signature
}).collect(),
};
assert!(check_justification(&authorities, parent_hash, unchecked).is_err());
// not enough signatures.
let unchecked = UncheckedJustification {
digest: hash,
round_number: 1,
signatures: authorities_keys.iter().take(2).map(|key| {
sign_message(generic::Message::Commit(1, hash), key, parent_hash).signature
}).collect(),
};
assert!(check_justification(&authorities, parent_hash, unchecked).is_err());
// wrong hash.
let unchecked = UncheckedJustification {
digest: [0xfe; 32].into(),
round_number: 1,
signatures: authorities_keys.iter().take(3).map(|key| {
sign_message(generic::Message::Commit(1, hash), key, parent_hash).signature
}).collect(),
};
assert!(check_justification(&authorities, parent_hash, unchecked).is_err());
}
}
+1
View File
@@ -10,6 +10,7 @@ parking_lot = "0.4"
triehash = "0.1"
hex-literal = "0.1"
ed25519 = { path = "../ed25519" }
substrate-bft = { path = "../bft" }
substrate-codec = { path = "../codec" }
substrate-executor = { path = "../executor" }
substrate-primitives = { path = "../primitives" }
+4 -4
View File
@@ -18,8 +18,8 @@
use state_machine;
use error;
use primitives::block;
use blockchain::{self, BlockId};
use primitives::block::{self, Id as BlockId};
use primitives;
/// Block insertion operation. Keeps hold if the inserted block state and data.
pub trait BlockImportOperation {
@@ -29,7 +29,7 @@ pub trait BlockImportOperation {
/// Returns pending state.
fn state(&self) -> error::Result<&Self::State>;
/// Append block data to the transaction.
fn set_block_data(&mut self, header: block::Header, body: Option<block::Body>, is_new_best: bool) -> error::Result<()>;
fn set_block_data(&mut self, header: block::Header, body: Option<block::Body>, justification: Option<primitives::bft::Justification>, is_new_best: bool) -> error::Result<()>;
/// Inject storage data into the database.
fn set_storage<I: Iterator<Item=(Vec<u8>, Option<Vec<u8>>)>>(&mut self, changes: I) -> error::Result<()>;
/// Inject storage data into the database.
@@ -41,7 +41,7 @@ pub trait Backend {
/// Associated block insertion operation type.
type BlockImportOperation: BlockImportOperation;
/// Associated blockchain backend type.
type Blockchain: blockchain::Backend;
type Blockchain: ::blockchain::Backend;
/// Associated state backend type.
type State: state_machine::backend::Backend;
@@ -20,8 +20,8 @@ use std::vec::Vec;
use codec::{Joiner, Slicable};
use state_machine::{self, CodeExecutor};
use primitives::{Header, Block};
use primitives::block::Transaction;
use {backend, error, BlockId, Client};
use primitives::block::{Id as BlockId, Transaction};
use {backend, error, Client};
use triehash::ordered_trie_root;
/// Utility for building new (valid) blocks from a stream of transactions.
+4 -19
View File
@@ -16,27 +16,10 @@
//! Polkadot blockchain trait
use std::fmt::{Display, Formatter, Error as FmtError};
use primitives::block;
use primitives::block::{self, Id as BlockId};
use primitives;
use error::Result;
/// Block indentification.
#[derive(Debug, Clone, Copy)]
pub enum BlockId {
/// Identify by block header hash.
Hash(block::HeaderHash),
/// Identify by block number.
Number(block::Number),
}
impl Display for BlockId {
fn fmt(&self, f: &mut Formatter) -> ::std::result::Result<(), FmtError> {
match *self {
BlockId::Hash(h) => h.fmt(f),
BlockId::Number(n) => n.fmt(f),
}
}
}
/// Blockchain database backend. Does not perform any validation.
pub trait Backend: Send + Sync {
@@ -44,6 +27,8 @@ pub trait Backend: Send + Sync {
fn header(&self, id: BlockId) -> Result<Option<block::Header>>;
/// Get block body. Returns `None` if block is not found.
fn body(&self, id: BlockId) -> Result<Option<block::Body>>;
/// Get block justification. Returns `None` if justification does not exist.
fn justification(&self, id: BlockId) -> Result<Option<primitives::bft::Justification>>;
/// Get blockchain info.
fn info(&self) -> Result<Info>;
/// Get block status.
+7 -1
View File
@@ -30,7 +30,7 @@ error_chain! {
}
/// Unknown block.
UnknownBlock(h: blockchain::BlockId) {
UnknownBlock(h: ::primitives::block::Id) {
description("unknown block"),
display("UnknownBlock: {}", h),
}
@@ -76,6 +76,12 @@ error_chain! {
description("authority value state error"),
display("Current state of blockchain has invalid authority value for index {}", i),
}
/// Bad justification for header.
BadJustification(h: ::primitives::block::Id) {
description("bad justification for header"),
display("bad justification for header: {}", h),
}
}
}
+13 -5
View File
@@ -22,8 +22,9 @@ use state_machine;
use error;
use backend;
use runtime_support::Hashable;
use primitives::block::{self, HeaderHash};
use blockchain::{self, BlockId, BlockStatus};
use primitives;
use primitives::block::{self, Id as BlockId, HeaderHash};
use blockchain::{self, BlockStatus};
use state_machine::backend::Backend as StateBackend;
fn header_hash(header: &block::Header) -> block::HeaderHash {
@@ -38,6 +39,7 @@ struct PendingBlock {
#[derive(PartialEq, Eq, Clone)]
struct Block {
header: block::Header,
justification: Option<primitives::bft::Justification>,
body: Option<block::Body>,
}
@@ -90,12 +92,13 @@ impl Blockchain {
}
}
fn insert(&self, hash: HeaderHash, header: block::Header, body: Option<block::Body>, is_new_best: bool) {
fn insert(&self, hash: HeaderHash, header: block::Header, justification: Option<primitives::bft::Justification>, body: Option<block::Body>, is_new_best: bool) {
let number = header.number;
let mut storage = self.storage.write();
storage.blocks.insert(hash, Block {
header: header,
body: body,
justification: justification,
});
storage.hashes.insert(number, hash);
if is_new_best {
@@ -132,6 +135,10 @@ impl blockchain::Backend for Blockchain {
Ok(self.id(id).and_then(|hash| self.storage.read().blocks.get(&hash).and_then(|b| b.body.clone())))
}
fn justification(&self, id: BlockId) -> error::Result<Option<primitives::bft::Justification>> {
Ok(self.id(id).and_then(|hash| self.storage.read().blocks.get(&hash).and_then(|b| b.justification.clone())))
}
fn info(&self) -> error::Result<blockchain::Info> {
let storage = self.storage.read();
Ok(blockchain::Info {
@@ -160,12 +167,13 @@ impl backend::BlockImportOperation for BlockImportOperation {
Ok(&self.pending_state)
}
fn set_block_data(&mut self, header: block::Header, body: Option<block::Body>, is_new_best: bool) -> error::Result<()> {
fn set_block_data(&mut self, header: block::Header, body: Option<block::Body>, justification: Option<primitives::bft::Justification>, is_new_best: bool) -> error::Result<()> {
assert!(self.pending_block.is_none(), "Only one block per operation is allowed");
self.pending_block = Some(PendingBlock {
block: Block {
header: header,
body: body,
justification: justification,
},
is_best: is_new_best,
});
@@ -220,7 +228,7 @@ impl backend::Backend for Backend {
if let Some(pending_block) = operation.pending_block {
let hash = header_hash(&pending_block.block.header);
self.states.write().insert(hash, operation.pending_state);
self.blockchain.insert(hash, pending_block.block.header, pending_block.block.body, pending_block.is_best);
self.blockchain.insert(hash, pending_block.block.header, pending_block.block.justification, pending_block.block.body, pending_block.is_best);
}
Ok(())
}
+1
View File
@@ -18,6 +18,7 @@
#![warn(missing_docs)]
extern crate substrate_bft as bft;
extern crate substrate_runtime_support as runtime_support;
extern crate substrate_runtime_io as runtime_io;
extern crate substrate_primitives as primitives;
+28 -2
View File
@@ -24,9 +24,18 @@ extern crate untrusted;
use ring::{rand, signature};
use primitives::hash::H512;
/// Alias to 520-bit hash when used in the context of a signature on the relay chain.
/// Alias to 512-bit hash when used in the context of a signature on the relay chain.
pub type Signature = H512;
/// A localized signature also contains sender information.
#[derive(PartialEq, Eq, Clone, Debug)]
pub struct LocalizedSignature {
/// The signer of the signature.
pub signer: Public,
/// The signature itself.
pub signature: Signature,
}
/// Verify a message without type checking the parameters' types for the right size.
pub fn verify(sig: &[u8], message: &[u8], public: &[u8]) -> bool {
let public_key = untrusted::Input::from(public);
@@ -40,7 +49,7 @@ pub fn verify(sig: &[u8], message: &[u8], public: &[u8]) -> bool {
}
/// A public key.
#[derive(PartialEq, Clone, Debug)]
#[derive(PartialEq, Eq, Clone, Debug)]
pub struct Public(pub [u8; 32]);
/// A key pair.
@@ -89,6 +98,12 @@ impl AsRef<[u8]> for Public {
}
}
impl Into<[u8; 32]> for Public {
fn into(self) -> [u8; 32] {
self.0
}
}
impl Pair {
/// Generate new secure (random) key pair.
pub fn new() -> Pair {
@@ -152,10 +167,21 @@ impl Verifiable for Signature {
}
}
impl Verifiable for LocalizedSignature {
fn verify(&self, message: &[u8], pubkey: &Public) -> bool {
pubkey == &self.signer && self.signature.verify(message, pubkey)
}
}
#[cfg(test)]
mod test {
use super::*;
fn _test_primitives_signature_and_local_the_same() {
fn takes_two<T>(_: T, _: T) { }
takes_two(Signature::default(), primitives::Signature::default())
}
#[test]
fn test_vector_should_work() {
let pair: Pair = Pair::from_seed(&hex!("9d61b19deffd5a60ba844af492ec2cc44449c5697b326919703bac031cae7f60"));
+1
View File
@@ -29,4 +29,5 @@ substrate-test-runtime = { path = "../test-runtime" }
substrate-executor = { path = "../../substrate/executor" }
substrate-keyring = { path = "../../substrate/keyring" }
substrate-codec = { path = "../../substrate/codec" }
substrate-bft = { path = "../bft" }
env_logger = "0.4"
@@ -205,6 +205,7 @@ mod test {
body: None,
message_queue: None,
receipt: None,
justification: None,
}).collect()
}
+16 -6
View File
@@ -16,14 +16,15 @@
//! Blockchain access trait
use client::{self, Client as PolkadotClient, ImportResult, ClientInfo, BlockStatus, BlockId};
use client::{self, Client as PolkadotClient, ImportResult, ClientInfo, BlockStatus};
use client::error::Error;
use state_machine;
use primitives::block;
use primitives::block::{self, Id as BlockId};
use primitives::bft::Justification;
pub trait Client : Send + Sync {
pub trait Client: Send + Sync {
/// Given a hash return a header
fn import(&self, header: block::Header, body: Option<block::Body>) -> Result<ImportResult, Error>;
fn import(&self, header: block::Header, justification: Justification, body: Option<block::Body>) -> Result<ImportResult, Error>;
/// Get blockchain info.
fn info(&self) -> Result<ClientInfo, Error>;
@@ -39,6 +40,9 @@ pub trait Client : Send + Sync {
/// Get block body.
fn body(&self, id: &BlockId) -> Result<Option<block::Body>, Error>;
/// Get block justification.
fn justification(&self, id: &BlockId) -> Result<Option<Justification>, Error>;
}
impl<B, E> Client for PolkadotClient<B, E> where
@@ -46,8 +50,10 @@ impl<B, E> Client for PolkadotClient<B, E> where
E: state_machine::CodeExecutor + Send + Sync + 'static,
Error: From<<<B as client::backend::Backend>::State as state_machine::backend::Backend>::Error>, {
fn import(&self, header: block::Header, body: Option<block::Body>) -> Result<ImportResult, Error> {
(self as &PolkadotClient<B, E>).import_block(header, body)
fn import(&self, header: block::Header, justification: Justification, body: Option<block::Body>) -> Result<ImportResult, Error> {
// TODO: defer justification check.
let justified_header = self.check_justification(header, justification.into())?;
(self as &PolkadotClient<B, E>).import_block(justified_header, body)
}
fn info(&self) -> Result<ClientInfo, Error> {
@@ -69,4 +75,8 @@ impl<B, E> Client for PolkadotClient<B, E> where
fn body(&self, id: &BlockId) -> Result<Option<block::Body>, Error> {
(self as &PolkadotClient<B, E>).body(id)
}
fn justification(&self, id: &BlockId) -> Result<Option<Justification>, Error> {
(self as &PolkadotClient<B, E>).justification(id)
}
}
+1
View File
@@ -40,6 +40,7 @@ extern crate serde_json;
#[cfg(test)] extern crate substrate_keyring as keyring;
#[cfg(test)] #[macro_use] extern crate substrate_executor as executor;
#[cfg(test)] extern crate substrate_codec as codec;
#[cfg(test)] extern crate substrate_bft as bft;
mod service;
mod sync;
@@ -19,6 +19,7 @@
use std::borrow::Borrow;
use primitives::AuthorityId;
use primitives::block::{Number as BlockNumber, HeaderHash, Header, Body};
use primitives::bft::Justification;
use service::Role as RoleFlags;
pub type RequestId = u64;
@@ -85,6 +86,8 @@ pub enum BlockAttribute {
Receipt,
/// Include block message queue.
MessageQueue,
/// Include a justification for the block.
Justification,
}
#[derive(Debug, PartialEq, Eq, Clone, Serialize, Deserialize)]
@@ -100,6 +103,8 @@ pub struct BlockData {
pub receipt: Option<Bytes>,
/// Block message queue if requested.
pub message_queue: Option<Bytes>,
/// Justification if requested.
pub justification: Option<Justification>,
}
#[serde(untagged)]
+5 -4
View File
@@ -17,10 +17,10 @@
use std::collections::{HashMap, HashSet, BTreeMap};
use std::{mem, cmp};
use std::sync::Arc;
use std::time;
use parking_lot::RwLock;
use serde_json;
use std::time;
use primitives::block::{HeaderHash, TransactionHash, Number as BlockNumber, Header};
use primitives::block::{HeaderHash, TransactionHash, Number as BlockNumber, Header, Id as BlockId};
use network::{PeerId, NodeId};
use message::{self, Message};
@@ -30,7 +30,6 @@ use config::ProtocolConfig;
use chain::Client;
use io::SyncIo;
use error;
use client::BlockId;
use super::header_hash;
const REQUEST_TIMEOUT_SEC: u64 = 15;
@@ -223,13 +222,14 @@ impl Protocol {
};
let max = cmp::min(request.max.unwrap_or(u32::max_value()), MAX_BLOCK_DATA_RESPONSE) as usize;
// TODO: receipts, etc.
let (mut get_header, mut get_body) = (false, false);
let (mut get_header, mut get_body, mut get_justification) = (false, false, false);
for a in request.fields {
match a {
message::BlockAttribute::Header => get_header = true,
message::BlockAttribute::Body => get_body = true,
message::BlockAttribute::Receipt => unimplemented!(),
message::BlockAttribute::MessageQueue => unimplemented!(),
message::BlockAttribute::Justification => get_justification = true,
}
}
while let Some(header) = self.chain.header(&id).unwrap_or(None) {
@@ -244,6 +244,7 @@ impl Protocol {
body: if get_body { self.chain.body(&BlockId::Hash(hash)).unwrap_or(None) } else { None },
receipt: None,
message_queue: None,
justification: if get_justification { self.chain.justification(&BlockId::Hash(hash)).unwrap_or(None) } else { None },
};
blocks.push(block_data);
match request.direction {
+54 -38
View File
@@ -18,8 +18,8 @@ use std::collections::HashMap;
use io::SyncIo;
use protocol::Protocol;
use network::PeerId;
use client::{ImportResult, BlockStatus, ClientInfo, BlockId};
use primitives::block::{HeaderHash, Number as BlockNumber, Header};
use client::{ImportResult, BlockStatus, ClientInfo};
use primitives::block::{HeaderHash, Number as BlockNumber, Header, Id as BlockId};
use blocks::{self, BlockCollection};
use message::{self, Message};
use super::header_hash;
@@ -80,7 +80,7 @@ impl ChainSync {
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],
required_block_attributes: vec![message::BlockAttribute::Header, message::BlockAttribute::Body, message::BlockAttribute::Justification],
}
}
@@ -215,41 +215,57 @@ impl ChainSync {
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;
match (block.header, block.justification) {
(Some(header), Some(justification)) => {
let number = header.number;
let hash = header_hash(&header);
let parent = header.parent_hash;
let result = protocol.chain().import(
header,
justification,
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;
}
}
},
(None, _) => {
debug!(target: "sync", "Header {} was not provided by {} ", block.hash, origin);
io.disable_peer(origin); //TODO: use persistent ID
return;
},
(_, None) => {
debug!(target: "sync", "Justification set for block {} was not provided by {} ", block.hash, origin);
io.disable_peer(origin); //TODO: use persistent ID
return;
}
}
}
@@ -398,7 +414,7 @@ impl ChainSync {
fn request_ancestry(io: &mut SyncIo, protocol: &Protocol, peer_id: PeerId, block: BlockNumber) {
let request = message::BlockRequest {
id: 0,
fields: vec![message::BlockAttribute::Header],
fields: vec![message::BlockAttribute::Header, message::BlockAttribute::Justification],
from: message::FromBlock::Number(block),
to: None,
direction: message::Direction::Ascending,
+31 -10
View File
@@ -19,8 +19,9 @@ mod sync;
use std::collections::{VecDeque, HashSet, HashMap};
use std::sync::Arc;
use parking_lot::RwLock;
use client::{self, BlockId, genesis};
use client::{self, genesis};
use client::block_builder::BlockBuilder;
use primitives::block::Id as BlockId;
use primitives;
use executor;
use io::SyncIo;
@@ -32,6 +33,7 @@ use runtime_support::Hashable;
use test_runtime;
use keyring::Keyring;
use codec::Slicable;
use bft;
native_executor_instance!(Executor, test_runtime::api::dispatch, include_bytes!("../../../test-runtime/wasm/target/wasm32-unknown-unknown/release/substrate_test_runtime.compact.wasm"));
@@ -100,7 +102,7 @@ pub struct TestPacket {
}
pub struct Peer {
chain: Arc<client::Client<client::in_mem::Backend, executor::NativeExecutor<Executor>>>,
client: Arc<client::Client<client::in_mem::Backend, executor::NativeExecutor<Executor>>>,
pub sync: Protocol,
pub queue: RwLock<VecDeque<TestPacket>>,
}
@@ -108,9 +110,9 @@ pub struct Peer {
impl Peer {
/// Called after blockchain has been populated to updated current state.
fn start(&self) {
// Update the sync state to the lates chain state.
let info = self.chain.info().expect("In-mem chain does not fail");
let header = self.chain.header(&BlockId::Hash(info.chain.best_hash)).unwrap().unwrap();
// Update the sync state to the latest chain state.
let info = self.client.info().expect("In-mem client does not fail");
let header = self.client.header(&BlockId::Hash(info.chain.best_hash)).unwrap().unwrap();
self.sync.on_block_imported(&header);
}
@@ -158,13 +160,32 @@ impl Peer {
fn flush(&self) {
}
fn justify(header: &primitives::block::Header) -> bft::UncheckedJustification {
let hash = header.hash();
let authorities = vec![ Keyring::Alice.into() ];
bft::UncheckedJustification {
digest: hash,
signatures: authorities.iter().map(|key| {
bft::sign_message(
bft::generic::Message::Commit(1, hash),
key,
header.parent_hash
).signature
}).collect(),
round_number: 1,
}
}
fn generate_blocks<F>(&self, count: usize, mut edit_block: F) where F: FnMut(&mut BlockBuilder<client::in_mem::Backend, executor::NativeExecutor<Executor>>) {
for _ in 0 .. count {
let mut builder = self.chain.new_block().unwrap();
let mut builder = self.client.new_block().unwrap();
edit_block(&mut builder);
let block = builder.bake().unwrap();
trace!("Generating {}, (#{})", primitives::block::HeaderHash::from(block.header.blake2_256()), block.header.number);
self.chain.import_block(block.header, Some(block.transactions)).unwrap();
let justification = Self::justify(&block.header);
let justified = self.client.check_justification(block.header, justification).unwrap();
self.client.import_block(justified, Some(block.transactions)).unwrap();
}
}
@@ -221,11 +242,11 @@ impl TestNet {
};
for _ in 0..n {
let chain = Arc::new(client::new_in_mem(Executor::new(), Self::prepare_genesis).unwrap());
let sync = Protocol::new(config.clone(), chain.clone()).unwrap();
let client = Arc::new(client::new_in_mem(Executor::new(), Self::prepare_genesis).unwrap());
let sync = Protocol::new(config.clone(), client.clone()).unwrap();
net.peers.push(Arc::new(Peer {
sync: sync,
chain: chain,
client: client,
queue: RwLock::new(VecDeque::new()),
}));
}
+8 -8
View File
@@ -25,7 +25,7 @@ fn sync_from_two_peers_works() {
net.peer(1).push_blocks(100, false);
net.peer(2).push_blocks(100, false);
net.sync();
assert!(net.peer(0).chain.backend().blockchain().equals_to(net.peer(1).chain.backend().blockchain()));
assert!(net.peer(0).client.backend().blockchain().equals_to(net.peer(1).client.backend().blockchain()));
let status = net.peer(0).sync.status();
assert_eq!(status.sync.state, SyncState::Idle);
}
@@ -39,7 +39,7 @@ fn sync_from_two_peers_with_ancestry_search_works() {
net.peer(2).push_blocks(100, false);
net.restart_peer(0);
net.sync();
assert!(net.peer(0).chain.backend().blockchain().canon_equals_to(net.peer(1).chain.backend().blockchain()));
assert!(net.peer(0).client.backend().blockchain().canon_equals_to(net.peer(1).client.backend().blockchain()));
}
#[test]
@@ -49,7 +49,7 @@ fn sync_long_chain_works() {
net.sync_steps(3);
assert_eq!(net.peer(0).sync.status().sync.state, SyncState::Downloading);
net.sync();
assert!(net.peer(0).chain.backend().blockchain().equals_to(net.peer(1).chain.backend().blockchain()));
assert!(net.peer(0).client.backend().blockchain().equals_to(net.peer(1).client.backend().blockchain()));
}
#[test]
@@ -59,7 +59,7 @@ fn sync_no_common_longer_chain_fails() {
net.peer(0).push_blocks(20, true);
net.peer(1).push_blocks(20, false);
net.sync();
assert!(!net.peer(0).chain.backend().blockchain().canon_equals_to(net.peer(1).chain.backend().blockchain()));
assert!(!net.peer(0).client.backend().blockchain().canon_equals_to(net.peer(1).client.backend().blockchain()));
}
#[test]
@@ -78,10 +78,10 @@ fn sync_after_fork_works() {
net.peer(2).push_blocks(1, false);
// peer 1 has the best chain
let peer1_chain = net.peer(1).chain.backend().blockchain().clone();
let peer1_chain = net.peer(1).client.backend().blockchain().clone();
net.sync();
assert!(net.peer(0).chain.backend().blockchain().canon_equals_to(&peer1_chain));
assert!(net.peer(1).chain.backend().blockchain().canon_equals_to(&peer1_chain));
assert!(net.peer(2).chain.backend().blockchain().canon_equals_to(&peer1_chain));
assert!(net.peer(0).client.backend().blockchain().canon_equals_to(&peer1_chain));
assert!(net.peer(1).client.backend().blockchain().canon_equals_to(&peer1_chain));
assert!(net.peer(2).client.backend().blockchain().canon_equals_to(&peer1_chain));
}
+32
View File
@@ -120,3 +120,35 @@ impl Slicable for Message {
})
}
}
/// Justification of a block.
#[derive(Clone, PartialEq, Eq)]
#[cfg_attr(feature = "std", derive(Debug, Serialize, Deserialize))]
pub struct Justification {
/// The round consensus was reached in.
pub round_number: u32,
/// The hash of the header justified.
pub hash: HeaderHash,
/// The signatures and signers of the hash.
pub signatures: Vec<(::AuthorityId, ::Signature)>
}
impl Slicable for Justification {
fn encode(&self) -> Vec<u8> {
let mut v = Vec::new();
self.round_number.using_encoded(|s| v.extend(s));
self.hash.using_encoded(|s| v.extend(s));
self.signatures.using_encoded(|s| v.extend(s));
v
}
fn decode<I: Input>(value: &mut I) -> Option<Self> {
Some(Justification {
round_number: try_opt!(Slicable::decode(value)),
hash: try_opt!(Slicable::decode(value)),
signatures: try_opt!(Slicable::decode(value)),
})
}
}
@@ -16,6 +16,7 @@
//! Block and header type definitions.
use rstd::fmt;
use rstd::vec::Vec;
#[cfg(feature = "std")]
use bytes;
@@ -177,6 +178,25 @@ impl Slicable for Header {
}
}
/// Block indentification.
#[derive(Clone, Copy)]
#[cfg_attr(feature = "std", derive(Debug))]
pub enum Id {
/// Identify by block header hash.
Hash(HeaderHash),
/// Identify by block number.
Number(Number),
}
impl fmt::Display for Id {
fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> {
match *self {
Id::Hash(h) => h.fmt(f),
Id::Number(n) => n.fmt(f),
}
}
}
#[cfg(test)]
mod tests {
use super::*;
@@ -98,3 +98,6 @@ pub type Hash = H256;
/// An identifier for an authority in the consensus algorithm. The same as ed25519::Public.
pub type AuthorityId = [u8; 32];
/// A 512-bit value interpreted as a signature.
pub type Signature = hash::H512;
+1 -1
View File
@@ -42,6 +42,6 @@ impl<B, E> ChainApi for client::Client<B, E> where
client::error::Error: From<<<B as client::backend::Backend>::State as state_machine::backend::Backend>::Error>,
{
fn header(&self, hash: block::HeaderHash) -> Result<Option<block::Header>> {
client::Client::header(self, &client::BlockId::Hash(hash)).chain_err(|| "Blockchain error")
client::Client::header(self, &block::Id::Hash(hash)).chain_err(|| "Blockchain error")
}
}
+3 -3
View File
@@ -21,7 +21,7 @@ mod error;
#[cfg(test)]
mod tests;
use client::{self, Client, BlockId};
use client::{self, Client};
use primitives::block;
use primitives::storage::{StorageKey, StorageData};
use state_machine;
@@ -47,10 +47,10 @@ impl<B, E> StateApi for Client<B, E> where
client::error::Error: From<<<B as client::backend::Backend>::State as state_machine::backend::Backend>::Error>,
{
fn storage(&self, key: StorageKey, block: block::HeaderHash) -> Result<StorageData> {
Ok(self.storage(&BlockId::Hash(block), &key)?)
Ok(self.storage(&block::Id::Hash(block), &key)?)
}
fn call(&self, method: String, data: Vec<u8>, block: block::HeaderHash) -> Result<Vec<u8>> {
Ok(self.call(&BlockId::Hash(block), &method, &data)?.return_data)
Ok(self.call(&block::Id::Hash(block), &method, &data)?.return_data)
}
}
@@ -17,6 +17,7 @@
pub use std::boxed;
pub use std::cell;
pub use std::cmp;
pub use std::fmt;
pub use std::iter;
pub use std::mem;
pub use std::ops;
@@ -33,3 +33,4 @@ pub use core::mem;
pub use core::ops;
pub use core::ptr;
pub use core::slice;
pub use core::fmt;