mirror of
https://github.com/pezkuwichain/pezkuwi-subxt.git
synced 2026-04-29 00:57:57 +00:00
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:
committed by
GitHub
parent
4f3d43a0c4
commit
a633e954f3
@@ -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
@@ -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"
|
||||
|
||||
@@ -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(
|
||||
¶chain_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(
|
||||
¶chain_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(|| {
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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));
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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));
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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()
|
||||
}
|
||||
}
|
||||
@@ -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 ¶chains[..] {
|
||||
&[(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 ¶chains[..] {
|
||||
&[(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())]
|
||||
));
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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, ¶chain_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, ¶chain_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),
|
||||
);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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 = ();
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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!( #(
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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",
|
||||
|
||||
+4
-2
@@ -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
|
||||
|
||||
+18
-9
@@ -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,
|
||||
|
||||
+26
-8
@@ -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",
|
||||
|
||||
+28
-6
@@ -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)
|
||||
},
|
||||
|
||||
+260
-2
@@ -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>>,
|
||||
|
||||
+65
-4
@@ -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)
|
||||
}
|
||||
|
||||
@@ -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
|
||||
Reference in New Issue
Block a user