pallet_xcm_bridge_hub/
dispatcher.rs

1// Copyright 2019-2021 Parity Technologies (UK) Ltd.
2// This file is part of Parity Bridges Common.
3
4// Parity Bridges Common is free software: you can redistribute it and/or modify
5// it under the terms of the GNU General Public License as published by
6// the Free Software Foundation, either version 3 of the License, or
7// (at your option) any later version.
8
9// Parity Bridges Common is distributed in the hope that it will be useful,
10// but WITHOUT ANY WARRANTY; without even the implied warranty of
11// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12// GNU General Public License for more details.
13
14// You should have received a copy of the GNU General Public License
15// along with Parity Bridges Common.  If not, see <http://www.gnu.org/licenses/>.
16
17//! The code that allows to use the pallet (`pallet-xcm-bridge-hub`) as inbound
18//! bridge messages dispatcher. Internally, it just forwards inbound blob to the
19//! XCM-level blob dispatcher, which pushes message to some other queue (e.g.
20//! to HRMP queue with the sibling target chain).
21//!
22//! This code is executed at the target bridge hub.
23
24use crate::{Config, Pallet, LOG_TARGET};
25
26use bp_messages::target_chain::{DispatchMessage, MessageDispatch};
27use bp_runtime::messages::MessageDispatchResult;
28use bp_xcm_bridge_hub::{LocalXcmChannelManager, XcmAsPlainPayload};
29use codec::{Decode, DecodeWithMemTracking, Encode};
30use frame_support::{weights::Weight, CloneNoBound, EqNoBound, PartialEqNoBound};
31use pallet_bridge_messages::{Config as BridgeMessagesConfig, WeightInfoExt};
32use scale_info::TypeInfo;
33use sp_runtime::SaturatedConversion;
34use xcm::prelude::*;
35use xcm_builder::{DispatchBlob, DispatchBlobError};
36
37/// Message dispatch result type for single message.
38#[derive(
39	CloneNoBound,
40	EqNoBound,
41	PartialEqNoBound,
42	Encode,
43	Decode,
44	DecodeWithMemTracking,
45	Debug,
46	TypeInfo,
47)]
48pub enum XcmBlobMessageDispatchResult {
49	/// We've been unable to decode message payload.
50	InvalidPayload,
51	/// Message has been dispatched.
52	Dispatched,
53	/// Message has **NOT** been dispatched because of given error.
54	NotDispatched(#[codec(skip)] Option<DispatchBlobError>),
55}
56
57/// An easy way to access associated messages pallet weights.
58type MessagesPalletWeights<T, I> =
59	<T as BridgeMessagesConfig<<T as Config<I>>::BridgeMessagesPalletInstance>>::WeightInfo;
60
61impl<T: Config<I>, I: 'static> MessageDispatch for Pallet<T, I>
62where
63	T: BridgeMessagesConfig<T::BridgeMessagesPalletInstance, InboundPayload = XcmAsPlainPayload>,
64{
65	type DispatchPayload = XcmAsPlainPayload;
66	type DispatchLevelResult = XcmBlobMessageDispatchResult;
67	type LaneId = T::LaneId;
68
69	fn is_active(lane: Self::LaneId) -> bool {
70		Pallet::<T, I>::bridge_by_lane_id(&lane)
71			.and_then(|(_, bridge)| (*bridge.bridge_origin_relative_location).try_into().ok())
72			.map(|recipient: Location| !T::LocalXcmChannelManager::is_congested(&recipient))
73			.unwrap_or(false)
74	}
75
76	fn dispatch_weight(
77		message: &mut DispatchMessage<Self::DispatchPayload, Self::LaneId>,
78	) -> Weight {
79		match message.data.payload {
80			Ok(ref payload) => {
81				let payload_size = payload.encoded_size().saturated_into();
82				MessagesPalletWeights::<T, I>::message_dispatch_weight(payload_size)
83			},
84			Err(_) => Weight::zero(),
85		}
86	}
87
88	fn dispatch(
89		message: DispatchMessage<Self::DispatchPayload, Self::LaneId>,
90	) -> MessageDispatchResult<Self::DispatchLevelResult> {
91		let payload = match message.data.payload {
92			Ok(payload) => payload,
93			Err(e) => {
94				log::error!(
95					target: LOG_TARGET,
96					"dispatch - payload error: {e:?} for lane_id: {:?} and message_nonce: {:?}",
97					message.key.lane_id,
98					message.key.nonce
99				);
100				return MessageDispatchResult {
101					unspent_weight: Weight::zero(),
102					dispatch_level_result: XcmBlobMessageDispatchResult::InvalidPayload,
103				}
104			},
105		};
106		let dispatch_level_result = match T::BlobDispatcher::dispatch_blob(payload) {
107			Ok(_) => {
108				log::debug!(
109					target: LOG_TARGET,
110					"dispatch - `DispatchBlob::dispatch_blob` was ok for lane_id: {:?} and message_nonce: {:?}",
111					message.key.lane_id,
112					message.key.nonce
113				);
114				XcmBlobMessageDispatchResult::Dispatched
115			},
116			Err(e) => {
117				log::error!(
118					target: LOG_TARGET,
119					"dispatch - `DispatchBlob::dispatch_blob` failed with error: {e:?} for lane_id: {:?} and message_nonce: {:?}",
120					message.key.lane_id,
121					message.key.nonce
122				);
123				XcmBlobMessageDispatchResult::NotDispatched(Some(e))
124			},
125		};
126		MessageDispatchResult { unspent_weight: Weight::zero(), dispatch_level_result }
127	}
128}
129
130#[cfg(test)]
131mod tests {
132	use super::*;
133	use crate::{mock::*, Bridges, LaneToBridge, LanesManagerOf};
134
135	use bp_messages::{target_chain::DispatchMessageData, LaneIdType, MessageKey};
136	use bp_xcm_bridge_hub::{Bridge, BridgeLocations, BridgeState};
137	use frame_support::assert_ok;
138	use pallet_bridge_messages::InboundLaneStorage;
139	use xcm_executor::traits::ConvertLocation;
140
141	fn bridge() -> (Box<BridgeLocations>, TestLaneIdType) {
142		let origin = OpenBridgeOrigin::sibling_parachain_origin();
143		let with = bridged_asset_hub_universal_location();
144		let locations =
145			XcmOverBridge::bridge_locations_from_origin(origin, Box::new(with.into())).unwrap();
146		let lane_id = locations.calculate_lane_id(xcm::latest::VERSION).unwrap();
147		(locations, lane_id)
148	}
149
150	fn run_test_with_opened_bridge(test: impl FnOnce()) {
151		run_test(|| {
152			let (bridge, lane_id) = bridge();
153
154			if !Bridges::<TestRuntime, ()>::contains_key(bridge.bridge_id()) {
155				// insert bridge
156				Bridges::<TestRuntime, ()>::insert(
157					bridge.bridge_id(),
158					Bridge {
159						bridge_origin_relative_location: Box::new(
160							bridge.bridge_origin_relative_location().clone().into(),
161						),
162						bridge_origin_universal_location: Box::new(
163							bridge.bridge_origin_universal_location().clone().into(),
164						),
165						bridge_destination_universal_location: Box::new(
166							bridge.bridge_destination_universal_location().clone().into(),
167						),
168						state: BridgeState::Opened,
169						bridge_owner_account: LocationToAccountId::convert_location(
170							bridge.bridge_origin_relative_location(),
171						)
172						.expect("valid accountId"),
173						deposit: 0,
174						lane_id,
175					},
176				);
177				LaneToBridge::<TestRuntime, ()>::insert(lane_id, bridge.bridge_id());
178
179				// create lanes
180				let lanes_manager = LanesManagerOf::<TestRuntime, ()>::new();
181				if lanes_manager.create_inbound_lane(lane_id).is_ok() {
182					assert_eq!(
183						0,
184						lanes_manager
185							.active_inbound_lane(lane_id)
186							.unwrap()
187							.storage()
188							.data()
189							.last_confirmed_nonce
190					);
191				}
192				if lanes_manager.create_outbound_lane(lane_id).is_ok() {
193					assert!(lanes_manager
194						.active_outbound_lane(lane_id)
195						.unwrap()
196						.queued_messages()
197						.is_empty());
198				}
199			}
200			assert_ok!(XcmOverBridge::do_try_state());
201
202			test();
203		});
204	}
205
206	fn invalid_message() -> DispatchMessage<Vec<u8>, TestLaneIdType> {
207		DispatchMessage {
208			key: MessageKey { lane_id: TestLaneIdType::try_new(1, 2).unwrap(), nonce: 1 },
209			data: DispatchMessageData { payload: Err(codec::Error::from("test")) },
210		}
211	}
212
213	fn valid_message() -> DispatchMessage<Vec<u8>, TestLaneIdType> {
214		DispatchMessage {
215			key: MessageKey { lane_id: TestLaneIdType::try_new(1, 2).unwrap(), nonce: 1 },
216			data: DispatchMessageData { payload: Ok(vec![42]) },
217		}
218	}
219
220	#[test]
221	fn dispatcher_is_inactive_when_channel_with_target_chain_is_congested() {
222		run_test_with_opened_bridge(|| {
223			TestLocalXcmChannelManager::make_congested();
224			assert!(!XcmOverBridge::is_active(bridge().1));
225		});
226	}
227
228	#[test]
229	fn dispatcher_is_active_when_channel_with_target_chain_is_not_congested() {
230		run_test_with_opened_bridge(|| {
231			assert!(XcmOverBridge::is_active(bridge().1));
232		});
233	}
234
235	#[test]
236	fn dispatch_weight_is_zero_if_we_have_failed_to_decode_message() {
237		run_test(|| {
238			assert_eq!(XcmOverBridge::dispatch_weight(&mut invalid_message()), Weight::zero());
239		});
240	}
241
242	#[test]
243	fn dispatch_weight_is_non_zero_if_we_have_decoded_message() {
244		run_test(|| {
245			assert_ne!(XcmOverBridge::dispatch_weight(&mut valid_message()), Weight::zero());
246		});
247	}
248
249	#[test]
250	fn message_is_not_dispatched_when_we_have_failed_to_decode_message() {
251		run_test(|| {
252			assert_eq!(
253				XcmOverBridge::dispatch(invalid_message()),
254				MessageDispatchResult {
255					unspent_weight: Weight::zero(),
256					dispatch_level_result: XcmBlobMessageDispatchResult::InvalidPayload,
257				},
258			);
259			assert!(!TestBlobDispatcher::is_dispatched());
260		});
261	}
262
263	#[test]
264	fn message_is_dispatched_when_we_have_decoded_message() {
265		run_test(|| {
266			assert_eq!(
267				XcmOverBridge::dispatch(valid_message()),
268				MessageDispatchResult {
269					unspent_weight: Weight::zero(),
270					dispatch_level_result: XcmBlobMessageDispatchResult::Dispatched,
271				},
272			);
273			assert!(TestBlobDispatcher::is_dispatched());
274		});
275	}
276}