This repository has been archived by the owner on Feb 21, 2024. It is now read-only.
forked from paritytech/substrate
-
Notifications
You must be signed in to change notification settings - Fork 7
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request paritytech#296 from subspace/feat/finality_verifier
- Loading branch information
Showing
8 changed files
with
864 additions
and
0 deletions.
There are no files selected for viewing
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,50 @@ | ||
[package] | ||
name = "pallet-grandpa-finality-verifier" | ||
version = "1.0.0" | ||
authors = ["Vedhavyas Singareddi <ved@subspace.network>"] | ||
edition = "2021" | ||
license = "Apache-2.0" | ||
homepage = "https://subspace.network" | ||
repository = "https://github.com/subspace/subspace" | ||
description = "Pallet to verify GRANDPA finality proofs for Substrate based chains" | ||
readme = "README.md" | ||
|
||
[dependencies] | ||
codec = { package = "parity-scale-codec", version = "3.0.0", default-features = false } | ||
finality-grandpa = { version = "0.15.0", default-features = false } | ||
log = { version = "0.4.14", default-features = false } | ||
num-traits = { version = "0.2", default-features = false } | ||
scale-info = { version = "2.0.1", default-features = false, features = ["derive"] } | ||
serde = { version = "1.0", optional = true } | ||
|
||
# Substrate Dependencies | ||
|
||
frame-support = { git = "https://github.com/paritytech/substrate", rev="c364008a6c7da8456e17967f55edf51e45146998", default-features = false } | ||
frame-system = { git = "https://github.com/paritytech/substrate", rev="c364008a6c7da8456e17967f55edf51e45146998", default-features = false } | ||
sp-finality-grandpa = { git = "https://github.com/paritytech/substrate", rev="c364008a6c7da8456e17967f55edf51e45146998", default-features = false } | ||
sp-runtime = { git = "https://github.com/paritytech/substrate", rev="c364008a6c7da8456e17967f55edf51e45146998", default-features = false } | ||
sp-std = { git = "https://github.com/paritytech/substrate", rev="c364008a6c7da8456e17967f55edf51e45146998", default-features = false } | ||
sp-trie = { git = "https://github.com/paritytech/substrate", rev="c364008a6c7da8456e17967f55edf51e45146998", default-features = false } | ||
|
||
[dev-dependencies] | ||
sp-core = { git = "https://github.com/paritytech/substrate", rev="c364008a6c7da8456e17967f55edf51e45146998" } | ||
sp-io = { git = "https://github.com/paritytech/substrate", rev="c364008a6c7da8456e17967f55edf51e45146998" } | ||
sp-application-crypto = { git = "https://github.com/paritytech/substrate", rev="c364008a6c7da8456e17967f55edf51e45146998" } | ||
ed25519-dalek = { version = "1.0", default-features = false, features = ["u64_backend"] } | ||
|
||
[features] | ||
default = ["std"] | ||
std = [ | ||
"codec/std", | ||
"finality-grandpa/std", | ||
"frame-support/std", | ||
"frame-system/std", | ||
"log/std", | ||
"num-traits/std", | ||
"scale-info/std", | ||
"serde", | ||
"sp-finality-grandpa/std", | ||
"sp-runtime/std", | ||
"sp-std/std", | ||
"sp-trie/std", | ||
] |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
# pallet-finality-verifier | ||
|
||
This is a fork of grandpa bridge for finality verification with support for verifying multiple substrate based chains. | ||
|
||
License: Apache-2.0 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,285 @@ | ||
// Copyright (C) 2022 Subspace Labs, Inc. | ||
// SPDX-License-Identifier: Apache-2.0 | ||
|
||
// Licensed under the Apache License, Version 2.0 (the "License"); | ||
// you may not use this file except in compliance with the License. | ||
// You may obtain a copy of the License at | ||
// | ||
// http://www.apache.org/licenses/LICENSE-2.0 | ||
// | ||
// Unless required by applicable law or agreed to in writing, software | ||
// distributed under the License is distributed on an "AS IS" BASIS, | ||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
// See the License for the specific language governing permissions and | ||
// limitations under the License. | ||
|
||
// GRANDPA verification is mostly taken from Parity's bridges https://github.com/paritytech/parity-bridges-common/tree/master/primitives/header-chain | ||
use codec::{Decode, Encode}; | ||
use finality_grandpa::voter_set::VoterSet; | ||
use frame_support::Parameter; | ||
use frame_support::RuntimeDebug; | ||
use num_traits::AsPrimitive; | ||
use scale_info::TypeInfo; | ||
#[cfg(feature = "std")] | ||
use serde::{Deserialize, Serialize}; | ||
use sp_finality_grandpa::{AuthorityId, AuthorityList, AuthoritySignature, SetId}; | ||
use sp_runtime::traits::{ | ||
AtLeast32BitUnsigned, Hash as HashT, Header as HeaderT, MaybeDisplay, MaybeMallocSizeOf, | ||
MaybeSerializeDeserialize, Member, Saturating, SimpleBitOps, Verify, | ||
}; | ||
use sp_std::collections::{btree_map::BTreeMap, btree_set::BTreeSet}; | ||
use sp_std::prelude::*; | ||
use sp_std::{hash::Hash, str::FromStr, vec::Vec}; | ||
|
||
/// Minimal Substrate-based chain representation that may be used from no_std environment. | ||
pub trait Chain: Send + Sync + 'static { | ||
/// A type that fulfills the abstract idea of what a Substrate block number is. | ||
// Constraits come from the associated Number type of `sp_runtime::traits::Header` | ||
// See here for more info: | ||
// https://crates.parity.io/sp_runtime/traits/trait.Header.html#associatedtype.Number | ||
// | ||
// Note that the `AsPrimitive<usize>` trait is required by the GRANDPA justification | ||
// verifier, and is not usually part of a Substrate Header's Number type. | ||
type BlockNumber: Parameter | ||
+ Member | ||
+ MaybeSerializeDeserialize | ||
+ Hash | ||
+ Copy | ||
+ Default | ||
+ MaybeDisplay | ||
+ AtLeast32BitUnsigned | ||
+ FromStr | ||
+ MaybeMallocSizeOf | ||
+ AsPrimitive<usize> | ||
+ Default | ||
+ Saturating | ||
// original `sp_runtime::traits::Header::BlockNumber` doesn't have this trait, but | ||
// `sp_runtime::generic::Era` requires block number -> `u64` conversion. | ||
+ Into<u64>; | ||
|
||
/// A type that fulfills the abstract idea of what a Substrate hash is. | ||
// Constraits come from the associated Hash type of `sp_runtime::traits::Header` | ||
// See here for more info: | ||
// https://crates.parity.io/sp_runtime/traits/trait.Header.html#associatedtype.Hash | ||
type Hash: Parameter | ||
+ Member | ||
+ MaybeSerializeDeserialize | ||
+ Hash | ||
+ Ord | ||
+ Copy | ||
+ MaybeDisplay | ||
+ Default | ||
+ SimpleBitOps | ||
+ AsRef<[u8]> | ||
+ AsMut<[u8]> | ||
+ MaybeMallocSizeOf; | ||
|
||
/// A type that fulfills the abstract idea of what a Substrate hasher (a type | ||
/// that produces hashes) is. | ||
// Constraits come from the associated Hashing type of `sp_runtime::traits::Header` | ||
// See here for more info: | ||
// https://crates.parity.io/sp_runtime/traits/trait.Header.html#associatedtype.Hashing | ||
type Hasher: HashT<Output = Self::Hash>; | ||
|
||
/// A type that fulfills the abstract idea of what a Substrate header is. | ||
// See here for more info: | ||
// https://crates.parity.io/sp_runtime/traits/trait.Header.html | ||
type Header: Parameter | ||
+ HeaderT<Number = Self::BlockNumber, Hash = Self::Hash> | ||
+ MaybeSerializeDeserialize; | ||
|
||
/// Signature type, used on this chain. | ||
type Signature: Parameter + Verify; | ||
} | ||
|
||
/// A GRANDPA Justification is a proof that a given header was finalized | ||
/// at a certain height and with a certain set of authorities. | ||
/// | ||
/// This particular proof is used to prove that headers on a bridged chain | ||
/// (so not our chain) have been finalized correctly. | ||
#[derive(Encode, Decode, RuntimeDebug, Clone, PartialEq, Eq, TypeInfo)] | ||
pub struct GrandpaJustification<Header: HeaderT> { | ||
/// The round (voting period) this justification is valid for. | ||
pub round: u64, | ||
/// The set of votes for the chain which is to be finalized. | ||
pub commit: | ||
finality_grandpa::Commit<Header::Hash, Header::Number, AuthoritySignature, AuthorityId>, | ||
/// A proof that the chain of blocks in the commit are related to each other. | ||
pub votes_ancestries: Vec<Header>, | ||
} | ||
|
||
/// A GRANDPA Authority List and ID. | ||
#[derive(Default, Encode, Decode, RuntimeDebug, PartialEq, Clone, TypeInfo)] | ||
#[cfg_attr(feature = "std", derive(Serialize, Deserialize))] | ||
pub struct AuthoritySet { | ||
/// List of GRANDPA authorities for the current round. | ||
pub authorities: AuthorityList, | ||
/// Monotonic identifier of the current GRANDPA authority set. | ||
pub set_id: SetId, | ||
} | ||
|
||
/// Votes ancestries with useful methods. | ||
#[derive(RuntimeDebug)] | ||
struct AncestryChain<Header: HeaderT> { | ||
/// Header hash => parent header hash mapping. | ||
parents: BTreeMap<Header::Hash, Header::Hash>, | ||
/// Hashes of headers that were not visited by `is_ancestor` method. | ||
unvisited: BTreeSet<Header::Hash>, | ||
} | ||
|
||
impl<Header: HeaderT> AncestryChain<Header> { | ||
/// Create new ancestry chain. | ||
fn new(ancestry: &[Header]) -> AncestryChain<Header> { | ||
let mut parents = BTreeMap::new(); | ||
let mut unvisited = BTreeSet::new(); | ||
for ancestor in ancestry { | ||
let hash = ancestor.hash(); | ||
let parent_hash = *ancestor.parent_hash(); | ||
parents.insert(hash, parent_hash); | ||
unvisited.insert(hash); | ||
} | ||
AncestryChain { parents, unvisited } | ||
} | ||
|
||
/// Returns `Ok(_)` if `precommit_target` is a descendant of the `commit_target` block and | ||
/// `Err(_)` otherwise. | ||
fn ensure_descendant( | ||
mut self, | ||
commit_target: &Header::Hash, | ||
precommit_target: &Header::Hash, | ||
) -> Result<Self, Error> { | ||
let mut current_hash = *precommit_target; | ||
while current_hash != *commit_target { | ||
let is_visited_before = !self.unvisited.remove(¤t_hash); | ||
current_hash = match self.parents.get(¤t_hash) { | ||
Some(parent_hash) => { | ||
if is_visited_before { | ||
// `Some(parent_hash)` means that the `current_hash` is in the `parents` | ||
// container `is_visited_before` means that it has been visited before in | ||
// some of previous calls => since we assume that previous call has finished | ||
// with `true`, this also will be finished with `true` | ||
return Ok(self); | ||
} | ||
|
||
*parent_hash | ||
} | ||
None => return Err(Error::PrecommitIsNotCommitDescendant), | ||
}; | ||
} | ||
|
||
Ok(self) | ||
} | ||
} | ||
|
||
/// Justification verification error. | ||
#[derive(RuntimeDebug, PartialEq)] | ||
pub enum Error { | ||
/// Justification is finalizing unexpected header. | ||
InvalidJustificationTarget, | ||
/// The authority has provided an invalid signature. | ||
InvalidAuthoritySignature, | ||
/// The justification contains precommit for header that is not a descendant of the commit | ||
/// header. | ||
PrecommitIsNotCommitDescendant, | ||
/// The cumulative weight of all votes in the justification is not enough to justify commit | ||
/// header finalization. | ||
TooLowCumulativeWeight, | ||
/// The justification contains extra (unused) headers in its `votes_ancestries` field. | ||
ExtraHeadersInVotesAncestries, | ||
} | ||
|
||
/// Verify that justification, that is generated by given authority set, finalizes given header. | ||
pub fn verify_justification<Header: HeaderT>( | ||
finalized_target: (Header::Hash, Header::Number), | ||
authorities_set_id: SetId, | ||
authorities_set: &VoterSet<AuthorityId>, | ||
justification: &GrandpaJustification<Header>, | ||
) -> Result<(), Error> | ||
where | ||
Header::Number: finality_grandpa::BlockNumberOps, | ||
{ | ||
// ensure that it is justification for the expected header | ||
if ( | ||
justification.commit.target_hash, | ||
justification.commit.target_number, | ||
) != finalized_target | ||
{ | ||
return Err(Error::InvalidJustificationTarget); | ||
} | ||
|
||
let mut chain = AncestryChain::new(&justification.votes_ancestries); | ||
let mut signature_buffer = Vec::new(); | ||
let mut votes = BTreeSet::new(); | ||
let mut cumulative_weight = 0u64; | ||
for signed in &justification.commit.precommits { | ||
// authority must be in the set | ||
let authority_info = match authorities_set.get(&signed.id) { | ||
Some(authority_info) => authority_info, | ||
None => { | ||
// just ignore precommit from unknown authority as | ||
// `finality_grandpa::import_precommit` does | ||
continue; | ||
} | ||
}; | ||
|
||
// check if authority has already voted in the same round. | ||
// | ||
// there's a lot of code in `validate_commit` and `import_precommit` functions inside | ||
// `finality-grandpa` crate (mostly related to reporting equivocations). But the only thing | ||
// that we care about is that only first vote from the authority is accepted | ||
if !votes.insert(signed.id.clone()) { | ||
continue; | ||
} | ||
|
||
// everything below this line can't just `continue`, because state is already altered | ||
|
||
// precommits aren't allowed for block lower than the target | ||
if signed.precommit.target_number < justification.commit.target_number { | ||
return Err(Error::PrecommitIsNotCommitDescendant); | ||
} | ||
// all precommits must be descendants of target block | ||
chain = chain.ensure_descendant( | ||
&justification.commit.target_hash, | ||
&signed.precommit.target_hash, | ||
)?; | ||
// since we know now that the precommit target is the descendant of the justification | ||
// target, we may increase 'weight' of the justification target | ||
// | ||
// there's a lot of code in the `VoteGraph::insert` method inside `finality-grandpa` crate, | ||
// but in the end it is only used to find GHOST, which we don't care about. The only thing | ||
// that we care about is that the justification target has enough weight | ||
cumulative_weight = cumulative_weight.checked_add(authority_info.weight().0.into()).expect( | ||
"sum of weights of ALL authorities is expected not to overflow - this is guaranteed by\ | ||
existence of VoterSet;\ | ||
the order of loop conditions guarantees that we can account vote from same authority\ | ||
multiple times;\ | ||
thus we'll never overflow the u64::MAX;\ | ||
qed", | ||
); | ||
// verify authority signature | ||
if !sp_finality_grandpa::check_message_signature_with_buffer( | ||
&finality_grandpa::Message::Precommit(signed.precommit.clone()), | ||
&signed.id, | ||
&signed.signature, | ||
justification.round, | ||
authorities_set_id, | ||
&mut signature_buffer, | ||
) { | ||
return Err(Error::InvalidAuthoritySignature); | ||
} | ||
} | ||
|
||
// check that there are no extra headers in the justification | ||
if !chain.unvisited.is_empty() { | ||
return Err(Error::ExtraHeadersInVotesAncestries); | ||
} | ||
|
||
// check that the cumulative weight of validators voted for the justification target (or one | ||
// of its descendents) is larger than required threshold. | ||
let threshold = authorities_set.threshold().0.into(); | ||
if cumulative_weight >= threshold { | ||
Ok(()) | ||
} else { | ||
Err(Error::TooLowCumulativeWeight) | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,22 @@ | ||
// Copyright (C) 2022 Subspace Labs, Inc. | ||
// SPDX-License-Identifier: Apache-2.0 | ||
|
||
// Licensed under the Apache License, Version 2.0 (the "License"); | ||
// you may not use this file except in compliance with the License. | ||
// You may obtain a copy of the License at | ||
// | ||
// http://www.apache.org/licenses/LICENSE-2.0 | ||
// | ||
// Unless required by applicable law or agreed to in writing, software | ||
// distributed under the License is distributed on an "AS IS" BASIS, | ||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
// See the License for the specific language governing permissions and | ||
// limitations under the License. | ||
|
||
#![cfg_attr(not(feature = "std"), no_std)] | ||
|
||
mod grandpa; | ||
pub use grandpa::verify_justification; | ||
|
||
#[cfg(test)] | ||
mod tests; |
Oops, something went wrong.