Add hash method to ExtrinsicDetails (#1676)

* Add hash method to ExtrinsicDetails

* fix test
This commit is contained in:
James Wilson
2024-07-22 13:54:32 +01:00
committed by GitHub
parent ada8e17b89
commit c3267ed488
3 changed files with 64 additions and 16 deletions
+53 -10
View File
@@ -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;
@@ -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> {
@@ -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()
@@ -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();
+5
View File
@@ -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()
@@ -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();