sp_partner_chains_bridge/
lib.rs

1//! Primitives and inherent data provider for the token bridge feature of Partner Chains Toolkit.
2#![cfg_attr(not(feature = "std"), no_std)]
3#![deny(missing_docs)]
4
5extern crate alloc;
6
7use alloc::vec::*;
8use parity_scale_codec::{Decode, DecodeWithMemTracking, Encode, MaxEncodedLen};
9use scale_info::TypeInfo;
10use serde::{Deserialize, Serialize};
11use sidechain_domain::{AssetName, MainchainAddress, McBlockHash, McBlockNumber, PolicyId, UtxoId};
12use sp_inherents::*;
13
14#[cfg(feature = "std")]
15use {sp_api::ApiExt, sp_api::ProvideRuntimeApi, sp_runtime::traits::Block as BlockT};
16
17/// Smart contract hashes and addresses used by the token bridge on Cardano.
18#[derive(
19	Default,
20	Debug,
21	Clone,
22	PartialEq,
23	Eq,
24	TypeInfo,
25	Encode,
26	Decode,
27	DecodeWithMemTracking,
28	MaxEncodedLen,
29	Serialize,
30	Deserialize,
31)]
32pub struct MainChainScripts {
33	/// Minting policy ID of the native token
34	pub token_policy_id: PolicyId,
35	/// Asset name of the native token
36	pub token_asset_name: AssetName,
37	/// Address of the illiquid supply validator.
38	///
39	/// All tokens sent to that address are effectively locked and considered "sent" to the Partner Chain.
40	pub illiquid_circulation_supply_validator_address: MainchainAddress,
41}
42
43/// Type containing all information needed to process a single transfer incoming from
44/// main chain, corresponding to a single UTXO on Cardano
45#[derive(
46	Clone, Debug, Encode, Decode, DecodeWithMemTracking, TypeInfo, PartialEq, Eq, MaxEncodedLen,
47)]
48pub enum BridgeTransferV1<RecipientAddress> {
49	/// Token transfer initiated by a user on Cardano
50	UserTransfer {
51		/// Amount of tokens tranfered
52		token_amount: u64,
53		/// Transfer recipient on the Partner Chain
54		recipient: RecipientAddress,
55	},
56	/// Token transfer carrying reserve funds being moved from Cardano to the Partner Chain
57	ReserveTransfer {
58		/// Amount of tokens tranfered
59		token_amount: u64,
60	},
61	/// Invalid transfer coming from a UTXO on Cardano that does not contain a datum that can be
62	/// correctly interpreted. These transfers can either be ignored and considered lost or recovered
63	/// through some custom mechanism.
64	InvalidTransfer {
65		/// Amount of tokens tranfered
66		token_amount: u64,
67		/// ID of the UTXO containing an invalid transfer
68		utxo_id: sidechain_domain::UtxoId,
69	},
70}
71
72/// Structure representing all token bridge transfers incoming from Cardano that are to be
73/// handled in one Partner Chain block.
74#[derive(Clone, Debug, Encode, Decode, TypeInfo, PartialEq)]
75pub struct TokenBridgeTransfersV1<RecipientAddress> {
76	/// Transfers to be handled in one Partner Chain block
77	pub transfers: Vec<BridgeTransferV1<RecipientAddress>>,
78	/// Pointer to last data processed, used as an idempotency key by the data layer
79	pub data_checkpoint: BridgeDataCheckpoint,
80}
81
82/// Inherent identifier used by the Partner Chains token bridge
83pub const INHERENT_IDENTIFIER: InherentIdentifier = *b"pctokbrg";
84
85/// Error type returned by failing calls of the bridge inherent
86#[derive(Debug, Encode, Decode, thiserror::Error, PartialEq)]
87pub enum InherentError {
88	/// Inherent was not produced when expected
89	#[error("Bridge inherent data was present but an inherent was not provided")]
90	InherentRequired,
91	/// Inherent produced when inherent data not present
92	#[error("Bridge inherent produced when no data present")]
93	InherentNotExpected,
94	/// Inherent produced does not match inherent data
95	#[error("Inherent produced does not match inherent data")]
96	IncorrectInherent,
97}
98
99impl IsFatalError for InherentError {
100	fn is_fatal_error(&self) -> bool {
101		true
102	}
103}
104
105/// Inherent data provider that provides data on token bridge transfers and special token transfers
106#[cfg(feature = "std")]
107#[derive(Debug)]
108pub enum TokenBridgeInherentDataProvider<RecipientAddress> {
109	/// Inert IDP. Will not provide inherent data and will never fail.
110	Inert,
111	/// Version 1
112	ActiveV1 {
113		/// Token bridge transfer data
114		data: TokenBridgeTransfersV1<RecipientAddress>,
115	},
116}
117
118/// Value specifying the point in time up to which bridge transfers have been processed
119///
120/// This type is an enum wrapping either a block number or a utxo to handle both a case
121/// where all transfers up to a block have been handled and a case where there were more
122/// transfers than the limit allows and observability needs to pick up after the last
123/// utxo that could be observed
124#[derive(
125	Clone, Debug, Encode, Decode, DecodeWithMemTracking, TypeInfo, PartialEq, Eq, MaxEncodedLen,
126)]
127pub enum BridgeDataCheckpoint {
128	/// Last transfer utxo that has been processed
129	Utxo(UtxoId),
130	/// Cardano block up to which data has been processed
131	Block(McBlockNumber),
132}
133
134/// Interface for data sources that can be used by [TokenBridgeInherentDataProvider]
135#[cfg(feature = "std")]
136#[async_trait::async_trait]
137pub trait TokenBridgeDataSource<RecipientAddress>: Send + Sync {
138	/// Fetches at most `max_transfers` of token bridge transfers after `data_checkpoint` up to `current_mc_block`
139	async fn get_transfers(
140		&self,
141		main_chain_scripts: MainChainScripts,
142		data_checkpoint: BridgeDataCheckpoint,
143		max_transfers: u32,
144		current_mc_block: McBlockHash,
145	) -> Result<
146		(Vec<BridgeTransferV1<RecipientAddress>>, BridgeDataCheckpoint),
147		Box<dyn std::error::Error + Send + Sync>,
148	>;
149}
150
151/// Error type returned when creating [TokenBridgeInherentDataProvider] fails
152#[cfg(feature = "std")]
153#[derive(Debug, thiserror::Error)]
154pub enum InherentDataCreationError {
155	/// Signals that a runtime API call failed
156	#[error("Runtime API call failed: {0}")]
157	ApiError(#[from] sp_api::ApiError),
158	/// Signals that the data source returned an error
159	#[error("Data source call failed: {0}")]
160	DataSourceError(Box<dyn std::error::Error + Send + Sync>),
161	/// Signals that the current pallet version on the chain is higher than supported by the node's IDP
162	#[error("Unsupported pallet version {0} (highest supported version: {1})")]
163	UnsupportedPalletVersion(u32, u32),
164}
165
166#[cfg(feature = "std")]
167#[async_trait::async_trait]
168impl<RecipientAddress: Encode + Send + Sync> sp_inherents::InherentDataProvider
169	for TokenBridgeInherentDataProvider<RecipientAddress>
170{
171	async fn provide_inherent_data(
172		&self,
173		inherent_data: &mut InherentData,
174	) -> Result<(), sp_inherents::Error> {
175		match self {
176			Self::Inert => {},
177			Self::ActiveV1 { data } => {
178				inherent_data.put_data(INHERENT_IDENTIFIER, data)?;
179			},
180		}
181		Ok(())
182	}
183
184	async fn try_handle_error(
185		&self,
186		identifier: &InherentIdentifier,
187		mut error: &[u8],
188	) -> Option<Result<(), sp_inherents::Error>> {
189		if identifier == &INHERENT_IDENTIFIER {
190			let error = InherentError::decode(&mut error).ok()?;
191			Some(Err(sp_inherents::Error::Application(Box::from(error))))
192		} else {
193			None
194		}
195	}
196}
197
198sp_api::decl_runtime_apis! {
199	/// Runtime API used by [TokenBridgeInherentDataProvider]
200	#[api_version(1)]
201	pub trait TokenBridgeIDPRuntimeApi {
202		/// Returns the current version of the pallet, 1-based.
203		fn get_pallet_version() -> u32;
204		/// Returns the currenlty configured main chain scripts
205		fn get_main_chain_scripts() -> Option<MainChainScripts>;
206		/// Returns the currently configured transfer number limit
207		fn get_max_transfers_per_block() -> u32;
208		/// Returns last data checkpoint saved in the pallet
209		fn get_last_data_checkpoint() -> Option<BridgeDataCheckpoint>;
210	}
211}
212
213#[cfg(feature = "std")]
214impl<RecipientAddress: Encode + Send + Sync> TokenBridgeInherentDataProvider<RecipientAddress> {
215	/// Creates new [TokenBridgeInherentDataProvider]
216	///
217	/// This function is version-aware and will create [TokenBridgeInherentDataProvider] based on
218	/// the version reported by the pallet through [TokenBridgeIDPRuntimeApi].
219	pub async fn new<Block, T>(
220		client: &T,
221		parent_hash: Block::Hash,
222		current_mc_hash: McBlockHash,
223		data_source: &(dyn TokenBridgeDataSource<RecipientAddress> + Send + Sync),
224	) -> Result<Self, InherentDataCreationError>
225	where
226		Block: BlockT,
227		T: ProvideRuntimeApi<Block> + Send + Sync,
228		T::Api: TokenBridgeIDPRuntimeApi<Block>,
229	{
230		let api = client.runtime_api();
231
232		let Some(pallet_version) =
233			api.api_version::<dyn TokenBridgeIDPRuntimeApi<Block>>(parent_hash)?
234		else {
235			log::info!(
236				"💤 Skipping token bridge transfer observation. Pallet not detected in the runtime."
237			);
238			return Ok(Self::Inert);
239		};
240
241		match pallet_version {
242			1 => Self::new_v1(api, parent_hash, current_mc_hash, data_source).await,
243			unsupported_version => {
244				Err(InherentDataCreationError::UnsupportedPalletVersion(unsupported_version, 1))
245			},
246		}
247	}
248
249	/// Creates new [TokenBridgeInherentDataProvider::ActiveV1]
250	pub async fn new_v1<'a, Block, Api>(
251		api: sp_api::ApiRef<'a, Api>,
252		parent_hash: Block::Hash,
253		current_mc_hash: McBlockHash,
254		data_source: &dyn TokenBridgeDataSource<RecipientAddress>,
255	) -> Result<Self, InherentDataCreationError>
256	where
257		Block: BlockT,
258		Api: TokenBridgeIDPRuntimeApi<Block>,
259	{
260		let max_transfers = api.get_max_transfers_per_block(parent_hash)?;
261		let (Some(last_checkpoint), Some(main_chain_scripts)) =
262			(api.get_last_data_checkpoint(parent_hash)?, api.get_main_chain_scripts(parent_hash)?)
263		else {
264			log::info!("💤 Skipping token bridge transfer observation. Pallet not configured.");
265			return Ok(Self::Inert);
266		};
267
268		let (transfers, new_checkpoint) = data_source
269			.get_transfers(main_chain_scripts, last_checkpoint, max_transfers, current_mc_hash)
270			.await
271			.map_err(InherentDataCreationError::DataSourceError)?;
272
273		Ok(Self::ActiveV1 {
274			data: TokenBridgeTransfersV1 { transfers, data_checkpoint: new_checkpoint },
275		})
276	}
277}