From a0ebe465a5bbebcb801f795a1b9ed98755e296f3 Mon Sep 17 00:00:00 2001 From: James Wilson Date: Thu, 3 Aug 2023 17:29:42 +0100 Subject: [PATCH 01/10] WIP new SignedExtensions --- subxt/src/config/mod.rs | 2 + subxt/src/config/signed_extensions.rs | 534 ++++++++++++++++++++++++++ subxt/src/error/mod.rs | 6 + 3 files changed, 542 insertions(+) create mode 100644 subxt/src/config/signed_extensions.rs diff --git a/subxt/src/config/mod.rs b/subxt/src/config/mod.rs index 77fb73001e..30118ad0ab 100644 --- a/subxt/src/config/mod.rs +++ b/subxt/src/config/mod.rs @@ -8,6 +8,8 @@ //! default Substrate node implementation, and [`PolkadotConfig`] for a //! Polkadot node. +pub mod signed_extensions; + pub mod extrinsic_params; pub mod polkadot; pub mod substrate; diff --git a/subxt/src/config/signed_extensions.rs b/subxt/src/config/signed_extensions.rs new file mode 100644 index 0000000000..d5b683e937 --- /dev/null +++ b/subxt/src/config/signed_extensions.rs @@ -0,0 +1,534 @@ +// 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 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 ([`BaseSignedExtensions`]) which is +//! used by the provided Substrate and Polkadot configuration. + +use crate::{client::OfflineClientT, Config}; +use crate::config::substrate::Era; +use codec::{Compact, Encode}; +use core::fmt::Debug; +use std::collections::HashMap; + +/// An error that can be emitted when trying to construct +/// extrinsic parameters. +#[derive(thiserror::Error, Debug)] +pub enum ExtrinsicParamsError { + /// A signed extension was encountered that we don't know about. + #[error("Unknown signed extension: {0}")] + UnknownSignedExtension(String) +} + +impl From 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. +/// Tuples of [`SignedExtension`]'s automatically implement this. +pub trait ExtrinsicParams: 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; + + /// The type of error returned from [`ExtrinsicParams::new()`]. + type Error: Into; + + /// Construct a new instance of our [`ExtrinsicParams`] + fn new>( + nonce: u64, + client: Client, + other_params: Self::OtherParams, + ) -> Result; +} + +/// 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; many signed extensions for +/// instance will only encode one or the other. +pub trait ExtrinsicParamsEncoder: Debug + '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) {} + + /// 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) {} +} + +/// 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: ExtrinsicParams { + /// 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 ExtrinsicParams for CheckSpecVersion { + type OtherParams = (); + type Error = std::convert::Infallible; + + fn new>( + _nonce: u64, + client: Client, + _other_params: Self::OtherParams, + ) -> Result { + Ok(CheckSpecVersion(client.runtime_version().spec_version)) + } +} + +impl ExtrinsicParamsEncoder for CheckSpecVersion { + fn encode_additional_to(&self, v: &mut Vec) { + self.0.encode_to(v); + } +} + +impl SignedExtension for CheckSpecVersion { + const NAME: &'static str = "CheckSpecVersion"; +} + +/// The [`CheckNonce`] signed extension. +#[derive(Debug)] +pub struct CheckNonce(Compact); + +impl ExtrinsicParams for CheckNonce { + type OtherParams = (); + type Error = std::convert::Infallible; + + fn new>( + nonce: u64, + _client: Client, + _other_params: Self::OtherParams, + ) -> Result { + Ok(CheckNonce(Compact(nonce))) + } +} + +impl ExtrinsicParamsEncoder for CheckNonce { + fn encode_extra_to(&self, v: &mut Vec) { + self.0.encode_to(v); + } +} + +impl SignedExtension for CheckNonce { + const NAME: &'static str = "CheckNonce"; +} + +/// The [`CheckTxVersion`] signed extension. +#[derive(Debug)] +pub struct CheckTxVersion(u32); + +impl ExtrinsicParams for CheckTxVersion { + type OtherParams = (); + type Error = std::convert::Infallible; + + fn new>( + _nonce: u64, + client: Client, + _other_params: Self::OtherParams, + ) -> Result { + Ok(CheckTxVersion(client.runtime_version().transaction_version)) + } +} + +impl ExtrinsicParamsEncoder for CheckTxVersion { + fn encode_additional_to(&self, v: &mut Vec) { + self.0.encode_to(v); + } +} + +impl SignedExtension for CheckTxVersion { + const NAME: &'static str = "CheckTxVersion"; +} + +/// The [`CheckGenesis`] signed extension. +pub struct CheckGenesis(T::Hash); + +impl std::fmt::Debug for CheckGenesis { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.debug_tuple("CheckGenesis").field(&self.0).finish() + } +} + +impl ExtrinsicParams for CheckGenesis { + type OtherParams = (); + type Error = std::convert::Infallible; + + fn new>( + _nonce: u64, + client: Client, + _other_params: Self::OtherParams, + ) -> Result { + Ok(CheckGenesis(client.genesis_hash())) + } +} + +impl ExtrinsicParamsEncoder for CheckGenesis { + fn encode_additional_to(&self, v: &mut Vec) { + self.0.encode_to(v); + } +} + +impl SignedExtension for CheckGenesis { + const NAME: &'static str = "CheckGenesis"; +} + +/// The [`CheckMortality`] signed extension. +pub struct CheckMortality { + era: Era, + checkpoint: T::Hash +} + +/// Parameters to configure the [`CheckMortality`] signed extension. +pub struct CheckMortalityParams { + era: Era, + checkpoint: Option +} + +impl CheckMortalityParams { + /// 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 std::fmt::Debug for CheckMortality { + 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 ExtrinsicParams for CheckMortality { + type OtherParams = CheckMortalityParams; + type Error = std::convert::Infallible; + + fn new>( + _nonce: u64, + client: Client, + other_params: Self::OtherParams, + ) -> Result { + Ok(CheckMortality { + era: other_params.era, + checkpoint: other_params.checkpoint.unwrap_or(client.genesis_hash()) + }) + } +} + +impl ExtrinsicParamsEncoder for CheckMortality { + fn encode_extra_to(&self, v: &mut Vec) { + self.era.encode_to(v); + } + fn encode_additional_to(&self, v: &mut Vec) { + self.checkpoint.encode_to(v) + } +} + +impl SignedExtension for CheckMortality { + const NAME: &'static str = "CheckMortality"; +} + +/// The [`ChargeAssetTxPayment`] signed extension. +#[derive(Debug)] +pub struct ChargeAssetTxPayment { + tip: Compact, + asset_id: Option +} + +/// Parameters to configure the [`ChargeAssetTxPayment`] signed extension. +pub struct ChargeAssetTxPaymentParams { + tip: u128, + asset_id: Option +} + +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 ExtrinsicParams for ChargeAssetTxPayment { + type OtherParams = ChargeAssetTxPaymentParams; + type Error = std::convert::Infallible; + + fn new>( + _nonce: u64, + _client: Client, + other_params: Self::OtherParams, + ) -> Result { + 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) { + (self.tip, self.asset_id).encode_to(v); + } +} + +impl SignedExtension for ChargeAssetTxPayment { + const NAME: &'static str = "ChargeAssetTxPayment"; +} + +/// The [`ChargeAssetTxPayment`] signed extension. +#[derive(Debug)] +pub struct ChargeTransactionPayment { + tip: Compact, +} + +/// Parameters to configure the [`ChargeTransactionPayment`] signed extension. +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 ExtrinsicParams for ChargeTransactionPayment { + type OtherParams = ChargeTransactionPaymentParams; + type Error = std::convert::Infallible; + + fn new>( + _nonce: u64, + _client: Client, + other_params: Self::OtherParams, + ) -> Result { + Ok(ChargeTransactionPayment { + tip: Compact(other_params.tip), + }) + } +} + +impl ExtrinsicParamsEncoder for ChargeTransactionPayment { + fn encode_extra_to(&self, v: &mut Vec) { + self.tip.encode_to(v); + } +} + +impl SignedExtension 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 DynamicExtrinsicParams { + params: Vec>, + _marker: std::marker::PhantomData<(T, Params)> +} + +impl std::fmt::Debug for DynamicExtrinsicParams { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.debug_struct("DynamicExtrinsicParams") + .field("params", &self.params) + .field("_marker", &"std::marker::PhantomData") + .finish() + } +} + +/// This accepts a tuple of [`SignedExtension`]s, and will ignore the metadata of the chain we're +/// connected to and simply encode exactly the additional and extra data of each of the extensions +/// in the order that they are provided. Prefer to use [`DynamicExtrinsicParams`]. +pub struct StaticExtrinsicParams { + params: Params, + _marker: std::marker::PhantomData +} + +impl std::fmt::Debug for StaticExtrinsicParams { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.debug_struct("StaticExtrinsicParams") + .field("params", &"") + .field("_marker", &"std::marker::PhantomData") + .finish() + } +} + +macro_rules! impl_tuples { + ($($ident:ident $index:tt),+) => { + // We do some magic when the tuple is wrapped in DynamicExtrinsicParams. 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 ExtrinsicParams for DynamicExtrinsicParams + where + T: Config, + $($ident: SignedExtension,)+ + { + type OtherParams = ($($ident::OtherParams,)+); + type Error = ExtrinsicParamsError; + + fn new>( + nonce: u64, + client: Client, + other_params: Self::OtherParams, + ) -> Result { + // First, push encoders to map as we are given them: + let mut map = HashMap::new(); + $({ + let e: Box + = 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(DynamicExtrinsicParams { + params: params, + _marker: std::marker::PhantomData + }) + } + } + + impl ExtrinsicParamsEncoder for DynamicExtrinsicParams + where + T: Config, + $($ident: SignedExtension,)+ + { + fn encode_extra_to(&self, v: &mut Vec) { + for ext in &self.params { + ext.encode_extra_to(v); + } + } + fn encode_additional_to(&self, v: &mut Vec) { + for ext in &self.params { + ext.encode_additional_to(v); + } + } + } + + // If we know exactly the structure of signed extensions that we need, and we don't want to + // use the node metadata to decide on which to encode, then we can instead provide a StaticExtrinsicParams + // wrapping a tuple of signed extensions. Here, using a tuple of 1 signed extension has the same behaviour + // as the signed extension on its own. Using `DynamicExtrinsicParams` is strongly preferred, but this is a + // little faster if you know precisely the signed extensions that the chain needs. + impl ExtrinsicParams for StaticExtrinsicParams + where + T: Config, + $($ident: SignedExtension,)+ + { + type OtherParams = ($($ident::OtherParams,)+); + type Error = ExtrinsicParamsError; + + fn new>( + nonce: u64, + client: Client, + other_params: Self::OtherParams, + ) -> Result { + Ok(StaticExtrinsicParams { + params: ( + $($ident::new(nonce, client.clone(), other_params.$index).map_err(Into::into)?,)+ + ), + _marker: std::marker::PhantomData + }) + } + } + + impl ExtrinsicParamsEncoder for StaticExtrinsicParams + where + T: Config, + $($ident: SignedExtension,)+ + { + fn encode_extra_to(&self, v: &mut Vec) { + $( + self.params.$index.encode_extra_to(v); + )+ + } + fn encode_additional_to(&self, v: &mut Vec) { + $( + self.params.$index.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); +}; + +/// Tries to encode an empty tuple into the type given. Returns true if this +/// succeeds, and thus if the type given is empty (and compatible with an empty tuple) +fn is_type_empty(type_id: u32, types: &scale_info::PortableRegistry) -> bool { + use scale_encode::EncodeAsType; + ().encode_as_type(type_id, types).is_ok() +} diff --git a/subxt/src/error/mod.rs b/subxt/src/error/mod.rs index f6d5687cc9..8594b8e649 100644 --- a/subxt/src/error/mod.rs +++ b/subxt/src/error/mod.rs @@ -88,6 +88,12 @@ impl From for Error { } } +impl From 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)] From fc004d54be067308e1b1d82fff43ccac767688e3 Mon Sep 17 00:00:00 2001 From: James Wilson Date: Fri, 4 Aug 2023 17:27:22 +0100 Subject: [PATCH 02/10] Integrate new signex extension stuff, update docs, remove Static wrapper --- subxt/examples/setup_config_custom.rs | 130 ++++----- .../examples/setup_config_signed_extension.rs | 99 +++++++ subxt/examples/tx_with_params.rs | 11 +- subxt/src/book/setup/config.rs | 82 +++--- subxt/src/config/default_extrinsic_params.rs | 155 ++++++++++ subxt/src/config/era.rs | 91 ++++++ subxt/src/config/extrinsic_params.rs | 274 +++--------------- subxt/src/config/mod.rs | 40 +-- subxt/src/config/polkadot.rs | 33 +-- subxt/src/config/signed_extensions.rs | 249 ++++++---------- subxt/src/config/substrate.rs | 43 +-- subxt/src/error/mod.rs | 4 + subxt/src/tx/tx_client.rs | 43 ++- subxt/src/tx/tx_progress.rs | 16 +- 14 files changed, 627 insertions(+), 643 deletions(-) create mode 100644 subxt/examples/setup_config_signed_extension.rs create mode 100644 subxt/src/config/default_extrinsic_params.rs create mode 100644 subxt/src/config/era.rs diff --git a/subxt/examples/setup_config_custom.rs b/subxt/examples/setup_config_custom.rs index d5edcc0490..4ba8c68e3a 100644 --- a/subxt/examples/setup_config_custom.rs +++ b/subxt/examples/setup_config_custom.rs @@ -1,95 +1,95 @@ use codec::Encode; -use primitive_types::H256; -use subxt::config::{Config, ExtrinsicParams}; +use subxt::client::OfflineClientT; +use subxt::config::{Config, ExtrinsicParams, ExtrinsicParamsEncoder}; +use subxt_signer::sr25519::dev; + +#[subxt::subxt(runtime_metadata_path = "../artifacts/polkadot_metadata_small.scale")] +pub mod runtime {} // We don't need to construct this at runtime, // so an empty enum is appropriate: -pub enum StatemintConfig {} +pub enum CustomConfig {} -impl Config for StatemintConfig { +impl Config for CustomConfig { type Hash = subxt::utils::H256; type AccountId = subxt::utils::AccountId32; type Address = subxt::utils::MultiAddress; type Signature = subxt::utils::MultiSignature; type Hasher = subxt::config::substrate::BlakeTwo256; type Header = subxt::config::substrate::SubstrateHeader; - type ExtrinsicParams = StatemintExtrinsicParams; + type ExtrinsicParams = CustomExtrinsicParams; } -#[derive(Encode, Debug, Clone, Eq, PartialEq)] -pub struct StatemintExtrinsicParams { - extra_params: StatemintExtraParams, - additional_params: StatemintAdditionalParams, +// This represents some arbitrary (and nonsensical) custom parameters that +// will be attached to transaction extra and additional payloads: +pub struct CustomExtrinsicParams { + genesis_hash: T::Hash, + tip: u128, + foo: bool, } -impl ExtrinsicParams for StatemintExtrinsicParams { - // We need these additional values that aren't otherwise - // provided. Calls like api.tx().sign_and_submit_then_watch() - // allow the user to provide an instance of these, so it's wise - // to give this a nicer interface in reality: - type OtherParams = ( - sp_core::H256, - sp_runtime::generic::Era, - ChargeAssetTxPayment, - ); - - // Gather together all of the params we will need to encode: - fn new( - spec_version: u32, - tx_version: u32, - nonce: u64, - genesis_hash: H256, - other_params: Self::OtherParams, - ) -> Self { - let (mortality_hash, era, charge) = other_params; - - let extra_params = StatemintExtraParams { era, nonce, charge }; - let additional_params = StatemintAdditionalParams { - spec_version, - tx_version, - genesis_hash, - mortality_hash, - }; +// We can provide a "pretty" interface to allow users to provide these: +#[derive(Default)] +pub struct CustomExtrinsicParamsBuilder { + tip: u128, + foo: bool, +} - Self { - extra_params, - additional_params, - } +impl CustomExtrinsicParamsBuilder { + pub fn new() -> Self { + Default::default() } - - // Encode the relevant params when asked: - fn encode_extra_to(&self, v: &mut Vec) { - self.extra_params.encode_to(v); + pub fn tip(mut self, value: u128) -> Self { + self.tip = value; + self } - fn encode_additional_to(&self, v: &mut Vec) { - self.additional_params.encode_to(v); + pub fn enable_foo(mut self) -> Self { + self.foo = true; + self } } -#[derive(Encode, Debug, Clone, Eq, PartialEq)] -pub struct StatemintExtraParams { - era: sp_runtime::generic::Era, - nonce: u64, - charge: ChargeAssetTxPayment, -} +// Describe how to fetch and then encode the params: +impl ExtrinsicParams for CustomExtrinsicParams { + type OtherParams = CustomExtrinsicParamsBuilder; + type Error = std::convert::Infallible; -#[derive(Encode, Debug, Clone, Eq, PartialEq)] -pub struct ChargeAssetTxPayment { - #[codec(compact)] - tip: u128, - asset_id: Option, + // Gather together all of the params we will need to encode: + fn new>( + _nonce: u64, + client: Client, + other_params: Self::OtherParams, + ) -> Result { + Ok(Self { + genesis_hash: client.genesis_hash(), + tip: other_params.tip, + foo: other_params.foo, + }) + } } -#[derive(Encode, Debug, Clone, Eq, PartialEq)] -pub struct StatemintAdditionalParams { - spec_version: u32, - tx_version: u32, - genesis_hash: sp_core::H256, - mortality_hash: sp_core::H256, +// Encode the relevant params when asked: +impl ExtrinsicParamsEncoder for CustomExtrinsicParams { + fn encode_extra_to(&self, v: &mut Vec) { + (self.tip, self.foo).encode_to(v); + } + fn encode_additional_to(&self, v: &mut Vec) { + self.genesis_hash.encode_to(v) + } } #[tokio::main] async fn main() { // With the config defined, it can be handed to Subxt as follows: - let _client_fut = subxt::OnlineClient::::new(); + let client = subxt::OnlineClient::::new().await.unwrap(); + + let tx_payload = runtime::tx().system().remark(b"Hello".to_vec()); + + // Build your custom "OtherParams": + let tx_config = CustomExtrinsicParamsBuilder::new().tip(1234).enable_foo(); + + // And provide them when submitting a transaction: + let _ = client + .tx() + .sign_and_submit_then_watch(&tx_payload, &dev::alice(), tx_config); } diff --git a/subxt/examples/setup_config_signed_extension.rs b/subxt/examples/setup_config_signed_extension.rs new file mode 100644 index 0000000000..f1e0a25f08 --- /dev/null +++ b/subxt/examples/setup_config_signed_extension.rs @@ -0,0 +1,99 @@ +use codec::Encode; +use subxt::client::OfflineClientT; +use subxt::config::signed_extensions; +use subxt::config::{ + Config, DefaultExtrinsicParamsBuilder, ExtrinsicParams, ExtrinsicParamsEncoder, +}; +use subxt_signer::sr25519::dev; + +#[subxt::subxt(runtime_metadata_path = "../artifacts/polkadot_metadata_small.scale")] +pub mod runtime {} + +// We don't need to construct this at runtime, +// so an empty enum is appropriate: +pub enum CustomConfig {} + +impl Config for CustomConfig { + type Hash = subxt::utils::H256; + type AccountId = subxt::utils::AccountId32; + type Address = subxt::utils::MultiAddress; + type Signature = subxt::utils::MultiSignature; + type Hasher = subxt::config::substrate::BlakeTwo256; + type Header = subxt::config::substrate::SubstrateHeader; + type ExtrinsicParams = signed_extensions::AnyOf< + Self, + ( + // Load in all of the existing signed extensions: + signed_extensions::CheckSpecVersion, + signed_extensions::CheckTxVersion, + signed_extensions::CheckNonce, + signed_extensions::CheckGenesis, + signed_extensions::CheckMortality, + signed_extensions::ChargeAssetTxPayment, + signed_extensions::ChargeTransactionPayment, + // And add a new one of our own: + CustomSignedExtension, + ), + >; +} + +// Our custom signed extension doesn't do much: +pub struct CustomSignedExtension; + +// Give the extension a name; this allows [`AnyOf`] to look it +// up in the chain metadata in order to know when and if to use it. +impl signed_extensions::SignedExtension for CustomSignedExtension { + const NAME: &'static str = "CustomSignedExtension"; +} + +// Gather together any params we need for our signed extension, here none. +impl ExtrinsicParams for CustomSignedExtension { + type OtherParams = (); + type Error = std::convert::Infallible; + + fn new>( + _nonce: u64, + _client: Client, + _other_params: Self::OtherParams, + ) -> Result { + Ok(CustomSignedExtension) + } +} + +// Encode whatever the extension needs to provide when asked: +impl ExtrinsicParamsEncoder for CustomSignedExtension { + fn encode_extra_to(&self, v: &mut Vec) { + "Hello".encode_to(v); + } + fn encode_additional_to(&self, v: &mut Vec) { + true.encode_to(v) + } +} + +// When composing a tuple of signed extensions, the user parameters we need must +// be able to convert `Into` a tuple of corresponding `OtherParams`. Here, we just +// "hijack" the default param builder, but add the `OtherParams` (`()`) for our +// new signed extension at the end, to make the types line up. IN reality you may wish +// to construct an entirely new interface to provide the relevant `OtherParams`. +pub fn custom( + params: DefaultExtrinsicParamsBuilder, +) -> <::ExtrinsicParams as ExtrinsicParams>::OtherParams { + let (a, b, c, d, e, f, g) = params.raw(); + (a, b, c, d, e, f, g, ()) +} + +#[tokio::main] +async fn main() { + // With the config defined, it can be handed to Subxt as follows: + let client = subxt::OnlineClient::::new().await.unwrap(); + + let tx_payload = runtime::tx().system().remark(b"Hello".to_vec()); + + // Configure the tx params: + let tx_config = DefaultExtrinsicParamsBuilder::new().tip(1234); + + // And provide them when submitting a transaction: + let _ = client + .tx() + .sign_and_submit_then_watch(&tx_payload, &dev::alice(), custom(tx_config)); +} diff --git a/subxt/examples/tx_with_params.rs b/subxt/examples/tx_with_params.rs index f1295d0810..843ffd0b6b 100644 --- a/subxt/examples/tx_with_params.rs +++ b/subxt/examples/tx_with_params.rs @@ -1,4 +1,4 @@ -use subxt::config::polkadot::{Era, PlainTip, PolkadotExtrinsicParamsBuilder as Params}; +use subxt::config::polkadot::PolkadotExtrinsicParamsBuilder as Params; use subxt::{OnlineClient, PolkadotConfig}; use subxt_signer::sr25519::dev; @@ -14,10 +14,11 @@ async fn main() -> Result<(), Box> { let dest = dev::bob().public_key().into(); let tx = polkadot::tx().balances().transfer(dest, 10_000); - // Configure the transaction parameters; for Polkadot the tip and era: - let tx_params = Params::new() - .tip(PlainTip::new(1_000)) - .era(Era::Immortal, api.genesis_hash()); + let latest_block = api.blocks().at_latest().await?; + + // Configure the transaction parameters; we give a small tip and set the + // transaction to live for 32 blocks from the `latest_block` above. + let tx_params = Params::new().tip(1_000).mortal(latest_block.header(), 32); // submit the transaction: let from = dev::alice(); diff --git a/subxt/src/book/setup/config.rs b/subxt/src/book/setup/config.rs index 85cbf84dfa..bfab118bb8 100644 --- a/subxt/src/book/setup/config.rs +++ b/subxt/src/book/setup/config.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`. //! 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](sp_runtime::MultiAddress` +//! - Address: `sp_runtime::MultiAddress` //! - 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 @@ -79,8 +73,19 @@ //! 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. //! -//! 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 +//! In most cases, the default [`crate::config::DefaultExtrinsicParams`] type will work here; 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 ensure that data is given in the correct order, and return an +//! error if the chain is using signed extensions that are unknown to it which require data. +//! +//! In the event that novel signed extensions are in use (or if you just wish to provide a different interface for users to +//! configure transactions), you can either implement an interface for the new signed extension and add it to the list of +//! configured ones, or you can create an entirely custom implementation of [`crate::config::ExtrinsicParams`] with whatever +//! arbitrary behaviour you require. +//! +//! ### Finding 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`]). //! @@ -122,39 +127,26 @@ //! 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. //! -//! 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: +//! ### Implementing and adding new signed extensions to the config. +//! +//! As it happens, Statemint only uses signed extensions that Subxt is natively aware of via [`crate::config::DefaultExtrinsicParams`], +//! and so the default config will work just fine. If it did not, then you could implement [`crate::config::signed_extensions::SignedExtension`] +//! on some custom type. This could then be placed into a new set of signed extensions, and (optionally) some novel interface could be +//! constructed to make it easy for users to configure them when submitting transactions. +//! +//! Let's see what this looks like: //! //! ```rust,ignore -#![doc = include_str ! ("../../../examples/setup_config_custom.rs")] +#![doc = include_str ! ("../../../examples/setup_config_signed_extension.rs")] //! ``` //! -//! ## Using [`PolkadotConfig`](crate::PolkadotConfig) and [`SubstrateConfig`](crate::SubstrateConfig) values to compose a Config +//! ### Implementing [`crate::config::ExtrinsicParams`] from scratch. //! -//! 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. +//! 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. This should be somewhat of a last resort. //! -//! 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: +//! Let's see what this looks like (this config won't work on any real node): //! //! ```rust,ignore -//! use subxt::{Config, PolkadotConfig, SubstrateConfig}; -//! -//! pub enum StatemintConfig {} -//! -//! impl Config for StatemintConfig { -//! type Hash = ::Hash; -//! type AccountId = ::AccountId; -//! type Address = ::Address; -//! type Signature = ::Signature; -//! type Hasher = ::Hasher; -//! type Header = ::Header; -//! // this is the only difference to the PolkadotConfig: -//! type ExtrinsicParams = ::ExtrinsicParams; -//! } +#![doc = include_str ! ("../../../examples/setup_config_custom.rs")] //! ``` -//! -//! When the types are very similar, building a custom config like this is much simpler than implementing one from scratch. diff --git a/subxt/src/config/default_extrinsic_params.rs b/subxt/src/config/default_extrinsic_params.rs new file mode 100644 index 0000000000..c8acb319e2 --- /dev/null +++ b/subxt/src/config/default_extrinsic_params.rs @@ -0,0 +1,155 @@ +// 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 = signed_extensions::AnyOf< + T, + ( + signed_extensions::CheckSpecVersion, + signed_extensions::CheckTxVersion, + signed_extensions::CheckNonce, + signed_extensions::CheckGenesis, + signed_extensions::CheckMortality, + 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 { + mortality_checkpoint_hash: Option, + mortality_checkpoint_number: u64, + mortality_period: u64, + tip: u128, + tip_of: u128, + tip_of_asset_id: Option, +} + +impl DefaultExtrinsicParamsBuilder { + /// Configure new extrinsic params. We default to providing no tip + /// and using an immortal transaction unless otherwise configured + pub fn new() -> Self { + Self { + mortality_checkpoint_hash: None, + mortality_checkpoint_number: 0, + mortality_period: 0, + tip: 0, + tip_of: 0, + tip_of_asset_id: None, + } + } + + /// 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_checkpoint_hash = Some(from_block.hash()); + self.mortality_checkpoint_number = from_block.number().into(); + self.mortality_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_checkpoint_hash = Some(from_block_hash); + self.mortality_checkpoint_number = from_block_number; + self.mortality_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 + } + + /// Return the "raw" params as required. This doesn't need to be called in normal usage. + pub fn raw(self) -> OtherParams { + let check_mortality_params = if let Some(checkpoint_hash) = self.mortality_checkpoint_hash { + signed_extensions::CheckMortalityParams::mortal( + self.mortality_period, + self.mortality_checkpoint_number, + 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, + ) + } +} + +type OtherParams = ( + (), + (), + (), + (), + signed_extensions::CheckMortalityParams, + signed_extensions::ChargeAssetTxPaymentParams, + signed_extensions::ChargeTransactionPaymentParams, +); + +impl From> for OtherParams { + fn from(v: DefaultExtrinsicParamsBuilder) -> Self { + v.raw() + } +} + +// We have to manually write out `OtherParams` for some reason to avoid type errors in the `From` impl. +// So, here we ensure that `OtherParams` is equal to ` as ExtrinsicParams>::OtherParams`. +// We'll get a compile error if not. +#[allow(unused)] +fn assert_otherparams_eq() { + struct Ty(Inner); + fn assert_eq(t: Ty>) { + match t { + Ty::< as ExtrinsicParams>::OtherParams>(_) => {} + } + } +} diff --git a/subxt/src/config/era.rs b/subxt/src/config/era.rs new file mode 100644 index 0000000000..4e9ba7b231 --- /dev/null +++ b/subxt/src/config/era.rs @@ -0,0 +1,91 @@ +// 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, + + /// 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) + } +} + +// 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(&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(input: &mut I) -> Result { + 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()) + } + } + } +} diff --git a/subxt/src/config/extrinsic_params.rs b/subxt/src/config/extrinsic_params.rs index 6dc16ec196..b42d5142a4 100644 --- a/subxt/src/config/extrinsic_params.rs +++ b/subxt/src/config/extrinsic_params.rs @@ -4,263 +4,67 @@ //! 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 +//! implementation of the trait is provided ([`BaseSignedExtensions`]) which is //! used by the provided Substrate and Polkadot configuration. -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; + +impl From 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: Debug + 'static { +/// Tuples of [`SignedExtension`]'s automatically implement this. +pub trait ExtrinsicParams: 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; + /// The type of error returned from [`ExtrinsicParams::new()`]. + type Error: Into; + /// Construct a new instance of our [`ExtrinsicParams`] - fn new( - spec_version: u32, - tx_version: u32, + fn new>( nonce: u64, - genesis_hash: Hash, + client: Client, other_params: Self::OtherParams, - ) -> Self; + ) -> Result; +} +/// 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); + fn encode_extra_to(&self, _v: &mut Vec) {} /// 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); -} - -/// 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 { - /// 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, -} - -/// 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 { - era: Era, - mortality_checkpoint: Option, - tip: Tip, -} - -impl BaseExtrinsicParamsBuilder { - /// 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) -> Self { - self.tip = tip.into(); - self - } -} - -impl Default for BaseExtrinsicParamsBuilder { - fn default() -> Self { - Self { - era: Era::Immortal, - mortality_checkpoint: None, - tip: Tip::default(), - } - } -} - -impl ExtrinsicParams - for BaseExtrinsicParams -{ - type OtherParams = BaseExtrinsicParamsBuilder; - - 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) { - 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) { - ( - 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(&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(input: &mut I) -> Result { - 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) {} } diff --git a/subxt/src/config/mod.rs b/subxt/src/config/mod.rs index 30118ad0ab..b372f1541d 100644 --- a/subxt/src/config/mod.rs +++ b/subxt/src/config/mod.rs @@ -8,25 +8,29 @@ //! default Substrate node implementation, and [`PolkadotConfig`] for a //! Polkadot node. -pub mod signed_extensions; +mod default_extrinsic_params; +mod era; +mod extrinsic_params; -pub 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 @@ -55,9 +59,12 @@ pub trait Config: 'static { type Header: Debug + Header + Sync + Send + DeserializeOwned; /// This type defines the extrinsic extra and additional parameters. - type ExtrinsicParams: extrinsic_params::ExtrinsicParams; + type ExtrinsicParams: ExtrinsicParams; } +/// given some [`Config`], this return the other params needed for its `ExtrinsicParams`. +pub type OtherParamsFor = <::ExtrinsicParams as ExtrinsicParams>::OtherParams; + /// This represents the hasher used by a node to hash things like block headers /// and extrinsics. pub trait Hasher { @@ -90,25 +97,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> { - _marker: std::marker::PhantomData<(T, E)>, -} - -impl> Config - for WithExtrinsicParams -{ - 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 { diff --git a/subxt/src/config/polkadot.rs b/subxt/src/config/polkadot.rs index ba33dc8ac6..5fb3390483 100644 --- a/subxt/src/config/polkadot.rs +++ b/subxt/src/config/polkadot.rs @@ -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 = BaseExtrinsicParams; +pub type PolkadotExtrinsicParams = DefaultExtrinsicParams; /// A builder which leads to [`PolkadotExtrinsicParams`] being constructed. /// This is what you provide to methods like `sign_and_submit()`. -pub type PolkadotExtrinsicParamsBuilder = BaseExtrinsicParamsBuilder; - -// 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 for PlainTip { - fn from(n: u128) -> Self { - PlainTip::new(n) - } -} +pub type PolkadotExtrinsicParamsBuilder = DefaultExtrinsicParamsBuilder; diff --git a/subxt/src/config/signed_extensions.rs b/subxt/src/config/signed_extensions.rs index d5b683e937..33d80ecda0 100644 --- a/subxt/src/config/signed_extensions.rs +++ b/subxt/src/config/signed_extensions.rs @@ -7,65 +7,13 @@ //! implementation of the trait is provided ([`BaseSignedExtensions`]) which is //! used by the provided Substrate and Polkadot configuration. +use super::era::Era; +use super::extrinsic_params::{ExtrinsicParams, ExtrinsicParamsEncoder, ExtrinsicParamsError}; use crate::{client::OfflineClientT, Config}; -use crate::config::substrate::Era; use codec::{Compact, Encode}; use core::fmt::Debug; use std::collections::HashMap; -/// An error that can be emitted when trying to construct -/// extrinsic parameters. -#[derive(thiserror::Error, Debug)] -pub enum ExtrinsicParamsError { - /// A signed extension was encountered that we don't know about. - #[error("Unknown signed extension: {0}")] - UnknownSignedExtension(String) -} - -impl From 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. -/// Tuples of [`SignedExtension`]'s automatically implement this. -pub trait ExtrinsicParams: 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; - - /// The type of error returned from [`ExtrinsicParams::new()`]. - type Error: Into; - - /// Construct a new instance of our [`ExtrinsicParams`] - fn new>( - nonce: u64, - client: Client, - other_params: Self::OtherParams, - ) -> Result; -} - -/// 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; many signed extensions for -/// instance will only encode one or the other. -pub trait ExtrinsicParamsEncoder: Debug + '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) {} - - /// 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) {} -} - /// A single [`SignedExtension`] has a unique name, but is otherwise the /// same as [`ExtrinsicParams`] in describing how to encode the extra and /// additional data. @@ -79,7 +27,7 @@ pub trait SignedExtension: ExtrinsicParams { #[derive(Debug)] pub struct CheckSpecVersion(u32); -impl ExtrinsicParams for CheckSpecVersion { +impl ExtrinsicParams for CheckSpecVersion { type OtherParams = (); type Error = std::convert::Infallible; @@ -98,7 +46,7 @@ impl ExtrinsicParamsEncoder for CheckSpecVersion { } } -impl SignedExtension for CheckSpecVersion { +impl SignedExtension for CheckSpecVersion { const NAME: &'static str = "CheckSpecVersion"; } @@ -106,7 +54,7 @@ impl SignedExtension for CheckSpecVersion { #[derive(Debug)] pub struct CheckNonce(Compact); -impl ExtrinsicParams for CheckNonce { +impl ExtrinsicParams for CheckNonce { type OtherParams = (); type Error = std::convert::Infallible; @@ -125,7 +73,7 @@ impl ExtrinsicParamsEncoder for CheckNonce { } } -impl SignedExtension for CheckNonce { +impl SignedExtension for CheckNonce { const NAME: &'static str = "CheckNonce"; } @@ -133,7 +81,7 @@ impl SignedExtension for CheckNonce { #[derive(Debug)] pub struct CheckTxVersion(u32); -impl ExtrinsicParams for CheckTxVersion { +impl ExtrinsicParams for CheckTxVersion { type OtherParams = (); type Error = std::convert::Infallible; @@ -152,20 +100,20 @@ impl ExtrinsicParamsEncoder for CheckTxVersion { } } -impl SignedExtension for CheckTxVersion { +impl SignedExtension for CheckTxVersion { const NAME: &'static str = "CheckTxVersion"; } /// The [`CheckGenesis`] signed extension. pub struct CheckGenesis(T::Hash); -impl std::fmt::Debug for CheckGenesis { +impl std::fmt::Debug for CheckGenesis { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { f.debug_tuple("CheckGenesis").field(&self.0).finish() } } -impl ExtrinsicParams for CheckGenesis { +impl ExtrinsicParams for CheckGenesis { type OtherParams = (); type Error = std::convert::Infallible; @@ -178,43 +126,58 @@ impl ExtrinsicParams for CheckGenesis { } } -impl ExtrinsicParamsEncoder for CheckGenesis { +impl ExtrinsicParamsEncoder for CheckGenesis { fn encode_additional_to(&self, v: &mut Vec) { self.0.encode_to(v); } } -impl SignedExtension for CheckGenesis { +impl SignedExtension for CheckGenesis { const NAME: &'static str = "CheckGenesis"; } /// The [`CheckMortality`] signed extension. pub struct CheckMortality { era: Era, - checkpoint: T::Hash + checkpoint: T::Hash, } /// Parameters to configure the [`CheckMortality`] signed extension. pub struct CheckMortalityParams { era: Era, - checkpoint: Option + checkpoint: Option, } -impl CheckMortalityParams { +impl Default for CheckMortalityParams { + fn default() -> Self { + Self { + era: Default::default(), + checkpoint: Default::default(), + } + } +} + +impl CheckMortalityParams { /// 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) } + CheckMortalityParams { + era: Era::mortal(period, block_number), + checkpoint: Some(block_hash), + } } /// An immortal transaction. pub fn immortal() -> Self { - CheckMortalityParams { era: Era::Immortal, checkpoint: None } + CheckMortalityParams { + era: Era::Immortal, + checkpoint: None, + } } } -impl std::fmt::Debug for CheckMortality { +impl std::fmt::Debug for CheckMortality { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { f.debug_struct("CheckMortality") .field("era", &self.era) @@ -223,7 +186,7 @@ impl std::fmt::Debug for CheckMortality { } } -impl ExtrinsicParams for CheckMortality { +impl ExtrinsicParams for CheckMortality { type OtherParams = CheckMortalityParams; type Error = std::convert::Infallible; @@ -234,12 +197,12 @@ impl ExtrinsicParams for CheckMortality { ) -> Result { Ok(CheckMortality { era: other_params.era, - checkpoint: other_params.checkpoint.unwrap_or(client.genesis_hash()) + checkpoint: other_params.checkpoint.unwrap_or(client.genesis_hash()), }) } } -impl ExtrinsicParamsEncoder for CheckMortality { +impl ExtrinsicParamsEncoder for CheckMortality { fn encode_extra_to(&self, v: &mut Vec) { self.era.encode_to(v); } @@ -248,7 +211,7 @@ impl ExtrinsicParamsEncoder for CheckMortality { } } -impl SignedExtension for CheckMortality { +impl SignedExtension for CheckMortality { const NAME: &'static str = "CheckMortality"; } @@ -256,31 +219,41 @@ impl SignedExtension for CheckMortality { #[derive(Debug)] pub struct ChargeAssetTxPayment { tip: Compact, - asset_id: Option + asset_id: Option, } /// Parameters to configure the [`ChargeAssetTxPayment`] signed extension. +#[derive(Default)] pub struct ChargeAssetTxPaymentParams { tip: u128, - asset_id: Option + asset_id: Option, } impl ChargeAssetTxPaymentParams { /// Don't provide a tip to the extrinsic author. pub fn no_tip() -> Self { - ChargeAssetTxPaymentParams { tip: 0, asset_id: None } + 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 } + 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) } + ChargeAssetTxPaymentParams { + tip, + asset_id: Some(asset_id), + } } } -impl ExtrinsicParams for ChargeAssetTxPayment { +impl ExtrinsicParams for ChargeAssetTxPayment { type OtherParams = ChargeAssetTxPaymentParams; type Error = std::convert::Infallible; @@ -291,7 +264,7 @@ impl ExtrinsicParams for ChargeAssetTxPayment { ) -> Result { Ok(ChargeAssetTxPayment { tip: Compact(other_params.tip), - asset_id: other_params.asset_id + asset_id: other_params.asset_id, }) } } @@ -302,17 +275,18 @@ impl ExtrinsicParamsEncoder for ChargeAssetTxPayment { } } -impl SignedExtension for ChargeAssetTxPayment { +impl SignedExtension for ChargeAssetTxPayment { const NAME: &'static str = "ChargeAssetTxPayment"; } -/// The [`ChargeAssetTxPayment`] signed extension. +/// The [`ChargeTransactionPayment`] signed extension. #[derive(Debug)] pub struct ChargeTransactionPayment { tip: Compact, } /// Parameters to configure the [`ChargeTransactionPayment`] signed extension. +#[derive(Default)] pub struct ChargeTransactionPaymentParams { tip: u128, } @@ -328,7 +302,7 @@ impl ChargeTransactionPaymentParams { } } -impl ExtrinsicParams for ChargeTransactionPayment { +impl ExtrinsicParams for ChargeTransactionPayment { type OtherParams = ChargeTransactionPaymentParams; type Error = std::convert::Infallible; @@ -349,50 +323,24 @@ impl ExtrinsicParamsEncoder for ChargeTransactionPayment { } } -impl SignedExtension for ChargeTransactionPayment { +impl SignedExtension 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 DynamicExtrinsicParams { +pub struct AnyOf { params: Vec>, - _marker: std::marker::PhantomData<(T, Params)> -} - -impl std::fmt::Debug for DynamicExtrinsicParams { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - f.debug_struct("DynamicExtrinsicParams") - .field("params", &self.params) - .field("_marker", &"std::marker::PhantomData") - .finish() - } -} - -/// This accepts a tuple of [`SignedExtension`]s, and will ignore the metadata of the chain we're -/// connected to and simply encode exactly the additional and extra data of each of the extensions -/// in the order that they are provided. Prefer to use [`DynamicExtrinsicParams`]. -pub struct StaticExtrinsicParams { - params: Params, - _marker: std::marker::PhantomData -} - -impl std::fmt::Debug for StaticExtrinsicParams { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - f.debug_struct("StaticExtrinsicParams") - .field("params", &"") - .field("_marker", &"std::marker::PhantomData") - .finish() - } + _marker: std::marker::PhantomData<(T, Params)>, } macro_rules! impl_tuples { ($($ident:ident $index:tt),+) => { - // We do some magic when the tuple is wrapped in DynamicExtrinsicParams. We + // 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 ExtrinsicParams for DynamicExtrinsicParams + impl ExtrinsicParams for AnyOf where T: Config, $($ident: SignedExtension,)+ @@ -431,14 +379,14 @@ macro_rules! impl_tuples { } } - Ok(DynamicExtrinsicParams { + Ok(AnyOf { params: params, _marker: std::marker::PhantomData }) } } - impl ExtrinsicParamsEncoder for DynamicExtrinsicParams + impl ExtrinsicParamsEncoder for AnyOf where T: Config, $($ident: SignedExtension,)+ @@ -454,50 +402,6 @@ macro_rules! impl_tuples { } } } - - // If we know exactly the structure of signed extensions that we need, and we don't want to - // use the node metadata to decide on which to encode, then we can instead provide a StaticExtrinsicParams - // wrapping a tuple of signed extensions. Here, using a tuple of 1 signed extension has the same behaviour - // as the signed extension on its own. Using `DynamicExtrinsicParams` is strongly preferred, but this is a - // little faster if you know precisely the signed extensions that the chain needs. - impl ExtrinsicParams for StaticExtrinsicParams - where - T: Config, - $($ident: SignedExtension,)+ - { - type OtherParams = ($($ident::OtherParams,)+); - type Error = ExtrinsicParamsError; - - fn new>( - nonce: u64, - client: Client, - other_params: Self::OtherParams, - ) -> Result { - Ok(StaticExtrinsicParams { - params: ( - $($ident::new(nonce, client.clone(), other_params.$index).map_err(Into::into)?,)+ - ), - _marker: std::marker::PhantomData - }) - } - } - - impl ExtrinsicParamsEncoder for StaticExtrinsicParams - where - T: Config, - $($ident: SignedExtension,)+ - { - fn encode_extra_to(&self, v: &mut Vec) { - $( - self.params.$index.encode_extra_to(v); - )+ - } - fn encode_additional_to(&self, v: &mut Vec) { - $( - self.params.$index.encode_additional_to(v); - )+ - } - } } } @@ -526,9 +430,24 @@ const _: () = { 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); }; -/// Tries to encode an empty tuple into the type given. Returns true if this -/// succeeds, and thus if the type given is empty (and compatible with an empty tuple) +/// 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 { - use scale_encode::EncodeAsType; - ().encode_as_type(type_id, types).is_ok() + 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, + } } diff --git a/subxt/src/config/substrate.rs b/subxt/src/config/substrate.rs index 83b780ac3c..29bff1f03d 100644 --- a/subxt/src/config/substrate.rs +++ b/subxt/src/config/substrate.rs @@ -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 = BaseExtrinsicParams; +pub type SubstrateExtrinsicParams = DefaultExtrinsicParams; /// A builder which leads to [`SubstrateExtrinsicParams`] being constructed. /// This is what you provide to methods like `sign_and_submit()`. -pub type SubstrateExtrinsicParamsBuilder = BaseExtrinsicParamsBuilder; - -// 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, -} - -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 for AssetTip { - fn from(n: u128) -> Self { - AssetTip::new(n) - } -} +pub type SubstrateExtrinsicParamsBuilder = DefaultExtrinsicParamsBuilder; /// A type that can hash values using the blaks2_256 algorithm. #[derive(Debug, Clone, Copy, PartialEq, Eq, Encode)] diff --git a/subxt/src/error/mod.rs b/subxt/src/error/mod.rs index 8594b8e649..54bd80414c 100644 --- a/subxt/src/error/mod.rs +++ b/subxt/src/error/mod.rs @@ -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), diff --git a/subxt/src/tx/tx_client.rs b/subxt/src/tx/tx_client.rs index 3dfc096ea8..3cf8940d1d 100644 --- a/subxt/src/tx/tx_client.rs +++ b/subxt/src/tx/tx_client.rs @@ -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> TxClient { &self, call: &Call, account_nonce: u64, - other_params: >::OtherParams, + other_params: impl Into<>::OtherParams>, ) -> Result, Error> where Call: TxPayload, @@ -124,17 +124,12 @@ impl> TxClient { 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(); - >::new( - runtime.spec_version, - runtime.transaction_version, - account_nonce, - self.client.genesis_hash(), - other_params, - ) - }; + let additional_and_extra_params = >::new( + account_nonce, + self.client.clone(), + other_params.into(), + ) + .map_err(Into::into)?; // Return these details, ready to construct a signed extrinsic from. Ok(PartialExtrinsic { @@ -150,7 +145,7 @@ impl> TxClient { call: &Call, signer: &Signer, account_nonce: u64, - other_params: >::OtherParams, + other_params: impl Into<>::OtherParams>, ) -> Result, Error> where Call: TxPayload, @@ -203,7 +198,7 @@ where &self, call: &Call, account_id: &T::AccountId, - other_params: >::OtherParams, + other_params: impl Into<>::OtherParams>, ) -> Result, Error> where Call: TxPayload, @@ -217,7 +212,7 @@ where &self, call: &Call, signer: &Signer, - other_params: >::OtherParams, + other_params: impl Into<>::OtherParams>, ) -> Result, Error> where Call: TxPayload, @@ -240,9 +235,11 @@ where where Call: TxPayload, Signer: SignerT, - >::OtherParams: Default, + >::OtherParams: Default, { - self.sign_and_submit_then_watch(call, signer, Default::default()) + let other_params: >::OtherParams = + Default::default(); + self.sign_and_submit_then_watch(call, signer, other_params) .await } @@ -254,7 +251,7 @@ where &self, call: &Call, signer: &Signer, - other_params: >::OtherParams, + other_params: impl Into<>::OtherParams>, ) -> Result, Error> where Call: TxPayload, @@ -284,9 +281,11 @@ where where Call: TxPayload, Signer: SignerT, - >::OtherParams: Default, + >::OtherParams: Default, { - self.sign_and_submit(call, signer, Default::default()).await + let other_params: >::OtherParams = + Default::default(); + self.sign_and_submit(call, signer, other_params).await } /// Creates and signs an extrinsic and submits to the chain for block inclusion. @@ -301,7 +300,7 @@ where &self, call: &Call, signer: &Signer, - other_params: >::OtherParams, + other_params: impl Into<>::OtherParams>, ) -> Result where Call: TxPayload, diff --git a/subxt/src/tx/tx_progress.rs b/subxt/src/tx/tx_progress.rs index 440ed10cb1..561bec2f95 100644 --- a/subxt/src/tx/tx_progress.rs +++ b/subxt/src/tx/tx_progress.rs @@ -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; - type MockHash = , - > as Config>::Hash; + type MockHash = ::Hash; type MockSubstrateTxStatus = SubstrateTxStatus; /// a mock client to satisfy trait bounds in tests @@ -441,21 +437,21 @@ mod test { impl OfflineClientT 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) -> ::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 for MockClient { fn rpc(&self) -> &crate::rpc::Rpc { - panic!("just a mock impl to satisfy trait bounds") + unimplemented!("just a mock impl to satisfy trait bounds") } } From 93feeb9de920d45a153aa7133266f97693fcb39f Mon Sep 17 00:00:00 2001 From: James Wilson Date: Fri, 4 Aug 2023 17:41:15 +0100 Subject: [PATCH 03/10] remove impl Into in tx_client to avoid type inference annoyances --- .../examples/setup_config_signed_extension.rs | 2 +- subxt/examples/tx_with_params.rs | 5 ++- subxt/src/config/default_extrinsic_params.rs | 33 ++----------------- subxt/src/tx/tx_client.rs | 20 +++++------ 4 files changed, 15 insertions(+), 45 deletions(-) diff --git a/subxt/examples/setup_config_signed_extension.rs b/subxt/examples/setup_config_signed_extension.rs index f1e0a25f08..818533c746 100644 --- a/subxt/examples/setup_config_signed_extension.rs +++ b/subxt/examples/setup_config_signed_extension.rs @@ -78,7 +78,7 @@ impl ExtrinsicParamsEncoder for CustomSignedExtension { pub fn custom( params: DefaultExtrinsicParamsBuilder, ) -> <::ExtrinsicParams as ExtrinsicParams>::OtherParams { - let (a, b, c, d, e, f, g) = params.raw(); + let (a, b, c, d, e, f, g) = params.build(); (a, b, c, d, e, f, g, ()) } diff --git a/subxt/examples/tx_with_params.rs b/subxt/examples/tx_with_params.rs index 843ffd0b6b..90a02430b0 100644 --- a/subxt/examples/tx_with_params.rs +++ b/subxt/examples/tx_with_params.rs @@ -18,7 +18,10 @@ async fn main() -> Result<(), Box> { // Configure the transaction parameters; we give a small tip and set the // transaction to live for 32 blocks from the `latest_block` above. - let tx_params = Params::new().tip(1_000).mortal(latest_block.header(), 32); + let tx_params = Params::new() + .tip(1_000) + .mortal(latest_block.header(), 32) + .build(); // submit the transaction: let from = dev::alice(); diff --git a/subxt/src/config/default_extrinsic_params.rs b/subxt/src/config/default_extrinsic_params.rs index c8acb319e2..1ba1c9b724 100644 --- a/subxt/src/config/default_extrinsic_params.rs +++ b/subxt/src/config/default_extrinsic_params.rs @@ -92,8 +92,8 @@ impl DefaultExtrinsicParamsBuilder { self } - /// Return the "raw" params as required. This doesn't need to be called in normal usage. - pub fn raw(self) -> OtherParams { + /// Build the extrinsic parameters. + pub fn build(self) -> as ExtrinsicParams>::OtherParams { let check_mortality_params = if let Some(checkpoint_hash) = self.mortality_checkpoint_hash { signed_extensions::CheckMortalityParams::mortal( self.mortality_period, @@ -124,32 +124,3 @@ impl DefaultExtrinsicParamsBuilder { ) } } - -type OtherParams = ( - (), - (), - (), - (), - signed_extensions::CheckMortalityParams, - signed_extensions::ChargeAssetTxPaymentParams, - signed_extensions::ChargeTransactionPaymentParams, -); - -impl From> for OtherParams { - fn from(v: DefaultExtrinsicParamsBuilder) -> Self { - v.raw() - } -} - -// We have to manually write out `OtherParams` for some reason to avoid type errors in the `From` impl. -// So, here we ensure that `OtherParams` is equal to ` as ExtrinsicParams>::OtherParams`. -// We'll get a compile error if not. -#[allow(unused)] -fn assert_otherparams_eq() { - struct Ty(Inner); - fn assert_eq(t: Ty>) { - match t { - Ty::< as ExtrinsicParams>::OtherParams>(_) => {} - } - } -} diff --git a/subxt/src/tx/tx_client.rs b/subxt/src/tx/tx_client.rs index 3cf8940d1d..ab2038ebdd 100644 --- a/subxt/src/tx/tx_client.rs +++ b/subxt/src/tx/tx_client.rs @@ -111,7 +111,7 @@ impl> TxClient { &self, call: &Call, account_nonce: u64, - other_params: impl Into<>::OtherParams>, + other_params: >::OtherParams, ) -> Result, Error> where Call: TxPayload, @@ -145,7 +145,7 @@ impl> TxClient { call: &Call, signer: &Signer, account_nonce: u64, - other_params: impl Into<>::OtherParams>, + other_params: >::OtherParams, ) -> Result, Error> where Call: TxPayload, @@ -198,7 +198,7 @@ where &self, call: &Call, account_id: &T::AccountId, - other_params: impl Into<>::OtherParams>, + other_params: >::OtherParams, ) -> Result, Error> where Call: TxPayload, @@ -212,7 +212,7 @@ where &self, call: &Call, signer: &Signer, - other_params: impl Into<>::OtherParams>, + other_params: >::OtherParams, ) -> Result, Error> where Call: TxPayload, @@ -237,9 +237,7 @@ where Signer: SignerT, >::OtherParams: Default, { - let other_params: >::OtherParams = - Default::default(); - self.sign_and_submit_then_watch(call, signer, other_params) + self.sign_and_submit_then_watch(call, signer, Default::default()) .await } @@ -251,7 +249,7 @@ where &self, call: &Call, signer: &Signer, - other_params: impl Into<>::OtherParams>, + other_params: >::OtherParams, ) -> Result, Error> where Call: TxPayload, @@ -283,9 +281,7 @@ where Signer: SignerT, >::OtherParams: Default, { - let other_params: >::OtherParams = - Default::default(); - self.sign_and_submit(call, signer, other_params).await + self.sign_and_submit(call, signer, Default::default()).await } /// Creates and signs an extrinsic and submits to the chain for block inclusion. @@ -300,7 +296,7 @@ where &self, call: &Call, signer: &Signer, - other_params: impl Into<>::OtherParams>, + other_params: >::OtherParams, ) -> Result where Call: TxPayload, From 3deeb8c9fd0575fc93605455bfa011eb9453a1f9 Mon Sep 17 00:00:00 2001 From: James Wilson Date: Fri, 4 Aug 2023 17:55:07 +0100 Subject: [PATCH 04/10] clippy and fix example --- examples/wasm-example/Cargo.lock | 88 ++++++++++---------- examples/wasm-example/src/services.rs | 6 +- subxt/src/config/default_extrinsic_params.rs | 14 +++- subxt/src/config/signed_extensions.rs | 2 +- subxt/src/tx/tx_client.rs | 2 +- 5 files changed, 59 insertions(+), 53 deletions(-) diff --git a/examples/wasm-example/Cargo.lock b/examples/wasm-example/Cargo.lock index 63aea8e290..174decfb19 100644 --- a/examples/wasm-example/Cargo.lock +++ b/examples/wasm-example/Cargo.lock @@ -113,7 +113,7 @@ checksum = "cc6dde6e4ed435a4c1ee4e73592f5ba9da2151af10076cc04858746af9352d09" dependencies = [ "proc-macro2", "quote", - "syn 2.0.27", + "syn 2.0.28", ] [[package]] @@ -537,7 +537,7 @@ dependencies = [ "proc-macro2", "quote", "strsim", - "syn 2.0.27", + "syn 2.0.28", ] [[package]] @@ -559,7 +559,7 @@ checksum = "836a9bbc7ad63342d6d6e7b815ccab164bc77a2d95d84bc3117a8c0d5c98e2d5" dependencies = [ "darling_core 0.20.3", "quote", - "syn 2.0.27", + "syn 2.0.28", ] [[package]] @@ -762,7 +762,7 @@ checksum = "89ca545a94061b6365f2c7355b4b32bd20df3ff95f02da9329b34ccc3bd6ee72" dependencies = [ "proc-macro2", "quote", - "syn 2.0.27", + "syn 2.0.28", ] [[package]] @@ -1704,7 +1704,7 @@ checksum = "ec2e072ecce94ec471b13398d5402c188e76ac03cf74dd1a975161b23a3f6d9c" dependencies = [ "proc-macro2", "quote", - "syn 2.0.27", + "syn 2.0.28", ] [[package]] @@ -2005,9 +2005,9 @@ checksum = "1ad4cc8da4ef723ed60bced201181d83791ad433213d8c24efffda1eec85d741" [[package]] name = "scale-bits" -version = "0.3.0" +version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8dd7aca73785181cc41f0bbe017263e682b585ca660540ba569133901d013ecf" +checksum = "036575c29af9b6e4866ffb7fa055dbf623fe7a9cc159b33786de6013a6969d89" dependencies = [ "parity-scale-codec", "scale-info", @@ -2016,24 +2016,24 @@ dependencies = [ [[package]] name = "scale-decode" -version = "0.7.0" +version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f0459d00b0dbd2e765009924a78ef36b2ff7ba116292d732f00eb0ed8e465d15" +checksum = "7789f5728e4e954aaa20cadcc370b99096fb8645fca3c9333ace44bb18f30095" dependencies = [ + "derive_more", "parity-scale-codec", "primitive-types", "scale-bits", "scale-decode-derive", "scale-info", "smallvec", - "thiserror", ] [[package]] name = "scale-decode-derive" -version = "0.7.0" +version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4391f0dfbb6690f035f6d2a15d6a12f88cc5395c36bcc056db07ffa2a90870ec" +checksum = "27873eb6005868f8cc72dcfe109fae664cf51223d35387bc2f28be4c28d94c47" dependencies = [ "darling 0.14.4", "proc-macro-crate", @@ -2044,24 +2044,24 @@ dependencies = [ [[package]] name = "scale-encode" -version = "0.3.0" +version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b0401b7cdae8b8aa33725f3611a051358d5b32887ecaa0fda5953a775b2d4d76" +checksum = "6d70cb4b29360105483fac1ed567ff95d65224a14dd275b6303ed0a654c78de5" dependencies = [ + "derive_more", "parity-scale-codec", "primitive-types", "scale-bits", "scale-encode-derive", "scale-info", "smallvec", - "thiserror", ] [[package]] name = "scale-encode-derive" -version = "0.3.0" +version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "316e0fb10ec0fee266822bd641bab5e332a4ab80ef8c5b5ff35e5401a394f5a6" +checksum = "995491f110efdc6bea96d6a746140e32bfceb4ea47510750a5467295a4707a25" dependencies = [ "darling 0.14.4", "proc-macro-crate", @@ -2098,12 +2098,13 @@ dependencies = [ [[package]] name = "scale-value" -version = "0.10.0" +version = "0.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f2096d36e94ce9bf87d8addb752423b6b19730dc88edd7cc452bb2b90573f7a7" +checksum = "6538d1cc1af9c0baf401c57da8a6d4730ef582db0d330d2efa56ec946b5b0283" dependencies = [ "base58", "blake2", + "derive_more", "either", "frame-metadata 15.1.0", "parity-scale-codec", @@ -2112,7 +2113,6 @@ dependencies = [ "scale-encode", "scale-info", "serde", - "thiserror", "yap", ] @@ -2194,9 +2194,9 @@ checksum = "cd0b0ec5f1c1ca621c432a25813d8d60c88abe6d3e08a3eb9cf37d97a0fe3d73" [[package]] name = "serde" -version = "1.0.175" +version = "1.0.181" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5d25439cd7397d044e2748a6fe2432b5e85db703d6d097bd014b3c0ad1ebff0b" +checksum = "6d3e73c93c3240c0bda063c239298e633114c69a888c3e37ca8bb33f343e9890" dependencies = [ "serde_derive", ] @@ -2214,20 +2214,20 @@ dependencies = [ [[package]] name = "serde_derive" -version = "1.0.175" +version = "1.0.181" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b23f7ade6f110613c0d63858ddb8b94c1041f550eab58a16b371bdf2c9c80ab4" +checksum = "be02f6cb0cd3a5ec20bbcfbcbd749f57daddb1a0882dc2e46a6c236c90b977ed" dependencies = [ "proc-macro2", "quote", - "syn 2.0.27", + "syn 2.0.28", ] [[package]] name = "serde_json" -version = "1.0.103" +version = "1.0.104" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d03b412469450d4404fe8499a268edd7f8b79fecb074b0d812ad64ca21f4031b" +checksum = "076066c5f1078eac5b722a31827a8832fe108bed65dfa75e233c89f8206e976c" dependencies = [ "itoa", "ryu", @@ -2490,7 +2490,7 @@ checksum = "734676eb262c623cec13c3155096e08d1f8f29adce39ba17948b18dad1e54142" [[package]] name = "subxt" -version = "0.30.0" +version = "0.31.0" dependencies = [ "base58", "blake2", @@ -2521,7 +2521,7 @@ dependencies = [ [[package]] name = "subxt-codegen" -version = "0.30.0" +version = "0.31.0" dependencies = [ "frame-metadata 16.0.0", "heck", @@ -2532,14 +2532,14 @@ dependencies = [ "quote", "scale-info", "subxt-metadata", - "syn 2.0.27", + "syn 2.0.28", "thiserror", "tokio", ] [[package]] name = "subxt-lightclient" -version = "0.30.0" +version = "0.31.0" dependencies = [ "futures", "futures-timer", @@ -2563,17 +2563,17 @@ dependencies = [ [[package]] name = "subxt-macro" -version = "0.30.0" +version = "0.31.0" dependencies = [ "darling 0.20.3", "proc-macro-error", "subxt-codegen", - "syn 2.0.27", + "syn 2.0.28", ] [[package]] name = "subxt-metadata" -version = "0.30.0" +version = "0.31.0" dependencies = [ "frame-metadata 16.0.0", "parity-scale-codec", @@ -2595,9 +2595,9 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.27" +version = "2.0.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b60f673f44a8255b9c8c657daf66a596d435f2da81a555b06dc644d080ba45e0" +checksum = "04361975b3f5e348b2189d8dc55bc942f278b2d482a6a0365de5bdd62d351567" dependencies = [ "proc-macro2", "quote", @@ -2647,7 +2647,7 @@ checksum = "090198534930841fab3a5d1bb637cde49e339654e606195f8d9c76eeb081dc96" dependencies = [ "proc-macro2", "quote", - "syn 2.0.27", + "syn 2.0.28", ] [[package]] @@ -2700,7 +2700,7 @@ checksum = "630bdcf245f78637c13ec01ffae6187cca34625e8c63150d424b59e55af2675e" dependencies = [ "proc-macro2", "quote", - "syn 2.0.27", + "syn 2.0.28", ] [[package]] @@ -2783,7 +2783,7 @@ checksum = "5f4f31f56159e98206da9efd823404b79b6ef3143b4a7ab76e67b1751b25a4ab" dependencies = [ "proc-macro2", "quote", - "syn 2.0.27", + "syn 2.0.28", ] [[package]] @@ -2895,7 +2895,7 @@ dependencies = [ "once_cell", "proc-macro2", "quote", - "syn 2.0.27", + "syn 2.0.28", "wasm-bindgen-shared", ] @@ -2929,7 +2929,7 @@ checksum = "54681b18a46765f095758388f2d0cf16eb8d4169b639ab575a8f5693af210c7b" dependencies = [ "proc-macro2", "quote", - "syn 2.0.27", + "syn 2.0.28", "wasm-bindgen-backend", "wasm-bindgen-shared", ] @@ -3136,9 +3136,9 @@ dependencies = [ [[package]] name = "yap" -version = "0.10.0" +version = "0.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e2a7eb6d82a11e4d0b8e6bda8347169aff4ccd8235d039bba7c47482d977dcf7" +checksum = "ff4524214bc4629eba08d78ceb1d6507070cc0bcbbed23af74e19e6e924a24cf" [[package]] name = "yew" @@ -3226,5 +3226,5 @@ checksum = "ce36e65b0d2999d2aafac989fb249189a141aee1f53c612c1f37d72631959f69" dependencies = [ "proc-macro2", "quote", - "syn 2.0.27", + "syn 2.0.28", ] diff --git a/examples/wasm-example/src/services.rs b/examples/wasm-example/src/services.rs index b1bd978527..5ee0f56551 100644 --- a/examples/wasm-example/src/services.rs +++ b/examples/wasm-example/src/services.rs @@ -4,7 +4,7 @@ use js_sys::Promise; use serde::{Deserialize, Serialize}; use serde_json::json; use std::fmt::Write; -use subxt::ext::codec::Encode; +use subxt::ext::codec::{ Compact, Encode }; use subxt::tx::PartialExtrinsic; use subxt::{self, OnlineClient, PolkadotConfig}; use subxt::utils::AccountId32; @@ -143,7 +143,7 @@ pub async fn extension_signature_for_partial_extrinsic( let spec_version = encode_to_hex_reverse(&api.runtime_version().spec_version); let transaction_version = encode_to_hex_reverse(&api.runtime_version().transaction_version); let mortality_checkpoint = encode_to_hex(&api.genesis_hash()); - let era = encode_to_hex(&subxt::config::extrinsic_params::Era::Immortal); + let era = encode_to_hex(&0u8); let genesis_hash = encode_to_hex(&api.genesis_hash()); let method = to_hex(partial_extrinsic.call_data()); let nonce = api.tx().account_nonce(account_id).await?; @@ -155,7 +155,7 @@ pub async fn extension_signature_for_partial_extrinsic( .iter() .map(|e| e.identifier().to_string()) .collect(); - let tip = encode_to_hex(&subxt::config::polkadot::PlainTip::new(0)); + let tip = encode_to_hex(&Compact(0u128)); let payload = json!({ "specVersion": spec_version, diff --git a/subxt/src/config/default_extrinsic_params.rs b/subxt/src/config/default_extrinsic_params.rs index 1ba1c9b724..0b4e809fca 100644 --- a/subxt/src/config/default_extrinsic_params.rs +++ b/subxt/src/config/default_extrinsic_params.rs @@ -32,10 +32,8 @@ pub struct DefaultExtrinsicParamsBuilder { tip_of_asset_id: Option, } -impl DefaultExtrinsicParamsBuilder { - /// Configure new extrinsic params. We default to providing no tip - /// and using an immortal transaction unless otherwise configured - pub fn new() -> Self { +impl Default for DefaultExtrinsicParamsBuilder { + fn default() -> Self { Self { mortality_checkpoint_hash: None, mortality_checkpoint_number: 0, @@ -45,6 +43,14 @@ impl DefaultExtrinsicParamsBuilder { tip_of_asset_id: None, } } +} + +impl DefaultExtrinsicParamsBuilder { + /// 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 diff --git a/subxt/src/config/signed_extensions.rs b/subxt/src/config/signed_extensions.rs index 33d80ecda0..e3d62246f8 100644 --- a/subxt/src/config/signed_extensions.rs +++ b/subxt/src/config/signed_extensions.rs @@ -380,7 +380,7 @@ macro_rules! impl_tuples { } Ok(AnyOf { - params: params, + params, _marker: std::marker::PhantomData }) } diff --git a/subxt/src/tx/tx_client.rs b/subxt/src/tx/tx_client.rs index ab2038ebdd..f2a538eddd 100644 --- a/subxt/src/tx/tx_client.rs +++ b/subxt/src/tx/tx_client.rs @@ -127,7 +127,7 @@ impl> TxClient { let additional_and_extra_params = >::new( account_nonce, self.client.clone(), - other_params.into(), + other_params, ) .map_err(Into::into)?; From 11f8c9def728200ef9758f60dc62a6346c6fe196 Mon Sep 17 00:00:00 2001 From: James Wilson Date: Mon, 7 Aug 2023 11:18:23 +0100 Subject: [PATCH 05/10] Fix book links --- subxt/examples/setup_config_signed_extension.rs | 8 +++++--- subxt/src/book/setup/config.rs | 8 ++++---- subxt/src/config/extrinsic_params.rs | 10 +++++----- subxt/src/config/signed_extensions.rs | 8 ++++---- 4 files changed, 18 insertions(+), 16 deletions(-) diff --git a/subxt/examples/setup_config_signed_extension.rs b/subxt/examples/setup_config_signed_extension.rs index 818533c746..38e871bfa1 100644 --- a/subxt/examples/setup_config_signed_extension.rs +++ b/subxt/examples/setup_config_signed_extension.rs @@ -23,7 +23,8 @@ impl Config for CustomConfig { type ExtrinsicParams = signed_extensions::AnyOf< Self, ( - // Load in all of the existing signed extensions: + // Load in the existing signed extensions we're interested in + // (if the extension isn't actually needed it'll just be ignored): signed_extensions::CheckSpecVersion, signed_extensions::CheckTxVersion, signed_extensions::CheckNonce, @@ -40,7 +41,7 @@ impl Config for CustomConfig { // Our custom signed extension doesn't do much: pub struct CustomSignedExtension; -// Give the extension a name; this allows [`AnyOf`] to look it +// Give the extension a name; this allows `AnyOf` to look it // up in the chain metadata in order to know when and if to use it. impl signed_extensions::SignedExtension for CustomSignedExtension { const NAME: &'static str = "CustomSignedExtension"; @@ -95,5 +96,6 @@ async fn main() { // And provide them when submitting a transaction: let _ = client .tx() - .sign_and_submit_then_watch(&tx_payload, &dev::alice(), custom(tx_config)); + .sign_and_submit_then_watch(&tx_payload, &dev::alice(), custom(tx_config)) + .await; } diff --git a/subxt/src/book/setup/config.rs b/subxt/src/book/setup/config.rs index bfab118bb8..1260fc0a0f 100644 --- a/subxt/src/book/setup/config.rs +++ b/subxt/src/book/setup/config.rs @@ -71,7 +71,8 @@ //! //! 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 most cases, the default [`crate::config::DefaultExtrinsicParams`] type will work here; 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 @@ -122,9 +123,8 @@ //! //! 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 +//! [`crate::config::ExtrinsicParams`] impl then is to provide the appropriate (SCALE encoded) data for these. +//! 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. //! //! ### Implementing and adding new signed extensions to the config. diff --git a/subxt/src/config/extrinsic_params.rs b/subxt/src/config/extrinsic_params.rs index b42d5142a4..312b64a00b 100644 --- a/subxt/src/config/extrinsic_params.rs +++ b/subxt/src/config/extrinsic_params.rs @@ -3,9 +3,9 @@ // 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 ([`BaseSignedExtensions`]) 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::{client::OfflineClientT, Config}; use core::fmt::Debug; @@ -33,8 +33,8 @@ impl From for ExtrinsicParamsError { } /// This trait allows you to configure the "signed extra" and -/// "additional" parameters that are signed and used in transactions. -/// Tuples of [`SignedExtension`]'s automatically implement this. +/// "additional" parameters that are a part of the transaction payload +/// or the signer payload respectively. pub trait ExtrinsicParams: ExtrinsicParamsEncoder + Sized + 'static { /// These parameters can be provided to the constructor along with /// some default parameters that `subxt` understands, in order to diff --git a/subxt/src/config/signed_extensions.rs b/subxt/src/config/signed_extensions.rs index e3d62246f8..6c57a80ac2 100644 --- a/subxt/src/config/signed_extensions.rs +++ b/subxt/src/config/signed_extensions.rs @@ -2,10 +2,10 @@ // This file is dual-licensed as Apache-2.0 or GPL-3.0. // 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 ([`BaseSignedExtensions`]) which is -//! used by the provided Substrate and Polkadot configuration. +//! 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::era::Era; use super::extrinsic_params::{ExtrinsicParams, ExtrinsicParamsEncoder, ExtrinsicParamsError}; From b82e2206075169c63d74290c8c81a4bec59d7014 Mon Sep 17 00:00:00 2001 From: James Wilson Date: Mon, 7 Aug 2023 11:27:02 +0100 Subject: [PATCH 06/10] clippy --- subxt/examples/setup_config_custom.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/subxt/examples/setup_config_custom.rs b/subxt/examples/setup_config_custom.rs index 4ba8c68e3a..46a3afc3a2 100644 --- a/subxt/examples/setup_config_custom.rs +++ b/subxt/examples/setup_config_custom.rs @@ -91,5 +91,6 @@ async fn main() { // And provide them when submitting a transaction: let _ = client .tx() - .sign_and_submit_then_watch(&tx_payload, &dev::alice(), tx_config); + .sign_and_submit_then_watch(&tx_payload, &dev::alice(), tx_config) + .await; } From a9ea2b5e83f0794c0bff2ceb9e2d86defcd050a2 Mon Sep 17 00:00:00 2001 From: James Wilson Date: Mon, 7 Aug 2023 12:49:34 +0100 Subject: [PATCH 07/10] book tweaks --- subxt/examples/setup_client_custom_config.rs | 28 ------------ subxt/src/book/setup/client.rs | 39 +++++++---------- subxt/src/book/setup/config.rs | 46 ++++++++++---------- 3 files changed, 40 insertions(+), 73 deletions(-) delete mode 100644 subxt/examples/setup_client_custom_config.rs diff --git a/subxt/examples/setup_client_custom_config.rs b/subxt/examples/setup_client_custom_config.rs deleted file mode 100644 index 7ee30ba0a7..0000000000 --- a/subxt/examples/setup_client_custom_config.rs +++ /dev/null @@ -1,28 +0,0 @@ -use subxt::{ - config::{substrate::SubstrateExtrinsicParams, Config, SubstrateConfig}, - OnlineClient, -}; - -/// Define a custom config type (see the `subxt::config::Config` docs for -/// more information about each type): -enum MyConfig {} -impl Config for MyConfig { - // We can point to the default types if we don't need to change things: - type Hash = ::Hash; - type Hasher = ::Hasher; - type Header = ::Header; - type AccountId = ::AccountId; - type Address = ::Address; - type Signature = ::Signature; - // ExtrinsicParams makes use of the index type, so we need to tweak it - // too to align with our modified index type, above: - type ExtrinsicParams = SubstrateExtrinsicParams; -} - -#[tokio::main] -async fn main() -> Result<(), Box> { - // Create a client which uses the custom config: - let _api = OnlineClient::::new().await?; - - Ok(()) -} diff --git a/subxt/src/book/setup/client.rs b/subxt/src/book/setup/client.rs index 9329d7839a..fb1567d687 100644 --- a/subxt/src/book/setup/client.rs +++ b/subxt/src/book/setup/client.rs @@ -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: -//! -//! ```rust,ignore -#![doc = include_str!("../../../examples/setup_client_custom_config.rs")] -//! ``` +//! Most of the other examples will instantiate a client. Here are a couple of examples for less common +//! cases. //! -//! 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")] diff --git a/subxt/src/book/setup/config.rs b/subxt/src/book/setup/config.rs index 1260fc0a0f..dba7654d7b 100644 --- a/subxt/src/book/setup/config.rs +++ b/subxt/src/book/setup/config.rs @@ -74,21 +74,24 @@ //! 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 most cases, the default [`crate::config::DefaultExtrinsicParams`] type will work here; it understands the "standard" +//! **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 ensure that data is given in the correct order, and return an -//! error if the chain is using signed extensions that are unknown to it which require data. +//! [`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. //! -//! In the event that novel signed extensions are in use (or if you just wish to provide a different interface for users to -//! configure transactions), you can either implement an interface for the new signed extension and add it to the list of -//! configured ones, or you can create an entirely custom implementation of [`crate::config::ExtrinsicParams`] with whatever -//! arbitrary behaviour you require. +//! 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. //! -//! 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 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): @@ -122,28 +125,27 @@ //! | [`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. -//! 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. //! -//! ### Implementing and adding new signed extensions to the config. +//! 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. //! -//! As it happens, Statemint only uses signed extensions that Subxt is natively aware of via [`crate::config::DefaultExtrinsicParams`], -//! and so the default config will work just fine. If it did not, then you could implement [`crate::config::signed_extensions::SignedExtension`] -//! on some custom type. This could then be placed into a new set of signed extensions, and (optionally) some novel interface could be -//! constructed to make it easy for users to configure them when submitting transactions. +//! ### Implementing and adding new signed extensions to the config //! -//! Let's see what this looks like: +//! 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. +//! ### 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. This should be somewhat of a last resort. +//! "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): //! From a26897abc59cc92f17bfc09f5a2ad5e6adfa9725 Mon Sep 17 00:00:00 2001 From: James Wilson Date: Mon, 7 Aug 2023 15:01:09 +0100 Subject: [PATCH 08/10] fmt: remove spaces Co-authored-by: Niklas Adolfsson --- examples/wasm-example/src/services.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/wasm-example/src/services.rs b/examples/wasm-example/src/services.rs index 5ee0f56551..7a879cab8d 100644 --- a/examples/wasm-example/src/services.rs +++ b/examples/wasm-example/src/services.rs @@ -4,7 +4,7 @@ use js_sys::Promise; use serde::{Deserialize, Serialize}; use serde_json::json; use std::fmt::Write; -use subxt::ext::codec::{ Compact, Encode }; +use subxt::ext::codec::{Compact, Encode}; use subxt::tx::PartialExtrinsic; use subxt::{self, OnlineClient, PolkadotConfig}; use subxt::utils::AccountId32; From 4fdc987cf898db04f093efa2b661c2619acedb63 Mon Sep 17 00:00:00 2001 From: James Wilson Date: Mon, 7 Aug 2023 15:49:27 +0100 Subject: [PATCH 09/10] re-expose Era in utils, and tweak wasm-example --- examples/wasm-example/src/routes/signing.rs | 29 +++++++----- examples/wasm-example/src/services.rs | 39 +++++++---------- subxt/src/config/default_extrinsic_params.rs | 46 ++++++++++++-------- subxt/src/config/mod.rs | 1 - subxt/src/config/signed_extensions.rs | 2 +- subxt/src/{config => utils}/era.rs | 33 +++++++------- subxt/src/utils/mod.rs | 2 + 7 files changed, 81 insertions(+), 71 deletions(-) rename subxt/src/{config => utils}/era.rs (78%) diff --git a/examples/wasm-example/src/routes/signing.rs b/examples/wasm-example/src/routes/signing.rs index 44fe024a56..64b0be7e01 100644 --- a/examples/wasm-example/src/routes/signing.rs +++ b/examples/wasm-example/src/routes/signing.rs @@ -8,7 +8,7 @@ use subxt::tx::SubmittableExtrinsic; use subxt::tx::TxPayload; use subxt::utils::{AccountId32, MultiSignature}; -use crate::services::{extension_signature_for_partial_extrinsic, get_accounts, polkadot, Account}; +use crate::services::{extension_signature_for_extrinsic, get_accounts, polkadot, Account}; use web_sys::HtmlInputElement; use yew::prelude::*; @@ -139,15 +139,15 @@ impl Component for SigningExamplesComponent { ctx.link() .send_future( async move { - let partial_extrinsic = - match api.tx().create_partial_signed(&remark_call, &account_id, Default::default()).await { - Ok(partial_extrinsic) => partial_extrinsic, - Err(err) => { - return Message::Error(anyhow!("could not create partial extrinsic:\n{:?}", err)); - } - }; - - let Ok(signature) = extension_signature_for_partial_extrinsic(&partial_extrinsic, &api, &account_id, account_source, account_address).await else { + let Ok(account_nonce) = api.tx().account_nonce(&account_id).await else { + return Message::Error(anyhow!("Fetching account nonce failed")); + }; + + let Ok(call_data) = api.tx().call_data(&remark_call) else { + return Message::Error(anyhow!("could not encode call data")); + }; + + let Ok(signature) = extension_signature_for_extrinsic(&call_data, &api, account_nonce, account_source, account_address).await else { return Message::Error(anyhow!("Signing via extension failed")); }; @@ -155,7 +155,12 @@ impl Component for SigningExamplesComponent { return Message::Error(anyhow!("MultiSignature Decoding")); }; - let signed_extrinsic = partial_extrinsic.sign_with_address_and_signature(&account_id.into(), &multi_signature); + let Ok(partial_signed) = api.tx().create_partial_signed_with_nonce(&remark_call, account_nonce, Default::default()) else { + return Message::Error(anyhow!("PartialExtrinsic creation failed")); + }; + + // Apply the signature + let signed_extrinsic = partial_signed.sign_with_address_and_signature(&account_id.into(), &multi_signature); // do a dry run (to debug in the js console if the extrinsic would work) let dry_res = signed_extrinsic.dry_run(None).await; @@ -193,7 +198,7 @@ impl Component for SigningExamplesComponent { match submit_wait_finalized_and_get_extrinsic_success_event( signed_extrinsic, ) - .await + .await { Ok(remark_event) => Message::ExtrinsicFinalized { remark_event }, Err(err) => Message::ExtrinsicFailed(err), diff --git a/examples/wasm-example/src/services.rs b/examples/wasm-example/src/services.rs index 7a879cab8d..59e0ca2b1e 100644 --- a/examples/wasm-example/src/services.rs +++ b/examples/wasm-example/src/services.rs @@ -5,9 +5,7 @@ use serde::{Deserialize, Serialize}; use serde_json::json; use std::fmt::Write; use subxt::ext::codec::{Compact, Encode}; -use subxt::tx::PartialExtrinsic; use subxt::{self, OnlineClient, PolkadotConfig}; -use subxt::utils::AccountId32; use wasm_bindgen::prelude::*; use wasm_bindgen_futures::JsFuture; use yew::{AttrValue, Callback}; @@ -117,37 +115,30 @@ fn to_hex(bytes: impl AsRef<[u8]>) -> String { format!("0x{}", hex::encode(bytes.as_ref())) } -fn encode_to_hex(input: &E) -> String { +fn encode_then_hex(input: &E) -> String { format!("0x{}", hex::encode(input.encode())) } -/// this is used because numeric types (e.g. u32) are encoded as little-endian via scale (e.g. 9430 -> d6240000) -/// while we need a big-endian representation for the json (e.g. 9430 -> 000024d6). -fn encode_to_hex_reverse(input: &E) -> String { - let mut bytes = input.encode(); - bytes.reverse(); - format!("0x{}", hex::encode(bytes)) -} - - /// communicates with JavaScript to obtain a signature for the `partial_extrinsic` via a browser extension (e.g. polkadot-js or Talisman) /// /// Some parameters are hard-coded here and not taken from the partial_extrinsic itself (mortality_checkpoint, era, tip). -pub async fn extension_signature_for_partial_extrinsic( - partial_extrinsic: &PartialExtrinsic>, +pub async fn extension_signature_for_extrinsic( + call_data: &[u8], api: &OnlineClient, - account_id: &AccountId32, + account_nonce: u64, account_source: String, account_address: String, ) -> Result, anyhow::Error> { - let spec_version = encode_to_hex_reverse(&api.runtime_version().spec_version); - let transaction_version = encode_to_hex_reverse(&api.runtime_version().transaction_version); - let mortality_checkpoint = encode_to_hex(&api.genesis_hash()); - let era = encode_to_hex(&0u8); - let genesis_hash = encode_to_hex(&api.genesis_hash()); - let method = to_hex(partial_extrinsic.call_data()); - let nonce = api.tx().account_nonce(account_id).await?; - let nonce = encode_to_hex_reverse(&nonce); + let genesis_hash = encode_then_hex(&api.genesis_hash()); + // These numbers aren't SCALE encoded; their bytes are just converted to hex: + let spec_version = to_hex(&api.runtime_version().spec_version.to_be_bytes()); + let transaction_version = to_hex(&api.runtime_version().transaction_version.to_be_bytes()); + let nonce = to_hex(&account_nonce.to_be_bytes()); + // If you construct a mortal transaction, then this block hash needs to correspond + // to the block number passed to `Era::mortal()`. + let mortality_checkpoint = encode_then_hex(&api.genesis_hash()); + let era = encode_then_hex(&subxt::utils::Era::Immortal); + let method = to_hex(call_data); let signed_extensions: Vec = api .metadata() .extrinsic() @@ -155,7 +146,7 @@ pub async fn extension_signature_for_partial_extrinsic( .iter() .map(|e| e.identifier().to_string()) .collect(); - let tip = encode_to_hex(&Compact(0u128)); + let tip = encode_then_hex(&Compact(0u128)); let payload = json!({ "specVersion": spec_version, diff --git a/subxt/src/config/default_extrinsic_params.rs b/subxt/src/config/default_extrinsic_params.rs index 0b4e809fca..106deeb8ef 100644 --- a/subxt/src/config/default_extrinsic_params.rs +++ b/subxt/src/config/default_extrinsic_params.rs @@ -24,20 +24,28 @@ pub type DefaultExtrinsicParams = signed_extensions::AnyOf< /// [`DefaultExtrinsicParams`]. This may expose methods that aren't applicable to the current /// chain; such values will simply be ignored if so. pub struct DefaultExtrinsicParamsBuilder { - mortality_checkpoint_hash: Option, - mortality_checkpoint_number: u64, - mortality_period: u64, + /// `None` means the tx will be immortal. + mortality: Option>, + /// `None` means we'll use the native token. + tip_of_asset_id: Option, tip: u128, tip_of: u128, - tip_of_asset_id: Option, +} + +struct Mortality { + /// 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 Default for DefaultExtrinsicParamsBuilder { fn default() -> Self { Self { - mortality_checkpoint_hash: None, - mortality_checkpoint_number: 0, - mortality_period: 0, + mortality: None, tip: 0, tip_of: 0, tip_of_asset_id: None, @@ -56,9 +64,11 @@ impl DefaultExtrinsicParamsBuilder { /// 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_checkpoint_hash = Some(from_block.hash()); - self.mortality_checkpoint_number = from_block.number().into(); - self.mortality_period = for_n_blocks; + self.mortality = Some(Mortality { + checkpoint_hash: from_block.hash(), + checkpoint_number: from_block.number().into(), + period: for_n_blocks, + }); self } @@ -74,9 +84,11 @@ impl DefaultExtrinsicParamsBuilder { from_block_hash: T::Hash, for_n_blocks: u64, ) -> Self { - self.mortality_checkpoint_hash = Some(from_block_hash); - self.mortality_checkpoint_number = from_block_number; - self.mortality_period = for_n_blocks; + self.mortality = Some(Mortality { + checkpoint_hash: from_block_hash, + checkpoint_number: from_block_number.into(), + period: for_n_blocks, + }); self } @@ -100,11 +112,11 @@ impl DefaultExtrinsicParamsBuilder { /// Build the extrinsic parameters. pub fn build(self) -> as ExtrinsicParams>::OtherParams { - let check_mortality_params = if let Some(checkpoint_hash) = self.mortality_checkpoint_hash { + let check_mortality_params = if let Some(mortality) = self.mortality { signed_extensions::CheckMortalityParams::mortal( - self.mortality_period, - self.mortality_checkpoint_number, - checkpoint_hash, + mortality.period, + mortality.checkpoint_number, + mortality.checkpoint_hash, ) } else { signed_extensions::CheckMortalityParams::immortal() diff --git a/subxt/src/config/mod.rs b/subxt/src/config/mod.rs index b372f1541d..7638ddd167 100644 --- a/subxt/src/config/mod.rs +++ b/subxt/src/config/mod.rs @@ -9,7 +9,6 @@ //! Polkadot node. mod default_extrinsic_params; -mod era; mod extrinsic_params; pub mod polkadot; diff --git a/subxt/src/config/signed_extensions.rs b/subxt/src/config/signed_extensions.rs index 6c57a80ac2..86e649af74 100644 --- a/subxt/src/config/signed_extensions.rs +++ b/subxt/src/config/signed_extensions.rs @@ -7,8 +7,8 @@ //! [`AnyOf`] to configure the set of signed extensions which are known about //! when interacting with a chain. -use super::era::Era; use super::extrinsic_params::{ExtrinsicParams, ExtrinsicParamsEncoder, ExtrinsicParamsError}; +use crate::utils::Era; use crate::{client::OfflineClientT, Config}; use codec::{Compact, Encode}; use core::fmt::Debug; diff --git a/subxt/src/config/era.rs b/subxt/src/utils/era.rs similarity index 78% rename from subxt/src/config/era.rs rename to subxt/src/utils/era.rs index 4e9ba7b231..63bede4432 100644 --- a/subxt/src/config/era.rs +++ b/subxt/src/utils/era.rs @@ -10,24 +10,22 @@ pub enum Era { #[default] 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. + /// 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(Period, Phase), + 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, + }, } -/// Era period -pub type Period = u64; - -/// Era phase -pub type Phase = u64; - // E.g. with period == 4: // 0 10 20 30 40 // 0123456789012345678901234567890123456789012 @@ -52,7 +50,10 @@ impl Era { let quantize_factor = (period >> 12).max(1); let quantized_phase = phase / quantize_factor * quantize_factor; - Self::Mortal(period, quantized_phase) + Self::Mortal { + period, + phase: quantized_phase, + } } } @@ -62,7 +63,7 @@ impl codec::Encode for Era { fn encode_to(&self, output: &mut T) { match self { Self::Immortal => output.push_byte(0), - Self::Mortal(period, phase) => { + 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; @@ -82,7 +83,7 @@ impl codec::Decode for Era { let quantize_factor = (period >> 12).max(1); let phase = (encoded >> 4) * quantize_factor; if period >= 4 && phase < period { - Ok(Self::Mortal(period, phase)) + Ok(Self::Mortal { period, phase }) } else { Err("Invalid period and phase".into()) } diff --git a/subxt/src/utils/mod.rs b/subxt/src/utils/mod.rs index a9965355cc..d216c2d761 100644 --- a/subxt/src/utils/mod.rs +++ b/subxt/src/utils/mod.rs @@ -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; From 3ed52cad358653358bdbcb28c652f4477fdbf0f9 Mon Sep 17 00:00:00 2001 From: James Wilson Date: Mon, 7 Aug 2023 15:56:28 +0100 Subject: [PATCH 10/10] clippy; remove useless conversion --- subxt/src/config/default_extrinsic_params.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/subxt/src/config/default_extrinsic_params.rs b/subxt/src/config/default_extrinsic_params.rs index 106deeb8ef..f044574258 100644 --- a/subxt/src/config/default_extrinsic_params.rs +++ b/subxt/src/config/default_extrinsic_params.rs @@ -86,7 +86,7 @@ impl DefaultExtrinsicParamsBuilder { ) -> Self { self.mortality = Some(Mortality { checkpoint_hash: from_block_hash, - checkpoint_number: from_block_number.into(), + checkpoint_number: from_block_number, period: for_n_blocks, }); self