From 5a431470d2a4155a4bb00ea6ac07d7f9707f8e82 Mon Sep 17 00:00:00 2001
From: tmpolaczyk <44604217+tmpolaczyk@users.noreply.github.com>
Date: Fri, 20 Sep 2024 09:17:39 +0200
Subject: [PATCH] Allow to call arbitrary runtime apis using
 RelayChainInterface (#5521)
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

When using the relay chain though a `Arc<dyn RelayChainInterface>` there
is no way to call arbitrary runtime apis. Both implementations of that
trait allow this, so it feels natural to expose this functionality in
the trait.

This PR adds a `call_runtime_api` method to RelayChainInterface trait,
and a separate function also named `call_runtime_api` which allows the
caller to specify the input and output types, as opposed to having to
encode them. This generic function cannot be part of the trait because a
`dyn Trait` object cannot have generic methods.

---------

Co-authored-by: Bastian Köcher <git@kchr.de>
---
 cumulus/client/consensus/common/src/tests.rs  |  9 +++++
 cumulus/client/network/src/tests.rs           |  9 +++++
 cumulus/client/pov-recovery/src/tests.rs      |  9 +++++
 .../src/lib.rs                                | 19 +++++++++-
 .../client/relay-chain-interface/src/lib.rs   | 37 ++++++++++++++++++-
 .../relay-chain-rpc-interface/src/lib.rs      | 12 ++++++
 .../src/rpc_client.rs                         | 26 ++++++++++---
 prdoc/pr_5521.prdoc                           | 24 ++++++++++++
 8 files changed, 136 insertions(+), 9 deletions(-)
 create mode 100644 prdoc/pr_5521.prdoc

diff --git a/cumulus/client/consensus/common/src/tests.rs b/cumulus/client/consensus/common/src/tests.rs
index 06f90330d47..794ce30de3e 100644
--- a/cumulus/client/consensus/common/src/tests.rs
+++ b/cumulus/client/consensus/common/src/tests.rs
@@ -268,6 +268,15 @@ impl RelayChainInterface for Relaychain {
 	async fn version(&self, _: PHash) -> RelayChainResult<RuntimeVersion> {
 		unimplemented!("Not needed for test")
 	}
+
+	async fn call_runtime_api(
+		&self,
+		_method_name: &'static str,
+		_hash: PHash,
+		_payload: &[u8],
+	) -> RelayChainResult<Vec<u8>> {
+		unimplemented!("Not needed for test")
+	}
 }
 
 fn sproof_with_best_parent(client: &Client) -> RelayStateSproofBuilder {
diff --git a/cumulus/client/network/src/tests.rs b/cumulus/client/network/src/tests.rs
index 1c8edd803ed..686943063bb 100644
--- a/cumulus/client/network/src/tests.rs
+++ b/cumulus/client/network/src/tests.rs
@@ -326,6 +326,15 @@ impl RelayChainInterface for DummyRelayChainInterface {
 			system_version: 1,
 		})
 	}
+
+	async fn call_runtime_api(
+		&self,
+		_method_name: &'static str,
+		_hash: PHash,
+		_payload: &[u8],
+	) -> RelayChainResult<Vec<u8>> {
+		unimplemented!("Not needed for test")
+	}
 }
 
 fn make_validator_and_api() -> (
diff --git a/cumulus/client/pov-recovery/src/tests.rs b/cumulus/client/pov-recovery/src/tests.rs
index 5935824e173..539f7f33ad3 100644
--- a/cumulus/client/pov-recovery/src/tests.rs
+++ b/cumulus/client/pov-recovery/src/tests.rs
@@ -487,6 +487,15 @@ impl RelayChainInterface for Relaychain {
 	) -> RelayChainResult<Vec<CoreState<PHash, NumberFor<Block>>>> {
 		unimplemented!("Not needed for test");
 	}
+
+	async fn call_runtime_api(
+		&self,
+		_method_name: &'static str,
+		_hash: PHash,
+		_payload: &[u8],
+	) -> RelayChainResult<Vec<u8>> {
+		unimplemented!("Not needed for test")
+	}
 }
 
 fn make_candidate_chain(candidate_number_range: Range<u32>) -> Vec<CommittedCandidateReceipt> {
diff --git a/cumulus/client/relay-chain-inprocess-interface/src/lib.rs b/cumulus/client/relay-chain-inprocess-interface/src/lib.rs
index 629fa728be3..0455c03fc4d 100644
--- a/cumulus/client/relay-chain-inprocess-interface/src/lib.rs
+++ b/cumulus/client/relay-chain-inprocess-interface/src/lib.rs
@@ -36,7 +36,7 @@ use sc_client_api::{
 	StorageProof,
 };
 use sc_telemetry::TelemetryWorkerHandle;
-use sp_api::ProvideRuntimeApi;
+use sp_api::{CallApiAt, CallApiAtParams, CallContext, ProvideRuntimeApi};
 use sp_consensus::SyncOracle;
 use sp_core::Pair;
 use sp_state_machine::{Backend as StateBackend, StorageValue};
@@ -180,6 +180,23 @@ impl RelayChainInterface for RelayChainInProcessInterface {
 		Ok(self.backend.blockchain().info().finalized_hash)
 	}
 
+	async fn call_runtime_api(
+		&self,
+		method_name: &'static str,
+		hash: PHash,
+		payload: &[u8],
+	) -> RelayChainResult<Vec<u8>> {
+		Ok(self.full_client.call_api_at(CallApiAtParams {
+			at: hash,
+			function: method_name,
+			arguments: payload.to_vec(),
+			overlayed_changes: &Default::default(),
+			call_context: CallContext::Offchain,
+			recorder: &None,
+			extensions: &Default::default(),
+		})?)
+	}
+
 	async fn is_major_syncing(&self) -> RelayChainResult<bool> {
 		Ok(self.sync_oracle.is_major_syncing())
 	}
diff --git a/cumulus/client/relay-chain-interface/src/lib.rs b/cumulus/client/relay-chain-interface/src/lib.rs
index d02035e84e9..8d172e423eb 100644
--- a/cumulus/client/relay-chain-interface/src/lib.rs
+++ b/cumulus/client/relay-chain-interface/src/lib.rs
@@ -22,11 +22,11 @@ use sc_client_api::StorageProof;
 use sp_version::RuntimeVersion;
 
 use async_trait::async_trait;
-use codec::Error as CodecError;
+use codec::{Decode, Encode, Error as CodecError};
 use jsonrpsee_core::ClientError as JsonRpcError;
 use sp_api::ApiError;
 
-use cumulus_primitives_core::relay_chain::BlockId;
+use cumulus_primitives_core::relay_chain::{BlockId, Hash as RelayHash};
 pub use cumulus_primitives_core::{
 	relay_chain::{
 		BlockNumber, CommittedCandidateReceipt, CoreState, Hash as PHash, Header as PHeader,
@@ -117,6 +117,14 @@ pub trait RelayChainInterface: Send + Sync {
 	/// Get the hash of the finalized block.
 	async fn finalized_block_hash(&self) -> RelayChainResult<PHash>;
 
+	/// Call an arbitrary runtime api. The input and output are SCALE-encoded.
+	async fn call_runtime_api(
+		&self,
+		method_name: &'static str,
+		hash: RelayHash,
+		payload: &[u8],
+	) -> RelayChainResult<Vec<u8>>;
+
 	/// Returns the whole contents of the downward message queue for the parachain we are collating
 	/// for.
 	///
@@ -296,6 +304,15 @@ where
 		(**self).finalized_block_hash().await
 	}
 
+	async fn call_runtime_api(
+		&self,
+		method_name: &'static str,
+		hash: RelayHash,
+		payload: &[u8],
+	) -> RelayChainResult<Vec<u8>> {
+		(**self).call_runtime_api(method_name, hash, payload).await
+	}
+
 	async fn is_major_syncing(&self) -> RelayChainResult<bool> {
 		(**self).is_major_syncing().await
 	}
@@ -364,3 +381,19 @@ where
 		(**self).version(relay_parent).await
 	}
 }
+
+/// Helper function to call an arbitrary runtime API using a `RelayChainInterface` client.
+/// Unlike the trait method, this function can be generic, so it handles the encoding of input and
+/// output params.
+pub async fn call_runtime_api<R>(
+	client: &(impl RelayChainInterface + ?Sized),
+	method_name: &'static str,
+	hash: RelayHash,
+	payload: impl Encode,
+) -> RelayChainResult<R>
+where
+	R: Decode,
+{
+	let res = client.call_runtime_api(method_name, hash, &payload.encode()).await?;
+	Decode::decode(&mut &*res).map_err(Into::into)
+}
diff --git a/cumulus/client/relay-chain-rpc-interface/src/lib.rs b/cumulus/client/relay-chain-rpc-interface/src/lib.rs
index 3698938bfd8..77dc1d7318a 100644
--- a/cumulus/client/relay-chain-rpc-interface/src/lib.rs
+++ b/cumulus/client/relay-chain-rpc-interface/src/lib.rs
@@ -165,6 +165,18 @@ impl RelayChainInterface for RelayChainRpcInterface {
 		self.rpc_client.chain_get_finalized_head().await
 	}
 
+	async fn call_runtime_api(
+		&self,
+		method_name: &'static str,
+		hash: RelayHash,
+		payload: &[u8],
+	) -> RelayChainResult<Vec<u8>> {
+		self.rpc_client
+			.call_remote_runtime_function_encoded(method_name, hash, payload)
+			.await
+			.map(|bytes| bytes.to_vec())
+	}
+
 	async fn is_major_syncing(&self) -> RelayChainResult<bool> {
 		self.rpc_client.system_health().await.map(|h| h.is_syncing)
 	}
diff --git a/cumulus/client/relay-chain-rpc-interface/src/rpc_client.rs b/cumulus/client/relay-chain-rpc-interface/src/rpc_client.rs
index 6e282281de6..381f4a046a4 100644
--- a/cumulus/client/relay-chain-rpc-interface/src/rpc_client.rs
+++ b/cumulus/client/relay-chain-rpc-interface/src/rpc_client.rs
@@ -148,15 +148,13 @@ impl RelayChainRpcClient {
 		}
 	}
 
-	/// Call a call to `state_call` rpc method.
-	pub async fn call_remote_runtime_function<R: Decode>(
+	/// Same as `call_remote_runtime_function` but work on encoded data
+	pub async fn call_remote_runtime_function_encoded(
 		&self,
 		method_name: &str,
 		hash: RelayHash,
-		payload: Option<impl Encode>,
-	) -> RelayChainResult<R> {
-		let payload_bytes =
-			payload.map_or(sp_core::Bytes(Vec::new()), |v| sp_core::Bytes(v.encode()));
+		payload_bytes: &[u8],
+	) -> RelayChainResult<sp_core::Bytes> {
 		let params = rpc_params! {
 			method_name,
 			payload_bytes,
@@ -174,6 +172,22 @@ impl RelayChainRpcClient {
 				);
 			})
 			.await?;
+
+		Ok(res)
+	}
+
+	/// Call a call to `state_call` rpc method.
+	pub async fn call_remote_runtime_function<R: Decode>(
+		&self,
+		method_name: &str,
+		hash: RelayHash,
+		payload: Option<impl Encode>,
+	) -> RelayChainResult<R> {
+		let payload_bytes =
+			payload.map_or(sp_core::Bytes(Vec::new()), |v| sp_core::Bytes(v.encode()));
+		let res = self
+			.call_remote_runtime_function_encoded(method_name, hash, &payload_bytes)
+			.await?;
 		Decode::decode(&mut &*res.0).map_err(Into::into)
 	}
 
diff --git a/prdoc/pr_5521.prdoc b/prdoc/pr_5521.prdoc
new file mode 100644
index 00000000000..564d9df58ce
--- /dev/null
+++ b/prdoc/pr_5521.prdoc
@@ -0,0 +1,24 @@
+# Schema: Polkadot SDK PRDoc Schema (prdoc) v1.0.0
+# See doc at https://raw.githubusercontent.com/paritytech/polkadot-sdk/master/prdoc/schema_user.json
+
+title: Allow to call arbitrary runtime apis using RelayChainInterface
+
+doc:
+  - audience: Node Dev
+    description: |
+      This PR adds a `call_runtime_api` method to RelayChainInterface trait, and a separate function also named `call_runtime_api`
+      which allows the caller to specify the input and output types, as opposed to having to encode them.
+
+crates:
+  - name: cumulus-relay-chain-interface
+    bump: patch
+  - name: cumulus-client-consensus-common
+    bump: patch
+  - name: cumulus-client-pov-recovery
+    bump: patch
+  - name: cumulus-client-network
+    bump: patch
+  - name: cumulus-relay-chain-inprocess-interface
+    bump: patch
+  - name: cumulus-relay-chain-rpc-interface
+    bump: patch
-- 
GitLab