mirror of
https://github.com/pezkuwichain/pezkuwi-subxt.git
synced 2026-06-13 21:01:05 +00:00
Introduce Metadata type (#974)
* WIP new Metadata type * Finish basic Metadata impl inc hashing and validation * remove caching from metadata; can add that higher up * remove caches * update retain to use Metadata * clippy fixes * update codegen to use Metadata * clippy * WIP fixing subxt lib * WIP fixing tests, rebuild artifacts, fix OrderedMap::retain * get --all-targets compiling * move DispatchError type lookup back to being optional * cargo clippy * fix docs * re-use VariantIndex to get variants * add docs and enforce docs on metadata crate * fix docs * add test and fix docs * cargo fmt * address review comments * update lockfiles * ExactSizeIter so we can ask for len() of things (and hopefully soon is_empty()
This commit is contained in:
@@ -4,13 +4,13 @@
|
||||
|
||||
use crate::{
|
||||
dynamic::{DecodedValueThunk, Value},
|
||||
error::{Error, StorageAddressError},
|
||||
error::{Error, MetadataError, StorageAddressError},
|
||||
metadata::{DecodeWithMetadata, EncodeWithMetadata, Metadata},
|
||||
utils::{Encoded, Static},
|
||||
};
|
||||
use frame_metadata::v15::{StorageEntryType, StorageHasher};
|
||||
use scale_info::TypeDef;
|
||||
use std::borrow::Cow;
|
||||
use subxt_metadata::{StorageEntryType, StorageHasher};
|
||||
|
||||
/// This represents a storage address. Anything implementing this trait
|
||||
/// can be used to fetch and iterate over storage entries.
|
||||
@@ -138,10 +138,17 @@ where
|
||||
}
|
||||
|
||||
fn append_entry_bytes(&self, metadata: &Metadata, bytes: &mut Vec<u8>) -> Result<(), Error> {
|
||||
let pallet = metadata.pallet(&self.pallet_name)?;
|
||||
let storage = pallet.storage(&self.entry_name)?;
|
||||
let pallet = metadata
|
||||
.pallet_by_name(self.pallet_name())
|
||||
.ok_or_else(|| MetadataError::PalletNameNotFound(self.pallet_name().to_owned()))?;
|
||||
let storage = pallet
|
||||
.storage()
|
||||
.ok_or_else(|| MetadataError::StorageNotFoundInPallet(self.pallet_name().to_owned()))?;
|
||||
let entry = storage
|
||||
.entry_by_name(self.entry_name())
|
||||
.ok_or_else(|| MetadataError::StorageEntryNotFound(self.entry_name().to_owned()))?;
|
||||
|
||||
match &storage.ty {
|
||||
match entry.entry_type() {
|
||||
StorageEntryType::Plain(_) => {
|
||||
if !self.storage_entry_keys.is_empty() {
|
||||
Err(StorageAddressError::WrongNumberOfKeys {
|
||||
@@ -153,10 +160,13 @@ where
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
StorageEntryType::Map { hashers, key, .. } => {
|
||||
StorageEntryType::Map {
|
||||
hashers, key_ty, ..
|
||||
} => {
|
||||
let ty = metadata
|
||||
.resolve_type(key.id)
|
||||
.ok_or(StorageAddressError::TypeNotFound(key.id))?;
|
||||
.types()
|
||||
.resolve(*key_ty)
|
||||
.ok_or(MetadataError::TypeNotFound(*key_ty))?;
|
||||
|
||||
// If the key is a tuple, we encode each value to the corresponding tuple type.
|
||||
// If the key is not a tuple, encode a single value to the key type.
|
||||
@@ -164,7 +174,7 @@ where
|
||||
TypeDef::Tuple(tuple) => {
|
||||
either::Either::Left(tuple.fields.iter().map(|f| f.id))
|
||||
}
|
||||
_other => either::Either::Right(std::iter::once(key.id)),
|
||||
_other => either::Either::Right(std::iter::once(*key_ty)),
|
||||
};
|
||||
|
||||
if type_ids.len() != self.storage_entry_keys.len() {
|
||||
|
||||
@@ -9,7 +9,7 @@ use super::{
|
||||
|
||||
use crate::{
|
||||
client::{OfflineClientT, OnlineClientT},
|
||||
error::Error,
|
||||
error::{Error, MetadataError},
|
||||
Config,
|
||||
};
|
||||
use derivative::Derivative;
|
||||
@@ -43,7 +43,11 @@ where
|
||||
/// Return an error if the address was not valid or something went wrong trying to validate it (ie
|
||||
/// the pallet or storage entry in question do not exist at all).
|
||||
pub fn validate<Address: StorageAddress>(&self, address: &Address) -> Result<(), Error> {
|
||||
validate_storage_address(address, &self.client.metadata())
|
||||
let metadata = self.client.metadata();
|
||||
let pallet_metadata = metadata
|
||||
.pallet_by_name(address.pallet_name())
|
||||
.ok_or_else(|| MetadataError::PalletNameNotFound(address.pallet_name().to_owned()))?;
|
||||
validate_storage_address(address, pallet_metadata)
|
||||
}
|
||||
|
||||
/// Convert some storage address into the raw bytes that would be submitted to the node in order
|
||||
|
||||
@@ -5,15 +5,14 @@
|
||||
use super::storage_address::{StorageAddress, Yes};
|
||||
use crate::{
|
||||
client::OnlineClientT,
|
||||
error::Error,
|
||||
error::{Error, MetadataError},
|
||||
metadata::{DecodeWithMetadata, Metadata},
|
||||
rpc::types::{StorageData, StorageKey},
|
||||
Config,
|
||||
};
|
||||
use derivative::Derivative;
|
||||
use frame_metadata::v15::StorageEntryType;
|
||||
use scale_info::form::PortableForm;
|
||||
use std::{future::Future, marker::PhantomData};
|
||||
use subxt_metadata::{PalletMetadata, StorageEntryMetadata, StorageEntryType};
|
||||
|
||||
/// Query the runtime storage.
|
||||
#[derive(Derivative)]
|
||||
@@ -94,22 +93,21 @@ where
|
||||
{
|
||||
let client = self.clone();
|
||||
async move {
|
||||
let metadata = client.client.metadata();
|
||||
let (pallet, entry) =
|
||||
lookup_entry_details(address.pallet_name(), address.entry_name(), &metadata)?;
|
||||
|
||||
// Metadata validation checks whether the static address given
|
||||
// is likely to actually correspond to a real storage entry or not.
|
||||
// if not, it means static codegen doesn't line up with runtime
|
||||
// metadata.
|
||||
validate_storage_address(address, &client.client.metadata())?;
|
||||
validate_storage_address(address, pallet)?;
|
||||
|
||||
// Look up the return type ID to enable DecodeWithMetadata:
|
||||
let metadata = client.client.metadata();
|
||||
let lookup_bytes = super::utils::storage_address_bytes(address, &metadata)?;
|
||||
if let Some(data) = client.fetch_raw(&lookup_bytes).await? {
|
||||
let val = decode_storage_with_metadata::<Address::Target>(
|
||||
&mut &*data,
|
||||
address.pallet_name(),
|
||||
address.entry_name(),
|
||||
&metadata,
|
||||
)?;
|
||||
let val =
|
||||
decode_storage_with_metadata::<Address::Target>(&mut &*data, &metadata, entry)?;
|
||||
Ok(Some(val))
|
||||
} else {
|
||||
Ok(None)
|
||||
@@ -128,18 +126,17 @@ where
|
||||
let client = self.clone();
|
||||
async move {
|
||||
let pallet_name = address.pallet_name();
|
||||
let storage_name = address.entry_name();
|
||||
let entry_name = address.entry_name();
|
||||
// Metadata validation happens via .fetch():
|
||||
if let Some(data) = client.fetch(address).await? {
|
||||
Ok(data)
|
||||
} else {
|
||||
let metadata = client.client.metadata();
|
||||
let (_pallet_metadata, storage_entry) =
|
||||
lookup_entry_details(pallet_name, entry_name, &metadata)?;
|
||||
|
||||
// We have to dig into metadata already, so no point using the optimised `decode_storage_with_metadata` call.
|
||||
let pallet_metadata = metadata.pallet(pallet_name)?;
|
||||
let storage_metadata = pallet_metadata.storage(storage_name)?;
|
||||
let return_ty_id = return_type_from_storage_entry_type(&storage_metadata.ty);
|
||||
let bytes = &mut &storage_metadata.default[..];
|
||||
let return_ty_id = return_type_from_storage_entry_type(storage_entry.entry_type());
|
||||
let bytes = &mut storage_entry.default_bytes();
|
||||
|
||||
let val = Address::Target::decode_with_metadata(bytes, return_ty_id, &metadata)?;
|
||||
Ok(val)
|
||||
@@ -209,19 +206,20 @@ where
|
||||
let client = self.clone();
|
||||
let block_hash = self.block_hash;
|
||||
async move {
|
||||
let metadata = client.client.metadata();
|
||||
let (pallet, entry) =
|
||||
lookup_entry_details(address.pallet_name(), address.entry_name(), &metadata)?;
|
||||
|
||||
// Metadata validation checks whether the static address given
|
||||
// is likely to actually correspond to a real storage entry or not.
|
||||
// if not, it means static codegen doesn't line up with runtime
|
||||
// metadata.
|
||||
validate_storage_address(&address, &client.client.metadata())?;
|
||||
|
||||
let metadata = client.client.metadata();
|
||||
validate_storage_address(&address, pallet)?;
|
||||
|
||||
// Look up the return type for flexible decoding. Do this once here to avoid
|
||||
// potentially doing it every iteration if we used `decode_storage_with_metadata`
|
||||
// in the iterator.
|
||||
let return_type_id =
|
||||
lookup_storage_return_type(&metadata, address.pallet_name(), address.entry_name())?;
|
||||
let return_type_id = return_type_from_storage_entry_type(entry.entry_type());
|
||||
|
||||
// The root pallet/entry bytes for this storage entry:
|
||||
let address_root_bytes = super::utils::storage_address_root_bytes(&address);
|
||||
@@ -309,68 +307,63 @@ where
|
||||
/// Validate a storage address against the metadata.
|
||||
pub(crate) fn validate_storage_address<Address: StorageAddress>(
|
||||
address: &Address,
|
||||
metadata: &Metadata,
|
||||
pallet: PalletMetadata<'_>,
|
||||
) -> Result<(), Error> {
|
||||
if let Some(hash) = address.validation_hash() {
|
||||
validate_storage(address.pallet_name(), address.entry_name(), hash, metadata)?;
|
||||
validate_storage(pallet, address.entry_name(), hash)?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Validate a storage entry against the metadata.
|
||||
fn validate_storage(
|
||||
/// Return details about the given storage entry.
|
||||
fn lookup_entry_details<'a>(
|
||||
pallet_name: &str,
|
||||
storage_name: &str,
|
||||
hash: [u8; 32],
|
||||
metadata: &Metadata,
|
||||
) -> Result<(), Error> {
|
||||
let expected_hash = match metadata.storage_hash(pallet_name, storage_name) {
|
||||
Ok(hash) => hash,
|
||||
Err(e) => return Err(e.into()),
|
||||
};
|
||||
match expected_hash == hash {
|
||||
true => Ok(()),
|
||||
false => Err(crate::error::MetadataError::IncompatibleStorageMetadata(
|
||||
pallet_name.into(),
|
||||
storage_name.into(),
|
||||
)
|
||||
.into()),
|
||||
}
|
||||
entry_name: &str,
|
||||
metadata: &'a Metadata,
|
||||
) -> Result<(PalletMetadata<'a>, &'a StorageEntryMetadata), Error> {
|
||||
let pallet_metadata = metadata
|
||||
.pallet_by_name(pallet_name)
|
||||
.ok_or_else(|| MetadataError::PalletNameNotFound(pallet_name.to_owned()))?;
|
||||
let storage_metadata = pallet_metadata
|
||||
.storage()
|
||||
.ok_or_else(|| MetadataError::StorageNotFoundInPallet(pallet_name.to_owned()))?;
|
||||
let storage_entry = storage_metadata
|
||||
.entry_by_name(entry_name)
|
||||
.ok_or_else(|| MetadataError::StorageEntryNotFound(entry_name.to_owned()))?;
|
||||
Ok((pallet_metadata, storage_entry))
|
||||
}
|
||||
|
||||
/// look up a return type ID for some storage entry.
|
||||
fn lookup_storage_return_type(
|
||||
metadata: &Metadata,
|
||||
pallet: &str,
|
||||
entry: &str,
|
||||
) -> Result<u32, Error> {
|
||||
let storage_entry_type = &metadata.pallet(pallet)?.storage(entry)?.ty;
|
||||
|
||||
Ok(return_type_from_storage_entry_type(storage_entry_type))
|
||||
/// Validate a storage entry against the metadata.
|
||||
fn validate_storage(
|
||||
pallet: PalletMetadata<'_>,
|
||||
storage_name: &str,
|
||||
hash: [u8; 32],
|
||||
) -> Result<(), Error> {
|
||||
let Some(expected_hash) = pallet.storage_hash(storage_name) else {
|
||||
return Err(MetadataError::IncompatibleCodegen.into())
|
||||
};
|
||||
if expected_hash != hash {
|
||||
return Err(MetadataError::IncompatibleCodegen.into());
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Fetch the return type out of a [`StorageEntryType`].
|
||||
fn return_type_from_storage_entry_type(entry: &StorageEntryType<PortableForm>) -> u32 {
|
||||
fn return_type_from_storage_entry_type(entry: &StorageEntryType) -> u32 {
|
||||
match entry {
|
||||
StorageEntryType::Plain(ty) => ty.id,
|
||||
StorageEntryType::Map { value, .. } => value.id,
|
||||
StorageEntryType::Plain(ty) => *ty,
|
||||
StorageEntryType::Map { value_ty, .. } => *value_ty,
|
||||
}
|
||||
}
|
||||
|
||||
/// Given some bytes, a pallet and storage name, decode the response.
|
||||
fn decode_storage_with_metadata<T: DecodeWithMetadata>(
|
||||
bytes: &mut &[u8],
|
||||
pallet_name: &str,
|
||||
storage_entry: &str,
|
||||
metadata: &Metadata,
|
||||
storage_metadata: &StorageEntryMetadata,
|
||||
) -> Result<T, Error> {
|
||||
let ty = &metadata.pallet(pallet_name)?.storage(storage_entry)?.ty;
|
||||
|
||||
let id = match ty {
|
||||
StorageEntryType::Plain(ty) => ty.id,
|
||||
StorageEntryType::Map { value, .. } => value.id,
|
||||
};
|
||||
|
||||
let val = T::decode_with_metadata(bytes, id, metadata)?;
|
||||
let ty = storage_metadata.entry_type();
|
||||
let return_ty = return_type_from_storage_entry_type(ty);
|
||||
let val = T::decode_with_metadata(bytes, return_ty, metadata)?;
|
||||
Ok(val)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user