mirror of
https://github.com/pezkuwichain/pezkuwi-subxt.git
synced 2026-05-30 22:11:02 +00:00
575b8f8d15
## Summary This pull request proposes a solution for improved control of the versioned XCM flow over the bridge (across different consensus chains) and resolves the situation where the sending chain/consensus has already migrated to a higher XCM version than the receiving chain/consensus. ## Problem/Motivation The current flow over the bridge involves a transfer from AssetHubRococo (AHR) to BridgeHubRococo (BHR) to BridgeHubWestend (BHW) and finally to AssetHubWestend (AHW), beginning with a reserve-backed transfer on AHR. In this process: 1. AHR sends XCM `ExportMessage` through `XcmpQueue`, incorporating XCM version checks using the `WrapVersion` feature, influenced by `pallet_xcm::SupportedVersion` (managed by `pallet_xcm::force_xcm_version` or version discovery). 2. BHR handles the `ExportMessage` instruction, utilizing the latest XCM version. The `HaulBlobExporter` converts the inner XCM to [`VersionedXcm::from`](https://github.com/paritytech/polkadot-sdk/blob/63ac2471aa0210f0ac9903bdd7d8f9351f9a635f/polkadot/xcm/xcm-builder/src/universal_exports.rs#L465-L467), also using the latest XCM version. However, challenges arise: - Incompatibility when BHW uses a different version than BHR. For instance, if BHR migrates to **XCMv4** while BHW remains on **XCMv3**, BHR's `VersionedXcm::from` uses `VersionedXcm::V4` variant, causing encoding issues for BHW. ``` /// Just a simulation of possible error, which could happen on BHW /// (this code is based on actual master without XCMv4) let encoded = hex_literal::hex!("0400"); println!("{:?}", VersionedXcm::<()>::decode(&mut &encoded[..])); Err(Error { cause: None, desc: "Could not decode `VersionedXcm`, variant doesn't exist" }) ``` - Similar compatibility issues exist between AHR and AHW. ## Solution This pull request introduces the following solutions: 1. **New trait `CheckVersion`** - added to the `xcm` module and exposing `pallet_xcm::SupportedVersion`. This enhancement allows checking the actual XCM version for desired destinations outside of the `pallet_xcm` module. 2. **Version Check in `HaulBlobExporter`** uses `CheckVersion` to check known/configured destination versions, ensuring compatibility. For example, in the scenario mentioned, BHR can store the version `3` for BHW. If BHR is on XCMv4, it will attempt to downgrade the message to version `3` instead of using the latest version `4`. 3. **Version Check in `pallet-xcm-bridge-hub-router`** - this check ensures compatibility with the real destination's XCM version, preventing the unnecessary sending of messages to the local bridge hub if versions are incompatible. These additions aim to improve the control and compatibility of XCM flows over the bridge and addressing issues related to version mismatches. ## Possible alternative solution _(More investigation is needed, and at the very least, it should extend to XCMv4/5. If this proves to be a viable option, I can open an RFC for XCM.)._ Add the `XcmVersion` attribute to the `ExportMessage` so that the sending chain can determine, based on what is stored in `pallet_xcm::SupportedVersion`, the version the destination is using. This way, we may not need to handle the version in `HaulBlobExporter`. ``` ExportMessage { network: NetworkId, destination: InteriorMultiLocation, xcm: Xcm<()> destination_xcm_version: Version, // <- new attritbute }, ``` ``` pub trait ExportXcm { fn validate( network: NetworkId, channel: u32, universal_source: &mut Option<InteriorMultiLocation>, destination: &mut Option<InteriorMultiLocation>, message: &mut Option<Xcm<()>>, destination_xcm_version: Version, , // <- new attritbute ) -> SendResult<Self::Ticket>; ``` ## Future Directions This PR does not fix version discovery over bridge, further investigation will be conducted here: https://github.com/paritytech/polkadot-sdk/issues/2417. ## TODO - [x] `pallet_xcm` mock for tests uses hard-coded XCM version `2` - change to 3 or lastest? - [x] fix `pallet-xcm-bridge-hub-router` - [x] fix HaulBlobExporter with version determination [here](https://github.com/paritytech/polkadot-sdk/blob/2183669d05f9b510f979a0cc3c7847707bacba2e/polkadot/xcm/xcm-builder/src/universal_exports.rs#L465) - [x] add unit-tests to the runtimes - [x] run benchmarks for `ExportMessage` - [x] extend local run scripts about `force_xcm_version(dest, version)` - [ ] when merged, prepare governance calls for Rococo/Westend - [ ] add PRDoc Part of: https://github.com/paritytech/parity-bridges-common/issues/2719 --------- Co-authored-by: command-bot <>
334 lines
11 KiB
Rust
334 lines
11 KiB
Rust
// Copyright (C) Parity Technologies (UK) Ltd.
|
|
// This file is part of Cumulus.
|
|
|
|
// Cumulus is free software: you can redistribute it and/or modify
|
|
// it under the terms of the GNU General Public License as published by
|
|
// the Free Software Foundation, either version 3 of the License, or
|
|
// (at your option) any later version.
|
|
|
|
// Cumulus is distributed in the hope that it will be useful,
|
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
// GNU General Public License for more details.
|
|
|
|
// You should have received a copy of the GNU General Public License
|
|
// along with Cumulus. If not, see <http://www.gnu.org/licenses/>.
|
|
|
|
#![cfg(test)]
|
|
|
|
use bp_polkadot_core::Signature;
|
|
use bridge_common_config::{DeliveryRewardInBalance, RequiredStakeForStakeAndSlash};
|
|
use bridge_hub_westend_runtime::{
|
|
bridge_common_config, bridge_to_rococo_config,
|
|
xcm_config::{RelayNetwork, WestendLocation, XcmConfig},
|
|
AllPalletsWithoutSystem, BridgeRejectObsoleteHeadersAndMessages, Executive, ExistentialDeposit,
|
|
ParachainSystem, PolkadotXcm, Runtime, RuntimeCall, RuntimeEvent, RuntimeOrigin, SessionKeys,
|
|
SignedExtra, TransactionPayment, UncheckedExtrinsic,
|
|
};
|
|
use bridge_to_rococo_config::{
|
|
BridgeGrandpaRococoInstance, BridgeHubRococoChainId, BridgeHubRococoLocation,
|
|
BridgeParachainRococoInstance, WithBridgeHubRococoMessageBridge,
|
|
WithBridgeHubRococoMessagesInstance, XCM_LANE_FOR_ASSET_HUB_WESTEND_TO_ASSET_HUB_ROCOCO,
|
|
};
|
|
use codec::{Decode, Encode};
|
|
use frame_support::{dispatch::GetDispatchInfo, parameter_types, traits::ConstU8};
|
|
use frame_system::pallet_prelude::HeaderFor;
|
|
use parachains_common::{westend::fee::WeightToFee, AccountId, AuraId, Balance};
|
|
use sp_keyring::AccountKeyring::Alice;
|
|
use sp_runtime::{
|
|
generic::{Era, SignedPayload},
|
|
AccountId32,
|
|
};
|
|
use xcm::latest::prelude::*;
|
|
|
|
// Para id of sibling chain used in tests.
|
|
pub const SIBLING_PARACHAIN_ID: u32 = 1000;
|
|
|
|
parameter_types! {
|
|
pub CheckingAccount: AccountId = PolkadotXcm::check_account();
|
|
}
|
|
|
|
fn construct_extrinsic(
|
|
sender: sp_keyring::AccountKeyring,
|
|
call: RuntimeCall,
|
|
) -> UncheckedExtrinsic {
|
|
let extra: SignedExtra = (
|
|
frame_system::CheckNonZeroSender::<Runtime>::new(),
|
|
frame_system::CheckSpecVersion::<Runtime>::new(),
|
|
frame_system::CheckTxVersion::<Runtime>::new(),
|
|
frame_system::CheckGenesis::<Runtime>::new(),
|
|
frame_system::CheckEra::<Runtime>::from(Era::immortal()),
|
|
frame_system::CheckNonce::<Runtime>::from(0),
|
|
frame_system::CheckWeight::<Runtime>::new(),
|
|
pallet_transaction_payment::ChargeTransactionPayment::<Runtime>::from(0),
|
|
BridgeRejectObsoleteHeadersAndMessages::default(),
|
|
(bridge_to_rococo_config::OnBridgeHubWestendRefundBridgeHubRococoMessages::default(),),
|
|
);
|
|
let payload = SignedPayload::new(call.clone(), extra.clone()).unwrap();
|
|
let signature = payload.using_encoded(|e| sender.sign(e));
|
|
UncheckedExtrinsic::new_signed(
|
|
call,
|
|
AccountId32::from(sender.public()).into(),
|
|
Signature::Sr25519(signature.clone()),
|
|
extra,
|
|
)
|
|
}
|
|
|
|
fn construct_and_apply_extrinsic(
|
|
relayer_at_target: sp_keyring::AccountKeyring,
|
|
batch: pallet_utility::Call<Runtime>,
|
|
) -> sp_runtime::DispatchOutcome {
|
|
let batch_call = RuntimeCall::Utility(batch);
|
|
let xt = construct_extrinsic(relayer_at_target, batch_call);
|
|
let r = Executive::apply_extrinsic(xt);
|
|
r.unwrap()
|
|
}
|
|
|
|
fn construct_and_estimate_extrinsic_fee(batch: pallet_utility::Call<Runtime>) -> Balance {
|
|
let batch_call = RuntimeCall::Utility(batch);
|
|
let batch_info = batch_call.get_dispatch_info();
|
|
let xt = construct_extrinsic(Alice, batch_call);
|
|
TransactionPayment::compute_fee(xt.encoded_size() as _, &batch_info, 0)
|
|
}
|
|
|
|
fn executive_init_block(header: &HeaderFor<Runtime>) {
|
|
Executive::initialize_block(header)
|
|
}
|
|
|
|
fn collator_session_keys() -> bridge_hub_test_utils::CollatorSessionKeys<Runtime> {
|
|
bridge_hub_test_utils::CollatorSessionKeys::new(
|
|
AccountId::from(Alice),
|
|
AccountId::from(Alice),
|
|
SessionKeys { aura: AuraId::from(Alice.public()) },
|
|
)
|
|
}
|
|
|
|
bridge_hub_test_utils::test_cases::include_teleports_for_native_asset_works!(
|
|
Runtime,
|
|
AllPalletsWithoutSystem,
|
|
XcmConfig,
|
|
CheckingAccount,
|
|
WeightToFee,
|
|
ParachainSystem,
|
|
collator_session_keys(),
|
|
ExistentialDeposit::get(),
|
|
Box::new(|runtime_event_encoded: Vec<u8>| {
|
|
match RuntimeEvent::decode(&mut &runtime_event_encoded[..]) {
|
|
Ok(RuntimeEvent::PolkadotXcm(event)) => Some(event),
|
|
_ => None,
|
|
}
|
|
}),
|
|
bp_bridge_hub_westend::BRIDGE_HUB_WESTEND_PARACHAIN_ID
|
|
);
|
|
|
|
#[test]
|
|
fn initialize_bridge_by_governance_works() {
|
|
bridge_hub_test_utils::test_cases::initialize_bridge_by_governance_works::<
|
|
Runtime,
|
|
BridgeGrandpaRococoInstance,
|
|
>(
|
|
collator_session_keys(),
|
|
bp_bridge_hub_westend::BRIDGE_HUB_WESTEND_PARACHAIN_ID,
|
|
Box::new(|call| RuntimeCall::BridgeRococoGrandpa(call).encode()),
|
|
)
|
|
}
|
|
|
|
#[test]
|
|
fn change_delivery_reward_by_governance_works() {
|
|
bridge_hub_test_utils::test_cases::change_storage_constant_by_governance_works::<
|
|
Runtime,
|
|
DeliveryRewardInBalance,
|
|
u64,
|
|
>(
|
|
collator_session_keys(),
|
|
bp_bridge_hub_westend::BRIDGE_HUB_WESTEND_PARACHAIN_ID,
|
|
Box::new(|call| RuntimeCall::System(call).encode()),
|
|
|| (DeliveryRewardInBalance::key().to_vec(), DeliveryRewardInBalance::get()),
|
|
|old_value| old_value.checked_mul(2).unwrap(),
|
|
)
|
|
}
|
|
|
|
#[test]
|
|
fn change_required_stake_by_governance_works() {
|
|
bridge_hub_test_utils::test_cases::change_storage_constant_by_governance_works::<
|
|
Runtime,
|
|
RequiredStakeForStakeAndSlash,
|
|
Balance,
|
|
>(
|
|
collator_session_keys(),
|
|
bp_bridge_hub_westend::BRIDGE_HUB_WESTEND_PARACHAIN_ID,
|
|
Box::new(|call| RuntimeCall::System(call).encode()),
|
|
|| (RequiredStakeForStakeAndSlash::key().to_vec(), RequiredStakeForStakeAndSlash::get()),
|
|
|old_value| old_value.checked_mul(2).unwrap(),
|
|
)
|
|
}
|
|
|
|
#[test]
|
|
fn handle_export_message_from_system_parachain_add_to_outbound_queue_works() {
|
|
bridge_hub_test_utils::test_cases::handle_export_message_from_system_parachain_to_outbound_queue_works::<
|
|
Runtime,
|
|
XcmConfig,
|
|
WithBridgeHubRococoMessagesInstance,
|
|
>(
|
|
collator_session_keys(),
|
|
bp_bridge_hub_westend::BRIDGE_HUB_WESTEND_PARACHAIN_ID,
|
|
SIBLING_PARACHAIN_ID,
|
|
Box::new(|runtime_event_encoded: Vec<u8>| {
|
|
match RuntimeEvent::decode(&mut &runtime_event_encoded[..]) {
|
|
Ok(RuntimeEvent::BridgeRococoMessages(event)) => Some(event),
|
|
_ => None,
|
|
}
|
|
}),
|
|
|| ExportMessage { network: Rococo, destination: X1(Parachain(bridge_to_rococo_config::AssetHubRococoParaId::get().into())), xcm: Xcm(vec![]) },
|
|
XCM_LANE_FOR_ASSET_HUB_WESTEND_TO_ASSET_HUB_ROCOCO,
|
|
Some((WestendLocation::get(), ExistentialDeposit::get()).into()),
|
|
// value should be >= than value generated by `can_calculate_weight_for_paid_export_message_with_reserve_transfer`
|
|
Some((WestendLocation::get(), bp_bridge_hub_westend::BridgeHubWestendBaseXcmFeeInWnds::get()).into()),
|
|
|| PolkadotXcm::force_xcm_version(RuntimeOrigin::root(), Box::new(BridgeHubRococoLocation::get()), XCM_VERSION).expect("version saved!"),
|
|
)
|
|
}
|
|
|
|
#[test]
|
|
fn message_dispatch_routing_works() {
|
|
bridge_hub_test_utils::test_cases::message_dispatch_routing_works::<
|
|
Runtime,
|
|
AllPalletsWithoutSystem,
|
|
XcmConfig,
|
|
ParachainSystem,
|
|
WithBridgeHubRococoMessagesInstance,
|
|
RelayNetwork,
|
|
bridge_to_rococo_config::RococoGlobalConsensusNetwork,
|
|
ConstU8<2>,
|
|
>(
|
|
collator_session_keys(),
|
|
bp_bridge_hub_westend::BRIDGE_HUB_WESTEND_PARACHAIN_ID,
|
|
SIBLING_PARACHAIN_ID,
|
|
Box::new(|runtime_event_encoded: Vec<u8>| {
|
|
match RuntimeEvent::decode(&mut &runtime_event_encoded[..]) {
|
|
Ok(RuntimeEvent::ParachainSystem(event)) => Some(event),
|
|
_ => None,
|
|
}
|
|
}),
|
|
Box::new(|runtime_event_encoded: Vec<u8>| {
|
|
match RuntimeEvent::decode(&mut &runtime_event_encoded[..]) {
|
|
Ok(RuntimeEvent::XcmpQueue(event)) => Some(event),
|
|
_ => None,
|
|
}
|
|
}),
|
|
XCM_LANE_FOR_ASSET_HUB_WESTEND_TO_ASSET_HUB_ROCOCO,
|
|
|| (),
|
|
)
|
|
}
|
|
|
|
#[test]
|
|
fn relayed_incoming_message_works() {
|
|
bridge_hub_test_utils::test_cases::relayed_incoming_message_works::<
|
|
Runtime,
|
|
AllPalletsWithoutSystem,
|
|
XcmConfig,
|
|
ParachainSystem,
|
|
BridgeGrandpaRococoInstance,
|
|
BridgeParachainRococoInstance,
|
|
WithBridgeHubRococoMessagesInstance,
|
|
WithBridgeHubRococoMessageBridge,
|
|
>(
|
|
collator_session_keys(),
|
|
bp_bridge_hub_westend::BRIDGE_HUB_WESTEND_PARACHAIN_ID,
|
|
bp_bridge_hub_rococo::BRIDGE_HUB_ROCOCO_PARACHAIN_ID,
|
|
SIBLING_PARACHAIN_ID,
|
|
Westend,
|
|
XCM_LANE_FOR_ASSET_HUB_WESTEND_TO_ASSET_HUB_ROCOCO,
|
|
|| (),
|
|
)
|
|
}
|
|
|
|
#[test]
|
|
pub fn complex_relay_extrinsic_works() {
|
|
bridge_hub_test_utils::test_cases::complex_relay_extrinsic_works::<
|
|
Runtime,
|
|
AllPalletsWithoutSystem,
|
|
XcmConfig,
|
|
ParachainSystem,
|
|
BridgeGrandpaRococoInstance,
|
|
BridgeParachainRococoInstance,
|
|
WithBridgeHubRococoMessagesInstance,
|
|
WithBridgeHubRococoMessageBridge,
|
|
>(
|
|
collator_session_keys(),
|
|
bp_bridge_hub_westend::BRIDGE_HUB_WESTEND_PARACHAIN_ID,
|
|
bp_bridge_hub_rococo::BRIDGE_HUB_ROCOCO_PARACHAIN_ID,
|
|
SIBLING_PARACHAIN_ID,
|
|
BridgeHubRococoChainId::get(),
|
|
Westend,
|
|
XCM_LANE_FOR_ASSET_HUB_WESTEND_TO_ASSET_HUB_ROCOCO,
|
|
ExistentialDeposit::get(),
|
|
executive_init_block,
|
|
construct_and_apply_extrinsic,
|
|
|| (),
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
pub fn can_calculate_weight_for_paid_export_message_with_reserve_transfer() {
|
|
let estimated = bridge_hub_test_utils::test_cases::can_calculate_weight_for_paid_export_message_with_reserve_transfer::<
|
|
Runtime,
|
|
XcmConfig,
|
|
WeightToFee,
|
|
>();
|
|
|
|
// check if estimated value is sane
|
|
let max_expected = bp_bridge_hub_westend::BridgeHubWestendBaseXcmFeeInWnds::get();
|
|
assert!(
|
|
estimated <= max_expected,
|
|
"calculated: {:?}, max_expected: {:?}, please adjust `bp_bridge_hub_westend::BridgeHubWestendBaseXcmFeeInWnds` value",
|
|
estimated,
|
|
max_expected
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
pub fn can_calculate_fee_for_complex_message_delivery_transaction() {
|
|
let estimated = bridge_hub_test_utils::test_cases::can_calculate_fee_for_complex_message_delivery_transaction::<
|
|
Runtime,
|
|
BridgeGrandpaRococoInstance,
|
|
BridgeParachainRococoInstance,
|
|
WithBridgeHubRococoMessagesInstance,
|
|
WithBridgeHubRococoMessageBridge,
|
|
>(
|
|
collator_session_keys(),
|
|
construct_and_estimate_extrinsic_fee
|
|
);
|
|
|
|
// check if estimated value is sane
|
|
let max_expected = bp_bridge_hub_westend::BridgeHubWestendBaseDeliveryFeeInWnds::get();
|
|
assert!(
|
|
estimated <= max_expected,
|
|
"calculated: {:?}, max_expected: {:?}, please adjust `bp_bridge_hub_westend::BridgeHubWestendBaseDeliveryFeeInWnds` value",
|
|
estimated,
|
|
max_expected
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
pub fn can_calculate_fee_for_complex_message_confirmation_transaction() {
|
|
let estimated = bridge_hub_test_utils::test_cases::can_calculate_fee_for_complex_message_confirmation_transaction::<
|
|
Runtime,
|
|
BridgeGrandpaRococoInstance,
|
|
BridgeParachainRococoInstance,
|
|
WithBridgeHubRococoMessagesInstance,
|
|
WithBridgeHubRococoMessageBridge,
|
|
>(
|
|
collator_session_keys(),
|
|
construct_and_estimate_extrinsic_fee
|
|
);
|
|
|
|
// check if estimated value is sane
|
|
let max_expected = bp_bridge_hub_westend::BridgeHubWestendBaseConfirmationFeeInWnds::get();
|
|
assert!(
|
|
estimated <= max_expected,
|
|
"calculated: {:?}, max_expected: {:?}, please adjust `bp_bridge_hub_westend::BridgeHubWestendBaseConfirmationFeeInWnds` value",
|
|
estimated,
|
|
max_expected
|
|
);
|
|
}
|