Metadata V15: Generate Runtime APIs (#918)

* Update frame-metadata to v15.1.0

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

* Enable V15 unstable metadata in frame-metadata

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

* metadata: Move validation hashing to dedicated file

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

* Use sp-metadata-ir from substrate to work with metadata

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

* Revert using sp-metadata-ir in favor of conversion to v15

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

* metadata: Convert v14 to v15

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

* metadata: Use v15 for validation

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

* codegen: Use v15 for codegen

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

* metadata/bench: Use v15

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

* Adjust to v15 metadata

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

* Adjust testing

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

* Improve documentation

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

* force CI

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

* rpc: Fetch metadata at version

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

* artifacts: Update polkadot.scale from commit 6dc9e84dde2

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

* codegen: Fetch V15 using the new API

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

* codegen: Add runtime API interface

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

* metadata: Hash runtime API metadata for validation

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

* metadata: Extract runtime API metadata wrapper from subxt::Metadata

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

* subxt: Adjust hashing cache to reflect root+item keys

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

* rpc: Add raw state_call API method

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

* runtime_api: Add payload with static and dynamic variants

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

* subxt: Allow payloads to call into the runtime

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

* examples: Add example to make a runtime API call both static and dynamic

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

* Update polkadot.rs

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

* codegen: Simplify client fetching

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

* Address feedback and fallback to old API if needed

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

* runtime_api: Make mutability conditional on input params

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

* Regenerate polkadot.rs

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

* metadata: Retain only pallets without runtime API info

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

* codegen: Retry via `Metadata_metadata` without conversion

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

* payload: Remove `Decode` and change validation fn

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

* metadata: Retain runtime API types

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

* codegen: Runtime APIs documentation based on flag

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

* Update examples/examples/custom_metadata_url.rs

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

* Update artifacts from polkadot-a6cfdb16e9

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

* Update polkadot.rs with polkadot-a6cfdb16e9

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

* codegen: Generate input structures for runtime API

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

* runtime_api: Remove the static paylaod and use single impl

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

* examples: Fetch account nonce

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

* testing: Adjust build script to fetch latest metadata

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

* testing: Check account nonce from runtime API

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

* Update cargo.lock

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

* codegen: Fix doc generation for runtime types

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

* codegen: Rename `inputs` runtime calls module to `types`

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

* codegen: Generate Calls structs inside the types module

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

* testing: Check Alice account nonce before submitting the tx

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

* cli: Add metadata version option flag supporting v14 and unstable

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

* cli: Specify version to fetch

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

* subxt: Fallback to fetching latest stable metadata

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

* subxt: Add unstable-metadata feature to fetch the latest

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

* RuntimeVersion with Latest and Version(u32)

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

* Update polkadot.rs

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

* codegen: Adjust fetch_metadata to inspect version list

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

* testing: Adjust metadata to metadata_legacy

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

* events: Adjust docs to use metadata_legacy

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

* have a pass over fetch_metadata

* cargo fmt

* Option<String> when fetch metadata via latest API

* clippy

* fmt

* cli: Use the MetadataVersion from codegen

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

* cli: Specify latest as default for MetadataVersion

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

* cli: Remove version from metadata and use the one from file_or_url

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

* Fix clippy

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

* codegen: Decode metadata independently for different RPC calls

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-05-03 17:31:27 +03:00
committed by GitHub
parent f4eb80e78d
commit 432e856c37
33 changed files with 13959 additions and 8390 deletions
+124 -7
View File
@@ -5,7 +5,8 @@
//! Utility functions for metadata validation.
use frame_metadata::v15::{
ExtrinsicMetadata, PalletMetadata, RuntimeMetadataV15, StorageEntryMetadata, StorageEntryType,
ExtrinsicMetadata, PalletMetadata, RuntimeApiMetadata, RuntimeApiMethodMetadata,
RuntimeMetadataV15, StorageEntryMetadata, StorageEntryType,
};
use scale_info::{form::PortableForm, Field, PortableRegistry, TypeDef, Variant};
use std::collections::HashSet;
@@ -253,7 +254,7 @@ pub fn get_storage_hash(
.pallets
.iter()
.find(|p| p.name == pallet_name)
.ok_or(NotFound::Pallet)?;
.ok_or(NotFound::Root)?;
let storage = pallet.storage.as_ref().ok_or(NotFound::Item)?;
@@ -277,7 +278,7 @@ pub fn get_constant_hash(
.pallets
.iter()
.find(|p| p.name == pallet_name)
.ok_or(NotFound::Pallet)?;
.ok_or(NotFound::Root)?;
let constant = pallet
.constants
@@ -300,7 +301,7 @@ pub fn get_call_hash(
.pallets
.iter()
.find(|p| p.name == pallet_name)
.ok_or(NotFound::Pallet)?;
.ok_or(NotFound::Root)?;
let call_id = pallet.calls.as_ref().ok_or(NotFound::Item)?.ty.id;
@@ -321,6 +322,96 @@ pub fn get_call_hash(
Ok(hash)
}
fn get_runtime_method_hash(
metadata: &RuntimeMetadataV15,
trait_metadata: &RuntimeApiMetadata<PortableForm>,
method_metadata: &RuntimeApiMethodMetadata<PortableForm>,
visited_ids: &mut HashSet<u32>,
) -> [u8; 32] {
// The trait name is part of the runtime API call that is being
// generated for this method. Therefore the trait name is strongly
// connected to the method in the same way as a parameter is
// to the method.
let mut bytes = hash(trait_metadata.name.as_bytes());
bytes = xor(bytes, hash(method_metadata.name.as_bytes()));
for input in &method_metadata.inputs {
bytes = xor(bytes, hash(input.name.as_bytes()));
bytes = xor(
bytes,
get_type_hash(&metadata.types, input.ty.id, visited_ids),
);
}
bytes = xor(
bytes,
get_type_hash(&metadata.types, method_metadata.output.id, visited_ids),
);
bytes
}
/// Obtain the hash of a specific runtime trait.
pub fn get_runtime_trait_hash(
metadata: &RuntimeMetadataV15,
trait_metadata: &RuntimeApiMetadata<PortableForm>,
) -> [u8; 32] {
// Start out with any hash, the trait name is already part of the
// runtime method hash.
let mut bytes = hash(trait_metadata.name.as_bytes());
let mut visited_ids = HashSet::new();
let mut methods: Vec<_> = trait_metadata
.methods
.iter()
.map(|method_metadata| {
let bytes = get_runtime_method_hash(
metadata,
trait_metadata,
method_metadata,
&mut visited_ids,
);
(&*method_metadata.name, bytes)
})
.collect();
// Sort by method name to create a deterministic representation of the underlying metadata.
methods.sort_by_key(|&(name, _hash)| name);
// Note: Hash already takes into account the method name.
for (_, hash) in methods {
bytes = xor(bytes, hash);
}
bytes
}
/// 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,
method_name: &str,
) -> Result<[u8; 32], NotFound> {
let trait_metadata = metadata
.apis
.iter()
.find(|m| m.name == trait_name)
.ok_or(NotFound::Root)?;
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,
method_metadata,
&mut HashSet::new(),
))
}
/// Obtain the hash representation of a `frame_metadata::v15::PalletMetadata`.
pub fn get_pallet_hash(
registry: &PortableRegistry,
@@ -404,6 +495,19 @@ pub fn get_metadata_hash(metadata: &RuntimeMetadataV15) -> [u8; 32] {
&mut visited_ids,
));
let mut apis: Vec<_> = metadata
.apis
.iter()
.map(|api| (&*api.name, get_runtime_trait_hash(metadata, api)))
.collect();
// Sort the runtime APIs by trait name to provide a deterministic output.
apis.sort_by_key(|&(name, _hash)| name);
for (_, hash) in apis.iter() {
bytes.extend(hash)
}
hash(&bytes)
}
@@ -449,11 +553,24 @@ pub fn get_metadata_per_pallet_hash<T: AsRef<str>>(
hash(&bytes)
}
/// An error returned if we attempt to get the hash for a specific call, constant
/// or storage item that doesn't exist.
/// 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 {
Pallet,
/// 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,
}