use crate::{
Client, Error, UserError,
block::{
BlockExtrinsicMetadata,
events::{BlockEvents, BlockEventsQuery},
extrinsic::BlockExtrinsic,
extrinsic_options::Options,
shared::BlockContext,
signed::BlockSignedExtrinsic,
},
};
use avail_rust_core::{
EncodeSelector, EncodedExtrinsic, ExtrinsicSignature, H256, HasHeader, HashNumber, RpcError,
rpc::{self, ExtrinsicFilter, ExtrinsicInfo},
types::HashStringNumber,
};
use codec::Decode;
pub struct BlockEncodedExtrinsicsQuery {
ctx: BlockContext,
}
impl BlockEncodedExtrinsicsQuery {
pub fn new(client: Client, block_id: HashStringNumber) -> Self {
Self { ctx: BlockContext::new(client, block_id) }
}
pub async fn get(&self, extrinsic_id: impl Into<HashStringNumber>) -> Result<Option<BlockEncodedExtrinsic>, Error> {
async fn inner(
s: &BlockEncodedExtrinsicsQuery,
extrinsic_id: HashStringNumber,
) -> Result<Option<BlockEncodedExtrinsic>, 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),
};
let opts = Options::new().filter(filter);
s.first(opts).await
}
inner(self, extrinsic_id.into()).await
}
pub async fn first(&self, opts: Options) -> Result<Option<BlockEncodedExtrinsic>, Error> {
let block_id = self.ctx.hash_number()?;
let chain = self.ctx.chain();
let opts = opts.to_rpc_opts(EncodeSelector::Extrinsic);
let mut result = chain.system_fetch_extrinsics(block_id, opts).await?;
let Some(info) = result.first_mut() else {
return Ok(None);
};
let ext = BlockEncodedExtrinsic::from_extrinsic_info(info, block_id)?;
Ok(Some(ext))
}
pub async fn last(&self, opts: Options) -> Result<Option<BlockEncodedExtrinsic>, Error> {
let block_id = self.ctx.hash_number()?;
let chain = self.ctx.chain();
let opts = opts.to_rpc_opts(EncodeSelector::Extrinsic);
let mut result = chain.system_fetch_extrinsics(block_id, opts).await?;
let Some(info) = result.last_mut() else {
return Ok(None);
};
let ext = BlockEncodedExtrinsic::from_extrinsic_info(info, block_id)?;
Ok(Some(ext))
}
pub async fn all(&self, opts: Options) -> Result<Vec<BlockEncodedExtrinsic>, Error> {
let block_id = self.ctx.hash_number()?;
let chain = self.ctx.chain();
let opts = opts.to_rpc_opts(EncodeSelector::Extrinsic);
let extrinsics = chain.system_fetch_extrinsics(block_id, opts).await?;
let mut result = Vec::with_capacity(extrinsics.len());
for info in extrinsics {
let ext = BlockEncodedExtrinsic::from_extrinsic_info(&info, block_id)?;
result.push(ext);
}
Ok(result)
}
pub async fn count(&self, opts: Options) -> Result<usize, Error> {
let opts: rpc::ExtrinsicOpts = opts.to_rpc_opts(EncodeSelector::None);
let block_id = self.ctx.block_id.clone();
let chain = self.ctx.chain();
let result = chain.system_fetch_extrinsics(block_id, opts).await?;
Ok(result.len())
}
pub async fn exists(&self, opts: Options) -> Result<bool, Error> {
self.count(opts).await.map(|x| x > 0)
}
pub fn set_retry_on_error(&mut self, value: Option<bool>) {
self.ctx.set_retry_on_error(value);
}
pub fn should_retry_on_error(&self) -> bool {
self.ctx.should_retry_on_error()
}
}
#[derive(Debug, Clone)]
pub struct BlockEncodedExtrinsic {
pub signature: Option<ExtrinsicSignature>,
pub call: Vec<u8>,
pub metadata: BlockExtrinsicMetadata,
}
impl BlockEncodedExtrinsic {
pub fn new(signature: Option<ExtrinsicSignature>, call: Vec<u8>, 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 the requested 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 {
avail_rust_core::MultiAddress::Id(account_id32) => Some(std::format!("{}", account_id32)),
_ => None,
}
}
pub fn as_extrinsic<T: HasHeader + Decode>(self) -> Result<BlockExtrinsic<T>, Error> {
BlockExtrinsic::<T>::try_from(self).map_err(Error::Other)
}
pub fn as_signed<T: HasHeader + Decode>(self) -> Result<BlockSignedExtrinsic<T>, Error> {
BlockSignedExtrinsic::<T>::try_from(self).map_err(Error::Other)
}
pub fn is<T: HasHeader>(&self) -> bool {
self.metadata.pallet_id == T::HEADER_INDEX.0 && self.metadata.variant_id == T::HEADER_INDEX.1
}
pub fn header(&self) -> (u8, u8) {
(self.metadata.pallet_id, self.metadata.variant_id)
}
pub fn from_extrinsic_info(info: &ExtrinsicInfo, block_id: HashNumber) -> Result<Self, Error> {
let metadata = BlockExtrinsicMetadata::from_extrinsic_info(info, block_id);
let Some(data) = info.data.as_ref() else {
return Err(Error::RpcError(RpcError::ExpectedData("Expected data for encoded extrinsic.".into())));
};
let extrinsic = EncodedExtrinsic::try_from(data).map_err(Error::Other)?;
Ok(BlockEncodedExtrinsic::new(extrinsic.signature, extrinsic.call, metadata))
}
}
#[cfg(test)]
pub mod tests {
use super::*;
use crate::TURING_ENDPOINT;
use avail_rust_core::{ExtrinsicDecodable, avail};
fn match_timestamp(ext: &BlockEncodedExtrinsic) {
assert_eq!(
std::format!("{:?}", ext.ext_hash()),
"0xdbfa60611f72a714100338db1c7b11c66636a76f116b214d879de069afe67a74"
);
assert_eq!(ext.ext_index(), 0);
assert_eq!(ext.nonce(), None);
assert_eq!(ext.header(), (3, 0));
assert!(ext.signature.is_none());
assert!(ext.app_id().is_none());
let set = avail::timestamp::tx::Set::from_call(&ext.call).unwrap();
assert_eq!(set.now, 1761567760000);
}
fn match_failed_send_message(ext: &BlockEncodedExtrinsic) {
assert_eq!(
std::format!("{:?}", ext.ext_hash()),
"0x92cdb77314063a01930b093516d19a453399710cc8ae635ff5ab6cf76b26f218"
);
assert_eq!(ext.header(), (39, 11));
assert_eq!(ext.ext_index(), 3);
assert_eq!(ext.nonce(), None);
assert!(ext.signature.is_none());
assert!(ext.app_id().is_none());
let f = avail::vector::tx::FailedSendMessageTxs::from_call(&ext.call).unwrap();
assert_eq!(f.failed_txs.len(), 0);
}
fn match_submit_data_1(ext: &BlockEncodedExtrinsic) {
assert_eq!(
std::format!("{:?}", ext.ext_hash()),
"0x8b84294cba5f2b88e2887ac999ebac3806af7be9cca2a521fc889421f240f3ef"
);
assert_eq!(ext.ext_index(), 1);
assert_eq!(ext.header(), (29, 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()));
let sd = avail::data_availability::tx::SubmitData::from_call(&ext.call).unwrap();
assert_eq!(String::from_utf8(sd.data).unwrap(), "AABBCC");
}
fn match_submit_data_2(ext: &BlockEncodedExtrinsic) {
assert_eq!(
std::format!("{:?}", ext.ext_hash()),
"0x19fab0492322016c644af12f1547c587ef51edd10311db85cb3aa2680f6ae4ba"
);
assert_eq!(ext.ext_index(), 2);
assert_eq!(ext.header(), (29, 1));
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()));
let sd = avail::data_availability::tx::SubmitData::from_call(&ext.call).unwrap();
assert_eq!(String::from_utf8(sd.data).unwrap(), "CCBBAA");
}
#[tokio::test]
async fn query_get_test() {
let client = Client::new(TURING_ENDPOINT).await.unwrap();
let query = client.block(2491314).encoded();
for i in 0..4usize {
let ext = query.get(i as u32).await.unwrap().unwrap();
match i {
0 => match_timestamp(&ext),
1 => match_submit_data_1(&ext),
2 => match_submit_data_2(&ext),
3 => match_failed_send_message(&ext),
_ => panic!(),
};
}
assert!(query.get(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).encoded();
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(avail::data_availability::tx::SubmitData::HEADER_INDEX);
let ext = query.first(opts).await.unwrap().unwrap();
match_submit_data_1(&ext);
let opts = Options::new().filter(avail::data_availability::tx::SubmitData::HEADER_INDEX.0);
let ext = query.first(opts).await.unwrap().unwrap();
match_submit_data_1(&ext);
let ext = query.first(Default::default()).await.unwrap().unwrap();
match_timestamp(&ext);
let opts = Options::new().filter(100u32);
assert!(query.first(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).encoded();
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(avail::data_availability::tx::SubmitData::HEADER_INDEX);
let ext = query.last(opts).await.unwrap().unwrap();
match_submit_data_2(&ext);
let opts = Options::new().filter(avail::data_availability::tx::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(100u32);
assert!(query.last(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).encoded();
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(avail::data_availability::tx::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(avail::data_availability::tx::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(Default::default()).await.unwrap();
match_timestamp(&ext[0]);
match_submit_data_1(&ext[1]);
match_submit_data_2(&ext[2]);
match_failed_send_message(&ext[3]);
assert_eq!(ext.len(), 4);
let opts = Options::new().filter(100u32);
let ext = query.all(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).encoded();
let opts = Options::new().app_id(1);
assert_eq!(query.count(opts).await.unwrap(), 1);
let opts = Options::new().app_id(2);
assert_eq!(query.count(opts).await.unwrap(), 1);
let opts = Options::new().nonce(30);
assert_eq!(query.count(opts).await.unwrap(), 1);
let opts = Options::new().nonce(4);
assert_eq!(query.count(opts).await.unwrap(), 1);
let opts = Options::new().filter(avail::data_availability::tx::SubmitData::HEADER_INDEX);
assert_eq!(query.count(opts).await.unwrap(), 2);
let opts = Options::new().filter(avail::data_availability::tx::SubmitData::HEADER_INDEX.0);
assert_eq!(query.count(opts).await.unwrap(), 2);
assert_eq!(query.count(Default::default()).await.unwrap(), 4);
let opts = Options::new().filter(100u32);
assert_eq!(query.count(opts).await.unwrap(), 0);
}
#[tokio::test]
async fn query_exists_test() {
let client = Client::new(TURING_ENDPOINT).await.unwrap();
let query = client.block(2491314).encoded();
let opts = Options::new().app_id(1);
assert_eq!(query.exists(opts).await.unwrap(), true);
let opts = Options::new().app_id(2);
assert_eq!(query.exists(opts).await.unwrap(), true);
let opts = Options::new().nonce(30);
assert_eq!(query.exists(opts).await.unwrap(), true);
let opts = Options::new().nonce(4);
assert_eq!(query.exists(opts).await.unwrap(), true);
let opts = Options::new().filter(avail::data_availability::tx::SubmitData::HEADER_INDEX);
assert_eq!(query.exists(opts).await.unwrap(), true);
let opts = Options::new().filter(avail::data_availability::tx::SubmitData::HEADER_INDEX.0);
assert_eq!(query.exists(opts).await.unwrap(), true);
assert_eq!(query.exists(Default::default()).await.unwrap(), true);
let opts = Options::new().filter(100u32);
assert_eq!(query.exists(opts).await.unwrap(), false);
}
}