-
Notifications
You must be signed in to change notification settings - Fork 254
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Allow generalising over RPC implementation (#634)
* WIP generalising RPC client * WIP: non-object-safe RpcClientT.. aah generics everywhere * WIP object-safe RpcClientT trait and no more extra generics * Get core things compiling again with object-safe RpcClientT trait * Make jsonrpsee optional and get test-runtime working again * cargo fmt * add RpcParams object to enforce correct formatting of rps params * Wee tweaks * clippy fixes * cargo fmt * TWeak a few types * make sure we get jsonrpsee-types, too * Add examples for rpc_params/RpcParams * more doc tweaks * remove a now unneeded dev note * Option<Box<RawValue>> instead to avoid allocations in some cases * update docs * tweak RpcClientT trait docs * Tweak docs around RpcClient and RpcClientT. Don't expose RpcClientT directly * more doc tweaking about RpcParams and undo decision not to expose RpcParamsT * Doc tweak Co-authored-by: Alexandru Vasile <60601340+lexnv@users.noreply.github.com> * more doc tweaks * Fix doc thing * Add an example of injecting a custom RPC client * Fix a typo * Address clippy things in example * Fix a silly typo * another clippy fix * rpc_params to panic instead of returning a result, like serde_json::json, and deref on Rpc<T> * fix docs Co-authored-by: Alexandru Vasile <60601340+lexnv@users.noreply.github.com>
- Loading branch information
Showing
17 changed files
with
737 additions
and
143 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,96 @@ | ||
// 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 std::{ | ||
fmt::Write, | ||
pin::Pin, | ||
sync::{ | ||
Arc, | ||
Mutex, | ||
}, | ||
}; | ||
use subxt::{ | ||
rpc::{ | ||
RawValue, | ||
RpcClientT, | ||
RpcFuture, | ||
RpcSubscription, | ||
}, | ||
OnlineClient, | ||
PolkadotConfig, | ||
}; | ||
|
||
// A dummy RPC client that doesn't actually handle requests properly | ||
// at all, but instead just logs what requests to it were made. | ||
struct MyLoggingClient { | ||
log: Arc<Mutex<String>>, | ||
} | ||
|
||
// We have to implement this fairly low level trait to turn [`MyLoggingClient`] | ||
// into an RPC client that we can make use of in Subxt. Here we just log the requests | ||
// made but don't forward them to any real node, and instead just return nonsense. | ||
impl RpcClientT for MyLoggingClient { | ||
fn request_raw<'a>( | ||
&'a self, | ||
method: &'a str, | ||
params: Option<Box<RawValue>>, | ||
) -> RpcFuture<'a, Box<RawValue>> { | ||
writeln!( | ||
self.log.lock().unwrap(), | ||
"{method}({})", | ||
params.as_ref().map(|p| p.get()).unwrap_or("[]") | ||
) | ||
.unwrap(); | ||
|
||
// We've logged the request; just return garbage. Because a boxed future is returned, | ||
// you're able to run whatever async code you'd need to actually talk to a node. | ||
let res = RawValue::from_string("[]".to_string()).unwrap(); | ||
Box::pin(std::future::ready(Ok(res))) | ||
} | ||
|
||
fn subscribe_raw<'a>( | ||
&'a self, | ||
sub: &'a str, | ||
params: Option<Box<RawValue>>, | ||
unsub: &'a str, | ||
) -> RpcFuture<'a, RpcSubscription> { | ||
writeln!( | ||
self.log.lock().unwrap(), | ||
"{sub}({}) (unsub: {unsub})", | ||
params.as_ref().map(|p| p.get()).unwrap_or("[]") | ||
) | ||
.unwrap(); | ||
|
||
// We've logged the request; just return garbage. Because a boxed future is returned, | ||
// and that will return a boxed Stream impl, you have a bunch of flexibility to build | ||
// and return whatever type of Stream you see fit. | ||
let res = RawValue::from_string("[]".to_string()).unwrap(); | ||
let stream = futures::stream::once(async move { Ok(res) }); | ||
let stream: Pin<Box<dyn futures::Stream<Item = _> + Send>> = Box::pin(stream); | ||
Box::pin(std::future::ready(Ok(stream))) | ||
} | ||
} | ||
|
||
#[subxt::subxt(runtime_metadata_path = "../artifacts/polkadot_metadata.scale")] | ||
pub mod polkadot {} | ||
|
||
#[tokio::main] | ||
async fn main() -> Result<(), Box<dyn std::error::Error>> { | ||
tracing_subscriber::fmt::init(); | ||
|
||
// Instantiate our replacement RPC client. | ||
let log = Arc::default(); | ||
let rpc_client = MyLoggingClient { | ||
log: Arc::clone(&log), | ||
}; | ||
|
||
// Pass this into our OnlineClient to instantiate it. This will lead to some | ||
// RPC calls being made to fetch chain details/metadata, which will immediately | ||
// fail.. | ||
let _ = OnlineClient::<PolkadotConfig>::from_rpc_client(rpc_client).await; | ||
|
||
// But, we can see that the calls were made via our custom RPC client: | ||
println!("Log of calls made:\n\n{}", log.lock().unwrap().as_str()); | ||
Ok(()) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,86 @@ | ||
// 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::{ | ||
RpcClientT, | ||
RpcFuture, | ||
RpcSubscription, | ||
}; | ||
use crate::error::RpcError; | ||
use futures::stream::{ | ||
StreamExt, | ||
TryStreamExt, | ||
}; | ||
use jsonrpsee::{ | ||
core::client::{ | ||
Client, | ||
ClientT, | ||
SubscriptionClientT, | ||
}, | ||
types::ParamsSer, | ||
}; | ||
use serde_json::value::{ | ||
RawValue, | ||
Value, | ||
}; | ||
|
||
impl RpcClientT for Client { | ||
fn request_raw<'a>( | ||
&'a self, | ||
method: &'a str, | ||
params: Option<Box<RawValue>>, | ||
) -> RpcFuture<'a, Box<RawValue>> { | ||
Box::pin(async move { | ||
let params = prep_params_for_jsonrpsee(params)?; | ||
let res = ClientT::request(self, method, Some(params)) | ||
.await | ||
.map_err(|e| RpcError(e.to_string()))?; | ||
Ok(res) | ||
}) | ||
} | ||
|
||
fn subscribe_raw<'a>( | ||
&'a self, | ||
sub: &'a str, | ||
params: Option<Box<RawValue>>, | ||
unsub: &'a str, | ||
) -> RpcFuture<'a, RpcSubscription> { | ||
Box::pin(async move { | ||
let params = prep_params_for_jsonrpsee(params)?; | ||
let sub = SubscriptionClientT::subscribe::<Box<RawValue>>( | ||
self, | ||
sub, | ||
Some(params), | ||
unsub, | ||
) | ||
.await | ||
.map_err(|e| RpcError(e.to_string()))? | ||
.map_err(|e| RpcError(e.to_string())) | ||
.boxed(); | ||
Ok(sub) | ||
}) | ||
} | ||
} | ||
|
||
// This is ugly; we have to encode to Value's to be compat with the jsonrpc interface. | ||
// Remove and simplify this once something like https://github.com/paritytech/jsonrpsee/issues/862 is in: | ||
fn prep_params_for_jsonrpsee( | ||
params: Option<Box<RawValue>>, | ||
) -> Result<ParamsSer<'static>, RpcError> { | ||
let params = match params { | ||
Some(params) => params, | ||
// No params? avoid any work and bail early. | ||
None => return Ok(ParamsSer::Array(Vec::new())), | ||
}; | ||
let val = serde_json::to_value(¶ms).expect("RawValue guarantees valid JSON"); | ||
let arr = match val { | ||
Value::Array(arr) => Ok(arr), | ||
_ => { | ||
Err(RpcError(format!( | ||
"RPC Params are expected to be an array but got {params}" | ||
))) | ||
} | ||
}?; | ||
Ok(ParamsSer::Array(arr)) | ||
} |
Oops, something went wrong.