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
+14 -7
View File
@@ -17,9 +17,7 @@ use crate::{
tx::TxClient,
Config, Metadata,
};
use codec::Compact;
use derivative::Derivative;
use frame_metadata::RuntimeMetadataPrefixed;
use futures::future;
use parking_lot::RwLock;
use std::sync::Arc;
@@ -136,10 +134,19 @@ impl<T: Config> OnlineClient<T> {
/// Fetch the metadata from substrate using the runtime API.
async fn fetch_metadata(rpc: &Rpc<T>) -> Result<Metadata, Error> {
let (_, meta) = rpc
.state_call::<(Compact<u32>, RuntimeMetadataPrefixed)>("Metadata_metadata", None, None)
.await?;
Ok(meta.try_into()?)
#[cfg(feature = "unstable-metadata")]
{
// Try to fetch the latest unstable metadata, if that fails fall back to
// fetching the latest stable metadata.
const V15_METADATA_VERSION: u32 = u32::MAX;
match rpc.metadata_at_version(V15_METADATA_VERSION).await {
Ok(bytes) => Ok(bytes),
Err(_) => rpc.metadata().await,
}
}
#[cfg(not(feature = "unstable-metadata"))]
rpc.metadata().await
}
/// Create an object which can be used to keep the runtime up to date
@@ -383,7 +390,7 @@ impl<T: Config> RuntimeUpdaterStream<T> {
Err(err) => return Some(Err(err)),
};
let metadata = match self.client.rpc().metadata(None).await {
let metadata = match self.client.rpc().metadata().await {
Ok(metadata) => metadata,
Err(err) => return Some(Err(err)),
};
+3
View File
@@ -28,6 +28,9 @@ pub use crate::constants::dynamic as constant;
// Lookup storage values dynamically.
pub use crate::storage::{dynamic as storage, dynamic_root as storage_root};
// Execute runtime API function call dynamically.
pub use crate::runtime_api::dynamic as runtime_api_call;
/// This is the result of making a dynamic request to a node. From this,
/// we can return the raw SCALE bytes that we were handed back, or we can
/// complete the decoding of the bytes into a [`DecodedValue`] type.
+1 -1
View File
@@ -76,7 +76,7 @@ impl<T: Config> Events<T> {
/// .await?
/// .expect("didn't pass a block number; qed");
/// // Fetch the metadata of the given block.
/// let metadata = client.rpc().metadata(Some(block_hash)).await?;
/// let metadata = client.rpc().metadata_legacy(Some(block_hash)).await?;
/// // Fetch the events from the client.
/// let events = Events::new_from_client(metadata, block_hash, client);
/// # Ok(())
+12 -13
View File
@@ -5,23 +5,23 @@
use parking_lot::RwLock;
use std::{borrow::Cow, collections::HashMap};
/// A cache with the simple goal of storing 32 byte hashes against pallet+item keys
/// A cache with the simple goal of storing 32 byte hashes against root+item keys
#[derive(Default, Debug)]
pub struct HashCache {
inner: RwLock<HashMap<PalletItemKey<'static>, [u8; 32]>>,
inner: RwLock<HashMap<RootItemKey<'static>, [u8; 32]>>,
}
impl HashCache {
/// get a hash out of the cache by its pallet and item key. If the item doesn't exist,
/// get a hash out of the cache by its root and item key. If the item doesn't exist,
/// run the function provided to obtain a hash to insert (or bail with some error on failure).
pub fn get_or_insert<F, E>(&self, pallet: &str, item: &str, f: F) -> Result<[u8; 32], E>
pub fn get_or_insert<F, E>(&self, root: &str, item: &str, f: F) -> Result<[u8; 32], E>
where
F: FnOnce() -> Result<[u8; 32], E>,
{
let maybe_hash = self
.inner
.read()
.get(&PalletItemKey::new(pallet, item))
.get(&RootItemKey::new(root, item))
.copied();
if let Some(hash) = maybe_hash {
@@ -29,10 +29,9 @@ impl HashCache {
}
let hash = f()?;
self.inner.write().insert(
PalletItemKey::new(pallet.to_string(), item.to_string()),
hash,
);
self.inner
.write()
.insert(RootItemKey::new(root.to_string(), item.to_string()), hash);
Ok(hash)
}
@@ -41,14 +40,14 @@ impl HashCache {
/// This exists so that we can look items up in the cache using &strs, without having to allocate
/// Strings first (as you'd have to do to construct something like an `&(String,String)` key).
#[derive(Debug, PartialEq, Eq, Hash)]
struct PalletItemKey<'a> {
struct RootItemKey<'a> {
pallet: Cow<'a, str>,
item: Cow<'a, str>,
}
impl<'a> PalletItemKey<'a> {
impl<'a> RootItemKey<'a> {
fn new(pallet: impl Into<Cow<'a, str>>, item: impl Into<Cow<'a, str>>) -> Self {
PalletItemKey {
RootItemKey {
pallet: pallet.into(),
item: item.into(),
}
@@ -75,7 +74,7 @@ mod tests {
cache
.inner
.read()
.get(&PalletItemKey::new(pallet, item))
.get(&RootItemKey::new(pallet, item))
.unwrap(),
&value.unwrap()
);
+116 -3
View File
@@ -30,6 +30,9 @@ pub enum MetadataError {
/// Event is not in metadata.
#[error("Pallet {0}, Error {0} not found")]
ErrorNotFound(u8, u8),
/// Runtime function is not in metadata.
#[error("Runtime function not found")]
RuntimeFnNotFound,
/// Storage is not in metadata.
#[error("Storage not found")]
StorageNotFound,
@@ -57,6 +60,9 @@ pub enum MetadataError {
/// Runtime storage metadata is incompatible with the static one.
#[error("Pallet {0} Storage {0} has incompatible metadata")]
IncompatibleStorageMetadata(String, String),
/// Runtime API metadata is incompatible with the static one.
#[error("Runtime API Trait {0} Method {0} has incompatible metadata")]
IncompatibleRuntimeApiMetadata(String, String),
/// Runtime metadata is not fully compatible with the static one.
#[error("Node metadata is not fully compatible")]
IncompatibleMetadata,
@@ -79,6 +85,9 @@ struct MetadataInner {
// an extrinsic fails.
dispatch_error_ty: Option<u32>,
// Runtime API metadata
runtime_apis: HashMap<String, RuntimeFnMetadata>,
// The hashes uniquely identify parts of the metadata; different
// hashes mean some type difference exists between static and runtime
// versions. We cache them here to avoid recalculating:
@@ -86,6 +95,7 @@ struct MetadataInner {
cached_call_hashes: HashCache,
cached_constant_hashes: HashCache,
cached_storage_hashes: HashCache,
cached_runtime_hashes: HashCache,
}
/// A representation of the runtime metadata received from a node.
@@ -95,6 +105,14 @@ pub struct Metadata {
}
impl Metadata {
/// Returns a reference to [`RuntimeFnMetadata`].
pub fn runtime_fn(&self, name: &str) -> Result<&RuntimeFnMetadata, MetadataError> {
self.inner
.runtime_apis
.get(name)
.ok_or(MetadataError::RuntimeFnNotFound)
}
/// Returns a reference to [`PalletMetadata`].
pub fn pallet(&self, name: &str) -> Result<&PalletMetadata, MetadataError> {
self.inner
@@ -158,7 +176,7 @@ impl Metadata {
.get_or_insert(pallet, storage, || {
subxt_metadata::get_storage_hash(&self.inner.metadata, pallet, storage).map_err(
|e| match e {
subxt_metadata::NotFound::Pallet => MetadataError::PalletNotFound,
subxt_metadata::NotFound::Root => MetadataError::PalletNotFound,
subxt_metadata::NotFound::Item => MetadataError::StorageNotFound,
},
)
@@ -172,7 +190,7 @@ impl Metadata {
.get_or_insert(pallet, constant, || {
subxt_metadata::get_constant_hash(&self.inner.metadata, pallet, constant).map_err(
|e| match e {
subxt_metadata::NotFound::Pallet => MetadataError::PalletNotFound,
subxt_metadata::NotFound::Root => MetadataError::PalletNotFound,
subxt_metadata::NotFound::Item => MetadataError::ConstantNotFound,
},
)
@@ -186,13 +204,27 @@ impl Metadata {
.get_or_insert(pallet, function, || {
subxt_metadata::get_call_hash(&self.inner.metadata, pallet, function).map_err(|e| {
match e {
subxt_metadata::NotFound::Pallet => MetadataError::PalletNotFound,
subxt_metadata::NotFound::Root => MetadataError::PalletNotFound,
subxt_metadata::NotFound::Item => MetadataError::CallNotFound,
}
})
})
}
/// Obtain the unique hash for a runtime API function.
pub fn runtime_api_hash(
&self,
trait_name: &str,
method_name: &str,
) -> Result<[u8; 32], MetadataError> {
self.inner
.cached_runtime_hashes
.get_or_insert(trait_name, method_name, || {
subxt_metadata::get_runtime_api_hash(&self.inner.metadata, trait_name, method_name)
.map_err(|_| MetadataError::RuntimeFnNotFound)
})
}
/// Obtain the unique hash for this metadata.
pub fn metadata_hash<T: AsRef<str>>(&self, pallets: &[T]) -> [u8; 32] {
if let Some(hash) = *self.inner.cached_metadata_hash.read() {
@@ -206,6 +238,42 @@ impl Metadata {
}
}
/// Metadata for a specific runtime API function.
#[derive(Clone, Debug)]
pub struct RuntimeFnMetadata {
/// The trait name of the runtime function.
trait_name: String,
/// The method name of the runtime function.
method_name: String,
/// The parameter name and type IDs interpreted as `scale_info::Field`
/// for ease of decoding.
fields: Vec<scale_info::Field<scale_info::form::PortableForm>>,
/// The type ID of the return type.
return_id: u32,
}
impl RuntimeFnMetadata {
/// Get the parameters as fields.
pub fn fields(&self) -> &[scale_info::Field<scale_info::form::PortableForm>] {
&self.fields
}
/// Return the trait name of the runtime function.
pub fn trait_name(&self) -> &str {
&self.trait_name
}
/// Return the method name of the runtime function.
pub fn method_name(&self) -> &str {
&self.method_name
}
/// Get the type ID of the return type.
pub fn return_id(&self) -> u32 {
self.return_id
}
}
/// Metadata for a specific pallet.
#[derive(Clone, Debug)]
pub struct PalletMetadata {
@@ -376,6 +444,49 @@ impl TryFrom<RuntimeMetadataPrefixed> for Metadata {
_ => return Err(InvalidMetadataError::InvalidVersion),
};
let runtime_apis: HashMap<String, RuntimeFnMetadata> = metadata
.apis
.iter()
.flat_map(|trait_metadata| {
let trait_name = &trait_metadata.name;
trait_metadata
.methods
.iter()
.map(|method_metadata| {
// Function named used by substrate to identify the runtime call.
let fn_name = format!("{}_{}", trait_name, method_metadata.name);
// Parameters mapped as `scale_info::Field` to allow dynamic decoding.
let fields: Vec<_> = method_metadata
.inputs
.iter()
.map(|input| {
let name = input.name.clone();
let ty = input.ty.id;
scale_info::Field {
name: Some(name),
ty: ty.into(),
type_name: None,
docs: Default::default(),
}
})
.collect();
let return_id = method_metadata.output.id;
let metadata = RuntimeFnMetadata {
fields,
return_id,
trait_name: trait_name.clone(),
method_name: method_metadata.name.clone(),
};
(fn_name, metadata)
})
.collect::<Vec<_>>()
})
.collect();
let get_type_def_variant = |type_id: u32| {
let ty = metadata
.types
@@ -492,10 +603,12 @@ impl TryFrom<RuntimeMetadataPrefixed> for Metadata {
events,
errors,
dispatch_error_ty,
runtime_apis,
cached_metadata_hash: Default::default(),
cached_call_hashes: Default::default(),
cached_constant_hashes: Default::default(),
cached_storage_hashes: Default::default(),
cached_runtime_hashes: Default::default(),
}),
})
}
+48 -5
View File
@@ -143,8 +143,8 @@ impl<T: Config> Rpc<T> {
genesis_hash.ok_or_else(|| "Genesis hash not found".into())
}
/// Fetch the metadata
pub async fn metadata(&self, at: Option<T::Hash>) -> Result<Metadata, Error> {
/// Fetch the metadata via the legacy `state_getMetadata` RPC method.
pub async fn metadata_legacy(&self, at: Option<T::Hash>) -> Result<Metadata, Error> {
let bytes: types::Bytes = self
.client
.request("state_getMetadata", rpc_params![at])
@@ -347,13 +347,13 @@ impl<T: Config> Rpc<T> {
Ok(xt_hash)
}
/// Execute a runtime API call.
pub async fn state_call<Res: Decode>(
/// Execute a runtime API call via `state_call` RPC method.
pub async fn state_call_raw(
&self,
function: &str,
call_parameters: Option<&[u8]>,
at: Option<T::Hash>,
) -> Result<Res, Error> {
) -> Result<types::Bytes, Error> {
let call_parameters = call_parameters.unwrap_or_default();
let bytes: types::Bytes = self
.client
@@ -362,11 +362,54 @@ impl<T: Config> Rpc<T> {
rpc_params![function, to_hex(call_parameters), at],
)
.await?;
Ok(bytes)
}
/// Execute a runtime API call and decode the result.
pub async fn state_call<Res: Decode>(
&self,
function: &str,
call_parameters: Option<&[u8]>,
at: Option<T::Hash>,
) -> Result<Res, Error> {
let bytes = self.state_call_raw(function, call_parameters, at).await?;
let cursor = &mut &bytes[..];
let res: Res = Decode::decode(cursor)?;
Ok(res)
}
/// Execute runtime API call and return the specified runtime metadata version.
pub async fn metadata_at_version(&self, version: u32) -> Result<Metadata, Error> {
let param = version.encode();
let opaque: Option<frame_metadata::OpaqueMetadata> = self
.state_call("Metadata_metadata_at_version", Some(&param), None)
.await?;
let bytes = opaque.ok_or(Error::Other("Metadata version not found".into()))?;
let meta: RuntimeMetadataPrefixed = Decode::decode(&mut &bytes.0[..])?;
let metadata: Metadata = meta.try_into()?;
Ok(metadata)
}
/// Execute a runtime API call into `Metadata_metadata` method
/// to fetch the latest available metadata.
///
/// # Note
///
/// This returns the same output as [`Self::metadata`], but calls directly
/// into the runtime.
pub async fn metadata(&self) -> Result<Metadata, Error> {
let bytes: frame_metadata::OpaqueMetadata =
self.state_call("Metadata_metadata", None, None).await?;
let meta: RuntimeMetadataPrefixed = Decode::decode(&mut &bytes.0[..])?;
let metadata: Metadata = meta.try_into()?;
Ok(metadata)
}
/// Create and submit an extrinsic and return a subscription to the events triggered.
pub async fn watch_extrinsic<X: Encode>(
&self,
+2
View File
@@ -5,7 +5,9 @@
//! Types associated with executing runtime API calls.
mod runtime_client;
mod runtime_payload;
mod runtime_types;
pub use runtime_client::RuntimeApiClient;
pub use runtime_payload::{dynamic, DynamicRuntimeApiPayload, Payload, RuntimeApiPayload};
pub use runtime_types::RuntimeApi;
+162
View File
@@ -0,0 +1,162 @@
// 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 core::marker::PhantomData;
use scale_encode::EncodeAsFields;
use scale_value::Composite;
use std::borrow::Cow;
use crate::dynamic::DecodedValueThunk;
use crate::{metadata::DecodeWithMetadata, Error, Metadata};
/// This represents a runtime API payload that can call into the runtime of node.
///
/// # Components
///
/// - associated return type
///
/// Resulting bytes of the call are interpreted into this type.
///
/// - runtime function name
///
/// The function name of the runtime API call. This is obtained by concatenating
/// the runtime trait name with the trait's method.
///
/// For example, the substrate runtime trait [Metadata](https://github.com/paritytech/substrate/blob/cb954820a8d8d765ce75021e244223a3b4d5722d/primitives/api/src/lib.rs#L745)
/// contains the `metadata_at_version` function. The corresponding runtime function
/// is `Metadata_metadata_at_version`.
///
/// - encoded arguments
///
/// Each argument of the runtime function must be scale-encoded.
pub trait RuntimeApiPayload {
/// The return type of the function call.
// Note: `DecodeWithMetadata` is needed to decode the function call result
// with the `subxt::Metadata.
type ReturnType: DecodeWithMetadata;
/// The runtime API function name.
fn fn_name(&self) -> &str;
/// Scale encode the arguments data.
fn encode_args_to(&self, metadata: &Metadata, out: &mut Vec<u8>) -> Result<(), Error>;
/// Encode arguments data and return the output. This is a convenience
/// wrapper around [`RuntimeApiPayload::encode_args_to`].
fn encode_args(&self, metadata: &Metadata) -> Result<Vec<u8>, Error> {
let mut v = Vec::new();
self.encode_args_to(metadata, &mut v)?;
Ok(v)
}
/// Returns the statically generated validation hash.
fn validation_hash(&self) -> Option<[u8; 32]> {
None
}
}
/// A runtime API payload containing the generic argument data
/// and interpreting the result of the call as `ReturnTy`.
///
/// This can be created from static values (ie those generated
/// via the `subxt` macro) or dynamic values via [`dynamic`].
#[derive(Clone, Debug)]
pub struct Payload<ArgsData, ReturnTy> {
fn_name: Cow<'static, str>,
args_data: ArgsData,
validation_hash: Option<[u8; 32]>,
_marker: PhantomData<ReturnTy>,
}
impl<ArgsData: EncodeAsFields, ReturnTy: DecodeWithMetadata> RuntimeApiPayload
for Payload<ArgsData, ReturnTy>
{
type ReturnType = ReturnTy;
fn fn_name(&self) -> &str {
&self.fn_name
}
fn encode_args_to(&self, metadata: &Metadata, out: &mut Vec<u8>) -> Result<(), Error> {
let fn_metadata = metadata.runtime_fn(&self.fn_name)?;
self.args_data
.encode_as_fields_to(fn_metadata.fields(), metadata.types(), out)?;
Ok(())
}
fn validation_hash(&self) -> Option<[u8; 32]> {
self.validation_hash
}
}
/// A dynamic runtime API payload.
pub type DynamicRuntimeApiPayload = Payload<Composite<()>, DecodedValueThunk>;
impl<ReturnTy, ArgsData> Payload<ArgsData, ReturnTy> {
/// Create a new [`Payload`].
pub fn new(
fn_name: impl Into<String>,
args_data: ArgsData,
validation_hash: Option<[u8; 32]>,
) -> Self {
Payload {
fn_name: Cow::Owned(fn_name.into()),
args_data,
validation_hash,
_marker: PhantomData,
}
}
/// Create a new static [`Payload`] using static function name
/// and scale-encoded argument data.
///
/// This is only expected to be used from codegen.
#[doc(hidden)]
pub fn new_static(
fn_name: &'static str,
args_data: ArgsData,
hash: [u8; 32],
) -> Payload<ArgsData, ReturnTy> {
Payload {
fn_name: Cow::Borrowed(fn_name),
args_data,
validation_hash: Some(hash),
_marker: std::marker::PhantomData,
}
}
/// Do not validate this call prior to submitting it.
pub fn unvalidated(self) -> Self {
Self {
validation_hash: None,
..self
}
}
/// Returns the function name.
pub fn fn_name(&self) -> &str {
&self.fn_name
}
/// Returns the arguments data.
pub fn args_data(&self) -> &ArgsData {
&self.args_data
}
}
/// Create a new [`DynamicRuntimeApiPayload`].
pub fn dynamic(
fn_name: impl Into<String>,
args_data: impl Into<Composite<()>>,
hash: Option<[u8; 32]>,
) -> DynamicRuntimeApiPayload {
DynamicRuntimeApiPayload {
fn_name: Cow::Owned(fn_name.into()),
args_data: args_data.into(),
validation_hash: hash,
_marker: std::marker::PhantomData,
}
}
+56 -1
View File
@@ -2,11 +2,13 @@
// This file is dual-licensed as Apache-2.0 or GPL-3.0.
// see LICENSE for license details.
use crate::{client::OnlineClientT, error::Error, Config};
use crate::{client::OnlineClientT, error::Error, metadata::DecodeWithMetadata, Config};
use codec::Decode;
use derivative::Derivative;
use std::{future::Future, marker::PhantomData};
use super::RuntimeApiPayload;
/// Execute runtime API calls.
#[derive(Derivative)]
#[derivative(Clone(bound = "Client: Clone"))]
@@ -50,4 +52,57 @@ where
Ok(data)
}
}
/// Execute a runtime API call.
pub fn call<Call: RuntimeApiPayload>(
&self,
payload: Call,
) -> impl Future<Output = Result<Call::ReturnType, Error>> {
let client = self.client.clone();
let block_hash = self.block_hash;
// Ensure that the returned future doesn't have a lifetime tied to api.runtime_api(),
// which is a temporary thing we'll be throwing away quickly:
async move {
let metadata = client.metadata();
let function = payload.fn_name();
// Check if the function is present in the runtime metadata.
let fn_metadata = metadata.runtime_fn(function)?;
// Return type ID used for dynamic decoding.
let return_id = fn_metadata.return_id();
// Validate the runtime API payload hash against the compile hash from codegen.
if let Some(static_hash) = payload.validation_hash() {
let runtime_hash = metadata
.runtime_api_hash(fn_metadata.trait_name(), fn_metadata.method_name())?;
if static_hash != runtime_hash {
return Err(
crate::metadata::MetadataError::IncompatibleRuntimeApiMetadata(
fn_metadata.trait_name().into(),
fn_metadata.method_name().into(),
)
.into(),
);
}
}
// Encode the arguments of the runtime call.
// For static payloads (codegen) this is pass-through, bytes are not altered.
// For dynamic payloads this relies on `scale_value::encode_as_fields_to`.
let params = payload.encode_args(&metadata)?;
let bytes = client
.rpc()
.state_call_raw(function, Some(params.as_slice()), Some(block_hash))
.await?;
let value = <Call::ReturnType as DecodeWithMetadata>::decode_with_metadata(
&mut &bytes[..],
return_id,
&metadata,
)?;
Ok(value)
}
}
}