use crate::{
BlockState, Client, Error, UserError, avail, conversions,
submission::SubmittedTransaction,
subxt_signer::sr25519::Keypair,
transaction_options::Options,
utils::{with_retry_on_error, with_retry_on_error_and_none},
};
use avail::{
balances::types::AccountData,
system::{storage as SystemStorage, types::AccountInfo},
};
#[cfg(feature = "next")]
use avail_rust_core::rpc::{
blob::{Blob, BlobInfo},
kate::DataProof,
};
use avail_rust_core::{
AccountId, AccountIdLike, AvailHeader, BlockInfo, H256, HashNumber, StorageMap, StorageValue, consensus,
ext::subxt_rpcs::client::RpcParams,
grandpa::GrandpaJustification,
header::DigestItem,
rpc::{
self, BlockPhaseEvent, Error as RpcError, ExtrinsicInfo, LegacyBlock,
kate::{BlockLength, Cell, GCellBlock, GDataProof, GMultiProof, GRow, ProofResponse},
runtime_api,
},
types::{
HashString,
metadata::{ChainInfo, HashStringNumber},
substrate::{FeeDetails, PerDispatchClassWeight, RuntimeDispatchInfo},
},
};
use codec::Decode;
pub struct Chain {
pub(crate) client: Client,
retry_on_error: Option<bool>,
retry_on_none: Option<bool>,
}
impl Chain {
pub fn new(client: Client) -> Self {
Self { client, retry_on_error: None, retry_on_none: None }
}
pub fn retry_on(mut self, error: Option<bool>, none: Option<bool>) -> Self {
self.retry_on_error = error;
self.retry_on_none = none;
self
}
pub async fn block_hash(&self, block_height: Option<u32>) -> Result<Option<H256>, RpcError> {
let retry = self.should_retry_on_error();
let retry_on_none = self.retry_on_none.unwrap_or(false);
let f = || async move { rpc::chain::get_block_hash(&self.client.rpc_client, block_height).await };
with_retry_on_error_and_none(f, retry, retry_on_none).await
}
pub async fn block_header(&self, at: Option<impl Into<HashStringNumber>>) -> Result<Option<AvailHeader>, Error> {
let retry_on_error = self.should_retry_on_error();
let retry_on_none = self.retry_on_none.unwrap_or(false);
let at = if let Some(at) = at {
Some(conversions::hash_string_number::to_hash(self, at).await?)
} else {
None
};
let f = || async move { rpc::chain::get_header(&self.client.rpc_client, at).await };
Ok(with_retry_on_error_and_none(f, retry_on_error, retry_on_none).await?)
}
pub async fn legacy_block(&self, at: Option<H256>) -> Result<Option<LegacyBlock>, RpcError> {
let retry = self.should_retry_on_error();
let retry_on_none = self.retry_on_none.unwrap_or(false);
let f = || async move { rpc::chain::get_block(&self.client.rpc_client, at).await };
with_retry_on_error_and_none(f, retry, retry_on_none).await
}
pub async fn block_nonce(
&self,
account_id: impl Into<AccountIdLike>,
at: impl Into<HashStringNumber>,
) -> Result<u32, Error> {
self.account_info(account_id, at).await.map(|x| x.nonce)
}
pub async fn account_nonce(&self, account_id: impl Into<AccountIdLike>) -> Result<u32, Error> {
let account_id = conversions::account_id_like::to_account_id(account_id)?;
let retry_on_error = self.should_retry_on_error();
let a = &account_id;
let f =
|| async move { rpc::system::account_next_index(&self.client.rpc_client, &std::format!("{}", a)).await };
Ok(with_retry_on_error(f, retry_on_error).await?)
}
pub async fn account_balance(
&self,
account_id: impl Into<AccountIdLike>,
at: impl Into<HashStringNumber>,
) -> Result<AccountData, Error> {
self.account_info(account_id, at).await.map(|x| x.data)
}
pub async fn account_info(
&self,
account_id: impl Into<AccountIdLike>,
at: impl Into<HashStringNumber>,
) -> Result<AccountInfo, Error> {
let account_id = conversions::account_id_like::to_account_id(account_id)?;
let at = conversions::hash_string_number::to_hash(self, at).await?;
let retry_on_error = self.should_retry_on_error();
let a = &account_id;
let f = || async move {
SystemStorage::Account::fetch(&self.client.rpc_client, a, Some(at))
.await
.map(|x| x.unwrap_or_default())
};
Ok(with_retry_on_error(f, retry_on_error).await?)
}
pub async fn block_state(&self, block_id: impl Into<HashStringNumber>) -> Result<BlockState, Error> {
let block_id = conversions::hash_string_number::to_hash_number(block_id)?;
let chain_info = self.chain_info().await?;
let n = match block_id {
HashNumber::Hash(h) => {
if h == chain_info.finalized_hash {
return Ok(BlockState::Finalized);
}
if h == chain_info.best_hash {
return Ok(BlockState::Included);
}
let Some(n) = self.block_height(h).await? else {
return Ok(BlockState::DoesNotExist);
};
let Some(block_hash) = self.block_hash(Some(n)).await? else {
return Ok(BlockState::DoesNotExist);
};
if block_hash != h {
return Ok(BlockState::Discarded);
}
n
},
HashNumber::Number(n) => n,
};
if n > chain_info.best_height {
return Ok(BlockState::DoesNotExist);
}
if n > chain_info.finalized_height {
return Ok(BlockState::Included);
}
Ok(BlockState::Finalized)
}
pub async fn block_height(&self, at: impl Into<HashString>) -> Result<Option<u32>, Error> {
let at = conversions::hash_string::to_hash(at)?;
let retry_on_error = self.should_retry_on_error();
let retry_on_none = self.retry_on_none.unwrap_or(false);
let f = || async move { rpc::system::get_block_number(&self.client.rpc_client, at).await };
Ok(with_retry_on_error_and_none(f, retry_on_error, retry_on_none).await?)
}
pub async fn block_info(&self, use_best_block: bool) -> Result<BlockInfo, RpcError> {
let retry = self.should_retry_on_error();
let f = || async move { rpc::system::latest_block_info(&self.client.rpc_client, use_best_block).await };
with_retry_on_error(f, retry).await
}
pub async fn block_info_from(&self, block_id: impl Into<HashStringNumber>) -> Result<BlockInfo, Error> {
let block_id = conversions::hash_string_number::to_hash_number(block_id)?;
let (height, hash) = match block_id {
HashNumber::Hash(hash) => {
let height = self.block_height(hash).await?;
let Some(height) = height else {
return Err(Error::User(UserError::Other(std::format!(
"No block height was found for hash: {}",
hash
))));
};
(height, hash)
},
HashNumber::Number(height) => {
let hash = self.block_hash(Some(height)).await?;
let Some(hash) = hash else {
return Err(Error::User(UserError::Other(std::format!(
"No block hash was found for height: {}",
height
))));
};
(height, hash)
},
};
Ok(BlockInfo::from((hash, height)))
}
pub async fn block_author(&self, block_id: impl Into<HashStringNumber>) -> Result<AccountId, Error> {
let hash = conversions::hash_string_number::to_hash(self, block_id).await?;
let header = self.block_header(Some(hash)).await?;
let Some(header) = header else {
return Err(Error::Other("No block header was found".into()));
};
for item in &header.digest.logs {
let (id, value) = match &item {
DigestItem::PreRuntime(id, value) => (id, value),
_ => continue,
};
if !id.eq(&consensus::babe::BABE_ENGINE_ID) {
continue;
}
let mut v = value.as_slice();
let pre_digest = consensus::babe::PreDigest::decode(&mut v).map_err(|e| Error::Other(e.to_string()))?;
let validators = avail::session::storage::Validators::fetch(&self.client.rpc_client, Some(hash)).await?;
let Some(validators) = validators else {
return Err(Error::Other(std::format!(
"No validators in storage was found for block hash: {:?}",
hash
)));
};
if let Some(account_id) = validators.get(pre_digest.authority_index() as usize) {
return Ok(account_id.clone());
}
}
Err(Error::Other(std::format!("Failed to find block author for block hash: {}", hash)))
}
pub async fn block_event_count(&self, block_id: impl Into<HashStringNumber>) -> Result<usize, Error> {
let hash = conversions::hash_string_number::to_hash(self, block_id).await?;
let retry_on_error = self.should_retry_on_error();
let f = || async move { avail::system::storage::EventCount::fetch(&self.client.rpc_client, Some(hash)).await };
let count = with_retry_on_error_and_none(f, retry_on_error, false).await?;
let Some(count) = count else {
return Err(Error::Other(std::format!("Failed to find block event count at block hash: {:?}", hash)));
};
Ok(count as usize)
}
pub async fn block_weight(&self, block_id: impl Into<HashStringNumber>) -> Result<PerDispatchClassWeight, Error> {
let hash = conversions::hash_string_number::to_hash(self, block_id).await?;
let retry_on_error = self.should_retry_on_error();
let f = || async move { avail::system::storage::BlockWeight::fetch(&self.client.rpc_client, Some(hash)).await };
let weight = with_retry_on_error_and_none(f, retry_on_error, false).await?;
let Some(weight) = weight else {
return Err(Error::Other(std::format!("Failed to find block weight at block hash: {:?}", hash)));
};
Ok(weight)
}
pub async fn chain_info(&self) -> Result<ChainInfo, RpcError> {
let retry = self.should_retry_on_error();
let f = || async move { rpc::system::latest_chain_info(&self.client.rpc_client).await };
with_retry_on_error(f, retry).await
}
pub async fn build_payload<'a>(
&self,
account_id: &AccountId,
call: &'a avail_rust_core::ExtrinsicCall,
options: Options,
) -> Result<avail_rust_core::ExtrinsicPayload<'a>, Error> {
let refined_options = options.build(&self.client, account_id, self.retry_on_error).await?;
let extra = avail_rust_core::ExtrinsicExtra::from(&refined_options);
let additional = avail_rust_core::ExtrinsicAdditional {
spec_version: self.client.online_client().spec_version(),
tx_version: self.client.online_client().transaction_version(),
genesis_hash: self.client.online_client().genesis_hash(),
fork_hash: refined_options.mortality.block_hash,
};
Ok(avail_rust_core::ExtrinsicPayload::new_borrowed(call, extra, additional))
}
pub async fn build_extrinsic_from_call<'a>(
&self,
signer: &Keypair,
call: &'a avail_rust_core::ExtrinsicCall,
options: Options,
) -> Result<avail_rust_core::GenericExtrinsic<'a>, Error> {
let account_id = signer.public_key().to_account_id();
let payload = self.build_payload(&account_id, call, options).await?;
let signature = payload.sign(signer);
Ok(avail_rust_core::GenericExtrinsic::new(account_id, signature, payload))
}
pub async fn submit(&self, ext: &avail_rust_core::GenericExtrinsic<'_>) -> Result<H256, RpcError> {
let retry = self.should_retry_on_error();
let encoded = ext.encode();
#[cfg(feature = "tracing")]
if let Some(signed) = &ext.signature {
if let avail_rust_core::MultiAddress::Id(account_id) = &signed.address {
tracing::info!(target: "tx", "Submitting Transaction. Address: {}, Nonce: {}, App Id: {}", account_id, signed.extra.nonce, signed.extra.app_id);
}
}
let enc_slice = encoded.as_slice();
let f = || async move { rpc::author::submit_extrinsic(&self.client.rpc_client, enc_slice).await };
let tx_hash = with_retry_on_error(f, retry).await?;
#[cfg(feature = "tracing")]
if let Some(signed) = &ext.signature {
if let avail_rust_core::MultiAddress::Id(account_id) = &signed.address {
tracing::info!(target: "tx", "Transaction Submitted. Address: {}, Nonce: {}, App Id: {}, Tx Hash: {:?},", account_id, signed.extra.nonce, signed.extra.app_id, tx_hash);
}
}
Ok(tx_hash)
}
pub async fn submit_raw(&self, ext: &[u8]) -> Result<H256, RpcError> {
let retry = self.should_retry_on_error();
let f = || async move { rpc::author::submit_extrinsic(&self.client.rpc_client, ext).await };
let tx_hash = with_retry_on_error(f, retry).await?;
Ok(tx_hash)
}
pub async fn sign_and_submit_payload(
&self,
signer: &Keypair,
tx_payload: avail_rust_core::ExtrinsicPayload<'_>,
) -> Result<H256, RpcError> {
use avail_rust_core::GenericExtrinsic;
let account_id = signer.public_key().to_account_id();
let signature = tx_payload.sign(signer);
let tx = GenericExtrinsic::new(account_id, signature, tx_payload);
let tx_hash = self.submit(&tx).await?;
Ok(tx_hash)
}
pub async fn sign_and_submit_call(
&self,
signer: &Keypair,
tx_call: &avail_rust_core::ExtrinsicCall,
options: Options,
) -> Result<SubmittedTransaction, Error> {
let account_id = signer.public_key().to_account_id();
let refined_options = options.build(&self.client, &account_id, self.retry_on_error).await?;
let extra = avail_rust_core::ExtrinsicExtra::from(&refined_options);
let tx_additional = avail_rust_core::ExtrinsicAdditional {
spec_version: self.client.online_client().spec_version(),
tx_version: self.client.online_client().transaction_version(),
genesis_hash: self.client.online_client().genesis_hash(),
fork_hash: refined_options.mortality.block_hash,
};
let tx_payload = avail_rust_core::ExtrinsicPayload::new_borrowed(tx_call, extra, tx_additional.clone());
let tx_hash = self.sign_and_submit_payload(signer, tx_payload).await?;
let value = SubmittedTransaction::new(self.client.clone(), tx_hash, account_id, refined_options, tx_additional);
Ok(value)
}
pub async fn state_call(&self, method: &str, data: &[u8], at: Option<H256>) -> Result<String, RpcError> {
let retry = self.should_retry_on_error();
let f = || async move { rpc::state::call(&self.client.rpc_client, method, data, at).await };
with_retry_on_error(f, retry).await
}
pub async fn state_get_metadata(&self, at: Option<H256>) -> Result<Vec<u8>, RpcError> {
let retry = self.should_retry_on_error();
let f = || async move { rpc::state::get_metadata(&self.client.rpc_client, at).await };
with_retry_on_error(f, retry).await
}
pub async fn state_get_storage(&self, key: &str, at: Option<H256>) -> Result<Option<Vec<u8>>, RpcError> {
let retry = self.should_retry_on_error();
let f = || async move { rpc::state::get_storage(&self.client.rpc_client, key, at).await };
with_retry_on_error(f, retry).await
}
pub async fn state_get_keys_paged(
&self,
prefix: Option<&str>,
count: u32,
start_key: Option<&str>,
at: Option<H256>,
) -> Result<Vec<String>, RpcError> {
let retry = self.should_retry_on_error();
let f =
|| async move { rpc::state::get_keys_paged(&self.client.rpc_client, prefix, count, start_key, at).await };
with_retry_on_error(f, retry).await
}
pub async fn rpc_raw_call<T: serde::de::DeserializeOwned>(
&self,
method: &str,
params: RpcParams,
) -> Result<T, RpcError> {
let retry = self.should_retry_on_error();
let p = ¶ms;
let f = || async move { rpc::raw_call(&self.client.rpc_client, method, p.clone()).await };
with_retry_on_error(f, retry).await
}
pub async fn runtime_api_raw_call<T: codec::Decode>(
&self,
method: &str,
data: &[u8],
at: Option<H256>,
) -> Result<T, RpcError> {
let retry = self.should_retry_on_error();
let f = || async move { runtime_api::raw_call(&self.client.rpc_client, method, data, at).await };
with_retry_on_error(f, retry).await
}
pub async fn grandpa_block_justification(&self, at: u32) -> Result<Option<GrandpaJustification>, RpcError> {
let retry = self.should_retry_on_error();
let f = || async move { rpc::grandpa::block_justification(&self.client.rpc_client, at).await };
let result = with_retry_on_error(f, retry).await?;
let Some(result) = result else {
return Ok(None);
};
let justification = const_hex::decode(result.trim_start_matches("0x"))
.map_err(|x| RpcError::MalformedResponse(x.to_string()))?;
let justification = GrandpaJustification::decode(&mut justification.as_slice());
let justification = justification.map_err(|e| RpcError::MalformedResponse(e.to_string()))?;
Ok(Some(justification))
}
pub async fn transaction_payment_query_info(
&self,
extrinsic: Vec<u8>,
at: Option<H256>,
) -> Result<RuntimeDispatchInfo, RpcError> {
let retry = self.should_retry_on_error();
let ext = &extrinsic;
let f = || async move {
runtime_api::api_transaction_payment_query_info(&self.client.rpc_client, ext.clone(), at).await
};
with_retry_on_error(f, retry).await
}
pub async fn transaction_payment_query_fee_details(
&self,
extrinsic: Vec<u8>,
at: Option<H256>,
) -> Result<FeeDetails, RpcError> {
let retry = self.should_retry_on_error();
let ext = &extrinsic;
let f = || async move {
runtime_api::api_transaction_payment_query_fee_details(&self.client.rpc_client, ext.clone(), at).await
};
with_retry_on_error(f, retry).await
}
pub async fn transaction_payment_query_call_info(
&self,
call: Vec<u8>,
at: Option<H256>,
) -> Result<RuntimeDispatchInfo, RpcError> {
let retry = self.should_retry_on_error();
let c = &call;
let f = || async move {
runtime_api::api_transaction_payment_query_call_info(&self.client.rpc_client, c.clone(), at).await
};
with_retry_on_error(f, retry).await
}
pub async fn transaction_payment_query_call_fee_details(
&self,
call: Vec<u8>,
at: Option<H256>,
) -> Result<FeeDetails, RpcError> {
let retry = self.should_retry_on_error();
let c = &call;
let f = || async move {
runtime_api::api_transaction_payment_query_call_fee_details(&self.client.rpc_client, c.clone(), at).await
};
with_retry_on_error(f, retry).await
}
pub async fn kate_block_length(&self, at: Option<H256>) -> Result<BlockLength, RpcError> {
let retry = self.should_retry_on_error();
let f = || async move { rpc::kate::block_length(&self.client.rpc_client, at).await };
with_retry_on_error(f, retry).await
}
pub async fn kate_query_data_proof(
&self,
transaction_index: u32,
at: Option<H256>,
) -> Result<ProofResponse, RpcError> {
let retry = self.should_retry_on_error();
let f = || async move { rpc::kate::query_data_proof(&self.client.rpc_client, transaction_index, at).await };
with_retry_on_error(f, retry).await
}
pub async fn kate_query_proof(&self, cells: Vec<Cell>, at: Option<H256>) -> Result<Vec<GDataProof>, RpcError> {
let retry = self.should_retry_on_error();
let cells_ref = &cells;
let f = || async move { rpc::kate::query_proof(&self.client.rpc_client, cells_ref.clone(), at).await };
with_retry_on_error(f, retry).await
}
pub async fn kate_query_rows(&self, rows: Vec<u32>, at: Option<H256>) -> Result<Vec<GRow>, RpcError> {
let retry = self.should_retry_on_error();
let rows_ref = &rows;
let f = || async move { rpc::kate::query_rows(&self.client.rpc_client, rows_ref.clone(), at).await };
with_retry_on_error(f, retry).await
}
pub async fn kate_query_multi_proof(
&self,
cells: Vec<Cell>,
at: Option<H256>,
) -> Result<Vec<(GMultiProof, GCellBlock)>, RpcError> {
let retry = self.should_retry_on_error();
let cells_ref = &cells;
let f = || async move { rpc::kate::query_multi_proof(&self.client.rpc_client, cells_ref.clone(), at).await };
with_retry_on_error(f, retry).await
}
#[cfg(feature = "next")]
pub async fn blob_submit_blob(&self, metadata_signed_transaction: &[u8], blob: &[u8]) -> Result<(), Error> {
let retry = self.should_retry_on_error();
let f =
|| async move { rpc::blob::submit_blob(&self.client.rpc_client, metadata_signed_transaction, blob).await };
Ok(with_retry_on_error(f, retry).await?)
}
#[cfg(feature = "next")]
pub async fn blob_get_blob(&self, blob_hash: H256, block_hash: Option<H256>) -> Result<Blob, Error> {
let retry = self.should_retry_on_error();
let f = || async move { rpc::blob::get_blob_v2(&self.client.rpc_client, blob_hash, block_hash).await };
Ok(with_retry_on_error(f, retry).await?)
}
#[cfg(feature = "next")]
pub async fn blob_get_blob_info(&self, blob_hash: H256) -> Result<BlobInfo, Error> {
let retry = self.should_retry_on_error();
let f = || async move { rpc::blob::get_blob_info(&self.client.rpc_client, blob_hash).await };
Ok(with_retry_on_error(f, retry).await?)
}
#[cfg(feature = "next")]
pub async fn blob_inclusion_proof(&self, blob_hash: H256, at: Option<H256>) -> Result<DataProof, Error> {
let retry = self.should_retry_on_error();
let f = || async move { rpc::blob::inclusion_proof(&self.client.rpc_client, blob_hash, at).await };
Ok(with_retry_on_error(f, retry).await?)
}
pub async fn system_fetch_extrinsics(
&self,
block_id: impl Into<HashStringNumber>,
opts: rpc::ExtrinsicOpts,
) -> Result<Vec<ExtrinsicInfo>, Error> {
let block_id = conversions::hash_string_number::to_hash_number(block_id)?;
let retry = self.should_retry_on_error();
let opts2 = &opts;
let f = || async move { rpc::system::fetch_extrinsics_v1(&self.client.rpc_client, block_id, opts2).await };
with_retry_on_error(f, retry).await.map_err(|e| e.into())
}
pub async fn system_fetch_events(
&self,
at: impl Into<HashStringNumber>,
opts: rpc::EventOpts,
) -> Result<Vec<BlockPhaseEvent>, Error> {
let at = conversions::hash_string_number::to_hash(self, at).await?;
let retry = self.should_retry_on_error();
let opts2 = &opts;
let f = || async move { rpc::system::fetch_events_v1(&self.client.rpc_client, at, opts2).await };
with_retry_on_error(f, retry).await.map_err(|e| e.into())
}
pub fn should_retry_on_error(&self) -> bool {
self.retry_on_error
.unwrap_or_else(|| self.client.is_global_retries_enabled())
}
}