Add solana_client::nonblocking::RpcClient

This commit is contained in:
Michael Vines
2022-01-24 17:01:07 -08:00
parent db481e1799
commit 85e8bece2e
13 changed files with 5579 additions and 1419 deletions

View File

@ -10,6 +10,7 @@ license = "Apache-2.0"
edition = "2021"
[dependencies]
async-trait = "0.1.52"
base64 = "0.13.0"
bincode = "1.3.3"
bs58 = "0.4.0"

View File

@ -1,4 +1,4 @@
//! The standard [`RpcSender`] over HTTP.
//! Nonblocking [`RpcSender`] over HTTP.
use {
crate::{
@ -8,6 +8,7 @@ use {
rpc_response::RpcSimulateTransactionResult,
rpc_sender::*,
},
async_trait::async_trait,
log::*,
reqwest::{
self,
@ -25,13 +26,13 @@ use {
};
pub struct HttpSender {
client: Arc<reqwest::blocking::Client>,
client: Arc<reqwest::Client>,
url: String,
request_id: AtomicU64,
stats: RwLock<RpcTransportStats>,
}
/// The standard [`RpcSender`] over HTTP.
/// Nonblocking [`RpcSender`] over HTTP.
impl HttpSender {
/// Create an HTTP RPC sender.
///
@ -45,15 +46,11 @@ impl HttpSender {
///
/// The URL is an HTTP URL, usually for port 8899.
pub fn new_with_timeout(url: String, timeout: Duration) -> Self {
// `reqwest::blocking::Client` panics if run in a tokio async context. Shuttle the
// request to a different tokio thread to avoid this
let client = Arc::new(
tokio::task::block_in_place(move || {
reqwest::blocking::Client::builder()
.timeout(timeout)
.build()
})
.expect("build rpc client"),
reqwest::Client::builder()
.timeout(timeout)
.build()
.expect("build rpc client"),
);
Self {
@ -100,12 +97,17 @@ impl<'a> Drop for StatsUpdater<'a> {
}
}
#[async_trait]
impl RpcSender for HttpSender {
fn get_transport_stats(&self) -> RpcTransportStats {
self.stats.read().unwrap().clone()
}
fn send(&self, request: RpcRequest, params: serde_json::Value) -> Result<serde_json::Value> {
async fn send(
&self,
request: RpcRequest,
params: serde_json::Value,
) -> Result<serde_json::Value> {
let mut stats_updater = StatsUpdater::new(&self.stats);
let request_id = self.request_id.fetch_add(1, Ordering::Relaxed);
@ -113,18 +115,15 @@ impl RpcSender for HttpSender {
let mut too_many_requests_retries = 5;
loop {
// `reqwest::blocking::Client` panics if run in a tokio async context. Shuttle the
// request to a different tokio thread to avoid this
let response = {
let client = self.client.clone();
let request_json = request_json.clone();
tokio::task::block_in_place(move || {
client
.post(&self.url)
.header(CONTENT_TYPE, "application/json")
.body(request_json)
.send()
})
client
.post(&self.url)
.header(CONTENT_TYPE, "application/json")
.body(request_json)
.send()
.await
}?;
if !response.status().is_success() {
@ -155,8 +154,7 @@ impl RpcSender for HttpSender {
return Err(response.error_for_status().unwrap_err().into());
}
let mut json =
tokio::task::block_in_place(move || response.json::<serde_json::Value>())?;
let mut json = response.json::<serde_json::Value>().await?;
if json["error"].is_object() {
return match serde_json::from_value::<RpcErrorObject>(json["error"].clone()) {
Ok(rpc_error_object) => {
@ -208,14 +206,16 @@ mod tests {
#[tokio::test(flavor = "multi_thread")]
async fn http_sender_on_tokio_multi_thread() {
let http_sender = HttpSender::new("http://localhost:1234".to_string());
let _ = http_sender.send(RpcRequest::GetVersion, serde_json::Value::Null);
let _ = http_sender
.send(RpcRequest::GetVersion, serde_json::Value::Null)
.await;
}
#[tokio::test(flavor = "current_thread")]
#[should_panic(expected = "can call blocking only when running on the multi-threaded runtime")]
async fn http_sender_ontokio_current_thread_should_panic() {
// RpcClient::new() will panic in the tokio current-thread runtime due to `tokio::task::block_in_place()` usage, and there
// doesn't seem to be a way to detect whether the tokio runtime is multi_thread or current_thread...
let _ = HttpSender::new("http://localhost:1234".to_string());
async fn http_sender_on_tokio_current_thread() {
let http_sender = HttpSender::new("http://localhost:1234".to_string());
let _ = http_sender
.send(RpcRequest::GetVersion, serde_json::Value::Null)
.await;
}
}

View File

@ -4,8 +4,9 @@ extern crate serde_derive;
pub mod blockhash_query;
pub mod client_error;
pub mod http_sender;
pub mod mock_sender;
pub(crate) mod http_sender;
pub(crate) mod mock_sender;
pub mod nonblocking;
pub mod nonce_utils;
pub mod perf_utils;
pub mod pubsub_client;
@ -17,8 +18,15 @@ pub mod rpc_deprecated_config;
pub mod rpc_filter;
pub mod rpc_request;
pub mod rpc_response;
pub mod rpc_sender;
pub(crate) mod rpc_sender;
pub mod spinner;
pub mod thin_client;
pub mod tpu_client;
pub mod transaction_executor;
pub mod mock_sender_for_cli {
/// Magic `SIGNATURE` value used by `solana-cli` unit tests.
/// Please don't use this constant.
pub const SIGNATURE: &str =
"43yNSFC6fYTuPgTNFFhF4axw7AfWxB2BPdurme8yrsWEYwm8299xh8n6TAHjGymiSub1XtyxTNyd9GBfY2hxoBw8";
}

View File

@ -1,4 +1,4 @@
//! An [`RpcSender`] used for unit testing [`RpcClient`](crate::rpc_client::RpcClient).
//! A nonblocking [`RpcSender`] used for unit testing [`RpcClient`](crate::rpc_client::RpcClient).
use {
crate::{
@ -15,6 +15,7 @@ use {
},
rpc_sender::*,
},
async_trait::async_trait,
serde_json::{json, Number, Value},
solana_account_decoder::{UiAccount, UiAccountEncoding},
solana_sdk::{
@ -40,8 +41,6 @@ use {
};
pub const PUBKEY: &str = "7RoSF9fUmdphVCpabEoefH81WwrW7orsWonXWqTXkKV8";
pub const SIGNATURE: &str =
"43yNSFC6fYTuPgTNFFhF4axw7AfWxB2BPdurme8yrsWEYwm8299xh8n6TAHjGymiSub1XtyxTNyd9GBfY2hxoBw8";
pub type Mocks = HashMap<RpcRequest, Value>;
pub struct MockSender {
@ -87,12 +86,17 @@ impl MockSender {
}
}
#[async_trait]
impl RpcSender for MockSender {
fn get_transport_stats(&self) -> RpcTransportStats {
RpcTransportStats::default()
}
fn send(&self, request: RpcRequest, params: serde_json::Value) -> Result<serde_json::Value> {
async fn send(
&self,
request: RpcRequest,
params: serde_json::Value,
) -> Result<serde_json::Value> {
if let Some(value) = self.mocks.write().unwrap().remove(&request) {
return Ok(value);
}
@ -386,7 +390,7 @@ impl RpcSender for MockSender {
"getBlocksWithLimit" => serde_json::to_value(vec![1, 2, 3])?,
"getSignaturesForAddress" => {
serde_json::to_value(vec![RpcConfirmedTransactionStatusWithSignature {
signature: SIGNATURE.to_string(),
signature: crate::mock_sender_for_cli::SIGNATURE.to_string(),
slot: 123,
err: None,
memo: None,

View File

@ -0,0 +1 @@
pub mod rpc_client;

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -1,7 +1,7 @@
//! A transport for RPC calls.
use {
crate::{client_error::Result, rpc_request::RpcRequest},
async_trait::async_trait,
std::time::Duration,
};
@ -26,10 +26,14 @@ pub struct RpcTransportStats {
/// It is typically implemented by [`HttpSender`] in production, and
/// [`MockSender`] in unit tests.
///
/// [`RpcClient`]: crate::rpc_client::RpcClient
/// [`HttpSender`]: crate::http_sender::HttpSender
/// [`MockSender`]: crate::mock_sender::MockSender
pub trait RpcSender {
fn send(&self, request: RpcRequest, params: serde_json::Value) -> Result<serde_json::Value>;
#[async_trait]
pub(crate) trait RpcSender {
async fn send(
&self,
request: RpcRequest,
params: serde_json::Value,
) -> Result<serde_json::Value>;
fn get_transport_stats(&self) -> RpcTransportStats;
}