mirror of
https://github.com/pezkuwichain/pezkuwi-subxt.git
synced 2026-05-31 22:41:02 +00:00
Verify header justification during import (#76)
* reshuffle consensus libraries * polkadot-useful type definitions for statement table * begin BftService * primary selection logic * bft service implementation without I/O * extract out `BlockImport` trait * allow bft primitives to compile on wasm * Block builder (substrate) * take polkadot-consensus down to the core. * test for preemption * fix test build * Fix wasm build * Bulid on any block * Test for block builder. * Block import tests for client. * Tidy ups * clean up block builder instantiation * justification verification logic * JustifiedHeader and import * Propert block generation for tests * Fixed rpc tests
This commit is contained in:
committed by
GitHub
parent
c56859f331
commit
1d0ad42230
Generated
+2
-2
@@ -1400,12 +1400,10 @@ dependencies = [
|
|||||||
"error-chain 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
"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)",
|
"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)",
|
"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-codec 0.1.0",
|
||||||
"substrate-executor 0.1.0",
|
"substrate-executor 0.1.0",
|
||||||
"substrate-keyring 0.1.0",
|
"substrate-keyring 0.1.0",
|
||||||
"substrate-primitives 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-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)",
|
"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)",
|
"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)",
|
"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)",
|
"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-codec 0.1.0",
|
||||||
"substrate-executor 0.1.0",
|
"substrate-executor 0.1.0",
|
||||||
"substrate-keyring 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 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_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)",
|
"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-client 0.1.0",
|
||||||
"substrate-codec 0.1.0",
|
"substrate-codec 0.1.0",
|
||||||
"substrate-executor 0.1.0",
|
"substrate-executor 0.1.0",
|
||||||
|
|||||||
@@ -28,12 +28,12 @@ extern crate substrate_state_machine as state_machine;
|
|||||||
extern crate error_chain;
|
extern crate error_chain;
|
||||||
|
|
||||||
use client::backend::Backend;
|
use client::backend::Backend;
|
||||||
use client::blockchain::BlockId;
|
|
||||||
use client::Client;
|
use client::Client;
|
||||||
use polkadot_runtime::runtime;
|
use polkadot_runtime::runtime;
|
||||||
use polkadot_executor::Executor as LocalDispatch;
|
use polkadot_executor::Executor as LocalDispatch;
|
||||||
use substrate_executor::{NativeExecutionDispatch, NativeExecutor};
|
use substrate_executor::{NativeExecutionDispatch, NativeExecutor};
|
||||||
use primitives::{AccountId, SessionKey};
|
use primitives::{AccountId, SessionKey};
|
||||||
|
use primitives::block::Id as BlockId;
|
||||||
use primitives::parachain::DutyRoster;
|
use primitives::parachain::DutyRoster;
|
||||||
|
|
||||||
error_chain! {
|
error_chain! {
|
||||||
|
|||||||
@@ -23,6 +23,8 @@ use rstd::vec::Vec;
|
|||||||
use codec::{Input, Slicable};
|
use codec::{Input, Slicable};
|
||||||
use transaction::UncheckedTransaction;
|
use transaction::UncheckedTransaction;
|
||||||
|
|
||||||
|
pub use primitives::block::Id;
|
||||||
|
|
||||||
/// Used to refer to a block number.
|
/// Used to refer to a block number.
|
||||||
pub type Number = u64;
|
pub type Number = u64;
|
||||||
|
|
||||||
|
|||||||
@@ -5,10 +5,8 @@ authors = ["Parity Technologies <admin@parity.io>"]
|
|||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
futures = "0.1.17"
|
futures = "0.1.17"
|
||||||
substrate-client = { path = "../client" }
|
|
||||||
substrate-codec = { path = "../codec" }
|
substrate-codec = { path = "../codec" }
|
||||||
substrate-primitives = { path = "../primitives" }
|
substrate-primitives = { path = "../primitives" }
|
||||||
substrate-state-machine = { path = "../state-machine" }
|
|
||||||
ed25519 = { path = "../ed25519" }
|
ed25519 = { path = "../ed25519" }
|
||||||
tokio-timer = "0.1.2"
|
tokio-timer = "0.1.2"
|
||||||
parking_lot = "0.4"
|
parking_lot = "0.4"
|
||||||
|
|||||||
@@ -19,7 +19,7 @@
|
|||||||
error_chain! {
|
error_chain! {
|
||||||
errors {
|
errors {
|
||||||
/// Missing state at block with given Id.
|
/// Missing state at block with given Id.
|
||||||
StateUnavailable(b: ::client::BlockId) {
|
StateUnavailable(b: ::primitives::block::Id) {
|
||||||
description("State missing at given block."),
|
description("State missing at given block."),
|
||||||
display("State unavailable at block {:?}", b),
|
display("State unavailable at block {:?}", b),
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -83,6 +83,13 @@ impl<D, S> UncheckedJustification<D, S> {
|
|||||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||||
pub struct Justification<D,S>(UncheckedJustification<D,S>);
|
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> {
|
impl<D, S> ::std::ops::Deref for Justification<D, S> {
|
||||||
type Target = UncheckedJustification<D, S>;
|
type Target = UncheckedJustification<D, S>;
|
||||||
|
|
||||||
|
|||||||
@@ -20,9 +20,7 @@ pub mod error;
|
|||||||
pub mod generic;
|
pub mod generic;
|
||||||
|
|
||||||
extern crate substrate_codec as codec;
|
extern crate substrate_codec as codec;
|
||||||
extern crate substrate_client as client;
|
|
||||||
extern crate substrate_primitives as primitives;
|
extern crate substrate_primitives as primitives;
|
||||||
extern crate substrate_state_machine as state_machine;
|
|
||||||
extern crate ed25519;
|
extern crate ed25519;
|
||||||
extern crate tokio_timer;
|
extern crate tokio_timer;
|
||||||
extern crate parking_lot;
|
extern crate parking_lot;
|
||||||
@@ -37,13 +35,11 @@ use std::collections::HashMap;
|
|||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
use std::sync::atomic::{AtomicBool, Ordering};
|
use std::sync::atomic::{AtomicBool, Ordering};
|
||||||
|
|
||||||
use client::{BlockId, Client};
|
|
||||||
use client::backend::Backend;
|
|
||||||
use codec::Slicable;
|
use codec::Slicable;
|
||||||
use ed25519::Signature;
|
use ed25519::LocalizedSignature;
|
||||||
use primitives::block::{Block, Header, HeaderHash};
|
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 primitives::AuthorityId;
|
||||||
use state_machine::CodeExecutor;
|
|
||||||
|
|
||||||
use futures::{stream, task, Async, Sink, Future, IntoFuture};
|
use futures::{stream, task, Async, Sink, Future, IntoFuture};
|
||||||
use futures::future::Executor;
|
use futures::future::Executor;
|
||||||
@@ -63,20 +59,46 @@ pub type LocalizedMessage = generic::LocalizedMessage<
|
|||||||
Block,
|
Block,
|
||||||
HeaderHash,
|
HeaderHash,
|
||||||
AuthorityId,
|
AuthorityId,
|
||||||
Signature
|
LocalizedSignature
|
||||||
>;
|
>;
|
||||||
|
|
||||||
/// Justification of some hash.
|
/// Justification of some hash.
|
||||||
pub type Justification = generic::Justification<HeaderHash, Signature>;
|
pub type Justification = generic::Justification<HeaderHash, LocalizedSignature>;
|
||||||
|
|
||||||
/// Justification of a prepare message.
|
/// 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
|
/// 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.
|
/// 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.
|
/// Logic for a proposer.
|
||||||
///
|
///
|
||||||
@@ -108,30 +130,6 @@ pub trait Authorities {
|
|||||||
fn authorities(&self, at: &BlockId) -> Result<Vec<AuthorityId>, Error>;
|
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.
|
/// Instance of BFT agreement.
|
||||||
struct BftInstance<P> {
|
struct BftInstance<P> {
|
||||||
key: Arc<ed25519::Pair>,
|
key: Arc<ed25519::Pair>,
|
||||||
@@ -145,7 +143,7 @@ struct BftInstance<P> {
|
|||||||
impl<P: Proposer> generic::Context for BftInstance<P> {
|
impl<P: Proposer> generic::Context for BftInstance<P> {
|
||||||
type AuthorityId = AuthorityId;
|
type AuthorityId = AuthorityId;
|
||||||
type Digest = HeaderHash;
|
type Digest = HeaderHash;
|
||||||
type Signature = Signature;
|
type Signature = LocalizedSignature;
|
||||||
type Candidate = Block;
|
type Candidate = Block;
|
||||||
type RoundTimeout = Box<Future<Item=(),Error=Error> + Send>;
|
type RoundTimeout = Box<Future<Item=(),Error=Error> + Send>;
|
||||||
type CreateProposal = <P::CreateProposal as IntoFuture>::Future;
|
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 {
|
fn sign_local(&self, message: Message) -> LocalizedMessage {
|
||||||
use primitives::bft::{Message as PrimitiveMessage, Action as PrimitiveAction};
|
sign_message(message, &*self.key, self.parent_hash.clone())
|
||||||
|
|
||||||
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
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn round_proposer(&self, round: usize) -> AuthorityId {
|
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.
|
// TODO: check key is one of the authorities.
|
||||||
let authorities = self.client.authorities(&BlockId::Hash(hash))?;
|
let authorities = self.client.authorities(&BlockId::Hash(hash))?;
|
||||||
let n = authorities.len();
|
let n = authorities.len();
|
||||||
let max_faulty = n.saturating_sub(1) / 3;
|
let max_faulty = max_faulty_of(n);
|
||||||
|
|
||||||
let bft_instance = BftInstance {
|
let bft_instance = BftInstance {
|
||||||
proposer,
|
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)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
@@ -470,4 +525,76 @@ mod tests {
|
|||||||
|
|
||||||
core.turn(Some(::std::time::Duration::from_millis(100)));
|
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());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ parking_lot = "0.4"
|
|||||||
triehash = "0.1"
|
triehash = "0.1"
|
||||||
hex-literal = "0.1"
|
hex-literal = "0.1"
|
||||||
ed25519 = { path = "../ed25519" }
|
ed25519 = { path = "../ed25519" }
|
||||||
|
substrate-bft = { path = "../bft" }
|
||||||
substrate-codec = { path = "../codec" }
|
substrate-codec = { path = "../codec" }
|
||||||
substrate-executor = { path = "../executor" }
|
substrate-executor = { path = "../executor" }
|
||||||
substrate-primitives = { path = "../primitives" }
|
substrate-primitives = { path = "../primitives" }
|
||||||
|
|||||||
@@ -18,8 +18,8 @@
|
|||||||
|
|
||||||
use state_machine;
|
use state_machine;
|
||||||
use error;
|
use error;
|
||||||
use primitives::block;
|
use primitives::block::{self, Id as BlockId};
|
||||||
use blockchain::{self, BlockId};
|
use primitives;
|
||||||
|
|
||||||
/// Block insertion operation. Keeps hold if the inserted block state and data.
|
/// Block insertion operation. Keeps hold if the inserted block state and data.
|
||||||
pub trait BlockImportOperation {
|
pub trait BlockImportOperation {
|
||||||
@@ -29,7 +29,7 @@ pub trait BlockImportOperation {
|
|||||||
/// Returns pending state.
|
/// Returns pending state.
|
||||||
fn state(&self) -> error::Result<&Self::State>;
|
fn state(&self) -> error::Result<&Self::State>;
|
||||||
/// Append block data to the transaction.
|
/// 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.
|
/// Inject storage data into the database.
|
||||||
fn set_storage<I: Iterator<Item=(Vec<u8>, Vec<u8>)>>(&mut self, iter: I) -> error::Result<()>;
|
fn set_storage<I: Iterator<Item=(Vec<u8>, Vec<u8>)>>(&mut self, iter: I) -> error::Result<()>;
|
||||||
/// Inject storage data into the database.
|
/// Inject storage data into the database.
|
||||||
@@ -41,7 +41,7 @@ pub trait Backend {
|
|||||||
/// Associated block insertion operation type.
|
/// Associated block insertion operation type.
|
||||||
type BlockImportOperation: BlockImportOperation;
|
type BlockImportOperation: BlockImportOperation;
|
||||||
/// Associated blockchain backend type.
|
/// Associated blockchain backend type.
|
||||||
type Blockchain: blockchain::Backend;
|
type Blockchain: ::blockchain::Backend;
|
||||||
/// Associated state backend type.
|
/// Associated state backend type.
|
||||||
type State: state_machine::backend::Backend;
|
type State: state_machine::backend::Backend;
|
||||||
|
|
||||||
|
|||||||
@@ -20,8 +20,8 @@ use std::vec::Vec;
|
|||||||
use codec::{Joiner, Slicable};
|
use codec::{Joiner, Slicable};
|
||||||
use state_machine::{self, CodeExecutor};
|
use state_machine::{self, CodeExecutor};
|
||||||
use primitives::{Header, Block};
|
use primitives::{Header, Block};
|
||||||
use primitives::block::Transaction;
|
use primitives::block::{Id as BlockId, Transaction};
|
||||||
use {backend, error, BlockId, Client};
|
use {backend, error, Client};
|
||||||
use triehash::ordered_trie_root;
|
use triehash::ordered_trie_root;
|
||||||
|
|
||||||
/// Utility for building new (valid) blocks from a stream of transactions.
|
/// Utility for building new (valid) blocks from a stream of transactions.
|
||||||
|
|||||||
@@ -16,27 +16,10 @@
|
|||||||
|
|
||||||
//! Polkadot blockchain trait
|
//! Polkadot blockchain trait
|
||||||
|
|
||||||
use std::fmt::{Display, Formatter, Error as FmtError};
|
use primitives::block::{self, Id as BlockId};
|
||||||
use primitives::block;
|
use primitives;
|
||||||
use error::Result;
|
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.
|
/// Blockchain database backend. Does not perform any validation.
|
||||||
pub trait Backend: Send + Sync {
|
pub trait Backend: Send + Sync {
|
||||||
@@ -44,6 +27,8 @@ pub trait Backend: Send + Sync {
|
|||||||
fn header(&self, id: BlockId) -> Result<Option<block::Header>>;
|
fn header(&self, id: BlockId) -> Result<Option<block::Header>>;
|
||||||
/// Get block body. Returns `None` if block is not found.
|
/// Get block body. Returns `None` if block is not found.
|
||||||
fn body(&self, id: BlockId) -> Result<Option<block::Body>>;
|
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.
|
/// Get blockchain info.
|
||||||
fn info(&self) -> Result<Info>;
|
fn info(&self) -> Result<Info>;
|
||||||
/// Get block status.
|
/// Get block status.
|
||||||
|
|||||||
@@ -18,7 +18,6 @@
|
|||||||
|
|
||||||
use std;
|
use std;
|
||||||
use state_machine;
|
use state_machine;
|
||||||
use blockchain;
|
|
||||||
|
|
||||||
error_chain! {
|
error_chain! {
|
||||||
errors {
|
errors {
|
||||||
@@ -29,7 +28,7 @@ error_chain! {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Unknown block.
|
/// Unknown block.
|
||||||
UnknownBlock(h: blockchain::BlockId) {
|
UnknownBlock(h: ::primitives::block::Id) {
|
||||||
description("unknown block"),
|
description("unknown block"),
|
||||||
display("UnknownBlock: {}", h),
|
display("UnknownBlock: {}", h),
|
||||||
}
|
}
|
||||||
@@ -46,6 +45,12 @@ error_chain! {
|
|||||||
display("Blockchain: {}", e),
|
display("Blockchain: {}", e),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Bad justification for header.
|
||||||
|
BadJustification(h: ::primitives::block::Id) {
|
||||||
|
description("bad justification for header"),
|
||||||
|
display("bad justification for header: {}", h),
|
||||||
|
}
|
||||||
|
|
||||||
/// Invalid state data.
|
/// Invalid state data.
|
||||||
AuthLen {
|
AuthLen {
|
||||||
description("authority count state error"),
|
description("authority count state error"),
|
||||||
|
|||||||
@@ -22,8 +22,9 @@ use state_machine;
|
|||||||
use error;
|
use error;
|
||||||
use backend;
|
use backend;
|
||||||
use runtime_support::Hashable;
|
use runtime_support::Hashable;
|
||||||
use primitives::block::{self, HeaderHash};
|
use primitives;
|
||||||
use blockchain::{self, BlockId, BlockStatus};
|
use primitives::block::{self, Id as BlockId, HeaderHash};
|
||||||
|
use blockchain::{self, BlockStatus};
|
||||||
use state_machine::backend::Backend as StateBackend;
|
use state_machine::backend::Backend as StateBackend;
|
||||||
|
|
||||||
fn header_hash(header: &block::Header) -> block::HeaderHash {
|
fn header_hash(header: &block::Header) -> block::HeaderHash {
|
||||||
@@ -38,6 +39,7 @@ struct PendingBlock {
|
|||||||
#[derive(PartialEq, Eq, Clone)]
|
#[derive(PartialEq, Eq, Clone)]
|
||||||
struct Block {
|
struct Block {
|
||||||
header: block::Header,
|
header: block::Header,
|
||||||
|
justification: Option<primitives::bft::Justification>,
|
||||||
body: Option<block::Body>,
|
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 number = header.number;
|
||||||
let mut storage = self.storage.write();
|
let mut storage = self.storage.write();
|
||||||
storage.blocks.insert(hash, Block {
|
storage.blocks.insert(hash, Block {
|
||||||
header: header,
|
header: header,
|
||||||
body: body,
|
body: body,
|
||||||
|
justification: justification,
|
||||||
});
|
});
|
||||||
storage.hashes.insert(number, hash);
|
storage.hashes.insert(number, hash);
|
||||||
if is_new_best {
|
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())))
|
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> {
|
fn info(&self) -> error::Result<blockchain::Info> {
|
||||||
let storage = self.storage.read();
|
let storage = self.storage.read();
|
||||||
Ok(blockchain::Info {
|
Ok(blockchain::Info {
|
||||||
@@ -160,12 +167,13 @@ impl backend::BlockImportOperation for BlockImportOperation {
|
|||||||
Ok(&self.pending_state)
|
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");
|
assert!(self.pending_block.is_none(), "Only one block per operation is allowed");
|
||||||
self.pending_block = Some(PendingBlock {
|
self.pending_block = Some(PendingBlock {
|
||||||
block: Block {
|
block: Block {
|
||||||
header: header,
|
header: header,
|
||||||
body: body,
|
body: body,
|
||||||
|
justification: justification,
|
||||||
},
|
},
|
||||||
is_best: is_new_best,
|
is_best: is_new_best,
|
||||||
});
|
});
|
||||||
@@ -220,7 +228,7 @@ impl backend::Backend for Backend {
|
|||||||
if let Some(pending_block) = operation.pending_block {
|
if let Some(pending_block) = operation.pending_block {
|
||||||
let hash = header_hash(&pending_block.block.header);
|
let hash = header_hash(&pending_block.block.header);
|
||||||
self.states.write().insert(hash, operation.pending_state);
|
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(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -18,6 +18,7 @@
|
|||||||
|
|
||||||
#![warn(missing_docs)]
|
#![warn(missing_docs)]
|
||||||
|
|
||||||
|
extern crate substrate_bft as bft;
|
||||||
extern crate substrate_runtime_support as runtime_support;
|
extern crate substrate_runtime_support as runtime_support;
|
||||||
extern crate substrate_runtime_io as runtime_io;
|
extern crate substrate_runtime_io as runtime_io;
|
||||||
extern crate substrate_primitives as primitives;
|
extern crate substrate_primitives as primitives;
|
||||||
@@ -42,9 +43,9 @@ pub mod genesis;
|
|||||||
pub mod block_builder;
|
pub mod block_builder;
|
||||||
|
|
||||||
pub use blockchain::Info as ChainInfo;
|
pub use blockchain::Info as ChainInfo;
|
||||||
pub use blockchain::BlockId;
|
|
||||||
|
|
||||||
use primitives::{block, AuthorityId};
|
use primitives::{block, AuthorityId};
|
||||||
|
use primitives::block::Id as BlockId;
|
||||||
use primitives::storage::{StorageKey, StorageData};
|
use primitives::storage::{StorageKey, StorageData};
|
||||||
use codec::{KeyedVec, Slicable};
|
use codec::{KeyedVec, Slicable};
|
||||||
|
|
||||||
@@ -109,6 +110,20 @@ pub enum BlockStatus {
|
|||||||
Unknown,
|
Unknown,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// A header paired with a justification which has already been checked.
|
||||||
|
#[derive(Debug, PartialEq, Eq, Clone)]
|
||||||
|
pub struct JustifiedHeader {
|
||||||
|
header: block::Header,
|
||||||
|
justification: bft::Justification,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl JustifiedHeader {
|
||||||
|
/// Deconstruct the justified header into parts.
|
||||||
|
pub fn into_inner(self) -> (block::Header, bft::Justification) {
|
||||||
|
(self.header, self.justification)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Create an instance of in-memory client.
|
/// Create an instance of in-memory client.
|
||||||
pub fn new_in_mem<E, F>(
|
pub fn new_in_mem<E, F>(
|
||||||
executor: E,
|
executor: E,
|
||||||
@@ -140,7 +155,7 @@ impl<B, E> Client<B, E> where
|
|||||||
let (genesis_header, genesis_store) = build_genesis();
|
let (genesis_header, genesis_store) = build_genesis();
|
||||||
let mut op = backend.begin_operation(BlockId::Hash(block::HeaderHash::default()))?;
|
let mut op = backend.begin_operation(BlockId::Hash(block::HeaderHash::default()))?;
|
||||||
op.reset_storage(genesis_store.into_iter())?;
|
op.reset_storage(genesis_store.into_iter())?;
|
||||||
op.set_block_data(genesis_header, Some(vec![]), true)?;
|
op.set_block_data(genesis_header, Some(vec![]), None, true)?;
|
||||||
backend.commit_operation(op)?;
|
backend.commit_operation(op)?;
|
||||||
}
|
}
|
||||||
Ok(Client {
|
Ok(Client {
|
||||||
@@ -229,10 +244,31 @@ impl<B, E> Client<B, E> where
|
|||||||
block_builder::BlockBuilder::at_block(parent, &self)
|
block_builder::BlockBuilder::at_block(parent, &self)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Check a header's justification.
|
||||||
|
pub fn check_justification(
|
||||||
|
&self,
|
||||||
|
header: block::Header,
|
||||||
|
justification: bft::UncheckedJustification,
|
||||||
|
) -> error::Result<JustifiedHeader> {
|
||||||
|
let authorities = self.authorities_at(&BlockId::Hash(header.parent_hash))?;
|
||||||
|
let just = bft::check_justification(&authorities[..], header.parent_hash, justification)
|
||||||
|
.map_err(|_| error::ErrorKind::BadJustification(BlockId::Hash(header.hash())))?;
|
||||||
|
Ok(JustifiedHeader {
|
||||||
|
header,
|
||||||
|
justification: just,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
/// Queue a block for import.
|
/// Queue a block for import.
|
||||||
pub fn import_block(&self, header: block::Header, body: Option<block::Body>) -> error::Result<ImportResult> {
|
pub fn import_block(
|
||||||
|
&self,
|
||||||
|
header: JustifiedHeader,
|
||||||
|
body: Option<block::Body>,
|
||||||
|
) -> error::Result<ImportResult> {
|
||||||
// TODO: import lock
|
// TODO: import lock
|
||||||
// TODO: validate block
|
// TODO: validate block
|
||||||
|
// TODO: import justification.
|
||||||
|
let (header, justification) = header.into_inner();
|
||||||
match self.backend.blockchain().status(BlockId::Hash(header.parent_hash))? {
|
match self.backend.blockchain().status(BlockId::Hash(header.parent_hash))? {
|
||||||
blockchain::BlockStatus::InChain => (),
|
blockchain::BlockStatus::InChain => (),
|
||||||
blockchain::BlockStatus::Unknown => return Ok(ImportResult::UnknownParent),
|
blockchain::BlockStatus::Unknown => return Ok(ImportResult::UnknownParent),
|
||||||
@@ -251,7 +287,7 @@ impl<B, E> Client<B, E> where
|
|||||||
|
|
||||||
let is_new_best = header.number == self.backend.blockchain().info()?.best_number + 1;
|
let is_new_best = header.number == self.backend.blockchain().info()?.best_number + 1;
|
||||||
trace!("Imported {}, (#{}), best={}", block::HeaderHash::from(header.blake2_256()), header.number, is_new_best);
|
trace!("Imported {}, (#{}), best={}", block::HeaderHash::from(header.blake2_256()), header.number, is_new_best);
|
||||||
transaction.set_block_data(header, body, is_new_best)?;
|
transaction.set_block_data(header, body, Some(justification.uncheck().into()), is_new_best)?;
|
||||||
transaction.set_storage(overlay.drain())?;
|
transaction.set_storage(overlay.drain())?;
|
||||||
self.backend.commit_operation(transaction)?;
|
self.backend.commit_operation(transaction)?;
|
||||||
Ok(ImportResult::Queued)
|
Ok(ImportResult::Queued)
|
||||||
@@ -306,6 +342,38 @@ impl<B, E> Client<B, E> where
|
|||||||
pub fn body(&self, id: &BlockId) -> error::Result<Option<block::Body>> {
|
pub fn body(&self, id: &BlockId) -> error::Result<Option<block::Body>> {
|
||||||
self.backend.blockchain().body(*id)
|
self.backend.blockchain().body(*id)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Get block justification set by id.
|
||||||
|
pub fn justification(&self, id: &BlockId) -> error::Result<Option<primitives::bft::Justification>> {
|
||||||
|
self.backend.blockchain().justification(*id)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<B, E> bft::BlockImport for Client<B, E>
|
||||||
|
where
|
||||||
|
B: backend::Backend,
|
||||||
|
E: state_machine::CodeExecutor,
|
||||||
|
error::Error: From<<B::State as state_machine::backend::Backend>::Error>
|
||||||
|
{
|
||||||
|
fn import_block(&self, block: block::Block, justification: bft::Justification) {
|
||||||
|
let justified_header = JustifiedHeader {
|
||||||
|
header: block.header,
|
||||||
|
justification,
|
||||||
|
};
|
||||||
|
|
||||||
|
let _ = self.import_block(justified_header, Some(block.transactions));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<B, E> bft::Authorities for Client<B, E>
|
||||||
|
where
|
||||||
|
B: backend::Backend,
|
||||||
|
E: state_machine::CodeExecutor,
|
||||||
|
error::Error: From<<B::State as state_machine::backend::Backend>::Error>
|
||||||
|
{
|
||||||
|
fn authorities(&self, at: &BlockId) -> Result<Vec<AuthorityId>, bft::Error> {
|
||||||
|
self.authorities_at(at).map_err(|_| bft::ErrorKind::StateUnavailable(*at).into())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
@@ -335,6 +403,31 @@ mod tests {
|
|||||||
(primitives::block::Header::decode(&mut block.header.encode().as_ref()).expect("to_vec() always gives a valid serialisation; qed"), storage.into_iter().collect())
|
(primitives::block::Header::decode(&mut block.header.encode().as_ref()).expect("to_vec() always gives a valid serialisation; qed"), storage.into_iter().collect())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// since we are in the client module we can create falsely justified
|
||||||
|
// headers.
|
||||||
|
// TODO: remove this in favor of custom verification pipelines for the
|
||||||
|
// client
|
||||||
|
fn justify(header: &block::Header) -> bft::UncheckedJustification {
|
||||||
|
let hash = header.hash();
|
||||||
|
let authorities = vec![
|
||||||
|
Keyring::Alice.into(),
|
||||||
|
Keyring::Bob.into(),
|
||||||
|
Keyring::Charlie.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,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn client_initialises_from_genesis_ok() {
|
fn client_initialises_from_genesis_ok() {
|
||||||
let client = new_in_mem(Executor::new(), prepare_genesis).unwrap();
|
let client = new_in_mem(Executor::new(), prepare_genesis).unwrap();
|
||||||
@@ -347,11 +440,7 @@ mod tests {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn authorities_call_works() {
|
fn authorities_call_works() {
|
||||||
let genesis_config = GenesisConfig::new_simple(vec![
|
let genesis_config = genesis_config();
|
||||||
Keyring::Alice.to_raw_public(),
|
|
||||||
Keyring::Bob.to_raw_public(),
|
|
||||||
Keyring::Charlie.to_raw_public()
|
|
||||||
], 1000);
|
|
||||||
|
|
||||||
let prepare_genesis = || {
|
let prepare_genesis = || {
|
||||||
let mut storage = genesis_config.genesis_map();
|
let mut storage = genesis_config.genesis_map();
|
||||||
@@ -371,11 +460,7 @@ mod tests {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn block_builder_works_with_no_transactions() {
|
fn block_builder_works_with_no_transactions() {
|
||||||
let genesis_config = GenesisConfig::new_simple(vec![
|
let genesis_config = genesis_config();
|
||||||
Keyring::Alice.to_raw_public(),
|
|
||||||
Keyring::Bob.to_raw_public(),
|
|
||||||
Keyring::Charlie.to_raw_public()
|
|
||||||
], 1000);
|
|
||||||
|
|
||||||
let prepare_genesis = || {
|
let prepare_genesis = || {
|
||||||
let mut storage = genesis_config.genesis_map();
|
let mut storage = genesis_config.genesis_map();
|
||||||
@@ -388,7 +473,9 @@ mod tests {
|
|||||||
let builder = client.new_block().unwrap();
|
let builder = client.new_block().unwrap();
|
||||||
let block = builder.bake().unwrap();
|
let block = builder.bake().unwrap();
|
||||||
|
|
||||||
client.import_block(block.header, Some(block.transactions)).unwrap();
|
let justification = justify(&block.header);
|
||||||
|
let justified = client.check_justification(block.header, justification).unwrap();
|
||||||
|
client.import_block(justified, Some(block.transactions)).unwrap();
|
||||||
|
|
||||||
assert_eq!(client.info().unwrap().chain.best_number, 1);
|
assert_eq!(client.info().unwrap().chain.best_number, 1);
|
||||||
assert_eq!(client.using_environment(|| test_runtime::system::latest_block_hash()).unwrap(), client.block_hash(1).unwrap().unwrap());
|
assert_eq!(client.using_environment(|| test_runtime::system::latest_block_hash()).unwrap(), client.block_hash(1).unwrap().unwrap());
|
||||||
@@ -406,11 +493,7 @@ mod tests {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn block_builder_works_with_transactions() {
|
fn block_builder_works_with_transactions() {
|
||||||
let genesis_config = GenesisConfig::new_simple(vec![
|
let genesis_config = genesis_config();
|
||||||
Keyring::Alice.to_raw_public(),
|
|
||||||
Keyring::Bob.to_raw_public(),
|
|
||||||
Keyring::Charlie.to_raw_public()
|
|
||||||
], 1000);
|
|
||||||
|
|
||||||
let prepare_genesis = || {
|
let prepare_genesis = || {
|
||||||
let mut storage = genesis_config.genesis_map();
|
let mut storage = genesis_config.genesis_map();
|
||||||
@@ -429,7 +512,10 @@ mod tests {
|
|||||||
nonce: 0
|
nonce: 0
|
||||||
}.signed()).unwrap();
|
}.signed()).unwrap();
|
||||||
let block = builder.bake().unwrap();
|
let block = builder.bake().unwrap();
|
||||||
client.import_block(block.header, Some(block.transactions)).unwrap();
|
|
||||||
|
let justification = justify(&block.header);
|
||||||
|
let justified = client.check_justification(block.header, justification).unwrap();
|
||||||
|
client.import_block(justified, Some(block.transactions)).unwrap();
|
||||||
|
|
||||||
assert_eq!(client.info().unwrap().chain.best_number, 1);
|
assert_eq!(client.info().unwrap().chain.best_number, 1);
|
||||||
assert!(client.state_at(&BlockId::Number(1)).unwrap() != client.state_at(&BlockId::Number(0)).unwrap());
|
assert!(client.state_at(&BlockId::Number(1)).unwrap() != client.state_at(&BlockId::Number(0)).unwrap());
|
||||||
|
|||||||
@@ -24,9 +24,18 @@ extern crate untrusted;
|
|||||||
use ring::{rand, signature};
|
use ring::{rand, signature};
|
||||||
use primitives::hash::H512;
|
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;
|
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.
|
/// Verify a message without type checking the parameters' types for the right size.
|
||||||
pub fn verify(sig: &[u8], message: &[u8], public: &[u8]) -> bool {
|
pub fn verify(sig: &[u8], message: &[u8], public: &[u8]) -> bool {
|
||||||
let public_key = untrusted::Input::from(public);
|
let public_key = untrusted::Input::from(public);
|
||||||
@@ -40,7 +49,7 @@ pub fn verify(sig: &[u8], message: &[u8], public: &[u8]) -> bool {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// A public key.
|
/// A public key.
|
||||||
#[derive(PartialEq, Clone, Debug)]
|
#[derive(PartialEq, Eq, Clone, Debug)]
|
||||||
pub struct Public(pub [u8; 32]);
|
pub struct Public(pub [u8; 32]);
|
||||||
|
|
||||||
/// A key pair.
|
/// 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 {
|
impl Pair {
|
||||||
/// Generate new secure (random) key pair.
|
/// Generate new secure (random) key pair.
|
||||||
pub fn new() -> 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)]
|
#[cfg(test)]
|
||||||
mod test {
|
mod test {
|
||||||
use super::*;
|
use super::*;
|
||||||
|
|
||||||
|
fn _test_primitives_signature_and_local_the_same() {
|
||||||
|
fn takes_two<T>(_: T, _: T) { }
|
||||||
|
takes_two(Signature::default(), primitives::Signature::default())
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_vector_should_work() {
|
fn test_vector_should_work() {
|
||||||
let pair: Pair = Pair::from_seed(&hex!("9d61b19deffd5a60ba844af492ec2cc44449c5697b326919703bac031cae7f60"));
|
let pair: Pair = Pair::from_seed(&hex!("9d61b19deffd5a60ba844af492ec2cc44449c5697b326919703bac031cae7f60"));
|
||||||
|
|||||||
@@ -29,4 +29,5 @@ substrate-test-runtime = { path = "../test-runtime" }
|
|||||||
substrate-executor = { path = "../../substrate/executor" }
|
substrate-executor = { path = "../../substrate/executor" }
|
||||||
substrate-keyring = { path = "../../substrate/keyring" }
|
substrate-keyring = { path = "../../substrate/keyring" }
|
||||||
substrate-codec = { path = "../../substrate/codec" }
|
substrate-codec = { path = "../../substrate/codec" }
|
||||||
|
substrate-bft = { path = "../bft" }
|
||||||
env_logger = "0.4"
|
env_logger = "0.4"
|
||||||
|
|||||||
@@ -205,6 +205,7 @@ mod test {
|
|||||||
body: None,
|
body: None,
|
||||||
message_queue: None,
|
message_queue: None,
|
||||||
receipt: None,
|
receipt: None,
|
||||||
|
justification: None,
|
||||||
}).collect()
|
}).collect()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -16,14 +16,15 @@
|
|||||||
|
|
||||||
//! Blockchain access trait
|
//! 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 client::error::Error;
|
||||||
use state_machine;
|
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
|
/// 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.
|
/// Get blockchain info.
|
||||||
fn info(&self) -> Result<ClientInfo, Error>;
|
fn info(&self) -> Result<ClientInfo, Error>;
|
||||||
@@ -39,6 +40,9 @@ pub trait Client : Send + Sync {
|
|||||||
|
|
||||||
/// Get block body.
|
/// Get block body.
|
||||||
fn body(&self, id: &BlockId) -> Result<Option<block::Body>, Error>;
|
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
|
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,
|
E: state_machine::CodeExecutor + Send + Sync + 'static,
|
||||||
Error: From<<<B as client::backend::Backend>::State as state_machine::backend::Backend>::Error>, {
|
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> {
|
fn import(&self, header: block::Header, justification: Justification, body: Option<block::Body>) -> Result<ImportResult, Error> {
|
||||||
(self as &PolkadotClient<B, E>).import_block(header, body)
|
// 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> {
|
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> {
|
fn body(&self, id: &BlockId) -> Result<Option<block::Body>, Error> {
|
||||||
(self as &PolkadotClient<B, E>).body(id)
|
(self as &PolkadotClient<B, E>).body(id)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn justification(&self, id: &BlockId) -> Result<Option<Justification>, Error> {
|
||||||
|
(self as &PolkadotClient<B, E>).justification(id)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -40,6 +40,7 @@ extern crate serde_json;
|
|||||||
#[cfg(test)] extern crate substrate_keyring as keyring;
|
#[cfg(test)] extern crate substrate_keyring as keyring;
|
||||||
#[cfg(test)] #[macro_use] extern crate substrate_executor as executor;
|
#[cfg(test)] #[macro_use] extern crate substrate_executor as executor;
|
||||||
#[cfg(test)] extern crate substrate_codec as codec;
|
#[cfg(test)] extern crate substrate_codec as codec;
|
||||||
|
#[cfg(test)] extern crate substrate_bft as bft;
|
||||||
|
|
||||||
mod service;
|
mod service;
|
||||||
mod sync;
|
mod sync;
|
||||||
|
|||||||
@@ -19,6 +19,7 @@
|
|||||||
use std::borrow::Borrow;
|
use std::borrow::Borrow;
|
||||||
use primitives::AuthorityId;
|
use primitives::AuthorityId;
|
||||||
use primitives::block::{Number as BlockNumber, HeaderHash, Header, Body};
|
use primitives::block::{Number as BlockNumber, HeaderHash, Header, Body};
|
||||||
|
use primitives::bft::Justification;
|
||||||
use service::Role as RoleFlags;
|
use service::Role as RoleFlags;
|
||||||
|
|
||||||
pub type RequestId = u64;
|
pub type RequestId = u64;
|
||||||
@@ -85,6 +86,8 @@ pub enum BlockAttribute {
|
|||||||
Receipt,
|
Receipt,
|
||||||
/// Include block message queue.
|
/// Include block message queue.
|
||||||
MessageQueue,
|
MessageQueue,
|
||||||
|
/// Include a justification for the block.
|
||||||
|
Justification,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, PartialEq, Eq, Clone, Serialize, Deserialize)]
|
#[derive(Debug, PartialEq, Eq, Clone, Serialize, Deserialize)]
|
||||||
@@ -100,6 +103,8 @@ pub struct BlockData {
|
|||||||
pub receipt: Option<Bytes>,
|
pub receipt: Option<Bytes>,
|
||||||
/// Block message queue if requested.
|
/// Block message queue if requested.
|
||||||
pub message_queue: Option<Bytes>,
|
pub message_queue: Option<Bytes>,
|
||||||
|
/// Justification if requested.
|
||||||
|
pub justification: Option<Justification>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[serde(untagged)]
|
#[serde(untagged)]
|
||||||
|
|||||||
@@ -17,10 +17,10 @@
|
|||||||
use std::collections::{HashMap, HashSet, BTreeMap};
|
use std::collections::{HashMap, HashSet, BTreeMap};
|
||||||
use std::{mem, cmp};
|
use std::{mem, cmp};
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
use std::time;
|
||||||
use parking_lot::RwLock;
|
use parking_lot::RwLock;
|
||||||
use serde_json;
|
use serde_json;
|
||||||
use std::time;
|
use primitives::block::{HeaderHash, TransactionHash, Number as BlockNumber, Header, Id as BlockId};
|
||||||
use primitives::block::{HeaderHash, TransactionHash, Number as BlockNumber, Header};
|
|
||||||
use network::{PeerId, NodeId};
|
use network::{PeerId, NodeId};
|
||||||
|
|
||||||
use message::{self, Message};
|
use message::{self, Message};
|
||||||
@@ -30,7 +30,6 @@ use config::ProtocolConfig;
|
|||||||
use chain::Client;
|
use chain::Client;
|
||||||
use io::SyncIo;
|
use io::SyncIo;
|
||||||
use error;
|
use error;
|
||||||
use client::BlockId;
|
|
||||||
use super::header_hash;
|
use super::header_hash;
|
||||||
|
|
||||||
const REQUEST_TIMEOUT_SEC: u64 = 15;
|
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;
|
let max = cmp::min(request.max.unwrap_or(u32::max_value()), MAX_BLOCK_DATA_RESPONSE) as usize;
|
||||||
// TODO: receipts, etc.
|
// 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 {
|
for a in request.fields {
|
||||||
match a {
|
match a {
|
||||||
message::BlockAttribute::Header => get_header = true,
|
message::BlockAttribute::Header => get_header = true,
|
||||||
message::BlockAttribute::Body => get_body = true,
|
message::BlockAttribute::Body => get_body = true,
|
||||||
message::BlockAttribute::Receipt => unimplemented!(),
|
message::BlockAttribute::Receipt => unimplemented!(),
|
||||||
message::BlockAttribute::MessageQueue => unimplemented!(),
|
message::BlockAttribute::MessageQueue => unimplemented!(),
|
||||||
|
message::BlockAttribute::Justification => get_justification = true,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
while let Some(header) = self.chain.header(&id).unwrap_or(None) {
|
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 },
|
body: if get_body { self.chain.body(&BlockId::Hash(hash)).unwrap_or(None) } else { None },
|
||||||
receipt: None,
|
receipt: None,
|
||||||
message_queue: None,
|
message_queue: None,
|
||||||
|
justification: if get_justification { self.chain.justification(&BlockId::Hash(hash)).unwrap_or(None) } else { None },
|
||||||
};
|
};
|
||||||
blocks.push(block_data);
|
blocks.push(block_data);
|
||||||
match request.direction {
|
match request.direction {
|
||||||
|
|||||||
@@ -18,8 +18,8 @@ use std::collections::HashMap;
|
|||||||
use io::SyncIo;
|
use io::SyncIo;
|
||||||
use protocol::Protocol;
|
use protocol::Protocol;
|
||||||
use network::PeerId;
|
use network::PeerId;
|
||||||
use client::{ImportResult, BlockStatus, ClientInfo, BlockId};
|
use client::{ImportResult, BlockStatus, ClientInfo};
|
||||||
use primitives::block::{HeaderHash, Number as BlockNumber, Header};
|
use primitives::block::{HeaderHash, Number as BlockNumber, Header, Id as BlockId};
|
||||||
use blocks::{self, BlockCollection};
|
use blocks::{self, BlockCollection};
|
||||||
use message::{self, Message};
|
use message::{self, Message};
|
||||||
use super::header_hash;
|
use super::header_hash;
|
||||||
@@ -80,7 +80,7 @@ impl ChainSync {
|
|||||||
blocks: BlockCollection::new(),
|
blocks: BlockCollection::new(),
|
||||||
best_queued_hash: info.best_queued_hash.unwrap_or(info.chain.best_hash),
|
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),
|
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 {
|
for block in new_blocks {
|
||||||
let origin = block.origin;
|
let origin = block.origin;
|
||||||
let block = block.block;
|
let block = block.block;
|
||||||
if let Some(header) = block.header {
|
match (block.header, block.justification) {
|
||||||
let number = header.number;
|
(Some(header), Some(justification)) => {
|
||||||
let hash = header_hash(&header);
|
let number = header.number;
|
||||||
let parent = header.parent_hash;
|
let hash = header_hash(&header);
|
||||||
let result = protocol.chain().import(header, block.body);
|
let parent = header.parent_hash;
|
||||||
match result {
|
let result = protocol.chain().import(
|
||||||
Ok(ImportResult::AlreadyInChain) => {
|
header,
|
||||||
trace!(target: "sync", "Block already in chain {}: {:?}", number, hash);
|
justification,
|
||||||
self.block_imported(&hash, number);
|
block.body
|
||||||
},
|
);
|
||||||
Ok(ImportResult::AlreadyQueued) => {
|
match result {
|
||||||
trace!(target: "sync", "Block already queued {}: {:?}", number, hash);
|
Ok(ImportResult::AlreadyInChain) => {
|
||||||
self.block_imported(&hash, number);
|
trace!(target: "sync", "Block already in chain {}: {:?}", number, hash);
|
||||||
},
|
self.block_imported(&hash, number);
|
||||||
Ok(ImportResult::Queued) => {
|
},
|
||||||
trace!(target: "sync", "Block queued {}: {:?}", number, hash);
|
Ok(ImportResult::AlreadyQueued) => {
|
||||||
self.block_imported(&hash, number);
|
trace!(target: "sync", "Block already queued {}: {:?}", number, hash);
|
||||||
imported = imported + 1;
|
self.block_imported(&hash, number);
|
||||||
},
|
},
|
||||||
Ok(ImportResult::UnknownParent) => {
|
Ok(ImportResult::Queued) => {
|
||||||
debug!(target: "sync", "Block with unknown parent {}: {:?}, parent: {:?}", number, hash, parent);
|
trace!(target: "sync", "Block queued {}: {:?}", number, hash);
|
||||||
self.restart(io, protocol);
|
self.block_imported(&hash, number);
|
||||||
return;
|
imported = imported + 1;
|
||||||
},
|
},
|
||||||
Ok(ImportResult::KnownBad) => {
|
Ok(ImportResult::UnknownParent) => {
|
||||||
debug!(target: "sync", "Bad block {}: {:?}", number, hash);
|
debug!(target: "sync", "Block with unknown parent {}: {:?}, parent: {:?}", number, hash, parent);
|
||||||
io.disable_peer(origin); //TODO: use persistent ID
|
self.restart(io, protocol);
|
||||||
self.restart(io, protocol);
|
return;
|
||||||
return;
|
},
|
||||||
}
|
Ok(ImportResult::KnownBad) => {
|
||||||
Err(e) => {
|
debug!(target: "sync", "Bad block {}: {:?}", number, hash);
|
||||||
debug!(target: "sync", "Error importing block {}: {:?}: {:?}", number, hash, e);
|
io.disable_peer(origin); //TODO: use persistent ID
|
||||||
self.restart(io, protocol);
|
self.restart(io, protocol);
|
||||||
return;
|
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) {
|
fn request_ancestry(io: &mut SyncIo, protocol: &Protocol, peer_id: PeerId, block: BlockNumber) {
|
||||||
let request = message::BlockRequest {
|
let request = message::BlockRequest {
|
||||||
id: 0,
|
id: 0,
|
||||||
fields: vec![message::BlockAttribute::Header],
|
fields: vec![message::BlockAttribute::Header, message::BlockAttribute::Justification],
|
||||||
from: message::FromBlock::Number(block),
|
from: message::FromBlock::Number(block),
|
||||||
to: None,
|
to: None,
|
||||||
direction: message::Direction::Ascending,
|
direction: message::Direction::Ascending,
|
||||||
|
|||||||
@@ -19,8 +19,9 @@ mod sync;
|
|||||||
use std::collections::{VecDeque, HashSet, HashMap};
|
use std::collections::{VecDeque, HashSet, HashMap};
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
use parking_lot::RwLock;
|
use parking_lot::RwLock;
|
||||||
use client::{self, BlockId, genesis};
|
use client::{self, genesis};
|
||||||
use client::block_builder::BlockBuilder;
|
use client::block_builder::BlockBuilder;
|
||||||
|
use primitives::block::Id as BlockId;
|
||||||
use primitives;
|
use primitives;
|
||||||
use executor;
|
use executor;
|
||||||
use io::SyncIo;
|
use io::SyncIo;
|
||||||
@@ -32,6 +33,7 @@ use runtime_support::Hashable;
|
|||||||
use test_runtime;
|
use test_runtime;
|
||||||
use keyring::Keyring;
|
use keyring::Keyring;
|
||||||
use codec::Slicable;
|
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"));
|
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 {
|
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 sync: Protocol,
|
||||||
pub queue: RwLock<VecDeque<TestPacket>>,
|
pub queue: RwLock<VecDeque<TestPacket>>,
|
||||||
}
|
}
|
||||||
@@ -108,9 +110,9 @@ pub struct Peer {
|
|||||||
impl Peer {
|
impl Peer {
|
||||||
/// Called after blockchain has been populated to updated current state.
|
/// Called after blockchain has been populated to updated current state.
|
||||||
fn start(&self) {
|
fn start(&self) {
|
||||||
// Update the sync state to the lates chain state.
|
// Update the sync state to the latest chain state.
|
||||||
let info = self.chain.info().expect("In-mem chain does not fail");
|
let info = self.client.info().expect("In-mem client does not fail");
|
||||||
let header = self.chain.header(&BlockId::Hash(info.chain.best_hash)).unwrap().unwrap();
|
let header = self.client.header(&BlockId::Hash(info.chain.best_hash)).unwrap().unwrap();
|
||||||
self.sync.on_block_imported(&header);
|
self.sync.on_block_imported(&header);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -158,13 +160,32 @@ impl Peer {
|
|||||||
fn flush(&self) {
|
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>>) {
|
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 {
|
for _ in 0 .. count {
|
||||||
let mut builder = self.chain.new_block().unwrap();
|
let mut builder = self.client.new_block().unwrap();
|
||||||
edit_block(&mut builder);
|
edit_block(&mut builder);
|
||||||
let block = builder.bake().unwrap();
|
let block = builder.bake().unwrap();
|
||||||
trace!("Generating {}, (#{})", primitives::block::HeaderHash::from(block.header.blake2_256()), block.header.number);
|
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 {
|
for _ in 0..n {
|
||||||
let chain = Arc::new(client::new_in_mem(Executor::new(), Self::prepare_genesis).unwrap());
|
let client = Arc::new(client::new_in_mem(Executor::new(), Self::prepare_genesis).unwrap());
|
||||||
let sync = Protocol::new(config.clone(), chain.clone()).unwrap();
|
let sync = Protocol::new(config.clone(), client.clone()).unwrap();
|
||||||
net.peers.push(Arc::new(Peer {
|
net.peers.push(Arc::new(Peer {
|
||||||
sync: sync,
|
sync: sync,
|
||||||
chain: chain,
|
client: client,
|
||||||
queue: RwLock::new(VecDeque::new()),
|
queue: RwLock::new(VecDeque::new()),
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -25,7 +25,7 @@ fn sync_from_two_peers_works() {
|
|||||||
net.peer(1).push_blocks(100, false);
|
net.peer(1).push_blocks(100, false);
|
||||||
net.peer(2).push_blocks(100, false);
|
net.peer(2).push_blocks(100, false);
|
||||||
net.sync();
|
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();
|
let status = net.peer(0).sync.status();
|
||||||
assert_eq!(status.sync.state, SyncState::Idle);
|
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.peer(2).push_blocks(100, false);
|
||||||
net.restart_peer(0);
|
net.restart_peer(0);
|
||||||
net.sync();
|
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]
|
#[test]
|
||||||
@@ -49,7 +49,7 @@ fn sync_long_chain_works() {
|
|||||||
net.sync_steps(3);
|
net.sync_steps(3);
|
||||||
assert_eq!(net.peer(0).sync.status().sync.state, SyncState::Downloading);
|
assert_eq!(net.peer(0).sync.status().sync.state, SyncState::Downloading);
|
||||||
net.sync();
|
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]
|
#[test]
|
||||||
@@ -59,7 +59,7 @@ fn sync_no_common_longer_chain_fails() {
|
|||||||
net.peer(0).push_blocks(20, true);
|
net.peer(0).push_blocks(20, true);
|
||||||
net.peer(1).push_blocks(20, false);
|
net.peer(1).push_blocks(20, false);
|
||||||
net.sync();
|
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]
|
#[test]
|
||||||
@@ -78,10 +78,10 @@ fn sync_after_fork_works() {
|
|||||||
net.peer(2).push_blocks(1, false);
|
net.peer(2).push_blocks(1, false);
|
||||||
|
|
||||||
// peer 1 has the best chain
|
// 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();
|
net.sync();
|
||||||
assert!(net.peer(0).chain.backend().blockchain().canon_equals_to(&peer1_chain));
|
assert!(net.peer(0).client.backend().blockchain().canon_equals_to(&peer1_chain));
|
||||||
assert!(net.peer(1).chain.backend().blockchain().canon_equals_to(&peer1_chain));
|
assert!(net.peer(1).client.backend().blockchain().canon_equals_to(&peer1_chain));
|
||||||
assert!(net.peer(2).chain.backend().blockchain().canon_equals_to(&peer1_chain));
|
assert!(net.peer(2).client.backend().blockchain().canon_equals_to(&peer1_chain));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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.
|
//! Block and header type definitions.
|
||||||
|
|
||||||
|
use rstd::fmt;
|
||||||
use rstd::vec::Vec;
|
use rstd::vec::Vec;
|
||||||
#[cfg(feature = "std")]
|
#[cfg(feature = "std")]
|
||||||
use bytes;
|
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)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
|
|||||||
@@ -98,3 +98,6 @@ pub type Hash = H256;
|
|||||||
|
|
||||||
/// An identifier for an authority in the consensus algorithm. The same as ed25519::Public.
|
/// An identifier for an authority in the consensus algorithm. The same as ed25519::Public.
|
||||||
pub type AuthorityId = [u8; 32];
|
pub type AuthorityId = [u8; 32];
|
||||||
|
|
||||||
|
/// A 512-bit value interpreted as a signature.
|
||||||
|
pub type Signature = hash::H512;
|
||||||
|
|||||||
@@ -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>,
|
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>> {
|
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")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -21,7 +21,7 @@ mod error;
|
|||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests;
|
mod tests;
|
||||||
|
|
||||||
use client::{self, Client, BlockId};
|
use client::{self, Client};
|
||||||
use primitives::block;
|
use primitives::block;
|
||||||
use primitives::storage::{StorageKey, StorageData};
|
use primitives::storage::{StorageKey, StorageData};
|
||||||
use state_machine;
|
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>,
|
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> {
|
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>> {
|
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::boxed;
|
||||||
pub use std::cell;
|
pub use std::cell;
|
||||||
pub use std::cmp;
|
pub use std::cmp;
|
||||||
|
pub use std::fmt;
|
||||||
pub use std::iter;
|
pub use std::iter;
|
||||||
pub use std::mem;
|
pub use std::mem;
|
||||||
pub use std::ops;
|
pub use std::ops;
|
||||||
|
|||||||
@@ -33,3 +33,4 @@ pub use core::mem;
|
|||||||
pub use core::ops;
|
pub use core::ops;
|
||||||
pub use core::ptr;
|
pub use core::ptr;
|
||||||
pub use core::slice;
|
pub use core::slice;
|
||||||
|
pub use core::fmt;
|
||||||
|
|||||||
Reference in New Issue
Block a user