grandpa: remove light-client specific block import pipeline (#7546)

* grandpa: remove light-client specific block import

* consensus, network: remove finality proofs
This commit is contained in:
André Silva
2020-11-23 14:28:55 +00:00
committed by GitHub
parent cd2490f56d
commit 1871a95088
44 changed files with 96 additions and 2512 deletions
@@ -17,7 +17,6 @@
//! Schema for stuff in the aux-db.
use std::fmt::Debug;
use std::sync::Arc;
use parity_scale_codec::{Encode, Decode};
use sc_client_api::backend::AuxStore;
use sp_blockchain::{Result as ClientResult, Error as ClientError};
@@ -28,7 +27,6 @@ use log::{info, warn};
use sp_finality_grandpa::{AuthorityList, SetId, RoundNumber};
use crate::authorities::{AuthoritySet, SharedAuthoritySet, PendingChange, DelayKind};
use crate::consensus_changes::{SharedConsensusChanges, ConsensusChanges};
use crate::environment::{
CompletedRound, CompletedRounds, CurrentRounds, HasVoted, SharedVoterSetState, VoterSetState,
};
@@ -38,7 +36,6 @@ const VERSION_KEY: &[u8] = b"grandpa_schema_version";
const SET_STATE_KEY: &[u8] = b"grandpa_completed_round";
const CONCLUDED_ROUNDS: &[u8] = b"grandpa_concluded_rounds";
const AUTHORITY_SET_KEY: &[u8] = b"grandpa_voters";
const CONSENSUS_CHANGES_KEY: &[u8] = b"grandpa_consensus_changes";
const CURRENT_VERSION: u32 = 2;
@@ -122,7 +119,6 @@ pub(crate) fn load_decode<B: AuxStore, T: Decode>(backend: &B, key: &[u8]) -> Cl
/// Persistent data kept between runs.
pub(crate) struct PersistentData<Block: BlockT> {
pub(crate) authority_set: SharedAuthoritySet<Block::Hash, NumberFor<Block>>,
pub(crate) consensus_changes: SharedConsensusChanges<Block::Hash, NumberFor<Block>>,
pub(crate) set_state: SharedVoterSetState<Block>,
}
@@ -272,8 +268,6 @@ pub(crate) fn load_persistent<Block: BlockT, B, G>(
G: FnOnce() -> ClientResult<AuthorityList>,
{
let version: Option<u32> = load_decode(backend, VERSION_KEY)?;
let consensus_changes = load_decode(backend, CONSENSUS_CHANGES_KEY)?
.unwrap_or_else(ConsensusChanges::<Block::Hash, NumberFor<Block>>::empty);
let make_genesis_round = move || RoundState::genesis((genesis_hash, genesis_number));
@@ -282,7 +276,6 @@ pub(crate) fn load_persistent<Block: BlockT, B, G>(
if let Some((new_set, set_state)) = migrate_from_version0::<Block, _, _>(backend, &make_genesis_round)? {
return Ok(PersistentData {
authority_set: new_set.into(),
consensus_changes: Arc::new(consensus_changes.into()),
set_state: set_state.into(),
});
}
@@ -291,7 +284,6 @@ pub(crate) fn load_persistent<Block: BlockT, B, G>(
if let Some((new_set, set_state)) = migrate_from_version1::<Block, _, _>(backend, &make_genesis_round)? {
return Ok(PersistentData {
authority_set: new_set.into(),
consensus_changes: Arc::new(consensus_changes.into()),
set_state: set_state.into(),
});
}
@@ -321,7 +313,6 @@ pub(crate) fn load_persistent<Block: BlockT, B, G>(
return Ok(PersistentData {
authority_set: set.into(),
consensus_changes: Arc::new(consensus_changes.into()),
set_state: set_state.into(),
});
}
@@ -359,7 +350,6 @@ pub(crate) fn load_persistent<Block: BlockT, B, G>(
Ok(PersistentData {
authority_set: genesis_set.into(),
set_state: genesis_state.into(),
consensus_changes: Arc::new(consensus_changes.into()),
})
}
@@ -421,18 +411,6 @@ pub(crate) fn write_concluded_round<Block: BlockT, B: AuxStore>(
backend.insert_aux(&[(&key[..], round_data.encode().as_slice())], &[])
}
/// Update the consensus changes.
pub(crate) fn update_consensus_changes<H, N, F, R>(
set: &ConsensusChanges<H, N>,
write_aux: F
) -> R where
H: Encode + Clone,
N: Encode + Clone,
F: FnOnce(&[(&'static [u8], &[u8])]) -> R,
{
write_aux(&[(CONSENSUS_CHANGES_KEY, set.encode().as_slice())])
}
#[cfg(test)]
pub(crate) fn load_authorities<B: AuxStore, H: Decode, N: Decode>(backend: &B)
-> Option<AuthoritySet<H, N>> {
@@ -1,78 +0,0 @@
// Copyright 2018-2020 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::sync::Arc;
use parity_scale_codec::{Encode, Decode};
/// Consensus-related data changes tracker.
#[derive(Clone, Debug, Encode, Decode)]
pub(crate) struct ConsensusChanges<H, N> {
pending_changes: Vec<(N, H)>,
}
impl<H, N> ConsensusChanges<H, N> {
/// Create empty consensus changes.
pub(crate) fn empty() -> Self {
ConsensusChanges { pending_changes: Vec::new(), }
}
}
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
.binary_search_by_key(&at.0, |change| change.0)
.unwrap_or_else(|i| i);
self.pending_changes.insert(idx, at);
}
/// Finalize all pending consensus changes that are finalized by given block.
/// Returns true if there any changes were finalized.
pub(crate) fn finalize<F: Fn(N) -> ::sp_blockchain::Result<Option<H>>>(
&mut self,
block: (N, H),
canonical_at_height: F,
) -> ::sp_blockchain::Result<(bool, bool)> {
let (split_idx, has_finalized_changes) = self.pending_changes.iter()
.enumerate()
.take_while(|(_, &(at_height, _))| at_height <= block.0)
.fold((None, Ok(false)), |(_, has_finalized_changes), (idx, ref at)|
(
Some(idx),
has_finalized_changes
.and_then(|has_finalized_changes| if has_finalized_changes {
Ok(has_finalized_changes)
} else {
canonical_at_height(at.0).map(|can_hash| Some(at.1) == can_hash)
}),
));
let altered_changes = split_idx.is_some();
if let Some(split_idx) = split_idx {
self.pending_changes = self.pending_changes.split_off(split_idx + 1);
}
has_finalized_changes.map(|has_finalized_changes| (altered_changes, has_finalized_changes))
}
}
/// Thread-safe consensus changes tracker reference.
pub(crate) type SharedConsensusChanges<H, N> = Arc<parking_lot::Mutex<ConsensusChanges<H, N>>>;
@@ -34,10 +34,10 @@ use finality_grandpa::{
BlockNumberOps, Error as GrandpaError, round::State as RoundState,
voter, voter_set::VoterSet,
};
use sp_blockchain::{HeaderBackend, HeaderMetadata, Error as ClientError};
use sp_blockchain::HeaderMetadata;
use sp_runtime::generic::BlockId;
use sp_runtime::traits::{
Block as BlockT, Header as HeaderT, NumberFor, One, Zero,
Block as BlockT, Header as HeaderT, NumberFor, Zero,
};
use sc_telemetry::{telemetry, CONSENSUS_DEBUG, CONSENSUS_INFO};
@@ -50,7 +50,6 @@ use sp_consensus::SelectChain;
use crate::authorities::{AuthoritySet, SharedAuthoritySet};
use crate::communication::Network as NetworkT;
use crate::consensus_changes::SharedConsensusChanges;
use crate::notification::GrandpaJustificationSender;
use crate::justification::GrandpaJustification;
use crate::until_imported::UntilVoteTargetImported;
@@ -440,7 +439,6 @@ pub(crate) struct Environment<Backend, Block: BlockT, C, N: NetworkT<Block>, SC,
pub(crate) voters: Arc<VoterSet<AuthorityId>>,
pub(crate) config: Config,
pub(crate) authority_set: SharedAuthoritySet<Block::Hash, NumberFor<Block>>,
pub(crate) consensus_changes: SharedConsensusChanges<Block::Hash, NumberFor<Block>>,
pub(crate) network: crate::communication::NetworkBridge<Block, N>,
pub(crate) set_id: SetId,
pub(crate) voter_set_state: SharedVoterSetState<Block>,
@@ -1115,7 +1113,6 @@ where
finalize_block(
self.client.clone(),
&self.authority_set,
&self.consensus_changes,
Some(self.config.justification_period.into()),
hash,
number,
@@ -1180,7 +1177,6 @@ impl<Block: BlockT> From<GrandpaJustification<Block>> for JustificationOrCommit<
pub(crate) fn finalize_block<BE, Block, Client>(
client: Arc<Client>,
authority_set: &SharedAuthoritySet<Block::Hash, NumberFor<Block>>,
consensus_changes: &SharedConsensusChanges<Block::Hash, NumberFor<Block>>,
justification_period: Option<NumberFor<Block>>,
hash: Block::Hash,
number: NumberFor<Block>,
@@ -1215,15 +1211,6 @@ where
// FIXME #1483: clone only when changed
let old_authority_set = authority_set.clone();
// holds the old consensus changes in case it is changed below, needed for
// reverting in case of failure
let mut old_consensus_changes = None;
let mut consensus_changes = consensus_changes.lock();
let canon_at_height = |canon_number| {
// "true" because the block is finalized
canonical_at_height(&*client, (hash, number), true, canon_number)
};
let update_res: Result<_, Error> = client.lock_import_and_run(|import_op| {
let status = authority_set.apply_standard_changes(
@@ -1233,26 +1220,6 @@ where
initial_sync,
).map_err(|e| Error::Safety(e.to_string()))?;
// check if this is this is the first finalization of some consensus changes
let (alters_consensus_changes, finalizes_consensus_changes) = consensus_changes
.finalize((number, hash), &canon_at_height)?;
if alters_consensus_changes {
old_consensus_changes = Some(consensus_changes.clone());
let write_result = crate::aux_schema::update_consensus_changes(
&*consensus_changes,
|insert| apply_aux(import_op, insert, &[]),
);
if let Err(e) = write_result {
warn!(target: "afg", "Failed to write updated consensus changes to disk. Bailing.");
warn!(target: "afg", "Node is in a potentially inconsistent state.");
return Err(e.into());
}
}
// send a justification notification if a sender exists and in case of error log it.
fn notify_justification<Block: BlockT>(
justification_sender: Option<&GrandpaJustificationSender<Block>>,
@@ -1280,9 +1247,7 @@ where
let mut justification_required =
// justification is always required when block that enacts new authorities
// set is finalized
status.new_set_block.is_some() ||
// justification is required when consensus changes are finalized
finalizes_consensus_changes;
status.new_set_block.is_some();
// justification is required every N blocks to be able to prove blocks
// finalization to remote nodes
@@ -1387,57 +1352,7 @@ where
Err(e) => {
*authority_set = old_authority_set;
if let Some(old_consensus_changes) = old_consensus_changes {
*consensus_changes = old_consensus_changes;
}
Err(CommandOrError::Error(e))
}
}
}
/// Using the given base get the block at the given height on this chain. The
/// target block must be an ancestor of base, therefore `height <= base.height`.
pub(crate) fn canonical_at_height<Block: BlockT, C: HeaderBackend<Block>>(
provider: &C,
base: (Block::Hash, NumberFor<Block>),
base_is_canonical: bool,
height: NumberFor<Block>,
) -> Result<Option<Block::Hash>, ClientError> {
if height > base.1 {
return Ok(None);
}
if height == base.1 {
if base_is_canonical {
return Ok(Some(base.0));
} else {
return Ok(provider.hash(height).unwrap_or(None));
}
} else if base_is_canonical {
return Ok(provider.hash(height).unwrap_or(None));
}
let one = NumberFor::<Block>::one();
// start by getting _canonical_ block with number at parent position and then iterating
// backwards by hash.
let mut current = match provider.header(BlockId::Number(base.1 - one))? {
Some(header) => header,
_ => return Ok(None),
};
// we've already checked that base > height above.
let mut steps = base.1 - height - one;
while steps > NumberFor::<Block>::zero() {
current = match provider.header(BlockId::Hash(*current.parent_hash()))? {
Some(header) => header,
_ => return Ok(None),
};
steps -= one;
}
Ok(Some(current.hash()))
}
@@ -16,6 +16,9 @@
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
// NOTE: should be removed with: https://github.com/paritytech/substrate/pull/7339
#![allow(dead_code)]
//! GRANDPA block finality proof generation and check.
//!
//! Finality of block B is proved by providing:
@@ -37,7 +40,7 @@
//! of the U) could be returned.
use std::sync::Arc;
use log::{trace, warn};
use log::trace;
use sp_blockchain::{Backend as BlockchainBackend, Error as ClientError, Result as ClientResult};
use sc_client_api::{
@@ -206,34 +209,6 @@ impl<B, Block> FinalityProofProvider<B, Block>
}
}
impl<B, Block> sc_network::config::FinalityProofProvider<Block> for FinalityProofProvider<B, Block>
where
Block: BlockT,
NumberFor<Block>: BlockNumberOps,
B: Backend<Block> + Send + Sync + 'static,
{
fn prove_finality(
&self,
for_block: Block::Hash,
request: &[u8],
) -> Result<Option<Vec<u8>>, ClientError> {
let request: FinalityProofRequest<Block::Hash> = Decode::decode(&mut &request[..])
.map_err(|e| {
warn!(target: "afg", "Unable to decode finality proof request: {}", e.what());
ClientError::Backend("Invalid finality proof request".to_string())
})?;
match request {
FinalityProofRequest::Original(request) => prove_finality::<_, _, GrandpaJustification<Block>>(
&*self.backend.blockchain(),
&*self.authority_provider,
request.authorities_set_id,
request.last_finalized,
for_block,
),
}
}
}
/// The effects of block finality.
#[derive(Debug, PartialEq)]
pub struct FinalityEffects<Header: HeaderT> {
@@ -290,14 +265,6 @@ struct OriginalFinalityProofRequest<H: Encode + Decode> {
pub last_finalized: H,
}
/// Prepare data blob associated with finality proof request.
pub(crate) fn make_finality_proof_request<H: Encode + Decode>(last_finalized: H, authorities_set_id: u64) -> Vec<u8> {
FinalityProofRequest::Original(OriginalFinalityProofRequest {
authorities_set_id,
last_finalized,
}).encode()
}
/// Prepare proof-of-finality for the best possible block in the range: (begin; end].
///
/// It is assumed that the caller already have a proof-of-finality for the block 'begin'.
@@ -41,7 +41,6 @@ use sp_runtime::traits::{
use crate::{Error, CommandOrError, NewAuthoritySet, VoterCommand};
use crate::authorities::{AuthoritySet, SharedAuthoritySet, DelayKind, PendingChange};
use crate::consensus_changes::SharedConsensusChanges;
use crate::environment::finalize_block;
use crate::justification::GrandpaJustification;
use crate::notification::GrandpaJustificationSender;
@@ -61,7 +60,6 @@ pub struct GrandpaBlockImport<Backend, Block: BlockT, Client, SC> {
select_chain: SC,
authority_set: SharedAuthoritySet<Block::Hash, NumberFor<Block>>,
send_voter_commands: TracingUnboundedSender<VoterCommand<Block::Hash, NumberFor<Block>>>,
consensus_changes: SharedConsensusChanges<Block::Hash, NumberFor<Block>>,
authority_set_hard_forks: HashMap<Block::Hash, PendingChange<Block::Hash, NumberFor<Block>>>,
justification_sender: GrandpaJustificationSender<Block>,
_phantom: PhantomData<Backend>,
@@ -76,7 +74,6 @@ impl<Backend, Block: BlockT, Client, SC: Clone> Clone for
select_chain: self.select_chain.clone(),
authority_set: self.authority_set.clone(),
send_voter_commands: self.send_voter_commands.clone(),
consensus_changes: self.consensus_changes.clone(),
authority_set_hard_forks: self.authority_set_hard_forks.clone(),
justification_sender: self.justification_sender.clone(),
_phantom: PhantomData,
@@ -439,7 +436,6 @@ impl<BE, Block: BlockT, Client, SC> BlockImport<Block>
// we don't want to finalize on `inner.import_block`
let mut justification = block.justification.take();
let enacts_consensus_change = !new_cache.is_empty();
let import_result = (&*self.inner).import_block(block, new_cache);
let mut imported_aux = {
@@ -517,7 +513,7 @@ impl<BE, Block: BlockT, Client, SC> BlockImport<Block>
);
import_res.unwrap_or_else(|err| {
if needs_justification || enacts_consensus_change {
if needs_justification {
debug!(target: "afg", "Imported block #{} that enacts authority set change with \
invalid justification: {:?}, requesting justification from peers.", number, err);
imported_aux.bad_justification = true;
@@ -535,12 +531,6 @@ impl<BE, Block: BlockT, Client, SC> BlockImport<Block>
imported_aux.needs_justification = true;
}
// we have imported block with consensus data changes, but without justification
// => remember to create justification when next block will be finalized
if enacts_consensus_change {
self.consensus_changes.lock().note_change((number, hash));
}
}
}
@@ -561,7 +551,6 @@ impl<Backend, Block: BlockT, Client, SC> GrandpaBlockImport<Backend, Block, Clie
select_chain: SC,
authority_set: SharedAuthoritySet<Block::Hash, NumberFor<Block>>,
send_voter_commands: TracingUnboundedSender<VoterCommand<Block::Hash, NumberFor<Block>>>,
consensus_changes: SharedConsensusChanges<Block::Hash, NumberFor<Block>>,
authority_set_hard_forks: Vec<(SetId, PendingChange<Block::Hash, NumberFor<Block>>)>,
justification_sender: GrandpaJustificationSender<Block>,
) -> GrandpaBlockImport<Backend, Block, Client, SC> {
@@ -605,7 +594,6 @@ impl<Backend, Block: BlockT, Client, SC> GrandpaBlockImport<Backend, Block, Clie
select_chain,
authority_set,
send_voter_commands,
consensus_changes,
authority_set_hard_forks,
justification_sender,
_phantom: PhantomData,
@@ -646,7 +634,6 @@ where
let result = finalize_block(
self.inner.clone(),
&self.authority_set,
&self.consensus_changes,
None,
hash,
number,
@@ -112,12 +112,10 @@ macro_rules! afg_log {
mod authorities;
mod aux_schema;
mod communication;
mod consensus_changes;
mod environment;
mod finality_proof;
mod import;
mod justification;
mod light_import;
mod notification;
mod observer;
mod until_imported;
@@ -128,7 +126,6 @@ pub use finality_proof::{FinalityProofFragment, FinalityProofProvider, StorageAn
pub use notification::{GrandpaJustificationSender, GrandpaJustificationStream};
pub use import::GrandpaBlockImport;
pub use justification::GrandpaJustification;
pub use light_import::{light_block_import, GrandpaLightBlockImport};
pub use voting_rule::{
BeforeBestBlockBy, ThreeQuartersOfTheUnfinalizedChain, VotingRule, VotingRulesBuilder
};
@@ -588,7 +585,6 @@ where
select_chain.clone(),
persistent_data.authority_set.clone(),
voter_commands_tx,
persistent_data.consensus_changes.clone(),
authority_set_hard_forks,
justification_sender.clone(),
),
@@ -844,7 +840,6 @@ where
network: network.clone(),
set_id: persistent_data.authority_set.set_id(),
authority_set: persistent_data.authority_set.clone(),
consensus_changes: persistent_data.consensus_changes.clone(),
voter_set_state: persistent_data.set_state,
metrics: metrics.as_ref().map(|m| m.environment.clone()),
justification_sender: Some(justification_sender),
@@ -989,7 +984,6 @@ where
select_chain: self.env.select_chain.clone(),
config: self.env.config.clone(),
authority_set: self.env.authority_set.clone(),
consensus_changes: self.env.consensus_changes.clone(),
network: self.env.network.clone(),
voting_rule: self.env.voting_rule.clone(),
metrics: self.env.metrics.clone(),
@@ -1,880 +0,0 @@
// Copyright 2019-2020 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_api::backend::{AuxStore, Backend, Finalizer, TransactionFor};
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 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<BE, Block: BlockT, Client>(
client: Arc<Client>,
backend: Arc<BE>,
genesis_authorities_provider: &dyn GenesisAuthoritySetProvider<Block>,
authority_set_provider: Arc<dyn AuthoritySetForFinalityChecker<Block>>,
) -> Result<GrandpaLightBlockImport<BE, Block, Client>, ClientError>
where
BE: Backend<Block>,
Client: crate::ClientForGrandpa<Block, BE>,
{
let info = client.info();
let import_data = load_aux_import_data(
info.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<BE, Block: BlockT, Client> {
client: Arc<Client>,
backend: Arc<BE>,
authority_set_provider: Arc<dyn AuthoritySetForFinalityChecker<Block>>,
data: Arc<RwLock<LightImportData<Block>>>,
}
impl<BE, Block: BlockT, Client> Clone for GrandpaLightBlockImport<BE, Block, Client> {
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> {
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<BE, Block: BlockT, Client> GrandpaLightBlockImport<BE, Block, Client> {
/// Create finality proof request builder.
pub fn create_finality_proof_request_builder(&self) -> BoxFinalityProofRequestBuilder<Block> {
Box::new(GrandpaFinalityProofRequestBuilder(self.data.clone())) as _
}
}
impl<BE, Block: BlockT, Client> BlockImport<Block>
for GrandpaLightBlockImport<BE, Block, Client> where
NumberFor<Block>: finality_grandpa::BlockNumberOps,
DigestFor<Block>: Encode,
BE: Backend<Block> + 'static,
for<'a> &'a Client:
HeaderBackend<Block>
+ BlockImport<Block, Error = ConsensusError, Transaction = TransactionFor<BE, Block>>
+ Finalizer<Block, BE>
+ AuxStore,
{
type Error = ConsensusError;
type Transaction = TransactionFor<BE, Block>;
fn import_block(
&mut self,
block: BlockImportParams<Block, Self::Transaction>,
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<BE, Block: BlockT, Client> FinalityProofImport<Block>
for GrandpaLightBlockImport<BE, Block, Client> where
NumberFor<Block>: finality_grandpa::BlockNumberOps,
DigestFor<Block>: Encode,
BE: Backend<Block> + 'static,
for<'a> &'a Client:
HeaderBackend<Block>
+ BlockImport<Block, Error = ConsensusError, Transaction = TransactionFor<BE, Block>>
+ Finalizer<Block, BE>
+ AuxStore,
{
type Error = ConsensusError;
fn on_start(&mut self) -> Vec<(Block::Hash, NumberFor<Block>)> {
let mut out = Vec::new();
let chain_info = (&*self.client).info();
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, *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;
self.authorities = authorities;
}
}
struct GrandpaFinalityProofRequestBuilder<B: BlockT>(Arc<RwLock<LightImportData<B>>>);
impl<B: BlockT> 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, J>(
mut client: C,
data: &mut LightImportData<Block>,
mut block: BlockImportParams<Block, TransactionFor<B, Block>>,
new_cache: HashMap<well_known_cache_keys::Id, Vec<u8>>,
) -> Result<ImportResult, ConsensusError>
where
C: HeaderBackend<Block>
+ AuxStore
+ Finalizer<Block, B>
+ BlockImport<Block, Transaction = TransactionFor<B, Block>>
+ Clone,
B: Backend<Block> + 'static,
NumberFor<Block>: finality_grandpa::BlockNumberOps,
DigestFor<Block>: Encode,
J: ProvableJustification<Block::Header>,
{
let hash = block.post_hash();
let number = *block.header.number();
// 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())),
};
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, 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, B>
+ BlockImport<Block, Transaction = TransactionFor<B, Block>>
+ Clone,
B: Backend<Block> + '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::<_, _, J>(
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.convert_transaction(),
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.clone(),
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,
);
// store new authorities set
require_insert_aux(
&client,
LIGHT_AUTHORITY_SET_KEY,
&data.authority_set,
"authority set",
)?;
Ok((finalized_block_hash, finalized_block_number))
}
/// Try to import justification.
fn do_import_justification<B, C, Block: BlockT, 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, B>
+ Clone,
B: Backend<Block> + '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()));
},
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>(
client: C,
data: &mut LightImportData<Block>,
hash: Block::Hash,
number: NumberFor<Block>,
justification: Justification,
) -> Result<ImportResult, ConsensusError>
where
C: HeaderBackend<Block>
+ AuxStore
+ Finalizer<Block, B>
+ Clone,
B: Backend<Block> + '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, (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>(
last_finalized: Block::Hash,
aux_store: &B,
genesis_authorities_provider: &dyn GenesisAuthoritySetProvider<Block>,
) -> Result<LightImportData<Block>, ClientError>
where
B: AuxStore,
Block: BlockT,
{
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::{import_queue::CacheKeyId, ForkChoiceStrategy, BlockImport};
use sp_finality_grandpa::AuthorityId;
use sp_core::{H256, crypto::Public};
use sc_client_api::{in_mem::Blockchain as InMemoryAuxStore, StorageProof, BlockBackend};
use substrate_test_runtime_client::runtime::{Block, Header};
use crate::tests::TestApi;
use crate::finality_proof::{
FinalityProofFragment,
tests::{TestJustification, ClosureAuthoritySetForFinalityChecker},
};
struct OkVerifier;
impl Verifier<Block> for OkVerifier {
fn verify(
&mut self,
origin: BlockOrigin,
header: Header,
_justification: Option<Justification>,
_body: Option<Vec<<Block as BlockT>::Extrinsic>>,
) -> Result<(BlockImportParams<Block, ()>, Option<Vec<(CacheKeyId, Vec<u8>)>>), String> {
Ok((BlockImportParams::new(origin, header), None))
}
}
pub struct NoJustificationsImport<BE, Block: BlockT, Client>(
pub GrandpaLightBlockImport<BE, Block, Client>
);
impl<BE, Block: BlockT, Client> Clone
for NoJustificationsImport<BE, Block, Client> where
NumberFor<Block>: finality_grandpa::BlockNumberOps,
DigestFor<Block>: Encode,
BE: Backend<Block> + 'static,
{
fn clone(&self) -> Self {
NoJustificationsImport(self.0.clone())
}
}
impl<BE, Block: BlockT, Client> BlockImport<Block>
for NoJustificationsImport<BE, Block, Client> where
NumberFor<Block>: finality_grandpa::BlockNumberOps,
DigestFor<Block>: Encode,
BE: Backend<Block> + 'static,
for <'a > &'a Client:
HeaderBackend<Block>
+ BlockImport<Block, Error = ConsensusError, Transaction = TransactionFor<BE, Block>>
+ Finalizer<Block, BE>
+ AuxStore,
GrandpaLightBlockImport<BE, Block, Client>:
BlockImport<Block, Transaction = TransactionFor<BE, Block>, Error = ConsensusError>
{
type Error = ConsensusError;
type Transaction = TransactionFor<BE, Block>;
fn import_block(
&mut self,
mut block: BlockImportParams<Block, Self::Transaction>,
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<BE, Block: BlockT, Client> FinalityProofImport<Block>
for NoJustificationsImport<BE, Block, Client> where
NumberFor<Block>: finality_grandpa::BlockNumberOps,
BE: Backend<Block> + 'static,
DigestFor<Block>: Encode,
for <'a > &'a Client:
HeaderBackend<Block>
+ BlockImport<Block, Error = ConsensusError, Transaction = TransactionFor<BE, Block>>
+ Finalizer<Block, BE>
+ AuxStore,
{
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<BE, Block: BlockT, Client>(
client: Arc<Client>,
backend: Arc<BE>,
genesis_authorities_provider: &dyn GenesisAuthoritySetProvider<Block>,
authority_set_provider: Arc<dyn AuthoritySetForFinalityChecker<Block>>,
) -> Result<NoJustificationsImport<BE, Block, Client>, ClientError>
where
BE: Backend<Block> + 'static,
Client: crate::ClientForGrandpa<Block, BE>,
{
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,
substrate_test_runtime_client::client::Client<substrate_test_runtime_client::LightBackend, substrate_test_runtime_client::LightExecutor, Block, substrate_test_runtime_client::runtime::RuntimeApi>,
Arc<substrate_test_runtime_client::LightBackend>,
) {
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 mut block = BlockImportParams::new(
BlockOrigin::Own,
Header {
number: 1,
parent_hash: client.chain_info().best_hash,
state_root: Default::default(),
digest: Default::default(),
extrinsics_root: Default::default(),
},
);
block.justification = justification;
block.fork_choice = Some(ForkChoiceStrategy::LongestChain);
(
do_import_block::<_, _, _, TestJustification>(
&client,
&mut import_data,
block,
new_cache,
).unwrap(),
client,
backend,
)
}
#[test]
fn finality_proof_not_required_when_consensus_data_does_not_changes_and_no_justification_provided() {
assert_eq!(import_block(HashMap::new(), None).0, 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)).0, 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).0, 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)).0,
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())]);
}
#[test]
fn authority_set_is_updated_on_finality_proof_import() {
let initial_set_id = 0;
let initial_set = vec![(AuthorityId::from_slice(&[1; 32]), 1)];
let updated_set = vec![(AuthorityId::from_slice(&[2; 32]), 2)];
let babe_set_signal = vec![AuthorityId::from_slice(&[42; 32])].encode();
// import block #1 without justification
let mut cache = HashMap::new();
cache.insert(well_known_cache_keys::AUTHORITIES, babe_set_signal);
let (_, client, backend) = import_block(cache, None);
// import finality proof for block #1
let hash = client.block_hash(1).unwrap().unwrap();
let mut verifier = OkVerifier;
let mut import_data = LightImportData {
last_finalized: Default::default(),
authority_set: LightAuthoritySet::genesis(initial_set.clone()),
consensus_changes: ConsensusChanges::empty(),
};
// import finality proof
do_import_finality_proof::<_, _, _, TestJustification>(
&client,
backend,
&ClosureAuthoritySetForFinalityChecker(
|_, _, _| Ok(updated_set.clone())
),
&mut import_data,
Default::default(),
Default::default(),
vec![
FinalityProofFragment::<Header> {
block: hash,
justification: TestJustification(
(initial_set_id, initial_set.clone()),
Vec::new(),
).encode(),
unknown_headers: Vec::new(),
authorities_proof: Some(StorageProof::new(vec![])),
},
].encode(),
&mut verifier,
).unwrap();
// verify that new authorities set has been saved to the aux storage
let data = load_aux_import_data(Default::default(), &client, &TestApi::new(initial_set)).unwrap();
assert_eq!(data.authority_set.authorities(), updated_set);
}
}
@@ -39,7 +39,6 @@ use crate::{
};
use crate::authorities::SharedAuthoritySet;
use crate::communication::{Network as NetworkT, NetworkBridge};
use crate::consensus_changes::SharedConsensusChanges;
use crate::notification::GrandpaJustificationSender;
use sp_finality_grandpa::AuthorityId;
use std::marker::{PhantomData, Unpin};
@@ -68,7 +67,6 @@ impl<'a, Block, Client> finality_grandpa::Chain<Block::Hash, NumberFor<Block>>
fn grandpa_observer<BE, Block: BlockT, Client, S, F>(
client: &Arc<Client>,
authority_set: &SharedAuthoritySet<Block::Hash, NumberFor<Block>>,
consensus_changes: &SharedConsensusChanges<Block::Hash, NumberFor<Block>>,
voters: &Arc<VoterSet<AuthorityId>>,
justification_sender: &Option<GrandpaJustificationSender<Block>>,
last_finalized_number: NumberFor<Block>,
@@ -83,7 +81,6 @@ where
Client: crate::ClientForGrandpa<Block, BE>,
{
let authority_set = authority_set.clone();
let consensus_changes = consensus_changes.clone();
let client = client.clone();
let voters = voters.clone();
let justification_sender = justification_sender.clone();
@@ -123,7 +120,6 @@ where
match environment::finalize_block(
client.clone(),
&authority_set,
&consensus_changes,
None,
finalized_hash,
finalized_number,
@@ -293,7 +289,6 @@ where
let observer = grandpa_observer(
&self.client,
&self.persistent_data.authority_set,
&self.persistent_data.consensus_changes,
&voters,
&self.justification_sender,
last_finalized_number,
+8 -178
View File
@@ -25,7 +25,7 @@ use sc_network_test::{
Block, BlockImportAdapter, Hash, PassThroughVerifier, Peer, PeersClient, PeersFullClient,
TestClient, TestNetFactory, FullPeerConfig,
};
use sc_network::config::{ProtocolConfig, BoxFinalityProofRequestBuilder};
use sc_network::config::ProtocolConfig;
use parking_lot::{RwLock, Mutex};
use futures_timer::Delay;
use tokio::runtime::{Runtime, Handle};
@@ -36,22 +36,21 @@ use sp_api::{ApiRef, StorageProof, ProvideRuntimeApi};
use substrate_test_runtime_client::runtime::BlockNumber;
use sp_consensus::{
BlockOrigin, ForkChoiceStrategy, ImportedAux, BlockImportParams, ImportResult, BlockImport,
import_queue::{BoxJustificationImport, BoxFinalityProofImport},
import_queue::BoxJustificationImport,
};
use std::{collections::{HashMap, HashSet}, pin::Pin};
use parity_scale_codec::Decode;
use sp_runtime::traits::{Block as BlockT, Header as HeaderT, HashFor};
use sp_runtime::generic::{BlockId, DigestItem};
use sp_core::{H256, crypto::Public};
use sp_core::H256;
use sp_keystore::{SyncCryptoStorePtr, SyncCryptoStore};
use sp_finality_grandpa::{GRANDPA_ENGINE_ID, AuthorityList, EquivocationProof, GrandpaApi, OpaqueKeyOwnershipProof};
use sp_state_machine::{InMemoryBackend, prove_read, read_proof_check};
use authorities::AuthoritySet;
use finality_proof::{
FinalityProofProvider, AuthoritySetForFinalityProver, AuthoritySetForFinalityChecker,
AuthoritySetForFinalityProver, AuthoritySetForFinalityChecker,
};
use consensus_changes::ConsensusChanges;
use sc_block_builder::BlockBuilderProvider;
use sc_consensus::LongestChain;
use sc_keystore::LocalKeystore;
@@ -117,8 +116,6 @@ impl TestNetFactory for GrandpaTestNet {
-> (
BlockImportAdapter<Transaction>,
Option<BoxJustificationImport<Block>>,
Option<BoxFinalityProofImport<Block>>,
Option<BoxFinalityProofRequestBuilder<Block>>,
PeerData,
)
{
@@ -133,48 +130,15 @@ impl TestNetFactory for GrandpaTestNet {
(
BlockImportAdapter::new_full(import),
Some(justification_import),
None,
None,
Mutex::new(Some(link)),
)
},
PeersClient::Light(ref client, ref backend) => {
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 came with the block
// => light clients will try to fetch finality proofs
let import = light_block_import_without_justifications(
client.clone(),
backend.clone(),
&self.test_config,
authorities_provider,
).expect("Could not create block import for fresh peer.");
let finality_proof_req_builder = import.0.create_finality_proof_request_builder();
let proof_import = Box::new(import.clone());
(
BlockImportAdapter::new_light(import),
None,
Some(proof_import),
Some(finality_proof_req_builder),
Mutex::new(None),
)
PeersClient::Light(..) => {
panic!("Light client is not used in tests.");
},
}
}
fn make_finality_proof_provider(
&self,
client: PeersClient
) -> Option<Arc<dyn sc_network::config::FinalityProofProvider<Block>>> {
match client {
PeersClient::Full(_, ref backend) => {
Some(Arc::new(FinalityProofProvider::new(backend.clone(), self.test_config.clone())))
},
PeersClient::Light(_, _) => None,
}
}
fn peer(&mut self, i: usize) -> &mut GrandpaPeer {
&mut self.peers[i]
}
@@ -679,24 +643,6 @@ fn transition_3_voters_twice_1_full_observer() {
block_until_complete(wait_for, &net, &mut runtime);
}
#[test]
fn justification_is_emitted_when_consensus_data_changes() {
let mut runtime = Runtime::new().unwrap();
let peers = &[Ed25519Keyring::Alice, Ed25519Keyring::Bob, Ed25519Keyring::Charlie];
let mut net = GrandpaTestNet::new(TestApi::new(make_ids(peers)), 3);
// import block#1 WITH consensus data change
let new_authorities = vec![sp_consensus_babe::AuthorityId::from_slice(&[42; 32])];
net.peer(0).push_authorities_change_block(new_authorities);
net.block_until_sync();
let net = Arc::new(Mutex::new(net));
run_to_completion(&mut runtime, 1, net.clone(), peers);
// ... 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");
}
#[test]
fn justification_is_generated_periodically() {
let mut runtime = Runtime::new().unwrap();
@@ -717,25 +663,6 @@ fn justification_is_generated_periodically() {
}
}
#[test]
fn consensus_changes_works() {
let mut changes = ConsensusChanges::<H256, u64>::empty();
// pending changes are not finalized
changes.note_change((10, H256::from_low_u64_be(1)));
assert_eq!(changes.finalize((5, H256::from_low_u64_be(5)), |_| Ok(None)).unwrap(), (false, false));
// no change is selected from competing pending changes
changes.note_change((1, H256::from_low_u64_be(1)));
changes.note_change((1, H256::from_low_u64_be(101)));
assert_eq!(changes.finalize((10, H256::from_low_u64_be(10)), |_| Ok(Some(H256::from_low_u64_be(1001)))).unwrap(), (true, false));
// change is selected from competing pending changes
changes.note_change((1, H256::from_low_u64_be(1)));
changes.note_change((1, H256::from_low_u64_be(101)));
assert_eq!(changes.finalize((10, H256::from_low_u64_be(10)), |_| Ok(Some(H256::from_low_u64_be(1)))).unwrap(), (true, true));
}
#[test]
fn sync_justifications_on_change_blocks() {
let mut runtime = Runtime::new().unwrap();
@@ -944,7 +871,6 @@ fn allows_reimporting_change_blocks() {
needs_justification: true,
clear_justification_requests: false,
bad_justification: false,
needs_finality_proof: false,
is_new_best: true,
header_only: false,
}),
@@ -1069,7 +995,7 @@ fn voter_persists_its_votes() {
Poll::Pending => return Poll::Pending,
Poll::Ready(None) => return Poll::Ready(()),
Poll::Ready(Some(())) => {
let (_block_import, _, _, _, link) =
let (_block_import, _, link) =
this.net.lock()
.make_block_import::<
TransactionFor<substrate_test_runtime_client::Backend, Block>
@@ -1144,7 +1070,7 @@ fn voter_persists_its_votes() {
};
let set_state = {
let (_, _, _, _, link) = net.lock()
let (_, _, link) = net.lock()
.make_block_import::<
TransactionFor<substrate_test_runtime_client::Backend, Block>
>(client);
@@ -1311,100 +1237,6 @@ fn finalize_3_voters_1_light_observer() {
});
}
#[test]
fn finality_proof_is_fetched_by_light_client_when_consensus_data_changes() {
sp_tracing::try_init_simple();
let mut runtime = Runtime::new().unwrap();
let peers = &[Ed25519Keyring::Alice];
let mut net = GrandpaTestNet::new(TestApi::new(make_ids(peers)), 1);
net.add_light_peer();
// 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![sp_consensus_babe::AuthorityId::from_slice(&[42; 32])]);
let net = Arc::new(Mutex::new(net));
run_to_completion(&mut runtime, 1, net.clone(), peers);
net.lock().block_until_sync();
// check that the block#1 is finalized on light client
runtime.block_on(futures::future::poll_fn(move |cx| {
if net.lock().peer(1).client().info().finalized_number == 1 {
Poll::Ready(())
} else {
net.lock().poll(cx);
Poll::Pending
}
}));
}
#[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;
sp_tracing::try_init_simple();
let mut runtime = Runtime::new().unwrap();
// two of these guys are offline.
let genesis_authorities = if FORCE_CHANGE {
vec![
Ed25519Keyring::Alice,
Ed25519Keyring::Bob,
Ed25519Keyring::Charlie,
Ed25519Keyring::One,
Ed25519Keyring::Two,
]
} else {
vec![
Ed25519Keyring::Alice,
Ed25519Keyring::Bob,
Ed25519Keyring::Charlie,
]
};
let peers_a = &[Ed25519Keyring::Alice, Ed25519Keyring::Bob, Ed25519Keyring::Charlie];
let api = TestApi::new(make_ids(&genesis_authorities));
let voters = make_ids(peers_a);
let net = GrandpaTestNet::new(api, 3);
let net = Arc::new(Mutex::new(net));
// best is #1
net.lock().peer(0).generate_blocks(1, BlockOrigin::File, |builder| {
// add a forced transition at block 5.
let mut block = builder.build().unwrap().block;
if FORCE_CHANGE {
add_forced_change(&mut block, 0, ScheduledChange {
next_authorities: voters.clone(),
delay: 3,
});
}
block
});
// 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![sp_consensus_babe::AuthorityId::from_slice(&[42; 32])]
); // #10
net.lock().peer(0).push_blocks(1, false); // best is #11
net.lock().block_until_sync();
// finalize block #11 on full clients
run_to_completion(&mut runtime, 11, net.clone(), peers_a);
// request finalization by light client
net.lock().add_light_peer();
net.lock().block_until_sync();
// check block, finalized on light client
assert_eq!(
net.lock().peer(3).client().info().finalized_number,
if FORCE_CHANGE { 0 } else { 10 },
);
}
#[test]
fn voter_catches_up_to_latest_round_when_behind() {
sp_tracing::try_init_simple();
@@ -1540,7 +1372,6 @@ where
{
let PersistentData {
ref authority_set,
ref consensus_changes,
ref set_state,
..
} = link.persistent_data;
@@ -1564,7 +1395,6 @@ where
Environment {
authority_set: authority_set.clone(),
config: config.clone(),
consensus_changes: consensus_changes.clone(),
client: link.client.clone(),
select_chain: link.select_chain.clone(),
set_id: authority_set.set_id(),