use crate::{
Client, Error, ExtrinsicDecodable, UserError,
block::{
BlockExtrinsicMetadata,
encoded::{BlockEncodedExtrinsic, BlockEncodedExtrinsicsQuery},
events::{BlockEvents, BlockEventsQuery},
extrinsic_options::Options,
signed::BlockSignedExtrinsic,
},
};
use avail_rust_core::{
ExtrinsicSignature, H256, HasHeader, MultiAddress, RpcError, rpc::ExtrinsicFilter, types::HashStringNumber,
};
use codec::Decode;
pub struct BlockExtrinsicsQuery {
xt: BlockEncodedExtrinsicsQuery,
}
impl BlockExtrinsicsQuery {
pub fn new(client: Client, block_id: HashStringNumber) -> Self {
Self { xt: BlockEncodedExtrinsicsQuery::new(client, block_id) }
}
pub async fn get<T: HasHeader + Decode>(
&self,
extrinsic_id: impl Into<HashStringNumber>,
) -> Result<Option<BlockExtrinsic<T>>, Error> {
async fn inner<T: HasHeader + Decode>(
s: &BlockExtrinsicsQuery,
extrinsic_id: HashStringNumber,
) -> Result<Option<BlockExtrinsic<T>>, Error> {
let filter = match extrinsic_id {
HashStringNumber::Hash(x) => ExtrinsicFilter::from(x),
HashStringNumber::String(x) => ExtrinsicFilter::try_from(x).map_err(UserError::Decoding)?,
HashStringNumber::Number(x) => ExtrinsicFilter::from(x),
};
s.first::<T>(Options::new().filter(filter)).await
}
inner::<T>(self, extrinsic_id.into()).await
}
pub async fn first<T: HasHeader + Decode>(&self, mut opts: Options) -> Result<Option<BlockExtrinsic<T>>, Error> {
opts.filter = opts.filter.or(Some(T::HEADER_INDEX.into()));
let encoded = self.xt.first(opts).await?;
let Some(encoded) = encoded else {
return Ok(None);
};
Ok(Some(encoded.as_extrinsic::<T>()?))
}
pub async fn last<T: HasHeader + Decode>(&self, mut opts: Options) -> Result<Option<BlockExtrinsic<T>>, Error> {
opts.filter = opts.filter.or(Some(T::HEADER_INDEX.into()));
let encoded = self.xt.last(opts).await?;
let Some(encoded) = encoded else {
return Ok(None);
};
Ok(Some(encoded.as_extrinsic::<T>()?))
}
pub async fn all<T: HasHeader + Decode>(&self, mut opts: Options) -> Result<Vec<BlockExtrinsic<T>>, Error> {
opts.filter = opts.filter.or(Some(T::HEADER_INDEX.into()));
let all = self.xt.all(opts).await?;
let mut result = Vec::with_capacity(all.len());
for encoded in all {
result.push(encoded.as_extrinsic::<T>()?);
}
Ok(result)
}
pub async fn count<T: HasHeader>(&self, mut opts: Options) -> Result<usize, Error> {
opts.filter = Some(T::HEADER_INDEX.into());
return self.xt.count(opts).await;
}
pub async fn exists<T: HasHeader>(&self, mut opts: Options) -> Result<bool, Error> {
opts.filter = Some(T::HEADER_INDEX.into());
return self.xt.exists(opts).await;
}
pub fn set_retry_on_error(&mut self, value: Option<bool>) {
self.xt.set_retry_on_error(value);
}
pub fn should_retry_on_error(&self) -> bool {
self.xt.should_retry_on_error()
}
}
#[derive(Debug, Clone)]
pub struct BlockExtrinsic<T: HasHeader + Decode> {
pub signature: Option<ExtrinsicSignature>,
pub call: T,
pub metadata: BlockExtrinsicMetadata,
}
impl<T: HasHeader + Decode> BlockExtrinsic<T> {
pub fn new(signature: Option<ExtrinsicSignature>, call: T, metadata: BlockExtrinsicMetadata) -> Self {
Self { signature, call, metadata }
}
pub async fn events(&self, client: Client) -> Result<BlockEvents, Error> {
let events = BlockEventsQuery::new(client, self.metadata.block_id)
.extrinsic(self.ext_index())
.await?;
if events.is_empty() {
return Err(RpcError::ExpectedData("No events found for extrinsic".into()).into());
};
Ok(events)
}
pub fn ext_index(&self) -> u32 {
self.metadata.ext_index
}
pub fn ext_hash(&self) -> H256 {
self.metadata.ext_hash
}
pub fn app_id(&self) -> Option<u32> {
Some(self.signature.as_ref()?.extra.app_id)
}
pub fn nonce(&self) -> Option<u32> {
Some(self.signature.as_ref()?.extra.nonce)
}
pub fn tip(&self) -> Option<u128> {
Some(self.signature.as_ref()?.extra.tip)
}
pub fn ss58_address(&self) -> Option<String> {
match &self.signature.as_ref()?.address {
MultiAddress::Id(x) => Some(std::format!("{}", x)),
_ => None,
}
}
pub fn as_signed(self) -> Result<BlockSignedExtrinsic<T>, Error> {
BlockSignedExtrinsic::<T>::try_from(self).map_err(Error::Other)
}
}
impl<T: HasHeader + Decode> TryFrom<BlockEncodedExtrinsic> for BlockExtrinsic<T> {
type Error = String;
fn try_from(value: BlockEncodedExtrinsic) -> Result<Self, Self::Error> {
let call = T::from_call(value.call)?;
Ok(Self::new(value.signature, call, value.metadata))
}
}
impl<T: HasHeader + Decode> TryFrom<&BlockEncodedExtrinsic> for BlockExtrinsic<T> {
type Error = String;
fn try_from(value: &BlockEncodedExtrinsic) -> Result<Self, Self::Error> {
let call = T::from_call(&value.call)?;
Ok(Self::new(value.signature.clone(), call, value.metadata.clone()))
}
}
#[cfg(test)]
pub mod tests {
use super::*;
use crate::TURING_ENDPOINT;
use avail_rust_core::avail::{
balances::tx::TransferAllowDeath, data_availability::tx::SubmitData, timestamp::tx::Set,
vector::tx::FailedSendMessageTxs,
};
fn match_timestamp(ext: &BlockExtrinsic<Set>) {
assert_eq!(
std::format!("{:?}", ext.ext_hash()),
"0xdbfa60611f72a714100338db1c7b11c66636a76f116b214d879de069afe67a74"
);
assert_eq!(ext.ext_index(), 0);
assert_eq!(ext.nonce(), None);
assert!(ext.signature.is_none());
assert!(ext.app_id().is_none());
assert_eq!(ext.call.now, 1761567760000);
}
fn match_failed_send_message(ext: &BlockExtrinsic<FailedSendMessageTxs>) {
assert_eq!(
std::format!("{:?}", ext.ext_hash()),
"0x92cdb77314063a01930b093516d19a453399710cc8ae635ff5ab6cf76b26f218"
);
assert_eq!(ext.ext_index(), 3);
assert_eq!(ext.nonce(), None);
assert!(ext.signature.is_none());
assert!(ext.app_id().is_none());
assert_eq!(ext.call.failed_txs.len(), 0);
}
fn match_submit_data_1(ext: &BlockExtrinsic<SubmitData>) {
assert_eq!(
std::format!("{:?}", ext.ext_hash()),
"0x8b84294cba5f2b88e2887ac999ebac3806af7be9cca2a521fc889421f240f3ef"
);
assert_eq!(ext.ext_index(), 1);
assert_eq!(ext.nonce(), Some(30));
assert!(ext.signature.is_some());
assert_eq!(ext.app_id(), Some(1));
assert_eq!(ext.ss58_address(), Some("5Ev2jfLbYH6ENZ8ThTmqBX58zoinvHyqvRMvtoiUnLLcv1NJ".to_string()));
assert_eq!(String::from_utf8(ext.call.data.clone()).unwrap(), "AABBCC");
}
fn match_submit_data_2(ext: &BlockExtrinsic<SubmitData>) {
assert_eq!(
std::format!("{:?}", ext.ext_hash()),
"0x19fab0492322016c644af12f1547c587ef51edd10311db85cb3aa2680f6ae4ba"
);
assert_eq!(ext.ext_index(), 2);
assert_eq!(ext.nonce(), Some(4));
assert!(ext.signature.is_some());
assert_eq!(ext.app_id(), Some(2));
assert_eq!(ext.ss58_address(), Some("5DPDXCcqk1YNVZ3M9s9iwJnr9XAVfTxf8hNa4LS51fjHKAzk".to_string()));
assert_eq!(String::from_utf8(ext.call.data.clone()).unwrap(), "CCBBAA");
}
#[tokio::test]
async fn query_get_test() {
let client = Client::new(TURING_ENDPOINT).await.unwrap();
let query = client.block(2491314).extrinsics();
let ext = query.get::<Set>(0).await.unwrap().unwrap();
match_timestamp(&ext);
let ext = query.get::<SubmitData>(1).await.unwrap().unwrap();
match_submit_data_1(&ext);
let ext = query.get::<SubmitData>(2).await.unwrap().unwrap();
match_submit_data_2(&ext);
let ext = query.get::<FailedSendMessageTxs>(3).await.unwrap().unwrap();
match_failed_send_message(&ext);
assert!(query.get::<FailedSendMessageTxs>(2).await.is_err());
assert!(query.get::<FailedSendMessageTxs>(4).await.unwrap().is_none());
}
#[tokio::test]
async fn query_first_test() {
let client = Client::new(TURING_ENDPOINT).await.unwrap();
let query = client.block(2491314).extrinsics();
let opts = Options::new().app_id(1);
let ext = query.first(opts).await.unwrap().unwrap();
match_submit_data_1(&ext);
let opts = Options::new().app_id(2);
let ext = query.first(opts).await.unwrap().unwrap();
match_submit_data_2(&ext);
let opts = Options::new().nonce(30);
let ext = query.first(opts).await.unwrap().unwrap();
match_submit_data_1(&ext);
let opts = Options::new().nonce(4);
let ext = query.first(opts).await.unwrap().unwrap();
match_submit_data_2(&ext);
let opts = Options::new().filter(SubmitData::HEADER_INDEX);
let ext = query.first(opts).await.unwrap().unwrap();
match_submit_data_1(&ext);
let opts = Options::new().filter(SubmitData::HEADER_INDEX.0);
let ext = query.first(opts).await.unwrap().unwrap();
match_submit_data_1(&ext);
let ext = query.first::<Set>(Default::default()).await.unwrap().unwrap();
match_timestamp(&ext);
let opts = Options::new().filter(1u32);
assert!(query.first::<Set>(opts).await.is_err());
let opts = Options::new().filter(100u32);
assert!(query.first::<Set>(opts).await.unwrap().is_none());
}
#[tokio::test]
async fn query_last_test() {
let client = Client::new(TURING_ENDPOINT).await.unwrap();
let query = client.block(2491314).extrinsics();
let opts = Options::new().app_id(1);
let ext = query.last(opts).await.unwrap().unwrap();
match_submit_data_1(&ext);
let opts = Options::new().app_id(2);
let ext = query.last(opts).await.unwrap().unwrap();
match_submit_data_2(&ext);
let opts = Options::new().nonce(30);
let ext = query.last(opts).await.unwrap().unwrap();
match_submit_data_1(&ext);
let opts = Options::new().nonce(4);
let ext = query.last(opts).await.unwrap().unwrap();
match_submit_data_2(&ext);
let opts = Options::new().filter(SubmitData::HEADER_INDEX);
let ext = query.last(opts).await.unwrap().unwrap();
match_submit_data_2(&ext);
let opts = Options::new().filter(SubmitData::HEADER_INDEX.0);
let ext = query.last(opts).await.unwrap().unwrap();
match_submit_data_2(&ext);
let ext = query.last(Default::default()).await.unwrap().unwrap();
match_failed_send_message(&ext);
let opts = Options::new().filter(1u32);
assert!(query.last::<Set>(opts).await.is_err());
let opts = Options::new().filter(100u32);
assert!(query.last::<Set>(opts).await.unwrap().is_none());
}
#[tokio::test]
async fn query_all_test() {
let client = Client::new(TURING_ENDPOINT).await.unwrap();
let query = client.block(2491314).extrinsics();
let opts = Options::new().app_id(1);
let ext = query.all(opts).await.unwrap();
match_submit_data_1(&ext[0]);
let opts = Options::new().app_id(2);
let ext = query.all(opts).await.unwrap();
match_submit_data_2(&ext[0]);
assert_eq!(ext.len(), 1);
let opts = Options::new().nonce(30);
let ext = query.all(opts).await.unwrap();
match_submit_data_1(&ext[0]);
assert_eq!(ext.len(), 1);
let opts = Options::new().nonce(4);
let ext = query.all(opts).await.unwrap();
match_submit_data_2(&ext[0]);
assert_eq!(ext.len(), 1);
let opts = Options::new().filter(SubmitData::HEADER_INDEX);
let ext = query.all(opts).await.unwrap();
match_submit_data_1(&ext[0]);
match_submit_data_2(&ext[1]);
assert_eq!(ext.len(), 2);
let opts = Options::new().filter(SubmitData::HEADER_INDEX.0);
let ext = query.all(opts).await.unwrap();
match_submit_data_1(&ext[0]);
match_submit_data_2(&ext[1]);
assert_eq!(ext.len(), 2);
let ext = query.all::<SubmitData>(Default::default()).await.unwrap();
match_submit_data_1(&ext[0]);
match_submit_data_2(&ext[1]);
assert_eq!(ext.len(), 2);
let ext = query.all::<Set>(Default::default()).await.unwrap();
match_timestamp(&ext[0]);
assert_eq!(ext.len(), 1);
let ext = query.all::<FailedSendMessageTxs>(Default::default()).await.unwrap();
match_failed_send_message(&ext[0]);
assert_eq!(ext.len(), 1);
let opts = Options::new().filter(1u32);
assert!(query.all::<Set>(opts).await.is_err());
let opts = Options::new().filter(100u32);
let ext = query.all::<Set>(opts).await.unwrap();
assert_eq!(ext.len(), 0)
}
#[tokio::test]
async fn query_count_test() {
let client = Client::new(TURING_ENDPOINT).await.unwrap();
let query = client.block(2491314).extrinsics();
let opts = Options::new().app_id(1);
assert_eq!(query.count::<SubmitData>(opts).await.unwrap(), 1);
let opts = Options::new().app_id(2);
assert_eq!(query.count::<SubmitData>(opts).await.unwrap(), 1);
let opts = Options::new().nonce(30);
assert_eq!(query.count::<SubmitData>(opts).await.unwrap(), 1);
let opts = Options::new().nonce(4);
assert_eq!(query.count::<SubmitData>(opts).await.unwrap(), 1);
assert_eq!(query.count::<SubmitData>(Default::default()).await.unwrap(), 2);
assert_eq!(query.count::<Set>(Default::default()).await.unwrap(), 1);
assert_eq!(query.count::<TransferAllowDeath>(Default::default()).await.unwrap(), 0);
}
#[tokio::test]
async fn query_exists_test() {
let client = Client::new(TURING_ENDPOINT).await.unwrap();
let query = client.block(2491314).extrinsics();
let opts = Options::new().app_id(1);
assert_eq!(query.exists::<SubmitData>(opts).await.unwrap(), true);
let opts = Options::new().app_id(2);
assert_eq!(query.exists::<SubmitData>(opts).await.unwrap(), true);
let opts = Options::new().nonce(30);
assert_eq!(query.exists::<SubmitData>(opts).await.unwrap(), true);
let opts = Options::new().nonce(4);
assert_eq!(query.exists::<SubmitData>(opts).await.unwrap(), true);
assert_eq!(query.exists::<SubmitData>(Default::default()).await.unwrap(), true);
assert_eq!(query.exists::<Set>(Default::default()).await.unwrap(), true);
assert_eq!(query.exists::<TransferAllowDeath>(Default::default()).await.unwrap(), false);
}
}