Utilize Metadata V15 (#1041)

* Update frame-metadata to the latest branch

Signed-off-by: Alexandru Vasile <alexandru.vasile@parity.io>

* metadata: Add outer enum types

Signed-off-by: Alexandru Vasile <alexandru.vasile@parity.io>

* metadata: Extend the extrinsic with address,call,sign,extra types

Signed-off-by: Alexandru Vasile <alexandru.vasile@parity.io>

* Codegen test Event, Error and Call for outer enums

Signed-off-by: Alexandru Vasile <alexandru.vasile@parity.io>

* Revert "Codegen test Event, Error and Call for outer enums"

This reverts commit db542dca0369eedd257a7ec031d5b5549bc46a88.

* Update frame-metadata from the latest release

Signed-off-by: Alexandru Vasile <alexandru.vasile@parity.io>

* Update scale-info

Signed-off-by: Alexandru Vasile <alexandru.vasile@parity.io>

* codegen/error: Support v15 message

Signed-off-by: Alexandru Vasile <alexandru.vasile@parity.io>

* metadata: Convert v14 to v15

Signed-off-by: Alexandru Vasile <alexandru.vasile@parity.io>

* metadata/retain: Adjust to extrinsic type for V15

Signed-off-by: Alexandru Vasile <alexandru.vasile@parity.io>

* metadata/validation: Adjust hashing for extrinsic types V15

Signed-off-by: Alexandru Vasile <alexandru.vasile@parity.io>

* scripts: Fetch V15 and output codegen for full_client only

Signed-off-by: Alexandru Vasile <alexandru.vasile@parity.io>

* subxt/blocks: Use extrinsic types directly

Signed-off-by: Alexandru Vasile <alexandru.vasile@parity.io>

* testing: Fetch V15 for build script

Signed-off-by: Alexandru Vasile <alexandru.vasile@parity.io>

* artifacts: Generate from latest polkadot version

Signed-off-by: Alexandru Vasile <alexandru.vasile@parity.io>

* codegen: Fetch legacy with old API for v14 only

Signed-off-by: Alexandru Vasile <alexandru.vasile@parity.io>

* rpc: Fetch metadata versions

Signed-off-by: Alexandru Vasile <alexandru.vasile@parity.io>

* client: Fetch latest unstable then V15 then V14

Signed-off-by: Alexandru Vasile <alexandru.vasile@parity.io>

* testing: Adjust testing API to latest interface

Signed-off-by: Alexandru Vasile <alexandru.vasile@parity.io>

* Adjust clippy

Signed-off-by: Alexandru Vasile <alexandru.vasile@parity.io>

* metadata: Generate the `RuntimeError` type for V14

Signed-off-by: Alexandru Vasile <alexandru.vasile@parity.io>

* Remove testing files

Signed-off-by: Alexandru Vasile <alexandru.vasile@parity.io>

* testing/staking: Remove controller account from bond

Signed-off-by: Alexandru Vasile <alexandru.vasile@parity.io>

* metadata/validation: Use specific variants for hashing RuntimeCall

Signed-off-by: Alexandru Vasile <alexandru.vasile@parity.io>

* XXX: Custom Substrate binary: must revert with next release

Signed-off-by: Alexandru Vasile <alexandru.vasile@parity.io>

* XXX: To revert: CI use hardcoded substrate

Signed-off-by: Alexandru Vasile <alexandru.vasile@parity.io>

* codegen: Use v15 outer enum types

Signed-off-by: Alexandru Vasile <alexandru.vasile@parity.io>

* metadata: Retain outer enum types

Signed-off-by: Alexandru Vasile <alexandru.vasile@parity.io>

* codegen: Use outer enum types instead of generating them

Signed-off-by: Alexandru Vasile <alexandru.vasile@parity.io>

* Update artifacts

Signed-off-by: Alexandru Vasile <alexandru.vasile@parity.io>

* Revert "XXX: Custom Substrate binary: must revert with next release"

This reverts commit e9705298661919f5769720b35030759fb8a7b01d.

Revert "XXX: To revert: CI use hardcoded substrate"

This reverts commit b18a5a0985a56ee4ad99bc9a1c0f9cd733cf4271.

* testing: Include env for dummy wat contracts

Signed-off-by: Alexandru Vasile <alexandru.vasile@parity.io>

* Adjsut clippy

Signed-off-by: Alexandru Vasile <alexandru.vasile@parity.io>

* ci: Use new link for fetching latest substrate binary

Signed-off-by: Alexandru Vasile <alexandru.vasile@parity.io>

* tests: Include dummy RuntimeEvent into test metadata

Signed-off-by: Alexandru Vasile <alexandru.vasile@parity.io>

* ci: Bump light-client timeout tests to 25min

Signed-off-by: Alexandru Vasile <alexandru.vasile@parity.io>

* metadata/validation: Use specific pallets as provided

Signed-off-by: Alexandru Vasile <alexandru.vasile@parity.io>

* testing: Rename metadata constant

Signed-off-by: Alexandru Vasile <alexandru.vasile@parity.io>

* metadata: Use call_ty instead of signature_ty

Signed-off-by: Alexandru Vasile <alexandru.vasile@parity.io>

* metadata: Rename retaining variant function

Signed-off-by: Alexandru Vasile <alexandru.vasile@parity.io>

* metadata: Use Option<&[&str]>

Signed-off-by: Alexandru Vasile <alexandru.vasile@parity.io>

* online_client: Fetch V15 metadata explicitely

Signed-off-by: Alexandru Vasile <alexandru.vasile@parity.io>

* metadata/validation: Include the hash of the outer enum types

Signed-off-by: Alexandru Vasile <alexandru.vasile@parity.io>

* metadata: Fix sign typo

Signed-off-by: Alexandru Vasile <alexandru.vasile@parity.io>

* artifacts: Update the artifacts

Signed-off-by: Alexandru Vasile <alexandru.vasile@parity.io>

* subxt: Remove RootError RootEvent and RootExtrinsic traits

Signed-off-by: Alexandru Vasile <alexandru.vasile@parity.io>

* Update polkadot.rs

Signed-off-by: Alexandru Vasile <alexandru.vasile@parity.io>

* metadata/tests: Ensure outer enum variants are retained

Signed-off-by: Alexandru Vasile <alexandru.vasile@parity.io>

* scripts: Include multiple pallets for our decoding purposes

Signed-off-by: Alexandru Vasile <alexandru.vasile@parity.io>

* subxt: Apply clippy

Signed-off-by: Alexandru Vasile <alexandru.vasile@parity.io>

* artifacts: Update small metadata

Signed-off-by: Alexandru Vasile <alexandru.vasile@parity.io>

* error: Keep raw bytes for the ModuleError representation

Signed-off-by: Alexandru Vasile <alexandru.vasile@parity.io>

* error: Modify docs to not include links

Signed-off-by: Alexandru Vasile <alexandru.vasile@parity.io>

* subxt/tests: Propagate `RuntimeCall` to outer enums

Signed-off-by: Alexandru Vasile <alexandru.vasile@parity.io>

* subxt: Provide proper byte slice for decoding

Signed-off-by: Alexandru Vasile <alexandru.vasile@parity.io>

* Update artifacts

Signed-off-by: Alexandru Vasile <alexandru.vasile@parity.io>

* cli/tests: Adjust expected pallets message

Signed-off-by: Alexandru Vasile <alexandru.vasile@parity.io>

* metadata: Test conversion from v14 to v15

Signed-off-by: Alexandru Vasile <alexandru.vasile@parity.io>

* metadata: Fix typo

Signed-off-by: Alexandru Vasile <alexandru.vasile@parity.io>

* Update subxt/src/blocks/extrinsic_types.rs

Co-authored-by: James Wilson <james@jsdw.me>

* metadata: Simplify type path for RuntimeError

Signed-off-by: Alexandru Vasile <alexandru.vasile@parity.io>

* metadata/validation: Use visited ids per outer enum

Signed-off-by: Alexandru Vasile <alexandru.vasile@parity.io>

* error: Remove RawModuleError

Signed-off-by: Alexandru Vasile <alexandru.vasile@parity.io>

* Fix new clippy error from updated rust version

Signed-off-by: Alexandru Vasile <alexandru.vasile@parity.io>

---------

Signed-off-by: Alexandru Vasile <alexandru.vasile@parity.io>
Co-authored-by: James Wilson <james@jsdw.me>
This commit is contained in:
Alexandru Vasile
2023-07-17 12:52:02 +03:00
committed by GitHub
parent 4fb051f233
commit 78a106f059
33 changed files with 3283 additions and 9056 deletions
+401 -5
View File
@@ -2,6 +2,8 @@
// This file is dual-licensed as Apache-2.0 or GPL-3.0.
// see LICENSE for license details.
use std::collections::HashMap;
use super::TryFromError;
use crate::Metadata;
use frame_metadata::{v14, v15};
@@ -21,7 +23,47 @@ impl From<Metadata> for v14::RuntimeMetadataV14 {
}
}
fn v15_to_v14(metadata: v15::RuntimeMetadataV15) -> v14::RuntimeMetadataV14 {
fn v15_to_v14(mut metadata: v15::RuntimeMetadataV15) -> v14::RuntimeMetadataV14 {
let types = &mut metadata.types;
// In subxt we care about the `Address`, `Call`, `Signature` and `Extra` types.
let extrinsic_type = scale_info::Type {
path: scale_info::Path {
segments: vec![
"primitives".to_string(),
"runtime".to_string(),
"generic".to_string(),
"UncheckedExtrinsic".to_string(),
],
},
type_params: vec![
scale_info::TypeParameter::<scale_info::form::PortableForm> {
name: "Address".to_string(),
ty: Some(metadata.extrinsic.address_ty),
},
scale_info::TypeParameter::<scale_info::form::PortableForm> {
name: "Call".to_string(),
ty: Some(metadata.extrinsic.call_ty),
},
scale_info::TypeParameter::<scale_info::form::PortableForm> {
name: "Signature".to_string(),
ty: Some(metadata.extrinsic.signature_ty),
},
scale_info::TypeParameter::<scale_info::form::PortableForm> {
name: "Extra".to_string(),
ty: Some(metadata.extrinsic.extra_ty),
},
],
type_def: scale_info::TypeDef::Composite(scale_info::TypeDefComposite { fields: vec![] }),
docs: vec![],
};
let extrinsic_type_id = types.types.len() as u32;
types.types.push(scale_info::PortableType {
id: extrinsic_type_id,
ty: extrinsic_type,
});
v14::RuntimeMetadataV14 {
types: metadata.types,
pallets: metadata
@@ -92,7 +134,7 @@ fn v15_to_v14(metadata: v15::RuntimeMetadataV15) -> v14::RuntimeMetadataV14 {
})
.collect(),
extrinsic: frame_metadata::v14::ExtrinsicMetadata {
ty: metadata.extrinsic.ty,
ty: extrinsic_type_id.into(),
version: metadata.extrinsic.version,
signed_extensions: metadata.extrinsic.signed_extensions.into_iter().map(|ext| {
frame_metadata::v14::SignedExtensionMetadata {
@@ -106,7 +148,13 @@ fn v15_to_v14(metadata: v15::RuntimeMetadataV15) -> v14::RuntimeMetadataV14 {
}
}
fn v14_to_v15(metadata: v14::RuntimeMetadataV14) -> v15::RuntimeMetadataV15 {
fn v14_to_v15(mut metadata: v14::RuntimeMetadataV14) -> v15::RuntimeMetadataV15 {
// Find the extrinsic types.
let extrinsic_parts = ExtrinsicPartTypeIds::new(&metadata)
.expect("Extrinsic types are always present on V14; qed");
let outer_enums = generate_outer_enums(&mut metadata);
v15::RuntimeMetadataV15 {
types: metadata.types,
pallets: metadata
@@ -178,7 +226,6 @@ fn v14_to_v15(metadata: v14::RuntimeMetadataV14) -> v15::RuntimeMetadataV15 {
})
.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 {
@@ -186,9 +233,358 @@ fn v14_to_v15(metadata: v14::RuntimeMetadataV14) -> v15::RuntimeMetadataV15 {
ty: ext.ty,
additional_signed: ext.additional_signed,
}
}).collect()
}).collect(),
address_ty: extrinsic_parts.address.into(),
call_ty: extrinsic_parts.call.into(),
signature_ty: extrinsic_parts.signature.into(),
extra_ty: extrinsic_parts.extra.into(),
},
ty: metadata.ty,
apis: Default::default(),
outer_enums,
custom: v15::CustomMetadata {
map: Default::default(),
},
}
}
/// The type IDs extracted from the metadata that represent the
/// generic type parameters passed to the `UncheckedExtrinsic` from
/// the substrate-based chain.
struct ExtrinsicPartTypeIds {
address: u32,
call: u32,
signature: u32,
extra: u32,
}
impl ExtrinsicPartTypeIds {
/// Extract the generic type parameters IDs from the extrinsic type.
fn new(metadata: &v14::RuntimeMetadataV14) -> Result<Self, String> {
const ADDRESS: &str = "Address";
const CALL: &str = "Call";
const SIGNATURE: &str = "Signature";
const EXTRA: &str = "Extra";
let extrinsic_id = metadata.extrinsic.ty.id;
let Some(extrinsic_ty) = metadata.types.resolve(extrinsic_id) else {
return Err("Missing extrinsic type".into())
};
let params: HashMap<_, _> = extrinsic_ty
.type_params
.iter()
.map(|ty_param| {
let Some(ty) = ty_param.ty else {
return Err("Missing type param type from extrinsic".to_string());
};
Ok((ty_param.name.as_str(), ty.id))
})
.collect::<Result<_, _>>()?;
let Some(address) = params.get(ADDRESS) else {
return Err("Missing address type from extrinsic".into());
};
let Some(call) = params.get(CALL) else {
return Err("Missing call type from extrinsic".into());
};
let Some(signature) = params.get(SIGNATURE) else {
return Err("Missing signature type from extrinsic".into());
};
let Some(extra) = params.get(EXTRA) else {
return Err("Missing extra type from extrinsic".into());
};
Ok(ExtrinsicPartTypeIds {
address: *address,
call: *call,
signature: *signature,
extra: *extra,
})
}
}
fn generate_outer_enums(
metadata: &mut v14::RuntimeMetadataV14,
) -> v15::OuterEnums<scale_info::form::PortableForm> {
let call_enum = metadata
.types
.types
.iter()
.find(|ty| {
let Some(ident) = ty.ty.path.ident() else { return false };
ident == "RuntimeCall"
})
.expect("RuntimeCall exists in V14; qed");
let event_enum = metadata
.types
.types
.iter()
.find(|ty| {
let Some(ident) = ty.ty.path.ident() else { return false };
ident == "RuntimeEvent"
})
.expect("RuntimeEvent exists in V14; qed");
let call_ty = call_enum.id.into();
let event_ty = event_enum.id.into();
let mut path_segments = call_enum.ty.path.segments.clone();
let last = path_segments
.last_mut()
.expect("Should have at least one segment checked above; qed");
*last = "RuntimeError".to_string();
let error_ty_id = generate_runtime_error_type(metadata, path_segments);
v15::OuterEnums {
call_enum_ty: call_ty,
event_enum_ty: event_ty,
error_enum_ty: error_ty_id.into(),
}
}
/// Generate the `RuntimeError` type and add it to the metadata.
///
/// Returns the `RuntimeError` Id from the registry.
fn generate_runtime_error_type(
metadata: &mut v14::RuntimeMetadataV14,
path_segments: Vec<String>,
) -> u32 {
let variants: Vec<_> = metadata
.pallets
.iter()
.filter_map(|pallet| {
let Some(pallet_error) = &pallet.error else { return None };
let path = format!("{}Error", pallet.name);
Some(scale_info::Variant {
name: pallet.name.clone(),
fields: vec![scale_info::Field {
name: None,
ty: pallet_error.ty.id.into(),
type_name: Some(path),
docs: vec![],
}],
index: pallet.index,
docs: vec![],
})
})
.collect();
let error_type = scale_info::Type {
path: scale_info::Path {
segments: path_segments,
},
type_params: vec![],
type_def: scale_info::TypeDef::Variant(scale_info::TypeDefVariant { variants }),
docs: vec![],
};
let error_type_id = metadata.types.types.len() as u32;
metadata.types.types.push(scale_info::PortableType {
id: error_type_id,
ty: error_type,
});
error_type_id
}
#[cfg(test)]
mod tests {
use super::*;
use codec::Decode;
use frame_metadata::{v15::RuntimeMetadataV15, RuntimeMetadata, RuntimeMetadataPrefixed};
use scale_info::TypeDef;
use std::{fs, path::Path};
fn load_v15_metadata() -> RuntimeMetadataV15 {
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::V15(v15) => v15,
_ => panic!("Unsupported metadata version {:?}", meta.1),
}
}
#[test]
fn test_extrinsic_id_generation() {
let v15 = load_v15_metadata();
let v14 = v15_to_v14(v15.clone());
let ext_ty = v14.types.resolve(v14.extrinsic.ty.id).unwrap();
let addr_id = ext_ty
.type_params
.iter()
.find_map(|ty| {
if ty.name == "Address" {
Some(ty.ty.unwrap().id)
} else {
None
}
})
.unwrap();
let call_id = ext_ty
.type_params
.iter()
.find_map(|ty| {
if ty.name == "Call" {
Some(ty.ty.unwrap().id)
} else {
None
}
})
.unwrap();
let extra_id = ext_ty
.type_params
.iter()
.find_map(|ty| {
if ty.name == "Extra" {
Some(ty.ty.unwrap().id)
} else {
None
}
})
.unwrap();
let signature_id = ext_ty
.type_params
.iter()
.find_map(|ty| {
if ty.name == "Signature" {
Some(ty.ty.unwrap().id)
} else {
None
}
})
.unwrap();
// Position in type registry shouldn't change.
assert_eq!(v15.extrinsic.address_ty.id, addr_id);
assert_eq!(v15.extrinsic.call_ty.id, call_id);
assert_eq!(v15.extrinsic.extra_ty.id, extra_id);
assert_eq!(v15.extrinsic.signature_ty.id, signature_id);
let v15_addr = v15.types.resolve(v15.extrinsic.address_ty.id).unwrap();
let v14_addr = v14.types.resolve(addr_id).unwrap();
assert_eq!(v15_addr, v14_addr);
let v15_call = v15.types.resolve(v15.extrinsic.call_ty.id).unwrap();
let v14_call = v14.types.resolve(call_id).unwrap();
assert_eq!(v15_call, v14_call);
let v15_extra = v15.types.resolve(v15.extrinsic.extra_ty.id).unwrap();
let v14_extra = v14.types.resolve(extra_id).unwrap();
assert_eq!(v15_extra, v14_extra);
let v15_sign = v15.types.resolve(v15.extrinsic.signature_ty.id).unwrap();
let v14_sign = v14.types.resolve(signature_id).unwrap();
assert_eq!(v15_sign, v14_sign);
// Ensure we don't lose the information when converting back to v15.
let converted_v15 = v14_to_v15(v14);
let v15_addr = v15.types.resolve(v15.extrinsic.address_ty.id).unwrap();
let converted_v15_addr = converted_v15
.types
.resolve(converted_v15.extrinsic.address_ty.id)
.unwrap();
assert_eq!(v15_addr, converted_v15_addr);
let v15_call = v15.types.resolve(v15.extrinsic.call_ty.id).unwrap();
let converted_v15_call = converted_v15
.types
.resolve(converted_v15.extrinsic.call_ty.id)
.unwrap();
assert_eq!(v15_call, converted_v15_call);
let v15_extra = v15.types.resolve(v15.extrinsic.extra_ty.id).unwrap();
let converted_v15_extra = converted_v15
.types
.resolve(converted_v15.extrinsic.extra_ty.id)
.unwrap();
assert_eq!(v15_extra, converted_v15_extra);
let v15_sign = v15.types.resolve(v15.extrinsic.signature_ty.id).unwrap();
let converted_v15_sign = converted_v15
.types
.resolve(converted_v15.extrinsic.signature_ty.id)
.unwrap();
assert_eq!(v15_sign, converted_v15_sign);
}
#[test]
fn test_outer_enums_generation() {
let v15 = load_v15_metadata();
let v14 = v15_to_v14(v15.clone());
// Convert back to v15 and expect to have the enum types properly generated.
let converted_v15 = v14_to_v15(v14);
// RuntimeCall and RuntimeEvent were already present in the metadata v14.
let v15_call = v15.types.resolve(v15.outer_enums.call_enum_ty.id).unwrap();
let converted_v15_call = converted_v15
.types
.resolve(converted_v15.outer_enums.call_enum_ty.id)
.unwrap();
assert_eq!(v15_call, converted_v15_call);
let v15_event = v15.types.resolve(v15.outer_enums.event_enum_ty.id).unwrap();
let converted_v15_event = converted_v15
.types
.resolve(converted_v15.outer_enums.event_enum_ty.id)
.unwrap();
assert_eq!(v15_event, converted_v15_event);
let v15_error = v15.types.resolve(v15.outer_enums.error_enum_ty.id).unwrap();
let converted_v15_error = converted_v15
.types
.resolve(converted_v15.outer_enums.error_enum_ty.id)
.unwrap();
// Ensure they match in terms of variants and fields ids.
assert_eq!(v15_error.path, converted_v15_error.path);
let TypeDef::Variant(v15_variant) = &v15_error.type_def else {
panic!("V15 error must be a variant");
};
let TypeDef::Variant(converted_v15_variant) = &converted_v15_error.type_def else {
panic!("Converted V15 error must be a variant");
};
assert_eq!(
v15_variant.variants.len(),
converted_v15_variant.variants.len()
);
for (v15_var, converted_v15_var) in v15_variant
.variants
.iter()
.zip(converted_v15_variant.variants.iter())
{
// Variant name must match.
assert_eq!(v15_var.name, converted_v15_var.name);
assert_eq!(v15_var.fields.len(), converted_v15_var.fields.len());
// Fields must have the same type.
for (v15_field, converted_v15_field) in
v15_var.fields.iter().zip(converted_v15_var.fields.iter())
{
assert_eq!(v15_field.ty.id, converted_v15_field.ty.id);
let ty = v15.types.resolve(v15_field.ty.id).unwrap();
let converted_ty = converted_v15
.types
.resolve(converted_v15_field.ty.id)
.unwrap();
assert_eq!(ty, converted_ty);
}
}
}
}
+22 -3
View File
@@ -6,7 +6,7 @@ use super::TryFromError;
use crate::utils::variant_index::VariantIndex;
use crate::{
utils::ordered_map::OrderedMap, ArcStr, ConstantMetadata, ExtrinsicMetadata, Metadata,
PalletMetadataInner, RuntimeApiMetadataInner, RuntimeApiMethodMetadata,
OuterEnumsMetadata, PalletMetadataInner, RuntimeApiMetadataInner, RuntimeApiMethodMetadata,
RuntimeApiMethodParamMetadata, SignedExtensionMetadata, StorageEntryMetadata,
StorageEntryModifier, StorageEntryType, StorageHasher, StorageMetadata,
};
@@ -88,6 +88,11 @@ mod from_v15 {
runtime_ty: m.ty.id,
dispatch_error_ty,
apis: apis.collect(),
outer_enums: OuterEnumsMetadata {
call_enum_ty: m.outer_enums.call_enum_ty.id,
event_enum_ty: m.outer_enums.event_enum_ty.id,
error_enum_ty: m.outer_enums.error_enum_ty.id,
},
})
}
}
@@ -104,13 +109,16 @@ mod from_v15 {
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(),
address_ty: value.address_ty.id,
call_ty: value.call_ty.id,
signature_ty: value.signature_ty.id,
extra_ty: value.extra_ty.id,
}
}
@@ -268,6 +276,14 @@ mod into_v15 {
.into_iter()
.map(from_runtime_api_metadata)
.collect(),
outer_enums: v15::OuterEnums {
call_enum_ty: m.outer_enums.call_enum_ty.into(),
event_enum_ty: m.outer_enums.event_enum_ty.into(),
error_enum_ty: m.outer_enums.error_enum_ty.into(),
},
custom: v15::CustomMetadata {
map: Default::default(),
},
}
}
}
@@ -313,13 +329,16 @@ mod into_v15 {
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(),
address_ty: e.address_ty.into(),
call_ty: e.call_ty.into(),
signature_ty: e.signature_ty.into(),
extra_ty: e.extra_ty.into(),
}
}
+59 -5
View File
@@ -45,6 +45,8 @@ pub struct Metadata {
extrinsic: ExtrinsicMetadata,
/// The type ID of the `Runtime` type.
runtime_ty: u32,
/// The types of the outer enums.
outer_enums: OuterEnumsMetadata,
/// 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.
@@ -77,6 +79,11 @@ impl Metadata {
&self.extrinsic
}
/// Return details about the outer enums.
pub fn outer_enums(&self) -> OuterEnumsMetadata {
self.outer_enums
}
/// An iterator over all of the available pallets.
pub fn pallets(&self) -> impl ExactSizeIterator<Item = PalletMetadata<'_>> {
self.pallets.values().iter().map(|inner| PalletMetadata {
@@ -440,8 +447,14 @@ impl ConstantMetadata {
/// Metadata for the extrinsic type.
#[derive(Debug, Clone)]
pub struct ExtrinsicMetadata {
/// The type of the extrinsic.
ty: u32,
/// The type of the address that signs the extrinsic
address_ty: u32,
/// The type of the outermost Call enum.
call_ty: u32,
/// The type of the extrinsic's signature.
signature_ty: u32,
/// The type of the outermost Extra enum.
extra_ty: u32,
/// Extrinsic version.
version: u8,
/// The signed extensions in the order they appear in the extrinsic.
@@ -449,9 +462,22 @@ pub struct ExtrinsicMetadata {
}
impl ExtrinsicMetadata {
/// Type of the extrinsic.
pub fn ty(&self) -> u32 {
self.ty
/// The type of the address that signs the extrinsic
pub fn address_ty(&self) -> u32 {
self.address_ty
}
/// The type of the outermost Call enum.
pub fn call_ty(&self) -> u32 {
self.call_ty
}
/// The type of the extrinsic's signature.
pub fn signature_ty(&self) -> u32 {
self.signature_ty
}
/// The type of the outermost Extra enum.
pub fn extra_ty(&self) -> u32 {
self.extra_ty
}
/// Extrinsic version.
@@ -491,6 +517,34 @@ impl SignedExtensionMetadata {
}
}
/// Metadata for the outer enums.
#[derive(Debug, Clone, Copy)]
pub struct OuterEnumsMetadata {
/// The type of the outer call enum.
call_enum_ty: u32,
/// The type of the outer event enum.
event_enum_ty: u32,
/// The type of the outer error enum.
error_enum_ty: u32,
}
impl OuterEnumsMetadata {
/// The type of the outer call enum.
pub fn call_enum_ty(&self) -> u32 {
self.call_enum_ty
}
/// The type of the outer event enum.
pub fn event_enum_ty(&self) -> u32 {
self.event_enum_ty
}
/// The type of the outer error enum.
pub fn error_enum_ty(&self) -> u32 {
self.error_enum_ty
}
}
/// Metadata for the available runtime APIs.
#[derive(Debug, Clone, Copy)]
pub struct RuntimeApiMetadata<'a> {
+66 -26
View File
@@ -5,7 +5,8 @@
//! Utility functions to generate a subset of the metadata.
use crate::{
ExtrinsicMetadata, Metadata, PalletMetadataInner, RuntimeApiMetadataInner, StorageEntryType,
ExtrinsicMetadata, Metadata, OuterEnumsMetadata, PalletMetadataInner, RuntimeApiMetadataInner,
StorageEntryType,
};
use scale_info::TypeDef;
use std::collections::{BTreeMap, HashSet};
@@ -82,7 +83,10 @@ fn update_pallet_types(pallet: &mut PalletMetadataInner, map_ids: &BTreeMap<u32,
/// Collect all type IDs needed to represent the extrinsic metadata.
fn collect_extrinsic_types(extrinsic: &ExtrinsicMetadata, type_ids: &mut HashSet<u32>) {
type_ids.insert(extrinsic.ty);
type_ids.insert(extrinsic.address_ty);
type_ids.insert(extrinsic.call_ty);
type_ids.insert(extrinsic.signature_ty);
type_ids.insert(extrinsic.extra_ty);
for signed in &extrinsic.signed_extensions {
type_ids.insert(signed.extra_ty);
@@ -92,7 +96,10 @@ fn collect_extrinsic_types(extrinsic: &ExtrinsicMetadata, type_ids: &mut HashSet
/// 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, map_ids: &BTreeMap<u32, u32>) {
update_type(&mut extrinsic.ty, map_ids);
update_type(&mut extrinsic.address_ty, map_ids);
update_type(&mut extrinsic.call_ty, map_ids);
update_type(&mut extrinsic.signature_ty, map_ids);
update_type(&mut extrinsic.extra_ty, map_ids);
for signed in &mut extrinsic.signed_extensions {
update_type(&mut signed.extra_ty, map_ids);
@@ -122,6 +129,20 @@ fn update_runtime_api_types(apis: &mut [RuntimeApiMetadataInner], map_ids: &BTre
}
}
/// Collect the outer enums type IDs.
fn collect_outer_enums(enums: &OuterEnumsMetadata, type_ids: &mut HashSet<u32>) {
type_ids.insert(enums.call_enum_ty);
type_ids.insert(enums.event_enum_ty);
type_ids.insert(enums.error_enum_ty);
}
/// Update all the type IDs for outer enums.
fn update_outer_enums(enums: &mut OuterEnumsMetadata, map_ids: &BTreeMap<u32, u32>) {
update_type(&mut enums.call_enum_ty, map_ids);
update_type(&mut enums.event_enum_ty, map_ids);
update_type(&mut enums.error_enum_ty, map_ids);
}
/// Update the given type using the new type ID from the portable registry.
///
/// # Panics
@@ -136,38 +157,36 @@ fn update_type(ty: &mut u32, map_ids: &BTreeMap<u32, u32>) {
*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 Metadata, mut filter: F)
/// Retain the enum type identified by ID and keep only the variants that
/// match the provided filter.
fn retain_variants_in_enum_type<F>(metadata: &mut Metadata, id: u32, mut filter: F)
where
F: FnMut(&str) -> bool,
{
let extrinsic_ty = metadata
let ty = metadata
.types
.types
.get_mut(metadata.extrinsic.ty as usize)
.expect("Metadata should contain extrinsic type in registry");
.get_mut(id as usize)
.expect("Metadata should contain enum type in registry");
let Some(call_ty) = extrinsic_ty.ty.type_params
.iter_mut()
.find(|ty| ty.name == "Call")
.and_then(|ty| ty.ty) else { return; };
let call_ty = metadata
.types
.types
.get_mut(call_ty.id as usize)
.expect("Metadata should contain Call type information");
let TypeDef::Variant(variant) = &mut call_ty.ty.type_def else {
panic!("Metadata Call type is expected to be a variant type");
let TypeDef::Variant(variant) = &mut ty.ty.type_def else {
panic!("Metadata type is expected to be a variant type");
};
// Remove all variants from the call type that aren't the pallet(s) we want to keep.
// Remove all variants from the type that aren't the pallet(s) we want to keep.
variant.variants.retain(|v| filter(&v.name));
}
/// Strip any pallets out of the outer enum types that aren't the ones we want to keep.
fn retain_pallets_in_runtime_outer_types<F>(metadata: &mut Metadata, mut filter: F)
where
F: FnMut(&str) -> bool,
{
retain_variants_in_enum_type(metadata, metadata.outer_enums.call_enum_ty, &mut filter);
retain_variants_in_enum_type(metadata, metadata.outer_enums.event_enum_ty, &mut filter);
retain_variants_in_enum_type(metadata, metadata.outer_enums.error_enum_ty, &mut filter);
}
/// Generate a subset of the metadata that contains only the
/// types needed to represent the provided pallets and runtime APIs.
///
@@ -190,10 +209,13 @@ pub fn retain_metadata<F, G>(
{
let mut type_ids = HashSet::new();
// There is a special RuntimeCall type which points to all pallets and call types by default.
// There are special outer enum types that point to all pallets types (call, error, event) by default.
// This brings in a significant chunk of types. We trim this down to only include variants
// for the pallets we're retaining, to avoid this.
retain_pallets_in_runtime_call_type(metadata, &mut pallets_filter);
retain_pallets_in_runtime_outer_types(metadata, &mut pallets_filter);
// Collect the stripped outer enums.
collect_outer_enums(&metadata.outer_enums, &mut type_ids);
// Filter our pallet list to only those pallets we want to keep. Keep hold of all
// type IDs in the pallets we're keeping. Retain all, if no filter specified.
@@ -245,6 +267,7 @@ 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:
update_outer_enums(&mut metadata.outer_enums, &map_ids);
for pallets in metadata.pallets.values_mut() {
update_pallet_types(pallets, &map_ids);
}
@@ -257,6 +280,7 @@ pub fn retain_metadata<F, G>(
mod tests {
use super::*;
use crate::Metadata;
use assert_matches::assert_matches;
use codec::Decode;
use frame_metadata::{RuntimeMetadata, RuntimeMetadataPrefixed};
use std::{fs, path::Path};
@@ -281,6 +305,7 @@ mod tests {
// Retain one pallet at a time ensuring the test does not panic.
for pallet in metadata_cache.pallets() {
let mut metadata = metadata_cache.clone();
retain_metadata(
&mut metadata,
|pallet_name| pallet_name == pallet.name(),
@@ -292,6 +317,21 @@ mod tests {
&*metadata.pallets.get_by_index(0).unwrap().name,
pallet.name()
);
let id = metadata.outer_enums().call_enum_ty;
let ty = metadata.types.resolve(id).unwrap();
let num_variants = if pallet.call_ty_id().is_some() { 1 } else { 0 };
assert_matches!(&ty.type_def, TypeDef::Variant(variant) if variant.variants.len() == num_variants);
let id = metadata.outer_enums().error_enum_ty;
let ty = metadata.types.resolve(id).unwrap();
let num_variants = if pallet.error_ty_id().is_some() { 1 } else { 0 };
assert_matches!(&ty.type_def, TypeDef::Variant(variant) if variant.variants.len() == num_variants);
let id = metadata.outer_enums().event_enum_ty;
let ty = metadata.types.resolve(id).unwrap();
let num_variants = if pallet.event_ty_id().is_some() { 1 } else { 0 };
assert_matches!(&ty.type_def, TypeDef::Variant(variant) if variant.variants.len() == num_variants);
}
}
+97 -18
View File
@@ -5,10 +5,10 @@
//! Utility functions for metadata validation.
use crate::{
ExtrinsicMetadata, Metadata, PalletMetadata, RuntimeApiMetadata, RuntimeApiMethodMetadata,
StorageEntryMetadata, StorageEntryType,
ExtrinsicMetadata, Metadata, OuterEnumsMetadata, PalletMetadata, RuntimeApiMetadata,
RuntimeApiMethodMetadata, StorageEntryMetadata, StorageEntryType,
};
use scale_info::{form::PortableForm, Field, PortableRegistry, TypeDef, Variant};
use scale_info::{form::PortableForm, Field, PortableRegistry, TypeDef, TypeDefVariant, Variant};
use std::collections::HashSet;
/// Predefined value to be returned when we already visited a type.
@@ -104,6 +104,30 @@ fn get_variant_hash(
concat_and_hash2(&variant_name_bytes, &variant_field_bytes)
}
fn get_type_def_variant_hash(
registry: &PortableRegistry,
variant: &TypeDefVariant<PortableForm>,
only_these_variants: Option<&[&str]>,
visited_ids: &mut HashSet<u32>,
) -> [u8; HASH_LEN] {
let variant_id_bytes = [TypeBeingHashed::Variant as u8; HASH_LEN];
let variant_field_bytes = variant.variants.iter().fold([0u8; HASH_LEN], |bytes, var| {
// With EncodeAsType and DecodeAsType we no longer care which order the variants are in,
// as long as all of the names+types are there. XOR to not care about ordering.
let should_hash = only_these_variants
.as_ref()
.map(|only_these_variants| only_these_variants.contains(&var.name.as_str()))
.unwrap_or(true);
if should_hash {
xor(bytes, get_variant_hash(registry, var, visited_ids))
} else {
bytes
}
});
concat_and_hash2(&variant_id_bytes, &variant_field_bytes)
}
/// Obtain the hash representation of a `scale_info::TypeDef`.
fn get_type_def_hash(
registry: &PortableRegistry,
@@ -125,14 +149,7 @@ fn get_type_def_hash(
concat_and_hash2(&composite_id_bytes, &composite_field_bytes)
}
TypeDef::Variant(variant) => {
let variant_id_bytes = [TypeBeingHashed::Variant as u8; HASH_LEN];
let variant_field_bytes =
variant.variants.iter().fold([0u8; HASH_LEN], |bytes, var| {
// With EncodeAsType and DecodeAsType we no longer care which order the variants are in,
// as long as all of the names+types are there. XOR to not care about ordering.
xor(bytes, get_variant_hash(registry, var, visited_ids))
});
concat_and_hash2(&variant_id_bytes, &variant_field_bytes)
get_type_def_variant_hash(registry, variant, None, visited_ids)
}
TypeDef::Sequence(sequence) => concat_and_hash2(
&[TypeBeingHashed::Sequence as u8; HASH_LEN],
@@ -198,8 +215,16 @@ fn get_extrinsic_hash(
) -> [u8; HASH_LEN] {
let mut visited_ids = HashSet::<u32>::new();
let mut bytes = concat_and_hash2(
&get_type_hash(registry, extrinsic.ty, &mut visited_ids),
// Get the hashes of the extrinsic type.
let address_hash = get_type_hash(registry, extrinsic.address_ty, &mut visited_ids);
// The `RuntimeCall` type is intentionally omitted and hashed by the outer enums instead.
let signature_hash = get_type_hash(registry, extrinsic.signature_ty, &mut visited_ids);
let extra_hash = get_type_hash(registry, extrinsic.extra_ty, &mut visited_ids);
let mut bytes = concat_and_hash4(
&address_hash,
&signature_hash,
&extra_hash,
&[extrinsic.version; 32],
);
@@ -215,6 +240,39 @@ fn get_extrinsic_hash(
bytes
}
/// Obtain the hash representation of the `frame_metadata::v15::OuterEnums`.
fn get_outer_enums_hash(
registry: &PortableRegistry,
enums: &OuterEnumsMetadata,
only_these_variants: Option<&[&str]>,
) -> [u8; HASH_LEN] {
/// Hash the provided enum type.
fn get_enum_hash(
registry: &PortableRegistry,
id: u32,
only_these_variants: Option<&[&str]>,
) -> [u8; HASH_LEN] {
let ty = registry
.types
.get(id as usize)
.expect("Metadata should contain enum type in registry");
if let TypeDef::Variant(variant) = &ty.ty.type_def {
get_type_def_variant_hash(registry, variant, only_these_variants, &mut HashSet::new())
} else {
get_type_hash(registry, id, &mut HashSet::new())
}
}
let call_hash = get_enum_hash(registry, enums.call_enum_ty, only_these_variants);
let event_hash = get_enum_hash(registry, enums.event_enum_ty, only_these_variants);
let error_hash = get_enum_hash(registry, enums.error_enum_ty, only_these_variants);
concat_and_hash3(&call_hash, &event_hash, &error_hash)
}
/// Get the hash corresponding to a single storage entry.
fn get_storage_entry_hash(
registry: &PortableRegistry,
@@ -441,8 +499,6 @@ impl<'a> MetadataHasher<'a> {
/// Hash the given metadata.
pub fn hash(&self) -> [u8; HASH_LEN] {
let mut visited_ids = HashSet::<u32>::new();
let metadata = self.metadata;
let pallet_hash = metadata.pallets().fold([0u8; HASH_LEN], |bytes, pallet| {
@@ -480,9 +536,21 @@ impl<'a> MetadataHasher<'a> {
});
let extrinsic_hash = get_extrinsic_hash(&metadata.types, &metadata.extrinsic);
let runtime_hash = get_type_hash(&metadata.types, metadata.runtime_ty(), &mut visited_ids);
let runtime_hash =
get_type_hash(&metadata.types, metadata.runtime_ty(), &mut HashSet::new());
let outer_enums_hash = get_outer_enums_hash(
&metadata.types,
&metadata.outer_enums(),
self.specific_pallets.as_deref(),
);
concat_and_hash4(&pallet_hash, &apis_hash, &extrinsic_hash, &runtime_hash)
concat_and_hash5(
&pallet_hash,
&apis_hash,
&extrinsic_hash,
&runtime_hash,
&outer_enums_hash,
)
}
}
@@ -552,9 +620,12 @@ mod tests {
fn build_default_extrinsic() -> v15::ExtrinsicMetadata {
v15::ExtrinsicMetadata {
ty: meta_type::<()>(),
version: 0,
signed_extensions: vec![],
address_ty: meta_type::<()>(),
call_ty: meta_type::<()>(),
signature_ty: meta_type::<()>(),
extra_ty: meta_type::<()>(),
}
}
@@ -597,6 +668,14 @@ mod tests {
build_default_extrinsic(),
meta_type::<()>(),
vec![],
v15::OuterEnums {
call_enum_ty: meta_type::<()>(),
event_enum_ty: meta_type::<()>(),
error_enum_ty: meta_type::<()>(),
},
v15::CustomMetadata {
map: Default::default(),
},
)
.try_into()
.expect("can build valid metadata")