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.
@@ -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,
)
}
}
+45 -241
View File
@@ -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
View File
@@ -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 {
+3 -30
View File
@@ -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>;
+453
View File
@@ -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,
}
}
+3 -40
View File
@@ -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)]
+10
View File
@@ -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
View File
@@ -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,
+6 -10
View File
@@ -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")
}
}
+92
View File
@@ -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())
}
}
}
}
+2
View File
@@ -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;