Metadata V16: Be more dynamic over which hasher is used. (#1974)

* Use DynamicHasher256 to support Blake2 or Keccack depending on chain

* remove Config::Hash associated type, replace with HashFor<Config> alias

* Fix doc links

* fix wasm tests

* Don't strip system pallet associated types. check System.Hashing, not Hash. Rename BlockHash trait to Hash

* Tweak comment

* fmt

* fix merge

* Fix typo
This commit is contained in:
James Wilson
2025-04-23 10:12:48 +01:00
committed by GitHub
parent a8ae55a61b
commit 21b3f52191
43 changed files with 573 additions and 371 deletions
+23 -5
View File
@@ -5,7 +5,7 @@
use super::BlockError;
use crate::blocks::extrinsic_transaction_extensions::ExtrinsicTransactionExtensions;
use crate::{
config::{Config, Hasher},
config::{Config, HashFor, Hasher},
error::{Error, MetadataError},
Metadata,
};
@@ -22,6 +22,7 @@ pub use crate::blocks::StaticExtrinsic;
pub struct Extrinsics<T: Config> {
extrinsics: Vec<Arc<(Extrinsic<'static, u32>, Vec<u8>)>>,
metadata: Metadata,
hasher: T::Hasher,
_marker: core::marker::PhantomData<T>,
}
@@ -30,6 +31,7 @@ impl<T: Config> Extrinsics<T> {
/// each extrinsic hash (in the form of bytes) and some metadata that
/// we'll use to decode them.
pub fn decode_from(extrinsics: Vec<Vec<u8>>, metadata: Metadata) -> Result<Self, Error> {
let hasher = T::Hasher::new(&metadata);
let extrinsics = extrinsics
.into_iter()
.enumerate()
@@ -63,6 +65,7 @@ impl<T: Config> Extrinsics<T> {
Ok(Self {
extrinsics,
hasher,
metadata,
_marker: core::marker::PhantomData,
})
@@ -85,10 +88,16 @@ impl<T: Config> Extrinsics<T> {
pub fn iter(&self) -> impl Iterator<Item = ExtrinsicDetails<T>> + Send + Sync + 'static {
let extrinsics = self.extrinsics.clone();
let num_extrinsics = self.extrinsics.len();
let hasher = self.hasher;
let metadata = self.metadata.clone();
(0..num_extrinsics).map(move |index| {
ExtrinsicDetails::new(index as u32, extrinsics[index].clone(), metadata.clone())
ExtrinsicDetails::new(
index as u32,
extrinsics[index].clone(),
hasher,
metadata.clone(),
)
})
}
@@ -133,6 +142,8 @@ pub struct ExtrinsicDetails<T: Config> {
index: u32,
/// Extrinsic bytes and decode info.
ext: Arc<(Extrinsic<'static, u32>, Vec<u8>)>,
/// Hash the extrinsic if we want.
hasher: T::Hasher,
/// Subxt metadata to fetch the extrinsic metadata.
metadata: Metadata,
_marker: core::marker::PhantomData<T>,
@@ -147,20 +158,22 @@ where
pub fn new(
index: u32,
ext: Arc<(Extrinsic<'static, u32>, Vec<u8>)>,
hasher: T::Hasher,
metadata: Metadata,
) -> ExtrinsicDetails<T> {
ExtrinsicDetails {
index,
ext,
hasher,
metadata,
_marker: core::marker::PhantomData,
}
}
/// Calculate and return the hash of the extrinsic, based on the configured hasher.
pub fn hash(&self) -> T::Hash {
pub fn hash(&self) -> HashFor<T> {
// Use hash(), not hash_of(), because we don't want to double encode the bytes.
T::Hasher::hash(self.bytes())
self.hasher.hash(self.bytes())
}
/// Is the extrinsic signed?
@@ -532,6 +545,7 @@ mod tests {
#[test]
fn tx_hashes_line_up() {
let metadata = metadata();
let hasher = <SubstrateConfig as Config>::Hasher::new(&metadata);
let tx = crate::dynamic::tx(
"Test",
@@ -559,7 +573,11 @@ mod tests {
// 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");
assert_eq!(
tx_encoded.hash_with(hasher),
extrinsic.hash(),
"hashes should eq"
);
}
#[test]
+5 -2
View File
@@ -4,7 +4,10 @@
//! A couple of client types that we use elsewhere.
use crate::{config::Config, metadata::Metadata};
use crate::{
config::{Config, HashFor},
metadata::Metadata,
};
use derive_where::derive_where;
/// This provides access to some relevant client state in transaction extensions,
@@ -12,7 +15,7 @@ use derive_where::derive_where;
#[derive_where(Clone, Debug)]
pub struct ClientState<C: Config> {
/// Genesis hash.
pub genesis_hash: C::Hash,
pub genesis_hash: HashFor<C>,
/// Runtime version.
pub runtime_version: RuntimeVersion,
/// Metadata.
+8 -3
View File
@@ -7,7 +7,11 @@
//! [`crate::config::DefaultExtrinsicParams`] provides a general-purpose
//! implementation of this that will work in many cases.
use crate::{client::ClientState, error::ExtrinsicParamsError, Config};
use crate::{
client::ClientState,
config::{Config, HashFor},
error::ExtrinsicParamsError,
};
use alloc::vec::Vec;
use core::any::Any;
@@ -74,7 +78,7 @@ pub trait Params<T: Config> {
/// Set the account nonce.
fn inject_account_nonce(&mut self, _nonce: u64) {}
/// Set the current block.
fn inject_block(&mut self, _number: u64, _hash: T::Hash) {}
fn inject_block(&mut self, _number: u64, _hash: HashFor<T>) {}
}
impl<T: Config> Params<T> for () {}
@@ -85,7 +89,8 @@ macro_rules! impl_tuples {
fn inject_account_nonce(&mut self, nonce: u64) {
$(self.$index.inject_account_nonce(nonce);)+
}
fn inject_block(&mut self, number: u64, hash: Conf::Hash) {
fn inject_block(&mut self, number: u64, hash: HashFor<Conf>) {
$(self.$index.inject_block(number, hash);)+
}
}
+16 -12
View File
@@ -20,6 +20,7 @@ use core::fmt::Debug;
use scale_decode::DecodeAsType;
use scale_encode::EncodeAsType;
use serde::{de::DeserializeOwned, Serialize};
use subxt_metadata::Metadata;
pub use default_extrinsic_params::{DefaultExtrinsicParams, DefaultExtrinsicParamsBuilder};
pub use extrinsic_params::{ExtrinsicParams, ExtrinsicParamsEncoder};
@@ -33,9 +34,6 @@ pub use transaction_extensions::TransactionExtension;
// And we want the compiler to infer `Send` and `Sync` OK for things which have `T: Config`
// rather than having to `unsafe impl` them ourselves.
pub trait Config: Sized + Send + Sync + 'static {
/// The output of the `Hasher` function.
type Hash: BlockHash;
/// The account ID type.
type AccountId: Debug + Clone + Encode + Decode + Serialize + Send;
@@ -46,7 +44,7 @@ pub trait Config: Sized + Send + Sync + 'static {
type Signature: Debug + Clone + Encode + Decode + Send;
/// The hashing system (algorithm) being used in the runtime (e.g. Blake2).
type Hasher: Debug + Hasher<Output = Self::Hash>;
type Hasher: Debug + Clone + Copy + Hasher + Send + Sync;
/// The block header.
type Header: Debug + Header<Hasher = Self::Hasher> + Sync + Send + DeserializeOwned;
@@ -58,11 +56,14 @@ pub trait Config: Sized + Send + Sync + 'static {
type AssetId: Debug + Clone + Encode + DecodeAsType + EncodeAsType + Send;
}
/// Given some [`Config`], this returns the type of hash used.
pub type HashFor<T> = <<T as Config>::Hasher as Hasher>::Output;
/// given some [`Config`], this return the other params needed for its `ExtrinsicParams`.
pub type ParamsFor<T> = <<T as Config>::ExtrinsicParams as ExtrinsicParams<T>>::Params;
/// Block hashes must conform to a bunch of things to be used in Subxt.
pub trait BlockHash:
pub trait Hash:
Debug
+ Copy
+ Send
@@ -77,7 +78,7 @@ pub trait BlockHash:
+ core::hash::Hash
{
}
impl<T> BlockHash for T where
impl<T> Hash for T where
T: Debug
+ Copy
+ Send
@@ -97,15 +98,18 @@ impl<T> BlockHash for T where
/// and extrinsics.
pub trait Hasher {
/// The type given back from the hash operation
type Output;
type Output: Hash;
/// Construct a new hasher.
fn new(metadata: &Metadata) -> Self;
/// Hash some bytes to the given output type.
fn hash(s: &[u8]) -> Self::Output;
fn hash(&self, s: &[u8]) -> Self::Output;
/// Hash some SCALE encodable type to the given output type.
fn hash_of<S: Encode>(s: &S) -> Self::Output {
fn hash_of<S: Encode>(&self, s: &S) -> Self::Output {
let out = s.encode();
Self::hash(&out)
self.hash(&out)
}
}
@@ -120,7 +124,7 @@ pub trait Header: Sized + Encode + Decode {
fn number(&self) -> Self::Number;
/// Hash this header.
fn hash(&self) -> <Self::Hasher as Hasher>::Output {
Self::Hasher::hash_of(self)
fn hash_with(&self, hasher: Self::Hasher) -> <Self::Hasher as Hasher>::Output {
hasher.hash_of(self)
}
}
-1
View File
@@ -17,7 +17,6 @@ pub use primitive_types::{H256, U256};
pub enum PolkadotConfig {}
impl Config for PolkadotConfig {
type Hash = <SubstrateConfig as Config>::Hash;
type AccountId = <SubstrateConfig as Config>::AccountId;
type Signature = <SubstrateConfig as Config>::Signature;
type Hasher = <SubstrateConfig as Config>::Hasher;
+64 -7
View File
@@ -11,6 +11,7 @@ use alloc::vec::Vec;
use codec::{Decode, Encode};
pub use primitive_types::{H256, U256};
use serde::{Deserialize, Serialize};
use subxt_metadata::Metadata;
/// Default set of commonly used types by Substrate runtimes.
// Note: We only use this at the type level, so it should be impossible to
@@ -21,12 +22,11 @@ use serde::{Deserialize, Serialize};
pub enum SubstrateConfig {}
impl Config for SubstrateConfig {
type Hash = H256;
type AccountId = AccountId32;
type Address = MultiAddress<Self::AccountId, u32>;
type Signature = MultiSignature;
type Hasher = BlakeTwo256;
type Header = SubstrateHeader<u32, BlakeTwo256>;
type Hasher = DynamicHasher256;
type Header = SubstrateHeader<u32, DynamicHasher256>;
type ExtrinsicParams = SubstrateExtrinsicParams<Self>;
type AssetId = u32;
}
@@ -39,17 +39,73 @@ pub type SubstrateExtrinsicParams<T> = DefaultExtrinsicParams<T>;
/// This is what you provide to methods like `sign_and_submit()`.
pub type SubstrateExtrinsicParamsBuilder<T> = DefaultExtrinsicParamsBuilder<T>;
/// A type that can hash values using the blaks2_256 algorithm.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Encode)]
/// A hasher (ie implements [`Hasher`]) which hashes values using the blaks2_256 algorithm.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct BlakeTwo256;
impl Hasher for BlakeTwo256 {
type Output = H256;
fn hash(s: &[u8]) -> Self::Output {
fn new(_metadata: &Metadata) -> Self {
Self
}
fn hash(&self, s: &[u8]) -> Self::Output {
sp_crypto_hashing::blake2_256(s).into()
}
}
/// A hasher (ie implements [`Hasher`]) which inspects the runtime metadata to decide how to
/// hash types, falling back to blake2_256 if the hasher information is not available.
///
/// Currently this hasher supports only `BlakeTwo256` and `Keccak256` hashing methods.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct DynamicHasher256(HashType);
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
enum HashType {
// Most chains use this:
BlakeTwo256,
// Chains like Hyperbridge use this (tends to be eth compatible chains)
Keccak256,
// If we don't have V16 metadata, we'll emit this and default to BlakeTwo256.
Unknown,
}
impl Hasher for DynamicHasher256 {
type Output = H256;
fn new(metadata: &Metadata) -> Self {
// Determine the Hash associated type used for the current chain, if possible.
let Some(system_pallet) = metadata.pallet_by_name("System") else {
return Self(HashType::Unknown);
};
let Some(hash_ty_id) = system_pallet.associated_type_id("Hashing") else {
return Self(HashType::Unknown);
};
let ty = metadata
.types()
.resolve(hash_ty_id)
.expect("Type information for 'Hashing' associated type should be in metadata");
let hash_type = match ty.path.ident().as_deref().unwrap_or("") {
"BlakeTwo256" => HashType::BlakeTwo256,
"Keccak256" => HashType::Keccak256,
_ => HashType::Unknown,
};
Self(hash_type)
}
fn hash(&self, s: &[u8]) -> Self::Output {
match self.0 {
HashType::BlakeTwo256 | HashType::Unknown => sp_crypto_hashing::blake2_256(s).into(),
HashType::Keccak256 => sp_crypto_hashing::keccak_256(s).into(),
}
}
}
/// A generic Substrate header type, adapted from `sp_runtime::generic::Header`.
/// The block number and hasher can be configured to adapt this for other nodes.
#[derive(Encode, Decode, Debug, PartialEq, Eq, Clone, Serialize, Deserialize)]
@@ -75,11 +131,12 @@ pub struct SubstrateHeader<N: Copy + Into<U256> + TryFrom<U256>, H: Hasher> {
impl<N, H> Header for SubstrateHeader<N, H>
where
N: Copy + Into<u64> + Into<U256> + TryFrom<U256> + Encode,
H: Hasher + Encode,
H: Hasher,
SubstrateHeader<N, H>: Encode + Decode,
{
type Number = N;
type Hasher = H;
fn number(&self) -> Self::Number {
self.number
}
+9 -18
View File
@@ -9,10 +9,10 @@
use super::extrinsic_params::ExtrinsicParams;
use crate::client::ClientState;
use crate::config::{ExtrinsicParamsEncoder, Header};
use crate::config::ExtrinsicParamsEncoder;
use crate::config::{Config, HashFor};
use crate::error::ExtrinsicParamsError;
use crate::utils::{Era, Static};
use crate::Config;
use alloc::borrow::ToOwned;
use alloc::boxed::Box;
use alloc::vec::Vec;
@@ -262,7 +262,7 @@ impl<T: Config> TransactionExtension<T> for CheckTxVersion {
}
/// The [`CheckGenesis`] transaction extension.
pub struct CheckGenesis<T: Config>(T::Hash);
pub struct CheckGenesis<T: Config>(HashFor<T>);
impl<T: Config> ExtrinsicParams<T> for CheckGenesis<T> {
type Params = ();
@@ -288,7 +288,7 @@ impl<T: Config> TransactionExtension<T> for CheckGenesis<T> {
/// The [`CheckMortality`] transaction extension.
pub struct CheckMortality<T: Config> {
params: CheckMortalityParamsInner<T>,
genesis_hash: T::Hash,
genesis_hash: HashFor<T>,
}
impl<T: Config> ExtrinsicParams<T> for CheckMortality<T> {
@@ -352,7 +352,7 @@ enum CheckMortalityParamsInner<T: Config> {
MortalFromBlock {
for_n_blocks: u64,
from_block_n: u64,
from_block_hash: T::Hash,
from_block_hash: HashFor<T>,
},
}
@@ -368,23 +368,14 @@ impl<T: Config> CheckMortalityParams<T> {
pub fn mortal(for_n_blocks: u64) -> Self {
Self(CheckMortalityParamsInner::MortalForBlocks(for_n_blocks))
}
/// Configure a transaction that will be mortal for the number of blocks given,
/// and from the block header provided.
pub fn mortal_from(for_n_blocks: u64, from_block: T::Header) -> Self {
Self(CheckMortalityParamsInner::MortalFromBlock {
for_n_blocks,
from_block_n: from_block.number().into(),
from_block_hash: from_block.hash(),
})
}
/// Configure a transaction that will be mortal for the number of blocks given,
/// and from the block details provided. Prefer to use [`CheckMortalityParams::mortal()`]
/// or [`CheckMortalityParams::mortal_from()`] which both avoid the block number and hash
/// from being misaligned.
/// where possible, which prevents the block number and hash from being misaligned.
pub fn mortal_from_unchecked(
for_n_blocks: u64,
from_block_n: u64,
from_block_hash: T::Hash,
from_block_hash: HashFor<T>,
) -> Self {
Self(CheckMortalityParamsInner::MortalFromBlock {
for_n_blocks,
@@ -399,7 +390,7 @@ impl<T: Config> CheckMortalityParams<T> {
}
impl<T: Config> Params<T> for CheckMortalityParams<T> {
fn inject_block(&mut self, from_block_n: u64, from_block_hash: <T as Config>::Hash) {
fn inject_block(&mut self, from_block_n: u64, from_block_hash: HashFor<T>) {
match &self.0 {
CheckMortalityParamsInner::MortalForBlocks(n) => {
self.0 = CheckMortalityParamsInner::MortalFromBlock {
+11 -7
View File
@@ -45,7 +45,11 @@ use derive_where::derive_where;
use scale_decode::{DecodeAsFields, DecodeAsType};
use subxt_metadata::PalletMetadata;
use crate::{error::MetadataError, Config, Error, Metadata};
use crate::{
config::{Config, HashFor},
error::MetadataError,
Error, Metadata,
};
/// Create a new [`Events`] instance from the given bytes.
///
@@ -232,7 +236,7 @@ pub struct EventDetails<T: Config> {
// end of everything (fields + topics)
end_idx: usize,
metadata: Metadata,
topics: Vec<T::Hash>,
topics: Vec<HashFor<T>>,
}
impl<T: Config> EventDetails<T> {
@@ -281,7 +285,7 @@ impl<T: Config> EventDetails<T> {
let event_fields_end_idx = all_bytes.len() - input.len();
// topics come after the event data in EventRecord.
let topics = Vec::<T::Hash>::decode(input)?;
let topics = Vec::<HashFor<T>>::decode(input)?;
// what bytes did we skip over in total, including topics.
let end_idx = all_bytes.len() - input.len();
@@ -413,7 +417,7 @@ impl<T: Config> EventDetails<T> {
}
/// Return the topics associated with this event.
pub fn topics(&self) -> &[T::Hash] {
pub fn topics(&self) -> &[HashFor<T>] {
&self.topics
}
}
@@ -430,7 +434,7 @@ pub struct EventMetadataDetails<'a> {
#[cfg(test)]
pub(crate) mod test_utils {
use super::*;
use crate::config::{Config, SubstrateConfig};
use crate::config::{HashFor, SubstrateConfig};
use codec::Encode;
use frame_metadata::{
v15::{
@@ -463,12 +467,12 @@ pub(crate) mod test_utils {
pub struct EventRecord<E: Encode> {
phase: Phase,
event: AllEvents<E>,
topics: Vec<<SubstrateConfig as Config>::Hash>,
topics: Vec<HashFor<SubstrateConfig>>,
}
impl<E: Encode> EventRecord<E> {
/// Create a new event record with the given phase, event, and topics.
pub fn new(phase: Phase, event: E, topics: Vec<<SubstrateConfig as Config>::Hash>) -> Self {
pub fn new(phase: Phase, event: E, topics: Vec<HashFor<SubstrateConfig>>) -> Self {
Self {
phase,
event: AllEvents::Test(event),
+10 -6
View File
@@ -9,7 +9,7 @@
//! ```rust
//! use subxt_signer::sr25519::dev;
//! use subxt_macro::subxt;
//! use subxt_core::config::PolkadotConfig;
//! use subxt_core::config::{PolkadotConfig, HashFor};
//! use subxt_core::config::DefaultExtrinsicParamsBuilder as Params;
//! use subxt_core::tx;
//! use subxt_core::utils::H256;
@@ -59,7 +59,7 @@
pub mod payload;
pub mod signer;
use crate::config::{Config, ExtrinsicParams, ExtrinsicParamsEncoder, Hasher};
use crate::config::{Config, ExtrinsicParams, ExtrinsicParamsEncoder, HashFor, Hasher};
use crate::error::{Error, ExtrinsicError, MetadataError};
use crate::metadata::Metadata;
use crate::utils::Encoded;
@@ -406,7 +406,8 @@ impl<T: Config> PartialTransactionV5<T> {
/// This represents a signed transaction that's ready to be submitted.
/// Use [`Transaction::encoded()`] or [`Transaction::into_encoded()`] to
/// get the bytes for it, or [`Transaction::hash()`] to get the hash.
/// get the bytes for it, or [`Transaction::hash_with()`] to hash the transaction
/// given an instance of [`Config::Hasher`].
pub struct Transaction<T> {
encoded: Encoded,
marker: core::marker::PhantomData<T>,
@@ -422,9 +423,12 @@ impl<T: Config> Transaction<T> {
}
}
/// Calculate and return the hash of the extrinsic, based on the configured hasher.
pub fn hash(&self) -> T::Hash {
T::Hasher::hash_of(&self.encoded)
/// Calculate and return the hash of the extrinsic, based on the provided hasher.
/// If you don't have a hasher to hand, you can construct one using the metadata
/// with `T::Hasher::new(&metadata)`. This will create a hasher suitable for the
/// current chain where possible.
pub fn hash_with(&self, hasher: T::Hasher) -> HashFor<T> {
hasher.hash_of(&self.encoded)
}
/// Returns the SCALE encoded extrinsic bytes.