// 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
}
}