Skip to content
Commits on Source (46)
...@@ -5,28 +5,41 @@ on: ...@@ -5,28 +5,41 @@ on:
- Review-Trigger - Review-Trigger
types: types:
- completed - completed
workflow_dispatch:
inputs:
pr-number:
description: "Number of the PR to evaluate"
required: true
type: number
jobs: jobs:
review-approvals: review-approvals:
runs-on: ubuntu-latest runs-on: ubuntu-latest
environment: master environment: master
steps: steps:
- name: Generate token
id: app_token
uses: actions/[email protected]
with:
app-id: ${{ secrets.REVIEW_APP_ID }}
private-key: ${{ secrets.REVIEW_APP_KEY }}
- name: Extract content of artifact - name: Extract content of artifact
if: ${{ !inputs.pr-number }}
id: number id: number
uses: Bullrich/[email protected].0 uses: Bullrich/[email protected].1
with: with:
artifact-name: pr_number artifact-name: pr_number
- name: Generate token
id: app_token
uses: tibdex/github-app-token@v1
with:
app_id: ${{ secrets.REVIEW_APP_ID }}
private_key: ${{ secrets.REVIEW_APP_KEY }}
- name: "Evaluates PR reviews and assigns reviewers" - name: "Evaluates PR reviews and assigns reviewers"
uses: paritytech/[email protected] uses: paritytech/[email protected]
with: with:
repo-token: ${{ steps.app_token.outputs.token }} repo-token: ${{ steps.app_token.outputs.token }}
team-token: ${{ steps.app_token.outputs.token }} team-token: ${{ steps.app_token.outputs.token }}
checks-token: ${{ steps.app_token.outputs.token }} checks-token: ${{ steps.app_token.outputs.token }}
pr-number: ${{ steps.number.outputs.content }} # This is extracted from the triggering event
pr-number: ${{ inputs.pr-number || steps.number.outputs.content }}
request-reviewers: true request-reviewers: true
- name: Log payload
if: ${{ failure() || runner.debug }}
run: echo "::debug::$payload"
env:
payload: ${{ toJson(github.event) }}
...@@ -65,7 +65,7 @@ jobs: ...@@ -65,7 +65,7 @@ jobs:
echo "Saving PR number: $PR_NUMBER" echo "Saving PR number: $PR_NUMBER"
mkdir -p ./pr mkdir -p ./pr
echo $PR_NUMBER > ./pr/pr_number echo $PR_NUMBER > ./pr/pr_number
- uses: actions/upload-artifact@v3 - uses: actions/upload-artifact@v4
name: Save PR number name: Save PR number
with: with:
name: pr_number name: pr_number
......
...@@ -61,7 +61,7 @@ jobs: ...@@ -61,7 +61,7 @@ jobs:
- name: Install toml-cli - name: Install toml-cli
run: cargo install --git https://github.com/gnprice/toml-cli --rev ea69e9d2ca4f0f858110dc7a5ae28bcb918c07fb # v0.2.3 run: cargo install --git https://github.com/gnprice/toml-cli --rev ea69e9d2ca4f0f858110dc7a5ae28bcb918c07fb # v0.2.3
- name: Install Polkadot SDK Version Manager - name: Install Polkadot SDK Version Manager
run: cargo install --git https://github.com/paritytech/psvm --rev c41261ffb52ab0c115adbbdb17e2cb7900d2bdfd psvm # master run: cargo install --git https://github.com/paritytech/psvm psvm
- name: Rust compilation prerequisites - name: Rust compilation prerequisites
run: | run: |
sudo apt update sudo apt update
......
...@@ -132,7 +132,6 @@ check-runtime-migration-westend: ...@@ -132,7 +132,6 @@ check-runtime-migration-westend:
WASM: "westend_runtime.compact.compressed.wasm" WASM: "westend_runtime.compact.compressed.wasm"
URI: "wss://westend-try-runtime-node.parity-chains.parity.io:443" URI: "wss://westend-try-runtime-node.parity-chains.parity.io:443"
SUBCOMMAND_EXTRA_ARGS: "--no-weight-warnings" SUBCOMMAND_EXTRA_ARGS: "--no-weight-warnings"
allow_failure: true
check-runtime-migration-rococo: check-runtime-migration-rococo:
stage: check stage: check
......
...@@ -74,6 +74,8 @@ publish-subsystem-benchmarks: ...@@ -74,6 +74,8 @@ publish-subsystem-benchmarks:
artifacts: true artifacts: true
- job: subsystem-benchmark-availability-distribution - job: subsystem-benchmark-availability-distribution
artifacts: true artifacts: true
- job: subsystem-benchmark-approval-voting
artifacts: true
- job: publish-rustdoc - job: publish-rustdoc
artifacts: false artifacts: false
script: script:
...@@ -115,6 +117,8 @@ trigger_workflow: ...@@ -115,6 +117,8 @@ trigger_workflow:
artifacts: true artifacts: true
- job: subsystem-benchmark-availability-distribution - job: subsystem-benchmark-availability-distribution
artifacts: true artifacts: true
- job: subsystem-benchmark-approval-voting
artifacts: true
script: script:
- echo "Triggering workflow" - echo "Triggering workflow"
- > - >
......
...@@ -511,7 +511,7 @@ test-syscalls: ...@@ -511,7 +511,7 @@ test-syscalls:
fi fi
allow_failure: false # this rarely triggers in practice allow_failure: false # this rarely triggers in practice
subsystem-benchmark-availability-recovery: .subsystem-benchmark-template:
stage: test stage: test
artifacts: artifacts:
name: "${CI_JOB_NAME}_${CI_COMMIT_REF_NAME}" name: "${CI_JOB_NAME}_${CI_COMMIT_REF_NAME}"
...@@ -523,26 +523,26 @@ subsystem-benchmark-availability-recovery: ...@@ -523,26 +523,26 @@ subsystem-benchmark-availability-recovery:
- .docker-env - .docker-env
- .common-refs - .common-refs
- .run-immediately - .run-immediately
script:
- cargo bench -p polkadot-availability-recovery --bench availability-recovery-regression-bench --features subsystem-benchmarks
tags: tags:
- benchmark - benchmark
subsystem-benchmark-availability-recovery:
extends:
- .subsystem-benchmark-template
script:
- cargo bench -p polkadot-availability-recovery --bench availability-recovery-regression-bench --features subsystem-benchmarks
allow_failure: true allow_failure: true
subsystem-benchmark-availability-distribution: subsystem-benchmark-availability-distribution:
stage: test
artifacts:
name: "${CI_JOB_NAME}_${CI_COMMIT_REF_NAME}"
when: always
expire_in: 1 hour
paths:
- charts/
extends: extends:
- .docker-env - .subsystem-benchmark-template
- .common-refs
- .run-immediately
script: script:
- cargo bench -p polkadot-availability-distribution --bench availability-distribution-regression-bench --features subsystem-benchmarks - cargo bench -p polkadot-availability-distribution --bench availability-distribution-regression-bench --features subsystem-benchmarks
tags: allow_failure: true
- benchmark
subsystem-benchmark-approval-voting:
extends:
- .subsystem-benchmark-template
script:
- cargo bench -p polkadot-node-core-approval-voting --bench approval-voting-regression-bench --features subsystem-benchmarks
allow_failure: true allow_failure: true
...@@ -55,9 +55,9 @@ zombienet-bridges-0001-asset-transfer-works: ...@@ -55,9 +55,9 @@ zombienet-bridges-0001-asset-transfer-works:
- /home/nonroot/bridges-polkadot-sdk/bridges/testing/run-new-test.sh 0001-asset-transfer --docker - /home/nonroot/bridges-polkadot-sdk/bridges/testing/run-new-test.sh 0001-asset-transfer --docker
- echo "Done" - echo "Done"
zombienet-bridges-0002-mandatory-headers-synced-while-idle: zombienet-bridges-0002-free-headers-synced-while-idle:
extends: extends:
- .zombienet-bridges-common - .zombienet-bridges-common
script: script:
- /home/nonroot/bridges-polkadot-sdk/bridges/testing/run-new-test.sh 0002-mandatory-headers-synced-while-idle --docker - /home/nonroot/bridges-polkadot-sdk/bridges/testing/run-new-test.sh 0002-free-headers-synced-while-idle --docker
- echo "Done" - echo "Done"
...@@ -2153,6 +2153,7 @@ dependencies = [ ...@@ -2153,6 +2153,7 @@ dependencies = [
"static_assertions", "static_assertions",
"substrate-wasm-builder", "substrate-wasm-builder",
"testnet-parachains-constants", "testnet-parachains-constants",
"tuplex",
] ]
[[package]] [[package]]
...@@ -2222,7 +2223,6 @@ dependencies = [ ...@@ -2222,7 +2223,6 @@ dependencies = [
"pallet-message-queue", "pallet-message-queue",
"pallet-xcm", "pallet-xcm",
"parachains-common", "parachains-common",
"parity-scale-codec",
"rococo-westend-system-emulated-network", "rococo-westend-system-emulated-network",
"sp-runtime", "sp-runtime",
"staging-xcm", "staging-xcm",
...@@ -2312,6 +2312,7 @@ dependencies = [ ...@@ -2312,6 +2312,7 @@ dependencies = [
"static_assertions", "static_assertions",
"substrate-wasm-builder", "substrate-wasm-builder",
"testnet-parachains-constants", "testnet-parachains-constants",
"tuplex",
"westend-runtime-constants", "westend-runtime-constants",
] ]
...@@ -2350,6 +2351,7 @@ dependencies = [ ...@@ -2350,6 +2351,7 @@ dependencies = [
"staging-xcm", "staging-xcm",
"staging-xcm-builder", "staging-xcm-builder",
"static_assertions", "static_assertions",
"tuplex",
] ]
[[package]] [[package]]
...@@ -11777,7 +11779,6 @@ dependencies = [ ...@@ -11777,7 +11779,6 @@ dependencies = [
"polkadot-primitives", "polkadot-primitives",
"polkadot-runtime-common", "polkadot-runtime-common",
"scale-info", "scale-info",
"sp-core",
"sp-io", "sp-io",
"sp-runtime", "sp-runtime",
"sp-std 14.0.0", "sp-std 14.0.0",
...@@ -13018,6 +13019,7 @@ dependencies = [ ...@@ -13018,6 +13019,7 @@ dependencies = [
"polkadot-overseer", "polkadot-overseer",
"polkadot-primitives", "polkadot-primitives",
"polkadot-primitives-test-helpers", "polkadot-primitives-test-helpers",
"polkadot-subsystem-bench",
"rand 0.8.5", "rand 0.8.5",
"rand_chacha 0.3.1", "rand_chacha 0.3.1",
"rand_core 0.6.4", "rand_core 0.6.4",
...@@ -14348,7 +14350,6 @@ dependencies = [ ...@@ -14348,7 +14350,6 @@ dependencies = [
name = "polkadot-test-runtime" name = "polkadot-test-runtime"
version = "1.0.0" version = "1.0.0"
dependencies = [ dependencies = [
"bitvec",
"frame-election-provider-support", "frame-election-provider-support",
"frame-executive", "frame-executive",
"frame-support", "frame-support",
...@@ -14373,16 +14374,12 @@ dependencies = [ ...@@ -14373,16 +14374,12 @@ dependencies = [
"pallet-vesting", "pallet-vesting",
"pallet-xcm", "pallet-xcm",
"parity-scale-codec", "parity-scale-codec",
"polkadot-parachain-primitives",
"polkadot-primitives", "polkadot-primitives",
"polkadot-runtime-common", "polkadot-runtime-common",
"polkadot-runtime-parachains", "polkadot-runtime-parachains",
"rustc-hex",
"scale-info", "scale-info",
"serde", "serde",
"serde_derive",
"serde_json", "serde_json",
"smallvec",
"sp-api", "sp-api",
"sp-authority-discovery", "sp-authority-discovery",
"sp-block-builder", "sp-block-builder",
...@@ -21273,11 +21270,8 @@ version = "1.0.0" ...@@ -21273,11 +21270,8 @@ version = "1.0.0"
dependencies = [ dependencies = [
"frame-support", "frame-support",
"polkadot-primitives", "polkadot-primitives",
"polkadot-runtime-common",
"smallvec", "smallvec",
"sp-core",
"sp-runtime", "sp-runtime",
"sp-weights",
] ]
[[package]] [[package]]
...@@ -22055,6 +22049,12 @@ dependencies = [ ...@@ -22055,6 +22049,12 @@ dependencies = [
"utf-8", "utf-8",
] ]
[[package]]
name = "tuplex"
version = "0.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "676ac81d5454c4dcf37955d34fa8626ede3490f744b86ca14a7b90168d2a08aa"
[[package]] [[package]]
name = "twox-hash" name = "twox-hash"
version = "1.6.3" version = "1.6.3"
......
...@@ -16,6 +16,7 @@ hash-db = { version = "0.16.0", default-features = false } ...@@ -16,6 +16,7 @@ hash-db = { version = "0.16.0", default-features = false }
log = { workspace = true } log = { workspace = true }
scale-info = { version = "2.11.1", default-features = false, features = ["derive"] } scale-info = { version = "2.11.1", default-features = false, features = ["derive"] }
static_assertions = { version = "1.1", optional = true } static_assertions = { version = "1.1", optional = true }
tuplex = { version = "0.1", default-features = false }
# Bridge dependencies # Bridge dependencies
...@@ -82,6 +83,7 @@ std = [ ...@@ -82,6 +83,7 @@ std = [
"sp-runtime/std", "sp-runtime/std",
"sp-std/std", "sp-std/std",
"sp-trie/std", "sp-trie/std",
"tuplex/std",
"xcm-builder/std", "xcm-builder/std",
"xcm/std", "xcm/std",
] ]
......
...@@ -18,55 +18,229 @@ ...@@ -18,55 +18,229 @@
//! obsolete (duplicated) data or do not pass some additional pallet-specific //! obsolete (duplicated) data or do not pass some additional pallet-specific
//! checks. //! checks.
use crate::messages_call_ext::MessagesCallSubType; use crate::{
use pallet_bridge_grandpa::CallSubType as GrandpaCallSubType; extensions::refund_relayer_extension::RefundableParachainId,
use pallet_bridge_parachains::CallSubType as ParachainsCallSubtype; messages_call_ext::MessagesCallSubType,
use sp_runtime::transaction_validity::TransactionValidity; };
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. /// A duplication of the `FilterCall` trait.
/// ///
/// We need this trait in order to be able to implement it for the messages pallet, /// 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. /// since the implementation is done outside of the pallet crate.
pub trait BridgeRuntimeFilterCall<Call> { pub trait BridgeRuntimeFilterCall<AccountId, Call> {
/// Checks if a runtime call is valid. /// Data that may be passed from the validate to `post_dispatch`.
fn validate(call: &Call) -> TransactionValidity; 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) {
} }
}
/// 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> BridgeRuntimeFilterCall<T::RuntimeCall> for pallet_bridge_grandpa::Pallet<T, I> 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 where
T: pallet_bridge_grandpa::Config<I>, T: pallet_bridge_grandpa::Config<I>,
T::RuntimeCall: GrandpaCallSubType<T, I>, T::RuntimeCall: GrandpaCallSubType<T, I>,
{ {
fn validate(call: &T::RuntimeCall) -> TransactionValidity { type ToPostDispatch = ();
fn validate(_who: &T::AccountId, call: &T::RuntimeCall) -> ((), TransactionValidity) {
(
(),
GrandpaCallSubType::<T, I>::check_obsolete_submit_finality_proof(call) 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> for pallet_bridge_parachains::Pallet<T, I>
where where
T: pallet_bridge_parachains::Config<I>, T: pallet_bridge_parachains::Config<I>,
T::RuntimeCall: ParachainsCallSubtype<T, I>, T::RuntimeCall: ParachainsCallSubtype<T, I>,
{ {
fn validate(call: &T::RuntimeCall) -> TransactionValidity { type ToPostDispatch = ();
fn validate(_who: &T::AccountId, call: &T::RuntimeCall) -> ((), TransactionValidity) {
(
(),
ParachainsCallSubtype::<T, I>::check_obsolete_submit_parachain_heads(call) 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> impl<T: pallet_bridge_messages::Config<I>, I: 'static>
for pallet_bridge_messages::Pallet<T, I> BridgeRuntimeFilterCall<T::AccountId, T::RuntimeCall> for pallet_bridge_messages::Pallet<T, I>
where where
T::RuntimeCall: MessagesCallSubType<T, I>, T::RuntimeCall: MessagesCallSubType<T, I>,
{ {
type ToPostDispatch = ();
/// Validate messages in order to avoid "mining" messages delivery and delivery confirmation /// Validate messages in order to avoid "mining" messages delivery and delivery confirmation
/// transactions, that are delivering outdated messages/confirmations. Without this validation, /// transactions, that are delivering outdated messages/confirmations. Without this validation,
/// even honest relayers may lose their funds if there are multiple relays running and /// even honest relayers may lose their funds if there are multiple relays running and
/// submitting the same messages/confirmations. /// submitting the same messages/confirmations.
fn validate(call: &T::RuntimeCall) -> TransactionValidity { fn validate(_who: &T::AccountId, call: &T::RuntimeCall) -> ((), TransactionValidity) {
call.check_obsolete_call() ((), 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. /// Declares a runtime-specific `BridgeRejectObsoleteHeadersAndMessages` signed extension.
/// ///
/// ## Example /// ## Example
...@@ -92,7 +266,15 @@ macro_rules! generate_bridge_reject_obsolete_headers_and_messages { ...@@ -92,7 +266,15 @@ macro_rules! generate_bridge_reject_obsolete_headers_and_messages {
type AccountId = $account_id; type AccountId = $account_id;
type Call = $call; type Call = $call;
type AdditionalSigned = (); 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< fn additional_signed(&self) -> sp_std::result::Result<
(), (),
...@@ -101,29 +283,72 @@ macro_rules! generate_bridge_reject_obsolete_headers_and_messages { ...@@ -101,29 +283,72 @@ macro_rules! generate_bridge_reject_obsolete_headers_and_messages {
Ok(()) Ok(())
} }
#[allow(unused_variables)]
fn validate( fn validate(
&self, &self,
_who: &Self::AccountId, who: &Self::AccountId,
call: &Self::Call, call: &Self::Call,
_info: &sp_runtime::traits::DispatchInfoOf<Self::Call>, _info: &sp_runtime::traits::DispatchInfoOf<Self::Call>,
_len: usize, _len: usize,
) -> sp_runtime::transaction_validity::TransactionValidity { ) -> 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 let (from_validate, call_filter_validity) = <
.combine_with(<$filter_call as $crate::extensions::check_obsolete_extension::BridgeRuntimeFilterCall<$call>>::validate(call)?); $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( fn pre_dispatch(
self, self,
who: &Self::AccountId, relayer: &Self::AccountId,
call: &Self::Call, call: &Self::Call,
info: &sp_runtime::traits::DispatchInfoOf<Self::Call>, info: &sp_runtime::traits::DispatchInfoOf<Self::Call>,
len: usize, len: usize,
) -> Result<Self::Pre, sp_runtime::transaction_validity::TransactionValidityError> { ) -> 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 { ...@@ -132,10 +357,23 @@ macro_rules! generate_bridge_reject_obsolete_headers_and_messages {
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use super::*; 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 frame_support::{assert_err, assert_ok};
use sp_runtime::{ use sp_runtime::{
traits::SignedExtension, traits::{ConstU64, SignedExtension},
transaction_validity::{InvalidTransaction, TransactionValidity, ValidTransaction}, transaction_validity::{InvalidTransaction, TransactionValidity, ValidTransaction},
DispatchError,
}; };
pub struct MockCall { pub struct MockCall {
...@@ -143,7 +381,7 @@ mod tests { ...@@ -143,7 +381,7 @@ mod tests {
} }
impl sp_runtime::traits::Dispatchable for MockCall { impl sp_runtime::traits::Dispatchable for MockCall {
type RuntimeOrigin = (); type RuntimeOrigin = u64;
type Config = (); type Config = ();
type Info = (); type Info = ();
type PostInfo = (); type PostInfo = ();
...@@ -156,50 +394,287 @@ mod tests { ...@@ -156,50 +394,287 @@ mod tests {
} }
} }
struct FirstFilterCall; pub struct FirstFilterCall;
impl BridgeRuntimeFilterCall<MockCall> for FirstFilterCall { impl FirstFilterCall {
fn validate(call: &MockCall) -> TransactionValidity { fn post_dispatch_called_with(success: bool) {
frame_support::storage::unhashed::put(&[1], &success);
}
fn verify_post_dispatch_called_with(success: bool) {
assert_eq!(frame_support::storage::unhashed::get::<bool>(&[1]), Some(success));
}
}
impl BridgeRuntimeFilterCall<u64, MockCall> for FirstFilterCall {
type ToPostDispatch = u64;
fn validate(_who: &u64, call: &MockCall) -> (u64, TransactionValidity) {
if call.data <= 1 { if call.data <= 1 {
return InvalidTransaction::Custom(1).into() return (1, InvalidTransaction::Custom(1).into())
}
(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);
} }
Ok(ValidTransaction { priority: 1, ..Default::default() }) fn verify_post_dispatch_called_with(success: bool) {
assert_eq!(frame_support::storage::unhashed::get::<bool>(&[2]), Some(success));
} }
} }
struct SecondFilterCall; impl BridgeRuntimeFilterCall<u64, MockCall> for SecondFilterCall {
impl BridgeRuntimeFilterCall<MockCall> for SecondFilterCall { type ToPostDispatch = u64;
fn validate(call: &MockCall) -> TransactionValidity { fn validate(_who: &u64, call: &MockCall) -> (u64, TransactionValidity) {
if call.data <= 2 { if call.data <= 2 {
return InvalidTransaction::Custom(2).into() return (2, InvalidTransaction::Custom(2).into())
}
(2, Ok(ValidTransaction { priority: 2, ..Default::default() }))
} }
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] #[test]
fn test() { fn test_generated_obsolete_extension() {
generate_bridge_reject_obsolete_headers_and_messages!( generate_bridge_reject_obsolete_headers_and_messages!(
MockCall, MockCall,
(), u64,
FirstFilterCall, FirstFilterCall,
SecondFilterCall SecondFilterCall
); );
run_test(|| {
assert_err!(
BridgeRejectObsoleteHeadersAndMessages.validate(&42, &MockCall { data: 1 }, &(), 0),
InvalidTransaction::Custom(1)
);
assert_err!( assert_err!(
BridgeRejectObsoleteHeadersAndMessages.validate(&(), &MockCall { data: 1 }, &(), 0), BridgeRejectObsoleteHeadersAndMessages.pre_dispatch(
&42,
&MockCall { data: 1 },
&(),
0
),
InvalidTransaction::Custom(1) InvalidTransaction::Custom(1)
); );
assert_err!( assert_err!(
BridgeRejectObsoleteHeadersAndMessages.validate(&(), &MockCall { data: 2 }, &(), 0), BridgeRejectObsoleteHeadersAndMessages.validate(&42, &MockCall { data: 2 }, &(), 0),
InvalidTransaction::Custom(2) InvalidTransaction::Custom(2)
); );
assert_err!(
BridgeRejectObsoleteHeadersAndMessages.pre_dispatch(
&42,
&MockCall { data: 2 },
&(),
0
),
InvalidTransaction::Custom(2)
);
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);
})
}
assert_ok!( #[test]
BridgeRejectObsoleteHeadersAndMessages.validate(&(), &MockCall { data: 3 }, &(), 0), fn parachains_wrapper_boosts_extensions_for_registered_relayer() {
ValidTransaction { priority: 3, ..Default::default() } 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 @@ ...@@ -22,7 +22,6 @@
//! single message with nonce `N`, then the transaction with nonces `N..=N+100` will //! 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. //! be rejected. This can lower bridge throughput down to one message per block.
use bp_messages::MessageNonce;
use frame_support::traits::Get; use frame_support::traits::Get;
use sp_runtime::transaction_validity::TransactionPriority; use sp_runtime::transaction_validity::TransactionPriority;
...@@ -30,16 +29,19 @@ use sp_runtime::transaction_validity::TransactionPriority; ...@@ -30,16 +29,19 @@ use sp_runtime::transaction_validity::TransactionPriority;
#[allow(unused_imports)] #[allow(unused_imports)]
pub use integrity_tests::*; pub use integrity_tests::*;
/// Compute priority boost for message delivery transaction that delivers /// We'll deal with different bridge items here - messages, headers, ...
/// given number of messages. /// To avoid being too verbose with generic code, let's just define a separate alias.
pub fn compute_priority_boost<PriorityBoostPerMessage>( pub type ItemCount = u64;
messages: MessageNonce,
) -> TransactionPriority /// 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 where
PriorityBoostPerMessage: Get<TransactionPriority>, PriorityBoostPerItem: Get<TransactionPriority>,
{ {
// we don't want any boost for transaction with single message => minus one // we don't want any boost for transaction with single (additional) item => minus one
PriorityBoostPerMessage::get().saturating_mul(messages.saturating_sub(1)) PriorityBoostPerItem::get().saturating_mul(n_items.saturating_sub(1))
} }
#[cfg(not(feature = "integrity-test"))] #[cfg(not(feature = "integrity-test"))]
...@@ -47,7 +49,8 @@ mod integrity_tests {} ...@@ -47,7 +49,8 @@ mod integrity_tests {}
#[cfg(feature = "integrity-test")] #[cfg(feature = "integrity-test")]
mod integrity_tests { 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_messages::MessageNonce;
use bp_runtime::PreComputedSize; use bp_runtime::PreComputedSize;
...@@ -55,7 +58,6 @@ mod integrity_tests { ...@@ -55,7 +58,6 @@ mod integrity_tests {
dispatch::{DispatchClass, DispatchInfo, Pays, PostDispatchInfo}, dispatch::{DispatchClass, DispatchInfo, Pays, PostDispatchInfo},
traits::Get, traits::Get,
}; };
use pallet_bridge_messages::WeightInfoExt;
use pallet_transaction_payment::OnChargeTransaction; use pallet_transaction_payment::OnChargeTransaction;
use sp_runtime::{ use sp_runtime::{
traits::{Dispatchable, UniqueSaturatedInto, Zero}, traits::{Dispatchable, UniqueSaturatedInto, Zero},
...@@ -68,37 +70,33 @@ mod integrity_tests { ...@@ -68,37 +70,33 @@ mod integrity_tests {
T, T,
>>::Balance; >>::Balance;
/// Ensures that the value of `PriorityBoostPerMessage` matches the value of /// Ensures that the value of `PriorityBoostPerItem` matches the value of
/// `tip_boost_per_message`. /// `tip_boost_per_item`.
/// ///
/// We want two transactions, `TX1` with `N` messages and `TX2` with `N+1` messages, have almost /// 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_message` tip to the `TX1`. We want to be sure /// 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 `PriorityBoostPerMessage` priority to `TX1`, the priority will be close /// that if we add plain `PriorityBoostPerItem` priority to `TX1`, the priority will be close
/// to `TX2` as well. /// to `TX2` as well.
pub fn ensure_priority_boost_is_sane<Runtime, MessagesInstance, PriorityBoostPerMessage>( fn ensure_priority_boost_is_sane<PriorityBoostPerItem, Balance>(
tip_boost_per_message: BalanceOf<Runtime>, param_name: &str,
max_items: ItemCount,
tip_boost_per_item: Balance,
estimate_priority: impl Fn(ItemCount, Balance) -> TransactionPriority,
) where ) where
Runtime: PriorityBoostPerItem: Get<TransactionPriority>,
pallet_transaction_payment::Config + pallet_bridge_messages::Config<MessagesInstance>, ItemCount: UniqueSaturatedInto<Balance>,
MessagesInstance: 'static, Balance: FixedPointOperand + Zero,
PriorityBoostPerMessage: Get<TransactionPriority>,
Runtime::RuntimeCall: Dispatchable<Info = DispatchInfo, PostInfo = PostDispatchInfo>,
BalanceOf<Runtime>: Send + Sync + FixedPointOperand,
{ {
let priority_boost_per_message = PriorityBoostPerMessage::get(); let priority_boost_per_item = PriorityBoostPerItem::get();
let maximal_messages_in_delivery_transaction = for n_items in 1..=max_items {
Runtime::MaxUnconfirmedMessagesAtInboundLane::get(); let base_priority = estimate_priority(n_items, Zero::zero());
for messages in 1..=maximal_messages_in_delivery_transaction { let priority_boost = compute_priority_boost::<PriorityBoostPerItem>(n_items);
let base_priority = estimate_message_delivery_transaction_priority::< let priority_with_boost = base_priority
Runtime, .checked_add(priority_boost)
MessagesInstance, .expect("priority overflow: try lowering `max_items` or `tip_boost_per_item`?");
>(messages, Zero::zero());
let priority_boost = compute_priority_boost::<PriorityBoostPerMessage>(messages);
let priority_with_boost = base_priority + priority_boost;
let tip = tip_boost_per_message.saturating_mul((messages - 1).unique_saturated_into()); let tip = tip_boost_per_item.saturating_mul((n_items - 1).unique_saturated_into());
let priority_with_tip = let priority_with_tip = estimate_priority(1, tip);
estimate_message_delivery_transaction_priority::<Runtime, MessagesInstance>(1, tip);
const ERROR_MARGIN: TransactionPriority = 5; // 5% const ERROR_MARGIN: TransactionPriority = 5; // 5%
if priority_with_boost.abs_diff(priority_with_tip).saturating_mul(100) / if priority_with_boost.abs_diff(priority_with_tip).saturating_mul(100) /
...@@ -106,46 +104,252 @@ mod integrity_tests { ...@@ -106,46 +104,252 @@ mod integrity_tests {
ERROR_MARGIN ERROR_MARGIN
{ {
panic!( panic!(
"The PriorityBoostPerMessage value ({}) must be fixed to: {}", "The {param_name} value ({}) must be fixed to: {}",
priority_boost_per_message, priority_boost_per_item,
compute_priority_boost_per_message::<Runtime, MessagesInstance>( compute_priority_boost_per_item(
tip_boost_per_message 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")] #[cfg(feature = "integrity-test")]
fn compute_priority_boost_per_message<Runtime, MessagesInstance>( fn compute_priority_boost_per_item<Balance>(
tip_boost_per_message: BalanceOf<Runtime>, max_items: ItemCount,
tip_boost_per_item: Balance,
estimate_priority: impl Fn(ItemCount, Balance) -> TransactionPriority,
) -> TransactionPriority
where
ItemCount: UniqueSaturatedInto<Balance>,
Balance: FixedPointOperand + Zero,
{
// estimate priority of transaction that delivers one item and has large tip
let small_with_tip_priority =
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(max_items - 1)
}
/// Computations, specific to bridge relay chains transactions.
pub mod per_relay_header {
use super::*;
use bp_header_chain::{
max_expected_submit_finality_proof_arguments_size, ChainWithGrandpa,
};
use pallet_bridge_grandpa::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, 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 ) -> TransactionPriority
where where
Runtime: Runtime:
pallet_transaction_payment::Config + pallet_bridge_messages::Config<MessagesInstance>, 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, MessagesInstance: 'static,
PriorityBoostPerMessage: Get<TransactionPriority>,
Runtime::RuntimeCall: Dispatchable<Info = DispatchInfo, PostInfo = PostDispatchInfo>, Runtime::RuntimeCall: Dispatchable<Info = DispatchInfo, PostInfo = PostDispatchInfo>,
BalanceOf<Runtime>: Send + Sync + FixedPointOperand, BalanceOf<Runtime>: Send + Sync + FixedPointOperand,
{ {
// estimate priority of transaction that delivers one message and has large tip
let maximal_messages_in_delivery_transaction = let maximal_messages_in_delivery_transaction =
Runtime::MaxUnconfirmedMessagesAtInboundLane::get(); Runtime::MaxUnconfirmedMessagesAtInboundLane::get();
let small_with_tip_priority = 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>( estimate_message_delivery_transaction_priority::<Runtime, MessagesInstance>(
1, n_messages, tip,
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());
small_with_tip_priority
.saturating_sub(large_without_tip_priority)
.saturating_div(maximal_messages_in_delivery_transaction - 1)
} }
/// Estimate message delivery transaction priority. /// Estimate message delivery transaction priority.
...@@ -155,8 +359,8 @@ mod integrity_tests { ...@@ -155,8 +359,8 @@ mod integrity_tests {
tip: BalanceOf<Runtime>, tip: BalanceOf<Runtime>,
) -> TransactionPriority ) -> TransactionPriority
where where
Runtime: Runtime: pallet_transaction_payment::Config
pallet_transaction_payment::Config + pallet_bridge_messages::Config<MessagesInstance>, + pallet_bridge_messages::Config<MessagesInstance>,
MessagesInstance: 'static, MessagesInstance: 'static,
Runtime::RuntimeCall: Dispatchable<Info = DispatchInfo, PostInfo = PostDispatchInfo>, Runtime::RuntimeCall: Dispatchable<Info = DispatchInfo, PostInfo = PostDispatchInfo>,
BalanceOf<Runtime>: Send + Sync + FixedPointOperand, BalanceOf<Runtime>: Send + Sync + FixedPointOperand,
...@@ -165,15 +369,15 @@ mod integrity_tests { ...@@ -165,15 +369,15 @@ mod integrity_tests {
// (including signature, signed extensions extra and etc + in our case it includes // (including signature, signed extensions extra and etc + in our case it includes
// all call arguments except the proof itself) // all call arguments except the proof itself)
let base_tx_size = 512; let base_tx_size = 512;
// let's say we are relaying similar small messages and for every message we add more trie // let's say we are relaying similar small messages and for every message we add more
// nodes to the proof (x0.5 because we expect some nodes to be reused) // trie nodes to the proof (x0.5 because we expect some nodes to be reused)
let estimated_message_size = 512; let estimated_message_size = 512;
// let's say all our messages have the same dispatch weight // let's say all our messages have the same dispatch weight
let estimated_message_dispatch_weight = let estimated_message_dispatch_weight =
Runtime::WeightInfo::message_dispatch_weight(estimated_message_size); Runtime::WeightInfo::message_dispatch_weight(estimated_message_size);
// messages proof argument size is (for every message) messages size + some additional // 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 // trie nodes. Some of them are reused by different messages, so let's take 2/3 of
// "overhead" constant // default "overhead" constant
let messages_proof_size = Runtime::WeightInfo::expected_extra_storage_proof_size() let messages_proof_size = Runtime::WeightInfo::expected_extra_storage_proof_size()
.saturating_mul(2) .saturating_mul(2)
.saturating_div(3) .saturating_div(3)
...@@ -200,3 +404,4 @@ mod integrity_tests { ...@@ -200,3 +404,4 @@ mod integrity_tests {
) )
} }
} }
}
...@@ -24,7 +24,7 @@ use crate::messages_call_ext::{ ...@@ -24,7 +24,7 @@ use crate::messages_call_ext::{
}; };
use bp_messages::{LaneId, MessageNonce}; use bp_messages::{LaneId, MessageNonce};
use bp_relayers::{ExplicitOrAccountParams, RewardsAccountOwner, RewardsAccountParams}; 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 codec::{Codec, Decode, Encode};
use frame_support::{ use frame_support::{
dispatch::{CallableCallFor, DispatchInfo, PostDispatchInfo}, dispatch::{CallableCallFor, DispatchInfo, PostDispatchInfo},
...@@ -33,8 +33,7 @@ use frame_support::{ ...@@ -33,8 +33,7 @@ use frame_support::{
CloneNoBound, DefaultNoBound, EqNoBound, PartialEqNoBound, RuntimeDebugNoBound, CloneNoBound, DefaultNoBound, EqNoBound, PartialEqNoBound, RuntimeDebugNoBound,
}; };
use pallet_bridge_grandpa::{ use pallet_bridge_grandpa::{
CallSubType as GrandpaCallSubType, Config as GrandpaConfig, SubmitFinalityProofHelper, CallSubType as GrandpaCallSubType, SubmitFinalityProofHelper, SubmitFinalityProofInfo,
SubmitFinalityProofInfo,
}; };
use pallet_bridge_messages::Config as MessagesConfig; use pallet_bridge_messages::Config as MessagesConfig;
use pallet_bridge_parachains::{ use pallet_bridge_parachains::{
...@@ -66,20 +65,9 @@ type CallOf<R> = <R as frame_system::Config>::RuntimeCall; ...@@ -66,20 +65,9 @@ type CallOf<R> = <R as frame_system::Config>::RuntimeCall;
/// coming from this parachain. /// coming from this parachain.
pub trait RefundableParachainId { pub trait RefundableParachainId {
/// The instance of the bridge parachains pallet. /// The instance of the bridge parachains pallet.
type Instance; type Instance: 'static;
/// The parachain Id. /// The parachain Id.
type Id: Get<u32>; type BridgedChain: Parachain;
}
/// 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;
} }
/// Implementation of `RefundableParachainId` for `trait Parachain`. /// Implementation of `RefundableParachainId` for `trait Parachain`.
...@@ -87,10 +75,11 @@ pub struct RefundableParachain<Instance, Para>(PhantomData<(Instance, Para)>); ...@@ -87,10 +75,11 @@ pub struct RefundableParachain<Instance, Para>(PhantomData<(Instance, Para)>);
impl<Instance, Para> RefundableParachainId for RefundableParachain<Instance, Para> impl<Instance, Para> RefundableParachainId for RefundableParachain<Instance, Para>
where where
Instance: 'static,
Para: Parachain, Para: Parachain,
{ {
type Instance = Instance; type Instance = Instance;
type Id = ParachainIdOf<Para>; type BridgedChain = Para;
} }
/// Trait identifying a bridged messages lane. A relayer might be refunded for delivering messages /// Trait identifying a bridged messages lane. A relayer might be refunded for delivering messages
...@@ -242,17 +231,10 @@ pub enum RelayerAccountAction<AccountId, Reward> { ...@@ -242,17 +231,10 @@ pub enum RelayerAccountAction<AccountId, Reward> {
/// Everything common among our refund signed extensions. /// Everything common among our refund signed extensions.
pub trait RefundSignedExtension: pub trait RefundSignedExtension:
'static + Clone + Codec + sp_std::fmt::Debug + Default + Eq + PartialEq + Send + Sync + TypeInfo '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. /// This chain runtime.
type Runtime: UtilityConfig<RuntimeCall = CallOf<Self::Runtime>> type Runtime: MessagesConfig<<Self::Msgs as RefundableMessagesLaneId>::Instance>
+ GrandpaConfig<Self::GrandpaInstance>
+ MessagesConfig<<Self::Msgs as RefundableMessagesLaneId>::Instance>
+ RelayersConfig; + RelayersConfig;
/// Grandpa pallet reference.
type GrandpaInstance: 'static;
/// Messages pallet and lane reference. /// Messages pallet and lane reference.
type Msgs: RefundableMessagesLaneId; type Msgs: RefundableMessagesLaneId;
/// Refund amount calculator. /// Refund amount calculator.
...@@ -276,11 +258,13 @@ where ...@@ -276,11 +258,13 @@ where
call: &CallOf<Self::Runtime>, call: &CallOf<Self::Runtime>,
) -> Result<&CallOf<Self::Runtime>, TransactionValidityError>; ) -> Result<&CallOf<Self::Runtime>, TransactionValidityError>;
/// Called from post-dispatch and shall perform additional checks (apart from relay /// Called from post-dispatch and shall perform additional checks (apart from messages
/// chain finality and messages transaction finality) of given call result. /// transaction success) of given call result.
fn additional_call_result_check( fn additional_call_result_check(
relayer: &AccountIdOf<Self::Runtime>, relayer: &AccountIdOf<Self::Runtime>,
call_info: &CallInfo, call_info: &CallInfo,
extra_weight: &mut Weight,
extra_size: &mut u32,
) -> bool; ) -> bool;
/// Given post-dispatch information, analyze the outcome of relayer call and return /// Given post-dispatch information, analyze the outcome of relayer call and return
...@@ -348,35 +332,6 @@ where ...@@ -348,35 +332,6 @@ where
return slash_relayer_if_delivery_result 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 // 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. // it contained. If this happens, we consider the transaction "helpful" and refund it.
let msgs_call_info = call_info.messages_call_info(); let msgs_call_info = call_info.messages_call_info();
...@@ -391,8 +346,13 @@ where ...@@ -391,8 +346,13 @@ where
return slash_relayer_if_delivery_result return slash_relayer_if_delivery_result
} }
// do additional check // do additional checks
if !Self::additional_call_result_check(&relayer, &call_info) { if !Self::additional_call_result_check(
&relayer,
&call_info,
&mut extra_weight,
&mut extra_size,
) {
return slash_relayer_if_delivery_result return slash_relayer_if_delivery_result
} }
...@@ -468,18 +428,11 @@ where ...@@ -468,18 +428,11 @@ where
RuntimeDebugNoBound, RuntimeDebugNoBound,
TypeInfo, TypeInfo,
)] )]
pub struct RefundSignedExtensionAdapter<T: RefundSignedExtension>(T) pub struct RefundSignedExtensionAdapter<T: RefundSignedExtension>(T);
where
<T::Runtime as GrandpaConfig<T::GrandpaInstance>>::BridgedChain:
Chain<BlockNumber = RelayBlockNumber>;
impl<T: RefundSignedExtension> SignedExtension for RefundSignedExtensionAdapter<T> impl<T: RefundSignedExtension> SignedExtension for RefundSignedExtensionAdapter<T>
where where
<T::Runtime as GrandpaConfig<T::GrandpaInstance>>::BridgedChain:
Chain<BlockNumber = RelayBlockNumber>,
CallOf<T::Runtime>: Dispatchable<Info = DispatchInfo, PostInfo = PostDispatchInfo> 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>, + MessagesCallSubType<T::Runtime, <T::Msgs as RefundableMessagesLaneId>::Instance>,
{ {
const IDENTIFIER: &'static str = T::Id::STR; const IDENTIFIER: &'static str = T::Id::STR;
...@@ -644,6 +597,14 @@ impl<Runtime, Para, Msgs, Refund, Priority, Id> RefundSignedExtension ...@@ -644,6 +597,14 @@ impl<Runtime, Para, Msgs, Refund, Priority, Id> RefundSignedExtension
for RefundBridgedParachainMessages<Runtime, Para, Msgs, Refund, Priority, Id> for RefundBridgedParachainMessages<Runtime, Para, Msgs, Refund, Priority, Id>
where where
Self: 'static + Send + Sync, Self: 'static + Send + Sync,
RefundBridgedGrandpaMessages<
Runtime,
Runtime::BridgesGrandpaPalletInstance,
Msgs,
Refund,
Priority,
Id,
>: 'static + Send + Sync,
Runtime: UtilityConfig<RuntimeCall = CallOf<Runtime>> Runtime: UtilityConfig<RuntimeCall = CallOf<Runtime>>
+ BoundedBridgeGrandpaConfig<Runtime::BridgesGrandpaPalletInstance> + BoundedBridgeGrandpaConfig<Runtime::BridgesGrandpaPalletInstance>
+ ParachainsConfig<Para::Instance> + ParachainsConfig<Para::Instance>
...@@ -661,7 +622,6 @@ where ...@@ -661,7 +622,6 @@ where
+ MessagesCallSubType<Runtime, Msgs::Instance>, + MessagesCallSubType<Runtime, Msgs::Instance>,
{ {
type Runtime = Runtime; type Runtime = Runtime;
type GrandpaInstance = Runtime::BridgesGrandpaPalletInstance;
type Msgs = Msgs; type Msgs = Msgs;
type Refund = Refund; type Refund = Refund;
type Priority = Priority; type Priority = Priority;
...@@ -687,7 +647,7 @@ where ...@@ -687,7 +647,7 @@ where
let para_finality_call = calls let para_finality_call = calls
.next() .next()
.transpose()? .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 = let relay_finality_call =
calls.next().transpose()?.and_then(|c| c.submit_finality_proof_info()); calls.next().transpose()?.and_then(|c| c.submit_finality_proof_info());
...@@ -711,7 +671,26 @@ where ...@@ -711,7 +671,26 @@ where
Ok(call) 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 // check if parachain state has been updated
if let Some(para_proof_info) = call_info.submit_parachain_heads_info() { if let Some(para_proof_info) = call_info.submit_parachain_heads_info() {
if !SubmitParachainHeadsHelper::<Runtime, Para::Instance>::was_successful( if !SubmitParachainHeadsHelper::<Runtime, Para::Instance>::was_successful(
...@@ -722,7 +701,7 @@ where ...@@ -722,7 +701,7 @@ where
target: "runtime::bridge", target: "runtime::bridge",
"{} from parachain {} via {:?}: relayer {:?} has submitted invalid parachain finality proof", "{} from parachain {} via {:?}: relayer {:?} has submitted invalid parachain finality proof",
Id::STR, Id::STR,
Para::Id::get(), Para::BridgedChain::PARACHAIN_ID,
Msgs::Id::get(), Msgs::Id::get(),
relayer, relayer,
); );
...@@ -794,7 +773,6 @@ where ...@@ -794,7 +773,6 @@ where
+ MessagesCallSubType<Runtime, Msgs::Instance>, + MessagesCallSubType<Runtime, Msgs::Instance>,
{ {
type Runtime = Runtime; type Runtime = Runtime;
type GrandpaInstance = GrandpaInstance;
type Msgs = Msgs; type Msgs = Msgs;
type Refund = Refund; type Refund = Refund;
type Priority = Priority; type Priority = Priority;
...@@ -836,13 +814,125 @@ where ...@@ -836,13 +814,125 @@ where
Ok(call) 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 true
} }
} }
#[cfg(test)] #[cfg(test)]
mod tests { pub(crate) mod tests {
use super::*; use super::*;
use crate::{ use crate::{
messages::{ messages::{
...@@ -854,6 +944,7 @@ mod tests { ...@@ -854,6 +944,7 @@ mod tests {
}, },
mock::*, mock::*,
}; };
use bp_header_chain::StoredHeaderDataBuilder;
use bp_messages::{ use bp_messages::{
DeliveredMessages, InboundLaneData, MessageNonce, MessagesOperatingMode, OutboundLaneData, DeliveredMessages, InboundLaneData, MessageNonce, MessagesOperatingMode, OutboundLaneData,
UnrewardedRelayer, UnrewardedRelayersState, UnrewardedRelayer, UnrewardedRelayersState,
...@@ -879,7 +970,6 @@ mod tests { ...@@ -879,7 +970,6 @@ mod tests {
}; };
parameter_types! { parameter_types! {
TestParachain: u32 = 1000;
pub TestLaneId: LaneId = TEST_LANE_ID; pub TestLaneId: LaneId = TEST_LANE_ID;
pub MsgProofsRewardsAccount: RewardsAccountParams = RewardsAccountParams::new( pub MsgProofsRewardsAccount: RewardsAccountParams = RewardsAccountParams::new(
TEST_LANE_ID, TEST_LANE_ID,
...@@ -895,6 +985,14 @@ mod tests { ...@@ -895,6 +985,14 @@ mod tests {
bp_runtime::generate_static_str_provider!(TestExtension); 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< type TestGrandpaExtensionProvider = RefundBridgedGrandpaMessages<
TestRuntime, TestRuntime,
(), (),
...@@ -906,7 +1004,7 @@ mod tests { ...@@ -906,7 +1004,7 @@ mod tests {
type TestGrandpaExtension = RefundSignedExtensionAdapter<TestGrandpaExtensionProvider>; type TestGrandpaExtension = RefundSignedExtensionAdapter<TestGrandpaExtensionProvider>;
type TestExtensionProvider = RefundBridgedParachainMessages< type TestExtensionProvider = RefundBridgedParachainMessages<
TestRuntime, TestRuntime,
DefaultRefundableParachainId<(), TestParachain>, RefundableParachain<(), BridgedUnderlyingParachain>,
RefundableMessagesLane<(), TestLaneId>, RefundableMessagesLane<(), TestLaneId>,
ActualFeeRefund<TestRuntime>, ActualFeeRefund<TestRuntime>,
ConstU64<1>, ConstU64<1>,
...@@ -930,7 +1028,7 @@ mod tests { ...@@ -930,7 +1028,7 @@ mod tests {
TestPaymentProcedure::rewards_account(MsgDeliveryProofsRewardsAccount::get()) TestPaymentProcedure::rewards_account(MsgDeliveryProofsRewardsAccount::get())
} }
fn relayer_account_at_this_chain() -> ThisChainAccountId { pub fn relayer_account_at_this_chain() -> ThisChainAccountId {
0 0
} }
...@@ -938,7 +1036,7 @@ mod tests { ...@@ -938,7 +1036,7 @@ mod tests {
0 0
} }
fn initialize_environment( pub fn initialize_environment(
best_relay_header_number: RelayBlockNumber, best_relay_header_number: RelayBlockNumber,
parachain_head_at_relay_header_number: RelayBlockNumber, parachain_head_at_relay_header_number: RelayBlockNumber,
best_message: MessageNonce, best_message: MessageNonce,
...@@ -949,8 +1047,12 @@ mod tests { ...@@ -949,8 +1047,12 @@ mod tests {
StoredAuthoritySet::try_new(authorities, TEST_GRANDPA_SET_ID).unwrap(), StoredAuthoritySet::try_new(authorities, TEST_GRANDPA_SET_ID).unwrap(),
); );
pallet_bridge_grandpa::BestFinalized::<TestRuntime>::put(best_relay_header); 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 { let para_info = ParaInfo {
best_head_hash: BestParaHeadHash { best_head_hash: BestParaHeadHash {
at_relay_block_number: parachain_head_at_relay_header_number, at_relay_block_number: parachain_head_at_relay_header_number,
...@@ -994,7 +1096,7 @@ mod tests { ...@@ -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( let relay_header = BridgedChainHeader::new(
relay_header_number, relay_header_number,
Default::default(), Default::default(),
...@@ -1008,6 +1110,7 @@ mod tests { ...@@ -1008,6 +1110,7 @@ mod tests {
finality_target: Box::new(relay_header), finality_target: Box::new(relay_header),
justification: relay_justification, justification: relay_justification,
current_set_id: TEST_GRANDPA_SET_ID, current_set_id: TEST_GRANDPA_SET_ID,
is_free_execution_expected: false,
}) })
} }
...@@ -1017,13 +1120,27 @@ mod tests { ...@@ -1017,13 +1120,27 @@ mod tests {
RuntimeCall::BridgeParachains(ParachainsCall::submit_parachain_heads { RuntimeCall::BridgeParachains(ParachainsCall::submit_parachain_heads {
at_relay_block: (parachain_head_at_relay_header_number, RelayBlockHash::default()), at_relay_block: (parachain_head_at_relay_header_number, RelayBlockHash::default()),
parachains: vec![( parachains: vec![(
ParaId(TestParachain::get()), ParaId(BridgedUnderlyingParachain::PARACHAIN_ID),
[parachain_head_at_relay_header_number as u8; 32].into(), [parachain_head_at_relay_header_number as u8; 32].into(),
)], )],
parachain_heads_proof: ParaHeadsProof { storage_proof: vec![] }, 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 { fn message_delivery_call(best_message: MessageNonce) -> RuntimeCall {
RuntimeCall::BridgeMessages(MessagesCall::receive_messages_proof { RuntimeCall::BridgeMessages(MessagesCall::receive_messages_proof {
relayer_id_at_bridged_chain: relayer_account_at_bridged_chain(), relayer_id_at_bridged_chain: relayer_account_at_bridged_chain(),
...@@ -1151,7 +1268,7 @@ mod tests { ...@@ -1151,7 +1268,7 @@ mod tests {
RuntimeCall::Utility(UtilityCall::batch_all { RuntimeCall::Utility(UtilityCall::batch_all {
calls: vec![ calls: vec![
submit_relay_header_call_ex(relay_header_number), 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), message_delivery_call(best_message),
], ],
}) })
...@@ -1179,7 +1296,7 @@ mod tests { ...@@ -1179,7 +1296,7 @@ mod tests {
RuntimeCall::Utility(UtilityCall::batch_all { RuntimeCall::Utility(UtilityCall::batch_all {
calls: vec![ calls: vec![
submit_relay_header_call_ex(relay_header_number), 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), message_confirmation_call(best_message),
], ],
}) })
...@@ -1194,11 +1311,14 @@ mod tests { ...@@ -1194,11 +1311,14 @@ mod tests {
current_set_id: None, current_set_id: None,
extra_weight: Weight::zero(), extra_weight: Weight::zero(),
extra_size: 0, extra_size: 0,
is_mandatory: false,
is_free_execution_expected: false,
}, },
SubmitParachainHeadsInfo { SubmitParachainHeadsInfo {
at_relay_block_number: 200, at_relay_block: HeaderId(200, [0u8; 32].into()),
para_id: ParaId(TestParachain::get()), para_id: ParaId(BridgedUnderlyingParachain::PARACHAIN_ID),
para_head_hash: [200u8; 32].into(), para_head_hash: [200u8; 32].into(),
is_free_execution_expected: false,
}, },
MessagesCallInfo::ReceiveMessagesProof(ReceiveMessagesProofInfo { MessagesCallInfo::ReceiveMessagesProof(ReceiveMessagesProofInfo {
base: BaseMessagesProofInfo { base: BaseMessagesProofInfo {
...@@ -1231,11 +1351,14 @@ mod tests { ...@@ -1231,11 +1351,14 @@ mod tests {
current_set_id: None, current_set_id: None,
extra_weight: Weight::zero(), extra_weight: Weight::zero(),
extra_size: 0, extra_size: 0,
is_mandatory: false,
is_free_execution_expected: false,
}, },
SubmitParachainHeadsInfo { SubmitParachainHeadsInfo {
at_relay_block_number: 200, at_relay_block: HeaderId(200, [0u8; 32].into()),
para_id: ParaId(TestParachain::get()), para_id: ParaId(BridgedUnderlyingParachain::PARACHAIN_ID),
para_head_hash: [200u8; 32].into(), para_head_hash: [200u8; 32].into(),
is_free_execution_expected: false,
}, },
MessagesCallInfo::ReceiveMessagesDeliveryProof(ReceiveMessagesDeliveryProofInfo( MessagesCallInfo::ReceiveMessagesDeliveryProof(ReceiveMessagesDeliveryProofInfo(
BaseMessagesProofInfo { BaseMessagesProofInfo {
...@@ -1264,6 +1387,8 @@ mod tests { ...@@ -1264,6 +1387,8 @@ mod tests {
current_set_id: None, current_set_id: None,
extra_weight: Weight::zero(), extra_weight: Weight::zero(),
extra_size: 0, extra_size: 0,
is_mandatory: false,
is_free_execution_expected: false,
}, },
MessagesCallInfo::ReceiveMessagesProof(ReceiveMessagesProofInfo { MessagesCallInfo::ReceiveMessagesProof(ReceiveMessagesProofInfo {
base: BaseMessagesProofInfo { base: BaseMessagesProofInfo {
...@@ -1296,6 +1421,8 @@ mod tests { ...@@ -1296,6 +1421,8 @@ mod tests {
current_set_id: None, current_set_id: None,
extra_weight: Weight::zero(), extra_weight: Weight::zero(),
extra_size: 0, extra_size: 0,
is_mandatory: false,
is_free_execution_expected: false,
}, },
MessagesCallInfo::ReceiveMessagesDeliveryProof(ReceiveMessagesDeliveryProofInfo( MessagesCallInfo::ReceiveMessagesDeliveryProof(ReceiveMessagesDeliveryProofInfo(
BaseMessagesProofInfo { BaseMessagesProofInfo {
...@@ -1320,9 +1447,10 @@ mod tests { ...@@ -1320,9 +1447,10 @@ mod tests {
relayer: relayer_account_at_this_chain(), relayer: relayer_account_at_this_chain(),
call_info: CallInfo::ParachainFinalityAndMsgs( call_info: CallInfo::ParachainFinalityAndMsgs(
SubmitParachainHeadsInfo { SubmitParachainHeadsInfo {
at_relay_block_number: 200, at_relay_block: HeaderId(200, [0u8; 32].into()),
para_id: ParaId(TestParachain::get()), para_id: ParaId(BridgedUnderlyingParachain::PARACHAIN_ID),
para_head_hash: [200u8; 32].into(), para_head_hash: [200u8; 32].into(),
is_free_execution_expected: false,
}, },
MessagesCallInfo::ReceiveMessagesProof(ReceiveMessagesProofInfo { MessagesCallInfo::ReceiveMessagesProof(ReceiveMessagesProofInfo {
base: BaseMessagesProofInfo { base: BaseMessagesProofInfo {
...@@ -1344,9 +1472,10 @@ mod tests { ...@@ -1344,9 +1472,10 @@ mod tests {
relayer: relayer_account_at_this_chain(), relayer: relayer_account_at_this_chain(),
call_info: CallInfo::ParachainFinalityAndMsgs( call_info: CallInfo::ParachainFinalityAndMsgs(
SubmitParachainHeadsInfo { SubmitParachainHeadsInfo {
at_relay_block_number: 200, at_relay_block: HeaderId(200, [0u8; 32].into()),
para_id: ParaId(TestParachain::get()), para_id: ParaId(BridgedUnderlyingParachain::PARACHAIN_ID),
para_head_hash: [200u8; 32].into(), para_head_hash: [200u8; 32].into(),
is_free_execution_expected: false,
}, },
MessagesCallInfo::ReceiveMessagesDeliveryProof(ReceiveMessagesDeliveryProofInfo( MessagesCallInfo::ReceiveMessagesDeliveryProof(ReceiveMessagesDeliveryProofInfo(
BaseMessagesProofInfo { BaseMessagesProofInfo {
...@@ -1421,8 +1550,14 @@ mod tests { ...@@ -1421,8 +1550,14 @@ mod tests {
extension.validate(&relayer_account_at_this_chain(), &call, &DispatchInfo::default(), 0) extension.validate(&relayer_account_at_this_chain(), &call, &DispatchInfo::default(), 0)
} }
fn run_validate_ignore_priority(call: RuntimeCall) -> TransactionValidity { fn run_messages_validate(call: RuntimeCall) -> TransactionValidity {
run_validate(call).map(|mut tx| { 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.priority = 0;
tx tx
}) })
...@@ -1444,6 +1579,14 @@ mod tests { ...@@ -1444,6 +1579,14 @@ mod tests {
extension.pre_dispatch(&relayer_account_at_this_chain(), &call, &DispatchInfo::default(), 0) 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 { fn dispatch_info() -> DispatchInfo {
DispatchInfo { DispatchInfo {
weight: Weight::from_parts( weight: Weight::from_parts(
...@@ -1502,40 +1645,48 @@ mod tests { ...@@ -1502,40 +1645,48 @@ mod tests {
Balances::set_balance(&relayer_account_at_this_chain(), ExistentialDeposit::get()); Balances::set_balance(&relayer_account_at_this_chain(), ExistentialDeposit::get());
// message delivery is failing // message delivery is failing
assert_eq!(run_validate(message_delivery_call(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!( assert_eq!(
run_validate(parachain_finality_and_delivery_batch_call(200, 200)), f(parachain_finality_and_delivery_batch_call(200, 200)),
Ok(Default::default()), Ok(Default::default()),
); );
assert_eq!( assert_eq!(
run_validate(all_finality_and_delivery_batch_call(200, 200, 200)), f(all_finality_and_delivery_batch_call(200, 200, 200)),
Ok(Default::default()), Ok(Default::default()),
); );
assert_eq!( assert_eq!(
run_validate(all_finality_and_delivery_batch_call_ex(200, 200, 200)), f(all_finality_and_delivery_batch_call_ex(200, 200, 200)),
Ok(Default::default()), Ok(Default::default()),
); );
}
// message confirmation validation is passing // message confirmation validation is passing
assert_eq!( assert_eq!(
run_validate_ignore_priority(message_confirmation_call(200)), ignore_priority(run_validate(message_confirmation_call(200))),
Ok(Default::default()), Ok(Default::default()),
); );
assert_eq!( 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 200, 200
)), ))),
Ok(Default::default()), Ok(Default::default()),
); );
assert_eq!( 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 200, 200, 200
)), ))),
Ok(Default::default()), Ok(Default::default()),
); );
assert_eq!( 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 200, 200, 200
)), ))),
Ok(Default::default()), Ok(Default::default()),
); );
}); });
...@@ -1549,10 +1700,12 @@ mod tests { ...@@ -1549,10 +1700,12 @@ mod tests {
BridgeRelayers::register(RuntimeOrigin::signed(relayer_account_at_this_chain()), 1000) BridgeRelayers::register(RuntimeOrigin::signed(relayer_account_at_this_chain()), 1000)
.unwrap(); .unwrap();
let fns = [run_validate, run_grandpa_validate, run_messages_validate];
for f in fns {
let priority_of_100_messages_delivery = let priority_of_100_messages_delivery =
run_validate(message_delivery_call(200)).unwrap().priority; f(message_delivery_call(200)).unwrap().priority;
let priority_of_200_messages_delivery = let priority_of_200_messages_delivery =
run_validate(message_delivery_call(300)).unwrap().priority; f(message_delivery_call(300)).unwrap().priority;
assert!( assert!(
priority_of_200_messages_delivery > priority_of_100_messages_delivery, priority_of_200_messages_delivery > priority_of_100_messages_delivery,
"Invalid priorities: {} for 200 messages vs {} for 100 messages", "Invalid priorities: {} for 200 messages vs {} for 100 messages",
...@@ -1561,13 +1714,14 @@ mod tests { ...@@ -1561,13 +1714,14 @@ mod tests {
); );
let priority_of_100_messages_confirmation = let priority_of_100_messages_confirmation =
run_validate(message_confirmation_call(200)).unwrap().priority; f(message_confirmation_call(200)).unwrap().priority;
let priority_of_200_messages_confirmation = let priority_of_200_messages_confirmation =
run_validate(message_confirmation_call(300)).unwrap().priority; f(message_confirmation_call(300)).unwrap().priority;
assert_eq!( assert_eq!(
priority_of_100_messages_confirmation, priority_of_100_messages_confirmation,
priority_of_200_messages_confirmation priority_of_200_messages_confirmation
); );
}
}); });
} }
...@@ -1579,14 +1733,14 @@ mod tests { ...@@ -1579,14 +1733,14 @@ mod tests {
BridgeRelayers::register(RuntimeOrigin::signed(relayer_account_at_this_chain()), 1000) BridgeRelayers::register(RuntimeOrigin::signed(relayer_account_at_this_chain()), 1000)
.unwrap(); .unwrap();
let priority_of_max_messages_delivery = run_validate(message_delivery_call( let fns = [run_validate, run_grandpa_validate, run_messages_validate];
100 + MaxUnconfirmedMessagesAtInboundLane::get(), for f in fns {
)) let priority_of_max_messages_delivery =
f(message_delivery_call(100 + MaxUnconfirmedMessagesAtInboundLane::get()))
.unwrap() .unwrap()
.priority; .priority;
let priority_of_more_than_max_messages_delivery = run_validate(message_delivery_call( let priority_of_more_than_max_messages_delivery =
100 + MaxUnconfirmedMessagesAtInboundLane::get() + 1, f(message_delivery_call(100 + MaxUnconfirmedMessagesAtInboundLane::get() + 1))
))
.unwrap() .unwrap()
.priority; .priority;
...@@ -1596,6 +1750,7 @@ mod tests { ...@@ -1596,6 +1750,7 @@ mod tests {
priority_of_max_messages_delivery, priority_of_max_messages_delivery,
priority_of_more_than_max_messages_delivery, priority_of_more_than_max_messages_delivery,
); );
}
}); });
} }
...@@ -1605,45 +1760,54 @@ mod tests { ...@@ -1605,45 +1760,54 @@ mod tests {
initialize_environment(100, 100, 100); initialize_environment(100, 100, 100);
assert_eq!( assert_eq!(
run_validate_ignore_priority(message_delivery_call(200)), ignore_priority(run_validate(message_delivery_call(200))),
Ok(ValidTransaction::default()), Ok(ValidTransaction::default()),
); );
assert_eq!( assert_eq!(
run_validate_ignore_priority(message_confirmation_call(200)), ignore_priority(run_validate(message_confirmation_call(200))),
Ok(ValidTransaction::default()), Ok(ValidTransaction::default()),
); );
assert_eq!( 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()), Ok(ValidTransaction::default()),
); );
assert_eq!( 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 200, 200
)), ))),
Ok(ValidTransaction::default()), Ok(ValidTransaction::default()),
); );
assert_eq!( 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()), Ok(ValidTransaction::default()),
); );
assert_eq!( 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 200, 200, 200
)), ))),
Ok(ValidTransaction::default()), Ok(ValidTransaction::default()),
); );
assert_eq!( 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 200, 200, 200
)), ))),
Ok(ValidTransaction::default()), Ok(ValidTransaction::default()),
); );
assert_eq!( 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 200, 200, 200
)), ))),
Ok(ValidTransaction::default()), Ok(ValidTransaction::default()),
); );
}); });
...@@ -1933,8 +2097,11 @@ mod tests { ...@@ -1933,8 +2097,11 @@ mod tests {
RuntimeCall::BridgeParachains(ParachainsCall::submit_parachain_heads { RuntimeCall::BridgeParachains(ParachainsCall::submit_parachain_heads {
at_relay_block: (100, RelayBlockHash::default()), at_relay_block: (100, RelayBlockHash::default()),
parachains: vec![ parachains: vec![
(ParaId(TestParachain::get()), [1u8; 32].into()), (ParaId(BridgedUnderlyingParachain::PARACHAIN_ID), [1u8; 32].into()),
(ParaId(TestParachain::get() + 1), [1u8; 32].into()), (
ParaId(BridgedUnderlyingParachain::PARACHAIN_ID + 1),
[1u8; 32].into(),
),
], ],
parachain_heads_proof: ParaHeadsProof { storage_proof: vec![] }, parachain_heads_proof: ParaHeadsProof { storage_proof: vec![] },
}), }),
...@@ -2318,6 +2485,148 @@ mod tests { ...@@ -2318,6 +2485,148 @@ mod tests {
}); });
} }
#[test]
fn messages_ext_only_parses_standalone_transactions() {
run_test(|| {
initialize_environment(100, 100, 100);
// relay + parachain + message delivery calls batch is ignored
assert_eq!(
TestMessagesExtensionProvider::parse_and_check_for_obsolete_call(
&all_finality_and_delivery_batch_call(200, 200, 200)
),
Ok(None),
);
assert_eq!(
TestMessagesExtensionProvider::parse_and_check_for_obsolete_call(
&all_finality_and_delivery_batch_call_ex(200, 200, 200)
),
Ok(None),
);
// relay + parachain + message confirmation calls batch is ignored
assert_eq!(
TestMessagesExtensionProvider::parse_and_check_for_obsolete_call(
&all_finality_and_confirmation_batch_call(200, 200, 200)
),
Ok(None),
);
assert_eq!(
TestMessagesExtensionProvider::parse_and_check_for_obsolete_call(
&all_finality_and_confirmation_batch_call_ex(200, 200, 200)
),
Ok(None),
);
// parachain + message delivery call batch is ignored
assert_eq!(
TestMessagesExtensionProvider::parse_and_check_for_obsolete_call(
&parachain_finality_and_delivery_batch_call(200, 200)
),
Ok(None),
);
// parachain + message confirmation call batch is ignored
assert_eq!(
TestMessagesExtensionProvider::parse_and_check_for_obsolete_call(
&parachain_finality_and_confirmation_batch_call(200, 200)
),
Ok(None),
);
// relay + message delivery call batch is ignored
assert_eq!(
TestMessagesExtensionProvider::parse_and_check_for_obsolete_call(
&relay_finality_and_delivery_batch_call(200, 200)
),
Ok(None),
);
assert_eq!(
TestMessagesExtensionProvider::parse_and_check_for_obsolete_call(
&relay_finality_and_delivery_batch_call_ex(200, 200)
),
Ok(None),
);
// relay + message confirmation call batch is ignored
assert_eq!(
TestMessagesExtensionProvider::parse_and_check_for_obsolete_call(
&relay_finality_and_confirmation_batch_call(200, 200)
),
Ok(None),
);
assert_eq!(
TestMessagesExtensionProvider::parse_and_check_for_obsolete_call(
&relay_finality_and_confirmation_batch_call_ex(200, 200)
),
Ok(None),
);
// message delivery call batch is accepted
assert_eq!(
TestMessagesExtensionProvider::parse_and_check_for_obsolete_call(
&message_delivery_call(200)
),
Ok(Some(delivery_pre_dispatch_data().call_info)),
);
// message confirmation call batch is accepted
assert_eq!(
TestMessagesExtensionProvider::parse_and_check_for_obsolete_call(
&message_confirmation_call(200)
),
Ok(Some(confirmation_pre_dispatch_data().call_info)),
);
});
}
#[test]
fn messages_ext_rejects_calls_with_obsolete_messages() {
run_test(|| {
initialize_environment(100, 100, 100);
assert_eq!(
run_messages_pre_dispatch(message_delivery_call(100)),
Err(TransactionValidityError::Invalid(InvalidTransaction::Stale)),
);
assert_eq!(
run_messages_pre_dispatch(message_confirmation_call(100)),
Err(TransactionValidityError::Invalid(InvalidTransaction::Stale)),
);
assert_eq!(
run_messages_validate(message_delivery_call(100)),
Err(TransactionValidityError::Invalid(InvalidTransaction::Stale)),
);
assert_eq!(
run_messages_validate(message_confirmation_call(100)),
Err(TransactionValidityError::Invalid(InvalidTransaction::Stale)),
);
});
}
#[test]
fn messages_ext_accepts_calls_with_new_messages() {
run_test(|| {
initialize_environment(100, 100, 100);
assert_eq!(
run_messages_pre_dispatch(message_delivery_call(200)),
Ok(Some(delivery_pre_dispatch_data())),
);
assert_eq!(
run_messages_pre_dispatch(message_confirmation_call(200)),
Ok(Some(confirmation_pre_dispatch_data())),
);
assert_eq!(run_messages_validate(message_delivery_call(200)), Ok(Default::default()),);
assert_eq!(
run_messages_validate(message_confirmation_call(200)),
Ok(Default::default()),
);
});
}
#[test] #[test]
fn grandpa_ext_only_parses_valid_batches() { fn grandpa_ext_only_parses_valid_batches() {
run_test(|| { run_test(|| {
......
...@@ -183,7 +183,8 @@ impl pallet_transaction_payment::Config for TestRuntime { ...@@ -183,7 +183,8 @@ impl pallet_transaction_payment::Config for TestRuntime {
impl pallet_bridge_grandpa::Config for TestRuntime { impl pallet_bridge_grandpa::Config for TestRuntime {
type RuntimeEvent = RuntimeEvent; type RuntimeEvent = RuntimeEvent;
type BridgedChain = BridgedUnderlyingChain; type BridgedChain = BridgedUnderlyingChain;
type MaxFreeMandatoryHeadersPerBlock = ConstU32<4>; type MaxFreeHeadersPerBlock = ConstU32<4>;
type FreeHeadersInterval = ConstU32<1_024>;
type HeadersToKeep = ConstU32<8>; type HeadersToKeep = ConstU32<8>;
type WeightInfo = pallet_bridge_grandpa::weights::BridgeWeight<TestRuntime>; type WeightInfo = pallet_bridge_grandpa::weights::BridgeWeight<TestRuntime>;
} }
...@@ -406,6 +407,7 @@ impl Chain for BridgedUnderlyingParachain { ...@@ -406,6 +407,7 @@ impl Chain for BridgedUnderlyingParachain {
impl Parachain for BridgedUnderlyingParachain { impl Parachain for BridgedUnderlyingParachain {
const PARACHAIN_ID: u32 = 42; const PARACHAIN_ID: u32 = 42;
const MAX_HEADER_SIZE: u32 = 1_024;
} }
/// The other, bridged chain, used in tests. /// The other, bridged chain, used in tests.
......
...@@ -39,6 +39,9 @@ use frame_support::{ ...@@ -39,6 +39,9 @@ use frame_support::{
use frame_system::limits; use frame_system::limits;
use sp_std::time::Duration; 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. /// Average block interval in Cumulus-based parachains.
/// ///
/// Corresponds to the `MILLISECS_PER_BLOCK` from `parachains_common` crate. /// Corresponds to the `MILLISECS_PER_BLOCK` from `parachains_common` crate.
......
...@@ -62,6 +62,7 @@ impl Chain for BridgeHubKusama { ...@@ -62,6 +62,7 @@ impl Chain for BridgeHubKusama {
impl Parachain for BridgeHubKusama { impl Parachain for BridgeHubKusama {
const PARACHAIN_ID: u32 = BRIDGE_HUB_KUSAMA_PARACHAIN_ID; const PARACHAIN_ID: u32 = BRIDGE_HUB_KUSAMA_PARACHAIN_ID;
const MAX_HEADER_SIZE: u32 = MAX_BRIDGE_HUB_HEADER_SIZE;
} }
impl ChainWithMessages for BridgeHubKusama { impl ChainWithMessages for BridgeHubKusama {
......
...@@ -59,6 +59,7 @@ impl Chain for BridgeHubPolkadot { ...@@ -59,6 +59,7 @@ impl Chain for BridgeHubPolkadot {
impl Parachain for BridgeHubPolkadot { impl Parachain for BridgeHubPolkadot {
const PARACHAIN_ID: u32 = BRIDGE_HUB_POLKADOT_PARACHAIN_ID; const PARACHAIN_ID: u32 = BRIDGE_HUB_POLKADOT_PARACHAIN_ID;
const MAX_HEADER_SIZE: u32 = MAX_BRIDGE_HUB_HEADER_SIZE;
} }
impl ChainWithMessages for BridgeHubPolkadot { impl ChainWithMessages for BridgeHubPolkadot {
......
...@@ -59,6 +59,7 @@ impl Chain for BridgeHubRococo { ...@@ -59,6 +59,7 @@ impl Chain for BridgeHubRococo {
impl Parachain for BridgeHubRococo { impl Parachain for BridgeHubRococo {
const PARACHAIN_ID: u32 = BRIDGE_HUB_ROCOCO_PARACHAIN_ID; const PARACHAIN_ID: u32 = BRIDGE_HUB_ROCOCO_PARACHAIN_ID;
const MAX_HEADER_SIZE: u32 = MAX_BRIDGE_HUB_HEADER_SIZE;
} }
impl ChainWithMessages for BridgeHubRococo { impl ChainWithMessages for BridgeHubRococo {
...@@ -103,9 +104,9 @@ frame_support::parameter_types! { ...@@ -103,9 +104,9 @@ frame_support::parameter_types! {
/// Transaction fee that is paid at the Rococo BridgeHub for delivering single inbound message. /// 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%`) /// (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. /// 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%`) /// (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 { ...@@ -58,6 +58,7 @@ impl Chain for BridgeHubWestend {
impl Parachain for BridgeHubWestend { impl Parachain for BridgeHubWestend {
const PARACHAIN_ID: u32 = BRIDGE_HUB_WESTEND_PARACHAIN_ID; const PARACHAIN_ID: u32 = BRIDGE_HUB_WESTEND_PARACHAIN_ID;
const MAX_HEADER_SIZE: u32 = MAX_BRIDGE_HUB_HEADER_SIZE;
} }
impl ChainWithMessages for BridgeHubWestend { impl ChainWithMessages for BridgeHubWestend {
...@@ -93,10 +94,10 @@ frame_support::parameter_types! { ...@@ -93,10 +94,10 @@ frame_support::parameter_types! {
pub const BridgeHubWestendBaseXcmFeeInWnds: u128 = 17_756_830_000; pub const BridgeHubWestendBaseXcmFeeInWnds: u128 = 17_756_830_000;
/// Transaction fee that is paid at the Westend BridgeHub for delivering single inbound message. /// 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%`) /// (initially was calculated by test `BridgeHubWestend::can_calculate_fee_for_standalone_message_delivery_transaction` + `33%`)
pub const BridgeHubWestendBaseDeliveryFeeInWnds: u128 = 1_695_489_961_344; pub const BridgeHubWestendBaseDeliveryFeeInWnds: u128 = 94_211_536_452;
/// Transaction fee that is paid at the Westend BridgeHub for delivering single outbound message confirmation. /// 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%`) /// (initially was calculated by test `BridgeHubWestend::can_calculate_fee_for_standalone_message_confirmation_transaction` + `33%`)
pub const BridgeHubWestendBaseConfirmationFeeInWnds: u128 = 1_618_309_961_344; pub const BridgeHubWestendBaseConfirmationFeeInWnds: u128 = 17_224_486_452;
} }
...@@ -67,6 +67,8 @@ pub const PARAS_PALLET_NAME: &str = "Paras"; ...@@ -67,6 +67,8 @@ pub const PARAS_PALLET_NAME: &str = "Paras";
/// Name of the With-Kusama GRANDPA pallet instance that is deployed at bridged chains. /// Name of the With-Kusama GRANDPA pallet instance that is deployed at bridged chains.
pub const WITH_KUSAMA_GRANDPA_PALLET_NAME: &str = "BridgeKusamaGrandpa"; pub const WITH_KUSAMA_GRANDPA_PALLET_NAME: &str = "BridgeKusamaGrandpa";
/// Name of the With-Kusama parachains pallet instance that is deployed at bridged chains.
pub const WITH_KUSAMA_BRIDGE_PARACHAINS_PALLET_NAME: &str = "BridgeKusamaParachains";
/// Maximal size of encoded `bp_parachains::ParaStoredHeaderData` structure among all Polkadot /// Maximal size of encoded `bp_parachains::ParaStoredHeaderData` structure among all Polkadot
/// parachains. /// parachains.
......
...@@ -69,6 +69,8 @@ pub const PARAS_PALLET_NAME: &str = "Paras"; ...@@ -69,6 +69,8 @@ pub const PARAS_PALLET_NAME: &str = "Paras";
/// Name of the With-Polkadot GRANDPA pallet instance that is deployed at bridged chains. /// Name of the With-Polkadot GRANDPA pallet instance that is deployed at bridged chains.
pub const WITH_POLKADOT_GRANDPA_PALLET_NAME: &str = "BridgePolkadotGrandpa"; pub const WITH_POLKADOT_GRANDPA_PALLET_NAME: &str = "BridgePolkadotGrandpa";
/// Name of the With-Polkadot parachains pallet instance that is deployed at bridged chains.
pub const WITH_POLKADOT_BRIDGE_PARACHAINS_PALLET_NAME: &str = "BridgePolkadotParachains";
/// Maximal size of encoded `bp_parachains::ParaStoredHeaderData` structure among all Polkadot /// Maximal size of encoded `bp_parachains::ParaStoredHeaderData` structure among all Polkadot
/// parachains. /// parachains.
......