mirror of
https://github.com/pezkuwichain/pezkuwi-subxt.git
synced 2026-07-21 05:05:41 +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
@@ -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" }
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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"),
|
||||
|
||||
@@ -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(())
|
||||
}
|
||||
|
||||
@@ -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());
|
||||
|
||||
Reference in New Issue
Block a user