Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
30 changes: 11 additions & 19 deletions bin/millau/runtime/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,7 @@ pub use frame_support::{
constants::WEIGHT_PER_SECOND, ConstantMultiplier, DispatchClass, IdentityFee,
RuntimeDbWeight, Weight,
},
StorageValue,
RuntimeDebug, StorageValue,
};

pub use frame_system::Call as SystemCall;
Expand All @@ -89,6 +89,7 @@ pub use pallet_bridge_parachains::Call as BridgeParachainsCall;
pub use pallet_sudo::Call as SudoCall;
pub use pallet_timestamp::Call as TimestampCall;

use bridge_runtime_common::generate_bridge_reject_obsolete_headers_and_messages;
#[cfg(any(feature = "std", test))]
pub use sp_runtime::BuildStorage;
pub use sp_runtime::{Perbill, Permill};
Expand Down Expand Up @@ -592,21 +593,14 @@ construct_runtime!(
}
);

pallet_bridge_grandpa::declare_bridge_reject_obsolete_grandpa_header! {
Runtime,
Call::BridgeRialtoGrandpa => RialtoGrandpaInstance,
Call::BridgeWestendGrandpa => WestendGrandpaInstance
}

pallet_bridge_parachains::declare_bridge_reject_obsolete_parachain_header! {
Runtime,
Call::BridgeRialtoParachains => WithRialtoParachainsInstance
}

bridge_runtime_common::declare_bridge_reject_obsolete_messages! {
Runtime,
Call::BridgeRialtoMessages => WithRialtoMessagesInstance,
Call::BridgeRialtoParachainMessages => WithRialtoParachainMessagesInstance
generate_bridge_reject_obsolete_headers_and_messages! {
Call, AccountId,
// Grandpa
BridgeRialtoGrandpa, BridgeWestendGrandpa,
// Parachains
BridgeRialtoParachains,
//Messages
BridgeRialtoMessages, BridgeRialtoParachainMessages
}

/// The address format for describing accounts.
Expand All @@ -629,9 +623,7 @@ pub type SignedExtra = (
frame_system::CheckNonce<Runtime>,
frame_system::CheckWeight<Runtime>,
pallet_transaction_payment::ChargeTransactionPayment<Runtime>,
BridgeRejectObsoleteGrandpaHeader,
BridgeRejectObsoleteParachainHeader,
BridgeRejectObsoleteMessages,
BridgeRejectObsoleteHeadersAndMessages,
);
/// The payload being signed in transactions.
pub type SignedPayload = generic::SignedPayload<Call, SignedExtra>;
Expand Down
167 changes: 167 additions & 0 deletions bin/runtime-common/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,9 @@

#![cfg_attr(not(feature = "std"), no_std)]

use bp_runtime::FilterCall;
use sp_runtime::transaction_validity::TransactionValidity;

pub mod messages;
pub mod messages_api;
pub mod messages_benchmarking;
Expand All @@ -26,3 +29,167 @@ pub mod parachains_benchmarking;

#[cfg(feature = "integrity-test")]
pub mod integrity;

/// A duplication of the `FilterCall` trait.
///
/// We need this trait in order to be able to implement it for the messages pallet,
/// since the implementation is done outside of the pallet crate.
pub trait BridgeRuntimeFilterCall<Call> {
/// Checks if a runtime call is valid.
fn validate(call: &Call) -> TransactionValidity;
}

impl<Call, T, I> BridgeRuntimeFilterCall<Call> for pallet_bridge_grandpa::Pallet<T, I>
where
pallet_bridge_grandpa::Pallet<T, I>: FilterCall<Call>,
{
fn validate(call: &Call) -> TransactionValidity {
<pallet_bridge_grandpa::Pallet<T, I> as FilterCall<Call>>::validate(call)
}
}

impl<Call, T, I> BridgeRuntimeFilterCall<Call> for pallet_bridge_parachains::Pallet<T, I>
where
pallet_bridge_parachains::Pallet<T, I>: FilterCall<Call>,
{
fn validate(call: &Call) -> TransactionValidity {
<pallet_bridge_parachains::Pallet<T, I> as FilterCall<Call>>::validate(call)
}
}

/// Declares a runtime-specific `BridgeRejectObsoleteHeadersAndMessages` signed extension.
///
/// ## Example
///
/// ```nocompile
/// generate_bridge_reject_obsolete_headers_and_messages!{
/// Call, AccountId
/// BridgeRialtoGrandpa, BridgeWestendGrandpa,
/// BridgeRialtoParachains
/// }
/// ```
///
/// The goal of this extension is to avoid "mining" transactions that provide outdated bridged
/// headers and messages. Without that extension, even honest relayers may lose their funds if
/// there are multiple relays running and submitting the same information.
#[macro_export]
macro_rules! generate_bridge_reject_obsolete_headers_and_messages {
($call:ty, $account_id:ty, $($filter_call:ty),*) => {
#[derive(Clone, codec::Decode, codec::Encode, Eq, PartialEq, frame_support::RuntimeDebug, scale_info::TypeInfo)]
pub struct BridgeRejectObsoleteHeadersAndMessages;
impl sp_runtime::traits::SignedExtension for BridgeRejectObsoleteHeadersAndMessages {
const IDENTIFIER: &'static str = "BridgeRejectObsoleteHeadersAndMessages";
type AccountId = $account_id;
type Call = $call;
type AdditionalSigned = ();
type Pre = ();

fn additional_signed(&self) -> sp_std::result::Result<
(),
sp_runtime::transaction_validity::TransactionValidityError,
> {
Ok(())
}

fn validate(
&self,
_who: &Self::AccountId,
call: &Self::Call,
_info: &sp_runtime::traits::DispatchInfoOf<Self::Call>,
_len: usize,
) -> sp_runtime::transaction_validity::TransactionValidity {
let valid = sp_runtime::transaction_validity::ValidTransaction::default();
$(
let valid = valid
.combine_with(<$filter_call as $crate::BridgeRuntimeFilterCall<$call>>::validate(call)?);
)*
Ok(valid)
}

fn pre_dispatch(
self,
who: &Self::AccountId,
call: &Self::Call,
info: &sp_runtime::traits::DispatchInfoOf<Self::Call>,
len: usize,
) -> Result<Self::Pre, sp_runtime::transaction_validity::TransactionValidityError> {
self.validate(who, call, info, len).map(drop)
}
}
};
}

#[cfg(test)]
mod tests {
use crate::BridgeRuntimeFilterCall;
use frame_support::{assert_err, assert_ok};
use sp_runtime::{
traits::SignedExtension,
transaction_validity::{InvalidTransaction, TransactionValidity, ValidTransaction},
};

pub struct MockCall {
data: u32,
}

impl sp_runtime::traits::Dispatchable for MockCall {
type Origin = ();
type Config = ();
type Info = ();
type PostInfo = ();

fn dispatch(
self,
_origin: Self::Origin,
) -> sp_runtime::DispatchResultWithInfo<Self::PostInfo> {
unimplemented!()
}
}

struct FirstFilterCall;
impl BridgeRuntimeFilterCall<MockCall> for FirstFilterCall {
fn validate(call: &MockCall) -> TransactionValidity {
if call.data <= 1 {
return InvalidTransaction::Custom(1).into()
}

Ok(ValidTransaction { priority: 1, ..Default::default() })
}
}

struct SecondFilterCall;
impl BridgeRuntimeFilterCall<MockCall> for SecondFilterCall {
fn validate(call: &MockCall) -> TransactionValidity {
if call.data <= 2 {
return InvalidTransaction::Custom(2).into()
}

Ok(ValidTransaction { priority: 2, ..Default::default() })
}
}

#[test]
fn test() {
generate_bridge_reject_obsolete_headers_and_messages!(
MockCall,
(),
FirstFilterCall,
SecondFilterCall
);

assert_err!(
BridgeRejectObsoleteHeadersAndMessages.validate(&(), &MockCall { data: 1 }, &(), 0),
InvalidTransaction::Custom(1)
);

assert_err!(
BridgeRejectObsoleteHeadersAndMessages.validate(&(), &MockCall { data: 2 }, &(), 0),
InvalidTransaction::Custom(2)
);

assert_ok!(
BridgeRejectObsoleteHeadersAndMessages.validate(&(), &MockCall { data: 3 }, &(), 0),
ValidTransaction { priority: 3, ..Default::default() }
)
}
}
Loading