Skip to content

Commit

Permalink
chain_getBlock extrinsics encoding (#1024)
Browse files Browse the repository at this point in the history
* deserialize without decoding

* change decoding approach

* fix tests

* decode without allocating

* strip compact prefix

* cargo fmt

* nit adjustment
  • Loading branch information
tadeohepperle authored Jul 13, 2023
1 parent 9a8fc33 commit cd310b9
Show file tree
Hide file tree
Showing 4 changed files with 36 additions and 38 deletions.
37 changes: 18 additions & 19 deletions subxt/src/blocks/extrinsic_types.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ use crate::{
Metadata,
};

use crate::utils::strip_compact_prefix;
use codec::Decode;
use derivative::Derivative;
use scale_decode::DecodeAsFields;
Expand Down Expand Up @@ -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(),
Expand Down Expand Up @@ -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<T>,
Expand All @@ -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 {
Expand All @@ -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(
Expand All @@ -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,
Expand All @@ -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,
Expand Down Expand Up @@ -717,6 +721,7 @@ mod tests {
signed: bool,
name: String,
}

impl StaticExtrinsic for TestCallExtrinsic {
const PALLET: &'static str = "Test";
const CALL: &'static str = "TestCall";
Expand Down Expand Up @@ -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(_)));
}

Expand All @@ -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(),
Expand Down Expand Up @@ -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(),
Expand Down
16 changes: 2 additions & 14 deletions subxt/src/rpc/types.rs
Original file line number Diff line number Diff line change
Expand Up @@ -110,20 +110,8 @@ pub type ConsensusEngineId = [u8; 4];
pub type EncodedJustification = Vec<u8>;

/// Bytes representing an extrinsic in a [`ChainBlock`].
#[derive(Clone, Debug)]
pub struct ChainBlockExtrinsic(pub Vec<u8>);

impl<'a> ::serde::Deserialize<'a> for ChainBlockExtrinsic {
fn deserialize<D>(de: D) -> Result<Self, D::Error>
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<u8>);

/// Wrapper for NumberOrHex to allow custom From impls
#[derive(Serialize)]
Expand Down
11 changes: 7 additions & 4 deletions subxt/src/tx/tx_progress.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
use std::task::Poll;

use crate::utils::strip_compact_prefix;
use crate::{
client::OnlineClientT,
error::{DispatchError, Error, RpcError, TransactionError},
Expand Down Expand Up @@ -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()),
Expand Down Expand Up @@ -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()),
Expand Down Expand Up @@ -260,7 +261,6 @@ impl<T: Config, C: Clone> Stream for TxProgress<T, C> {
/// 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<T: Config, C> {
Expand Down Expand Up @@ -389,7 +389,10 @@ impl<T: Config, C: OnlineClientT<T>> TxInBlock<T, C> {
.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
Expand Down
10 changes: 9 additions & 1 deletion subxt/src/utils/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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 = <Compact<u64>>::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).
Expand Down

0 comments on commit cd310b9

Please sign in to comment.