Bridge: make some headers submissions free (#4102)

supersedes https://github.com/paritytech/parity-bridges-common/pull/2873

Draft because of couple of TODOs:
- [x] fix remaining TODOs;
- [x] double check that all changes from
https://github.com/paritytech/parity-bridges-common/pull/2873 are
correctly ported;
- [x] create a separate PR (on top of that one or a follow up?) for
https://github.com/paritytech/polkadot-sdk/tree/sv-try-new-bridge-fees;
- [x] fix compilation issues (haven't checked, but there should be
many).

---------

Co-authored-by: Adrian Catangiu <adrian@parity.io>
This commit is contained in:
Svyatoslav Nikolsky
2024-04-25 08:26:16 +03:00
committed by GitHub
parent 4f3d43a0c4
commit a633e954f3
54 changed files with 3731 additions and 616 deletions
@@ -18,55 +18,229 @@
//! obsolete (duplicated) data or do not pass some additional pallet-specific
//! checks.
use crate::messages_call_ext::MessagesCallSubType;
use pallet_bridge_grandpa::CallSubType as GrandpaCallSubType;
use pallet_bridge_parachains::CallSubType as ParachainsCallSubtype;
use sp_runtime::transaction_validity::TransactionValidity;
use crate::{
extensions::refund_relayer_extension::RefundableParachainId,
messages_call_ext::MessagesCallSubType,
};
use bp_relayers::ExplicitOrAccountParams;
use bp_runtime::Parachain;
use pallet_bridge_grandpa::{
BridgedBlockNumber, CallSubType as GrandpaCallSubType, SubmitFinalityProofHelper,
};
use pallet_bridge_parachains::{
CallSubType as ParachainsCallSubtype, SubmitParachainHeadsHelper, SubmitParachainHeadsInfo,
};
use pallet_bridge_relayers::Pallet as RelayersPallet;
use sp_runtime::{
traits::{Get, PhantomData, UniqueSaturatedInto},
transaction_validity::{TransactionPriority, TransactionValidity, ValidTransactionBuilder},
};
/// 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;
pub trait BridgeRuntimeFilterCall<AccountId, Call> {
/// Data that may be passed from the validate to `post_dispatch`.
type ToPostDispatch;
/// Called during validation. Needs to checks whether a runtime call, submitted
/// by the `who` is valid. `who` may be `None` if transaction is not signed
/// by a regular account.
fn validate(who: &AccountId, call: &Call) -> (Self::ToPostDispatch, TransactionValidity);
/// Called after transaction is dispatched.
fn post_dispatch(_who: &AccountId, _has_failed: bool, _to_post_dispatch: Self::ToPostDispatch) {
}
}
impl<T, I: 'static> BridgeRuntimeFilterCall<T::RuntimeCall> for pallet_bridge_grandpa::Pallet<T, I>
/// Wrapper for the bridge GRANDPA pallet that checks calls for obsolete submissions
/// and also boosts transaction priority if it has submitted by registered relayer.
/// The boost is computed as
/// `(BundledHeaderNumber - 1 - BestFinalizedHeaderNumber) * Priority::get()`.
/// The boost is only applied if submitter has active registration in the relayers
/// pallet.
pub struct CheckAndBoostBridgeGrandpaTransactions<T, I, Priority, SlashAccount>(
PhantomData<(T, I, Priority, SlashAccount)>,
);
impl<T, I: 'static, Priority: Get<TransactionPriority>, SlashAccount: Get<T::AccountId>>
BridgeRuntimeFilterCall<T::AccountId, T::RuntimeCall>
for CheckAndBoostBridgeGrandpaTransactions<T, I, Priority, SlashAccount>
where
T: pallet_bridge_relayers::Config + pallet_bridge_grandpa::Config<I>,
T::RuntimeCall: GrandpaCallSubType<T, I>,
{
// bridged header number, bundled in transaction
type ToPostDispatch = Option<BridgedBlockNumber<T, I>>;
fn validate(
who: &T::AccountId,
call: &T::RuntimeCall,
) -> (Self::ToPostDispatch, TransactionValidity) {
match GrandpaCallSubType::<T, I>::check_obsolete_submit_finality_proof(call) {
Ok(Some(our_tx)) => {
let to_post_dispatch = Some(our_tx.base.block_number);
let total_priority_boost =
compute_priority_boost::<T, _, Priority>(who, our_tx.improved_by);
(
to_post_dispatch,
ValidTransactionBuilder::default().priority(total_priority_boost).build(),
)
},
Ok(None) => (None, ValidTransactionBuilder::default().build()),
Err(e) => (None, Err(e)),
}
}
fn post_dispatch(
relayer: &T::AccountId,
has_failed: bool,
bundled_block_number: Self::ToPostDispatch,
) {
// we are only interested in associated pallet submissions
let Some(bundled_block_number) = bundled_block_number else { return };
// we are only interested in failed or unneeded transactions
let has_failed =
has_failed || !SubmitFinalityProofHelper::<T, I>::was_successful(bundled_block_number);
if !has_failed {
return
}
// let's slash registered relayer
RelayersPallet::<T>::slash_and_deregister(
relayer,
ExplicitOrAccountParams::Explicit(SlashAccount::get()),
);
}
}
/// Wrapper for the bridge parachains pallet that checks calls for obsolete submissions
/// and also boosts transaction priority if it has submitted by registered relayer.
/// The boost is computed as
/// `(BundledHeaderNumber - 1 - BestKnownHeaderNumber) * Priority::get()`.
/// The boost is only applied if submitter has active registration in the relayers
/// pallet.
pub struct CheckAndBoostBridgeParachainsTransactions<T, RefPara, Priority, SlashAccount>(
PhantomData<(T, RefPara, Priority, SlashAccount)>,
);
impl<T, RefPara, Priority: Get<TransactionPriority>, SlashAccount: Get<T::AccountId>>
BridgeRuntimeFilterCall<T::AccountId, T::RuntimeCall>
for CheckAndBoostBridgeParachainsTransactions<T, RefPara, Priority, SlashAccount>
where
T: pallet_bridge_relayers::Config + pallet_bridge_parachains::Config<RefPara::Instance>,
RefPara: RefundableParachainId,
T::RuntimeCall: ParachainsCallSubtype<T, RefPara::Instance>,
{
// bridged header number, bundled in transaction
type ToPostDispatch = Option<SubmitParachainHeadsInfo>;
fn validate(
who: &T::AccountId,
call: &T::RuntimeCall,
) -> (Self::ToPostDispatch, TransactionValidity) {
match ParachainsCallSubtype::<T, RefPara::Instance>::check_obsolete_submit_parachain_heads(
call,
) {
Ok(Some(our_tx)) if our_tx.base.para_id.0 == RefPara::BridgedChain::PARACHAIN_ID => {
let to_post_dispatch = Some(our_tx.base);
let total_priority_boost =
compute_priority_boost::<T, _, Priority>(&who, our_tx.improved_by);
(
to_post_dispatch,
ValidTransactionBuilder::default().priority(total_priority_boost).build(),
)
},
Ok(_) => (None, ValidTransactionBuilder::default().build()),
Err(e) => (None, Err(e)),
}
}
fn post_dispatch(relayer: &T::AccountId, has_failed: bool, maybe_update: Self::ToPostDispatch) {
// we are only interested in associated pallet submissions
let Some(update) = maybe_update else { return };
// we are only interested in failed or unneeded transactions
let has_failed = has_failed ||
!SubmitParachainHeadsHelper::<T, RefPara::Instance>::was_successful(&update);
if !has_failed {
return
}
// let's slash registered relayer
RelayersPallet::<T>::slash_and_deregister(
relayer,
ExplicitOrAccountParams::Explicit(SlashAccount::get()),
);
}
}
impl<T, I: 'static> BridgeRuntimeFilterCall<T::AccountId, T::RuntimeCall>
for pallet_bridge_grandpa::Pallet<T, I>
where
T: pallet_bridge_grandpa::Config<I>,
T::RuntimeCall: GrandpaCallSubType<T, I>,
{
fn validate(call: &T::RuntimeCall) -> TransactionValidity {
GrandpaCallSubType::<T, I>::check_obsolete_submit_finality_proof(call)
type ToPostDispatch = ();
fn validate(_who: &T::AccountId, call: &T::RuntimeCall) -> ((), TransactionValidity) {
(
(),
GrandpaCallSubType::<T, I>::check_obsolete_submit_finality_proof(call)
.and_then(|_| ValidTransactionBuilder::default().build()),
)
}
}
impl<T, I: 'static> BridgeRuntimeFilterCall<T::RuntimeCall>
impl<T, I: 'static> BridgeRuntimeFilterCall<T::AccountId, T::RuntimeCall>
for pallet_bridge_parachains::Pallet<T, I>
where
T: pallet_bridge_parachains::Config<I>,
T::RuntimeCall: ParachainsCallSubtype<T, I>,
{
fn validate(call: &T::RuntimeCall) -> TransactionValidity {
ParachainsCallSubtype::<T, I>::check_obsolete_submit_parachain_heads(call)
type ToPostDispatch = ();
fn validate(_who: &T::AccountId, call: &T::RuntimeCall) -> ((), TransactionValidity) {
(
(),
ParachainsCallSubtype::<T, I>::check_obsolete_submit_parachain_heads(call)
.and_then(|_| ValidTransactionBuilder::default().build()),
)
}
}
impl<T: pallet_bridge_messages::Config<I>, I: 'static> BridgeRuntimeFilterCall<T::RuntimeCall>
for pallet_bridge_messages::Pallet<T, I>
impl<T: pallet_bridge_messages::Config<I>, I: 'static>
BridgeRuntimeFilterCall<T::AccountId, T::RuntimeCall> for pallet_bridge_messages::Pallet<T, I>
where
T::RuntimeCall: MessagesCallSubType<T, I>,
{
type ToPostDispatch = ();
/// Validate messages in order to avoid "mining" messages delivery and delivery confirmation
/// transactions, that are delivering outdated messages/confirmations. Without this validation,
/// even honest relayers may lose their funds if there are multiple relays running and
/// submitting the same messages/confirmations.
fn validate(call: &T::RuntimeCall) -> TransactionValidity {
call.check_obsolete_call()
fn validate(_who: &T::AccountId, call: &T::RuntimeCall) -> ((), TransactionValidity) {
((), call.check_obsolete_call())
}
}
/// Computes priority boost that improved known header by `improved_by`
fn compute_priority_boost<T, N, Priority>(
relayer: &T::AccountId,
improved_by: N,
) -> TransactionPriority
where
T: pallet_bridge_relayers::Config,
N: UniqueSaturatedInto<TransactionPriority>,
Priority: Get<TransactionPriority>,
{
// we only boost priority if relayer has staked required balance
let is_relayer_registration_active = RelayersPallet::<T>::is_registration_active(relayer);
// if tx improves by just one, there's no need to bump its priority
let improved_by: TransactionPriority = improved_by.unique_saturated_into().saturating_sub(1);
// if relayer is registered, for every skipped header we improve by `Priority`
let boost_per_header = if is_relayer_registration_active { Priority::get() } else { 0 };
improved_by.saturating_mul(boost_per_header)
}
/// Declares a runtime-specific `BridgeRejectObsoleteHeadersAndMessages` signed extension.
///
/// ## Example
@@ -92,7 +266,15 @@ macro_rules! generate_bridge_reject_obsolete_headers_and_messages {
type AccountId = $account_id;
type Call = $call;
type AdditionalSigned = ();
type Pre = ();
type Pre = (
$account_id,
( $(
<$filter_call as $crate::extensions::check_obsolete_extension::BridgeRuntimeFilterCall<
$account_id,
$call,
>>::ToPostDispatch,
)* ),
);
fn additional_signed(&self) -> sp_std::result::Result<
(),
@@ -101,29 +283,72 @@ macro_rules! generate_bridge_reject_obsolete_headers_and_messages {
Ok(())
}
#[allow(unused_variables)]
fn validate(
&self,
_who: &Self::AccountId,
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 tx_validity = sp_runtime::transaction_validity::ValidTransaction::default();
let to_prepare = ();
$(
let valid = valid
.combine_with(<$filter_call as $crate::extensions::check_obsolete_extension::BridgeRuntimeFilterCall<$call>>::validate(call)?);
let (from_validate, call_filter_validity) = <
$filter_call as
$crate::extensions::check_obsolete_extension::BridgeRuntimeFilterCall<
Self::AccountId,
$call,
>>::validate(&who, call);
let tx_validity = tx_validity.combine_with(call_filter_validity?);
)*
Ok(valid)
Ok(tx_validity)
}
#[allow(unused_variables)]
fn pre_dispatch(
self,
who: &Self::AccountId,
relayer: &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)
use tuplex::PushBack;
let to_post_dispatch = ();
$(
let (from_validate, call_filter_validity) = <
$filter_call as
$crate::extensions::check_obsolete_extension::BridgeRuntimeFilterCall<
$account_id,
$call,
>>::validate(&relayer, call);
let _ = call_filter_validity?;
let to_post_dispatch = to_post_dispatch.push_back(from_validate);
)*
Ok((relayer.clone(), to_post_dispatch))
}
#[allow(unused_variables)]
fn post_dispatch(
to_post_dispatch: Option<Self::Pre>,
info: &sp_runtime::traits::DispatchInfoOf<Self::Call>,
post_info: &sp_runtime::traits::PostDispatchInfoOf<Self::Call>,
len: usize,
result: &sp_runtime::DispatchResult,
) -> Result<(), sp_runtime::transaction_validity::TransactionValidityError> {
use tuplex::PopFront;
let Some((relayer, to_post_dispatch)) = to_post_dispatch else { return Ok(()) };
let has_failed = result.is_err();
$(
let (item, to_post_dispatch) = to_post_dispatch.pop_front();
<
$filter_call as
$crate::extensions::check_obsolete_extension::BridgeRuntimeFilterCall<
$account_id,
$call,
>>::post_dispatch(&relayer, has_failed, item);
)*
Ok(())
}
}
};
@@ -132,10 +357,23 @@ macro_rules! generate_bridge_reject_obsolete_headers_and_messages {
#[cfg(test)]
mod tests {
use super::*;
use crate::{
extensions::refund_relayer_extension::{
tests::{
initialize_environment, relayer_account_at_this_chain,
submit_parachain_head_call_ex, submit_relay_header_call_ex,
},
RefundableParachain,
},
mock::*,
};
use bp_polkadot_core::parachains::ParaId;
use bp_runtime::HeaderId;
use frame_support::{assert_err, assert_ok};
use sp_runtime::{
traits::SignedExtension,
traits::{ConstU64, SignedExtension},
transaction_validity::{InvalidTransaction, TransactionValidity, ValidTransaction},
DispatchError,
};
pub struct MockCall {
@@ -143,7 +381,7 @@ mod tests {
}
impl sp_runtime::traits::Dispatchable for MockCall {
type RuntimeOrigin = ();
type RuntimeOrigin = u64;
type Config = ();
type Info = ();
type PostInfo = ();
@@ -156,50 +394,287 @@ mod tests {
}
}
struct FirstFilterCall;
impl BridgeRuntimeFilterCall<MockCall> for FirstFilterCall {
fn validate(call: &MockCall) -> TransactionValidity {
if call.data <= 1 {
return InvalidTransaction::Custom(1).into()
}
pub struct FirstFilterCall;
impl FirstFilterCall {
fn post_dispatch_called_with(success: bool) {
frame_support::storage::unhashed::put(&[1], &success);
}
Ok(ValidTransaction { priority: 1, ..Default::default() })
fn verify_post_dispatch_called_with(success: bool) {
assert_eq!(frame_support::storage::unhashed::get::<bool>(&[1]), Some(success));
}
}
struct SecondFilterCall;
impl BridgeRuntimeFilterCall<MockCall> for SecondFilterCall {
fn validate(call: &MockCall) -> TransactionValidity {
if call.data <= 2 {
return InvalidTransaction::Custom(2).into()
impl BridgeRuntimeFilterCall<u64, MockCall> for FirstFilterCall {
type ToPostDispatch = u64;
fn validate(_who: &u64, call: &MockCall) -> (u64, TransactionValidity) {
if call.data <= 1 {
return (1, InvalidTransaction::Custom(1).into())
}
Ok(ValidTransaction { priority: 2, ..Default::default() })
(1, Ok(ValidTransaction { priority: 1, ..Default::default() }))
}
fn post_dispatch(_who: &u64, has_failed: bool, to_post_dispatch: Self::ToPostDispatch) {
Self::post_dispatch_called_with(!has_failed);
assert_eq!(to_post_dispatch, 1);
}
}
pub struct SecondFilterCall;
impl SecondFilterCall {
fn post_dispatch_called_with(success: bool) {
frame_support::storage::unhashed::put(&[2], &success);
}
fn verify_post_dispatch_called_with(success: bool) {
assert_eq!(frame_support::storage::unhashed::get::<bool>(&[2]), Some(success));
}
}
impl BridgeRuntimeFilterCall<u64, MockCall> for SecondFilterCall {
type ToPostDispatch = u64;
fn validate(_who: &u64, call: &MockCall) -> (u64, TransactionValidity) {
if call.data <= 2 {
return (2, InvalidTransaction::Custom(2).into())
}
(2, Ok(ValidTransaction { priority: 2, ..Default::default() }))
}
fn post_dispatch(_who: &u64, has_failed: bool, to_post_dispatch: Self::ToPostDispatch) {
Self::post_dispatch_called_with(!has_failed);
assert_eq!(to_post_dispatch, 2);
}
}
#[test]
fn test() {
fn test_generated_obsolete_extension() {
generate_bridge_reject_obsolete_headers_and_messages!(
MockCall,
(),
u64,
FirstFilterCall,
SecondFilterCall
);
assert_err!(
BridgeRejectObsoleteHeadersAndMessages.validate(&(), &MockCall { data: 1 }, &(), 0),
InvalidTransaction::Custom(1)
);
run_test(|| {
assert_err!(
BridgeRejectObsoleteHeadersAndMessages.validate(&42, &MockCall { data: 1 }, &(), 0),
InvalidTransaction::Custom(1)
);
assert_err!(
BridgeRejectObsoleteHeadersAndMessages.pre_dispatch(
&42,
&MockCall { data: 1 },
&(),
0
),
InvalidTransaction::Custom(1)
);
assert_err!(
BridgeRejectObsoleteHeadersAndMessages.validate(&(), &MockCall { data: 2 }, &(), 0),
InvalidTransaction::Custom(2)
);
assert_err!(
BridgeRejectObsoleteHeadersAndMessages.validate(&42, &MockCall { data: 2 }, &(), 0),
InvalidTransaction::Custom(2)
);
assert_err!(
BridgeRejectObsoleteHeadersAndMessages.pre_dispatch(
&42,
&MockCall { data: 2 },
&(),
0
),
InvalidTransaction::Custom(2)
);
assert_ok!(
BridgeRejectObsoleteHeadersAndMessages.validate(&(), &MockCall { data: 3 }, &(), 0),
ValidTransaction { priority: 3, ..Default::default() }
)
assert_eq!(
BridgeRejectObsoleteHeadersAndMessages
.validate(&42, &MockCall { data: 3 }, &(), 0)
.unwrap(),
ValidTransaction { priority: 3, ..Default::default() },
);
assert_eq!(
BridgeRejectObsoleteHeadersAndMessages
.pre_dispatch(&42, &MockCall { data: 3 }, &(), 0)
.unwrap(),
(42, (1, 2)),
);
// when post_dispatch is called with `Ok(())`, it is propagated to all "nested"
// extensions
assert_ok!(BridgeRejectObsoleteHeadersAndMessages::post_dispatch(
Some((0, (1, 2))),
&(),
&(),
0,
&Ok(())
));
FirstFilterCall::verify_post_dispatch_called_with(true);
SecondFilterCall::verify_post_dispatch_called_with(true);
// when post_dispatch is called with `Err(())`, it is propagated to all "nested"
// extensions
assert_ok!(BridgeRejectObsoleteHeadersAndMessages::post_dispatch(
Some((0, (1, 2))),
&(),
&(),
0,
&Err(DispatchError::BadOrigin)
));
FirstFilterCall::verify_post_dispatch_called_with(false);
SecondFilterCall::verify_post_dispatch_called_with(false);
});
}
frame_support::parameter_types! {
pub SlashDestination: ThisChainAccountId = 42;
}
type BridgeGrandpaWrapper =
CheckAndBoostBridgeGrandpaTransactions<TestRuntime, (), ConstU64<1_000>, SlashDestination>;
#[test]
fn grandpa_wrapper_does_not_boost_extensions_for_unregistered_relayer() {
run_test(|| {
initialize_environment(100, 100, 100);
let priority_boost = BridgeGrandpaWrapper::validate(
&relayer_account_at_this_chain(),
&submit_relay_header_call_ex(200),
)
.1
.unwrap()
.priority;
assert_eq!(priority_boost, 0);
})
}
#[test]
fn grandpa_wrapper_boosts_extensions_for_registered_relayer() {
run_test(|| {
initialize_environment(100, 100, 100);
BridgeRelayers::register(RuntimeOrigin::signed(relayer_account_at_this_chain()), 1000)
.unwrap();
let priority_boost = BridgeGrandpaWrapper::validate(
&relayer_account_at_this_chain(),
&submit_relay_header_call_ex(200),
)
.1
.unwrap()
.priority;
assert_eq!(priority_boost, 99_000);
})
}
#[test]
fn grandpa_wrapper_slashes_registered_relayer_if_transaction_fails() {
run_test(|| {
initialize_environment(100, 100, 100);
BridgeRelayers::register(RuntimeOrigin::signed(relayer_account_at_this_chain()), 1000)
.unwrap();
assert!(BridgeRelayers::is_registration_active(&relayer_account_at_this_chain()));
BridgeGrandpaWrapper::post_dispatch(&relayer_account_at_this_chain(), true, Some(150));
assert!(!BridgeRelayers::is_registration_active(&relayer_account_at_this_chain()));
})
}
#[test]
fn grandpa_wrapper_does_not_slash_registered_relayer_if_transaction_succeeds() {
run_test(|| {
initialize_environment(100, 100, 100);
BridgeRelayers::register(RuntimeOrigin::signed(relayer_account_at_this_chain()), 1000)
.unwrap();
assert!(BridgeRelayers::is_registration_active(&relayer_account_at_this_chain()));
BridgeGrandpaWrapper::post_dispatch(&relayer_account_at_this_chain(), false, Some(100));
assert!(BridgeRelayers::is_registration_active(&relayer_account_at_this_chain()));
})
}
type BridgeParachainsWrapper = CheckAndBoostBridgeParachainsTransactions<
TestRuntime,
RefundableParachain<(), BridgedUnderlyingParachain>,
ConstU64<1_000>,
SlashDestination,
>;
#[test]
fn parachains_wrapper_does_not_boost_extensions_for_unregistered_relayer() {
run_test(|| {
initialize_environment(100, 100, 100);
let priority_boost = BridgeParachainsWrapper::validate(
&relayer_account_at_this_chain(),
&submit_parachain_head_call_ex(200),
)
.1
.unwrap()
.priority;
assert_eq!(priority_boost, 0);
})
}
#[test]
fn parachains_wrapper_boosts_extensions_for_registered_relayer() {
run_test(|| {
initialize_environment(100, 100, 100);
BridgeRelayers::register(RuntimeOrigin::signed(relayer_account_at_this_chain()), 1000)
.unwrap();
let priority_boost = BridgeParachainsWrapper::validate(
&relayer_account_at_this_chain(),
&submit_parachain_head_call_ex(200),
)
.1
.unwrap()
.priority;
assert_eq!(priority_boost, 99_000);
})
}
#[test]
fn parachains_wrapper_slashes_registered_relayer_if_transaction_fails() {
run_test(|| {
initialize_environment(100, 100, 100);
BridgeRelayers::register(RuntimeOrigin::signed(relayer_account_at_this_chain()), 1000)
.unwrap();
assert!(BridgeRelayers::is_registration_active(&relayer_account_at_this_chain()));
BridgeParachainsWrapper::post_dispatch(
&relayer_account_at_this_chain(),
true,
Some(SubmitParachainHeadsInfo {
at_relay_block: HeaderId(150, Default::default()),
para_id: ParaId(BridgedUnderlyingParachain::PARACHAIN_ID),
para_head_hash: [150u8; 32].into(),
is_free_execution_expected: false,
}),
);
assert!(!BridgeRelayers::is_registration_active(&relayer_account_at_this_chain()));
})
}
#[test]
fn parachains_wrapper_does_not_slash_registered_relayer_if_transaction_succeeds() {
run_test(|| {
initialize_environment(100, 100, 100);
BridgeRelayers::register(RuntimeOrigin::signed(relayer_account_at_this_chain()), 1000)
.unwrap();
assert!(BridgeRelayers::is_registration_active(&relayer_account_at_this_chain()));
BridgeParachainsWrapper::post_dispatch(
&relayer_account_at_this_chain(),
false,
Some(SubmitParachainHeadsInfo {
at_relay_block: HeaderId(100, Default::default()),
para_id: ParaId(BridgedUnderlyingParachain::PARACHAIN_ID),
para_head_hash: [100u8; 32].into(),
is_free_execution_expected: false,
}),
);
assert!(BridgeRelayers::is_registration_active(&relayer_account_at_this_chain()));
})
}
}
@@ -22,7 +22,6 @@
//! single message with nonce `N`, then the transaction with nonces `N..=N+100` will
//! be rejected. This can lower bridge throughput down to one message per block.
use bp_messages::MessageNonce;
use frame_support::traits::Get;
use sp_runtime::transaction_validity::TransactionPriority;
@@ -30,16 +29,19 @@ use sp_runtime::transaction_validity::TransactionPriority;
#[allow(unused_imports)]
pub use integrity_tests::*;
/// Compute priority boost for message delivery transaction that delivers
/// given number of messages.
pub fn compute_priority_boost<PriorityBoostPerMessage>(
messages: MessageNonce,
) -> TransactionPriority
/// We'll deal with different bridge items here - messages, headers, ...
/// To avoid being too verbose with generic code, let's just define a separate alias.
pub type ItemCount = u64;
/// Compute priority boost for transaction that brings given number of bridge
/// items (messages, headers, ...), when every additional item adds `PriorityBoostPerItem`
/// to transaction priority.
pub fn compute_priority_boost<PriorityBoostPerItem>(n_items: ItemCount) -> TransactionPriority
where
PriorityBoostPerMessage: Get<TransactionPriority>,
PriorityBoostPerItem: Get<TransactionPriority>,
{
// we don't want any boost for transaction with single message => minus one
PriorityBoostPerMessage::get().saturating_mul(messages.saturating_sub(1))
// we don't want any boost for transaction with single (additional) item => minus one
PriorityBoostPerItem::get().saturating_mul(n_items.saturating_sub(1))
}
#[cfg(not(feature = "integrity-test"))]
@@ -47,7 +49,8 @@ mod integrity_tests {}
#[cfg(feature = "integrity-test")]
mod integrity_tests {
use super::compute_priority_boost;
use super::{compute_priority_boost, ItemCount};
use crate::extensions::refund_relayer_extension::RefundableParachainId;
use bp_messages::MessageNonce;
use bp_runtime::PreComputedSize;
@@ -55,7 +58,6 @@ mod integrity_tests {
dispatch::{DispatchClass, DispatchInfo, Pays, PostDispatchInfo},
traits::Get,
};
use pallet_bridge_messages::WeightInfoExt;
use pallet_transaction_payment::OnChargeTransaction;
use sp_runtime::{
traits::{Dispatchable, UniqueSaturatedInto, Zero},
@@ -68,37 +70,33 @@ mod integrity_tests {
T,
>>::Balance;
/// Ensures that the value of `PriorityBoostPerMessage` matches the value of
/// `tip_boost_per_message`.
/// Ensures that the value of `PriorityBoostPerItem` matches the value of
/// `tip_boost_per_item`.
///
/// We want two transactions, `TX1` with `N` messages and `TX2` with `N+1` messages, have almost
/// the same priority if we'll add `tip_boost_per_message` tip to the `TX1`. We want to be sure
/// that if we add plain `PriorityBoostPerMessage` priority to `TX1`, the priority will be close
/// We want two transactions, `TX1` with `N` items and `TX2` with `N+1` items, have almost
/// the same priority if we'll add `tip_boost_per_item` tip to the `TX1`. We want to be sure
/// that if we add plain `PriorityBoostPerItem` priority to `TX1`, the priority will be close
/// to `TX2` as well.
pub fn ensure_priority_boost_is_sane<Runtime, MessagesInstance, PriorityBoostPerMessage>(
tip_boost_per_message: BalanceOf<Runtime>,
fn ensure_priority_boost_is_sane<PriorityBoostPerItem, Balance>(
param_name: &str,
max_items: ItemCount,
tip_boost_per_item: Balance,
estimate_priority: impl Fn(ItemCount, Balance) -> TransactionPriority,
) where
Runtime:
pallet_transaction_payment::Config + pallet_bridge_messages::Config<MessagesInstance>,
MessagesInstance: 'static,
PriorityBoostPerMessage: Get<TransactionPriority>,
Runtime::RuntimeCall: Dispatchable<Info = DispatchInfo, PostInfo = PostDispatchInfo>,
BalanceOf<Runtime>: Send + Sync + FixedPointOperand,
PriorityBoostPerItem: Get<TransactionPriority>,
ItemCount: UniqueSaturatedInto<Balance>,
Balance: FixedPointOperand + Zero,
{
let priority_boost_per_message = PriorityBoostPerMessage::get();
let maximal_messages_in_delivery_transaction =
Runtime::MaxUnconfirmedMessagesAtInboundLane::get();
for messages in 1..=maximal_messages_in_delivery_transaction {
let base_priority = estimate_message_delivery_transaction_priority::<
Runtime,
MessagesInstance,
>(messages, Zero::zero());
let priority_boost = compute_priority_boost::<PriorityBoostPerMessage>(messages);
let priority_with_boost = base_priority + priority_boost;
let priority_boost_per_item = PriorityBoostPerItem::get();
for n_items in 1..=max_items {
let base_priority = estimate_priority(n_items, Zero::zero());
let priority_boost = compute_priority_boost::<PriorityBoostPerItem>(n_items);
let priority_with_boost = base_priority
.checked_add(priority_boost)
.expect("priority overflow: try lowering `max_items` or `tip_boost_per_item`?");
let tip = tip_boost_per_message.saturating_mul((messages - 1).unique_saturated_into());
let priority_with_tip =
estimate_message_delivery_transaction_priority::<Runtime, MessagesInstance>(1, tip);
let tip = tip_boost_per_item.saturating_mul((n_items - 1).unique_saturated_into());
let priority_with_tip = estimate_priority(1, tip);
const ERROR_MARGIN: TransactionPriority = 5; // 5%
if priority_with_boost.abs_diff(priority_with_tip).saturating_mul(100) /
@@ -106,97 +104,304 @@ mod integrity_tests {
ERROR_MARGIN
{
panic!(
"The PriorityBoostPerMessage value ({}) must be fixed to: {}",
priority_boost_per_message,
compute_priority_boost_per_message::<Runtime, MessagesInstance>(
tip_boost_per_message
"The {param_name} value ({}) must be fixed to: {}",
priority_boost_per_item,
compute_priority_boost_per_item(
max_items,
tip_boost_per_item,
estimate_priority
),
);
}
}
}
/// Compute priority boost that we give to message delivery transaction for additional message.
/// Compute priority boost that we give to bridge transaction for every
/// additional bridge item.
#[cfg(feature = "integrity-test")]
fn compute_priority_boost_per_message<Runtime, MessagesInstance>(
tip_boost_per_message: BalanceOf<Runtime>,
fn compute_priority_boost_per_item<Balance>(
max_items: ItemCount,
tip_boost_per_item: Balance,
estimate_priority: impl Fn(ItemCount, Balance) -> TransactionPriority,
) -> TransactionPriority
where
Runtime:
pallet_transaction_payment::Config + pallet_bridge_messages::Config<MessagesInstance>,
MessagesInstance: 'static,
Runtime::RuntimeCall: Dispatchable<Info = DispatchInfo, PostInfo = PostDispatchInfo>,
BalanceOf<Runtime>: Send + Sync + FixedPointOperand,
ItemCount: UniqueSaturatedInto<Balance>,
Balance: FixedPointOperand + Zero,
{
// estimate priority of transaction that delivers one message and has large tip
let maximal_messages_in_delivery_transaction =
Runtime::MaxUnconfirmedMessagesAtInboundLane::get();
// estimate priority of transaction that delivers one item and has large tip
let small_with_tip_priority =
estimate_message_delivery_transaction_priority::<Runtime, MessagesInstance>(
1,
tip_boost_per_message
.saturating_mul(maximal_messages_in_delivery_transaction.saturated_into()),
);
// estimate priority of transaction that delivers maximal number of messages, but has no tip
let large_without_tip_priority = estimate_message_delivery_transaction_priority::<
Runtime,
MessagesInstance,
>(maximal_messages_in_delivery_transaction, Zero::zero());
estimate_priority(1, tip_boost_per_item.saturating_mul(max_items.saturated_into()));
// estimate priority of transaction that delivers maximal number of items, but has no tip
let large_without_tip_priority = estimate_priority(max_items, Zero::zero());
small_with_tip_priority
.saturating_sub(large_without_tip_priority)
.saturating_div(maximal_messages_in_delivery_transaction - 1)
.saturating_div(max_items - 1)
}
/// Estimate message delivery transaction priority.
#[cfg(feature = "integrity-test")]
fn estimate_message_delivery_transaction_priority<Runtime, MessagesInstance>(
messages: MessageNonce,
tip: BalanceOf<Runtime>,
) -> TransactionPriority
where
Runtime:
pallet_transaction_payment::Config + pallet_bridge_messages::Config<MessagesInstance>,
MessagesInstance: 'static,
Runtime::RuntimeCall: Dispatchable<Info = DispatchInfo, PostInfo = PostDispatchInfo>,
BalanceOf<Runtime>: Send + Sync + FixedPointOperand,
{
// just an estimation of extra transaction bytes that are added to every transaction
// (including signature, signed extensions extra and etc + in our case it includes
// all call arguments except the proof itself)
let base_tx_size = 512;
// let's say we are relaying similar small messages and for every message we add more trie
// nodes to the proof (x0.5 because we expect some nodes to be reused)
let estimated_message_size = 512;
// let's say all our messages have the same dispatch weight
let estimated_message_dispatch_weight =
Runtime::WeightInfo::message_dispatch_weight(estimated_message_size);
// messages proof argument size is (for every message) messages size + some additional
// trie nodes. Some of them are reused by different messages, so let's take 2/3 of default
// "overhead" constant
let messages_proof_size = Runtime::WeightInfo::expected_extra_storage_proof_size()
.saturating_mul(2)
.saturating_div(3)
.saturating_add(estimated_message_size)
.saturating_mul(messages as _);
/// Computations, specific to bridge relay chains transactions.
pub mod per_relay_header {
use super::*;
// finally we are able to estimate transaction size and weight
let transaction_size = base_tx_size.saturating_add(messages_proof_size);
let transaction_weight = Runtime::WeightInfo::receive_messages_proof_weight(
&PreComputedSize(transaction_size as _),
messages as _,
estimated_message_dispatch_weight.saturating_mul(messages),
);
use bp_header_chain::{
max_expected_submit_finality_proof_arguments_size, ChainWithGrandpa,
};
use pallet_bridge_grandpa::WeightInfoExt;
pallet_transaction_payment::ChargeTransactionPayment::<Runtime>::get_priority(
&DispatchInfo {
weight: transaction_weight,
class: DispatchClass::Normal,
pays_fee: Pays::Yes,
},
transaction_size as _,
tip,
Zero::zero(),
)
/// Ensures that the value of `PriorityBoostPerHeader` matches the value of
/// `tip_boost_per_header`.
///
/// We want two transactions, `TX1` with `N` headers and `TX2` with `N+1` headers, have
/// almost the same priority if we'll add `tip_boost_per_header` tip to the `TX1`. We want
/// to be sure that if we add plain `PriorityBoostPerHeader` priority to `TX1`, the priority
/// will be close to `TX2` as well.
pub fn ensure_priority_boost_is_sane<Runtime, GrandpaInstance, PriorityBoostPerHeader>(
tip_boost_per_header: BalanceOf<Runtime>,
) where
Runtime:
pallet_transaction_payment::Config + pallet_bridge_grandpa::Config<GrandpaInstance>,
GrandpaInstance: 'static,
PriorityBoostPerHeader: Get<TransactionPriority>,
Runtime::RuntimeCall: Dispatchable<Info = DispatchInfo, PostInfo = PostDispatchInfo>,
BalanceOf<Runtime>: Send + Sync + FixedPointOperand,
{
// the meaning of `max_items` here is different when comparing with message
// transactions - with messages we have a strict limit on maximal number of
// messages we can fit into a single transaction. With headers, current best
// header may be improved by any "number of items". But this number is only
// used to verify priority boost, so it should be fine to select this arbitrary
// value - it SHALL NOT affect any value, it just adds more tests for the value.
let maximal_improved_by = 4_096;
super::ensure_priority_boost_is_sane::<PriorityBoostPerHeader, BalanceOf<Runtime>>(
"PriorityBoostPerRelayHeader",
maximal_improved_by,
tip_boost_per_header,
|_n_headers, tip| {
estimate_relay_header_submit_transaction_priority::<Runtime, GrandpaInstance>(
tip,
)
},
);
}
/// Estimate relay header delivery transaction priority.
#[cfg(feature = "integrity-test")]
fn estimate_relay_header_submit_transaction_priority<Runtime, GrandpaInstance>(
tip: BalanceOf<Runtime>,
) -> TransactionPriority
where
Runtime:
pallet_transaction_payment::Config + pallet_bridge_grandpa::Config<GrandpaInstance>,
GrandpaInstance: 'static,
Runtime::RuntimeCall: Dispatchable<Info = DispatchInfo, PostInfo = PostDispatchInfo>,
BalanceOf<Runtime>: Send + Sync + FixedPointOperand,
{
// just an estimation of extra transaction bytes that are added to every transaction
// (including signature, signed extensions extra and etc + in our case it includes
// all call arguments except the proof itself)
let base_tx_size = 512;
// let's say we are relaying largest relay chain headers
let tx_call_size = max_expected_submit_finality_proof_arguments_size::<
Runtime::BridgedChain,
>(true, Runtime::BridgedChain::MAX_AUTHORITIES_COUNT * 2 / 3 + 1);
// finally we are able to estimate transaction size and weight
let transaction_size = base_tx_size.saturating_add(tx_call_size);
let transaction_weight = Runtime::WeightInfo::submit_finality_proof_weight(
Runtime::BridgedChain::MAX_AUTHORITIES_COUNT * 2 / 3 + 1,
Runtime::BridgedChain::REASONABLE_HEADERS_IN_JUSTIFICATION_ANCESTRY,
);
pallet_transaction_payment::ChargeTransactionPayment::<Runtime>::get_priority(
&DispatchInfo {
weight: transaction_weight,
class: DispatchClass::Normal,
pays_fee: Pays::Yes,
},
transaction_size as _,
tip,
Zero::zero(),
)
}
}
/// Computations, specific to bridge parachains transactions.
pub mod per_parachain_header {
use super::*;
use bp_runtime::Parachain;
use pallet_bridge_parachains::WeightInfoExt;
/// Ensures that the value of `PriorityBoostPerHeader` matches the value of
/// `tip_boost_per_header`.
///
/// We want two transactions, `TX1` with `N` headers and `TX2` with `N+1` headers, have
/// almost the same priority if we'll add `tip_boost_per_header` tip to the `TX1`. We want
/// to be sure that if we add plain `PriorityBoostPerHeader` priority to `TX1`, the priority
/// will be close to `TX2` as well.
pub fn ensure_priority_boost_is_sane<Runtime, RefundableParachain, PriorityBoostPerHeader>(
tip_boost_per_header: BalanceOf<Runtime>,
) where
Runtime: pallet_transaction_payment::Config
+ pallet_bridge_parachains::Config<RefundableParachain::Instance>,
RefundableParachain: RefundableParachainId,
PriorityBoostPerHeader: Get<TransactionPriority>,
Runtime::RuntimeCall: Dispatchable<Info = DispatchInfo, PostInfo = PostDispatchInfo>,
BalanceOf<Runtime>: Send + Sync + FixedPointOperand,
{
// the meaning of `max_items` here is different when comparing with message
// transactions - with messages we have a strict limit on maximal number of
// messages we can fit into a single transaction. With headers, current best
// header may be improved by any "number of items". But this number is only
// used to verify priority boost, so it should be fine to select this arbitrary
// value - it SHALL NOT affect any value, it just adds more tests for the value.
let maximal_improved_by = 4_096;
super::ensure_priority_boost_is_sane::<PriorityBoostPerHeader, BalanceOf<Runtime>>(
"PriorityBoostPerParachainHeader",
maximal_improved_by,
tip_boost_per_header,
|_n_headers, tip| {
estimate_parachain_header_submit_transaction_priority::<
Runtime,
RefundableParachain,
>(tip)
},
);
}
/// Estimate parachain header delivery transaction priority.
#[cfg(feature = "integrity-test")]
fn estimate_parachain_header_submit_transaction_priority<Runtime, RefundableParachain>(
tip: BalanceOf<Runtime>,
) -> TransactionPriority
where
Runtime: pallet_transaction_payment::Config
+ pallet_bridge_parachains::Config<RefundableParachain::Instance>,
RefundableParachain: RefundableParachainId,
Runtime::RuntimeCall: Dispatchable<Info = DispatchInfo, PostInfo = PostDispatchInfo>,
BalanceOf<Runtime>: Send + Sync + FixedPointOperand,
{
// just an estimation of extra transaction bytes that are added to every transaction
// (including signature, signed extensions extra and etc + in our case it includes
// all call arguments except the proof itself)
let base_tx_size = 512;
// let's say we are relaying largest parachain headers and proof takes some more bytes
let tx_call_size = <Runtime as pallet_bridge_parachains::Config<
RefundableParachain::Instance,
>>::WeightInfo::expected_extra_storage_proof_size()
.saturating_add(RefundableParachain::BridgedChain::MAX_HEADER_SIZE);
// finally we are able to estimate transaction size and weight
let transaction_size = base_tx_size.saturating_add(tx_call_size);
let transaction_weight = <Runtime as pallet_bridge_parachains::Config<
RefundableParachain::Instance,
>>::WeightInfo::submit_parachain_heads_weight(
Runtime::DbWeight::get(),
&PreComputedSize(transaction_size as _),
// just one parachain - all other submissions won't receive any boost
1,
);
pallet_transaction_payment::ChargeTransactionPayment::<Runtime>::get_priority(
&DispatchInfo {
weight: transaction_weight,
class: DispatchClass::Normal,
pays_fee: Pays::Yes,
},
transaction_size as _,
tip,
Zero::zero(),
)
}
}
/// Computations, specific to bridge messages transactions.
pub mod per_message {
use super::*;
use pallet_bridge_messages::WeightInfoExt;
/// Ensures that the value of `PriorityBoostPerMessage` matches the value of
/// `tip_boost_per_message`.
///
/// We want two transactions, `TX1` with `N` messages and `TX2` with `N+1` messages, have
/// almost the same priority if we'll add `tip_boost_per_message` tip to the `TX1`. We want
/// to be sure that if we add plain `PriorityBoostPerMessage` priority to `TX1`, the
/// priority will be close to `TX2` as well.
pub fn ensure_priority_boost_is_sane<Runtime, MessagesInstance, PriorityBoostPerMessage>(
tip_boost_per_message: BalanceOf<Runtime>,
) where
Runtime: pallet_transaction_payment::Config
+ pallet_bridge_messages::Config<MessagesInstance>,
MessagesInstance: 'static,
PriorityBoostPerMessage: Get<TransactionPriority>,
Runtime::RuntimeCall: Dispatchable<Info = DispatchInfo, PostInfo = PostDispatchInfo>,
BalanceOf<Runtime>: Send + Sync + FixedPointOperand,
{
let maximal_messages_in_delivery_transaction =
Runtime::MaxUnconfirmedMessagesAtInboundLane::get();
super::ensure_priority_boost_is_sane::<PriorityBoostPerMessage, BalanceOf<Runtime>>(
"PriorityBoostPerMessage",
maximal_messages_in_delivery_transaction,
tip_boost_per_message,
|n_messages, tip| {
estimate_message_delivery_transaction_priority::<Runtime, MessagesInstance>(
n_messages, tip,
)
},
);
}
/// Estimate message delivery transaction priority.
#[cfg(feature = "integrity-test")]
fn estimate_message_delivery_transaction_priority<Runtime, MessagesInstance>(
messages: MessageNonce,
tip: BalanceOf<Runtime>,
) -> TransactionPriority
where
Runtime: pallet_transaction_payment::Config
+ pallet_bridge_messages::Config<MessagesInstance>,
MessagesInstance: 'static,
Runtime::RuntimeCall: Dispatchable<Info = DispatchInfo, PostInfo = PostDispatchInfo>,
BalanceOf<Runtime>: Send + Sync + FixedPointOperand,
{
// just an estimation of extra transaction bytes that are added to every transaction
// (including signature, signed extensions extra and etc + in our case it includes
// all call arguments except the proof itself)
let base_tx_size = 512;
// let's say we are relaying similar small messages and for every message we add more
// trie nodes to the proof (x0.5 because we expect some nodes to be reused)
let estimated_message_size = 512;
// let's say all our messages have the same dispatch weight
let estimated_message_dispatch_weight =
Runtime::WeightInfo::message_dispatch_weight(estimated_message_size);
// messages proof argument size is (for every message) messages size + some additional
// trie nodes. Some of them are reused by different messages, so let's take 2/3 of
// default "overhead" constant
let messages_proof_size = Runtime::WeightInfo::expected_extra_storage_proof_size()
.saturating_mul(2)
.saturating_div(3)
.saturating_add(estimated_message_size)
.saturating_mul(messages as _);
// finally we are able to estimate transaction size and weight
let transaction_size = base_tx_size.saturating_add(messages_proof_size);
let transaction_weight = Runtime::WeightInfo::receive_messages_proof_weight(
&PreComputedSize(transaction_size as _),
messages as _,
estimated_message_dispatch_weight.saturating_mul(messages),
);
pallet_transaction_payment::ChargeTransactionPayment::<Runtime>::get_priority(
&DispatchInfo {
weight: transaction_weight,
class: DispatchClass::Normal,
pays_fee: Pays::Yes,
},
transaction_size as _,
tip,
Zero::zero(),
)
}
}
}
@@ -24,7 +24,7 @@ use crate::messages_call_ext::{
};
use bp_messages::{LaneId, MessageNonce};
use bp_relayers::{ExplicitOrAccountParams, RewardsAccountOwner, RewardsAccountParams};
use bp_runtime::{Chain, Parachain, ParachainIdOf, RangeInclusiveExt, StaticStrProvider};
use bp_runtime::{Parachain, RangeInclusiveExt, StaticStrProvider};
use codec::{Codec, Decode, Encode};
use frame_support::{
dispatch::{CallableCallFor, DispatchInfo, PostDispatchInfo},
@@ -33,8 +33,7 @@ use frame_support::{
CloneNoBound, DefaultNoBound, EqNoBound, PartialEqNoBound, RuntimeDebugNoBound,
};
use pallet_bridge_grandpa::{
CallSubType as GrandpaCallSubType, Config as GrandpaConfig, SubmitFinalityProofHelper,
SubmitFinalityProofInfo,
CallSubType as GrandpaCallSubType, SubmitFinalityProofHelper, SubmitFinalityProofInfo,
};
use pallet_bridge_messages::Config as MessagesConfig;
use pallet_bridge_parachains::{
@@ -66,20 +65,9 @@ type CallOf<R> = <R as frame_system::Config>::RuntimeCall;
/// coming from this parachain.
pub trait RefundableParachainId {
/// The instance of the bridge parachains pallet.
type Instance;
type Instance: 'static;
/// The parachain Id.
type Id: Get<u32>;
}
/// Default implementation of `RefundableParachainId`.
pub struct DefaultRefundableParachainId<Instance, Id>(PhantomData<(Instance, Id)>);
impl<Instance, Id> RefundableParachainId for DefaultRefundableParachainId<Instance, Id>
where
Id: Get<u32>,
{
type Instance = Instance;
type Id = Id;
type BridgedChain: Parachain;
}
/// Implementation of `RefundableParachainId` for `trait Parachain`.
@@ -87,10 +75,11 @@ pub struct RefundableParachain<Instance, Para>(PhantomData<(Instance, Para)>);
impl<Instance, Para> RefundableParachainId for RefundableParachain<Instance, Para>
where
Instance: 'static,
Para: Parachain,
{
type Instance = Instance;
type Id = ParachainIdOf<Para>;
type BridgedChain = Para;
}
/// Trait identifying a bridged messages lane. A relayer might be refunded for delivering messages
@@ -242,17 +231,10 @@ pub enum RelayerAccountAction<AccountId, Reward> {
/// Everything common among our refund signed extensions.
pub trait RefundSignedExtension:
'static + Clone + Codec + sp_std::fmt::Debug + Default + Eq + PartialEq + Send + Sync + TypeInfo
where
<Self::Runtime as GrandpaConfig<Self::GrandpaInstance>>::BridgedChain:
Chain<BlockNumber = RelayBlockNumber>,
{
/// This chain runtime.
type Runtime: UtilityConfig<RuntimeCall = CallOf<Self::Runtime>>
+ GrandpaConfig<Self::GrandpaInstance>
+ MessagesConfig<<Self::Msgs as RefundableMessagesLaneId>::Instance>
type Runtime: MessagesConfig<<Self::Msgs as RefundableMessagesLaneId>::Instance>
+ RelayersConfig;
/// Grandpa pallet reference.
type GrandpaInstance: 'static;
/// Messages pallet and lane reference.
type Msgs: RefundableMessagesLaneId;
/// Refund amount calculator.
@@ -276,11 +258,13 @@ where
call: &CallOf<Self::Runtime>,
) -> Result<&CallOf<Self::Runtime>, TransactionValidityError>;
/// Called from post-dispatch and shall perform additional checks (apart from relay
/// chain finality and messages transaction finality) of given call result.
/// Called from post-dispatch and shall perform additional checks (apart from messages
/// transaction success) of given call result.
fn additional_call_result_check(
relayer: &AccountIdOf<Self::Runtime>,
call_info: &CallInfo,
extra_weight: &mut Weight,
extra_size: &mut u32,
) -> bool;
/// Given post-dispatch information, analyze the outcome of relayer call and return
@@ -348,35 +332,6 @@ where
return slash_relayer_if_delivery_result
}
// check if relay chain state has been updated
if let Some(finality_proof_info) = call_info.submit_finality_proof_info() {
if !SubmitFinalityProofHelper::<Self::Runtime, Self::GrandpaInstance>::was_successful(
finality_proof_info.block_number,
) {
// we only refund relayer if all calls have updated chain state
log::trace!(
target: "runtime::bridge",
"{} via {:?}: relayer {:?} has submitted invalid relay chain finality proof",
Self::Id::STR,
<Self::Msgs as RefundableMessagesLaneId>::Id::get(),
relayer,
);
return slash_relayer_if_delivery_result
}
// there's a conflict between how bridge GRANDPA pallet works and a `utility.batchAll`
// transaction. If relay chain header is mandatory, the GRANDPA pallet returns
// `Pays::No`, because such transaction is mandatory for operating the bridge. But
// `utility.batchAll` transaction always requires payment. But in both cases we'll
// refund relayer - either explicitly here, or using `Pays::No` if he's choosing
// to submit dedicated transaction.
// submitter has means to include extra weight/bytes in the `submit_finality_proof`
// call, so let's subtract extra weight/size to avoid refunding for this extra stuff
extra_weight = finality_proof_info.extra_weight;
extra_size = finality_proof_info.extra_size;
}
// Check if the `ReceiveMessagesProof` call delivered at least some of the messages that
// it contained. If this happens, we consider the transaction "helpful" and refund it.
let msgs_call_info = call_info.messages_call_info();
@@ -391,8 +346,13 @@ where
return slash_relayer_if_delivery_result
}
// do additional check
if !Self::additional_call_result_check(&relayer, &call_info) {
// do additional checks
if !Self::additional_call_result_check(
&relayer,
&call_info,
&mut extra_weight,
&mut extra_size,
) {
return slash_relayer_if_delivery_result
}
@@ -468,18 +428,11 @@ where
RuntimeDebugNoBound,
TypeInfo,
)]
pub struct RefundSignedExtensionAdapter<T: RefundSignedExtension>(T)
where
<T::Runtime as GrandpaConfig<T::GrandpaInstance>>::BridgedChain:
Chain<BlockNumber = RelayBlockNumber>;
pub struct RefundSignedExtensionAdapter<T: RefundSignedExtension>(T);
impl<T: RefundSignedExtension> SignedExtension for RefundSignedExtensionAdapter<T>
where
<T::Runtime as GrandpaConfig<T::GrandpaInstance>>::BridgedChain:
Chain<BlockNumber = RelayBlockNumber>,
CallOf<T::Runtime>: Dispatchable<Info = DispatchInfo, PostInfo = PostDispatchInfo>
+ IsSubType<CallableCallFor<UtilityPallet<T::Runtime>, T::Runtime>>
+ GrandpaCallSubType<T::Runtime, T::GrandpaInstance>
+ MessagesCallSubType<T::Runtime, <T::Msgs as RefundableMessagesLaneId>::Instance>,
{
const IDENTIFIER: &'static str = T::Id::STR;
@@ -644,6 +597,14 @@ impl<Runtime, Para, Msgs, Refund, Priority, Id> RefundSignedExtension
for RefundBridgedParachainMessages<Runtime, Para, Msgs, Refund, Priority, Id>
where
Self: 'static + Send + Sync,
RefundBridgedGrandpaMessages<
Runtime,
Runtime::BridgesGrandpaPalletInstance,
Msgs,
Refund,
Priority,
Id,
>: 'static + Send + Sync,
Runtime: UtilityConfig<RuntimeCall = CallOf<Runtime>>
+ BoundedBridgeGrandpaConfig<Runtime::BridgesGrandpaPalletInstance>
+ ParachainsConfig<Para::Instance>
@@ -661,7 +622,6 @@ where
+ MessagesCallSubType<Runtime, Msgs::Instance>,
{
type Runtime = Runtime;
type GrandpaInstance = Runtime::BridgesGrandpaPalletInstance;
type Msgs = Msgs;
type Refund = Refund;
type Priority = Priority;
@@ -687,7 +647,7 @@ where
let para_finality_call = calls
.next()
.transpose()?
.and_then(|c| c.submit_parachain_heads_info_for(Para::Id::get()));
.and_then(|c| c.submit_parachain_heads_info_for(Para::BridgedChain::PARACHAIN_ID));
let relay_finality_call =
calls.next().transpose()?.and_then(|c| c.submit_finality_proof_info());
@@ -711,7 +671,26 @@ where
Ok(call)
}
fn additional_call_result_check(relayer: &Runtime::AccountId, call_info: &CallInfo) -> bool {
fn additional_call_result_check(
relayer: &Runtime::AccountId,
call_info: &CallInfo,
extra_weight: &mut Weight,
extra_size: &mut u32,
) -> bool {
// check if relay chain state has been updated
let is_grandpa_call_successful =
RefundBridgedGrandpaMessages::<
Runtime,
Runtime::BridgesGrandpaPalletInstance,
Msgs,
Refund,
Priority,
Id,
>::additional_call_result_check(relayer, call_info, extra_weight, extra_size);
if !is_grandpa_call_successful {
return false
}
// check if parachain state has been updated
if let Some(para_proof_info) = call_info.submit_parachain_heads_info() {
if !SubmitParachainHeadsHelper::<Runtime, Para::Instance>::was_successful(
@@ -722,7 +701,7 @@ where
target: "runtime::bridge",
"{} from parachain {} via {:?}: relayer {:?} has submitted invalid parachain finality proof",
Id::STR,
Para::Id::get(),
Para::BridgedChain::PARACHAIN_ID,
Msgs::Id::get(),
relayer,
);
@@ -794,7 +773,6 @@ where
+ MessagesCallSubType<Runtime, Msgs::Instance>,
{
type Runtime = Runtime;
type GrandpaInstance = GrandpaInstance;
type Msgs = Msgs;
type Refund = Refund;
type Priority = Priority;
@@ -836,13 +814,125 @@ where
Ok(call)
}
fn additional_call_result_check(_relayer: &Runtime::AccountId, _call_info: &CallInfo) -> bool {
fn additional_call_result_check(
relayer: &Runtime::AccountId,
call_info: &CallInfo,
extra_weight: &mut Weight,
extra_size: &mut u32,
) -> bool {
// check if relay chain state has been updated
if let Some(finality_proof_info) = call_info.submit_finality_proof_info() {
if !SubmitFinalityProofHelper::<Self::Runtime, GrandpaInstance>::was_successful(
finality_proof_info.block_number,
) {
// we only refund relayer if all calls have updated chain state
log::trace!(
target: "runtime::bridge",
"{} via {:?}: relayer {:?} has submitted invalid relay chain finality proof",
Self::Id::STR,
<Self::Msgs as RefundableMessagesLaneId>::Id::get(),
relayer,
);
return false
}
// there's a conflict between how bridge GRANDPA pallet works and a `utility.batchAll`
// transaction. If relay chain header is mandatory, the GRANDPA pallet returns
// `Pays::No`, because such transaction is mandatory for operating the bridge. But
// `utility.batchAll` transaction always requires payment. But in both cases we'll
// refund relayer - either explicitly here, or using `Pays::No` if he's choosing
// to submit dedicated transaction.
// submitter has means to include extra weight/bytes in the `submit_finality_proof`
// call, so let's subtract extra weight/size to avoid refunding for this extra stuff
*extra_weight = (*extra_weight).saturating_add(finality_proof_info.extra_weight);
*extra_size = (*extra_size).saturating_add(finality_proof_info.extra_size);
}
true
}
}
/// Transaction extension that refunds a relayer for standalone messages delivery and confirmation
/// transactions. Finality transactions are not refunded.
#[derive(
DefaultNoBound,
CloneNoBound,
Decode,
Encode,
EqNoBound,
PartialEqNoBound,
RuntimeDebugNoBound,
TypeInfo,
)]
#[scale_info(skip_type_params(Runtime, GrandpaInstance, Msgs, Refund, Priority, Id))]
pub struct RefundBridgedMessages<Runtime, Msgs, Refund, Priority, Id>(
PhantomData<(
// runtime with `pallet-bridge-messages` and `pallet-bridge-relayers` pallets deployed
Runtime,
// implementation of `RefundableMessagesLaneId` trait, which specifies the instance of
// the used `pallet-bridge-messages` pallet and the lane within this pallet
Msgs,
// implementation of the `RefundCalculator` trait, that is used to compute refund that
// we give to relayer for his transaction
Refund,
// getter for per-message `TransactionPriority` boost that we give to message
// delivery transactions
Priority,
// the runtime-unique identifier of this signed extension
Id,
)>,
);
impl<Runtime, Msgs, Refund, Priority, Id> RefundSignedExtension
for RefundBridgedMessages<Runtime, Msgs, Refund, Priority, Id>
where
Self: 'static + Send + Sync,
Runtime: MessagesConfig<Msgs::Instance> + RelayersConfig,
Msgs: RefundableMessagesLaneId,
Refund: RefundCalculator<Balance = Runtime::Reward>,
Priority: Get<TransactionPriority>,
Id: StaticStrProvider,
CallOf<Runtime>: Dispatchable<Info = DispatchInfo, PostInfo = PostDispatchInfo>
+ MessagesCallSubType<Runtime, Msgs::Instance>,
{
type Runtime = Runtime;
type Msgs = Msgs;
type Refund = Refund;
type Priority = Priority;
type Id = Id;
fn expand_call(call: &CallOf<Runtime>) -> Vec<&CallOf<Runtime>> {
vec![call]
}
fn parse_and_check_for_obsolete_call(
call: &CallOf<Runtime>,
) -> Result<Option<CallInfo>, TransactionValidityError> {
let call = Self::check_obsolete_parsed_call(call)?;
Ok(call.call_info_for(Msgs::Id::get()).map(CallInfo::Msgs))
}
fn check_obsolete_parsed_call(
call: &CallOf<Runtime>,
) -> Result<&CallOf<Runtime>, TransactionValidityError> {
call.check_obsolete_call()?;
Ok(call)
}
fn additional_call_result_check(
_relayer: &Runtime::AccountId,
_call_info: &CallInfo,
_extra_weight: &mut Weight,
_extra_size: &mut u32,
) -> bool {
// everything is checked by the `RefundTransactionExtension`
true
}
}
#[cfg(test)]
mod tests {
pub(crate) mod tests {
use super::*;
use crate::{
messages::{
@@ -854,6 +944,7 @@ mod tests {
},
mock::*,
};
use bp_header_chain::StoredHeaderDataBuilder;
use bp_messages::{
DeliveredMessages, InboundLaneData, MessageNonce, MessagesOperatingMode, OutboundLaneData,
UnrewardedRelayer, UnrewardedRelayersState,
@@ -879,7 +970,6 @@ mod tests {
};
parameter_types! {
TestParachain: u32 = 1000;
pub TestLaneId: LaneId = TEST_LANE_ID;
pub MsgProofsRewardsAccount: RewardsAccountParams = RewardsAccountParams::new(
TEST_LANE_ID,
@@ -895,6 +985,14 @@ mod tests {
bp_runtime::generate_static_str_provider!(TestExtension);
type TestMessagesExtensionProvider = RefundBridgedMessages<
TestRuntime,
RefundableMessagesLane<(), TestLaneId>,
ActualFeeRefund<TestRuntime>,
ConstU64<1>,
StrTestExtension,
>;
type TestMessagesExtension = RefundSignedExtensionAdapter<TestMessagesExtensionProvider>;
type TestGrandpaExtensionProvider = RefundBridgedGrandpaMessages<
TestRuntime,
(),
@@ -906,7 +1004,7 @@ mod tests {
type TestGrandpaExtension = RefundSignedExtensionAdapter<TestGrandpaExtensionProvider>;
type TestExtensionProvider = RefundBridgedParachainMessages<
TestRuntime,
DefaultRefundableParachainId<(), TestParachain>,
RefundableParachain<(), BridgedUnderlyingParachain>,
RefundableMessagesLane<(), TestLaneId>,
ActualFeeRefund<TestRuntime>,
ConstU64<1>,
@@ -930,7 +1028,7 @@ mod tests {
TestPaymentProcedure::rewards_account(MsgDeliveryProofsRewardsAccount::get())
}
fn relayer_account_at_this_chain() -> ThisChainAccountId {
pub fn relayer_account_at_this_chain() -> ThisChainAccountId {
0
}
@@ -938,7 +1036,7 @@ mod tests {
0
}
fn initialize_environment(
pub fn initialize_environment(
best_relay_header_number: RelayBlockNumber,
parachain_head_at_relay_header_number: RelayBlockNumber,
best_message: MessageNonce,
@@ -949,8 +1047,12 @@ mod tests {
StoredAuthoritySet::try_new(authorities, TEST_GRANDPA_SET_ID).unwrap(),
);
pallet_bridge_grandpa::BestFinalized::<TestRuntime>::put(best_relay_header);
pallet_bridge_grandpa::ImportedHeaders::<TestRuntime>::insert(
best_relay_header.hash(),
bp_test_utils::test_header::<BridgedChainHeader>(0).build(),
);
let para_id = ParaId(TestParachain::get());
let para_id = ParaId(BridgedUnderlyingParachain::PARACHAIN_ID);
let para_info = ParaInfo {
best_head_hash: BestParaHeadHash {
at_relay_block_number: parachain_head_at_relay_header_number,
@@ -994,7 +1096,7 @@ mod tests {
})
}
fn submit_relay_header_call_ex(relay_header_number: RelayBlockNumber) -> RuntimeCall {
pub fn submit_relay_header_call_ex(relay_header_number: RelayBlockNumber) -> RuntimeCall {
let relay_header = BridgedChainHeader::new(
relay_header_number,
Default::default(),
@@ -1008,6 +1110,7 @@ mod tests {
finality_target: Box::new(relay_header),
justification: relay_justification,
current_set_id: TEST_GRANDPA_SET_ID,
is_free_execution_expected: false,
})
}
@@ -1017,13 +1120,27 @@ mod tests {
RuntimeCall::BridgeParachains(ParachainsCall::submit_parachain_heads {
at_relay_block: (parachain_head_at_relay_header_number, RelayBlockHash::default()),
parachains: vec![(
ParaId(TestParachain::get()),
ParaId(BridgedUnderlyingParachain::PARACHAIN_ID),
[parachain_head_at_relay_header_number as u8; 32].into(),
)],
parachain_heads_proof: ParaHeadsProof { storage_proof: vec![] },
})
}
pub fn submit_parachain_head_call_ex(
parachain_head_at_relay_header_number: RelayBlockNumber,
) -> RuntimeCall {
RuntimeCall::BridgeParachains(ParachainsCall::submit_parachain_heads_ex {
at_relay_block: (parachain_head_at_relay_header_number, RelayBlockHash::default()),
parachains: vec![(
ParaId(BridgedUnderlyingParachain::PARACHAIN_ID),
[parachain_head_at_relay_header_number as u8; 32].into(),
)],
parachain_heads_proof: ParaHeadsProof { storage_proof: vec![] },
is_free_execution_expected: false,
})
}
fn message_delivery_call(best_message: MessageNonce) -> RuntimeCall {
RuntimeCall::BridgeMessages(MessagesCall::receive_messages_proof {
relayer_id_at_bridged_chain: relayer_account_at_bridged_chain(),
@@ -1151,7 +1268,7 @@ mod tests {
RuntimeCall::Utility(UtilityCall::batch_all {
calls: vec![
submit_relay_header_call_ex(relay_header_number),
submit_parachain_head_call(parachain_head_at_relay_header_number),
submit_parachain_head_call_ex(parachain_head_at_relay_header_number),
message_delivery_call(best_message),
],
})
@@ -1179,7 +1296,7 @@ mod tests {
RuntimeCall::Utility(UtilityCall::batch_all {
calls: vec![
submit_relay_header_call_ex(relay_header_number),
submit_parachain_head_call(parachain_head_at_relay_header_number),
submit_parachain_head_call_ex(parachain_head_at_relay_header_number),
message_confirmation_call(best_message),
],
})
@@ -1194,11 +1311,14 @@ mod tests {
current_set_id: None,
extra_weight: Weight::zero(),
extra_size: 0,
is_mandatory: false,
is_free_execution_expected: false,
},
SubmitParachainHeadsInfo {
at_relay_block_number: 200,
para_id: ParaId(TestParachain::get()),
at_relay_block: HeaderId(200, [0u8; 32].into()),
para_id: ParaId(BridgedUnderlyingParachain::PARACHAIN_ID),
para_head_hash: [200u8; 32].into(),
is_free_execution_expected: false,
},
MessagesCallInfo::ReceiveMessagesProof(ReceiveMessagesProofInfo {
base: BaseMessagesProofInfo {
@@ -1231,11 +1351,14 @@ mod tests {
current_set_id: None,
extra_weight: Weight::zero(),
extra_size: 0,
is_mandatory: false,
is_free_execution_expected: false,
},
SubmitParachainHeadsInfo {
at_relay_block_number: 200,
para_id: ParaId(TestParachain::get()),
at_relay_block: HeaderId(200, [0u8; 32].into()),
para_id: ParaId(BridgedUnderlyingParachain::PARACHAIN_ID),
para_head_hash: [200u8; 32].into(),
is_free_execution_expected: false,
},
MessagesCallInfo::ReceiveMessagesDeliveryProof(ReceiveMessagesDeliveryProofInfo(
BaseMessagesProofInfo {
@@ -1264,6 +1387,8 @@ mod tests {
current_set_id: None,
extra_weight: Weight::zero(),
extra_size: 0,
is_mandatory: false,
is_free_execution_expected: false,
},
MessagesCallInfo::ReceiveMessagesProof(ReceiveMessagesProofInfo {
base: BaseMessagesProofInfo {
@@ -1296,6 +1421,8 @@ mod tests {
current_set_id: None,
extra_weight: Weight::zero(),
extra_size: 0,
is_mandatory: false,
is_free_execution_expected: false,
},
MessagesCallInfo::ReceiveMessagesDeliveryProof(ReceiveMessagesDeliveryProofInfo(
BaseMessagesProofInfo {
@@ -1320,9 +1447,10 @@ mod tests {
relayer: relayer_account_at_this_chain(),
call_info: CallInfo::ParachainFinalityAndMsgs(
SubmitParachainHeadsInfo {
at_relay_block_number: 200,
para_id: ParaId(TestParachain::get()),
at_relay_block: HeaderId(200, [0u8; 32].into()),
para_id: ParaId(BridgedUnderlyingParachain::PARACHAIN_ID),
para_head_hash: [200u8; 32].into(),
is_free_execution_expected: false,
},
MessagesCallInfo::ReceiveMessagesProof(ReceiveMessagesProofInfo {
base: BaseMessagesProofInfo {
@@ -1344,9 +1472,10 @@ mod tests {
relayer: relayer_account_at_this_chain(),
call_info: CallInfo::ParachainFinalityAndMsgs(
SubmitParachainHeadsInfo {
at_relay_block_number: 200,
para_id: ParaId(TestParachain::get()),
at_relay_block: HeaderId(200, [0u8; 32].into()),
para_id: ParaId(BridgedUnderlyingParachain::PARACHAIN_ID),
para_head_hash: [200u8; 32].into(),
is_free_execution_expected: false,
},
MessagesCallInfo::ReceiveMessagesDeliveryProof(ReceiveMessagesDeliveryProofInfo(
BaseMessagesProofInfo {
@@ -1421,8 +1550,14 @@ mod tests {
extension.validate(&relayer_account_at_this_chain(), &call, &DispatchInfo::default(), 0)
}
fn run_validate_ignore_priority(call: RuntimeCall) -> TransactionValidity {
run_validate(call).map(|mut tx| {
fn run_messages_validate(call: RuntimeCall) -> TransactionValidity {
let extension: TestMessagesExtension =
RefundSignedExtensionAdapter(RefundBridgedMessages(PhantomData));
extension.validate(&relayer_account_at_this_chain(), &call, &DispatchInfo::default(), 0)
}
fn ignore_priority(tx: TransactionValidity) -> TransactionValidity {
tx.map(|mut tx| {
tx.priority = 0;
tx
})
@@ -1444,6 +1579,14 @@ mod tests {
extension.pre_dispatch(&relayer_account_at_this_chain(), &call, &DispatchInfo::default(), 0)
}
fn run_messages_pre_dispatch(
call: RuntimeCall,
) -> Result<Option<PreDispatchData<ThisChainAccountId>>, TransactionValidityError> {
let extension: TestMessagesExtension =
RefundSignedExtensionAdapter(RefundBridgedMessages(PhantomData));
extension.pre_dispatch(&relayer_account_at_this_chain(), &call, &DispatchInfo::default(), 0)
}
fn dispatch_info() -> DispatchInfo {
DispatchInfo {
weight: Weight::from_parts(
@@ -1502,40 +1645,48 @@ mod tests {
Balances::set_balance(&relayer_account_at_this_chain(), ExistentialDeposit::get());
// message delivery is failing
assert_eq!(run_validate(message_delivery_call(200)), Ok(Default::default()),);
assert_eq!(
run_validate(parachain_finality_and_delivery_batch_call(200, 200)),
Ok(Default::default()),
);
assert_eq!(
run_validate(all_finality_and_delivery_batch_call(200, 200, 200)),
Ok(Default::default()),
);
assert_eq!(
run_validate(all_finality_and_delivery_batch_call_ex(200, 200, 200)),
Ok(Default::default()),
);
let fns = [run_validate, run_grandpa_validate, run_messages_validate];
for f in fns {
assert_eq!(f(message_delivery_call(200)), Ok(Default::default()),);
assert_eq!(
f(parachain_finality_and_delivery_batch_call(200, 200)),
Ok(Default::default()),
);
assert_eq!(
f(all_finality_and_delivery_batch_call(200, 200, 200)),
Ok(Default::default()),
);
assert_eq!(
f(all_finality_and_delivery_batch_call_ex(200, 200, 200)),
Ok(Default::default()),
);
}
// message confirmation validation is passing
assert_eq!(
run_validate_ignore_priority(message_confirmation_call(200)),
ignore_priority(run_validate(message_confirmation_call(200))),
Ok(Default::default()),
);
assert_eq!(
run_validate_ignore_priority(parachain_finality_and_confirmation_batch_call(
ignore_priority(run_messages_validate(message_confirmation_call(200))),
Ok(Default::default()),
);
assert_eq!(
ignore_priority(run_validate(parachain_finality_and_confirmation_batch_call(
200, 200
)),
))),
Ok(Default::default()),
);
assert_eq!(
run_validate_ignore_priority(all_finality_and_confirmation_batch_call(
ignore_priority(run_validate(all_finality_and_confirmation_batch_call(
200, 200, 200
)),
))),
Ok(Default::default()),
);
assert_eq!(
run_validate_ignore_priority(all_finality_and_confirmation_batch_call_ex(
ignore_priority(run_validate(all_finality_and_confirmation_batch_call_ex(
200, 200, 200
)),
))),
Ok(Default::default()),
);
});
@@ -1549,25 +1700,28 @@ mod tests {
BridgeRelayers::register(RuntimeOrigin::signed(relayer_account_at_this_chain()), 1000)
.unwrap();
let priority_of_100_messages_delivery =
run_validate(message_delivery_call(200)).unwrap().priority;
let priority_of_200_messages_delivery =
run_validate(message_delivery_call(300)).unwrap().priority;
assert!(
priority_of_200_messages_delivery > priority_of_100_messages_delivery,
"Invalid priorities: {} for 200 messages vs {} for 100 messages",
priority_of_200_messages_delivery,
priority_of_100_messages_delivery,
);
let fns = [run_validate, run_grandpa_validate, run_messages_validate];
for f in fns {
let priority_of_100_messages_delivery =
f(message_delivery_call(200)).unwrap().priority;
let priority_of_200_messages_delivery =
f(message_delivery_call(300)).unwrap().priority;
assert!(
priority_of_200_messages_delivery > priority_of_100_messages_delivery,
"Invalid priorities: {} for 200 messages vs {} for 100 messages",
priority_of_200_messages_delivery,
priority_of_100_messages_delivery,
);
let priority_of_100_messages_confirmation =
run_validate(message_confirmation_call(200)).unwrap().priority;
let priority_of_200_messages_confirmation =
run_validate(message_confirmation_call(300)).unwrap().priority;
assert_eq!(
priority_of_100_messages_confirmation,
priority_of_200_messages_confirmation
);
let priority_of_100_messages_confirmation =
f(message_confirmation_call(200)).unwrap().priority;
let priority_of_200_messages_confirmation =
f(message_confirmation_call(300)).unwrap().priority;
assert_eq!(
priority_of_100_messages_confirmation,
priority_of_200_messages_confirmation
);
}
});
}
@@ -1579,23 +1733,24 @@ mod tests {
BridgeRelayers::register(RuntimeOrigin::signed(relayer_account_at_this_chain()), 1000)
.unwrap();
let priority_of_max_messages_delivery = run_validate(message_delivery_call(
100 + MaxUnconfirmedMessagesAtInboundLane::get(),
))
.unwrap()
.priority;
let priority_of_more_than_max_messages_delivery = run_validate(message_delivery_call(
100 + MaxUnconfirmedMessagesAtInboundLane::get() + 1,
))
.unwrap()
.priority;
let fns = [run_validate, run_grandpa_validate, run_messages_validate];
for f in fns {
let priority_of_max_messages_delivery =
f(message_delivery_call(100 + MaxUnconfirmedMessagesAtInboundLane::get()))
.unwrap()
.priority;
let priority_of_more_than_max_messages_delivery =
f(message_delivery_call(100 + MaxUnconfirmedMessagesAtInboundLane::get() + 1))
.unwrap()
.priority;
assert!(
priority_of_max_messages_delivery > priority_of_more_than_max_messages_delivery,
"Invalid priorities: {} for MAX messages vs {} for MAX+1 messages",
priority_of_max_messages_delivery,
priority_of_more_than_max_messages_delivery,
);
assert!(
priority_of_max_messages_delivery > priority_of_more_than_max_messages_delivery,
"Invalid priorities: {} for MAX messages vs {} for MAX+1 messages",
priority_of_max_messages_delivery,
priority_of_more_than_max_messages_delivery,
);
}
});
}
@@ -1605,45 +1760,54 @@ mod tests {
initialize_environment(100, 100, 100);
assert_eq!(
run_validate_ignore_priority(message_delivery_call(200)),
ignore_priority(run_validate(message_delivery_call(200))),
Ok(ValidTransaction::default()),
);
assert_eq!(
run_validate_ignore_priority(message_confirmation_call(200)),
ignore_priority(run_validate(message_confirmation_call(200))),
Ok(ValidTransaction::default()),
);
assert_eq!(
run_validate_ignore_priority(parachain_finality_and_delivery_batch_call(200, 200)),
ignore_priority(run_messages_validate(message_delivery_call(200))),
Ok(ValidTransaction::default()),
);
assert_eq!(
run_validate_ignore_priority(parachain_finality_and_confirmation_batch_call(
ignore_priority(run_messages_validate(message_confirmation_call(200))),
Ok(ValidTransaction::default()),
);
assert_eq!(
ignore_priority(run_validate(parachain_finality_and_delivery_batch_call(200, 200))),
Ok(ValidTransaction::default()),
);
assert_eq!(
ignore_priority(run_validate(parachain_finality_and_confirmation_batch_call(
200, 200
)),
))),
Ok(ValidTransaction::default()),
);
assert_eq!(
run_validate_ignore_priority(all_finality_and_delivery_batch_call(200, 200, 200)),
ignore_priority(run_validate(all_finality_and_delivery_batch_call(200, 200, 200))),
Ok(ValidTransaction::default()),
);
assert_eq!(
run_validate_ignore_priority(all_finality_and_delivery_batch_call_ex(
ignore_priority(run_validate(all_finality_and_delivery_batch_call_ex(
200, 200, 200
)),
))),
Ok(ValidTransaction::default()),
);
assert_eq!(
run_validate_ignore_priority(all_finality_and_confirmation_batch_call(
ignore_priority(run_validate(all_finality_and_confirmation_batch_call(
200, 200, 200
)),
))),
Ok(ValidTransaction::default()),
);
assert_eq!(
run_validate_ignore_priority(all_finality_and_confirmation_batch_call_ex(
ignore_priority(run_validate(all_finality_and_confirmation_batch_call_ex(
200, 200, 200
)),
))),
Ok(ValidTransaction::default()),
);
});
@@ -1933,8 +2097,11 @@ mod tests {
RuntimeCall::BridgeParachains(ParachainsCall::submit_parachain_heads {
at_relay_block: (100, RelayBlockHash::default()),
parachains: vec![
(ParaId(TestParachain::get()), [1u8; 32].into()),
(ParaId(TestParachain::get() + 1), [1u8; 32].into()),
(ParaId(BridgedUnderlyingParachain::PARACHAIN_ID), [1u8; 32].into()),
(
ParaId(BridgedUnderlyingParachain::PARACHAIN_ID + 1),
[1u8; 32].into(),
),
],
parachain_heads_proof: ParaHeadsProof { storage_proof: vec![] },
}),
@@ -2318,6 +2485,148 @@ mod tests {
});
}
#[test]
fn messages_ext_only_parses_standalone_transactions() {
run_test(|| {
initialize_environment(100, 100, 100);
// relay + parachain + message delivery calls batch is ignored
assert_eq!(
TestMessagesExtensionProvider::parse_and_check_for_obsolete_call(
&all_finality_and_delivery_batch_call(200, 200, 200)
),
Ok(None),
);
assert_eq!(
TestMessagesExtensionProvider::parse_and_check_for_obsolete_call(
&all_finality_and_delivery_batch_call_ex(200, 200, 200)
),
Ok(None),
);
// relay + parachain + message confirmation calls batch is ignored
assert_eq!(
TestMessagesExtensionProvider::parse_and_check_for_obsolete_call(
&all_finality_and_confirmation_batch_call(200, 200, 200)
),
Ok(None),
);
assert_eq!(
TestMessagesExtensionProvider::parse_and_check_for_obsolete_call(
&all_finality_and_confirmation_batch_call_ex(200, 200, 200)
),
Ok(None),
);
// parachain + message delivery call batch is ignored
assert_eq!(
TestMessagesExtensionProvider::parse_and_check_for_obsolete_call(
&parachain_finality_and_delivery_batch_call(200, 200)
),
Ok(None),
);
// parachain + message confirmation call batch is ignored
assert_eq!(
TestMessagesExtensionProvider::parse_and_check_for_obsolete_call(
&parachain_finality_and_confirmation_batch_call(200, 200)
),
Ok(None),
);
// relay + message delivery call batch is ignored
assert_eq!(
TestMessagesExtensionProvider::parse_and_check_for_obsolete_call(
&relay_finality_and_delivery_batch_call(200, 200)
),
Ok(None),
);
assert_eq!(
TestMessagesExtensionProvider::parse_and_check_for_obsolete_call(
&relay_finality_and_delivery_batch_call_ex(200, 200)
),
Ok(None),
);
// relay + message confirmation call batch is ignored
assert_eq!(
TestMessagesExtensionProvider::parse_and_check_for_obsolete_call(
&relay_finality_and_confirmation_batch_call(200, 200)
),
Ok(None),
);
assert_eq!(
TestMessagesExtensionProvider::parse_and_check_for_obsolete_call(
&relay_finality_and_confirmation_batch_call_ex(200, 200)
),
Ok(None),
);
// message delivery call batch is accepted
assert_eq!(
TestMessagesExtensionProvider::parse_and_check_for_obsolete_call(
&message_delivery_call(200)
),
Ok(Some(delivery_pre_dispatch_data().call_info)),
);
// message confirmation call batch is accepted
assert_eq!(
TestMessagesExtensionProvider::parse_and_check_for_obsolete_call(
&message_confirmation_call(200)
),
Ok(Some(confirmation_pre_dispatch_data().call_info)),
);
});
}
#[test]
fn messages_ext_rejects_calls_with_obsolete_messages() {
run_test(|| {
initialize_environment(100, 100, 100);
assert_eq!(
run_messages_pre_dispatch(message_delivery_call(100)),
Err(TransactionValidityError::Invalid(InvalidTransaction::Stale)),
);
assert_eq!(
run_messages_pre_dispatch(message_confirmation_call(100)),
Err(TransactionValidityError::Invalid(InvalidTransaction::Stale)),
);
assert_eq!(
run_messages_validate(message_delivery_call(100)),
Err(TransactionValidityError::Invalid(InvalidTransaction::Stale)),
);
assert_eq!(
run_messages_validate(message_confirmation_call(100)),
Err(TransactionValidityError::Invalid(InvalidTransaction::Stale)),
);
});
}
#[test]
fn messages_ext_accepts_calls_with_new_messages() {
run_test(|| {
initialize_environment(100, 100, 100);
assert_eq!(
run_messages_pre_dispatch(message_delivery_call(200)),
Ok(Some(delivery_pre_dispatch_data())),
);
assert_eq!(
run_messages_pre_dispatch(message_confirmation_call(200)),
Ok(Some(confirmation_pre_dispatch_data())),
);
assert_eq!(run_messages_validate(message_delivery_call(200)), Ok(Default::default()),);
assert_eq!(
run_messages_validate(message_confirmation_call(200)),
Ok(Default::default()),
);
});
}
#[test]
fn grandpa_ext_only_parses_valid_batches() {
run_test(|| {
+3 -1
View File
@@ -183,7 +183,8 @@ impl pallet_transaction_payment::Config for TestRuntime {
impl pallet_bridge_grandpa::Config for TestRuntime {
type RuntimeEvent = RuntimeEvent;
type BridgedChain = BridgedUnderlyingChain;
type MaxFreeMandatoryHeadersPerBlock = ConstU32<4>;
type MaxFreeHeadersPerBlock = ConstU32<4>;
type FreeHeadersInterval = ConstU32<1_024>;
type HeadersToKeep = ConstU32<8>;
type WeightInfo = pallet_bridge_grandpa::weights::BridgeWeight<TestRuntime>;
}
@@ -406,6 +407,7 @@ impl Chain for BridgedUnderlyingParachain {
impl Parachain for BridgedUnderlyingParachain {
const PARACHAIN_ID: u32 = 42;
const MAX_HEADER_SIZE: u32 = 1_024;
}
/// The other, bridged chain, used in tests.