Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

chain_getBlock extrinsics encoding #1024

Merged
Merged
Show file tree
Hide file tree
Changes from 7 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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.
niklasad1 marked this conversation as resolved.
Show resolved Hide resolved
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 {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
let Ok((_,stripped)) = strip_compact_prefix(&ext.0)else {
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
8 changes: 7 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,12 @@ impl codec::Encode for Encoded {
}
}

pub(crate) fn strip_compact_prefix(bytes: &[u8]) -> Result<(u64, &[u8]), codec::Error> {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: maybe worth a quick doc commanet like

/// Decodes a compact encoded value from the beginning of the provided bytes,
/// returning the value and any remaining bytes.

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