diff --git a/Cargo.lock b/Cargo.lock index 95143d2d77ff..1b9c046fb31c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2017,6 +2017,8 @@ dependencies = [ "parity-scale-codec", "scale-info", "serde", + "sp-core", + "sp-io", "sp-std 14.0.0", "staging-xcm", ] diff --git a/bridges/modules/xcm-bridge-hub/src/dispatcher.rs b/bridges/modules/xcm-bridge-hub/src/dispatcher.rs index e08a21bc9af8..b6f6ed760653 100644 --- a/bridges/modules/xcm-bridge-hub/src/dispatcher.rs +++ b/bridges/modules/xcm-bridge-hub/src/dispatcher.rs @@ -28,7 +28,7 @@ use bp_messages::{ LaneId, }; use bp_runtime::messages::MessageDispatchResult; -use bp_xcm_bridge_hub::{BridgeId, LocalXcmChannelManager, XcmAsPlainPayload}; +use bp_xcm_bridge_hub::{LocalXcmChannelManager, XcmAsPlainPayload}; use codec::{Decode, Encode}; use frame_support::{weights::Weight, CloneNoBound, EqNoBound, PartialEqNoBound}; use pallet_bridge_messages::{Config as BridgeMessagesConfig, WeightInfoExt}; @@ -60,9 +60,8 @@ where type DispatchLevelResult = XcmBlobMessageDispatchResult; fn is_active(lane: LaneId) -> bool { - let bridge_id = BridgeId::from_lane_id(lane); - Pallet::::bridge(bridge_id) - .and_then(|bridge| bridge.bridge_origin_relative_location.try_as().cloned().ok()) + Pallet::::bridge_by_lane_id(&lane) + .and_then(|(_, bridge)| bridge.bridge_origin_relative_location.try_as().cloned().ok()) .map(|recipient: Location| !T::LocalXcmChannelManager::is_congested(&recipient)) .unwrap_or(false) } @@ -122,26 +121,31 @@ where #[cfg(test)] mod tests { use super::*; - use crate::{mock::*, Bridges}; + use crate::{mock::*, Bridges, LaneToBridge}; use bp_messages::{target_chain::DispatchMessageData, MessageKey}; - use bp_xcm_bridge_hub::{Bridge, BridgeState}; + use bp_xcm_bridge_hub::{Bridge, BridgeId, BridgeState}; + use frame_support::assert_ok; + use sp_core::H256; - fn bridge_id() -> BridgeId { - BridgeId::from_lane_id(LaneId::new(1, 2)) + fn bridge() -> (BridgeId, LaneId) { + (BridgeId::from_inner(H256::from([1u8; 32])), LaneId::new(1, 2)) } fn run_test_with_opened_bridge(test: impl FnOnce()) { run_test(|| { Bridges::::insert( - bridge_id(), + bridge().0, Bridge { bridge_origin_relative_location: Box::new(Location::new(0, Here).into()), state: BridgeState::Opened, bridge_owner_account: [0u8; 32].into(), reserve: 0, + lane_id: bridge().1, }, ); + LaneToBridge::::insert(bridge().1, bridge().0); + assert_ok!(XcmOverBridge::do_try_state()); test(); }); @@ -165,14 +169,14 @@ mod tests { fn dispatcher_is_inactive_when_channel_with_target_chain_is_congested() { run_test_with_opened_bridge(|| { TestLocalXcmChannelManager::make_congested(); - assert!(!XcmOverBridge::is_active(bridge_id().lane_id())); + assert!(!XcmOverBridge::is_active(bridge().1)); }); } #[test] fn dispatcher_is_active_when_channel_with_target_chain_is_not_congested() { run_test_with_opened_bridge(|| { - assert!(XcmOverBridge::is_active(bridge_id().lane_id())); + assert!(XcmOverBridge::is_active(bridge().1)); }); } diff --git a/bridges/modules/xcm-bridge-hub/src/exporter.rs b/bridges/modules/xcm-bridge-hub/src/exporter.rs index e5b0e795710e..72d34790ebd4 100644 --- a/bridges/modules/xcm-bridge-hub/src/exporter.rs +++ b/bridges/modules/xcm-bridge-hub/src/exporter.rs @@ -113,34 +113,33 @@ where ); SendError::Unroutable })?; - let bridge = Self::bridge(locations.bridge_id).ok_or(SendError::Unroutable)?; - - let bridge_message = - MessagesPallet::::validate_message(locations.bridge_id.lane_id(), &blob) - .map_err(|e| { - // TODO:(bridges-v2) - add test/std feature gate? FAIL-CI - match e { - Error::LanesManager(ref ei) => - log::error!(target: LOG_TARGET, "LanesManager: {ei:?}"), - Error::MessageRejectedByPallet(ref ei) => - log::error!(target: LOG_TARGET, "MessageRejectedByPallet: {ei:?}"), - Error::ReceptionConfirmation(ref ei) => - log::error!(target: LOG_TARGET, "ReceptionConfirmation: {ei:?}"), - _ => (), - }; - - log::error!( - target: LOG_TARGET, - "XCM message {:?} cannot be exported because of bridge error: {:?} on bridge {:?} and laneId: {:?}", - id, - e, - locations, - locations.bridge_id.lane_id(), - ); - SendError::Transport("BridgeValidateError") - })?; - - Ok(((locations.bridge_id, bridge, bridge_message, id), price)) + let bridge = Self::bridge(locations.bridge_id()).ok_or(SendError::Unroutable)?; + + let bridge_message = MessagesPallet::::validate_message(bridge.lane_id, &blob) + .map_err(|e| { + // TODO:(bridges-v2) - add test/std feature gate? FAIL-CI + match e { + Error::LanesManager(ref ei) => + log::error!(target: LOG_TARGET, "LanesManager: {ei:?}"), + Error::MessageRejectedByPallet(ref ei) => + log::error!(target: LOG_TARGET, "MessageRejectedByPallet: {ei:?}"), + Error::ReceptionConfirmation(ref ei) => + log::error!(target: LOG_TARGET, "ReceptionConfirmation: {ei:?}"), + _ => (), + }; + + log::error!( + target: LOG_TARGET, + "XCM message {:?} cannot be exported because of bridge error: {:?} on bridge {:?} and laneId: {:?}", + id, + e, + locations, + bridge.lane_id, + ); + SendError::Transport("BridgeValidateError") + })?; + + Ok(((locations.bridge_id().clone(), bridge, bridge_message, id), price)) } fn deliver( @@ -150,9 +149,10 @@ where log::info!( target: LOG_TARGET, - "XCM message {:?} has been enqueued at bridge {:?} with nonce {}", + "XCM message {:?} has been enqueued at bridge {:?} and lane_id: {:?} with nonce {}", id, bridge_id, + bridge.lane_id, artifacts.nonce, ); @@ -247,9 +247,8 @@ impl, I: 'static> Pallet { // if we have not suspended the bridge before (or it is closed), we don't want to do // anything - let bridge_id = BridgeId::from_lane_id(lane_id); - let bridge = match Self::bridge(bridge_id) { - Some(bridge) if bridge.state == BridgeState::Suspended => bridge, + let (bridge_id, bridge) = match Self::bridge_by_lane_id(&lane_id) { + Some(bridge) if bridge.1.state == BridgeState::Suspended => bridge, _ => { // if there is no bridge or it has been closed, then we don't need to send resume // signal to the local origin - it has closed bridge itself, so it should have @@ -265,7 +264,8 @@ impl, I: 'static> Pallet { Err(e) => { log::debug!( target: LOG_TARGET, - "Failed to convert the bridge {:?} location: {:?}", + "Failed to convert the bridge {:?} location for lane_id: {:?}, error {:?}", + bridge_id, lane_id, e, ); @@ -280,7 +280,8 @@ impl, I: 'static> Pallet { Ok(_) => { log::debug!( target: LOG_TARGET, - "Resumed the bridge {:?}, originated by the {:?}", + "Resumed the bridge {:?} and lane_id: {:?}, originated by the {:?}", + bridge_id, lane_id, bridge_origin_relative_location, ); @@ -288,7 +289,8 @@ impl, I: 'static> Pallet { Err(e) => { log::debug!( target: LOG_TARGET, - "Failed to resume the bridge {:?}, originated by the {:?}: {:?}", + "Failed to resume the bridge {:?} and lane_id: {:?}, originated by the {:?}: {:?}", + bridge_id, lane_id, bridge_origin_relative_location, e, @@ -323,10 +325,10 @@ impl HaulBlob for DummyHaulBlob { #[cfg(test)] mod tests { use super::*; - use crate::{mock::*, Bridges, LanesManagerOf}; + use crate::{mock::*, Bridges, LaneToBridge, LanesManagerOf}; use bp_runtime::RangeInclusiveExt; - use bp_xcm_bridge_hub::{Bridge, BridgeLocations, BridgeState}; + use bp_xcm_bridge_hub::{bridge_locations, Bridge, BridgeLocations, BridgeState}; use frame_support::assert_ok; use xcm_executor::traits::export_xcm; @@ -342,61 +344,66 @@ mod tests { BridgedUniversalDestination::get() } - fn open_lane() -> BridgeLocations { + fn open_lane() -> (BridgeLocations, LaneId) { // open expected outbound lane let origin = OpenBridgeOrigin::sibling_parachain_origin(); let with = bridged_asset_hub_universal_location(); let locations = XcmOverBridge::bridge_locations_from_origin(origin, Box::new(with.into())).unwrap(); + let lane_id = locations.calculate_lane_id(xcm::latest::VERSION).unwrap(); let lanes_manager = LanesManagerOf::::new(); - if lanes_manager.create_outbound_lane(locations.bridge_id.lane_id()).is_ok() { + if lanes_manager.create_outbound_lane(lane_id).is_ok() { assert!(lanes_manager - .active_outbound_lane(locations.bridge_id.lane_id()) + .active_outbound_lane(lane_id) .unwrap() .queued_messages() .is_empty()); // insert bridge Bridges::::insert( - locations.bridge_id, + locations.bridge_id(), Bridge { bridge_origin_relative_location: Box::new(SiblingLocation::get().into()), state: BridgeState::Opened, bridge_owner_account: [0u8; 32].into(), reserve: 0, + lane_id, }, ); + LaneToBridge::::insert(lane_id, locations.bridge_id()); } - *locations + assert_ok!(XcmOverBridge::do_try_state()); + + (*locations, lane_id) } - fn open_lane_and_send_regular_message() -> BridgeId { - let locations = open_lane(); + fn open_lane_and_send_regular_message() -> (BridgeId, LaneId) { + let (locations, lane_id) = open_lane(); // now let's try to enqueue message using our `ExportXcm` implementation export_xcm::( BridgedRelayNetwork::get(), 0, - locations.bridge_origin_universal_location, - locations.bridge_destination_universal_location, + locations.bridge_origin_universal_location().clone(), + locations.bridge_destination_universal_location().clone(), vec![Instruction::ClearOrigin].into(), ) .unwrap(); - locations.bridge_id + (locations.bridge_id().clone(), lane_id) } #[test] fn exporter_works() { run_test(|| { - let bridge_id = open_lane_and_send_regular_message(); + let (_, lane_id) = open_lane_and_send_regular_message(); // double check that the message has been pushed to the expected lane // (it should already been checked during `send_message` call) assert!(!LanesManagerOf::::new() - .active_outbound_lane(bridge_id.lane_id()) + .active_outbound_lane(lane_id) .unwrap() .queued_messages() .is_empty()); @@ -406,7 +413,7 @@ mod tests { #[test] fn exporter_does_not_suspend_the_bridge_if_outbound_bridge_queue_is_not_congested() { run_test(|| { - let bridge_id = open_lane_and_send_regular_message(); + let (bridge_id, _) = open_lane_and_send_regular_message(); assert!(!TestLocalXcmChannelManager::is_bridge_suspened()); assert_eq!(XcmOverBridge::bridge(bridge_id).unwrap().state, BridgeState::Opened); }); @@ -415,7 +422,7 @@ mod tests { #[test] fn exporter_does_not_suspend_the_bridge_if_it_is_already_suspended() { run_test(|| { - let bridge_id = open_lane_and_send_regular_message(); + let (bridge_id, _) = open_lane_and_send_regular_message(); Bridges::::mutate_extant(bridge_id, |bridge| { bridge.state = BridgeState::Suspended; }); @@ -431,7 +438,7 @@ mod tests { #[test] fn exporter_suspends_the_bridge_if_outbound_bridge_queue_is_congested() { run_test(|| { - let bridge_id = open_lane_and_send_regular_message(); + let (bridge_id, _) = open_lane_and_send_regular_message(); for _ in 1..OUTBOUND_LANE_CONGESTED_THRESHOLD { open_lane_and_send_regular_message(); } @@ -448,12 +455,12 @@ mod tests { #[test] fn bridge_is_not_resumed_if_outbound_bridge_queue_is_still_congested() { run_test(|| { - let bridge_id = open_lane_and_send_regular_message(); + let (bridge_id, lane_id) = open_lane_and_send_regular_message(); Bridges::::mutate_extant(bridge_id, |bridge| { bridge.state = BridgeState::Suspended; }); XcmOverBridge::on_bridge_messages_delivered( - bridge_id.lane_id(), + lane_id, OUTBOUND_LANE_UNCONGESTED_THRESHOLD + 1, ); @@ -465,9 +472,9 @@ mod tests { #[test] fn bridge_is_not_resumed_if_it_was_not_suspended_before() { run_test(|| { - let bridge_id = open_lane_and_send_regular_message(); + let (bridge_id, lane_id) = open_lane_and_send_regular_message(); XcmOverBridge::on_bridge_messages_delivered( - bridge_id.lane_id(), + lane_id, OUTBOUND_LANE_UNCONGESTED_THRESHOLD, ); @@ -479,12 +486,12 @@ mod tests { #[test] fn bridge_is_resumed_when_enough_messages_are_delivered() { run_test(|| { - let bridge_id = open_lane_and_send_regular_message(); + let (bridge_id, lane_id) = open_lane_and_send_regular_message(); Bridges::::mutate_extant(bridge_id, |bridge| { bridge.state = BridgeState::Suspended; }); XcmOverBridge::on_bridge_messages_delivered( - bridge_id.lane_id(), + lane_id, OUTBOUND_LANE_UNCONGESTED_THRESHOLD, ); @@ -569,7 +576,7 @@ mod tests { let dest = Location::new(2, BridgedUniversalDestination::get()); // open bridge - let expected_lane_id = open_lane().bridge_id.lane_id(); + let (_, expected_lane_id) = open_lane(); // check before - no messages assert_eq!( diff --git a/bridges/modules/xcm-bridge-hub/src/lib.rs b/bridges/modules/xcm-bridge-hub/src/lib.rs index 46f2ab01714e..722104818bb0 100644 --- a/bridges/modules/xcm-bridge-hub/src/lib.rs +++ b/bridges/modules/xcm-bridge-hub/src/lib.rs @@ -51,7 +51,7 @@ #![warn(missing_docs)] #![cfg_attr(not(feature = "std"), no_std)] -use bp_messages::{LaneState, MessageNonce}; +use bp_messages::{LaneId, LaneState, MessageNonce}; use bp_runtime::{AccountIdOf, BalanceOf, RangeInclusiveExt}; use bp_xcm_bridge_hub::{ bridge_locations, Bridge, BridgeId, BridgeLocations, BridgeLocationsError, BridgeState, @@ -171,6 +171,11 @@ pub mod pallet { T::BridgedNetwork::get() ) } + + #[cfg(feature = "try-runtime")] + fn try_state(_n: BlockNumberFor) -> Result<(), sp_runtime::TryRuntimeError> { + Self::do_try_state() + } } #[pallet::call] @@ -192,59 +197,79 @@ pub mod pallet { origin: OriginFor, bridge_destination_universal_location: Box, ) -> DispatchResult { - // check and compute required bridge locations + // check and compute required bridge locations and laneId + let xcm_version = bridge_destination_universal_location.identify_version(); let locations = Self::bridge_locations_from_origin(origin, bridge_destination_universal_location)?; + let lane_id = locations.calculate_lane_id(xcm_version).map_err(|e| { + log::trace!( + target: LOG_TARGET, + "calculate_lane_id error: {e:?}", + ); + Error::::BridgeLocations(e) + })?; // reserve balance on the parachain sovereign account let deposit = T::BridgeDeposit::get(); let bridge_owner_account = T::BridgeOriginAccountIdConverter::convert_location( - &locations.bridge_origin_relative_location, + locations.bridge_origin_relative_location(), ) .ok_or(Error::::InvalidBridgeOriginAccount)?; T::Currency::hold(&HoldReason::BridgeDeposit.into(), &bridge_owner_account, deposit) .map_err(|_| Error::::FailedToReserveBridgeDeposit)?; // save bridge metadata - Bridges::::try_mutate(locations.bridge_id, |bridge| match bridge { + Bridges::::try_mutate(locations.bridge_id(), |bridge| match bridge { Some(_) => Err(Error::::BridgeAlreadyExists), None => { *bridge = Some(BridgeOf:: { bridge_origin_relative_location: Box::new( - locations.bridge_origin_relative_location.clone().into(), + locations.bridge_origin_relative_location().clone().into(), ), state: BridgeState::Opened, bridge_owner_account, reserve: deposit, + lane_id, }); Ok(()) }, })?; + // save lane to bridge mapping + LaneToBridge::::try_mutate(lane_id, |bridge| match bridge { + Some(_) => Err(Error::::BridgeAlreadyExists), + None => { + *bridge = Some(locations.bridge_id().clone()); + Ok(()) + }, + })?; // create new lanes. Under normal circumstances, following calls shall never fail let lanes_manager = LanesManagerOf::::new(); lanes_manager - .create_inbound_lane(locations.bridge_id.lane_id()) + .create_inbound_lane(lane_id) .map_err(Error::::LanesManager)?; lanes_manager - .create_outbound_lane(locations.bridge_id.lane_id()) + .create_outbound_lane(lane_id) .map_err(Error::::LanesManager)?; // write something to log log::trace!( target: LOG_TARGET, - "Bridge {:?} between {:?} and {:?} has been opened", - locations.bridge_id, - locations.bridge_origin_universal_location, - locations.bridge_destination_universal_location, + "Bridge {:?} between {:?} and {:?} has been opened using lane_id: {lane_id:?}", + locations.bridge_id(), + locations.bridge_origin_universal_location(), + locations.bridge_destination_universal_location(), ); // deposit `BridgeOpened` event Self::deposit_event(Event::::BridgeOpened { - bridge_id: locations.bridge_id, + bridge_id: locations.bridge_id().clone(), bridge_deposit: Some(deposit), - local_endpoint: Box::new(locations.bridge_origin_universal_location), - remote_endpoint: Box::new(locations.bridge_destination_universal_location), + local_endpoint: Box::new(locations.bridge_origin_universal_location().clone()), + remote_endpoint: Box::new( + locations.bridge_destination_universal_location().clone(), + ), + lane_id, }); Ok(()) @@ -283,7 +308,7 @@ pub mod pallet { // update bridge metadata - this also guarantees that the bridge is in the proper state let bridge = - Bridges::::try_mutate_exists(locations.bridge_id, |bridge| match bridge { + Bridges::::try_mutate_exists(locations.bridge_id(), |bridge| match bridge { Some(bridge) => { bridge.state = BridgeState::Closed; Ok(bridge.clone()) @@ -294,10 +319,10 @@ pub mod pallet { // close inbound and outbound lanes let lanes_manager = LanesManagerOf::::new(); let mut inbound_lane = lanes_manager - .any_state_inbound_lane(locations.bridge_id.lane_id()) + .any_state_inbound_lane(bridge.lane_id) .map_err(Error::::LanesManager)?; let mut outbound_lane = lanes_manager - .any_state_outbound_lane(locations.bridge_id.lane_id()) + .any_state_outbound_lane(bridge.lane_id) .map_err(Error::::LanesManager)?; // now prune queued messages @@ -321,16 +346,18 @@ pub mod pallet { let enqueued_messages = outbound_lane.queued_messages().saturating_len(); log::trace!( target: LOG_TARGET, - "Bridge {:?} between {:?} and {:?} is closing. {} messages remaining", - locations.bridge_id, - locations.bridge_origin_universal_location, - locations.bridge_destination_universal_location, + "Bridge {:?} between {:?} and {:?} is closing lane_id: {:?}. {} messages remaining", + locations.bridge_id(), + locations.bridge_origin_universal_location(), + locations.bridge_destination_universal_location(), + bridge.lane_id, enqueued_messages, ); // deposit the `ClosingBridge` event Self::deposit_event(Event::::ClosingBridge { - bridge_id: locations.bridge_id, + bridge_id: locations.bridge_id().clone(), + lane_id: bridge.lane_id, pruned_messages, enqueued_messages, }); @@ -341,7 +368,8 @@ pub mod pallet { // else we have pruned all messages, so lanes and the bridge itself may gone inbound_lane.purge(); outbound_lane.purge(); - Bridges::::remove(locations.bridge_id); + Bridges::::remove(locations.bridge_id()); + LaneToBridge::::remove(bridge.lane_id); // return deposit let released_deposit = T::Currency::release( @@ -356,7 +384,7 @@ pub mod pallet { log::error!( target: LOG_TARGET, "Failed to unreserve during the bridge {:?} closure with error: {e:?}", - locations.bridge_id, + locations.bridge_id(), ); e }) @@ -365,15 +393,17 @@ pub mod pallet { // write something to log log::trace!( target: LOG_TARGET, - "Bridge {:?} between {:?} and {:?} has been closed, the bridge deposit {released_deposit:?} was returned", - locations.bridge_id, - locations.bridge_origin_universal_location, - locations.bridge_destination_universal_location, + "Bridge {:?} between {:?} and {:?} has closed lane_id: {:?}, the bridge deposit {released_deposit:?} was returned", + locations.bridge_id(), + bridge.lane_id, + locations.bridge_origin_universal_location(), + locations.bridge_destination_universal_location(), ); // deposit the `BridgePruned` event Self::deposit_event(Event::::BridgePruned { - bridge_id: locations.bridge_id, + bridge_id: locations.bridge_id().clone(), + lane_id: bridge.lane_id, bridge_deposit: released_deposit, pruned_messages, }); @@ -417,23 +447,23 @@ pub mod pallet { ) -> Result, sp_runtime::DispatchError> { Self::bridge_locations( Box::new(T::OpenBridgeOrigin::ensure_origin(origin)?), - bridge_destination_universal_location, + Box::new( + (*bridge_destination_universal_location) + .try_into() + .map_err(|_| Error::::UnsupportedXcmVersion)?, + ), ) } - /// Return bridge endpoint locations and dedicated lane identifier. + /// Return bridge endpoint locations and dedicated **bridge** identifier (`BridgeId`). pub fn bridge_locations( bridge_origin_relative_location: Box, - bridge_destination_universal_location: Box, + bridge_destination_universal_location: Box, ) -> Result, sp_runtime::DispatchError> { bridge_locations( Box::new(T::UniversalLocation::get()), bridge_origin_relative_location, - Box::new( - (*bridge_destination_universal_location) - .try_into() - .map_err(|_| Error::::UnsupportedXcmVersion)?, - ), + bridge_destination_universal_location, Self::bridged_network_id()?, ) .map_err(|e| { @@ -444,6 +474,12 @@ pub mod pallet { Error::::BridgeLocations(e).into() }) } + + /// Return bridge metadata by lane_id + pub fn bridge_by_lane_id(lane_id: &LaneId) -> Option<(BridgeId, BridgeOf)> { + LaneToBridge::::get(lane_id) + .and_then(|bridge_id| Self::bridge(bridge_id).map(|bridge| (bridge_id, bridge))) + } } impl, I: 'static> Pallet { @@ -459,11 +495,50 @@ pub mod pallet { } } + #[cfg(any(feature = "try-runtime", test))] + impl, I: 'static> Pallet { + /// Ensure the correctness of the state of this pallet. + pub fn do_try_state() -> Result<(), sp_runtime::TryRuntimeError> { + use sp_std::collections::btree_set::BTreeSet; + + let mut lanes = BTreeSet::new(); + + // check all known bridge configurations + for (bridge_id, bridge) in Bridges::::iter() { + log::info!(target: LOG_TARGET, "Checking `do_try_state` for bridge_id: {bridge_id:?} and bridge: {bridge:?}"); + lanes.insert(Self::do_try_state_for_bridge(bridge_id, bridge)?); + } + ensure!( + lanes.len() == Bridges::::iter().count(), + "Invalid `Bridges` configuration, probably two bridges handle the same laneId!" + ); + ensure!( + lanes.len() == LaneToBridge::::iter().count(), + "Invalid `LaneToBridge` configuration, probably missing or not removed laneId!" + ); + + Ok(()) + } + /// Ensure the correctness of the state of the bridge. + pub fn do_try_state_for_bridge( + bridge_id: BridgeId, + bridge: BridgeOf, + ) -> Result { + + Ok(bridge.lane_id) + } + } + /// All registered bridges. #[pallet::storage] #[pallet::getter(fn bridge)] pub type Bridges, I: 'static = ()> = StorageMap<_, Identity, BridgeId, BridgeOf>; + /// All registered `lane_id` and `bridge_id` mappings. + #[pallet::storage] + #[pallet::getter(fn lane_to_bridge)] + pub type LaneToBridge, I: 'static = ()> = + StorageMap<_, Identity, LaneId, BridgeId>; #[pallet::genesis_config] #[derive(DefaultNoBound)] @@ -492,22 +567,34 @@ pub mod pallet { Box::new(bridge_destination_universal_location.clone().into()), ) .expect("Invalid genesis configuration"); + let lane_id = + locations.calculate_lane_id(xcm::latest::VERSION).expect("Valid locations"); let bridge_owner_account = T::BridgeOriginAccountIdConverter::convert_location( - &locations.bridge_origin_relative_location, + locations.bridge_origin_relative_location(), ) .expect("Invalid genesis configuration"); Bridges::::insert( - locations.bridge_id, + locations.bridge_id(), Bridge { bridge_origin_relative_location: Box::new( - locations.bridge_origin_relative_location.into(), + locations.bridge_origin_relative_location().clone().into(), ), state: BridgeState::Opened, bridge_owner_account, reserve: Zero::zero(), + lane_id, }, ); + LaneToBridge::::insert(lane_id, locations.bridge_id()); + + let lanes_manager = LanesManagerOf::::new(); + lanes_manager + .create_inbound_lane(lane_id) + .expect("Invalid genesis configuration"); + lanes_manager + .create_outbound_lane(lane_id) + .expect("Invalid genesis configuration"); } } } @@ -517,19 +604,24 @@ pub mod pallet { pub enum Event, I: 'static = ()> { /// The bridge between two locations has been opened. BridgeOpened { - /// Universal location of local bridge endpoint. - local_endpoint: Box, - /// Universal location of remote bridge endpoint. - remote_endpoint: Box, /// Bridge identifier. bridge_id: BridgeId, /// Amount of deposit held. bridge_deposit: Option>>, + + /// Universal location of local bridge endpoint. + local_endpoint: Box, + /// Universal location of remote bridge endpoint. + remote_endpoint: Box, + /// Lane identifier. + lane_id: LaneId, }, /// Bridge is going to be closed, but not yet fully pruned from the runtime storage. ClosingBridge { /// Bridge identifier. bridge_id: BridgeId, + /// Lane identifier. + lane_id: LaneId, /// Number of pruned messages during the close call. pruned_messages: MessageNonce, /// Number of enqueued messages that need to be pruned in follow up calls. @@ -540,6 +632,8 @@ pub mod pallet { BridgePruned { /// Bridge identifier. bridge_id: BridgeId, + /// Lane identifier. + lane_id: LaneId, /// Amount of deposit released. bridge_deposit: Option>>, /// Number of pruned messages during the close call. @@ -576,12 +670,14 @@ mod tests { use mock::*; use bp_messages::LaneId; - use frame_support::{assert_noop, assert_ok, traits::fungible::Mutate, BoundedVec}; + use frame_support::{assert_err, assert_noop, assert_ok, traits::fungible::Mutate, BoundedVec}; use frame_system::{EventRecord, Phase}; + use sp_core::H256; + use sp_runtime::TryRuntimeError; fn fund_origin_sovereign_account(locations: &BridgeLocations, balance: Balance) -> AccountId { let bridge_owner_account = - LocationToAccountId::convert_location(&locations.bridge_origin_relative_location) + LocationToAccountId::convert_location(locations.bridge_origin_relative_location()) .unwrap(); assert_ok!(Balances::mint_into(&bridge_owner_account, balance)); bridge_owner_account @@ -594,23 +690,28 @@ mod tests { let reserve = BridgeDeposit::get(); let locations = XcmOverBridge::bridge_locations_from_origin(origin, Box::new(with.into())).unwrap(); + let lane_id = locations.calculate_lane_id(xcm::latest::VERSION).unwrap(); let bridge_owner_account = fund_origin_sovereign_account(&locations, reserve + ExistentialDeposit::get()); Balances::hold(&HoldReason::BridgeDeposit.into(), &bridge_owner_account, reserve).unwrap(); let bridge = Bridge { bridge_origin_relative_location: Box::new( - locations.bridge_origin_relative_location.clone().into(), + locations.bridge_origin_relative_location().clone().into(), ), state: BridgeState::Opened, bridge_owner_account, reserve, + lane_id, }; - Bridges::::insert(locations.bridge_id, bridge.clone()); + Bridges::::insert(locations.bridge_id(), bridge.clone()); + LaneToBridge::::insert(bridge.lane_id, locations.bridge_id()); let lanes_manager = LanesManagerOf::::new(); - lanes_manager.create_inbound_lane(locations.bridge_id.lane_id()).unwrap(); - lanes_manager.create_outbound_lane(locations.bridge_id.lane_id()).unwrap(); + lanes_manager.create_inbound_lane(bridge.lane_id).unwrap(); + lanes_manager.create_outbound_lane(bridge.lane_id).unwrap(); + + assert_ok!(XcmOverBridge::do_try_state()); (bridge, *locations) } @@ -739,20 +840,22 @@ mod tests { Box::new(bridged_asset_hub_universal_location().into()), ) .unwrap(); + let lane_id = locations.calculate_lane_id(xcm::latest::VERSION).unwrap(); fund_origin_sovereign_account( &locations, BridgeDeposit::get() + ExistentialDeposit::get(), ); Bridges::::insert( - locations.bridge_id, + locations.bridge_id(), Bridge { bridge_origin_relative_location: Box::new( - locations.bridge_origin_relative_location.into(), + locations.bridge_origin_relative_location().clone().into(), ), state: BridgeState::Opened, bridge_owner_account: [0u8; 32].into(), reserve: 0, + lane_id, }, ); @@ -775,6 +878,7 @@ mod tests { Box::new(bridged_asset_hub_universal_location().into()), ) .unwrap(); + let lane_id = locations.calculate_lane_id(xcm::latest::VERSION).unwrap(); fund_origin_sovereign_account( &locations, BridgeDeposit::get() + ExistentialDeposit::get(), @@ -782,7 +886,7 @@ mod tests { let lanes_manager = LanesManagerOf::::new(); - lanes_manager.create_inbound_lane(locations.bridge_id.lane_id()).unwrap(); + lanes_manager.create_inbound_lane(lane_id).unwrap(); assert_noop!( XcmOverBridge::open_bridge( origin.clone(), @@ -791,11 +895,8 @@ mod tests { Error::::LanesManager(LanesManagerError::InboundLaneAlreadyExists), ); - lanes_manager - .active_inbound_lane(locations.bridge_id.lane_id()) - .unwrap() - .purge(); - lanes_manager.create_outbound_lane(locations.bridge_id.lane_id()).unwrap(); + lanes_manager.active_inbound_lane(lane_id).unwrap().purge(); + lanes_manager.create_outbound_lane(lane_id).unwrap(); assert_noop!( XcmOverBridge::open_bridge( origin, @@ -828,22 +929,29 @@ mod tests { System::reset_events(); // compute all other locations + let xcm_version = xcm::latest::VERSION; let locations = XcmOverBridge::bridge_locations_from_origin( origin.clone(), - Box::new(bridged_asset_hub_universal_location().into()), + Box::new( + VersionedInteriorLocation::from(bridged_asset_hub_universal_location()) + .into_version(xcm_version) + .expect("valid conversion"), + ), ) .unwrap(); + let lane_id = locations.calculate_lane_id(xcm_version).unwrap(); // ensure that there's no bridge and lanes in the storage - assert_eq!(Bridges::::get(locations.bridge_id), None); + assert_eq!(Bridges::::get(locations.bridge_id()), None); assert_eq!( - lanes_manager.active_inbound_lane(locations.bridge_id.lane_id()).map(drop), + lanes_manager.active_inbound_lane(lane_id).map(drop), Err(LanesManagerError::UnknownInboundLane) ); assert_eq!( - lanes_manager.active_outbound_lane(locations.bridge_id.lane_id()).map(drop), + lanes_manager.active_outbound_lane(lane_id).map(drop), Err(LanesManagerError::UnknownOutboundLane) ); + assert_eq!(LaneToBridge::::get(lane_id), None); // give enough funds to the sovereign account of the bridge origin let bridge_owner_account = fund_origin_sovereign_account( @@ -859,33 +967,34 @@ mod tests { // now open the bridge assert_ok!(XcmOverBridge::open_bridge( origin, - Box::new(locations.bridge_destination_universal_location.clone().into()), + Box::new(locations.bridge_destination_universal_location().clone().into()), )); // ensure that everything has been set up in the runtime storage assert_eq!( - Bridges::::get(locations.bridge_id), + Bridges::::get(locations.bridge_id()), Some(Bridge { bridge_origin_relative_location: Box::new( - locations.bridge_origin_relative_location.into() + locations.bridge_origin_relative_location().clone().into() ), state: BridgeState::Opened, bridge_owner_account: bridge_owner_account.clone(), reserve: expected_reserve, + lane_id }), ); assert_eq!( - lanes_manager - .active_inbound_lane(locations.bridge_id.lane_id()) - .map(|l| l.state()), + lanes_manager.active_inbound_lane(lane_id).map(|l| l.state()), Ok(LaneState::Opened) ); assert_eq!( - lanes_manager - .active_outbound_lane(locations.bridge_id.lane_id()) - .map(|l| l.state()), + lanes_manager.active_outbound_lane(lane_id).map(|l| l.state()), Ok(LaneState::Opened) ); + assert_eq!( + LaneToBridge::::get(lane_id), + Some(locations.bridge_id().clone()) + ); assert_eq!(Balances::free_balance(&bridge_owner_account), existential_deposit); assert_eq!(Balances::reserved_balance(&bridge_owner_account), expected_reserve); @@ -895,12 +1004,15 @@ mod tests { Some(&EventRecord { phase: Phase::Initialization, event: RuntimeEvent::XcmOverBridge(Event::BridgeOpened { - bridge_id: locations.bridge_id, + bridge_id: locations.bridge_id().clone(), bridge_deposit: Some(BridgeDeposit::get()), - local_endpoint: Box::new(locations.bridge_origin_universal_location), + local_endpoint: Box::new( + locations.bridge_origin_universal_location().clone() + ), remote_endpoint: Box::new( - locations.bridge_destination_universal_location + locations.bridge_destination_universal_location().clone() ), + lane_id }), topics: vec![], }), @@ -954,35 +1066,26 @@ mod tests { fn close_bridge_fails_if_its_lanes_are_unknown() { run_test(|| { let origin = OpenBridgeOrigin::parent_relay_chain_origin(); - let (_, locations) = mock_open_bridge_from(origin.clone()); + let (bridge, locations) = mock_open_bridge_from(origin.clone()); let lanes_manager = LanesManagerOf::::new(); - lanes_manager - .any_state_inbound_lane(locations.bridge_id.lane_id()) - .unwrap() - .purge(); + lanes_manager.any_state_inbound_lane(bridge.lane_id).unwrap().purge(); assert_noop!( XcmOverBridge::close_bridge( origin.clone(), - Box::new(locations.bridge_destination_universal_location.into()), + Box::new(locations.bridge_destination_universal_location().clone().into()), 0, ), Error::::LanesManager(LanesManagerError::UnknownInboundLane), ); - lanes_manager - .any_state_outbound_lane(locations.bridge_id.lane_id()) - .unwrap() - .purge(); + lanes_manager.any_state_outbound_lane(bridge.lane_id).unwrap().purge(); let (_, locations) = mock_open_bridge_from(origin.clone()); - lanes_manager - .any_state_outbound_lane(locations.bridge_id.lane_id()) - .unwrap() - .purge(); + lanes_manager.any_state_outbound_lane(bridge.lane_id).unwrap().purge(); assert_noop!( XcmOverBridge::close_bridge( origin, - Box::new(locations.bridge_destination_universal_location.into()), + Box::new(locations.bridge_destination_universal_location().clone().into()), 0, ), Error::::LanesManager(LanesManagerError::UnknownOutboundLane), @@ -1003,13 +1106,13 @@ mod tests { // enqueue some messages for _ in 0..32 { - enqueue_message(locations.bridge_id.lane_id()); + enqueue_message(bridge.lane_id); } // now call the `close_bridge`, which will only partially prune messages assert_ok!(XcmOverBridge::close_bridge( origin.clone(), - Box::new(locations.bridge_destination_universal_location.clone().into()), + Box::new(locations.bridge_destination_universal_location().clone().into()), 16, ),); @@ -1017,31 +1120,29 @@ mod tests { // are pruned, but funds are not unreserved let lanes_manager = LanesManagerOf::::new(); assert_eq!( - Bridges::::get(locations.bridge_id).map(|b| b.state), + Bridges::::get(locations.bridge_id()).map(|b| b.state), Some(BridgeState::Closed) ); assert_eq!( - lanes_manager - .any_state_inbound_lane(locations.bridge_id.lane_id()) - .unwrap() - .state(), + lanes_manager.any_state_inbound_lane(bridge.lane_id).unwrap().state(), LaneState::Closed ); assert_eq!( - lanes_manager - .any_state_outbound_lane(locations.bridge_id.lane_id()) - .unwrap() - .state(), + lanes_manager.any_state_outbound_lane(bridge.lane_id).unwrap().state(), LaneState::Closed ); assert_eq!( lanes_manager - .any_state_outbound_lane(locations.bridge_id.lane_id()) + .any_state_outbound_lane(bridge.lane_id) .unwrap() .queued_messages() .checked_len(), Some(16) ); + assert_eq!( + LaneToBridge::::get(bridge.lane_id), + Some(locations.bridge_id().clone()) + ); assert_eq!(Balances::free_balance(&bridge.bridge_owner_account), free_balance); assert_eq!(Balances::reserved_balance(&bridge.bridge_owner_account), reserved_balance); assert_eq!( @@ -1049,7 +1150,8 @@ mod tests { Some(&EventRecord { phase: Phase::Initialization, event: RuntimeEvent::XcmOverBridge(Event::ClosingBridge { - bridge_id: locations.bridge_id, + bridge_id: locations.bridge_id().clone(), + lane_id: bridge.lane_id, pruned_messages: 16, enqueued_messages: 16, }), @@ -1060,37 +1162,35 @@ mod tests { // now call the `close_bridge` again, which will only partially prune messages assert_ok!(XcmOverBridge::close_bridge( origin.clone(), - Box::new(locations.bridge_destination_universal_location.clone().into()), + Box::new(locations.bridge_destination_universal_location().clone().into()), 8, ),); // nothing is changed (apart from the pruned messages) assert_eq!( - Bridges::::get(locations.bridge_id).map(|b| b.state), + Bridges::::get(locations.bridge_id()).map(|b| b.state), Some(BridgeState::Closed) ); assert_eq!( - lanes_manager - .any_state_inbound_lane(locations.bridge_id.lane_id()) - .unwrap() - .state(), + lanes_manager.any_state_inbound_lane(bridge.lane_id).unwrap().state(), LaneState::Closed ); assert_eq!( - lanes_manager - .any_state_outbound_lane(locations.bridge_id.lane_id()) - .unwrap() - .state(), + lanes_manager.any_state_outbound_lane(bridge.lane_id).unwrap().state(), LaneState::Closed ); assert_eq!( lanes_manager - .any_state_outbound_lane(locations.bridge_id.lane_id()) + .any_state_outbound_lane(bridge.lane_id) .unwrap() .queued_messages() .checked_len(), Some(8) ); + assert_eq!( + LaneToBridge::::get(bridge.lane_id), + Some(locations.bridge_id().clone()) + ); assert_eq!(Balances::free_balance(&bridge.bridge_owner_account), free_balance); assert_eq!(Balances::reserved_balance(&bridge.bridge_owner_account), reserved_balance); assert_eq!( @@ -1098,7 +1198,8 @@ mod tests { Some(&EventRecord { phase: Phase::Initialization, event: RuntimeEvent::XcmOverBridge(Event::ClosingBridge { - bridge_id: locations.bridge_id, + bridge_id: locations.bridge_id().clone(), + lane_id: bridge.lane_id, pruned_messages: 8, enqueued_messages: 8, }), @@ -1110,20 +1211,24 @@ mod tests { // bridge assert_ok!(XcmOverBridge::close_bridge( origin, - Box::new(locations.bridge_destination_universal_location.into()), + Box::new(locations.bridge_destination_universal_location().clone().into()), 9, ),); // there's no traces of bridge in the runtime storage and funds are unreserved - assert_eq!(Bridges::::get(locations.bridge_id).map(|b| b.state), None); assert_eq!( - lanes_manager.any_state_inbound_lane(locations.bridge_id.lane_id()).map(drop), + Bridges::::get(locations.bridge_id()).map(|b| b.state), + None + ); + assert_eq!( + lanes_manager.any_state_inbound_lane(bridge.lane_id).map(drop), Err(LanesManagerError::UnknownInboundLane) ); assert_eq!( - lanes_manager.any_state_outbound_lane(locations.bridge_id.lane_id()).map(drop), + lanes_manager.any_state_outbound_lane(bridge.lane_id).map(drop), Err(LanesManagerError::UnknownOutboundLane) ); + assert_eq!(LaneToBridge::::get(bridge.lane_id), None); assert_eq!( Balances::free_balance(&bridge.bridge_owner_account), free_balance + reserved_balance @@ -1134,7 +1239,8 @@ mod tests { Some(&EventRecord { phase: Phase::Initialization, event: RuntimeEvent::XcmOverBridge(Event::BridgePruned { - bridge_id: locations.bridge_id, + bridge_id: locations.bridge_id().clone(), + lane_id: bridge.lane_id, bridge_deposit: Some(BridgeDeposit::get()), pruned_messages: 8, }), @@ -1143,4 +1249,48 @@ mod tests { ); }); } + + #[test] + fn do_try_state_works() { + use sp_runtime::Either; + + let older_xcm_version = xcm::latest::VERSION - 1; + let bridge_origin_relative_location = Location::new(1, [Parachain(2025)]); + let bridge_owner_account = + LocationToAccountId::convert_location(&bridge_origin_relative_location) + .expect("valid accountId"); + let bridge_id = BridgeId::from_inner(H256::from([1u8; 32])); + let lane_id = LaneId::from_inner(Either::Left(H256::default())); + + let test_bridge_state = + |id, bridge, (lane_id, bridge_id), expected_error: Option| { + Bridges::::insert(id, bridge); + LaneToBridge::::insert(lane_id, bridge_id); + + let result = XcmOverBridge::do_try_state(); + if let Some(e) = expected_error { + assert_err!(XcmOverBridge::do_try_state(), e); + } else { + assert_ok!(result); + } + }; + + run_test(|| { + // ok state + test_bridge_state( + bridge_id, + Bridge { + bridge_origin_relative_location: Box::new(VersionedLocation::from( + bridge_origin_relative_location.clone(), + )), + state: BridgeState::Opened, + bridge_owner_account: bridge_owner_account.clone(), + reserve: Zero::zero(), + lane_id, + }, + (lane_id, bridge_id), + None, + ); + }); + } } diff --git a/bridges/primitives/messages/src/lane.rs b/bridges/primitives/messages/src/lane.rs index cd085096f1b6..60c25381ed89 100644 --- a/bridges/primitives/messages/src/lane.rs +++ b/bridges/primitives/messages/src/lane.rs @@ -26,8 +26,8 @@ use sp_io::hashing::blake2_256; /// Bridge lane identifier. /// /// Lane connects two endpoints at both sides of the bridge. We assume that every endpoint -/// has its own unique identifier. We want lane identifiers to be the same on the both sides -/// of the bridge (and naturally unique across global consensus if endpoints have unique +/// has its own unique identifier. We want lane identifiers to be **the same on the both sides +/// of the bridge** (and naturally unique across global consensus if endpoints have unique /// identifiers). So lane id is the hash (`blake2_256`) of **ordered** encoded locations /// concatenation (separated by some binary data). I.e.: /// diff --git a/bridges/primitives/xcm-bridge-hub/Cargo.toml b/bridges/primitives/xcm-bridge-hub/Cargo.toml index 9949108af21f..9dc7403fb966 100644 --- a/bridges/primitives/xcm-bridge-hub/Cargo.toml +++ b/bridges/primitives/xcm-bridge-hub/Cargo.toml @@ -21,6 +21,8 @@ bp-runtime = { path = "../runtime", default-features = false } # Substrate Dependencies sp-std = { workspace = true } +sp-io = { workspace = true } +sp-core = { workspace = true } frame-support = { workspace = true } # Polkadot Dependencies @@ -35,6 +37,8 @@ std = [ "frame-support/std", "scale-info/std", "serde/std", + "sp-core/std", + "sp-io/std", "sp-std/std", "xcm/std", ] diff --git a/bridges/primitives/xcm-bridge-hub/src/lib.rs b/bridges/primitives/xcm-bridge-hub/src/lib.rs index ba2ef20fd14f..3d7058d3480a 100644 --- a/bridges/primitives/xcm-bridge-hub/src/lib.rs +++ b/bridges/primitives/xcm-bridge-hub/src/lib.rs @@ -28,14 +28,29 @@ use frame_support::{ }; use scale_info::TypeInfo; use serde::{Deserialize, Serialize}; +use sp_core::H256; +use sp_io::hashing::blake2_256; use sp_std::boxed::Box; -use xcm::{latest::prelude::*, VersionedInteriorLocation, VersionedLocation}; +use xcm::{ + latest::prelude::*, prelude::XcmVersion, IntoVersion, VersionedInteriorLocation, + VersionedLocation, +}; /// Encoded XCM blob. We expect the bridge messages pallet to use this blob type for both inbound /// and outbound payloads. pub type XcmAsPlainPayload = sp_std::vec::Vec; -/// Bridge identifier. +/// Bridge identifier - used **only** for communicating with sibling/parent chains in the same +/// consensus. +/// +/// For example, `SendXcm` implementations (which use the `latest` XCM) can use it to identify a +/// bridge and the corresponding `LaneId` that is used for over-consensus communication between +/// bridge hubs. +/// +/// This identifier is constructed from the `latest` XCM, so it is expected to ensure migration to +/// the `latest` XCM version. This could change the `BridgeId`, but it will not affect the `LaneId`. +/// In other words, `LaneId` will never change, while `BridgeId` could change with (every) XCM +/// upgrade. #[derive( Clone, Copy, @@ -51,47 +66,28 @@ pub type XcmAsPlainPayload = sp_std::vec::Vec; Serialize, Deserialize, )] -pub struct BridgeId(LaneId); +pub struct BridgeId(H256); impl BridgeId { /// Create bridge identifier from two universal locations. /// - /// The fact that we are using versioned locations here means that XCM version upgrades must - /// be coordinated at all involved chains (at source and target chains + at bridge hubs). - /// Otherwise, messages may simply be dropped anywhere on its path to the target chain. - pub fn new( - universal_location1: &VersionedInteriorLocation, - universal_location2: &VersionedInteriorLocation, - ) -> Self { - // a tricky helper struct that adds required `Ord` support for - // `VersionedInteriorMultiLocation` - #[derive(Eq, PartialEq, Ord, PartialOrd)] - struct EncodedVersionedInteriorMultiLocation(sp_std::vec::Vec); - - impl Encode for EncodedVersionedInteriorMultiLocation { - fn encode(&self) -> sp_std::vec::Vec { - self.0.clone() - } - } - - Self(LaneId::new( - EncodedVersionedInteriorMultiLocation(universal_location1.encode()), - EncodedVersionedInteriorMultiLocation(universal_location2.encode()), - )) - } - - /// Creates bridge id using lane id. - /// - /// **ATTENTION**: this function may be removed in the future. - pub fn from_lane_id(lane_id: LaneId) -> Self { - // in the future we may want to keep using the same lane identifiers if we'll be upgrading - // the XCM version (and `VersionedInteriorMultiLocation` will change) - Self(lane_id) + /// Note: The `BridgeId` is constructed from `latest` XCM, so if stored, you need to ensure + /// compatibility with newer XCM versions. + fn new(universal_source: &InteriorLocation, universal_destination: &InteriorLocation) -> Self { + const VALUES_SEPARATOR: [u8; 33] = *b"bridges-bridge-id-value-separator"; + + BridgeId( + (universal_source, VALUES_SEPARATOR, universal_destination) + .using_encoded(blake2_256) + .into(), + ) } - /// Return lane id, used by this bridge. - pub fn lane_id(&self) -> LaneId { - self.0 + /// Create bridge identifier from given hash. + /// (for testing purposes) + #[cfg(any(feature = "std", test))] + pub const fn from_inner(inner: H256) -> Self { + BridgeId(inner) } } @@ -160,23 +156,26 @@ pub struct Bridge { pub bridge_origin_relative_location: Box, /// Current bridge state. pub state: BridgeState, - /// Account with the reserved funds. + /// Account with the reserved funds. Derived from `self.bridge_origin_relative_location`. pub bridge_owner_account: AccountIdOf, /// Reserved amount on the sovereign account of the sibling bridge origin. pub reserve: BalanceOf, + + /// Mapping to the unique `LaneId`. + pub lane_id: LaneId, } /// Locations of bridge endpoints at both sides of the bridge. #[derive(Clone, RuntimeDebug, PartialEq, Eq)] pub struct BridgeLocations { /// Relative (to this bridge hub) location of this side of the bridge. - pub bridge_origin_relative_location: Location, + bridge_origin_relative_location: Location, /// Universal (unique) location of this side of the bridge. - pub bridge_origin_universal_location: InteriorLocation, + bridge_origin_universal_location: InteriorLocation, /// Universal (unique) location of other side of the bridge. - pub bridge_destination_universal_location: InteriorLocation, + bridge_destination_universal_location: InteriorLocation, /// An identifier of the dedicated bridge message lane. - pub bridge_id: BridgeId, + bridge_id: BridgeId, } /// Errors that may happen when we check bridge locations. @@ -195,6 +194,8 @@ pub enum BridgeLocationsError { /// Destination location is unsupported. We only support bridges with relay /// chain or its parachains. UnsupportedDestinationLocation, + /// The version of XCM location argument is unsupported. + UnsupportedXcmVersion, } /// Given XCM locations, generate lane id and universal locations of bridge endpoints. @@ -271,10 +272,8 @@ pub fn bridge_locations( // `GlobalConsensus` and we know that the `bridge_origin_universal_location` // is also within the `GlobalConsensus`. So we know that the lane id will be // the same on both ends of the bridge - let bridge_id = BridgeId::new( - &bridge_origin_universal_location.clone().into(), - &bridge_destination_universal_location.clone().into(), - ); + let bridge_id = + BridgeId::new(&bridge_origin_universal_location, &bridge_destination_universal_location); Ok(Box::new(BridgeLocations { bridge_origin_relative_location: *bridge_origin_relative_location, @@ -284,6 +283,60 @@ pub fn bridge_locations( })) } +impl BridgeLocations { + /// Getter for `bridge_origin_relative_location` + pub fn bridge_origin_relative_location(&self) -> &Location { + &self.bridge_origin_relative_location + } + + /// Getter for `bridge_origin_universal_location` + pub fn bridge_origin_universal_location(&self) -> &InteriorLocation { + &self.bridge_origin_universal_location + } + + /// Getter for `bridge_destination_universal_location` + pub fn bridge_destination_universal_location(&self) -> &InteriorLocation { + &self.bridge_destination_universal_location + } + + /// Getter for `bridge_id` + pub fn bridge_id(&self) -> &BridgeId { + &self.bridge_id + } + + /// Generates the exact same `LaneId` on the both bridge hubs. + /// + /// Note: Use this **only** when opening a new bridge. + pub fn calculate_lane_id( + &self, + xcm_version: XcmVersion, + ) -> Result { + // a tricky helper struct that adds required `Ord` support for + // `VersionedInteriorLocation` + #[derive(Eq, PartialEq, Ord, PartialOrd)] + struct EncodedVersionedInteriorLocation(sp_std::vec::Vec); + impl Encode for EncodedVersionedInteriorLocation { + fn encode(&self) -> sp_std::vec::Vec { + self.0.clone() + } + } + + let universal_location1 = + VersionedInteriorLocation::from(self.bridge_origin_universal_location.clone()) + .into_version(xcm_version) + .map_err(|_| BridgeLocationsError::UnsupportedXcmVersion); + let universal_location2 = + VersionedInteriorLocation::from(self.bridge_destination_universal_location.clone()) + .into_version(xcm_version) + .map_err(|_| BridgeLocationsError::UnsupportedXcmVersion); + + Ok(LaneId::new( + EncodedVersionedInteriorLocation(universal_location1.encode()), + EncodedVersionedInteriorLocation(universal_location2.encode()), + )) + } +} + #[cfg(test)] mod tests { use super::*; @@ -301,6 +354,8 @@ mod tests { bridge_origin_universal_location: InteriorLocation, bridge_destination_universal_location: InteriorLocation, + + expected_remote_network: NetworkId, } fn run_successful_test(test: SuccessfulTest) -> BridgeLocations { @@ -308,7 +363,7 @@ mod tests { Box::new(test.here_universal_location), Box::new(test.bridge_origin_relative_location.clone()), Box::new(test.bridge_destination_universal_location.clone()), - REMOTE_NETWORK, + test.expected_remote_network, ); assert_eq!( locations, @@ -338,6 +393,8 @@ mod tests { bridge_origin_universal_location: [GlobalConsensus(LOCAL_NETWORK)].into(), bridge_destination_universal_location: [GlobalConsensus(REMOTE_NETWORK)].into(), + + expected_remote_network: REMOTE_NETWORK, }); } @@ -353,6 +410,8 @@ mod tests { ] .into(), bridge_destination_universal_location: [GlobalConsensus(REMOTE_NETWORK)].into(), + + expected_remote_network: REMOTE_NETWORK, }); } @@ -368,6 +427,8 @@ mod tests { Parachain(REMOTE_PARACHAIN), ] .into(), + + expected_remote_network: REMOTE_NETWORK, }); } @@ -387,6 +448,8 @@ mod tests { Parachain(REMOTE_PARACHAIN), ] .into(), + + expected_remote_network: REMOTE_NETWORK, }); } @@ -399,6 +462,8 @@ mod tests { bridge_origin_universal_location: [GlobalConsensus(LOCAL_NETWORK)].into(), bridge_destination_universal_location: [GlobalConsensus(REMOTE_NETWORK)].into(), + + expected_remote_network: REMOTE_NETWORK, }); } @@ -416,6 +481,8 @@ mod tests { ] .into(), bridge_destination_universal_location: [GlobalConsensus(REMOTE_NETWORK)].into(), + + expected_remote_network: REMOTE_NETWORK, }); } @@ -432,6 +499,8 @@ mod tests { Parachain(REMOTE_PARACHAIN), ] .into(), + + expected_remote_network: REMOTE_NETWORK, }); } @@ -453,6 +522,8 @@ mod tests { Parachain(REMOTE_PARACHAIN), ] .into(), + + expected_remote_network: REMOTE_NETWORK, }); } @@ -466,6 +537,8 @@ mod tests { bridge_origin_universal_location: [GlobalConsensus(LOCAL_NETWORK)].into(), bridge_destination_universal_location: [GlobalConsensus(REMOTE_NETWORK)].into(), + + expected_remote_network: REMOTE_NETWORK, }); let locations2 = run_successful_test(SuccessfulTest { here_universal_location: [GlobalConsensus(LOCAL_NETWORK)].into(), @@ -473,6 +546,8 @@ mod tests { bridge_origin_universal_location: [GlobalConsensus(LOCAL_NETWORK)].into(), bridge_destination_universal_location: [GlobalConsensus(REMOTE_NETWORK)].into(), + + expected_remote_network: REMOTE_NETWORK, }); assert_eq!(locations1.bridge_id, locations2.bridge_id); @@ -486,6 +561,8 @@ mod tests { bridge_origin_universal_location: [GlobalConsensus(LOCAL_NETWORK)].into(), bridge_destination_universal_location: [GlobalConsensus(REMOTE_NETWORK)].into(), + + expected_remote_network: REMOTE_NETWORK, }); let locations2 = run_successful_test(SuccessfulTest { here_universal_location: [GlobalConsensus(LOCAL_NETWORK)].into(), @@ -493,11 +570,65 @@ mod tests { bridge_origin_universal_location: [GlobalConsensus(LOCAL_NETWORK)].into(), bridge_destination_universal_location: [GlobalConsensus(REMOTE_NETWORK)].into(), + + expected_remote_network: REMOTE_NETWORK, }); assert_eq!(locations1.bridge_id, locations2.bridge_id); } + #[test] + fn calculate_lane_id_works() { + let from_local_to_remote = run_successful_test(SuccessfulTest { + here_universal_location: [GlobalConsensus(LOCAL_NETWORK), Parachain(LOCAL_BRIDGE_HUB)] + .into(), + bridge_origin_relative_location: ParentThen([Parachain(SIBLING_PARACHAIN)].into()) + .into(), + + bridge_origin_universal_location: [ + GlobalConsensus(LOCAL_NETWORK), + Parachain(SIBLING_PARACHAIN), + ] + .into(), + bridge_destination_universal_location: [ + GlobalConsensus(REMOTE_NETWORK), + Parachain(REMOTE_PARACHAIN), + ] + .into(), + + expected_remote_network: REMOTE_NETWORK, + }); + + let from_remote_to_local = run_successful_test(SuccessfulTest { + here_universal_location: [GlobalConsensus(REMOTE_NETWORK), Parachain(LOCAL_BRIDGE_HUB)] + .into(), + bridge_origin_relative_location: ParentThen([Parachain(REMOTE_PARACHAIN)].into()) + .into(), + + bridge_origin_universal_location: [ + GlobalConsensus(REMOTE_NETWORK), + Parachain(REMOTE_PARACHAIN), + ] + .into(), + bridge_destination_universal_location: [ + GlobalConsensus(LOCAL_NETWORK), + Parachain(SIBLING_PARACHAIN), + ] + .into(), + + expected_remote_network: LOCAL_NETWORK, + }); + + assert_ne!( + from_local_to_remote.calculate_lane_id(xcm::latest::VERSION), + from_remote_to_local.calculate_lane_id(xcm::latest::VERSION - 1), + ); + assert_eq!( + from_local_to_remote.calculate_lane_id(xcm::latest::VERSION), + from_remote_to_local.calculate_lane_id(xcm::latest::VERSION), + ); + } + // negative tests #[test]