mirror of
https://github.com/pezkuwichain/pezkuwi-subxt.git
synced 2026-06-13 08:11:04 +00:00
Make ExtrinsicParams more flexible, and introduce signed extensions (#1107)
* WIP new SignedExtensions * Integrate new signex extension stuff, update docs, remove Static wrapper * remove impl Into in tx_client to avoid type inference annoyances * clippy and fix example * Fix book links * clippy * book tweaks * fmt: remove spaces Co-authored-by: Niklas Adolfsson <niklasadolfsson1@gmail.com> * re-expose Era in utils, and tweak wasm-example * clippy; remove useless conversion --------- Co-authored-by: Niklas Adolfsson <niklasadolfsson1@gmail.com>
This commit is contained in:
@@ -2,25 +2,21 @@
|
||||
// This file is dual-licensed as Apache-2.0 or GPL-3.0.
|
||||
// see LICENSE for license details.
|
||||
|
||||
//! # Configuring the Subxt client
|
||||
//! # The Subxt client.
|
||||
//!
|
||||
//! Subxt ships with two clients, an [offline client](crate::client::OfflineClient) and an [online
|
||||
//! client](crate::client::OnlineClient). These are backed by the traits
|
||||
//! [`crate::client::OfflineClientT`] and [`crate::client::OnlineClientT`], so in theory it's
|
||||
//! possible for users to implement their own clients, although this isn't generally expected.
|
||||
//! The client forms the entry point to all of the Subxt APIs. Every client implements one or
|
||||
//! both of [`crate::client::OfflineClientT`] and [`crate::client::OnlineClientT`].
|
||||
//!
|
||||
//! Both clients are generic over a [`crate::config::Config`] trait, which is the way that we give
|
||||
//! the client certain information about how to interact with a node that isn't otherwise available
|
||||
//! or possible to include in the node metadata.
|
||||
//! Subxt ships with three clients which implement one or both of traits:
|
||||
//! - An [online client](crate::client::OnlineClient).
|
||||
//! - An [offline client](crate::client::OfflineClient).
|
||||
//! - A light client (which is currently still unstable).
|
||||
//!
|
||||
//! The [`crate::config::Config`] trait mimics the `frame_system::Config` trait and
|
||||
//! subxt ships out of the box with two default implementations:
|
||||
//! In theory it's possible for users to implement their own clients, although this isn't generally
|
||||
//! expected.
|
||||
//!
|
||||
//! - [`crate::config::PolkadotConfig`] for talking to Polkadot nodes, and
|
||||
//! - [`crate::config::SubstrateConfig`] for talking to generic nodes built with Substrate.
|
||||
//!
|
||||
//! The latter will generally work in many cases, but [may need special customization](super::config) if
|
||||
//! the node differs in any of the types the [`Config`](crate::config::Config) trait wants to know about.
|
||||
//! The provided clients are all generic over the [`crate::config::Config`] that they accept, which
|
||||
//! determines how they will interact with the chain.
|
||||
//!
|
||||
//! In the case of the [`crate::OnlineClient`], we have a few options to instantiate it:
|
||||
//!
|
||||
@@ -31,23 +27,20 @@
|
||||
//!
|
||||
//! The latter accepts anything that implements the low level [`crate::rpc::RpcClientT`] trait; this
|
||||
//! allows you to decide how Subxt will attempt to talk to a node if you'd prefer something other
|
||||
//! than the provided interfaces.
|
||||
//! than the provided interfaces. Under the hood, this is also how the light client is implemented.
|
||||
//!
|
||||
//! ## Examples
|
||||
//!
|
||||
//! Defining some custom config based off the default Substrate config:
|
||||
//! Most of the other examples will instantiate a client. Here are a couple of examples for less common
|
||||
//! cases.
|
||||
//!
|
||||
//! ```rust,ignore
|
||||
#![doc = include_str!("../../../examples/setup_client_custom_config.rs")]
|
||||
//! ```
|
||||
//!
|
||||
//! Writing a custom [`crate::rpc::RpcClientT`] implementation:
|
||||
//! ### Writing a custom [`crate::rpc::RpcClientT`] implementation:
|
||||
//!
|
||||
//! ```rust,ignore
|
||||
#![doc = include_str!("../../../examples/setup_client_custom_rpc.rs")]
|
||||
//! ```
|
||||
//!
|
||||
//! Creating an [`crate::OfflineClient`]:
|
||||
//! ### Creating an [`crate::OfflineClient`]:
|
||||
//!
|
||||
//! ```rust,ignore
|
||||
#![doc = include_str!("../../../examples/setup_client_offline.rs")]
|
||||
|
||||
@@ -12,27 +12,21 @@
|
||||
//! Some chains may use config that is not compatible with our [`PolkadotConfig`](crate::config::PolkadotConfig) or
|
||||
//! [`SubstrateConfig`](crate::config::SubstrateConfig).
|
||||
//!
|
||||
//! We now walk through creating a [`crate::config::Config`] for a parachain, using the
|
||||
//! We now walk through creating a custom [`crate::config::Config`] for a parachain, using the
|
||||
//! ["Statemint"](https://parachains.info/details/statemint) parachain, also known as "Asset Hub", as an example. It
|
||||
//! is currently (as of 2023-06-26) deployed on Polkadot and [Kusama (as "Statemine")](https://parachains.info/details/statemine).
|
||||
//!
|
||||
//! To construct a config, we need to investigate which types Statemint uses as `AccountId`, `Hasher`, etc.
|
||||
//! We need to take a look at the source code of Statemint and find out how it implements some substrate functionalities.
|
||||
//! Statemint (Polkadot Asset Hub) is part of the [Cumulus Github repository](https://github.com/paritytech/cumulus).
|
||||
//! The crate defining the parachains runtime can be found [here](https://github.com/paritytech/cumulus/tree/master/parachains/runtimes/assets/asset-hub-polkadot).
|
||||
//! To construct a valid [`crate::config::Config`] implementation, we need to find out which types to use for `AccountId`, `Hasher`, etc.
|
||||
//! For this, we need to take a look at the source code of Statemint, which is currently a part of the [Cumulus Github repository](https://github.com/paritytech/cumulus).
|
||||
//! The crate defining the asset hub runtime can be found [here](https://github.com/paritytech/cumulus/tree/master/parachains/runtimes/assets/asset-hub-polkadot).
|
||||
//!
|
||||
//! ## Creating the `Config` from scratch
|
||||
//!
|
||||
//! Creating the config from scratch is the most arduous approach but also the most flexible, so first we'll walk through
|
||||
//! how to do this, and then we'll show how to simplify the process where possible.
|
||||
//!
|
||||
//! ### AccountId, Hash, Hasher and Header
|
||||
//! ## `AccountId`, `Hash`, `Hasher` and `Header`
|
||||
//!
|
||||
//! For these config types, we need to find out where the parachain runtime implements the `frame_system::Config` trait.
|
||||
//! Look for a code fragment like `impl frame_system::Config for Runtime { ... }` In the source code.
|
||||
//! For Statemint it looks like [this](https://github.com/paritytech/cumulus/blob/e2b7ad2061824f490c08df27a922c64f50accd6b/parachains/runtimes/assets/asset-hub-polkadot/src/lib.rs#L179)
|
||||
//! at the time of writing. The `AccountId`, `Hash` and `Header` types of the [frame_system::pallet::Config](https://docs.rs/frame-system/latest/frame_system/pallet/trait.Config.html)
|
||||
//! correspond to the ones we want to use for implementing [crate::Config]. In the Case of Statemint (Asset Hub) they are:
|
||||
//! correspond to the ones we want to use in our Subxt [crate::Config]. In the Case of Statemint (Asset Hub) they are:
|
||||
//!
|
||||
//! - AccountId: `sp_core::crypto::AccountId32`
|
||||
//! - Hash: `sp_core::H256`
|
||||
@@ -49,17 +43,17 @@
|
||||
//! Having a look at how those types are implemented can give some clues as to how to implement other custom types that
|
||||
//! you may need to use as part of your config.
|
||||
//!
|
||||
//! ### Address, Signature
|
||||
//! ## `Address`, `Signature`
|
||||
//!
|
||||
//! A Substrate runtime is typically constructed by using the [frame_support::construct_runtime](https://docs.rs/frame-support/latest/frame_support/macro.construct_runtime.html) macro.
|
||||
//! In this macro, we need to specify the type of an `UncheckedExtrinsic`. Most of the time, the `UncheckedExtrinsic` will be of the type
|
||||
//! `sp_runtime::generic::UncheckedExtrinsic<Address, RuntimeCall, Signature, SignedExtra>`.
|
||||
//! The generic parameters `Address` and `Signature` specified when declaring the `UncheckedExtrinsic` type
|
||||
//! are the types for `Address` and `Signature` we should use when implementing the [crate::Config] trait. This information can
|
||||
//! are the types for `Address` and `Signature` we should use with our [crate::Config] implementation. This information can
|
||||
//! also be obtained from the metadata (see [`frame_metadata::v15::ExtrinsicMetadata`]). In case of Statemint (Polkadot Asset Hub)
|
||||
//! we see the following types being used in `UncheckedExtrinsic`:
|
||||
//!
|
||||
//! - Address: `sp_runtime::MultiAddress<Self::AccountId, ()>](sp_runtime::MultiAddress`
|
||||
//! - Address: `sp_runtime::MultiAddress<Self::AccountId, ()>`
|
||||
//! - Signature: `sp_runtime::MultiSignature`
|
||||
//!
|
||||
//! As above, Subxt has its own versions of these types that can be used instead to avoid pulling in Substrate dependencies.
|
||||
@@ -68,7 +62,7 @@
|
||||
//! - `sp_runtime::MultiAddress` can be swapped with [`crate::utils::MultiAddress`].
|
||||
//! - `sp_runtime::MultiSignature` can be swapped with [`crate::utils::MultiSignature`].
|
||||
//!
|
||||
//! ### ExtrinsicParams
|
||||
//! ## 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
|
||||
@@ -77,12 +71,27 @@
|
||||
//!
|
||||
//! 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
|
||||
//! accept arbitrary `OtherParams` from users, and are then expected to provide this "extra" and "additional" data when asked.
|
||||
//! accept arbitrary `OtherParams` from users, and are then expected to provide this "extra" and "additional" data when asked
|
||||
//! via the required [`crate::config::ExtrinsicParamsEncoder`] impl.
|
||||
//!
|
||||
//! In order to construct a valid implementation of the `ExtrinsicParams` trait, you must first find out which signed extensions
|
||||
//! are in use by a chain. This information can be obtained from the `SignedExtra` parameter of the `UncheckedExtrinsic` of your
|
||||
//! parachain, which will be a tuple of signed extensions. It can also be obtained from the metadata (see
|
||||
//! [`frame_metadata::v15::SignedExtensionMetadata`]).
|
||||
//! **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.
|
||||
//!
|
||||
//! If the chain uses novel signed 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.
|
||||
//! 2. Implement [`crate::config::DefaultExtrinsicParams`] from scratch.
|
||||
//!
|
||||
//! See below for examples of each.
|
||||
//!
|
||||
//! ### Finding out which signed 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.
|
||||
//! 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):
|
||||
@@ -116,45 +125,30 @@
|
||||
//! | [`frame_system::ChargeAssetTxPayment`](https://docs.rs/frame-system/latest/frame_system/struct.ChargeAssetTxPayment.html) | [pallet_asset_tx_payment::ChargeAssetTxPayment](https://docs.rs/pallet-asset-tx-payment/latest/pallet_asset_tx_payment/struct.ChargeAssetTxPayment.html) | () |
|
||||
//!
|
||||
//! 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. The goal of an
|
||||
//! [`crate::config::ExtrinsicParams`] impl then is to provide the appropriate (SCALE encoded) data for these via
|
||||
//! [`crate::config::ExtrinsicParams::encode_extra_to()`] and [`crate::config::ExtrinsicParams::encode_additional_to()`]
|
||||
//! respectively. If the [`crate::config::ExtrinsicParams`] impl needs additional data to be able to do this, it can use
|
||||
//! the [`crate::config::ExtrinsicParams::OtherParams`] associated type to obtain it from the user.
|
||||
//! `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
|
||||
//! [`crate::config::ExtrinsicParams`] from scratch.
|
||||
//!
|
||||
//! Given the above information, here is a fairly naive approach to implementing config for Statemint, including the
|
||||
//! [`crate::config::ExtrinsicParams`] trait, in a compatible way:
|
||||
//! As it happens, all of the signed extensions in the table are either already exported in [`crate::config::signed_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
|
||||
//!
|
||||
//! 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:
|
||||
//!
|
||||
//! ```rust,ignore
|
||||
#![doc = include_str ! ("../../../examples/setup_config_signed_extension.rs")]
|
||||
//! ```
|
||||
//!
|
||||
//! ### Implementing [`crate::config::ExtrinsicParams`] from scratch
|
||||
//!
|
||||
//! Alternately, you are free to implement [`crate::config::ExtrinsicParams`] entirely from scratch if you know exactly what "extra" and`
|
||||
//! "additional" data your node needs and would prefer to craft your own interface.
|
||||
//!
|
||||
//! Let's see what this looks like (this config won't work on any real node):
|
||||
//!
|
||||
//! ```rust,ignore
|
||||
#![doc = include_str ! ("../../../examples/setup_config_custom.rs")]
|
||||
//! ```
|
||||
//!
|
||||
//! ## Using [`PolkadotConfig`](crate::PolkadotConfig) and [`SubstrateConfig`](crate::SubstrateConfig) values to compose a Config
|
||||
//!
|
||||
//! Subxt already provides [`PolkadotConfig`](crate::config::PolkadotConfig) and [`SubstrateConfig`](crate::SubstrateConfig). These
|
||||
//! two configs are actually very similar, and only differ slightly in the `MultiAddress` type, and in terms of the type of `Tip` provided
|
||||
//! as part of the `ExtrinsicParams`. Many chains share a lot of the same types as these, and so often times you can just reuse parts
|
||||
//! of these implementations.
|
||||
//!
|
||||
//! From looking at the types needed for the Statemint config above, we can see that it is indeed very similar to these. It ultimately
|
||||
//! uses the same signed extensions that Substrate uses, and otherwise uses the same types as Polkadot. So, we can create a config which
|
||||
//! just reuses these existing types:
|
||||
//!
|
||||
//! ```rust,ignore
|
||||
//! use subxt::{Config, PolkadotConfig, SubstrateConfig};
|
||||
//!
|
||||
//! pub enum StatemintConfig {}
|
||||
//!
|
||||
//! impl Config for StatemintConfig {
|
||||
//! type Hash = <PolkadotConfig as Config>::Hash;
|
||||
//! type AccountId = <PolkadotConfig as Config>::AccountId;
|
||||
//! type Address = <PolkadotConfig as Config>::Address;
|
||||
//! type Signature = <PolkadotConfig as Config>::Signature;
|
||||
//! type Hasher = <PolkadotConfig as Config>::Hasher;
|
||||
//! type Header = <PolkadotConfig as Config>::Header;
|
||||
//! // this is the only difference to the PolkadotConfig:
|
||||
//! type ExtrinsicParams = <SubstrateConfig as Config>::ExtrinsicParams;
|
||||
//! }
|
||||
//! ```
|
||||
//!
|
||||
//! When the types are very similar, building a custom config like this is much simpler than implementing one from scratch.
|
||||
|
||||
Reference in New Issue
Block a user