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
+3 -1
View File
@@ -12,4 +12,6 @@ include:
# polkadot tests
- .gitlab/pipeline/zombienet/polkadot.yml
# bridges tests
- .gitlab/pipeline/zombienet/bridges.yml
# TODO: https://github.com/paritytech/parity-bridges-common/pull/2884
# commenting until we have a new relatye, compatible with updated fees scheme
# - .gitlab/pipeline/zombienet/bridges.yml
Generated
+9
View File
@@ -2153,6 +2153,7 @@ dependencies = [
"static_assertions",
"substrate-wasm-builder",
"testnet-parachains-constants",
"tuplex",
]
[[package]]
@@ -2311,6 +2312,7 @@ dependencies = [
"static_assertions",
"substrate-wasm-builder",
"testnet-parachains-constants",
"tuplex",
"westend-runtime-constants",
]
@@ -2349,6 +2351,7 @@ dependencies = [
"staging-xcm",
"staging-xcm-builder",
"static_assertions",
"tuplex",
]
[[package]]
@@ -22046,6 +22049,12 @@ dependencies = [
"utf-8",
]
[[package]]
name = "tuplex"
version = "0.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "676ac81d5454c4dcf37955d34fa8626ede3490f744b86ca14a7b90168d2a08aa"
[[package]]
name = "twox-hash"
version = "1.6.3"
+2
View File
@@ -16,6 +16,7 @@ hash-db = { version = "0.16.0", default-features = false }
log = { workspace = true }
scale-info = { version = "2.11.1", default-features = false, features = ["derive"] }
static_assertions = { version = "1.1", optional = true }
tuplex = { version = "0.1", default-features = false }
# Bridge dependencies
@@ -82,6 +83,7 @@ std = [
"sp-runtime/std",
"sp-std/std",
"sp-trie/std",
"tuplex/std",
"xcm-builder/std",
"xcm/std",
]
@@ -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.
@@ -39,6 +39,9 @@ use frame_support::{
use frame_system::limits;
use sp_std::time::Duration;
/// Maximal bridge hub header size.
pub const MAX_BRIDGE_HUB_HEADER_SIZE: u32 = 4_096;
/// Average block interval in Cumulus-based parachains.
///
/// Corresponds to the `MILLISECS_PER_BLOCK` from `parachains_common` crate.
@@ -62,6 +62,7 @@ impl Chain for BridgeHubKusama {
impl Parachain for BridgeHubKusama {
const PARACHAIN_ID: u32 = BRIDGE_HUB_KUSAMA_PARACHAIN_ID;
const MAX_HEADER_SIZE: u32 = MAX_BRIDGE_HUB_HEADER_SIZE;
}
impl ChainWithMessages for BridgeHubKusama {
@@ -59,6 +59,7 @@ impl Chain for BridgeHubPolkadot {
impl Parachain for BridgeHubPolkadot {
const PARACHAIN_ID: u32 = BRIDGE_HUB_POLKADOT_PARACHAIN_ID;
const MAX_HEADER_SIZE: u32 = MAX_BRIDGE_HUB_HEADER_SIZE;
}
impl ChainWithMessages for BridgeHubPolkadot {
@@ -59,6 +59,7 @@ impl Chain for BridgeHubRococo {
impl Parachain for BridgeHubRococo {
const PARACHAIN_ID: u32 = BRIDGE_HUB_ROCOCO_PARACHAIN_ID;
const MAX_HEADER_SIZE: u32 = MAX_BRIDGE_HUB_HEADER_SIZE;
}
impl ChainWithMessages for BridgeHubRococo {
@@ -103,9 +104,9 @@ frame_support::parameter_types! {
/// Transaction fee that is paid at the Rococo BridgeHub for delivering single inbound message.
/// (initially was calculated by test `BridgeHubRococo::can_calculate_fee_for_complex_message_delivery_transaction` + `33%`)
pub const BridgeHubRococoBaseDeliveryFeeInRocs: u128 = 5_651_581_649;
pub const BridgeHubRococoBaseDeliveryFeeInRocs: u128 = 314_037_860;
/// Transaction fee that is paid at the Rococo BridgeHub for delivering single outbound message confirmation.
/// (initially was calculated by test `BridgeHubRococo::can_calculate_fee_for_complex_message_confirmation_transaction` + `33%`)
pub const BridgeHubRococoBaseConfirmationFeeInRocs: u128 = 5_380_901_781;
pub const BridgeHubRococoBaseConfirmationFeeInRocs: u128 = 57_414_813;
}
@@ -58,6 +58,7 @@ impl Chain for BridgeHubWestend {
impl Parachain for BridgeHubWestend {
const PARACHAIN_ID: u32 = BRIDGE_HUB_WESTEND_PARACHAIN_ID;
const MAX_HEADER_SIZE: u32 = MAX_BRIDGE_HUB_HEADER_SIZE;
}
impl ChainWithMessages for BridgeHubWestend {
@@ -93,10 +94,10 @@ frame_support::parameter_types! {
pub const BridgeHubWestendBaseXcmFeeInWnds: u128 = 17_756_830_000;
/// Transaction fee that is paid at the Westend BridgeHub for delivering single inbound message.
/// (initially was calculated by test `BridgeHubWestend::can_calculate_fee_for_complex_message_delivery_transaction` + `33%`)
pub const BridgeHubWestendBaseDeliveryFeeInWnds: u128 = 1_695_489_961_344;
/// (initially was calculated by test `BridgeHubWestend::can_calculate_fee_for_standalone_message_delivery_transaction` + `33%`)
pub const BridgeHubWestendBaseDeliveryFeeInWnds: u128 = 94_211_536_452;
/// Transaction fee that is paid at the Westend BridgeHub for delivering single outbound message confirmation.
/// (initially was calculated by test `BridgeHubWestend::can_calculate_fee_for_complex_message_confirmation_transaction` + `33%`)
pub const BridgeHubWestendBaseConfirmationFeeInWnds: u128 = 1_618_309_961_344;
/// (initially was calculated by test `BridgeHubWestend::can_calculate_fee_for_standalone_message_confirmation_transaction` + `33%`)
pub const BridgeHubWestendBaseConfirmationFeeInWnds: u128 = 17_224_486_452;
}
+323 -35
View File
@@ -15,20 +15,24 @@
// along with Parity Bridges Common. If not, see <http://www.gnu.org/licenses/>.
use crate::{
weights::WeightInfo, BridgedBlockNumber, BridgedHeader, Config, CurrentAuthoritySet, Error,
Pallet,
weights::WeightInfo, BestFinalized, BridgedBlockNumber, BridgedHeader, Config,
CurrentAuthoritySet, Error, FreeHeadersRemaining, Pallet,
};
use bp_header_chain::{
justification::GrandpaJustification, max_expected_submit_finality_proof_arguments_size,
ChainWithGrandpa, GrandpaConsensusLogReader,
};
use bp_runtime::{BlockNumberOf, OwnedBridgeModule};
use bp_runtime::{BlockNumberOf, Chain, OwnedBridgeModule};
use codec::Encode;
use frame_support::{dispatch::CallableCallFor, traits::IsSubType, weights::Weight};
use frame_support::{
dispatch::CallableCallFor,
traits::{Get, IsSubType},
weights::Weight,
};
use sp_consensus_grandpa::SetId;
use sp_runtime::{
traits::{Header, Zero},
transaction_validity::{InvalidTransaction, TransactionValidity, ValidTransaction},
traits::{CheckedSub, Header, Zero},
transaction_validity::{InvalidTransaction, TransactionValidityError},
RuntimeDebug, SaturatedConversion,
};
@@ -40,6 +44,11 @@ pub struct SubmitFinalityProofInfo<N> {
/// An identifier of the validators set that has signed the submitted justification.
/// It might be `None` if deprecated version of the `submit_finality_proof` is used.
pub current_set_id: Option<SetId>,
/// If `true`, then the call proves new **mandatory** header.
pub is_mandatory: bool,
/// If `true`, then the call must be free (assuming that everything else is valid) to
/// be treated as valid.
pub is_free_execution_expected: bool,
/// Extra weight that we assume is included in the call.
///
/// We have some assumptions about headers and justifications of the bridged chain.
@@ -54,6 +63,16 @@ pub struct SubmitFinalityProofInfo<N> {
pub extra_size: u32,
}
/// Verified `SubmitFinalityProofInfo<N>`.
#[derive(Copy, Clone, PartialEq, RuntimeDebug)]
pub struct VerifiedSubmitFinalityProofInfo<N> {
/// Base call information.
pub base: SubmitFinalityProofInfo<N>,
/// A difference between bundled bridged header and best bridged header known to us
/// before the call.
pub improved_by: N,
}
impl<N> SubmitFinalityProofInfo<N> {
/// Returns `true` if call size/weight is below our estimations for regular calls.
pub fn fits_limits(&self) -> bool {
@@ -67,14 +86,86 @@ pub struct SubmitFinalityProofHelper<T: Config<I>, I: 'static> {
}
impl<T: Config<I>, I: 'static> SubmitFinalityProofHelper<T, I> {
/// Returns `true` if we may fit more free headers into the current block. If `false` is
/// returned, the call will be paid even if `is_free_execution_expected` has been set
/// to `true`.
pub fn has_free_header_slots() -> bool {
// `unwrap_or(u32::MAX)` means that if `FreeHeadersRemaining` is `None`, we may accept
// this header for free. That is a small cheat - it is `None` if executed outside of
// transaction (e.g. during block initialization). Normal relayer would never submit
// such calls, but if he did, that is not our problem. During normal transactions,
// the `FreeHeadersRemaining` is always `Some(_)`.
let free_headers_remaining = FreeHeadersRemaining::<T, I>::get().unwrap_or(u32::MAX);
free_headers_remaining > 0
}
/// Check that the: (1) GRANDPA head provided by the `SubmitFinalityProof` is better than the
/// best one we know (2) if `current_set_id` matches the current authority set id, if specified
/// and (3) whether transaction MAY be free for the submitter if `is_free_execution_expected`
/// is `true`.
///
/// Returns number of headers between the current best finalized header, known to the pallet
/// and the bundled header.
pub fn check_obsolete_from_extension(
call_info: &SubmitFinalityProofInfo<BlockNumberOf<T::BridgedChain>>,
) -> Result<BlockNumberOf<T::BridgedChain>, Error<T, I>> {
// do basic checks first
let improved_by = Self::check_obsolete(call_info.block_number, call_info.current_set_id)?;
// if submitter has NOT specified that it wants free execution, then we are done
if !call_info.is_free_execution_expected {
return Ok(improved_by);
}
// else - if we can not accept more free headers, "reject" the transaction
if !Self::has_free_header_slots() {
log::trace!(
target: crate::LOG_TARGET,
"Cannot accept free {:?} header {:?}. No more free slots remaining",
T::BridgedChain::ID,
call_info.block_number,
);
return Err(Error::<T, I>::FreeHeadersLimitExceded);
}
// ensure that the `improved_by` is larger than the configured free interval
if !call_info.is_mandatory {
if let Some(free_headers_interval) = T::FreeHeadersInterval::get() {
if improved_by < free_headers_interval.into() {
log::trace!(
target: crate::LOG_TARGET,
"Cannot accept free {:?} header {:?}. Too small difference \
between submitted headers: {:?} vs {}",
T::BridgedChain::ID,
call_info.block_number,
improved_by,
free_headers_interval,
);
return Err(Error::<T, I>::BelowFreeHeaderInterval);
}
}
}
// we do not check whether the header matches free submission criteria here - it is the
// relayer responsibility to check that
Ok(improved_by)
}
/// Check that the GRANDPA head provided by the `SubmitFinalityProof` is better than the best
/// one we know. Additionally, checks if `current_set_id` matches the current authority set
/// id, if specified.
/// id, if specified. This method is called by the call code and the transaction extension,
/// so it does not check the free execution.
///
/// Returns number of headers between the current best finalized header, known to the pallet
/// and the bundled header.
pub fn check_obsolete(
finality_target: BlockNumberOf<T::BridgedChain>,
current_set_id: Option<SetId>,
) -> Result<(), Error<T, I>> {
let best_finalized = crate::BestFinalized::<T, I>::get().ok_or_else(|| {
) -> Result<BlockNumberOf<T::BridgedChain>, Error<T, I>> {
let best_finalized = BestFinalized::<T, I>::get().ok_or_else(|| {
log::trace!(
target: crate::LOG_TARGET,
"Cannot finalize header {:?} because pallet is not yet initialized",
@@ -83,16 +174,19 @@ impl<T: Config<I>, I: 'static> SubmitFinalityProofHelper<T, I> {
<Error<T, I>>::NotInitialized
})?;
if best_finalized.number() >= finality_target {
log::trace!(
target: crate::LOG_TARGET,
"Cannot finalize obsolete header: bundled {:?}, best {:?}",
finality_target,
best_finalized,
);
let improved_by = match finality_target.checked_sub(&best_finalized.number()) {
Some(improved_by) if improved_by > Zero::zero() => improved_by,
_ => {
log::trace!(
target: crate::LOG_TARGET,
"Cannot finalize obsolete header: bundled {:?}, best {:?}",
finality_target,
best_finalized,
);
return Err(Error::<T, I>::OldHeader)
}
return Err(Error::<T, I>::OldHeader)
},
};
if let Some(current_set_id) = current_set_id {
let actual_set_id = <CurrentAuthoritySet<T, I>>::get().set_id;
@@ -108,12 +202,12 @@ impl<T: Config<I>, I: 'static> SubmitFinalityProofHelper<T, I> {
}
}
Ok(())
Ok(improved_by)
}
/// Check if the `SubmitFinalityProof` was successfully executed.
pub fn was_successful(finality_target: BlockNumberOf<T::BridgedChain>) -> bool {
match crate::BestFinalized::<T, I>::get() {
match BestFinalized::<T, I>::get() {
Some(best_finalized) => best_finalized.number() == finality_target,
None => false,
}
@@ -135,17 +229,20 @@ pub trait CallSubType<T: Config<I, RuntimeCall = Self>, I: 'static>:
finality_target,
justification,
None,
false,
))
} else if let Some(crate::Call::<T, I>::submit_finality_proof_ex {
finality_target,
justification,
current_set_id,
is_free_execution_expected,
}) = self.is_sub_type()
{
return Some(submit_finality_proof_info_from_args::<T, I>(
finality_target,
justification,
Some(*current_set_id),
*is_free_execution_expected,
))
}
@@ -155,26 +252,36 @@ pub trait CallSubType<T: Config<I, RuntimeCall = Self>, I: 'static>:
/// Validate Grandpa headers in order to avoid "mining" transactions that provide outdated
/// bridged chain headers. Without this validation, even honest relayers may lose their funds
/// if there are multiple relays running and submitting the same information.
fn check_obsolete_submit_finality_proof(&self) -> TransactionValidity
///
/// Returns `Ok(None)` if the call is not the `submit_finality_proof` call of our pallet.
/// Returns `Ok(Some(_))` if the call is the `submit_finality_proof` call of our pallet and
/// we believe the call brings header that improves the pallet state.
/// Returns `Err(_)` if the call is the `submit_finality_proof` call of our pallet and we
/// believe that the call will fail.
fn check_obsolete_submit_finality_proof(
&self,
) -> Result<
Option<VerifiedSubmitFinalityProofInfo<BridgedBlockNumber<T, I>>>,
TransactionValidityError,
>
where
Self: Sized,
{
let finality_target = match self.submit_finality_proof_info() {
let call_info = match self.submit_finality_proof_info() {
Some(finality_proof) => finality_proof,
_ => return Ok(ValidTransaction::default()),
_ => return Ok(None),
};
if Pallet::<T, I>::ensure_not_halted().is_err() {
return InvalidTransaction::Call.into()
return Err(InvalidTransaction::Call.into())
}
match SubmitFinalityProofHelper::<T, I>::check_obsolete(
finality_target.block_number,
finality_target.current_set_id,
) {
Ok(_) => Ok(ValidTransaction::default()),
Err(Error::<T, I>::OldHeader) => InvalidTransaction::Stale.into(),
Err(_) => InvalidTransaction::Call.into(),
let result = SubmitFinalityProofHelper::<T, I>::check_obsolete_from_extension(&call_info);
match result {
Ok(improved_by) =>
Ok(Some(VerifiedSubmitFinalityProofInfo { base: call_info, improved_by })),
Err(Error::<T, I>::OldHeader) => Err(InvalidTransaction::Stale.into()),
Err(_) => Err(InvalidTransaction::Call.into()),
}
}
}
@@ -189,6 +296,7 @@ pub(crate) fn submit_finality_proof_info_from_args<T: Config<I>, I: 'static>(
finality_target: &BridgedHeader<T, I>,
justification: &GrandpaJustification<BridgedHeader<T, I>>,
current_set_id: Option<SetId>,
is_free_execution_expected: bool,
) -> SubmitFinalityProofInfo<BridgedBlockNumber<T, I>> {
let block_number = *finality_target.number();
@@ -230,16 +338,26 @@ pub(crate) fn submit_finality_proof_info_from_args<T: Config<I>, I: 'static>(
);
let extra_size = actual_call_size.saturating_sub(max_expected_call_size);
SubmitFinalityProofInfo { block_number, current_set_id, extra_weight, extra_size }
SubmitFinalityProofInfo {
block_number,
current_set_id,
is_mandatory: is_mandatory_finality_target,
is_free_execution_expected,
extra_weight,
extra_size,
}
}
#[cfg(test)]
mod tests {
use crate::{
call_ext::CallSubType,
mock::{run_test, test_header, RuntimeCall, TestBridgedChain, TestNumber, TestRuntime},
BestFinalized, Config, CurrentAuthoritySet, PalletOperatingMode, StoredAuthoritySet,
SubmitFinalityProofInfo, WeightInfo,
mock::{
run_test, test_header, FreeHeadersInterval, RuntimeCall, TestBridgedChain, TestNumber,
TestRuntime,
},
BestFinalized, Config, CurrentAuthoritySet, FreeHeadersRemaining, PalletOperatingMode,
StoredAuthoritySet, SubmitFinalityProofInfo, WeightInfo,
};
use bp_header_chain::ChainWithGrandpa;
use bp_runtime::{BasicOperatingMode, HeaderId};
@@ -247,6 +365,7 @@ mod tests {
make_default_justification, make_justification_for_header, JustificationGeneratorParams,
TEST_GRANDPA_SET_ID,
};
use codec::Encode;
use frame_support::weights::Weight;
use sp_runtime::{testing::DigestItem, traits::Header as _, SaturatedConversion};
@@ -256,6 +375,7 @@ mod tests {
justification: make_default_justification(&test_header(num)),
// not initialized => zero
current_set_id: 0,
is_free_execution_expected: false,
};
RuntimeCall::check_obsolete_submit_finality_proof(&RuntimeCall::Grandpa(
bridge_grandpa_call,
@@ -311,6 +431,121 @@ mod tests {
});
}
#[test]
fn extension_rejects_new_header_if_free_execution_is_requested_and_free_submissions_are_not_accepted(
) {
run_test(|| {
let bridge_grandpa_call = crate::Call::<TestRuntime, ()>::submit_finality_proof_ex {
finality_target: Box::new(test_header(10 + FreeHeadersInterval::get() as u64)),
justification: make_default_justification(&test_header(
10 + FreeHeadersInterval::get() as u64,
)),
current_set_id: 0,
is_free_execution_expected: true,
};
sync_to_header_10();
// when we can accept free headers => Ok
FreeHeadersRemaining::<TestRuntime, ()>::put(2);
assert!(RuntimeCall::check_obsolete_submit_finality_proof(&RuntimeCall::Grandpa(
bridge_grandpa_call.clone(),
),)
.is_ok());
// when we can NOT accept free headers => Err
FreeHeadersRemaining::<TestRuntime, ()>::put(0);
assert!(RuntimeCall::check_obsolete_submit_finality_proof(&RuntimeCall::Grandpa(
bridge_grandpa_call.clone(),
),)
.is_err());
// when called outside of transaction => Ok
FreeHeadersRemaining::<TestRuntime, ()>::kill();
assert!(RuntimeCall::check_obsolete_submit_finality_proof(&RuntimeCall::Grandpa(
bridge_grandpa_call,
),)
.is_ok());
})
}
#[test]
fn extension_rejects_new_header_if_free_execution_is_requested_and_improved_by_is_below_expected(
) {
run_test(|| {
let bridge_grandpa_call = crate::Call::<TestRuntime, ()>::submit_finality_proof_ex {
finality_target: Box::new(test_header(100)),
justification: make_default_justification(&test_header(100)),
current_set_id: 0,
is_free_execution_expected: true,
};
sync_to_header_10();
// when `improved_by` is less than the free interval
BestFinalized::<TestRuntime, ()>::put(HeaderId(
100 - FreeHeadersInterval::get() as u64 + 1,
sp_core::H256::default(),
));
assert!(RuntimeCall::check_obsolete_submit_finality_proof(&RuntimeCall::Grandpa(
bridge_grandpa_call.clone(),
),)
.is_err());
// when `improved_by` is equal to the free interval
BestFinalized::<TestRuntime, ()>::put(HeaderId(
100 - FreeHeadersInterval::get() as u64,
sp_core::H256::default(),
));
assert!(RuntimeCall::check_obsolete_submit_finality_proof(&RuntimeCall::Grandpa(
bridge_grandpa_call.clone(),
),)
.is_ok());
// when `improved_by` is larger than the free interval
BestFinalized::<TestRuntime, ()>::put(HeaderId(
100 - FreeHeadersInterval::get() as u64 - 1,
sp_core::H256::default(),
));
assert!(RuntimeCall::check_obsolete_submit_finality_proof(&RuntimeCall::Grandpa(
bridge_grandpa_call.clone(),
),)
.is_ok());
// when `improved_by` is less than the free interval BUT it is a mandatory header
let mut mandatory_header = test_header(100);
let consensus_log = sp_consensus_grandpa::ConsensusLog::<TestNumber>::ScheduledChange(
sp_consensus_grandpa::ScheduledChange {
next_authorities: bp_test_utils::authority_list(),
delay: 0,
},
);
mandatory_header.digest = sp_runtime::Digest {
logs: vec![DigestItem::Consensus(
sp_consensus_grandpa::GRANDPA_ENGINE_ID,
consensus_log.encode(),
)],
};
let justification = make_justification_for_header(JustificationGeneratorParams {
header: mandatory_header.clone(),
set_id: 1,
..Default::default()
});
let bridge_grandpa_call = crate::Call::<TestRuntime, ()>::submit_finality_proof_ex {
finality_target: Box::new(mandatory_header),
justification,
current_set_id: 0,
is_free_execution_expected: true,
};
BestFinalized::<TestRuntime, ()>::put(HeaderId(
100 - FreeHeadersInterval::get() as u64 + 1,
sp_core::H256::default(),
));
assert!(RuntimeCall::check_obsolete_submit_finality_proof(&RuntimeCall::Grandpa(
bridge_grandpa_call.clone(),
),)
.is_ok());
})
}
#[test]
fn extension_accepts_new_header() {
run_test(|| {
@@ -336,6 +571,8 @@ mod tests {
current_set_id: None,
extra_weight: Weight::zero(),
extra_size: 0,
is_mandatory: false,
is_free_execution_expected: false,
})
);
@@ -345,6 +582,7 @@ mod tests {
finality_target: Box::new(test_header(42)),
justification: make_default_justification(&test_header(42)),
current_set_id: 777,
is_free_execution_expected: false,
});
assert_eq!(
deprecated_call.submit_finality_proof_info(),
@@ -353,6 +591,8 @@ mod tests {
current_set_id: Some(777),
extra_weight: Weight::zero(),
extra_size: 0,
is_mandatory: false,
is_free_execution_expected: false,
})
);
}
@@ -370,6 +610,7 @@ mod tests {
finality_target: Box::new(small_finality_target),
justification: small_justification,
current_set_id: TEST_GRANDPA_SET_ID,
is_free_execution_expected: false,
});
assert_eq!(small_call.submit_finality_proof_info().unwrap().extra_size, 0);
@@ -387,6 +628,7 @@ mod tests {
finality_target: Box::new(large_finality_target),
justification: large_justification,
current_set_id: TEST_GRANDPA_SET_ID,
is_free_execution_expected: false,
});
assert_ne!(large_call.submit_finality_proof_info().unwrap().extra_size, 0);
}
@@ -406,6 +648,7 @@ mod tests {
finality_target: Box::new(finality_target.clone()),
justification,
current_set_id: TEST_GRANDPA_SET_ID,
is_free_execution_expected: false,
});
assert_eq!(call.submit_finality_proof_info().unwrap().extra_weight, Weight::zero());
@@ -420,7 +663,52 @@ mod tests {
finality_target: Box::new(finality_target),
justification,
current_set_id: TEST_GRANDPA_SET_ID,
is_free_execution_expected: false,
});
assert_eq!(call.submit_finality_proof_info().unwrap().extra_weight, call_weight);
}
#[test]
fn check_obsolete_submit_finality_proof_returns_correct_improved_by() {
run_test(|| {
fn make_call(number: u64) -> RuntimeCall {
RuntimeCall::Grandpa(crate::Call::<TestRuntime, ()>::submit_finality_proof_ex {
finality_target: Box::new(test_header(number)),
justification: make_default_justification(&test_header(number)),
current_set_id: 0,
is_free_execution_expected: false,
})
}
sync_to_header_10();
// when the difference between headers is 1
assert_eq!(
RuntimeCall::check_obsolete_submit_finality_proof(&make_call(11))
.unwrap()
.unwrap()
.improved_by,
1,
);
// when the difference between headers is 2
assert_eq!(
RuntimeCall::check_obsolete_submit_finality_proof(&make_call(12))
.unwrap()
.unwrap()
.improved_by,
2,
);
})
}
#[test]
fn check_obsolete_submit_finality_proof_ignores_other_calls() {
run_test(|| {
let call =
RuntimeCall::System(frame_system::Call::<TestRuntime>::remark { remark: vec![42] });
assert_eq!(RuntimeCall::check_obsolete_submit_finality_proof(&call), Ok(None));
})
}
}
+211 -38
View File
@@ -44,6 +44,7 @@ use bp_header_chain::{
};
use bp_runtime::{BlockNumberOf, HashOf, HasherOf, HeaderId, HeaderOf, OwnedBridgeModule};
use frame_support::{dispatch::PostDispatchInfo, ensure, DefaultNoBound};
use sp_consensus_grandpa::SetId;
use sp_runtime::{
traits::{Header as HeaderT, Zero},
SaturatedConversion,
@@ -57,6 +58,7 @@ mod storage_types;
/// Module, containing weights for this pallet.
pub mod weights;
pub mod weights_ext;
#[cfg(feature = "runtime-benchmarks")]
pub mod benchmarking;
@@ -65,6 +67,7 @@ pub mod benchmarking;
pub use call_ext::*;
pub use pallet::*;
pub use weights::WeightInfo;
pub use weights_ext::WeightInfoExt;
/// The target that will be used when publishing logs related to this pallet.
pub const LOG_TARGET: &str = "runtime::bridge-grandpa";
@@ -101,17 +104,31 @@ pub mod pallet {
/// The chain we are bridging to here.
type BridgedChain: ChainWithGrandpa;
/// Maximal number of "free" mandatory header transactions per block.
/// Maximal number of "free" header transactions per block.
///
/// To be able to track the bridged chain, the pallet requires all headers that are
/// changing GRANDPA authorities set at the bridged chain (we call them mandatory).
/// So it is a common good deed to submit mandatory headers to the pallet. However, if the
/// bridged chain gets compromised, its validators may generate as many mandatory headers
/// as they want. And they may fill the whole block (at this chain) for free. This constants
/// limits number of calls that we may refund in a single block. All calls above this
/// limit are accepted, but are not refunded.
/// So it is a common good deed to submit mandatory headers to the pallet.
///
/// The pallet may be configured (see `[Self::FreeHeadersInterval]`) to import some
/// non-mandatory headers for free as well. It also may be treated as a common good
/// deed, because it may help to reduce bridge fees - this cost may be deducted from
/// bridge fees, paid by message senders.
///
/// However, if the bridged chain gets compromised, its validators may generate as many
/// "free" headers as they want. And they may fill the whole block (at this chain) for
/// free. This constants limits number of calls that we may refund in a single block.
/// All calls above this limit are accepted, but are not refunded.
#[pallet::constant]
type MaxFreeMandatoryHeadersPerBlock: Get<u32>;
type MaxFreeHeadersPerBlock: Get<u32>;
/// The distance between bridged chain headers, that may be submitted for free. The
/// first free header is header number zero, the next one is header number
/// `FreeHeadersInterval::get()` or any of its descendant if that header has not
/// been submitted. In other words, interval between free headers should be at least
/// `FreeHeadersInterval`.
#[pallet::constant]
type FreeHeadersInterval: Get<Option<u32>>;
/// Maximal number of finalized headers to keep in the storage.
///
@@ -124,7 +141,7 @@ pub mod pallet {
type HeadersToKeep: Get<u32>;
/// Weights gathered through benchmarking.
type WeightInfo: WeightInfo;
type WeightInfo: WeightInfoExt;
}
#[pallet::pallet]
@@ -133,12 +150,12 @@ pub mod pallet {
#[pallet::hooks]
impl<T: Config<I>, I: 'static> Hooks<BlockNumberFor<T>> for Pallet<T, I> {
fn on_initialize(_n: BlockNumberFor<T>) -> Weight {
FreeMandatoryHeadersRemaining::<T, I>::put(T::MaxFreeMandatoryHeadersPerBlock::get());
FreeHeadersRemaining::<T, I>::put(T::MaxFreeHeadersPerBlock::get());
Weight::zero()
}
fn on_finalize(_n: BlockNumberFor<T>) {
FreeMandatoryHeadersRemaining::<T, I>::kill();
FreeHeadersRemaining::<T, I>::kill();
}
}
@@ -155,7 +172,7 @@ pub mod pallet {
/// `submit_finality_proof_ex` instead. Semantically, this call is an equivalent of the
/// `submit_finality_proof_ex` call without current authority set id check.
#[pallet::call_index(0)]
#[pallet::weight(<T::WeightInfo as WeightInfo>::submit_finality_proof(
#[pallet::weight(T::WeightInfo::submit_finality_proof_weight(
justification.commit.precommits.len().saturated_into(),
justification.votes_ancestries.len().saturated_into(),
))]
@@ -175,6 +192,8 @@ pub mod pallet {
// the `submit_finality_proof_ex` also reads this value, but it is done from the
// cache, so we don't treat it as an additional db access
<CurrentAuthoritySet<T, I>>::get().set_id,
// cannot enforce free execution using this call
false,
)
}
@@ -250,8 +269,14 @@ pub mod pallet {
/// - verification is not optimized or invalid;
///
/// - header contains forced authorities set change or change with non-zero delay.
///
/// The `is_free_execution_expected` parameter is not really used inside the call. It is
/// used by the transaction extension, which should be registered at the runtime level. If
/// this parameter is `true`, the transaction will be treated as invalid, if the call won't
/// be executed for free. If transaction extension is not used by the runtime, this
/// parameter is not used at all.
#[pallet::call_index(4)]
#[pallet::weight(<T::WeightInfo as WeightInfo>::submit_finality_proof(
#[pallet::weight(T::WeightInfo::submit_finality_proof_weight(
justification.commit.precommits.len().saturated_into(),
justification.votes_ancestries.len().saturated_into(),
))]
@@ -260,6 +285,7 @@ pub mod pallet {
finality_target: Box<BridgedHeader<T, I>>,
justification: GrandpaJustification<BridgedHeader<T, I>>,
current_set_id: sp_consensus_grandpa::SetId,
_is_free_execution_expected: bool,
) -> DispatchResultWithPostInfo {
Self::ensure_not_halted().map_err(Error::<T, I>::BridgeModule)?;
ensure_signed(origin)?;
@@ -273,7 +299,8 @@ pub mod pallet {
// it checks whether the `number` is better than the current best block number
// and whether the `current_set_id` matches the best known set id
SubmitFinalityProofHelper::<T, I>::check_obsolete(number, Some(current_set_id))?;
let improved_by =
SubmitFinalityProofHelper::<T, I>::check_obsolete(number, Some(current_set_id))?;
let authority_set = <CurrentAuthoritySet<T, I>>::get();
let unused_proof_size = authority_set.unused_proof_size();
@@ -283,23 +310,16 @@ pub mod pallet {
let maybe_new_authority_set =
try_enact_authority_change::<T, I>(&finality_target, set_id)?;
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
submit_finality_proof_info_from_args::<T, I>(&finality_target, &justification, Some(current_set_id))
.fits_limits();
let may_refund_call_fee = may_refund_call_fee::<T, I>(
&finality_target,
&justification,
current_set_id,
improved_by,
);
if may_refund_call_fee {
FreeMandatoryHeadersRemaining::<T, I>::mutate(|count| {
*count = count.saturating_sub(1)
});
on_free_header_imported::<T, I>();
}
insert_header::<T, I>(*finality_target, hash);
log::info!(
target: LOG_TARGET,
"Successfully imported finalized header with hash {:?}!",
hash
);
// mandatory header is a header that changes authorities set. The pallet can't go
// further without importing this header. So every bridge MUST import mandatory headers.
@@ -311,6 +331,13 @@ pub mod pallet {
// to pay for the transaction.
let pays_fee = if may_refund_call_fee { Pays::No } else { Pays::Yes };
log::info!(
target: LOG_TARGET,
"Successfully imported finalized header with hash {:?}! Free: {}",
hash,
if may_refund_call_fee { "Yes" } else { "No" },
);
// the proof size component of the call weight assumes that there are
// `MaxBridgedAuthorities` in the `CurrentAuthoritySet` (we use `MaxEncodedLen`
// estimation). But if their number is lower, then we may "refund" some `proof_size`,
@@ -335,20 +362,18 @@ pub mod pallet {
}
}
/// Number mandatory headers that we may accept in the current block for free (returning
/// `Pays::No`).
/// Number of free header submissions that we may yet accept in the current block.
///
/// If the `FreeMandatoryHeadersRemaining` hits zero, all following mandatory headers in the
/// If the `FreeHeadersRemaining` hits zero, all following mandatory headers in the
/// current block are accepted with fee (`Pays::Yes` is returned).
///
/// The `FreeMandatoryHeadersRemaining` is an ephemeral value that is set to
/// `MaxFreeMandatoryHeadersPerBlock` at each block initialization and is killed on block
/// The `FreeHeadersRemaining` is an ephemeral value that is set to
/// `MaxFreeHeadersPerBlock` at each block initialization and is killed on block
/// finalization. So it never ends up in the storage trie.
#[pallet::storage]
#[pallet::whitelist_storage]
#[pallet::getter(fn free_mandatory_headers_remaining)]
pub(super) type FreeMandatoryHeadersRemaining<T: Config<I>, I: 'static = ()> =
StorageValue<_, u32, ValueQuery>;
pub type FreeHeadersRemaining<T: Config<I>, I: 'static = ()> =
StorageValue<_, u32, OptionQuery>;
/// Hash of the header used to bootstrap the pallet.
#[pallet::storage]
@@ -473,6 +498,68 @@ pub mod pallet {
/// The `current_set_id` argument of the `submit_finality_proof_ex` doesn't match
/// the id of the current set, known to the pallet.
InvalidAuthoritySetId,
/// The submitter wanted free execution, but we can't fit more free transactions
/// to the block.
FreeHeadersLimitExceded,
/// The submitter wanted free execution, but the difference between best known and
/// bundled header numbers is below the `FreeHeadersInterval`.
BelowFreeHeaderInterval,
}
/// Called when new free header is imported.
pub fn on_free_header_imported<T: Config<I>, I: 'static>() {
FreeHeadersRemaining::<T, I>::mutate(|count| {
*count = match *count {
None => None,
// the signed extension expects that `None` means outside of block
// execution - i.e. when transaction is validated from the transaction pool,
// so use `saturating_sub` and don't go from `Some(0)`->`None`
Some(count) => Some(count.saturating_sub(1)),
}
});
}
/// Return true if we may refund transaction cost to the submitter. In other words,
/// this transaction is considered as common good deed w.r.t to pallet configuration.
fn may_refund_call_fee<T: Config<I>, I: 'static>(
finality_target: &BridgedHeader<T, I>,
justification: &GrandpaJustification<BridgedHeader<T, I>>,
current_set_id: SetId,
improved_by: BridgedBlockNumber<T, I>,
) -> bool {
// if we have refunded too much at this block => not refunding
if FreeHeadersRemaining::<T, I>::get().unwrap_or(0) == 0 {
return false;
}
// if size/weight of call is larger than expected => not refunding
let call_info = submit_finality_proof_info_from_args::<T, I>(
&finality_target,
&justification,
Some(current_set_id),
// this function is called from the transaction body and we do not want
// to do MAY-be-free-executed checks here - they had to be done in the
// transaction extension before
false,
);
if !call_info.fits_limits() {
return false;
}
// if that's a mandatory header => refund
if call_info.is_mandatory {
return true;
}
// if configuration allows free non-mandatory headers and the header
// matches criteria => refund
if let Some(free_headers_interval) = T::FreeHeadersInterval::get() {
if improved_by >= free_headers_interval.into() {
return true;
}
}
false
}
/// Check the given header for a GRANDPA scheduled authority set change. If a change
@@ -692,8 +779,8 @@ pub fn initialize_for_benchmarks<T: Config<I>, I: 'static>(header: BridgedHeader
mod tests {
use super::*;
use crate::mock::{
run_test, test_header, RuntimeEvent as TestEvent, RuntimeOrigin, System, TestBridgedChain,
TestHeader, TestNumber, TestRuntime, MAX_BRIDGED_AUTHORITIES,
run_test, test_header, FreeHeadersInterval, RuntimeEvent as TestEvent, RuntimeOrigin,
System, TestBridgedChain, TestHeader, TestNumber, TestRuntime, MAX_BRIDGED_AUTHORITIES,
};
use bp_header_chain::BridgeGrandpaCall;
use bp_runtime::BasicOperatingMode;
@@ -747,6 +834,7 @@ mod tests {
Box::new(header),
justification,
TEST_GRANDPA_SET_ID,
false,
)
}
@@ -766,6 +854,7 @@ mod tests {
Box::new(header),
justification,
set_id,
false,
)
}
@@ -794,6 +883,7 @@ mod tests {
Box::new(header),
justification,
set_id,
false,
)
}
@@ -1009,6 +1099,7 @@ mod tests {
Box::new(header.clone()),
justification.clone(),
TEST_GRANDPA_SET_ID,
false,
),
<Error<TestRuntime>>::InvalidJustification
);
@@ -1018,6 +1109,7 @@ mod tests {
Box::new(header),
justification,
next_set_id,
false,
),
<Error<TestRuntime>>::InvalidAuthoritySetId
);
@@ -1039,6 +1131,7 @@ mod tests {
Box::new(header),
justification,
TEST_GRANDPA_SET_ID,
false,
),
<Error<TestRuntime>>::InvalidJustification
);
@@ -1069,6 +1162,7 @@ mod tests {
Box::new(header),
justification,
TEST_GRANDPA_SET_ID,
false,
),
<Error<TestRuntime>>::InvalidAuthoritySet
);
@@ -1108,6 +1202,7 @@ mod tests {
Box::new(header.clone()),
justification.clone(),
TEST_GRANDPA_SET_ID,
false,
);
assert_ok!(result);
assert_eq!(result.unwrap().pays_fee, frame_support::dispatch::Pays::No);
@@ -1171,6 +1266,7 @@ mod tests {
Box::new(header.clone()),
justification,
TEST_GRANDPA_SET_ID,
false,
);
assert_ok!(result);
assert_eq!(result.unwrap().pays_fee, frame_support::dispatch::Pays::Yes);
@@ -1203,6 +1299,7 @@ mod tests {
Box::new(header.clone()),
justification,
TEST_GRANDPA_SET_ID,
false,
);
assert_ok!(result);
assert_eq!(result.unwrap().pays_fee, frame_support::dispatch::Pays::Yes);
@@ -1233,6 +1330,7 @@ mod tests {
Box::new(header),
justification,
TEST_GRANDPA_SET_ID,
false,
),
<Error<TestRuntime>>::UnsupportedScheduledChange
);
@@ -1259,6 +1357,7 @@ mod tests {
Box::new(header),
justification,
TEST_GRANDPA_SET_ID,
false,
),
<Error<TestRuntime>>::UnsupportedScheduledChange
);
@@ -1285,6 +1384,7 @@ mod tests {
Box::new(header),
justification,
TEST_GRANDPA_SET_ID,
false,
),
<Error<TestRuntime>>::TooManyAuthoritiesInSet
);
@@ -1350,12 +1450,13 @@ mod tests {
Box::new(header),
invalid_justification,
TEST_GRANDPA_SET_ID,
false,
)
};
initialize_substrate_bridge();
for _ in 0..<TestRuntime as Config>::MaxFreeMandatoryHeadersPerBlock::get() + 1 {
for _ in 0..<TestRuntime as Config>::MaxFreeHeadersPerBlock::get() + 1 {
assert_err!(submit_invalid_request(), <Error<TestRuntime>>::InvalidJustification);
}
@@ -1423,6 +1524,64 @@ mod tests {
})
}
#[test]
fn may_import_non_mandatory_header_for_free() {
run_test(|| {
initialize_substrate_bridge();
// set best finalized to `100`
const BEST: u8 = 12;
fn reset_best() {
BestFinalized::<TestRuntime, ()>::set(Some(HeaderId(
BEST as _,
Default::default(),
)));
}
// non-mandatory header is imported with fee
reset_best();
let non_free_header_number = BEST + FreeHeadersInterval::get() as u8 - 1;
let result = submit_finality_proof(non_free_header_number);
assert_eq!(result.unwrap().pays_fee, Pays::Yes);
// non-mandatory free header is imported without fee
reset_best();
let free_header_number = BEST + FreeHeadersInterval::get() as u8;
let result = submit_finality_proof(free_header_number);
assert_eq!(result.unwrap().pays_fee, Pays::No);
// another non-mandatory free header is imported without fee
let free_header_number = BEST + FreeHeadersInterval::get() as u8 * 2;
let result = submit_finality_proof(free_header_number);
assert_eq!(result.unwrap().pays_fee, Pays::No);
// now the rate limiter starts charging fees even for free headers
let free_header_number = BEST + FreeHeadersInterval::get() as u8 * 3;
let result = submit_finality_proof(free_header_number);
assert_eq!(result.unwrap().pays_fee, Pays::Yes);
// check that we can import for free if `improved_by` is larger
// than the free interval
next_block();
reset_best();
let free_header_number = FreeHeadersInterval::get() as u8 + 42;
let result = submit_finality_proof(free_header_number);
assert_eq!(result.unwrap().pays_fee, Pays::No);
// check that the rate limiter shares the counter between mandatory
// and free non-mandatory headers
next_block();
reset_best();
let free_header_number = BEST + FreeHeadersInterval::get() as u8 * 4;
let result = submit_finality_proof(free_header_number);
assert_eq!(result.unwrap().pays_fee, Pays::No);
let result = submit_mandatory_finality_proof(free_header_number + 1, 1);
assert_eq!(result.expect("call failed").pays_fee, Pays::No);
let result = submit_mandatory_finality_proof(free_header_number + 2, 2);
assert_eq!(result.expect("call failed").pays_fee, Pays::Yes);
});
}
#[test]
fn should_prune_headers_over_headers_to_keep_parameter() {
run_test(|| {
@@ -1519,9 +1678,23 @@ mod tests {
Box::new(header),
justification,
TEST_GRANDPA_SET_ID,
false,
),
DispatchError::BadOrigin,
);
})
}
#[test]
fn on_free_header_imported_never_sets_to_none() {
run_test(|| {
FreeHeadersRemaining::<TestRuntime, ()>::set(Some(2));
on_free_header_imported::<TestRuntime, ()>();
assert_eq!(FreeHeadersRemaining::<TestRuntime, ()>::get(), Some(1));
on_free_header_imported::<TestRuntime, ()>();
assert_eq!(FreeHeadersRemaining::<TestRuntime, ()>::get(), Some(0));
on_free_header_imported::<TestRuntime, ()>();
assert_eq!(FreeHeadersRemaining::<TestRuntime, ()>::get(), Some(0));
})
}
}
+4 -2
View File
@@ -48,14 +48,16 @@ impl frame_system::Config for TestRuntime {
}
parameter_types! {
pub const MaxFreeMandatoryHeadersPerBlock: u32 = 2;
pub const MaxFreeHeadersPerBlock: u32 = 2;
pub const FreeHeadersInterval: u32 = 32;
pub const HeadersToKeep: u32 = 5;
}
impl grandpa::Config for TestRuntime {
type RuntimeEvent = RuntimeEvent;
type BridgedChain = TestBridgedChain;
type MaxFreeMandatoryHeadersPerBlock = MaxFreeMandatoryHeadersPerBlock;
type MaxFreeHeadersPerBlock = MaxFreeHeadersPerBlock;
type FreeHeadersInterval = FreeHeadersInterval;
type HeadersToKeep = HeadersToKeep;
type WeightInfo = ();
}
@@ -0,0 +1,58 @@
// Copyright (C) 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/>.
//! Weight-related utilities.
use crate::weights::{BridgeWeight, WeightInfo};
use frame_support::weights::Weight;
/// Extended weight info.
pub trait WeightInfoExt: WeightInfo {
// Our configuration assumes that the runtime has special signed extensions used to:
//
// 1) boost priority of `submit_finality_proof` transactions;
//
// 2) slash relayer if he submits an invalid transaction.
//
// We read and update storage values of other pallets (`pallet-bridge-relayers` and
// balances/assets pallet). So we need to add this weight to the weight of our call.
// Hence two following methods.
/// Extra weight that is added to the `submit_finality_proof` call weight by signed extensions
/// that are declared at runtime level.
fn submit_finality_proof_overhead_from_runtime() -> Weight;
// Functions that are directly mapped to extrinsics weights.
/// Weight of message delivery extrinsic.
fn submit_finality_proof_weight(precommits_len: u32, votes_ancestries_len: u32) -> Weight {
let base_weight = Self::submit_finality_proof(precommits_len, votes_ancestries_len);
base_weight.saturating_add(Self::submit_finality_proof_overhead_from_runtime())
}
}
impl<T: frame_system::Config> WeightInfoExt for BridgeWeight<T> {
fn submit_finality_proof_overhead_from_runtime() -> Weight {
Weight::zero()
}
}
impl WeightInfoExt for () {
fn submit_finality_proof_overhead_from_runtime() -> Weight {
Weight::zero()
}
}
+247 -52
View File
@@ -14,25 +14,45 @@
// 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/>.
use crate::{Config, Pallet, RelayBlockNumber};
use crate::{Config, GrandpaPalletOf, Pallet, RelayBlockHash, RelayBlockNumber};
use bp_header_chain::HeaderChain;
use bp_parachains::BestParaHeadHash;
use bp_polkadot_core::parachains::{ParaHash, ParaId};
use bp_runtime::OwnedBridgeModule;
use frame_support::{dispatch::CallableCallFor, traits::IsSubType};
use bp_runtime::{HeaderId, OwnedBridgeModule};
use frame_support::{
dispatch::CallableCallFor,
traits::{Get, IsSubType},
};
use pallet_bridge_grandpa::SubmitFinalityProofHelper;
use sp_runtime::{
transaction_validity::{InvalidTransaction, TransactionValidity, ValidTransaction},
traits::Zero,
transaction_validity::{InvalidTransaction, TransactionValidityError},
RuntimeDebug,
};
/// Info about a `SubmitParachainHeads` call which tries to update a single parachain.
#[derive(PartialEq, RuntimeDebug)]
pub struct SubmitParachainHeadsInfo {
/// Number of the finalized relay block that has been used to prove parachain finality.
pub at_relay_block_number: RelayBlockNumber,
/// Number and hash of the finalized relay block that has been used to prove parachain
/// finality.
pub at_relay_block: HeaderId<RelayBlockHash, RelayBlockNumber>,
/// Parachain identifier.
pub para_id: ParaId,
/// Hash of the bundled parachain head.
pub para_head_hash: ParaHash,
/// If `true`, then the call must be free (assuming that everything else is valid) to
/// be treated as valid.
pub is_free_execution_expected: bool,
}
/// Verified `SubmitParachainHeadsInfo`.
#[derive(PartialEq, RuntimeDebug)]
pub struct VerifiedSubmitParachainHeadsInfo {
/// Base call information.
pub base: SubmitParachainHeadsInfo,
/// A difference between bundled bridged relay chain header and relay chain header number
/// used to prove best bridged parachain header, known to us before the call.
pub improved_by: RelayBlockNumber,
}
/// Helper struct that provides methods for working with the `SubmitParachainHeads` call.
@@ -41,40 +61,117 @@ pub struct SubmitParachainHeadsHelper<T: Config<I>, I: 'static> {
}
impl<T: Config<I>, I: 'static> SubmitParachainHeadsHelper<T, I> {
/// Check if the para head provided by the `SubmitParachainHeads` is better than the best one
/// we know.
pub fn is_obsolete(update: &SubmitParachainHeadsInfo) -> bool {
let stored_best_head = match crate::ParasInfo::<T, I>::get(update.para_id) {
Some(stored_best_head) => stored_best_head,
None => return false,
/// Check that is called from signed extension and takes the `is_free_execution_expected`
/// into account.
pub fn check_obsolete_from_extension(
update: &SubmitParachainHeadsInfo,
) -> Result<RelayBlockNumber, TransactionValidityError> {
// first do all base checks
let improved_by = Self::check_obsolete(update)?;
// if we don't expect free execution - no more checks
if !update.is_free_execution_expected {
return Ok(improved_by);
}
// reject if no more free slots remaining in the block
if !SubmitFinalityProofHelper::<T, T::BridgesGrandpaPalletInstance>::has_free_header_slots()
{
log::trace!(
target: crate::LOG_TARGET,
"The free parachain {:?} head can't be updated: no more free slots \
left in the block.",
update.para_id,
);
return Err(InvalidTransaction::Call.into());
}
// if free headers interval is not configured and call is expected to execute
// for free => it is a relayer error, it should've been able to detect that.
let free_headers_interval = match T::FreeHeadersInterval::get() {
Some(free_headers_interval) => free_headers_interval,
None => return Ok(improved_by),
};
if stored_best_head.best_head_hash.at_relay_block_number >= update.at_relay_block_number {
// reject if we are importing parachain headers too often
if improved_by < free_headers_interval {
log::trace!(
target: crate::LOG_TARGET,
"The parachain head can't be updated. The parachain head for {:?} \
was already updated at better relay chain block {} >= {}.",
"The free parachain {:?} head can't be updated: it improves previous
best head by {} while at least {} is expected.",
update.para_id,
stored_best_head.best_head_hash.at_relay_block_number,
update.at_relay_block_number
improved_by,
free_headers_interval,
);
return true
return Err(InvalidTransaction::Stale.into());
}
if stored_best_head.best_head_hash.head_hash == update.para_head_hash {
Ok(improved_by)
}
/// Check if the para head provided by the `SubmitParachainHeads` is better than the best one
/// we know.
pub fn check_obsolete(
update: &SubmitParachainHeadsInfo,
) -> Result<RelayBlockNumber, TransactionValidityError> {
// check if we know better parachain head already
let improved_by = match crate::ParasInfo::<T, I>::get(update.para_id) {
Some(stored_best_head) => {
let improved_by = match update
.at_relay_block
.0
.checked_sub(stored_best_head.best_head_hash.at_relay_block_number)
{
Some(improved_by) if improved_by > Zero::zero() => improved_by,
_ => {
log::trace!(
target: crate::LOG_TARGET,
"The parachain head can't be updated. The parachain head for {:?} \
was already updated at better relay chain block {} >= {}.",
update.para_id,
stored_best_head.best_head_hash.at_relay_block_number,
update.at_relay_block.0
);
return Err(InvalidTransaction::Stale.into())
},
};
if stored_best_head.best_head_hash.head_hash == update.para_head_hash {
log::trace!(
target: crate::LOG_TARGET,
"The parachain head can't be updated. The parachain head hash for {:?} \
was already updated to {} at block {} < {}.",
update.para_id,
update.para_head_hash,
stored_best_head.best_head_hash.at_relay_block_number,
update.at_relay_block.0
);
return Err(InvalidTransaction::Stale.into())
}
improved_by
},
None => RelayBlockNumber::MAX,
};
// let's check if our chain had no reorgs and we still know the relay chain header
// used to craft the proof
if GrandpaPalletOf::<T, I>::finalized_header_state_root(update.at_relay_block.1).is_none() {
log::trace!(
target: crate::LOG_TARGET,
"The parachain head can't be updated. The parachain head hash for {:?} \
was already updated to {} at block {} < {}.",
"The parachain {:?} head can't be updated. Relay chain header {}/{} used to create \
parachain proof is missing from the storage.",
update.para_id,
update.para_head_hash,
stored_best_head.best_head_hash.at_relay_block_number,
update.at_relay_block_number
update.at_relay_block.0,
update.at_relay_block.1,
);
return true
return Err(InvalidTransaction::Call.into())
}
false
Ok(improved_by)
}
/// Check if the `SubmitParachainHeads` was successfully executed.
@@ -83,7 +180,7 @@ impl<T: Config<I>, I: 'static> SubmitParachainHeadsHelper<T, I> {
Some(stored_best_head) =>
stored_best_head.best_head_hash ==
BestParaHeadHash {
at_relay_block_number: update.at_relay_block_number,
at_relay_block_number: update.at_relay_block.0,
head_hash: update.para_head_hash,
},
None => false,
@@ -98,22 +195,36 @@ pub trait CallSubType<T: Config<I, RuntimeCall = Self>, I: 'static>:
/// Create a new instance of `SubmitParachainHeadsInfo` from a `SubmitParachainHeads` call with
/// one single parachain entry.
fn one_entry_submit_parachain_heads_info(&self) -> Option<SubmitParachainHeadsInfo> {
if let Some(crate::Call::<T, I>::submit_parachain_heads {
ref at_relay_block,
ref parachains,
..
}) = self.is_sub_type()
{
if let &[(para_id, para_head_hash)] = parachains.as_slice() {
return Some(SubmitParachainHeadsInfo {
at_relay_block_number: at_relay_block.0,
match self.is_sub_type() {
Some(crate::Call::<T, I>::submit_parachain_heads {
ref at_relay_block,
ref parachains,
..
}) => match &parachains[..] {
&[(para_id, para_head_hash)] => Some(SubmitParachainHeadsInfo {
at_relay_block: HeaderId(at_relay_block.0, at_relay_block.1),
para_id,
para_head_hash,
})
}
is_free_execution_expected: false,
}),
_ => None,
},
Some(crate::Call::<T, I>::submit_parachain_heads_ex {
ref at_relay_block,
ref parachains,
is_free_execution_expected,
..
}) => match &parachains[..] {
&[(para_id, para_head_hash)] => Some(SubmitParachainHeadsInfo {
at_relay_block: HeaderId(at_relay_block.0, at_relay_block.1),
para_id,
para_head_hash,
is_free_execution_expected: *is_free_execution_expected,
}),
_ => None,
},
_ => None,
}
None
}
/// Create a new instance of `SubmitParachainHeadsInfo` from a `SubmitParachainHeads` call with
@@ -133,24 +244,23 @@ pub trait CallSubType<T: Config<I, RuntimeCall = Self>, I: 'static>:
/// block production, or "eat" significant portion of block production time literally
/// for nothing. In addition, the single-parachain-head-per-transaction is how the
/// pallet will be used in our environment.
fn check_obsolete_submit_parachain_heads(&self) -> TransactionValidity
fn check_obsolete_submit_parachain_heads(
&self,
) -> Result<Option<VerifiedSubmitParachainHeadsInfo>, TransactionValidityError>
where
Self: Sized,
{
let update = match self.one_entry_submit_parachain_heads_info() {
Some(update) => update,
None => return Ok(ValidTransaction::default()),
None => return Ok(None),
};
if Pallet::<T, I>::ensure_not_halted().is_err() {
return InvalidTransaction::Call.into()
return Err(InvalidTransaction::Call.into())
}
if SubmitParachainHeadsHelper::<T, I>::is_obsolete(&update) {
return InvalidTransaction::Stale.into()
}
Ok(ValidTransaction::default())
SubmitParachainHeadsHelper::<T, I>::check_obsolete_from_extension(&update)
.map(|improved_by| Some(VerifiedSubmitParachainHeadsInfo { base: update, improved_by }))
}
}
@@ -164,9 +274,10 @@ where
#[cfg(test)]
mod tests {
use crate::{
mock::{run_test, RuntimeCall, TestRuntime},
CallSubType, PalletOperatingMode, ParaInfo, ParasInfo, RelayBlockNumber,
mock::{run_test, FreeHeadersInterval, RuntimeCall, TestRuntime},
CallSubType, PalletOperatingMode, ParaInfo, ParasInfo, RelayBlockHash, RelayBlockNumber,
};
use bp_header_chain::StoredHeaderData;
use bp_parachains::BestParaHeadHash;
use bp_polkadot_core::parachains::{ParaHash, ParaHeadsProof, ParaId};
use bp_runtime::BasicOperatingMode;
@@ -175,15 +286,37 @@ mod tests {
num: RelayBlockNumber,
parachains: Vec<(ParaId, ParaHash)>,
) -> bool {
RuntimeCall::Parachains(crate::Call::<TestRuntime, ()>::submit_parachain_heads {
at_relay_block: (num, Default::default()),
RuntimeCall::Parachains(crate::Call::<TestRuntime, ()>::submit_parachain_heads_ex {
at_relay_block: (num, [num as u8; 32].into()),
parachains,
parachain_heads_proof: ParaHeadsProof { storage_proof: Vec::new() },
is_free_execution_expected: false,
})
.check_obsolete_submit_parachain_heads()
.is_ok()
}
fn validate_free_submit_parachain_heads(
num: RelayBlockNumber,
parachains: Vec<(ParaId, ParaHash)>,
) -> bool {
RuntimeCall::Parachains(crate::Call::<TestRuntime, ()>::submit_parachain_heads_ex {
at_relay_block: (num, [num as u8; 32].into()),
parachains,
parachain_heads_proof: ParaHeadsProof { storage_proof: Vec::new() },
is_free_execution_expected: true,
})
.check_obsolete_submit_parachain_heads()
.is_ok()
}
fn insert_relay_block(num: RelayBlockNumber) {
pallet_bridge_grandpa::ImportedHeaders::<TestRuntime, crate::Instance1>::insert(
RelayBlockHash::from([num as u8; 32]),
StoredHeaderData { number: num, state_root: RelayBlockHash::from([10u8; 32]) },
);
}
fn sync_to_relay_header_10() {
ParasInfo::<TestRuntime, ()>::insert(
ParaId(1),
@@ -244,6 +377,7 @@ mod tests {
// when current best finalized is #10 and we're trying to import header#15 => tx is
// accepted
sync_to_relay_header_10();
insert_relay_block(15);
assert!(validate_submit_parachain_heads(15, vec![(ParaId(1), [2u8; 32].into())]));
});
}
@@ -260,4 +394,65 @@ mod tests {
));
});
}
#[test]
fn extension_rejects_initial_parachain_head_if_missing_relay_chain_header() {
run_test(|| {
// when relay chain header is unknown => "obsolete"
assert!(!validate_submit_parachain_heads(10, vec![(ParaId(1), [1u8; 32].into())]));
// when relay chain header is unknown => "ok"
insert_relay_block(10);
assert!(validate_submit_parachain_heads(10, vec![(ParaId(1), [1u8; 32].into())]));
});
}
#[test]
fn extension_rejects_free_parachain_head_if_missing_relay_chain_header() {
run_test(|| {
sync_to_relay_header_10();
// when relay chain header is unknown => "obsolete"
assert!(!validate_submit_parachain_heads(15, vec![(ParaId(2), [15u8; 32].into())]));
// when relay chain header is unknown => "ok"
insert_relay_block(15);
assert!(validate_submit_parachain_heads(15, vec![(ParaId(2), [15u8; 32].into())]));
});
}
#[test]
fn extension_rejects_free_parachain_head_if_no_free_slots_remaining() {
run_test(|| {
// when current best finalized is #10 and we're trying to import header#15 => tx should
// be accepted
sync_to_relay_header_10();
insert_relay_block(15);
// ... but since we have specified `is_free_execution_expected = true`, it'll be
// rejected
assert!(!validate_free_submit_parachain_heads(15, vec![(ParaId(1), [2u8; 32].into())]));
// ... if we have specify `is_free_execution_expected = false`, it'll be accepted
assert!(validate_submit_parachain_heads(15, vec![(ParaId(1), [2u8; 32].into())]));
});
}
#[test]
fn extension_rejects_free_parachain_head_if_improves_by_is_below_expected() {
run_test(|| {
// when current best finalized is #10 and we're trying to import header#15 => tx should
// be accepted
sync_to_relay_header_10();
insert_relay_block(10 + FreeHeadersInterval::get() - 1);
insert_relay_block(10 + FreeHeadersInterval::get());
// try to submit at 10 + FreeHeadersInterval::get() - 1 => failure
let relay_header = 10 + FreeHeadersInterval::get() - 1;
assert!(!validate_free_submit_parachain_heads(
relay_header,
vec![(ParaId(1), [2u8; 32].into())]
));
// try to submit at 10 + FreeHeadersInterval::get() => ok
let relay_header = 10 + FreeHeadersInterval::get();
assert!(validate_free_submit_parachain_heads(
relay_header,
vec![(ParaId(1), [2u8; 32].into())]
));
});
}
}
+303 -41
View File
@@ -32,6 +32,7 @@ use bp_parachains::{parachain_head_storage_key_at_source, ParaInfo, ParaStoredHe
use bp_polkadot_core::parachains::{ParaHash, ParaHead, ParaHeadsProof, ParaId};
use bp_runtime::{Chain, HashOf, HeaderId, HeaderIdOf, Parachain, StorageProofError};
use frame_support::{dispatch::PostDispatchInfo, DefaultNoBound};
use pallet_bridge_grandpa::SubmitFinalityProofHelper;
use sp_std::{marker::PhantomData, vec::Vec};
#[cfg(feature = "runtime-benchmarks")]
@@ -92,7 +93,8 @@ pub mod pallet {
BoundedStorageValue<<T as Config<I>>::MaxParaHeadDataSize, ParaStoredHeaderData>;
/// Weight info of the given parachains pallet.
pub type WeightInfoOf<T, I> = <T as Config<I>>::WeightInfo;
type GrandpaPalletOf<T, I> =
/// Bridge GRANDPA pallet that is used to verify parachain proofs.
pub type GrandpaPalletOf<T, I> =
pallet_bridge_grandpa::Pallet<T, <T as Config<I>>::BridgesGrandpaPalletInstance>;
#[pallet::event]
@@ -192,6 +194,21 @@ pub mod pallet {
///
/// The GRANDPA pallet instance must be configured to import headers of relay chain that
/// we're interested in.
///
/// The associated GRANDPA pallet is also used to configure free parachain heads
/// submissions. The parachain head submission will be free if:
///
/// 1) the submission contains exactly one parachain head update that succeeds;
///
/// 2) the difference between relay chain block numbers, used to prove new parachain head
/// and previous best parachain head is larger than the `FreeHeadersInterval`, configured
/// at the associated GRANDPA pallet;
///
/// 3) there are slots for free submissions, remaining at the block. This is also configured
/// at the associated GRANDPA pallet using `MaxFreeHeadersPerBlock` parameter.
///
/// First parachain head submission is also free for the submitted, if free submissions
/// are yet accepted to this block.
type BridgesGrandpaPalletInstance: 'static;
/// Name of the original `paras` pallet in the `construct_runtime!()` call at the bridged
@@ -335,10 +352,83 @@ pub mod pallet {
at_relay_block: (RelayBlockNumber, RelayBlockHash),
parachains: Vec<(ParaId, ParaHash)>,
parachain_heads_proof: ParaHeadsProof,
) -> DispatchResultWithPostInfo {
Self::submit_parachain_heads_ex(
origin,
at_relay_block,
parachains,
parachain_heads_proof,
false,
)
}
/// Change `PalletOwner`.
///
/// May only be called either by root, or by `PalletOwner`.
#[pallet::call_index(1)]
#[pallet::weight((T::DbWeight::get().reads_writes(1, 1), DispatchClass::Operational))]
pub fn set_owner(origin: OriginFor<T>, new_owner: Option<T::AccountId>) -> DispatchResult {
<Self as OwnedBridgeModule<_>>::set_owner(origin, new_owner)
}
/// Halt or resume all pallet operations.
///
/// May only be called either by root, or by `PalletOwner`.
#[pallet::call_index(2)]
#[pallet::weight((T::DbWeight::get().reads_writes(1, 1), DispatchClass::Operational))]
pub fn set_operating_mode(
origin: OriginFor<T>,
operating_mode: BasicOperatingMode,
) -> DispatchResult {
<Self as OwnedBridgeModule<_>>::set_operating_mode(origin, operating_mode)
}
/// Submit proof of one or several parachain heads.
///
/// The proof is supposed to be proof of some `Heads` entries from the
/// `polkadot-runtime-parachains::paras` pallet instance, deployed at the bridged chain.
/// The proof is supposed to be crafted at the `relay_header_hash` that must already be
/// imported by corresponding GRANDPA pallet at this chain.
///
/// The call fails if:
///
/// - the pallet is halted;
///
/// - the relay chain block `at_relay_block` is not imported by the associated bridge
/// GRANDPA pallet.
///
/// The call may succeed, but some heads may not be updated e.g. because pallet knows
/// better head or it isn't tracked by the pallet.
///
/// The `is_free_execution_expected` parameter is not really used inside the call. It is
/// used by the transaction extension, which should be registered at the runtime level. If
/// this parameter is `true`, the transaction will be treated as invalid, if the call won't
/// be executed for free. If transaction extension is not used by the runtime, this
/// parameter is not used at all.
#[pallet::call_index(3)]
#[pallet::weight(WeightInfoOf::<T, I>::submit_parachain_heads_weight(
T::DbWeight::get(),
parachain_heads_proof,
parachains.len() as _,
))]
pub fn submit_parachain_heads_ex(
origin: OriginFor<T>,
at_relay_block: (RelayBlockNumber, RelayBlockHash),
parachains: Vec<(ParaId, ParaHash)>,
parachain_heads_proof: ParaHeadsProof,
_is_free_execution_expected: bool,
) -> DispatchResultWithPostInfo {
Self::ensure_not_halted().map_err(Error::<T, I>::BridgeModule)?;
ensure_signed(origin)?;
let total_parachains = parachains.len();
let free_headers_interval =
T::FreeHeadersInterval::get().unwrap_or(RelayBlockNumber::MAX);
// the pallet allows two kind of free submissions
// 1) if distance between all parachain heads is gte than the [`T::FreeHeadersInterval`]
// 2) if all heads are the first heads of their parachains
let mut free_parachain_heads = 0;
// we'll need relay chain header to verify that parachains heads are always increasing.
let (relay_block_number, relay_block_hash) = at_relay_block;
let relay_block = pallet_bridge_grandpa::ImportedHeaders::<
@@ -358,6 +448,7 @@ pub mod pallet {
parachains.len() as _,
);
let mut is_updated_something = false;
let mut storage = GrandpaPalletOf::<T, I>::storage_proof_checker(
relay_block_hash,
parachain_heads_proof.storage_proof,
@@ -414,6 +505,7 @@ pub mod pallet {
}
// convert from parachain head into stored parachain head data
let parachain_head_size = parachain_head.0.len();
let parachain_head_data =
match T::ParaStoredHeaderDataBuilder::try_build(parachain, &parachain_head) {
Some(parachain_head_data) => parachain_head_data,
@@ -430,13 +522,30 @@ pub mod pallet {
let update_result: Result<_, ()> =
ParasInfo::<T, I>::try_mutate(parachain, |stored_best_head| {
let is_free = parachain_head_size <
T::ParaStoredHeaderDataBuilder::max_free_head_size() as usize &&
match stored_best_head {
Some(ref best_head)
if at_relay_block.0.saturating_sub(
best_head.best_head_hash.at_relay_block_number,
) >= free_headers_interval =>
true,
Some(_) => false,
None => true,
};
let artifacts = Pallet::<T, I>::update_parachain_head(
parachain,
stored_best_head.take(),
relay_block_number,
HeaderId(relay_block_number, relay_block_hash),
parachain_head_data,
parachain_head_hash,
)?;
is_updated_something = true;
if is_free {
free_parachain_heads = free_parachain_heads + 1;
}
*stored_best_head = Some(artifacts.best_head);
Ok(artifacts.prune_happened)
});
@@ -467,28 +576,21 @@ pub mod pallet {
Error::<T, I>::HeaderChainStorageProof(HeaderChainError::StorageProof(e))
})?;
Ok(PostDispatchInfo { actual_weight: Some(actual_weight), pays_fee: Pays::Yes })
}
// check if we allow this submission for free
let is_free = total_parachains == 1
&& free_parachain_heads == total_parachains
&& SubmitFinalityProofHelper::<T, T::BridgesGrandpaPalletInstance>::has_free_header_slots();
let pays_fee = if is_free {
log::trace!(target: LOG_TARGET, "Parachain heads update transaction is free");
pallet_bridge_grandpa::on_free_header_imported::<T, T::BridgesGrandpaPalletInstance>(
);
Pays::No
} else {
log::trace!(target: LOG_TARGET, "Parachain heads update transaction is paid");
Pays::Yes
};
/// Change `PalletOwner`.
///
/// May only be called either by root, or by `PalletOwner`.
#[pallet::call_index(1)]
#[pallet::weight((T::DbWeight::get().reads_writes(1, 1), DispatchClass::Operational))]
pub fn set_owner(origin: OriginFor<T>, new_owner: Option<T::AccountId>) -> DispatchResult {
<Self as OwnedBridgeModule<_>>::set_owner(origin, new_owner)
}
/// Halt or resume all pallet operations.
///
/// May only be called either by root, or by `PalletOwner`.
#[pallet::call_index(2)]
#[pallet::weight((T::DbWeight::get().reads_writes(1, 1), DispatchClass::Operational))]
pub fn set_operating_mode(
origin: OriginFor<T>,
operating_mode: BasicOperatingMode,
) -> DispatchResult {
<Self as OwnedBridgeModule<_>>::set_operating_mode(origin, operating_mode)
Ok(PostDispatchInfo { actual_weight: Some(actual_weight), pays_fee })
}
}
@@ -545,18 +647,20 @@ pub mod pallet {
pub(super) fn update_parachain_head(
parachain: ParaId,
stored_best_head: Option<ParaInfo>,
new_at_relay_block_number: RelayBlockNumber,
new_at_relay_block: HeaderId<RelayBlockHash, RelayBlockNumber>,
new_head_data: ParaStoredHeaderData,
new_head_hash: ParaHash,
) -> Result<UpdateParachainHeadArtifacts, ()> {
// check if head has been already updated at better relay chain block. Without this
// check, we may import heads in random order
let update = SubmitParachainHeadsInfo {
at_relay_block_number: new_at_relay_block_number,
at_relay_block: new_at_relay_block,
para_id: parachain,
para_head_hash: new_head_hash,
// doesn't actually matter here
is_free_execution_expected: false,
};
if SubmitParachainHeadsHelper::<T, I>::is_obsolete(&update) {
if SubmitParachainHeadsHelper::<T, I>::check_obsolete(&update).is_err() {
Self::deposit_event(Event::RejectedObsoleteParachainHead {
parachain,
parachain_head_hash: new_head_hash,
@@ -596,7 +700,7 @@ pub mod pallet {
ImportedParaHashes::<T, I>::try_get(parachain, next_imported_hash_position);
let updated_best_para_head = ParaInfo {
best_head_hash: BestParaHeadHash {
at_relay_block_number: new_at_relay_block_number,
at_relay_block_number: new_at_relay_block.0,
head_hash: new_head_hash,
},
next_imported_hash_position: (next_imported_hash_position + 1) %
@@ -610,9 +714,10 @@ pub mod pallet {
ImportedParaHeads::<T, I>::insert(parachain, new_head_hash, updated_head_data);
log::trace!(
target: LOG_TARGET,
"Updated head of parachain {:?} to {}",
"Updated head of parachain {:?} to {} at relay block {}",
parachain,
new_head_hash,
new_at_relay_block.0,
);
// remove old head
@@ -696,14 +801,28 @@ impl<T: Config<I>, I: 'static, C: Parachain<Hash = ParaHash>> HeaderChain<C>
pub fn initialize_for_benchmarks<T: Config<I>, I: 'static, PC: Parachain<Hash = ParaHash>>(
header: HeaderOf<PC>,
) {
use bp_runtime::HeaderIdProvider;
use sp_runtime::traits::Header;
let relay_head =
pallet_bridge_grandpa::BridgedHeader::<T, T::BridgesGrandpaPalletInstance>::new(
0,
Default::default(),
Default::default(),
Default::default(),
Default::default(),
);
let parachain = ParaId(PC::PARACHAIN_ID);
let parachain_head = ParaHead(header.encode());
let updated_head_data = T::ParaStoredHeaderDataBuilder::try_build(parachain, &parachain_head)
.expect("failed to build stored parachain head in benchmarks");
pallet_bridge_grandpa::initialize_for_benchmarks::<T, T::BridgesGrandpaPalletInstance>(
relay_head.clone(),
);
Pallet::<T, I>::update_parachain_head(
parachain,
None,
0,
relay_head.id(),
updated_head_data,
parachain_head.hash(),
)
@@ -714,9 +833,9 @@ pub fn initialize_for_benchmarks<T: Config<I>, I: 'static, PC: Parachain<Hash =
pub(crate) mod tests {
use super::*;
use crate::mock::{
run_test, test_relay_header, BigParachainHeader, RegularParachainHasher,
RegularParachainHeader, RelayBlockHeader, RuntimeEvent as TestEvent, RuntimeOrigin,
TestRuntime, UNTRACKED_PARACHAIN_ID,
run_test, test_relay_header, BigParachain, BigParachainHeader, FreeHeadersInterval,
RegularParachainHasher, RegularParachainHeader, RelayBlockHeader,
RuntimeEvent as TestEvent, RuntimeOrigin, TestRuntime, UNTRACKED_PARACHAIN_ID,
};
use bp_test_utils::prepare_parachain_heads_proof;
use codec::Encode;
@@ -736,8 +855,9 @@ pub(crate) mod tests {
use frame_support::{
assert_noop, assert_ok,
dispatch::DispatchResultWithPostInfo,
pallet_prelude::Pays,
storage::generator::{StorageDoubleMap, StorageMap},
traits::{Get, OnInitialize},
traits::Get,
weights::Weight,
};
use frame_system::{EventRecord, Pallet as System, Phase};
@@ -749,6 +869,7 @@ pub(crate) mod tests {
type DbWeight = <TestRuntime as frame_system::Config>::DbWeight;
pub(crate) fn initialize(state_root: RelayBlockHash) -> RelayBlockHash {
pallet_bridge_grandpa::FreeHeadersRemaining::<TestRuntime, BridgesGrandpaPalletInstance>::set(Some(100));
pallet_bridge_grandpa::Pallet::<TestRuntime, BridgesGrandpaPalletInstance>::initialize(
RuntimeOrigin::root(),
bp_header_chain::InitializationData {
@@ -770,10 +891,6 @@ pub(crate) mod tests {
num: RelayBlockNumber,
state_root: RelayBlockHash,
) -> (ParaHash, GrandpaJustification<RelayBlockHeader>) {
pallet_bridge_grandpa::Pallet::<TestRuntime, BridgesGrandpaPalletInstance>::on_initialize(
0,
);
let header = test_relay_header(num, state_root);
let hash = header.hash();
let justification = make_default_justification(&header);
@@ -783,6 +900,7 @@ pub(crate) mod tests {
Box::new(header),
justification.clone(),
TEST_GRANDPA_SET_ID,
false,
)
);
@@ -908,7 +1026,7 @@ pub(crate) mod tests {
run_test(|| {
initialize(state_root);
// we're trying to update heads of parachains 1, 2 and 3
// we're trying to update heads of parachains 1 and 3
let expected_weight =
WeightInfo::submit_parachain_heads_weight(DbWeight::get(), &proof, 2);
let result = Pallet::<TestRuntime>::submit_parachain_heads(
@@ -918,9 +1036,10 @@ pub(crate) mod tests {
proof,
);
assert_ok!(result);
assert_eq!(result.expect("checked above").pays_fee, Pays::Yes);
assert_eq!(result.expect("checked above").actual_weight, Some(expected_weight));
// but only 1 and 2 are updated, because proof is missing head of parachain#2
// 1 and 3 are updated, because proof is missing head of parachain#2
assert_eq!(ParasInfo::<TestRuntime>::get(ParaId(1)), Some(initial_best_head(1)));
assert_eq!(ParasInfo::<TestRuntime>::get(ParaId(2)), None);
assert_eq!(
@@ -989,7 +1108,9 @@ pub(crate) mod tests {
run_test(|| {
// start with relay block #0 and import head#5 of parachain#1
initialize(state_root_5);
assert_ok!(import_parachain_1_head(0, state_root_5, parachains_5, proof_5));
let result = import_parachain_1_head(0, state_root_5, parachains_5, proof_5);
// first parachain head is imported for free
assert_eq!(result.unwrap().pays_fee, Pays::No);
assert_eq!(
ParasInfo::<TestRuntime>::get(ParaId(1)),
Some(ParaInfo {
@@ -1024,7 +1145,9 @@ pub(crate) mod tests {
// import head#10 of parachain#1 at relay block #1
let (relay_1_hash, justification) = proceed(1, state_root_10);
assert_ok!(import_parachain_1_head(1, state_root_10, parachains_10, proof_10));
let result = import_parachain_1_head(1, state_root_10, parachains_10, proof_10);
// second parachain head is imported for fee
assert_eq!(result.unwrap().pays_fee, Pays::Yes);
assert_eq!(
ParasInfo::<TestRuntime>::get(ParaId(1)),
Some(ParaInfo {
@@ -1647,4 +1770,143 @@ pub(crate) mod tests {
);
})
}
#[test]
fn may_be_free_for_submitting_filtered_heads() {
run_test(|| {
let (state_root, proof, parachains) =
prepare_parachain_heads_proof::<RegularParachainHeader>(vec![(2, head_data(2, 5))]);
// start with relay block #0 and import head#5 of parachain#2
initialize(state_root);
// first submission is free
let result = Pallet::<TestRuntime>::submit_parachain_heads(
RuntimeOrigin::signed(1),
(0, test_relay_header(0, state_root).hash()),
parachains.clone(),
proof.clone(),
);
assert_eq!(result.unwrap().pays_fee, Pays::No);
// next submission is NOT free, because we haven't updated anything
let result = Pallet::<TestRuntime>::submit_parachain_heads(
RuntimeOrigin::signed(1),
(0, test_relay_header(0, state_root).hash()),
parachains,
proof,
);
assert_eq!(result.unwrap().pays_fee, Pays::Yes);
// then we submit new head, proved at relay block `FreeHeadersInterval - 1` => Pays::Yes
let (state_root, proof, parachains) = prepare_parachain_heads_proof::<
RegularParachainHeader,
>(vec![(2, head_data(2, 50))]);
let relay_block_number = FreeHeadersInterval::get() - 1;
proceed(relay_block_number, state_root);
let result = Pallet::<TestRuntime>::submit_parachain_heads(
RuntimeOrigin::signed(1),
(relay_block_number, test_relay_header(relay_block_number, state_root).hash()),
parachains,
proof,
);
assert_eq!(result.unwrap().pays_fee, Pays::Yes);
// then we submit new head, proved after `FreeHeadersInterval` => Pays::No
let (state_root, proof, parachains) = prepare_parachain_heads_proof::<
RegularParachainHeader,
>(vec![(2, head_data(2, 100))]);
let relay_block_number = relay_block_number + FreeHeadersInterval::get();
proceed(relay_block_number, state_root);
let result = Pallet::<TestRuntime>::submit_parachain_heads(
RuntimeOrigin::signed(1),
(relay_block_number, test_relay_header(relay_block_number, state_root).hash()),
parachains,
proof,
);
assert_eq!(result.unwrap().pays_fee, Pays::No);
// then we submit new BIG head, proved after `FreeHeadersInterval` => Pays::Yes
// then we submit new head, proved after `FreeHeadersInterval` => Pays::No
let mut large_head = head_data(2, 100);
large_head.0.extend(&[42u8; BigParachain::MAX_HEADER_SIZE as _]);
let (state_root, proof, parachains) =
prepare_parachain_heads_proof::<RegularParachainHeader>(vec![(2, large_head)]);
let relay_block_number = relay_block_number + FreeHeadersInterval::get();
proceed(relay_block_number, state_root);
let result = Pallet::<TestRuntime>::submit_parachain_heads(
RuntimeOrigin::signed(1),
(relay_block_number, test_relay_header(relay_block_number, state_root).hash()),
parachains,
proof,
);
assert_eq!(result.unwrap().pays_fee, Pays::Yes);
})
}
#[test]
fn grandpa_and_parachain_pallets_share_free_headers_counter() {
run_test(|| {
initialize(Default::default());
// set free headers limit to `4`
let mut free_headers_remaining = 4;
pallet_bridge_grandpa::FreeHeadersRemaining::<TestRuntime, BridgesGrandpaPalletInstance>::set(
Some(free_headers_remaining),
);
// import free GRANDPA and parachain headers
let mut relay_block_number = 0;
for i in 0..2 {
// import free GRANDPA header
let (state_root, proof, parachains) = prepare_parachain_heads_proof::<
RegularParachainHeader,
>(vec![(2, head_data(2, 5 + i))]);
relay_block_number = relay_block_number + FreeHeadersInterval::get();
proceed(relay_block_number, state_root);
assert_eq!(
pallet_bridge_grandpa::FreeHeadersRemaining::<
TestRuntime,
BridgesGrandpaPalletInstance,
>::get(),
Some(free_headers_remaining - 1),
);
free_headers_remaining = free_headers_remaining - 1;
// import free parachain header
assert_ok!(Pallet::<TestRuntime>::submit_parachain_heads(
RuntimeOrigin::signed(1),
(relay_block_number, test_relay_header(relay_block_number, state_root).hash()),
parachains,
proof,
),);
assert_eq!(
pallet_bridge_grandpa::FreeHeadersRemaining::<
TestRuntime,
BridgesGrandpaPalletInstance,
>::get(),
Some(free_headers_remaining - 1),
);
free_headers_remaining = free_headers_remaining - 1;
}
// try to import free GRANDPA header => non-free execution
let (state_root, proof, parachains) =
prepare_parachain_heads_proof::<RegularParachainHeader>(vec![(2, head_data(2, 7))]);
relay_block_number = relay_block_number + FreeHeadersInterval::get();
let result = pallet_bridge_grandpa::Pallet::<TestRuntime, BridgesGrandpaPalletInstance>::submit_finality_proof_ex(
RuntimeOrigin::signed(1),
Box::new(test_relay_header(relay_block_number, state_root)),
make_default_justification(&test_relay_header(relay_block_number, state_root)),
TEST_GRANDPA_SET_ID,
false,
);
assert_eq!(result.unwrap().pays_fee, Pays::Yes);
// try to import free parachain header => non-free execution
let result = Pallet::<TestRuntime>::submit_parachain_heads(
RuntimeOrigin::signed(1),
(relay_block_number, test_relay_header(relay_block_number, state_root).hash()),
parachains,
proof,
);
assert_eq!(result.unwrap().pays_fee, Pays::Yes);
assert_eq!(
pallet_bridge_grandpa::FreeHeadersRemaining::<
TestRuntime,
BridgesGrandpaPalletInstance,
>::get(),
Some(0),
);
});
}
}
+9 -2
View File
@@ -70,6 +70,7 @@ impl Chain for Parachain1 {
impl Parachain for Parachain1 {
const PARACHAIN_ID: u32 = 1;
const MAX_HEADER_SIZE: u32 = 1_024;
}
pub struct Parachain2;
@@ -96,6 +97,7 @@ impl Chain for Parachain2 {
impl Parachain for Parachain2 {
const PARACHAIN_ID: u32 = 2;
const MAX_HEADER_SIZE: u32 = 1_024;
}
pub struct Parachain3;
@@ -122,6 +124,7 @@ impl Chain for Parachain3 {
impl Parachain for Parachain3 {
const PARACHAIN_ID: u32 = 3;
const MAX_HEADER_SIZE: u32 = 1_024;
}
// this parachain is using u128 as block number and stored head data size exceeds limit
@@ -149,6 +152,7 @@ impl Chain for BigParachain {
impl Parachain for BigParachain {
const PARACHAIN_ID: u32 = 4;
const MAX_HEADER_SIZE: u32 = 2_048;
}
construct_runtime! {
@@ -168,12 +172,14 @@ impl frame_system::Config for TestRuntime {
parameter_types! {
pub const HeadersToKeep: u32 = 5;
pub const FreeHeadersInterval: u32 = 15;
}
impl pallet_bridge_grandpa::Config<pallet_bridge_grandpa::Instance1> for TestRuntime {
type RuntimeEvent = RuntimeEvent;
type BridgedChain = TestBridgedChain;
type MaxFreeMandatoryHeadersPerBlock = ConstU32<2>;
type MaxFreeHeadersPerBlock = ConstU32<2>;
type FreeHeadersInterval = FreeHeadersInterval;
type HeadersToKeep = HeadersToKeep;
type WeightInfo = ();
}
@@ -181,7 +187,8 @@ impl pallet_bridge_grandpa::Config<pallet_bridge_grandpa::Instance1> for TestRun
impl pallet_bridge_grandpa::Config<pallet_bridge_grandpa::Instance2> for TestRuntime {
type RuntimeEvent = RuntimeEvent;
type BridgedChain = TestBridgedChain;
type MaxFreeMandatoryHeadersPerBlock = ConstU32<2>;
type MaxFreeHeadersPerBlock = ConstU32<2>;
type FreeHeadersInterval = FreeHeadersInterval;
type HeadersToKeep = HeadersToKeep;
type WeightInfo = ();
}
+26 -1
View File
@@ -36,6 +36,20 @@ pub const EXTRA_STORAGE_PROOF_SIZE: u32 = 1024;
/// Extended weight info.
pub trait WeightInfoExt: WeightInfo {
// Our configuration assumes that the runtime has special signed extensions used to:
//
// 1) boost priority of `submit_parachain_heads` transactions;
//
// 2) slash relayer if he submits an invalid transaction.
//
// We read and update storage values of other pallets (`pallet-bridge-relayers` and
// balances/assets pallet). So we need to add this weight to the weight of our call.
// Hence two following methods.
/// Extra weight that is added to the `submit_finality_proof` call weight by signed extensions
/// that are declared at runtime level.
fn submit_parachain_heads_overhead_from_runtime() -> Weight;
/// Storage proof overhead, that is included in every storage proof.
///
/// The relayer would pay some extra fee for additional proof bytes, since they mean
@@ -65,7 +79,10 @@ pub trait WeightInfoExt: WeightInfo {
let pruning_weight =
Self::parachain_head_pruning_weight(db_weight).saturating_mul(parachains_count as u64);
base_weight.saturating_add(proof_size_overhead).saturating_add(pruning_weight)
base_weight
.saturating_add(proof_size_overhead)
.saturating_add(pruning_weight)
.saturating_add(Self::submit_parachain_heads_overhead_from_runtime())
}
/// Returns weight of single parachain head storage update.
@@ -95,12 +112,20 @@ pub trait WeightInfoExt: WeightInfo {
}
impl WeightInfoExt for () {
fn submit_parachain_heads_overhead_from_runtime() -> Weight {
Weight::zero()
}
fn expected_extra_storage_proof_size() -> u32 {
EXTRA_STORAGE_PROOF_SIZE
}
}
impl<T: frame_system::Config> WeightInfoExt for BridgeWeight<T> {
fn submit_parachain_heads_overhead_from_runtime() -> Weight {
Weight::zero()
}
fn expected_extra_storage_proof_size() -> u32 {
EXTRA_STORAGE_PROOF_SIZE
}
+19
View File
@@ -116,6 +116,10 @@ impl ParaStoredHeaderData {
/// Stored parachain head data builder.
pub trait ParaStoredHeaderDataBuilder {
/// Maximal parachain head size that we may accept for free. All heads above
/// this limit are submitted for a regular fee.
fn max_free_head_size() -> u32;
/// Return number of parachains that are supported by this builder.
fn supported_parachains() -> u32;
@@ -127,6 +131,10 @@ pub trait ParaStoredHeaderDataBuilder {
pub struct SingleParaStoredHeaderDataBuilder<C: Parachain>(PhantomData<C>);
impl<C: Parachain> ParaStoredHeaderDataBuilder for SingleParaStoredHeaderDataBuilder<C> {
fn max_free_head_size() -> u32 {
C::MAX_HEADER_SIZE
}
fn supported_parachains() -> u32 {
1
}
@@ -147,6 +155,17 @@ impl<C: Parachain> ParaStoredHeaderDataBuilder for SingleParaStoredHeaderDataBui
#[impl_trait_for_tuples::impl_for_tuples(1, 30)]
#[tuple_types_custom_trait_bound(Parachain)]
impl ParaStoredHeaderDataBuilder for C {
fn max_free_head_size() -> u32 {
let mut result = 0_u32;
for_tuples!( #(
result = sp_std::cmp::max(
result,
SingleParaStoredHeaderDataBuilder::<C>::max_free_head_size(),
);
)* );
result
}
fn supported_parachains() -> u32 {
let mut result = 0;
for_tuples!( #(
+20
View File
@@ -236,6 +236,12 @@ where
pub trait Parachain: Chain {
/// Parachain identifier.
const PARACHAIN_ID: u32;
/// Maximal size of the parachain header.
///
/// This isn't a strict limit. The relayer may submit larger headers and the
/// pallet will accept the call. The limit is only used to compute whether
/// the refund can be made.
const MAX_HEADER_SIZE: u32;
}
impl<T> Parachain for T
@@ -244,6 +250,8 @@ where
<T as UnderlyingChainProvider>::Chain: Parachain,
{
const PARACHAIN_ID: u32 = <<T as UnderlyingChainProvider>::Chain as Parachain>::PARACHAIN_ID;
const MAX_HEADER_SIZE: u32 =
<<T as UnderlyingChainProvider>::Chain as Parachain>::MAX_HEADER_SIZE;
}
/// Adapter for `Get<u32>` to access `PARACHAIN_ID` from `trait Parachain`
@@ -306,6 +314,11 @@ 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::free_headers_interval` runtime method.
pub const [<FREE_HEADERS_INTERVAL_FOR_ $chain:upper _METHOD>]: &str =
stringify!([<$chain:camel FinalityApi_free_headers_interval>]);
$(
/// Name of the `<ThisChain>FinalityApi::accepted_<consensus>_finality_proofs`
/// runtime method.
@@ -322,6 +335,13 @@ macro_rules! decl_bridge_finality_runtime_apis {
/// Returns number and hash of the best finalized header known to the bridge module.
fn best_finalized() -> Option<bp_runtime::HeaderId<Hash, BlockNumber>>;
/// Returns free headers interval, if it is configured in the runtime.
/// The caller expects that if his transaction improves best known header
/// at least by the free_headers_interval`, it will be fee-free.
///
/// See [`pallet_bridge_grandpa::Config::FreeHeadersInterval`] for details.
fn free_headers_interval() -> Option<BlockNumber>;
$(
/// Returns the justifications accepted in the current block.
fn [<synced_headers_ $consensus:lower _info>](
@@ -110,6 +110,7 @@ impl bp_runtime::Chain for TestParachainBase {
impl bp_runtime::Parachain for TestParachainBase {
const PARACHAIN_ID: u32 = 1000;
const MAX_HEADER_SIZE: u32 = 1_024;
}
/// Parachain that may be used in tests.
@@ -40,7 +40,7 @@ cumulus_based = true
rpc_port = 8933
ws_port = 8943
args = [
"-lparachain=debug,runtime::bridge-hub=trace,runtime::bridge=trace,runtime::bridge-dispatch=trace,bridge=trace,runtime::bridge-messages=trace,xcm=trace"
"-lparachain=debug,runtime::bridge=trace,xcm=trace,txpool=trace"
]
# run bob as parachain collator
@@ -51,7 +51,7 @@ cumulus_based = true
rpc_port = 8934
ws_port = 8944
args = [
"-lparachain=trace,runtime::bridge-hub=trace,runtime::bridge=trace,runtime::bridge-dispatch=trace,bridge=trace,runtime::bridge-messages=trace,xcm=trace"
"-lparachain=debug,runtime::bridge=trace,xcm=trace,txpool=trace"
]
[[parachains]]
@@ -65,14 +65,14 @@ cumulus_based = true
ws_port = 9910
command = "{{POLKADOT_PARACHAIN_BINARY}}"
args = [
"-lparachain=debug,xcm=trace,runtime::bridge-transfer=trace"
"-lparachain=debug,xcm=trace,runtime::bridge=trace,txpool=trace"
]
[[parachains.collators]]
name = "asset-hub-rococo-collator2"
command = "{{POLKADOT_PARACHAIN_BINARY}}"
args = [
"-lparachain=debug,xcm=trace,runtime::bridge-transfer=trace"
"-lparachain=debug,xcm=trace,runtime::bridge=trace,txpool=trace"
]
#[[hrmp_channels]]
@@ -40,7 +40,7 @@ cumulus_based = true
rpc_port = 8935
ws_port = 8945
args = [
"-lparachain=debug,runtime::mmr=info,substrate=info,runtime=info,runtime::bridge-hub=trace,runtime::bridge=trace,runtime::bridge-dispatch=trace,bridge=trace,runtime::bridge-messages=trace,xcm=trace"
"-lparachain=debug,runtime::bridge=trace,xcm=trace,txpool=trace"
]
# run bob as parachain collator
@@ -51,7 +51,7 @@ cumulus_based = true
rpc_port = 8936
ws_port = 8946
args = [
"-lparachain=trace,runtime::mmr=info,substrate=info,runtime=info,runtime::bridge-hub=trace,runtime::bridge=trace,runtime::bridge-dispatch=trace,bridge=trace,runtime::bridge-messages=trace,xcm=trace"
"-lparachain=debug,runtime::bridge=trace,xcm=trace,txpool=trace"
]
[[parachains]]
@@ -65,14 +65,14 @@ cumulus_based = true
ws_port = 9010
command = "{{POLKADOT_PARACHAIN_BINARY}}"
args = [
"-lparachain=debug,xcm=trace,runtime::bridge-transfer=trace"
"-lparachain=debug,xcm=trace,runtime::bridge=trace,txpool=trace"
]
[[parachains.collators]]
name = "asset-hub-westend-collator2"
command = "{{POLKADOT_PARACHAIN_BINARY}}"
args = [
"-lparachain=debug,xcm=trace,runtime::bridge-transfer=trace"
"-lparachain=debug,xcm=trace,runtime::bridge=trace,txpool=trace"
]
#[[hrmp_channels]]
@@ -169,12 +169,107 @@ function run_relay() {
--lane "${LANE_ID}"
}
function run_finality_relay() {
local relayer_path=$(ensure_relayer)
RUST_LOG=runtime=trace,rpc=trace,bridge=trace \
$relayer_path relay-headers rococo-to-bridge-hub-westend \
--only-free-headers \
--source-host localhost \
--source-port 9942 \
--target-host localhost \
--target-port 8945 \
--target-version-mode Auto \
--target-signer //Charlie \
--target-transactions-mortality 4&
RUST_LOG=runtime=trace,rpc=trace,bridge=trace \
$relayer_path relay-headers westend-to-bridge-hub-rococo \
--only-free-headers \
--source-host localhost \
--source-port 9945 \
--target-host localhost \
--target-port 8943 \
--target-version-mode Auto \
--target-signer //Charlie \
--target-transactions-mortality 4
}
function run_parachains_relay() {
local relayer_path=$(ensure_relayer)
RUST_LOG=runtime=trace,rpc=trace,bridge=trace \
$relayer_path relay-parachains rococo-to-bridge-hub-westend \
--only-free-headers \
--source-host localhost \
--source-port 9942 \
--target-host localhost \
--target-port 8945 \
--target-version-mode Auto \
--target-signer //Dave \
--target-transactions-mortality 4&
RUST_LOG=runtime=trace,rpc=trace,bridge=trace \
$relayer_path relay-parachains westend-to-bridge-hub-rococo \
--only-free-headers \
--source-host localhost \
--source-port 9945 \
--target-host localhost \
--target-port 8943 \
--target-version-mode Auto \
--target-signer //Dave \
--target-transactions-mortality 4
}
function run_messages_relay() {
local relayer_path=$(ensure_relayer)
RUST_LOG=runtime=trace,rpc=trace,bridge=trace \
$relayer_path relay-messages bridge-hub-rococo-to-bridge-hub-westend \
--source-host localhost \
--source-port 8943 \
--source-version-mode Auto \
--source-signer //Eve \
--source-transactions-mortality 4 \
--target-host localhost \
--target-port 8945 \
--target-version-mode Auto \
--target-signer //Eve \
--target-transactions-mortality 4 \
--lane $LANE_ID&
RUST_LOG=runtime=trace,rpc=trace,bridge=trace \
$relayer_path relay-messages bridge-hub-westend-to-bridge-hub-rococo \
--source-host localhost \
--source-port 8945 \
--source-version-mode Auto \
--source-signer //Ferdie \
--source-transactions-mortality 4 \
--target-host localhost \
--target-port 8943 \
--target-version-mode Auto \
--target-signer //Ferdie \
--target-transactions-mortality 4 \
--lane $LANE_ID
}
case "$1" in
run-relay)
init_wnd_ro
init_ro_wnd
run_relay
;;
run-finality-relay)
init_wnd_ro
init_ro_wnd
run_finality_relay
;;
run-parachains-relay)
run_parachains_relay
;;
run-messages-relay)
run_messages_relay
;;
init-asset-hub-rococo-local)
ensure_polkadot_js_api
# create foreign assets for native Westend token (governance call on Rococo)
@@ -386,6 +481,9 @@ case "$1" in
echo "A command is require. Supported commands for:
Local (zombienet) run:
- run-relay
- run-finality-relay
- run-parachains-relay
- run-messages-relay
- init-asset-hub-rococo-local
- init-bridge-hub-rococo-local
- init-asset-hub-westend-local
+11
View File
@@ -0,0 +1,11 @@
#!/bin/bash
# Rococo AH
xdg-open https://polkadot.js.org/apps/?rpc=ws://127.0.0.1:9910#/explorer&
# Rococo BH
xdg-open https://polkadot.js.org/apps/?rpc=ws://127.0.0.1:8943#/explorer&
# Westend BH
xdg-open https://polkadot.js.org/apps/?rpc=ws://127.0.0.1:8945#/explorer&
# Westend AH
xdg-open https://polkadot.js.org/apps/?rpc=ws://127.0.0.1:9010#/explorer&
@@ -1,3 +1,9 @@
#!/bin/bash
$ENV_PATH/bridges_rococo_westend.sh "$@"
if [ $1 == "auto-log" ]; then
shift # ignore "auto-log"
log_name=$1
$ENV_PATH/bridges_rococo_westend.sh "$@" >$TEST_DIR/logs/$log_name.log
else
$ENV_PATH/bridges_rococo_westend.sh "$@"
fi
@@ -59,12 +59,12 @@ if [[ $init -eq 1 ]]; then
fi
if [[ $start_relayer -eq 1 ]]; then
${BASH_SOURCE%/*}/start_relayer.sh $rococo_dir $westend_dir relayer_pid
${BASH_SOURCE%/*}/start_relayer.sh $rococo_dir $westend_dir finality_relayer_pid parachains_relayer_pid messages_relayer_pid
fi
echo $rococo_dir > $TEST_DIR/rococo.env
echo $westend_dir > $TEST_DIR/westend.env
echo
wait -n $rococo_pid $westend_pid $relayer_pid
wait -n $rococo_pid $westend_pid $finality_relayer_pid $parachains_relayer_pid $messages_relayer_pid
kill -9 -$$
@@ -7,17 +7,31 @@ source "$FRAMEWORK_PATH/utils/zombienet.sh"
rococo_dir=$1
westend_dir=$2
__relayer_pid=$3
__finality_relayer_pid=$3
__parachains_relayer_pid=$4
__messages_relayer_pid=$5
logs_dir=$TEST_DIR/logs
helper_script="${BASH_SOURCE%/*}/helper.sh"
relayer_log=$logs_dir/relayer.log
echo -e "Starting rococo-westend relayer. Logs available at: $relayer_log\n"
start_background_process "$helper_script run-relay" $relayer_log relayer_pid
# start finality relayer
finality_relayer_log=$logs_dir/relayer_finality.log
echo -e "Starting rococo-westend finality relayer. Logs available at: $finality_relayer_log\n"
start_background_process "$helper_script run-finality-relay" $finality_relayer_log finality_relayer_pid
# start parachains relayer
parachains_relayer_log=$logs_dir/relayer_parachains.log
echo -e "Starting rococo-westend parachains relayer. Logs available at: $parachains_relayer_log\n"
start_background_process "$helper_script run-parachains-relay" $parachains_relayer_log parachains_relayer_pid
# start messages relayer
messages_relayer_log=$logs_dir/relayer_messages.log
echo -e "Starting rococo-westend messages relayer. Logs available at: $messages_relayer_log\n"
start_background_process "$helper_script run-messages-relay" $messages_relayer_log messages_relayer_pid
run_zndsl ${BASH_SOURCE%/*}/rococo.zndsl $rococo_dir
run_zndsl ${BASH_SOURCE%/*}/westend.zndsl $westend_dir
eval $__relayer_pid="'$relayer_pid'"
eval $__finality_relayer_pid="'$finality_relayer_pid'"
eval $__parachains_relayer_pid="'$parachains_relayer_pid'"
eval $__messages_relayer_pid="'$messages_relayer_pid'"
@@ -0,0 +1,12 @@
async function run(nodeName, networkInfo, args) {
const {wsUri, userDefinedTypes} = networkInfo.nodesByName[nodeName];
const api = await zombie.connect(wsUri, userDefinedTypes);
const accountAddress = args[0];
const accountData = await api.query.system.account(accountAddress);
const accountBalance = accountData.data['free'];
console.log("Balance of " + accountAddress + ": " + accountBalance);
return accountBalance;
}
module.exports = {run}
@@ -3,10 +3,10 @@ Network: {{ENV_PATH}}/bridge_hub_westend_local_network.toml
Creds: config
# send 5 ROC to //Alice from Rococo AH to Westend AH
asset-hub-westend-collator1: run {{ENV_PATH}}/helper.sh with "reserve-transfer-assets-from-asset-hub-rococo-local 5000000000000" within 120 seconds
asset-hub-westend-collator1: run {{ENV_PATH}}/helper.sh with "auto-log reserve-transfer-assets-from-asset-hub-rococo-local 5000000000000" within 120 seconds
# check that //Alice received at least 4.8 ROC on Westend AH
asset-hub-westend-collator1: js-script {{FRAMEWORK_PATH}}/js-helpers/wrapped-assets-balance.js with "5GrwvaEF5zXb26Fz9rcQpDWS57CtERHpNehXCPcNoHGKutQY,4800000000000,Rococo" within 600 seconds
# check that the relayer //Charlie is rewarded by Westend AH
bridge-hub-westend-collator1: js-script {{FRAMEWORK_PATH}}/js-helpers/relayer-rewards.js with "5FLSigC9HGRKVhB9FiEo4Y3koPsNmBmLJbpXg2mp1hXcS59Y,0x00000002,0x6268726F,ThisChain,0" within 30 seconds
# relayer //Ferdie is rewarded for delivering messages from Rococo BH
bridge-hub-westend-collator1: js-script {{FRAMEWORK_PATH}}/js-helpers/relayer-rewards.js with "5HGjWAeFDfFCWPsjFQdVV2Msvz2XtMktvgocEZcCj68kUMaw,0x00000002,0x6268726F,ThisChain,0" within 300 seconds
@@ -0,0 +1,11 @@
Description: Finality and parachain relays should have the constant balance, because their transactions are free
Network: {{ENV_PATH}}/bridge_hub_rococo_local_network.toml
Creds: config
# local chain spec gives `1u64 << 60` tokens to every endowed account: if it'll ever
# change, it'd need to be fixed here as well
# //Charlie only submits free and mandatory relay chain headers, so the balance should stay the same
bridge-hub-rococo-collator1: js-script {{FRAMEWORK_PATH}}/js-helpers/native-asset-balance.js with "5FLSigC9HGRKVhB9FiEo4Y3koPsNmBmLJbpXg2mp1hXcS59Y" return is 1152921504606846976 within 30 seconds
# //Dave only submits free parachain headers, so the balance should stay the same
bridge-hub-rococo-collator1: js-script {{FRAMEWORK_PATH}}/js-helpers/native-asset-balance.js with "5DAAnrj7VHTznn2AWBemMuyBwZWs6FNFjdyVXUeYum3PTXFy" return is 1152921504606846976 within 30 seconds
@@ -18,8 +18,14 @@ ensure_process_file $env_pid $TEST_DIR/westend.env 300
westend_dir=`cat $TEST_DIR/westend.env`
echo
run_zndsl ${BASH_SOURCE%/*}/roc-relayer-balance-does-not-change.zndsl $rococo_dir
run_zndsl ${BASH_SOURCE%/*}/wnd-relayer-balance-does-not-change.zndsl $westend_dir
run_zndsl ${BASH_SOURCE%/*}/roc-reaches-westend.zndsl $westend_dir
run_zndsl ${BASH_SOURCE%/*}/wnd-reaches-rococo.zndsl $rococo_dir
run_zndsl ${BASH_SOURCE%/*}/wroc-reaches-rococo.zndsl $rococo_dir
run_zndsl ${BASH_SOURCE%/*}/wwnd-reaches-westend.zndsl $westend_dir
run_zndsl ${BASH_SOURCE%/*}/roc-relayer-balance-does-not-change.zndsl $rococo_dir
run_zndsl ${BASH_SOURCE%/*}/wnd-relayer-balance-does-not-change.zndsl $westend_dir
@@ -3,10 +3,10 @@ Network: {{ENV_PATH}}/bridge_hub_rococo_local_network.toml
Creds: config
# send 5 WND to //Alice from Westend AH to Rococo AH
asset-hub-rococo-collator1: run {{ENV_PATH}}/helper.sh with "reserve-transfer-assets-from-asset-hub-westend-local 5000000000000" within 120 seconds
asset-hub-rococo-collator1: run {{ENV_PATH}}/helper.sh with "auto-log reserve-transfer-assets-from-asset-hub-westend-local 5000000000000" within 120 seconds
# check that //Alice received at least 4.8 WND on Rococo AH
asset-hub-rococo-collator1: js-script {{FRAMEWORK_PATH}}/js-helpers/wrapped-assets-balance.js with "5GrwvaEF5zXb26Fz9rcQpDWS57CtERHpNehXCPcNoHGKutQY,4800000000000,Westend" within 600 seconds
# check that the relayer //Charlie is rewarded by Rococo AH
bridge-hub-rococo-collator1: js-script {{FRAMEWORK_PATH}}/js-helpers/relayer-rewards.js with "5FLSigC9HGRKVhB9FiEo4Y3koPsNmBmLJbpXg2mp1hXcS59Y,0x00000002,0x62687764,ThisChain,0" within 30 seconds
# relayer //Eve is rewarded for delivering messages from Westend BH
bridge-hub-rococo-collator1: js-script {{FRAMEWORK_PATH}}/js-helpers/relayer-rewards.js with "5CiPPseXPECbkjWCa6MnjNokrgYjMqmKndv2rSnekmSK2DjL,0x00000002,0x62687764,ThisChain,0" within 300 seconds
@@ -0,0 +1,11 @@
Description: Finality and parachain relays should have the constant balance, because their transactions are free
Network: {{ENV_PATH}}/bridge_hub_westend_local_network.toml
Creds: config
# local chain spec gives `1u64 << 60` tokens to every endowed account: if it'll ever
# change, it'd need to be fixed here as well
# //Charlie only submits free and mandatory relay chain headers, so the balance should stay the same
bridge-hub-westend-collator1: js-script {{FRAMEWORK_PATH}}/js-helpers/native-asset-balance.js with "5FLSigC9HGRKVhB9FiEo4Y3koPsNmBmLJbpXg2mp1hXcS59Y" return is 1152921504606846976 within 30 seconds
# //Dave only submits free parachain headers, so the balance should stay the same
bridge-hub-westend-collator1: js-script {{FRAMEWORK_PATH}}/js-helpers/native-asset-balance.js with "5DAAnrj7VHTznn2AWBemMuyBwZWs6FNFjdyVXUeYum3PTXFy" return is 1152921504606846976 within 30 seconds
@@ -22,6 +22,7 @@ scale-info = { version = "2.11.1", default-features = false, features = [
"derive",
] }
serde = { optional = true, features = ["derive"], workspace = true, default-features = true }
tuplex = { version = "0.1", default-features = false }
# Substrate
frame-benchmarking = { path = "../../../../../substrate/frame/benchmarking", default-features = false, optional = true }
@@ -218,6 +219,7 @@ std = [
"sp-version/std",
"substrate-wasm-builder",
"testnet-parachains-constants/std",
"tuplex/std",
"xcm-builder/std",
"xcm-executor/std",
"xcm/std",
@@ -49,7 +49,8 @@ pub type BridgeGrandpaWestendInstance = pallet_bridge_grandpa::Instance3;
impl pallet_bridge_grandpa::Config<BridgeGrandpaWestendInstance> for Runtime {
type RuntimeEvent = RuntimeEvent;
type BridgedChain = bp_westend::Westend;
type MaxFreeMandatoryHeadersPerBlock = ConstU32<4>;
type MaxFreeHeadersPerBlock = ConstU32<4>;
type FreeHeadersInterval = ConstU32<5>;
type HeadersToKeep = RelayChainHeadersToKeep;
type WeightInfo = weights::pallet_bridge_grandpa::WeightInfo<Runtime>;
}
@@ -89,7 +90,8 @@ pub type BridgeGrandpaRococoBulletinInstance = pallet_bridge_grandpa::Instance4;
impl pallet_bridge_grandpa::Config<BridgeGrandpaRococoBulletinInstance> for Runtime {
type RuntimeEvent = RuntimeEvent;
type BridgedChain = bp_polkadot_bulletin::PolkadotBulletin;
type MaxFreeMandatoryHeadersPerBlock = ConstU32<4>;
type MaxFreeHeadersPerBlock = ConstU32<4>;
type FreeHeadersInterval = ConstU32<5>;
type HeadersToKeep = RelayChainHeadersToKeep;
// Technically this is incorrect - we have two pallet instances and ideally we shall
// benchmark every instance separately. But the benchmarking engine has a flaw - it
@@ -20,17 +20,15 @@
//! are reusing Polkadot Bulletin chain primitives everywhere here.
use crate::{
bridge_common_config::{BridgeGrandpaRococoBulletinInstance, BridgeHubRococo},
weights,
xcm_config::UniversalLocation,
AccountId, BridgeRococoBulletinGrandpa, BridgeRococoBulletinMessages, PolkadotXcm, Runtime,
RuntimeEvent, XcmOverRococoBulletin, XcmRouter,
bridge_common_config::BridgeHubRococo, weights, xcm_config::UniversalLocation, AccountId,
BridgeRococoBulletinGrandpa, BridgeRococoBulletinMessages, PolkadotXcm, Runtime, RuntimeEvent,
XcmOverRococoBulletin, XcmRouter,
};
use bp_messages::LaneId;
use bp_runtime::Chain;
use bridge_runtime_common::{
extensions::refund_relayer_extension::{
ActualFeeRefund, RefundBridgedGrandpaMessages, RefundSignedExtensionAdapter,
ActualFeeRefund, RefundBridgedMessages, RefundSignedExtensionAdapter,
RefundableMessagesLane,
},
messages,
@@ -83,6 +81,9 @@ parameter_types! {
pub const RococoPeopleToRococoBulletinMessagesLane: bp_messages::LaneId
= XCM_LANE_FOR_ROCOCO_PEOPLE_TO_ROCOCO_BULLETIN;
// see the `FEE_BOOST_PER_RELAY_HEADER` constant get the meaning of this value
pub PriorityBoostPerRelayHeader: u64 = 58_014_163_614_163;
/// Priority boost that the registered relayer receives for every additional message in the message
/// delivery transaction.
///
@@ -169,9 +170,8 @@ impl messages::BridgedChainWithMessages for RococoBulletin {}
/// Signed extension that refunds relayers that are delivering messages from the Rococo Bulletin
/// chain.
pub type OnBridgeHubRococoRefundRococoBulletinMessages = RefundSignedExtensionAdapter<
RefundBridgedGrandpaMessages<
RefundBridgedMessages<
Runtime,
BridgeGrandpaRococoBulletinInstance,
RefundableMessagesLane<
WithRococoBulletinMessagesInstance,
RococoPeopleToRococoBulletinMessagesLane,
@@ -244,6 +244,9 @@ mod tests {
/// operational costs and a faster bridge), so this value should be significant.
const FEE_BOOST_PER_MESSAGE: Balance = 2 * rococo::currency::UNITS;
// see `FEE_BOOST_PER_MESSAGE` comment
const FEE_BOOST_PER_RELAY_HEADER: Balance = 2 * rococo::currency::UNITS;
#[test]
fn ensure_bridge_hub_rococo_message_lane_weights_are_correct() {
check_message_lane_weights::<
@@ -273,7 +276,13 @@ mod tests {
// Bulletin chain - it has the same (almost) runtime for Polkadot Bulletin and Rococo
// Bulletin, so we have to adhere Polkadot names here
bridge_runtime_common::extensions::priority_calculator::ensure_priority_boost_is_sane::<
bridge_runtime_common::extensions::priority_calculator::per_relay_header::ensure_priority_boost_is_sane::<
Runtime,
BridgeGrandpaRococoBulletinInstance,
PriorityBoostPerRelayHeader,
>(FEE_BOOST_PER_RELAY_HEADER);
bridge_runtime_common::extensions::priority_calculator::per_message::ensure_priority_boost_is_sane::<
Runtime,
WithRococoBulletinMessagesInstance,
PriorityBoostPerMessage,
@@ -29,8 +29,8 @@ use bp_messages::LaneId;
use bp_runtime::Chain;
use bridge_runtime_common::{
extensions::refund_relayer_extension::{
ActualFeeRefund, RefundBridgedParachainMessages, RefundSignedExtensionAdapter,
RefundableMessagesLane, RefundableParachain,
ActualFeeRefund, RefundBridgedMessages, RefundSignedExtensionAdapter,
RefundableMessagesLane,
},
messages,
messages::{
@@ -65,6 +65,10 @@ parameter_types! {
2,
[GlobalConsensus(WestendGlobalConsensusNetwork::get())]
);
// see the `FEE_BOOST_PER_RELAY_HEADER` constant get the meaning of this value
pub PriorityBoostPerRelayHeader: u64 = 32_007_814_407_814;
// see the `FEE_BOOST_PER_PARACHAIN_HEADER` constant get the meaning of this value
pub PriorityBoostPerParachainHeader: u64 = 1_396_340_903_540_903;
// see the `FEE_BOOST_PER_MESSAGE` constant to get the meaning of this value
pub PriorityBoostPerMessage: u64 = 182_044_444_444_444;
@@ -174,12 +178,8 @@ impl messages::BridgedChainWithMessages for BridgeHubWestend {}
/// Signed extension that refunds relayers that are delivering messages from the Westend parachain.
pub type OnBridgeHubRococoRefundBridgeHubWestendMessages = RefundSignedExtensionAdapter<
RefundBridgedParachainMessages<
RefundBridgedMessages<
Runtime,
RefundableParachain<
BridgeParachainWestendInstance,
bp_bridge_hub_westend::BridgeHubWestend,
>,
RefundableMessagesLane<
WithBridgeHubWestendMessagesInstance,
AssetHubRococoToAssetHubWestendMessagesLane,
@@ -246,6 +246,7 @@ mod tests {
use crate::bridge_common_config::BridgeGrandpaWestendInstance;
use bridge_runtime_common::{
assert_complete_bridge_types,
extensions::refund_relayer_extension::RefundableParachain,
integrity::{
assert_complete_bridge_constants, check_message_lane_weights,
AssertBridgeMessagesPalletConstants, AssertBridgePalletNames, AssertChainConstants,
@@ -266,6 +267,11 @@ mod tests {
/// operational costs and a faster bridge), so this value should be significant.
const FEE_BOOST_PER_MESSAGE: Balance = 2 * rococo::currency::UNITS;
// see `FEE_BOOST_PER_MESSAGE` comment
const FEE_BOOST_PER_RELAY_HEADER: Balance = 2 * rococo::currency::UNITS;
// see `FEE_BOOST_PER_MESSAGE` comment
const FEE_BOOST_PER_PARACHAIN_HEADER: Balance = 2 * rococo::currency::UNITS;
#[test]
fn ensure_bridge_hub_rococo_message_lane_weights_are_correct() {
check_message_lane_weights::<
@@ -318,7 +324,19 @@ mod tests {
},
});
bridge_runtime_common::extensions::priority_calculator::ensure_priority_boost_is_sane::<
bridge_runtime_common::extensions::priority_calculator::per_relay_header::ensure_priority_boost_is_sane::<
Runtime,
BridgeGrandpaWestendInstance,
PriorityBoostPerRelayHeader,
>(FEE_BOOST_PER_RELAY_HEADER);
bridge_runtime_common::extensions::priority_calculator::per_parachain_header::ensure_priority_boost_is_sane::<
Runtime,
RefundableParachain<WithBridgeHubWestendMessagesInstance, BridgeHubWestend>,
PriorityBoostPerParachainHeader,
>(FEE_BOOST_PER_PARACHAIN_HEADER);
bridge_runtime_common::extensions::priority_calculator::per_message::ensure_priority_boost_is_sane::<
Runtime,
WithBridgeHubWestendMessagesInstance,
PriorityBoostPerMessage,
@@ -35,6 +35,12 @@ pub mod bridge_to_westend_config;
mod weights;
pub mod xcm_config;
use bridge_runtime_common::extensions::{
check_obsolete_extension::{
CheckAndBoostBridgeGrandpaTransactions, CheckAndBoostBridgeParachainsTransactions,
},
refund_relayer_extension::RefundableParachain,
};
use cumulus_pallet_parachain_system::RelayNumberMonotonicallyIncreases;
use snowbridge_beacon_primitives::{Fork, ForkVersions};
use snowbridge_core::{
@@ -63,7 +69,7 @@ use frame_support::{
dispatch::DispatchClass,
genesis_builder_helper::{build_state, get_preset},
parameter_types,
traits::{ConstBool, ConstU32, ConstU64, ConstU8, TransformOrigin},
traits::{ConstBool, ConstU32, ConstU64, ConstU8, Get, TransformOrigin},
weights::{ConstantMultiplier, Weight},
PalletId,
};
@@ -740,10 +746,28 @@ pub type XcmOverRococoBulletin = XcmOverPolkadotBulletin;
bridge_runtime_common::generate_bridge_reject_obsolete_headers_and_messages! {
RuntimeCall, AccountId,
// Grandpa
BridgeWestendGrandpa,
BridgeRococoBulletinGrandpa,
CheckAndBoostBridgeGrandpaTransactions<
Runtime,
bridge_common_config::BridgeGrandpaWestendInstance,
bridge_to_westend_config::PriorityBoostPerRelayHeader,
xcm_config::TreasuryAccount,
>,
CheckAndBoostBridgeGrandpaTransactions<
Runtime,
bridge_common_config::BridgeGrandpaRococoBulletinInstance,
bridge_to_bulletin_config::PriorityBoostPerRelayHeader,
xcm_config::TreasuryAccount,
>,
// Parachains
BridgeWestendParachains,
CheckAndBoostBridgeParachainsTransactions<
Runtime,
RefundableParachain<
bridge_common_config::BridgeParachainWestendInstance,
bp_bridge_hub_westend::BridgeHubWestend,
>,
bridge_to_westend_config::PriorityBoostPerParachainHeader,
xcm_config::TreasuryAccount,
>,
// Messages
BridgeWestendMessages,
BridgeRococoBulletinMessages
@@ -938,6 +962,11 @@ impl_runtime_apis! {
fn best_finalized() -> Option<HeaderId<bp_westend::Hash, bp_westend::BlockNumber>> {
BridgeWestendGrandpa::best_finalized()
}
fn free_headers_interval() -> Option<bp_westend::BlockNumber> {
<Runtime as pallet_bridge_grandpa::Config<
bridge_common_config::BridgeGrandpaWestendInstance
>>::FreeHeadersInterval::get()
}
fn synced_headers_grandpa_info(
) -> Vec<bp_header_chain::StoredHeaderGrandpaInfo<bp_westend::Header>> {
BridgeWestendGrandpa::synced_headers_grandpa_info()
@@ -950,6 +979,10 @@ impl_runtime_apis! {
bp_bridge_hub_westend::BridgeHubWestend
>().unwrap_or(None)
}
fn free_headers_interval() -> Option<bp_bridge_hub_westend::BlockNumber> {
// "free interval" is not currently used for parachains
None
}
}
// This is exposed by BridgeHubRococo
@@ -984,6 +1017,12 @@ impl_runtime_apis! {
BridgePolkadotBulletinGrandpa::best_finalized()
}
fn free_headers_interval() -> Option<bp_polkadot_bulletin::BlockNumber> {
<Runtime as pallet_bridge_grandpa::Config<
bridge_common_config::BridgeGrandpaRococoBulletinInstance
>>::FreeHeadersInterval::get()
}
fn synced_headers_grandpa_info(
) -> Vec<bp_header_chain::StoredHeaderGrandpaInfo<bp_polkadot_bulletin::Header>> {
BridgePolkadotBulletinGrandpa::synced_headers_grandpa_info()
@@ -17,8 +17,10 @@
//! Expose the auto generated weight files.
use ::pallet_bridge_grandpa::WeightInfoExt as GrandpaWeightInfoExt;
use ::pallet_bridge_messages::WeightInfoExt as MessagesWeightInfoExt;
use ::pallet_bridge_parachains::WeightInfoExt as ParachainsWeightInfoExt;
use ::pallet_bridge_relayers::WeightInfo as _;
pub mod block_weights;
pub mod cumulus_pallet_parachain_system;
@@ -56,6 +58,16 @@ use frame_support::weights::Weight;
// import trait from dependency module
use ::pallet_bridge_relayers::WeightInfoExt as _;
impl GrandpaWeightInfoExt for pallet_bridge_grandpa::WeightInfo<crate::Runtime> {
fn submit_finality_proof_overhead_from_runtime() -> Weight {
// our signed extension:
// 1) checks whether relayer registration is active from validate/pre_dispatch;
// 2) may slash and deregister relayer from post_dispatch
// (2) includes (1), so (2) is the worst case
pallet_bridge_relayers::WeightInfo::<Runtime>::slash_and_deregister()
}
}
impl MessagesWeightInfoExt
for pallet_bridge_messages_rococo_to_rococo_bulletin::WeightInfo<crate::Runtime>
{
@@ -94,4 +106,12 @@ impl ParachainsWeightInfoExt for pallet_bridge_parachains::WeightInfo<crate::Run
fn expected_extra_storage_proof_size() -> u32 {
bp_bridge_hub_westend::EXTRA_STORAGE_PROOF_SIZE
}
fn submit_parachain_heads_overhead_from_runtime() -> Weight {
// our signed extension:
// 1) checks whether relayer registration is active from validate/pre_dispatch;
// 2) may slash and deregister relayer from post_dispatch
// (2) includes (1), so (2) is the worst case
pallet_bridge_relayers::WeightInfo::<Runtime>::slash_and_deregister()
}
}
@@ -80,11 +80,10 @@ fn construct_and_apply_extrinsic(
r.unwrap()
}
fn construct_and_estimate_extrinsic_fee(batch: pallet_utility::Call<Runtime>) -> Balance {
let batch_call = RuntimeCall::Utility(batch);
let batch_info = batch_call.get_dispatch_info();
let xt = construct_extrinsic(Alice, batch_call);
TransactionPayment::compute_fee(xt.encoded_size() as _, &batch_info, 0)
fn construct_and_estimate_extrinsic_fee(call: RuntimeCall) -> Balance {
let info = call.get_dispatch_info();
let xt = construct_extrinsic(Alice, call);
TransactionPayment::compute_fee(xt.encoded_size() as _, &info, 0)
}
fn collator_session_keys() -> bridge_hub_test_utils::CollatorSessionKeys<Runtime> {
@@ -376,20 +375,20 @@ mod bridge_hub_westend_tests {
}
#[test]
pub fn complex_relay_extrinsic_works() {
// for Westend
from_parachain::complex_relay_extrinsic_works::<RuntimeTestsAdapter>(
fn free_relay_extrinsic_works() {
// from Westend
from_parachain::free_relay_extrinsic_works::<RuntimeTestsAdapter>(
collator_session_keys(),
slot_durations(),
bp_bridge_hub_rococo::BRIDGE_HUB_ROCOCO_PARACHAIN_ID,
bp_bridge_hub_westend::BRIDGE_HUB_WESTEND_PARACHAIN_ID,
SIBLING_PARACHAIN_ID,
BridgeHubWestendChainId::get(),
SIBLING_PARACHAIN_ID,
Rococo,
XCM_LANE_FOR_ASSET_HUB_ROCOCO_TO_ASSET_HUB_WESTEND,
|| (),
construct_and_apply_extrinsic,
);
)
}
#[test]
@@ -414,12 +413,12 @@ mod bridge_hub_westend_tests {
}
#[test]
pub fn can_calculate_fee_for_complex_message_delivery_transaction() {
fn can_calculate_fee_for_standalone_message_delivery_transaction() {
bridge_hub_test_utils::check_sane_fees_values(
"bp_bridge_hub_rococo::BridgeHubRococoBaseDeliveryFeeInRocs",
bp_bridge_hub_rococo::BridgeHubRococoBaseDeliveryFeeInRocs::get(),
|| {
from_parachain::can_calculate_fee_for_complex_message_delivery_transaction::<
from_parachain::can_calculate_fee_for_standalone_message_delivery_transaction::<
RuntimeTestsAdapter,
>(collator_session_keys(), construct_and_estimate_extrinsic_fee)
},
@@ -433,12 +432,12 @@ mod bridge_hub_westend_tests {
}
#[test]
pub fn can_calculate_fee_for_complex_message_confirmation_transaction() {
fn can_calculate_fee_for_standalone_message_confirmation_transaction() {
bridge_hub_test_utils::check_sane_fees_values(
"bp_bridge_hub_rococo::BridgeHubRococoBaseConfirmationFeeInRocs",
bp_bridge_hub_rococo::BridgeHubRococoBaseConfirmationFeeInRocs::get(),
|| {
from_parachain::can_calculate_fee_for_complex_message_confirmation_transaction::<
from_parachain::can_calculate_fee_for_standalone_message_confirmation_transaction::<
RuntimeTestsAdapter,
>(collator_session_keys(), construct_and_estimate_extrinsic_fee)
},
@@ -581,28 +580,28 @@ mod bridge_hub_bulletin_tests {
}
#[test]
pub fn complex_relay_extrinsic_works() {
// for Bulletin
from_grandpa_chain::complex_relay_extrinsic_works::<RuntimeTestsAdapter>(
fn free_relay_extrinsic_works() {
// from Bulletin
from_grandpa_chain::free_relay_extrinsic_works::<RuntimeTestsAdapter>(
collator_session_keys(),
slot_durations(),
bp_bridge_hub_rococo::BRIDGE_HUB_ROCOCO_PARACHAIN_ID,
SIBLING_PARACHAIN_ID,
RococoBulletinChainId::get(),
SIBLING_PARACHAIN_ID,
Rococo,
XCM_LANE_FOR_ROCOCO_PEOPLE_TO_ROCOCO_BULLETIN,
|| (),
construct_and_apply_extrinsic,
);
)
}
#[test]
pub fn can_calculate_fee_for_complex_message_delivery_transaction() {
pub fn can_calculate_fee_for_standalone_message_delivery_transaction() {
bridge_hub_test_utils::check_sane_fees_values(
"bp_bridge_hub_rococo::BridgeHubRococoBaseDeliveryFeeInRocs",
bp_bridge_hub_rococo::BridgeHubRococoBaseDeliveryFeeInRocs::get(),
|| {
from_grandpa_chain::can_calculate_fee_for_complex_message_delivery_transaction::<
from_grandpa_chain::can_calculate_fee_for_standalone_message_delivery_transaction::<
RuntimeTestsAdapter,
>(collator_session_keys(), construct_and_estimate_extrinsic_fee)
},
@@ -617,12 +616,12 @@ mod bridge_hub_bulletin_tests {
}
#[test]
pub fn can_calculate_fee_for_complex_message_confirmation_transaction() {
pub fn can_calculate_fee_for_standalone_message_confirmation_transaction() {
bridge_hub_test_utils::check_sane_fees_values(
"bp_bridge_hub_rococo::BridgeHubRococoBaseConfirmationFeeInRocs",
bp_bridge_hub_rococo::BridgeHubRococoBaseConfirmationFeeInRocs::get(),
|| {
from_grandpa_chain::can_calculate_fee_for_complex_message_confirmation_transaction::<
from_grandpa_chain::can_calculate_fee_for_standalone_message_confirmation_transaction::<
RuntimeTestsAdapter,
>(collator_session_keys(), construct_and_estimate_extrinsic_fee)
},
@@ -18,6 +18,7 @@ hex-literal = { version = "0.4.1" }
log = { workspace = true }
scale-info = { version = "2.11.1", default-features = false, features = ["derive"] }
serde = { optional = true, features = ["derive"], workspace = true, default-features = true }
tuplex = { version = "0.1", default-features = false }
# Substrate
frame-benchmarking = { path = "../../../../../substrate/frame/benchmarking", default-features = false, optional = true }
@@ -180,6 +181,7 @@ std = [
"sp-version/std",
"substrate-wasm-builder",
"testnet-parachains-constants/std",
"tuplex/std",
"westend-runtime-constants/std",
"xcm-builder/std",
"xcm-executor/std",
@@ -26,8 +26,8 @@ use bp_parachains::SingleParaStoredHeaderDataBuilder;
use bp_runtime::Chain;
use bridge_runtime_common::{
extensions::refund_relayer_extension::{
ActualFeeRefund, RefundBridgedParachainMessages, RefundSignedExtensionAdapter,
RefundableMessagesLane, RefundableParachain,
ActualFeeRefund, RefundBridgedMessages, RefundSignedExtensionAdapter,
RefundableMessagesLane,
},
messages,
messages::{
@@ -70,6 +70,10 @@ parameter_types! {
2,
[GlobalConsensus(RococoGlobalConsensusNetwork::get())]
);
// see the `FEE_BOOST_PER_RELAY_HEADER` constant get the meaning of this value
pub PriorityBoostPerRelayHeader: u64 = 32_007_814_407_814;
// see the `FEE_BOOST_PER_PARACHAIN_HEADER` constant get the meaning of this value
pub PriorityBoostPerParachainHeader: u64 = 1_396_340_903_540_903;
// see the `FEE_BOOST_PER_MESSAGE` constant to get the meaning of this value
pub PriorityBoostPerMessage: u64 = 182_044_444_444_444;
@@ -191,9 +195,8 @@ impl ThisChainWithMessages for BridgeHubWestend {
/// Signed extension that refunds relayers that are delivering messages from the Rococo parachain.
pub type OnBridgeHubWestendRefundBridgeHubRococoMessages = RefundSignedExtensionAdapter<
RefundBridgedParachainMessages<
RefundBridgedMessages<
Runtime,
RefundableParachain<BridgeParachainRococoInstance, bp_bridge_hub_rococo::BridgeHubRococo>,
RefundableMessagesLane<
WithBridgeHubRococoMessagesInstance,
AssetHubWestendToAssetHubRococoMessagesLane,
@@ -210,7 +213,8 @@ pub type BridgeGrandpaRococoInstance = pallet_bridge_grandpa::Instance1;
impl pallet_bridge_grandpa::Config<BridgeGrandpaRococoInstance> for Runtime {
type RuntimeEvent = RuntimeEvent;
type BridgedChain = bp_rococo::Rococo;
type MaxFreeMandatoryHeadersPerBlock = ConstU32<4>;
type MaxFreeHeadersPerBlock = ConstU32<4>;
type FreeHeadersInterval = ConstU32<5>;
type HeadersToKeep = RelayChainHeadersToKeep;
type WeightInfo = weights::pallet_bridge_grandpa::WeightInfo<Runtime>;
}
@@ -281,6 +285,7 @@ mod tests {
use super::*;
use bridge_runtime_common::{
assert_complete_bridge_types,
extensions::refund_relayer_extension::RefundableParachain,
integrity::{
assert_complete_bridge_constants, check_message_lane_weights,
AssertBridgeMessagesPalletConstants, AssertBridgePalletNames, AssertChainConstants,
@@ -301,6 +306,11 @@ mod tests {
/// operational costs and a faster bridge), so this value should be significant.
const FEE_BOOST_PER_MESSAGE: Balance = 2 * westend::currency::UNITS;
// see `FEE_BOOST_PER_MESSAGE` comment
const FEE_BOOST_PER_RELAY_HEADER: Balance = 2 * westend::currency::UNITS;
// see `FEE_BOOST_PER_MESSAGE` comment
const FEE_BOOST_PER_PARACHAIN_HEADER: Balance = 2 * westend::currency::UNITS;
#[test]
fn ensure_bridge_hub_westend_message_lane_weights_are_correct() {
check_message_lane_weights::<
@@ -352,7 +362,19 @@ mod tests {
},
});
bridge_runtime_common::extensions::priority_calculator::ensure_priority_boost_is_sane::<
bridge_runtime_common::extensions::priority_calculator::per_relay_header::ensure_priority_boost_is_sane::<
Runtime,
BridgeGrandpaRococoInstance,
PriorityBoostPerRelayHeader,
>(FEE_BOOST_PER_RELAY_HEADER);
bridge_runtime_common::extensions::priority_calculator::per_parachain_header::ensure_priority_boost_is_sane::<
Runtime,
RefundableParachain<WithBridgeHubRococoMessagesInstance, BridgeHubRococo>,
PriorityBoostPerParachainHeader,
>(FEE_BOOST_PER_PARACHAIN_HEADER);
bridge_runtime_common::extensions::priority_calculator::per_message::ensure_priority_boost_is_sane::<
Runtime,
WithBridgeHubRococoMessagesInstance,
PriorityBoostPerMessage,
@@ -32,6 +32,12 @@ pub mod bridge_to_rococo_config;
mod weights;
pub mod xcm_config;
use bridge_runtime_common::extensions::{
check_obsolete_extension::{
CheckAndBoostBridgeGrandpaTransactions, CheckAndBoostBridgeParachainsTransactions,
},
refund_relayer_extension::RefundableParachain,
};
use cumulus_pallet_parachain_system::RelayNumberMonotonicallyIncreases;
use cumulus_primitives_core::ParaId;
use sp_api::impl_runtime_apis;
@@ -57,7 +63,7 @@ use frame_support::{
dispatch::DispatchClass,
genesis_builder_helper::{build_state, get_preset},
parameter_types,
traits::{ConstBool, ConstU32, ConstU64, ConstU8, TransformOrigin},
traits::{ConstBool, ConstU32, ConstU64, ConstU8, Get, TransformOrigin},
weights::{ConstantMultiplier, Weight},
PalletId,
};
@@ -502,9 +508,22 @@ construct_runtime!(
bridge_runtime_common::generate_bridge_reject_obsolete_headers_and_messages! {
RuntimeCall, AccountId,
// Grandpa
BridgeRococoGrandpa,
CheckAndBoostBridgeGrandpaTransactions<
Runtime,
bridge_to_rococo_config::BridgeGrandpaRococoInstance,
bridge_to_rococo_config::PriorityBoostPerRelayHeader,
xcm_config::TreasuryAccount,
>,
// Parachains
BridgeRococoParachains,
CheckAndBoostBridgeParachainsTransactions<
Runtime,
RefundableParachain<
bridge_to_rococo_config::BridgeParachainRococoInstance,
bp_bridge_hub_rococo::BridgeHubRococo,
>,
bridge_to_rococo_config::PriorityBoostPerParachainHeader,
xcm_config::TreasuryAccount,
>,
// Messages
BridgeRococoMessages
}
@@ -692,6 +711,11 @@ impl_runtime_apis! {
fn best_finalized() -> Option<HeaderId<bp_rococo::Hash, bp_rococo::BlockNumber>> {
BridgeRococoGrandpa::best_finalized()
}
fn free_headers_interval() -> Option<bp_rococo::BlockNumber> {
<Runtime as pallet_bridge_grandpa::Config<
bridge_to_rococo_config::BridgeGrandpaRococoInstance
>>::FreeHeadersInterval::get()
}
fn synced_headers_grandpa_info(
) -> Vec<bp_header_chain::StoredHeaderGrandpaInfo<bp_rococo::Header>> {
BridgeRococoGrandpa::synced_headers_grandpa_info()
@@ -704,6 +728,10 @@ impl_runtime_apis! {
bp_bridge_hub_rococo::BridgeHubRococo
>().unwrap_or(None)
}
fn free_headers_interval() -> Option<bp_bridge_hub_rococo::BlockNumber> {
// "free interval" is not currently used for parachains
None
}
}
impl bp_bridge_hub_rococo::FromBridgeHubRococoInboundLaneApi<Block> for Runtime {
@@ -17,8 +17,10 @@
//! Expose the auto generated weight files.
use ::pallet_bridge_grandpa::WeightInfoExt as GrandpaWeightInfoExt;
use ::pallet_bridge_messages::WeightInfoExt as MessagesWeightInfoExt;
use ::pallet_bridge_parachains::WeightInfoExt as ParachainsWeightInfoExt;
use ::pallet_bridge_relayers::WeightInfo as _;
pub mod block_weights;
pub mod cumulus_pallet_parachain_system;
@@ -51,6 +53,16 @@ use frame_support::weights::Weight;
// import trait from dependency module
use ::pallet_bridge_relayers::WeightInfoExt as _;
impl GrandpaWeightInfoExt for pallet_bridge_grandpa::WeightInfo<crate::Runtime> {
fn submit_finality_proof_overhead_from_runtime() -> Weight {
// our signed extension:
// 1) checks whether relayer registration is active from validate/pre_dispatch;
// 2) may slash and deregister relayer from post_dispatch
// (2) includes (1), so (2) is the worst case
pallet_bridge_relayers::WeightInfo::<Runtime>::slash_and_deregister()
}
}
impl MessagesWeightInfoExt for pallet_bridge_messages::WeightInfo<crate::Runtime> {
fn expected_extra_storage_proof_size() -> u32 {
bp_bridge_hub_rococo::EXTRA_STORAGE_PROOF_SIZE
@@ -70,4 +82,12 @@ impl ParachainsWeightInfoExt for pallet_bridge_parachains::WeightInfo<crate::Run
fn expected_extra_storage_proof_size() -> u32 {
bp_bridge_hub_rococo::EXTRA_STORAGE_PROOF_SIZE
}
fn submit_parachain_heads_overhead_from_runtime() -> Weight {
// our signed extension:
// 1) checks whether relayer registration is active from validate/pre_dispatch;
// 2) may slash and deregister relayer from post_dispatch
// (2) includes (1), so (2) is the worst case
pallet_bridge_relayers::WeightInfo::<Runtime>::slash_and_deregister()
}
}
@@ -94,11 +94,10 @@ fn construct_and_apply_extrinsic(
r.unwrap()
}
fn construct_and_estimate_extrinsic_fee(batch: pallet_utility::Call<Runtime>) -> Balance {
let batch_call = RuntimeCall::Utility(batch);
let batch_info = batch_call.get_dispatch_info();
let xt = construct_extrinsic(Alice, batch_call);
TransactionPayment::compute_fee(xt.encoded_size() as _, &batch_info, 0)
fn construct_and_estimate_extrinsic_fee(call: RuntimeCall) -> Balance {
let info = call.get_dispatch_info();
let xt = construct_extrinsic(Alice, call);
TransactionPayment::compute_fee(xt.encoded_size() as _, &info, 0)
}
fn collator_session_keys() -> bridge_hub_test_utils::CollatorSessionKeys<Runtime> {
@@ -271,22 +270,6 @@ fn relayed_incoming_message_works() {
)
}
#[test]
pub fn complex_relay_extrinsic_works() {
from_parachain::complex_relay_extrinsic_works::<RuntimeTestsAdapter>(
collator_session_keys(),
slot_durations(),
bp_bridge_hub_westend::BRIDGE_HUB_WESTEND_PARACHAIN_ID,
bp_bridge_hub_rococo::BRIDGE_HUB_ROCOCO_PARACHAIN_ID,
SIBLING_PARACHAIN_ID,
BridgeHubRococoChainId::get(),
Westend,
XCM_LANE_FOR_ASSET_HUB_WESTEND_TO_ASSET_HUB_ROCOCO,
|| (),
construct_and_apply_extrinsic,
);
}
#[test]
pub fn can_calculate_weight_for_paid_export_message_with_reserve_transfer() {
bridge_hub_test_utils::check_sane_fees_values(
@@ -309,12 +292,12 @@ pub fn can_calculate_weight_for_paid_export_message_with_reserve_transfer() {
}
#[test]
pub fn can_calculate_fee_for_complex_message_delivery_transaction() {
pub fn can_calculate_fee_for_standalone_message_delivery_transaction() {
bridge_hub_test_utils::check_sane_fees_values(
"bp_bridge_hub_westend::BridgeHubWestendBaseDeliveryFeeInWnds",
bp_bridge_hub_westend::BridgeHubWestendBaseDeliveryFeeInWnds::get(),
|| {
from_parachain::can_calculate_fee_for_complex_message_delivery_transaction::<
from_parachain::can_calculate_fee_for_standalone_message_delivery_transaction::<
RuntimeTestsAdapter,
>(collator_session_keys(), construct_and_estimate_extrinsic_fee)
},
@@ -328,12 +311,12 @@ pub fn can_calculate_fee_for_complex_message_delivery_transaction() {
}
#[test]
pub fn can_calculate_fee_for_complex_message_confirmation_transaction() {
pub fn can_calculate_fee_for_standalone_message_confirmation_transaction() {
bridge_hub_test_utils::check_sane_fees_values(
"bp_bridge_hub_westend::BridgeHubWestendBaseConfirmationFeeInWnds",
bp_bridge_hub_westend::BridgeHubWestendBaseConfirmationFeeInWnds::get(),
|| {
from_parachain::can_calculate_fee_for_complex_message_confirmation_transaction::<
from_parachain::can_calculate_fee_for_standalone_message_confirmation_transaction::<
RuntimeTestsAdapter,
>(collator_session_keys(), construct_and_estimate_extrinsic_fee)
},
@@ -41,6 +41,7 @@ use frame_system::pallet_prelude::BlockNumberFor;
use parachains_runtimes_test_utils::{
AccountIdOf, BasicParachainRuntime, CollatorSessionKeys, RuntimeCallOf, SlotDurations,
};
use sp_core::Get;
use sp_keyring::AccountKeyring::*;
use sp_runtime::{traits::Header as HeaderT, AccountId32};
use xcm::latest::prelude::*;
@@ -162,7 +163,14 @@ pub fn relayed_incoming_message_works<RuntimeHelper>(
test_data::from_grandpa_chain::make_complex_relayer_delivery_proofs::<
RuntimeHelper::MB,
(),
>(lane_id, xcm.into(), message_nonce, message_destination, relay_header_number);
>(
lane_id,
xcm.into(),
message_nonce,
message_destination,
relay_header_number,
false,
);
let relay_chain_header_hash = relay_chain_header.hash();
vec![
@@ -202,6 +210,142 @@ pub fn relayed_incoming_message_works<RuntimeHelper>(
);
}
/// Test-case makes sure that Runtime can dispatch XCM messages submitted by relayer,
/// with proofs (finality, message) independently submitted.
/// Finality proof is submitted for free in this test.
/// Also verifies relayer transaction signed extensions work as intended.
pub fn free_relay_extrinsic_works<RuntimeHelper>(
collator_session_key: CollatorSessionKeys<RuntimeHelper::Runtime>,
slot_durations: SlotDurations,
runtime_para_id: u32,
bridged_chain_id: bp_runtime::ChainId,
sibling_parachain_id: u32,
local_relay_chain_id: NetworkId,
lane_id: LaneId,
prepare_configuration: impl Fn(),
construct_and_apply_extrinsic: fn(
sp_keyring::AccountKeyring,
RuntimeCallOf<RuntimeHelper::Runtime>,
) -> sp_runtime::DispatchOutcome,
) where
RuntimeHelper: WithRemoteGrandpaChainHelper,
RuntimeHelper::Runtime: pallet_balances::Config,
AccountIdOf<RuntimeHelper::Runtime>: From<AccountId32>,
RuntimeCallOf<RuntimeHelper::Runtime>: From<BridgeGrandpaCall<RuntimeHelper::Runtime, RuntimeHelper::GPI>>
+ From<BridgeMessagesCall<RuntimeHelper::Runtime, RuntimeHelper::MPI>>,
UnderlyingChainOf<MessageBridgedChain<RuntimeHelper::MB>>: ChainWithGrandpa,
<RuntimeHelper::Runtime as BridgeMessagesConfig<RuntimeHelper::MPI>>::SourceHeaderChain:
SourceHeaderChain<
MessagesProof = FromBridgedChainMessagesProof<
HashOf<MessageBridgedChain<RuntimeHelper::MB>>,
>,
>,
{
// ensure that the runtime allows free header submissions
let free_headers_interval = <RuntimeHelper::Runtime as BridgeGrandpaConfig<
RuntimeHelper::GPI,
>>::FreeHeadersInterval::get()
.expect("this test requires runtime, configured to accept headers for free; qed");
helpers::relayed_incoming_message_works::<
RuntimeHelper::Runtime,
RuntimeHelper::AllPalletsWithoutSystem,
RuntimeHelper::MPI,
>(
collator_session_key,
slot_durations,
runtime_para_id,
sibling_parachain_id,
local_relay_chain_id,
construct_and_apply_extrinsic,
|relayer_id_at_this_chain,
relayer_id_at_bridged_chain,
message_destination,
message_nonce,
xcm| {
prepare_configuration();
// start with bridged relay chain block#0
let initial_block_number = 0;
helpers::initialize_bridge_grandpa_pallet::<RuntimeHelper::Runtime, RuntimeHelper::GPI>(
test_data::initialization_data::<RuntimeHelper::Runtime, RuntimeHelper::GPI>(
initial_block_number,
),
);
// free relay chain header is `0 + free_headers_interval`
let relay_header_number = initial_block_number + free_headers_interval;
// relayer balance shall not change after relay and para header submissions
let initial_relayer_balance =
pallet_balances::Pallet::<RuntimeHelper::Runtime>::free_balance(
relayer_id_at_this_chain.clone(),
);
// initialize the `FreeHeadersRemaining` storage value
pallet_bridge_grandpa::Pallet::<RuntimeHelper::Runtime, RuntimeHelper::GPI>::on_initialize(
0u32.into(),
);
// generate bridged relay chain finality, parachain heads and message proofs,
// to be submitted by relayer to this chain.
let (relay_chain_header, grandpa_justification, message_proof) =
test_data::from_grandpa_chain::make_complex_relayer_delivery_proofs::<
RuntimeHelper::MB,
(),
>(
lane_id,
xcm.into(),
message_nonce,
message_destination,
relay_header_number.into(),
true,
);
let relay_chain_header_hash = relay_chain_header.hash();
vec![
(
BridgeGrandpaCall::<RuntimeHelper::Runtime, RuntimeHelper::GPI>::submit_finality_proof {
finality_target: Box::new(relay_chain_header),
justification: grandpa_justification,
}.into(),
Box::new((
helpers::VerifySubmitGrandpaFinalityProofOutcome::<RuntimeHelper::Runtime, RuntimeHelper::GPI>::expect_best_header_hash(
relay_chain_header_hash,
),
helpers::VerifyRelayerBalance::<RuntimeHelper::Runtime>::expect_relayer_balance(
relayer_id_at_this_chain.clone(),
initial_relayer_balance,
),
))
),
(
BridgeMessagesCall::<RuntimeHelper::Runtime, RuntimeHelper::MPI>::receive_messages_proof {
relayer_id_at_bridged_chain,
proof: message_proof,
messages_count: 1,
dispatch_weight: Weight::from_parts(1000000000, 0),
}.into(),
Box::new((
helpers::VerifySubmitMessagesProofOutcome::<RuntimeHelper::Runtime, RuntimeHelper::MPI>::expect_last_delivered_nonce(
lane_id,
1,
),
helpers::VerifyRelayerRewarded::<RuntimeHelper::Runtime>::expect_relayer_reward(
relayer_id_at_this_chain,
RewardsAccountParams::new(
lane_id,
bridged_chain_id,
RewardsAccountOwner::ThisChain,
),
),
)),
),
]
},
);
}
/// Test-case makes sure that Runtime can dispatch XCM messages submitted by relayer,
/// with proofs (finality, message) batched together in signed extrinsic.
/// Also verifies relayer transaction signed extensions work as intended.
@@ -265,7 +409,14 @@ pub fn complex_relay_extrinsic_works<RuntimeHelper>(
test_data::from_grandpa_chain::make_complex_relayer_delivery_proofs::<
RuntimeHelper::MB,
(),
>(lane_id, xcm.into(), message_nonce, message_destination, relay_header_number);
>(
lane_id,
xcm.into(),
message_nonce,
message_destination,
relay_header_number,
false,
);
let relay_chain_header_hash = relay_chain_header.hash();
vec![(
@@ -344,6 +495,7 @@ where
1,
[GlobalConsensus(Polkadot), Parachain(1_000)].into(),
1u32.into(),
false,
);
// generate batch call that provides finality for bridged relay and parachains + message
@@ -423,3 +575,109 @@ where
compute_extrinsic_fee(batch)
})
}
/// Estimates transaction fee for default message delivery transaction from bridged GRANDPA chain.
pub fn can_calculate_fee_for_standalone_message_delivery_transaction<RuntimeHelper>(
collator_session_key: CollatorSessionKeys<RuntimeHelper::Runtime>,
compute_extrinsic_fee: fn(
<RuntimeHelper::Runtime as frame_system::Config>::RuntimeCall,
) -> u128,
) -> u128
where
RuntimeHelper: WithRemoteGrandpaChainHelper,
RuntimeCallOf<RuntimeHelper::Runtime>:
From<BridgeMessagesCall<RuntimeHelper::Runtime, RuntimeHelper::MPI>>,
UnderlyingChainOf<MessageBridgedChain<RuntimeHelper::MB>>: ChainWithGrandpa,
<RuntimeHelper::Runtime as BridgeMessagesConfig<RuntimeHelper::MPI>>::SourceHeaderChain:
SourceHeaderChain<
MessagesProof = FromBridgedChainMessagesProof<
HashOf<MessageBridgedChain<RuntimeHelper::MB>>,
>,
>,
{
run_test::<RuntimeHelper::Runtime, _>(collator_session_key, 1000, vec![], || {
// generate bridged relay chain finality, parachain heads and message proofs,
// to be submitted by relayer to this chain.
//
// we don't care about parameter values here, apart from the XCM message size. But we
// do not need to have a large message here, because we're charging for every byte of
// the message additionally
let (_, _, message_proof) =
test_data::from_grandpa_chain::make_complex_relayer_delivery_proofs::<
RuntimeHelper::MB,
(),
>(
LaneId::default(),
vec![Instruction::<()>::ClearOrigin; 1_024].into(),
1,
[GlobalConsensus(Polkadot), Parachain(1_000)].into(),
1u32.into(),
false,
);
let call = test_data::from_grandpa_chain::make_standalone_relayer_delivery_call::<
RuntimeHelper::Runtime,
RuntimeHelper::GPI,
RuntimeHelper::MPI,
>(
message_proof,
helpers::relayer_id_at_bridged_chain::<RuntimeHelper::Runtime, RuntimeHelper::MPI>(),
);
compute_extrinsic_fee(call)
})
}
/// Estimates transaction fee for default message confirmation transaction (batched with required
/// proofs) from bridged parachain.
pub fn can_calculate_fee_for_standalone_message_confirmation_transaction<RuntimeHelper>(
collator_session_key: CollatorSessionKeys<RuntimeHelper::Runtime>,
compute_extrinsic_fee: fn(
<RuntimeHelper::Runtime as frame_system::Config>::RuntimeCall,
) -> u128,
) -> u128
where
RuntimeHelper: WithRemoteGrandpaChainHelper,
AccountIdOf<RuntimeHelper::Runtime>: From<AccountId32>,
MessageThisChain<RuntimeHelper::MB>:
bp_runtime::Chain<AccountId = AccountIdOf<RuntimeHelper::Runtime>>,
RuntimeCallOf<RuntimeHelper::Runtime>:
From<BridgeMessagesCall<RuntimeHelper::Runtime, RuntimeHelper::MPI>>,
UnderlyingChainOf<MessageBridgedChain<RuntimeHelper::MB>>: ChainWithGrandpa,
<RuntimeHelper::Runtime as BridgeMessagesConfig<RuntimeHelper::MPI>>::TargetHeaderChain:
TargetHeaderChain<
XcmAsPlainPayload,
AccountIdOf<RuntimeHelper::Runtime>,
MessagesDeliveryProof = FromBridgedChainMessagesDeliveryProof<
HashOf<UnderlyingChainOf<MessageBridgedChain<RuntimeHelper::MB>>>,
>,
>,
{
run_test::<RuntimeHelper::Runtime, _>(collator_session_key, 1000, vec![], || {
// generate bridged relay chain finality, parachain heads and message proofs,
// to be submitted by relayer to this chain.
let unrewarded_relayers = UnrewardedRelayersState {
unrewarded_relayer_entries: 1,
total_messages: 1,
..Default::default()
};
let (_, _, message_delivery_proof) =
test_data::from_grandpa_chain::make_complex_relayer_confirmation_proofs::<
RuntimeHelper::MB,
(),
>(
LaneId::default(),
1u32.into(),
AccountId32::from(Alice.public()).into(),
unrewarded_relayers.clone(),
);
let call = test_data::from_grandpa_chain::make_standalone_relayer_confirmation_call::<
RuntimeHelper::Runtime,
RuntimeHelper::GPI,
RuntimeHelper::MPI,
>(message_delivery_proof, unrewarded_relayers);
compute_extrinsic_fee(call)
})
}
@@ -42,6 +42,7 @@ use frame_system::pallet_prelude::BlockNumberFor;
use parachains_runtimes_test_utils::{
AccountIdOf, BasicParachainRuntime, CollatorSessionKeys, RuntimeCallOf, SlotDurations,
};
use sp_core::Get;
use sp_keyring::AccountKeyring::*;
use sp_runtime::{traits::Header as HeaderT, AccountId32};
use xcm::latest::prelude::*;
@@ -188,6 +189,7 @@ pub fn relayed_incoming_message_works<RuntimeHelper>(
para_header_number,
relay_header_number,
bridged_para_id,
false,
);
let parachain_head_hash = parachain_head.hash();
@@ -241,6 +243,177 @@ pub fn relayed_incoming_message_works<RuntimeHelper>(
);
}
/// Test-case makes sure that Runtime can dispatch XCM messages submitted by relayer,
/// with proofs (finality, para heads, message) independently submitted.
/// Finality and para heads are submitted for free in this test.
/// Also verifies relayer transaction signed extensions work as intended.
pub fn free_relay_extrinsic_works<RuntimeHelper>(
collator_session_key: CollatorSessionKeys<RuntimeHelper::Runtime>,
slot_durations: SlotDurations,
runtime_para_id: u32,
bridged_para_id: u32,
bridged_chain_id: bp_runtime::ChainId,
sibling_parachain_id: u32,
local_relay_chain_id: NetworkId,
lane_id: LaneId,
prepare_configuration: impl Fn(),
construct_and_apply_extrinsic: fn(
sp_keyring::AccountKeyring,
<RuntimeHelper::Runtime as frame_system::Config>::RuntimeCall,
) -> sp_runtime::DispatchOutcome,
) where
RuntimeHelper: WithRemoteParachainHelper,
RuntimeHelper::Runtime: pallet_balances::Config,
AccountIdOf<RuntimeHelper::Runtime>: From<AccountId32>,
RuntimeCallOf<RuntimeHelper::Runtime>: From<BridgeGrandpaCall<RuntimeHelper::Runtime, RuntimeHelper::GPI>>
+ From<BridgeParachainsCall<RuntimeHelper::Runtime, RuntimeHelper::PPI>>
+ From<BridgeMessagesCall<RuntimeHelper::Runtime, RuntimeHelper::MPI>>,
UnderlyingChainOf<MessageBridgedChain<RuntimeHelper::MB>>:
bp_runtime::Chain<Hash = ParaHash> + Parachain,
<RuntimeHelper::Runtime as BridgeGrandpaConfig<RuntimeHelper::GPI>>::BridgedChain:
bp_runtime::Chain<Hash = RelayBlockHash, BlockNumber = RelayBlockNumber> + ChainWithGrandpa,
<RuntimeHelper::Runtime as BridgeMessagesConfig<RuntimeHelper::MPI>>::SourceHeaderChain:
SourceHeaderChain<
MessagesProof = FromBridgedChainMessagesProof<
HashOf<MessageBridgedChain<RuntimeHelper::MB>>,
>,
>,
{
// ensure that the runtime allows free header submissions
let free_headers_interval = <RuntimeHelper::Runtime as BridgeGrandpaConfig<
RuntimeHelper::GPI,
>>::FreeHeadersInterval::get()
.expect("this test requires runtime, configured to accept headers for free; qed");
helpers::relayed_incoming_message_works::<
RuntimeHelper::Runtime,
RuntimeHelper::AllPalletsWithoutSystem,
RuntimeHelper::MPI,
>(
collator_session_key,
slot_durations,
runtime_para_id,
sibling_parachain_id,
local_relay_chain_id,
construct_and_apply_extrinsic,
|relayer_id_at_this_chain,
relayer_id_at_bridged_chain,
message_destination,
message_nonce,
xcm| {
prepare_configuration();
// start with bridged relay chain block#0
let initial_block_number = 0;
helpers::initialize_bridge_grandpa_pallet::<RuntimeHelper::Runtime, RuntimeHelper::GPI>(
test_data::initialization_data::<RuntimeHelper::Runtime, RuntimeHelper::GPI>(
initial_block_number,
),
);
// free relay chain header is `0 + free_headers_interval`
let relay_header_number = initial_block_number + free_headers_interval;
// first parachain header is always submitted for free
let para_header_number = 1;
// relayer balance shall not change after relay and para header submissions
let initial_relayer_balance =
pallet_balances::Pallet::<RuntimeHelper::Runtime>::free_balance(
relayer_id_at_this_chain.clone(),
);
// initialize the `FreeHeadersRemaining` storage value
pallet_bridge_grandpa::Pallet::<RuntimeHelper::Runtime, RuntimeHelper::GPI>::on_initialize(
0u32.into(),
);
// generate bridged relay chain finality, parachain heads and message proofs,
// to be submitted by relayer to this chain.
let (
relay_chain_header,
grandpa_justification,
parachain_head,
parachain_heads,
para_heads_proof,
message_proof,
) = test_data::from_parachain::make_complex_relayer_delivery_proofs::<
<RuntimeHelper::Runtime as BridgeGrandpaConfig<RuntimeHelper::GPI>>::BridgedChain,
RuntimeHelper::MB,
(),
>(
lane_id,
xcm.into(),
message_nonce,
message_destination,
para_header_number,
relay_header_number,
bridged_para_id,
true,
);
let parachain_head_hash = parachain_head.hash();
let relay_chain_header_hash = relay_chain_header.hash();
let relay_chain_header_number = *relay_chain_header.number();
vec![
(
BridgeGrandpaCall::<RuntimeHelper::Runtime, RuntimeHelper::GPI>::submit_finality_proof {
finality_target: Box::new(relay_chain_header),
justification: grandpa_justification,
}.into(),
Box::new((
helpers::VerifySubmitGrandpaFinalityProofOutcome::<RuntimeHelper::Runtime, RuntimeHelper::GPI>::expect_best_header_hash(
relay_chain_header_hash,
),
helpers::VerifyRelayerBalance::<RuntimeHelper::Runtime>::expect_relayer_balance(
relayer_id_at_this_chain.clone(),
initial_relayer_balance,
),
)),
),
(
BridgeParachainsCall::<RuntimeHelper::Runtime, RuntimeHelper::PPI>::submit_parachain_heads {
at_relay_block: (relay_chain_header_number, relay_chain_header_hash),
parachains: parachain_heads,
parachain_heads_proof: para_heads_proof,
}.into(),
Box::new((
helpers::VerifySubmitParachainHeaderProofOutcome::<RuntimeHelper::Runtime, RuntimeHelper::PPI>::expect_best_header_hash(
bridged_para_id,
parachain_head_hash,
),
/*helpers::VerifyRelayerBalance::<RuntimeHelper::Runtime>::expect_relayer_balance(
relayer_id_at_this_chain.clone(),
initial_relayer_balance,
),*/
)),
),
(
BridgeMessagesCall::<RuntimeHelper::Runtime, RuntimeHelper::MPI>::receive_messages_proof {
relayer_id_at_bridged_chain,
proof: message_proof,
messages_count: 1,
dispatch_weight: Weight::from_parts(1000000000, 0),
}.into(),
Box::new((
helpers::VerifySubmitMessagesProofOutcome::<RuntimeHelper::Runtime, RuntimeHelper::MPI>::expect_last_delivered_nonce(
lane_id,
1,
),
helpers::VerifyRelayerRewarded::<RuntimeHelper::Runtime>::expect_relayer_reward(
relayer_id_at_this_chain,
RewardsAccountParams::new(
lane_id,
bridged_chain_id,
RewardsAccountOwner::ThisChain,
),
),
)),
),
]
},
);
}
/// Test-case makes sure that Runtime can dispatch XCM messages submitted by relayer,
/// with proofs (finality, para heads, message) batched together in signed extrinsic.
/// Also verifies relayer transaction signed extensions work as intended.
@@ -325,6 +498,7 @@ pub fn complex_relay_extrinsic_works<RuntimeHelper>(
para_header_number,
relay_header_number,
bridged_para_id,
false,
);
let parachain_head_hash = parachain_head.hash();
@@ -428,6 +602,7 @@ where
1,
5,
1_000,
false,
);
// generate batch call that provides finality for bridged relay and parachains + message
@@ -527,3 +702,126 @@ where
compute_extrinsic_fee(batch)
})
}
/// Estimates transaction fee for default message delivery transaction from bridged parachain.
pub fn can_calculate_fee_for_standalone_message_delivery_transaction<RuntimeHelper>(
collator_session_key: CollatorSessionKeys<RuntimeHelper::Runtime>,
compute_extrinsic_fee: fn(
<RuntimeHelper::Runtime as frame_system::Config>::RuntimeCall,
) -> u128,
) -> u128
where
RuntimeHelper: WithRemoteParachainHelper,
RuntimeCallOf<RuntimeHelper::Runtime>:
From<BridgeMessagesCall<RuntimeHelper::Runtime, RuntimeHelper::MPI>>,
UnderlyingChainOf<MessageBridgedChain<RuntimeHelper::MB>>:
bp_runtime::Chain<Hash = ParaHash> + Parachain,
<RuntimeHelper::Runtime as BridgeGrandpaConfig<RuntimeHelper::GPI>>::BridgedChain:
bp_runtime::Chain<Hash = RelayBlockHash, BlockNumber = RelayBlockNumber> + ChainWithGrandpa,
<RuntimeHelper::Runtime as BridgeMessagesConfig<RuntimeHelper::MPI>>::SourceHeaderChain:
SourceHeaderChain<
MessagesProof = FromBridgedChainMessagesProof<
HashOf<MessageBridgedChain<RuntimeHelper::MB>>,
>,
>,
{
run_test::<RuntimeHelper::Runtime, _>(collator_session_key, 1000, vec![], || {
// generate bridged relay chain finality, parachain heads and message proofs,
// to be submitted by relayer to this chain.
//
// we don't care about parameter values here, apart from the XCM message size. But we
// do not need to have a large message here, because we're charging for every byte of
// the message additionally
let (
_,
_,
_,
_,
_,
message_proof,
) = test_data::from_parachain::make_complex_relayer_delivery_proofs::<
<RuntimeHelper::Runtime as pallet_bridge_grandpa::Config<RuntimeHelper::GPI>>::BridgedChain,
RuntimeHelper::MB,
(),
>(
LaneId::default(),
vec![Instruction::<()>::ClearOrigin; 1_024].into(),
1,
[GlobalConsensus(Polkadot), Parachain(1_000)].into(),
1,
5,
1_000,
false,
);
let call = test_data::from_parachain::make_standalone_relayer_delivery_call::<
RuntimeHelper::Runtime,
RuntimeHelper::MPI,
_,
>(
message_proof,
helpers::relayer_id_at_bridged_chain::<RuntimeHelper::Runtime, RuntimeHelper::MPI>(),
);
compute_extrinsic_fee(call)
})
}
/// Estimates transaction fee for default message confirmation transaction (batched with required
/// proofs) from bridged parachain.
pub fn can_calculate_fee_for_standalone_message_confirmation_transaction<RuntimeHelper>(
collator_session_key: CollatorSessionKeys<RuntimeHelper::Runtime>,
compute_extrinsic_fee: fn(
<RuntimeHelper::Runtime as frame_system::Config>::RuntimeCall,
) -> u128,
) -> u128
where
RuntimeHelper: WithRemoteParachainHelper,
AccountIdOf<RuntimeHelper::Runtime>: From<AccountId32>,
MessageThisChain<RuntimeHelper::MB>:
bp_runtime::Chain<AccountId = AccountIdOf<RuntimeHelper::Runtime>>,
RuntimeCallOf<RuntimeHelper::Runtime>:
From<BridgeMessagesCall<RuntimeHelper::Runtime, RuntimeHelper::MPI>>,
UnderlyingChainOf<MessageBridgedChain<RuntimeHelper::MB>>:
bp_runtime::Chain<Hash = ParaHash> + Parachain,
<RuntimeHelper::Runtime as BridgeGrandpaConfig<RuntimeHelper::GPI>>::BridgedChain:
bp_runtime::Chain<Hash = RelayBlockHash, BlockNumber = RelayBlockNumber> + ChainWithGrandpa,
<RuntimeHelper::Runtime as BridgeMessagesConfig<RuntimeHelper::MPI>>::TargetHeaderChain:
TargetHeaderChain<
XcmAsPlainPayload,
AccountIdOf<RuntimeHelper::Runtime>,
MessagesDeliveryProof = FromBridgedChainMessagesDeliveryProof<
HashOf<UnderlyingChainOf<MessageBridgedChain<RuntimeHelper::MB>>>,
>,
>,
{
run_test::<RuntimeHelper::Runtime, _>(collator_session_key, 1000, vec![], || {
// generate bridged relay chain finality, parachain heads and message proofs,
// to be submitted by relayer to this chain.
let unrewarded_relayers = UnrewardedRelayersState {
unrewarded_relayer_entries: 1,
total_messages: 1,
..Default::default()
};
let (_, _, _, _, _, message_delivery_proof) =
test_data::from_parachain::make_complex_relayer_confirmation_proofs::<
<RuntimeHelper::Runtime as BridgeGrandpaConfig<RuntimeHelper::GPI>>::BridgedChain,
RuntimeHelper::MB,
(),
>(
LaneId::default(),
1,
5,
1_000,
AccountId32::from(Alice.public()).into(),
unrewarded_relayers.clone(),
);
let call = test_data::from_parachain::make_standalone_relayer_confirmation_call::<
RuntimeHelper::Runtime,
RuntimeHelper::MPI,
>(message_delivery_proof, unrewarded_relayers);
compute_extrinsic_fee(call)
})
}
@@ -193,6 +193,34 @@ where
}
}
/// Verifies that relayer balance is equal to given value.
pub struct VerifyRelayerBalance<Runtime: pallet_balances::Config> {
relayer: Runtime::AccountId,
balance: Runtime::Balance,
}
impl<Runtime> VerifyRelayerBalance<Runtime>
where
Runtime: pallet_balances::Config,
{
/// Expect given relayer balance after transaction.
pub fn expect_relayer_balance(
relayer: Runtime::AccountId,
balance: Runtime::Balance,
) -> Box<dyn VerifyTransactionOutcome> {
Box::new(Self { relayer, balance })
}
}
impl<Runtime> VerifyTransactionOutcome for VerifyRelayerBalance<Runtime>
where
Runtime: pallet_balances::Config,
{
fn verify_outcome(&self) {
assert_eq!(pallet_balances::Pallet::<Runtime>::free_balance(&self.relayer), self.balance,);
}
}
/// Initialize bridge GRANDPA pallet.
pub(crate) fn initialize_bridge_grandpa_pallet<Runtime, GPI>(
init_data: bp_header_chain::InitializationData<BridgedHeader<Runtime, GPI>>,
@@ -121,6 +121,60 @@ where
}
}
/// Prepare a call with message proof.
pub fn make_standalone_relayer_delivery_call<Runtime, GPI, MPI>(
message_proof: FromBridgedChainMessagesProof<HashOf<BridgedChain<Runtime, GPI>>>,
relayer_id_at_bridged_chain: AccountIdOf<BridgedChain<Runtime, GPI>>,
) -> Runtime::RuntimeCall
where
Runtime: pallet_bridge_grandpa::Config<GPI>
+ pallet_bridge_messages::Config<
MPI,
InboundPayload = XcmAsPlainPayload,
InboundRelayer = AccountIdOf<BridgedChain<Runtime, GPI>>,
>,
MPI: 'static,
<Runtime as pallet_bridge_messages::Config<MPI>>::SourceHeaderChain: SourceHeaderChain<
MessagesProof = FromBridgedChainMessagesProof<HashOf<BridgedChain<Runtime, GPI>>>,
>,
Runtime::RuntimeCall: From<pallet_bridge_messages::Call<Runtime, MPI>>,
{
pallet_bridge_messages::Call::<Runtime, MPI>::receive_messages_proof {
relayer_id_at_bridged_chain,
proof: message_proof,
messages_count: 1,
dispatch_weight: Weight::from_parts(1000000000, 0),
}
.into()
}
/// Prepare a call with message delivery proof.
pub fn make_standalone_relayer_confirmation_call<Runtime, GPI, MPI>(
message_delivery_proof: FromBridgedChainMessagesDeliveryProof<
HashOf<BridgedChain<Runtime, GPI>>,
>,
relayers_state: UnrewardedRelayersState,
) -> Runtime::RuntimeCall
where
Runtime: pallet_bridge_grandpa::Config<GPI>
+ pallet_bridge_messages::Config<MPI, OutboundPayload = XcmAsPlainPayload>,
MPI: 'static,
<Runtime as pallet_bridge_messages::Config<MPI>>::TargetHeaderChain: TargetHeaderChain<
XcmAsPlainPayload,
Runtime::AccountId,
MessagesDeliveryProof = FromBridgedChainMessagesDeliveryProof<
HashOf<BridgedChain<Runtime, GPI>>,
>,
>,
Runtime::RuntimeCall: From<pallet_bridge_messages::Call<Runtime, MPI>>,
{
pallet_bridge_messages::Call::<Runtime, MPI>::receive_messages_delivery_proof {
proof: message_delivery_proof,
relayers_state,
}
.into()
}
/// Prepare storage proofs of messages, stored at the (bridged) source GRANDPA chain.
pub fn make_complex_relayer_delivery_proofs<MB, InnerXcmRuntimeCall>(
lane_id: LaneId,
@@ -128,6 +182,7 @@ pub fn make_complex_relayer_delivery_proofs<MB, InnerXcmRuntimeCall>(
message_nonce: MessageNonce,
message_destination: Junctions,
header_number: BlockNumberOf<MessageBridgedChain<MB>>,
is_minimal_call: bool,
) -> (
HeaderOf<MessageBridgedChain<MB>>,
GrandpaJustification<HeaderOf<MessageBridgedChain<MB>>>,
@@ -153,7 +208,7 @@ where
let (header, justification) = make_complex_bridged_grandpa_header_proof::<
MessageBridgedChain<MB>,
>(state_root, header_number);
>(state_root, header_number, is_minimal_call);
let message_proof = FromBridgedChainMessagesProof {
bridged_header_hash: header.hash(),
@@ -200,8 +255,11 @@ where
StorageProofSize::Minimal(0),
);
let (header, justification) =
make_complex_bridged_grandpa_header_proof::<MB::BridgedChain>(state_root, header_number);
let (header, justification) = make_complex_bridged_grandpa_header_proof::<MB::BridgedChain>(
state_root,
header_number,
false,
);
let message_delivery_proof = FromBridgedChainMessagesDeliveryProof {
bridged_header_hash: header.hash(),
@@ -216,6 +274,7 @@ where
pub fn make_complex_bridged_grandpa_header_proof<BridgedChain>(
state_root: HashOf<BridgedChain>,
header_number: BlockNumberOf<BridgedChain>,
is_minimal_call: bool,
) -> (HeaderOf<BridgedChain>, GrandpaJustification<HeaderOf<BridgedChain>>)
where
BridgedChain: ChainWithGrandpa,
@@ -229,7 +288,9 @@ where
// `submit_finality_proof` call size would be close to maximal expected (and refundable)
let extra_bytes_required = maximal_expected_submit_finality_proof_call_size::<BridgedChain>()
.saturating_sub(header.encoded_size());
header.digest_mut().push(DigestItem::Other(vec![42; extra_bytes_required]));
if !is_minimal_call {
header.digest_mut().push(DigestItem::Other(vec![42; extra_bytes_required]));
}
let justification = make_default_justification(&header);
(header, justification)
@@ -159,6 +159,52 @@ where
}
}
/// Prepare a call with message proof.
pub fn make_standalone_relayer_delivery_call<Runtime, MPI, InboundRelayer>(
message_proof: FromBridgedChainMessagesProof<ParaHash>,
relayer_id_at_bridged_chain: InboundRelayer,
) -> Runtime::RuntimeCall where
Runtime: pallet_bridge_messages::Config<
MPI,
InboundPayload = XcmAsPlainPayload,
InboundRelayer = InboundRelayer,
>,
MPI: 'static,
Runtime::RuntimeCall: From<pallet_bridge_messages::Call::<Runtime, MPI>>,
<<Runtime as pallet_bridge_messages::Config<MPI>>::SourceHeaderChain as SourceHeaderChain>::MessagesProof:
From<FromBridgedChainMessagesProof<ParaHash>>,
{
pallet_bridge_messages::Call::<Runtime, MPI>::receive_messages_proof {
relayer_id_at_bridged_chain: relayer_id_at_bridged_chain.into(),
proof: message_proof.into(),
messages_count: 1,
dispatch_weight: Weight::from_parts(1000000000, 0),
}
.into()
}
/// Prepare a call with message delivery proof.
pub fn make_standalone_relayer_confirmation_call<Runtime, MPI>(
message_delivery_proof: FromBridgedChainMessagesDeliveryProof<ParaHash>,
relayers_state: UnrewardedRelayersState,
) -> Runtime::RuntimeCall
where
Runtime: pallet_bridge_messages::Config<MPI, OutboundPayload = XcmAsPlainPayload>,
MPI: 'static,
Runtime::RuntimeCall: From<pallet_bridge_messages::Call<Runtime, MPI>>,
<Runtime as pallet_bridge_messages::Config<MPI>>::TargetHeaderChain: TargetHeaderChain<
XcmAsPlainPayload,
Runtime::AccountId,
MessagesDeliveryProof = FromBridgedChainMessagesDeliveryProof<ParaHash>,
>,
{
pallet_bridge_messages::Call::<Runtime, MPI>::receive_messages_delivery_proof {
proof: message_delivery_proof,
relayers_state,
}
.into()
}
/// Prepare storage proofs of messages, stored at the source chain.
pub fn make_complex_relayer_delivery_proofs<BridgedRelayChain, MB, InnerXcmRuntimeCall>(
lane_id: LaneId,
@@ -168,6 +214,7 @@ pub fn make_complex_relayer_delivery_proofs<BridgedRelayChain, MB, InnerXcmRunti
para_header_number: u32,
relay_header_number: u32,
bridged_para_id: u32,
is_minimal_call: bool,
) -> (
HeaderOf<BridgedRelayChain>,
GrandpaJustification<HeaderOf<BridgedRelayChain>>,
@@ -201,6 +248,7 @@ where
para_header_number,
relay_header_number,
bridged_para_id,
is_minimal_call,
);
let message_proof = FromBridgedChainMessagesProof {
@@ -266,6 +314,7 @@ where
para_header_number,
relay_header_number,
bridged_para_id,
false,
);
let message_delivery_proof = FromBridgedChainMessagesDeliveryProof {
@@ -290,6 +339,7 @@ pub fn make_complex_bridged_parachain_heads_proof<BridgedRelayChain, MB>(
para_header_number: u32,
relay_header_number: BlockNumberOf<BridgedRelayChain>,
bridged_para_id: u32,
is_minimal_call: bool,
) -> (
HeaderOf<BridgedRelayChain>,
GrandpaJustification<HeaderOf<BridgedRelayChain>>,
@@ -319,9 +369,12 @@ where
)]);
assert_eq!(bridged_para_head.hash(), parachain_heads[0].1);
let (relay_chain_header, justification) = make_complex_bridged_grandpa_header_proof::<
BridgedRelayChain,
>(relay_state_root, relay_header_number);
let (relay_chain_header, justification) =
make_complex_bridged_grandpa_header_proof::<BridgedRelayChain>(
relay_state_root,
relay_header_number,
is_minimal_call,
);
(relay_chain_header, justification, bridged_para_head, parachain_heads, para_heads_proof)
}
+43
View File
@@ -0,0 +1,43 @@
title: "Bridge: make some headers submissions free"
doc:
- audience: Runtime Dev
description: |
Adds `FreeHeadersInterval` configuration constant to the `pallet_bridge_grandpa`.
Transactions that improve best known header by at least `FreeHeadersInterval` headers
are now free for the submitter. Additionally, we allow single free parachain header
update per every free relay chain header. Bridge signed extensions are adjusted
to support that new scheme. Bridge runtime APIs are extended to support that new
scheme. Bridge fees are decreased by ~98% because now they do not include cost of
finality submissions - we assume relayers will be submitting finality transactions
for free.
crates:
- name: bridge-runtime-common
bump: major
- name: bp-bridge-hub-cumulus
bump: patch
- name: bp-bridge-hub-kusama
bump: major
- name: bp-bridge-hub-polkadot
bump: major
- name: bp-bridge-hub-rococo
bump: major
- name: bp-bridge-hub-westend
bump: major
- name: pallet-bridge-grandpa
bump: major
- name: pallet-bridge-parachains
bump: major
- name: bp-parachains
bump: major
- name: bp-runtime
bump: major
- name: relay-substrate-client
bump: major
- name: bridge-hub-rococo-runtime
bump: major
- name: bridge-hub-westend-runtime
bump: major
- name: bridge-hub-test-utils
bump: minor