mirror of
https://github.com/pezkuwichain/pezkuwi-subxt.git
synced 2026-06-13 09:21:05 +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.
|
||||
|
||||
@@ -0,0 +1,144 @@
|
||||
// Copyright 2019-2023 Parity Technologies (UK) Ltd.
|
||||
// This file is dual-licensed as Apache-2.0 or GPL-3.0.
|
||||
// see LICENSE for license details.
|
||||
|
||||
use super::{signed_extensions, ExtrinsicParams};
|
||||
use super::{Config, Header};
|
||||
|
||||
/// The default [`super::ExtrinsicParams`] implementation understands common signed extensions
|
||||
/// and how to apply them to a given chain.
|
||||
pub type DefaultExtrinsicParams<T> = signed_extensions::AnyOf<
|
||||
T,
|
||||
(
|
||||
signed_extensions::CheckSpecVersion,
|
||||
signed_extensions::CheckTxVersion,
|
||||
signed_extensions::CheckNonce,
|
||||
signed_extensions::CheckGenesis<T>,
|
||||
signed_extensions::CheckMortality<T>,
|
||||
signed_extensions::ChargeAssetTxPayment,
|
||||
signed_extensions::ChargeTransactionPayment,
|
||||
),
|
||||
>;
|
||||
|
||||
/// A builder that outputs the set of [`super::ExtrinsicParams::OtherParams`] required for
|
||||
/// [`DefaultExtrinsicParams`]. This may expose methods that aren't applicable to the current
|
||||
/// chain; such values will simply be ignored if so.
|
||||
pub struct DefaultExtrinsicParamsBuilder<T: Config> {
|
||||
/// `None` means the tx will be immortal.
|
||||
mortality: Option<Mortality<T::Hash>>,
|
||||
/// `None` means we'll use the native token.
|
||||
tip_of_asset_id: Option<u32>,
|
||||
tip: u128,
|
||||
tip_of: u128,
|
||||
}
|
||||
|
||||
struct Mortality<Hash> {
|
||||
/// Block hash that mortality starts from
|
||||
checkpoint_hash: Hash,
|
||||
/// Block number that mortality starts from (must
|
||||
// point to the same block as the hash above)
|
||||
checkpoint_number: u64,
|
||||
/// How many blocks the tx is mortal for
|
||||
period: u64,
|
||||
}
|
||||
|
||||
impl<T: Config> Default for DefaultExtrinsicParamsBuilder<T> {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
mortality: None,
|
||||
tip: 0,
|
||||
tip_of: 0,
|
||||
tip_of_asset_id: None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Config> DefaultExtrinsicParamsBuilder<T> {
|
||||
/// Configure new extrinsic params. We default to providing no tip
|
||||
/// and using an immortal transaction unless otherwise configured
|
||||
pub fn new() -> Self {
|
||||
Default::default()
|
||||
}
|
||||
|
||||
/// Make the transaction mortal, given a block header that it should be mortal from,
|
||||
/// and the number of blocks (roughly; it'll be rounded to a power of two) that it will
|
||||
/// be mortal for.
|
||||
pub fn mortal(mut self, from_block: &T::Header, for_n_blocks: u64) -> Self {
|
||||
self.mortality = Some(Mortality {
|
||||
checkpoint_hash: from_block.hash(),
|
||||
checkpoint_number: from_block.number().into(),
|
||||
period: for_n_blocks,
|
||||
});
|
||||
self
|
||||
}
|
||||
|
||||
/// Make the transaction mortal, given a block number and block hash (which must both point to
|
||||
/// the same block) that it should be mortal from, and the number of blocks (roughly; it'll be
|
||||
/// rounded to a power of two) that it will be mortal for.
|
||||
///
|
||||
/// Prefer to use [`DefaultExtrinsicParamsBuilder::mortal()`], which ensures that the block hash
|
||||
/// and number align.
|
||||
pub fn mortal_unchecked(
|
||||
mut self,
|
||||
from_block_number: u64,
|
||||
from_block_hash: T::Hash,
|
||||
for_n_blocks: u64,
|
||||
) -> Self {
|
||||
self.mortality = Some(Mortality {
|
||||
checkpoint_hash: from_block_hash,
|
||||
checkpoint_number: from_block_number,
|
||||
period: for_n_blocks,
|
||||
});
|
||||
self
|
||||
}
|
||||
|
||||
/// Provide a tip to the block author in the chain's native token.
|
||||
pub fn tip(mut self, tip: u128) -> Self {
|
||||
self.tip = tip;
|
||||
self.tip_of = tip;
|
||||
self.tip_of_asset_id = None;
|
||||
self
|
||||
}
|
||||
|
||||
/// Provide a tip to the block auther using the token denominated by the `asset_id` provided. This
|
||||
/// is not applicable on chains which don't use the `ChargeAssetTxPayment` signed extension; in this
|
||||
/// case, no tip will be given.
|
||||
pub fn tip_of(mut self, tip: u128, asset_id: u32) -> Self {
|
||||
self.tip = 0;
|
||||
self.tip_of = tip;
|
||||
self.tip_of_asset_id = Some(asset_id);
|
||||
self
|
||||
}
|
||||
|
||||
/// Build the extrinsic parameters.
|
||||
pub fn build(self) -> <DefaultExtrinsicParams<T> as ExtrinsicParams<T>>::OtherParams {
|
||||
let check_mortality_params = if let Some(mortality) = self.mortality {
|
||||
signed_extensions::CheckMortalityParams::mortal(
|
||||
mortality.period,
|
||||
mortality.checkpoint_number,
|
||||
mortality.checkpoint_hash,
|
||||
)
|
||||
} else {
|
||||
signed_extensions::CheckMortalityParams::immortal()
|
||||
};
|
||||
|
||||
let charge_asset_tx_params = if let Some(asset_id) = self.tip_of_asset_id {
|
||||
signed_extensions::ChargeAssetTxPaymentParams::tip_of(self.tip, asset_id)
|
||||
} else {
|
||||
signed_extensions::ChargeAssetTxPaymentParams::tip(self.tip)
|
||||
};
|
||||
|
||||
let charge_transaction_params =
|
||||
signed_extensions::ChargeTransactionPaymentParams::tip(self.tip);
|
||||
|
||||
(
|
||||
(),
|
||||
(),
|
||||
(),
|
||||
(),
|
||||
check_mortality_params,
|
||||
charge_asset_tx_params,
|
||||
charge_transaction_params,
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -3,264 +3,68 @@
|
||||
// see LICENSE for license details.
|
||||
|
||||
//! This module contains a trait which controls the parameters that must
|
||||
//! be provided in order to successfully construct an extrinsic. A basic
|
||||
//! implementation of the trait is provided ([`BaseExtrinsicParams`]) which is
|
||||
//! used by the provided Substrate and Polkadot configuration.
|
||||
//! be provided in order to successfully construct an extrinsic.
|
||||
//! [`crate::config::DefaultExtrinsicParams`] provides a general-purpose
|
||||
//! implementation of this that will work in many cases.
|
||||
|
||||
use crate::{utils::Encoded, Config};
|
||||
use codec::{Compact, Decode, Encode};
|
||||
use crate::{client::OfflineClientT, Config};
|
||||
use core::fmt::Debug;
|
||||
use derivative::Derivative;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
/// An error that can be emitted when trying to construct
|
||||
/// an instance of [`ExtrinsicParams`].
|
||||
#[derive(thiserror::Error, Debug)]
|
||||
#[non_exhaustive]
|
||||
pub enum ExtrinsicParamsError {
|
||||
/// A signed extension was encountered that we don't know about.
|
||||
#[error("Error constructing extrinsic parameters: Unknown signed extension '{0}'")]
|
||||
UnknownSignedExtension(String),
|
||||
/// Some custom error.
|
||||
#[error("Error constructing extrinsic parameters: {0}")]
|
||||
Custom(CustomError),
|
||||
}
|
||||
|
||||
/// A custom error.
|
||||
type CustomError = Box<dyn std::error::Error + Send + Sync + 'static>;
|
||||
|
||||
impl From<std::convert::Infallible> for ExtrinsicParamsError {
|
||||
fn from(value: std::convert::Infallible) -> Self {
|
||||
match value {}
|
||||
}
|
||||
}
|
||||
|
||||
/// This trait allows you to configure the "signed extra" and
|
||||
/// "additional" parameters that are signed and used in transactions.
|
||||
/// see [`BaseExtrinsicParams`] for an implementation that is compatible with
|
||||
/// a Polkadot node.
|
||||
pub trait ExtrinsicParams<Hash>: Debug + 'static {
|
||||
/// "additional" parameters that are a part of the transaction payload
|
||||
/// or the signer payload respectively.
|
||||
pub trait ExtrinsicParams<T: Config>: ExtrinsicParamsEncoder + Sized + 'static {
|
||||
/// These parameters can be provided to the constructor along with
|
||||
/// some default parameters that `subxt` understands, in order to
|
||||
/// help construct your [`ExtrinsicParams`] object.
|
||||
type OtherParams;
|
||||
|
||||
/// Construct a new instance of our [`ExtrinsicParams`]
|
||||
fn new(
|
||||
spec_version: u32,
|
||||
tx_version: u32,
|
||||
nonce: u64,
|
||||
genesis_hash: Hash,
|
||||
other_params: Self::OtherParams,
|
||||
) -> Self;
|
||||
/// The type of error returned from [`ExtrinsicParams::new()`].
|
||||
type Error: Into<ExtrinsicParamsError>;
|
||||
|
||||
/// Construct a new instance of our [`ExtrinsicParams`]
|
||||
fn new<Client: OfflineClientT<T>>(
|
||||
nonce: u64,
|
||||
client: Client,
|
||||
other_params: Self::OtherParams,
|
||||
) -> Result<Self, Self::Error>;
|
||||
}
|
||||
|
||||
/// This trait is expected to be implemented for any [`ExtrinsicParams`], and
|
||||
/// defines how to encode the "additional" and "extra" params. Both functions
|
||||
/// are optional and will encode nothing by default.
|
||||
pub trait ExtrinsicParamsEncoder: 'static {
|
||||
/// This is expected to SCALE encode the "signed extra" parameters
|
||||
/// to some buffer that has been provided. These are the parameters
|
||||
/// which are sent along with the transaction, as well as taken into
|
||||
/// account when signing the transaction.
|
||||
fn encode_extra_to(&self, v: &mut Vec<u8>);
|
||||
fn encode_extra_to(&self, _v: &mut Vec<u8>) {}
|
||||
|
||||
/// This is expected to SCALE encode the "additional" parameters
|
||||
/// to some buffer that has been provided. These parameters are _not_
|
||||
/// sent along with the transaction, but are taken into account when
|
||||
/// signing it, meaning the client and node must agree on their values.
|
||||
fn encode_additional_to(&self, v: &mut Vec<u8>);
|
||||
}
|
||||
|
||||
/// An implementation of [`ExtrinsicParams`] that is suitable for constructing
|
||||
/// extrinsics that can be sent to a node with the same signed extra and additional
|
||||
/// parameters as a Polkadot/Substrate node. The way that tip payments are specified
|
||||
/// differs between Substrate and Polkadot nodes, and so we are generic over that in
|
||||
/// order to support both here with relative ease.
|
||||
///
|
||||
/// If your node differs in the "signed extra" and "additional" parameters expected
|
||||
/// to be sent/signed with a transaction, then you can define your own type which
|
||||
/// implements the [`ExtrinsicParams`] trait.
|
||||
#[derive(Derivative)]
|
||||
#[derivative(Debug(bound = "Tip: Debug"))]
|
||||
pub struct BaseExtrinsicParams<T: Config, Tip: Debug> {
|
||||
/// Era defines how long the transaction will be valid for.
|
||||
era: Era,
|
||||
/// Nonce (account nonce sent along an extrinsic, such that no extrinsic is submitted twice)
|
||||
nonce: u64,
|
||||
/// The tip you would like to give to the block author for this transaction.
|
||||
/// Note: Can be zero.
|
||||
tip: Tip,
|
||||
/// spec version
|
||||
spec_version: u32,
|
||||
/// transaction version
|
||||
transaction_version: u32,
|
||||
/// genesis hash of the chain
|
||||
genesis_hash: T::Hash,
|
||||
/// The block after which the transaction becomes valid.
|
||||
mortality_checkpoint: T::Hash,
|
||||
marker: std::marker::PhantomData<T>,
|
||||
}
|
||||
|
||||
/// This builder allows you to provide the parameters that can be configured in order to
|
||||
/// construct a [`BaseExtrinsicParams`] value. This implements [`Default`], which allows
|
||||
/// [`BaseExtrinsicParams`] to be used with convenience methods like `sign_and_submit_default()`.
|
||||
///
|
||||
/// Prefer to use [`super::substrate::SubstrateExtrinsicParamsBuilder`] for a version of this
|
||||
/// tailored towards Substrate, or [`super::polkadot::PolkadotExtrinsicParamsBuilder`] for a
|
||||
/// version tailored to Polkadot.
|
||||
#[derive(Derivative)]
|
||||
#[derivative(
|
||||
Debug(bound = "Tip: Debug"),
|
||||
Clone(bound = "Tip: Clone"),
|
||||
Copy(bound = "Tip: Copy"),
|
||||
PartialEq(bound = "Tip: PartialEq")
|
||||
)]
|
||||
pub struct BaseExtrinsicParamsBuilder<T: Config, Tip> {
|
||||
era: Era,
|
||||
mortality_checkpoint: Option<T::Hash>,
|
||||
tip: Tip,
|
||||
}
|
||||
|
||||
impl<T: Config, Tip: Default> BaseExtrinsicParamsBuilder<T, Tip> {
|
||||
/// Instantiate the default set of [`BaseExtrinsicParamsBuilder`]
|
||||
pub fn new() -> Self {
|
||||
Self::default()
|
||||
}
|
||||
|
||||
/// Set the [`Era`], which defines how long the transaction will be valid for
|
||||
/// (it can be either immortal, or it can be mortal and expire after a certain amount
|
||||
/// of time). The second argument is the block hash after which the transaction
|
||||
/// becomes valid, and must align with the era phase (see the [`Era::Mortal`] docs
|
||||
/// for more detail on that).
|
||||
pub fn era(mut self, era: Era, checkpoint: T::Hash) -> Self {
|
||||
self.era = era;
|
||||
self.mortality_checkpoint = Some(checkpoint);
|
||||
self
|
||||
}
|
||||
|
||||
/// Set the tip you'd like to give to the block author
|
||||
/// for this transaction.
|
||||
pub fn tip(mut self, tip: impl Into<Tip>) -> Self {
|
||||
self.tip = tip.into();
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Config, Tip: Default> Default for BaseExtrinsicParamsBuilder<T, Tip> {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
era: Era::Immortal,
|
||||
mortality_checkpoint: None,
|
||||
tip: Tip::default(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Config, Tip: Debug + Encode + 'static> ExtrinsicParams<T::Hash>
|
||||
for BaseExtrinsicParams<T, Tip>
|
||||
{
|
||||
type OtherParams = BaseExtrinsicParamsBuilder<T, Tip>;
|
||||
|
||||
fn new(
|
||||
// Provided from subxt client:
|
||||
spec_version: u32,
|
||||
transaction_version: u32,
|
||||
nonce: u64,
|
||||
genesis_hash: T::Hash,
|
||||
// Provided externally:
|
||||
other_params: Self::OtherParams,
|
||||
) -> Self {
|
||||
BaseExtrinsicParams {
|
||||
era: other_params.era,
|
||||
mortality_checkpoint: other_params.mortality_checkpoint.unwrap_or(genesis_hash),
|
||||
tip: other_params.tip,
|
||||
nonce,
|
||||
spec_version,
|
||||
transaction_version,
|
||||
genesis_hash,
|
||||
marker: std::marker::PhantomData,
|
||||
}
|
||||
}
|
||||
|
||||
fn encode_extra_to(&self, v: &mut Vec<u8>) {
|
||||
let nonce: u64 = self.nonce;
|
||||
let tip = Encoded(self.tip.encode());
|
||||
(self.era, Compact(nonce), tip).encode_to(v);
|
||||
}
|
||||
|
||||
fn encode_additional_to(&self, v: &mut Vec<u8>) {
|
||||
(
|
||||
self.spec_version,
|
||||
self.transaction_version,
|
||||
self.genesis_hash,
|
||||
self.mortality_checkpoint,
|
||||
)
|
||||
.encode_to(v);
|
||||
}
|
||||
}
|
||||
|
||||
// Dev note: This and related bits taken from `sp_runtime::generic::Era`
|
||||
/// An era to describe the longevity of a transaction.
|
||||
#[derive(PartialEq, Eq, Clone, Copy, Debug, Serialize, Deserialize)]
|
||||
pub enum Era {
|
||||
/// The transaction is valid forever. The genesis hash must be present in the signed content.
|
||||
Immortal,
|
||||
|
||||
/// Period and phase are encoded:
|
||||
/// - The period of validity from the block hash found in the signing material.
|
||||
/// - The phase in the period that this transaction's lifetime begins (and, importantly,
|
||||
/// implies which block hash is included in the signature material). If the `period` is
|
||||
/// greater than 1 << 12, then it will be a factor of the times greater than 1<<12 that
|
||||
/// `period` is.
|
||||
///
|
||||
/// When used on `FRAME`-based runtimes, `period` cannot exceed `BlockHashCount` parameter
|
||||
/// of `system` module.
|
||||
Mortal(Period, Phase),
|
||||
}
|
||||
|
||||
/// Era period
|
||||
pub type Period = u64;
|
||||
|
||||
/// Era phase
|
||||
pub type Phase = u64;
|
||||
|
||||
// E.g. with period == 4:
|
||||
// 0 10 20 30 40
|
||||
// 0123456789012345678901234567890123456789012
|
||||
// |...|
|
||||
// authored -/ \- expiry
|
||||
// phase = 1
|
||||
// n = Q(current - phase, period) + phase
|
||||
impl Era {
|
||||
/// Create a new era based on a period (which should be a power of two between 4 and 65536
|
||||
/// inclusive) and a block number on which it should start (or, for long periods, be shortly
|
||||
/// after the start).
|
||||
///
|
||||
/// If using `Era` in the context of `FRAME` runtime, make sure that `period`
|
||||
/// does not exceed `BlockHashCount` parameter passed to `system` module, since that
|
||||
/// prunes old blocks and renders transactions immediately invalid.
|
||||
pub fn mortal(period: u64, current: u64) -> Self {
|
||||
let period = period
|
||||
.checked_next_power_of_two()
|
||||
.unwrap_or(1 << 16)
|
||||
.clamp(4, 1 << 16);
|
||||
let phase = current % period;
|
||||
let quantize_factor = (period >> 12).max(1);
|
||||
let quantized_phase = phase / quantize_factor * quantize_factor;
|
||||
|
||||
Self::Mortal(period, quantized_phase)
|
||||
}
|
||||
|
||||
/// Create an "immortal" transaction.
|
||||
pub fn immortal() -> Self {
|
||||
Self::Immortal
|
||||
}
|
||||
}
|
||||
|
||||
// Both copied from `sp_runtime::generic::Era`; this is the wire interface and so
|
||||
// it's really the most important bit here.
|
||||
impl Encode for Era {
|
||||
fn encode_to<T: codec::Output + ?Sized>(&self, output: &mut T) {
|
||||
match self {
|
||||
Self::Immortal => output.push_byte(0),
|
||||
Self::Mortal(period, phase) => {
|
||||
let quantize_factor = (*period >> 12).max(1);
|
||||
let encoded = (period.trailing_zeros() - 1).clamp(1, 15) as u16
|
||||
| ((phase / quantize_factor) << 4) as u16;
|
||||
encoded.encode_to(output);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
impl Decode for Era {
|
||||
fn decode<I: codec::Input>(input: &mut I) -> Result<Self, codec::Error> {
|
||||
let first = input.read_byte()?;
|
||||
if first == 0 {
|
||||
Ok(Self::Immortal)
|
||||
} else {
|
||||
let encoded = first as u64 + ((input.read_byte()? as u64) << 8);
|
||||
let period = 2 << (encoded % (1 << 4));
|
||||
let quantize_factor = (period >> 12).max(1);
|
||||
let phase = (encoded >> 4) * quantize_factor;
|
||||
if period >= 4 && phase < period {
|
||||
Ok(Self::Mortal(period, phase))
|
||||
} else {
|
||||
Err("Invalid period and phase".into())
|
||||
}
|
||||
}
|
||||
}
|
||||
fn encode_additional_to(&self, _v: &mut Vec<u8>) {}
|
||||
}
|
||||
|
||||
+14
-25
@@ -8,23 +8,28 @@
|
||||
//! default Substrate node implementation, and [`PolkadotConfig`] for a
|
||||
//! Polkadot node.
|
||||
|
||||
pub mod extrinsic_params;
|
||||
mod default_extrinsic_params;
|
||||
mod extrinsic_params;
|
||||
|
||||
pub mod polkadot;
|
||||
pub mod signed_extensions;
|
||||
pub mod substrate;
|
||||
|
||||
use codec::{Decode, Encode};
|
||||
use core::fmt::Debug;
|
||||
use serde::{de::DeserializeOwned, Serialize};
|
||||
|
||||
pub use extrinsic_params::ExtrinsicParams;
|
||||
pub use polkadot::PolkadotConfig;
|
||||
pub use substrate::SubstrateConfig;
|
||||
pub use default_extrinsic_params::{DefaultExtrinsicParams, DefaultExtrinsicParamsBuilder};
|
||||
pub use extrinsic_params::{ExtrinsicParams, ExtrinsicParamsEncoder, ExtrinsicParamsError};
|
||||
pub use polkadot::{PolkadotConfig, PolkadotExtrinsicParams, PolkadotExtrinsicParamsBuilder};
|
||||
pub use signed_extensions::SignedExtension;
|
||||
pub use substrate::{SubstrateConfig, SubstrateExtrinsicParams, SubstrateExtrinsicParamsBuilder};
|
||||
|
||||
/// Runtime types.
|
||||
// Note: the 'static bound isn't strictly required, but currently deriving TypeInfo
|
||||
// automatically applies a 'static bound to all generic types (including this one),
|
||||
// and so until that is resolved, we'll keep the (easy to satisfy) constraint here.
|
||||
pub trait Config: 'static {
|
||||
pub trait Config: Sized + 'static {
|
||||
/// The output of the `Hasher` function.
|
||||
type Hash: Debug
|
||||
+ Copy
|
||||
@@ -53,9 +58,12 @@ pub trait Config: 'static {
|
||||
type Header: Debug + Header<Hasher = Self::Hasher> + Sync + Send + DeserializeOwned;
|
||||
|
||||
/// This type defines the extrinsic extra and additional parameters.
|
||||
type ExtrinsicParams: extrinsic_params::ExtrinsicParams<Self::Hash>;
|
||||
type ExtrinsicParams: ExtrinsicParams<Self>;
|
||||
}
|
||||
|
||||
/// given some [`Config`], this return the other params needed for its `ExtrinsicParams`.
|
||||
pub type OtherParamsFor<T> = <<T as Config>::ExtrinsicParams as ExtrinsicParams<T>>::OtherParams;
|
||||
|
||||
/// This represents the hasher used by a node to hash things like block headers
|
||||
/// and extrinsics.
|
||||
pub trait Hasher {
|
||||
@@ -88,25 +96,6 @@ pub trait Header: Sized + Encode {
|
||||
}
|
||||
}
|
||||
|
||||
/// Take a type implementing [`Config`] (eg [`SubstrateConfig`]), and some type which describes the
|
||||
/// additional and extra parameters to pass to an extrinsic (see [`ExtrinsicParams`]),
|
||||
/// and returns a type implementing [`Config`] with those new [`ExtrinsicParams`].
|
||||
pub struct WithExtrinsicParams<T: Config, E: extrinsic_params::ExtrinsicParams<T::Hash>> {
|
||||
_marker: std::marker::PhantomData<(T, E)>,
|
||||
}
|
||||
|
||||
impl<T: Config, E: extrinsic_params::ExtrinsicParams<T::Hash>> Config
|
||||
for WithExtrinsicParams<T, E>
|
||||
{
|
||||
type Hash = T::Hash;
|
||||
type AccountId = T::AccountId;
|
||||
type Address = T::Address;
|
||||
type Signature = T::Signature;
|
||||
type Hasher = T::Hasher;
|
||||
type Header = T::Header;
|
||||
type ExtrinsicParams = E;
|
||||
}
|
||||
|
||||
/// implement subxt's Hasher and Header traits for some substrate structs
|
||||
#[cfg(feature = "substrate-compat")]
|
||||
mod substrate_impls {
|
||||
|
||||
@@ -4,11 +4,7 @@
|
||||
|
||||
//! Polkadot specific configuration
|
||||
|
||||
use super::{
|
||||
extrinsic_params::{BaseExtrinsicParams, BaseExtrinsicParamsBuilder},
|
||||
Config,
|
||||
};
|
||||
use codec::Encode;
|
||||
use super::{Config, DefaultExtrinsicParams, DefaultExtrinsicParamsBuilder};
|
||||
|
||||
pub use crate::utils::{AccountId32, MultiAddress, MultiSignature};
|
||||
use crate::SubstrateConfig;
|
||||
@@ -29,31 +25,8 @@ impl Config for PolkadotConfig {
|
||||
|
||||
/// A struct representing the signed extra and additional parameters required
|
||||
/// to construct a transaction for a polkadot node.
|
||||
pub type PolkadotExtrinsicParams<T> = BaseExtrinsicParams<T, PlainTip>;
|
||||
pub type PolkadotExtrinsicParams<T> = DefaultExtrinsicParams<T>;
|
||||
|
||||
/// A builder which leads to [`PolkadotExtrinsicParams`] being constructed.
|
||||
/// This is what you provide to methods like `sign_and_submit()`.
|
||||
pub type PolkadotExtrinsicParamsBuilder<T> = BaseExtrinsicParamsBuilder<T, PlainTip>;
|
||||
|
||||
// Because Era is one of the args to our extrinsic params.
|
||||
pub use super::extrinsic_params::Era;
|
||||
|
||||
/// A tip payment.
|
||||
#[derive(Copy, Clone, Debug, Default, Encode)]
|
||||
pub struct PlainTip {
|
||||
#[codec(compact)]
|
||||
tip: u128,
|
||||
}
|
||||
|
||||
impl PlainTip {
|
||||
/// Create a new tip of the amount provided.
|
||||
pub fn new(amount: u128) -> Self {
|
||||
PlainTip { tip: amount }
|
||||
}
|
||||
}
|
||||
|
||||
impl From<u128> for PlainTip {
|
||||
fn from(n: u128) -> Self {
|
||||
PlainTip::new(n)
|
||||
}
|
||||
}
|
||||
pub type PolkadotExtrinsicParamsBuilder<T> = DefaultExtrinsicParamsBuilder<T>;
|
||||
|
||||
@@ -0,0 +1,453 @@
|
||||
// Copyright 2019-2023 Parity Technologies (UK) Ltd.
|
||||
// This file is dual-licensed as Apache-2.0 or GPL-3.0.
|
||||
// see LICENSE for license details.
|
||||
|
||||
//! This module contains implementations for common signed extensions, each
|
||||
//! of which implements [`SignedExtension`], and can be used in conjunction with
|
||||
//! [`AnyOf`] to configure the set of signed extensions which are known about
|
||||
//! when interacting with a chain.
|
||||
|
||||
use super::extrinsic_params::{ExtrinsicParams, ExtrinsicParamsEncoder, ExtrinsicParamsError};
|
||||
use crate::utils::Era;
|
||||
use crate::{client::OfflineClientT, Config};
|
||||
use codec::{Compact, Encode};
|
||||
use core::fmt::Debug;
|
||||
use std::collections::HashMap;
|
||||
|
||||
/// A single [`SignedExtension`] has a unique name, but is otherwise the
|
||||
/// same as [`ExtrinsicParams`] in describing how to encode the extra and
|
||||
/// additional data.
|
||||
pub trait SignedExtension<T: Config>: ExtrinsicParams<T> {
|
||||
/// The name of the signed extension. This is used to associate it
|
||||
/// with the signed extensions that the node is making use of.
|
||||
const NAME: &'static str;
|
||||
}
|
||||
|
||||
/// The [`CheckSpecVersion`] signed extension.
|
||||
#[derive(Debug)]
|
||||
pub struct CheckSpecVersion(u32);
|
||||
|
||||
impl<T: Config> ExtrinsicParams<T> for CheckSpecVersion {
|
||||
type OtherParams = ();
|
||||
type Error = std::convert::Infallible;
|
||||
|
||||
fn new<Client: OfflineClientT<T>>(
|
||||
_nonce: u64,
|
||||
client: Client,
|
||||
_other_params: Self::OtherParams,
|
||||
) -> Result<Self, Self::Error> {
|
||||
Ok(CheckSpecVersion(client.runtime_version().spec_version))
|
||||
}
|
||||
}
|
||||
|
||||
impl ExtrinsicParamsEncoder for CheckSpecVersion {
|
||||
fn encode_additional_to(&self, v: &mut Vec<u8>) {
|
||||
self.0.encode_to(v);
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Config> SignedExtension<T> for CheckSpecVersion {
|
||||
const NAME: &'static str = "CheckSpecVersion";
|
||||
}
|
||||
|
||||
/// The [`CheckNonce`] signed extension.
|
||||
#[derive(Debug)]
|
||||
pub struct CheckNonce(Compact<u64>);
|
||||
|
||||
impl<T: Config> ExtrinsicParams<T> for CheckNonce {
|
||||
type OtherParams = ();
|
||||
type Error = std::convert::Infallible;
|
||||
|
||||
fn new<Client: OfflineClientT<T>>(
|
||||
nonce: u64,
|
||||
_client: Client,
|
||||
_other_params: Self::OtherParams,
|
||||
) -> Result<Self, Self::Error> {
|
||||
Ok(CheckNonce(Compact(nonce)))
|
||||
}
|
||||
}
|
||||
|
||||
impl ExtrinsicParamsEncoder for CheckNonce {
|
||||
fn encode_extra_to(&self, v: &mut Vec<u8>) {
|
||||
self.0.encode_to(v);
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Config> SignedExtension<T> for CheckNonce {
|
||||
const NAME: &'static str = "CheckNonce";
|
||||
}
|
||||
|
||||
/// The [`CheckTxVersion`] signed extension.
|
||||
#[derive(Debug)]
|
||||
pub struct CheckTxVersion(u32);
|
||||
|
||||
impl<T: Config> ExtrinsicParams<T> for CheckTxVersion {
|
||||
type OtherParams = ();
|
||||
type Error = std::convert::Infallible;
|
||||
|
||||
fn new<Client: OfflineClientT<T>>(
|
||||
_nonce: u64,
|
||||
client: Client,
|
||||
_other_params: Self::OtherParams,
|
||||
) -> Result<Self, Self::Error> {
|
||||
Ok(CheckTxVersion(client.runtime_version().transaction_version))
|
||||
}
|
||||
}
|
||||
|
||||
impl ExtrinsicParamsEncoder for CheckTxVersion {
|
||||
fn encode_additional_to(&self, v: &mut Vec<u8>) {
|
||||
self.0.encode_to(v);
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Config> SignedExtension<T> for CheckTxVersion {
|
||||
const NAME: &'static str = "CheckTxVersion";
|
||||
}
|
||||
|
||||
/// The [`CheckGenesis`] signed extension.
|
||||
pub struct CheckGenesis<T: Config>(T::Hash);
|
||||
|
||||
impl<T: Config> std::fmt::Debug for CheckGenesis<T> {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
f.debug_tuple("CheckGenesis").field(&self.0).finish()
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Config> ExtrinsicParams<T> for CheckGenesis<T> {
|
||||
type OtherParams = ();
|
||||
type Error = std::convert::Infallible;
|
||||
|
||||
fn new<Client: OfflineClientT<T>>(
|
||||
_nonce: u64,
|
||||
client: Client,
|
||||
_other_params: Self::OtherParams,
|
||||
) -> Result<Self, Self::Error> {
|
||||
Ok(CheckGenesis(client.genesis_hash()))
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Config> ExtrinsicParamsEncoder for CheckGenesis<T> {
|
||||
fn encode_additional_to(&self, v: &mut Vec<u8>) {
|
||||
self.0.encode_to(v);
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Config> SignedExtension<T> for CheckGenesis<T> {
|
||||
const NAME: &'static str = "CheckGenesis";
|
||||
}
|
||||
|
||||
/// The [`CheckMortality`] signed extension.
|
||||
pub struct CheckMortality<T: Config> {
|
||||
era: Era,
|
||||
checkpoint: T::Hash,
|
||||
}
|
||||
|
||||
/// Parameters to configure the [`CheckMortality`] signed extension.
|
||||
pub struct CheckMortalityParams<T: Config> {
|
||||
era: Era,
|
||||
checkpoint: Option<T::Hash>,
|
||||
}
|
||||
|
||||
impl<T: Config> Default for CheckMortalityParams<T> {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
era: Default::default(),
|
||||
checkpoint: Default::default(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Config> CheckMortalityParams<T> {
|
||||
/// Configure a mortal transaction. The `period` is (roughly) how many
|
||||
/// blocks the transaction will be valid for. The `block_number` and
|
||||
/// `block_hash` should both point to the same block, and are the block that
|
||||
/// the transaction is mortal from.
|
||||
pub fn mortal(period: u64, block_number: u64, block_hash: T::Hash) -> Self {
|
||||
CheckMortalityParams {
|
||||
era: Era::mortal(period, block_number),
|
||||
checkpoint: Some(block_hash),
|
||||
}
|
||||
}
|
||||
/// An immortal transaction.
|
||||
pub fn immortal() -> Self {
|
||||
CheckMortalityParams {
|
||||
era: Era::Immortal,
|
||||
checkpoint: None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Config> std::fmt::Debug for CheckMortality<T> {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
f.debug_struct("CheckMortality")
|
||||
.field("era", &self.era)
|
||||
.field("checkpoint", &self.checkpoint)
|
||||
.finish()
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Config> ExtrinsicParams<T> for CheckMortality<T> {
|
||||
type OtherParams = CheckMortalityParams<T>;
|
||||
type Error = std::convert::Infallible;
|
||||
|
||||
fn new<Client: OfflineClientT<T>>(
|
||||
_nonce: u64,
|
||||
client: Client,
|
||||
other_params: Self::OtherParams,
|
||||
) -> Result<Self, Self::Error> {
|
||||
Ok(CheckMortality {
|
||||
era: other_params.era,
|
||||
checkpoint: other_params.checkpoint.unwrap_or(client.genesis_hash()),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Config> ExtrinsicParamsEncoder for CheckMortality<T> {
|
||||
fn encode_extra_to(&self, v: &mut Vec<u8>) {
|
||||
self.era.encode_to(v);
|
||||
}
|
||||
fn encode_additional_to(&self, v: &mut Vec<u8>) {
|
||||
self.checkpoint.encode_to(v)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Config> SignedExtension<T> for CheckMortality<T> {
|
||||
const NAME: &'static str = "CheckMortality";
|
||||
}
|
||||
|
||||
/// The [`ChargeAssetTxPayment`] signed extension.
|
||||
#[derive(Debug)]
|
||||
pub struct ChargeAssetTxPayment {
|
||||
tip: Compact<u128>,
|
||||
asset_id: Option<u32>,
|
||||
}
|
||||
|
||||
/// Parameters to configure the [`ChargeAssetTxPayment`] signed extension.
|
||||
#[derive(Default)]
|
||||
pub struct ChargeAssetTxPaymentParams {
|
||||
tip: u128,
|
||||
asset_id: Option<u32>,
|
||||
}
|
||||
|
||||
impl ChargeAssetTxPaymentParams {
|
||||
/// Don't provide a tip to the extrinsic author.
|
||||
pub fn no_tip() -> Self {
|
||||
ChargeAssetTxPaymentParams {
|
||||
tip: 0,
|
||||
asset_id: None,
|
||||
}
|
||||
}
|
||||
/// Tip the extrinsic author in the native chain token.
|
||||
pub fn tip(tip: u128) -> Self {
|
||||
ChargeAssetTxPaymentParams {
|
||||
tip,
|
||||
asset_id: None,
|
||||
}
|
||||
}
|
||||
/// Tip the extrinsic author using the asset ID given.
|
||||
pub fn tip_of(tip: u128, asset_id: u32) -> Self {
|
||||
ChargeAssetTxPaymentParams {
|
||||
tip,
|
||||
asset_id: Some(asset_id),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Config> ExtrinsicParams<T> for ChargeAssetTxPayment {
|
||||
type OtherParams = ChargeAssetTxPaymentParams;
|
||||
type Error = std::convert::Infallible;
|
||||
|
||||
fn new<Client: OfflineClientT<T>>(
|
||||
_nonce: u64,
|
||||
_client: Client,
|
||||
other_params: Self::OtherParams,
|
||||
) -> Result<Self, Self::Error> {
|
||||
Ok(ChargeAssetTxPayment {
|
||||
tip: Compact(other_params.tip),
|
||||
asset_id: other_params.asset_id,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl ExtrinsicParamsEncoder for ChargeAssetTxPayment {
|
||||
fn encode_extra_to(&self, v: &mut Vec<u8>) {
|
||||
(self.tip, self.asset_id).encode_to(v);
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Config> SignedExtension<T> for ChargeAssetTxPayment {
|
||||
const NAME: &'static str = "ChargeAssetTxPayment";
|
||||
}
|
||||
|
||||
/// The [`ChargeTransactionPayment`] signed extension.
|
||||
#[derive(Debug)]
|
||||
pub struct ChargeTransactionPayment {
|
||||
tip: Compact<u128>,
|
||||
}
|
||||
|
||||
/// Parameters to configure the [`ChargeTransactionPayment`] signed extension.
|
||||
#[derive(Default)]
|
||||
pub struct ChargeTransactionPaymentParams {
|
||||
tip: u128,
|
||||
}
|
||||
|
||||
impl ChargeTransactionPaymentParams {
|
||||
/// Don't provide a tip to the extrinsic author.
|
||||
pub fn no_tip() -> Self {
|
||||
ChargeTransactionPaymentParams { tip: 0 }
|
||||
}
|
||||
/// Tip the extrinsic author in the native chain token.
|
||||
pub fn tip(tip: u128) -> Self {
|
||||
ChargeTransactionPaymentParams { tip }
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Config> ExtrinsicParams<T> for ChargeTransactionPayment {
|
||||
type OtherParams = ChargeTransactionPaymentParams;
|
||||
type Error = std::convert::Infallible;
|
||||
|
||||
fn new<Client: OfflineClientT<T>>(
|
||||
_nonce: u64,
|
||||
_client: Client,
|
||||
other_params: Self::OtherParams,
|
||||
) -> Result<Self, Self::Error> {
|
||||
Ok(ChargeTransactionPayment {
|
||||
tip: Compact(other_params.tip),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl ExtrinsicParamsEncoder for ChargeTransactionPayment {
|
||||
fn encode_extra_to(&self, v: &mut Vec<u8>) {
|
||||
self.tip.encode_to(v);
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Config> SignedExtension<T> for ChargeTransactionPayment {
|
||||
const NAME: &'static str = "ChargeTransactionPayment";
|
||||
}
|
||||
|
||||
/// This accepts a tuple of [`SignedExtension`]s, and will dynamically make use of whichever
|
||||
/// ones are actually required for the chain in the correct order, ignoring the rest. This
|
||||
/// is a sensible default, and allows for a single configuration to work across multiple chains.
|
||||
pub struct AnyOf<T, Params> {
|
||||
params: Vec<Box<dyn ExtrinsicParamsEncoder>>,
|
||||
_marker: std::marker::PhantomData<(T, Params)>,
|
||||
}
|
||||
|
||||
macro_rules! impl_tuples {
|
||||
($($ident:ident $index:tt),+) => {
|
||||
// We do some magic when the tuple is wrapped in AnyOf. We
|
||||
// look at the metadata, and use this to select and make use of only the extensions
|
||||
// that we actually need for the chain we're dealing with.
|
||||
impl <T, $($ident),+> ExtrinsicParams<T> for AnyOf<T, ($($ident,)+)>
|
||||
where
|
||||
T: Config,
|
||||
$($ident: SignedExtension<T>,)+
|
||||
{
|
||||
type OtherParams = ($($ident::OtherParams,)+);
|
||||
type Error = ExtrinsicParamsError;
|
||||
|
||||
fn new<Client: OfflineClientT<T>>(
|
||||
nonce: u64,
|
||||
client: Client,
|
||||
other_params: Self::OtherParams,
|
||||
) -> Result<Self, Self::Error> {
|
||||
// First, push encoders to map as we are given them:
|
||||
let mut map = HashMap::new();
|
||||
$({
|
||||
let e: Box<dyn ExtrinsicParamsEncoder>
|
||||
= Box::new($ident::new(nonce, client.clone(), other_params.$index).map_err(Into::into)?);
|
||||
map.insert($ident::NAME, e);
|
||||
})+
|
||||
|
||||
// Next, based on metadata, push to vec in the order the node needs:
|
||||
let mut params = Vec::new();
|
||||
let metadata = client.metadata();
|
||||
let types = metadata.types();
|
||||
for ext in metadata.extrinsic().signed_extensions() {
|
||||
if let Some(ext) = map.remove(ext.identifier()) {
|
||||
params.push(ext)
|
||||
} else {
|
||||
if is_type_empty(ext.extra_ty(), types) && is_type_empty(ext.additional_ty(), types) {
|
||||
// If we don't know about the signed extension, _but_ it appears to require zero bytes
|
||||
// to encode its extra and additional data, then we can safely ignore it as it makes
|
||||
// no difference either way.
|
||||
continue;
|
||||
}
|
||||
return Err(ExtrinsicParamsError::UnknownSignedExtension(ext.identifier().to_owned()));
|
||||
}
|
||||
}
|
||||
|
||||
Ok(AnyOf {
|
||||
params,
|
||||
_marker: std::marker::PhantomData
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl <T, $($ident),+> ExtrinsicParamsEncoder for AnyOf<T, ($($ident,)+)>
|
||||
where
|
||||
T: Config,
|
||||
$($ident: SignedExtension<T>,)+
|
||||
{
|
||||
fn encode_extra_to(&self, v: &mut Vec<u8>) {
|
||||
for ext in &self.params {
|
||||
ext.encode_extra_to(v);
|
||||
}
|
||||
}
|
||||
fn encode_additional_to(&self, v: &mut Vec<u8>) {
|
||||
for ext in &self.params {
|
||||
ext.encode_additional_to(v);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[rustfmt::skip]
|
||||
const _: () = {
|
||||
impl_tuples!(A 0);
|
||||
impl_tuples!(A 0, B 1);
|
||||
impl_tuples!(A 0, B 1, C 2);
|
||||
impl_tuples!(A 0, B 1, C 2, D 3);
|
||||
impl_tuples!(A 0, B 1, C 2, D 3, E 4);
|
||||
impl_tuples!(A 0, B 1, C 2, D 3, E 4, F 5);
|
||||
impl_tuples!(A 0, B 1, C 2, D 3, E 4, F 5, G 6);
|
||||
impl_tuples!(A 0, B 1, C 2, D 3, E 4, F 5, G 6, H 7);
|
||||
impl_tuples!(A 0, B 1, C 2, D 3, E 4, F 5, G 6, H 7, I 8);
|
||||
impl_tuples!(A 0, B 1, C 2, D 3, E 4, F 5, G 6, H 7, I 8, J 9);
|
||||
impl_tuples!(A 0, B 1, C 2, D 3, E 4, F 5, G 6, H 7, I 8, J 9, K 10);
|
||||
impl_tuples!(A 0, B 1, C 2, D 3, E 4, F 5, G 6, H 7, I 8, J 9, K 10, L 11);
|
||||
impl_tuples!(A 0, B 1, C 2, D 3, E 4, F 5, G 6, H 7, I 8, J 9, K 10, L 11, M 12);
|
||||
impl_tuples!(A 0, B 1, C 2, D 3, E 4, F 5, G 6, H 7, I 8, J 9, K 10, L 11, M 12, N 13);
|
||||
impl_tuples!(A 0, B 1, C 2, D 3, E 4, F 5, G 6, H 7, I 8, J 9, K 10, L 11, M 12, N 13, O 14);
|
||||
impl_tuples!(A 0, B 1, C 2, D 3, E 4, F 5, G 6, H 7, I 8, J 9, K 10, L 11, M 12, N 13, O 14, P 15);
|
||||
impl_tuples!(A 0, B 1, C 2, D 3, E 4, F 5, G 6, H 7, I 8, J 9, K 10, L 11, M 12, N 13, O 14, P 15, Q 16);
|
||||
impl_tuples!(A 0, B 1, C 2, D 3, E 4, F 5, G 6, H 7, I 8, J 9, K 10, L 11, M 12, N 13, O 14, P 15, Q 16, R 17);
|
||||
impl_tuples!(A 0, B 1, C 2, D 3, E 4, F 5, G 6, H 7, I 8, J 9, K 10, L 11, M 12, N 13, O 14, P 15, Q 16, R 17, S 18);
|
||||
impl_tuples!(A 0, B 1, C 2, D 3, E 4, F 5, G 6, H 7, I 8, J 9, K 10, L 11, M 12, N 13, O 14, P 15, Q 16, R 17, S 18, U 19);
|
||||
impl_tuples!(A 0, B 1, C 2, D 3, E 4, F 5, G 6, H 7, I 8, J 9, K 10, L 11, M 12, N 13, O 14, P 15, Q 16, R 17, S 18, U 19, V 20);
|
||||
};
|
||||
|
||||
/// Checks to see whether the type being given is empty, ie would require
|
||||
/// 0 bytes to encode.
|
||||
fn is_type_empty(type_id: u32, types: &scale_info::PortableRegistry) -> bool {
|
||||
let Some(ty) = types.resolve(type_id) else {
|
||||
// Can't resolve; type may not be empty. Not expected to hit this.
|
||||
return false
|
||||
};
|
||||
|
||||
use scale_info::TypeDef;
|
||||
match &ty.type_def {
|
||||
TypeDef::Composite(c) => c.fields.iter().all(|f| is_type_empty(f.ty.id, types)),
|
||||
TypeDef::Array(a) => a.len == 0 || is_type_empty(a.type_param.id, types),
|
||||
TypeDef::Tuple(t) => t.fields.iter().all(|f| is_type_empty(f.id, types)),
|
||||
// Explicitly list these in case any additions are made in the future.
|
||||
TypeDef::BitSequence(_)
|
||||
| TypeDef::Variant(_)
|
||||
| TypeDef::Sequence(_)
|
||||
| TypeDef::Compact(_)
|
||||
| TypeDef::Primitive(_) => false,
|
||||
}
|
||||
}
|
||||
@@ -4,10 +4,7 @@
|
||||
|
||||
//! Substrate specific configuration
|
||||
|
||||
use super::{
|
||||
extrinsic_params::{BaseExtrinsicParams, BaseExtrinsicParamsBuilder},
|
||||
Config, Hasher, Header,
|
||||
};
|
||||
use super::{Config, DefaultExtrinsicParams, DefaultExtrinsicParamsBuilder, Hasher, Header};
|
||||
use codec::{Decode, Encode};
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
@@ -31,45 +28,11 @@ impl Config for SubstrateConfig {
|
||||
|
||||
/// A struct representing the signed extra and additional parameters required
|
||||
/// to construct a transaction for the default substrate node.
|
||||
pub type SubstrateExtrinsicParams<T> = BaseExtrinsicParams<T, AssetTip>;
|
||||
pub type SubstrateExtrinsicParams<T> = DefaultExtrinsicParams<T>;
|
||||
|
||||
/// A builder which leads to [`SubstrateExtrinsicParams`] being constructed.
|
||||
/// This is what you provide to methods like `sign_and_submit()`.
|
||||
pub type SubstrateExtrinsicParamsBuilder<T> = BaseExtrinsicParamsBuilder<T, AssetTip>;
|
||||
|
||||
// Because Era is one of the args to our extrinsic params.
|
||||
pub use super::extrinsic_params::Era;
|
||||
|
||||
/// A tip payment made in the form of a specific asset.
|
||||
#[derive(Copy, Clone, Debug, Default, Encode)]
|
||||
pub struct AssetTip {
|
||||
#[codec(compact)]
|
||||
tip: u128,
|
||||
asset: Option<u32>,
|
||||
}
|
||||
|
||||
impl AssetTip {
|
||||
/// Create a new tip of the amount provided.
|
||||
pub fn new(amount: u128) -> Self {
|
||||
AssetTip {
|
||||
tip: amount,
|
||||
asset: None,
|
||||
}
|
||||
}
|
||||
|
||||
/// Designate the tip as being of a particular asset class.
|
||||
/// If this is not set, then the native currency is used.
|
||||
pub fn of_asset(mut self, asset: u32) -> Self {
|
||||
self.asset = Some(asset);
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
impl From<u128> for AssetTip {
|
||||
fn from(n: u128) -> Self {
|
||||
AssetTip::new(n)
|
||||
}
|
||||
}
|
||||
pub type SubstrateExtrinsicParamsBuilder<T> = DefaultExtrinsicParamsBuilder<T>;
|
||||
|
||||
/// A type that can hash values using the blaks2_256 algorithm.
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Encode)]
|
||||
|
||||
@@ -17,6 +17,7 @@ pub use dispatch_error::{
|
||||
};
|
||||
|
||||
// Re-expose the errors we use from other crates here:
|
||||
pub use crate::config::ExtrinsicParamsError;
|
||||
pub use crate::metadata::Metadata;
|
||||
pub use scale_decode::Error as DecodeError;
|
||||
pub use scale_encode::Error as EncodeError;
|
||||
@@ -58,6 +59,9 @@ pub enum Error {
|
||||
/// Transaction progress error.
|
||||
#[error("Transaction error: {0}")]
|
||||
Transaction(#[from] TransactionError),
|
||||
/// Error constructing the appropriate extrinsic params.
|
||||
#[error("{0}")]
|
||||
ExtrinsicParams(#[from] ExtrinsicParamsError),
|
||||
/// Block related error.
|
||||
#[error("Block error: {0}")]
|
||||
Block(#[from] BlockError),
|
||||
@@ -88,6 +92,12 @@ impl From<String> for Error {
|
||||
}
|
||||
}
|
||||
|
||||
impl From<std::convert::Infallible> for Error {
|
||||
fn from(value: std::convert::Infallible) -> Self {
|
||||
match value {}
|
||||
}
|
||||
}
|
||||
|
||||
/// An RPC error. Since we are generic over the RPC client that is used,
|
||||
/// the error is boxed and could be casted.
|
||||
#[derive(Debug, thiserror::Error)]
|
||||
|
||||
+15
-20
@@ -11,7 +11,7 @@ use sp_core_hashing::blake2_256;
|
||||
use crate::error::DecodeError;
|
||||
use crate::{
|
||||
client::{OfflineClientT, OnlineClientT},
|
||||
config::{Config, ExtrinsicParams, Hasher},
|
||||
config::{Config, ExtrinsicParams, ExtrinsicParamsEncoder, Hasher},
|
||||
error::{Error, MetadataError},
|
||||
tx::{Signer as SignerT, TxPayload, TxProgress},
|
||||
utils::{Encoded, PhantomDataSendSync},
|
||||
@@ -111,7 +111,7 @@ impl<T: Config, C: OfflineClientT<T>> TxClient<T, C> {
|
||||
&self,
|
||||
call: &Call,
|
||||
account_nonce: u64,
|
||||
other_params: <T::ExtrinsicParams as ExtrinsicParams<T::Hash>>::OtherParams,
|
||||
other_params: <T::ExtrinsicParams as ExtrinsicParams<T>>::OtherParams,
|
||||
) -> Result<PartialExtrinsic<T, C>, Error>
|
||||
where
|
||||
Call: TxPayload,
|
||||
@@ -124,17 +124,12 @@ impl<T: Config, C: OfflineClientT<T>> TxClient<T, C> {
|
||||
let call_data = self.call_data(call)?;
|
||||
|
||||
// 3. Construct our custom additional/extra params.
|
||||
let additional_and_extra_params = {
|
||||
// Obtain spec version and transaction version from the runtime version of the client.
|
||||
let runtime = self.client.runtime_version();
|
||||
<T::ExtrinsicParams as ExtrinsicParams<T::Hash>>::new(
|
||||
runtime.spec_version,
|
||||
runtime.transaction_version,
|
||||
account_nonce,
|
||||
self.client.genesis_hash(),
|
||||
other_params,
|
||||
)
|
||||
};
|
||||
let additional_and_extra_params = <T::ExtrinsicParams as ExtrinsicParams<T>>::new(
|
||||
account_nonce,
|
||||
self.client.clone(),
|
||||
other_params,
|
||||
)
|
||||
.map_err(Into::into)?;
|
||||
|
||||
// Return these details, ready to construct a signed extrinsic from.
|
||||
Ok(PartialExtrinsic {
|
||||
@@ -150,7 +145,7 @@ impl<T: Config, C: OfflineClientT<T>> TxClient<T, C> {
|
||||
call: &Call,
|
||||
signer: &Signer,
|
||||
account_nonce: u64,
|
||||
other_params: <T::ExtrinsicParams as ExtrinsicParams<T::Hash>>::OtherParams,
|
||||
other_params: <T::ExtrinsicParams as ExtrinsicParams<T>>::OtherParams,
|
||||
) -> Result<SubmittableExtrinsic<T, C>, Error>
|
||||
where
|
||||
Call: TxPayload,
|
||||
@@ -203,7 +198,7 @@ where
|
||||
&self,
|
||||
call: &Call,
|
||||
account_id: &T::AccountId,
|
||||
other_params: <T::ExtrinsicParams as ExtrinsicParams<T::Hash>>::OtherParams,
|
||||
other_params: <T::ExtrinsicParams as ExtrinsicParams<T>>::OtherParams,
|
||||
) -> Result<PartialExtrinsic<T, C>, Error>
|
||||
where
|
||||
Call: TxPayload,
|
||||
@@ -217,7 +212,7 @@ where
|
||||
&self,
|
||||
call: &Call,
|
||||
signer: &Signer,
|
||||
other_params: <T::ExtrinsicParams as ExtrinsicParams<T::Hash>>::OtherParams,
|
||||
other_params: <T::ExtrinsicParams as ExtrinsicParams<T>>::OtherParams,
|
||||
) -> Result<SubmittableExtrinsic<T, C>, Error>
|
||||
where
|
||||
Call: TxPayload,
|
||||
@@ -240,7 +235,7 @@ where
|
||||
where
|
||||
Call: TxPayload,
|
||||
Signer: SignerT<T>,
|
||||
<T::ExtrinsicParams as ExtrinsicParams<T::Hash>>::OtherParams: Default,
|
||||
<T::ExtrinsicParams as ExtrinsicParams<T>>::OtherParams: Default,
|
||||
{
|
||||
self.sign_and_submit_then_watch(call, signer, Default::default())
|
||||
.await
|
||||
@@ -254,7 +249,7 @@ where
|
||||
&self,
|
||||
call: &Call,
|
||||
signer: &Signer,
|
||||
other_params: <T::ExtrinsicParams as ExtrinsicParams<T::Hash>>::OtherParams,
|
||||
other_params: <T::ExtrinsicParams as ExtrinsicParams<T>>::OtherParams,
|
||||
) -> Result<TxProgress<T, C>, Error>
|
||||
where
|
||||
Call: TxPayload,
|
||||
@@ -284,7 +279,7 @@ where
|
||||
where
|
||||
Call: TxPayload,
|
||||
Signer: SignerT<T>,
|
||||
<T::ExtrinsicParams as ExtrinsicParams<T::Hash>>::OtherParams: Default,
|
||||
<T::ExtrinsicParams as ExtrinsicParams<T>>::OtherParams: Default,
|
||||
{
|
||||
self.sign_and_submit(call, signer, Default::default()).await
|
||||
}
|
||||
@@ -301,7 +296,7 @@ where
|
||||
&self,
|
||||
call: &Call,
|
||||
signer: &Signer,
|
||||
other_params: <T::ExtrinsicParams as ExtrinsicParams<T::Hash>>::OtherParams,
|
||||
other_params: <T::ExtrinsicParams as ExtrinsicParams<T>>::OtherParams,
|
||||
) -> Result<T::Hash, Error>
|
||||
where
|
||||
Call: TxPayload,
|
||||
|
||||
@@ -419,7 +419,6 @@ mod test {
|
||||
|
||||
use crate::{
|
||||
client::{OfflineClientT, OnlineClientT},
|
||||
config::{extrinsic_params::BaseExtrinsicParams, polkadot::PlainTip, WithExtrinsicParams},
|
||||
error::RpcError,
|
||||
rpc::{types::SubstrateTxStatus, RpcSubscription, Subscription},
|
||||
tx::TxProgress,
|
||||
@@ -429,10 +428,7 @@ mod test {
|
||||
use serde_json::value::RawValue;
|
||||
|
||||
type MockTxProgress = TxProgress<SubstrateConfig, MockClient>;
|
||||
type MockHash = <WithExtrinsicParams<
|
||||
SubstrateConfig,
|
||||
BaseExtrinsicParams<SubstrateConfig, PlainTip>,
|
||||
> as Config>::Hash;
|
||||
type MockHash = <SubstrateConfig as Config>::Hash;
|
||||
type MockSubstrateTxStatus = SubstrateTxStatus<MockHash, MockHash>;
|
||||
|
||||
/// a mock client to satisfy trait bounds in tests
|
||||
@@ -441,21 +437,21 @@ mod test {
|
||||
|
||||
impl OfflineClientT<SubstrateConfig> for MockClient {
|
||||
fn metadata(&self) -> crate::Metadata {
|
||||
panic!("just a mock impl to satisfy trait bounds")
|
||||
unimplemented!("just a mock impl to satisfy trait bounds")
|
||||
}
|
||||
|
||||
fn genesis_hash(&self) -> <SubstrateConfig as crate::Config>::Hash {
|
||||
panic!("just a mock impl to satisfy trait bounds")
|
||||
fn genesis_hash(&self) -> MockHash {
|
||||
unimplemented!("just a mock impl to satisfy trait bounds")
|
||||
}
|
||||
|
||||
fn runtime_version(&self) -> crate::rpc::types::RuntimeVersion {
|
||||
panic!("just a mock impl to satisfy trait bounds")
|
||||
unimplemented!("just a mock impl to satisfy trait bounds")
|
||||
}
|
||||
}
|
||||
|
||||
impl OnlineClientT<SubstrateConfig> for MockClient {
|
||||
fn rpc(&self) -> &crate::rpc::Rpc<SubstrateConfig> {
|
||||
panic!("just a mock impl to satisfy trait bounds")
|
||||
unimplemented!("just a mock impl to satisfy trait bounds")
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,92 @@
|
||||
// Copyright 2019-2023 Parity Technologies (UK) Ltd.
|
||||
// This file is dual-licensed as Apache-2.0 or GPL-3.0.
|
||||
// see LICENSE for license details.
|
||||
|
||||
// Dev note: This and related bits taken from `sp_runtime::generic::Era`
|
||||
/// An era to describe the longevity of a transaction.
|
||||
#[derive(PartialEq, Default, Eq, Clone, Copy, Debug, serde::Serialize, serde::Deserialize)]
|
||||
pub enum Era {
|
||||
/// The transaction is valid forever. The genesis hash must be present in the signed content.
|
||||
#[default]
|
||||
Immortal,
|
||||
|
||||
/// The transaction will expire. Use [`Era::mortal`] to construct this with correct values.
|
||||
///
|
||||
/// When used on `FRAME`-based runtimes, `period` cannot exceed `BlockHashCount` parameter
|
||||
/// of `system` module.
|
||||
Mortal {
|
||||
/// The number of blocks that the tx will be valid for after the checkpoint block
|
||||
/// hash found in the signer payload.
|
||||
period: u64,
|
||||
/// The phase in the period that this transaction's lifetime begins (and, importantly,
|
||||
/// implies which block hash is included in the signature material). If the `period` is
|
||||
/// greater than 1 << 12, then it will be a factor of the times greater than 1<<12 that
|
||||
/// `period` is.
|
||||
phase: u64,
|
||||
},
|
||||
}
|
||||
|
||||
// E.g. with period == 4:
|
||||
// 0 10 20 30 40
|
||||
// 0123456789012345678901234567890123456789012
|
||||
// |...|
|
||||
// authored -/ \- expiry
|
||||
// phase = 1
|
||||
// n = Q(current - phase, period) + phase
|
||||
impl Era {
|
||||
/// Create a new era based on a period (which should be a power of two between 4 and 65536
|
||||
/// inclusive) and a block number on which it should start (or, for long periods, be shortly
|
||||
/// after the start).
|
||||
///
|
||||
/// If using `Era` in the context of `FRAME` runtime, make sure that `period`
|
||||
/// does not exceed `BlockHashCount` parameter passed to `system` module, since that
|
||||
/// prunes old blocks and renders transactions immediately invalid.
|
||||
pub fn mortal(period: u64, current: u64) -> Self {
|
||||
let period = period
|
||||
.checked_next_power_of_two()
|
||||
.unwrap_or(1 << 16)
|
||||
.clamp(4, 1 << 16);
|
||||
let phase = current % period;
|
||||
let quantize_factor = (period >> 12).max(1);
|
||||
let quantized_phase = phase / quantize_factor * quantize_factor;
|
||||
|
||||
Self::Mortal {
|
||||
period,
|
||||
phase: quantized_phase,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Both copied from `sp_runtime::generic::Era`; this is the wire interface and so
|
||||
// it's really the most important bit here.
|
||||
impl codec::Encode for Era {
|
||||
fn encode_to<T: codec::Output + ?Sized>(&self, output: &mut T) {
|
||||
match self {
|
||||
Self::Immortal => output.push_byte(0),
|
||||
Self::Mortal { period, phase } => {
|
||||
let quantize_factor = (*period >> 12).max(1);
|
||||
let encoded = (period.trailing_zeros() - 1).clamp(1, 15) as u16
|
||||
| ((phase / quantize_factor) << 4) as u16;
|
||||
encoded.encode_to(output);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
impl codec::Decode for Era {
|
||||
fn decode<I: codec::Input>(input: &mut I) -> Result<Self, codec::Error> {
|
||||
let first = input.read_byte()?;
|
||||
if first == 0 {
|
||||
Ok(Self::Immortal)
|
||||
} else {
|
||||
let encoded = first as u64 + ((input.read_byte()? as u64) << 8);
|
||||
let period = 2 << (encoded % (1 << 4));
|
||||
let quantize_factor = (period >> 12).max(1);
|
||||
let phase = (encoded >> 4) * quantize_factor;
|
||||
if period >= 4 && phase < period {
|
||||
Ok(Self::Mortal { period, phase })
|
||||
} else {
|
||||
Err("Invalid period and phase".into())
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -6,6 +6,7 @@
|
||||
|
||||
mod account_id;
|
||||
pub mod bits;
|
||||
mod era;
|
||||
mod multi_address;
|
||||
mod multi_signature;
|
||||
mod static_type;
|
||||
@@ -16,6 +17,7 @@ use codec::{Compact, Decode, Encode};
|
||||
use derivative::Derivative;
|
||||
|
||||
pub use account_id::AccountId32;
|
||||
pub use era::Era;
|
||||
pub use multi_address::MultiAddress;
|
||||
pub use multi_signature::MultiSignature;
|
||||
pub use static_type::Static;
|
||||
|
||||
Reference in New Issue
Block a user