diff --git a/subxt/src/blocks/extrinsic_types.rs b/subxt/src/blocks/extrinsic_types.rs index 92dc42c196..6778b940f5 100644 --- a/subxt/src/blocks/extrinsic_types.rs +++ b/subxt/src/blocks/extrinsic_types.rs @@ -13,6 +13,7 @@ use crate::{ Metadata, }; +use crate::utils::strip_compact_prefix; use codec::Decode; use derivative::Derivative; use scale_decode::DecodeAsFields; @@ -119,7 +120,7 @@ where } else { match ExtrinsicDetails::decode_from( index as u32, - extrinsics[index].0.clone().into(), + &extrinsics[index].0, client.clone(), hash, cached_events.clone(), @@ -203,7 +204,7 @@ where // Attempt to dynamically decode a single extrinsic from the given input. pub(crate) fn decode_from( index: u32, - extrinsic_bytes: Arc<[u8]>, + extrinsic_bytes: &[u8], client: C, block_hash: T::Hash, cached_events: CachedEvents, @@ -215,11 +216,14 @@ where let metadata = client.metadata(); + // removing the compact encoded prefix: + let bytes: Arc<[u8]> = strip_compact_prefix(extrinsic_bytes)?.1.into(); + // Extrinsic are encoded in memory in the following way: // - first byte: abbbbbbb (a = 0 for unsigned, 1 for signed, b = version) // - signature: [unknown TBD with metadata]. // - extrinsic data - let first_byte: u8 = Decode::decode(&mut &extrinsic_bytes[..])?; + let first_byte: u8 = Decode::decode(&mut &bytes[..])?; let version = first_byte & VERSION_MASK; if version != LATEST_EXTRINSIC_VERSION { @@ -229,13 +233,13 @@ where let is_signed = first_byte & SIGNATURE_MASK != 0; // Skip over the first byte which denotes the version and signing. - let cursor = &mut &extrinsic_bytes[1..]; + let cursor = &mut &bytes[1..]; let mut address_start_idx = 0; let mut address_end_idx = 0; if is_signed { - address_start_idx = extrinsic_bytes.len() - cursor.len(); + address_start_idx = bytes.len() - cursor.len(); // Skip over the address, signature and extra fields. scale_decode::visitor::decode_with_visitor( @@ -245,7 +249,7 @@ where scale_decode::visitor::IgnoreVisitor, ) .map_err(scale_decode::Error::from)?; - address_end_idx = extrinsic_bytes.len() - cursor.len(); + address_end_idx = bytes.len() - cursor.len(); scale_decode::visitor::decode_with_visitor( cursor, @@ -264,17 +268,17 @@ where .map_err(scale_decode::Error::from)?; } - let call_start_idx = extrinsic_bytes.len() - cursor.len(); + let call_start_idx = bytes.len() - cursor.len(); // Decode the pallet index, then the call variant. - let cursor = &mut &extrinsic_bytes[call_start_idx..]; + let cursor = &mut &bytes[call_start_idx..]; let pallet_index: u8 = Decode::decode(cursor)?; let variant_index: u8 = Decode::decode(cursor)?; Ok(ExtrinsicDetails { index, - bytes: extrinsic_bytes, + bytes, is_signed, address_start_idx, address_end_idx, @@ -717,6 +721,7 @@ mod tests { signed: bool, name: String, } + impl StaticExtrinsic for TestCallExtrinsic { const PALLET: &'static str = "Test"; const CALL: &'static str = "TestCall"; @@ -782,14 +787,8 @@ mod tests { let ids = ExtrinsicPartTypeIds::new(&metadata).unwrap(); // Decode with empty bytes. - let result = ExtrinsicDetails::decode_from( - 1, - vec![].into(), - client, - H256::random(), - Default::default(), - ids, - ); + let result = + ExtrinsicDetails::decode_from(1, &[], client, H256::random(), Default::default(), ids); assert_matches!(result.err(), Some(crate::Error::Codec(_))); } @@ -802,7 +801,7 @@ mod tests { // Decode with invalid version. let result = ExtrinsicDetails::decode_from( 1, - 3u8.encode().into(), + &vec![3u8].encode(), client, H256::random(), Default::default(), @@ -841,7 +840,7 @@ mod tests { // The length is handled deserializing `ChainBlockExtrinsic`, therefore the first byte is not needed. let extrinsic = ExtrinsicDetails::decode_from( 1, - tx_encoded.encoded()[1..].into(), + tx_encoded.encoded(), client, H256::random(), Default::default(), diff --git a/subxt/src/rpc/types.rs b/subxt/src/rpc/types.rs index c12c8d34df..dd9bf2e89e 100644 --- a/subxt/src/rpc/types.rs +++ b/subxt/src/rpc/types.rs @@ -110,20 +110,8 @@ pub type ConsensusEngineId = [u8; 4]; pub type EncodedJustification = Vec; /// Bytes representing an extrinsic in a [`ChainBlock`]. -#[derive(Clone, Debug)] -pub struct ChainBlockExtrinsic(pub Vec); - -impl<'a> ::serde::Deserialize<'a> for ChainBlockExtrinsic { - fn deserialize(de: D) -> Result - where - D: ::serde::Deserializer<'a>, - { - let r = impl_serde::serialize::deserialize(de)?; - let bytes = Decode::decode(&mut &r[..]) - .map_err(|e| ::serde::de::Error::custom(format!("Decode error: {e}")))?; - Ok(ChainBlockExtrinsic(bytes)) - } -} +#[derive(Clone, Debug, Deserialize)] +pub struct ChainBlockExtrinsic(#[serde(with = "impl_serde::serialize")] pub Vec); /// Wrapper for NumberOrHex to allow custom From impls #[derive(Serialize)] diff --git a/subxt/src/tx/tx_progress.rs b/subxt/src/tx/tx_progress.rs index 97aa77e8c3..440ed10cb1 100644 --- a/subxt/src/tx/tx_progress.rs +++ b/subxt/src/tx/tx_progress.rs @@ -6,6 +6,7 @@ use std::task::Poll; +use crate::utils::strip_compact_prefix; use crate::{ client::OnlineClientT, error::{DispatchError, Error, RpcError, TransactionError}, @@ -80,7 +81,7 @@ where TxStatus::InBlock(s) | TxStatus::Finalized(s) => return Ok(s), // Error scenarios; return the error. TxStatus::FinalityTimeout(_) => { - return Err(TransactionError::FinalityTimeout.into()) + return Err(TransactionError::FinalityTimeout.into()); } TxStatus::Invalid => return Err(TransactionError::Invalid.into()), TxStatus::Usurped(_) => return Err(TransactionError::Usurped.into()), @@ -109,7 +110,7 @@ where TxStatus::Finalized(s) => return Ok(s), // Error scenarios; return the error. TxStatus::FinalityTimeout(_) => { - return Err(TransactionError::FinalityTimeout.into()) + return Err(TransactionError::FinalityTimeout.into()); } TxStatus::Invalid => return Err(TransactionError::Invalid.into()), TxStatus::Usurped(_) => return Err(TransactionError::Usurped.into()), @@ -260,7 +261,6 @@ impl Stream for TxProgress { /// In any of these cases the client side TxProgress stream is also closed. /// In those cases the stream is closed however, so you currently have no way to find /// out if they finally made it into a block or not. - #[derive(Derivative)] #[derivative(Debug(bound = "C: std::fmt::Debug"))] pub enum TxStatus { @@ -389,7 +389,10 @@ impl> TxInBlock { .iter() .position(|ext| { use crate::config::Hasher; - let hash = T::Hasher::hash_of(&ext.0); + let Ok((_,stripped)) = strip_compact_prefix(&ext.0) else { + return false; + }; + let hash = T::Hasher::hash_of(&stripped); hash == self.ext_hash }) // If we successfully obtain the block hash we think contains our diff --git a/subxt/src/utils/mod.rs b/subxt/src/utils/mod.rs index b6a371fe44..f7bd186605 100644 --- a/subxt/src/utils/mod.rs +++ b/subxt/src/utils/mod.rs @@ -11,7 +11,7 @@ mod multi_signature; mod static_type; mod wrapper_opaque; -use codec::{Decode, Encode}; +use codec::{Compact, Decode, Encode}; use derivative::Derivative; pub use account_id::AccountId32; @@ -35,6 +35,14 @@ impl codec::Encode for Encoded { } } +/// Decodes a compact encoded value from the beginning of the provided bytes, +/// returning the value and any remaining bytes. +pub(crate) fn strip_compact_prefix(bytes: &[u8]) -> Result<(u64, &[u8]), codec::Error> { + let cursor = &mut &*bytes; + let val = >::decode(cursor)?; + Ok((val.0, *cursor)) +} + /// A version of [`std::marker::PhantomData`] that is also Send and Sync (which is fine /// because regardless of the generic param, it is always possible to Send + Sync this /// 0 size type).