use crate::{
Client, Error, UserError,
block::{self, Block},
conversions,
subscription::Sub,
transaction_options::{RefinedMortality, RefinedOptions},
};
use avail_rust_core::{
AccountId, BlockInfo, EncodeSelector, H256, HasHeader, RpcError, rpc::ExtrinsicOpts,
substrate::extrinsic::ExtrinsicAdditional, types::metadata::HashString,
};
use codec::Decode;
#[cfg(feature = "tracing")]
use tracing::info;
#[derive(Clone)]
pub struct SubmittedTransaction {
client: Client,
pub ext_hash: H256,
pub account_id: AccountId,
pub options: RefinedOptions,
pub additional: ExtrinsicAdditional,
}
impl SubmittedTransaction {
pub fn new(
client: Client,
ext_hash: H256,
account_id: AccountId,
options: RefinedOptions,
additional: ExtrinsicAdditional,
) -> Self {
Self { client, ext_hash, account_id, options, additional }
}
pub async fn receipt(&self, use_best_block: bool) -> Result<Option<TransactionReceipt>, Error> {
Utils::transaction_receipt(
self.client.clone(),
self.ext_hash,
self.options.nonce,
&self.account_id,
&self.options.mortality,
use_best_block,
)
.await
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[repr(u8)]
pub enum BlockState {
Included = 0,
Finalized = 1,
Discarded = 2,
DoesNotExist = 3,
}
impl std::fmt::Display for BlockState {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
BlockState::Included => std::write!(f, "Included"),
BlockState::Finalized => std::write!(f, "Finalized"),
BlockState::Discarded => std::write!(f, "Discarded"),
BlockState::DoesNotExist => std::write!(f, "DoesNotExist"),
}
}
}
#[derive(Clone)]
pub struct TransactionReceipt {
client: Client,
pub block_hash: H256,
pub block_height: u32,
pub ext_hash: H256,
pub ext_index: u32,
}
impl TransactionReceipt {
pub fn new(client: Client, block_hash: H256, block_height: u32, ext_hash: H256, ext_index: u32) -> Self {
Self { client, block_hash, block_height, ext_hash, ext_index }
}
pub async fn block_state(&self) -> Result<BlockState, Error> {
self.client.chain().block_state(self.block_hash).await
}
pub async fn extrinsic<T: HasHeader + Decode>(&self) -> Result<block::BlockExtrinsic<T>, Error> {
let block = Block::new(self.client.clone(), self.block_hash).extrinsics();
let ext: Option<block::BlockExtrinsic<T>> = block.get(self.ext_index).await?;
let Some(ext) = ext else {
return Err(RpcError::ExpectedData("No extrinsic found at the requested index.".into()).into());
};
Ok(ext)
}
pub async fn encoded(&self) -> Result<block::BlockEncodedExtrinsic, Error> {
let block = Block::new(self.client.clone(), self.block_hash).encoded();
let ext = block.get(self.ext_index).await?;
let Some(ext) = ext else {
return Err(RpcError::ExpectedData("No extrinsic found at the requested index.".into()).into());
};
Ok(ext)
}
pub async fn events(&self) -> Result<crate::block::events::BlockEvents, Error> {
let block = Block::new(self.client.clone(), self.block_hash).events();
let events = block.extrinsic(self.ext_index).await?;
if events.is_empty() {
return Err(RpcError::ExpectedData("No events found for the requested extrinsic.".into()).into());
};
Ok(events)
}
pub async fn from_range(
client: Client,
ext_hash: impl Into<HashString>,
block_start: u32,
block_end: u32,
use_best_block: bool,
) -> Result<Option<TransactionReceipt>, Error> {
if block_start > block_end {
return Err(UserError::ValidationFailed("Block Start cannot start after Block End".into()).into());
}
let tx_hash = conversions::hash_string::to_hash(ext_hash)?;
let mut sub = Sub::new(client.clone());
sub.use_best_block(use_best_block);
sub.set_block_height(block_start);
loop {
let block_info = sub.next().await?;
let block = Block::new(client.clone(), block_info.height);
let opts = ExtrinsicOpts::new().filter(tx_hash).encode_as(EncodeSelector::None);
let infos = block.extrinsic_infos(opts).await?;
if let Some(info) = infos.first() {
let tr = TransactionReceipt::new(
client.clone(),
block_info.hash,
block_info.height,
info.ext_hash,
info.ext_index,
);
return Ok(Some(tr));
}
if block_info.height >= block_end {
return Ok(None);
}
}
}
}
pub struct Utils;
impl Utils {
pub async fn transaction_receipt(
client: Client,
tx_hash: H256,
nonce: u32,
account_id: &AccountId,
mortality: &RefinedMortality,
use_best_block: bool,
) -> Result<Option<TransactionReceipt>, Error> {
let Some(block_info) =
Self::find_correct_block_info(&client, nonce, tx_hash, account_id, mortality, use_best_block).await?
else {
return Ok(None);
};
let block = Block::new(client.clone(), block_info.hash);
let opts = ExtrinsicOpts::new().filter(tx_hash).encode_as(EncodeSelector::None);
let ext_info = block.extrinsic_infos(opts).await?;
let Some(ext_info) = ext_info.first() else {
return Ok(None);
};
Ok(Some(TransactionReceipt::new(
client, block_info.hash, block_info.height, ext_info.ext_hash, ext_info.ext_index,
)))
}
pub async fn find_correct_block_info(
client: &Client,
nonce: u32,
tx_hash: H256,
account_id: &AccountId,
mortality: &RefinedMortality,
use_best_block: bool,
) -> Result<Option<BlockInfo>, Error> {
let mortality_ends_height = mortality.block_height.saturating_add(mortality.period as u32);
let mut sub = Sub::new(client.clone());
sub.set_block_height(mortality.block_height);
sub.use_best_block(use_best_block);
let mut current_block_height = mortality.block_height;
#[cfg(feature = "tracing")]
{
match use_best_block {
true => {
let info = client.best().block_info().await?;
info!(target: "lib", "Nonce: {} Account address: {} Current Best Height: {} Mortality End Height: {}", nonce, account_id, info.height, mortality_ends_height);
},
false => {
let info = client.finalized().block_info().await?;
info!(target: "lib", "Nonce: {} Account address: {} Current Finalized Height: {} Mortality End Height: {}", nonce, account_id, info.height, mortality_ends_height);
},
};
}
while mortality_ends_height >= current_block_height {
let info = sub.next().await?;
current_block_height = info.height;
let state_nonce = client.chain().block_nonce(account_id.clone(), info.hash).await?;
if state_nonce > nonce {
trace_new_block(nonce, state_nonce, account_id, info, true);
return Ok(Some(info));
}
if state_nonce == 0 {
let block = Block::new(client.clone(), info.hash);
let opts = ExtrinsicOpts::new().filter(tx_hash).encode_as(EncodeSelector::None);
let ext = block.extrinsic_infos(opts).await?;
if !ext.is_empty() {
trace_new_block(nonce, state_nonce, account_id, info, true);
return Ok(Some(info));
}
}
trace_new_block(nonce, state_nonce, account_id, info, false);
}
Ok(None)
}
}
fn trace_new_block(nonce: u32, state_nonce: u32, account_id: &AccountId, block_info: BlockInfo, search_done: bool) {
#[cfg(feature = "tracing")]
{
if search_done {
info!(target: "lib", "Account ({}, {}). At block ({}, {:?}) found nonce: {}. Search is done", nonce, account_id, block_info.height, block_info.hash, state_nonce);
} else {
info!(target: "lib", "Account ({}, {}). At block ({}, {:?}) found nonce: {}.", nonce, account_id, block_info.height, block_info.hash, state_nonce);
}
}
#[cfg(not(feature = "tracing"))]
{
let _ = (nonce, state_nonce, account_id, block_info, search_done);
}
}