referrerpolicy=no-referrer-when-downgrade

bp_relayers/
lib.rs

1// Copyright (C) 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//! Primitives of messages module.
18
19#![warn(missing_docs)]
20#![cfg_attr(not(feature = "std"), no_std)]
21
22pub use extension::{
23	BatchCallUnpacker, ExtensionCallData, ExtensionCallInfo, ExtensionConfig,
24	RuntimeWithUtilityPallet,
25};
26pub use registration::{ExplicitOrAccountParams, Registration, StakeAndSlash};
27
28use bp_runtime::{ChainId, StorageDoubleMapKeyProvider};
29use frame_support::{traits::tokens::Preservation, Blake2_128Concat, Identity};
30use scale_info::TypeInfo;
31use sp_runtime::{
32	codec::{Codec, Decode, DecodeWithMemTracking, Encode, EncodeLike, MaxEncodedLen},
33	traits::AccountIdConversion,
34	TypeId,
35};
36use sp_std::{fmt::Debug, marker::PhantomData};
37
38mod extension;
39mod registration;
40
41/// The owner of the sovereign account that should pay the rewards.
42///
43/// Each of the 2 final points connected by a bridge owns a sovereign account at each end of the
44/// bridge. So here, at this end of the bridge there can be 2 sovereign accounts that pay rewards.
45#[derive(
46	Copy,
47	Clone,
48	Debug,
49	Decode,
50	DecodeWithMemTracking,
51	Encode,
52	Eq,
53	PartialEq,
54	TypeInfo,
55	MaxEncodedLen,
56)]
57pub enum RewardsAccountOwner {
58	/// The sovereign account of the final chain on this end of the bridge.
59	ThisChain,
60	/// The sovereign account of the final chain on the other end of the bridge.
61	BridgedChain,
62}
63
64/// Structure used to identify the account that pays a reward to the relayer.
65///
66/// A bridge connects 2 bridge ends. Each one is located on a separate relay chain. The bridge ends
67/// can be the final destinations of the bridge, or they can be intermediary points
68/// (e.g. a bridge hub) used to forward messages between pairs of parachains on the bridged relay
69/// chains. A pair of such parachains is connected using a bridge lane. Each of the 2 final
70/// destinations of a bridge lane must have a sovereign account at each end of the bridge and each
71/// of the sovereign accounts will pay rewards for different operations. So we need multiple
72/// parameters to identify the account that pays a reward to the relayer.
73#[derive(
74	Copy,
75	Clone,
76	Debug,
77	Decode,
78	DecodeWithMemTracking,
79	Encode,
80	Eq,
81	PartialEq,
82	TypeInfo,
83	MaxEncodedLen,
84)]
85pub struct RewardsAccountParams<LaneId> {
86	// **IMPORTANT NOTE**: the order of fields here matters - we are using
87	// `into_account_truncating` and lane id is already `32` byte, so if other fields are encoded
88	// after it, they're simply dropped. So lane id shall be the last field.
89	owner: RewardsAccountOwner,
90	bridged_chain_id: ChainId,
91	lane_id: LaneId,
92}
93
94impl<LaneId: Decode + Encode> RewardsAccountParams<LaneId> {
95	/// Create a new instance of `RewardsAccountParams`.
96	pub const fn new(
97		lane_id: LaneId,
98		bridged_chain_id: ChainId,
99		owner: RewardsAccountOwner,
100	) -> Self {
101		Self { lane_id, bridged_chain_id, owner }
102	}
103
104	/// Getter for `lane_id`.
105	pub const fn lane_id(&self) -> &LaneId {
106		&self.lane_id
107	}
108}
109
110impl<LaneId: Decode + Encode> TypeId for RewardsAccountParams<LaneId> {
111	const TYPE_ID: [u8; 4] = *b"brap";
112}
113
114/// Reward payment procedure.
115pub trait PaymentProcedure<Relayer, Reward, RewardBalance> {
116	/// Error that may be returned by the procedure.
117	type Error: Debug;
118
119	/// Type parameter used to identify the beneficiaries eligible to receive rewards.
120	type Beneficiary: Clone + Debug + Decode + Encode + Eq + TypeInfo;
121
122	/// Pay reward to the relayer (or alternative beneficiary if provided) from the account with
123	/// provided params.
124	fn pay_reward(
125		relayer: &Relayer,
126		reward: Reward,
127		reward_balance: RewardBalance,
128		beneficiary: Self::Beneficiary,
129	) -> Result<(), Self::Error>;
130}
131
132impl<Relayer, Reward, RewardBalance> PaymentProcedure<Relayer, Reward, RewardBalance> for () {
133	type Error = &'static str;
134	type Beneficiary = ();
135
136	fn pay_reward(
137		_: &Relayer,
138		_: Reward,
139		_: RewardBalance,
140		_: Self::Beneficiary,
141	) -> Result<(), Self::Error> {
142		Ok(())
143	}
144}
145
146/// Reward payment procedure that executes a `balances::transfer` call from the account
147/// derived from the given `RewardsAccountParams` to the relayer or an alternative beneficiary.
148pub struct PayRewardFromAccount<T, Relayer, LaneId, RewardBalance>(
149	PhantomData<(T, Relayer, LaneId, RewardBalance)>,
150);
151
152impl<T, Relayer, LaneId, RewardBalance> PayRewardFromAccount<T, Relayer, LaneId, RewardBalance>
153where
154	Relayer: Decode + Encode,
155	LaneId: Decode + Encode,
156{
157	/// Return account that pays rewards based on the provided parameters.
158	pub fn rewards_account(params: RewardsAccountParams<LaneId>) -> Relayer {
159		params.into_sub_account_truncating(b"rewards-account")
160	}
161}
162
163impl<T, Relayer, LaneId, RewardBalance>
164	PaymentProcedure<Relayer, RewardsAccountParams<LaneId>, RewardBalance>
165	for PayRewardFromAccount<T, Relayer, LaneId, RewardBalance>
166where
167	T: frame_support::traits::fungible::Mutate<Relayer>,
168	T::Balance: From<RewardBalance>,
169	Relayer: Clone + Debug + Decode + Encode + Eq + TypeInfo,
170	LaneId: Decode + Encode,
171{
172	type Error = sp_runtime::DispatchError;
173	type Beneficiary = Relayer;
174
175	fn pay_reward(
176		_: &Relayer,
177		reward_kind: RewardsAccountParams<LaneId>,
178		reward: RewardBalance,
179		beneficiary: Self::Beneficiary,
180	) -> Result<(), Self::Error> {
181		T::transfer(
182			&Self::rewards_account(reward_kind),
183			&beneficiary.into(),
184			reward.into(),
185			Preservation::Expendable,
186		)
187		.map(drop)
188	}
189}
190
191/// Can be used to access the runtime storage key within the `RelayerRewards` map of the relayers
192/// pallet.
193pub struct RelayerRewardsKeyProvider<AccountId, Reward, RewardBalance>(
194	PhantomData<(AccountId, Reward, RewardBalance)>,
195);
196
197impl<AccountId, Reward, RewardBalance> StorageDoubleMapKeyProvider
198	for RelayerRewardsKeyProvider<AccountId, Reward, RewardBalance>
199where
200	AccountId: 'static + Codec + EncodeLike + Send + Sync,
201	Reward: Codec + EncodeLike + Send + Sync,
202	RewardBalance: 'static + Codec + EncodeLike + Send + Sync,
203{
204	const MAP_NAME: &'static str = "RelayerRewards";
205
206	type Hasher1 = Blake2_128Concat;
207	type Key1 = AccountId;
208	type Hasher2 = Identity;
209	type Key2 = Reward;
210	type Value = RewardBalance;
211}
212
213/// A trait defining a reward ledger, which tracks rewards that can be later claimed.
214///
215/// This ledger allows registering rewards for a relayer, categorized by a specific `Reward`.
216/// The registered rewards can be claimed later through an appropriate payment procedure.
217pub trait RewardLedger<Relayer, Reward, RewardBalance> {
218	/// Registers a reward for a given relayer.
219	fn register_reward(relayer: &Relayer, reward: Reward, reward_balance: RewardBalance);
220}
221
222#[cfg(test)]
223mod tests {
224	use super::*;
225	use bp_messages::{HashedLaneId, LaneIdType, LegacyLaneId};
226	use sp_runtime::{app_crypto::Ss58Codec, testing::H256};
227
228	#[test]
229	fn different_lanes_are_using_different_accounts() {
230		assert_eq!(
231			PayRewardFromAccount::<(), H256, HashedLaneId, ()>::rewards_account(
232				RewardsAccountParams::new(
233					HashedLaneId::try_new(1, 2).unwrap(),
234					*b"test",
235					RewardsAccountOwner::ThisChain
236				)
237			),
238			hex_literal::hex!("627261700074657374b1d3dccd8b3c3a012afe265f3e3c4432129b8aee50c9dc")
239				.into(),
240		);
241
242		assert_eq!(
243			PayRewardFromAccount::<(), H256, HashedLaneId, ()>::rewards_account(
244				RewardsAccountParams::new(
245					HashedLaneId::try_new(1, 3).unwrap(),
246					*b"test",
247					RewardsAccountOwner::ThisChain
248				)
249			),
250			hex_literal::hex!("627261700074657374a43e8951aa302c133beb5f85821a21645f07b487270ef3")
251				.into(),
252		);
253	}
254
255	#[test]
256	fn different_directions_are_using_different_accounts() {
257		assert_eq!(
258			PayRewardFromAccount::<(), H256, HashedLaneId, ()>::rewards_account(
259				RewardsAccountParams::new(
260					HashedLaneId::try_new(1, 2).unwrap(),
261					*b"test",
262					RewardsAccountOwner::ThisChain
263				)
264			),
265			hex_literal::hex!("627261700074657374b1d3dccd8b3c3a012afe265f3e3c4432129b8aee50c9dc")
266				.into(),
267		);
268
269		assert_eq!(
270			PayRewardFromAccount::<(), H256, HashedLaneId, ()>::rewards_account(
271				RewardsAccountParams::new(
272					HashedLaneId::try_new(1, 2).unwrap(),
273					*b"test",
274					RewardsAccountOwner::BridgedChain
275				)
276			),
277			hex_literal::hex!("627261700174657374b1d3dccd8b3c3a012afe265f3e3c4432129b8aee50c9dc")
278				.into(),
279		);
280	}
281
282	#[test]
283	fn pay_reward_from_account_for_legacy_lane_id_works() {
284		let test_data = vec![
285			// Note: these accounts are used for integration tests within
286			// `bridges_rococo_westend.sh`
287			(
288				LegacyLaneId([0, 0, 0, 1]),
289				b"bhks",
290				RewardsAccountOwner::ThisChain,
291				(0_u16, "13E5fui97x6KTwNnSjaEKZ8s7kJNot5F3aUsy3jUtuoMyUec"),
292			),
293			(
294				LegacyLaneId([0, 0, 0, 1]),
295				b"bhks",
296				RewardsAccountOwner::BridgedChain,
297				(0_u16, "13E5fui9Ka9Vz4JbGN3xWjmwDNxnxF1N9Hhhbeu3VCqLChuj"),
298			),
299			(
300				LegacyLaneId([0, 0, 0, 1]),
301				b"bhpd",
302				RewardsAccountOwner::ThisChain,
303				(2_u16, "EoQBtnwtXqnSnr9cgBEJpKU7NjeC9EnR4D1VjgcvHz9ZYmS"),
304			),
305			(
306				LegacyLaneId([0, 0, 0, 1]),
307				b"bhpd",
308				RewardsAccountOwner::BridgedChain,
309				(2_u16, "EoQBtnx69txxumxSJexVzxYD1Q4LWAuWmRq8LrBWb27nhYN"),
310			),
311			// Note: these accounts are used for integration tests within
312			// `bridges_polkadot_kusama.sh` from fellows.
313			(
314				LegacyLaneId([0, 0, 0, 2]),
315				b"bhwd",
316				RewardsAccountOwner::ThisChain,
317				(4_u16, "SNihsskf7bFhnHH9HJFMjWD3FJ96ESdAQTFZUAtXudRQbaH"),
318			),
319			(
320				LegacyLaneId([0, 0, 0, 2]),
321				b"bhwd",
322				RewardsAccountOwner::BridgedChain,
323				(4_u16, "SNihsskrjeSDuD5xumyYv9H8sxZEbNkG7g5C5LT8CfPdaSE"),
324			),
325			(
326				LegacyLaneId([0, 0, 0, 2]),
327				b"bhro",
328				RewardsAccountOwner::ThisChain,
329				(4_u16, "SNihsskf7bF2vWogkC6uFoiqPhd3dUX6TGzYZ1ocJdo3xHp"),
330			),
331			(
332				LegacyLaneId([0, 0, 0, 2]),
333				b"bhro",
334				RewardsAccountOwner::BridgedChain,
335				(4_u16, "SNihsskrjeRZ3ScWNfq6SSnw2N3BzQeCAVpBABNCbfmHENB"),
336			),
337		];
338
339		for (lane_id, bridged_chain_id, owner, (expected_ss58, expected_account)) in test_data {
340			assert_eq!(
341				expected_account,
342				sp_runtime::AccountId32::new(PayRewardFromAccount::<
343					[u8; 32],
344					[u8; 32],
345					LegacyLaneId,
346					(),
347				>::rewards_account(RewardsAccountParams::new(
348					lane_id,
349					*bridged_chain_id,
350					owner
351				)))
352				.to_ss58check_with_version(expected_ss58.into())
353			);
354		}
355	}
356}