Files
pezkuwi-subxt/cumulus/scripts/bridges_common.sh
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

307 lines
11 KiB
Bash
Executable File

#!/bin/bash
function ensure_binaries() {
if [[ ! -f ~/local_bridge_testing/bin/polkadot ]]; then
echo " Required polkadot binary '~/local_bridge_testing/bin/polkadot' does not exist!"
echo " You need to build it and copy to this location!"
echo " Please, check ./parachains/runtimes/bridge-hubs/README.md (Prepare/Build/Deploy)"
exit 1
fi
if [[ ! -f ~/local_bridge_testing/bin/polkadot-parachain ]]; then
echo " Required polkadot-parachain binary '~/local_bridge_testing/bin/polkadot-parachain' does not exist!"
echo " You need to build it and copy to this location!"
echo " Please, check ./parachains/runtimes/bridge-hubs/README.md (Prepare/Build/Deploy)"
exit 1
fi
}
function ensure_relayer() {
if [[ ! -f ~/local_bridge_testing/bin/substrate-relay ]]; then
echo " Required substrate-relay binary '~/local_bridge_testing/bin/substrate-relay' does not exist!"
echo " You need to build it and copy to this location!"
echo " Please, check ./parachains/runtimes/bridge-hubs/README.md (Prepare/Build/Deploy)"
exit 1
fi
}
function ensure_polkadot_js_api() {
if ! which polkadot-js-api &> /dev/null; then
echo ''
echo 'Required command `polkadot-js-api` not in PATH, please, install, e.g.:'
echo "npm install -g @polkadot/api-cli@beta"
echo " or"
echo "yarn global add @polkadot/api-cli"
echo ''
exit 1
fi
if ! which jq &> /dev/null; then
echo ''
echo 'Required command `jq` not in PATH, please, install, e.g.:'
echo "apt install -y jq"
echo ''
exit 1
fi
generate_hex_encoded_call_data "check" "--"
local retVal=$?
if [ $retVal -ne 0 ]; then
echo ""
echo ""
echo "-------------------"
echo "Installing (nodejs) sub module: $(dirname "$0")/generate_hex_encoded_call"
pushd $(dirname "$0")/generate_hex_encoded_call
npm install
popd
fi
}
function generate_hex_encoded_call_data() {
local type=$1
local endpoint=$2
local output=$3
shift
shift
shift
echo "Input params: $@"
node $(dirname "$0")/generate_hex_encoded_call "$type" "$endpoint" "$output" "$@"
local retVal=$?
if [ $type != "check" ]; then
local hex_encoded_data=$(cat $output)
echo "Generated hex-encoded bytes to file '$output': $hex_encoded_data"
fi
return $retVal
}
function transfer_balance() {
local runtime_para_endpoint=$1
local seed=$2
local target_account=$3
local amount=$4
echo " calling transfer_balance:"
echo " runtime_para_endpoint: ${runtime_para_endpoint}"
echo " seed: ${seed}"
echo " target_account: ${target_account}"
echo " amount: ${amount}"
echo "--------------------------------------------------"
polkadot-js-api \
--ws "${runtime_para_endpoint}" \
--seed "${seed?}" \
tx.balances.transferAllowDeath \
"${target_account}" \
"${amount}"
}
function send_governance_transact() {
local relay_url=$1
local relay_chain_seed=$2
local para_id=$3
local hex_encoded_data=$4
local require_weight_at_most_ref_time=$5
local require_weight_at_most_proof_size=$6
echo " calling send_governance_transact:"
echo " relay_url: ${relay_url}"
echo " relay_chain_seed: ${relay_chain_seed}"
echo " para_id: ${para_id}"
echo " hex_encoded_data: ${hex_encoded_data}"
echo " require_weight_at_most_ref_time: ${require_weight_at_most_ref_time}"
echo " require_weight_at_most_proof_size: ${require_weight_at_most_proof_size}"
echo " params:"
local dest=$(jq --null-input \
--arg para_id "$para_id" \
'{ "V3": { "parents": 0, "interior": { "X1": { "Parachain": $para_id } } } }')
local message=$(jq --null-input \
--argjson hex_encoded_data $hex_encoded_data \
--arg require_weight_at_most_ref_time "$require_weight_at_most_ref_time" \
--arg require_weight_at_most_proof_size "$require_weight_at_most_proof_size" \
'
{
"V3": [
{
"UnpaidExecution": {
"weight_limit": "Unlimited"
}
},
{
"Transact": {
"origin_kind": "Superuser",
"require_weight_at_most": {
"ref_time": $require_weight_at_most_ref_time,
"proof_size": $require_weight_at_most_proof_size,
},
"call": {
"encoded": $hex_encoded_data
}
}
}
]
}
')
echo ""
echo " dest:"
echo "${dest}"
echo ""
echo " message:"
echo "${message}"
echo ""
echo "--------------------------------------------------"
polkadot-js-api \
--ws "${relay_url?}" \
--seed "${relay_chain_seed?}" \
--sudo \
tx.xcmPallet.send \
"${dest}" \
"${message}"
}
function open_hrmp_channels() {
local relay_url=$1
local relay_chain_seed=$2
local sender_para_id=$3
local recipient_para_id=$4
local max_capacity=$5
local max_message_size=$6
echo " calling open_hrmp_channels:"
echo " relay_url: ${relay_url}"
echo " relay_chain_seed: ${relay_chain_seed}"
echo " sender_para_id: ${sender_para_id}"
echo " recipient_para_id: ${recipient_para_id}"
echo " max_capacity: ${max_capacity}"
echo " max_message_size: ${max_message_size}"
echo " params:"
echo "--------------------------------------------------"
polkadot-js-api \
--ws "${relay_url?}" \
--seed "${relay_chain_seed?}" \
--sudo \
tx.hrmp.forceOpenHrmpChannel \
${sender_para_id} \
${recipient_para_id} \
${max_capacity} \
${max_message_size}
}
function force_xcm_version() {
local relay_url=$1
local relay_chain_seed=$2
local runtime_para_id=$3
local runtime_para_endpoint=$4
local dest=$5
local xcm_version=$6
echo " calling force_xcm_version:"
echo " relay_url: ${relay_url}"
echo " relay_chain_seed: ${relay_chain_seed}"
echo " runtime_para_id: ${runtime_para_id}"
echo " runtime_para_endpoint: ${runtime_para_endpoint}"
echo " dest: ${dest}"
echo " xcm_version: ${xcm_version}"
echo " params:"
# 1. generate data for Transact (PolkadotXcm::force_xcm_version)
local tmp_output_file=$(mktemp)
generate_hex_encoded_call_data "force-xcm-version" "${runtime_para_endpoint}" "${tmp_output_file}" "$dest" "$xcm_version"
local hex_encoded_data=$(cat $tmp_output_file)
# 2. trigger governance call
send_governance_transact "${relay_url}" "${relay_chain_seed}" "${runtime_para_id}" "${hex_encoded_data}" 200000000 12000
}
function force_create_foreign_asset() {
local relay_url=$1
local relay_chain_seed=$2
local runtime_para_id=$3
local runtime_para_endpoint=$4
local asset_multilocation=$5
local asset_owner_account_id=$6
local min_balance=$7
local is_sufficient=$8
echo " calling force_create_foreign_asset:"
echo " relay_url: ${relay_url}"
echo " relay_chain_seed: ${relay_chain_seed}"
echo " runtime_para_id: ${runtime_para_id}"
echo " runtime_para_endpoint: ${runtime_para_endpoint}"
echo " asset_multilocation: ${asset_multilocation}"
echo " asset_owner_account_id: ${asset_owner_account_id}"
echo " min_balance: ${min_balance}"
echo " is_sufficient: ${is_sufficient}"
echo " params:"
# 1. generate data for Transact (ForeignAssets::force_create)
local tmp_output_file=$(mktemp)
generate_hex_encoded_call_data "force-create-asset" "${runtime_para_endpoint}" "${tmp_output_file}" "$asset_multilocation" "$asset_owner_account_id" $is_sufficient $min_balance
local hex_encoded_data=$(cat $tmp_output_file)
# 2. trigger governance call
send_governance_transact "${relay_url}" "${relay_chain_seed}" "${runtime_para_id}" "${hex_encoded_data}" 200000000 12000
}
function limited_reserve_transfer_assets() {
local url=$1
local seed=$2
local destination=$3
local beneficiary=$4
local assets=$5
local fee_asset_item=$6
local weight_limit=$7
echo " calling limited_reserve_transfer_assets:"
echo " url: ${url}"
echo " seed: ${seed}"
echo " destination: ${destination}"
echo " beneficiary: ${beneficiary}"
echo " assets: ${assets}"
echo " fee_asset_item: ${fee_asset_item}"
echo " weight_limit: ${weight_limit}"
echo ""
echo "--------------------------------------------------"
polkadot-js-api \
--ws "${url?}" \
--seed "${seed?}" \
tx.polkadotXcm.limitedReserveTransferAssets \
"${destination}" \
"${beneficiary}" \
"${assets}" \
"${fee_asset_item}" \
"${weight_limit}"
}
function claim_rewards() {
local runtime_para_endpoint=$1
local seed=$2
local lane_id=$3
local bridged_chain_id=$4
local owner=$5
echo " calling claim_rewards:"
echo " runtime_para_endpoint: ${runtime_para_endpoint}"
echo " seed: ${seed}"
echo " lane_id: ${lane_id}"
echo " bridged_chain_id: ${bridged_chain_id}"
echo " owner: ${owner}"
echo ""
local rewards_account_params=$(jq --null-input \
--arg lane_id "$lane_id" \
--arg bridged_chain_id "$bridged_chain_id" \
--arg owner "$owner" \
'{
"laneId": $lane_id,
"bridgedChainId": $bridged_chain_id,
"owner": $owner
}')
echo " rewards_account_params:"
echo "${rewards_account_params}"
echo "--------------------------------------------------"
polkadot-js-api \
--ws "${runtime_para_endpoint}" \
--seed "${seed?}" \
tx.bridgeRelayers.claimRewards \
"${rewards_account_params}"
}