referrerpolicy=no-referrer-when-downgrade

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				tracing::error!(
95					target: LOG_TARGET,
96					error=?e,
97					lane_id=?message.key.lane_id,
98					message_nonce=?message.key.nonce,
99					"dispatch - payload error"
100				);
101				return MessageDispatchResult {
102					unspent_weight: Weight::zero(),
103					dispatch_level_result: XcmBlobMessageDispatchResult::InvalidPayload,
104				}
105			},
106		};
107		let dispatch_level_result = match T::BlobDispatcher::dispatch_blob(payload) {
108			Ok(_) => {
109				tracing::debug!(
110					target: LOG_TARGET,
111					lane_id=?message.key.lane_id,
112					message_nonce=?message.key.nonce,
113					"dispatch - `DispatchBlob::dispatch_blob` was ok"
114				);
115				XcmBlobMessageDispatchResult::Dispatched
116			},
117			Err(e) => {
118				tracing::error!(
119					target: LOG_TARGET,
120					error=?e,
121					lane_id=?message.key.lane_id,
122					message_nonce=?message.key.nonce,
123					"dispatch - `DispatchBlob::dispatch_blob` failed"
124				);
125				XcmBlobMessageDispatchResult::NotDispatched(Some(e))
126			},
127		};
128		MessageDispatchResult { unspent_weight: Weight::zero(), dispatch_level_result }
129	}
130}
131
132#[cfg(test)]
133mod tests {
134	use super::*;
135	use crate::{mock::*, Bridges, LaneToBridge, LanesManagerOf};
136
137	use bp_messages::{target_chain::DispatchMessageData, LaneIdType, MessageKey};
138	use bp_xcm_bridge_hub::{Bridge, BridgeLocations, BridgeState};
139	use frame_support::assert_ok;
140	use pallet_bridge_messages::InboundLaneStorage;
141	use xcm_executor::traits::ConvertLocation;
142
143	fn bridge() -> (Box<BridgeLocations>, TestLaneIdType) {
144		let origin = OpenBridgeOrigin::sibling_parachain_origin();
145		let with = bridged_asset_hub_universal_location();
146		let locations =
147			XcmOverBridge::bridge_locations_from_origin(origin, Box::new(with.into())).unwrap();
148		let lane_id = locations.calculate_lane_id(xcm::latest::VERSION).unwrap();
149		(locations, lane_id)
150	}
151
152	fn run_test_with_opened_bridge(test: impl FnOnce()) {
153		run_test(|| {
154			let (bridge, lane_id) = bridge();
155
156			if !Bridges::<TestRuntime, ()>::contains_key(bridge.bridge_id()) {
157				// insert bridge
158				Bridges::<TestRuntime, ()>::insert(
159					bridge.bridge_id(),
160					Bridge {
161						bridge_origin_relative_location: Box::new(
162							bridge.bridge_origin_relative_location().clone().into(),
163						),
164						bridge_origin_universal_location: Box::new(
165							bridge.bridge_origin_universal_location().clone().into(),
166						),
167						bridge_destination_universal_location: Box::new(
168							bridge.bridge_destination_universal_location().clone().into(),
169						),
170						state: BridgeState::Opened,
171						bridge_owner_account: LocationToAccountId::convert_location(
172							bridge.bridge_origin_relative_location(),
173						)
174						.expect("valid accountId"),
175						deposit: 0,
176						lane_id,
177					},
178				);
179				LaneToBridge::<TestRuntime, ()>::insert(lane_id, bridge.bridge_id());
180
181				// create lanes
182				let lanes_manager = LanesManagerOf::<TestRuntime, ()>::new();
183				if lanes_manager.create_inbound_lane(lane_id).is_ok() {
184					assert_eq!(
185						0,
186						lanes_manager
187							.active_inbound_lane(lane_id)
188							.unwrap()
189							.storage()
190							.data()
191							.last_confirmed_nonce
192					);
193				}
194				if lanes_manager.create_outbound_lane(lane_id).is_ok() {
195					assert!(lanes_manager
196						.active_outbound_lane(lane_id)
197						.unwrap()
198						.queued_messages()
199						.is_empty());
200				}
201			}
202			assert_ok!(XcmOverBridge::do_try_state());
203
204			test();
205		});
206	}
207
208	fn invalid_message() -> DispatchMessage<Vec<u8>, TestLaneIdType> {
209		DispatchMessage {
210			key: MessageKey { lane_id: TestLaneIdType::try_new(1, 2).unwrap(), nonce: 1 },
211			data: DispatchMessageData { payload: Err(codec::Error::from("test")) },
212		}
213	}
214
215	fn valid_message() -> DispatchMessage<Vec<u8>, TestLaneIdType> {
216		DispatchMessage {
217			key: MessageKey { lane_id: TestLaneIdType::try_new(1, 2).unwrap(), nonce: 1 },
218			data: DispatchMessageData { payload: Ok(vec![42]) },
219		}
220	}
221
222	#[test]
223	fn dispatcher_is_inactive_when_channel_with_target_chain_is_congested() {
224		run_test_with_opened_bridge(|| {
225			TestLocalXcmChannelManager::make_congested();
226			assert!(!XcmOverBridge::is_active(bridge().1));
227		});
228	}
229
230	#[test]
231	fn dispatcher_is_active_when_channel_with_target_chain_is_not_congested() {
232		run_test_with_opened_bridge(|| {
233			assert!(XcmOverBridge::is_active(bridge().1));
234		});
235	}
236
237	#[test]
238	fn dispatch_weight_is_zero_if_we_have_failed_to_decode_message() {
239		run_test(|| {
240			assert_eq!(XcmOverBridge::dispatch_weight(&mut invalid_message()), Weight::zero());
241		});
242	}
243
244	#[test]
245	fn dispatch_weight_is_non_zero_if_we_have_decoded_message() {
246		run_test(|| {
247			assert_ne!(XcmOverBridge::dispatch_weight(&mut valid_message()), Weight::zero());
248		});
249	}
250
251	#[test]
252	fn message_is_not_dispatched_when_we_have_failed_to_decode_message() {
253		run_test(|| {
254			assert_eq!(
255				XcmOverBridge::dispatch(invalid_message()),
256				MessageDispatchResult {
257					unspent_weight: Weight::zero(),
258					dispatch_level_result: XcmBlobMessageDispatchResult::InvalidPayload,
259				},
260			);
261			assert!(!TestBlobDispatcher::is_dispatched());
262		});
263	}
264
265	#[test]
266	fn message_is_dispatched_when_we_have_decoded_message() {
267		run_test(|| {
268			assert_eq!(
269				XcmOverBridge::dispatch(valid_message()),
270				MessageDispatchResult {
271					unspent_weight: Weight::zero(),
272					dispatch_level_result: XcmBlobMessageDispatchResult::Dispatched,
273				},
274			);
275			assert!(TestBlobDispatcher::is_dispatched());
276		});
277	}
278}