Add some docs for RpcClient and friends (#18748)

* Add some docs for RpcSender, HttpSender, MockSender

* Support SimulateTransaction in MockSender

* Add docs for RpcClient constructors

* Add some more RpcClient examples

* rustfmt

* Reflow docs in rpc_client and friends
This commit is contained in:
Brian Anderson
2021-07-20 13:49:32 -05:00
committed by GitHub
parent 8549c19f1a
commit 5dcfd7ce74
4 changed files with 474 additions and 1 deletions

View File

@@ -1,3 +1,11 @@
//! Communication with a Solana node over RPC.
//!
//! Software that interacts with the Solana blockchain, whether querying its
//! state or submitting transactions, communicates with a Solana node over
//! [JSON-RPC], using the [`RpcClient`] type.
//!
//! [JSON-RPC]: https://www.jsonrpc.org/specification
#[allow(deprecated)]
use crate::rpc_deprecated_config::{
RpcConfirmedBlockConfig, RpcConfirmedTransactionConfig,
@@ -64,6 +72,40 @@ impl RpcClientConfig {
}
}
/// A client of a remote Solana node.
///
/// `RpcClient` communicates with a Solana node over [JSON-RPC], with the
/// [Solana JSON-RPC protocol][jsonprot]. It is the primary Rust interface for
/// querying and transacting with the network from external programs.
///
/// `RpcClient`s generally communicate over HTTP on port 8899, a typical server
/// URL being "http://localhost:8899".
///
/// By default, requests to confirm transactions are only completed once those
/// transactions are finalized, meaning they are definitely permanently
/// committed. Transactions can be confirmed with less finality by creating
/// `RpcClient` with an explicit [`CommitmentConfig`], or by calling the various
/// `_with_commitment` methods, like
/// [`RpcClient::confirm_transaction_with_commitment`].
///
/// Requests may timeout, in which case they return a [`ClientError`] where the
/// [`ClientErrorKind`] is [`ClientErrorKind::Reqwest`], and where the interior
/// [`reqwest::Error`](crate::client_error::reqwest::Error)s
/// [`is_timeout`](crate::client_error::reqwest::Error::is_timeout) method
/// returns `true`. The default timeout is 30 seconds, and may be changed by
/// calling an appropriate constructor with a `timeout` parameter.
///
/// `RpcClient` encapsulates an [`RpcSender`], which implements the underlying
/// RPC protocol. On top of `RpcSender` it adds methods for common tasks, while
/// re-exposing the underlying RPC sending functionality through the
/// [`send`][RpcClient::send] method.
///
/// [jsonprot]: https://docs.solana.com/developing/clients/jsonrpc-api
/// [JSON-RPC]: https://www.jsonrpc.org/specification
///
/// While `RpcClient` encapsulates an abstract `RpcSender`, it is most commonly
/// created with an [`HttpSender`], communicating over HTTP, usually on port
/// 8899. It can also be created with [`MockSender`] during testing.
pub struct RpcClient {
sender: Box<dyn RpcSender + Send + Sync + 'static>,
config: RpcClientConfig,
@@ -71,6 +113,12 @@ pub struct RpcClient {
}
impl RpcClient {
/// Create an `RpcClient` from an [`RpcSender`] and an [`RpcClientConfig`].
///
/// This is the basic constructor, allowing construction with any type of
/// `RpcSender`. Most applications should use one of the other constructors,
/// such as [`new`] and [`new_mock`], which create an `RpcClient`
/// encapsulating an [`HttpSender`] and [`MockSender`] respectively.
fn new_sender<T: RpcSender + Send + Sync + 'static>(
sender: T,
config: RpcClientConfig,
@@ -82,10 +130,42 @@ impl RpcClient {
}
}
/// Create an HTTP `RpcClient`.
///
/// The URL is an HTTP URL, usually for port 8899, as in
/// "http://localhost:8899".
///
/// The client has a default timeout of 30 seconds, and a default commitment
/// level of [`Finalized`](CommitmentLevel::Finalized).
///
/// # Examples
///
/// ```
/// # use solana_client::rpc_client::RpcClient;
/// let url = "http://localhost:8899".to_string();
/// let client = RpcClient::new(url);
/// ```
pub fn new(url: String) -> Self {
Self::new_with_commitment(url, CommitmentConfig::default())
}
/// Create an HTTP `RpcClient` with specified commitment level.
///
/// The URL is an HTTP URL, usually for port 8899, as in
/// "http://localhost:8899".
///
/// The client has a default timeout of 30 seconds, and a user-specified
/// [`CommitmentLevel`] via [`CommitmentConfig`].
///
/// # Examples
///
/// ```
/// # use solana_sdk::commitment_config::CommitmentConfig;
/// # use solana_client::rpc_client::RpcClient;
/// let url = "http://localhost:8899".to_string();
/// let commitment_config = CommitmentConfig::processed();
/// let client = RpcClient::new_with_commitment(url, commitment_config);
/// ```
pub fn new_with_commitment(url: String, commitment_config: CommitmentConfig) -> Self {
Self::new_sender(
HttpSender::new(url),
@@ -93,6 +173,23 @@ impl RpcClient {
)
}
/// Create an HTTP `RpcClient` with specified timeout.
///
/// The URL is an HTTP URL, usually for port 8899, as in
/// "http://localhost:8899".
///
/// The client has and a default commitment level of
/// [`Finalized`](CommitmentLevel::Finalized).
///
/// # Examples
///
/// ```
/// # use std::time::Duration;
/// # use solana_client::rpc_client::RpcClient;
/// let url = "http://localhost::8899".to_string();
/// let timeout = Duration::from_secs(1);
/// let client = RpcClient::new_with_timeout(url, timeout);
/// ```
pub fn new_with_timeout(url: String, timeout: Duration) -> Self {
Self::new_sender(
HttpSender::new_with_timeout(url, timeout),
@@ -100,6 +197,26 @@ impl RpcClient {
)
}
/// Create an HTTP `RpcClient` with specified timeout and commitment level.
///
/// The URL is an HTTP URL, usually for port 8899, as in
/// "http://localhost:8899".
///
/// # Examples
///
/// ```
/// # use std::time::Duration;
/// # use solana_client::rpc_client::RpcClient;
/// # use solana_sdk::commitment_config::CommitmentConfig;
/// let url = "http://localhost::8899".to_string();
/// let timeout = Duration::from_secs(1);
/// let commitment_config = CommitmentConfig::processed();
/// let client = RpcClient::new_with_timeout_and_commitment(
/// url,
/// timeout,
/// commitment_config,
/// );
/// ```
pub fn new_with_timeout_and_commitment(
url: String,
timeout: Duration,
@@ -111,6 +228,36 @@ impl RpcClient {
)
}
/// Create an HTTP `RpcClient` with specified timeout and commitment level.
///
/// The URL is an HTTP URL, usually for port 8899, as in
/// "http://localhost:8899".
///
/// The `confirm_transaction_initial_timeout` argument specifies, when
/// confirming a transaction via one of the `_with_spinner` methods, like
/// [`RpcClient::send_and_confirm_transaction_with_spinner`], the amount of
/// time to allow for the server to initially process a transaction. In
/// other words, setting `confirm_transaction_initial_timeout` to > 0 allows
/// `RpcClient` to wait for confirmation of a transaction that the server
/// has not "seen" yet.
///
/// # Examples
///
/// ```
/// # use std::time::Duration;
/// # use solana_client::rpc_client::RpcClient;
/// # use solana_sdk::commitment_config::CommitmentConfig;
/// let url = "http://localhost::8899".to_string();
/// let timeout = Duration::from_secs(1);
/// let commitment_config = CommitmentConfig::processed();
/// let confirm_transaction_initial_timeout = Duration::from_secs(10);
/// let client = RpcClient::new_with_timeouts_and_commitment(
/// url,
/// timeout,
/// commitment_config,
/// confirm_transaction_initial_timeout,
/// );
/// ```
pub fn new_with_timeouts_and_commitment(
url: String,
timeout: Duration,
@@ -126,6 +273,26 @@ impl RpcClient {
)
}
/// Create a mock `RpcClient`.
///
/// See the [`MockSender`] documentation for an explanation of
/// how it treats the `url` argument.
///
/// # Examples
///
/// ```
/// # use solana_client::rpc_client::RpcClient;
/// // Create an `RpcClient` that always succeeds
/// let url = "succeeds".to_string();
/// let successful_client = RpcClient::new_mock(url);
/// ```
///
/// ```
/// # use solana_client::rpc_client::RpcClient;
/// // Create an `RpcClient` that always fails
/// let url = "fails".to_string();
/// let successful_client = RpcClient::new_mock(url);
/// ```
pub fn new_mock(url: String) -> Self {
Self::new_sender(
MockSender::new(url),
@@ -133,6 +300,34 @@ impl RpcClient {
)
}
/// Create a mock `RpcClient`.
///
/// See the [`MockSender`] documentation for an explanation of how it treats
/// the `url` argument.
///
/// # Examples
///
/// ```
/// # use solana_client::{
/// # rpc_client::RpcClient,
/// # rpc_request::RpcRequest,
/// # };
/// # use std::collections::HashMap;
/// # use serde_json::json;
/// use solana_client::rpc_response::{Response, RpcResponseContext};
///
/// // Create a mock with a custom repsonse to the `GetBalance` request
/// let account_balance = 50;
/// let account_balance_response = json!(Response {
/// context: RpcResponseContext { slot: 1 },
/// value: json!(account_balance),
/// });
///
/// let mut mocks = HashMap::new();
/// mocks.insert(RpcRequest::GetBalance, account_balance_response);
/// let url = "succeeds".to_string();
/// let client = RpcClient::new_mock_with_mocks(url, mocks);
/// ```
pub fn new_mock_with_mocks(url: String, mocks: Mocks) -> Self {
Self::new_sender(
MockSender::new_with_mocks(url, mocks),
@@ -140,10 +335,41 @@ impl RpcClient {
)
}
/// Create an HTTP `RpcClient` from a [`SocketAddr`].
///
/// The client has a default timeout of 30 seconds, and a default commitment
/// level of [`Finalized`](CommitmentLevel::Finalized).
///
/// # Examples
///
/// ```
/// # use std::net::SocketAddr;
/// # use solana_client::rpc_client::RpcClient;
/// let addr = SocketAddr::from(([127, 0, 0, 1], 8899));
/// let client = RpcClient::new_socket(addr);
/// ```
pub fn new_socket(addr: SocketAddr) -> Self {
Self::new(get_rpc_request_str(addr, false))
}
/// Create an HTTP `RpcClient` from a [`SocketAddr`] with specified commitment level.
///
/// The client has a default timeout of 30 seconds, and a user-specified
/// [`CommitmentLevel`] via [`CommitmentConfig`].
///
/// # Examples
///
/// ```
/// # use std::net::SocketAddr;
/// # use solana_client::rpc_client::RpcClient;
/// # use solana_sdk::commitment_config::CommitmentConfig;
/// let addr = SocketAddr::from(([127, 0, 0, 1], 8899));
/// let commitment_config = CommitmentConfig::processed();
/// let client = RpcClient::new_socket_with_commitment(
/// addr,
/// commitment_config
/// );
/// ```
pub fn new_socket_with_commitment(
addr: SocketAddr,
commitment_config: CommitmentConfig,
@@ -151,6 +377,20 @@ impl RpcClient {
Self::new_with_commitment(get_rpc_request_str(addr, false), commitment_config)
}
/// Create an HTTP `RpcClient` from a [`SocketAddr`] with specified timeout.
///
/// The client has and a default commitment level of [`Finalized`](CommitmentLevel::Finalized).
///
/// # Examples
///
/// ```
/// # use std::net::SocketAddr;
/// # use std::time::Duration;
/// # use solana_client::rpc_client::RpcClient;
/// let addr = SocketAddr::from(([127, 0, 0, 1], 8899));
/// let timeout = Duration::from_secs(1);
/// let client = RpcClient::new_socket_with_timeout(addr, timeout);
/// ```
pub fn new_socket_with_timeout(addr: SocketAddr, timeout: Duration) -> Self {
let url = get_rpc_request_str(addr, false);
Self::new_with_timeout(url, timeout)
@@ -215,12 +455,69 @@ impl RpcClient {
Ok(request)
}
/// # Examples
///
/// ```
/// # use solana_client::{
/// # client_error::ClientError,
/// # rpc_client::RpcClient,
/// # rpc_config::RpcSimulateTransactionConfig,
/// # };
/// # use solana_sdk::{
/// # signature::Signature,
/// # signer::keypair::Keypair,
/// # hash::Hash,
/// # system_transaction,
/// # };
/// # let rpc_client = RpcClient::new_mock("succeeds".to_string());
/// // Transfer lamports from some account to a random account
/// let key = Keypair::new();
/// let to = solana_sdk::pubkey::new_rand();
/// let lamports = 50;
/// # let recent_blockhash = Hash::default();
/// let tx = system_transaction::transfer(&key, &to, lamports, recent_blockhash);
/// let signature = rpc_client.send_transaction(&tx)?;
/// let confirmed = rpc_client.confirm_transaction(&signature)?;
/// assert!(confirmed);
/// # Ok::<(), ClientError>(())
/// ```
pub fn confirm_transaction(&self, signature: &Signature) -> ClientResult<bool> {
Ok(self
.confirm_transaction_with_commitment(signature, self.commitment())?
.value)
}
/// # Examples
///
/// ```
/// # use solana_client::{
/// # client_error::ClientError,
/// # rpc_client::RpcClient,
/// # rpc_config::RpcSimulateTransactionConfig,
/// # };
/// # use solana_sdk::{
/// # commitment_config::CommitmentConfig,
/// # signature::Signature,
/// # signer::keypair::Keypair,
/// # hash::Hash,
/// # system_transaction,
/// # };
/// # let rpc_client = RpcClient::new_mock("succeeds".to_string());
/// // Transfer lamports from some account to a random account
/// let key = Keypair::new();
/// let to = solana_sdk::pubkey::new_rand();
/// let lamports = 50;
/// # let recent_blockhash = Hash::default();
/// let tx = system_transaction::transfer(&key, &to, lamports, recent_blockhash);
/// let signature = rpc_client.send_transaction(&tx)?;
/// let commitment_config = CommitmentConfig::confirmed();
/// let confirmed = rpc_client.confirm_transaction_with_commitment(
/// &signature,
/// commitment_config,
/// )?;
/// assert!(confirmed.value);
/// # Ok::<(), ClientError>(())
/// ```
pub fn confirm_transaction_with_commitment(
&self,
signature: &Signature,
@@ -238,6 +535,31 @@ impl RpcClient {
})
}
/// # Examples
///
/// ```
/// # use solana_client::{
/// # client_error::ClientError,
/// # rpc_client::RpcClient,
/// # };
/// # use solana_sdk::{
/// # signature::Signature,
/// # signer::keypair::Keypair,
/// # hash::Hash,
/// # system_transaction,
/// # };
/// # let rpc_client = RpcClient::new_mock("succeeds".to_string());
/// // Transfer lamports from some account to a random account
/// let key = Keypair::new();
/// let to = solana_sdk::pubkey::new_rand();
/// let lamports = 50;
/// # let recent_blockhash = Hash::default();
/// let tx = system_transaction::transfer(&key, &to, lamports, recent_blockhash);
/// let signature = rpc_client.send_transaction(&tx)?;
/// let confirmed = rpc_client.confirm_transaction(&signature)?;
/// assert!(confirmed);
/// # Ok::<(), ClientError>(())
/// ```
pub fn send_transaction(&self, transaction: &Transaction) -> ClientResult<Signature> {
self.send_transaction_with_config(
transaction,
@@ -258,6 +580,39 @@ impl RpcClient {
}
}
/// # Examples
///
/// ```
/// # use solana_client::{
/// # client_error::ClientError,
/// # rpc_client::RpcClient,
/// # rpc_config::RpcSendTransactionConfig,
/// # };
/// # use solana_sdk::{
/// # signature::Signature,
/// # signer::keypair::Keypair,
/// # hash::Hash,
/// # system_transaction,
/// # };
/// # let rpc_client = RpcClient::new_mock("succeeds".to_string());
/// // Transfer lamports from some account to a random account
/// let key = Keypair::new();
/// let to = solana_sdk::pubkey::new_rand();
/// let lamports = 50;
/// # let recent_blockhash = Hash::default();
/// let tx = system_transaction::transfer(&key, &to, lamports, recent_blockhash);
/// let config = RpcSendTransactionConfig {
/// skip_preflight: true,
/// .. RpcSendTransactionConfig::default()
/// };
/// let signature = rpc_client.send_transaction_with_config(
/// &tx,
/// config,
/// )?;
/// let confirmed = rpc_client.confirm_transaction(&signature)?;
/// assert!(confirmed);
/// # Ok::<(), ClientError>(())
/// ```
pub fn send_transaction_with_config(
&self,
transaction: &Transaction,
@@ -325,6 +680,31 @@ impl RpcClient {
}
}
/// # Examples
///
/// ```
/// # use solana_client::{
/// # client_error::ClientError,
/// # rpc_client::RpcClient,
/// # rpc_response::RpcSimulateTransactionResult,
/// # };
/// # use solana_sdk::{
/// # signature::Signature,
/// # signer::keypair::Keypair,
/// # hash::Hash,
/// # system_transaction,
/// # };
/// # let rpc_client = RpcClient::new_mock("succeeds".to_string());
/// // Transfer lamports from some account to a random account
/// let key = Keypair::new();
/// let to = solana_sdk::pubkey::new_rand();
/// let lamports = 50;
/// # let recent_blockhash = Hash::default();
/// let tx = system_transaction::transfer(&key, &to, lamports, recent_blockhash);
/// let result = rpc_client.simulate_transaction(&tx)?;
/// assert!(result.value.err.is_none());
/// # Ok::<(), ClientError>(())
/// ```
pub fn simulate_transaction(
&self,
transaction: &Transaction,
@@ -338,6 +718,39 @@ impl RpcClient {
)
}
/// # Examples
///
/// ```
/// # use solana_client::{
/// # client_error::ClientError,
/// # rpc_client::RpcClient,
/// # rpc_config::RpcSimulateTransactionConfig,
/// # rpc_response::RpcSimulateTransactionResult,
/// # };
/// # use solana_sdk::{
/// # signature::Signature,
/// # signer::keypair::Keypair,
/// # hash::Hash,
/// # system_transaction,
/// # };
/// # let rpc_client = RpcClient::new_mock("succeeds".to_string());
/// // Transfer lamports from some account to a random account
/// let key = Keypair::new();
/// let to = solana_sdk::pubkey::new_rand();
/// let lamports = 50;
/// # let recent_blockhash = Hash::default();
/// let tx = system_transaction::transfer(&key, &to, lamports, recent_blockhash);
/// let config = RpcSimulateTransactionConfig {
/// sig_verify: false,
/// .. RpcSimulateTransactionConfig::default()
/// };
/// let result = rpc_client.simulate_transaction_with_config(
/// &tx,
/// config,
/// )?;
/// assert!(result.value.err.is_none());
/// # Ok::<(), ClientError>(())
/// ```
pub fn simulate_transaction_with_config(
&self,
transaction: &Transaction,