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:
James Wilson
2023-05-25 10:35:21 +01:00
committed by GitHub
parent f344d0dd4d
commit b9f5419095
64 changed files with 6818 additions and 5719 deletions
+19 -9
View File
@@ -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() {
+6 -2
View File
@@ -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
+57 -64
View File
@@ -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)
}