partner_chains_dolos_data_sources/
bridge.rs1use crate::{
2 Result,
3 client::{MiniBFClient, api::MiniBFApi, minibf::format_asset_id},
4};
5use blockfrost_openapi::models::{
6 tx_content::TxContent, tx_content_output_amount_inner::TxContentOutputAmountInner,
7 tx_content_utxo::TxContentUtxo,
8};
9use cardano_serialization_lib::PlutusData;
10use partner_chains_plutus_data::bridge::{TokenTransferDatum, TokenTransferDatumV1};
11use sidechain_domain::*;
12use sp_partner_chains_bridge::{
13 BridgeDataCheckpoint, BridgeTransferV1, MainChainScripts, TokenBridgeDataSource,
14};
15use std::fmt::Debug;
16use std::marker::PhantomData;
17
18pub struct TokenBridgeDataSourceImpl<RecipientAddress> {
19 client: MiniBFClient,
20 _phantom: PhantomData<RecipientAddress>,
21}
22
23impl<RecipientAddress> TokenBridgeDataSourceImpl<RecipientAddress> {
24 pub fn new(client: MiniBFClient) -> Self {
25 Self { client, _phantom: PhantomData::default() }
26 }
27}
28
29#[async_trait::async_trait]
30impl<RecipientAddress: Send + Sync> TokenBridgeDataSource<RecipientAddress>
31 for TokenBridgeDataSourceImpl<RecipientAddress>
32where
33 RecipientAddress: Debug,
34 RecipientAddress: (for<'a> TryFrom<&'a [u8]>),
35{
36 async fn get_transfers(
37 &self,
38 main_chain_scripts: MainChainScripts,
39 data_checkpoint: BridgeDataCheckpoint,
40 max_transfers: u32,
41 current_mc_block_hash: McBlockHash,
42 ) -> Result<(Vec<BridgeTransferV1<RecipientAddress>>, BridgeDataCheckpoint)> {
43 let current_mc_block = self.client.blocks_by_id(current_mc_block_hash).await?;
44
45 let data_checkpoint = match data_checkpoint {
46 BridgeDataCheckpoint::Utxo(utxo) => {
47 let TxBlockInfo { block_number, tx_ix } =
48 get_block_info_for_utxo(&self.client, utxo.tx_hash.into()).await?.ok_or(
49 format!(
50 "Could not find block info for data checkpoint: {data_checkpoint:?}"
51 ),
52 )?;
53 ResolvedBridgeDataCheckpoint::Utxo {
54 block_number,
55 tx_ix,
56 tx_out_ix: utxo.index.into(),
57 }
58 },
59 BridgeDataCheckpoint::Block(number) => {
60 ResolvedBridgeDataCheckpoint::Block { number: number.into() }
61 },
62 };
63
64 let asset = AssetId {
65 policy_id: main_chain_scripts.token_policy_id.into(),
66 asset_name: main_chain_scripts.token_asset_name.into(),
67 };
68 let current_mc_block_height: McBlockNumber = McBlockNumber(
69 current_mc_block.height.expect("current mc block has valid height") as u32,
70 );
71 let utxos = get_bridge_utxos_tx(
72 &self.client,
73 &main_chain_scripts.illiquid_circulation_supply_validator_address.into(),
74 asset,
75 data_checkpoint,
76 current_mc_block_height,
77 Some(max_transfers),
78 )
79 .await?;
80
81 let new_checkpoint = match utxos.last() {
82 None => BridgeDataCheckpoint::Block(current_mc_block_height),
83 Some(_) if (utxos.len() as u32) < max_transfers => {
84 BridgeDataCheckpoint::Block(current_mc_block_height)
85 },
86 Some(utxo) => BridgeDataCheckpoint::Utxo(utxo.utxo_id()),
87 };
88
89 let transfers = utxos.into_iter().flat_map(utxo_to_transfer).collect();
90
91 Ok((transfers, new_checkpoint))
92 }
93}
94
95fn utxo_to_transfer<RecipientAddress>(
96 utxo: BridgeUtxo,
97) -> Option<BridgeTransferV1<RecipientAddress>>
98where
99 RecipientAddress: for<'a> TryFrom<&'a [u8]>,
100{
101 let token_delta = utxo.tokens_out.0.checked_sub(utxo.tokens_in.0)?;
102
103 if token_delta == 0 {
104 return None;
105 }
106
107 let token_amount = token_delta as u64;
108
109 let Some(datum) = utxo.datum.clone() else {
110 return Some(BridgeTransferV1::InvalidTransfer { token_amount, utxo_id: utxo.utxo_id() });
111 };
112
113 let transfer = match TokenTransferDatum::try_from(datum) {
114 Ok(TokenTransferDatum::V1(TokenTransferDatumV1::UserTransfer { receiver })) => {
115 match RecipientAddress::try_from(receiver.0.as_ref()) {
116 Ok(recipient) => BridgeTransferV1::UserTransfer { token_amount, recipient },
117 Err(_) => {
118 BridgeTransferV1::InvalidTransfer { token_amount, utxo_id: utxo.utxo_id() }
119 },
120 }
121 },
122 Ok(TokenTransferDatum::V1(TokenTransferDatumV1::ReserveTransfer)) => {
123 BridgeTransferV1::ReserveTransfer { token_amount }
124 },
125 Err(_) => BridgeTransferV1::InvalidTransfer { token_amount, utxo_id: utxo.utxo_id() },
126 };
127
128 Some(transfer)
129}
130
131pub(crate) struct BridgeUtxo {
132 pub(crate) block_number: McBlockNumber,
133 pub(crate) tx_ix: McTxIndexInBlock,
134 pub(crate) tx_hash: McTxHash,
135 pub(crate) utxo_ix: UtxoIndex,
136 pub(crate) tokens_out: NativeTokenAmount,
137 pub(crate) tokens_in: NativeTokenAmount,
138 pub(crate) datum: Option<cardano_serialization_lib::PlutusData>,
139}
140
141impl BridgeUtxo {
142 pub(crate) fn utxo_id(&self) -> UtxoId {
143 UtxoId { tx_hash: self.tx_hash.into(), index: self.utxo_ix.into() }
144 }
145
146 pub(crate) fn ordering_key(&self) -> UtxoOrderingKey {
147 (self.block_number, self.tx_ix, self.utxo_ix)
148 }
149}
150
151pub(crate) type UtxoOrderingKey = (McBlockNumber, McTxIndexInBlock, UtxoIndex);
152
153#[derive(Debug, Clone, PartialEq)]
154pub(crate) struct TxBlockInfo {
155 pub(crate) block_number: McBlockNumber,
156 pub(crate) tx_ix: McTxIndexInBlock,
157}
158
159pub(crate) async fn get_block_info_for_utxo(
160 client: &MiniBFClient,
161 tx_hash: McTxHash,
162) -> Result<Option<TxBlockInfo>> {
163 let tx = client.transaction_by_hash(tx_hash).await?;
164 Ok(Some(TxBlockInfo {
165 block_number: McBlockNumber(tx.block_height as u32),
166 tx_ix: McTxIndexInBlock(tx.index as u32),
167 }))
168}
169
170#[derive(Clone)]
171pub(crate) enum ResolvedBridgeDataCheckpoint {
172 Utxo { block_number: McBlockNumber, tx_ix: McTxIndexInBlock, tx_out_ix: UtxoIndex },
173 Block { number: McBlockNumber },
174}
175
176impl ResolvedBridgeDataCheckpoint {
177 fn block_number(&self) -> McBlockNumber {
178 match self {
179 ResolvedBridgeDataCheckpoint::Utxo { block_number, .. } => *block_number,
180 ResolvedBridgeDataCheckpoint::Block { number } => *number,
181 }
182 }
183}
184
185pub(crate) async fn get_bridge_utxos_tx(
186 client: &MiniBFClient,
187 icp_address: &MainchainAddress,
188 native_token: AssetId,
189 checkpoint: ResolvedBridgeDataCheckpoint,
190 to_block: McBlockNumber,
191 max_utxos: Option<u32>,
192) -> Result<Vec<BridgeUtxo>> {
193 let txs = client.assets_transactions(native_token.clone()).await?;
194 let checkpoint_block_no = checkpoint.block_number().0;
195 let futures = txs.into_iter().map(|a| async move {
196 let block_no = a.block_height as u32;
197 if checkpoint_block_no < block_no && block_no <= to_block.0 {
198 let tx_hash = McTxHash::from_hex_unsafe(&a.tx_hash);
199 let utxos = client.transactions_utxos(tx_hash).await?;
200 let tx = client.transaction_by_hash(tx_hash).await?;
201 Result::Ok(Some((utxos, tx)))
202 } else {
203 Result::Ok(None)
204 }
205 });
206 let mut bridge_utxos = futures::future::try_join_all(futures)
207 .await?
208 .iter()
209 .flatten()
210 .flat_map(|(utxos, tx): &(TxContentUtxo, TxContent)| {
211 let inputs = utxos.inputs.iter().filter(|i| i.address == icp_address.to_string());
212 let outputs = utxos.outputs.iter().filter(|o| o.address == icp_address.to_string());
213 let native_token = native_token.clone();
214 let checkpoint_clone = checkpoint.clone();
215 outputs.filter_map(move |output| {
216 let native_token = native_token.clone();
217 let output_tokens = get_all_tokens(&output.amount, &native_token.clone());
218 let input_tokens = inputs
219 .clone()
220 .map(move |input| get_all_tokens(&input.amount, &native_token.clone()))
221 .sum();
222
223 match checkpoint_clone {
224 ResolvedBridgeDataCheckpoint::Utxo { tx_ix, tx_out_ix, .. }
225 if tx.block_height <= tx_ix.0 as i32
226 && output.output_index <= tx_out_ix.0.into() =>
227 {
228 None
229 },
230 _ => Some(BridgeUtxo {
231 block_number: McBlockNumber(tx.block_height as u32),
232 tokens_out: NativeTokenAmount(output_tokens),
233 tokens_in: NativeTokenAmount(input_tokens),
234 datum: output
235 .inline_datum
236 .clone()
237 .map(|d| PlutusData::from_hex(&d).expect("valid datum")),
238 tx_ix: McTxIndexInBlock(tx.index as u32),
239 tx_hash: McTxHash::from_hex_unsafe(&tx.hash),
240 utxo_ix: UtxoIndex(output.output_index as u16),
241 }),
242 }
243 })
244 })
245 .collect::<Vec<_>>();
246 bridge_utxos.sort_by_key(|b| b.ordering_key());
247
248 if let Some(max_utxos) = max_utxos {
249 bridge_utxos.truncate(max_utxos as usize);
250 }
251
252 Ok(bridge_utxos)
253}
254
255fn get_all_tokens(amount: &Vec<TxContentOutputAmountInner>, asset_id: &AssetId) -> u128 {
256 amount
257 .iter()
258 .map(|v| {
259 if v.unit == format_asset_id(asset_id) {
260 v.quantity.parse::<u128>().expect("valid quantity is u128")
261 } else {
262 0u128
263 }
264 })
265 .sum()
266}