mirror of
https://github.com/pezkuwichain/pezkuwi-subxt.git
synced 2026-06-29 23:07:26 +00:00
775 lines
24 KiB
Rust
775 lines
24 KiB
Rust
// 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 sc_client::Client;
|
|
use sc_client_api::{CallExecutor, backend::{AuxStore, Backend, Finalizer}};
|
|
use sp_blockchain::{HeaderBackend, Error as ClientError, well_known_cache_keys};
|
|
use parity_scale_codec::{Encode, Decode};
|
|
use sp_consensus::{
|
|
import_queue::Verifier,
|
|
BlockOrigin, BlockImport, FinalityProofImport, BlockImportParams, ImportResult, ImportedAux,
|
|
BlockCheckParams, Error as ConsensusError,
|
|
};
|
|
use sc_network::config::{BoxFinalityProofRequestBuilder, FinalityProofRequestBuilder};
|
|
use sp_runtime::Justification;
|
|
use sp_runtime::traits::{NumberFor, Block as BlockT, Header as HeaderT, DigestFor};
|
|
use sp_finality_grandpa::{self, AuthorityList};
|
|
use sp_runtime::generic::BlockId;
|
|
use sp_core::{H256, Blake2Hasher};
|
|
|
|
use crate::GenesisAuthoritySetProvider;
|
|
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>(
|
|
client: Arc<Client<B, E, Block, RA>>,
|
|
backend: Arc<B>,
|
|
genesis_authorities_provider: &dyn GenesisAuthoritySetProvider<Block>,
|
|
authority_set_provider: Arc<dyn AuthoritySetForFinalityChecker<Block>>,
|
|
) -> Result<GrandpaLightBlockImport<B, E, Block, RA>, ClientError>
|
|
where
|
|
B: Backend<Block, Blake2Hasher> + 'static,
|
|
E: CallExecutor<Block, Blake2Hasher> + 'static + Clone + Send + Sync,
|
|
RA: Send + Sync,
|
|
{
|
|
let info = client.info();
|
|
let import_data = load_aux_import_data(
|
|
info.chain.finalized_hash,
|
|
&*client,
|
|
genesis_authorities_provider,
|
|
)?;
|
|
Ok(GrandpaLightBlockImport {
|
|
client,
|
|
backend,
|
|
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>>,
|
|
backend: Arc<B>,
|
|
authority_set_provider: Arc<dyn AuthoritySetForFinalityChecker<Block>>,
|
|
data: Arc<RwLock<LightImportData<Block>>>,
|
|
}
|
|
|
|
impl<B, E, Block: BlockT<Hash=H256>, RA> Clone for GrandpaLightBlockImport<B, E, Block, RA> {
|
|
fn clone(&self) -> Self {
|
|
GrandpaLightBlockImport {
|
|
client: self.client.clone(),
|
|
backend: self.backend.clone(),
|
|
authority_set_provider: self.authority_set_provider.clone(),
|
|
data: self.data.clone(),
|
|
}
|
|
}
|
|
}
|
|
|
|
/// 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: AuthorityList,
|
|
}
|
|
|
|
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) -> BoxFinalityProofRequestBuilder<Block> {
|
|
Box::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>: finality_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(
|
|
&mut self,
|
|
block: BlockImportParams<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(
|
|
&mut self,
|
|
block: BlockCheckParams<Block>,
|
|
) -> Result<ImportResult, Self::Error> {
|
|
self.client.check_block(block)
|
|
}
|
|
}
|
|
|
|
impl<B, E, Block: BlockT<Hash=H256>, RA> FinalityProofImport<Block>
|
|
for GrandpaLightBlockImport<B, E, Block, RA> where
|
|
NumberFor<Block>: finality_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(&mut self) -> Vec<(Block::Hash, NumberFor<Block>)> {
|
|
let mut out = Vec::new();
|
|
let chain_info = self.client.info().chain;
|
|
|
|
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 {
|
|
out.push((pending_hash.clone(), *pending_number));
|
|
}
|
|
}
|
|
|
|
out
|
|
}
|
|
|
|
fn import_finality_proof(
|
|
&mut self,
|
|
hash: Block::Hash,
|
|
number: NumberFor<Block>,
|
|
finality_proof: Vec<u8>,
|
|
verifier: &mut dyn Verifier<Block>,
|
|
) -> Result<(Block::Hash, NumberFor<Block>), Self::Error> {
|
|
do_import_finality_proof::<_, _, _, GrandpaJustification<Block>>(
|
|
&*self.client,
|
|
self.backend.clone(),
|
|
&*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: AuthorityList) -> Self {
|
|
LightAuthoritySet {
|
|
set_id: sp_finality_grandpa::SetId::default(),
|
|
authorities: initial,
|
|
}
|
|
}
|
|
|
|
/// Get latest set id.
|
|
pub fn set_id(&self) -> u64 {
|
|
self.set_id
|
|
}
|
|
|
|
/// Get latest authorities set.
|
|
pub fn authorities(&self) -> AuthorityList {
|
|
self.authorities.clone()
|
|
}
|
|
|
|
/// Set new authorities set.
|
|
pub fn update(&mut self, set_id: u64, authorities: AuthorityList) {
|
|
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(&mut 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, C, Block: BlockT<Hash=H256>, J>(
|
|
mut client: C,
|
|
data: &mut LightImportData<Block>,
|
|
mut block: BlockImportParams<Block>,
|
|
new_cache: HashMap<well_known_cache_keys::Id, Vec<u8>>,
|
|
) -> Result<ImportResult, ConsensusError>
|
|
where
|
|
C: HeaderBackend<Block>
|
|
+ AuxStore
|
|
+ Finalizer<Block, Blake2Hasher, B>
|
|
+ BlockImport<Block>
|
|
+ Clone,
|
|
B: Backend<Block, Blake2Hasher> + 'static,
|
|
NumberFor<Block>: finality_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(ConsensusError::ClientImport(e.to_string()).into()),
|
|
};
|
|
|
|
match justification {
|
|
Some(justification) => {
|
|
trace!(
|
|
target: "afg",
|
|
"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: "afg",
|
|
"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, C, Block: BlockT<Hash=H256>, J>(
|
|
client: C,
|
|
backend: Arc<B>,
|
|
authority_set_provider: &dyn AuthoritySetForFinalityChecker<Block>,
|
|
data: &mut LightImportData<Block>,
|
|
_hash: Block::Hash,
|
|
_number: NumberFor<Block>,
|
|
finality_proof: Vec<u8>,
|
|
verifier: &mut dyn Verifier<Block>,
|
|
) -> Result<(Block::Hash, NumberFor<Block>), ConsensusError>
|
|
where
|
|
C: HeaderBackend<Block>
|
|
+ AuxStore
|
|
+ Finalizer<Block, Blake2Hasher, B>
|
|
+ BlockImport<Block>
|
|
+ Clone,
|
|
B: Backend<Block, Blake2Hasher> + 'static,
|
|
DigestFor<Block>: Encode,
|
|
NumberFor<Block>: finality_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(
|
|
backend.blockchain(),
|
|
authority_set_id,
|
|
authorities,
|
|
authority_set_provider,
|
|
finality_proof,
|
|
).map_err(|e| ConsensusError::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)
|
|
.map_err(|e| ConsensusError::ClientImport(e))?;
|
|
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.clone(), data, block_to_import, cache)?;
|
|
}
|
|
|
|
// try to import latest justification
|
|
let finalized_block_hash = finality_effects.block;
|
|
let finalized_block_number = backend.blockchain()
|
|
.expect_block_number_from_id(&BlockId::Hash(finality_effects.block))
|
|
.map_err(|e| ConsensusError::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, C, Block: BlockT<Hash=H256>, J>(
|
|
client: C,
|
|
data: &mut LightImportData<Block>,
|
|
hash: Block::Hash,
|
|
number: NumberFor<Block>,
|
|
justification: Justification,
|
|
) -> Result<ImportResult, ConsensusError>
|
|
where
|
|
C: HeaderBackend<Block>
|
|
+ AuxStore
|
|
+ Finalizer<Block, Blake2Hasher, B>
|
|
+ Clone,
|
|
B: Backend<Block, Blake2Hasher> + 'static,
|
|
NumberFor<Block>: finality_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: "afg",
|
|
"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: "afg",
|
|
"Justification for {} is not valid. Bailing.",
|
|
hash,
|
|
);
|
|
|
|
return Err(ConsensusError::ClientImport(e.to_string()).into());
|
|
},
|
|
Ok(justification) => {
|
|
trace!(
|
|
target: "afg",
|
|
"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, C, Block: BlockT<Hash=H256>>(
|
|
client: C,
|
|
data: &mut LightImportData<Block>,
|
|
hash: Block::Hash,
|
|
number: NumberFor<Block>,
|
|
justification: Justification,
|
|
) -> Result<ImportResult, ConsensusError>
|
|
where
|
|
C: HeaderBackend<Block>
|
|
+ AuxStore
|
|
+ Finalizer<Block, Blake2Hasher, B>
|
|
+ Clone,
|
|
B: Backend<Block, Blake2Hasher> + 'static,
|
|
NumberFor<Block>: finality_grandpa::BlockNumberOps,
|
|
{
|
|
// finalize the block
|
|
client.finalize_block(BlockId::Hash(hash), Some(justification), true).map_err(|e| {
|
|
warn!(target: "afg", "Error applying finality to block {:?}: {:?}", (hash, number), e);
|
|
ConsensusError::ClientImport(e.to_string())
|
|
})?;
|
|
|
|
// forget obsoleted consensus changes
|
|
let consensus_finalization_res = data.consensus_changes
|
|
.finalize((number, hash), |at_height| canonical_at_height(client.clone(), (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;
|
|
|
|
// we just finalized this block, so if we were importing it, it is now the new best
|
|
Ok(ImportResult::imported(true))
|
|
}
|
|
|
|
/// Load light import aux data from the store.
|
|
fn load_aux_import_data<B, Block: BlockT<Hash=H256>>(
|
|
last_finalized: Block::Hash,
|
|
aux_store: &B,
|
|
genesis_authorities_provider: &dyn GenesisAuthoritySetProvider<Block>,
|
|
) -> Result<LightImportData<Block>, ClientError>
|
|
where
|
|
B: AuxStore,
|
|
{
|
|
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 = genesis_authorities_provider.get()?;
|
|
|
|
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, A: AuxStore>(
|
|
store: &A,
|
|
key: &[u8],
|
|
value: &T,
|
|
value_type: &str,
|
|
) -> Result<(), ConsensusError> {
|
|
let encoded = value.encode();
|
|
let update_res = store.insert_aux(&[(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: "afg", "Failed to write updated {} to disk. Bailing.", value_type);
|
|
warn!(target: "afg", "Node is in a potentially inconsistent state.");
|
|
ConsensusError::ClientImport(error.to_string())
|
|
}
|
|
|
|
#[cfg(test)]
|
|
pub mod tests {
|
|
use super::*;
|
|
use sp_consensus::ForkChoiceStrategy;
|
|
use sp_finality_grandpa::AuthorityId;
|
|
use sp_core::{H256, crypto::Public};
|
|
use substrate_test_runtime_client::sc_client::in_mem::Blockchain as InMemoryAuxStore;
|
|
use substrate_test_runtime_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> Clone
|
|
for NoJustificationsImport<B, E, Block, RA> where
|
|
NumberFor<Block>: finality_grandpa::BlockNumberOps,
|
|
B: Backend<Block, Blake2Hasher> + 'static,
|
|
E: CallExecutor<Block, Blake2Hasher> + 'static + Clone + Send + Sync,
|
|
DigestFor<Block>: Encode,
|
|
RA: Send + Sync,
|
|
{
|
|
fn clone(&self) -> Self {
|
|
NoJustificationsImport(self.0.clone())
|
|
}
|
|
}
|
|
|
|
impl<B, E, Block: BlockT<Hash=H256>, RA> BlockImport<Block>
|
|
for NoJustificationsImport<B, E, Block, RA> where
|
|
NumberFor<Block>: finality_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(
|
|
&mut self,
|
|
mut block: BlockImportParams<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(
|
|
&mut self,
|
|
block: BlockCheckParams<Block>,
|
|
) -> Result<ImportResult, Self::Error> {
|
|
self.0.check_block(block)
|
|
}
|
|
}
|
|
|
|
impl<B, E, Block: BlockT<Hash=H256>, RA> FinalityProofImport<Block>
|
|
for NoJustificationsImport<B, E, Block, RA> where
|
|
NumberFor<Block>: finality_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(&mut self) -> Vec<(Block::Hash, NumberFor<Block>)> {
|
|
self.0.on_start()
|
|
}
|
|
|
|
fn import_finality_proof(
|
|
&mut self,
|
|
hash: Block::Hash,
|
|
number: NumberFor<Block>,
|
|
finality_proof: Vec<u8>,
|
|
verifier: &mut dyn 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>(
|
|
client: Arc<Client<B, E, Block, RA>>,
|
|
backend: Arc<B>,
|
|
genesis_authorities_provider: &dyn GenesisAuthoritySetProvider<Block>,
|
|
authority_set_provider: Arc<dyn AuthoritySetForFinalityChecker<Block>>,
|
|
) -> Result<NoJustificationsImport<B, E, Block, RA>, ClientError>
|
|
where
|
|
B: Backend<Block, Blake2Hasher> + 'static,
|
|
E: CallExecutor<Block, Blake2Hasher> + 'static + Clone + Send + Sync,
|
|
RA: Send + Sync,
|
|
{
|
|
light_block_import(client, backend, genesis_authorities_provider, authority_set_provider)
|
|
.map(NoJustificationsImport)
|
|
}
|
|
|
|
fn import_block(
|
|
new_cache: HashMap<well_known_cache_keys::Id, Vec<u8>>,
|
|
justification: Option<Justification>,
|
|
) -> ImportResult {
|
|
let (client, _backend) = substrate_test_runtime_client::new_light();
|
|
let mut import_data = LightImportData {
|
|
last_finalized: Default::default(),
|
|
authority_set: LightAuthoritySet::genesis(vec![(AuthorityId::from_slice(&[1; 32]), 1)]),
|
|
consensus_changes: ConsensusChanges::empty(),
|
|
};
|
|
let block = BlockImportParams {
|
|
origin: BlockOrigin::Own,
|
|
header: Header {
|
|
number: 1,
|
|
parent_hash: client.info().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,
|
|
allow_missing_state: true,
|
|
import_existing: false,
|
|
};
|
|
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,
|
|
is_new_best: true,
|
|
header_only: false,
|
|
}));
|
|
}
|
|
|
|
#[test]
|
|
fn finality_proof_not_required_when_consensus_data_does_not_changes_and_correct_justification_provided() {
|
|
let justification = TestJustification((0, vec![(AuthorityId::from_slice(&[1; 32]), 1)]), 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,
|
|
is_new_best: true,
|
|
header_only: 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::from_slice(&[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,
|
|
is_new_best: true,
|
|
header_only: false,
|
|
}));
|
|
}
|
|
|
|
#[test]
|
|
fn finality_proof_required_when_consensus_data_changes_and_incorrect_justification_provided() {
|
|
let justification = TestJustification((0, vec![]), Vec::new()).encode();
|
|
let mut cache = HashMap::new();
|
|
cache.insert(well_known_cache_keys::AUTHORITIES, vec![AuthorityId::from_slice(&[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,
|
|
is_new_best: false,
|
|
header_only: false,
|
|
},
|
|
));
|
|
}
|
|
|
|
|
|
#[test]
|
|
fn aux_data_updated_on_start() {
|
|
let aux_store = InMemoryAuxStore::<Block>::new();
|
|
let api = TestApi::new(vec![(AuthorityId::from_slice(&[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 = TestApi::new(vec![(AuthorityId::from_slice(&[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::from_slice(&[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::from_slice(&[42; 32]), 2)]);
|
|
assert_eq!(data.consensus_changes.pending_changes(), &[(42, Default::default())]);
|
|
}
|
|
}
|