use codec::{Decode, Encode};
use finality_grandpa::{voter_set::VoterSet, Error as GrandpaError};
use sp_finality_grandpa::{AuthorityId, AuthoritySignature, SetId};
use sp_runtime::{
traits::{Block as BlockT, Header as HeaderT, NumberFor},
RuntimeDebug,
};
use sp_std::{
collections::{btree_map::BTreeMap, btree_set::BTreeSet},
prelude::*,
};
type Commit<Block> = finality_grandpa::Commit<
<Block as BlockT>::Hash,
NumberFor<Block>,
AuthoritySignature,
AuthorityId,
>;
#[derive(Encode, Decode, RuntimeDebug)]
pub struct GrandpaJustification<Block: BlockT> {
round: u64,
pub commit: Commit<Block>,
votes_ancestries: Vec<Block::Header>,
}
impl<Block: BlockT> GrandpaJustification<Block> {
pub fn verify(&self, set_id: SetId, voters: &VoterSet<AuthorityId>) -> Result<(), Error>
where
NumberFor<Block>: finality_grandpa::BlockNumberOps,
{
use finality_grandpa::Chain;
let ancestry_chain = AncestryChain::<Block>::new(&self.votes_ancestries);
match finality_grandpa::validate_commit(&self.commit, voters, &ancestry_chain) {
Ok(ref result) if result.ghost().is_some() => {}
_ => {
return Err(Error::BadJustification);
}
}
let mut buf = Vec::new();
let mut visited_hashes = BTreeSet::new();
for signed in self.commit.precommits.iter() {
if !sp_finality_grandpa::check_message_signature_with_buffer(
&finality_grandpa::Message::Precommit(signed.precommit.clone()),
&signed.id,
&signed.signature,
self.round,
set_id,
&mut buf,
) {
return Err(Error::BadJustification.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) => {
visited_hashes.insert(signed.precommit.target_hash);
for hash in route {
visited_hashes.insert(hash);
}
}
_ => {
return Err(Error::BadJustification.into());
}
}
}
let ancestry_hashes = self
.votes_ancestries
.iter()
.map(|h: &Block::Header| h.hash())
.collect();
if visited_hashes != ancestry_hashes {
return Err(Error::BadJustification.into());
}
Ok(())
}
}
#[derive(RuntimeDebug)]
pub enum Error {
InvalidAuthoritiesSet,
VersionInvalid,
GenesisInvalid,
JustificationDecode,
BadJustification,
InvalidStateRoot,
}
struct AncestryChain<Block: BlockT> {
ancestry: BTreeMap<Block::Hash, Block::Header>,
}
impl<Block: BlockT> AncestryChain<Block> {
fn new(ancestry: &[Block::Header]) -> AncestryChain<Block> {
let ancestry: BTreeMap<_, _> = ancestry
.iter()
.cloned()
.map(|h: Block::Header| (h.hash(), h))
.collect();
AncestryChain { ancestry }
}
}
impl<Block: BlockT> finality_grandpa::Chain<Block::Hash, NumberFor<Block>> for AncestryChain<Block>
where
NumberFor<Block>: finality_grandpa::BlockNumberOps,
{
fn ancestry(
&self,
base: Block::Hash,
block: Block::Hash,
) -> Result<Vec<Block::Hash>, 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();
Ok(route)
}
fn best_chain_containing(
&self,
_block: Block::Hash,
) -> Option<(Block::Hash, NumberFor<Block>)> {
None
}
}