mirror of
https://github.com/pezkuwichain/pezkuwi-subxt.git
synced 2026-06-30 10:47:23 +00:00
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:
committed by
Gav Wood
parent
28cc4d0fd6
commit
b7d095a2e0
@@ -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.
|
||||
|
||||
@@ -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>>;
|
||||
|
||||
@@ -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()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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(())
|
||||
}
|
||||
|
||||
@@ -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()
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
|
||||
Reference in New Issue
Block a user