Support constructing and submitting V5 transactions (#1931)

* TransactionExtensions basic support for V5 VerifySignature and renames

* WIP: subxt-core v5 transaction support

* Subxt to support V5 extrinsics

* WIP tests failing with wsm trap error

* Actually encode mortality to fix tx encode issue

* fmt

* rename to sign_with_account_and_signature

* Add explicit methods for v4 and v5 ext construction

* clippy

* fix wasm example and no mut self where not needed

* fix doc example

* another doc fix

* Add tests for tx encoding and fix v5 encode issue

* add copyright and todo

* refactor APIs to have clear v4/v5 split in core and slightly nicer split in subxt proper

* rename Partial/SubmittableExtrinsic to *Transaction

* Remove SignerT::address since it's not needed

* doc fixes

* fmt

* doc fixes

* Fix comment number

* Clarify panic behaviour of inject_signature

* fmt
This commit is contained in:
James Wilson
2025-03-11 11:14:27 +00:00
committed by GitHub
parent dcb9c27fcc
commit b6b9ac65c7
50 changed files with 1368 additions and 781 deletions
+25 -9
View File
@@ -8,10 +8,11 @@ use crate::utils::variant_index::VariantIndex;
use crate::{
utils::ordered_map::OrderedMap, ArcStr, ConstantMetadata, ExtrinsicMetadata, Metadata,
OuterEnumsMetadata, PalletMetadataInner, RuntimeApiMetadataInner, RuntimeApiMethodMetadata,
RuntimeApiMethodParamMetadata, SignedExtensionMetadata, StorageEntryMetadata,
StorageEntryModifier, StorageEntryType, StorageHasher, StorageMetadata,
RuntimeApiMethodParamMetadata, StorageEntryMetadata, StorageEntryModifier, StorageEntryType,
StorageHasher, StorageMetadata, TransactionExtensionMetadata,
};
use alloc::borrow::ToOwned;
use alloc::vec;
use frame_metadata::v15;
use hashbrown::HashMap;
use scale_info::form::PortableForm;
@@ -102,8 +103,8 @@ mod from_v15 {
fn from_signed_extension_metadata(
value: v15::SignedExtensionMetadata<PortableForm>,
) -> SignedExtensionMetadata {
SignedExtensionMetadata {
) -> TransactionExtensionMetadata {
TransactionExtensionMetadata {
identifier: value.identifier,
extra_ty: value.ty.id,
additional_ty: value.additional_signed.id,
@@ -112,8 +113,8 @@ mod from_v15 {
fn from_extrinsic_metadata(value: v15::ExtrinsicMetadata<PortableForm>) -> ExtrinsicMetadata {
ExtrinsicMetadata {
version: value.version,
signed_extensions: value
supported_versions: vec![value.version],
transaction_extensions: value
.signed_extensions
.into_iter()
.map(from_signed_extension_metadata)
@@ -122,6 +123,7 @@ mod from_v15 {
call_ty: value.call_ty.id,
signature_ty: value.signature_ty.id,
extra_ty: value.extra_ty.id,
transaction_extensions_version: 0,
}
}
@@ -330,9 +332,23 @@ mod into_v15 {
fn from_extrinsic_metadata(e: ExtrinsicMetadata) -> v15::ExtrinsicMetadata<PortableForm> {
v15::ExtrinsicMetadata {
version: e.version,
// V16 and above metadata can have multiple supported extrinsic versions. We have to
// pick just one of these if converting back to V14/V15 metadata.
//
// - Picking the largest may mean that older tooling won't be compatible (it may only
// check/support older version).
// - Picking the smallest may mean that newer tooling won't work, or newer methods won't
// work.
//
// Either could make sense, but we keep the oldest to prioritize backward compat with
// older tooling.
version: *e
.supported_versions
.iter()
.min()
.expect("at least one extrinsic version expected"),
signed_extensions: e
.signed_extensions
.transaction_extensions
.into_iter()
.map(from_signed_extension_metadata)
.collect(),
@@ -344,7 +360,7 @@ mod into_v15 {
}
fn from_signed_extension_metadata(
s: SignedExtensionMetadata,
s: TransactionExtensionMetadata,
) -> v15::SignedExtensionMetadata<PortableForm> {
v15::SignedExtensionMetadata {
identifier: s.identifier,
+20 -11
View File
@@ -131,7 +131,7 @@ impl frame_decode::extrinsics::ExtrinsicTypeInfo for Metadata {
Ok(ExtrinsicExtensionInfo {
extension_ids: self
.extrinsic()
.signed_extensions()
.transaction_extensions()
.iter()
.map(|f| ExtrinsicInfoArg {
name: Cow::Borrowed(f.identifier()),
@@ -601,10 +601,14 @@ pub struct ExtrinsicMetadata {
signature_ty: u32,
/// The type of the outermost Extra enum.
extra_ty: u32,
/// Extrinsic version.
version: u8,
/// Which extrinsic versions are supported by this chain.
supported_versions: Vec<u8>,
/// The signed extensions in the order they appear in the extrinsic.
signed_extensions: Vec<SignedExtensionMetadata>,
transaction_extensions: Vec<TransactionExtensionMetadata>,
/// Version of the transaction extensions.
// TODO [jsdw]: V16 metadata groups transaction extensions by version.
// need to work out what to do once there is more than one version to deal with.
transaction_extensions_version: u8,
}
impl ExtrinsicMetadata {
@@ -626,20 +630,25 @@ impl ExtrinsicMetadata {
self.extra_ty
}
/// Extrinsic version.
pub fn version(&self) -> u8 {
self.version
/// Which extrinsic versions are supported.
pub fn supported_versions(&self) -> &[u8] {
&self.supported_versions
}
/// The extra/additional information associated with the extrinsic.
pub fn signed_extensions(&self) -> &[SignedExtensionMetadata] {
&self.signed_extensions
pub fn transaction_extensions(&self) -> &[TransactionExtensionMetadata] {
&self.transaction_extensions
}
/// Which version are these transaction extensions?
pub fn transaction_extensions_version(&self) -> u8 {
self.transaction_extensions_version
}
}
/// Metadata for the signed extensions used by extrinsics.
#[derive(Debug, Clone)]
pub struct SignedExtensionMetadata {
pub struct TransactionExtensionMetadata {
/// The unique signed extension identifier, which may be different from the type name.
identifier: String,
/// The type of the signed extension, with the data to be included in the extrinsic.
@@ -648,7 +657,7 @@ pub struct SignedExtensionMetadata {
additional_ty: u32,
}
impl SignedExtensionMetadata {
impl TransactionExtensionMetadata {
/// The unique signed extension identifier, which may be different from the type name.
pub fn identifier(&self) -> &str {
&self.identifier
+2 -2
View File
@@ -95,7 +95,7 @@ impl TypeSet {
self.insert(ty);
}
for signed in &extrinsic.signed_extensions {
for signed in &extrinsic.transaction_extensions {
self.insert(signed.extra_ty);
self.insert(signed.additional_ty);
}
@@ -294,7 +294,7 @@ fn iterate_metadata_types(metadata: &mut Metadata) -> impl Iterator<Item = &mut
types.push(ty);
}
for signed in &mut metadata.extrinsic.signed_extensions {
for signed in &mut metadata.extrinsic.transaction_extensions {
types.push(&mut signed.extra_ty);
types.push(&mut signed.additional_ty);
}
+13 -2
View File
@@ -292,14 +292,25 @@ fn get_extrinsic_hash(
let signature_hash = get_type_hash(registry, extrinsic.signature_ty, outer_enum_hashes);
let extra_hash = get_type_hash(registry, extrinsic.extra_ty, outer_enum_hashes);
// Supported versions are just u8s and we will likely never have more than 32 of these, so put them into
// an array of u8s and panic if more than 32.
if extrinsic.supported_versions.len() > 32 {
panic!("The metadata validation logic does not support more than 32 extrinsic versions.");
}
let supported_extrinsic_versions = {
let mut a = [0u8; 32];
a[0..extrinsic.supported_versions.len()].copy_from_slice(&extrinsic.supported_versions);
a
};
let mut bytes = concat_and_hash4(
&address_hash,
&signature_hash,
&extra_hash,
&[extrinsic.version; 32],
&supported_extrinsic_versions,
);
for signed_extension in extrinsic.signed_extensions.iter() {
for signed_extension in extrinsic.transaction_extensions.iter() {
bytes = concat_and_hash4(
&bytes,
&hash(signed_extension.identifier.as_bytes()),