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:
James Wilson
2023-08-08 09:37:06 +01:00
committed by GitHub
parent 9cfac4eec7
commit 9b86b55b56
20 changed files with 1106 additions and 624 deletions
+16 -23
View File
@@ -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")]
+52 -58
View File
@@ -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.