mirror of
https://github.com/pezkuwichain/pezkuwi-subxt.git
synced 2026-05-31 04:01:02 +00:00
grandpa: always store justification for the latest finalized block (#8392)
* grandpa: always store justification for best finalized block * grandpa-warp-sync: add latest justification when finished proving * grandpa-warp-sync: change logic for sending best justification when finished * grandpa: test storing best justification * grandpa: reorder variants in WarpSyncFinished
This commit is contained in:
@@ -120,14 +120,14 @@ impl<TBlock: BlockT, TBackend: Backend<TBlock>> GrandpaWarpSyncRequestHandler<TB
|
|||||||
fn handle_request(
|
fn handle_request(
|
||||||
&self,
|
&self,
|
||||||
payload: Vec<u8>,
|
payload: Vec<u8>,
|
||||||
pending_response: oneshot::Sender<OutgoingResponse>
|
pending_response: oneshot::Sender<OutgoingResponse>,
|
||||||
) -> Result<(), HandleRequestError>
|
) -> Result<(), HandleRequestError>
|
||||||
where NumberFor<TBlock>: sc_finality_grandpa::BlockNumberOps,
|
where NumberFor<TBlock>: sc_finality_grandpa::BlockNumberOps,
|
||||||
{
|
{
|
||||||
let request = Request::<TBlock>::decode(&mut &payload[..])?;
|
let request = Request::<TBlock>::decode(&mut &payload[..])?;
|
||||||
|
|
||||||
let proof = WarpSyncProof::generate(
|
let proof = WarpSyncProof::generate(
|
||||||
self.backend.blockchain(),
|
&*self.backend,
|
||||||
request.begin,
|
request.begin,
|
||||||
&self.authority_set.authority_set_changes(),
|
&self.authority_set.authority_set_changes(),
|
||||||
)?;
|
)?;
|
||||||
|
|||||||
@@ -16,14 +16,15 @@
|
|||||||
|
|
||||||
use codec::{Decode, Encode};
|
use codec::{Decode, Encode};
|
||||||
|
|
||||||
|
use sc_client_api::Backend as ClientBackend;
|
||||||
use sc_finality_grandpa::{
|
use sc_finality_grandpa::{
|
||||||
find_scheduled_change, AuthoritySetChanges, BlockNumberOps, GrandpaJustification,
|
find_scheduled_change, AuthoritySetChanges, BlockNumberOps, GrandpaJustification,
|
||||||
};
|
};
|
||||||
use sp_blockchain::Backend as BlockchainBackend;
|
use sp_blockchain::{Backend as BlockchainBackend, HeaderBackend};
|
||||||
use sp_finality_grandpa::{AuthorityList, SetId, GRANDPA_ENGINE_ID};
|
use sp_finality_grandpa::{AuthorityList, SetId, GRANDPA_ENGINE_ID};
|
||||||
use sp_runtime::{
|
use sp_runtime::{
|
||||||
generic::BlockId,
|
generic::BlockId,
|
||||||
traits::{Block as BlockT, NumberFor},
|
traits::{Block as BlockT, NumberFor, One},
|
||||||
};
|
};
|
||||||
|
|
||||||
use crate::HandleRequestError;
|
use crate::HandleRequestError;
|
||||||
@@ -42,11 +43,21 @@ pub struct AuthoritySetChangeProof<Block: BlockT> {
|
|||||||
pub justification: GrandpaJustification<Block>,
|
pub justification: GrandpaJustification<Block>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Represents the current state of the warp sync, namely whether it is considered
|
||||||
|
/// finished, i.e. we have proved everything up until the latest authority set, or not.
|
||||||
|
/// When the warp sync is finished we might optionally provide a justification for the
|
||||||
|
/// latest finalized block, which should be checked against the latest authority set.
|
||||||
|
#[derive(Debug, Decode, Encode)]
|
||||||
|
pub enum WarpSyncFinished<Block: BlockT> {
|
||||||
|
No,
|
||||||
|
Yes(Option<GrandpaJustification<Block>>),
|
||||||
|
}
|
||||||
|
|
||||||
/// An accumulated proof of multiple authority set changes.
|
/// An accumulated proof of multiple authority set changes.
|
||||||
#[derive(Decode, Encode)]
|
#[derive(Decode, Encode)]
|
||||||
pub struct WarpSyncProof<Block: BlockT> {
|
pub struct WarpSyncProof<Block: BlockT> {
|
||||||
proofs: Vec<AuthoritySetChangeProof<Block>>,
|
proofs: Vec<AuthoritySetChangeProof<Block>>,
|
||||||
is_finished: bool,
|
is_finished: WarpSyncFinished<Block>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<Block: BlockT> WarpSyncProof<Block> {
|
impl<Block: BlockT> WarpSyncProof<Block> {
|
||||||
@@ -59,21 +70,22 @@ impl<Block: BlockT> WarpSyncProof<Block> {
|
|||||||
set_changes: &AuthoritySetChanges<NumberFor<Block>>,
|
set_changes: &AuthoritySetChanges<NumberFor<Block>>,
|
||||||
) -> Result<WarpSyncProof<Block>, HandleRequestError>
|
) -> Result<WarpSyncProof<Block>, HandleRequestError>
|
||||||
where
|
where
|
||||||
Backend: BlockchainBackend<Block>,
|
Backend: ClientBackend<Block>,
|
||||||
{
|
{
|
||||||
// TODO: cache best response (i.e. the one with lowest begin_number)
|
// TODO: cache best response (i.e. the one with lowest begin_number)
|
||||||
|
let blockchain = backend.blockchain();
|
||||||
|
|
||||||
let begin_number = backend
|
let begin_number = blockchain
|
||||||
.block_number_from_id(&BlockId::Hash(begin))?
|
.block_number_from_id(&BlockId::Hash(begin))?
|
||||||
.ok_or_else(|| HandleRequestError::InvalidRequest("Missing start block".to_string()))?;
|
.ok_or_else(|| HandleRequestError::InvalidRequest("Missing start block".to_string()))?;
|
||||||
|
|
||||||
if begin_number > backend.info().finalized_number {
|
if begin_number > blockchain.info().finalized_number {
|
||||||
return Err(HandleRequestError::InvalidRequest(
|
return Err(HandleRequestError::InvalidRequest(
|
||||||
"Start block is not finalized".to_string(),
|
"Start block is not finalized".to_string(),
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
let canon_hash = backend.hash(begin_number)?.expect(
|
let canon_hash = blockchain.hash(begin_number)?.expect(
|
||||||
"begin number is lower than finalized number; \
|
"begin number is lower than finalized number; \
|
||||||
all blocks below finalized number must have been imported; \
|
all blocks below finalized number must have been imported; \
|
||||||
qed.",
|
qed.",
|
||||||
@@ -86,7 +98,6 @@ impl<Block: BlockT> WarpSyncProof<Block> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
let mut proofs = Vec::new();
|
let mut proofs = Vec::new();
|
||||||
|
|
||||||
let mut proof_limit_reached = false;
|
let mut proof_limit_reached = false;
|
||||||
|
|
||||||
for (_, last_block) in set_changes.iter_from(begin_number) {
|
for (_, last_block) in set_changes.iter_from(begin_number) {
|
||||||
@@ -95,7 +106,7 @@ impl<Block: BlockT> WarpSyncProof<Block> {
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
let header = backend.header(BlockId::Number(*last_block))?.expect(
|
let header = blockchain.header(BlockId::Number(*last_block))?.expect(
|
||||||
"header number comes from previously applied set changes; must exist in db; qed.",
|
"header number comes from previously applied set changes; must exist in db; qed.",
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -108,7 +119,7 @@ impl<Block: BlockT> WarpSyncProof<Block> {
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
let justification = backend
|
let justification = blockchain
|
||||||
.justifications(BlockId::Number(*last_block))?
|
.justifications(BlockId::Number(*last_block))?
|
||||||
.and_then(|just| just.into_justification(GRANDPA_ENGINE_ID))
|
.and_then(|just| just.into_justification(GRANDPA_ENGINE_ID))
|
||||||
.expect(
|
.expect(
|
||||||
@@ -125,9 +136,29 @@ impl<Block: BlockT> WarpSyncProof<Block> {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let is_finished = if proof_limit_reached {
|
||||||
|
WarpSyncFinished::No
|
||||||
|
} else {
|
||||||
|
let latest =
|
||||||
|
sc_finality_grandpa::best_justification(backend)?.filter(|justification| {
|
||||||
|
// the existing best justification must be for a block higher than the
|
||||||
|
// last authority set change. if we didn't prove any authority set
|
||||||
|
// change then we fallback to make sure it's higher or equal to the
|
||||||
|
// initial warp sync block.
|
||||||
|
let limit = proofs
|
||||||
|
.last()
|
||||||
|
.map(|proof| proof.justification.target().0 + One::one())
|
||||||
|
.unwrap_or(begin_number);
|
||||||
|
|
||||||
|
justification.target().0 >= limit
|
||||||
|
});
|
||||||
|
|
||||||
|
WarpSyncFinished::Yes(latest)
|
||||||
|
};
|
||||||
|
|
||||||
Ok(WarpSyncProof {
|
Ok(WarpSyncProof {
|
||||||
proofs,
|
proofs,
|
||||||
is_finished: !proof_limit_reached,
|
is_finished,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -160,6 +191,12 @@ impl<Block: BlockT> WarpSyncProof<Block> {
|
|||||||
current_set_id += 1;
|
current_set_id += 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if let WarpSyncFinished::Yes(Some(ref justification)) = self.is_finished {
|
||||||
|
justification
|
||||||
|
.verify(current_set_id, ¤t_authorities)
|
||||||
|
.map_err(|err| HandleRequestError::InvalidProof(err.to_string()))?;
|
||||||
|
}
|
||||||
|
|
||||||
Ok((current_set_id, current_authorities))
|
Ok((current_set_id, current_authorities))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -170,7 +207,6 @@ mod tests {
|
|||||||
use codec::Encode;
|
use codec::Encode;
|
||||||
use rand::prelude::*;
|
use rand::prelude::*;
|
||||||
use sc_block_builder::BlockBuilderProvider;
|
use sc_block_builder::BlockBuilderProvider;
|
||||||
use sc_client_api::Backend;
|
|
||||||
use sc_finality_grandpa::{AuthoritySetChanges, GrandpaJustification};
|
use sc_finality_grandpa::{AuthoritySetChanges, GrandpaJustification};
|
||||||
use sp_blockchain::HeaderBackend;
|
use sp_blockchain::HeaderBackend;
|
||||||
use sp_consensus::BlockOrigin;
|
use sp_consensus::BlockOrigin;
|
||||||
@@ -295,8 +331,7 @@ mod tests {
|
|||||||
let genesis_hash = client.hash(0).unwrap().unwrap();
|
let genesis_hash = client.hash(0).unwrap().unwrap();
|
||||||
|
|
||||||
let warp_sync_proof =
|
let warp_sync_proof =
|
||||||
WarpSyncProof::generate(backend.blockchain(), genesis_hash, &authority_set_changes)
|
WarpSyncProof::generate(&*backend, genesis_hash, &authority_set_changes).unwrap();
|
||||||
.unwrap();
|
|
||||||
|
|
||||||
// verifying the proof should yield the last set id and authorities
|
// verifying the proof should yield the last set id and authorities
|
||||||
let (new_set_id, new_authorities) = warp_sync_proof.verify(0, genesis_authorities).unwrap();
|
let (new_set_id, new_authorities) = warp_sync_proof.verify(0, genesis_authorities).unwrap();
|
||||||
|
|||||||
@@ -19,27 +19,30 @@
|
|||||||
//! Schema for stuff in the aux-db.
|
//! Schema for stuff in the aux-db.
|
||||||
|
|
||||||
use std::fmt::Debug;
|
use std::fmt::Debug;
|
||||||
use parity_scale_codec::{Encode, Decode};
|
|
||||||
use sc_client_api::backend::AuxStore;
|
|
||||||
use sp_blockchain::{Result as ClientResult, Error as ClientError};
|
|
||||||
use fork_tree::ForkTree;
|
|
||||||
use finality_grandpa::round::State as RoundState;
|
use finality_grandpa::round::State as RoundState;
|
||||||
use sp_runtime::traits::{Block as BlockT, NumberFor};
|
|
||||||
use log::{info, warn};
|
use log::{info, warn};
|
||||||
use sp_finality_grandpa::{AuthorityList, SetId, RoundNumber};
|
use parity_scale_codec::{Decode, Encode};
|
||||||
|
|
||||||
|
use fork_tree::ForkTree;
|
||||||
|
use sc_client_api::backend::AuxStore;
|
||||||
|
use sp_blockchain::{Error as ClientError, Result as ClientResult};
|
||||||
|
use sp_finality_grandpa::{AuthorityList, RoundNumber, SetId};
|
||||||
|
use sp_runtime::traits::{Block as BlockT, NumberFor};
|
||||||
|
|
||||||
use crate::authorities::{
|
use crate::authorities::{
|
||||||
AuthoritySet, AuthoritySetChanges, SharedAuthoritySet, PendingChange, DelayKind,
|
AuthoritySet, AuthoritySetChanges, DelayKind, PendingChange, SharedAuthoritySet,
|
||||||
};
|
};
|
||||||
use crate::environment::{
|
use crate::environment::{
|
||||||
CompletedRound, CompletedRounds, CurrentRounds, HasVoted, SharedVoterSetState, VoterSetState,
|
CompletedRound, CompletedRounds, CurrentRounds, HasVoted, SharedVoterSetState, VoterSetState,
|
||||||
};
|
};
|
||||||
use crate::NewAuthoritySet;
|
use crate::{GrandpaJustification, NewAuthoritySet};
|
||||||
|
|
||||||
const VERSION_KEY: &[u8] = b"grandpa_schema_version";
|
const VERSION_KEY: &[u8] = b"grandpa_schema_version";
|
||||||
const SET_STATE_KEY: &[u8] = b"grandpa_completed_round";
|
const SET_STATE_KEY: &[u8] = b"grandpa_completed_round";
|
||||||
const CONCLUDED_ROUNDS: &[u8] = b"grandpa_concluded_rounds";
|
const CONCLUDED_ROUNDS: &[u8] = b"grandpa_concluded_rounds";
|
||||||
const AUTHORITY_SET_KEY: &[u8] = b"grandpa_voters";
|
const AUTHORITY_SET_KEY: &[u8] = b"grandpa_voters";
|
||||||
|
const BEST_JUSTIFICATION: &[u8] = b"grandpa_best_justification";
|
||||||
|
|
||||||
const CURRENT_VERSION: u32 = 3;
|
const CURRENT_VERSION: u32 = 3;
|
||||||
|
|
||||||
@@ -464,7 +467,7 @@ where
|
|||||||
pub(crate) fn update_authority_set<Block: BlockT, F, R>(
|
pub(crate) fn update_authority_set<Block: BlockT, F, R>(
|
||||||
set: &AuthoritySet<Block::Hash, NumberFor<Block>>,
|
set: &AuthoritySet<Block::Hash, NumberFor<Block>>,
|
||||||
new_set: Option<&NewAuthoritySet<Block::Hash, NumberFor<Block>>>,
|
new_set: Option<&NewAuthoritySet<Block::Hash, NumberFor<Block>>>,
|
||||||
write_aux: F
|
write_aux: F,
|
||||||
) -> R
|
) -> R
|
||||||
where
|
where
|
||||||
F: FnOnce(&[(&'static [u8], &[u8])]) -> R,
|
F: FnOnce(&[(&'static [u8], &[u8])]) -> R,
|
||||||
@@ -492,6 +495,33 @@ where
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Update the justification for the latest finalized block on-disk.
|
||||||
|
///
|
||||||
|
/// We always keep around the justification for the best finalized block and overwrite it
|
||||||
|
/// as we finalize new blocks, this makes sure that we don't store useless justifications
|
||||||
|
/// but can always prove finality of the latest block.
|
||||||
|
pub(crate) fn update_best_justification<Block: BlockT, F, R>(
|
||||||
|
justification: &GrandpaJustification<Block>,
|
||||||
|
write_aux: F,
|
||||||
|
) -> R
|
||||||
|
where
|
||||||
|
F: FnOnce(&[(&'static [u8], &[u8])]) -> R,
|
||||||
|
{
|
||||||
|
let encoded_justification = justification.encode();
|
||||||
|
write_aux(&[(BEST_JUSTIFICATION, &encoded_justification[..])])
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Fetch the justification for the latest block finalized by GRANDPA, if any.
|
||||||
|
pub fn best_justification<B, Block>(
|
||||||
|
backend: &B,
|
||||||
|
) -> ClientResult<Option<GrandpaJustification<Block>>>
|
||||||
|
where
|
||||||
|
B: AuxStore,
|
||||||
|
Block: BlockT,
|
||||||
|
{
|
||||||
|
load_decode::<_, GrandpaJustification<Block>>(backend, BEST_JUSTIFICATION)
|
||||||
|
}
|
||||||
|
|
||||||
/// Write voter set state.
|
/// Write voter set state.
|
||||||
pub(crate) fn write_voter_set_state<Block: BlockT, B: AuxStore>(
|
pub(crate) fn write_voter_set_state<Block: BlockT, B: AuxStore>(
|
||||||
backend: &B,
|
backend: &B,
|
||||||
@@ -517,10 +547,9 @@ pub(crate) fn write_concluded_round<Block: BlockT, B: AuxStore>(
|
|||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
pub(crate) fn load_authorities<B: AuxStore, H: Decode, N: Decode + Clone + Ord>(
|
pub(crate) fn load_authorities<B: AuxStore, H: Decode, N: Decode + Clone + Ord>(
|
||||||
backend: &B
|
backend: &B,
|
||||||
) -> Option<AuthoritySet<H, N>> {
|
) -> Option<AuthoritySet<H, N>> {
|
||||||
load_decode::<_, AuthoritySet<H, N>>(backend, AUTHORITY_SET_KEY)
|
load_decode::<_, AuthoritySet<H, N>>(backend, AUTHORITY_SET_KEY).expect("backend error")
|
||||||
.expect("backend error")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
|
|||||||
@@ -1275,11 +1275,8 @@ where
|
|||||||
// `N+1`. this assumption is required to make sure we store
|
// `N+1`. this assumption is required to make sure we store
|
||||||
// justifications for transition blocks which will be requested by
|
// justifications for transition blocks which will be requested by
|
||||||
// syncing clients.
|
// syncing clients.
|
||||||
let justification = match justification_or_commit {
|
let (justification_required, justification) = match justification_or_commit {
|
||||||
JustificationOrCommit::Justification(justification) => {
|
JustificationOrCommit::Justification(justification) => (true, justification),
|
||||||
notify_justification(justification_sender, || Ok(justification.clone()));
|
|
||||||
Some(justification.encode())
|
|
||||||
},
|
|
||||||
JustificationOrCommit::Commit((round_number, commit)) => {
|
JustificationOrCommit::Commit((round_number, commit)) => {
|
||||||
let mut justification_required =
|
let mut justification_required =
|
||||||
// justification is always required when block that enacts new authorities
|
// justification is always required when block that enacts new authorities
|
||||||
@@ -1297,42 +1294,35 @@ where
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// NOTE: the code below is a bit more verbose because we
|
let justification = GrandpaJustification::from_commit(
|
||||||
// really want to avoid creating a justification if it isn't
|
|
||||||
// needed (e.g. if there's no subscribers), and also to avoid
|
|
||||||
// creating it twice. depending on the vote tree for the round,
|
|
||||||
// creating a justification might require multiple fetches of
|
|
||||||
// headers from the database.
|
|
||||||
let justification = || GrandpaJustification::from_commit(
|
|
||||||
&client,
|
&client,
|
||||||
round_number,
|
round_number,
|
||||||
commit,
|
commit,
|
||||||
);
|
)?;
|
||||||
|
|
||||||
if justification_required {
|
(justification_required, justification)
|
||||||
let justification = justification()?;
|
|
||||||
notify_justification(justification_sender, || Ok(justification.clone()));
|
|
||||||
|
|
||||||
Some(justification.encode())
|
|
||||||
} else {
|
|
||||||
notify_justification(justification_sender, justification);
|
|
||||||
|
|
||||||
None
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
debug!(target: "afg", "Finalizing blocks up to ({:?}, {})", number, hash);
|
notify_justification(justification_sender, || Ok(justification.clone()));
|
||||||
|
|
||||||
|
let persisted_justification = if justification_required {
|
||||||
|
Some((GRANDPA_ENGINE_ID, justification.encode()))
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
};
|
||||||
|
|
||||||
// ideally some handle to a synchronization oracle would be used
|
// ideally some handle to a synchronization oracle would be used
|
||||||
// to avoid unconditionally notifying.
|
// to avoid unconditionally notifying.
|
||||||
let justification = justification.map(|j| (GRANDPA_ENGINE_ID, j.clone()));
|
|
||||||
client
|
client
|
||||||
.apply_finality(import_op, BlockId::Hash(hash), justification, true)
|
.apply_finality(import_op, BlockId::Hash(hash), persisted_justification, true)
|
||||||
.map_err(|e| {
|
.map_err(|e| {
|
||||||
warn!(target: "afg", "Error applying finality to block {:?}: {:?}", (hash, number), e);
|
warn!(target: "afg", "Error applying finality to block {:?}: {:?}", (hash, number), e);
|
||||||
e
|
e
|
||||||
})?;
|
})?;
|
||||||
|
|
||||||
|
debug!(target: "afg", "Finalizing blocks up to ({:?}, {})", number, hash);
|
||||||
|
|
||||||
telemetry!(
|
telemetry!(
|
||||||
telemetry;
|
telemetry;
|
||||||
CONSENSUS_INFO;
|
CONSENSUS_INFO;
|
||||||
@@ -1340,6 +1330,11 @@ where
|
|||||||
"number" => ?number, "hash" => ?hash,
|
"number" => ?number, "hash" => ?hash,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
crate::aux_schema::update_best_justification(
|
||||||
|
&justification,
|
||||||
|
|insert| apply_aux(import_op, insert, &[]),
|
||||||
|
)?;
|
||||||
|
|
||||||
let new_authorities = if let Some((canon_hash, canon_number)) = status.new_set_block {
|
let new_authorities = if let Some((canon_hash, canon_number)) = status.new_set_block {
|
||||||
// the authority set has changed.
|
// the authority set has changed.
|
||||||
let (new_id, set_ref) = authority_set.current();
|
let (new_id, set_ref) = authority_set.current();
|
||||||
|
|||||||
@@ -195,6 +195,11 @@ impl<Block: BlockT> GrandpaJustification<Block> {
|
|||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// The target block number and hash that this justifications proves finality for.
|
||||||
|
pub fn target(&self) -> (NumberFor<Block>, Block::Hash) {
|
||||||
|
(self.commit.target_number, self.commit.target_hash)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A utility trait implementing `finality_grandpa::Chain` using a given set of headers.
|
/// A utility trait implementing `finality_grandpa::Chain` using a given set of headers.
|
||||||
|
|||||||
@@ -122,6 +122,7 @@ mod until_imported;
|
|||||||
mod voting_rule;
|
mod voting_rule;
|
||||||
|
|
||||||
pub use authorities::{AuthoritySet, AuthoritySetChanges, SharedAuthoritySet};
|
pub use authorities::{AuthoritySet, AuthoritySetChanges, SharedAuthoritySet};
|
||||||
|
pub use aux_schema::best_justification;
|
||||||
pub use finality_proof::{FinalityProof, FinalityProofProvider, FinalityProofError};
|
pub use finality_proof::{FinalityProof, FinalityProofProvider, FinalityProofError};
|
||||||
pub use notification::{GrandpaJustificationSender, GrandpaJustificationStream};
|
pub use notification::{GrandpaJustificationSender, GrandpaJustificationStream};
|
||||||
pub use import::{find_scheduled_change, find_forced_change, GrandpaBlockImport};
|
pub use import::{find_scheduled_change, find_forced_change, GrandpaBlockImport};
|
||||||
|
|||||||
@@ -451,10 +451,19 @@ fn finalize_3_voters_1_full_observer() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// wait for all finalized on each.
|
// wait for all finalized on each.
|
||||||
let wait_for = futures::future::join_all(finality_notifications)
|
let wait_for = futures::future::join_all(finality_notifications).map(|_| ());
|
||||||
.map(|_| ());
|
|
||||||
|
|
||||||
block_until_complete(wait_for, &net, &mut runtime);
|
block_until_complete(wait_for, &net, &mut runtime);
|
||||||
|
|
||||||
|
// all peers should have stored the justification for the best finalized block #20
|
||||||
|
for peer_id in 0..4 {
|
||||||
|
let client = net.lock().peers[peer_id].client().as_full().unwrap();
|
||||||
|
let justification = crate::aux_schema::best_justification::<_, Block>(&*client)
|
||||||
|
.unwrap()
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
assert_eq!(justification.commit.target_number, 20);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
|||||||
Reference in New Issue
Block a user