diff --git a/Cargo.lock b/Cargo.lock index b12e79f998993..62e3721f8c60a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -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", diff --git a/polkadot/api/src/lib.rs b/polkadot/api/src/lib.rs index a16781eb69b28..efe0f45220099 100644 --- a/polkadot/api/src/lib.rs +++ b/polkadot/api/src/lib.rs @@ -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! { diff --git a/polkadot/primitives/src/block.rs b/polkadot/primitives/src/block.rs index d71fa82e1c287..987744baa61bc 100644 --- a/polkadot/primitives/src/block.rs +++ b/polkadot/primitives/src/block.rs @@ -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; diff --git a/substrate/bft/Cargo.toml b/substrate/bft/Cargo.toml index f1af1cda411ce..5d9fa23c63406 100644 --- a/substrate/bft/Cargo.toml +++ b/substrate/bft/Cargo.toml @@ -5,10 +5,8 @@ authors = ["Parity Technologies "] [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" diff --git a/substrate/bft/src/error.rs b/substrate/bft/src/error.rs index 1f5286f5e665e..38f560c2cc78d 100644 --- a/substrate/bft/src/error.rs +++ b/substrate/bft/src/error.rs @@ -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), } diff --git a/substrate/bft/src/generic/accumulator.rs b/substrate/bft/src/generic/accumulator.rs index 273fc658478e9..a7b2076bba5e6 100644 --- a/substrate/bft/src/generic/accumulator.rs +++ b/substrate/bft/src/generic/accumulator.rs @@ -83,6 +83,13 @@ impl UncheckedJustification { #[derive(Debug, Clone, PartialEq, Eq)] pub struct Justification(UncheckedJustification); +impl Justification { + /// Convert this justification back to unchecked. + pub fn uncheck(self) -> UncheckedJustification { + self.0 + } +} + impl ::std::ops::Deref for Justification { type Target = UncheckedJustification; diff --git a/substrate/bft/src/lib.rs b/substrate/bft/src/lib.rs index f4b1f72a0112d..1f81b23cac0e5 100644 --- a/substrate/bft/src/lib.rs +++ b/substrate/bft/src/lib.rs @@ -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; +pub type Justification = generic::Justification; /// Justification of a prepare message. -pub type PrepareJustification = generic::PrepareJustification; +pub type PrepareJustification = generic::PrepareJustification; + +/// Unchecked justification. +pub type UncheckedJustification = generic::UncheckedJustification; + +impl From 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 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; +pub type Committed = generic::Committed; /// Communication between BFT participants. -pub type Communication = generic::Communication; +pub type Communication = generic::Communication; /// Logic for a proposer. /// @@ -108,30 +130,6 @@ pub trait Authorities { fn authorities(&self, at: &BlockId) -> Result, Error>; } -impl BlockImport for Client - where - B: Backend, - E: CodeExecutor, - client::error::Error: From<::Error> -{ - fn import_block(&self, block: Block, _justification: Justification) { - // TODO: use justification. - let _ = self.import_block(block.header, Some(block.transactions)); - } -} - -impl Authorities for Client - where - B: Backend, - E: CodeExecutor, - client::error::Error: From<::Error> -{ - fn authorities(&self, at: &BlockId) -> Result, Error> { - self.authorities_at(at).map_err(|_| ErrorKind::StateUnavailable(*at).into()) - } -} - - /// Instance of BFT agreement. struct BftInstance

{ key: Arc, @@ -145,7 +143,7 @@ struct BftInstance

{ impl generic::Context for BftInstance

{ type AuthorityId = AuthorityId; type Digest = HeaderHash; - type Signature = Signature; + type Signature = LocalizedSignature; type Candidate = Block; type RoundTimeout = Box + Send>; type CreateProposal = ::Future; @@ -163,28 +161,7 @@ impl generic::Context for BftInstance

{ } 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 BftService // 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 BftService } } +/// 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 +{ + 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 +{ + 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 +{ + 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()); + } } diff --git a/substrate/client/Cargo.toml b/substrate/client/Cargo.toml index ad9fa2d2ad12e..879e05e3c9442 100644 --- a/substrate/client/Cargo.toml +++ b/substrate/client/Cargo.toml @@ -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" } diff --git a/substrate/client/src/backend.rs b/substrate/client/src/backend.rs index 4421c82b3742f..eef904679173c 100644 --- a/substrate/client/src/backend.rs +++ b/substrate/client/src/backend.rs @@ -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, is_new_best: bool) -> error::Result<()>; + fn set_block_data(&mut self, header: block::Header, body: Option, justification: Option, is_new_best: bool) -> error::Result<()>; /// Inject storage data into the database. fn set_storage, Vec)>>(&mut self, iter: 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; diff --git a/substrate/client/src/block_builder.rs b/substrate/client/src/block_builder.rs index 323fb61c29fc1..54fcd8df7ebf0 100644 --- a/substrate/client/src/block_builder.rs +++ b/substrate/client/src/block_builder.rs @@ -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. diff --git a/substrate/client/src/blockchain.rs b/substrate/client/src/blockchain.rs index 6eb9061eb7dfa..095cbe3cbee8a 100644 --- a/substrate/client/src/blockchain.rs +++ b/substrate/client/src/blockchain.rs @@ -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>; /// Get block body. Returns `None` if block is not found. fn body(&self, id: BlockId) -> Result>; + /// Get block justification. Returns `None` if justification does not exist. + fn justification(&self, id: BlockId) -> Result>; /// Get blockchain info. fn info(&self) -> Result; /// Get block status. diff --git a/substrate/client/src/error.rs b/substrate/client/src/error.rs index 1c97387e817c9..2ae07c870c59f 100644 --- a/substrate/client/src/error.rs +++ b/substrate/client/src/error.rs @@ -18,7 +18,6 @@ use std; use state_machine; -use blockchain; error_chain! { errors { @@ -29,7 +28,7 @@ error_chain! { } /// Unknown block. - UnknownBlock(h: blockchain::BlockId) { + UnknownBlock(h: ::primitives::block::Id) { description("unknown block"), display("UnknownBlock: {}", h), } @@ -46,6 +45,12 @@ error_chain! { 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. AuthLen { description("authority count state error"), diff --git a/substrate/client/src/in_mem.rs b/substrate/client/src/in_mem.rs index 6097d12e51962..62ef3b971552b 100644 --- a/substrate/client/src/in_mem.rs +++ b/substrate/client/src/in_mem.rs @@ -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, body: Option, } @@ -90,12 +92,13 @@ impl Blockchain { } } - fn insert(&self, hash: HeaderHash, header: block::Header, body: Option, is_new_best: bool) { + fn insert(&self, hash: HeaderHash, header: block::Header, justification: Option, body: Option, 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> { + Ok(self.id(id).and_then(|hash| self.storage.read().blocks.get(&hash).and_then(|b| b.justification.clone()))) + } + fn info(&self) -> error::Result { 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, is_new_best: bool) -> error::Result<()> { + fn set_block_data(&mut self, header: block::Header, body: Option, justification: Option, 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(()) } diff --git a/substrate/client/src/lib.rs b/substrate/client/src/lib.rs index 54f93405e1b8f..c86c06afef917 100644 --- a/substrate/client/src/lib.rs +++ b/substrate/client/src/lib.rs @@ -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; @@ -42,9 +43,9 @@ pub mod genesis; pub mod block_builder; pub use blockchain::Info as ChainInfo; -pub use blockchain::BlockId; use primitives::{block, AuthorityId}; +use primitives::block::Id as BlockId; use primitives::storage::{StorageKey, StorageData}; use codec::{KeyedVec, Slicable}; @@ -109,6 +110,20 @@ pub enum BlockStatus { 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. pub fn new_in_mem( executor: E, @@ -140,7 +155,7 @@ impl Client where let (genesis_header, genesis_store) = build_genesis(); let mut op = backend.begin_operation(BlockId::Hash(block::HeaderHash::default()))?; 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)?; } Ok(Client { @@ -229,10 +244,31 @@ impl Client where 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 { + 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. - pub fn import_block(&self, header: block::Header, body: Option) -> error::Result { + pub fn import_block( + &self, + header: JustifiedHeader, + body: Option, + ) -> error::Result { // TODO: import lock // TODO: validate block + // TODO: import justification. + let (header, justification) = header.into_inner(); match self.backend.blockchain().status(BlockId::Hash(header.parent_hash))? { blockchain::BlockStatus::InChain => (), blockchain::BlockStatus::Unknown => return Ok(ImportResult::UnknownParent), @@ -251,7 +287,7 @@ impl Client where 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); - 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())?; self.backend.commit_operation(transaction)?; Ok(ImportResult::Queued) @@ -306,6 +342,38 @@ impl Client where pub fn body(&self, id: &BlockId) -> error::Result> { self.backend.blockchain().body(*id) } + + /// Get block justification set by id. + pub fn justification(&self, id: &BlockId) -> error::Result> { + self.backend.blockchain().justification(*id) + } +} + +impl bft::BlockImport for Client + where + B: backend::Backend, + E: state_machine::CodeExecutor, + error::Error: From<::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 bft::Authorities for Client + where + B: backend::Backend, + E: state_machine::CodeExecutor, + error::Error: From<::Error> +{ + fn authorities(&self, at: &BlockId) -> Result, bft::Error> { + self.authorities_at(at).map_err(|_| bft::ErrorKind::StateUnavailable(*at).into()) + } } #[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()) } + // 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] fn client_initialises_from_genesis_ok() { let client = new_in_mem(Executor::new(), prepare_genesis).unwrap(); @@ -347,11 +440,7 @@ mod tests { #[test] fn authorities_call_works() { - let genesis_config = GenesisConfig::new_simple(vec![ - Keyring::Alice.to_raw_public(), - Keyring::Bob.to_raw_public(), - Keyring::Charlie.to_raw_public() - ], 1000); + let genesis_config = genesis_config(); let prepare_genesis = || { let mut storage = genesis_config.genesis_map(); @@ -371,11 +460,7 @@ mod tests { #[test] fn block_builder_works_with_no_transactions() { - let genesis_config = GenesisConfig::new_simple(vec![ - Keyring::Alice.to_raw_public(), - Keyring::Bob.to_raw_public(), - Keyring::Charlie.to_raw_public() - ], 1000); + let genesis_config = genesis_config(); let prepare_genesis = || { let mut storage = genesis_config.genesis_map(); @@ -388,7 +473,9 @@ mod tests { let builder = client.new_block().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.using_environment(|| test_runtime::system::latest_block_hash()).unwrap(), client.block_hash(1).unwrap().unwrap()); @@ -406,11 +493,7 @@ mod tests { #[test] fn block_builder_works_with_transactions() { - let genesis_config = GenesisConfig::new_simple(vec![ - Keyring::Alice.to_raw_public(), - Keyring::Bob.to_raw_public(), - Keyring::Charlie.to_raw_public() - ], 1000); + let genesis_config = genesis_config(); let prepare_genesis = || { let mut storage = genesis_config.genesis_map(); @@ -429,7 +512,10 @@ mod tests { nonce: 0 }.signed()).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!(client.state_at(&BlockId::Number(1)).unwrap() != client.state_at(&BlockId::Number(0)).unwrap()); diff --git a/substrate/ed25519/src/lib.rs b/substrate/ed25519/src/lib.rs index 30496adefff22..4d8117186bc61 100644 --- a/substrate/ed25519/src/lib.rs +++ b/substrate/ed25519/src/lib.rs @@ -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) { } + takes_two(Signature::default(), primitives::Signature::default()) + } + #[test] fn test_vector_should_work() { let pair: Pair = Pair::from_seed(&hex!("9d61b19deffd5a60ba844af492ec2cc44449c5697b326919703bac031cae7f60")); diff --git a/substrate/network/Cargo.toml b/substrate/network/Cargo.toml index 5e3314e7b7b3c..2c07934052605 100644 --- a/substrate/network/Cargo.toml +++ b/substrate/network/Cargo.toml @@ -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" diff --git a/substrate/network/src/blocks.rs b/substrate/network/src/blocks.rs index 7cb472ec549f3..f111d431049fb 100644 --- a/substrate/network/src/blocks.rs +++ b/substrate/network/src/blocks.rs @@ -205,6 +205,7 @@ mod test { body: None, message_queue: None, receipt: None, + justification: None, }).collect() } diff --git a/substrate/network/src/chain.rs b/substrate/network/src/chain.rs index 709a988060cd3..1a51797f10b37 100644 --- a/substrate/network/src/chain.rs +++ b/substrate/network/src/chain.rs @@ -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) -> Result; + fn import(&self, header: block::Header, justification: Justification, body: Option) -> Result; /// Get blockchain info. fn info(&self) -> Result; @@ -39,6 +40,9 @@ pub trait Client : Send + Sync { /// Get block body. fn body(&self, id: &BlockId) -> Result, Error>; + + /// Get block justification. + fn justification(&self, id: &BlockId) -> Result, Error>; } impl Client for PolkadotClient where @@ -46,8 +50,10 @@ impl Client for PolkadotClient where E: state_machine::CodeExecutor + Send + Sync + 'static, Error: From<<::State as state_machine::backend::Backend>::Error>, { - fn import(&self, header: block::Header, body: Option) -> Result { - (self as &PolkadotClient).import_block(header, body) + fn import(&self, header: block::Header, justification: Justification, body: Option) -> Result { + // TODO: defer justification check. + let justified_header = self.check_justification(header, justification.into())?; + (self as &PolkadotClient).import_block(justified_header, body) } fn info(&self) -> Result { @@ -69,4 +75,8 @@ impl Client for PolkadotClient where fn body(&self, id: &BlockId) -> Result, Error> { (self as &PolkadotClient).body(id) } + + fn justification(&self, id: &BlockId) -> Result, Error> { + (self as &PolkadotClient).justification(id) + } } diff --git a/substrate/network/src/lib.rs b/substrate/network/src/lib.rs index bc2ebe7196573..71e6ab755f2e3 100644 --- a/substrate/network/src/lib.rs +++ b/substrate/network/src/lib.rs @@ -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; diff --git a/substrate/network/src/message.rs b/substrate/network/src/message.rs index 63c4691cfe7d6..793daa444f79e 100644 --- a/substrate/network/src/message.rs +++ b/substrate/network/src/message.rs @@ -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, /// Block message queue if requested. pub message_queue: Option, + /// Justification if requested. + pub justification: Option, } #[serde(untagged)] diff --git a/substrate/network/src/protocol.rs b/substrate/network/src/protocol.rs index 006ed39c3ccde..980bf441f5c2a 100644 --- a/substrate/network/src/protocol.rs +++ b/substrate/network/src/protocol.rs @@ -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 { diff --git a/substrate/network/src/sync.rs b/substrate/network/src/sync.rs index 465bffcc3164e..c952bd5f2e44a 100644 --- a/substrate/network/src/sync.rs +++ b/substrate/network/src/sync.rs @@ -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, diff --git a/substrate/network/src/test/mod.rs b/substrate/network/src/test/mod.rs index 86c8cd91bdf81..4cddc1bf8af8e 100644 --- a/substrate/network/src/test/mod.rs +++ b/substrate/network/src/test/mod.rs @@ -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: Arc>>, pub sync: Protocol, pub queue: RwLock>, } @@ -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(&self, count: usize, mut edit_block: F) where F: FnMut(&mut BlockBuilder>) { 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()), })); } diff --git a/substrate/network/src/test/sync.rs b/substrate/network/src/test/sync.rs index 3e77c01c75fc8..e5cc1ebfad082 100644 --- a/substrate/network/src/test/sync.rs +++ b/substrate/network/src/test/sync.rs @@ -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)); } diff --git a/substrate/primitives/src/bft.rs b/substrate/primitives/src/bft.rs index f6be42e2490c6..49e14075285bf 100644 --- a/substrate/primitives/src/bft.rs +++ b/substrate/primitives/src/bft.rs @@ -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 { + 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(value: &mut I) -> Option { + Some(Justification { + round_number: try_opt!(Slicable::decode(value)), + hash: try_opt!(Slicable::decode(value)), + signatures: try_opt!(Slicable::decode(value)), + }) + } +} diff --git a/substrate/primitives/src/block.rs b/substrate/primitives/src/block.rs index c1799ef551955..36640873ba3bf 100644 --- a/substrate/primitives/src/block.rs +++ b/substrate/primitives/src/block.rs @@ -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::*; diff --git a/substrate/primitives/src/lib.rs b/substrate/primitives/src/lib.rs index fe42a997ca385..ecf187cf910f3 100644 --- a/substrate/primitives/src/lib.rs +++ b/substrate/primitives/src/lib.rs @@ -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; diff --git a/substrate/rpc/src/chain/mod.rs b/substrate/rpc/src/chain/mod.rs index ef827696145d8..2d668826c40dd 100644 --- a/substrate/rpc/src/chain/mod.rs +++ b/substrate/rpc/src/chain/mod.rs @@ -42,6 +42,6 @@ impl ChainApi for client::Client where client::error::Error: From<<::State as state_machine::backend::Backend>::Error>, { fn header(&self, hash: block::HeaderHash) -> Result> { - client::Client::header(self, &client::BlockId::Hash(hash)).chain_err(|| "Blockchain error") + client::Client::header(self, &block::Id::Hash(hash)).chain_err(|| "Blockchain error") } } diff --git a/substrate/rpc/src/state/mod.rs b/substrate/rpc/src/state/mod.rs index de643de7c7290..b8977f30859ae 100644 --- a/substrate/rpc/src/state/mod.rs +++ b/substrate/rpc/src/state/mod.rs @@ -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 StateApi for Client where client::error::Error: From<<::State as state_machine::backend::Backend>::Error>, { fn storage(&self, key: StorageKey, block: block::HeaderHash) -> Result { - Ok(self.storage(&BlockId::Hash(block), &key)?) + Ok(self.storage(&block::Id::Hash(block), &key)?) } fn call(&self, method: String, data: Vec, block: block::HeaderHash) -> Result> { - Ok(self.call(&BlockId::Hash(block), &method, &data)?.return_data) + Ok(self.call(&block::Id::Hash(block), &method, &data)?.return_data) } } diff --git a/substrate/runtime-std/with_std.rs b/substrate/runtime-std/with_std.rs index 960871665e2c4..32124b04c80f7 100644 --- a/substrate/runtime-std/with_std.rs +++ b/substrate/runtime-std/with_std.rs @@ -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; diff --git a/substrate/runtime-std/without_std.rs b/substrate/runtime-std/without_std.rs index b27db9bc7d217..b3e50eb2adf08 100644 --- a/substrate/runtime-std/without_std.rs +++ b/substrate/runtime-std/without_std.rs @@ -33,3 +33,4 @@ pub use core::mem; pub use core::ops; pub use core::ptr; pub use core::slice; +pub use core::fmt;