Bridges subtree sync (#2991)

* Squashed 'bridges/' changes from 0417308a48..278119fec2

278119fec2 Grandpa: Store the authority set changes (#2336) (#2337)
19f9c8ffdb remove message sender origin (#2322)
3c7c271d2e GRANDPA module: store accepted justifications (#2298) (#2301)
fb7d12e793 GRANDPA justifications: equivocation detection primitives (#2295) (#2297)
d03a2ed450 More backports from Cumulus subtree to polkadot-staging (#2283)
3c4ada921b Update dependecies (#2277) (#2281)
3e195c9e76 GRANDPA: optimize votes_ancestries when needed (#2262) (#2264)
7065bbabc6 Implement RuntimeDebug for GrandpaJustification (#2254)
8c9e59bcbc Define generate_grandpa_key_ownership_proof() (#2247) (#2248)
0b46956df7 Deduplicate Grandpa consensus log reading logic (#2245) (#2246)
96c9701710 Fix deps from Cumulus (#2244)

git-subtree-dir: bridges
git-subtree-split: 278119fec2b45990cf1271999b0c21befe7003d9

* Subtree update

* Squashed 'bridges/' changes from 278119fec2..edf33a2c85

edf33a2c85 Backport fix (for wasm `std` env) (#2339)

git-subtree-dir: bridges
git-subtree-split: edf33a2c85399d366e008228f2d9e63e8a492d95
This commit is contained in:
Branislav Kontur
2023-08-10 11:24:53 +02:00
committed by GitHub
parent 0765e2d152
commit e077bdd99f
39 changed files with 1375 additions and 791 deletions
+3
View File
@@ -1257,6 +1257,7 @@ dependencies = [
"bp-runtime", "bp-runtime",
"frame-support", "frame-support",
"sp-api", "sp-api",
"sp-std",
] ]
[[package]] [[package]]
@@ -1310,6 +1311,7 @@ dependencies = [
"bp-runtime", "bp-runtime",
"frame-support", "frame-support",
"sp-api", "sp-api",
"sp-std",
] ]
[[package]] [[package]]
@@ -1472,6 +1474,7 @@ version = "0.1.0"
dependencies = [ dependencies = [
"bp-bridge-hub-rococo", "bp-bridge-hub-rococo",
"bp-bridge-hub-wococo", "bp-bridge-hub-wococo",
"bp-header-chain",
"bp-messages", "bp-messages",
"bp-parachains", "bp-parachains",
"bp-polkadot-core", "bp-polkadot-core",
@@ -72,8 +72,6 @@ pub type HasherOf<C> = bp_runtime::HasherOf<UnderlyingChainOf<C>>;
pub type AccountIdOf<C> = bp_runtime::AccountIdOf<UnderlyingChainOf<C>>; pub type AccountIdOf<C> = bp_runtime::AccountIdOf<UnderlyingChainOf<C>>;
/// Type of balances that is used on the chain. /// Type of balances that is used on the chain.
pub type BalanceOf<C> = bp_runtime::BalanceOf<UnderlyingChainOf<C>>; pub type BalanceOf<C> = bp_runtime::BalanceOf<UnderlyingChainOf<C>>;
/// Type of origin that is used on the chain.
pub type OriginOf<C> = <C as ThisChainWithMessages>::RuntimeOrigin;
/// Sub-module that is declaring types required for processing This -> Bridged chain messages. /// Sub-module that is declaring types required for processing This -> Bridged chain messages.
pub mod source { pub mod source {
@@ -138,17 +136,11 @@ pub mod source {
#[derive(RuntimeDebug)] #[derive(RuntimeDebug)]
pub struct FromThisChainMessageVerifier<B>(PhantomData<B>); pub struct FromThisChainMessageVerifier<B>(PhantomData<B>);
impl<B> LaneMessageVerifier<OriginOf<ThisChain<B>>, FromThisChainMessagePayload> impl<B> LaneMessageVerifier<FromThisChainMessagePayload> for FromThisChainMessageVerifier<B>
for FromThisChainMessageVerifier<B>
where where
B: MessageBridge, B: MessageBridge,
// matches requirements from the `frame_system::Config::Origin`
OriginOf<ThisChain<B>>: Clone
+ Into<Result<frame_system::RawOrigin<AccountIdOf<ThisChain<B>>>, OriginOf<ThisChain<B>>>>,
AccountIdOf<ThisChain<B>>: PartialEq + Clone,
{ {
fn verify_message( fn verify_message(
_submitter: &OriginOf<ThisChain<B>>,
_lane: &LaneId, _lane: &LaneId,
_lane_outbound_data: &OutboundLaneData, _lane_outbound_data: &OutboundLaneData,
_payload: &FromThisChainMessagePayload, _payload: &FromThisChainMessagePayload,
@@ -110,12 +110,7 @@ impl<BlobDispatcher: DispatchBlob, Weights: MessagesPalletWeights> MessageDispat
/// one side, where on the other it can be dispatched by [`XcmBlobMessageDispatch`]. /// one side, where on the other it can be dispatched by [`XcmBlobMessageDispatch`].
pub trait XcmBlobHauler { pub trait XcmBlobHauler {
/// Runtime message sender adapter. /// Runtime message sender adapter.
type MessageSender: MessagesBridge<Self::MessageSenderOrigin, XcmAsPlainPayload>; type MessageSender: MessagesBridge<XcmAsPlainPayload>;
/// Runtime message sender origin, which is used by [`Self::MessageSender`].
type MessageSenderOrigin;
/// Our location within the Consensus Universe.
fn message_sender_origin() -> Self::MessageSenderOrigin;
/// Return message lane (as "point-to-point link") used to deliver XCM messages. /// Return message lane (as "point-to-point link") used to deliver XCM messages.
fn xcm_lane() -> LaneId; fn xcm_lane() -> LaneId;
@@ -124,12 +119,10 @@ pub trait XcmBlobHauler {
/// XCM bridge adapter which connects [`XcmBlobHauler`] with [`XcmBlobHauler::MessageSender`] and /// XCM bridge adapter which connects [`XcmBlobHauler`] with [`XcmBlobHauler::MessageSender`] and
/// makes sure that XCM blob is sent to the [`pallet_bridge_messages`] queue to be relayed. /// makes sure that XCM blob is sent to the [`pallet_bridge_messages`] queue to be relayed.
pub struct XcmBlobHaulerAdapter<XcmBlobHauler>(sp_std::marker::PhantomData<XcmBlobHauler>); pub struct XcmBlobHaulerAdapter<XcmBlobHauler>(sp_std::marker::PhantomData<XcmBlobHauler>);
impl<HaulerOrigin, H: XcmBlobHauler<MessageSenderOrigin = HaulerOrigin>> HaulBlob impl<H: XcmBlobHauler> HaulBlob for XcmBlobHaulerAdapter<H> {
for XcmBlobHaulerAdapter<H>
{
fn haul_blob(blob: sp_std::prelude::Vec<u8>) -> Result<(), HaulBlobError> { fn haul_blob(blob: sp_std::prelude::Vec<u8>) -> Result<(), HaulBlobError> {
let lane = H::xcm_lane(); let lane = H::xcm_lane();
H::MessageSender::send_message(H::message_sender_origin(), lane, blob) H::MessageSender::send_message(lane, blob)
.map(|artifacts| (lane, artifacts.nonce).using_encoded(sp_io::hashing::blake2_256)) .map(|artifacts| (lane, artifacts.nonce).using_encoded(sp_io::hashing::blake2_256))
.map(|result| { .map(|result| {
log::info!( log::info!(
+70 -17
View File
@@ -39,8 +39,8 @@
pub use storage_types::StoredAuthoritySet; pub use storage_types::StoredAuthoritySet;
use bp_header_chain::{ use bp_header_chain::{
justification::GrandpaJustification, ChainWithGrandpa, GrandpaConsensusLogReader, HeaderChain, justification::GrandpaJustification, AuthoritySet, ChainWithGrandpa, GrandpaConsensusLogReader,
InitializationData, StoredHeaderData, StoredHeaderDataBuilder, HeaderChain, HeaderGrandpaInfo, InitializationData, StoredHeaderData, StoredHeaderDataBuilder,
}; };
use bp_runtime::{BlockNumberOf, HashOf, HasherOf, HeaderId, HeaderOf, OwnedBridgeModule}; use bp_runtime::{BlockNumberOf, HashOf, HasherOf, HeaderId, HeaderOf, OwnedBridgeModule};
use finality_grandpa::voter_set::VoterSet; use finality_grandpa::voter_set::VoterSet;
@@ -49,7 +49,7 @@ use sp_runtime::{
traits::{Header as HeaderT, Zero}, traits::{Header as HeaderT, Zero},
SaturatedConversion, SaturatedConversion,
}; };
use sp_std::{boxed::Box, convert::TryInto}; use sp_std::{boxed::Box, convert::TryInto, prelude::*};
mod call_ext; mod call_ext;
#[cfg(test)] #[cfg(test)]
@@ -194,11 +194,12 @@ pub mod pallet {
let authority_set = <CurrentAuthoritySet<T, I>>::get(); let authority_set = <CurrentAuthoritySet<T, I>>::get();
let unused_proof_size = authority_set.unused_proof_size(); let unused_proof_size = authority_set.unused_proof_size();
let set_id = authority_set.set_id; let set_id = authority_set.set_id;
verify_justification::<T, I>(&justification, hash, number, authority_set.into())?; let authority_set: AuthoritySet = authority_set.into();
verify_justification::<T, I>(&justification, hash, number, authority_set)?;
let is_authorities_change_enacted = let maybe_new_authority_set =
try_enact_authority_change::<T, I>(&finality_target, set_id)?; try_enact_authority_change::<T, I>(&finality_target, set_id)?;
let may_refund_call_fee = is_authorities_change_enacted && let may_refund_call_fee = maybe_new_authority_set.is_some() &&
// if we have seen too many mandatory headers in this block, we don't want to refund // if we have seen too many mandatory headers in this block, we don't want to refund
Self::free_mandatory_headers_remaining() > 0 && Self::free_mandatory_headers_remaining() > 0 &&
// if arguments out of expected bounds, we don't want to refund // if arguments out of expected bounds, we don't want to refund
@@ -237,7 +238,14 @@ pub mod pallet {
let actual_weight = pre_dispatch_weight let actual_weight = pre_dispatch_weight
.set_proof_size(pre_dispatch_weight.proof_size().saturating_sub(unused_proof_size)); .set_proof_size(pre_dispatch_weight.proof_size().saturating_sub(unused_proof_size));
Self::deposit_event(Event::UpdatedBestFinalizedHeader { number, hash }); Self::deposit_event(Event::UpdatedBestFinalizedHeader {
number,
hash,
grandpa_info: HeaderGrandpaInfo {
justification,
authority_set: maybe_new_authority_set,
},
});
Ok(PostDispatchInfo { actual_weight: Some(actual_weight), pays_fee }) Ok(PostDispatchInfo { actual_weight: Some(actual_weight), pays_fee })
} }
@@ -402,6 +410,8 @@ pub mod pallet {
UpdatedBestFinalizedHeader { UpdatedBestFinalizedHeader {
number: BridgedBlockNumber<T, I>, number: BridgedBlockNumber<T, I>,
hash: BridgedBlockHash<T, I>, hash: BridgedBlockHash<T, I>,
/// The Grandpa info associated to the new best finalized header.
grandpa_info: HeaderGrandpaInfo<BridgedHeader<T, I>>,
}, },
} }
@@ -437,9 +447,7 @@ pub mod pallet {
pub(crate) fn try_enact_authority_change<T: Config<I>, I: 'static>( pub(crate) fn try_enact_authority_change<T: Config<I>, I: 'static>(
header: &BridgedHeader<T, I>, header: &BridgedHeader<T, I>,
current_set_id: sp_consensus_grandpa::SetId, current_set_id: sp_consensus_grandpa::SetId,
) -> Result<bool, sp_runtime::DispatchError> { ) -> Result<Option<AuthoritySet>, DispatchError> {
let mut change_enacted = false;
// We don't support forced changes - at that point governance intervention is required. // We don't support forced changes - at that point governance intervention is required.
ensure!( ensure!(
GrandpaConsensusLogReader::<BridgedBlockNumber<T, I>>::find_forced_change( GrandpaConsensusLogReader::<BridgedBlockNumber<T, I>>::find_forced_change(
@@ -468,7 +476,6 @@ pub mod pallet {
// Since our header schedules a change and we know the delay is 0, it must also enact // Since our header schedules a change and we know the delay is 0, it must also enact
// the change. // the change.
<CurrentAuthoritySet<T, I>>::put(&next_authorities); <CurrentAuthoritySet<T, I>>::put(&next_authorities);
change_enacted = true;
log::info!( log::info!(
target: LOG_TARGET, target: LOG_TARGET,
@@ -477,9 +484,11 @@ pub mod pallet {
current_set_id + 1, current_set_id + 1,
next_authorities, next_authorities,
); );
return Ok(Some(next_authorities.into()))
}; };
Ok(change_enacted) Ok(None)
} }
/// Verify a GRANDPA justification (finality proof) for a given header. /// Verify a GRANDPA justification (finality proof) for a given header.
@@ -603,10 +612,22 @@ pub mod pallet {
} }
} }
impl<T: Config<I>, I: 'static> Pallet<T, I> { impl<T: Config<I>, I: 'static> Pallet<T, I>
/// Get the best finalized block number. where
pub fn best_finalized_number() -> Option<BridgedBlockNumber<T, I>> { <T as frame_system::Config>::RuntimeEvent: TryInto<Event<T, I>>,
BestFinalized::<T, I>::get().map(|id| id.number()) {
/// Get the GRANDPA justifications accepted in the current block.
pub fn synced_headers_grandpa_info() -> Vec<HeaderGrandpaInfo<BridgedHeader<T, I>>> {
frame_system::Pallet::<T>::read_events_no_consensus()
.filter_map(|event| {
if let Event::<T, I>::UpdatedBestFinalizedHeader { grandpa_info, .. } =
event.event.try_into().ok()?
{
return Some(grandpa_info)
}
None
})
.collect()
} }
} }
@@ -913,10 +934,18 @@ mod tests {
event: TestEvent::Grandpa(Event::UpdatedBestFinalizedHeader { event: TestEvent::Grandpa(Event::UpdatedBestFinalizedHeader {
number: *header.number(), number: *header.number(),
hash: header.hash(), hash: header.hash(),
grandpa_info: HeaderGrandpaInfo {
justification: justification.clone(),
authority_set: None,
},
}), }),
topics: vec![], topics: vec![],
}], }],
); );
assert_eq!(
Pallet::<TestRuntime>::synced_headers_grandpa_info(),
vec![HeaderGrandpaInfo { justification, authority_set: None }]
);
}) })
} }
@@ -1022,7 +1051,7 @@ mod tests {
let result = Pallet::<TestRuntime>::submit_finality_proof( let result = Pallet::<TestRuntime>::submit_finality_proof(
RuntimeOrigin::signed(1), RuntimeOrigin::signed(1),
Box::new(header.clone()), Box::new(header.clone()),
justification, justification.clone(),
); );
assert_ok!(result); assert_ok!(result);
assert_eq!(result.unwrap().pays_fee, frame_support::dispatch::Pays::No); assert_eq!(result.unwrap().pays_fee, frame_support::dispatch::Pays::No);
@@ -1037,6 +1066,30 @@ mod tests {
StoredAuthoritySet::<TestRuntime, ()>::try_new(next_authorities, next_set_id) StoredAuthoritySet::<TestRuntime, ()>::try_new(next_authorities, next_set_id)
.unwrap(), .unwrap(),
); );
// Here
assert_eq!(
System::events(),
vec![EventRecord {
phase: Phase::Initialization,
event: TestEvent::Grandpa(Event::UpdatedBestFinalizedHeader {
number: *header.number(),
hash: header.hash(),
grandpa_info: HeaderGrandpaInfo {
justification: justification.clone(),
authority_set: 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()),
}]
);
}) })
} }
@@ -20,7 +20,7 @@ use crate::{Config, Error};
use bp_header_chain::{AuthoritySet, ChainWithGrandpa}; use bp_header_chain::{AuthoritySet, ChainWithGrandpa};
use codec::{Decode, Encode, MaxEncodedLen}; use codec::{Decode, Encode, MaxEncodedLen};
use frame_support::{traits::Get, BoundedVec, RuntimeDebugNoBound}; use frame_support::{traits::Get, BoundedVec, CloneNoBound, RuntimeDebugNoBound};
use scale_info::TypeInfo; use scale_info::TypeInfo;
use sp_consensus_grandpa::{AuthorityId, AuthorityList, AuthorityWeight, SetId}; use sp_consensus_grandpa::{AuthorityId, AuthorityList, AuthorityWeight, SetId};
use sp_std::marker::PhantomData; use sp_std::marker::PhantomData;
@@ -39,7 +39,7 @@ impl<T: Config<I>, I: 'static> Get<u32> for StoredAuthorityListLimit<T, I> {
} }
/// A bounded GRANDPA Authority List and ID. /// A bounded GRANDPA Authority List and ID.
#[derive(Clone, Decode, Encode, Eq, TypeInfo, MaxEncodedLen, RuntimeDebugNoBound)] #[derive(CloneNoBound, Decode, Encode, Eq, TypeInfo, MaxEncodedLen, RuntimeDebugNoBound)]
#[scale_info(skip_type_params(T, I))] #[scale_info(skip_type_params(T, I))]
pub struct StoredAuthoritySet<T: Config<I>, I: 'static> { pub struct StoredAuthoritySet<T: Config<I>, I: 'static> {
/// List of GRANDPA authorities for the current round. /// List of GRANDPA authorities for the current round.
+16 -57
View File
@@ -153,7 +153,7 @@ pub mod pallet {
/// Target header chain. /// Target header chain.
type TargetHeaderChain: TargetHeaderChain<Self::OutboundPayload, Self::AccountId>; type TargetHeaderChain: TargetHeaderChain<Self::OutboundPayload, Self::AccountId>;
/// Message payload verifier. /// Message payload verifier.
type LaneMessageVerifier: LaneMessageVerifier<Self::RuntimeOrigin, Self::OutboundPayload>; type LaneMessageVerifier: LaneMessageVerifier<Self::OutboundPayload>;
/// Delivery confirmation payments. /// Delivery confirmation payments.
type DeliveryConfirmationPayments: DeliveryConfirmationPayments<Self::AccountId>; type DeliveryConfirmationPayments: DeliveryConfirmationPayments<Self::AccountId>;
@@ -643,8 +643,7 @@ pub mod pallet {
} }
} }
impl<T, I> bp_messages::source_chain::MessagesBridge<T::RuntimeOrigin, T::OutboundPayload> impl<T, I> bp_messages::source_chain::MessagesBridge<T::OutboundPayload> for Pallet<T, I>
for Pallet<T, I>
where where
T: Config<I>, T: Config<I>,
I: 'static, I: 'static,
@@ -652,17 +651,15 @@ where
type Error = sp_runtime::DispatchErrorWithPostInfo<PostDispatchInfo>; type Error = sp_runtime::DispatchErrorWithPostInfo<PostDispatchInfo>;
fn send_message( fn send_message(
sender: T::RuntimeOrigin,
lane: LaneId, lane: LaneId,
message: T::OutboundPayload, message: T::OutboundPayload,
) -> Result<SendMessageArtifacts, Self::Error> { ) -> Result<SendMessageArtifacts, Self::Error> {
crate::send_message::<T, I>(sender, lane, message) crate::send_message::<T, I>(lane, message)
} }
} }
/// Function that actually sends message. /// Function that actually sends message.
fn send_message<T: Config<I>, I: 'static>( fn send_message<T: Config<I>, I: 'static>(
submitter: T::RuntimeOrigin,
lane_id: LaneId, lane_id: LaneId,
payload: T::OutboundPayload, payload: T::OutboundPayload,
) -> sp_std::result::Result< ) -> sp_std::result::Result<
@@ -688,8 +685,7 @@ fn send_message<T: Config<I>, I: 'static>(
// now let's enforce any additional lane rules // now let's enforce any additional lane rules
let mut lane = outbound_lane::<T, I>(lane_id); let mut lane = outbound_lane::<T, I>(lane_id);
T::LaneMessageVerifier::verify_message(&submitter, &lane_id, &lane.data(), &payload).map_err( T::LaneMessageVerifier::verify_message(&lane_id, &lane.data(), &payload).map_err(|err| {
|err| {
log::trace!( log::trace!(
target: LOG_TARGET, target: LOG_TARGET,
"Message to lane {:?} is rejected by lane verifier: {:?}", "Message to lane {:?} is rejected by lane verifier: {:?}",
@@ -698,8 +694,7 @@ fn send_message<T: Config<I>, I: 'static>(
); );
Error::<T, I>::MessageRejectedByLaneVerifier(err) Error::<T, I>::MessageRejectedByLaneVerifier(err)
}, })?;
)?;
// finally, save message in outbound storage and emit event // finally, save message in outbound storage and emit event
let encoded_payload = payload.encode(); let encoded_payload = payload.encode();
@@ -915,7 +910,7 @@ mod tests {
let message_nonce = let message_nonce =
outbound_lane::<TestRuntime, ()>(TEST_LANE_ID).data().latest_generated_nonce + 1; outbound_lane::<TestRuntime, ()>(TEST_LANE_ID).data().latest_generated_nonce + 1;
send_message::<TestRuntime, ()>(RuntimeOrigin::signed(1), TEST_LANE_ID, REGULAR_PAYLOAD) send_message::<TestRuntime, ()>(TEST_LANE_ID, REGULAR_PAYLOAD)
.expect("send_message has failed"); .expect("send_message has failed");
// check event with assigned nonce // check event with assigned nonce
@@ -982,11 +977,7 @@ mod tests {
)); ));
assert_noop!( assert_noop!(
send_message::<TestRuntime, ()>( send_message::<TestRuntime, ()>(TEST_LANE_ID, REGULAR_PAYLOAD,),
RuntimeOrigin::signed(1),
TEST_LANE_ID,
REGULAR_PAYLOAD,
),
Error::<TestRuntime, ()>::NotOperatingNormally, Error::<TestRuntime, ()>::NotOperatingNormally,
); );
@@ -1036,11 +1027,7 @@ mod tests {
); );
assert_noop!( assert_noop!(
send_message::<TestRuntime, ()>( send_message::<TestRuntime, ()>(TEST_LANE_ID, REGULAR_PAYLOAD,),
RuntimeOrigin::signed(1),
TEST_LANE_ID,
REGULAR_PAYLOAD,
),
Error::<TestRuntime, ()>::NotOperatingNormally, Error::<TestRuntime, ()>::NotOperatingNormally,
); );
@@ -1090,11 +1077,7 @@ mod tests {
.extra .extra
.extend_from_slice(&[0u8; MAX_OUTBOUND_PAYLOAD_SIZE as usize]); .extend_from_slice(&[0u8; MAX_OUTBOUND_PAYLOAD_SIZE as usize]);
assert_noop!( assert_noop!(
send_message::<TestRuntime, ()>( send_message::<TestRuntime, ()>(TEST_LANE_ID, message_payload.clone(),),
RuntimeOrigin::signed(1),
TEST_LANE_ID,
message_payload.clone(),
),
Error::<TestRuntime, ()>::MessageRejectedByPallet( Error::<TestRuntime, ()>::MessageRejectedByPallet(
VerificationError::MessageTooLarge VerificationError::MessageTooLarge
), ),
@@ -1105,11 +1088,7 @@ mod tests {
message_payload.extra.pop(); message_payload.extra.pop();
} }
assert_eq!(message_payload.encoded_size() as u32, MAX_OUTBOUND_PAYLOAD_SIZE); assert_eq!(message_payload.encoded_size() as u32, MAX_OUTBOUND_PAYLOAD_SIZE);
assert_ok!(send_message::<TestRuntime, ()>( assert_ok!(send_message::<TestRuntime, ()>(TEST_LANE_ID, message_payload,),);
RuntimeOrigin::signed(1),
TEST_LANE_ID,
message_payload,
),);
}) })
} }
@@ -1118,11 +1097,7 @@ mod tests {
run_test(|| { run_test(|| {
// messages with this payload are rejected by target chain verifier // messages with this payload are rejected by target chain verifier
assert_noop!( assert_noop!(
send_message::<TestRuntime, ()>( send_message::<TestRuntime, ()>(TEST_LANE_ID, PAYLOAD_REJECTED_BY_TARGET_CHAIN,),
RuntimeOrigin::signed(1),
TEST_LANE_ID,
PAYLOAD_REJECTED_BY_TARGET_CHAIN,
),
Error::<TestRuntime, ()>::MessageRejectedByChainVerifier(VerificationError::Other( Error::<TestRuntime, ()>::MessageRejectedByChainVerifier(VerificationError::Other(
mock::TEST_ERROR mock::TEST_ERROR
)), )),
@@ -1137,7 +1112,7 @@ mod tests {
let mut message = REGULAR_PAYLOAD; let mut message = REGULAR_PAYLOAD;
message.reject_by_lane_verifier = true; message.reject_by_lane_verifier = true;
assert_noop!( assert_noop!(
send_message::<TestRuntime, ()>(RuntimeOrigin::signed(1), TEST_LANE_ID, message,), send_message::<TestRuntime, ()>(TEST_LANE_ID, message,),
Error::<TestRuntime, ()>::MessageRejectedByLaneVerifier(VerificationError::Other( Error::<TestRuntime, ()>::MessageRejectedByLaneVerifier(VerificationError::Other(
mock::TEST_ERROR mock::TEST_ERROR
)), )),
@@ -1293,16 +1268,8 @@ mod tests {
#[test] #[test]
fn receive_messages_delivery_proof_rewards_relayers() { fn receive_messages_delivery_proof_rewards_relayers() {
run_test(|| { run_test(|| {
assert_ok!(send_message::<TestRuntime, ()>( assert_ok!(send_message::<TestRuntime, ()>(TEST_LANE_ID, REGULAR_PAYLOAD,));
RuntimeOrigin::signed(1), assert_ok!(send_message::<TestRuntime, ()>(TEST_LANE_ID, REGULAR_PAYLOAD,));
TEST_LANE_ID,
REGULAR_PAYLOAD,
));
assert_ok!(send_message::<TestRuntime, ()>(
RuntimeOrigin::signed(1),
TEST_LANE_ID,
REGULAR_PAYLOAD,
));
// this reports delivery of message 1 => reward is paid to TEST_RELAYER_A // this reports delivery of message 1 => reward is paid to TEST_RELAYER_A
let single_message_delivery_proof = TestMessagesDeliveryProof(Ok(( let single_message_delivery_proof = TestMessagesDeliveryProof(Ok((
@@ -1899,11 +1866,7 @@ mod tests {
send_regular_message(); send_regular_message();
receive_messages_delivery_proof(); receive_messages_delivery_proof();
// send + receive confirmation for lane 2 // send + receive confirmation for lane 2
assert_ok!(send_message::<TestRuntime, ()>( assert_ok!(send_message::<TestRuntime, ()>(TEST_LANE_ID_2, REGULAR_PAYLOAD,));
RuntimeOrigin::signed(1),
TEST_LANE_ID_2,
REGULAR_PAYLOAD,
));
assert_ok!(Pallet::<TestRuntime>::receive_messages_delivery_proof( assert_ok!(Pallet::<TestRuntime>::receive_messages_delivery_proof(
RuntimeOrigin::signed(1), RuntimeOrigin::signed(1),
TestMessagesDeliveryProof(Ok(( TestMessagesDeliveryProof(Ok((
@@ -1979,11 +1942,7 @@ mod tests {
fn outbound_message_from_unconfigured_lane_is_rejected() { fn outbound_message_from_unconfigured_lane_is_rejected() {
run_test(|| { run_test(|| {
assert_noop!( assert_noop!(
send_message::<TestRuntime, ()>( send_message::<TestRuntime, ()>(TEST_LANE_ID_3, REGULAR_PAYLOAD,),
RuntimeOrigin::signed(1),
TEST_LANE_ID_3,
REGULAR_PAYLOAD,
),
Error::<TestRuntime, ()>::InactiveOutboundLane, Error::<TestRuntime, ()>::InactiveOutboundLane,
); );
}); });
+1 -2
View File
@@ -310,9 +310,8 @@ impl TargetHeaderChain<TestPayload, TestRelayer> for TestTargetHeaderChain {
#[derive(Debug, Default)] #[derive(Debug, Default)]
pub struct TestLaneMessageVerifier; pub struct TestLaneMessageVerifier;
impl LaneMessageVerifier<RuntimeOrigin, TestPayload> for TestLaneMessageVerifier { impl LaneMessageVerifier<TestPayload> for TestLaneMessageVerifier {
fn verify_message( fn verify_message(
_submitter: &RuntimeOrigin,
_lane: &LaneId, _lane: &LaneId,
_lane_outbound_data: &OutboundLaneData, _lane_outbound_data: &OutboundLaneData,
payload: &TestPayload, payload: &TestPayload,
+28 -8
View File
@@ -691,12 +691,13 @@ pub(crate) mod tests {
use super::*; use super::*;
use crate::mock::{ use crate::mock::{
run_test, test_relay_header, BigParachainHeader, RegularParachainHasher, run_test, test_relay_header, BigParachainHeader, RegularParachainHasher,
RegularParachainHeader, RuntimeEvent as TestEvent, RuntimeOrigin, TestRuntime, RegularParachainHeader, RelayBlockHeader, RuntimeEvent as TestEvent, RuntimeOrigin,
UNTRACKED_PARACHAIN_ID, TestRuntime, UNTRACKED_PARACHAIN_ID,
}; };
use bp_test_utils::prepare_parachain_heads_proof; use bp_test_utils::prepare_parachain_heads_proof;
use codec::Encode; use codec::Encode;
use bp_header_chain::{justification::GrandpaJustification, HeaderGrandpaInfo};
use bp_parachains::{ use bp_parachains::{
BestParaHeadHash, BridgeParachainCall, ImportedParaHeadsKeyProvider, ParasInfoKeyProvider, BestParaHeadHash, BridgeParachainCall, ImportedParaHeadsKeyProvider, ParasInfoKeyProvider,
}; };
@@ -740,7 +741,10 @@ pub(crate) mod tests {
test_relay_header(0, state_root).hash() test_relay_header(0, state_root).hash()
} }
fn proceed(num: RelayBlockNumber, state_root: RelayBlockHash) -> ParaHash { fn proceed(
num: RelayBlockNumber,
state_root: RelayBlockHash,
) -> (ParaHash, GrandpaJustification<RelayBlockHeader>) {
pallet_bridge_grandpa::Pallet::<TestRuntime, BridgesGrandpaPalletInstance>::on_initialize( pallet_bridge_grandpa::Pallet::<TestRuntime, BridgesGrandpaPalletInstance>::on_initialize(
0, 0,
); );
@@ -752,11 +756,11 @@ pub(crate) mod tests {
pallet_bridge_grandpa::Pallet::<TestRuntime, BridgesGrandpaPalletInstance>::submit_finality_proof( pallet_bridge_grandpa::Pallet::<TestRuntime, BridgesGrandpaPalletInstance>::submit_finality_proof(
RuntimeOrigin::signed(1), RuntimeOrigin::signed(1),
Box::new(header), Box::new(header),
justification, justification.clone(),
) )
); );
hash (hash, justification)
} }
fn initial_best_head(parachain: u32) -> ParaInfo { fn initial_best_head(parachain: u32) -> ParaInfo {
@@ -993,7 +997,7 @@ pub(crate) mod tests {
); );
// import head#10 of parachain#1 at relay block #1 // import head#10 of parachain#1 at relay block #1
let relay_1_hash = proceed(1, state_root_10); let (relay_1_hash, justification) = proceed(1, state_root_10);
assert_ok!(import_parachain_1_head(1, state_root_10, parachains_10, proof_10)); assert_ok!(import_parachain_1_head(1, state_root_10, parachains_10, proof_10));
assert_eq!( assert_eq!(
ParasInfo::<TestRuntime>::get(ParaId(1)), ParasInfo::<TestRuntime>::get(ParaId(1)),
@@ -1032,6 +1036,10 @@ pub(crate) mod tests {
pallet_bridge_grandpa::Event::UpdatedBestFinalizedHeader { pallet_bridge_grandpa::Event::UpdatedBestFinalizedHeader {
number: 1, number: 1,
hash: relay_1_hash, hash: relay_1_hash,
grandpa_info: HeaderGrandpaInfo {
justification,
authority_set: None,
},
} }
), ),
topics: vec![], topics: vec![],
@@ -1149,7 +1157,7 @@ pub(crate) mod tests {
// try to import head#0 of parachain#1 at relay block#1 // try to import head#0 of parachain#1 at relay block#1
// => call succeeds, but nothing is changed // => call succeeds, but nothing is changed
let relay_1_hash = proceed(1, state_root); let (relay_1_hash, justification) = proceed(1, state_root);
assert_ok!(import_parachain_1_head(1, state_root, parachains, proof)); assert_ok!(import_parachain_1_head(1, state_root, parachains, proof));
assert_eq!(ParasInfo::<TestRuntime>::get(ParaId(1)), Some(initial_best_head(1))); assert_eq!(ParasInfo::<TestRuntime>::get(ParaId(1)), Some(initial_best_head(1)));
assert_eq!( assert_eq!(
@@ -1169,6 +1177,10 @@ pub(crate) mod tests {
pallet_bridge_grandpa::Event::UpdatedBestFinalizedHeader { pallet_bridge_grandpa::Event::UpdatedBestFinalizedHeader {
number: 1, number: 1,
hash: relay_1_hash, hash: relay_1_hash,
grandpa_info: HeaderGrandpaInfo {
justification,
authority_set: None,
}
} }
), ),
topics: vec![], topics: vec![],
@@ -1197,7 +1209,7 @@ pub(crate) mod tests {
initialize(state_root_5); initialize(state_root_5);
// head#10 of parachain#1 at relay block#1 // head#10 of parachain#1 at relay block#1
let relay_1_hash = proceed(1, state_root_10); let (relay_1_hash, justification) = proceed(1, state_root_10);
assert_ok!(import_parachain_1_head(1, state_root_10, parachains_10, proof_10)); assert_ok!(import_parachain_1_head(1, state_root_10, parachains_10, proof_10));
assert_eq!( assert_eq!(
ParasInfo::<TestRuntime>::get(ParaId(1)), ParasInfo::<TestRuntime>::get(ParaId(1)),
@@ -1218,6 +1230,10 @@ pub(crate) mod tests {
pallet_bridge_grandpa::Event::UpdatedBestFinalizedHeader { pallet_bridge_grandpa::Event::UpdatedBestFinalizedHeader {
number: 1, number: 1,
hash: relay_1_hash, hash: relay_1_hash,
grandpa_info: HeaderGrandpaInfo {
justification: justification.clone(),
authority_set: None,
}
} }
), ),
topics: vec![], topics: vec![],
@@ -1255,6 +1271,10 @@ pub(crate) mod tests {
pallet_bridge_grandpa::Event::UpdatedBestFinalizedHeader { pallet_bridge_grandpa::Event::UpdatedBestFinalizedHeader {
number: 1, number: 1,
hash: relay_1_hash, hash: relay_1_hash,
grandpa_info: HeaderGrandpaInfo {
justification,
authority_set: None,
}
} }
), ),
topics: vec![], topics: vec![],
@@ -29,7 +29,7 @@ use frame_support::{
sp_runtime::{MultiAddress, MultiSigner}, sp_runtime::{MultiAddress, MultiSigner},
RuntimeDebug, RuntimeDebug,
}; };
use sp_std::prelude::*; use sp_std::prelude::Vec;
/// BridgeHubKusama parachain. /// BridgeHubKusama parachain.
#[derive(RuntimeDebug)] #[derive(RuntimeDebug)]
@@ -25,7 +25,7 @@ use bp_runtime::{
decl_bridge_finality_runtime_apis, decl_bridge_messages_runtime_apis, Chain, Parachain, decl_bridge_finality_runtime_apis, decl_bridge_messages_runtime_apis, Chain, Parachain,
}; };
use frame_support::{dispatch::DispatchClass, RuntimeDebug}; use frame_support::{dispatch::DispatchClass, RuntimeDebug};
use sp_std::prelude::*; use sp_std::prelude::Vec;
/// BridgeHubPolkadot parachain. /// BridgeHubPolkadot parachain.
#[derive(RuntimeDebug)] #[derive(RuntimeDebug)]
@@ -29,7 +29,7 @@ use frame_support::{
sp_runtime::{MultiAddress, MultiSigner}, sp_runtime::{MultiAddress, MultiSigner},
RuntimeDebug, RuntimeDebug,
}; };
use sp_std::prelude::*; use sp_std::prelude::Vec;
/// BridgeHubRococo parachain. /// BridgeHubRococo parachain.
#[derive(RuntimeDebug)] #[derive(RuntimeDebug)]
@@ -25,7 +25,7 @@ use bp_runtime::{
decl_bridge_finality_runtime_apis, decl_bridge_messages_runtime_apis, Chain, Parachain, decl_bridge_finality_runtime_apis, decl_bridge_messages_runtime_apis, Chain, Parachain,
}; };
use frame_support::{dispatch::DispatchClass, RuntimeDebug}; use frame_support::{dispatch::DispatchClass, RuntimeDebug};
use sp_std::prelude::*; use sp_std::prelude::Vec;
/// BridgeHubWococo parachain. /// BridgeHubWococo parachain.
#[derive(RuntimeDebug)] #[derive(RuntimeDebug)]
@@ -18,6 +18,7 @@ bp-runtime = { path = "../runtime", default-features = false }
frame-support = { git = "https://github.com/paritytech/substrate", branch = "master", default-features = false } frame-support = { git = "https://github.com/paritytech/substrate", branch = "master", default-features = false }
sp-api = { git = "https://github.com/paritytech/substrate", branch = "master", default-features = false } sp-api = { git = "https://github.com/paritytech/substrate", branch = "master", default-features = false }
sp-std = { git = "https://github.com/paritytech/substrate", branch = "master", default-features = false }
[features] [features]
default = ["std"] default = ["std"]
@@ -27,4 +28,5 @@ std = [
"bp-runtime/std", "bp-runtime/std",
"frame-support/std", "frame-support/std",
"sp-api/std", "sp-api/std",
"sp-std/std",
] ]
@@ -23,6 +23,7 @@ pub use bp_polkadot_core::*;
use bp_header_chain::ChainWithGrandpa; use bp_header_chain::ChainWithGrandpa;
use bp_runtime::{decl_bridge_finality_runtime_apis, Chain}; use bp_runtime::{decl_bridge_finality_runtime_apis, Chain};
use frame_support::weights::Weight; use frame_support::weights::Weight;
use sp_std::prelude::Vec;
/// Kusama Chain /// Kusama Chain
pub struct Kusama; pub struct Kusama;
@@ -69,4 +70,4 @@ pub const WITH_KUSAMA_GRANDPA_PALLET_NAME: &str = "BridgeKusamaGrandpa";
/// reserve. /// reserve.
pub const MAX_NESTED_PARACHAIN_HEAD_DATA_SIZE: u32 = 128; pub const MAX_NESTED_PARACHAIN_HEAD_DATA_SIZE: u32 = 128;
decl_bridge_finality_runtime_apis!(kusama); decl_bridge_finality_runtime_apis!(kusama, grandpa);
@@ -18,6 +18,7 @@ bp-runtime = { path = "../runtime", default-features = false }
frame-support = { git = "https://github.com/paritytech/substrate", branch = "master", default-features = false } frame-support = { git = "https://github.com/paritytech/substrate", branch = "master", default-features = false }
sp-api = { git = "https://github.com/paritytech/substrate", branch = "master", default-features = false } sp-api = { git = "https://github.com/paritytech/substrate", branch = "master", default-features = false }
sp-std = { git = "https://github.com/paritytech/substrate", branch = "master", default-features = false }
[features] [features]
default = ["std"] default = ["std"]
@@ -27,4 +28,5 @@ std = [
"bp-runtime/std", "bp-runtime/std",
"frame-support/std", "frame-support/std",
"sp-api/std", "sp-api/std",
"sp-std/std",
] ]
@@ -23,6 +23,7 @@ pub use bp_polkadot_core::*;
use bp_header_chain::ChainWithGrandpa; use bp_header_chain::ChainWithGrandpa;
use bp_runtime::{decl_bridge_finality_runtime_apis, Chain}; use bp_runtime::{decl_bridge_finality_runtime_apis, Chain};
use frame_support::weights::Weight; use frame_support::weights::Weight;
use sp_std::prelude::Vec;
/// Polkadot Chain /// Polkadot Chain
pub struct Polkadot; pub struct Polkadot;
@@ -69,4 +70,4 @@ pub const WITH_POLKADOT_GRANDPA_PALLET_NAME: &str = "BridgePolkadotGrandpa";
/// reserve. /// reserve.
pub const MAX_NESTED_PARACHAIN_HEAD_DATA_SIZE: u32 = 128; pub const MAX_NESTED_PARACHAIN_HEAD_DATA_SIZE: u32 = 128;
decl_bridge_finality_runtime_apis!(polkadot); decl_bridge_finality_runtime_apis!(polkadot, grandpa);
@@ -17,6 +17,7 @@ bp-runtime = { path = "../runtime", default-features = false }
# Substrate Based Dependencies # Substrate Based Dependencies
sp-api = { git = "https://github.com/paritytech/substrate", branch = "master", default-features = false } sp-api = { git = "https://github.com/paritytech/substrate", branch = "master", default-features = false }
sp-std = { git = "https://github.com/paritytech/substrate", branch = "master", default-features = false }
frame-support = { git = "https://github.com/paritytech/substrate", branch = "master", default-features = false } frame-support = { git = "https://github.com/paritytech/substrate", branch = "master", default-features = false }
[features] [features]
@@ -27,4 +28,5 @@ std = [
"bp-runtime/std", "bp-runtime/std",
"frame-support/std", "frame-support/std",
"sp-api/std", "sp-api/std",
"sp-std/std",
] ]
@@ -23,6 +23,7 @@ pub use bp_polkadot_core::*;
use bp_header_chain::ChainWithGrandpa; use bp_header_chain::ChainWithGrandpa;
use bp_runtime::{decl_bridge_finality_runtime_apis, Chain}; use bp_runtime::{decl_bridge_finality_runtime_apis, Chain};
use frame_support::{parameter_types, weights::Weight}; use frame_support::{parameter_types, weights::Weight};
use sp_std::prelude::Vec;
/// Rococo Chain /// Rococo Chain
pub struct Rococo; pub struct Rococo;
@@ -73,4 +74,4 @@ pub const WITH_ROCOCO_GRANDPA_PALLET_NAME: &str = "BridgeRococoGrandpa";
/// reserve. /// reserve.
pub const MAX_NESTED_PARACHAIN_HEAD_DATA_SIZE: u32 = 128; pub const MAX_NESTED_PARACHAIN_HEAD_DATA_SIZE: u32 = 128;
decl_bridge_finality_runtime_apis!(rococo); decl_bridge_finality_runtime_apis!(rococo, grandpa);
@@ -19,6 +19,7 @@ bp-rococo = { path = "../chain-rococo", default-features = false }
frame-support = { git = "https://github.com/paritytech/substrate", branch = "master", default-features = false } frame-support = { git = "https://github.com/paritytech/substrate", branch = "master", default-features = false }
sp-api = { git = "https://github.com/paritytech/substrate", branch = "master", default-features = false } sp-api = { git = "https://github.com/paritytech/substrate", branch = "master", default-features = false }
sp-std = { git = "https://github.com/paritytech/substrate", branch = "master", default-features = false }
[features] [features]
default = ["std"] default = ["std"]
@@ -29,4 +30,5 @@ std = [
"bp-rococo/std", "bp-rococo/std",
"frame-support/std", "frame-support/std",
"sp-api/std", "sp-api/std",
"sp-std/std",
] ]
@@ -26,6 +26,7 @@ pub use bp_rococo::{
use bp_header_chain::ChainWithGrandpa; use bp_header_chain::ChainWithGrandpa;
use bp_runtime::{decl_bridge_finality_runtime_apis, Chain}; use bp_runtime::{decl_bridge_finality_runtime_apis, Chain};
use frame_support::weights::Weight; use frame_support::weights::Weight;
use sp_std::prelude::Vec;
/// Wococo Chain /// Wococo Chain
pub struct Wococo; pub struct Wococo;
@@ -62,4 +63,4 @@ impl ChainWithGrandpa for Wococo {
/// Name of the With-Wococo GRANDPA pallet instance that is deployed at bridged chains. /// Name of the With-Wococo GRANDPA pallet instance that is deployed at bridged chains.
pub const WITH_WOCOCO_GRANDPA_PALLET_NAME: &str = "BridgeWococoGrandpa"; pub const WITH_WOCOCO_GRANDPA_PALLET_NAME: &str = "BridgeWococoGrandpa";
decl_bridge_finality_runtime_apis!(wococo); decl_bridge_finality_runtime_apis!(wococo, grandpa);
@@ -1,444 +0,0 @@
// Copyright 2019-2021 Parity Technologies (UK) Ltd.
// This file is part of Parity Bridges Common.
// Parity Bridges Common 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.
// Parity Bridges Common 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 Parity Bridges Common. If not, see <http://www.gnu.org/licenses/>.
//! Pallet for checking GRANDPA Finality Proofs.
//!
//! Adapted copy of substrate/client/finality-grandpa/src/justification.rs. If origin
//! will ever be moved to the sp_consensus_grandpa, we should reuse that implementation.
use crate::ChainWithGrandpa;
use bp_runtime::{BlockNumberOf, Chain, HashOf, HeaderId};
use codec::{Decode, Encode, MaxEncodedLen};
use finality_grandpa::voter_set::VoterSet;
use frame_support::{RuntimeDebug, RuntimeDebugNoBound};
use scale_info::TypeInfo;
use sp_consensus_grandpa::{AuthorityId, AuthoritySignature, SetId};
use sp_runtime::{traits::Header as HeaderT, SaturatedConversion};
use sp_std::{
collections::{btree_map::BTreeMap, btree_set::BTreeSet},
prelude::*,
};
/// 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, Clone, PartialEq, Eq, TypeInfo, RuntimeDebugNoBound)]
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>,
}
impl<H: HeaderT> GrandpaJustification<H> {
/// Returns reasonable size of justification using constants from the provided chain.
///
/// An imprecise analogue of `MaxEncodedLen` implementation. We don't use it for
/// any precise calculations - that's just an estimation.
pub fn max_reasonable_size<C>(required_precommits: u32) -> u32
where
C: Chain + ChainWithGrandpa,
{
// we don't need precise results here - just estimations, so some details
// are removed from computations (e.g. bytes required to encode vector length)
// structures in `finality_grandpa` crate are not implementing `MaxEncodedLength`, so
// here's our estimation for the `finality_grandpa::Commit` struct size
//
// precommit is: hash + number
// signed precommit is: precommit + signature (64b) + authority id
// commit is: hash + number + vec of signed precommits
let signed_precommit_size: u32 = BlockNumberOf::<C>::max_encoded_len()
.saturating_add(HashOf::<C>::max_encoded_len().saturated_into())
.saturating_add(64)
.saturating_add(AuthorityId::max_encoded_len().saturated_into())
.saturated_into();
let max_expected_signed_commit_size = signed_precommit_size
.saturating_mul(required_precommits)
.saturating_add(BlockNumberOf::<C>::max_encoded_len().saturated_into())
.saturating_add(HashOf::<C>::max_encoded_len().saturated_into());
// justification is a signed GRANDPA commit, `votes_ancestries` vector and round number
let max_expected_votes_ancestries_size = C::REASONABLE_HEADERS_IN_JUSTIFICATON_ANCESTRY
.saturating_mul(C::AVERAGE_HEADER_SIZE_IN_JUSTIFICATION);
8u32.saturating_add(max_expected_signed_commit_size)
.saturating_add(max_expected_votes_ancestries_size)
}
pub fn commit_target_id(&self) -> HeaderId<H::Hash, H::Number> {
HeaderId(self.commit.target_number, self.commit.target_hash)
}
}
impl<H: HeaderT> crate::FinalityProof<H::Number> for GrandpaJustification<H> {
fn target_header_number(&self) -> H::Number {
self.commit.target_number
}
}
/// Justification verification error.
#[derive(Eq, RuntimeDebug, PartialEq)]
pub enum Error {
/// Failed to decode justification.
JustificationDecode,
/// Justification is finalizing unexpected header.
InvalidJustificationTarget,
/// Justification contains redundant votes.
RedundantVotesInJustification,
/// Justification contains unknown authority precommit.
UnknownAuthorityVote,
/// Justification contains duplicate authority precommit.
DuplicateAuthorityVote,
/// The authority has provided an invalid signature.
InvalidAuthoritySignature,
/// The justification contains precommit for header that is not a descendant of the commit
/// header.
UnrelatedAncestryVote,
/// 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.
RedundantVotesAncestries,
}
/// Given GRANDPA authorities set size, return number of valid authorities votes that the
/// justification must have to be valid.
///
/// This function assumes that all authorities have the same vote weight.
pub fn required_justification_precommits(authorities_set_length: u32) -> u32 {
authorities_set_length - authorities_set_length.saturating_sub(1) / 3
}
/// Decode justification target.
pub fn decode_justification_target<Header: HeaderT>(
raw_justification: &[u8],
) -> Result<(Header::Hash, Header::Number), Error> {
GrandpaJustification::<Header>::decode(&mut &*raw_justification)
.map(|justification| (justification.commit.target_hash, justification.commit.target_number))
.map_err(|_| Error::JustificationDecode)
}
/// Verify and optimize given justification by removing unknown and duplicate votes.
pub fn verify_and_optimize_justification<Header: HeaderT>(
finalized_target: (Header::Hash, Header::Number),
authorities_set_id: SetId,
authorities_set: &VoterSet<AuthorityId>,
justification: &mut GrandpaJustification<Header>,
) -> Result<(), Error> {
let mut optimizer = OptimizationCallbacks {
extra_precommits: vec![],
redundant_votes_ancestries: Default::default(),
};
verify_justification_with_callbacks(
finalized_target,
authorities_set_id,
authorities_set,
justification,
&mut optimizer,
)?;
optimizer.optimize(justification);
Ok(())
}
/// 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> {
verify_justification_with_callbacks(
finalized_target,
authorities_set_id,
authorities_set,
justification,
&mut StrictVerificationCallbacks,
)
}
/// Verification callbacks.
trait VerificationCallbacks<Header: HeaderT> {
/// Called when we see a precommit from unknown authority.
fn on_unkown_authority(&mut self, precommit_idx: usize) -> Result<(), Error>;
/// Called when we see a precommit with duplicate vote from known authority.
fn on_duplicate_authority_vote(&mut self, precommit_idx: usize) -> Result<(), Error>;
/// Called when we see a precommit with an invalid signature.
fn on_invalid_authority_signature(&mut self, precommit_idx: usize) -> Result<(), Error>;
/// Called when we see a precommit after we've collected enough votes from authorities.
fn on_redundant_authority_vote(&mut self, precommit_idx: usize) -> Result<(), Error>;
/// Called when we see a precommit that is not a descendant of the commit target.
fn on_unrelated_ancestry_vote(&mut self, precommit_idx: usize) -> Result<(), Error>;
/// Called when there are redundant headers in the votes ancestries.
fn on_redundant_votes_ancestries(
&mut self,
redundant_votes_ancestries: BTreeSet<Header::Hash>,
) -> Result<(), Error>;
}
/// Verification callbacks that reject all unknown, duplicate or redundant votes.
struct StrictVerificationCallbacks;
impl<Header: HeaderT> VerificationCallbacks<Header> for StrictVerificationCallbacks {
fn on_unkown_authority(&mut self, _precommit_idx: usize) -> Result<(), Error> {
Err(Error::UnknownAuthorityVote)
}
fn on_duplicate_authority_vote(&mut self, _precommit_idx: usize) -> Result<(), Error> {
Err(Error::DuplicateAuthorityVote)
}
fn on_invalid_authority_signature(&mut self, _precommit_idx: usize) -> Result<(), Error> {
Err(Error::InvalidAuthoritySignature)
}
fn on_redundant_authority_vote(&mut self, _precommit_idx: usize) -> Result<(), Error> {
Err(Error::RedundantVotesInJustification)
}
fn on_unrelated_ancestry_vote(&mut self, _precommit_idx: usize) -> Result<(), Error> {
Err(Error::UnrelatedAncestryVote)
}
fn on_redundant_votes_ancestries(
&mut self,
_redundant_votes_ancestries: BTreeSet<Header::Hash>,
) -> Result<(), Error> {
Err(Error::RedundantVotesAncestries)
}
}
/// Verification callbacks for justification optimization.
struct OptimizationCallbacks<Header: HeaderT> {
extra_precommits: Vec<usize>,
redundant_votes_ancestries: BTreeSet<Header::Hash>,
}
impl<Header: HeaderT> OptimizationCallbacks<Header> {
fn optimize(self, justification: &mut GrandpaJustification<Header>) {
for invalid_precommit_idx in self.extra_precommits.into_iter().rev() {
justification.commit.precommits.remove(invalid_precommit_idx);
}
if !self.redundant_votes_ancestries.is_empty() {
justification
.votes_ancestries
.retain(|header| !self.redundant_votes_ancestries.contains(&header.hash()))
}
}
}
impl<Header: HeaderT> VerificationCallbacks<Header> for OptimizationCallbacks<Header> {
fn on_unkown_authority(&mut self, precommit_idx: usize) -> Result<(), Error> {
self.extra_precommits.push(precommit_idx);
Ok(())
}
fn on_duplicate_authority_vote(&mut self, precommit_idx: usize) -> Result<(), Error> {
self.extra_precommits.push(precommit_idx);
Ok(())
}
fn on_invalid_authority_signature(&mut self, precommit_idx: usize) -> Result<(), Error> {
self.extra_precommits.push(precommit_idx);
Ok(())
}
fn on_redundant_authority_vote(&mut self, precommit_idx: usize) -> Result<(), Error> {
self.extra_precommits.push(precommit_idx);
Ok(())
}
fn on_unrelated_ancestry_vote(&mut self, precommit_idx: usize) -> Result<(), Error> {
self.extra_precommits.push(precommit_idx);
Ok(())
}
fn on_redundant_votes_ancestries(
&mut self,
redundant_votes_ancestries: BTreeSet<Header::Hash>,
) -> Result<(), Error> {
self.redundant_votes_ancestries = redundant_votes_ancestries;
Ok(())
}
}
/// Verify that justification, that is generated by given authority set, finalizes given header.
fn verify_justification_with_callbacks<Header: HeaderT, C: VerificationCallbacks<Header>>(
finalized_target: (Header::Hash, Header::Number),
authorities_set_id: SetId,
authorities_set: &VoterSet<AuthorityId>,
justification: &GrandpaJustification<Header>,
callbacks: &mut C,
) -> Result<(), Error> {
// 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 threshold = authorities_set.threshold().get();
let mut chain = AncestryChain::new(justification);
let mut signature_buffer = Vec::new();
let mut votes = BTreeSet::new();
let mut cumulative_weight = 0u64;
for (precommit_idx, signed) in justification.commit.precommits.iter().enumerate() {
// if we have collected enough precommits, we probabably want to fail/remove extra
// precommits
if cumulative_weight >= threshold {
callbacks.on_redundant_authority_vote(precommit_idx)?;
continue
}
// authority must be in the set
let authority_info = match authorities_set.get(&signed.id) {
Some(authority_info) => authority_info,
None => {
callbacks.on_unkown_authority(precommit_idx)?;
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.contains(&signed.id) {
callbacks.on_duplicate_authority_vote(precommit_idx)?;
continue
}
// all precommits must be descendants of the target block
let route =
match chain.ancestry(&signed.precommit.target_hash, &signed.precommit.target_number) {
Some(route) => route,
None => {
callbacks.on_unrelated_ancestry_vote(precommit_idx)?;
continue
},
};
// verify authority signature
if !sp_consensus_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,
) {
callbacks.on_invalid_authority_signature(precommit_idx)?;
continue
}
// now we can count the vote since we know that it is valid
votes.insert(signed.id.clone());
chain.mark_route_as_visited(route);
cumulative_weight = cumulative_weight.saturating_add(authority_info.weight().get());
}
// check that the cumulative weight of validators that voted for the justification target (or
// one of its descendents) is larger than the required threshold.
if cumulative_weight < threshold {
return Err(Error::TooLowCumulativeWeight)
}
// check that there are no extra headers in the justification
if !chain.is_fully_visited() {
callbacks.on_redundant_votes_ancestries(chain.unvisited)?;
}
Ok(())
}
/// Votes ancestries with useful methods.
#[derive(RuntimeDebug)]
pub struct AncestryChain<Header: HeaderT> {
/// We expect all forks in the ancestry chain to be descendants of base.
base: HeaderId<Header::Hash, Header::Number>,
/// Header hash => parent header hash mapping.
pub parents: BTreeMap<Header::Hash, Header::Hash>,
/// Hashes of headers that were not visited by `ancestry()`.
pub unvisited: BTreeSet<Header::Hash>,
}
impl<Header: HeaderT> AncestryChain<Header> {
/// Create new ancestry chain.
pub fn new(justification: &GrandpaJustification<Header>) -> AncestryChain<Header> {
let mut parents = BTreeMap::new();
let mut unvisited = BTreeSet::new();
for ancestor in &justification.votes_ancestries {
let hash = ancestor.hash();
let parent_hash = *ancestor.parent_hash();
parents.insert(hash, parent_hash);
unvisited.insert(hash);
}
AncestryChain { base: justification.commit_target_id(), parents, unvisited }
}
/// Returns a route if the precommit target block is a descendant of the `base` block.
pub fn ancestry(
&self,
precommit_target_hash: &Header::Hash,
precommit_target_number: &Header::Number,
) -> Option<Vec<Header::Hash>> {
if precommit_target_number < &self.base.number() {
return None
}
let mut route = vec![];
let mut current_hash = *precommit_target_hash;
loop {
if current_hash == self.base.hash() {
break
}
current_hash = match self.parents.get(&current_hash) {
Some(parent_hash) => {
let is_visited_before = self.unvisited.get(&current_hash).is_none();
if is_visited_before {
// If the current header has been visited in a previous call, it is a
// descendent of `base` (we assume that the previous call was successful).
return Some(route)
}
route.push(current_hash);
*parent_hash
},
None => return None,
};
}
Some(route)
}
fn mark_route_as_visited(&mut self, route: Vec<Header::Hash>) {
for hash in route {
self.unvisited.remove(&hash);
}
}
fn is_fully_visited(&self) -> bool {
self.unvisited.is_empty()
}
}
@@ -0,0 +1,127 @@
// Copyright 2019-2023 Parity Technologies (UK) Ltd.
// This file is part of Parity Bridges Common.
// Parity Bridges Common 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.
// Parity Bridges Common 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 Parity Bridges Common. If not, see <http://www.gnu.org/licenses/>.
//! Logic for checking GRANDPA Finality Proofs.
//!
//! Adapted copy of substrate/client/finality-grandpa/src/justification.rs. If origin
//! will ever be moved to the sp_consensus_grandpa, we should reuse that implementation.
mod verification;
use crate::ChainWithGrandpa;
pub use verification::{
equivocation::{EquivocationsCollector, Error as EquivocationsCollectorError},
optimizer::verify_and_optimize_justification,
strict::verify_justification,
AncestryChain, Error as JustificationVerificationError, PrecommitError,
};
use bp_runtime::{BlockNumberOf, Chain, HashOf, HeaderId};
use codec::{Decode, Encode, MaxEncodedLen};
use frame_support::{RuntimeDebug, RuntimeDebugNoBound};
use scale_info::TypeInfo;
use sp_consensus_grandpa::{AuthorityId, AuthoritySignature};
use sp_runtime::{traits::Header as HeaderT, SaturatedConversion};
use sp_std::prelude::*;
/// 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, Clone, PartialEq, Eq, TypeInfo, RuntimeDebugNoBound)]
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>,
}
impl<H: HeaderT> GrandpaJustification<H> {
/// Returns reasonable size of justification using constants from the provided chain.
///
/// An imprecise analogue of `MaxEncodedLen` implementation. We don't use it for
/// any precise calculations - that's just an estimation.
pub fn max_reasonable_size<C>(required_precommits: u32) -> u32
where
C: Chain + ChainWithGrandpa,
{
// we don't need precise results here - just estimations, so some details
// are removed from computations (e.g. bytes required to encode vector length)
// structures in `finality_grandpa` crate are not implementing `MaxEncodedLength`, so
// here's our estimation for the `finality_grandpa::Commit` struct size
//
// precommit is: hash + number
// signed precommit is: precommit + signature (64b) + authority id
// commit is: hash + number + vec of signed precommits
let signed_precommit_size: u32 = BlockNumberOf::<C>::max_encoded_len()
.saturating_add(HashOf::<C>::max_encoded_len().saturated_into())
.saturating_add(64)
.saturating_add(AuthorityId::max_encoded_len().saturated_into())
.saturated_into();
let max_expected_signed_commit_size = signed_precommit_size
.saturating_mul(required_precommits)
.saturating_add(BlockNumberOf::<C>::max_encoded_len().saturated_into())
.saturating_add(HashOf::<C>::max_encoded_len().saturated_into());
let max_expected_votes_ancestries_size = C::REASONABLE_HEADERS_IN_JUSTIFICATON_ANCESTRY
.saturating_mul(C::AVERAGE_HEADER_SIZE_IN_JUSTIFICATION);
// justification is round number (u64=8b), a signed GRANDPA commit and the
// `votes_ancestries` vector
8u32.saturating_add(max_expected_signed_commit_size)
.saturating_add(max_expected_votes_ancestries_size)
}
/// Return identifier of header that this justification claims to finalize.
pub fn commit_target_id(&self) -> HeaderId<H::Hash, H::Number> {
HeaderId(self.commit.target_number, self.commit.target_hash)
}
}
impl<H: HeaderT> crate::FinalityProof<H::Number> for GrandpaJustification<H> {
fn target_header_number(&self) -> H::Number {
self.commit.target_number
}
}
/// Justification verification error.
#[derive(Eq, RuntimeDebug, PartialEq)]
pub enum Error {
/// Failed to decode justification.
JustificationDecode,
}
/// Given GRANDPA authorities set size, return number of valid authorities votes that the
/// justification must have to be valid.
///
/// This function assumes that all authorities have the same vote weight.
pub fn required_justification_precommits(authorities_set_length: u32) -> u32 {
authorities_set_length - authorities_set_length.saturating_sub(1) / 3
}
/// Decode justification target.
pub fn decode_justification_target<Header: HeaderT>(
raw_justification: &[u8],
) -> Result<(Header::Hash, Header::Number), Error> {
GrandpaJustification::<Header>::decode(&mut &*raw_justification)
.map(|justification| (justification.commit.target_hash, justification.commit.target_number))
.map_err(|_| Error::JustificationDecode)
}
@@ -0,0 +1,179 @@
// Copyright 2019-2023 Parity Technologies (UK) Ltd.
// This file is part of Parity Bridges Common.
// Parity Bridges Common 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.
// Parity Bridges Common 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 Parity Bridges Common. If not, see <http://www.gnu.org/licenses/>.
//! Logic for extracting equivocations from multiple GRANDPA Finality Proofs.
use crate::justification::{
verification::{
Error as JustificationVerificationError, JustificationVerifier, PrecommitError,
SignedPrecommit,
},
GrandpaJustification,
};
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 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(
finality_grandpa::Equivocation<AuthorityId, Precommit<Header>, AuthoritySignature>,
),
}
/// 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>,
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>,
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)?;
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)
}
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.
pub fn into_equivocation_proofs(self) -> Vec<EquivocationProof<Header::Hash, Header::Number>> {
let mut equivocations = vec![];
for (_authority, vote) in self.votes {
if let AuthorityVotes::Equivocation(equivocation) = vote {
equivocations.push(EquivocationProof::new(
self.authorities_set_id,
sp_consensus_grandpa::Equivocation::Precommit(equivocation),
));
}
}
equivocations
}
}
impl<'a, Header: HeaderT> JustificationVerifier<Header> for EquivocationsCollector<'a, Header> {
fn process_redundant_vote(
&mut self,
_precommit_idx: usize,
) -> Result<IterationFlow, PrecommitError> {
Ok(IterationFlow::Run)
}
fn process_known_authority_vote(
&mut self,
_precommit_idx: usize,
_signed: &SignedPrecommit<Header>,
) -> Result<IterationFlow, PrecommitError> {
Ok(IterationFlow::Run)
}
fn process_unknown_authority_vote(
&mut self,
_precommit_idx: usize,
) -> Result<(), PrecommitError> {
Ok(())
}
fn process_unrelated_ancestry_vote(
&mut self,
_precommit_idx: usize,
) -> Result<IterationFlow, PrecommitError> {
Ok(IterationFlow::Run)
}
fn process_invalid_signature_vote(
&mut self,
_precommit_idx: usize,
) -> Result<(), PrecommitError> {
Ok(())
}
fn process_valid_vote(&mut self, signed: &SignedPrecommit<Header>) {
match self.votes.get_mut(&signed.id) {
Some(vote) => match vote {
AuthorityVotes::SingleVote(first_vote) => {
if first_vote.precommit != signed.precommit {
*vote = AuthorityVotes::Equivocation(finality_grandpa::Equivocation {
round_number: self.round,
identity: signed.id.clone(),
first: (first_vote.precommit.clone(), first_vote.signature.clone()),
second: (signed.precommit.clone(), signed.signature.clone()),
});
}
},
AuthorityVotes::Equivocation(_) => {},
},
None => {
self.votes.insert(signed.id.clone(), AuthorityVotes::SingleVote(signed.clone()));
},
}
}
fn process_redundant_votes_ancestries(
&mut self,
_redundant_votes_ancestries: BTreeSet<Header::Hash>,
) -> Result<(), JustificationVerificationError> {
Ok(())
}
}
@@ -0,0 +1,279 @@
// Copyright 2019-2023 Parity Technologies (UK) Ltd.
// This file is part of Parity Bridges Common.
// Parity Bridges Common 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.
// Parity Bridges Common 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 Parity Bridges Common. If not, see <http://www.gnu.org/licenses/>.
//! Logic for checking GRANDPA Finality Proofs.
pub mod equivocation;
pub mod optimizer;
pub mod strict;
use crate::justification::GrandpaJustification;
use bp_runtime::HeaderId;
use finality_grandpa::voter_set::VoterSet;
use frame_support::RuntimeDebug;
use sp_consensus_grandpa::{AuthorityId, AuthoritySignature, SetId};
use sp_runtime::traits::Header as HeaderT;
use sp_std::{
collections::{btree_map::BTreeMap, btree_set::BTreeSet},
prelude::*,
};
type SignedPrecommit<Header> = finality_grandpa::SignedPrecommit<
<Header as HeaderT>::Hash,
<Header as HeaderT>::Number,
AuthoritySignature,
AuthorityId,
>;
/// Votes ancestries with useful methods.
#[derive(RuntimeDebug)]
pub struct AncestryChain<Header: HeaderT> {
/// We expect all forks in the ancestry chain to be descendants of base.
base: HeaderId<Header::Hash, Header::Number>,
/// Header hash => parent header hash mapping.
pub parents: BTreeMap<Header::Hash, Header::Hash>,
/// Hashes of headers that were not visited by `ancestry()`.
pub unvisited: BTreeSet<Header::Hash>,
}
impl<Header: HeaderT> AncestryChain<Header> {
/// Create new ancestry chain.
pub fn new(justification: &GrandpaJustification<Header>) -> AncestryChain<Header> {
let mut parents = BTreeMap::new();
let mut unvisited = BTreeSet::new();
for ancestor in &justification.votes_ancestries {
let hash = ancestor.hash();
let parent_hash = *ancestor.parent_hash();
parents.insert(hash, parent_hash);
unvisited.insert(hash);
}
AncestryChain { base: justification.commit_target_id(), parents, unvisited }
}
/// Returns a route if the precommit target block is a descendant of the `base` block.
pub fn ancestry(
&self,
precommit_target_hash: &Header::Hash,
precommit_target_number: &Header::Number,
) -> Option<Vec<Header::Hash>> {
if precommit_target_number < &self.base.number() {
return None
}
let mut route = vec![];
let mut current_hash = *precommit_target_hash;
loop {
if current_hash == self.base.hash() {
break
}
current_hash = match self.parents.get(&current_hash) {
Some(parent_hash) => {
let is_visited_before = self.unvisited.get(&current_hash).is_none();
if is_visited_before {
// If the current header has been visited in a previous call, it is a
// descendent of `base` (we assume that the previous call was successful).
return Some(route)
}
route.push(current_hash);
*parent_hash
},
None => return None,
};
}
Some(route)
}
fn mark_route_as_visited(&mut self, route: Vec<Header::Hash>) {
for hash in route {
self.unvisited.remove(&hash);
}
}
fn is_fully_visited(&self) -> bool {
self.unvisited.is_empty()
}
}
/// Justification verification error.
#[derive(Eq, RuntimeDebug, PartialEq)]
pub enum Error {
/// Justification is finalizing unexpected header.
InvalidJustificationTarget,
/// Error validating a precommit
Precommit(PrecommitError),
/// 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.
RedundantVotesAncestries,
}
/// Justification verification error.
#[derive(Eq, RuntimeDebug, PartialEq)]
pub enum PrecommitError {
/// Justification contains redundant votes.
RedundantAuthorityVote,
/// Justification contains unknown authority precommit.
UnknownAuthorityVote,
/// Justification contains duplicate authority precommit.
DuplicateAuthorityVote,
/// The authority has provided an invalid signature.
InvalidAuthoritySignature,
/// The justification contains precommit for header that is not a descendant of the commit
/// header.
UnrelatedAncestryVote,
}
enum IterationFlow {
Run,
Skip,
}
/// Verification callbacks.
trait JustificationVerifier<Header: HeaderT> {
fn process_redundant_vote(
&mut self,
precommit_idx: usize,
) -> Result<IterationFlow, PrecommitError>;
fn process_known_authority_vote(
&mut self,
precommit_idx: usize,
signed: &SignedPrecommit<Header>,
) -> Result<IterationFlow, PrecommitError>;
fn process_unknown_authority_vote(
&mut self,
precommit_idx: usize,
) -> Result<(), PrecommitError>;
fn process_unrelated_ancestry_vote(
&mut self,
precommit_idx: usize,
) -> Result<IterationFlow, PrecommitError>;
fn process_invalid_signature_vote(
&mut self,
precommit_idx: usize,
) -> Result<(), PrecommitError>;
fn process_valid_vote(&mut self, signed: &SignedPrecommit<Header>);
/// Called when there are redundant headers in the votes ancestries.
fn process_redundant_votes_ancestries(
&mut self,
redundant_votes_ancestries: BTreeSet<Header::Hash>,
) -> Result<(), Error>;
fn verify_justification(
&mut self,
finalized_target: (Header::Hash, Header::Number),
authorities_set_id: SetId,
authorities_set: &VoterSet<AuthorityId>,
justification: &GrandpaJustification<Header>,
) -> Result<(), Error> {
// 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 threshold = authorities_set.threshold().get();
let mut chain = AncestryChain::new(justification);
let mut signature_buffer = Vec::new();
let mut cumulative_weight = 0u64;
for (precommit_idx, signed) in justification.commit.precommits.iter().enumerate() {
if cumulative_weight >= threshold {
let action =
self.process_redundant_vote(precommit_idx).map_err(Error::Precommit)?;
if matches!(action, IterationFlow::Skip) {
continue
}
}
// authority must be in the set
let authority_info = match authorities_set.get(&signed.id) {
Some(authority_info) => {
// The implementer may want to do extra checks here.
// For example to see if the authority has already voted in the same round.
let action = self
.process_known_authority_vote(precommit_idx, signed)
.map_err(Error::Precommit)?;
if matches!(action, IterationFlow::Skip) {
continue
}
authority_info
},
None => {
self.process_unknown_authority_vote(precommit_idx).map_err(Error::Precommit)?;
continue
},
};
// all precommits must be descendants of the target block
let maybe_route =
chain.ancestry(&signed.precommit.target_hash, &signed.precommit.target_number);
if maybe_route.is_none() {
let action = self
.process_unrelated_ancestry_vote(precommit_idx)
.map_err(Error::Precommit)?;
if matches!(action, IterationFlow::Skip) {
continue
}
}
// verify authority signature
if !sp_consensus_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,
) {
self.process_invalid_signature_vote(precommit_idx).map_err(Error::Precommit)?;
continue
}
// now we can count the vote since we know that it is valid
self.process_valid_vote(signed);
if let Some(route) = maybe_route {
chain.mark_route_as_visited(route);
cumulative_weight = cumulative_weight.saturating_add(authority_info.weight().get());
}
}
// check that the cumulative weight of validators that voted for the justification target
// (or one of its descendents) is larger than the required threshold.
if cumulative_weight < threshold {
return Err(Error::TooLowCumulativeWeight)
}
// check that there are no extra headers in the justification
if !chain.is_fully_visited() {
self.process_redundant_votes_ancestries(chain.unvisited)?;
}
Ok(())
}
}
@@ -0,0 +1,132 @@
// Copyright 2019-2023 Parity Technologies (UK) Ltd.
// This file is part of Parity Bridges Common.
// Parity Bridges Common 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.
// Parity Bridges Common 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 Parity Bridges Common. If not, see <http://www.gnu.org/licenses/>.
//! Logic for optimizing GRANDPA Finality Proofs.
use crate::justification::{
verification::{Error, JustificationVerifier, PrecommitError},
GrandpaJustification,
};
use crate::justification::verification::{IterationFlow, SignedPrecommit};
use finality_grandpa::voter_set::VoterSet;
use sp_consensus_grandpa::{AuthorityId, SetId};
use sp_runtime::traits::Header as HeaderT;
use sp_std::{collections::btree_set::BTreeSet, prelude::*};
// Verification callbacks for justification optimization.
struct JustificationOptimizer<Header: HeaderT> {
votes: BTreeSet<AuthorityId>,
extra_precommits: Vec<usize>,
redundant_votes_ancestries: BTreeSet<Header::Hash>,
}
impl<Header: HeaderT> JustificationOptimizer<Header> {
fn optimize(self, justification: &mut GrandpaJustification<Header>) {
for invalid_precommit_idx in self.extra_precommits.into_iter().rev() {
justification.commit.precommits.remove(invalid_precommit_idx);
}
if !self.redundant_votes_ancestries.is_empty() {
justification
.votes_ancestries
.retain(|header| !self.redundant_votes_ancestries.contains(&header.hash()))
}
}
}
impl<Header: HeaderT> JustificationVerifier<Header> for JustificationOptimizer<Header> {
fn process_redundant_vote(
&mut self,
precommit_idx: usize,
) -> Result<IterationFlow, PrecommitError> {
self.extra_precommits.push(precommit_idx);
Ok(IterationFlow::Skip)
}
fn process_known_authority_vote(
&mut self,
precommit_idx: usize,
signed: &SignedPrecommit<Header>,
) -> Result<IterationFlow, PrecommitError> {
// Skip duplicate votes
if self.votes.contains(&signed.id) {
self.extra_precommits.push(precommit_idx);
return Ok(IterationFlow::Skip)
}
Ok(IterationFlow::Run)
}
fn process_unknown_authority_vote(
&mut self,
precommit_idx: usize,
) -> Result<(), PrecommitError> {
self.extra_precommits.push(precommit_idx);
Ok(())
}
fn process_unrelated_ancestry_vote(
&mut self,
precommit_idx: usize,
) -> Result<IterationFlow, PrecommitError> {
self.extra_precommits.push(precommit_idx);
Ok(IterationFlow::Skip)
}
fn process_invalid_signature_vote(
&mut self,
precommit_idx: usize,
) -> Result<(), PrecommitError> {
self.extra_precommits.push(precommit_idx);
Ok(())
}
fn process_valid_vote(&mut self, signed: &SignedPrecommit<Header>) {
self.votes.insert(signed.id.clone());
}
fn process_redundant_votes_ancestries(
&mut self,
redundant_votes_ancestries: BTreeSet<Header::Hash>,
) -> Result<(), Error> {
self.redundant_votes_ancestries = redundant_votes_ancestries;
Ok(())
}
}
/// Verify and optimize given justification by removing unknown and duplicate votes.
pub fn verify_and_optimize_justification<Header: HeaderT>(
finalized_target: (Header::Hash, Header::Number),
authorities_set_id: SetId,
authorities_set: &VoterSet<AuthorityId>,
justification: &mut GrandpaJustification<Header>,
) -> Result<(), Error> {
let mut optimizer = JustificationOptimizer {
votes: BTreeSet::new(),
extra_precommits: vec![],
redundant_votes_ancestries: Default::default(),
};
optimizer.verify_justification(
finalized_target,
authorities_set_id,
authorities_set,
justification,
)?;
optimizer.optimize(justification);
Ok(())
}
@@ -0,0 +1,106 @@
// Copyright 2019-2023 Parity Technologies (UK) Ltd.
// This file is part of Parity Bridges Common.
// Parity Bridges Common 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.
// Parity Bridges Common 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 Parity Bridges Common. If not, see <http://www.gnu.org/licenses/>.
//! Logic for checking if GRANDPA Finality Proofs are valid and optimal.
use crate::justification::{
verification::{Error, JustificationVerifier, PrecommitError},
GrandpaJustification,
};
use crate::justification::verification::{IterationFlow, SignedPrecommit};
use finality_grandpa::voter_set::VoterSet;
use sp_consensus_grandpa::{AuthorityId, SetId};
use sp_runtime::traits::Header as HeaderT;
use sp_std::collections::btree_set::BTreeSet;
/// Verification callbacks that reject all unknown, duplicate or redundant votes.
struct StrictJustificationVerifier {
votes: BTreeSet<AuthorityId>,
}
impl<Header: HeaderT> JustificationVerifier<Header> for StrictJustificationVerifier {
fn process_redundant_vote(
&mut self,
_precommit_idx: usize,
) -> Result<IterationFlow, PrecommitError> {
Err(PrecommitError::RedundantAuthorityVote)
}
fn process_known_authority_vote(
&mut self,
_precommit_idx: usize,
signed: &SignedPrecommit<Header>,
) -> Result<IterationFlow, PrecommitError> {
if self.votes.contains(&signed.id) {
// 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
return Err(PrecommitError::DuplicateAuthorityVote)
}
Ok(IterationFlow::Run)
}
fn process_unknown_authority_vote(
&mut self,
_precommit_idx: usize,
) -> Result<(), PrecommitError> {
Err(PrecommitError::UnknownAuthorityVote)
}
fn process_unrelated_ancestry_vote(
&mut self,
_precommit_idx: usize,
) -> Result<IterationFlow, PrecommitError> {
Err(PrecommitError::UnrelatedAncestryVote)
}
fn process_invalid_signature_vote(
&mut self,
_precommit_idx: usize,
) -> Result<(), PrecommitError> {
Err(PrecommitError::InvalidAuthoritySignature)
}
fn process_valid_vote(&mut self, signed: &SignedPrecommit<Header>) {
self.votes.insert(signed.id.clone());
}
fn process_redundant_votes_ancestries(
&mut self,
_redundant_votes_ancestries: BTreeSet<Header::Hash>,
) -> Result<(), Error> {
Err(Error::RedundantVotesAncestries)
}
}
/// 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> {
let mut verifier = StrictJustificationVerifier { votes: BTreeSet::new() };
verifier.verify_justification(
finalized_target,
authorities_set_id,
authorities_set,
justification,
)
}
@@ -21,7 +21,7 @@
use bp_runtime::{ use bp_runtime::{
BasicOperatingMode, Chain, HashOf, HasherOf, HeaderOf, RawStorageProof, StorageProofChecker, BasicOperatingMode, Chain, HashOf, HasherOf, HeaderOf, RawStorageProof, StorageProofChecker,
StorageProofError, StorageProofError, UnderlyingChainProvider,
}; };
use codec::{Codec, Decode, Encode, EncodeLike, MaxEncodedLen}; use codec::{Codec, Decode, Encode, EncodeLike, MaxEncodedLen};
use core::{clone::Clone, cmp::Eq, default::Default, fmt::Debug}; use core::{clone::Clone, cmp::Eq, default::Default, fmt::Debug};
@@ -172,6 +172,15 @@ impl<Number: Codec> ConsensusLogReader for GrandpaConsensusLogReader<Number> {
} }
} }
/// The Grandpa-related info associated to a header.
#[derive(Encode, Decode, Debug, PartialEq, Clone, TypeInfo)]
pub struct HeaderGrandpaInfo<Header: HeaderT> {
/// The header justification
pub justification: justification::GrandpaJustification<Header>,
/// The authority set introduced by the header.
pub authority_set: Option<AuthoritySet>,
}
/// A minimized version of `pallet-bridge-grandpa::Call` that can be used without a runtime. /// A minimized version of `pallet-bridge-grandpa::Call` that can be used without a runtime.
#[derive(Encode, Decode, Debug, PartialEq, Eq, Clone, TypeInfo)] #[derive(Encode, Decode, Debug, PartialEq, Eq, Clone, TypeInfo)]
#[allow(non_camel_case_types)] #[allow(non_camel_case_types)]
@@ -234,3 +243,17 @@ pub trait ChainWithGrandpa: Chain {
/// refund amount and doing calls which exceed the limit, may be costly to submitter. /// refund amount and doing calls which exceed the limit, may be costly to submitter.
const AVERAGE_HEADER_SIZE_IN_JUSTIFICATION: u32; const AVERAGE_HEADER_SIZE_IN_JUSTIFICATION: u32;
} }
/// A trait that provides the type of the underlying `ChainWithGrandpa`.
pub trait UnderlyingChainWithGrandpaProvider: UnderlyingChainProvider {
/// Underlying `ChainWithGrandpa` type.
type ChainWithGrandpa: ChainWithGrandpa;
}
impl<T> UnderlyingChainWithGrandpaProvider for T
where
T: UnderlyingChainProvider,
T::Chain: ChainWithGrandpa,
{
type ChainWithGrandpa = T::Chain;
}
@@ -21,7 +21,9 @@
//! Some of tests in this module may partially duplicate tests from `justification.rs`, //! Some of tests in this module may partially duplicate tests from `justification.rs`,
//! but their purpose is different. //! but their purpose is different.
use bp_header_chain::justification::{verify_justification, Error, GrandpaJustification}; use bp_header_chain::justification::{
verify_justification, GrandpaJustification, JustificationVerificationError, PrecommitError,
};
use bp_test_utils::{ use bp_test_utils::{
header_id, make_justification_for_header, signed_precommit, test_header, Account, header_id, make_justification_for_header, signed_precommit, test_header, Account,
JustificationGeneratorParams, ALICE, BOB, CHARLIE, DAVE, EVE, FERDIE, TEST_GRANDPA_SET_ID, JustificationGeneratorParams, ALICE, BOB, CHARLIE, DAVE, EVE, FERDIE, TEST_GRANDPA_SET_ID,
@@ -85,13 +87,6 @@ fn minimal_accounts_set() -> Vec<(Account, AuthorityWeight)> {
vec![(ALICE, 1), (BOB, 1), (CHARLIE, 1), (DAVE, 1)] vec![(ALICE, 1), (BOB, 1), (CHARLIE, 1), (DAVE, 1)]
} }
/// Get a minimal subset of GRANDPA authorities that have enough cumulative vote weight to justify a
/// header finality.
pub fn minimal_voter_set() -> VoterSet<AuthorityId> {
VoterSet::new(minimal_accounts_set().iter().map(|(id, w)| (AuthorityId::from(*id), *w)))
.unwrap()
}
/// Make a valid GRANDPA justification with sensible defaults. /// Make a valid GRANDPA justification with sensible defaults.
pub fn make_default_justification(header: &TestHeader) -> GrandpaJustification<TestHeader> { pub fn make_default_justification(header: &TestHeader) -> GrandpaJustification<TestHeader> {
make_justification_for_header(JustificationGeneratorParams { make_justification_for_header(JustificationGeneratorParams {
@@ -124,7 +119,7 @@ fn same_result_when_precommit_target_has_lower_number_than_commit_target() {
&full_voter_set(), &full_voter_set(),
&justification, &justification,
), ),
Err(Error::UnrelatedAncestryVote), Err(JustificationVerificationError::Precommit(PrecommitError::UnrelatedAncestryVote)),
); );
// original implementation returns `Ok(validation_result)` // original implementation returns `Ok(validation_result)`
@@ -157,7 +152,7 @@ fn same_result_when_precommit_target_is_not_descendant_of_commit_target() {
&full_voter_set(), &full_voter_set(),
&justification, &justification,
), ),
Err(Error::UnrelatedAncestryVote), Err(JustificationVerificationError::Precommit(PrecommitError::UnrelatedAncestryVote)),
); );
// original implementation returns `Ok(validation_result)` // original implementation returns `Ok(validation_result)`
@@ -191,7 +186,7 @@ fn same_result_when_there_are_not_enough_cumulative_weight_to_finalize_commit_ta
&full_voter_set(), &full_voter_set(),
&justification, &justification,
), ),
Err(Error::TooLowCumulativeWeight), Err(JustificationVerificationError::TooLowCumulativeWeight),
); );
// original implementation returns `Ok(validation_result)` // original implementation returns `Ok(validation_result)`
// with `validation_result.is_valid() == false`. // with `validation_result.is_valid() == false`.
@@ -229,7 +224,7 @@ fn different_result_when_justification_contains_duplicate_vote() {
&full_voter_set(), &full_voter_set(),
&justification, &justification,
), ),
Err(Error::DuplicateAuthorityVote), Err(JustificationVerificationError::Precommit(PrecommitError::DuplicateAuthorityVote)),
); );
// original implementation returns `Ok(validation_result)` // original implementation returns `Ok(validation_result)`
// with `validation_result.is_valid() == true`. // with `validation_result.is_valid() == true`.
@@ -270,7 +265,7 @@ fn different_results_when_authority_equivocates_once_in_a_round() {
&full_voter_set(), &full_voter_set(),
&justification, &justification,
), ),
Err(Error::DuplicateAuthorityVote), Err(JustificationVerificationError::Precommit(PrecommitError::DuplicateAuthorityVote)),
); );
// original implementation returns `Ok(validation_result)` // original implementation returns `Ok(validation_result)`
// with `validation_result.is_valid() == true`. // with `validation_result.is_valid() == true`.
@@ -323,7 +318,7 @@ fn different_results_when_authority_equivocates_twice_in_a_round() {
&full_voter_set(), &full_voter_set(),
&justification, &justification,
), ),
Err(Error::DuplicateAuthorityVote), Err(JustificationVerificationError::Precommit(PrecommitError::DuplicateAuthorityVote)),
); );
// original implementation returns `Ok(validation_result)` // original implementation returns `Ok(validation_result)`
// with `validation_result.is_valid() == true`. // with `validation_result.is_valid() == true`.
@@ -362,7 +357,7 @@ fn different_results_when_there_are_more_than_enough_votes() {
&full_voter_set(), &full_voter_set(),
&justification, &justification,
), ),
Err(Error::RedundantVotesInJustification), Err(JustificationVerificationError::Precommit(PrecommitError::RedundantAuthorityVote)),
); );
// original implementation returns `Ok(validation_result)` // original implementation returns `Ok(validation_result)`
// with `validation_result.is_valid() == true`. // with `validation_result.is_valid() == true`.
@@ -403,7 +398,7 @@ fn different_results_when_there_is_a_vote_of_unknown_authority() {
&full_voter_set(), &full_voter_set(),
&justification, &justification,
), ),
Err(Error::UnknownAuthorityVote), Err(JustificationVerificationError::Precommit(PrecommitError::UnknownAuthorityVote)),
); );
// original implementation returns `Ok(validation_result)` // original implementation returns `Ok(validation_result)`
// with `validation_result.is_valid() == true`. // with `validation_result.is_valid() == true`.
@@ -0,0 +1,124 @@
// Copyright 2020-2021 Parity Technologies (UK) Ltd.
// This file is part of Parity Bridges Common.
// Parity Bridges Common 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.
// Parity Bridges Common 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 Parity Bridges Common. If not, see <http://www.gnu.org/licenses/>.
//! Tests for Grandpa equivocations collector code.
use bp_header_chain::justification::EquivocationsCollector;
use bp_test_utils::*;
use finality_grandpa::Precommit;
use sp_consensus_grandpa::EquivocationProof;
type TestHeader = sp_runtime::testing::Header;
#[test]
fn duplicate_votes_are_not_considered_equivocations() {
let voter_set = voter_set();
let base_justification = make_default_justification::<TestHeader>(&test_header(1));
let mut collector =
EquivocationsCollector::new(TEST_GRANDPA_SET_ID, &voter_set, &base_justification).unwrap();
collector.parse_justification(&base_justification.clone()).unwrap();
assert_eq!(collector.into_equivocation_proofs().len(), 0);
}
#[test]
fn equivocations_are_detected_in_base_justification_redundant_votes() {
let voter_set = voter_set();
let mut base_justification = make_default_justification::<TestHeader>(&test_header(1));
let first_vote = base_justification.commit.precommits[0].clone();
let equivocation = signed_precommit::<TestHeader>(
&ALICE,
header_id::<TestHeader>(1),
base_justification.round,
TEST_GRANDPA_SET_ID,
);
base_justification.commit.precommits.push(equivocation.clone());
let collector =
EquivocationsCollector::new(TEST_GRANDPA_SET_ID, &voter_set, &base_justification).unwrap();
assert_eq!(
collector.into_equivocation_proofs(),
vec![EquivocationProof::new(
1,
sp_consensus_grandpa::Equivocation::Precommit(finality_grandpa::Equivocation {
round_number: 1,
identity: ALICE.into(),
first: (
Precommit {
target_hash: first_vote.precommit.target_hash,
target_number: first_vote.precommit.target_number
},
first_vote.signature
),
second: (
Precommit {
target_hash: equivocation.precommit.target_hash,
target_number: equivocation.precommit.target_number
},
equivocation.signature
)
})
)]
);
}
#[test]
fn equivocations_are_detected_in_extra_justification_redundant_votes() {
let voter_set = voter_set();
let base_justification = make_default_justification::<TestHeader>(&test_header(1));
let first_vote = base_justification.commit.precommits[0].clone();
let mut extra_justification = base_justification.clone();
let equivocation = signed_precommit::<TestHeader>(
&ALICE,
header_id::<TestHeader>(1),
base_justification.round,
TEST_GRANDPA_SET_ID,
);
extra_justification.commit.precommits.push(equivocation.clone());
let mut collector =
EquivocationsCollector::new(TEST_GRANDPA_SET_ID, &voter_set, &base_justification).unwrap();
collector.parse_justification(&extra_justification).unwrap();
assert_eq!(
collector.into_equivocation_proofs(),
vec![EquivocationProof::new(
1,
sp_consensus_grandpa::Equivocation::Precommit(finality_grandpa::Equivocation {
round_number: 1,
identity: ALICE.into(),
first: (
Precommit {
target_hash: first_vote.precommit.target_hash,
target_number: first_vote.precommit.target_number
},
first_vote.signature
),
second: (
Precommit {
target_hash: equivocation.precommit.target_hash,
target_number: equivocation.precommit.target_number
},
equivocation.signature
)
})
)]
);
}
@@ -14,189 +14,15 @@
// You should have received a copy of the GNU General Public License // You should have received a copy of the GNU General Public License
// along with Parity Bridges Common. If not, see <http://www.gnu.org/licenses/>. // along with Parity Bridges Common. If not, see <http://www.gnu.org/licenses/>.
//! Tests for Grandpa Justification code. //! Tests for Grandpa Justification optimizer code.
use bp_header_chain::justification::{ use bp_header_chain::justification::verify_and_optimize_justification;
required_justification_precommits, verify_and_optimize_justification, verify_justification,
Error,
};
use bp_test_utils::*; use bp_test_utils::*;
use finality_grandpa::SignedPrecommit; use finality_grandpa::SignedPrecommit;
use sp_consensus_grandpa::AuthoritySignature; use sp_consensus_grandpa::AuthoritySignature;
type TestHeader = sp_runtime::testing::Header; type TestHeader = sp_runtime::testing::Header;
#[test]
fn valid_justification_accepted() {
let authorities = vec![(ALICE, 1), (BOB, 1), (CHARLIE, 1)];
let params = JustificationGeneratorParams {
header: test_header(1),
round: TEST_GRANDPA_ROUND,
set_id: TEST_GRANDPA_SET_ID,
authorities: authorities.clone(),
ancestors: 7,
forks: 3,
};
let justification = make_justification_for_header::<TestHeader>(params.clone());
assert_eq!(
verify_justification::<TestHeader>(
header_id::<TestHeader>(1),
TEST_GRANDPA_SET_ID,
&voter_set(),
&justification,
),
Ok(()),
);
assert_eq!(justification.commit.precommits.len(), authorities.len());
assert_eq!(justification.votes_ancestries.len(), params.ancestors as usize);
}
#[test]
fn valid_justification_accepted_with_single_fork() {
let params = JustificationGeneratorParams {
header: test_header(1),
round: TEST_GRANDPA_ROUND,
set_id: TEST_GRANDPA_SET_ID,
authorities: vec![(ALICE, 1), (BOB, 1), (CHARLIE, 1)],
ancestors: 5,
forks: 1,
};
assert_eq!(
verify_justification::<TestHeader>(
header_id::<TestHeader>(1),
TEST_GRANDPA_SET_ID,
&voter_set(),
&make_justification_for_header::<TestHeader>(params)
),
Ok(()),
);
}
#[test]
fn valid_justification_accepted_with_arbitrary_number_of_authorities() {
use finality_grandpa::voter_set::VoterSet;
use sp_consensus_grandpa::AuthorityId;
let n = 15;
let required_signatures = required_justification_precommits(n as _);
let authorities = accounts(n).iter().map(|k| (*k, 1)).collect::<Vec<_>>();
let params = JustificationGeneratorParams {
header: test_header(1),
round: TEST_GRANDPA_ROUND,
set_id: TEST_GRANDPA_SET_ID,
authorities: authorities.clone().into_iter().take(required_signatures as _).collect(),
ancestors: n.into(),
forks: required_signatures,
};
let authorities = authorities
.iter()
.map(|(id, w)| (AuthorityId::from(*id), *w))
.collect::<Vec<(AuthorityId, _)>>();
let voter_set = VoterSet::new(authorities).unwrap();
assert_eq!(
verify_justification::<TestHeader>(
header_id::<TestHeader>(1),
TEST_GRANDPA_SET_ID,
&voter_set,
&make_justification_for_header::<TestHeader>(params)
),
Ok(()),
);
}
#[test]
fn justification_with_invalid_target_rejected() {
assert_eq!(
verify_justification::<TestHeader>(
header_id::<TestHeader>(2),
TEST_GRANDPA_SET_ID,
&voter_set(),
&make_default_justification::<TestHeader>(&test_header(1)),
),
Err(Error::InvalidJustificationTarget),
);
}
#[test]
fn justification_with_invalid_commit_rejected() {
let mut justification = make_default_justification::<TestHeader>(&test_header(1));
justification.commit.precommits.clear();
assert_eq!(
verify_justification::<TestHeader>(
header_id::<TestHeader>(1),
TEST_GRANDPA_SET_ID,
&voter_set(),
&justification,
),
Err(Error::TooLowCumulativeWeight),
);
}
#[test]
fn justification_with_invalid_authority_signature_rejected() {
let mut justification = make_default_justification::<TestHeader>(&test_header(1));
justification.commit.precommits[0].signature =
sp_core::crypto::UncheckedFrom::unchecked_from([1u8; 64]);
assert_eq!(
verify_justification::<TestHeader>(
header_id::<TestHeader>(1),
TEST_GRANDPA_SET_ID,
&voter_set(),
&justification,
),
Err(Error::InvalidAuthoritySignature),
);
}
#[test]
fn justification_with_invalid_precommit_ancestry() {
let mut justification = make_default_justification::<TestHeader>(&test_header(1));
justification.votes_ancestries.push(test_header(10));
assert_eq!(
verify_justification::<TestHeader>(
header_id::<TestHeader>(1),
TEST_GRANDPA_SET_ID,
&voter_set(),
&justification,
),
Err(Error::RedundantVotesAncestries),
);
}
#[test]
fn justification_is_invalid_if_we_dont_meet_threshold() {
// Need at least three authorities to sign off or else the voter set threshold can't be reached
let authorities = vec![(ALICE, 1), (BOB, 1)];
let params = JustificationGeneratorParams {
header: test_header(1),
round: TEST_GRANDPA_ROUND,
set_id: TEST_GRANDPA_SET_ID,
authorities: authorities.clone(),
ancestors: 2 * authorities.len() as u32,
forks: 2,
};
assert_eq!(
verify_justification::<TestHeader>(
header_id::<TestHeader>(1),
TEST_GRANDPA_SET_ID,
&voter_set(),
&make_justification_for_header::<TestHeader>(params)
),
Err(Error::TooLowCumulativeWeight),
);
}
#[test] #[test]
fn optimizer_does_noting_with_minimal_justification() { fn optimizer_does_noting_with_minimal_justification() {
let mut justification = make_default_justification::<TestHeader>(&test_header(1)); let mut justification = make_default_justification::<TestHeader>(&test_header(1));
@@ -0,0 +1,196 @@
// Copyright 2020-2021 Parity Technologies (UK) Ltd.
// This file is part of Parity Bridges Common.
// Parity Bridges Common 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.
// Parity Bridges Common 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 Parity Bridges Common. If not, see <http://www.gnu.org/licenses/>.
//! Tests for Grandpa strict justification verifier code.
use bp_header_chain::justification::{
required_justification_precommits, verify_justification, JustificationVerificationError,
PrecommitError,
};
use bp_test_utils::*;
type TestHeader = sp_runtime::testing::Header;
#[test]
fn valid_justification_accepted() {
let authorities = vec![(ALICE, 1), (BOB, 1), (CHARLIE, 1)];
let params = JustificationGeneratorParams {
header: test_header(1),
round: TEST_GRANDPA_ROUND,
set_id: TEST_GRANDPA_SET_ID,
authorities: authorities.clone(),
ancestors: 7,
forks: 3,
};
let justification = make_justification_for_header::<TestHeader>(params.clone());
assert_eq!(
verify_justification::<TestHeader>(
header_id::<TestHeader>(1),
TEST_GRANDPA_SET_ID,
&voter_set(),
&justification,
),
Ok(()),
);
assert_eq!(justification.commit.precommits.len(), authorities.len());
assert_eq!(justification.votes_ancestries.len(), params.ancestors as usize);
}
#[test]
fn valid_justification_accepted_with_single_fork() {
let params = JustificationGeneratorParams {
header: test_header(1),
round: TEST_GRANDPA_ROUND,
set_id: TEST_GRANDPA_SET_ID,
authorities: vec![(ALICE, 1), (BOB, 1), (CHARLIE, 1)],
ancestors: 5,
forks: 1,
};
assert_eq!(
verify_justification::<TestHeader>(
header_id::<TestHeader>(1),
TEST_GRANDPA_SET_ID,
&voter_set(),
&make_justification_for_header::<TestHeader>(params)
),
Ok(()),
);
}
#[test]
fn valid_justification_accepted_with_arbitrary_number_of_authorities() {
use finality_grandpa::voter_set::VoterSet;
use sp_consensus_grandpa::AuthorityId;
let n = 15;
let required_signatures = required_justification_precommits(n as _);
let authorities = accounts(n).iter().map(|k| (*k, 1)).collect::<Vec<_>>();
let params = JustificationGeneratorParams {
header: test_header(1),
round: TEST_GRANDPA_ROUND,
set_id: TEST_GRANDPA_SET_ID,
authorities: authorities.clone().into_iter().take(required_signatures as _).collect(),
ancestors: n.into(),
forks: required_signatures,
};
let authorities = authorities
.iter()
.map(|(id, w)| (AuthorityId::from(*id), *w))
.collect::<Vec<(AuthorityId, _)>>();
let voter_set = VoterSet::new(authorities).unwrap();
assert_eq!(
verify_justification::<TestHeader>(
header_id::<TestHeader>(1),
TEST_GRANDPA_SET_ID,
&voter_set,
&make_justification_for_header::<TestHeader>(params)
),
Ok(()),
);
}
#[test]
fn justification_with_invalid_target_rejected() {
assert_eq!(
verify_justification::<TestHeader>(
header_id::<TestHeader>(2),
TEST_GRANDPA_SET_ID,
&voter_set(),
&make_default_justification::<TestHeader>(&test_header(1)),
),
Err(JustificationVerificationError::InvalidJustificationTarget),
);
}
#[test]
fn justification_with_invalid_commit_rejected() {
let mut justification = make_default_justification::<TestHeader>(&test_header(1));
justification.commit.precommits.clear();
assert_eq!(
verify_justification::<TestHeader>(
header_id::<TestHeader>(1),
TEST_GRANDPA_SET_ID,
&voter_set(),
&justification,
),
Err(JustificationVerificationError::TooLowCumulativeWeight),
);
}
#[test]
fn justification_with_invalid_authority_signature_rejected() {
let mut justification = make_default_justification::<TestHeader>(&test_header(1));
justification.commit.precommits[0].signature =
sp_core::crypto::UncheckedFrom::unchecked_from([1u8; 64]);
assert_eq!(
verify_justification::<TestHeader>(
header_id::<TestHeader>(1),
TEST_GRANDPA_SET_ID,
&voter_set(),
&justification,
),
Err(JustificationVerificationError::Precommit(PrecommitError::InvalidAuthoritySignature)),
);
}
#[test]
fn justification_with_invalid_precommit_ancestry() {
let mut justification = make_default_justification::<TestHeader>(&test_header(1));
justification.votes_ancestries.push(test_header(10));
assert_eq!(
verify_justification::<TestHeader>(
header_id::<TestHeader>(1),
TEST_GRANDPA_SET_ID,
&voter_set(),
&justification,
),
Err(JustificationVerificationError::RedundantVotesAncestries),
);
}
#[test]
fn justification_is_invalid_if_we_dont_meet_threshold() {
// Need at least three authorities to sign off or else the voter set threshold can't be reached
let authorities = vec![(ALICE, 1), (BOB, 1)];
let params = JustificationGeneratorParams {
header: test_header(1),
round: TEST_GRANDPA_ROUND,
set_id: TEST_GRANDPA_SET_ID,
authorities: authorities.clone(),
ancestors: 2 * authorities.len() as u32,
forks: 2,
};
assert_eq!(
verify_justification::<TestHeader>(
header_id::<TestHeader>(1),
TEST_GRANDPA_SET_ID,
&voter_set(),
&make_justification_for_header::<TestHeader>(params)
),
Err(JustificationVerificationError::TooLowCumulativeWeight),
);
}
@@ -0,0 +1,7 @@
mod justification {
mod equivocation;
mod optimizer;
mod strict;
}
mod implementation_match;
@@ -71,11 +71,10 @@ pub trait TargetHeaderChain<Payload, AccountId> {
/// Lane3 until some block, ...), then it may be built using this verifier. /// Lane3 until some block, ...), then it may be built using this verifier.
/// ///
/// Any fee requirements should also be enforced here. /// Any fee requirements should also be enforced here.
pub trait LaneMessageVerifier<SenderOrigin, Payload> { pub trait LaneMessageVerifier<Payload> {
/// Verify message payload and return Ok(()) if message is valid and allowed to be sent over the /// Verify message payload and return Ok(()) if message is valid and allowed to be sent over the
/// lane. /// lane.
fn verify_message( fn verify_message(
submitter: &SenderOrigin,
lane: &LaneId, lane: &LaneId,
outbound_data: &OutboundLaneData, outbound_data: &OutboundLaneData,
payload: &Payload, payload: &Payload,
@@ -124,32 +123,24 @@ pub struct SendMessageArtifacts {
} }
/// Messages bridge API to be used from other pallets. /// Messages bridge API to be used from other pallets.
pub trait MessagesBridge<SenderOrigin, Payload> { pub trait MessagesBridge<Payload> {
/// Error type. /// Error type.
type Error: Debug; type Error: Debug;
/// Send message over the bridge. /// Send message over the bridge.
/// ///
/// Returns unique message nonce or error if send has failed. /// Returns unique message nonce or error if send has failed.
fn send_message( fn send_message(lane: LaneId, message: Payload) -> Result<SendMessageArtifacts, Self::Error>;
sender: SenderOrigin,
lane: LaneId,
message: Payload,
) -> Result<SendMessageArtifacts, Self::Error>;
} }
/// Bridge that does nothing when message is being sent. /// Bridge that does nothing when message is being sent.
#[derive(Eq, RuntimeDebug, PartialEq)] #[derive(Eq, RuntimeDebug, PartialEq)]
pub struct NoopMessagesBridge; pub struct NoopMessagesBridge;
impl<SenderOrigin, Payload> MessagesBridge<SenderOrigin, Payload> for NoopMessagesBridge { impl<Payload> MessagesBridge<Payload> for NoopMessagesBridge {
type Error = &'static str; type Error = &'static str;
fn send_message( fn send_message(_lane: LaneId, _message: Payload) -> Result<SendMessageArtifacts, Self::Error> {
_sender: SenderOrigin,
_lane: LaneId,
_message: Payload,
) -> Result<SendMessageArtifacts, Self::Error> {
Ok(SendMessageArtifacts { nonce: 0 }) Ok(SendMessageArtifacts { nonce: 0 })
} }
} }
@@ -176,9 +167,8 @@ impl<Payload, AccountId> TargetHeaderChain<Payload, AccountId> for ForbidOutboun
} }
} }
impl<SenderOrigin, Payload> LaneMessageVerifier<SenderOrigin, Payload> for ForbidOutboundMessages { impl<Payload> LaneMessageVerifier<Payload> for ForbidOutboundMessages {
fn verify_message( fn verify_message(
_submitter: &SenderOrigin,
_lane: &LaneId, _lane: &LaneId,
_outbound_data: &OutboundLaneData, _outbound_data: &OutboundLaneData,
_payload: &Payload, _payload: &Payload,
@@ -279,10 +279,11 @@ pub type TransactionEraOf<C> = crate::TransactionEra<BlockNumberOf<C>, HashOf<C>
/// - `<ThisChain>FinalityApi` /// - `<ThisChain>FinalityApi`
/// - constants that are stringified names of runtime API methods: /// - constants that are stringified names of runtime API methods:
/// - `BEST_FINALIZED_<THIS_CHAIN>_HEADER_METHOD` /// - `BEST_FINALIZED_<THIS_CHAIN>_HEADER_METHOD`
/// - `<THIS_CHAIN>_ACCEPTED_<CONSENSUS>_FINALITY_PROOFS_METHOD`
/// The name of the chain has to be specified in snake case (e.g. `rialto_parachain`). /// The name of the chain has to be specified in snake case (e.g. `rialto_parachain`).
#[macro_export] #[macro_export]
macro_rules! decl_bridge_finality_runtime_apis { macro_rules! decl_bridge_finality_runtime_apis {
($chain: ident) => { ($chain: ident $(, $consensus: ident => $justification_type: ty)?) => {
bp_runtime::paste::item! { bp_runtime::paste::item! {
mod [<$chain _finality_api>] { mod [<$chain _finality_api>] {
use super::*; use super::*;
@@ -291,6 +292,13 @@ macro_rules! decl_bridge_finality_runtime_apis {
pub const [<BEST_FINALIZED_ $chain:upper _HEADER_METHOD>]: &str = pub const [<BEST_FINALIZED_ $chain:upper _HEADER_METHOD>]: &str =
stringify!([<$chain:camel FinalityApi_best_finalized>]); stringify!([<$chain:camel FinalityApi_best_finalized>]);
$(
/// Name of the `<ThisChain>FinalityApi::accepted_<consensus>_finality_proofs`
/// runtime method.
pub const [<$chain:upper _SYNCED_HEADERS_ $consensus:upper _INFO_METHOD>]: &str =
stringify!([<$chain:camel FinalityApi_synced_headers_ $consensus:lower _info>]);
)?
sp_api::decl_runtime_apis! { sp_api::decl_runtime_apis! {
/// API for querying information about the finalized chain headers. /// API for querying information about the finalized chain headers.
/// ///
@@ -299,6 +307,12 @@ macro_rules! decl_bridge_finality_runtime_apis {
pub trait [<$chain:camel FinalityApi>] { pub trait [<$chain:camel FinalityApi>] {
/// Returns number and hash of the best finalized header known to the bridge module. /// Returns number and hash of the best finalized header known to the bridge module.
fn best_finalized() -> Option<bp_runtime::HeaderId<Hash, BlockNumber>>; fn best_finalized() -> Option<bp_runtime::HeaderId<Hash, BlockNumber>>;
$(
/// Returns the justifications accepted in the current block.
fn [<synced_headers_ $consensus:lower _info>](
) -> Vec<$justification_type>;
)?
} }
} }
} }
@@ -306,6 +320,9 @@ macro_rules! decl_bridge_finality_runtime_apis {
pub use [<$chain _finality_api>]::*; pub use [<$chain _finality_api>]::*;
} }
}; };
($chain: ident, grandpa) => {
decl_bridge_finality_runtime_apis!($chain, grandpa => bp_header_chain::HeaderGrandpaInfo<Header>);
};
} }
/// Convenience macro that declares bridge messages runtime apis and related constants for a chain. /// Convenience macro that declares bridge messages runtime apis and related constants for a chain.
@@ -376,8 +393,8 @@ macro_rules! decl_bridge_messages_runtime_apis {
/// The name of the chain has to be specified in snake case (e.g. `rialto_parachain`). /// The name of the chain has to be specified in snake case (e.g. `rialto_parachain`).
#[macro_export] #[macro_export]
macro_rules! decl_bridge_runtime_apis { macro_rules! decl_bridge_runtime_apis {
($chain: ident) => { ($chain: ident $(, $consensus: ident)?) => {
bp_runtime::decl_bridge_finality_runtime_apis!($chain); bp_runtime::decl_bridge_finality_runtime_apis!($chain $(, $consensus)?);
bp_runtime::decl_bridge_messages_runtime_apis!($chain); bp_runtime::decl_bridge_messages_runtime_apis!($chain);
}; };
} }
@@ -77,7 +77,7 @@ pub const KUSAMA_CHAIN_ID: ChainId = *b"ksma";
/// Westend chain id. /// Westend chain id.
pub const WESTEND_CHAIN_ID: ChainId = *b"wend"; pub const WESTEND_CHAIN_ID: ChainId = *b"wend";
/// AssetHubWestend chain id. /// `AssetHubWestmint` chain id.
pub const ASSET_HUB_WESTEND_CHAIN_ID: ChainId = *b"ahwe"; pub const ASSET_HUB_WESTEND_CHAIN_ID: ChainId = *b"ahwe";
/// Rococo chain id. /// Rococo chain id.
@@ -74,6 +74,7 @@ parachains-common = { path = "../../../../parachains/common", default-features =
# Bridges # Bridges
bp-bridge-hub-rococo = { path = "../../../../bridges/primitives/chain-bridge-hub-rococo", default-features = false } bp-bridge-hub-rococo = { path = "../../../../bridges/primitives/chain-bridge-hub-rococo", default-features = false }
bp-bridge-hub-wococo = { path = "../../../../bridges/primitives/chain-bridge-hub-wococo", default-features = false } bp-bridge-hub-wococo = { path = "../../../../bridges/primitives/chain-bridge-hub-wococo", default-features = false }
bp-header-chain = { path = "../../../../bridges/primitives/header-chain", default-features = false }
bp-messages = { path = "../../../../bridges/primitives/messages", default-features = false } bp-messages = { path = "../../../../bridges/primitives/messages", default-features = false }
bp-parachains = { path = "../../../../bridges/primitives/parachains", default-features = false } bp-parachains = { path = "../../../../bridges/primitives/parachains", default-features = false }
bp-polkadot-core = { path = "../../../../bridges/primitives/polkadot-core", default-features = false } bp-polkadot-core = { path = "../../../../bridges/primitives/polkadot-core", default-features = false }
@@ -100,6 +101,7 @@ default = [
std = [ std = [
"bp-bridge-hub-rococo/std", "bp-bridge-hub-rococo/std",
"bp-bridge-hub-wococo/std", "bp-bridge-hub-wococo/std",
"bp-header-chain/std",
"bp-messages/std", "bp-messages/std",
"bp-parachains/std", "bp-parachains/std",
"bp-polkadot-core/std", "bp-polkadot-core/std",
@@ -78,13 +78,6 @@ impl XcmBlobHauler for ToBridgeHubWococoXcmBlobHauler {
type MessageSender = type MessageSender =
pallet_bridge_messages::Pallet<Runtime, WithBridgeHubWococoMessagesInstance>; pallet_bridge_messages::Pallet<Runtime, WithBridgeHubWococoMessagesInstance>;
type MessageSenderOrigin = super::RuntimeOrigin;
fn message_sender_origin() -> Self::MessageSenderOrigin {
pallet_xcm::Origin::from(MultiLocation::new(1, crate::xcm_config::UniversalLocation::get()))
.into()
}
fn xcm_lane() -> LaneId { fn xcm_lane() -> LaneId {
DEFAULT_XCM_LANE_TO_BRIDGE_HUB_WOCOCO DEFAULT_XCM_LANE_TO_BRIDGE_HUB_WOCOCO
} }
@@ -78,13 +78,6 @@ impl XcmBlobHauler for ToBridgeHubRococoXcmBlobHauler {
type MessageSender = type MessageSender =
pallet_bridge_messages::Pallet<Runtime, WithBridgeHubRococoMessagesInstance>; pallet_bridge_messages::Pallet<Runtime, WithBridgeHubRococoMessagesInstance>;
type MessageSenderOrigin = super::RuntimeOrigin;
fn message_sender_origin() -> super::RuntimeOrigin {
pallet_xcm::Origin::from(MultiLocation::new(1, crate::xcm_config::UniversalLocation::get()))
.into()
}
fn xcm_lane() -> LaneId { fn xcm_lane() -> LaneId {
DEFAULT_XCM_LANE_TO_BRIDGE_HUB_ROCOCO DEFAULT_XCM_LANE_TO_BRIDGE_HUB_ROCOCO
} }
@@ -783,12 +783,20 @@ impl_runtime_apis! {
fn best_finalized() -> Option<HeaderId<bp_rococo::Hash, bp_rococo::BlockNumber>> { fn best_finalized() -> Option<HeaderId<bp_rococo::Hash, bp_rococo::BlockNumber>> {
BridgeRococoGrandpa::best_finalized() BridgeRococoGrandpa::best_finalized()
} }
fn synced_headers_grandpa_info(
) -> Vec<bp_header_chain::HeaderGrandpaInfo<bp_rococo::Header>> {
BridgeRococoGrandpa::synced_headers_grandpa_info()
}
} }
impl bp_wococo::WococoFinalityApi<Block> for Runtime { impl bp_wococo::WococoFinalityApi<Block> for Runtime {
fn best_finalized() -> Option<HeaderId<bp_wococo::Hash, bp_wococo::BlockNumber>> { fn best_finalized() -> Option<HeaderId<bp_wococo::Hash, bp_wococo::BlockNumber>> {
BridgeWococoGrandpa::best_finalized() BridgeWococoGrandpa::best_finalized()
} }
fn synced_headers_grandpa_info(
) -> Vec<bp_header_chain::HeaderGrandpaInfo<bp_wococo::Header>> {
BridgeWococoGrandpa::synced_headers_grandpa_info()
}
} }