diff --git a/Cargo.lock b/Cargo.lock index e2f68c2e2..b1066e973 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 0d575e777..b4f025765 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 0a9077a7d..ef820e633 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 07c50d2fa..448865007 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,