// 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)]); } }