1use crate::{
4 Client, Error, UserError,
5 block::{self, Block},
6 conversions,
7 subscription::Sub,
8 transaction_options::{RefinedMortality, RefinedOptions},
9};
10use avail_rust_core::{
11 AccountId, BlockInfo, EncodeSelector, H256, HasHeader, RpcError, rpc::ExtrinsicOpts,
12 substrate::extrinsic::ExtrinsicAdditional, types::metadata::HashString,
13};
14use codec::Decode;
15#[cfg(feature = "tracing")]
16use tracing::info;
17
18#[derive(Clone)]
21pub struct SubmittedTransaction {
22 client: Client,
23 pub ext_hash: H256,
24 pub account_id: AccountId,
25 pub options: RefinedOptions,
26 pub additional: ExtrinsicAdditional,
27}
28
29impl SubmittedTransaction {
30 pub fn new(
35 client: Client,
36 ext_hash: H256,
37 account_id: AccountId,
38 options: RefinedOptions,
39 additional: ExtrinsicAdditional,
40 ) -> Self {
41 Self { client, ext_hash, account_id, options, additional }
42 }
43
44 pub async fn receipt(&self, use_best_block: bool) -> Result<Option<TransactionReceipt>, Error> {
55 Utils::transaction_receipt(
56 self.client.clone(),
57 self.ext_hash,
58 self.options.nonce,
59 &self.account_id,
60 &self.options.mortality,
61 use_best_block,
62 )
63 .await
64 }
65}
66
67#[derive(Debug, Clone, Copy, PartialEq, Eq)]
71#[repr(u8)]
72pub enum BlockState {
73 Included = 0,
75 Finalized = 1,
77 Discarded = 2,
79 DoesNotExist = 3,
81}
82
83impl std::fmt::Display for BlockState {
84 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
85 match self {
86 BlockState::Included => std::write!(f, "Included"),
87 BlockState::Finalized => std::write!(f, "Finalized"),
88 BlockState::Discarded => std::write!(f, "Discarded"),
89 BlockState::DoesNotExist => std::write!(f, "DoesNotExist"),
90 }
91 }
92}
93
94#[derive(Clone)]
96pub struct TransactionReceipt {
97 client: Client,
98 pub block_hash: H256,
99 pub block_height: u32,
100 pub ext_hash: H256,
101 pub ext_index: u32,
102}
103
104impl TransactionReceipt {
105 pub fn new(client: Client, block_hash: H256, block_height: u32, ext_hash: H256, ext_index: u32) -> Self {
107 Self { client, block_hash, block_height, ext_hash, ext_index }
108 }
109
110 pub async fn block_state(&self) -> Result<BlockState, Error> {
116 self.client.chain().block_state(self.block_hash).await
117 }
118
119 pub async fn extrinsic<T: HasHeader + Decode>(&self) -> Result<block::BlockExtrinsic<T>, Error> {
125 let block = Block::new(self.client.clone(), self.block_hash).extrinsics();
126 let ext: Option<block::BlockExtrinsic<T>> = block.get(self.ext_index).await?;
127 let Some(ext) = ext else {
128 return Err(RpcError::ExpectedData("No extrinsic found at the requested index.".into()).into());
129 };
130
131 Ok(ext)
132 }
133
134 pub async fn encoded(&self) -> Result<block::BlockEncodedExtrinsic, Error> {
140 let block = Block::new(self.client.clone(), self.block_hash).encoded();
141 let ext = block.get(self.ext_index).await?;
142 let Some(ext) = ext else {
143 return Err(RpcError::ExpectedData("No extrinsic found at the requested index.".into()).into());
144 };
145
146 Ok(ext)
147 }
148
149 pub async fn events(&self) -> Result<crate::block::events::BlockEvents, Error> {
155 let block = Block::new(self.client.clone(), self.block_hash).events();
156 let events = block.extrinsic(self.ext_index).await?;
157 if events.is_empty() {
158 return Err(RpcError::ExpectedData("No events found for the requested extrinsic.".into()).into());
159 };
160
161 Ok(events)
162 }
163
164 pub async fn from_range(
175 client: Client,
176 ext_hash: impl Into<HashString>,
177 block_start: u32,
178 block_end: u32,
179 use_best_block: bool,
180 ) -> Result<Option<TransactionReceipt>, Error> {
181 if block_start > block_end {
182 return Err(UserError::ValidationFailed("Block Start cannot start after Block End".into()).into());
183 }
184
185 let tx_hash = conversions::hash_string::to_hash(ext_hash)?;
186 let mut sub = Sub::new(client.clone());
187 sub.use_best_block(use_best_block);
188 sub.set_block_height(block_start);
189
190 loop {
191 let block_info = sub.next().await?;
192
193 let block = Block::new(client.clone(), block_info.height);
194 let opts = ExtrinsicOpts::new().filter(tx_hash).encode_as(EncodeSelector::None);
195 let infos = block.extrinsic_infos(opts).await?;
196
197 if let Some(info) = infos.first() {
198 let tr = TransactionReceipt::new(
199 client.clone(),
200 block_info.hash,
201 block_info.height,
202 info.ext_hash,
203 info.ext_index,
204 );
205 return Ok(Some(tr));
206 }
207
208 if block_info.height >= block_end {
209 return Ok(None);
210 }
211 }
212 }
213}
214
215pub struct Utils;
217impl Utils {
218 pub async fn transaction_receipt(
225 client: Client,
226 tx_hash: H256,
227 nonce: u32,
228 account_id: &AccountId,
229 mortality: &RefinedMortality,
230 use_best_block: bool,
231 ) -> Result<Option<TransactionReceipt>, Error> {
232 let Some(block_info) =
233 Self::find_correct_block_info(&client, nonce, tx_hash, account_id, mortality, use_best_block).await?
234 else {
235 return Ok(None);
236 };
237
238 let block = Block::new(client.clone(), block_info.hash);
239 let opts = ExtrinsicOpts::new().filter(tx_hash).encode_as(EncodeSelector::None);
240 let ext_info = block.extrinsic_infos(opts).await?;
241
242 let Some(ext_info) = ext_info.first() else {
243 return Ok(None);
244 };
245
246 Ok(Some(TransactionReceipt::new(
247 client, block_info.hash, block_info.height, ext_info.ext_hash, ext_info.ext_index,
248 )))
249 }
250
251 pub async fn find_correct_block_info(
262 client: &Client,
263 nonce: u32,
264 tx_hash: H256,
265 account_id: &AccountId,
266 mortality: &RefinedMortality,
267 use_best_block: bool,
268 ) -> Result<Option<BlockInfo>, Error> {
269 let mortality_ends_height = mortality.block_height.saturating_add(mortality.period as u32);
270
271 let mut sub = Sub::new(client.clone());
272 sub.set_block_height(mortality.block_height);
273 sub.use_best_block(use_best_block);
274
275 let mut current_block_height = mortality.block_height;
276
277 #[cfg(feature = "tracing")]
278 {
279 match use_best_block {
280 true => {
281 let info = client.best().block_info().await?;
282 info!(target: "lib", "Nonce: {} Account address: {} Current Best Height: {} Mortality End Height: {}", nonce, account_id, info.height, mortality_ends_height);
283 },
284 false => {
285 let info = client.finalized().block_info().await?;
286 info!(target: "lib", "Nonce: {} Account address: {} Current Finalized Height: {} Mortality End Height: {}", nonce, account_id, info.height, mortality_ends_height);
287 },
288 };
289 }
290
291 while mortality_ends_height >= current_block_height {
292 let info = sub.next().await?;
293 current_block_height = info.height;
294
295 let state_nonce = client.chain().block_nonce(account_id.clone(), info.hash).await?;
296 if state_nonce > nonce {
297 trace_new_block(nonce, state_nonce, account_id, info, true);
298 return Ok(Some(info));
299 }
300 if state_nonce == 0 {
301 let block = Block::new(client.clone(), info.hash);
302 let opts = ExtrinsicOpts::new().filter(tx_hash).encode_as(EncodeSelector::None);
303 let ext = block.extrinsic_infos(opts).await?;
304 if !ext.is_empty() {
305 trace_new_block(nonce, state_nonce, account_id, info, true);
306 return Ok(Some(info));
307 }
308 }
309
310 trace_new_block(nonce, state_nonce, account_id, info, false);
311 }
312
313 Ok(None)
314 }
315}
316
317fn trace_new_block(nonce: u32, state_nonce: u32, account_id: &AccountId, block_info: BlockInfo, search_done: bool) {
322 #[cfg(feature = "tracing")]
323 {
324 if search_done {
325 info!(target: "lib", "Account ({}, {}). At block ({}, {:?}) found nonce: {}. Search is done", nonce, account_id, block_info.height, block_info.hash, state_nonce);
326 } else {
327 info!(target: "lib", "Account ({}, {}). At block ({}, {:?}) found nonce: {}.", nonce, account_id, block_info.height, block_info.hash, state_nonce);
328 }
329 }
330
331 #[cfg(not(feature = "tracing"))]
332 {
333 let _ = (nonce, state_nonce, account_id, block_info, search_done);
334 }
335}