Merge v0.50.x to master (#2127)

* v0.50.0: Integrate frame-decode, redo storage APIs and break up Error. (#2100)

* WIP integrating new frame-decode and working out new storage APIS

* WIP: first pass adding new storage things to subxt-core

* Second pass over Address type and start impl in Subxt

* WIP new storage APIs

* WIP New storage APIs roughly completed, lots of errors still

* Remove PlainorMap enum; plain and map values now use same struct to simplify usage

* Begin 'fixing' errors

* WIP splitting errors and tidying payload/address traits

* Get subxt-core compiling

* Small fixes in subxt-core and remove metadata mod

* subxt-core: cargo check --all-targets passes

* Fix test

* WIP starting to update subxt from subxt-core changes

* WIP splitting up subxt errors into smaller variants

* WIP errors: add DispatchError errors

* Port new Storage APIs to subxt-core

* cargo check -p subxt passes

* Quick-fix errors in subxt-cli (explore subcommand)

* fmt

* Finish fixing codegen up and start fixing examples

* get Subxt examples compiling and bytes_at for constants

* Add some arcs to limit lifetimes in subxt/subxt-core storage APIs

* A little Arcing to allow more method chaining in Storage APIs, aligning with Subxt

* Update codegen test

* cargo check --all-targets passing

* cargo check --features 'unstable-light-client' passing

* clippy

* Remove unused dep in subxt

* use published frame-decode

* fix wasm-example

* Add new tx extension to fix daily tests

* Remove unused subxt_core::dynamic::DecodedValue type

* Update book to match changes

* Update docs to fix more broken bits

* Add missing docs

* fmt

* allow larger result errs for now

* Add missing alloc imports in subxt-core

* Fix doc tests and fix bug getting constant info

* Fix V14 -> Metadata transform for storage & constants

* Fix parachain example

* Fix FFI example

* BlockLength decodes t ostruct, not u128

* use fetch/iter shorthands rather than entry in most storage tests

* Fix some integration tests

* Fix Runtime codegen tests

* Expose the dynamic custom_value selecter and use in a UI test

* Update codegen metadata

* Tidy CLI storage query and support (str,str) as a storage address

* Add (str,str) as valid constant address too

* Show string tuple in constants example

* Via the magic of traits, avoid needing any clones of queries/addresses and accept references to them

* clippy

* [v0.50] update scale-info-legacy and frame-decode to latest (#2119)

* bump scale-info-legacy and frame-decode to latest

* Remove something we don't need in this PR

* Fully remove unused for now dep

* [v0.50] Convert historic metadata to subxt::Metadata (#2120)

* First pass converting historic metadatas to our subxt::Metadata type

* use published frame-decode

* fmt and rename legacy metadata macro

* Enable legacy feature where needed in subxt_metadata so it compiles on its own

* Use cargo hack more in CI and fix subxt-metadata features

* Add tests for metadata conversion (need to optimise; some too expensive right now

* Address performance and equality issues in metadata conversion testing

* fmt

* fmt all

* clippy

* Fix a doc link

* Test codegen and fixes to make it work

* Remove local frame-decode patch

* bump frame-decode to latest

* [v0.50.0] Allow visiting extrinsic fields in subxt_historic (#2124)

* Allow visiting extrinsic fields

* fmt

* Don't use local scale-decode dep

* Clippy and tidy

* Extend 'subxt codegen' CLI to work with legacy metadatas

* Simplify historic extrinsics example now that AccountId32s have paths/names

* clippy

* clippy

* clippy..

* Allow visiting storage values, too, and clean up extrinsic visiting a little by narrowing lifetime

* Try to fix flaky test

* Add custom value decode to extrinsics example

* Remove useless else branch ra thought I needed

* Simplify examples

* Prep to release v0.0.5 (#2126)
This commit is contained in:
James Wilson
2025-11-22 10:44:03 +00:00
committed by GitHub
parent 586b814ecd
commit 8203679cbd
158 changed files with 13736 additions and 16451 deletions
+104 -52
View File
@@ -6,13 +6,13 @@ use crate::{
backend::{BackendExt, BlockRef, TransactionStatus},
client::{OfflineClientT, OnlineClientT},
config::{Config, ExtrinsicParams, HashFor, Header},
error::{BlockError, Error},
error::{ExtrinsicError, TransactionStatusError},
tx::{Payload, Signer as SignerT, TxProgress},
utils::PhantomDataSendSync,
};
use codec::{Compact, Decode, Encode};
use derive_where::derive_where;
use futures::future::try_join;
use futures::future::{TryFutureExt, try_join};
use subxt_core::tx::TransactionVersion;
/// A client for working with transactions.
@@ -37,7 +37,7 @@ impl<T: Config, C: OfflineClientT<T>> TxClient<T, C> {
/// if the call is valid (or if it's not possible to check since the call has no validation hash).
/// Return an error if the call was not valid or something went wrong trying to validate it (ie
/// the pallet or call in question do not exist at all).
pub fn validate<Call>(&self, call: &Call) -> Result<(), Error>
pub fn validate<Call>(&self, call: &Call) -> Result<(), ExtrinsicError>
where
Call: Payload,
{
@@ -45,7 +45,7 @@ impl<T: Config, C: OfflineClientT<T>> TxClient<T, C> {
}
/// Return the SCALE encoded bytes representing the call data of the transaction.
pub fn call_data<Call>(&self, call: &Call) -> Result<Vec<u8>, Error>
pub fn call_data<Call>(&self, call: &Call) -> Result<Vec<u8>, ExtrinsicError>
where
Call: Payload,
{
@@ -55,7 +55,10 @@ impl<T: Config, C: OfflineClientT<T>> TxClient<T, C> {
/// Creates an unsigned transaction without submitting it. Depending on the metadata, we might end
/// up constructing either a v4 or v5 transaction. See [`Self::create_v4_unsigned`] or
/// [`Self::create_v5_bare`] if you'd like to explicitly create an unsigned transaction of a certain version.
pub fn create_unsigned<Call>(&self, call: &Call) -> Result<SubmittableTransaction<T, C>, Error>
pub fn create_unsigned<Call>(
&self,
call: &Call,
) -> Result<SubmittableTransaction<T, C>, ExtrinsicError>
where
Call: Payload,
{
@@ -78,7 +81,7 @@ impl<T: Config, C: OfflineClientT<T>> TxClient<T, C> {
pub fn create_v4_unsigned<Call>(
&self,
call: &Call,
) -> Result<SubmittableTransaction<T, C>, Error>
) -> Result<SubmittableTransaction<T, C>, ExtrinsicError>
where
Call: Payload,
{
@@ -95,7 +98,10 @@ impl<T: Config, C: OfflineClientT<T>> TxClient<T, C> {
///
/// Prefer [`Self::create_unsigned()`] if you don't know which version to create; this will pick the
/// most suitable one for the given chain.
pub fn create_v5_bare<Call>(&self, call: &Call) -> Result<SubmittableTransaction<T, C>, Error>
pub fn create_v5_bare<Call>(
&self,
call: &Call,
) -> Result<SubmittableTransaction<T, C>, ExtrinsicError>
where
Call: Payload,
{
@@ -117,7 +123,7 @@ impl<T: Config, C: OfflineClientT<T>> TxClient<T, C> {
&self,
call: &Call,
params: <T::ExtrinsicParams as ExtrinsicParams<T>>::Params,
) -> Result<PartialTransaction<T, C>, Error>
) -> Result<PartialTransaction<T, C>, ExtrinsicError>
where
Call: Payload,
{
@@ -148,7 +154,7 @@ impl<T: Config, C: OfflineClientT<T>> TxClient<T, C> {
&self,
call: &Call,
params: <T::ExtrinsicParams as ExtrinsicParams<T>>::Params,
) -> Result<PartialTransaction<T, C>, Error>
) -> Result<PartialTransaction<T, C>, ExtrinsicError>
where
Call: Payload,
{
@@ -175,7 +181,7 @@ impl<T: Config, C: OfflineClientT<T>> TxClient<T, C> {
&self,
call: &Call,
params: <T::ExtrinsicParams as ExtrinsicParams<T>>::Params,
) -> Result<PartialTransaction<T, C>, Error>
) -> Result<PartialTransaction<T, C>, ExtrinsicError>
where
Call: Payload,
{
@@ -198,9 +204,21 @@ where
C: OnlineClientT<T>,
{
/// Get the account nonce for a given account ID.
pub async fn account_nonce(&self, account_id: &T::AccountId) -> Result<u64, Error> {
let block_ref = self.client.backend().latest_finalized_block_ref().await?;
crate::blocks::get_account_nonce(&self.client, account_id, block_ref.hash()).await
pub async fn account_nonce(&self, account_id: &T::AccountId) -> Result<u64, ExtrinsicError> {
let block_ref = self
.client
.backend()
.latest_finalized_block_ref()
.await
.map_err(ExtrinsicError::CannotGetLatestFinalizedBlock)?;
crate::blocks::get_account_nonce(&self.client, account_id, block_ref.hash())
.await
.map_err(|e| ExtrinsicError::AccountNonceError {
block_hash: block_ref.hash().into(),
account_id: account_id.encode().into(),
reason: e,
})
}
/// Creates a partial transaction, without submitting it. This can then be signed and submitted.
@@ -209,7 +227,7 @@ where
call: &Call,
account_id: &T::AccountId,
mut params: <T::ExtrinsicParams as ExtrinsicParams<T>>::Params,
) -> Result<PartialTransaction<T, C>, Error>
) -> Result<PartialTransaction<T, C>, ExtrinsicError>
where
Call: Payload,
{
@@ -226,7 +244,7 @@ where
call: &Call,
account_id: &T::AccountId,
mut params: <T::ExtrinsicParams as ExtrinsicParams<T>>::Params,
) -> Result<PartialTransaction<T, C>, Error>
) -> Result<PartialTransaction<T, C>, ExtrinsicError>
where
Call: Payload,
{
@@ -243,7 +261,7 @@ where
call: &Call,
account_id: &T::AccountId,
mut params: <T::ExtrinsicParams as ExtrinsicParams<T>>::Params,
) -> Result<PartialTransaction<T, C>, Error>
) -> Result<PartialTransaction<T, C>, ExtrinsicError>
where
Call: Payload,
{
@@ -257,7 +275,7 @@ where
call: &Call,
signer: &Signer,
params: <T::ExtrinsicParams as ExtrinsicParams<T>>::Params,
) -> Result<SubmittableTransaction<T, C>, Error>
) -> Result<SubmittableTransaction<T, C>, ExtrinsicError>
where
Call: Payload,
Signer: SignerT<T>,
@@ -278,7 +296,7 @@ where
&mut self,
call: &Call,
signer: &Signer,
) -> Result<TxProgress<T, C>, Error>
) -> Result<TxProgress<T, C>, ExtrinsicError>
where
Call: Payload,
Signer: SignerT<T>,
@@ -297,7 +315,7 @@ where
call: &Call,
signer: &Signer,
params: <T::ExtrinsicParams as ExtrinsicParams<T>>::Params,
) -> Result<TxProgress<T, C>, Error>
) -> Result<TxProgress<T, C>, ExtrinsicError>
where
Call: Payload,
Signer: SignerT<T>,
@@ -322,7 +340,7 @@ where
&mut self,
call: &Call,
signer: &Signer,
) -> Result<HashFor<T>, Error>
) -> Result<HashFor<T>, ExtrinsicError>
where
Call: Payload,
Signer: SignerT<T>,
@@ -345,7 +363,7 @@ where
call: &Call,
signer: &Signer,
params: <T::ExtrinsicParams as ExtrinsicParams<T>>::Params,
) -> Result<HashFor<T>, Error>
) -> Result<HashFor<T>, ExtrinsicError>
where
Call: Payload,
Signer: SignerT<T>,
@@ -485,7 +503,7 @@ where
///
/// Returns a [`TxProgress`], which can be used to track the status of the transaction
/// and obtain details about it, once it has made it into a block.
pub async fn submit_and_watch(&self) -> Result<TxProgress<T, C>, Error> {
pub async fn submit_and_watch(&self) -> Result<TxProgress<T, C>, ExtrinsicError> {
// Get a hash of the transaction (we'll need this later).
let ext_hash = self.hash();
@@ -494,7 +512,8 @@ where
.client
.backend()
.submit_transaction(self.encoded())
.await?;
.await
.map_err(ExtrinsicError::ErrorSubmittingTransaction)?;
Ok(TxProgress::new(sub, self.client.clone(), ext_hash))
}
@@ -504,13 +523,14 @@ where
/// It's usually better to call `submit_and_watch` to get an idea of the progress of the
/// submission and whether it's eventually successful or not. This call does not guarantee
/// success, and is just sending the transaction to the chain.
pub async fn submit(&self) -> Result<HashFor<T>, Error> {
pub async fn submit(&self) -> Result<HashFor<T>, ExtrinsicError> {
let ext_hash = self.hash();
let mut sub = self
.client
.backend()
.submit_transaction(self.encoded())
.await?;
.await
.map_err(ExtrinsicError::ErrorSubmittingTransaction)?;
// If we get a bad status or error back straight away then error, else return the hash.
match sub.next().await {
@@ -520,20 +540,22 @@ where
| TransactionStatus::InBestBlock { .. }
| TransactionStatus::NoLongerInBestBlock
| TransactionStatus::InFinalizedBlock { .. } => Ok(ext_hash),
TransactionStatus::Error { message } => {
Err(Error::Other(format!("Transaction error: {message}")))
}
TransactionStatus::Error { message } => Err(
ExtrinsicError::TransactionStatusError(TransactionStatusError::Error(message)),
),
TransactionStatus::Invalid { message } => {
Err(Error::Other(format!("Transaction invalid: {message}")))
Err(ExtrinsicError::TransactionStatusError(
TransactionStatusError::Invalid(message),
))
}
TransactionStatus::Dropped { message } => {
Err(Error::Other(format!("Transaction dropped: {message}")))
Err(ExtrinsicError::TransactionStatusError(
TransactionStatusError::Dropped(message),
))
}
},
Some(Err(e)) => Err(e),
None => Err(Error::Other(
"Transaction broadcast was unsuccessful; stream terminated early".into(),
)),
Some(Err(e)) => Err(ExtrinsicError::TransactionStatusStreamError(e)),
None => Err(ExtrinsicError::UnexpectedEndOfTransactionStatusStream),
}
}
@@ -541,8 +563,13 @@ where
/// valid can be added to a block, but may still end up in an error state.
///
/// Returns `Ok` with a [`ValidationResult`], which is the result of attempting to dry run the transaction.
pub async fn validate(&self) -> Result<ValidationResult, Error> {
let latest_block_ref = self.client.backend().latest_finalized_block_ref().await?;
pub async fn validate(&self) -> Result<ValidationResult, ExtrinsicError> {
let latest_block_ref = self
.client
.backend()
.latest_finalized_block_ref()
.await
.map_err(ExtrinsicError::CannotGetLatestFinalizedBlock)?;
self.validate_at(latest_block_ref).await
}
@@ -553,7 +580,7 @@ where
pub async fn validate_at(
&self,
at: impl Into<BlockRef<HashFor<T>>>,
) -> Result<ValidationResult, Error> {
) -> Result<ValidationResult, ExtrinsicError> {
let block_hash = at.into().hash();
// Approach taken from https://github.com/paritytech/json-rpc-interface-spec/issues/55.
@@ -570,17 +597,23 @@ where
Some(&params),
block_hash,
)
.await?;
.await
.map_err(ExtrinsicError::CannotGetValidationInfo)?;
ValidationResult::try_from_bytes(res)
}
/// This returns an estimate for what the transaction is expected to cost to execute, less any tips.
/// The actual amount paid can vary from block to block based on node traffic and other factors.
pub async fn partial_fee_estimate(&self) -> Result<u128, Error> {
pub async fn partial_fee_estimate(&self) -> Result<u128, ExtrinsicError> {
let mut params = self.encoded().to_vec();
(self.encoded().len() as u32).encode_to(&mut params);
let latest_block_ref = self.client.backend().latest_finalized_block_ref().await?;
let latest_block_ref = self
.client
.backend()
.latest_finalized_block_ref()
.await
.map_err(ExtrinsicError::CannotGetLatestFinalizedBlock)?;
// destructuring RuntimeDispatchInfo, see type information <https://paritytech.github.io/substrate/master/pallet_transaction_payment_rpc_runtime_api/struct.RuntimeDispatchInfo.html>
// data layout: {weight_ref_time: Compact<u64>, weight_proof_size: Compact<u64>, class: u8, partial_fee: u128}
@@ -592,7 +625,9 @@ where
Some(&params),
latest_block_ref.hash(),
)
.await?;
.await
.map_err(ExtrinsicError::CannotGetFeeInfo)?;
Ok(partial_fee)
}
}
@@ -602,19 +637,33 @@ async fn inject_account_nonce_and_block<T: Config, Client: OnlineClientT<T>>(
client: &Client,
account_id: &T::AccountId,
params: &mut <T::ExtrinsicParams as ExtrinsicParams<T>>::Params,
) -> Result<(), Error> {
) -> Result<(), ExtrinsicError> {
use subxt_core::config::transaction_extensions::Params;
let block_ref = client.backend().latest_finalized_block_ref().await?;
let block_ref = client
.backend()
.latest_finalized_block_ref()
.await
.map_err(ExtrinsicError::CannotGetLatestFinalizedBlock)?;
let (block_header, account_nonce) = try_join(
client.backend().block_header(block_ref.hash()),
crate::blocks::get_account_nonce(client, account_id, block_ref.hash()),
client
.backend()
.block_header(block_ref.hash())
.map_err(ExtrinsicError::CannotGetLatestFinalizedBlock),
crate::blocks::get_account_nonce(client, account_id, block_ref.hash()).map_err(|e| {
ExtrinsicError::AccountNonceError {
block_hash: block_ref.hash().into(),
account_id: account_id.encode().into(),
reason: e,
}
}),
)
.await?;
let block_header =
block_header.ok_or_else(|| Error::Block(BlockError::not_found(block_ref.hash())))?;
let block_header = block_header.ok_or_else(|| ExtrinsicError::CannotFindBlockHeader {
block_hash: block_ref.hash().into(),
})?;
params.inject_account_nonce(account_nonce);
params.inject_block(block_header.number().into(), block_ref.hash());
@@ -624,26 +673,29 @@ async fn inject_account_nonce_and_block<T: Config, Client: OnlineClientT<T>>(
impl ValidationResult {
#[allow(clippy::get_first)]
fn try_from_bytes(bytes: Vec<u8>) -> Result<ValidationResult, crate::Error> {
fn try_from_bytes(bytes: Vec<u8>) -> Result<ValidationResult, ExtrinsicError> {
// TaggedTransactionQueue_validate_transaction returns this:
// https://github.com/paritytech/substrate/blob/0cdf7029017b70b7c83c21a4dc0aa1020e7914f6/primitives/runtime/src/transaction_validity.rs#L210
// We copy some of the inner types and put the three states (valid, invalid, unknown) into one enum,
// because from our perspective, the call was successful regardless.
if bytes.get(0) == Some(&0) {
// ok: valid. Decode but, for now we discard most of the information
let res = TransactionValid::decode(&mut &bytes[1..])?;
let res = TransactionValid::decode(&mut &bytes[1..])
.map_err(ExtrinsicError::CannotDecodeValidationResult)?;
Ok(ValidationResult::Valid(res))
} else if bytes.get(0) == Some(&1) && bytes.get(1) == Some(&0) {
// error: invalid
let res = TransactionInvalid::decode(&mut &bytes[2..])?;
let res = TransactionInvalid::decode(&mut &bytes[2..])
.map_err(ExtrinsicError::CannotDecodeValidationResult)?;
Ok(ValidationResult::Invalid(res))
} else if bytes.get(0) == Some(&1) && bytes.get(1) == Some(&1) {
// error: unknown
let res = TransactionUnknown::decode(&mut &bytes[2..])?;
let res = TransactionUnknown::decode(&mut &bytes[2..])
.map_err(ExtrinsicError::CannotDecodeValidationResult)?;
Ok(ValidationResult::Unknown(res))
} else {
// unable to decode the bytes; they aren't what we expect.
Err(crate::Error::Unknown(bytes))
Err(ExtrinsicError::UnexpectedValidationResultBytes(bytes))
}
}
}
+89 -49
View File
@@ -10,7 +10,10 @@ use crate::{
backend::{BlockRef, StreamOfResults, TransactionStatus as BackendTxStatus},
client::OnlineClientT,
config::{Config, HashFor},
error::{DispatchError, Error, RpcError, TransactionError},
error::{
DispatchError, TransactionEventsError, TransactionFinalizedSuccessError,
TransactionProgressError, TransactionStatusError,
},
events::EventsClient,
utils::strip_compact_prefix,
};
@@ -67,7 +70,7 @@ where
/// Return the next transaction status when it's emitted. This just delegates to the
/// [`futures::Stream`] implementation for [`TxProgress`], but allows you to
/// avoid importing that trait if you don't otherwise need it.
pub async fn next(&mut self) -> Option<Result<TxStatus<T, C>, Error>> {
pub async fn next(&mut self) -> Option<Result<TxStatus<T, C>, TransactionProgressError>> {
StreamExt::next(self).await
}
@@ -81,24 +84,26 @@ where
/// probability that the transaction will not make it into a block but there is no guarantee
/// that this is true. In those cases the stream is closed however, so you currently have no way to find
/// out if they finally made it into a block or not.
pub async fn wait_for_finalized(mut self) -> Result<TxInBlock<T, C>, Error> {
pub async fn wait_for_finalized(mut self) -> Result<TxInBlock<T, C>, TransactionProgressError> {
while let Some(status) = self.next().await {
match status? {
// Finalized! Return.
TxStatus::InFinalizedBlock(s) => return Ok(s),
// Error scenarios; return the error.
TxStatus::Error { message } => return Err(TransactionError::Error(message).into()),
TxStatus::Error { message } => {
return Err(TransactionStatusError::Error(message).into());
}
TxStatus::Invalid { message } => {
return Err(TransactionError::Invalid(message).into());
return Err(TransactionStatusError::Invalid(message).into());
}
TxStatus::Dropped { message } => {
return Err(TransactionError::Dropped(message).into());
return Err(TransactionStatusError::Dropped(message).into());
}
// Ignore and wait for next status event:
_ => continue,
}
}
Err(RpcError::SubscriptionDropped.into())
Err(TransactionProgressError::UnexpectedEndOfTransactionStatusStream)
}
/// Wait for the transaction to be finalized, and for the transaction events to indicate
@@ -114,14 +119,14 @@ where
/// out if they finally made it into a block or not.
pub async fn wait_for_finalized_success(
self,
) -> Result<crate::blocks::ExtrinsicEvents<T>, Error> {
) -> Result<crate::blocks::ExtrinsicEvents<T>, TransactionFinalizedSuccessError> {
let evs = self.wait_for_finalized().await?.wait_for_success().await?;
Ok(evs)
}
}
impl<T: Config, C: Clone> Stream for TxProgress<T, C> {
type Item = Result<TxStatus<T, C>, Error>;
type Item = Result<TxStatus<T, C>, TransactionProgressError>;
fn poll_next(
mut self: std::pin::Pin<&mut Self>,
@@ -132,37 +137,41 @@ impl<T: Config, C: Clone> Stream for TxProgress<T, C> {
None => return Poll::Ready(None),
};
sub.poll_next_unpin(cx).map_ok(|status| {
match status {
BackendTxStatus::Validated => TxStatus::Validated,
BackendTxStatus::Broadcasted => TxStatus::Broadcasted,
BackendTxStatus::NoLongerInBestBlock => TxStatus::NoLongerInBestBlock,
BackendTxStatus::InBestBlock { hash } => {
TxStatus::InBestBlock(TxInBlock::new(hash, self.ext_hash, self.client.clone()))
}
// These stream events mean that nothing further will be sent:
BackendTxStatus::InFinalizedBlock { hash } => {
self.sub = None;
TxStatus::InFinalizedBlock(TxInBlock::new(
sub.poll_next_unpin(cx)
.map_err(TransactionProgressError::CannotGetNextProgressUpdate)
.map_ok(|status| {
match status {
BackendTxStatus::Validated => TxStatus::Validated,
BackendTxStatus::Broadcasted => TxStatus::Broadcasted,
BackendTxStatus::NoLongerInBestBlock => TxStatus::NoLongerInBestBlock,
BackendTxStatus::InBestBlock { hash } => TxStatus::InBestBlock(TxInBlock::new(
hash,
self.ext_hash,
self.client.clone(),
))
)),
// These stream events mean that nothing further will be sent:
BackendTxStatus::InFinalizedBlock { hash } => {
self.sub = None;
TxStatus::InFinalizedBlock(TxInBlock::new(
hash,
self.ext_hash,
self.client.clone(),
))
}
BackendTxStatus::Error { message } => {
self.sub = None;
TxStatus::Error { message }
}
BackendTxStatus::Invalid { message } => {
self.sub = None;
TxStatus::Invalid { message }
}
BackendTxStatus::Dropped { message } => {
self.sub = None;
TxStatus::Dropped { message }
}
}
BackendTxStatus::Error { message } => {
self.sub = None;
TxStatus::Error { message }
}
BackendTxStatus::Invalid { message } => {
self.sub = None;
TxStatus::Invalid { message }
}
BackendTxStatus::Dropped { message } => {
self.sub = None;
TxStatus::Dropped { message }
}
}
})
})
}
}
@@ -258,15 +267,27 @@ impl<T: Config, C: OnlineClientT<T>> TxInBlock<T, C> {
///
/// **Note:** This has to download block details from the node and decode events
/// from them.
pub async fn wait_for_success(&self) -> Result<crate::blocks::ExtrinsicEvents<T>, Error> {
pub async fn wait_for_success(
&self,
) -> Result<crate::blocks::ExtrinsicEvents<T>, TransactionEventsError> {
let events = self.fetch_events().await?;
// Try to find any errors; return the first one we encounter.
for ev in events.iter() {
let ev = ev?;
for (ev_idx, ev) in events.iter().enumerate() {
let ev = ev.map_err(|e| TransactionEventsError::CannotDecodeEventInBlock {
event_index: ev_idx,
block_hash: self.block_hash().into(),
error: e,
})?;
if ev.pallet_name() == "System" && ev.variant_name() == "ExtrinsicFailed" {
let dispatch_error =
DispatchError::decode_from(ev.field_bytes(), self.client.metadata())?;
DispatchError::decode_from(ev.field_bytes(), self.client.metadata()).map_err(
|e| TransactionEventsError::CannotDecodeDispatchError {
error: e,
bytes: ev.field_bytes().to_vec(),
},
)?;
return Err(dispatch_error.into());
}
}
@@ -280,15 +301,23 @@ impl<T: Config, C: OnlineClientT<T>> TxInBlock<T, C> {
///
/// **Note:** This has to download block details from the node and decode events
/// from them.
pub async fn fetch_events(&self) -> Result<crate::blocks::ExtrinsicEvents<T>, Error> {
pub async fn fetch_events(
&self,
) -> Result<crate::blocks::ExtrinsicEvents<T>, TransactionEventsError> {
let hasher = self.client.hasher();
let block_body = self
.client
.backend()
.block_body(self.block_ref.hash())
.await?
.ok_or(Error::Transaction(TransactionError::BlockNotFound))?;
.await
.map_err(|e| TransactionEventsError::CannotFetchBlockBody {
block_hash: self.block_hash().into(),
error: e,
})?
.ok_or_else(|| TransactionEventsError::BlockNotFound {
block_hash: self.block_hash().into(),
})?;
let extrinsic_idx = block_body
.iter()
@@ -302,11 +331,21 @@ impl<T: Config, C: OnlineClientT<T>> TxInBlock<T, C> {
})
// If we successfully obtain the block hash we think contains our
// extrinsic, the extrinsic should be in there somewhere..
.ok_or(Error::Transaction(TransactionError::BlockNotFound))?;
.ok_or_else(|| TransactionEventsError::CannotFindTransactionInBlock {
block_hash: self.block_hash().into(),
transaction_hash: self.ext_hash.into(),
})?;
let events = EventsClient::new(self.client.clone())
.at(self.block_ref.clone())
.await?;
.await
.map_err(
|e| TransactionEventsError::CannotFetchEventsForTransaction {
block_hash: self.block_hash().into(),
transaction_hash: self.ext_hash.into(),
error: e,
},
)?;
Ok(crate::blocks::ExtrinsicEvents::new(
self.ext_hash,
@@ -318,10 +357,11 @@ impl<T: Config, C: OnlineClientT<T>> TxInBlock<T, C> {
#[cfg(test)]
mod test {
use super::*;
use subxt_core::client::RuntimeVersion;
use crate::{
Error, SubstrateConfig,
SubstrateConfig,
backend::{StreamOfResults, TransactionStatus},
client::{OfflineClientT, OnlineClientT},
config::{Config, HashFor},
@@ -375,7 +415,7 @@ mod test {
let finalized_result = tx_progress.wait_for_finalized().await;
assert!(matches!(
finalized_result,
Err(Error::Transaction(crate::error::TransactionError::Error(e))) if e == "err"
Err(TransactionProgressError::TransactionStatusError(TransactionStatusError::Error(e))) if e == "err"
));
}
@@ -390,7 +430,7 @@ mod test {
let finalized_result = tx_progress.wait_for_finalized().await;
assert!(matches!(
finalized_result,
Err(Error::Transaction(crate::error::TransactionError::Invalid(e))) if e == "err"
Err(TransactionProgressError::TransactionStatusError(TransactionStatusError::Invalid(e))) if e == "err"
));
}
@@ -405,7 +445,7 @@ mod test {
let finalized_result = tx_progress.wait_for_finalized().await;
assert!(matches!(
finalized_result,
Err(Error::Transaction(crate::error::TransactionError::Dropped(e))) if e == "err"
Err(TransactionProgressError::TransactionStatusError(TransactionStatusError::Dropped(e))) if e == "err"
));
}