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:
Robert Habermeier
2018-02-16 17:28:42 +01:00
committed by GitHub
parent c56859f331
commit 1d0ad42230
31 changed files with 549 additions and 191 deletions
+1
View File
@@ -10,6 +10,7 @@ parking_lot = "0.4"
triehash = "0.1"
hex-literal = "0.1"
ed25519 = { path = "../ed25519" }
substrate-bft = { path = "../bft" }
substrate-codec = { path = "../codec" }
substrate-executor = { path = "../executor" }
substrate-primitives = { path = "../primitives" }
+4 -4
View File
@@ -18,8 +18,8 @@
use state_machine;
use error;
use primitives::block;
use blockchain::{self, BlockId};
use primitives::block::{self, Id as BlockId};
use primitives;
/// Block insertion operation. Keeps hold if the inserted block state and data.
pub trait BlockImportOperation {
@@ -29,7 +29,7 @@ pub trait BlockImportOperation {
/// Returns pending state.
fn state(&self) -> error::Result<&Self::State>;
/// Append block data to the transaction.
fn set_block_data(&mut self, header: block::Header, body: Option<block::Body>, is_new_best: bool) -> error::Result<()>;
fn set_block_data(&mut self, header: block::Header, body: Option<block::Body>, justification: Option<primitives::bft::Justification>, is_new_best: bool) -> error::Result<()>;
/// Inject storage data into the database.
fn set_storage<I: Iterator<Item=(Vec<u8>, Vec<u8>)>>(&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;
@@ -20,8 +20,8 @@ use std::vec::Vec;
use codec::{Joiner, Slicable};
use state_machine::{self, CodeExecutor};
use primitives::{Header, Block};
use primitives::block::Transaction;
use {backend, error, BlockId, Client};
use primitives::block::{Id as BlockId, Transaction};
use {backend, error, Client};
use triehash::ordered_trie_root;
/// Utility for building new (valid) blocks from a stream of transactions.
+4 -19
View File
@@ -16,27 +16,10 @@
//! Polkadot blockchain trait
use std::fmt::{Display, Formatter, Error as FmtError};
use primitives::block;
use primitives::block::{self, Id as BlockId};
use primitives;
use error::Result;
/// Block indentification.
#[derive(Debug, Clone, Copy)]
pub enum BlockId {
/// Identify by block header hash.
Hash(block::HeaderHash),
/// Identify by block number.
Number(block::Number),
}
impl Display for BlockId {
fn fmt(&self, f: &mut Formatter) -> ::std::result::Result<(), FmtError> {
match *self {
BlockId::Hash(h) => h.fmt(f),
BlockId::Number(n) => n.fmt(f),
}
}
}
/// Blockchain database backend. Does not perform any validation.
pub trait Backend: Send + Sync {
@@ -44,6 +27,8 @@ pub trait Backend: Send + Sync {
fn header(&self, id: BlockId) -> Result<Option<block::Header>>;
/// Get block body. Returns `None` if block is not found.
fn body(&self, id: BlockId) -> Result<Option<block::Body>>;
/// Get block justification. Returns `None` if justification does not exist.
fn justification(&self, id: BlockId) -> Result<Option<primitives::bft::Justification>>;
/// Get blockchain info.
fn info(&self) -> Result<Info>;
/// Get block status.
+7 -2
View File
@@ -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"),
+13 -5
View File
@@ -22,8 +22,9 @@ use state_machine;
use error;
use backend;
use runtime_support::Hashable;
use primitives::block::{self, HeaderHash};
use blockchain::{self, BlockId, BlockStatus};
use primitives;
use primitives::block::{self, Id as BlockId, HeaderHash};
use blockchain::{self, BlockStatus};
use state_machine::backend::Backend as StateBackend;
fn header_hash(header: &block::Header) -> block::HeaderHash {
@@ -38,6 +39,7 @@ struct PendingBlock {
#[derive(PartialEq, Eq, Clone)]
struct Block {
header: block::Header,
justification: Option<primitives::bft::Justification>,
body: Option<block::Body>,
}
@@ -90,12 +92,13 @@ impl Blockchain {
}
}
fn insert(&self, hash: HeaderHash, header: block::Header, body: Option<block::Body>, is_new_best: bool) {
fn insert(&self, hash: HeaderHash, header: block::Header, justification: Option<primitives::bft::Justification>, body: Option<block::Body>, is_new_best: bool) {
let number = header.number;
let mut storage = self.storage.write();
storage.blocks.insert(hash, Block {
header: header,
body: body,
justification: justification,
});
storage.hashes.insert(number, hash);
if is_new_best {
@@ -132,6 +135,10 @@ impl blockchain::Backend for Blockchain {
Ok(self.id(id).and_then(|hash| self.storage.read().blocks.get(&hash).and_then(|b| b.body.clone())))
}
fn justification(&self, id: BlockId) -> error::Result<Option<primitives::bft::Justification>> {
Ok(self.id(id).and_then(|hash| self.storage.read().blocks.get(&hash).and_then(|b| b.justification.clone())))
}
fn info(&self) -> error::Result<blockchain::Info> {
let storage = self.storage.read();
Ok(blockchain::Info {
@@ -160,12 +167,13 @@ impl backend::BlockImportOperation for BlockImportOperation {
Ok(&self.pending_state)
}
fn set_block_data(&mut self, header: block::Header, body: Option<block::Body>, is_new_best: bool) -> error::Result<()> {
fn set_block_data(&mut self, header: block::Header, body: Option<block::Body>, justification: Option<primitives::bft::Justification>, is_new_best: bool) -> error::Result<()> {
assert!(self.pending_block.is_none(), "Only one block per operation is allowed");
self.pending_block = Some(PendingBlock {
block: Block {
header: header,
body: body,
justification: justification,
},
is_best: is_new_best,
});
@@ -220,7 +228,7 @@ impl backend::Backend for Backend {
if let Some(pending_block) = operation.pending_block {
let hash = header_hash(&pending_block.block.header);
self.states.write().insert(hash, operation.pending_state);
self.blockchain.insert(hash, pending_block.block.header, pending_block.block.body, pending_block.is_best);
self.blockchain.insert(hash, pending_block.block.header, pending_block.block.justification, pending_block.block.body, pending_block.is_best);
}
Ok(())
}
+107 -21
View File
@@ -18,6 +18,7 @@
#![warn(missing_docs)]
extern crate substrate_bft as bft;
extern crate substrate_runtime_support as runtime_support;
extern crate substrate_runtime_io as runtime_io;
extern crate substrate_primitives as primitives;
@@ -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<E, F>(
executor: E,
@@ -140,7 +155,7 @@ impl<B, E> Client<B, E> 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<B, E> Client<B, E> 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<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.
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: 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<B, E> Client<B, E> 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<B, E> Client<B, E> where
pub fn body(&self, id: &BlockId) -> error::Result<Option<block::Body>> {
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)]
@@ -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());