XXX/Dynamic: Add dynamic Runtime API calls

Signed-off-by: Alexandru Vasile <alexandru.vasile@parity.io>
This commit is contained in:
Alexandru Vasile
2023-02-14 19:30:11 +02:00
parent c131c48124
commit 16944ea1fc
4 changed files with 179 additions and 5 deletions
+10 -1
View File
@@ -20,6 +20,8 @@ use subxt::{
},
SubstrateConfig,
},
dynamic::Value,
runtime_api::dynamic,
tx::PairSigner,
OnlineClient,
};
@@ -49,10 +51,17 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {
let alice = AccountKeyring::Alice.to_account_id().into();
let api_tx = polkadot::runtime_api::AccountNonceApi::account_nonce(alice);
println!("RuntimeApi payload: {:?}", api_tx);
let bytes = api.runtime_api().at(None).await?.call(api_tx).await?;
println!("Result: {:?}", bytes);
let alice = AccountKeyring::Alice.to_account_id();
let api_tx = dynamic::<polkadot::runtime_api::AccountNonceApi::account_nonce_target>(
"AccountNonceApi_account_nonce",
vec![Value::from_bytes(&alice)],
);
let bytes = api.runtime_api().at(None).await?.dyn_call(api_tx).await?;
println!("Result: {:?}", bytes);
// Send from Alice to Bob.
let signer = PairSigner::new(AccountKeyring::Alice.pair());
let dest = AccountKeyring::Bob.to_account_id().into();
+1 -1
View File
@@ -9,5 +9,5 @@ mod runtime_payload;
mod runtime_types;
pub use runtime_client::RuntimeApiClient;
pub use runtime_payload::RuntimeAPIPayload;
pub use runtime_payload::{RuntimeApiPayload, RuntimeAPIPayload, dynamic, DynamicRuntimeApiPayload, StaticRuntimeApiPayload};
pub use runtime_types::RuntimeApi;
+128 -2
View File
@@ -2,9 +2,23 @@
// This file is dual-licensed as Apache-2.0 or GPL-3.0.
// see LICENSE for license details.
use std::marker::PhantomData;
use std::{
borrow::Cow,
marker::PhantomData,
};
/// Payload for a runtime API fn.
use codec::{
Decode,
Encode,
};
use scale_value::Composite;
use crate::{
Error,
Metadata,
};
/// Payload for calling into a runtime API function.
#[derive(Debug)]
pub struct RuntimeAPIPayload<ReturnTy> {
func_name: &'static str,
@@ -46,3 +60,115 @@ impl<ReturnTy> RuntimeAPIPayload<ReturnTy> {
&self.data
}
}
/// RuntimeApiPayload
pub trait RuntimeApiPayload {
/// The return type into which the result of the call is interpreted.
type Target;
// TODO: Could do with some lifetimes.
/// The function name of the runtime API.
fn func_name(&self) -> String;
/// Encode arguments to the provided output.
fn encode_args_to(&self, metadata: &Metadata, out: &mut Vec<u8>)
-> Result<(), Error>;
/// Encode arguments and return the output. This is a convenience
/// wrapper around [`RuntimeApiPayload::encode_params_to`].
fn encode_args(&self, metadata: &Metadata) -> Result<Vec<u8>, Error> {
let mut v = Vec::new();
self.encode_args_to(metadata, &mut v)?;
Ok(v)
}
}
/// StaticRuntimeApiPayload
pub struct StaticRuntimeApiPayload<ArgData, ReturnTy> {
func_name: &'static str,
data: ArgData,
_marker: PhantomData<ReturnTy>,
}
impl<ArgData, ReturnTy> RuntimeApiPayload for StaticRuntimeApiPayload<ArgData, ReturnTy>
where
ArgData: Encode,
ReturnTy: Decode,
{
type Target = ReturnTy;
fn func_name(&self) -> String {
self.func_name.into()
}
fn encode_args_to(
&self,
_metadata: &Metadata,
out: &mut Vec<u8>,
) -> Result<(), Error> {
self.data.encode_to(out);
Ok(())
}
}
/// DynamicRuntimeApiPayload
pub struct DynamicRuntimeApiPayload<ReturnTy> {
func_name: &'static str,
fields: Composite<()>,
_marker: PhantomData<ReturnTy>,
}
// impl<'a, ReturnTy> DynamicRuntimeApiPayload<'a, ReturnTy> {
// pub fn into_value(self) -> Value<()> {
// }
// }
/// Construct a dynamic runtime API call.
pub fn dynamic<ReturnTy>(
func_name: &'static str,
fields: impl Into<Composite<()>>,
) -> DynamicRuntimeApiPayload<ReturnTy> {
DynamicRuntimeApiPayload {
func_name: func_name.into(),
fields: fields.into(),
_marker: PhantomData,
}
}
impl<ReturnTy> RuntimeApiPayload for DynamicRuntimeApiPayload<ReturnTy>
where
ReturnTy: Decode,
{
type Target = ReturnTy;
fn encode_args_to(
&self,
metadata: &Metadata,
out: &mut Vec<u8>,
) -> Result<(), Error> {
let args = match &self.fields {
// TODO: Composite::Named WIP.
Composite::Named(_) => panic!("Composite::Named unsupported yet."),
Composite::Unnamed(args) => args,
};
let fn_metadata = metadata.runtime_fn(&self.func_name)?;
let param_ty = fn_metadata.params_ty_ids();
if param_ty.len() != args.len() {
return Err(Error::Other(
"Provided different number of params than expected".into(),
))
}
for (value, ty) in args.iter().zip(param_ty) {
scale_value::scale::encode_as_type(value, *ty, metadata.types(), out)?;
}
Ok(())
}
fn func_name(&self) -> String {
self.func_name.to_owned().into()
}
}
+40 -1
View File
@@ -14,7 +14,13 @@ use std::{
marker::PhantomData,
};
use super::RuntimeAPIPayload;
use super::{
runtime_payload::{
DynamicRuntimeApiPayload,
RuntimeApiPayload,
},
RuntimeAPIPayload,
};
/// Execute runtime API calls.
#[derive(Derivative)]
@@ -74,6 +80,7 @@ where
let payload = payload;
let function = payload.func_name();
let call_parameters = Some(payload.param_data());
println!("StaticCall: {:?}", call_parameters);
let data = client
.rpc()
@@ -84,4 +91,36 @@ where
Ok(result)
}
}
/// Dynamic call.
pub fn dyn_call<DynCall>(
&self,
payload: DynCall,
) -> impl Future<Output = Result<DynCall::Target, Error>>
where
DynCall: RuntimeApiPayload,
<DynCall as RuntimeApiPayload>::Target: Decode,
{
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 payload = payload;
let function = payload.func_name();
let bytes = payload.encode_args(&client.metadata())?;
let call_parameters = Some(bytes.as_slice());
println!("DynCall: {:?}", call_parameters);
let data = client
.rpc()
.state_call(&function, call_parameters, Some(block_hash))
.await?;
let result: DynCall::Target = Decode::decode(&mut &data.0[..])?;
Ok(result)
}
}
}