From fed367c525131f169ff94e49da3993b68e7f1a48 Mon Sep 17 00:00:00 2001 From: tarikgul Date: Thu, 17 Sep 2026 10:16:41 -0400 Subject: [PATCH] chore: update subxt to 0.51.0 and drop the v4 extension version workaround subxt 0.51.0 carries the upstream fix from paritytech/subxt#2277, so `extrinsic_extension_info(None)` now resolves to transaction extension version 0 for v4 extrinsics rather than the newest version in the metadata. `V4CompatMetadata` existed only to correct that, so it goes. frame-decode moves to 0.18.1 alongside, since subxt 0.51 builds against it and the `ExtrinsicTypeInfo` implementation we decode through has to come from the same version. `DecodedExtrinsic` and `fetch_block_body` stay. They are not about the extension version; subxt still keeps the raw bytes of each block body entry private, which is what lets us report an undecodable extrinsic at its own index and log the bytes that failed. The test that pinned the old upstream behaviour now asserts the fix instead, so a regression on a future bump fails here rather than quietly dropping extrinsics from block responses. --- Cargo.lock | 38 +++---- crates/server/Cargo.toml | 10 +- crates/server/src/utils/extrinsic_decode.rs | 120 ++++---------------- crates/server/src/utils/mod.rs | 3 +- 4 files changed, 50 insertions(+), 121 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index e2f68c2e..b1066e97 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1816,9 +1816,9 @@ dependencies = [ [[package]] name = "frame-decode" -version = "0.17.1" +version = "0.18.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "42ea4168fb6383e5abb84b5316804cb463ba2d6c9fa89981792e2b88c209192a" +checksum = "c9a9e93cab13c8e9a6accf029997d19943cf54fea5f5eb028a1133b7179d06aa" dependencies = [ "frame-metadata 23.0.1", "parity-scale-codec", @@ -3898,7 +3898,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8a56d757972c98b346a9b766e3f02746cde6dd1cd1d1d563472929fdd74bec4d" dependencies = [ "anyhow", - "itertools 0.10.5", + "itertools 0.14.0", "proc-macro2", "quote", "syn 2.0.114", @@ -5962,9 +5962,9 @@ checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" [[package]] name = "subxt" -version = "0.50.2" +version = "0.51.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "440b28070d3bba98d637791bc7ebbe362f5beb9e749204c16caaf344fef260ba" +checksum = "f932aa24dcc1147d6177f533fb229a5a0a5694639d6546bc9dcb3d06834b16c3" dependencies = [ "async-trait", "derive-where", @@ -6003,9 +6003,9 @@ dependencies = [ [[package]] name = "subxt-codegen" -version = "0.50.2" +version = "0.51.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8dc8a165f929034fd00e9bfa86b63e5d7c26b635e526fa9cadb01389eab92029" +checksum = "d70a175b236f7edf9482cf36ce87b49f08e4c2981881eddbee8319286d57bf72" dependencies = [ "heck", "parity-scale-codec", @@ -6020,9 +6020,9 @@ dependencies = [ [[package]] name = "subxt-lightclient" -version = "0.50.2" +version = "0.51.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0c4556eeef22af52ed4a77e4b665003f2e63264336d351376ef44bcab2a3bc1e" +checksum = "5bfc532adec91c98e8a379d5d782e7bffe1d94633c1a68eacc8fce213e9b1dc6" dependencies = [ "futures", "futures-util", @@ -6037,9 +6037,9 @@ dependencies = [ [[package]] name = "subxt-macro" -version = "0.50.2" +version = "0.51.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5132bb78596d4957bf3039b9c54f5f88cefefd64ab61f0e71592526e14ef7f13" +checksum = "f126a3139bdb2f15bf0c15046948e15d35e16eb0f682939fcb5322caa4bd798c" dependencies = [ "darling", "parity-scale-codec", @@ -6054,9 +6054,9 @@ dependencies = [ [[package]] name = "subxt-metadata" -version = "0.50.2" +version = "0.51.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a1e48c34696956f995df947adeb77061cb413377fb412487c41e7c2089112d5b" +checksum = "2f1d30123c960852fa8c052da5a6961d0827f17c26891abba8f02943ff3bd3fb" dependencies = [ "frame-decode", "frame-metadata 23.0.1", @@ -6071,9 +6071,9 @@ dependencies = [ [[package]] name = "subxt-rpcs" -version = "0.50.2" +version = "0.51.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c239a933407ae77306be8b6db72db1594c3295ba39781a0668c340e3d909160f" +checksum = "959f5e8af30edfdf68ce7e2f0ba5fa64727bdbdb692c3ad8d7bf353f0ce277cc" dependencies = [ "derive-where", "finito", @@ -6096,9 +6096,9 @@ dependencies = [ [[package]] name = "subxt-utils-accountid32" -version = "0.50.2" +version = "0.51.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7d43f74707b4c7b7e1e40bf362aebaf42630211a8bcac46a94318284f305debd" +checksum = "4b4bdec31f2961a75ed9c272eccf1032f39bb9be5a75152ac6e0b295cec41473" dependencies = [ "base58", "blake2", @@ -6112,9 +6112,9 @@ dependencies = [ [[package]] name = "subxt-utils-fetchmetadata" -version = "0.50.2" +version = "0.51.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2d58c4d891f3f8bd56acae29706b8bcde969ebb7421c447a8dff0117128416cb" +checksum = "682d47625f01bc5b9bc8ff65cd15b560a1b1a8d55d52a72a7351bb7e4451e552" dependencies = [ "hex", "parity-scale-codec", diff --git a/crates/server/Cargo.toml b/crates/server/Cargo.toml index 0d575e77..b4f02576 100644 --- a/crates/server/Cargo.toml +++ b/crates/server/Cargo.toml @@ -27,10 +27,10 @@ tower-http = { version = "0.6.6", features = ["trace", "cors", "limit", "normali include_dir = "0.7" socket2 = "0.6" polkadot-rest-api-config = { path = "../config", version = "0.2.1" } -subxt = { version = "0.50.2", features = ["reconnecting-rpc-client"] } -subxt-rpcs = { version = "0.50.2", features = ["reconnecting-rpc-client"] } -subxt-metadata = "=0.50.2" -frame-decode = { version = "0.17.1", default-features = false, features = ["legacy-types"] } +subxt = { version = "0.51.0", features = ["reconnecting-rpc-client"] } +subxt-rpcs = { version = "0.51.0", features = ["reconnecting-rpc-client"] } +subxt-metadata = "=0.51.0" +frame-decode = { version = "0.18.1", default-features = false, features = ["legacy-types"] } frame-metadata = { version = "23", default-features = false, features = ["current", "decode", "legacy", "std"] } scale-info = "2.11" heck = "0.5" @@ -67,4 +67,4 @@ tikv-jemallocator = "0.6" [dev-dependencies] tower = { version = "0.5", features = ["util"] } http-body-util = "0.1" -subxt-rpcs = { version = "0.50.2", features = ["mock-rpc-client"] } +subxt-rpcs = { version = "0.51.0", features = ["mock-rpc-client"] } diff --git a/crates/server/src/utils/extrinsic_decode.rs b/crates/server/src/utils/extrinsic_decode.rs index 0a9077a7..ef820e63 100644 --- a/crates/server/src/utils/extrinsic_decode.rs +++ b/crates/server/src/utils/extrinsic_decode.rs @@ -1,93 +1,29 @@ // Copyright (C) 2026 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: GPL-3.0-or-later -//! Extrinsic decoding that is correct for V4 extrinsics on runtimes exposing -//! more than one transaction extension version. +//! Extrinsic decoding that keeps the bytes alongside what was decoded. //! //! # Why this module exists //! -//! A V4 extrinsic carries no transaction extension version byte: it is *defined* -//! to use version 0 of the runtime's transaction extensions. Only V5 `General` -//! extrinsics carry an explicit version byte. +//! `subxt` decodes a block body for us, but keeps the raw bytes of each entry +//! private and hands back an error for any entry it cannot decode. That leaves no +//! way to report an undecodable extrinsic at its own index, or to log the bytes +//! that failed. So we fetch the block body ourselves and drive `frame_decode` +//! directly, and [`DecodedExtrinsic`] mirrors the slice of +//! `subxt::extrinsics::Extrinsic` that the block handlers use. //! -//! `frame_decode` models this by passing `None` as the extension version when it -//! decodes a V4 extrinsic, and asks the [`ExtrinsicTypeInfo`] implementation what -//! to do with it. `subxt_metadata::Metadata`'s implementation answers with -//! [`transaction_extension_version_to_use_for_decoding()`], which returns the -//! *highest* version in the metadata (see `paritytech/subxt#1998`). -//! -//! That was harmless while chains only ever exposed version 0. Polkadot Asset Hub -//! spec 2005000 exposes versions `[0, 1]`, where version 1 prepends extensions such -//! as `UnitTransactionExtension` and `VerifyMultiSignature`. Decoding a V4 -//! extrinsic against version 1 reads the first bytes of the era as enum variant -//! indexes, and decoding fails with `VariantNotFound(..)`. subxt then yields an -//! error for that extrinsic and the block response loses it entirely. -//! -//! [`V4CompatMetadata`] wraps the metadata and answers `None` with version 0, which -//! is what the extrinsic format requires. V5 extrinsics are unaffected: they pass -//! their own version through and it is used verbatim. -//! -//! [`transaction_extension_version_to_use_for_decoding()`]: -//! subxt_metadata::ExtrinsicMetadata::transaction_extension_version_to_use_for_decoding +//! This module used to also correct subxt's choice of transaction extension +//! version for V4 extrinsics. That is fixed upstream as of subxt 0.51.0, so the +//! wrapper is gone and `decode_extrinsic_info` uses the metadata directly. See +//! `paritytech/subxt#2277`. use frame_decode::extrinsics::{ - ExtrinsicCallInfo, ExtrinsicDecodeError, ExtrinsicExtensionInfo, ExtrinsicExtensions, - ExtrinsicInfoError, ExtrinsicOwned, ExtrinsicSignatureInfo, ExtrinsicTypeInfo, - decode_extrinsic, + ExtrinsicDecodeError, ExtrinsicExtensions, ExtrinsicOwned, decode_extrinsic, }; use scale_info::PortableRegistry; use subxt::Metadata; use subxt_metadata::ArcMetadata; -/// Wraps [`Metadata`] so that extrinsics carrying no transaction extension version -/// (that is, V4 extrinsics) are decoded against extension version 0 rather than -/// against the highest version the metadata happens to expose. -/// -/// Every other part of [`ExtrinsicTypeInfo`] is delegated unchanged. -pub struct V4CompatMetadata<'a>(pub &'a Metadata); - -impl ExtrinsicTypeInfo for V4CompatMetadata<'_> { - type TypeId = u32; - - fn extrinsic_call_info_by_index( - &self, - pallet_index: u8, - call_index: u8, - ) -> Result, ExtrinsicInfoError<'_>> { - self.0 - .extrinsic_call_info_by_index(pallet_index, call_index) - } - - fn extrinsic_call_info_by_name( - &self, - pallet_name: &str, - call_name: &str, - ) -> Result, ExtrinsicInfoError<'_>> { - self.0.extrinsic_call_info_by_name(pallet_name, call_name) - } - - fn extrinsic_signature_info( - &self, - ) -> Result, ExtrinsicInfoError<'_>> { - self.0.extrinsic_signature_info() - } - - fn extrinsic_extension_info( - &self, - extension_version: Option, - ) -> Result, ExtrinsicInfoError<'_>> { - // The only change: a V4 extrinsic (`None`) always uses extension version 0. - self.0 - .extrinsic_extension_info(Some(extension_version.unwrap_or(0))) - } - - fn extrinsic_extension_version_info( - &self, - ) -> Result, ExtrinsicInfoError<'_>> { - self.0.extrinsic_extension_version_info() - } -} - /// Something went wrong decoding a block body entry. #[derive(Debug, thiserror::Error)] pub enum DecodeExtrinsicError { @@ -109,9 +45,8 @@ pub fn decode_extrinsic_info( bytes: &[u8], metadata: &Metadata, ) -> Result, DecodeExtrinsicError> { - let compat = V4CompatMetadata(metadata); let cursor = &mut &bytes[..]; - let info = decode_extrinsic(cursor, &compat, metadata.types())?.into_owned(); + let info = decode_extrinsic(cursor, metadata, metadata.types())?.into_owned(); // Leftover bytes mean we misread the extrinsic even though every individual // part decoded, so treat it as a failure like subxt does. @@ -127,9 +62,8 @@ pub fn decode_extrinsic_info( /// A decoded extrinsic plus the bytes it was decoded from. /// /// This mirrors the parts of `subxt::extrinsics::Extrinsic` that the block handlers -/// use. We can't use subxt's type directly because its decoding goes through -/// `Metadata`'s [`ExtrinsicTypeInfo`] implementation, which is what [`V4CompatMetadata`] -/// exists to correct. +/// use. We can't use subxt's type directly because it keeps the raw bytes of each +/// block body entry private, so there is no way to re-read an entry it rejected. pub struct DecodedExtrinsic { /// The block body entry, including its compact length prefix. All ranges in /// `info` are relative to these bytes. @@ -349,7 +283,7 @@ pub async fn fetch_block_body( mod tests { use super::*; use crate::test_fixtures::ASSET_HUB_METADATA_V16; - use frame_decode::extrinsics::ExtrinsicType; + use frame_decode::extrinsics::{ExtrinsicType, ExtrinsicTypeInfo}; use parity_scale_codec::Decode; /// Polkadot Asset Hub block 20487777, extrinsic #2: a V4 signed @@ -402,31 +336,27 @@ mod tests { assert_eq!(&v1_names[1], "VerifyMultiSignature"); } - /// Pins the upstream behaviour we work around: `subxt_metadata::Metadata` - /// answers "which extension version for a V4 extrinsic?" with the highest - /// version in the metadata, and decoding then fails. + /// Guards the upstream fix we now rely on instead of our own wrapper. /// - /// See `paritytech/subxt#1998`. When that is fixed upstream and we bump to - /// the release carrying the fix, this test starts failing, which is the signal - /// that [`V4CompatMetadata`] can be removed. + /// `subxt_metadata::Metadata` answers "which extension version for a V4 + /// extrinsic?" with version 0 as of subxt 0.51.0. If a future bump regresses + /// that, this fails here rather than silently dropping extrinsics from block + /// responses again. See `paritytech/subxt#2277`. #[test] - fn subxt_metadata_still_picks_the_newest_version_for_v4() { + fn subxt_metadata_uses_version_0_for_v4() { let metadata = metadata(); assert_eq!( metadata .extrinsic() .transaction_extension_version_to_use_for_decoding(), - 1, + 0, ); for hex_str in [V4_TRANSFER_ALLOW_DEATH, V4_TRANSFER_KEEP_ALIVE] { let raw = bytes(hex_str); - let result = decode_extrinsic(&mut &raw[..], &metadata, metadata.types()); - assert!( - result.is_err(), - "expected the unfixed path to fail; if this now succeeds, subxt#1998 is fixed" - ); + decode_extrinsic(&mut &raw[..], &metadata, metadata.types()) + .expect("subxt should decode a v4 extrinsic against extension version 0"); } } diff --git a/crates/server/src/utils/mod.rs b/crates/server/src/utils/mod.rs index 07c50d2f..44886500 100644 --- a/crates/server/src/utils/mod.rs +++ b/crates/server/src/utils/mod.rs @@ -22,8 +22,7 @@ pub use extrinsic::{ EraInfo, decode_era_from_bytes, extract_era_from_extrinsic_bytes, parse_era_info, }; pub use extrinsic_decode::{ - DecodeExtrinsicError, DecodedExtrinsic, V4CompatMetadata, decode_extrinsic_info, - fetch_block_body, + DecodeExtrinsicError, DecodedExtrinsic, decode_extrinsic_info, fetch_block_body, }; pub use fee::{ FeeCalcError, FeeDetails, FeeServiceError, QueryFeeDetailsCache, RuntimeDispatchInfoRaw,