Skip to content

Commit

Permalink
Add equivocation detector crate and implement clients (#2348) (#2353)
Browse files Browse the repository at this point in the history
* Split FinalitySyncPipeline and SourceClient

* Move some logic to finality_base

* Add empty equivocation detection clients

* Add equivocation reporting logic to the source client

* Use convenience trait for SubstrateFinalitySyncPipeline

* Define JustificationVerificationContext for GRANDPA

* Equivocation source client: finality_verification_context()

* Equivocation source client: synced_headers_finality_info()

* reuse HeaderFinalityInfo

* Define EquivocationsFinder

* Fix spellcheck

* Address review comments

* Avoid equivocations lookup errors
  • Loading branch information
serban300 authored and bkchr committed Apr 10, 2024
1 parent 9bfad80 commit 48cae06
Show file tree
Hide file tree
Showing 35 changed files with 930 additions and 374 deletions.
4 changes: 2 additions & 2 deletions bridges/bin/millau/runtime/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -884,7 +884,7 @@ impl_runtime_apis! {
}

fn synced_headers_grandpa_info(
) -> Vec<bp_header_chain::HeaderGrandpaInfo<bp_rialto::Header>> {
) -> Vec<bp_header_chain::StoredHeaderGrandpaInfo<bp_rialto::Header>> {
BridgeRialtoGrandpa::synced_headers_grandpa_info()
}
}
Expand All @@ -895,7 +895,7 @@ impl_runtime_apis! {
}

fn synced_headers_grandpa_info(
) -> Vec<bp_header_chain::HeaderGrandpaInfo<bp_westend::Header>> {
) -> Vec<bp_header_chain::StoredHeaderGrandpaInfo<bp_westend::Header>> {
BridgeWestendGrandpa::synced_headers_grandpa_info()
}
}
Expand Down
2 changes: 1 addition & 1 deletion bridges/bin/rialto-parachain/runtime/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -748,7 +748,7 @@ impl_runtime_apis! {
}

fn synced_headers_grandpa_info(
) -> Vec<bp_header_chain::HeaderGrandpaInfo<bp_millau::Header>> {
) -> Vec<bp_header_chain::StoredHeaderGrandpaInfo<bp_millau::Header>> {
BridgeMillauGrandpa::synced_headers_grandpa_info()
}
}
Expand Down
2 changes: 1 addition & 1 deletion bridges/bin/rialto/runtime/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -696,7 +696,7 @@ impl_runtime_apis! {
}

fn synced_headers_grandpa_info(
) -> Vec<bp_header_chain::HeaderGrandpaInfo<bp_millau::Header>> {
) -> Vec<bp_header_chain::StoredHeaderGrandpaInfo<bp_millau::Header>> {
BridgeMillauGrandpa::synced_headers_grandpa_info()
}
}
Expand Down
48 changes: 25 additions & 23 deletions bridges/modules/grandpa/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -40,10 +40,10 @@ pub use storage_types::StoredAuthoritySet;

use bp_header_chain::{
justification::GrandpaJustification, AuthoritySet, ChainWithGrandpa, GrandpaConsensusLogReader,
HeaderChain, HeaderGrandpaInfo, InitializationData, StoredHeaderData, StoredHeaderDataBuilder,
HeaderChain, InitializationData, StoredHeaderData, StoredHeaderDataBuilder,
StoredHeaderGrandpaInfo,
};
use bp_runtime::{BlockNumberOf, HashOf, HasherOf, HeaderId, HeaderOf, OwnedBridgeModule};
use finality_grandpa::voter_set::VoterSet;
use frame_support::{dispatch::PostDispatchInfo, ensure, DefaultNoBound};
use sp_runtime::{
traits::{Header as HeaderT, Zero},
Expand Down Expand Up @@ -241,9 +241,9 @@ pub mod pallet {
Self::deposit_event(Event::UpdatedBestFinalizedHeader {
number,
hash,
grandpa_info: HeaderGrandpaInfo {
justification,
authority_set: maybe_new_authority_set,
grandpa_info: StoredHeaderGrandpaInfo {
finality_proof: justification,
new_verification_context: maybe_new_authority_set,
},
});

Expand Down Expand Up @@ -411,7 +411,7 @@ pub mod pallet {
number: BridgedBlockNumber<T, I>,
hash: BridgedBlockHash<T, I>,
/// The Grandpa info associated to the new best finalized header.
grandpa_info: HeaderGrandpaInfo<BridgedHeader<T, I>>,
grandpa_info: StoredHeaderGrandpaInfo<BridgedHeader<T, I>>,
},
}

Expand Down Expand Up @@ -505,14 +505,9 @@ pub mod pallet {
) -> Result<(), sp_runtime::DispatchError> {
use bp_header_chain::justification::verify_justification;

let voter_set =
VoterSet::new(authority_set.authorities).ok_or(<Error<T, I>>::InvalidAuthoritySet)?;
let set_id = authority_set.set_id;

Ok(verify_justification::<BridgedHeader<T, I>>(
(hash, number),
set_id,
&voter_set,
&authority_set.try_into().map_err(|_| <Error<T, I>>::InvalidAuthoritySet)?,
justification,
)
.map_err(|e| {
Expand Down Expand Up @@ -617,7 +612,7 @@ where
<T as frame_system::Config>::RuntimeEvent: TryInto<Event<T, I>>,
{
/// Get the GRANDPA justifications accepted in the current block.
pub fn synced_headers_grandpa_info() -> Vec<HeaderGrandpaInfo<BridgedHeader<T, I>>> {
pub fn synced_headers_grandpa_info() -> Vec<StoredHeaderGrandpaInfo<BridgedHeader<T, I>>> {
frame_system::Pallet::<T>::read_events_no_consensus()
.filter_map(|event| {
if let Event::<T, I>::UpdatedBestFinalizedHeader { grandpa_info, .. } =
Expand Down Expand Up @@ -934,17 +929,20 @@ mod tests {
event: TestEvent::Grandpa(Event::UpdatedBestFinalizedHeader {
number: *header.number(),
hash: header.hash(),
grandpa_info: HeaderGrandpaInfo {
justification: justification.clone(),
authority_set: None,
grandpa_info: StoredHeaderGrandpaInfo {
finality_proof: justification.clone(),
new_verification_context: None,
},
}),
topics: vec![],
}],
);
assert_eq!(
Pallet::<TestRuntime>::synced_headers_grandpa_info(),
vec![HeaderGrandpaInfo { justification, authority_set: None }]
vec![StoredHeaderGrandpaInfo {
finality_proof: justification,
new_verification_context: None
}]
);
})
}
Expand Down Expand Up @@ -1075,19 +1073,23 @@ mod tests {
event: TestEvent::Grandpa(Event::UpdatedBestFinalizedHeader {
number: *header.number(),
hash: header.hash(),
grandpa_info: HeaderGrandpaInfo {
justification: justification.clone(),
authority_set: Some(<CurrentAuthoritySet<TestRuntime>>::get().into()),
grandpa_info: StoredHeaderGrandpaInfo {
finality_proof: justification.clone(),
new_verification_context: Some(
<CurrentAuthoritySet<TestRuntime>>::get().into()
),
},
}),
topics: vec![],
}],
);
assert_eq!(
Pallet::<TestRuntime>::synced_headers_grandpa_info(),
vec![HeaderGrandpaInfo {
justification,
authority_set: Some(<CurrentAuthoritySet<TestRuntime>>::get().into()),
vec![StoredHeaderGrandpaInfo {
finality_proof: justification,
new_verification_context: Some(
<CurrentAuthoritySet<TestRuntime>>::get().into()
),
}]
);
})
Expand Down
26 changes: 13 additions & 13 deletions bridges/modules/parachains/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -697,7 +697,7 @@ pub(crate) mod tests {
use bp_test_utils::prepare_parachain_heads_proof;
use codec::Encode;

use bp_header_chain::{justification::GrandpaJustification, HeaderGrandpaInfo};
use bp_header_chain::{justification::GrandpaJustification, StoredHeaderGrandpaInfo};
use bp_parachains::{
BestParaHeadHash, BridgeParachainCall, ImportedParaHeadsKeyProvider, ParasInfoKeyProvider,
};
Expand Down Expand Up @@ -1036,9 +1036,9 @@ pub(crate) mod tests {
pallet_bridge_grandpa::Event::UpdatedBestFinalizedHeader {
number: 1,
hash: relay_1_hash,
grandpa_info: HeaderGrandpaInfo {
justification,
authority_set: None,
grandpa_info: StoredHeaderGrandpaInfo {
finality_proof: justification,
new_verification_context: None,
},
}
),
Expand Down Expand Up @@ -1177,9 +1177,9 @@ pub(crate) mod tests {
pallet_bridge_grandpa::Event::UpdatedBestFinalizedHeader {
number: 1,
hash: relay_1_hash,
grandpa_info: HeaderGrandpaInfo {
justification,
authority_set: None,
grandpa_info: StoredHeaderGrandpaInfo {
finality_proof: justification,
new_verification_context: None,
}
}
),
Expand Down Expand Up @@ -1230,9 +1230,9 @@ pub(crate) mod tests {
pallet_bridge_grandpa::Event::UpdatedBestFinalizedHeader {
number: 1,
hash: relay_1_hash,
grandpa_info: HeaderGrandpaInfo {
justification: justification.clone(),
authority_set: None,
grandpa_info: StoredHeaderGrandpaInfo {
finality_proof: justification.clone(),
new_verification_context: None,
}
}
),
Expand Down Expand Up @@ -1271,9 +1271,9 @@ pub(crate) mod tests {
pallet_bridge_grandpa::Event::UpdatedBestFinalizedHeader {
number: 1,
hash: relay_1_hash,
grandpa_info: HeaderGrandpaInfo {
justification,
authority_set: None,
grandpa_info: StoredHeaderGrandpaInfo {
finality_proof: justification,
new_verification_context: None,
}
}
),
Expand Down
5 changes: 3 additions & 2 deletions bridges/primitives/header-chain/src/justification/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -23,10 +23,11 @@ mod verification;

use crate::ChainWithGrandpa;
pub use verification::{
equivocation::{EquivocationsCollector, Error as EquivocationsCollectorError},
equivocation::{EquivocationsCollector, GrandpaEquivocationsFinder},
optimizer::verify_and_optimize_justification,
strict::verify_justification,
AncestryChain, Error as JustificationVerificationError, PrecommitError,
AncestryChain, Error as JustificationVerificationError, JustificationVerificationContext,
PrecommitError,
};

use bp_runtime::{BlockNumberOf, Chain, HashOf, HeaderId};
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,33 +16,26 @@

//! Logic for extracting equivocations from multiple GRANDPA Finality Proofs.

use crate::justification::{
verification::{
Error as JustificationVerificationError, JustificationVerifier, PrecommitError,
SignedPrecommit,
use crate::{
justification::{
verification::{
Error as JustificationVerificationError, IterationFlow,
JustificationVerificationContext, JustificationVerifier, PrecommitError,
SignedPrecommit,
},
GrandpaJustification,
},
GrandpaJustification,
ChainWithGrandpa, FindEquivocations,
};

use crate::justification::verification::IterationFlow;
use finality_grandpa::voter_set::VoterSet;
use frame_support::RuntimeDebug;
use sp_consensus_grandpa::{AuthorityId, AuthoritySignature, EquivocationProof, Precommit, SetId};
use bp_runtime::{BlockNumberOf, HashOf, HeaderOf};
use sp_consensus_grandpa::{AuthorityId, AuthoritySignature, EquivocationProof, Precommit};
use sp_runtime::traits::Header as HeaderT;
use sp_std::{
collections::{btree_map::BTreeMap, btree_set::BTreeSet},
prelude::*,
};

/// Justification verification error.
#[derive(Eq, RuntimeDebug, PartialEq)]
pub enum Error {
/// Justification is targeting unexpected round.
InvalidRound,
/// Justification verification error.
JustificationVerification(JustificationVerificationError),
}

enum AuthorityVotes<Header: HeaderT> {
SingleVote(SignedPrecommit<Header>),
Equivocation(
Expand All @@ -53,47 +46,42 @@ enum AuthorityVotes<Header: HeaderT> {
/// Structure that can extract equivocations from multiple GRANDPA justifications.
pub struct EquivocationsCollector<'a, Header: HeaderT> {
round: u64,
authorities_set_id: SetId,
authorities_set: &'a VoterSet<AuthorityId>,
context: &'a JustificationVerificationContext,

votes: BTreeMap<AuthorityId, AuthorityVotes<Header>>,
}

impl<'a, Header: HeaderT> EquivocationsCollector<'a, Header> {
/// Create a new instance of `EquivocationsCollector`.
pub fn new(
authorities_set_id: SetId,
authorities_set: &'a VoterSet<AuthorityId>,
context: &'a JustificationVerificationContext,
base_justification: &GrandpaJustification<Header>,
) -> Result<Self, Error> {
let mut checker = Self {
round: base_justification.round,
authorities_set_id,
authorities_set,
votes: BTreeMap::new(),
};

checker.parse_justification(base_justification)?;
) -> Result<Self, JustificationVerificationError> {
let mut checker = Self { round: base_justification.round, context, votes: BTreeMap::new() };

checker.verify_justification(
(base_justification.commit.target_hash, base_justification.commit.target_number),
checker.context,
base_justification,
)?;

Ok(checker)
}

/// Parse an additional justification for equivocations.
pub fn parse_justification(
&mut self,
justification: &GrandpaJustification<Header>,
) -> Result<(), Error> {
// The justification should target the same round as the base justification.
if self.round != justification.round {
return Err(Error::InvalidRound)
/// Parse additional justifications for equivocations.
pub fn parse_justifications(&mut self, justifications: &[GrandpaJustification<Header>]) {
let round = self.round;
for justification in
justifications.iter().filter(|justification| round == justification.round)
{
// We ignore the Errors received here since we don't care if the proofs are valid.
// We only care about collecting equivocations.
let _ = self.verify_justification(
(justification.commit.target_hash, justification.commit.target_number),
self.context,
justification,
);
}

self.verify_justification(
(justification.commit.target_hash, justification.commit.target_number),
self.authorities_set_id,
self.authorities_set,
justification,
)
.map_err(Error::JustificationVerification)
}

/// Extract the equivocation proofs that have been collected.
Expand All @@ -102,7 +90,7 @@ impl<'a, Header: HeaderT> EquivocationsCollector<'a, Header> {
for (_authority, vote) in self.votes {
if let AuthorityVotes::Equivocation(equivocation) = vote {
equivocations.push(EquivocationProof::new(
self.authorities_set_id,
self.context.authority_set_id,
sp_consensus_grandpa::Equivocation::Precommit(equivocation),
));
}
Expand Down Expand Up @@ -177,3 +165,29 @@ impl<'a, Header: HeaderT> JustificationVerifier<Header> for EquivocationsCollect
Ok(())
}
}

/// Helper struct for finding equivocations in GRANDPA proofs.
pub struct GrandpaEquivocationsFinder<C>(sp_std::marker::PhantomData<C>);

impl<C: ChainWithGrandpa>
FindEquivocations<
GrandpaJustification<HeaderOf<C>>,
JustificationVerificationContext,
EquivocationProof<HashOf<C>, BlockNumberOf<C>>,
> for GrandpaEquivocationsFinder<C>
{
type Error = JustificationVerificationError;

fn find_equivocations(
verification_context: &JustificationVerificationContext,
synced_proof: &GrandpaJustification<HeaderOf<C>>,
source_proofs: &[GrandpaJustification<HeaderOf<C>>],
) -> Result<Vec<EquivocationProof<HashOf<C>, BlockNumberOf<C>>>, Self::Error> {
let mut equivocations_collector =
EquivocationsCollector::new(verification_context, synced_proof)?;

equivocations_collector.parse_justifications(source_proofs);

Ok(equivocations_collector.into_equivocation_proofs())
}
}
Loading

0 comments on commit 48cae06

Please sign in to comment.