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` 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 --- 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 06f90330d474..794ce30de3e1 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 { unimplemented!("Not needed for test") } + + async fn call_runtime_api( + &self, + _method_name: &'static str, + _hash: PHash, + _payload: &[u8], + ) -> RelayChainResult> { + 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 1c8edd803ed8..686943063bb0 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> { + 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 5935824e173a..539f7f33ad34 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>>> { unimplemented!("Not needed for test"); } + + async fn call_runtime_api( + &self, + _method_name: &'static str, + _hash: PHash, + _payload: &[u8], + ) -> RelayChainResult> { + unimplemented!("Not needed for test") + } } fn make_candidate_chain(candidate_number_range: Range) -> Vec { diff --git a/cumulus/client/relay-chain-inprocess-interface/src/lib.rs b/cumulus/client/relay-chain-inprocess-interface/src/lib.rs index 629fa728be37..0455c03fc4de 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> { + 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 { 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 d02035e84e92..8d172e423eb9 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; + /// 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>; + /// 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> { + (**self).call_runtime_api(method_name, hash, payload).await + } + async fn is_major_syncing(&self) -> RelayChainResult { (**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( + client: &(impl RelayChainInterface + ?Sized), + method_name: &'static str, + hash: RelayHash, + payload: impl Encode, +) -> RelayChainResult +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 3698938bfd8f..77dc1d7318ab 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> { + 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 { 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 6e282281de69..381f4a046a40 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( + /// 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, - ) -> RelayChainResult { - let payload_bytes = - payload.map_or(sp_core::Bytes(Vec::new()), |v| sp_core::Bytes(v.encode())); + payload_bytes: &[u8], + ) -> RelayChainResult { 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( + &self, + method_name: &str, + hash: RelayHash, + payload: Option, + ) -> RelayChainResult { + 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 000000000000..564d9df58ceb --- /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