Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add Runtime API to execute runtime calls #777

Merged
merged 8 commits into from
Jan 18, 2023
Merged
Show file tree
Hide file tree
Changes from 7 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions subxt/src/blocks/block_types.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ use crate::{
},
events,
rpc::types::ChainBlockResponse,
runtime_api::RuntimeApi,
};
use derivative::Derivative;
use futures::lock::Mutex as AsyncMutex;
Expand Down Expand Up @@ -89,6 +90,11 @@ where
self.cached_events.clone(),
))
}

/// Execute a runtime API call at this block.
pub async fn runtime_api(&self) -> Result<RuntimeApi<T, C>, Error> {
Ok(RuntimeApi::new(self.client.clone(), self.hash()))
}
}

/// The body of a block.
Expand Down
6 changes: 6 additions & 0 deletions subxt/src/client/offline_client.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ use crate::{
constants::ConstantsClient,
events::EventsClient,
rpc::types::RuntimeVersion,
runtime_api::RuntimeApiClient,
storage::StorageClient,
tx::TxClient,
Config,
Expand Down Expand Up @@ -49,6 +50,11 @@ pub trait OfflineClientT<T: Config>: Clone + Send + Sync + 'static {
fn blocks(&self) -> BlocksClient<T, Self> {
BlocksClient::new(self.clone())
}

/// Work with runtime API.
fn runtime_api(&self) -> RuntimeApiClient<T, Self> {
RuntimeApiClient::new(self.clone())
}
}

/// A client that is capable of performing offline-only operations.
Expand Down
22 changes: 21 additions & 1 deletion subxt/src/client/online_client.rs
Original file line number Diff line number Diff line change
Expand Up @@ -19,12 +19,18 @@ use crate::{
Rpc,
RpcClientT,
},
runtime_api::RuntimeApiClient,
storage::StorageClient,
tx::TxClient,
Config,
Metadata,
};
use codec::{
Compact,
Decode,
};
use derivative::Derivative;
use frame_metadata::RuntimeMetadataPrefixed;
use futures::future;
use parking_lot::RwLock;
use std::sync::Arc;
Expand Down Expand Up @@ -95,7 +101,7 @@ impl<T: Config> OnlineClient<T> {
let (genesis_hash, runtime_version, metadata) = future::join3(
rpc.genesis_hash(),
rpc.runtime_version(None),
rpc.metadata(None),
OnlineClient::fetch_metadata(&rpc),
)
.await;

Expand All @@ -109,6 +115,15 @@ 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 bytes = rpc.state_call("Metadata_metadata", None, None).await?;
let cursor = &mut &*bytes;
let _ = <Compact<u32>>::decode(cursor)?;
let meta: RuntimeMetadataPrefixed = Decode::decode(cursor)?;
Ok(meta.try_into()?)
}

/// Create an object which can be used to keep the runtime up to date
/// in a separate thread.
///
Expand Down Expand Up @@ -214,6 +229,11 @@ impl<T: Config> OnlineClient<T> {
pub fn blocks(&self) -> BlocksClient<T, Self> {
<Self as OfflineClientT<T>>::blocks(self)
}

/// Work with runtime API.
pub fn runtime_api(&self) -> RuntimeApiClient<T, Self> {
<Self as OfflineClientT<T>>::runtime_api(self)
}
}

impl<T: Config> OfflineClientT<T> for OnlineClient<T> {
Expand Down
1 change: 1 addition & 0 deletions subxt/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -155,6 +155,7 @@ pub mod error;
pub mod events;
pub mod metadata;
pub mod rpc;
pub mod runtime_api;
pub mod storage;
pub mod tx;
pub mod utils;
Expand Down
19 changes: 19 additions & 0 deletions subxt/src/rpc/rpc.rs
Original file line number Diff line number Diff line change
Expand Up @@ -371,6 +371,25 @@ impl<T: Config> Rpc<T> {
Ok(xt_hash)
}

/// Execute a runtime API call.
pub async fn state_call(
&self,
function: &str,
call_parameters: Option<&[u8]>,
at: Option<T::Hash>,
) -> Result<types::Bytes, Error> {
let call_parameters = call_parameters.unwrap_or_default();

let bytes: types::Bytes = self
.client
.request(
"state_call",
rpc_params![function, to_hex(call_parameters), at],
)
.await?;
Ok(bytes)
}

/// Create and submit an extrinsic and return a subscription to the events triggered.
pub async fn watch_extrinsic<X: Encode>(
&self,
Expand Down
11 changes: 11 additions & 0 deletions subxt/src/runtime_api/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
// Copyright 2019-2022 Parity Technologies (UK) Ltd.
// This file is dual-licensed as Apache-2.0 or GPL-3.0.
// see LICENSE for license details.

//! Types associated with executing runtime API calls.

mod runtime_client;
mod runtime_types;

pub use runtime_client::RuntimeApiClient;
pub use runtime_types::RuntimeApi;
66 changes: 66 additions & 0 deletions subxt/src/runtime_api/runtime_client.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
// Copyright 2019-2022 Parity Technologies (UK) Ltd.
// This file is dual-licensed as Apache-2.0 or GPL-3.0.
// see LICENSE for license details.

use super::runtime_types::RuntimeApi;

use crate::{
client::OnlineClientT,
error::Error,
Config,
};
use derivative::Derivative;
use std::{
future::Future,
marker::PhantomData,
};

/// Execute runtime API calls.
#[derive(Derivative)]
#[derivative(Clone(bound = "Client: Clone"))]
pub struct RuntimeApiClient<T, Client> {
client: Client,
_marker: PhantomData<T>,
}

impl<T, Client> RuntimeApiClient<T, Client> {
/// Create a new [`RuntimeApiClient`]
pub fn new(client: Client) -> Self {
Self {
client,
_marker: PhantomData,
}
}
}

impl<T, Client> RuntimeApiClient<T, Client>
where
T: Config,
Client: OnlineClientT<T>,
{
/// Obtain a runtime API at some block hash.
pub fn at(
&self,
block_hash: Option<T::Hash>,
) -> impl Future<Output = Result<RuntimeApi<T, Client>, Error>> + Send + 'static {
// Clone and pass the client in like this so that we can explicitly
// return a Future that's Send + 'static, rather than tied to &self.
let client = self.client.clone();
async move {
// If block hash is not provided, get the hash
// for the latest block and use that.
let block_hash = match block_hash {
Some(hash) => hash,
None => {
client
.rpc()
.block_hash(None)
.await?
.expect("didn't pass a block number; qed")
niklasad1 marked this conversation as resolved.
Show resolved Hide resolved
lexnv marked this conversation as resolved.
Show resolved Hide resolved
}
};

Ok(RuntimeApi::new(client, block_hash))
}
}
}
59 changes: 59 additions & 0 deletions subxt/src/runtime_api/runtime_types.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
// Copyright 2019-2022 Parity Technologies (UK) Ltd.
// 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 derivative::Derivative;
use std::{
future::Future,
marker::PhantomData,
};

/// Execute runtime API calls.
#[derive(Derivative)]
#[derivative(Clone(bound = "Client: Clone"))]
pub struct RuntimeApi<T: Config, Client> {
client: Client,
block_hash: T::Hash,
_marker: PhantomData<T>,
}

impl<T: Config, Client> RuntimeApi<T, Client> {
/// Create a new [`RuntimeApi`]
pub(crate) fn new(client: Client, block_hash: T::Hash) -> Self {
Self {
client,
block_hash,
_marker: PhantomData,
}
}
}

impl<T, Client> RuntimeApi<T, Client>
where
T: Config,
Client: OnlineClientT<T>,
{
/// Execute a raw runtime API call.
pub fn call_raw<'a>(
&self,
function: &'a str,
call_parameters: Option<&'a [u8]>,
) -> impl Future<Output = Result<Vec<u8>, Error>> + 'a {
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 data = client
.rpc()
.state_call(function, call_parameters, Some(block_hash))
.await?;
Ok(data.0)
}
}
}
32 changes: 32 additions & 0 deletions testing/integration-tests/src/blocks/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,11 @@
// see LICENSE for license details.

use crate::test_context;
use codec::{
Compact,
Decode,
};
use frame_metadata::RuntimeMetadataPrefixed;
use futures::StreamExt;

// Check that we can subscribe to non-finalized blocks.
Expand Down Expand Up @@ -87,3 +92,30 @@ async fn missing_block_headers_will_be_filled_in() -> Result<(), subxt::Error> {
assert!(last_block_number.is_some());
Ok(())
}

// Check that we can subscribe to non-finalized blocks.
#[tokio::test]
async fn runtime_api_call() -> Result<(), subxt::Error> {
let ctx = test_context().await;
let api = ctx.client();

let mut sub = api.blocks().subscribe_best().await?;

let block = sub.next().await.unwrap()?;
let rt = block.runtime_api().await?;

let bytes = rt.call_raw("Metadata_metadata", None).await?;
let cursor = &mut &*bytes;
let _ = <Compact<u32>>::decode(cursor)?;
let meta: RuntimeMetadataPrefixed = Decode::decode(cursor)?;
let metadata_call = match meta.1 {
frame_metadata::RuntimeMetadata::V14(metadata) => metadata,
_ => panic!("Metadata V14 unavailable"),
};

// Compare the runtime API call against the `state_getMetadata`.
let metadata = api.rpc().metadata(None).await?;
let metadata = metadata.runtime_metadata();
assert_eq!(&metadata_call, metadata);
Ok(())
}
31 changes: 31 additions & 0 deletions testing/integration-tests/src/client/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,11 @@ use crate::{
wait_for_blocks,
},
};
use codec::{
Compact,
Decode,
};
use frame_metadata::RuntimeMetadataPrefixed;
use sp_core::{
sr25519::Pair as Sr25519Pair,
storage::well_known_keys,
Expand Down Expand Up @@ -250,3 +255,29 @@ async fn unsigned_extrinsic_is_same_shape_as_polkadotjs() {
// Make sure our encoding is the same as the encoding polkadot UI created.
assert_eq!(actual_tx_bytes, expected_tx_bytes);
}

#[tokio::test]
async fn rpc_state_call() {
let ctx = test_context().await;
let api = ctx.client();

// Call into the runtime of the chain to get the Metadata.
let metadata_bytes = api
.rpc()
.state_call("Metadata_metadata", None, None)
.await
.unwrap();

let cursor = &mut &*metadata_bytes;
let _ = <Compact<u32>>::decode(cursor).unwrap();
let meta: RuntimeMetadataPrefixed = Decode::decode(cursor).unwrap();
let metadata_call = match meta.1 {
frame_metadata::RuntimeMetadata::V14(metadata) => metadata,
_ => panic!("Metadata V14 unavailable"),
};

// Compare the runtime API call against the `state_getMetadata`.
let metadata = api.rpc().metadata(None).await.unwrap();
let metadata = metadata.runtime_metadata();
assert_eq!(&metadata_call, metadata);
}