12 breaking releases

0.13.0 Sep 26, 2024
0.12.0 Jul 19, 2024
0.10.0 Jul 13, 2024
0.5.0 Mar 19, 2024
0.1.0 Dec 13, 2023

#7 in #xcm

Download history 623/week @ 2024-07-29 234/week @ 2024-08-05 408/week @ 2024-08-12 566/week @ 2024-08-19 420/week @ 2024-08-26 106/week @ 2024-09-02 253/week @ 2024-09-09 205/week @ 2024-09-16 337/week @ 2024-09-23 322/week @ 2024-09-30 441/week @ 2024-10-07 729/week @ 2024-10-14 946/week @ 2024-10-21 724/week @ 2024-10-28 618/week @ 2024-11-04 20380/week @ 2024-11-11

22,737 downloads per month
Used in 13 crates (5 directly)

GPL-3.0-or-later…

3MB
51K SLoC

Release

Polkadot SDK stable2409


lib.rs:

Module that adds XCM support to bridge pallets. The pallet allows to dynamically open and close bridges between local (to this pallet location) and remote XCM destinations.

The pallet_xcm_bridge_hub pallet is used to manage (open, close) bridges between chains from different consensuses. The new extrinsics fn open_bridge and fn close_bridge are introduced. Other chains can manage channels with different bridged global consensuses.

Concept of lane and LaneId

There is another pallet_bridge_messages pallet that handles inbound/outbound lanes for messages. Each lane is a unique connection between two chains from different consensuses and is identified by LaneId. LaneId is generated once when a new bridge is requested by fn open_bridge. It is generated by BridgeLocations::calculate_lane_id based on the following parameters:

  • Source bridge_origin_universal_location (latest XCM)
  • Destination bridge_destination_universal_location (latest XCM)
  • XCM version (both sides of the bridge must use the same parameters to generate the same LaneId)
    • bridge_origin_universal_location, bridge_destination_universal_location is converted to the Versioned* structs

LaneId is expected to never change because:

  • We need the same LaneId on both sides of the bridge, as LaneId is part of the message key proofs.
  • Runtime upgrades are entirely asynchronous.
  • We already have a running production Polkadot/Kusama bridge that uses LaneId([0, 0, 0, 0]).

LaneId is backward compatible, meaning it can be encoded/decoded from the older format [u8; 4] used for static lanes, as well as the new format H256 generated by BridgeLocations::calculate_lane_id.

Concept of bridge and BridgeId

The pallet_xcm_bridge_hub pallet needs to store some metadata about opened bridges. The bridge (or bridge metadata) is stored under the BridgeId key.

BridgeId is generated from bridge_origin_relative_location and bridge_origin_universal_location using the latest XCM structs. BridgeId is not transferred over the bridge; it is only important for local consensus. It essentially serves as an index/key to bridge metadata. All the XCM infrastructure around XcmExecutor, SendXcm, ExportXcm use the latest XCM, so BridgeId must remain compatible with the latest XCM. For example, we have an ExportXcm implementation in exporter.rs that handles the ExportMessage instruction with universal_source and destination (latest XCM), so we need to create BridgeId and the corresponding LaneId.

Migrations and State

This pallet implements try_state, ensuring compatibility and checking everything so we know if any migration is needed. do_try_state checks for BridgeId compatibility, which is recalculated on runtime upgrade. Upgrading to a new XCM version should not break anything, except removing older XCM versions. In such cases, we need to add migration for BridgeId and stored Versioned* structs and update LaneToBridge mapping, but this won't affect LaneId over the bridge.

How to Open a Bridge?

The pallet_xcm_bridge_hub pallet has the extrinsic fn open_bridge and an important configuration pallet_xcm_bridge_hub::Config::OpenBridgeOrigin, which translates the call's origin to the XCM Location and converts it to the bridge_origin_universal_location. With the current setup, this origin/location is expected to be either the relay chain or a sibling parachain as one side of the bridge. Another parameter is bridge_destination_universal_location, which is the other side of the bridge from a different global consensus.

Every bridge between two XCM locations has a dedicated lane in associated messages pallet. Assuming that this pallet is deployed at the bridge hub parachain and there's a similar pallet at the bridged network, the dynamic bridge lifetime is as follows:

  1. the sibling parachain opens a XCMP channel with this bridge hub;

  2. the sibling parachain funds its sovereign parachain account at this bridge hub. It shall hold enough funds to pay for the bridge (see BridgeDeposit);

  3. the sibling parachain opens the bridge by sending XCM Transact instruction with the open_bridge call. The BridgeDeposit amount is reserved on the sovereign account of sibling parachain;

  4. at the other side of the bridge, the same thing (1, 2, 3) happens. Parachains that need to connect over the bridge need to coordinate the moment when they start sending messages over the bridge. Otherwise they may lose messages and/or bundled assets;

  5. when either side wants to close the bridge, it sends the XCM Transact with the close_bridge call. The bridge is closed immediately if there are no queued messages. Otherwise, the owner must repeat the close_bridge call to prune all queued messages first.

The pallet doesn't provide any mechanism for graceful closure, because it always involves some contract between two connected chains and the bridge hub knows nothing about that. It is the task for the connected chains to make sure that all required actions are completed before the closure. In the end, the bridge hub can't even guarantee that all messages that are delivered to the destination, are processed in the way their sender expects. So if we can't guarantee that, we shall not care about more complex procedures and leave it to the participating parties.

Example

Example of opening a bridge between some random parachains from Polkadot and Kusama:

  1. Let's have:
    • BridgeHubPolkadot with UniversalLocation = [GlobalConsensus(Polkadot), Parachain(1002)]
    • BridgeHubKusama with UniversalLocation = [GlobalConsensus(Kusama), Parachain(1002)]
  2. The Polkadot local sibling parachain Location::new(1, Parachain(1234)) must send some DOTs to its sovereign account on BridgeHubPolkadot to cover BridgeDeposit, fees for Transact, and the existential deposit.
  3. Send a call to the BridgeHubPolkadot from the local sibling parachain: Location::new(1, Parachain(1234)) xcm::Transact( origin_kind: OriginKind::Xcm, XcmOverBridgeHubKusama::open_bridge( VersionedInteriorLocation::V4([GlobalConsensus(Kusama), Parachain(4567)].into()), ); )
  4. Check the stored bridge metadata and generated LaneId.
  5. The Kusama local sibling parachain Location::new(1, Parachain(4567)) must send some KSMs to its sovereign account on BridgeHubKusama to cover BridgeDeposit, fees for Transact, and the existential deposit.
  6. Send a call to the BridgeHubKusama from the local sibling parachain: Location::new(1, Parachain(4567)) xcm::Transact( origin_kind: OriginKind::Xcm, XcmOverBridgeHubKusama::open_bridge( VersionedInteriorLocation::V4([GlobalConsensus(Polkadot), Parachain(1234)].into()), ); )
  7. Check the stored bridge metadata and generated LaneId.
  8. Both LaneIds from steps 3 and 6 must be the same (see above Concept of lane and LaneId).
  9. Run the bridge messages relayer for LaneId.
  10. Send messages from both sides.

The opening bridge holds the configured BridgeDeposit from the origin's sovereign account, but this deposit is returned when the bridge is closed with fn close_bridge.

Dependencies

~22–37MB
~633K SLoC