mirror of
https://github.com/pezkuwichain/pezkuwi-subxt.git
synced 2026-05-30 22:11:02 +00:00
Light GRANDPA import handler (#1669)
* GrandpaLightBlockImport * extract authorities in AuraVerifier * post-merge fix * restore authorities cache * license * new finality proof draft * generalized PendingJustifications * finality proof messages * fixed compilation * pass verifier to import_finality_proof * do not fetch remote proof from light import directly * FinalityProofProvider * fixed authorities cache test * restored finality proof tests * finality_proof docs * use DB backend in test client * justification_is_fetched_by_light_client_when_consensus_data_changes * restore justification_is_fetched_by_light_client_when_consensus_data_changes * some more tests * added authorities-related TODO * removed unneeded clear_finality_proof_requests field * truncated some long lines * more granular light import tests * only provide finality proof if it is generated by the requested set * post-merge fix * finality_proof_is_none_if_first_justification_is_generated_by_unknown_set * make light+grandpa test rely on finality proofs (instead of simple justifications) * empty_finality_proof_is_returned_to_light_client_when_authority_set_is_different * missing trait method impl * fixed proof-of-finality docs * one more doc fix * fix docs * initialize authorities cache (post-merge fix) * fixed cache initialization (post-merge fix) * post-fix merge: fix light + GRANDPA tests (bad way) * proper fix of empty_finality_proof_is_returned_to_light_client_when_authority_set_is_different * fixed easy grumbles * import finality proofs in BlockImportWorker thread * allow import of finality proofs for non-requested blocks * limit number of fragments in finality proof * GRANDPA post-merge fix * BABE: pos-merge fix
This commit is contained in:
committed by
Gavin Wood
parent
258f0835e4
commit
22586113ea
@@ -105,7 +105,7 @@ where H: Clone + Debug + PartialEq,
|
||||
}
|
||||
}
|
||||
|
||||
fn load_decode<B: AuxStore, T: Decode>(backend: &B, key: &[u8]) -> ClientResult<Option<T>> {
|
||||
pub(crate) fn load_decode<B: AuxStore, T: Decode>(backend: &B, key: &[u8]) -> ClientResult<Option<T>> {
|
||||
match backend.get_aux(key)? {
|
||||
None => Ok(None),
|
||||
Some(t) => T::decode(&mut &t[..])
|
||||
|
||||
@@ -32,6 +32,11 @@ impl<H, N> ConsensusChanges<H, N> {
|
||||
|
||||
impl<H: Copy + PartialEq, N: Copy + Ord> ConsensusChanges<H, N> {
|
||||
|
||||
/// Returns reference to all pending changes.
|
||||
pub fn pending_changes(&self) -> &[(N, H)] {
|
||||
&self.pending_changes
|
||||
}
|
||||
|
||||
/// Note unfinalized change of consensus-related data.
|
||||
pub(crate) fn note_change(&mut self, at: (N, H)) {
|
||||
let idx = self.pending_changes
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -552,7 +552,7 @@ where
|
||||
enacts_change: bool,
|
||||
) -> Result<(), ConsensusError> {
|
||||
let justification = GrandpaJustification::decode_and_verify_finalizes(
|
||||
justification,
|
||||
&justification,
|
||||
(hash, number),
|
||||
self.authority_set.set_id(),
|
||||
&self.authority_set.current_authorities(),
|
||||
|
||||
@@ -95,17 +95,16 @@ impl<Block: BlockT<Hash=H256>> GrandpaJustification<Block> {
|
||||
/// Decode a GRANDPA justification and validate the commit and the votes'
|
||||
/// ancestry proofs finalize the given block.
|
||||
pub(crate) fn decode_and_verify_finalizes(
|
||||
encoded: Vec<u8>,
|
||||
encoded: &[u8],
|
||||
finalized_target: (Block::Hash, NumberFor<Block>),
|
||||
set_id: u64,
|
||||
voters: &VoterSet<AuthorityId>,
|
||||
) -> Result<GrandpaJustification<Block>, ClientError> where
|
||||
NumberFor<Block>: grandpa::BlockNumberOps,
|
||||
{
|
||||
let justification = GrandpaJustification::<Block>::decode(&mut &*encoded).ok_or_else(|| {
|
||||
let msg = "failed to decode grandpa justification".to_string();
|
||||
ClientError::from(ClientError::BadJustification(msg))
|
||||
})?;
|
||||
|
||||
let justification = GrandpaJustification::<Block>::decode(&mut &*encoded)
|
||||
.ok_or(ClientError::JustificationDecode)?;
|
||||
|
||||
if (justification.commit.target_hash, justification.commit.target_number) != finalized_target {
|
||||
let msg = "invalid commit target in grandpa justification".to_string();
|
||||
|
||||
@@ -93,15 +93,17 @@ mod environment;
|
||||
mod finality_proof;
|
||||
mod import;
|
||||
mod justification;
|
||||
mod light_import;
|
||||
mod observer;
|
||||
mod until_imported;
|
||||
|
||||
#[cfg(feature="service-integration")]
|
||||
mod service_integration;
|
||||
#[cfg(feature="service-integration")]
|
||||
pub use service_integration::{LinkHalfForService, BlockImportForService};
|
||||
pub use service_integration::{LinkHalfForService, BlockImportForService, BlockImportForLightService};
|
||||
pub use communication::Network;
|
||||
pub use finality_proof::{prove_finality, check_finality_proof};
|
||||
pub use finality_proof::FinalityProofProvider;
|
||||
pub use light_import::light_block_import;
|
||||
pub use observer::run_grandpa_observer;
|
||||
|
||||
use aux_schema::PersistentData;
|
||||
@@ -300,7 +302,7 @@ pub struct LinkHalf<B, E, Block: BlockT<Hash=H256>, RA, SC> {
|
||||
pub fn block_import<B, E, Block: BlockT<Hash=H256>, RA, PRA, SC>(
|
||||
client: Arc<Client<B, E, Block, RA>>,
|
||||
api: Arc<PRA>,
|
||||
select_chain: SC
|
||||
select_chain: SC,
|
||||
) -> Result<(
|
||||
GrandpaBlockImport<B, E, Block, RA, PRA, SC>,
|
||||
LinkHalf<B, E, Block, RA, SC>
|
||||
|
||||
@@ -0,0 +1,728 @@
|
||||
// Copyright 2019 Parity Technologies (UK) Ltd.
|
||||
// This file is part of Substrate.
|
||||
|
||||
// Substrate is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
|
||||
// Substrate is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Substrate. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
use std::collections::HashMap;
|
||||
use std::sync::Arc;
|
||||
use log::{info, trace, warn};
|
||||
use parking_lot::RwLock;
|
||||
|
||||
use client::{
|
||||
CallExecutor, Client,
|
||||
backend::{AuxStore, Backend},
|
||||
blockchain::HeaderBackend,
|
||||
error::Error as ClientError,
|
||||
};
|
||||
use parity_codec::{Encode, Decode};
|
||||
use consensus_common::{
|
||||
import_queue::{Verifier, SharedFinalityProofRequestBuilder}, well_known_cache_keys,
|
||||
BlockOrigin, BlockImport, FinalityProofImport, ImportBlock, ImportResult, ImportedAux,
|
||||
Error as ConsensusError, ErrorKind as ConsensusErrorKind, FinalityProofRequestBuilder,
|
||||
};
|
||||
use runtime_primitives::Justification;
|
||||
use runtime_primitives::traits::{
|
||||
NumberFor, Block as BlockT, Header as HeaderT, ProvideRuntimeApi, DigestFor,
|
||||
};
|
||||
use fg_primitives::GrandpaApi;
|
||||
use runtime_primitives::generic::BlockId;
|
||||
use substrate_primitives::{H256, Blake2Hasher, ed25519::Public as AuthorityId};
|
||||
|
||||
use crate::aux_schema::load_decode;
|
||||
use crate::consensus_changes::ConsensusChanges;
|
||||
use crate::environment::canonical_at_height;
|
||||
use crate::finality_proof::{AuthoritySetForFinalityChecker, ProvableJustification, make_finality_proof_request};
|
||||
use crate::justification::GrandpaJustification;
|
||||
|
||||
/// LightAuthoritySet is saved under this key in aux storage.
|
||||
const LIGHT_AUTHORITY_SET_KEY: &[u8] = b"grandpa_voters";
|
||||
/// ConsensusChanges is saver under this key in aux storage.
|
||||
const LIGHT_CONSENSUS_CHANGES_KEY: &[u8] = b"grandpa_consensus_changes";
|
||||
|
||||
/// Create light block importer.
|
||||
pub fn light_block_import<B, E, Block: BlockT<Hash=H256>, RA, PRA>(
|
||||
client: Arc<Client<B, E, Block, RA>>,
|
||||
authority_set_provider: Arc<AuthoritySetForFinalityChecker<Block>>,
|
||||
api: Arc<PRA>,
|
||||
) -> Result<GrandpaLightBlockImport<B, E, Block, RA>, ClientError>
|
||||
where
|
||||
B: Backend<Block, Blake2Hasher> + 'static,
|
||||
E: CallExecutor<Block, Blake2Hasher> + 'static + Clone + Send + Sync,
|
||||
RA: Send + Sync,
|
||||
PRA: ProvideRuntimeApi,
|
||||
PRA::Api: GrandpaApi<Block>,
|
||||
{
|
||||
let info = client.info()?;
|
||||
let import_data = load_aux_import_data(info.chain.finalized_hash, &**client.backend(), api)?;
|
||||
Ok(GrandpaLightBlockImport {
|
||||
client,
|
||||
authority_set_provider,
|
||||
data: Arc::new(RwLock::new(import_data)),
|
||||
})
|
||||
}
|
||||
|
||||
/// A light block-import handler for GRANDPA.
|
||||
///
|
||||
/// It is responsible for:
|
||||
/// - checking GRANDPA justifications;
|
||||
/// - fetching finality proofs for blocks that are enacting consensus changes.
|
||||
pub struct GrandpaLightBlockImport<B, E, Block: BlockT<Hash=H256>, RA> {
|
||||
client: Arc<Client<B, E, Block, RA>>,
|
||||
authority_set_provider: Arc<AuthoritySetForFinalityChecker<Block>>,
|
||||
data: Arc<RwLock<LightImportData<Block>>>,
|
||||
}
|
||||
|
||||
/// Mutable data of light block importer.
|
||||
struct LightImportData<Block: BlockT<Hash=H256>> {
|
||||
last_finalized: Block::Hash,
|
||||
authority_set: LightAuthoritySet,
|
||||
consensus_changes: ConsensusChanges<Block::Hash, NumberFor<Block>>,
|
||||
}
|
||||
|
||||
/// Latest authority set tracker.
|
||||
#[derive(Debug, Encode, Decode)]
|
||||
struct LightAuthoritySet {
|
||||
set_id: u64,
|
||||
authorities: Vec<(AuthorityId, u64)>,
|
||||
}
|
||||
|
||||
impl<B, E, Block: BlockT<Hash=H256>, RA> GrandpaLightBlockImport<B, E, Block, RA> {
|
||||
/// Create finality proof request builder.
|
||||
pub fn create_finality_proof_request_builder(&self) -> SharedFinalityProofRequestBuilder<Block> {
|
||||
Arc::new(GrandpaFinalityProofRequestBuilder(self.data.clone())) as _
|
||||
}
|
||||
}
|
||||
|
||||
impl<B, E, Block: BlockT<Hash=H256>, RA> BlockImport<Block>
|
||||
for GrandpaLightBlockImport<B, E, Block, RA> where
|
||||
NumberFor<Block>: grandpa::BlockNumberOps,
|
||||
B: Backend<Block, Blake2Hasher> + 'static,
|
||||
E: CallExecutor<Block, Blake2Hasher> + 'static + Clone + Send + Sync,
|
||||
DigestFor<Block>: Encode,
|
||||
RA: Send + Sync,
|
||||
{
|
||||
type Error = ConsensusError;
|
||||
|
||||
fn import_block(
|
||||
&self,
|
||||
block: ImportBlock<Block>,
|
||||
new_cache: HashMap<well_known_cache_keys::Id, Vec<u8>>,
|
||||
) -> Result<ImportResult, Self::Error> {
|
||||
do_import_block::<_, _, _, _, GrandpaJustification<Block>>(&*self.client, &mut *self.data.write(), block, new_cache)
|
||||
}
|
||||
|
||||
fn check_block(
|
||||
&self,
|
||||
hash: Block::Hash,
|
||||
parent_hash: Block::Hash,
|
||||
) -> Result<ImportResult, Self::Error> {
|
||||
self.client.check_block(hash, parent_hash)
|
||||
}
|
||||
}
|
||||
|
||||
impl<B, E, Block: BlockT<Hash=H256>, RA> FinalityProofImport<Block>
|
||||
for GrandpaLightBlockImport<B, E, Block, RA> where
|
||||
NumberFor<Block>: grandpa::BlockNumberOps,
|
||||
B: Backend<Block, Blake2Hasher> + 'static,
|
||||
E: CallExecutor<Block, Blake2Hasher> + 'static + Clone + Send + Sync,
|
||||
DigestFor<Block>: Encode,
|
||||
RA: Send + Sync,
|
||||
{
|
||||
type Error = ConsensusError;
|
||||
|
||||
fn on_start(&self, link: &::consensus_common::import_queue::Link<Block>) {
|
||||
let chain_info = match self.client.info() {
|
||||
Ok(info) => info.chain,
|
||||
_ => return,
|
||||
};
|
||||
|
||||
let data = self.data.read();
|
||||
for (pending_number, pending_hash) in data.consensus_changes.pending_changes() {
|
||||
if *pending_number > chain_info.finalized_number && *pending_number <= chain_info.best_number {
|
||||
link.request_finality_proof(pending_hash, *pending_number);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn import_finality_proof(
|
||||
&self,
|
||||
hash: Block::Hash,
|
||||
number: NumberFor<Block>,
|
||||
finality_proof: Vec<u8>,
|
||||
verifier: &Verifier<Block>,
|
||||
) -> Result<(Block::Hash, NumberFor<Block>), Self::Error> {
|
||||
do_import_finality_proof::<_, _, _, _, GrandpaJustification<Block>>(
|
||||
&*self.client,
|
||||
&*self.authority_set_provider,
|
||||
&mut *self.data.write(),
|
||||
hash,
|
||||
number,
|
||||
finality_proof,
|
||||
verifier,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
impl LightAuthoritySet {
|
||||
/// Get a genesis set with given authorities.
|
||||
pub fn genesis(initial: Vec<(AuthorityId, u64)>) -> Self {
|
||||
LightAuthoritySet {
|
||||
set_id: 0,
|
||||
authorities: initial,
|
||||
}
|
||||
}
|
||||
|
||||
/// Get latest set id.
|
||||
pub fn set_id(&self) -> u64 {
|
||||
self.set_id
|
||||
}
|
||||
|
||||
/// Get latest authorities set.
|
||||
pub fn authorities(&self) -> Vec<(AuthorityId, u64)> {
|
||||
self.authorities.clone()
|
||||
}
|
||||
|
||||
/// Set new authorities set.
|
||||
pub fn update(&mut self, set_id: u64, authorities: Vec<(AuthorityId, u64)>) {
|
||||
self.set_id = set_id;
|
||||
std::mem::replace(&mut self.authorities, authorities);
|
||||
}
|
||||
}
|
||||
|
||||
struct GrandpaFinalityProofRequestBuilder<B: BlockT<Hash=H256>>(Arc<RwLock<LightImportData<B>>>);
|
||||
|
||||
impl<B: BlockT<Hash=H256>> FinalityProofRequestBuilder<B> for GrandpaFinalityProofRequestBuilder<B> {
|
||||
fn build_request_data(&self, _hash: &B::Hash) -> Vec<u8> {
|
||||
let data = self.0.read();
|
||||
make_finality_proof_request(
|
||||
data.last_finalized,
|
||||
data.authority_set.set_id(),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/// Try to import new block.
|
||||
fn do_import_block<B, E, Block: BlockT<Hash=H256>, RA, J>(
|
||||
client: &Client<B, E, Block, RA>,
|
||||
data: &mut LightImportData<Block>,
|
||||
mut block: ImportBlock<Block>,
|
||||
new_cache: HashMap<well_known_cache_keys::Id, Vec<u8>>,
|
||||
) -> Result<ImportResult, ConsensusError>
|
||||
where
|
||||
B: Backend<Block, Blake2Hasher> + 'static,
|
||||
E: CallExecutor<Block, Blake2Hasher> + 'static + Clone + Send + Sync,
|
||||
RA: Send + Sync,
|
||||
NumberFor<Block>: grandpa::BlockNumberOps,
|
||||
DigestFor<Block>: Encode,
|
||||
J: ProvableJustification<Block::Header>,
|
||||
{
|
||||
let hash = block.post_header().hash();
|
||||
let number = block.header.number().clone();
|
||||
|
||||
// we don't want to finalize on `inner.import_block`
|
||||
let justification = block.justification.take();
|
||||
let enacts_consensus_change = !new_cache.is_empty();
|
||||
let import_result = client.import_block(block, new_cache);
|
||||
|
||||
let mut imported_aux = match import_result {
|
||||
Ok(ImportResult::Imported(aux)) => aux,
|
||||
Ok(r) => return Ok(r),
|
||||
Err(e) => return Err(ConsensusErrorKind::ClientImport(e.to_string()).into()),
|
||||
};
|
||||
|
||||
match justification {
|
||||
Some(justification) => {
|
||||
trace!(
|
||||
target: "finality",
|
||||
"Imported block {}{}. Importing justification.",
|
||||
if enacts_consensus_change { " which enacts consensus changes" } else { "" },
|
||||
hash,
|
||||
);
|
||||
|
||||
do_import_justification::<_, _, _, _, J>(client, data, hash, number, justification)
|
||||
},
|
||||
None if enacts_consensus_change => {
|
||||
trace!(
|
||||
target: "finality",
|
||||
"Imported block {} which enacts consensus changes. Requesting finality proof.",
|
||||
hash,
|
||||
);
|
||||
|
||||
// remember that we need finality proof for this block
|
||||
imported_aux.needs_finality_proof = true;
|
||||
data.consensus_changes.note_change((number, hash));
|
||||
Ok(ImportResult::Imported(imported_aux))
|
||||
},
|
||||
None => Ok(ImportResult::Imported(imported_aux)),
|
||||
}
|
||||
}
|
||||
|
||||
/// Try to import finality proof.
|
||||
fn do_import_finality_proof<B, E, Block: BlockT<Hash=H256>, RA, J>(
|
||||
client: &Client<B, E, Block, RA>,
|
||||
authority_set_provider: &AuthoritySetForFinalityChecker<Block>,
|
||||
data: &mut LightImportData<Block>,
|
||||
_hash: Block::Hash,
|
||||
_number: NumberFor<Block>,
|
||||
finality_proof: Vec<u8>,
|
||||
verifier: &Verifier<Block>,
|
||||
) -> Result<(Block::Hash, NumberFor<Block>), ConsensusError>
|
||||
where
|
||||
B: Backend<Block, Blake2Hasher> + 'static,
|
||||
E: CallExecutor<Block, Blake2Hasher> + 'static + Clone + Send + Sync,
|
||||
RA: Send + Sync,
|
||||
DigestFor<Block>: Encode,
|
||||
NumberFor<Block>: grandpa::BlockNumberOps,
|
||||
J: ProvableJustification<Block::Header>,
|
||||
{
|
||||
let authority_set_id = data.authority_set.set_id();
|
||||
let authorities = data.authority_set.authorities();
|
||||
let finality_effects = crate::finality_proof::check_finality_proof(
|
||||
&*client.backend().blockchain(),
|
||||
authority_set_id,
|
||||
authorities,
|
||||
authority_set_provider,
|
||||
finality_proof,
|
||||
).map_err(|e| ConsensusError::from(ConsensusErrorKind::ClientImport(e.to_string())))?;
|
||||
|
||||
// try to import all new headers
|
||||
let block_origin = BlockOrigin::NetworkBroadcast;
|
||||
for header_to_import in finality_effects.headers_to_import {
|
||||
let (block_to_import, new_authorities) = verifier.verify(block_origin, header_to_import, None, None)?;
|
||||
assert!(block_to_import.justification.is_none(), "We have passed None as justification to verifier.verify");
|
||||
|
||||
let mut cache = HashMap::new();
|
||||
if let Some(authorities) = new_authorities {
|
||||
cache.insert(well_known_cache_keys::AUTHORITIES, authorities.encode());
|
||||
}
|
||||
do_import_block::<_, _, _, _, J>(client, data, block_to_import, cache)?;
|
||||
}
|
||||
|
||||
// try to import latest justification
|
||||
let finalized_block_hash = finality_effects.block;
|
||||
let finalized_block_number = client.backend().blockchain()
|
||||
.expect_block_number_from_id(&BlockId::Hash(finality_effects.block))
|
||||
.map_err(|e| ConsensusError::from(ConsensusErrorKind::ClientImport(e.to_string())))?;
|
||||
do_finalize_block(
|
||||
client,
|
||||
data,
|
||||
finalized_block_hash,
|
||||
finalized_block_number,
|
||||
finality_effects.justification.encode(),
|
||||
)?;
|
||||
|
||||
// apply new authorities set
|
||||
data.authority_set.update(
|
||||
finality_effects.new_set_id,
|
||||
finality_effects.new_authorities,
|
||||
);
|
||||
|
||||
Ok((finalized_block_hash, finalized_block_number))
|
||||
}
|
||||
|
||||
/// Try to import justification.
|
||||
fn do_import_justification<B, E, Block: BlockT<Hash=H256>, RA, J>(
|
||||
client: &Client<B, E, Block, RA>,
|
||||
data: &mut LightImportData<Block>,
|
||||
hash: Block::Hash,
|
||||
number: NumberFor<Block>,
|
||||
justification: Justification,
|
||||
) -> Result<ImportResult, ConsensusError>
|
||||
where
|
||||
B: Backend<Block, Blake2Hasher> + 'static,
|
||||
E: CallExecutor<Block, Blake2Hasher> + 'static + Clone + Send + Sync,
|
||||
RA: Send + Sync,
|
||||
NumberFor<Block>: grandpa::BlockNumberOps,
|
||||
J: ProvableJustification<Block::Header>,
|
||||
{
|
||||
// with justification, we have two cases
|
||||
//
|
||||
// optimistic: the same GRANDPA authorities set has generated intermediate justification
|
||||
// => justification is verified using current authorities set + we could proceed further
|
||||
//
|
||||
// pessimistic scenario: the GRANDPA authorities set has changed
|
||||
// => we need to fetch new authorities set (i.e. finality proof) from remote node
|
||||
|
||||
// first, try to behave optimistically
|
||||
let authority_set_id = data.authority_set.set_id();
|
||||
let justification = J::decode_and_verify(
|
||||
&justification,
|
||||
authority_set_id,
|
||||
&data.authority_set.authorities(),
|
||||
);
|
||||
|
||||
// BadJustification error means that justification has been successfully decoded, but
|
||||
// it isn't valid within current authority set
|
||||
let justification = match justification {
|
||||
Err(ClientError::BadJustification(_)) => {
|
||||
trace!(
|
||||
target: "finality",
|
||||
"Justification for {} is not valid within current authorities set. Requesting finality proof.",
|
||||
hash,
|
||||
);
|
||||
|
||||
let mut imported_aux = ImportedAux::default();
|
||||
imported_aux.needs_finality_proof = true;
|
||||
return Ok(ImportResult::Imported(imported_aux));
|
||||
},
|
||||
Err(e) => {
|
||||
trace!(
|
||||
target: "finality",
|
||||
"Justification for {} is not valid. Bailing.",
|
||||
hash,
|
||||
);
|
||||
|
||||
return Err(ConsensusErrorKind::ClientImport(e.to_string()).into());
|
||||
},
|
||||
Ok(justification) => {
|
||||
trace!(
|
||||
target: "finality",
|
||||
"Justification for {} is valid. Finalizing the block.",
|
||||
hash,
|
||||
);
|
||||
|
||||
justification
|
||||
},
|
||||
};
|
||||
|
||||
// finalize the block
|
||||
do_finalize_block(client, data, hash, number, justification.encode())
|
||||
}
|
||||
|
||||
/// Finalize the block.
|
||||
fn do_finalize_block<B, E, Block: BlockT<Hash=H256>, RA>(
|
||||
client: &Client<B, E, Block, RA>,
|
||||
data: &mut LightImportData<Block>,
|
||||
hash: Block::Hash,
|
||||
number: NumberFor<Block>,
|
||||
justification: Justification,
|
||||
) -> Result<ImportResult, ConsensusError>
|
||||
where
|
||||
B: Backend<Block, Blake2Hasher> + 'static,
|
||||
E: CallExecutor<Block, Blake2Hasher> + 'static + Clone + Send + Sync,
|
||||
RA: Send + Sync,
|
||||
NumberFor<Block>: grandpa::BlockNumberOps,
|
||||
{
|
||||
// finalize the block
|
||||
client.finalize_block(BlockId::Hash(hash), Some(justification), true).map_err(|e| {
|
||||
warn!(target: "finality", "Error applying finality to block {:?}: {:?}", (hash, number), e);
|
||||
ConsensusError::from(ConsensusErrorKind::ClientImport(e.to_string()))
|
||||
})?;
|
||||
|
||||
// forget obsoleted consensus changes
|
||||
let consensus_finalization_res = data.consensus_changes
|
||||
.finalize((number, hash), |at_height| canonical_at_height(&client, (hash, number), true, at_height));
|
||||
match consensus_finalization_res {
|
||||
Ok((true, _)) => require_insert_aux(
|
||||
&client,
|
||||
LIGHT_CONSENSUS_CHANGES_KEY,
|
||||
&data.consensus_changes,
|
||||
"consensus changes",
|
||||
)?,
|
||||
Ok(_) => (),
|
||||
Err(error) => return Err(on_post_finalization_error(error, "consensus changes")),
|
||||
}
|
||||
|
||||
// update last finalized block reference
|
||||
data.last_finalized = hash;
|
||||
|
||||
Ok(ImportResult::imported())
|
||||
}
|
||||
|
||||
/// Load light import aux data from the store.
|
||||
fn load_aux_import_data<B, Block: BlockT<Hash=H256>, PRA>(
|
||||
last_finalized: Block::Hash,
|
||||
aux_store: &B,
|
||||
api: Arc<PRA>,
|
||||
) -> Result<LightImportData<Block>, ClientError>
|
||||
where
|
||||
B: AuxStore,
|
||||
PRA: ProvideRuntimeApi,
|
||||
PRA::Api: GrandpaApi<Block>,
|
||||
{
|
||||
use runtime_primitives::traits::Zero;
|
||||
let authority_set = match load_decode(aux_store, LIGHT_AUTHORITY_SET_KEY)? {
|
||||
Some(authority_set) => authority_set,
|
||||
None => {
|
||||
info!(target: "afg", "Loading GRANDPA authorities \
|
||||
from genesis on what appears to be first startup.");
|
||||
|
||||
// no authority set on disk: fetch authorities from genesis state
|
||||
let genesis_authorities = api.runtime_api().grandpa_authorities(&BlockId::number(Zero::zero()))?;
|
||||
|
||||
let authority_set = LightAuthoritySet::genesis(genesis_authorities);
|
||||
let encoded = authority_set.encode();
|
||||
aux_store.insert_aux(&[(LIGHT_AUTHORITY_SET_KEY, &encoded[..])], &[])?;
|
||||
|
||||
authority_set
|
||||
},
|
||||
};
|
||||
|
||||
let consensus_changes = match load_decode(aux_store, LIGHT_CONSENSUS_CHANGES_KEY)? {
|
||||
Some(consensus_changes) => consensus_changes,
|
||||
None => {
|
||||
let consensus_changes = ConsensusChanges::<Block::Hash, NumberFor<Block>>::empty();
|
||||
|
||||
let encoded = authority_set.encode();
|
||||
aux_store.insert_aux(&[(LIGHT_CONSENSUS_CHANGES_KEY, &encoded[..])], &[])?;
|
||||
|
||||
consensus_changes
|
||||
},
|
||||
};
|
||||
|
||||
Ok(LightImportData {
|
||||
last_finalized,
|
||||
authority_set,
|
||||
consensus_changes,
|
||||
})
|
||||
}
|
||||
|
||||
/// Insert into aux store. If failed, return error && show inconsistency warning.
|
||||
fn require_insert_aux<T: Encode, B, E, Block: BlockT<Hash=H256>, RA>(
|
||||
client: &Client<B, E, Block, RA>,
|
||||
key: &[u8],
|
||||
value: &T,
|
||||
value_type: &str,
|
||||
) -> Result<(), ConsensusError>
|
||||
where
|
||||
B: Backend<Block, Blake2Hasher> + 'static,
|
||||
E: CallExecutor<Block, Blake2Hasher> + 'static + Clone + Send + Sync,
|
||||
{
|
||||
let backend = &**client.backend();
|
||||
let encoded = value.encode();
|
||||
let update_res = Backend::insert_aux(backend, &[(key, &encoded[..])], &[]);
|
||||
if let Err(error) = update_res {
|
||||
return Err(on_post_finalization_error(error, value_type));
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Display inconsistency warning.
|
||||
fn on_post_finalization_error(error: ClientError, value_type: &str) -> ConsensusError {
|
||||
warn!(target: "finality", "Failed to write updated {} to disk. Bailing.", value_type);
|
||||
warn!(target: "finality", "Node is in a potentially inconsistent state.");
|
||||
ConsensusError::from(ConsensusErrorKind::ClientImport(error.to_string()))
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
pub mod tests {
|
||||
use super::*;
|
||||
use consensus_common::ForkChoiceStrategy;
|
||||
use substrate_primitives::H256;
|
||||
use test_client::client::in_mem::Blockchain as InMemoryAuxStore;
|
||||
use test_client::runtime::{Block, Header};
|
||||
use crate::tests::TestApi;
|
||||
use crate::finality_proof::tests::TestJustification;
|
||||
|
||||
pub struct NoJustificationsImport<B, E, Block: BlockT<Hash=H256>, RA>(
|
||||
pub GrandpaLightBlockImport<B, E, Block, RA>
|
||||
);
|
||||
|
||||
impl<B, E, Block: BlockT<Hash=H256>, RA> BlockImport<Block>
|
||||
for NoJustificationsImport<B, E, Block, RA> where
|
||||
NumberFor<Block>: grandpa::BlockNumberOps,
|
||||
B: Backend<Block, Blake2Hasher> + 'static,
|
||||
E: CallExecutor<Block, Blake2Hasher> + 'static + Clone + Send + Sync,
|
||||
DigestFor<Block>: Encode,
|
||||
RA: Send + Sync,
|
||||
{
|
||||
type Error = ConsensusError;
|
||||
|
||||
fn import_block(
|
||||
&self,
|
||||
mut block: ImportBlock<Block>,
|
||||
new_cache: HashMap<well_known_cache_keys::Id, Vec<u8>>,
|
||||
) -> Result<ImportResult, Self::Error> {
|
||||
block.justification.take();
|
||||
self.0.import_block(block, new_cache)
|
||||
}
|
||||
|
||||
fn check_block(
|
||||
&self,
|
||||
hash: Block::Hash,
|
||||
parent_hash: Block::Hash,
|
||||
) -> Result<ImportResult, Self::Error> {
|
||||
self.0.check_block(hash, parent_hash)
|
||||
}
|
||||
}
|
||||
|
||||
impl<B, E, Block: BlockT<Hash=H256>, RA> FinalityProofImport<Block>
|
||||
for NoJustificationsImport<B, E, Block, RA> where
|
||||
NumberFor<Block>: grandpa::BlockNumberOps,
|
||||
B: Backend<Block, Blake2Hasher> + 'static,
|
||||
E: CallExecutor<Block, Blake2Hasher> + 'static + Clone + Send + Sync,
|
||||
DigestFor<Block>: Encode,
|
||||
RA: Send + Sync,
|
||||
{
|
||||
type Error = ConsensusError;
|
||||
|
||||
fn on_start(&self, link: &::consensus_common::import_queue::Link<Block>) {
|
||||
self.0.on_start(link)
|
||||
}
|
||||
|
||||
fn import_finality_proof(
|
||||
&self,
|
||||
hash: Block::Hash,
|
||||
number: NumberFor<Block>,
|
||||
finality_proof: Vec<u8>,
|
||||
verifier: &Verifier<Block>,
|
||||
) -> Result<(Block::Hash, NumberFor<Block>), Self::Error> {
|
||||
self.0.import_finality_proof(hash, number, finality_proof, verifier)
|
||||
}
|
||||
}
|
||||
|
||||
/// Creates light block import that ignores justifications that came outside of finality proofs.
|
||||
pub fn light_block_import_without_justifications<B, E, Block: BlockT<Hash=H256>, RA, PRA>(
|
||||
client: Arc<Client<B, E, Block, RA>>,
|
||||
authority_set_provider: Arc<AuthoritySetForFinalityChecker<Block>>,
|
||||
api: Arc<PRA>,
|
||||
) -> Result<NoJustificationsImport<B, E, Block, RA>, ClientError>
|
||||
where
|
||||
B: Backend<Block, Blake2Hasher> + 'static,
|
||||
E: CallExecutor<Block, Blake2Hasher> + 'static + Clone + Send + Sync,
|
||||
RA: Send + Sync,
|
||||
PRA: ProvideRuntimeApi,
|
||||
PRA::Api: GrandpaApi<Block>,
|
||||
{
|
||||
light_block_import(client, authority_set_provider, api).map(NoJustificationsImport)
|
||||
}
|
||||
|
||||
fn import_block(
|
||||
new_cache: HashMap<well_known_cache_keys::Id, Vec<u8>>,
|
||||
justification: Option<Justification>,
|
||||
) -> ImportResult {
|
||||
let client = test_client::new_light();
|
||||
let mut import_data = LightImportData {
|
||||
last_finalized: Default::default(),
|
||||
authority_set: LightAuthoritySet::genesis(vec![(AuthorityId([1; 32]), 1)]),
|
||||
consensus_changes: ConsensusChanges::empty(),
|
||||
};
|
||||
let block = ImportBlock {
|
||||
origin: BlockOrigin::Own,
|
||||
header: Header {
|
||||
number: 1,
|
||||
parent_hash: client.info().unwrap().chain.best_hash,
|
||||
state_root: Default::default(),
|
||||
digest: Default::default(),
|
||||
extrinsics_root: Default::default(),
|
||||
},
|
||||
justification,
|
||||
post_digests: Vec::new(),
|
||||
body: None,
|
||||
finalized: false,
|
||||
auxiliary: Vec::new(),
|
||||
fork_choice: ForkChoiceStrategy::LongestChain,
|
||||
};
|
||||
do_import_block::<_, _, _, _, TestJustification>(
|
||||
&client,
|
||||
&mut import_data,
|
||||
block,
|
||||
new_cache,
|
||||
).unwrap()
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn finality_proof_not_required_when_consensus_data_does_not_changes_and_no_justification_provided() {
|
||||
assert_eq!(import_block(HashMap::new(), None), ImportResult::Imported(ImportedAux {
|
||||
clear_justification_requests: false,
|
||||
needs_justification: false,
|
||||
bad_justification: false,
|
||||
needs_finality_proof: false,
|
||||
}));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn finality_proof_not_required_when_consensus_data_does_not_changes_and_correct_justification_provided() {
|
||||
let justification = TestJustification(true, Vec::new()).encode();
|
||||
assert_eq!(import_block(HashMap::new(), Some(justification)), ImportResult::Imported(ImportedAux {
|
||||
clear_justification_requests: false,
|
||||
needs_justification: false,
|
||||
bad_justification: false,
|
||||
needs_finality_proof: false,
|
||||
}));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn finality_proof_required_when_consensus_data_changes_and_no_justification_provided() {
|
||||
let mut cache = HashMap::new();
|
||||
cache.insert(well_known_cache_keys::AUTHORITIES, vec![AuthorityId([2; 32])].encode());
|
||||
assert_eq!(import_block(cache, None), ImportResult::Imported(ImportedAux {
|
||||
clear_justification_requests: false,
|
||||
needs_justification: false,
|
||||
bad_justification: false,
|
||||
needs_finality_proof: true,
|
||||
}));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn finality_proof_required_when_consensus_data_changes_and_incorrect_justification_provided() {
|
||||
let justification = TestJustification(false, Vec::new()).encode();
|
||||
let mut cache = HashMap::new();
|
||||
cache.insert(well_known_cache_keys::AUTHORITIES, vec![AuthorityId([2; 32])].encode());
|
||||
assert_eq!(
|
||||
import_block(cache, Some(justification)),
|
||||
ImportResult::Imported(ImportedAux {
|
||||
clear_justification_requests: false,
|
||||
needs_justification: false,
|
||||
bad_justification: false,
|
||||
needs_finality_proof: true,
|
||||
},
|
||||
));
|
||||
}
|
||||
|
||||
|
||||
#[test]
|
||||
fn aux_data_updated_on_start() {
|
||||
let aux_store = InMemoryAuxStore::<Block>::new();
|
||||
let api = Arc::new(TestApi::new(vec![(AuthorityId([1; 32]), 1)]));
|
||||
|
||||
// when aux store is empty initially
|
||||
assert!(aux_store.get_aux(LIGHT_AUTHORITY_SET_KEY).unwrap().is_none());
|
||||
assert!(aux_store.get_aux(LIGHT_CONSENSUS_CHANGES_KEY).unwrap().is_none());
|
||||
|
||||
// it is updated on importer start
|
||||
load_aux_import_data(Default::default(), &aux_store, api).unwrap();
|
||||
assert!(aux_store.get_aux(LIGHT_AUTHORITY_SET_KEY).unwrap().is_some());
|
||||
assert!(aux_store.get_aux(LIGHT_CONSENSUS_CHANGES_KEY).unwrap().is_some());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn aux_data_loaded_on_restart() {
|
||||
let aux_store = InMemoryAuxStore::<Block>::new();
|
||||
let api = Arc::new(TestApi::new(vec![(AuthorityId([1; 32]), 1)]));
|
||||
|
||||
// when aux store is non-empty initially
|
||||
let mut consensus_changes = ConsensusChanges::<H256, u64>::empty();
|
||||
consensus_changes.note_change((42, Default::default()));
|
||||
aux_store.insert_aux(
|
||||
&[
|
||||
(
|
||||
LIGHT_AUTHORITY_SET_KEY,
|
||||
LightAuthoritySet::genesis(vec![(AuthorityId([42; 32]), 2)]).encode().as_slice(),
|
||||
),
|
||||
(
|
||||
LIGHT_CONSENSUS_CHANGES_KEY,
|
||||
consensus_changes.encode().as_slice(),
|
||||
),
|
||||
],
|
||||
&[],
|
||||
).unwrap();
|
||||
|
||||
// importer uses it on start
|
||||
let data = load_aux_import_data(Default::default(), &aux_store, api).unwrap();
|
||||
assert_eq!(data.authority_set.authorities(), vec![(AuthorityId([42; 32]), 2)]);
|
||||
assert_eq!(data.consensus_changes.pending_changes(), &[(42, Default::default())]);
|
||||
}
|
||||
}
|
||||
@@ -17,7 +17,7 @@
|
||||
/// Integrate grandpa finality with substrate service
|
||||
|
||||
use client;
|
||||
use service::{FullBackend, FullExecutor, ServiceFactory};
|
||||
use service::{FullBackend, FullExecutor, LightBackend, LightExecutor, ServiceFactory};
|
||||
|
||||
pub type BlockImportForService<F> = crate::GrandpaBlockImport<
|
||||
FullBackend<F>,
|
||||
@@ -25,12 +25,12 @@ pub type BlockImportForService<F> = crate::GrandpaBlockImport<
|
||||
<F as ServiceFactory>::Block,
|
||||
<F as ServiceFactory>::RuntimeApi,
|
||||
client::Client<
|
||||
FullBackend<F>,
|
||||
FullExecutor<F>,
|
||||
<F as ServiceFactory>::Block,
|
||||
<F as ServiceFactory>::RuntimeApi
|
||||
>,
|
||||
<F as ServiceFactory>::SelectChain
|
||||
FullBackend<F>,
|
||||
FullExecutor<F>,
|
||||
<F as ServiceFactory>::Block,
|
||||
<F as ServiceFactory>::RuntimeApi
|
||||
>,
|
||||
<F as ServiceFactory>::SelectChain,
|
||||
>;
|
||||
|
||||
pub type LinkHalfForService<F> = crate::LinkHalf<
|
||||
@@ -40,3 +40,10 @@ pub type LinkHalfForService<F> = crate::LinkHalf<
|
||||
<F as ServiceFactory>::RuntimeApi,
|
||||
<F as ServiceFactory>::SelectChain
|
||||
>;
|
||||
|
||||
pub type BlockImportForLightService<F> = crate::light_import::GrandpaLightBlockImport<
|
||||
LightBackend<F>,
|
||||
LightExecutor<F>,
|
||||
<F as ServiceFactory>::Block,
|
||||
<F as ServiceFactory>::RuntimeApi,
|
||||
>;
|
||||
|
||||
@@ -25,21 +25,24 @@ use parking_lot::Mutex;
|
||||
use tokio::runtime::current_thread;
|
||||
use keyring::ed25519::{Keyring as AuthorityKeyring};
|
||||
use client::{
|
||||
BlockchainEvents, error::Result,
|
||||
blockchain::Backend as BlockchainBackend,
|
||||
error::Result,
|
||||
runtime_api::{Core, RuntimeVersion, ApiExt},
|
||||
LongestChain,
|
||||
};
|
||||
use test_client::{self, runtime::BlockNumber};
|
||||
use consensus_common::{BlockOrigin, ForkChoiceStrategy, ImportedAux, ImportBlock, ImportResult};
|
||||
use consensus_common::import_queue::{SharedBlockImport, SharedJustificationImport};
|
||||
use consensus_common::import_queue::{SharedBlockImport, SharedJustificationImport, SharedFinalityProofImport,
|
||||
SharedFinalityProofRequestBuilder,
|
||||
};
|
||||
use std::collections::{HashMap, HashSet};
|
||||
use std::result;
|
||||
use parity_codec::Decode;
|
||||
use runtime_primitives::traits::{ApiRef, ProvideRuntimeApi, Header as HeaderT};
|
||||
use runtime_primitives::generic::BlockId;
|
||||
use substrate_primitives::{NativeOrEncoded, ExecutionContext, ed25519::Public as AuthorityId};
|
||||
|
||||
use authorities::AuthoritySet;
|
||||
use finality_proof::{FinalityProofProvider, AuthoritySetForFinalityProver, AuthoritySetForFinalityChecker};
|
||||
use communication::GRANDPA_ENGINE_ID;
|
||||
use consensus_changes::ConsensusChanges;
|
||||
|
||||
@@ -72,7 +75,7 @@ impl GrandpaTestNet {
|
||||
};
|
||||
let config = Self::default_config();
|
||||
for _ in 0..n_peers {
|
||||
net.add_peer(&config);
|
||||
net.add_full_peer(&config);
|
||||
}
|
||||
net
|
||||
}
|
||||
@@ -99,27 +102,61 @@ impl TestNetFactory for GrandpaTestNet {
|
||||
}
|
||||
}
|
||||
|
||||
fn make_verifier(&self, _client: Arc<PeersClient>, _cfg: &ProtocolConfig)
|
||||
fn make_verifier(&self, _client: PeersClient, _cfg: &ProtocolConfig)
|
||||
-> Arc<Self::Verifier>
|
||||
{
|
||||
Arc::new(PassThroughVerifier(false)) // use non-instant finality.
|
||||
}
|
||||
|
||||
fn make_block_import(&self, client: Arc<PeersClient>)
|
||||
-> (SharedBlockImport<Block>, Option<SharedJustificationImport<Block>>, PeerData)
|
||||
fn make_block_import(&self, client: PeersClient)
|
||||
-> (
|
||||
SharedBlockImport<Block>,
|
||||
Option<SharedJustificationImport<Block>>,
|
||||
Option<SharedFinalityProofImport<Block>>,
|
||||
Option<SharedFinalityProofRequestBuilder<Block>>,
|
||||
PeerData,
|
||||
)
|
||||
{
|
||||
|
||||
let select_chain = LongestChain::new(
|
||||
client.backend().clone(),
|
||||
client.import_lock().clone()
|
||||
);
|
||||
let (import, link) = block_import(
|
||||
client,
|
||||
Arc::new(self.test_config.clone()),
|
||||
select_chain,
|
||||
).expect("Could not create block import for fresh peer.");
|
||||
let shared_import = Arc::new(import);
|
||||
(shared_import.clone(), Some(shared_import), Mutex::new(Some(link)))
|
||||
match client {
|
||||
PeersClient::Full(ref client) => {
|
||||
let select_chain = LongestChain::new(
|
||||
client.backend().clone(),
|
||||
client.import_lock().clone()
|
||||
);
|
||||
let (import, link) = block_import(
|
||||
client.clone(),
|
||||
Arc::new(self.test_config.clone()),
|
||||
select_chain,
|
||||
).expect("Could not create block import for fresh peer.");
|
||||
let shared_import = Arc::new(import);
|
||||
(shared_import.clone(), Some(shared_import), None, None, Mutex::new(Some(link)))
|
||||
},
|
||||
PeersClient::Light(ref client) => {
|
||||
use crate::light_import::tests::light_block_import_without_justifications;
|
||||
|
||||
let authorities_provider = Arc::new(self.test_config.clone());
|
||||
// forbid direct finalization using justification that cames with the block
|
||||
// => light clients will try to fetch finality proofs
|
||||
let import = light_block_import_without_justifications(
|
||||
client.clone(),
|
||||
authorities_provider,
|
||||
Arc::new(self.test_config.clone())
|
||||
).expect("Could not create block import for fresh peer.");
|
||||
let finality_proof_req_builder = import.0.create_finality_proof_request_builder();
|
||||
let shared_import = Arc::new(import);
|
||||
(shared_import.clone(), None, Some(shared_import), Some(finality_proof_req_builder), Mutex::new(None))
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
fn make_finality_proof_provider(&self, client: PeersClient) -> Option<Arc<network::FinalityProofProvider<Block>>> {
|
||||
match client {
|
||||
PeersClient::Full(ref client) => {
|
||||
let authorities_provider = Arc::new(self.test_config.clone());
|
||||
Some(Arc::new(FinalityProofProvider::new(client.clone(), authorities_provider)))
|
||||
},
|
||||
PeersClient::Light(_) => None,
|
||||
}
|
||||
}
|
||||
|
||||
fn peer(&self, i: usize) -> &GrandpaPeer {
|
||||
@@ -234,14 +271,14 @@ impl Future for Exit {
|
||||
}
|
||||
|
||||
#[derive(Default, Clone)]
|
||||
struct TestApi {
|
||||
pub(crate) struct TestApi {
|
||||
genesis_authorities: Vec<(AuthorityId, u64)>,
|
||||
scheduled_changes: Arc<Mutex<HashMap<Hash, ScheduledChange<BlockNumber>>>>,
|
||||
forced_changes: Arc<Mutex<HashMap<Hash, (BlockNumber, ScheduledChange<BlockNumber>)>>>,
|
||||
}
|
||||
|
||||
impl TestApi {
|
||||
fn new(genesis_authorities: Vec<(AuthorityId, u64)>) -> Self {
|
||||
pub fn new(genesis_authorities: Vec<(AuthorityId, u64)>) -> Self {
|
||||
TestApi {
|
||||
genesis_authorities,
|
||||
scheduled_changes: Arc::new(Mutex::new(HashMap::new())),
|
||||
@@ -250,7 +287,7 @@ impl TestApi {
|
||||
}
|
||||
}
|
||||
|
||||
struct RuntimeApi {
|
||||
pub(crate) struct RuntimeApi {
|
||||
inner: TestApi,
|
||||
}
|
||||
|
||||
@@ -327,16 +364,12 @@ impl ApiExt<Block> for RuntimeApi {
|
||||
impl GrandpaApi<Block> for RuntimeApi {
|
||||
fn GrandpaApi_grandpa_authorities_runtime_api_impl(
|
||||
&self,
|
||||
at: &BlockId<Block>,
|
||||
_: &BlockId<Block>,
|
||||
_: ExecutionContext,
|
||||
_: Option<()>,
|
||||
_: Vec<u8>,
|
||||
) -> Result<NativeOrEncoded<Vec<(substrate_primitives::ed25519::Public, u64)>>> {
|
||||
if at == &BlockId::Number(0) {
|
||||
Ok(self.inner.genesis_authorities.clone()).map(NativeOrEncoded::Native)
|
||||
} else {
|
||||
panic!("should generally only request genesis authorities")
|
||||
}
|
||||
Ok(self.inner.genesis_authorities.clone()).map(NativeOrEncoded::Native)
|
||||
}
|
||||
|
||||
fn GrandpaApi_grandpa_pending_change_runtime_api_impl(
|
||||
@@ -375,6 +408,33 @@ impl GrandpaApi<Block> for RuntimeApi {
|
||||
}
|
||||
}
|
||||
|
||||
impl AuthoritySetForFinalityProver<Block> for TestApi {
|
||||
fn authorities(&self, block: &BlockId<Block>) -> Result<Vec<(AuthorityId, u64)>> {
|
||||
let runtime_api = RuntimeApi { inner: self.clone() };
|
||||
runtime_api.GrandpaApi_grandpa_authorities_runtime_api_impl(block, ExecutionContext::Syncing, None, Vec::new())
|
||||
.map(|v| match v {
|
||||
NativeOrEncoded::Native(value) => value,
|
||||
_ => unreachable!("only providing native values"),
|
||||
})
|
||||
}
|
||||
|
||||
fn prove_authorities(&self, block: &BlockId<Block>) -> Result<Vec<Vec<u8>>> {
|
||||
self.authorities(block).map(|auth| vec![auth.encode()])
|
||||
}
|
||||
}
|
||||
|
||||
impl AuthoritySetForFinalityChecker<Block> for TestApi {
|
||||
fn check_authorities_proof(
|
||||
&self,
|
||||
_hash: <Block as BlockT>::Hash,
|
||||
_header: <Block as BlockT>::Header,
|
||||
proof: Vec<Vec<u8>>,
|
||||
) -> Result<Vec<(AuthorityId, u64)>> {
|
||||
Decode::decode(&mut &proof[0][..])
|
||||
.ok_or_else(|| unreachable!("incorrect value is passed as GRANDPA authorities proof"))
|
||||
}
|
||||
}
|
||||
|
||||
const TEST_GOSSIP_DURATION: Duration = Duration::from_millis(500);
|
||||
const TEST_ROUTING_INTERVAL: Duration = Duration::from_millis(50);
|
||||
|
||||
@@ -499,7 +559,7 @@ fn finalize_3_voters_no_observers() {
|
||||
run_to_completion(20, net.clone(), peers);
|
||||
|
||||
// normally there's no justification for finalized blocks
|
||||
assert!(net.lock().peer(0).client().backend().blockchain().justification(BlockId::Number(20)).unwrap().is_none(),
|
||||
assert!(net.lock().peer(0).client().justification(&BlockId::Number(20)).unwrap().is_none(),
|
||||
"Extra justification for block#1");
|
||||
}
|
||||
|
||||
@@ -602,11 +662,12 @@ fn transition_3_voters_twice_1_full_observer() {
|
||||
net.lock().sync();
|
||||
|
||||
for (i, peer) in net.lock().peers().iter().enumerate() {
|
||||
assert_eq!(peer.client().info().unwrap().chain.best_number, 1,
|
||||
let full_client = peer.client().as_full().expect("only full clients are used in test");
|
||||
assert_eq!(full_client.info().unwrap().chain.best_number, 1,
|
||||
"Peer #{} failed to sync", i);
|
||||
|
||||
let set: AuthoritySet<Hash, BlockNumber> = crate::aux_schema::load_authorities(
|
||||
&**peer.client().backend()
|
||||
&**full_client.backend()
|
||||
).unwrap();
|
||||
|
||||
assert_eq!(set.current(), (0, make_ids(peers_a).as_slice()));
|
||||
@@ -693,8 +754,9 @@ fn transition_3_voters_twice_1_full_observer() {
|
||||
.take_while(|n| Ok(n.header.number() < &30))
|
||||
.for_each(move |_| Ok(()))
|
||||
.map(move |()| {
|
||||
let full_client = client.as_full().expect("only full clients are used in test");
|
||||
let set: AuthoritySet<Hash, BlockNumber> = crate::aux_schema::load_authorities(
|
||||
&**client.backend()
|
||||
&**full_client.backend()
|
||||
).unwrap();
|
||||
|
||||
assert_eq!(set.current(), (2, make_ids(peers_c).as_slice()));
|
||||
@@ -749,8 +811,8 @@ fn justification_is_emitted_when_consensus_data_changes() {
|
||||
let net = Arc::new(Mutex::new(net));
|
||||
run_to_completion(1, net.clone(), peers);
|
||||
|
||||
// ... and check that there's no justification for block#1
|
||||
assert!(net.lock().peer(0).client().backend().blockchain().justification(BlockId::Number(1)).unwrap().is_some(),
|
||||
// ... and check that there's justification for block#1
|
||||
assert!(net.lock().peer(0).client().justification(&BlockId::Number(1)).unwrap().is_some(),
|
||||
"Missing justification for block#1");
|
||||
}
|
||||
|
||||
@@ -769,8 +831,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().backend().blockchain()
|
||||
.justification(BlockId::Number(32)).unwrap().is_some());
|
||||
assert!(net.lock().peer(i).client().justification(&BlockId::Number(32)).unwrap().is_some());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -963,8 +1024,9 @@ fn force_change_to_new_set() {
|
||||
assert_eq!(peer.client().info().unwrap().chain.best_number, 26,
|
||||
"Peer #{} failed to sync", i);
|
||||
|
||||
let full_client = peer.client().as_full().expect("only full clients are used in test");
|
||||
let set: AuthoritySet<Hash, BlockNumber> = crate::aux_schema::load_authorities(
|
||||
&**peer.client().backend()
|
||||
&**full_client.backend()
|
||||
).unwrap();
|
||||
|
||||
assert_eq!(set.current(), (1, voters.as_slice()));
|
||||
@@ -991,7 +1053,8 @@ fn allows_reimporting_change_blocks() {
|
||||
let client = net.peer(0).client().clone();
|
||||
let (block_import, ..) = net.make_block_import(client.clone());
|
||||
|
||||
let builder = client.new_block_at(&BlockId::Number(0)).unwrap();
|
||||
let full_client = client.as_full().unwrap();
|
||||
let builder = full_client.new_block_at(&BlockId::Number(0)).unwrap();
|
||||
let block = builder.bake().unwrap();
|
||||
api.scheduled_changes.lock().insert(*block.header.parent_hash(), ScheduledChange {
|
||||
next_authorities: make_ids(peers_b),
|
||||
@@ -1014,7 +1077,12 @@ fn allows_reimporting_change_blocks() {
|
||||
|
||||
assert_eq!(
|
||||
block_import.import_block(block(), HashMap::new()).unwrap(),
|
||||
ImportResult::Imported(ImportedAux { needs_justification: true, clear_justification_requests: false, bad_justification: false }),
|
||||
ImportResult::Imported(ImportedAux {
|
||||
needs_justification: true,
|
||||
clear_justification_requests: false,
|
||||
bad_justification: false,
|
||||
needs_finality_proof: false,
|
||||
}),
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
@@ -1034,7 +1102,8 @@ fn test_bad_justification() {
|
||||
let client = net.peer(0).client().clone();
|
||||
let (block_import, ..) = net.make_block_import(client.clone());
|
||||
|
||||
let builder = client.new_block_at(&BlockId::Number(0)).unwrap();
|
||||
let full_client = client.as_full().expect("only full clients are used in test");
|
||||
let builder = full_client.new_block_at(&BlockId::Number(0)).unwrap();
|
||||
let block = builder.bake().unwrap();
|
||||
api.scheduled_changes.lock().insert(*block.header.parent_hash(), ScheduledChange {
|
||||
next_authorities: make_ids(peers_b),
|
||||
@@ -1057,7 +1126,12 @@ fn test_bad_justification() {
|
||||
|
||||
assert_eq!(
|
||||
block_import.import_block(block(), HashMap::new()).unwrap(),
|
||||
ImportResult::Imported(ImportedAux { needs_justification: true, clear_justification_requests: false, bad_justification: true }),
|
||||
ImportResult::Imported(ImportedAux {
|
||||
needs_justification: true,
|
||||
clear_justification_requests: false,
|
||||
bad_justification: true,
|
||||
..Default::default()
|
||||
}),
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
@@ -1102,7 +1176,7 @@ fn voter_persists_its_votes() {
|
||||
let net = net.clone();
|
||||
|
||||
let voter = future::loop_fn(voter_rx, move |rx| {
|
||||
let (_block_import, _, link) = net.lock().make_block_import(client.clone());
|
||||
let (_block_import, _, _, _, link) = net.lock().make_block_import(client.clone());
|
||||
let link = link.lock().take().unwrap();
|
||||
|
||||
let grandpa_params = GrandpaParams {
|
||||
@@ -1201,7 +1275,7 @@ fn voter_persists_its_votes() {
|
||||
"Peer #{} failed to sync", 0);
|
||||
|
||||
let block_30_hash =
|
||||
net.lock().peer(0).client().backend().blockchain().hash(30).unwrap().unwrap();
|
||||
net.lock().peer(0).client().as_full().unwrap().backend().blockchain().hash(30).unwrap().unwrap();
|
||||
|
||||
// we restart alice's voter
|
||||
voter_tx.unbounded_send(()).unwrap();
|
||||
@@ -1302,3 +1376,94 @@ fn finalize_3_voters_1_light_observer() {
|
||||
Some(Box::new(finality_notifications.map(|_| ())))
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn finality_proof_is_fetched_by_light_client_when_consensus_data_changes() {
|
||||
let _ = ::env_logger::try_init();
|
||||
|
||||
let peers = &[AuthorityKeyring::Alice];
|
||||
let mut net = GrandpaTestNet::new(TestApi::new(make_ids(peers)), 1);
|
||||
net.add_light_peer(&GrandpaTestNet::default_config());
|
||||
|
||||
// import block#1 WITH consensus data change. Light client ignores justification
|
||||
// && instead fetches finality proof for block #1
|
||||
net.peer(0).push_authorities_change_block(vec![substrate_primitives::sr25519::Public::from_raw([42; 32])]);
|
||||
let net = Arc::new(Mutex::new(net));
|
||||
run_to_completion(1, net.clone(), peers);
|
||||
net.lock().sync_without_disconnects();
|
||||
|
||||
// check that the block#1 is finalized on light client
|
||||
while net.lock().peer(1).client().info().unwrap().chain.finalized_number != 1 {
|
||||
net.lock().tick_peer(1);
|
||||
net.lock().sync_without_disconnects();
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn empty_finality_proof_is_returned_to_light_client_when_authority_set_is_different() {
|
||||
// for debug: to ensure that without forced change light client will sync finality proof
|
||||
const FORCE_CHANGE: bool = true;
|
||||
|
||||
let _ = ::env_logger::try_init();
|
||||
|
||||
// two of these guys are offline.
|
||||
let genesis_authorities = if FORCE_CHANGE {
|
||||
vec![
|
||||
AuthorityKeyring::Alice,
|
||||
AuthorityKeyring::Bob,
|
||||
AuthorityKeyring::Charlie,
|
||||
AuthorityKeyring::One,
|
||||
AuthorityKeyring::Two,
|
||||
]
|
||||
} else {
|
||||
vec![
|
||||
AuthorityKeyring::Alice,
|
||||
AuthorityKeyring::Bob,
|
||||
AuthorityKeyring::Charlie,
|
||||
]
|
||||
};
|
||||
let peers_a = &[AuthorityKeyring::Alice, AuthorityKeyring::Bob, AuthorityKeyring::Charlie];
|
||||
let api = TestApi::new(make_ids(&genesis_authorities));
|
||||
|
||||
let voters = make_ids(peers_a);
|
||||
let forced_transitions = api.forced_changes.clone();
|
||||
let net = GrandpaTestNet::new(api, 3);
|
||||
let net = Arc::new(Mutex::new(net));
|
||||
|
||||
let runner_net = net.clone();
|
||||
let add_blocks = move |_| {
|
||||
net.lock().peer(0).push_blocks(1, false); // best is #1
|
||||
|
||||
// add a forced transition at block 5.
|
||||
if FORCE_CHANGE {
|
||||
let parent_hash = net.lock().peer(0).client().info().unwrap().chain.best_hash;
|
||||
forced_transitions.lock().insert(parent_hash, (0, ScheduledChange {
|
||||
next_authorities: voters.clone(),
|
||||
delay: 3,
|
||||
}));
|
||||
}
|
||||
|
||||
// ensure block#10 enacts authorities set change => justification is generated
|
||||
// normally it will reach light client, but because of the forced change, it will not
|
||||
net.lock().peer(0).push_blocks(8, false); // best is #9
|
||||
net.lock().peer(0).push_authorities_change_block(
|
||||
vec![substrate_primitives::sr25519::Public::from_raw([42; 32])]
|
||||
); // #10
|
||||
net.lock().peer(0).push_blocks(1, false); // best is #11
|
||||
net.lock().sync_without_disconnects();
|
||||
|
||||
None
|
||||
};
|
||||
|
||||
// finalize block #11 on full clients
|
||||
run_to_completion_with(11, runner_net.clone(), peers_a, add_blocks);
|
||||
// request finalization by light client
|
||||
runner_net.lock().add_light_peer(&GrandpaTestNet::default_config());
|
||||
runner_net.lock().sync_without_disconnects();
|
||||
|
||||
// check block, finalized on light client
|
||||
assert_eq!(
|
||||
runner_net.lock().peer(3).client().info().unwrap().chain.finalized_number,
|
||||
if FORCE_CHANGE { 0 } else { 10 },
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user