Support constructing and submitting V5 transactions (#1931)

* TransactionExtensions basic support for V5 VerifySignature and renames

* WIP: subxt-core v5 transaction support

* Subxt to support V5 extrinsics

* WIP tests failing with wsm trap error

* Actually encode mortality to fix tx encode issue

* fmt

* rename to sign_with_account_and_signature

* Add explicit methods for v4 and v5 ext construction

* clippy

* fix wasm example and no mut self where not needed

* fix doc example

* another doc fix

* Add tests for tx encoding and fix v5 encode issue

* add copyright and todo

* refactor APIs to have clear v4/v5 split in core and slightly nicer split in subxt proper

* rename Partial/SubmittableExtrinsic to *Transaction

* Remove SignerT::address since it's not needed

* doc fixes

* fmt

* doc fixes

* Fix comment number

* Clarify panic behaviour of inject_signature

* fmt
This commit is contained in:
James Wilson
2025-03-11 11:14:27 +00:00
committed by GitHub
parent dcb9c27fcc
commit b6b9ac65c7
50 changed files with 1368 additions and 781 deletions
+3 -3
View File
@@ -17,7 +17,7 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {
// Decode each signed extrinsic in the block dynamically
let extrinsics = block.extrinsics().await?;
for ext in extrinsics.iter() {
let Some(signed_extensions) = ext.signed_extensions() else {
let Some(transaction_extensions) = ext.transaction_extensions() else {
continue; // we do not look at inherents in this example
};
@@ -25,8 +25,8 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {
let fields = ext.field_values()?;
println!(" {}/{}", meta.pallet.name(), meta.variant.name);
println!(" Signed Extensions:");
for signed_ext in signed_extensions.iter() {
println!(" Transaction Extensions:");
for signed_ext in transaction_extensions.iter() {
// We only want to take a look at these 3 signed extensions, because the others all just have unit fields.
if ["CheckMortality", "CheckNonce", "ChargeTransactionPayment"]
.contains(&signed_ext.name())
+1 -1
View File
@@ -30,7 +30,7 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {
for transfer in extrinsics.find::<TransferKeepAlive>() {
let transfer = transfer?;
let Some(extensions) = transfer.details.signed_extensions() else {
let Some(extensions) = transfer.details.transaction_extensions() else {
panic!("TransferKeepAlive should be signed")
};
+5 -5
View File
@@ -48,11 +48,11 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {
println!(" {}", event_values);
}
println!(" Signed Extensions:");
if let Some(signed_extensions) = ext.signed_extensions() {
for signed_extension in signed_extensions.iter() {
let name = signed_extension.name();
let value = signed_extension.value()?.to_string();
println!(" Transaction Extensions:");
if let Some(transaction_extensions) = ext.transaction_extensions() {
for transaction_extension in transaction_extensions.iter() {
let name = transaction_extension.name();
let value = transaction_extension.value()?.to_string();
println!(" {name}: {value}");
}
}
+3 -6
View File
@@ -38,12 +38,8 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {
let current_nonce = rpc
.system_account_next_index(&alice.public_key().into())
.await?;
let current_header = rpc.chain_get_header(None).await?.unwrap();
let ext_params = Params::new()
.mortal(&current_header, 8)
.nonce(current_nonce)
.build();
let ext_params = Params::new().mortal(8).nonce(current_nonce).build();
let balance_transfer = polkadot::tx()
.balances()
@@ -51,7 +47,8 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {
let ext_hash = api
.tx()
.create_signed_offline(&balance_transfer, &alice, ext_params)?
.create_partial_offline(&balance_transfer, ext_params)?
.sign(&alice)
.submit()
.await?;
+5 -6
View File
@@ -2,7 +2,8 @@
use codec::Encode;
use subxt::client::ClientState;
use subxt::config::{
Config, ExtrinsicParams, ExtrinsicParamsEncoder, ExtrinsicParamsError, RefineParams,
transaction_extensions::Params, Config, ExtrinsicParams, ExtrinsicParamsEncoder,
ExtrinsicParamsError,
};
use subxt_signer::sr25519::dev;
@@ -53,7 +54,7 @@ impl CustomExtrinsicParamsBuilder {
}
}
impl<T: Config> RefineParams<T> for CustomExtrinsicParamsBuilder {}
impl<T: Config> Params<T> for CustomExtrinsicParamsBuilder {}
// Describe how to fetch and then encode the params:
impl<T: Config> ExtrinsicParams<T> for CustomExtrinsicParams<T> {
@@ -69,14 +70,12 @@ impl<T: Config> ExtrinsicParams<T> for CustomExtrinsicParams<T> {
}
}
impl<T: Config> RefineParams<T> for CustomExtrinsicParams<T> {}
// Encode the relevant params when asked:
impl<T: Config> ExtrinsicParamsEncoder for CustomExtrinsicParams<T> {
fn encode_extra_to(&self, v: &mut Vec<u8>) {
fn encode_value_to(&self, v: &mut Vec<u8>) {
(self.tip, self.foo).encode_to(v);
}
fn encode_additional_to(&self, v: &mut Vec<u8>) {
fn encode_implicit_to(&self, v: &mut Vec<u8>) {
self.genesis_hash.encode_to(v)
}
}
@@ -3,7 +3,7 @@ use codec::Encode;
use scale_encode::EncodeAsType;
use scale_info::PortableRegistry;
use subxt::client::ClientState;
use subxt::config::signed_extensions;
use subxt::config::transaction_extensions;
use subxt::config::{
Config, DefaultExtrinsicParamsBuilder, ExtrinsicParams, ExtrinsicParamsEncoder,
ExtrinsicParamsError,
@@ -25,53 +25,54 @@ impl Config for CustomConfig {
type Signature = subxt::utils::MultiSignature;
type Hasher = subxt::config::substrate::BlakeTwo256;
type Header = subxt::config::substrate::SubstrateHeader<u32, Self::Hasher>;
type ExtrinsicParams = signed_extensions::AnyOf<
type ExtrinsicParams = transaction_extensions::AnyOf<
Self,
(
// Load in the existing signed extensions we're interested in
// (if the extension isn't actually needed it'll just be ignored):
signed_extensions::CheckSpecVersion,
signed_extensions::CheckTxVersion,
signed_extensions::CheckNonce,
signed_extensions::CheckGenesis<Self>,
signed_extensions::CheckMortality<Self>,
signed_extensions::ChargeAssetTxPayment<Self>,
signed_extensions::ChargeTransactionPayment,
signed_extensions::CheckMetadataHash,
transaction_extensions::VerifySignature<Self>,
transaction_extensions::CheckSpecVersion,
transaction_extensions::CheckTxVersion,
transaction_extensions::CheckNonce,
transaction_extensions::CheckGenesis<Self>,
transaction_extensions::CheckMortality<Self>,
transaction_extensions::ChargeAssetTxPayment<Self>,
transaction_extensions::ChargeTransactionPayment,
transaction_extensions::CheckMetadataHash,
// And add a new one of our own:
CustomSignedExtension,
CustomTransactionExtension,
),
>;
type AssetId = u32;
}
// Our custom signed extension doesn't do much:
pub struct CustomSignedExtension;
pub struct CustomTransactionExtension;
// Give the extension a name; this allows `AnyOf` to look it
// up in the chain metadata in order to know when and if to use it.
impl<T: Config> signed_extensions::SignedExtension<T> for CustomSignedExtension {
impl<T: Config> transaction_extensions::TransactionExtension<T> for CustomTransactionExtension {
type Decoded = ();
fn matches(identifier: &str, _type_id: u32, _types: &PortableRegistry) -> bool {
identifier == "CustomSignedExtension"
identifier == "CustomTransactionExtension"
}
}
// Gather together any params we need for our signed extension, here none.
impl<T: Config> ExtrinsicParams<T> for CustomSignedExtension {
impl<T: Config> ExtrinsicParams<T> for CustomTransactionExtension {
type Params = ();
fn new(_client: &ClientState<T>, _params: Self::Params) -> Result<Self, ExtrinsicParamsError> {
Ok(CustomSignedExtension)
Ok(CustomTransactionExtension)
}
}
// Encode whatever the extension needs to provide when asked:
impl ExtrinsicParamsEncoder for CustomSignedExtension {
fn encode_extra_to(&self, v: &mut Vec<u8>) {
impl ExtrinsicParamsEncoder for CustomTransactionExtension {
fn encode_value_to(&self, v: &mut Vec<u8>) {
"Hello".encode_to(v);
}
fn encode_additional_to(&self, v: &mut Vec<u8>) {
fn encode_implicit_to(&self, v: &mut Vec<u8>) {
true.encode_to(v)
}
}
@@ -84,8 +85,8 @@ impl ExtrinsicParamsEncoder for CustomSignedExtension {
pub fn custom(
params: DefaultExtrinsicParamsBuilder<CustomConfig>,
) -> <<CustomConfig as Config>::ExtrinsicParams as ExtrinsicParams<CustomConfig>>::Params {
let (a, b, c, d, e, f, g, h) = params.build();
(a, b, c, d, e, f, g, h, ())
let (a, b, c, d, e, f, g, h, i) = params.build();
(a, b, c, d, e, f, g, h, i, ())
}
#[tokio::main]
@@ -69,10 +69,6 @@ mod pair_signer {
self.account_id.clone()
}
fn address(&self) -> <PolkadotConfig as Config>::Address {
self.account_id.clone().into()
}
fn sign(&self, signer_payload: &[u8]) -> <PolkadotConfig as Config>::Signature {
let signature = self.signer.sign(signer_payload);
MultiSignature::Sr25519(signature.0)
+5 -5
View File
@@ -10,7 +10,7 @@ pub mod polkadot {}
#[tokio::main]
async fn main() -> Result<(), BoxedError> {
// Spawned tasks require things held across await points to impl Send,
// so we use one to demonstrate that this is possible with `PartialExtrinsic`
// so we use one to demonstrate that this is possible with `PartialTransaction`
tokio::spawn(signing_example()).await??;
Ok(())
}
@@ -25,9 +25,9 @@ async fn signing_example() -> Result<(), BoxedError> {
let alice = dev::alice();
// Create partial tx, ready to be signed.
let partial_tx = api
let mut partial_tx = api
.tx()
.create_partial_signed(
.create_partial(
&balance_transfer_tx,
&alice.public_key().to_account_id(),
Default::default(),
@@ -35,13 +35,13 @@ async fn signing_example() -> Result<(), BoxedError> {
.await?;
// Simulate taking some time to get a signature back, in part to
// show that the `PartialExtrinsic` can be held across await points.
// show that the `PartialTransaction` can be held across await points.
tokio::time::sleep(tokio::time::Duration::from_millis(100)).await;
let signature = alice.sign(&partial_tx.signer_payload());
// Sign the transaction.
let tx = partial_tx
.sign_with_address_and_signature(&alice.public_key().to_address(), &signature.into());
.sign_with_account_and_signature(&alice.public_key().to_account_id(), &signature.into());
// Submit it.
tx.submit_and_watch()
+1 -6
View File
@@ -15,14 +15,9 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {
let dest = dev::bob().public_key().into();
let tx = polkadot::tx().balances().transfer_allow_death(dest, 10_000);
let latest_block = api.blocks().at_latest().await?;
// Configure the transaction parameters; we give a small tip and set the
// transaction to live for 32 blocks from the `latest_block` above.
let tx_params = Params::new()
.tip(1_000)
.mortal(latest_block.header(), 32)
.build();
let tx_params = Params::new().tip(1_000).mortal(32).build();
// submit the transaction:
let from = dev::alice();
+8 -7
View File
@@ -16,7 +16,8 @@ use subxt_core::blocks::{ExtrinsicDetails as CoreExtrinsicDetails, Extrinsics as
// Re-export anything that's directly returned/used in the APIs below.
pub use subxt_core::blocks::{
ExtrinsicMetadataDetails, ExtrinsicSignedExtension, ExtrinsicSignedExtensions, StaticExtrinsic,
ExtrinsicMetadataDetails, ExtrinsicTransactionExtension, ExtrinsicTransactionExtensions,
StaticExtrinsic,
};
/// The body of a block.
@@ -191,14 +192,14 @@ where
self.inner.signature_bytes()
}
/// See [`subxt_core::blocks::ExtrinsicDetails::signed_extensions_bytes()`].
pub fn signed_extensions_bytes(&self) -> Option<&[u8]> {
self.inner.signed_extensions_bytes()
/// See [`subxt_core::blocks::ExtrinsicDetails::transaction_extensions_bytes()`].
pub fn transaction_extensions_bytes(&self) -> Option<&[u8]> {
self.inner.transaction_extensions_bytes()
}
/// See [`subxt_core::blocks::ExtrinsicDetails::signed_extensions()`].
pub fn signed_extensions(&self) -> Option<ExtrinsicSignedExtensions<'_, T>> {
self.inner.signed_extensions()
/// See [`subxt_core::blocks::ExtrinsicDetails::transaction_extensions()`].
pub fn transaction_extensions(&self) -> Option<ExtrinsicTransactionExtensions<'_, T>> {
self.inner.transaction_extensions()
}
/// See [`subxt_core::blocks::ExtrinsicDetails::pallet_index()`].
+2 -2
View File
@@ -14,8 +14,8 @@ pub use crate::backend::BlockRef;
pub use block_types::Block;
pub use blocks_client::BlocksClient;
pub use extrinsic_types::{
ExtrinsicDetails, ExtrinsicEvents, ExtrinsicSignedExtension, ExtrinsicSignedExtensions,
Extrinsics, FoundExtrinsic, StaticExtrinsic,
ExtrinsicDetails, ExtrinsicEvents, ExtrinsicTransactionExtension,
ExtrinsicTransactionExtensions, Extrinsics, FoundExtrinsic, StaticExtrinsic,
};
// We get account nonce info in tx_client, too, so re-use the logic:
+19 -19
View File
@@ -64,10 +64,10 @@
//!
//! ## ExtrinsicParams
//!
//! Chains each have a set of "signed extensions" configured. Signed extensions provide a means to extend how transactions
//! work. Each signed extension can potentially encode some "extra" data which is sent along with a transaction, as well as some
//! Chains each have a set of "transaction extensions" (formally called "signed extensions") configured. Transaction extensions provide
//! a means to extend how transactions work. Each transaction extension can potentially encode some "extra" data which is sent along with a transaction, as well as some
//! "additional" data which is included in the transaction signer payload, but not transmitted along with the transaction. On
//! a node, signed extensions can then perform additional checks on the submitted transactions to ensure their validity.
//! a node, transaction extensions can then perform additional checks on the submitted transactions to ensure their validity.
//!
//! The `ExtrinsicParams` config type expects to be given an implementation of the [`crate::config::ExtrinsicParams`] trait.
//! Implementations of the [`crate::config::ExtrinsicParams`] trait are handed some parameters from Subxt itself, and can
@@ -75,26 +75,26 @@
//! via the required [`crate::config::ExtrinsicParamsEncoder`] impl.
//!
//! **In most cases, the default [crate::config::DefaultExtrinsicParams] type will work**: it understands the "standard"
//! signed extensions that are in use, and allows the user to provide things like a tip, and set the extrinsic mortality via
//! [`crate::config::DefaultExtrinsicParamsBuilder`]. It will use the chain metadata to decide which signed extensions to use
//! and in which order. It will return an error if the chain uses a signed extension which it doesn't know how to handle.
//! transaction extensions that are in use, and allows the user to provide things like a tip, and set the extrinsic mortality via
//! [`crate::config::DefaultExtrinsicParamsBuilder`]. It will use the chain metadata to decide which transaction extensions to use
//! and in which order. It will return an error if the chain uses a transaction extension which it doesn't know how to handle.
//!
//! If the chain uses novel signed extensions (or if you just wish to provide a different interface for users to configure
//! If the chain uses novel transaction extensions (or if you just wish to provide a different interface for users to configure
//! transactions), you can either:
//!
//! 1. Implement a new signed extension and add it to the list.
//! 1. Implement a new transaction extension and add it to the list.
//! 2. Implement [`crate::config::DefaultExtrinsicParams`] from scratch.
//!
//! See below for examples of each.
//!
//! ### Finding out which signed extensions a chain is using.
//! ### Finding out which transaction extensions a chain is using.
//!
//! In either case, you'll want to find out which signed extensions a chain is using. This information can be obtained from
//! the `SignedExtra` parameter of the `UncheckedExtrinsic` of your parachain, which will be a tuple of signed extensions.
//! In either case, you'll want to find out which transaction extensions a chain is using. This information can be obtained from
//! the `SignedExtra` parameter of the `UncheckedExtrinsic` of your parachain, which will be a tuple of transaction extensions.
//! It can also be obtained from the metadata (see [`frame_metadata::v15::SignedExtensionMetadata`]).
//!
//! For statemint, the signed extensions look like
//! [this](https://github.com/paritytech/cumulus/tree/master/parachains/runtimes/assets/asset-hub-polkadot/src/lib.rs#L779):
//! For statemint, the transaction extensions look like
//! [this](https://github.com/paritytech/cumulus/blob/d4bb2215bb28ee05159c4c7df1b3435177b5bf4e/parachains/runtimes/assets/asset-hub-polkadot/src/lib.rs#L786):
//!
//! ```rs
//! pub type SignedExtra = (
@@ -126,20 +126,20 @@
//!
//! All types in the `struct type` column make up the "extra" data that we're expected to provide. All types in the
//! `AdditionalSigned` column make up the "additional" data that we're expected to provide. This information will be useful
//! whether we want to implement [`crate::config::SignedExtension`] for a signed extension, or implement
//! whether we want to implement [`crate::config::TransactionExtension`] for a transaction extension, or implement
//! [`crate::config::ExtrinsicParams`] from scratch.
//!
//! As it happens, all of the signed extensions in the table are either already exported in [`crate::config::signed_extensions`],
//! As it happens, all of the transaction extensions in the table are either already exported in [`crate::config::transaction_extensions`],
//! or they hand back no "additional" or "extra" data. In both of these cases, the default `ExtrinsicParams` configuration will
//! work out of the box.
//!
//! ### Implementing and adding new signed extensions to the config
//! ### Implementing and adding new transaction extensions to the config
//!
//! If you do need to implement a novel signed extension, then you can implement [`crate::config::signed_extensions::SignedExtension`]
//! on a custom type and place it into a new set of signed extensions, like so:
//! If you do need to implement a novel transaction extension, then you can implement [`crate::config::transaction_extensions::TransactionExtension`]
//! on a custom type and place it into a new set of transaction extensions, like so:
//!
//! ```rust,ignore
#![doc = include_str ! ("../../../examples/setup_config_signed_extension.rs")]
#![doc = include_str ! ("../../../examples/setup_config_transaction_extension.rs")]
//! ```
//!
//! ### Implementing [`crate::config::ExtrinsicParams`] from scratch
+13 -12
View File
@@ -49,7 +49,7 @@
//!
//! This example shows how to subscribe to blocks and decode the extrinsics in each block into the root extrinsic type.
//! Once we get hold of the [ExtrinsicDetails](crate::blocks::ExtrinsicDetails), we can decode it statically or dynamically.
//! We can also access details about the extrinsic, including the associated events and signed extensions.
//! We can also access details about the extrinsic, including the associated events and transaction extensions.
//!
//! ```rust,ignore
#![doc = include_str!("../../../examples/blocks_subscribing.rs")]
@@ -64,7 +64,8 @@
//! get only [the first one](crate::blocks::Extrinsics::find_first), or [the last one](crate::blocks::Extrinsics::find_last).
//!
//! The following example monitors `TransferKeepAlive` extrinsics on the Polkadot network.
//! We statically decode them and access the [tip](crate::blocks::ExtrinsicSignedExtensions::tip()) and [account nonce](crate::blocks::ExtrinsicSignedExtensions::nonce()) signed extensions.
//! We statically decode them and access the [tip](crate::blocks::ExtrinsicTransactionExtensions::tip()) and [account nonce](crate::blocks::ExtrinsicTransactionExtensions::nonce())
//! transaction extensions.
//!
//! ```rust,ignore
#![doc = include_str!("../../../examples/block_decoding_static.rs")]
@@ -75,7 +76,7 @@
//! Sometimes you might use subxt with metadata that is not known at compile time. In this case, you do not have access to a statically generated
//! interface module that contains the relevant Rust types. You can [decode ExtrinsicDetails dynamically](crate::blocks::ExtrinsicDetails::field_values()),
//! which gives you access to it's fields as a [scale value composite](scale_value::Composite).
//! The following example looks for signed extrinsics on the Polkadot network and retrieves their pallet name, variant name, data fields and signed extensions dynamically.
//! The following example looks for signed extrinsics on the Polkadot network and retrieves their pallet name, variant name, data fields and transaction extensions dynamically.
//! Notice how we do not need to use code generation via the subxt macro. The only fixed component we provide is the [PolkadotConfig](crate::config::PolkadotConfig).
//! Other than that it works in a chain-agnostic way:
//!
@@ -83,16 +84,16 @@
#![doc = include_str!("../../../examples/block_decoding_dynamic.rs")]
//! ```
//!
//! ## Decoding signed extensions
//! ## Decoding transaction extensions
//!
//! Extrinsics can contain signed extensions. The signed extensions can be different across chains.
//! The [Config](crate::Config) implementation for your chain defines which signed extensions you expect.
//! Extrinsics can contain transaction extensions. The transaction extensions can be different across chains.
//! The [Config](crate::Config) implementation for your chain defines which transaction extensions you expect.
//! Once you get hold of the [ExtrinsicDetails](crate::blocks::ExtrinsicDetails) for an extrinsic you are interested in,
//! you can try to [get its signed extensions](crate::blocks::ExtrinsicDetails::signed_extensions()).
//! These are only available on signed extrinsics. You can try to [find a specific signed extension](crate::blocks::ExtrinsicSignedExtensions::find),
//! in the returned [signed extensions](crate::blocks::ExtrinsicSignedExtensions).
//! you can try to [get its transaction extensions](crate::blocks::ExtrinsicDetails::transaction_extensions()).
//! These are only available on V4 signed extrinsics or V5 general extrinsics. You can try to [find a specific transaction extension](crate::blocks::ExtrinsicTransactionExtensions::find),
//! in the returned [transaction extensions](crate::blocks::ExtrinsicTransactionExtensions).
//!
//! Subxt also provides utility functions to get the [tip](crate::blocks::ExtrinsicSignedExtensions::tip()) and the
//! [account nonce](crate::blocks::ExtrinsicSignedExtensions::tip()) associated with an extrinsic, given its signed extensions.
//! If you prefer to do things dynamically you can get the data of the signed extension as a [scale value](crate::blocks::ExtrinsicSignedExtension::value()).
//! Subxt also provides utility functions to get the [tip](crate::blocks::ExtrinsicTransactionExtensions::tip()) and the
//! [account nonce](crate::blocks::ExtrinsicTransactionExtensions::tip()) associated with an extrinsic, given its transaction extensions.
//! If you prefer to do things dynamically you can get the data of the transaction extension as a [scale value](crate::blocks::ExtrinsicTransactionExtension::value()).
//!
+6 -6
View File
@@ -113,8 +113,8 @@
//! ]);
//!
//! // Construct the tx but don't sign it. The account nonce here defaults to 0.
//! // You can use `create_partial_signed` to fetch the correct nonce.
//! let partial_tx = client.tx().create_partial_signed_offline(
//! // You can use `create_partial` to fetch the correct nonce.
//! let mut partial_tx = client.tx().create_partial_offline(
//! &payload,
//! Default::default()
//! )?;
@@ -126,16 +126,16 @@
//! // Ultimately we need to be given back a `signature` (or really, anything
//! // that can be SCALE encoded) and an `address`:
//! let signature;
//! let address;
//! let account_id;
//! # use subxt::tx::Signer;
//! # let signer = subxt_signer::sr25519::dev::alice();
//! # signature = signer.sign(&signer_payload).into();
//! # address = signer.public_key().to_address();
//! # account_id = signer.public_key().to_account_id();
//!
//! // Now we can build an tx, which one can call `submit` or `submit_and_watch`
//! // on to submit to a node and optionally watch the status.
//! let tx = partial_tx.sign_with_address_and_signature(
//! &address,
//! let tx = partial_tx.sign_with_account_and_signature(
//! &account_id,
//! &signature
//! );
//! # Ok(())
+3 -3
View File
@@ -21,7 +21,7 @@ pub use dispatch_error::{
pub use crate::metadata::Metadata;
pub use scale_decode::Error as DecodeError;
pub use scale_encode::Error as EncodeError;
pub use subxt_core::error::{ExtrinsicParamsError, MetadataError, StorageAddressError};
pub use subxt_core::error::{ExtrinsicError, MetadataError, StorageAddressError};
pub use subxt_metadata::TryFromError as MetadataTryFromError;
/// The underlying error enum, generic over the type held by the `Runtime`
@@ -62,7 +62,7 @@ pub enum Error {
Transaction(#[from] TransactionError),
/// Error constructing the appropriate extrinsic params.
#[error("Extrinsic params error: {0}")]
ExtrinsicParams(#[from] ExtrinsicParamsError),
Extrinsic(#[from] ExtrinsicError),
/// Block related error.
#[error("Block error: {0}")]
Block(#[from] BlockError),
@@ -90,7 +90,7 @@ impl From<CoreError> for Error {
CoreError::StorageAddress(e) => Error::StorageAddress(e),
CoreError::Decode(e) => Error::Decode(e),
CoreError::Encode(e) => Error::Encode(e),
CoreError::ExtrinsicParams(e) => Error::ExtrinsicParams(e),
CoreError::Extrinsic(e) => Error::Extrinsic(e),
CoreError::Block(e) => Error::Block(e.into()),
}
}
+3 -3
View File
@@ -57,10 +57,10 @@ pub mod utils;
/// Polkadot node.
pub mod config {
pub use subxt_core::config::{
polkadot, signed_extensions, substrate, BlockHash, Config, DefaultExtrinsicParams,
polkadot, substrate, transaction_extensions, BlockHash, Config, DefaultExtrinsicParams,
DefaultExtrinsicParamsBuilder, ExtrinsicParams, ExtrinsicParamsEncoder, Hasher, Header,
PolkadotConfig, PolkadotExtrinsicParams, RefineParams, RefineParamsData, SignedExtension,
SubstrateConfig, SubstrateExtrinsicParams,
PolkadotConfig, PolkadotExtrinsicParams, SubstrateConfig, SubstrateExtrinsicParams,
TransactionExtension,
};
pub use subxt_core::error::ExtrinsicParamsError;
}
+1 -1
View File
@@ -15,7 +15,7 @@ mod tx_progress;
pub use subxt_core::tx::payload::{dynamic, DefaultPayload, DynamicPayload, Payload};
pub use subxt_core::tx::signer::{self, Signer};
pub use tx_client::{
PartialExtrinsic, SubmittableExtrinsic, TransactionInvalid, TransactionUnknown, TxClient,
PartialTransaction, SubmittableTransaction, TransactionInvalid, TransactionUnknown, TxClient,
ValidationResult,
};
pub use tx_progress::{TxInBlock, TxProgress, TxStatus};
+276 -144
View File
@@ -5,13 +5,14 @@
use crate::{
backend::{BackendExt, BlockRef, TransactionStatus},
client::{OfflineClientT, OnlineClientT},
config::{Config, ExtrinsicParams, Header, RefineParams, RefineParamsData},
config::{Config, ExtrinsicParams, Header},
error::{BlockError, Error},
tx::{Payload, Signer as SignerT, TxProgress},
utils::PhantomDataSendSync,
};
use codec::{Compact, Decode, Encode};
use derive_where::derive_where;
use subxt_core::tx::TransactionVersion;
/// A client for working with transactions.
#[derive_where(Clone; Client)]
@@ -31,7 +32,7 @@ impl<T: Config, Client> TxClient<T, Client> {
}
impl<T: Config, C: OfflineClientT<T>> TxClient<T, C> {
/// Run the validation logic against some extrinsic you'd like to submit. Returns `Ok(())`
/// Run the validation logic against some transaction you'd like to submit. Returns `Ok(())`
/// 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).
@@ -50,59 +51,143 @@ impl<T: Config, C: OfflineClientT<T>> TxClient<T, C> {
subxt_core::tx::call_data(call, &self.client.metadata()).map_err(Into::into)
}
/// Creates an unsigned extrinsic without submitting it.
pub fn create_unsigned<Call>(&self, call: &Call) -> Result<SubmittableExtrinsic<T, C>, Error>
/// 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>
where
Call: Payload,
{
subxt_core::tx::create_unsigned(call, &self.client.metadata())
.map(|tx| SubmittableExtrinsic {
client: self.client.clone(),
inner: tx,
})
.map_err(Into::into)
let metadata = self.client.metadata();
let tx = match subxt_core::tx::suggested_version(&metadata)? {
TransactionVersion::V4 => subxt_core::tx::create_v4_unsigned(call, &metadata),
TransactionVersion::V5 => subxt_core::tx::create_v5_bare(call, &metadata),
}?;
Ok(SubmittableTransaction {
client: self.client.clone(),
inner: tx,
})
}
/// Create a partial extrinsic.
/// Creates a v4 unsigned (no signature or transaction extensions) transaction without submitting it.
///
/// 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_v4_unsigned<Call>(
&self,
call: &Call,
) -> Result<SubmittableTransaction<T, C>, Error>
where
Call: Payload,
{
let metadata = self.client.metadata();
let tx = subxt_core::tx::create_v4_unsigned(call, &metadata)?;
Ok(SubmittableTransaction {
client: self.client.clone(),
inner: tx,
})
}
/// Creates a v5 "bare" (no signature or transaction extensions) transaction without submitting it.
///
/// 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>
where
Call: Payload,
{
let metadata = self.client.metadata();
let tx = subxt_core::tx::create_v5_bare(call, &metadata)?;
Ok(SubmittableTransaction {
client: self.client.clone(),
inner: tx,
})
}
/// Create a partial transaction. Depending on the metadata, we might end up constructing either a v4 or
/// v5 transaction. See [`subxt_core::tx`] if you'd like to manually pick the version to construct
///
/// Note: if not provided, the default account nonce will be set to 0 and the default mortality will be _immortal_.
/// This is because this method runs offline, and so is unable to fetch the data needed for more appropriate values.
pub fn create_partial_signed_offline<Call>(
pub fn create_partial_offline<Call>(
&self,
call: &Call,
params: <T::ExtrinsicParams as ExtrinsicParams<T>>::Params,
) -> Result<PartialExtrinsic<T, C>, Error>
) -> Result<PartialTransaction<T, C>, Error>
where
Call: Payload,
{
subxt_core::tx::create_partial_signed(call, &self.client.client_state(), params)
.map(|tx| PartialExtrinsic {
client: self.client.clone(),
inner: tx,
})
.map_err(Into::into)
let metadata = self.client.metadata();
let tx = match subxt_core::tx::suggested_version(&metadata)? {
TransactionVersion::V4 => PartialTransactionInner::V4(
subxt_core::tx::create_v4_signed(call, &self.client.client_state(), params)?,
),
TransactionVersion::V5 => PartialTransactionInner::V5(
subxt_core::tx::create_v5_general(call, &self.client.client_state(), params)?,
),
};
Ok(PartialTransaction {
client: self.client.clone(),
inner: tx,
})
}
/// Creates a signed extrinsic without submitting it.
/// Create a v4 partial transaction, ready to sign.
///
/// Note: if not provided, the default account nonce will be set to 0 and the default mortality will be _immortal_.
/// This is because this method runs offline, and so is unable to fetch the data needed for more appropriate values.
pub fn create_signed_offline<Call, Signer>(
///
/// Prefer [`Self::create_partial_offline()`] if you don't know which version to create; this will pick the
/// most suitable one for the given chain.
pub fn create_v4_partial_offline<Call>(
&self,
call: &Call,
signer: &Signer,
params: <T::ExtrinsicParams as ExtrinsicParams<T>>::Params,
) -> Result<SubmittableExtrinsic<T, C>, Error>
) -> Result<PartialTransaction<T, C>, Error>
where
Call: Payload,
Signer: SignerT<T>,
{
subxt_core::tx::create_signed(call, &self.client.client_state(), signer, params)
.map(|tx| SubmittableExtrinsic {
client: self.client.clone(),
inner: tx,
})
.map_err(Into::into)
let tx = PartialTransactionInner::V4(subxt_core::tx::create_v4_signed(
call,
&self.client.client_state(),
params,
)?);
Ok(PartialTransaction {
client: self.client.clone(),
inner: tx,
})
}
/// Create a v5 partial transaction, ready to sign.
///
/// Note: if not provided, the default account nonce will be set to 0 and the default mortality will be _immortal_.
/// This is because this method runs offline, and so is unable to fetch the data needed for more appropriate values.
///
/// Prefer [`Self::create_partial_offline()`] if you don't know which version to create; this will pick the
/// most suitable one for the given chain.
pub fn create_v5_partial_offline<Call>(
&self,
call: &Call,
params: <T::ExtrinsicParams as ExtrinsicParams<T>>::Params,
) -> Result<PartialTransaction<T, C>, Error>
where
Call: Payload,
{
let tx = PartialTransactionInner::V5(subxt_core::tx::create_v5_general(
call,
&self.client.client_state(),
params,
)?);
Ok(PartialTransaction {
client: self.client.clone(),
inner: tx,
})
}
}
@@ -111,84 +196,85 @@ where
T: Config,
C: OnlineClientT<T>,
{
/// Fetch the latest block header and account nonce from the backend and use them to refine [`ExtrinsicParams::Params`].
async fn refine_params(
&self,
account_id: &T::AccountId,
params: &mut <T::ExtrinsicParams as ExtrinsicParams<T>>::Params,
) -> Result<(), Error> {
let block_ref = self.client.backend().latest_finalized_block_ref().await?;
let block_header = self
.client
.backend()
.block_header(block_ref.hash())
.await?
.ok_or_else(|| Error::Block(BlockError::not_found(block_ref.hash())))?;
let account_nonce =
crate::blocks::get_account_nonce(&self.client, account_id, block_ref.hash()).await?;
params.refine(&RefineParamsData::new(
account_nonce,
block_header.number().into(),
block_header.hash(),
));
Ok(())
}
/// 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
}
/// Creates a partial signed extrinsic, without submitting it.
pub async fn create_partial_signed<Call>(
/// Creates a partial transaction, without submitting it. This can then be signed and submitted.
pub async fn create_partial<Call>(
&self,
call: &Call,
account_id: &T::AccountId,
mut params: <T::ExtrinsicParams as ExtrinsicParams<T>>::Params,
) -> Result<PartialExtrinsic<T, C>, Error>
) -> Result<PartialTransaction<T, C>, Error>
where
Call: Payload,
{
// Refine the params by adding account nonce and latest block information:
self.refine_params(account_id, &mut params).await?;
// Create the partial extrinsic with the refined params:
self.create_partial_signed_offline(call, params)
inject_account_nonce_and_block(&self.client, account_id, &mut params).await?;
self.create_partial_offline(call, params)
}
/// Creates a signed extrinsic, without submitting it.
pub async fn create_signed<Call, Signer>(
/// Creates a partial V4 transaction, without submitting it. This can then be signed and submitted.
///
/// Prefer [`Self::create_partial()`] if you don't know which version to create; this will pick the
/// most suitable one for the given chain.
pub async fn create_v4_partial<Call>(
&self,
call: &Call,
account_id: &T::AccountId,
mut params: <T::ExtrinsicParams as ExtrinsicParams<T>>::Params,
) -> Result<PartialTransaction<T, C>, Error>
where
Call: Payload,
{
inject_account_nonce_and_block(&self.client, account_id, &mut params).await?;
self.create_v4_partial_offline(call, params)
}
/// Creates a partial V5 transaction, without submitting it. This can then be signed and submitted.
///
/// Prefer [`Self::create_partial()`] if you don't know which version to create; this will pick the
/// most suitable one for the given chain.
pub async fn create_v5_partial<Call>(
&self,
call: &Call,
account_id: &T::AccountId,
mut params: <T::ExtrinsicParams as ExtrinsicParams<T>>::Params,
) -> Result<PartialTransaction<T, C>, Error>
where
Call: Payload,
{
inject_account_nonce_and_block(&self.client, account_id, &mut params).await?;
self.create_v5_partial_offline(call, params)
}
/// Creates a signed transaction, without submitting it.
pub async fn create_signed<Call, Signer>(
&mut self,
call: &Call,
signer: &Signer,
params: <T::ExtrinsicParams as ExtrinsicParams<T>>::Params,
) -> Result<SubmittableExtrinsic<T, C>, Error>
) -> Result<SubmittableTransaction<T, C>, Error>
where
Call: Payload,
Signer: SignerT<T>,
{
// 1. Validate this call against the current node metadata if the call comes
// with a hash allowing us to do so.
self.validate(call)?;
// 2. Gather the "additional" and "extra" params along with the encoded call data,
// ready to be signed.
let partial_signed = self
.create_partial_signed(call, &signer.account_id(), params)
let mut partial = self
.create_partial(call, &signer.account_id(), params)
.await?;
// 3. Sign and construct an extrinsic from these details.
Ok(partial_signed.sign(signer))
Ok(partial.sign(signer))
}
/// Creates and signs an extrinsic and submits it to the chain. Passes default parameters
/// to construct the "signed extra" and "additional" payloads needed by the extrinsic.
/// Creates and signs an transaction and submits it to the chain. Passes default parameters
/// to construct the "signed extra" and "additional" payloads needed by the transaction.
///
/// 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 sign_and_submit_then_watch_default<Call, Signer>(
&self,
&mut self,
call: &Call,
signer: &Signer,
) -> Result<TxProgress<T, C>, Error>
@@ -201,12 +287,12 @@ where
.await
}
/// Creates and signs an extrinsic and submits it to the chain.
/// Creates and signs an transaction and submits it to the chain.
///
/// 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 sign_and_submit_then_watch<Call, Signer>(
&self,
&mut self,
call: &Call,
signer: &Signer,
params: <T::ExtrinsicParams as ExtrinsicParams<T>>::Params,
@@ -221,18 +307,18 @@ where
.await
}
/// Creates and signs an extrinsic and submits to the chain for block inclusion. Passes
/// Creates and signs an transaction and submits to the chain for block inclusion. Passes
/// default parameters to construct the "signed extra" and "additional" payloads needed
/// by the extrinsic.
/// by the transaction.
///
/// Returns `Ok` with the extrinsic hash if it is valid extrinsic.
/// Returns `Ok` with the transaction hash if it is valid transaction.
///
/// # Note
///
/// Success does not mean the extrinsic has been included in the block, just that it is valid
/// Success does not mean the transaction has been included in the block, just that it is valid
/// and has been included in the transaction pool.
pub async fn sign_and_submit_default<Call, Signer>(
&self,
&mut self,
call: &Call,
signer: &Signer,
) -> Result<T::Hash, Error>
@@ -244,16 +330,16 @@ where
self.sign_and_submit(call, signer, Default::default()).await
}
/// Creates and signs an extrinsic and submits to the chain for block inclusion.
/// Creates and signs an transaction and submits to the chain for block inclusion.
///
/// Returns `Ok` with the extrinsic hash if it is valid extrinsic.
/// Returns `Ok` with the transaction hash if it is valid transaction.
///
/// # Note
///
/// Success does not mean the extrinsic has been included in the block, just that it is valid
/// Success does not mean the transaction has been included in the block, just that it is valid
/// and has been included in the transaction pool.
pub async fn sign_and_submit<Call, Signer>(
&self,
&mut self,
call: &Call,
signer: &Signer,
params: <T::ExtrinsicParams as ExtrinsicParams<T>>::Params,
@@ -269,76 +355,99 @@ where
}
}
/// This payload contains the information needed to produce an extrinsic.
pub struct PartialExtrinsic<T: Config, C> {
/// This payload contains the information needed to produce an transaction.
pub struct PartialTransaction<T: Config, C> {
client: C,
inner: subxt_core::tx::PartialTransaction<T>,
inner: PartialTransactionInner<T>,
}
impl<T, C> PartialExtrinsic<T, C>
enum PartialTransactionInner<T: Config> {
V4(subxt_core::tx::PartialTransactionV4<T>),
V5(subxt_core::tx::PartialTransactionV5<T>),
}
impl<T, C> PartialTransaction<T, C>
where
T: Config,
C: OfflineClientT<T>,
{
/// Return the signer payload for this extrinsic. These are the bytes that must
/// be signed in order to produce a valid signature for the extrinsic.
/// Return the signer payload for this transaction. These are the bytes that must
/// be signed in order to produce a valid signature for the transaction.
pub fn signer_payload(&self) -> Vec<u8> {
self.inner.signer_payload()
}
/// Return the bytes representing the call data for this partially constructed
/// extrinsic.
pub fn call_data(&self) -> &[u8] {
self.inner.call_data()
}
/// Convert this [`PartialExtrinsic`] into a [`SubmittableExtrinsic`], ready to submit.
/// The provided `signer` is responsible for providing the "from" address for the transaction,
/// as well as providing a signature to attach to it.
pub fn sign<Signer>(&self, signer: &Signer) -> SubmittableExtrinsic<T, C>
where
Signer: SignerT<T>,
{
SubmittableExtrinsic {
client: self.client.clone(),
inner: self.inner.sign(signer),
match &self.inner {
PartialTransactionInner::V4(tx) => tx.signer_payload(),
PartialTransactionInner::V5(tx) => tx.signer_payload().to_vec(),
}
}
/// Convert this [`PartialExtrinsic`] into a [`SubmittableExtrinsic`], ready to submit.
/// Return the bytes representing the call data for this partially constructed
/// transaction.
pub fn call_data(&self) -> &[u8] {
match &self.inner {
PartialTransactionInner::V4(tx) => tx.call_data(),
PartialTransactionInner::V5(tx) => tx.call_data(),
}
}
/// Convert this [`PartialTransaction`] into a [`SubmittableTransaction`], ready to submit.
/// The provided `signer` is responsible for providing the "from" address for the transaction,
/// as well as providing a signature to attach to it.
pub fn sign<Signer>(&mut self, signer: &Signer) -> SubmittableTransaction<T, C>
where
Signer: SignerT<T>,
{
let tx = match &mut self.inner {
PartialTransactionInner::V4(tx) => tx.sign(signer),
PartialTransactionInner::V5(tx) => tx.sign(signer),
};
SubmittableTransaction {
client: self.client.clone(),
inner: tx,
}
}
/// Convert this [`PartialTransaction`] into a [`SubmittableTransaction`], ready to submit.
/// An address, and something representing a signature that can be SCALE encoded, are both
/// needed in order to construct it. If you have a `Signer` to hand, you can use
/// [`PartialExtrinsic::sign()`] instead.
pub fn sign_with_address_and_signature(
&self,
address: &T::Address,
/// [`PartialTransaction::sign()`] instead.
pub fn sign_with_account_and_signature(
&mut self,
account_id: &T::AccountId,
signature: &T::Signature,
) -> SubmittableExtrinsic<T, C> {
SubmittableExtrinsic {
) -> SubmittableTransaction<T, C> {
let tx = match &mut self.inner {
PartialTransactionInner::V4(tx) => {
tx.sign_with_account_and_signature(account_id.clone(), signature)
}
PartialTransactionInner::V5(tx) => {
tx.sign_with_account_and_signature(account_id, signature)
}
};
SubmittableTransaction {
client: self.client.clone(),
inner: self
.inner
.sign_with_address_and_signature(address, signature),
inner: tx,
}
}
}
/// This represents an extrinsic that has been signed and is ready to submit.
pub struct SubmittableExtrinsic<T, C> {
/// This represents an transaction that has been signed and is ready to submit.
pub struct SubmittableTransaction<T, C> {
client: C,
inner: subxt_core::tx::Transaction<T>,
}
impl<T, C> SubmittableExtrinsic<T, C>
impl<T, C> SubmittableTransaction<T, C>
where
T: Config,
C: OfflineClientT<T>,
{
/// Create a [`SubmittableExtrinsic`] from some already-signed and prepared
/// extrinsic bytes, and some client (anything implementing [`OfflineClientT`]
/// Create a [`SubmittableTransaction`] from some already-signed and prepared
/// transaction bytes, and some client (anything implementing [`OfflineClientT`]
/// or [`OnlineClientT`]).
///
/// Prefer to use [`TxClient`] to create and sign extrinsics. This is simply
/// Prefer to use [`TxClient`] to create and sign transactions. This is simply
/// exposed in case you want to skip this process and submit something you've
/// already created.
pub fn from_bytes(client: C, tx_bytes: Vec<u8>) -> Self {
@@ -348,34 +457,34 @@ where
}
}
/// Calculate and return the hash of the extrinsic, based on the configured hasher.
/// Calculate and return the hash of the transaction, based on the configured hasher.
pub fn hash(&self) -> T::Hash {
self.inner.hash()
}
/// Returns the SCALE encoded extrinsic bytes.
/// Returns the SCALE encoded transaction bytes.
pub fn encoded(&self) -> &[u8] {
self.inner.encoded()
}
/// Consumes [`SubmittableExtrinsic`] and returns the SCALE encoded
/// extrinsic bytes.
/// Consumes [`SubmittableTransaction`] and returns the SCALE encoded
/// transaction bytes.
pub fn into_encoded(self) -> Vec<u8> {
self.inner.into_encoded()
}
}
impl<T, C> SubmittableExtrinsic<T, C>
impl<T, C> SubmittableTransaction<T, C>
where
T: Config,
C: OnlineClientT<T>,
{
/// Submits the extrinsic to the chain.
/// Submits the transaction to the chain.
///
/// 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> {
// Get a hash of the extrinsic (we'll need this later).
// Get a hash of the transaction (we'll need this later).
let ext_hash = self.hash();
// Submit and watch for transaction progress.
@@ -388,7 +497,7 @@ where
Ok(TxProgress::new(sub, self.client.clone(), ext_hash))
}
/// Submits the extrinsic to the chain for block inclusion.
/// Submits the transaction to the chain for block inclusion.
///
/// 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
@@ -429,7 +538,7 @@ where
/// Validate a transaction by submitting it to the relevant Runtime API. A transaction that is
/// 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 extrinsic.
/// 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?;
self.validate_at(latest_block_ref).await
@@ -438,7 +547,7 @@ where
/// Validate a transaction by submitting it to the relevant Runtime API. A transaction that is
/// 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 extrinsic.
/// Returns `Ok` with a [`ValidationResult`], which is the result of attempting to dry run the transaction.
pub async fn validate_at(
&self,
at: impl Into<BlockRef<T::Hash>>,
@@ -464,7 +573,7 @@ where
ValidationResult::try_from_bytes(res)
}
/// This returns an estimate for what the extrinsic is expected to cost to execute, less any tips.
/// 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> {
let mut params = self.encoded().to_vec();
@@ -486,6 +595,29 @@ where
}
}
/// Fetch the latest block header and account nonce from the backend and use them to refine [`ExtrinsicParams::Params`].
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> {
use subxt_core::config::transaction_extensions::Params;
let block_ref = client.backend().latest_finalized_block_ref().await?;
let block_header = client
.backend()
.block_header(block_ref.hash())
.await?
.ok_or_else(|| Error::Block(BlockError::not_found(block_ref.hash())))?;
let account_nonce =
crate::blocks::get_account_nonce(client, account_id, block_ref.hash()).await?;
params.inject_account_nonce(account_nonce);
params.inject_block(block_header.number().into(), block_header.hash());
Ok(())
}
impl ValidationResult {
#[allow(clippy::get_first)]
fn try_from_bytes(bytes: Vec<u8>) -> Result<ValidationResult, crate::Error> {
@@ -512,7 +644,7 @@ impl ValidationResult {
}
}
/// The result of performing [`SubmittableExtrinsic::validate()`].
/// The result of performing [`SubmittableTransaction::validate()`].
#[derive(Clone, Debug, PartialEq)]
pub enum ValidationResult {
/// The transaction is valid
@@ -611,12 +743,12 @@ pub enum TransactionInvalid {
ExhaustsResources,
/// Any other custom invalid validity that is not covered by this enum.
Custom(u8),
/// An extrinsic with a Mandatory dispatch resulted in Error. This is indicative of either a
/// An transaction with a Mandatory dispatch resulted in Error. This is indicative of either a
/// malicious validator or a buggy `provide_inherent`. In any case, it can result in
/// dangerously overweight blocks and therefore if found, invalidates the block.
BadMandatory,
/// An extrinsic with a mandatory dispatch tried to be validated.
/// This is invalid; only inherent extrinsics are allowed to have mandatory dispatches.
/// An transaction with a mandatory dispatch tried to be validated.
/// This is invalid; only inherent transactions are allowed to have mandatory dispatches.
MandatoryValidation,
/// The sending address is disabled or known to be invalid.
BadSigner,