From 245aca78eed73a660e4548a2b4bfe275556d7f70 Mon Sep 17 00:00:00 2001 From: Tadeo hepperle Date: Thu, 26 Oct 2023 18:48:47 +0200 Subject: [PATCH] static decoding of signed extensions --- subxt/src/blocks/extrinsic_types.rs | 43 ++++-- subxt/src/config/signed_extensions.rs | 126 ++++++++++++++---- subxt/src/utils/era.rs | 12 +- .../src/full_client/blocks/mod.rs | 9 ++ 4 files changed, 149 insertions(+), 41 deletions(-) diff --git a/subxt/src/blocks/extrinsic_types.rs b/subxt/src/blocks/extrinsic_types.rs index 8ba413c3f3..2e02dca2b2 100644 --- a/subxt/src/blocks/extrinsic_types.rs +++ b/subxt/src/blocks/extrinsic_types.rs @@ -19,6 +19,8 @@ use codec::{Compact, Decode}; use derivative::Derivative; use scale_decode::{DecodeAsFields, DecodeAsType}; +use crate::config::signed_extensions::{ChargeAssetTxPayment, ChargeTransactionPayment}; +use crate::config::SignedExtension; use std::sync::Arc; /// Trait to uniquely identify the extrinsic's identity from the runtime metadata. @@ -371,12 +373,13 @@ where } /// Returns `None` if the extrinsic is not signed. - pub fn signed_extensions(&self) -> Option { + pub fn signed_extensions(&self) -> Option> { let signed = self.signed_details.as_ref()?; let extra_bytes = &self.bytes[signed.signature_end_idx..signed.extra_end_idx]; Some(ExtrinsicSignedExtensions { bytes: extra_bytes, metadata: self.metadata.clone(), + _marker: std::marker::PhantomData, }) } @@ -610,24 +613,26 @@ impl ExtrinsicEvents { /// The signed extensions of an extrinsic. #[derive(Debug, Clone)] -pub struct ExtrinsicSignedExtensions<'a> { +pub struct ExtrinsicSignedExtensions<'a, T: Config> { bytes: &'a [u8], metadata: Metadata, + _marker: std::marker::PhantomData, } /// A single signed extension #[derive(Debug, Clone)] -pub struct ExtrinsicSignedExtension<'a> { +pub struct ExtrinsicSignedExtension<'a, T: Config> { bytes: &'a [u8], ty_id: u32, identifier: &'a str, metadata: Metadata, + _marker: std::marker::PhantomData, } -impl<'a> ExtrinsicSignedExtensions<'a> { +impl<'a, T: Config> ExtrinsicSignedExtensions<'a, T> { /// Returns an iterator ove all signed extensions, allowing access to their names, bytes and types. /// If the decoding of any signed extension fails, an error item is yielded and the iterator stops. - pub fn iter(&'a self) -> impl Iterator, Error>> { + pub fn iter(&'a self) -> impl Iterator, Error>> { let signed_extension_types = self.metadata.extrinsic().signed_extensions(); let num_signed_extensions = signed_extension_types.len(); let mut index = 0; @@ -661,17 +666,29 @@ impl<'a> ExtrinsicSignedExtensions<'a> { ty_id, identifier: extension.identifier(), metadata: self.metadata.clone(), + _marker: std::marker::PhantomData, })) }) } + fn find_by_name(&self, name: impl AsRef) -> Option> { + let signed_extension = self + .iter() + .find_map(|e| e.ok().filter(|e| e.name() == name.as_ref()))?; + Some(signed_extension) + } + + pub fn find>(&self) -> Option> { + let signed_extension = self.find_by_name(S::NAME)?; + Some(signed_extension.as_type().map_err(Into::into)) + } + /// The tip of an extrinsic, extracted from the ChargeTransactionPayment or ChargeAssetTxPayment signed extension, depending on which is present. pub fn tip(&self) -> Option { - let tip = self.iter().find_map(|e| { - e.ok().filter(|e| { - e.name() == "ChargeTransactionPayment" || e.name() == "ChargeAssetTxPayment" - }) - })?; + // Note: the overhead of iterating twice should be negligible. + let tip = self + .find_by_name(>::NAME) + .or_else(|| self.find_by_name(>::NAME))?; // Note: ChargeAssetTxPayment might have addition information in it (asset_id). // But both should start with a compact encoded u128, so this decoding is fine. let tip = Compact::::decode(&mut tip.bytes()).ok()?.0; @@ -688,7 +705,7 @@ impl<'a> ExtrinsicSignedExtensions<'a> { } } -impl<'a> ExtrinsicSignedExtension<'a> { +impl<'a, T: Config> ExtrinsicSignedExtension<'a, T> { /// The bytes representing this signed extension. pub fn bytes(&self) -> &[u8] { self.bytes @@ -719,8 +736,8 @@ impl<'a> ExtrinsicSignedExtension<'a> { self.decoded()?.to_value() } - pub fn as_type(&self) -> Result { - self.decoded()?.as_type::().map_err(Into::into) + pub fn as_type(&self) -> Result { + self.decoded()?.as_type::().map_err(Into::into) } } diff --git a/subxt/src/config/signed_extensions.rs b/subxt/src/config/signed_extensions.rs index 5b6e43d58a..508792c491 100644 --- a/subxt/src/config/signed_extensions.rs +++ b/subxt/src/config/signed_extensions.rs @@ -10,19 +10,45 @@ use super::extrinsic_params::{ExtrinsicParams, ExtrinsicParamsEncoder, ExtrinsicParamsError}; use crate::utils::Era; use crate::{client::OfflineClientT, Config}; -use codec::{Compact, Encode}; +use codec::{Compact, Decode, Encode}; use core::fmt::Debug; +use scale_decode::DecodeAsType; use std::collections::HashMap; /// A single [`SignedExtension`] has a unique name, but is otherwise the /// same as [`ExtrinsicParams`] in describing how to encode the extra and /// additional data. -pub trait SignedExtension: ExtrinsicParams { +pub trait SignedExtension: ExtrinsicParams + SignedExtensionDecoder { /// 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; } +/// Specifies the Extra and Additional data types of the signed extension. +/// `ExtrinsicParamsEncoder` is implemented for all `SignedExtensionDecoder`s. +pub trait SignedExtensionDecoder { + /// Signed Extra parameter of a Signed Extension. + /// Included in a signed extension behind the signature. + type Extra: Encode + DecodeAsType; + /// Additional Signed parameter of a Signed Extension. + /// Used as part of the payload for signing an extrinsic. + type Additional: Encode; + /// Retrieves the signed extra parameter. + fn extra(&self) -> &Self::Extra; + /// Retrieves the additional signed parameter. + fn additional(&self) -> &Self::Additional; +} + +impl ExtrinsicParamsEncoder for S { + fn encode_extra_to(&self, v: &mut Vec) { + self.extra().encode_to(v); + } + + fn encode_additional_to(&self, v: &mut Vec) { + self.additional().encode_to(v); + } +} + /// The [`CheckSpecVersion`] signed extension. #[derive(Debug)] pub struct CheckSpecVersion(u32); @@ -40,9 +66,16 @@ impl ExtrinsicParams for CheckSpecVersion { } } -impl ExtrinsicParamsEncoder for CheckSpecVersion { - fn encode_additional_to(&self, v: &mut Vec) { - self.0.encode_to(v); +impl SignedExtensionDecoder for CheckSpecVersion { + type Extra = (); + type Additional = u32; + + fn extra(&self) -> &Self::Extra { + &() + } + + fn additional(&self) -> &Self::Additional { + &self.0 } } @@ -67,9 +100,16 @@ impl ExtrinsicParams for CheckNonce { } } -impl ExtrinsicParamsEncoder for CheckNonce { - fn encode_extra_to(&self, v: &mut Vec) { - self.0.encode_to(v); +impl SignedExtensionDecoder for CheckNonce { + type Extra = Compact; + type Additional = (); + + fn extra(&self) -> &Self::Extra { + &self.0 + } + + fn additional(&self) -> &Self::Additional { + &() } } @@ -94,9 +134,16 @@ impl ExtrinsicParams for CheckTxVersion { } } -impl ExtrinsicParamsEncoder for CheckTxVersion { - fn encode_additional_to(&self, v: &mut Vec) { - self.0.encode_to(v); +impl SignedExtensionDecoder for CheckTxVersion { + type Extra = (); + type Additional = u32; + + fn extra(&self) -> &Self::Extra { + &() + } + + fn additional(&self) -> &Self::Additional { + &self.0 } } @@ -126,9 +173,16 @@ impl ExtrinsicParams for CheckGenesis { } } -impl ExtrinsicParamsEncoder for CheckGenesis { - fn encode_additional_to(&self, v: &mut Vec) { - self.0.encode_to(v); +impl SignedExtensionDecoder for CheckGenesis { + type Extra = (); + type Additional = T::Hash; + + fn extra(&self) -> &Self::Extra { + &() + } + + fn additional(&self) -> &Self::Additional { + &self.0 } } @@ -202,12 +256,16 @@ impl ExtrinsicParams for CheckMortality { } } -impl ExtrinsicParamsEncoder for CheckMortality { - fn encode_extra_to(&self, v: &mut Vec) { - self.era.encode_to(v); +impl SignedExtensionDecoder for CheckMortality { + type Extra = Era; + type Additional = T::Hash; + + fn extra(&self) -> &Self::Extra { + &self.era } - fn encode_additional_to(&self, v: &mut Vec) { - self.checkpoint.encode_to(v) + + fn additional(&self) -> &Self::Additional { + &self.checkpoint } } @@ -216,7 +274,7 @@ impl SignedExtension for CheckMortality { } /// The [`ChargeAssetTxPayment`] signed extension. -#[derive(Debug)] +#[derive(Debug, Encode, Decode, DecodeAsType)] pub struct ChargeAssetTxPayment { tip: Compact, asset_id: Option, @@ -269,9 +327,16 @@ impl ExtrinsicParams for ChargeAssetTxPayment { } } -impl ExtrinsicParamsEncoder for ChargeAssetTxPayment { - fn encode_extra_to(&self, v: &mut Vec) { - (self.tip, self.asset_id).encode_to(v); +impl SignedExtensionDecoder for ChargeAssetTxPayment { + type Extra = Self; + type Additional = (); + + fn extra(&self) -> &Self::Extra { + &self + } + + fn additional(&self) -> &Self::Additional { + &() } } @@ -280,7 +345,7 @@ impl SignedExtension for ChargeAssetTxPayment { } /// The [`ChargeTransactionPayment`] signed extension. -#[derive(Debug)] +#[derive(Debug, Encode, Decode, DecodeAsType)] pub struct ChargeTransactionPayment { tip: Compact, } @@ -317,9 +382,16 @@ impl ExtrinsicParams for ChargeTransactionPayment { } } -impl ExtrinsicParamsEncoder for ChargeTransactionPayment { - fn encode_extra_to(&self, v: &mut Vec) { - self.tip.encode_to(v); +impl SignedExtensionDecoder for ChargeTransactionPayment { + type Extra = Self; + type Additional = (); + + fn extra(&self) -> &Self::Extra { + &self + } + + fn additional(&self) -> &Self::Additional { + &() } } diff --git a/subxt/src/utils/era.rs b/subxt/src/utils/era.rs index 63bede4432..781365178e 100644 --- a/subxt/src/utils/era.rs +++ b/subxt/src/utils/era.rs @@ -4,7 +4,17 @@ // 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)] +#[derive( + PartialEq, + Default, + Eq, + Clone, + Copy, + Debug, + serde::Serialize, + serde::Deserialize, + scale_decode::DecodeAsType, +)] pub enum Era { /// The transaction is valid forever. The genesis hash must be present in the signed content. #[default] diff --git a/testing/integration-tests/src/full_client/blocks/mod.rs b/testing/integration-tests/src/full_client/blocks/mod.rs index 52bebe2e59..61fb1f0f2f 100644 --- a/testing/integration-tests/src/full_client/blocks/mod.rs +++ b/testing/integration-tests/src/full_client/blocks/mod.rs @@ -6,6 +6,7 @@ use crate::{test_context, utils::node_runtime}; use codec::{Compact, Encode}; use futures::StreamExt; +use subxt::config::signed_extensions::{ChargeAssetTxPayment, CheckNonce}; use subxt::config::DefaultExtrinsicParamsBuilder; use subxt_metadata::Metadata; use subxt_signer::sr25519::dev; @@ -286,17 +287,25 @@ async fn decode_signed_extensions_from_blocks() { let transaction1 = submit_transfer_extrinsic_and_get_it_back!(1234); let extensions1 = transaction1.signed_extensions().unwrap(); let nonce1 = extensions1.nonce().unwrap(); + let nonce1_static = extensions1.find::().0; let tip1 = extensions1.tip().unwrap(); + let tip1_static: u128 = extensions1.find::().tip.0; let transaction2 = submit_transfer_extrinsic_and_get_it_back!(5678); let extensions2 = transaction2.signed_extensions().unwrap(); let nonce2 = extensions2.nonce().unwrap(); + let nonce2_static: u64 = extensions2.find::().0; let tip2 = extensions2.tip().unwrap(); + let tip2_static: u128 = extensions2.find::().tip.0; assert_eq!(nonce1, 0); + assert_eq!(nonce1, nonce1_static); assert_eq!(tip1, 1234); + assert_eq!(tip1, tip1_static); assert_eq!(nonce2, 1); + assert_eq!(nonce2, nonce2_static); assert_eq!(tip2, 5678); + assert_eq!(tip2, tip2_static); assert_eq!(extensions1.iter().count(), expected_signed_extensions.len()); for (e, expected_name) in extensions1.iter().zip(expected_signed_extensions.iter()) {