Skip to content

Commit

Permalink
Static Decoding of Signed Extensions: Simple Approach (#1235)
Browse files Browse the repository at this point in the history
* skeleton commit

* signed extension decoding

* fix some minor things

* make api more similar to Extrinsics

* defer decoding of signed extensions

* fix byte slices

* add test for nonce signed extension

* adjust test and extend for tip

* clippy

* support both  ChargeTransactionPayment and ChargeAssetTxPayment

* address PR comments

* Extend lifetimes, expose pub structs, remove as_type

* add signed extensions to block subscribing example

* add Decoded type

* fix merging bug and tests

* add decoded type in CustomSignedExtension

* fix minor issues, extend test

* cargo fmt differences

* remove the `decoded` function

* new as_signed_extra fn, do not expose as_type anymore

* fix Result-Option order, simplify obtaining Nonce

* tx: Remove `wait_for_in_block` helper method (#1237)

Signed-off-by: Alexandru Vasile <[email protected]>

* Update smoldot to 0.12 (#1212)

* Update lightclient

Signed-off-by: Alexandru Vasile <[email protected]>

* testing: Fix typo

Signed-off-by: Alexandru Vasile <[email protected]>

* testing: Update cargo.toml

Signed-off-by: Alexandru Vasile <[email protected]>

* lightclient: Add tracing logs to improve debugging

Signed-off-by: Alexandru Vasile <[email protected]>

* lightclient: Add socket buffers module for `PlatformRef`

Signed-off-by: Alexandru Vasile <[email protected]>

* lightclient: Update `SubxtPlatform`

Signed-off-by: Alexandru Vasile <[email protected]>

* cargo: Add lightclient dependencies

Signed-off-by: Alexandru Vasile <[email protected]>

* Update cargo.lock of wasm tests

Signed-off-by: Alexandru Vasile <[email protected]>

* lightclient: Add constant for with-buffer module

Signed-off-by: Alexandru Vasile <[email protected]>

* lightclient: Replace rand crate with getrandom

Signed-off-by: Alexandru Vasile <[email protected]>

* example: Update cargo lock file

Signed-off-by: Alexandru Vasile <[email protected]>

* examples: Update deps

Signed-off-by: Alexandru Vasile <[email protected]>

---------

Signed-off-by: Alexandru Vasile <[email protected]>
Co-authored-by: Tadeo Hepperle <[email protected]>

* ChargeAssetTxPayment: support providing u32 or MultiLocation in default impl (#1227)

* Asset Id in Config trait

* add example configuring the config

* fmt

* fix Default trait bound

* merge examples, fix default again

* adjust config in examples

* Update subxt/src/config/mod.rs

Co-authored-by: James Wilson <[email protected]>

---------

Co-authored-by: James Wilson <[email protected]>

* generic AssetId

* fix generics

* fmt

---------

Signed-off-by: Alexandru Vasile <[email protected]>
Co-authored-by: James Wilson <[email protected]>
Co-authored-by: Alexandru Vasile <[email protected]>
  • Loading branch information
3 people authored Nov 10, 2023
1 parent e7c1f73 commit 56d0cda
Show file tree
Hide file tree
Showing 7 changed files with 146 additions and 40 deletions.
1 change: 0 additions & 1 deletion subxt/examples/blocks_subscribing.rs
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,6 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {

for evt in events.iter() {
let evt = evt?;

let pallet_name = evt.pallet_name();
let event_name = evt.variant_name();
let event_values = evt.field_values()?;
Expand Down
1 change: 1 addition & 0 deletions subxt/examples/setup_config_signed_extension.rs
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ pub struct CustomSignedExtension;
// up in the chain metadata in order to know when and if to use it.
impl<T: Config> signed_extensions::SignedExtension<T> for CustomSignedExtension {
const NAME: &'static str = "CustomSignedExtension";
type Decoded = ();
}

// Gather together any params we need for our signed extension, here none.
Expand Down
84 changes: 61 additions & 23 deletions subxt/src/blocks/extrinsic_types.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,13 @@ use crate::{
Metadata,
};

use crate::config::signed_extensions::{
ChargeAssetTxPayment, ChargeTransactionPayment, CheckNonce,
};
use crate::config::SignedExtension;
use crate::dynamic::DecodedValue;
use crate::utils::strip_compact_prefix;
use codec::{Compact, Decode};
use codec::Decode;
use derivative::Derivative;
use scale_decode::{DecodeAsFields, DecodeAsType};

Expand Down Expand Up @@ -366,12 +370,13 @@ where
}

/// 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 extra_bytes = &self.bytes[signed.signature_end_idx..signed.extra_end_idx];
Some(ExtrinsicSignedExtensions {
bytes: extra_bytes,
metadata: &self.metadata,
_marker: std::marker::PhantomData,
})
}

Expand Down Expand Up @@ -605,21 +610,22 @@ impl<T: Config> ExtrinsicEvents<T> {

/// The signed extensions of an extrinsic.
#[derive(Debug, Clone)]
pub struct ExtrinsicSignedExtensions<'a> {
pub struct ExtrinsicSignedExtensions<'a, T: Config> {
bytes: &'a [u8],
metadata: &'a Metadata,
_marker: std::marker::PhantomData<T>,
}

impl<'a> ExtrinsicSignedExtensions<'a> {
impl<'a, T: Config> ExtrinsicSignedExtensions<'a, T> {
/// 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(&self) -> impl Iterator<Item = Result<ExtrinsicSignedExtension<'a>, Error>> {
pub fn iter(&self) -> impl Iterator<Item = Result<ExtrinsicSignedExtension<T>, 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;
let metadata = &self.metadata;

std::iter::from_fn(move || {
if index == num_signed_extensions {
Expand Down Expand Up @@ -649,49 +655,69 @@ impl<'a> ExtrinsicSignedExtensions<'a> {
ty_id,
identifier: extension.identifier(),
metadata,
_marker: std::marker::PhantomData,
}))
})
}

fn find_by_name(&self, name: &str) -> Option<ExtrinsicSignedExtension<'_, T>> {
let signed_extension = self
.iter()
.find_map(|e| e.ok().filter(|e| e.name() == name))?;
Some(signed_extension)
}

/// Searches through all signed extensions to find a specific one.
/// If the Signed Extension is not found `Ok(None)` is returned.
/// If the Signed Extension is found but decoding failed `Err(_)` is returned.
pub fn find<S: SignedExtension<T>>(&self) -> Result<Option<S::Decoded>, Error> {
self.find_by_name(S::NAME)
.map(|s| {
s.as_signed_extra::<S>().map(|e| {
e.expect("signed extra name is correct, because it was found before; qed.")
})
})
.transpose()
}

/// 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<u128> {
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.
self.find::<ChargeTransactionPayment>()
.ok()
.flatten()
.map(|e| e.tip())
.or_else(|| {
self.find::<ChargeAssetTxPayment<T>>()
.ok()
.flatten()
.map(|e| e.tip())
})
})?;

// 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::<u128>::decode(&mut tip.bytes()).ok()?.0;
Some(tip)
}

/// 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<u64> {
let nonce = self
.iter()
.find_map(|e| e.ok().filter(|e| e.name() == "CheckNonce"))?;
let nonce = Compact::<u64>::decode(&mut nonce.bytes()).ok()?.0;
let nonce = self.find::<CheckNonce>().ok()??.0;
Some(nonce)
}
}

/// 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: &'a Metadata,
_marker: std::marker::PhantomData<T>,
}

impl<'a> ExtrinsicSignedExtension<'a> {
impl<'a, T: Config> ExtrinsicSignedExtension<'a, T> {
/// The bytes representing this signed extension.
pub fn bytes(&self) -> &'a [u8] {
self.bytes
Expand All @@ -709,11 +735,23 @@ impl<'a> ExtrinsicSignedExtension<'a> {

/// Signed Extension as a [`scale_value::Value`]
pub fn value(&self) -> Result<DecodedValue, Error> {
let value =
DecodedValue::decode_as_type(&mut &self.bytes[..], self.ty_id, self.metadata.types())?;
self.as_type()
}

/// Decodes the `extra` bytes of this Signed Extension into a static type.
fn as_type<E: DecodeAsType>(&self) -> Result<E, Error> {
let value = E::decode_as_type(&mut &self.bytes[..], self.ty_id, self.metadata.types())?;
Ok(value)
}

/// Decodes the `extra` bytes of this Signed Extension into its associated `Decoded` type.
/// Returns `Ok(None)` if the identitfier of this Signed Extension object does not line up with the `NAME` constant of the provided Signed Extension type.
pub fn as_signed_extra<S: SignedExtension<T>>(&self) -> Result<Option<S::Decoded>, Error> {
if self.identifier != S::NAME {
return Ok(None);
}
self.as_type::<S::Decoded>().map(Some)
}
}

#[cfg(test)]
Expand Down
3 changes: 2 additions & 1 deletion subxt/src/config/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ pub mod substrate;

use codec::{Decode, Encode};
use core::fmt::Debug;
use scale_decode::DecodeAsType;
use serde::{de::DeserializeOwned, Serialize};

pub use default_extrinsic_params::{DefaultExtrinsicParams, DefaultExtrinsicParamsBuilder};
Expand Down Expand Up @@ -53,7 +54,7 @@ pub trait Config: Sized + Send + Sync + 'static {
type ExtrinsicParams: ExtrinsicParams<Self>;

/// This is used to identify an asset in the `ChargeAssetTxPayment` signed extension.
type AssetId: Debug + Encode;
type AssetId: Debug + Encode + DecodeAsType;
}

/// given some [`Config`], this return the other params needed for its `ExtrinsicParams`.
Expand Down
39 changes: 37 additions & 2 deletions subxt/src/config/signed_extensions.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,9 @@ use crate::utils::Era;
use crate::{client::OfflineClientT, Config};
use codec::{Compact, Encode};
use core::fmt::Debug;

use scale_decode::DecodeAsType;

use std::collections::HashMap;

/// A single [`SignedExtension`] has a unique name, but is otherwise the
Expand All @@ -21,6 +24,11 @@ pub trait SignedExtension<T: Config>: ExtrinsicParams<T> {
/// The name of the signed extension. This is used to associate it
/// with the signed extensions that the node is making use of.
const NAME: &'static str;

/// The type representing the `extra` bytes of a signed extension.
/// Decoding from this type should be symmetrical to the respective
/// `ExtrinsicParamsEncoder::encode_extra_to()` implementation of this signed extension.
type Decoded: DecodeAsType;
}

/// The [`CheckSpecVersion`] signed extension.
Expand Down Expand Up @@ -48,6 +56,7 @@ impl ExtrinsicParamsEncoder for CheckSpecVersion {

impl<T: Config> SignedExtension<T> for CheckSpecVersion {
const NAME: &'static str = "CheckSpecVersion";
type Decoded = ();
}

/// The [`CheckNonce`] signed extension.
Expand Down Expand Up @@ -75,6 +84,7 @@ impl ExtrinsicParamsEncoder for CheckNonce {

impl<T: Config> SignedExtension<T> for CheckNonce {
const NAME: &'static str = "CheckNonce";
type Decoded = Compact<u64>;
}

/// The [`CheckTxVersion`] signed extension.
Expand Down Expand Up @@ -102,6 +112,7 @@ impl ExtrinsicParamsEncoder for CheckTxVersion {

impl<T: Config> SignedExtension<T> for CheckTxVersion {
const NAME: &'static str = "CheckTxVersion";
type Decoded = ();
}

/// The [`CheckGenesis`] signed extension.
Expand Down Expand Up @@ -134,6 +145,7 @@ impl<T: Config> ExtrinsicParamsEncoder for CheckGenesis<T> {

impl<T: Config> SignedExtension<T> for CheckGenesis<T> {
const NAME: &'static str = "CheckGenesis";
type Decoded = ();
}

/// The [`CheckMortality`] signed extension.
Expand Down Expand Up @@ -213,15 +225,29 @@ impl<T: Config> ExtrinsicParamsEncoder for CheckMortality<T> {

impl<T: Config> SignedExtension<T> for CheckMortality<T> {
const NAME: &'static str = "CheckMortality";
type Decoded = Era;
}

/// The [`ChargeAssetTxPayment`] signed extension.
#[derive(Debug)]
#[derive(Debug, DecodeAsType)]
#[decode_as_type(trait_bounds = "T::AssetId: DecodeAsType")]
pub struct ChargeAssetTxPayment<T: Config> {
tip: Compact<u128>,
asset_id: Option<T::AssetId>,
}

impl<T: Config> ChargeAssetTxPayment<T> {
/// Tip to the extrinsic author in the native chain token.
pub fn tip(&self) -> u128 {
self.tip.0
}

/// Tip to the extrinsic author using the asset ID given.
pub fn asset_id(&self) -> Option<&T::AssetId> {
self.asset_id.as_ref()
}
}

/// Parameters to configure the [`ChargeAssetTxPayment`] signed extension.
pub struct ChargeAssetTxPaymentParams<T: Config> {
tip: u128,
Expand Down Expand Up @@ -285,14 +311,22 @@ impl<T: Config> ExtrinsicParamsEncoder for ChargeAssetTxPayment<T> {

impl<T: Config> SignedExtension<T> for ChargeAssetTxPayment<T> {
const NAME: &'static str = "ChargeAssetTxPayment";
type Decoded = Self;
}

/// The [`ChargeTransactionPayment`] signed extension.
#[derive(Debug)]
#[derive(Debug, DecodeAsType)]
pub struct ChargeTransactionPayment {
tip: Compact<u128>,
}

impl ChargeTransactionPayment {
/// Tip to the extrinsic author in the native chain token.
pub fn tip(&self) -> u128 {
self.tip.0
}
}

/// Parameters to configure the [`ChargeTransactionPayment`] signed extension.
#[derive(Default)]
pub struct ChargeTransactionPaymentParams {
Expand Down Expand Up @@ -333,6 +367,7 @@ impl ExtrinsicParamsEncoder for ChargeTransactionPayment {

impl<T: Config> SignedExtension<T> for ChargeTransactionPayment {
const NAME: &'static str = "ChargeTransactionPayment";
type Decoded = Self;
}

/// This accepts a tuple of [`SignedExtension`]s, and will dynamically make use of whichever
Expand Down
6 changes: 5 additions & 1 deletion subxt/src/utils/era.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,13 @@
// This file is dual-licensed as Apache-2.0 or GPL-3.0.
// see LICENSE for license details.

use scale_decode::DecodeAsType;

// 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, DecodeAsType,
)]
pub enum Era {
/// The transaction is valid forever. The genesis hash must be present in the signed content.
#[default]
Expand Down
Loading

0 comments on commit 56d0cda

Please sign in to comment.