sp_partner_chains_bridge/
lib.rs1#![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#[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 pub token_policy_id: PolicyId,
35 pub token_asset_name: AssetName,
37 pub illiquid_circulation_supply_validator_address: MainchainAddress,
41}
42
43#[derive(
46 Clone, Debug, Encode, Decode, DecodeWithMemTracking, TypeInfo, PartialEq, Eq, MaxEncodedLen,
47)]
48pub enum BridgeTransferV1<RecipientAddress> {
49 UserTransfer {
51 token_amount: u64,
53 recipient: RecipientAddress,
55 },
56 ReserveTransfer {
58 token_amount: u64,
60 },
61 InvalidTransfer {
65 token_amount: u64,
67 utxo_id: sidechain_domain::UtxoId,
69 },
70}
71
72#[derive(Clone, Debug, Encode, Decode, TypeInfo, PartialEq)]
75pub struct TokenBridgeTransfersV1<RecipientAddress> {
76 pub transfers: Vec<BridgeTransferV1<RecipientAddress>>,
78 pub data_checkpoint: BridgeDataCheckpoint,
80}
81
82pub const INHERENT_IDENTIFIER: InherentIdentifier = *b"pctokbrg";
84
85#[derive(Debug, Encode, Decode, thiserror::Error, PartialEq)]
87pub enum InherentError {
88 #[error("Bridge inherent data was present but an inherent was not provided")]
90 InherentRequired,
91 #[error("Bridge inherent produced when no data present")]
93 InherentNotExpected,
94 #[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#[cfg(feature = "std")]
107#[derive(Debug)]
108pub enum TokenBridgeInherentDataProvider<RecipientAddress> {
109 Inert,
111 ActiveV1 {
113 data: TokenBridgeTransfersV1<RecipientAddress>,
115 },
116}
117
118#[derive(
125 Clone, Debug, Encode, Decode, DecodeWithMemTracking, TypeInfo, PartialEq, Eq, MaxEncodedLen,
126)]
127pub enum BridgeDataCheckpoint {
128 Utxo(UtxoId),
130 Block(McBlockNumber),
132}
133
134#[cfg(feature = "std")]
136#[async_trait::async_trait]
137pub trait TokenBridgeDataSource<RecipientAddress>: Send + Sync {
138 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#[cfg(feature = "std")]
153#[derive(Debug, thiserror::Error)]
154pub enum InherentDataCreationError {
155 #[error("Runtime API call failed: {0}")]
157 ApiError(#[from] sp_api::ApiError),
158 #[error("Data source call failed: {0}")]
160 DataSourceError(Box<dyn std::error::Error + Send + Sync>),
161 #[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 #[api_version(1)]
201 pub trait TokenBridgeIDPRuntimeApi {
202 fn get_pallet_version() -> u32;
204 fn get_main_chain_scripts() -> Option<MainChainScripts>;
206 fn get_max_transfers_per_block() -> u32;
208 fn get_last_data_checkpoint() -> Option<BridgeDataCheckpoint>;
210 }
211}
212
213#[cfg(feature = "std")]
214impl<RecipientAddress: Encode + Send + Sync> TokenBridgeInherentDataProvider<RecipientAddress> {
215 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 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}