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
+82
View File
@@ -0,0 +1,82 @@
// Copyright 2019-2023 Parity Technologies (UK) Ltd.
// This file is dual-licensed as Apache-2.0 or GPL-3.0.
// see LICENSE for license details.
mod v14;
mod v15;
/// An error emitted if something goes wrong converting [`frame_metadata`]
/// types into [`crate::Metadata`].
#[derive(Debug, thiserror::Error)]
#[non_exhaustive]
pub enum TryFromError {
/// Type missing from type registry
#[error("Type {0} is expected but not found in the type registry")]
TypeNotFound(u32),
/// Type was not a variant/enum type
#[error("Type {0} was not a variant/enum type, but is expected to be one")]
VariantExpected(u32),
/// An unsupported metadata version was provided.
#[error("Cannot convert v{0} metadata into Metadata type")]
UnsupportedMetadataVersion(u32),
}
impl From<crate::Metadata> for frame_metadata::RuntimeMetadataPrefixed {
fn from(value: crate::Metadata) -> Self {
let m: frame_metadata::v15::RuntimeMetadataV15 = value.into();
m.into()
}
}
impl TryFrom<frame_metadata::RuntimeMetadataPrefixed> for crate::Metadata {
type Error = TryFromError;
fn try_from(value: frame_metadata::RuntimeMetadataPrefixed) -> Result<Self, Self::Error> {
match value.1 {
frame_metadata::RuntimeMetadata::V0(_) => {
Err(TryFromError::UnsupportedMetadataVersion(0))
}
frame_metadata::RuntimeMetadata::V1(_) => {
Err(TryFromError::UnsupportedMetadataVersion(1))
}
frame_metadata::RuntimeMetadata::V2(_) => {
Err(TryFromError::UnsupportedMetadataVersion(2))
}
frame_metadata::RuntimeMetadata::V3(_) => {
Err(TryFromError::UnsupportedMetadataVersion(3))
}
frame_metadata::RuntimeMetadata::V4(_) => {
Err(TryFromError::UnsupportedMetadataVersion(4))
}
frame_metadata::RuntimeMetadata::V5(_) => {
Err(TryFromError::UnsupportedMetadataVersion(5))
}
frame_metadata::RuntimeMetadata::V6(_) => {
Err(TryFromError::UnsupportedMetadataVersion(6))
}
frame_metadata::RuntimeMetadata::V7(_) => {
Err(TryFromError::UnsupportedMetadataVersion(7))
}
frame_metadata::RuntimeMetadata::V8(_) => {
Err(TryFromError::UnsupportedMetadataVersion(8))
}
frame_metadata::RuntimeMetadata::V9(_) => {
Err(TryFromError::UnsupportedMetadataVersion(9))
}
frame_metadata::RuntimeMetadata::V10(_) => {
Err(TryFromError::UnsupportedMetadataVersion(10))
}
frame_metadata::RuntimeMetadata::V11(_) => {
Err(TryFromError::UnsupportedMetadataVersion(11))
}
frame_metadata::RuntimeMetadata::V12(_) => {
Err(TryFromError::UnsupportedMetadataVersion(12))
}
frame_metadata::RuntimeMetadata::V13(_) => {
Err(TryFromError::UnsupportedMetadataVersion(13))
}
frame_metadata::RuntimeMetadata::V14(m) => m.try_into(),
frame_metadata::RuntimeMetadata::V15(m) => m.try_into(),
}
}
}
+194
View File
@@ -0,0 +1,194 @@
// Copyright 2019-2023 Parity Technologies (UK) Ltd.
// This file is dual-licensed as Apache-2.0 or GPL-3.0.
// see LICENSE for license details.
use super::TryFromError;
use crate::Metadata;
use frame_metadata::{v14, v15};
impl TryFrom<v14::RuntimeMetadataV14> for Metadata {
type Error = TryFromError;
fn try_from(value: v14::RuntimeMetadataV14) -> Result<Self, Self::Error> {
// Convert to v15 and then convert that into Metadata.
v14_to_v15(value).try_into()
}
}
impl From<Metadata> for v14::RuntimeMetadataV14 {
fn from(val: Metadata) -> Self {
let v15 = val.into();
v15_to_v14(v15)
}
}
fn v15_to_v14(metadata: v15::RuntimeMetadataV15) -> v14::RuntimeMetadataV14 {
v14::RuntimeMetadataV14 {
types: metadata.types,
pallets: metadata
.pallets
.into_iter()
.map(|pallet| frame_metadata::v14::PalletMetadata {
name: pallet.name,
storage: pallet
.storage
.map(|storage| frame_metadata::v14::PalletStorageMetadata {
prefix: storage.prefix,
entries: storage
.entries
.into_iter()
.map(|entry| {
let modifier = match entry.modifier {
frame_metadata::v15::StorageEntryModifier::Optional => {
frame_metadata::v14::StorageEntryModifier::Optional
}
frame_metadata::v15::StorageEntryModifier::Default => {
frame_metadata::v14::StorageEntryModifier::Default
}
};
let ty = match entry.ty {
frame_metadata::v15::StorageEntryType::Plain(ty) => {
frame_metadata::v14::StorageEntryType::Plain(ty)
},
frame_metadata::v15::StorageEntryType::Map {
hashers,
key,
value,
} => frame_metadata::v14::StorageEntryType::Map {
hashers: hashers.into_iter().map(|hasher| match hasher {
frame_metadata::v15::StorageHasher::Blake2_128 => frame_metadata::v14::StorageHasher::Blake2_128,
frame_metadata::v15::StorageHasher::Blake2_256 => frame_metadata::v14::StorageHasher::Blake2_256,
frame_metadata::v15::StorageHasher::Blake2_128Concat => frame_metadata::v14::StorageHasher::Blake2_128Concat ,
frame_metadata::v15::StorageHasher::Twox128 => frame_metadata::v14::StorageHasher::Twox128,
frame_metadata::v15::StorageHasher::Twox256 => frame_metadata::v14::StorageHasher::Twox256,
frame_metadata::v15::StorageHasher::Twox64Concat => frame_metadata::v14::StorageHasher::Twox64Concat,
frame_metadata::v15::StorageHasher::Identity=> frame_metadata::v14::StorageHasher::Identity,
}).collect(),
key,
value,
},
};
frame_metadata::v14::StorageEntryMetadata {
name: entry.name,
modifier,
ty,
default: entry.default,
docs: entry.docs,
}
})
.collect(),
}),
calls: pallet.calls.map(|calls| frame_metadata::v14::PalletCallMetadata { ty: calls.ty } ),
event: pallet.event.map(|event| frame_metadata::v14::PalletEventMetadata { ty: event.ty } ),
constants: pallet.constants.into_iter().map(|constant| frame_metadata::v14::PalletConstantMetadata {
name: constant.name,
ty: constant.ty,
value: constant.value,
docs: constant.docs,
} ).collect(),
error: pallet.error.map(|error| frame_metadata::v14::PalletErrorMetadata { ty: error.ty } ),
index: pallet.index,
})
.collect(),
extrinsic: frame_metadata::v14::ExtrinsicMetadata {
ty: metadata.extrinsic.ty,
version: metadata.extrinsic.version,
signed_extensions: metadata.extrinsic.signed_extensions.into_iter().map(|ext| {
frame_metadata::v14::SignedExtensionMetadata {
identifier: ext.identifier,
ty: ext.ty,
additional_signed: ext.additional_signed,
}
}).collect()
},
ty: metadata.ty,
}
}
fn v14_to_v15(metadata: v14::RuntimeMetadataV14) -> v15::RuntimeMetadataV15 {
v15::RuntimeMetadataV15 {
types: metadata.types,
pallets: metadata
.pallets
.into_iter()
.map(|pallet| frame_metadata::v15::PalletMetadata {
name: pallet.name,
storage: pallet
.storage
.map(|storage| frame_metadata::v15::PalletStorageMetadata {
prefix: storage.prefix,
entries: storage
.entries
.into_iter()
.map(|entry| {
let modifier = match entry.modifier {
frame_metadata::v14::StorageEntryModifier::Optional => {
frame_metadata::v15::StorageEntryModifier::Optional
}
frame_metadata::v14::StorageEntryModifier::Default => {
frame_metadata::v15::StorageEntryModifier::Default
}
};
let ty = match entry.ty {
frame_metadata::v14::StorageEntryType::Plain(ty) => {
frame_metadata::v15::StorageEntryType::Plain(ty)
},
frame_metadata::v14::StorageEntryType::Map {
hashers,
key,
value,
} => frame_metadata::v15::StorageEntryType::Map {
hashers: hashers.into_iter().map(|hasher| match hasher {
frame_metadata::v14::StorageHasher::Blake2_128 => frame_metadata::v15::StorageHasher::Blake2_128,
frame_metadata::v14::StorageHasher::Blake2_256 => frame_metadata::v15::StorageHasher::Blake2_256,
frame_metadata::v14::StorageHasher::Blake2_128Concat => frame_metadata::v15::StorageHasher::Blake2_128Concat ,
frame_metadata::v14::StorageHasher::Twox128 => frame_metadata::v15::StorageHasher::Twox128,
frame_metadata::v14::StorageHasher::Twox256 => frame_metadata::v15::StorageHasher::Twox256,
frame_metadata::v14::StorageHasher::Twox64Concat => frame_metadata::v15::StorageHasher::Twox64Concat,
frame_metadata::v14::StorageHasher::Identity=> frame_metadata::v15::StorageHasher::Identity,
}).collect(),
key,
value,
},
};
frame_metadata::v15::StorageEntryMetadata {
name: entry.name,
modifier,
ty,
default: entry.default,
docs: entry.docs,
}
})
.collect(),
}),
calls: pallet.calls.map(|calls| frame_metadata::v15::PalletCallMetadata { ty: calls.ty } ),
event: pallet.event.map(|event| frame_metadata::v15::PalletEventMetadata { ty: event.ty } ),
constants: pallet.constants.into_iter().map(|constant| frame_metadata::v15::PalletConstantMetadata {
name: constant.name,
ty: constant.ty,
value: constant.value,
docs: constant.docs,
} ).collect(),
error: pallet.error.map(|error| frame_metadata::v15::PalletErrorMetadata { ty: error.ty } ),
index: pallet.index,
docs: Default::default(),
})
.collect(),
extrinsic: frame_metadata::v15::ExtrinsicMetadata {
ty: metadata.extrinsic.ty,
version: metadata.extrinsic.version,
signed_extensions: metadata.extrinsic.signed_extensions.into_iter().map(|ext| {
frame_metadata::v15::SignedExtensionMetadata {
identifier: ext.identifier,
ty: ext.ty,
additional_signed: ext.additional_signed,
}
}).collect()
},
ty: metadata.ty,
apis: Default::default(),
}
}
+390
View File
@@ -0,0 +1,390 @@
// Copyright 2019-2023 Parity Technologies (UK) Ltd.
// This file is dual-licensed as Apache-2.0 or GPL-3.0.
// see LICENSE for license details.
use super::TryFromError;
use crate::utils::variant_index::VariantIndex;
use crate::{
utils::ordered_map::OrderedMap, ArcStr, ConstantMetadata, ExtrinsicMetadata, Metadata,
PalletMetadataInner, RuntimeApiMetadataInner, RuntimeApiMethodMetadata,
RuntimeApiMethodParamMetadata, SignedExtensionMetadata, StorageEntryMetadata,
StorageEntryModifier, StorageEntryType, StorageHasher, StorageMetadata,
};
use frame_metadata::v15;
use scale_info::form::PortableForm;
use std::collections::HashMap;
// Converting from V15 metadata into our Subxt repr.
mod from_v15 {
use super::*;
impl TryFrom<v15::RuntimeMetadataV15> for Metadata {
type Error = TryFromError;
fn try_from(m: v15::RuntimeMetadataV15) -> Result<Self, TryFromError> {
let mut pallets = OrderedMap::new();
let mut pallets_by_index = HashMap::new();
for (pos, p) in m.pallets.into_iter().enumerate() {
let name: ArcStr = p.name.into();
let storage = p.storage.map(|s| StorageMetadata {
prefix: s.prefix,
entries: s
.entries
.into_iter()
.map(|s| {
let name: ArcStr = s.name.clone().into();
(name.clone(), from_storage_entry_metadata(name, s))
})
.collect(),
});
let constants = p.constants.into_iter().map(|c| {
let name: ArcStr = c.name.clone().into();
(name.clone(), from_constant_metadata(name, c))
});
let call_variant_index =
VariantIndex::build(p.calls.as_ref().map(|c| c.ty.id), &m.types);
let error_variant_index =
VariantIndex::build(p.error.as_ref().map(|e| e.ty.id), &m.types);
let event_variant_index =
VariantIndex::build(p.event.as_ref().map(|e| e.ty.id), &m.types);
pallets_by_index.insert(p.index, pos);
pallets.push_insert(
name.clone(),
PalletMetadataInner {
name,
index: p.index,
storage,
call_ty: p.calls.map(|c| c.ty.id),
call_variant_index,
event_ty: p.event.map(|e| e.ty.id),
event_variant_index,
error_ty: p.error.map(|e| e.ty.id),
error_variant_index,
constants: constants.collect(),
docs: p.docs,
},
);
}
let apis = m.apis.into_iter().map(|api| {
let name: ArcStr = api.name.clone().into();
(name.clone(), from_runtime_api_metadata(name, api))
});
let dispatch_error_ty = m
.types
.types
.iter()
.find(|ty| ty.ty.path.segments == ["sp_runtime", "DispatchError"])
.map(|ty| ty.id);
Ok(Metadata {
types: m.types,
pallets,
pallets_by_index,
extrinsic: from_extrinsic_metadata(m.extrinsic),
runtime_ty: m.ty.id,
dispatch_error_ty,
apis: apis.collect(),
})
}
}
fn from_signed_extension_metadata(
value: v15::SignedExtensionMetadata<PortableForm>,
) -> SignedExtensionMetadata {
SignedExtensionMetadata {
identifier: value.identifier,
extra_ty: value.ty.id,
additional_ty: value.additional_signed.id,
}
}
fn from_extrinsic_metadata(value: v15::ExtrinsicMetadata<PortableForm>) -> ExtrinsicMetadata {
ExtrinsicMetadata {
ty: value.ty.id,
version: value.version,
signed_extensions: value
.signed_extensions
.into_iter()
.map(from_signed_extension_metadata)
.collect(),
}
}
fn from_storage_hasher(value: v15::StorageHasher) -> StorageHasher {
match value {
v15::StorageHasher::Blake2_128 => StorageHasher::Blake2_128,
v15::StorageHasher::Blake2_256 => StorageHasher::Blake2_256,
v15::StorageHasher::Blake2_128Concat => StorageHasher::Blake2_128Concat,
v15::StorageHasher::Twox128 => StorageHasher::Twox128,
v15::StorageHasher::Twox256 => StorageHasher::Twox256,
v15::StorageHasher::Twox64Concat => StorageHasher::Twox64Concat,
v15::StorageHasher::Identity => StorageHasher::Identity,
}
}
fn from_storage_entry_type(value: v15::StorageEntryType<PortableForm>) -> StorageEntryType {
match value {
v15::StorageEntryType::Plain(ty) => StorageEntryType::Plain(ty.id),
v15::StorageEntryType::Map {
hashers,
key,
value,
} => StorageEntryType::Map {
hashers: hashers.into_iter().map(from_storage_hasher).collect(),
key_ty: key.id,
value_ty: value.id,
},
}
}
fn from_storage_entry_modifier(value: v15::StorageEntryModifier) -> StorageEntryModifier {
match value {
v15::StorageEntryModifier::Optional => StorageEntryModifier::Optional,
v15::StorageEntryModifier::Default => StorageEntryModifier::Default,
}
}
fn from_storage_entry_metadata(
name: ArcStr,
s: v15::StorageEntryMetadata<PortableForm>,
) -> StorageEntryMetadata {
StorageEntryMetadata {
name,
modifier: from_storage_entry_modifier(s.modifier),
entry_type: from_storage_entry_type(s.ty),
default: s.default,
docs: s.docs,
}
}
fn from_constant_metadata(
name: ArcStr,
s: v15::PalletConstantMetadata<PortableForm>,
) -> ConstantMetadata {
ConstantMetadata {
name,
ty: s.ty.id,
value: s.value,
docs: s.docs,
}
}
fn from_runtime_api_metadata(
name: ArcStr,
s: v15::RuntimeApiMetadata<PortableForm>,
) -> RuntimeApiMetadataInner {
RuntimeApiMetadataInner {
name,
docs: s.docs,
methods: s
.methods
.into_iter()
.map(|m| {
let name: ArcStr = m.name.clone().into();
(name.clone(), from_runtime_api_method_metadata(name, m))
})
.collect(),
}
}
fn from_runtime_api_method_metadata(
name: ArcStr,
s: v15::RuntimeApiMethodMetadata<PortableForm>,
) -> RuntimeApiMethodMetadata {
RuntimeApiMethodMetadata {
name,
inputs: s
.inputs
.into_iter()
.map(from_runtime_api_method_param_metadata)
.collect(),
output_ty: s.output.id,
docs: s.docs,
}
}
fn from_runtime_api_method_param_metadata(
s: v15::RuntimeApiMethodParamMetadata<PortableForm>,
) -> RuntimeApiMethodParamMetadata {
RuntimeApiMethodParamMetadata {
name: s.name,
ty: s.ty.id,
}
}
}
// Converting from our metadata repr to V15 metadata.
mod into_v15 {
use super::*;
impl From<Metadata> for v15::RuntimeMetadataV15 {
fn from(m: Metadata) -> Self {
let pallets = m.pallets.into_values().into_iter().map(|p| {
let storage = p.storage.map(|s| v15::PalletStorageMetadata {
prefix: s.prefix,
entries: s
.entries
.into_values()
.into_iter()
.map(from_storage_entry_metadata)
.collect(),
});
v15::PalletMetadata {
name: (*p.name).to_owned(),
calls: p
.call_ty
.map(|id| v15::PalletCallMetadata { ty: id.into() }),
event: p
.event_ty
.map(|id| v15::PalletEventMetadata { ty: id.into() }),
error: p
.error_ty
.map(|id| v15::PalletErrorMetadata { ty: id.into() }),
storage,
constants: p
.constants
.into_values()
.into_iter()
.map(from_constant_metadata)
.collect(),
index: p.index,
docs: p.docs,
}
});
v15::RuntimeMetadataV15 {
types: m.types,
pallets: pallets.collect(),
ty: m.runtime_ty.into(),
extrinsic: from_extrinsic_metadata(m.extrinsic),
apis: m
.apis
.into_values()
.into_iter()
.map(from_runtime_api_metadata)
.collect(),
}
}
}
fn from_runtime_api_metadata(
r: RuntimeApiMetadataInner,
) -> v15::RuntimeApiMetadata<PortableForm> {
v15::RuntimeApiMetadata {
name: (*r.name).to_owned(),
methods: r
.methods
.into_values()
.into_iter()
.map(from_runtime_api_method_metadata)
.collect(),
docs: r.docs,
}
}
fn from_runtime_api_method_metadata(
m: RuntimeApiMethodMetadata,
) -> v15::RuntimeApiMethodMetadata<PortableForm> {
v15::RuntimeApiMethodMetadata {
name: (*m.name).to_owned(),
inputs: m
.inputs
.into_iter()
.map(from_runtime_api_method_param_metadata)
.collect(),
output: m.output_ty.into(),
docs: m.docs,
}
}
fn from_runtime_api_method_param_metadata(
p: RuntimeApiMethodParamMetadata,
) -> v15::RuntimeApiMethodParamMetadata<PortableForm> {
v15::RuntimeApiMethodParamMetadata {
name: p.name,
ty: p.ty.into(),
}
}
fn from_extrinsic_metadata(e: ExtrinsicMetadata) -> v15::ExtrinsicMetadata<PortableForm> {
v15::ExtrinsicMetadata {
ty: e.ty.into(),
version: e.version,
signed_extensions: e
.signed_extensions
.into_iter()
.map(from_signed_extension_metadata)
.collect(),
}
}
fn from_signed_extension_metadata(
s: SignedExtensionMetadata,
) -> v15::SignedExtensionMetadata<PortableForm> {
v15::SignedExtensionMetadata {
identifier: s.identifier,
ty: s.extra_ty.into(),
additional_signed: s.additional_ty.into(),
}
}
fn from_constant_metadata(c: ConstantMetadata) -> v15::PalletConstantMetadata<PortableForm> {
v15::PalletConstantMetadata {
name: (*c.name).to_owned(),
ty: c.ty.into(),
value: c.value,
docs: c.docs,
}
}
fn from_storage_entry_metadata(
s: StorageEntryMetadata,
) -> v15::StorageEntryMetadata<PortableForm> {
v15::StorageEntryMetadata {
docs: s.docs,
default: s.default,
name: (*s.name).to_owned(),
ty: from_storage_entry_type(s.entry_type),
modifier: from_storage_entry_modifier(s.modifier),
}
}
fn from_storage_entry_modifier(s: StorageEntryModifier) -> v15::StorageEntryModifier {
match s {
StorageEntryModifier::Default => v15::StorageEntryModifier::Default,
StorageEntryModifier::Optional => v15::StorageEntryModifier::Optional,
}
}
fn from_storage_entry_type(s: StorageEntryType) -> v15::StorageEntryType<PortableForm> {
match s {
StorageEntryType::Plain(ty) => v15::StorageEntryType::Plain(ty.into()),
StorageEntryType::Map {
hashers,
key_ty,
value_ty,
} => v15::StorageEntryType::Map {
hashers: hashers.into_iter().map(from_storage_hasher).collect(),
key: key_ty.into(),
value: value_ty.into(),
},
}
}
fn from_storage_hasher(s: StorageHasher) -> v15::StorageHasher {
match s {
StorageHasher::Blake2_128 => v15::StorageHasher::Blake2_128,
StorageHasher::Blake2_256 => v15::StorageHasher::Blake2_256,
StorageHasher::Blake2_128Concat => v15::StorageHasher::Blake2_128Concat,
StorageHasher::Twox128 => v15::StorageHasher::Twox128,
StorageHasher::Twox256 => v15::StorageHasher::Twox256,
StorageHasher::Twox64Concat => v15::StorageHasher::Twox64Concat,
StorageHasher::Identity => v15::StorageHasher::Identity,
}
}
}
+609 -91
View File
@@ -2,101 +2,619 @@
// This file is dual-licensed as Apache-2.0 or GPL-3.0.
// see LICENSE for license details.
mod retain;
mod validation;
//! A representation of the metadata provided by a substrate based node.
//! This representation is optimized to be used by Subxt and related crates,
//! and is independent of the different versions of metadata that can be
//! provided from a node.
//!
//! Typically, this will be constructed by either:
//!
//! 1. Calling `Metadata::decode()` given some metadata bytes obtained
//! from a node (this uses [`codec::Decode`]).
//! 2. Obtaining [`frame_metadata::RuntimeMetadataPrefixed`], and then
//! using `.try_into()` to convert it into [`Metadata`].
use frame_metadata::{v14::RuntimeMetadataV14, v15::RuntimeMetadataV15};
#![deny(missing_docs)]
pub use retain::retain_metadata;
pub use validation::{
get_call_hash, get_constant_hash, get_pallet_hash, get_runtime_api_hash, get_storage_hash,
MetadataHasher, NotFound,
};
mod from_into;
mod utils;
/// Convert the metadata V14 to the latest metadata version.
pub fn metadata_v14_to_latest(metadata: RuntimeMetadataV14) -> RuntimeMetadataV15 {
RuntimeMetadataV15 {
types: metadata.types,
pallets: metadata
.pallets
.into_iter()
.map(|pallet| frame_metadata::v15::PalletMetadata {
name: pallet.name,
storage: pallet
.storage
.map(|storage| frame_metadata::v15::PalletStorageMetadata {
prefix: storage.prefix,
entries: storage
.entries
.into_iter()
.map(|entry| {
let modifier = match entry.modifier {
frame_metadata::v14::StorageEntryModifier::Optional => {
frame_metadata::v15::StorageEntryModifier::Optional
}
frame_metadata::v14::StorageEntryModifier::Default => {
frame_metadata::v15::StorageEntryModifier::Default
}
};
use scale_info::{form::PortableForm, PortableRegistry, Variant};
use std::collections::HashMap;
use std::sync::Arc;
use utils::ordered_map::OrderedMap;
use utils::variant_index::VariantIndex;
let ty = match entry.ty {
frame_metadata::v14::StorageEntryType::Plain(ty) => {
frame_metadata::v15::StorageEntryType::Plain(ty)
},
frame_metadata::v14::StorageEntryType::Map {
hashers,
key,
value,
} => frame_metadata::v15::StorageEntryType::Map {
hashers: hashers.into_iter().map(|hasher| match hasher {
frame_metadata::v14::StorageHasher::Blake2_128 => frame_metadata::v15::StorageHasher::Blake2_128,
frame_metadata::v14::StorageHasher::Blake2_256 => frame_metadata::v15::StorageHasher::Blake2_256,
frame_metadata::v14::StorageHasher::Blake2_128Concat => frame_metadata::v15::StorageHasher::Blake2_128Concat ,
frame_metadata::v14::StorageHasher::Twox128 => frame_metadata::v15::StorageHasher::Twox128,
frame_metadata::v14::StorageHasher::Twox256 => frame_metadata::v15::StorageHasher::Twox256,
frame_metadata::v14::StorageHasher::Twox64Concat => frame_metadata::v15::StorageHasher::Twox64Concat,
frame_metadata::v14::StorageHasher::Identity=> frame_metadata::v15::StorageHasher::Identity,
}).collect(),
key,
value,
},
};
type ArcStr = Arc<str>;
frame_metadata::v15::StorageEntryMetadata {
name: entry.name,
modifier,
ty,
default: entry.default,
docs: entry.docs,
}
})
.collect(),
}),
calls: pallet.calls.map(|calls| frame_metadata::v15::PalletCallMetadata { ty: calls.ty } ),
event: pallet.event.map(|event| frame_metadata::v15::PalletEventMetadata { ty: event.ty } ),
constants: pallet.constants.into_iter().map(|constant| frame_metadata::v15::PalletConstantMetadata {
name: constant.name,
ty: constant.ty,
value: constant.value,
docs: constant.docs,
} ).collect(),
error: pallet.error.map(|error| frame_metadata::v15::PalletErrorMetadata { ty: error.ty } ),
index: pallet.index,
docs: Default::default(),
})
.collect(),
extrinsic: frame_metadata::v15::ExtrinsicMetadata {
ty: metadata.extrinsic.ty,
version: metadata.extrinsic.version,
signed_extensions: metadata.extrinsic.signed_extensions.into_iter().map(|ext| {
frame_metadata::v15::SignedExtensionMetadata {
identifier: ext.identifier,
ty: ext.ty,
additional_signed: ext.additional_signed,
}
}).collect()
},
ty: metadata.ty,
apis: Default::default(),
pub use from_into::TryFromError;
pub use utils::validation::MetadataHasher;
/// Node metadata. This can be constructed by providing some compatible [`frame_metadata`]
/// which is then decoded into this. We aim to preserve all of the existing information in
/// the incoming metadata while optimizing the format a little for Subxt's use cases.
#[derive(Debug, Clone)]
pub struct Metadata {
/// Type registry containing all types used in the metadata.
types: PortableRegistry,
/// Metadata of all the pallets.
pallets: OrderedMap<ArcStr, PalletMetadataInner>,
/// Find the location in the pallet Vec by pallet index.
pallets_by_index: HashMap<u8, usize>,
/// Metadata of the extrinsic.
extrinsic: ExtrinsicMetadata,
/// The type ID of the `Runtime` type.
runtime_ty: u32,
/// The type Id of the `DispatchError` type, which Subxt makes use of.
dispatch_error_ty: Option<u32>,
/// Details about each of the runtime API traits.
apis: OrderedMap<ArcStr, RuntimeApiMetadataInner>,
}
impl Metadata {
/// Access the underlying type registry.
pub fn types(&self) -> &PortableRegistry {
&self.types
}
/// Mutable access to the underlying type registry.
pub fn types_mut(&mut self) -> &mut PortableRegistry {
&mut self.types
}
/// The type ID of the `Runtime` type.
pub fn runtime_ty(&self) -> u32 {
self.runtime_ty
}
/// The type ID of the `DispatchError` type, if it exists.
pub fn dispatch_error_ty(&self) -> Option<u32> {
self.dispatch_error_ty
}
/// Return details about the extrinsic format.
pub fn extrinsic(&self) -> &ExtrinsicMetadata {
&self.extrinsic
}
/// An iterator over all of the available pallets.
pub fn pallets(&self) -> impl ExactSizeIterator<Item = PalletMetadata<'_>> {
self.pallets.values().iter().map(|inner| PalletMetadata {
inner,
types: self.types(),
})
}
/// Access a pallet given its encoded variant index.
pub fn pallet_by_index(&self, variant_index: u8) -> Option<PalletMetadata<'_>> {
let inner = self
.pallets_by_index
.get(&variant_index)
.and_then(|i| self.pallets.get_by_index(*i))?;
Some(PalletMetadata {
inner,
types: self.types(),
})
}
/// Access a pallet given its name.
pub fn pallet_by_name(&self, pallet_name: &str) -> Option<PalletMetadata<'_>> {
let inner = self.pallets.get_by_key(pallet_name)?;
Some(PalletMetadata {
inner,
types: self.types(),
})
}
/// An iterator over all of the runtime APIs.
pub fn runtime_api_traits(&self) -> impl ExactSizeIterator<Item = RuntimeApiMetadata<'_>> {
self.apis.values().iter().map(|inner| RuntimeApiMetadata {
inner,
types: self.types(),
})
}
/// Access a runtime API trait given its name.
pub fn runtime_api_trait_by_name(&'_ self, name: &str) -> Option<RuntimeApiMetadata<'_>> {
let inner = self.apis.get_by_key(name)?;
Some(RuntimeApiMetadata {
inner,
types: self.types(),
})
}
/// Obtain a unique hash representing this metadata or specific parts of it.
pub fn hasher(&self) -> MetadataHasher {
MetadataHasher::new(self)
}
/// Filter out any pallets that we don't want to keep, retaining only those that we do.
pub fn retain<F, G>(&mut self, pallet_filter: F, api_filter: G)
where
F: FnMut(&str) -> bool,
G: FnMut(&str) -> bool,
{
utils::retain::retain_metadata(self, pallet_filter, api_filter);
}
}
/// Metadata for a specific pallet.
#[derive(Debug, Clone, Copy)]
pub struct PalletMetadata<'a> {
inner: &'a PalletMetadataInner,
types: &'a PortableRegistry,
}
impl<'a> PalletMetadata<'a> {
/// The pallet name.
pub fn name(&self) -> &'a str {
&self.inner.name
}
/// The pallet index.
pub fn index(&self) -> u8 {
self.inner.index
}
/// The pallet docs.
pub fn docs(&self) -> &'a [String] {
&self.inner.docs
}
/// Type ID for the pallet's Call type, if it exists.
pub fn call_ty_id(&self) -> Option<u32> {
self.inner.call_ty
}
/// Type ID for the pallet's Event type, if it exists.
pub fn event_ty_id(&self) -> Option<u32> {
self.inner.event_ty
}
/// Type ID for the pallet's Error type, if it exists.
pub fn error_ty_id(&self) -> Option<u32> {
self.inner.error_ty
}
/// Return metadata about the pallet's storage entries.
pub fn storage(&self) -> Option<&'a StorageMetadata> {
self.inner.storage.as_ref()
}
/// Return all of the event variants, if an event type exists.
pub fn event_variants(&self) -> Option<&'a [Variant<PortableForm>]> {
VariantIndex::get(self.inner.event_ty, self.types)
}
/// Return an event variant given it's encoded variant index.
pub fn event_variant_by_index(&self, variant_index: u8) -> Option<&'a Variant<PortableForm>> {
self.inner.event_variant_index.lookup_by_index(
variant_index,
self.inner.event_ty,
self.types,
)
}
/// Return all of the call variants, if a call type exists.
pub fn call_variants(&self) -> Option<&'a [Variant<PortableForm>]> {
VariantIndex::get(self.inner.call_ty, self.types)
}
/// Return a call variant given it's encoded variant index.
pub fn call_variant_by_index(&self, variant_index: u8) -> Option<&'a Variant<PortableForm>> {
self.inner
.call_variant_index
.lookup_by_index(variant_index, self.inner.call_ty, self.types)
}
/// Return a call variant given it's name.
pub fn call_variant_by_name(&self, call_name: &str) -> Option<&'a Variant<PortableForm>> {
self.inner
.call_variant_index
.lookup_by_name(call_name, self.inner.call_ty, self.types)
}
/// Return all of the error variants, if an error type exists.
pub fn error_variants(&self) -> Option<&'a [Variant<PortableForm>]> {
VariantIndex::get(self.inner.error_ty, self.types)
}
/// Return an error variant given it's encoded variant index.
pub fn error_variant_by_index(&self, variant_index: u8) -> Option<&'a Variant<PortableForm>> {
self.inner.error_variant_index.lookup_by_index(
variant_index,
self.inner.error_ty,
self.types,
)
}
/// Return constant details given the constant name.
pub fn constant_by_name(&self, name: &str) -> Option<&'a ConstantMetadata> {
self.inner.constants.get_by_key(name)
}
/// An iterator over the constants in this pallet.
pub fn constants(&self) -> impl ExactSizeIterator<Item = &'a ConstantMetadata> {
self.inner.constants.values().iter()
}
/// Return a hash for the storage entry, or None if it was not found.
pub fn storage_hash(&self, entry_name: &str) -> Option<[u8; 32]> {
crate::utils::validation::get_storage_hash(self, entry_name)
}
/// Return a hash for the constant, or None if it was not found.
pub fn constant_hash(&self, constant_name: &str) -> Option<[u8; 32]> {
crate::utils::validation::get_constant_hash(self, constant_name)
}
/// Return a hash for the call, or None if it was not found.
pub fn call_hash(&self, call_name: &str) -> Option<[u8; 32]> {
crate::utils::validation::get_call_hash(self, call_name)
}
/// Return a hash for the entire pallet.
pub fn hash(&self) -> [u8; 32] {
crate::utils::validation::get_pallet_hash(*self)
}
}
#[derive(Debug, Clone)]
struct PalletMetadataInner {
/// Pallet name.
name: ArcStr,
/// Pallet index.
index: u8,
/// Pallet storage metadata.
storage: Option<StorageMetadata>,
/// Type ID for the pallet Call enum.
call_ty: Option<u32>,
/// Call variants by name/u8.
call_variant_index: VariantIndex,
/// Type ID for the pallet Event enum.
event_ty: Option<u32>,
/// Event variants by name/u8.
event_variant_index: VariantIndex,
/// Type ID for the pallet Error enum.
error_ty: Option<u32>,
/// Error variants by name/u8.
error_variant_index: VariantIndex,
/// Map from constant name to constant details.
constants: OrderedMap<ArcStr, ConstantMetadata>,
/// Pallet documentation.
docs: Vec<String>,
}
/// Metadata for the storage entries in a pallet.
#[derive(Debug, Clone)]
pub struct StorageMetadata {
/// The common prefix used by all storage entries.
prefix: String,
/// Map from storage entry name to details.
entries: OrderedMap<ArcStr, StorageEntryMetadata>,
}
impl StorageMetadata {
/// The common prefix used by all storage entries.
pub fn prefix(&self) -> &str {
&self.prefix
}
/// An iterator over the storage entries.
pub fn entries(&self) -> impl ExactSizeIterator<Item = &StorageEntryMetadata> {
self.entries.values().iter()
}
/// Return a specific storage entry given its name.
pub fn entry_by_name(&self, name: &str) -> Option<&StorageEntryMetadata> {
self.entries.get_by_key(name)
}
}
/// Metadata for a single storage entry.
#[derive(Debug, Clone)]
pub struct StorageEntryMetadata {
/// Variable name of the storage entry.
name: ArcStr,
/// An `Option` modifier of that storage entry.
modifier: StorageEntryModifier,
/// Type of the value stored in the entry.
entry_type: StorageEntryType,
/// Default value (SCALE encoded).
default: Vec<u8>,
/// Storage entry documentation.
docs: Vec<String>,
}
impl StorageEntryMetadata {
/// Name of this entry.
pub fn name(&self) -> &str {
&self.name
}
/// Is the entry value optional or does it have a default value.
pub fn modifier(&self) -> StorageEntryModifier {
self.modifier
}
/// Type of the storage entry.
pub fn entry_type(&self) -> &StorageEntryType {
&self.entry_type
}
/// The SCALE encoded default value for this entry.
pub fn default_bytes(&self) -> &[u8] {
&self.default
}
/// Storage entry documentation.
pub fn docs(&self) -> &[String] {
&self.docs
}
}
/// The type of a storage entry.
#[derive(Debug, Clone)]
pub enum StorageEntryType {
/// Plain storage entry (just the value).
Plain(u32),
/// A storage map.
Map {
/// One or more hashers, should be one hasher per key element.
hashers: Vec<StorageHasher>,
/// The type of the key, can be a tuple with elements for each of the hashers.
key_ty: u32,
/// The type of the value.
value_ty: u32,
},
}
/// Hasher used by storage maps.
#[derive(Debug, Clone, Copy)]
pub enum StorageHasher {
/// 128-bit Blake2 hash.
Blake2_128,
/// 256-bit Blake2 hash.
Blake2_256,
/// Multiple 128-bit Blake2 hashes concatenated.
Blake2_128Concat,
/// 128-bit XX hash.
Twox128,
/// 256-bit XX hash.
Twox256,
/// Multiple 64-bit XX hashes concatenated.
Twox64Concat,
/// Identity hashing (no hashing).
Identity,
}
/// Is the storage entry optional, or does it have a default value.
#[derive(Debug, Clone, Copy)]
pub enum StorageEntryModifier {
/// The storage entry returns an `Option<T>`, with `None` if the key is not present.
Optional,
/// The storage entry returns `T::Default` if the key is not present.
Default,
}
/// Metadata for a single constant.
#[derive(Debug, Clone)]
pub struct ConstantMetadata {
/// Name of the pallet constant.
name: ArcStr,
/// Type of the pallet constant.
ty: u32,
/// Value stored in the constant (SCALE encoded).
value: Vec<u8>,
/// Constant documentation.
docs: Vec<String>,
}
impl ConstantMetadata {
/// Name of the pallet constant.
pub fn name(&self) -> &str {
&self.name
}
/// Type of the pallet constant.
pub fn ty(&self) -> u32 {
self.ty
}
/// Value stored in the constant (SCALE encoded).
pub fn value(&self) -> &[u8] {
&self.value
}
/// Constant documentation.
pub fn docs(&self) -> &[String] {
&self.docs
}
}
/// Metadata for the extrinsic type.
#[derive(Debug, Clone)]
pub struct ExtrinsicMetadata {
/// The type of the extrinsic.
ty: u32,
/// Extrinsic version.
version: u8,
/// The signed extensions in the order they appear in the extrinsic.
signed_extensions: Vec<SignedExtensionMetadata>,
}
impl ExtrinsicMetadata {
/// Type of the extrinsic.
pub fn ty(&self) -> u32 {
self.ty
}
/// Extrinsic version.
pub fn version(&self) -> u8 {
self.version
}
/// The extra/additional information associated with the extrinsic.
pub fn signed_extensions(&self) -> &[SignedExtensionMetadata] {
&self.signed_extensions
}
}
/// Metadata for the signed extensions used by extrinsics.
#[derive(Debug, Clone)]
pub struct SignedExtensionMetadata {
/// The unique signed extension identifier, which may be different from the type name.
identifier: String,
/// The type of the signed extension, with the data to be included in the extrinsic.
extra_ty: u32,
/// The type of the additional signed data, with the data to be included in the signed payload
additional_ty: u32,
}
impl SignedExtensionMetadata {
/// The unique signed extension identifier, which may be different from the type name.
pub fn identifier(&self) -> &str {
&self.identifier
}
/// The type of the signed extension, with the data to be included in the extrinsic.
pub fn extra_ty(&self) -> u32 {
self.extra_ty
}
/// The type of the additional signed data, with the data to be included in the signed payload
pub fn additional_ty(&self) -> u32 {
self.additional_ty
}
}
/// Metadata for the available runtime APIs.
#[derive(Debug, Clone, Copy)]
pub struct RuntimeApiMetadata<'a> {
inner: &'a RuntimeApiMetadataInner,
types: &'a PortableRegistry,
}
impl<'a> RuntimeApiMetadata<'a> {
/// Trait name.
pub fn name(&self) -> &str {
&self.inner.name
}
/// Trait documentation.
pub fn docs(&self) -> &[String] {
&self.inner.docs
}
/// An iterator over the trait methods.
pub fn methods(&self) -> impl ExactSizeIterator<Item = &'a RuntimeApiMethodMetadata> {
self.inner.methods.values().iter()
}
/// Get a specific trait method given its name.
pub fn method_by_name(&self, name: &str) -> Option<&'a RuntimeApiMethodMetadata> {
self.inner.methods.get_by_key(name)
}
/// Return a hash for the constant, or None if it was not found.
pub fn method_hash(&self, method_name: &str) -> Option<[u8; 32]> {
crate::utils::validation::get_runtime_api_hash(self, method_name)
}
}
#[derive(Debug, Clone)]
struct RuntimeApiMetadataInner {
/// Trait name.
name: ArcStr,
/// Trait methods.
methods: OrderedMap<ArcStr, RuntimeApiMethodMetadata>,
/// Trait documentation.
docs: Vec<String>,
}
/// Metadata for a single runtime API method.
#[derive(Debug, Clone)]
pub struct RuntimeApiMethodMetadata {
/// Method name.
name: ArcStr,
/// Method parameters.
inputs: Vec<RuntimeApiMethodParamMetadata>,
/// Method output type.
output_ty: u32,
/// Method documentation.
docs: Vec<String>,
}
impl RuntimeApiMethodMetadata {
/// Method name.
pub fn name(&self) -> &str {
&self.name
}
/// Method documentation.
pub fn docs(&self) -> &[String] {
&self.docs
}
/// Method inputs.
pub fn inputs(&self) -> impl ExactSizeIterator<Item = &RuntimeApiMethodParamMetadata> {
self.inputs.iter()
}
/// Method return type.
pub fn output_ty(&self) -> u32 {
self.output_ty
}
}
/// Metadata for a single input parameter to a runtime API method.
#[derive(Debug, Clone)]
pub struct RuntimeApiMethodParamMetadata {
/// Parameter name.
pub name: String,
/// Parameter type.
pub ty: u32,
}
// Support decoding metadata from the "wire" format directly into this.
// Errors may be lost in the case that the metadata content is somehow invalid.
impl codec::Decode for Metadata {
fn decode<I: codec::Input>(input: &mut I) -> Result<Self, codec::Error> {
let metadata = frame_metadata::RuntimeMetadataPrefixed::decode(input)?;
let metadata = match metadata.1 {
frame_metadata::RuntimeMetadata::V14(md) => md.try_into(),
frame_metadata::RuntimeMetadata::V15(md) => md.try_into(),
_ => return Err("Cannot try_into() to Metadata: unsupported metadata version".into()),
};
metadata.map_err(|_e| "Cannot try_into() to Metadata.".into())
}
}
// Metadata can be encoded, too. It will encode into a format that's compatible with what
// Subxt requires, and that it can be decoded back from. The actual specifics of the format
// can change over time.
impl codec::Encode for Metadata {
fn encode_to<T: codec::Output + ?Sized>(&self, dest: &mut T) {
let m: frame_metadata::v15::RuntimeMetadataV15 = self.clone().into();
let m: frame_metadata::RuntimeMetadataPrefixed = m.into();
m.encode_to(dest)
}
}
#[cfg(test)]
mod test {
use super::*;
use codec::{Decode, Encode};
fn load_metadata() -> Vec<u8> {
std::fs::read("../artifacts/polkadot_metadata_full.scale").unwrap()
}
// We don't expect to lose any information converting back and forth between
// our own representation and the latest version emitted from a node that we can
// work with.
#[test]
fn is_isomorphic_to_v15() {
let bytes = load_metadata();
// Decode into our metadata struct:
let metadata = Metadata::decode(&mut &*bytes).unwrap();
// Convert into v15 metadata:
let v15: frame_metadata::v15::RuntimeMetadataV15 = metadata.into();
let prefixed = frame_metadata::RuntimeMetadataPrefixed::from(v15);
// Re-encode that:
let new_bytes = prefixed.encode();
// The bytes should be identical:
assert_eq!(bytes, new_bytes);
}
}
+8
View File
@@ -0,0 +1,8 @@
// Copyright 2019-2023 Parity Technologies (UK) Ltd.
// This file is dual-licensed as Apache-2.0 or GPL-3.0.
// see LICENSE for license details.
pub mod ordered_map;
pub mod retain;
pub mod validation;
pub mod variant_index;
+137
View File
@@ -0,0 +1,137 @@
// Copyright 2019-2023 Parity Technologies (UK) Ltd.
// This file is dual-licensed as Apache-2.0 or GPL-3.0.
// see LICENSE for license details.
use std::collections::HashMap;
/// A minimal ordered map to let one search for
/// things by key or get the values in insert order.
#[derive(Debug, Clone)]
pub struct OrderedMap<K, V> {
values: Vec<V>,
map: HashMap<K, usize>,
}
impl<K, V> Default for OrderedMap<K, V> {
fn default() -> Self {
Self {
values: Default::default(),
map: Default::default(),
}
}
}
impl<K, V> OrderedMap<K, V>
where
K: PartialEq + Eq + std::hash::Hash,
{
/// Create a new, empty [`OrderedMap`].
pub fn new() -> Self {
Self::default()
}
/// Number of entries in the map.
#[allow(dead_code)]
pub fn len(&self) -> usize {
self.values.len()
}
/// Is the map empty.
#[allow(dead_code)]
pub fn is_empty(&self) -> bool {
self.values.is_empty()
}
/// Retain specific entries.
pub fn retain<F>(&mut self, mut f: F)
where
F: FnMut(&V) -> bool,
{
let values = std::mem::take(&mut self.values);
let map = std::mem::take(&mut self.map);
// Filter the values, storing a map from old to new positions:
let mut new_values = Vec::new();
let mut old_pos_to_new_pos = HashMap::new();
for (pos, value) in values.into_iter().enumerate().filter(|(_, v)| f(v)) {
old_pos_to_new_pos.insert(pos, new_values.len());
new_values.push(value);
}
// Update the values now we've filtered them:
self.values = new_values;
// Rebuild the map using the new positions:
self.map = map
.into_iter()
.filter_map(|(k, v)| old_pos_to_new_pos.get(&v).map(|v2| (k, *v2)))
.collect();
}
/// Push/insert an item to the end of the map.
pub fn push_insert(&mut self, key: K, value: V) {
let idx = self.values.len();
self.values.push(value);
self.map.insert(key, idx);
}
/// Get an item by its key.
pub fn get_by_key<Q>(&self, key: &Q) -> Option<&V>
where
K: std::borrow::Borrow<Q>,
Q: std::hash::Hash + Eq + ?Sized,
{
self.map.get(key).and_then(|&v| self.values.get(v))
}
/// Get an item by its index.
pub fn get_by_index(&self, i: usize) -> Option<&V> {
self.values.get(i)
}
/// Access the underlying values.
pub fn values(&self) -> &[V] {
&self.values
}
/// Mutable access to the underlying values.
pub fn values_mut(&mut self) -> &mut [V] {
&mut self.values
}
/// Return the underlying values.
pub fn into_values(self) -> Vec<V> {
self.values
}
}
impl<K, V> FromIterator<(K, V)> for OrderedMap<K, V>
where
K: PartialEq + Eq + std::hash::Hash,
{
fn from_iter<T: IntoIterator<Item = (K, V)>>(iter: T) -> Self {
let mut map = OrderedMap::new();
for (k, v) in iter {
map.push_insert(k, v)
}
map
}
}
#[cfg(test)]
mod test {
use super::*;
#[test]
fn retain() {
let mut m = OrderedMap::from_iter([(1, 'a'), (2, 'b'), (3, 'c')]);
m.retain(|v| *v != 'b');
assert_eq!(m.get_by_key(&1), Some(&'a'));
assert_eq!(m.get_by_key(&2), None);
assert_eq!(m.get_by_key(&3), Some(&'c'));
assert_eq!(m.values(), &['a', 'c'])
}
}
@@ -4,128 +4,120 @@
//! Utility functions to generate a subset of the metadata.
use frame_metadata::v15::{
ExtrinsicMetadata, PalletMetadata, RuntimeApiMetadata, RuntimeMetadataV15, StorageEntryType,
};
use scale_info::{form::PortableForm, interner::UntrackedSymbol, TypeDef};
use std::{
any::TypeId,
collections::{BTreeMap, HashSet},
use crate::{
ExtrinsicMetadata, Metadata, PalletMetadataInner, RuntimeApiMetadataInner, StorageEntryType,
};
use scale_info::TypeDef;
use std::collections::{BTreeMap, HashSet};
/// Collect all type IDs needed to represent the provided pallet.
fn collect_pallet_types(pallet: &PalletMetadata<PortableForm>, type_ids: &mut HashSet<u32>) {
fn collect_pallet_types(pallet: &PalletMetadataInner, type_ids: &mut HashSet<u32>) {
if let Some(storage) = &pallet.storage {
for entry in &storage.entries {
match entry.ty {
for entry in storage.entries() {
match entry.entry_type {
StorageEntryType::Plain(ty) => {
type_ids.insert(ty.id);
type_ids.insert(ty);
}
StorageEntryType::Map { key, value, .. } => {
type_ids.insert(key.id);
type_ids.insert(value.id);
StorageEntryType::Map {
key_ty, value_ty, ..
} => {
type_ids.insert(key_ty);
type_ids.insert(value_ty);
}
}
}
}
if let Some(calls) = &pallet.calls {
type_ids.insert(calls.ty.id);
if let Some(ty) = pallet.call_ty {
type_ids.insert(ty);
}
if let Some(event) = &pallet.event {
type_ids.insert(event.ty.id);
if let Some(ty) = pallet.event_ty {
type_ids.insert(ty);
}
for constant in &pallet.constants {
type_ids.insert(constant.ty.id);
for constant in pallet.constants.values() {
type_ids.insert(constant.ty);
}
if let Some(error) = &pallet.error {
type_ids.insert(error.ty.id);
if let Some(ty) = pallet.error_ty {
type_ids.insert(ty);
}
}
/// Update all type IDs of the provided pallet using the new type IDs from the portable registry.
fn update_pallet_types(pallet: &mut PalletMetadata<PortableForm>, map_ids: &BTreeMap<u32, u32>) {
fn update_pallet_types(pallet: &mut PalletMetadataInner, map_ids: &BTreeMap<u32, u32>) {
if let Some(storage) = &mut pallet.storage {
for entry in &mut storage.entries {
match &mut entry.ty {
for entry in storage.entries.values_mut() {
match &mut entry.entry_type {
StorageEntryType::Plain(ty) => {
update_type(ty, map_ids);
}
StorageEntryType::Map { key, value, .. } => {
update_type(key, map_ids);
update_type(value, map_ids);
StorageEntryType::Map {
key_ty, value_ty, ..
} => {
update_type(key_ty, map_ids);
update_type(value_ty, map_ids);
}
}
}
}
if let Some(calls) = &mut pallet.calls {
update_type(&mut calls.ty, map_ids);
if let Some(ty) = &mut pallet.call_ty {
update_type(ty, map_ids);
}
if let Some(event) = &mut pallet.event {
update_type(&mut event.ty, map_ids);
if let Some(ty) = &mut pallet.event_ty {
update_type(ty, map_ids);
}
for constant in &mut pallet.constants {
if let Some(ty) = &mut pallet.error_ty {
update_type(ty, map_ids);
}
for constant in pallet.constants.values_mut() {
update_type(&mut constant.ty, map_ids);
}
if let Some(error) = &mut pallet.error {
update_type(&mut error.ty, map_ids);
}
}
/// Collect all type IDs needed to represent the extrinsic metadata.
fn collect_extrinsic_types(
extrinsic: &ExtrinsicMetadata<PortableForm>,
type_ids: &mut HashSet<u32>,
) {
type_ids.insert(extrinsic.ty.id);
fn collect_extrinsic_types(extrinsic: &ExtrinsicMetadata, type_ids: &mut HashSet<u32>) {
type_ids.insert(extrinsic.ty);
for signed in &extrinsic.signed_extensions {
type_ids.insert(signed.ty.id);
type_ids.insert(signed.additional_signed.id);
type_ids.insert(signed.extra_ty);
type_ids.insert(signed.additional_ty);
}
}
/// Update all type IDs of the provided extrinsic metadata using the new type IDs from the portable registry.
fn update_extrinsic_types(
extrinsic: &mut ExtrinsicMetadata<PortableForm>,
map_ids: &BTreeMap<u32, u32>,
) {
fn update_extrinsic_types(extrinsic: &mut ExtrinsicMetadata, map_ids: &BTreeMap<u32, u32>) {
update_type(&mut extrinsic.ty, map_ids);
for signed in &mut extrinsic.signed_extensions {
update_type(&mut signed.ty, map_ids);
update_type(&mut signed.additional_signed, map_ids);
update_type(&mut signed.extra_ty, map_ids);
update_type(&mut signed.additional_ty, map_ids);
}
}
/// Collect all type IDs needed to represent the runtime APIs.
fn collect_runtime_api_types(api: &RuntimeApiMetadata<PortableForm>, type_ids: &mut HashSet<u32>) {
for method in &api.methods {
fn collect_runtime_api_types(api: &RuntimeApiMetadataInner, type_ids: &mut HashSet<u32>) {
for method in api.methods.values() {
for input in &method.inputs {
type_ids.insert(input.ty.id);
type_ids.insert(input.ty);
}
type_ids.insert(method.output.id);
type_ids.insert(method.output_ty);
}
}
/// Update all type IDs of the provided runtime APIs metadata using the new type IDs from the portable registry.
fn update_runtime_api_types(
apis: &mut [RuntimeApiMetadata<PortableForm>],
map_ids: &BTreeMap<u32, u32>,
) {
fn update_runtime_api_types(apis: &mut [RuntimeApiMetadataInner], map_ids: &BTreeMap<u32, u32>) {
for api in apis {
for method in &mut api.methods {
for method in api.methods.values_mut() {
for input in &mut method.inputs {
update_type(&mut input.ty, map_ids);
}
update_type(&mut method.output, map_ids);
update_type(&mut method.output_ty, map_ids);
}
}
}
@@ -135,26 +127,26 @@ fn update_runtime_api_types(
/// # Panics
///
/// Panics if the [`scale_info::PortableRegistry`] did not retain all needed types.
fn update_type(ty: &mut UntrackedSymbol<TypeId>, map_ids: &BTreeMap<u32, u32>) {
let old_id = ty.id;
fn update_type(ty: &mut u32, map_ids: &BTreeMap<u32, u32>) {
let old_id = *ty;
let new_id = map_ids
.get(&old_id)
.copied()
.unwrap_or_else(|| panic!("PortableRegistry did not retain type id {old_id}. This is a bug. Please open an issue."));
*ty = new_id.into();
*ty = new_id;
}
/// Strip any pallets out of the RuntimeCall type that aren't the ones we want to keep.
/// The RuntimeCall type is referenced in a bunch of places, so doing this prevents us from
/// holding on to stuff in pallets we've asked not to keep.
fn retain_pallets_in_runtime_call_type<F>(metadata: &mut RuntimeMetadataV15, mut filter: F)
fn retain_pallets_in_runtime_call_type<F>(metadata: &mut Metadata, mut filter: F)
where
F: FnMut(&str) -> bool,
{
let extrinsic_ty = metadata
.types
.types
.get_mut(metadata.extrinsic.ty.id as usize)
.get_mut(metadata.extrinsic.ty as usize)
.expect("Metadata should contain extrinsic type in registry");
let Some(call_ty) = extrinsic_ty.ty.type_params
@@ -189,7 +181,7 @@ where
/// Panics if the [`scale_info::PortableRegistry`] did not retain all needed types,
/// or the metadata does not contain the "sp_runtime::DispatchError" type.
pub fn retain_metadata<F, G>(
metadata: &mut RuntimeMetadataV15,
metadata: &mut Metadata,
mut pallets_filter: F,
mut runtime_apis_filter: G,
) where
@@ -213,11 +205,20 @@ pub fn retain_metadata<F, G>(
should_retain
});
// We index pallets by their u8 index for easy access. Rebuild this index.
metadata.pallets_by_index = metadata
.pallets
.values()
.iter()
.enumerate()
.map(|(pos, p)| (p.index, pos))
.collect();
// Keep the extrinsic stuff referenced in our metadata.
collect_extrinsic_types(&metadata.extrinsic, &mut type_ids);
// Keep the "runtime" type ID, since it's referenced in our metadata.
type_ids.insert(metadata.ty.id);
type_ids.insert(metadata.runtime_ty);
// Keep only the runtime API types that the filter allows for. Keep hold of all
// type IDs in the runtime apis we're keeping. Retain all, if no filter specified.
@@ -244,31 +245,31 @@ pub fn retain_metadata<F, G>(
let map_ids = metadata.types.retain(|id| type_ids.contains(&id));
// And finally, we can go and update all of our type IDs in the metadata as a result of this:
for pallets in &mut metadata.pallets {
for pallets in metadata.pallets.values_mut() {
update_pallet_types(pallets, &map_ids);
}
update_extrinsic_types(&mut metadata.extrinsic, &map_ids);
update_type(&mut metadata.ty, &map_ids);
update_runtime_api_types(&mut metadata.apis, &map_ids);
update_type(&mut metadata.runtime_ty, &map_ids);
update_runtime_api_types(metadata.apis.values_mut(), &map_ids);
}
#[cfg(test)]
mod tests {
use super::*;
use crate::metadata_v14_to_latest;
use crate::Metadata;
use codec::Decode;
use frame_metadata::{v15::RuntimeMetadataV15, RuntimeMetadata, RuntimeMetadataPrefixed};
use frame_metadata::{RuntimeMetadata, RuntimeMetadataPrefixed};
use std::{fs, path::Path};
fn load_metadata() -> RuntimeMetadataV15 {
fn load_metadata() -> Metadata {
let bytes = fs::read(Path::new("../artifacts/polkadot_metadata_full.scale"))
.expect("Cannot read metadata blob");
let meta: RuntimeMetadataPrefixed =
Decode::decode(&mut &*bytes).expect("Cannot decode scale metadata");
match meta.1 {
RuntimeMetadata::V14(v14) => metadata_v14_to_latest(v14),
RuntimeMetadata::V15(v15) => v15,
RuntimeMetadata::V14(v14) => v14.try_into().unwrap(),
RuntimeMetadata::V15(v15) => v15.try_into().unwrap(),
_ => panic!("Unsupported metadata version {:?}", meta.1),
}
}
@@ -278,16 +279,19 @@ mod tests {
let metadata_cache = load_metadata();
// Retain one pallet at a time ensuring the test does not panic.
for pallet in &metadata_cache.pallets {
for pallet in metadata_cache.pallets() {
let mut metadata = metadata_cache.clone();
retain_metadata(
&mut metadata,
|pallet_name| pallet_name == pallet.name,
|pallet_name| pallet_name == pallet.name(),
|_| true,
);
assert_eq!(metadata.pallets.len(), 1);
assert_eq!(metadata.pallets.get(0).unwrap().name, pallet.name);
assert_eq!(
&*metadata.pallets.get_by_index(0).unwrap().name,
pallet.name()
);
}
}
@@ -296,16 +300,19 @@ mod tests {
let metadata_cache = load_metadata();
// Retain one runtime API at a time ensuring the test does not panic.
for runtime_api in &metadata_cache.apis {
for runtime_api in metadata_cache.runtime_api_traits() {
let mut metadata = metadata_cache.clone();
retain_metadata(
&mut metadata,
|_| true,
|runtime_api_name| runtime_api_name == runtime_api.name,
|runtime_api_name| runtime_api_name == runtime_api.name(),
);
assert_eq!(metadata.apis.len(), 1);
assert_eq!(metadata.apis.get(0).unwrap().name, runtime_api.name);
assert_eq!(
&*metadata.apis.get_by_index(0).unwrap().name,
runtime_api.name()
);
}
}
}
@@ -4,9 +4,9 @@
//! Utility functions for metadata validation.
use frame_metadata::v15::{
ExtrinsicMetadata, PalletMetadata, RuntimeApiMetadata, RuntimeApiMethodMetadata,
RuntimeMetadataV15, StorageEntryMetadata, StorageEntryType,
use crate::{
ExtrinsicMetadata, Metadata, PalletMetadata, RuntimeApiMetadata, RuntimeApiMethodMetadata,
StorageEntryMetadata, StorageEntryType,
};
use scale_info::{form::PortableForm, Field, PortableRegistry, TypeDef, Variant};
use std::collections::HashSet;
@@ -194,12 +194,12 @@ fn get_type_hash(
/// Obtain the hash representation of a `frame_metadata::v15::ExtrinsicMetadata`.
fn get_extrinsic_hash(
registry: &PortableRegistry,
extrinsic: &ExtrinsicMetadata<PortableForm>,
extrinsic: &ExtrinsicMetadata,
) -> [u8; HASH_LEN] {
let mut visited_ids = HashSet::<u32>::new();
let mut bytes = concat_and_hash2(
&get_type_hash(registry, extrinsic.ty.id, &mut visited_ids),
&get_type_hash(registry, extrinsic.ty, &mut visited_ids),
&[extrinsic.version; 32],
);
@@ -207,12 +207,8 @@ fn get_extrinsic_hash(
bytes = concat_and_hash4(
&bytes,
&hash(signed_extension.identifier.as_bytes()),
&get_type_hash(registry, signed_extension.ty.id, &mut visited_ids),
&get_type_hash(
registry,
signed_extension.additional_signed.id,
&mut visited_ids,
),
&get_type_hash(registry, signed_extension.extra_ty, &mut visited_ids),
&get_type_hash(registry, signed_extension.additional_ty, &mut visited_ids),
)
}
@@ -222,33 +218,33 @@ fn get_extrinsic_hash(
/// Get the hash corresponding to a single storage entry.
fn get_storage_entry_hash(
registry: &PortableRegistry,
entry: &StorageEntryMetadata<PortableForm>,
entry: &StorageEntryMetadata,
visited_ids: &mut HashSet<u32>,
) -> [u8; HASH_LEN] {
let mut bytes = concat_and_hash3(
&hash(entry.name.as_bytes()),
// Cloning 'entry.modifier' should essentially be a copy.
&[entry.modifier.clone() as u8; HASH_LEN],
&[entry.modifier as u8; HASH_LEN],
&hash(&entry.default),
);
match &entry.ty {
match &entry.entry_type {
StorageEntryType::Plain(ty) => {
concat_and_hash2(&bytes, &get_type_hash(registry, ty.id, visited_ids))
concat_and_hash2(&bytes, &get_type_hash(registry, *ty, visited_ids))
}
StorageEntryType::Map {
hashers,
key,
value,
key_ty,
value_ty,
} => {
for hasher in hashers {
// Cloning the hasher should essentially be a copy.
bytes = concat_and_hash2(&bytes, &[hasher.clone() as u8; HASH_LEN]);
bytes = concat_and_hash2(&bytes, &[*hasher as u8; HASH_LEN]);
}
concat_and_hash3(
&bytes,
&get_type_hash(registry, key.id, visited_ids),
&get_type_hash(registry, value.id, visited_ids),
&get_type_hash(registry, *key_ty, visited_ids),
&get_type_hash(registry, *value_ty, visited_ids),
)
}
}
@@ -256,9 +252,9 @@ fn get_storage_entry_hash(
/// Get the hash corresponding to a single runtime API method.
fn get_runtime_method_hash(
metadata: &RuntimeMetadataV15,
trait_metadata: &RuntimeApiMetadata<PortableForm>,
method_metadata: &RuntimeApiMethodMetadata<PortableForm>,
registry: &PortableRegistry,
trait_name: &str,
method_metadata: &RuntimeApiMethodMetadata,
visited_ids: &mut HashSet<u32>,
) -> [u8; HASH_LEN] {
// The trait name is part of the runtime API call that is being
@@ -266,7 +262,7 @@ fn get_runtime_method_hash(
// connected to the method in the same way as a parameter is
// to the method.
let mut bytes = concat_and_hash2(
&hash(trait_metadata.name.as_bytes()),
&hash(trait_name.as_bytes()),
&hash(method_metadata.name.as_bytes()),
);
@@ -274,198 +270,123 @@ fn get_runtime_method_hash(
bytes = concat_and_hash3(
&bytes,
&hash(input.name.as_bytes()),
&get_type_hash(&metadata.types, input.ty.id, visited_ids),
&get_type_hash(registry, input.ty, visited_ids),
);
}
bytes = concat_and_hash2(
&bytes,
&get_type_hash(&metadata.types, method_metadata.output.id, visited_ids),
&get_type_hash(registry, method_metadata.output_ty, visited_ids),
);
bytes
}
/// Obtain the hash of all of a runtime API trait, including all of its methods.
fn get_runtime_trait_hash(
metadata: &RuntimeMetadataV15,
trait_metadata: &RuntimeApiMetadata<PortableForm>,
) -> [u8; HASH_LEN] {
fn get_runtime_trait_hash(trait_metadata: RuntimeApiMetadata) -> [u8; HASH_LEN] {
let mut visited_ids = HashSet::new();
let method_name = hash(trait_metadata.name.as_bytes());
let trait_name = &*trait_metadata.inner.name;
let method_bytes = trait_metadata
.methods()
.fold([0u8; HASH_LEN], |bytes, method_metadata| {
// We don't care what order the trait methods exist in, and want the hash to
// be identical regardless. For this, we can just XOR the hashes for each method
// together; we'll get the same output whichever order they are XOR'd together in,
// so long as each individual method is the same.
xor(
bytes,
get_runtime_method_hash(
trait_metadata.types,
trait_name,
method_metadata,
&mut visited_ids,
),
)
});
let method_bytes =
trait_metadata
.methods
.iter()
.fold([0u8; HASH_LEN], |bytes, method_metadata| {
// We don't care what order the trait methods exist in, and want the hash to
// be identical regardless. For this, we can just XOR the hashes for each method
// together; we'll get the same output whichever order they are XOR'd together in,
// so long as each individual method is the same.
xor(
bytes,
get_runtime_method_hash(
metadata,
trait_metadata,
method_metadata,
&mut visited_ids,
),
)
});
concat_and_hash2(&method_name, &method_bytes)
concat_and_hash2(&hash(trait_name.as_bytes()), &method_bytes)
}
/// Obtain the hash for a specific storage item, or an error if it's not found.
pub fn get_storage_hash(
metadata: &RuntimeMetadataV15,
pallet_name: &str,
storage_name: &str,
) -> Result<[u8; HASH_LEN], NotFound> {
let pallet = metadata
.pallets
.iter()
.find(|p| p.name == pallet_name)
.ok_or(NotFound::Root)?;
pub fn get_storage_hash(pallet: &PalletMetadata, entry_name: &str) -> Option<[u8; HASH_LEN]> {
let storage = pallet.storage()?;
let entry = storage.entry_by_name(entry_name)?;
let storage = pallet.storage.as_ref().ok_or(NotFound::Item)?;
let entry = storage
.entries
.iter()
.find(|s| s.name == storage_name)
.ok_or(NotFound::Item)?;
let hash = get_storage_entry_hash(&metadata.types, entry, &mut HashSet::new());
Ok(hash)
let hash = get_storage_entry_hash(pallet.types, entry, &mut HashSet::new());
Some(hash)
}
/// Obtain the hash for a specific constant, or an error if it's not found.
pub fn get_constant_hash(
metadata: &RuntimeMetadataV15,
pallet_name: &str,
constant_name: &str,
) -> Result<[u8; HASH_LEN], NotFound> {
let pallet = metadata
.pallets
.iter()
.find(|p| p.name == pallet_name)
.ok_or(NotFound::Root)?;
let constant = pallet
.constants
.iter()
.find(|c| c.name == constant_name)
.ok_or(NotFound::Item)?;
pub fn get_constant_hash(pallet: &PalletMetadata, constant_name: &str) -> Option<[u8; HASH_LEN]> {
let constant = pallet.constant_by_name(constant_name)?;
// We only need to check that the type of the constant asked for matches.
let bytes = get_type_hash(&metadata.types, constant.ty.id, &mut HashSet::new());
Ok(bytes)
let bytes = get_type_hash(pallet.types, constant.ty, &mut HashSet::new());
Some(bytes)
}
/// Obtain the hash for a specific call, or an error if it's not found.
pub fn get_call_hash(
metadata: &RuntimeMetadataV15,
pallet_name: &str,
call_name: &str,
) -> Result<[u8; HASH_LEN], NotFound> {
let pallet = metadata
.pallets
.iter()
.find(|p| p.name == pallet_name)
.ok_or(NotFound::Root)?;
let call_id = pallet.calls.as_ref().ok_or(NotFound::Item)?.ty.id;
let call_ty = metadata.types.resolve(call_id).ok_or(NotFound::Item)?;
let call_variants = match &call_ty.type_def {
TypeDef::Variant(variant) => &variant.variants,
_ => return Err(NotFound::Item),
};
let variant = call_variants
.iter()
.find(|v| v.name == call_name)
.ok_or(NotFound::Item)?;
pub fn get_call_hash(pallet: &PalletMetadata, call_name: &str) -> Option<[u8; HASH_LEN]> {
let call_variant = pallet.call_variant_by_name(call_name)?;
// hash the specific variant representing the call we are interested in.
let hash = get_variant_hash(&metadata.types, variant, &mut HashSet::new());
Ok(hash)
let hash = get_variant_hash(pallet.types, call_variant, &mut HashSet::new());
Some(hash)
}
/// Obtain the hash of a specific runtime API function, or an error if it's not found.
pub fn get_runtime_api_hash(
metadata: &RuntimeMetadataV15,
trait_name: &str,
runtime_apis: &RuntimeApiMetadata,
method_name: &str,
) -> Result<[u8; HASH_LEN], NotFound> {
let trait_metadata = metadata
.apis
.iter()
.find(|m| m.name == trait_name)
.ok_or(NotFound::Root)?;
) -> Option<[u8; HASH_LEN]> {
let trait_name = &*runtime_apis.inner.name;
let method_metadata = runtime_apis.method_by_name(method_name)?;
let method_metadata = trait_metadata
.methods
.iter()
.find(|m| m.name == method_name)
.ok_or(NotFound::Item)?;
Ok(get_runtime_method_hash(
metadata,
trait_metadata,
Some(get_runtime_method_hash(
runtime_apis.types,
trait_name,
method_metadata,
&mut HashSet::new(),
))
}
/// Obtain the hash representation of a `frame_metadata::v15::PalletMetadata`.
pub fn get_pallet_hash(
registry: &PortableRegistry,
pallet: &PalletMetadata<PortableForm>,
) -> [u8; HASH_LEN] {
pub fn get_pallet_hash(pallet: PalletMetadata) -> [u8; HASH_LEN] {
let mut visited_ids = HashSet::<u32>::new();
let registry = pallet.types;
let call_bytes = match &pallet.calls {
Some(calls) => get_type_hash(registry, calls.ty.id, &mut visited_ids),
let call_bytes = match pallet.call_ty_id() {
Some(calls) => get_type_hash(registry, calls, &mut visited_ids),
None => [0u8; HASH_LEN],
};
let event_bytes = match &pallet.event {
Some(event) => get_type_hash(registry, event.ty.id, &mut visited_ids),
let event_bytes = match pallet.event_ty_id() {
Some(event) => get_type_hash(registry, event, &mut visited_ids),
None => [0u8; HASH_LEN],
};
let error_bytes = match &pallet.error {
Some(error) => get_type_hash(registry, error.ty.id, &mut visited_ids),
let error_bytes = match pallet.error_ty_id() {
Some(error) => get_type_hash(registry, error, &mut visited_ids),
None => [0u8; HASH_LEN],
};
let constant_bytes = pallet
.constants
.iter()
.fold([0u8; HASH_LEN], |bytes, constant| {
// We don't care what order the constants occur in, so XOR together the combinations
// of (constantName, constantType) to make the order we see them irrelevant.
let constant_hash = concat_and_hash2(
&hash(constant.name.as_bytes()),
&get_type_hash(registry, constant.ty.id, &mut visited_ids),
);
xor(bytes, constant_hash)
});
let storage_bytes = match &pallet.storage {
let constant_bytes = pallet.constants().fold([0u8; HASH_LEN], |bytes, constant| {
// We don't care what order the constants occur in, so XOR together the combinations
// of (constantName, constantType) to make the order we see them irrelevant.
let constant_hash = concat_and_hash2(
&hash(constant.name.as_bytes()),
&get_type_hash(registry, constant.ty(), &mut visited_ids),
);
xor(bytes, constant_hash)
});
let storage_bytes = match pallet.storage() {
Some(storage) => {
let prefix_hash = hash(storage.prefix.as_bytes());
let entries_hash = storage
.entries
.iter()
.fold([0u8; HASH_LEN], |bytes, entry| {
// We don't care what order the storage entries occur in, so XOR them together
// to make the order irrelevant.
xor(
bytes,
get_storage_entry_hash(registry, entry, &mut visited_ids),
)
});
let prefix_hash = hash(storage.prefix().as_bytes());
let entries_hash = storage.entries().fold([0u8; HASH_LEN], |bytes, entry| {
// We don't care what order the storage entries occur in, so XOR them together
// to make the order irrelevant.
xor(
bytes,
get_storage_entry_hash(registry, entry, &mut visited_ids),
)
});
concat_and_hash2(&prefix_hash, &entries_hash)
}
None => [0u8; HASH_LEN],
@@ -481,21 +402,18 @@ pub fn get_pallet_hash(
)
}
/// Obtain the hash representation of a `frame_metadata::v15::RuntimeMetadataV15`.
/// Obtain a hash representation of our metadata or some part of it.
/// This is obtained by calling [`crate::Metadata::hasher()`].
pub struct MetadataHasher<'a> {
metadata: &'a Metadata,
specific_pallets: Option<Vec<&'a str>>,
}
impl<'a> Default for MetadataHasher<'a> {
fn default() -> Self {
MetadataHasher::new()
}
}
impl<'a> MetadataHasher<'a> {
/// Create a new [`MetadataHasher`]
pub fn new() -> Self {
pub(crate) fn new(metadata: &'a Metadata) -> Self {
Self {
metadata,
specific_pallets: None,
}
}
@@ -507,67 +425,43 @@ impl<'a> MetadataHasher<'a> {
}
/// Hash the given metadata.
pub fn hash(&self, metadata: &RuntimeMetadataV15) -> [u8; HASH_LEN] {
pub fn hash(&self) -> [u8; HASH_LEN] {
let mut visited_ids = HashSet::<u32>::new();
let pallet_hash = metadata
.pallets
.iter()
.fold([0u8; HASH_LEN], |bytes, pallet| {
// If specific pallets are given, only include this pallet if it's
// in the list.
if let Some(specific_pallets) = &self.specific_pallets {
if specific_pallets.iter().all(|&p| p != pallet.name) {
return bytes;
}
}
// We don't care what order the pallets are seen in, so XOR their
// hashes together to be order independent.
xor(bytes, get_pallet_hash(&metadata.types, pallet))
});
let metadata = self.metadata;
let apis_hash = metadata.apis.iter().fold([0u8; HASH_LEN], |bytes, api| {
// We don't care what order the runtime APIs are seen in, so XOR
xor(bytes, get_runtime_trait_hash(metadata, api))
let pallet_hash = metadata.pallets().fold([0u8; HASH_LEN], |bytes, pallet| {
// If specific pallets are given, only include this pallet if it's
// in the list.
if let Some(specific_pallets) = &self.specific_pallets {
if specific_pallets.iter().all(|&p| p != pallet.name()) {
return bytes;
}
}
// We don't care what order the pallets are seen in, so XOR their
// hashes together to be order independent.
xor(bytes, get_pallet_hash(pallet))
});
let apis_hash = metadata
.runtime_api_traits()
.fold([0u8; HASH_LEN], |bytes, api| {
// We don't care what order the runtime APIs are seen in, so XOR
xor(bytes, get_runtime_trait_hash(api))
});
let extrinsic_hash = get_extrinsic_hash(&metadata.types, &metadata.extrinsic);
let runtime_hash = get_type_hash(&metadata.types, metadata.ty.id, &mut visited_ids);
let runtime_hash = get_type_hash(&metadata.types, metadata.runtime_ty(), &mut visited_ids);
concat_and_hash4(&pallet_hash, &apis_hash, &extrinsic_hash, &runtime_hash)
}
}
/// An error returned if we attempt to get the hash for a specific call, constant,
/// storage or runtime API function does not exist.
///
/// The location of the specific item (call, constant, storage or runtime API function)
/// is stored with two indirections:
/// - Root
/// The root location of the item. For calls, constants, storage this represents the
/// pallet name. While for runtime API function this represents the trait name.
/// - Item
/// The actual item. For calls, constants, storage this represents the actual name.
/// While for runtime API functions this represents the method name.
#[derive(Clone, Debug)]
pub enum NotFound {
/// The root location of the item cannot be found.
/// - pallet name: for calls, constants, storage
/// - trait name: for runtime API functions
Root,
/// The actual item name cannot be found.
Item,
}
#[cfg(test)]
mod tests {
use super::*;
use bitvec::{order::Lsb0, vec::BitVec};
use frame_metadata::v15::{
ExtrinsicMetadata, PalletCallMetadata, PalletConstantMetadata, PalletErrorMetadata,
PalletEventMetadata, PalletMetadata, PalletStorageMetadata, RuntimeMetadataV15,
StorageEntryMetadata, StorageEntryModifier,
};
use frame_metadata::v15;
use scale_info::meta_type;
// Define recursive types.
@@ -623,16 +517,16 @@ mod tests {
Remark { remark: DigestItem },
}
fn build_default_extrinsic() -> ExtrinsicMetadata {
ExtrinsicMetadata {
fn build_default_extrinsic() -> v15::ExtrinsicMetadata {
v15::ExtrinsicMetadata {
ty: meta_type::<()>(),
version: 0,
signed_extensions: vec![],
}
}
fn default_pallet() -> PalletMetadata {
PalletMetadata {
fn default_pallet() -> v15::PalletMetadata {
v15::PalletMetadata {
name: "Test",
storage: None,
calls: None,
@@ -644,19 +538,19 @@ mod tests {
}
}
fn build_default_pallets() -> Vec<PalletMetadata> {
fn build_default_pallets() -> Vec<v15::PalletMetadata> {
vec![
PalletMetadata {
v15::PalletMetadata {
name: "First",
calls: Some(PalletCallMetadata {
calls: Some(v15::PalletCallMetadata {
ty: meta_type::<MetadataTestType>(),
}),
..default_pallet()
},
PalletMetadata {
v15::PalletMetadata {
name: "Second",
index: 1,
calls: Some(PalletCallMetadata {
calls: Some(v15::PalletCallMetadata {
ty: meta_type::<(DigestItem, AccountId32, A)>(),
}),
..default_pallet()
@@ -664,13 +558,15 @@ mod tests {
]
}
fn pallets_to_metadata(pallets: Vec<PalletMetadata>) -> RuntimeMetadataV15 {
RuntimeMetadataV15::new(
fn pallets_to_metadata(pallets: Vec<v15::PalletMetadata>) -> Metadata {
v15::RuntimeMetadataV15::new(
pallets,
build_default_extrinsic(),
meta_type::<()>(),
vec![],
)
.try_into()
.expect("can build valid metadata")
}
#[test]
@@ -686,8 +582,8 @@ mod tests {
pallets_swap[1].index = 1;
let metadata_swap = pallets_to_metadata(pallets_swap);
let hash = MetadataHasher::new().hash(&metadata);
let hash_swap = MetadataHasher::new().hash(&metadata_swap);
let hash = MetadataHasher::new(&metadata).hash();
let hash_swap = MetadataHasher::new(&metadata_swap).hash();
// Changing pallet order must still result in a deterministic unique hash.
assert_eq!(hash, hash_swap);
@@ -696,13 +592,13 @@ mod tests {
#[test]
fn recursive_type() {
let mut pallet = default_pallet();
pallet.calls = Some(PalletCallMetadata {
pallet.calls = Some(v15::PalletCallMetadata {
ty: meta_type::<A>(),
});
let metadata = pallets_to_metadata(vec![pallet]);
// Check hashing algorithm finishes on a recursive type.
MetadataHasher::new().hash(&metadata);
MetadataHasher::new(&metadata).hash();
}
#[test]
@@ -713,10 +609,10 @@ mod tests {
/// must produce the same deterministic hashing value.
fn recursive_types_different_order() {
let mut pallets = build_default_pallets();
pallets[0].calls = Some(PalletCallMetadata {
pallets[0].calls = Some(v15::PalletCallMetadata {
ty: meta_type::<A>(),
});
pallets[1].calls = Some(PalletCallMetadata {
pallets[1].calls = Some(v15::PalletCallMetadata {
ty: meta_type::<B>(),
});
pallets[1].index = 1;
@@ -728,8 +624,8 @@ mod tests {
pallets_swap[1].index = 1;
let metadata_swap = pallets_to_metadata(pallets_swap);
let hash = MetadataHasher::new().hash(&metadata);
let hash_swap = MetadataHasher::new().hash(&metadata_swap);
let hash = MetadataHasher::new(&metadata).hash();
let hash_swap = MetadataHasher::new(&metadata_swap).hash();
// Changing pallet order must still result in a deterministic unique hash.
assert_eq!(hash, hash_swap);
@@ -737,12 +633,12 @@ mod tests {
#[test]
fn pallet_hash_correctness() {
let compare_pallets_hash = |lhs: &PalletMetadata, rhs: &PalletMetadata| {
let compare_pallets_hash = |lhs: &v15::PalletMetadata, rhs: &v15::PalletMetadata| {
let metadata = pallets_to_metadata(vec![lhs.clone()]);
let hash = MetadataHasher::new().hash(&metadata);
let hash = MetadataHasher::new(&metadata).hash();
let metadata = pallets_to_metadata(vec![rhs.clone()]);
let new_hash = MetadataHasher::new().hash(&metadata);
let new_hash = MetadataHasher::new(&metadata).hash();
assert_ne!(hash, new_hash);
};
@@ -750,12 +646,12 @@ mod tests {
// Build metadata progressively from an empty pallet to a fully populated pallet.
let mut pallet = default_pallet();
let pallet_lhs = pallet.clone();
pallet.storage = Some(PalletStorageMetadata {
pallet.storage = Some(v15::PalletStorageMetadata {
prefix: "Storage",
entries: vec![StorageEntryMetadata {
entries: vec![v15::StorageEntryMetadata {
name: "BlockWeight",
modifier: StorageEntryModifier::Default,
ty: StorageEntryType::Plain(meta_type::<u8>()),
modifier: v15::StorageEntryModifier::Default,
ty: v15::StorageEntryType::Plain(meta_type::<u8>()),
default: vec![],
docs: vec![],
}],
@@ -771,20 +667,20 @@ mod tests {
// call_name_02 { arg01: type, arg02: type }
// }
// ```
pallet.calls = Some(PalletCallMetadata {
pallet.calls = Some(v15::PalletCallMetadata {
ty: meta_type::<Call>(),
});
compare_pallets_hash(&pallet_lhs, &pallet);
let pallet_lhs = pallet.clone();
// Events are similar to Calls.
pallet.event = Some(PalletEventMetadata {
pallet.event = Some(v15::PalletEventMetadata {
ty: meta_type::<Call>(),
});
compare_pallets_hash(&pallet_lhs, &pallet);
let pallet_lhs = pallet.clone();
pallet.constants = vec![PalletConstantMetadata {
pallet.constants = vec![v15::PalletConstantMetadata {
name: "BlockHashCount",
ty: meta_type::<u64>(),
value: vec![96u8, 0, 0, 0],
@@ -793,7 +689,7 @@ mod tests {
compare_pallets_hash(&pallet_lhs, &pallet);
let pallet_lhs = pallet.clone();
pallet.error = Some(PalletErrorMetadata {
pallet.error = Some(v15::PalletErrorMetadata {
ty: meta_type::<MetadataTestType>(),
});
compare_pallets_hash(&pallet_lhs, &pallet);
@@ -809,27 +705,27 @@ mod tests {
let metadata_both = pallets_to_metadata(pallets);
// Hashing will ignore any non-existant pallet and return the same result.
let hash = MetadataHasher::new()
let hash = MetadataHasher::new(&metadata_one)
.only_these_pallets(&["First", "Second"])
.hash(&metadata_one);
let hash_rhs = MetadataHasher::new()
.hash();
let hash_rhs = MetadataHasher::new(&metadata_one)
.only_these_pallets(&["First"])
.hash(&metadata_one);
.hash();
assert_eq!(hash, hash_rhs, "hashing should ignore non-existant pallets");
// Hashing one pallet from metadata with 2 pallets inserted will ignore the second pallet.
let hash_second = MetadataHasher::new()
let hash_second = MetadataHasher::new(&metadata_both)
.only_these_pallets(&["First"])
.hash(&metadata_both);
.hash();
assert_eq!(
hash_second, hash,
"hashing one pallet should ignore the others"
);
// Check hashing with all pallets.
let hash_second = MetadataHasher::new()
let hash_second = MetadataHasher::new(&metadata_both)
.only_these_pallets(&["First", "Second"])
.hash(&metadata_both);
.hash();
assert_ne!(
hash_second, hash,
"hashing both pallets should produce a different result from hashing just one pallet"
@@ -841,12 +737,12 @@ mod tests {
// Get a hash representation of the provided meta type,
// inserted in the context of pallet metadata call.
let to_hash = |meta_ty| {
let pallet = PalletMetadata {
calls: Some(PalletCallMetadata { ty: meta_ty }),
let pallet = v15::PalletMetadata {
calls: Some(v15::PalletCallMetadata { ty: meta_ty }),
..default_pallet()
};
let metadata = pallets_to_metadata(vec![pallet]);
MetadataHasher::new().hash(&metadata)
MetadataHasher::new(&metadata).hash()
};
#[allow(dead_code)]
+86
View File
@@ -0,0 +1,86 @@
// Copyright 2019-2023 Parity Technologies (UK) Ltd.
// This file is dual-licensed as Apache-2.0 or GPL-3.0.
// see LICENSE for license details.
use scale_info::{form::PortableForm, PortableRegistry, TypeDef, Variant};
use std::collections::HashMap;
/// Given some type ID and type registry, build a couple of
/// indexes to look up variants by index or name. If the ID provided
/// is not a variant, the index will be empty.
///
/// API optimized for dealing with the `Option<u32>` variant type IDs
/// that we get in metadata pallets.
#[derive(Debug, Clone)]
pub struct VariantIndex {
by_name: HashMap<String, usize>,
by_index: HashMap<u8, usize>,
}
impl VariantIndex {
/// Build indexes from the optional variant ID.
pub fn build(variant_id: Option<u32>, types: &PortableRegistry) -> Self {
let Some(variants) = Self::get(variant_id, types) else {
return Self::empty()
};
let mut by_name = HashMap::new();
let mut by_index = HashMap::new();
for (pos, variant) in variants.iter().enumerate() {
by_name.insert(variant.name.to_owned(), pos);
by_index.insert(variant.index, pos);
}
Self { by_name, by_index }
}
/// Build an empty index.
pub fn empty() -> Self {
Self {
by_name: Default::default(),
by_index: Default::default(),
}
}
/// Get the variants we're pointing at; None if this isn't possible.
pub fn get(
variant_id: Option<u32>,
types: &PortableRegistry,
) -> Option<&[Variant<PortableForm>]> {
let Some(variant_id) = variant_id else {
return None
};
let TypeDef::Variant(v) = &types.resolve(variant_id)?.type_def else {
return None
};
Some(&v.variants)
}
/// Lookup a variant by name; `None` if the type is not a variant or name isn't found.
pub fn lookup_by_name<'a, K>(
&self,
name: &K,
variant_id: Option<u32>,
types: &'a PortableRegistry,
) -> Option<&'a Variant<PortableForm>>
where
String: std::borrow::Borrow<K>,
K: std::hash::Hash + Eq + ?Sized,
{
let pos = *self.by_name.get(name)?;
let variants = Self::get(variant_id, types)?;
variants.get(pos)
}
/// Lookup a variant by index; `None` if the type is not a variant or index isn't found.
pub fn lookup_by_index<'a>(
&self,
index: u8,
variant_id: Option<u32>,
types: &'a PortableRegistry,
) -> Option<&'a Variant<PortableForm>> {
let pos = *self.by_index.get(&index)?;
let variants = Self::get(variant_id, types)?;
variants.get(pos)
}
}