mirror of
https://github.com/pezkuwichain/pezkuwi-subxt.git
synced 2026-06-19 22:51:03 +00:00
Move grandpa crates to consensus folder (#13458)
* Move grandpa under consensus dir * Rename grandpa folder * Finish grandpa renaming * Minor tweaks * Cargo fmt * Adjust path to chain spec
This commit is contained in:
@@ -0,0 +1,591 @@
|
||||
// This file is part of Substrate.
|
||||
|
||||
// Copyright (C) Parity Technologies (UK) Ltd.
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0
|
||||
|
||||
// This program 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.
|
||||
|
||||
// This program 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 this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
//! GRANDPA block finality proof generation and check.
|
||||
//!
|
||||
//! Finality of block B is proved by providing:
|
||||
//! 1) the justification for the descendant block F;
|
||||
//! 2) headers sub-chain (B; F] if B != F;
|
||||
//! 3) proof of GRANDPA::authorities() if the set changes at block F.
|
||||
//!
|
||||
//! Since earliest possible justification is returned, the GRANDPA authorities set
|
||||
//! at the block F is guaranteed to be the same as in the block B (this is because block
|
||||
//! that enacts new GRANDPA authorities set always comes with justification). It also
|
||||
//! means that the `set_id` is the same at blocks B and F.
|
||||
//!
|
||||
//! Let U be the last finalized block known to caller. If authorities set has changed several
|
||||
//! times in the (U; F] interval, multiple finality proof fragments are returned (one for each
|
||||
//! authority set change) and they must be verified in-order.
|
||||
//!
|
||||
//! Finality proof provider can choose how to provide finality proof on its own. The incomplete
|
||||
//! finality proof (that finalizes some block C that is ancestor of the B and descendant
|
||||
//! of the U) could be returned.
|
||||
|
||||
use log::{trace, warn};
|
||||
use std::sync::Arc;
|
||||
|
||||
use parity_scale_codec::{Decode, Encode};
|
||||
use sc_client_api::backend::Backend;
|
||||
use sp_blockchain::{Backend as BlockchainBackend, HeaderBackend};
|
||||
use sp_consensus_grandpa::GRANDPA_ENGINE_ID;
|
||||
use sp_runtime::{
|
||||
generic::BlockId,
|
||||
traits::{Block as BlockT, Header as HeaderT, NumberFor, One},
|
||||
};
|
||||
|
||||
use crate::{
|
||||
authorities::{AuthoritySetChangeId, AuthoritySetChanges},
|
||||
best_justification,
|
||||
justification::GrandpaJustification,
|
||||
SharedAuthoritySet, LOG_TARGET,
|
||||
};
|
||||
|
||||
const MAX_UNKNOWN_HEADERS: usize = 100_000;
|
||||
|
||||
/// Finality proof provider for serving network requests.
|
||||
#[derive(Clone)]
|
||||
pub struct FinalityProofProvider<BE, Block: BlockT> {
|
||||
backend: Arc<BE>,
|
||||
shared_authority_set: Option<SharedAuthoritySet<Block::Hash, NumberFor<Block>>>,
|
||||
}
|
||||
|
||||
impl<B, Block> FinalityProofProvider<B, Block>
|
||||
where
|
||||
Block: BlockT,
|
||||
B: Backend<Block>,
|
||||
{
|
||||
/// Create new finality proof provider using:
|
||||
///
|
||||
/// - backend for accessing blockchain data;
|
||||
/// - authority_provider for calling and proving runtime methods.
|
||||
/// - shared_authority_set for accessing authority set data
|
||||
pub fn new(
|
||||
backend: Arc<B>,
|
||||
shared_authority_set: Option<SharedAuthoritySet<Block::Hash, NumberFor<Block>>>,
|
||||
) -> Self {
|
||||
FinalityProofProvider { backend, shared_authority_set }
|
||||
}
|
||||
|
||||
/// Create new finality proof provider for the service using:
|
||||
///
|
||||
/// - backend for accessing blockchain data;
|
||||
/// - storage_provider, which is generally a client.
|
||||
/// - shared_authority_set for accessing authority set data
|
||||
pub fn new_for_service(
|
||||
backend: Arc<B>,
|
||||
shared_authority_set: Option<SharedAuthoritySet<Block::Hash, NumberFor<Block>>>,
|
||||
) -> Arc<Self> {
|
||||
Arc::new(Self::new(backend, shared_authority_set))
|
||||
}
|
||||
}
|
||||
|
||||
impl<B, Block> FinalityProofProvider<B, Block>
|
||||
where
|
||||
Block: BlockT,
|
||||
B: Backend<Block>,
|
||||
{
|
||||
/// Prove finality for the given block number by returning a Justification for the last block of
|
||||
/// the authority set in bytes.
|
||||
pub fn prove_finality(
|
||||
&self,
|
||||
block: NumberFor<Block>,
|
||||
) -> Result<Option<Vec<u8>>, FinalityProofError> {
|
||||
Ok(self.prove_finality_proof(block, true)?.map(|proof| proof.encode()))
|
||||
}
|
||||
|
||||
/// Prove finality for the given block number by returning a Justification for the last block of
|
||||
/// the authority set.
|
||||
///
|
||||
/// If `collect_unknown_headers` is true, the finality proof will include all headers from the
|
||||
/// requested block until the block the justification refers to.
|
||||
pub fn prove_finality_proof(
|
||||
&self,
|
||||
block: NumberFor<Block>,
|
||||
collect_unknown_headers: bool,
|
||||
) -> Result<Option<FinalityProof<Block::Header>>, FinalityProofError> {
|
||||
let authority_set_changes = if let Some(changes) = self
|
||||
.shared_authority_set
|
||||
.as_ref()
|
||||
.map(SharedAuthoritySet::authority_set_changes)
|
||||
{
|
||||
changes
|
||||
} else {
|
||||
return Ok(None)
|
||||
};
|
||||
|
||||
prove_finality(&*self.backend, authority_set_changes, block, collect_unknown_headers)
|
||||
}
|
||||
}
|
||||
|
||||
/// Finality for block B is proved by providing:
|
||||
/// 1) the justification for the descendant block F;
|
||||
/// 2) headers sub-chain (B; F] if B != F;
|
||||
#[derive(Debug, PartialEq, Encode, Decode, Clone)]
|
||||
pub struct FinalityProof<Header: HeaderT> {
|
||||
/// The hash of block F for which justification is provided.
|
||||
pub block: Header::Hash,
|
||||
/// Justification of the block F.
|
||||
pub justification: Vec<u8>,
|
||||
/// The set of headers in the range (B; F] that we believe are unknown to the caller. Ordered.
|
||||
pub unknown_headers: Vec<Header>,
|
||||
}
|
||||
|
||||
/// Errors occurring when trying to prove finality
|
||||
#[derive(Debug, thiserror::Error)]
|
||||
pub enum FinalityProofError {
|
||||
/// The requested block has not yet been finalized.
|
||||
#[error("Block not yet finalized")]
|
||||
BlockNotYetFinalized,
|
||||
/// The requested block is not covered by authority set changes. Likely this means the block is
|
||||
/// in the latest authority set, and the subscription API is more appropriate.
|
||||
#[error("Block not covered by authority set changes")]
|
||||
BlockNotInAuthoritySetChanges,
|
||||
/// Errors originating from the client.
|
||||
#[error(transparent)]
|
||||
Client(#[from] sp_blockchain::Error),
|
||||
}
|
||||
|
||||
/// Prove finality for the given block number by returning a justification for the last block of
|
||||
/// the authority set of which the given block is part of, or a justification for the latest
|
||||
/// finalized block if the given block is part of the current authority set.
|
||||
///
|
||||
/// If `collect_unknown_headers` is true, the finality proof will include all headers from the
|
||||
/// requested block until the block the justification refers to.
|
||||
fn prove_finality<Block, B>(
|
||||
backend: &B,
|
||||
authority_set_changes: AuthoritySetChanges<NumberFor<Block>>,
|
||||
block: NumberFor<Block>,
|
||||
collect_unknown_headers: bool,
|
||||
) -> Result<Option<FinalityProof<Block::Header>>, FinalityProofError>
|
||||
where
|
||||
Block: BlockT,
|
||||
B: Backend<Block>,
|
||||
{
|
||||
// Early-return if we are sure that there are no blocks finalized that cover the requested
|
||||
// block.
|
||||
let finalized_number = backend.blockchain().info().finalized_number;
|
||||
if finalized_number < block {
|
||||
let err = format!(
|
||||
"Requested finality proof for descendant of #{} while we only have finalized #{}.",
|
||||
block, finalized_number,
|
||||
);
|
||||
trace!(target: LOG_TARGET, "{}", &err);
|
||||
return Err(FinalityProofError::BlockNotYetFinalized)
|
||||
}
|
||||
|
||||
let (justification, just_block) = match authority_set_changes.get_set_id(block) {
|
||||
AuthoritySetChangeId::Latest => {
|
||||
if let Some(justification) = best_justification(backend)?
|
||||
.map(|j: GrandpaJustification<Block>| (j.encode(), j.target().0))
|
||||
{
|
||||
justification
|
||||
} else {
|
||||
trace!(
|
||||
target: LOG_TARGET,
|
||||
"No justification found for the latest finalized block. \
|
||||
Returning empty proof.",
|
||||
);
|
||||
return Ok(None)
|
||||
}
|
||||
},
|
||||
AuthoritySetChangeId::Set(_, last_block_for_set) => {
|
||||
let last_block_for_set_id = backend
|
||||
.blockchain()
|
||||
.expect_block_hash_from_id(&BlockId::Number(last_block_for_set))?;
|
||||
let justification = if let Some(grandpa_justification) = backend
|
||||
.blockchain()
|
||||
.justifications(last_block_for_set_id)?
|
||||
.and_then(|justifications| justifications.into_justification(GRANDPA_ENGINE_ID))
|
||||
{
|
||||
grandpa_justification
|
||||
} else {
|
||||
trace!(
|
||||
target: LOG_TARGET,
|
||||
"No justification found when making finality proof for {}. \
|
||||
Returning empty proof.",
|
||||
block,
|
||||
);
|
||||
return Ok(None)
|
||||
};
|
||||
(justification, last_block_for_set)
|
||||
},
|
||||
AuthoritySetChangeId::Unknown => {
|
||||
warn!(
|
||||
target: LOG_TARGET,
|
||||
"AuthoritySetChanges does not cover the requested block #{} due to missing data. \
|
||||
You need to resync to populate AuthoritySetChanges properly.",
|
||||
block,
|
||||
);
|
||||
return Err(FinalityProofError::BlockNotInAuthoritySetChanges)
|
||||
},
|
||||
};
|
||||
|
||||
let mut headers = Vec::new();
|
||||
if collect_unknown_headers {
|
||||
// Collect all headers from the requested block until the last block of the set
|
||||
let mut current = block + One::one();
|
||||
loop {
|
||||
if current > just_block || headers.len() >= MAX_UNKNOWN_HEADERS {
|
||||
break
|
||||
}
|
||||
let hash = backend.blockchain().expect_block_hash_from_id(&BlockId::Number(current))?;
|
||||
headers.push(backend.blockchain().expect_header(hash)?);
|
||||
current += One::one();
|
||||
}
|
||||
};
|
||||
|
||||
Ok(Some(FinalityProof {
|
||||
block: backend.blockchain().expect_block_hash_from_id(&BlockId::Number(just_block))?,
|
||||
justification,
|
||||
unknown_headers: headers,
|
||||
}))
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::{authorities::AuthoritySetChanges, BlockNumberOps, ClientError, SetId};
|
||||
use futures::executor::block_on;
|
||||
use sc_block_builder::BlockBuilderProvider;
|
||||
use sc_client_api::{apply_aux, LockImportRun};
|
||||
use sp_consensus::BlockOrigin;
|
||||
use sp_consensus_grandpa::GRANDPA_ENGINE_ID as ID;
|
||||
use sp_core::crypto::UncheckedFrom;
|
||||
use sp_keyring::Ed25519Keyring;
|
||||
use substrate_test_runtime_client::{
|
||||
runtime::{Block, Header, H256},
|
||||
Backend as TestBackend, ClientBlockImportExt, ClientExt, DefaultTestClientBuilderExt,
|
||||
TestClient, TestClientBuilder, TestClientBuilderExt,
|
||||
};
|
||||
|
||||
/// Check GRANDPA proof-of-finality for the given block.
|
||||
///
|
||||
/// Returns the vector of headers that MUST be validated + imported
|
||||
/// AND if at least one of those headers is invalid, all other MUST be considered invalid.
|
||||
fn check_finality_proof<Block: BlockT>(
|
||||
current_set_id: SetId,
|
||||
current_authorities: sp_consensus_grandpa::AuthorityList,
|
||||
remote_proof: Vec<u8>,
|
||||
) -> sp_blockchain::Result<super::FinalityProof<Block::Header>>
|
||||
where
|
||||
NumberFor<Block>: BlockNumberOps,
|
||||
{
|
||||
let proof = super::FinalityProof::<Block::Header>::decode(&mut &remote_proof[..])
|
||||
.map_err(|_| ClientError::BadJustification("failed to decode finality proof".into()))?;
|
||||
|
||||
let justification: GrandpaJustification<Block> =
|
||||
Decode::decode(&mut &proof.justification[..])
|
||||
.map_err(|_| ClientError::JustificationDecode)?;
|
||||
|
||||
justification.verify(current_set_id, ¤t_authorities)?;
|
||||
|
||||
Ok(proof)
|
||||
}
|
||||
|
||||
pub(crate) type FinalityProof = super::FinalityProof<Header>;
|
||||
|
||||
fn header(number: u64) -> Header {
|
||||
let parent_hash = match number {
|
||||
0 => Default::default(),
|
||||
_ => header(number - 1).hash(),
|
||||
};
|
||||
Header::new(
|
||||
number,
|
||||
H256::from_low_u64_be(0),
|
||||
H256::from_low_u64_be(0),
|
||||
parent_hash,
|
||||
Default::default(),
|
||||
)
|
||||
}
|
||||
|
||||
fn test_blockchain(
|
||||
number_of_blocks: u64,
|
||||
to_finalize: &[u64],
|
||||
) -> (Arc<TestClient>, Arc<TestBackend>, Vec<Block>) {
|
||||
let builder = TestClientBuilder::new();
|
||||
let backend = builder.backend();
|
||||
let mut client = Arc::new(builder.build());
|
||||
|
||||
let mut blocks = Vec::new();
|
||||
for _ in 0..number_of_blocks {
|
||||
let block = client.new_block(Default::default()).unwrap().build().unwrap().block;
|
||||
block_on(client.import(BlockOrigin::Own, block.clone())).unwrap();
|
||||
blocks.push(block);
|
||||
}
|
||||
|
||||
for block in to_finalize {
|
||||
let hash = blocks[*block as usize - 1].hash();
|
||||
client.finalize_block(hash, None).unwrap();
|
||||
}
|
||||
(client, backend, blocks)
|
||||
}
|
||||
|
||||
fn store_best_justification(client: &TestClient, just: &GrandpaJustification<Block>) {
|
||||
client
|
||||
.lock_import_and_run(|import_op| {
|
||||
crate::aux_schema::update_best_justification(just, |insert| {
|
||||
apply_aux(import_op, insert, &[])
|
||||
})
|
||||
})
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn finality_proof_fails_if_no_more_last_finalized_blocks() {
|
||||
let (_, backend, _) = test_blockchain(6, &[4]);
|
||||
let authority_set_changes = AuthoritySetChanges::empty();
|
||||
|
||||
// The last finalized block is 4, so we cannot provide further justifications.
|
||||
let proof_of_5 = prove_finality(&*backend, authority_set_changes, 5, true);
|
||||
assert!(matches!(proof_of_5, Err(FinalityProofError::BlockNotYetFinalized)));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn finality_proof_is_none_if_no_justification_known() {
|
||||
let (_, backend, _) = test_blockchain(6, &[4]);
|
||||
|
||||
let mut authority_set_changes = AuthoritySetChanges::empty();
|
||||
authority_set_changes.append(0, 4);
|
||||
|
||||
// Block 4 is finalized without justification
|
||||
// => we can't prove finality of 3
|
||||
let proof_of_3 = prove_finality(&*backend, authority_set_changes, 3, true).unwrap();
|
||||
assert_eq!(proof_of_3, None);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn finality_proof_check_fails_when_proof_decode_fails() {
|
||||
// When we can't decode proof from Vec<u8>
|
||||
check_finality_proof::<Block>(
|
||||
1,
|
||||
vec![(UncheckedFrom::unchecked_from([3u8; 32]), 1u64)],
|
||||
vec![42],
|
||||
)
|
||||
.unwrap_err();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn finality_proof_check_fails_when_proof_is_empty() {
|
||||
// When decoded proof has zero length
|
||||
check_finality_proof::<Block>(
|
||||
1,
|
||||
vec![(UncheckedFrom::unchecked_from([3u8; 32]), 1u64)],
|
||||
Vec::<GrandpaJustification<Block>>::new().encode(),
|
||||
)
|
||||
.unwrap_err();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn finality_proof_check_fails_with_incomplete_justification() {
|
||||
let (_, _, blocks) = test_blockchain(8, &[4, 5, 8]);
|
||||
|
||||
// Create a commit without precommits
|
||||
let commit = finality_grandpa::Commit {
|
||||
target_hash: blocks[7].hash(),
|
||||
target_number: *blocks[7].header().number(),
|
||||
precommits: Vec::new(),
|
||||
};
|
||||
|
||||
let grandpa_just: GrandpaJustification<Block> =
|
||||
sp_consensus_grandpa::GrandpaJustification::<Header> {
|
||||
round: 8,
|
||||
votes_ancestries: Vec::new(),
|
||||
commit,
|
||||
}
|
||||
.into();
|
||||
|
||||
let finality_proof = FinalityProof {
|
||||
block: header(2).hash(),
|
||||
justification: grandpa_just.encode(),
|
||||
unknown_headers: Vec::new(),
|
||||
};
|
||||
|
||||
check_finality_proof::<Block>(
|
||||
1,
|
||||
vec![(UncheckedFrom::unchecked_from([3u8; 32]), 1u64)],
|
||||
finality_proof.encode(),
|
||||
)
|
||||
.unwrap_err();
|
||||
}
|
||||
|
||||
fn create_commit<S, Id>(
|
||||
block: Block,
|
||||
round: u64,
|
||||
set_id: SetId,
|
||||
auth: &[Ed25519Keyring],
|
||||
) -> finality_grandpa::Commit<H256, u64, S, Id>
|
||||
where
|
||||
Id: From<sp_core::ed25519::Public>,
|
||||
S: From<sp_core::ed25519::Signature>,
|
||||
{
|
||||
let mut precommits = Vec::new();
|
||||
|
||||
for voter in auth {
|
||||
let precommit = finality_grandpa::Precommit {
|
||||
target_hash: block.hash(),
|
||||
target_number: *block.header().number(),
|
||||
};
|
||||
|
||||
let msg = finality_grandpa::Message::Precommit(precommit.clone());
|
||||
let encoded = sp_consensus_grandpa::localized_payload(round, set_id, &msg);
|
||||
let signature = voter.sign(&encoded[..]).into();
|
||||
|
||||
let signed_precommit = finality_grandpa::SignedPrecommit {
|
||||
precommit,
|
||||
signature,
|
||||
id: voter.public().into(),
|
||||
};
|
||||
precommits.push(signed_precommit);
|
||||
}
|
||||
|
||||
finality_grandpa::Commit {
|
||||
target_hash: block.hash(),
|
||||
target_number: *block.header().number(),
|
||||
precommits,
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn finality_proof_check_works_with_correct_justification() {
|
||||
let (client, _, blocks) = test_blockchain(8, &[4, 5, 8]);
|
||||
|
||||
let alice = Ed25519Keyring::Alice;
|
||||
let set_id = 1;
|
||||
let round = 8;
|
||||
let commit = create_commit(blocks[7].clone(), round, set_id, &[alice]);
|
||||
let grandpa_just = GrandpaJustification::from_commit(&client, round, commit).unwrap();
|
||||
|
||||
let finality_proof = FinalityProof {
|
||||
block: header(2).hash(),
|
||||
justification: grandpa_just.encode(),
|
||||
unknown_headers: Vec::new(),
|
||||
};
|
||||
assert_eq!(
|
||||
finality_proof,
|
||||
check_finality_proof::<Block>(
|
||||
set_id,
|
||||
vec![(alice.public().into(), 1u64)],
|
||||
finality_proof.encode(),
|
||||
)
|
||||
.unwrap(),
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn finality_proof_using_authority_set_changes_fails_with_undefined_start() {
|
||||
let (_, backend, _) = test_blockchain(8, &[4, 5, 8]);
|
||||
|
||||
// We have stored the correct block number for the relevant set, but as we are missing the
|
||||
// block for the preceding set the start is not well-defined.
|
||||
let mut authority_set_changes = AuthoritySetChanges::empty();
|
||||
authority_set_changes.append(1, 8);
|
||||
|
||||
let proof_of_6 = prove_finality(&*backend, authority_set_changes, 6, true);
|
||||
assert!(matches!(proof_of_6, Err(FinalityProofError::BlockNotInAuthoritySetChanges)));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn finality_proof_using_authority_set_changes_works() {
|
||||
let (client, backend, blocks) = test_blockchain(8, &[4, 5]);
|
||||
let block7 = &blocks[6];
|
||||
let block8 = &blocks[7];
|
||||
|
||||
let round = 8;
|
||||
let commit = create_commit(block8.clone(), round, 1, &[Ed25519Keyring::Alice]);
|
||||
let grandpa_just8 = GrandpaJustification::from_commit(&client, round, commit).unwrap();
|
||||
|
||||
client
|
||||
.finalize_block(block8.hash(), Some((ID, grandpa_just8.encode().clone())))
|
||||
.unwrap();
|
||||
|
||||
// Authority set change at block 8, so the justification stored there will be used in the
|
||||
// FinalityProof for block 6
|
||||
let mut authority_set_changes = AuthoritySetChanges::empty();
|
||||
authority_set_changes.append(0, 5);
|
||||
authority_set_changes.append(1, 8);
|
||||
|
||||
let proof_of_6: FinalityProof =
|
||||
prove_finality(&*backend, authority_set_changes.clone(), 6, true)
|
||||
.unwrap()
|
||||
.unwrap();
|
||||
|
||||
assert_eq!(
|
||||
proof_of_6,
|
||||
FinalityProof {
|
||||
block: block8.hash(),
|
||||
justification: grandpa_just8.encode(),
|
||||
unknown_headers: vec![block7.header().clone(), block8.header().clone()],
|
||||
},
|
||||
);
|
||||
|
||||
let proof_of_6_without_unknown: FinalityProof =
|
||||
prove_finality(&*backend, authority_set_changes.clone(), 6, false)
|
||||
.unwrap()
|
||||
.unwrap();
|
||||
|
||||
assert_eq!(
|
||||
proof_of_6_without_unknown,
|
||||
FinalityProof {
|
||||
block: block8.hash(),
|
||||
justification: grandpa_just8.encode(),
|
||||
unknown_headers: vec![],
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn finality_proof_in_last_set_fails_without_latest() {
|
||||
let (_, backend, _) = test_blockchain(8, &[4, 5, 8]);
|
||||
|
||||
// No recent authority set change, so we are in the latest set, and we will try to pickup
|
||||
// the best stored justification, for which there is none in this case.
|
||||
let mut authority_set_changes = AuthoritySetChanges::empty();
|
||||
authority_set_changes.append(0, 5);
|
||||
|
||||
assert!(matches!(prove_finality(&*backend, authority_set_changes, 6, true), Ok(None)));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn finality_proof_in_last_set_using_latest_justification_works() {
|
||||
let (client, backend, blocks) = test_blockchain(8, &[4, 5, 8]);
|
||||
let block7 = &blocks[6];
|
||||
let block8 = &blocks[7];
|
||||
|
||||
let round = 8;
|
||||
let commit = create_commit(block8.clone(), round, 1, &[Ed25519Keyring::Alice]);
|
||||
let grandpa_just8 = GrandpaJustification::from_commit(&client, round, commit).unwrap();
|
||||
store_best_justification(&client, &grandpa_just8);
|
||||
|
||||
// No recent authority set change, so we are in the latest set, and will pickup the best
|
||||
// stored justification
|
||||
let mut authority_set_changes = AuthoritySetChanges::empty();
|
||||
authority_set_changes.append(0, 5);
|
||||
|
||||
let proof_of_6: FinalityProof =
|
||||
prove_finality(&*backend, authority_set_changes, 6, true).unwrap().unwrap();
|
||||
|
||||
assert_eq!(
|
||||
proof_of_6,
|
||||
FinalityProof {
|
||||
block: block8.hash(),
|
||||
justification: grandpa_just8.encode(),
|
||||
unknown_headers: vec![block7.header().clone(), block8.header().clone()],
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user