Skip to content

Commit

Permalink
Allow to call arbitrary runtime apis using RelayChainInterface (#5521)
Browse files Browse the repository at this point in the history
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>
  • Loading branch information
tmpolaczyk and bkchr committed Sep 20, 2024
1 parent 86bb5cb commit 5a43147
Show file tree
Hide file tree
Showing 8 changed files with 136 additions and 9 deletions.
9 changes: 9 additions & 0 deletions cumulus/client/consensus/common/src/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down
9 changes: 9 additions & 0 deletions cumulus/client/network/src/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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() -> (
Expand Down
9 changes: 9 additions & 0 deletions cumulus/client/pov-recovery/src/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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> {
Expand Down
19 changes: 18 additions & 1 deletion cumulus/client/relay-chain-inprocess-interface/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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};
Expand Down Expand Up @@ -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())
}
Expand Down
37 changes: 35 additions & 2 deletions cumulus/client/relay-chain-interface/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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.
///
Expand Down Expand Up @@ -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
}
Expand Down Expand Up @@ -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)
}
12 changes: 12 additions & 0 deletions cumulus/client/relay-chain-rpc-interface/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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)
}
Expand Down
26 changes: 20 additions & 6 deletions cumulus/client/relay-chain-rpc-interface/src/rpc_client.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -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)
}

Expand Down
24 changes: 24 additions & 0 deletions prdoc/pr_5521.prdoc
Original file line number Diff line number Diff line change
@@ -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

0 comments on commit 5a43147

Please sign in to comment.