From c0226dc1163c1c73c554e89ce5272a057c961c61 Mon Sep 17 00:00:00 2001 From: Tadeo hepperle Date: Mon, 9 Oct 2023 11:02:02 +0200 Subject: [PATCH 01/14] skeleton commit --- subxt/src/blocks/extrinsic_types.rs | 137 ++++++++++++++++++++++++---- 1 file changed, 117 insertions(+), 20 deletions(-) diff --git a/subxt/src/blocks/extrinsic_types.rs b/subxt/src/blocks/extrinsic_types.rs index ba83173bbf..1e3bc2702f 100644 --- a/subxt/src/blocks/extrinsic_types.rs +++ b/subxt/src/blocks/extrinsic_types.rs @@ -11,7 +11,11 @@ use crate::{ metadata::types::PalletMetadata, Metadata, }; +use std::marker::PhantomData; +use std::ops::Range; +use crate::dynamic::DecodedValueThunk; +use crate::metadata::DecodeWithMetadata; use crate::utils::strip_compact_prefix; use codec::Decode; use derivative::Derivative; @@ -155,12 +159,8 @@ pub struct ExtrinsicDetails { index: u32, /// Extrinsic bytes. bytes: Arc<[u8]>, - /// True if the extrinsic payload is signed. - is_signed: bool, - /// The start index in the `bytes` from which the address is encoded. - address_start_idx: usize, - /// The end index of the address in the encoded `bytes`. - address_end_idx: usize, + /// Some if the extrinsic payload is signed. + signed: Option, /// The start index in the `bytes` from which the call is encoded. call_start_idx: usize, /// The pallet index. @@ -178,6 +178,20 @@ pub struct ExtrinsicDetails { _marker: std::marker::PhantomData, } +/// Details only available in signed extrinsics. +pub struct SignedExtrinsicDetails { + /// start index of the range in `bytes` of `ExtrinsicDetails` that encodes the address. + address_start_idx: usize, + /// end index of the range in `bytes` of `ExtrinsicDetails` that encodes the address. Equivalent to signature_start_idx. + address_end_idx: usize, + /// end index of the range in `bytes` of `ExtrinsicDetails` that encodes the signature. Equivalent to extra_start_idx. + signature_end_idx: usize, + /// end index of the range in `bytes` of `ExtrinsicDetails` that encodes the signature. + extra_end_idx: usize, + /// Extrinsic part type ids (address, signature, extra) + ids: ExtrinsicPartTypeIds, +} + impl ExtrinsicDetails where T: Config, @@ -217,12 +231,10 @@ where // Skip over the first byte which denotes the version and signing. let cursor = &mut &bytes[1..]; - let mut address_start_idx = 0; - let mut address_end_idx = 0; - - if is_signed { - address_start_idx = bytes.len() - cursor.len(); - + let signed = if !is_signed { + None + } else { + let address_start_idx = bytes.len() - cursor.len(); // Skip over the address, signature and extra fields. scale_decode::visitor::decode_with_visitor( cursor, @@ -231,7 +243,7 @@ where scale_decode::visitor::IgnoreVisitor, ) .map_err(scale_decode::Error::from)?; - address_end_idx = bytes.len() - cursor.len(); + let address_end_idx = bytes.len() - cursor.len(); scale_decode::visitor::decode_with_visitor( cursor, @@ -240,6 +252,7 @@ where scale_decode::visitor::IgnoreVisitor, ) .map_err(scale_decode::Error::from)?; + let signature_end_idx = bytes.len() - cursor.len(); scale_decode::visitor::decode_with_visitor( cursor, @@ -248,7 +261,16 @@ where scale_decode::visitor::IgnoreVisitor, ) .map_err(scale_decode::Error::from)?; - } + let extra_end_idx = bytes.len() - cursor.len(); + + Some(SignedExtrinsicDetails { + address_start_idx, + address_end_idx, + signature_end_idx, + extra_end_idx, + ids, + }) + }; let call_start_idx = bytes.len() - cursor.len(); @@ -261,9 +283,7 @@ where Ok(ExtrinsicDetails { index, bytes, - is_signed, - address_start_idx, - address_end_idx, + signed, call_start_idx, pallet_index, variant_index, @@ -277,7 +297,7 @@ where /// Is the extrinsic signed? pub fn is_signed(&self) -> bool { - self.is_signed + self.signed.is_some() } /// The index of the extrinsic in the block. @@ -326,8 +346,44 @@ where /// /// Returns `None` if the extrinsic is not signed. pub fn address_bytes(&self) -> Option<&[u8]> { - self.is_signed - .then(|| &self.bytes[self.address_start_idx..self.address_end_idx]) + self.signed + .as_ref() + .map(|e| &self.bytes[e.address_start_idx..e.address_end_idx]) + } + + /// Return only the bytes of the signature for this signed extrinsic. + /// + /// # Note + /// + /// Returns `None` if the extrinsic is not signed. + pub fn signature_bytes(&self) -> Option<&[u8]> { + self.signed + .as_ref() + .map(|e| &self.bytes[e.address_end_idx..e.signature_end_idx]) + } + + /// Return only the bytes of the extra fields for this signed extrinsic. + /// + /// # Note + /// + /// Returns `None` if the extrinsic is not signed. + pub fn extra_bytes(&self) -> Option<&[u8]> { + self.signed + .as_ref() + .map(|e| &self.bytes[e.signature_end_idx..e.extra_end_idx]) + } + + /// Returns a reference to the signed extensions. + pub fn signed_extensions(&self) -> Option<()> { + let extra_bytes = self.extra_bytes()?; + let cursor: &mut &[u8] = &mut &extra_bytes[..]; + let signed = self.signed.as_ref().unwrap(); + let d = DecodedValueThunk::decode_with_metadata(cursor, signed.ids.extra, &self.metadata) + .expect("decoding err"); + + let val = d.to_value().expect("ok"); + println!("{}", val.to_string()); + Some(()) } /// The index of the pallet that the extrinsic originated from. @@ -558,6 +614,47 @@ impl ExtrinsicEvents { } } +pub struct ExtrinsicSignedExtensions<'a, T: Config> { + bytes: &'a [u8], + phantom: PhantomData, +} + +pub struct ExtrinsicSignedExtension { + phantom: PhantomData, +} + +impl<'a, T: Config> ExtrinsicSignedExtensions<'a, T> { + pub fn bytes(&self) -> &[u8] { + &self.bytes + } + + pub fn find(&self, signed_extension: impl AsRef) -> ExtrinsicSignedExtension { + todo!() + } + + pub fn tip() -> Option { + todo!() + } + + pub fn nonce() -> Option { + todo!() + } +} + +impl ExtrinsicSignedExtension { + pub fn bytes(&self) -> &[u8] { + todo!() + } + + pub fn type_id(&self) -> u32 { + todo!() + } + + pub fn value(&self) -> scale_value::Value { + todo!() + } +} + #[cfg(test)] mod tests { use super::*; From ffec0b1400f442d285b3907cb3431b2a7bef2155 Mon Sep 17 00:00:00 2001 From: Tadeo hepperle Date: Thu, 12 Oct 2023 15:32:37 +0200 Subject: [PATCH 02/14] signed extension decoding --- subxt/src/blocks/extrinsic_types.rs | 141 +++++++++++++++++++++++----- subxt/src/config/polkadot.rs | 1 + subxt/src/config/substrate.rs | 1 + 3 files changed, 119 insertions(+), 24 deletions(-) diff --git a/subxt/src/blocks/extrinsic_types.rs b/subxt/src/blocks/extrinsic_types.rs index 1e3bc2702f..62496f6222 100644 --- a/subxt/src/blocks/extrinsic_types.rs +++ b/subxt/src/blocks/extrinsic_types.rs @@ -14,12 +14,14 @@ use crate::{ use std::marker::PhantomData; use std::ops::Range; -use crate::dynamic::DecodedValueThunk; +use crate::dynamic::{DecodedValue, DecodedValueThunk}; use crate::metadata::DecodeWithMetadata; use crate::utils::strip_compact_prefix; -use codec::Decode; +use codec::{Compact, Decode}; use derivative::Derivative; use scale_decode::{DecodeAsFields, DecodeAsType}; +use scale_info::form::PortableForm; +use scale_info::{PortableRegistry, TypeDef}; use std::sync::Arc; /// Trait to uniquely identify the extrinsic's identity from the runtime metadata. @@ -374,16 +376,71 @@ where } /// Returns a reference to the signed extensions. - pub fn signed_extensions(&self) -> Option<()> { - let extra_bytes = self.extra_bytes()?; - let cursor: &mut &[u8] = &mut &extra_bytes[..]; - let signed = self.signed.as_ref().unwrap(); - let d = DecodedValueThunk::decode_with_metadata(cursor, signed.ids.extra, &self.metadata) - .expect("decoding err"); + /// + /// Returns `None` if the extrinsic is not signed. + /// Returns `Some(Err(..))` if the extrinsic is singed but something went wrong decoding the signed extensions. + pub fn signed_extensions(&self) -> Option, Error>> { + fn signed_extensions_or_err<'a, T: Config>( + extra_bytes: &'a [u8], + extra_ty_id: u32, + metadata: &'a Metadata, + ) -> Result, Error> { + let extra_type = metadata + .types() + .resolve(extra_ty_id) + .ok_or(MetadataError::TypeNotFound(extra_ty_id))?; + // the type behind this is expected to be a tuple that holds all the individual signed extensions + let TypeDef::Tuple(extra_tuple_type_def) = &extra_type.type_def else { + return Err(Error::Other( + "singed extra type def should be a tuple".into(), + )); + }; + + let mut signed_extensions: Vec> = vec![]; + + let mut cursor: &mut &[u8] = &mut &extra_bytes[..]; + let mut start_idx: usize = 0; + for field in extra_tuple_type_def.fields.iter() { + let ty_id = field.id; + let ty = metadata + .types() + .resolve(ty_id) + .ok_or(MetadataError::TypeNotFound(ty_id))?; + let name = ty.path.segments.last().ok_or_else(|| Error::Other("signed extension path segments should contain the signed extension name as the last element".into()))?; + scale_decode::visitor::decode_with_visitor( + cursor, + ty_id, + metadata.types(), + scale_decode::visitor::IgnoreVisitor, + ) + .map_err(|e| Error::Decode(e.into()))?; + let end_idx = extra_bytes.len() - cursor.len(); + let bytes = &extra_bytes[start_idx..end_idx]; + start_idx = end_idx; + signed_extensions.push(ExtrinsicSignedExtension { + bytes, + ty_id, + name, + metadata, + phantom: PhantomData, + }); + } + + Ok(ExtrinsicSignedExtensions { + bytes: extra_bytes, + signed_extensions, + metadata, + phantom: PhantomData, + }) + } - let val = d.to_value().expect("ok"); - println!("{}", val.to_string()); - Some(()) + let signed = self.signed.as_ref()?; + let extra_bytes = &self.bytes[signed.signature_end_idx..signed.extra_end_idx]; + Some(signed_extensions_or_err( + extra_bytes, + signed.ids.extra, + &self.metadata, + )) } /// The index of the pallet that the extrinsic originated from. @@ -614,44 +671,80 @@ impl ExtrinsicEvents { } } +#[derive(Debug, Clone)] pub struct ExtrinsicSignedExtensions<'a, T: Config> { + signed_extensions: Vec>, bytes: &'a [u8], + metadata: &'a Metadata, phantom: PhantomData, } -pub struct ExtrinsicSignedExtension { +#[derive(Debug, Clone)] +pub struct ExtrinsicSignedExtension<'a, T: Config> { + bytes: &'a [u8], + ty_id: u32, + name: &'a str, + metadata: &'a Metadata, phantom: PhantomData, } impl<'a, T: Config> ExtrinsicSignedExtensions<'a, T> { + /// Returns the slice of bytes pub fn bytes(&self) -> &[u8] { - &self.bytes + self.bytes } - pub fn find(&self, signed_extension: impl AsRef) -> ExtrinsicSignedExtension { - todo!() + /// Returns a slice of all signed extensions + pub fn signed_extensions(&self) -> &[ExtrinsicSignedExtension] { + &self.signed_extensions } - pub fn tip() -> Option { - todo!() + /// Get a certain signed extension by its name. + pub fn find(&self, signed_extension: impl AsRef) -> Option<&ExtrinsicSignedExtension> { + self.signed_extensions + .iter() + .find(|e| e.name == signed_extension.as_ref()) + } + + /// The tip of an extrinsic, extracted from the ChargeTransactionPayment signed extension. + pub fn tip(&self) -> Option { + let tip = self.find("ChargeTransactionPayment")?; + let tip = Compact::::decode(&mut tip.bytes()).ok()?.0; + Some(tip) } - pub fn nonce() -> Option { - todo!() + /// The nonce of the account that submitted the extrinsic, extracted from the CheckNonce signed extension. + pub fn nonce(&self) -> Option { + let nonce = self.find("CheckNonce")?; + let nonce = Compact::::decode(&mut nonce.bytes()).ok()?.0; + Some(nonce) } } -impl ExtrinsicSignedExtension { +impl<'a, T: Config> ExtrinsicSignedExtension<'a, T> { pub fn bytes(&self) -> &[u8] { - todo!() + self.bytes + } + + pub fn name(&self) -> &str { + self.name } pub fn type_id(&self) -> u32 { - todo!() + self.ty_id } - pub fn value(&self) -> scale_value::Value { - todo!() + pub fn decoded(&self) -> Result { + let decoded_value_thunk = DecodedValueThunk::decode_with_metadata( + &mut &self.bytes[..], + self.ty_id, + self.metadata, + )?; + Ok(decoded_value_thunk) + } + pub fn value(&self) -> Result { + let value = self.decoded()?.to_value()?; + Ok(value) } } diff --git a/subxt/src/config/polkadot.rs b/subxt/src/config/polkadot.rs index 5fb3390483..f47ae176f2 100644 --- a/subxt/src/config/polkadot.rs +++ b/subxt/src/config/polkadot.rs @@ -11,6 +11,7 @@ use crate::SubstrateConfig; pub use primitive_types::{H256, U256}; /// Default set of commonly used types by Polkadot nodes. +#[derive(Debug)] pub enum PolkadotConfig {} impl Config for PolkadotConfig { diff --git a/subxt/src/config/substrate.rs b/subxt/src/config/substrate.rs index 609b20a778..ea1d4a0a95 100644 --- a/subxt/src/config/substrate.rs +++ b/subxt/src/config/substrate.rs @@ -14,6 +14,7 @@ pub use primitive_types::{H256, U256}; /// Default set of commonly used types by Substrate runtimes. // Note: We only use this at the type level, so it should be impossible to // create an instance of it. +#[derive(Debug)] pub enum SubstrateConfig {} impl Config for SubstrateConfig { From b0fb96afd9ce901ab89e09bde8b611e226f940f0 Mon Sep 17 00:00:00 2001 From: Tadeo hepperle Date: Mon, 16 Oct 2023 10:09:26 +0200 Subject: [PATCH 03/14] fix some minor things --- subxt/src/blocks/extrinsic_types.rs | 26 ++++++++++++++++++++++---- 1 file changed, 22 insertions(+), 4 deletions(-) diff --git a/subxt/src/blocks/extrinsic_types.rs b/subxt/src/blocks/extrinsic_types.rs index 62496f6222..1d1a2ca9bc 100644 --- a/subxt/src/blocks/extrinsic_types.rs +++ b/subxt/src/blocks/extrinsic_types.rs @@ -12,7 +12,6 @@ use crate::{ Metadata, }; use std::marker::PhantomData; -use std::ops::Range; use crate::dynamic::{DecodedValue, DecodedValueThunk}; use crate::metadata::DecodeWithMetadata; @@ -20,8 +19,8 @@ use crate::utils::strip_compact_prefix; use codec::{Compact, Decode}; use derivative::Derivative; use scale_decode::{DecodeAsFields, DecodeAsType}; -use scale_info::form::PortableForm; -use scale_info::{PortableRegistry, TypeDef}; + +use scale_info::TypeDef; use std::sync::Arc; /// Trait to uniquely identify the extrinsic's identity from the runtime metadata. @@ -398,7 +397,7 @@ where let mut signed_extensions: Vec> = vec![]; - let mut cursor: &mut &[u8] = &mut &extra_bytes[..]; + let cursor: &mut &[u8] = &mut &extra_bytes[..]; let mut start_idx: usize = 0; for field in extra_tuple_type_def.fields.iter() { let ty_id = field.id; @@ -429,6 +428,7 @@ where Ok(ExtrinsicSignedExtensions { bytes: extra_bytes, signed_extensions, + ty_id: extra_ty_id, metadata, phantom: PhantomData, }) @@ -675,6 +675,7 @@ impl ExtrinsicEvents { pub struct ExtrinsicSignedExtensions<'a, T: Config> { signed_extensions: Vec>, bytes: &'a [u8], + ty_id: u32, metadata: &'a Metadata, phantom: PhantomData, } @@ -694,6 +695,17 @@ impl<'a, T: Config> ExtrinsicSignedExtensions<'a, T> { self.bytes } + /// Returns a DecodedValueThunk of the signed extensions type. + /// Can be used to get all signed extensions as a scale value. + pub fn decoded(&self) -> Result { + let decoded_value_thunk = DecodedValueThunk::decode_with_metadata( + &mut &self.bytes[..], + self.ty_id, + self.metadata, + )?; + Ok(decoded_value_thunk) + } + /// Returns a slice of all signed extensions pub fn signed_extensions(&self) -> &[ExtrinsicSignedExtension] { &self.signed_extensions @@ -722,18 +734,22 @@ impl<'a, T: Config> ExtrinsicSignedExtensions<'a, T> { } impl<'a, T: Config> ExtrinsicSignedExtension<'a, T> { + /// The extra bytes associated with the signed extension. pub fn bytes(&self) -> &[u8] { self.bytes } + /// The name of the signed extension. pub fn name(&self) -> &str { self.name } + /// The type id of the signed extension. pub fn type_id(&self) -> u32 { self.ty_id } + /// DecodedValueThunk representing the type of the extra of this signed extension. pub fn decoded(&self) -> Result { let decoded_value_thunk = DecodedValueThunk::decode_with_metadata( &mut &self.bytes[..], @@ -742,6 +758,8 @@ impl<'a, T: Config> ExtrinsicSignedExtension<'a, T> { )?; Ok(decoded_value_thunk) } + + /// Signed Extension as a [`scale_value::Value`] pub fn value(&self) -> Result { let value = self.decoded()?.to_value()?; Ok(value) From c87f165f71df924e9235b09521313415072ebd9a Mon Sep 17 00:00:00 2001 From: Tadeo hepperle Date: Mon, 23 Oct 2023 10:50:36 +0200 Subject: [PATCH 04/14] make api more similar to Extrinsics --- subxt/src/blocks/extrinsic_types.rs | 219 +++++++++++++--------------- subxt/src/config/polkadot.rs | 1 - subxt/src/config/substrate.rs | 1 - 3 files changed, 104 insertions(+), 117 deletions(-) diff --git a/subxt/src/blocks/extrinsic_types.rs b/subxt/src/blocks/extrinsic_types.rs index 1d1a2ca9bc..21bbd2bd67 100644 --- a/subxt/src/blocks/extrinsic_types.rs +++ b/subxt/src/blocks/extrinsic_types.rs @@ -11,7 +11,6 @@ use crate::{ metadata::types::PalletMetadata, Metadata, }; -use std::marker::PhantomData; use crate::dynamic::{DecodedValue, DecodedValueThunk}; use crate::metadata::DecodeWithMetadata; @@ -20,7 +19,6 @@ use codec::{Compact, Decode}; use derivative::Derivative; use scale_decode::{DecodeAsFields, DecodeAsType}; -use scale_info::TypeDef; use std::sync::Arc; /// Trait to uniquely identify the extrinsic's identity from the runtime metadata. @@ -161,7 +159,7 @@ pub struct ExtrinsicDetails { /// Extrinsic bytes. bytes: Arc<[u8]>, /// Some if the extrinsic payload is signed. - signed: Option, + signed_details: Option, /// The start index in the `bytes` from which the call is encoded. call_start_idx: usize, /// The pallet index. @@ -189,8 +187,6 @@ pub struct SignedExtrinsicDetails { signature_end_idx: usize, /// end index of the range in `bytes` of `ExtrinsicDetails` that encodes the signature. extra_end_idx: usize, - /// Extrinsic part type ids (address, signature, extra) - ids: ExtrinsicPartTypeIds, } impl ExtrinsicDetails @@ -232,46 +228,45 @@ where // Skip over the first byte which denotes the version and signing. let cursor = &mut &bytes[1..]; - let signed = if !is_signed { - None - } else { - let address_start_idx = bytes.len() - cursor.len(); - // Skip over the address, signature and extra fields. - scale_decode::visitor::decode_with_visitor( - cursor, - ids.address, - metadata.types(), - scale_decode::visitor::IgnoreVisitor, - ) - .map_err(scale_decode::Error::from)?; - let address_end_idx = bytes.len() - cursor.len(); - - scale_decode::visitor::decode_with_visitor( - cursor, - ids.signature, - metadata.types(), - scale_decode::visitor::IgnoreVisitor, - ) - .map_err(scale_decode::Error::from)?; - let signature_end_idx = bytes.len() - cursor.len(); - - scale_decode::visitor::decode_with_visitor( - cursor, - ids.extra, - metadata.types(), - scale_decode::visitor::IgnoreVisitor, - ) - .map_err(scale_decode::Error::from)?; - let extra_end_idx = bytes.len() - cursor.len(); - - Some(SignedExtrinsicDetails { - address_start_idx, - address_end_idx, - signature_end_idx, - extra_end_idx, - ids, + let signed_details = is_signed + .then(|| -> Result { + let address_start_idx = bytes.len() - cursor.len(); + // Skip over the address, signature and extra fields. + scale_decode::visitor::decode_with_visitor( + cursor, + ids.address, + metadata.types(), + scale_decode::visitor::IgnoreVisitor, + ) + .map_err(scale_decode::Error::from)?; + let address_end_idx = bytes.len() - cursor.len(); + + scale_decode::visitor::decode_with_visitor( + cursor, + ids.signature, + metadata.types(), + scale_decode::visitor::IgnoreVisitor, + ) + .map_err(scale_decode::Error::from)?; + let signature_end_idx = bytes.len() - cursor.len(); + + scale_decode::visitor::decode_with_visitor( + cursor, + ids.extra, + metadata.types(), + scale_decode::visitor::IgnoreVisitor, + ) + .map_err(scale_decode::Error::from)?; + let extra_end_idx = bytes.len() - cursor.len(); + + Ok(SignedExtrinsicDetails { + address_start_idx, + address_end_idx, + signature_end_idx, + extra_end_idx, + }) }) - }; + .transpose()?; let call_start_idx = bytes.len() - cursor.len(); @@ -284,7 +279,7 @@ where Ok(ExtrinsicDetails { index, bytes, - signed, + signed_details, call_start_idx, pallet_index, variant_index, @@ -298,7 +293,7 @@ where /// Is the extrinsic signed? pub fn is_signed(&self) -> bool { - self.signed.is_some() + self.signed_details.is_some() } /// The index of the extrinsic in the block. @@ -347,7 +342,7 @@ where /// /// Returns `None` if the extrinsic is not signed. pub fn address_bytes(&self) -> Option<&[u8]> { - self.signed + self.signed_details .as_ref() .map(|e| &self.bytes[e.address_start_idx..e.address_end_idx]) } @@ -358,7 +353,7 @@ where /// /// Returns `None` if the extrinsic is not signed. pub fn signature_bytes(&self) -> Option<&[u8]> { - self.signed + self.signed_details .as_ref() .map(|e| &self.bytes[e.address_end_idx..e.signature_end_idx]) } @@ -369,7 +364,7 @@ where /// /// Returns `None` if the extrinsic is not signed. pub fn extra_bytes(&self) -> Option<&[u8]> { - self.signed + self.signed_details .as_ref() .map(|e| &self.bytes[e.signature_end_idx..e.extra_end_idx]) } @@ -378,29 +373,18 @@ where /// /// Returns `None` if the extrinsic is not signed. /// Returns `Some(Err(..))` if the extrinsic is singed but something went wrong decoding the signed extensions. - pub fn signed_extensions(&self) -> Option, Error>> { - fn signed_extensions_or_err<'a, T: Config>( + pub fn signed_extensions(&self) -> Option> { + fn signed_extensions_or_err<'a>( extra_bytes: &'a [u8], - extra_ty_id: u32, metadata: &'a Metadata, - ) -> Result, Error> { - let extra_type = metadata - .types() - .resolve(extra_ty_id) - .ok_or(MetadataError::TypeNotFound(extra_ty_id))?; - // the type behind this is expected to be a tuple that holds all the individual signed extensions - let TypeDef::Tuple(extra_tuple_type_def) = &extra_type.type_def else { - return Err(Error::Other( - "singed extra type def should be a tuple".into(), - )); - }; - - let mut signed_extensions: Vec> = vec![]; + ) -> Result, Error> { + let signed_extension_types = metadata.extrinsic().signed_extensions(); + let mut signed_extensions: Vec = vec![]; let cursor: &mut &[u8] = &mut &extra_bytes[..]; let mut start_idx: usize = 0; - for field in extra_tuple_type_def.fields.iter() { - let ty_id = field.id; + for signed_extension_type in signed_extension_types { + let ty_id = signed_extension_type.extra_ty(); let ty = metadata .types() .resolve(ty_id) @@ -419,28 +403,20 @@ where signed_extensions.push(ExtrinsicSignedExtension { bytes, ty_id, - name, - metadata, - phantom: PhantomData, + identifier: name, + metadata: metadata.clone(), }); } Ok(ExtrinsicSignedExtensions { bytes: extra_bytes, - signed_extensions, - ty_id: extra_ty_id, - metadata, - phantom: PhantomData, + metadata: metadata.clone(), }) } - let signed = self.signed.as_ref()?; + let signed = self.signed_details.as_ref()?; let extra_bytes = &self.bytes[signed.signature_end_idx..signed.extra_end_idx]; - Some(signed_extensions_or_err( - extra_bytes, - signed.ids.extra, - &self.metadata, - )) + Some(signed_extensions_or_err(extra_bytes, &self.metadata)) } /// The index of the pallet that the extrinsic originated from. @@ -672,50 +648,63 @@ impl ExtrinsicEvents { } #[derive(Debug, Clone)] -pub struct ExtrinsicSignedExtensions<'a, T: Config> { - signed_extensions: Vec>, +pub struct ExtrinsicSignedExtensions<'a> { bytes: &'a [u8], - ty_id: u32, - metadata: &'a Metadata, - phantom: PhantomData, + metadata: Metadata, } #[derive(Debug, Clone)] -pub struct ExtrinsicSignedExtension<'a, T: Config> { +pub struct ExtrinsicSignedExtension<'a> { bytes: &'a [u8], ty_id: u32, - name: &'a str, - metadata: &'a Metadata, - phantom: PhantomData, + identifier: &'a str, + metadata: Metadata, } -impl<'a, T: Config> ExtrinsicSignedExtensions<'a, T> { - /// Returns the slice of bytes - pub fn bytes(&self) -> &[u8] { - self.bytes +impl<'a> ExtrinsicSignedExtensions<'a> { + /// Get a certain signed extension by its name. + pub fn find(&self, signed_extension: impl AsRef) -> Option { + self.iter() + .find_map(|e| e.ok().filter(|e| e.name() == signed_extension.as_ref())) } - /// Returns a DecodedValueThunk of the signed extensions type. - /// Can be used to get all signed extensions as a scale value. - pub fn decoded(&self) -> Result { - let decoded_value_thunk = DecodedValueThunk::decode_with_metadata( - &mut &self.bytes[..], - self.ty_id, - self.metadata, - )?; - Ok(decoded_value_thunk) - } + 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; + let mut byte_start_idx = 0; - /// Returns a slice of all signed extensions - pub fn signed_extensions(&self) -> &[ExtrinsicSignedExtension] { - &self.signed_extensions - } + std::iter::from_fn(move || { + if index == num_signed_extensions { + None + } else { + let extension = &signed_extension_types[index]; + let ty_id = extension.extra_ty(); + let cursor = &mut &self.bytes[byte_start_idx..]; + if let Err(err) = scale_decode::visitor::decode_with_visitor( + cursor, + ty_id, + self.metadata.types(), + scale_decode::visitor::IgnoreVisitor, + ) + .map_err(|e| Error::Decode(e.into())) + { + index = num_signed_extensions; // (Such that None is returned in next iteration) + return Some(Err(err)); + } - /// Get a certain signed extension by its name. - pub fn find(&self, signed_extension: impl AsRef) -> Option<&ExtrinsicSignedExtension> { - self.signed_extensions - .iter() - .find(|e| e.name == signed_extension.as_ref()) + let byte_end_idx = self.bytes.len() - cursor.len(); + byte_start_idx = byte_end_idx; + index += 1; + + Some(Ok(ExtrinsicSignedExtension { + bytes: &self.bytes[byte_start_idx..byte_end_idx], + ty_id, + identifier: extension.identifier(), + metadata: self.metadata.clone(), + })) + } + }) } /// The tip of an extrinsic, extracted from the ChargeTransactionPayment signed extension. @@ -733,7 +722,7 @@ impl<'a, T: Config> ExtrinsicSignedExtensions<'a, T> { } } -impl<'a, T: Config> ExtrinsicSignedExtension<'a, T> { +impl<'a> ExtrinsicSignedExtension<'a> { /// The extra bytes associated with the signed extension. pub fn bytes(&self) -> &[u8] { self.bytes @@ -741,7 +730,7 @@ impl<'a, T: Config> ExtrinsicSignedExtension<'a, T> { /// The name of the signed extension. pub fn name(&self) -> &str { - self.name + self.identifier } /// The type id of the signed extension. @@ -754,7 +743,7 @@ impl<'a, T: Config> ExtrinsicSignedExtension<'a, T> { let decoded_value_thunk = DecodedValueThunk::decode_with_metadata( &mut &self.bytes[..], self.ty_id, - self.metadata, + &self.metadata, )?; Ok(decoded_value_thunk) } diff --git a/subxt/src/config/polkadot.rs b/subxt/src/config/polkadot.rs index f47ae176f2..5fb3390483 100644 --- a/subxt/src/config/polkadot.rs +++ b/subxt/src/config/polkadot.rs @@ -11,7 +11,6 @@ use crate::SubstrateConfig; pub use primitive_types::{H256, U256}; /// Default set of commonly used types by Polkadot nodes. -#[derive(Debug)] pub enum PolkadotConfig {} impl Config for PolkadotConfig { diff --git a/subxt/src/config/substrate.rs b/subxt/src/config/substrate.rs index ea1d4a0a95..609b20a778 100644 --- a/subxt/src/config/substrate.rs +++ b/subxt/src/config/substrate.rs @@ -14,7 +14,6 @@ pub use primitive_types::{H256, U256}; /// Default set of commonly used types by Substrate runtimes. // Note: We only use this at the type level, so it should be impossible to // create an instance of it. -#[derive(Debug)] pub enum SubstrateConfig {} impl Config for SubstrateConfig { From 4ed554d39734843f2c77f8b74d29b6474b046e0c Mon Sep 17 00:00:00 2001 From: Tadeo hepperle Date: Mon, 23 Oct 2023 14:42:23 +0200 Subject: [PATCH 05/14] defer decoding of signed extensions --- subxt/src/blocks/extrinsic_types.rs | 50 +++-------------------------- 1 file changed, 5 insertions(+), 45 deletions(-) diff --git a/subxt/src/blocks/extrinsic_types.rs b/subxt/src/blocks/extrinsic_types.rs index 21bbd2bd67..b01affb4d1 100644 --- a/subxt/src/blocks/extrinsic_types.rs +++ b/subxt/src/blocks/extrinsic_types.rs @@ -369,54 +369,14 @@ where .map(|e| &self.bytes[e.signature_end_idx..e.extra_end_idx]) } - /// Returns a reference to the signed extensions. - /// /// Returns `None` if the extrinsic is not signed. - /// Returns `Some(Err(..))` if the extrinsic is singed but something went wrong decoding the signed extensions. - pub fn signed_extensions(&self) -> Option> { - fn signed_extensions_or_err<'a>( - extra_bytes: &'a [u8], - metadata: &'a Metadata, - ) -> Result, Error> { - let signed_extension_types = metadata.extrinsic().signed_extensions(); - let mut signed_extensions: Vec = vec![]; - - let cursor: &mut &[u8] = &mut &extra_bytes[..]; - let mut start_idx: usize = 0; - for signed_extension_type in signed_extension_types { - let ty_id = signed_extension_type.extra_ty(); - let ty = metadata - .types() - .resolve(ty_id) - .ok_or(MetadataError::TypeNotFound(ty_id))?; - let name = ty.path.segments.last().ok_or_else(|| Error::Other("signed extension path segments should contain the signed extension name as the last element".into()))?; - scale_decode::visitor::decode_with_visitor( - cursor, - ty_id, - metadata.types(), - scale_decode::visitor::IgnoreVisitor, - ) - .map_err(|e| Error::Decode(e.into()))?; - let end_idx = extra_bytes.len() - cursor.len(); - let bytes = &extra_bytes[start_idx..end_idx]; - start_idx = end_idx; - signed_extensions.push(ExtrinsicSignedExtension { - bytes, - ty_id, - identifier: name, - metadata: metadata.clone(), - }); - } - - Ok(ExtrinsicSignedExtensions { - bytes: extra_bytes, - metadata: metadata.clone(), - }) - } - + 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(signed_extensions_or_err(extra_bytes, &self.metadata)) + Some(ExtrinsicSignedExtensions { + bytes: extra_bytes, + metadata: self.metadata.clone(), + }) } /// The index of the pallet that the extrinsic originated from. From 460bfaf55e7d0c4091a034dc3241b6e7eff64eca Mon Sep 17 00:00:00 2001 From: Tadeo hepperle Date: Mon, 23 Oct 2023 15:46:06 +0200 Subject: [PATCH 06/14] fix byte slices --- subxt/src/blocks/extrinsic_types.rs | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/subxt/src/blocks/extrinsic_types.rs b/subxt/src/blocks/extrinsic_types.rs index b01affb4d1..de864be178 100644 --- a/subxt/src/blocks/extrinsic_types.rs +++ b/subxt/src/blocks/extrinsic_types.rs @@ -649,16 +649,15 @@ impl<'a> ExtrinsicSignedExtensions<'a> { ) .map_err(|e| Error::Decode(e.into())) { - index = num_signed_extensions; // (Such that None is returned in next iteration) + index = num_signed_extensions; // (such that None is returned in next iteration) return Some(Err(err)); } - let byte_end_idx = self.bytes.len() - cursor.len(); + let bytes = &self.bytes[byte_start_idx..byte_end_idx]; byte_start_idx = byte_end_idx; index += 1; - Some(Ok(ExtrinsicSignedExtension { - bytes: &self.bytes[byte_start_idx..byte_end_idx], + bytes, ty_id, identifier: extension.identifier(), metadata: self.metadata.clone(), From ea59e6c5b5c48e8d2be88eeeb1fc69017f0391d8 Mon Sep 17 00:00:00 2001 From: Tadeo hepperle Date: Mon, 23 Oct 2023 17:50:37 +0200 Subject: [PATCH 07/14] add test for nonce signed extension --- .../src/full_client/blocks/mod.rs | 51 +++++++++++++++++++ 1 file changed, 51 insertions(+) diff --git a/testing/integration-tests/src/full_client/blocks/mod.rs b/testing/integration-tests/src/full_client/blocks/mod.rs index 3ea80a4afe..a8b1e0deb0 100644 --- a/testing/integration-tests/src/full_client/blocks/mod.rs +++ b/testing/integration-tests/src/full_client/blocks/mod.rs @@ -227,3 +227,54 @@ async fn fetch_block_and_decode_extrinsic_details() { assert_eq!(ext.value, 10_000); assert!(tx.is_signed()); } + +#[tokio::test] +async fn decode_signed_extensions_from_blocks() { + let ctx = test_context().await; + let api = ctx.client(); + let alice = dev::alice(); + let bob = dev::bob(); + + let submit_tranfer_extrinsic_and_get_it_back = || async { + let tx = node_runtime::tx() + .balances() + .transfer_allow_death(bob.public_key().into(), 10_000); + + let signed_extrinsic = api + .tx() + .create_signed(&tx, &alice, Default::default()) + .await + .unwrap(); + + let in_block = signed_extrinsic + .submit_and_watch() + .await + .unwrap() + .wait_for_in_block() + .await + .unwrap(); + + let block_hash = in_block.block_hash(); + let block = api.blocks().at(block_hash).await.unwrap(); + let extrinsics = block.extrinsics().await.unwrap(); + let extrinsic_details = extrinsics.iter().next().unwrap().unwrap(); + extrinsic_details + }; + + let first_transaction = submit_tranfer_extrinsic_and_get_it_back().await; + let first_transaction_nonce = first_transaction + .signed_extensions() + .unwrap() + .nonce() + .unwrap(); + + let second_transaction = submit_tranfer_extrinsic_and_get_it_back().await; + let second_transaction_nonce = first_transaction + .signed_extensions() + .unwrap() + .nonce() + .unwrap(); + + assert_eq!(first_transaction_nonce, 0); + assert_eq!(second_transaction_nonce, 1); +} From 3dad99711e3d83eea93931ce7d99daf0d6dfd52d Mon Sep 17 00:00:00 2001 From: Tadeo hepperle Date: Tue, 24 Oct 2023 10:32:00 +0200 Subject: [PATCH 08/14] adjust test and extend for tip --- subxt/src/blocks/extrinsic_types.rs | 2 +- .../src/full_client/blocks/mod.rs | 91 +++++++++++-------- 2 files changed, 52 insertions(+), 41 deletions(-) diff --git a/subxt/src/blocks/extrinsic_types.rs b/subxt/src/blocks/extrinsic_types.rs index de864be178..3ca0f1b50b 100644 --- a/subxt/src/blocks/extrinsic_types.rs +++ b/subxt/src/blocks/extrinsic_types.rs @@ -668,7 +668,7 @@ impl<'a> ExtrinsicSignedExtensions<'a> { /// The tip of an extrinsic, extracted from the ChargeTransactionPayment signed extension. pub fn tip(&self) -> Option { - let tip = self.find("ChargeTransactionPayment")?; + let tip = self.find("ChargeAssetTxPayment")?; let tip = Compact::::decode(&mut tip.bytes()).ok()?.0; Some(tip) } diff --git a/testing/integration-tests/src/full_client/blocks/mod.rs b/testing/integration-tests/src/full_client/blocks/mod.rs index a8b1e0deb0..14d16c272a 100644 --- a/testing/integration-tests/src/full_client/blocks/mod.rs +++ b/testing/integration-tests/src/full_client/blocks/mod.rs @@ -2,9 +2,13 @@ // This file is dual-licensed as Apache-2.0 or GPL-3.0. // see LICENSE for license details. +use crate::utils::TestContext; use crate::{test_context, utils::node_runtime}; use codec::{Compact, Encode}; use futures::StreamExt; +use std::future::Future; +use std::time::Duration; +use subxt::config::DefaultExtrinsicParamsBuilder; use subxt_metadata::Metadata; use subxt_signer::sr25519::dev; @@ -235,46 +239,53 @@ async fn decode_signed_extensions_from_blocks() { let alice = dev::alice(); let bob = dev::bob(); - let submit_tranfer_extrinsic_and_get_it_back = || async { - let tx = node_runtime::tx() - .balances() - .transfer_allow_death(bob.public_key().into(), 10_000); - - let signed_extrinsic = api - .tx() - .create_signed(&tx, &alice, Default::default()) - .await - .unwrap(); - - let in_block = signed_extrinsic - .submit_and_watch() - .await - .unwrap() - .wait_for_in_block() - .await - .unwrap(); - - let block_hash = in_block.block_hash(); - let block = api.blocks().at(block_hash).await.unwrap(); - let extrinsics = block.extrinsics().await.unwrap(); - let extrinsic_details = extrinsics.iter().next().unwrap().unwrap(); - extrinsic_details - }; - - let first_transaction = submit_tranfer_extrinsic_and_get_it_back().await; - let first_transaction_nonce = first_transaction - .signed_extensions() - .unwrap() - .nonce() - .unwrap(); + macro_rules! submit_transfer_extrinsic_and_get_it_back { + ($tip:expr) => {{ + let tx = node_runtime::tx() + .balances() + .transfer_allow_death(bob.public_key().into(), 10_000); + + let signed_extrinsic = api + .tx() + .create_signed( + &tx, + &alice, + DefaultExtrinsicParamsBuilder::new().tip($tip).build(), + ) + .await + .unwrap(); + + let in_block = signed_extrinsic + .submit_and_watch() + .await + .unwrap() + .wait_for_finalized() + .await + .unwrap(); + + let block_hash = in_block.block_hash(); + let block = api.blocks().at(block_hash).await.unwrap(); + let extrinsics = block.extrinsics().await.unwrap(); + let extrinsic_details = extrinsics + .iter() + .find_map(|e| e.ok().filter(|e| e.is_signed())) + .unwrap(); + extrinsic_details + }}; + } - let second_transaction = submit_tranfer_extrinsic_and_get_it_back().await; - let second_transaction_nonce = first_transaction - .signed_extensions() - .unwrap() - .nonce() - .unwrap(); + let transaction1 = submit_transfer_extrinsic_and_get_it_back!(1234); + let extensions1 = transaction1.signed_extensions().unwrap(); + let nonce1 = extensions1.nonce().unwrap(); + let tip1 = extensions1.tip().unwrap(); + + let transaction2 = submit_transfer_extrinsic_and_get_it_back!(5678); + let extensions2 = transaction2.signed_extensions().unwrap(); + let nonce2 = extensions2.nonce().unwrap(); + let tip2 = extensions2.tip().unwrap(); - assert_eq!(first_transaction_nonce, 0); - assert_eq!(second_transaction_nonce, 1); + assert_eq!(nonce1, 0); + assert_eq!(tip1, 1234); + assert_eq!(nonce2, 1); + assert_eq!(tip2, 5678); } From 0eabab961805ef983479a4dc2bf54e35a8c922d4 Mon Sep 17 00:00:00 2001 From: Tadeo hepperle Date: Tue, 24 Oct 2023 13:18:20 +0200 Subject: [PATCH 09/14] clippy --- testing/integration-tests/src/full_client/blocks/mod.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/testing/integration-tests/src/full_client/blocks/mod.rs b/testing/integration-tests/src/full_client/blocks/mod.rs index 14d16c272a..c9278aa0c4 100644 --- a/testing/integration-tests/src/full_client/blocks/mod.rs +++ b/testing/integration-tests/src/full_client/blocks/mod.rs @@ -2,12 +2,12 @@ // This file is dual-licensed as Apache-2.0 or GPL-3.0. // see LICENSE for license details. -use crate::utils::TestContext; + use crate::{test_context, utils::node_runtime}; use codec::{Compact, Encode}; use futures::StreamExt; -use std::future::Future; -use std::time::Duration; + + use subxt::config::DefaultExtrinsicParamsBuilder; use subxt_metadata::Metadata; use subxt_signer::sr25519::dev; From 58fd6543d6f45c4840c4525e62706cb1d18cad30 Mon Sep 17 00:00:00 2001 From: Tadeo hepperle Date: Tue, 24 Oct 2023 13:50:01 +0200 Subject: [PATCH 10/14] support both ChargeTransactionPayment and ChargeAssetTxPayment --- subxt/src/blocks/extrinsic_types.rs | 6 +++++- testing/integration-tests/src/full_client/blocks/mod.rs | 2 -- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/subxt/src/blocks/extrinsic_types.rs b/subxt/src/blocks/extrinsic_types.rs index 3ca0f1b50b..543577b8f0 100644 --- a/subxt/src/blocks/extrinsic_types.rs +++ b/subxt/src/blocks/extrinsic_types.rs @@ -668,7 +668,11 @@ impl<'a> ExtrinsicSignedExtensions<'a> { /// The tip of an extrinsic, extracted from the ChargeTransactionPayment signed extension. pub fn tip(&self) -> Option { - let tip = self.find("ChargeAssetTxPayment")?; + let tip = self + .find("ChargeTransactionPayment") + .or_else(|| self.find("ChargeAssetTxPayment"))?; + // 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; Some(tip) } diff --git a/testing/integration-tests/src/full_client/blocks/mod.rs b/testing/integration-tests/src/full_client/blocks/mod.rs index c9278aa0c4..209f76bef2 100644 --- a/testing/integration-tests/src/full_client/blocks/mod.rs +++ b/testing/integration-tests/src/full_client/blocks/mod.rs @@ -2,12 +2,10 @@ // This file is dual-licensed as Apache-2.0 or GPL-3.0. // see LICENSE for license details. - use crate::{test_context, utils::node_runtime}; use codec::{Compact, Encode}; use futures::StreamExt; - use subxt::config::DefaultExtrinsicParamsBuilder; use subxt_metadata::Metadata; use subxt_signer::sr25519::dev; From 1dd8f53656e84341cf07c4e0b443a77673380c4f Mon Sep 17 00:00:00 2001 From: Tadeo hepperle Date: Wed, 25 Oct 2023 19:38:30 +0200 Subject: [PATCH 11/14] address PR comments --- subxt/src/blocks/extrinsic_types.rs | 98 ++++++++++--------- .../src/full_client/blocks/mod.rs | 20 ++++ 2 files changed, 72 insertions(+), 46 deletions(-) diff --git a/subxt/src/blocks/extrinsic_types.rs b/subxt/src/blocks/extrinsic_types.rs index 543577b8f0..8ba413c3f3 100644 --- a/subxt/src/blocks/extrinsic_types.rs +++ b/subxt/src/blocks/extrinsic_types.rs @@ -358,12 +358,13 @@ where .map(|e| &self.bytes[e.address_end_idx..e.signature_end_idx]) } - /// Return only the bytes of the extra fields for this signed extrinsic. + /// Returns the signed extension `extra` bytes of the extrinsic. + /// Each signed extension has an `extra` type (May be zero-sized). + /// These bytes are the scale encoded `extra` fields of each signed extension in order of the signed extensions. + /// They do *not* include the `additional` signed bytes that are used as part of the payload that is signed. /// - /// # Note - /// - /// Returns `None` if the extrinsic is not signed. - pub fn extra_bytes(&self) -> Option<&[u8]> { + /// Note: Returns `None` if the extrinsic is not signed. + pub fn signed_extensions_bytes(&self) -> Option<&[u8]> { self.signed_details .as_ref() .map(|e| &self.bytes[e.signature_end_idx..e.extra_end_idx]) @@ -607,12 +608,14 @@ impl ExtrinsicEvents { } } +/// The signed extensions of an extrinsic. #[derive(Debug, Clone)] pub struct ExtrinsicSignedExtensions<'a> { bytes: &'a [u8], metadata: Metadata, } +/// A single signed extension #[derive(Debug, Clone)] pub struct ExtrinsicSignedExtension<'a> { bytes: &'a [u8], @@ -622,12 +625,8 @@ pub struct ExtrinsicSignedExtension<'a> { } impl<'a> ExtrinsicSignedExtensions<'a> { - /// Get a certain signed extension by its name. - pub fn find(&self, signed_extension: impl AsRef) -> Option { - self.iter() - .find_map(|e| e.ok().filter(|e| e.name() == signed_extension.as_ref())) - } - + /// 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>> { let signed_extension_types = self.metadata.extrinsic().signed_extensions(); let num_signed_extensions = signed_extension_types.len(); @@ -636,41 +635,43 @@ impl<'a> ExtrinsicSignedExtensions<'a> { std::iter::from_fn(move || { if index == num_signed_extensions { - None - } else { - let extension = &signed_extension_types[index]; - let ty_id = extension.extra_ty(); - let cursor = &mut &self.bytes[byte_start_idx..]; - if let Err(err) = scale_decode::visitor::decode_with_visitor( - cursor, - ty_id, - self.metadata.types(), - scale_decode::visitor::IgnoreVisitor, - ) - .map_err(|e| Error::Decode(e.into())) - { - index = num_signed_extensions; // (such that None is returned in next iteration) - return Some(Err(err)); - } - let byte_end_idx = self.bytes.len() - cursor.len(); - let bytes = &self.bytes[byte_start_idx..byte_end_idx]; - byte_start_idx = byte_end_idx; - index += 1; - Some(Ok(ExtrinsicSignedExtension { - bytes, - ty_id, - identifier: extension.identifier(), - metadata: self.metadata.clone(), - })) + return None; + } + + let extension = &signed_extension_types[index]; + let ty_id = extension.extra_ty(); + let cursor = &mut &self.bytes[byte_start_idx..]; + if let Err(err) = scale_decode::visitor::decode_with_visitor( + cursor, + ty_id, + self.metadata.types(), + scale_decode::visitor::IgnoreVisitor, + ) + .map_err(|e| Error::Decode(e.into())) + { + index = num_signed_extensions; // (such that None is returned in next iteration) + return Some(Err(err)); } + let byte_end_idx = self.bytes.len() - cursor.len(); + let bytes = &self.bytes[byte_start_idx..byte_end_idx]; + byte_start_idx = byte_end_idx; + index += 1; + Some(Ok(ExtrinsicSignedExtension { + bytes, + ty_id, + identifier: extension.identifier(), + metadata: self.metadata.clone(), + })) }) } - /// The tip of an extrinsic, extracted from the ChargeTransactionPayment signed extension. + /// 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 - .find("ChargeTransactionPayment") - .or_else(|| self.find("ChargeAssetTxPayment"))?; + let tip = self.iter().find_map(|e| { + e.ok().filter(|e| { + e.name() == "ChargeTransactionPayment" || e.name() == "ChargeAssetTxPayment" + }) + })?; // 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; @@ -679,14 +680,16 @@ impl<'a> ExtrinsicSignedExtensions<'a> { /// The nonce of the account that submitted the extrinsic, extracted from the CheckNonce signed extension. pub fn nonce(&self) -> Option { - let nonce = self.find("CheckNonce")?; + let nonce = self + .iter() + .find_map(|e| e.ok().filter(|e| e.name() == "CheckNonce"))?; let nonce = Compact::::decode(&mut nonce.bytes()).ok()?.0; Some(nonce) } } impl<'a> ExtrinsicSignedExtension<'a> { - /// The extra bytes associated with the signed extension. + /// The bytes representing this signed extension. pub fn bytes(&self) -> &[u8] { self.bytes } @@ -702,7 +705,7 @@ impl<'a> ExtrinsicSignedExtension<'a> { } /// DecodedValueThunk representing the type of the extra of this signed extension. - pub fn decoded(&self) -> Result { + fn decoded(&self) -> Result { let decoded_value_thunk = DecodedValueThunk::decode_with_metadata( &mut &self.bytes[..], self.ty_id, @@ -713,8 +716,11 @@ impl<'a> ExtrinsicSignedExtension<'a> { /// Signed Extension as a [`scale_value::Value`] pub fn value(&self) -> Result { - let value = self.decoded()?.to_value()?; - Ok(value) + self.decoded()?.to_value() + } + + pub fn as_type(&self) -> Result { + self.decoded()?.as_type::().map_err(Into::into) } } diff --git a/testing/integration-tests/src/full_client/blocks/mod.rs b/testing/integration-tests/src/full_client/blocks/mod.rs index 209f76bef2..52bebe2e59 100644 --- a/testing/integration-tests/src/full_client/blocks/mod.rs +++ b/testing/integration-tests/src/full_client/blocks/mod.rs @@ -272,6 +272,17 @@ async fn decode_signed_extensions_from_blocks() { }}; } + let expected_signed_extensions = [ + "CheckNonZeroSender", + "CheckSpecVersion", + "CheckTxVersion", + "CheckGenesis", + "CheckMortality", + "CheckNonce", + "CheckWeight", + "ChargeAssetTxPayment", + ]; + let transaction1 = submit_transfer_extrinsic_and_get_it_back!(1234); let extensions1 = transaction1.signed_extensions().unwrap(); let nonce1 = extensions1.nonce().unwrap(); @@ -286,4 +297,13 @@ async fn decode_signed_extensions_from_blocks() { assert_eq!(tip1, 1234); assert_eq!(nonce2, 1); assert_eq!(tip2, 5678); + + assert_eq!(extensions1.iter().count(), expected_signed_extensions.len()); + for (e, expected_name) in extensions1.iter().zip(expected_signed_extensions.iter()) { + assert_eq!(e.unwrap().name(), *expected_name); + } + assert_eq!(extensions2.iter().count(), expected_signed_extensions.len()); + for (e, expected_name) in extensions2.iter().zip(expected_signed_extensions.iter()) { + assert_eq!(e.unwrap().name(), *expected_name); + } } From 64b5c90852bfca7dfa00c8dc80e2818f05075bdb Mon Sep 17 00:00:00 2001 From: James Wilson Date: Fri, 27 Oct 2023 16:06:37 +0100 Subject: [PATCH 12/14] Extend lifetimes, expose pub structs, remove as_type --- subxt/src/blocks/extrinsic_types.rs | 73 +++++++++++++---------------- subxt/src/blocks/mod.rs | 5 +- 2 files changed, 37 insertions(+), 41 deletions(-) diff --git a/subxt/src/blocks/extrinsic_types.rs b/subxt/src/blocks/extrinsic_types.rs index 8ba413c3f3..3bf4f87acc 100644 --- a/subxt/src/blocks/extrinsic_types.rs +++ b/subxt/src/blocks/extrinsic_types.rs @@ -12,8 +12,7 @@ use crate::{ Metadata, }; -use crate::dynamic::{DecodedValue, DecodedValueThunk}; -use crate::metadata::DecodeWithMetadata; +use crate::dynamic::DecodedValue; use crate::utils::strip_compact_prefix; use codec::{Compact, Decode}; use derivative::Derivative; @@ -371,12 +370,12 @@ 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(), + metadata: &self.metadata, }) } @@ -612,24 +611,17 @@ impl ExtrinsicEvents { #[derive(Debug, Clone)] pub struct ExtrinsicSignedExtensions<'a> { bytes: &'a [u8], - metadata: Metadata, -} - -/// A single signed extension -#[derive(Debug, Clone)] -pub struct ExtrinsicSignedExtension<'a> { - bytes: &'a [u8], - ty_id: u32, - identifier: &'a str, - metadata: Metadata, + metadata: &'a Metadata, } impl<'a> ExtrinsicSignedExtensions<'a> { - /// Returns an iterator ove all signed extensions, allowing access to their names, bytes and types. + /// Returns an iterator over each of the signed extension details of the extrinsic. /// 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(&self) -> impl Iterator, Error>> { let signed_extension_types = self.metadata.extrinsic().signed_extensions(); let num_signed_extensions = signed_extension_types.len(); + let bytes = self.bytes; + let metadata = self.metadata; let mut index = 0; let mut byte_start_idx = 0; @@ -640,11 +632,11 @@ impl<'a> ExtrinsicSignedExtensions<'a> { let extension = &signed_extension_types[index]; let ty_id = extension.extra_ty(); - let cursor = &mut &self.bytes[byte_start_idx..]; + let cursor = &mut &bytes[byte_start_idx..]; if let Err(err) = scale_decode::visitor::decode_with_visitor( cursor, ty_id, - self.metadata.types(), + metadata.types(), scale_decode::visitor::IgnoreVisitor, ) .map_err(|e| Error::Decode(e.into())) @@ -652,33 +644,36 @@ impl<'a> ExtrinsicSignedExtensions<'a> { index = num_signed_extensions; // (such that None is returned in next iteration) return Some(Err(err)); } - let byte_end_idx = self.bytes.len() - cursor.len(); - let bytes = &self.bytes[byte_start_idx..byte_end_idx]; + let byte_end_idx = bytes.len() - cursor.len(); + let bytes = &bytes[byte_start_idx..byte_end_idx]; byte_start_idx = byte_end_idx; index += 1; Some(Ok(ExtrinsicSignedExtension { bytes, ty_id, identifier: extension.identifier(), - metadata: self.metadata.clone(), + metadata, })) }) } - /// The tip of an extrinsic, extracted from the ChargeTransactionPayment or ChargeAssetTxPayment signed extension, depending on which is present. + /// 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: ChargeAssetTxPayment might have addition information in it (asset_id). - // But both should start with a compact encoded u128, so this decoding is fine. + // But both should start with a compact encoded u128, so this decoding is fine. let tip = Compact::::decode(&mut tip.bytes()).ok()?.0; Some(tip) } - /// The nonce of the account that submitted the extrinsic, extracted from the CheckNonce signed extension. + /// The nonce of the account that submitted the extrinsic, extracted from the + /// CheckNonce signed extension. pub fn nonce(&self) -> Option { let nonce = self .iter() @@ -688,14 +683,23 @@ impl<'a> ExtrinsicSignedExtensions<'a> { } } +/// A single signed extension +#[derive(Debug, Clone)] +pub struct ExtrinsicSignedExtension<'a> { + bytes: &'a [u8], + ty_id: u32, + identifier: &'a str, + metadata: &'a Metadata, +} + impl<'a> ExtrinsicSignedExtension<'a> { /// The bytes representing this signed extension. - pub fn bytes(&self) -> &[u8] { + pub fn bytes(&self) -> &'a [u8] { self.bytes } /// The name of the signed extension. - pub fn name(&self) -> &str { + pub fn name(&self) -> &'a str { self.identifier } @@ -704,23 +708,12 @@ impl<'a> ExtrinsicSignedExtension<'a> { self.ty_id } - /// DecodedValueThunk representing the type of the extra of this signed extension. - fn decoded(&self) -> Result { - let decoded_value_thunk = DecodedValueThunk::decode_with_metadata( - &mut &self.bytes[..], - self.ty_id, - &self.metadata, - )?; - Ok(decoded_value_thunk) - } - /// Signed Extension as a [`scale_value::Value`] pub fn value(&self) -> Result { - self.decoded()?.to_value() - } + let value = + DecodedValue::decode_as_type(&mut &self.bytes[..], self.ty_id, self.metadata.types())?; - pub fn as_type(&self) -> Result { - self.decoded()?.as_type::().map_err(Into::into) + Ok(value) } } diff --git a/subxt/src/blocks/mod.rs b/subxt/src/blocks/mod.rs index fd06594e60..7cf2501b9d 100644 --- a/subxt/src/blocks/mod.rs +++ b/subxt/src/blocks/mod.rs @@ -13,7 +13,10 @@ pub use crate::backend::BlockRef; pub use block_types::Block; pub use blocks_client::BlocksClient; -pub use extrinsic_types::{ExtrinsicDetails, ExtrinsicEvents, Extrinsics, StaticExtrinsic}; +pub use extrinsic_types::{ + ExtrinsicDetails, ExtrinsicEvents, ExtrinsicSignedExtension, ExtrinsicSignedExtensions, + Extrinsics, StaticExtrinsic, +}; // We get account nonce info in tx_client, too, so re-use the logic: pub(crate) use block_types::get_account_nonce; From fe1db8c154f1e051c85c2d58a0da560f95ab2e4f Mon Sep 17 00:00:00 2001 From: Tadeo hepperle Date: Mon, 30 Oct 2023 16:57:44 +0100 Subject: [PATCH 13/14] add signed extensions to block subscribing example --- subxt/examples/blocks_subscribing.rs | 11 +++++++++++ subxt/src/blocks/extrinsic_types.rs | 17 ++++++++--------- 2 files changed, 19 insertions(+), 9 deletions(-) diff --git a/subxt/examples/blocks_subscribing.rs b/subxt/examples/blocks_subscribing.rs index 4f06221484..80b732795c 100644 --- a/subxt/examples/blocks_subscribing.rs +++ b/subxt/examples/blocks_subscribing.rs @@ -34,6 +34,17 @@ async fn main() -> Result<(), Box> { let decoded_ext = ext.as_root_extrinsic::(); println!(" Extrinsic #{idx}:"); + if let Some(signed_extensions) = ext.signed_extensions() { + println!(" Signed Extensions:"); + for signed_extension in signed_extensions.iter() { + let signed_extension = signed_extension?; + let name = signed_extension.name(); + let value = signed_extension.value()?.to_string(); + println!(" {name}: {value}"); + } + } else { + println!(" No Signed Extensions"); + } println!(" Bytes: {bytes_hex}"); println!(" Decoded: {decoded_ext:?}"); println!(" Events:"); diff --git a/subxt/src/blocks/extrinsic_types.rs b/subxt/src/blocks/extrinsic_types.rs index 3bf4f87acc..6f9d1fbf39 100644 --- a/subxt/src/blocks/extrinsic_types.rs +++ b/subxt/src/blocks/extrinsic_types.rs @@ -346,11 +346,7 @@ where .map(|e| &self.bytes[e.address_start_idx..e.address_end_idx]) } - /// Return only the bytes of the signature for this signed extrinsic. - /// - /// # Note - /// - /// Returns `None` if the extrinsic is not signed. + /// Returns Some(signature_bytes) if the extrinsic was signed otherwise None is returned. pub fn signature_bytes(&self) -> Option<&[u8]> { self.signed_details .as_ref() @@ -657,8 +653,10 @@ impl<'a> ExtrinsicSignedExtensions<'a> { }) } - /// The tip of an extrinsic, extracted from the `ChargeTransactionPayment` or - /// `ChargeAssetTxPayment` signed extension, depending on which is present. + /// The tip of an extrinsic, extracted from the ChargeTransactionPayment or ChargeAssetTxPayment + /// signed extension, depending on which is present. + /// + /// Returns `None` if `tip` was not found or decoding failed. pub fn tip(&self) -> Option { let tip = self.iter().find_map(|e| { e.ok().filter(|e| { @@ -672,8 +670,9 @@ impl<'a> ExtrinsicSignedExtensions<'a> { Some(tip) } - /// The nonce of the account that submitted the extrinsic, extracted from the - /// CheckNonce signed extension. + /// The nonce of the account that submitted the extrinsic, extracted from the CheckNonce signed extension. + /// + /// Returns `None` if `nonce` was not found or decoding failed. pub fn nonce(&self) -> Option { let nonce = self .iter() From d354b1a1c3ddff0dbb3ef4b042c4cf304235aea2 Mon Sep 17 00:00:00 2001 From: Tadeo hepperle Date: Mon, 30 Oct 2023 18:55:11 +0100 Subject: [PATCH 14/14] make example less ugly --- subxt/examples/blocks_subscribing.rs | 21 ++++++++++----------- 1 file changed, 10 insertions(+), 11 deletions(-) diff --git a/subxt/examples/blocks_subscribing.rs b/subxt/examples/blocks_subscribing.rs index 80b732795c..54ab2818e6 100644 --- a/subxt/examples/blocks_subscribing.rs +++ b/subxt/examples/blocks_subscribing.rs @@ -34,17 +34,6 @@ async fn main() -> Result<(), Box> { let decoded_ext = ext.as_root_extrinsic::(); println!(" Extrinsic #{idx}:"); - if let Some(signed_extensions) = ext.signed_extensions() { - println!(" Signed Extensions:"); - for signed_extension in signed_extensions.iter() { - let signed_extension = signed_extension?; - let name = signed_extension.name(); - let value = signed_extension.value()?.to_string(); - println!(" {name}: {value}"); - } - } else { - println!(" No Signed Extensions"); - } println!(" Bytes: {bytes_hex}"); println!(" Decoded: {decoded_ext:?}"); println!(" Events:"); @@ -59,6 +48,16 @@ async fn main() -> Result<(), Box> { println!(" {pallet_name}_{event_name}"); println!(" {}", event_values); } + + println!(" Signed Extensions:"); + if let Some(signed_extensions) = ext.signed_extensions() { + for signed_extension in signed_extensions.iter() { + let signed_extension = signed_extension?; + let name = signed_extension.name(); + let value = signed_extension.value()?.to_string(); + println!(" {name}: {value}"); + } + } } }