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
+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(())