// 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 . use std::collections::{HashMap, HashSet}; use client::Client; use interfaces::{call_exectutor::CallExecutor, backend::Backend, error::Error as ClientError}; use codec::{Encode, Decode}; use grandpa::voter_set::VoterSet; use grandpa::{Error as GrandpaError}; use sr_primitives::generic::BlockId; use sr_primitives::traits::{NumberFor, Block as BlockT, Header as HeaderT}; use primitives::{H256, Blake2Hasher}; use fg_primitives::AuthorityId; use crate::{Commit, Error}; use crate::communication; /// A GRANDPA justification for block finality, it includes a commit message and /// an ancestry proof including all headers routing all precommit target blocks /// to the commit target block. Due to the current voting strategy the precommit /// targets should be the same as the commit target, since honest voters don't /// vote past authority set change blocks. /// /// This is meant to be stored in the db and passed around the network to other /// nodes, and are used by syncing nodes to prove authority set handoffs. #[derive(Encode, Decode)] pub struct GrandpaJustification { round: u64, pub(crate) commit: Commit, votes_ancestries: Vec, } impl> GrandpaJustification { /// Create a GRANDPA justification from the given commit. This method /// assumes the commit is valid and well-formed. pub(crate) fn from_commit( client: &Client, round: u64, commit: Commit, ) -> Result, Error> where B: Backend, E: CallExecutor + Send + Sync, RA: Send + Sync, { let mut votes_ancestries_hashes = HashSet::new(); let mut votes_ancestries = Vec::new(); let error = || { let msg = "invalid precommits for target commit".to_string(); Err(Error::Client(ClientError::BadJustification(msg))) }; for signed in commit.precommits.iter() { let mut current_hash = signed.precommit.target_hash.clone(); loop { if current_hash == commit.target_hash { break; } match client.header(&BlockId::Hash(current_hash))? { Some(current_header) => { if *current_header.number() <= commit.target_number { return error(); } let parent_hash = current_header.parent_hash().clone(); if votes_ancestries_hashes.insert(current_hash) { votes_ancestries.push(current_header); } current_hash = parent_hash; }, _ => return error(), } } } Ok(GrandpaJustification { round, commit, votes_ancestries }) } /// Decode a GRANDPA justification and validate the commit and the votes' /// ancestry proofs finalize the given block. pub(crate) fn decode_and_verify_finalizes( encoded: &[u8], finalized_target: (Block::Hash, NumberFor), set_id: u64, voters: &VoterSet, ) -> Result, ClientError> where NumberFor: grandpa::BlockNumberOps, { let justification = GrandpaJustification::::decode(&mut &*encoded) .map_err(|_| ClientError::JustificationDecode)?; if (justification.commit.target_hash, justification.commit.target_number) != finalized_target { let msg = "invalid commit target in grandpa justification".to_string(); Err(ClientError::BadJustification(msg)) } else { justification.verify(set_id, voters).map(|_| justification) } } /// Validate the commit and the votes' ancestry proofs. pub(crate) fn verify(&self, set_id: u64, voters: &VoterSet) -> Result<(), ClientError> where NumberFor: grandpa::BlockNumberOps, { use grandpa::Chain; let ancestry_chain = AncestryChain::::new(&self.votes_ancestries); match grandpa::validate_commit( &self.commit, voters, &ancestry_chain, ) { Ok(ref result) if result.ghost().is_some() => {}, _ => { let msg = "invalid commit in grandpa justification".to_string(); return Err(ClientError::BadJustification(msg)); } } let mut visited_hashes = HashSet::new(); for signed in self.commit.precommits.iter() { if let Err(_) = communication::check_message_sig::( &grandpa::Message::Precommit(signed.precommit.clone()), &signed.id, &signed.signature, self.round, set_id, ) { return Err(ClientError::BadJustification( "invalid signature for precommit in grandpa justification".to_string()).into()); } if self.commit.target_hash == signed.precommit.target_hash { continue; } match ancestry_chain.ancestry(self.commit.target_hash, signed.precommit.target_hash) { Ok(route) => { // ancestry starts from parent hash but the precommit target hash has been visited visited_hashes.insert(signed.precommit.target_hash); for hash in route { visited_hashes.insert(hash); } }, _ => { return Err(ClientError::BadJustification( "invalid precommit ancestry proof in grandpa justification".to_string()).into()); }, } } let ancestry_hashes = self.votes_ancestries .iter() .map(|h: &Block::Header| h.hash()) .collect(); if visited_hashes != ancestry_hashes { return Err(ClientError::BadJustification( "invalid precommit ancestries in grandpa justification with unused headers".to_string()).into()); } Ok(()) } } /// A utility trait implementing `grandpa::Chain` using a given set of headers. /// This is useful when validating commits, using the given set of headers to /// verify a valid ancestry route to the target commit block. struct AncestryChain { ancestry: HashMap, } impl AncestryChain { fn new(ancestry: &[Block::Header]) -> AncestryChain { let ancestry: HashMap<_, _> = ancestry .iter() .cloned() .map(|h: Block::Header| (h.hash(), h)) .collect(); AncestryChain { ancestry } } } impl grandpa::Chain> for AncestryChain where NumberFor: grandpa::BlockNumberOps { fn ancestry(&self, base: Block::Hash, block: Block::Hash) -> Result, GrandpaError> { let mut route = Vec::new(); let mut current_hash = block; loop { if current_hash == base { break; } match self.ancestry.get(¤t_hash) { Some(current_header) => { current_hash = *current_header.parent_hash(); route.push(current_hash); }, _ => return Err(GrandpaError::NotDescendent), } } route.pop(); // remove the base Ok(route) } fn best_chain_containing(&self, _block: Block::Hash) -> Option<(Block::Hash, NumberFor)> { None } }