Introduce notion of finality to substrate (#760)

* finalization for in_mem

* fetch last finalized block

* pruning: use canonical term instead of final

* finalize blocks in full node

* begin to port light client DB

* add tree-route

* keep number index consistent in full nodes

* fix tests

* disable cache and finish porting light client

* add AsMut to system module

* final leaf is always best

* fix all tests

* Fix comment and trace

* removed unused Into call

* add comment on behavior of `finalize_block`
This commit is contained in:
Robert Habermeier
2018-09-21 15:56:21 +02:00
committed by Gav Wood
parent 28cc4d0fd6
commit b7d095a2e0
19 changed files with 976 additions and 370 deletions
+28 -1
View File
@@ -27,6 +27,27 @@ use patricia_trie::NodeCodec;
use hashdb::Hasher;
use memorydb::MemoryDB;
/// State of a new block.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum NewBlockState {
/// Normal block.
Normal,
/// New best block.
Best,
/// Newly finalized block (implicitly best).
Final,
}
impl NewBlockState {
/// Whether this block is the new best block.
pub fn is_best(self) -> bool {
match self {
NewBlockState::Best | NewBlockState::Final => true,
NewBlockState::Normal => false,
}
}
}
/// Block insertion operation. Keeps hold if the inserted block state and data.
pub trait BlockImportOperation<Block, H, C>
where
@@ -45,7 +66,7 @@ where
header: Block::Header,
body: Option<Vec<Block::Extrinsic>>,
justification: Option<Justification<Block::Hash>>,
is_new_best: bool
state: NewBlockState,
) -> error::Result<()>;
/// Append authorities set to the transaction. This is a set of parent block (set which
@@ -87,6 +108,12 @@ where
fn begin_operation(&self, block: BlockId<Block>) -> error::Result<Self::BlockImportOperation>;
/// Commit block insertion.
fn commit_operation(&self, transaction: Self::BlockImportOperation) -> error::Result<()>;
/// Finalize block with given Id. This should also implicitly finalize all ancestors.
///
/// If the finalized block is not an ancestor of the current "best block", then
/// the chain will be implicitly reorganized to the best chain containing the newly
/// finalized block.
fn finalize_block(&self, block: BlockId<Block>) -> error::Result<()>;
/// Returns reference to blockchain backend.
fn blockchain(&self) -> &Self::Blockchain;
/// Returns reference to changes trie storage.
+2
View File
@@ -48,6 +48,8 @@ pub trait Backend<Block: BlockT>: HeaderBackend<Block> {
fn body(&self, id: BlockId<Block>) -> Result<Option<Vec<<Block as BlockT>::Extrinsic>>>;
/// Get block justification. Returns `None` if justification does not exist.
fn justification(&self, id: BlockId<Block>) -> Result<Option<Justification<Block::Hash>>>;
/// Get last finalized block hash.
fn last_finalized(&self) -> Result<Block::Hash>;
/// Returns data cache reference, if it is enabled on this backend.
fn cache(&self) -> Option<&Cache<Block>>;
+43 -5
View File
@@ -195,7 +195,12 @@ impl<B, E, Block> Client<B, E, Block> where
info!("Initialising Genesis block/state (state: {}, header-hash: {})", genesis_block.header().state_root(), genesis_block.header().hash());
let mut op = backend.begin_operation(BlockId::Hash(Default::default()))?;
op.reset_storage(genesis_storage.into_iter())?;
op.set_block_data(genesis_block.deconstruct().0, Some(vec![]), None, true)?;
op.set_block_data(
genesis_block.deconstruct().0,
Some(vec![]),
None,
::backend::NewBlockState::Final
)?;
backend.commit_operation(op)?;
}
Ok(Client {
@@ -383,6 +388,7 @@ impl<B, E, Block> Client<B, E, Block> where
origin: BlockOrigin,
header: JustifiedHeader<Block>,
body: Option<Vec<<Block as BlockT>::Extrinsic>>,
finalized: bool,
) -> error::Result<ImportResult> {
let (header, justification, authorities) = header.into_inner();
let parent_hash = header.parent_hash().clone();
@@ -394,7 +400,17 @@ impl<B, E, Block> Client<B, E, Block> where
let _import_lock = self.import_lock.lock();
let height: u64 = header.number().as_();
*self.importing_block.write() = Some(hash);
let result = self.execute_and_import_block(origin, hash, header, justification, body, authorities);
let result = self.execute_and_import_block(
origin,
hash,
header,
justification,
body,
authorities,
finalized
);
*self.importing_block.write() = None;
telemetry!("block.import";
"height" => height,
@@ -412,6 +428,7 @@ impl<B, E, Block> Client<B, E, Block> where
justification: bft::Justification<Block::Hash>,
body: Option<Vec<Block::Extrinsic>>,
authorities: Vec<AuthorityId>,
finalized: bool,
) -> error::Result<ImportResult> {
let parent_hash = header.parent_hash().clone();
match self.backend.blockchain().status(BlockId::Hash(hash))? {
@@ -454,9 +471,24 @@ impl<B, E, Block> Client<B, E, Block> where
};
let is_new_best = header.number() == &(self.backend.blockchain().info()?.best_number + One::one());
let leaf_state = if finalized {
::backend::NewBlockState::Final
} else if is_new_best {
::backend::NewBlockState::Best
} else {
::backend::NewBlockState::Normal
};
trace!("Imported {}, (#{}), best={}, origin={:?}", hash, header.number(), is_new_best, origin);
let unchecked: bft::UncheckedJustification<_> = justification.uncheck().into();
transaction.set_block_data(header.clone(), body, Some(unchecked.into()), is_new_best)?;
transaction.set_block_data(
header.clone(),
body,
Some(unchecked.into()),
leaf_state,
)?;
transaction.update_authorities(authorities);
if let Some(storage_update) = storage_update {
transaction.update_storage(storage_update)?;
@@ -601,7 +633,7 @@ impl<B, E, Block> bft::BlockImport<Block> for Client<B, E, Block>
&self,
block: Block,
justification: ::bft::Justification<Block::Hash>,
authorities: &[AuthorityId]
authorities: &[AuthorityId],
) -> bool {
let (header, extrinsics) = block.deconstruct();
let justified_header = JustifiedHeader {
@@ -610,7 +642,13 @@ impl<B, E, Block> bft::BlockImport<Block> for Client<B, E, Block>
authorities: authorities.to_vec(),
};
self.import_block(BlockOrigin::ConsensusBroadcast, justified_header, Some(extrinsics)).is_ok()
// TODO [rob]: non-instant finality.
self.import_block(
BlockOrigin::ConsensusBroadcast,
justified_header,
Some(extrinsics),
true
).is_ok()
}
}
+50 -14
View File
@@ -20,14 +20,14 @@ use std::collections::HashMap;
use std::sync::Arc;
use parking_lot::RwLock;
use error;
use backend;
use backend::{self, NewBlockState};
use light;
use primitives::AuthorityId;
use runtime_primitives::generic::BlockId;
use runtime_primitives::traits::{Block as BlockT, Header as HeaderT, Zero,
NumberFor, As, Digest, DigestItem};
use runtime_primitives::bft::Justification;
use blockchain::{self, BlockStatus};
use blockchain::{self, BlockStatus, HeaderBackend};
use state_machine::backend::{Backend as StateBackend, InMemory};
use state_machine::InMemoryChangesTrieStorage;
use patricia_trie::NodeCodec;
@@ -37,7 +37,7 @@ use memorydb::MemoryDB;
struct PendingBlock<B: BlockT> {
block: StoredBlock<B>,
is_best: bool,
state: NewBlockState,
}
#[derive(PartialEq, Eq, Clone)]
@@ -91,6 +91,7 @@ struct BlockchainStorage<Block: BlockT> {
hashes: HashMap<NumberFor<Block>, Block::Hash>,
best_hash: Block::Hash,
best_number: NumberFor<Block>,
finalized_hash: Block::Hash,
genesis_hash: Block::Hash,
cht_roots: HashMap<NumberFor<Block>, Block::Hash>,
}
@@ -136,6 +137,7 @@ impl<Block: BlockT> Blockchain<Block> {
hashes: HashMap::new(),
best_hash: Default::default(),
best_number: Zero::zero(),
finalized_hash: Default::default(),
genesis_hash: Default::default(),
cht_roots: HashMap::new(),
}));
@@ -155,16 +157,21 @@ impl<Block: BlockT> Blockchain<Block> {
header: <Block as BlockT>::Header,
justification: Option<Justification<Block::Hash>>,
body: Option<Vec<<Block as BlockT>::Extrinsic>>,
is_new_best: bool
new_state: NewBlockState,
) {
let number = header.number().clone();
let mut storage = self.storage.write();
storage.blocks.insert(hash.clone(), StoredBlock::new(header, body, justification));
storage.hashes.insert(number, hash.clone());
if is_new_best {
if new_state.is_best() {
storage.best_hash = hash.clone();
storage.best_number = number.clone();
}
if let NewBlockState::Final = new_state {
storage.finalized_hash = hash;
}
if number == Zero::zero() {
storage.genesis_hash = hash;
}
@@ -189,9 +196,19 @@ impl<Block: BlockT> Blockchain<Block> {
pub fn insert_cht_root(&self, block: NumberFor<Block>, cht_root: Block::Hash) {
self.storage.write().cht_roots.insert(block, cht_root);
}
fn finalize_header(&self, id: BlockId<Block>) -> error::Result<()> {
let hash = match self.header(id)? {
Some(h) => h.hash(),
None => return Err(error::ErrorKind::UnknownBlock(format!("{}", id)).into()),
};
self.storage.write().finalized_hash = hash;
Ok(())
}
}
impl<Block: BlockT> blockchain::HeaderBackend<Block> for Blockchain<Block> {
impl<Block: BlockT> HeaderBackend<Block> for Blockchain<Block> {
fn header(&self, id: BlockId<Block>) -> error::Result<Option<<Block as BlockT>::Header>> {
Ok(self.id(id).and_then(|hash| {
self.storage.read().blocks.get(&hash).map(|b| b.header().clone())
@@ -238,6 +255,10 @@ impl<Block: BlockT> blockchain::Backend<Block> for Blockchain<Block> {
))
}
fn last_finalized(&self) -> error::Result<Block::Hash> {
Ok(self.storage.read().finalized_hash.clone())
}
fn cache(&self) -> Option<&blockchain::Cache<Block>> {
Some(&self.cache)
}
@@ -249,19 +270,28 @@ impl<Block: BlockT> light::blockchain::Storage<Block> for Blockchain<Block>
{
fn import_header(
&self,
is_new_best: bool,
header: Block::Header,
authorities: Option<Vec<AuthorityId>>
authorities: Option<Vec<AuthorityId>>,
state: NewBlockState,
) -> error::Result<()> {
let hash = header.hash();
let parent_hash = *header.parent_hash();
self.insert(hash, header, None, None, is_new_best);
if is_new_best {
self.insert(hash, header, None, None, state);
if state.is_best() {
self.cache.insert(parent_hash, authorities);
}
Ok(())
}
fn last_finalized(&self) -> error::Result<Block::Hash> {
Ok(self.storage.read().finalized_hash.clone())
}
fn finalize_header(&self, id: BlockId<Block>) -> error::Result<()> {
Blockchain::finalize_header(self, id)
}
fn cht_root(&self, _cht_size: u64, block: NumberFor<Block>) -> error::Result<Block::Hash> {
self.storage.read().cht_roots.get(&block).cloned()
.ok_or_else(|| error::ErrorKind::Backend(format!("CHT for block {} not exists", block)).into())
@@ -299,12 +329,12 @@ where
header: <Block as BlockT>::Header,
body: Option<Vec<<Block as BlockT>::Extrinsic>>,
justification: Option<Justification<Block::Hash>>,
is_new_best: bool
state: NewBlockState,
) -> error::Result<()> {
assert!(self.pending_block.is_none(), "Only one block per operation is allowed");
self.pending_block = Some(PendingBlock {
block: StoredBlock::new(header, body, justification),
is_best: is_new_best,
state,
});
Ok(())
}
@@ -395,6 +425,7 @@ where
let parent_hash = *header.parent_hash();
self.states.write().insert(hash, operation.new_state.unwrap_or_else(|| old_state.clone()));
let changes_trie_root = header.digest().logs().iter()
.find(|log| log.as_changes_trie_root().is_some())
.and_then(DigestItem::as_changes_trie_root)
@@ -405,15 +436,20 @@ where
self.changes_trie_storage.insert(header.number().as_(), changes_trie_root, changes_trie_update);
}
}
self.blockchain.insert(hash, header, justification, body, pending_block.is_best);
self.blockchain.insert(hash, header, justification, body, pending_block.state);
// dumb implementation - store value for each block
if pending_block.is_best {
if pending_block.state.is_best() {
self.blockchain.cache.insert(parent_hash, operation.pending_authorities);
}
}
Ok(())
}
fn finalize_block(&self, block: BlockId<Block>) -> error::Result<()> {
self.blockchain.finalize_header(block)
}
fn blockchain(&self) -> &Self::Blockchain {
&self.blockchain
}
+14 -6
View File
@@ -26,7 +26,7 @@ use runtime_primitives::{bft::Justification, generic::BlockId};
use runtime_primitives::traits::{Block as BlockT, NumberFor};
use state_machine::{Backend as StateBackend, InMemoryChangesTrieStorage, TrieBackend};
use backend::{Backend as ClientBackend, BlockImportOperation, RemoteBackend};
use backend::{Backend as ClientBackend, BlockImportOperation, RemoteBackend, NewBlockState};
use blockchain::HeaderBackend as BlockchainHeaderBackend;
use error::{Error as ClientError, ErrorKind as ClientErrorKind, Result as ClientResult};
use light::blockchain::{Blockchain, Storage as BlockchainStorage};
@@ -43,9 +43,9 @@ pub struct Backend<S, F> {
/// Light block (header and justification) import operation.
pub struct ImportOperation<Block: BlockT, S, F> {
is_new_best: bool,
header: Option<Block::Header>,
authorities: Option<Vec<AuthorityId>>,
leaf_state: NewBlockState,
_phantom: ::std::marker::PhantomData<(S, F)>,
}
@@ -84,16 +84,24 @@ impl<S, F, Block, H, C> ClientBackend<Block, H, C> for Backend<S, F> where
fn begin_operation(&self, _block: BlockId<Block>) -> ClientResult<Self::BlockImportOperation> {
Ok(ImportOperation {
is_new_best: false,
header: None,
authorities: None,
leaf_state: NewBlockState::Normal,
_phantom: Default::default(),
})
}
fn commit_operation(&self, operation: Self::BlockImportOperation) -> ClientResult<()> {
let header = operation.header.expect("commit is called after set_block_data; set_block_data sets header; qed");
self.blockchain.storage().import_header(operation.is_new_best, header, operation.authorities)
self.blockchain.storage().import_header(
header,
operation.authorities,
operation.leaf_state,
)
}
fn finalize_block(&self, block: BlockId<Block>) -> ClientResult<()> {
self.blockchain.storage().finalize_header(block)
}
fn blockchain(&self) -> &Blockchain<S, F> {
@@ -153,9 +161,9 @@ where
header: Block::Header,
_body: Option<Vec<Block::Extrinsic>>,
_justification: Option<Justification<Block::Hash>>,
is_new_best: bool
state: NewBlockState,
) -> ClientResult<()> {
self.is_new_best = is_new_best;
self.leaf_state = state;
self.header = Some(header);
Ok(())
}
+14 -3
View File
@@ -25,6 +25,7 @@ use primitives::AuthorityId;
use runtime_primitives::{bft::Justification, generic::BlockId};
use runtime_primitives::traits::{Block as BlockT, Header as HeaderT, NumberFor, Zero};
use backend::NewBlockState;
use blockchain::{Backend as BlockchainBackend, BlockStatus, Cache as BlockchainCache,
HeaderBackend as BlockchainHeaderBackend, Info as BlockchainInfo};
use cht;
@@ -33,14 +34,20 @@ use light::fetcher::{Fetcher, RemoteHeaderRequest};
/// Light client blockchain storage.
pub trait Storage<Block: BlockT>: BlockchainHeaderBackend<Block> {
/// Store new header.
/// Store new header. Should refuse to revert any finalized blocks.
fn import_header(
&self,
is_new_best: bool,
header: Block::Header,
authorities: Option<Vec<AuthorityId>>
authorities: Option<Vec<AuthorityId>>,
state: NewBlockState,
) -> ClientResult<()>;
/// Mark historic header as finalized.
fn finalize_header(&self, block: BlockId<Block>) -> ClientResult<()>;
/// Get last finalized header.
fn last_finalized(&self) -> ClientResult<Block::Hash>;
/// Get CHT root for given block. Fails if the block is not pruned (not a part of any CHT).
fn cht_root(&self, cht_size: u64, block: NumberFor<Block>) -> ClientResult<Block::Hash>;
@@ -136,6 +143,10 @@ impl<S, F, Block> BlockchainBackend<Block> for Blockchain<S, F> where Block: Blo
Ok(None)
}
fn last_finalized(&self) -> ClientResult<Block::Hash> {
self.storage.last_finalized()
}
fn cache(&self) -> Option<&BlockchainCache<Block>> {
self.storage.cache()
}
+7 -1
View File
@@ -230,7 +230,13 @@ pub mod tests {
// check remote read proof locally
let local_storage = InMemoryBlockchain::<Block>::new();
local_storage.insert(remote_block_hash, remote_block_header.clone(), None, None, true);
local_storage.insert(
remote_block_hash,
remote_block_header.clone(),
None,
None,
::backend::NewBlockState::Final,
);
let local_executor = test_client::LocalExecutor::new();
let local_checker = LightDataChecker::new(local_executor);
(local_checker, remote_block_header, remote_read_proof, authorities_len)