// Copyright 2018-2019 Parity Technologies (UK) Ltd.
// This file is part of Substrate.
// Substrate is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// Substrate is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
// You should have received a copy of the GNU General Public License
// along with Substrate. If not, see .
//! GRANDPA block finality proof generation and check.
//!
//! Finality of block B is proved by providing:
//! 1) valid headers sub-chain from the block B to the block F;
//! 2) valid (with respect to proved authorities) GRANDPA justification of the block F;
//! 3) proof-of-execution of the `grandpa_authorities` call at the 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.
//!
//! The caller should track the `set_id`. The most straightforward way is to fetch finality
//! proofs ONLY for blocks on the tip of the chain and track the latest known `set_id`.
use grandpa::VoterSet;
use client::{
blockchain::Backend as BlockchainBackend,
error::{Error as ClientError, ErrorKind as ClientErrorKind, Result as ClientResult},
light::fetcher::RemoteCallRequest,
};
use parity_codec::{Encode, Decode};
use grandpa::BlockNumberOps;
use runtime_primitives::generic::BlockId;
use runtime_primitives::traits::{
NumberFor, Block as BlockT, Header as HeaderT, One,
};
use substrate_primitives::{ed25519, H256};
use ed25519::Public as AuthorityId;
use substrate_telemetry::{telemetry, CONSENSUS_INFO};
use crate::justification::GrandpaJustification;
/// Prepare proof-of-finality for the given block.
///
/// The proof is the serialized `FinalityProof` constructed using earliest known
/// justification of the block. None is returned if there's no known justification atm.
pub fn prove_finality(
blockchain: &B,
generate_execution_proof: G,
block: Block::Hash,
) -> ::client::error::Result>>
where
B: BlockchainBackend,
G: Fn(&BlockId, &str, &[u8]) -> ClientResult>>,
{
let block_id = BlockId::Hash(block);
let mut block_number = blockchain.expect_block_number_from_id(&block_id)?;
// early-return if we sure that the block isn't finalized yet
let info = blockchain.info()?;
if info.finalized_number < block_number {
return Ok(None);
}
// early-return if we sure that the block is NOT a part of canonical chain
let canonical_block = blockchain.expect_block_hash_from_id(&BlockId::Number(block_number))?;
if block != canonical_block {
return Err(ClientErrorKind::Backend(
"Cannot generate finality proof for non-canonical block".into()
).into());
}
// now that we know that the block is finalized, we can generate finalization proof
// we need to prove grandpa authorities set that has generated justification
// BUT since `GrandpaApi::grandpa_authorities` call returns the set that becames actual
// at the next block, the proof-of execution is generated using parent block' state
// (this will fail if we're trying to prove genesis finality, but such the call itself is redundant)
let mut current_header = blockchain.expect_header(BlockId::Hash(block))?;
let parent_block_id = BlockId::Hash(*current_header.parent_hash());
let authorities_proof = generate_execution_proof(
&parent_block_id,
"GrandpaApi_grandpa_authorities",
&[],
)?;
// search for earliest post-block (inclusive) justification
let mut finalization_path = Vec::new();
loop {
finalization_path.push(current_header);
match blockchain.justification(BlockId::Number(block_number))? {
Some(justification) => return Ok(Some(FinalityProof {
finalization_path,
justification,
authorities_proof,
}.encode())),
None if block_number == info.finalized_number => break,
None => {
block_number = block_number + One::one();
current_header = blockchain.expect_header(BlockId::Number(block_number))?;
},
}
}
Err(ClientErrorKind::Backend(
"cannot find justification for finalized block".into()
).into())
}
/// Check proof-of-finality for the given block.
///
/// Returns the vector of headers (including `block` header, ordered by ASC block number) that MUST be
/// validated + imported at once (i.e. within single db transaction). If at least one of those headers
/// is invalid, all other MUST be considered invalid.
pub fn check_finality_proof, C>(
check_execution_proof: C,
parent_header: Block::Header,
block: (NumberFor, Block::Hash),
set_id: u64,
remote_proof: Vec,
) -> ClientResult>
where
NumberFor: grandpa::BlockNumberOps,
C: Fn(&RemoteCallRequest) -> ClientResult>,
{
do_check_finality_proof::>(
check_execution_proof,
parent_header,
block,
set_id,
remote_proof,
)
}
/// Check proof-of-finality using given justification type.
fn do_check_finality_proof, C, J>(
check_execution_proof: C,
parent_header: Block::Header,
block: (NumberFor, Block::Hash),
set_id: u64,
remote_proof: Vec,
) -> ClientResult>
where
NumberFor: grandpa::BlockNumberOps,
C: Fn(&RemoteCallRequest) -> ClientResult>,
J: ProvableJustification,
{
// decode finality proof
let proof = FinalityProof::::decode(&mut &remote_proof[..])
.ok_or_else(|| ClientErrorKind::BadJustification("failed to decode finality proof".into()))?;
// check that the first header in finalization path is the block itself
{
let finalized_header = proof.finalization_path.first()
.ok_or_else(|| ClientError::from(ClientErrorKind::BadJustification(
"finality proof: finalized path is empty".into()
)))?;
if *finalized_header.number() != block.0 || finalized_header.hash() != block.1 {
return Err(ClientErrorKind::BadJustification(
"finality proof: block is not a part of finalized path".into()
).into());
}
}
// check that the last header in finalization path is the justification target block
let just_block = proof.justification.target_block();
{
let finalized_header = proof.finalization_path.last()
.expect("checked above that proof.finalization_path is not empty; qed");
if *finalized_header.number() != just_block.0 || finalized_header.hash() != just_block.1 {
return Err(ClientErrorKind::BadJustification(
"finality proof: target justification block is not a part of finalized path".into()
).into());
}
}
// check authorities set proof && get grandpa authorities that should have signed justification
let grandpa_authorities = check_execution_proof(&RemoteCallRequest {
block: just_block.1,
header: parent_header,
method: "GrandpaApi_grandpa_authorities".into(),
call_data: vec![],
retry_count: None,
})?;
let grandpa_authorities: Vec<(AuthorityId, u64)> = Decode::decode(&mut &grandpa_authorities[..])
.ok_or_else(|| ClientErrorKind::BadJustification("failed to decode GRANDPA authorities set proof".into()))?;
// and now check justification
proof.justification.verify(set_id, &grandpa_authorities.into_iter().collect())?;
telemetry!(CONSENSUS_INFO; "afg.finality_proof_ok";
"set_id" => ?set_id, "finalized_header_hash" => ?block.1);
Ok(proof.finalization_path)
}
/// Proof of finality.
///
/// Finality of block B is proved by providing:
/// 1) valid headers sub-chain from the block B to the block F;
/// 2) proof of `GrandpaApi::grandpa_authorities()` call at the block F;
/// 3) valid (with respect to proved authorities) GRANDPA justification of the block F.
#[derive(Debug, PartialEq, Encode, Decode)]
struct FinalityProof {
/// Headers-path (ordered by block number, ascending) from the block we're gathering proof for
/// (inclusive) to the target block of the justification (inclusive).
pub finalization_path: Vec,
/// Justification (finalization) of the last block from the `finalization_path`.
pub justification: Justification,
/// Proof of `GrandpaApi::grandpa_authorities` call execution at the
/// justification' target block.
pub authorities_proof: Vec>,
}
/// Justification used to prove block finality.
trait ProvableJustification: Encode + Decode {
/// Get target block of this justification.
fn target_block(&self) -> (Header::Number, Header::Hash);
/// Verify justification with respect to authorities set and authorities set id.
fn verify(&self, set_id: u64, authorities: &VoterSet) -> ClientResult<()>;
}
impl> ProvableJustification for GrandpaJustification
where
NumberFor: BlockNumberOps,
{
fn target_block(&self) -> (NumberFor, Block::Hash) {
(self.commit.target_number, self.commit.target_hash)
}
fn verify(&self, set_id: u64, authorities: &VoterSet) -> ClientResult<()> {
GrandpaJustification::verify(self, set_id, authorities)
}
}
#[cfg(test)]
mod tests {
use test_client::runtime::{Block, Header};
use test_client::client::backend::NewBlockState;
use test_client::client::in_mem::Blockchain as InMemoryBlockchain;
use super::*;
type FinalityProof = super::FinalityProof>;
#[derive(Encode, Decode)]
struct ValidFinalityProof(Vec);
impl ProvableJustification for ValidFinalityProof {
fn target_block(&self) -> (u64, H256) { (3, header(3).hash()) }
fn verify(&self, set_id: u64, authorities: &VoterSet) -> ClientResult<()> {
assert_eq!(set_id, 1);
assert_eq!(authorities, &vec![
(AuthorityId([1u8; 32]), 1),
(AuthorityId([2u8; 32]), 2),
(AuthorityId([3u8; 32]), 3),
].into_iter().collect());
Ok(())
}
}
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 side_header(number: u64) -> Header {
Header::new(number, H256::from_low_u64_be(0), H256::from_low_u64_be(1), header(number - 1).hash(), Default::default())
}
fn test_blockchain() -> InMemoryBlockchain {
let blockchain = InMemoryBlockchain::::new();
blockchain.insert(header(0).hash(), header(0), Some(vec![0]), None, NewBlockState::Final).unwrap();
blockchain.insert(header(1).hash(), header(1), Some(vec![1]), None, NewBlockState::Final).unwrap();
blockchain.insert(header(2).hash(), header(2), None, None, NewBlockState::Best).unwrap();
blockchain.insert(header(3).hash(), header(3), Some(vec![3]), None, NewBlockState::Final).unwrap();
blockchain
}
#[test]
fn finality_proof_is_not_generated_for_non_final_block() {
let blockchain = test_blockchain();
blockchain.insert(header(4).hash(), header(4), None, None, NewBlockState::Best).unwrap();
// when asking for finality of block 4, None is returned
let proof_of_4 = prove_finality(&blockchain, |_, _, _| Ok(vec![vec![42]]), header(4).hash())
.unwrap();
assert_eq!(proof_of_4, None);
}
#[test]
fn finality_proof_fails_for_non_canonical_block() {
let blockchain = test_blockchain();
blockchain.insert(header(4).hash(), header(4), None, None, NewBlockState::Best).unwrap();
blockchain.insert(side_header(4).hash(), side_header(4), None, None, NewBlockState::Best).unwrap();
blockchain.insert(header(5).hash(), header(5), Some(vec![5]), None, NewBlockState::Final).unwrap();
// when asking for finality of side-block 42, None is returned
let proof_of_side_4_fails = prove_finality(&blockchain, |_, _, _| Ok(vec![vec![42]]), H256::from_low_u64_be(42)).is_err();
assert_eq!(proof_of_side_4_fails, true);
}
#[test]
fn finality_proof_fails_if_no_justification_known() {
let blockchain = test_blockchain();
blockchain.insert(header(4).hash(), header(4), None, None, NewBlockState::Final).unwrap();
// when asking for finality of block 4, search for justification failing
let proof_of_4_fails = prove_finality(&blockchain, |_, _, _| Ok(vec![vec![42]]), H256::from_low_u64_be(42)).is_err();
assert_eq!(proof_of_4_fails, true);
}
#[test]
fn prove_finality_is_generated() {
let blockchain = test_blockchain();
// when asking for finality of block 2, justification of 3 is returned
let proof_of_2: FinalityProof = prove_finality(&blockchain, |_, _, _| Ok(vec![vec![42]]), header(2).hash())
.unwrap().and_then(|p| Decode::decode(&mut &p[..])).unwrap();
assert_eq!(proof_of_2, FinalityProof {
finalization_path: vec![header(2), header(3)],
justification: vec![3],
authorities_proof: vec![vec![42]],
});
// when asking for finality of block 3, justification of 3 is returned
let proof_of_3: FinalityProof = prove_finality(&blockchain, |_, _, _| Ok(vec![vec![42]]), header(3).hash())
.unwrap().and_then(|p| Decode::decode(&mut &p[..])).unwrap();
assert_eq!(proof_of_3, FinalityProof {
finalization_path: vec![header(3)],
justification: vec![3],
authorities_proof: vec![vec![42]],
});
}
#[test]
fn finality_proof_check_fails_when_block_is_not_included() {
let mut proof_of_2: FinalityProof = prove_finality(
&test_blockchain(),
|_, _, _| Ok(vec![vec![42]]),
header(2).hash(),
).unwrap().and_then(|p| Decode::decode(&mut &p[..])).unwrap();
proof_of_2.finalization_path.remove(0);
// block for which we're trying to request finality proof is missing from finalization_path
assert_eq!(do_check_finality_proof::(
|_| Ok(Vec::::new().encode()),
header(1),
(2, header(2).hash()),
1,
proof_of_2.encode(),
).is_err(), true);
}
#[test]
fn finality_proof_check_fails_when_justified_block_is_not_included() {
let mut proof_of_2: FinalityProof = prove_finality(
&test_blockchain(),
|_, _, _| Ok(vec![vec![42]]),
header(2).hash(),
).unwrap().and_then(|p| Decode::decode(&mut &p[..])).unwrap();
proof_of_2.finalization_path.remove(1);
// justified block is missing from finalization_path
assert_eq!(do_check_finality_proof::(
|_| Ok(Vec::::new().encode()),
header(1),
(2, header(2).hash()),
1,
proof_of_2.encode(),
).is_err(), true);
}
#[test]
fn finality_proof_check_fails_when_justification_verification_fails() {
#[derive(Encode, Decode)]
struct InvalidFinalityProof(Vec);
impl ProvableJustification for InvalidFinalityProof {
fn target_block(&self) -> (u64, H256) { (3, header(3).hash()) }
fn verify(&self, _set_id: u64, _authorities: &VoterSet) -> ClientResult<()> {
Err(ClientErrorKind::Backend("test error".into()).into())
}
}
let mut proof_of_2: FinalityProof = prove_finality(
&test_blockchain(),
|_, _, _| Ok(vec![vec![42]]),
header(2).hash(),
).unwrap().and_then(|p| Decode::decode(&mut &p[..])).unwrap();
proof_of_2.finalization_path.remove(1);
// justification is not valid
assert_eq!(do_check_finality_proof::(
|_| Ok(Vec::::new().encode()),
header(1),
(2, header(2).hash()),
1,
proof_of_2.encode(),
).is_err(), true);
}
#[test]
fn finality_proof_check_works() {
let proof_of_2 = prove_finality(&test_blockchain(), |_, _, _| Ok(vec![vec![42]]), header(2).hash())
.unwrap().unwrap();
assert_eq!(do_check_finality_proof::(
|_| Ok(vec![
(AuthorityId([1u8; 32]), 1u64),
(AuthorityId([2u8; 32]), 2u64),
(AuthorityId([3u8; 32]), 3u64),
].encode()),
header(1),
(2, header(2).hash()),
1,
proof_of_2,
).unwrap(), vec![header(2), header(3)]);
}
}