static decoding of signed extensions

This commit is contained in:
Tadeo hepperle
2023-10-26 18:48:47 +02:00
parent 1dd8f53656
commit 245aca78ee
4 changed files with 149 additions and 41 deletions
+30 -13
View File
@@ -19,6 +19,8 @@ use codec::{Compact, Decode};
use derivative::Derivative; use derivative::Derivative;
use scale_decode::{DecodeAsFields, DecodeAsType}; use scale_decode::{DecodeAsFields, DecodeAsType};
use crate::config::signed_extensions::{ChargeAssetTxPayment, ChargeTransactionPayment};
use crate::config::SignedExtension;
use std::sync::Arc; use std::sync::Arc;
/// Trait to uniquely identify the extrinsic's identity from the runtime metadata. /// 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. /// Returns `None` if the extrinsic is not signed.
pub fn signed_extensions(&self) -> Option<ExtrinsicSignedExtensions> { pub fn signed_extensions(&self) -> Option<ExtrinsicSignedExtensions<'_, T>> {
let signed = self.signed_details.as_ref()?; let signed = self.signed_details.as_ref()?;
let extra_bytes = &self.bytes[signed.signature_end_idx..signed.extra_end_idx]; let extra_bytes = &self.bytes[signed.signature_end_idx..signed.extra_end_idx];
Some(ExtrinsicSignedExtensions { Some(ExtrinsicSignedExtensions {
bytes: extra_bytes, bytes: extra_bytes,
metadata: self.metadata.clone(), metadata: self.metadata.clone(),
_marker: std::marker::PhantomData,
}) })
} }
@@ -610,24 +613,26 @@ impl<T: Config> ExtrinsicEvents<T> {
/// The signed extensions of an extrinsic. /// The signed extensions of an extrinsic.
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
pub struct ExtrinsicSignedExtensions<'a> { pub struct ExtrinsicSignedExtensions<'a, T: Config> {
bytes: &'a [u8], bytes: &'a [u8],
metadata: Metadata, metadata: Metadata,
_marker: std::marker::PhantomData<T>,
} }
/// A single signed extension /// A single signed extension
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
pub struct ExtrinsicSignedExtension<'a> { pub struct ExtrinsicSignedExtension<'a, T: Config> {
bytes: &'a [u8], bytes: &'a [u8],
ty_id: u32, ty_id: u32,
identifier: &'a str, identifier: &'a str,
metadata: Metadata, metadata: Metadata,
_marker: std::marker::PhantomData<T>,
} }
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. /// 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. /// If the decoding of any signed extension fails, an error item is yielded and the iterator stops.
pub fn iter(&'a self) -> impl Iterator<Item = Result<ExtrinsicSignedExtension<'a>, Error>> { pub fn iter(&'a self) -> impl Iterator<Item = Result<ExtrinsicSignedExtension<'a, T>, Error>> {
let signed_extension_types = self.metadata.extrinsic().signed_extensions(); let signed_extension_types = self.metadata.extrinsic().signed_extensions();
let num_signed_extensions = signed_extension_types.len(); let num_signed_extensions = signed_extension_types.len();
let mut index = 0; let mut index = 0;
@@ -661,17 +666,29 @@ impl<'a> ExtrinsicSignedExtensions<'a> {
ty_id, ty_id,
identifier: extension.identifier(), identifier: extension.identifier(),
metadata: self.metadata.clone(), metadata: self.metadata.clone(),
_marker: std::marker::PhantomData,
})) }))
}) })
} }
fn find_by_name(&self, name: impl AsRef<str>) -> Option<ExtrinsicSignedExtension<'_, T>> {
let signed_extension = self
.iter()
.find_map(|e| e.ok().filter(|e| e.name() == name.as_ref()))?;
Some(signed_extension)
}
pub fn find<S: SignedExtension<T>>(&self) -> Option<Result<S::Extra, Error>> {
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. /// The tip of an extrinsic, extracted from the ChargeTransactionPayment or ChargeAssetTxPayment signed extension, depending on which is present.
pub fn tip(&self) -> Option<u128> { pub fn tip(&self) -> Option<u128> {
let tip = self.iter().find_map(|e| { // Note: the overhead of iterating twice should be negligible.
e.ok().filter(|e| { let tip = self
e.name() == "ChargeTransactionPayment" || e.name() == "ChargeAssetTxPayment" .find_by_name(<ChargeTransactionPayment as SignedExtension<T>>::NAME)
}) .or_else(|| self.find_by_name(<ChargeAssetTxPayment as SignedExtension<T>>::NAME))?;
})?;
// Note: ChargeAssetTxPayment might have addition information in it (asset_id). // 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::<u128>::decode(&mut tip.bytes()).ok()?.0; let tip = Compact::<u128>::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. /// The bytes representing this signed extension.
pub fn bytes(&self) -> &[u8] { pub fn bytes(&self) -> &[u8] {
self.bytes self.bytes
@@ -719,8 +736,8 @@ impl<'a> ExtrinsicSignedExtension<'a> {
self.decoded()?.to_value() self.decoded()?.to_value()
} }
pub fn as_type<T: DecodeAsType>(&self) -> Result<T, Error> { pub fn as_type<E: DecodeAsType>(&self) -> Result<E, Error> {
self.decoded()?.as_type::<T>().map_err(Into::into) self.decoded()?.as_type::<E>().map_err(Into::into)
} }
} }
+99 -27
View File
@@ -10,19 +10,45 @@
use super::extrinsic_params::{ExtrinsicParams, ExtrinsicParamsEncoder, ExtrinsicParamsError}; use super::extrinsic_params::{ExtrinsicParams, ExtrinsicParamsEncoder, ExtrinsicParamsError};
use crate::utils::Era; use crate::utils::Era;
use crate::{client::OfflineClientT, Config}; use crate::{client::OfflineClientT, Config};
use codec::{Compact, Encode}; use codec::{Compact, Decode, Encode};
use core::fmt::Debug; use core::fmt::Debug;
use scale_decode::DecodeAsType;
use std::collections::HashMap; use std::collections::HashMap;
/// A single [`SignedExtension`] has a unique name, but is otherwise the /// A single [`SignedExtension`] has a unique name, but is otherwise the
/// same as [`ExtrinsicParams`] in describing how to encode the extra and /// same as [`ExtrinsicParams`] in describing how to encode the extra and
/// additional data. /// additional data.
pub trait SignedExtension<T: Config>: ExtrinsicParams<T> { pub trait SignedExtension<T: Config>: ExtrinsicParams<T> + SignedExtensionDecoder {
/// The name of the signed extension. This is used to associate it /// The name of the signed extension. This is used to associate it
/// with the signed extensions that the node is making use of. /// with the signed extensions that the node is making use of.
const NAME: &'static str; 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<S: SignedExtensionDecoder + 'static> ExtrinsicParamsEncoder for S {
fn encode_extra_to(&self, v: &mut Vec<u8>) {
self.extra().encode_to(v);
}
fn encode_additional_to(&self, v: &mut Vec<u8>) {
self.additional().encode_to(v);
}
}
/// The [`CheckSpecVersion`] signed extension. /// The [`CheckSpecVersion`] signed extension.
#[derive(Debug)] #[derive(Debug)]
pub struct CheckSpecVersion(u32); pub struct CheckSpecVersion(u32);
@@ -40,9 +66,16 @@ impl<T: Config> ExtrinsicParams<T> for CheckSpecVersion {
} }
} }
impl ExtrinsicParamsEncoder for CheckSpecVersion { impl SignedExtensionDecoder for CheckSpecVersion {
fn encode_additional_to(&self, v: &mut Vec<u8>) { type Extra = ();
self.0.encode_to(v); type Additional = u32;
fn extra(&self) -> &Self::Extra {
&()
}
fn additional(&self) -> &Self::Additional {
&self.0
} }
} }
@@ -67,9 +100,16 @@ impl<T: Config> ExtrinsicParams<T> for CheckNonce {
} }
} }
impl ExtrinsicParamsEncoder for CheckNonce { impl SignedExtensionDecoder for CheckNonce {
fn encode_extra_to(&self, v: &mut Vec<u8>) { type Extra = Compact<u64>;
self.0.encode_to(v); type Additional = ();
fn extra(&self) -> &Self::Extra {
&self.0
}
fn additional(&self) -> &Self::Additional {
&()
} }
} }
@@ -94,9 +134,16 @@ impl<T: Config> ExtrinsicParams<T> for CheckTxVersion {
} }
} }
impl ExtrinsicParamsEncoder for CheckTxVersion { impl SignedExtensionDecoder for CheckTxVersion {
fn encode_additional_to(&self, v: &mut Vec<u8>) { type Extra = ();
self.0.encode_to(v); type Additional = u32;
fn extra(&self) -> &Self::Extra {
&()
}
fn additional(&self) -> &Self::Additional {
&self.0
} }
} }
@@ -126,9 +173,16 @@ impl<T: Config> ExtrinsicParams<T> for CheckGenesis<T> {
} }
} }
impl<T: Config> ExtrinsicParamsEncoder for CheckGenesis<T> { impl<T: Config> SignedExtensionDecoder for CheckGenesis<T> {
fn encode_additional_to(&self, v: &mut Vec<u8>) { type Extra = ();
self.0.encode_to(v); type Additional = T::Hash;
fn extra(&self) -> &Self::Extra {
&()
}
fn additional(&self) -> &Self::Additional {
&self.0
} }
} }
@@ -202,12 +256,16 @@ impl<T: Config> ExtrinsicParams<T> for CheckMortality<T> {
} }
} }
impl<T: Config> ExtrinsicParamsEncoder for CheckMortality<T> { impl<T: Config> SignedExtensionDecoder for CheckMortality<T> {
fn encode_extra_to(&self, v: &mut Vec<u8>) { type Extra = Era;
self.era.encode_to(v); type Additional = T::Hash;
fn extra(&self) -> &Self::Extra {
&self.era
} }
fn encode_additional_to(&self, v: &mut Vec<u8>) {
self.checkpoint.encode_to(v) fn additional(&self) -> &Self::Additional {
&self.checkpoint
} }
} }
@@ -216,7 +274,7 @@ impl<T: Config> SignedExtension<T> for CheckMortality<T> {
} }
/// The [`ChargeAssetTxPayment`] signed extension. /// The [`ChargeAssetTxPayment`] signed extension.
#[derive(Debug)] #[derive(Debug, Encode, Decode, DecodeAsType)]
pub struct ChargeAssetTxPayment { pub struct ChargeAssetTxPayment {
tip: Compact<u128>, tip: Compact<u128>,
asset_id: Option<u32>, asset_id: Option<u32>,
@@ -269,9 +327,16 @@ impl<T: Config> ExtrinsicParams<T> for ChargeAssetTxPayment {
} }
} }
impl ExtrinsicParamsEncoder for ChargeAssetTxPayment { impl SignedExtensionDecoder for ChargeAssetTxPayment {
fn encode_extra_to(&self, v: &mut Vec<u8>) { type Extra = Self;
(self.tip, self.asset_id).encode_to(v); type Additional = ();
fn extra(&self) -> &Self::Extra {
&self
}
fn additional(&self) -> &Self::Additional {
&()
} }
} }
@@ -280,7 +345,7 @@ impl<T: Config> SignedExtension<T> for ChargeAssetTxPayment {
} }
/// The [`ChargeTransactionPayment`] signed extension. /// The [`ChargeTransactionPayment`] signed extension.
#[derive(Debug)] #[derive(Debug, Encode, Decode, DecodeAsType)]
pub struct ChargeTransactionPayment { pub struct ChargeTransactionPayment {
tip: Compact<u128>, tip: Compact<u128>,
} }
@@ -317,9 +382,16 @@ impl<T: Config> ExtrinsicParams<T> for ChargeTransactionPayment {
} }
} }
impl ExtrinsicParamsEncoder for ChargeTransactionPayment { impl SignedExtensionDecoder for ChargeTransactionPayment {
fn encode_extra_to(&self, v: &mut Vec<u8>) { type Extra = Self;
self.tip.encode_to(v); type Additional = ();
fn extra(&self) -> &Self::Extra {
&self
}
fn additional(&self) -> &Self::Additional {
&()
} }
} }
+11 -1
View File
@@ -4,7 +4,17 @@
// Dev note: This and related bits taken from `sp_runtime::generic::Era` // Dev note: This and related bits taken from `sp_runtime::generic::Era`
/// An era to describe the longevity of a transaction. /// 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 { pub enum Era {
/// The transaction is valid forever. The genesis hash must be present in the signed content. /// The transaction is valid forever. The genesis hash must be present in the signed content.
#[default] #[default]
@@ -6,6 +6,7 @@ use crate::{test_context, utils::node_runtime};
use codec::{Compact, Encode}; use codec::{Compact, Encode};
use futures::StreamExt; use futures::StreamExt;
use subxt::config::signed_extensions::{ChargeAssetTxPayment, CheckNonce};
use subxt::config::DefaultExtrinsicParamsBuilder; use subxt::config::DefaultExtrinsicParamsBuilder;
use subxt_metadata::Metadata; use subxt_metadata::Metadata;
use subxt_signer::sr25519::dev; 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 transaction1 = submit_transfer_extrinsic_and_get_it_back!(1234);
let extensions1 = transaction1.signed_extensions().unwrap(); let extensions1 = transaction1.signed_extensions().unwrap();
let nonce1 = extensions1.nonce().unwrap(); let nonce1 = extensions1.nonce().unwrap();
let nonce1_static = extensions1.find::<CheckNonce>().0;
let tip1 = extensions1.tip().unwrap(); let tip1 = extensions1.tip().unwrap();
let tip1_static: u128 = extensions1.find::<ChargeAssetTxPayment>().tip.0;
let transaction2 = submit_transfer_extrinsic_and_get_it_back!(5678); let transaction2 = submit_transfer_extrinsic_and_get_it_back!(5678);
let extensions2 = transaction2.signed_extensions().unwrap(); let extensions2 = transaction2.signed_extensions().unwrap();
let nonce2 = extensions2.nonce().unwrap(); let nonce2 = extensions2.nonce().unwrap();
let nonce2_static: u64 = extensions2.find::<CheckNonce>().0;
let tip2 = extensions2.tip().unwrap(); let tip2 = extensions2.tip().unwrap();
let tip2_static: u128 = extensions2.find::<ChargeAssetTxPayment>().tip.0;
assert_eq!(nonce1, 0); assert_eq!(nonce1, 0);
assert_eq!(nonce1, nonce1_static);
assert_eq!(tip1, 1234); assert_eq!(tip1, 1234);
assert_eq!(tip1, tip1_static);
assert_eq!(nonce2, 1); assert_eq!(nonce2, 1);
assert_eq!(nonce2, nonce2_static);
assert_eq!(tip2, 5678); assert_eq!(tip2, 5678);
assert_eq!(tip2, tip2_static);
assert_eq!(extensions1.iter().count(), expected_signed_extensions.len()); assert_eq!(extensions1.iter().count(), expected_signed_extensions.len());
for (e, expected_name) in extensions1.iter().zip(expected_signed_extensions.iter()) { for (e, expected_name) in extensions1.iter().zip(expected_signed_extensions.iter()) {