From 78ec57c6a1b44fd143dc4e194b0902f692b949dd Mon Sep 17 00:00:00 2001 From: Alexandru Vasile Date: Fri, 13 Jan 2023 09:15:54 +0000 Subject: [PATCH 1/8] rpc: Add RuntimeAPI call via `state_call` method Signed-off-by: Alexandru Vasile --- subxt/src/rpc/rpc.rs | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/subxt/src/rpc/rpc.rs b/subxt/src/rpc/rpc.rs index b8d1c1b486..7213af8168 100644 --- a/subxt/src/rpc/rpc.rs +++ b/subxt/src/rpc/rpc.rs @@ -371,6 +371,25 @@ impl Rpc { Ok(xt_hash) } + /// Execute a runtime API call. + pub async fn state_call( + &self, + function: String, + call_parameters: Option<&[u8]>, + at: Option, + ) -> Result { + 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( &self, From c00330e19f391515198e4b42a6d82217d606fc8c Mon Sep 17 00:00:00 2001 From: Alexandru Vasile Date: Fri, 13 Jan 2023 10:08:36 +0000 Subject: [PATCH 2/8] subxt: Add runtime API Signed-off-by: Alexandru Vasile --- subxt/src/lib.rs | 1 + subxt/src/runtime_api/mod.rs | 11 +++++ subxt/src/runtime_api/runtime_client.rs | 66 +++++++++++++++++++++++++ subxt/src/runtime_api/runtime_types.rs | 59 ++++++++++++++++++++++ 4 files changed, 137 insertions(+) create mode 100644 subxt/src/runtime_api/mod.rs create mode 100644 subxt/src/runtime_api/runtime_client.rs create mode 100644 subxt/src/runtime_api/runtime_types.rs diff --git a/subxt/src/lib.rs b/subxt/src/lib.rs index b9b46fb85c..4a37a59751 100644 --- a/subxt/src/lib.rs +++ b/subxt/src/lib.rs @@ -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; diff --git a/subxt/src/runtime_api/mod.rs b/subxt/src/runtime_api/mod.rs new file mode 100644 index 0000000000..7b55a3905a --- /dev/null +++ b/subxt/src/runtime_api/mod.rs @@ -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; diff --git a/subxt/src/runtime_api/runtime_client.rs b/subxt/src/runtime_api/runtime_client.rs new file mode 100644 index 0000000000..f7b367eed9 --- /dev/null +++ b/subxt/src/runtime_api/runtime_client.rs @@ -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 { + client: Client, + _marker: PhantomData, +} + +impl RuntimeApiClient { + /// Create a new [`RuntimeApiClient`] + pub fn new(client: Client) -> Self { + Self { + client, + _marker: PhantomData, + } + } +} + +impl RuntimeApiClient +where + T: Config, + Client: OnlineClientT, +{ + /// Obtain a runtime API at some block hash. + pub fn at( + &self, + block_hash: Option, + ) -> impl Future, 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") + } + }; + + Ok(RuntimeApi::new(client, block_hash)) + } + } +} diff --git a/subxt/src/runtime_api/runtime_types.rs b/subxt/src/runtime_api/runtime_types.rs new file mode 100644 index 0000000000..a4c15d6e6c --- /dev/null +++ b/subxt/src/runtime_api/runtime_types.rs @@ -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 { + client: Client, + block_hash: T::Hash, + _marker: PhantomData, +} + +impl RuntimeApi { + /// Create a new [`RuntimeApi`] + pub(crate) fn new(client: Client, block_hash: T::Hash) -> Self { + Self { + client, + block_hash, + _marker: PhantomData, + } + } +} + +impl RuntimeApi +where + T: Config, + Client: OnlineClientT, +{ + /// Execute a raw runtime API call. + pub fn call_raw<'a>( + &self, + function: String, + call_parameters: Option<&'a [u8]>, + ) -> impl Future, 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) + } + } +} From 5e0bb44fe971ece75077c82d01c634c8cc513795 Mon Sep 17 00:00:00 2001 From: Alexandru Vasile Date: Fri, 13 Jan 2023 10:16:04 +0000 Subject: [PATCH 3/8] Expose the RuntimeAPI client Signed-off-by: Alexandru Vasile --- subxt/src/blocks/block_types.rs | 6 ++++++ subxt/src/client/offline_client.rs | 6 ++++++ subxt/src/client/online_client.rs | 6 ++++++ 3 files changed, 18 insertions(+) diff --git a/subxt/src/blocks/block_types.rs b/subxt/src/blocks/block_types.rs index 9adba3b8ea..a05c8d40c4 100644 --- a/subxt/src/blocks/block_types.rs +++ b/subxt/src/blocks/block_types.rs @@ -18,6 +18,7 @@ use crate::{ }, events, rpc::types::ChainBlockResponse, + runtime_api::RuntimeApi, }; use derivative::Derivative; use futures::lock::Mutex as AsyncMutex; @@ -89,6 +90,11 @@ where self.cached_events.clone(), )) } + + /// Execute a runtime API calls at this block. + pub async fn runtime_api(&self) -> Result, Error> { + Ok(RuntimeApi::new(self.client.clone(), self.hash())) + } } /// The body of a block. diff --git a/subxt/src/client/offline_client.rs b/subxt/src/client/offline_client.rs index 4055787f98..c2681102f5 100644 --- a/subxt/src/client/offline_client.rs +++ b/subxt/src/client/offline_client.rs @@ -7,6 +7,7 @@ use crate::{ constants::ConstantsClient, events::EventsClient, rpc::types::RuntimeVersion, + runtime_api::RuntimeApiClient, storage::StorageClient, tx::TxClient, Config, @@ -49,6 +50,11 @@ pub trait OfflineClientT: Clone + Send + Sync + 'static { fn blocks(&self) -> BlocksClient { BlocksClient::new(self.clone()) } + + /// Work with runtime API. + fn runtime_api(&self) -> RuntimeApiClient { + RuntimeApiClient::new(self.clone()) + } } /// A client that is capable of performing offline-only operations. diff --git a/subxt/src/client/online_client.rs b/subxt/src/client/online_client.rs index 0fea1591a4..e2a113b63c 100644 --- a/subxt/src/client/online_client.rs +++ b/subxt/src/client/online_client.rs @@ -19,6 +19,7 @@ use crate::{ Rpc, RpcClientT, }, + runtime_api::RuntimeApiClient, storage::StorageClient, tx::TxClient, Config, @@ -214,6 +215,11 @@ impl OnlineClient { pub fn blocks(&self) -> BlocksClient { >::blocks(self) } + + /// Work with runtime API. + pub fn runtime_api(&self) -> RuntimeApiClient { + >::runtime_api(self) + } } impl OfflineClientT for OnlineClient { From d42791a74aa8861a1bee0f89180f5f5d2c396ade Mon Sep 17 00:00:00 2001 From: Alexandru Vasile Date: Fri, 13 Jan 2023 12:10:10 +0000 Subject: [PATCH 4/8] tests: Test the runtime API call against the metadata Signed-off-by: Alexandru Vasile --- testing/integration-tests/src/blocks/mod.rs | 32 +++++++++++++++++++++ testing/integration-tests/src/client/mod.rs | 31 ++++++++++++++++++++ 2 files changed, 63 insertions(+) diff --git a/testing/integration-tests/src/blocks/mod.rs b/testing/integration-tests/src/blocks/mod.rs index 71ddc257eb..2f64fc098b 100644 --- a/testing/integration-tests/src/blocks/mod.rs +++ b/testing/integration-tests/src/blocks/mod.rs @@ -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. @@ -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".into(), None).await?; + let cursor = &mut &*bytes; + let _ = >::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(()) +} diff --git a/testing/integration-tests/src/client/mod.rs b/testing/integration-tests/src/client/mod.rs index 0214c4901f..80f0afb331 100644 --- a/testing/integration-tests/src/client/mod.rs +++ b/testing/integration-tests/src/client/mod.rs @@ -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, @@ -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".into(), None, None) + .await + .unwrap(); + + let cursor = &mut &*metadata_bytes; + let _ = >::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); +} From 49d435256461a297617b58d6c0d3524fd8842bba Mon Sep 17 00:00:00 2001 From: Alexandru Vasile Date: Fri, 13 Jan 2023 12:39:39 +0000 Subject: [PATCH 5/8] client: Fetch the metadata from runtime API Signed-off-by: Alexandru Vasile --- subxt/src/client/online_client.rs | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) diff --git a/subxt/src/client/online_client.rs b/subxt/src/client/online_client.rs index e2a113b63c..817c1ec91c 100644 --- a/subxt/src/client/online_client.rs +++ b/subxt/src/client/online_client.rs @@ -25,7 +25,12 @@ use crate::{ Config, Metadata, }; +use codec::{ + Compact, + Decode, +}; use derivative::Derivative; +use frame_metadata::RuntimeMetadataPrefixed; use futures::future; use parking_lot::RwLock; use std::sync::Arc; @@ -96,7 +101,7 @@ impl OnlineClient { let (genesis_hash, runtime_version, metadata) = future::join3( rpc.genesis_hash(), rpc.runtime_version(None), - rpc.metadata(None), + OnlineClient::fetch_metadata(&rpc), ) .await; @@ -110,6 +115,17 @@ impl OnlineClient { }) } + /// Fetch the metadata from substrate using the runtime API. + async fn fetch_metadata(rpc: &Rpc) -> Result { + let bytes = rpc + .state_call("Metadata_metadata".into(), None, None) + .await?; + let cursor = &mut &*bytes; + let _ = >::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. /// From 54c28b0e6e653f65efeba6ccc8d9c5931389de10 Mon Sep 17 00:00:00 2001 From: Alexandru Vasile Date: Fri, 13 Jan 2023 13:37:22 +0000 Subject: [PATCH 6/8] blocks: Fix doc typo Signed-off-by: Alexandru Vasile --- subxt/src/blocks/block_types.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/subxt/src/blocks/block_types.rs b/subxt/src/blocks/block_types.rs index a05c8d40c4..82372974ba 100644 --- a/subxt/src/blocks/block_types.rs +++ b/subxt/src/blocks/block_types.rs @@ -91,7 +91,7 @@ where )) } - /// Execute a runtime API calls at this block. + /// Execute a runtime API call at this block. pub async fn runtime_api(&self) -> Result, Error> { Ok(RuntimeApi::new(self.client.clone(), self.hash())) } From 29bec1a617b13f7308221f1f82aeca1b65bb1a5c Mon Sep 17 00:00:00 2001 From: Alexandru Vasile Date: Tue, 17 Jan 2023 11:23:21 +0000 Subject: [PATCH 7/8] blocks: Use &str instead of String to identify fn names Signed-off-by: Alexandru Vasile --- subxt/src/client/online_client.rs | 4 +--- subxt/src/rpc/rpc.rs | 2 +- subxt/src/runtime_api/runtime_types.rs | 2 +- testing/integration-tests/src/blocks/mod.rs | 2 +- testing/integration-tests/src/client/mod.rs | 2 +- 5 files changed, 5 insertions(+), 7 deletions(-) diff --git a/subxt/src/client/online_client.rs b/subxt/src/client/online_client.rs index 817c1ec91c..96433a7b50 100644 --- a/subxt/src/client/online_client.rs +++ b/subxt/src/client/online_client.rs @@ -117,9 +117,7 @@ impl OnlineClient { /// Fetch the metadata from substrate using the runtime API. async fn fetch_metadata(rpc: &Rpc) -> Result { - let bytes = rpc - .state_call("Metadata_metadata".into(), None, None) - .await?; + let bytes = rpc.state_call("Metadata_metadata", None, None).await?; let cursor = &mut &*bytes; let _ = >::decode(cursor)?; let meta: RuntimeMetadataPrefixed = Decode::decode(cursor)?; diff --git a/subxt/src/rpc/rpc.rs b/subxt/src/rpc/rpc.rs index 7213af8168..fee29799d4 100644 --- a/subxt/src/rpc/rpc.rs +++ b/subxt/src/rpc/rpc.rs @@ -374,7 +374,7 @@ impl Rpc { /// Execute a runtime API call. pub async fn state_call( &self, - function: String, + function: &str, call_parameters: Option<&[u8]>, at: Option, ) -> Result { diff --git a/subxt/src/runtime_api/runtime_types.rs b/subxt/src/runtime_api/runtime_types.rs index a4c15d6e6c..a449c0e377 100644 --- a/subxt/src/runtime_api/runtime_types.rs +++ b/subxt/src/runtime_api/runtime_types.rs @@ -41,7 +41,7 @@ where /// Execute a raw runtime API call. pub fn call_raw<'a>( &self, - function: String, + function: &'a str, call_parameters: Option<&'a [u8]>, ) -> impl Future, Error>> + 'a { let client = self.client.clone(); diff --git a/testing/integration-tests/src/blocks/mod.rs b/testing/integration-tests/src/blocks/mod.rs index 2f64fc098b..a3730752fd 100644 --- a/testing/integration-tests/src/blocks/mod.rs +++ b/testing/integration-tests/src/blocks/mod.rs @@ -104,7 +104,7 @@ async fn runtime_api_call() -> Result<(), subxt::Error> { let block = sub.next().await.unwrap()?; let rt = block.runtime_api().await?; - let bytes = rt.call_raw("Metadata_metadata".into(), None).await?; + let bytes = rt.call_raw("Metadata_metadata", None).await?; let cursor = &mut &*bytes; let _ = >::decode(cursor)?; let meta: RuntimeMetadataPrefixed = Decode::decode(cursor)?; diff --git a/testing/integration-tests/src/client/mod.rs b/testing/integration-tests/src/client/mod.rs index 80f0afb331..ad18d439e4 100644 --- a/testing/integration-tests/src/client/mod.rs +++ b/testing/integration-tests/src/client/mod.rs @@ -264,7 +264,7 @@ async fn rpc_state_call() { // Call into the runtime of the chain to get the Metadata. let metadata_bytes = api .rpc() - .state_call("Metadata_metadata".into(), None, None) + .state_call("Metadata_metadata", None, None) .await .unwrap(); From b225532513d84ed7e81131e303031eeacb25bb3a Mon Sep 17 00:00:00 2001 From: Alexandru Vasile <60601340+lexnv@users.noreply.github.com> Date: Wed, 18 Jan 2023 17:24:38 +0200 Subject: [PATCH 8/8] Update subxt/src/runtime_api/runtime_client.rs Co-authored-by: Niklas Adolfsson --- subxt/src/runtime_api/runtime_client.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/subxt/src/runtime_api/runtime_client.rs b/subxt/src/runtime_api/runtime_client.rs index f7b367eed9..745bd4f820 100644 --- a/subxt/src/runtime_api/runtime_client.rs +++ b/subxt/src/runtime_api/runtime_client.rs @@ -56,7 +56,7 @@ where .rpc() .block_hash(None) .await? - .expect("didn't pass a block number; qed") + .expect("substrate RPC returns the best block when no block number is provided; qed") } };