Files
pezkuwi-subxt/bridges/modules/xcm-bridge-hub/src/exporter.rs
T
Branislav Kontur 575b8f8d15 Ensure xcm versions over bridge (on sending chains) (#2481)
## 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 <>
2023-12-12 17:04:26 +02:00

210 lines
6.0 KiB
Rust

// Copyright 2019-2021 Parity Technologies (UK) Ltd.
// This file is part of Parity Bridges Common.
// Parity Bridges Common is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// Parity Bridges Common is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
// You should have received a copy of the GNU General Public License
// along with Parity Bridges Common. If not, see <http://www.gnu.org/licenses/>.
//! The code that allows to use the pallet (`pallet-xcm-bridge-hub`) as XCM message
//! exporter at the sending bridge hub. Internally, it just enqueues outbound blob
//! in the messages pallet queue.
//!
//! This code is executed at the source bridge hub.
use crate::{Config, Pallet, LOG_TARGET};
use bp_messages::source_chain::MessagesBridge;
use bp_xcm_bridge_hub::XcmAsPlainPayload;
use bridge_runtime_common::messages_xcm_extension::{LocalXcmQueueManager, SenderAndLane};
use pallet_bridge_messages::{Config as BridgeMessagesConfig, Pallet as BridgeMessagesPallet};
use xcm::prelude::*;
use xcm_builder::{HaulBlob, HaulBlobError, HaulBlobExporter};
use xcm_executor::traits::ExportXcm;
/// An easy way to access `HaulBlobExporter`.
pub type PalletAsHaulBlobExporter<T, I> = HaulBlobExporter<
DummyHaulBlob,
<T as Config<I>>::BridgedNetwork,
<T as Config<I>>::DestinationVersion,
<T as Config<I>>::MessageExportPrice,
>;
/// An easy way to access associated messages pallet.
type MessagesPallet<T, I> = BridgeMessagesPallet<T, <T as Config<I>>::BridgeMessagesPalletInstance>;
impl<T: Config<I>, I: 'static> ExportXcm for Pallet<T, I>
where
T: BridgeMessagesConfig<
<T as Config<I>>::BridgeMessagesPalletInstance,
OutboundPayload = XcmAsPlainPayload,
>,
{
type Ticket = (SenderAndLane, XcmAsPlainPayload, XcmHash);
fn validate(
network: NetworkId,
channel: u32,
universal_source: &mut Option<InteriorMultiLocation>,
destination: &mut Option<InteriorMultiLocation>,
message: &mut Option<Xcm<()>>,
) -> Result<(Self::Ticket, MultiAssets), SendError> {
// Find supported lane_id.
let sender_and_lane = Self::lane_for(
universal_source.as_ref().ok_or(SendError::MissingArgument)?,
(&network, destination.as_ref().ok_or(SendError::MissingArgument)?),
)
.ok_or(SendError::NotApplicable)?;
// check if we are able to route the message. We use existing `HaulBlobExporter` for that.
// It will make all required changes and will encode message properly, so that the
// `DispatchBlob` at the bridged bridge hub will be able to decode it
let ((blob, id), price) = PalletAsHaulBlobExporter::<T, I>::validate(
network,
channel,
universal_source,
destination,
message,
)?;
Ok(((sender_and_lane, blob, id), price))
}
fn deliver(
(sender_and_lane, blob, id): (SenderAndLane, XcmAsPlainPayload, XcmHash),
) -> Result<XcmHash, SendError> {
let lane_id = sender_and_lane.lane;
let send_result = MessagesPallet::<T, I>::send_message(lane_id, blob);
match send_result {
Ok(artifacts) => {
log::info!(
target: LOG_TARGET,
"XCM message {:?} has been enqueued at bridge {:?} with nonce {}",
id,
lane_id,
artifacts.nonce,
);
// notify XCM queue manager about updated lane state
LocalXcmQueueManager::<T::LanesSupport>::on_bridge_message_enqueued(
&sender_and_lane,
artifacts.enqueued_messages,
);
},
Err(error) => {
log::debug!(
target: LOG_TARGET,
"XCM message {:?} has been dropped because of bridge error {:?} on bridge {:?}",
id,
error,
lane_id,
);
return Err(SendError::Transport("BridgeSendError"))
},
}
Ok(id)
}
}
/// Dummy implementation of the `HaulBlob` trait that is never called.
///
/// We are using `HaulBlobExporter`, which requires `HaulBlob` implementation. It assumes that
/// there's a single channel between two bridge hubs - `HaulBlob` only accepts the blob and nothing
/// else. But bridge messages pallet may have a dedicated channel (lane) for every pair of bridged
/// chains. So we are using our own `ExportXcm` implementation, but to utilize `HaulBlobExporter` we
/// still need this `DummyHaulBlob`.
pub struct DummyHaulBlob;
impl HaulBlob for DummyHaulBlob {
fn haul_blob(_blob: XcmAsPlainPayload) -> Result<(), HaulBlobError> {
Err(HaulBlobError::Transport("DummyHaulBlob"))
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::mock::*;
use frame_support::assert_ok;
use xcm_executor::traits::export_xcm;
fn universal_source() -> InteriorMultiLocation {
X2(GlobalConsensus(RelayNetwork::get()), Parachain(SIBLING_ASSET_HUB_ID))
}
fn universal_destination() -> InteriorMultiLocation {
BridgedDestination::get()
}
#[test]
fn export_works() {
run_test(|| {
assert_ok!(export_xcm::<XcmOverBridge>(
BridgedRelayNetwork::get(),
0,
universal_source(),
universal_destination(),
vec![Instruction::ClearOrigin].into(),
));
})
}
#[test]
fn export_fails_if_argument_is_missing() {
run_test(|| {
assert_eq!(
XcmOverBridge::validate(
BridgedRelayNetwork::get(),
0,
&mut None,
&mut Some(universal_destination()),
&mut Some(Vec::new().into()),
),
Err(SendError::MissingArgument),
);
assert_eq!(
XcmOverBridge::validate(
BridgedRelayNetwork::get(),
0,
&mut Some(universal_source()),
&mut None,
&mut Some(Vec::new().into()),
),
Err(SendError::MissingArgument),
);
})
}
#[test]
fn exporter_computes_correct_lane_id() {
run_test(|| {
let expected_lane_id = TEST_LANE_ID;
assert_eq!(
XcmOverBridge::validate(
BridgedRelayNetwork::get(),
0,
&mut Some(universal_source()),
&mut Some(universal_destination()),
&mut Some(Vec::new().into()),
)
.unwrap()
.0
.0
.lane,
expected_lane_id,
);
})
}
}