mirror of
https://github.com/pezkuwichain/pezkuwi-subxt.git
synced 2026-05-30 07:01:03 +00:00
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:
Generated
+3
@@ -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!(
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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,
|
||||
);
|
||||
});
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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(¤t_hash) {
|
||||
Some(parent_hash) => {
|
||||
let is_visited_before = self.unvisited.get(¤t_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)
|
||||
}
|
||||
+179
@@ -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(¤t_hash) {
|
||||
Some(parent_hash) => {
|
||||
let is_visited_before = self.unvisited.get(¤t_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
|
||||
)
|
||||
})
|
||||
)]
|
||||
);
|
||||
}
|
||||
+2
-176
@@ -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",
|
||||
|
||||
-7
@@ -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
|
||||
}
|
||||
|
||||
-7
@@ -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()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user