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",
"frame-support",
"sp-api",
"sp-std",
]
[[package]]
@@ -1310,6 +1311,7 @@ dependencies = [
"bp-runtime",
"frame-support",
"sp-api",
"sp-std",
]
[[package]]
@@ -1472,6 +1474,7 @@ version = "0.1.0"
dependencies = [
"bp-bridge-hub-rococo",
"bp-bridge-hub-wococo",
"bp-header-chain",
"bp-messages",
"bp-parachains",
"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>>;
/// Type of balances that is used on the chain.
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.
pub mod source {
@@ -138,17 +136,11 @@ pub mod source {
#[derive(RuntimeDebug)]
pub struct FromThisChainMessageVerifier<B>(PhantomData<B>);
impl<B> LaneMessageVerifier<OriginOf<ThisChain<B>>, FromThisChainMessagePayload>
for FromThisChainMessageVerifier<B>
impl<B> LaneMessageVerifier<FromThisChainMessagePayload> for FromThisChainMessageVerifier<B>
where
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(
_submitter: &OriginOf<ThisChain<B>>,
_lane: &LaneId,
_lane_outbound_data: &OutboundLaneData,
_payload: &FromThisChainMessagePayload,
@@ -110,12 +110,7 @@ impl<BlobDispatcher: DispatchBlob, Weights: MessagesPalletWeights> MessageDispat
/// one side, where on the other it can be dispatched by [`XcmBlobMessageDispatch`].
pub trait XcmBlobHauler {
/// Runtime message sender adapter.
type MessageSender: MessagesBridge<Self::MessageSenderOrigin, 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;
type MessageSender: MessagesBridge<XcmAsPlainPayload>;
/// Return message lane (as "point-to-point link") used to deliver XCM messages.
fn xcm_lane() -> LaneId;
@@ -124,12 +119,10 @@ pub trait XcmBlobHauler {
/// 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.
pub struct XcmBlobHaulerAdapter<XcmBlobHauler>(sp_std::marker::PhantomData<XcmBlobHauler>);
impl<HaulerOrigin, H: XcmBlobHauler<MessageSenderOrigin = HaulerOrigin>> HaulBlob
for XcmBlobHaulerAdapter<H>
{
impl<H: XcmBlobHauler> HaulBlob for XcmBlobHaulerAdapter<H> {
fn haul_blob(blob: sp_std::prelude::Vec<u8>) -> Result<(), HaulBlobError> {
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(|result| {
log::info!(
+70 -17
View File
@@ -39,8 +39,8 @@
pub use storage_types::StoredAuthoritySet;
use bp_header_chain::{
justification::GrandpaJustification, ChainWithGrandpa, GrandpaConsensusLogReader, HeaderChain,
InitializationData, StoredHeaderData, StoredHeaderDataBuilder,
justification::GrandpaJustification, AuthoritySet, ChainWithGrandpa, GrandpaConsensusLogReader,
HeaderChain, HeaderGrandpaInfo, InitializationData, StoredHeaderData, StoredHeaderDataBuilder,
};
use bp_runtime::{BlockNumberOf, HashOf, HasherOf, HeaderId, HeaderOf, OwnedBridgeModule};
use finality_grandpa::voter_set::VoterSet;
@@ -49,7 +49,7 @@ use sp_runtime::{
traits::{Header as HeaderT, Zero},
SaturatedConversion,
};
use sp_std::{boxed::Box, convert::TryInto};
use sp_std::{boxed::Box, convert::TryInto, prelude::*};
mod call_ext;
#[cfg(test)]
@@ -194,11 +194,12 @@ pub mod pallet {
let authority_set = <CurrentAuthoritySet<T, I>>::get();
let unused_proof_size = authority_set.unused_proof_size();
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)?;
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
Self::free_mandatory_headers_remaining() > 0 &&
// 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
.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 })
}
@@ -402,6 +410,8 @@ pub mod pallet {
UpdatedBestFinalizedHeader {
number: BridgedBlockNumber<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>(
header: &BridgedHeader<T, I>,
current_set_id: sp_consensus_grandpa::SetId,
) -> Result<bool, sp_runtime::DispatchError> {
let mut change_enacted = false;
) -> Result<Option<AuthoritySet>, DispatchError> {
// We don't support forced changes - at that point governance intervention is required.
ensure!(
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
// the change.
<CurrentAuthoritySet<T, I>>::put(&next_authorities);
change_enacted = true;
log::info!(
target: LOG_TARGET,
@@ -477,9 +484,11 @@ pub mod pallet {
current_set_id + 1,
next_authorities,
);
return Ok(Some(next_authorities.into()))
};
Ok(change_enacted)
Ok(None)
}
/// 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> {
/// Get the best finalized block number.
pub fn best_finalized_number() -> Option<BridgedBlockNumber<T, I>> {
BestFinalized::<T, I>::get().map(|id| id.number())
impl<T: Config<I>, I: 'static> Pallet<T, I>
where
<T as frame_system::Config>::RuntimeEvent: TryInto<Event<T, I>>,
{
/// Get the GRANDPA justifications accepted in the current block.
pub fn synced_headers_grandpa_info() -> Vec<HeaderGrandpaInfo<BridgedHeader<T, I>>> {
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 {
number: *header.number(),
hash: header.hash(),
grandpa_info: HeaderGrandpaInfo {
justification: justification.clone(),
authority_set: None,
},
}),
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(
RuntimeOrigin::signed(1),
Box::new(header.clone()),
justification,
justification.clone(),
);
assert_ok!(result);
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)
.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 codec::{Decode, Encode, MaxEncodedLen};
use frame_support::{traits::Get, BoundedVec, RuntimeDebugNoBound};
use frame_support::{traits::Get, BoundedVec, CloneNoBound, RuntimeDebugNoBound};
use scale_info::TypeInfo;
use sp_consensus_grandpa::{AuthorityId, AuthorityList, AuthorityWeight, SetId};
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.
#[derive(Clone, Decode, Encode, Eq, TypeInfo, MaxEncodedLen, RuntimeDebugNoBound)]
#[derive(CloneNoBound, Decode, Encode, Eq, TypeInfo, MaxEncodedLen, RuntimeDebugNoBound)]
#[scale_info(skip_type_params(T, I))]
pub struct StoredAuthoritySet<T: Config<I>, I: 'static> {
/// List of GRANDPA authorities for the current round.
+16 -57
View File
@@ -153,7 +153,7 @@ pub mod pallet {
/// Target header chain.
type TargetHeaderChain: TargetHeaderChain<Self::OutboundPayload, Self::AccountId>;
/// Message payload verifier.
type LaneMessageVerifier: LaneMessageVerifier<Self::RuntimeOrigin, Self::OutboundPayload>;
type LaneMessageVerifier: LaneMessageVerifier<Self::OutboundPayload>;
/// Delivery confirmation payments.
type DeliveryConfirmationPayments: DeliveryConfirmationPayments<Self::AccountId>;
@@ -643,8 +643,7 @@ pub mod pallet {
}
}
impl<T, I> bp_messages::source_chain::MessagesBridge<T::RuntimeOrigin, T::OutboundPayload>
for Pallet<T, I>
impl<T, I> bp_messages::source_chain::MessagesBridge<T::OutboundPayload> for Pallet<T, I>
where
T: Config<I>,
I: 'static,
@@ -652,17 +651,15 @@ where
type Error = sp_runtime::DispatchErrorWithPostInfo<PostDispatchInfo>;
fn send_message(
sender: T::RuntimeOrigin,
lane: LaneId,
message: T::OutboundPayload,
) -> Result<SendMessageArtifacts, Self::Error> {
crate::send_message::<T, I>(sender, lane, message)
crate::send_message::<T, I>(lane, message)
}
}
/// Function that actually sends message.
fn send_message<T: Config<I>, I: 'static>(
submitter: T::RuntimeOrigin,
lane_id: LaneId,
payload: T::OutboundPayload,
) -> sp_std::result::Result<
@@ -688,8 +685,7 @@ fn send_message<T: Config<I>, I: 'static>(
// now let's enforce any additional lane rules
let mut lane = outbound_lane::<T, I>(lane_id);
T::LaneMessageVerifier::verify_message(&submitter, &lane_id, &lane.data(), &payload).map_err(
|err| {
T::LaneMessageVerifier::verify_message(&lane_id, &lane.data(), &payload).map_err(|err| {
log::trace!(
target: LOG_TARGET,
"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)
},
)?;
})?;
// finally, save message in outbound storage and emit event
let encoded_payload = payload.encode();
@@ -915,7 +910,7 @@ mod tests {
let message_nonce =
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");
// check event with assigned nonce
@@ -982,11 +977,7 @@ mod tests {
));
assert_noop!(
send_message::<TestRuntime, ()>(
RuntimeOrigin::signed(1),
TEST_LANE_ID,
REGULAR_PAYLOAD,
),
send_message::<TestRuntime, ()>(TEST_LANE_ID, REGULAR_PAYLOAD,),
Error::<TestRuntime, ()>::NotOperatingNormally,
);
@@ -1036,11 +1027,7 @@ mod tests {
);
assert_noop!(
send_message::<TestRuntime, ()>(
RuntimeOrigin::signed(1),
TEST_LANE_ID,
REGULAR_PAYLOAD,
),
send_message::<TestRuntime, ()>(TEST_LANE_ID, REGULAR_PAYLOAD,),
Error::<TestRuntime, ()>::NotOperatingNormally,
);
@@ -1090,11 +1077,7 @@ mod tests {
.extra
.extend_from_slice(&[0u8; MAX_OUTBOUND_PAYLOAD_SIZE as usize]);
assert_noop!(
send_message::<TestRuntime, ()>(
RuntimeOrigin::signed(1),
TEST_LANE_ID,
message_payload.clone(),
),
send_message::<TestRuntime, ()>(TEST_LANE_ID, message_payload.clone(),),
Error::<TestRuntime, ()>::MessageRejectedByPallet(
VerificationError::MessageTooLarge
),
@@ -1105,11 +1088,7 @@ mod tests {
message_payload.extra.pop();
}
assert_eq!(message_payload.encoded_size() as u32, MAX_OUTBOUND_PAYLOAD_SIZE);
assert_ok!(send_message::<TestRuntime, ()>(
RuntimeOrigin::signed(1),
TEST_LANE_ID,
message_payload,
),);
assert_ok!(send_message::<TestRuntime, ()>(TEST_LANE_ID, message_payload,),);
})
}
@@ -1118,11 +1097,7 @@ mod tests {
run_test(|| {
// messages with this payload are rejected by target chain verifier
assert_noop!(
send_message::<TestRuntime, ()>(
RuntimeOrigin::signed(1),
TEST_LANE_ID,
PAYLOAD_REJECTED_BY_TARGET_CHAIN,
),
send_message::<TestRuntime, ()>(TEST_LANE_ID, PAYLOAD_REJECTED_BY_TARGET_CHAIN,),
Error::<TestRuntime, ()>::MessageRejectedByChainVerifier(VerificationError::Other(
mock::TEST_ERROR
)),
@@ -1137,7 +1112,7 @@ mod tests {
let mut message = REGULAR_PAYLOAD;
message.reject_by_lane_verifier = true;
assert_noop!(
send_message::<TestRuntime, ()>(RuntimeOrigin::signed(1), TEST_LANE_ID, message,),
send_message::<TestRuntime, ()>(TEST_LANE_ID, message,),
Error::<TestRuntime, ()>::MessageRejectedByLaneVerifier(VerificationError::Other(
mock::TEST_ERROR
)),
@@ -1293,16 +1268,8 @@ mod tests {
#[test]
fn receive_messages_delivery_proof_rewards_relayers() {
run_test(|| {
assert_ok!(send_message::<TestRuntime, ()>(
RuntimeOrigin::signed(1),
TEST_LANE_ID,
REGULAR_PAYLOAD,
));
assert_ok!(send_message::<TestRuntime, ()>(
RuntimeOrigin::signed(1),
TEST_LANE_ID,
REGULAR_PAYLOAD,
));
assert_ok!(send_message::<TestRuntime, ()>(TEST_LANE_ID, REGULAR_PAYLOAD,));
assert_ok!(send_message::<TestRuntime, ()>(TEST_LANE_ID, REGULAR_PAYLOAD,));
// this reports delivery of message 1 => reward is paid to TEST_RELAYER_A
let single_message_delivery_proof = TestMessagesDeliveryProof(Ok((
@@ -1899,11 +1866,7 @@ mod tests {
send_regular_message();
receive_messages_delivery_proof();
// send + receive confirmation for lane 2
assert_ok!(send_message::<TestRuntime, ()>(
RuntimeOrigin::signed(1),
TEST_LANE_ID_2,
REGULAR_PAYLOAD,
));
assert_ok!(send_message::<TestRuntime, ()>(TEST_LANE_ID_2, REGULAR_PAYLOAD,));
assert_ok!(Pallet::<TestRuntime>::receive_messages_delivery_proof(
RuntimeOrigin::signed(1),
TestMessagesDeliveryProof(Ok((
@@ -1979,11 +1942,7 @@ mod tests {
fn outbound_message_from_unconfigured_lane_is_rejected() {
run_test(|| {
assert_noop!(
send_message::<TestRuntime, ()>(
RuntimeOrigin::signed(1),
TEST_LANE_ID_3,
REGULAR_PAYLOAD,
),
send_message::<TestRuntime, ()>(TEST_LANE_ID_3, REGULAR_PAYLOAD,),
Error::<TestRuntime, ()>::InactiveOutboundLane,
);
});
+1 -2
View File
@@ -310,9 +310,8 @@ impl TargetHeaderChain<TestPayload, TestRelayer> for TestTargetHeaderChain {
#[derive(Debug, Default)]
pub struct TestLaneMessageVerifier;
impl LaneMessageVerifier<RuntimeOrigin, TestPayload> for TestLaneMessageVerifier {
impl LaneMessageVerifier<TestPayload> for TestLaneMessageVerifier {
fn verify_message(
_submitter: &RuntimeOrigin,
_lane: &LaneId,
_lane_outbound_data: &OutboundLaneData,
payload: &TestPayload,
+28 -8
View File
@@ -691,12 +691,13 @@ pub(crate) mod tests {
use super::*;
use crate::mock::{
run_test, test_relay_header, BigParachainHeader, RegularParachainHasher,
RegularParachainHeader, RuntimeEvent as TestEvent, RuntimeOrigin, TestRuntime,
UNTRACKED_PARACHAIN_ID,
RegularParachainHeader, RelayBlockHeader, RuntimeEvent as TestEvent, RuntimeOrigin,
TestRuntime, UNTRACKED_PARACHAIN_ID,
};
use bp_test_utils::prepare_parachain_heads_proof;
use codec::Encode;
use bp_header_chain::{justification::GrandpaJustification, HeaderGrandpaInfo};
use bp_parachains::{
BestParaHeadHash, BridgeParachainCall, ImportedParaHeadsKeyProvider, ParasInfoKeyProvider,
};
@@ -740,7 +741,10 @@ pub(crate) mod tests {
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(
0,
);
@@ -752,11 +756,11 @@ pub(crate) mod tests {
pallet_bridge_grandpa::Pallet::<TestRuntime, BridgesGrandpaPalletInstance>::submit_finality_proof(
RuntimeOrigin::signed(1),
Box::new(header),
justification,
justification.clone(),
)
);
hash
(hash, justification)
}
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
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_eq!(
ParasInfo::<TestRuntime>::get(ParaId(1)),
@@ -1032,6 +1036,10 @@ pub(crate) mod tests {
pallet_bridge_grandpa::Event::UpdatedBestFinalizedHeader {
number: 1,
hash: relay_1_hash,
grandpa_info: HeaderGrandpaInfo {
justification,
authority_set: None,
},
}
),
topics: vec![],
@@ -1149,7 +1157,7 @@ pub(crate) mod tests {
// try to import head#0 of parachain#1 at relay block#1
// => 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_eq!(ParasInfo::<TestRuntime>::get(ParaId(1)), Some(initial_best_head(1)));
assert_eq!(
@@ -1169,6 +1177,10 @@ pub(crate) mod tests {
pallet_bridge_grandpa::Event::UpdatedBestFinalizedHeader {
number: 1,
hash: relay_1_hash,
grandpa_info: HeaderGrandpaInfo {
justification,
authority_set: None,
}
}
),
topics: vec![],
@@ -1197,7 +1209,7 @@ pub(crate) mod tests {
initialize(state_root_5);
// 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_eq!(
ParasInfo::<TestRuntime>::get(ParaId(1)),
@@ -1218,6 +1230,10 @@ pub(crate) mod tests {
pallet_bridge_grandpa::Event::UpdatedBestFinalizedHeader {
number: 1,
hash: relay_1_hash,
grandpa_info: HeaderGrandpaInfo {
justification: justification.clone(),
authority_set: None,
}
}
),
topics: vec![],
@@ -1255,6 +1271,10 @@ pub(crate) mod tests {
pallet_bridge_grandpa::Event::UpdatedBestFinalizedHeader {
number: 1,
hash: relay_1_hash,
grandpa_info: HeaderGrandpaInfo {
justification,
authority_set: None,
}
}
),
topics: vec![],
@@ -29,7 +29,7 @@ use frame_support::{
sp_runtime::{MultiAddress, MultiSigner},
RuntimeDebug,
};
use sp_std::prelude::*;
use sp_std::prelude::Vec;
/// BridgeHubKusama parachain.
#[derive(RuntimeDebug)]
@@ -25,7 +25,7 @@ use bp_runtime::{
decl_bridge_finality_runtime_apis, decl_bridge_messages_runtime_apis, Chain, Parachain,
};
use frame_support::{dispatch::DispatchClass, RuntimeDebug};
use sp_std::prelude::*;
use sp_std::prelude::Vec;
/// BridgeHubPolkadot parachain.
#[derive(RuntimeDebug)]
@@ -29,7 +29,7 @@ use frame_support::{
sp_runtime::{MultiAddress, MultiSigner},
RuntimeDebug,
};
use sp_std::prelude::*;
use sp_std::prelude::Vec;
/// BridgeHubRococo parachain.
#[derive(RuntimeDebug)]
@@ -25,7 +25,7 @@ use bp_runtime::{
decl_bridge_finality_runtime_apis, decl_bridge_messages_runtime_apis, Chain, Parachain,
};
use frame_support::{dispatch::DispatchClass, RuntimeDebug};
use sp_std::prelude::*;
use sp_std::prelude::Vec;
/// BridgeHubWococo parachain.
#[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 }
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]
default = ["std"]
@@ -27,4 +28,5 @@ std = [
"bp-runtime/std",
"frame-support/std",
"sp-api/std",
"sp-std/std",
]
@@ -23,6 +23,7 @@ pub use bp_polkadot_core::*;
use bp_header_chain::ChainWithGrandpa;
use bp_runtime::{decl_bridge_finality_runtime_apis, Chain};
use frame_support::weights::Weight;
use sp_std::prelude::Vec;
/// Kusama Chain
pub struct Kusama;
@@ -69,4 +70,4 @@ pub const WITH_KUSAMA_GRANDPA_PALLET_NAME: &str = "BridgeKusamaGrandpa";
/// reserve.
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 }
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]
default = ["std"]
@@ -27,4 +28,5 @@ std = [
"bp-runtime/std",
"frame-support/std",
"sp-api/std",
"sp-std/std",
]
@@ -23,6 +23,7 @@ pub use bp_polkadot_core::*;
use bp_header_chain::ChainWithGrandpa;
use bp_runtime::{decl_bridge_finality_runtime_apis, Chain};
use frame_support::weights::Weight;
use sp_std::prelude::Vec;
/// Polkadot Chain
pub struct Polkadot;
@@ -69,4 +70,4 @@ pub const WITH_POLKADOT_GRANDPA_PALLET_NAME: &str = "BridgePolkadotGrandpa";
/// reserve.
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
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 }
[features]
@@ -27,4 +28,5 @@ std = [
"bp-runtime/std",
"frame-support/std",
"sp-api/std",
"sp-std/std",
]
@@ -23,6 +23,7 @@ pub use bp_polkadot_core::*;
use bp_header_chain::ChainWithGrandpa;
use bp_runtime::{decl_bridge_finality_runtime_apis, Chain};
use frame_support::{parameter_types, weights::Weight};
use sp_std::prelude::Vec;
/// Rococo Chain
pub struct Rococo;
@@ -73,4 +74,4 @@ pub const WITH_ROCOCO_GRANDPA_PALLET_NAME: &str = "BridgeRococoGrandpa";
/// reserve.
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 }
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]
default = ["std"]
@@ -29,4 +30,5 @@ std = [
"bp-rococo/std",
"frame-support/std",
"sp-api/std",
"sp-std/std",
]
@@ -26,6 +26,7 @@ pub use bp_rococo::{
use bp_header_chain::ChainWithGrandpa;
use bp_runtime::{decl_bridge_finality_runtime_apis, Chain};
use frame_support::weights::Weight;
use sp_std::prelude::Vec;
/// Wococo Chain
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.
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::{
BasicOperatingMode, Chain, HashOf, HasherOf, HeaderOf, RawStorageProof, StorageProofChecker,
StorageProofError,
StorageProofError, UnderlyingChainProvider,
};
use codec::{Codec, Decode, Encode, EncodeLike, MaxEncodedLen};
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.
#[derive(Encode, Decode, Debug, PartialEq, Eq, Clone, TypeInfo)]
#[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.
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`,
//! 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::{
header_id, make_justification_for_header, signed_precommit, test_header, Account,
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)]
}
/// 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.
pub fn make_default_justification(header: &TestHeader) -> GrandpaJustification<TestHeader> {
make_justification_for_header(JustificationGeneratorParams {
@@ -124,7 +119,7 @@ fn same_result_when_precommit_target_has_lower_number_than_commit_target() {
&full_voter_set(),
&justification,
),
Err(Error::UnrelatedAncestryVote),
Err(JustificationVerificationError::Precommit(PrecommitError::UnrelatedAncestryVote)),
);
// 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(),
&justification,
),
Err(Error::UnrelatedAncestryVote),
Err(JustificationVerificationError::Precommit(PrecommitError::UnrelatedAncestryVote)),
);
// 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(),
&justification,
),
Err(Error::TooLowCumulativeWeight),
Err(JustificationVerificationError::TooLowCumulativeWeight),
);
// original implementation returns `Ok(validation_result)`
// with `validation_result.is_valid() == false`.
@@ -229,7 +224,7 @@ fn different_result_when_justification_contains_duplicate_vote() {
&full_voter_set(),
&justification,
),
Err(Error::DuplicateAuthorityVote),
Err(JustificationVerificationError::Precommit(PrecommitError::DuplicateAuthorityVote)),
);
// original implementation returns `Ok(validation_result)`
// with `validation_result.is_valid() == true`.
@@ -270,7 +265,7 @@ fn different_results_when_authority_equivocates_once_in_a_round() {
&full_voter_set(),
&justification,
),
Err(Error::DuplicateAuthorityVote),
Err(JustificationVerificationError::Precommit(PrecommitError::DuplicateAuthorityVote)),
);
// original implementation returns `Ok(validation_result)`
// with `validation_result.is_valid() == true`.
@@ -323,7 +318,7 @@ fn different_results_when_authority_equivocates_twice_in_a_round() {
&full_voter_set(),
&justification,
),
Err(Error::DuplicateAuthorityVote),
Err(JustificationVerificationError::Precommit(PrecommitError::DuplicateAuthorityVote)),
);
// original implementation returns `Ok(validation_result)`
// with `validation_result.is_valid() == true`.
@@ -362,7 +357,7 @@ fn different_results_when_there_are_more_than_enough_votes() {
&full_voter_set(),
&justification,
),
Err(Error::RedundantVotesInJustification),
Err(JustificationVerificationError::Precommit(PrecommitError::RedundantAuthorityVote)),
);
// original implementation returns `Ok(validation_result)`
// with `validation_result.is_valid() == true`.
@@ -403,7 +398,7 @@ fn different_results_when_there_is_a_vote_of_unknown_authority() {
&full_voter_set(),
&justification,
),
Err(Error::UnknownAuthorityVote),
Err(JustificationVerificationError::Precommit(PrecommitError::UnknownAuthorityVote)),
);
// original implementation returns `Ok(validation_result)`
// 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
// 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::{
required_justification_precommits, verify_and_optimize_justification, verify_justification,
Error,
};
use bp_header_chain::justification::verify_and_optimize_justification;
use bp_test_utils::*;
use finality_grandpa::SignedPrecommit;
use sp_consensus_grandpa::AuthoritySignature;
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]
fn optimizer_does_noting_with_minimal_justification() {
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.
///
/// 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
/// lane.
fn verify_message(
submitter: &SenderOrigin,
lane: &LaneId,
outbound_data: &OutboundLaneData,
payload: &Payload,
@@ -124,32 +123,24 @@ pub struct SendMessageArtifacts {
}
/// Messages bridge API to be used from other pallets.
pub trait MessagesBridge<SenderOrigin, Payload> {
pub trait MessagesBridge<Payload> {
/// Error type.
type Error: Debug;
/// Send message over the bridge.
///
/// Returns unique message nonce or error if send has failed.
fn send_message(
sender: SenderOrigin,
lane: LaneId,
message: Payload,
) -> Result<SendMessageArtifacts, Self::Error>;
fn send_message(lane: LaneId, message: Payload) -> Result<SendMessageArtifacts, Self::Error>;
}
/// Bridge that does nothing when message is being sent.
#[derive(Eq, RuntimeDebug, PartialEq)]
pub struct NoopMessagesBridge;
impl<SenderOrigin, Payload> MessagesBridge<SenderOrigin, Payload> for NoopMessagesBridge {
impl<Payload> MessagesBridge<Payload> for NoopMessagesBridge {
type Error = &'static str;
fn send_message(
_sender: SenderOrigin,
_lane: LaneId,
_message: Payload,
) -> Result<SendMessageArtifacts, Self::Error> {
fn send_message(_lane: LaneId, _message: Payload) -> Result<SendMessageArtifacts, Self::Error> {
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(
_submitter: &SenderOrigin,
_lane: &LaneId,
_outbound_data: &OutboundLaneData,
_payload: &Payload,
@@ -279,10 +279,11 @@ pub type TransactionEraOf<C> = crate::TransactionEra<BlockNumberOf<C>, HashOf<C>
/// - `<ThisChain>FinalityApi`
/// - constants that are stringified names of runtime API methods:
/// - `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`).
#[macro_export]
macro_rules! decl_bridge_finality_runtime_apis {
($chain: ident) => {
($chain: ident $(, $consensus: ident => $justification_type: ty)?) => {
bp_runtime::paste::item! {
mod [<$chain _finality_api>] {
use super::*;
@@ -291,6 +292,13 @@ macro_rules! decl_bridge_finality_runtime_apis {
pub const [<BEST_FINALIZED_ $chain:upper _HEADER_METHOD>]: &str =
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! {
/// 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>] {
/// Returns number and hash of the best finalized header known to the bridge module.
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>]::*;
}
};
($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.
@@ -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`).
#[macro_export]
macro_rules! decl_bridge_runtime_apis {
($chain: ident) => {
bp_runtime::decl_bridge_finality_runtime_apis!($chain);
($chain: ident $(, $consensus: ident)?) => {
bp_runtime::decl_bridge_finality_runtime_apis!($chain $(, $consensus)?);
bp_runtime::decl_bridge_messages_runtime_apis!($chain);
};
}
@@ -77,7 +77,7 @@ pub const KUSAMA_CHAIN_ID: ChainId = *b"ksma";
/// Westend chain id.
pub const WESTEND_CHAIN_ID: ChainId = *b"wend";
/// AssetHubWestend chain id.
/// `AssetHubWestmint` chain id.
pub const ASSET_HUB_WESTEND_CHAIN_ID: ChainId = *b"ahwe";
/// Rococo chain id.
@@ -74,6 +74,7 @@ parachains-common = { path = "../../../../parachains/common", default-features =
# Bridges
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-header-chain = { path = "../../../../bridges/primitives/header-chain", default-features = false }
bp-messages = { path = "../../../../bridges/primitives/messages", default-features = false }
bp-parachains = { path = "../../../../bridges/primitives/parachains", default-features = false }
bp-polkadot-core = { path = "../../../../bridges/primitives/polkadot-core", default-features = false }
@@ -100,6 +101,7 @@ default = [
std = [
"bp-bridge-hub-rococo/std",
"bp-bridge-hub-wococo/std",
"bp-header-chain/std",
"bp-messages/std",
"bp-parachains/std",
"bp-polkadot-core/std",
@@ -78,13 +78,6 @@ impl XcmBlobHauler for ToBridgeHubWococoXcmBlobHauler {
type MessageSender =
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 {
DEFAULT_XCM_LANE_TO_BRIDGE_HUB_WOCOCO
}
@@ -78,13 +78,6 @@ impl XcmBlobHauler for ToBridgeHubRococoXcmBlobHauler {
type MessageSender =
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 {
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>> {
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 {
fn best_finalized() -> Option<HeaderId<bp_wococo::Hash, bp_wococo::BlockNumber>> {
BridgeWococoGrandpa::best_finalized()
}
fn synced_headers_grandpa_info(
) -> Vec<bp_header_chain::HeaderGrandpaInfo<bp_wococo::Header>> {
BridgeWococoGrandpa::synced_headers_grandpa_info()
}
}