Storing multiple Justifications per block (#7640)

* primitives/runtime: initial changes on supporting multiple Justifications

* primitives/runtime: make Justifications strongly typed

* Encode/decode Justifications

* primitives/runtime: add Justification type

* backend: apply_finality and finalize_block takes a single Justification

* manual-seal: create engine id and let rpc take encoded justification

* backend: skeleton functions for appending justifications

* backend: initial implementation append_justification

Initial implementation of append_justification on the Backend trait, and also remove unused skeleton
functions for append_justificaton on Finaziler trait.
k

* backend: guard against duplicate consensus engine id

* client/db: add check for block finality

* client/api: add append_justification to in_mem db

* client/light: add no-op append_justification

* network: fix decode call for Justification

* network: only send a single Justification in BlockData

* network: minor comment update

* protocol: update field names to distinguish single justification

* client: further field renames to plural

* client: update function names to plural justifications

* client/db: upgrade existing database for new format

* network: remove dependency on grandpa crate

* db: fix check for finalized block

* grandpa: check for multiple grandpa justifications hwne importing

* backend: update Finalizer trait to take multiple Justifications

* db: remove debugging statements in migration code

* manual-seal: update note about engine id

* db: fix check for finalized block

* client: update variable name to reflect it is now plural

* grandpa: fix incorrect empty Justications in test

* primitives: make Justifications opaque to avoid being empty

* network: fix detecting empty Justification

* runtime: doc strings for Justifications functions

* runtime: add into_justifications

* primitives: check for duplicates in when adding to Justifications

* network/test: use real grandpa engine id in test

* client: fix reviewer comments

* primitives: rename Justifications::push to append

* backend: revert changes to Finalizer trait

* backend: revert mark_finalized

* backend: revert changes to finalize_block

* backend: revert finalized_blocks

* db: add a quick early return for performance

* client: minor reviewer comments

* service/test: use local ConsensusEngineId

* network: add link to issue for sending multiple Justifications

* Apply suggestions from code review

Co-authored-by: Pierre Krieger <pierre.krieger1708@gmail.com>

* Apply suggestions from code review

Co-authored-by: Pierre Krieger <pierre.krieger1708@gmail.com>

* network: tweaks to review suggestions

* network: revert change to BlockData for backwards compatibility

* Apply suggestion from code review

Co-authored-by: Pierre Krieger <pierre.krieger1708@gmail.com>

* Apply suggestions from code review

Co-authored-by: André Silva <123550+andresilva@users.noreply.github.com>

* primitives: update doc comment for Justifications

* client/db/upgrade: avoid grandpa crate dependency

* consensus: revert to single Justification for import_justification

* primitives: improve justifications docs

* style cleanups

* use and_then

* client: rename JUSTIFICATIONS db column

* network: revert to using FRNK in network-test

Co-authored-by: Pierre Krieger <pierre.krieger1708@gmail.com>
Co-authored-by: André Silva <123550+andresilva@users.noreply.github.com>
Co-authored-by: André Silva <andrerfosilva@gmail.com>
This commit is contained in:
Jon Häggblad
2021-03-17 22:18:16 +01:00
committed by GitHub
parent c7d32ba9a6
commit 0d6884b919
43 changed files with 635 additions and 270 deletions
+12 -4
View File
@@ -22,7 +22,7 @@ use std::sync::Arc;
use std::collections::{HashMap, HashSet};
use sp_core::ChangesTrieConfigurationRange;
use sp_core::offchain::OffchainStorage;
use sp_runtime::{generic::BlockId, Justification, Storage};
use sp_runtime::{generic::BlockId, Justification, Justifications, Storage};
use sp_runtime::traits::{Block as BlockT, NumberFor, HashFor};
use sp_state_machine::{
ChangesTrieState, ChangesTrieStorage as StateChangesTrieStorage, ChangesTrieTransaction,
@@ -148,7 +148,7 @@ pub trait BlockImportOperation<Block: BlockT> {
&mut self,
header: Block::Header,
body: Option<Vec<Block::Extrinsic>>,
justification: Option<Justification>,
justifications: Option<Justifications>,
state: NewBlockState,
) -> sp_blockchain::Result<()>;
@@ -197,6 +197,7 @@ pub trait BlockImportOperation<Block: BlockT> {
id: BlockId<Block>,
justification: Option<Justification>,
) -> sp_blockchain::Result<()>;
/// Mark a block as new head. If both block import and set head are specified, set head
/// overrides block import's best block rule.
fn mark_head(&mut self, id: BlockId<Block>) -> sp_blockchain::Result<()>;
@@ -230,7 +231,6 @@ pub trait Finalizer<Block: BlockT, B: Backend<Block>> {
notify: bool,
) -> sp_blockchain::Result<()>;
/// Finalize a block.
///
/// This will implicitly finalize all blocks up to it and
@@ -250,7 +250,6 @@ pub trait Finalizer<Block: BlockT, B: Backend<Block>> {
justification: Option<Justification>,
notify: bool,
) -> sp_blockchain::Result<()>;
}
/// Provides access to an auxiliary database.
@@ -432,6 +431,15 @@ pub trait Backend<Block: BlockT>: AuxStore + Send + Sync {
justification: Option<Justification>,
) -> sp_blockchain::Result<()>;
/// Append justification to the block with the given Id.
///
/// This should only be called for blocks that are already finalized.
fn append_justification(
&self,
block: BlockId<Block>,
justification: Justification,
) -> sp_blockchain::Result<()>;
/// Returns reference to blockchain backend.
fn blockchain(&self) -> &Self::Blockchain;
+3 -3
View File
@@ -23,7 +23,7 @@ use sp_core::storage::StorageKey;
use sp_runtime::{
traits::{Block as BlockT, NumberFor},
generic::{BlockId, SignedBlock},
Justification,
Justifications,
};
use sp_consensus::BlockOrigin;
@@ -90,8 +90,8 @@ pub trait BlockBackend<Block: BlockT> {
/// Get block status.
fn block_status(&self, id: &BlockId<Block>) -> sp_blockchain::Result<sp_consensus::BlockStatus>;
/// Get block justification set by id.
fn justification(&self, id: &BlockId<Block>) -> sp_blockchain::Result<Option<Justification>>;
/// Get block justifications for the block with the given id.
fn justifications(&self, id: &BlockId<Block>) -> sp_blockchain::Result<Option<Justifications>>;
/// Get block hash by number.
fn block_hash(&self, number: NumberFor<Block>) -> sp_blockchain::Result<Option<Block::Hash>>;
+111 -14
View File
@@ -27,7 +27,7 @@ use sp_core::{
};
use sp_runtime::generic::BlockId;
use sp_runtime::traits::{Block as BlockT, Header as HeaderT, Zero, NumberFor, HashFor};
use sp_runtime::{Justification, Storage};
use sp_runtime::{Justification, Justifications, Storage};
use sp_state_machine::{
ChangesTrieTransaction, InMemoryBackend, Backend as StateBackend, StorageCollection,
ChildStorageCollection,
@@ -51,12 +51,12 @@ struct PendingBlock<B: BlockT> {
#[derive(PartialEq, Eq, Clone)]
enum StoredBlock<B: BlockT> {
Header(B::Header, Option<Justification>),
Full(B, Option<Justification>),
Header(B::Header, Option<Justifications>),
Full(B, Option<Justifications>),
}
impl<B: BlockT> StoredBlock<B> {
fn new(header: B::Header, body: Option<Vec<B::Extrinsic>>, just: Option<Justification>) -> Self {
fn new(header: B::Header, body: Option<Vec<B::Extrinsic>>, just: Option<Justifications>) -> Self {
match body {
Some(body) => StoredBlock::Full(B::new(header, body), just),
None => StoredBlock::Header(header, just),
@@ -70,7 +70,7 @@ impl<B: BlockT> StoredBlock<B> {
}
}
fn justification(&self) -> Option<&Justification> {
fn justifications(&self) -> Option<&Justifications> {
match *self {
StoredBlock::Header(_, ref j) | StoredBlock::Full(_, ref j) => j.as_ref()
}
@@ -83,7 +83,7 @@ impl<B: BlockT> StoredBlock<B> {
}
}
fn into_inner(self) -> (B::Header, Option<Vec<B::Extrinsic>>, Option<Justification>) {
fn into_inner(self) -> (B::Header, Option<Vec<B::Extrinsic>>, Option<Justifications>) {
match self {
StoredBlock::Header(header, just) => (header, None, just),
StoredBlock::Full(block, just) => {
@@ -164,7 +164,7 @@ impl<Block: BlockT> Blockchain<Block> {
&self,
hash: Block::Hash,
header: <Block as BlockT>::Header,
justification: Option<Justification>,
justifications: Option<Justifications>,
body: Option<Vec<<Block as BlockT>::Extrinsic>>,
new_state: NewBlockState,
) -> sp_blockchain::Result<()> {
@@ -176,7 +176,7 @@ impl<Block: BlockT> Blockchain<Block> {
{
let mut storage = self.storage.write();
storage.leaves.import(hash.clone(), number.clone(), header.parent_hash().clone());
storage.blocks.insert(hash.clone(), StoredBlock::new(header, body, justification));
storage.blocks.insert(hash.clone(), StoredBlock::new(header, body, justifications));
if let NewBlockState::Final = new_state {
storage.finalized_hash = hash;
@@ -285,16 +285,44 @@ impl<Block: BlockT> Blockchain<Block> {
let block = storage.blocks.get_mut(&hash)
.expect("hash was fetched from a block in the db; qed");
let block_justification = match block {
let block_justifications = match block {
StoredBlock::Header(_, ref mut j) | StoredBlock::Full(_, ref mut j) => j
};
*block_justification = justification;
*block_justifications = justification.map(Justifications::from);
}
Ok(())
}
fn append_justification(&self, id: BlockId<Block>, justification: Justification)
-> sp_blockchain::Result<()>
{
let hash = self.expect_block_hash_from_id(&id)?;
let mut storage = self.storage.write();
let block = storage
.blocks
.get_mut(&hash)
.expect("hash was fetched from a block in the db; qed");
let block_justifications = match block {
StoredBlock::Header(_, ref mut j) | StoredBlock::Full(_, ref mut j) => j
};
if let Some(stored_justifications) = block_justifications {
if !stored_justifications.append(justification) {
return Err(sp_blockchain::Error::BadJustification(
"Duplicate consensus engine ID".into()
));
}
} else {
*block_justifications = Some(Justifications::from(justification));
};
Ok(())
}
fn write_aux(&self, ops: Vec<(Vec<u8>, Option<Vec<u8>>)>) {
let mut storage = self.storage.write();
for (k, v) in ops {
@@ -365,9 +393,9 @@ impl<Block: BlockT> blockchain::Backend<Block> for Blockchain<Block> {
}))
}
fn justification(&self, id: BlockId<Block>) -> sp_blockchain::Result<Option<Justification>> {
fn justifications(&self, id: BlockId<Block>) -> sp_blockchain::Result<Option<Justifications>> {
Ok(self.id(id).and_then(|hash| self.storage.read().blocks.get(&hash).and_then(|b|
b.justification().map(|x| x.clone()))
b.justifications().map(|x| x.clone()))
))
}
@@ -508,12 +536,12 @@ impl<Block: BlockT> backend::BlockImportOperation<Block> for BlockImportOperatio
&mut self,
header: <Block as BlockT>::Header,
body: Option<Vec<<Block as BlockT>::Extrinsic>>,
justification: Option<Justification>,
justifications: Option<Justifications>,
state: NewBlockState,
) -> sp_blockchain::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),
block: StoredBlock::new(header, body, justifications),
state,
});
Ok(())
@@ -696,6 +724,14 @@ impl<Block: BlockT> backend::Backend<Block> for Backend<Block> where Block::Hash
self.blockchain.finalize_header(block, justification)
}
fn append_justification(
&self,
block: BlockId<Block>,
justification: Justification,
) -> sp_blockchain::Result<()> {
self.blockchain.append_justification(block, justification)
}
fn blockchain(&self) -> &Self::Blockchain {
&self.blockchain
}
@@ -766,3 +802,64 @@ pub fn check_genesis_storage(storage: &Storage) -> sp_blockchain::Result<()> {
Ok(())
}
#[cfg(test)]
mod tests {
use crate::{NewBlockState, in_mem::Blockchain};
use sp_api::{BlockId, HeaderT};
use sp_runtime::{ConsensusEngineId, Justifications};
use sp_blockchain::Backend;
use substrate_test_runtime::{Block, Header, H256};
pub const ID1: ConsensusEngineId = *b"TST1";
pub const ID2: ConsensusEngineId = *b"TST2";
fn header(number: u64) -> Header {
let parent_hash = match number {
0 => Default::default(),
_ => header(number - 1).hash(),
};
Header::new(number, H256::from_low_u64_be(0), H256::from_low_u64_be(0), parent_hash, Default::default())
}
fn test_blockchain() -> Blockchain<Block> {
let blockchain = Blockchain::<Block>::new();
let just0 = Some(Justifications::from((ID1, vec![0])));
let just1 = Some(Justifications::from((ID1, vec![1])));
let just2 = None;
let just3 = Some(Justifications::from((ID1, vec![3])));
blockchain.insert(header(0).hash(), header(0), just0, None, NewBlockState::Final).unwrap();
blockchain.insert(header(1).hash(), header(1), just1, None, NewBlockState::Final).unwrap();
blockchain.insert(header(2).hash(), header(2), just2, None, NewBlockState::Best).unwrap();
blockchain.insert(header(3).hash(), header(3), just3, None, NewBlockState::Final).unwrap();
blockchain
}
#[test]
fn append_and_retrieve_justifications() {
let blockchain = test_blockchain();
let last_finalized = blockchain.last_finalized().unwrap();
let block = BlockId::Hash(last_finalized);
blockchain.append_justification(block, (ID2, vec![4])).unwrap();
let justifications = {
let mut just = Justifications::from((ID1, vec![3]));
just.append((ID2, vec![4]));
just
};
assert_eq!(blockchain.justifications(block).unwrap(), Some(justifications));
}
#[test]
fn store_duplicate_justifications_is_forbidden() {
let blockchain = test_blockchain();
let last_finalized = blockchain.last_finalized().unwrap();
let block = BlockId::Hash(last_finalized);
blockchain.append_justification(block, (ID2, vec![0])).unwrap();
assert!(matches!(
blockchain.append_justification(block, (ID2, vec![1])),
Err(sp_blockchain::Error::BadJustification(_)),
));
}
}
@@ -39,7 +39,7 @@ use sp_consensus::{
use sc_client_api::{backend::AuxStore, BlockOf};
use sp_blockchain::{well_known_cache_keys::{self, Id as CacheKeyId}, ProvideCache, HeaderBackend};
use sp_block_builder::BlockBuilder as BlockBuilderApi;
use sp_runtime::{generic::{BlockId, OpaqueDigestItemId}, Justification};
use sp_runtime::{generic::{BlockId, OpaqueDigestItemId}, Justifications};
use sp_runtime::traits::{Block as BlockT, Header, DigestItemFor, Zero};
use sp_api::ProvideRuntimeApi;
use sp_core::crypto::Pair;
@@ -238,7 +238,7 @@ impl<B: BlockT, C, P, CAW> Verifier<B> for AuraVerifier<C, P, CAW> where
&mut self,
origin: BlockOrigin,
header: B::Header,
justification: Option<Justification>,
justifications: Option<Justifications>,
mut body: Option<Vec<B::Extrinsic>>,
) -> Result<(BlockImportParams<B, ()>, Option<Vec<(CacheKeyId, Vec<u8>)>>), String> {
let mut inherent_data = self.inherent_data_providers
@@ -317,7 +317,7 @@ impl<B: BlockT, C, P, CAW> Verifier<B> for AuraVerifier<C, P, CAW> where
let mut import_block = BlockImportParams::new(origin, pre_header);
import_block.post_digests.push(seal);
import_block.body = body;
import_block.justification = justification;
import_block.justifications = justifications;
import_block.fork_choice = Some(ForkChoiceStrategy::LongestChain);
import_block.post_hash = Some(hash);
+5 -5
View File
@@ -84,7 +84,7 @@ use sp_core::crypto::Public;
use sp_application_crypto::AppKey;
use sp_keystore::{SyncCryptoStorePtr, SyncCryptoStore};
use sp_runtime::{
generic::{BlockId, OpaqueDigestItemId}, Justification,
generic::{BlockId, OpaqueDigestItemId}, Justifications,
traits::{Block as BlockT, Header, DigestItemFor, Zero},
};
use sp_api::{ProvideRuntimeApi, NumberFor};
@@ -1097,15 +1097,15 @@ where
&mut self,
origin: BlockOrigin,
header: Block::Header,
justification: Option<Justification>,
justifications: Option<Justifications>,
mut body: Option<Vec<Block::Extrinsic>>,
) -> Result<(BlockImportParams<Block, ()>, Option<Vec<(CacheKeyId, Vec<u8>)>>), String> {
trace!(
target: "babe",
"Verifying origin: {:?} header: {:?} justification: {:?} body: {:?}",
"Verifying origin: {:?} header: {:?} justification(s): {:?} body: {:?}",
origin,
header,
justification,
justifications,
body,
);
@@ -1194,7 +1194,7 @@ where
let mut import_block = BlockImportParams::new(origin, pre_header);
import_block.post_digests.push(verified_info.seal);
import_block.body = body;
import_block.justification = justification;
import_block.justifications = justifications;
import_block.intermediates.insert(
Cow::from(INTERMEDIATE_KEY),
Box::new(BabeIntermediate::<Block> { epoch_descriptor }) as Box<dyn Any>,
+2 -2
View File
@@ -235,12 +235,12 @@ impl Verifier<TestBlock> for TestVerifier {
&mut self,
origin: BlockOrigin,
mut header: TestHeader,
justification: Option<Justification>,
justifications: Option<Justifications>,
body: Option<Vec<TestExtrinsic>>,
) -> Result<(BlockImportParams<TestBlock, ()>, Option<Vec<(CacheKeyId, Vec<u8>)>>), String> {
// apply post-sealing mutations (i.e. stripping seal, if desired).
(self.mutator)(&mut header, Stage::PostSeal);
self.inner.verify(origin, header, justification, body)
self.inner.verify(origin, header, justifications, body)
}
}
@@ -27,7 +27,7 @@ use sp_consensus::{
};
use sp_blockchain::HeaderBackend;
use sp_inherents::InherentDataProviders;
use sp_runtime::{traits::Block as BlockT, Justification};
use sp_runtime::{traits::Block as BlockT, Justifications, ConsensusEngineId};
use sc_client_api::backend::{Backend as ClientBackend, Finalizer};
use sc_transaction_pool::txpool;
use std::{sync::Arc, marker::PhantomData};
@@ -49,6 +49,9 @@ pub use self::{
};
use sp_api::{ProvideRuntimeApi, TransactionFor};
/// The `ConsensusEngineId` of Manual Seal.
pub const MANUAL_SEAL_ENGINE_ID: ConsensusEngineId = [b'm', b'a', b'n', b'l'];
/// The verifier for the manual seal engine; instantly finalizes.
struct ManualSealVerifier;
@@ -57,11 +60,11 @@ impl<B: BlockT> Verifier<B> for ManualSealVerifier {
&mut self,
origin: BlockOrigin,
header: B::Header,
justification: Option<Justification>,
justifications: Option<Justifications>,
body: Option<Vec<B::Extrinsic>>,
) -> Result<(BlockImportParams<B, ()>, Option<Vec<(CacheKeyId, Vec<u8>)>>), String> {
let mut import_params = BlockImportParams::new(origin, header);
import_params.justification = justification;
import_params.justifications = justifications;
import_params.body = body;
import_params.finalized = false;
import_params.fork_choice = Some(ForkChoiceStrategy::LongestChain);
@@ -193,6 +196,7 @@ pub async fn run_manual_seal<B, BI, CB, E, C, A, SC, CS>(
).await;
}
EngineCommand::FinalizeBlock { hash, sender, justification } => {
let justification = justification.map(|j| (MANUAL_SEAL_ENGINE_ID, j));
finalize_block(
FinalizeBlockParams {
hash,
@@ -28,7 +28,7 @@ use futures::{
SinkExt
};
use serde::{Deserialize, Serialize};
use sp_runtime::Justification;
use sp_runtime::EncodedJustification;
pub use self::gen_client::Client as ManualSealClient;
/// Future's type for jsonrpc
@@ -62,7 +62,7 @@ pub enum EngineCommand<Hash> {
/// sender to report errors/success to the rpc.
sender: Sender<()>,
/// finalization justification
justification: Option<Justification>,
justification: Option<EncodedJustification>,
}
}
@@ -83,7 +83,7 @@ pub trait ManualSealApi<Hash> {
fn finalize_block(
&self,
hash: Hash,
justification: Option<Justification>
justification: Option<EncodedJustification>
) -> FutureResult<bool>;
}
@@ -131,7 +131,7 @@ impl<Hash: Send + 'static> ManualSealApi<Hash> for ManualSeal<Hash> {
Box::new(future.map_err(Error::from).compat())
}
fn finalize_block(&self, hash: Hash, justification: Option<Justification>) -> FutureResult<bool> {
fn finalize_block(&self, hash: Hash, justification: Option<EncodedJustification>) -> FutureResult<bool> {
let mut sink = self.import_block_channel.clone();
let future = async move {
let (sender, receiver) = oneshot::channel();
+3 -3
View File
@@ -44,7 +44,7 @@ use parking_lot::Mutex;
use sc_client_api::{BlockOf, backend::AuxStore, BlockchainEvents};
use sp_blockchain::{HeaderBackend, ProvideCache, well_known_cache_keys::Id as CacheKeyId};
use sp_block_builder::BlockBuilder as BlockBuilderApi;
use sp_runtime::{Justification, RuntimeString};
use sp_runtime::{Justifications, RuntimeString};
use sp_runtime::generic::{BlockId, Digest, DigestItem};
use sp_runtime::traits::{Block as BlockT, Header as HeaderT};
use sp_api::ProvideRuntimeApi;
@@ -457,7 +457,7 @@ impl<B: BlockT, Algorithm> Verifier<B> for PowVerifier<B, Algorithm> where
&mut self,
origin: BlockOrigin,
header: B::Header,
justification: Option<Justification>,
justifications: Option<Justifications>,
body: Option<Vec<B::Extrinsic>>,
) -> Result<(BlockImportParams<B, ()>, Option<Vec<(CacheKeyId, Vec<u8>)>>), String> {
let hash = header.hash();
@@ -470,7 +470,7 @@ impl<B: BlockT, Algorithm> Verifier<B> for PowVerifier<B, Algorithm> where
let mut import_block = BlockImportParams::new(origin, checked_header);
import_block.post_digests.push(seal);
import_block.body = body;
import_block.justification = justification;
import_block.justifications = justifications;
import_block.intermediates.insert(
Cow::from(INTERMEDIATE_KEY),
Box::new(intermediate) as Box<dyn Any>
@@ -955,7 +955,8 @@ mod tests {
let block0 = insert_header_with_configuration_change(&backend, 0, Default::default(), None, config0);
let config1 = Some(ChangesTrieConfiguration::new(2, 6));
let block1 = insert_header_with_configuration_change(&backend, 1, block0, changes(0), config1);
backend.finalize_block(BlockId::Number(1), Some(vec![42])).unwrap();
let just1 = Some((*b"TEST", vec![42]));
backend.finalize_block(BlockId::Number(1), just1).unwrap();
let config2 = Some(ChangesTrieConfiguration::new(2, 7));
let block2 = insert_header_with_configuration_change(&backend, 2, block1, changes(1), config2);
let config2_1 = Some(ChangesTrieConfiguration::new(2, 8));
+100 -16
View File
@@ -57,10 +57,11 @@ use sc_client_api::{
UsageInfo, MemoryInfo, IoInfo, MemorySize,
backend::{NewBlockState, PrunableStateChangesTrieStorage, ProvideChtRoots},
leaves::{LeafSet, FinalizationDisplaced}, cht,
utils::is_descendent_of,
};
use sp_blockchain::{
Result as ClientResult, Error as ClientError,
well_known_cache_keys, HeaderBackend,
well_known_cache_keys, Backend as _, HeaderBackend,
};
use codec::{Decode, Encode};
use hash_db::Prefix;
@@ -70,7 +71,7 @@ use sp_core::{Hasher, ChangesTrieConfiguration};
use sp_core::offchain::OffchainOverlayedChange;
use sp_core::storage::{well_known_keys, ChildInfo};
use sp_arithmetic::traits::Saturating;
use sp_runtime::{generic::{DigestItem, BlockId}, Justification, Storage};
use sp_runtime::{generic::{DigestItem, BlockId}, Justification, Justifications, Storage};
use sp_runtime::traits::{
Block as BlockT, Header as HeaderT, NumberFor, Zero, One, SaturatedConversion, HashFor,
};
@@ -351,7 +352,7 @@ pub(crate) mod columns {
pub const KEY_LOOKUP: u32 = 3;
pub const HEADER: u32 = 4;
pub const BODY: u32 = 5;
pub const JUSTIFICATION: u32 = 6;
pub const JUSTIFICATIONS: u32 = 6;
pub const CHANGES_TRIE: u32 = 7;
pub const AUX: u32 = 8;
/// Offchain workers local storage
@@ -363,7 +364,7 @@ pub(crate) mod columns {
struct PendingBlock<Block: BlockT> {
header: Block::Header,
justification: Option<Justification>,
justifications: Option<Justifications>,
body: Option<Vec<Block::Extrinsic>>,
leaf_state: NewBlockState,
}
@@ -535,8 +536,8 @@ impl<Block: BlockT> sc_client_api::blockchain::Backend<Block> for BlockchainDb<B
}
}
fn justification(&self, id: BlockId<Block>) -> ClientResult<Option<Justification>> {
match read_db(&*self.db, columns::KEY_LOOKUP, columns::JUSTIFICATION, id)? {
fn justifications(&self, id: BlockId<Block>) -> ClientResult<Option<Justifications>> {
match read_db(&*self.db, columns::KEY_LOOKUP, columns::JUSTIFICATIONS, id)? {
Some(justification) => match Decode::decode(&mut &justification[..]) {
Ok(justification) => Ok(Some(justification)),
Err(err) => return Err(sp_blockchain::Error::Backend(
@@ -716,7 +717,7 @@ impl<Block: BlockT> sc_client_api::backend::BlockImportOperation<Block> for Bloc
&mut self,
header: Block::Header,
body: Option<Vec<Block::Extrinsic>>,
justification: Option<Justification>,
justifications: Option<Justifications>,
leaf_state: NewBlockState,
) -> ClientResult<()> {
assert!(self.pending_block.is_none(), "Only one block per operation is allowed");
@@ -726,7 +727,7 @@ impl<Block: BlockT> sc_client_api::backend::BlockImportOperation<Block> for Bloc
self.pending_block = Some(PendingBlock {
header,
body,
justification,
justifications,
leaf_state,
});
Ok(())
@@ -1130,9 +1131,9 @@ impl<Block: BlockT> Backend<Block> {
if let Some(justification) = justification {
transaction.set_from_vec(
columns::JUSTIFICATION,
columns::JUSTIFICATIONS,
&utils::number_and_hash_to_lookup_key(number, hash)?,
justification.encode(),
Justifications::from(justification).encode(),
);
}
Ok((*hash, number, false, true))
@@ -1241,8 +1242,8 @@ impl<Block: BlockT> Backend<Block> {
},
}
}
if let Some(justification) = pending_block.justification {
transaction.set_from_vec(columns::JUSTIFICATION, &lookup_key, justification.encode());
if let Some(justifications) = pending_block.justifications {
transaction.set_from_vec(columns::JUSTIFICATIONS, &lookup_key, justifications.encode());
}
if number.is_zero() {
@@ -1409,7 +1410,7 @@ impl<Block: BlockT> Backend<Block> {
self.storage.db.commit(transaction)?;
// Apply all in-memory state shanges.
// Apply all in-memory state changes.
// Code beyond this point can't fail.
if let Some((
@@ -1668,6 +1669,50 @@ impl<Block: BlockT> sc_client_api::backend::Backend<Block> for Backend<Block> {
Ok(())
}
fn append_justification(
&self,
block: BlockId<Block>,
justification: Justification,
) -> ClientResult<()> {
let mut transaction: Transaction<DbHash> = Transaction::new();
let hash = self.blockchain.expect_block_hash_from_id(&block)?;
let header = self.blockchain.expect_header(block)?;
let number = *header.number();
// Check if the block is finalized first.
let is_descendent_of = is_descendent_of(&self.blockchain, None);
let last_finalized = self.blockchain.last_finalized()?;
// We can do a quick check first, before doing a proper but more expensive check
if number > self.blockchain.info().finalized_number
|| (hash != last_finalized && !is_descendent_of(&hash, &last_finalized)?)
{
return Err(ClientError::NotInFinalizedChain);
}
let justifications =
if let Some(mut stored_justifications) = self.blockchain.justifications(block)? {
if !stored_justifications.append(justification) {
return Err(ClientError::BadJustification(
"Duplicate consensus engine ID".into()
));
}
stored_justifications
} else {
Justifications::from(justification)
};
transaction.set_from_vec(
columns::JUSTIFICATIONS,
&utils::number_and_hash_to_lookup_key(number, hash)?,
justifications.encode(),
);
self.storage.db.commit(transaction)?;
Ok(())
}
fn changes_trie_storage(&self) -> Option<&dyn PrunableStateChangesTrieStorage<Block>> {
Some(&self.changes_tries_storage)
}
@@ -1918,12 +1963,16 @@ pub(crate) mod tests {
use sp_core::H256;
use sc_client_api::backend::{Backend as BTrait, BlockImportOperation as Op};
use sc_client_api::blockchain::Backend as BLBTrait;
use sp_runtime::ConsensusEngineId;
use sp_runtime::testing::{Header, Block as RawBlock, ExtrinsicWrapper};
use sp_runtime::traits::{Hash, BlakeTwo256};
use sp_runtime::generic::DigestItem;
use sp_state_machine::{TrieMut, TrieDBMut};
use sp_blockchain::{lowest_common_ancestor, tree_route};
const CONS0_ENGINE_ID: ConsensusEngineId = *b"CON0";
const CONS1_ENGINE_ID: ConsensusEngineId = *b"CON1";
pub(crate) type Block = RawBlock<ExtrinsicWrapper<u64>>;
pub fn prepare_changes(changes: Vec<(Vec<u8>, Vec<u8>)>) -> (H256, MemoryDB<BlakeTwo256>) {
@@ -2511,12 +2560,47 @@ pub(crate) mod tests {
let block0 = insert_header(&backend, 0, Default::default(), None, Default::default());
let _ = insert_header(&backend, 1, block0, None, Default::default());
let justification = Some(vec![1, 2, 3]);
let justification = Some((CONS0_ENGINE_ID, vec![1, 2, 3]));
backend.finalize_block(BlockId::Number(1), justification.clone()).unwrap();
assert_eq!(
backend.blockchain().justification(BlockId::Number(1)).unwrap(),
justification,
backend.blockchain().justifications(BlockId::Number(1)).unwrap(),
justification.map(Justifications::from),
);
}
#[test]
fn test_append_justification_to_finalized_block() {
use sc_client_api::blockchain::{Backend as BlockChainBackend};
let backend = Backend::<Block>::new_test(10, 10);
let block0 = insert_header(&backend, 0, Default::default(), None, Default::default());
let _ = insert_header(&backend, 1, block0, None, Default::default());
let just0 = (CONS0_ENGINE_ID, vec![1, 2, 3]);
backend.finalize_block(
BlockId::Number(1),
Some(just0.clone().into()),
).unwrap();
let just1 = (CONS1_ENGINE_ID, vec![4, 5]);
backend.append_justification(BlockId::Number(1), just1.clone()).unwrap();
let just2 = (CONS1_ENGINE_ID, vec![6, 7]);
assert!(matches!(
backend.append_justification(BlockId::Number(1), just2),
Err(ClientError::BadJustification(_))
));
let justifications = {
let mut just = Justifications::from(just0);
just.append(just1);
just
};
assert_eq!(
backend.blockchain().justifications(BlockId::Number(1)).unwrap(),
Some(justifications),
);
}
+36 -5
View File
@@ -23,17 +23,19 @@ use std::io::{Read, Write, ErrorKind};
use std::path::{Path, PathBuf};
use sp_runtime::traits::Block as BlockT;
use crate::utils::DatabaseType;
use crate::{columns, utils::DatabaseType};
use kvdb_rocksdb::{Database, DatabaseConfig};
use codec::Encode;
/// Version file name.
const VERSION_FILE_NAME: &'static str = "db_version";
/// Current db version.
const CURRENT_VERSION: u32 = 2;
const CURRENT_VERSION: u32 = 3;
/// Number of columns in v1.
const V1_NUM_COLUMNS: u32 = 11;
const V2_NUM_COLUMNS: u32 = 12;
/// Upgrade database to current version.
pub fn upgrade_db<Block: BlockT>(db_path: &Path, db_type: DatabaseType) -> sp_blockchain::Result<()> {
@@ -42,7 +44,11 @@ pub fn upgrade_db<Block: BlockT>(db_path: &Path, db_type: DatabaseType) -> sp_bl
let db_version = current_version(db_path)?;
match db_version {
0 => Err(sp_blockchain::Error::Backend(format!("Unsupported database version: {}", db_version)))?,
1 => migrate_1_to_2::<Block>(db_path, db_type)?,
1 => {
migrate_1_to_2::<Block>(db_path, db_type)?;
migrate_2_to_3::<Block>(db_path, db_type)?
},
2 => migrate_2_to_3::<Block>(db_path, db_type)?,
CURRENT_VERSION => (),
_ => Err(sp_blockchain::Error::Backend(format!("Future database version: {}", db_version)))?,
}
@@ -62,6 +68,31 @@ fn migrate_1_to_2<Block: BlockT>(db_path: &Path, _db_type: DatabaseType) -> sp_b
db.add_column().map_err(db_err)
}
/// Migration from version2 to version3:
/// - The format of the stored Justification changed to support multiple Justifications.
fn migrate_2_to_3<Block: BlockT>(db_path: &Path, _db_type: DatabaseType) -> sp_blockchain::Result<()> {
let db_path = db_path.to_str()
.ok_or_else(|| sp_blockchain::Error::Backend("Invalid database path".into()))?;
let db_cfg = DatabaseConfig::with_columns(V2_NUM_COLUMNS);
let db = Database::open(&db_cfg, db_path).map_err(db_err)?;
// Get all the keys we need to update
let keys: Vec<_> = db.iter(columns::JUSTIFICATIONS).map(|entry| entry.0).collect();
// Read and update each entry
let mut transaction = db.transaction();
for key in keys {
if let Some(justification) = db.get(columns::JUSTIFICATIONS, &key).map_err(db_err)? {
// Tag each Justification with the hardcoded ID for GRANDPA. Avoid the dependency on the GRANDPA crate
let justifications = sp_runtime::Justifications::from((*b"FRNK", justification));
transaction.put_vec(columns::JUSTIFICATIONS, &key, justifications.encode());
}
}
db.write(transaction).map_err(db_err)?;
Ok(())
}
/// Reads current database version from the file at given path.
/// If the file does not exist returns 0.
fn current_version(path: &Path) -> sp_blockchain::Result<u32> {
@@ -141,8 +172,8 @@ mod tests {
}
#[test]
fn upgrade_from_1_to_2_works() {
for version_from_file in &[None, Some(1)] {
fn upgrade_to_3_works() {
for version_from_file in &[None, Some(1), Some(2)] {
let db_dir = tempfile::TempDir::new().unwrap();
let db_path = db_dir.path();
create_db(db_path, *version_from_file);
@@ -20,7 +20,7 @@ use sc_finality_grandpa::{
find_scheduled_change, AuthoritySetChanges, BlockNumberOps, GrandpaJustification,
};
use sp_blockchain::Backend as BlockchainBackend;
use sp_finality_grandpa::{AuthorityList, SetId};
use sp_finality_grandpa::{AuthorityList, SetId, GRANDPA_ENGINE_ID};
use sp_runtime::{
generic::BlockId,
traits::{Block as BlockT, NumberFor},
@@ -108,11 +108,14 @@ impl<Block: BlockT> WarpSyncProof<Block> {
break;
}
let justification = backend.justification(BlockId::Number(*last_block))?.expect(
"header is last in set and contains standard change signal; \
must have justification; \
qed.",
);
let justification = backend
.justifications(BlockId::Number(*last_block))?
.and_then(|just| just.into_justification(GRANDPA_ENGINE_ID))
.expect(
"header is last in set and contains standard change signal; \
must have justification; \
qed.",
);
let justification = GrandpaJustification::<Block>::decode(&mut &justification[..])?;
@@ -171,6 +174,7 @@ mod tests {
use sc_finality_grandpa::{AuthoritySetChanges, GrandpaJustification};
use sp_blockchain::HeaderBackend;
use sp_consensus::BlockOrigin;
use sp_finality_grandpa::GRANDPA_ENGINE_ID;
use sp_keyring::Ed25519Keyring;
use sp_runtime::{generic::BlockId, traits::Header as _};
use std::sync::Arc;
@@ -272,7 +276,10 @@ mod tests {
let justification = GrandpaJustification::from_commit(&client, 42, commit).unwrap();
client
.finalize_block(BlockId::Hash(target_hash), Some(justification.encode()))
.finalize_block(
BlockId::Hash(target_hash),
Some((GRANDPA_ENGINE_ID, justification.encode()))
)
.unwrap();
authority_set_changes.push((current_set_id, n));
@@ -55,7 +55,7 @@ use crate::justification::GrandpaJustification;
use crate::until_imported::UntilVoteTargetImported;
use crate::voting_rule::VotingRule;
use sp_finality_grandpa::{
AuthorityId, AuthoritySignature, Equivocation, EquivocationProof,
AuthorityId, AuthoritySignature, Equivocation, EquivocationProof, GRANDPA_ENGINE_ID,
GrandpaApi, RoundNumber, SetId,
};
use prometheus_endpoint::{register, Counter, Gauge, PrometheusError, U64};
@@ -1326,10 +1326,13 @@ where
// ideally some handle to a synchronization oracle would be used
// to avoid unconditionally notifying.
client.apply_finality(import_op, BlockId::Hash(hash), justification, true).map_err(|e| {
warn!(target: "afg", "Error applying finality to block {:?}: {:?}", (hash, number), e);
e
})?;
let justification = justification.map(|j| (GRANDPA_ENGINE_ID, j.clone()));
client
.apply_finality(import_op, BlockId::Hash(hash), justification, true)
.map_err(|e| {
warn!(target: "afg", "Error applying finality to block {:?}: {:?}", (hash, number), e);
e
})?;
telemetry!(
telemetry;
CONSENSUS_INFO;
@@ -43,11 +43,11 @@ use finality_grandpa::BlockNumberOps;
use parity_scale_codec::{Encode, Decode};
use sp_blockchain::{Backend as BlockchainBackend, Error as ClientError, Result as ClientResult};
use sp_runtime::{
Justification, generic::BlockId,
EncodedJustification, generic::BlockId,
traits::{NumberFor, Block as BlockT, Header as HeaderT, One},
};
use sc_client_api::backend::Backend;
use sp_finality_grandpa::AuthorityId;
use sp_finality_grandpa::{AuthorityId, GRANDPA_ENGINE_ID};
use crate::authorities::AuthoritySetChanges;
use crate::justification::GrandpaJustification;
@@ -190,8 +190,10 @@ where
// Get the Justification stored at the last block of the set
let last_block_for_set_id = BlockId::Number(last_block_for_set);
let justification =
if let Some(justification) = blockchain.justification(last_block_for_set_id)? {
justification
if let Some(grandpa_justification) = blockchain.justifications(last_block_for_set_id)?
.and_then(|justifications| justifications.into_justification(GRANDPA_ENGINE_ID))
{
grandpa_justification
} else {
trace!(
target: "afg",
@@ -257,7 +259,7 @@ pub trait ProvableJustification<Header: HeaderT>: Encode + Decode {
/// Decode and verify justification.
fn decode_and_verify(
justification: &Justification,
justification: &EncodedJustification,
set_id: u64,
authorities: &[(AuthorityId, u64)],
) -> ClientResult<Self> {
@@ -286,6 +288,7 @@ pub(crate) mod tests {
use super::*;
use crate::authorities::AuthoritySetChanges;
use sp_core::crypto::Public;
use sp_runtime::Justifications;
use sp_finality_grandpa::AuthorityList;
use sc_client_api::NewBlockState;
use sc_client_api::in_mem::Blockchain as InMemoryBlockchain;
@@ -330,31 +333,27 @@ pub(crate) mod tests {
}
fn test_blockchain() -> InMemoryBlockchain<Block> {
use sp_finality_grandpa::GRANDPA_ENGINE_ID as ID;
let blockchain = InMemoryBlockchain::<Block>::new();
blockchain
.insert(header(0).hash(), header(0), Some(vec![0]), None, NewBlockState::Final)
.unwrap();
blockchain
.insert(header(1).hash(), header(1), Some(vec![1]), None, NewBlockState::Final)
.unwrap();
blockchain
.insert(header(2).hash(), header(2), None, None, NewBlockState::Best)
.unwrap();
blockchain
.insert(header(3).hash(), header(3), Some(vec![3]), None, NewBlockState::Final)
.unwrap();
let just0 = Some(Justifications::from((ID, vec![0])));
let just1 = Some(Justifications::from((ID, vec![1])));
let just2 = None;
let just3 = Some(Justifications::from((ID, vec![3])));
blockchain.insert(header(0).hash(), header(0), just0, None, NewBlockState::Final).unwrap();
blockchain.insert(header(1).hash(), header(1), just1, None, NewBlockState::Final).unwrap();
blockchain.insert(header(2).hash(), header(2), just2, None, NewBlockState::Best).unwrap();
blockchain.insert(header(3).hash(), header(3), just3, None, NewBlockState::Final).unwrap();
blockchain
}
#[test]
fn finality_proof_fails_if_no_more_last_finalized_blocks() {
use sp_finality_grandpa::GRANDPA_ENGINE_ID as ID;
let blockchain = test_blockchain();
blockchain
.insert(header(4).hash(), header(4), Some(vec![1]), None, NewBlockState::Best)
.unwrap();
blockchain
.insert(header(5).hash(), header(5), Some(vec![2]), None, NewBlockState::Best)
.unwrap();
let just1 = Some(Justifications::from((ID, vec![1])));
let just2 = Some(Justifications::from((ID, vec![2])));
blockchain.insert(header(4).hash(), header(4), just1, None, NewBlockState::Best).unwrap();
blockchain.insert(header(5).hash(), header(5), just2, None, NewBlockState::Best).unwrap();
let mut authority_set_changes = AuthoritySetChanges::empty();
authority_set_changes.append(0, 5);
@@ -430,22 +429,17 @@ pub(crate) mod tests {
#[test]
fn finality_proof_using_authority_set_changes_fails_with_undefined_start() {
use sp_finality_grandpa::GRANDPA_ENGINE_ID as ID;
let blockchain = test_blockchain();
let auth = vec![(AuthorityId::from_slice(&[1u8; 32]), 1u64)];
let just4 = TestJustification((0, auth.clone()), vec![4]).encode();
let just7 = TestJustification((1, auth.clone()), vec![7]).encode();
blockchain
.insert(header(4).hash(), header(4), Some(just4), None, NewBlockState::Final)
.unwrap();
blockchain
.insert(header(5).hash(), header(5), None, None, NewBlockState::Final)
.unwrap();
blockchain
.insert(header(6).hash(), header(6), None, None, NewBlockState::Final)
.unwrap();
blockchain
.insert(header(7).hash(), header(7), Some(just7.clone()), None, NewBlockState::Final)
.unwrap();
let grandpa_just4 = TestJustification((0, auth.clone()), vec![4]).encode();
let grandpa_just7 = TestJustification((1, auth.clone()), vec![7]).encode();
let just4 = Some(Justifications::from((ID, grandpa_just4)));
let just7 = Some(Justifications::from((ID, grandpa_just7)));
blockchain.insert(header(4).hash(), header(4), just4, None, NewBlockState::Final).unwrap();
blockchain.insert(header(5).hash(), header(5), None, None, NewBlockState::Final).unwrap();
blockchain.insert(header(6).hash(), header(6), None, None, NewBlockState::Final).unwrap();
blockchain.insert(header(7).hash(), header(7), just7, None, NewBlockState::Final).unwrap();
// We have stored the correct block number for the relevant set, but as we are missing the
// block for the preceding set the start is not well-defined.
@@ -462,22 +456,17 @@ pub(crate) mod tests {
#[test]
fn finality_proof_using_authority_set_changes_works() {
use sp_finality_grandpa::GRANDPA_ENGINE_ID as ID;
let blockchain = test_blockchain();
let auth = vec![(AuthorityId::from_slice(&[1u8; 32]), 1u64)];
let just4 = TestJustification((0, auth.clone()), vec![4]).encode();
let just7 = TestJustification((1, auth.clone()), vec![7]).encode();
blockchain
.insert(header(4).hash(), header(4), Some(just4), None, NewBlockState::Final)
.unwrap();
blockchain
.insert(header(5).hash(), header(5), None, None, NewBlockState::Final)
.unwrap();
blockchain
.insert(header(6).hash(), header(6), None, None, NewBlockState::Final)
.unwrap();
blockchain
.insert(header(7).hash(), header(7), Some(just7.clone()), None, NewBlockState::Final)
.unwrap();
let grandpa_just4 = TestJustification((0, auth.clone()), vec![4]).encode();
let grandpa_just7 = TestJustification((1, auth.clone()), vec![7]).encode();
let just4 = Some(Justifications::from((ID, grandpa_just4)));
let just7 = Some(Justifications::from((ID, grandpa_just7.clone())));
blockchain.insert(header(4).hash(), header(4), just4, None, NewBlockState::Final) .unwrap();
blockchain.insert(header(5).hash(), header(5), None, None, NewBlockState::Final) .unwrap();
blockchain.insert(header(6).hash(), header(6), None, None, NewBlockState::Final).unwrap();
blockchain.insert(header(7).hash(), header(7), just7, None, NewBlockState::Final).unwrap();
let mut authority_set_changes = AuthoritySetChanges::empty();
authority_set_changes.append(0, 4);
@@ -497,7 +486,7 @@ pub(crate) mod tests {
proof_of_5,
FinalityProof {
block: header(7).hash(),
justification: just7,
justification: grandpa_just7,
unknown_headers: vec![header(6)],
}
);
@@ -451,7 +451,7 @@ impl<BE, Block: BlockT, Client, SC> BlockImport<Block>
let pending_changes = self.make_authorities_changes(&mut block, hash, initial_sync)?;
// we don't want to finalize on `inner.import_block`
let mut justification = block.justification.take();
let mut justifications = block.justifications.take();
let import_result = (&*self.inner).import_block(block, new_cache);
let mut imported_aux = {
@@ -513,17 +513,20 @@ impl<BE, Block: BlockT, Client, SC> BlockImport<Block>
// need to apply first, drop any justification that might have been provided with
// the block to make sure we request them from `sync` which will ensure they'll be
// applied in-order.
justification.take();
justifications.take();
},
_ => {},
}
match justification {
let grandpa_justification = justifications
.and_then(|just| just.into_justification(GRANDPA_ENGINE_ID));
match grandpa_justification {
Some(justification) => {
let import_res = self.import_justification(
hash,
number,
justification,
(GRANDPA_ENGINE_ID, justification),
needs_justification,
initial_sync,
);
@@ -637,8 +640,14 @@ where
enacts_change: bool,
initial_sync: bool,
) -> Result<(), ConsensusError> {
if justification.0 != GRANDPA_ENGINE_ID {
return Err(ConsensusError::ClientImport(
"GRANDPA can only import GRANDPA Justifications.".into(),
));
}
let justification = GrandpaJustification::decode_and_verify_finalizes(
&justification,
&justification.1,
(hash, number),
self.authority_set.set_id(),
&self.authority_set.current_authorities(),
@@ -39,7 +39,7 @@ use sp_consensus::{
import_queue::BoxJustificationImport,
};
use std::{collections::{HashMap, HashSet}, pin::Pin};
use sp_runtime::traits::{Block as BlockT, Header as HeaderT};
use sp_runtime::{Justifications, traits::{Block as BlockT, Header as HeaderT}};
use sp_runtime::generic::{BlockId, DigestItem};
use sp_core::H256;
use sp_keystore::{SyncCryptoStorePtr, SyncCryptoStore};
@@ -369,7 +369,7 @@ fn finalize_3_voters_no_observers() {
// normally there's no justification for finalized blocks
assert!(
net.lock().peer(0).client().justification(&BlockId::Number(20)).unwrap().is_none(),
net.lock().peer(0).client().justifications(&BlockId::Number(20)).unwrap().is_none(),
"Extra justification for block#1",
);
}
@@ -613,7 +613,7 @@ fn justification_is_generated_periodically() {
// when block#32 (justification_period) is finalized, justification
// is required => generated
for i in 0..3 {
assert!(net.lock().peer(i).client().justification(&BlockId::Number(32)).unwrap().is_some());
assert!(net.lock().peer(i).client().justifications(&BlockId::Number(32)).unwrap().is_some());
}
}
@@ -658,12 +658,12 @@ fn sync_justifications_on_change_blocks() {
// the first 3 peers are grandpa voters and therefore have already finalized
// block 21 and stored a justification
for i in 0..3 {
assert!(net.lock().peer(i).client().justification(&BlockId::Number(21)).unwrap().is_some());
assert!(net.lock().peer(i).client().justifications(&BlockId::Number(21)).unwrap().is_some());
}
// the last peer should get the justification by syncing from other peers
futures::executor::block_on(futures::future::poll_fn(move |cx| {
if net.lock().peer(3).client().justification(&BlockId::Number(21)).unwrap().is_none() {
if net.lock().peer(3).client().justifications(&BlockId::Number(21)).unwrap().is_none() {
net.lock().poll(cx);
Poll::Pending
} else {
@@ -868,7 +868,7 @@ fn test_bad_justification() {
let block = || {
let block = block.clone();
let mut import = BlockImportParams::new(BlockOrigin::File, block.header);
import.justification = Some(Vec::new());
import.justifications = Some(Justifications::from((GRANDPA_ENGINE_ID, Vec::new())));
import.body = Some(block.extrinsics);
import.fork_choice = Some(ForkChoiceStrategy::LongestChain);
@@ -1583,7 +1583,7 @@ fn imports_justification_for_regular_blocks_on_import() {
// we import the block with justification attached
let mut import = BlockImportParams::new(BlockOrigin::File, block.header);
import.justification = Some(justification.encode());
import.justifications = Some((GRANDPA_ENGINE_ID, justification.encode()).into());
import.body = Some(block.extrinsics);
import.fork_choice = Some(ForkChoiceStrategy::LongestChain);
@@ -1600,7 +1600,7 @@ fn imports_justification_for_regular_blocks_on_import() {
// the justification should be imported and available from the client
assert!(
client.justification(&BlockId::Hash(block_hash)).unwrap().is_some(),
client.justifications(&BlockId::Hash(block_hash)).unwrap().is_some(),
);
}
+11 -3
View File
@@ -32,7 +32,7 @@ use sp_state_machine::{
Backend as StateBackend, TrieBackend, InMemoryBackend, ChangesTrieTransaction,
StorageCollection, ChildStorageCollection,
};
use sp_runtime::{generic::BlockId, Justification, Storage};
use sp_runtime::{generic::BlockId, Justification, Justifications, Storage};
use sp_runtime::traits::{Block as BlockT, NumberFor, Zero, Header, HashFor};
use sp_blockchain::{Error as ClientError, Result as ClientResult};
use sc_client_api::{
@@ -199,6 +199,14 @@ impl<S, Block> ClientBackend<Block> for Backend<S, HashFor<Block>>
self.blockchain.storage().finalize_header(block)
}
fn append_justification(
&self,
_block: BlockId<Block>,
_justification: Justification,
) -> ClientResult<()> {
Ok(())
}
fn blockchain(&self) -> &Blockchain<S> {
&self.blockchain
}
@@ -278,7 +286,7 @@ impl<S, Block> BlockImportOperation<Block> for ImportOperation<Block, S>
&mut self,
header: Block::Header,
_body: Option<Vec<Block::Extrinsic>>,
_justification: Option<Justification>,
_justifications: Option<Justifications>,
state: NewBlockState,
) -> ClientResult<()> {
self.leaf_state = state;
@@ -356,7 +364,7 @@ impl<S, Block> BlockImportOperation<Block> for ImportOperation<Block, S>
fn mark_finalized(
&mut self,
block: BlockId<Block>,
_justification: Option<Justification>,
_justifications: Option<Justification>,
) -> ClientResult<()> {
self.finalized_blocks.push(block);
Ok(())
+2 -2
View File
@@ -21,7 +21,7 @@
use std::sync::Arc;
use sp_runtime::{Justification, generic::BlockId};
use sp_runtime::{Justifications, generic::BlockId};
use sp_runtime::traits::{Block as BlockT, Header as HeaderT, NumberFor, Zero};
use sp_blockchain::{
@@ -109,7 +109,7 @@ impl<S, Block> BlockchainBackend<Block> for Blockchain<S> where Block: BlockT, S
Err(ClientError::NotAvailableOnLightClient)
}
fn justification(&self, _id: BlockId<Block>) -> ClientResult<Option<Justification>> {
fn justifications(&self, _id: BlockId<Block>) -> ClientResult<Option<Justifications>> {
Err(ClientError::NotAvailableOnLightClient)
}
+2 -2
View File
@@ -37,7 +37,7 @@ use libp2p::swarm::{
use log::debug;
use prost::Message;
use sp_consensus::{BlockOrigin, import_queue::{IncomingBlock, Origin}};
use sp_runtime::{traits::{Block as BlockT, NumberFor}, Justification};
use sp_runtime::{traits::{Block as BlockT, NumberFor}, Justifications};
use std::{
borrow::Cow,
collections::{HashSet, VecDeque},
@@ -84,7 +84,7 @@ pub struct Behaviour<B: BlockT> {
/// Event generated by `Behaviour`.
pub enum BehaviourOut<B: BlockT> {
BlockImport(BlockOrigin, Vec<IncomingBlock<B>>),
JustificationImport(Origin, B::Hash, NumberFor<B>, Justification),
JustificationImport(Origin, B::Hash, NumberFor<B>, Justifications),
/// Started a random iterative Kademlia discovery query.
RandomKademliaStarted(ProtocolId),
@@ -275,12 +275,28 @@ impl<B: BlockT> BlockRequestHandler<B> {
let number = *header.number();
let hash = header.hash();
let parent_hash = *header.parent_hash();
let justification = if get_justification {
self.client.justification(&BlockId::Hash(hash))?
let justifications = if get_justification {
self.client.justifications(&BlockId::Hash(hash))?
} else {
None
};
let is_empty_justification = justification.as_ref().map(|j| j.is_empty()).unwrap_or(false);
// TODO: In a follow up PR tracked by https://github.com/paritytech/substrate/issues/8172
// we want to send/receive all justifications.
// For now we keep compatibility by selecting precisely the GRANDPA one, and not just
// the first one. When sending we could have just taken the first one, since we don't
// expect there to be any other kind currently, but when receiving we need to add the
// engine ID tag.
// The ID tag is hardcoded here to avoid depending on the GRANDPA crate, and will be
// removed when resolving the above issue.
let justification = justifications.and_then(|just| just.into_justification(*b"FRNK"));
let is_empty_justification = justification
.as_ref()
.map(|j| j.is_empty())
.unwrap_or(false);
let justification = justification.unwrap_or_default();
let body = if get_body {
match self.client.block_body(&BlockId::Hash(hash))? {
@@ -306,7 +322,7 @@ impl<B: BlockT> BlockRequestHandler<B> {
body,
receipt: Vec::new(),
message_queue: Vec::new(),
justification: justification.unwrap_or_default(),
justification,
is_empty_justification,
};
+2 -2
View File
@@ -52,7 +52,7 @@ fn build_test_full_node(network_config: config::NetworkConfiguration)
&mut self,
origin: sp_consensus::BlockOrigin,
header: B::Header,
justification: Option<sp_runtime::Justification>,
justifications: Option<sp_runtime::Justifications>,
body: Option<Vec<B::Extrinsic>>,
) -> Result<
(
@@ -79,7 +79,7 @@ fn build_test_full_node(network_config: config::NetworkConfiguration)
let mut import = sp_consensus::BlockImportParams::new(origin, header);
import.body = body;
import.finalized = self.0;
import.justification = justification;
import.justifications = justifications;
import.fork_choice = Some(sp_consensus::ForkChoiceStrategy::LongestChain);
Ok((import, maybe_keys))
}
+7 -6
View File
@@ -43,9 +43,10 @@ use sp_consensus::{
block_validation::BlockAnnounceValidator,
import_queue::{BlockImportResult, BlockImportError, IncomingBlock, Origin}
};
use sp_runtime::{generic::BlockId, Justification};
use sp_runtime::traits::{
Block as BlockT, Header as HeaderT, NumberFor, Zero, CheckedSub
use sp_runtime::{
Justifications,
generic::BlockId,
traits::{Block as BlockT, Header as HeaderT, NumberFor, Zero, CheckedSub},
};
use sp_arithmetic::traits::SaturatedConversion;
use sync::{ChainSync, SyncState};
@@ -612,8 +613,8 @@ impl<B: BlockT> Protocol<B> {
if request.fields == message::BlockAttributes::JUSTIFICATION {
match self.sync.on_block_justification(peer_id, block_response) {
Ok(sync::OnBlockJustification::Nothing) => CustomMessageOutcome::None,
Ok(sync::OnBlockJustification::Import { peer, hash, number, justification }) =>
CustomMessageOutcome::JustificationImport(peer, hash, number, justification),
Ok(sync::OnBlockJustification::Import { peer, hash, number, justifications }) =>
CustomMessageOutcome::JustificationImport(peer, hash, number, justifications),
Err(sync::BadPeer(id, repu)) => {
self.behaviour.disconnect_peer(&id, HARDCODED_PEERSETS_SYNC);
self.peerset_handle.report_peer(id, repu);
@@ -1134,7 +1135,7 @@ fn prepare_block_request<B: BlockT>(
#[must_use]
pub enum CustomMessageOutcome<B: BlockT> {
BlockImport(BlockOrigin, Vec<IncomingBlock<B>>),
JustificationImport(Origin, B::Hash, NumberFor<B>, Justification),
JustificationImport(Origin, B::Hash, NumberFor<B>, Justifications),
/// Notification protocols have been opened with a remote.
NotificationStreamOpened {
remote: PeerId,
@@ -148,7 +148,7 @@ pub struct RemoteReadResponse {
pub mod generic {
use bitflags::bitflags;
use codec::{Encode, Decode, Input, Output};
use sp_runtime::Justification;
use sp_runtime::EncodedJustification;
use super::{
RemoteReadResponse, Transactions, Direction,
RequestId, BlockAttributes, RemoteCallResponse, ConsensusEngineId,
@@ -233,7 +233,7 @@ pub mod generic {
/// Block message queue if requested.
pub message_queue: Option<Vec<u8>>,
/// Justification if requested.
pub justification: Option<Justification>,
pub justification: Option<EncodedJustification>,
}
/// Identifies starting point of a block sequence.
+22 -8
View File
@@ -44,7 +44,7 @@ use extra_requests::ExtraRequests;
use libp2p::PeerId;
use log::{debug, trace, warn, info, error};
use sp_runtime::{
Justification,
EncodedJustification, Justifications,
generic::BlockId,
traits::{
Block as BlockT, Header as HeaderT, NumberFor, Zero, One, CheckedSub, SaturatedConversion,
@@ -425,7 +425,7 @@ pub enum OnBlockJustification<B: BlockT> {
peer: PeerId,
hash: B::Hash,
number: NumberFor<B>,
justification: Justification
justifications: Justifications
}
}
@@ -823,11 +823,13 @@ impl<B: BlockT> ChainSync<B> {
.drain(self.best_queued_number + One::one())
.into_iter()
.map(|block_data| {
let justifications =
legacy_justification_mapping(block_data.block.justification);
IncomingBlock {
hash: block_data.block.hash,
header: block_data.block.header,
body: block_data.block.body,
justification: block_data.block.justification,
justifications,
origin: block_data.origin,
allow_missing_state: true,
import_existing: false,
@@ -846,7 +848,7 @@ impl<B: BlockT> ChainSync<B> {
hash: b.hash,
header: b.header,
body: b.body,
justification: b.justification,
justifications: legacy_justification_mapping(b.justification),
origin: Some(who.clone()),
allow_missing_state: true,
import_existing: false,
@@ -955,7 +957,7 @@ impl<B: BlockT> ChainSync<B> {
hash: b.hash,
header: b.header,
body: b.body,
justification: b.justification,
justifications: legacy_justification_mapping(b.justification),
origin: Some(who.clone()),
allow_missing_state: true,
import_existing: false,
@@ -1039,8 +1041,11 @@ impl<B: BlockT> ChainSync<B> {
None
};
if let Some((peer, hash, number, j)) = self.extra_justifications.on_response(who, justification) {
return Ok(OnBlockJustification::Import { peer, hash, number, justification: j })
if let Some((peer, hash, number, j)) = self
.extra_justifications
.on_response(who, legacy_justification_mapping(justification))
{
return Ok(OnBlockJustification::Import { peer, hash, number, justifications: j })
}
}
@@ -1597,6 +1602,14 @@ impl<B: BlockT> ChainSync<B> {
}
}
// This is purely during a backwards compatible transitionary period and should be removed
// once we can assume all nodes can send and receive multiple Justifications
// The ID tag is hardcoded here to avoid depending on the GRANDPA crate.
// TODO: https://github.com/paritytech/substrate/issues/8172
fn legacy_justification_mapping(justification: Option<EncodedJustification>) -> Option<Justifications> {
justification.map(|just| (*b"FRNK", just).into())
}
#[derive(Debug)]
pub(crate) struct Metrics {
pub(crate) queued_blocks: u32,
@@ -2396,7 +2409,8 @@ mod test {
);
let finalized_block = blocks[MAX_BLOCKS_TO_LOOK_BACKWARDS as usize * 2 - 1].clone();
client.finalize_block(BlockId::Hash(finalized_block.hash()), Some(Vec::new())).unwrap();
let just = (*b"TEST", Vec::new());
client.finalize_block(BlockId::Hash(finalized_block.hash()), Some(just)).unwrap();
sync.update_chain_info(&info.best_hash, info.best_number);
let peer_id1 = PeerId::random();
+2 -2
View File
@@ -1452,11 +1452,11 @@ impl<B: BlockT + 'static, H: ExHashT> Future for NetworkWorker<B, H> {
}
this.import_queue.import_blocks(origin, blocks);
},
Poll::Ready(SwarmEvent::Behaviour(BehaviourOut::JustificationImport(origin, hash, nb, justification))) => {
Poll::Ready(SwarmEvent::Behaviour(BehaviourOut::JustificationImport(origin, hash, nb, justifications))) => {
if let Some(metrics) = this.metrics.as_ref() {
metrics.import_queue_justifications_submitted.inc();
}
this.import_queue.import_justification(origin, hash, nb, justification);
this.import_queue.import_justifications(origin, hash, nb, justifications);
},
Poll::Ready(SwarmEvent::Behaviour(BehaviourOut::InboundRequest { protocol, result, .. })) => {
if let Some(metrics) = this.metrics.as_ref() {
@@ -52,7 +52,7 @@ fn build_test_full_node(config: config::NetworkConfiguration)
&mut self,
origin: sp_consensus::BlockOrigin,
header: B::Header,
justification: Option<sp_runtime::Justification>,
justifications: Option<sp_runtime::Justifications>,
body: Option<Vec<B::Extrinsic>>,
) -> Result<
(
@@ -79,7 +79,7 @@ fn build_test_full_node(config: config::NetworkConfiguration)
let mut import = sp_consensus::BlockImportParams::new(origin, header);
import.body = body;
import.finalized = self.0;
import.justification = justification;
import.justifications = justifications;
import.fork_choice = Some(sp_consensus::ForkChoiceStrategy::LongestChain);
Ok((import, maybe_keys))
}
@@ -35,13 +35,13 @@ fn prepare_good_block() -> (TestClient, Hash, u64, PeerId, IncomingBlock<Block>)
let (hash, number) = (client.block_hash(1).unwrap().unwrap(), 1);
let header = client.header(&BlockId::Number(1)).unwrap();
let justification = client.justification(&BlockId::Number(1)).unwrap();
let justifications = client.justifications(&BlockId::Number(1)).unwrap();
let peer_id = PeerId::random();
(client, hash, number, peer_id.clone(), IncomingBlock {
hash,
header,
body: Some(Vec::new()),
justification,
justifications,
origin: Some(peer_id.clone()),
allow_missing_state: false,
import_existing: false,
+8 -8
View File
@@ -63,7 +63,7 @@ use sp_core::H256;
use sc_network::config::ProtocolConfig;
use sp_runtime::generic::{BlockId, OpaqueDigestItemId};
use sp_runtime::traits::{Block as BlockT, Header as HeaderT, NumberFor};
use sp_runtime::Justification;
use sp_runtime::{Justification, Justifications};
use substrate_test_runtime_client::{self, AccountKeyring};
use sc_service::client::Client;
pub use sc_network::config::EmptyTransactionPool;
@@ -109,7 +109,7 @@ impl<B: BlockT> Verifier<B> for PassThroughVerifier {
&mut self,
origin: BlockOrigin,
header: B::Header,
justification: Option<Justification>,
justifications: Option<Justifications>,
body: Option<Vec<B::Extrinsic>>
) -> Result<(BlockImportParams<B, ()>, Option<Vec<(CacheKeyId, Vec<u8>)>>), String> {
let maybe_keys = header.digest()
@@ -120,7 +120,7 @@ impl<B: BlockT> Verifier<B> for PassThroughVerifier {
let mut import = BlockImportParams::new(origin, header);
import.body = body;
import.finalized = self.finalized;
import.justification = justification;
import.justifications = justifications;
import.fork_choice = Some(self.fork_choice.clone());
Ok((import, maybe_keys))
@@ -184,10 +184,10 @@ impl PeersClient {
}
}
pub fn justification(&self, block: &BlockId<Block>) -> ClientResult<Option<Justification>> {
pub fn justifications(&self, block: &BlockId<Block>) -> ClientResult<Option<Justifications>> {
match *self {
PeersClient::Full(ref client, ref _backend) => client.justification(block),
PeersClient::Light(ref client, ref _backend) => client.justification(block),
PeersClient::Full(ref client, ref _backend) => client.justifications(block),
PeersClient::Light(ref client, ref _backend) => client.justifications(block),
}
}
@@ -577,11 +577,11 @@ impl<B: BlockT> Verifier<B> for VerifierAdapter<B> {
&mut self,
origin: BlockOrigin,
header: B::Header,
justification: Option<Justification>,
justifications: Option<Justifications>,
body: Option<Vec<B::Extrinsic>>
) -> Result<(BlockImportParams<B, ()>, Option<Vec<(CacheKeyId, Vec<u8>)>>), String> {
let hash = header.hash();
self.verifier.lock().verify(origin, header, justification, body).map_err(|e| {
self.verifier.lock().verify(origin, header, justifications, body).map_err(|e| {
self.failed_verifications.lock().insert(hash, e.clone());
e
})
+47 -16
View File
@@ -22,6 +22,7 @@ use futures::{Future, executor::block_on};
use super::*;
use sp_consensus::block_validation::Validation;
use substrate_test_runtime::Header;
use sp_runtime::Justifications;
fn test_ancestor_search_when_common_is(n: usize) {
sp_tracing::try_init_simple();
@@ -248,13 +249,14 @@ fn sync_justifications() {
net.block_until_sync();
// there's currently no justification for block #10
assert_eq!(net.peer(0).client().justification(&BlockId::Number(10)).unwrap(), None);
assert_eq!(net.peer(1).client().justification(&BlockId::Number(10)).unwrap(), None);
assert_eq!(net.peer(0).client().justifications(&BlockId::Number(10)).unwrap(), None);
assert_eq!(net.peer(1).client().justifications(&BlockId::Number(10)).unwrap(), None);
// we finalize block #10, #15 and #20 for peer 0 with a justification
net.peer(0).client().finalize_block(BlockId::Number(10), Some(Vec::new()), true).unwrap();
net.peer(0).client().finalize_block(BlockId::Number(15), Some(Vec::new()), true).unwrap();
net.peer(0).client().finalize_block(BlockId::Number(20), Some(Vec::new()), true).unwrap();
let just = (*b"FRNK", Vec::new());
net.peer(0).client().finalize_block(BlockId::Number(10), Some(just.clone()), true).unwrap();
net.peer(0).client().finalize_block(BlockId::Number(15), Some(just.clone()), true).unwrap();
net.peer(0).client().finalize_block(BlockId::Number(20), Some(just.clone()), true).unwrap();
let h1 = net.peer(1).client().header(&BlockId::Number(10)).unwrap().unwrap();
let h2 = net.peer(1).client().header(&BlockId::Number(15)).unwrap().unwrap();
@@ -269,10 +271,20 @@ fn sync_justifications() {
net.poll(cx);
for height in (10..21).step_by(5) {
if net.peer(0).client().justification(&BlockId::Number(height)).unwrap() != Some(Vec::new()) {
if net
.peer(0)
.client()
.justifications(&BlockId::Number(height))
.unwrap() != Some(Justifications::from((*b"FRNK", Vec::new())))
{
return Poll::Pending;
}
if net.peer(1).client().justification(&BlockId::Number(height)).unwrap() != Some(Vec::new()) {
if net
.peer(1)
.client()
.justifications(&BlockId::Number(height))
.unwrap() != Some(Justifications::from((*b"FRNK", Vec::new())))
{
return Poll::Pending;
}
}
@@ -295,7 +307,8 @@ fn sync_justifications_across_forks() {
// for both and finalize the small fork instead.
net.block_until_sync();
net.peer(0).client().finalize_block(BlockId::Hash(f1_best), Some(Vec::new()), true).unwrap();
let just = (*b"FRNK", Vec::new());
net.peer(0).client().finalize_block(BlockId::Hash(f1_best), Some(just), true).unwrap();
net.peer(1).request_justification(&f1_best, 10);
net.peer(1).request_justification(&f2_best, 11);
@@ -303,8 +316,16 @@ fn sync_justifications_across_forks() {
block_on(futures::future::poll_fn::<(), _>(|cx| {
net.poll(cx);
if net.peer(0).client().justification(&BlockId::Number(10)).unwrap() == Some(Vec::new()) &&
net.peer(1).client().justification(&BlockId::Number(10)).unwrap() == Some(Vec::new())
if net
.peer(0)
.client()
.justifications(&BlockId::Number(10))
.unwrap() == Some(Justifications::from((*b"FRNK", Vec::new())))
&& net
.peer(1)
.client()
.justifications(&BlockId::Number(10))
.unwrap() == Some(Justifications::from((*b"FRNK", Vec::new())))
{
Poll::Ready(())
} else {
@@ -696,8 +717,9 @@ fn can_sync_to_peers_with_wrong_common_block() {
net.block_until_connected();
// both peers re-org to the same fork without notifying each other
net.peer(0).client().finalize_block(BlockId::Hash(fork_hash), Some(Vec::new()), true).unwrap();
net.peer(1).client().finalize_block(BlockId::Hash(fork_hash), Some(Vec::new()), true).unwrap();
let just = Some((*b"FRNK", Vec::new()));
net.peer(0).client().finalize_block(BlockId::Hash(fork_hash), just.clone(), true).unwrap();
net.peer(1).client().finalize_block(BlockId::Hash(fork_hash), just, true).unwrap();
let final_hash = net.peer(0).push_blocks(1, false);
net.block_until_sync();
@@ -948,8 +970,8 @@ fn multiple_requests_are_accepted_as_long_as_they_are_not_fulfilled() {
net.block_until_sync();
// there's currently no justification for block #10
assert_eq!(net.peer(0).client().justification(&BlockId::Number(10)).unwrap(), None);
assert_eq!(net.peer(1).client().justification(&BlockId::Number(10)).unwrap(), None);
assert_eq!(net.peer(0).client().justifications(&BlockId::Number(10)).unwrap(), None);
assert_eq!(net.peer(1).client().justifications(&BlockId::Number(10)).unwrap(), None);
let h1 = net.peer(1).client().header(&BlockId::Number(10)).unwrap().unwrap();
@@ -967,12 +989,21 @@ fn multiple_requests_are_accepted_as_long_as_they_are_not_fulfilled() {
}
// Finalize the block and make the justification available.
net.peer(0).client().finalize_block(BlockId::Number(10), Some(Vec::new()), true).unwrap();
net.peer(0).client().finalize_block(
BlockId::Number(10),
Some((*b"FRNK", Vec::new())),
true,
).unwrap();
block_on(futures::future::poll_fn::<(), _>(|cx| {
net.poll(cx);
if net.peer(1).client().justification(&BlockId::Number(10)).unwrap() != Some(Vec::new()) {
if net
.peer(1)
.client()
.justifications(&BlockId::Number(10))
.unwrap() != Some(Justifications::from((*b"FRNK", Vec::new())))
{
return Poll::Pending;
}
@@ -106,7 +106,7 @@ impl<Block, Client, F> ChainBackend<Client, Block> for LightChain<Block, Client,
.compat()
.map(move |body| Some(SignedBlock {
block: Block::new(header, body),
justification: None,
justifications: None,
}))
.map_err(client_err)
),
+1 -1
View File
@@ -72,7 +72,7 @@ fn should_return_a_block() {
// Genesis block is not justified
assert_matches!(
api.block(Some(client.genesis_hash()).into()).wait(),
Ok(Some(SignedBlock { justification: None, .. }))
Ok(Some(SignedBlock { justifications: None, .. }))
);
assert_matches!(
@@ -168,7 +168,7 @@ fn import_block_to_queue<TBl, TImpQu>(
hash,
header: Some(header),
body: Some(extrinsics),
justification: signed_block.justification,
justifications: signed_block.justifications,
origin: None,
allow_missing_state: false,
import_existing: force,
+11 -11
View File
@@ -41,7 +41,7 @@ use sc_telemetry::{
SUBSTRATE_INFO,
};
use sp_runtime::{
Justification, BuildStorage,
Justification, Justifications, BuildStorage,
generic::{BlockId, SignedBlock, DigestItem},
traits::{
Block as BlockT, Header as HeaderT, Zero, NumberFor,
@@ -625,7 +625,7 @@ impl<B, E, Block, RA> Client<B, E, Block, RA> where
let BlockImportParams {
origin,
header,
justification,
justifications,
post_digests,
body,
storage_changes,
@@ -637,7 +637,7 @@ impl<B, E, Block, RA> Client<B, E, Block, RA> where
..
} = import_block;
assert!(justification.is_some() && finalized || justification.is_none());
assert!(justifications.is_some() && finalized || justifications.is_none());
if !intermediates.is_empty() {
return Err(Error::IncompletePipeline)
@@ -665,7 +665,7 @@ impl<B, E, Block, RA> Client<B, E, Block, RA> where
origin,
hash,
import_headers,
justification,
justifications,
body,
storage_changes,
new_cache,
@@ -704,7 +704,7 @@ impl<B, E, Block, RA> Client<B, E, Block, RA> where
origin: BlockOrigin,
hash: Block::Hash,
import_headers: PrePostHeader<Block::Header>,
justification: Option<Justification>,
justifications: Option<Justifications>,
body: Option<Vec<Block::Extrinsic>>,
storage_changes: Option<sp_api::StorageChanges<backend::StateBackendFor<B, Block>, Block>>,
new_cache: HashMap<CacheKeyId, Vec<u8>>,
@@ -820,7 +820,7 @@ impl<B, E, Block, RA> Client<B, E, Block, RA> where
operation.op.set_block_data(
import_headers.post().clone(),
body,
justification,
justifications,
leaf_state,
)?;
@@ -1926,9 +1926,9 @@ impl<B, E, Block, RA> BlockBackend<Block> for Client<B, E, Block, RA>
}
fn block(&self, id: &BlockId<Block>) -> sp_blockchain::Result<Option<SignedBlock<Block>>> {
Ok(match (self.header(id)?, self.body(id)?, self.justification(id)?) {
(Some(header), Some(extrinsics), justification) =>
Some(SignedBlock { block: Block::new(header, extrinsics), justification }),
Ok(match (self.header(id)?, self.body(id)?, self.justifications(id)?) {
(Some(header), Some(extrinsics), justifications) =>
Some(SignedBlock { block: Block::new(header, extrinsics), justifications }),
_ => None,
})
}
@@ -1937,8 +1937,8 @@ impl<B, E, Block, RA> BlockBackend<Block> for Client<B, E, Block, RA>
Client::block_status(self, id)
}
fn justification(&self, id: &BlockId<Block>) -> sp_blockchain::Result<Option<Justification>> {
self.backend.blockchain().justification(*id)
fn justifications(&self, id: &BlockId<Block>) -> sp_blockchain::Result<Option<Justifications>> {
self.backend.blockchain().justifications(*id)
}
fn block_hash(&self, number: NumberFor<Block>) -> sp_blockchain::Result<Option<Block::Hash>> {
@@ -28,8 +28,9 @@ use sc_light::{
};
use std::sync::Arc;
use sp_runtime::{
traits::{BlakeTwo256, HashFor, NumberFor},
generic::BlockId, traits::{Block as _, Header as HeaderT}, Digest,
generic::BlockId,
traits::{BlakeTwo256, Block as _, HashFor, Header as HeaderT, NumberFor},
Digest, Justifications,
};
use std::collections::HashMap;
use parking_lot::Mutex;
@@ -377,7 +378,7 @@ fn execution_proof_is_generated_and_checked() {
remote_client.import_justified(
BlockOrigin::Own,
remote_client.new_block(digest).unwrap().build().unwrap().block,
Default::default(),
Justifications::from((*b"TEST", Default::default())),
).unwrap();
}
@@ -36,8 +36,9 @@ use sc_client_db::{
};
use sc_block_builder::BlockBuilderProvider;
use sc_service::client::{self, Client, LocalCallExecutor, new_in_mem};
use sp_runtime::traits::{
BlakeTwo256, Block as BlockT, Header as HeaderT,
use sp_runtime::{
ConsensusEngineId,
traits::{BlakeTwo256, Block as BlockT, Header as HeaderT},
};
use substrate_test_runtime::TestAPI;
use sp_state_machine::backend::Backend as _;
@@ -51,12 +52,14 @@ use sp_consensus::{
};
use sp_storage::StorageKey;
use sp_trie::{TrieConfiguration, trie_types::Layout};
use sp_runtime::{generic::BlockId, DigestItem};
use sp_runtime::{generic::BlockId, DigestItem, Justifications};
use hex_literal::hex;
mod light;
mod db;
const TEST_ENGINE_ID: ConsensusEngineId = *b"TEST";
native_executor_instance!(
Executor,
substrate_test_runtime_client::runtime::api::dispatch,
@@ -1016,7 +1019,7 @@ fn import_with_justification() {
client.import(BlockOrigin::Own, a2.clone()).unwrap();
// A2 -> A3
let justification = vec![1, 2, 3];
let justification = Justifications::from((TEST_ENGINE_ID, vec![1, 2, 3]));
let a3 = client.new_block_at(
&BlockId::Hash(a2.hash()),
Default::default(),
@@ -1030,17 +1033,17 @@ fn import_with_justification() {
);
assert_eq!(
client.justification(&BlockId::Hash(a3.hash())).unwrap(),
client.justifications(&BlockId::Hash(a3.hash())).unwrap(),
Some(justification),
);
assert_eq!(
client.justification(&BlockId::Hash(a1.hash())).unwrap(),
client.justifications(&BlockId::Hash(a1.hash())).unwrap(),
None,
);
assert_eq!(
client.justification(&BlockId::Hash(a2.hash())).unwrap(),
client.justifications(&BlockId::Hash(a2.hash())).unwrap(),
None,
);
}
@@ -1088,7 +1091,7 @@ fn importing_diverged_finalized_block_should_trigger_reorg() {
);
// importing B1 as finalized should trigger a re-org and set it as new best
let justification = vec![1, 2, 3];
let justification = Justifications::from((TEST_ENGINE_ID, vec![1, 2, 3]));
client.import_justified(BlockOrigin::Own, b1.clone(), justification).unwrap();
assert_eq!(
@@ -21,7 +21,7 @@ use std::sync::Arc;
use sp_runtime::traits::{Block as BlockT, Header as HeaderT, NumberFor};
use sp_runtime::generic::BlockId;
use sp_runtime::Justification;
use sp_runtime::Justifications;
use log::warn;
use parking_lot::RwLock;
@@ -84,8 +84,8 @@ pub trait HeaderBackend<Block: BlockT>: Send + Sync {
pub trait Backend<Block: BlockT>: HeaderBackend<Block> + HeaderMetadata<Block, Error=Error> {
/// Get block body. Returns `None` if block is not found.
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>>;
/// Get block justifications. Returns `None` if no justification exists.
fn justifications(&self, id: BlockId<Block>) -> Result<Option<Justifications>>;
/// Get last finalized block hash.
fn last_finalized(&self) -> Result<Block::Hash>;
/// Returns data cache reference, if it is enabled on this backend.
@@ -18,7 +18,7 @@
//! Block import helpers.
use sp_runtime::traits::{Block as BlockT, DigestItemFor, Header as HeaderT, NumberFor, HashFor};
use sp_runtime::Justification;
use sp_runtime::{Justification, Justifications};
use serde::{Serialize, Deserialize};
use std::borrow::Cow;
use std::collections::HashMap;
@@ -128,8 +128,8 @@ pub struct BlockImportParams<Block: BlockT, Transaction> {
/// re-executed in a runtime that checks digest equivalence -- the
/// post-runtime digests are pushed back on after.
pub header: Block::Header,
/// Justification provided for this block from the outside.
pub justification: Option<Justification>,
/// Justification(s) provided for this block from the outside.
pub justifications: Option<Justifications>,
/// Digest items that have been added after the runtime for external
/// work, like a consensus signature.
pub post_digests: Vec<DigestItemFor<Block>>,
@@ -174,7 +174,7 @@ impl<Block: BlockT, Transaction> BlockImportParams<Block, Transaction> {
) -> Self {
Self {
origin, header,
justification: None,
justifications: None,
post_digests: Vec::new(),
body: None,
storage_changes: None,
@@ -219,7 +219,7 @@ impl<Block: BlockT, Transaction> BlockImportParams<Block, Transaction> {
BlockImportParams {
origin: self.origin,
header: self.header,
justification: self.justification,
justifications: self.justifications,
post_digests: self.post_digests,
body: self.body,
storage_changes: None,
@@ -28,7 +28,7 @@
use std::collections::HashMap;
use sp_runtime::{Justification, traits::{Block as BlockT, Header as _, NumberFor}};
use sp_runtime::{Justifications, traits::{Block as BlockT, Header as _, NumberFor}};
use crate::{
error::Error as ConsensusError,
@@ -68,8 +68,8 @@ pub struct IncomingBlock<B: BlockT> {
pub header: Option<<B as BlockT>::Header>,
/// Block body if requested.
pub body: Option<Vec<<B as BlockT>::Extrinsic>>,
/// Justification if requested.
pub justification: Option<Justification>,
/// Justification(s) if requested.
pub justifications: Option<Justifications>,
/// The peer, we received this from
pub origin: Option<Origin>,
/// Allow importing the block skipping state verification if parent state is missing.
@@ -90,7 +90,7 @@ pub trait Verifier<B: BlockT>: Send + Sync {
&mut self,
origin: BlockOrigin,
header: B::Header,
justification: Option<Justification>,
justifications: Option<Justifications>,
body: Option<Vec<B::Extrinsic>>,
) -> Result<(BlockImportParams<B, ()>, Option<Vec<(CacheKeyId, Vec<u8>)>>), String>;
}
@@ -102,13 +102,13 @@ pub trait Verifier<B: BlockT>: Send + Sync {
pub trait ImportQueue<B: BlockT>: Send {
/// Import bunch of blocks.
fn import_blocks(&mut self, origin: BlockOrigin, blocks: Vec<IncomingBlock<B>>);
/// Import a block justification.
fn import_justification(
/// Import block justifications.
fn import_justifications(
&mut self,
who: Origin,
hash: B::Hash,
number: NumberFor<B>,
justification: Justification
justifications: Justifications
);
/// Polls for actions to perform on the network.
///
@@ -182,8 +182,8 @@ pub(crate) fn import_single_block_metered<B: BlockT, V: Verifier<B>, Transaction
) -> Result<BlockImportResult<NumberFor<B>>, BlockImportError> {
let peer = block.origin;
let (header, justification) = match (block.header, block.justification) {
(Some(header), justification) => (header, justification),
let (header, justifications) = match (block.header, block.justifications) {
(Some(header), justifications) => (header, justifications),
(None, _) => {
if let Some(ref peer) = peer {
debug!(target: "sync", "Header {} was not provided by {} ", block.hash, peer);
@@ -238,7 +238,7 @@ pub(crate) fn import_single_block_metered<B: BlockT, V: Verifier<B>, Transaction
}
let started = wasm_timer::Instant::now();
let (mut import_block, maybe_keys) = verifier.verify(block_origin, header, justification, block.body)
let (mut import_block, maybe_keys) = verifier.verify(block_origin, header, justifications, block.body)
.map_err(|msg| {
if let Some(ref peer) = peer {
trace!(target: "sync", "Verifying {}({}) from {} failed: {}", number, hash, peer, msg);
@@ -18,7 +18,7 @@
use std::{pin::Pin, time::Duration, marker::PhantomData};
use futures::{prelude::*, task::Context, task::Poll};
use futures_timer::Delay;
use sp_runtime::{Justification, traits::{Block as BlockT, Header as HeaderT, NumberFor}};
use sp_runtime::{Justification, Justifications, traits::{Block as BlockT, Header as HeaderT, NumberFor}};
use sp_utils::mpsc::{TracingUnboundedSender, tracing_unbounded, TracingUnboundedReceiver};
use prometheus_endpoint::Registry;
@@ -112,22 +112,24 @@ impl<B: BlockT, Transaction: Send> ImportQueue<B> for BasicQueue<B, Transaction>
}
}
fn import_justification(
fn import_justifications(
&mut self,
who: Origin,
hash: B::Hash,
number: NumberFor<B>,
justification: Justification,
justifications: Justifications,
) {
let res = self.justification_sender.unbounded_send(
worker_messages::ImportJustification(who, hash, number, justification),
);
if res.is_err() {
log::error!(
target: "sync",
"import_justification: Background import task is no longer alive"
for justification in justifications {
let res = self.justification_sender.unbounded_send(
worker_messages::ImportJustification(who, hash, number, justification),
);
if res.is_err() {
log::error!(
target: "sync",
"import_justification: Background import task is no longer alive"
);
}
}
}
@@ -281,7 +283,7 @@ impl<B: BlockT> BlockImportWorker<B> {
who: Origin,
hash: B::Hash,
number: NumberFor<B>,
justification: Justification
justification: Justification,
) {
let started = wasm_timer::Instant::now();
let success = self.justification_import.as_mut().map(|justification_import| {
@@ -442,7 +444,7 @@ mod tests {
&mut self,
origin: BlockOrigin,
header: Header,
_justification: Option<Justification>,
_justifications: Option<Justifications>,
_body: Option<Vec<Extrinsic>>,
) -> Result<(BlockImportParams<Block, ()>, Option<Vec<(CacheKeyId, Vec<u8>)>>), String> {
Ok((BlockImportParams::new(origin, header), None))
@@ -541,7 +543,7 @@ mod tests {
hash,
header: Some(header),
body: None,
justification: None,
justifications: None,
origin: None,
allow_missing_state: false,
import_existing: false,
@@ -554,12 +556,11 @@ mod tests {
let mut import_justification = || {
let hash = Hash::random();
block_on(finality_sender.send(worker_messages::ImportJustification(
libp2p::PeerId::random(),
hash,
1,
Vec::new(),
(*b"TEST", Vec::new()),
)))
.unwrap();
@@ -30,7 +30,7 @@ use crate::traits::{
self, Member, Block as BlockT, Header as HeaderT, MaybeSerialize, MaybeMallocSizeOf,
NumberFor,
};
use crate::Justification;
use crate::Justifications;
/// Something to identify a block.
#[derive(PartialEq, Eq, Clone, RuntimeDebug)]
@@ -112,5 +112,5 @@ pub struct SignedBlock<Block> {
/// Full block.
pub block: Block,
/// Block justification.
pub justification: Option<Justification>,
pub justifications: Option<Justifications>,
}
+59 -1
View File
@@ -96,7 +96,65 @@ pub use either::Either;
/// the block itself would allow swapping justifications to change the block's hash
/// (and thus fork the chain). Sending a `Justification` alongside a block instead
/// bypasses this problem.
pub type Justification = Vec<u8>;
///
/// Each justification is provided as an encoded blob, and is tagged with an ID
/// to identify the consensus engine that generated the proof (we might have
/// multiple justifications from different engines for the same block).
pub type Justification = (ConsensusEngineId, EncodedJustification);
/// The encoded justification specific to a consensus engine.
pub type EncodedJustification = Vec<u8>;
/// Collection of justifications for a given block, multiple justifications may
/// be provided by different consensus engines for the same block.
#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]
#[derive(Debug, Clone, PartialEq, Eq, Encode, Decode)]
pub struct Justifications(Vec<Justification>);
impl Justifications {
/// Return an iterator over the justifications.
pub fn iter(&self) -> impl Iterator<Item = &Justification> {
self.0.iter()
}
/// Append a justification. Returns false if a justification with the same
/// `ConsensusEngineId` already exists, in which case the justification is
/// not inserted.
pub fn append(&mut self, justification: Justification) -> bool {
if self.get(justification.0).is_some() {
return false;
}
self.0.push(justification);
true
}
/// Return the encoded justification for the given consensus engine, if it
/// exists.
pub fn get(&self, engine_id: ConsensusEngineId) -> Option<&EncodedJustification> {
self.iter().find(|j| j.0 == engine_id).map(|j| &j.1)
}
/// Return a copy of the encoded justification for the given consensus
/// engine, if it exists.
pub fn into_justification(self, engine_id: ConsensusEngineId) -> Option<EncodedJustification> {
self.into_iter().find(|j| j.0 == engine_id).map(|j| j.1)
}
}
impl IntoIterator for Justifications {
type Item = Justification;
type IntoIter = sp_std::vec::IntoIter<Self::Item>;
fn into_iter(self) -> Self::IntoIter {
self.0.into_iter()
}
}
impl From<Justification> for Justifications {
fn from(justification: Justification) -> Self {
Self(vec![justification])
}
}
use traits::{Verify, Lazy};
@@ -24,7 +24,7 @@ use sp_consensus::{
BlockImportParams, BlockImport, BlockOrigin, Error as ConsensusError,
ForkChoiceStrategy,
};
use sp_runtime::Justification;
use sp_runtime::{Justification, Justifications};
use sp_runtime::traits::{Block as BlockT};
use sp_runtime::generic::BlockId;
use codec::alloc::collections::hash_map::HashMap;
@@ -51,15 +51,14 @@ pub trait ClientBlockImportExt<Block: BlockT>: Sized {
fn import_as_best(&mut self, origin: BlockOrigin, block: Block) -> Result<(), ConsensusError>;
/// Import a block and finalize it.
fn import_as_final(&mut self, origin: BlockOrigin, block: Block)
-> Result<(), ConsensusError>;
fn import_as_final(&mut self, origin: BlockOrigin, block: Block) -> Result<(), ConsensusError>;
/// Import block with justification, finalizes block.
/// Import block with justification(s), finalizes block.
fn import_justified(
&mut self,
origin: BlockOrigin,
block: Block,
justification: Justification
justifications: Justifications,
) -> Result<(), ConsensusError>;
}
@@ -119,11 +118,11 @@ impl<Block: BlockT, T, Transaction> ClientBlockImportExt<Block> for std::sync::A
&mut self,
origin: BlockOrigin,
block: Block,
justification: Justification,
justifications: Justifications,
) -> Result<(), ConsensusError> {
let (header, extrinsics) = block.deconstruct();
let mut import = BlockImportParams::new(origin, header);
import.justification = Some(justification);
import.justifications = Some(justifications);
import.body = Some(extrinsics);
import.finalized = true;
import.fork_choice = Some(ForkChoiceStrategy::LongestChain);
@@ -168,11 +167,11 @@ impl<B, E, RA, Block: BlockT> ClientBlockImportExt<Block> for Client<B, E, Block
&mut self,
origin: BlockOrigin,
block: Block,
justification: Justification,
justifications: Justifications,
) -> Result<(), ConsensusError> {
let (header, extrinsics) = block.deconstruct();
let mut import = BlockImportParams::new(origin, header);
import.justification = Some(justification);
import.justifications = Some(justifications);
import.body = Some(extrinsics);
import.finalized = true;
import.fork_choice = Some(ForkChoiceStrategy::LongestChain);