Skip to content

Commit

Permalink
Add hash method to ExtrinsicDetails (#1676)
Browse files Browse the repository at this point in the history
* Add hash method to ExtrinsicDetails

* fix test
  • Loading branch information
jsdw authored Jul 22, 2024
1 parent ada8e17 commit c3267ed
Show file tree
Hide file tree
Showing 3 changed files with 64 additions and 16 deletions.
63 changes: 53 additions & 10 deletions core/src/blocks/extrinsics.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,15 +3,14 @@
// see LICENSE for license details.

use crate::blocks::extrinsic_signed_extensions::ExtrinsicSignedExtensions;
use crate::utils::strip_compact_prefix;
use crate::{
config::Config,
config::{Config, Hasher},
error::{BlockError, Error, MetadataError},
Metadata,
};
use alloc::sync::Arc;
use alloc::vec::Vec;
use codec::Decode;
use codec::{Compact, CompactLen, Decode};
use scale_decode::DecodeAsType;
use subxt_metadata::PalletMetadata;

Expand Down Expand Up @@ -169,24 +168,28 @@ where
const VERSION_MASK: u8 = 0b0111_1111;
const LATEST_EXTRINSIC_VERSION: u8 = 4;

// removing the compact encoded prefix:
let bytes: Arc<[u8]> = strip_compact_prefix(extrinsic_bytes)?.1.into();
// Wrap all of the bytes in Arc for easy sharing.
let bytes: Arc<[u8]> = Arc::from(extrinsic_bytes);

// The compact encoded length prefix.
let prefix = <Compact<u64>>::decode(&mut &*extrinsic_bytes)?;
let prefix_len = <Compact<u64>>::compact_len(&prefix.0);

// 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 &bytes[..])?;
let version_byte: u8 = Decode::decode(&mut &bytes[prefix_len..])?;

let version = first_byte & VERSION_MASK;
let version = version_byte & VERSION_MASK;
if version != LATEST_EXTRINSIC_VERSION {
return Err(BlockError::UnsupportedVersion(version).into());
}

let is_signed = first_byte & SIGNATURE_MASK != 0;
let is_signed = version_byte & SIGNATURE_MASK != 0;

// Skip over the first byte which denotes the version and signing.
let cursor = &mut &bytes[1..];
// Skip over the prefix and first byte which denotes the version and signing.
let cursor = &mut &bytes[prefix_len + 1..];

let signed_details = is_signed
.then(|| -> Result<SignedExtrinsicDetails, Error> {
Expand Down Expand Up @@ -248,6 +251,12 @@ where
})
}

/// Calculate and return the hash of the extrinsic, based on the configured hasher.
pub fn hash(&self) -> T::Hash {
// Use hash(), not hash_of(), because we don't want to double encode the bytes.
T::Hasher::hash(&self.bytes)
}

/// Is the extrinsic signed?
pub fn is_signed(&self) -> bool {
self.signed_details.is_some()
Expand Down Expand Up @@ -630,6 +639,40 @@ mod tests {
);
}

#[test]
fn tx_hashes_line_up() {
let metadata = metadata();
let ids = ExtrinsicPartTypeIds::new(&metadata).unwrap();

let tx = crate::dynamic::tx(
"Test",
"TestCall",
vec![
Value::u128(10),
Value::bool(true),
Value::string("SomeValue"),
],
);

// Encoded TX ready to submit.
let tx_encoded = crate::tx::create_unsigned::<SubstrateConfig, _>(&tx, &metadata)
.expect("Valid dynamic parameters are provided");

// Extrinsic details ready to decode.
let extrinsic = ExtrinsicDetails::<SubstrateConfig>::decode_from(
1,
tx_encoded.encoded(),
metadata,
ids,
)
.expect("Valid extrinsic");

// Both of these types should produce the same bytes.
assert_eq!(tx_encoded.encoded(), extrinsic.bytes(), "bytes should eq");
// Both of these types should produce the same hash.
assert_eq!(tx_encoded.hash(), extrinsic.hash(), "hashes should eq");
}

#[test]
fn statically_decode_extrinsic() {
let metadata = metadata();
Expand Down
5 changes: 5 additions & 0 deletions subxt/src/blocks/extrinsic_types.rs
Original file line number Diff line number Diff line change
Expand Up @@ -159,6 +159,11 @@ where
}
}

/// See [`subxt_core::blocks::ExtrinsicDetails::hash()`].
pub fn hash(&self) -> T::Hash {
self.inner.hash()
}

/// See [`subxt_core::blocks::ExtrinsicDetails::is_signed()`].
pub fn is_signed(&self) -> bool {
self.inner.is_signed()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -326,18 +326,18 @@ async fn transaction_v1_broadcast() {
let api = ctx.client();
let rpc = ctx.unstable_rpc_methods().await;

let tx = node_runtime::tx()
let tx_payload = node_runtime::tx()
.balances()
.transfer_allow_death(bob_address.clone(), 10_001);

let tx_bytes = ctx
let tx = ctx
.client()
.tx()
.create_signed_offline(&tx, &dev::alice(), Default::default())
.unwrap()
.into_encoded();
.create_signed_offline(&tx_payload, &dev::alice(), Default::default())
.unwrap();

let tx_hash = <SubstrateConfig as subxt::Config>::Hasher::hash(&tx_bytes[2..]);
let tx_hash = tx.hash();
let tx_bytes = tx.into_encoded();

// Subscribe to finalized blocks.
let mut finalized_sub = api.blocks().subscribe_finalized().await.unwrap();
Expand Down

0 comments on commit c3267ed

Please sign in to comment.