1use crate::platform::fetch_current_no_parameters::FetchCurrent;
2use crate::platform::types::epoch::Epoch;
3use crate::{Error, Sdk};
4use bip37_bloom_filter::{BloomFilter, BloomFilterData};
5use dapi_grpc::core::v0::{
6 transactions_with_proofs_request, transactions_with_proofs_response, GetTransactionRequest,
7 GetTransactionResponse, TransactionsWithProofsRequest, TransactionsWithProofsResponse,
8};
9use dpp::dashcore::consensus::Decodable;
10use dpp::dashcore::{Address, InstantLock, MerkleBlock, OutPoint, Transaction, Txid};
11use dpp::identity::state_transition::asset_lock_proof::chain::ChainAssetLockProof;
12use dpp::identity::state_transition::asset_lock_proof::InstantAssetLockProof;
13use dpp::prelude::AssetLockProof;
14
15use dapi_grpc::tonic::Code;
16use rs_dapi_client::transport::TransportError;
17use rs_dapi_client::{DapiClientError, DapiRequestExecutor, IntoInner, RequestSettings};
18use std::time::Duration;
19use tokio::time::{sleep, timeout};
20
21#[derive(Clone, Debug)]
25pub struct FetchedCoreTransaction {
26 pub transaction: Transaction,
28 pub height: u32,
30 pub is_chain_locked: bool,
32 pub is_instant_locked: bool,
36}
37
38fn error_is_not_found(err: &Error) -> bool {
42 match err {
43 Error::DapiClientError(DapiClientError::Transport(TransportError::Grpc(status))) => {
44 status.code() == Code::NotFound
45 }
46 Error::NoAvailableAddressesToRetry(inner) => error_is_not_found(inner),
47 _ => false,
48 }
49}
50
51impl Sdk {
52 pub async fn get_transaction(
61 &self,
62 txid: &str,
63 ) -> Result<Option<FetchedCoreTransaction>, Error> {
64 let response = match self
65 .execute(
66 GetTransactionRequest {
67 id: txid.to_string(),
68 },
69 RequestSettings::default(),
70 )
71 .await
72 .into_inner()
73 {
74 Ok(response) => response,
75 Err(e) => {
76 let err: Error = e.into();
77 return if error_is_not_found(&err) {
78 Ok(None)
79 } else {
80 Err(err)
81 };
82 }
83 };
84
85 let GetTransactionResponse {
86 transaction,
87 height,
88 is_chain_locked,
89 is_instant_locked,
90 ..
91 } = response;
92
93 if transaction.is_empty() {
94 return Ok(None);
95 }
96
97 let transaction = Transaction::consensus_decode(&mut transaction.as_slice())
98 .map_err(|e| Error::CoreError(e.into()))?;
99
100 Ok(Some(FetchedCoreTransaction {
101 transaction,
102 height,
103 is_chain_locked,
104 is_instant_locked,
105 }))
106 }
107
108 pub async fn start_instant_send_lock_stream(
110 &self,
111 from_block_hash: Vec<u8>,
112 address: &Address,
113 ) -> Result<dapi_grpc::tonic::Streaming<TransactionsWithProofsResponse>, Error> {
114 let address_bytes = address.as_unchecked().payload_to_vec();
115
116 let bloom_filter = BloomFilter::builder(1, 0.001)
118 .expect("this FP rate allows up to 10000 items")
119 .add_element(&address_bytes)
120 .build();
121
122 let bloom_filter_proto = {
123 let BloomFilterData {
124 v_data,
125 n_hash_funcs,
126 n_tweak,
127 n_flags,
128 } = bloom_filter.into();
129 dapi_grpc::core::v0::BloomFilter {
130 v_data,
131 n_hash_funcs,
132 n_tweak,
133 n_flags,
134 }
135 };
136
137 let core_transactions_stream = TransactionsWithProofsRequest {
138 bloom_filter: Some(bloom_filter_proto),
139 count: 0, send_transaction_hashes: true,
141 from_block: Some(transactions_with_proofs_request::FromBlock::FromBlockHash(
142 from_block_hash,
143 )),
144 };
145 self.execute(core_transactions_stream, RequestSettings::default())
146 .await
147 .into_inner()
148 .map_err(|e| e.into())
149 }
150
151 pub async fn wait_for_asset_lock_proof_for_transaction(
153 &self,
154 mut stream: dapi_grpc::tonic::Streaming<TransactionsWithProofsResponse>,
155 transaction: &Transaction,
156 time_out: Option<Duration>,
157 ) -> Result<AssetLockProof, Error> {
158 let transaction_id = transaction.txid();
159
160 let _span = tracing::debug_span!(
161 "wait_for_asset_lock_proof_for_transaction",
162 transaction_id = transaction_id.to_string(),
163 )
164 .entered();
165
166 tracing::debug!("waiting for messages from stream");
167
168 let stream_processing = async {
170 loop {
171 let message = stream
173 .message()
174 .await
175 .map_err(|e| Error::Generic(format!("can't receive message: {e}")))?;
176
177 let Some(TransactionsWithProofsResponse { responses }) = message else {
178 return Err(Error::Generic("stream closed unexpectedly".to_string()));
179 };
180
181 match responses {
182 Some(
183 transactions_with_proofs_response::Responses::InstantSendLockMessages(
184 instant_send_lock_messages,
185 ),
186 ) => {
187 tracing::debug!(
188 "received {} instant lock message(s)",
189 instant_send_lock_messages.messages.len()
190 );
191
192 for instant_lock_bytes in instant_send_lock_messages.messages {
193 let instant_lock =
194 InstantLock::consensus_decode(&mut instant_lock_bytes.as_slice())
195 .map_err(|e| {
196 tracing::error!("invalid asset lock: {}", e);
197
198 Error::CoreError(e.into())
199 })?;
200
201 if instant_lock.txid == transaction_id {
202 let asset_lock_proof =
203 AssetLockProof::Instant(InstantAssetLockProof {
204 instant_lock,
205 transaction: transaction.clone(),
206 output_index: 0,
207 });
208
209 tracing::debug!(
210 ?asset_lock_proof,
211 "instant lock is matching to the broadcasted transaction, returning instant asset lock proof"
212 );
213
214 return Ok(asset_lock_proof);
215 } else {
216 tracing::debug!(
217 "instant lock is not matching, waiting for the next message"
218 );
219 }
220 }
221 }
222 Some(transactions_with_proofs_response::Responses::RawMerkleBlock(
223 raw_merkle_block,
224 )) => {
225 tracing::debug!("received merkle block");
226
227 let merkle_block =
228 MerkleBlock::consensus_decode(&mut raw_merkle_block.as_slice())
229 .map_err(|e| {
230 tracing::error!("can't decode merkle block: {}", e);
231
232 Error::CoreError(e.into())
233 })?;
234
235 let mut matches: Vec<Txid> = vec![];
236 let mut index: Vec<u32> = vec![];
237
238 merkle_block.extract_matches(&mut matches, &mut index)?;
239
240 if !matches.contains(&transaction_id) {
242 tracing::debug!(
243 "merkle block doesn't contain the transaction, waiting for the next message"
244 );
245
246 continue;
247 }
248
249 tracing::debug!(
250 "merkle block contains the transaction, obtaining core chain locked height"
251 );
252
253 let mut core_chain_locked_height;
258 loop {
259 let GetTransactionResponse {
260 height,
261 is_chain_locked,
262 ..
263 } = self
264 .execute(
265 GetTransactionRequest {
266 id: transaction_id.to_string(),
267 },
268 RequestSettings::default(),
269 )
270 .await .into_inner()?;
272
273 core_chain_locked_height = height;
274
275 if is_chain_locked {
276 break;
277 }
278
279 tracing::trace!("the transaction is on height {} but not chainlocked. try again in 1 sec", height);
280
281 sleep(Duration::from_secs(1)).await;
282 }
283
284 tracing::debug!(
285 "the transaction is chainlocked on height {}, waiting platform for reaching the same core height",
286 core_chain_locked_height
287 );
288
289 loop {
291 let (_epoch, metadata) =
292 Epoch::fetch_current_with_metadata(self).await?;
293
294 if metadata.core_chain_locked_height >= core_chain_locked_height {
295 break;
296 }
297
298 tracing::trace!(
299 "platform chain locked core height {} but we need {}. try again in 1 sec",
300 metadata.core_chain_locked_height,
301 core_chain_locked_height,
302 );
303
304 sleep(Duration::from_secs(1)).await;
305 }
306
307 let asset_lock_proof = AssetLockProof::Chain(ChainAssetLockProof {
308 core_chain_locked_height,
309 out_point: OutPoint {
310 txid: transaction.txid(),
311 vout: 0,
312 },
313 });
314
315 tracing::debug!(
316 ?asset_lock_proof,
317 "merkle block contains the broadcasted transaction, returning chain asset lock proof"
318 );
319
320 return Ok(asset_lock_proof);
321 }
322 Some(transactions_with_proofs_response::Responses::RawTransactions(_)) => {
323 tracing::trace!("received transaction(s), ignoring")
324 }
325 None => tracing::trace!(
326 "received empty response as a workaround for the bug in tonic, ignoring"
327 ),
328 }
329 }
330 };
331
332 match time_out {
334 Some(duration) => timeout(duration, stream_processing).await.map_err(|_| {
335 Error::TimeoutReached(duration, String::from("receiving asset lock proof"))
336 })?,
337 None => stream_processing.await,
338 }
339 }
340}